@snowcone-app/canvas 0.1.10 → 0.1.13

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 (48) hide show
  1. package/dist/{CanvasStateV1-BmE5V6me.cjs → CanvasStateV1-C4hC1MCe.cjs} +5 -5
  2. package/dist/{CanvasStateV1-BmE5V6me.cjs.map → CanvasStateV1-C4hC1MCe.cjs.map} +1 -1
  3. package/dist/{CanvasStateV1-CD3Q94F4.js → CanvasStateV1-CJU_xYW5.js} +3 -3
  4. package/dist/{CanvasStateV1-CD3Q94F4.js.map → CanvasStateV1-CJU_xYW5.js.map} +1 -1
  5. package/dist/{HybridHistoryManager-BV6XV0nD.js → HybridHistoryManager-jBBnVim8.js} +54 -54
  6. package/dist/{HybridHistoryManager-BV6XV0nD.js.map → HybridHistoryManager-jBBnVim8.js.map} +1 -1
  7. package/dist/{ElementFactory-Ckv6sSev.js → ImportManager-Oqu2yB54.js} +595 -378
  8. package/dist/ImportManager-Oqu2yB54.js.map +1 -0
  9. package/dist/{ElementFactory-DEjwp-Wg.cjs → ImportManager-W1eWhfyM.cjs} +5 -5
  10. package/dist/ImportManager-W1eWhfyM.cjs.map +1 -0
  11. package/dist/ThemeContext-BMNQKl1c.cjs +2 -0
  12. package/dist/{ThemeContext-4mJ_y0Me.cjs.map → ThemeContext-BMNQKl1c.cjs.map} +1 -1
  13. package/dist/ThemeContext-wj-wSO7J.js +1158 -0
  14. package/dist/{ThemeContext-H0Z-MqqR.js.map → ThemeContext-wj-wSO7J.js.map} +1 -1
  15. package/dist/advanced.js +5 -32
  16. package/dist/advanced.js.map +1 -1
  17. package/dist/advanced.mjs +588 -15069
  18. package/dist/advanced.mjs.map +1 -1
  19. package/dist/components/embed/KitLayout.d.ts +22 -0
  20. package/dist/components/embed/UndoRedoControls.d.ts +3 -0
  21. package/dist/compose-Dqh2f8tS.js +22222 -0
  22. package/dist/compose-Dqh2f8tS.js.map +1 -0
  23. package/dist/compose-HDJp4Z_d.cjs +60 -0
  24. package/dist/compose-HDJp4Z_d.cjs.map +1 -0
  25. package/dist/index.js +1 -1
  26. package/dist/index.js.map +1 -1
  27. package/dist/index.mjs +600 -516
  28. package/dist/index.mjs.map +1 -1
  29. package/dist/internals.js +1 -1
  30. package/dist/internals.js.map +1 -1
  31. package/dist/internals.mjs +101 -102
  32. package/dist/internals.mjs.map +1 -1
  33. package/dist/style.css.d.ts +4 -0
  34. package/dist/testing.js +1 -1
  35. package/dist/testing.mjs +11 -11
  36. package/package.json +8 -4
  37. package/dist/ElementFactory-Ckv6sSev.js.map +0 -1
  38. package/dist/ElementFactory-DEjwp-Wg.cjs.map +0 -1
  39. package/dist/ImportManager-64OYjELO.js +0 -222
  40. package/dist/ImportManager-64OYjELO.js.map +0 -1
  41. package/dist/ImportManager-wSzrR-5a.cjs +0 -2
  42. package/dist/ImportManager-wSzrR-5a.cjs.map +0 -1
  43. package/dist/ThemeContext-4mJ_y0Me.cjs +0 -2
  44. package/dist/ThemeContext-H0Z-MqqR.js +0 -1077
  45. package/dist/compose-DHBRwi_A.cjs +0 -33
  46. package/dist/compose-DHBRwi_A.cjs.map +0 -1
  47. package/dist/compose-DIPiisIw.js +0 -7690
  48. package/dist/compose-DIPiisIw.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"compose-DHBRwi_A.cjs","sources":["../../../node_modules/.pnpm/@iconify+react@6.0.2_react@18.3.1/node_modules/@iconify/react/dist/iconify.js","../src/icons/icon-data.ts","../src/icons/registry.ts","../src/types/capabilities.ts","../src/kits/presets.ts","../src/kits/registry.ts","../src/contexts/KitContext.tsx","../src/utils/ElementPreviewRenderer.ts","../src/hooks/useLayers.ts","../src/hooks/useKeyboardShortcuts.ts","../src/components/CanvasEditor/hooks/useAnimatedFocusRect.ts","../src/utils/textCursorUtils.ts","../src/components/CanvasEditor/hooks/useMarqueeSelection.ts","../src/core/PenToolManager.ts","../src/components/CanvasEditor/hooks/usePenTool.tsx","../src/components/CanvasEditor/hooks/useActiveChild.ts","../src/components/CanvasEditor/hooks/useHoverState.ts","../src/core/CoordinateTransform.ts","../src/core/CropModeController.ts","../src/components/CanvasEditor/hooks/useCropMode.ts","../src/components/CanvasEditor/hooks/useMultiSelection.ts","../src/components/CanvasEditor/hooks/useTextEditing.ts","../src/components/CanvasEditor/hooks/useCanvasLayout.ts","../src/components/CanvasEditor/hooks/useInteractionState.ts","../src/components/CanvasEditor/hooks/useRenderState.ts","../src/rendering/PieceGuideRenderer.ts","../src/components/CanvasEditor/renderers/multiSelectionRenderer.ts","../src/components/CanvasEditor/renderers/marqueeRenderer.ts","../src/components/CanvasEditor/renderers/hoverRenderer.ts","../src/utils/PerformanceMonitor.ts","../src/components/CanvasEditor/hooks/buildSpreadClipShape.ts","../src/components/CanvasEditor/hooks/useCanvasRenderLoop.ts","../src/core/PinchHandler.ts","../src/utils/clickProtection.ts","../src/components/CanvasEditor/handlers/multiSelectionHandlers.ts","../src/components/CanvasEditor/handlers/cropModeHandlers.ts","../src/components/CanvasEditor/handlers/groupChildHandlers.ts","../src/components/CanvasEditor/hooks/useCanvasInteraction.ts","../src/components/CanvasEditor/hooks/useTextEditingHandlers.ts","../src/components/CanvasEditor.tsx","../src/components/embed/Canvas.tsx","../src/hooks/useArtboards.ts","../src/hooks/useExport.ts","../src/services/AutoExportManager.ts","../src/components/embed/LoadingStates.tsx","../src/kits/validation.ts","../src/kits/compose.ts"],"sourcesContent":["'use client';\n\nimport { createElement, forwardRef, useState, useEffect } from 'react';\n\n/**\n* Resolve icon set icons\n*\n* Returns parent icon for each icon\n*/\nfunction getIconsTree(data, names) {\n\tconst icons = data.icons;\n\tconst aliases = data.aliases || Object.create(null);\n\tconst resolved = Object.create(null);\n\tfunction resolve(name) {\n\t\tif (icons[name]) return resolved[name] = [];\n\t\tif (!(name in resolved)) {\n\t\t\tresolved[name] = null;\n\t\t\tconst parent = aliases[name] && aliases[name].parent;\n\t\t\tconst value = parent && resolve(parent);\n\t\t\tif (value) resolved[name] = [parent].concat(value);\n\t\t}\n\t\treturn resolved[name];\n\t}\n\t(Object.keys(icons).concat(Object.keys(aliases))).forEach(resolve);\n\treturn resolved;\n}\n\n/**\n* Default values for dimensions\n*/\nconst defaultIconDimensions = Object.freeze({\n\tleft: 0,\n\ttop: 0,\n\twidth: 16,\n\theight: 16\n});\n/**\n* Default values for transformations\n*/\nconst defaultIconTransformations = Object.freeze({\n\trotate: 0,\n\tvFlip: false,\n\thFlip: false\n});\n/**\n* Default values for all optional IconifyIcon properties\n*/\nconst defaultIconProps = Object.freeze({\n\t...defaultIconDimensions,\n\t...defaultIconTransformations\n});\n/**\n* Default values for all properties used in ExtendedIconifyIcon\n*/\nconst defaultExtendedIconProps = Object.freeze({\n\t...defaultIconProps,\n\tbody: \"\",\n\thidden: false\n});\n\n/**\n* Merge transformations\n*/\nfunction mergeIconTransformations(obj1, obj2) {\n\tconst result = {};\n\tif (!obj1.hFlip !== !obj2.hFlip) result.hFlip = true;\n\tif (!obj1.vFlip !== !obj2.vFlip) result.vFlip = true;\n\tconst rotate = ((obj1.rotate || 0) + (obj2.rotate || 0)) % 4;\n\tif (rotate) result.rotate = rotate;\n\treturn result;\n}\n\n/**\n* Merge icon and alias\n*\n* Can also be used to merge default values and icon\n*/\nfunction mergeIconData(parent, child) {\n\tconst result = mergeIconTransformations(parent, child);\n\tfor (const key in defaultExtendedIconProps) if (key in defaultIconTransformations) {\n\t\tif (key in parent && !(key in result)) result[key] = defaultIconTransformations[key];\n\t} else if (key in child) result[key] = child[key];\n\telse if (key in parent) result[key] = parent[key];\n\treturn result;\n}\n\n/**\n* Get icon data, using prepared aliases tree\n*/\nfunction internalGetIconData(data, name, tree) {\n\tconst icons = data.icons;\n\tconst aliases = data.aliases || Object.create(null);\n\tlet currentProps = {};\n\tfunction parse(name$1) {\n\t\tcurrentProps = mergeIconData(icons[name$1] || aliases[name$1], currentProps);\n\t}\n\tparse(name);\n\ttree.forEach(parse);\n\treturn mergeIconData(data, currentProps);\n}\n\n/**\n* Extract icons from an icon set\n*\n* Returns list of icons that were found in icon set\n*/\nfunction parseIconSet(data, callback) {\n\tconst names = [];\n\tif (typeof data !== \"object\" || typeof data.icons !== \"object\") return names;\n\tif (data.not_found instanceof Array) data.not_found.forEach((name) => {\n\t\tcallback(name, null);\n\t\tnames.push(name);\n\t});\n\tconst tree = getIconsTree(data);\n\tfor (const name in tree) {\n\t\tconst item = tree[name];\n\t\tif (item) {\n\t\t\tcallback(name, internalGetIconData(data, name, item));\n\t\t\tnames.push(name);\n\t\t}\n\t}\n\treturn names;\n}\n\n/**\n* Optional properties\n*/\nconst optionalPropertyDefaults = {\n\tprovider: \"\",\n\taliases: {},\n\tnot_found: {},\n\t...defaultIconDimensions\n};\n/**\n* Check props\n*/\nfunction checkOptionalProps(item, defaults) {\n\tfor (const prop in defaults) if (prop in item && typeof item[prop] !== typeof defaults[prop]) return false;\n\treturn true;\n}\n/**\n* Validate icon set, return it as IconifyJSON on success, null on failure\n*\n* Unlike validateIconSet(), this function is very basic.\n* It does not throw exceptions, it does not check metadata, it does not fix stuff.\n*/\nfunction quicklyValidateIconSet(obj) {\n\tif (typeof obj !== \"object\" || obj === null) return null;\n\tconst data = obj;\n\tif (typeof data.prefix !== \"string\" || !obj.icons || typeof obj.icons !== \"object\") return null;\n\tif (!checkOptionalProps(obj, optionalPropertyDefaults)) return null;\n\tconst icons = data.icons;\n\tfor (const name in icons) {\n\t\tconst icon = icons[name];\n\t\tif (!name || typeof icon.body !== \"string\" || !checkOptionalProps(icon, defaultExtendedIconProps)) return null;\n\t}\n\tconst aliases = data.aliases || Object.create(null);\n\tfor (const name in aliases) {\n\t\tconst icon = aliases[name];\n\t\tconst parent = icon.parent;\n\t\tif (!name || typeof parent !== \"string\" || !icons[parent] && !aliases[parent] || !checkOptionalProps(icon, defaultExtendedIconProps)) return null;\n\t}\n\treturn data;\n}\n\n/**\n* Storage by provider and prefix\n*/\nconst dataStorage = Object.create(null);\n/**\n* Create new storage\n*/\nfunction newStorage(provider, prefix) {\n\treturn {\n\t\tprovider,\n\t\tprefix,\n\t\ticons: Object.create(null),\n\t\tmissing: /* @__PURE__ */ new Set()\n\t};\n}\n/**\n* Get storage for provider and prefix\n*/\nfunction getStorage(provider, prefix) {\n\tconst providerStorage = dataStorage[provider] || (dataStorage[provider] = Object.create(null));\n\treturn providerStorage[prefix] || (providerStorage[prefix] = newStorage(provider, prefix));\n}\n/**\n* Add icon set to storage\n*\n* Returns array of added icons\n*/\nfunction addIconSet(storage, data) {\n\tif (!quicklyValidateIconSet(data)) return [];\n\treturn parseIconSet(data, (name, icon) => {\n\t\tif (icon) storage.icons[name] = icon;\n\t\telse storage.missing.add(name);\n\t});\n}\n/**\n* Add icon to storage\n*/\nfunction addIconToStorage(storage, name, icon) {\n\ttry {\n\t\tif (typeof icon.body === \"string\") {\n\t\t\tstorage.icons[name] = { ...icon };\n\t\t\treturn true;\n\t\t}\n\t} catch (err) {}\n\treturn false;\n}\n/**\n* List available icons\n*/\nfunction listIcons(provider, prefix) {\n\tlet allIcons = [];\n\tconst providers = typeof provider === \"string\" ? [provider] : Object.keys(dataStorage);\n\tproviders.forEach((provider$1) => {\n\t\tconst prefixes = typeof provider$1 === \"string\" && typeof prefix === \"string\" ? [prefix] : Object.keys(dataStorage[provider$1] || {});\n\t\tprefixes.forEach((prefix$1) => {\n\t\t\tconst storage = getStorage(provider$1, prefix$1);\n\t\t\tallIcons = allIcons.concat(Object.keys(storage.icons).map((name) => (provider$1 !== \"\" ? \"@\" + provider$1 + \":\" : \"\") + prefix$1 + \":\" + name));\n\t\t});\n\t});\n\treturn allIcons;\n}\n\n/**\n* Expression to test part of icon name.\n*\n* Used when loading icons from Iconify API due to project naming convension.\n* Ignored when using custom icon sets - convension does not apply.\n*/\nconst matchIconName = /^[a-z0-9]+(-[a-z0-9]+)*$/;\n/**\n* Convert string icon name to IconifyIconName object.\n*/\nconst stringToIcon = (value, validate, allowSimpleName, provider = \"\") => {\n\tconst colonSeparated = value.split(\":\");\n\tif (value.slice(0, 1) === \"@\") {\n\t\tif (colonSeparated.length < 2 || colonSeparated.length > 3) return null;\n\t\tprovider = colonSeparated.shift().slice(1);\n\t}\n\tif (colonSeparated.length > 3 || !colonSeparated.length) return null;\n\tif (colonSeparated.length > 1) {\n\t\tconst name$1 = colonSeparated.pop();\n\t\tconst prefix = colonSeparated.pop();\n\t\tconst result = {\n\t\t\tprovider: colonSeparated.length > 0 ? colonSeparated[0] : provider,\n\t\t\tprefix,\n\t\t\tname: name$1\n\t\t};\n\t\treturn validate && !validateIconName(result) ? null : result;\n\t}\n\tconst name = colonSeparated[0];\n\tconst dashSeparated = name.split(\"-\");\n\tif (dashSeparated.length > 1) {\n\t\tconst result = {\n\t\t\tprovider,\n\t\t\tprefix: dashSeparated.shift(),\n\t\t\tname: dashSeparated.join(\"-\")\n\t\t};\n\t\treturn validate && !validateIconName(result) ? null : result;\n\t}\n\tif (allowSimpleName && provider === \"\") {\n\t\tconst result = {\n\t\t\tprovider,\n\t\t\tprefix: \"\",\n\t\t\tname\n\t\t};\n\t\treturn validate && !validateIconName(result, allowSimpleName) ? null : result;\n\t}\n\treturn null;\n};\n/**\n* Check if icon is valid.\n*\n* This function is not part of stringToIcon because validation is not needed for most code.\n*/\nconst validateIconName = (icon, allowSimpleName) => {\n\tif (!icon) return false;\n\treturn !!((allowSimpleName && icon.prefix === \"\" || !!icon.prefix) && !!icon.name);\n};\n\n/**\n* Allow storing icons without provider or prefix, making it possible to store icons like \"home\"\n*/\nlet simpleNames = false;\nfunction allowSimpleNames(allow) {\n\tif (typeof allow === \"boolean\") simpleNames = allow;\n\treturn simpleNames;\n}\n/**\n* Get icon data\n*\n* Returns:\n* - IconifyIcon on success, object directly from storage so don't modify it\n* - null if icon is marked as missing (returned in `not_found` property from API, so don't bother sending API requests)\n* - undefined if icon is missing in storage\n*/\nfunction getIconData(name) {\n\tconst icon = typeof name === \"string\" ? stringToIcon(name, true, simpleNames) : name;\n\tif (icon) {\n\t\tconst storage = getStorage(icon.provider, icon.prefix);\n\t\tconst iconName = icon.name;\n\t\treturn storage.icons[iconName] || (storage.missing.has(iconName) ? null : void 0);\n\t}\n}\n/**\n* Add one icon\n*/\nfunction addIcon(name, data) {\n\tconst icon = stringToIcon(name, true, simpleNames);\n\tif (!icon) return false;\n\tconst storage = getStorage(icon.provider, icon.prefix);\n\tif (data) return addIconToStorage(storage, icon.name, data);\n\telse {\n\t\tstorage.missing.add(icon.name);\n\t\treturn true;\n\t}\n}\n/**\n* Add icon set\n*/\nfunction addCollection(data, provider) {\n\tif (typeof data !== \"object\") return false;\n\tif (typeof provider !== \"string\") provider = data.provider || \"\";\n\tif (simpleNames && !provider && !data.prefix) {\n\t\tlet added = false;\n\t\tif (quicklyValidateIconSet(data)) {\n\t\t\tdata.prefix = \"\";\n\t\t\tparseIconSet(data, (name, icon) => {\n\t\t\t\tif (addIcon(name, icon)) added = true;\n\t\t\t});\n\t\t}\n\t\treturn added;\n\t}\n\tconst prefix = data.prefix;\n\tif (!validateIconName({\n\t\tprefix,\n\t\tname: \"a\"\n\t})) return false;\n\tconst storage = getStorage(provider, prefix);\n\treturn !!addIconSet(storage, data);\n}\n/**\n* Check if icon data is available\n*/\nfunction iconLoaded(name) {\n\treturn !!getIconData(name);\n}\n/**\n* Get full icon\n*/\nfunction getIcon(name) {\n\tconst result = getIconData(name);\n\treturn result ? {\n\t\t...defaultIconProps,\n\t\t...result\n\t} : result;\n}\n\n/**\n* Default icon customisations values\n*/\nconst defaultIconSizeCustomisations = Object.freeze({\n\twidth: null,\n\theight: null\n});\nconst defaultIconCustomisations = Object.freeze({\n\t...defaultIconSizeCustomisations,\n\t...defaultIconTransformations\n});\n\n/**\n* Regular expressions for calculating dimensions\n*/\nconst unitsSplit = /(-?[0-9.]*[0-9]+[0-9.]*)/g;\nconst unitsTest = /^-?[0-9.]*[0-9]+[0-9.]*$/g;\nfunction calculateSize(size, ratio, precision) {\n\tif (ratio === 1) return size;\n\tprecision = precision || 100;\n\tif (typeof size === \"number\") return Math.ceil(size * ratio * precision) / precision;\n\tif (typeof size !== \"string\") return size;\n\tconst oldParts = size.split(unitsSplit);\n\tif (oldParts === null || !oldParts.length) return size;\n\tconst newParts = [];\n\tlet code = oldParts.shift();\n\tlet isNumber = unitsTest.test(code);\n\twhile (true) {\n\t\tif (isNumber) {\n\t\t\tconst num = parseFloat(code);\n\t\t\tif (isNaN(num)) newParts.push(code);\n\t\t\telse newParts.push(Math.ceil(num * ratio * precision) / precision);\n\t\t} else newParts.push(code);\n\t\tcode = oldParts.shift();\n\t\tif (code === void 0) return newParts.join(\"\");\n\t\tisNumber = !isNumber;\n\t}\n}\n\nfunction splitSVGDefs(content, tag = \"defs\") {\n\tlet defs = \"\";\n\tconst index = content.indexOf(\"<\" + tag);\n\twhile (index >= 0) {\n\t\tconst start = content.indexOf(\">\", index);\n\t\tconst end = content.indexOf(\"</\" + tag);\n\t\tif (start === -1 || end === -1) break;\n\t\tconst endEnd = content.indexOf(\">\", end);\n\t\tif (endEnd === -1) break;\n\t\tdefs += content.slice(start + 1, end).trim();\n\t\tcontent = content.slice(0, index).trim() + content.slice(endEnd + 1);\n\t}\n\treturn {\n\t\tdefs,\n\t\tcontent\n\t};\n}\n/**\n* Merge defs and content\n*/\nfunction mergeDefsAndContent(defs, content) {\n\treturn defs ? \"<defs>\" + defs + \"</defs>\" + content : content;\n}\n/**\n* Wrap SVG content, without wrapping definitions\n*/\nfunction wrapSVGContent(body, start, end) {\n\tconst split = splitSVGDefs(body);\n\treturn mergeDefsAndContent(split.defs, start + split.content + end);\n}\n\n/**\n* Check if value should be unset. Allows multiple keywords\n*/\nconst isUnsetKeyword = (value) => value === \"unset\" || value === \"undefined\" || value === \"none\";\n/**\n* Get SVG attributes and content from icon + customisations\n*\n* Does not generate style to make it compatible with frameworks that use objects for style, such as React.\n* Instead, it generates 'inline' value. If true, rendering engine should add verticalAlign: -0.125em to icon.\n*\n* Customisations should be normalised by platform specific parser.\n* Result should be converted to <svg> by platform specific parser.\n* Use replaceIDs to generate unique IDs for body.\n*/\nfunction iconToSVG(icon, customisations) {\n\tconst fullIcon = {\n\t\t...defaultIconProps,\n\t\t...icon\n\t};\n\tconst fullCustomisations = {\n\t\t...defaultIconCustomisations,\n\t\t...customisations\n\t};\n\tconst box = {\n\t\tleft: fullIcon.left,\n\t\ttop: fullIcon.top,\n\t\twidth: fullIcon.width,\n\t\theight: fullIcon.height\n\t};\n\tlet body = fullIcon.body;\n\t[fullIcon, fullCustomisations].forEach((props) => {\n\t\tconst transformations = [];\n\t\tconst hFlip = props.hFlip;\n\t\tconst vFlip = props.vFlip;\n\t\tlet rotation = props.rotate;\n\t\tif (hFlip) if (vFlip) rotation += 2;\n\t\telse {\n\t\t\ttransformations.push(\"translate(\" + (box.width + box.left).toString() + \" \" + (0 - box.top).toString() + \")\");\n\t\t\ttransformations.push(\"scale(-1 1)\");\n\t\t\tbox.top = box.left = 0;\n\t\t}\n\t\telse if (vFlip) {\n\t\t\ttransformations.push(\"translate(\" + (0 - box.left).toString() + \" \" + (box.height + box.top).toString() + \")\");\n\t\t\ttransformations.push(\"scale(1 -1)\");\n\t\t\tbox.top = box.left = 0;\n\t\t}\n\t\tlet tempValue;\n\t\tif (rotation < 0) rotation -= Math.floor(rotation / 4) * 4;\n\t\trotation = rotation % 4;\n\t\tswitch (rotation) {\n\t\t\tcase 1:\n\t\t\t\ttempValue = box.height / 2 + box.top;\n\t\t\t\ttransformations.unshift(\"rotate(90 \" + tempValue.toString() + \" \" + tempValue.toString() + \")\");\n\t\t\t\tbreak;\n\t\t\tcase 2:\n\t\t\t\ttransformations.unshift(\"rotate(180 \" + (box.width / 2 + box.left).toString() + \" \" + (box.height / 2 + box.top).toString() + \")\");\n\t\t\t\tbreak;\n\t\t\tcase 3:\n\t\t\t\ttempValue = box.width / 2 + box.left;\n\t\t\t\ttransformations.unshift(\"rotate(-90 \" + tempValue.toString() + \" \" + tempValue.toString() + \")\");\n\t\t\t\tbreak;\n\t\t}\n\t\tif (rotation % 2 === 1) {\n\t\t\tif (box.left !== box.top) {\n\t\t\t\ttempValue = box.left;\n\t\t\t\tbox.left = box.top;\n\t\t\t\tbox.top = tempValue;\n\t\t\t}\n\t\t\tif (box.width !== box.height) {\n\t\t\t\ttempValue = box.width;\n\t\t\t\tbox.width = box.height;\n\t\t\t\tbox.height = tempValue;\n\t\t\t}\n\t\t}\n\t\tif (transformations.length) body = wrapSVGContent(body, \"<g transform=\\\"\" + transformations.join(\" \") + \"\\\">\", \"</g>\");\n\t});\n\tconst customisationsWidth = fullCustomisations.width;\n\tconst customisationsHeight = fullCustomisations.height;\n\tconst boxWidth = box.width;\n\tconst boxHeight = box.height;\n\tlet width;\n\tlet height;\n\tif (customisationsWidth === null) {\n\t\theight = customisationsHeight === null ? \"1em\" : customisationsHeight === \"auto\" ? boxHeight : customisationsHeight;\n\t\twidth = calculateSize(height, boxWidth / boxHeight);\n\t} else {\n\t\twidth = customisationsWidth === \"auto\" ? boxWidth : customisationsWidth;\n\t\theight = customisationsHeight === null ? calculateSize(width, boxHeight / boxWidth) : customisationsHeight === \"auto\" ? boxHeight : customisationsHeight;\n\t}\n\tconst attributes = {};\n\tconst setAttr = (prop, value) => {\n\t\tif (!isUnsetKeyword(value)) attributes[prop] = value.toString();\n\t};\n\tsetAttr(\"width\", width);\n\tsetAttr(\"height\", height);\n\tconst viewBox = [\n\t\tbox.left,\n\t\tbox.top,\n\t\tboxWidth,\n\t\tboxHeight\n\t];\n\tattributes.viewBox = viewBox.join(\" \");\n\treturn {\n\t\tattributes,\n\t\tviewBox,\n\t\tbody\n\t};\n}\n\n/**\n* IDs usage:\n*\n* id=\"{id}\"\n* xlink:href=\"#{id}\"\n* url(#{id})\n*\n* From SVG animations:\n*\n* begin=\"0;{id}.end\"\n* begin=\"{id}.end\"\n* begin=\"{id}.click\"\n*/\n/**\n* Regular expression for finding ids\n*/\nconst regex = /\\sid=\"(\\S+)\"/g;\n/**\n* New random-ish prefix for ids\n*\n* Do not use dash, it cannot be used in SVG 2 animations\n*/\nconst randomPrefix = \"IconifyId\" + Date.now().toString(16) + (Math.random() * 16777216 | 0).toString(16);\n/**\n* Counter for ids, increasing with every replacement\n*/\nlet counter = 0;\n/**\n* Replace IDs in SVG output with unique IDs\n*/\nfunction replaceIDs(body, prefix = randomPrefix) {\n\tconst ids = [];\n\tlet match;\n\twhile (match = regex.exec(body)) ids.push(match[1]);\n\tif (!ids.length) return body;\n\tconst suffix = \"suffix\" + (Math.random() * 16777216 | Date.now()).toString(16);\n\tids.forEach((id) => {\n\t\tconst newID = typeof prefix === \"function\" ? prefix(id) : prefix + (counter++).toString();\n\t\tconst escapedID = id.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n\t\tbody = body.replace(new RegExp(\"([#;\\\"])(\" + escapedID + \")([\\\")]|\\\\.[a-z])\", \"g\"), \"$1\" + newID + suffix + \"$3\");\n\t});\n\tbody = body.replace(new RegExp(suffix, \"g\"), \"\");\n\treturn body;\n}\n\n/**\n* Local storate types and entries\n*/\nconst storage = Object.create(null);\n/**\n* Set API module\n*/\nfunction setAPIModule(provider, item) {\n\tstorage[provider] = item;\n}\n/**\n* Get API module\n*/\nfunction getAPIModule(provider) {\n\treturn storage[provider] || storage[\"\"];\n}\n\n/**\n* Create full API configuration from partial data\n*/\nfunction createAPIConfig(source) {\n\tlet resources;\n\tif (typeof source.resources === \"string\") resources = [source.resources];\n\telse {\n\t\tresources = source.resources;\n\t\tif (!(resources instanceof Array) || !resources.length) return null;\n\t}\n\tconst result = {\n\t\tresources,\n\t\tpath: source.path || \"/\",\n\t\tmaxURL: source.maxURL || 500,\n\t\trotate: source.rotate || 750,\n\t\ttimeout: source.timeout || 5e3,\n\t\trandom: source.random === true,\n\t\tindex: source.index || 0,\n\t\tdataAfterTimeout: source.dataAfterTimeout !== false\n\t};\n\treturn result;\n}\n/**\n* Local storage\n*/\nconst configStorage = Object.create(null);\n/**\n* Redundancy for API servers.\n*\n* API should have very high uptime because of implemented redundancy at server level, but\n* sometimes bad things happen. On internet 100% uptime is not possible.\n*\n* There could be routing problems. Server might go down for whatever reason, but it takes\n* few minutes to detect that downtime, so during those few minutes API might not be accessible.\n*\n* This script has some redundancy to mitigate possible network issues.\n*\n* If one host cannot be reached in 'rotate' (750 by default) ms, script will try to retrieve\n* data from different host. Hosts have different configurations, pointing to different\n* API servers hosted at different providers.\n*/\nconst fallBackAPISources = [\"https://api.simplesvg.com\", \"https://api.unisvg.com\"];\nconst fallBackAPI = [];\nwhile (fallBackAPISources.length > 0) if (fallBackAPISources.length === 1) fallBackAPI.push(fallBackAPISources.shift());\nelse if (Math.random() > .5) fallBackAPI.push(fallBackAPISources.shift());\nelse fallBackAPI.push(fallBackAPISources.pop());\nconfigStorage[\"\"] = createAPIConfig({ resources: [\"https://api.iconify.design\"].concat(fallBackAPI) });\n/**\n* Add custom config for provider\n*/\nfunction addAPIProvider(provider, customConfig) {\n\tconst config = createAPIConfig(customConfig);\n\tif (config === null) return false;\n\tconfigStorage[provider] = config;\n\treturn true;\n}\n/**\n* Get API configuration\n*/\nfunction getAPIConfig(provider) {\n\treturn configStorage[provider];\n}\n/**\n* List API providers\n*/\nfunction listAPIProviders() {\n\treturn Object.keys(configStorage);\n}\n\nconst detectFetch = () => {\n\tlet callback;\n\ttry {\n\t\tcallback = fetch;\n\t\tif (typeof callback === \"function\") return callback;\n\t} catch (err) {}\n};\n/**\n* Fetch function\n*/\nlet fetchModule = detectFetch();\n/**\n* Set custom fetch() function\n*/\nfunction setFetch(fetch$1) {\n\tfetchModule = fetch$1;\n}\n/**\n* Get fetch() function. Used by Icon Finder Core\n*/\nfunction getFetch() {\n\treturn fetchModule;\n}\n/**\n* Calculate maximum icons list length for prefix\n*/\nfunction calculateMaxLength(provider, prefix) {\n\tconst config = getAPIConfig(provider);\n\tif (!config) return 0;\n\tlet result;\n\tif (!config.maxURL) result = 0;\n\telse {\n\t\tlet maxHostLength = 0;\n\t\tconfig.resources.forEach((item) => {\n\t\t\tconst host = item;\n\t\t\tmaxHostLength = Math.max(maxHostLength, host.length);\n\t\t});\n\t\tconst url = prefix + \".json?icons=\";\n\t\tresult = config.maxURL - maxHostLength - config.path.length - url.length;\n\t}\n\treturn result;\n}\n/**\n* Should query be aborted, based on last HTTP status\n*/\nfunction shouldAbort(status) {\n\treturn status === 404;\n}\n/**\n* Prepare params\n*/\nconst prepare = (provider, prefix, icons) => {\n\tconst results = [];\n\tconst maxLength = calculateMaxLength(provider, prefix);\n\tconst type = \"icons\";\n\tlet item = {\n\t\ttype,\n\t\tprovider,\n\t\tprefix,\n\t\ticons: []\n\t};\n\tlet length = 0;\n\ticons.forEach((name, index) => {\n\t\tlength += name.length + 1;\n\t\tif (length >= maxLength && index > 0) {\n\t\t\tresults.push(item);\n\t\t\titem = {\n\t\t\t\ttype,\n\t\t\t\tprovider,\n\t\t\t\tprefix,\n\t\t\t\ticons: []\n\t\t\t};\n\t\t\tlength = name.length;\n\t\t}\n\t\titem.icons.push(name);\n\t});\n\tresults.push(item);\n\treturn results;\n};\n/**\n* Get path\n*/\nfunction getPath(provider) {\n\tif (typeof provider === \"string\") {\n\t\tconst config = getAPIConfig(provider);\n\t\tif (config) return config.path;\n\t}\n\treturn \"/\";\n}\n/**\n* Load icons\n*/\nconst send = (host, params, callback) => {\n\tif (!fetchModule) {\n\t\tcallback(\"abort\", 424);\n\t\treturn;\n\t}\n\tlet path = getPath(params.provider);\n\tswitch (params.type) {\n\t\tcase \"icons\": {\n\t\t\tconst prefix = params.prefix;\n\t\t\tconst icons = params.icons;\n\t\t\tconst iconsList = icons.join(\",\");\n\t\t\tconst urlParams = new URLSearchParams({ icons: iconsList });\n\t\t\tpath += prefix + \".json?\" + urlParams.toString();\n\t\t\tbreak;\n\t\t}\n\t\tcase \"custom\": {\n\t\t\tconst uri = params.uri;\n\t\t\tpath += uri.slice(0, 1) === \"/\" ? uri.slice(1) : uri;\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t\tcallback(\"abort\", 400);\n\t\t\treturn;\n\t}\n\tlet defaultError = 503;\n\tfetchModule(host + path).then((response) => {\n\t\tconst status = response.status;\n\t\tif (status !== 200) {\n\t\t\tsetTimeout(() => {\n\t\t\t\tcallback(shouldAbort(status) ? \"abort\" : \"next\", status);\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tdefaultError = 501;\n\t\treturn response.json();\n\t}).then((data) => {\n\t\tif (typeof data !== \"object\" || data === null) {\n\t\t\tsetTimeout(() => {\n\t\t\t\tif (data === 404) callback(\"abort\", data);\n\t\t\t\telse callback(\"next\", defaultError);\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tsetTimeout(() => {\n\t\t\tcallback(\"success\", data);\n\t\t});\n\t}).catch(() => {\n\t\tcallback(\"next\", defaultError);\n\t});\n};\n/**\n* Export module\n*/\nconst fetchAPIModule = {\n\tprepare,\n\tsend\n};\n\n/**\n* Remove callback\n*/\nfunction removeCallback(storages, id) {\n\tstorages.forEach((storage) => {\n\t\tconst items = storage.loaderCallbacks;\n\t\tif (items) storage.loaderCallbacks = items.filter((row) => row.id !== id);\n\t});\n}\n/**\n* Update all callbacks for provider and prefix\n*/\nfunction updateCallbacks(storage) {\n\tif (!storage.pendingCallbacksFlag) {\n\t\tstorage.pendingCallbacksFlag = true;\n\t\tsetTimeout(() => {\n\t\t\tstorage.pendingCallbacksFlag = false;\n\t\t\tconst items = storage.loaderCallbacks ? storage.loaderCallbacks.slice(0) : [];\n\t\t\tif (!items.length) return;\n\t\t\tlet hasPending = false;\n\t\t\tconst provider = storage.provider;\n\t\t\tconst prefix = storage.prefix;\n\t\t\titems.forEach((item) => {\n\t\t\t\tconst icons = item.icons;\n\t\t\t\tconst oldLength = icons.pending.length;\n\t\t\t\ticons.pending = icons.pending.filter((icon) => {\n\t\t\t\t\tif (icon.prefix !== prefix) return true;\n\t\t\t\t\tconst name = icon.name;\n\t\t\t\t\tif (storage.icons[name]) icons.loaded.push({\n\t\t\t\t\t\tprovider,\n\t\t\t\t\t\tprefix,\n\t\t\t\t\t\tname\n\t\t\t\t\t});\n\t\t\t\t\telse if (storage.missing.has(name)) icons.missing.push({\n\t\t\t\t\t\tprovider,\n\t\t\t\t\t\tprefix,\n\t\t\t\t\t\tname\n\t\t\t\t\t});\n\t\t\t\t\telse {\n\t\t\t\t\t\thasPending = true;\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t\treturn false;\n\t\t\t\t});\n\t\t\t\tif (icons.pending.length !== oldLength) {\n\t\t\t\t\tif (!hasPending) removeCallback([storage], item.id);\n\t\t\t\t\titem.callback(icons.loaded.slice(0), icons.missing.slice(0), icons.pending.slice(0), item.abort);\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t}\n}\n/**\n* Unique id counter for callbacks\n*/\nlet idCounter = 0;\n/**\n* Add callback\n*/\nfunction storeCallback(callback, icons, pendingSources) {\n\tconst id = idCounter++;\n\tconst abort = removeCallback.bind(null, pendingSources, id);\n\tif (!icons.pending.length) return abort;\n\tconst item = {\n\t\tid,\n\t\ticons,\n\t\tcallback,\n\t\tabort\n\t};\n\tpendingSources.forEach((storage) => {\n\t\t(storage.loaderCallbacks || (storage.loaderCallbacks = [])).push(item);\n\t});\n\treturn abort;\n}\n\n/**\n* Check if icons have been loaded\n*/\nfunction sortIcons(icons) {\n\tconst result = {\n\t\tloaded: [],\n\t\tmissing: [],\n\t\tpending: []\n\t};\n\tconst storage = Object.create(null);\n\ticons.sort((a, b) => {\n\t\tif (a.provider !== b.provider) return a.provider.localeCompare(b.provider);\n\t\tif (a.prefix !== b.prefix) return a.prefix.localeCompare(b.prefix);\n\t\treturn a.name.localeCompare(b.name);\n\t});\n\tlet lastIcon = {\n\t\tprovider: \"\",\n\t\tprefix: \"\",\n\t\tname: \"\"\n\t};\n\ticons.forEach((icon) => {\n\t\tif (lastIcon.name === icon.name && lastIcon.prefix === icon.prefix && lastIcon.provider === icon.provider) return;\n\t\tlastIcon = icon;\n\t\tconst provider = icon.provider;\n\t\tconst prefix = icon.prefix;\n\t\tconst name = icon.name;\n\t\tconst providerStorage = storage[provider] || (storage[provider] = Object.create(null));\n\t\tconst localStorage = providerStorage[prefix] || (providerStorage[prefix] = getStorage(provider, prefix));\n\t\tlet list;\n\t\tif (name in localStorage.icons) list = result.loaded;\n\t\telse if (prefix === \"\" || localStorage.missing.has(name)) list = result.missing;\n\t\telse list = result.pending;\n\t\tconst item = {\n\t\t\tprovider,\n\t\t\tprefix,\n\t\t\tname\n\t\t};\n\t\tlist.push(item);\n\t});\n\treturn result;\n}\n\n/**\n* Convert icons list from string/icon mix to icons and validate them\n*/\nfunction listToIcons(list, validate = true, simpleNames = false) {\n\tconst result = [];\n\tlist.forEach((item) => {\n\t\tconst icon = typeof item === \"string\" ? stringToIcon(item, validate, simpleNames) : item;\n\t\tif (icon) result.push(icon);\n\t});\n\treturn result;\n}\n\n/**\n* Default RedundancyConfig for API calls\n*/\nconst defaultConfig = {\n\tresources: [],\n\tindex: 0,\n\ttimeout: 2e3,\n\trotate: 750,\n\trandom: false,\n\tdataAfterTimeout: false\n};\n\n/**\n* Send query\n*/\nfunction sendQuery(config, payload, query, done) {\n\tconst resourcesCount = config.resources.length;\n\tconst startIndex = config.random ? Math.floor(Math.random() * resourcesCount) : config.index;\n\tlet resources;\n\tif (config.random) {\n\t\tlet list = config.resources.slice(0);\n\t\tresources = [];\n\t\twhile (list.length > 1) {\n\t\t\tconst nextIndex = Math.floor(Math.random() * list.length);\n\t\t\tresources.push(list[nextIndex]);\n\t\t\tlist = list.slice(0, nextIndex).concat(list.slice(nextIndex + 1));\n\t\t}\n\t\tresources = resources.concat(list);\n\t} else resources = config.resources.slice(startIndex).concat(config.resources.slice(0, startIndex));\n\tconst startTime = Date.now();\n\tlet status = \"pending\";\n\tlet queriesSent = 0;\n\tlet lastError;\n\tlet timer = null;\n\tlet queue = [];\n\tlet doneCallbacks = [];\n\tif (typeof done === \"function\") doneCallbacks.push(done);\n\t/**\n\t* Reset timer\n\t*/\n\tfunction resetTimer() {\n\t\tif (timer) {\n\t\t\tclearTimeout(timer);\n\t\t\ttimer = null;\n\t\t}\n\t}\n\t/**\n\t* Abort everything\n\t*/\n\tfunction abort() {\n\t\tif (status === \"pending\") status = \"aborted\";\n\t\tresetTimer();\n\t\tqueue.forEach((item) => {\n\t\t\tif (item.status === \"pending\") item.status = \"aborted\";\n\t\t});\n\t\tqueue = [];\n\t}\n\t/**\n\t* Add / replace callback to call when execution is complete.\n\t* This can be used to abort pending query implementations when query is complete or aborted.\n\t*/\n\tfunction subscribe(callback, overwrite) {\n\t\tif (overwrite) doneCallbacks = [];\n\t\tif (typeof callback === \"function\") doneCallbacks.push(callback);\n\t}\n\t/**\n\t* Get query status\n\t*/\n\tfunction getQueryStatus() {\n\t\treturn {\n\t\t\tstartTime,\n\t\t\tpayload,\n\t\t\tstatus,\n\t\t\tqueriesSent,\n\t\t\tqueriesPending: queue.length,\n\t\t\tsubscribe,\n\t\t\tabort\n\t\t};\n\t}\n\t/**\n\t* Fail query\n\t*/\n\tfunction failQuery() {\n\t\tstatus = \"failed\";\n\t\tdoneCallbacks.forEach((callback) => {\n\t\t\tcallback(void 0, lastError);\n\t\t});\n\t}\n\t/**\n\t* Clear queue\n\t*/\n\tfunction clearQueue() {\n\t\tqueue.forEach((item) => {\n\t\t\tif (item.status === \"pending\") item.status = \"aborted\";\n\t\t});\n\t\tqueue = [];\n\t}\n\t/**\n\t* Got response from module\n\t*/\n\tfunction moduleResponse(item, response, data) {\n\t\tconst isError = response !== \"success\";\n\t\tqueue = queue.filter((queued) => queued !== item);\n\t\tswitch (status) {\n\t\t\tcase \"pending\": break;\n\t\t\tcase \"failed\":\n\t\t\t\tif (isError || !config.dataAfterTimeout) return;\n\t\t\t\tbreak;\n\t\t\tdefault: return;\n\t\t}\n\t\tif (response === \"abort\") {\n\t\t\tlastError = data;\n\t\t\tfailQuery();\n\t\t\treturn;\n\t\t}\n\t\tif (isError) {\n\t\t\tlastError = data;\n\t\t\tif (!queue.length) if (!resources.length) failQuery();\n\t\t\telse execNext();\n\t\t\treturn;\n\t\t}\n\t\tresetTimer();\n\t\tclearQueue();\n\t\tif (!config.random) {\n\t\t\tconst index = config.resources.indexOf(item.resource);\n\t\t\tif (index !== -1 && index !== config.index) config.index = index;\n\t\t}\n\t\tstatus = \"completed\";\n\t\tdoneCallbacks.forEach((callback) => {\n\t\t\tcallback(data);\n\t\t});\n\t}\n\t/**\n\t* Execute next query\n\t*/\n\tfunction execNext() {\n\t\tif (status !== \"pending\") return;\n\t\tresetTimer();\n\t\tconst resource = resources.shift();\n\t\tif (resource === void 0) {\n\t\t\tif (queue.length) {\n\t\t\t\ttimer = setTimeout(() => {\n\t\t\t\t\tresetTimer();\n\t\t\t\t\tif (status === \"pending\") {\n\t\t\t\t\t\tclearQueue();\n\t\t\t\t\t\tfailQuery();\n\t\t\t\t\t}\n\t\t\t\t}, config.timeout);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tfailQuery();\n\t\t\treturn;\n\t\t}\n\t\tconst item = {\n\t\t\tstatus: \"pending\",\n\t\t\tresource,\n\t\t\tcallback: (status$1, data) => {\n\t\t\t\tmoduleResponse(item, status$1, data);\n\t\t\t}\n\t\t};\n\t\tqueue.push(item);\n\t\tqueriesSent++;\n\t\ttimer = setTimeout(execNext, config.rotate);\n\t\tquery(resource, payload, item.callback);\n\t}\n\tsetTimeout(execNext);\n\treturn getQueryStatus;\n}\n\n/**\n* Redundancy instance\n*/\nfunction initRedundancy(cfg) {\n\tconst config = {\n\t\t...defaultConfig,\n\t\t...cfg\n\t};\n\tlet queries = [];\n\t/**\n\t* Remove aborted and completed queries\n\t*/\n\tfunction cleanup() {\n\t\tqueries = queries.filter((item) => item().status === \"pending\");\n\t}\n\t/**\n\t* Send query\n\t*/\n\tfunction query(payload, queryCallback, doneCallback) {\n\t\tconst query$1 = sendQuery(config, payload, queryCallback, (data, error) => {\n\t\t\tcleanup();\n\t\t\tif (doneCallback) doneCallback(data, error);\n\t\t});\n\t\tqueries.push(query$1);\n\t\treturn query$1;\n\t}\n\t/**\n\t* Find instance\n\t*/\n\tfunction find(callback) {\n\t\treturn queries.find((value) => {\n\t\t\treturn callback(value);\n\t\t}) || null;\n\t}\n\tconst instance = {\n\t\tquery,\n\t\tfind,\n\t\tsetIndex: (index) => {\n\t\t\tconfig.index = index;\n\t\t},\n\t\tgetIndex: () => config.index,\n\t\tcleanup\n\t};\n\treturn instance;\n}\n\nfunction emptyCallback$1() {}\nconst redundancyCache = Object.create(null);\n/**\n* Get Redundancy instance for provider\n*/\nfunction getRedundancyCache(provider) {\n\tif (!redundancyCache[provider]) {\n\t\tconst config = getAPIConfig(provider);\n\t\tif (!config) return;\n\t\tconst redundancy = initRedundancy(config);\n\t\tconst cachedReundancy = {\n\t\t\tconfig,\n\t\t\tredundancy\n\t\t};\n\t\tredundancyCache[provider] = cachedReundancy;\n\t}\n\treturn redundancyCache[provider];\n}\n/**\n* Send API query\n*/\nfunction sendAPIQuery(target, query, callback) {\n\tlet redundancy;\n\tlet send;\n\tif (typeof target === \"string\") {\n\t\tconst api = getAPIModule(target);\n\t\tif (!api) {\n\t\t\tcallback(void 0, 424);\n\t\t\treturn emptyCallback$1;\n\t\t}\n\t\tsend = api.send;\n\t\tconst cached = getRedundancyCache(target);\n\t\tif (cached) redundancy = cached.redundancy;\n\t} else {\n\t\tconst config = createAPIConfig(target);\n\t\tif (config) {\n\t\t\tredundancy = initRedundancy(config);\n\t\t\tconst moduleKey = target.resources ? target.resources[0] : \"\";\n\t\t\tconst api = getAPIModule(moduleKey);\n\t\t\tif (api) send = api.send;\n\t\t}\n\t}\n\tif (!redundancy || !send) {\n\t\tcallback(void 0, 424);\n\t\treturn emptyCallback$1;\n\t}\n\treturn redundancy.query(query, send, callback)().abort;\n}\n\nfunction emptyCallback() {}\n/**\n* Function called when new icons have been loaded\n*/\nfunction loadedNewIcons(storage) {\n\tif (!storage.iconsLoaderFlag) {\n\t\tstorage.iconsLoaderFlag = true;\n\t\tsetTimeout(() => {\n\t\t\tstorage.iconsLoaderFlag = false;\n\t\t\tupdateCallbacks(storage);\n\t\t});\n\t}\n}\n/**\n* Check icon names for API\n*/\nfunction checkIconNamesForAPI(icons) {\n\tconst valid = [];\n\tconst invalid = [];\n\ticons.forEach((name) => {\n\t\t(name.match(matchIconName) ? valid : invalid).push(name);\n\t});\n\treturn {\n\t\tvalid,\n\t\tinvalid\n\t};\n}\n/**\n* Parse loader response\n*/\nfunction parseLoaderResponse(storage, icons, data) {\n\tfunction checkMissing() {\n\t\tconst pending = storage.pendingIcons;\n\t\ticons.forEach((name) => {\n\t\t\tif (pending) pending.delete(name);\n\t\t\tif (!storage.icons[name]) storage.missing.add(name);\n\t\t});\n\t}\n\tif (data && typeof data === \"object\") try {\n\t\tconst parsed = addIconSet(storage, data);\n\t\tif (!parsed.length) {\n\t\t\tcheckMissing();\n\t\t\treturn;\n\t\t}\n\t} catch (err) {\n\t\tconsole.error(err);\n\t}\n\tcheckMissing();\n\tloadedNewIcons(storage);\n}\n/**\n* Handle response that can be async\n*/\nfunction parsePossiblyAsyncResponse(response, callback) {\n\tif (response instanceof Promise) response.then((data) => {\n\t\tcallback(data);\n\t}).catch(() => {\n\t\tcallback(null);\n\t});\n\telse callback(response);\n}\n/**\n* Load icons\n*/\nfunction loadNewIcons(storage, icons) {\n\tif (!storage.iconsToLoad) storage.iconsToLoad = icons;\n\telse storage.iconsToLoad = storage.iconsToLoad.concat(icons).sort();\n\tif (!storage.iconsQueueFlag) {\n\t\tstorage.iconsQueueFlag = true;\n\t\tsetTimeout(() => {\n\t\t\tstorage.iconsQueueFlag = false;\n\t\t\tconst { provider, prefix } = storage;\n\t\t\tconst icons$1 = storage.iconsToLoad;\n\t\t\tdelete storage.iconsToLoad;\n\t\t\tif (!icons$1 || !icons$1.length) return;\n\t\t\tconst customIconLoader = storage.loadIcon;\n\t\t\tif (storage.loadIcons && (icons$1.length > 1 || !customIconLoader)) {\n\t\t\t\tparsePossiblyAsyncResponse(storage.loadIcons(icons$1, prefix, provider), (data) => {\n\t\t\t\t\tparseLoaderResponse(storage, icons$1, data);\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (customIconLoader) {\n\t\t\t\ticons$1.forEach((name) => {\n\t\t\t\t\tconst response = customIconLoader(name, prefix, provider);\n\t\t\t\t\tparsePossiblyAsyncResponse(response, (data) => {\n\t\t\t\t\t\tconst iconSet = data ? {\n\t\t\t\t\t\t\tprefix,\n\t\t\t\t\t\t\ticons: { [name]: data }\n\t\t\t\t\t\t} : null;\n\t\t\t\t\t\tparseLoaderResponse(storage, [name], iconSet);\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst { valid, invalid } = checkIconNamesForAPI(icons$1);\n\t\t\tif (invalid.length) parseLoaderResponse(storage, invalid, null);\n\t\t\tif (!valid.length) return;\n\t\t\tconst api = prefix.match(matchIconName) ? getAPIModule(provider) : null;\n\t\t\tif (!api) {\n\t\t\t\tparseLoaderResponse(storage, valid, null);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst params = api.prepare(provider, prefix, valid);\n\t\t\tparams.forEach((item) => {\n\t\t\t\tsendAPIQuery(provider, item, (data) => {\n\t\t\t\t\tparseLoaderResponse(storage, item.icons, data);\n\t\t\t\t});\n\t\t\t});\n\t\t});\n\t}\n}\n/**\n* Load icons\n*/\nconst loadIcons = (icons, callback) => {\n\tconst cleanedIcons = listToIcons(icons, true, allowSimpleNames());\n\tconst sortedIcons = sortIcons(cleanedIcons);\n\tif (!sortedIcons.pending.length) {\n\t\tlet callCallback = true;\n\t\tif (callback) setTimeout(() => {\n\t\t\tif (callCallback) callback(sortedIcons.loaded, sortedIcons.missing, sortedIcons.pending, emptyCallback);\n\t\t});\n\t\treturn () => {\n\t\t\tcallCallback = false;\n\t\t};\n\t}\n\tconst newIcons = Object.create(null);\n\tconst sources = [];\n\tlet lastProvider, lastPrefix;\n\tsortedIcons.pending.forEach((icon) => {\n\t\tconst { provider, prefix } = icon;\n\t\tif (prefix === lastPrefix && provider === lastProvider) return;\n\t\tlastProvider = provider;\n\t\tlastPrefix = prefix;\n\t\tsources.push(getStorage(provider, prefix));\n\t\tconst providerNewIcons = newIcons[provider] || (newIcons[provider] = Object.create(null));\n\t\tif (!providerNewIcons[prefix]) providerNewIcons[prefix] = [];\n\t});\n\tsortedIcons.pending.forEach((icon) => {\n\t\tconst { provider, prefix, name } = icon;\n\t\tconst storage = getStorage(provider, prefix);\n\t\tconst pendingQueue = storage.pendingIcons || (storage.pendingIcons = /* @__PURE__ */ new Set());\n\t\tif (!pendingQueue.has(name)) {\n\t\t\tpendingQueue.add(name);\n\t\t\tnewIcons[provider][prefix].push(name);\n\t\t}\n\t});\n\tsources.forEach((storage) => {\n\t\tconst list = newIcons[storage.provider][storage.prefix];\n\t\tif (list.length) loadNewIcons(storage, list);\n\t});\n\treturn callback ? storeCallback(callback, sortedIcons, sources) : emptyCallback;\n};\n/**\n* Load one icon using Promise\n*/\nconst loadIcon = (icon) => {\n\treturn new Promise((fulfill, reject) => {\n\t\tconst iconObj = typeof icon === \"string\" ? stringToIcon(icon, true) : icon;\n\t\tif (!iconObj) {\n\t\t\treject(icon);\n\t\t\treturn;\n\t\t}\n\t\tloadIcons([iconObj || icon], (loaded) => {\n\t\t\tif (loaded.length && iconObj) {\n\t\t\t\tconst data = getIconData(iconObj);\n\t\t\t\tif (data) {\n\t\t\t\t\tfulfill({\n\t\t\t\t\t\t...defaultIconProps,\n\t\t\t\t\t\t...data\n\t\t\t\t\t});\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\treject(icon);\n\t\t});\n\t});\n};\n\n/**\n* Set custom loader for multiple icons\n*/\nfunction setCustomIconsLoader(loader, prefix, provider) {\n\tgetStorage(provider || \"\", prefix).loadIcons = loader;\n}\n/**\n* Set custom loader for one icon\n*/\nfunction setCustomIconLoader(loader, prefix, provider) {\n\tgetStorage(provider || \"\", prefix).loadIcon = loader;\n}\n\n/**\n* Convert IconifyIconCustomisations to FullIconCustomisations, checking value types\n*/\nfunction mergeCustomisations(defaults, item) {\n\tconst result = { ...defaults };\n\tfor (const key in item) {\n\t\tconst value = item[key];\n\t\tconst valueType = typeof value;\n\t\tif (key in defaultIconSizeCustomisations) {\n\t\t\tif (value === null || value && (valueType === \"string\" || valueType === \"number\")) result[key] = value;\n\t\t} else if (valueType === typeof result[key]) result[key] = key === \"rotate\" ? value % 4 : value;\n\t}\n\treturn result;\n}\n\nconst separator = /[\\s,]+/;\n/**\n* Apply \"flip\" string to icon customisations\n*/\nfunction flipFromString(custom, flip) {\n\tflip.split(separator).forEach((str) => {\n\t\tconst value = str.trim();\n\t\tswitch (value) {\n\t\t\tcase \"horizontal\":\n\t\t\t\tcustom.hFlip = true;\n\t\t\t\tbreak;\n\t\t\tcase \"vertical\":\n\t\t\t\tcustom.vFlip = true;\n\t\t\t\tbreak;\n\t\t}\n\t});\n}\n\n/**\n* Get rotation value\n*/\nfunction rotateFromString(value, defaultValue = 0) {\n\tconst units = value.replace(/^-?[0-9.]*/, \"\");\n\tfunction cleanup(value$1) {\n\t\twhile (value$1 < 0) value$1 += 4;\n\t\treturn value$1 % 4;\n\t}\n\tif (units === \"\") {\n\t\tconst num = parseInt(value);\n\t\treturn isNaN(num) ? 0 : cleanup(num);\n\t} else if (units !== value) {\n\t\tlet split = 0;\n\t\tswitch (units) {\n\t\t\tcase \"%\":\n\t\t\t\tsplit = 25;\n\t\t\t\tbreak;\n\t\t\tcase \"deg\": split = 90;\n\t\t}\n\t\tif (split) {\n\t\t\tlet num = parseFloat(value.slice(0, value.length - units.length));\n\t\t\tif (isNaN(num)) return 0;\n\t\t\tnum = num / split;\n\t\t\treturn num % 1 === 0 ? cleanup(num) : 0;\n\t\t}\n\t}\n\treturn defaultValue;\n}\n\n/**\n* Generate <svg>\n*/\nfunction iconToHTML(body, attributes) {\n\tlet renderAttribsHTML = body.indexOf(\"xlink:\") === -1 ? \"\" : \" xmlns:xlink=\\\"http://www.w3.org/1999/xlink\\\"\";\n\tfor (const attr in attributes) renderAttribsHTML += \" \" + attr + \"=\\\"\" + attributes[attr] + \"\\\"\";\n\treturn \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\"\" + renderAttribsHTML + \">\" + body + \"</svg>\";\n}\n\n/**\n* Encode SVG for use in url()\n*\n* Short alternative to encodeURIComponent() that encodes only stuff used in SVG, generating\n* smaller code.\n*/\nfunction encodeSVGforURL(svg) {\n\treturn svg.replace(/\"/g, \"'\").replace(/%/g, \"%25\").replace(/#/g, \"%23\").replace(/</g, \"%3C\").replace(/>/g, \"%3E\").replace(/\\s+/g, \" \");\n}\n/**\n* Generate data: URL from SVG\n*/\nfunction svgToData(svg) {\n\treturn \"data:image/svg+xml,\" + encodeSVGforURL(svg);\n}\n/**\n* Generate url() from SVG\n*/\nfunction svgToURL(svg) {\n\treturn \"url(\\\"\" + svgToData(svg) + \"\\\")\";\n}\n\nlet policy;\n/**\n* Attempt to create policy\n*/\nfunction createPolicy() {\n\ttry {\n\t\tpolicy = window.trustedTypes.createPolicy(\"iconify\", { createHTML: (s) => s });\n\t} catch (err) {\n\t\tpolicy = null;\n\t}\n}\n/**\n* Clean up value for innerHTML assignment\n*\n* This code doesn't actually clean up anything.\n* It is intended be used with Iconify icon data, which has already been validated\n*/\nfunction cleanUpInnerHTML(html) {\n\tif (policy === void 0) createPolicy();\n\treturn policy ? policy.createHTML(html) : html;\n}\n\nconst defaultExtendedIconCustomisations = {\n ...defaultIconCustomisations,\n inline: false,\n};\n\n/**\n * Default SVG attributes\n */\nconst svgDefaults = {\n 'xmlns': 'http://www.w3.org/2000/svg',\n 'xmlnsXlink': 'http://www.w3.org/1999/xlink',\n 'aria-hidden': true,\n 'role': 'img',\n};\n/**\n * Style modes\n */\nconst commonProps = {\n display: 'inline-block',\n};\nconst monotoneProps = {\n backgroundColor: 'currentColor',\n};\nconst coloredProps = {\n backgroundColor: 'transparent',\n};\n// Dynamically add common props to variables above\nconst propsToAdd = {\n Image: 'var(--svg)',\n Repeat: 'no-repeat',\n Size: '100% 100%',\n};\nconst propsToAddTo = {\n WebkitMask: monotoneProps,\n mask: monotoneProps,\n background: coloredProps,\n};\nfor (const prefix in propsToAddTo) {\n const list = propsToAddTo[prefix];\n for (const prop in propsToAdd) {\n list[prefix + prop] = propsToAdd[prop];\n }\n}\n/**\n * Default values for customisations for inline icon\n */\nconst inlineDefaults = {\n ...defaultExtendedIconCustomisations,\n inline: true,\n};\n/**\n * Fix size: add 'px' to numbers\n */\nfunction fixSize(value) {\n return value + (value.match(/^[-0-9.]+$/) ? 'px' : '');\n}\n/**\n * Render icon\n */\nconst render = (\n// Icon must be validated before calling this function\nicon, \n// Partial properties\nprops, \n// Icon name\nname) => {\n // Get default properties\n const defaultProps = props.inline\n ? inlineDefaults\n : defaultExtendedIconCustomisations;\n // Get all customisations\n const customisations = mergeCustomisations(defaultProps, props);\n // Check mode\n const mode = props.mode || 'svg';\n // Create style\n const style = {};\n const customStyle = props.style || {};\n // Create SVG component properties\n const componentProps = {\n ...(mode === 'svg' ? svgDefaults : {}),\n };\n if (name) {\n const iconName = stringToIcon(name, false, true);\n if (iconName) {\n const classNames = ['iconify'];\n const props = [\n 'provider',\n 'prefix',\n ];\n for (const prop of props) {\n if (iconName[prop]) {\n classNames.push('iconify--' + iconName[prop]);\n }\n }\n componentProps.className = classNames.join(' ');\n }\n }\n // Get element properties\n for (let key in props) {\n const value = props[key];\n if (value === void 0) {\n continue;\n }\n switch (key) {\n // Properties to ignore\n case 'icon':\n case 'style':\n case 'children':\n case 'onLoad':\n case 'mode':\n case 'ssr':\n case 'fallback':\n break;\n // Forward ref\n case '_ref':\n componentProps.ref = value;\n break;\n // Merge class names\n case 'className':\n componentProps[key] =\n (componentProps[key] ? componentProps[key] + ' ' : '') +\n value;\n break;\n // Boolean attributes\n case 'inline':\n case 'hFlip':\n case 'vFlip':\n customisations[key] =\n value === true || value === 'true' || value === 1;\n break;\n // Flip as string: 'horizontal,vertical'\n case 'flip':\n if (typeof value === 'string') {\n flipFromString(customisations, value);\n }\n break;\n // Color: copy to style\n case 'color':\n style.color = value;\n break;\n // Rotation as string\n case 'rotate':\n if (typeof value === 'string') {\n customisations[key] = rotateFromString(value);\n }\n else if (typeof value === 'number') {\n customisations[key] = value;\n }\n break;\n // Remove aria-hidden\n case 'ariaHidden':\n case 'aria-hidden':\n if (value !== true && value !== 'true') {\n delete componentProps['aria-hidden'];\n }\n break;\n // Copy missing property if it does not exist in customisations\n default:\n if (defaultProps[key] === void 0) {\n componentProps[key] = value;\n }\n }\n }\n // Generate icon\n const item = iconToSVG(icon, customisations);\n const renderAttribs = item.attributes;\n // Inline display\n if (customisations.inline) {\n style.verticalAlign = '-0.125em';\n }\n if (mode === 'svg') {\n // Add style\n componentProps.style = {\n ...style,\n ...customStyle,\n };\n // Add icon stuff\n Object.assign(componentProps, renderAttribs);\n // Counter for ids based on \"id\" property to render icons consistently on server and client\n let localCounter = 0;\n let id = props.id;\n if (typeof id === 'string') {\n // Convert '-' to '_' to avoid errors in animations\n id = id.replace(/-/g, '_');\n }\n // Add icon stuff\n componentProps.dangerouslySetInnerHTML = {\n __html: cleanUpInnerHTML(replaceIDs(item.body, id ? () => id + 'ID' + localCounter++ : 'iconifyReact')),\n };\n return createElement('svg', componentProps);\n }\n // Render <span> with style\n const { body, width, height } = icon;\n const useMask = mode === 'mask' ||\n (mode === 'bg' ? false : body.indexOf('currentColor') !== -1);\n // Generate SVG\n const html = iconToHTML(body, {\n ...renderAttribs,\n width: width + '',\n height: height + '',\n });\n // Generate style\n componentProps.style = {\n ...style,\n '--svg': svgToURL(html),\n 'width': fixSize(renderAttribs.width),\n 'height': fixSize(renderAttribs.height),\n ...commonProps,\n ...(useMask ? monotoneProps : coloredProps),\n ...customStyle,\n };\n return createElement('span', componentProps);\n};\n\n/**\n * Initialise stuff\n */\n// Enable short names\nallowSimpleNames(true);\n// Set API module\nsetAPIModule('', fetchAPIModule);\n/**\n * Browser stuff\n */\nif (typeof document !== 'undefined' && typeof window !== 'undefined') {\n const _window = window;\n // Load icons from global \"IconifyPreload\"\n if (_window.IconifyPreload !== void 0) {\n const preload = _window.IconifyPreload;\n const err = 'Invalid IconifyPreload syntax.';\n if (typeof preload === 'object' && preload !== null) {\n (preload instanceof Array ? preload : [preload]).forEach((item) => {\n try {\n if (\n // Check if item is an object and not null/array\n typeof item !== 'object' ||\n item === null ||\n item instanceof Array ||\n // Check for 'icons' and 'prefix'\n typeof item.icons !== 'object' ||\n typeof item.prefix !== 'string' ||\n // Add icon set\n !addCollection(item)) {\n console.error(err);\n }\n }\n catch (e) {\n console.error(err);\n }\n });\n }\n }\n // Set API from global \"IconifyProviders\"\n if (_window.IconifyProviders !== void 0) {\n const providers = _window.IconifyProviders;\n if (typeof providers === 'object' && providers !== null) {\n for (let key in providers) {\n const err = 'IconifyProviders[' + key + '] is invalid.';\n try {\n const value = providers[key];\n if (typeof value !== 'object' ||\n !value ||\n value.resources === void 0) {\n continue;\n }\n if (!addAPIProvider(key, value)) {\n console.error(err);\n }\n }\n catch (e) {\n console.error(err);\n }\n }\n }\n }\n}\nfunction IconComponent(props) {\n const [mounted, setMounted] = useState(!!props.ssr);\n const [abort, setAbort] = useState({});\n // Get initial state\n function getInitialState(mounted) {\n if (mounted) {\n const name = props.icon;\n if (typeof name === 'object') {\n // Icon as object\n return {\n name: '',\n data: name,\n };\n }\n const data = getIconData(name);\n if (data) {\n return {\n name,\n data,\n };\n }\n }\n return {\n name: '',\n };\n }\n const [state, setState] = useState(getInitialState(!!props.ssr));\n // Cancel loading\n function cleanup() {\n const callback = abort.callback;\n if (callback) {\n callback();\n setAbort({});\n }\n }\n // Change state if it is different\n function changeState(newState) {\n if (JSON.stringify(state) !== JSON.stringify(newState)) {\n cleanup();\n setState(newState);\n return true;\n }\n }\n // Update state\n function updateState() {\n var _a;\n const name = props.icon;\n if (typeof name === 'object') {\n // Icon as object\n changeState({\n name: '',\n data: name,\n });\n return;\n }\n // New icon or got icon data\n const data = getIconData(name);\n if (changeState({\n name,\n data,\n })) {\n if (data === undefined) {\n // Load icon, update state when done\n const callback = loadIcons([name], updateState);\n setAbort({\n callback,\n });\n }\n else if (data) {\n // Icon data is available: trigger onLoad callback if present\n (_a = props.onLoad) === null || _a === void 0 ? void 0 : _a.call(props, name);\n }\n }\n }\n // Mounted state, cleanup for loader\n useEffect(() => {\n setMounted(true);\n return cleanup;\n }, []);\n // Icon changed or component mounted\n useEffect(() => {\n if (mounted) {\n updateState();\n }\n }, [props.icon, mounted]);\n // Render icon\n const { name, data } = state;\n if (!data) {\n return props.children\n ? props.children\n : props.fallback\n ? props.fallback\n : createElement('span', {});\n }\n return render({\n ...defaultIconProps,\n ...data,\n }, props, name);\n}\n/**\n * Block icon\n *\n * @param props - Component properties\n */\nconst Icon = forwardRef((props, ref) => IconComponent({\n ...props,\n _ref: ref,\n}));\n/**\n * Inline icon (has negative verticalAlign that makes it behave like icon font)\n *\n * @param props - Component properties\n */\nconst InlineIcon = forwardRef((props, ref) => IconComponent({\n inline: true,\n ...props,\n _ref: ref,\n}));\n/**\n * Internal API\n */\nconst _api = {\n getAPIConfig,\n setAPIModule,\n sendAPIQuery,\n setFetch,\n getFetch,\n listAPIProviders,\n};\n\nexport { Icon, InlineIcon, _api, addAPIProvider, addCollection, addIcon, iconToSVG as buildIcon, calculateSize, getIcon, iconLoaded, listIcons, loadIcon, loadIcons, replaceIDs, setCustomIconLoader, setCustomIconsLoader };\n","// AUTO-GENERATED FILE - DO NOT EDIT\n// Generated by: pnpm generate:icons\n// Icons included: 85\n// Approximate size: 31.7 KB\n//\n// To regenerate this file after adding new icons:\n// pnpm generate:icons\n\nimport type { IconifyIcon } from '@iconify/react';\n\nexport const canvasIcons: Record<string, IconifyIcon> = {\n \"eos-icons:loading\": {\n \"body\": \"<path fill=\\\"currentColor\\\" d=\\\"M12 2A10 10 0 1 0 22 12A10 10 0 0 0 12 2Zm0 18a8 8 0 1 1 8-8A8 8 0 0 1 12 20Z\\\" opacity=\\\".5\\\"/><path fill=\\\"currentColor\\\" d=\\\"M20 12h2A10 10 0 0 0 12 2V4A8 8 0 0 1 20 12Z\\\"><animateTransform attributeName=\\\"transform\\\" dur=\\\"1s\\\" from=\\\"0 12 12\\\" repeatCount=\\\"indefinite\\\" to=\\\"360 12 12\\\" type=\\\"rotate\\\"/></path>\",\n \"width\": 24,\n \"height\": 24\n },\n \"fa6-solid:icons\": {\n \"body\": \"<path fill=\\\"currentColor\\\" d=\\\"M500.3 7.3c7.4 6 11.7 15.1 11.7 24.7v144c0 26.5-28.7 48-64 48s-64-21.5-64-48s28.7-48 64-48V71l-96 19.2V208c0 26.5-28.7 48-64 48s-64-21.5-64-48s28.7-48 64-48V64c0-15.3 10.8-28.4 25.7-31.4l160-32c9.4-1.9 19.1.6 26.6 6.6zM74.7 304l11.8-17.8c5.9-8.9 15.9-14.2 26.6-14.2h61.7c10.7 0 20.7 5.3 26.6 14.2l11.9 17.8H240c26.5 0 48 21.5 48 48v112c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V352c0-26.5 21.5-48 48-48zM192 408a48 48 0 1 0-96 0a48 48 0 1 0 96 0m286.7-129.7L440.3 368H496c6.7 0 12.6 4.1 15 10.4s.6 13.3-4.4 17.7l-128 112c-5.6 4.9-13.9 5.3-19.9.9s-8.2-12.4-5.3-19.2l38.3-89.8H336c-6.7 0-12.6-4.1-15-10.4s-.6-13.3 4.4-17.7l128-112c5.6-4.9 13.9-5.3 19.9-.9s8.2 12.4 5.3 19.2zm-339-59.2c-6.5 6.5-17 6.5-23 0l-96.8-99.9c-28-29-26.5-76.9 5-103.9c27-23.5 68.4-19 93.4 6.5l10 10.5l9.5-10.5c25-25.5 65.9-30 93.9-6.5c31 27 32.5 74.9 4.5 103.9l-96.4 99.9z\\\"/>\",\n \"width\": 512,\n \"height\": 512\n },\n \"gravity-ui:arrow-down-to-square\": {\n \"body\": \"<path fill=\\\"currentColor\\\" fill-rule=\\\"evenodd\\\" d=\\\"M8.53 11.78a.75.75 0 0 1-1.06 0l-2.5-2.5a.75.75 0 0 1 1.06-1.06l1.22 1.22V1.75a.75.75 0 0 1 1.5 0v7.69l1.22-1.22a.75.75 0 1 1 1.06 1.06zM4.25 4a.75.75 0 1 1 0 1.5H4A1.5 1.5 0 0 0 2.5 7v5A1.5 1.5 0 0 0 4 13.5h8a1.5 1.5 0 0 0 1.5-1.5V7A1.5 1.5 0 0 0 12 5.5h-.25a.75.75 0 0 1 0-1.5H12a3 3 0 0 1 3 3v5a3 3 0 0 1-3 3H4a3 3 0 0 1-3-3V7a3 3 0 0 1 3-3z\\\" clip-rule=\\\"evenodd\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"gravity-ui:arrow-left\": {\n \"body\": \"<path fill=\\\"currentColor\\\" fill-rule=\\\"evenodd\\\" d=\\\"M14.75 8a.75.75 0 0 1-.75.75H3.81l2.72 2.72a.75.75 0 1 1-1.06 1.06l-4-4a.75.75 0 0 1 0-1.06l4-4a.75.75 0 0 1 1.06 1.06L3.81 7.25H14a.75.75 0 0 1 .75.75\\\" clip-rule=\\\"evenodd\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"gravity-ui:arrow-rotate-left\": {\n \"body\": \"<path fill=\\\"currentColor\\\" fill-rule=\\\"evenodd\\\" d=\\\"M8 1.5a6.5 6.5 0 1 1-6.445 7.348a.75.75 0 1 1 1.487-.194A5.001 5.001 0 1 0 4.43 4.5h1.32a.75.75 0 0 1 0 1.5h-3A.75.75 0 0 1 2 5.25v-3a.75.75 0 0 1 1.5 0v1.06A6.48 6.48 0 0 1 8 1.5\\\" clip-rule=\\\"evenodd\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"gravity-ui:bars\": {\n \"body\": \"<path fill=\\\"currentColor\\\" fill-rule=\\\"evenodd\\\" d=\\\"M1.25 3.25A.75.75 0 0 1 2 2.5h12A.75.75 0 0 1 14 4H2a.75.75 0 0 1-.75-.75m0 4.75A.75.75 0 0 1 2 7.25h12a.75.75 0 0 1 0 1.5H2A.75.75 0 0 1 1.25 8M2 12a.75.75 0 0 0 0 1.5h12a.75.75 0 0 0 0-1.5z\\\" clip-rule=\\\"evenodd\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"gravity-ui:bold\": {\n \"body\": \"<path fill=\\\"currentColor\\\" fill-rule=\\\"evenodd\\\" d=\\\"M4.25 2.25A.75.75 0 0 0 3.5 3v10c0 .414.336.75.75.75H9.5a3.25 3.25 0 0 0 1.477-6.146A3.25 3.25 0 0 0 8.5 2.25zm3.5 5a1.75 1.75 0 1 0 0-3.5h-2v3.5zm-2 1.5v3.5h3a1.75 1.75 0 1 0 0-3.5z\\\" clip-rule=\\\"evenodd\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"gravity-ui:copy\": {\n \"body\": \"<path fill=\\\"currentColor\\\" fill-rule=\\\"evenodd\\\" d=\\\"M12 2.5H8A1.5 1.5 0 0 0 6.5 4v1H8a3 3 0 0 1 3 3v1.5h1A1.5 1.5 0 0 0 13.5 8V4A1.5 1.5 0 0 0 12 2.5M11 11h1a3 3 0 0 0 3-3V4a3 3 0 0 0-3-3H8a3 3 0 0 0-3 3v1H4a3 3 0 0 0-3 3v4a3 3 0 0 0 3 3h4a3 3 0 0 0 3-3zM4 6.5h4A1.5 1.5 0 0 1 9.5 8v4A1.5 1.5 0 0 1 8 13.5H4A1.5 1.5 0 0 1 2.5 12V8A1.5 1.5 0 0 1 4 6.5\\\" clip-rule=\\\"evenodd\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"gravity-ui:italic\": {\n \"body\": \"<path fill=\\\"currentColor\\\" fill-rule=\\\"evenodd\\\" d=\\\"M7.25 2a.75.75 0 0 0 0 1.5h1.317l-2.7 9H4.25a.75.75 0 1 0 0 1.5h4.5a.75.75 0 0 0 0-1.5H7.433l2.7-9h1.617a.75.75 0 0 0 0-1.5z\\\" clip-rule=\\\"evenodd\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"gravity-ui:layers\": {\n \"body\": \"<path fill=\\\"currentColor\\\" fill-rule=\\\"evenodd\\\" d=\\\"m2.789 5.283l4.037-2.02A2.5 2.5 0 0 1 7.944 3h.112c.388 0 .77.09 1.118.264l4.037 2.019a.522.522 0 0 1 0 .934l-4.037 2.02a2.5 2.5 0 0 1-1.118.263h-.112a2.5 2.5 0 0 1-1.118-.264L2.79 6.217a.523.523 0 0 1 0-.934M1 5.75c0-.766.433-1.466 1.118-1.809l4.037-2.019a4 4 0 0 1 1.79-.422h.11a4 4 0 0 1 1.79.422l4.037 2.019a2.023 2.023 0 0 1 0 3.618l-.882.44l.882.442a2.023 2.023 0 0 1 0 3.618l-4.037 2.019a4 4 0 0 1-1.79.422h-.11a4 4 0 0 1-1.79-.422l-4.037-2.02a2.023 2.023 0 0 1 0-3.617L3 8l-.882-.441A2.02 2.02 0 0 1 1 5.75m3.677 3.088l-1.888.945a.523.523 0 0 0 0 .934l4.037 2.019A2.5 2.5 0 0 0 7.944 13h.112a2.5 2.5 0 0 0 1.118-.264l4.037-2.019a.523.523 0 0 0 0-.934l-1.888-.945l-1.478.74a4 4 0 0 1-1.79.422h-.11a4 4 0 0 1-1.79-.422z\\\" clip-rule=\\\"evenodd\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"gravity-ui:magnifier\": {\n \"body\": \"<path fill=\\\"currentColor\\\" fill-rule=\\\"evenodd\\\" d=\\\"M11.5 7a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0m-.82 4.74a6 6 0 1 1 1.06-1.06l2.79 2.79a.75.75 0 1 1-1.06 1.06z\\\" clip-rule=\\\"evenodd\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"gravity-ui:pencil\": {\n \"body\": \"<path fill=\\\"currentColor\\\" fill-rule=\\\"evenodd\\\" d=\\\"M11.423 1A3.577 3.577 0 0 1 15 4.577c0 .27-.108.53-.3.722l-.528.529l-1.971 1.971l-5.059 5.059a3 3 0 0 1-1.533.82l-2.638.528a1 1 0 0 1-1.177-1.177l.528-2.638a3 3 0 0 1 .82-1.533l5.059-5.059l2.5-2.5c.191-.191.451-.299.722-.299m-2.31 4.009l-4.91 4.91a1.5 1.5 0 0 0-.41.766l-.38 1.903l1.902-.38a1.5 1.5 0 0 0 .767-.41l4.91-4.91a2.08 2.08 0 0 0-1.88-1.88m3.098.658a3.6 3.6 0 0 0-1.878-1.879l1.28-1.28c.995.09 1.788.884 1.878 1.88z\\\" clip-rule=\\\"evenodd\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"gravity-ui:picture\": {\n \"body\": \"<path fill=\\\"currentColor\\\" fill-rule=\\\"evenodd\\\" d=\\\"M11.5 3h-7A1.5 1.5 0 0 0 3 4.5v5.027l.962-.7a1.75 1.75 0 0 1 2.079.016l.928.696l2.368-2.03a1.75 1.75 0 0 1 2.325.043L13 8.787V4.5A1.5 1.5 0 0 0 11.5 3m3 7.498V4.5a3 3 0 0 0-3-3h-7a3 3 0 0 0-3 3v7a3 3 0 0 0 3 3h7a3 3 0 0 0 3-3zm-1.5.33l-2.355-2.174a.25.25 0 0 0-.332-.006L7.488 11.07l-.457.392l-.481-.361l-1.41-1.057a.25.25 0 0 0-.296-.002L3 11.381v.119A1.5 1.5 0 0 0 4.5 13h7a1.5 1.5 0 0 0 1.5-1.5zM7.5 6a1.5 1.5 0 1 1-3 0a1.5 1.5 0 0 1 3 0\\\" clip-rule=\\\"evenodd\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"gravity-ui:scissors\": {\n \"body\": \"<path fill=\\\"currentColor\\\" fill-rule=\\\"evenodd\\\" d=\\\"M4.5 6a1.5 1.5 0 1 0 0-3a1.5 1.5 0 0 0 0 3m-3-1.5a3 3 0 0 0 4.524 2.585L6.939 8l-.915.915a3 3 0 1 0 1.06 1.06L8.122 8.94l5.501 3.209a.75.75 0 1 0 .756-1.296L9.488 8l4.89-2.852a.75.75 0 0 0-.756-1.296l-5.5 3.209l-1.037-1.037A3 3 0 1 0 1.5 4.5m3 5.5a1.5 1.5 0 1 1 0 3a1.5 1.5 0 0 1 0-3\\\" clip-rule=\\\"evenodd\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"gravity-ui:sparkles\": {\n \"body\": \"<path fill=\\\"currentColor\\\" d=\\\"M13 10a.75.75 0 0 1 .725.556a2.37 2.37 0 0 0 1.72 1.72a.75.75 0 0 1 0 1.449a2.37 2.37 0 0 0-1.72 1.72a.75.75 0 0 1-1.45 0a2.37 2.37 0 0 0-1.72-1.72a.75.75 0 0 1 0-1.45a2.37 2.37 0 0 0 1.72-1.72l.043-.117A.75.75 0 0 1 13 10M7 0a1.5 1.5 0 0 1 1.48 1.253c.242 1.455.696 2.364 1.3 2.968c.603.603 1.512 1.057 2.967 1.3a1.5 1.5 0 0 1 0 2.958c-1.455.243-2.364.697-2.968 1.3c-.603.604-1.057 1.513-1.3 2.968a1.5 1.5 0 0 1-2.958 0c-.243-1.455-.697-2.364-1.3-2.968c-.604-.603-1.513-1.057-2.968-1.3a1.5 1.5 0 0 1 0-2.958c1.455-.243 2.364-.697 2.968-1.3c.603-.604 1.057-1.513 1.3-2.968l.028-.133A1.5 1.5 0 0 1 7 0m0 1.5C6.45 4.8 4.8 6.45 1.5 7c3.3.55 4.95 2.2 5.5 5.5c.55-3.3 2.2-4.95 5.5-5.5C9.2 6.45 7.55 4.8 7 1.5\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"gravity-ui:text\": {\n \"body\": \"<path fill=\\\"currentColor\\\" fill-rule=\\\"evenodd\\\" d=\\\"M3.279 2.544A.75.75 0 0 1 4 2h8a.75.75 0 0 1 .721.544l.5 1.75a.75.75 0 1 1-1.442.412L11.434 3.5H8.75l-.004 9H9.5a.75.75 0 0 1 0 1.5h-3a.75.75 0 0 1 0-1.5h.746l.004-9H4.566L4.22 4.706a.75.75 0 1 1-1.442-.412z\\\" clip-rule=\\\"evenodd\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"gravity-ui:text-align-center\": {\n \"body\": \"<path fill=\\\"currentColor\\\" fill-rule=\\\"evenodd\\\" d=\\\"M2.75 2a.75.75 0 0 0 0 1.5h10.5a.75.75 0 0 0 0-1.5zm0 7a.75.75 0 0 0 0 1.5h10.5a.75.75 0 0 0 0-1.5zm2 3.5a.75.75 0 0 0 0 1.5h6.5a.75.75 0 0 0 0-1.5zM4 6.25a.75.75 0 0 1 .75-.75h6.5a.75.75 0 0 1 0 1.5h-6.5A.75.75 0 0 1 4 6.25\\\" clip-rule=\\\"evenodd\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"gravity-ui:text-align-justify\": {\n \"body\": \"<path fill=\\\"currentColor\\\" fill-rule=\\\"evenodd\\\" d=\\\"M2.75 2a.75.75 0 0 0 0 1.5h10.5a.75.75 0 0 0 0-1.5zM2 6.25a.75.75 0 0 1 .75-.75h10.5a.75.75 0 0 1 0 1.5H2.75A.75.75 0 0 1 2 6.25M2.75 9a.75.75 0 0 0 0 1.5h10.5a.75.75 0 0 0 0-1.5zm0 3.5a.75.75 0 0 0 0 1.5h10.5a.75.75 0 0 0 0-1.5z\\\" clip-rule=\\\"evenodd\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"gravity-ui:text-align-left\": {\n \"body\": \"<path fill=\\\"currentColor\\\" fill-rule=\\\"evenodd\\\" d=\\\"M2.75 2a.75.75 0 0 0 0 1.5h10.5a.75.75 0 0 0 0-1.5zm0 7a.75.75 0 0 0 0 1.5h10.5a.75.75 0 0 0 0-1.5zm0 3.5a.75.75 0 0 0 0 1.5h6.5a.75.75 0 0 0 0-1.5zM2 6.25a.75.75 0 0 1 .75-.75h6.5a.75.75 0 0 1 0 1.5h-6.5A.75.75 0 0 1 2 6.25\\\" clip-rule=\\\"evenodd\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"gravity-ui:text-align-right\": {\n \"body\": \"<path fill=\\\"currentColor\\\" fill-rule=\\\"evenodd\\\" d=\\\"M2.75 2a.75.75 0 0 0 0 1.5h10.5a.75.75 0 0 0 0-1.5zM6 6.25a.75.75 0 0 1 .75-.75h6.5a.75.75 0 0 1 0 1.5h-6.5A.75.75 0 0 1 6 6.25M2.75 9a.75.75 0 0 0 0 1.5h10.5a.75.75 0 0 0 0-1.5zm4 3.5a.75.75 0 0 0 0 1.5h6.5a.75.75 0 0 0 0-1.5z\\\" clip-rule=\\\"evenodd\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"gravity-ui:underline\": {\n \"body\": \"<path fill=\\\"currentColor\\\" fill-rule=\\\"evenodd\\\" d=\\\"M5 2.75a.75.75 0 0 0-1.5 0V7a4.5 4.5 0 0 0 9 0V2.75a.75.75 0 0 0-1.5 0V7a3 3 0 0 1-6 0zm-.75 9.75a.75.75 0 0 0 0 1.5h7.5a.75.75 0 0 0 0-1.5z\\\" clip-rule=\\\"evenodd\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"healthicons:polygon\": {\n \"body\": \"<path fill=\\\"currentColor\\\" fill-rule=\\\"evenodd\\\" d=\\\"M28 10c0 .507-.094.992-.266 1.438l7.696 5.497a4 4 0 1 1 2.706 7.063l-2.32 10.437A4 4 0 1 1 30.126 39H17.874A4.002 4.002 0 0 1 10 38a4 4 0 0 1 2.184-3.565l-2.32-10.437a4 4 0 1 1 2.706-7.063l7.696-5.497A4 4 0 1 1 28 10m-6.57 3.065l-7.696 5.497a4 4 0 0 1-1.917 5.003l2.319 10.437A4 4 0 0 1 17.874 37h12.252a4 4 0 0 1 3.738-2.998l2.32-10.437a4 4 0 0 1-1.918-5.003l-7.695-5.497A3.98 3.98 0 0 1 24 14a3.98 3.98 0 0 1-2.57-.935\\\" clip-rule=\\\"evenodd\\\"/>\",\n \"width\": 48,\n \"height\": 48\n },\n \"heroicons:swatch\": {\n \"body\": \"<path fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"1.5\\\" d=\\\"M4.098 19.902a3.75 3.75 0 0 0 5.304 0l6.401-6.402M6.75 21A3.75 3.75 0 0 1 3 17.25V4.125C3 3.504 3.504 3 4.125 3h5.25c.621 0 1.125.504 1.125 1.125v4.072M6.75 21a3.75 3.75 0 0 0 3.75-3.75V8.197M6.75 21h13.125c.621 0 1.125-.504 1.125-1.125v-5.25c0-.621-.504-1.125-1.125-1.125h-4.072M10.5 8.197l2.88-2.88a1.124 1.124 0 0 1 1.59 0l3.712 3.713c.44.44.44 1.152 0 1.59l-2.879 2.88M6.75 17.25h.008v.008H6.75z\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"hugeicons:layer-bring-forward\": {\n \"body\": \"<path fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"1.5\\\" d=\\\"m15.89 11.5l3.397 1.56C21.096 13.892 22 14.308 22 15s-.904 1.109-2.713 1.94l-4.893 2.247c-1.18.542-1.77.813-2.394.813s-1.214-.271-2.394-.813l-4.893-2.248C2.904 16.11 2 15.693 2 15s.904-1.109 2.713-1.94L8.11 11.5m3.89-7V15m3-8c-.59-.607-2.16-3-3-3S9.59 6.393 9 7\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"hugeicons:layer-send-backward\": {\n \"body\": \"<path fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"1.5\\\" d=\\\"m15.89 12.5l3.397-1.56C21.096 10.108 22 9.692 22 9s-.904-1.109-2.713-1.94l-4.893-2.247C13.214 4.271 12.624 4 12 4s-1.214.271-2.394.813L4.713 7.061C2.904 7.89 2 8.307 2 9s.904 1.109 2.713 1.94L8.11 12.5m3.89 7V9m3 8c-.59.607-2.16 3-3 3s-2.41-2.393-3-3\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:a-large-small\": {\n \"body\": \"<path fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\" d=\\\"m15 16l2.536-7.328a1.02 1.02 1 0 1 1.928 0L22 16m-6.303-2h5.606M2 16l4.039-9.69a.5.5 0 0 1 .923 0L11 16m-7.696-3h6.392\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:aperture\": {\n \"body\": \"<g fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\"><circle cx=\\\"12\\\" cy=\\\"12\\\" r=\\\"10\\\"/><path d=\\\"m14.31 8l5.74 9.94M9.69 8h11.48M7.38 12l5.74-9.94M9.69 16L3.95 6.06M14.31 16H2.83m13.79-4l-5.74 9.94\\\"/></g>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:arrow-left\": {\n \"body\": \"<path fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\" d=\\\"m12 19l-7-7l7-7m7 7H5\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:arrow-up-from-dot\": {\n \"body\": \"<g fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\"><path d=\\\"m5 9l7-7l7 7m-7 7V2\\\"/><circle cx=\\\"12\\\" cy=\\\"21\\\" r=\\\"1\\\"/></g>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:blend\": {\n \"body\": \"<g fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\"><circle cx=\\\"9\\\" cy=\\\"9\\\" r=\\\"7\\\"/><circle cx=\\\"15\\\" cy=\\\"15\\\" r=\\\"7\\\"/></g>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:case-upper\": {\n \"body\": \"<path fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\" d=\\\"M15 11h4.5a1 1 0 0 1 0 5h-4a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5h3a1 1 0 0 1 0 5M2 16l4.039-9.69a.5.5 0 0 1 .923 0L11 16m-7.696-3h6.392\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:circle\": {\n \"body\": \"<circle cx=\\\"12\\\" cy=\\\"12\\\" r=\\\"10\\\" fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:circle-dashed\": {\n \"body\": \"<path fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\" d=\\\"M10.1 2.182a10 10 0 0 1 3.8 0m0 19.636a10 10 0 0 1-3.8 0m7.509-18.097a10 10 0 0 1 2.69 2.7M2.182 13.9a10 10 0 0 1 0-3.8m18.097 7.509a10 10 0 0 1-2.7 2.69M21.818 10.1a10 10 0 0 1 0 3.8M3.721 6.391a10 10 0 0 1 2.7-2.69m-.03 16.578a10 10 0 0 1-2.69-2.7\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:cloud\": {\n \"body\": \"<path fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\" d=\\\"M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:copy\": {\n \"body\": \"<g fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\"><rect width=\\\"14\\\" height=\\\"14\\\" x=\\\"8\\\" y=\\\"8\\\" rx=\\\"2\\\" ry=\\\"2\\\"/><path d=\\\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\\\"/></g>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:credit-card\": {\n \"body\": \"<g fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\"><rect width=\\\"20\\\" height=\\\"14\\\" x=\\\"2\\\" y=\\\"5\\\" rx=\\\"2\\\"/><path d=\\\"M2 10h20\\\"/></g>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:crop\": {\n \"body\": \"<g fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\"><path d=\\\"M6 2v14a2 2 0 0 0 2 2h14\\\"/><path d=\\\"M18 22V8a2 2 0 0 0-2-2H2\\\"/></g>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:ellipsis-vertical\": {\n \"body\": \"<g fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\"><circle cx=\\\"12\\\" cy=\\\"12\\\" r=\\\"1\\\"/><circle cx=\\\"12\\\" cy=\\\"5\\\" r=\\\"1\\\"/><circle cx=\\\"12\\\" cy=\\\"19\\\" r=\\\"1\\\"/></g>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:flag\": {\n \"body\": \"<path fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\" d=\\\"M4 22V4a1 1 0 0 1 .4-.8A6 6 0 0 1 8 2c3 0 5 2 7.333 2q2 0 3.067-.8A1 1 0 0 1 20 4v10a1 1 0 0 1-.4.8A6 6 0 0 1 16 16c-3 0-5-2-8-2a6 6 0 0 0-4 1.528\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:flip-horizontal-2\": {\n \"body\": \"<path fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\" d=\\\"m3 7l5 5l-5 5zm18 0l-5 5l5 5zm-9 13v2m0-8v2m0-8v2m0-8v2\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:flip-vertical-2\": {\n \"body\": \"<path fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\" d=\\\"m17 3l-5 5l-5-5zm0 18l-5-5l-5 5zM4 12H2m8 0H8m8 0h-2m8 0h-2\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:folder-open\": {\n \"body\": \"<path fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\" d=\\\"m6 14l1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:folder-plus\": {\n \"body\": \"<path fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\" d=\\\"M12 10v6m-3-3h6m5 7a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:grid-2x2\": {\n \"body\": \"<g fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\"><path d=\\\"M12 3v18m-9-9h18\\\"/><rect width=\\\"18\\\" height=\\\"18\\\" x=\\\"3\\\" y=\\\"3\\\" rx=\\\"2\\\"/></g>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:grid-3x3\": {\n \"body\": \"<g fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\"><rect width=\\\"18\\\" height=\\\"18\\\" x=\\\"3\\\" y=\\\"3\\\" rx=\\\"2\\\"/><path d=\\\"M3 9h18M3 15h18M9 3v18m6-18v18\\\"/></g>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:grip-vertical\": {\n \"body\": \"<g fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\"><circle cx=\\\"9\\\" cy=\\\"12\\\" r=\\\"1\\\"/><circle cx=\\\"9\\\" cy=\\\"5\\\" r=\\\"1\\\"/><circle cx=\\\"9\\\" cy=\\\"19\\\" r=\\\"1\\\"/><circle cx=\\\"15\\\" cy=\\\"12\\\" r=\\\"1\\\"/><circle cx=\\\"15\\\" cy=\\\"5\\\" r=\\\"1\\\"/><circle cx=\\\"15\\\" cy=\\\"19\\\" r=\\\"1\\\"/></g>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:hexagon\": {\n \"body\": \"<path fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\" d=\\\"M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:image\": {\n \"body\": \"<g fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\"><rect width=\\\"18\\\" height=\\\"18\\\" x=\\\"3\\\" y=\\\"3\\\" rx=\\\"2\\\" ry=\\\"2\\\"/><circle cx=\\\"9\\\" cy=\\\"9\\\" r=\\\"2\\\"/><path d=\\\"m21 15l-3.086-3.086a2 2 0 0 0-2.828 0L6 21\\\"/></g>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:italic\": {\n \"body\": \"<path fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\" d=\\\"M19 4h-9m4 16H5M15 4L9 20\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:layout-grid\": {\n \"body\": \"<g fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\"><rect width=\\\"7\\\" height=\\\"7\\\" x=\\\"3\\\" y=\\\"3\\\" rx=\\\"1\\\"/><rect width=\\\"7\\\" height=\\\"7\\\" x=\\\"14\\\" y=\\\"3\\\" rx=\\\"1\\\"/><rect width=\\\"7\\\" height=\\\"7\\\" x=\\\"14\\\" y=\\\"14\\\" rx=\\\"1\\\"/><rect width=\\\"7\\\" height=\\\"7\\\" x=\\\"3\\\" y=\\\"14\\\" rx=\\\"1\\\"/></g>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:lightbulb\": {\n \"body\": \"<path fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\" d=\\\"M15 14c.2-1 .7-1.7 1.5-2.5c1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5c.7.7 1.3 1.5 1.5 2.5m0 4h6m-5 4h4\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:loader-circle\": {\n \"body\": \"<path fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\" d=\\\"M21 12a9 9 0 1 1-6.219-8.56\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:minus\": {\n \"body\": \"<path fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\" d=\\\"M5 12h14\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:moon\": {\n \"body\": \"<path fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\" d=\\\"M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:move-vertical\": {\n \"body\": \"<path fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\" d=\\\"M12 2v20m-4-4l4 4l4-4M8 6l4-4l4 4\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:paint-bucket\": {\n \"body\": \"<path fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\" d=\\\"M11 7L6 2m12.992 10H2.041m19.104 6.38A3.34 3.34 0 0 1 20 16.5a3.3 3.3 0 0 1-1.145 1.88c-.575.46-.855 1.02-.855 1.595A2 2 0 0 0 20 22a2 2 0 0 0 2-2.025c0-.58-.285-1.13-.855-1.595M8.5 4.5l2.148-2.148a1.205 1.205 0 0 1 1.704 0l7.296 7.296a1.205 1.205 0 0 1 0 1.704l-7.592 7.592a3.615 3.615 0 0 1-5.112 0l-3.888-3.888a3.615 3.615 0 0 1 0-5.112L5.67 7.33\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:palette\": {\n \"body\": \"<g fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\"><path d=\\\"M12 22a1 1 0 0 1 0-20a10 9 0 0 1 10 9a5 5 0 0 1-5 5h-2.25a1.75 1.75 0 0 0-1.4 2.8l.3.4a1.75 1.75 0 0 1-1.4 2.8z\\\"/><circle cx=\\\"13.5\\\" cy=\\\"6.5\\\" r=\\\".5\\\" fill=\\\"currentColor\\\"/><circle cx=\\\"17.5\\\" cy=\\\"10.5\\\" r=\\\".5\\\" fill=\\\"currentColor\\\"/><circle cx=\\\"6.5\\\" cy=\\\"12.5\\\" r=\\\".5\\\" fill=\\\"currentColor\\\"/><circle cx=\\\"8.5\\\" cy=\\\"7.5\\\" r=\\\".5\\\" fill=\\\"currentColor\\\"/></g>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:pen-line\": {\n \"body\": \"<path fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\" d=\\\"M13 21h8m.174-14.188a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:pen-tool\": {\n \"body\": \"<g fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\"><path d=\\\"M15.707 21.293a1 1 0 0 1-1.414 0l-1.586-1.586a1 1 0 0 1 0-1.414l5.586-5.586a1 1 0 0 1 1.414 0l1.586 1.586a1 1 0 0 1 0 1.414z\\\"/><path d=\\\"m18 13l-1.375-6.874a1 1 0 0 0-.746-.776L3.235 2.028a1 1 0 0 0-1.207 1.207L5.35 15.879a1 1 0 0 0 .776.746L13 18M2.3 2.3l7.286 7.286\\\"/><circle cx=\\\"11\\\" cy=\\\"11\\\" r=\\\"2\\\"/></g>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:redo-2\": {\n \"body\": \"<g fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\"><path d=\\\"m15 14l5-5l-5-5\\\"/><path d=\\\"M20 9H9.5A5.5 5.5 0 0 0 4 14.5A5.5 5.5 0 0 0 9.5 20H13\\\"/></g>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:rotate-ccw\": {\n \"body\": \"<g fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\"><path d=\\\"M3 12a9 9 0 1 0 9-9a9.75 9.75 0 0 0-6.74 2.74L3 8\\\"/><path d=\\\"M3 3v5h5\\\"/></g>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:rotate-cw\": {\n \"body\": \"<g fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\"><path d=\\\"M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8\\\"/><path d=\\\"M21 3v5h-5\\\"/></g>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:scissors\": {\n \"body\": \"<g fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\"><circle cx=\\\"6\\\" cy=\\\"6\\\" r=\\\"3\\\"/><path d=\\\"M8.12 8.12L12 12m8-8L8.12 15.88\\\"/><circle cx=\\\"6\\\" cy=\\\"18\\\" r=\\\"3\\\"/><path d=\\\"M14.8 14.8L20 20\\\"/></g>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:settings\": {\n \"body\": \"<g fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\"><path d=\\\"M9.671 4.136a2.34 2.34 0 0 1 4.659 0a2.34 2.34 0 0 0 3.319 1.915a2.34 2.34 0 0 1 2.33 4.033a2.34 2.34 0 0 0 0 3.831a2.34 2.34 0 0 1-2.33 4.033a2.34 2.34 0 0 0-3.319 1.915a2.34 2.34 0 0 1-4.659 0a2.34 2.34 0 0 0-3.32-1.915a2.34 2.34 0 0 1-2.33-4.033a2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915\\\"/><circle cx=\\\"12\\\" cy=\\\"12\\\" r=\\\"3\\\"/></g>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:shapes\": {\n \"body\": \"<g fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\"><path d=\\\"M8.3 10a.7.7 0 0 1-.626-1.079L11.4 3a.7.7 0 0 1 1.198-.043L16.3 8.9a.7.7 0 0 1-.572 1.1Z\\\"/><rect width=\\\"7\\\" height=\\\"7\\\" x=\\\"3\\\" y=\\\"14\\\" rx=\\\"1\\\"/><circle cx=\\\"17.5\\\" cy=\\\"17.5\\\" r=\\\"3.5\\\"/></g>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:sparkles\": {\n \"body\": \"<g fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\"><path d=\\\"M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594zM20 2v4m2-2h-4\\\"/><circle cx=\\\"4\\\" cy=\\\"20\\\" r=\\\"2\\\"/></g>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:square\": {\n \"body\": \"<rect width=\\\"18\\\" height=\\\"18\\\" x=\\\"3\\\" y=\\\"3\\\" fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\" rx=\\\"2\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:square-round-corner\": {\n \"body\": \"<path fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\" d=\\\"M21 11a8 8 0 0 0-8-8m8 12v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:stamp\": {\n \"body\": \"<path fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\" d=\\\"M14 13V8.5C14 7 15 7 15 5a3 3 0 0 0-6 0c0 2 1 2 1 3.5V13m10 2.5a2.5 2.5 0 0 0-2.5-2.5h-11A2.5 2.5 0 0 0 4 15.5V17a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1zM5 22h14\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:star\": {\n \"body\": \"<path fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\" d=\\\"M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.12 2.12 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.12 2.12 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.12 2.12 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.12 2.12 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.12 2.12 0 0 0 1.597-1.16z\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:sun\": {\n \"body\": \"<g fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\"><circle cx=\\\"12\\\" cy=\\\"12\\\" r=\\\"4\\\"/><path d=\\\"M12 2v2m0 16v2M4.93 4.93l1.41 1.41m11.32 11.32l1.41 1.41M2 12h2m16 0h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41\\\"/></g>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:text-cursor\": {\n \"body\": \"<path fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\" d=\\\"M17 22h-1a4 4 0 0 1-4-4V6a4 4 0 0 1 4-4h1M7 22h1a4 4 0 0 0 4-4M7 2h1a4 4 0 0 1 4 4\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:trash\": {\n \"body\": \"<path fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\" d=\\\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:trending-up\": {\n \"body\": \"<g fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\"><path d=\\\"M16 7h6v6\\\"/><path d=\\\"m22 7l-8.5 8.5l-5-5L2 17\\\"/></g>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:triangle\": {\n \"body\": \"<path fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\" d=\\\"M13.73 4a2 2 0 0 0-3.46 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:type\": {\n \"body\": \"<path fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\" d=\\\"M12 4v16M4 7V5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2M9 20h6\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:undo-2\": {\n \"body\": \"<g fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\"><path d=\\\"M9 14L4 9l5-5\\\"/><path d=\\\"M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11\\\"/></g>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:venetian-mask\": {\n \"body\": \"<g fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\"><path d=\\\"M18 11c-1.5 0-2.5.5-3 2\\\"/><path d=\\\"M4 6a2 2 0 0 0-2 2v4a5 5 0 0 0 5 5a8 8 0 0 1 5 2a8 8 0 0 1 5-2a5 5 0 0 0 5-5V8a2 2 0 0 0-2-2h-3a8 8 0 0 0-5 2a8 8 0 0 0-5-2z\\\"/><path d=\\\"M6 11c1.5 0 2.5.5 3 2\\\"/></g>\",\n \"width\": 24,\n \"height\": 24\n },\n \"lucide:waves\": {\n \"body\": \"<path fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" stroke-width=\\\"2\\\" d=\\\"M2 6c.6.5 1.2 1 2.5 1C7 7 7 5 9.5 5c2.6 0 2.4 2 5 2c2.5 0 2.5-2 5-2c1.3 0 1.9.5 2.5 1M2 12c.6.5 1.2 1 2.5 1c2.5 0 2.5-2 5-2c2.6 0 2.4 2 5 2c2.5 0 2.5-2 5-2c1.3 0 1.9.5 2.5 1M2 18c.6.5 1.2 1 2.5 1c2.5 0 2.5-2 5-2c2.6 0 2.4 2 5 2c2.5 0 2.5-2 5-2c1.3 0 1.9.5 2.5 1\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"material-symbols:award-star-outline\": {\n \"body\": \"<path fill=\\\"currentColor\\\" d=\\\"M9.075 16.25L12 14.475l2.925 1.775l-.775-3.325l2.6-2.25l-3.425-.275L12 7.25l-1.325 3.15l-3.425.275l2.6 2.25zM12 23.3L8.65 20H4v-4.65L.7 12L4 8.65V4h4.65L12 .7L15.35 4H20v4.65L23.3 12L20 15.35V20h-4.65zm0-2.8l2.5-2.5H18v-3.5l2.5-2.5L18 9.5V6h-3.5L12 3.5L9.5 6H6v3.5L3.5 12L6 14.5V18h3.5zm0-8.5\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"mdi:cube-outline\": {\n \"body\": \"<path fill=\\\"currentColor\\\" d=\\\"M21 16.5c0 .38-.21.71-.53.88l-7.9 4.44c-.16.12-.36.18-.57.18s-.41-.06-.57-.18l-7.9-4.44A.99.99 0 0 1 3 16.5v-9c0-.38.21-.71.53-.88l7.9-4.44c.16-.12.36-.18.57-.18s.41.06.57.18l7.9 4.44c.32.17.53.5.53.88zM12 4.15L6.04 7.5L12 10.85l5.96-3.35zM5 15.91l6 3.38v-6.71L5 9.21zm14 0v-6.7l-6 3.37v6.71z\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"mdi:format-color-fill\": {\n \"body\": \"<path fill=\\\"currentColor\\\" d=\\\"M19 11.5s-2 2.17-2 3.5a2 2 0 0 0 2 2a2 2 0 0 0 2-2c0-1.33-2-3.5-2-3.5M5.21 10L10 5.21L14.79 10m1.77-1.06L7.62 0L6.21 1.41l2.38 2.38l-5.15 5.15c-.59.56-.59 1.53 0 2.12l5.5 5.5c.29.29.68.44 1.06.44s.77-.15 1.06-.44l5.5-5.5c.59-.59.59-1.56 0-2.12\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"mdi:image-remove\": {\n \"body\": \"<path fill=\\\"currentColor\\\" d=\\\"M13.3 21H5c-1.1 0-2-.9-2-2V5c0-1.1.9-2 2-2h14c1.1 0 2 .9 2 2v8.3c-.6-.2-1.3-.3-2-.3c-1.1 0-2.2.3-3.1.9L14.5 12L11 16.5l-2.5-3L5 18h8.1c-.1.3-.1.7-.1 1c0 .7.1 1.4.3 2m7.1-2l2.1 2.1l-1.4 1.4l-2.1-2.1l-2.1 2.1l-1.4-1.4l2.1-2.1l-2.1-2.1l1.4-1.4l2.1 2.1l2.1-2.1l1.4 1.4z\\\"/>\",\n \"width\": 24,\n \"height\": 24\n },\n \"ph:monitor-fill\": {\n \"body\": \"<path fill=\\\"currentColor\\\" d=\\\"M232 64v112a24 24 0 0 1-24 24H48a24 24 0 0 1-24-24V64a24 24 0 0 1 24-24h160a24 24 0 0 1 24 24m-72 152H96a8 8 0 0 0 0 16h64a8 8 0 0 0 0-16\\\"/>\",\n \"width\": 256,\n \"height\": 256\n },\n \"radix-icons:font-size\": {\n \"body\": \"<path fill=\\\"currentColor\\\" d=\\\"M2.5 2.1a.4.4 0 0 1 .283.117l2 2l.05.064a.4.4 0 0 1-.05.503a.4.4 0 0 1-.503.05l-.063-.05L2.9 3.467v8.067l1.317-1.317a.4.4 0 0 1 .566.566l-2 2a.4.4 0 0 1-.566 0l-2-2l-.051-.063a.4.4 0 0 1 .554-.554l.063.051l1.316 1.316V3.467L.783 4.784a.401.401 0 0 1-.566-.567l2-2l.061-.05A.4.4 0 0 1 2.5 2.1m5.47 9.64a.501.501 0 0 1-.94-.34zm2.53-8.99a.5.5 0 0 1 .47.33l3 8.32a.5.5 0 0 1-.94.34l-.983-2.724H8.953l-.982 2.724l-.471-.17l-.47-.17l3-8.32l.031-.07a.5.5 0 0 1 .439-.26M9.259 8.166h2.482L10.5 4.725z\\\"/>\",\n \"width\": 15,\n \"height\": 15\n }\n};\n","/**\n * Icon Registry - Registers only the icons actually used in the canvas package\n *\n * This ensures icons work when the canvas package is bundled and used in\n * consuming applications (like Next.js) where CDN icon loading may fail.\n *\n * Icons are auto-generated by scripts/generate-icons.ts which scans the source\n * files and extracts only the SVG data for icons that are actually used.\n * This dramatically reduces bundle size compared to bundling entire icon sets.\n *\n * To regenerate after adding new icons:\n * pnpm generate:icons\n *\n * @see https://iconify.design/docs/icons/json.html\n */\n\nimport { addIcon } from '@iconify/react';\nimport { canvasIcons } from './icon-data.js';\n\n/**\n * Registers all icons used in the canvas package.\n * Call this function once when the application loads.\n *\n * This function registers only the ~100-150 icons actually used,\n * rather than ~20,000+ icons from the full icon sets.\n */\nexport function registerCanvasIcons(): void {\n for (const [name, data] of Object.entries(canvasIcons)) {\n addIcon(name, data);\n }\n}\n\n// Track if icons have been registered\nlet iconsRegistered = false;\n\n/**\n * Ensures icons are registered exactly once.\n * Safe to call multiple times.\n */\nexport function ensureIconsRegistered(): void {\n if (!iconsRegistered) {\n registerCanvasIcons();\n iconsRegistered = true;\n }\n}\n","/**\n * EditorCapabilities - Feature flags for editor components\n *\n * Controls which elements, transforms, effects, panels, and tools\n * are available in a given editor instance. Used by KitDefinition\n * and KitRenderer to configure the editor for different use cases.\n */\n\nimport type { TransformType } from './index.js';\n\n/** Types of elements that can be created in the editor */\nexport type ElementType = 'text' | 'image' | 'shape' | 'path' | 'group';\n\n/** Types of visual effects available */\nexport type EffectType = 'stroke' | 'mask' | 'distress' | 'knockout';\n\n/** Types of UI panels available */\nexport type PanelType = 'layers' | 'effects' | 'export' | 'images' | 'artboards';\n\n/** Types of editing tools available */\nexport type ToolType = 'select' | 'pen' | 'crop' | 'zoom';\n\n/**\n * Defines the full set of capabilities available in an editor instance.\n *\n * Each field controls a specific feature set. Kit presets use these\n * to restrict or expand what the editor can do.\n */\nexport interface EditorCapabilities {\n /** Allowed element types that can be created */\n elements: ElementType[];\n\n /** Allowed transform types for text elements */\n transforms: TransformType[];\n\n /** Allowed visual effect types */\n effects: EffectType[];\n\n /** UI panels that are available */\n panels: PanelType[];\n\n /** Editing tools available in the toolbar */\n tools: ToolType[];\n\n /** Boolean feature flags for editor behavior */\n features: {\n /** Whether exporting artboards is enabled */\n export: boolean;\n /** Whether the user can create and manage multiple artboards */\n multiArtboard: boolean;\n /** Whether rich text (per-character styling) is enabled */\n richText: boolean;\n /** Whether undo/redo is enabled */\n undo: boolean;\n /** Whether keyboard shortcuts are active */\n keyboardShortcuts: boolean;\n };\n}\n\n/**\n * All capabilities enabled -- for the full pro editor.\n * Includes every element type, transform, effect, panel, and tool.\n */\nexport const ALL_CAPABILITIES: EditorCapabilities = {\n elements: ['text', 'image', 'shape', 'path', 'group'],\n transforms: ['custom', 'circle', 'lean', 'arch', 'ascend', 'wave', 'flag', 'image', 'group', 'shape', 'path'],\n effects: ['stroke', 'mask', 'distress', 'knockout'],\n panels: ['layers', 'effects', 'export', 'images', 'artboards'],\n tools: ['select', 'pen', 'crop', 'zoom'],\n features: {\n export: true,\n multiArtboard: true,\n richText: true,\n undo: true,\n keyboardShortcuts: true,\n },\n};\n\n/**\n * Minimal capabilities -- text and image only, single artboard.\n * Suitable for simple product customizers and embedded editors.\n */\nexport const MINIMAL_CAPABILITIES: EditorCapabilities = {\n elements: ['text', 'image'],\n transforms: ['custom', 'circle', 'arch', 'wave', 'image'],\n effects: ['stroke'],\n panels: [],\n tools: ['select', 'zoom'],\n features: {\n export: true,\n multiArtboard: false,\n richText: false,\n undo: true,\n keyboardShortcuts: true,\n },\n};\n","/**\n * Kit Presets - Reference implementations\n *\n * Three preset kits for common use cases:\n * - COMPACT_CUSTOMIZER: Canvas + contextual toolbar, text/image only\n * - PRO_STUDIO: Full panels, all features, multi-artboard\n * - EMBED_ONLY: Canvas only, auto-export, no UI chrome\n */\n\nimport { ALL_CAPABILITIES, MINIMAL_CAPABILITIES } from '../types/capabilities.js';\nimport type { KitDefinition } from './types.js';\n\n/**\n * Compact customizer -- minimal UI for product customization.\n *\n * Includes a contextual toolbar and single-selection mode.\n * Best for e-commerce product customization flows where the user\n * adds text or uploads an image onto a product.\n *\n * @example\n * ```tsx\n * <KitRenderer kit={COMPACT_CUSTOMIZER} />\n * ```\n */\nexport const COMPACT_CUSTOMIZER: KitDefinition = {\n name: 'compact-customizer',\n layout: {\n slots: {\n topbar: ['contextual-toolbar'],\n canvas: ['canvas'],\n },\n },\n capabilities: {\n ...MINIMAL_CAPABILITIES,\n effects: ['stroke'],\n panels: [],\n tools: ['select', 'zoom'],\n },\n behavior: {\n toolbar: 'contextual',\n selection: 'single',\n panelMode: 'drawer',\n onEmptyClick: 'deselect',\n autoExport: false,\n },\n style: {\n density: 'compact',\n toolbarVariant: 'pill',\n panelVariant: 'floating',\n },\n content: {\n sampleText: 'Your Text',\n defaultFontFamily: 'Poppins',\n },\n};\n\n/**\n * Pro studio -- full editor with all panels and features.\n *\n * Includes layers panel, effects panel, multi-selection, and all\n * transform/effect types. Best for design professionals or admin\n * interfaces where full editing power is needed.\n *\n * @example\n * ```tsx\n * <KitRenderer kit={PRO_STUDIO} />\n * ```\n */\nexport const PRO_STUDIO: KitDefinition = {\n name: 'pro-studio',\n layout: {\n slots: {\n topbar: ['add-element-menu', 'undo-redo-controls', 'contextual-toolbar'],\n left: ['layers-panel'],\n canvas: ['canvas'],\n right: ['effects-panel'],\n },\n },\n capabilities: ALL_CAPABILITIES,\n behavior: {\n toolbar: 'contextual',\n selection: 'multi',\n panelMode: 'sidebar',\n onEmptyClick: 'deselect',\n autoExport: false,\n },\n style: {\n density: 'comfortable',\n toolbarVariant: 'flat',\n panelVariant: 'card',\n },\n content: {\n sampleText: 'New Text',\n defaultFontFamily: 'Poppins',\n },\n};\n\n/**\n * Embed only -- canvas with no UI chrome, for embedding in other apps.\n *\n * No toolbar, no panels, auto-export enabled. Best for headless\n * integration where the host app provides all UI and just needs\n * the canvas rendering engine.\n *\n * @example\n * ```tsx\n * <KitRenderer kit={EMBED_ONLY} />\n * ```\n */\nexport const EMBED_ONLY: KitDefinition = {\n name: 'embed-only',\n layout: {\n slots: {\n canvas: ['canvas'],\n },\n },\n capabilities: {\n ...MINIMAL_CAPABILITIES,\n panels: [],\n tools: ['select'],\n features: {\n export: true,\n multiArtboard: false,\n richText: false,\n undo: true,\n keyboardShortcuts: false,\n },\n },\n behavior: {\n toolbar: 'none',\n selection: 'single',\n panelMode: 'inline',\n onEmptyClick: 'noop',\n autoExport: true,\n },\n style: {\n density: 'compact',\n toolbarVariant: 'minimal',\n panelVariant: 'flush',\n },\n};\n","/**\n * Kit Preset Registry\n *\n * Central registry that maps preset IDs to their KitDefinition objects.\n * Provides `resolveKit()` to accept either a preset ID string or a\n * full KitDefinition, making it easy for consumers to use shorthand\n * names while still supporting custom kit definitions.\n */\n\nimport type { KitDefinition, KitPresetId } from './types.js';\nimport { COMPACT_CUSTOMIZER, PRO_STUDIO, EMBED_ONLY } from './presets.js';\n\n/**\n * Map of all registered kit presets, keyed by their preset ID.\n *\n * @example\n * ```ts\n * const kit = KIT_PRESETS.get('compact-customizer');\n * // kit === COMPACT_CUSTOMIZER\n * ```\n */\nexport const KIT_PRESETS: ReadonlyMap<KitPresetId, KitDefinition> = new Map<KitPresetId, KitDefinition>([\n ['compact-customizer', COMPACT_CUSTOMIZER],\n ['pro-studio', PRO_STUDIO],\n ['embed-only', EMBED_ONLY],\n]);\n\n/**\n * Resolve a kit from either a preset ID string or a full KitDefinition.\n *\n * This is the primary entry point for consumers. It allows components\n * like `<KitRenderer>` to accept a `kit` prop that is either a string\n * shorthand or a fully custom definition.\n *\n * @param kit - A preset ID string (e.g., 'compact-customizer') or a full KitDefinition object\n * @returns The resolved KitDefinition\n * @throws {Error} If a string is passed that does not match any registered preset\n *\n * @example\n * ```ts\n * // Using a preset ID\n * const kit = resolveKit('pro-studio');\n *\n * // Using a custom definition (passed through unchanged)\n * const custom: KitDefinition = { name: 'my-kit', ... };\n * const kit = resolveKit(custom);\n * ```\n */\nexport function resolveKit(kit: KitPresetId | KitDefinition): KitDefinition {\n if (typeof kit === 'string') {\n const preset = KIT_PRESETS.get(kit);\n if (!preset) {\n const validIds = Array.from(KIT_PRESETS.keys()).join(', ');\n throw new Error(\n `[resolveKit] Unknown kit preset \"${kit}\". Valid presets: ${validIds}`\n );\n }\n return preset;\n }\n return kit;\n}\n","/**\n * KitContext - Provides the resolved KitDefinition to child components\n *\n * Allows any component in the tree to access the current kit configuration,\n * including capabilities, behavior settings, layout, and style preferences.\n * Supports runtime kit switching via `setKit`.\n *\n * @example\n * ```tsx\n * import { useKit, useCapabilities } from '@snowcone-app/canvas';\n *\n * function MyToolbar() {\n * const capabilities = useCapabilities();\n * if (!capabilities.features.richText) return null;\n * return <RichTextToolbar />;\n * }\n *\n * function KitSwitcher() {\n * const { kit, setKit } = useKit();\n * return (\n * <button onClick={() => setKit('pro-studio')}>\n * Switch to Pro Studio (current: {kit.name})\n * </button>\n * );\n * }\n * ```\n */\n\nimport { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';\nimport type { KitDefinition, KitPresetId } from '../kits/types.js';\nimport { resolveKit } from '../kits/registry.js';\n\n/**\n * The value provided by KitContext, including the current kit\n * and a function to switch kits at runtime.\n */\nexport interface KitContextValue {\n /** The current resolved KitDefinition */\n kit: KitDefinition;\n /** Switch the active kit. Accepts a preset ID string or a full KitDefinition. */\n setKit: (kit: KitPresetId | KitDefinition) => void;\n}\n\nconst KitContext = createContext<KitContextValue | null>(null);\n\n/**\n * Props for the KitProvider component.\n */\nexport interface KitProviderProps {\n /** The initial kit definition to provide. Updates to this prop will sync the internal state. */\n kit: KitDefinition;\n children: React.ReactNode;\n}\n\n/**\n * Provides a resolved KitDefinition to all child components via context.\n * Supports runtime kit switching: children can call `setKit` from `useKit()`\n * to dynamically change the active kit. If the parent passes a new `kit` prop,\n * the internal state is synced to match.\n */\nexport function KitProvider({ kit: initialKit, children }: KitProviderProps) {\n const [currentKit, setCurrentKit] = useState<KitDefinition>(initialKit);\n\n // Sync if parent passes a new kit prop\n useEffect(() => {\n setCurrentKit(initialKit);\n }, [initialKit]);\n\n const setKit = useCallback((kit: KitPresetId | KitDefinition) => {\n const resolved = typeof kit === 'string' ? resolveKit(kit) : kit;\n setCurrentKit(resolved);\n }, []);\n\n const value = useMemo<KitContextValue>(\n () => ({ kit: currentKit, setKit }),\n [currentKit, setKit],\n );\n\n return <KitContext.Provider value={value}>{children}</KitContext.Provider>;\n}\n\n/**\n * Returns the current kit context value, including the active KitDefinition\n * and a `setKit` function for runtime kit switching.\n *\n * Must be called within a KitProvider.\n *\n * @throws {Error} If called outside of a KitProvider\n *\n * @example\n * ```tsx\n * const { kit, setKit } = useKit();\n * console.log(kit.name);\n * setKit('pro-studio'); // switch to a preset\n * setKit(myCustomKit); // switch to a custom kit\n * ```\n */\nexport function useKit(): KitContextValue {\n const ctx = useContext(KitContext);\n if (!ctx) throw new Error('useKit must be used within a KitProvider');\n return ctx;\n}\n\n/**\n * Convenience hook that returns just the capabilities from the current kit.\n * Equivalent to `useKit().kit.capabilities`.\n *\n * @throws {Error} If called outside of a KitProvider\n */\nexport function useCapabilities() {\n const { kit } = useKit();\n return kit.capabilities;\n}\n","/**\n * ElementPreviewRenderer - Generates thumbnail previews for layer panel\n * Creates small canvas thumbnails of elements for display in the layers panel\n * Uses the element's actual renderContent method for accurate previews\n */\n\nimport { TextElement } from '../core/TextElement.js';\nimport { ImageElement } from '../core/ImageElement.js';\nimport { GroupElement } from '../core/GroupElement.js';\nimport { ShapeElement } from '../core/ShapeElement.js';\nimport { PathElement } from '../core/PathElement.js';\nimport { createLogger } from './logger';\n\nconst logger = createLogger('ElementPreviewRenderer');\n\ntype LayerElement = TextElement | ImageElement | GroupElement | ShapeElement | PathElement;\n\nexport interface PreviewOptions {\n width?: number;\n height?: number;\n padding?: number;\n backgroundColor?: string;\n}\n\nconst DEFAULT_OPTIONS: Required<PreviewOptions> = {\n width: 40,\n height: 40,\n padding: 4,\n backgroundColor: 'transparent',\n};\n\n/**\n * Generate a thumbnail preview for an element\n * Returns a data URL that can be used as an image src\n * Uses the element's actual renderContent method for accurate representation\n */\nexport function generateElementPreview(\n element: LayerElement,\n options: PreviewOptions = {}\n): string {\n const opts = { ...DEFAULT_OPTIONS, ...options };\n\n // Create a small canvas for the thumbnail\n const canvas = document.createElement('canvas');\n canvas.width = opts.width;\n canvas.height = opts.height;\n const ctx = canvas.getContext('2d');\n\n if (!ctx) {\n return '';\n }\n\n // Clear with background\n if (opts.backgroundColor !== 'transparent') {\n ctx.fillStyle = opts.backgroundColor;\n ctx.fillRect(0, 0, opts.width, opts.height);\n }\n\n // Save context state\n ctx.save();\n\n try {\n // Use actual element rendering for accurate preview\n if (element instanceof GroupElement) {\n renderGroupPreview(ctx, element, opts);\n } else {\n renderElementUsingActualRenderer(ctx, element, opts);\n }\n } catch (error) {\n logger.warn('Failed to generate preview:', error);\n }\n\n // Restore context state\n ctx.restore();\n\n // Return data URL\n return canvas.toDataURL('image/png');\n}\n\n/**\n * Render element using its actual render method\n * This ensures the preview matches exactly what's on the canvas\n */\nfunction renderElementUsingActualRenderer(\n ctx: CanvasRenderingContext2D,\n element: LayerElement,\n opts: Required<PreviewOptions>\n): void {\n // Get the element's bounding box\n const bbox = element.getBoundingBox?.() || { x: 0, y: 0, width: 100, height: 100 };\n const elementWidth = bbox.width;\n const elementHeight = bbox.height;\n\n if (elementWidth === 0 || elementHeight === 0) {\n // Fallback for empty elements\n renderFallbackPreview(ctx, element, opts);\n return;\n }\n\n // Calculate scale to fit in preview with padding\n const availableWidth = opts.width - opts.padding * 2;\n const availableHeight = opts.height - opts.padding * 2;\n const scale = Math.min(\n availableWidth / elementWidth,\n availableHeight / elementHeight,\n 1 // Don't scale up, only down\n );\n\n // Calculate centered position\n const scaledWidth = elementWidth * scale;\n const scaledHeight = elementHeight * scale;\n const offsetX = opts.padding + (availableWidth - scaledWidth) / 2;\n const offsetY = opts.padding + (availableHeight - scaledHeight) / 2;\n\n // Apply transformations for preview\n ctx.save();\n ctx.translate(offsetX - bbox.x * scale, offsetY - bbox.y * scale);\n ctx.scale(scale, scale);\n\n // Render the element using its actual render method\n // Elements have render(ctx, isSelected, isHovered) method\n if (typeof element.render === 'function') {\n element.render(ctx, false, false);\n } else {\n // Fallback if no render method\n renderFallbackPreview(ctx, element, opts);\n }\n\n ctx.restore();\n}\n\n/**\n * Fallback preview renderer for elements without render method\n */\nfunction renderFallbackPreview(\n ctx: CanvasRenderingContext2D,\n element: LayerElement,\n opts: Required<PreviewOptions>\n): void {\n // Draw placeholder\n ctx.fillStyle = '#888888';\n ctx.font = '10px Arial';\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n\n const label = element.transformType || 'Element';\n ctx.fillText(label, opts.width / 2, opts.height / 2);\n}\n\n/**\n * Render group element preview\n */\nfunction renderGroupPreview(\n ctx: CanvasRenderingContext2D,\n element: GroupElement,\n opts: Required<PreviewOptions>\n): void {\n // Draw a simple folder/group icon\n const availableWidth = opts.width - opts.padding * 2;\n const availableHeight = opts.height - opts.padding * 2;\n const x = opts.padding;\n const y = opts.padding;\n\n ctx.fillStyle = '#888888';\n ctx.strokeStyle = '#666666';\n ctx.lineWidth = 1;\n\n // Draw folder shape\n const folderHeight = availableHeight * 0.7;\n const folderY = y + availableHeight * 0.3;\n const tabWidth = availableWidth * 0.4;\n const tabHeight = availableHeight * 0.2;\n\n // Draw folder body\n ctx.fillRect(x, folderY, availableWidth, folderHeight);\n\n // Draw folder tab\n ctx.fillRect(x, folderY - tabHeight, tabWidth, tabHeight);\n\n // Optional: Draw children count if group has children\n if (element.children && element.children.length > 0) {\n ctx.fillStyle = '#FFFFFF';\n ctx.font = 'bold 10px Arial';\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(\n element.children.length.toString(),\n x + availableWidth / 2,\n folderY + folderHeight / 2\n );\n }\n}\n","/**\n * useLayers - Hook for accessing layer data with hierarchy and operations\n * Provides layer tree with preview images for custom layer panels\n */\n\nimport { useMemo, useCallback, useRef, useState } from 'react';\nimport { useEditor } from '../contexts/EditorContext.js';\nimport { GroupElement } from '../core/GroupElement.js';\nimport { TextElement } from '../core/TextElement.js';\nimport { ImageElement } from '../core/ImageElement.js';\nimport { generateElementPreview } from '../utils/ElementPreviewRenderer.js';\nimport { createLogger } from '../utils/logger.js';\nimport type { EditorElement } from '../contexts/EditorContext.js';\nimport type { TransformType } from '../types/index.js';\nimport type { ArtboardManager } from '../core/ArtboardManager.js';\n\nconst log = createLogger('Layers');\n\nexport interface LayerInfo {\n id: string;\n name: string;\n type: TransformType;\n visible: boolean;\n locked: boolean;\n isSelected: boolean;\n isGroup: boolean;\n depth: number;\n parentId: string | null;\n children?: LayerInfo[];\n\n // Preview image generation (implemented in Sprint 2)\n previewUrl: string | null; // Data URL for thumbnail\n refreshPreview: () => Promise<void>;\n}\n\nexport interface UseLayersOptions {\n artboardId?: string | null; // Filter by artboard (default: active)\n includeHidden?: boolean; // Include hidden layers (default: true)\n previewSize?: number; // Preview thumbnail size (default: 64)\n previewBackgroundColor?: string; // Preview background color (default: transparent)\n autoRefreshPreviews?: boolean; // Auto-regenerate on changes (default: false)\n}\n\nexport interface UseLayersReturn {\n // Hierarchical layer tree\n layers: LayerInfo[];\n flatLayers: LayerInfo[]; // Flat list for simple rendering\n\n // Selection\n selectedLayers: LayerInfo[];\n selectLayer: (id: string, addToSelection?: boolean) => void;\n selectMultipleLayers: (ids: string[]) => void;\n clearSelection: () => void;\n\n // Operations\n toggleVisibility: (id: string) => void;\n toggleLock: (id: string) => void;\n renameLayer: (id: string, name: string) => void;\n deleteLayer: (id: string) => void;\n duplicateLayer: (id: string) => void;\n reorderLayers: (fromIndex: number, toIndex: number) => void;\n reorderById: (draggedId: string, targetId: string, position: 'before' | 'after') => void;\n reorderWithinGroup: (draggedId: string, targetId: string, position: 'before' | 'after', groupId: string) => void;\n moveLayerForward: (id: string) => void;\n moveLayerBackward: (id: string) => void;\n groupLayers: (ids: string[]) => void;\n ungroupLayer: (id: string) => void;\n addToGroup: (elementId: string, groupId: string) => void;\n removeFromGroup: (elementId: string, targetId?: string, position?: 'before' | 'after') => void;\n createEmptyGroup: () => void;\n findElementById: (id: string) => EditorElement | null;\n findParentGroup: (id: string) => GroupElement | null;\n\n // Preview management (implemented in Sprint 2)\n generateAllPreviews: (size?: number) => Promise<void>;\n clearPreviewCache: () => void;\n\n // Artboard filtering\n artboardId: string | null; // Current artboard\n artboardManager: ArtboardManager; // Access to artboard manager for advanced use cases\n layerCount: number;\n}\n\n/**\n * Generate preview image for an element (async wrapper for shared utility)\n * Uses the shared ElementPreviewRenderer for consistency\n */\nasync function generateElementPreviewAsync(\n element: EditorElement,\n size: number = 64,\n backgroundColor: string = 'transparent'\n): Promise<string | null> {\n try {\n // Use shared preview renderer for consistency with main LayersPanel\n const preview = generateElementPreview(element, {\n width: size,\n height: size,\n padding: 8,\n backgroundColor,\n });\n return preview || null;\n } catch (error) {\n log.error('Failed to generate preview:', error);\n return null;\n }\n}\n\n/**\n * Build hierarchical layer tree from flat element list\n */\nfunction buildLayerTree(\n elements: EditorElement[],\n selectedId: string | null,\n multiSelection: string[],\n previewCache: Map<string, string | null>,\n generatePreview: (id: string) => Promise<void>,\n depth: number = 0,\n parentId: string | null = null\n): LayerInfo[] {\n // Find elements at this level.\n // At depth > 0, `elements` is already the direct children array from\n // GroupElement.children — no filtering needed.\n // At depth 0 (top level), filter to elements that aren't nested in a group.\n const layersAtLevel = depth > 0 ? elements : elements.filter(el => {\n const elParentId = (el as unknown as Record<string, unknown>).parentId as string | null ?? null;\n if (el instanceof GroupElement) {\n return elParentId === null;\n }\n return elParentId === null;\n });\n\n return layersAtLevel.map(element => {\n const isGroup = element instanceof GroupElement;\n const isSelected = selectedId === element.id || multiSelection.includes(element.id);\n\n const layerInfo: LayerInfo = {\n id: element.id,\n name: element.name || `Layer ${element.id.slice(0, 8)}`,\n type: element.transformType,\n visible: element.visible ?? true,\n locked: element.locked ?? false,\n isSelected,\n isGroup,\n depth,\n parentId,\n previewUrl: previewCache.get(element.id) || null,\n refreshPreview: async () => {\n await generatePreview(element.id);\n },\n };\n\n // Recursively build children for groups\n if (isGroup && element instanceof GroupElement) {\n layerInfo.children = buildLayerTree(\n element.children,\n selectedId,\n multiSelection,\n previewCache,\n generatePreview,\n depth + 1,\n element.id\n );\n }\n\n return layerInfo;\n });\n}\n\n/**\n * Flatten hierarchical layer tree\n */\nfunction flattenLayerTree(layers: LayerInfo[]): LayerInfo[] {\n const result: LayerInfo[] = [];\n for (const layer of layers) {\n result.push(layer);\n if (layer.children) {\n result.push(...flattenLayerTree(layer.children));\n }\n }\n return result;\n}\n\n/**\n * Hook for accessing layer data and operations\n * Must be used within EditorProvider\n *\n * @example\n * ```tsx\n * function CustomLayersPanel() {\n * const { layers, selectLayer, toggleVisibility, deleteLayer } = useLayers({\n * previewSize: 80,\n * autoRefreshPreviews: true\n * });\n *\n * return (\n * <div className=\"layers-panel\">\n * {layers.map(layer => (\n * <div\n * key={layer.id}\n * className={layer.isSelected ? 'selected' : ''}\n * style={{ paddingLeft: layer.depth * 20 }}\n * >\n * {layer.previewUrl && (\n * <img src={layer.previewUrl} alt={layer.name} width={40} />\n * )}\n * <span onClick={() => selectLayer(layer.id)}>{layer.name}</span>\n * <button onClick={() => toggleVisibility(layer.id)}>\n * {layer.visible ? '👁' : '🚫'}\n * </button>\n * <button onClick={() => deleteLayer(layer.id)}>🗑</button>\n * </div>\n * ))}\n * </div>\n * );\n * }\n * ```\n */\nexport function useLayers(options: UseLayersOptions = {}): UseLayersReturn {\n const {\n artboardId: filterArtboardId,\n includeHidden = true,\n previewSize = 64,\n previewBackgroundColor = 'transparent',\n } = options;\n\n const {\n elements,\n selectedId,\n multiSelection,\n artboardManager,\n handleSelectionChange,\n handleElementUpdate,\n setMultiSelection,\n executeAddElement,\n executeRemoveElement,\n executeReorderElement,\n } = useEditor();\n\n // Preview cache and version for triggering re-renders\n const previewCache = useRef<Map<string, string | null>>(new Map());\n const [previewVersion, setPreviewVersion] = useState(0);\n\n // Determine which artboard to filter by\n const activeArtboardId = artboardManager.getActiveArtboardId();\n const effectiveArtboardId = filterArtboardId !== undefined ? filterArtboardId : activeArtboardId;\n\n // Generate preview for a specific element\n const generatePreview = useCallback(async (elementId: string) => {\n const element = elements.find(el => el.id === elementId);\n if (!element) return;\n\n const preview = await generateElementPreviewAsync(element, previewSize, previewBackgroundColor);\n previewCache.current.set(elementId, preview);\n // Trigger re-render by updating version\n setPreviewVersion(v => v + 1);\n }, [elements, previewSize, previewBackgroundColor]);\n\n // Filter elements by artboard\n const filteredElements = useMemo(() => {\n let filtered = elements;\n\n // Filter by artboard using ArtboardManager\n if (effectiveArtboardId) {\n const elementIds = artboardManager.getElementsOnArtboard(effectiveArtboardId);\n filtered = filtered.filter(el => elementIds.includes(el.id));\n }\n\n // Filter hidden elements if needed\n if (!includeHidden) {\n filtered = filtered.filter(el => el.visible !== false);\n }\n\n return filtered;\n }, [elements, effectiveArtboardId, includeHidden, artboardManager]);\n\n // Build hierarchical layer tree\n const layers = useMemo(() => {\n return buildLayerTree(\n filteredElements,\n selectedId,\n multiSelection,\n previewCache.current,\n generatePreview\n );\n }, [filteredElements, selectedId, multiSelection, generatePreview, previewVersion]);\n\n // Flatten layer tree\n const flatLayers = useMemo(() => {\n return flattenLayerTree(layers);\n }, [layers]);\n\n // Get selected layers\n const selectedLayers = useMemo(() => {\n return flatLayers.filter(layer => layer.isSelected);\n }, [flatLayers]);\n\n // Select layer\n const selectLayer = useCallback((id: string, addToSelection: boolean = false) => {\n if (addToSelection) {\n // Add to multi-selection\n if (multiSelection.includes(id)) {\n // Deselect if already selected\n setMultiSelection(multiSelection.filter(sid => sid !== id));\n } else {\n // Add to selection\n setMultiSelection([...multiSelection, id]);\n }\n } else {\n // Single selection\n handleSelectionChange(id);\n }\n }, [multiSelection, setMultiSelection, handleSelectionChange]);\n\n // Select multiple layers\n const selectMultipleLayers = useCallback((ids: string[]) => {\n setMultiSelection(ids);\n }, [setMultiSelection]);\n\n // Clear selection\n const clearSelection = useCallback(() => {\n handleSelectionChange(null);\n setMultiSelection([]);\n }, [handleSelectionChange, setMultiSelection]);\n\n // Toggle visibility\n const toggleVisibility = useCallback((id: string) => {\n const element = elements.find(el => el.id === id);\n if (!element) return;\n\n const updated = element.clone();\n updated.visible = !(element.visible ?? true);\n handleElementUpdate(updated);\n }, [elements, handleElementUpdate]);\n\n // Toggle lock\n const toggleLock = useCallback((id: string) => {\n const element = elements.find(el => el.id === id);\n if (!element) return;\n\n const updated = element.clone();\n updated.locked = !(element.locked ?? false);\n handleElementUpdate(updated);\n }, [elements, handleElementUpdate]);\n\n // Rename layer\n const renameLayer = useCallback((id: string, name: string) => {\n const element = elements.find(el => el.id === id);\n if (!element) return;\n\n const updated = element.clone();\n updated.name = name;\n handleElementUpdate(updated);\n }, [elements, handleElementUpdate]);\n\n // Delete layer\n const deleteLayer = useCallback((id: string) => {\n const element = elements.find(el => el.id === id);\n if (!element) {\n log.warn(`Element ${id} not found for deletion`);\n return;\n }\n\n executeRemoveElement(element);\n\n // Clear selection if deleted element was selected\n if (selectedId === id || multiSelection.includes(id)) {\n handleSelectionChange(null);\n setMultiSelection(multiSelection.filter(sid => sid !== id));\n }\n }, [elements, executeRemoveElement, selectedId, multiSelection, handleSelectionChange, setMultiSelection]);\n\n // Duplicate layer\n const duplicateLayer = useCallback((id: string) => {\n const element = elements.find(el => el.id === id);\n if (!element) {\n log.warn(`Element ${id} not found for duplication`);\n return;\n }\n\n // Clone the element and reconstruct with a new ID\n const cloned = element.clone();\n const json = cloned.toJSON();\n // Remove old ID so constructor generates a new one\n delete (json as Record<string, unknown>).id;\n // Reconstruct to get a new ID\n const Constructor = element.constructor as new (config: Record<string, unknown>) => EditorElement;\n const clonedWithNewId = new Constructor(json as Record<string, unknown>);\n\n // Offset position slightly so it's visible\n clonedWithNewId.x = element.x + 20;\n clonedWithNewId.y = element.y + 20;\n\n // Get artboard for this element\n const artboardId = artboardManager.getArtboardIdForElement(id);\n if (!artboardId) {\n log.warn(`Element ${id} not associated with any artboard`);\n return;\n }\n\n // Add cloned element after the original\n executeAddElement(clonedWithNewId, artboardId, id);\n\n // Select the duplicated element\n handleSelectionChange(clonedWithNewId.id);\n }, [elements, artboardManager, executeAddElement, handleSelectionChange]);\n\n // Reorder layers\n const reorderLayers = useCallback((fromIndex: number, toIndex: number) => {\n if (fromIndex === toIndex) return;\n if (fromIndex < 0 || fromIndex >= filteredElements.length) {\n log.warn(`Invalid fromIndex: ${fromIndex}`);\n return;\n }\n if (toIndex < 0 || toIndex >= filteredElements.length) {\n log.warn(`Invalid toIndex: ${toIndex}`);\n return;\n }\n\n const draggedElement = filteredElements[fromIndex];\n const targetElement = filteredElements[toIndex];\n\n // Determine position based on direction\n const position = fromIndex < toIndex ? 'after' : 'before';\n\n executeReorderElement(draggedElement.id, targetElement.id, position);\n }, [filteredElements, executeReorderElement]);\n\n // Move layer one position forward (toward front/top of stack)\n // In the element array, higher index = rendered on top = \"forward\"\n const moveLayerForward = useCallback((id: string) => {\n const index = flatLayers.findIndex(l => l.id === id);\n if (index < 0) {\n log.warn(`Layer ${id} not found`);\n return;\n }\n if (index >= flatLayers.length - 1) {\n // Already at the front (last in array = top of stack)\n return;\n }\n reorderLayers(index, index + 1);\n }, [flatLayers, reorderLayers]);\n\n // Move layer one position backward (toward back/bottom of stack)\n // In the element array, lower index = rendered first = \"backward\"\n const moveLayerBackward = useCallback((id: string) => {\n const index = flatLayers.findIndex(l => l.id === id);\n if (index < 0) {\n log.warn(`Layer ${id} not found`);\n return;\n }\n if (index <= 0) {\n // Already at the back (first in array = bottom of stack)\n return;\n }\n reorderLayers(index, index - 1);\n }, [flatLayers, reorderLayers]);\n\n // Group layers\n const groupLayers = useCallback((ids: string[]) => {\n if (ids.length < 2) {\n log.warn('Need at least 2 elements to create a group');\n return;\n }\n\n // Find all elements to group (filter out ShapeElements - they can't be grouped)\n const elementsToGroup = elements.filter(el =>\n ids.includes(el.id) && el.transformType !== 'shape'\n ) as (TextElement | ImageElement | GroupElement)[];\n\n if (elementsToGroup.length < 2) {\n log.warn('Need at least 2 groupable elements (shapes cannot be grouped)');\n return;\n }\n\n // All elements must be on the same artboard\n const artboardIds = elementsToGroup.map(el => artboardManager.getArtboardIdForElement(el.id));\n const uniqueArtboardIds = [...new Set(artboardIds)];\n if (uniqueArtboardIds.length !== 1 || !uniqueArtboardIds[0]) {\n log.warn('All elements must be on the same artboard to group');\n return;\n }\n const artboardId = uniqueArtboardIds[0];\n\n // Calculate group bounding box\n const bboxes = elementsToGroup.map(el => el.getBoundingBox()).filter(Boolean);\n if (bboxes.length === 0) {\n log.warn('Could not calculate bounding boxes for group');\n return;\n }\n\n const minX = Math.min(...bboxes.map(b => b!.x));\n const minY = Math.min(...bboxes.map(b => b!.y));\n\n // Create group element\n const group = new GroupElement({\n x: minX,\n y: minY,\n });\n\n // Set children manually (GroupElement constructor accepts instances but type is for configs)\n group.children = elementsToGroup.map(el => {\n const clone = el.clone() as TextElement | ImageElement | GroupElement;\n // Convert to relative positioning\n clone.x = el.x - minX;\n clone.y = el.y - minY;\n return clone;\n });\n\n // Remove individual elements and add group\n elementsToGroup.forEach(el => executeRemoveElement(el));\n executeAddElement(group, artboardId);\n\n // Select the new group\n handleSelectionChange(group.id);\n setMultiSelection([]);\n }, [elements, artboardManager, executeRemoveElement, executeAddElement, handleSelectionChange, setMultiSelection]);\n\n // Ungroup layer\n const ungroupLayer = useCallback((id: string) => {\n const element = elements.find(el => el.id === id);\n if (!element) {\n log.warn(`Element ${id} not found for ungrouping`);\n return;\n }\n\n if (!(element instanceof GroupElement)) {\n log.warn(`Element ${id} is not a group`);\n return;\n }\n\n const group = element as GroupElement;\n if (!group.children || group.children.length === 0) {\n log.warn(`Group ${id} has no children`);\n return;\n }\n\n // Get artboard for this group\n const artboardId = artboardManager.getArtboardIdForElement(id);\n if (!artboardId) {\n log.warn(`Group ${id} not associated with any artboard`);\n return;\n }\n\n // Extract children and convert to absolute positioning\n const extractedChildren = group.children.map(child => {\n const clone = child.clone();\n clone.x = child.x + group.x;\n clone.y = child.y + group.y;\n return clone;\n });\n\n // Remove group and add children\n executeRemoveElement(group);\n extractedChildren.forEach(child => executeAddElement(child, artboardId));\n\n // Select first extracted child\n if (extractedChildren.length > 0) {\n handleSelectionChange(extractedChildren[0].id);\n }\n setMultiSelection([]);\n }, [elements, artboardManager, executeRemoveElement, executeAddElement, handleSelectionChange, setMultiSelection]);\n\n // Generate all previews\n const generateAllPreviews = useCallback(async (size: number = previewSize) => {\n\n // Generate previews for all elements, including group children\n const generateForElements = async (elements: EditorElement[]) => {\n for (const element of elements) {\n const preview = await generateElementPreviewAsync(element, size, previewBackgroundColor);\n previewCache.current.set(element.id, preview);\n // Recurse into group children\n if (element instanceof GroupElement && element.children.length > 0) {\n await generateForElements(element.children as unknown as EditorElement[]);\n }\n }\n };\n await generateForElements(filteredElements);\n\n // Trigger re-render by updating version\n setPreviewVersion(v => v + 1);\n }, [previewSize, previewBackgroundColor, filteredElements]);\n\n // Clear preview cache\n const clearPreviewCache = useCallback(() => {\n previewCache.current.clear();\n }, []);\n\n // ── ID-based helpers for drag-drop ─────────────────────────────────────\n\n /** Reorder by element IDs (wraps executeReorderElement) */\n const reorderById = useCallback(\n (draggedId: string, targetId: string, position: 'before' | 'after') => {\n executeReorderElement(draggedId, targetId, position);\n },\n [executeReorderElement]\n );\n\n /** Reorder children within a group */\n const reorderWithinGroup = useCallback(\n (draggedId: string, targetId: string, position: 'before' | 'after', groupId: string) => {\n const group = elements.find(el => el.id === groupId);\n if (!group || !(group instanceof GroupElement)) {\n log.warn(`Group ${groupId} not found for reorder`);\n return;\n }\n\n const newGroup = group.clone() as GroupElement;\n const draggedIndex = newGroup.children.findIndex(c => c.id === draggedId);\n const targetIndex = newGroup.children.findIndex(c => c.id === targetId);\n if (draggedIndex === -1 || targetIndex === -1) {\n log.warn(`Children not found in group for reorder: ${draggedId}, ${targetId}`);\n return;\n }\n\n // Remove dragged child and re-insert at target position\n const [dragged] = newGroup.children.splice(draggedIndex, 1);\n const newTargetIndex = newGroup.children.findIndex(c => c.id === targetId);\n const insertIndex = position === 'before' ? newTargetIndex : newTargetIndex + 1;\n newGroup.children.splice(insertIndex, 0, dragged);\n\n handleElementUpdate(newGroup);\n },\n [elements, handleElementUpdate]\n );\n\n /** Find an element by ID */\n const findElementById = useCallback(\n (id: string): EditorElement | null => {\n // Check top-level elements\n for (const el of elements) {\n if (el.id === id) return el;\n // Check group children\n if (el instanceof GroupElement) {\n const child = el.children.find(c => c.id === id);\n if (child) return child as unknown as EditorElement;\n }\n }\n return null;\n },\n [elements]\n );\n\n /** Find the parent group of an element */\n const findParentGroup = useCallback(\n (id: string): GroupElement | null => {\n for (const el of elements) {\n if (el instanceof GroupElement) {\n if (el.children.some(child => child.id === id)) {\n return el;\n }\n }\n }\n return null;\n },\n [elements]\n );\n\n /** Add an element into a group */\n const addToGroup = useCallback(\n (elementId: string, groupId: string) => {\n const element = elements.find(el => el.id === elementId);\n const group = elements.find(el => el.id === groupId);\n if (!element || !group || !(group instanceof GroupElement)) {\n log.warn(`Cannot add ${elementId} to group ${groupId}`);\n return;\n }\n\n const artboardId = artboardManager.getArtboardIdForElement(elementId);\n if (!artboardId) return;\n\n // Clone group and add element as child.\n // Children in a GroupElement use WORLD coordinates (not relative to group center).\n // addChild() calls updateBoundsFromChildren() which recalculates group.x/y\n // to the center of the bounding box, but children positions stay unchanged.\n const newGroup = group.clone() as GroupElement;\n const childClone = element.clone() as TextElement | ImageElement | GroupElement;\n childClone.x = element.x;\n childClone.y = element.y;\n newGroup.addChild(childClone);\n\n // Remove original element and update group\n executeRemoveElement(element);\n handleElementUpdate(newGroup);\n },\n [elements, artboardManager, executeRemoveElement, handleElementUpdate]\n );\n\n /** Remove an element from its parent group (promote to top level).\n * Optional targetId + position specify where to insert in the top-level array.\n * Without them, inserts before the group (= below it visually). */\n const removeFromGroup = useCallback(\n (elementId: string, targetId?: string, position?: 'before' | 'after') => {\n const parentGroup = findParentGroup(elementId);\n if (!parentGroup) {\n log.warn(`Element ${elementId} is not inside a group`);\n return;\n }\n\n const child = parentGroup.children.find(c => c.id === elementId);\n if (!child) return;\n\n const artboardId = artboardManager.getArtboardIdForElement(parentGroup.id!);\n if (!artboardId) return;\n\n // Clone the child — children already use world coordinates, no conversion needed\n const promoted = child.clone();\n\n // Update group without this child\n const newGroup = parentGroup.clone() as GroupElement;\n newGroup.removeChild(elementId);\n\n handleElementUpdate(newGroup);\n\n // Insert at the specified position, or default to before the group\n if (targetId && position) {\n // position is already in array terms (inverted by caller)\n const insertAfter = position === 'after' ? targetId : undefined;\n const insertBefore = position === 'before' ? targetId : undefined;\n executeAddElement(promoted, artboardId, insertAfter, insertBefore);\n } else {\n // Default: insert before the group in array = below it visually\n executeAddElement(promoted, artboardId, undefined, parentGroup.id!);\n }\n handleSelectionChange(promoted.id);\n },\n [findParentGroup, artboardManager, executeRemoveElement, handleElementUpdate, executeAddElement, handleSelectionChange]\n );\n\n /** Create an empty group on the active artboard */\n const createEmptyGroup = useCallback(() => {\n const artboardId = artboardManager.getActiveArtboardId();\n if (!artboardId) {\n log.warn('No active artboard for creating a group');\n return;\n }\n\n const group = new GroupElement({\n x: 0,\n y: 0,\n name: 'New Group',\n });\n\n executeAddElement(group, artboardId);\n handleSelectionChange(group.id);\n }, [artboardManager, executeAddElement, handleSelectionChange]);\n\n return {\n layers,\n flatLayers,\n selectedLayers,\n selectLayer,\n selectMultipleLayers,\n clearSelection,\n toggleVisibility,\n toggleLock,\n renameLayer,\n deleteLayer,\n duplicateLayer,\n reorderLayers,\n reorderById,\n reorderWithinGroup,\n moveLayerForward,\n moveLayerBackward,\n groupLayers,\n ungroupLayer,\n addToGroup,\n removeFromGroup,\n createEmptyGroup,\n findElementById,\n findParentGroup,\n generateAllPreviews,\n clearPreviewCache,\n artboardId: effectiveArtboardId,\n artboardManager,\n layerCount: flatLayers.length,\n };\n}\n","/**\n * useKeyboardShortcuts - Hook for handling keyboard shortcuts in the editor\n * Provides copy/paste and other keyboard shortcuts\n */\n\nimport { useEffect } from 'react';\nimport { useEditor } from '../contexts/EditorContext.js';\nimport { useLayers } from './useLayers.js';\nimport { ImageElement } from '../core/ImageElement.js';\nimport { GroupElement } from '../core/GroupElement.js';\n\n/**\n * Check if any image is currently in crop mode\n * Handles both standalone images and images inside groups\n */\nfunction isAnyCroppingActive(\n selectedElement: ReturnType<typeof useEditor>['selectedElement'],\n activeChildElement: ReturnType<typeof useEditor>['activeChildElement']\n): boolean {\n // Check if active child element is an image in crop mode\n if (activeChildElement instanceof ImageElement && activeChildElement.isCropping) {\n return true;\n }\n\n // Check if selected element is an image in crop mode\n if (selectedElement instanceof ImageElement && selectedElement.isCropping) {\n return true;\n }\n\n // Check if selected element is a group with a child image in crop mode\n if (selectedElement instanceof GroupElement) {\n for (const child of selectedElement.children) {\n if (child instanceof ImageElement && child.isCropping) {\n return true;\n }\n }\n }\n\n return false;\n}\n\n/**\n * Hook that sets up keyboard shortcuts for the editor\n * Handles copy/paste, delete, undo/redo, and other shortcuts\n *\n * @param options.enabled - Whether keyboard shortcuts are active (default: true)\n */\nexport function useKeyboardShortcuts(options?: { enabled?: boolean }) {\n const enabled = options?.enabled ?? true;\n const {\n selectedElement,\n activeChildElement,\n multiSelection,\n elements,\n handleElementUpdate,\n handleCopyElements,\n handlePasteElements,\n executeRemoveElement,\n handleSelectionChange,\n setMultiSelection,\n // Use per-artboard undo/redo for keyboard shortcuts\n undoActiveArtboard,\n redoActiveArtboard,\n setHideHandles,\n handleAddElement,\n isToolbarMenuOpen,\n } = useEditor();\n\n const { duplicateLayer } = useLayers();\n\n useEffect(() => {\n if (!enabled) return;\n\n const handleKeyDown = (e: KeyboardEvent) => {\n // Check if user is interacting with a UI control that needs keyboard input\n const target = e.target as HTMLElement;\n if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') return;\n // Skip when focus is inside a listbox (e.g. preset carousels) or other\n // interactive toolbar controls that use arrow keys for navigation\n if (target.closest?.('[role=\"listbox\"], [role=\"menu\"], [role=\"tablist\"]')) return;\n\n // Cmd/Ctrl+Z: Undo (per-artboard)\n if ((e.metaKey || e.ctrlKey) && e.key === 'z' && !e.shiftKey) {\n e.preventDefault();\n undoActiveArtboard();\n return;\n }\n\n // Cmd/Ctrl+Shift+Z or Ctrl+Y: Redo (per-artboard)\n if (((e.metaKey || e.ctrlKey) && e.shiftKey && e.key === 'z') || (e.ctrlKey && e.key === 'y')) {\n e.preventDefault();\n redoActiveArtboard();\n return;\n }\n\n // Cmd/Ctrl+C: Copy selected element(s)\n if ((e.metaKey || e.ctrlKey) && e.key === 'c') {\n e.preventDefault();\n handleCopyElements();\n return;\n }\n\n // Cmd/Ctrl+V: Paste element(s) from clipboard\n if ((e.metaKey || e.ctrlKey) && e.key === 'v') {\n e.preventDefault();\n handlePasteElements();\n return;\n }\n\n // Cmd/Ctrl+D: Duplicate selected element(s)\n if ((e.metaKey || e.ctrlKey) && e.key === 'd') {\n e.preventDefault();\n if (selectedElement) {\n duplicateLayer(selectedElement.id);\n } else if (multiSelection.length > 0) {\n // Duplicate all selected elements\n multiSelection.forEach((id) => {\n duplicateLayer(id);\n });\n }\n return;\n }\n\n // Escape: Deselect element (text edit exit is handled by CanvasEditor)\n if (e.key === 'Escape') {\n e.preventDefault();\n // Deselect current selection\n if (selectedElement || multiSelection.length > 0) {\n handleSelectionChange(null);\n setMultiSelection([]);\n }\n return;\n }\n\n // T: Add new text element (like Figma/Sketch)\n if (e.key === 't' && !e.metaKey && !e.ctrlKey && !e.shiftKey && !e.altKey) {\n e.preventDefault();\n handleAddElement('text');\n return;\n }\n\n // Delete or Backspace: Delete selected element(s)\n // Only works when element is selected and no tools/panels are open\n if (e.key === 'Delete' || e.key === 'Backspace') {\n // Don't delete when any toolbar menu/panel/dropdown is open\n if (isToolbarMenuOpen) {\n return;\n }\n\n // Don't delete when any image is in crop mode (standalone or in group)\n if (isAnyCroppingActive(selectedElement, activeChildElement)) {\n return;\n }\n\n e.preventDefault();\n // Handle multi-selection delete. Locked elements are deletable\n // — locking gates *manipulation* (drag/resize/rotate) but\n // delete is a deliberate, single-shortcut action so the user\n // doesn't have to first toggle the lock to remove an element.\n if (multiSelection.length > 0) {\n const elementsToDelete = elements.filter((s) => multiSelection.includes(s.id));\n elementsToDelete.forEach((element) => {\n executeRemoveElement(element);\n });\n setMultiSelection([]);\n handleSelectionChange(null);\n return;\n }\n\n // Handle single selection delete\n if (selectedElement) {\n executeRemoveElement(selectedElement);\n handleSelectionChange(null);\n }\n return;\n }\n\n // Handle arrow keys for nudging or multi-selection nudging\n if (e.key === 'ArrowUp' || e.key === 'ArrowDown' || e.key === 'ArrowLeft' || e.key === 'ArrowRight') {\n // If multi-selection is active, nudge all selected elements\n if (multiSelection.length > 0) {\n e.preventDefault();\n\n // Hide handles during nudging\n setHideHandles(true);\n\n // Use 10px increment if Shift is held, otherwise 1px\n const increment = e.shiftKey ? 10 : 1;\n\n let dx = 0;\n let dy = 0;\n\n if (e.key === 'ArrowLeft') dx = -increment;\n if (e.key === 'ArrowRight') dx = increment;\n if (e.key === 'ArrowUp') dy = -increment;\n if (e.key === 'ArrowDown') dy = increment;\n\n // Update all selected elements\n const selectedElements = elements.filter((s) => multiSelection.includes(s.id));\n selectedElements.forEach((element) => {\n const updatedElement = element.clone();\n updatedElement.x += dx;\n updatedElement.y += dy;\n handleElementUpdate(updatedElement);\n });\n\n return;\n }\n\n // If single element is selected, nudge it\n if (selectedElement) {\n e.preventDefault();\n\n // Hide handles during nudging\n setHideHandles(true);\n\n // Use 10px increment if Shift is held, otherwise 1px\n const increment = e.shiftKey ? 10 : 1;\n\n let dx = 0;\n let dy = 0;\n\n if (e.key === 'ArrowLeft') dx = -increment;\n if (e.key === 'ArrowRight') dx = increment;\n if (e.key === 'ArrowUp') dy = -increment;\n if (e.key === 'ArrowDown') dy = increment;\n\n const updatedElement = selectedElement.clone();\n updatedElement.x += dx;\n updatedElement.y += dy;\n handleElementUpdate(updatedElement);\n\n return;\n }\n }\n };\n\n const handleKeyUp = (e: KeyboardEvent) => {\n // Restore selection handles after nudge ends\n if (e.key === 'ArrowUp' || e.key === 'ArrowDown' || e.key === 'ArrowLeft' || e.key === 'ArrowRight') {\n setHideHandles(false);\n }\n };\n\n window.addEventListener('keydown', handleKeyDown);\n window.addEventListener('keyup', handleKeyUp);\n return () => {\n window.removeEventListener('keydown', handleKeyDown);\n window.removeEventListener('keyup', handleKeyUp);\n };\n }, [\n enabled,\n selectedElement,\n activeChildElement,\n multiSelection,\n elements,\n handleElementUpdate,\n handleCopyElements,\n handlePasteElements,\n executeRemoveElement,\n handleSelectionChange,\n setMultiSelection,\n undoActiveArtboard,\n redoActiveArtboard,\n setHideHandles,\n duplicateLayer,\n isToolbarMenuOpen,\n ]);\n}\n\n","/**\n * useAnimatedFocusRect — smoothly interpolates a focus rect when the\n * target changes. Used by ADR-0060 multi-piece focus to \"dolly\"\n * between pieces (Back ↔ Spine ↔ Front) instead of snapping.\n *\n * Snaps (no animation) on first mount and when target becomes null,\n * since there's no meaningful \"from\" value in those cases. Cancels\n * the in-flight tween on every target change so rapid switches feel\n * responsive instead of queueing.\n */\n\nimport { useEffect, useRef, useState } from 'react';\n\nexport interface FocusRect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nconst DEFAULT_DURATION_MS = 280;\nconst easeOutCubic = (t: number): number => 1 - Math.pow(1 - t, 3);\n\nexport function useAnimatedFocusRect(\n target: FocusRect | null,\n durationMs: number = DEFAULT_DURATION_MS,\n): FocusRect | null {\n const [rect, setRect] = useState<FocusRect | null>(target);\n const rectRef = useRef<FocusRect | null>(rect);\n rectRef.current = rect;\n const rafRef = useRef<number | null>(null);\n\n const targetKey = target\n ? `${target.x},${target.y},${target.width},${target.height}`\n : 'none';\n\n useEffect(() => {\n if (rafRef.current !== null) {\n cancelAnimationFrame(rafRef.current);\n rafRef.current = null;\n }\n\n if (!target) {\n setRect(null);\n return;\n }\n\n const from = rectRef.current;\n if (!from) {\n setRect(target);\n return;\n }\n\n if (\n from.x === target.x &&\n from.y === target.y &&\n from.width === target.width &&\n from.height === target.height\n ) {\n return;\n }\n\n const start = performance.now();\n const tick = (now: number) => {\n const t = Math.min(1, (now - start) / durationMs);\n const eased = easeOutCubic(t);\n setRect({\n x: from.x + (target.x - from.x) * eased,\n y: from.y + (target.y - from.y) * eased,\n width: from.width + (target.width - from.width) * eased,\n height: from.height + (target.height - from.height) * eased,\n });\n if (t < 1) {\n rafRef.current = requestAnimationFrame(tick);\n } else {\n rafRef.current = null;\n }\n };\n rafRef.current = requestAnimationFrame(tick);\n\n return () => {\n if (rafRef.current !== null) {\n cancelAnimationFrame(rafRef.current);\n rafRef.current = null;\n }\n };\n }, [targetKey, durationMs]);\n\n return rect;\n}\n","import { TextElement } from '../core/TextElement';\nimport { wrapText, measureTextWidth } from '../core/TextMetrics';\nimport { HORIZONTAL_PADDING } from '../constants';\n\n/**\n * Get word boundaries at a given character index.\n * Returns the start and end indices of the word containing the given index.\n * Words are separated by whitespace.\n *\n * @param text The text to search in\n * @param index The character index to find the word at\n * @returns Object with start and end indices of the word\n */\nexport function getWordBoundaryAt(\n text: string,\n index: number\n): { start: number; end: number } {\n if (text.length === 0) {\n return { start: 0, end: 0 };\n }\n\n // Clamp index to valid range\n const clampedIndex = Math.max(0, Math.min(index, text.length - 1));\n\n // Word boundary pattern - non-whitespace characters\n const wordChars = /[^\\s]/;\n\n // If clicking on whitespace, find adjacent word\n if (!wordChars.test(text[clampedIndex])) {\n // Look for word to the right first\n let rightStart = clampedIndex;\n while (rightStart < text.length && !wordChars.test(text[rightStart])) {\n rightStart++;\n }\n if (rightStart < text.length) {\n // Found word to the right\n let rightEnd = rightStart;\n while (rightEnd < text.length && wordChars.test(text[rightEnd])) {\n rightEnd++;\n }\n return { start: rightStart, end: rightEnd };\n }\n\n // Look for word to the left\n let leftEnd = clampedIndex;\n while (leftEnd > 0 && !wordChars.test(text[leftEnd - 1])) {\n leftEnd--;\n }\n if (leftEnd > 0) {\n let leftStart = leftEnd;\n while (leftStart > 0 && wordChars.test(text[leftStart - 1])) {\n leftStart--;\n }\n return { start: leftStart, end: leftEnd };\n }\n\n // No words found, return current position\n return { start: clampedIndex, end: clampedIndex };\n }\n\n // Find word start (scan backwards)\n let start = clampedIndex;\n while (start > 0 && wordChars.test(text[start - 1])) {\n start--;\n }\n\n // Find word end (scan forwards)\n let end = clampedIndex;\n while (end < text.length && wordChars.test(text[end])) {\n end++;\n }\n\n return { start, end };\n}\n\n/**\n * Get line info for a cursor position - which line it's on and the X position within that line\n */\nexport function getCursorLineInfo(\n element: TextElement,\n cursorIndex: number\n): { lineIndex: number; lineCount: number; xPositionInLine: number; lineStartIndex: number } {\n const { fontSize, fontFamily, bold, italic, textAlign } = element;\n const richText = element.getRichText();\n\n const visualBbox = element.getVisualBoundingBox();\n const maxWidth = visualBbox.width;\n\n let wrappingWidth = maxWidth;\n let horizontalPadding = 0;\n\n if (element.transformType === 'custom') {\n horizontalPadding = HORIZONTAL_PADDING;\n wrappingWidth = maxWidth - (horizontalPadding * 2);\n }\n\n const lines = wrapText(\n element.getText(),\n wrappingWidth,\n fontSize,\n fontFamily,\n bold,\n italic,\n undefined,\n true\n );\n\n // Find which line the cursor is on\n let charStartIndex = 0;\n let lineIndex = 0;\n\n for (let i = 0; i < lines.length; i++) {\n const lineEndIndex = charStartIndex + lines[i].length;\n if (cursorIndex <= lineEndIndex) {\n lineIndex = i;\n break;\n }\n charStartIndex += lines[i].length + 1; // +1 for newline\n if (i === lines.length - 1) {\n lineIndex = i;\n }\n }\n\n // Calculate X position within the line\n const line = lines[lineIndex];\n const posInLine = cursorIndex - charStartIndex;\n\n // Handle space collapsing\n let visibleLineText = line;\n let leadingSpacesCount = 0;\n\n const isParagraphStart = lineIndex === 0;\n const isParagraphEnd = lineIndex === lines.length - 1;\n\n if (!isParagraphStart) {\n const leadingSpacesMatch = line.match(/^ +/);\n leadingSpacesCount = leadingSpacesMatch ? leadingSpacesMatch[0].length : 0;\n if (leadingSpacesCount > 0) {\n visibleLineText = visibleLineText.substring(leadingSpacesCount);\n }\n }\n\n if (!isParagraphEnd) {\n const trailingSpacesMatch = visibleLineText.match(/ +$/);\n const trailingSpacesCount = trailingSpacesMatch ? trailingSpacesMatch[0].length : 0;\n if (trailingSpacesCount > 0) {\n visibleLineText = visibleLineText.substring(0, visibleLineText.length - trailingSpacesCount);\n }\n }\n\n const lineWidth = measureTextWidth(visibleLineText, fontSize, fontFamily, bold, italic);\n\n let lineStartX = horizontalPadding;\n if (textAlign === 'center') {\n lineStartX = (maxWidth - lineWidth) / 2;\n } else if (textAlign === 'right') {\n lineStartX = maxWidth - horizontalPadding - lineWidth;\n }\n\n // Measure X position up to cursor within line\n let xPosition = lineStartX;\n const measureEnd = Math.min(posInLine, line.length);\n\n for (let i = 0; i < measureEnd; i++) {\n if (!isParagraphStart && i < leadingSpacesCount) continue;\n\n const char = line[i];\n const globalIndex = charStartIndex + i;\n const style = richText.getStyleAt(globalIndex);\n\n const charFontSize = style.fontSize || fontSize;\n const charFontFamily = style.fontFamily || fontFamily;\n const charBold = style.bold !== undefined ? style.bold : bold;\n const charItalic = style.italic !== undefined ? style.italic : italic;\n\n xPosition += measureTextWidth(char, charFontSize, charFontFamily, charBold, charItalic);\n }\n\n return {\n lineIndex,\n lineCount: lines.length,\n xPositionInLine: xPosition,\n lineStartIndex: charStartIndex\n };\n}\n\n/**\n * Move cursor up or down by one line, trying to maintain the same X position\n */\nexport function moveCursorVertically(\n element: TextElement,\n cursorIndex: number,\n direction: 'up' | 'down'\n): number {\n const { fontSize } = element;\n const lineHeight = 1.2;\n const lineSpacing = fontSize * lineHeight;\n\n const info = getCursorLineInfo(element, cursorIndex);\n\n // Calculate target line\n const targetLineIndex = direction === 'up'\n ? Math.max(0, info.lineIndex - 1)\n : Math.min(info.lineCount - 1, info.lineIndex + 1);\n\n // If already at first/last line, stay at same position\n if (targetLineIndex === info.lineIndex) {\n return cursorIndex;\n }\n\n // Use getCursorIndexFromPoint with the target line's Y and same X\n const targetY = targetLineIndex * lineSpacing + lineSpacing / 2; // Middle of target line\n\n return getCursorIndexFromPoint(element, {\n x: info.xPositionInLine,\n y: targetY\n });\n}\n\n/**\n * Calculates the cursor character index based on a point relative to the text element's\n * top-left corner (local coordinates).\n * \n * @param element The text element\n * @param point Local coordinates {x, y} relative to the element's visual bounding box top-left\n * @returns The character index closest to the point\n */\nexport function getCursorIndexFromPoint(\n element: TextElement,\n point: { x: number; y: number }\n): number {\n const { fontSize, fontFamily, bold, italic, textAlign } = element;\n const richText = element.getRichText();\n \n // Determine wrapping configuration\n // For CustomTransform, width is fixed and we have padding\n const visualBbox = element.getVisualBoundingBox();\n const maxWidth = visualBbox.width;\n \n let wrappingWidth = maxWidth;\n let horizontalPadding = 0;\n \n // Check transform type - currently focusing on 'custom' which has padding\n if (element.transformType === 'custom') {\n horizontalPadding = HORIZONTAL_PADDING;\n wrappingWidth = maxWidth - (horizontalPadding * 2);\n }\n\n // Get wrapped lines\n // Note: This must match getWrappedTextForEditing logic in CanvasEditor\n // and rendering logic in CanvasRenderer to ensure consistency.\n const lines = wrapText(\n element.getText(),\n wrappingWidth,\n fontSize,\n fontFamily,\n bold,\n italic,\n undefined, // No locked line count\n true // strict: true to match rendering logic\n );\n\n // Calculate layout metrics\n const lineHeight = 1.2; // Default line height multiplier\n const lineSpacing = fontSize * lineHeight;\n \n // Adjust input point for padding if necessary\n // We treat the point as relative to the Visual Bounding Box.\n // Text rendering usually starts at (padding, 0) relative to bbox for CustomTransform?\n // CustomTransform getBoundingBox returns y = center - height/2.\n // Text is rendered starting at that top-y.\n // So Y is fine. X needs padding adjustment.\n \n // Find which line the Y coordinate falls into\n let lineIndex = 0;\n let currentY = 0;\n \n // If above the first line\n if (point.y < 0) {\n return 0;\n }\n\n for (let i = 0; i < lines.length; i++) {\n // Check if point is within this line's vertical span\n // Using lineSpacing as the height per line\n if (point.y >= currentY && point.y < currentY + lineSpacing) {\n lineIndex = i;\n break;\n }\n \n currentY += lineSpacing;\n \n // If we reached the last line and haven't found it (y is below text),\n // stick to the last line\n if (i === lines.length - 1) {\n lineIndex = i;\n }\n }\n \n // Calculate the starting character index for this line\n // Since lines are joined by '\\n' in the editor, we count line length + 1 for newline\n let charStartIndex = 0;\n for (let i = 0; i < lineIndex; i++) {\n charStartIndex += lines[i].length + 1; // +1 for the newline character\n }\n \n // Now find the character within the line\n const line = lines[lineIndex];\n \n // Handle space collapsing logic to determine visual line width\n // This matches renderTextStroke logic\n let visibleLineText = line;\n let leadingSpacesCount = 0;\n let trailingSpacesCount = 0;\n\n const isParagraphStart = lineIndex === 0;\n const isParagraphEnd = lineIndex === lines.length - 1;\n\n // Hide leading spaces when NOT at paragraph start\n if (!isParagraphStart) {\n const leadingSpacesMatch = line.match(/^ +/);\n leadingSpacesCount = leadingSpacesMatch ? leadingSpacesMatch[0].length : 0;\n if (leadingSpacesCount > 0) {\n visibleLineText = visibleLineText.substring(leadingSpacesCount);\n }\n }\n\n // Hide trailing spaces when NOT at paragraph end\n if (!isParagraphEnd) {\n const trailingSpacesMatch = visibleLineText.match(/ +$/);\n trailingSpacesCount = trailingSpacesMatch ? trailingSpacesMatch[0].length : 0;\n if (trailingSpacesCount > 0) {\n visibleLineText = visibleLineText.substring(0, visibleLineText.length - trailingSpacesCount);\n }\n }\n\n const lineWidth = measureTextWidth(visibleLineText, fontSize, fontFamily, bold, italic);\n \n // Calculate line start X based on alignment\n let lineStartX = horizontalPadding;\n if (textAlign === 'center') {\n lineStartX = (maxWidth - lineWidth) / 2;\n } else if (textAlign === 'right') {\n lineStartX = maxWidth - horizontalPadding - lineWidth;\n }\n \n // Relative X on the line\n const relativeX = point.x - lineStartX;\n \n // If clicked before the start of line\n if (relativeX <= 0) {\n // Return start of visual content\n return charStartIndex + leadingSpacesCount;\n }\n \n // If clicked after the end of line\n if (relativeX >= lineWidth) {\n // Return end of visual content\n return charStartIndex + line.length - trailingSpacesCount;\n }\n \n // Iterate characters to find the split point\n let currentX = 0;\n \n // We need to iterate over the ORIGINAL line characters to correctly map to indices\n for (let i = 0; i < line.length; i++) {\n // Skip collapsed characters (effectively 0 width)\n if (!isParagraphStart && i < leadingSpacesCount) continue;\n if (!isParagraphEnd && i >= line.length - trailingSpacesCount) continue;\n\n const char = line[i];\n const globalIndex = charStartIndex + i;\n \n // Get style for this character from RichText\n const style = richText.getStyleAt(globalIndex);\n \n // Use character-specific style or fallback to element defaults\n const charFontSize = style.fontSize || fontSize;\n const charFontFamily = style.fontFamily || fontFamily;\n const charBold = style.bold !== undefined ? style.bold : bold;\n const charItalic = style.italic !== undefined ? style.italic : italic;\n \n const charWidth = measureTextWidth(char, charFontSize, charFontFamily, charBold, charItalic);\n \n // Check if the point is within this character's horizontal span\n // We use midpoint to decide whether to place cursor before or after\n if (relativeX < currentX + charWidth / 2) {\n return globalIndex;\n }\n \n currentX += charWidth;\n }\n \n // If we fall through, it's at the end of the line\n return charStartIndex + line.length;\n}\n\n","/**\n * useMarqueeSelection - Manages marquee selection box state and logic\n */\n\nimport { useState, useEffect } from 'react';\nimport type { TextElement } from '../../../core/TextElement.js';\nimport type { ImageElement } from '../../../core/ImageElement.js';\nimport type { GroupElement } from '../../../core/GroupElement.js';\nimport type { ShapeElement } from '../../../core/ShapeElement.js';\nimport type { PathElement } from '../../../core/PathElement.js';\n\ntype CanvasElement = TextElement | ImageElement | GroupElement | ShapeElement | PathElement;\n\nexport function useMarqueeSelection() {\n const [isMarqueeSelecting, setIsMarqueeSelecting] = useState<boolean>(false);\n const [marqueeStart, setMarqueeStart] = useState<{ x: number; y: number }>({\n x: 0,\n y: 0,\n });\n const [marqueeEnd, setMarqueeEnd] = useState<{ x: number; y: number }>({\n x: 0,\n y: 0,\n });\n const [marqueePreviewSelection, setMarqueePreviewSelection] = useState<string[]>([]);\n\n // Debug marquee state changes\n useEffect(() => {\n }, [isMarqueeSelecting]);\n\n const startMarqueeSelection = (x: number, y: number) => {\n setIsMarqueeSelecting(true);\n setMarqueeStart({ x, y });\n setMarqueeEnd({ x, y });\n setMarqueePreviewSelection([]);\n };\n\n const updateMarqueeSelection = (\n x: number,\n y: number,\n elements: CanvasElement[]\n ) => {\n setMarqueeEnd({ x, y });\n\n // Calculate which elements are currently overlapping with the marquee\n const minX = Math.min(marqueeStart.x, x);\n const maxX = Math.max(marqueeStart.x, x);\n const minY = Math.min(marqueeStart.y, y);\n const maxY = Math.max(marqueeStart.y, y);\n\n const previewSelectedIds: string[] = [];\n elements.forEach((element) => {\n // Skip invisible elements\n if (element.visible === false) return;\n\n const bbox = element.getVisualBoundingBox();\n\n // Check if element's bounding box overlaps with marquee\n const elementLeft = bbox.x;\n const elementRight = bbox.x + bbox.width;\n const elementTop = bbox.y;\n const elementBottom = bbox.y + bbox.height;\n\n const overlaps = !(\n elementRight < minX || // element is left of marquee\n elementLeft > maxX || // element is right of marquee\n elementBottom < minY || // element is above marquee\n elementTop > maxY // element is below marquee\n );\n\n if (overlaps) {\n previewSelectedIds.push(element.id);\n }\n });\n\n setMarqueePreviewSelection(previewSelectedIds);\n };\n\n const finishMarqueeSelection = (\n onMultiSelectionChange: (ids: string[]) => void\n ) => {\n\n // Capture the selection before clearing state\n const selectedIds = [...marqueePreviewSelection];\n\n setIsMarqueeSelecting(false);\n\n // Use the preview selection as the final selection\n if (selectedIds.length > 0) {\n onMultiSelectionChange(selectedIds);\n }\n\n // Clear preview\n setMarqueePreviewSelection([]);\n };\n\n return {\n isMarqueeSelecting,\n marqueeStart,\n marqueeEnd,\n marqueePreviewSelection,\n startMarqueeSelection,\n updateMarqueeSelection,\n finishMarqueeSelection,\n };\n}\n","/**\n * PenToolManager - Manages pen tool state for creating custom paths\n * Handles:\n * - Click to add anchor points\n * - Drag after click to create bezier handles\n * - Path preview rendering\n * - Path completion (close or finish)\n */\n\nimport type { PathPoint, Point } from '../types/index.js';\nimport { PathElement } from './PathElement.js';\nimport { getThemeAccentColor, getThemePenAnchorFillColor, getThemePenHandleColor, getThemePenPathColor, getThemeShapeFillColor } from '../constants.js';\n\nexport interface PenToolState {\n active: boolean;\n pathElement: PathElement | null;\n points: PathPoint[];\n currentPoint: PathPoint | null;\n isDraggingHandle: boolean;\n previewPoint: Point | null;\n isClosing: boolean; // Track if we're in the process of closing the path\n closingClickPos: Point | null; // Position where user clicked to close (for handle calculation)\n}\n\nexport class PenToolManager {\n private state: PenToolState = {\n active: false,\n pathElement: null,\n points: [],\n currentPoint: null,\n isDraggingHandle: false,\n previewPoint: null,\n isClosing: false,\n closingClickPos: null,\n };\n\n /**\n * Start pen tool mode with a new path element\n */\n startPath(pathElement: PathElement): void {\n this.state.active = true;\n this.state.pathElement = pathElement;\n this.state.points = [];\n this.state.currentPoint = null;\n this.state.isDraggingHandle = false;\n this.state.previewPoint = null;\n this.state.isClosing = false;\n this.state.closingClickPos = null;\n }\n\n /**\n * Edit an existing path element\n * Loads the existing points and enters edit mode\n */\n editPath(pathElement: PathElement): void {\n this.state.active = true;\n this.state.pathElement = pathElement;\n // Load existing points from the path element\n this.state.points = pathElement.transformData.points.map((p) => ({ ...p }));\n this.state.currentPoint = null;\n this.state.isDraggingHandle = false;\n this.state.previewPoint = null;\n this.state.isClosing = false;\n this.state.closingClickPos = null;\n }\n\n /**\n * Check if pen tool is active\n */\n isActive(): boolean {\n return this.state.active;\n }\n\n /**\n * Get current state\n */\n getState(): PenToolState {\n return { ...this.state };\n }\n\n /**\n * Update preview point (for cursor following)\n */\n setPreviewPoint(x: number, y: number): void {\n this.state.previewPoint = { x, y };\n }\n\n /**\n * Clear preview point\n */\n clearPreviewPoint(): void {\n this.state.previewPoint = null;\n }\n\n /**\n * Add a new anchor point at the given position (relative to path element)\n * Returns the created point\n */\n addPoint(x: number, y: number): PathPoint {\n const newPoint: PathPoint = {\n id: `point-${Date.now()}-${Math.random()}`,\n x,\n y,\n type: 'corner', // Default to corner, will convert to bezier if handle is dragged\n };\n\n this.state.points.push(newPoint);\n this.state.currentPoint = newPoint;\n this.state.isDraggingHandle = false;\n\n return newPoint;\n }\n\n /**\n * Start dragging handle for the current point\n */\n startDraggingHandle(): void {\n if (!this.state.currentPoint) return;\n this.state.isDraggingHandle = true;\n\n // Convert point to bezier type if not already\n if (this.state.currentPoint.type === 'corner') {\n this.state.currentPoint.type = 'bezier';\n }\n }\n\n /**\n * Update handle position for current point (relative to point position)\n */\n updateHandle(handleX: number, handleY: number): void {\n if (!this.state.currentPoint || !this.state.isDraggingHandle) return;\n\n if (this.state.isClosing && this.state.closingClickPos) {\n // When closing, calculate offset from where the user clicked (not from first point)\n const offsetX = handleX - this.state.closingClickPos.x;\n const offsetY = handleY - this.state.closingClickPos.y;\n\n const firstPoint = this.state.points[0];\n\n // Set first point's handleIn (curve arriving at first point from last point)\n firstPoint.handleIn = { x: offsetX, y: offsetY };\n\n // Also set handleOut as the mirror so both handles are visible\n firstPoint.handleOut = { x: -offsetX, y: -offsetY };\n\n if (firstPoint.type === 'corner') {\n firstPoint.type = 'bezier';\n }\n } else {\n // Normal point: Calculate handle offset from point\n const offsetX = handleX - this.state.currentPoint.x;\n const offsetY = handleY - this.state.currentPoint.y;\n\n // Set handleOut (control point for outgoing curve)\n this.state.currentPoint.handleOut = { x: offsetX, y: offsetY };\n\n // For smooth points, mirror the handle\n if (this.state.currentPoint.type === 'smooth') {\n this.state.currentPoint.handleIn = { x: -offsetX, y: -offsetY };\n } else if (this.state.currentPoint.type === 'bezier') {\n // For bezier, set handleIn to opposite direction (mirrored)\n // User can adjust independently later in edit mode\n this.state.currentPoint.handleIn = { x: -offsetX, y: -offsetY };\n }\n }\n }\n\n /**\n * Finish dragging handle\n * If closing the path, this will complete the closing operation\n */\n finishDraggingHandle(): void {\n this.state.isDraggingHandle = false;\n\n // If we were closing the path, complete it now\n if (this.state.isClosing) {\n this.closePath();\n } else {\n this.state.currentPoint = null;\n }\n }\n\n /**\n * Check if we can close the path (clicking near first point)\n */\n canClosePath(x: number, y: number, threshold: number = 10): boolean {\n if (this.state.points.length < 3) return false;\n\n const firstPoint = this.state.points[0];\n const dx = x - firstPoint.x;\n const dy = y - firstPoint.y;\n const distance = Math.sqrt(dx * dx + dy * dy);\n\n return distance <= threshold;\n }\n\n /**\n * Start closing the path (allows handle dragging for the closing point)\n * Records where the user clicked so we can calculate handle offsets correctly\n */\n startClosing(clickX: number, clickY: number): void {\n if (this.state.points.length < 3) return;\n\n this.state.isClosing = true;\n this.state.closingClickPos = { x: clickX, y: clickY };\n // Set the first point as current (for reference, though we'll use closingClickPos for offset calc)\n this.state.currentPoint = this.state.points[0];\n }\n\n /**\n * Check if currently in closing mode\n */\n isClosingPath(): boolean {\n return this.state.isClosing;\n }\n\n /**\n * Recenter the path element to match where the path was actually drawn\n * Adjusts element position and converts points to be relative to the actual center\n */\n private recenterPath(): void {\n if (!this.state.pathElement || this.state.points.length === 0) return;\n\n // Calculate the bounding box of the drawn points\n let minX = Infinity,\n minY = Infinity,\n maxX = -Infinity,\n maxY = -Infinity;\n for (const point of this.state.points) {\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 // Calculate the center of the drawn points\n const centerX = (minX + maxX) / 2;\n const centerY = (minY + maxY) / 2;\n\n // Update element position to the actual drawn center\n this.state.pathElement.x += centerX;\n this.state.pathElement.y += centerY;\n\n // Adjust all points to be relative to the new center\n for (const point of this.state.points) {\n point.x -= centerX;\n point.y -= centerY;\n }\n }\n\n /**\n * Close the path (connect last point to first)\n */\n closePath(): void {\n if (!this.state.pathElement) return;\n\n // Recenter the path to match where it was drawn\n this.recenterPath();\n\n // Update path element with points and set closed = true\n this.state.pathElement.transformData.points = [...this.state.points];\n this.state.pathElement.transformData.closed = true;\n\n // Enable fill with default color for closed shapes\n this.state.pathElement.transformData.fillEnabled = true;\n this.state.pathElement.transformData.fillColor = getThemeShapeFillColor();\n\n this.state.pathElement.updateBounds();\n\n // Exit pen mode\n this.endPath();\n }\n\n /**\n * Finish the path without closing (leave open)\n */\n finishPath(): void {\n if (!this.state.pathElement) return;\n\n // Recenter the path to match where it was drawn\n this.recenterPath();\n\n // Update path element with points and set closed = false\n this.state.pathElement.transformData.points = [...this.state.points];\n this.state.pathElement.transformData.closed = false;\n this.state.pathElement.updateBounds();\n\n // Exit pen mode\n this.endPath();\n }\n\n /**\n * Delete the last added point (undo last point)\n */\n deleteLastPoint(): void {\n if (this.state.points.length === 0) return;\n this.state.points.pop();\n this.state.currentPoint = null;\n }\n\n /**\n * Cancel path creation entirely\n */\n cancelPath(): PathElement | null {\n const element = this.state.pathElement;\n this.state.active = false;\n this.state.pathElement = null;\n this.state.points = [];\n this.state.currentPoint = null;\n this.state.isDraggingHandle = false;\n this.state.previewPoint = null;\n this.state.isClosing = false;\n this.state.closingClickPos = null;\n return element; // Return element so it can be removed\n }\n\n /**\n * End path creation mode\n */\n private endPath(): void {\n this.state.active = false;\n this.state.pathElement = null;\n this.state.points = [];\n this.state.currentPoint = null;\n this.state.isDraggingHandle = false;\n this.state.previewPoint = null;\n this.state.isClosing = false;\n this.state.closingClickPos = null;\n }\n\n /**\n * Get points for rendering preview\n */\n getPoints(): PathPoint[] {\n return this.state.points;\n }\n\n /**\n * Get current point being edited\n */\n getCurrentPoint(): PathPoint | null {\n return this.state.currentPoint;\n }\n\n /**\n * Check if currently dragging a handle\n */\n isDraggingHandle(): boolean {\n return this.state.isDraggingHandle;\n }\n\n /**\n * Render pen tool preview on canvas\n */\n renderPreview(ctx: CanvasRenderingContext2D, pathElement: PathElement): void {\n if (!this.state.active || this.state.points.length === 0) return;\n\n const { points, previewPoint, currentPoint: _currentPoint } = this.state;\n\n ctx.save();\n\n // Translate to path element position\n ctx.translate(pathElement.x, pathElement.y);\n\n // Draw existing path segments\n ctx.strokeStyle = getThemePenPathColor(); // Theme-aware path stroke\n ctx.lineWidth = 2;\n ctx.setLineDash([]); // Solid line (not dashed)\n ctx.beginPath();\n\n // Draw path\n if (points.length > 0) {\n const firstPoint = points[0];\n ctx.moveTo(firstPoint.x, firstPoint.y);\n\n for (let i = 1; i < points.length; i++) {\n const prevPoint = points[i - 1];\n const currentPoint = points[i];\n\n // Draw bezier curve if handles exist\n const hasCurve = prevPoint.handleOut || currentPoint.handleIn;\n\n if (hasCurve) {\n const cp1x = prevPoint.x + (prevPoint.handleOut?.x || 0);\n const cp1y = prevPoint.y + (prevPoint.handleOut?.y || 0);\n const cp2x = currentPoint.x + (currentPoint.handleIn?.x || 0);\n const cp2y = currentPoint.y + (currentPoint.handleIn?.y || 0);\n\n ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, currentPoint.x, currentPoint.y);\n } else {\n ctx.lineTo(currentPoint.x, currentPoint.y);\n }\n }\n\n // Draw closing segment if in closing mode OR if editing a closed path\n const isClosed = this.state.isClosing || pathElement.transformData.closed;\n if (isClosed && points.length > 2) {\n const lastPoint = points[points.length - 1];\n const firstPoint = points[0];\n\n // Draw bezier curve from last to first using their handles\n const hasCurve = lastPoint.handleOut || firstPoint.handleIn;\n\n if (hasCurve) {\n const cp1x = lastPoint.x + (lastPoint.handleOut?.x || 0);\n const cp1y = lastPoint.y + (lastPoint.handleOut?.y || 0);\n const cp2x = firstPoint.x + (firstPoint.handleIn?.x || 0);\n const cp2y = firstPoint.y + (firstPoint.handleIn?.y || 0);\n\n ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, firstPoint.x, firstPoint.y);\n } else {\n ctx.lineTo(firstPoint.x, firstPoint.y);\n }\n } else if (previewPoint && !this.state.isDraggingHandle) {\n // Draw line to preview point if present (only when not closing)\n ctx.lineTo(previewPoint.x, previewPoint.y);\n }\n\n ctx.stroke();\n\n // Draw fill for closed paths\n if (isClosed && pathElement.transformData.fillEnabled) {\n ctx.fillStyle = pathElement.transformData.fillColor || getThemeShapeFillColor();\n ctx.globalAlpha = pathElement.transformData.fillOpacity ?? 1;\n ctx.fill();\n ctx.globalAlpha = 1; // Reset alpha\n }\n }\n\n ctx.setLineDash([]); // Reset dash\n\n // Draw anchor points\n for (let i = 0; i < points.length; i++) {\n const point = points[i];\n const isFirstPoint = i === 0;\n\n // Draw special ring indicator for first point (to show where path can be closed)\n if (isFirstPoint && points.length > 1) {\n ctx.strokeStyle = getThemeAccentColor(); // Theme color ring\n ctx.lineWidth = 2;\n ctx.beginPath();\n ctx.arc(point.x, point.y, 8, 0, Math.PI * 2);\n ctx.stroke();\n }\n\n // Draw point\n ctx.fillStyle = getThemePenAnchorFillColor(); // Theme background color so anchor is visible on light/dark themes\n ctx.strokeStyle = getThemeAccentColor(); // Use theme color for all points\n ctx.lineWidth = 2;\n ctx.beginPath();\n ctx.arc(point.x, point.y, 4, 0, Math.PI * 2);\n ctx.fill();\n ctx.stroke();\n\n // Draw handles if point has them\n if (point.handleIn) {\n const handleInX = point.x + point.handleIn.x;\n const handleInY = point.y + point.handleIn.y;\n\n // Handle line\n ctx.strokeStyle = getThemePenHandleColor();\n ctx.lineWidth = 1;\n ctx.beginPath();\n ctx.moveTo(point.x, point.y);\n ctx.lineTo(handleInX, handleInY);\n ctx.stroke();\n\n // Handle point\n ctx.fillStyle = getThemePenHandleColor();\n ctx.beginPath();\n ctx.arc(handleInX, handleInY, 3, 0, Math.PI * 2);\n ctx.fill();\n }\n\n if (point.handleOut) {\n const handleOutX = point.x + point.handleOut.x;\n const handleOutY = point.y + point.handleOut.y;\n\n // Handle line\n ctx.strokeStyle = getThemePenHandleColor();\n ctx.lineWidth = 1;\n ctx.beginPath();\n ctx.moveTo(point.x, point.y);\n ctx.lineTo(handleOutX, handleOutY);\n ctx.stroke();\n\n // Handle point\n ctx.fillStyle = getThemePenHandleColor();\n ctx.beginPath();\n ctx.arc(handleOutX, handleOutY, 3, 0, Math.PI * 2);\n ctx.fill();\n }\n }\n\n // Highlight first point if we can close the path\n if (previewPoint && this.canClosePath(previewPoint.x, previewPoint.y)) {\n const firstPoint = points[0];\n ctx.strokeStyle = getThemeAccentColor(); // Theme color to indicate closeable\n ctx.lineWidth = 3;\n ctx.beginPath();\n ctx.arc(firstPoint.x, firstPoint.y, 6, 0, Math.PI * 2);\n ctx.stroke();\n }\n\n // Draw preview point\n if (previewPoint && !this.state.isDraggingHandle) {\n const themeColor = getThemeAccentColor();\n // Convert hex to rgba\n const hex = themeColor.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 ctx.fillStyle = `rgba(${r}, ${g}, ${b}, 0.3)`;\n ctx.strokeStyle = themeColor;\n ctx.lineWidth = 2;\n ctx.beginPath();\n ctx.arc(previewPoint.x, previewPoint.y, 4, 0, Math.PI * 2);\n ctx.fill();\n ctx.stroke();\n }\n\n ctx.restore();\n }\n}\n","/**\n * usePenTool - Manages pen tool mode for path elements\n */\n\nimport { useState, useEffect } from 'react';\nimport { PenToolManager } from '../../../core/PenToolManager.js';\nimport { PathElement } from '../../../core/PathElement.js';\nimport type { BaseElement } from '../../../core/BaseElement.js';\nimport { Icon } from '@iconify/react';\nimport { renderToStaticMarkup } from 'react-dom/server';\nimport { createLogger } from '../../../utils/logger.js';\n\nconst logger = createLogger('usePenTool');\n\ninterface UsePenToolProps {\n selectedElement: BaseElement | null | undefined;\n}\n\nexport function usePenTool({ selectedElement }: UsePenToolProps) {\n // Create pen tool manager\n const [penTool] = useState(() => new PenToolManager());\n\n // Custom pen cursor\n const [penCursor, setPenCursor] = useState<string>('crosshair');\n\n // Create custom pen cursor from icon\n useEffect(() => {\n try {\n // Get the pen icon SVG content\n const penIconContent = renderToStaticMarkup(<Icon icon=\"lucide:pen-tool\" width={24} height={24} />).replace(\n /<svg[^>]*>|<\\/svg>/g,\n ''\n );\n\n // Create SVG with white stroke behind the black stroke\n const cursorSvg = `\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n <!-- White background stroke (thicker) -->\n <g stroke=\"white\" stroke-width=\"4\" fill=\"none\" stroke-linejoin=\"round\" stroke-linecap=\"round\">\n ${penIconContent}\n </g>\n <!-- Black pen icon stroke on top (thinner) -->\n <g stroke=\"#000000\" stroke-width=\"2\" fill=\"none\" stroke-linejoin=\"round\" stroke-linecap=\"round\">\n ${penIconContent}\n </g>\n </svg>\n `.trim();\n\n // Create a data URL from the SVG\n const encodedSvg = encodeURIComponent(cursorSvg);\n const dataUrl = `data:image/svg+xml,${encodedSvg}`;\n\n // Set cursor with hotspot at pen tip (lower-left corner where tip touches)\n setPenCursor(`url('${dataUrl}') 0 0, crosshair`);\n } catch (error) {\n logger.error('Failed to create pen cursor:', error);\n setPenCursor('crosshair');\n }\n }, []);\n\n // Auto-enter pen mode when path element with no points is selected\n useEffect(() => {\n if (\n selectedElement instanceof PathElement &&\n selectedElement.transformData.points.length === 0 &&\n !penTool.isActive()\n ) {\n penTool.startPath(selectedElement);\n }\n }, [selectedElement, penTool]);\n\n return {\n penTool,\n penCursor,\n };\n}\n","/**\n * useActiveChild - Manages active child element for groups\n */\n\nimport { useState, useCallback, useEffect, useRef } from 'react';\nimport { GroupElement } from '../../../core/GroupElement.js';\nimport type { TextElement } from '../../../core/TextElement.js';\nimport type { ImageElement } from '../../../core/ImageElement.js';\nimport type { ShapeElement } from '../../../core/ShapeElement.js';\nimport type { PathElement } from '../../../core/PathElement.js';\nimport type { TransformHandles } from '../../../core/TransformHandles.js';\n// Note: GroupElement is imported above as a value, used in type here\n\n/** Elements that can be on the canvas */\ntype CanvasElement = TextElement | ImageElement | GroupElement | ShapeElement | PathElement;\n\n/** Elements that can be children of a group (including nested groups) */\ntype ChildElement = TextElement | ImageElement | ShapeElement | PathElement | GroupElement;\n\ninterface UseActiveChildProps {\n elements: CanvasElement[];\n selectedElement: CanvasElement | null | undefined;\n transformHandles: TransformHandles;\n onActiveChildChange?: (child: CanvasElement | null) => void;\n}\n\nexport function useActiveChild({\n elements,\n selectedElement,\n transformHandles,\n onActiveChildChange,\n}: UseActiveChildProps) {\n // Active child for editing (when editing a child within a group)\n const [activeChildElement, setActiveChildElement] = useState<ChildElement | null>(null);\n\n // Track which child ID is being edited (for text editing in groups)\n const editingChildIdRef = useRef<string | null>(null);\n\n // Helper to update active child and notify parent\n const updateActiveChild = useCallback(\n (child: ChildElement | null) => {\n setActiveChildElement(child);\n onActiveChildChange?.(child);\n },\n [onActiveChildChange]\n );\n\n // Sync activeChildElement when elements array updates\n // When a child in a group is modified, we need to get the fresh reference from the updated group\n useEffect(() => {\n if (activeChildElement && selectedElement instanceof GroupElement) {\n const updatedChild = selectedElement.children.find((c) => c.id === activeChildElement.id);\n if (updatedChild && updatedChild !== activeChildElement) {\n // Child was updated, refresh our reference (exclude GroupElement for active child)\n if (!(updatedChild instanceof GroupElement)) {\n setActiveChildElement(updatedChild);\n // Notify parent that active child has been updated\n onActiveChildChange?.(updatedChild);\n transformHandles.update(updatedChild);\n }\n }\n }\n }, [elements, activeChildElement, selectedElement, transformHandles, onActiveChildChange]);\n\n return {\n activeChildElement,\n setActiveChildElement,\n updateActiveChild,\n editingChildIdRef,\n };\n}\n","/**\n * useHoverState - Manages hover detection and cursor management with animation timing\n */\n\nimport { useState, useEffect, useCallback } from 'react';\nimport type { GroupElement } from '../../../core/GroupElement.js';\nimport type { LocalHoverState } from '../types/index.js';\nimport type { TextElement } from '../../../core/TextElement.js';\nimport type { ImageElement } from '../../../core/ImageElement.js';\n\ninterface UseHoverStateProps {\n onHoverChange?: (elementId: string | null) => void;\n}\n\nexport function useHoverState({ onHoverChange }: UseHoverStateProps) {\n // Hover state with timing for animation\n const [hoverState, setHoverStateInternal] = useState<LocalHoverState>({\n type: null,\n data: null,\n startTime: null,\n });\n\n // Wrapped setter that automatically adds startTime for new hovers\n const setHoverState = useCallback((newState: Omit<LocalHoverState, 'startTime'>) => {\n setHoverStateInternal((prevState) => {\n // If transitioning to a new hover (type or element changed), record start time\n const isNewHover =\n newState.type !== prevState.type ||\n (newState.type === 'element' && (newState.data as { id?: string })?.id !== (prevState.data as { id?: string })?.id) ||\n (newState.type === 'group-sibling' && (newState.data as { id?: string })?.id !== (prevState.data as { id?: string })?.id);\n\n return {\n ...newState,\n startTime: isNewHover && newState.type !== null ? performance.now() : prevState.startTime,\n };\n });\n }, []);\n\n // Notify parent of hover changes for layers panel synchronization\n useEffect(() => {\n if (!onHoverChange) return;\n\n let hoveredElementId: string | null = null;\n\n // Extract element ID from hover state\n if (hoverState.type === 'element' || hoverState.type === 'group-sibling') {\n const hoveredElement = hoverState.data as TextElement | ImageElement | GroupElement | null;\n hoveredElementId = hoveredElement?.id ?? null;\n }\n\n onHoverChange(hoveredElementId);\n }, [hoverState, onHoverChange]);\n\n return {\n hoverState,\n setHoverState,\n };\n}\n","import { RotationUtils } from './RotationUtils.js';\n\n/** Minimal interface for objects that can be used with CoordinateTransform */\ninterface TransformableElement {\n x: number;\n y: number;\n rotation?: number;\n flipH?: boolean;\n flipV?: boolean;\n}\n\n/**\n * CoordinateTransform - Handles coordinate transformations between world and local space\n *\n * Manages transformations including:\n * - Translation (x, y position)\n * - Rotation\n * - Horizontal and vertical flips\n */\nexport class CoordinateTransform {\n x: number;\n y: number;\n rotation: number;\n flipH: boolean;\n flipV: boolean;\n\n /**\n * @param x - Center X position in world space\n * @param y - Center Y position in world space\n * @param rotation - Rotation in degrees (positive = clockwise)\n * @param flipH - Horizontal flip\n * @param flipV - Vertical flip\n */\n constructor(x: number, y: number, rotation: number, flipH: boolean, flipV: boolean) {\n this.x = x;\n this.y = y;\n this.rotation = rotation;\n this.flipH = flipH;\n this.flipV = flipV;\n }\n\n /**\n * Convert world coordinates to local coordinates\n * @param worldX - X coordinate in world space\n * @param worldY - Y coordinate in world space\n * @returns Local coordinates\n */\n worldToLocal(worldX: number, worldY: number) {\n // Translate to origin\n const dx = worldX - this.x;\n const dy = worldY - this.y;\n\n // Rotate (inverse rotation)\n const rotRad = RotationUtils.toRadiansInverse(this.rotation);\n const cos = Math.cos(rotRad);\n const sin = Math.sin(rotRad);\n\n let localX = dx * cos + dy * sin;\n let localY = -dx * sin + dy * cos;\n\n // Apply flips\n if (this.flipH) localX = -localX;\n if (this.flipV) localY = -localY;\n\n return { x: localX, y: localY };\n }\n\n /**\n * Convert local coordinates to world coordinates\n * @param localX - X coordinate in local space\n * @param localY - Y coordinate in local space\n * @returns World coordinates\n */\n localToWorld(localX: number, localY: number) {\n // Unapply flips\n let x = this.flipH ? -localX : localX;\n let y = this.flipV ? -localY : localY;\n\n // Rotate (forward rotation)\n const rotRad = RotationUtils.toRadians(this.rotation);\n const cos = Math.cos(rotRad);\n const sin = Math.sin(rotRad);\n\n const rotatedX = x * cos - y * sin;\n const rotatedY = x * sin + y * cos;\n\n // Translate from origin\n return {\n x: rotatedX + this.x,\n y: rotatedY + this.y,\n };\n }\n\n /**\n * Transform a vector (direction/offset) without translation\n * @param dx - X component of vector\n * @param dy - Y component of vector\n * @param inverse - If true, apply inverse transform\n * @returns Transformed vector\n */\n transformVector(dx: number, dy: number, inverse: boolean = false) {\n // Apply flips\n let x = this.flipH ? -dx : dx;\n let y = this.flipV ? -dy : dy;\n\n // Rotate\n const rotRad = inverse ? RotationUtils.toRadiansInverse(this.rotation) : RotationUtils.toRadians(this.rotation);\n\n const cos = Math.cos(rotRad);\n const sin = Math.sin(rotRad);\n\n if (inverse) {\n // Inverse rotation\n return {\n dx: x * cos + y * sin,\n dy: -x * sin + y * cos,\n };\n } else {\n // Forward rotation\n return {\n dx: x * cos - y * sin,\n dy: x * sin + y * cos,\n };\n }\n }\n\n /**\n * Create a CoordinateTransform from an element object\n * @param element - Element with x, y, rotation, flipH, flipV properties\n * @returns CoordinateTransform\n */\n static fromElement(element: TransformableElement) {\n return new CoordinateTransform(\n element.x,\n element.y,\n element.rotation || 0,\n element.flipH || false,\n element.flipV || false\n );\n }\n}\n","import { CoordinateTransform } from './CoordinateTransform.js';\nimport { Transform } from './Transform.js';\nimport { MIN_CROP_SIZE } from '../constants.js';\nimport type { BaseElement } from './BaseElement.js';\nimport type { TransformStartData, ImageTransformData } from '../types/index.js';\n\n/** Shape of the context object from the interaction state machine */\ninterface DragContext {\n startX: number;\n startY: number;\n startData: TransformStartData;\n}\n\n/** Shape of the resize context from the interaction state machine */\ninterface ResizeContext {\n startData: TransformStartData;\n}\n\n/** Result of a crop image drag operation */\ninterface CropDragResult {\n cropX: number;\n cropY: number;\n cropWidth: number;\n cropHeight: number;\n x: number;\n y: number;\n}\n\n/** Result of a crop box resize operation */\ninterface CropResizeResult {\n cropX: number;\n cropY: number;\n cropWidth: number;\n cropHeight: number;\n x: number;\n y: number;\n rotation: number;\n width: number;\n height: number;\n cursor: string;\n}\n\n/** Result of constrained crop values */\ninterface CropValues {\n cropX: number;\n cropY: number;\n cropWidth: number;\n cropHeight: number;\n}\n\n/**\n * CropModeController - Handles all crop mode interactions\n *\n * Manages:\n * - Dragging the image behind the crop window\n * - Resizing the crop box\n * - Coordinate transformations for crop operations\n */\nexport class CropModeController {\n /**\n * Handle dragging the image in crop mode\n */\n handleCropImageDrag(_element: BaseElement, context: DragContext, currentX: number, currentY: number): CropDragResult {\n const dx = currentX - context.startX;\n const dy = currentY - context.startY;\n\n // Use START rotation and dimensions for consistent coordinate conversion\n const startX = context.startData.x;\n const startY = context.startData.y;\n const td = context.startData.transformData as ImageTransformData;\n const startWidth = td.width;\n const startHeight = td.height;\n\n // Get start crop values\n const startCropX = context.startData.cropX ?? 0;\n const startCropY = context.startData.cropY ?? 0;\n const startCropWidth = context.startData.cropWidth ?? 1;\n const startCropHeight = context.startData.cropHeight ?? 1;\n\n // Convert the position delta to local coordinates (inverse transform: world → local)\n const startTransform = Transform.fromSnapshot(context.startData);\n const localDelta = startTransform.worldDeltaToLocal(dx, dy);\n let localDx = localDelta.dx;\n let localDy = localDelta.dy;\n\n // Account for flips\n const flipH = td.flipHorizontal ? -1 : 1;\n const flipV = td.flipVertical ? -1 : 1;\n localDx *= flipH;\n localDy *= flipV;\n\n // Convert delta to normalized coordinates (move crop in opposite direction)\n const normalizedDx = -localDx / startWidth;\n const normalizedDy = -localDy / startHeight;\n\n // Calculate desired crop coordinates\n let newCropX = startCropX + normalizedDx;\n let newCropY = startCropY + normalizedDy;\n\n // Constrain: Crop box must stay within image bounds (0-1 range)\n const maxCropX = 1 - startCropWidth;\n const maxCropY = 1 - startCropHeight;\n newCropX = Math.max(0, Math.min(maxCropX, newCropX));\n newCropY = Math.max(0, Math.min(maxCropY, newCropY));\n\n return {\n cropX: newCropX,\n cropY: newCropY,\n cropWidth: startCropWidth,\n cropHeight: startCropHeight,\n x: startX, // Position stays the same\n y: startY,\n };\n }\n\n /**\n * Handle resizing the crop box\n */\n handleCropBoxResize(\n _element: BaseElement,\n anchor: string,\n resizeContext: ResizeContext,\n stateMachineContext: DragContext,\n currentX: number,\n currentY: number\n ): CropResizeResult {\n const td = resizeContext.startData.transformData as ImageTransformData;\n const flipH = td.flipHorizontal;\n const flipV = td.flipVertical;\n\n // Get cursor for this anchor (accounting for flips)\n const cursor = this._getCropResizeCursor(anchor, !!flipH, !!flipV);\n\n // Use START element's rotation and position for coordinate conversions\n const startRotation = resizeContext.startData.rotation;\n const startX = resizeContext.startData.x;\n const startY = resizeContext.startData.y;\n\n // Create transform for inverse operations (world → local)\n const startTransform = Transform.fromSnapshot(resizeContext.startData);\n const { cos, sin } = startTransform.getInverseCosSin();\n\n // Flip multipliers\n const flipHMult = flipH ? -1 : 1;\n const flipVMult = flipV ? -1 : 1;\n\n // Convert current mouse position to local coordinates\n const dxCurrent = currentX - startX;\n const dyCurrent = currentY - startY;\n let localCurrentX = dxCurrent * cos - dyCurrent * sin;\n let localCurrentY = dxCurrent * sin + dyCurrent * cos;\n localCurrentX *= flipHMult;\n localCurrentY *= flipVMult;\n\n // Convert start mouse position to local coordinates\n const dxStart = stateMachineContext.startX - startX;\n const dyStart = stateMachineContext.startY - startY;\n let localStartX = dxStart * cos - dyStart * sin;\n let localStartY = dxStart * sin + dyStart * cos;\n localStartX *= flipHMult;\n localStartY *= flipVMult;\n\n // Calculate delta in local space\n const localDx = localCurrentX - localStartX;\n const localDy = localCurrentY - localStartY;\n\n // Get original crop values\n const startCropX = td.cropX;\n const startCropY = td.cropY;\n const startCropWidth = td.cropWidth;\n const startCropHeight = td.cropHeight;\n const startWidth = td.width;\n const startHeight = td.height;\n\n // Convert to normalized coordinates\n const normalizedDx = localDx / startWidth;\n const normalizedDy = localDy / startHeight;\n\n // Calculate new crop values based on anchor\n const result = this._calculateNewCropValues(\n anchor,\n startCropX,\n startCropY,\n startCropWidth,\n startCropHeight,\n normalizedDx,\n normalizedDy\n );\n\n // Calculate position offset to keep crop center in same world position\n const positionOffset = this._calculateCropCenterOffset(\n startCropX,\n startCropY,\n startCropWidth,\n startCropHeight,\n result.cropX,\n result.cropY,\n result.cropWidth,\n result.cropHeight,\n startWidth,\n startHeight,\n startRotation,\n flipHMult,\n flipVMult\n );\n\n return {\n ...result,\n x: startX + positionOffset.worldOffsetX,\n y: startY + positionOffset.worldOffsetY,\n rotation: startRotation,\n width: startWidth,\n height: startHeight,\n cursor,\n };\n }\n\n /**\n * Get cursor for crop resize handle\n * @private\n */\n _getCropResizeCursor(anchor: string, flipH: boolean, flipV: boolean): string {\n // Adjust anchor for flips\n let cursorAnchor = anchor;\n if (flipH) {\n if (cursorAnchor.includes('left')) {\n cursorAnchor = cursorAnchor.replace('left', 'right');\n } else if (cursorAnchor.includes('right')) {\n cursorAnchor = cursorAnchor.replace('right', 'left');\n }\n }\n if (flipV) {\n if (cursorAnchor.includes('top')) {\n cursorAnchor = cursorAnchor.replace('top', 'bottom');\n } else if (cursorAnchor.includes('bottom')) {\n cursorAnchor = cursorAnchor.replace('bottom', 'top');\n }\n }\n\n if (cursorAnchor === 'top-left' || cursorAnchor === 'bottom-right') {\n return 'nwse-resize';\n } else if (cursorAnchor === 'top-right' || cursorAnchor === 'bottom-left') {\n return 'nesw-resize';\n } else if (cursorAnchor === 'top' || cursorAnchor === 'bottom') {\n return 'ns-resize';\n } else if (cursorAnchor === 'left' || cursorAnchor === 'right') {\n return 'ew-resize';\n }\n return 'default';\n }\n\n /**\n * Calculate new crop values based on anchor and delta\n * @private\n */\n _calculateNewCropValues(\n anchor: string,\n startCropX: number,\n startCropY: number,\n startCropWidth: number,\n startCropHeight: number,\n normalizedDx: number,\n normalizedDy: number\n ): CropValues {\n const minSize = MIN_CROP_SIZE; // Minimum crop size\n\n let newCropX = startCropX;\n let newCropY = startCropY;\n let newCropWidth = startCropWidth;\n let newCropHeight = startCropHeight;\n\n // Adjust crop based on handle\n if (anchor === 'top-left') {\n newCropX = startCropX + normalizedDx;\n newCropY = startCropY + normalizedDy;\n newCropWidth = startCropWidth - normalizedDx;\n newCropHeight = startCropHeight - normalizedDy;\n } else if (anchor === 'top-right') {\n newCropY = startCropY + normalizedDy;\n newCropWidth = startCropWidth + normalizedDx;\n newCropHeight = startCropHeight - normalizedDy;\n } else if (anchor === 'bottom-left') {\n newCropX = startCropX + normalizedDx;\n newCropWidth = startCropWidth - normalizedDx;\n newCropHeight = startCropHeight + normalizedDy;\n } else if (anchor === 'bottom-right') {\n newCropWidth = startCropWidth + normalizedDx;\n newCropHeight = startCropHeight + normalizedDy;\n } else if (anchor === 'top') {\n newCropY = startCropY + normalizedDy;\n newCropHeight = startCropHeight - normalizedDy;\n } else if (anchor === 'bottom') {\n newCropHeight = startCropHeight + normalizedDy;\n } else if (anchor === 'left') {\n newCropX = startCropX + normalizedDx;\n newCropWidth = startCropWidth - normalizedDx;\n } else if (anchor === 'right') {\n newCropWidth = startCropWidth + normalizedDx;\n }\n\n // Apply constraints\n return this._constrainCropValues(\n anchor,\n newCropX,\n newCropY,\n newCropWidth,\n newCropHeight,\n startCropX,\n startCropY,\n startCropWidth,\n startCropHeight,\n minSize\n );\n }\n\n /**\n * Constrain crop values to valid bounds\n * @private\n */\n _constrainCropValues(\n anchor: string,\n cropX: number,\n cropY: number,\n cropWidth: number,\n cropHeight: number,\n startCropX: number,\n startCropY: number,\n startCropWidth: number,\n startCropHeight: number,\n minSize: number\n ): CropValues {\n let newCropX = cropX;\n let newCropY = cropY;\n let newCropWidth = cropWidth;\n let newCropHeight = cropHeight;\n\n // Handle width constraints\n if (newCropWidth < minSize) {\n newCropWidth = minSize;\n if (anchor.includes('left')) {\n newCropX = startCropX + startCropWidth - minSize;\n }\n }\n if (newCropX + newCropWidth > 1) {\n if (anchor.includes('left')) {\n newCropX = 1 - newCropWidth;\n } else {\n newCropWidth = 1 - newCropX;\n }\n }\n if (newCropX < 0) {\n newCropWidth = newCropWidth + newCropX;\n newCropX = 0;\n }\n\n // Handle height constraints\n if (newCropHeight < minSize) {\n newCropHeight = minSize;\n if (anchor.includes('top')) {\n newCropY = startCropY + startCropHeight - minSize;\n }\n }\n if (newCropY + newCropHeight > 1) {\n if (anchor.includes('top')) {\n newCropY = 1 - newCropHeight;\n } else {\n newCropHeight = 1 - newCropY;\n }\n }\n if (newCropY < 0) {\n newCropHeight = newCropHeight + newCropY;\n newCropY = 0;\n }\n\n return { cropX: newCropX, cropY: newCropY, cropWidth: newCropWidth, cropHeight: newCropHeight };\n }\n\n /**\n * Calculate offset to keep crop center at same world position\n * @private\n */\n _calculateCropCenterOffset(\n oldCropX: number,\n oldCropY: number,\n oldCropWidth: number,\n oldCropHeight: number,\n newCropX: number,\n newCropY: number,\n newCropWidth: number,\n newCropHeight: number,\n width: number,\n height: number,\n rotation: number,\n flipHMult: number,\n flipVMult: number\n ): { worldOffsetX: number; worldOffsetY: number } {\n // Calculate old crop center\n const oldCropCenterX = oldCropX * width + (oldCropWidth * width) / 2;\n const oldCropCenterY = oldCropY * height + (oldCropHeight * height) / 2;\n\n // Calculate new crop center\n const newCropCenterX = newCropX * width + (newCropWidth * width) / 2;\n const newCropCenterY = newCropY * height + (newCropHeight * height) / 2;\n\n // Calculate offset in local space\n let localOffsetX = newCropCenterX - oldCropCenterX;\n let localOffsetY = newCropCenterY - oldCropCenterY;\n\n // Apply flip transformation\n localOffsetX *= flipHMult;\n localOffsetY *= flipVMult;\n\n // Rotate to world space (forward transform: local → world)\n const transform = new Transform({ rotation, x: 0, y: 0 }); // Only need rotation\n const worldOffset = transform.localDeltaToWorld(localOffsetX, localOffsetY);\n const worldOffsetX = worldOffset.dx;\n const worldOffsetY = worldOffset.dy;\n\n return { worldOffsetX, worldOffsetY };\n }\n\n /**\n * Get the world position of a crop box handle\n */\n getCropBoxHandlePosition(element: BaseElement, anchor: string): { x: number; y: number } {\n const transform = CoordinateTransform.fromElement(element);\n const td = element.transformData as ImageTransformData;\n const width = td.width;\n const height = td.height;\n\n // Get crop dimensions\n const cropX = td.cropX || 0;\n const cropY = td.cropY || 0;\n const cropWidth = td.cropWidth || 1;\n const cropHeight = td.cropHeight || 1;\n\n // Calculate handle position in local coordinates\n let localX = 0;\n let localY = 0;\n\n if (anchor.includes('left')) {\n localX = (cropX - 0.5) * width;\n } else if (anchor.includes('right')) {\n localX = (cropX + cropWidth - 0.5) * width;\n } else {\n localX = (cropX + cropWidth / 2 - 0.5) * width;\n }\n\n if (anchor.includes('top')) {\n localY = (cropY - 0.5) * height;\n } else if (anchor.includes('bottom')) {\n localY = (cropY + cropHeight - 0.5) * height;\n } else {\n localY = (cropY + cropHeight / 2 - 0.5) * height;\n }\n\n return transform.localToWorld(localX, localY);\n }\n}\n","/**\n * useCropMode - Manages image crop mode state and logic\n */\n\nimport { useState } from 'react';\nimport { CropModeController } from '../../../core/CropModeController.js';\n\nexport function useCropMode() {\n // Create crop mode controller\n const [cropController] = useState(() => new CropModeController());\n\n return {\n cropController,\n };\n}\n","/**\n * useMultiSelection - Manages multi-element selection state\n */\n\nimport { useEffect, useRef } from 'react';\nimport type { MultiSelectionOBB, MultiSelectionGroupBounds } from '../renderers/index.js';\n\nexport function useMultiSelection(multiSelection: string[]) {\n // Multi-selection group bounds (for resize and rotation handles)\n const multiSelectionGroupBoundsRef = useRef<MultiSelectionGroupBounds | null>(null);\n\n // Persistent oriented bounding box for multi-selection (preserves rotation).\n // Stored as a ref instead of state so the RAF render loop can write the computed\n // OBB directly without triggering React re-renders.\n const multiSelectionOBBRef = useRef<MultiSelectionOBB | null>(null);\n\n // Reset OBB when multi-selection changes\n useEffect(() => {\n multiSelectionOBBRef.current = null;\n }, [multiSelection]);\n\n return {\n multiSelectionGroupBoundsRef,\n multiSelectionOBBRef,\n };\n}\n","/**\n * useTextEditing - Manages rich text editing state and formatting\n */\n\nimport { useState, useRef, useCallback, useImperativeHandle, type Ref } from 'react';\nimport type { RichText, CharacterStyle } from '../../../types/index.js';\nimport { TextElement } from '../../../core/TextElement.js';\nimport type { ImageElement } from '../../../core/ImageElement.js';\nimport type { GroupElement } from '../../../core/GroupElement.js';\nimport type { ShapeElement } from '../../../core/ShapeElement.js';\nimport type { PathElement } from '../../../core/PathElement.js';\n\n/** Elements that can be selected in the canvas */\ntype CanvasElement = TextElement | ImageElement | GroupElement | ShapeElement | PathElement;\n\n/** Helper to check if an element has text editing capabilities (getDefaultStyle method) */\nfunction hasTextCapabilities(element: CanvasElement | null | undefined): element is TextElement {\n return element !== null && element !== undefined && element instanceof TextElement;\n}\n\nexport interface TextEditingHandle {\n applyTextFormatting: (style: CharacterStyle) => void;\n toggleFormattingProperty: (property: 'bold' | 'italic' | 'underline' | 'strikethrough') => void;\n isEditingText: () => boolean;\n getSelectionStyle: () => CharacterStyle | null;\n}\n\ninterface UseTextEditingProps {\n selectedElement: CanvasElement | null | undefined;\n ref?: Ref<TextEditingHandle>;\n}\n\nexport function useTextEditing({ selectedElement, ref }: UseTextEditingProps) {\n // Edit state\n const [isEditing, setIsEditing] = useState<boolean>(false);\n const [editText, setEditText] = useState<string>('');\n const [editPosition, setEditPosition] = useState<{\n x: number;\n y: number;\n width: number;\n height?: number;\n }>({ x: 0, y: 0, width: 200, height: 50 });\n const [cursorPosition, setCursorPosition] = useState<number>(0);\n const [selectionStart, setSelectionStart] = useState<number>(0);\n const [selectionEnd, setSelectionEnd] = useState<number>(0);\n const cursorOpacityRef = useRef<number>(1); // Use ref instead of state to avoid triggering re-renders\n const cursorStartTimeRef = useRef<number>(performance.now()); // Track animation start time\n const selectionAnchorRef = useRef<number>(0); // Immediate anchor for drag selection (avoids async state issues)\n const didDragSelectRef = useRef<boolean>(false); // Track if drag selection occurred (to skip onClick)\n\n // Rich text editing state\n const [editRichText, setEditRichText] = useState<RichText | null>(null);\n const [currentFormattingStyle, setCurrentFormattingStyle] = useState<CharacterStyle | null>(null);\n\n // Ref to track latest editRichText synchronously (for handleTextEditComplete to read\n // immediately after formatting is applied, before React state update completes)\n const editRichTextRef = useRef<RichText | null>(null);\n\n // Apply formatting to selected text\n const applyFormattingToSelection = useCallback(\n (style: CharacterStyle) => {\n\n if (!isEditing || !editRichText) {\n return;\n }\n\n const start = Math.min(selectionStart, selectionEnd);\n const end = Math.max(selectionStart, selectionEnd);\n\n\n if (start === end) {\n // No selection - apply to ALL characters\n\n const updated = editRichText.clone();\n const textLength = updated.getText().length;\n updated.applyStyle(0, textLength, style);\n\n // Update ref synchronously so handleTextEditComplete can read latest value\n editRichTextRef.current = updated;\n setEditRichText(updated);\n setEditText(updated.getText());\n\n // Also update current formatting style so next character uses the same formatting\n setCurrentFormattingStyle((prevStyle) => {\n const newStyle = { ...prevStyle, ...style };\n return newStyle;\n });\n } else {\n // Apply to selection\n\n const updated = editRichText.clone();\n updated.applyStyle(start, end, style);\n\n // Update ref synchronously so handleTextEditComplete can read latest value\n editRichTextRef.current = updated;\n setEditRichText(updated);\n setEditText(updated.getText());\n\n // Also update current formatting style so next character uses the same formatting\n setCurrentFormattingStyle((prevStyle) => {\n const newStyle = { ...prevStyle, ...style };\n return newStyle;\n });\n }\n },\n [isEditing, editRichText, selectionStart, selectionEnd]\n );\n\n // Get the style of the current selection (for toolbar state)\n const getSelectionStyle = useCallback((): CharacterStyle | null => {\n if (!isEditing || !editRichText || !selectedElement) return null;\n\n const start = Math.min(selectionStart, selectionEnd);\n const end = Math.max(selectionStart, selectionEnd);\n\n // If no selection, get style at cursor position\n if (start === end) {\n if (start === 0) {\n // At beginning, use element defaults (only for elements with text capabilities)\n return hasTextCapabilities(selectedElement) ? selectedElement.getDefaultStyle() : {};\n }\n // Use style of character before cursor\n return editRichText.getStyleAt(Math.max(0, start - 1));\n }\n\n // Get style of first character in selection\n return editRichText.getStyleAt(start);\n }, [isEditing, editRichText, selectionStart, selectionEnd, selectedElement]);\n\n // Helper function to toggle boolean formatting properties\n const toggleFormattingProperty = useCallback(\n (property: 'bold' | 'italic' | 'underline' | 'strikethrough') => {\n if (!isEditing || !editRichText || !selectedElement) return;\n\n // Get current style at cursor/selection\n const currentStyle = getSelectionStyle();\n const defaultStyle = hasTextCapabilities(selectedElement) ? selectedElement.getDefaultStyle() : {};\n\n // Merge with defaults to get effective style\n const effectiveStyle = { ...defaultStyle, ...currentStyle };\n\n // Toggle the property\n const newValue = !effectiveStyle[property];\n\n\n // Apply the toggled value\n applyFormattingToSelection({ [property]: newValue });\n },\n [isEditing, editRichText, selectedElement, getSelectionStyle, applyFormattingToSelection]\n );\n\n // Expose formatting methods to parent component via ref\n useImperativeHandle(\n ref,\n () => ({\n applyTextFormatting: (style: CharacterStyle) => {\n applyFormattingToSelection(style);\n },\n toggleFormattingProperty: (property: 'bold' | 'italic' | 'underline' | 'strikethrough') => {\n toggleFormattingProperty(property);\n },\n isEditingText: () => isEditing,\n getSelectionStyle: () => getSelectionStyle(),\n }),\n [applyFormattingToSelection, toggleFormattingProperty, isEditing, getSelectionStyle]\n );\n\n return {\n isEditing,\n setIsEditing,\n editText,\n setEditText,\n editPosition,\n setEditPosition,\n cursorPosition,\n setCursorPosition,\n selectionStart,\n setSelectionStart,\n selectionEnd,\n setSelectionEnd,\n cursorOpacityRef, // Export ref instead of state\n cursorStartTimeRef, // Export animation start time ref\n selectionAnchorRef, // Export anchor ref for drag selection\n didDragSelectRef, // Export drag selection flag ref\n editRichText,\n setEditRichText,\n editRichTextRef, // Export ref for synchronous access in handleTextEditComplete\n currentFormattingStyle,\n setCurrentFormattingStyle,\n applyFormattingToSelection,\n getSelectionStyle,\n toggleFormattingProperty,\n };\n}\n","/**\n * useCanvasLayout - Composite hook for canvas sizing, touch detection, and view calculations\n *\n * Groups related state:\n * - Canvas size (responsive to props, artboard changes, and window resize)\n * - Touch device detection\n * - Touch target tracking for iOS Safari blur handling\n * - View calculations (padding, margins, zoom-invariant border radius)\n */\n\nimport { useState, useEffect, useRef } from 'react';\nimport type { ArtboardElement } from '../../../core/ArtboardElement.js';\nimport type { ArtboardManager } from '../../../core/ArtboardManager.js';\n\nexport interface UseCanvasLayoutParams {\n width: number | undefined;\n height: number | undefined;\n zoom: number;\n viewPadding: number;\n artboardBorderRadius: number;\n fixedMargin: number | undefined;\n fixedMarginX: number | undefined;\n fixedMarginY: number | undefined;\n artboards: ArtboardElement[];\n artboardManager: ArtboardManager;\n /**\n * ADR-0060: when present, fit the viewport to this piece (in artboard\n * / spread coordinates) plus a peek margin so neighbouring pieces\n * stay visible at the edges. When `null` / `undefined`, the viewport\n * fits the active artboard (historical behaviour).\n */\n focusedPieceRect?: { x: number; y: number; width: number; height: number } | null;\n}\n\nexport interface UseCanvasLayoutReturn {\n /** Whether the device supports touch input */\n isTouchDevice: boolean;\n /** Current canvas dimensions */\n canvasSize: { width: number; height: number };\n /** Ref tracking the last touch/pointer target for iOS Safari blur handling */\n lastTouchTargetRef: React.RefObject<HTMLElement | null>;\n\n // View calculations\n /** View padding clamped to [0.1, 1.0] */\n effectiveViewPadding: number;\n /** Canvas width including padding/margins, in world space */\n paddedCanvasWidth: number;\n /** Canvas height including padding/margins, in world space */\n paddedCanvasHeight: number;\n /** Horizontal padding offset in world space */\n paddingOffsetX: number;\n /** Vertical padding offset in world space */\n paddingOffsetY: number;\n /** Border radius adjusted for current zoom level */\n zoomInvariantBorderRadius: number;\n}\n\nexport function useCanvasLayout({\n width,\n height,\n zoom,\n viewPadding,\n artboardBorderRadius,\n fixedMargin,\n fixedMarginX,\n fixedMarginY,\n artboards,\n artboardManager,\n focusedPieceRect,\n}: UseCanvasLayoutParams): UseCanvasLayoutReturn {\n // ── Touch device detection ──────────────────────────────────────────────\n const [isTouchDevice, setIsTouchDevice] = useState(false);\n\n useEffect(() => {\n const hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0;\n setIsTouchDevice(hasTouch);\n }, []);\n\n // ── Touch target tracking (iOS Safari blur handling) ────────────────────\n const lastTouchTargetRef = useRef<HTMLElement | null>(null);\n\n useEffect(() => {\n const handleTouchStart = (e: TouchEvent | PointerEvent) => {\n lastTouchTargetRef.current = e.target as HTMLElement;\n setTimeout(() => { lastTouchTargetRef.current = null; }, 500);\n };\n document.addEventListener('touchstart', handleTouchStart, { capture: true, passive: true });\n document.addEventListener('pointerdown', handleTouchStart, { capture: true, passive: true });\n return () => {\n document.removeEventListener('touchstart', handleTouchStart, { capture: true });\n document.removeEventListener('pointerdown', handleTouchStart, { capture: true });\n };\n }, []);\n\n // ── Canvas size ─────────────────────────────────────────────────────────\n const [canvasSize, setCanvasSize] = useState({\n width: width ?? window.innerWidth,\n height: height ?? window.innerHeight,\n });\n\n // Update canvas size when width/height props change. When a focused\n // piece is provided (ADR-0060), the canvas resizes to that piece\n // instead of the full artboard so the piece fills most of the\n // display; the surrounding render-loop translate puts neighbouring\n // pieces just past the canvas bounds where the peek margin makes\n // them visible.\n //\n // Order matters: `focusedPieceRect` MUST win over the explicit\n // `width` / `height` props. The Canvas.tsx wrapper passes the\n // active artboard's dims as width/height — which would otherwise\n // override the focused piece and put us back at full-artboard\n // sizing. Focus is a more specific, intentional signal.\n useEffect(() => {\n if (focusedPieceRect) {\n setCanvasSize({ width: focusedPieceRect.width, height: focusedPieceRect.height });\n return;\n }\n if (width !== undefined && height !== undefined) {\n setCanvasSize({ width, height });\n return;\n }\n const activeArtboard = artboardManager.getActiveArtboard();\n if (activeArtboard) {\n setCanvasSize({ width: activeArtboard.width, height: activeArtboard.height });\n }\n }, [width, height, artboards, artboardManager, focusedPieceRect]);\n\n // Window resize handler\n useEffect(() => {\n const handleResize = () => {\n if (focusedPieceRect) {\n setCanvasSize({ width: focusedPieceRect.width, height: focusedPieceRect.height });\n return;\n }\n if (width === undefined && height === undefined) {\n const activeArtboard = artboardManager.getActiveArtboard();\n if (!activeArtboard) {\n setCanvasSize({ width: window.innerWidth, height: window.innerHeight });\n } else {\n setCanvasSize({ width: activeArtboard.width, height: activeArtboard.height });\n }\n }\n };\n window.addEventListener('resize', handleResize);\n return () => window.removeEventListener('resize', handleResize);\n }, [width, height, artboardManager, focusedPieceRect]);\n\n // ── View calculations ───────────────────────────────────────────────────\n const effectiveViewPadding = Math.max(0.1, Math.min(1.0, viewPadding));\n const effectiveMarginX = fixedMarginX ?? fixedMargin;\n const effectiveMarginY = fixedMarginY ?? fixedMargin;\n const worldMarginX = effectiveMarginX !== undefined && zoom > 0 ? effectiveMarginX / zoom : 0;\n const worldMarginY = effectiveMarginY !== undefined && zoom > 0 ? effectiveMarginY / zoom : 0;\n\n const paddedCanvasWidth = effectiveMarginX !== undefined\n ? canvasSize.width + (worldMarginX * 2)\n : canvasSize.width / effectiveViewPadding;\n const paddedCanvasHeight = effectiveMarginY !== undefined\n ? canvasSize.height + (worldMarginY * 2)\n : canvasSize.height / effectiveViewPadding;\n\n // ADR-0060: in focused mode, shift the world transform so the\n // focused piece's `(x, y)` origin in artboard / spread coords lands\n // at `(worldMarginX, worldMarginY)` on the canvas. Elements still\n // live at their spread coordinates; the render-loop translate just\n // recentres what the user sees. Anything outside the focused piece's\n // rect (other pieces, element overflow) draws beyond the canvas\n // bounds and is naturally clipped — neighbours are visible only via\n // the peek margin baked into `fixedMargin`.\n const baseOffsetX = effectiveMarginX !== undefined\n ? worldMarginX\n : (paddedCanvasWidth - canvasSize.width) / 2;\n const baseOffsetY = effectiveMarginY !== undefined\n ? worldMarginY\n : (paddedCanvasHeight - canvasSize.height) / 2;\n const paddingOffsetX = focusedPieceRect ? baseOffsetX - focusedPieceRect.x : baseOffsetX;\n const paddingOffsetY = focusedPieceRect ? baseOffsetY - focusedPieceRect.y : baseOffsetY;\n\n const zoomInvariantBorderRadius = zoom > 0 ? artboardBorderRadius / zoom : artboardBorderRadius;\n\n return {\n isTouchDevice,\n canvasSize,\n lastTouchTargetRef,\n effectiveViewPadding,\n paddedCanvasWidth,\n paddedCanvasHeight,\n paddingOffsetX,\n paddingOffsetY,\n zoomInvariantBorderRadius,\n };\n}\n","/**\n * useInteractionState - Composite hook for interaction overlay state\n *\n * Groups state that drives visual overlays during user interaction:\n * - Snap guides (alignment lines shown during drag/resize)\n * - Spacing indicators (distance labels shown when Alt is pressed)\n * - Alt key tracking\n * - Rotation state (angle tracking during rotation gesture)\n */\n\nimport { useState } from 'react';\nimport type { SnapGuide, SpacingIndicator } from '../../../types/index.js';\n\nexport interface UseInteractionStateReturn {\n /** Active snap guide lines */\n snapGuides: SnapGuide[];\n setSnapGuides: React.Dispatch<React.SetStateAction<SnapGuide[]>>;\n\n /** Spacing measurement indicators (shown with Alt key) */\n spacingIndicators: SpacingIndicator[];\n setSpacingIndicators: React.Dispatch<React.SetStateAction<SpacingIndicator[]>>;\n\n /** Whether the Alt key is currently pressed */\n isAltKeyPressed: boolean;\n setIsAltKeyPressed: React.Dispatch<React.SetStateAction<boolean>>;\n\n /** Whether the user is currently rotating an element */\n isRotating: boolean;\n setIsRotating: React.Dispatch<React.SetStateAction<boolean>>;\n\n /** The angle at which rotation started */\n setRotationStartAngle: React.Dispatch<React.SetStateAction<number>>;\n\n /** Current rotation angle during a rotation gesture */\n currentRotation: number;\n setCurrentRotation: React.Dispatch<React.SetStateAction<number>>;\n\n /** Version counter to trigger re-renders when rotating element updates */\n setRotatingElementVersion: React.Dispatch<React.SetStateAction<number>>;\n}\n\nexport function useInteractionState(): UseInteractionStateReturn {\n const [snapGuides, setSnapGuides] = useState<SnapGuide[]>([]);\n const [spacingIndicators, setSpacingIndicators] = useState<SpacingIndicator[]>([]);\n const [isAltKeyPressed, setIsAltKeyPressed] = useState(false);\n const [isRotating, setIsRotating] = useState<boolean>(false);\n const [_rotationStartAngle, setRotationStartAngle] = useState<number>(0);\n const [currentRotation, setCurrentRotation] = useState<number>(0);\n const [, setRotatingElementVersion] = useState<number>(0);\n\n return {\n snapGuides,\n setSnapGuides,\n spacingIndicators,\n setSpacingIndicators,\n isAltKeyPressed,\n setIsAltKeyPressed,\n isRotating,\n setIsRotating,\n setRotationStartAngle,\n currentRotation,\n setCurrentRotation,\n setRotatingElementVersion,\n };\n}\n","/**\n * useRenderState - Composite hook for render versioning and triggers\n *\n * Groups state that controls when the canvas re-renders:\n * - renderVersion: incremented when external events (image loads) require a re-render\n * - handlesVersion: incremented when transform handles need recalculation\n * - forceUpdate: generic trigger for component re-render\n * - cursorAnimationFrame: drives text cursor blink animation\n * - Image load subscription: listens for async image loads to trigger re-renders\n */\n\nimport { useState, useEffect, useCallback } from 'react';\nimport { subscribeToImageLoads } from '../../../core/ImageLoadEvents.js';\n\nexport interface UseRenderStateParams {\n /** Current elements array, used to check if loaded image belongs to an active element */\n elements: Array<{ id: string }>;\n}\n\nexport interface UseRenderStateReturn {\n /** Version counter for render-triggering events */\n renderVersion: number;\n\n /** Force a render by incrementing the render version */\n forceRender: () => void;\n\n /** Version counter for transform handle recalculation */\n handlesVersion: number;\n setHandlesVersion: React.Dispatch<React.SetStateAction<number>>;\n\n /** Generic force re-render trigger */\n forceUpdate: React.Dispatch<React.SetStateAction<object>>;\n\n /** Frame counter for cursor blink animation */\n cursorAnimationFrame: number;\n setCursorAnimationFrame: React.Dispatch<React.SetStateAction<number>>;\n}\n\nexport function useRenderState({ elements }: UseRenderStateParams): UseRenderStateReturn {\n const [, forceUpdate] = useState<object>({});\n const [handlesVersion, setHandlesVersion] = useState(0);\n const [renderVersion, setRenderVersion] = useState(0);\n const [cursorAnimationFrame, setCursorAnimationFrame] = useState(0);\n\n const forceRender = useCallback(() => {\n setRenderVersion((v) => v + 1);\n }, []);\n\n // Subscribe to image load events to trigger re-renders\n useEffect(() => {\n const unsubscribe = subscribeToImageLoads((elementId) => {\n const hasElement = elements.some(e => e.id === elementId);\n if (hasElement) { setRenderVersion(v => v + 1); }\n });\n return unsubscribe;\n }, [elements]);\n\n return {\n renderVersion,\n forceRender,\n handlesVersion,\n setHandlesVersion,\n forceUpdate,\n cursorAnimationFrame,\n setCursorAnimationFrame,\n };\n}\n","/**\n * ADR-0054: canvas-native piece-guide rendering.\n *\n * This replaces the old `VisualGuideOverlay` SVG-DOM overlay. The overlay\n * approach lived outside the canvas element and had to re-derive the\n * canvas's viewport math from scratch (fixedMargin units, aspect-ratio\n * letterboxing, canvas-wrapper negative margins, etc.), which made it\n * drift away from the artboard at any zoom that wasn't 1×.\n *\n * This module draws the same visual layers (bleed ring, zones, die-cut\n * knockouts, boundary outline, safe-area wash, labels) directly into the\n * canvas render loop's 2D context — which is already in artboard-local\n * coordinates after the loop has applied DPR / zoom / padding translate.\n * That means piece-local shape coords work verbatim and no unit or\n * coordinate-system conversion is needed anywhere.\n *\n * ── Rendering contract (IMPORTANT) ────────────────────────────────────\n * Piece-guide products (phone cases, anything with a non-rectangular\n * outer shape) treat the canvas as a true knockout — the page chrome /\n * product mockup behind shows through wherever the artwork doesn't\n * cover. To make that work end-to-end, several paint passes that exist\n * for plain rectangular products are gated off in this mode:\n *\n * 1. The `<canvas>` element's CSS `background` is `transparent`\n * instead of `var(--color-canvas-bg)` (CanvasEditor.tsx).\n * 2. The artboard 2D bg fill is skipped (useCanvasRenderLoop.ts).\n * 3. The ArtboardRenderer's own background paint is skipped\n * (useCanvasRenderLoop.ts) — its default `'#ffffff'` would\n * otherwise blanket the corner wedges.\n * 4. The Pass-1 overflow-dim halo uses a *rounded* inverted clip,\n * so the 30% blurred preview of selected/hovered elements\n * extends naturally into the corner wedges during interaction\n * (CanvasRenderer.ts). At idle the halo is inactive, so corner\n * wedges read as fully transparent.\n * 5. Die-cut zones in `dieCutMode: 'knockout'` skip their\n * `destination-out` fill while the user is interacting (see\n * `interacting` param + `drawDieCutKnockout` below). Idle reads\n * as a clean punched hole; mid-edit the artwork and selection\n * chrome inside the cutout stay visible so manipulation\n * feedback isn't erased.\n *\n * Together, those gates mean the renderer here doesn't have to do any\n * per-boundary clipping or \"final crop\" pass — the surrounding paint\n * passes simply don't paint into anywhere that would need erasing.\n *\n * Rendering order (back → front) must match the old SVG overlay so the\n * visual language stays identical:\n *\n * 1. Bleed ring — `outerShape ∖ wantInner`, where `wantInner` is\n * `boundary` (default) or `safeArea` per `bleedInnerEdge`. Two\n * modes: `'gradient'` (Apple-style soft vignette via blurred\n * destination-out knockout) and `'solid'` (flat fill at\n * `bleedOpacity`).\n * 2. Non-die-cut zones — flat fill + stroke per zone kind.\n * 3. Die-cut zones — `'blur-tint'` (blurred content + dark tint,\n * reads as a punched window) or `'knockout'`\n * (`destination-out`-cleared pixels — true transparency through\n * the canvas, suppressed mid-interaction).\n * 4. Boundary outline — optional (default off), solid stroke.\n * 5. Safe-area outline — optional legacy dashed stroke (default off).\n * 6. Labels — halo-stroked text for zone/area annotations.\n */\n\nimport type {\n PieceGuide,\n SvgPath,\n ZoneShape,\n GuideLabel,\n} from '../components/VisualGuideOverlay.js';\nimport { setZoomInvariantStroke } from '../core/GeometryUtils.js';\n\nexport type { PieceGuide, SvgPath, ZoneShape, GuideLabel };\n\n/** Minimum piece info the renderer needs. Matches the ADR-0054 backend shape. */\nexport interface PieceGuideRendererPiece {\n /** Stable piece id (matches placements[].name and pieceGuides keys). */\n id: string;\n /** Piece's top-left position in artboard pixel coords. */\n x: number;\n y: number;\n /** Piece-local dimensions in px. */\n width: number;\n height: number;\n /**\n * Fallback safe-area inset (piece-local px) when no authored\n * `pieceGuide.safeArea` is present. Used only if the caller wants the\n * legacy centered-inset fallback; otherwise omit.\n */\n safeAreaInsetPx?: number;\n /** Clockwise rotation in degrees about the piece center. Default 0. */\n rotation?: number;\n}\n\nexport interface PieceGuideRendererStyle {\n /**\n * Bleed-ring rendering mode. Default `'gradient'` (the original\n * Apple-style vignette: opaque fill softly fading into the design\n * canvas via a blurred destination-out knockout).\n *\n * `'solid'` draws a flat uniform fill over `pieceRect ∖ boundary` at\n * `bleedOpacity`. Useful when the vignette's soft inner edge reads as\n * visual noise against the artwork — e.g. photographic designs where\n * a clean sharp bleed edge is preferred.\n */\n bleedMode?: 'gradient' | 'solid';\n /**\n * Which shape the bleed ring rings around. Default `'boundary'` (the\n * design-canvas edge — the current behaviour). `'safeArea'` pulls the\n * inner edge inwards to the safe-area shape, so the fade starts from\n * the text-safe zone outward and the region between safe area and\n * boundary is covered too. Falls back to `'boundary'` if the piece\n * has no safeArea authored.\n */\n bleedInnerEdge?: 'boundary' | 'safeArea';\n /** Bleed-ring fill colour. Defaults to #333. */\n bleedFill?: string;\n /**\n * Bleed-ring opacity (0–1). Default 1.0.\n *\n * Must be 1.0 whenever the main canvas does any per-boundary\n * clipping. Translucent overlays combined with sharp underlying\n * clips produce a visible \"halo then jump\" at the boundary instead\n * of a smooth vignette — see the rendering contract in this file's\n * header. If you lower this, the bleed band will read as a tinted\n * wash instead of a solid frame.\n */\n bleedOpacity?: number;\n /**\n * Die-cut rendering mode.\n *\n * - `'blur-tint'` (default) — the current behaviour. The zone's\n * artwork is blurred and a dark tint composites on top so the\n * punched hole reads as a \"window\" onto the product underneath.\n * - `'knockout'` — the zone's pixels are cleared from the canvas\n * with `destination-out` compositing. Whatever's drawn *beneath*\n * the canvas element (page background, a product mockup layered\n * underneath, etc.) shows through. Useful when the canvas sits\n * on top of a real product mockup and you want the hole to be\n * honestly transparent.\n */\n dieCutMode?: 'blur-tint' | 'knockout';\n /** Die-cut tint colour. Defaults to --color-bg-tertiary. Only used in `'blur-tint'` mode. */\n dieCutTint?: string;\n /** Alpha applied to the die-cut tint (0–1). Default 0.7 (matches old overlay). */\n dieCutTintAlpha?: number;\n /** Gaussian blur radius (CSS pixels) applied to bleed + die-cut content. Default 4. */\n dieCutBlurPx?: number;\n /**\n * Die-cut outline stroke colour. Empty string (default) disables the\n * stroke — matches the canvas renderer's historical behaviour. Set\n * to e.g. `'#ffffff'` to get a thin rim around the punched hole.\n */\n dieCutStroke?: string;\n /** Die-cut outline stroke width (CSS pixels). Default 1. */\n dieCutStrokeWidthPx?: number;\n /** Extra blur radius (CSS pixels) for the bleed ring only. Scales with artboard size. */\n bleedBlurScale?: number;\n /** Boundary outline stroke colour. Empty string disables (default). */\n boundaryStroke?: string;\n boundaryStrokeWidthPx?: number;\n /** Legacy dashed safe-area outline. Empty string disables (default). */\n safeAreaStroke?: string;\n safeAreaStrokeWidthPx?: number;\n safeAreaDashPx?: [number, number];\n /** Per-kind zone fill colours (rgba). */\n zoneFill?: Partial<Record<ZoneShape['kind'], string>>;\n /** Per-kind zone stroke colours. */\n zoneStroke?: Partial<Record<ZoneShape['kind'], string>>;\n /** Label text + halo style. */\n labelColor?: string;\n labelFontSizePx?: number;\n labelFontFamily?: string;\n labelHaloColor?: string;\n}\n\nexport interface PieceGuideVisibility {\n bleed?: boolean;\n zones?: boolean;\n boundary?: boolean;\n safeArea?: boolean;\n labels?: boolean;\n}\n\nexport interface PieceGuideRenderParams {\n ctx: CanvasRenderingContext2D;\n /**\n * The artboard-local origin (in world pixels). The render loop has\n * already applied DPR/zoom/padding transforms, so shape coordinates\n * can be used directly after `ctx.translate(artboard.x + piece.x, ...)`\n * — artboardOrigin is just where the artboard itself starts in world\n * space (typically the active artboard's (x, y)).\n */\n artboardOrigin: { x: number; y: number };\n artboardWidth: number;\n artboardHeight: number;\n pieces: PieceGuideRendererPiece[];\n pieceGuides?: Record<string, PieceGuide>;\n show?: PieceGuideVisibility;\n style?: PieceGuideRendererStyle;\n /** Canvas CSS pixels per world pixel (from useCanvasLayout). */\n zoom: number;\n /**\n * True while an element is selected, hovered, dragged, or otherwise\n * being manipulated. When set, die-cut zones in `dieCutMode:\n * 'knockout'` skip the `destination-out` fill that erases pixels\n * inside the cut shape — the stroke / outline still renders, but\n * underlying artwork (and any selection chrome the element drew\n * during its own render pass) stays visible. This mirrors the\n * rounded-outer-corner behaviour: idle reads as a hard knockout to\n * the page chrome below, but during interaction the user sees what\n * sits beneath the cutout so resize/drag feedback isn't suddenly\n * \"punched out\". Idle behaviour (false / undefined) is unchanged.\n */\n interacting?: boolean;\n /**\n * ADR-0060: id of the piece currently in focus. When set, ONLY that\n * piece's guide chrome (boundary, safe area, bleed ring, die-cut\n * zones, labels) is drawn — every other piece is skipped entirely.\n * The companion narrowing in `useCanvasRenderLoop` filters the\n * artboard's `composite-path` clip to the focused piece's\n * silhouette so element rendering only paints inside that piece\n * either; neighbour artwork and chrome are completely hidden.\n * When `null` / `undefined` every piece renders (spread mode).\n *\n * Earlier this dimmed neighbours to ≈0.3 alpha for \"structural\n * context\", but the current UX preference is to fully hide them\n * when focused — the focus picker is the affordance to switch back\n * to spread, neighbour pieces don't need to leak through.\n */\n focusedPieceId?: string | null;\n}\n\n/** Default style. Kept in sync with the old VisualGuideOverlay defaults. */\nconst DEFAULTS: Required<\n Omit<PieceGuideRendererStyle, 'zoneFill' | 'zoneStroke'>\n> & {\n zoneFill: Record<ZoneShape['kind'], string>;\n zoneStroke: Record<ZoneShape['kind'], string>;\n} = {\n bleedMode: 'gradient',\n bleedInnerEdge: 'boundary',\n // Pure black at full opacity. With the `multiply` blend mode applied\n // at composite time, this collapses both the editor surface and any\n // artwork in the bleed band to black — maximum visual signal that\n // \"this region is the crop zone.\" The themed default at render time\n // overrides this for dark mode (→ white + `screen`); this value is\n // the safe light-mode choice that callers fall back to.\n bleedFill: '#000000',\n bleedOpacity: 1,\n // Die-cut is a \"punched hole in the material\" — it should always\n // read as dark regardless of editor theme.\n dieCutMode: 'blur-tint',\n dieCutTint: '#333',\n dieCutTintAlpha: 0.7,\n dieCutBlurPx: 4,\n dieCutStroke: '',\n dieCutStrokeWidthPx: 1,\n // Width of the fade from transparent (deep inside boundary) to\n // opaque (at/past boundary edge), as a fraction of the piece's\n // short dimension. ~0.04 gives a visibly soft vignette on a phone\n // case without darkening the whole design area.\n bleedBlurScale: 0.04,\n boundaryStroke: '',\n boundaryStrokeWidthPx: 1,\n safeAreaStroke: '',\n safeAreaStrokeWidthPx: 1.5,\n safeAreaDashPx: [6, 4],\n zoneFill: {\n wrap: 'rgba(148, 163, 184, 0.25)',\n binding: 'rgba(234, 88, 12, 0.20)',\n dieCut: 'rgba(0, 0, 0, 0.7)', // not used directly — see renderDieCut\n warning: 'rgba(245, 158, 11, 0.25)',\n custom: 'rgba(100, 100, 100, 0.15)',\n },\n zoneStroke: {\n wrap: '#64748b',\n binding: '#ea580c',\n dieCut: '#ffffff',\n warning: '#f59e0b',\n custom: '#6b7280',\n },\n labelColor: '#111827',\n labelFontSizePx: 14,\n labelFontFamily:\n 'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif',\n labelHaloColor: '#ffffff',\n};\n\n/**\n * Capture the current canvas pixels into a detached backing canvas. We\n * keep a single reusable element across calls so we're not allocating a\n * new canvas per render frame. Retina / DPI safety: the backing canvas\n * is sized to the source's physical pixel dims, not CSS dims.\n */\nlet cachedSnapshotEl: HTMLCanvasElement | null = null;\nfunction snapshotCanvas(source: HTMLCanvasElement): HTMLCanvasElement | null {\n const w = source.width;\n const h = source.height;\n if (w <= 0 || h <= 0) return null;\n if (!cachedSnapshotEl) cachedSnapshotEl = document.createElement('canvas');\n if (cachedSnapshotEl.width !== w) cachedSnapshotEl.width = w;\n if (cachedSnapshotEl.height !== h) cachedSnapshotEl.height = h;\n const sctx = cachedSnapshotEl.getContext('2d');\n if (!sctx) return null;\n sctx.setTransform(1, 0, 0, 1, 0, 0);\n sctx.clearRect(0, 0, w, h);\n sctx.drawImage(source, 0, 0);\n return cachedSnapshotEl;\n}\n\n/**\n * Resolve a possibly-CSS-var colour string into a concrete CSS colour\n * the canvas 2D context accepts. Canvas 2D does not resolve `var(...)`,\n * so we read the variable off the canvas element (or documentElement\n * fallback) and use the fallback literal inside the var() expression\n * if the property is unset.\n *\n * If the input is not a var() expression we return it unchanged.\n */\nfunction resolveCssColor(\n input: string,\n target: HTMLElement | null,\n): string {\n const trimmed = input.trim();\n if (!trimmed.startsWith('var(')) return trimmed;\n // Parse \"var(--name[, fallback])\"\n const inner = trimmed.slice(4, -1);\n const [name, ...rest] = inner.split(',');\n const varName = name.trim();\n const fallback = rest.length > 0 ? rest.join(',').trim() : '';\n const source = target ?? document.documentElement;\n const resolved = getComputedStyle(source).getPropertyValue(varName).trim();\n return resolved || fallback || '#333';\n}\n\n/**\n * True when the editor is in dark mode. Used to pick a\n * high-contrast bleed-wash colour.\n *\n * Trusts the app's explicit theme signal only:\n * 1. `.dark` class on `<html>` (Tailwind / ThemeContext convention)\n * 2. `data-theme=\"dark\"` attribute on `<html>` (alternative\n * convention some apps use)\n *\n * `prefers-color-scheme: dark` is intentionally NOT consulted: when\n * the system preference is dark but the app is explicitly in light\n * mode (no `.dark` class), falling through to the media query made\n * the bleed wash render with the dark-mode (white + screen) ink\n * over a white editor surface — the wash became invisible because\n * the renderer's notion of \"dark\" diverged from what the user\n * actually saw on screen. Apps that want dark behaviour must set\n * the explicit signal; this matches how the rest of the design\n * system resolves theme.\n */\nfunction isDocumentDark(_target: HTMLElement | null): boolean {\n if (typeof document === 'undefined') return false;\n const html = document.documentElement;\n if (html.classList.contains('dark')) return true;\n if (html.getAttribute('data-theme') === 'dark') return true;\n return false;\n}\n\n/**\n * Quick \"is this ink dark / light\" check used to decide whether to\n * flip an author-supplied colour for the current theme. We only\n * need to handle hex literals (`#rgb`, `#rrggbb`) and `rgb(...)` —\n * the formats Loom emits. CSS colour names / `hsl()` would fall\n * through to \"neither\" and skip the flip, which is the safe choice\n * (no surprising visual change for unfamiliar inputs).\n *\n * @internal — exposed for tests; not part of the package's public\n * API. Don't import these from outside the canvas package.\n */\nexport function isInkDark(css: string): boolean {\n const luma = inkLuminance(css);\n return luma !== null && luma < 0.5;\n}\n/** @internal */\nexport function isInkLight(css: string): boolean {\n const luma = inkLuminance(css);\n return luma !== null && luma >= 0.5;\n}\n/** @internal */\nexport function inkLuminance(css: string): number | null {\n const trimmed = css.trim();\n // #rgb / #rrggbb\n if (trimmed.startsWith('#')) {\n const hex = trimmed.slice(1);\n let r: number, g: number, b: number;\n if (hex.length === 3) {\n r = parseInt(hex[0] + hex[0], 16);\n g = parseInt(hex[1] + hex[1], 16);\n b = parseInt(hex[2] + hex[2], 16);\n } else if (hex.length === 6) {\n r = parseInt(hex.slice(0, 2), 16);\n g = parseInt(hex.slice(2, 4), 16);\n b = parseInt(hex.slice(4, 6), 16);\n } else return null;\n if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) return null;\n return relLuma(r, g, b);\n }\n // rgb(...) / rgba(...)\n const m = trimmed.match(/^rgba?\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)/);\n if (m) return relLuma(Number(m[1]), Number(m[2]), Number(m[3]));\n return null;\n}\nfunction relLuma(r: number, g: number, b: number): number {\n const lin = (c: number) => {\n const cs = c / 255;\n return cs <= 0.03928 ? cs / 12.92 : Math.pow((cs + 0.055) / 1.055, 2.4);\n };\n return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);\n}\n\n// ── SvgPath helpers ───────────────────────────────────────────────────────────\n\nfunction pathToCanvas(ctx: CanvasRenderingContext2D, path: SvgPath): void {\n if (path.kind === 'rect') {\n ctx.rect(path.x, path.y, path.width, path.height);\n return;\n }\n if (path.kind === 'roundedRect') {\n const rx = path.rx ?? path.ry ?? 0;\n const ry = path.ry ?? path.rx ?? 0;\n if (rx <= 0 && ry <= 0) {\n ctx.rect(path.x, path.y, path.width, path.height);\n return;\n }\n // `roundRect` is supported in every browser we target. rx/ry can\n // differ, so pass an array of [rx, ry, rx, ry] equivalents via the\n // { x, y } radii form.\n if (rx === ry) {\n ctx.roundRect(path.x, path.y, path.width, path.height, rx);\n } else {\n ctx.roundRect(path.x, path.y, path.width, path.height, [\n { x: rx, y: ry },\n ] as unknown as number);\n }\n return;\n }\n // Free-form SVG path. We rely on Path2D's SVG-path-data parser, then\n // add it to the current path. A bit indirect, but it covers arbitrary\n // author-supplied polygons without us writing a parser.\n const p2d = new Path2D(path.d);\n // Path2D path can be added but only via ctx.fill/ctx.stroke directly.\n // For our uses (fills, strokes), drawing the Path2D instance works\n // and respects the current transform.\n (ctx as unknown as { __pieceGuidePath2D?: Path2D }).__pieceGuidePath2D = p2d;\n}\n\nfunction fillSvgPath(\n ctx: CanvasRenderingContext2D,\n path: SvgPath,\n fillRule?: CanvasFillRule,\n): void {\n if (path.kind === 'path') {\n const p2d = new Path2D(path.d);\n if (fillRule) ctx.fill(p2d, fillRule);\n else ctx.fill(p2d);\n return;\n }\n ctx.beginPath();\n pathToCanvas(ctx, path);\n if (fillRule) ctx.fill(fillRule);\n else ctx.fill();\n}\n\nfunction strokeSvgPath(ctx: CanvasRenderingContext2D, path: SvgPath): void {\n if (path.kind === 'path') {\n const p2d = new Path2D(path.d);\n ctx.stroke(p2d);\n return;\n }\n ctx.beginPath();\n pathToCanvas(ctx, path);\n ctx.stroke();\n}\n\nfunction addSvgPathSubpath(ctx: CanvasRenderingContext2D, path: SvgPath): void {\n if (path.kind === 'path') {\n // Path-kind can't be added as a sub-path on the ctx (Path2D\n // commands don't have a \"merge into current ctx path\" API). Callers\n // that need a compound (outer ∖ inner) for path-kind use the\n // `addToPath2D` helper below, which takes a Path2D as the\n // accumulator and supports every SvgPath kind via Path2D.addPath\n // for path-kind. Pure-ctx callers either fall through to a flat\n // outer fill (drawBleedRing-step 1) or use `fillSvgPath` directly.\n return;\n }\n pathToCanvas(ctx, path);\n}\n\n/**\n * Add an SvgPath to a Path2D accumulator. Mirrors `addSvgPathSubpath`\n * but works for every SvgPath kind: rect/roundedRect via the Path2D\n * primitive methods, path-kind via `Path2D.addPath(new Path2D(d))`.\n *\n * Use the resulting Path2D with `ctx.fill(p2d, 'evenodd')` /\n * `ctx.clip(p2d, 'evenodd')` for compound (outer ∖ inner) operations\n * — including across mixed-kind shapes (rect outer, path-kind inner).\n */\nfunction addToPath2D(p2d: Path2D, path: SvgPath): void {\n if (path.kind === 'rect') {\n p2d.rect(path.x, path.y, path.width, path.height);\n return;\n }\n if (path.kind === 'roundedRect') {\n const rx = path.rx ?? path.ry ?? 0;\n const ry = path.ry ?? path.rx ?? 0;\n if (rx <= 0 && ry <= 0) {\n p2d.rect(path.x, path.y, path.width, path.height);\n return;\n }\n if (rx === ry) {\n p2d.roundRect(path.x, path.y, path.width, path.height, rx);\n } else {\n p2d.roundRect(path.x, path.y, path.width, path.height, [\n { x: rx, y: ry },\n ] as unknown as number);\n }\n return;\n }\n // path-kind: parse via Path2D, attach to accumulator.\n p2d.addPath(new Path2D(path.d));\n}\n\nfunction resolveBoundary(\n guide: PieceGuide | undefined,\n naturalW: number,\n naturalH: number,\n): SvgPath {\n // Authored boundaries live in the piece's NATURAL coord system (the\n // unrotated frame the layout was authored in). The fallback \"full\n // piece\" rect must therefore also be in natural dims, NOT the\n // displayed (post-rotation) `piece.width × piece.height`. Otherwise\n // a 90°/270° piece's fallback boundary lands sideways in the rotated\n // local frame.\n return (\n guide?.boundary ?? {\n kind: 'rect',\n x: 0,\n y: 0,\n width: naturalW,\n height: naturalH,\n }\n );\n}\n\nfunction resolveSafeArea(\n guide: PieceGuide | undefined,\n piece: PieceGuideRendererPiece,\n naturalW: number,\n naturalH: number,\n): SvgPath | null {\n if (guide?.safeArea) return guide.safeArea;\n if (piece.safeAreaInsetPx && piece.safeAreaInsetPx > 0) {\n const inset = piece.safeAreaInsetPx;\n return {\n kind: 'rect',\n x: inset,\n y: inset,\n width: Math.max(0, naturalW - inset * 2),\n height: Math.max(0, naturalH - inset * 2),\n };\n }\n return null;\n}\n\n// ── Main renderer ─────────────────────────────────────────────────────────────\n\nexport function renderPieceGuides(params: PieceGuideRenderParams): void {\n const { ctx, pieces, pieceGuides, artboardOrigin, zoom } = params;\n if (!pieces.length) return;\n\n const show: Required<PieceGuideVisibility> = {\n bleed: params.show?.bleed ?? true,\n zones: params.show?.zones ?? true,\n boundary: params.show?.boundary ?? true,\n safeArea: params.show?.safeArea ?? true,\n labels: params.show?.labels ?? true,\n };\n\n const canvasEl = ctx.canvas as HTMLCanvasElement | OffscreenCanvas;\n const domTarget =\n canvasEl instanceof HTMLCanvasElement ? canvasEl : null;\n const isDark = isDocumentDark(domTarget);\n // Theme-aware default for the bleed wash. The wash is the band\n // that fades from transparent inside the piece to opaque past the\n // boundary. It needs to *contrast* with the editor surface\n // underneath:\n // - Light mode: surface is near-white → black ink pushed through `multiply`.\n // - Dark mode: surface is near-black → white ink pushed through `screen`.\n // Pure black / pure white (rather than `#333` / `#ccc`) so the band\n // pops against the editor surface AND visibly transforms artwork\n // that crosses into it — the blend math collapses everything in\n // the band to a single tone, telegraphing \"this region is the\n // crop zone, not part of your design.\"\n const themedDefaults = {\n ...DEFAULTS,\n bleedFill: isDark ? '#ffffff' : '#000000',\n };\n const s = { ...themedDefaults, ...params.style };\n // Merge per-kind colour maps shallowly.\n s.zoneFill = { ...DEFAULTS.zoneFill, ...(params.style?.zoneFill ?? {}) };\n s.zoneStroke = { ...DEFAULTS.zoneStroke, ...(params.style?.zoneStroke ?? {}) };\n\n // Resolve CSS vars ONCE per render against the canvas element. The\n // canvas 2D context doesn't understand var(...), but its owning\n // <canvas> can be read for computed styles because .app-modern (which\n // wraps the canvas) carries the theme variables we care about.\n let bleedFillCss = resolveCssColor(s.bleedFill, domTarget);\n const dieCutTintCss = resolveCssColor(s.dieCutTint, domTarget);\n let boundaryStrokeCss = resolveCssColor(s.boundaryStroke, domTarget);\n\n // Author-supplied `bleedFill` (e.g. `#000000` saved in a Loom\n // layout) was likely picked while looking at the white editor\n // surface. In dark mode that ink is invisible against the dark\n // surface — same value, no contrast. Flip dark fills to white in\n // dark mode (and vice versa) so the wash band stays visible\n // regardless of editor theme. Keep the original alpha — the\n // author's `bleedOpacity` is a deliberate intensity choice we\n // don't want to override.\n if (isDark && isInkDark(bleedFillCss)) {\n bleedFillCss = '#ffffff';\n } else if (!isDark && isInkLight(bleedFillCss)) {\n bleedFillCss = '#000000';\n }\n\n // Same flip for boundary outline: a multi-piece spread (AFOOES and\n // similar) authors a white boundary so it pops against a dark editor\n // surface, but on a light surface white is invisible. Mirror the\n // bleed-fill behaviour so the stroke stays high-contrast across themes.\n if (isDark && isInkDark(boundaryStrokeCss)) {\n boundaryStrokeCss = '#ffffff';\n } else if (!isDark && isInkLight(boundaryStrokeCss)) {\n boundaryStrokeCss = '#000000';\n }\n\n // ADR-0054: for die-cut blur we need a SNAPSHOT of the canvas taken\n // *before* we draw any guide pixels. Without this, the blur pass\n // copies the canvas onto itself and re-includes the bleed-ring +\n // boundary we're about to draw, producing a \"smaller vignette\n // inside the die-cut\" ghost artifact. Only take the snapshot when\n // there's at least one die-cut zone to draw.\n const hasDieCut = pieces.some((p) =>\n (pieceGuides?.[p.id]?.zones ?? []).some((z) => z.kind === 'dieCut'),\n );\n const backdrop =\n hasDieCut && domTarget ? snapshotCanvas(domTarget) : null;\n\n ctx.save();\n // Enter artboard coordinates. artboardOrigin is where the active\n // artboard starts in world space (usually (0, 0) for single-artboard\n // setups, but non-zero for multi-artboard layouts).\n ctx.translate(artboardOrigin.x, artboardOrigin.y);\n\n for (const piece of pieces) {\n // When focused on a specific piece, skip non-focused pieces\n // entirely — no boundary stroke, no zones, no labels, no chrome at\n // all. The downstream element-clip is already narrowed to the\n // focused silhouette in `useCanvasRenderLoop`, so painting\n // neighbour chrome here would just add stray strokes the user\n // can't actually design against. Earlier we dimmed neighbours to\n // `FOCUSED_NEIGHBOUR_ALPHA` (≈0.3) for \"structural context\"; the\n // current UX preference is to fully hide them when focused, with\n // the focus picker as the affordance to switch back to spread.\n if (\n params.focusedPieceId != null &&\n piece.id !== params.focusedPieceId\n ) {\n continue;\n }\n\n const guide = pieceGuides?.[piece.id];\n const zones = guide?.zones ?? [];\n const labels = guide?.labels ?? [];\n\n ctx.save();\n ctx.translate(piece.x, piece.y);\n // `piece.rotation` is the visualLayout rotation that swaps the\n // piece's displayed dim relative to its natural (bbox) dim. We need\n // to draw boundary / safeArea / bleed-ring in the piece's NATURAL\n // coord system (where they were authored) and have the result land\n // axis-aligned within the piece's DISPLAYED bounding box. The old\n // \"translate to center, rotate, translate back\" pattern uses the\n // displayed center as the pivot — when natural and displayed dims\n // differ (as they do whenever rotation is 90/270), the rotated rect\n // ends up offset from the displayed bounds by (Nw-Nh)/2 in each\n // axis, which made the bleed shading misalign with the actual\n // piece edges. The explicit per-rotation transforms below map\n // natural (0,0)→displayed corner exactly:\n // 90° CW : translate(D_w, 0) rotate(π/2)\n // 180° : translate(D_w, D_h) rotate(π)\n // 270° CW: translate(0, D_h) rotate(3π/2)\n // After the transform, drawing at natural piece-local coords lands\n // axis-aligned within the displayed bounds (0,0)-(piece.width,\n // piece.height).\n const rotation = piece.rotation ?? 0;\n if (rotation === 90) {\n ctx.translate(piece.width, 0);\n ctx.rotate(Math.PI / 2);\n } else if (rotation === 180) {\n ctx.translate(piece.width, piece.height);\n ctx.rotate(Math.PI);\n } else if (rotation === 270) {\n ctx.translate(0, piece.height);\n ctx.rotate((3 * Math.PI) / 2);\n }\n // Natural (unrotated) dim — what boundary / safeArea / outerShape\n // were authored against. Equals (piece.height, piece.width) when\n // rotation swaps dims, else (piece.width, piece.height).\n const isOrtho = rotation === 90 || rotation === 270;\n const naturalW = isOrtho ? piece.height : piece.width;\n const naturalH = isOrtho ? piece.width : piece.height;\n\n // Resolve boundary / safe area in the piece's NATURAL coord system.\n // `guide.boundary` and `guide.safeArea` are authored against natural\n // dims; the fallbacks (full-piece rect, inset rect) must match.\n const boundary = resolveBoundary(guide, naturalW, naturalH);\n const safeArea = resolveSafeArea(guide, piece, naturalW, naturalH);\n\n // We only reach this point for focused / spread pieces. In spread\n // mode `focusedPieceId == null`, so this is the focused piece by\n // definition; the variable is preserved for any future neighbour-\n // specific rendering inside this block.\n const isFocusedPiece = true;\n\n // Piece outer shape — the *physical* outer edge of the product\n // (e.g. a phone case's rounded corners). If the author set\n // `outerRadius` on the guide, use a rounded rect; otherwise fall\n // back to the sharp piece rect. All per-piece rendering below\n // clips to this shape so nothing bleeds into the sharp corners\n // of the piece's bounding rect.\n const outerRadius = Math.max(0, guide?.outerRadius ?? 0);\n const outerShape: SvgPath =\n outerRadius > 0\n ? {\n kind: 'roundedRect',\n x: 0,\n y: 0,\n width: naturalW,\n height: naturalH,\n rx: outerRadius,\n ry: outerRadius,\n }\n : {\n kind: 'rect',\n x: 0,\n y: 0,\n width: naturalW,\n height: naturalH,\n };\n // Guides are drawn inside the outer shape only. We keep this as an\n // inner save() so the clip can be released at the end of the piece\n // to run a destination-out pass that erases *everything* outside\n // the outer shape — including artwork the caller drew before\n // renderPieceGuides ran. Without that final pass, the artwork's\n // sharp corners would still show through the rounded edge.\n if (outerRadius > 0) {\n ctx.save();\n ctx.beginPath();\n addSvgPathSubpath(ctx, outerShape);\n ctx.clip();\n }\n\n // 1. Bleed ring — outerShape ∖ (boundary | safeArea).\n //\n // `bleedInnerEdge: 'safeArea'` lets authors pull the fade inwards\n // so the \"safe zone\" reads as the clean area instead of the\n // design-canvas boundary. Falls back to boundary when no safe\n // area is authored or when it's a free-form path we can't yet\n // knock out via the blur trick.\n // `bleedOpacity: 0` is the explicit \"disable bleed wash\" signal —\n // some products (e.g. notebooks where the wrap geometry is\n // captured by the artboard margin, not by piece bleed) don't want\n // a visible bleed band at all. Honour that here so the data is\n // the source of truth.\n if (show.bleed && s.bleedOpacity > 0) {\n const wantInner: SvgPath | null =\n s.bleedInnerEdge === 'safeArea' && safeArea ? safeArea : boundary;\n // All SvgPath kinds are supported now — `addToPath2D` handles\n // rect / roundedRect / path uniformly via Path2D composition.\n // Earlier versions gated path-kind out because the legacy\n // `addSvgPathSubpath` was a no-op for path-kind in compound\n // operations; that limitation is gone.\n if (wantInner) {\n // Theme-aware blend: in light mode `multiply` darkens artwork\n // that crosses into the bleed area (and produces a dark wash\n // over empty canvas). In dark mode `screen` lightens — both\n // make the cropped artwork *visibly affected* instead of being\n // covered by a flat tint.\n const bleedBlend: GlobalCompositeOperation = isDark\n ? 'screen'\n : 'multiply';\n // Honour the author's `bleedOpacity` — multiply/screen blend\n // amplifies the contrast (compared to the old `source-over`\n // paradigm), so legacy values like `0.15` now produce a\n // visible-but-subtle wash; values up to ~0.3 read as the\n // \"strong dark band\" UX. `0` is honoured upstream as\n // \"disable wash entirely\" so we don't bother short-circuiting\n // here. Authors set the strength per layout — phone case ≈ 0.3\n // for a strong frame, notebook ≈ 0.1–0.15 for subtle structure.\n const effectiveBleedOpacity = s.bleedOpacity;\n // Bleed ring fills `outerShape ∖ wantInner` — i.e. from the\n // piece edge inward, stopping at the safe-area (or boundary,\n // depending on `bleedInnerEdge`). The wash hugs the actual\n // clip edge so the user reads \"everything outside the safe\n // area is bleed.\"\n if (s.bleedMode === 'solid') {\n drawBleedRingSolid(\n ctx,\n outerShape,\n wantInner,\n bleedFillCss,\n effectiveBleedOpacity,\n bleedBlend,\n );\n } else {\n drawBleedRing(\n ctx,\n naturalW,\n naturalH,\n outerShape,\n wantInner,\n bleedFillCss,\n effectiveBleedOpacity,\n s.bleedBlurScale,\n zoom,\n bleedBlend,\n );\n }\n }\n }\n\n // 2. Non-die-cut zones.\n if (show.zones) {\n for (const zone of zones) {\n if (zone.kind === 'dieCut') continue;\n ctx.save();\n // `s.zoneFill`/`s.zoneStroke` come from a `Partial` style override\n // map, so a per-kind entry can be missing — fall back to the\n // (complete) DEFAULTS map rather than painting with `undefined`.\n ctx.fillStyle = s.zoneFill[zone.kind] ?? DEFAULTS.zoneFill[zone.kind];\n fillSvgPath(ctx, zone.path);\n ctx.strokeStyle = s.zoneStroke[zone.kind] ?? DEFAULTS.zoneStroke[zone.kind];\n setZoomInvariantStroke(\n ctx,\n 1.5,\n zone.kind === 'wrap' ? [4, 3] : [],\n );\n strokeSvgPath(ctx, zone.path);\n ctx.setLineDash([]);\n ctx.restore();\n }\n }\n\n // 3. Die-cut zones.\n if (show.zones) {\n for (const zone of zones) {\n if (zone.kind !== 'dieCut') continue;\n if (s.dieCutMode === 'knockout') {\n // ADR-0060: for non-focused pieces, draw only the outline\n // stroke and skip the destination-out fill. Punching real\n // transparent holes through the canvas for neighbour\n // pieces would dominate the focused-piece view — the\n // structural context is communicated by the dimmed stroke\n // alone. The focused piece keeps the full knockout\n // (including the partial-knockout-while-interacting\n // behaviour from earlier).\n drawDieCutKnockout(\n ctx,\n zone,\n s.dieCutStroke,\n s.dieCutStrokeWidthPx,\n params.interacting === true,\n isFocusedPiece,\n backdrop,\n s.dieCutBlurPx,\n );\n } else {\n drawDieCut(\n ctx,\n zone,\n backdrop,\n dieCutTintCss,\n s.dieCutTintAlpha,\n s.dieCutBlurPx,\n s.dieCutStroke,\n s.dieCutStrokeWidthPx,\n );\n }\n }\n }\n\n // 4. Boundary outline (opt-in). Uses the theme-flipped colour so\n // the stroke stays visible on both light and dark editor surfaces.\n if (show.boundary && boundaryStrokeCss && s.boundaryStrokeWidthPx > 0) {\n ctx.save();\n ctx.strokeStyle = boundaryStrokeCss;\n setZoomInvariantStroke(ctx, s.boundaryStrokeWidthPx);\n strokeSvgPath(ctx, boundary);\n ctx.restore();\n }\n\n // 5. Legacy dashed safe-area outline (opt-in).\n if (show.safeArea && safeArea && s.safeAreaStroke) {\n ctx.save();\n ctx.strokeStyle = s.safeAreaStroke;\n setZoomInvariantStroke(ctx, s.safeAreaStrokeWidthPx, s.safeAreaDashPx);\n strokeSvgPath(ctx, safeArea);\n ctx.setLineDash([]);\n ctx.restore();\n }\n\n // 6. Labels.\n if (show.labels && labels.length > 0) {\n drawLabels(ctx, labels, s, zoom);\n }\n\n // Release the outer-shape clip. We intentionally do NOT run a\n // `destination-out` \"final crop\" pass to clear the piece rect's\n // corner wedges anymore.\n //\n // Why we used to: the bg fill / artwork was painted as a sharp\n // rect, so the corners had to be erased after the fact to get a\n // rounded outer shape.\n //\n // Why we no longer need it: the artboard bg fill in\n // `useCanvasRenderLoop.ts` already rounds itself to\n // `effectiveBorderRadius` (the piece's `outerRadius`), and the\n // element-layer clip in `CanvasRenderer.applyArtboardClipPath`\n // does the same — neither paints into the corner wedges to begin\n // with. The crop pass was only catching the overflow-dim 30%\n // halo that selected/hovered elements draw outside the artboard.\n // Erasing that halo while a user was resizing or repositioning\n // an image stripped exactly the manipulation feedback users rely\n // on, so during interaction the corners would suddenly read as\n // hard rounded clips instead of letting the user see how far the\n // element actually extended. Letting the halo through restores\n // that feedback at idle (no halo, corners transparent) and\n // during interaction (soft 30% preview into the corners).\n if (outerRadius > 0) {\n ctx.restore();\n }\n\n ctx.restore();\n }\n\n ctx.restore();\n}\n\n// ── Layer helpers ─────────────────────────────────────────────────────────────\n\n/**\n * Bleed ring = pieceRect ∖ boundary, rendered as a soft vignette that\n * fades gradually on *both* edges — the outer edge at the piece rect\n * (into the editor bg) and the inner edge at the boundary (into the\n * design canvas). Crucially the fade lives in the alpha channel only,\n * so the translucent fill doesn't let artwork colours \"glow\" through.\n *\n * Technique:\n * 1. Render to an offscreen canvas PADDED by the blur radius on each\n * side. Without padding, canvas 2D's blur filter can't spread\n * beyond the canvas dimensions — it clamps at the edge, which\n * leaves a hard rectangular outer edge where the vignette meets\n * the editor bg. Padding gives the blur room to fade outward.\n * 2. Fill the piece rect (offset into the padded offscreen) with the\n * bleed colour, using a CSS blur filter. The blur softens the\n * outer edge: alpha ≈ opacity inside the piece, tapers to 0 about\n * blurRadius outside the piece rect.\n * 3. Punch out the boundary using `destination-out` with the same\n * blur — this fades the fill to zero along the inner boundary\n * edge, creating the symmetric inner fade.\n * 4. `drawImage` the padded offscreen back onto the main ctx, shifted\n * by `-pad` so the padded fade region sits outside the piece rect\n * as intended.\n *\n * Result: soft bi-directional gradient in the bleed band, no hard line\n * at either edge, no colour bleeding from artwork.\n */\nfunction drawBleedRing(\n ctx: CanvasRenderingContext2D,\n /**\n * Natural (unrotated) piece dims. Drawing here happens in the piece's\n * natural coord system AFTER the per-rotation transform set up by the\n * caller, so the offscreen bitmap and blur radius must be sized\n * against natural dims, not the displayed (post-rotation) dims.\n */\n naturalW: number,\n naturalH: number,\n outerShape: SvgPath,\n boundary: SvgPath,\n fill: string,\n opacity: number,\n blurScale: number,\n zoom: number,\n /**\n * Blend mode used to composite the band onto the main canvas. `multiply`\n * (light mode) darkens artwork that crosses into the bleed area; `screen`\n * (dark mode) lightens it. Falls back to `source-over` when not provided\n * so the wash sits as a flat tint over empty canvas.\n */\n blendMode: GlobalCompositeOperation = 'source-over',\n): void {\n const blurWorldPx = Math.max(\n 6,\n Math.round(Math.min(naturalW, naturalH) * blurScale),\n );\n const blurCssPx = zoom > 0 ? blurWorldPx * zoom : blurWorldPx;\n\n const dpr =\n typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1;\n\n // Pad the offscreen canvas by the blur radius on each side so the\n // Gaussian can fade outward past the piece edge. `pad` is in world\n // (piece-local) px.\n const pad = blurWorldPx;\n const paddedW = naturalW + 2 * pad;\n const paddedH = naturalH + 2 * pad;\n const physW = Math.max(1, Math.ceil(paddedW * zoom * dpr));\n const physH = Math.max(1, Math.ceil(paddedH * zoom * dpr));\n\n const off = acquireOffscreen(physW, physH);\n if (!off) return;\n const octx = off.getContext('2d');\n if (!octx) return;\n\n octx.setTransform(1, 0, 0, 1, 0, 0);\n octx.clearRect(0, 0, physW, physH);\n // Piece-local → physical pixel transform. After this, (0, 0) in the\n // offscreen is the padded-offscreen's top-left; we translate by\n // `pad` so piece (0, 0) lands at offscreen (pad, pad).\n octx.scale(zoom * dpr, zoom * dpr);\n octx.translate(pad, pad);\n\n // 1. Flood-fill the piece outer shape with the bleed colour, NO\n // blur. Previously this pass was also blurred so the outer edge\n // of the ring faded into the surrounding editor surface — but\n // that tapered alpha to ~0 at the piece corners, revealing\n // whatever's behind the canvas element (white editor bg =>\n // \"white corners\"). We now fill the outer shape cleanly at the\n // author's `bleedOpacity` and rely on step 2's blurred knockout\n // at the boundary for the inner soft fade. The outer edge sits\n // exactly at the piece's outer shape, no fade required.\n octx.fillStyle = fill;\n octx.globalAlpha = opacity;\n const outerPath = new Path2D();\n addToPath2D(outerPath, outerShape);\n octx.fill(outerPath);\n\n // 2. Punch out the boundary with a blurred alpha mask. Same blur\n // radius so the inner fade visually matches the outer fade.\n // Uses Path2D so path-kind boundaries (multi-piece spreads) work\n // the same as rect/roundedRect.\n octx.globalAlpha = 1;\n octx.filter = `blur(${blurCssPx * dpr}px)`;\n octx.globalCompositeOperation = 'destination-out';\n octx.fillStyle = '#000';\n const boundaryPath = new Path2D();\n addToPath2D(boundaryPath, boundary);\n octx.fill(boundaryPath);\n octx.filter = 'none';\n octx.globalCompositeOperation = 'source-over';\n\n // 3. Composite the padded offscreen onto the main ctx. The dest rect\n // starts at (-pad, -pad) in piece-local coords so the offscreen's\n // padded zone sits just outside the piece rect (where the outer\n // fade belongs). Apply the theme-aware blend so artwork that\n // crosses into the bleed band visibly darkens (light mode) or\n // lightens (dark mode), instead of the band sitting as a flat\n // tint that hides the underlying pixels.\n const prevOp = ctx.globalCompositeOperation;\n ctx.globalCompositeOperation = blendMode;\n ctx.drawImage(off, -pad, -pad, paddedW, paddedH);\n ctx.globalCompositeOperation = prevOp;\n}\n\n/**\n * Flat-fill variant of the bleed ring — `pieceRect ∖ boundary`, no blur.\n * Uses the even-odd fill rule with two sub-paths so the inside of the\n * boundary is cut out cleanly regardless of the boundary shape.\n * `opacity` is honoured verbatim here (unlike the gradient variant\n * which relies on `opacity === 1` to avoid a halo-then-jump artifact).\n */\nfunction drawBleedRingSolid(\n ctx: CanvasRenderingContext2D,\n outerShape: SvgPath,\n boundary: SvgPath,\n fill: string,\n opacity: number,\n /** See `drawBleedRing` — applied via `globalCompositeOperation`. */\n blendMode: GlobalCompositeOperation = 'source-over',\n): void {\n ctx.save();\n // Multiply against any existing globalAlpha (set by the per-piece\n // dim wrapper for non-focused pieces in ADR-0060) instead of\n // overwriting, so the bleed wash scales with the piece's dim level.\n ctx.globalAlpha = ctx.globalAlpha * opacity;\n ctx.globalCompositeOperation = blendMode;\n ctx.fillStyle = fill;\n // Build a compound Path2D so we can mix path-kind boundaries with\n // rect / roundedRect outers (or any other combination). Even-odd\n // fill rule turns \"outer + inner\" into \"outer minus inner\".\n const compound = new Path2D();\n addToPath2D(compound, outerShape);\n addToPath2D(compound, boundary);\n ctx.fill(compound, 'evenodd');\n ctx.restore();\n}\n\n/**\n * A single reusable offscreen canvas for the bleed-ring compositing\n * pass, resized lazily. Avoids allocating a new canvas per frame.\n */\nlet cachedBleedOffscreen: HTMLCanvasElement | null = null;\nfunction acquireOffscreen(\n physW: number,\n physH: number,\n): HTMLCanvasElement | null {\n if (typeof document === 'undefined') return null;\n if (!cachedBleedOffscreen) {\n cachedBleedOffscreen = document.createElement('canvas');\n }\n if (cachedBleedOffscreen.width !== physW)\n cachedBleedOffscreen.width = physW;\n if (cachedBleedOffscreen.height !== physH)\n cachedBleedOffscreen.height = physH;\n return cachedBleedOffscreen;\n}\n\n/**\n * Die-cut zones look like a \"punched hole\" in the design canvas. The\n * old overlay did this via `backdrop-filter: blur(4px)` + semi-transparent\n * dark fill in a <foreignObject>. We reproduce it by:\n *\n * 1. Clipping to the zone's shape.\n * 2. Drawing a pre-guide snapshot of the canvas (taken before any\n * piece-guide pixels landed) with a CSS blur filter applied, in\n * identity transform space so the snapshot's physical pixels line\n * up 1:1 with the live canvas. The snapshot predates the bleed\n * ring, so blurring it doesn't re-include the ring and produce a\n * ghosted \"smaller vignette\" inside the cutout.\n * 3. Overlaying the dark tint to read as a punched hole.\n *\n * No stroke — the dark tint on its own reads as a hole; the 1px white\n * outline we used to draw just added chrome that fought with the\n * artwork. Bring it back via `style.zoneStroke.dieCut` if needed.\n */\nfunction drawDieCut(\n ctx: CanvasRenderingContext2D,\n zone: ZoneShape,\n backdrop: HTMLCanvasElement | null,\n tint: string,\n tintAlpha: number,\n blurCssPx: number,\n stroke: string,\n strokeWidthPx: number,\n): void {\n const p = zone.path;\n\n ctx.save();\n // Clip to the zone in the CURRENT (world) transform space. The clip\n // path is rasterised to physical pixels at clip() time, so it\n // survives any transform changes we make below.\n ctx.beginPath();\n pathToCanvas(ctx, p);\n ctx.clip();\n\n if (backdrop) {\n // Match the overflow-dim treatment exactly (CanvasRenderer's\n // `renderElementWithOverflowDimming`, Pass 1): erase the canvas\n // pixels in the zone, then redraw the blurred backdrop at\n // `INTERACTION_PARTIAL_KNOCKOUT_RETAIN` (0.3) alpha. The result\n // is artwork at 30% over whatever the editor surface shows\n // through — same vocabulary as the overflow halo, so the\n // die-cut and overflow regions read as the same \"dimmed\n // preview\" treatment instead of the old constant-tint look.\n //\n // 1. Knock out the canvas pixels in the zone so the redraw\n // composites against transparency (and thus the editor\n // surface), not on top of the original opaque artwork.\n ctx.save();\n ctx.globalCompositeOperation = 'destination-out';\n ctx.fillStyle = '#000';\n if (p.kind === 'path') {\n fillSvgPath(ctx, p);\n } else {\n ctx.fillRect(p.x, p.y, p.width, p.height);\n }\n ctx.restore();\n\n // 2. Redraw the blurred snapshot at the dim alpha. We reset to\n // identity transform so the snapshot's physical pixels map\n // 1:1 to the canvas's pixel grid. Blur radius is unscaled by\n // DPR to match CanvasRenderer's overflow-dim pass\n // (`blur(4px)` at CanvasRenderer.ts line ~269) — both passes\n // should read as the same blur strength on the same display.\n if (blurCssPx > 0) {\n const baseAlpha = ctx.globalAlpha;\n const t = ctx.getTransform();\n ctx.save();\n ctx.setTransform(1, 0, 0, 1, 0, 0);\n ctx.filter = `blur(${blurCssPx}px)`;\n // Multiply against any per-piece dim already applied via\n // `ctx.globalAlpha` (ADR-0060) so non-focused pieces' die-cut\n // areas dim with the rest of the piece chrome.\n // 0.4 instead of `INTERACTION_PARTIAL_KNOCKOUT_RETAIN` (0.3): the\n // overflow halo composites artwork at 0.3 over a partially\n // populated canvas (other elements bleed through), so the eye\n // reads the *visible* alpha as a bit higher than the literal\n // 0.3. The die-cut snapshot here erases first and redraws onto\n // a clean transparent slate, so 0.3 looks subjectively darker.\n // 0.4 brings the perceived dim into alignment with the overflow\n // region.\n ctx.globalAlpha = baseAlpha * 0.4;\n ctx.drawImage(backdrop, 0, 0);\n ctx.filter = 'none';\n ctx.globalAlpha = baseAlpha;\n ctx.setTransform(t);\n ctx.restore();\n }\n }\n\n // Optional flat tint on top of the dimmed-blurred backdrop. Authors\n // can set `dieCutTintAlpha: 0` to disable entirely (matches the\n // overflow treatment). Default behaviour keeps a subtle wash for\n // legacy layouts that still expect it.\n if (tintAlpha > 0) {\n const baseAlpha = ctx.globalAlpha;\n ctx.fillStyle = tint;\n ctx.globalAlpha = baseAlpha * tintAlpha;\n if (p.kind === 'path') {\n fillSvgPath(ctx, p);\n } else {\n ctx.fillRect(p.x, p.y, p.width, p.height);\n }\n ctx.globalAlpha = baseAlpha;\n }\n\n ctx.restore();\n\n // Outline stroke — drawn OUTSIDE the clip so the stroke sits on top\n // of everything (blurred backdrop + tint + artwork outside). Only if\n // the author opted in via a non-empty colour. Because the stroke is\n // centred on the path, clipping it to the zone would hide half of it.\n if (stroke) {\n ctx.save();\n ctx.strokeStyle = stroke;\n setZoomInvariantStroke(ctx, strokeWidthPx);\n strokeSvgPath(ctx, p);\n ctx.restore();\n }\n}\n\n/**\n * Knockout variant — clears the zone's pixels from the canvas using\n * `destination-out` compositing. Whatever is beneath the canvas\n * element (page background, an underlying mockup, etc.) shows\n * through. The outline is still drawn on top.\n *\n * When `interacting` is true (the editor is mid-interaction — an\n * element is selected, hovered, or being dragged) we do a *partial*\n * knockout instead of a full one: `destination-out` with\n * `globalAlpha = 1 - INTERACTION_PARTIAL_KNOCKOUT_RETAIN`, which\n * leaves 30% of the underlying alpha intact. That matches the\n * overflow-dim halo's 30% multiplier on the outside corner wedges\n * (see `renderElementWithOverflowDimming` Pass 1) — visually, the\n * cutout reads as a window onto the artwork at the same opacity as\n * the overflow halo, so resize/drag feedback is consistent on both\n * sides of the artboard's outer shape. At idle (no interaction)\n * we fall through to the original full knockout for a clean punched\n * hole through to the page chrome below.\n */\nconst INTERACTION_PARTIAL_KNOCKOUT_RETAIN = 0.3;\n\nfunction drawDieCutKnockout(\n ctx: CanvasRenderingContext2D,\n zone: ZoneShape,\n stroke: string,\n strokeWidthPx: number,\n interacting: boolean,\n /**\n * ADR-0060: when false (this is a non-focused piece's die-cut in a\n * multi-piece spread), skip the destination-out fill entirely —\n * only the outline stroke draws. Real transparent holes in\n * neighbour pieces would dominate the focused-piece view; the\n * dimmed stroke is enough to communicate where the cutout sits.\n */\n isFocusedPiece: boolean = true,\n /**\n * Mid-interaction backdrop snapshot. When set AND `interacting` is\n * true, the cutout shows a blurred + dimmed preview of the artwork\n * underneath (matching the overflow halo treatment) so the user\n * can see what's getting clipped while they drag/resize. At idle,\n * this param is unused — full knockout fires regardless.\n */\n backdrop?: HTMLCanvasElement | null,\n blurCssPx: number = 0,\n): void {\n const p = zone.path;\n if (isFocusedPiece) {\n if (interacting && backdrop && blurCssPx > 0) {\n // Interacting + backdrop available: do the blur+dim preview\n // instead of a partial knockout. Step 1 erases the canvas\n // pixels in the zone (full destination-out), step 2 redraws\n // the blurred snapshot at the overflow-halo dim alpha so the\n // artwork crossing the cutout reads as \"this is what gets\n // clipped.\" Mirrors `drawDieCut`'s blur-tint pattern but\n // gated on interaction so idle stays a clean punched hole.\n ctx.save();\n ctx.beginPath();\n pathToCanvas(ctx, p);\n ctx.clip();\n\n // 1. Erase canvas pixels in the zone.\n ctx.save();\n ctx.globalCompositeOperation = 'destination-out';\n ctx.fillStyle = '#000';\n if (p.kind === 'path' || p.kind === 'roundedRect') {\n fillSvgPath(ctx, p);\n } else {\n ctx.fillRect(p.x, p.y, p.width, p.height);\n }\n ctx.restore();\n\n // 2. Redraw blurred snapshot at the overflow-halo alpha.\n const baseAlpha = ctx.globalAlpha;\n const t = ctx.getTransform();\n ctx.save();\n ctx.setTransform(1, 0, 0, 1, 0, 0);\n ctx.filter = `blur(${blurCssPx}px)`;\n ctx.globalAlpha = baseAlpha * 0.4;\n ctx.drawImage(backdrop, 0, 0);\n ctx.filter = 'none';\n ctx.globalAlpha = baseAlpha;\n ctx.setTransform(t);\n ctx.restore();\n\n ctx.restore();\n } else {\n // Idle (or no backdrop available): full knockout — punched\n // hole through to the page chrome below.\n ctx.save();\n ctx.globalCompositeOperation = 'destination-out';\n ctx.globalAlpha = interacting\n ? 1 - INTERACTION_PARTIAL_KNOCKOUT_RETAIN\n : 1;\n ctx.fillStyle = '#000';\n if (p.kind === 'path' || p.kind === 'roundedRect') {\n fillSvgPath(ctx, p);\n } else {\n ctx.fillRect(p.x, p.y, p.width, p.height);\n }\n ctx.restore();\n }\n }\n if (stroke) {\n ctx.save();\n ctx.strokeStyle = stroke;\n setZoomInvariantStroke(ctx, strokeWidthPx);\n strokeSvgPath(ctx, p);\n ctx.restore();\n }\n}\n\nfunction drawLabels(\n ctx: CanvasRenderingContext2D,\n labels: GuideLabel[],\n s: Required<Omit<PieceGuideRendererStyle, 'zoneFill' | 'zoneStroke'>>,\n zoom: number,\n): void {\n // Fonts and strokes naturally scale with ctx.scale(zoom). We want a\n // fixed CSS-pixel appearance, so divide by zoom to counteract.\n const fontWorldPx = zoom > 0 ? s.labelFontSizePx / zoom : s.labelFontSizePx;\n const haloWidth = Math.max(2, s.labelFontSizePx * 0.25);\n const haloWorldPx = zoom > 0 ? haloWidth / zoom : haloWidth;\n\n ctx.save();\n ctx.font = `${fontWorldPx}px ${s.labelFontFamily}`;\n\n for (const label of labels) {\n const anchor = label.anchor ?? 'c';\n ctx.textAlign = anchorToAlign(anchor);\n ctx.textBaseline = anchorToBaseline(anchor);\n\n // Halo: stroke first (white, thick) then fill on top for readable\n // labels over any background. Matches the old overlay's\n // paintOrder: 'stroke' behaviour.\n ctx.lineJoin = 'round';\n ctx.strokeStyle = s.labelHaloColor;\n ctx.lineWidth = haloWorldPx;\n ctx.strokeText(label.text, label.x, label.y);\n ctx.fillStyle = s.labelColor;\n ctx.fillText(label.text, label.x, label.y);\n }\n ctx.restore();\n}\n\nfunction anchorToAlign(a: NonNullable<GuideLabel['anchor']>): CanvasTextAlign {\n if (a.endsWith('l')) return 'start';\n if (a.endsWith('r')) return 'end';\n return 'center';\n}\n\nfunction anchorToBaseline(\n a: NonNullable<GuideLabel['anchor']>,\n): CanvasTextBaseline {\n if (a.startsWith('t')) return 'hanging';\n if (a.startsWith('b')) return 'alphabetic';\n return 'middle';\n}\n","/**\n * Multi-Selection Renderer\n * Handles rendering of multi-selection indicators, group bounds, handles, and dimension labels\n */\n\nimport { TextElement } from '../../../core/TextElement.js';\nimport { ImageElement } from '../../../core/ImageElement.js';\nimport { GroupElement } from '../../../core/GroupElement.js';\nimport type { ShapeElement } from '../../../core/ShapeElement.js';\nimport type { PathElement } from '../../../core/PathElement.js';\nimport type { InteractionStateMachine } from '../../../core/InteractionStateMachine.js';\n\n/** Elements that can be on the canvas */\ntype CanvasElement = TextElement | ImageElement | GroupElement | ShapeElement | PathElement;\n\n/** Multi-selection drag context stored on the state machine */\ninterface MultiSelectionDragContext {\n startX: number;\n startY: number;\n elementsStartData: Array<{ element: CanvasElement; startData: Record<string, unknown> }>;\n currentElements?: Map<string, CanvasElement>;\n currentDelta?: { dx: number; dy: number };\n startOBB?: MultiSelectionOBB;\n}\n\n/** Multi-selection resize context stored on the state machine */\ninterface MultiSelectionResizeContext {\n anchor: string;\n startBounds: { minX: number; maxX: number; minY: number; maxY: number };\n elementsStartData: Array<{ element: CanvasElement; startData: Record<string, unknown> }>;\n}\n\n/** Augmented state machine with multi-selection ad-hoc properties */\ntype AugmentedStateMachine = InteractionStateMachine & {\n _multiSelectionDragContext?: MultiSelectionDragContext;\n _multiSelectionResizeContext?: MultiSelectionResizeContext;\n};\n\nimport { getThemeAccentColor, ROTATION_HANDLE_DISTANCE } from '../../../constants.js';\nimport {\n getLineWidth,\n getHandleRadius,\n getDashPattern,\n renderRotationHandle,\n} from './renderingConstants.js';\n\nexport interface MultiSelectionOBB {\n centerX: number;\n centerY: number;\n width: number;\n height: number;\n rotation: number;\n}\n\nexport interface MultiSelectionGroupBounds {\n minX: number;\n minY: number;\n maxX: number;\n maxY: number;\n centerX: number;\n centerY: number;\n rotationHandleX: number;\n rotationHandleY: number;\n rotation: number;\n}\n\nexport interface RenderMultiSelectionParams {\n ctx: CanvasRenderingContext2D;\n displaySelection: string[];\n elements: CanvasElement[];\n activeArtboardElementIds: Set<string>;\n stateMachine: InteractionStateMachine;\n multiSelectionOBB: MultiSelectionOBB | null;\n isRotating: boolean;\n currentRotation: number;\n isMarqueeSelecting: boolean;\n zoom?: number;\n}\n\nexport interface RenderMultiSelectionResult {\n updatedOBB: MultiSelectionOBB | null;\n groupBounds: MultiSelectionGroupBounds | null;\n}\n\n/**\n * Renders multi-selection indicators including:\n * - Individual element borders\n * - Group bounding box with dotted border\n * - Corner resize handles\n * - Rotation handle\n * - Dimension label during resize\n */\nexport function renderMultiSelectionIndicators(params: RenderMultiSelectionParams): RenderMultiSelectionResult {\n const {\n ctx,\n displaySelection,\n elements,\n activeArtboardElementIds,\n stateMachine,\n multiSelectionOBB,\n isRotating,\n currentRotation,\n isMarqueeSelecting,\n zoom = 1.0,\n } = params;\n\n // Filter to only show elements in the active artboard\n const filteredDisplaySelection = displaySelection.filter((id) => activeArtboardElementIds.has(id));\n\n if (filteredDisplaySelection.length === 0) {\n return { updatedOBB: null, groupBounds: null };\n }\n\n // Check if we're currently dragging multi-selection to use in-progress positions\n const multiDragContext = (stateMachine as AugmentedStateMachine)._multiSelectionDragContext;\n const isCurrentlyDraggingMulti = stateMachine.getMode() === 'drag' && multiDragContext;\n\n // Render individual element borders\n filteredDisplaySelection.forEach((id) => {\n let element = elements.find((e) => e.id === id);\n\n // If actively dragging multi-selection, use the cached updated element from drag context\n if (isCurrentlyDraggingMulti && multiDragContext.currentElements) {\n const cachedElement = multiDragContext.currentElements.get(id);\n if (cachedElement) {\n element = cachedElement;\n }\n }\n\n if (element) {\n const bbox = element.getVisualBoundingBox();\n const rotationAnchor = element.getRotationAnchor();\n\n ctx.save();\n\n // Apply rotation around the element's rotation anchor\n if (element.rotation !== 0) {\n ctx.translate(rotationAnchor.x, rotationAnchor.y);\n ctx.rotate((-element.rotation * Math.PI) / 180);\n ctx.translate(-rotationAnchor.x, -rotationAnchor.y);\n }\n\n ctx.strokeStyle = getThemeAccentColor();\n ctx.lineWidth = getLineWidth(zoom);\n ctx.strokeRect(bbox.x, bbox.y, bbox.width, bbox.height);\n ctx.restore();\n }\n });\n\n // Calculate and render group bounds (combined bounding box)\n let selectedElements = filteredDisplaySelection\n .map((id) => elements.find((e) => e.id === id))\n .filter((e) => e !== undefined);\n\n // If actively dragging, use cached current elements for group bounds calculation\n if (isCurrentlyDraggingMulti && multiDragContext.currentElements) {\n selectedElements = selectedElements.map((element) => {\n const cachedElement = multiDragContext.currentElements!.get(element.id);\n return cachedElement || element;\n });\n }\n\n if (selectedElements.length === 0) {\n return { updatedOBB: null, groupBounds: null };\n }\n\n // Calculate axis-aligned bounding box containing all selected elements\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n\n selectedElements.forEach((element) => {\n const bbox = element.getVisualBoundingBox();\n // Get corners of potentially rotated element\n const corners = [\n { x: bbox.x, y: bbox.y },\n { x: bbox.x + bbox.width, y: bbox.y },\n { x: bbox.x, y: bbox.y + bbox.height },\n { x: bbox.x + bbox.width, y: bbox.y + bbox.height },\n ];\n\n // If element is rotated, we need to rotate corners\n if (element.rotation !== 0) {\n const rotationAnchor = element.getRotationAnchor();\n const rotationRad = (-element.rotation * Math.PI) / 180;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n\n corners.forEach((corner) => {\n const dx = corner.x - rotationAnchor.x;\n const dy = corner.y - rotationAnchor.y;\n corner.x = rotationAnchor.x + (dx * cos - dy * sin);\n corner.y = rotationAnchor.y + (dx * sin + dy * cos);\n });\n }\n\n // Update bounds\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 const groupWidth = maxX - minX;\n const groupHeight = maxY - minY;\n const centerX = (minX + maxX) / 2;\n const centerY = (minY + maxY) / 2;\n\n // Initialize or update OBB for multi-selection\n let obb = multiSelectionOBB;\n\n // If OBB doesn't exist or selection just changed, initialize it\n if (!obb || obb.width === 0 || obb.height === 0) {\n obb = {\n centerX,\n centerY,\n width: groupWidth,\n height: groupHeight,\n rotation: 0,\n };\n }\n\n // During drag, update the OBB center to follow the elements\n const isCurrentlyDragging = stateMachine.getMode() === 'drag' && (stateMachine as AugmentedStateMachine)._multiSelectionDragContext;\n if (isCurrentlyDragging) {\n const dragContext = (stateMachine as AugmentedStateMachine)._multiSelectionDragContext;\n // Use the stored start OBB and apply current delta for smooth rendering\n if (dragContext?.startOBB && dragContext?.currentDelta) {\n const { dx, dy } = dragContext.currentDelta;\n obb = {\n ...obb,\n centerX: dragContext.startOBB.centerX + dx,\n centerY: dragContext.startOBB.centerY + dy,\n };\n } else {\n // Fallback: use recalculated center from actual element positions\n obb = { ...obb, centerX, centerY };\n }\n }\n\n // During rotation, update the OBB rotation\n const isCurrentlyRotating = isRotating && stateMachine.getMode() === 'rotate';\n const groupRotation = isCurrentlyRotating ? obb.rotation + currentRotation : obb.rotation;\n\n // Use OBB dimensions for rendering (not recalculated axis-aligned bounds)\n const renderWidth = obb.width;\n const renderHeight = obb.height;\n const renderCenterX = obb.centerX;\n const renderCenterY = obb.centerY;\n const renderMinX = renderCenterX - renderWidth / 2;\n const renderMinY = renderCenterY - renderHeight / 2;\n const renderMaxX = renderCenterX + renderWidth / 2;\n const renderMaxY = renderCenterY + renderHeight / 2;\n\n // Calculate rotation handle position (below bottom center, rotated with the group)\n let rotationHandleX = renderCenterX;\n let rotationHandleY = renderMaxY + ROTATION_HANDLE_DISTANCE;\n\n if (groupRotation !== 0) {\n const rotationRad = (-groupRotation * Math.PI) / 180;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n const dx = rotationHandleX - renderCenterX;\n const dy = rotationHandleY - renderCenterY;\n rotationHandleX = renderCenterX + (dx * cos - dy * sin);\n rotationHandleY = renderCenterY + (dx * sin + dy * cos);\n }\n\n // Prepare group bounds for hit testing\n const groupBounds: MultiSelectionGroupBounds = {\n minX: renderMinX,\n minY: renderMinY,\n maxX: renderMaxX,\n maxY: renderMaxY,\n centerX: renderCenterX,\n centerY: renderCenterY,\n rotationHandleX,\n rotationHandleY,\n rotation: groupRotation,\n };\n\n // Render dotted border around group bounds (with rotation)\n ctx.save();\n ctx.translate(renderCenterX, renderCenterY);\n ctx.rotate((-groupRotation * Math.PI) / 180);\n ctx.translate(-renderCenterX, -renderCenterY);\n\n ctx.strokeStyle = getThemeAccentColor();\n ctx.lineWidth = getLineWidth(zoom);\n ctx.setLineDash(getDashPattern(zoom));\n ctx.strokeRect(renderMinX, renderMinY, renderWidth, renderHeight);\n ctx.restore();\n\n // Don't render handles during marquee selection (only show after mouse release)\n if (!isMarqueeSelecting) {\n renderMultiSelectionHandles(ctx, {\n renderMinX,\n renderMinY,\n renderMaxX,\n renderMaxY,\n renderCenterX,\n renderCenterY,\n renderWidth,\n renderHeight,\n groupRotation,\n rotationHandleX,\n rotationHandleY,\n stateMachine,\n zoom,\n });\n }\n\n return { updatedOBB: obb, groupBounds };\n}\n\ninterface RenderHandlesParams {\n renderMinX: number;\n renderMinY: number;\n renderMaxX: number;\n renderMaxY: number;\n renderCenterX: number;\n renderCenterY: number;\n renderWidth: number;\n renderHeight: number;\n groupRotation: number;\n rotationHandleX: number;\n rotationHandleY: number;\n stateMachine: InteractionStateMachine;\n zoom: number;\n}\n\n/**\n * Renders corner resize handles and rotation handle for multi-selection\n */\nfunction renderMultiSelectionHandles(ctx: CanvasRenderingContext2D, params: RenderHandlesParams): void {\n const {\n renderMinX,\n renderMinY,\n renderMaxX,\n renderMaxY,\n renderCenterX,\n renderCenterY,\n renderWidth,\n renderHeight,\n groupRotation,\n rotationHandleX,\n rotationHandleY,\n stateMachine,\n zoom,\n } = params;\n\n // Render 4 white circle corner handles (with rotation)\n const handleRadius = getHandleRadius(zoom);\n const corners = [\n { x: renderMinX, y: renderMinY, anchor: 'top-left' },\n { x: renderMaxX, y: renderMinY, anchor: 'top-right' },\n { x: renderMinX, y: renderMaxY, anchor: 'bottom-left' },\n { x: renderMaxX, y: renderMaxY, anchor: 'bottom-right' },\n ];\n\n const isResizingMultiSelection = stateMachine.getMode() === 'resize';\n const activeHandle = isResizingMultiSelection ? (stateMachine as AugmentedStateMachine)._multiSelectionResizeContext?.anchor : null;\n\n corners.forEach((corner) => {\n // During resize, only show the active handle\n if (isResizingMultiSelection && activeHandle && corner.anchor !== activeHandle) {\n return;\n }\n\n // Rotate corner position around center\n let cornerX = corner.x;\n let cornerY = corner.y;\n\n if (groupRotation !== 0) {\n const rotationRad = (-groupRotation * Math.PI) / 180;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n const dx = corner.x - renderCenterX;\n const dy = corner.y - renderCenterY;\n cornerX = renderCenterX + (dx * cos - dy * sin);\n cornerY = renderCenterY + (dx * sin + dy * cos);\n }\n\n ctx.save();\n\n ctx.beginPath();\n ctx.arc(cornerX, cornerY, handleRadius, 0, Math.PI * 2);\n ctx.fillStyle = '#fff';\n ctx.fill();\n ctx.strokeStyle = getThemeAccentColor();\n ctx.lineWidth = isResizingMultiSelection && activeHandle && corner.anchor === activeHandle\n ? getLineWidth(zoom) * 1.25\n : getLineWidth(zoom);\n ctx.stroke();\n ctx.restore();\n });\n\n // Render rotation handle (same style as regular elements)\n // Hide during resize\n if (!isResizingMultiSelection) {\n renderRotationHandle(ctx, rotationHandleX, rotationHandleY, zoom, getThemeAccentColor());\n }\n\n // Render dimension label during resize\n if (stateMachine.getMode() === 'resize') {\n renderDimensionLabel(ctx, renderCenterX, renderMaxY, renderWidth, renderHeight);\n }\n}\n\n/**\n * Renders dimension label showing width × height\n */\nfunction renderDimensionLabel(\n ctx: CanvasRenderingContext2D,\n centerX: number,\n maxY: number,\n width: number,\n height: number\n): void {\n const infoText = `${Math.round(width)} × ${Math.round(height)}`;\n\n // Position below the bottom edge\n const tooltipX = centerX;\n const tooltipY = maxY + 30;\n\n ctx.save();\n\n // Set smaller font\n ctx.font = '10px system-ui, -apple-system, sans-serif';\n const textMetrics = ctx.measureText(infoText);\n const tooltipWidth = textMetrics.width + 12;\n const tooltipHeight = 18;\n\n // Shadow for depth\n ctx.shadowColor = 'rgba(0,0,0,0.2)';\n ctx.shadowBlur = 4;\n ctx.shadowOffsetY = 1;\n\n // Background\n ctx.fillStyle = '#333';\n ctx.beginPath();\n ctx.roundRect(tooltipX - tooltipWidth / 2, tooltipY - tooltipHeight / 2, tooltipWidth, tooltipHeight, 3);\n ctx.fill();\n\n // Reset shadow\n ctx.shadowColor = 'transparent';\n ctx.shadowBlur = 0;\n ctx.shadowOffsetY = 0;\n\n // Text\n ctx.fillStyle = '#fff';\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(infoText, tooltipX, tooltipY + 0.5);\n\n ctx.restore();\n}\n","/**\n * Marquee Selection Renderer\n * Handles rendering of the marquee selection box during drag\n */\n\nimport { getThemeAccentColor, getThemeAccentColorWithAlpha } from '../../../constants.js';\nimport { getLineWidth } from './renderingConstants.js';\n\nexport interface RenderMarqueeParams {\n ctx: CanvasRenderingContext2D;\n marqueeStart: { x: number; y: number };\n marqueeEnd: { x: number; y: number };\n zoom?: number;\n}\n\n/**\n * Renders a marquee selection box with stroke and semi-transparent fill\n * @param params.zoom - Zoom factor (default 1.0) - line width is adjusted to maintain visual consistency\n */\nexport function renderMarqueeSelection(params: RenderMarqueeParams): void {\n const { ctx, marqueeStart, marqueeEnd, zoom = 1.0 } = params;\n\n const minX = Math.min(marqueeStart.x, marqueeEnd.x);\n const maxX = Math.max(marqueeStart.x, marqueeEnd.x);\n const minY = Math.min(marqueeStart.y, marqueeEnd.y);\n const maxY = Math.max(marqueeStart.y, marqueeEnd.y);\n\n ctx.save();\n ctx.strokeStyle = getThemeAccentColor();\n ctx.lineWidth = getLineWidth(zoom);\n ctx.strokeRect(minX, minY, maxX - minX, maxY - minY);\n\n // Semi-transparent fill\n ctx.fillStyle = getThemeAccentColorWithAlpha(0.1);\n ctx.fillRect(minX, minY, maxX - minX, maxY - minY);\n ctx.restore();\n}\n","/**\n * Hover Effect Renderer\n * Handles rendering of hover effects for group siblings\n */\n\nimport { TextElement } from '../../../core/TextElement.js';\nimport { ImageElement } from '../../../core/ImageElement.js';\nimport { getThemeHoverBorderColor } from '../../../constants.js';\nimport { getLineWidth } from './renderingConstants.js';\n\nexport interface RenderGroupSiblingHoverParams {\n ctx: CanvasRenderingContext2D;\n hoveredChild: TextElement | ImageElement;\n zoom?: number;\n}\n\n/**\n * Renders hover effect outline for a group sibling element\n * @param params.zoom - Zoom factor (default 1.0) - line width is adjusted to maintain visual consistency\n */\nexport function renderGroupSiblingHover(params: RenderGroupSiblingHoverParams): void {\n const { ctx, hoveredChild, zoom = 1.0 } = params;\n const bbox = hoveredChild.getVisualBoundingBox();\n\n ctx.save();\n ctx.translate(bbox.x + bbox.width / 2, bbox.y + bbox.height / 2);\n ctx.rotate((-hoveredChild.rotation * Math.PI) / 180);\n ctx.translate(-(bbox.x + bbox.width / 2), -(bbox.y + bbox.height / 2));\n\n // Draw solid outline with hover border color\n ctx.strokeStyle = getThemeHoverBorderColor();\n ctx.lineWidth = getLineWidth(zoom);\n ctx.setLineDash([]); // Solid line\n ctx.strokeRect(bbox.x, bbox.y, bbox.width, bbox.height);\n\n ctx.restore();\n}\n","/**\n * PerformanceMonitor - Dev-mode performance metrics for the canvas render loop.\n *\n * Singleton class that tracks frame times, element counts, cache hit rates,\n * and export durations. Exposed on `window.__CANVAS_PERF__` in dev mode\n * so developers can inspect real-time metrics from the browser console.\n *\n * @example\n * ```ts\n * // Record a frame render time\n * const start = performance.now();\n * renderFrame();\n * PerformanceMonitor.getInstance().recordFrameTime(performance.now() - start);\n *\n * // Read metrics from console in dev mode\n * window.__CANVAS_PERF__.getMetrics();\n * ```\n */\n\nexport interface CanvasPerformanceMetrics {\n /** Average frame render time in ms (rolling window of last 60 frames) */\n frameTime: number;\n /** Total frames rendered since last reset */\n frameCount: number;\n /** Current element count on the active artboard */\n elementCount: number;\n /** Bitmap cache hit rate as a ratio 0-1 */\n cacheHitRate: number;\n /** Duration of the last export operation in ms */\n lastExportDuration: number;\n}\n\n/** Size of the rolling window for frame time averaging */\nconst ROLLING_WINDOW_SIZE = 60;\n\nexport class PerformanceMonitor {\n private static instance: PerformanceMonitor;\n\n private metrics: CanvasPerformanceMetrics;\n /** Rolling window of the last N frame times (ms) */\n private frameTimes: number[];\n\n private constructor() {\n this.frameTimes = [];\n this.metrics = {\n frameTime: 0,\n frameCount: 0,\n elementCount: 0,\n cacheHitRate: 0,\n lastExportDuration: 0,\n };\n }\n\n static getInstance(): PerformanceMonitor {\n if (!PerformanceMonitor.instance) {\n PerformanceMonitor.instance = new PerformanceMonitor();\n }\n return PerformanceMonitor.instance;\n }\n\n /**\n * Record a single frame's render duration. Maintains a rolling window\n * of the last 60 values and recomputes the average.\n */\n recordFrameTime(ms: number): void {\n this.frameTimes.push(ms);\n if (this.frameTimes.length > ROLLING_WINDOW_SIZE) {\n this.frameTimes.shift();\n }\n this.metrics.frameCount++;\n this.metrics.frameTime = this._average(this.frameTimes);\n }\n\n /** Record the duration of an export operation. */\n recordExportDuration(ms: number): void {\n this.metrics.lastExportDuration = ms;\n }\n\n /** Update the current element count (call once per frame or on change). */\n setElementCount(count: number): void {\n this.metrics.elementCount = count;\n }\n\n /** Update the bitmap cache hit rate (0-1). */\n setCacheHitRate(rate: number): void {\n this.metrics.cacheHitRate = rate;\n }\n\n /** Return a readonly snapshot of the current metrics. */\n getMetrics(): Readonly<CanvasPerformanceMetrics> {\n return { ...this.metrics };\n }\n\n /** Reset all metrics and the rolling window. */\n reset(): void {\n this.frameTimes = [];\n this.metrics = {\n frameTime: 0,\n frameCount: 0,\n elementCount: 0,\n cacheHitRate: 0,\n lastExportDuration: 0,\n };\n }\n\n // ── internal helpers ──────────────────────────────────────────────────\n\n private _average(values: number[]): number {\n if (values.length === 0) return 0;\n let sum = 0;\n for (let i = 0; i < values.length; i++) {\n sum += values[i];\n }\n return sum / values.length;\n }\n}\n\n// Expose globally in dev mode so developers can inspect from the console:\n// window.__CANVAS_PERF__.getMetrics()\nif (typeof window !== 'undefined' && import.meta.env?.DEV) {\n (window as any).__CANVAS_PERF__ = PerformanceMonitor.getInstance();\n}\n","import type { ClipShape } from '../../../types/index.js';\nimport type {\n OverlayPiece,\n PieceGuide,\n} from '../../VisualGuideOverlay.js';\n\n/**\n * Build the artboard's `composite-path` clip shape from a piece-guide\n * bundle. Returns `undefined` when the bundle isn't fully path-kind —\n * the renderer falls back to its rectangular / rounded-rect path then.\n *\n * - When `focusedPieceId` is set and matches one of the pieces, the clip\n * narrows to just that piece's silhouette. Other pieces are excluded\n * so element rendering, the Pass-1 overflow-dim cutout, and Pass-2\n * per-element clipping all align on the same shape — only the focused\n * piece gets artwork painted into it. (Companion behaviour in\n * `PieceGuideRenderer` skips non-focused pieces' chrome too.)\n * - When `focusedPieceId` is null, the clip is the union of every\n * piece (spread mode).\n *\n * Pure function — extracted from `useCanvasRenderLoop.ts` to make the\n * filtering + rotation/swap math directly unit-testable.\n */\nexport function buildSpreadClipShape(\n pieces: OverlayPiece[],\n guides: Record<string, PieceGuide> | undefined,\n focusedPieceId: string | null | undefined,\n): ClipShape | undefined {\n if (pieces.length === 0) return undefined;\n const allPathKind = pieces.every((p) => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const b = (guides?.[p.id] as any)?.boundary;\n return (\n b &&\n typeof b === 'object' &&\n b.kind === 'path' &&\n typeof b.d === 'string'\n );\n });\n if (!allPathKind) return undefined;\n\n const focused = focusedPieceId\n ? pieces.filter((p) => p.id === focusedPieceId)\n : pieces;\n // Defensive: an unknown focusedPieceId would otherwise produce a\n // zero-piece clip and hide the entire canvas. Fall back to the full\n // spread so the user still sees something.\n const composite = focused.length > 0 ? focused : pieces;\n\n return {\n type: 'composite-path',\n pieces: composite.map((p) => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const boundary = (guides?.[p.id] as any)?.boundary;\n const rot = ((p.rotation ?? 0) % 360) as 0 | 90 | 180 | 270;\n const swap = rot === 90 || rot === 270;\n // p.width / p.height are POST-rotation visual extents. The path\n // itself is in the UN-rotated frame, so reverse the swap to\n // recover the path's native bbox (used by the renderer to rotate\n // around the piece center).\n return {\n d: boundary.d as string,\n x: p.x,\n y: p.y,\n rotation: rot,\n baseWidth: swap ? p.height : p.width,\n baseHeight: swap ? p.width : p.height,\n };\n }),\n };\n}\n","/**\n * useCanvasRenderLoop - Manages the main canvas render loop\n *\n * Extracted from CanvasEditor.tsx to isolate all rendering logic including:\n * - Canvas clearing and DPR handling\n * - Zoom and padding transforms\n * - Artboard background rendering\n * - Element rendering via CanvasRenderer\n * - Multi-selection indicators\n * - Marquee selection rendering\n * - Hover effects\n * - Pen tool preview\n * - Cursor animation during text editing\n */\n\nimport React, { useEffect, useRef } from 'react';\nimport { CanvasRenderer } from '../../../core/CanvasRenderer.js';\nimport { renderPieceGuides } from '../../../rendering/PieceGuideRenderer.js';\nimport type { PieceGuidesConfig } from '../../embed/SnowconeCanvas.js';\nimport type { ArtboardRenderer } from '../../../core/ArtboardRenderer.js';\nimport type { ArtboardElement } from '../../../core/ArtboardElement.js';\nimport type { ArtboardManager } from '../../../core/ArtboardManager.js';\nimport type { TransformHandles } from '../../../core/TransformHandles.js';\nimport type { InteractionStateMachine } from '../../../core/InteractionStateMachine.js';\nimport type { BaseElement } from '../../../core/BaseElement.js';\nimport { TextElement } from '../../../core/TextElement.js';\nimport type { ImageElement } from '../../../core/ImageElement.js';\nimport { GroupElement } from '../../../core/GroupElement.js';\nimport type { ShapeElement } from '../../../core/ShapeElement.js';\nimport { PathElement } from '../../../core/PathElement.js';\nimport type { SnapGuide, SpacingIndicator, RichText, ClipShape } from '../../../types/index.js';\nimport type { LocalHoverState } from '../types/index.js';\nimport {\n renderMultiSelectionIndicators,\n renderMarqueeSelection,\n renderGroupSiblingHover,\n} from '../renderers/index.js';\nimport type { MultiSelectionOBB, MultiSelectionGroupBounds } from '../renderers/index.js';\nimport { PerformanceMonitor } from '../../../utils/PerformanceMonitor.js';\nimport { buildSpreadClipShape } from './buildSpreadClipShape.js';\n\ntype CanvasElement = TextElement | ImageElement | GroupElement | ShapeElement | PathElement;\ntype ChildElement = TextElement | ImageElement | ShapeElement | PathElement | GroupElement;\n\n/** Cached clone produced during text editing to avoid cloning every frame */\ninterface EditCache {\n elementId: string;\n childId: string | null;\n editRichText: RichText | null;\n clonedElement: CanvasElement;\n}\n\n/** PenTool interface - only the methods used by the render loop */\ninterface PenToolLike {\n isActive(): boolean;\n renderPreview(ctx: CanvasRenderingContext2D, element: PathElement): void;\n}\n\nexport interface UseCanvasRenderLoopParams {\n canvasRef: React.RefObject<HTMLCanvasElement | null>;\n elements: CanvasElement[];\n elementsByArtboard: Map<string | null, CanvasElement[]>;\n artboards: ArtboardElement[];\n artboardManager: ArtboardManager;\n artboardRenderer: ArtboardRenderer;\n selectedId: string | null;\n selectedElement: CanvasElement | undefined;\n activeChildElement: ChildElement | null;\n editingChildIdRef: React.MutableRefObject<string | null>;\n transformHandles: TransformHandles;\n stateMachine: InteractionStateMachine;\n\n // Canvas dimensions and view\n canvasSize: { width: number; height: number };\n paddedCanvasWidth: number;\n paddedCanvasHeight: number;\n paddingOffsetX: number;\n paddingOffsetY: number;\n zoom: number;\n zoomInvariantBorderRadius: number;\n effectiveViewPadding: number;\n showRotationHandle: boolean;\n\n // Interaction state\n isRotating: boolean;\n currentRotation: number;\n hoverState: LocalHoverState;\n isEditing: boolean;\n editText: string;\n editRichText: RichText | null;\n cursorPosition: number;\n selectionStart: number;\n selectionEnd: number;\n cursorOpacityRef: React.MutableRefObject<number>;\n cursorStartTimeRef: React.MutableRefObject<number>;\n isTouchDevice: boolean;\n selectionHandlePositionsRef: React.MutableRefObject<{\n start: { center: { x: number; y: number }; textEdge: { x: number; y: number } } | null;\n end: { center: { x: number; y: number }; textEdge: { x: number; y: number } } | null;\n }>;\n\n // Multi-click debounce refs\n multiClickSelectionRef: React.MutableRefObject<{ start: number; end: number; cursor: number } | null>;\n lastMultiClickTimeRef: React.MutableRefObject<number>;\n MULTI_CLICK_DEBOUNCE: number;\n\n // Snap and spacing\n snapGuides: SnapGuide[];\n spacingIndicators: SpacingIndicator[];\n isAltKeyPressed: boolean;\n\n // Selection state\n multiSelection: string[];\n multiSelectionOBBRef: React.MutableRefObject<MultiSelectionOBB | null>;\n multiSelectionGroupBoundsRef: React.MutableRefObject<MultiSelectionGroupBounds | null>;\n isMarqueeSelecting: boolean;\n marqueeStart: { x: number; y: number };\n marqueeEnd: { x: number; y: number };\n marqueePreviewSelection: string[];\n\n // Pen tool\n penTool: PenToolLike;\n\n // Handles version (for triggering re-renders)\n handlesVersion: number;\n\n // Hide handles flag\n hideHandles: boolean;\n\n // Theme\n resolvedTheme: string;\n\n // Cursor animation frame (triggers re-render)\n cursorAnimationFrame: number;\n\n // Render version (for forceRender)\n renderVersion: number;\n\n /** ADR-0054: piece-guide overlays drawn after elements, before\n * selection UI. Coordinates are in artboard-local px. */\n pieceGuides?: PieceGuidesConfig;\n\n /** ADR-0060: id of the currently focused piece (or null for spread\n * / single-piece). Forwarded to `renderPieceGuides` so non-focused\n * pieces' guide chrome dims to keep visual focus on the active\n * piece without losing the structural context of neighbours. */\n focusedPieceId?: string | null;\n\n /** ADR-0060: animated rect of the focused piece (in artboard\n * coords). Drives the post-pass that dims artwork outside the\n * focused piece. Decoupled from `focusedPieceId` so the parent\n * can interpolate the rect during piece switches without\n * changing which piece is \"logically\" focused. Falls back to\n * the static rect of `focusedPieceId` when not provided. */\n focusedPieceRect?: { x: number; y: number; width: number; height: number } | null;\n}\n\n/**\n * Hook that manages the main canvas render loop.\n *\n * Uses a dirty flag + requestAnimationFrame pattern to prevent double-renders\n * when multiple state updates happen in the same frame. Dependencies mark the\n * render as dirty, and a single RAF loop performs the actual render only when needed.\n */\nexport function useCanvasRenderLoop(params: UseCanvasRenderLoopParams): void {\n // Keep a ref to latest params so the RAF loop always reads current values\n const paramsRef = useRef(params);\n paramsRef.current = params;\n\n // Dirty flag: when true, the next RAF iteration will render\n const dirtyRef = useRef(true);\n\n // Cache for cloned elements during text editing — rebuilt only when editRichText changes\n const editCacheRef = useRef<EditCache | null>(null);\n\n // Cache the canvas background color so getComputedStyle is not called every RAF frame.\n // Only re-read when the theme changes (resolvedTheme dependency).\n const canvasBgColorRef = useRef('#0a0a0a');\n useEffect(() => {\n const targetElement = paramsRef.current.canvasRef.current ?? document.documentElement;\n const color = getComputedStyle(targetElement).getPropertyValue('--color-canvas-bg').trim();\n canvasBgColorRef.current = color || '#0a0a0a';\n }, [params.resolvedTheme]);\n\n // Mark dirty when core visual content changes (elements, artboard, canvas view).\n // This effect is NOT triggered by interaction-only state changes (hover, drag handles, etc.)\n // which means React skips re-running this closure during pointer-move events.\n useEffect(() => {\n dirtyRef.current = true;\n }, [\n params.elementsByArtboard,\n params.artboards,\n params.selectedId,\n params.selectedElement,\n params.activeChildElement,\n params.canvasSize,\n params.zoom,\n params.renderVersion,\n params.resolvedTheme,\n params.pieceGuides,\n ]);\n\n // Mark dirty when interaction-visual state changes (hover, selection, handles, text editing).\n // Separated from core deps so that canvas-content changes don't unnecessarily\n // re-run interaction closures, and vice-versa.\n useEffect(() => {\n dirtyRef.current = true;\n }, [\n params.transformHandles,\n params.currentRotation,\n params.isRotating,\n params.hoverState,\n params.isEditing,\n params.editText,\n params.editRichText,\n params.cursorPosition,\n params.selectionStart,\n params.selectionEnd,\n params.stateMachine,\n params.snapGuides,\n params.spacingIndicators,\n params.handlesVersion,\n params.multiSelection,\n params.isMarqueeSelecting,\n params.marqueeStart,\n params.marqueeEnd,\n params.marqueePreviewSelection,\n params.isAltKeyPressed,\n params.penTool,\n params.cursorAnimationFrame,\n ]);\n\n // Single RAF loop that only renders when dirty\n useEffect(() => {\n let frameId: number;\n\n const HOVER_FADE_DURATION = 150; // Must match HoverRenderer.ts FADE_DURATION\n\n const renderLoop = () => {\n // Keep dirty while hover fade animation is running so the RAF loop\n // drives the animation instead of a setInterval forcing React re-renders.\n const { hoverState } = paramsRef.current;\n if (hoverState.startTime !== null && hoverState.type !== null) {\n const elapsed = performance.now() - hoverState.startTime;\n if (elapsed < HOVER_FADE_DURATION) {\n dirtyRef.current = true;\n }\n }\n\n if (dirtyRef.current) {\n dirtyRef.current = false;\n const t0 = performance.now();\n renderFrame(paramsRef.current, canvasBgColorRef, editCacheRef);\n PerformanceMonitor.getInstance().recordFrameTime(performance.now() - t0);\n }\n frameId = requestAnimationFrame(renderLoop);\n };\n\n frameId = requestAnimationFrame(renderLoop);\n return () => cancelAnimationFrame(frameId);\n }, []);\n}\n\n/**\n * Performs a single render frame. Extracted from the original useEffect body\n * so it can be called from the RAF loop.\n */\nfunction renderFrame(\n params: UseCanvasRenderLoopParams,\n canvasBgColorRef: React.MutableRefObject<string>,\n editCacheRef: React.MutableRefObject<EditCache | null>\n): void {\n const {\n canvasRef,\n elements,\n elementsByArtboard,\n artboards,\n artboardManager,\n artboardRenderer,\n selectedId,\n selectedElement,\n activeChildElement,\n editingChildIdRef,\n transformHandles,\n stateMachine,\n paddedCanvasWidth,\n paddedCanvasHeight,\n paddingOffsetX,\n paddingOffsetY,\n zoom,\n zoomInvariantBorderRadius,\n effectiveViewPadding,\n showRotationHandle,\n isRotating,\n currentRotation,\n hoverState,\n isEditing,\n editText,\n editRichText,\n cursorPosition,\n selectionStart,\n selectionEnd,\n cursorOpacityRef,\n cursorStartTimeRef,\n isTouchDevice,\n selectionHandlePositionsRef,\n multiClickSelectionRef,\n lastMultiClickTimeRef,\n MULTI_CLICK_DEBOUNCE,\n snapGuides,\n spacingIndicators,\n isAltKeyPressed,\n multiSelection,\n multiSelectionOBBRef,\n multiSelectionGroupBoundsRef,\n isMarqueeSelecting,\n marqueeStart,\n marqueeEnd,\n marqueePreviewSelection,\n penTool,\n hideHandles,\n pieceGuides,\n } = params;\n\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n // Calculate cursor opacity for text editing cursor animation\n // This runs in the main render loop to avoid separate RAF + setInterval\n if (isEditing) {\n const cycleDuration = 1060;\n const easeInOutCubic = (t: number): number => {\n return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;\n };\n\n const elapsed = performance.now() - cursorStartTimeRef.current;\n const progress = (elapsed % cycleDuration) / cycleDuration;\n\n const opacity =\n progress < 0.5\n ? 1 - easeInOutCubic(progress * 2)\n : easeInOutCubic((progress - 0.5) * 2);\n\n cursorOpacityRef.current = opacity;\n } else {\n cursorOpacityRef.current = 1;\n }\n\n // Support high DPI displays\n const dpr = window.devicePixelRatio || 1;\n\n // Set actual size in memory (display size x DPR, accounting for zoom and padding)\n const displayWidth = paddedCanvasWidth * zoom;\n const displayHeight = paddedCanvasHeight * zoom;\n\n // Skip rendering when dimensions are zero (e.g. before zoom calculation completes).\n // Without this guard, knockout/compositing renderers create 0-size OffscreenCanvas\n // which throws and kills the RAF loop permanently.\n if (displayWidth <= 0 || displayHeight <= 0) return;\n\n // Cap the canvas backing buffer to at most `MAX_BUFFER_DIM` on its\n // longest side. Without this, user-zoom (up to 8× in editor mode)\n // multiplies the buffer's pixel count by 64×, blowing past 90+\n // megapixels for typical artboards — the canvas re-paints from\n // scratch each dirty frame, and that much pixel work tanks\n // interactivity (especially on mobile Safari, which also has\n // tight memory limits). The cap means past the threshold the CSS\n // box keeps growing (so layout/pan still feels right) while the\n // backing buffer holds at the cap and the browser bilinear-\n // stretches the last buffer-resolution to fill the box. Slightly\n // softer at extreme zooms; smooth interaction always.\n //\n // 4096 is the safe everywhere choice: every WebGL / Canvas2D\n // implementation in the wild supports it, including older iPads.\n const MAX_BUFFER_DIM = 4096;\n const rawBufferWidth = displayWidth * dpr;\n const rawBufferHeight = displayHeight * dpr;\n const longest = Math.max(rawBufferWidth, rawBufferHeight);\n const scaleFactor = longest > MAX_BUFFER_DIM ? MAX_BUFFER_DIM / longest : 1;\n const effectiveDpr = dpr * scaleFactor;\n\n canvas.width = displayWidth * effectiveDpr;\n canvas.height = displayHeight * effectiveDpr;\n\n // IMPORTANT: Reset transform to identity before applying dpr scaling\n ctx.setTransform(1, 0, 0, 1, 0, 0);\n\n // Clear canvas FIRST with no transform (use full buffer size)\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n // Use the cached background color (updated on theme change, never called in-frame)\n const canvasBgColor = canvasBgColorRef.current;\n\n // NOW apply transforms for content rendering. Using `effectiveDpr`\n // (not `dpr`) keeps the world-coord-to-buffer-pixel mapping\n // consistent with the capped canvas dimensions above — the canvas\n // CSS still occupies the full `displayWidth × displayHeight` box\n // and the browser scales our smaller buffer up to fit.\n ctx.scale(effectiveDpr, effectiveDpr);\n\n // Apply zoom transform\n if (zoom !== 1.0) {\n ctx.scale(zoom, zoom);\n }\n\n // Apply padding offset to center artboard within padded canvas\n if (paddingOffsetX !== 0 || paddingOffsetY !== 0) {\n ctx.translate(paddingOffsetX, paddingOffsetY);\n }\n\n // Find the active artboard once and reuse for both background and rendering\n const activeArtboard = artboards.find((a) => a.id === artboardManager.getActiveArtboardId());\n const activeArtboardId = activeArtboard?.id ?? null;\n\n // ADR-0054 rendering contract — IMPORTANT:\n // Do NOT derive a clip path from `pieceGuides.boundary` here when the\n // boundary is a rounded-rect — the gradient bleed-ring vignette\n // assumes a continuous underlying canvas to feather into; clipping at\n // the boundary would defeat the soft fade. See the \"Rendering\n // contract\" block at the top of PieceGuideRenderer.ts.\n //\n // BUT for **path-kind** boundaries (multi-piece spreads — AFOOES\n // hoodie etc.), the bleed-ring pathway is already skipped server-side\n // (the API only injects the vignette default when boundary is rect-\n // shaped). With no vignette to preserve, we *do* want a hard clip:\n // without it, artwork bleeds into corner wedges of each piece's bbox\n // and into the gaps between pieces (the \"looks like a rectangle\" bug).\n // Build a composite clipShape from the union of every piece's\n // path-kind boundary, translated/rotated into the artboard frame.\n const effectiveClipShape: ClipShape | undefined =\n pieceGuides && activeArtboard\n ? buildSpreadClipShape(\n pieceGuides.pieces,\n pieceGuides.guides,\n params.focusedPieceId ?? null,\n )\n : undefined;\n // Prefer the piece-guide's own outer corner radius if authored\n // (either explicit `outerRadius` on the guide, or inferred from a\n // rounded-rect `boundary`). The radius drives Pass-2's rounded\n // element clip in CanvasRenderer (so the artwork's natural\n // rectangle is shaped like the product's outer edge — phone case,\n // rounded card, etc.). Pass-1 overflow-dim and beyond-rect\n // selection chrome use the same radius to determine which corner\n // wedges to paint into during interaction.\n // For piece-guide products the artboard's rounded radius should\n // match the *product*, not the consumer's stylistic\n // `artboardBorderRadius` prop. A rectangular notebook spread should\n // render with sharp corners (matching its spec sheet), and a phone\n // case should use its piece's `outerRadius`. Default to 0 here for\n // piece-guide products; the per-piece block below bumps it back up\n // when the piece itself encodes a rounded outer shape. Non-piece-\n // guide products still inherit the legacy `zoomInvariantBorderRadius`\n // for the soft-corner editor look.\n let effectiveBorderRadius = pieceGuides ? 0 : zoomInvariantBorderRadius;\n if (\n pieceGuides &&\n pieceGuides.pieces.length === 1 &&\n activeArtboard\n ) {\n const firstPieceId = pieceGuides.pieces[0].id;\n const guide = pieceGuides.guides?.[firstPieceId];\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const guideAny = guide as any;\n const explicit = guideAny?.outerRadius;\n const boundary = guideAny?.boundary;\n // Fall back to the rounded-rect boundary's rx when no explicit\n // outerRadius is set — matches typical phone-case authoring\n // where the boundary already encodes the product's outer curve.\n const inferred =\n boundary &&\n typeof boundary === 'object' &&\n boundary.kind === 'roundedRect'\n ? (boundary.rx ?? boundary.ry)\n : undefined;\n const candidate =\n typeof explicit === 'number' && explicit > 0\n ? explicit\n : typeof inferred === 'number' && inferred > 0\n ? inferred\n : 0;\n if (candidate > 0) {\n effectiveBorderRadius = candidate;\n }\n }\n\n // Artboard bg fill — full artboard rect (rounded at the legacy\n // CSS-px border radius). Vignette is added on top by the overlay.\n //\n // Skipped entirely when piece guides are present: those products\n // (phone cases, anything with a non-rectangular outer shape) need\n // the canvas to be a true knockout so the page chrome / product\n // mockup behind shows through everywhere the artwork doesn't cover.\n // Painting `--color-canvas-bg` here was leaking a white frame\n // between the artwork and the bleed ring whenever the artwork\n // didn't fill the artboard (small images, mid-resize, mid-drag),\n // and it was the same opaque fill that the rounded-corner knockout\n // had to erase via a `destination-out` final crop. Removing it\n // makes both situations Just Work — alpha goes straight to the\n // page below.\n if (activeArtboard && !pieceGuides) {\n ctx.fillStyle = canvasBgColor;\n if (\n effectiveClipShape &&\n typeof effectiveClipShape === 'object' &&\n effectiveClipShape.type === 'path'\n ) {\n try {\n const p2d = new Path2D(effectiveClipShape.d);\n ctx.save();\n ctx.translate(activeArtboard.x, activeArtboard.y);\n ctx.fill(p2d);\n ctx.restore();\n } catch {\n ctx.fillRect(\n activeArtboard.x,\n activeArtboard.y,\n activeArtboard.width,\n activeArtboard.height,\n );\n }\n } else if (effectiveBorderRadius > 0) {\n ctx.beginPath();\n ctx.roundRect(\n activeArtboard.x,\n activeArtboard.y,\n activeArtboard.width,\n activeArtboard.height,\n effectiveBorderRadius\n );\n ctx.fill();\n } else {\n ctx.fillRect(\n activeArtboard.x,\n activeArtboard.y,\n activeArtboard.width,\n activeArtboard.height\n );\n }\n }\n\n // Render only the active artboard.\n //\n // Skipped entirely for piece-guide products. ArtboardRenderer paints\n // `artboard.backgroundColor` (which defaults to '#ffffff' in\n // ArtboardElement) across the *sharp* artboard rect with no\n // outerRadius awareness — that's the white frame that was bleeding\n // through the rounded outer corners and the bleed-ring band on\n // phone cases. The piece-guide overlay renders its own boundary,\n // safe-area, and label chrome from the authored layout JSON, so\n // dropping the ArtboardRenderer chrome here also avoids border/\n // label duplication. Plain rectangular products keep the legacy\n // ArtboardRenderer pass for the editor's design surface.\n if (activeArtboard && !pieceGuides) {\n artboardRenderer.render(ctx, activeArtboard, true);\n }\n\n // Render all layers using CanvasRenderer\n let elementToRender = activeChildElement || selectedElement;\n\n // Get cached elements for active artboard\n let elementsForRender = elementsByArtboard.get(activeArtboardId) || [];\n\n // During drag, use cached element if available for immediate visual feedback\n if (stateMachine.getMode() === 'drag' && selectedElement) {\n const dragContext = stateMachine.getContext() as ReturnType<typeof stateMachine.getContext> & {\n _cachedElement?: Record<string, CanvasElement>;\n };\n const cachedElements = dragContext._cachedElement;\n if (cachedElements && cachedElements[selectedElement.id]) {\n elementsForRender = elementsForRender.map((e) =>\n e.id === selectedElement.id ? cachedElements[selectedElement.id] : e\n );\n if (!activeChildElement) {\n elementToRender = cachedElements[selectedElement.id];\n }\n }\n }\n\n // During edit mode, update the element being edited with current edit text.\n // Cache the cloned element and only re-clone when editRichText reference changes —\n // the cursor-blink RAF ticks (60fps) no longer trigger an expensive clone each frame.\n if (isEditing && selectedElement) {\n const childId = editingChildIdRef.current ?? null;\n const cache = editCacheRef.current;\n const cacheHit =\n cache !== null &&\n cache.elementId === selectedElement.id &&\n cache.childId === childId &&\n cache.editRichText === editRichText;\n\n if (!cacheHit) {\n // (Re-)build the clone only when something meaningful changed\n let clonedElement: CanvasElement | null = null;\n\n if (childId && selectedElement instanceof GroupElement) {\n const sourceGroup = elementsForRender.find(\n (e) => e.id === selectedElement.id && e instanceof GroupElement\n ) as GroupElement | undefined;\n if (sourceGroup) {\n const clonedGroup = sourceGroup.clone();\n clonedGroup.children = clonedGroup.children.map((child) => {\n if (child.id === childId && editRichText && child instanceof TextElement) {\n const clonedChild = child.clone();\n clonedChild.setRichText(editRichText);\n return clonedChild;\n }\n return child;\n });\n clonedElement = clonedGroup;\n }\n } else if (editRichText && selectedElement instanceof TextElement) {\n const cloned = selectedElement.clone();\n cloned.setRichText(editRichText);\n clonedElement = cloned;\n }\n\n if (clonedElement) {\n editCacheRef.current = { elementId: selectedElement.id, childId, editRichText, clonedElement };\n } else {\n editCacheRef.current = null;\n }\n }\n\n if (editCacheRef.current) {\n const { clonedElement } = editCacheRef.current;\n elementsForRender = elementsForRender.map((e) =>\n e.id === selectedElement.id ? clonedElement : e\n );\n }\n } else {\n // Not editing — clear the cache so a fresh clone is built next time editing starts\n editCacheRef.current = null;\n }\n\n // Hide handles when pen tool is active\n const shouldHideHandles = hideHandles || (penTool.isActive() && selectedElement instanceof PathElement) || isEditing;\n\n CanvasRenderer.render({\n ctx,\n elements: elementsForRender,\n selectedId,\n selectedElement: elementToRender || null,\n transformHandles,\n currentRotation,\n isRotating,\n hoverState: hoverState as { type: 'element' | 'resize-handle' | 'rotation-handle' | null; data?: BaseElement | null },\n isEditing,\n stateMachine,\n snapGuides: snapGuides,\n spacingIndicators: spacingIndicators,\n hideHandles: shouldHideHandles,\n isAltKeyPressed,\n artboardBackgroundColor: activeArtboard?.backgroundColor,\n distressTexture: activeArtboard?.distressTexture,\n imageMask: activeArtboard?.imageMask,\n artboard: activeArtboard\n ? {\n x: activeArtboard.x,\n y: activeArtboard.y,\n width: activeArtboard.width,\n height: activeArtboard.height,\n borderRadius: effectiveBorderRadius,\n clipShape: effectiveClipShape ?? activeArtboard.clipShape,\n }\n : undefined,\n zoom,\n paddingOffset: { x: paddingOffsetX, y: paddingOffsetY },\n showOverflowDimmed: effectiveViewPadding < 1.0,\n // ADR-0054: overflow-dim renders a 30%+blur version of selected\n // elements outside the artboard's rounded shape. Users rely on\n // this as feedback while a layer is selected — they need to see\n // how far the element extends past the visible piece edge.\n // Hover doesn't trigger this anymore (CanvasRenderer.ts ~line\n // 489 gates on `shouldShowAsSelected` only), so the halo only\n // fires on explicit selection — no on-mouseover noise.\n suppressOverflowDim: false,\n showRotationHandle,\n editModeData: isEditing\n ? (() => {\n const savedSelection = multiClickSelectionRef.current;\n const withinDebounce = Date.now() - lastMultiClickTimeRef.current < MULTI_CLICK_DEBOUNCE;\n const useRefSelection = savedSelection && withinDebounce;\n\n return {\n cursorPosition: useRefSelection ? savedSelection.cursor : cursorPosition,\n selectionStart: useRefSelection ? savedSelection.start : selectionStart,\n selectionEnd: useRefSelection ? savedSelection.end : selectionEnd,\n editText,\n editRichText,\n cursorOpacity: cursorOpacityRef.current,\n isTouchDevice,\n selectionHandlePositionsRef,\n };\n })()\n : undefined,\n });\n\n // ADR-0054: piece-guide overlays. Drawn AFTER the artwork / elements\n // so boundary + zone + safe-area visuals sit on top of the user's\n // design (which is the whole point — they dim the bleed and flag\n // die-cuts), but BEFORE selection/marquee/handle UI so interaction\n // chrome stays legible on top. Coordinates are in artboard-local px;\n // the renderer translates to (activeArtboard.x, activeArtboard.y)\n // internally so multi-artboard layouts compose correctly.\n if (pieceGuides && pieceGuides.pieces.length > 0 && activeArtboard) {\n // Detect whether the user is mid-interaction with an element. We\n // pass this to the piece-guide renderer so die-cut zones in\n // `dieCutMode: 'knockout'` swap their full destination-out for a\n // partial one (alpha 0.7 → 30% of underlying pixels survive)\n // while a user is selecting, hovering, or dragging. That matches\n // the outside overflow halo's 30% multiplier, so the cutout reads\n // as a soft window onto the artwork at the same opacity as the\n // rounded outer corners, instead of a hard knockout that punches\n // through the user's manipulation feedback.\n // Only switch the die-cut to partial knockout when the focused\n // element actually overlaps a die-cut zone. Without this gate,\n // selecting any element (e.g. text far away) would soften every\n // die-cut on the artboard, letting the topmost layers under the\n // cutout bleed through 30% — visually wrong because the user\n // isn't manipulating anything inside that cutout.\n let selectedOverlapsDieCut = false;\n // Only the *selected* element triggers the die-cut blur preview —\n // hover used to fall through here, but that meant the cutout\n // flickered between knockout and blurred-preview every time the\n // cursor moved over a layer near the cutout. The signal is\n // reserved for explicit selection now (matches the overflow-halo\n // gating in CanvasRenderer).\n const interactingElement = (elementToRender as BaseElement | null) ?? null;\n if (interactingElement && pieceGuides) {\n const bbox = interactingElement.getVisualBoundingBox();\n outer: for (const piece of pieceGuides.pieces) {\n const zones = pieceGuides.guides?.[piece.id]?.zones ?? [];\n for (const zone of zones) {\n if (zone.kind !== 'dieCut') continue;\n // Only rect / roundedRect paths expose easy bbox info.\n // `path`-kind die-cuts (raw SVG) skip overlap detection\n // and fall through to the historical \"any selection\n // softens die-cut\" behaviour for them — the geometry to\n // do AABB on a path string is non-trivial and rare.\n const path = zone.path as { kind: string; x?: number; y?: number; width?: number; height?: number };\n if (path.kind !== 'rect' && path.kind !== 'roundedRect') {\n selectedOverlapsDieCut = true;\n break outer;\n }\n const zx = piece.x + (path.x ?? 0);\n const zy = piece.y + (path.y ?? 0);\n const zw = path.width ?? 0;\n const zh = path.height ?? 0;\n if (\n bbox.x < zx + zw &&\n bbox.x + bbox.width > zx &&\n bbox.y < zy + zh &&\n bbox.y + bbox.height > zy\n ) {\n selectedOverlapsDieCut = true;\n break outer;\n }\n }\n }\n }\n const interacting = selectedOverlapsDieCut;\n // Clip the piece-guide pass to the same rounded artboard shape\n // the artwork was clipped to (Pass 2). Without this, the bleed\n // wash + per-piece chrome paint with sharp piece-rect corners\n // and overshoot the artwork's rounded corners — leaving an\n // L-shaped wedge of bleed wash visible outside the rounded\n // artboard edge.\n const guidesNeedRoundedClip =\n effectiveBorderRadius > 0 &&\n activeArtboard !== undefined;\n if (guidesNeedRoundedClip) {\n ctx.save();\n ctx.beginPath();\n ctx.roundRect(\n activeArtboard.x,\n activeArtboard.y,\n activeArtboard.width,\n activeArtboard.height,\n effectiveBorderRadius,\n );\n ctx.clip();\n }\n renderPieceGuides({\n ctx,\n artboardOrigin: { x: activeArtboard.x, y: activeArtboard.y },\n artboardWidth: activeArtboard.width,\n artboardHeight: activeArtboard.height,\n pieces: pieceGuides.pieces,\n pieceGuides: pieceGuides.guides,\n show: pieceGuides.show,\n style: pieceGuides.style,\n zoom,\n interacting,\n focusedPieceId: params.focusedPieceId ?? null,\n });\n if (guidesNeedRoundedClip) {\n ctx.restore();\n }\n\n // ADR-0060 post-pass: when in focused mode, dim everything that\n // sits OUTSIDE the focused piece's rect to ~30% alpha. This gives\n // neighbour artwork + their chrome the same \"overflow\" treatment\n // the user already sees outside the artboard's rounded shape via\n // `renderElementWithOverflowDimming` (Pass 1, 30% halo). The\n // per-piece chrome dim earlier was correct but only covered guide\n // chrome — the actual painted artwork on neighbour pieces stayed\n // at full alpha and read as competing visual subjects. This pass\n // unifies the dim across artwork + chrome.\n //\n // Drawn AFTER renderPieceGuides and BEFORE the interaction chrome\n // layer below, so selection bbox / handles stay at full 100%\n // alpha even when they cross the focused-piece boundary. Idle and\n // spread mode are no-ops (nothing in `pieces.length <= 1` is\n // multi-piece, and no `focusedPieceId` means spread).\n if (params.focusedPieceId && pieceGuides.pieces.length > 1) {\n // Prefer the animated rect when provided; fall back to the\n // static rect of `focusedPieceId` otherwise.\n const preservedRect =\n params.focusedPieceRect ??\n pieceGuides.pieces.find((p) => p.id === params.focusedPieceId) ??\n null;\n if (preservedRect) {\n ctx.save();\n ctx.translate(activeArtboard.x, activeArtboard.y);\n ctx.globalCompositeOperation = 'destination-out';\n // When an element is selected, dim the neighbouring pieces to\n // 30% so the user can see how their selection extends past\n // the focused piece's edge — same opacity multiplier as the\n // overflow halo. When *nothing* is selected, fully erase the\n // neighbours: at idle the \"peek\" of adjacent artwork reads as\n // visual chatter (\"why is half another page in my view?\"),\n // and the user always has the `All (spread)` toggle to see\n // the full layout when they want.\n const hasSelection = !!selectedElement;\n ctx.globalAlpha = hasSelection ? 1 - 0.3 : 1;\n ctx.fillStyle = '#000';\n ctx.beginPath();\n // Outer rect covering the entire visible canvas in artboard\n // coords. Use a generous bound rather than computing exactly\n // — `evenodd` cares only that it includes everything that\n // could be on-screen; oversized doesn't hurt.\n const HUGE = 1e7;\n ctx.rect(-HUGE, -HUGE, 2 * HUGE, 2 * HUGE);\n // Inner rect: the focused piece — carved out so its content\n // stays at full alpha.\n ctx.rect(\n preservedRect.x,\n preservedRect.y,\n preservedRect.width,\n preservedRect.height,\n );\n ctx.fill('evenodd');\n ctx.restore();\n }\n }\n }\n\n // Single-selection interaction chrome (bbox + handles + position/size\n // info + hover outline + crop-mode chrome). Drawn here, AFTER piece\n // guides, so the die-cut partial knockout (which intentionally dims\n // artwork inside the cutout to 30% to match the outside overflow\n // halo) doesn't also dim selection chrome — chrome stays at full\n // 100% opacity, sharp blue bbox edge and fully opaque resize-handle\n // dots, regardless of where the cutout sits relative to the element.\n // Mirrors how multi-selection chrome already renders below.\n if (activeArtboard) {\n CanvasRenderer.renderInteractionChromeLayer({\n ctx,\n selectedElement: elementToRender || null,\n transformHandles,\n stateMachine,\n currentRotation,\n isRotating,\n hoverState: hoverState as { type: 'element' | 'resize-handle' | 'rotation-handle' | null; data?: BaseElement | null },\n selectedId,\n hideHandles: shouldHideHandles,\n isEditing,\n isAltKeyPressed,\n artboardBackgroundColor: activeArtboard.backgroundColor,\n // Hover ring is clipped to the visible artboard / piece bounds\n // so it doesn't paint into the overflow region. Uses the piece-\n // shape-aware `effectiveBorderRadius` computed above, which\n // matches how the artwork itself is clipped, so the ring hugs\n // the same edge the user sees as the design canvas.\n hoverClipArtboard: {\n x: activeArtboard.x,\n y: activeArtboard.y,\n width: activeArtboard.width,\n height: activeArtboard.height,\n borderRadius: effectiveBorderRadius,\n clipShape: activeArtboard.clipShape,\n },\n showRotationHandle,\n zoom,\n paddingOffset: { x: paddingOffsetX, y: paddingOffsetY },\n });\n }\n\n // Render multi-selection indicators\n const displaySelection = isMarqueeSelecting ? marqueePreviewSelection : multiSelection;\n const activeArtboardElementIds = new Set(elementsForRender.map((e) => e.id));\n\n const { updatedOBB, groupBounds } = renderMultiSelectionIndicators({\n ctx,\n displaySelection,\n elements,\n activeArtboardElementIds,\n stateMachine,\n multiSelectionOBB: multiSelectionOBBRef.current,\n isRotating,\n currentRotation,\n isMarqueeSelecting,\n zoom,\n });\n\n // Write computed OBB directly to ref — no React setState, no re-render triggered from RAF.\n if (updatedOBB && multiSelectionOBBRef.current !== updatedOBB) {\n multiSelectionOBBRef.current = updatedOBB;\n }\n multiSelectionGroupBoundsRef.current = groupBounds;\n\n // Render marquee selection box\n if (isMarqueeSelecting) {\n renderMarqueeSelection({ ctx, marqueeStart, marqueeEnd, zoom });\n }\n\n // Render hover effect for group siblings\n if (hoverState.type === 'group-sibling' && hoverState.data) {\n renderGroupSiblingHover({ ctx, hoveredChild: hoverState.data as TextElement | ImageElement, zoom });\n }\n\n // Render pen tool preview\n if (penTool.isActive() && selectedElement instanceof PathElement) {\n penTool.renderPreview(ctx, selectedElement);\n }\n}\n","/**\n * PinchHandler — applies a two-finger pinch gesture (uniform scale +\n * rotation around the gesture centroid) to a canvas element.\n *\n * Stateless mirror of `ResizeHandler.calculateResize`. Inputs are:\n * - the element's start snapshot (`startData` from\n * `BaseElement.getTransformStartData`),\n * - the live gesture state (scale ratio, rotation delta, and\n * start / current centroid in world coords) produced by\n * `InteractionStateMachine.updatePinch`.\n *\n * Output is a fresh cloned element with the new x / y / size /\n * rotation. Aspect ratio is preserved (uniform scale). Stroke width\n * scales with size to keep visual proportions intact.\n *\n * The geometry: keep the element-local point that was under the\n * gesture centroid at start fixed under the centroid for every\n * subsequent frame. Equivalent to the affine transform\n *\n * T(P) = currentCentroid + R(rotationDelta, scale * (P - startCentroid))\n *\n * applied to the element's origin, where `R(θ, p)` rotates `p` by `θ`\n * radians. The element's local axes also rotate by `rotationDelta`,\n * so `rotation` becomes `startRotation + rotationDelta` (in degrees).\n *\n * The point of this approach: it's the natural \"the photograph stays\n * under my fingers\" feel users expect from native pinch gestures —\n * no element-center bias, no opposite-corner pinning. The two\n * fingers ARE the frame of reference.\n */\n\nimport type { BaseElement } from './BaseElement.js';\nimport type { Point, TransformStartData } from '../types/index.js';\n\nexport interface PinchInputs {\n /** Cloned element to mutate — typically `stateMachine.context.element`. */\n element: BaseElement;\n /** Start snapshot of the element's transform (from\n * `BaseElement.getTransformStartData`). Carries the start position,\n * rotation, dimensions, and stroke width that the math reads. */\n startData: TransformStartData;\n /** Distance ratio (current / start). 1 = no scale change. */\n scale: number;\n /** Angle delta in radians (current - start). 0 = no rotate change. */\n rotationDelta: number;\n /** Gesture centroid at gesture start, in world coords. */\n startCentroid: Point;\n /** Gesture centroid right now, in world coords. */\n currentCentroid: Point;\n /**\n * Optional snap hook for cardinal-angle rotation snapping. Same\n * shape as `InteractionStateMachine`'s rotation snap callback —\n * returns possibly-adjusted rotation in degrees.\n */\n snapRotation?: (rotation: number, element: BaseElement) => number;\n}\n\nexport class PinchHandler {\n /**\n * Apply the pinch gesture to `element` in place and return it. The\n * element is expected to already be a clone (the state machine\n * clones on `startPinch`) — this method does not clone again.\n */\n static calculatePinch({\n element,\n startData,\n scale,\n rotationDelta,\n startCentroid,\n currentCentroid,\n snapRotation,\n }: PinchInputs): BaseElement {\n const startWidth = startData.width ?? 0;\n const startHeight = startData.height ?? 0;\n const newWidth = Math.max(1, startWidth * scale);\n const newHeight = Math.max(1, startHeight * scale);\n\n // Apply size via the element's own resize() so type-specific\n // scaling (fontSize on text, lineCount, etc.) lands. Anchor is\n // arbitrary here — pinch overrides position below — but\n // 'bottom-right' is the natural identity anchor: scaling from\n // the top-left without rotation. Element-specific resize\n // implementations may also scale stroke width based on\n // `startData.strokeWidth` so we don't need to do that ourselves.\n element.resize('bottom-right', newWidth, newHeight, startData);\n\n // Position the element so the gesture's start-centroid local\n // point (the canvas pixel that was under the user's fingertip\n // midpoint at gesture start) lands exactly at currentCentroid.\n //\n // T(P) = currentCentroid + R(rotationDelta, scale * (P - startCentroid))\n //\n // applied to the element's origin (startData.x, startData.y).\n const dx = startData.x - startCentroid.x;\n const dy = startData.y - startCentroid.y;\n const cos = Math.cos(rotationDelta);\n const sin = Math.sin(rotationDelta);\n const sdx = scale * dx;\n const sdy = scale * dy;\n element.x = currentCentroid.x + sdx * cos - sdy * sin;\n element.y = currentCentroid.y + sdx * sin + sdy * cos;\n\n // Rotation value: in this codebase, the element's stored\n // `rotation` field uses an inverted sign relative to the screen\n // coordinate system (`RotationUtils.toRadians` negates when\n // converting for rendering, and the rotate-handle handler does\n // `newRotation = current - delta` for the same reason). We\n // mirror that here: subtract the gesture's atan2 delta so a\n // CW-visual finger twist produces a CW-visual element rotation.\n // Position math above intentionally stays un-negated — the\n // origin's location around the gesture centroid is computed in\n // screen coords, where the same `rotationDelta` produces the\n // matching visual motion. Only the stored rotation field flips.\n let newRotation = startData.rotation - (rotationDelta * 180) / Math.PI;\n // Normalize to (-180, 180] so snap callbacks receive a stable\n // domain regardless of how many full turns the user has done.\n newRotation = ((newRotation % 360) + 360) % 360;\n if (newRotation > 180) newRotation -= 360;\n if (snapRotation) {\n newRotation = snapRotation(newRotation, element);\n }\n element.setRotation(newRotation);\n\n return element;\n }\n}\n","/**\n * Click Protection Utility\n *\n * Provides a mechanism to prevent deselection when clicking on overlays\n * like color pickers, context menus, etc.\n *\n * Usage:\n * 1. Call `protectNextClick()` before showing an overlay\n * 2. In your click handler, check `shouldProtectClick()` before deselecting\n * 3. The protection automatically expires after one click or a timeout\n */\n\nclass ClickProtectionManager {\n isProtectedFlag: boolean;\n timeout: ReturnType<typeof setTimeout> | null;\n timeoutDuration: number;\n\n constructor() {\n this.isProtectedFlag = false;\n this.timeout = null;\n this.timeoutDuration = 5000; // 5 seconds max protection\n }\n\n /**\n * Protect the next click from triggering deselection\n * Automatically expires after one check or timeout\n */\n protectNextClick() {\n this.isProtectedFlag = true;\n\n // Clear any existing timeout\n if (this.timeout) {\n clearTimeout(this.timeout);\n }\n\n // Set timeout to automatically clear protection\n this.timeout = setTimeout(() => {\n this.isProtectedFlag = false;\n this.timeout = null;\n }, this.timeoutDuration);\n }\n\n /**\n * Check if the current click should be protected\n * This consumes the protection (one-time use)\n * @returns {boolean} true if click should be protected, false otherwise\n */\n shouldProtectClick() {\n const wasProtected = this.isProtectedFlag;\n\n // Consume the protection\n if (this.isProtectedFlag) {\n this.isProtectedFlag = false;\n if (this.timeout) {\n clearTimeout(this.timeout);\n this.timeout = null;\n }\n }\n\n return wasProtected;\n }\n\n /**\n * Manually clear protection (useful for cleanup)\n */\n clearProtection() {\n this.isProtectedFlag = false;\n if (this.timeout) {\n clearTimeout(this.timeout);\n this.timeout = null;\n }\n }\n\n /**\n * Check if protection is currently active (without consuming it)\n * @returns {boolean} true if protection is active\n */\n isProtected() {\n return this.isProtectedFlag;\n }\n}\n\n// Export singleton instance\nexport const clickProtection = new ClickProtectionManager();\n","/**\n * Multi-Selection Interaction Handlers\n * Handles hit testing and interaction for multi-selection bounds, handles, and rotation\n */\n\nimport { TextElement } from '../../../core/TextElement.js';\nimport { ImageElement } from '../../../core/ImageElement.js';\nimport { GroupElement } from '../../../core/GroupElement.js';\nimport type { ShapeElement } from '../../../core/ShapeElement.js';\nimport type { PathElement } from '../../../core/PathElement.js';\nimport type { InteractionStateMachine } from '../../../core/InteractionStateMachine.js';\nimport type { MultiSelectionGroupBounds } from '../renderers/index.js';\nimport { calculateAngle } from '../../../core/GeometryUtils.js';\nimport { CORNER_HANDLE_HIT_RADIUS } from '../../../constants.js';\n\nimport type { LocalHoverState } from '../types/index.js';\n\n/** Elements that can be on the canvas */\ntype CanvasElement = TextElement | ImageElement | GroupElement | ShapeElement | PathElement;\n\n/** Multi-selection resize context stored on the state machine */\ninterface MultiSelectionResizeContext {\n anchor: string;\n startBounds: { minX: number; maxX: number; minY: number; maxY: number };\n elementsStartData: Array<{ element: CanvasElement; startData: Record<string, unknown> }>;\n}\n\n/** Multi-selection rotation context stored on the state machine */\ninterface MultiSelectionRotationContext {\n startBounds: { centerX: number; centerY: number };\n elementsStartData: Array<{ element: CanvasElement; startData: Record<string, unknown> }>;\n}\n\n/** Multi-selection drag context stored on the state machine */\ninterface MultiSelectionDragContext {\n startX: number;\n startY: number;\n elementsStartData: Array<{ element: CanvasElement; startData: Record<string, unknown> }>;\n currentElements: Map<string, CanvasElement>;\n}\n\n/** Augmented state machine with multi-selection ad-hoc properties */\ntype AugmentedStateMachine = InteractionStateMachine & {\n _multiSelectionDragContext?: MultiSelectionDragContext;\n _multiSelectionResizeContext?: MultiSelectionResizeContext;\n _multiSelectionRotationContext?: MultiSelectionRotationContext;\n};\n\nexport type MultiSelectionHitResult =\n | { type: 'corner-handle'; anchor: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'; corner: { x: number; y: number } }\n | { type: 'rotation-handle' }\n | { type: 'bounds-drag' }\n | { type: 'none' };\n\nexport interface MultiSelectionHitTestParams {\n x: number;\n y: number;\n bounds: MultiSelectionGroupBounds;\n multiSelection: string[];\n elements: CanvasElement[];\n zoom?: number;\n}\n\n/**\n * Hit test for multi-selection interactions\n * Returns what was clicked: corner handle, rotation handle, bounds drag area, or nothing\n */\nexport function hitTestMultiSelection(params: MultiSelectionHitTestParams): MultiSelectionHitResult {\n const { x, y, bounds, zoom = 1.0 } = params;\n\n // Use the same hit radius as single-selection handles, scaled by zoom\n // Coordinates are in world space, so hit radius needs to be in world space too\n const hitRadius = CORNER_HANDLE_HIT_RADIUS / zoom;\n const groupRotation = bounds.rotation || 0;\n\n // Define corners\n const corners = [\n { x: bounds.minX, y: bounds.minY, anchor: 'top-left' as const },\n { x: bounds.maxX, y: bounds.minY, anchor: 'top-right' as const },\n { x: bounds.minX, y: bounds.maxY, anchor: 'bottom-left' as const },\n { x: bounds.maxX, y: bounds.maxY, anchor: 'bottom-right' as const },\n ];\n\n // Apply rotation to corner positions if group is rotated\n const rotatedCorners = corners.map((corner) => {\n if (groupRotation !== 0) {\n const rotationRad = (-groupRotation * Math.PI) / 180;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n const dx = corner.x - bounds.centerX;\n const dy = corner.y - bounds.centerY;\n return {\n x: bounds.centerX + (dx * cos - dy * sin),\n y: bounds.centerY + (dx * sin + dy * cos),\n anchor: corner.anchor,\n };\n }\n return corner;\n });\n\n // Check corner handles\n for (const corner of rotatedCorners) {\n const dx = x - corner.x;\n const dy = y - corner.y;\n const distance = Math.sqrt(dx * dx + dy * dy);\n\n if (distance <= hitRadius) {\n return { type: 'corner-handle', anchor: corner.anchor, corner };\n }\n }\n\n // Check rotation handle - use larger hit radius scaled by zoom\n const rotationHitRadius = (16 + 2) / zoom; // 16px visual + 2px padding, in world space\n const rotationHandleDistance = Math.sqrt(\n Math.pow(x - bounds.rotationHandleX, 2) + Math.pow(y - bounds.rotationHandleY, 2)\n );\n\n if (rotationHandleDistance <= rotationHitRadius) {\n return { type: 'rotation-handle' };\n }\n\n // Check if clicking within bounds (for dragging)\n // Transform click point to the rotated coordinate system\n let localX = x;\n let localY = y;\n\n if (groupRotation !== 0) {\n const rotationRad = (groupRotation * Math.PI) / 180;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n const dx = x - bounds.centerX;\n const dy = y - bounds.centerY;\n localX = bounds.centerX + (dx * cos - dy * sin);\n localY = bounds.centerY + (dx * sin + dy * cos);\n }\n\n // Check if point is within the bounding box\n if (localX >= bounds.minX && localX <= bounds.maxX && localY >= bounds.minY && localY <= bounds.maxY) {\n return { type: 'bounds-drag' };\n }\n\n return { type: 'none' };\n}\n\nexport interface StartMultiSelectionResizeParams {\n multiSelection: string[];\n elements: CanvasElement[];\n bounds: MultiSelectionGroupBounds;\n anchor: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';\n corner: { x: number; y: number };\n x: number;\n y: number;\n stateMachine: InteractionStateMachine;\n}\n\n/**\n * Starts a resize operation on multi-selection\n * Sets up state machine context for multi-selection resize\n */\nexport function startMultiSelectionResize(params: StartMultiSelectionResizeParams): boolean {\n const { multiSelection, elements, bounds, anchor, corner, x, y, stateMachine } = params;\n\n // Get all selected elements (excluding locked ones)\n const selectedElements = multiSelection\n .map((id) => elements.find((e) => e.id === id))\n .filter((e): e is CanvasElement => e !== undefined && !e.locked);\n\n // If all elements are locked, don't allow resize\n if (selectedElements.length === 0) {\n return false;\n }\n\n (stateMachine as AugmentedStateMachine)._multiSelectionResizeContext = {\n anchor,\n startBounds: { ...bounds },\n elementsStartData: selectedElements.map((element) => ({\n element,\n startData: element.getTransformStartData(),\n })),\n };\n\n stateMachine.startResize(\n selectedElements[0], // Use first element as reference\n {\n anchor,\n x: corner.x,\n y: corner.y,\n cursor: 'nwse-resize',\n type: 'corner',\n },\n x,\n y,\n {\n x: bounds.minX,\n y: bounds.minY,\n width: bounds.maxX - bounds.minX,\n height: bounds.maxY - bounds.minY,\n },\n {\n x: bounds.minX,\n y: bounds.minY,\n width: bounds.maxX - bounds.minX,\n height: bounds.maxY - bounds.minY,\n }\n );\n\n return true;\n}\n\nexport interface StartMultiSelectionRotateParams {\n multiSelection: string[];\n elements: CanvasElement[];\n bounds: MultiSelectionGroupBounds;\n x: number;\n y: number;\n stateMachine: InteractionStateMachine;\n setIsRotating: (value: boolean) => void;\n setRotationStartAngle: (angle: number) => void;\n setCurrentRotation: (rotation: number) => void;\n}\n\n/**\n * Starts a rotation operation on multi-selection\n * Sets up state machine context for multi-selection rotation\n */\nexport function startMultiSelectionRotate(params: StartMultiSelectionRotateParams): boolean {\n const {\n multiSelection,\n elements,\n bounds,\n x,\n y,\n stateMachine,\n setIsRotating,\n setRotationStartAngle,\n setCurrentRotation,\n } = params;\n\n // Get all selected elements (excluding locked ones)\n const selectedElements = multiSelection\n .map((id) => elements.find((e) => e.id === id))\n .filter((e): e is CanvasElement => e !== undefined && !e.locked);\n\n // If all elements are locked, don't allow rotation\n if (selectedElements.length === 0) {\n return false;\n }\n\n const startAngle = calculateAngle(bounds.centerX, bounds.centerY, x, y);\n\n (stateMachine as AugmentedStateMachine)._multiSelectionRotationContext = {\n startBounds: { ...bounds },\n elementsStartData: selectedElements.map((element) => ({\n element,\n startData: element.getTransformStartData(),\n })),\n };\n\n setIsRotating(true);\n setRotationStartAngle(startAngle);\n setCurrentRotation(0);\n stateMachine.startRotate(\n selectedElements[0], // Use first element as reference\n x,\n y,\n startAngle,\n 0 // Initial rotation for the group\n );\n\n return true;\n}\n\nexport interface StartMultiSelectionDragParams {\n multiSelection: string[];\n elements: CanvasElement[];\n x: number;\n y: number;\n stateMachine: InteractionStateMachine;\n dragOriginalElementsRef: React.MutableRefObject<Map<string, CanvasElement> | null>;\n setHoverState: (state: Omit<LocalHoverState, 'startTime'>) => void;\n}\n\n/**\n * Starts a drag operation on multi-selection\n * Sets up state machine context for multi-selection group drag\n */\nexport function startMultiSelectionDrag(params: StartMultiSelectionDragParams): boolean {\n const { multiSelection, elements, x, y, stateMachine, dragOriginalElementsRef, setHoverState } = params;\n\n // Get all selected elements (excluding locked ones)\n const selectedElements = multiSelection\n .map((id) => elements.find((e) => e.id === id))\n .filter((e): e is CanvasElement => e !== undefined && !e.locked);\n\n if (selectedElements.length === 0) {\n return false;\n }\n\n // Store start data for all selected elements to enable group dragging\n (stateMachine as AugmentedStateMachine)._multiSelectionDragContext = {\n startX: x,\n startY: y,\n elementsStartData: selectedElements.map((element) => ({\n element,\n startData: element.getTransformStartData(),\n })),\n currentElements: new Map(), // Track current elements during drag\n };\n\n // Store original elements before drag for single undo entry\n dragOriginalElementsRef.current = new Map();\n selectedElements.forEach((element) => {\n dragOriginalElementsRef.current!.set(element.id, element.clone());\n });\n\n // Start drag with the first element as reference\n stateMachine.startDrag(selectedElements[0], x, y);\n setHoverState({ type: null, data: null }); // Clear hover state\n\n return true;\n}\n","/**\n * Crop Mode Interaction Handlers\n * Handles crop mode interactions for images (both standalone and within groups)\n */\n\nimport { TextElement } from '../../../core/TextElement.js';\nimport { ImageElement } from '../../../core/ImageElement.js';\nimport { GroupElement } from '../../../core/GroupElement.js';\nimport type { ShapeElement } from '../../../core/ShapeElement.js';\nimport type { PathElement } from '../../../core/PathElement.js';\nimport type { InteractionStateMachine } from '../../../core/InteractionStateMachine.js';\nimport type { TransformHandles } from '../../../core/TransformHandles.js';\nimport type { HandleInfo, ResizeAnchor } from '../../../types/index.js';\n\n/** Return type from ImageElement.hitTestCropHandle() */\ninterface CropHandleHitResult {\n type: 'corner' | 'edge';\n anchor: string;\n worldX: number;\n worldY: number;\n}\n\n/** Elements that can be on the canvas */\ntype CanvasElement = TextElement | ImageElement | GroupElement | ShapeElement | PathElement;\n\n/** Elements that can be children of a group (including nested groups) */\ntype ChildElement = TextElement | ImageElement | ShapeElement | PathElement | GroupElement;\n\nexport interface CropModeContext {\n croppingImage: ImageElement;\n croppingChild: ImageElement | null;\n croppingChildIndex: number;\n isChildInGroup: boolean;\n}\n\nexport interface FindCroppingImageParams {\n selectedElement: CanvasElement | null;\n activeChildElement: ChildElement | null;\n}\n\n/**\n * Finds the image currently in crop mode (either standalone or child in group)\n */\nexport function findCroppingImage(params: FindCroppingImageParams): CropModeContext | null {\n const { selectedElement, activeChildElement } = params;\n\n let croppingChild: ImageElement | null = null;\n let croppingChildIndex = -1;\n\n // First check if activeChildElement is in crop mode\n if (activeChildElement instanceof ImageElement && activeChildElement.isCropping) {\n croppingChild = activeChildElement;\n // Find the index in the group\n if (selectedElement instanceof GroupElement) {\n croppingChildIndex = selectedElement.children.findIndex((c) => c.id === activeChildElement.id);\n }\n } else if (selectedElement instanceof GroupElement) {\n // Otherwise look for any child in crop mode\n for (let i = 0; i < selectedElement.children.length; i++) {\n const child = selectedElement.children[i];\n // Skip invisible children\n if (child.visible === false) continue;\n\n if (child instanceof ImageElement && child.isCropping) {\n croppingChild = child;\n croppingChildIndex = i;\n break;\n }\n }\n }\n\n // Determine the actual cropping image (child or standalone)\n const croppingImage =\n croppingChild || (selectedElement instanceof ImageElement && selectedElement.isCropping ? selectedElement : null);\n\n if (!croppingImage) {\n return null;\n }\n\n return {\n croppingImage,\n croppingChild,\n croppingChildIndex,\n isChildInGroup: croppingChild !== null,\n };\n}\n\nexport type CropModeHitResult =\n | { type: 'crop-handle'; handle: CropHandleHitResult }\n | { type: 'image-handle'; handle: HandleInfo }\n | { type: 'image-drag' }\n | { type: 'exit-crop' }\n | { type: 'none' };\n\nexport interface CropModeHitTestParams {\n x: number;\n y: number;\n cropContext: CropModeContext;\n transformHandles: TransformHandles;\n zoom?: number;\n}\n\n/**\n * Hit test for crop mode interactions\n * Priority: crop handles > image handles > image drag > exit crop mode\n */\nexport function hitTestCropMode(params: CropModeHitTestParams): CropModeHitResult {\n const { x, y, cropContext, transformHandles, zoom = 1.0 } = params;\n const { croppingImage } = cropContext;\n\n // Priority 1: Check if clicking on crop box handles (for resizing crop area)\n const cropHandle = croppingImage.hitTestCropHandle(x, y, zoom);\n if (cropHandle) {\n return { type: 'crop-handle', handle: cropHandle };\n }\n\n // Priority 2: Check if clicking on image corner handles (for scaling the image)\n const imageHandle = transformHandles.hitTestResize(x, y, zoom);\n if (imageHandle) {\n return { type: 'image-handle', handle: imageHandle };\n }\n\n // Priority 3: Check if clicking inside the full image (for dragging image within crop)\n if (croppingImage.hitTest(x, y)) {\n return { type: 'image-drag' };\n }\n\n // Priority 4: Clicking outside exits crop mode\n return { type: 'exit-crop' };\n}\n\nexport interface HandleCropHandleClickParams {\n cropContext: CropModeContext;\n cropHandle: CropHandleHitResult;\n x: number;\n y: number;\n selectedElement: CanvasElement | null;\n activeChildElement: ChildElement | null;\n stateMachine: InteractionStateMachine;\n}\n\n/**\n * Handles clicking on a crop box handle (resize crop area)\n */\nexport function handleCropHandleClick(params: HandleCropHandleClickParams): void {\n const { cropContext, cropHandle, x, y, selectedElement, activeChildElement, stateMachine } = params;\n const { croppingImage, croppingChild, croppingChildIndex } = cropContext;\n\n const cropBox = croppingImage.getCropBoxBounds();\n if (!cropBox) return;\n\n stateMachine.startResize(\n croppingImage,\n {\n // Crop anchors use a 'crop-' prefix to distinguish from regular resize anchors.\n // The ResizeHandler checks for this prefix via anchor.startsWith('crop-').\n // This cast is necessary because ResizeAnchor doesn't include crop-prefixed variants.\n anchor: ('crop-' + cropHandle.anchor) as ResizeAnchor,\n x: 0,\n y: 0,\n cursor: 'grab',\n type: cropHandle.type,\n },\n x,\n y,\n cropBox,\n cropBox\n );\n\n // Store group context if editing child\n if (croppingChild) {\n // Use activeChildContext if we came from child editing mode\n if (activeChildElement && activeChildElement.id === croppingChild.id) {\n stateMachine._activeChildContext = {\n group: selectedElement as GroupElement,\n childId: croppingChild.id,\n };\n } else {\n stateMachine._cropGroupContext = {\n group: selectedElement!,\n childIndex: croppingChildIndex,\n };\n }\n }\n}\n\nexport interface HandleImageHandleClickParams {\n cropContext: CropModeContext;\n imageHandle: HandleInfo;\n x: number;\n y: number;\n selectedElement: CanvasElement | null;\n activeChildElement: ChildElement | null;\n stateMachine: InteractionStateMachine;\n}\n\n/**\n * Handles clicking on an image corner handle (scale image)\n */\nexport function handleImageHandleClick(params: HandleImageHandleClickParams): void {\n const { cropContext, imageHandle, x, y, selectedElement, activeChildElement, stateMachine } = params;\n const { croppingImage, croppingChild, croppingChildIndex } = cropContext;\n\n const bbox = croppingImage.getBoundingBox();\n const visualBbox = croppingImage.getVisualBoundingBox();\n stateMachine.startResize(croppingImage, imageHandle, x, y, bbox, visualBbox);\n\n // Store group context if editing child\n if (croppingChild) {\n // Use activeChildContext if we came from child editing mode\n if (activeChildElement && activeChildElement.id === croppingChild.id) {\n stateMachine._activeChildContext = {\n group: selectedElement as GroupElement,\n childId: croppingChild.id,\n };\n } else {\n stateMachine._cropGroupContext = {\n group: selectedElement!,\n childIndex: croppingChildIndex,\n };\n }\n }\n}\n\nexport interface HandleImageDragClickParams {\n cropContext: CropModeContext;\n x: number;\n y: number;\n selectedElement: CanvasElement | null;\n activeChildElement: ChildElement | null;\n stateMachine: InteractionStateMachine;\n dragOriginalElementsRef: React.MutableRefObject<Map<string, CanvasElement> | null>;\n setHoverState: (state: { type: null; data: null }) => void;\n}\n\n/**\n * Handles clicking inside the image (drag image within crop)\n */\nexport function handleImageDragClick(params: HandleImageDragClickParams): void {\n const { cropContext, x, y, selectedElement, activeChildElement, stateMachine, dragOriginalElementsRef, setHoverState } = params;\n const { croppingImage, croppingChild, croppingChildIndex } = cropContext;\n\n // Store original element before drag for single undo entry\n dragOriginalElementsRef.current = new Map();\n if (croppingChild && selectedElement) {\n // If cropping a child, store the parent group\n dragOriginalElementsRef.current.set(selectedElement.id, selectedElement.clone());\n } else {\n dragOriginalElementsRef.current.set(croppingImage.id, croppingImage.clone());\n }\n\n stateMachine.startDrag(croppingImage, x, y, 'crop-image-drag');\n setHoverState({ type: null, data: null }); // Clear hover state\n\n // Store group context if editing child\n if (croppingChild) {\n // Use activeChildContext if we came from child editing mode\n if (activeChildElement && activeChildElement.id === croppingChild.id) {\n stateMachine._activeChildContext = {\n group: selectedElement as GroupElement,\n childId: croppingChild.id,\n };\n } else {\n stateMachine._cropGroupContext = {\n group: selectedElement!,\n childIndex: croppingChildIndex,\n };\n }\n }\n}\n\nexport interface ExitCropModeParams {\n cropContext: CropModeContext;\n selectedElement: CanvasElement | null;\n onElementUpdate: (element: CanvasElement) => void;\n transformHandles: TransformHandles;\n updateActiveChild: (child: ChildElement | null) => void;\n zoom: number;\n}\n\n/**\n * Exits crop mode when clicking outside the image\n */\nexport function exitCropMode(params: ExitCropModeParams): void {\n const { cropContext, selectedElement, onElementUpdate, transformHandles, updateActiveChild, zoom } = params;\n const { croppingImage, croppingChild, croppingChildIndex } = cropContext;\n\n if (croppingChild && selectedElement instanceof GroupElement) {\n // Exit crop mode for child in group\n const updatedChild = croppingChild.clone();\n updatedChild.exitCropMode();\n const updatedGroup = selectedElement.clone();\n updatedGroup.children[croppingChildIndex] = updatedChild;\n onElementUpdate(updatedGroup);\n transformHandles.update(updatedGroup, zoom);\n updateActiveChild(null);\n } else {\n // Exit crop mode for direct image\n const updatedElement = croppingImage.clone();\n updatedElement.exitCropMode();\n onElementUpdate(updatedElement);\n transformHandles.update(updatedElement, zoom);\n }\n}\n","/**\n * Group Child Interaction Handlers\n * Handles selection and interaction with children within groups\n */\n\nimport { TextElement } from '../../../core/TextElement.js';\nimport { ImageElement } from '../../../core/ImageElement.js';\nimport { GroupElement } from '../../../core/GroupElement.js';\nimport type { ShapeElement } from '../../../core/ShapeElement.js';\nimport type { PathElement } from '../../../core/PathElement.js';\nimport type { InteractionStateMachine } from '../../../core/InteractionStateMachine.js';\nimport type { TransformHandles } from '../../../core/TransformHandles.js';\nimport type { LocalHoverState } from '../types/index.js';\n\n/** Elements that can be on the canvas */\ntype CanvasElement = TextElement | ImageElement | GroupElement | ShapeElement | PathElement;\n\n/** Elements that can be children of a group (including nested groups) */\ntype ChildElement = TextElement | ImageElement | ShapeElement | PathElement | GroupElement;\n\nexport interface HandleSiblingClickParams {\n x: number;\n y: number;\n activeChildElement: ChildElement;\n selectedElement: GroupElement;\n updateActiveChild: (child: ChildElement | null) => void;\n transformHandles: TransformHandles;\n setHandlesVersion: React.Dispatch<React.SetStateAction<number>>;\n stateMachine: InteractionStateMachine;\n dragOriginalElementsRef: React.MutableRefObject<Map<string, CanvasElement> | null>;\n setHoverState: (state: Omit<LocalHoverState, 'startTime'>) => void;\n forceUpdate: (value: Record<string, unknown>) => void;\n zoom: number;\n}\n\n/**\n * Handles clicking on a sibling element when in active child mode\n * Selects the sibling and starts drag if clicked\n * Returns true if a sibling was clicked, false otherwise\n */\nexport function handleSiblingClick(params: HandleSiblingClickParams): boolean {\n const {\n x,\n y,\n activeChildElement,\n selectedElement,\n updateActiveChild,\n transformHandles,\n setHandlesVersion,\n stateMachine,\n dragOriginalElementsRef,\n setHoverState,\n forceUpdate,\n zoom,\n } = params;\n\n // Check if clicking on a sibling\n for (let i = selectedElement.children.length - 1; i >= 0; i--) {\n const child = selectedElement.children[i];\n\n // Skip invisible children and the currently active child\n if (child.visible === false || child.id === activeChildElement.id) continue;\n\n if (child.hitTest(x, y)) {\n\n // Select this sibling\n updateActiveChild(child as ImageElement | TextElement);\n transformHandles.update(child, zoom);\n setHandlesVersion((v) => v + 1);\n\n // Start drag for the newly selected sibling\n stateMachine.startDrag(child, x, y);\n\n // Store original element before drag for single undo entry\n dragOriginalElementsRef.current = new Map();\n dragOriginalElementsRef.current.set(child.id, child.clone());\n\n // Clear hover state when starting drag\n setHoverState({ type: null, data: null });\n\n // Store group context\n (stateMachine as unknown as Record<string, unknown>)._activeChildContext = {\n group: selectedElement,\n childId: child.id,\n };\n\n forceUpdate({});\n return true;\n }\n }\n\n return false;\n}\n\nexport interface ExitActiveChildModeParams {\n updateActiveChild: (child: ChildElement | null) => void;\n selectedElement: CanvasElement | null;\n transformHandles: TransformHandles;\n setHandlesVersion: React.Dispatch<React.SetStateAction<number>>;\n zoom: number;\n}\n\n/**\n * Exits active child editing mode\n * Called when clicking outside all children in a group\n */\nexport function exitActiveChildMode(params: ExitActiveChildModeParams): void {\n const { updateActiveChild, selectedElement, transformHandles, setHandlesVersion, zoom } = params;\n\n updateActiveChild(null);\n if (selectedElement) {\n transformHandles.update(selectedElement, zoom);\n setHandlesVersion((v) => v + 1);\n }\n}\n\nexport interface HandleElementSelectionParams {\n x: number;\n y: number;\n shiftKey: boolean;\n activeArtboardElements: CanvasElement[];\n selectedElement: CanvasElement | null;\n multiSelection: string[];\n onSelectionChange: (id: string | null) => void;\n onMultiSelectionChange: (ids: string[]) => void;\n stateMachine: InteractionStateMachine;\n dragOriginalElementsRef: React.MutableRefObject<Map<string, CanvasElement> | null>;\n setHoverState: (state: Omit<LocalHoverState, 'startTime'>) => void;\n forceUpdate: (value: Record<string, unknown>) => void;\n}\n\n/**\n * Handles clicking on elements in the canvas for selection\n * Supports shift-click multi-selection and normal selection\n * Returns true if an element was clicked, false otherwise\n */\nexport function handleElementSelection(params: HandleElementSelectionParams): boolean {\n const {\n x,\n y,\n shiftKey,\n activeArtboardElements,\n selectedElement,\n multiSelection,\n onSelectionChange,\n onMultiSelectionChange,\n stateMachine,\n dragOriginalElementsRef,\n setHoverState,\n forceUpdate,\n } = params;\n\n for (let i = activeArtboardElements.length - 1; i >= 0; i--) {\n // Skip invisible elements. Locked elements remain selectable —\n // they just can't be manipulated (drag/resize/rotate are gated\n // by `if (locked) return` in the gesture handlers). Selecting a\n // locked element lets the user open the lock toggle in the\n // selection chip without first having to unlock via a panel.\n if (activeArtboardElements[i].visible === false) continue;\n\n if (activeArtboardElements[i].hitTest(x, y)) {\n\n const clickedId = activeArtboardElements[i].id;\n\n // Shift-click: add/remove from multi-selection\n if (shiftKey) {\n\n // If there's a single selected element, convert it to multi-selection\n if (selectedElement && multiSelection.length === 0) {\n onMultiSelectionChange([selectedElement.id, clickedId]);\n onSelectionChange(null);\n } else {\n // Toggle element in multi-selection\n if (multiSelection.includes(clickedId)) {\n // Remove from selection\n const newSelection = multiSelection.filter((id) => id !== clickedId);\n onMultiSelectionChange(newSelection);\n } else {\n // Add to selection\n onMultiSelectionChange([...multiSelection, clickedId]);\n }\n }\n return true;\n }\n\n // Check if clicking on an element that's already in the multi-selection\n if (multiSelection.length > 0 && multiSelection.includes(clickedId)) {\n\n // Get all selected elements (excluding locked ones)\n const selectedElements = multiSelection\n .map((id) => activeArtboardElements.find((e) => e.id === id))\n .filter((e): e is CanvasElement => e !== undefined && !e.locked);\n\n if (selectedElements.length > 0) {\n // Store start data for all selected elements to enable group dragging\n (stateMachine as unknown as Record<string, unknown>)._multiSelectionDragContext = {\n startX: x,\n startY: y,\n elementsStartData: selectedElements.map((element) => ({\n element,\n startData: element.getTransformStartData(),\n })),\n currentElements: new Map(), // Track current elements during drag\n };\n\n // Store original elements before drag for single undo entry\n dragOriginalElementsRef.current = new Map();\n selectedElements.forEach((element) => {\n dragOriginalElementsRef.current!.set(element.id, element.clone());\n });\n\n // Start drag with the first element as reference\n stateMachine.startDrag(selectedElements[0], x, y);\n setHoverState({ type: null, data: null }); // Clear hover state\n forceUpdate({});\n return true;\n }\n }\n\n // Normal click: select only this element\n onSelectionChange(clickedId);\n onMultiSelectionChange([]); // Clear multi-selection\n\n // Store original element before drag for single undo entry\n dragOriginalElementsRef.current = new Map();\n dragOriginalElementsRef.current.set(clickedId, activeArtboardElements[i].clone());\n\n stateMachine.startDrag(activeArtboardElements[i], x, y);\n setHoverState({ type: null, data: null }); // Clear hover state\n\n // For groups, store the original element reference to clone from during drag\n if (activeArtboardElements[i] instanceof GroupElement) {\n (stateMachine as unknown as Record<string, unknown>)._groupDragOriginalElement = activeArtboardElements[i].clone();\n }\n\n forceUpdate({});\n return true;\n }\n }\n\n return false;\n}\n","/**\n * useCanvasInteraction - Canvas pointer/mouse event handlers\n *\n * Extracted from CanvasEditor.tsx to isolate all interaction logic including:\n * - handlePointerDown: Selection, drag start, resize start, rotation start, crop, pen tool\n * - handlePointerMove: Drag, resize, rotation updates, hover cursor, spacing detection\n * - handlePointerUp: Commit changes, clear contexts, text edit mode entry\n * - handleDoubleClick: Enter text edit or crop mode\n * - Document-level event forwarding for out-of-canvas interactions\n */\n\nimport React, { useCallback, useEffect, useRef } from 'react';\nimport { TextElement } from '../../../core/TextElement.js';\nimport { ImageElement } from '../../../core/ImageElement.js';\nimport { GroupElement } from '../../../core/GroupElement.js';\nimport { ShapeElement } from '../../../core/ShapeElement.js';\nimport { PathElement } from '../../../core/PathElement.js';\nimport type { BaseElement } from '../../../core/BaseElement.js';\nimport { CustomTransform } from '../../../transforms/CustomTransform.js';\nimport { useViewportContext } from '../../../contexts/ViewportContext.js';\n\nimport type { TransformHandles } from '../../../core/TransformHandles.js';\nimport type { InteractionStateMachine, ActiveChildContext } from '../../../core/InteractionStateMachine.js';\nimport type { AlignmentSnapSystem } from '../../../core/AlignmentSnapSystem.js';\nimport type { SpacingSystem } from '../../../core/SpacingSystem.js';\nimport { globalResizePipeline } from '../../../core/ResizePipeline.js';\nimport { PinchHandler } from '../../../core/PinchHandler.js';\nimport { calculateAngle, getBoundingBoxCenter, getCursorForWorldPosition } from '../../../core/GeometryUtils.js';\nimport { Transform } from '../../../core/Transform.js';\nimport { RotationUtils } from '../../../core/RotationUtils.js';\nimport { clickProtection } from '../../../utils/clickProtection.js';\nimport { shouldPreserveSelection } from '../../../utils/selectionPreservation.js';\nimport { getCursorIndexFromPoint } from '../../../utils/textCursorUtils.js';\nimport { getWordBoundaryAt } from '../../../utils/textCursorUtils.js';\nimport type { SnapGuide, SpacingIndicator, RichText, CharacterStyle, ImageTransformData, ResizeAnchor, TransformStartData } from '../../../types/index.js';\nimport type { LocalHoverState } from '../types/index.js';\nimport type { MultiSelectionOBB, MultiSelectionGroupBounds } from '../renderers/index.js';\n\nimport {\n hitTestMultiSelection,\n startMultiSelectionResize,\n startMultiSelectionRotate,\n startMultiSelectionDrag,\n findCroppingImage,\n hitTestCropMode,\n handleCropHandleClick,\n handleImageHandleClick,\n handleImageDragClick,\n exitCropMode,\n handleSiblingClick,\n exitActiveChildMode,\n handleElementSelection,\n} from '../handlers/index.js';\n\ntype CanvasElement = TextElement | ImageElement | GroupElement | ShapeElement | PathElement;\ntype ChildElement = TextElement | ImageElement | ShapeElement | PathElement | GroupElement;\n\n// ---------------------------------------------------------------------------\n// Ad-hoc context types used on InteractionStateMachine\n//\n// The InteractionStateMachine is extended at runtime with temporary properties\n// that live only for the duration of a single interaction (drag/resize/rotate).\n// These interfaces describe those properties so we can avoid `as any`.\n// ---------------------------------------------------------------------------\n\n/** Context attached when the user clicks a group child (pending selection on pointerUp) */\ntype PendingChildSelection = ChildElement;\n\n/** Context for pending text edit mode entry (set on pointerDown, consumed on pointerUp) */\ninterface PendingTextEditMode {\n elementId: string;\n originalX: number;\n originalY: number;\n clickX: number;\n clickY: number;\n}\n\n/** Per-element start data stored in multi-selection drag/resize/rotation contexts */\ninterface MultiElementStartData {\n element: CanvasElement;\n startData: {\n x: number;\n y: number;\n width: number;\n height: number;\n rotation: number;\n transformData: ImageTransformData | Record<string, unknown>;\n [key: string]: unknown;\n };\n}\n\n/**\n * Minimum absolute area (in world / artboard pixels) an element\n * must have inside the clipped zone for the focus filter to\n * restrict its hit-area to inside that zone. Roughly a 30×30 world\n * pixel region — large enough to reject sliver overlaps that the\n * user couldn't intentionally tap, small enough that any element\n * with a visible presence in the clipped zone qualifies.\n */\nconst HIT_FILTER_MIN_VISIBLE_AREA = 900;\n\n/**\n * Gate element hit-tests by clipped-zone overlap.\n *\n * If an element has at least `HIT_FILTER_MIN_VISIBLE_AREA` square\n * world-pixels INSIDE the clipped rect, we treat it as \"primarily\n * visible inside\" — taps outside the rect won't select it, so a\n * user can't grab an element from the dim overflow next to it.\n * Below that threshold the overlap is too small to count as\n * \"visibly present in the clipped area\" and we let tap-anywhere\n * behaviour stand (covers neighbour-piece elements that just\n * graze the clipped area).\n *\n * Absolute area instead of a percentage handles the case where the\n * element is huge but the clipped area is small — the percentage\n * rule was missing those because the element's total area dwarfed\n * the overlap.\n *\n * No-op when `focusedPieceRect` is null (no clipped area defined).\n *\n * @internal — exposed for tests; not part of the public canvas API.\n */\nexport function isHitAllowedInFocus(\n element: BaseElement,\n pointX: number,\n pointY: number,\n focusedPieceRect:\n | { x: number; y: number; width: number; height: number }\n | null\n | undefined,\n): boolean {\n if (!focusedPieceRect) return true;\n const bbox = element.getVisualBoundingBox();\n const ix = Math.max(bbox.x, focusedPieceRect.x);\n const iy = Math.max(bbox.y, focusedPieceRect.y);\n const ix2 = Math.min(\n bbox.x + bbox.width,\n focusedPieceRect.x + focusedPieceRect.width,\n );\n const iy2 = Math.min(\n bbox.y + bbox.height,\n focusedPieceRect.y + focusedPieceRect.height,\n );\n const overlapArea = Math.max(0, ix2 - ix) * Math.max(0, iy2 - iy);\n if (overlapArea < HIT_FILTER_MIN_VISIBLE_AREA) return true;\n // Enough of the element sits inside the clipped zone — only honour\n // clicks that also land inside it.\n const insideFocus =\n pointX >= focusedPieceRect.x &&\n pointX <= focusedPieceRect.x + focusedPieceRect.width &&\n pointY >= focusedPieceRect.y &&\n pointY <= focusedPieceRect.y + focusedPieceRect.height;\n return insideFocus;\n}\n\n/** Multi-selection drag context */\ninterface MultiSelectionDragContext {\n startX: number;\n startY: number;\n elementsStartData: MultiElementStartData[];\n currentElements?: Map<string, CanvasElement>;\n currentDelta?: { dx: number; dy: number };\n startOBB?: MultiSelectionOBB;\n}\n\n/** Multi-selection resize context */\ninterface MultiSelectionResizeContext {\n startBounds: { minX: number; maxX: number; minY: number; maxY: number };\n anchor: string;\n elementsStartData: MultiElementStartData[];\n}\n\n/** Multi-selection rotation context */\ninterface MultiSelectionRotationContext {\n startBounds: { centerX: number; centerY: number };\n elementsStartData: MultiElementStartData[];\n currentRotation: number;\n}\n\n/** Extended context data that may contain a cached element map */\ninterface ExtendedInteractionContext {\n mode: string | null;\n dragMode?: string | null;\n startX: number;\n startY: number;\n startData: Record<string, unknown>;\n activeHandle: unknown;\n element: CanvasElement | null;\n _cachedElement?: Record<string, CanvasElement>;\n}\n\n/**\n * Augmented InteractionStateMachine type that includes the temporary ad-hoc\n * properties used during interactions. This avoids `as any` throughout the file.\n */\ntype AugmentedStateMachine = InteractionStateMachine & {\n _pendingChildSelection?: PendingChildSelection;\n _pendingTextEditMode?: PendingTextEditMode;\n _groupDragOriginalElement?: CanvasElement;\n _multiSelectionDragContext?: MultiSelectionDragContext;\n _multiSelectionResizeContext?: MultiSelectionResizeContext;\n _multiSelectionRotationContext?: MultiSelectionRotationContext;\n};\n\n/** PenTool interface - only the methods needed by interaction handlers */\ninterface PenToolLike {\n isActive(): boolean;\n canClosePath(x: number, y: number, threshold: number): boolean;\n startClosing(x: number, y: number): void;\n startDraggingHandle(): void;\n addPoint(x: number, y: number): void;\n isDraggingHandle(): boolean;\n updateHandle(x: number, y: number): void;\n setPreviewPoint(x: number, y: number): void;\n finishDraggingHandle(): void;\n isClosingPath(): boolean;\n editPath(element: PathElement): void;\n finishPath(): void;\n deleteLastPoint(): void;\n}\n\n/** CropController interface - mirrors CropModeController methods */\ninterface CropControllerLike {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- context types from InteractionStateMachine are complex; CropModeController handles narrowing\n handleCropImageDrag(image: BaseElement, context: any, x: number, y: number): {\n x: number; y: number; cropX: number; cropY: number; cropWidth: number; cropHeight: number;\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- context types from InteractionStateMachine are complex; CropModeController handles narrowing\n handleCropBoxResize(image: BaseElement, anchor: string, resizeContext: any, context: any, x: number, y: number): {\n x: number; y: number; rotation: number; width: number; height: number;\n cropX: number; cropY: number; cropWidth: number; cropHeight: number; cursor: string;\n };\n}\n\nexport interface UseCanvasInteractionParams {\n // Refs\n canvasRef: React.RefObject<HTMLCanvasElement | null>;\n inputRef: React.RefObject<HTMLTextAreaElement | null>;\n repositioningCursorRef: React.MutableRefObject<boolean>;\n lastClickTimeRef: React.MutableRefObject<number>;\n clickCountRef: React.MutableRefObject<number>;\n lastProcessedClickTimeRef: React.MutableRefObject<number>;\n lastMultiClickTimeRef: React.MutableRefObject<number>;\n multiClickSelectionRef: React.MutableRefObject<{ start: number; end: number; cursor: number } | null>;\n skipDragSelectionRef: React.MutableRefObject<boolean>;\n draggingSelectionHandleRef: React.MutableRefObject<'start' | 'end' | null>;\n selectionHandlePositionsRef: React.MutableRefObject<{\n start: { center: { x: number; y: number }; textEdge: { x: number; y: number } } | null;\n end: { center: { x: number; y: number }; textEdge: { x: number; y: number } } | null;\n }>;\n lastMousePosRef: React.MutableRefObject<{ x: number; y: number } | null>;\n dragOriginalElementsRef: React.MutableRefObject<Map<string, CanvasElement> | null>;\n rotatingElementRef: React.MutableRefObject<CanvasElement | null>;\n editingChildIdRef: React.MutableRefObject<string | null>;\n editRichTextRef: React.MutableRefObject<RichText | null>;\n editModeEnteredAtRef: React.MutableRefObject<number>;\n multiSelectionGroupBoundsRef: React.MutableRefObject<MultiSelectionGroupBounds | null>;\n elementsRef: React.MutableRefObject<CanvasElement[]>;\n\n // Constants\n MULTI_CLICK_THRESHOLD: number;\n MULTI_CLICK_DEBOUNCE: number;\n CLICK_DEDUP_THRESHOLD: number;\n\n // State values\n isEditing: boolean;\n editText: string;\n isTouchDevice: boolean;\n selectionStart: number;\n selectionEnd: number;\n isRotating: boolean;\n isMarqueeSelecting: boolean;\n isAltKeyPressed: boolean;\n selectedId: string | null;\n selectedElement: CanvasElement | undefined;\n activeChildElement: ChildElement | null;\n elements: CanvasElement[];\n multiSelection: string[];\n multiSelectionOBBRef: React.MutableRefObject<MultiSelectionOBB | null>;\n zoom: number;\n paddingOffsetX: number;\n paddingOffsetY: number;\n spacingIndicators: SpacingIndicator[];\n /**\n * ADR-0060 focused-piece rect (in artboard coords). When set,\n * elements whose visible-bbox sits primarily inside this rect can\n * only be hit-tested by pointer positions also inside the rect —\n * a tap on the dim overflow next to a \"focused\" element no longer\n * selects it. Lets users tap dim regions without accidentally\n * grabbing elements that happen to extend into them.\n */\n focusedPieceRect?: { x: number; y: number; width: number; height: number } | null;\n\n // State setters\n setCursorPosition: React.Dispatch<React.SetStateAction<number>>;\n setSelectionStart: React.Dispatch<React.SetStateAction<number>>;\n setSelectionEnd: React.Dispatch<React.SetStateAction<number>>;\n setIsRotating: (v: boolean) => void;\n setRotationStartAngle: (v: number) => void;\n setCurrentRotation: (v: number) => void;\n setRotatingElementVersion: React.Dispatch<React.SetStateAction<number>>;\n setIsAltKeyPressed: (v: boolean) => void;\n setSnapGuides: (v: SnapGuide[]) => void;\n setSpacingIndicators: (v: SpacingIndicator[]) => void;\n setHandlesVersion: React.Dispatch<React.SetStateAction<number>>;\n setHoverState: (v: Omit<LocalHoverState, 'startTime'>) => void;\n setElements: React.Dispatch<React.SetStateAction<CanvasElement[]>>;\n forceUpdate: (v: object) => void;\n\n // Systems\n stateMachine: AugmentedStateMachine;\n transformHandles: TransformHandles;\n snapSystem: AlignmentSnapSystem;\n spacingSystem: SpacingSystem;\n penTool: PenToolLike;\n cropController: CropControllerLike;\n\n // Callbacks\n onElementUpdate: (element: CanvasElement) => void;\n executeElementUpdate: (oldElement: CanvasElement | undefined, newElement: CanvasElement) => void;\n onSelectionChange: (id: string | null) => void;\n onMultiSelectionChange: (ids: string[]) => void;\n onRotationStateChange?: (isRotating: boolean) => void;\n onCropModeEnter?: () => void;\n updateActiveChild: (child: ChildElement | null) => void;\n getActiveArtboardElements: () => CanvasElement[];\n startMarqueeSelection: (x: number, y: number) => void;\n updateMarqueeSelection: (x: number, y: number, elements: CanvasElement[]) => void;\n finishMarqueeSelection: (onMultiSelectionChange: (ids: string[]) => void) => void;\n enterTextEditMode: (element: TextElement, worldX: number, worldY: number) => void;\n\n // Text editing state/setters for double-click entering edit mode\n setEditPosition: (pos: { x: number; y: number; width: number; height: number }) => void;\n setEditText: (v: string) => void;\n setEditRichText: (v: RichText | null) => void;\n setIsEditing: (v: boolean) => void;\n setCurrentFormattingStyle: React.Dispatch<React.SetStateAction<CharacterStyle | null>>;\n getWrappedTextForEditing: (element: TextElement) => string;\n}\n\nexport interface CanvasInteractionResult {\n handlePointerDown: (e: React.PointerEvent<HTMLCanvasElement>) => void;\n handlePointerMove: (e: React.PointerEvent<HTMLCanvasElement>) => void;\n handlePointerUp: (e: React.PointerEvent<HTMLCanvasElement>) => void;\n handleDoubleClick: (e: React.MouseEvent<HTMLCanvasElement>) => void;\n}\n\nexport function useCanvasInteraction(params: UseCanvasInteractionParams): CanvasInteractionResult {\n const {\n canvasRef,\n inputRef,\n repositioningCursorRef,\n lastClickTimeRef,\n clickCountRef,\n lastProcessedClickTimeRef,\n lastMultiClickTimeRef,\n multiClickSelectionRef,\n skipDragSelectionRef,\n draggingSelectionHandleRef,\n selectionHandlePositionsRef,\n lastMousePosRef,\n dragOriginalElementsRef,\n rotatingElementRef,\n editingChildIdRef,\n editRichTextRef,\n editModeEnteredAtRef,\n multiSelectionGroupBoundsRef,\n MULTI_CLICK_THRESHOLD,\n MULTI_CLICK_DEBOUNCE,\n CLICK_DEDUP_THRESHOLD,\n isEditing,\n editText,\n isTouchDevice,\n selectionStart,\n selectionEnd,\n isRotating,\n isMarqueeSelecting,\n isAltKeyPressed,\n selectedElement,\n activeChildElement,\n elements,\n multiSelection,\n multiSelectionOBBRef,\n zoom,\n paddingOffsetX,\n paddingOffsetY,\n spacingIndicators,\n focusedPieceRect,\n setCursorPosition,\n setSelectionStart,\n setSelectionEnd,\n setIsRotating,\n setRotationStartAngle,\n setCurrentRotation,\n setRotatingElementVersion,\n setIsAltKeyPressed,\n setSnapGuides,\n setSpacingIndicators,\n setHandlesVersion,\n setHoverState,\n setElements,\n forceUpdate,\n stateMachine,\n transformHandles,\n snapSystem,\n spacingSystem,\n penTool,\n cropController,\n onElementUpdate,\n executeElementUpdate,\n onSelectionChange,\n onMultiSelectionChange,\n onRotationStateChange,\n onCropModeEnter,\n updateActiveChild,\n getActiveArtboardElements,\n startMarqueeSelection,\n updateMarqueeSelection,\n finishMarqueeSelection,\n enterTextEditMode,\n setEditPosition,\n setEditText,\n setEditRichText,\n setIsEditing,\n setCurrentFormattingStyle,\n getWrappedTextForEditing,\n } = params;\n\n // Cache canvas bounding rect to avoid forced layout on every pointermove event.\n // The document-level pointermove handler fires hundreds of times per second during\n // drag, so calling getBoundingClientRect() inline causes continuous forced reflow.\n // Cache is updated on scroll, resize, and zoom change instead.\n const canvasBoundsRef = useRef<DOMRect | null>(null);\n\n // Multi-touch pinch tracking. The Pointer Events API delivers each\n // finger as its own pointerId — we collect the live world position\n // of every active pointer here. When a second pointer lands while\n // an element is selected, the pointer-down handler enters the\n // state machine's PINCHING mode and snapshots which two pointer\n // ids drive the gesture (handle further fingers gracefully — they\n // get tracked but ignored). On pointer-up we drop the entry; the\n // last-of-the-two leaving ends pinch.\n // Tracks every active pointer's positions. World coords (x/y) are\n // used by the element-resize pinch (matches the rest of the canvas\n // hit-test math); screen coords (clientX/clientY) are used by the\n // canvas-zoom pinch (works directly in CSS pixels relative to the\n // viewport, no zoom/pan round-trip needed).\n const activePointersRef = useRef<\n Map<number, { x: number; y: number; clientX: number; clientY: number }>\n >(new Map());\n const pinchPointerIdsRef = useRef<{ a: number; b: number } | null>(null);\n // Capture the original element snapshot at gesture start so the\n // commit step has a `before` to pass into `executeElementUpdate`\n // for clean undo/redo.\n const pinchOriginalElementRef = useRef<BaseElement | null>(null);\n\n // What selection existed BEFORE the user started touching the canvas.\n // Captured on the very first pointerdown of a gesture (when\n // `activePointersRef.size` is about to go from 0 → 1) and used by\n // the pinch entry path to decide: pinch resizes only when the user\n // had ALREADY selected an element prior to the gesture. If the\n // first pointer auto-selected something and a second pointer\n // arrives right after, that's a zoom-pinch on an unselected\n // element — revert the auto-select instead of resizing.\n const selectedIdAtGestureStartRef = useRef<string | null>(null);\n\n // When the user pointer-downs on the currently-selected element,\n // we tag this id as a \"deselect candidate\". Cleared if the gesture\n // turns into a drag (movement past threshold) or a multi-touch\n // pinch. On pointerup with no movement, the candidate fires and\n // we deselect — closes the \"tap to deselect\" loop. Skipped for\n // text elements (their tap-on-selected already enters edit mode).\n const pendingDeselectIdRef = useRef<string | null>(null);\n const pendingDeselectStartPosRef = useRef<{ x: number; y: number } | null>(null);\n\n // Canvas-zoom pinch state. When the user pinches on empty canvas\n // (or an unselected element — see Fix #1) we DON'T resize an\n // element; we zoom + pan the viewport. We capture the start state\n // here and apply scale + centroid drift on each pointermove.\n const canvasZoomPinchRef = useRef<{\n pointerA: number;\n pointerB: number;\n startUserZoom: number;\n startPanOffset: { x: number; y: number };\n startCentroidClient: { x: number; y: number };\n startDistance: number;\n /** Container's screen-left at gesture start, used to convert\n * cursor clientX into container-relative pixels for the pan\n * formula. Reading the container rect once at start avoids\n * per-frame layout churn. */\n containerRect: { left: number; top: number };\n } | null>(null);\n\n // Single-finger pan state. Active when the user is zoomed in\n // (userZoom > 1) and starts a pointer drag on empty canvas. At\n // fit zoom there's nothing to pan to, so we fall back to the\n // existing marquee-select behaviour and don't enter this mode.\n const singleFingerPanRef = useRef<{\n pointerId: number;\n startClientX: number;\n startClientY: number;\n startPanOffset: { x: number; y: number };\n /** Movement threshold (screen px). Until exceeded, the gesture\n * could still resolve as a tap rather than a drag, so we hold\n * off committing pan updates AND keep `pendingTap` live. */\n movedPastThreshold: boolean;\n /** What this gesture *would* fire on pointerup if it stays a\n * tap (no movement past threshold). When the gesture lands on\n * an unselected element at zoom > 1, we record the selection\n * intent here so a tap-to-select still works while a real\n * drag becomes a pan. `null` means \"this was a pan over empty\n * space, no tap action\". */\n pendingTap:\n | { kind: 'select-element'; elementId: string; x: number; y: number; shiftKey: boolean }\n | { kind: 'deselect' }\n | null;\n } | null>(null);\n\n const { userZoom, setUserZoom, panOffset, setPanOffset } = useViewportContext();\n const userZoomRef = useRef(userZoom);\n userZoomRef.current = userZoom;\n const panOffsetRef = useRef(panOffset);\n panOffsetRef.current = panOffset;\n\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n const updateBounds = () => {\n canvasBoundsRef.current = canvas.getBoundingClientRect();\n };\n updateBounds();\n window.addEventListener('scroll', updateBounds, { passive: true, capture: true });\n window.addEventListener('resize', updateBounds, { passive: true });\n return () => {\n window.removeEventListener('scroll', updateBounds, { capture: true });\n window.removeEventListener('resize', updateBounds);\n };\n }, [canvasRef]);\n\n // Re-cache when zoom changes — that resizes the canvas and shifts\n // its position. We deliberately do NOT depend on `panOffset` here:\n // a single-finger pan can fire panOffset updates 60×/sec, and a\n // synchronous `getBoundingClientRect` per update causes forced\n // reflows under sustained drag — observable as iOS Safari memory\n // pressure / spontaneous page refresh. `handlePointerDown`\n // explicitly refreshes the cache on every pointerdown, which\n // covers the \"drag-after-pan-without-releasing\" race that\n // originally motivated panOffset's inclusion.\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n canvasBoundsRef.current = canvas.getBoundingClientRect();\n }, [zoom, canvasRef]);\n\n // ============================================\n // POINTER DOWN\n // ============================================\n const handlePointerDown = useCallback(\n (e: React.PointerEvent<HTMLCanvasElement>) => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n const rect = canvas.getBoundingClientRect();\n // Sync the move-handler's bounds cache to the same fresh rect\n // pointer-down is using, so the first pointer-move can't end up\n // computing world coords against a stale cached rect (which\n // surfaced as an element-jump-on-drag-start after pan/pinch).\n canvasBoundsRef.current = rect;\n const x = (e.clientX - rect.left) / zoom - paddingOffsetX;\n const y = (e.clientY - rect.top) / zoom - paddingOffsetY;\n\n // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n // PINCH GESTURE — second-pointer interception\n // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n // Track every active pointer's world position. When the second\n // pointer lands and an element is selected (and we're not in a\n // modal mode like text-edit or crop), abandon the existing\n // single-pointer flow and start a pinch gesture. This means\n // two-finger gestures take over from corner-handle dragging\n // for resize on touch devices — natural UX, matches iOS /\n // native apps.\n //\n // BEFORE inserting into the active set, capture what was\n // selected when the gesture began. The pinch entry below uses\n // this to distinguish \"pinch on already-selected element\"\n // (resize) from \"pinch landed on an element while unselected\"\n // (zoom-pinch — should NOT auto-select + resize).\n if (activePointersRef.current.size === 0) {\n selectedIdAtGestureStartRef.current = selectedElement?.id ?? null;\n }\n activePointersRef.current.set(e.pointerId, {\n x,\n y,\n clientX: e.clientX,\n clientY: e.clientY,\n });\n\n const isModalMode =\n isEditing ||\n (selectedElement instanceof ImageElement && selectedElement.isCropping) ||\n penTool.isActive();\n\n // ━━━ Two-finger gesture routing ━━━\n // Element-resize pinch only fires when:\n // 1) An element was selected BEFORE the gesture (not auto-\n // selected by the first pointerdown — Fix #1), AND\n // 2) At least one finger landed ON the selected element's\n // hit area.\n // Otherwise the gesture is a canvas-zoom pinch (pan + zoom\n // the viewport). This lets users always pan with two fingers\n // by pinching in empty space, even when something is selected.\n if (activePointersRef.current.size === 2 && !isModalMode) {\n const ids = Array.from(activePointersRef.current.keys());\n const a = activePointersRef.current.get(ids[0])!;\n const b = activePointersRef.current.get(ids[1])!;\n const wasSelectedBefore = selectedIdAtGestureStartRef.current !== null;\n const hitSelectedElement =\n wasSelectedBefore &&\n selectedElement &&\n !selectedElement.locked &&\n (selectedElement.hitTest(a.x, a.y) || selectedElement.hitTest(b.x, b.y));\n\n if (hitSelectedElement) {\n // Element-resize pinch. Existing path; falls through to the\n // block below for the actual `startPinch` call.\n } else {\n // Canvas-zoom pinch. If the first pointerdown auto-selected\n // and started dragging an element, undo that side-effect:\n // restore the element to its pre-drag position, clear the\n // selection, kill the drag, drop pending tap-side-effects.\n if (dragOriginalElementsRef.current && dragOriginalElementsRef.current.size > 0) {\n const originals = dragOriginalElementsRef.current;\n setElements((prev) =>\n prev.map((el) => {\n const original = originals.get(el.id);\n return original ? (original.clone() as typeof el) : el;\n }),\n );\n dragOriginalElementsRef.current = null;\n }\n if (!wasSelectedBefore && selectedElement) {\n onSelectionChange(null);\n }\n if (stateMachine.isInteracting()) {\n stateMachine.end();\n }\n // Critical: kill any in-flight single-finger pan from the\n // first pointer. Without this, finger 1's pointermoves\n // continue to drive `panOffset` *in addition to* the pinch\n // handler updating it, producing jitter and runaway pan\n // (the symptom: pinch jumps and content disappears).\n singleFingerPanRef.current = null;\n pendingDeselectIdRef.current = null;\n pendingDeselectStartPosRef.current = null;\n delete stateMachine._pendingTextEditMode;\n delete stateMachine._pendingChildSelection;\n\n const dx = b.clientX - a.clientX;\n const dy = b.clientY - a.clientY;\n const distance = Math.hypot(dx, dy);\n const container =\n (canvas.parentElement?.parentElement as HTMLElement | null) ?? canvas;\n const containerRect = container.getBoundingClientRect();\n canvasZoomPinchRef.current = {\n pointerA: ids[0],\n pointerB: ids[1],\n startUserZoom: userZoomRef.current,\n startPanOffset: { ...panOffsetRef.current },\n startCentroidClient: {\n x: (a.clientX + b.clientX) / 2,\n y: (a.clientY + b.clientY) / 2,\n },\n startDistance: Math.max(distance, 1),\n containerRect: { left: containerRect.left, top: containerRect.top },\n };\n try {\n canvas.setPointerCapture(e.pointerId);\n } catch {\n /* non-fatal */\n }\n e.preventDefault();\n return;\n }\n }\n\n if (\n activePointersRef.current.size === 2 &&\n selectedElement &&\n !selectedElement.locked &&\n !isModalMode\n ) {\n // If a single-pointer interaction is in progress, commit it\n // as its own undo step before pinch starts. The user's\n // mental model is \"I dragged here, then pinched\" — splitting\n // those into two undo steps is fine and avoids splicing two\n // unrelated geometry deltas into one history entry.\n if (stateMachine.isInteracting()) {\n const ctx = stateMachine.getContext();\n if (ctx.element) {\n // The state machine holds the cloned element at its\n // current (live-updated) position. The matching element\n // in the elements array is the `before` snapshot.\n const original = getActiveArtboardElements().find(\n (el) => el.id === ctx.element!.id,\n );\n executeElementUpdate(original, ctx.element as CanvasElement);\n }\n stateMachine.end();\n }\n\n const ids = Array.from(activePointersRef.current.keys());\n pinchPointerIdsRef.current = { a: ids[0], b: ids[1] };\n const p1 = activePointersRef.current.get(ids[0])!;\n const p2 = activePointersRef.current.get(ids[1])!;\n\n // Pinch supersedes a tap — no deselect should fire when the\n // gesture finishes. Also kill any in-flight single-finger\n // pan from the first pointer so it doesn't drive panOffset\n // in parallel with the pinch resize.\n pendingDeselectIdRef.current = null;\n pendingDeselectStartPosRef.current = null;\n singleFingerPanRef.current = null;\n\n pinchOriginalElementRef.current = selectedElement.clone();\n stateMachine.startPinch(selectedElement, p1, p2);\n\n try {\n canvas.setPointerCapture(e.pointerId);\n } catch {\n // setPointerCapture can throw if the pointer is already\n // captured by another element — non-fatal, the document-\n // level pointermove listeners will still receive events.\n }\n e.preventDefault();\n return;\n }\n // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n // Handle clicks while editing text\n if (isEditing && selectedElement instanceof TextElement) {\n // Check if tapping on a selection handle first (iOS-style selection adjustment)\n if (isTouchDevice && selectionStart !== selectionEnd) {\n const handlePositions = selectionHandlePositionsRef.current;\n if (handlePositions.start || handlePositions.end) {\n const rotationRad = RotationUtils.toRadiansInverse(selectedElement.rotation);\n const center = selectedElement.getRotationAnchor();\n const dx = x - center.x;\n const dy = y - center.y;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n const localRotatedX = dx * cos - dy * sin;\n const localRotatedY = dx * sin + dy * cos;\n const localX = localRotatedX;\n const localY = localRotatedY;\n\n const hitRadius = 44 / zoom;\n\n if (handlePositions.start) {\n const distStart = Math.sqrt(\n Math.pow(localX - handlePositions.start.center.x, 2) +\n Math.pow(localY - handlePositions.start.center.y, 2)\n );\n if (distStart <= hitRadius) {\n e.preventDefault();\n draggingSelectionHandleRef.current = 'start';\n inputRef.current?.focus();\n return;\n }\n }\n\n if (handlePositions.end) {\n const distEnd = Math.sqrt(\n Math.pow(localX - handlePositions.end.center.x, 2) +\n Math.pow(localY - handlePositions.end.center.y, 2)\n );\n if (distEnd <= hitRadius) {\n e.preventDefault();\n draggingSelectionHandleRef.current = 'end';\n inputRef.current?.focus();\n return;\n }\n }\n }\n }\n\n // Check if clicking on the same text element - if so, reposition cursor\n if (selectedElement.hitTest(x, y)) {\n e.preventDefault();\n repositioningCursorRef.current = true;\n\n const visualBbox = selectedElement.getVisualBoundingBox();\n const rotationRad = RotationUtils.toRadiansInverse(selectedElement.rotation);\n const center = selectedElement.getRotationAnchor();\n const dx = x - center.x;\n const dy = y - center.y;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n const localRotatedX = dx * cos - dy * sin;\n const localRotatedY = dx * sin + dy * cos;\n const relativeX = localRotatedX + visualBbox.width / 2;\n const relativeY = localRotatedY + visualBbox.height / 2;\n\n const clickIndex = getCursorIndexFromPoint(selectedElement, { x: relativeX, y: relativeY });\n\n const now = Date.now();\n\n if (now - lastProcessedClickTimeRef.current < CLICK_DEDUP_THRESHOLD) {\n inputRef.current?.focus();\n return;\n }\n lastProcessedClickTimeRef.current = now;\n\n if (now - lastMultiClickTimeRef.current >= MULTI_CLICK_DEBOUNCE) {\n multiClickSelectionRef.current = null;\n }\n\n const timeSinceLastClick = now - lastClickTimeRef.current;\n\n if (timeSinceLastClick < MULTI_CLICK_THRESHOLD) {\n clickCountRef.current += 1;\n } else {\n clickCountRef.current = 1;\n }\n lastClickTimeRef.current = now;\n\n const currentText = editText;\n\n if (clickCountRef.current >= 3) {\n e.preventDefault();\n lastMultiClickTimeRef.current = now;\n multiClickSelectionRef.current = { start: 0, end: currentText.length, cursor: currentText.length };\n skipDragSelectionRef.current = true;\n clickCountRef.current = 0;\n setCursorPosition(currentText.length);\n setSelectionStart(0);\n setSelectionEnd(currentText.length);\n if (inputRef.current) {\n inputRef.current.focus();\n inputRef.current.setSelectionRange(0, currentText.length);\n }\n } else if (clickCountRef.current === 2) {\n e.preventDefault();\n lastMultiClickTimeRef.current = now;\n skipDragSelectionRef.current = true;\n const wordBounds = getWordBoundaryAt(currentText, clickIndex);\n multiClickSelectionRef.current = { start: wordBounds.start, end: wordBounds.end, cursor: wordBounds.end };\n setCursorPosition(wordBounds.end);\n setSelectionStart(wordBounds.start);\n setSelectionEnd(wordBounds.end);\n if (inputRef.current) {\n inputRef.current.focus();\n inputRef.current.setSelectionRange(wordBounds.start, wordBounds.end);\n }\n } else {\n multiClickSelectionRef.current = null;\n setCursorPosition(clickIndex);\n setSelectionStart(clickIndex);\n setSelectionEnd(clickIndex);\n if (inputRef.current) {\n inputRef.current.focus();\n inputRef.current.setSelectionRange(clickIndex, clickIndex);\n }\n skipDragSelectionRef.current = false;\n }\n return;\n }\n multiClickSelectionRef.current = null;\n inputRef.current?.blur();\n return;\n }\n\n if (isEditing) return;\n\n (e.target as HTMLCanvasElement).setPointerCapture(e.pointerId);\n\n // Handle pen tool mode\n if (penTool.isActive() && selectedElement instanceof PathElement) {\n const relX = x - selectedElement.x;\n const relY = y - selectedElement.y;\n\n if (penTool.canClosePath(relX, relY, 15)) {\n penTool.startClosing(relX, relY);\n penTool.startDraggingHandle();\n setHandlesVersion((v) => v + 1);\n return;\n }\n\n penTool.addPoint(relX, relY);\n penTool.startDraggingHandle();\n setHandlesVersion((v) => v + 1);\n return;\n }\n\n // Check multi-selection group bounds handles\n if (multiSelection.length > 0 && multiSelectionGroupBoundsRef.current) {\n const bounds = multiSelectionGroupBoundsRef.current;\n\n const hitResult = hitTestMultiSelection({ x, y, bounds, multiSelection, elements, zoom });\n\n if (hitResult.type === 'corner-handle') {\n const success = startMultiSelectionResize({\n multiSelection,\n elements,\n bounds,\n anchor: hitResult.anchor,\n corner: hitResult.corner,\n x,\n y,\n stateMachine,\n });\n if (success) {\n forceUpdate({});\n return;\n }\n } else if (hitResult.type === 'rotation-handle') {\n const success = startMultiSelectionRotate({\n multiSelection,\n elements,\n bounds,\n x,\n y,\n stateMachine,\n setIsRotating,\n setRotationStartAngle,\n setCurrentRotation,\n });\n if (success) {\n forceUpdate({});\n return;\n }\n } else if (hitResult.type === 'bounds-drag') {\n const success = startMultiSelectionDrag({\n multiSelection,\n elements,\n x,\n y,\n stateMachine,\n dragOriginalElementsRef,\n setHoverState,\n });\n if (success) {\n forceUpdate({});\n return;\n }\n }\n }\n\n const workingElement = activeChildElement || selectedElement;\n\n // Locked workingElement → skip the entire rotation / resize /\n // drag-on-selected branch and fall through to the single-finger\n // pan + tap-to-cycle path. This makes a locked layer behave\n // \"like nothing is selected\" for gesture routing (pan / zoom),\n // while still letting the user see the selection chip and lock\n // toggle. Tapping pans, pinching zooms, repeated taps cycle\n // through stacked elements (handled by my pendingTap branch).\n if (workingElement && workingElement.visible !== false && !workingElement.locked && (multiSelection.length === 0 || activeChildElement)) {\n // Handle crop mode interactions\n const cropContext = findCroppingImage({ selectedElement: selectedElement || null, activeChildElement });\n if (cropContext) {\n const cropHitResult = hitTestCropMode({ x, y, cropContext, transformHandles, zoom });\n\n if (cropHitResult.type === 'crop-handle') {\n handleCropHandleClick({\n cropContext,\n cropHandle: cropHitResult.handle,\n x,\n y,\n selectedElement: selectedElement || null,\n activeChildElement,\n stateMachine,\n });\n forceUpdate({});\n return;\n } else if (cropHitResult.type === 'image-handle') {\n handleImageHandleClick({\n cropContext,\n imageHandle: cropHitResult.handle,\n x,\n y,\n selectedElement: selectedElement || null,\n activeChildElement,\n stateMachine,\n });\n forceUpdate({});\n return;\n } else if (cropHitResult.type === 'image-drag') {\n handleImageDragClick({\n cropContext,\n x,\n y,\n selectedElement: selectedElement || null,\n activeChildElement,\n stateMachine,\n dragOriginalElementsRef,\n setHoverState,\n });\n forceUpdate({});\n return;\n } else if (cropHitResult.type === 'exit-crop') {\n exitCropMode({\n cropContext,\n selectedElement: selectedElement || null,\n onElementUpdate,\n transformHandles,\n updateActiveChild,\n zoom,\n });\n setHandlesVersion((v) => v + 1);\n return;\n }\n }\n\n // Check rotation handle\n if (transformHandles.hitTestRotation(x, y, zoom)) {\n if (workingElement.locked) return;\n\n const bbox = workingElement.getBoundingBox();\n const center = getBoundingBoxCenter(bbox);\n const startAngle = calculateAngle(center.x, center.y, x, y);\n\n setIsRotating(true);\n setRotationStartAngle(startAngle);\n setCurrentRotation(workingElement.rotation);\n stateMachine.startRotate(workingElement, x, y, startAngle, workingElement.rotation);\n\n if (activeChildElement && selectedElement instanceof GroupElement) {\n stateMachine._activeChildContext = {\n group: selectedElement,\n childId: activeChildElement.id,\n };\n }\n\n // A tap (no movement past threshold) on the rotation handle\n // should still count as a tap for the cycle/deselect logic.\n // For small elements the handles extend well outside the\n // element's hit area; without this, those taps would just\n // get eaten by the handle hit-test and never reach pointer-\n // up's cycle path.\n pendingDeselectIdRef.current = workingElement.id;\n pendingDeselectStartPosRef.current = { x, y };\n\n forceUpdate({});\n return;\n }\n\n // Check resize handles\n const handle = transformHandles.hitTestResize(x, y, zoom);\n if (handle) {\n if (workingElement.locked) return;\n\n const bbox = workingElement.getBoundingBox();\n const visualBbox = workingElement.getVisualBoundingBox();\n stateMachine.startResize(workingElement, handle, x, y, bbox, visualBbox);\n\n // Same as the rotation handle: tap-without-drag on a resize\n // handle should fall through to the cycle/deselect logic on\n // pointerup. Resize itself fires only when the pointer moves\n // past the threshold.\n pendingDeselectIdRef.current = workingElement.id;\n pendingDeselectStartPosRef.current = { x, y };\n\n if (activeChildElement && selectedElement instanceof GroupElement) {\n stateMachine._activeChildContext = {\n group: selectedElement,\n childId: activeChildElement.id,\n };\n }\n\n forceUpdate({});\n return;\n }\n\n // Check if clicking on element itself (for drag).\n // No `isHitAllowedInFocus` gate here, unlike the cycle/select\n // paths: once an element is already selected, the user has\n // explicitly committed to it, and the 30%-blur overflow halo\n // is rendered specifically to let them grab the element from\n // outside the piece's clipped area. The focus filter is for\n // disambiguating *which* element to select among a stack —\n // it shouldn't block dragging the one the user already chose.\n if (workingElement.hitTest(x, y)) {\n if (workingElement.locked) return;\n\n // (We previously had a \"higher-element handoff\" here that\n // would auto-select the topmost visible element above the\n // selected one when the user clicked on a stack. That\n // intercepts the tap before the cycle in pointerup runs,\n // and short-circuits a [A, B, C] stack into A↔B oscillation\n // — the cycle never reaches C. Cycling-down via\n // `pendingDeselect`'s cycle handler is now the single\n // source of truth for tap-on-stack semantics. To grab a\n // hidden-under-stack element, the user taps to cycle.)\n\n // If selected element is a group without an active child, store pending child selection\n if (selectedElement instanceof GroupElement && !activeChildElement) {\n const clickedChild = selectedElement.hitTestChild(x, y);\n if (clickedChild) {\n stateMachine._pendingChildSelection = clickedChild;\n }\n }\n\n // Tap on already-selected non-text element → tag for\n // deselect on pointerup (cleared if the user drags). For\n // text elements the existing pending-text-edit path takes\n // precedence — tap-on-selected-text enters edit mode.\n if (\n selectedElement &&\n workingElement.id === selectedElement.id &&\n !(workingElement instanceof TextElement)\n ) {\n pendingDeselectIdRef.current = workingElement.id;\n pendingDeselectStartPosRef.current = { x, y };\n }\n\n // If clicking on an already-selected text element, store pending edit mode\n if (\n workingElement instanceof TextElement &&\n !(workingElement instanceof GroupElement) &&\n selectedElement &&\n workingElement.id === selectedElement.id &&\n !isEditing\n ) {\n stateMachine._pendingTextEditMode = {\n elementId: workingElement.id,\n originalX: workingElement.x,\n originalY: workingElement.y,\n clickX: x,\n clickY: y,\n };\n }\n\n stateMachine.startDrag(workingElement, x, y);\n\n dragOriginalElementsRef.current = new Map();\n dragOriginalElementsRef.current.set(workingElement.id, workingElement.clone());\n\n setHoverState({ type: null, data: null });\n\n if (workingElement instanceof GroupElement) {\n stateMachine._groupDragOriginalElement = workingElement.clone();\n }\n\n if (activeChildElement && selectedElement instanceof GroupElement) {\n stateMachine._activeChildContext = {\n group: selectedElement,\n childId: activeChildElement.id,\n };\n }\n\n forceUpdate({});\n return;\n }\n }\n\n // Handle group child selection\n if (activeChildElement && selectedElement instanceof GroupElement) {\n const siblingClicked = handleSiblingClick({\n x,\n y,\n activeChildElement,\n selectedElement,\n updateActiveChild,\n transformHandles,\n setHandlesVersion,\n stateMachine,\n dragOriginalElementsRef,\n setHoverState,\n forceUpdate,\n zoom,\n });\n if (siblingClicked) return;\n\n exitActiveChildMode({ updateActiveChild, selectedElement: selectedElement || null, transformHandles, setHandlesVersion, zoom });\n return;\n }\n\n if (activeChildElement) {\n exitActiveChildMode({ updateActiveChild, selectedElement: selectedElement || null, transformHandles, setHandlesVersion, zoom });\n return;\n }\n\n // Single-finger pan also covers landing on an *unselected*\n // element. Auto-select + drag was exactly the unintentional\n // move users were reporting. Drag on the *already-selected*\n // element above (workingElement.hitTest branch) is unchanged,\n // so explicit tap-then-drag on a logo still moves it.\n //\n // Tap-to-select still works because we record the would-be\n // selection in `pendingTap` and only commit a pan once the\n // pointer moves past a small threshold; a true tap commits\n // the selection on pointerup instead.\n //\n // Applies at ALL zoom levels for consistency — at fit zoom a\n // pan is just a no-op visually, but the user still doesn't\n // get an unintended element grab. Shift-drag bypasses this so\n // shift-click multi-select keeps working on desktop.\n if (!e.shiftKey) {\n // Hit-test under the cursor and collect ALL hits so we can\n // cycle through stacked elements on repeated taps. Locked\n // elements are still selectable — they just can't be\n // manipulated (gesture routing falls through to pan/zoom\n // when a locked element is the current selection).\n const activeArtboardElements = getActiveArtboardElements();\n const hits: typeof activeArtboardElements = [];\n for (let i = activeArtboardElements.length - 1; i >= 0; i--) {\n const el = activeArtboardElements[i];\n if (el.visible === false) continue;\n if (!el.hitTest(x, y)) continue;\n if (!isHitAllowedInFocus(el, x, y, focusedPieceRect)) continue;\n hits.push(el);\n }\n // Resolve the pendingTap action by walking the cycle:\n // no current selection in hits → topmost hit\n // current is in hits, more below → next one down\n // current is in hits, at the bottom → deselect (don't wrap\n // back to top — feels\n // like \"I'm done with\n // this stack\")\n // no hits, something is selected → deselect (tap empty)\n // no hits, nothing selected → no-op\n let pendingTap: NonNullable<typeof singleFingerPanRef.current>['pendingTap'] = null;\n if (hits.length === 0) {\n if (selectedElement) pendingTap = { kind: 'deselect' };\n } else {\n const currentIdx = selectedElement\n ? hits.findIndex((el) => el.id === selectedElement.id)\n : -1;\n if (currentIdx === -1) {\n pendingTap = {\n kind: 'select-element',\n elementId: hits[0].id,\n x,\n y,\n shiftKey: e.shiftKey,\n };\n } else if (currentIdx + 1 < hits.length) {\n pendingTap = {\n kind: 'select-element',\n elementId: hits[currentIdx + 1].id,\n x,\n y,\n shiftKey: e.shiftKey,\n };\n } else {\n pendingTap = { kind: 'deselect' };\n }\n }\n singleFingerPanRef.current = {\n pointerId: e.pointerId,\n startClientX: e.clientX,\n startClientY: e.clientY,\n startPanOffset: { ...panOffsetRef.current },\n movedPastThreshold: false,\n pendingTap,\n };\n try {\n canvas.setPointerCapture(e.pointerId);\n } catch {\n /* non-fatal */\n }\n e.preventDefault();\n return;\n }\n\n // Handle element selection\n const activeArtboardElements = getActiveArtboardElements();\n const elementClicked = handleElementSelection({\n x,\n y,\n shiftKey: e.shiftKey,\n activeArtboardElements,\n selectedElement: selectedElement || null,\n multiSelection,\n onSelectionChange,\n onMultiSelectionChange,\n stateMachine,\n dragOriginalElementsRef,\n setHoverState,\n forceUpdate,\n });\n if (elementClicked) return;\n\n // Clicking on empty space.\n // - Zoomed in (userZoom > 1): start a single-finger pan instead\n // of marquee. The user wants to navigate; marquee select on a\n // zoomed-in canvas would mostly miss elements anyway.\n // - At fit (userZoom ≤ 1): existing marquee select behaviour,\n // panning is a no-op there since the whole canvas is already\n // visible.\n const isProtected = clickProtection.shouldProtectClick();\n const shouldPreserve = shouldPreserveSelection(e.target);\n if (!isProtected && !shouldPreserve) {\n if (userZoomRef.current > 1.001) {\n // Don't deselect — preserving selection while panning means\n // the user can keep their selected element + pan to see\n // another part of the design. No `pendingTap` here because\n // there's nothing under the finger to select.\n singleFingerPanRef.current = {\n pointerId: e.pointerId,\n startClientX: e.clientX,\n startClientY: e.clientY,\n startPanOffset: { ...panOffsetRef.current },\n movedPastThreshold: false,\n pendingTap: null,\n };\n try {\n canvas.setPointerCapture(e.pointerId);\n } catch {\n /* non-fatal */\n }\n e.preventDefault();\n } else {\n onSelectionChange(null);\n onMultiSelectionChange([]);\n startMarqueeSelection(x, y);\n }\n }\n },\n [\n isEditing,\n selectedElement,\n getActiveArtboardElements,\n onSelectionChange,\n onMultiSelectionChange,\n transformHandles,\n activeChildElement,\n multiSelection,\n stateMachine,\n penTool,\n executeElementUpdate,\n setHandlesVersion,\n zoom,\n setCursorPosition,\n setSelectionStart,\n setSelectionEnd,\n // Canvas-zoom pinch entry uses these setters / refs.\n canvasRef,\n setElements,\n focusedPieceRect,\n ]\n );\n\n // ============================================\n // POINTER MOVE\n // ============================================\n const handlePointerMove = useCallback(\n (e: React.PointerEvent<HTMLCanvasElement>) => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n const rect = canvasBoundsRef.current ?? canvas.getBoundingClientRect();\n const x = (e.clientX - rect.left) / zoom - paddingOffsetX;\n const y = (e.clientY - rect.top) / zoom - paddingOffsetY;\n\n lastMousePosRef.current = { x, y };\n\n // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n // PINCH GESTURE — track + apply\n // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n // Always update the live pointer position, whether or not we're\n // currently pinching. (A third finger landed before pinch\n // started, say, would still want its position tracked so when\n // the user lifts a finger we have an up-to-date map.)\n if (activePointersRef.current.has(e.pointerId)) {\n activePointersRef.current.set(e.pointerId, {\n x,\n y,\n clientX: e.clientX,\n clientY: e.clientY,\n });\n }\n\n // Canvas-zoom pinch update — applies scale + pan to the\n // viewport so the world point under the gesture's start\n // centroid stays stable as fingers move. Checked BEFORE the\n // single-finger pan branch as a safety net: if both refs\n // somehow ended up populated (e.g. timing oddity), the pinch\n // wins. The pinch entry path itself also clears\n // `singleFingerPanRef`, so this should normally be redundant.\n if (canvasZoomPinchRef.current) {\n const z = canvasZoomPinchRef.current;\n const a = activePointersRef.current.get(z.pointerA);\n const b = activePointersRef.current.get(z.pointerB);\n if (a && b) {\n const dx = b.clientX - a.clientX;\n const dy = b.clientY - a.clientY;\n const currentDistance = Math.hypot(dx, dy);\n const scale = currentDistance / z.startDistance;\n const newUserZoom = Math.min(\n Math.max(z.startUserZoom * scale, 0.25),\n 8,\n );\n // Centroid in container-relative pixels (pre-transform\n // wrapper origin). Same coord frame `panOffset` is applied\n // in, so the formula below stays consistent.\n const startCentroidContainerX =\n z.startCentroidClient.x - z.containerRect.left;\n const startCentroidContainerY =\n z.startCentroidClient.y - z.containerRect.top;\n const currentCentroidContainerX =\n (a.clientX + b.clientX) / 2 - z.containerRect.left;\n const currentCentroidContainerY =\n (a.clientY + b.clientY) / 2 - z.containerRect.top;\n // Effective-zoom ratio drives how much the canvas content\n // grows; calculated via userZoom because calculatedZoom\n // (auto-fit) doesn't change during a gesture.\n const ratio = newUserZoom / z.startUserZoom;\n // Combined formula:\n // (a) zoom around the gesture's start centroid (keep that\n // world point under that screen point as scale changes),\n // (b) pan by the centroid's drift since gesture start.\n const rawNewPanX =\n currentCentroidContainerX -\n (startCentroidContainerX - z.startPanOffset.x) * ratio;\n const rawNewPanY =\n currentCentroidContainerY -\n (startCentroidContainerY - z.startPanOffset.y) * ratio;\n // Clamp panOffset so the canvas always overlaps the\n // container by at least `minVisible` px in each direction.\n // Without this, pinching past the user-zoom max cap (8×)\n // keeps drifting the centroid → panOffset grows unbounded\n // and content vanishes off-screen.\n //\n // The canvas DOM at this moment is at `userZoomRef.current`;\n // the impending `setUserZoom(newUserZoom)` will scale it\n // by `growthRatio`. We predict the post-render canvas dims\n // and clamp the new pan so the canvas stays anchored in\n // view at those predicted dims.\n const canvas2 = canvasRef.current;\n if (canvas2) {\n const canvasRect = canvas2.getBoundingClientRect();\n const containerEl = canvas2.parentElement?.parentElement;\n const containerWidth = containerEl?.clientWidth ?? canvasRect.width;\n const containerHeight = containerEl?.clientHeight ?? canvasRect.height;\n const minVisible = 80;\n const growthRatio = newUserZoom / Math.max(userZoomRef.current, 0.0001);\n const predictedCanvasWidth = canvasRect.width * growthRatio;\n const predictedCanvasHeight = canvasRect.height * growthRatio;\n // canvas-left in container = effectiveMarginX + panOffset.x.\n // After this update: shift by (rawNewPanX - currentPanX).\n const currentPanX = panOffsetRef.current.x;\n const currentPanY = panOffsetRef.current.y;\n const currentLeftInContainer = canvasRect.left - z.containerRect.left;\n const currentTopInContainer = canvasRect.top - z.containerRect.top;\n const predictedLeft = currentLeftInContainer + (rawNewPanX - currentPanX);\n const predictedTop = currentTopInContainer + (rawNewPanY - currentPanY);\n const clampedLeft = Math.min(\n Math.max(predictedLeft, -predictedCanvasWidth + minVisible),\n containerWidth - minVisible,\n );\n const clampedTop = Math.min(\n Math.max(predictedTop, -predictedCanvasHeight + minVisible),\n containerHeight - minVisible,\n );\n setUserZoom(newUserZoom);\n setPanOffset({\n x: rawNewPanX + (clampedLeft - predictedLeft),\n y: rawNewPanY + (clampedTop - predictedTop),\n });\n } else {\n setUserZoom(newUserZoom);\n setPanOffset({ x: rawNewPanX, y: rawNewPanY });\n }\n }\n return;\n }\n\n // Single-finger pan update — translate panOffset by the\n // pointer's screen-pixel delta. Below threshold the gesture\n // could still land as a tap, so we hold off updating panOffset\n // (and crucially keep `pendingTap` live) until the user moves\n // far enough that the intent is clearly a drag.\n if (\n singleFingerPanRef.current &&\n singleFingerPanRef.current.pointerId === e.pointerId\n ) {\n const p = singleFingerPanRef.current;\n const dx = e.clientX - p.startClientX;\n const dy = e.clientY - p.startClientY;\n if (!p.movedPastThreshold) {\n if (Math.hypot(dx, dy) > 5) {\n p.movedPastThreshold = true;\n // Movement committed: this is a pan, not a tap.\n p.pendingTap = null;\n } else {\n // Still might be a tap — don't move the canvas yet.\n return;\n }\n }\n // Pan is a no-op at fit zoom (1×) — the entire canvas is\n // already visible, there's nothing to navigate to. We still\n // mark the gesture as \"moved past threshold\" above so the\n // pendingTap resolves correctly (no spurious tap-on-release\n // after a drag), we just don't apply the offset.\n if (userZoomRef.current <= 1.001) return;\n setPanOffset({\n x: p.startPanOffset.x + dx,\n y: p.startPanOffset.y + dy,\n });\n return;\n }\n\n if (\n stateMachine.getMode() === 'pinch' &&\n pinchPointerIdsRef.current\n ) {\n const { a, b } = pinchPointerIdsRef.current;\n const p1 = activePointersRef.current.get(a);\n const p2 = activePointersRef.current.get(b);\n if (p1 && p2) {\n const gesture = stateMachine.updatePinch(p1, p2);\n const { element, startData } = stateMachine.getPinchContext();\n PinchHandler.calculatePinch({\n element,\n startData,\n scale: gesture.scale,\n rotationDelta: gesture.rotationDelta,\n startCentroid: gesture.startCentroid,\n currentCentroid: gesture.currentCentroid,\n snapRotation: snapSystem\n ? (rot, el) => snapSystem.snapRotation(rot, el)\n : undefined,\n });\n // Live-update the elements array so the canvas paints the\n // gesture in progress. Commit happens on pointer-up via\n // `executeElementUpdate` for undo/redo.\n setElements((prev) =>\n prev.map((el) =>\n el.id === element.id ? (element as CanvasElement) : el,\n ),\n );\n }\n e.preventDefault();\n return;\n }\n // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n // Handle selection handle dragging (iOS-style)\n if (draggingSelectionHandleRef.current && selectedElement instanceof TextElement) {\n const visualBbox = selectedElement.getVisualBoundingBox();\n const center = selectedElement.getRotationAnchor();\n const rotationRad = RotationUtils.toRadiansInverse(selectedElement.rotation);\n const dx = x - center.x;\n const dy = y - center.y;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n const localRotatedX = dx * cos - dy * sin;\n const localRotatedY = dx * sin + dy * cos;\n\n const handlePositions = selectionHandlePositionsRef.current;\n let yOffset = 0;\n if (draggingSelectionHandleRef.current === 'start' && handlePositions?.start) {\n yOffset = handlePositions.start.textEdge.y - handlePositions.start.center.y;\n } else if (draggingSelectionHandleRef.current === 'end' && handlePositions?.end) {\n yOffset = handlePositions.end.textEdge.y - handlePositions.end.center.y;\n }\n\n const relativeX = localRotatedX + visualBbox.width / 2;\n const relativeY = (localRotatedY + yOffset) + visualBbox.height / 2;\n\n const dragIndex = getCursorIndexFromPoint(selectedElement, { x: relativeX, y: relativeY });\n\n if (draggingSelectionHandleRef.current === 'start') {\n if (dragIndex <= selectionEnd) {\n setSelectionStart(dragIndex);\n setCursorPosition(dragIndex);\n } else {\n setSelectionStart(selectionEnd);\n setSelectionEnd(dragIndex);\n setCursorPosition(dragIndex);\n draggingSelectionHandleRef.current = 'end';\n }\n } else {\n if (dragIndex >= selectionStart) {\n setSelectionEnd(dragIndex);\n setCursorPosition(dragIndex);\n } else {\n setSelectionEnd(selectionStart);\n setSelectionStart(dragIndex);\n setCursorPosition(dragIndex);\n draggingSelectionHandleRef.current = 'start';\n }\n }\n\n if (inputRef.current) {\n const start = Math.min(selectionStart, selectionEnd);\n const end = Math.max(selectionStart, selectionEnd);\n inputRef.current.setSelectionRange(start, end);\n }\n return;\n }\n\n // Handle pen tool mode\n if (penTool.isActive() && selectedElement instanceof PathElement) {\n if (penTool.isDraggingHandle()) {\n penTool.updateHandle(x - selectedElement.x, y - selectedElement.y);\n setHandlesVersion((v) => v + 1);\n } else {\n penTool.setPreviewPoint(x - selectedElement.x, y - selectedElement.y);\n setHandlesVersion((v) => v + 1);\n }\n return;\n }\n\n // Update Alt key state from mouse event\n if (e.altKey !== isAltKeyPressed) {\n setIsAltKeyPressed(e.altKey);\n }\n\n // Handle marquee selection\n if (isMarqueeSelecting) {\n updateMarqueeSelection(x, y, elements);\n return;\n }\n\n // Check if dragging multi-selection group\n if (stateMachine.getMode() === 'drag') {\n const multiDragContext = stateMachine._multiSelectionDragContext;\n\n if (multiDragContext && multiSelection.length > 0) {\n const dx = x - multiDragContext.startX;\n const dy = y - multiDragContext.startY;\n\n if (!multiDragContext.currentElements) {\n multiDragContext.currentElements = new Map();\n }\n\n const updatedElements: CanvasElement[] = [];\n multiDragContext.elementsStartData.forEach((elementData: MultiElementStartData) => {\n const element = elementData.element;\n const startData = elementData.startData;\n\n const updatedElement = element.clone();\n updatedElement.x = startData.x + dx;\n updatedElement.y = startData.y + dy;\n\n multiDragContext.currentElements!.set(updatedElement.id, updatedElement);\n updatedElements.push(updatedElement);\n });\n\n setElements((prevElements) => prevElements.map((el) => updatedElements.find((ue) => ue.id === el.id) || el));\n\n multiDragContext.currentDelta = { dx, dy };\n\n if (!multiDragContext.startOBB && multiSelectionOBBRef.current) {\n multiDragContext.startOBB = { ...multiSelectionOBBRef.current };\n }\n\n setHandlesVersion((v) => v + 1);\n forceUpdate({});\n return;\n }\n }\n\n if (stateMachine.getMode() === 'drag' && selectedElement) {\n const dragMode = stateMachine.getDragMode();\n\n // Special handling for crop image drag\n if (dragMode === 'crop-image-drag') {\n const context = stateMachine.getContext();\n const draggedImage = context.element as ImageElement;\n\n if (draggedImage instanceof ImageElement && draggedImage.isCropping) {\n const result = cropController.handleCropImageDrag(draggedImage, context, x, y);\n\n const cropGroupContext = stateMachine._cropGroupContext;\n const activeChildContext = stateMachine._activeChildContext;\n const groupContext = cropGroupContext || activeChildContext;\n\n if (groupContext && selectedElement instanceof GroupElement) {\n const updatedChild = draggedImage.clone();\n updatedChild.x = result.x;\n updatedChild.y = result.y;\n updatedChild.updateCrop(result.cropX, result.cropY, result.cropWidth, result.cropHeight, false);\n\n const updatedGroup = selectedElement.clone();\n const childIndex = cropGroupContext\n ? cropGroupContext.childIndex\n : updatedGroup.children.findIndex((c) => c.id === (groupContext as ActiveChildContext).childId);\n if (childIndex >= 0) {\n updatedGroup.children[childIndex] = updatedChild;\n updatedGroup.updateBoundsFromChildren(true);\n }\n\n setElements((prevElements) => prevElements.map((el) => (el.id === updatedGroup.id ? updatedGroup : el)));\n\n const dragContext = stateMachine.getContext() as ExtendedInteractionContext;\n if (!dragContext._cachedElement) {\n dragContext._cachedElement = {};\n }\n dragContext._cachedElement[updatedGroup.id] = updatedGroup;\n\n if (activeChildContext && childIndex >= 0) {\n updateActiveChild(updatedGroup.children[childIndex]);\n transformHandles.update(updatedChild, zoom);\n }\n\n setHandlesVersion((v) => v + 1);\n } else {\n const updatedElement = draggedImage.clone();\n updatedElement.x = result.x;\n updatedElement.y = result.y;\n updatedElement.updateCrop(result.cropX, result.cropY, result.cropWidth, result.cropHeight, false);\n\n setElements((prevElements) =>\n prevElements.map((el) => (el.id === updatedElement.id ? updatedElement : el))\n );\n\n const dragContext = stateMachine.getContext() as ExtendedInteractionContext;\n if (!dragContext._cachedElement) {\n dragContext._cachedElement = {};\n }\n dragContext._cachedElement[updatedElement.id] = updatedElement;\n\n transformHandles.update(updatedElement, zoom);\n setHandlesVersion((v) => v + 1);\n }\n return;\n }\n }\n\n // Normal drag handling\n const activeChildContext = stateMachine._activeChildContext;\n\n const activeArtboardElements = getActiveArtboardElements();\n const flattenedElements: CanvasElement[] = [];\n activeArtboardElements.forEach((s) => {\n if (s instanceof GroupElement) {\n flattenedElements.push(...s.children);\n } else {\n flattenedElements.push(s);\n }\n });\n\n let newPos = stateMachine.updateDrag(x, y, flattenedElements);\n\n const elementForSpacingSnap =\n activeChildContext && selectedElement instanceof GroupElement\n ? selectedElement.children.find((c) => c.id === activeChildContext.childId)\n : selectedElement;\n\n if (elementForSpacingSnap) {\n newPos = spacingSystem.snapToSpacing(newPos, elementForSpacingSnap, flattenedElements);\n }\n\n if (activeChildContext && selectedElement instanceof GroupElement) {\n const updatedGroup = selectedElement.clone();\n const childIndex = updatedGroup.children.findIndex((c) => c.id === activeChildContext.childId);\n if (childIndex >= 0) {\n const updatedChild = updatedGroup.children[childIndex].clone();\n updatedChild.x = newPos.x;\n updatedChild.y = newPos.y;\n updatedGroup.children[childIndex] = updatedChild;\n updatedGroup.updateBoundsFromChildren(true);\n }\n\n setElements((prevElements) => prevElements.map((el) => (el.id === updatedGroup.id ? updatedGroup : el)));\n\n const dragContext = stateMachine.getContext() as ExtendedInteractionContext;\n if (!dragContext._cachedElement) {\n dragContext._cachedElement = {};\n }\n dragContext._cachedElement[updatedGroup.id] = updatedGroup;\n\n if (childIndex >= 0) {\n const newActiveChild = updatedGroup.children[childIndex];\n updateActiveChild(newActiveChild);\n transformHandles.update(newActiveChild, zoom);\n }\n\n setHandlesVersion((v) => v + 1);\n forceUpdate({});\n } else {\n const groupDragOriginal = stateMachine._groupDragOriginalElement;\n const updatedElement =\n selectedElement instanceof GroupElement && groupDragOriginal\n ? groupDragOriginal.clone()\n : selectedElement.clone();\n\n if (updatedElement instanceof GroupElement) {\n const context = stateMachine.getContext();\n const dragStartData = context.startData as TransformStartData;\n const dx = newPos.x - dragStartData.x;\n const dy = newPos.y - dragStartData.y;\n updatedElement.move(dx, dy);\n } else {\n updatedElement.x = newPos.x;\n updatedElement.y = newPos.y;\n }\n\n setElements((prevElements) => prevElements.map((el) => (el.id === updatedElement.id ? updatedElement : el)));\n transformHandles.update(updatedElement, zoom);\n\n const dragContext = stateMachine.getContext() as ExtendedInteractionContext;\n if (!dragContext._cachedElement) {\n dragContext._cachedElement = {};\n }\n dragContext._cachedElement[updatedElement.id] = updatedElement;\n }\n\n setHandlesVersion((v) => v + 1);\n setSnapGuides(snapSystem.getGuides());\n\n let movingElement: CanvasElement | undefined;\n if (activeChildContext && selectedElement instanceof GroupElement) {\n movingElement = selectedElement.children.find((c) => c.id === activeChildContext.childId);\n } else {\n const dragContext = stateMachine.getContext() as ExtendedInteractionContext;\n if (dragContext._cachedElement && selectedElement) {\n movingElement = dragContext._cachedElement[selectedElement.id];\n } else {\n movingElement = selectedElement || undefined;\n }\n }\n\n if (movingElement) {\n const indicators = spacingSystem.detectSpacing(movingElement, flattenedElements, isAltKeyPressed);\n setSpacingIndicators(indicators);\n }\n } else if (stateMachine.getMode() === 'resize') {\n const { dx, dy } = stateMachine.updateResize(x, y);\n const resizeContext = stateMachine.getResizeContext();\n\n // Handle multi-selection resize\n const multiResizeContext = stateMachine._multiSelectionResizeContext;\n if (multiResizeContext && multiSelection.length > 0) {\n const startBounds = multiResizeContext.startBounds;\n const anchor = multiResizeContext.anchor;\n\n let fixedX: number, fixedY: number;\n let draggedStartX: number, draggedStartY: number;\n\n switch (anchor) {\n case 'top-left':\n fixedX = startBounds.maxX; fixedY = startBounds.maxY;\n draggedStartX = startBounds.minX; draggedStartY = startBounds.minY;\n break;\n case 'top-right':\n fixedX = startBounds.minX; fixedY = startBounds.maxY;\n draggedStartX = startBounds.maxX; draggedStartY = startBounds.minY;\n break;\n case 'bottom-left':\n fixedX = startBounds.maxX; fixedY = startBounds.minY;\n draggedStartX = startBounds.minX; draggedStartY = startBounds.maxY;\n break;\n case 'bottom-right':\n fixedX = startBounds.minX; fixedY = startBounds.minY;\n draggedStartX = startBounds.maxX; draggedStartY = startBounds.maxY;\n break;\n default:\n fixedX = startBounds.minX; fixedY = startBounds.minY;\n draggedStartX = startBounds.maxX; draggedStartY = startBounds.maxY;\n }\n\n const draggedCurrentX = draggedStartX + dx;\n const draggedCurrentY = draggedStartY + dy;\n\n const startDistX = Math.abs(draggedStartX - fixedX);\n const startDistY = Math.abs(draggedStartY - fixedY);\n const newDistX = Math.abs(draggedCurrentX - fixedX);\n const newDistY = Math.abs(draggedCurrentY - fixedY);\n\n const scaleX = startDistX > 0 ? newDistX / startDistX : 1;\n const scaleY = startDistY > 0 ? newDistY / startDistY : 1;\n const scale = (scaleX + scaleY) / 2;\n\n const startWidth = startBounds.maxX - startBounds.minX;\n const startHeight = startBounds.maxY - startBounds.minY;\n const newWidth = startWidth * scale;\n const newHeight = startHeight * scale;\n\n let newMinX: number = fixedX, newMinY: number = fixedY;\n let newMaxX: number = fixedX, newMaxY: number = fixedY;\n\n switch (anchor) {\n case 'top-left':\n newMaxX = fixedX; newMaxY = fixedY;\n newMinX = fixedX - newWidth; newMinY = fixedY - newHeight;\n break;\n case 'top-right':\n newMinX = fixedX; newMaxY = fixedY;\n newMaxX = fixedX + newWidth; newMinY = fixedY - newHeight;\n break;\n case 'bottom-left':\n newMaxX = fixedX; newMinY = fixedY;\n newMinX = fixedX - newWidth; newMaxY = fixedY + newHeight;\n break;\n case 'bottom-right':\n newMinX = fixedX; newMinY = fixedY;\n newMaxX = fixedX + newWidth; newMaxY = fixedY + newHeight;\n break;\n }\n\n multiResizeContext.elementsStartData.forEach((elementData: MultiElementStartData) => {\n const element = elementData.element;\n const startData = elementData.startData;\n\n const offsetX = startData.x - fixedX;\n const offsetY = startData.y - fixedY;\n\n const newX = fixedX + offsetX * scale;\n const newY = fixedY + offsetY * scale;\n\n const updatedElement = element.clone();\n\n if (updatedElement instanceof ImageElement) {\n updatedElement.x = newX;\n updatedElement.y = newY;\n const imageData = updatedElement.transformData;\n const startImageData = startData.transformData as ImageTransformData;\n imageData.width = startImageData.width * scale;\n imageData.height = startImageData.height * scale;\n } else if (updatedElement instanceof GroupElement) {\n updatedElement.x = newX;\n updatedElement.y = newY;\n const newChildWidth = startData.width * scale;\n const newChildHeight = startData.height * scale;\n updatedElement.resize(anchor as ResizeAnchor, newChildWidth, newChildHeight, startData as unknown as TransformStartData);\n } else {\n const newElementWidth = startData.width * scale;\n const newElementHeight = startData.height * scale;\n updatedElement.resize(anchor as ResizeAnchor, newElementWidth, newElementHeight, startData as unknown as TransformStartData);\n updatedElement.x = newX;\n updatedElement.y = newY;\n }\n\n onElementUpdate(updatedElement);\n });\n\n if (multiSelectionOBBRef.current) {\n const newCenterX = (newMinX + newMaxX) / 2;\n const newCenterY = (newMinY + newMaxY) / 2;\n multiSelectionOBBRef.current = {\n centerX: newCenterX,\n centerY: newCenterY,\n width: newMaxX - newMinX,\n height: newMaxY - newMinY,\n rotation: multiSelectionOBBRef.current.rotation,\n };\n }\n\n return;\n }\n\n // Handle crop box resize\n const resizedImage = resizeContext.element as ImageElement;\n if (\n resizedImage instanceof ImageElement &&\n resizedImage.isCropping &&\n resizeContext.handle.anchor &&\n resizeContext.handle.anchor.startsWith('crop-')\n ) {\n const cropAnchor = resizeContext.handle.anchor.replace('crop-', '');\n const context = stateMachine.getContext();\n\n const result = cropController.handleCropBoxResize(resizedImage, cropAnchor, resizeContext, context, x, y);\n\n canvas.style.cursor = result.cursor;\n\n const cropGroupContext = stateMachine._cropGroupContext;\n const activeChildCtx = stateMachine._activeChildContext;\n const groupContext = cropGroupContext || activeChildCtx;\n\n if (groupContext && selectedElement instanceof GroupElement) {\n const updatedChild = resizedImage.clone();\n updatedChild.x = result.x;\n updatedChild.y = result.y;\n updatedChild.rotation = result.rotation;\n updatedChild.transformData.width = result.width;\n updatedChild.transformData.height = result.height;\n updatedChild.updateCrop(result.cropX, result.cropY, result.cropWidth, result.cropHeight, false);\n\n const updatedGroup = selectedElement.clone();\n const childIndex = cropGroupContext\n ? cropGroupContext.childIndex\n : updatedGroup.children.findIndex((c) => c.id === (groupContext as ActiveChildContext).childId);\n if (childIndex >= 0) {\n updatedGroup.children[childIndex] = updatedChild;\n updatedGroup.updateBoundsFromChildren(true);\n }\n onElementUpdate(updatedGroup);\n\n if (activeChildCtx && childIndex >= 0) {\n updateActiveChild(updatedGroup.children[childIndex]);\n }\n } else {\n const updatedElement = resizedImage.clone();\n updatedElement.x = result.x;\n updatedElement.y = result.y;\n updatedElement.rotation = result.rotation;\n updatedElement.transformData.width = result.width;\n updatedElement.transformData.height = result.height;\n updatedElement.updateCrop(result.cropX, result.cropY, result.cropWidth, result.cropHeight, false);\n onElementUpdate(updatedElement);\n }\n return;\n }\n\n if (!selectedElement) return;\n\n const activeChildCtx = stateMachine._activeChildContext;\n const isResizingChildInGroup = activeChildCtx && selectedElement instanceof GroupElement;\n\n const elementsForSnap: CanvasElement[] = [];\n elements.forEach((s) => {\n if (s instanceof GroupElement) {\n elementsForSnap.push(...s.children);\n } else {\n elementsForSnap.push(s);\n }\n });\n\n const result = globalResizePipeline.executeResize({\n element: resizeContext.element as TextElement,\n handle: resizeContext.handle,\n dx,\n dy,\n startData: resizeContext.startData,\n allElements: elementsForSnap,\n snapSystem: snapSystem,\n });\n\n let updatedElement = result.element;\n\n stateMachine.updateResizeElement(updatedElement);\n\n if (isResizingChildInGroup) {\n const updatedGroup = selectedElement.clone();\n const childIndex = updatedGroup.children.findIndex((c) => c.id === activeChildCtx.childId);\n if (childIndex >= 0) {\n updatedGroup.children[childIndex] = updatedElement;\n updatedGroup.updateBoundsFromChildren(true);\n }\n onElementUpdate(updatedGroup);\n\n if (childIndex >= 0) {\n updateActiveChild(updatedGroup.children[childIndex]);\n transformHandles.update(updatedGroup.children[childIndex], zoom);\n }\n } else {\n onElementUpdate(updatedElement);\n transformHandles.update(updatedElement, zoom);\n }\n\n setHandlesVersion((v) => v + 1);\n setSnapGuides(snapSystem.getGuides());\n } else if (stateMachine.getMode() === 'rotate') {\n canvas.style.cursor = 'grabbing';\n\n if (!isRotating) {\n setIsRotating(true);\n onRotationStateChange?.(true);\n }\n\n // Check multi-selection rotation\n const multiRotationContext = stateMachine._multiSelectionRotationContext;\n if (multiRotationContext && multiSelection.length > 0) {\n const bounds = multiRotationContext.startBounds;\n const deltaRotation = stateMachine.updateRotate(x, y, bounds.centerX, bounds.centerY);\n setCurrentRotation(deltaRotation);\n\n multiRotationContext.elementsStartData.forEach((elementData: MultiElementStartData) => {\n const element = elementData.element;\n const startData = elementData.startData;\n\n const rotatedPos = Transform.rotatePointAroundAnchor(\n startData.x, startData.y,\n bounds.centerX, bounds.centerY,\n deltaRotation\n );\n\n const updatedElement = element.clone();\n updatedElement.x = rotatedPos.x;\n updatedElement.y = rotatedPos.y;\n updatedElement.setRotation(startData.rotation + deltaRotation);\n\n onElementUpdate(updatedElement);\n });\n return;\n }\n\n const activeChildCtx = stateMachine._activeChildContext;\n const workingEl = activeChildCtx ? activeChildElement : selectedElement;\n\n if (!workingEl) return;\n\n const bbox = workingEl.getBoundingBox();\n const center = getBoundingBoxCenter(bbox);\n const centerXBefore = center.x;\n const centerYBefore = center.y;\n\n const newRotation = stateMachine.updateRotate(x, y, center.x, center.y);\n\n const updatedElement = workingEl.clone();\n updatedElement.setRotation(newRotation);\n\n if (!(updatedElement instanceof GroupElement)) {\n const newBbox = updatedElement.getBoundingBox();\n const newCenter = getBoundingBoxCenter(newBbox);\n const offsetX = centerXBefore - newCenter.x;\n const offsetY = centerYBefore - newCenter.y;\n updatedElement.x += offsetX;\n updatedElement.y += offsetY;\n }\n\n rotatingElementRef.current = updatedElement;\n setCurrentRotation(updatedElement.rotation);\n setRotatingElementVersion(v => v + 1);\n\n if (activeChildCtx && selectedElement instanceof GroupElement) {\n const updatedGroup = selectedElement.clone();\n const childIndex = updatedGroup.children.findIndex((c) => c.id === activeChildCtx.childId);\n if (childIndex >= 0) {\n updatedGroup.children[childIndex] = updatedElement;\n updatedGroup.updateBoundsFromChildren(true);\n }\n onElementUpdate(updatedGroup);\n\n if (childIndex >= 0) {\n updateActiveChild(updatedGroup.children[childIndex]);\n transformHandles.update(updatedGroup.children[childIndex], zoom);\n }\n } else {\n onElementUpdate(updatedElement);\n transformHandles.update(updatedElement, zoom);\n }\n\n setHandlesVersion((v) => v + 1);\n } else {\n // Idle mode: Update cursor and hover state\n\n // Check multi-selection handles\n if (multiSelection.length > 0 && multiSelectionGroupBoundsRef.current) {\n const bounds = multiSelectionGroupBoundsRef.current;\n const hitRadius = 16 / zoom;\n\n const corners = [\n { x: bounds.minX, y: bounds.minY, anchor: 'top-left', cursor: 'nwse-resize' },\n { x: bounds.maxX, y: bounds.minY, anchor: 'top-right', cursor: 'nesw-resize' },\n { x: bounds.minX, y: bounds.maxY, anchor: 'bottom-left', cursor: 'nesw-resize' },\n { x: bounds.maxX, y: bounds.maxY, anchor: 'bottom-right', cursor: 'nwse-resize' },\n ];\n\n const groupRotation = bounds.rotation || 0;\n const rotatedCorners = corners.map((corner) => {\n if (groupRotation !== 0) {\n const rotationRad = (-groupRotation * Math.PI) / 180;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n const cdx = corner.x - bounds.centerX;\n const cdy = corner.y - bounds.centerY;\n return {\n x: bounds.centerX + (cdx * cos - cdy * sin),\n y: bounds.centerY + (cdx * sin + cdy * cos),\n anchor: corner.anchor,\n cursor: corner.cursor,\n };\n }\n return corner;\n });\n\n for (const corner of rotatedCorners) {\n const cdx = x - corner.x;\n const cdy = y - corner.y;\n const distance = Math.sqrt(cdx * cdx + cdy * cdy);\n\n if (distance <= hitRadius) {\n canvas.style.cursor = corner.cursor;\n setHoverState({ type: 'multi-selection-handle', data: corner });\n return;\n }\n }\n\n const rotationHitRadius = (16 + 2) / zoom;\n const rotationHandleDistance = Math.sqrt(\n Math.pow(x - bounds.rotationHandleX, 2) + Math.pow(y - bounds.rotationHandleY, 2)\n );\n\n if (rotationHandleDistance <= rotationHitRadius) {\n canvas.style.cursor = 'grab';\n setHoverState({ type: 'multi-selection-rotation-handle', data: null });\n return;\n }\n\n let localX = x;\n let localY = y;\n\n if (groupRotation !== 0) {\n const rotationRad = (groupRotation * Math.PI) / 180;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n const cdx = x - bounds.centerX;\n const cdy = y - bounds.centerY;\n localX = bounds.centerX + (cdx * cos - cdy * sin);\n localY = bounds.centerY + (cdx * sin + cdy * cos);\n }\n\n if (localX >= bounds.minX && localX <= bounds.maxX && localY >= bounds.minY && localY <= bounds.maxY) {\n canvas.style.cursor = 'move';\n setHoverState({ type: 'multi-selection-bounds', data: bounds });\n return;\n }\n }\n\n // Alt+hover: Show spacing measurements\n if (e.altKey) {\n let hoveredElement: CanvasElement | null = null;\n const activeArtboardElements = getActiveArtboardElements();\n for (let i = activeArtboardElements.length - 1; i >= 0; i--) {\n const element = activeArtboardElements[i];\n if (element.visible === false) continue;\n\n if (element instanceof GroupElement) {\n for (let j = element.children.length - 1; j >= 0; j--) {\n const child = element.children[j];\n if (child.visible === false) continue;\n const bbox = child.getVisualBoundingBox();\n if (x >= bbox.x && x <= bbox.x + bbox.width && y >= bbox.y && y <= bbox.y + bbox.height) {\n hoveredElement = child;\n break;\n }\n }\n } else {\n const bbox = element.getVisualBoundingBox();\n if (x >= bbox.x && x <= bbox.x + bbox.width && y >= bbox.y && y <= bbox.y + bbox.height) {\n hoveredElement = element;\n break;\n }\n }\n if (hoveredElement) break;\n }\n\n if (hoveredElement) {\n const flattenedElements: CanvasElement[] = [];\n activeArtboardElements.forEach((s) => {\n if (s instanceof GroupElement) {\n flattenedElements.push(...s.children);\n } else {\n flattenedElements.push(s);\n }\n });\n\n const indicators = spacingSystem.detectSpacing(hoveredElement, flattenedElements, true);\n setSpacingIndicators(indicators);\n } else {\n setSpacingIndicators([]);\n }\n } else {\n if (spacingIndicators.length > 0) {\n setSpacingIndicators([]);\n }\n }\n\n // Handle single element hover\n const hoverCheckElement = activeChildElement || selectedElement;\n\n if (hoverCheckElement && hoverCheckElement.visible !== false) {\n if (hoverCheckElement instanceof ImageElement && hoverCheckElement.isCropping) {\n const cropHandle = hoverCheckElement.hitTestCropHandle(x, y, zoom);\n if (cropHandle) {\n const cursor = getCursorForWorldPosition(\n cropHandle.worldX, cropHandle.worldY,\n hoverCheckElement.x, hoverCheckElement.y,\n cropHandle.type, hoverCheckElement.rotation, cropHandle.anchor\n );\n canvas.style.cursor = cursor;\n setHoverState({ type: 'crop-handle', data: cropHandle });\n return;\n }\n\n const imageHandle = transformHandles.hitTestResize(x, y, zoom);\n if (imageHandle) {\n canvas.style.cursor = imageHandle.cursor;\n setHoverState({ type: 'resize-handle', data: imageHandle });\n return;\n }\n\n if (hoverCheckElement.hitTest(x, y)) {\n canvas.style.cursor = 'move';\n setHoverState({ type: 'element', data: hoverCheckElement });\n return;\n }\n } else {\n if (transformHandles.hitTestRotation(x, y, zoom)) {\n canvas.style.cursor = 'grab';\n setHoverState({ type: 'rotation-handle', data: null });\n return;\n }\n\n const resizeHandle = transformHandles.hitTestResize(x, y, zoom);\n if (resizeHandle) {\n canvas.style.cursor = resizeHandle.cursor;\n setHoverState({ type: 'resize-handle', data: resizeHandle });\n return;\n }\n\n if (hoverCheckElement.hitTest(x, y)) {\n canvas.style.cursor = 'move';\n setHoverState({ type: 'element', data: hoverCheckElement });\n return;\n }\n }\n }\n\n // Check group siblings hover\n if (activeChildElement && selectedElement instanceof GroupElement) {\n for (let i = selectedElement.children.length - 1; i >= 0; i--) {\n const child = selectedElement.children[i];\n if (child.visible === false || child.id === activeChildElement.id) continue;\n if (child.hitTest(x, y)) {\n canvas.style.cursor = 'pointer';\n setHoverState({ type: 'group-sibling', data: child });\n return;\n }\n }\n }\n\n // Check hovering over any element. Mirror the focus filter\n // applied to the click path (see `isHitAllowedInFocus` usage\n // around `pointerdown` line ~1207): if the cursor is over the\n // overflow region of a non-selected element (outside the\n // visible piece bounds) the click would be ignored, so the\n // cursor shouldn't promise that hovering anywhere is\n // interactive. Keep the cursor default in that case so the\n // affordance matches the behaviour. The selected element's\n // own drag IS allowed in overflow (see comment at line ~1075),\n // so the selected-element hover branch above intentionally\n // does NOT apply this filter.\n const activeArtboardElements = getActiveArtboardElements();\n for (let i = activeArtboardElements.length - 1; i >= 0; i--) {\n if (activeArtboardElements[i].visible === false) continue;\n if (activeArtboardElements[i].locked) continue;\n if (activeArtboardElements[i].hitTest(x, y)) {\n if (!isHitAllowedInFocus(activeArtboardElements[i], x, y, focusedPieceRect)) {\n continue;\n }\n canvas.style.cursor = 'pointer';\n setHoverState({ type: 'element', data: activeArtboardElements[i] });\n return;\n }\n }\n\n canvas.style.cursor = 'default';\n setHoverState({ type: null, data: null });\n }\n },\n [\n stateMachine,\n selectedElement,\n getActiveArtboardElements,\n onElementUpdate,\n transformHandles,\n isMarqueeSelecting,\n snapSystem,\n multiSelection,\n multiSelectionOBBRef,\n activeChildElement,\n penTool,\n setHandlesVersion,\n zoom,\n paddingOffsetX,\n paddingOffsetY,\n setHoverState,\n spacingSystem,\n setSpacingIndicators,\n spacingIndicators,\n cropController,\n // Canvas-zoom pinch update uses these viewport setters.\n setUserZoom,\n setPanOffset,\n ]\n );\n\n // ============================================\n // POINTER UP\n // ============================================\n const handlePointerUp = useCallback((e: React.PointerEvent<HTMLCanvasElement>) => {\n (e.target as HTMLCanvasElement).releasePointerCapture(e.pointerId);\n\n // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n // PINCH GESTURE — release + commit\n // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n activePointersRef.current.delete(e.pointerId);\n\n // Canvas-zoom pinch release — no commit step (zoom + pan live in\n // ViewportContext, already applied frame-by-frame). Just drop the\n // gesture state when either driving finger lifts.\n if (\n canvasZoomPinchRef.current &&\n (e.pointerId === canvasZoomPinchRef.current.pointerA ||\n e.pointerId === canvasZoomPinchRef.current.pointerB)\n ) {\n canvasZoomPinchRef.current = null;\n activePointersRef.current.clear();\n return;\n }\n\n // Single-finger pan release. If the user never moved past the\n // pan threshold and we have a pending tap action (typically\n // \"select element X\"), fire it here so tap-to-select still\n // works when panning is the default-on-empty behaviour.\n // Otherwise this was a real pan and the canvas is already where\n // the user left it.\n if (\n singleFingerPanRef.current &&\n singleFingerPanRef.current.pointerId === e.pointerId\n ) {\n const p = singleFingerPanRef.current;\n singleFingerPanRef.current = null;\n if (!p.movedPastThreshold) {\n // Pure setSelection / deselect. We avoid going through\n // `handleElementSelection` because that helper ALSO sets up\n // drag state — for a tap-to-select the state machine would\n // briefly enter DRAGGING then end(), and the residue caused\n // a small jump on the user's *next* drag. These direct calls\n // leave the state machine in IDLE so the next pointerdown\n // captures clean startData.\n if (p.pendingTap?.kind === 'select-element') {\n onSelectionChange(p.pendingTap.elementId);\n onMultiSelectionChange([]);\n } else if (p.pendingTap?.kind === 'deselect') {\n onSelectionChange(null);\n onMultiSelectionChange([]);\n }\n }\n return;\n }\n\n if (\n stateMachine.getMode() === 'pinch' &&\n pinchPointerIdsRef.current\n ) {\n const { a, b } = pinchPointerIdsRef.current;\n // If either of the two pinch-driving fingers lifts, the\n // gesture is over — commit the final element state via\n // executeElementUpdate so it lands in undo/redo, then drop\n // back to IDLE. Don't try to \"downgrade\" to single-finger\n // drag; that's a UX rabbit hole (accidental jumps when one\n // finger lifts mid-gesture).\n if (e.pointerId === a || e.pointerId === b) {\n const ctx = stateMachine.getContext();\n const finalElement = ctx.element;\n const startElement = pinchOriginalElementRef.current ?? undefined;\n if (finalElement) {\n executeElementUpdate(\n startElement as CanvasElement | undefined,\n finalElement as CanvasElement,\n );\n }\n stateMachine.end();\n pinchPointerIdsRef.current = null;\n pinchOriginalElementRef.current = null;\n // Drop any remaining pointers from the active map — the\n // gesture is over, and a stray pointerId hanging around\n // would re-trigger pinch when the next finger lands.\n activePointersRef.current.clear();\n return;\n }\n }\n // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n if (draggingSelectionHandleRef.current) {\n draggingSelectionHandleRef.current = null;\n }\n\n // Handle pen tool mode\n if (penTool.isActive() && penTool.isDraggingHandle()) {\n const wasClosing = penTool.isClosingPath();\n penTool.finishDraggingHandle();\n\n if (wasClosing && selectedElement instanceof PathElement) {\n const updatedElement = selectedElement.clone();\n executeElementUpdate(selectedElement, updatedElement);\n }\n\n setHandlesVersion((v) => v + 1);\n return;\n }\n\n // Handle marquee selection completion\n if (isMarqueeSelecting) {\n finishMarqueeSelection(onMultiSelectionChange);\n return;\n }\n\n // Commit drag changes to history\n if (dragOriginalElementsRef.current && stateMachine.getMode() === 'drag') {\n const originalElements = dragOriginalElementsRef.current;\n\n originalElements.forEach((originalElement, elementId) => {\n const finalElement = elements.find((el) => el.id === elementId);\n if (finalElement) {\n const hasChanged =\n originalElement.x !== finalElement.x ||\n originalElement.y !== finalElement.y ||\n (originalElement instanceof GroupElement &&\n finalElement instanceof GroupElement &&\n JSON.stringify(originalElement.children.map((c) => ({ x: c.x, y: c.y }))) !==\n JSON.stringify(finalElement.children.map((c) => ({ x: c.x, y: c.y }))));\n\n if (hasChanged) {\n executeElementUpdate(originalElement, finalElement);\n }\n }\n });\n\n dragOriginalElementsRef.current = null;\n }\n\n const canvas = canvasRef.current;\n if (canvas) {\n canvas.style.cursor = 'default';\n }\n\n snapSystem.clearGuides();\n snapSystem.clearSnapState();\n setSnapGuides([]);\n\n spacingSystem.clearIndicators();\n setSpacingIndicators([]);\n\n if (stateMachine._cropGroupContext) {\n delete stateMachine._cropGroupContext;\n }\n\n if (stateMachine._activeChildContext) {\n if (activeChildElement && selectedElement instanceof GroupElement) {\n const dragContext = stateMachine.getContext() as ExtendedInteractionContext;\n const cachedGroup = dragContext._cachedElement && dragContext._cachedElement[selectedElement.id];\n const groupToUse = (cachedGroup as GroupElement) || selectedElement;\n\n const updatedChild = groupToUse.children.find((c) => c.id === activeChildElement.id);\n if (updatedChild) {\n updateActiveChild(updatedChild as ImageElement | TextElement);\n transformHandles.update(updatedChild, zoom);\n }\n }\n delete stateMachine._activeChildContext;\n }\n\n if (stateMachine._multiSelectionResizeContext) {\n delete stateMachine._multiSelectionResizeContext;\n }\n\n elements.forEach((element) => {\n if (element instanceof CustomTransform && element._lockedLineCount !== undefined) {\n const updatedElement = element.clone();\n updatedElement._lockedLineCount = undefined;\n onElementUpdate(updatedElement);\n }\n });\n\n if (stateMachine._multiSelectionDragContext) {\n const multiDragContext = stateMachine._multiSelectionDragContext;\n\n if (multiDragContext.startOBB && multiDragContext.currentDelta) {\n const { dx, dy } = multiDragContext.currentDelta;\n const startOBB = multiDragContext.startOBB;\n multiSelectionOBBRef.current = {\n ...startOBB,\n centerX: startOBB.centerX + dx,\n centerY: startOBB.centerY + dy,\n };\n }\n\n delete stateMachine._multiSelectionDragContext;\n }\n\n if (stateMachine._multiSelectionRotationContext) {\n if (stateMachine.getMode() === 'rotate' && multiSelection.length > 0 && multiSelectionOBBRef.current) {\n const prevOBB = multiSelectionOBBRef.current;\n multiSelectionOBBRef.current = {\n ...prevOBB,\n rotation: prevOBB.rotation + stateMachine._multiSelectionRotationContext.currentRotation || prevOBB.rotation,\n };\n }\n delete stateMachine._multiSelectionRotationContext;\n }\n\n const dragContext = stateMachine.getContext() as ExtendedInteractionContext;\n if (dragContext._cachedElement) {\n delete dragContext._cachedElement;\n }\n\n // Handle pending child selection\n if (stateMachine._pendingChildSelection) {\n const pendingChild = stateMachine._pendingChildSelection;\n let shouldSelectChild = true;\n\n if (selectedElement instanceof GroupElement) {\n const originalGroup = stateMachine._groupDragOriginalElement;\n if (originalGroup) {\n const moved =\n Math.abs(selectedElement.x - originalGroup.x) > 0.1 ||\n Math.abs(selectedElement.y - originalGroup.y) > 0.1;\n shouldSelectChild = !moved;\n }\n }\n\n if (shouldSelectChild) {\n updateActiveChild(pendingChild);\n transformHandles.update(pendingChild, zoom);\n setHandlesVersion((v) => v + 1);\n }\n\n delete stateMachine._pendingChildSelection;\n }\n\n // Handle pending text edit mode\n if (stateMachine._pendingTextEditMode) {\n const pending = stateMachine._pendingTextEditMode;\n const currentElement = elements.find((el) => el.id === pending.elementId);\n\n const moved =\n currentElement &&\n (Math.abs(currentElement.x - pending.originalX) > 0.1 ||\n Math.abs(currentElement.y - pending.originalY) > 0.1);\n\n if (!moved && currentElement instanceof TextElement && !(currentElement instanceof ImageElement) && !(currentElement instanceof ShapeElement)) {\n enterTextEditMode(currentElement, pending.clickX, pending.clickY);\n }\n\n delete stateMachine._pendingTextEditMode;\n }\n\n // Handle pending tap-to-deselect: fire only if the gesture was a\n // genuine tap (no significant movement). The pinch path above\n // already cleared the candidate, so we only land here for\n // single-pointer taps. We compare the tapped element's current\n // position against its position at pointerdown — if it didn't\n // move it's a tap, not a drag.\n if (pendingDeselectIdRef.current) {\n const id = pendingDeselectIdRef.current;\n const startPos = pendingDeselectStartPosRef.current;\n const currentElement = elements.find((el) => el.id === id);\n const original = dragOriginalElementsRef.current?.get(id);\n const moved = !!(\n currentElement &&\n original &&\n (Math.abs(currentElement.x - original.x) > 0.1 ||\n Math.abs(currentElement.y - original.y) > 0.1)\n );\n // Belt-and-suspenders: also bail if pointer travelled far in\n // screen space (covers cases where drag started but element\n // didn't move yet — e.g. snapped back to origin).\n const screenMoved =\n startPos\n ? Math.hypot(\n (e.clientX - (canvasBoundsRef.current?.left ?? 0)) / zoom -\n paddingOffsetX -\n startPos.x,\n (e.clientY - (canvasBoundsRef.current?.top ?? 0)) / zoom -\n paddingOffsetY -\n startPos.y,\n ) > 4 / Math.max(zoom, 0.0001)\n : false;\n if (!moved && !screenMoved && id === selectedElement?.id && startPos) {\n // Tap-without-drag on the selected element. Walk the cycle:\n // collect every visible element under the tap point, find the\n // currently-selected one in the stack, and either advance to\n // the next layer below OR deselect when there's nothing else\n // to cycle to. Mirrors the cycle in the `pendingTap` (pan)\n // path so locked-and-unlocked selected elements behave\n // identically — tap-on-selected always cycles down, ending\n // in deselect at the bottom.\n const activeArtboardElements = getActiveArtboardElements();\n const hits: typeof activeArtboardElements = [];\n for (let i = activeArtboardElements.length - 1; i >= 0; i--) {\n const el = activeArtboardElements[i];\n if (el.visible === false) continue;\n if (!el.hitTest(startPos.x, startPos.y)) continue;\n if (!isHitAllowedInFocus(el, startPos.x, startPos.y, focusedPieceRect))\n continue;\n hits.push(el);\n }\n const currentIdx = hits.findIndex((el) => el.id === id);\n if (currentIdx !== -1 && currentIdx + 1 < hits.length) {\n onSelectionChange(hits[currentIdx + 1].id);\n } else {\n onSelectionChange(null);\n }\n }\n pendingDeselectIdRef.current = null;\n pendingDeselectStartPosRef.current = null;\n }\n\n if (stateMachine._groupDragOriginalElement) {\n delete stateMachine._groupDragOriginalElement;\n }\n\n stateMachine.end();\n setIsRotating(false);\n onRotationStateChange?.(false);\n rotatingElementRef.current = null;\n setRotatingElementVersion(0);\n forceUpdate({});\n }, [\n stateMachine,\n snapSystem,\n isMarqueeSelecting,\n onMultiSelectionChange,\n multiSelection,\n multiSelectionOBBRef,\n activeChildElement,\n selectedElement,\n updateActiveChild,\n transformHandles,\n canvasRef,\n penTool,\n setHandlesVersion,\n zoom,\n paddingOffsetX,\n paddingOffsetY,\n onRotationStateChange,\n enterTextEditMode,\n elements,\n onSelectionChange,\n executeElementUpdate,\n focusedPieceRect,\n ]);\n\n // ============================================\n // DOUBLE CLICK\n // ============================================\n const handleDoubleClick = useCallback(\n (e: React.MouseEvent<HTMLCanvasElement>) => {\n if (!selectedElement) return;\n\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n const rect = canvasBoundsRef.current ?? canvas.getBoundingClientRect();\n const x = (e.clientX - rect.left) / zoom - paddingOffsetX;\n const y = (e.clientY - rect.top) / zoom - paddingOffsetY;\n\n if (selectedElement.hitTest(x, y)) {\n // Group element - check child\n if (selectedElement instanceof GroupElement) {\n const clickedChild = selectedElement.hitTestChild(x, y);\n if (clickedChild) {\n const isChildAlreadyActive = activeChildElement && activeChildElement.id === clickedChild.id;\n if (!isChildAlreadyActive) return;\n\n // Image children no longer enter crop mode on double-\n // click — too easy to trigger by accident. Users open\n // crop via the explicit toolbar control instead.\n\n // Handle text child - enter text edit mode\n if (clickedChild instanceof TextElement && !(clickedChild instanceof GroupElement) && !(clickedChild instanceof ShapeElement)) {\n const visualBbox = clickedChild.getVisualBoundingBox();\n\n const rotationRad = RotationUtils.toRadiansInverse(clickedChild.rotation);\n const center = clickedChild.getRotationAnchor();\n const cdx = x - center.x;\n const cdy = y - center.y;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n const localRotatedX = cdx * cos - cdy * sin;\n const localRotatedY = cdx * sin + cdy * cos;\n const relativeX = localRotatedX + visualBbox.width / 2;\n const relativeY = localRotatedY + visualBbox.height / 2;\n\n const clickIndex = getCursorIndexFromPoint(clickedChild, { x: relativeX, y: relativeY });\n\n const contentHeight = visualBbox.height + 16;\n const offset = 6;\n\n setEditPosition({\n x: rect.left + (visualBbox.x + paddingOffsetX) * zoom - offset,\n y: rect.top + (visualBbox.y + paddingOffsetY) * zoom - offset,\n width: Math.max(20, visualBbox.width * zoom),\n height: Math.max(contentHeight * zoom, 50),\n });\n setEditText(getWrappedTextForEditing(clickedChild));\n const initialRichText = clickedChild.getRichText().clone();\n editRichTextRef.current = initialRichText;\n setEditRichText(initialRichText);\n const styleAtEnd = clickedChild.getRichText().getStyleAt(Math.max(0, clickedChild.text.length - 1));\n setCurrentFormattingStyle({ ...clickedChild.getDefaultStyle(), ...styleAtEnd });\n\n setCursorPosition(clickIndex);\n setSelectionStart(clickIndex);\n setSelectionEnd(clickIndex);\n\n setIsEditing(true);\n editModeEnteredAtRef.current = Date.now();\n editingChildIdRef.current = clickedChild.id;\n return;\n }\n }\n return;\n }\n\n // Image elements no longer enter crop mode on double-click\n // — too easy to trigger by accident. Users open crop via the\n // explicit toolbar control instead.\n\n // Handle path elements - enter path edit mode\n if (selectedElement instanceof PathElement) {\n penTool.editPath(selectedElement);\n setHandlesVersion((v) => v + 1);\n return;\n }\n\n // Handle shape elements - do nothing\n if (selectedElement instanceof ShapeElement) {\n return;\n }\n\n // Only text elements enter edit mode on double-click. Image elements\n // fall through here (no crop-on-double-click — see note above) and the\n // guard also narrows the TextElement | ImageElement union for the\n // text-only accessors below (getRichText, text, getDefaultStyle).\n if (!(selectedElement instanceof TextElement)) {\n return;\n }\n\n // Handle text elements - enter edit mode\n const visualBbox = selectedElement.getVisualBoundingBox();\n\n const rotationRad = RotationUtils.toRadiansInverse(selectedElement.rotation);\n const center = selectedElement.getRotationAnchor();\n const cdx = x - center.x;\n const cdy = y - center.y;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n const localRotatedX = cdx * cos - cdy * sin;\n const localRotatedY = cdx * sin + cdy * cos;\n const relativeX = localRotatedX + visualBbox.width / 2;\n const relativeY = localRotatedY + visualBbox.height / 2;\n\n const clickIndex = getCursorIndexFromPoint(selectedElement as TextElement, { x: relativeX, y: relativeY });\n\n const contentHeight = visualBbox.height + 16;\n const offset = 6;\n\n setEditPosition({\n x: rect.left + (visualBbox.x + paddingOffsetX) * zoom - offset,\n y: rect.top + (visualBbox.y + paddingOffsetY) * zoom - offset,\n width: Math.max(20, visualBbox.width * zoom),\n height: Math.max(contentHeight * zoom, 50),\n });\n setEditText(getWrappedTextForEditing(selectedElement));\n const initialRichText = selectedElement.getRichText().clone();\n editRichTextRef.current = initialRichText;\n setEditRichText(initialRichText);\n const styleAtEnd = selectedElement.getRichText().getStyleAt(Math.max(0, selectedElement.text.length - 1));\n setCurrentFormattingStyle({ ...selectedElement.getDefaultStyle(), ...styleAtEnd });\n setCursorPosition(clickIndex);\n setSelectionStart(clickIndex);\n setSelectionEnd(clickIndex);\n setIsEditing(true);\n editModeEnteredAtRef.current = Date.now();\n }\n },\n [\n selectedElement,\n onElementUpdate,\n activeChildElement,\n transformHandles,\n penTool,\n setHandlesVersion,\n zoom,\n paddingOffsetX,\n paddingOffsetY,\n setEditPosition,\n setEditText,\n setEditRichText,\n setCurrentFormattingStyle,\n setCursorPosition,\n setSelectionStart,\n setSelectionEnd,\n setIsEditing,\n updateActiveChild,\n onCropModeEnter,\n getWrappedTextForEditing,\n ]\n );\n\n // ============================================\n // DOCUMENT-LEVEL EVENT FORWARDING\n // ============================================\n\n // Document-level mousemove handler\n const handleDocumentMouseMove = useCallback(\n (e: MouseEvent) => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n const mode = stateMachine.getMode();\n if (mode === 'idle') return;\n\n const rect = canvasBoundsRef.current ?? canvas.getBoundingClientRect();\n const isInsideCanvas =\n e.clientX >= rect.left &&\n e.clientX <= rect.right &&\n e.clientY >= rect.top &&\n e.clientY <= rect.bottom;\n\n if (isInsideCanvas) return;\n\n const syntheticEvent = {\n clientX: e.clientX,\n clientY: e.clientY,\n altKey: e.altKey,\n shiftKey: e.shiftKey,\n ctrlKey: e.ctrlKey,\n metaKey: e.metaKey,\n bubbles: true,\n cancelable: true,\n currentTarget: canvas,\n target: canvas,\n nativeEvent: e,\n type: 'pointermove',\n pointerId: (e as PointerEvent).pointerId || 0,\n pointerType: (e as PointerEvent).pointerType || 'mouse',\n preventDefault: () => e.preventDefault(),\n stopPropagation: () => e.stopPropagation(),\n isPropagationStopped: () => false,\n isDefaultPrevented: () => e.defaultPrevented,\n } as unknown as React.PointerEvent<HTMLCanvasElement>;\n\n handlePointerMove(syntheticEvent);\n },\n [handlePointerMove, canvasRef, stateMachine]\n );\n\n // Document-level pointerup handler.\n //\n // This catches pointer-up that the canvas's own React `onPointerUp`\n // would miss — e.g. when the user drags an element off the canvas\n // before lifting, or releases over a sibling overlay. We MUST gate\n // on an active gesture though: without this filter, every\n // pointer-up anywhere on the page (including taps on chip / pill /\n // editor-header buttons) runs `handlePointerUp`, which fires any\n // pending side-effects from a prior canvas interaction (most\n // visibly: pendingDeselect → unselects on a tap that wasn't even\n // on the canvas).\n const handleDocumentPointerUp = useCallback(\n (e: PointerEvent) => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n // Only handle the event if it originated inside the canvas, OR\n // we're tracking an active gesture on this pointer (drag /\n // pinch / pan in progress whose finger may have travelled\n // outside the canvas DOM).\n const targetNode = e.target as Node | null;\n const insideCanvas = targetNode ? canvas.contains(targetNode) : false;\n const hasActiveGesture =\n activePointersRef.current.has(e.pointerId) ||\n canvasZoomPinchRef.current !== null ||\n singleFingerPanRef.current !== null ||\n pinchPointerIdsRef.current !== null ||\n stateMachine.isInteracting();\n if (!insideCanvas && !hasActiveGesture) return;\n\n const syntheticEvent = {\n clientX: e.clientX,\n clientY: e.clientY,\n currentTarget: canvas,\n target: canvas,\n pointerId: e.pointerId || 0,\n pointerType: e.pointerType || 'mouse',\n } as unknown as React.PointerEvent<HTMLCanvasElement>;\n\n handlePointerUp(syntheticEvent);\n },\n [handlePointerUp, canvasRef, stateMachine]\n );\n\n // Attach document listeners\n useEffect(() => {\n document.addEventListener('pointermove', handleDocumentMouseMove);\n document.addEventListener('pointerup', handleDocumentPointerUp);\n\n return () => {\n document.removeEventListener('pointermove', handleDocumentMouseMove);\n document.removeEventListener('pointerup', handleDocumentPointerUp);\n };\n }, [handleDocumentMouseMove, handleDocumentPointerUp]);\n\n return {\n handlePointerDown,\n handlePointerMove,\n handlePointerUp,\n handleDoubleClick,\n };\n}\n","/**\n * useTextEditingHandlers - Text editing event handlers and effects\n *\n * Extracted from CanvasEditor.tsx to isolate text editing logic including:\n * - Text edit completion (handleTextEditComplete)\n * - Blur handling (handleTextEditBlur)\n * - Keyboard handling during editing (handleTextKeyDown)\n * - Character-level formatting (applyFormattingToSelection, toggleFormattingProperty, getSelectionStyle)\n * - Cursor animation effects\n * - Formatting style tracking\n * - Edit position updates\n * - Textarea cursor sync\n * - Enter key to enter edit mode\n */\n\nimport { useCallback, useEffect, useRef } from 'react';\nimport { TextElement } from '../../../core/TextElement.js';\nimport { ImageElement } from '../../../core/ImageElement.js';\nimport { GroupElement } from '../../../core/GroupElement.js';\nimport { ShapeElement } from '../../../core/ShapeElement.js';\nimport type { TransformHandles } from '../../../core/TransformHandles.js';\nimport type { RichText, CharacterStyle } from '../../../types/index.js';\nimport { RotationUtils } from '../../../core/RotationUtils.js';\nimport { getCursorIndexFromPoint, moveCursorVertically } from '../../../utils/textCursorUtils.js';\nimport { wrapText } from '../../../core/TextMetrics.js';\nimport type { PathElement } from '../../../core/PathElement.js';\n\ntype CanvasElement = TextElement | ImageElement | GroupElement | ShapeElement | PathElement;\ntype ChildElement = TextElement | ImageElement | ShapeElement | PathElement | GroupElement;\n\n/**\n * Helper function to convert plain text to wrapped text with explicit newlines\n * that match the canvas wrapping behavior. This ensures textarea and canvas\n * wrap text identically, preventing cursor misalignment.\n */\nfunction getWrappedTextForEditing(element: TextElement): string {\n // For transforms that don't use standard wrapping (circle, arch, etc.),\n // return text as-is without wrapping\n if (element.transformType !== 'custom') {\n return element.text;\n }\n\n // Get visual bbox to determine wrapping width\n const visualBbox = element.getVisualBoundingBox();\n const maxWidth = visualBbox.width;\n\n // Use getText() method instead of .text property to get the latest value from richText\n const elementText = element.getText();\n\n // Use the same wrapText algorithm as canvas rendering\n const lines = wrapText(\n elementText,\n maxWidth,\n element.fontSize,\n element.fontFamily,\n element.bold,\n element.italic\n );\n\n // Join lines with newlines for textarea\n return lines.join('\\n');\n}\n\nexport interface UseTextEditingHandlersParams {\n // Refs\n canvasRef: React.RefObject<HTMLCanvasElement | null>;\n inputRef: React.RefObject<HTMLTextAreaElement | null>;\n editingChildIdRef: React.MutableRefObject<string | null>;\n editRichTextRef: React.MutableRefObject<RichText | null>;\n selectionAnchorRef: React.MutableRefObject<number>;\n cursorStartTimeRef: React.MutableRefObject<number>;\n lastMultiClickTimeRef: React.MutableRefObject<number>;\n multiClickSelectionRef: React.MutableRefObject<{ start: number; end: number; cursor: number } | null>;\n editPositionUpdateTimerRef: React.MutableRefObject<ReturnType<typeof setTimeout> | null>;\n draggingSelectionHandleRef: React.MutableRefObject<'start' | 'end' | null>;\n repositioningCursorRef: React.MutableRefObject<boolean>;\n editModeEnteredAtRef: React.MutableRefObject<number>;\n lastTouchTargetRef: React.MutableRefObject<HTMLElement | null>;\n\n // Constants\n MULTI_CLICK_DEBOUNCE: number;\n EDIT_MODE_BLUR_DEBOUNCE: number;\n\n // State values\n isEditing: boolean;\n editText: string;\n editRichText: RichText | null;\n cursorPosition: number;\n selectionStart: number;\n selectionEnd: number;\n selectedId: string | null;\n selectedElement: CanvasElement | undefined;\n activeChildElement: ChildElement | null;\n elements: CanvasElement[];\n zoom: number;\n paddingOffsetX: number;\n paddingOffsetY: number;\n // State setters\n setIsEditing: (v: boolean) => void;\n setEditText: (v: string) => void;\n setEditRichText: (v: RichText | null) => void;\n setCursorPosition: React.Dispatch<React.SetStateAction<number>>;\n setSelectionStart: React.Dispatch<React.SetStateAction<number>>;\n setSelectionEnd: React.Dispatch<React.SetStateAction<number>>;\n setEditPosition: (pos: { x: number; y: number; width: number; height: number }) => void;\n setCurrentFormattingStyle: React.Dispatch<React.SetStateAction<CharacterStyle | null>>;\n setCursorAnimationFrame: React.Dispatch<React.SetStateAction<number>>;\n setHandlesVersion: React.Dispatch<React.SetStateAction<number>>;\n setElements: React.Dispatch<React.SetStateAction<CanvasElement[]>>;\n\n // Callbacks\n onElementUpdate: (element: CanvasElement) => void;\n onTextSelectionChange?: () => void;\n transformHandles: TransformHandles;\n updateActiveChild: (child: ChildElement | null) => void;\n}\n\nexport interface TextEditingHandlersResult {\n handleTextEditComplete: () => void;\n handleTextEditBlur: () => void;\n handleTextKeyDown: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void;\n applyFormattingToSelection: (style: CharacterStyle) => void;\n getSelectionStyle: () => CharacterStyle | null;\n toggleFormattingProperty: (property: 'bold' | 'italic' | 'underline' | 'strikethrough') => void;\n enterTextEditMode: (element: TextElement, worldX: number, worldY: number) => void;\n getWrappedTextForEditing: (element: TextElement) => string;\n}\n\nexport function useTextEditingHandlers(params: UseTextEditingHandlersParams): TextEditingHandlersResult {\n const {\n canvasRef,\n inputRef,\n editingChildIdRef,\n editRichTextRef,\n selectionAnchorRef,\n cursorStartTimeRef,\n lastMultiClickTimeRef,\n multiClickSelectionRef,\n editPositionUpdateTimerRef,\n draggingSelectionHandleRef,\n repositioningCursorRef,\n editModeEnteredAtRef,\n lastTouchTargetRef,\n MULTI_CLICK_DEBOUNCE,\n EDIT_MODE_BLUR_DEBOUNCE,\n isEditing,\n editText,\n editRichText,\n cursorPosition,\n selectionStart,\n selectionEnd,\n selectedId,\n selectedElement,\n activeChildElement,\n elements,\n zoom,\n paddingOffsetX,\n paddingOffsetY,\n setIsEditing,\n setEditText,\n setEditRichText,\n setCursorPosition,\n setSelectionStart,\n setSelectionEnd,\n setEditPosition,\n setCurrentFormattingStyle,\n setCursorAnimationFrame,\n setHandlesVersion,\n setElements,\n onElementUpdate,\n onTextSelectionChange,\n transformHandles,\n updateActiveChild,\n } = params;\n\n // Helper to enter text edit mode for a text element\n const enterTextEditMode = useCallback(\n (element: TextElement, worldX: number, worldY: number) => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n const rect = canvas.getBoundingClientRect();\n const visualBbox = element.getVisualBoundingBox();\n\n // Calculate cursor position from click\n const rotationRad = RotationUtils.toRadiansInverse(element.rotation);\n const center = element.getRotationAnchor();\n const dx = worldX - center.x;\n const dy = worldY - center.y;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n const localRotatedX = dx * cos - dy * sin;\n const localRotatedY = dx * sin + dy * cos;\n const relativeX = localRotatedX + visualBbox.width / 2;\n const relativeY = localRotatedY + visualBbox.height / 2;\n\n const clickIndex = getCursorIndexFromPoint(element, { x: relativeX, y: relativeY });\n\n // Convert world coordinates to screen coordinates with zoom\n const contentHeight = visualBbox.height + 16; // +16 for padding\n\n // Offset for textarea padding (4px) + border (2px) = 6px\n const offset = 6;\n\n setEditPosition({\n x: rect.left + (visualBbox.x + paddingOffsetX) * zoom - offset,\n y: rect.top + (visualBbox.y + paddingOffsetY) * zoom - offset,\n width: Math.max(20, visualBbox.width * zoom),\n height: Math.max(contentHeight * zoom, 50),\n });\n setEditText(getWrappedTextForEditing(element));\n const initialRichText = element.getRichText().clone();\n editRichTextRef.current = initialRichText;\n setEditRichText(initialRichText);\n // Get style at end of text, falling back to element defaults\n const styleAtEnd = element.getRichText().getStyleAt(Math.max(0, element.text.length - 1));\n setCurrentFormattingStyle({ ...element.getDefaultStyle(), ...styleAtEnd });\n setCursorPosition(clickIndex);\n setSelectionStart(clickIndex);\n setSelectionEnd(clickIndex);\n setIsEditing(true);\n // Record when we entered edit mode to prevent immediate blur from exiting (iOS fix)\n editModeEnteredAtRef.current = Date.now();\n },\n [zoom, setEditPosition, setEditText, setEditRichText, setCurrentFormattingStyle, setCursorPosition, setSelectionStart, setSelectionEnd, setIsEditing]\n );\n\n // Apply formatting to selected text\n const applyFormattingToSelection = useCallback(\n (style: CharacterStyle) => {\n if (!isEditing || !editRichText) {\n return;\n }\n\n const start = Math.min(selectionStart, selectionEnd);\n const end = Math.max(selectionStart, selectionEnd);\n\n if (start === end) {\n // No selection - apply to ALL characters\n const updated = editRichText.clone();\n const textLength = updated.getText().length;\n updated.applyStyle(0, textLength, style);\n\n editRichTextRef.current = updated;\n setEditRichText(updated);\n setEditText(updated.getText());\n\n setCurrentFormattingStyle((prevStyle) => {\n const newStyle = { ...prevStyle, ...style };\n return newStyle;\n });\n } else {\n // Apply to selection\n const updated = editRichText.clone();\n updated.applyStyle(start, end, style);\n\n editRichTextRef.current = updated;\n setEditRichText(updated);\n setEditText(updated.getText());\n\n setCurrentFormattingStyle((prevStyle) => {\n const newStyle = { ...prevStyle, ...style };\n return newStyle;\n });\n }\n },\n [isEditing, editRichText, selectionStart, selectionEnd]\n );\n\n // Get the style of the current selection (for toolbar state)\n const getSelectionStyle = useCallback((): CharacterStyle | null => {\n if (!isEditing || !editRichText || !selectedElement) return null;\n\n const start = Math.min(selectionStart, selectionEnd);\n const end = Math.max(selectionStart, selectionEnd);\n\n if (start === end) {\n if (start === 0) {\n return selectedElement instanceof TextElement ? selectedElement.getDefaultStyle() : {};\n }\n return editRichText.getStyleAt(Math.max(0, start - 1));\n }\n\n return editRichText.getStyleAt(start);\n }, [isEditing, editRichText, selectionStart, selectionEnd, selectedElement]);\n\n // Toggle boolean formatting properties\n const toggleFormattingProperty = useCallback(\n (property: 'bold' | 'italic' | 'underline' | 'strikethrough') => {\n if (!isEditing || !editRichText || !selectedElement) return;\n\n const currentStyle = getSelectionStyle();\n const defaultStyle = selectedElement instanceof TextElement ? selectedElement.getDefaultStyle() : {};\n const effectiveStyle = { ...defaultStyle, ...currentStyle };\n const newValue = !effectiveStyle[property];\n\n applyFormattingToSelection({ [property]: newValue });\n },\n [isEditing, editRichText, selectedElement, getSelectionStyle, applyFormattingToSelection]\n );\n\n // Handle finishing text edit\n const handleTextEditComplete = useCallback(() => {\n if (!isEditing) return;\n\n const latestRichText = editRichTextRef.current || editRichText;\n\n if (selectedElement && latestRichText) {\n const editingChildId = editingChildIdRef.current;\n\n if (editingChildId && selectedElement instanceof GroupElement) {\n const updatedGroup = selectedElement.clone();\n const childIndex = updatedGroup.children.findIndex((c) => c.id === editingChildId);\n if (childIndex >= 0) {\n const updatedChild = updatedGroup.children[childIndex];\n if (updatedChild instanceof TextElement) {\n updatedChild.setRichText(latestRichText);\n }\n updatedGroup.updateBoundsFromChildren(true);\n }\n onElementUpdate(updatedGroup);\n\n const updatedChild = updatedGroup.children[childIndex];\n if (updatedChild) {\n updateActiveChild(updatedChild);\n transformHandles.update(updatedChild, zoom);\n setHandlesVersion((v) => v + 1);\n }\n } else if (selectedElement instanceof TextElement) {\n const updatedElement = selectedElement.clone();\n updatedElement.setRichText(latestRichText);\n onElementUpdate(updatedElement);\n\n transformHandles.update(updatedElement, zoom);\n setHandlesVersion((v) => v + 1);\n }\n }\n\n multiClickSelectionRef.current = null;\n setIsEditing(false);\n setEditText('');\n setEditRichText(null);\n editRichTextRef.current = null;\n setCurrentFormattingStyle(null);\n editingChildIdRef.current = null;\n }, [isEditing, editText, editRichText, editRichTextRef, selectedElement, onElementUpdate, transformHandles, updateActiveChild, zoom]);\n\n // Handle textarea blur\n const handleTextEditBlur = useCallback(() => {\n const now = Date.now();\n const timeSinceMultiClick = now - lastMultiClickTimeRef.current;\n if (timeSinceMultiClick < MULTI_CLICK_DEBOUNCE) {\n if (inputRef.current) {\n inputRef.current.focus();\n const savedSelection = multiClickSelectionRef.current;\n if (savedSelection) {\n inputRef.current.setSelectionRange(savedSelection.start, savedSelection.end);\n }\n }\n return;\n }\n\n setTimeout(() => {\n const timeSinceEditStart = Date.now() - editModeEnteredAtRef.current;\n if (timeSinceEditStart < EDIT_MODE_BLUR_DEBOUNCE) {\n inputRef.current?.focus();\n return;\n }\n\n if (repositioningCursorRef.current) {\n repositioningCursorRef.current = false;\n inputRef.current?.focus();\n if (inputRef.current) {\n inputRef.current.setSelectionRange(selectionStart, selectionEnd);\n }\n return;\n }\n\n const activeElement = document.activeElement;\n let element = activeElement as HTMLElement | null;\n let isToolbarClick = false;\n\n while (element) {\n const classList = element.classList;\n const dataSlot = element.getAttribute('data-slot');\n if (dataSlot && (dataSlot.includes('popover') || dataSlot.includes('dialog'))) {\n isToolbarClick = true;\n break;\n }\n if (element.hasAttribute('data-preserve-selection')) {\n isToolbarClick = true;\n break;\n }\n if (classList) {\n if (\n classList.contains('toolbar') ||\n classList.contains('toolbar-items') ||\n classList.contains('toolbar-btn') ||\n classList.contains('toolbar-btn-sm') ||\n classList.contains('toolbar-btn-icon') ||\n classList.contains('toolbar-color') ||\n classList.contains('toolbar-label') ||\n classList.contains('toolbar-select') ||\n classList.contains('toolbar-select-sm') ||\n classList.contains('toolbar-group') ||\n classList.contains('toolbar-scroll') ||\n classList.contains('toolbar-color-label') ||\n classList.contains('toolbar-color-square') ||\n classList.contains('color-picker-dropdown') ||\n classList.contains('text-color-panel') ||\n classList.contains('tooltip-wrapper') ||\n classList.contains('font-dropdown-with-label') ||\n classList.contains('font-family-select-button') ||\n classList.contains('more-menu-btn') ||\n classList.contains('more-menu-toolbar') ||\n classList.contains('font-drawer-overlay') ||\n classList.contains('font-drawer-content') ||\n classList.contains('font-drawer-handle') ||\n classList.contains('font-drawer-handle-bar') ||\n classList.contains('font-dropdown-list-container') ||\n classList.contains('font-dropdown-search') ||\n classList.contains('font-search-input') ||\n classList.contains('font-category-filters') ||\n classList.contains('font-category-btn') ||\n classList.contains('font-grid-option') ||\n classList.contains('font-section') ||\n classList.contains('font-section-header') ||\n classList.contains('font-section-used') ||\n classList.contains('font-section-header-used') ||\n classList.contains('font-dropdown-list-grid') ||\n classList.contains('font-dropdown-list-grid-used') ||\n classList.contains('font-preview') ||\n classList.contains('font-name-label') ||\n classList.contains('font-size-slider-container') ||\n classList.contains('font-size-slider') ||\n classList.contains('font-size-icon-small') ||\n classList.contains('font-size-icon-large') ||\n classList.contains('font-icon-toggle') ||\n classList.contains('font-dropdown-empty') ||\n classList.contains('vaul-drawer-wrapper') ||\n classList.contains('vaul-overlay')\n ) {\n isToolbarClick = true;\n break;\n }\n }\n element = element.parentElement;\n }\n\n // iOS Safari fix: also check the tracked touch target\n if (!isToolbarClick && lastTouchTargetRef.current) {\n element = lastTouchTargetRef.current;\n while (element) {\n const classList = element.classList;\n const dataSlot = element.getAttribute('data-slot');\n if (dataSlot && (dataSlot.includes('popover') || dataSlot.includes('dialog'))) {\n isToolbarClick = true;\n break;\n }\n if (element.hasAttribute('data-preserve-selection')) {\n isToolbarClick = true;\n break;\n }\n if (classList) {\n if (\n classList.contains('toolbar') ||\n classList.contains('toolbar-items') ||\n classList.contains('toolbar-btn') ||\n classList.contains('toolbar-btn-sm') ||\n classList.contains('toolbar-btn-icon') ||\n classList.contains('toolbar-color') ||\n classList.contains('toolbar-label') ||\n classList.contains('toolbar-select') ||\n classList.contains('toolbar-select-sm') ||\n classList.contains('toolbar-group') ||\n classList.contains('toolbar-scroll') ||\n classList.contains('toolbar-color-label') ||\n classList.contains('toolbar-color-square') ||\n classList.contains('color-picker-dropdown') ||\n classList.contains('text-color-panel') ||\n classList.contains('tooltip-wrapper') ||\n classList.contains('font-dropdown-with-label') ||\n classList.contains('font-family-select-button') ||\n classList.contains('more-menu-btn') ||\n classList.contains('more-menu-toolbar') ||\n classList.contains('vaul-drawer-wrapper') ||\n classList.contains('vaul-overlay')\n ) {\n isToolbarClick = true;\n break;\n }\n }\n element = element.parentElement;\n }\n }\n\n if (!isToolbarClick) {\n handleTextEditComplete();\n } else {\n const savedStart = selectionStart;\n const savedEnd = selectionEnd;\n inputRef.current?.focus();\n if (inputRef.current) {\n inputRef.current.setSelectionRange(savedStart, savedEnd);\n }\n }\n }, 0);\n }, [handleTextEditComplete, selectionStart, selectionEnd]);\n\n // Handle keyboard during editing\n const handleTextKeyDown = useCallback(\n (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\n const textarea = e.currentTarget;\n\n if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n handleTextEditComplete();\n } else if (e.key === 'Escape') {\n e.preventDefault();\n e.stopPropagation();\n setIsEditing(false);\n setEditText('');\n setEditRichText(null);\n editRichTextRef.current = null;\n setCurrentFormattingStyle(null);\n editingChildIdRef.current = null;\n }\n else if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'b') {\n e.preventDefault();\n toggleFormattingProperty('bold');\n } else if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'i') {\n e.preventDefault();\n toggleFormattingProperty('italic');\n } else if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'u') {\n e.preventDefault();\n toggleFormattingProperty('underline');\n }\n else if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {\n setTimeout(() => {\n const newPos = textarea.selectionStart;\n setCursorPosition(newPos);\n setSelectionStart(newPos);\n setSelectionEnd(textarea.selectionEnd);\n selectionAnchorRef.current = newPos;\n }, 0);\n }\n else if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {\n if (selectedElement && selectedElement instanceof TextElement) {\n e.preventDefault();\n\n const direction = e.key === 'ArrowUp' ? 'up' : 'down';\n const newPos = moveCursorVertically(selectedElement, cursorPosition, direction);\n\n setCursorPosition(newPos);\n setSelectionStart(newPos);\n setSelectionEnd(newPos);\n selectionAnchorRef.current = newPos;\n\n textarea.setSelectionRange(newPos, newPos);\n }\n }\n else if (e.key === 'Home' || e.key === 'End') {\n setTimeout(() => {\n const newPos = textarea.selectionStart;\n setCursorPosition(newPos);\n setSelectionStart(newPos);\n setSelectionEnd(textarea.selectionEnd);\n selectionAnchorRef.current = newPos;\n }, 0);\n }\n },\n [handleTextEditComplete, toggleFormattingProperty, setCursorPosition, setSelectionStart, setSelectionEnd, selectionAnchorRef, cursorPosition, selectedElement]\n );\n\n // ============================================\n // EFFECTS\n // ============================================\n\n // Reset cursor animation start time when entering edit mode or moving cursor\n useEffect(() => {\n if (isEditing) {\n cursorStartTimeRef.current = performance.now();\n }\n }, [isEditing, cursorPosition, cursorStartTimeRef]);\n\n // RAF loop to keep cursor animating during edit mode\n useEffect(() => {\n if (!isEditing) return;\n\n let rafId: number;\n let lastUpdate = 0;\n const FRAME_INTERVAL = 33; // ~30fps\n\n const animate = (timestamp: number) => {\n if (timestamp - lastUpdate >= FRAME_INTERVAL) {\n setCursorAnimationFrame((prev) => (prev + 1) % 1000);\n lastUpdate = timestamp;\n }\n rafId = requestAnimationFrame(animate);\n };\n\n rafId = requestAnimationFrame(animate);\n return () => cancelAnimationFrame(rafId);\n }, [isEditing]);\n\n // Load font and focus input when editing starts\n const prevIsEditingRef = useRef(false);\n useEffect(() => {\n if (isEditing && !prevIsEditingRef.current && inputRef.current) {\n inputRef.current.focus();\n const endPos = editText.length;\n setCursorPosition(endPos);\n setSelectionStart(endPos);\n setSelectionEnd(endPos);\n inputRef.current.setSelectionRange(endPos, endPos);\n }\n prevIsEditingRef.current = isEditing;\n }, [isEditing, editText]);\n\n // Ref to track last formatting style to prevent infinite loops\n const lastFormattingStyleRef = useRef<CharacterStyle | null>(null);\n\n const stylesAreEqual = (a: CharacterStyle | null, b: CharacterStyle | null): boolean => {\n if (a === b) return true;\n if (!a || !b) return false;\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 );\n };\n\n // Update current formatting style when cursor moves\n useEffect(() => {\n if (!isEditing || !editRichText) return;\n\n const start = Math.min(selectionStart, selectionEnd);\n const end = Math.max(selectionStart, selectionEnd);\n\n if (start === end) {\n let newStyle = null;\n\n if (start > 0) {\n newStyle = editRichText.getStyleAt(start - 1);\n } else {\n const element = elements.find((e) => e.id === selectedId);\n if (element instanceof TextElement) {\n newStyle = element.getDefaultStyle();\n }\n }\n\n if (newStyle && !stylesAreEqual(newStyle, lastFormattingStyleRef.current)) {\n lastFormattingStyleRef.current = newStyle;\n setCurrentFormattingStyle(newStyle);\n }\n }\n }, [isEditing, editRichText, selectionStart, selectionEnd, selectedId]);\n\n // Sync textarea cursor/selection position with state\n useEffect(() => {\n if (!isEditing || !inputRef.current) return;\n\n const textarea = inputRef.current;\n\n if (textarea.selectionStart !== selectionStart || textarea.selectionEnd !== selectionEnd) {\n if (selectionStart !== selectionEnd) {\n textarea.setSelectionRange(selectionStart, selectionEnd);\n } else {\n textarea.setSelectionRange(cursorPosition, cursorPosition);\n }\n }\n }, [editText, isEditing, cursorPosition, selectionStart, selectionEnd]);\n\n // Update edit position dynamically as text changes\n useEffect(() => {\n const selectedEl = elements.find((e) => e.id === selectedId);\n if (!isEditing || !selectedEl || !canvasRef.current || !inputRef.current) return;\n\n if (editPositionUpdateTimerRef.current) {\n clearTimeout(editPositionUpdateTimerRef.current);\n }\n\n editPositionUpdateTimerRef.current = setTimeout(() => {\n let editingElement: TextElement | ImageElement | GroupElement | ShapeElement | PathElement = selectedEl;\n if (editingChildIdRef.current && selectedEl instanceof GroupElement) {\n const child = selectedEl.children.find((c) => c.id === editingChildIdRef.current);\n if (child) {\n editingElement = child;\n }\n }\n\n if (!(editingElement instanceof TextElement)) return;\n\n const tempElement = editingElement.clone();\n if (editRichText) {\n tempElement.setRichText(editRichText);\n }\n\n const visualBbox = tempElement.getVisualBoundingBox();\n const rect = canvasRef.current!.getBoundingClientRect();\n const contentHeight = visualBbox.height + 16;\n const offset = 6;\n\n setEditPosition({\n x: rect.left + (visualBbox.x + paddingOffsetX) * zoom - offset,\n y: rect.top + (visualBbox.y + paddingOffsetY) * zoom - offset,\n width: Math.max(20, visualBbox.width * zoom),\n height: Math.max(contentHeight * zoom, 50),\n });\n\n if (editingChildIdRef.current && selectedEl instanceof GroupElement) {\n const updatedGroup = selectedEl.clone();\n const childIndex = updatedGroup.children.findIndex((c) => c.id === editingChildIdRef.current);\n if (childIndex >= 0) {\n updatedGroup.children[childIndex] = tempElement;\n updatedGroup.updateBoundsFromChildren(true);\n }\n setElements((prevElements) => prevElements.map((e) => (e.id === updatedGroup.id ? updatedGroup : e)));\n transformHandles.update(tempElement, zoom);\n } else {\n setElements((prevElements) => prevElements.map((e) => (e.id === tempElement.id ? tempElement : e)));\n transformHandles.update(tempElement, zoom);\n }\n\n setHandlesVersion((v) => v + 1);\n }, 50);\n\n return () => {\n if (editPositionUpdateTimerRef.current) {\n clearTimeout(editPositionUpdateTimerRef.current);\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [editText, editRichText, isEditing, selectedId, zoom]);\n\n // Update cursor position and selection when textarea selection changes\n useEffect(() => {\n if (!isEditing || !inputRef.current) return;\n\n const textarea = inputRef.current;\n const updateCursor = () => {\n if (draggingSelectionHandleRef.current) {\n return;\n }\n\n const timeSinceMultiClick = Date.now() - lastMultiClickTimeRef.current;\n if (timeSinceMultiClick < MULTI_CLICK_DEBOUNCE) {\n return;\n }\n\n const start = textarea.selectionStart || 0;\n const end = textarea.selectionEnd || 0;\n setCursorPosition((prev) => prev === end ? prev : end);\n setSelectionStart((prev) => prev === start ? prev : start);\n setSelectionEnd((prev) => prev === end ? prev : end);\n\n onTextSelectionChange?.();\n };\n\n textarea.addEventListener('click', updateCursor);\n textarea.addEventListener('keyup', updateCursor);\n textarea.addEventListener('select', updateCursor);\n textarea.addEventListener('mouseup', updateCursor);\n\n const handleSelectionChange = () => {\n if (document.activeElement === textarea) {\n updateCursor();\n }\n };\n document.addEventListener('selectionchange', handleSelectionChange);\n\n updateCursor();\n\n return () => {\n textarea.removeEventListener('click', updateCursor);\n textarea.removeEventListener('keyup', updateCursor);\n textarea.removeEventListener('select', updateCursor);\n textarea.removeEventListener('mouseup', updateCursor);\n document.removeEventListener('selectionchange', handleSelectionChange);\n };\n }, [isEditing]);\n\n // Handle Enter key to enter edit mode for selected text element\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n const target = e.target as HTMLElement;\n if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') return;\n if (e.key !== 'Enter') return;\n if (isEditing) return;\n if (!canvasRef.current) return;\n\n const rect = canvasRef.current.getBoundingClientRect();\n\n // Check if there's an active child element in a group (text element only)\n if (\n activeChildElement &&\n activeChildElement instanceof TextElement &&\n !(activeChildElement instanceof GroupElement) &&\n !(activeChildElement instanceof ImageElement) &&\n !(activeChildElement instanceof ShapeElement)\n ) {\n e.preventDefault();\n const visualBbox = activeChildElement.getVisualBoundingBox();\n const contentHeight = visualBbox.height + 16;\n const offset = 6;\n\n setEditPosition({\n x: rect.left + (visualBbox.x + paddingOffsetX) * zoom - offset,\n y: rect.top + (visualBbox.y + paddingOffsetY) * zoom - offset,\n width: Math.max(200, visualBbox.width * zoom),\n height: Math.max(contentHeight * zoom, 50),\n });\n setEditText(getWrappedTextForEditing(activeChildElement));\n setIsEditing(true);\n editModeEnteredAtRef.current = Date.now();\n editingChildIdRef.current = activeChildElement.id;\n return;\n }\n\n // Check if there's a selected text element\n if (\n selectedElement &&\n selectedElement instanceof TextElement &&\n !(selectedElement instanceof GroupElement) &&\n !(selectedElement instanceof ImageElement) &&\n !(selectedElement instanceof ShapeElement)\n ) {\n e.preventDefault();\n const visualBbox = selectedElement.getVisualBoundingBox();\n const contentHeight = visualBbox.height + 16;\n const offset = 6;\n\n setEditPosition({\n x: rect.left + (visualBbox.x + paddingOffsetX) * zoom - offset,\n y: rect.top + (visualBbox.y + paddingOffsetY) * zoom - offset,\n width: Math.max(200, visualBbox.width * zoom),\n height: Math.max(contentHeight * zoom, 50),\n });\n setEditText(getWrappedTextForEditing(selectedElement));\n setIsEditing(true);\n editModeEnteredAtRef.current = Date.now();\n }\n };\n\n window.addEventListener('keydown', handleKeyDown);\n return () => window.removeEventListener('keydown', handleKeyDown);\n }, [selectedElement, activeChildElement, isEditing]);\n\n return {\n handleTextEditComplete,\n handleTextEditBlur,\n handleTextKeyDown,\n applyFormattingToSelection,\n getSelectionStyle,\n toggleFormattingProperty,\n enterTextEditMode,\n getWrappedTextForEditing,\n };\n}\n","/**\n * CanvasEditor - Main component for text transformation canvas\n * Manages elements, selection, interaction, and rendering\n *\n * This is the orchestrator component. Heavy logic has been extracted into:\n * - useCanvasRenderLoop: Canvas rendering (DPR, zoom, artboards, elements, overlays)\n * - useCanvasInteraction: Pointer/mouse event handlers (drag, resize, rotate, selection)\n * - useTextEditingHandlers: Text editing (formatting, blur, keyboard, cursor sync)\n */\n\nimport React, { useRef, useState, useEffect, useCallback, useMemo, forwardRef, useImperativeHandle } from 'react';\nimport { createPortal } from 'react-dom';\nimport { TransformHandles } from '../core/TransformHandles.js';\nimport { globalResizePipeline } from '../core/ResizePipeline.js';\nimport { InteractionStateMachine } from '../core/InteractionStateMachine.js';\nimport { AlignmentSnapSystem } from '../core/AlignmentSnapSystem.js';\nimport { SpacingSystem } from '../core/SpacingSystem.js';\nimport { ArtboardRenderer } from '../core/ArtboardRenderer.js';\nimport { TextElement } from '../core/TextElement.js';\nimport { ImageElement } from '../core/ImageElement.js';\nimport { GroupElement } from '../core/GroupElement.js';\nimport { PathElement } from '../core/PathElement.js';\nimport { ShapeElement } from '../core/ShapeElement.js';\nimport { ArtboardElement } from '../core/ArtboardElement.js';\nimport type { ArtboardManager } from '../core/ArtboardManager.js';\nimport type { CharacterStyle } from '../types/index.js';\nimport { RichText } from '../types/index.js';\nimport { useTheme } from '../contexts/ThemeContext.js';\nimport { RotationUtils } from '../core/RotationUtils.js';\nimport { getCursorIndexFromPoint, getWordBoundaryAt } from '../utils/textCursorUtils.js';\n\n/** Union type for all canvas element types */\ntype CanvasElement = TextElement | ImageElement | GroupElement | ShapeElement | PathElement;\n\n// Import custom hooks\nimport {\n useMarqueeSelection,\n usePenTool,\n useActiveChild,\n useHoverState,\n useCropMode,\n useMultiSelection,\n useTextEditing,\n useCanvasRenderLoop,\n useCanvasInteraction,\n useTextEditingHandlers,\n useCanvasLayout,\n useInteractionState,\n useRenderState,\n} from './CanvasEditor/hooks/index.js';\n\ninterface CanvasEditorProps {\n elements: CanvasElement[];\n artboards: ArtboardElement[];\n artboardManager: ArtboardManager;\n selectedId: string | null;\n multiSelection: string[];\n onElementUpdate: (element: CanvasElement) => void;\n setElements: React.Dispatch<React.SetStateAction<CanvasElement[]>>;\n executeElementUpdate: (\n oldElement: CanvasElement | undefined,\n newElement: CanvasElement\n ) => void;\n onSelectionChange: (id: string | null) => void;\n onMultiSelectionChange: (ids: string[]) => void;\n onActiveChildChange?: (child: CanvasElement | null) => void;\n onTextSelectionChange?: () => void;\n onHoverChange?: (elementId: string | null) => void;\n onRotationStateChange?: (isRotating: boolean) => void;\n onCropModeEnter?: () => void;\n hideHandles?: boolean;\n canvasRef?: React.RefObject<HTMLCanvasElement | null>;\n width?: number;\n height?: number;\n zoom?: number;\n offsetX?: number;\n offsetY?: number;\n viewPadding?: number;\n artboardBorderRadius?: number;\n fixedMargin?: number;\n fixedMarginX?: number;\n fixedMarginY?: number;\n showRotationHandle?: boolean;\n /** ADR-0054 Phase 4: cutout shapes (artboard px) punched through the\n * `<canvas>` as truly transparent holes. Translated internally to a\n * CSS `clip-path: path(evenodd, ...)` that accounts for padding +\n * zoom, so consumers can think purely in artboard coordinates. */\n canvasCutouts?: import(\"./embed/SnowconeCanvas\").CanvasCutoutShape[];\n /** ADR-0054: piece-guide overlays drawn inside the canvas render loop. */\n pieceGuides?: import(\"./embed/SnowconeCanvas\").PieceGuidesConfig;\n /**\n * ADR-0060: which piece (if any) the editor is focused on inside a\n * multi-piece spread. `'spread'` (or undefined) shows every piece in\n * the layout at full size — the historical behaviour. `{ pieceId }`\n * fits the viewport to that piece (with a peek margin so neighbouring\n * pieces remain visible at reduced opacity for overflow feedback).\n * The pieces themselves come from `pieceGuides.pieces`.\n *\n * Spread-mode is derived from `pieces.length`: layouts with a single\n * piece are unaffected by this prop (the toggle is a no-op for them).\n */\n pieceFocus?: 'spread' | { pieceId: string };\n /**\n * ADR-0060: optional override for the focused-piece rect, used by\n * the parent (`Canvas.tsx`) to feed in an animated rect when\n * switching pieces. When provided, it replaces the rect that would\n * otherwise be derived from `pieceFocus` + `pieceGuides`. Lets the\n * zoom-fit math (lives in `Canvas.tsx`) and the layout/render-loop\n * (lives here) share one interpolating value during the dolly.\n */\n focusedPieceRect?: { x: number; y: number; width: number; height: number } | null;\n}\n\nexport interface CanvasEditorHandle {\n applyTextFormatting: (style: CharacterStyle) => void;\n toggleFormattingProperty: (property: 'bold' | 'italic' | 'underline' | 'strikethrough') => void;\n isEditingText: () => boolean;\n isRotating: () => boolean;\n getSelectionStyle: () => CharacterStyle | null;\n getZoom: () => number;\n forceRender: () => void;\n getEditingState: () => {\n isEditing: boolean;\n editText: string;\n cursorPosition: number;\n selectionStart: number;\n selectionEnd: number;\n };\n getCropState: () => {\n isCropping: boolean;\n elementId: string | null;\n };\n getPaddingOffset: () => { x: number; y: number };\n}\n\nconst CanvasEditor = forwardRef<CanvasEditorHandle, CanvasEditorProps>(\n (\n {\n elements,\n artboards,\n artboardManager,\n selectedId,\n multiSelection,\n onElementUpdate,\n setElements,\n executeElementUpdate,\n onSelectionChange,\n onMultiSelectionChange,\n onActiveChildChange,\n onTextSelectionChange,\n onHoverChange,\n onRotationStateChange,\n onCropModeEnter,\n hideHandles = false,\n canvasRef: externalCanvasRef,\n width,\n height,\n zoom = 1.0,\n offsetX = 0,\n offsetY = 0,\n viewPadding = 1.0,\n artboardBorderRadius = 0,\n fixedMargin,\n fixedMarginX,\n fixedMarginY,\n showRotationHandle = true,\n canvasCutouts,\n pieceGuides,\n pieceFocus,\n focusedPieceRect: focusedPieceRectOverride,\n },\n ref\n ) => {\n // ============================================\n // THEME\n // ============================================\n const { resolvedTheme } = useTheme();\n\n // ============================================\n // REFS\n // ============================================\n const internalCanvasRef = useRef<HTMLCanvasElement>(null);\n const canvasRef = externalCanvasRef || internalCanvasRef;\n const inputRef = useRef<HTMLTextAreaElement>(null);\n\n // Text editing coordination refs\n const repositioningCursorRef = useRef(false);\n const lastClickTimeRef = useRef<number>(0);\n const clickCountRef = useRef<number>(0);\n const editModeEnteredAtRef = useRef<number>(0);\n const skipDragSelectionRef = useRef<boolean>(false);\n const lastMultiClickTimeRef = useRef<number>(0);\n const multiClickSelectionRef = useRef<{ start: number; end: number; cursor: number } | null>(null);\n const lastProcessedClickTimeRef = useRef<number>(0);\n const draggingSelectionHandleRef = useRef<'start' | 'end' | null>(null);\n const selectionHandlePositionsRef = useRef<{\n start: { center: { x: number; y: number }; textEdge: { x: number; y: number } } | null;\n end: { center: { x: number; y: number }; textEdge: { x: number; y: number } } | null;\n }>({ start: null, end: null });\n const lastMousePosRef = useRef<{ x: number; y: number } | null>(null);\n const dragOriginalElementsRef = useRef<Map<string, CanvasElement> | null>(null);\n const rotatingElementRef = useRef<CanvasElement | null>(null);\n const editPositionUpdateTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n // Constants\n const MULTI_CLICK_THRESHOLD = 400;\n const MULTI_CLICK_DEBOUNCE = 300;\n const CLICK_DEDUP_THRESHOLD = 50;\n const EDIT_MODE_BLUR_DEBOUNCE = 200;\n\n // ============================================\n // COMPOSITE HOOKS: Layout, Interaction, Render\n // ============================================\n // ADR-0060: when `pieceFocus` names a piece in the active layout's\n // `pieces[]`, useCanvasLayout fits the viewport to that piece's\n // rect plus a peek margin (so neighbour pieces remain visible at\n // edges for overflow feedback). Spread mode (or unrecognised piece\n // id) falls back to artboard-sized viewport.\n // When the parent (Canvas.tsx) supplies an animated rect, defer to\n // it; otherwise derive from `pieceFocus` directly so this component\n // still works standalone (e.g. inside a host that doesn't animate).\n const derivedFocusedPieceRect = React.useMemo(() => {\n if (!pieceFocus || pieceFocus === 'spread') return null;\n const pieces = pieceGuides?.pieces ?? [];\n // Single-piece layouts ignore pieceFocus — focus toggle is a\n // no-op for them per ADR-0060.\n if (pieces.length <= 1) return null;\n const match = pieces.find((p) => p.id === pieceFocus.pieceId);\n if (!match) return null;\n return { x: match.x, y: match.y, width: match.width, height: match.height };\n }, [pieceFocus, pieceGuides]);\n const focusedPieceRect =\n focusedPieceRectOverride !== undefined\n ? focusedPieceRectOverride\n : derivedFocusedPieceRect;\n\n // Selection-clipping rect for the hit-test focus filter. Falls\n // back to the single piece's bounds for products with one piece\n // (phone cases, etc.) so clicks outside the clipped boundary\n // don't select elements that are primarily inside it.\n const selectionClipRect = React.useMemo(() => {\n if (focusedPieceRect) return focusedPieceRect;\n const pieces = pieceGuides?.pieces ?? [];\n if (pieces.length === 1) {\n const p = pieces[0];\n return { x: p.x, y: p.y, width: p.width, height: p.height };\n }\n return null;\n }, [focusedPieceRect, pieceGuides]);\n\n const {\n isTouchDevice,\n canvasSize,\n lastTouchTargetRef,\n effectiveViewPadding,\n paddedCanvasWidth,\n paddedCanvasHeight,\n paddingOffsetX,\n paddingOffsetY,\n zoomInvariantBorderRadius,\n } = useCanvasLayout({\n width, height, zoom, viewPadding, artboardBorderRadius,\n fixedMargin, fixedMarginX, fixedMarginY,\n artboards, artboardManager,\n focusedPieceRect,\n });\n\n const {\n snapGuides, setSnapGuides,\n spacingIndicators, setSpacingIndicators,\n isAltKeyPressed, setIsAltKeyPressed,\n isRotating, setIsRotating,\n setRotationStartAngle,\n currentRotation, setCurrentRotation,\n setRotatingElementVersion,\n } = useInteractionState();\n\n const {\n renderVersion,\n forceRender,\n handlesVersion, setHandlesVersion,\n forceUpdate,\n cursorAnimationFrame, setCursorAnimationFrame,\n } = useRenderState({ elements });\n\n // ============================================\n // CORE SYSTEMS (stable references)\n // ============================================\n const [transformHandles] = useState(() => new TransformHandles());\n const [artboardRenderer] = useState(() => new ArtboardRenderer({ showBorder: false, showLabel: false }));\n const [snapSystem] = useState(() => new AlignmentSnapSystem({ enabled: true, showGuides: true }));\n const [spacingSystem] = useState(() => new SpacingSystem({ enabled: true, showLabels: true }));\n const [stateMachine] = useState(() => {\n const machine = new InteractionStateMachine();\n machine.setResizePipeline(globalResizePipeline);\n machine.setSnapHandler(snapSystem);\n return machine;\n });\n\n // Update artboardRenderer borderRadius\n useEffect(() => {\n artboardRenderer.setOptions({ borderRadius: zoomInvariantBorderRadius });\n }, [zoomInvariantBorderRadius, artboardRenderer]);\n\n // ADR-0060: when a piece is focused inside a multi-piece spread,\n // expose the piece's bounds as additional snap targets. The snap\n // system then offers piece-center / piece-edge guides alongside\n // the regular element-to-element snapping, so dragging an element\n // to \"centre on Front\" feels right — it snaps to the visible\n // piece's centre, not the full 4042×3075 spread's centre. Cleared\n // when no piece is focused (spread/'All' view, single-piece\n // products) so single-piece editing keeps its old behaviour.\n useEffect(() => {\n const focused = focusedPieceRectOverride !== undefined\n ? focusedPieceRectOverride\n : derivedFocusedPieceRect;\n snapSystem.setExtraSnapRects(\n focused\n ? [{ id: 'focused-piece', ...focused }]\n : [],\n );\n }, [snapSystem, focusedPieceRectOverride, derivedFocusedPieceRect]);\n\n // ============================================\n // ELEMENT REFS (for use in callbacks)\n // ============================================\n const elementsRef = useRef(elements);\n elementsRef.current = elements;\n\n const selectedElement = elements.find((e) => e.id === selectedId);\n const selectedElementRef = useRef(selectedElement);\n selectedElementRef.current = selectedElement;\n\n // ============================================\n // ARIA ANNOUNCEMENTS\n // ============================================\n const [ariaAnnouncement, setAriaAnnouncement] = useState('');\n\n useEffect(() => {\n if (!selectedId) return;\n const el = elements.find((e) => e.id === selectedId);\n if (!el) return;\n if (el instanceof TextElement) {\n const label = (el as TextElement).text?.slice(0, 40) || 'empty';\n setAriaAnnouncement(`Selected text element: ${label}`);\n } else if (el instanceof ImageElement) {\n setAriaAnnouncement('Selected image element');\n } else if (el instanceof GroupElement) {\n setAriaAnnouncement('Selected group');\n } else {\n setAriaAnnouncement('Selected element');\n }\n }, [selectedId, elements]);\n\n const artboardManagerRef = useRef(artboardManager);\n artboardManagerRef.current = artboardManager;\n\n // ============================================\n // SNAP HOOK\n // ============================================\n const elementsByArtboardRef = useRef(new Map<string | null, CanvasElement[]>());\n\n useEffect(() => {\n const snapHook = (result: unknown) => {\n const resizeResult = result as { element: CanvasElement; handle: unknown; startData: unknown; [key: string]: unknown };\n const activeArtboardId = artboardManagerRef.current.getActiveArtboardId();\n const activeArtboardElements = elementsByArtboardRef.current.get(activeArtboardId) || [];\n const allElements: CanvasElement[] = [];\n activeArtboardElements.forEach((e) => {\n if (e instanceof GroupElement) {\n allElements.push(...e.children);\n } else {\n allElements.push(e);\n }\n });\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- ResizeResult type is internal to AlignmentSnapSystem\n return snapSystem.snapDimensions(resizeResult as any, allElements, (resizeResult as any).startData);\n };\n globalResizePipeline.addHook('afterResize', snapHook);\n return () => { globalResizePipeline.removeHook('afterResize', snapHook); };\n }, [snapSystem]);\n\n // ============================================\n // CUSTOM HOOKS (existing Phase 1-3 extractions)\n // ============================================\n const { cropController } = useCropMode();\n const { penTool, penCursor } = usePenTool({ selectedElement });\n\n const {\n isEditing, setIsEditing,\n editText, setEditText,\n editPosition, setEditPosition,\n cursorPosition, setCursorPosition,\n selectionStart, setSelectionStart,\n selectionEnd, setSelectionEnd,\n cursorOpacityRef, cursorStartTimeRef,\n selectionAnchorRef, didDragSelectRef,\n editRichText, setEditRichText, editRichTextRef,\n currentFormattingStyle: _currentFormattingStyle,\n setCurrentFormattingStyle,\n } = useTextEditing({ selectedElement, ref });\n\n // Multi-click selection force-restore\n useEffect(() => {\n const timeSinceMultiClick = Date.now() - lastMultiClickTimeRef.current;\n const savedSelection = multiClickSelectionRef.current;\n if (savedSelection && timeSinceMultiClick < MULTI_CLICK_DEBOUNCE) {\n if (selectionStart !== savedSelection.start || selectionEnd !== savedSelection.end) {\n setCursorPosition(savedSelection.cursor);\n setSelectionStart(savedSelection.start);\n setSelectionEnd(savedSelection.end);\n if (inputRef.current) {\n inputRef.current.setSelectionRange(savedSelection.start, savedSelection.end);\n }\n }\n } else if (savedSelection && timeSinceMultiClick >= MULTI_CLICK_DEBOUNCE) {\n multiClickSelectionRef.current = null;\n }\n }, [selectionStart, selectionEnd, cursorPosition]);\n\n const {\n isMarqueeSelecting, marqueeStart, marqueeEnd, marqueePreviewSelection,\n startMarqueeSelection, updateMarqueeSelection, finishMarqueeSelection,\n } = useMarqueeSelection();\n\n const {\n multiSelectionGroupBoundsRef, multiSelectionOBBRef,\n } = useMultiSelection(multiSelection);\n\n const {\n activeChildElement, updateActiveChild, editingChildIdRef,\n } = useActiveChild({ elements, selectedElement, transformHandles, onActiveChildChange });\n\n const { hoverState, setHoverState } = useHoverState({ onHoverChange });\n\n // ============================================\n // ELEMENT CACHE BY ARTBOARD\n // ============================================\n const elementsByArtboard = useMemo(() => {\n const cache = new Map<string | null, CanvasElement[]>();\n elements.forEach((element) => {\n const artboardId = artboardManager.getArtboardIdForElement(element.id);\n if (!cache.has(artboardId)) { cache.set(artboardId, []); }\n cache.get(artboardId)!.push(element);\n });\n return cache;\n }, [elements, artboardManager]);\n\n elementsByArtboardRef.current = elementsByArtboard;\n\n const getActiveArtboardElements = useCallback(() => {\n const activeArtboardId = artboardManager.getActiveArtboardId();\n return elementsByArtboard.get(activeArtboardId) || [];\n }, [elementsByArtboard, artboardManager]);\n\n // ============================================\n // ALT KEY + PEN TOOL SHORTCUTS\n // ============================================\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n if (isEditing) return;\n\n if (penTool.isActive() && selectedElement instanceof PathElement) {\n if (e.key === 'Escape') {\n e.preventDefault();\n penTool.finishPath();\n const updatedElement = selectedElement.clone();\n executeElementUpdate(selectedElement, updatedElement);\n setHandlesVersion((v) => v + 1);\n return;\n } else if (e.key === 'Backspace' || e.key === 'Delete') {\n e.preventDefault();\n penTool.deleteLastPoint();\n setHandlesVersion((v) => v + 1);\n return;\n }\n }\n\n if (e.altKey) { setIsAltKeyPressed(true); }\n };\n\n const handleKeyUp = (e: KeyboardEvent) => {\n if (!e.altKey) { setIsAltKeyPressed(false); }\n };\n\n const handleBlur = (e: FocusEvent) => {\n if (!e.relatedTarget || !(e.relatedTarget as Node).ownerDocument) {\n setIsAltKeyPressed(false);\n }\n };\n\n window.addEventListener('keydown', handleKeyDown);\n window.addEventListener('keyup', handleKeyUp);\n window.addEventListener('blur', handleBlur as EventListener);\n return () => {\n window.removeEventListener('keydown', handleKeyDown);\n window.removeEventListener('keyup', handleKeyUp);\n window.removeEventListener('blur', handleBlur);\n };\n }, [penTool, selectedElement, executeElementUpdate, setHandlesVersion, isEditing]);\n\n // Re-detect spacing when Alt key is pressed/released\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas || !lastMousePosRef.current ||\n stateMachine.getMode() === 'drag' || stateMachine.getMode() === 'resize') {\n return;\n }\n\n const { x, y } = lastMousePosRef.current;\n\n if (isAltKeyPressed) {\n let hoveredElement: CanvasElement | null = null;\n const activeArtboardElements = getActiveArtboardElements();\n for (let i = activeArtboardElements.length - 1; i >= 0; i--) {\n const element = activeArtboardElements[i];\n if (element.visible === false) continue;\n if (element instanceof GroupElement) {\n for (let j = element.children.length - 1; j >= 0; j--) {\n const child = element.children[j];\n if (child.visible === false) continue;\n const bbox = child.getVisualBoundingBox();\n if (x >= bbox.x && x <= bbox.x + bbox.width && y >= bbox.y && y <= bbox.y + bbox.height) {\n hoveredElement = child;\n break;\n }\n }\n } else {\n const bbox = element.getVisualBoundingBox();\n if (x >= bbox.x && x <= bbox.x + bbox.width && y >= bbox.y && y <= bbox.y + bbox.height) {\n hoveredElement = element;\n break;\n }\n }\n if (hoveredElement) break;\n }\n\n if (hoveredElement) {\n const flattenedElements: CanvasElement[] = [];\n activeArtboardElements.forEach((e) => {\n if (e instanceof GroupElement) { flattenedElements.push(...e.children); }\n else { flattenedElements.push(e); }\n });\n const indicators = spacingSystem.detectSpacing(hoveredElement, flattenedElements, true);\n setSpacingIndicators(indicators);\n }\n } else {\n setSpacingIndicators([]);\n }\n }, [isAltKeyPressed, getActiveArtboardElements, spacingSystem, stateMachine]);\n\n // ============================================\n // TRANSFORM HANDLES UPDATE\n // ============================================\n const lastHandlesUpdateRef = useRef<string | null>(null);\n\n useEffect(() => {\n const selectedEl = elements.find((e) => e.id === selectedId);\n const elementToUpdate = activeChildElement || selectedEl;\n\n if (elementToUpdate) {\n const bbox = elementToUpdate.getBoundingBox();\n const elementKey = `${elementToUpdate.id}-${bbox.x}-${bbox.y}-${bbox.width}-${bbox.height}-${elementToUpdate.rotation}`;\n\n if (lastHandlesUpdateRef.current !== elementKey) {\n lastHandlesUpdateRef.current = elementKey;\n transformHandles.update(elementToUpdate, zoom);\n setHandlesVersion((v) => v + 1);\n }\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [selectedId, activeChildElement, transformHandles, zoom, elements]);\n\n // ============================================\n // EXTRACTED HOOKS (Phase 4)\n // ============================================\n\n // Text editing handlers\n const {\n handleTextEditBlur,\n handleTextKeyDown,\n applyFormattingToSelection,\n getSelectionStyle,\n toggleFormattingProperty,\n enterTextEditMode,\n getWrappedTextForEditing,\n } = useTextEditingHandlers({\n canvasRef,\n inputRef,\n editingChildIdRef,\n editRichTextRef,\n selectionAnchorRef,\n cursorStartTimeRef,\n lastMultiClickTimeRef,\n multiClickSelectionRef,\n editPositionUpdateTimerRef,\n draggingSelectionHandleRef,\n repositioningCursorRef,\n editModeEnteredAtRef,\n lastTouchTargetRef,\n MULTI_CLICK_DEBOUNCE,\n EDIT_MODE_BLUR_DEBOUNCE,\n isEditing,\n editText,\n editRichText,\n cursorPosition,\n selectionStart,\n selectionEnd,\n selectedId,\n selectedElement,\n activeChildElement,\n elements,\n zoom,\n paddingOffsetX,\n paddingOffsetY,\n setIsEditing,\n setEditText,\n setEditRichText,\n setCursorPosition,\n setSelectionStart,\n setSelectionEnd,\n setEditPosition,\n setCurrentFormattingStyle,\n setCursorAnimationFrame,\n setHandlesVersion,\n setElements,\n onElementUpdate,\n onTextSelectionChange,\n transformHandles,\n updateActiveChild,\n });\n\n // Canvas interaction handlers\n const {\n handlePointerDown,\n handlePointerMove,\n handlePointerUp,\n handleDoubleClick,\n } = useCanvasInteraction({\n canvasRef,\n inputRef,\n repositioningCursorRef,\n lastClickTimeRef,\n clickCountRef,\n lastProcessedClickTimeRef,\n lastMultiClickTimeRef,\n multiClickSelectionRef,\n skipDragSelectionRef,\n draggingSelectionHandleRef,\n selectionHandlePositionsRef,\n lastMousePosRef,\n dragOriginalElementsRef,\n rotatingElementRef,\n editingChildIdRef,\n editRichTextRef,\n editModeEnteredAtRef,\n multiSelectionGroupBoundsRef,\n elementsRef,\n MULTI_CLICK_THRESHOLD,\n MULTI_CLICK_DEBOUNCE,\n CLICK_DEDUP_THRESHOLD,\n isEditing,\n editText,\n isTouchDevice,\n selectionStart,\n selectionEnd,\n isRotating,\n isMarqueeSelecting,\n isAltKeyPressed,\n selectedId,\n selectedElement,\n activeChildElement,\n elements,\n multiSelection,\n multiSelectionOBBRef,\n zoom,\n paddingOffsetX,\n paddingOffsetY,\n spacingIndicators,\n focusedPieceRect: selectionClipRect,\n setCursorPosition,\n setSelectionStart,\n setSelectionEnd,\n setIsRotating,\n setRotationStartAngle,\n setCurrentRotation,\n setRotatingElementVersion,\n setIsAltKeyPressed,\n setSnapGuides,\n setSpacingIndicators,\n setHandlesVersion,\n setHoverState,\n setElements,\n forceUpdate,\n stateMachine,\n transformHandles,\n snapSystem,\n spacingSystem,\n penTool,\n cropController,\n onElementUpdate,\n executeElementUpdate,\n onSelectionChange,\n onMultiSelectionChange,\n onRotationStateChange,\n onCropModeEnter,\n updateActiveChild,\n getActiveArtboardElements,\n startMarqueeSelection,\n updateMarqueeSelection,\n finishMarqueeSelection,\n enterTextEditMode,\n setEditPosition,\n setEditText,\n setEditRichText,\n setIsEditing,\n setCurrentFormattingStyle,\n getWrappedTextForEditing,\n });\n\n // Canvas render loop\n useCanvasRenderLoop({\n canvasRef,\n elements,\n elementsByArtboard,\n artboards,\n artboardManager,\n artboardRenderer,\n selectedId,\n selectedElement,\n activeChildElement,\n editingChildIdRef,\n transformHandles,\n stateMachine,\n canvasSize,\n paddedCanvasWidth,\n paddedCanvasHeight,\n paddingOffsetX,\n paddingOffsetY,\n zoom,\n zoomInvariantBorderRadius,\n effectiveViewPadding,\n showRotationHandle,\n isRotating,\n currentRotation,\n hoverState,\n isEditing,\n editText,\n editRichText,\n cursorPosition,\n selectionStart,\n selectionEnd,\n cursorOpacityRef,\n cursorStartTimeRef,\n isTouchDevice,\n selectionHandlePositionsRef,\n multiClickSelectionRef,\n lastMultiClickTimeRef,\n MULTI_CLICK_DEBOUNCE,\n snapGuides,\n spacingIndicators,\n isAltKeyPressed,\n multiSelection,\n multiSelectionOBBRef,\n multiSelectionGroupBoundsRef,\n isMarqueeSelecting,\n marqueeStart,\n marqueeEnd,\n marqueePreviewSelection,\n penTool,\n handlesVersion,\n hideHandles,\n resolvedTheme,\n cursorAnimationFrame,\n renderVersion,\n pieceGuides,\n focusedPieceId:\n pieceFocus && pieceFocus !== 'spread' ? pieceFocus.pieceId : null,\n focusedPieceRect,\n });\n\n // ============================================\n // IMPERATIVE HANDLE\n // ============================================\n useImperativeHandle(\n ref,\n () => ({\n applyTextFormatting: (style: CharacterStyle) => {\n applyFormattingToSelection(style);\n },\n toggleFormattingProperty: (property: 'bold' | 'italic' | 'underline' | 'strikethrough') => {\n toggleFormattingProperty(property);\n },\n isEditingText: () => isEditing,\n isRotating: () => isRotating,\n getSelectionStyle: () => getSelectionStyle(),\n getZoom: () => zoom,\n forceRender,\n getEditingState: () => ({\n isEditing,\n editText,\n cursorPosition,\n selectionStart,\n selectionEnd,\n }),\n getCropState: () => ({\n isCropping: selectedElement instanceof ImageElement && (selectedElement as ImageElement).isCropping,\n elementId: selectedElement?.id || null,\n }),\n getPaddingOffset: () => ({ x: paddingOffsetX, y: paddingOffsetY }),\n }),\n [applyFormattingToSelection, toggleFormattingProperty, isEditing, isRotating, getSelectionStyle, zoom, forceRender, editText, cursorPosition, selectionStart, selectionEnd, selectedElement, paddingOffsetX, paddingOffsetY]\n );\n\n // ============================================\n // ADR-0054 Phase 4: Canvas clip-path for true die-cut knockout.\n // ============================================\n // Translate artboard-space cutouts into a CSS clip-path in the\n // `<canvas>` element's own CSS-pixel coordinate system. The canvas\n // element's CSS size is `paddedCanvas{W,H} * zoom`; the artboard is\n // drawn at `(paddingOffset{X,Y}, paddingOffset{X,Y})` inside it. So\n // for every cutout at artboard `(cx, cy, cw, ch)` the equivalent\n // canvas-CSS position is `((paddingOffsetX + cx) * zoom, ... )`.\n // Evenodd fill rule punches the cutout shapes out of a full-canvas\n // outer rect.\n const canvasClipPathValue = useMemo(() => {\n if (!canvasCutouts || canvasCutouts.length === 0) return undefined;\n const cssW = paddedCanvasWidth * zoom;\n const cssH = paddedCanvasHeight * zoom;\n const outer = `M 0 0 h ${cssW} v ${cssH} h ${-cssW} Z`;\n const cutouts = canvasCutouts\n .map((c) => {\n if (c.kind === \"rect\" || c.kind === \"roundedRect\") {\n const x = (paddingOffsetX + c.x) * zoom;\n const y = (paddingOffsetY + c.y) * zoom;\n const w = c.width * zoom;\n const h = c.height * zoom;\n if (c.kind === \"roundedRect\") {\n const rx = (c.rx ?? c.ry ?? 0) * zoom;\n const ry = (c.ry ?? c.rx ?? 0) * zoom;\n if (rx > 0 || ry > 0) {\n return [\n `M ${x + rx} ${y}`,\n `h ${w - 2 * rx}`,\n `a ${rx} ${ry} 0 0 1 ${rx} ${ry}`,\n `v ${h - 2 * ry}`,\n `a ${rx} ${ry} 0 0 1 ${-rx} ${ry}`,\n `h ${-(w - 2 * rx)}`,\n `a ${rx} ${ry} 0 0 1 ${-rx} ${-ry}`,\n `v ${-(h - 2 * ry)}`,\n `a ${rx} ${ry} 0 0 1 ${rx} ${-ry}`,\n \"Z\",\n ].join(\" \");\n }\n }\n return `M ${x} ${y} h ${w} v ${h} h ${-w} Z`;\n }\n // kind === \"path\": raw SVG path-data. Assumes the `d` string\n // is already in artboard-space px; we don't attempt to parse\n // and transform (Phase 5 territory). Shift via a wrapping\n // `translate` is not possible inside a CSS path() string — so\n // the consumer should provide rect/roundedRect for now.\n return \"\";\n })\n .filter(Boolean);\n if (cutouts.length === 0) return undefined;\n return `path(evenodd, \"${outer} ${cutouts.join(\" \")}\")`;\n }, [canvasCutouts, paddedCanvasWidth, paddedCanvasHeight, paddingOffsetX, paddingOffsetY, zoom]);\n\n // ============================================\n // JSX RENDER\n // ============================================\n return (\n <>\n <div\n role=\"status\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n style={{ position: 'absolute', width: 1, height: 1, margin: -1, padding: 0, overflow: 'hidden', clip: 'rect(0,0,0,0)', whiteSpace: 'nowrap', borderWidth: 0 }}\n >\n {ariaAnnouncement}\n </div>\n <canvas\n role=\"application\"\n aria-label=\"Design editor canvas. Use mouse or touch to select, move, and resize elements.\"\n ref={canvasRef}\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={handlePointerUp}\n onPointerCancel={handlePointerUp}\n onDoubleClick={handleDoubleClick}\n style={{\n display: 'block',\n // When pieceGuides are present (phone cases, anything with\n // a non-rectangular outer shape) the canvas must be fully\n // transparent: the artboard 2D fill is already rounded to\n // the piece's outer radius, and the bleed-ring vignette\n // uses `destination-out` to knock the corners out. An\n // opaque CSS bg here defeats both — the area outside the\n // rounded artboard reads as a white frame, and the\n // knocked-out corners reveal that same white instead of\n // whatever sits behind the canvas. Plain rectangular\n // products keep the legacy editor-surface fill so the\n // design canvas still reads as a distinct surface.\n background: pieceGuides ? 'transparent' : 'var(--color-canvas-bg)',\n width: `${paddedCanvasWidth * zoom}px`,\n height: `${paddedCanvasHeight * zoom}px`,\n cursor: penTool.isActive() && selectedElement instanceof PathElement ? penCursor : undefined,\n position: 'absolute',\n left: `${offsetX}px`,\n top: `${offsetY}px`,\n touchAction: 'none',\n clipPath: canvasClipPathValue,\n }}\n />\n {/* Render textarea via portal to document.body to escape any containing blocks\n created by backdrop-filter, transform, or filter on parent elements. */}\n {isEditing &&\n (() => {\n // Determine which element is being edited to get font properties\n let editingElement: CanvasElement | null | undefined = selectedElement;\n if (editingChildIdRef.current && selectedElement instanceof GroupElement) {\n const child = selectedElement.children.find((c) => c.id === editingChildIdRef.current);\n if (child) { editingElement = child; }\n }\n\n const textEl = editingElement instanceof TextElement ? editingElement : null;\n const fontSize = textEl?.fontSize || 24;\n const fontFamily = textEl?.fontFamily || 'Arial';\n const bold = textEl?.bold || false;\n const italic = textEl?.italic || false;\n const textAlign = textEl?.textAlign || 'center';\n\n const textareaElement = (\n <textarea\n ref={inputRef}\n data-preserve-selection=\"\"\n value={editText}\n onChange={(e) => {\n const newValue = (e.target as HTMLTextAreaElement).value;\n\n if (!editRichText) {\n const newRichText = RichText.fromPlainText(newValue, {});\n editRichTextRef.current = newRichText;\n setEditRichText(newRichText);\n setEditText(newValue);\n const newCursorPos = e.target.selectionEnd;\n setCursorPosition(newCursorPos);\n setSelectionStart(e.target.selectionStart);\n setSelectionEnd(e.target.selectionEnd);\n return;\n }\n\n const oldText = editText;\n const updatedRichText = editRichText.clone();\n\n let prefixLen = 0;\n while (prefixLen < oldText.length && prefixLen < newValue.length && oldText[prefixLen] === newValue[prefixLen]) {\n prefixLen++;\n }\n\n let suffixLen = 0;\n while (suffixLen < oldText.length - prefixLen && suffixLen < newValue.length - prefixLen &&\n oldText[oldText.length - 1 - suffixLen] === newValue[newValue.length - 1 - suffixLen]) {\n suffixLen++;\n }\n\n const deleteStart = prefixLen;\n const deleteEnd = oldText.length - suffixLen;\n const insertText = newValue.substring(prefixLen, newValue.length - suffixLen);\n\n if (deleteEnd > deleteStart) {\n updatedRichText.delete(deleteStart, deleteEnd);\n }\n\n if (insertText.length > 0) {\n let insertStyle: CharacterStyle;\n if (_currentFormattingStyle) {\n insertStyle = _currentFormattingStyle;\n } else if (deleteStart > 0) {\n insertStyle = updatedRichText.getStyleAt(deleteStart - 1);\n } else if (selectedElement instanceof TextElement) {\n insertStyle = selectedElement.getDefaultStyle();\n } else {\n insertStyle = {};\n }\n updatedRichText.insert(deleteStart, insertText, insertStyle);\n }\n\n editRichTextRef.current = updatedRichText;\n setEditRichText(updatedRichText);\n setEditText(newValue);\n\n const newCursorPos = e.target.selectionEnd;\n setCursorPosition(newCursorPos);\n setSelectionStart(e.target.selectionStart);\n setSelectionEnd(e.target.selectionEnd);\n selectionAnchorRef.current = newCursorPos;\n\n const textarea = e.target;\n setTimeout(() => {\n if (textarea && document.activeElement === textarea) {\n textarea.setSelectionRange(newCursorPos, newCursorPos);\n }\n }, 0);\n }}\n onBlur={handleTextEditBlur}\n onKeyDown={handleTextKeyDown}\n onPointerDown={(e) => {\n e.preventDefault();\n if (!selectedElement || !(selectedElement instanceof TextElement) || !canvasRef.current) return;\n\n const now = Date.now();\n if (now - lastProcessedClickTimeRef.current < CLICK_DEDUP_THRESHOLD) return;\n lastProcessedClickTimeRef.current = now;\n\n if (now - lastMultiClickTimeRef.current >= MULTI_CLICK_DEBOUNCE) {\n multiClickSelectionRef.current = null;\n }\n\n const canvasRect = canvasRef.current.getBoundingClientRect();\n const canvasX = (e.clientX - canvasRect.left) / zoom - paddingOffsetX;\n const canvasY = (e.clientY - canvasRect.top) / zoom - paddingOffsetY;\n\n const visualBbox = selectedElement.getVisualBoundingBox();\n const center = selectedElement.getRotationAnchor();\n const rotationRad = RotationUtils.toRadiansInverse(selectedElement.rotation);\n const dx = canvasX - center.x;\n const dy = canvasY - center.y;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n const localRotatedX = dx * cos - dy * sin;\n const localRotatedY = dx * sin + dy * cos;\n const relativeX = localRotatedX + visualBbox.width / 2;\n const relativeY = localRotatedY + visualBbox.height / 2;\n\n const clickIndex = getCursorIndexFromPoint(selectedElement, { x: relativeX, y: relativeY });\n\n const timeSinceLastClick = now - lastClickTimeRef.current;\n if (timeSinceLastClick < MULTI_CLICK_THRESHOLD) {\n clickCountRef.current += 1;\n } else {\n clickCountRef.current = 1;\n }\n lastClickTimeRef.current = now;\n\n const currentText = editText;\n didDragSelectRef.current = false;\n\n if (clickCountRef.current >= 3) {\n e.preventDefault();\n lastMultiClickTimeRef.current = now;\n multiClickSelectionRef.current = { start: 0, end: currentText.length, cursor: currentText.length };\n skipDragSelectionRef.current = true;\n clickCountRef.current = 0;\n selectionAnchorRef.current = 0;\n setCursorPosition(currentText.length);\n setSelectionStart(0);\n setSelectionEnd(currentText.length);\n if (inputRef.current) {\n inputRef.current.focus();\n inputRef.current.setSelectionRange(0, currentText.length);\n }\n } else if (clickCountRef.current === 2) {\n e.preventDefault();\n const wordBounds = getWordBoundaryAt(currentText, clickIndex);\n lastMultiClickTimeRef.current = now;\n multiClickSelectionRef.current = { start: wordBounds.start, end: wordBounds.end, cursor: wordBounds.end };\n skipDragSelectionRef.current = true;\n selectionAnchorRef.current = wordBounds.start;\n setCursorPosition(wordBounds.end);\n setSelectionStart(wordBounds.start);\n setSelectionEnd(wordBounds.end);\n if (inputRef.current) {\n inputRef.current.focus();\n inputRef.current.setSelectionRange(wordBounds.start, wordBounds.end);\n }\n } else {\n multiClickSelectionRef.current = null;\n selectionAnchorRef.current = clickIndex;\n setSelectionStart(clickIndex);\n setCursorPosition(clickIndex);\n setSelectionEnd(clickIndex);\n if (inputRef.current) {\n inputRef.current.focus();\n inputRef.current.setSelectionRange(clickIndex, clickIndex);\n }\n skipDragSelectionRef.current = false;\n }\n }}\n onPointerMove={(e) => {\n if (e.buttons !== 1) return;\n\n // Handle selection handle dragging\n if (draggingSelectionHandleRef.current && selectedElement instanceof TextElement && canvasRef.current) {\n const canvasRect = canvasRef.current.getBoundingClientRect();\n const canvasX = (e.clientX - canvasRect.left) / zoom - paddingOffsetX;\n const canvasY = (e.clientY - canvasRect.top) / zoom - paddingOffsetY;\n\n const visualBbox = selectedElement.getVisualBoundingBox();\n const center = selectedElement.getRotationAnchor();\n const rotationRad = RotationUtils.toRadiansInverse(selectedElement.rotation);\n const dx = canvasX - center.x;\n const dy = canvasY - center.y;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n const localRotatedX = dx * cos - dy * sin;\n const localRotatedY = dx * sin + dy * cos;\n\n const handlePositions = selectionHandlePositionsRef.current;\n let yOffset = 0;\n if (draggingSelectionHandleRef.current === 'start' && handlePositions?.start) {\n yOffset = handlePositions.start.textEdge.y - handlePositions.start.center.y;\n } else if (draggingSelectionHandleRef.current === 'end' && handlePositions?.end) {\n yOffset = handlePositions.end.textEdge.y - handlePositions.end.center.y;\n }\n\n const relativeX = localRotatedX + visualBbox.width / 2;\n const relativeY = (localRotatedY + yOffset) + visualBbox.height / 2;\n\n const dragIndex = getCursorIndexFromPoint(selectedElement, { x: relativeX, y: relativeY });\n\n if (draggingSelectionHandleRef.current === 'start') {\n if (dragIndex <= selectionEnd) {\n setSelectionStart(dragIndex);\n setCursorPosition(dragIndex);\n } else {\n setSelectionStart(selectionEnd);\n setSelectionEnd(dragIndex);\n setCursorPosition(dragIndex);\n draggingSelectionHandleRef.current = 'end';\n }\n } else {\n if (dragIndex >= selectionStart) {\n setSelectionEnd(dragIndex);\n setCursorPosition(dragIndex);\n } else {\n setSelectionEnd(selectionStart);\n setSelectionStart(dragIndex);\n setCursorPosition(dragIndex);\n draggingSelectionHandleRef.current = 'start';\n }\n }\n\n if (inputRef.current) {\n const start = Math.min(selectionStart, selectionEnd);\n const end = Math.max(selectionStart, selectionEnd);\n inputRef.current.setSelectionRange(start, end);\n }\n return;\n }\n\n if (skipDragSelectionRef.current) return;\n if (!selectedElement || !(selectedElement instanceof TextElement) || !canvasRef.current) return;\n\n const canvasRect = canvasRef.current.getBoundingClientRect();\n const canvasX = (e.clientX - canvasRect.left) / zoom - paddingOffsetX;\n const canvasY = (e.clientY - canvasRect.top) / zoom - paddingOffsetY;\n\n const visualBbox = selectedElement.getVisualBoundingBox();\n const center = selectedElement.getRotationAnchor();\n const rotationRad = RotationUtils.toRadiansInverse(selectedElement.rotation);\n const dx = canvasX - center.x;\n const dy = canvasY - center.y;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n const localRotatedX = dx * cos - dy * sin;\n const localRotatedY = dx * sin + dy * cos;\n const relativeX = localRotatedX + visualBbox.width / 2;\n const relativeY = localRotatedY + visualBbox.height / 2;\n\n const dragIndex = getCursorIndexFromPoint(selectedElement, { x: relativeX, y: relativeY });\n const anchor = selectionAnchorRef.current;\n\n if (anchor !== dragIndex) {\n didDragSelectRef.current = true;\n }\n\n setSelectionEnd(dragIndex);\n setCursorPosition(dragIndex);\n setSelectionStart(anchor);\n\n if (inputRef.current) {\n const start = Math.min(anchor, dragIndex);\n const end = Math.max(anchor, dragIndex);\n inputRef.current.setSelectionRange(start, end);\n }\n }}\n onPointerUp={() => {\n if (draggingSelectionHandleRef.current) {\n draggingSelectionHandleRef.current = null;\n }\n }}\n onClick={() => {\n didDragSelectRef.current = false;\n skipDragSelectionRef.current = false;\n }}\n onSelect={() => {\n // Selection handled manually in onPointerDown/onPointerMove\n }}\n autoFocus\n spellCheck={false}\n style={{\n position: 'fixed',\n left: `${editPosition.x}px`,\n top: `${editPosition.y}px`,\n width: `${editPosition.width}px`,\n height: `${editPosition.height || 50}px`,\n fontSize: `${fontSize * zoom}px`,\n fontFamily: `\"${fontFamily}\", Arial, sans-serif`,\n fontWeight: bold ? 700 : 400,\n fontStyle: italic ? 'italic' : 'normal',\n textAlign: textAlign,\n lineHeight: 1.2,\n opacity: 0,\n zIndex: 1001,\n resize: 'none',\n border: '2px solid transparent',\n outline: 'none',\n padding: '4px',\n margin: '0',\n overflow: 'hidden',\n color: 'transparent',\n background: 'transparent',\n caretColor: 'transparent',\n whiteSpace: 'pre-wrap',\n wordBreak: 'normal',\n overflowWrap: 'normal',\n }}\n />\n );\n\n return createPortal(textareaElement, document.body);\n })()}\n </>\n );\n }\n);\n\nCanvasEditor.displayName = 'CanvasEditor';\n\nexport default CanvasEditor;\n","/**\n * Canvas - Simple wrapper around CanvasEditor for embedding\n * Extracted from working App.tsx - no fancy logic, just props passthrough\n *\n * Interactive mode: Enables infinite canvas with pan/zoom:\n * - Scroll wheel: zoom in/out (centered on cursor)\n * - Middle mouse drag OR Space+drag: pan the canvas\n * - Pinch gesture: zoom on touch devices\n */\n\nimport React from 'react';\nimport { useEditor } from '../../contexts/EditorContext.js';\nimport { useViewportContext } from '../../contexts/ViewportContext.js';\nimport { useSelectionContext } from '../../contexts/SelectionContext.js';\nimport { useToolStateContext } from '../../contexts/ToolStateContext.js';\nimport { useKeyboardShortcuts } from '../../hooks/useKeyboardShortcuts.js';\nimport { useAnimatedFocusRect } from '../CanvasEditor/hooks/useAnimatedFocusRect.js';\nimport CanvasEditor from '../CanvasEditor.jsx';\n\n/** Bounds for the editor-mode user zoom multiplier. 0.25× lets the\n * user zoom out enough to overview a wide spread; 8× covers fine-\n * detail editing on logos / small text. */\nconst USER_MIN_ZOOM = 0.25;\nconst USER_MAX_ZOOM = 8;\n\n/** Zoom limits for interactive mode */\nconst MIN_ZOOM = 0.1;\nconst MAX_ZOOM = 4.0;\nconst ZOOM_SENSITIVITY = 0.001; // How fast wheel zoom responds\n\nexport interface CanvasProps {\n /** CSS class name */\n className?: string;\n /** Inline styles */\n style?: React.CSSProperties;\n /** Viewport width (container size) */\n width?: number;\n /** Viewport height (container size) */\n height?: number;\n /** Zoom level - if not provided, automatically fits artboard to container */\n zoom?: number;\n /** Padding percentage when auto-fitting (0-1, default 0.95 = 95% of container) */\n fitPadding?: number;\n /**\n * Enable interactive pan/zoom mode (infinite canvas)\n * - Scroll wheel: zoom in/out\n * - Space+drag or middle mouse: pan\n */\n interactive?: boolean;\n /** Border radius for artboard corners (in pixels) */\n artboardBorderRadius?: number;\n /**\n * Fixed screen-space margin in pixels (overrides fitPadding for positioning)\n * When set, the artboard will have this exact margin regardless of zoom level.\n * The fitPadding still controls the zoom calculation, but positioning uses this fixed margin.\n */\n fixedMargin?: number;\n /**\n * Maximum HTML height in pixels. When the artboard (with vertical fixedMargin padding)\n * would exceed this height, horizontal padding is increased to shrink the artboard\n * until it fits within this constraint while still respecting vertical fixedMargin.\n */\n maxHeight?: number;\n /**\n * Whether to show the rotation handle on canvas (defaults to true)\n * Set to false for embedded mode where rotation is handled via toolbar panel\n */\n showRotationHandle?: boolean;\n /**\n * Whether keyboard shortcuts (copy/paste, delete, undo/redo, etc.) are enabled.\n * @default true\n */\n enableShortcuts?: boolean;\n /**\n * ADR-0054 Phase 4: cutout shapes (artboard px) punched through the\n * `<canvas>` as truly transparent holes. See SnowconeCanvasProps.\n */\n canvasCutouts?: import(\"./SnowconeCanvas\").CanvasCutoutShape[];\n /**\n * ADR-0054: piece-guide overlays drawn directly into the canvas\n * render loop (boundary, safe-area wash, zones, die-cut, labels).\n */\n pieceGuides?: import(\"./SnowconeCanvas\").PieceGuidesConfig;\n /**\n * ADR-0060: viewport focus mode for multi-piece spread layouts. See\n * `SnowconeCanvasProps.pieceFocus` for the full contract.\n */\n pieceFocus?: 'spread' | { pieceId: string };\n}\n\n/**\n * Canvas component - renders CanvasEditor without UI chrome\n * Exactly like App.tsx but as a reusable component\n *\n * Auto-fit behavior: If no zoom is provided, the canvas will automatically\n * calculate the zoom level to fit the artboard within the container with padding.\n *\n * Interactive mode: Enables infinite canvas with pan/zoom controls.\n */\nexport const Canvas: React.FC<CanvasProps> = ({\n className,\n style,\n width,\n height,\n zoom: propZoom,\n fitPadding = 0.95,\n interactive = false,\n artboardBorderRadius = 0,\n fixedMargin,\n maxHeight,\n showRotationHandle = true,\n enableShortcuts = true,\n canvasCutouts,\n pieceGuides,\n pieceFocus,\n}) => {\n // Debug: log artboardBorderRadius\n // --- Core editor state (elements, artboards, refs, command history) ---\n const {\n canvasEditorRef,\n canvasRef,\n elements,\n artboards,\n artboardManager,\n handleElementUpdate,\n setElements,\n executeElementUpdate,\n } = useEditor();\n\n // --- Viewport state (zoom, pan) ---\n const {\n zoom: contextZoom,\n setZoom,\n panOffset,\n setPanOffset,\n isPanning,\n setIsPanning,\n userZoom,\n setUserZoom,\n resetUserView,\n } = useViewportContext();\n\n // --- Selection state ---\n const {\n selectedId,\n multiSelection,\n handleSelectionChange,\n setMultiSelection,\n handleActiveChildChange,\n setHoveredElementId,\n hideHandles,\n } = useSelectionContext();\n\n // --- Tool state ---\n const {\n setTextSelectionVersion,\n setExpandedPanelType,\n } = useToolStateContext();\n\n // Handler to open crop panel when entering crop mode via double-click\n const handleCropModeEnter = React.useCallback(() => {\n setExpandedPanelType('crop');\n }, [setExpandedPanelType]);\n\n const containerRef = React.useRef<HTMLDivElement>(null);\n // Start with 0 zoom - will be calculated after layout\n const [calculatedZoom, setCalculatedZoom] = React.useState<number>(0);\n // Track container width for margin calculations\n const [containerWidth, setContainerWidth] = React.useState<number>(0);\n // Track whether maxHeight is constraining (set during zoom calculation)\n const [isHeightConstrained, setIsHeightConstrained] = React.useState<boolean>(false);\n // Track last zoom we synced to context to detect external changes\n const lastSyncedZoomRef = React.useRef<number>(0);\n // Whether we've already done the initial post-layout fit calc.\n // First run uses a double-rAF so CSS layout has settled; later\n // runs (driven by animation, dep changes, etc.) compute\n // synchronously to keep up with 60fps tweens.\n const hasFitOnceRef = React.useRef(false);\n\n // Track if space key is held for pan mode\n const [isSpaceHeld, setIsSpaceHeld] = React.useState(false);\n // Track pan start position\n const panStartRef = React.useRef<{ x: number; y: number; panX: number; panY: number } | null>(null);\n\n // Set up keyboard shortcuts\n useKeyboardShortcuts({ enabled: enableShortcuts });\n\n // Get active artboard for sizing\n const activeArtboard = artboardManager.getActiveArtboard();\n\n // ============================================================================\n // INTERACTIVE MODE: Wheel zoom and space+drag pan\n // ============================================================================\n\n // Handle wheel zoom (centered on cursor position)\n const handleWheel = React.useCallback(\n (e: WheelEvent) => {\n // Interactive mode: any wheel zooms (infinite-canvas feel).\n // Non-interactive (editor) mode: only zoom on `ctrlKey` (Mac\n // trackpad pinch fires this) or `metaKey` (Cmd+scroll explicit\n // zoom). Ordinary scroll passes through to the page.\n const isZoomGesture = interactive || e.ctrlKey || e.metaKey;\n if (!isZoomGesture) return;\n\n // Prevent page scroll\n e.preventDefault();\n\n const container = containerRef.current;\n if (!container) return;\n\n // Get cursor position relative to container\n const rect = container.getBoundingClientRect();\n const cursorX = e.clientX - rect.left;\n const cursorY = e.clientY - rect.top;\n\n // Calculate zoom delta\n const delta = -e.deltaY * ZOOM_SENSITIVITY;\n\n if (interactive) {\n // Interactive mode operates directly on calculatedZoom (no\n // separate auto-fit baseline).\n const oldZoom = calculatedZoom;\n const newZoom = Math.min(Math.max(oldZoom * (1 + delta), MIN_ZOOM), MAX_ZOOM);\n if (newZoom === oldZoom) return;\n const zoomRatio = newZoom / oldZoom;\n const newPanX = cursorX - (cursorX - panOffset.x) * zoomRatio;\n const newPanY = cursorY - (cursorY - panOffset.y) * zoomRatio;\n setCalculatedZoom(newZoom);\n setPanOffset({ x: newPanX, y: newPanY });\n } else {\n // Non-interactive mode keeps `calculatedZoom` as the auto-fit\n // baseline and zooms via `userZoom` on top. This way switching\n // pieces / resizing the container still re-fits cleanly even\n // after the user has zoomed in.\n const oldUserZoom = userZoom;\n const newUserZoom = Math.min(\n Math.max(oldUserZoom * (1 + delta), USER_MIN_ZOOM),\n USER_MAX_ZOOM,\n );\n if (newUserZoom === oldUserZoom) return;\n // Effective zoom changes by ratio = newUserZoom / oldUserZoom\n // (calculatedZoom is constant during this gesture).\n const ratio = newUserZoom / oldUserZoom;\n const newPanX = cursorX - (cursorX - panOffset.x) * ratio;\n const newPanY = cursorY - (cursorY - panOffset.y) * ratio;\n setUserZoom(newUserZoom);\n setPanOffset({ x: newPanX, y: newPanY });\n }\n },\n [interactive, calculatedZoom, panOffset, setPanOffset, userZoom, setUserZoom]\n );\n\n // Attach wheel listener (needs to be non-passive to preventDefault).\n // Both interactive and non-interactive modes attach — the handler\n // itself decides whether a given wheel event is a zoom gesture.\n React.useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n container.addEventListener('wheel', handleWheel, { passive: false });\n return () => container.removeEventListener('wheel', handleWheel);\n }, [handleWheel]);\n\n // Track space key for pan mode\n React.useEffect(() => {\n if (!interactive) return;\n\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.code === 'Space' && !e.repeat) {\n setIsSpaceHeld(true);\n }\n };\n\n const handleKeyUp = (e: KeyboardEvent) => {\n if (e.code === 'Space') {\n setIsSpaceHeld(false);\n panStartRef.current = null;\n }\n };\n\n window.addEventListener('keydown', handleKeyDown);\n window.addEventListener('keyup', handleKeyUp);\n\n return () => {\n window.removeEventListener('keydown', handleKeyDown);\n window.removeEventListener('keyup', handleKeyUp);\n };\n }, [interactive]);\n\n // Pan handlers (using pointer events for unified mouse/touch/pen support)\n const handlePanStart = React.useCallback(\n (e: React.PointerEvent) => {\n if (!interactive) return;\n\n // Start pan on middle mouse button OR space+left click\n // For touch, two-finger pan could be added in the future\n const isMiddleMouse = e.button === 1;\n const isSpacePan = isSpaceHeld && e.button === 0;\n\n if (isMiddleMouse || isSpacePan) {\n e.preventDefault();\n (e.target as HTMLElement).setPointerCapture(e.pointerId);\n setIsPanning(true);\n panStartRef.current = {\n x: e.clientX,\n y: e.clientY,\n panX: panOffset.x,\n panY: panOffset.y,\n };\n }\n },\n [interactive, isSpaceHeld, panOffset, setIsPanning]\n );\n\n const handlePanMove = React.useCallback(\n (e: React.PointerEvent) => {\n if (!isPanning || !panStartRef.current) return;\n\n const dx = e.clientX - panStartRef.current.x;\n const dy = e.clientY - panStartRef.current.y;\n\n setPanOffset({\n x: panStartRef.current.panX + dx,\n y: panStartRef.current.panY + dy,\n });\n },\n [isPanning, setPanOffset]\n );\n\n const handlePanEnd = React.useCallback((e?: React.PointerEvent) => {\n if (isPanning) {\n if (e) {\n (e.target as HTMLElement).releasePointerCapture(e.pointerId);\n }\n setIsPanning(false);\n panStartRef.current = null;\n }\n }, [isPanning, setIsPanning]);\n\n // Global pointer up to catch releases outside container\n React.useEffect(() => {\n if (!interactive || !isPanning) return;\n\n const handleGlobalPointerUp = () => {\n setIsPanning(false);\n panStartRef.current = null;\n };\n\n window.addEventListener('pointerup', handleGlobalPointerUp);\n return () => window.removeEventListener('pointerup', handleGlobalPointerUp);\n }, [interactive, isPanning, setIsPanning]);\n\n // ADR-0060: when `pieceFocus` names a piece, the canvas's visual\n // subject is THAT piece, not the full artboard. Width / height for\n // CSS sizing + centring math should follow the focused piece. Falls\n // back to full artboard for spread mode / single-piece layouts.\n //\n // The target rect comes from `pieceGuides.pieces`; the animated\n // value smoothly interpolates between targets when the user\n // switches pieces (single source of truth for both the zoom-fit\n // calc here and the layout / render-loop downstream — keeps zoom\n // and pan in lock-step during the dolly).\n const targetFocusedRect = React.useMemo(() => {\n if (!pieceFocus || !pieceGuides) return null;\n const pieces = pieceGuides.pieces ?? [];\n if (pieces.length <= 1) return null;\n // 'spread' (= the picker's \"All\" view): animate to/from a\n // full-artboard rect so the camera dollies smoothly between a\n // piece and the all-pieces view. Returning `null` here would\n // make the animation hook snap (no `from`/`to` to interpolate),\n // producing the same instant jump the user wants to avoid.\n if (pieceFocus === 'spread') {\n if (!activeArtboard) return null;\n return {\n x: 0,\n y: 0,\n width: activeArtboard.width,\n height: activeArtboard.height,\n };\n }\n const match = pieces.find((p) => p.id === pieceFocus.pieceId);\n if (!match) return null;\n return { x: match.x, y: match.y, width: match.width, height: match.height };\n }, [pieceFocus, pieceGuides, activeArtboard]);\n const focusedPiece = useAnimatedFocusRect(targetFocusedRect);\n\n // Calculate zoom to fit artboard width in container\n // Height is determined by artboard aspect ratio, not container\n React.useEffect(() => {\n if (propZoom !== undefined) {\n // Use explicit zoom from props\n setCalculatedZoom(propZoom);\n return;\n }\n\n if (!containerRef.current || !activeArtboard) {\n return;\n }\n\n let rafId: number = 0;\n\n const calculateFitZoom = () => {\n if (!containerRef.current || !activeArtboard) return;\n\n // Only measure width - height will be determined by artboard aspect ratio\n const containerWidth = containerRef.current.clientWidth;\n\n\n if (containerWidth === 0) {\n return;\n }\n\n // ADR-0060: when `pieceFocus` names a piece in the active layout,\n // fit the zoom to THAT piece's dimensions instead of the full\n // artboard. Otherwise a focused 1500-wide piece on a 3100-wide\n // spread gets squashed because the zoom assumes the full 3100\n // needs to fit the container — the focused piece ends up at <50%\n // of the container width with neighbours visible everywhere.\n // Falling back to artboard dims when no focus or unknown piece.\n let fitWidth = activeArtboard.width;\n let fitHeight = activeArtboard.height;\n if (pieceFocus && pieceFocus !== 'spread' && pieceGuides) {\n const pieces = pieceGuides.pieces ?? [];\n if (pieces.length > 1) {\n const match = pieces.find((p) => p.id === pieceFocus.pieceId);\n if (match) {\n fitWidth = match.width;\n fitHeight = match.height;\n }\n }\n }\n\n // Calculate zoom to fit artboard width with padding\n // When fixedMargin is provided, use it for both left and right margins (fixed pixels)\n // Otherwise, use fitPadding as a percentage\n let fitZoom: number;\n let constrainedByHeight = false;\n\n if (fixedMargin !== undefined) {\n // Fixed margin mode: artboard fills container minus 2x fixed margin\n const availableWidth = containerWidth - (fixedMargin * 2);\n const widthFitZoom = Math.min(availableWidth / fitWidth, 1.0);\n\n if (focusedPiece && maxHeight !== undefined) {\n // ADR-0060: in focused mode, fit the piece by whichever\n // axis is tighter. On a narrow viewport with a wide piece\n // (e.g. 1500×2000 Front in a 400px-wide container), pure\n // height-fit would make the canvas wider than the container\n // and clip the focused piece horizontally. Width-fit keeps\n // the piece fully visible — the canvas ends up shorter than\n // the panel slot, which the consumer's flex layout absorbs\n // by vertically centring the canvas inside the slot.\n const availableHeight = maxHeight - (fixedMargin * 2);\n const heightFitZoom = Math.min(availableHeight / fitHeight, 1.0);\n fitZoom = Math.min(widthFitZoom, heightFitZoom);\n constrainedByHeight = heightFitZoom <= widthFitZoom;\n } else {\n fitZoom = widthFitZoom;\n if (maxHeight !== undefined) {\n const artboardHeightAtZoom = fitHeight * fitZoom;\n const totalHeightAtZoom = artboardHeightAtZoom + (fixedMargin * 2);\n if (totalHeightAtZoom > maxHeight) {\n const availableHeight = maxHeight - (fixedMargin * 2);\n const heightConstrainedZoom = availableHeight / fitHeight;\n fitZoom = Math.min(fitZoom, heightConstrainedZoom, 1.0);\n constrainedByHeight = true;\n }\n }\n }\n } else {\n // Percentage mode: artboard fills fitPadding% of container\n fitZoom = Math.min((containerWidth * fitPadding) / fitWidth, 1.0);\n }\n\n setCalculatedZoom(fitZoom);\n setContainerWidth(containerWidth);\n setIsHeightConstrained(constrainedByHeight);\n };\n\n // First run: double-RAF so CSS layout has settled before we\n // measure containerWidth. Subsequent runs (animation, dep\n // changes) compute synchronously — re-scheduling rAFs at 60fps\n // would lag the focused-piece dolly by 2 frames.\n let cancelled = false;\n if (hasFitOnceRef.current) {\n calculateFitZoom();\n } else {\n rafId = requestAnimationFrame(() => {\n rafId = requestAnimationFrame(() => {\n if (cancelled) return;\n calculateFitZoom();\n hasFitOnceRef.current = true;\n });\n });\n }\n\n // Watch for container width changes\n const resizeObserver = new ResizeObserver(() => {\n calculateFitZoom();\n });\n\n resizeObserver.observe(containerRef.current);\n\n return () => {\n cancelled = true;\n cancelAnimationFrame(rafId);\n resizeObserver.disconnect();\n };\n }, [propZoom, activeArtboard, fitPadding, fixedMargin, maxHeight, pieceFocus, pieceGuides, focusedPiece]);\n\n // The zoom that the rest of the canvas system sees. `calculatedZoom`\n // is the auto-fit value driven by container size; `userZoom` is the\n // user's zoom-in / zoom-out multiplier (pinch, wheel, pill). Multiply\n // them so element rendering, hit-testing, and pointer math all stay\n // in sync with what the user sees.\n const effectiveZoom =\n calculatedZoom > 0 && userZoom > 0 ? calculatedZoom * userZoom : calculatedZoom;\n\n // Update zoom in context (and track what we synced)\n React.useEffect(() => {\n lastSyncedZoomRef.current = effectiveZoom;\n setZoom(effectiveZoom);\n }, [effectiveZoom, setZoom]);\n\n // Reset user zoom + pan when the focused piece changes — switching\n // to a different piece (Front → Back, etc.) should land on fit-to-\n // piece, not preserve a previous zoom that won't make sense for\n // the new framing. Skip on the initial mount (`focusedPiece`\n // becomes non-null then; resetting an already-default state is a\n // no-op but feels cleaner without a guard).\n const lastFocusedPieceIdRef = React.useRef<string | null>(null);\n React.useEffect(() => {\n const id = focusedPiece && 'id' in focusedPiece ? (focusedPiece as { id?: string }).id ?? null : null;\n if (id !== lastFocusedPieceIdRef.current) {\n if (lastFocusedPieceIdRef.current !== null) {\n // Don't reset on the first focus event — only on transitions.\n resetUserView();\n }\n lastFocusedPieceIdRef.current = id;\n }\n }, [focusedPiece, resetUserView]);\n\n // Listen for external zoom changes (from ZoomControls)\n // In interactive mode, sync context zoom back to local state\n React.useEffect(() => {\n if (!interactive) return;\n\n // If context zoom differs from what we last synced, it's an external change\n if (contextZoom !== lastSyncedZoomRef.current && contextZoom > 0) {\n setCalculatedZoom(contextZoom);\n lastSyncedZoomRef.current = contextZoom;\n }\n }, [interactive, contextZoom]);\n\n // `focusedPiece` itself is hoisted above the `calculateFitZoom`\n // useEffect — these helpers stay co-located with the dimension math\n // that uses them.\n const subjectWidth = focusedPiece ? focusedPiece.width : activeArtboard?.width ?? 0;\n const subjectHeight = focusedPiece ? focusedPiece.height : activeArtboard?.height ?? 0;\n\n const canvasWidth = subjectWidth * effectiveZoom;\n const canvasHeight = subjectHeight * effectiveZoom;\n\n // The canvas's *layout* width / height at fit zoom (userZoom = 1).\n // Used for centring math so the canvas's left/top edge stays\n // anchored when the user zooms in — we then translate via\n // `panOffset` to recentre around the gesture centroid. Without\n // this anchoring, `effectiveMarginX = (container - canvasWidth)/2`\n // would shrink (and go negative) as zoom grows, fighting the pan\n // adjustment.\n const fitCanvasWidth = subjectWidth * calculatedZoom;\n\n // Calculate padded canvas dimensions (what CanvasEditor will render)\n // When fixedMargin is provided, add fixed margins to artboard size\n // Otherwise use fitPadding as a percentage multiplier\n let paddedCanvasHeight = activeArtboard\n ? (fixedMargin !== undefined ? canvasHeight + (fixedMargin * 2) : canvasHeight / fitPadding)\n : 0;\n\n // Enforce maxHeight constraint on display height\n if (maxHeight !== undefined && paddedCanvasHeight > maxHeight) {\n paddedCanvasHeight = maxHeight;\n }\n\n // Calculate effective margins for CanvasEditor\n // When maxHeight constrains (tracked via isHeightConstrained state),\n // horizontal margin increases to center artboard at full container width\n let effectiveMarginX = fixedMargin;\n let effectiveMarginY = fixedMargin;\n\n // Use current container width from ref (more reliable than state which may be stale)\n const currentContainerWidth = containerRef.current?.clientWidth || containerWidth;\n\n if (fixedMargin !== undefined && isHeightConstrained && currentContainerWidth > 0 && fitCanvasWidth > 0) {\n // Center horizontally based on fit-zoom canvas width, NOT the\n // current (possibly-zoomed) width — see `fitCanvasWidth` comment\n // above. PanOffset compensates around the user's gesture centroid\n // when they zoom in.\n effectiveMarginX = (currentContainerWidth - fitCanvasWidth) / 2;\n }\n\n // Display height matches padded canvas height\n const displayHeight = paddedCanvasHeight;\n\n // Cache container dims via ResizeObserver instead of reading\n // `clientWidth/Height` per pan update. During a drag the user\n // can fire dozens of `panOffset` updates per second, and\n // synchronous layout reads on every one cause forced reflows\n // (visible iOS Safari memory pressure → page refresh under\n // sustained drag).\n const containerSizeRef = React.useRef({ w: 0, h: 0 });\n React.useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n containerSizeRef.current = {\n w: container.clientWidth,\n h: container.clientHeight,\n };\n const observer = new ResizeObserver((entries) => {\n const entry = entries[0];\n if (!entry) return;\n containerSizeRef.current = {\n w: entry.contentRect.width,\n h: entry.contentRect.height,\n };\n });\n observer.observe(container);\n return () => observer.disconnect();\n }, []);\n\n // Clamp panOffset so at least 25% of the canvas's smaller axis stays\n // visible on each side. Without this, a single-finger pan at fit\n // zoom can shove the canvas off-screen to the point where the user\n // can \"lose\" it. Only fires when the current panOffset would leave\n // less than 25% visible — at fit (panOffset {0,0}) this is a no-op.\n // We do this in `Canvas.tsx` instead of inside each gesture handler\n // so all pan paths (pinch, single-finger pan, pill, wheel) share\n // the same clamp.\n React.useEffect(() => {\n if (interactive) return; // interactive mode is intentional infinite canvas\n\n // Belt-and-suspenders to the per-gesture guards: at fit zoom the\n // entire canvas is in view, so any non-zero pan is meaningless.\n // Pinch / wheel / programmatic-set paths can briefly leave a\n // residual offset; snap it to zero here so the next paint is\n // clean. The single-finger pan path already short-circuits at\n // userZoom <= 1.001 (see useCanvasInteraction).\n if (userZoom <= 1.001) {\n if (panOffset.x !== 0 || panOffset.y !== 0) {\n setPanOffset({ x: 0, y: 0 });\n }\n return;\n }\n\n const containerW = containerSizeRef.current.w;\n const containerH = containerSizeRef.current.h;\n if (containerW === 0 || containerH === 0) return;\n if (canvasWidth === 0 || canvasHeight === 0) return;\n\n const minVisibleX = 0.25 * Math.min(canvasWidth, containerW);\n const minVisibleY = 0.25 * Math.min(canvasHeight, containerH);\n\n // Canvas screen-left in container coords = effectiveMarginX + panOffset.x.\n // Constraints to keep `minVisibleX` of the canvas inside the container:\n // canvas right edge ≥ minVisibleX → panOffset.x ≥ minVisibleX - effectiveMarginX - canvasWidth\n // canvas left edge ≤ containerW - minVisibleX → panOffset.x ≤ containerW - minVisibleX - effectiveMarginX\n const minPanX = minVisibleX - (effectiveMarginX ?? 0) - canvasWidth;\n const maxPanX = containerW - minVisibleX - (effectiveMarginX ?? 0);\n const minPanY = minVisibleY - (effectiveMarginY ?? 0) - canvasHeight;\n const maxPanY = containerH - minVisibleY - (effectiveMarginY ?? 0);\n\n const clampedX = Math.max(minPanX, Math.min(maxPanX, panOffset.x));\n const clampedY = Math.max(minPanY, Math.min(maxPanY, panOffset.y));\n\n if (clampedX !== panOffset.x || clampedY !== panOffset.y) {\n setPanOffset({ x: clampedX, y: clampedY });\n }\n }, [\n interactive,\n userZoom,\n panOffset,\n canvasWidth,\n canvasHeight,\n effectiveMarginX,\n effectiveMarginY,\n setPanOffset,\n ]);\n\n // Container style - different for interactive vs non-interactive mode\n // Both modes include overscroll-behavior-x: none to prevent iOS Safari edge swipe navigation\n const containerStyle: React.CSSProperties = interactive\n ? {\n // Interactive mode: full viewport with overflow hidden for infinite canvas feel\n position: 'relative',\n width: width ? `${width}px` : '100%',\n height: height ? `${height}px` : '100%',\n overflow: 'hidden',\n cursor: isPanning ? 'grabbing' : isSpaceHeld ? 'grab' : 'default',\n touchAction: 'none', // Disable browser touch gestures for proper canvas interaction\n overscrollBehaviorX: 'none', // Prevent iOS Safari edge swipe navigation\n ...style,\n }\n : {\n // Standard mode: width fills parent, height calculated from artboard aspect ratio\n // Canvas is absolutely positioned, so no flexbox centering needed - we use offsets\n position: 'relative',\n width: width ? `${width}px` : '100%',\n height: height ? `${height}px` : displayHeight > 0 ? `${displayHeight}px` : 'auto',\n overflow: 'hidden', // Prevent canvas content from causing horizontal scroll\n touchAction: 'none', // Disable browser touch gestures for canvas control\n overscrollBehaviorX: 'none', // Prevent iOS Safari edge swipe navigation\n ...style,\n };\n\n // Inner wrapper style. Both modes apply the pan offset as a CSS\n // translate so the user's \"drag to see another part of the\n // zoomed-in design\" works in non-interactive mode too. Interactive\n // mode adds absolute positioning for the infinite-canvas feel.\n const innerWrapperStyle: React.CSSProperties = interactive\n ? {\n position: 'absolute',\n left: 0,\n top: 0,\n transform: `translate(${panOffset.x}px, ${panOffset.y}px)`,\n transformOrigin: '0 0',\n // Prevent pointer events during pan for smoother experience\n pointerEvents: isPanning ? 'none' : 'auto',\n }\n : {\n transform:\n panOffset.x !== 0 || panOffset.y !== 0\n ? `translate(${panOffset.x}px, ${panOffset.y}px)`\n : undefined,\n transformOrigin: '0 0',\n // Critical: `will-change: transform` keeps the wrapper on its\n // own compositor layer so panning feels smooth even at high\n // zoom (canvas DOM is bigger than container).\n willChange: panOffset.x !== 0 || panOffset.y !== 0 ? 'transform' : undefined,\n };\n\n return (\n <div\n ref={containerRef}\n className={className}\n style={containerStyle}\n onPointerDown={interactive ? handlePanStart : undefined}\n onPointerMove={interactive ? handlePanMove : undefined}\n onPointerUp={interactive ? handlePanEnd : undefined}\n onPointerLeave={interactive ? handlePanEnd : undefined}\n onPointerCancel={interactive ? handlePanEnd : undefined}\n >\n <div style={innerWrapperStyle}>\n <CanvasEditor\n ref={canvasEditorRef}\n elements={elements}\n artboards={artboards}\n artboardManager={artboardManager}\n selectedId={selectedId}\n multiSelection={multiSelection}\n onElementUpdate={handleElementUpdate}\n setElements={setElements}\n executeElementUpdate={executeElementUpdate}\n onSelectionChange={handleSelectionChange}\n onMultiSelectionChange={setMultiSelection}\n onActiveChildChange={handleActiveChildChange}\n onTextSelectionChange={() => setTextSelectionVersion((v) => v + 1)}\n onHoverChange={setHoveredElementId}\n onCropModeEnter={handleCropModeEnter}\n hideHandles={hideHandles}\n canvasRef={canvasRef}\n width={activeArtboard?.width}\n height={activeArtboard?.height}\n zoom={effectiveZoom}\n viewPadding={fitPadding}\n artboardBorderRadius={artboardBorderRadius}\n fixedMargin={fixedMargin}\n fixedMarginX={effectiveMarginX}\n fixedMarginY={effectiveMarginY}\n showRotationHandle={showRotationHandle}\n canvasCutouts={canvasCutouts}\n pieceGuides={pieceGuides}\n pieceFocus={pieceFocus}\n focusedPieceRect={focusedPiece}\n />\n </div>\n {!interactive && (userZoom < 0.999 || userZoom > 1.001) && (\n <ZoomPill\n userZoom={userZoom}\n onSetZoom={(target) => {\n // 1× is the auto-fit baseline → reset both zoom AND pan\n // (no useful pan at fit; reset gives a predictable home).\n if (target <= 1.001) {\n resetUserView();\n return;\n }\n const container = containerRef.current;\n if (!container) {\n setUserZoom(target);\n return;\n }\n const rect = container.getBoundingClientRect();\n // Anchor the zoom on the container centre when picked\n // from the menu (no cursor or pinch centroid to use).\n const cx = rect.width / 2;\n const cy = rect.height / 2;\n const ratio = target / Math.max(userZoom, 0.0001);\n setUserZoom(target);\n setPanOffset({\n x: cx - (cx - panOffset.x) * ratio,\n y: cy - (cy - panOffset.y) * ratio,\n });\n }}\n />\n )}\n </div>\n );\n};\n\n// ZoomPill — small floating control rendered inside the canvas\n// container. Lives bottom-right; only appears in non-interactive\n// (editor) mode where the user explicitly drives zoom. Interactive\n// mode has its own infinite-canvas wheel-based UX and doesn't need\n// a discrete pill. Tapping the percentage opens a menu of preset\n// zoom levels — no separate +/- since pinch / wheel handle granular\n// zooming and the presets cover the meaningful steps for editing.\nconst ZOOM_PRESETS = [1, 2, 4, 8] as const;\n\nfunction ZoomPill({\n userZoom,\n onSetZoom,\n}: {\n userZoom: number;\n onSetZoom: (target: number) => void;\n}) {\n const pct = Math.round(userZoom * 100);\n const [menuOpen, setMenuOpen] = React.useState(false);\n return (\n <div\n style={{\n position: 'absolute',\n bottom: 12,\n right: 12,\n zIndex: 30,\n pointerEvents: 'auto',\n }}\n // Tapping the zoom pill / its menu shouldn't deselect — see\n // SelectionChip's matching marker for the rationale.\n data-preserve-selection\n >\n {menuOpen && (\n <>\n {/* Click-outside scrim. Fixed-positioned full-viewport so\n the menu closes on any tap outside it, including\n outside the canvas. */}\n <div\n onClick={() => setMenuOpen(false)}\n style={{\n position: 'fixed',\n inset: 0,\n zIndex: 29,\n }}\n />\n {/* Preset menu, anchored above the trigger. */}\n <div\n role=\"menu\"\n style={{\n position: 'absolute',\n bottom: 'calc(100% + 6px)',\n right: 0,\n zIndex: 31,\n minWidth: 96,\n backdropFilter: 'blur(6px)',\n }}\n className=\"flex flex-col rounded-2xl border border-border/40 bg-popover/95 p-1 shadow-lg\"\n >\n {ZOOM_PRESETS.map((preset) => {\n const presetPct = preset * 100;\n const active = Math.abs(userZoom - preset) < 0.05;\n return (\n <button\n key={preset}\n type=\"button\"\n role=\"menuitem\"\n onClick={() => {\n setMenuOpen(false);\n onSetZoom(preset);\n }}\n className={\n active\n ? 'rounded-xl bg-foreground/10 px-4 py-2 text-base font-medium tabular-nums text-foreground text-right'\n : 'rounded-xl px-4 py-2 text-base font-medium tabular-nums text-foreground/80 hover:bg-foreground/5 hover:text-foreground text-right'\n }\n >\n {presetPct}%\n </button>\n );\n })}\n </div>\n </>\n )}\n <div\n className=\"flex items-center gap-1 rounded-full border border-border/40 bg-popover/85 p-1 shadow-sm\"\n style={{ backdropFilter: 'blur(6px)' }}\n >\n <button\n type=\"button\"\n onClick={() => setMenuOpen((open) => !open)}\n aria-haspopup=\"menu\"\n aria-expanded={menuOpen}\n aria-label=\"Zoom level\"\n className=\"rounded-full px-4 py-1.5 text-base font-medium tabular-nums text-foreground/80 hover:text-foreground\"\n style={{ minWidth: 72 }}\n >\n {pct}%\n </button>\n </div>\n </div>\n );\n}\n","/**\n * useArtboards - Hook for accessing artboard data and operations\n * Provides simplified interface for managing artboards in embed contexts\n */\n\nimport { useMemo, useCallback } from 'react';\nimport { useEditor } from '../contexts/EditorContext.js';\nimport { createLogger } from '../utils/logger.js';\nimport type { ArtboardElement } from '../core/ArtboardElement.js';\nimport type { ClipShape, ArtboardDistressTexture } from '../types/index.js';\n\nconst logger = createLogger('useArtboards');\n\nexport interface ArtboardInfo {\n id: string;\n name: string;\n x: number;\n y: number;\n width: number;\n height: number;\n backgroundColor: string;\n clipShape?: ClipShape;\n isActive: boolean;\n elementCount: number;\n}\n\nexport interface UseArtboardsReturn {\n // All artboards\n artboards: ArtboardElement[];\n\n // Artboard info (simplified for external consumption)\n artboardsInfo: ArtboardInfo[];\n\n // Active artboard\n activeArtboard: ArtboardElement | null;\n activeArtboardId: string | null;\n activeArtboardInfo: ArtboardInfo | null;\n\n // Operations\n createArtboard: (width?: number, height?: number, config?: Partial<{\n name: string;\n x: number;\n y: number;\n backgroundColor: string;\n clipShape: ClipShape;\n }>) => void;\n deleteArtboard: (artboardId: string) => void;\n duplicateArtboard: (artboardId: string) => void;\n renameArtboard: (artboardId: string, newName: string) => void;\n updateArtboard: (artboardId: string, updates: Partial<{\n name: string;\n width: number;\n height: number;\n backgroundColor: string;\n clipShape: ClipShape;\n distressTexture: ArtboardDistressTexture | undefined;\n }>) => void;\n selectArtboard: (artboardId: string) => void;\n reorderArtboards: (fromIndex: number, toIndex: number) => void;\n\n // Count\n count: number;\n}\n\n/**\n * Hook to access artboard data and operations\n * Must be used within EditorProvider\n *\n * @example\n * ```tsx\n * function ArtboardSwitcher() {\n * const { artboardsInfo, selectArtboard, createArtboard } = useArtboards();\n *\n * return (\n * <div>\n * {artboardsInfo.map(ab => (\n * <button\n * key={ab.id}\n * onClick={() => selectArtboard(ab.id)}\n * className={ab.isActive ? 'active' : ''}\n * >\n * {ab.name} ({ab.width}×{ab.height})\n * </button>\n * ))}\n * <button onClick={() => createArtboard(1200, 1200)}>\n * + New Artboard\n * </button>\n * </div>\n * );\n * }\n * ```\n */\nexport const useArtboards = (): UseArtboardsReturn => {\n const {\n artboards,\n artboardManager,\n refreshArtboards,\n setSelectedId,\n setMultiSelection,\n } = useEditor();\n\n const activeArtboardId = artboardManager.getActiveArtboardId();\n const activeArtboard = artboardManager.getActiveArtboard();\n\n // Convert artboards to simplified info objects\n const artboardsInfo = useMemo(() => {\n return artboards.map((artboard) => {\n // Count elements that belong to this artboard using ArtboardManager\n const elementIds = artboardManager.getElementsOnArtboard(artboard.id);\n const elementCount = elementIds.length;\n\n return {\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 clipShape: artboard.clipShape,\n isActive: artboard.id === activeArtboardId,\n elementCount,\n };\n });\n }, [artboards, artboardManager, activeArtboardId]);\n\n // Active artboard info\n const activeArtboardInfo = useMemo(() => {\n if (!activeArtboard) return null;\n const elementIds = artboardManager.getElementsOnArtboard(activeArtboard.id);\n const elementCount = elementIds.length;\n return {\n id: activeArtboard.id,\n name: activeArtboard.name,\n x: activeArtboard.x,\n y: activeArtboard.y,\n width: activeArtboard.width,\n height: activeArtboard.height,\n backgroundColor: activeArtboard.backgroundColor,\n clipShape: activeArtboard.clipShape,\n isActive: true,\n elementCount,\n };\n }, [activeArtboard, artboardManager]);\n\n // Create new artboard\n const createArtboard = useCallback((\n width = 1200,\n height = 1200,\n config?: Partial<{\n name: string;\n x: number;\n y: number;\n backgroundColor: string;\n clipShape: ClipShape;\n }>\n ) => {\n // Build complete config object\n const artboardConfig = {\n width,\n height,\n ...config,\n };\n\n artboardManager.createArtboard(artboardConfig);\n refreshArtboards();\n }, [artboardManager, refreshArtboards]);\n\n // Delete artboard\n const deleteArtboard = useCallback((artboardId: string) => {\n artboardManager.deleteArtboard(artboardId);\n refreshArtboards();\n }, [artboardManager, refreshArtboards]);\n\n // Duplicate artboard\n const duplicateArtboard = useCallback((artboardId: string) => {\n const artboard = artboards.find(ab => ab.id === artboardId);\n if (!artboard) {\n logger.warn(`[useArtboards] Artboard ${artboardId} not found`);\n return;\n }\n\n // Create new artboard with same properties (including clipShape)\n artboardManager.createArtboard({\n width: artboard.width,\n height: artboard.height,\n name: `${artboard.name} Copy`,\n backgroundColor: artboard.backgroundColor,\n clipShape: artboard.clipShape,\n });\n\n // Note: Elements are not duplicated - only the artboard itself\n // To duplicate elements, use element operations separately\n\n refreshArtboards();\n }, [artboards, artboardManager, refreshArtboards]);\n\n // Rename artboard\n const renameArtboard = useCallback((artboardId: string, newName: string) => {\n const artboard = artboards.find(ab => ab.id === artboardId);\n if (!artboard) {\n logger.warn(`[useArtboards] Artboard ${artboardId} not found`);\n return;\n }\n\n artboard.name = newName;\n refreshArtboards();\n }, [artboards, refreshArtboards]);\n\n // Update artboard properties\n const updateArtboard = useCallback((\n artboardId: string,\n updates: Partial<{\n name: string;\n width: number;\n height: number;\n backgroundColor: string;\n clipShape: ClipShape;\n distressTexture: ArtboardDistressTexture | undefined;\n }>\n ) => {\n const artboard = artboards.find(ab => ab.id === artboardId);\n if (!artboard) {\n logger.warn(`[useArtboards] Artboard ${artboardId} not found`);\n return;\n }\n\n if (updates.name !== undefined) artboard.name = updates.name;\n if (updates.width !== undefined) artboard.width = updates.width;\n if (updates.height !== undefined) artboard.height = updates.height;\n if (updates.backgroundColor !== undefined) artboard.backgroundColor = updates.backgroundColor;\n if (updates.clipShape !== undefined) artboard.clipShape = updates.clipShape;\n if ('distressTexture' in updates) artboard.distressTexture = updates.distressTexture ? { ...updates.distressTexture } : undefined;\n\n refreshArtboards();\n }, [artboards, refreshArtboards]);\n\n // Select artboard - also clears selection to avoid stale element references\n const selectArtboard = useCallback((artboardId: string) => {\n // Clear selection before switching artboards\n // This prevents issues where selected elements from the previous artboard\n // remain visually selected on the new artboard\n setSelectedId(null);\n setMultiSelection([]);\n\n artboardManager.setActiveArtboard(artboardId);\n refreshArtboards();\n }, [artboardManager, refreshArtboards, setSelectedId, setMultiSelection]);\n\n // Reorder artboards\n const reorderArtboards = useCallback((fromIndex: number, toIndex: number) => {\n if (fromIndex === toIndex) return;\n if (fromIndex < 0 || fromIndex >= artboards.length) return;\n if (toIndex < 0 || toIndex >= artboards.length) return;\n\n const reordered = [...artboards];\n const [moved] = reordered.splice(fromIndex, 1);\n reordered.splice(toIndex, 0, moved);\n\n artboardManager.reorderArtboards(fromIndex, toIndex);\n refreshArtboards();\n }, [artboards, artboardManager, refreshArtboards]);\n\n return {\n artboards,\n artboardsInfo,\n activeArtboard,\n activeArtboardId,\n activeArtboardInfo,\n createArtboard,\n deleteArtboard,\n duplicateArtboard,\n renameArtboard,\n updateArtboard,\n selectArtboard,\n reorderArtboards,\n count: artboards.length,\n };\n};\n","/**\n * useExport - Hook for exporting artboards to images\n * Supports single export and continuous export (Sprint 2) for live mockup updates\n */\n\nimport { useState, useCallback, useRef, useEffect } from 'react';\nimport { useEditor } from '../contexts/EditorContext.js';\nimport { ExportManager } from '../utils/ExportManager.js';\nimport { createLogger } from '../utils/logger.js';\nimport type { ExportImageOptions } from '../utils/ExportManager.js';\n\nconst logger = createLogger('useExport');\n\nexport interface ExportProgress {\n artboardId: string;\n artboardName: string;\n status: 'pending' | 'rendering' | 'complete' | 'error';\n progress: number; // 0-100\n dataUrl?: string;\n error?: Error;\n timestamp: number;\n}\n\nexport interface ContinuousExportOptions {\n artboardIds?: string[]; // Default: all artboards\n interval?: number; // Milliseconds (default: 500)\n scale?: number; // Resolution multiplier (default: 3)\n format?: 'png' | 'jpg';\n onChange?: (results: Record<string, string>) => void; // Callback with new exports\n}\n\nexport interface UseExportReturn {\n // Single export\n exportArtboard: (\n artboardId: string,\n options?: ExportImageOptions\n ) => Promise<string>; // Returns data URL\n\n exportArtboardAsBlob: (\n artboardId: string,\n options?: ExportImageOptions\n ) => Promise<Blob>; // Returns Blob (more efficient for uploads)\n\n exportAllArtboards: (\n options?: ExportImageOptions\n ) => Promise<Record<string, string>>; // artboardId -> data URL\n\n exportAllArtboardsAsBlobs: (\n options?: ExportImageOptions\n ) => Promise<Record<string, Blob>>; // artboardId -> Blob\n\n // Continuous export (Sprint 2)\n startContinuousExport: (options: ContinuousExportOptions) => void;\n stopContinuousExport: () => void;\n\n // State\n isExporting: boolean;\n isContinuousExporting: boolean;\n progress: ExportProgress[];\n latestExports: Record<string, string>; // artboardId -> data URL\n\n // Worker stats\n supportsWorkerExport: boolean;\n workerStats: {\n totalExports: number;\n avgExportTime: number;\n isWorkerActive: boolean;\n };\n}\n\n/**\n * Hook for exporting artboards to images\n * Must be used within EditorProvider\n *\n * @example Single Export\n * ```tsx\n * function ExportButton() {\n * const { exportArtboard, isExporting } = useExport();\n * const { activeArtboardId } = useArtboards();\n *\n * const handleExport = async () => {\n * if (!activeArtboardId) return;\n * const dataUrl = await exportArtboard(activeArtboardId, {\n * scale: 3,\n * format: 'png'\n * });\n * // Download or display dataUrl\n * };\n *\n * return (\n * <button onClick={handleExport} disabled={isExporting}>\n * {isExporting ? 'Exporting...' : 'Export'}\n * </button>\n * );\n * }\n * ```\n *\n * @example Continuous Export (Sprint 2)\n * ```tsx\n * function LiveMockupPreview() {\n * const { startContinuousExport, stopContinuousExport, latestExports } = useExport();\n * const { artboardsInfo } = useArtboards();\n *\n * useEffect(() => {\n * startContinuousExport({\n * interval: 500,\n * scale: 3,\n * onChange: (exports) => {\n * // Send to mockup API\n * Object.entries(exports).forEach(([artboardId, dataUrl]) => {\n * const artboard = artboardsInfo.find(a => a.id === artboardId);\n * sendToMockupAPI(artboard.name, dataUrl);\n * });\n * }\n * });\n *\n * return () => stopContinuousExport();\n * }, []);\n *\n * return <div>Mockup preview...</div>;\n * }\n * ```\n */\nexport function useExport(): UseExportReturn {\n const {\n artboards,\n artboardManager,\n elements,\n canvasRef,\n } = useEditor();\n\n const [isExporting, setIsExporting] = useState(false);\n const [isContinuousExporting, setIsContinuousExporting] = useState(false);\n const [progress, setProgress] = useState<ExportProgress[]>([]);\n const [latestExports, setLatestExports] = useState<Record<string, string>>({});\n const [workerStats, setWorkerStats] = useState({\n totalExports: 0,\n avgExportTime: 0,\n isWorkerActive: false,\n });\n\n // Continuous export loop control\n // Each startContinuousExport call bumps the generation; any running loop\n // from a previous call sees the mismatch and exits without exporting again.\n const continuousExportGeneration = useRef(0);\n const continuousExportOptions = useRef<ContinuousExportOptions | null>(null);\n\n // Check if worker export is supported\n const supportsWorkerExport = ExportManager.supportsWorkerExport();\n\n /**\n * Export a single artboard\n */\n const exportArtboard = useCallback(async (\n artboardId: string,\n options: ExportImageOptions = {}\n ): Promise<string> => {\n const artboard = artboards.find(ab => ab.id === artboardId);\n if (!artboard) {\n throw new Error(`[useExport] Artboard ${artboardId} not found`);\n }\n\n if (!canvasRef.current) {\n throw new Error('[useExport] Canvas ref not available');\n }\n\n setIsExporting(true);\n\n // Add progress tracking\n const progressEntry: ExportProgress = {\n artboardId,\n artboardName: artboard.name,\n status: 'rendering',\n progress: 0,\n timestamp: Date.now(),\n };\n setProgress(prev => [...prev, progressEntry]);\n\n try {\n const startTime = Date.now();\n\n // Read elements fresh via ref — not from closure (which can be stale)\n const currentElements = elementsRef.current;\n const elementIds = artboardManager.getElementsOnArtboard(artboardId);\n const artboardElements = currentElements.filter(el => elementIds.includes(el.id));\n\n // Export using ExportManager (use exportArtboardToDataURL which returns string)\n const dataUrl = await ExportManager.exportArtboardToDataURL(\n artboard,\n artboardElements,\n canvasRef.current!,\n {\n format: options.format || 'png',\n scale: options.scale || 3,\n transparentBackground: options.transparentBackground ?? true,\n ...options,\n }\n );\n\n const exportTime = Date.now() - startTime;\n\n // Update progress\n setProgress(prev => prev.map(p =>\n p.artboardId === artboardId\n ? { ...p, status: 'complete', progress: 100, dataUrl, timestamp: Date.now() }\n : p\n ) as ExportProgress[]);\n\n // Update latest exports\n setLatestExports(prev => ({ ...prev, [artboardId]: dataUrl }));\n\n // Update worker stats\n setWorkerStats(prev => ({\n totalExports: prev.totalExports + 1,\n avgExportTime: (prev.avgExportTime * prev.totalExports + exportTime) / (prev.totalExports + 1),\n isWorkerActive: supportsWorkerExport,\n }));\n\n return dataUrl;\n } catch (error) {\n // Update progress with error\n setProgress(prev => prev.map(p =>\n p.artboardId === artboardId\n ? { ...p, status: 'error', error: error as Error, timestamp: Date.now() }\n : p\n ));\n throw error;\n } finally {\n setIsExporting(false);\n }\n }, [artboards, artboardManager, elements, canvasRef, supportsWorkerExport]);\n\n /**\n * Export all artboards in PARALLEL for maximum performance\n */\n const exportAllArtboards = useCallback(async (\n options: ExportImageOptions = {}\n ): Promise<Record<string, string>> => {\n setIsExporting(true);\n\n try {\n // Export ALL artboards in parallel using Promise.all\n const exportPromises = artboards.map(async (artboard) => {\n const dataUrl = await exportArtboard(artboard.id, options);\n return { id: artboard.id, dataUrl };\n });\n\n const exportResults = await Promise.all(exportPromises);\n\n // Convert array to record\n const results: Record<string, string> = {};\n for (const { id, dataUrl } of exportResults) {\n results[id] = dataUrl;\n }\n\n return results;\n } finally {\n setIsExporting(false);\n }\n }, [artboards, exportArtboard]);\n\n /**\n * Export a single artboard as Blob (more efficient for uploads)\n */\n // Ref to always read the latest elements — avoids stale closure in export callbacks.\n // Without this, the export callback captures `elements` from the render when the\n // callback was created, which can be 1-2 renders behind the actual current state.\n const elementsRef = useRef(elements);\n elementsRef.current = elements;\n\n const exportArtboardAsBlob = useCallback(async (\n artboardId: string,\n options: ExportImageOptions = {}\n ): Promise<Blob> => {\n const artboard = artboards.find(ab => ab.id === artboardId);\n if (!artboard) {\n throw new Error(`[useExport] Artboard ${artboardId} not found`);\n }\n\n if (!canvasRef.current) {\n throw new Error('[useExport] Canvas ref not available');\n }\n\n setIsExporting(true);\n\n try {\n // Read elements fresh via ref — not from closure (which can be stale)\n const currentElements = elementsRef.current;\n const elementIds = artboardManager.getElementsOnArtboard(artboardId);\n const artboardElements = currentElements.filter(el => elementIds.includes(el.id));\n\n // Export using ExportManager blob method\n const blob = await ExportManager.exportArtboardToBlob(\n artboard,\n artboardElements,\n canvasRef.current!,\n {\n format: options.format || 'png',\n scale: options.scale || 3,\n transparentBackground: options.transparentBackground ?? true,\n ...options,\n }\n );\n\n return blob;\n } finally {\n setIsExporting(false);\n }\n }, [artboards, artboardManager, elements, canvasRef]);\n\n /**\n * Export all artboards as Blobs in PARALLEL for maximum performance\n */\n const exportAllArtboardsAsBlobs = useCallback(async (\n options: ExportImageOptions = {}\n ): Promise<Record<string, Blob>> => {\n setIsExporting(true);\n\n try {\n // Export ALL artboards in parallel using Promise.all\n const exportPromises = artboards.map(async (artboard) => {\n const blob = await exportArtboardAsBlob(artboard.id, options);\n return { id: artboard.id, blob };\n });\n\n const exportResults = await Promise.all(exportPromises);\n\n // Convert array to record\n const results: Record<string, Blob> = {};\n for (const { id, blob } of exportResults) {\n results[id] = blob;\n }\n\n return results;\n } finally {\n setIsExporting(false);\n }\n }, [artboards, exportArtboardAsBlob]);\n\n /**\n * Stop continuous export (Sprint 2)\n */\n const stopContinuousExport = useCallback(() => {\n // Bump generation so any in-flight loop exits after its current export\n continuousExportGeneration.current++;\n setIsContinuousExporting(false);\n continuousExportOptions.current = null;\n }, []);\n\n /**\n * Start continuous export\n * Exports artboards at a specified interval for live mockup updates.\n * Uses a promise-based loop so a new export only starts after the previous\n * one completes, preventing unbounded queue buildup when exports are slow.\n */\n const startContinuousExport = useCallback((options: ContinuousExportOptions) => {\n // Invalidate any existing loop by bumping the generation counter\n continuousExportGeneration.current++;\n const myGeneration = continuousExportGeneration.current;\n\n // Store options\n continuousExportOptions.current = options;\n setIsContinuousExporting(true);\n\n const {\n artboardIds,\n interval = 500,\n scale = 3,\n format = 'png',\n onChange,\n } = options;\n\n // Determine which artboards to export\n const targetArtboards = artboardIds\n ? artboards.filter(ab => artboardIds.includes(ab.id))\n : artboards;\n\n // Returns true only while this particular loop instance is still active\n const isCurrentGeneration = () =>\n continuousExportGeneration.current === myGeneration;\n\n // Export function - exports ALL artboards in PARALLEL\n const performExport = async () => {\n try {\n const exportPromises = targetArtboards.map(async (artboard) => {\n const dataUrl = await exportArtboard(artboard.id, {\n scale,\n format,\n transparentBackground: true,\n });\n return { id: artboard.id, dataUrl };\n });\n\n const exportResults = await Promise.all(exportPromises);\n\n const results: Record<string, string> = {};\n for (const { id, dataUrl } of exportResults) {\n results[id] = dataUrl;\n }\n\n setLatestExports(prev => ({ ...prev, ...results }));\n\n if (onChange) {\n onChange(results);\n }\n } catch (error) {\n logger.error('[useExport] Continuous export error:', error);\n }\n };\n\n // Promise-based scheduling: perform export, wait for interval, repeat.\n // This guarantees no new export starts until the previous one finishes,\n // eliminating queue buildup when exports take longer than the interval.\n const runLoop = async () => {\n await performExport();\n while (isCurrentGeneration()) {\n await new Promise<void>(resolve => setTimeout(resolve, interval));\n if (!isCurrentGeneration()) break;\n await performExport();\n }\n };\n\n runLoop();\n }, [artboards, exportArtboard]);\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n stopContinuousExport();\n };\n }, [stopContinuousExport]);\n\n return {\n exportArtboard,\n exportArtboardAsBlob,\n exportAllArtboards,\n exportAllArtboardsAsBlobs,\n startContinuousExport,\n stopContinuousExport,\n isExporting,\n isContinuousExporting,\n progress,\n latestExports,\n supportsWorkerExport,\n workerStats,\n };\n}\n","import { debounce } from 'lodash-es';\nimport { createLogger } from '../utils/logger.js';\n\nconst logger = createLogger('AutoExportManager');\n\n/**\n * Configuration for auto-export behavior\n */\nexport interface AutoExportConfig {\n /** Enable/disable auto-export */\n enabled: boolean;\n\n /** Debounce delay - wait this long after last change before exporting (ms) */\n debounceMs: number;\n\n /** Maximum wait time - force export after this duration even if changes keep coming (ms) */\n maxWaitMs: number;\n}\n\n/**\n * Default configuration values\n */\nexport const DEFAULT_AUTO_EXPORT_CONFIG: AutoExportConfig = {\n enabled: true,\n debounceMs: 100,\n maxWaitMs: 1000,\n};\n\n/**\n * Statistics about auto-export behavior\n */\nexport interface AutoExportStats {\n /** Total number of exports triggered */\n totalExports: number;\n\n /** Number of exports skipped due to no changes */\n skippedExports: number;\n\n /** Last export timestamp */\n lastExportTime: number | null;\n\n /** Average time between exports (ms) */\n avgExportInterval: number;\n}\n\n/**\n * Manages automatic export triggered by editor state changes.\n *\n * Uses lodash debounce with `maxWait` to batch rapid changes (drag operations,\n * continuous typing) into a single export while guaranteeing a maximum latency.\n * Performs deep state comparison to skip no-op exports.\n *\n * Typically used via the `useAutoExport` hook rather than directly.\n *\n * @example\n * ```ts\n * const manager = new AutoExportManager({ enabled: true, debounceMs: 100, maxWaitMs: 1000 });\n * manager.onExport(async () => { await exportAllArtboards(); });\n * manager.scheduleExport(); // debounced\n * manager.destroy(); // cleanup\n * ```\n */\nexport class AutoExportManager {\n private config: AutoExportConfig;\n private debouncedExport: ReturnType<typeof debounce>;\n private onExportCallback: (() => void | Promise<void>) | null = null;\n private onExportScheduledCallback: (() => void) | null = null;\n // getStateCallback removed — no longer needed with revision counter\n\n // Change detection via revision counter (replaces expensive JSON.stringify)\n private revision = 0;\n private lastExportedRevision = -1;\n\n // Statistics\n private stats: AutoExportStats = {\n totalExports: 0,\n skippedExports: 0,\n lastExportTime: null,\n avgExportInterval: 0,\n };\n private exportTimestamps: number[] = [];\n\n constructor(config: Partial<AutoExportConfig> = {}) {\n this.config = { ...DEFAULT_AUTO_EXPORT_CONFIG, ...config };\n\n // Create debounced export with maxWait\n this.debouncedExport = debounce(\n () => this.performExport(),\n this.config.debounceMs,\n {\n maxWait: this.config.maxWaitMs,\n leading: false,\n trailing: true,\n }\n );\n }\n\n /**\n * Schedule an export (will be debounced)\n * Call this whenever any change happens in the editor\n */\n scheduleExport(): void {\n const timestamp = Date.now();\n\n if (!this.config.enabled) {\n return;\n }\n\n // Always bump revision so no changes are lost\n this.revision++;\n\n // Ignore scheduling within 200ms of last export (likely re-render artifacts)\n // But the revision was still bumped above, so the next scheduled export\n // will pick up this change — it won't be silently dropped.\n const timeSinceLastExport = this.stats.lastExportTime ? timestamp - this.stats.lastExportTime : Infinity;\n if (timeSinceLastExport < 200) {\n return;\n }\n\n // Notify immediately that export is scheduled (for shimmer feedback)\n // This fires when a change is detected, not after debounce, so UI can\n // immediately stop showing stale mockups and show loading state instead\n if (this.onExportScheduledCallback) {\n this.onExportScheduledCallback();\n }\n\n // Trigger debounced export\n this.debouncedExport();\n }\n\n /**\n * Force an immediate export (bypassing debounce)\n */\n async forceExport(): Promise<void> {\n // Cancel any pending debounced export\n this.debouncedExport.cancel();\n\n // Bump revision so performExport sees a change\n this.revision++;\n\n // Perform export immediately\n await this.performExport();\n }\n\n /**\n * Internal: Perform the actual export\n *\n * PERF: Uses a revision counter instead of JSON.stringify for change detection.\n * scheduleExport() bumps `this.revision`; we compare it to `lastExportedRevision`.\n * This eliminates the expensive JSON.stringify of all elements+artboards that was\n * causing ~50-200ms of main-thread work every time the debounce fired (~1s).\n *\n * The export callback itself (element serialization + worker postMessage) is\n * deferred via setTimeout(0) so it doesn't block the current animation frame.\n */\n private async performExport(): Promise<void> {\n // CRITICAL: Check if disabled BEFORE doing any work\n // This catches the case where a debounced call was already scheduled\n // but we've since disabled exports (e.g., user pressed \"Done\" during drag)\n if (!this.config.enabled) {\n return;\n }\n\n // Check if anything actually changed since last export\n if (this.revision === this.lastExportedRevision) {\n this.stats.skippedExports++;\n return;\n }\n\n // Capture the revision we're about to export\n const exportingRevision = this.revision;\n\n const now = Date.now();\n\n // Update statistics\n this.exportTimestamps.push(now);\n\n // Keep only last 10 timestamps for averaging\n if (this.exportTimestamps.length > 10) {\n this.exportTimestamps.shift();\n }\n\n // Calculate average interval\n if (this.exportTimestamps.length > 1) {\n const intervals = [];\n for (let i = 1; i < this.exportTimestamps.length; i++) {\n intervals.push(this.exportTimestamps[i] - this.exportTimestamps[i - 1]);\n }\n this.stats.avgExportInterval = intervals.reduce((a, b) => a + b, 0) / intervals.length;\n }\n\n this.stats.totalExports++;\n this.stats.lastExportTime = now;\n\n // Trigger export callback — deferred to next macrotask so it doesn't\n // block the current animation frame (prevents drag stutter).\n // Fire-and-forget: stats are already updated above, and the actual\n // export work (worker postMessage) is inherently async.\n if (this.onExportCallback) {\n // Deferred to next macrotask so it doesn't block the current animation frame.\n // CRITICAL: Read this.onExportCallback INSIDE the timeout, not before it.\n // Reading it before creates a race condition: React may update the callback\n // (via useAutoExport re-registration) between capture and execution.\n setTimeout(async () => {\n const callback = this.onExportCallback;\n if (!callback) return;\n try {\n await callback();\n } catch (error) {\n logger.error('Export failed:', error);\n }\n this.lastExportedRevision = exportingRevision;\n // If new changes arrived during the export, schedule another one\n if (this.revision > exportingRevision && this.config.enabled) {\n this.debouncedExport();\n }\n }, 0);\n } else {\n logger.warn('No export callback registered');\n }\n }\n\n // getCurrentStateSnapshot removed — replaced with revision counter\n // (JSON.stringify was causing 50-200ms main-thread stutter per export cycle)\n\n /**\n * Register callback to be called when export should happen\n */\n onExport(callback: () => void | Promise<void>): void {\n this.onExportCallback = callback;\n }\n\n /**\n * Register callback to be notified when an export is scheduled (before debounce)\n * This is called immediately when a change is detected, allowing consumers\n * to show loading indicators before the actual export completes.\n */\n onExportScheduled(callback: () => void): void {\n this.onExportScheduledCallback = callback;\n }\n\n // onGetState removed — no longer needed with revision counter\n\n /**\n * Update configuration (can be called while running)\n */\n updateConfig(newConfig: Partial<AutoExportConfig>): void {\n const oldConfig = { ...this.config };\n\n // IMPORTANT: If disabling, flush pending exports BEFORE updating config\n // This ensures the user's last edits are exported before we stop\n // (e.g., when clicking \"Done\" shortly after making edits)\n // We must flush before setting enabled=false, otherwise performExport()\n // will see enabled=false and return early, losing the final export.\n if (oldConfig.enabled && newConfig.enabled === false) {\n this.debouncedExport.flush();\n }\n\n this.config = { ...this.config, ...newConfig };\n\n // If timing changed, recreate debounced function\n if (oldConfig.debounceMs !== this.config.debounceMs || oldConfig.maxWaitMs !== this.config.maxWaitMs) {\n // Cancel old debounced function\n this.debouncedExport.cancel();\n\n // Create new debounced function with updated timings\n this.debouncedExport = debounce(\n () => this.performExport(),\n this.config.debounceMs,\n {\n maxWait: this.config.maxWaitMs,\n leading: false,\n trailing: true,\n }\n );\n\n }\n }\n\n /**\n * Get current statistics\n */\n getStats(): AutoExportStats {\n return { ...this.stats };\n }\n\n /**\n * Reset statistics\n */\n resetStats(): void {\n this.stats = {\n totalExports: 0,\n skippedExports: 0,\n lastExportTime: null,\n avgExportInterval: 0,\n };\n this.exportTimestamps = [];\n }\n\n /**\n * Reset change detection (next export will always trigger)\n */\n resetChangeDetection(): void {\n this.lastExportedRevision = -1;\n }\n\n /**\n * Cleanup resources\n */\n destroy(): void {\n // Cancel any pending exports\n this.debouncedExport.cancel();\n\n // Clear callbacks\n this.onExportCallback = null;\n this.onExportScheduledCallback = null;\n\n // Reset revision tracking\n this.lastExportedRevision = -1;\n\n }\n}\n","/**\n * LoadingStates - Loading and skeleton components for async operations\n * Provides loading states for editor components\n *\n * @example\n * ```tsx\n * import { CanvasSkeleton, useProjectLoader } from '@snowcone-app/canvas/embed';\n *\n * function ProjectLoader({ projectId }) {\n * const { loadFromJSON, isLoading } = useProjectLoader();\n *\n * useEffect(() => {\n * loadProject(projectId);\n * }, [projectId]);\n *\n * if (isLoading) {\n * return <CanvasSkeleton width={800} height={600} />;\n * }\n *\n * return <Canvas />;\n * }\n * ```\n */\n\nimport { CSSProperties } from 'react';\n\nexport interface CanvasSkeletonProps {\n width?: number | string;\n height?: number | string;\n style?: CSSProperties;\n}\n\nexport interface LayersPanelSkeletonProps {\n itemCount?: number;\n style?: CSSProperties;\n}\n\nexport interface ToolbarSkeletonProps {\n style?: CSSProperties;\n}\n\n/**\n * Canvas loading skeleton\n */\nexport function CanvasSkeleton({ width = 800, height = 600, style }: CanvasSkeletonProps) {\n return (\n <div\n className=\"canvas-skeleton\"\n style={{\n width,\n height,\n backgroundColor: 'var(--divider)',\n borderRadius: '8px',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n position: 'relative',\n overflow: 'hidden',\n ...style,\n }}\n >\n {/* Shimmer effect */}\n <div\n style={{\n position: 'absolute',\n top: 0,\n left: '-100%',\n width: '100%',\n height: '100%',\n background: 'linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent)',\n animation: 'shimmer 2s infinite',\n }}\n />\n\n {/* Loading text */}\n <div style={{ fontSize: '14px', color: 'var(--muted)', fontFamily: 'system-ui, sans-serif' }}>\n Loading canvas...\n </div>\n\n <style>{`\n @keyframes shimmer {\n 0% { transform: translateX(0); }\n 100% { transform: translateX(200%); }\n }\n `}</style>\n </div>\n );\n}\n\n/**\n * Layers panel loading skeleton\n */\nexport function LayersPanelSkeleton({ itemCount = 5, style }: LayersPanelSkeletonProps) {\n return (\n <div\n className=\"layers-panel-skeleton\"\n style={{\n width: '100%',\n padding: '16px',\n backgroundColor: 'var(--surface)',\n borderRadius: '8px',\n ...style,\n }}\n >\n {Array.from({ length: itemCount }).map((_, index) => (\n <div\n key={index}\n className=\"layer-item-skeleton\"\n style={{\n height: '48px',\n backgroundColor: 'var(--divider)',\n borderRadius: '6px',\n marginBottom: index < itemCount - 1 ? '8px' : 0,\n position: 'relative',\n overflow: 'hidden',\n }}\n >\n {/* Shimmer effect */}\n <div\n style={{\n position: 'absolute',\n top: 0,\n left: '-100%',\n width: '100%',\n height: '100%',\n background: 'linear-gradient(90deg, transparent, rgba(255,255,255,0.5), transparent)',\n animation: 'shimmer 2s infinite',\n animationDelay: `${index * 0.1}s`,\n }}\n />\n </div>\n ))}\n\n <style>{`\n @keyframes shimmer {\n 0% { transform: translateX(0); }\n 100% { transform: translateX(200%); }\n }\n `}</style>\n </div>\n );\n}\n\n/**\n * Toolbar loading skeleton\n */\nexport function ToolbarSkeleton({ style }: ToolbarSkeletonProps) {\n return (\n <div\n className=\"toolbar-skeleton\"\n style={{\n height: '56px',\n display: 'flex',\n alignItems: 'center',\n gap: '8px',\n padding: '0 16px',\n backgroundColor: 'var(--surface)',\n borderBottom: '1px solid var(--divider)',\n ...style,\n }}\n >\n {/* Button skeletons */}\n {[40, 60, 80, 100, 60, 40].map((width, index) => (\n <div\n key={index}\n style={{\n width: `${width}px`,\n height: '32px',\n backgroundColor: 'var(--divider)',\n borderRadius: '6px',\n position: 'relative',\n overflow: 'hidden',\n }}\n >\n {/* Shimmer effect */}\n <div\n style={{\n position: 'absolute',\n top: 0,\n left: '-100%',\n width: '100%',\n height: '100%',\n background: 'linear-gradient(90deg, transparent, rgba(255,255,255,0.5), transparent)',\n animation: 'shimmer 2s infinite',\n animationDelay: `${index * 0.15}s`,\n }}\n />\n </div>\n ))}\n\n <style>{`\n @keyframes shimmer {\n 0% { transform: translateX(0); }\n 100% { transform: translateX(200%); }\n }\n `}</style>\n </div>\n );\n}\n\n/**\n * Generic loading spinner\n */\nexport function Spinner({ size = 32, color = 'var(--primary)' }: { size?: number; color?: string }) {\n return (\n <div\n className=\"spinner\"\n style={{\n width: `${size}px`,\n height: `${size}px`,\n border: `3px solid var(--divider)`,\n borderTopColor: color,\n borderRadius: '50%',\n animation: 'spin 0.8s linear infinite',\n }}\n >\n <style>{`\n @keyframes spin {\n to { transform: rotate(360deg); }\n }\n `}</style>\n </div>\n );\n}\n","import type { KitDefinition } from './types.js';\nimport type { PanelType } from '../types/capabilities.js';\n\n/**\n * Result of validating a {@link KitDefinition}.\n */\nexport interface KitValidationResult {\n /** Whether the kit is valid (no errors). Warnings do not affect validity. */\n valid: boolean;\n /** Fatal configuration errors that would prevent the editor from functioning. */\n errors: string[];\n /** Non-fatal issues that may produce unexpected behavior. */\n warnings: string[];\n}\n\n/**\n * Validates a {@link KitDefinition} for consistency and completeness.\n *\n * Use this to catch configuration errors before passing a kit to `KitRenderer`.\n * Errors indicate fatal issues (editor won't work), while warnings indicate\n * potential misconfigurations that may produce unexpected behavior.\n *\n * @param kit - The kit definition to validate.\n * @returns A {@link KitValidationResult} with errors and warnings.\n *\n * @example\n * ```ts\n * import { validateKit, COMPACT_CUSTOMIZER } from '@snowcone-app/canvas';\n *\n * const result = validateKit(COMPACT_CUSTOMIZER);\n * if (!result.valid) {\n * console.error('Kit errors:', result.errors);\n * }\n * if (result.warnings.length > 0) {\n * console.warn('Kit warnings:', result.warnings);\n * }\n * ```\n */\nexport function validateKit(kit: KitDefinition): KitValidationResult {\n const errors: string[] = [];\n const warnings: string[] = [];\n\n // --- Name ---\n if (!kit.name || kit.name.trim() === '') {\n errors.push('Kit name is required');\n }\n\n // --- Layout / Behavior consistency ---\n if (kit.behavior.toolbar === 'none' && kit.layout.slots.topbar?.length) {\n warnings.push('Toolbar mode is \"none\" but topbar has sections — topbar sections will be ignored');\n }\n\n // --- Capabilities / Behavior alignment ---\n if (kit.behavior.selection === 'multi' && !kit.capabilities.tools.includes('select')) {\n warnings.push('Multi-selection enabled but \"select\" tool not in capabilities');\n }\n\n // --- Capabilities minimums ---\n if (kit.capabilities.elements.length === 0) {\n errors.push('At least one element type must be enabled in capabilities.elements');\n }\n\n if (kit.capabilities.transforms.length === 0) {\n errors.push('At least one transform type must be enabled in capabilities.transforms');\n }\n\n // --- Auto-export without export capability ---\n if (kit.behavior.autoExport && !kit.capabilities.features.export) {\n warnings.push('autoExport is enabled but features.export is false — exports will not be produced');\n }\n\n // --- Panel layout references unknown panels ---\n const panelSlots = [...(kit.layout.slots.left ?? []), ...(kit.layout.slots.right ?? [])];\n const knownPanelSections = ['layers-panel', 'effects-panel', 'export-panel', 'images-panel', 'artboard-tabs'];\n for (const section of panelSlots) {\n if (knownPanelSections.includes(section)) {\n // Check that the corresponding panel capability is enabled\n const sectionToPanelType: Record<string, string> = {\n 'layers-panel': 'layers',\n 'effects-panel': 'effects',\n 'export-panel': 'export',\n 'images-panel': 'images',\n 'artboard-tabs': 'artboards',\n };\n const panelType = sectionToPanelType[section];\n if (panelType && !kit.capabilities.panels.includes(panelType as PanelType)) {\n warnings.push(`Layout references \"${section}\" but \"${panelType}\" is not in capabilities.panels`);\n }\n }\n }\n\n return { valid: errors.length === 0, errors, warnings };\n}\n","/**\n * Kit Composition Helpers\n *\n * Utilities for creating and extending kit definitions. Use `extendKit()` to\n * customize an existing preset, or `createKit()` to build a kit from scratch\n * with sensible defaults.\n *\n * @example Extend a preset\n * ```ts\n * import { extendKit, COMPACT_CUSTOMIZER } from '@snowcone-app/canvas';\n *\n * const myKit = extendKit(COMPACT_CUSTOMIZER, {\n * name: 'my-customizer',\n * layout: { slots: { right: ['my-effects-panel'] } },\n * capabilities: { effects: ['stroke', 'mask'] },\n * behavior: { autoExport: true },\n * });\n * ```\n *\n * @example Create a minimal kit\n * ```ts\n * import { createKit } from '@snowcone-app/canvas';\n *\n * const simpleKit = createKit({\n * name: 'text-only',\n * capabilities: { elements: ['text'] },\n * });\n * ```\n */\n\nimport type { KitDefinition } from './types.js';\n\n// ---------------------------------------------------------------------------\n// DeepPartial utility\n// ---------------------------------------------------------------------------\n\n/** Utility type for deep partial objects */\ntype DeepPartial<T> = {\n [P in keyof T]?: T[P] extends (infer U)[]\n ? U[]\n : T[P] extends object\n ? DeepPartial<T[P]>\n : T[P];\n};\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Merge two arrays by creating a union with deduplication.\n * Order: base items first, then new items not already present.\n */\nfunction mergeArrays<T>(base: T[], override: T[]): T[] {\n const seen = new Set(base);\n const result = [...base];\n for (const item of override) {\n if (!seen.has(item)) {\n seen.add(item);\n result.push(item);\n }\n }\n return result;\n}\n\n/**\n * Shallow-merge two plain objects. Override wins for each key.\n * Returns a new object (does not mutate inputs).\n */\nfunction shallowMerge<T extends Record<string, unknown>>(\n base: T,\n override: Partial<T>,\n): T {\n return { ...base, ...override };\n}\n\n// ---------------------------------------------------------------------------\n// Slot keys (layout.slots) — used to merge arrays per slot\n// ---------------------------------------------------------------------------\n\nconst SLOT_KEYS = ['topbar', 'left', 'canvas', 'right', 'bottombar'] as const;\n\n// ---------------------------------------------------------------------------\n// extendKit\n// ---------------------------------------------------------------------------\n\n/**\n * Deep-merges overrides into a base kit definition to create a new customized kit.\n *\n * Merge behavior by field type:\n * - **Arrays** (layout slots, capability lists) — union/concatenate with deduplication\n * - **Objects** (behavior, style, content, features) — shallow merge (override wins per key)\n * - **Primitives** (name, strings, numbers, booleans) — override replaces base\n * - **Undefined fields** — base value is kept\n *\n * @param base - The base kit to extend (not mutated)\n * @param overrides - Partial overrides to apply on top\n * @returns A new KitDefinition with the merged result\n *\n * @example\n * ```ts\n * const myKit = extendKit(COMPACT_CUSTOMIZER, {\n * name: 'my-customizer',\n * layout: { slots: { right: ['my-effects-panel'] } },\n * capabilities: { effects: ['stroke', 'mask'] },\n * behavior: { autoExport: true },\n * });\n * ```\n */\nexport function extendKit(\n base: KitDefinition,\n overrides: DeepPartial<KitDefinition>,\n): KitDefinition {\n // --- name ---\n const name = overrides.name ?? base.name;\n\n // --- layout.slots ---\n const baseSlots = base.layout.slots;\n const overrideSlots = overrides.layout?.slots;\n const mergedSlots: KitDefinition['layout']['slots'] = {};\n\n for (const key of SLOT_KEYS) {\n const baseArr = baseSlots[key];\n const overrideArr = overrideSlots?.[key];\n if (baseArr && overrideArr) {\n mergedSlots[key] = mergeArrays(baseArr, overrideArr);\n } else if (overrideArr) {\n mergedSlots[key] = [...overrideArr];\n } else if (baseArr) {\n mergedSlots[key] = [...baseArr];\n }\n }\n\n // --- capabilities ---\n const baseCaps = base.capabilities;\n const overrideCaps = overrides.capabilities;\n\n const mergedCapabilities: KitDefinition['capabilities'] = {\n elements: baseCaps.elements,\n transforms: baseCaps.transforms,\n effects: baseCaps.effects,\n panels: baseCaps.panels,\n tools: baseCaps.tools,\n features: { ...baseCaps.features },\n };\n\n if (overrideCaps) {\n if (overrideCaps.elements) {\n mergedCapabilities.elements = mergeArrays(baseCaps.elements, overrideCaps.elements);\n }\n if (overrideCaps.transforms) {\n mergedCapabilities.transforms = mergeArrays(baseCaps.transforms, overrideCaps.transforms);\n }\n if (overrideCaps.effects) {\n mergedCapabilities.effects = mergeArrays(baseCaps.effects, overrideCaps.effects);\n }\n if (overrideCaps.panels) {\n mergedCapabilities.panels = mergeArrays(baseCaps.panels, overrideCaps.panels);\n }\n if (overrideCaps.tools) {\n mergedCapabilities.tools = mergeArrays(baseCaps.tools, overrideCaps.tools);\n }\n if (overrideCaps.features) {\n mergedCapabilities.features = shallowMerge(\n baseCaps.features,\n overrideCaps.features,\n );\n }\n }\n\n // --- behavior ---\n const mergedBehavior = overrides.behavior\n ? shallowMerge(base.behavior, overrides.behavior as Partial<KitDefinition['behavior']>)\n : { ...base.behavior };\n\n // --- style ---\n const mergedStyle = overrides.style\n ? shallowMerge(base.style, overrides.style as Partial<KitDefinition['style']>)\n : { ...base.style };\n\n // --- content ---\n const baseContent = base.content ?? {};\n const mergedContent = overrides.content\n ? shallowMerge(baseContent, overrides.content as Partial<NonNullable<KitDefinition['content']>>)\n : { ...baseContent };\n\n return {\n name,\n layout: { slots: mergedSlots },\n capabilities: mergedCapabilities,\n behavior: mergedBehavior,\n style: mergedStyle,\n content: mergedContent,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Default minimal kit — used as the base for createKit()\n// ---------------------------------------------------------------------------\n\nconst MINIMAL_KIT_DEFAULTS: Omit<KitDefinition, 'name'> = {\n layout: {\n slots: {\n canvas: ['canvas'],\n },\n },\n capabilities: {\n elements: ['text', 'image'],\n transforms: ['custom', 'image'],\n effects: [],\n panels: [],\n tools: ['select'],\n features: {\n export: true,\n multiArtboard: false,\n richText: false,\n undo: true,\n keyboardShortcuts: true,\n },\n },\n behavior: {\n toolbar: 'contextual',\n selection: 'single',\n panelMode: 'drawer',\n onEmptyClick: 'deselect',\n autoExport: false,\n },\n style: {\n density: 'compact',\n toolbarVariant: 'pill',\n panelVariant: 'floating',\n },\n content: {\n sampleText: 'Your Text',\n defaultFontFamily: 'Poppins',\n },\n};\n\n// ---------------------------------------------------------------------------\n// createKit\n// ---------------------------------------------------------------------------\n\n/**\n * Creates a kit definition from minimal input with sensible defaults.\n *\n * Only the `name` field is required. All other fields fall back to minimal\n * defaults suitable for a simple product customizer: canvas-only layout,\n * text + image elements, contextual toolbar, single selection.\n *\n * Internally delegates to `extendKit()` using the minimal defaults as the\n * base, so the same merge semantics apply (arrays are union-merged, objects\n * are shallow-merged, etc.).\n *\n * @param partial - Kit configuration with at least a `name`.\n * @returns A complete KitDefinition\n *\n * @example\n * ```ts\n * const simpleKit = createKit({\n * name: 'text-only',\n * capabilities: { elements: ['text'] },\n * });\n * ```\n *\n * @example\n * ```ts\n * const proKit = createKit({\n * name: 'pro-lite',\n * layout: { slots: { left: ['layers-panel'] } },\n * capabilities: {\n * elements: ['text', 'image', 'shape'],\n * effects: ['stroke', 'mask'],\n * panels: ['layers', 'effects'],\n * },\n * behavior: { selection: 'multi', panelMode: 'sidebar' },\n * });\n * ```\n */\nexport function createKit(\n partial: { name: string } & DeepPartial<Omit<KitDefinition, 'name'>>,\n): KitDefinition {\n const { name, ...rest } = partial;\n const base: KitDefinition = { name, ...MINIMAL_KIT_DEFAULTS };\n return extendKit(base, rest);\n}\n"],"names":["getIconsTree","data","names","icons","aliases","resolved","resolve","name","parent","value","defaultIconDimensions","defaultIconTransformations","defaultIconProps","defaultExtendedIconProps","mergeIconTransformations","obj1","obj2","result","rotate","mergeIconData","child","key","internalGetIconData","tree","currentProps","parse","name$1","parseIconSet","callback","item","optionalPropertyDefaults","checkOptionalProps","defaults","prop","quicklyValidateIconSet","obj","icon","dataStorage","newStorage","provider","prefix","getStorage","providerStorage","addIconSet","storage","addIconToStorage","matchIconName","stringToIcon","validate","allowSimpleName","colonSeparated","validateIconName","dashSeparated","simpleNames","allowSimpleNames","allow","getIconData","iconName","addIcon","addCollection","added","defaultIconSizeCustomisations","defaultIconCustomisations","unitsSplit","unitsTest","calculateSize","size","ratio","precision","oldParts","newParts","code","isNumber","num","splitSVGDefs","content","tag","defs","index","start","end","endEnd","mergeDefsAndContent","wrapSVGContent","body","split","isUnsetKeyword","iconToSVG","customisations","fullIcon","fullCustomisations","box","props","transformations","hFlip","vFlip","rotation","tempValue","customisationsWidth","customisationsHeight","boxWidth","boxHeight","width","height","attributes","setAttr","viewBox","regex","randomPrefix","counter","replaceIDs","ids","match","suffix","id","newID","escapedID","setAPIModule","getAPIModule","createAPIConfig","source","resources","configStorage","fallBackAPISources","fallBackAPI","addAPIProvider","customConfig","config","getAPIConfig","detectFetch","fetchModule","calculateMaxLength","maxHostLength","url","shouldAbort","status","prepare","results","maxLength","type","length","getPath","send","host","params","path","iconsList","urlParams","uri","defaultError","response","fetchAPIModule","removeCallback","storages","items","row","updateCallbacks","hasPending","oldLength","idCounter","storeCallback","pendingSources","abort","sortIcons","a","b","lastIcon","localStorage","list","listToIcons","defaultConfig","sendQuery","payload","query","done","resourcesCount","startIndex","nextIndex","startTime","queriesSent","lastError","timer","queue","doneCallbacks","resetTimer","subscribe","overwrite","getQueryStatus","failQuery","clearQueue","moduleResponse","isError","queued","execNext","resource","status$1","initRedundancy","cfg","queries","cleanup","queryCallback","doneCallback","query$1","error","find","emptyCallback$1","redundancyCache","getRedundancyCache","redundancy","cachedReundancy","sendAPIQuery","target","api","cached","moduleKey","emptyCallback","loadedNewIcons","checkIconNamesForAPI","valid","invalid","parseLoaderResponse","checkMissing","pending","err","parsePossiblyAsyncResponse","loadNewIcons","icons$1","customIconLoader","iconSet","loadIcons","cleanedIcons","sortedIcons","callCallback","newIcons","sources","lastProvider","lastPrefix","providerNewIcons","pendingQueue","mergeCustomisations","valueType","separator","flipFromString","custom","flip","str","rotateFromString","defaultValue","units","value$1","iconToHTML","renderAttribsHTML","attr","encodeSVGforURL","svg","svgToData","svgToURL","policy","createPolicy","s","cleanUpInnerHTML","html","defaultExtendedIconCustomisations","svgDefaults","commonProps","monotoneProps","coloredProps","propsToAdd","propsToAddTo","inlineDefaults","fixSize","render","defaultProps","mode","style","customStyle","componentProps","classNames","renderAttribs","localCounter","createElement","useMask","_window","preload","providers","IconComponent","mounted","setMounted","useState","setAbort","getInitialState","state","setState","changeState","newState","updateState","_a","useEffect","Icon","forwardRef","ref","canvasIcons","registerCanvasIcons","iconsRegistered","ensureIconsRegistered","ALL_CAPABILITIES","MINIMAL_CAPABILITIES","COMPACT_CUSTOMIZER","PRO_STUDIO","EMBED_ONLY","KIT_PRESETS","resolveKit","kit","preset","validIds","KitContext","createContext","KitProvider","initialKit","children","currentKit","setCurrentKit","setKit","useCallback","useMemo","jsx","useKit","ctx","useContext","useCapabilities","logger","createLogger","DEFAULT_OPTIONS","generateElementPreview","element","options","opts","canvas","GroupElement","renderGroupPreview","renderElementUsingActualRenderer","bbox","elementWidth","elementHeight","renderFallbackPreview","availableWidth","availableHeight","scale","scaledWidth","scaledHeight","offsetX","offsetY","label","x","y","folderHeight","folderY","tabWidth","tabHeight","log","generateElementPreviewAsync","backgroundColor","buildLayerTree","elements","selectedId","multiSelection","previewCache","generatePreview","depth","parentId","el","elParentId","isGroup","isSelected","layerInfo","flattenLayerTree","layers","layer","useLayers","filterArtboardId","includeHidden","previewSize","previewBackgroundColor","artboardManager","handleSelectionChange","handleElementUpdate","setMultiSelection","executeAddElement","executeRemoveElement","executeReorderElement","useEditor","useRef","previewVersion","setPreviewVersion","activeArtboardId","effectiveArtboardId","elementId","preview","v","filteredElements","filtered","elementIds","flatLayers","selectedLayers","selectLayer","addToSelection","sid","selectMultipleLayers","clearSelection","toggleVisibility","updated","toggleLock","renameLayer","deleteLayer","duplicateLayer","json","Constructor","clonedWithNewId","artboardId","reorderLayers","fromIndex","toIndex","draggedElement","targetElement","position","moveLayerForward","l","moveLayerBackward","groupLayers","elementsToGroup","artboardIds","uniqueArtboardIds","bboxes","minX","minY","group","clone","ungroupLayer","extractedChildren","generateAllPreviews","generateForElements","clearPreviewCache","reorderById","draggedId","targetId","reorderWithinGroup","groupId","newGroup","draggedIndex","c","targetIndex","dragged","newTargetIndex","insertIndex","findElementById","findParentGroup","addToGroup","childClone","removeFromGroup","parentGroup","promoted","createEmptyGroup","isAnyCroppingActive","selectedElement","activeChildElement","ImageElement","useKeyboardShortcuts","enabled","handleCopyElements","handlePasteElements","undoActiveArtboard","redoActiveArtboard","setHideHandles","handleAddElement","isToolbarMenuOpen","handleKeyDown","e","increment","dx","dy","updatedElement","handleKeyUp","DEFAULT_DURATION_MS","easeOutCubic","useAnimatedFocusRect","durationMs","rect","setRect","rectRef","rafRef","targetKey","from","tick","now","t","eased","getWordBoundaryAt","text","clampedIndex","wordChars","rightStart","rightEnd","leftEnd","leftStart","getCursorLineInfo","cursorIndex","fontSize","fontFamily","bold","italic","textAlign","richText","maxWidth","wrappingWidth","horizontalPadding","HORIZONTAL_PADDING","lines","wrapText","charStartIndex","lineIndex","i","lineEndIndex","line","posInLine","visibleLineText","leadingSpacesCount","isParagraphStart","isParagraphEnd","leadingSpacesMatch","trailingSpacesMatch","trailingSpacesCount","lineWidth","measureTextWidth","lineStartX","xPosition","measureEnd","char","globalIndex","charFontSize","charFontFamily","charBold","charItalic","moveCursorVertically","direction","lineSpacing","info","targetLineIndex","targetY","getCursorIndexFromPoint","point","currentY","relativeX","currentX","charWidth","useMarqueeSelection","isMarqueeSelecting","setIsMarqueeSelecting","marqueeStart","setMarqueeStart","marqueeEnd","setMarqueeEnd","marqueePreviewSelection","setMarqueePreviewSelection","maxX","maxY","previewSelectedIds","elementLeft","elementRight","elementTop","elementBottom","onMultiSelectionChange","selectedIds","PenToolManager","pathElement","p","newPoint","handleX","handleY","firstPoint","threshold","clickX","clickY","centerX","centerY","getThemeShapeFillColor","points","previewPoint","_currentPoint","getThemePenPathColor","prevPoint","currentPoint","cp1x","cp1y","_b","cp2x","_c","cp2y","_d","isClosed","lastPoint","_e","_f","getThemeAccentColor","getThemePenAnchorFillColor","handleInX","handleInY","getThemePenHandleColor","handleOutX","handleOutY","themeColor","hex","r","g","usePenTool","penTool","penCursor","setPenCursor","penIconContent","renderToStaticMarkup","cursorSvg","dataUrl","PathElement","useActiveChild","transformHandles","onActiveChildChange","setActiveChildElement","editingChildIdRef","updateActiveChild","updatedChild","useHoverState","onHoverChange","hoverState","setHoverStateInternal","setHoverState","prevState","isNewHover","hoveredElementId","hoveredElement","CoordinateTransform","flipH","flipV","worldX","worldY","rotRad","RotationUtils","cos","sin","localX","localY","rotatedX","rotatedY","inverse","CropModeController","_element","context","startX","startY","td","startWidth","startHeight","startCropX","startCropY","startCropWidth","startCropHeight","localDelta","Transform","localDx","localDy","normalizedDx","normalizedDy","newCropX","newCropY","maxCropX","maxCropY","anchor","resizeContext","stateMachineContext","cursor","startRotation","startTransform","flipHMult","flipVMult","dxCurrent","dyCurrent","localCurrentX","localCurrentY","dxStart","dyStart","localStartX","localStartY","positionOffset","cursorAnchor","minSize","MIN_CROP_SIZE","newCropWidth","newCropHeight","cropX","cropY","cropWidth","cropHeight","oldCropX","oldCropY","oldCropWidth","oldCropHeight","oldCropCenterX","oldCropCenterY","newCropCenterX","newCropCenterY","localOffsetX","localOffsetY","worldOffset","worldOffsetX","worldOffsetY","transform","useCropMode","cropController","useMultiSelection","multiSelectionGroupBoundsRef","multiSelectionOBBRef","hasTextCapabilities","TextElement","useTextEditing","isEditing","setIsEditing","editText","setEditText","editPosition","setEditPosition","cursorPosition","setCursorPosition","selectionStart","setSelectionStart","selectionEnd","setSelectionEnd","cursorOpacityRef","cursorStartTimeRef","selectionAnchorRef","didDragSelectRef","editRichText","setEditRichText","currentFormattingStyle","setCurrentFormattingStyle","editRichTextRef","applyFormattingToSelection","textLength","prevStyle","getSelectionStyle","toggleFormattingProperty","property","currentStyle","newValue","useImperativeHandle","useCanvasLayout","zoom","viewPadding","artboardBorderRadius","fixedMargin","fixedMarginX","fixedMarginY","artboards","focusedPieceRect","isTouchDevice","setIsTouchDevice","hasTouch","lastTouchTargetRef","handleTouchStart","canvasSize","setCanvasSize","activeArtboard","handleResize","effectiveViewPadding","effectiveMarginX","effectiveMarginY","worldMarginX","worldMarginY","paddedCanvasWidth","paddedCanvasHeight","baseOffsetX","baseOffsetY","paddingOffsetX","paddingOffsetY","zoomInvariantBorderRadius","useInteractionState","snapGuides","setSnapGuides","spacingIndicators","setSpacingIndicators","isAltKeyPressed","setIsAltKeyPressed","isRotating","setIsRotating","_rotationStartAngle","setRotationStartAngle","currentRotation","setCurrentRotation","setRotatingElementVersion","useRenderState","forceUpdate","handlesVersion","setHandlesVersion","renderVersion","setRenderVersion","cursorAnimationFrame","setCursorAnimationFrame","forceRender","subscribeToImageLoads","DEFAULTS","cachedSnapshotEl","snapshotCanvas","w","h","sctx","resolveCssColor","input","trimmed","inner","rest","varName","fallback","isDocumentDark","_target","isInkDark","css","luma","inkLuminance","isInkLight","relLuma","m","lin","cs","pathToCanvas","rx","ry","p2d","fillSvgPath","fillRule","strokeSvgPath","addSvgPathSubpath","addToPath2D","resolveBoundary","guide","naturalW","naturalH","resolveSafeArea","piece","inset","renderPieceGuides","pieces","pieceGuides","artboardOrigin","show","canvasEl","domTarget","isDark","_g","bleedFillCss","dieCutTintCss","boundaryStrokeCss","backdrop","z","zones","labels","isOrtho","boundary","safeArea","isFocusedPiece","outerRadius","outerShape","wantInner","bleedBlend","effectiveBleedOpacity","drawBleedRingSolid","drawBleedRing","zone","setZoomInvariantStroke","drawDieCutKnockout","drawDieCut","drawLabels","fill","opacity","blurScale","blendMode","blurWorldPx","blurCssPx","dpr","pad","paddedW","paddedH","physW","physH","off","acquireOffscreen","octx","outerPath","boundaryPath","prevOp","compound","cachedBleedOffscreen","tint","tintAlpha","stroke","strokeWidthPx","baseAlpha","INTERACTION_PARTIAL_KNOCKOUT_RETAIN","interacting","fontWorldPx","haloWidth","haloWorldPx","anchorToAlign","anchorToBaseline","renderMultiSelectionIndicators","displaySelection","activeArtboardElementIds","stateMachine","multiSelectionOBB","filteredDisplaySelection","multiDragContext","isCurrentlyDraggingMulti","cachedElement","rotationAnchor","getLineWidth","selectedElements","corners","rotationRad","corner","groupWidth","groupHeight","obb","dragContext","groupRotation","renderWidth","renderHeight","renderCenterX","renderCenterY","renderMinX","renderMinY","renderMaxX","renderMaxY","rotationHandleX","rotationHandleY","ROTATION_HANDLE_DISTANCE","groupBounds","getDashPattern","renderMultiSelectionHandles","handleRadius","getHandleRadius","isResizingMultiSelection","activeHandle","cornerX","cornerY","renderRotationHandle","renderDimensionLabel","infoText","tooltipX","tooltipY","tooltipWidth","tooltipHeight","renderMarqueeSelection","getThemeAccentColorWithAlpha","renderGroupSiblingHover","hoveredChild","getThemeHoverBorderColor","ROLLING_WINDOW_SIZE","PerformanceMonitor","ms","count","rate","values","sum","buildSpreadClipShape","guides","focusedPieceId","focused","rot","swap","useCanvasRenderLoop","paramsRef","dirtyRef","editCacheRef","canvasBgColorRef","color","frameId","renderLoop","t0","renderFrame","canvasRef","elementsByArtboard","artboardRenderer","showRotationHandle","selectionHandlePositionsRef","multiClickSelectionRef","lastMultiClickTimeRef","MULTI_CLICK_DEBOUNCE","hideHandles","easeInOutCubic","progress","displayWidth","displayHeight","MAX_BUFFER_DIM","rawBufferWidth","rawBufferHeight","longest","scaleFactor","effectiveDpr","canvasBgColor","effectiveClipShape","effectiveBorderRadius","firstPieceId","guideAny","explicit","inferred","candidate","elementToRender","elementsForRender","cachedElements","childId","cache","clonedElement","sourceGroup","clonedGroup","clonedChild","cloned","shouldHideHandles","CanvasRenderer","savedSelection","withinDebounce","useRefSelection","selectedOverlapsDieCut","interactingElement","outer","zx","zy","zw","zh","guidesNeedRoundedClip","preservedRect","hasSelection","HUGE","updatedOBB","PinchHandler","startData","rotationDelta","startCentroid","currentCentroid","snapRotation","newWidth","newHeight","sdx","sdy","newRotation","ClickProtectionManager","wasProtected","clickProtection","hitTestMultiSelection","bounds","hitRadius","CORNER_HANDLE_HIT_RADIUS","rotatedCorners","rotationHitRadius","startMultiSelectionResize","startMultiSelectionRotate","startAngle","calculateAngle","startMultiSelectionDrag","dragOriginalElementsRef","findCroppingImage","croppingChild","croppingChildIndex","croppingImage","hitTestCropMode","cropContext","cropHandle","imageHandle","handleCropHandleClick","cropBox","handleImageHandleClick","visualBbox","handleImageDragClick","exitCropMode","onElementUpdate","updatedGroup","handleSiblingClick","exitActiveChildMode","handleElementSelection","shiftKey","activeArtboardElements","onSelectionChange","clickedId","newSelection","HIT_FILTER_MIN_VISIBLE_AREA","isHitAllowedInFocus","pointX","pointY","ix","iy","ix2","iy2","useCanvasInteraction","inputRef","repositioningCursorRef","lastClickTimeRef","clickCountRef","lastProcessedClickTimeRef","skipDragSelectionRef","draggingSelectionHandleRef","lastMousePosRef","rotatingElementRef","editModeEnteredAtRef","MULTI_CLICK_THRESHOLD","CLICK_DEDUP_THRESHOLD","setElements","snapSystem","spacingSystem","executeElementUpdate","onRotationStateChange","onCropModeEnter","getActiveArtboardElements","startMarqueeSelection","updateMarqueeSelection","finishMarqueeSelection","enterTextEditMode","getWrappedTextForEditing","canvasBoundsRef","activePointersRef","pinchPointerIdsRef","pinchOriginalElementRef","selectedIdAtGestureStartRef","pendingDeselectIdRef","pendingDeselectStartPosRef","canvasZoomPinchRef","singleFingerPanRef","userZoom","setUserZoom","panOffset","setPanOffset","useViewportContext","userZoomRef","panOffsetRef","updateBounds","handlePointerDown","isModalMode","wasSelectedBefore","originals","prev","original","distance","containerRect","p1","p2","handlePositions","center","localRotatedX","localRotatedY","relativeY","clickIndex","currentText","wordBounds","relX","relY","hitResult","workingElement","cropHitResult","getBoundingBoxCenter","handle","clickedChild","hits","pendingTap","currentIdx","isProtected","shouldPreserve","shouldPreserveSelection","handlePointerMove","newUserZoom","startCentroidContainerX","startCentroidContainerY","currentCentroidContainerX","currentCentroidContainerY","rawNewPanX","rawNewPanY","canvas2","canvasRect","containerEl","containerWidth","containerHeight","minVisible","growthRatio","predictedCanvasWidth","predictedCanvasHeight","currentPanX","currentPanY","currentLeftInContainer","currentTopInContainer","predictedLeft","predictedTop","clampedLeft","clampedTop","gesture","yOffset","dragIndex","updatedElements","elementData","prevElements","ue","draggedImage","cropGroupContext","activeChildContext","groupContext","childIndex","flattenedElements","newPos","elementForSpacingSnap","newActiveChild","groupDragOriginal","dragStartData","movingElement","indicators","multiResizeContext","startBounds","fixedX","fixedY","draggedStartX","draggedStartY","draggedCurrentX","draggedCurrentY","startDistX","startDistY","newDistX","newDistY","scaleX","scaleY","newMinX","newMinY","newMaxX","newMaxY","newX","newY","imageData","startImageData","newChildWidth","newChildHeight","newElementWidth","newElementHeight","newCenterX","newCenterY","resizedImage","cropAnchor","activeChildCtx","isResizingChildInGroup","elementsForSnap","globalResizePipeline","multiRotationContext","deltaRotation","rotatedPos","workingEl","centerXBefore","centerYBefore","newBbox","newCenter","cdx","cdy","j","hoverCheckElement","getCursorForWorldPosition","resizeHandle","handlePointerUp","finalElement","startElement","wasClosing","originalElement","CustomTransform","startOBB","prevOBB","pendingChild","shouldSelectChild","originalGroup","currentElement","ShapeElement","startPos","moved","screenMoved","handleDoubleClick","contentHeight","offset","initialRichText","styleAtEnd","handleDocumentMouseMove","syntheticEvent","handleDocumentPointerUp","targetNode","insideCanvas","hasActiveGesture","elementText","useTextEditingHandlers","editPositionUpdateTimerRef","EDIT_MODE_BLUR_DEBOUNCE","onTextSelectionChange","handleTextEditComplete","latestRichText","editingChildId","handleTextEditBlur","isToolbarClick","classList","dataSlot","savedStart","savedEnd","handleTextKeyDown","textarea","rafId","lastUpdate","FRAME_INTERVAL","animate","timestamp","prevIsEditingRef","endPos","lastFormattingStyleRef","stylesAreEqual","newStyle","selectedEl","editingElement","tempElement","updateCursor","CanvasEditor","externalCanvasRef","canvasCutouts","pieceFocus","focusedPieceRectOverride","resolvedTheme","useTheme","internalCanvasRef","derivedFocusedPieceRect","React","selectionClipRect","TransformHandles","ArtboardRenderer","AlignmentSnapSystem","SpacingSystem","machine","InteractionStateMachine","elementsRef","selectedElementRef","ariaAnnouncement","setAriaAnnouncement","artboardManagerRef","elementsByArtboardRef","snapHook","resizeResult","allElements","_currentFormattingStyle","timeSinceMultiClick","handleBlur","lastHandlesUpdateRef","elementToUpdate","elementKey","canvasClipPathValue","cssW","cssH","cutouts","jsxs","Fragment","textEl","textareaElement","newRichText","RichText","newCursorPos","oldText","updatedRichText","prefixLen","suffixLen","deleteStart","deleteEnd","insertText","insertStyle","canvasX","canvasY","createPortal","USER_MIN_ZOOM","USER_MAX_ZOOM","MIN_ZOOM","MAX_ZOOM","ZOOM_SENSITIVITY","Canvas","className","propZoom","fitPadding","interactive","maxHeight","enableShortcuts","canvasEditorRef","contextZoom","setZoom","isPanning","setIsPanning","resetUserView","handleActiveChildChange","setHoveredElementId","useSelectionContext","setTextSelectionVersion","setExpandedPanelType","useToolStateContext","handleCropModeEnter","containerRef","calculatedZoom","setCalculatedZoom","setContainerWidth","isHeightConstrained","setIsHeightConstrained","lastSyncedZoomRef","hasFitOnceRef","isSpaceHeld","setIsSpaceHeld","panStartRef","handleWheel","container","cursorX","cursorY","delta","oldZoom","newZoom","zoomRatio","newPanX","newPanY","oldUserZoom","handlePanStart","isMiddleMouse","isSpacePan","handlePanMove","handlePanEnd","handleGlobalPointerUp","targetFocusedRect","focusedPiece","calculateFitZoom","fitWidth","fitHeight","fitZoom","constrainedByHeight","widthFitZoom","heightFitZoom","heightConstrainedZoom","cancelled","resizeObserver","effectiveZoom","lastFocusedPieceIdRef","subjectWidth","subjectHeight","canvasWidth","canvasHeight","fitCanvasWidth","currentContainerWidth","containerSizeRef","observer","entries","entry","containerW","containerH","minVisibleX","minVisibleY","minPanX","maxPanX","minPanY","maxPanY","clampedX","clampedY","containerStyle","innerWrapperStyle","ZoomPill","cx","cy","ZOOM_PRESETS","onSetZoom","pct","menuOpen","setMenuOpen","presetPct","active","open","useArtboards","refreshArtboards","setSelectedId","artboardsInfo","artboard","elementCount","activeArtboardInfo","createArtboard","artboardConfig","deleteArtboard","duplicateArtboard","ab","renameArtboard","newName","updateArtboard","updates","selectArtboard","reorderArtboards","useExport","isExporting","setIsExporting","isContinuousExporting","setIsContinuousExporting","setProgress","latestExports","setLatestExports","workerStats","setWorkerStats","continuousExportGeneration","continuousExportOptions","supportsWorkerExport","ExportManager","exportArtboard","progressEntry","currentElements","artboardElements","exportTime","exportAllArtboards","exportPromises","exportResults","exportArtboardAsBlob","exportAllArtboardsAsBlobs","blob","stopContinuousExport","startContinuousExport","myGeneration","interval","format","onChange","targetArtboards","isCurrentGeneration","performExport","DEFAULT_AUTO_EXPORT_CONFIG","AutoExportManager","debounce","exportingRevision","intervals","newConfig","oldConfig","CanvasSkeleton","LayersPanelSkeleton","itemCount","_","ToolbarSkeleton","Spinner","validateKit","errors","warnings","panelSlots","knownPanelSections","section","panelType","mergeArrays","base","override","seen","shallowMerge","SLOT_KEYS","extendKit","overrides","baseSlots","overrideSlots","mergedSlots","baseArr","overrideArr","baseCaps","overrideCaps","mergedCapabilities","mergedBehavior","mergedStyle","baseContent","mergedContent","MINIMAL_KIT_DEFAULTS","createKit","partial"],"mappings":"4RASA,SAASA,GAAaC,EAAMC,EAAO,CAClC,MAAMC,EAAQF,EAAK,MACbG,EAAUH,EAAK,SAAW,OAAO,OAAO,IAAI,EAC5CI,EAAW,OAAO,OAAO,IAAI,EACnC,SAASC,EAAQC,EAAM,CACtB,GAAIJ,EAAMI,CAAI,EAAG,OAAOF,EAASE,CAAI,EAAI,CAAA,EACzC,GAAI,EAAEA,KAAQF,GAAW,CACxBA,EAASE,CAAI,EAAI,KACjB,MAAMC,EAASJ,EAAQG,CAAI,GAAKH,EAAQG,CAAI,EAAE,OACxCE,EAAQD,GAAUF,EAAQE,CAAM,EAClCC,IAAOJ,EAASE,CAAI,EAAI,CAACC,CAAM,EAAE,OAAOC,CAAK,EAClD,CACA,OAAOJ,EAASE,CAAI,CACrB,CACA,OAAC,OAAO,KAAKJ,CAAK,EAAE,OAAO,OAAO,KAAKC,CAAO,CAAC,EAAG,QAAQE,CAAO,EAC1DD,CACR,CAKA,MAAMK,GAAwB,OAAO,OAAO,CAC3C,KAAM,EACN,IAAK,EACL,MAAO,GACP,OAAQ,EACT,CAAC,EAIKC,GAA6B,OAAO,OAAO,CAChD,OAAQ,EACR,MAAO,GACP,MAAO,EACR,CAAC,EAIKC,GAAmB,OAAO,OAAO,CACtC,GAAGF,GACH,GAAGC,EACJ,CAAC,EAIKE,GAA2B,OAAO,OAAO,CAC9C,GAAGD,GACH,KAAM,GACN,OAAQ,EACT,CAAC,EAKD,SAASE,GAAyBC,EAAMC,EAAM,CAC7C,MAAMC,EAAS,CAAA,EACX,CAACF,EAAK,OAAU,CAACC,EAAK,QAAOC,EAAO,MAAQ,IAC5C,CAACF,EAAK,OAAU,CAACC,EAAK,QAAOC,EAAO,MAAQ,IAChD,MAAMC,IAAWH,EAAK,QAAU,IAAMC,EAAK,QAAU,IAAM,EAC3D,OAAIE,IAAQD,EAAO,OAASC,GACrBD,CACR,CAOA,SAASE,GAAcX,EAAQY,EAAO,CACrC,MAAMH,EAASH,GAAyBN,EAAQY,CAAK,EACrD,UAAWC,KAAOR,GAA8BQ,KAAOV,GAClDU,KAAOb,GAAU,EAAEa,KAAOJ,KAASA,EAAOI,CAAG,EAAIV,GAA2BU,CAAG,GACzEA,KAAOD,EAAOH,EAAOI,CAAG,EAAID,EAAMC,CAAG,EACvCA,KAAOb,IAAQS,EAAOI,CAAG,EAAIb,EAAOa,CAAG,GAChD,OAAOJ,CACR,CAKA,SAASK,GAAoBrB,EAAMM,EAAMgB,EAAM,CAC9C,MAAMpB,EAAQF,EAAK,MACbG,EAAUH,EAAK,SAAW,OAAO,OAAO,IAAI,EAClD,IAAIuB,EAAe,CAAA,EACnB,SAASC,EAAMC,EAAQ,CACtBF,EAAeL,GAAchB,EAAMuB,CAAM,GAAKtB,EAAQsB,CAAM,EAAGF,CAAY,CAC5E,CACA,OAAAC,EAAMlB,CAAI,EACVgB,EAAK,QAAQE,CAAK,EACXN,GAAclB,EAAMuB,CAAY,CACxC,CAOA,SAASG,GAAa1B,EAAM2B,EAAU,CACrC,MAAM1B,EAAQ,CAAA,EACd,GAAI,OAAOD,GAAS,UAAY,OAAOA,EAAK,OAAU,SAAU,OAAOC,EACnED,EAAK,qBAAqB,OAAOA,EAAK,UAAU,QAASM,GAAS,CACrEqB,EAASrB,EAAM,IAAI,EACnBL,EAAM,KAAKK,CAAI,CAChB,CAAC,EACD,MAAMgB,EAAOvB,GAAaC,CAAI,EAC9B,UAAWM,KAAQgB,EAAM,CACxB,MAAMM,EAAON,EAAKhB,CAAI,EAClBsB,IACHD,EAASrB,EAAMe,GAAoBrB,EAAMM,EAAMsB,CAAI,CAAC,EACpD3B,EAAM,KAAKK,CAAI,EAEjB,CACA,OAAOL,CACR,CAKA,MAAM4B,GAA2B,CAChC,SAAU,GACV,QAAS,CAAA,EACT,UAAW,CAAA,EACX,GAAGpB,EACJ,EAIA,SAASqB,GAAmBF,EAAMG,EAAU,CAC3C,UAAWC,KAAQD,EAAU,GAAIC,KAAQJ,GAAQ,OAAOA,EAAKI,CAAI,GAAM,OAAOD,EAASC,CAAI,EAAG,MAAO,GACrG,MAAO,EACR,CAOA,SAASC,GAAuBC,EAAK,CACpC,GAAI,OAAOA,GAAQ,UAAYA,IAAQ,KAAM,OAAO,KACpD,MAAMlC,EAAOkC,EAEb,GADI,OAAOlC,EAAK,QAAW,UAAY,CAACkC,EAAI,OAAS,OAAOA,EAAI,OAAU,UACtE,CAACJ,GAAmBI,EAAKL,EAAwB,EAAG,OAAO,KAC/D,MAAM3B,EAAQF,EAAK,MACnB,UAAWM,KAAQJ,EAAO,CACzB,MAAMiC,EAAOjC,EAAMI,CAAI,EACvB,GAAI,CAACA,GAAQ,OAAO6B,EAAK,MAAS,UAAY,CAACL,GAAmBK,EAAMvB,EAAwB,EAAG,OAAO,IAC3G,CACA,MAAMT,EAAUH,EAAK,SAAW,OAAO,OAAO,IAAI,EAClD,UAAWM,KAAQH,EAAS,CAC3B,MAAMgC,EAAOhC,EAAQG,CAAI,EACnBC,EAAS4B,EAAK,OACpB,GAAI,CAAC7B,GAAQ,OAAOC,GAAW,UAAY,CAACL,EAAMK,CAAM,GAAK,CAACJ,EAAQI,CAAM,GAAK,CAACuB,GAAmBK,EAAMvB,EAAwB,EAAG,OAAO,IAC9I,CACA,OAAOZ,CACR,CAKA,MAAMoC,GAAc,OAAO,OAAO,IAAI,EAItC,SAASC,GAAWC,EAAUC,EAAQ,CACrC,MAAO,CACN,SAAAD,EACA,OAAAC,EACA,MAAO,OAAO,OAAO,IAAI,EACzB,QAAyB,IAAI,GAC/B,CACA,CAIA,SAASC,GAAWF,EAAUC,EAAQ,CACrC,MAAME,EAAkBL,GAAYE,CAAQ,IAAMF,GAAYE,CAAQ,EAAI,OAAO,OAAO,IAAI,GAC5F,OAAOG,EAAgBF,CAAM,IAAME,EAAgBF,CAAM,EAAIF,GAAWC,EAAUC,CAAM,EACzF,CAMA,SAASG,GAAWC,EAAS3C,EAAM,CAClC,OAAKiC,GAAuBjC,CAAI,EACzB0B,GAAa1B,EAAM,CAACM,EAAM6B,IAAS,CACrCA,EAAMQ,EAAQ,MAAMrC,CAAI,EAAI6B,EAC3BQ,EAAQ,QAAQ,IAAIrC,CAAI,CAC9B,CAAC,EAJyC,CAAA,CAK3C,CAIA,SAASsC,GAAiBD,EAASrC,EAAM6B,EAAM,CAC9C,GAAI,CACH,GAAI,OAAOA,EAAK,MAAS,SACxB,OAAAQ,EAAQ,MAAMrC,CAAI,EAAI,CAAE,GAAG6B,CAAI,EACxB,EAET,MAAc,CAAC,CACf,MAAO,EACR,CAuBA,MAAMU,GAAgB,2BAIhBC,GAAe,CAACtC,EAAOuC,EAAUC,EAAiBV,EAAW,KAAO,CACzE,MAAMW,EAAiBzC,EAAM,MAAM,GAAG,EACtC,GAAIA,EAAM,MAAM,EAAG,CAAC,IAAM,IAAK,CAC9B,GAAIyC,EAAe,OAAS,GAAKA,EAAe,OAAS,EAAG,OAAO,KACnEX,EAAWW,EAAe,QAAQ,MAAM,CAAC,CAC1C,CACA,GAAIA,EAAe,OAAS,GAAK,CAACA,EAAe,OAAQ,OAAO,KAChE,GAAIA,EAAe,OAAS,EAAG,CAC9B,MAAMxB,EAASwB,EAAe,IAAG,EAC3BV,EAASU,EAAe,IAAG,EAC3BjC,EAAS,CACd,SAAUiC,EAAe,OAAS,EAAIA,EAAe,CAAC,EAAIX,EAC1D,OAAAC,EACA,KAAMd,CACT,EACE,OAAOsB,GAAY,CAACG,GAAiBlC,CAAM,EAAI,KAAOA,CACvD,CACA,MAAMV,EAAO2C,EAAe,CAAC,EACvBE,EAAgB7C,EAAK,MAAM,GAAG,EACpC,GAAI6C,EAAc,OAAS,EAAG,CAC7B,MAAMnC,EAAS,CACd,SAAAsB,EACA,OAAQa,EAAc,MAAK,EAC3B,KAAMA,EAAc,KAAK,GAAG,CAC/B,EACE,OAAOJ,GAAY,CAACG,GAAiBlC,CAAM,EAAI,KAAOA,CACvD,CACA,GAAIgC,GAAmBV,IAAa,GAAI,CACvC,MAAMtB,EAAS,CACd,SAAAsB,EACA,OAAQ,GACR,KAAAhC,CACH,EACE,OAAOyC,GAAY,CAACG,GAAiBlC,EAAQgC,CAAe,EAAI,KAAOhC,CACxE,CACA,OAAO,IACR,EAMMkC,GAAmB,CAACf,EAAMa,IAC1Bb,EACE,CAAC,GAAGa,GAAmBb,EAAK,SAAW,IAAQA,EAAK,SAAaA,EAAK,MAD3D,GAOnB,IAAIiB,GAAc,GAClB,SAASC,GAAiBC,EAAO,CAChC,OAAI,OAAOA,GAAU,YAAWF,GAAcE,GACvCF,EACR,CASA,SAASG,GAAYjD,EAAM,CAC1B,MAAM6B,EAAO,OAAO7B,GAAS,SAAWwC,GAAaxC,EAAM,GAAM8C,EAAW,EAAI9C,EAChF,GAAI6B,EAAM,CACT,MAAMQ,EAAUH,GAAWL,EAAK,SAAUA,EAAK,MAAM,EAC/CqB,EAAWrB,EAAK,KACtB,OAAOQ,EAAQ,MAAMa,CAAQ,IAAMb,EAAQ,QAAQ,IAAIa,CAAQ,EAAI,KAAO,OAC3E,CACD,CAIA,SAASC,GAAQnD,EAAMN,EAAM,CAC5B,MAAMmC,EAAOW,GAAaxC,EAAM,GAAM8C,EAAW,EACjD,GAAI,CAACjB,EAAM,MAAO,GAClB,MAAMQ,EAAUH,GAAWL,EAAK,SAAUA,EAAK,MAAM,EACrD,OAAInC,EAAa4C,GAAiBD,EAASR,EAAK,KAAMnC,CAAI,GAEzD2C,EAAQ,QAAQ,IAAIR,EAAK,IAAI,EACtB,GAET,CAIA,SAASuB,GAAc1D,EAAMsC,EAAU,CACtC,GAAI,OAAOtC,GAAS,SAAU,MAAO,GAErC,GADI,OAAOsC,GAAa,WAAUA,EAAWtC,EAAK,UAAY,IAC1DoD,IAAe,CAACd,GAAY,CAACtC,EAAK,OAAQ,CAC7C,IAAI2D,EAAQ,GACZ,OAAI1B,GAAuBjC,CAAI,IAC9BA,EAAK,OAAS,GACd0B,GAAa1B,EAAM,CAACM,EAAM6B,IAAS,CAC9BsB,GAAQnD,EAAM6B,CAAI,IAAGwB,EAAQ,GAClC,CAAC,GAEKA,CACR,CACA,MAAMpB,EAASvC,EAAK,OACpB,GAAI,CAACkD,GAAiB,CACrB,OAAAX,EACA,KAAM,GACR,CAAE,EAAG,MAAO,GACX,MAAMI,EAAUH,GAAWF,EAAUC,CAAM,EAC3C,MAAO,CAAC,CAACG,GAAWC,EAAS3C,CAAI,CAClC,CAqBA,MAAM4D,GAAgC,OAAO,OAAO,CACnD,MAAO,KACP,OAAQ,IACT,CAAC,EACKC,GAA4B,OAAO,OAAO,CAC/C,GAAGD,GACH,GAAGlD,EACJ,CAAC,EAKKoD,GAAa,4BACbC,GAAY,4BAClB,SAASC,GAAcC,EAAMC,EAAOC,EAAW,CAC9C,GAAID,IAAU,EAAG,OAAOD,EAExB,GADAE,EAAYA,GAAa,IACrB,OAAOF,GAAS,SAAU,OAAO,KAAK,KAAKA,EAAOC,EAAQC,CAAS,EAAIA,EAC3E,GAAI,OAAOF,GAAS,SAAU,OAAOA,EACrC,MAAMG,EAAWH,EAAK,MAAMH,EAAU,EACtC,GAAIM,IAAa,MAAQ,CAACA,EAAS,OAAQ,OAAOH,EAClD,MAAMI,EAAW,CAAA,EACjB,IAAIC,EAAOF,EAAS,MAAK,EACrBG,EAAWR,GAAU,KAAKO,CAAI,EAClC,OAAa,CACZ,GAAIC,EAAU,CACb,MAAMC,EAAM,WAAWF,CAAI,EACvB,MAAME,CAAG,EAAGH,EAAS,KAAKC,CAAI,EAC7BD,EAAS,KAAK,KAAK,KAAKG,EAAMN,EAAQC,CAAS,EAAIA,CAAS,CAClE,MAAOE,EAAS,KAAKC,CAAI,EAEzB,GADAA,EAAOF,EAAS,MAAK,EACjBE,IAAS,OAAQ,OAAOD,EAAS,KAAK,EAAE,EAC5CE,EAAW,CAACA,CACb,CACD,CAEA,SAASE,GAAaC,EAASC,EAAM,OAAQ,CAC5C,IAAIC,EAAO,GACX,MAAMC,EAAQH,EAAQ,QAAQ,IAAMC,CAAG,EACvC,KAAOE,GAAS,GAAG,CAClB,MAAMC,EAAQJ,EAAQ,QAAQ,IAAKG,CAAK,EAClCE,EAAML,EAAQ,QAAQ,KAAOC,CAAG,EACtC,GAAIG,IAAU,IAAMC,IAAQ,GAAI,MAChC,MAAMC,EAASN,EAAQ,QAAQ,IAAKK,CAAG,EACvC,GAAIC,IAAW,GAAI,MACnBJ,GAAQF,EAAQ,MAAMI,EAAQ,EAAGC,CAAG,EAAE,KAAI,EAC1CL,EAAUA,EAAQ,MAAM,EAAGG,CAAK,EAAE,KAAI,EAAKH,EAAQ,MAAMM,EAAS,CAAC,CACpE,CACA,MAAO,CACN,KAAAJ,EACA,QAAAF,CACF,CACA,CAIA,SAASO,GAAoBL,EAAMF,EAAS,CAC3C,OAAOE,EAAO,SAAWA,EAAO,UAAYF,EAAUA,CACvD,CAIA,SAASQ,GAAeC,EAAML,EAAOC,EAAK,CACzC,MAAMK,EAAQX,GAAaU,CAAI,EAC/B,OAAOF,GAAoBG,EAAM,KAAMN,EAAQM,EAAM,QAAUL,CAAG,CACnE,CAKA,MAAMM,GAAkB7E,GAAUA,IAAU,SAAWA,IAAU,aAAeA,IAAU,OAW1F,SAAS8E,GAAUnD,EAAMoD,EAAgB,CACxC,MAAMC,EAAW,CAChB,GAAG7E,GACH,GAAGwB,CACL,EACOsD,EAAqB,CAC1B,GAAG5B,GACH,GAAG0B,CACL,EACOG,EAAM,CACX,KAAMF,EAAS,KACf,IAAKA,EAAS,IACd,MAAOA,EAAS,MAChB,OAAQA,EAAS,MACnB,EACC,IAAIL,EAAOK,EAAS,KACpB,CAACA,EAAUC,CAAkB,EAAE,QAASE,GAAU,CACjD,MAAMC,EAAkB,CAAA,EAClBC,EAAQF,EAAM,MACdG,EAAQH,EAAM,MACpB,IAAII,EAAWJ,EAAM,OACjBE,EAAWC,EAAOC,GAAY,GAEjCH,EAAgB,KAAK,cAAgBF,EAAI,MAAQA,EAAI,MAAM,SAAQ,EAAK,KAAO,EAAIA,EAAI,KAAK,SAAQ,EAAK,GAAG,EAC5GE,EAAgB,KAAK,aAAa,EAClCF,EAAI,IAAMA,EAAI,KAAO,GAEbI,IACRF,EAAgB,KAAK,cAAgB,EAAIF,EAAI,MAAM,SAAQ,EAAK,KAAOA,EAAI,OAASA,EAAI,KAAK,SAAQ,EAAK,GAAG,EAC7GE,EAAgB,KAAK,aAAa,EAClCF,EAAI,IAAMA,EAAI,KAAO,GAEtB,IAAIM,EAGJ,OAFID,EAAW,IAAGA,GAAY,KAAK,MAAMA,EAAW,CAAC,EAAI,GACzDA,EAAWA,EAAW,EACdA,EAAQ,CACf,IAAK,GACJC,EAAYN,EAAI,OAAS,EAAIA,EAAI,IACjCE,EAAgB,QAAQ,aAAeI,EAAU,WAAa,IAAMA,EAAU,SAAQ,EAAK,GAAG,EAC9F,MACD,IAAK,GACJJ,EAAgB,QAAQ,eAAiBF,EAAI,MAAQ,EAAIA,EAAI,MAAM,SAAQ,EAAK,KAAOA,EAAI,OAAS,EAAIA,EAAI,KAAK,SAAQ,EAAK,GAAG,EACjI,MACD,IAAK,GACJM,EAAYN,EAAI,MAAQ,EAAIA,EAAI,KAChCE,EAAgB,QAAQ,cAAgBI,EAAU,WAAa,IAAMA,EAAU,SAAQ,EAAK,GAAG,EAC/F,KACJ,CACMD,EAAW,IAAM,IAChBL,EAAI,OAASA,EAAI,MACpBM,EAAYN,EAAI,KAChBA,EAAI,KAAOA,EAAI,IACfA,EAAI,IAAMM,GAEPN,EAAI,QAAUA,EAAI,SACrBM,EAAYN,EAAI,MAChBA,EAAI,MAAQA,EAAI,OAChBA,EAAI,OAASM,IAGXJ,EAAgB,SAAQT,EAAOD,GAAeC,EAAM,iBAAoBS,EAAgB,KAAK,GAAG,EAAI,KAAO,MAAM,EACtH,CAAC,EACD,MAAMK,EAAsBR,EAAmB,MACzCS,EAAuBT,EAAmB,OAC1CU,EAAWT,EAAI,MACfU,EAAYV,EAAI,OACtB,IAAIW,EACAC,EACAL,IAAwB,MAC3BK,EAASJ,IAAyB,KAAO,MAAQA,IAAyB,OAASE,EAAYF,EAC/FG,EAAQrC,GAAcsC,EAAQH,EAAWC,CAAS,IAElDC,EAAQJ,IAAwB,OAASE,EAAWF,EACpDK,EAASJ,IAAyB,KAAOlC,GAAcqC,EAAOD,EAAYD,CAAQ,EAAID,IAAyB,OAASE,EAAYF,GAErI,MAAMK,EAAa,CAAA,EACbC,EAAU,CAACxE,EAAMxB,IAAU,CAC3B6E,GAAe7E,CAAK,IAAG+F,EAAWvE,CAAI,EAAIxB,EAAM,SAAQ,EAC9D,EACAgG,EAAQ,QAASH,CAAK,EACtBG,EAAQ,SAAUF,CAAM,EACxB,MAAMG,EAAU,CACff,EAAI,KACJA,EAAI,IACJS,EACAC,CACF,EACC,OAAAG,EAAW,QAAUE,EAAQ,KAAK,GAAG,EAC9B,CACN,WAAAF,EACA,QAAAE,EACA,KAAAtB,CACF,CACA,CAkBA,MAAMuB,GAAQ,gBAMRC,GAAe,YAAc,KAAK,IAAG,EAAG,SAAS,EAAE,GAAK,KAAK,OAAM,EAAK,SAAW,GAAG,SAAS,EAAE,EAIvG,IAAIC,GAAU,EAId,SAASC,GAAW1B,EAAM5C,EAASoE,GAAc,CAChD,MAAMG,EAAM,CAAA,EACZ,IAAIC,EACJ,KAAOA,EAAQL,GAAM,KAAKvB,CAAI,GAAG2B,EAAI,KAAKC,EAAM,CAAC,CAAC,EAClD,GAAI,CAACD,EAAI,OAAQ,OAAO3B,EACxB,MAAM6B,EAAS,UAAY,KAAK,OAAM,EAAK,SAAW,KAAK,OAAO,SAAS,EAAE,EAC7E,OAAAF,EAAI,QAASG,GAAO,CACnB,MAAMC,EAAQ,OAAO3E,GAAW,WAAaA,EAAO0E,CAAE,EAAI1E,GAAUqE,MAAW,SAAQ,EACjFO,EAAYF,EAAG,QAAQ,sBAAuB,MAAM,EAC1D9B,EAAOA,EAAK,QAAQ,IAAI,OAAO,WAAcgC,EAAY,mBAAqB,GAAG,EAAG,KAAOD,EAAQF,EAAS,IAAI,CACjH,CAAC,EACD7B,EAAOA,EAAK,QAAQ,IAAI,OAAO6B,EAAQ,GAAG,EAAG,EAAE,EACxC7B,CACR,CAKA,MAAMxC,GAAU,OAAO,OAAO,IAAI,EAIlC,SAASyE,GAAa9E,EAAUV,EAAM,CACrCe,GAAQL,CAAQ,EAAIV,CACrB,CAIA,SAASyF,GAAa/E,EAAU,CAC/B,OAAOK,GAAQL,CAAQ,GAAKK,GAAQ,EAAE,CACvC,CAKA,SAAS2E,GAAgBC,EAAQ,CAChC,IAAIC,EACJ,GAAI,OAAOD,EAAO,WAAc,SAAUC,EAAY,CAACD,EAAO,SAAS,UAEtEC,EAAYD,EAAO,UACf,EAAEC,aAAqB,QAAU,CAACA,EAAU,OAAQ,OAAO,KAYhE,MAVe,CACd,UAAAA,EACA,KAAMD,EAAO,MAAQ,IACrB,OAAQA,EAAO,QAAU,IACzB,OAAQA,EAAO,QAAU,IACzB,QAASA,EAAO,SAAW,IAC3B,OAAQA,EAAO,SAAW,GAC1B,MAAOA,EAAO,OAAS,EACvB,iBAAkBA,EAAO,mBAAqB,EAChD,CAEA,CAIA,MAAME,GAAgB,OAAO,OAAO,IAAI,EAgBlCC,GAAqB,CAAC,4BAA6B,wBAAwB,EAC3EC,GAAc,CAAA,EACpB,KAAOD,GAAmB,OAAS,GAAOA,GAAmB,SAAW,GAC/D,KAAK,OAAM,EAAK,GADkDC,GAAY,KAAKD,GAAmB,MAAK,CAAE,EAEjHC,GAAY,KAAKD,GAAmB,KAAK,EAC9CD,GAAc,EAAE,EAAIH,GAAgB,CAAE,UAAW,CAAC,4BAA4B,EAAE,OAAOK,EAAW,EAAG,EAIrG,SAASC,GAAetF,EAAUuF,EAAc,CAC/C,MAAMC,EAASR,GAAgBO,CAAY,EAC3C,OAAIC,IAAW,KAAa,IAC5BL,GAAcnF,CAAQ,EAAIwF,EACnB,GACR,CAIA,SAASC,GAAazF,EAAU,CAC/B,OAAOmF,GAAcnF,CAAQ,CAC9B,CAQA,MAAM0F,GAAc,IAAM,CACzB,IAAIrG,EACJ,GAAI,CAEH,GADAA,EAAW,MACP,OAAOA,GAAa,WAAY,OAAOA,CAC5C,MAAc,CAAC,CAChB,EAIA,IAAIsG,GAAcD,GAAW,EAgB7B,SAASE,GAAmB5F,EAAUC,EAAQ,CAC7C,MAAMuF,EAASC,GAAazF,CAAQ,EACpC,GAAI,CAACwF,EAAQ,MAAO,GACpB,IAAI9G,EACJ,GAAI,CAAC8G,EAAO,OAAQ9G,EAAS,MACxB,CACJ,IAAImH,EAAgB,EACpBL,EAAO,UAAU,QAASlG,GAAS,CAElCuG,EAAgB,KAAK,IAAIA,EADZvG,EACgC,MAAM,CACpD,CAAC,EACD,MAAMwG,EAAM7F,EAAS,eACrBvB,EAAS8G,EAAO,OAASK,EAAgBL,EAAO,KAAK,OAASM,EAAI,MACnE,CACA,OAAOpH,CACR,CAIA,SAASqH,GAAYC,EAAQ,CAC5B,OAAOA,IAAW,GACnB,CAIA,MAAMC,GAAU,CAACjG,EAAUC,EAAQrC,IAAU,CAC5C,MAAMsI,EAAU,CAAA,EACVC,EAAYP,GAAmB5F,EAAUC,CAAM,EAC/CmG,EAAO,QACb,IAAI9G,EAAO,CACV,KAAA8G,EACA,SAAApG,EACA,OAAAC,EACA,MAAO,CAAA,CACT,EACKoG,EAAS,EACb,OAAAzI,EAAM,QAAQ,CAACI,EAAMuE,IAAU,CAC9B8D,GAAUrI,EAAK,OAAS,EACpBqI,GAAUF,GAAa5D,EAAQ,IAClC2D,EAAQ,KAAK5G,CAAI,EACjBA,EAAO,CACN,KAAA8G,EACA,SAAApG,EACA,OAAAC,EACA,MAAO,CAAA,CACX,EACGoG,EAASrI,EAAK,QAEfsB,EAAK,MAAM,KAAKtB,CAAI,CACrB,CAAC,EACDkI,EAAQ,KAAK5G,CAAI,EACV4G,CACR,EAIA,SAASI,GAAQtG,EAAU,CAC1B,GAAI,OAAOA,GAAa,SAAU,CACjC,MAAMwF,EAASC,GAAazF,CAAQ,EACpC,GAAIwF,EAAQ,OAAOA,EAAO,IAC3B,CACA,MAAO,GACR,CAIA,MAAMe,GAAO,CAACC,EAAMC,EAAQpH,IAAa,CACxC,GAAI,CAACsG,GAAa,CACjBtG,EAAS,QAAS,GAAG,EACrB,MACD,CACA,IAAIqH,EAAOJ,GAAQG,EAAO,QAAQ,EAClC,OAAQA,EAAO,KAAI,CAClB,IAAK,QAAS,CACb,MAAMxG,EAASwG,EAAO,OAEhBE,EADQF,EAAO,MACG,KAAK,GAAG,EAC1BG,EAAY,IAAI,gBAAgB,CAAE,MAAOD,CAAS,CAAE,EAC1DD,GAAQzG,EAAS,SAAW2G,EAAU,SAAQ,EAC9C,KACD,CACA,IAAK,SAAU,CACd,MAAMC,EAAMJ,EAAO,IACnBC,GAAQG,EAAI,MAAM,EAAG,CAAC,IAAM,IAAMA,EAAI,MAAM,CAAC,EAAIA,EACjD,KACD,CACA,QACCxH,EAAS,QAAS,GAAG,EACrB,MACH,CACC,IAAIyH,EAAe,IACnBnB,GAAYa,EAAOE,CAAI,EAAE,KAAMK,GAAa,CAC3C,MAAMf,EAASe,EAAS,OACxB,GAAIf,IAAW,IAAK,CACnB,WAAW,IAAM,CAChB3G,EAAS0G,GAAYC,CAAM,EAAI,QAAU,OAAQA,CAAM,CACxD,CAAC,EACD,MACD,CACA,OAAAc,EAAe,IACRC,EAAS,KAAI,CACrB,CAAC,EAAE,KAAMrJ,GAAS,CACjB,GAAI,OAAOA,GAAS,UAAYA,IAAS,KAAM,CAC9C,WAAW,IAAM,CACZA,IAAS,IAAK2B,EAAS,QAAS3B,CAAI,EACnC2B,EAAS,OAAQyH,CAAY,CACnC,CAAC,EACD,MACD,CACA,WAAW,IAAM,CAChBzH,EAAS,UAAW3B,CAAI,CACzB,CAAC,CACF,CAAC,EAAE,MAAM,IAAM,CACd2B,EAAS,OAAQyH,CAAY,CAC9B,CAAC,CACF,EAIME,GAAiB,CACtB,QAAAf,GACA,KAAAM,EACD,EAKA,SAASU,GAAeC,EAAUvC,EAAI,CACrCuC,EAAS,QAAS7G,GAAY,CAC7B,MAAM8G,EAAQ9G,EAAQ,gBAClB8G,IAAO9G,EAAQ,gBAAkB8G,EAAM,OAAQC,GAAQA,EAAI,KAAOzC,CAAE,EACzE,CAAC,CACF,CAIA,SAAS0C,GAAgBhH,EAAS,CAC5BA,EAAQ,uBACZA,EAAQ,qBAAuB,GAC/B,WAAW,IAAM,CAChBA,EAAQ,qBAAuB,GAC/B,MAAM8G,EAAQ9G,EAAQ,gBAAkBA,EAAQ,gBAAgB,MAAM,CAAC,EAAI,CAAA,EAC3E,GAAI,CAAC8G,EAAM,OAAQ,OACnB,IAAIG,EAAa,GACjB,MAAMtH,EAAWK,EAAQ,SACnBJ,EAASI,EAAQ,OACvB8G,EAAM,QAAS7H,GAAS,CACvB,MAAM1B,EAAQ0B,EAAK,MACbiI,EAAY3J,EAAM,QAAQ,OAChCA,EAAM,QAAUA,EAAM,QAAQ,OAAQiC,GAAS,CAC9C,GAAIA,EAAK,SAAWI,EAAQ,MAAO,GACnC,MAAMjC,EAAO6B,EAAK,KAClB,GAAIQ,EAAQ,MAAMrC,CAAI,EAAGJ,EAAM,OAAO,KAAK,CAC1C,SAAAoC,EACA,OAAAC,EACA,KAAAjC,CACN,CAAM,UACQqC,EAAQ,QAAQ,IAAIrC,CAAI,EAAGJ,EAAM,QAAQ,KAAK,CACtD,SAAAoC,EACA,OAAAC,EACA,KAAAjC,CACN,CAAM,MAEA,QAAAsJ,EAAa,GACN,GAER,MAAO,EACR,CAAC,EACG1J,EAAM,QAAQ,SAAW2J,IACvBD,GAAYL,GAAe,CAAC5G,CAAO,EAAGf,EAAK,EAAE,EAClDA,EAAK,SAAS1B,EAAM,OAAO,MAAM,CAAC,EAAGA,EAAM,QAAQ,MAAM,CAAC,EAAGA,EAAM,QAAQ,MAAM,CAAC,EAAG0B,EAAK,KAAK,EAEjG,CAAC,CACF,CAAC,EAEH,CAIA,IAAIkI,GAAY,EAIhB,SAASC,GAAcpI,EAAUzB,EAAO8J,EAAgB,CACvD,MAAM/C,EAAK6C,KACLG,EAAQV,GAAe,KAAK,KAAMS,EAAgB/C,CAAE,EAC1D,GAAI,CAAC/G,EAAM,QAAQ,OAAQ,OAAO+J,EAClC,MAAMrI,EAAO,CACZ,GAAAqF,EACA,MAAA/G,EACA,SAAAyB,EACA,MAAAsI,CACF,EACC,OAAAD,EAAe,QAASrH,GAAY,EAClCA,EAAQ,kBAAoBA,EAAQ,gBAAkB,KAAK,KAAKf,CAAI,CACtE,CAAC,EACMqI,CACR,CAKA,SAASC,GAAUhK,EAAO,CACzB,MAAMc,EAAS,CACd,OAAQ,CAAA,EACR,QAAS,CAAA,EACT,QAAS,CAAA,CACX,EACO2B,EAAU,OAAO,OAAO,IAAI,EAClCzC,EAAM,KAAK,CAACiK,EAAGC,IACVD,EAAE,WAAaC,EAAE,SAAiBD,EAAE,SAAS,cAAcC,EAAE,QAAQ,EACrED,EAAE,SAAWC,EAAE,OAAeD,EAAE,OAAO,cAAcC,EAAE,MAAM,EAC1DD,EAAE,KAAK,cAAcC,EAAE,IAAI,CAClC,EACD,IAAIC,EAAW,CACd,SAAU,GACV,OAAQ,GACR,KAAM,EACR,EACC,OAAAnK,EAAM,QAASiC,GAAS,CACvB,GAAIkI,EAAS,OAASlI,EAAK,MAAQkI,EAAS,SAAWlI,EAAK,QAAUkI,EAAS,WAAalI,EAAK,SAAU,OAC3GkI,EAAWlI,EACX,MAAMG,EAAWH,EAAK,SAChBI,EAASJ,EAAK,OACd7B,EAAO6B,EAAK,KACZM,EAAkBE,EAAQL,CAAQ,IAAMK,EAAQL,CAAQ,EAAI,OAAO,OAAO,IAAI,GAC9EgI,EAAe7H,EAAgBF,CAAM,IAAME,EAAgBF,CAAM,EAAIC,GAAWF,EAAUC,CAAM,GACtG,IAAIgI,EACAjK,KAAQgK,EAAa,MAAOC,EAAOvJ,EAAO,OACrCuB,IAAW,IAAM+H,EAAa,QAAQ,IAAIhK,CAAI,EAAGiK,EAAOvJ,EAAO,QACnEuJ,EAAOvJ,EAAO,QACnB,MAAMY,EAAO,CACZ,SAAAU,EACA,OAAAC,EACA,KAAAjC,CACH,EACEiK,EAAK,KAAK3I,CAAI,CACf,CAAC,EACMZ,CACR,CAKA,SAASwJ,GAAYD,EAAMxH,EAAW,GAAMK,EAAc,GAAO,CAChE,MAAMpC,EAAS,CAAA,EACf,OAAAuJ,EAAK,QAAS3I,GAAS,CACtB,MAAMO,EAAO,OAAOP,GAAS,SAAWkB,GAAalB,EAAMmB,EAAUK,CAAW,EAAIxB,EAChFO,GAAMnB,EAAO,KAAKmB,CAAI,CAC3B,CAAC,EACMnB,CACR,CAKA,MAAMyJ,GAAgB,CACrB,UAAW,CAAA,EACX,MAAO,EACP,QAAS,IACT,OAAQ,IACR,OAAQ,GACR,iBAAkB,EACnB,EAKA,SAASC,GAAU5C,EAAQ6C,EAASC,EAAOC,EAAM,CAChD,MAAMC,EAAiBhD,EAAO,UAAU,OAClCiD,EAAajD,EAAO,OAAS,KAAK,MAAM,KAAK,OAAM,EAAKgD,CAAc,EAAIhD,EAAO,MACvF,IAAIN,EACJ,GAAIM,EAAO,OAAQ,CAClB,IAAIyC,EAAOzC,EAAO,UAAU,MAAM,CAAC,EAEnC,IADAN,EAAY,CAAA,EACL+C,EAAK,OAAS,GAAG,CACvB,MAAMS,EAAY,KAAK,MAAM,KAAK,OAAM,EAAKT,EAAK,MAAM,EACxD/C,EAAU,KAAK+C,EAAKS,CAAS,CAAC,EAC9BT,EAAOA,EAAK,MAAM,EAAGS,CAAS,EAAE,OAAOT,EAAK,MAAMS,EAAY,CAAC,CAAC,CACjE,CACAxD,EAAYA,EAAU,OAAO+C,CAAI,CAClC,MAAO/C,EAAYM,EAAO,UAAU,MAAMiD,CAAU,EAAE,OAAOjD,EAAO,UAAU,MAAM,EAAGiD,CAAU,CAAC,EAClG,MAAME,EAAY,KAAK,IAAG,EAC1B,IAAI3C,EAAS,UACT4C,EAAc,EACdC,EACAC,EAAQ,KACRC,EAAQ,CAAA,EACRC,EAAgB,CAAA,EAChB,OAAOT,GAAS,YAAYS,EAAc,KAAKT,CAAI,EAIvD,SAASU,GAAa,CACjBH,IACH,aAAaA,CAAK,EAClBA,EAAQ,KAEV,CAIA,SAASnB,GAAQ,CACZ3B,IAAW,YAAWA,EAAS,WACnCiD,EAAU,EACVF,EAAM,QAASzJ,GAAS,CACnBA,EAAK,SAAW,YAAWA,EAAK,OAAS,UAC9C,CAAC,EACDyJ,EAAQ,CAAA,CACT,CAKA,SAASG,EAAU7J,EAAU8J,EAAW,CACnCA,IAAWH,EAAgB,CAAA,GAC3B,OAAO3J,GAAa,YAAY2J,EAAc,KAAK3J,CAAQ,CAChE,CAIA,SAAS+J,GAAiB,CACzB,MAAO,CACN,UAAAT,EACA,QAAAN,EACA,OAAArC,EACA,YAAA4C,EACA,eAAgBG,EAAM,OACtB,UAAAG,EACA,MAAAvB,CACH,CACC,CAIA,SAAS0B,GAAY,CACpBrD,EAAS,SACTgD,EAAc,QAAS3J,GAAa,CACnCA,EAAS,OAAQwJ,CAAS,CAC3B,CAAC,CACF,CAIA,SAASS,GAAa,CACrBP,EAAM,QAASzJ,GAAS,CACnBA,EAAK,SAAW,YAAWA,EAAK,OAAS,UAC9C,CAAC,EACDyJ,EAAQ,CAAA,CACT,CAIA,SAASQ,EAAejK,EAAMyH,EAAUrJ,EAAM,CAC7C,MAAM8L,EAAUzC,IAAa,UAE7B,OADAgC,EAAQA,EAAM,OAAQU,GAAWA,IAAWnK,CAAI,EACxC0G,EAAM,CACb,IAAK,UAAW,MAChB,IAAK,SACJ,GAAIwD,GAAW,CAAChE,EAAO,iBAAkB,OACzC,MACD,QAAS,MACZ,CACE,GAAIuB,IAAa,QAAS,CACzB8B,EAAYnL,EACZ2L,EAAS,EACT,MACD,CACA,GAAIG,EAAS,CACZX,EAAYnL,EACPqL,EAAM,SAAa7D,EAAU,OAC7BwE,EAAQ,EAD6BL,EAAS,GAEnD,MACD,CAGA,GAFAJ,EAAU,EACVK,EAAU,EACN,CAAC9D,EAAO,OAAQ,CACnB,MAAMjD,EAAQiD,EAAO,UAAU,QAAQlG,EAAK,QAAQ,EAChDiD,IAAU,IAAMA,IAAUiD,EAAO,QAAOA,EAAO,MAAQjD,EAC5D,CACAyD,EAAS,YACTgD,EAAc,QAAS3J,GAAa,CACnCA,EAAS3B,CAAI,CACd,CAAC,CACF,CAIA,SAASgM,GAAW,CACnB,GAAI1D,IAAW,UAAW,OAC1BiD,EAAU,EACV,MAAMU,EAAWzE,EAAU,MAAK,EAChC,GAAIyE,IAAa,OAAQ,CACxB,GAAIZ,EAAM,OAAQ,CACjBD,EAAQ,WAAW,IAAM,CACxBG,EAAU,EACNjD,IAAW,YACdsD,EAAU,EACVD,EAAS,EAEX,EAAG7D,EAAO,OAAO,EACjB,MACD,CACA6D,EAAS,EACT,MACD,CACA,MAAM/J,EAAO,CACZ,OAAQ,UACR,SAAAqK,EACA,SAAU,CAACC,EAAUlM,IAAS,CAC7B6L,EAAejK,EAAMsK,EAAUlM,CAAI,CACpC,CACH,EACEqL,EAAM,KAAKzJ,CAAI,EACfsJ,IACAE,EAAQ,WAAWY,EAAUlE,EAAO,MAAM,EAC1C8C,EAAMqB,EAAUtB,EAAS/I,EAAK,QAAQ,CACvC,CACA,kBAAWoK,CAAQ,EACZN,CACR,CAKA,SAASS,GAAeC,EAAK,CAC5B,MAAMtE,EAAS,CACd,GAAG2C,GACH,GAAG2B,CACL,EACC,IAAIC,EAAU,CAAA,EAId,SAASC,GAAU,CAClBD,EAAUA,EAAQ,OAAQzK,GAASA,EAAI,EAAG,SAAW,SAAS,CAC/D,CAIA,SAASgJ,EAAMD,EAAS4B,EAAeC,EAAc,CACpD,MAAMC,EAAU/B,GAAU5C,EAAQ6C,EAAS4B,EAAe,CAACvM,EAAM0M,IAAU,CAC1EJ,EAAO,EACHE,GAAcA,EAAaxM,EAAM0M,CAAK,CAC3C,CAAC,EACD,OAAAL,EAAQ,KAAKI,CAAO,EACbA,CACR,CAIA,SAASE,EAAKhL,EAAU,CACvB,OAAO0K,EAAQ,KAAM7L,GACbmB,EAASnB,CAAK,CACrB,GAAK,IACP,CAUA,MATiB,CAChB,MAAAoK,EACA,KAAA+B,EACA,SAAW9H,GAAU,CACpBiD,EAAO,MAAQjD,CAChB,EACA,SAAU,IAAMiD,EAAO,MACvB,QAAAwE,CACF,CAEA,CAEA,SAASM,IAAkB,CAAC,CAC5B,MAAMC,GAAkB,OAAO,OAAO,IAAI,EAI1C,SAASC,GAAmBxK,EAAU,CACrC,GAAI,CAACuK,GAAgBvK,CAAQ,EAAG,CAC/B,MAAMwF,EAASC,GAAazF,CAAQ,EACpC,GAAI,CAACwF,EAAQ,OACb,MAAMiF,EAAaZ,GAAerE,CAAM,EAClCkF,EAAkB,CACvB,OAAAlF,EACA,WAAAiF,CACH,EACEF,GAAgBvK,CAAQ,EAAI0K,CAC7B,CACA,OAAOH,GAAgBvK,CAAQ,CAChC,CAIA,SAAS2K,GAAaC,EAAQtC,EAAOjJ,EAAU,CAC9C,IAAIoL,EACAlE,EACJ,GAAI,OAAOqE,GAAW,SAAU,CAC/B,MAAMC,EAAM9F,GAAa6F,CAAM,EAC/B,GAAI,CAACC,EACJ,OAAAxL,EAAS,OAAQ,GAAG,EACbiL,GAER/D,EAAOsE,EAAI,KACX,MAAMC,EAASN,GAAmBI,CAAM,EACpCE,IAAQL,EAAaK,EAAO,WACjC,KAAO,CACN,MAAMtF,EAASR,GAAgB4F,CAAM,EACrC,GAAIpF,EAAQ,CACXiF,EAAaZ,GAAerE,CAAM,EAClC,MAAMuF,EAAYH,EAAO,UAAYA,EAAO,UAAU,CAAC,EAAI,GACrDC,EAAM9F,GAAagG,CAAS,EAC9BF,IAAKtE,EAAOsE,EAAI,KACrB,CACD,CACA,MAAI,CAACJ,GAAc,CAAClE,GACnBlH,EAAS,OAAQ,GAAG,EACbiL,IAEDG,EAAW,MAAMnC,EAAO/B,EAAMlH,CAAQ,EAAC,EAAG,KAClD,CAEA,SAAS2L,IAAgB,CAAC,CAI1B,SAASC,GAAe5K,EAAS,CAC3BA,EAAQ,kBACZA,EAAQ,gBAAkB,GAC1B,WAAW,IAAM,CAChBA,EAAQ,gBAAkB,GAC1BgH,GAAgBhH,CAAO,CACxB,CAAC,EAEH,CAIA,SAAS6K,GAAqBtN,EAAO,CACpC,MAAMuN,EAAQ,CAAA,EACRC,EAAU,CAAA,EAChB,OAAAxN,EAAM,QAASI,GAAS,EACtBA,EAAK,MAAMuC,EAAa,EAAI4K,EAAQC,GAAS,KAAKpN,CAAI,CACxD,CAAC,EACM,CACN,MAAAmN,EACA,QAAAC,CACF,CACA,CAIA,SAASC,GAAoBhL,EAASzC,EAAOF,EAAM,CAClD,SAAS4N,GAAe,CACvB,MAAMC,EAAUlL,EAAQ,aACxBzC,EAAM,QAASI,GAAS,CACnBuN,GAASA,EAAQ,OAAOvN,CAAI,EAC3BqC,EAAQ,MAAMrC,CAAI,GAAGqC,EAAQ,QAAQ,IAAIrC,CAAI,CACnD,CAAC,CACF,CACA,GAAIN,GAAQ,OAAOA,GAAS,SAAU,GAAI,CAEzC,GAAI,CADW0C,GAAWC,EAAS3C,CAAI,EAC3B,OAAQ,CACnB4N,EAAY,EACZ,MACD,CACD,OAASE,EAAK,CACb,QAAQ,MAAMA,CAAG,CAClB,CACAF,EAAY,EACZL,GAAe5K,CAAO,CACvB,CAIA,SAASoL,GAA2B1E,EAAU1H,EAAU,CACnD0H,aAAoB,QAASA,EAAS,KAAMrJ,GAAS,CACxD2B,EAAS3B,CAAI,CACd,CAAC,EAAE,MAAM,IAAM,CACd2B,EAAS,IAAI,CACd,CAAC,EACIA,EAAS0H,CAAQ,CACvB,CAIA,SAAS2E,GAAarL,EAASzC,EAAO,CAChCyC,EAAQ,YACRA,EAAQ,YAAcA,EAAQ,YAAY,OAAOzC,CAAK,EAAE,KAAI,EADvCyC,EAAQ,YAAczC,EAE3CyC,EAAQ,iBACZA,EAAQ,eAAiB,GACzB,WAAW,IAAM,CAChBA,EAAQ,eAAiB,GACzB,KAAM,CAAE,SAAAL,EAAU,OAAAC,CAAM,EAAKI,EACvBsL,EAAUtL,EAAQ,YAExB,GADA,OAAOA,EAAQ,YACX,CAACsL,GAAW,CAACA,EAAQ,OAAQ,OACjC,MAAMC,EAAmBvL,EAAQ,SACjC,GAAIA,EAAQ,YAAcsL,EAAQ,OAAS,GAAK,CAACC,GAAmB,CACnEH,GAA2BpL,EAAQ,UAAUsL,EAAS1L,EAAQD,CAAQ,EAAItC,GAAS,CAClF2N,GAAoBhL,EAASsL,EAASjO,CAAI,CAC3C,CAAC,EACD,MACD,CACA,GAAIkO,EAAkB,CACrBD,EAAQ,QAAS3N,GAAS,CACzB,MAAM+I,EAAW6E,EAAiB5N,EAAMiC,EAAQD,CAAQ,EACxDyL,GAA2B1E,EAAWrJ,GAAS,CAC9C,MAAMmO,EAAUnO,EAAO,CACtB,OAAAuC,EACA,MAAO,CAAE,CAACjC,CAAI,EAAGN,CAAI,CAC5B,EAAU,KACJ2N,GAAoBhL,EAAS,CAACrC,CAAI,EAAG6N,CAAO,CAC7C,CAAC,CACF,CAAC,EACD,MACD,CACA,KAAM,CAAE,MAAAV,EAAO,QAAAC,GAAYF,GAAqBS,CAAO,EAEvD,GADIP,EAAQ,QAAQC,GAAoBhL,EAAS+K,EAAS,IAAI,EAC1D,CAACD,EAAM,OAAQ,OACnB,MAAMN,EAAM5K,EAAO,MAAMM,EAAa,EAAIwE,GAAa/E,CAAQ,EAAI,KACnE,GAAI,CAAC6K,EAAK,CACTQ,GAAoBhL,EAAS8K,EAAO,IAAI,EACxC,MACD,CACeN,EAAI,QAAQ7K,EAAUC,EAAQkL,CAAK,EAC3C,QAAS7L,GAAS,CACxBqL,GAAa3K,EAAUV,EAAO5B,GAAS,CACtC2N,GAAoBhL,EAASf,EAAK,MAAO5B,CAAI,CAC9C,CAAC,CACF,CAAC,CACF,CAAC,EAEH,CAIA,MAAMoO,GAAY,CAAClO,EAAOyB,IAAa,CACtC,MAAM0M,EAAe7D,GAAYtK,EAAO,GAAMmD,GAAgB,CAAE,EAC1DiL,EAAcpE,GAAUmE,CAAY,EAC1C,GAAI,CAACC,EAAY,QAAQ,OAAQ,CAChC,IAAIC,EAAe,GACnB,OAAI5M,GAAU,WAAW,IAAM,CAC1B4M,GAAc5M,EAAS2M,EAAY,OAAQA,EAAY,QAASA,EAAY,QAAShB,EAAa,CACvG,CAAC,EACM,IAAM,CACZiB,EAAe,EAChB,CACD,CACA,MAAMC,EAAW,OAAO,OAAO,IAAI,EAC7BC,EAAU,CAAA,EAChB,IAAIC,EAAcC,EAClB,OAAAL,EAAY,QAAQ,QAASnM,GAAS,CACrC,KAAM,CAAE,SAAAG,EAAU,OAAAC,CAAM,EAAKJ,EAC7B,GAAII,IAAWoM,GAAcrM,IAAaoM,EAAc,OACxDA,EAAepM,EACfqM,EAAapM,EACbkM,EAAQ,KAAKjM,GAAWF,EAAUC,CAAM,CAAC,EACzC,MAAMqM,EAAmBJ,EAASlM,CAAQ,IAAMkM,EAASlM,CAAQ,EAAI,OAAO,OAAO,IAAI,GAClFsM,EAAiBrM,CAAM,IAAGqM,EAAiBrM,CAAM,EAAI,CAAA,EAC3D,CAAC,EACD+L,EAAY,QAAQ,QAASnM,GAAS,CACrC,KAAM,CAAE,SAAAG,EAAU,OAAAC,EAAQ,KAAAjC,CAAI,EAAK6B,EAC7BQ,EAAUH,GAAWF,EAAUC,CAAM,EACrCsM,EAAelM,EAAQ,eAAiBA,EAAQ,aAA+B,IAAI,KACpFkM,EAAa,IAAIvO,CAAI,IACzBuO,EAAa,IAAIvO,CAAI,EACrBkO,EAASlM,CAAQ,EAAEC,CAAM,EAAE,KAAKjC,CAAI,EAEtC,CAAC,EACDmO,EAAQ,QAAS9L,GAAY,CAC5B,MAAM4H,EAAOiE,EAAS7L,EAAQ,QAAQ,EAAEA,EAAQ,MAAM,EAClD4H,EAAK,QAAQyD,GAAarL,EAAS4H,CAAI,CAC5C,CAAC,EACM5I,EAAWoI,GAAcpI,EAAU2M,EAAaG,CAAO,EAAInB,EACnE,EA2CA,SAASwB,GAAoB/M,EAAUH,EAAM,CAC5C,MAAMZ,EAAS,CAAE,GAAGe,CAAQ,EAC5B,UAAWX,KAAOQ,EAAM,CACvB,MAAMpB,EAAQoB,EAAKR,CAAG,EAChB2N,EAAY,OAAOvO,EACrBY,KAAOwC,IACNpD,IAAU,MAAQA,IAAUuO,IAAc,UAAYA,IAAc,aAAW/N,EAAOI,CAAG,EAAIZ,GACvFuO,IAAc,OAAO/N,EAAOI,CAAG,IAAGJ,EAAOI,CAAG,EAAIA,IAAQ,SAAWZ,EAAQ,EAAIA,EAC3F,CACA,OAAOQ,CACR,CAEA,MAAMgO,GAAY,SAIlB,SAASC,GAAeC,EAAQC,EAAM,CACrCA,EAAK,MAAMH,EAAS,EAAE,QAASI,GAAQ,CAEtC,OADcA,EAAI,KAAI,EACT,CACZ,IAAK,aACJF,EAAO,MAAQ,GACf,MACD,IAAK,WACJA,EAAO,MAAQ,GACf,KACJ,CACC,CAAC,CACF,CAKA,SAASG,GAAiB7O,EAAO8O,EAAe,EAAG,CAClD,MAAMC,EAAQ/O,EAAM,QAAQ,aAAc,EAAE,EAC5C,SAAS8L,EAAQkD,EAAS,CACzB,KAAOA,EAAU,GAAGA,GAAW,EAC/B,OAAOA,EAAU,CAClB,CACA,GAAID,IAAU,GAAI,CACjB,MAAM/K,EAAM,SAAShE,CAAK,EAC1B,OAAO,MAAMgE,CAAG,EAAI,EAAI8H,EAAQ9H,CAAG,CACpC,SAAW+K,IAAU/O,EAAO,CAC3B,IAAI4E,EAAQ,EACZ,OAAQmK,EAAK,CACZ,IAAK,IACJnK,EAAQ,GACR,MACD,IAAK,MAAOA,EAAQ,EACvB,CACE,GAAIA,EAAO,CACV,IAAIZ,EAAM,WAAWhE,EAAM,MAAM,EAAGA,EAAM,OAAS+O,EAAM,MAAM,CAAC,EAChE,OAAI,MAAM/K,CAAG,EAAU,GACvBA,EAAMA,EAAMY,EACLZ,EAAM,IAAM,EAAI8H,EAAQ9H,CAAG,EAAI,EACvC,CACD,CACA,OAAO8K,CACR,CAKA,SAASG,GAAWtK,EAAMoB,EAAY,CACrC,IAAImJ,EAAoBvK,EAAK,QAAQ,QAAQ,IAAM,GAAK,GAAK,8CAC7D,UAAWwK,KAAQpJ,EAAYmJ,GAAqB,IAAMC,EAAO,KAAQpJ,EAAWoJ,CAAI,EAAI,IAC5F,MAAO,0CAA8CD,EAAoB,IAAMvK,EAAO,QACvF,CAQA,SAASyK,GAAgBC,EAAK,CAC7B,OAAOA,EAAI,QAAQ,KAAM,GAAG,EAAE,QAAQ,KAAM,KAAK,EAAE,QAAQ,KAAM,KAAK,EAAE,QAAQ,KAAM,KAAK,EAAE,QAAQ,KAAM,KAAK,EAAE,QAAQ,OAAQ,GAAG,CACtI,CAIA,SAASC,GAAUD,EAAK,CACvB,MAAO,sBAAwBD,GAAgBC,CAAG,CACnD,CAIA,SAASE,GAASF,EAAK,CACtB,MAAO,QAAWC,GAAUD,CAAG,EAAI,IACpC,CAEA,IAAIG,GAIJ,SAASC,IAAe,CACvB,GAAI,CACHD,GAAS,OAAO,aAAa,aAAa,UAAW,CAAE,WAAaE,GAAMA,EAAG,CAC9E,MAAc,CACbF,GAAS,IACV,CACD,CAOA,SAASG,GAAiBC,EAAM,CAC/B,OAAIJ,KAAW,QAAQC,GAAY,EAC5BD,GAASA,GAAO,WAAWI,CAAI,EAAIA,CAC3C,CAEA,MAAMC,GAAoC,CACtC,GAAGxM,GACH,OAAQ,EACZ,EAKMyM,GAAc,CAChB,MAAS,6BACT,WAAc,+BACd,cAAe,GACf,KAAQ,KACZ,EAIMC,GAAc,CAChB,QAAS,cACb,EACMC,GAAgB,CAClB,gBAAiB,cACrB,EACMC,GAAe,CACjB,gBAAiB,aACrB,EAEMC,GAAa,CACf,MAAO,aACP,OAAQ,YACR,KAAM,WACV,EACMC,GAAe,CACjB,WAAYH,GACZ,KAAMA,GACN,WAAYC,EAChB,EACA,UAAWlO,KAAUoO,GAAc,CAC/B,MAAMpG,EAAOoG,GAAapO,CAAM,EAChC,UAAWP,KAAQ0O,GACfnG,EAAKhI,EAASP,CAAI,EAAI0O,GAAW1O,CAAI,CAE7C,CAIA,MAAM4O,GAAiB,CACnB,GAAGP,GACH,OAAQ,EACZ,EAIA,SAASQ,GAAQrQ,EAAO,CACpB,OAAOA,GAASA,EAAM,MAAM,YAAY,EAAI,KAAO,GACvD,CAIA,MAAMsQ,GAAS,CAEf3O,EAEAwD,EAEArF,IAAS,CAEL,MAAMyQ,EAAepL,EAAM,OACrBiL,GACAP,GAEA9K,EAAiBuJ,GAAoBiC,EAAcpL,CAAK,EAExDqL,EAAOrL,EAAM,MAAQ,MAErBsL,EAAQ,CAAA,EACRC,EAAcvL,EAAM,OAAS,CAAA,EAE7BwL,EAAiB,CACnB,GAAIH,IAAS,MAAQV,GAAc,EAC3C,EACI,GAAIhQ,EAAM,CACN,MAAMkD,EAAWV,GAAaxC,EAAM,GAAO,EAAI,EAC/C,GAAIkD,EAAU,CACV,MAAM4N,EAAa,CAAC,SAAS,EACvBzL,EAAQ,CACV,WACA,QAChB,EACY,UAAW3D,KAAQ2D,EACXnC,EAASxB,CAAI,GACboP,EAAW,KAAK,YAAc5N,EAASxB,CAAI,CAAC,EAGpDmP,EAAe,UAAYC,EAAW,KAAK,GAAG,CAClD,CACJ,CAEA,QAAShQ,KAAOuE,EAAO,CACnB,MAAMnF,EAAQmF,EAAMvE,CAAG,EACvB,GAAIZ,IAAU,OAGd,OAAQY,EAAG,CAEP,IAAK,OACL,IAAK,QACL,IAAK,WACL,IAAK,SACL,IAAK,OACL,IAAK,MACL,IAAK,WACD,MAEJ,IAAK,OACD+P,EAAe,IAAM3Q,EACrB,MAEJ,IAAK,YACD2Q,EAAe/P,CAAG,GACb+P,EAAe/P,CAAG,EAAI+P,EAAe/P,CAAG,EAAI,IAAM,IAC/CZ,EACR,MAEJ,IAAK,SACL,IAAK,QACL,IAAK,QACD+E,EAAenE,CAAG,EACdZ,IAAU,IAAQA,IAAU,QAAUA,IAAU,EACpD,MAEJ,IAAK,OACG,OAAOA,GAAU,UACjByO,GAAe1J,EAAgB/E,CAAK,EAExC,MAEJ,IAAK,QACDyQ,EAAM,MAAQzQ,EACd,MAEJ,IAAK,SACG,OAAOA,GAAU,SACjB+E,EAAenE,CAAG,EAAIiO,GAAiB7O,CAAK,EAEvC,OAAOA,GAAU,WACtB+E,EAAenE,CAAG,EAAIZ,GAE1B,MAEJ,IAAK,aACL,IAAK,cACGA,IAAU,IAAQA,IAAU,QAC5B,OAAO2Q,EAAe,aAAa,EAEvC,MAEJ,QACQJ,EAAa3P,CAAG,IAAM,SACtB+P,EAAe/P,CAAG,EAAIZ,EAE1C,CACI,CAEA,MAAMoB,EAAO0D,GAAUnD,EAAMoD,CAAc,EACrC8L,EAAgBzP,EAAK,WAK3B,GAHI2D,EAAe,SACf0L,EAAM,cAAgB,YAEtBD,IAAS,MAAO,CAEhBG,EAAe,MAAQ,CACnB,GAAGF,EACH,GAAGC,CACf,EAEQ,OAAO,OAAOC,EAAgBE,CAAa,EAE3C,IAAIC,EAAe,EACfrK,EAAKtB,EAAM,GACf,OAAI,OAAOsB,GAAO,WAEdA,EAAKA,EAAG,QAAQ,KAAM,GAAG,GAG7BkK,EAAe,wBAA0B,CACrC,OAAQhB,GAAiBtJ,GAAWjF,EAAK,KAAMqF,EAAK,IAAMA,EAAK,KAAOqK,IAAiB,cAAc,CAAC,CAClH,EACeC,EAAAA,cAAc,MAAOJ,CAAc,CAC9C,CAEA,KAAM,CAAE,KAAAhM,EAAM,MAAAkB,EAAO,OAAAC,CAAM,EAAKnE,EAC1BqP,EAAUR,IAAS,SACpBA,IAAS,KAAO,GAAQ7L,EAAK,QAAQ,cAAc,IAAM,IAExDiL,EAAOX,GAAWtK,EAAM,CAC1B,GAAGkM,EACH,MAAOhL,EAAQ,GACf,OAAQC,EAAS,EACzB,CAAK,EAED,OAAA6K,EAAe,MAAQ,CACnB,GAAGF,EACH,QAASlB,GAASK,CAAI,EACtB,MAASS,GAAQQ,EAAc,KAAK,EACpC,OAAUR,GAAQQ,EAAc,MAAM,EACtC,GAAGd,GACH,GAAIiB,EAAUhB,GAAgBC,GAC9B,GAAGS,CACX,EACWK,EAAAA,cAAc,OAAQJ,CAAc,CAC/C,EAMA9N,GAAiB,EAAI,EAErB+D,GAAa,GAAIkC,EAAc,EAI/B,GAAI,OAAO,SAAa,KAAe,OAAO,OAAW,IAAa,CAClE,MAAMmI,EAAU,OAEhB,GAAIA,EAAQ,iBAAmB,OAAQ,CACnC,MAAMC,EAAUD,EAAQ,eAClB3D,EAAM,iCACR,OAAO4D,GAAY,UAAYA,IAAY,OAC1CA,aAAmB,MAAQA,EAAU,CAACA,CAAO,GAAG,QAAS9P,GAAS,CAC/D,GAAI,EAGA,OAAOA,GAAS,UACZA,IAAS,MACTA,aAAgB,OAEhB,OAAOA,EAAK,OAAU,UACtB,OAAOA,EAAK,QAAW,UAEvB,CAAC8B,GAAc9B,CAAI,IACnB,QAAQ,MAAMkM,CAAG,CAEzB,MACU,CACN,QAAQ,MAAMA,CAAG,CACrB,CACJ,CAAC,CAET,CAEA,GAAI2D,EAAQ,mBAAqB,OAAQ,CACrC,MAAME,EAAYF,EAAQ,iBAC1B,GAAI,OAAOE,GAAc,UAAYA,IAAc,KAC/C,QAASvQ,KAAOuQ,EAAW,CACvB,MAAM7D,EAAM,oBAAsB1M,EAAM,gBACxC,GAAI,CACA,MAAMZ,EAAQmR,EAAUvQ,CAAG,EAC3B,GAAI,OAAOZ,GAAU,UACjB,CAACA,GACDA,EAAM,YAAc,OACpB,SAECoH,GAAexG,EAAKZ,CAAK,GAC1B,QAAQ,MAAMsN,CAAG,CAEzB,MACU,CACN,QAAQ,MAAMA,CAAG,CACrB,CACJ,CAER,CACJ,CACA,SAAS8D,GAAcjM,EAAO,CAC1B,KAAM,CAACkM,EAASC,CAAU,EAAIC,EAAAA,SAAS,CAAC,CAACpM,EAAM,GAAG,EAC5C,CAACsE,EAAO+H,CAAQ,EAAID,EAAAA,SAAS,CAAA,CAAE,EAErC,SAASE,EAAgBJ,EAAS,CAC9B,GAAIA,EAAS,CACT,MAAMvR,EAAOqF,EAAM,KACnB,GAAI,OAAOrF,GAAS,SAEhB,MAAO,CACH,KAAM,GACN,KAAMA,CAC1B,EAEY,MAAMN,EAAOuD,GAAYjD,CAAI,EAC7B,GAAIN,EACA,MAAO,CACH,KAAAM,EACA,KAAAN,CACpB,CAEQ,CACA,MAAO,CACH,KAAM,EAClB,CACI,CACA,KAAM,CAACkS,EAAOC,CAAQ,EAAIJ,EAAAA,SAASE,EAAgB,CAAC,CAACtM,EAAM,GAAG,CAAC,EAE/D,SAAS2G,GAAU,CACf,MAAM3K,EAAWsI,EAAM,SACnBtI,IACAA,EAAQ,EACRqQ,EAAS,CAAA,CAAE,EAEnB,CAEA,SAASI,EAAYC,EAAU,CAC3B,GAAI,KAAK,UAAUH,CAAK,IAAM,KAAK,UAAUG,CAAQ,EACjD,OAAA/F,EAAO,EACP6F,EAASE,CAAQ,EACV,EAEf,CAEA,SAASC,GAAc,CACnB,IAAIC,EACJ,MAAMjS,EAAOqF,EAAM,KACnB,GAAI,OAAOrF,GAAS,SAAU,CAE1B8R,EAAY,CACR,KAAM,GACN,KAAM9R,CACtB,CAAa,EACD,MACJ,CAEA,MAAMN,EAAOuD,GAAYjD,CAAI,EAC7B,GAAI8R,EAAY,CACZ,KAAA9R,EACA,KAAAN,CACZ,CAAS,EACG,GAAIA,IAAS,OAAW,CAEpB,MAAM2B,EAAWyM,GAAU,CAAC9N,CAAI,EAAGgS,CAAW,EAC9CN,EAAS,CACL,SAAArQ,CACpB,CAAiB,CACL,MACS3B,KAEJuS,EAAK5M,EAAM,UAAY,MAAQ4M,IAAO,QAAkBA,EAAG,KAAK5M,EAAOrF,CAAI,EAGxF,CAEAkS,EAAAA,UAAU,KACNV,EAAW,EAAI,EACRxF,GACR,CAAA,CAAE,EAELkG,EAAAA,UAAU,IAAM,CACRX,GACAS,EAAW,CAEnB,EAAG,CAAC3M,EAAM,KAAMkM,CAAO,CAAC,EAExB,KAAM,CAAE,KAAAvR,EAAM,KAAAN,CAAI,EAAKkS,EACvB,OAAKlS,EAOE8Q,GAAO,CACV,GAAGnQ,GACH,GAAGX,CACX,EAAO2F,EAAOrF,CAAI,EATHqF,EAAM,SACPA,EAAM,SACNA,EAAM,SACFA,EAAM,SACN4L,EAAAA,cAAc,OAAQ,EAAE,CAM1C,CAMK,MAACkB,GAAOC,EAAAA,WAAW,CAAC/M,EAAOgN,IAAQf,GAAc,CAClD,GAAGjM,EACH,KAAMgN,CACV,CAAC,CAAC,EAMiBD,EAAAA,WAAW,CAAC/M,EAAOgN,IAAQf,GAAc,CACxD,OAAQ,GACR,GAAGjM,EACH,KAAMgN,CACV,CAAC,CAAC,ECn3DK,MAAMC,GAA2C,CACtD,oBAAqB,CACnB,KAAQ,0UACR,MAAS,GACT,OAAU,EAAA,EAEZ,kBAAmB,CACjB,KAAQ,m3BACR,MAAS,IACT,OAAU,GAAA,EAEZ,kCAAmC,CACjC,KAAQ,maACR,MAAS,GACT,OAAU,EAAA,EAEZ,wBAAyB,CACvB,KAAQ,kOACR,MAAS,GACT,OAAU,EAAA,EAEZ,+BAAgC,CAC9B,KAAQ,8PACR,MAAS,GACT,OAAU,EAAA,EAEZ,kBAAmB,CACjB,KAAQ,0QACR,MAAS,GACT,OAAU,EAAA,EAEZ,kBAAmB,CACjB,KAAQ,iQACR,MAAS,GACT,OAAU,EAAA,EAEZ,kBAAmB,CACjB,KAAQ,qXACR,MAAS,GACT,OAAU,EAAA,EAEZ,oBAAqB,CACnB,KAAQ,uMACR,MAAS,GACT,OAAU,EAAA,EAEZ,oBAAqB,CACnB,KAAQ,gyBACR,MAAS,GACT,OAAU,EAAA,EAEZ,uBAAwB,CACtB,KAAQ,sLACR,MAAS,GACT,OAAU,EAAA,EAEZ,oBAAqB,CACnB,KAAQ,ofACR,MAAS,GACT,OAAU,EAAA,EAEZ,qBAAsB,CACpB,KAAQ,mgBACR,MAAS,GACT,OAAU,EAAA,EAEZ,sBAAuB,CACrB,KAAQ,sWACR,MAAS,GACT,OAAU,EAAA,EAEZ,sBAAuB,CACrB,KAAQ,kuBACR,MAAS,GACT,OAAU,EAAA,EAEZ,kBAAmB,CACjB,KAAQ,0RACR,MAAS,GACT,OAAU,EAAA,EAEZ,+BAAgC,CAC9B,KAAQ,2SACR,MAAS,GACT,OAAU,EAAA,EAEZ,gCAAiC,CAC/B,KAAQ,gTACR,MAAS,GACT,OAAU,EAAA,EAEZ,6BAA8B,CAC5B,KAAQ,2SACR,MAAS,GACT,OAAU,EAAA,EAEZ,8BAA+B,CAC7B,KAAQ,8SACR,MAAS,GACT,OAAU,EAAA,EAEZ,uBAAwB,CACtB,KAAQ,uNACR,MAAS,GACT,OAAU,EAAA,EAEZ,sBAAuB,CACrB,KAAQ,+eACR,MAAS,GACT,OAAU,EAAA,EAEZ,mBAAoB,CAClB,KAAQ,kgBACR,MAAS,GACT,OAAU,EAAA,EAEZ,gCAAiC,CAC/B,KAAQ,wXACR,MAAS,GACT,OAAU,EAAA,EAEZ,gCAAiC,CAC/B,KAAQ,6WACR,MAAS,GACT,OAAU,EAAA,EAEZ,uBAAwB,CACtB,KAAQ,uOACR,MAAS,GACT,OAAU,EAAA,EAEZ,kBAAmB,CACjB,KAAQ,4PACR,MAAS,GACT,OAAU,EAAA,EAEZ,oBAAqB,CACnB,KAAQ,sIACR,MAAS,GACT,OAAU,EAAA,EAEZ,2BAA4B,CAC1B,KAAQ,0KACR,MAAS,GACT,OAAU,EAAA,EAEZ,eAAgB,CACd,KAAQ,wKACR,MAAS,GACT,OAAU,EAAA,EAEZ,oBAAqB,CACnB,KAAQ,wPACR,MAAS,GACT,OAAU,EAAA,EAEZ,gBAAiB,CACf,KAAQ,qIACR,MAAS,GACT,OAAU,EAAA,EAEZ,uBAAwB,CACtB,KAAQ,0WACR,MAAS,GACT,OAAU,EAAA,EAEZ,eAAgB,CACd,KAAQ,mKACR,MAAS,GACT,OAAU,EAAA,EAEZ,cAAe,CACb,KAAQ,uOACR,MAAS,GACT,OAAU,EAAA,EAEZ,qBAAsB,CACpB,KAAQ,iLACR,MAAS,GACT,OAAU,EAAA,EAEZ,cAAe,CACb,KAAQ,oLACR,MAAS,GACT,OAAU,EAAA,EAEZ,2BAA4B,CAC1B,KAAQ,wMACR,MAAS,GACT,OAAU,EAAA,EAEZ,cAAe,CACb,KAAQ,mQACR,MAAS,GACT,OAAU,EAAA,EAEZ,2BAA4B,CAC1B,KAAQ,wKACR,MAAS,GACT,OAAU,EAAA,EAEZ,yBAA0B,CACxB,KAAQ,4KACR,MAAS,GACT,OAAU,EAAA,EAEZ,qBAAsB,CACpB,KAAQ,kSACR,MAAS,GACT,OAAU,EAAA,EAEZ,qBAAsB,CACpB,KAAQ,oPACR,MAAS,GACT,OAAU,EAAA,EAEZ,kBAAmB,CACjB,KAAQ,yLACR,MAAS,GACT,OAAU,EAAA,EAEZ,kBAAmB,CACjB,KAAQ,uMACR,MAAS,GACT,OAAU,EAAA,EAEZ,uBAAwB,CACtB,KAAQ,iSACR,MAAS,GACT,OAAU,EAAA,EAEZ,iBAAkB,CAChB,KAAQ,yOACR,MAAS,GACT,OAAU,EAAA,EAEZ,eAAgB,CACd,KAAQ,uPACR,MAAS,GACT,OAAU,EAAA,EAEZ,gBAAiB,CACf,KAAQ,0IACR,MAAS,GACT,OAAU,EAAA,EAEZ,qBAAsB,CACpB,KAAQ,4SACR,MAAS,GACT,OAAU,EAAA,EAEZ,mBAAoB,CAClB,KAAQ,mOACR,MAAS,GACT,OAAU,EAAA,EAEZ,uBAAwB,CACtB,KAAQ,4IACR,MAAS,GACT,OAAU,EAAA,EAEZ,eAAgB,CACd,KAAQ,yHACR,MAAS,GACT,OAAU,EAAA,EAEZ,cAAe,CACb,KAAQ,+NACR,MAAS,GACT,OAAU,EAAA,EAEZ,uBAAwB,CACtB,KAAQ,kJACR,MAAS,GACT,OAAU,EAAA,EAEZ,sBAAuB,CACrB,KAAQ,8cACR,MAAS,GACT,OAAU,EAAA,EAEZ,iBAAkB,CAChB,KAAQ,mcACR,MAAS,GACT,OAAU,EAAA,EAEZ,kBAAmB,CACjB,KAAQ,wPACR,MAAS,GACT,OAAU,EAAA,EAEZ,kBAAmB,CACjB,KAAQ,iaACR,MAAS,GACT,OAAU,EAAA,EAEZ,gBAAiB,CACf,KAAQ,yMACR,MAAS,GACT,OAAU,EAAA,EAEZ,oBAAqB,CACnB,KAAQ,6LACR,MAAS,GACT,OAAU,EAAA,EAEZ,mBAAoB,CAClB,KAAQ,+LACR,MAAS,GACT,OAAU,EAAA,EAEZ,kBAAmB,CACjB,KAAQ,8OACR,MAAS,GACT,OAAU,EAAA,EAEZ,kBAAmB,CACjB,KAAQ,8dACR,MAAS,GACT,OAAU,EAAA,EAEZ,gBAAiB,CACf,KAAQ,qSACR,MAAS,GACT,OAAU,EAAA,EAEZ,kBAAmB,CACjB,KAAQ,0aACR,MAAS,GACT,OAAU,EAAA,EAEZ,gBAAiB,CACf,KAAQ,sJACR,MAAS,GACT,OAAU,EAAA,EAEZ,6BAA8B,CAC5B,KAAQ,4LACR,MAAS,GACT,OAAU,EAAA,EAEZ,eAAgB,CACd,KAAQ,0QACR,MAAS,GACT,OAAU,EAAA,EAEZ,cAAe,CACb,KAAQ,mdACR,MAAS,GACT,OAAU,EAAA,EAEZ,aAAc,CACZ,KAAQ,yQACR,MAAS,GACT,OAAU,EAAA,EAEZ,qBAAsB,CACpB,KAAQ,mMACR,MAAS,GACT,OAAU,EAAA,EAEZ,eAAgB,CACd,KAAQ,sMACR,MAAS,GACT,OAAU,EAAA,EAEZ,qBAAsB,CACpB,KAAQ,qKACR,MAAS,GACT,OAAU,EAAA,EAEZ,kBAAmB,CACjB,KAAQ,oLACR,MAAS,GACT,OAAU,EAAA,EAEZ,cAAe,CACb,KAAQ,uKACR,MAAS,GACT,OAAU,EAAA,EAEZ,gBAAiB,CACf,KAAQ,yMACR,MAAS,GACT,OAAU,EAAA,EAEZ,uBAAwB,CACtB,KAAQ,wTACR,MAAS,GACT,OAAU,EAAA,EAEZ,eAAgB,CACd,KAAQ,sXACR,MAAS,GACT,OAAU,EAAA,EAEZ,sCAAuC,CACrC,KAAQ,uUACR,MAAS,GACT,OAAU,EAAA,EAEZ,mBAAoB,CAClB,KAAQ,uUACR,MAAS,GACT,OAAU,EAAA,EAEZ,wBAAyB,CACvB,KAAQ,sRACR,MAAS,GACT,OAAU,EAAA,EAEZ,mBAAoB,CAClB,KAAQ,4SACR,MAAS,GACT,OAAU,EAAA,EAEZ,kBAAmB,CACjB,KAAQ,4KACR,MAAS,IACT,OAAU,GAAA,EAEZ,wBAAyB,CACvB,KAAQ,ghBACR,MAAS,GACT,OAAU,EAAA,CAEd,EC1ZO,SAASC,IAA4B,CAC1C,SAAW,CAACvS,EAAMN,CAAI,IAAK,OAAO,QAAQ4S,EAAW,EACnDnP,GAAQnD,EAAMN,CAAI,CAEtB,CAGA,IAAI8S,GAAkB,GAMf,SAASC,IAA8B,CACvCD,KACHD,GAAA,EACAC,GAAkB,GAEtB,CCmBO,MAAME,GAAuC,CAClD,SAAU,CAAC,OAAQ,QAAS,QAAS,OAAQ,OAAO,EACpD,WAAY,CAAC,SAAU,SAAU,OAAQ,OAAQ,SAAU,OAAQ,OAAQ,QAAS,QAAS,QAAS,MAAM,EAC5G,QAAS,CAAC,SAAU,OAAQ,WAAY,UAAU,EAClD,OAAQ,CAAC,SAAU,UAAW,SAAU,SAAU,WAAW,EAC7D,MAAO,CAAC,SAAU,MAAO,OAAQ,MAAM,EACvC,SAAU,CACR,OAAQ,GACR,cAAe,GACf,SAAU,GACV,KAAM,GACN,kBAAmB,EAAA,CAEvB,EAMaC,GAA2C,CACtD,SAAU,CAAC,OAAQ,OAAO,EAC1B,WAAY,CAAC,SAAU,SAAU,OAAQ,OAAQ,OAAO,EACxD,QAAS,CAAC,QAAQ,EAClB,OAAQ,CAAA,EACR,MAAO,CAAC,SAAU,MAAM,EACxB,SAAU,CACR,OAAQ,GACR,cAAe,GACf,SAAU,GACV,KAAM,GACN,kBAAmB,EAAA,CAEvB,ECvEaC,GAAoC,CAC/C,KAAM,qBACN,OAAQ,CACN,MAAO,CACL,OAAQ,CAAC,oBAAoB,EAC7B,OAAQ,CAAC,QAAQ,CAAA,CACnB,EAEF,aAAc,CACZ,GAAGD,GACH,QAAS,CAAC,QAAQ,EAClB,OAAQ,CAAA,EACR,MAAO,CAAC,SAAU,MAAM,CAAA,EAE1B,SAAU,CACR,QAAS,aACT,UAAW,SACX,UAAW,SACX,aAAc,WACd,WAAY,EAAA,EAEd,MAAO,CACL,QAAS,UACT,eAAgB,OAChB,aAAc,UAAA,EAEhB,QAAS,CACP,WAAY,YACZ,kBAAmB,SAAA,CAEvB,EAcaE,GAA4B,CACvC,KAAM,aACN,OAAQ,CACN,MAAO,CACL,OAAQ,CAAC,mBAAoB,qBAAsB,oBAAoB,EACvE,KAAM,CAAC,cAAc,EACrB,OAAQ,CAAC,QAAQ,EACjB,MAAO,CAAC,eAAe,CAAA,CACzB,EAEF,aAAcH,GACd,SAAU,CACR,QAAS,aACT,UAAW,QACX,UAAW,UACX,aAAc,WACd,WAAY,EAAA,EAEd,MAAO,CACL,QAAS,cACT,eAAgB,OAChB,aAAc,MAAA,EAEhB,QAAS,CACP,WAAY,WACZ,kBAAmB,SAAA,CAEvB,EAcaI,GAA4B,CACvC,KAAM,aACN,OAAQ,CACN,MAAO,CACL,OAAQ,CAAC,QAAQ,CAAA,CACnB,EAEF,aAAc,CACZ,GAAGH,GACH,OAAQ,CAAA,EACR,MAAO,CAAC,QAAQ,EAChB,SAAU,CACR,OAAQ,GACR,cAAe,GACf,SAAU,GACV,KAAM,GACN,kBAAmB,EAAA,CACrB,EAEF,SAAU,CACR,QAAS,OACT,UAAW,SACX,UAAW,SACX,aAAc,OACd,WAAY,EAAA,EAEd,MAAO,CACL,QAAS,UACT,eAAgB,UAChB,aAAc,OAAA,CAElB,ECvHaI,OAA2D,IAAgC,CACtG,CAAC,qBAAsBH,EAAkB,EACzC,CAAC,aAAcC,EAAU,EACzB,CAAC,aAAcC,EAAU,CAC3B,CAAC,EAuBM,SAASE,GAAWC,EAAiD,CAC1E,GAAI,OAAOA,GAAQ,SAAU,CAC3B,MAAMC,EAASH,GAAY,IAAIE,CAAG,EAClC,GAAI,CAACC,EAAQ,CACX,MAAMC,EAAW,MAAM,KAAKJ,GAAY,MAAM,EAAE,KAAK,IAAI,EACzD,MAAM,IAAI,MACR,oCAAoCE,CAAG,qBAAqBE,CAAQ,EAAA,CAExE,CACA,OAAOD,CACT,CACA,OAAOD,CACT,CCjBA,MAAMG,GAAaC,EAAAA,cAAsC,IAAI,EAiBtD,SAASC,GAAY,CAAE,IAAKC,EAAY,SAAAC,GAA8B,CAC3E,KAAM,CAACC,EAAYC,CAAa,EAAIjC,EAAAA,SAAwB8B,CAAU,EAGtErB,EAAAA,UAAU,IAAM,CACdwB,EAAcH,CAAU,CAC1B,EAAG,CAACA,CAAU,CAAC,EAEf,MAAMI,EAASC,cAAaX,GAAqC,CAC/D,MAAMnT,EAAW,OAAOmT,GAAQ,SAAWD,GAAWC,CAAG,EAAIA,EAC7DS,EAAc5T,CAAQ,CACxB,EAAG,CAAA,CAAE,EAECI,EAAQ2T,EAAAA,QACZ,KAAO,CAAE,IAAKJ,EAAY,OAAAE,IAC1B,CAACF,EAAYE,CAAM,CAAA,EAGrB,OAAOG,GAAAA,IAACV,GAAW,SAAX,CAAoB,MAAAlT,EAAe,SAAAsT,CAAA,CAAS,CACtD,CAkBO,SAASO,IAA0B,CACxC,MAAMC,EAAMC,EAAAA,WAAWb,EAAU,EACjC,GAAI,CAACY,EAAK,MAAM,IAAI,MAAM,0CAA0C,EACpE,OAAOA,CACT,CAQO,SAASE,IAAkB,CAChC,KAAM,CAAE,IAAAjB,CAAA,EAAQc,GAAA,EAChB,OAAOd,EAAI,YACb,CCnGA,MAAMkB,GAASC,EAAAA,aAAa,wBAAwB,EAW9CC,GAA4C,CAChD,MAAO,GACP,OAAQ,GACR,QAAS,EACT,gBAAiB,aACnB,EAOO,SAASC,GACdC,EACAC,EAA0B,GAClB,CACR,MAAMC,EAAO,CAAE,GAAGJ,GAAiB,GAAGG,CAAA,EAGhCE,EAAS,SAAS,cAAc,QAAQ,EAC9CA,EAAO,MAAQD,EAAK,MACpBC,EAAO,OAASD,EAAK,OACrB,MAAMT,EAAMU,EAAO,WAAW,IAAI,EAElC,GAAI,CAACV,EACH,MAAO,GAILS,EAAK,kBAAoB,gBAC3BT,EAAI,UAAYS,EAAK,gBACrBT,EAAI,SAAS,EAAG,EAAGS,EAAK,MAAOA,EAAK,MAAM,GAI5CT,EAAI,KAAA,EAEJ,GAAI,CAEEO,aAAmBI,EAAAA,aACrBC,GAAmBZ,EAAKO,EAASE,CAAI,EAErCI,GAAiCb,EAAKO,EAASE,CAAI,CAEvD,OAASrI,EAAO,CACd+H,GAAO,KAAK,8BAA+B/H,CAAK,CAClD,CAGA,OAAA4H,EAAI,QAAA,EAGGU,EAAO,UAAU,WAAW,CACrC,CAMA,SAASG,GACPb,EACAO,EACAE,EACM,OAEN,MAAMK,IAAO7C,EAAAsC,EAAQ,iBAAR,YAAAtC,EAAA,KAAAsC,KAA8B,CAAE,EAAG,EAAG,EAAG,EAAG,MAAO,IAAK,OAAQ,GAAA,EACvEQ,EAAeD,EAAK,MACpBE,EAAgBF,EAAK,OAE3B,GAAIC,IAAiB,GAAKC,IAAkB,EAAG,CAE7CC,GAAsBjB,EAAKO,EAASE,CAAI,EACxC,MACF,CAGA,MAAMS,EAAiBT,EAAK,MAAQA,EAAK,QAAU,EAC7CU,EAAkBV,EAAK,OAASA,EAAK,QAAU,EAC/CW,EAAQ,KAAK,IACjBF,EAAiBH,EACjBI,EAAkBH,EAClB,CAAA,EAIIK,EAAcN,EAAeK,EAC7BE,EAAeN,EAAgBI,EAC/BG,EAAUd,EAAK,SAAWS,EAAiBG,GAAe,EAC1DG,EAAUf,EAAK,SAAWU,EAAkBG,GAAgB,EAGlEtB,EAAI,KAAA,EACJA,EAAI,UAAUuB,EAAUT,EAAK,EAAIM,EAAOI,EAAUV,EAAK,EAAIM,CAAK,EAChEpB,EAAI,MAAMoB,EAAOA,CAAK,EAIlB,OAAOb,EAAQ,QAAW,WAC5BA,EAAQ,OAAOP,EAAK,GAAO,EAAK,EAGhCiB,GAAsBjB,EAAKO,EAASE,CAAI,EAG1CT,EAAI,QAAA,CACN,CAKA,SAASiB,GACPjB,EACAO,EACAE,EACM,CAENT,EAAI,UAAY,UAChBA,EAAI,KAAO,aACXA,EAAI,UAAY,SAChBA,EAAI,aAAe,SAEnB,MAAMyB,EAAQlB,EAAQ,eAAiB,UACvCP,EAAI,SAASyB,EAAOhB,EAAK,MAAQ,EAAGA,EAAK,OAAS,CAAC,CACrD,CAKA,SAASG,GACPZ,EACAO,EACAE,EACM,CAEN,MAAMS,EAAiBT,EAAK,MAAQA,EAAK,QAAU,EAC7CU,EAAkBV,EAAK,OAASA,EAAK,QAAU,EAC/CiB,EAAIjB,EAAK,QACTkB,EAAIlB,EAAK,QAEfT,EAAI,UAAY,UAChBA,EAAI,YAAc,UAClBA,EAAI,UAAY,EAGhB,MAAM4B,EAAeT,EAAkB,GACjCU,EAAUF,EAAIR,EAAkB,GAChCW,EAAWZ,EAAiB,GAC5Ba,EAAYZ,EAAkB,GAGpCnB,EAAI,SAAS0B,EAAGG,EAASX,EAAgBU,CAAY,EAGrD5B,EAAI,SAAS0B,EAAGG,EAAUE,EAAWD,EAAUC,CAAS,EAGpDxB,EAAQ,UAAYA,EAAQ,SAAS,OAAS,IAChDP,EAAI,UAAY,UAChBA,EAAI,KAAO,kBACXA,EAAI,UAAY,SAChBA,EAAI,aAAe,SACnBA,EAAI,SACFO,EAAQ,SAAS,OAAO,SAAA,EACxBmB,EAAIR,EAAiB,EACrBW,EAAUD,EAAe,CAAA,EAG/B,CC/KA,MAAMI,GAAM5B,EAAAA,aAAa,QAAQ,EAuEjC,eAAe6B,GACb1B,EACA5Q,EAAe,GACfuS,EAA0B,cACF,CACxB,GAAI,CAQF,OANgB5B,GAAuBC,EAAS,CAC9C,MAAO5Q,EACP,OAAQA,EACR,QAAS,EACT,gBAAAuS,CAAA,CACD,GACiB,IACpB,OAAS9J,EAAO,CACd,OAAA4J,GAAI,MAAM,8BAA+B5J,CAAK,EACvC,IACT,CACF,CAKA,SAAS+J,GACPC,EACAC,EACAC,EACAC,EACAC,EACAC,EAAgB,EAChBC,EAA0B,KACb,CAab,OARsBD,EAAQ,EAAIL,EAAWA,EAAS,OAAOO,GAAM,CACjE,MAAMC,EAAcD,EAA0C,UAA6B,KAC3F,OAAIA,aAAchC,EAAAA,aACTiC,IAAe,IAG1B,CAAC,GAEoB,IAAIrC,GAAW,CAClC,MAAMsC,EAAUtC,aAAmBI,EAAAA,aAC7BmC,EAAaT,IAAe9B,EAAQ,IAAM+B,EAAe,SAAS/B,EAAQ,EAAE,EAE5EwC,EAAuB,CAC3B,GAAIxC,EAAQ,GACZ,KAAMA,EAAQ,MAAQ,SAASA,EAAQ,GAAG,MAAM,EAAG,CAAC,CAAC,GACrD,KAAMA,EAAQ,cACd,QAASA,EAAQ,SAAW,GAC5B,OAAQA,EAAQ,QAAU,GAC1B,WAAAuC,EACA,QAAAD,EACA,MAAAJ,EACA,SAAAC,EACA,WAAYH,EAAa,IAAIhC,EAAQ,EAAE,GAAK,KAC5C,eAAgB,SAAY,CAC1B,MAAMiC,EAAgBjC,EAAQ,EAAE,CAClC,CAAA,EAIF,OAAIsC,GAAWtC,aAAmBI,iBAChCoC,EAAU,SAAWZ,GACnB5B,EAAQ,SACR8B,EACAC,EACAC,EACAC,EACAC,EAAQ,EACRlC,EAAQ,EAAA,GAILwC,CACT,CAAC,CACH,CAKA,SAASC,GAAiBC,EAAkC,CAC1D,MAAMvW,EAAsB,CAAA,EAC5B,UAAWwW,KAASD,EAClBvW,EAAO,KAAKwW,CAAK,EACbA,EAAM,UACRxW,EAAO,KAAK,GAAGsW,GAAiBE,EAAM,QAAQ,CAAC,EAGnD,OAAOxW,CACT,CAqCO,SAASyW,GAAU3C,EAA4B,GAAqB,CACzE,KAAM,CACJ,WAAY4C,EACZ,cAAAC,EAAgB,GAChB,YAAAC,EAAc,GACd,uBAAAC,EAAyB,aAAA,EACvB/C,EAEE,CACJ,SAAA4B,EACA,WAAAC,EACA,eAAAC,EACA,gBAAAkB,EACA,sBAAAC,EACA,oBAAAC,EACA,kBAAAC,EACA,kBAAAC,EACA,qBAAAC,EACA,sBAAAC,CAAA,EACEC,aAAA,EAGExB,EAAeyB,EAAAA,OAAmC,IAAI,GAAK,EAC3D,CAACC,EAAgBC,CAAiB,EAAIzG,EAAAA,SAAS,CAAC,EAGhD0G,EAAmBX,EAAgB,oBAAA,EACnCY,EAAsBhB,IAAqB,OAAYA,EAAmBe,EAG1E3B,EAAkB5C,cAAY,MAAOyE,GAAsB,CAC/D,MAAM9D,EAAU6B,EAAS,KAAKO,IAAMA,GAAG,KAAO0B,CAAS,EACvD,GAAI,CAAC9D,EAAS,OAEd,MAAM+D,GAAU,MAAMrC,GAA4B1B,EAAS+C,EAAaC,CAAsB,EAC9FhB,EAAa,QAAQ,IAAI8B,EAAWC,EAAO,EAE3CJ,EAAkBK,IAAKA,GAAI,CAAC,CAC9B,EAAG,CAACnC,EAAUkB,EAAaC,CAAsB,CAAC,EAG5CiB,EAAmB3E,EAAAA,QAAQ,IAAM,CACrC,IAAI4E,EAAWrC,EAGf,GAAIgC,EAAqB,CACvB,MAAMM,EAAalB,EAAgB,sBAAsBY,CAAmB,EAC5EK,EAAWA,EAAS,OAAO9B,IAAM+B,EAAW,SAAS/B,GAAG,EAAE,CAAC,CAC7D,CAGA,OAAKU,IACHoB,EAAWA,EAAS,OAAO9B,GAAMA,EAAG,UAAY,EAAK,GAGhD8B,CACT,EAAG,CAACrC,EAAUgC,EAAqBf,EAAeG,CAAe,CAAC,EAG5DP,EAASpD,EAAAA,QAAQ,IACdsC,GACLqC,EACAnC,EACAC,EACAC,EAAa,QACbC,CAAA,EAED,CAACgC,EAAkBnC,EAAYC,EAAgBE,EAAiByB,CAAc,CAAC,EAG5EU,EAAa9E,EAAAA,QAAQ,IAClBmD,GAAiBC,CAAM,EAC7B,CAACA,CAAM,CAAC,EAGL2B,EAAiB/E,EAAAA,QAAQ,IACtB8E,EAAW,OAAOzB,GAASA,EAAM,UAAU,EACjD,CAACyB,CAAU,CAAC,EAGTE,EAAcjF,EAAAA,YAAY,CAACjN,EAAYmS,EAA0B,KAAU,CAC3EA,EAEExC,EAAe,SAAS3P,CAAE,EAE5BgR,EAAkBrB,EAAe,OAAOyC,IAAOA,KAAQpS,CAAE,CAAC,EAG1DgR,EAAkB,CAAC,GAAGrB,EAAgB3P,CAAE,CAAC,EAI3C8Q,EAAsB9Q,CAAE,CAE5B,EAAG,CAAC2P,EAAgBqB,EAAmBF,CAAqB,CAAC,EAGvDuB,EAAuBpF,cAAapN,GAAkB,CAC1DmR,EAAkBnR,CAAG,CACvB,EAAG,CAACmR,CAAiB,CAAC,EAGhBsB,GAAiBrF,EAAAA,YAAY,IAAM,CACvC6D,EAAsB,IAAI,EAC1BE,EAAkB,CAAA,CAAE,CACtB,EAAG,CAACF,EAAuBE,CAAiB,CAAC,EAGvCuB,EAAmBtF,cAAajN,GAAe,CACnD,MAAM4N,EAAU6B,EAAS,KAAKO,IAAMA,GAAG,KAAOhQ,CAAE,EAChD,GAAI,CAAC4N,EAAS,OAEd,MAAM4E,GAAU5E,EAAQ,MAAA,EACxB4E,GAAQ,QAAU,EAAE5E,EAAQ,SAAW,IACvCmD,EAAoByB,EAAO,CAC7B,EAAG,CAAC/C,EAAUsB,CAAmB,CAAC,EAG5B0B,EAAaxF,cAAajN,GAAe,CAC7C,MAAM4N,EAAU6B,EAAS,KAAKO,IAAMA,GAAG,KAAOhQ,CAAE,EAChD,GAAI,CAAC4N,EAAS,OAEd,MAAM4E,GAAU5E,EAAQ,MAAA,EACxB4E,GAAQ,OAAS,EAAE5E,EAAQ,QAAU,IACrCmD,EAAoByB,EAAO,CAC7B,EAAG,CAAC/C,EAAUsB,CAAmB,CAAC,EAG5B2B,EAAczF,EAAAA,YAAY,CAACjN,EAAY3G,IAAiB,CAC5D,MAAMuU,GAAU6B,EAAS,KAAKO,IAAMA,GAAG,KAAOhQ,CAAE,EAChD,GAAI,CAAC4N,GAAS,OAEd,MAAM4E,GAAU5E,GAAQ,MAAA,EACxB4E,GAAQ,KAAOnZ,EACf0X,EAAoByB,EAAO,CAC7B,EAAG,CAAC/C,EAAUsB,CAAmB,CAAC,EAG5B4B,GAAc1F,cAAajN,GAAe,CAC9C,MAAM4N,EAAU6B,EAAS,KAAKO,IAAMA,GAAG,KAAOhQ,CAAE,EAChD,GAAI,CAAC4N,EAAS,CACZyB,GAAI,KAAK,WAAWrP,CAAE,yBAAyB,EAC/C,MACF,CAEAkR,EAAqBtD,CAAO,GAGxB8B,IAAe1P,GAAM2P,EAAe,SAAS3P,CAAE,KACjD8Q,EAAsB,IAAI,EAC1BE,EAAkBrB,EAAe,OAAOyC,IAAOA,KAAQpS,CAAE,CAAC,EAE9D,EAAG,CAACyP,EAAUyB,EAAsBxB,EAAYC,EAAgBmB,EAAuBE,CAAiB,CAAC,EAGnG4B,GAAiB3F,cAAajN,GAAe,CACjD,MAAM4N,EAAU6B,EAAS,KAAKO,GAAMA,EAAG,KAAOhQ,CAAE,EAChD,GAAI,CAAC4N,EAAS,CACZyB,GAAI,KAAK,WAAWrP,CAAE,4BAA4B,EAClD,MACF,CAIA,MAAM6S,GADSjF,EAAQ,MAAA,EACH,OAAA,EAEpB,OAAQiF,GAAiC,GAEzC,MAAMC,GAAclF,EAAQ,YACtBmF,GAAkB,IAAID,GAAYD,EAA+B,EAGvEE,GAAgB,EAAInF,EAAQ,EAAI,GAChCmF,GAAgB,EAAInF,EAAQ,EAAI,GAGhC,MAAMoF,EAAanC,EAAgB,wBAAwB7Q,CAAE,EAC7D,GAAI,CAACgT,EAAY,CACf3D,GAAI,KAAK,WAAWrP,CAAE,mCAAmC,EACzD,MACF,CAGAiR,EAAkB8B,GAAiBC,EAAYhT,CAAE,EAGjD8Q,EAAsBiC,GAAgB,EAAE,CAC1C,EAAG,CAACtD,EAAUoB,EAAiBI,EAAmBH,CAAqB,CAAC,EAGlEmC,GAAgBhG,EAAAA,YAAY,CAACiG,EAAmBC,IAAoB,CACxE,GAAID,IAAcC,EAAS,OAC3B,GAAID,EAAY,GAAKA,GAAarB,EAAiB,OAAQ,CACzDxC,GAAI,KAAK,sBAAsB6D,CAAS,EAAE,EAC1C,MACF,CACA,GAAIC,EAAU,GAAKA,GAAWtB,EAAiB,OAAQ,CACrDxC,GAAI,KAAK,oBAAoB8D,CAAO,EAAE,EACtC,MACF,CAEA,MAAMC,GAAiBvB,EAAiBqB,CAAS,EAC3CG,GAAgBxB,EAAiBsB,CAAO,EAGxCG,GAAWJ,EAAYC,EAAU,QAAU,SAEjDhC,EAAsBiC,GAAe,GAAIC,GAAc,GAAIC,EAAQ,CACrE,EAAG,CAACzB,EAAkBV,CAAqB,CAAC,EAItCoC,GAAmBtG,cAAajN,GAAe,CACnD,MAAMpC,EAAQoU,EAAW,UAAUwB,IAAKA,GAAE,KAAOxT,CAAE,EACnD,GAAIpC,EAAQ,EAAG,CACbyR,GAAI,KAAK,SAASrP,CAAE,YAAY,EAChC,MACF,CACIpC,GAASoU,EAAW,OAAS,GAIjCiB,GAAcrV,EAAOA,EAAQ,CAAC,CAChC,EAAG,CAACoU,EAAYiB,EAAa,CAAC,EAIxBQ,EAAoBxG,cAAajN,GAAe,CACpD,MAAMpC,EAAQoU,EAAW,UAAUwB,IAAKA,GAAE,KAAOxT,CAAE,EACnD,GAAIpC,EAAQ,EAAG,CACbyR,GAAI,KAAK,SAASrP,CAAE,YAAY,EAChC,MACF,CACIpC,GAAS,GAIbqV,GAAcrV,EAAOA,EAAQ,CAAC,CAChC,EAAG,CAACoU,EAAYiB,EAAa,CAAC,EAGxBS,GAAczG,cAAapN,GAAkB,CACjD,GAAIA,EAAI,OAAS,EAAG,CAClBwP,GAAI,KAAK,4CAA4C,EACrD,MACF,CAGA,MAAMsE,EAAkBlE,EAAS,UAC/B5P,EAAI,SAASmQ,EAAG,EAAE,GAAKA,EAAG,gBAAkB,OAAA,EAG9C,GAAI2D,EAAgB,OAAS,EAAG,CAC9BtE,GAAI,KAAK,+DAA+D,EACxE,MACF,CAGA,MAAMuE,GAAcD,EAAgB,IAAI3D,GAAMa,EAAgB,wBAAwBb,EAAG,EAAE,CAAC,EACtF6D,GAAoB,CAAC,GAAG,IAAI,IAAID,EAAW,CAAC,EAClD,GAAIC,GAAkB,SAAW,GAAK,CAACA,GAAkB,CAAC,EAAG,CAC3DxE,GAAI,KAAK,oDAAoD,EAC7D,MACF,CACA,MAAM2D,GAAaa,GAAkB,CAAC,EAGhCC,GAASH,EAAgB,IAAI3D,GAAMA,EAAG,eAAA,CAAgB,EAAE,OAAO,OAAO,EAC5E,GAAI8D,GAAO,SAAW,EAAG,CACvBzE,GAAI,KAAK,8CAA8C,EACvD,MACF,CAEA,MAAM0E,EAAO,KAAK,IAAI,GAAGD,GAAO,IAAI3Q,GAAKA,EAAG,CAAC,CAAC,EACxC6Q,EAAO,KAAK,IAAI,GAAGF,GAAO,IAAI3Q,GAAKA,EAAG,CAAC,CAAC,EAGxC8Q,EAAQ,IAAIjG,eAAa,CAC7B,EAAG+F,EACH,EAAGC,CAAA,CACJ,EAGDC,EAAM,SAAWN,EAAgB,IAAI3D,GAAM,CACzC,MAAMkE,GAAQlE,EAAG,MAAA,EAEjB,OAAAkE,GAAM,EAAIlE,EAAG,EAAI+D,EACjBG,GAAM,EAAIlE,EAAG,EAAIgE,EACVE,EACT,CAAC,EAGDP,EAAgB,QAAQ3D,GAAMkB,EAAqBlB,CAAE,CAAC,EACtDiB,EAAkBgD,EAAOjB,EAAU,EAGnClC,EAAsBmD,EAAM,EAAE,EAC9BjD,EAAkB,CAAA,CAAE,CACtB,EAAG,CAACvB,EAAUoB,EAAiBK,EAAsBD,EAAmBH,EAAuBE,CAAiB,CAAC,EAG3GmD,GAAelH,cAAajN,GAAe,CAC/C,MAAM4N,EAAU6B,EAAS,KAAKO,IAAMA,GAAG,KAAOhQ,CAAE,EAChD,GAAI,CAAC4N,EAAS,CACZyB,GAAI,KAAK,WAAWrP,CAAE,2BAA2B,EACjD,MACF,CAEA,GAAI,EAAE4N,aAAmBI,EAAAA,cAAe,CACtCqB,GAAI,KAAK,WAAWrP,CAAE,iBAAiB,EACvC,MACF,CAEA,MAAMiU,GAAQrG,EACd,GAAI,CAACqG,GAAM,UAAYA,GAAM,SAAS,SAAW,EAAG,CAClD5E,GAAI,KAAK,SAASrP,CAAE,kBAAkB,EACtC,MACF,CAGA,MAAMgT,GAAanC,EAAgB,wBAAwB7Q,CAAE,EAC7D,GAAI,CAACgT,GAAY,CACf3D,GAAI,KAAK,SAASrP,CAAE,mCAAmC,EACvD,MACF,CAGA,MAAMoU,GAAoBH,GAAM,SAAS,IAAI/Z,IAAS,CACpD,MAAMga,EAAQha,GAAM,MAAA,EACpB,OAAAga,EAAM,EAAIha,GAAM,EAAI+Z,GAAM,EAC1BC,EAAM,EAAIha,GAAM,EAAI+Z,GAAM,EACnBC,CACT,CAAC,EAGDhD,EAAqB+C,EAAK,EAC1BG,GAAkB,QAAQla,IAAS+W,EAAkB/W,GAAO8Y,EAAU,CAAC,EAGnEoB,GAAkB,OAAS,GAC7BtD,EAAsBsD,GAAkB,CAAC,EAAE,EAAE,EAE/CpD,EAAkB,CAAA,CAAE,CACtB,EAAG,CAACvB,EAAUoB,EAAiBK,EAAsBD,EAAmBH,EAAuBE,CAAiB,CAAC,EAG3GqD,GAAsBpH,EAAAA,YAAY,MAAOjQ,EAAe2T,IAAgB,CAG5E,MAAM2D,EAAsB,MAAO7E,IAA8B,CAC/D,UAAW7B,MAAW6B,GAAU,CAC9B,MAAMkC,GAAU,MAAMrC,GAA4B1B,GAAS5Q,EAAM4T,CAAsB,EACvFhB,EAAa,QAAQ,IAAIhC,GAAQ,GAAI+D,EAAO,EAExC/D,cAAmBI,EAAAA,cAAgBJ,GAAQ,SAAS,OAAS,GAC/D,MAAM0G,EAAoB1G,GAAQ,QAAsC,CAE5E,CACF,EACA,MAAM0G,EAAoBzC,CAAgB,EAG1CN,EAAkBK,IAAKA,GAAI,CAAC,CAC9B,EAAG,CAACjB,EAAaC,EAAwBiB,CAAgB,CAAC,EAGpD0C,GAAoBtH,EAAAA,YAAY,IAAM,CAC1C2C,EAAa,QAAQ,MAAA,CACvB,EAAG,CAAA,CAAE,EAKC4E,GAAcvH,EAAAA,YAClB,CAACwH,EAAmBC,EAAkBpB,KAAiC,CACrEnC,EAAsBsD,EAAWC,EAAUpB,EAAQ,CACrD,EACA,CAACnC,CAAqB,CAAA,EAIlBwD,GAAqB1H,EAAAA,YACzB,CAACwH,EAAmBC,EAAkBpB,GAA8BsB,KAAoB,CACtF,MAAMX,GAAQxE,EAAS,KAAKO,GAAMA,EAAG,KAAO4E,EAAO,EACnD,GAAI,CAACX,IAAS,EAAEA,cAAiBjG,EAAAA,cAAe,CAC9CqB,GAAI,KAAK,SAASuF,EAAO,wBAAwB,EACjD,MACF,CAEA,MAAMC,GAAWZ,GAAM,MAAA,EACjBa,EAAeD,GAAS,SAAS,UAAUE,GAAKA,EAAE,KAAON,CAAS,EAClEO,EAAcH,GAAS,SAAS,UAAUE,GAAKA,EAAE,KAAOL,CAAQ,EACtE,GAAII,IAAiB,IAAME,IAAgB,GAAI,CAC7C3F,GAAI,KAAK,4CAA4CoF,CAAS,KAAKC,CAAQ,EAAE,EAC7E,MACF,CAGA,KAAM,CAACO,CAAO,EAAIJ,GAAS,SAAS,OAAOC,EAAc,CAAC,EACpDI,EAAiBL,GAAS,SAAS,UAAUE,GAAKA,EAAE,KAAOL,CAAQ,EACnES,GAAc7B,KAAa,SAAW4B,EAAiBA,EAAiB,EAC9EL,GAAS,SAAS,OAAOM,GAAa,EAAGF,CAAO,EAEhDlE,EAAoB8D,EAAQ,CAC9B,EACA,CAACpF,EAAUsB,CAAmB,CAAA,EAI1BqE,GAAkBnI,EAAAA,YACrBjN,GAAqC,CAEpC,UAAWgQ,KAAMP,EAAU,CACzB,GAAIO,EAAG,KAAOhQ,EAAI,OAAOgQ,EAEzB,GAAIA,aAAchC,EAAAA,aAAc,CAC9B,MAAM9T,GAAQ8V,EAAG,SAAS,KAAK+E,IAAKA,GAAE,KAAO/U,CAAE,EAC/C,GAAI9F,GAAO,OAAOA,EACpB,CACF,CACA,OAAO,IACT,EACA,CAACuV,CAAQ,CAAA,EAIL4F,GAAkBpI,EAAAA,YACrBjN,GAAoC,CACnC,UAAWgQ,KAAMP,EACf,GAAIO,aAAchC,EAAAA,cACZgC,EAAG,SAAS,SAAc9V,GAAM,KAAO8F,CAAE,EAC3C,OAAOgQ,EAIb,OAAO,IACT,EACA,CAACP,CAAQ,CAAA,EAIL6F,GAAarI,EAAAA,YACjB,CAACyE,EAAmBkD,IAAoB,CACtC,MAAMhH,GAAU6B,EAAS,KAAKO,GAAMA,EAAG,KAAO0B,CAAS,EACjDuC,GAAQxE,EAAS,KAAKO,GAAMA,EAAG,KAAO4E,CAAO,EACnD,GAAI,CAAChH,IAAW,CAACqG,IAAS,EAAEA,cAAiBjG,EAAAA,cAAe,CAC1DqB,GAAI,KAAK,cAAcqC,CAAS,aAAakD,CAAO,EAAE,EACtD,MACF,CAGA,GAAI,CADe/D,EAAgB,wBAAwBa,CAAS,EACnD,OAMjB,MAAMmD,GAAWZ,GAAM,MAAA,EACjBsB,EAAa3H,GAAQ,MAAA,EAC3B2H,EAAW,EAAI3H,GAAQ,EACvB2H,EAAW,EAAI3H,GAAQ,EACvBiH,GAAS,SAASU,CAAU,EAG5BrE,EAAqBtD,EAAO,EAC5BmD,EAAoB8D,EAAQ,CAC9B,EACA,CAACpF,EAAUoB,EAAiBK,EAAsBH,CAAmB,CAAA,EAMjEyE,GAAkBvI,EAAAA,YACtB,CAACyE,EAAmBgD,EAAmBpB,KAAkC,CACvE,MAAMmC,GAAcJ,GAAgB3D,CAAS,EAC7C,GAAI,CAAC+D,GAAa,CAChBpG,GAAI,KAAK,WAAWqC,CAAS,wBAAwB,EACrD,MACF,CAEA,MAAMxX,GAAQub,GAAY,SAAS,KAAKV,GAAKA,EAAE,KAAOrD,CAAS,EAC/D,GAAI,CAACxX,GAAO,OAEZ,MAAM8Y,GAAanC,EAAgB,wBAAwB4E,GAAY,EAAG,EAC1E,GAAI,CAACzC,GAAY,OAGjB,MAAM0C,EAAWxb,GAAM,MAAA,EAGjB2a,EAAWY,GAAY,MAAA,EAC7BZ,EAAS,YAAYnD,CAAS,EAE9BX,EAAoB8D,CAAQ,EAGxBH,GAAYpB,GAIdrC,EAAkByE,EAAU1C,GAFRM,KAAa,QAAUoB,EAAW,OACjCpB,KAAa,SAAWoB,EAAW,MACS,EAGjEzD,EAAkByE,EAAU1C,GAAY,OAAWyC,GAAY,EAAG,EAEpE3E,EAAsB4E,EAAS,EAAE,CACnC,EACA,CAACL,GAAiBxE,EAAiBK,EAAsBH,EAAqBE,EAAmBH,CAAqB,CAAA,EAIlH6E,GAAmB1I,EAAAA,YAAY,IAAM,CACzC,MAAM+F,EAAanC,EAAgB,oBAAA,EACnC,GAAI,CAACmC,EAAY,CACf3D,GAAI,KAAK,yCAAyC,EAClD,MACF,CAEA,MAAM4E,EAAQ,IAAIjG,eAAa,CAC7B,EAAG,EACH,EAAG,EACH,KAAM,WAAA,CACP,EAEDiD,EAAkBgD,EAAOjB,CAAU,EACnClC,EAAsBmD,EAAM,EAAE,CAChC,EAAG,CAACpD,EAAiBI,EAAmBH,CAAqB,CAAC,EAE9D,MAAO,CACL,OAAAR,EACA,WAAA0B,EACA,eAAAC,EACA,YAAAC,EACA,qBAAAG,EACA,eAAAC,GACA,iBAAAC,EACA,WAAAE,EACA,YAAAC,EACA,YAAAC,GACA,eAAAC,GACA,cAAAK,GACA,YAAAuB,GACA,mBAAAG,GACA,iBAAApB,GACA,kBAAAE,EACA,YAAAC,GACA,aAAAS,GACA,WAAAmB,GACA,gBAAAE,GACA,iBAAAG,GACA,gBAAAP,GACA,gBAAAC,GACA,oBAAAhB,GACA,kBAAAE,GACA,WAAY9C,EACZ,gBAAAZ,EACA,WAAYmB,EAAW,MAAA,CAE3B,CCzvBA,SAAS4D,GACPC,EACAC,EACS,CAOT,GALIA,aAA8BC,EAAAA,cAAgBD,EAAmB,YAKjED,aAA2BE,EAAAA,cAAgBF,EAAgB,WAC7D,MAAO,GAIT,GAAIA,aAA2B7H,EAAAA,cAC7B,UAAW9T,KAAS2b,EAAgB,SAClC,GAAI3b,aAAiB6b,EAAAA,cAAgB7b,EAAM,WACzC,MAAO,GAKb,MAAO,EACT,CAQO,SAAS8b,GAAqBnI,EAAiC,CACpE,MAAMoI,GAAUpI,GAAA,YAAAA,EAAS,UAAW,GAC9B,CACJ,gBAAAgI,EACA,mBAAAC,EACA,eAAAnG,EACA,SAAAF,EACA,oBAAAsB,EACA,mBAAAmF,EACA,oBAAAC,EACA,qBAAAjF,EACA,sBAAAJ,EACA,kBAAAE,EAEA,mBAAAoF,EACA,mBAAAC,EACA,eAAAC,EACA,iBAAAC,EACA,kBAAAC,CAAA,EACEpF,aAAA,EAEE,CAAE,eAAAwB,CAAA,EAAmBpC,GAAA,EAE3BjF,EAAAA,UAAU,IAAM,CACd,GAAI,CAAC0K,EAAS,OAEd,MAAMQ,EAAiBC,GAAqB,OAE1C,MAAMzQ,EAASyQ,EAAE,OACjB,GAAI,EAAAzQ,EAAO,UAAY,SAAWA,EAAO,UAAY,aAGjD,GAAAqF,EAAArF,EAAO,UAAP,MAAAqF,EAAA,KAAArF,EAAiB,sDAGrB,KAAKyQ,EAAE,SAAWA,EAAE,UAAYA,EAAE,MAAQ,KAAO,CAACA,EAAE,SAAU,CAC5DA,EAAE,eAAA,EACFN,EAAA,EACA,MACF,CAGA,IAAMM,EAAE,SAAWA,EAAE,UAAYA,EAAE,UAAYA,EAAE,MAAQ,KAASA,EAAE,SAAWA,EAAE,MAAQ,IAAM,CAC7FA,EAAE,eAAA,EACFL,EAAA,EACA,MACF,CAGA,IAAKK,EAAE,SAAWA,EAAE,UAAYA,EAAE,MAAQ,IAAK,CAC7CA,EAAE,eAAA,EACFR,EAAA,EACA,MACF,CAGA,IAAKQ,EAAE,SAAWA,EAAE,UAAYA,EAAE,MAAQ,IAAK,CAC7CA,EAAE,eAAA,EACFP,EAAA,EACA,MACF,CAGA,IAAKO,EAAE,SAAWA,EAAE,UAAYA,EAAE,MAAQ,IAAK,CAC7CA,EAAE,eAAA,EACEb,EACFjD,EAAeiD,EAAgB,EAAE,EACxBlG,EAAe,OAAS,GAEjCA,EAAe,QAAS3P,GAAO,CAC7B4S,EAAe5S,CAAE,CACnB,CAAC,EAEH,MACF,CAGA,GAAI0W,EAAE,MAAQ,SAAU,CACtBA,EAAE,eAAA,GAEEb,GAAmBlG,EAAe,OAAS,KAC7CmB,EAAsB,IAAI,EAC1BE,EAAkB,CAAA,CAAE,GAEtB,MACF,CAGA,GAAI0F,EAAE,MAAQ,KAAO,CAACA,EAAE,SAAW,CAACA,EAAE,SAAW,CAACA,EAAE,UAAY,CAACA,EAAE,OAAQ,CACzEA,EAAE,eAAA,EACFH,EAAiB,MAAM,EACvB,MACF,CAIA,GAAIG,EAAE,MAAQ,UAAYA,EAAE,MAAQ,YAAa,CAO/C,GALIF,GAKAZ,GAAoBC,EAAiBC,CAAkB,EACzD,OAQF,GALAY,EAAE,eAAA,EAKE/G,EAAe,OAAS,EAAG,CACJF,EAAS,OAAQxG,GAAM0G,EAAe,SAAS1G,EAAE,EAAE,CAAC,EAC5D,QAAS2E,GAAY,CACpCsD,EAAqBtD,CAAO,CAC9B,CAAC,EACDoD,EAAkB,CAAA,CAAE,EACpBF,EAAsB,IAAI,EAC1B,MACF,CAGI+E,IACF3E,EAAqB2E,CAAe,EACpC/E,EAAsB,IAAI,GAE5B,MACF,CAGA,GAAI4F,EAAE,MAAQ,WAAaA,EAAE,MAAQ,aAAeA,EAAE,MAAQ,aAAeA,EAAE,MAAQ,aAAc,CAEnG,GAAI/G,EAAe,OAAS,EAAG,CAC7B+G,EAAE,eAAA,EAGFJ,EAAe,EAAI,EAGnB,MAAMK,EAAYD,EAAE,SAAW,GAAK,EAEpC,IAAIE,EAAK,EACLC,EAAK,EAELH,EAAE,MAAQ,cAAaE,EAAK,CAACD,GAC7BD,EAAE,MAAQ,eAAcE,EAAKD,GAC7BD,EAAE,MAAQ,YAAWG,EAAK,CAACF,GAC3BD,EAAE,MAAQ,cAAaG,EAAKF,GAGPlH,EAAS,OAAQxG,IAAM0G,EAAe,SAAS1G,GAAE,EAAE,CAAC,EAC5D,QAAS2E,IAAY,CACpC,MAAMkJ,EAAiBlJ,GAAQ,MAAA,EAC/BkJ,EAAe,GAAKF,EACpBE,EAAe,GAAKD,EACpB9F,EAAoB+F,CAAc,CACpC,CAAC,EAED,MACF,CAGA,GAAIjB,EAAiB,CACnBa,EAAE,eAAA,EAGFJ,EAAe,EAAI,EAGnB,MAAMK,EAAYD,EAAE,SAAW,GAAK,EAEpC,IAAIE,EAAK,EACLC,EAAK,EAELH,EAAE,MAAQ,cAAaE,EAAK,CAACD,GAC7BD,EAAE,MAAQ,eAAcE,EAAKD,GAC7BD,EAAE,MAAQ,YAAWG,EAAK,CAACF,GAC3BD,EAAE,MAAQ,cAAaG,EAAKF,GAEhC,MAAMG,EAAiBjB,EAAgB,MAAA,EACvCiB,EAAe,GAAKF,EACpBE,EAAe,GAAKD,EACpB9F,EAAoB+F,CAAc,EAElC,MACF,CACF,EACF,EAEMC,EAAeL,GAAqB,EAEpCA,EAAE,MAAQ,WAAaA,EAAE,MAAQ,aAAeA,EAAE,MAAQ,aAAeA,EAAE,MAAQ,eACrFJ,EAAe,EAAK,CAExB,EAEA,cAAO,iBAAiB,UAAWG,CAAa,EAChD,OAAO,iBAAiB,QAASM,CAAW,EACrC,IAAM,CACX,OAAO,oBAAoB,UAAWN,CAAa,EACnD,OAAO,oBAAoB,QAASM,CAAW,CACjD,CACF,EAAG,CACDd,EACAJ,EACAC,EACAnG,EACAF,EACAsB,EACAmF,EACAC,EACAjF,EACAJ,EACAE,EACAoF,EACAC,EACAC,EACA1D,EACA4D,CAAA,CACD,CACH,CCxPA,MAAMQ,GAAsB,IACtBC,GAAgB,GAAsB,EAAI,KAAK,IAAI,EAAI,EAAG,CAAC,EAE1D,SAASC,GACdjR,EACAkR,EAAqBH,GACH,CAClB,KAAM,CAACI,EAAMC,CAAO,EAAIvM,EAAAA,SAA2B7E,CAAM,EACnDqR,EAAUjG,EAAAA,OAAyB+F,CAAI,EAC7CE,EAAQ,QAAUF,EAClB,MAAMG,EAASlG,EAAAA,OAAsB,IAAI,EAEnCmG,EAAYvR,EACd,GAAGA,EAAO,CAAC,IAAIA,EAAO,CAAC,IAAIA,EAAO,KAAK,IAAIA,EAAO,MAAM,GACxD,OAEJsF,OAAAA,EAAAA,UAAU,IAAM,CAMd,GALIgM,EAAO,UAAY,OACrB,qBAAqBA,EAAO,OAAO,EACnCA,EAAO,QAAU,MAGf,CAACtR,EAAQ,CACXoR,EAAQ,IAAI,EACZ,MACF,CAEA,MAAMI,EAAOH,EAAQ,QACrB,GAAI,CAACG,EAAM,CACTJ,EAAQpR,CAAM,EACd,MACF,CAEA,GACEwR,EAAK,IAAMxR,EAAO,GAClBwR,EAAK,IAAMxR,EAAO,GAClBwR,EAAK,QAAUxR,EAAO,OACtBwR,EAAK,SAAWxR,EAAO,OAEvB,OAGF,MAAMpI,EAAQ,YAAY,IAAA,EACpB6Z,EAAQC,GAAgB,CAC5B,MAAMC,EAAI,KAAK,IAAI,GAAID,EAAM9Z,GAASsZ,CAAU,EAC1CU,EAAQZ,GAAaW,CAAC,EAC5BP,EAAQ,CACN,EAAGI,EAAK,GAAKxR,EAAO,EAAIwR,EAAK,GAAKI,EAClC,EAAGJ,EAAK,GAAKxR,EAAO,EAAIwR,EAAK,GAAKI,EAClC,MAAOJ,EAAK,OAASxR,EAAO,MAAQwR,EAAK,OAASI,EAClD,OAAQJ,EAAK,QAAUxR,EAAO,OAASwR,EAAK,QAAUI,CAAA,CACvD,EACGD,EAAI,EACNL,EAAO,QAAU,sBAAsBG,CAAI,EAE3CH,EAAO,QAAU,IAErB,EACA,OAAAA,EAAO,QAAU,sBAAsBG,CAAI,EAEpC,IAAM,CACPH,EAAO,UAAY,OACrB,qBAAqBA,EAAO,OAAO,EACnCA,EAAO,QAAU,KAErB,CACF,EAAG,CAACC,EAAWL,CAAU,CAAC,EAEnBC,CACT,CC5EO,SAASU,GACdC,EACAna,EACgC,CAChC,GAAIma,EAAK,SAAW,EAClB,MAAO,CAAE,MAAO,EAAG,IAAK,CAAA,EAI1B,MAAMC,EAAe,KAAK,IAAI,EAAG,KAAK,IAAIpa,EAAOma,EAAK,OAAS,CAAC,CAAC,EAG3DE,EAAY,QAGlB,GAAI,CAACA,EAAU,KAAKF,EAAKC,CAAY,CAAC,EAAG,CAEvC,IAAIE,EAAaF,EACjB,KAAOE,EAAaH,EAAK,QAAU,CAACE,EAAU,KAAKF,EAAKG,CAAU,CAAC,GACjEA,IAEF,GAAIA,EAAaH,EAAK,OAAQ,CAE5B,IAAII,EAAWD,EACf,KAAOC,EAAWJ,EAAK,QAAUE,EAAU,KAAKF,EAAKI,CAAQ,CAAC,GAC5DA,IAEF,MAAO,CAAE,MAAOD,EAAY,IAAKC,CAAA,CACnC,CAGA,IAAIC,EAAUJ,EACd,KAAOI,EAAU,GAAK,CAACH,EAAU,KAAKF,EAAKK,EAAU,CAAC,CAAC,GACrDA,IAEF,GAAIA,EAAU,EAAG,CACf,IAAIC,EAAYD,EAChB,KAAOC,EAAY,GAAKJ,EAAU,KAAKF,EAAKM,EAAY,CAAC,CAAC,GACxDA,IAEF,MAAO,CAAE,MAAOA,EAAW,IAAKD,CAAA,CAClC,CAGA,MAAO,CAAE,MAAOJ,EAAc,IAAKA,CAAA,CACrC,CAGA,IAAIna,EAAQma,EACZ,KAAOna,EAAQ,GAAKoa,EAAU,KAAKF,EAAKla,EAAQ,CAAC,CAAC,GAChDA,IAIF,IAAIC,EAAMka,EACV,KAAOla,EAAMia,EAAK,QAAUE,EAAU,KAAKF,EAAKja,CAAG,CAAC,GAClDA,IAGF,MAAO,CAAE,MAAAD,EAAO,IAAAC,CAAA,CAClB,CAKO,SAASwa,GACd1K,EACA2K,EAC2F,CAC3F,KAAM,CAAE,SAAAC,EAAU,WAAAC,EAAY,KAAAC,EAAM,OAAAC,EAAQ,UAAAC,GAAchL,EACpDiL,EAAWjL,EAAQ,YAAA,EAGnBkL,EADalL,EAAQ,qBAAA,EACC,MAE5B,IAAImL,EAAgBD,EAChBE,EAAoB,EAEpBpL,EAAQ,gBAAkB,WAC5BoL,EAAoBC,EAAAA,mBACpBF,EAAgBD,EAAYE,EAAoB,GAGlD,MAAME,EAAQC,EAAAA,SACZvL,EAAQ,QAAA,EACRmL,EACAP,EACAC,EACAC,EACAC,EACA,OACA,EAAA,EAIF,IAAIS,EAAiB,EACjBC,EAAY,EAEhB,QAASC,EAAI,EAAGA,EAAIJ,EAAM,OAAQI,IAAK,CACrC,MAAMC,EAAeH,EAAiBF,EAAMI,CAAC,EAAE,OAC/C,GAAIf,GAAegB,EAAc,CAC/BF,EAAYC,EACZ,KACF,CACAF,GAAkBF,EAAMI,CAAC,EAAE,OAAS,EAChCA,IAAMJ,EAAM,OAAS,IACvBG,EAAYC,EAEhB,CAGA,MAAME,EAAON,EAAMG,CAAS,EACtBI,EAAYlB,EAAca,EAGhC,IAAIM,EAAkBF,EAClBG,EAAqB,EAEzB,MAAMC,EAAmBP,IAAc,EACjCQ,EAAiBR,IAAcH,EAAM,OAAS,EAEpD,GAAI,CAACU,EAAkB,CACrB,MAAME,EAAqBN,EAAK,MAAM,KAAK,EAC3CG,EAAqBG,EAAqBA,EAAmB,CAAC,EAAE,OAAS,EACrEH,EAAqB,IACvBD,EAAkBA,EAAgB,UAAUC,CAAkB,EAElE,CAEA,GAAI,CAACE,EAAgB,CACnB,MAAME,EAAsBL,EAAgB,MAAM,KAAK,EACjDM,EAAsBD,EAAsBA,EAAoB,CAAC,EAAE,OAAS,EAC9EC,EAAsB,IACxBN,EAAkBA,EAAgB,UAAU,EAAGA,EAAgB,OAASM,CAAmB,EAE/F,CAEA,MAAMC,EAAYC,EAAAA,iBAAiBR,EAAiBlB,EAAUC,EAAYC,EAAMC,CAAM,EAEtF,IAAIwB,EAAanB,EACbJ,IAAc,SAChBuB,GAAcrB,EAAWmB,GAAa,EAC7BrB,IAAc,UACvBuB,EAAarB,EAAWE,EAAoBiB,GAI9C,IAAIG,EAAYD,EAChB,MAAME,EAAa,KAAK,IAAIZ,EAAWD,EAAK,MAAM,EAElD,QAASF,EAAI,EAAGA,EAAIe,EAAYf,IAAK,CACnC,GAAI,CAACM,GAAoBN,EAAIK,EAAoB,SAEjD,MAAMW,EAAOd,EAAKF,CAAC,EACbiB,GAAcnB,EAAiBE,EAC/BtP,EAAQ6O,EAAS,WAAW0B,EAAW,EAEvCC,EAAexQ,EAAM,UAAYwO,EACjCiC,EAAiBzQ,EAAM,YAAcyO,EACrCiC,GAAW1Q,EAAM,OAAS,OAAYA,EAAM,KAAO0O,EACnDiC,GAAa3Q,EAAM,SAAW,OAAYA,EAAM,OAAS2O,EAE/DyB,GAAaF,EAAAA,iBAAiBI,EAAME,EAAcC,EAAgBC,GAAUC,EAAU,CACxF,CAEA,MAAO,CACL,UAAAtB,EACA,UAAWH,EAAM,OACjB,gBAAiBkB,EACjB,eAAgBhB,CAAA,CAEpB,CAKO,SAASwB,GACdhN,EACA2K,EACAsC,EACQ,CACR,KAAM,CAAE,SAAArC,GAAa5K,EAEfkN,EAActC,EADD,IAGbuC,EAAOzC,GAAkB1K,EAAS2K,CAAW,EAG7CyC,EAAkBH,IAAc,KAClC,KAAK,IAAI,EAAGE,EAAK,UAAY,CAAC,EAC9B,KAAK,IAAIA,EAAK,UAAY,EAAGA,EAAK,UAAY,CAAC,EAGnD,GAAIC,IAAoBD,EAAK,UAC3B,OAAOxC,EAIT,MAAM0C,EAAUD,EAAkBF,EAAcA,EAAc,EAE9D,OAAOI,GAAwBtN,EAAS,CACtC,EAAGmN,EAAK,gBACR,EAAGE,CAAA,CACJ,CACH,CAUO,SAASC,GACdtN,EACAuN,EACQ,CACR,KAAM,CAAE,SAAA3C,EAAU,WAAAC,EAAY,KAAAC,EAAM,OAAAC,EAAQ,UAAAC,GAAchL,EACpDiL,EAAWjL,EAAQ,YAAA,EAKnBkL,EADalL,EAAQ,qBAAA,EACC,MAE5B,IAAImL,EAAgBD,EAChBE,EAAoB,EAGpBpL,EAAQ,gBAAkB,WAC5BoL,EAAoBC,EAAAA,mBACpBF,EAAgBD,EAAYE,EAAoB,GAMlD,MAAME,EAAQC,EAAAA,SACZvL,EAAQ,QAAA,EACRmL,EACAP,EACAC,EACAC,EACAC,EACA,OACA,EAAA,EAKImC,EAActC,EADD,IAWnB,IAAIa,EAAY,EACZ+B,EAAW,EAGf,GAAID,EAAM,EAAI,EACZ,MAAO,GAGT,QAAS7B,EAAI,EAAGA,EAAIJ,EAAM,OAAQI,IAAK,CAGrC,GAAI6B,EAAM,GAAKC,GAAYD,EAAM,EAAIC,EAAWN,EAAa,CAC3DzB,EAAYC,EACZ,KACF,CAEA8B,GAAYN,EAIRxB,IAAMJ,EAAM,OAAS,IACvBG,EAAYC,EAEhB,CAIA,IAAIF,EAAiB,EACrB,QAASE,EAAI,EAAGA,EAAID,EAAWC,IAC7BF,GAAkBF,EAAMI,CAAC,EAAE,OAAS,EAItC,MAAME,EAAON,EAAMG,CAAS,EAI5B,IAAIK,EAAkBF,EAClBG,EAAqB,EACrBK,EAAsB,EAE1B,MAAMJ,EAAmBP,IAAc,EACjCQ,EAAiBR,IAAcH,EAAM,OAAS,EAGpD,GAAI,CAACU,EAAkB,CACrB,MAAME,EAAqBN,EAAK,MAAM,KAAK,EAC3CG,EAAqBG,EAAqBA,EAAmB,CAAC,EAAE,OAAS,EACrEH,EAAqB,IACvBD,EAAkBA,EAAgB,UAAUC,CAAkB,EAElE,CAGA,GAAI,CAACE,EAAgB,CACnB,MAAME,EAAsBL,EAAgB,MAAM,KAAK,EACvDM,EAAsBD,EAAsBA,EAAoB,CAAC,EAAE,OAAS,EACxEC,EAAsB,IACxBN,EAAkBA,EAAgB,UAAU,EAAGA,EAAgB,OAASM,CAAmB,EAE/F,CAEA,MAAMC,EAAYC,EAAAA,iBAAiBR,EAAiBlB,EAAUC,EAAYC,EAAMC,CAAM,EAGtF,IAAIwB,EAAanB,EACbJ,IAAc,SAChBuB,GAAcrB,EAAWmB,GAAa,EAC7BrB,IAAc,UACvBuB,EAAarB,EAAWE,EAAoBiB,GAI9C,MAAMoB,EAAYF,EAAM,EAAIhB,EAG5B,GAAIkB,GAAa,EAEf,OAAOjC,EAAiBO,EAI1B,GAAI0B,GAAapB,EAEf,OAAOb,EAAiBI,EAAK,OAASQ,EAIxC,IAAIsB,GAAW,EAGf,QAAShC,EAAI,EAAGA,EAAIE,EAAK,OAAQF,IAAK,CAGpC,GADI,CAACM,GAAoBN,EAAIK,GACzB,CAACE,GAAkBP,GAAKE,EAAK,OAASQ,EAAqB,SAE/D,MAAMM,EAAOd,EAAKF,CAAC,EACbiB,EAAcnB,EAAiBE,EAG/BtP,GAAQ6O,EAAS,WAAW0B,CAAW,EAGvCC,GAAexQ,GAAM,UAAYwO,EACjCiC,GAAiBzQ,GAAM,YAAcyO,EACrCiC,GAAW1Q,GAAM,OAAS,OAAYA,GAAM,KAAO0O,EACnDiC,EAAa3Q,GAAM,SAAW,OAAYA,GAAM,OAAS2O,EAEzD4C,GAAYrB,EAAAA,iBAAiBI,EAAME,GAAcC,GAAgBC,GAAUC,CAAU,EAI3F,GAAIU,EAAYC,GAAWC,GAAY,EACrC,OAAOhB,EAGTe,IAAYC,EACd,CAGA,OAAOnC,EAAiBI,EAAK,MAC/B,CC9XO,SAASgC,IAAsB,CACpC,KAAM,CAACC,EAAoBC,CAAqB,EAAI5Q,EAAAA,SAAkB,EAAK,EACrE,CAAC6Q,EAAcC,CAAe,EAAI9Q,WAAmC,CACzE,EAAG,EACH,EAAG,CAAA,CACJ,EACK,CAAC+Q,EAAYC,CAAa,EAAIhR,WAAmC,CACrE,EAAG,EACH,EAAG,CAAA,CACJ,EACK,CAACiR,EAAyBC,CAA0B,EAAIlR,EAAAA,SAAmB,CAAA,CAAE,EAGnFS,OAAAA,EAAAA,UAAU,IAAM,CAChB,EAAG,CAACkQ,CAAkB,CAAC,EAoEhB,CACL,mBAAAA,EACA,aAAAE,EACA,WAAAE,EACA,wBAAAE,EACA,sBAvE4B,CAAChN,EAAWC,IAAc,CACtD0M,EAAsB,EAAI,EAC1BE,EAAgB,CAAE,EAAA7M,EAAG,EAAAC,EAAG,EACxB8M,EAAc,CAAE,EAAA/M,EAAG,EAAAC,EAAG,EACtBgN,EAA2B,CAAA,CAAE,CAC/B,EAmEE,uBAjE6B,CAC7BjN,EACAC,EACAS,IACG,CACHqM,EAAc,CAAE,EAAA/M,EAAG,EAAAC,EAAG,EAGtB,MAAM+E,EAAO,KAAK,IAAI4H,EAAa,EAAG5M,CAAC,EACjCkN,EAAO,KAAK,IAAIN,EAAa,EAAG5M,CAAC,EACjCiF,EAAO,KAAK,IAAI2H,EAAa,EAAG3M,CAAC,EACjCkN,EAAO,KAAK,IAAIP,EAAa,EAAG3M,CAAC,EAEjCmN,EAA+B,CAAA,EACrC1M,EAAS,QAAS7B,GAAY,CAE5B,GAAIA,EAAQ,UAAY,GAAO,OAE/B,MAAMO,EAAOP,EAAQ,qBAAA,EAGfwO,EAAcjO,EAAK,EACnBkO,EAAelO,EAAK,EAAIA,EAAK,MAC7BmO,EAAanO,EAAK,EAClBoO,EAAgBpO,EAAK,EAAIA,EAAK,OAEnB,EACfkO,EAAetI,GACfqI,EAAcH,GACdM,EAAgBvI,GAChBsI,EAAaJ,IAIbC,EAAmB,KAAKvO,EAAQ,EAAE,CAEtC,CAAC,EAEDoO,EAA2BG,CAAkB,CAC/C,EA2BE,uBAxBAK,GACG,CAGH,MAAMC,EAAc,CAAC,GAAGV,CAAuB,EAE/CL,EAAsB,EAAK,EAGvBe,EAAY,OAAS,GACvBD,EAAuBC,CAAW,EAIpCT,EAA2B,CAAA,CAAE,CAC/B,CASE,CAEJ,CChFO,MAAMU,EAAe,CAArB,aAAA,CACL,KAAQ,MAAsB,CAC5B,OAAQ,GACR,YAAa,KACb,OAAQ,CAAA,EACR,aAAc,KACd,iBAAkB,GAClB,aAAc,KACd,UAAW,GACX,gBAAiB,IAAA,CACnB,CAKA,UAAUC,EAAgC,CACxC,KAAK,MAAM,OAAS,GACpB,KAAK,MAAM,YAAcA,EACzB,KAAK,MAAM,OAAS,CAAA,EACpB,KAAK,MAAM,aAAe,KAC1B,KAAK,MAAM,iBAAmB,GAC9B,KAAK,MAAM,aAAe,KAC1B,KAAK,MAAM,UAAY,GACvB,KAAK,MAAM,gBAAkB,IAC/B,CAMA,SAASA,EAAgC,CACvC,KAAK,MAAM,OAAS,GACpB,KAAK,MAAM,YAAcA,EAEzB,KAAK,MAAM,OAASA,EAAY,cAAc,OAAO,IAAKC,IAAO,CAAE,GAAGA,CAAA,EAAI,EAC1E,KAAK,MAAM,aAAe,KAC1B,KAAK,MAAM,iBAAmB,GAC9B,KAAK,MAAM,aAAe,KAC1B,KAAK,MAAM,UAAY,GACvB,KAAK,MAAM,gBAAkB,IAC/B,CAKA,UAAoB,CAClB,OAAO,KAAK,MAAM,MACpB,CAKA,UAAyB,CACvB,MAAO,CAAE,GAAG,KAAK,KAAA,CACnB,CAKA,gBAAgB7N,EAAWC,EAAiB,CAC1C,KAAK,MAAM,aAAe,CAAE,EAAAD,EAAG,EAAAC,CAAA,CACjC,CAKA,mBAA0B,CACxB,KAAK,MAAM,aAAe,IAC5B,CAMA,SAASD,EAAWC,EAAsB,CACxC,MAAM6N,EAAsB,CAC1B,GAAI,SAAS,KAAK,IAAA,CAAK,IAAI,KAAK,QAAQ,GACxC,EAAA9N,EACA,EAAAC,EACA,KAAM,QAAA,EAGR,YAAK,MAAM,OAAO,KAAK6N,CAAQ,EAC/B,KAAK,MAAM,aAAeA,EAC1B,KAAK,MAAM,iBAAmB,GAEvBA,CACT,CAKA,qBAA4B,CACrB,KAAK,MAAM,eAChB,KAAK,MAAM,iBAAmB,GAG1B,KAAK,MAAM,aAAa,OAAS,WACnC,KAAK,MAAM,aAAa,KAAO,UAEnC,CAKA,aAAaC,EAAiBC,EAAuB,CACnD,GAAI,GAAC,KAAK,MAAM,cAAgB,CAAC,KAAK,MAAM,kBAE5C,GAAI,KAAK,MAAM,WAAa,KAAK,MAAM,gBAAiB,CAEtD,MAAMnO,EAAUkO,EAAU,KAAK,MAAM,gBAAgB,EAC/CjO,EAAUkO,EAAU,KAAK,MAAM,gBAAgB,EAE/CC,EAAa,KAAK,MAAM,OAAO,CAAC,EAGtCA,EAAW,SAAW,CAAE,EAAGpO,EAAS,EAAGC,CAAA,EAGvCmO,EAAW,UAAY,CAAE,EAAG,CAACpO,EAAS,EAAG,CAACC,CAAA,EAEtCmO,EAAW,OAAS,WACtBA,EAAW,KAAO,SAEtB,KAAO,CAEL,MAAMpO,EAAUkO,EAAU,KAAK,MAAM,aAAa,EAC5CjO,EAAUkO,EAAU,KAAK,MAAM,aAAa,EAGlD,KAAK,MAAM,aAAa,UAAY,CAAE,EAAGnO,EAAS,EAAGC,CAAA,EAGjD,KAAK,MAAM,aAAa,OAAS,SACnC,KAAK,MAAM,aAAa,SAAW,CAAE,EAAG,CAACD,EAAS,EAAG,CAACC,CAAA,EAC7C,KAAK,MAAM,aAAa,OAAS,WAG1C,KAAK,MAAM,aAAa,SAAW,CAAE,EAAG,CAACD,EAAS,EAAG,CAACC,CAAA,EAE1D,CACF,CAMA,sBAA6B,CAC3B,KAAK,MAAM,iBAAmB,GAG1B,KAAK,MAAM,UACb,KAAK,UAAA,EAEL,KAAK,MAAM,aAAe,IAE9B,CAKA,aAAaE,EAAWC,EAAWiO,EAAoB,GAAa,CAClE,GAAI,KAAK,MAAM,OAAO,OAAS,EAAG,MAAO,GAEzC,MAAMD,EAAa,KAAK,MAAM,OAAO,CAAC,EAChCpG,EAAK7H,EAAIiO,EAAW,EACpBnG,EAAK7H,EAAIgO,EAAW,EAG1B,OAFiB,KAAK,KAAKpG,EAAKA,EAAKC,EAAKA,CAAE,GAEzBoG,CACrB,CAMA,aAAaC,EAAgBC,EAAsB,CAC7C,KAAK,MAAM,OAAO,OAAS,IAE/B,KAAK,MAAM,UAAY,GACvB,KAAK,MAAM,gBAAkB,CAAE,EAAGD,EAAQ,EAAGC,CAAA,EAE7C,KAAK,MAAM,aAAe,KAAK,MAAM,OAAO,CAAC,EAC/C,CAKA,eAAyB,CACvB,OAAO,KAAK,MAAM,SACpB,CAMQ,cAAqB,CAC3B,GAAI,CAAC,KAAK,MAAM,aAAe,KAAK,MAAM,OAAO,SAAW,EAAG,OAG/D,IAAIpJ,EAAO,IACTC,EAAO,IACPiI,EAAO,KACPC,EAAO,KACT,UAAWf,KAAS,KAAK,MAAM,OAC7BpH,EAAO,KAAK,IAAIA,EAAMoH,EAAM,CAAC,EAC7BnH,EAAO,KAAK,IAAIA,EAAMmH,EAAM,CAAC,EAC7Bc,EAAO,KAAK,IAAIA,EAAMd,EAAM,CAAC,EAC7Be,EAAO,KAAK,IAAIA,EAAMf,EAAM,CAAC,EAI/B,MAAMiC,GAAWrJ,EAAOkI,GAAQ,EAC1BoB,GAAWrJ,EAAOkI,GAAQ,EAGhC,KAAK,MAAM,YAAY,GAAKkB,EAC5B,KAAK,MAAM,YAAY,GAAKC,EAG5B,UAAWlC,KAAS,KAAK,MAAM,OAC7BA,EAAM,GAAKiC,EACXjC,EAAM,GAAKkC,CAEf,CAKA,WAAkB,CACX,KAAK,MAAM,cAGhB,KAAK,aAAA,EAGL,KAAK,MAAM,YAAY,cAAc,OAAS,CAAC,GAAG,KAAK,MAAM,MAAM,EACnE,KAAK,MAAM,YAAY,cAAc,OAAS,GAG9C,KAAK,MAAM,YAAY,cAAc,YAAc,GACnD,KAAK,MAAM,YAAY,cAAc,UAAYC,EAAAA,uBAAA,EAEjD,KAAK,MAAM,YAAY,aAAA,EAGvB,KAAK,QAAA,EACP,CAKA,YAAmB,CACZ,KAAK,MAAM,cAGhB,KAAK,aAAA,EAGL,KAAK,MAAM,YAAY,cAAc,OAAS,CAAC,GAAG,KAAK,MAAM,MAAM,EACnE,KAAK,MAAM,YAAY,cAAc,OAAS,GAC9C,KAAK,MAAM,YAAY,aAAA,EAGvB,KAAK,QAAA,EACP,CAKA,iBAAwB,CAClB,KAAK,MAAM,OAAO,SAAW,IACjC,KAAK,MAAM,OAAO,IAAA,EAClB,KAAK,MAAM,aAAe,KAC5B,CAKA,YAAiC,CAC/B,MAAM1P,EAAU,KAAK,MAAM,YAC3B,YAAK,MAAM,OAAS,GACpB,KAAK,MAAM,YAAc,KACzB,KAAK,MAAM,OAAS,CAAA,EACpB,KAAK,MAAM,aAAe,KAC1B,KAAK,MAAM,iBAAmB,GAC9B,KAAK,MAAM,aAAe,KAC1B,KAAK,MAAM,UAAY,GACvB,KAAK,MAAM,gBAAkB,KACtBA,CACT,CAKQ,SAAgB,CACtB,KAAK,MAAM,OAAS,GACpB,KAAK,MAAM,YAAc,KACzB,KAAK,MAAM,OAAS,CAAA,EACpB,KAAK,MAAM,aAAe,KAC1B,KAAK,MAAM,iBAAmB,GAC9B,KAAK,MAAM,aAAe,KAC1B,KAAK,MAAM,UAAY,GACvB,KAAK,MAAM,gBAAkB,IAC/B,CAKA,WAAyB,CACvB,OAAO,KAAK,MAAM,MACpB,CAKA,iBAAoC,CAClC,OAAO,KAAK,MAAM,YACpB,CAKA,kBAA4B,CAC1B,OAAO,KAAK,MAAM,gBACpB,CAKA,cAAcP,EAA+BsP,EAAgC,qBAC3E,GAAI,CAAC,KAAK,MAAM,QAAU,KAAK,MAAM,OAAO,SAAW,EAAG,OAE1D,KAAM,CAAE,OAAAY,EAAQ,aAAAC,EAAc,aAAcC,CAAA,EAAkB,KAAK,MAcnE,GAZApQ,EAAI,KAAA,EAGJA,EAAI,UAAUsP,EAAY,EAAGA,EAAY,CAAC,EAG1CtP,EAAI,YAAcqQ,uBAAA,EAClBrQ,EAAI,UAAY,EAChBA,EAAI,YAAY,EAAE,EAClBA,EAAI,UAAA,EAGAkQ,EAAO,OAAS,EAAG,CACrB,MAAMP,EAAaO,EAAO,CAAC,EAC3BlQ,EAAI,OAAO2P,EAAW,EAAGA,EAAW,CAAC,EAErC,QAAS1D,EAAI,EAAGA,EAAIiE,EAAO,OAAQjE,IAAK,CACtC,MAAMqE,EAAYJ,EAAOjE,EAAI,CAAC,EACxBsE,EAAeL,EAAOjE,CAAC,EAK7B,GAFiBqE,EAAU,WAAaC,EAAa,SAEvC,CACZ,MAAMC,EAAOF,EAAU,KAAKrS,EAAAqS,EAAU,YAAV,YAAArS,EAAqB,IAAK,GAChDwS,EAAOH,EAAU,KAAKI,EAAAJ,EAAU,YAAV,YAAAI,EAAqB,IAAK,GAChDC,EAAOJ,EAAa,KAAKK,EAAAL,EAAa,WAAb,YAAAK,EAAuB,IAAK,GACrDC,EAAON,EAAa,KAAKO,EAAAP,EAAa,WAAb,YAAAO,EAAuB,IAAK,GAE3D9Q,EAAI,cAAcwQ,EAAMC,EAAME,EAAME,EAAMN,EAAa,EAAGA,EAAa,CAAC,CAC1E,MACEvQ,EAAI,OAAOuQ,EAAa,EAAGA,EAAa,CAAC,CAE7C,CAGA,MAAMQ,EAAW,KAAK,MAAM,WAAazB,EAAY,cAAc,OACnE,GAAIyB,GAAYb,EAAO,OAAS,EAAG,CACjC,MAAMc,EAAYd,EAAOA,EAAO,OAAS,CAAC,EACpCP,EAAaO,EAAO,CAAC,EAK3B,GAFiBc,EAAU,WAAarB,EAAW,SAErC,CACZ,MAAMa,EAAOQ,EAAU,KAAKC,EAAAD,EAAU,YAAV,YAAAC,EAAqB,IAAK,GAChDR,EAAOO,EAAU,KAAKE,EAAAF,EAAU,YAAV,YAAAE,EAAqB,IAAK,GAChDP,EAAOhB,EAAW,KAAKA,EAAAA,EAAW,WAAXA,YAAAA,EAAqB,IAAK,GACjDkB,EAAOlB,EAAW,KAAKA,EAAAA,EAAW,WAAXA,YAAAA,EAAqB,IAAK,GAEvD3P,EAAI,cAAcwQ,EAAMC,EAAME,EAAME,EAAMlB,EAAW,EAAGA,EAAW,CAAC,CACtE,MACE3P,EAAI,OAAO2P,EAAW,EAAGA,EAAW,CAAC,CAEzC,MAAWQ,GAAgB,CAAC,KAAK,MAAM,kBAErCnQ,EAAI,OAAOmQ,EAAa,EAAGA,EAAa,CAAC,EAG3CnQ,EAAI,OAAA,EAGA+Q,GAAYzB,EAAY,cAAc,cACxCtP,EAAI,UAAYsP,EAAY,cAAc,WAAaW,EAAAA,uBAAA,EACvDjQ,EAAI,YAAcsP,EAAY,cAAc,aAAe,EAC3DtP,EAAI,KAAA,EACJA,EAAI,YAAc,EAEtB,CAEAA,EAAI,YAAY,EAAE,EAGlB,QAASiM,EAAI,EAAGA,EAAIiE,EAAO,OAAQjE,IAAK,CACtC,MAAM6B,EAAQoC,EAAOjE,CAAC,EAsBtB,GArBqBA,IAAM,GAGPiE,EAAO,OAAS,IAClClQ,EAAI,YAAcmR,sBAAA,EAClBnR,EAAI,UAAY,EAChBA,EAAI,UAAA,EACJA,EAAI,IAAI8N,EAAM,EAAGA,EAAM,EAAG,EAAG,EAAG,KAAK,GAAK,CAAC,EAC3C9N,EAAI,OAAA,GAINA,EAAI,UAAYoR,6BAAA,EAChBpR,EAAI,YAAcmR,sBAAA,EAClBnR,EAAI,UAAY,EAChBA,EAAI,UAAA,EACJA,EAAI,IAAI8N,EAAM,EAAGA,EAAM,EAAG,EAAG,EAAG,KAAK,GAAK,CAAC,EAC3C9N,EAAI,KAAA,EACJA,EAAI,OAAA,EAGA8N,EAAM,SAAU,CAClB,MAAMuD,EAAYvD,EAAM,EAAIA,EAAM,SAAS,EACrCwD,EAAYxD,EAAM,EAAIA,EAAM,SAAS,EAG3C9N,EAAI,YAAcuR,yBAAA,EAClBvR,EAAI,UAAY,EAChBA,EAAI,UAAA,EACJA,EAAI,OAAO8N,EAAM,EAAGA,EAAM,CAAC,EAC3B9N,EAAI,OAAOqR,EAAWC,CAAS,EAC/BtR,EAAI,OAAA,EAGJA,EAAI,UAAYuR,yBAAA,EAChBvR,EAAI,UAAA,EACJA,EAAI,IAAIqR,EAAWC,EAAW,EAAG,EAAG,KAAK,GAAK,CAAC,EAC/CtR,EAAI,KAAA,CACN,CAEA,GAAI8N,EAAM,UAAW,CACnB,MAAM0D,EAAa1D,EAAM,EAAIA,EAAM,UAAU,EACvC2D,EAAa3D,EAAM,EAAIA,EAAM,UAAU,EAG7C9N,EAAI,YAAcuR,yBAAA,EAClBvR,EAAI,UAAY,EAChBA,EAAI,UAAA,EACJA,EAAI,OAAO8N,EAAM,EAAGA,EAAM,CAAC,EAC3B9N,EAAI,OAAOwR,EAAYC,CAAU,EACjCzR,EAAI,OAAA,EAGJA,EAAI,UAAYuR,yBAAA,EAChBvR,EAAI,UAAA,EACJA,EAAI,IAAIwR,EAAYC,EAAY,EAAG,EAAG,KAAK,GAAK,CAAC,EACjDzR,EAAI,KAAA,CACN,CACF,CAGA,GAAImQ,GAAgB,KAAK,aAAaA,EAAa,EAAGA,EAAa,CAAC,EAAG,CACrE,MAAMR,EAAaO,EAAO,CAAC,EAC3BlQ,EAAI,YAAcmR,sBAAA,EAClBnR,EAAI,UAAY,EAChBA,EAAI,UAAA,EACJA,EAAI,IAAI2P,EAAW,EAAGA,EAAW,EAAG,EAAG,EAAG,KAAK,GAAK,CAAC,EACrD3P,EAAI,OAAA,CACN,CAGA,GAAImQ,GAAgB,CAAC,KAAK,MAAM,iBAAkB,CAChD,MAAMuB,EAAaP,EAAAA,oBAAA,EAEbQ,EAAMD,EAAW,QAAQ,IAAK,EAAE,EAChCE,EAAI,SAASD,EAAI,UAAU,EAAG,CAAC,EAAG,EAAE,EACpCE,EAAI,SAASF,EAAI,UAAU,EAAG,CAAC,EAAG,EAAE,EACpC7b,EAAI,SAAS6b,EAAI,UAAU,EAAG,CAAC,EAAG,EAAE,EAC1C3R,EAAI,UAAY,QAAQ4R,CAAC,KAAKC,CAAC,KAAK/b,CAAC,SACrCkK,EAAI,YAAc0R,EAClB1R,EAAI,UAAY,EAChBA,EAAI,UAAA,EACJA,EAAI,IAAImQ,EAAa,EAAGA,EAAa,EAAG,EAAG,EAAG,KAAK,GAAK,CAAC,EACzDnQ,EAAI,KAAA,EACJA,EAAI,OAAA,CACN,CAEAA,EAAI,QAAA,CACN,CACF,CC/fA,MAAMG,GAASC,EAAAA,aAAa,YAAY,EAMjC,SAAS0R,GAAW,CAAE,gBAAAtJ,GAAoC,CAE/D,KAAM,CAACuJ,CAAO,EAAItU,EAAAA,SAAS,IAAM,IAAI4R,EAAgB,EAG/C,CAAC2C,EAAWC,CAAY,EAAIxU,EAAAA,SAAiB,WAAW,EAG9DS,OAAAA,EAAAA,UAAU,IAAM,CACd,GAAI,CAEF,MAAMgU,EAAiBC,GAAAA,qBAAqBrS,OAAC3B,GAAA,CAAK,KAAK,kBAAkB,MAAO,GAAI,OAAQ,EAAA,CAAI,CAAE,EAAE,QAClG,sBACA,EAAA,EAIIiU,EAAY;AAAA;AAAA;AAAA;AAAA,cAIVF,CAAc;AAAA;AAAA;AAAA;AAAA,cAIdA,CAAc;AAAA;AAAA;AAAA,QAGpB,KAAA,EAIIG,EAAU,sBADG,mBAAmBD,CAAS,CACC,GAGhDH,EAAa,QAAQI,CAAO,mBAAmB,CACjD,OAASja,EAAO,CACd+H,GAAO,MAAM,+BAAgC/H,CAAK,EAClD6Z,EAAa,WAAW,CAC1B,CACF,EAAG,CAAA,CAAE,EAGL/T,EAAAA,UAAU,IAAM,CAEZsK,aAA2B8J,EAAAA,aAC3B9J,EAAgB,cAAc,OAAO,SAAW,GAChD,CAACuJ,EAAQ,YAETA,EAAQ,UAAUvJ,CAAe,CAErC,EAAG,CAACA,EAAiBuJ,CAAO,CAAC,EAEtB,CACL,QAAAA,EACA,UAAAC,CAAA,CAEJ,CCjDO,SAASO,GAAe,CAC7B,SAAAnQ,EACA,gBAAAoG,EACA,iBAAAgK,EACA,oBAAAC,CACF,EAAwB,CAEtB,KAAM,CAAChK,EAAoBiK,CAAqB,EAAIjV,EAAAA,SAA8B,IAAI,EAGhFkV,EAAoB3O,EAAAA,OAAsB,IAAI,EAG9C4O,EAAoBhT,EAAAA,YACvB/S,GAA+B,CAC9B6lB,EAAsB7lB,CAAK,EAC3B4lB,GAAA,MAAAA,EAAsB5lB,EACxB,EACA,CAAC4lB,CAAmB,CAAA,EAKtBvU,OAAAA,EAAAA,UAAU,IAAM,CACd,GAAIuK,GAAsBD,aAA2B7H,eAAc,CACjE,MAAMkS,EAAerK,EAAgB,SAAS,KAAM,GAAM,EAAE,KAAOC,EAAmB,EAAE,EACpFoK,GAAgBA,IAAiBpK,IAE7BoK,aAAwBlS,EAAAA,eAC5B+R,EAAsBG,CAAY,EAElCJ,GAAA,MAAAA,EAAsBI,GACtBL,EAAiB,OAAOK,CAAY,GAG1C,CACF,EAAG,CAACzQ,EAAUqG,EAAoBD,EAAiBgK,EAAkBC,CAAmB,CAAC,EAElF,CACL,mBAAAhK,EACA,sBAAAiK,EACA,kBAAAE,EACA,kBAAAD,CAAA,CAEJ,CCxDO,SAASG,GAAc,CAAE,cAAAC,GAAqC,CAEnE,KAAM,CAACC,EAAYC,CAAqB,EAAIxV,WAA0B,CACpE,KAAM,KACN,KAAM,KACN,UAAW,IAAA,CACZ,EAGKyV,EAAgBtT,cAAa7B,GAAiD,CAClFkV,EAAuBE,GAAc,aAEnC,MAAMC,EACJrV,EAAS,OAASoV,EAAU,MAC3BpV,EAAS,OAAS,aAAcE,EAAAF,EAAS,OAAT,YAAAE,EAAmC,QAAQyS,EAAAyC,EAAU,OAAV,YAAAzC,EAAoC,KAC/G3S,EAAS,OAAS,mBAAoB6S,EAAA7S,EAAS,OAAT,YAAA6S,EAAmC,QAAQE,EAAAqC,EAAU,OAAV,YAAArC,EAAoC,IAExH,MAAO,CACL,GAAG/S,EACH,UAAWqV,GAAcrV,EAAS,OAAS,KAAO,YAAY,IAAA,EAAQoV,EAAU,SAAA,CAEpF,CAAC,CACH,EAAG,CAAA,CAAE,EAGLjV,OAAAA,EAAAA,UAAU,IAAM,CACd,GAAI,CAAC6U,EAAe,OAEpB,IAAIM,EAAkC,KAGtC,GAAIL,EAAW,OAAS,WAAaA,EAAW,OAAS,gBAAiB,CACxE,MAAMM,EAAiBN,EAAW,KAClCK,GAAmBC,GAAA,YAAAA,EAAgB,KAAM,IAC3C,CAEAP,EAAcM,CAAgB,CAChC,EAAG,CAACL,EAAYD,CAAa,CAAC,EAEvB,CACL,WAAAC,EACA,cAAAE,CAAA,CAEJ,CCtCO,MAAMK,EAAoB,CAc/B,YAAY7R,EAAWC,EAAWlQ,EAAkB+hB,EAAgBC,EAAgB,CAClF,KAAK,EAAI/R,EACT,KAAK,EAAIC,EACT,KAAK,SAAWlQ,EAChB,KAAK,MAAQ+hB,EACb,KAAK,MAAQC,CACf,CAQA,aAAaC,EAAgBC,EAAgB,CAE3C,MAAMpK,EAAKmK,EAAS,KAAK,EACnBlK,EAAKmK,EAAS,KAAK,EAGnBC,EAASC,EAAAA,cAAc,iBAAiB,KAAK,QAAQ,EACrDC,EAAM,KAAK,IAAIF,CAAM,EACrBG,EAAM,KAAK,IAAIH,CAAM,EAE3B,IAAII,EAASzK,EAAKuK,EAAMtK,EAAKuK,EACzBE,EAAS,CAAC1K,EAAKwK,EAAMvK,EAAKsK,EAG9B,OAAI,KAAK,QAAOE,EAAS,CAACA,GACtB,KAAK,QAAOC,EAAS,CAACA,GAEnB,CAAE,EAAGD,EAAQ,EAAGC,CAAA,CACzB,CAQA,aAAaD,EAAgBC,EAAgB,CAE3C,IAAIvS,EAAI,KAAK,MAAQ,CAACsS,EAASA,EAC3BrS,EAAI,KAAK,MAAQ,CAACsS,EAASA,EAG/B,MAAML,EAASC,EAAAA,cAAc,UAAU,KAAK,QAAQ,EAC9CC,EAAM,KAAK,IAAIF,CAAM,EACrBG,EAAM,KAAK,IAAIH,CAAM,EAErBM,EAAWxS,EAAIoS,EAAMnS,EAAIoS,EACzBI,EAAWzS,EAAIqS,EAAMpS,EAAImS,EAG/B,MAAO,CACL,EAAGI,EAAW,KAAK,EACnB,EAAGC,EAAW,KAAK,CAAA,CAEvB,CASA,gBAAgB5K,EAAYC,EAAY4K,EAAmB,GAAO,CAEhE,IAAI1S,EAAI,KAAK,MAAQ,CAAC6H,EAAKA,EACvB5H,EAAI,KAAK,MAAQ,CAAC6H,EAAKA,EAG3B,MAAMoK,EAASQ,EAAUP,EAAAA,cAAc,iBAAiB,KAAK,QAAQ,EAAIA,EAAAA,cAAc,UAAU,KAAK,QAAQ,EAExGC,EAAM,KAAK,IAAIF,CAAM,EACrBG,EAAM,KAAK,IAAIH,CAAM,EAE3B,OAAIQ,EAEK,CACL,GAAI1S,EAAIoS,EAAMnS,EAAIoS,EAClB,GAAI,CAACrS,EAAIqS,EAAMpS,EAAImS,CAAA,EAId,CACL,GAAIpS,EAAIoS,EAAMnS,EAAIoS,EAClB,GAAIrS,EAAIqS,EAAMpS,EAAImS,CAAA,CAGxB,CAOA,OAAO,YAAYvT,EAA+B,CAChD,OAAO,IAAIgT,GACThT,EAAQ,EACRA,EAAQ,EACRA,EAAQ,UAAY,EACpBA,EAAQ,OAAS,GACjBA,EAAQ,OAAS,EAAA,CAErB,CACF,CClFO,MAAM8T,EAAmB,CAI9B,oBAAoBC,EAAuBC,EAAsBtG,EAAkBF,EAAkC,CACnH,MAAMxE,EAAK0E,EAAWsG,EAAQ,OACxB/K,EAAKuE,EAAWwG,EAAQ,OAGxBC,EAASD,EAAQ,UAAU,EAC3BE,EAASF,EAAQ,UAAU,EAC3BG,EAAKH,EAAQ,UAAU,cACvBI,EAAaD,EAAG,MAChBE,EAAcF,EAAG,OAGjBG,EAAaN,EAAQ,UAAU,OAAS,EACxCO,EAAaP,EAAQ,UAAU,OAAS,EACxCQ,EAAiBR,EAAQ,UAAU,WAAa,EAChDS,EAAkBT,EAAQ,UAAU,YAAc,EAIlDU,EADiBC,EAAAA,UAAU,aAAaX,EAAQ,SAAS,EAC7B,kBAAkBhL,EAAIC,CAAE,EAC1D,IAAI2L,EAAUF,EAAW,GACrBG,EAAUH,EAAW,GAGzB,MAAMzB,EAAQkB,EAAG,eAAiB,GAAK,EACjCjB,EAAQiB,EAAG,aAAe,GAAK,EACrCS,GAAW3B,EACX4B,GAAW3B,EAGX,MAAM4B,EAAe,CAACF,EAAUR,EAC1BW,EAAe,CAACF,EAAUR,EAGhC,IAAIW,EAAWV,EAAaQ,EACxBG,EAAWV,EAAaQ,EAG5B,MAAMG,EAAW,EAAIV,EACfW,GAAW,EAAIV,EACrB,OAAAO,EAAW,KAAK,IAAI,EAAG,KAAK,IAAIE,EAAUF,CAAQ,CAAC,EACnDC,EAAW,KAAK,IAAI,EAAG,KAAK,IAAIE,GAAUF,CAAQ,CAAC,EAE5C,CACL,MAAOD,EACP,MAAOC,EACP,UAAWT,EACX,WAAYC,EACZ,EAAGR,EACH,EAAGC,CAAA,CAEP,CAKA,oBACEH,EACAqB,EACAC,EACAC,EACA5H,EACAF,EACkB,CAClB,MAAM2G,EAAKkB,EAAc,UAAU,cAC7BpC,EAAQkB,EAAG,eACXjB,EAAQiB,EAAG,aAGXoB,EAAS,KAAK,qBAAqBH,EAAQ,CAAC,CAACnC,EAAO,CAAC,CAACC,CAAK,EAG3DsC,EAAgBH,EAAc,UAAU,SACxCpB,EAASoB,EAAc,UAAU,EACjCnB,EAASmB,EAAc,UAAU,EAGjCI,EAAiBd,EAAAA,UAAU,aAAaU,EAAc,SAAS,EAC/D,CAAE,IAAA9B,EAAK,IAAAC,GAAQiC,EAAe,iBAAA,EAG9BC,EAAYzC,EAAQ,GAAK,EACzB0C,EAAYzC,EAAQ,GAAK,EAGzB0C,EAAYlI,EAAWuG,EACvB4B,EAAYrI,EAAW0G,EAC7B,IAAI4B,EAAgBF,EAAYrC,EAAMsC,EAAYrC,EAC9CuC,EAAgBH,EAAYpC,EAAMqC,EAAYtC,EAClDuC,GAAiBJ,EACjBK,GAAiBJ,EAGjB,MAAMK,EAAUV,EAAoB,OAASrB,EACvCgC,EAAUX,EAAoB,OAASpB,EAC7C,IAAIgC,EAAcF,EAAUzC,EAAM0C,EAAUzC,EACxC2C,EAAcH,EAAUxC,EAAMyC,EAAU1C,EAC5C2C,GAAeR,EACfS,GAAeR,EAGf,MAAMf,GAAUkB,EAAgBI,EAC1BrB,EAAUkB,EAAgBI,EAG1B7B,EAAaH,EAAG,MAChBI,EAAaJ,EAAG,MAChBK,GAAiBL,EAAG,UACpBM,GAAkBN,EAAG,WACrBC,GAAaD,EAAG,MAChBE,GAAcF,EAAG,OAGjBW,EAAeF,GAAUR,GACzBW,GAAeF,EAAUR,GAGzBloB,GAAS,KAAK,wBAClBipB,EACAd,EACAC,EACAC,GACAC,GACAK,EACAC,EAAA,EAIIqB,GAAiB,KAAK,2BAC1B9B,EACAC,EACAC,GACAC,GACAtoB,GAAO,MACPA,GAAO,MACPA,GAAO,UACPA,GAAO,WACPioB,GACAC,GACAmB,EACAE,EACAC,CAAA,EAGF,MAAO,CACL,GAAGxpB,GACH,EAAG8nB,EAASmC,GAAe,aAC3B,EAAGlC,EAASkC,GAAe,aAC3B,SAAUZ,EACV,MAAOpB,GACP,OAAQC,GACR,OAAAkB,CAAA,CAEJ,CAMA,qBAAqBH,EAAgBnC,EAAgBC,EAAwB,CAE3E,IAAImD,EAAejB,EAgBnB,OAfInC,IACEoD,EAAa,SAAS,MAAM,EAC9BA,EAAeA,EAAa,QAAQ,OAAQ,OAAO,EAC1CA,EAAa,SAAS,OAAO,IACtCA,EAAeA,EAAa,QAAQ,QAAS,MAAM,IAGnDnD,IACEmD,EAAa,SAAS,KAAK,EAC7BA,EAAeA,EAAa,QAAQ,MAAO,QAAQ,EAC1CA,EAAa,SAAS,QAAQ,IACvCA,EAAeA,EAAa,QAAQ,SAAU,KAAK,IAInDA,IAAiB,YAAcA,IAAiB,eAC3C,cACEA,IAAiB,aAAeA,IAAiB,cACnD,cACEA,IAAiB,OAASA,IAAiB,SAC7C,YACEA,IAAiB,QAAUA,IAAiB,QAC9C,YAEF,SACT,CAMA,wBACEjB,EACAd,EACAC,EACAC,EACAC,EACAK,EACAC,EACY,CACZ,MAAMuB,EAAUC,EAAAA,cAEhB,IAAIvB,EAAWV,EACXW,EAAWV,EACXiC,EAAehC,EACfiC,EAAgBhC,EAGpB,OAAIW,IAAW,YACbJ,EAAWV,EAAaQ,EACxBG,EAAWV,EAAaQ,EACxByB,EAAehC,EAAiBM,EAChC2B,EAAgBhC,EAAkBM,GACzBK,IAAW,aACpBH,EAAWV,EAAaQ,EACxByB,EAAehC,EAAiBM,EAChC2B,EAAgBhC,EAAkBM,GACzBK,IAAW,eACpBJ,EAAWV,EAAaQ,EACxB0B,EAAehC,EAAiBM,EAChC2B,EAAgBhC,EAAkBM,GACzBK,IAAW,gBACpBoB,EAAehC,EAAiBM,EAChC2B,EAAgBhC,EAAkBM,GACzBK,IAAW,OACpBH,EAAWV,EAAaQ,EACxB0B,EAAgBhC,EAAkBM,GACzBK,IAAW,SACpBqB,EAAgBhC,EAAkBM,EACzBK,IAAW,QACpBJ,EAAWV,EAAaQ,EACxB0B,EAAehC,EAAiBM,GACvBM,IAAW,UACpBoB,EAAehC,EAAiBM,GAI3B,KAAK,qBACVM,EACAJ,EACAC,EACAuB,EACAC,EACAnC,EACAC,EACAC,EACAC,EACA6B,CAAA,CAEJ,CAMA,qBACElB,EACAsB,EACAC,EACAC,EACAC,EACAvC,EACAC,EACAC,EACAC,EACA6B,EACY,CACZ,IAAItB,EAAW0B,EACXzB,EAAW0B,EACXH,EAAeI,EACfH,EAAgBI,EAGpB,OAAIL,EAAeF,IACjBE,EAAeF,EACXlB,EAAO,SAAS,MAAM,IACxBJ,EAAWV,EAAaE,EAAiB8B,IAGzCtB,EAAWwB,EAAe,IACxBpB,EAAO,SAAS,MAAM,EACxBJ,EAAW,EAAIwB,EAEfA,EAAe,EAAIxB,GAGnBA,EAAW,IACbwB,EAAeA,EAAexB,EAC9BA,EAAW,GAITyB,EAAgBH,IAClBG,EAAgBH,EACZlB,EAAO,SAAS,KAAK,IACvBH,EAAWV,EAAaE,EAAkB6B,IAG1CrB,EAAWwB,EAAgB,IACzBrB,EAAO,SAAS,KAAK,EACvBH,EAAW,EAAIwB,EAEfA,EAAgB,EAAIxB,GAGpBA,EAAW,IACbwB,EAAgBA,EAAgBxB,EAChCA,EAAW,GAGN,CAAE,MAAOD,EAAU,MAAOC,EAAU,UAAWuB,EAAc,WAAYC,CAAA,CAClF,CAMA,2BACEK,EACAC,EACAC,EACAC,EACAjC,EACAC,EACAuB,EACAC,EACAjlB,EACAC,EACAP,EACAwkB,EACAC,EACgD,CAEhD,MAAMuB,EAAiBJ,EAAWtlB,EAASwlB,EAAexlB,EAAS,EAC7D2lB,EAAiBJ,EAAWtlB,EAAUwlB,EAAgBxlB,EAAU,EAGhE2lB,EAAiBpC,EAAWxjB,EAASglB,EAAehlB,EAAS,EAC7D6lB,EAAiBpC,EAAWxjB,EAAUglB,EAAgBhlB,EAAU,EAGtE,IAAI6lB,EAAeF,EAAiBF,EAChCK,EAAeF,EAAiBF,EAGpCG,GAAgB5B,EAChB6B,GAAgB5B,EAIhB,MAAM6B,EADY,IAAI7C,EAAAA,UAAU,CAAE,SAAAzjB,EAAU,EAAG,EAAG,EAAG,EAAG,EAC1B,kBAAkBomB,EAAcC,CAAY,EACpEE,EAAeD,EAAY,GAC3BE,EAAeF,EAAY,GAEjC,MAAO,CAAE,aAAAC,EAAc,aAAAC,CAAA,CACzB,CAKA,yBAAyB1X,EAAsBoV,EAA0C,CACvF,MAAMuC,EAAY3E,GAAoB,YAAYhT,CAAO,EACnDmU,EAAKnU,EAAQ,cACbxO,EAAQ2iB,EAAG,MACX1iB,EAAS0iB,EAAG,OAGZuC,EAAQvC,EAAG,OAAS,EACpBwC,EAAQxC,EAAG,OAAS,EACpByC,EAAYzC,EAAG,WAAa,EAC5B0C,EAAa1C,EAAG,YAAc,EAGpC,IAAIV,EAAS,EACTC,EAAS,EAEb,OAAI0B,EAAO,SAAS,MAAM,EACxB3B,GAAUiD,EAAQ,IAAOllB,EAChB4jB,EAAO,SAAS,OAAO,EAChC3B,GAAUiD,EAAQE,EAAY,IAAOplB,EAErCiiB,GAAUiD,EAAQE,EAAY,EAAI,IAAOplB,EAGvC4jB,EAAO,SAAS,KAAK,EACvB1B,GAAUiD,EAAQ,IAAOllB,EAChB2jB,EAAO,SAAS,QAAQ,EACjC1B,GAAUiD,EAAQE,EAAa,IAAOplB,EAEtCiiB,GAAUiD,EAAQE,EAAa,EAAI,IAAOplB,EAGrCkmB,EAAU,aAAalE,EAAQC,CAAM,CAC9C,CACF,CCncO,SAASkE,IAAc,CAE5B,KAAM,CAACC,CAAc,EAAI3a,EAAAA,SAAS,IAAM,IAAI4W,EAAoB,EAEhE,MAAO,CACL,eAAA+D,CAAA,CAEJ,CCPO,SAASC,GAAkB/V,EAA0B,CAE1D,MAAMgW,EAA+BtU,EAAAA,OAAyC,IAAI,EAK5EuU,EAAuBvU,EAAAA,OAAiC,IAAI,EAGlE9F,OAAAA,EAAAA,UAAU,IAAM,CACdqa,EAAqB,QAAU,IACjC,EAAG,CAACjW,CAAc,CAAC,EAEZ,CACL,6BAAAgW,EACA,qBAAAC,CAAA,CAEJ,CCTA,SAASC,GAAoBjY,EAAmE,CAC9F,OAAOA,GAAY,MAAiCA,aAAmBkY,EAAAA,WACzE,CAcO,SAASC,GAAe,CAAE,gBAAAlQ,EAAiB,IAAAnK,GAA4B,CAE5E,KAAM,CAACsa,EAAWC,CAAY,EAAInb,EAAAA,SAAkB,EAAK,EACnD,CAACob,EAAUC,CAAW,EAAIrb,EAAAA,SAAiB,EAAE,EAC7C,CAACsb,EAAcC,CAAe,EAAIvb,EAAAA,SAKrC,CAAE,EAAG,EAAG,EAAG,EAAG,MAAO,IAAK,OAAQ,GAAI,EACnC,CAACwb,EAAgBC,CAAiB,EAAIzb,EAAAA,SAAiB,CAAC,EACxD,CAAC0b,EAAgBC,CAAiB,EAAI3b,EAAAA,SAAiB,CAAC,EACxD,CAAC4b,EAAcC,CAAe,EAAI7b,EAAAA,SAAiB,CAAC,EACpD8b,EAAmBvV,EAAAA,OAAe,CAAC,EACnCwV,EAAqBxV,EAAAA,OAAe,YAAY,IAAA,CAAK,EACrDyV,EAAqBzV,EAAAA,OAAe,CAAC,EACrC0V,EAAmB1V,EAAAA,OAAgB,EAAK,EAGxC,CAAC2V,EAAcC,CAAe,EAAInc,EAAAA,SAA0B,IAAI,EAChE,CAACoc,EAAwBC,CAAyB,EAAIrc,EAAAA,SAAgC,IAAI,EAI1Fsc,EAAkB/V,EAAAA,OAAwB,IAAI,EAG9CgW,EAA6Bpa,EAAAA,YAChCjD,GAA0B,CAEzB,GAAI,CAACgc,GAAa,CAACgB,EACjB,OAGF,MAAMnpB,GAAQ,KAAK,IAAI2oB,EAAgBE,CAAY,EAC7C5oB,EAAM,KAAK,IAAI0oB,EAAgBE,CAAY,EAGjD,GAAI7oB,KAAUC,EAAK,CAGjB,MAAM0U,EAAUwU,EAAa,MAAA,EACvBM,EAAa9U,EAAQ,QAAA,EAAU,OACrCA,EAAQ,WAAW,EAAG8U,EAAYtd,CAAK,EAGvCod,EAAgB,QAAU5U,EAC1ByU,EAAgBzU,CAAO,EACvB2T,EAAY3T,EAAQ,SAAS,EAG7B2U,EAA2BI,KACR,CAAE,GAAGA,GAAW,GAAGvd,CAAA,EAErC,CACH,KAAO,CAGL,MAAMwI,EAAUwU,EAAa,MAAA,EAC7BxU,EAAQ,WAAW3U,GAAOC,EAAKkM,CAAK,EAGpCod,EAAgB,QAAU5U,EAC1ByU,EAAgBzU,CAAO,EACvB2T,EAAY3T,EAAQ,SAAS,EAG7B2U,EAA2BI,IACR,CAAE,GAAGA,EAAW,GAAGvd,CAAA,EAErC,CACH,CACF,EACA,CAACgc,EAAWgB,EAAcR,EAAgBE,CAAY,CAAA,EAIlDc,EAAoBva,EAAAA,YAAY,IAA6B,CACjE,GAAI,CAAC+Y,GAAa,CAACgB,GAAgB,CAACnR,EAAiB,OAAO,KAE5D,MAAMhY,EAAQ,KAAK,IAAI2oB,EAAgBE,CAAY,EAC7C5oB,GAAM,KAAK,IAAI0oB,EAAgBE,CAAY,EAGjD,OAAI7oB,IAAUC,GACRD,IAAU,EAELgoB,GAAoBhQ,CAAe,EAAIA,EAAgB,gBAAA,EAAoB,CAAA,EAG7EmR,EAAa,WAAW,KAAK,IAAI,EAAGnpB,EAAQ,CAAC,CAAC,EAIhDmpB,EAAa,WAAWnpB,CAAK,CACtC,EAAG,CAACmoB,EAAWgB,EAAcR,EAAgBE,EAAc7Q,CAAe,CAAC,EAGrE4R,EAA2Bxa,EAAAA,YAC9Bya,GAAgE,CAC/D,GAAI,CAAC1B,GAAa,CAACgB,GAAgB,CAACnR,EAAiB,OAGrD,MAAM8R,GAAeH,EAAA,EAOfI,EAAW,CAHM,CAAE,GAHJ/B,GAAoBhQ,CAAe,EAAIA,EAAgB,gBAAA,EAAoB,CAAA,EAGtD,GAAG8R,EAAA,EAGZD,CAAQ,EAIzCL,EAA2B,CAAE,CAACK,CAAQ,EAAGE,EAAU,CACrD,EACA,CAAC5B,EAAWgB,EAAcnR,EAAiB2R,EAAmBH,CAA0B,CAAA,EAI1FQ,OAAAA,EAAAA,oBACEnc,EACA,KAAO,CACL,oBAAsB1B,GAA0B,CAC9Cqd,EAA2Brd,CAAK,CAClC,EACA,yBAA2B0d,GAAgE,CACzFD,EAAyBC,CAAQ,CACnC,EACA,cAAe,IAAM1B,EACrB,kBAAmB,IAAMwB,EAAA,CAAkB,GAE7C,CAACH,EAA4BI,EAA0BzB,EAAWwB,CAAiB,CAAA,EAG9E,CACL,UAAAxB,EACA,aAAAC,EACA,SAAAC,EACA,YAAAC,EACA,aAAAC,EACA,gBAAAC,EACA,eAAAC,EACA,kBAAAC,EACA,eAAAC,EACA,kBAAAC,EACA,aAAAC,EACA,gBAAAC,EACA,iBAAAC,EACA,mBAAAC,EACA,mBAAAC,EACA,iBAAAC,EACA,aAAAC,EACA,gBAAAC,EACA,gBAAAG,EACA,uBAAAF,EACA,0BAAAC,EACA,2BAAAE,EACA,kBAAAG,EACA,yBAAAC,CAAA,CAEJ,CCxIO,SAASK,GAAgB,CAC9B,MAAA1oB,EACA,OAAAC,EACA,KAAA0oB,EACA,YAAAC,EACA,qBAAAC,EACA,YAAAC,EACA,aAAAC,EACA,aAAAC,EACA,UAAAC,EACA,gBAAAxX,EACA,iBAAAyX,CACF,EAAiD,CAE/C,KAAM,CAACC,EAAeC,CAAgB,EAAI1d,EAAAA,SAAS,EAAK,EAExDS,EAAAA,UAAU,IAAM,CACd,MAAMkd,EAAW,iBAAkB,QAAU,UAAU,eAAiB,EACxED,EAAiBC,CAAQ,CAC3B,EAAG,CAAA,CAAE,EAGL,MAAMC,EAAqBrX,EAAAA,OAA2B,IAAI,EAE1D9F,EAAAA,UAAU,IAAM,CACd,MAAMod,EAAoBjS,GAAiC,CACzDgS,EAAmB,QAAUhS,EAAE,OAC/B,WAAW,IAAM,CAAEgS,EAAmB,QAAU,IAAM,EAAG,GAAG,CAC9D,EACA,gBAAS,iBAAiB,aAAcC,EAAkB,CAAE,QAAS,GAAM,QAAS,GAAM,EAC1F,SAAS,iBAAiB,cAAeA,EAAkB,CAAE,QAAS,GAAM,QAAS,GAAM,EACpF,IAAM,CACX,SAAS,oBAAoB,aAAcA,EAAkB,CAAE,QAAS,GAAM,EAC9E,SAAS,oBAAoB,cAAeA,EAAkB,CAAE,QAAS,GAAM,CACjF,CACF,EAAG,CAAA,CAAE,EAGL,KAAM,CAACC,EAAYC,CAAa,EAAI/d,WAAS,CAC3C,MAAO1L,GAAS,OAAO,WACvB,OAAQC,GAAU,OAAO,WAAA,CAC1B,EAcDkM,EAAAA,UAAU,IAAM,CACd,GAAI+c,EAAkB,CACpBO,EAAc,CAAE,MAAOP,EAAiB,MAAO,OAAQA,EAAiB,OAAQ,EAChF,MACF,CACA,GAAIlpB,IAAU,QAAaC,IAAW,OAAW,CAC/CwpB,EAAc,CAAE,MAAAzpB,EAAO,OAAAC,EAAQ,EAC/B,MACF,CACA,MAAMypB,EAAiBjY,EAAgB,kBAAA,EACnCiY,GACFD,EAAc,CAAE,MAAOC,EAAe,MAAO,OAAQA,EAAe,OAAQ,CAEhF,EAAG,CAAC1pB,EAAOC,EAAQgpB,EAAWxX,EAAiByX,CAAgB,CAAC,EAGhE/c,EAAAA,UAAU,IAAM,CACd,MAAMwd,EAAe,IAAM,CACzB,GAAIT,EAAkB,CACpBO,EAAc,CAAE,MAAOP,EAAiB,MAAO,OAAQA,EAAiB,OAAQ,EAChF,MACF,CACA,GAAIlpB,IAAU,QAAaC,IAAW,OAAW,CAC/C,MAAMypB,EAAiBjY,EAAgB,kBAAA,EAIrCgY,EAHGC,EAGW,CAAE,MAAOA,EAAe,MAAO,OAAQA,EAAe,QAFtD,CAAE,MAAO,OAAO,WAAY,OAAQ,OAAO,YAEmB,CAEhF,CACF,EACA,cAAO,iBAAiB,SAAUC,CAAY,EACvC,IAAM,OAAO,oBAAoB,SAAUA,CAAY,CAChE,EAAG,CAAC3pB,EAAOC,EAAQwR,EAAiByX,CAAgB,CAAC,EAGrD,MAAMU,EAAuB,KAAK,IAAI,GAAK,KAAK,IAAI,EAAKhB,CAAW,CAAC,EAC/DiB,EAAmBd,GAAgBD,EACnCgB,EAAmBd,GAAgBF,EACnCiB,EAAeF,IAAqB,QAAalB,EAAO,EAAIkB,EAAmBlB,EAAO,EACtFqB,EAAeF,IAAqB,QAAanB,EAAO,EAAImB,EAAmBnB,EAAO,EAEtFsB,EAAoBJ,IAAqB,OAC3CL,EAAW,MAASO,EAAe,EACnCP,EAAW,MAAQI,EACjBM,EAAqBJ,IAAqB,OAC5CN,EAAW,OAAUQ,EAAe,EACpCR,EAAW,OAASI,EAUlBO,EAAcN,IAAqB,OACrCE,GACCE,EAAoBT,EAAW,OAAS,EACvCY,EAAcN,IAAqB,OACrCE,GACCE,EAAqBV,EAAW,QAAU,EACzCa,EAAiBnB,EAAmBiB,EAAcjB,EAAiB,EAAIiB,EACvEG,EAAiBpB,EAAmBkB,EAAclB,EAAiB,EAAIkB,EAEvEG,GAA4B5B,EAAO,EAAIE,EAAuBF,EAAOE,EAE3E,MAAO,CACL,cAAAM,EACA,WAAAK,EACA,mBAAAF,EACA,qBAAAM,EACA,kBAAAK,EACA,mBAAAC,EACA,eAAAG,EACA,eAAAC,EACA,0BAAAC,EAAA,CAEJ,CCtJO,SAASC,IAAiD,CAC/D,KAAM,CAACC,EAAYC,CAAa,EAAIhf,EAAAA,SAAsB,CAAA,CAAE,EACtD,CAACif,EAAmBC,CAAoB,EAAIlf,EAAAA,SAA6B,CAAA,CAAE,EAC3E,CAACmf,EAAiBC,CAAkB,EAAIpf,EAAAA,SAAS,EAAK,EACtD,CAACqf,EAAYC,CAAa,EAAItf,EAAAA,SAAkB,EAAK,EACrD,CAACuf,EAAqBC,CAAqB,EAAIxf,EAAAA,SAAiB,CAAC,EACjE,CAACyf,EAAiBC,CAAkB,EAAI1f,EAAAA,SAAiB,CAAC,EAC1D,EAAG2f,CAAyB,EAAI3f,EAAAA,SAAiB,CAAC,EAExD,MAAO,CACL,WAAA+e,EACA,cAAAC,EACA,kBAAAC,EACA,qBAAAC,EACA,gBAAAC,EACA,mBAAAC,EACA,WAAAC,EACA,cAAAC,EACA,sBAAAE,EACA,gBAAAC,EACA,mBAAAC,EACA,0BAAAC,CAAA,CAEJ,CC1BO,SAASC,GAAe,CAAE,SAAAjb,GAAwD,CACvF,KAAM,EAAGkb,CAAW,EAAI7f,EAAAA,SAAiB,EAAE,EACrC,CAAC8f,EAAgBC,CAAiB,EAAI/f,EAAAA,SAAS,CAAC,EAChD,CAACggB,EAAeC,CAAgB,EAAIjgB,EAAAA,SAAS,CAAC,EAC9C,CAACkgB,EAAsBC,CAAuB,EAAIngB,EAAAA,SAAS,CAAC,EAE5DogB,EAAcje,EAAAA,YAAY,IAAM,CACpC8d,EAAkBnZ,GAAMA,EAAI,CAAC,CAC/B,EAAG,CAAA,CAAE,EAGLrG,OAAAA,EAAAA,UAAU,IACY4f,wBAAuBzZ,GAAc,CACpCjC,EAAS,KAAKiH,GAAKA,EAAE,KAAOhF,CAAS,GACtCqZ,EAAiBnZ,GAAKA,EAAI,CAAC,CAC/C,CAAC,EAEA,CAACnC,CAAQ,CAAC,EAEN,CACL,cAAAqb,EACA,YAAAI,EACA,eAAAN,EACA,kBAAAC,EACA,YAAAF,EACA,qBAAAK,EACA,wBAAAC,CAAA,CAEJ,CCuKA,MAAMG,GAKF,CACF,UAAW,WACX,eAAgB,WAOhB,UAAW,UACX,aAAc,EAGd,WAAY,YACZ,WAAY,OACZ,gBAAiB,GACjB,aAAc,EACd,aAAc,GACd,oBAAqB,EAKrB,eAAgB,IAChB,eAAgB,GAChB,sBAAuB,EACvB,eAAgB,GAChB,sBAAuB,IACvB,eAAgB,CAAC,EAAG,CAAC,EACrB,SAAU,CACR,KAAM,4BACN,QAAS,0BACT,OAAQ,qBACR,QAAS,2BACT,OAAQ,2BAAA,EAEV,WAAY,CACV,KAAM,UACN,QAAS,UACT,OAAQ,UACR,QAAS,UACT,OAAQ,SAAA,EAEV,WAAY,UACZ,gBAAiB,GACjB,gBACE,0EACF,eAAgB,SAClB,EAQA,IAAIC,GAA6C,KACjD,SAASC,GAAehrB,EAAqD,CAC3E,MAAMirB,EAAIjrB,EAAO,MACXkrB,EAAIlrB,EAAO,OACjB,GAAIirB,GAAK,GAAKC,GAAK,EAAG,OAAO,KACxBH,KAAkBA,GAAmB,SAAS,cAAc,QAAQ,GACrEA,GAAiB,QAAUE,IAAGF,GAAiB,MAAQE,GACvDF,GAAiB,SAAWG,IAAGH,GAAiB,OAASG,GAC7D,MAAMC,EAAOJ,GAAiB,WAAW,IAAI,EAC7C,OAAKI,GACLA,EAAK,aAAa,EAAG,EAAG,EAAG,EAAG,EAAG,CAAC,EAClCA,EAAK,UAAU,EAAG,EAAGF,EAAGC,CAAC,EACzBC,EAAK,UAAUnrB,EAAQ,EAAG,CAAC,EACpB+qB,IAJW,IAKpB,CAWA,SAASK,GACPC,EACA1lB,EACQ,CACR,MAAM2lB,EAAUD,EAAM,KAAA,EACtB,GAAI,CAACC,EAAQ,WAAW,MAAM,EAAG,OAAOA,EAExC,MAAMC,EAAQD,EAAQ,MAAM,EAAG,EAAE,EAC3B,CAACvyB,EAAM,GAAGyyB,CAAI,EAAID,EAAM,MAAM,GAAG,EACjCE,EAAU1yB,EAAK,KAAA,EACf2yB,EAAWF,EAAK,OAAS,EAAIA,EAAK,KAAK,GAAG,EAAE,KAAA,EAAS,GACrDxrB,EAAS2F,GAAU,SAAS,gBAElC,OADiB,iBAAiB3F,CAAM,EAAE,iBAAiByrB,CAAO,EAAE,KAAA,GACjDC,GAAY,MACjC,CAqBA,SAASC,GAAeC,EAAsC,CAC5D,GAAI,OAAO,SAAa,IAAa,MAAO,GAC5C,MAAM/iB,EAAO,SAAS,gBAEtB,MADI,GAAAA,EAAK,UAAU,SAAS,MAAM,GAC9BA,EAAK,aAAa,YAAY,IAAM,OAE1C,CAaO,SAASgjB,GAAUC,EAAsB,CAC9C,MAAMC,EAAOC,GAAaF,CAAG,EAC7B,OAAOC,IAAS,MAAQA,EAAO,EACjC,CAEO,SAASE,GAAWH,EAAsB,CAC/C,MAAMC,EAAOC,GAAaF,CAAG,EAC7B,OAAOC,IAAS,MAAQA,GAAQ,EAClC,CAEO,SAASC,GAAaF,EAA4B,CACvD,MAAMR,EAAUQ,EAAI,KAAA,EAEpB,GAAIR,EAAQ,WAAW,GAAG,EAAG,CAC3B,MAAM5M,EAAM4M,EAAQ,MAAM,CAAC,EAC3B,IAAI,EAAW1M,EAAW/b,EAC1B,GAAI6b,EAAI,SAAW,EACjB,EAAI,SAASA,EAAI,CAAC,EAAIA,EAAI,CAAC,EAAG,EAAE,EAChCE,EAAI,SAASF,EAAI,CAAC,EAAIA,EAAI,CAAC,EAAG,EAAE,EAChC7b,EAAI,SAAS6b,EAAI,CAAC,EAAIA,EAAI,CAAC,EAAG,EAAE,UACvBA,EAAI,SAAW,EACxB,EAAI,SAASA,EAAI,MAAM,EAAG,CAAC,EAAG,EAAE,EAChCE,EAAI,SAASF,EAAI,MAAM,EAAG,CAAC,EAAG,EAAE,EAChC7b,EAAI,SAAS6b,EAAI,MAAM,EAAG,CAAC,EAAG,EAAE,MAC3B,QAAO,KACd,OAAI,OAAO,MAAM,CAAC,GAAK,OAAO,MAAME,CAAC,GAAK,OAAO,MAAM/b,CAAC,EAAU,KAC3DqpB,GAAQ,EAAGtN,EAAG/b,CAAC,CACxB,CAEA,MAAMspB,EAAIb,EAAQ,MAAM,0CAA0C,EAClE,OAAIa,EAAUD,GAAQ,OAAOC,EAAE,CAAC,CAAC,EAAG,OAAOA,EAAE,CAAC,CAAC,EAAG,OAAOA,EAAE,CAAC,CAAC,CAAC,EACvD,IACT,CACA,SAASD,GAAQvN,EAAWC,EAAW/b,EAAmB,CACxD,MAAMupB,EAAO3X,GAAc,CACzB,MAAM4X,EAAK5X,EAAI,IACf,OAAO4X,GAAM,OAAUA,EAAK,MAAQ,KAAK,KAAKA,EAAK,MAAS,MAAO,GAAG,CACxE,EACA,MAAO,OAASD,EAAIzN,CAAC,EAAI,MAASyN,EAAIxN,CAAC,EAAI,MAASwN,EAAIvpB,CAAC,CAC3D,CAIA,SAASypB,GAAavf,EAA+BtL,EAAqB,CACxE,GAAIA,EAAK,OAAS,OAAQ,CACxBsL,EAAI,KAAKtL,EAAK,EAAGA,EAAK,EAAGA,EAAK,MAAOA,EAAK,MAAM,EAChD,MACF,CACA,GAAIA,EAAK,OAAS,cAAe,CAC/B,MAAM8qB,EAAK9qB,EAAK,IAAMA,EAAK,IAAM,EAC3B+qB,EAAK/qB,EAAK,IAAMA,EAAK,IAAM,EACjC,GAAI8qB,GAAM,GAAKC,GAAM,EAAG,CACtBzf,EAAI,KAAKtL,EAAK,EAAGA,EAAK,EAAGA,EAAK,MAAOA,EAAK,MAAM,EAChD,MACF,CAII8qB,IAAOC,EACTzf,EAAI,UAAUtL,EAAK,EAAGA,EAAK,EAAGA,EAAK,MAAOA,EAAK,OAAQ8qB,CAAE,EAEzDxf,EAAI,UAAUtL,EAAK,EAAGA,EAAK,EAAGA,EAAK,MAAOA,EAAK,OAAQ,CACrD,CAAE,EAAG8qB,EAAI,EAAGC,CAAA,CAAG,CACK,EAExB,MACF,CAIA,MAAMC,EAAM,IAAI,OAAOhrB,EAAK,CAAC,EAI5BsL,EAAmD,mBAAqB0f,CAC3E,CAEA,SAASC,GACP3f,EACAtL,EACAkrB,EACM,CACN,GAAIlrB,EAAK,OAAS,OAAQ,CACxB,MAAMgrB,EAAM,IAAI,OAAOhrB,EAAK,CAAC,EAExBsL,EAAI,KAAK0f,CAAG,EACjB,MACF,CACA1f,EAAI,UAAA,EACJuf,GAAavf,EAAKtL,CAAI,IAEb,KAAA,CACX,CAEA,SAASmrB,GAAc7f,EAA+BtL,EAAqB,CACzE,GAAIA,EAAK,OAAS,OAAQ,CACxB,MAAMgrB,EAAM,IAAI,OAAOhrB,EAAK,CAAC,EAC7BsL,EAAI,OAAO0f,CAAG,EACd,MACF,CACA1f,EAAI,UAAA,EACJuf,GAAavf,EAAKtL,CAAI,EACtBsL,EAAI,OAAA,CACN,CAEA,SAAS8f,GAAkB9f,EAA+BtL,EAAqB,CACzEA,EAAK,OAAS,QAUlB6qB,GAAavf,EAAKtL,CAAI,CACxB,CAWA,SAASqrB,GAAYL,EAAahrB,EAAqB,CACrD,GAAIA,EAAK,OAAS,OAAQ,CACxBgrB,EAAI,KAAKhrB,EAAK,EAAGA,EAAK,EAAGA,EAAK,MAAOA,EAAK,MAAM,EAChD,MACF,CACA,GAAIA,EAAK,OAAS,cAAe,CAC/B,MAAM8qB,EAAK9qB,EAAK,IAAMA,EAAK,IAAM,EAC3B+qB,EAAK/qB,EAAK,IAAMA,EAAK,IAAM,EACjC,GAAI8qB,GAAM,GAAKC,GAAM,EAAG,CACtBC,EAAI,KAAKhrB,EAAK,EAAGA,EAAK,EAAGA,EAAK,MAAOA,EAAK,MAAM,EAChD,MACF,CACI8qB,IAAOC,EACTC,EAAI,UAAUhrB,EAAK,EAAGA,EAAK,EAAGA,EAAK,MAAOA,EAAK,OAAQ8qB,CAAE,EAEzDE,EAAI,UAAUhrB,EAAK,EAAGA,EAAK,EAAGA,EAAK,MAAOA,EAAK,OAAQ,CACrD,CAAE,EAAG8qB,EAAI,EAAGC,CAAA,CAAG,CACK,EAExB,MACF,CAEAC,EAAI,QAAQ,IAAI,OAAOhrB,EAAK,CAAC,CAAC,CAChC,CAEA,SAASsrB,GACPC,EACAC,EACAC,EACS,CAOT,OACEF,GAAA,YAAAA,EAAO,WAAY,CACjB,KAAM,OACN,EAAG,EACH,EAAG,EACH,MAAOC,EACP,OAAQC,CAAA,CAGd,CAEA,SAASC,GACPH,EACAI,EACAH,EACAC,EACgB,CAChB,GAAIF,GAAA,MAAAA,EAAO,SAAU,OAAOA,EAAM,SAClC,GAAII,EAAM,iBAAmBA,EAAM,gBAAkB,EAAG,CACtD,MAAMC,EAAQD,EAAM,gBACpB,MAAO,CACL,KAAM,OACN,EAAGC,EACH,EAAGA,EACH,MAAO,KAAK,IAAI,EAAGJ,EAAWI,EAAQ,CAAC,EACvC,OAAQ,KAAK,IAAI,EAAGH,EAAWG,EAAQ,CAAC,CAAA,CAE5C,CACA,OAAO,IACT,CAIO,SAASC,GAAkB9rB,EAAsC,mBACtE,KAAM,CAAE,IAAAuL,EAAK,OAAAwgB,EAAQ,YAAAC,EAAa,eAAAC,EAAgB,KAAAhG,GAASjmB,EAC3D,GAAI,CAAC+rB,EAAO,OAAQ,OAEpB,MAAMG,EAAuC,CAC3C,QAAO1iB,EAAAxJ,EAAO,OAAP,YAAAwJ,EAAa,QAAS,GAC7B,QAAOyS,EAAAjc,EAAO,OAAP,YAAAic,EAAa,QAAS,GAC7B,WAAUE,EAAAnc,EAAO,OAAP,YAAAmc,EAAa,WAAY,GACnC,WAAUE,EAAArc,EAAO,OAAP,YAAAqc,EAAa,WAAY,GACnC,SAAQG,EAAAxc,EAAO,OAAP,YAAAwc,EAAa,SAAU,EAAA,EAG3B2P,EAAW5gB,EAAI,OACf6gB,EACJD,aAAoB,kBAAoBA,EAAW,KAC/CE,EAASlC,GAAwB,EAgBjChjB,EAAI,CAAE,GAJW,CACrB,GAAGmiB,GACH,UAAW+C,EAAS,UAAY,SAAA,EAEH,GAAGrsB,EAAO,KAAA,EAEzCmH,EAAE,SAAW,CAAE,GAAGmiB,GAAS,SAAU,KAAI7M,EAAAzc,EAAO,QAAP,YAAAyc,EAAc,WAAY,EAAC,EACpEtV,EAAE,WAAa,CAAE,GAAGmiB,GAAS,WAAY,KAAIgD,EAAAtsB,EAAO,QAAP,YAAAssB,EAAc,aAAc,EAAC,EAM1E,IAAIC,EAAe3C,GAAgBziB,EAAE,UAAWilB,CAAS,EACzD,MAAMI,EAAgB5C,GAAgBziB,EAAE,WAAYilB,CAAS,EAC7D,IAAIK,EAAoB7C,GAAgBziB,EAAE,eAAgBilB,CAAS,EAU/DC,GAAUhC,GAAUkC,CAAY,EAClCA,EAAe,UACN,CAACF,GAAU5B,GAAW8B,CAAY,IAC3CA,EAAe,WAObF,GAAUhC,GAAUoC,CAAiB,EACvCA,EAAoB,UACX,CAACJ,GAAU5B,GAAWgC,CAAiB,IAChDA,EAAoB,WAYtB,MAAMC,EAHYX,EAAO,KAAMjR,GAAA,OAC5B,SAAAtR,EAAAwiB,GAAA,YAAAA,EAAclR,EAAE,MAAhB,YAAAtR,EAAqB,QAAS,CAAA,GAAI,KAAMmjB,GAAMA,EAAE,OAAS,QAAQ,EAAA,GAGrDP,EAAY5C,GAAe4C,CAAS,EAAI,KAEvD7gB,EAAI,KAAA,EAIJA,EAAI,UAAU0gB,EAAe,EAAGA,EAAe,CAAC,EAEhD,UAAWL,KAASG,EAAQ,CAU1B,GACE/rB,EAAO,gBAAkB,MACzB4rB,EAAM,KAAO5rB,EAAO,eAEpB,SAGF,MAAMwrB,EAAQQ,GAAA,YAAAA,EAAcJ,EAAM,IAC5BgB,GAAQpB,GAAA,YAAAA,EAAO,QAAS,CAAA,EACxBqB,IAASrB,GAAA,YAAAA,EAAO,SAAU,CAAA,EAEhCjgB,EAAI,KAAA,EACJA,EAAI,UAAUqgB,EAAM,EAAGA,EAAM,CAAC,EAmB9B,MAAM5uB,EAAW4uB,EAAM,UAAY,EAC/B5uB,IAAa,IACfuO,EAAI,UAAUqgB,EAAM,MAAO,CAAC,EAC5BrgB,EAAI,OAAO,KAAK,GAAK,CAAC,GACbvO,IAAa,KACtBuO,EAAI,UAAUqgB,EAAM,MAAOA,EAAM,MAAM,EACvCrgB,EAAI,OAAO,KAAK,EAAE,GACTvO,IAAa,MACtBuO,EAAI,UAAU,EAAGqgB,EAAM,MAAM,EAC7BrgB,EAAI,OAAQ,EAAI,KAAK,GAAM,CAAC,GAK9B,MAAMuhB,EAAU9vB,IAAa,IAAMA,IAAa,IAC1CyuB,EAAWqB,EAAUlB,EAAM,OAASA,EAAM,MAC1CF,GAAWoB,EAAUlB,EAAM,MAAQA,EAAM,OAKzCmB,GAAWxB,GAAgBC,EAAOC,EAAUC,EAAQ,EACpDsB,GAAWrB,GAAgBH,EAAOI,EAAOH,EAAUC,EAAQ,EAM3DuB,GAAiB,GAQjBC,EAAc,KAAK,IAAI,GAAG1B,GAAA,YAAAA,EAAO,cAAe,CAAC,EACjD2B,GACJD,EAAc,EACV,CACE,KAAM,cACN,EAAG,EACH,EAAG,EACH,MAAOzB,EACP,OAAQC,GACR,GAAIwB,EACJ,GAAIA,CAAA,EAEN,CACE,KAAM,OACN,EAAG,EACH,EAAG,EACH,MAAOzB,EACP,OAAQC,EAAA,EA2BhB,GAnBIwB,EAAc,IAChB3hB,EAAI,KAAA,EACJA,EAAI,UAAA,EACJ8f,GAAkB9f,EAAK4hB,EAAU,EACjC5hB,EAAI,KAAA,GAeF2gB,EAAK,OAAS/kB,EAAE,aAAe,EAAG,CACpC,MAAMimB,GACJjmB,EAAE,iBAAmB,YAAc6lB,GAAWA,GAAWD,GAM3D,GAAIK,GAAW,CAMb,MAAMC,GAAuChB,EACzC,SACA,WASEiB,GAAwBnmB,EAAE,aAM5BA,EAAE,YAAc,QAClBomB,GACEhiB,EACA4hB,GACAC,GACAb,EACAe,GACAD,EAAA,EAGFG,GACEjiB,EACAkgB,EACAC,GACAyB,GACAC,GACAb,EACAe,GACAnmB,EAAE,eACF8e,EACAoH,EAAA,CAGN,CACF,CAGA,GAAInB,EAAK,MACP,UAAWuB,MAAQb,EACba,GAAK,OAAS,WAClBliB,EAAI,KAAA,EAIJA,EAAI,UAAYpE,EAAE,SAASsmB,GAAK,IAAI,GAAKnE,GAAS,SAASmE,GAAK,IAAI,EACpEvC,GAAY3f,EAAKkiB,GAAK,IAAI,EAC1BliB,EAAI,YAAcpE,EAAE,WAAWsmB,GAAK,IAAI,GAAKnE,GAAS,WAAWmE,GAAK,IAAI,EAC1EC,EAAAA,uBACEniB,EACA,IACAkiB,GAAK,OAAS,OAAS,CAAC,EAAG,CAAC,EAAI,CAAA,CAAC,EAEnCrC,GAAc7f,EAAKkiB,GAAK,IAAI,EAC5BliB,EAAI,YAAY,EAAE,EAClBA,EAAI,QAAA,GAKR,GAAI2gB,EAAK,MACP,UAAWuB,MAAQb,EACba,GAAK,OAAS,WACdtmB,EAAE,aAAe,WASnBwmB,GACEpiB,EACAkiB,GACAtmB,EAAE,aACFA,EAAE,oBACFnH,EAAO,cAAgB,GACvBitB,GACAP,EACAvlB,EAAE,YAAA,EAGJymB,GACEriB,EACAkiB,GACAf,EACAF,EACArlB,EAAE,gBACFA,EAAE,aACFA,EAAE,aACFA,EAAE,mBAAA,GAQN+kB,EAAK,UAAYO,GAAqBtlB,EAAE,sBAAwB,IAClEoE,EAAI,KAAA,EACJA,EAAI,YAAckhB,EAClBiB,yBAAuBniB,EAAKpE,EAAE,qBAAqB,EACnDikB,GAAc7f,EAAKwhB,EAAQ,EAC3BxhB,EAAI,QAAA,GAIF2gB,EAAK,UAAYc,IAAY7lB,EAAE,iBACjCoE,EAAI,KAAA,EACJA,EAAI,YAAcpE,EAAE,eACpBumB,EAAAA,uBAAuBniB,EAAKpE,EAAE,sBAAuBA,EAAE,cAAc,EACrEikB,GAAc7f,EAAKyhB,EAAQ,EAC3BzhB,EAAI,YAAY,EAAE,EAClBA,EAAI,QAAA,GAIF2gB,EAAK,QAAUW,GAAO,OAAS,GACjCgB,GAAWtiB,EAAKshB,GAAQ1lB,EAAG8e,CAAI,EAyB7BiH,EAAc,GAChB3hB,EAAI,QAAA,EAGNA,EAAI,QAAA,CACN,CAEAA,EAAI,QAAA,CACN,CA+BA,SAASiiB,GACPjiB,EAOAkgB,EACAC,EACAyB,EACAJ,EACAe,EACAC,EACAC,EACA/H,EAOAgI,EAAsC,cAChC,CACN,MAAMC,EAAc,KAAK,IACvB,EACA,KAAK,MAAM,KAAK,IAAIzC,EAAUC,CAAQ,EAAIsC,CAAS,CAAA,EAE/CG,EAAYlI,EAAO,EAAIiI,EAAcjI,EAAOiI,EAE5CE,EACJ,OAAO,OAAW,KAAc,OAAO,kBAAoB,EAKvDC,EAAMH,EACNI,EAAU7C,EAAW,EAAI4C,EACzBE,EAAU7C,EAAW,EAAI2C,EACzBG,EAAQ,KAAK,IAAI,EAAG,KAAK,KAAKF,EAAUrI,EAAOmI,CAAG,CAAC,EACnDK,EAAQ,KAAK,IAAI,EAAG,KAAK,KAAKF,EAAUtI,EAAOmI,CAAG,CAAC,EAEnDM,EAAMC,GAAiBH,EAAOC,CAAK,EACzC,GAAI,CAACC,EAAK,OACV,MAAME,EAAOF,EAAI,WAAW,IAAI,EAChC,GAAI,CAACE,EAAM,OAEXA,EAAK,aAAa,EAAG,EAAG,EAAG,EAAG,EAAG,CAAC,EAClCA,EAAK,UAAU,EAAG,EAAGJ,EAAOC,CAAK,EAIjCG,EAAK,MAAM3I,EAAOmI,EAAKnI,EAAOmI,CAAG,EACjCQ,EAAK,UAAUP,EAAKA,CAAG,EAWvBO,EAAK,UAAYd,EACjBc,EAAK,YAAcb,EACnB,MAAMc,EAAY,IAAI,OACtBvD,GAAYuD,EAAW1B,CAAU,EACjCyB,EAAK,KAAKC,CAAS,EAMnBD,EAAK,YAAc,EACnBA,EAAK,OAAS,QAAQT,EAAYC,CAAG,MACrCQ,EAAK,yBAA2B,kBAChCA,EAAK,UAAY,OACjB,MAAME,EAAe,IAAI,OACzBxD,GAAYwD,EAAc/B,CAAQ,EAClC6B,EAAK,KAAKE,CAAY,EACtBF,EAAK,OAAS,OACdA,EAAK,yBAA2B,cAShC,MAAMG,EAASxjB,EAAI,yBACnBA,EAAI,yBAA2B0iB,EAC/B1iB,EAAI,UAAUmjB,EAAK,CAACL,EAAK,CAACA,EAAKC,EAASC,CAAO,EAC/ChjB,EAAI,yBAA2BwjB,CACjC,CASA,SAASxB,GACPhiB,EACA4hB,EACAJ,EACAe,EACAC,EAEAE,EAAsC,cAChC,CACN1iB,EAAI,KAAA,EAIJA,EAAI,YAAcA,EAAI,YAAcwiB,EACpCxiB,EAAI,yBAA2B0iB,EAC/B1iB,EAAI,UAAYuiB,EAIhB,MAAMkB,EAAW,IAAI,OACrB1D,GAAY0D,EAAU7B,CAAU,EAChC7B,GAAY0D,EAAUjC,CAAQ,EAC9BxhB,EAAI,KAAKyjB,EAAU,SAAS,EAC5BzjB,EAAI,QAAA,CACN,CAMA,IAAI0jB,GAAiD,KACrD,SAASN,GACPH,EACAC,EAC0B,CAC1B,OAAI,OAAO,SAAa,IAAoB,MACvCQ,KACHA,GAAuB,SAAS,cAAc,QAAQ,GAEpDA,GAAqB,QAAUT,IACjCS,GAAqB,MAAQT,GAC3BS,GAAqB,SAAWR,IAClCQ,GAAqB,OAASR,GACzBQ,GACT,CAoBA,SAASrB,GACPriB,EACAkiB,EACAf,EACAwC,EACAC,EACAhB,EACAiB,EACAC,EACM,CACN,MAAMvU,EAAI2S,EAAK,KAUf,GARAliB,EAAI,KAAA,EAIJA,EAAI,UAAA,EACJuf,GAAavf,EAAKuP,CAAC,EACnBvP,EAAI,KAAA,EAEAmhB,IAaFnhB,EAAI,KAAA,EACJA,EAAI,yBAA2B,kBAC/BA,EAAI,UAAY,OACZuP,EAAE,OAAS,OACboQ,GAAY3f,EAAKuP,CAAC,EAElBvP,EAAI,SAASuP,EAAE,EAAGA,EAAE,EAAGA,EAAE,MAAOA,EAAE,MAAM,EAE1CvP,EAAI,QAAA,EAQA4iB,EAAY,GAAG,CACjB,MAAMmB,EAAY/jB,EAAI,YAChBuK,EAAIvK,EAAI,aAAA,EACdA,EAAI,KAAA,EACJA,EAAI,aAAa,EAAG,EAAG,EAAG,EAAG,EAAG,CAAC,EACjCA,EAAI,OAAS,QAAQ4iB,CAAS,MAY9B5iB,EAAI,YAAc+jB,EAAY,GAC9B/jB,EAAI,UAAUmhB,EAAU,EAAG,CAAC,EAC5BnhB,EAAI,OAAS,OACbA,EAAI,YAAc+jB,EAClB/jB,EAAI,aAAauK,CAAC,EAClBvK,EAAI,QAAA,CACN,CAOF,GAAI4jB,EAAY,EAAG,CACjB,MAAMG,EAAY/jB,EAAI,YACtBA,EAAI,UAAY2jB,EAChB3jB,EAAI,YAAc+jB,EAAYH,EAC1BrU,EAAE,OAAS,OACboQ,GAAY3f,EAAKuP,CAAC,EAElBvP,EAAI,SAASuP,EAAE,EAAGA,EAAE,EAAGA,EAAE,MAAOA,EAAE,MAAM,EAE1CvP,EAAI,YAAc+jB,CACpB,CAEA/jB,EAAI,QAAA,EAMA6jB,IACF7jB,EAAI,KAAA,EACJA,EAAI,YAAc6jB,EAClB1B,EAAAA,uBAAuBniB,EAAK8jB,CAAa,EACzCjE,GAAc7f,EAAKuP,CAAC,EACpBvP,EAAI,QAAA,EAER,CAqBA,MAAMgkB,GAAsC,GAE5C,SAAS5B,GACPpiB,EACAkiB,EACA2B,EACAC,EACAG,EAQAvC,EAA0B,GAQ1BP,EACAyB,EAAoB,EACd,CACN,MAAMrT,EAAI2S,EAAK,KACf,GAAIR,EACF,GAAIuC,GAAe9C,GAAYyB,EAAY,EAAG,CAQ5C5iB,EAAI,KAAA,EACJA,EAAI,UAAA,EACJuf,GAAavf,EAAKuP,CAAC,EACnBvP,EAAI,KAAA,EAGJA,EAAI,KAAA,EACJA,EAAI,yBAA2B,kBAC/BA,EAAI,UAAY,OACZuP,EAAE,OAAS,QAAUA,EAAE,OAAS,cAClCoQ,GAAY3f,EAAKuP,CAAC,EAElBvP,EAAI,SAASuP,EAAE,EAAGA,EAAE,EAAGA,EAAE,MAAOA,EAAE,MAAM,EAE1CvP,EAAI,QAAA,EAGJ,MAAM+jB,EAAY/jB,EAAI,YAChBuK,EAAIvK,EAAI,aAAA,EACdA,EAAI,KAAA,EACJA,EAAI,aAAa,EAAG,EAAG,EAAG,EAAG,EAAG,CAAC,EACjCA,EAAI,OAAS,QAAQ4iB,CAAS,MAC9B5iB,EAAI,YAAc+jB,EAAY,GAC9B/jB,EAAI,UAAUmhB,EAAU,EAAG,CAAC,EAC5BnhB,EAAI,OAAS,OACbA,EAAI,YAAc+jB,EAClB/jB,EAAI,aAAauK,CAAC,EAClBvK,EAAI,QAAA,EAEJA,EAAI,QAAA,CACN,MAGEA,EAAI,KAAA,EACJA,EAAI,yBAA2B,kBAC/BA,EAAI,YAAcikB,EACd,EAAID,GACJ,EACJhkB,EAAI,UAAY,OACZuP,EAAE,OAAS,QAAUA,EAAE,OAAS,cAClCoQ,GAAY3f,EAAKuP,CAAC,EAElBvP,EAAI,SAASuP,EAAE,EAAGA,EAAE,EAAGA,EAAE,MAAOA,EAAE,MAAM,EAE1CvP,EAAI,QAAA,EAGJ6jB,IACF7jB,EAAI,KAAA,EACJA,EAAI,YAAc6jB,EAClB1B,EAAAA,uBAAuBniB,EAAK8jB,CAAa,EACzCjE,GAAc7f,EAAKuP,CAAC,EACpBvP,EAAI,QAAA,EAER,CAEA,SAASsiB,GACPtiB,EACAshB,EACA1lB,EACA8e,EACM,CAGN,MAAMwJ,EAAcxJ,EAAO,EAAI9e,EAAE,gBAAkB8e,EAAO9e,EAAE,gBACtDuoB,EAAY,KAAK,IAAI,EAAGvoB,EAAE,gBAAkB,GAAI,EAChDwoB,EAAc1J,EAAO,EAAIyJ,EAAYzJ,EAAOyJ,EAElDnkB,EAAI,KAAA,EACJA,EAAI,KAAO,GAAGkkB,CAAW,MAAMtoB,EAAE,eAAe,GAEhD,UAAW6F,KAAS6f,EAAQ,CAC1B,MAAM3L,EAASlU,EAAM,QAAU,IAC/BzB,EAAI,UAAYqkB,GAAc1O,CAAM,EACpC3V,EAAI,aAAeskB,GAAiB3O,CAAM,EAK1C3V,EAAI,SAAW,QACfA,EAAI,YAAcpE,EAAE,eACpBoE,EAAI,UAAYokB,EAChBpkB,EAAI,WAAWyB,EAAM,KAAMA,EAAM,EAAGA,EAAM,CAAC,EAC3CzB,EAAI,UAAYpE,EAAE,WAClBoE,EAAI,SAASyB,EAAM,KAAMA,EAAM,EAAGA,EAAM,CAAC,CAC3C,CACAzB,EAAI,QAAA,CACN,CAEA,SAASqkB,GAAcxuB,EAAuD,CAC5E,OAAIA,EAAE,SAAS,GAAG,EAAU,QACxBA,EAAE,SAAS,GAAG,EAAU,MACrB,QACT,CAEA,SAASyuB,GACPzuB,EACoB,CACpB,OAAIA,EAAE,WAAW,GAAG,EAAU,UAC1BA,EAAE,WAAW,GAAG,EAAU,aACvB,QACT,CCtyCO,SAAS0uB,GAA+B9vB,EAAgE,CAC7G,KAAM,CACJ,IAAAuL,EACA,iBAAAwkB,EACA,SAAApiB,EACA,yBAAAqiB,EACA,aAAAC,EACA,kBAAAC,EACA,WAAA7H,EACA,gBAAAI,EACA,mBAAA9O,EACA,KAAAsM,EAAO,CAAA,EACLjmB,EAGEmwB,EAA2BJ,EAAiB,OAAQ7xB,IAAO8xB,EAAyB,IAAI9xB,EAAE,CAAC,EAEjG,GAAIiyB,EAAyB,SAAW,EACtC,MAAO,CAAE,WAAY,KAAM,YAAa,IAAA,EAI1C,MAAMC,EAAoBH,EAAuC,2BAC3DI,EAA2BJ,EAAa,QAAA,IAAc,QAAUG,EAGtED,EAAyB,QAASjyB,IAAO,CACvC,IAAI4N,GAAU6B,EAAS,KAAMiH,IAAMA,GAAE,KAAO1W,EAAE,EAG9C,GAAImyB,GAA4BD,EAAiB,gBAAiB,CAChE,MAAME,GAAgBF,EAAiB,gBAAgB,IAAIlyB,EAAE,EACzDoyB,KACFxkB,GAAUwkB,GAEd,CAEA,GAAIxkB,GAAS,CACX,MAAMO,GAAOP,GAAQ,qBAAA,EACfykB,GAAiBzkB,GAAQ,kBAAA,EAE/BP,EAAI,KAAA,EAGAO,GAAQ,WAAa,IACvBP,EAAI,UAAUglB,GAAe,EAAGA,GAAe,CAAC,EAChDhlB,EAAI,OAAQ,CAACO,GAAQ,SAAW,KAAK,GAAM,GAAG,EAC9CP,EAAI,UAAU,CAACglB,GAAe,EAAG,CAACA,GAAe,CAAC,GAGpDhlB,EAAI,YAAcmR,sBAAA,EAClBnR,EAAI,UAAYilB,GAAAA,aAAavK,CAAI,EACjC1a,EAAI,WAAWc,GAAK,EAAGA,GAAK,EAAGA,GAAK,MAAOA,GAAK,MAAM,EACtDd,EAAI,QAAA,CACN,CACF,CAAC,EAGD,IAAIklB,EAAmBN,EACpB,IAAKjyB,IAAOyP,EAAS,KAAMiH,IAAMA,GAAE,KAAO1W,EAAE,CAAC,EAC7C,OAAQ0W,IAAMA,KAAM,MAAS,EAUhC,GAPIyb,GAA4BD,EAAiB,kBAC/CK,EAAmBA,EAAiB,IAAK3kB,IACjBskB,EAAiB,gBAAiB,IAAItkB,GAAQ,EAAE,GAC9CA,EACzB,GAGC2kB,EAAiB,SAAW,EAC9B,MAAO,CAAE,WAAY,KAAM,YAAa,IAAA,EAI1C,IAAIxe,EAAO,IACPC,EAAO,IACPiI,EAAO,KACPC,EAAO,KAEXqW,EAAiB,QAAS3kB,IAAY,CACpC,MAAMO,GAAOP,GAAQ,qBAAA,EAEf4kB,GAAU,CACd,CAAE,EAAGrkB,GAAK,EAAG,EAAGA,GAAK,CAAA,EACrB,CAAE,EAAGA,GAAK,EAAIA,GAAK,MAAO,EAAGA,GAAK,CAAA,EAClC,CAAE,EAAGA,GAAK,EAAG,EAAGA,GAAK,EAAIA,GAAK,MAAA,EAC9B,CAAE,EAAGA,GAAK,EAAIA,GAAK,MAAO,EAAGA,GAAK,EAAIA,GAAK,MAAA,CAAO,EAIpD,GAAIP,GAAQ,WAAa,EAAG,CAC1B,MAAMykB,GAAiBzkB,GAAQ,kBAAA,EACzB6kB,GAAe,CAAC7kB,GAAQ,SAAW,KAAK,GAAM,IAC9CuT,GAAM,KAAK,IAAIsR,EAAW,EAC1BrR,GAAM,KAAK,IAAIqR,EAAW,EAEhCD,GAAQ,QAASE,IAAW,CAC1B,MAAM9b,GAAK8b,GAAO,EAAIL,GAAe,EAC/Bxb,EAAK6b,GAAO,EAAIL,GAAe,EACrCK,GAAO,EAAIL,GAAe,GAAKzb,GAAKuK,GAAMtK,EAAKuK,IAC/CsR,GAAO,EAAIL,GAAe,GAAKzb,GAAKwK,GAAMvK,EAAKsK,GACjD,CAAC,CACH,CAGAqR,GAAQ,QAASE,IAAW,CAC1B3e,EAAO,KAAK,IAAIA,EAAM2e,GAAO,CAAC,EAC9B1e,EAAO,KAAK,IAAIA,EAAM0e,GAAO,CAAC,EAC9BzW,EAAO,KAAK,IAAIA,EAAMyW,GAAO,CAAC,EAC9BxW,EAAO,KAAK,IAAIA,EAAMwW,GAAO,CAAC,CAChC,CAAC,CACH,CAAC,EAED,MAAMC,EAAa1W,EAAOlI,EACpB6e,EAAc1W,EAAOlI,EACrBoJ,GAAWrJ,EAAOkI,GAAQ,EAC1BoB,GAAWrJ,EAAOkI,GAAQ,EAGhC,IAAI2W,EAAMb,EAeV,IAZI,CAACa,GAAOA,EAAI,QAAU,GAAKA,EAAI,SAAW,KAC5CA,EAAM,CACJ,QAAAzV,EACA,QAAAC,EACA,MAAOsV,EACP,OAAQC,EACR,SAAU,CAAA,GAKcb,EAAa,QAAA,IAAc,QAAWA,EAAuC,2BAChF,CACvB,MAAMe,GAAef,EAAuC,2BAE5D,GAAIe,IAAA,MAAAA,GAAa,WAAYA,IAAA,MAAAA,GAAa,cAAc,CACtD,KAAM,CAAE,GAAAlc,GAAI,GAAAC,EAAA,EAAOic,GAAY,aAC/BD,EAAM,CACJ,GAAGA,EACH,QAASC,GAAY,SAAS,QAAUlc,GACxC,QAASkc,GAAY,SAAS,QAAUjc,EAAA,CAE5C,MAEEgc,EAAM,CAAE,GAAGA,EAAK,QAAAzV,EAAS,QAAAC,CAAA,CAE7B,CAIA,MAAM0V,EADsB5I,GAAc4H,EAAa,QAAA,IAAc,SACzBc,EAAI,SAAWtI,EAAkBsI,EAAI,SAG3EG,GAAcH,EAAI,MAClBI,EAAeJ,EAAI,OACnBK,EAAgBL,EAAI,QACpBM,EAAgBN,EAAI,QACpBO,GAAaF,EAAgBF,GAAc,EAC3CK,GAAaF,EAAgBF,EAAe,EAC5CK,GAAaJ,EAAgBF,GAAc,EAC3CO,GAAaJ,EAAgBF,EAAe,EAGlD,IAAIO,EAAkBN,EAClBO,GAAkBF,GAAaG,EAAAA,yBAEnC,GAAIX,IAAkB,EAAG,CACvB,MAAMN,GAAe,CAACM,EAAgB,KAAK,GAAM,IAC3C5R,GAAM,KAAK,IAAIsR,EAAW,EAC1BrR,GAAM,KAAK,IAAIqR,EAAW,EAC1B7b,GAAK4c,EAAkBN,EACvBrc,GAAK4c,GAAkBN,EAC7BK,EAAkBN,GAAiBtc,GAAKuK,GAAMtK,GAAKuK,IACnDqS,GAAkBN,GAAiBvc,GAAKwK,GAAMvK,GAAKsK,GACrD,CAGA,MAAMwS,GAAyC,CAC7C,KAAMP,GACN,KAAMC,GACN,KAAMC,GACN,KAAMC,GACN,QAASL,EACT,QAASC,EACT,gBAAAK,EACA,gBAAAC,GACA,SAAUV,CAAA,EAIZ,OAAA1lB,EAAI,KAAA,EACJA,EAAI,UAAU6lB,EAAeC,CAAa,EAC1C9lB,EAAI,OAAQ,CAAC0lB,EAAgB,KAAK,GAAM,GAAG,EAC3C1lB,EAAI,UAAU,CAAC6lB,EAAe,CAACC,CAAa,EAE5C9lB,EAAI,YAAcmR,sBAAA,EAClBnR,EAAI,UAAYilB,GAAAA,aAAavK,CAAI,EACjC1a,EAAI,YAAYumB,kBAAe7L,CAAI,CAAC,EACpC1a,EAAI,WAAW+lB,GAAYC,GAAYL,GAAaC,CAAY,EAChE5lB,EAAI,QAAA,EAGCoO,GACHoY,GAA4BxmB,EAAK,CAC/B,WAAA+lB,GACA,WAAAC,GACA,WAAAC,GACA,WAAAC,GACA,cAAAL,EACA,cAAAC,EACA,YAAAH,GACA,aAAAC,EACA,cAAAF,EACA,gBAAAS,EACA,gBAAAC,GACA,aAAA1B,EACA,KAAAhK,CAAA,CACD,EAGI,CAAE,WAAY8K,EAAK,YAAAc,EAAA,CAC5B,CAqBA,SAASE,GAA4BxmB,EAA+BvL,EAAmC,OACrG,KAAM,CACJ,WAAAsxB,EACA,WAAAC,EACA,WAAAC,EACA,WAAAC,EACA,cAAAL,EACA,cAAAC,EACA,YAAAH,EACA,aAAAC,EACA,cAAAF,EACA,gBAAAS,EACA,gBAAAC,EACA,aAAA1B,EACA,KAAAhK,CAAA,EACEjmB,EAGEgyB,EAAeC,GAAAA,gBAAgBhM,CAAI,EACnCyK,EAAU,CACd,CAAE,EAAGY,EAAY,EAAGC,EAAY,OAAQ,UAAA,EACxC,CAAE,EAAGC,EAAY,EAAGD,EAAY,OAAQ,WAAA,EACxC,CAAE,EAAGD,EAAY,EAAGG,EAAY,OAAQ,aAAA,EACxC,CAAE,EAAGD,EAAY,EAAGC,EAAY,OAAQ,cAAA,CAAe,EAGnDS,EAA2BjC,EAAa,QAAA,IAAc,SACtDkC,EAAeD,GAA4B1oB,EAAAymB,EAAuC,+BAAvC,YAAAzmB,EAAqE,OAAS,KAE/HknB,EAAQ,QAASE,GAAW,CAE1B,GAAIsB,GAA4BC,GAAgBvB,EAAO,SAAWuB,EAChE,OAIF,IAAIC,EAAUxB,EAAO,EACjByB,EAAUzB,EAAO,EAErB,GAAIK,IAAkB,EAAG,CACvB,MAAMN,EAAe,CAACM,EAAgB,KAAK,GAAM,IAC3C5R,EAAM,KAAK,IAAIsR,CAAW,EAC1BrR,EAAM,KAAK,IAAIqR,CAAW,EAC1B7b,EAAK8b,EAAO,EAAIQ,EAChBrc,GAAK6b,EAAO,EAAIS,EACtBe,EAAUhB,GAAiBtc,EAAKuK,EAAMtK,GAAKuK,GAC3C+S,EAAUhB,GAAiBvc,EAAKwK,EAAMvK,GAAKsK,EAC7C,CAEA9T,EAAI,KAAA,EAEJA,EAAI,UAAA,EACJA,EAAI,IAAI6mB,EAASC,EAASL,EAAc,EAAG,KAAK,GAAK,CAAC,EACtDzmB,EAAI,UAAY,OAChBA,EAAI,KAAA,EACJA,EAAI,YAAcmR,sBAAA,EAClBnR,EAAI,UAAY2mB,GAA4BC,GAAgBvB,EAAO,SAAWuB,EAC1E3B,GAAAA,aAAavK,CAAI,EAAI,KACrBuK,GAAAA,aAAavK,CAAI,EACrB1a,EAAI,OAAA,EACJA,EAAI,QAAA,CACN,CAAC,EAII2mB,GACHI,GAAAA,qBAAqB/mB,EAAKmmB,EAAiBC,EAAiB1L,EAAMvJ,EAAAA,qBAAqB,EAIrFuT,EAAa,QAAA,IAAc,UAC7BsC,GAAqBhnB,EAAK6lB,EAAeK,EAAYP,EAAaC,CAAY,CAElF,CAKA,SAASoB,GACPhnB,EACA+P,EACAlB,EACA9c,EACAC,EACM,CACN,MAAMi1B,EAAW,GAAG,KAAK,MAAMl1B,CAAK,CAAC,MAAM,KAAK,MAAMC,CAAM,CAAC,GAGvDk1B,EAAWnX,EACXoX,EAAWtY,EAAO,GAExB7O,EAAI,KAAA,EAGJA,EAAI,KAAO,4CAEX,MAAMonB,EADcpnB,EAAI,YAAYinB,CAAQ,EACX,MAAQ,GACnCI,EAAgB,GAGtBrnB,EAAI,YAAc,kBAClBA,EAAI,WAAa,EACjBA,EAAI,cAAgB,EAGpBA,EAAI,UAAY,OAChBA,EAAI,UAAA,EACJA,EAAI,UAAUknB,EAAWE,EAAe,EAAGD,EAAWE,EAAgB,EAAGD,EAAcC,EAAe,CAAC,EACvGrnB,EAAI,KAAA,EAGJA,EAAI,YAAc,cAClBA,EAAI,WAAa,EACjBA,EAAI,cAAgB,EAGpBA,EAAI,UAAY,OAChBA,EAAI,UAAY,SAChBA,EAAI,aAAe,SACnBA,EAAI,SAASinB,EAAUC,EAAUC,EAAW,EAAG,EAE/CnnB,EAAI,QAAA,CACN,CCxbO,SAASsnB,GAAuB7yB,EAAmC,CACxE,KAAM,CAAE,IAAAuL,EAAK,aAAAsO,EAAc,WAAAE,EAAY,KAAAkM,EAAO,GAAQjmB,EAEhDiS,EAAO,KAAK,IAAI4H,EAAa,EAAGE,EAAW,CAAC,EAC5CI,EAAO,KAAK,IAAIN,EAAa,EAAGE,EAAW,CAAC,EAC5C7H,EAAO,KAAK,IAAI2H,EAAa,EAAGE,EAAW,CAAC,EAC5CK,EAAO,KAAK,IAAIP,EAAa,EAAGE,EAAW,CAAC,EAElDxO,EAAI,KAAA,EACJA,EAAI,YAAcmR,sBAAA,EAClBnR,EAAI,UAAYilB,GAAAA,aAAavK,CAAI,EACjC1a,EAAI,WAAW0G,EAAMC,EAAMiI,EAAOlI,EAAMmI,EAAOlI,CAAI,EAGnD3G,EAAI,UAAYunB,EAAAA,6BAA6B,EAAG,EAChDvnB,EAAI,SAAS0G,EAAMC,EAAMiI,EAAOlI,EAAMmI,EAAOlI,CAAI,EACjD3G,EAAI,QAAA,CACN,CChBO,SAASwnB,GAAwB/yB,EAA6C,CACnF,KAAM,CAAE,IAAAuL,EAAK,aAAAynB,EAAc,KAAA/M,EAAO,GAAQjmB,EACpCqM,EAAO2mB,EAAa,qBAAA,EAE1BznB,EAAI,KAAA,EACJA,EAAI,UAAUc,EAAK,EAAIA,EAAK,MAAQ,EAAGA,EAAK,EAAIA,EAAK,OAAS,CAAC,EAC/Dd,EAAI,OAAQ,CAACynB,EAAa,SAAW,KAAK,GAAM,GAAG,EACnDznB,EAAI,UAAU,EAAEc,EAAK,EAAIA,EAAK,MAAQ,GAAI,EAAEA,EAAK,EAAIA,EAAK,OAAS,EAAE,EAGrEd,EAAI,YAAc0nB,2BAAA,EAClB1nB,EAAI,UAAYilB,GAAAA,aAAavK,CAAI,EACjC1a,EAAI,YAAY,EAAE,EAClBA,EAAI,WAAWc,EAAK,EAAGA,EAAK,EAAGA,EAAK,MAAOA,EAAK,MAAM,EAEtDd,EAAI,QAAA,CACN,CCHA,MAAM2nB,GAAsB,GAErB,MAAMC,EAAmB,CAOtB,aAAc,CACpB,KAAK,WAAa,CAAA,EAClB,KAAK,QAAU,CACb,UAAW,EACX,WAAY,EACZ,aAAc,EACd,aAAc,EACd,mBAAoB,CAAA,CAExB,CAEA,OAAO,aAAkC,CACvC,OAAKA,GAAmB,WACtBA,GAAmB,SAAW,IAAIA,IAE7BA,GAAmB,QAC5B,CAMA,gBAAgBC,EAAkB,CAChC,KAAK,WAAW,KAAKA,CAAE,EACnB,KAAK,WAAW,OAASF,IAC3B,KAAK,WAAW,MAAA,EAElB,KAAK,QAAQ,aACb,KAAK,QAAQ,UAAY,KAAK,SAAS,KAAK,UAAU,CACxD,CAGA,qBAAqBE,EAAkB,CACrC,KAAK,QAAQ,mBAAqBA,CACpC,CAGA,gBAAgBC,EAAqB,CACnC,KAAK,QAAQ,aAAeA,CAC9B,CAGA,gBAAgBC,EAAoB,CAClC,KAAK,QAAQ,aAAeA,CAC9B,CAGA,YAAiD,CAC/C,MAAO,CAAE,GAAG,KAAK,OAAA,CACnB,CAGA,OAAc,CACZ,KAAK,WAAa,CAAA,EAClB,KAAK,QAAU,CACb,UAAW,EACX,WAAY,EACZ,aAAc,EACd,aAAc,EACd,mBAAoB,CAAA,CAExB,CAIQ,SAASC,EAA0B,CACzC,GAAIA,EAAO,SAAW,EAAG,MAAO,GAChC,IAAIC,EAAM,EACV,QAAShc,EAAI,EAAGA,EAAI+b,EAAO,OAAQ/b,IACjCgc,GAAOD,EAAO/b,CAAC,EAEjB,OAAOgc,EAAMD,EAAO,MACtB,CACF,CC5FO,SAASE,GACd1H,EACA2H,EACAC,EACuB,CAYvB,GAXI5H,EAAO,SAAW,GAWlB,CAVgBA,EAAO,MAAOjR,GAAM,OAEtC,MAAMzZ,GAAKmI,EAAAkqB,GAAA,YAAAA,EAAS5Y,EAAE,MAAX,YAAAtR,EAAwB,SACnC,OACEnI,GACA,OAAOA,GAAM,UACbA,EAAE,OAAS,QACX,OAAOA,EAAE,GAAM,QAEnB,CAAC,EACiB,OAElB,MAAMuyB,EAAUD,EACZ5H,EAAO,OAAQjR,GAAMA,EAAE,KAAO6Y,CAAc,EAC5C5H,EAMJ,MAAO,CACL,KAAM,iBACN,QAJgB6H,EAAQ,OAAS,EAAIA,EAAU7H,GAI7B,IAAKjR,GAAM,OAE3B,MAAMiS,GAAYvjB,EAAAkqB,GAAA,YAAAA,EAAS5Y,EAAE,MAAX,YAAAtR,EAAwB,SACpCqqB,GAAQ/Y,EAAE,UAAY,GAAK,IAC3BgZ,EAAOD,IAAQ,IAAMA,IAAQ,IAKnC,MAAO,CACL,EAAG9G,EAAS,EACZ,EAAGjS,EAAE,EACL,EAAGA,EAAE,EACL,SAAU+Y,EACV,UAAWC,EAAOhZ,EAAE,OAASA,EAAE,MAC/B,WAAYgZ,EAAOhZ,EAAE,MAAQA,EAAE,MAAA,CAEnC,CAAC,CAAA,CAEL,CC8FO,SAASiZ,GAAoB/zB,EAAyC,CAE3E,MAAMg0B,EAAYzkB,EAAAA,OAAOvP,CAAM,EAC/Bg0B,EAAU,QAAUh0B,EAGpB,MAAMi0B,EAAW1kB,EAAAA,OAAO,EAAI,EAGtB2kB,EAAe3kB,EAAAA,OAAyB,IAAI,EAI5C4kB,EAAmB5kB,EAAAA,OAAO,SAAS,EACzC9F,EAAAA,UAAU,IAAM,CACd,MAAM8H,EAAgByiB,EAAU,QAAQ,UAAU,SAAW,SAAS,gBAChEI,EAAQ,iBAAiB7iB,CAAa,EAAE,iBAAiB,mBAAmB,EAAE,KAAA,EACpF4iB,EAAiB,QAAUC,GAAS,SACtC,EAAG,CAACp0B,EAAO,aAAa,CAAC,EAKzByJ,EAAAA,UAAU,IAAM,CACdwqB,EAAS,QAAU,EACrB,EAAG,CACDj0B,EAAO,mBACPA,EAAO,UACPA,EAAO,WACPA,EAAO,gBACPA,EAAO,mBACPA,EAAO,WACPA,EAAO,KACPA,EAAO,cACPA,EAAO,cACPA,EAAO,WAAA,CACR,EAKDyJ,EAAAA,UAAU,IAAM,CACdwqB,EAAS,QAAU,EACrB,EAAG,CACDj0B,EAAO,iBACPA,EAAO,gBACPA,EAAO,WACPA,EAAO,WACPA,EAAO,UACPA,EAAO,SACPA,EAAO,aACPA,EAAO,eACPA,EAAO,eACPA,EAAO,aACPA,EAAO,aACPA,EAAO,WACPA,EAAO,kBACPA,EAAO,eACPA,EAAO,eACPA,EAAO,mBACPA,EAAO,aACPA,EAAO,WACPA,EAAO,wBACPA,EAAO,gBACPA,EAAO,QACPA,EAAO,oBAAA,CACR,EAGDyJ,EAAAA,UAAU,IAAM,CACd,IAAI4qB,EAIJ,MAAMC,EAAa,IAAM,CAGvB,KAAM,CAAE,WAAA/V,GAAeyV,EAAU,QAQjC,GAPIzV,EAAW,YAAc,MAAQA,EAAW,OAAS,MACvC,YAAY,IAAA,EAAQA,EAAW,UACjC,MACZ0V,EAAS,QAAU,IAInBA,EAAS,QAAS,CACpBA,EAAS,QAAU,GACnB,MAAMM,EAAK,YAAY,IAAA,EACvBC,GAAYR,EAAU,QAASG,EAAkBD,CAAY,EAC7Df,GAAmB,cAAc,gBAAgB,YAAY,IAAA,EAAQoB,CAAE,CACzE,CACAF,EAAU,sBAAsBC,CAAU,CAC5C,EAEA,OAAAD,EAAU,sBAAsBC,CAAU,EACnC,IAAM,qBAAqBD,CAAO,CAC3C,EAAG,CAAA,CAAE,CACP,CAMA,SAASG,GACPx0B,EACAm0B,EACAD,EACM,cACN,KAAM,CACJ,UAAAO,EACA,SAAA9mB,EACA,mBAAA+mB,EACA,UAAAnO,EACA,gBAAAxX,EACA,iBAAA4lB,EACA,WAAA/mB,EACA,gBAAAmG,EACA,mBAAAC,EACA,kBAAAkK,EACA,iBAAAH,EACA,aAAAkS,EACA,kBAAA1I,EACA,mBAAAC,EACA,eAAAG,EACA,eAAAC,EACA,KAAA3B,EACA,0BAAA4B,EACA,qBAAAX,EACA,mBAAA0N,EACA,WAAAvM,EACA,gBAAAI,EACA,WAAAlK,EACA,UAAA2F,EACA,SAAAE,GACA,aAAAc,EACA,eAAAV,EACA,eAAAE,EACA,aAAAE,GACA,iBAAAE,GACA,mBAAAC,GACA,cAAA0B,GACA,4BAAAoO,EACA,uBAAAC,GACA,sBAAAC,GACA,qBAAAC,GACA,WAAAjN,GACA,kBAAAE,GACA,gBAAAE,GACA,eAAAta,GACA,qBAAAiW,GACA,6BAAAD,GACA,mBAAAlK,GACA,aAAAE,GACA,WAAAE,EACA,wBAAAE,EACA,QAAAqD,GACA,YAAA2X,GACA,YAAAjJ,EAAA,EACEhsB,EAEEiM,GAASwoB,EAAU,QACzB,GAAI,CAACxoB,GAAQ,OAEb,MAAMV,EAAMU,GAAO,WAAW,IAAI,EAClC,GAAI,CAACV,EAAK,OAIV,GAAI2Y,EAAW,CAEb,MAAMgR,GAAkBpf,IACfA,GAAI,GAAM,EAAIA,GAAIA,GAAIA,GAAI,EAAI,KAAK,IAAI,GAAKA,GAAI,EAAG,CAAC,EAAI,EAI3Dqf,IADU,YAAY,IAAA,EAAQpQ,GAAmB,SAC3B,KAAiB,KAEvCgJ,GACJoH,GAAW,GACP,EAAID,GAAeC,GAAW,CAAC,EAC/BD,IAAgBC,GAAW,IAAO,CAAC,EAEzCrQ,GAAiB,QAAUiJ,EAC7B,MACEjJ,GAAiB,QAAU,EAI7B,MAAMsJ,EAAM,OAAO,kBAAoB,EAGjCgH,EAAe7N,EAAoBtB,EACnCoP,EAAgB7N,EAAqBvB,EAK3C,GAAImP,GAAgB,GAAKC,GAAiB,EAAG,OAgB7C,MAAMC,GAAiB,KACjBC,EAAiBH,EAAehH,EAChCoH,GAAkBH,EAAgBjH,EAClCqH,GAAU,KAAK,IAAIF,EAAgBC,EAAe,EAClDE,GAAcD,GAAUH,GAAiBA,GAAiBG,GAAU,EACpEE,GAAevH,EAAMsH,GAE3BzpB,GAAO,MAAQmpB,EAAeO,GAC9B1pB,GAAO,OAASopB,EAAgBM,GAGhCpqB,EAAI,aAAa,EAAG,EAAG,EAAG,EAAG,EAAG,CAAC,EAGjCA,EAAI,UAAU,EAAG,EAAGU,GAAO,MAAOA,GAAO,MAAM,EAE/C,MAAM2pB,EAAgBzB,EAAiB,QAOvC5oB,EAAI,MAAMoqB,GAAcA,EAAY,EAGhC1P,IAAS,GACX1a,EAAI,MAAM0a,EAAMA,CAAI,GAIlB0B,IAAmB,GAAKC,IAAmB,IAC7Crc,EAAI,UAAUoc,EAAgBC,CAAc,EAI9C,MAAMZ,EAAiBT,EAAU,KAAMnlB,IAAMA,GAAE,KAAO2N,EAAgB,qBAAqB,EACrFW,IAAmBsX,GAAA,YAAAA,EAAgB,KAAM,KAiBzC6O,GACJ7J,IAAehF,EACXyM,GACEzH,GAAY,OACZA,GAAY,OACZhsB,EAAO,gBAAkB,IAAA,EAE3B,OAkBN,IAAI81B,GAAwB9J,GAAc,EAAInE,EAC9C,GACEmE,IACAA,GAAY,OAAO,SAAW,GAC9BhF,EACA,CACA,MAAM+O,GAAe/J,GAAY,OAAO,CAAC,EAAE,GAGrCgK,IAFQxsB,GAAAwiB,GAAY,SAAZ,YAAAxiB,GAAqBusB,IAG7BE,GAAWD,IAAA,YAAAA,GAAU,YACrBjJ,GAAWiJ,IAAA,YAAAA,GAAU,SAIrBE,GACJnJ,IACA,OAAOA,IAAa,UACpBA,GAAS,OAAS,cACbA,GAAS,IAAMA,GAAS,GACzB,OACAoJ,GACJ,OAAOF,IAAa,UAAYA,GAAW,EACvCA,GACA,OAAOC,IAAa,UAAYA,GAAW,EACzCA,GACA,EACJC,GAAY,IACdL,GAAwBK,GAE5B,CAgBA,GAAInP,GAAkB,CAACgF,GAErB,GADAzgB,EAAI,UAAYqqB,EAEdC,IACA,OAAOA,IAAuB,UAC9BA,GAAmB,OAAS,OAE5B,GAAI,CACF,MAAM5K,GAAM,IAAI,OAAO4K,GAAmB,CAAC,EAC3CtqB,EAAI,KAAA,EACJA,EAAI,UAAUyb,EAAe,EAAGA,EAAe,CAAC,EAChDzb,EAAI,KAAK0f,EAAG,EACZ1f,EAAI,QAAA,CACN,MAAQ,CACNA,EAAI,SACFyb,EAAe,EACfA,EAAe,EACfA,EAAe,MACfA,EAAe,MAAA,CAEnB,MACS8O,GAAwB,GACjCvqB,EAAI,UAAA,EACJA,EAAI,UACFyb,EAAe,EACfA,EAAe,EACfA,EAAe,MACfA,EAAe,OACf8O,EAAA,EAEFvqB,EAAI,KAAA,GAEJA,EAAI,SACFyb,EAAe,EACfA,EAAe,EACfA,EAAe,MACfA,EAAe,MAAA,EAiBjBA,GAAkB,CAACgF,IACrB2I,EAAiB,OAAOppB,EAAKyb,EAAgB,EAAI,EAInD,IAAIoP,GAAkBpiB,GAAsBD,EAGxCsiB,GAAoB3B,EAAmB,IAAIhlB,EAAgB,GAAK,CAAA,EAGpE,GAAIugB,EAAa,YAAc,QAAUlc,EAAiB,CAIxD,MAAMuiB,GAHcrG,EAAa,WAAA,EAGE,eAC/BqG,IAAkBA,GAAeviB,EAAgB,EAAE,IACrDsiB,GAAoBA,GAAkB,IAAKzhB,IACzCA,GAAE,KAAOb,EAAgB,GAAKuiB,GAAeviB,EAAgB,EAAE,EAAIa,EAAA,EAEhEZ,IACHoiB,GAAkBE,GAAeviB,EAAgB,EAAE,GAGzD,CAKA,GAAImQ,GAAanQ,EAAiB,CAChC,MAAMwiB,GAAUrY,EAAkB,SAAW,KACvCsY,GAAQtC,EAAa,QAO3B,GAAI,EALFsC,KAAU,MACVA,GAAM,YAAcziB,EAAgB,IACpCyiB,GAAM,UAAYD,IAClBC,GAAM,eAAiBtR,GAEV,CAEb,IAAIuR,GAAsC,KAE1C,GAAIF,IAAWxiB,aAA2B7H,eAAc,CACtD,MAAMwqB,GAAcL,GAAkB,KACnCzhB,IAAMA,GAAE,KAAOb,EAAgB,IAAMa,cAAa1I,EAAAA,YAAA,EAErD,GAAIwqB,GAAa,CACf,MAAMC,GAAcD,GAAY,MAAA,EAChCC,GAAY,SAAWA,GAAY,SAAS,IAAKv+B,IAAU,CACzD,GAAIA,GAAM,KAAOm+B,IAAWrR,GAAgB9sB,cAAiB4rB,EAAAA,YAAa,CACxE,MAAM4S,GAAcx+B,GAAM,MAAA,EAC1B,OAAAw+B,GAAY,YAAY1R,CAAY,EAC7B0R,EACT,CACA,OAAOx+B,EACT,CAAC,EACDq+B,GAAgBE,EAClB,CACF,SAAWzR,GAAgBnR,aAA2BiQ,cAAa,CACjE,MAAM6S,GAAS9iB,EAAgB,MAAA,EAC/B8iB,GAAO,YAAY3R,CAAY,EAC/BuR,GAAgBI,EAClB,CAEIJ,GACFvC,EAAa,QAAU,CAAE,UAAWngB,EAAgB,GAAI,QAAAwiB,GAAS,aAAArR,EAAc,cAAAuR,EAAA,EAE/EvC,EAAa,QAAU,IAE3B,CAEA,GAAIA,EAAa,QAAS,CACxB,KAAM,CAAE,cAAAuC,IAAkBvC,EAAa,QACvCmC,GAAoBA,GAAkB,IAAKzhB,IACzCA,GAAE,KAAOb,EAAgB,GAAK0iB,GAAgB7hB,EAAA,CAElD,CACF,MAEEsf,EAAa,QAAU,KAIzB,MAAM4C,GAAoB7B,IAAgB3X,GAAQ,SAAA,GAAcvJ,aAA2B8J,EAAAA,aAAgBqG,EAqE3G,GAnEA6S,GAAAA,eAAe,OAAO,CACpB,IAAAxrB,EACA,SAAU8qB,GACV,WAAAzoB,EACA,gBAAiBwoB,IAAmB,KACpC,iBAAArY,EACA,gBAAA0K,EACA,WAAAJ,EACA,WAAA9J,EACA,UAAA2F,EACA,aAAA+L,EACA,WAAAlI,GACA,kBAAAE,GACA,YAAa6O,GACb,gBAAA3O,GACA,wBAAyBnB,GAAA,YAAAA,EAAgB,gBACzC,gBAAiBA,GAAA,YAAAA,EAAgB,gBACjC,UAAWA,GAAA,YAAAA,EAAgB,UAC3B,SAAUA,EACN,CACE,EAAGA,EAAe,EAClB,EAAGA,EAAe,EAClB,MAAOA,EAAe,MACtB,OAAQA,EAAe,OACvB,aAAc8O,GACd,UAAWD,IAAsB7O,EAAe,SAAA,EAElD,OACJ,KAAAf,EACA,cAAe,CAAE0B,EAAmB,EAAGC,CAAA,EACvC,mBAAoBV,EAAuB,EAQ3C,oBAAqB,GACrB,mBAAA0N,EACA,aAAc1Q,GACT,IAAM,CACL,MAAM8S,GAAiBlC,GAAuB,QACxCmC,GAAiB,KAAK,IAAA,EAAQlC,GAAsB,QAAUC,GAC9DkC,GAAkBF,IAAkBC,GAE1C,MAAO,CACL,eAAgBC,GAAkBF,GAAe,OAASxS,EAC1D,eAAgB0S,GAAkBF,GAAe,MAAQtS,EACzD,aAAcwS,GAAkBF,GAAe,IAAMpS,GACrD,SAAAR,GACA,aAAAc,EACA,cAAeJ,GAAiB,QAChC,cAAA2B,GACA,4BAAAoO,CAAA,CAEJ,KACA,MAAA,CACL,EASG7I,IAAeA,GAAY,OAAO,OAAS,GAAKhF,EAAgB,CAgBlE,IAAImQ,GAAyB,GAO7B,MAAMC,GAAsBhB,IAA0C,KACtE,GAAIgB,IAAsBpL,GAAa,CACrC,MAAM3f,GAAO+qB,GAAmB,qBAAA,EAChCC,EAAO,UAAWzL,MAASI,GAAY,OAAQ,CAC7C,MAAMY,KAAQzQ,IAAAF,GAAA+P,GAAY,SAAZ,YAAA/P,GAAqB2P,GAAM,MAA3B,YAAAzP,GAAgC,QAAS,CAAA,EACvD,UAAWsR,MAAQb,GAAO,CACxB,GAAIa,GAAK,OAAS,SAAU,SAM5B,MAAMxtB,GAAOwtB,GAAK,KAClB,GAAIxtB,GAAK,OAAS,QAAUA,GAAK,OAAS,cAAe,CACvDk3B,GAAyB,GACzB,MAAME,CACR,CACA,MAAMC,GAAK1L,GAAM,GAAK3rB,GAAK,GAAK,GAC1Bs3B,GAAK3L,GAAM,GAAK3rB,GAAK,GAAK,GAC1Bu3B,GAAKv3B,GAAK,OAAS,EACnBw3B,GAAKx3B,GAAK,QAAU,EAC1B,GACEoM,GAAK,EAAIirB,GAAKE,IACdnrB,GAAK,EAAIA,GAAK,MAAQirB,IACtBjrB,GAAK,EAAIkrB,GAAKE,IACdprB,GAAK,EAAIA,GAAK,OAASkrB,GACvB,CACAJ,GAAyB,GACzB,MAAME,CACR,CACF,CACF,CACF,CACA,MAAM7H,GAAc2H,GAOdO,GACJ5B,GAAwB,GACxB9O,IAAmB,OA6CrB,GA5CI0Q,KACFnsB,EAAI,KAAA,EACJA,EAAI,UAAA,EACJA,EAAI,UACFyb,EAAe,EACfA,EAAe,EACfA,EAAe,MACfA,EAAe,OACf8O,EAAA,EAEFvqB,EAAI,KAAA,GAENugB,GAAkB,CAChB,IAAAvgB,EACA,eAAgB,CAAE,EAAGyb,EAAe,EAAG,EAAGA,EAAe,CAAA,EACzD,cAAeA,EAAe,MAC9B,eAAgBA,EAAe,OAC/B,OAAQgF,GAAY,OACpB,YAAaA,GAAY,OACzB,KAAMA,GAAY,KAClB,MAAOA,GAAY,MACnB,KAAA/F,EACA,YAAAuJ,GACA,eAAgBxvB,EAAO,gBAAkB,IAAA,CAC1C,EACG03B,IACFnsB,EAAI,QAAA,EAkBFvL,EAAO,gBAAkBgsB,GAAY,OAAO,OAAS,EAAG,CAG1D,MAAM2L,GACJ33B,EAAO,kBACPgsB,GAAY,OAAO,KAAMlR,IAAMA,GAAE,KAAO9a,EAAO,cAAc,GAC7D,KACF,GAAI23B,GAAe,CACjBpsB,EAAI,KAAA,EACJA,EAAI,UAAUyb,EAAe,EAAGA,EAAe,CAAC,EAChDzb,EAAI,yBAA2B,kBAS/B,MAAMqsB,GAAe,CAAC,CAAC7jB,EACvBxI,EAAI,YAAcqsB,GAAe,EAAI,GAAM,EAC3CrsB,EAAI,UAAY,OAChBA,EAAI,UAAA,EAKJ,MAAMssB,GAAO,IACbtsB,EAAI,KAAK,CAACssB,GAAM,CAACA,GAAM,EAAIA,GAAM,EAAIA,EAAI,EAGzCtsB,EAAI,KACFosB,GAAc,EACdA,GAAc,EACdA,GAAc,MACdA,GAAc,MAAA,EAEhBpsB,EAAI,KAAK,SAAS,EAClBA,EAAI,QAAA,CACN,CACF,CACF,CAUIyb,GACF+P,GAAAA,eAAe,6BAA6B,CAC1C,IAAAxrB,EACA,gBAAiB6qB,IAAmB,KACpC,iBAAArY,EACA,aAAAkS,EACA,gBAAAxH,EACA,WAAAJ,EACA,WAAA9J,EACA,WAAA3Q,EACA,YAAakpB,GACb,UAAA5S,EACA,gBAAAiE,GACA,wBAAyBnB,EAAe,gBAMxC,kBAAmB,CACjB,EAAGA,EAAe,EAClB,EAAGA,EAAe,EAClB,MAAOA,EAAe,MACtB,OAAQA,EAAe,OACvB,aAAc8O,GACd,UAAW9O,EAAe,SAAA,EAE5B,mBAAA4N,EACA,KAAA3O,EACA,cAAe,CAAE0B,EAAmB,EAAGC,CAAA,CAAe,CACvD,EAIH,MAAMmI,GAAmBpW,GAAqBM,EAA0BpM,GAClEmiB,GAA2B,IAAI,IAAIqG,GAAkB,IAAKzhB,IAAMA,GAAE,EAAE,CAAC,EAErE,CAAE,WAAAkjB,GAAY,YAAAjG,EAAA,EAAgB/B,GAA+B,CACjE,IAAAvkB,EACA,iBAAAwkB,GACA,SAAApiB,EACA,yBAAAqiB,GACA,aAAAC,EACA,kBAAmBnM,GAAqB,QACxC,WAAAuE,EACA,gBAAAI,EACA,mBAAA9O,GACA,KAAAsM,CAAA,CACD,EAGG6R,IAAchU,GAAqB,UAAYgU,KACjDhU,GAAqB,QAAUgU,IAEjCjU,GAA6B,QAAUgO,GAGnClY,IACFkZ,GAAuB,CAAE,IAAAtnB,EAAK,aAAAsO,GAAc,WAAAE,EAAY,KAAAkM,EAAM,EAI5D1H,EAAW,OAAS,iBAAmBA,EAAW,MACpDwU,GAAwB,CAAE,IAAAxnB,EAAK,aAAcgT,EAAW,KAAoC,KAAA0H,EAAM,EAIhG3I,GAAQ,YAAcvJ,aAA2B8J,EAAAA,aACnDP,GAAQ,cAAc/R,EAAKwI,CAAe,CAE9C,CCj3BO,MAAMgkB,EAAa,CAMxB,OAAO,eAAe,CACpB,QAAAjsB,EACA,UAAAksB,EACA,MAAArrB,EACA,cAAAsrB,EACA,cAAAC,EACA,gBAAAC,EACA,aAAAC,CAAA,EAC2B,CAC3B,MAAMlY,EAAa8X,EAAU,OAAS,EAChC7X,EAAc6X,EAAU,QAAU,EAClCK,EAAW,KAAK,IAAI,EAAGnY,EAAavT,CAAK,EACzC2rB,EAAY,KAAK,IAAI,EAAGnY,EAAcxT,CAAK,EASjDb,EAAQ,OAAO,eAAgBusB,EAAUC,EAAWN,CAAS,EAS7D,MAAMljB,EAAKkjB,EAAU,EAAIE,EAAc,EACjCnjB,EAAKijB,EAAU,EAAIE,EAAc,EACjC7Y,EAAM,KAAK,IAAI4Y,CAAa,EAC5B3Y,EAAM,KAAK,IAAI2Y,CAAa,EAC5BM,EAAM5rB,EAAQmI,EACd0jB,EAAM7rB,EAAQoI,EACpBjJ,EAAQ,EAAIqsB,EAAgB,EAAII,EAAMlZ,EAAMmZ,EAAMlZ,EAClDxT,EAAQ,EAAIqsB,EAAgB,EAAII,EAAMjZ,EAAMkZ,EAAMnZ,EAalD,IAAIoZ,EAAcT,EAAU,SAAYC,EAAgB,IAAO,KAAK,GAGpE,OAAAQ,GAAgBA,EAAc,IAAO,KAAO,IACxCA,EAAc,MAAKA,GAAe,KAClCL,IACFK,EAAcL,EAAaK,EAAa3sB,CAAO,GAEjDA,EAAQ,YAAY2sB,CAAW,EAExB3sB,CACT,CACF,CCjHA,MAAM4sB,EAAuB,CAK3B,aAAc,CACZ,KAAK,gBAAkB,GACvB,KAAK,QAAU,KACf,KAAK,gBAAkB,GACzB,CAMA,kBAAmB,CACjB,KAAK,gBAAkB,GAGnB,KAAK,SACP,aAAa,KAAK,OAAO,EAI3B,KAAK,QAAU,WAAW,IAAM,CAC9B,KAAK,gBAAkB,GACvB,KAAK,QAAU,IACjB,EAAG,KAAK,eAAe,CACzB,CAOA,oBAAqB,CACnB,MAAMC,EAAe,KAAK,gBAG1B,OAAI,KAAK,kBACP,KAAK,gBAAkB,GACnB,KAAK,UACP,aAAa,KAAK,OAAO,EACzB,KAAK,QAAU,OAIZA,CACT,CAKA,iBAAkB,CAChB,KAAK,gBAAkB,GACnB,KAAK,UACP,aAAa,KAAK,OAAO,EACzB,KAAK,QAAU,KAEnB,CAMA,aAAc,CACZ,OAAO,KAAK,eACd,CACF,CAGO,MAAMC,GAAkB,IAAIF,GChB5B,SAASG,GAAsB74B,EAA8D,CAClG,KAAM,CAAE,EAAAiN,EAAG,EAAAC,EAAG,OAAA4rB,EAAQ,KAAA7S,EAAO,GAAQjmB,EAI/B+4B,EAAYC,EAAAA,yBAA2B/S,EACvCgL,EAAgB6H,EAAO,UAAY,EAWnCG,EARU,CACd,CAAE,EAAGH,EAAO,KAAM,EAAGA,EAAO,KAAM,OAAQ,UAAA,EAC1C,CAAE,EAAGA,EAAO,KAAM,EAAGA,EAAO,KAAM,OAAQ,WAAA,EAC1C,CAAE,EAAGA,EAAO,KAAM,EAAGA,EAAO,KAAM,OAAQ,aAAA,EAC1C,CAAE,EAAGA,EAAO,KAAM,EAAGA,EAAO,KAAM,OAAQ,cAAA,CAAwB,EAIrC,IAAKlI,GAAW,CAC7C,GAAIK,IAAkB,EAAG,CACvB,MAAMN,EAAe,CAACM,EAAgB,KAAK,GAAM,IAC3C5R,EAAM,KAAK,IAAIsR,CAAW,EAC1BrR,EAAM,KAAK,IAAIqR,CAAW,EAC1B7b,EAAK8b,EAAO,EAAIkI,EAAO,QACvB/jB,EAAK6b,EAAO,EAAIkI,EAAO,QAC7B,MAAO,CACL,EAAGA,EAAO,SAAWhkB,EAAKuK,EAAMtK,EAAKuK,GACrC,EAAGwZ,EAAO,SAAWhkB,EAAKwK,EAAMvK,EAAKsK,GACrC,OAAQuR,EAAO,MAAA,CAEnB,CACA,OAAOA,CACT,CAAC,EAGD,UAAWA,KAAUqI,EAAgB,CACnC,MAAMnkB,EAAK7H,EAAI2jB,EAAO,EAChB7b,EAAK7H,EAAI0jB,EAAO,EAGtB,GAFiB,KAAK,KAAK9b,EAAKA,EAAKC,EAAKA,CAAE,GAE5BgkB,EACd,MAAO,CAAE,KAAM,gBAAiB,OAAQnI,EAAO,OAAQ,OAAAA,CAAA,CAE3D,CAGA,MAAMsI,EAAqB,GAAUjT,EAKrC,GAJ+B,KAAK,KAClC,KAAK,IAAIhZ,EAAI6rB,EAAO,gBAAiB,CAAC,EAAI,KAAK,IAAI5rB,EAAI4rB,EAAO,gBAAiB,CAAC,CAAA,GAGpDI,EAC5B,MAAO,CAAE,KAAM,iBAAA,EAKjB,IAAI3Z,EAAStS,EACTuS,EAAStS,EAEb,GAAI+jB,IAAkB,EAAG,CACvB,MAAMN,EAAeM,EAAgB,KAAK,GAAM,IAC1C5R,EAAM,KAAK,IAAIsR,CAAW,EAC1BrR,EAAM,KAAK,IAAIqR,CAAW,EAC1B7b,EAAK7H,EAAI6rB,EAAO,QAChB/jB,EAAK7H,EAAI4rB,EAAO,QACtBvZ,EAASuZ,EAAO,SAAWhkB,EAAKuK,EAAMtK,EAAKuK,GAC3CE,EAASsZ,EAAO,SAAWhkB,EAAKwK,EAAMvK,EAAKsK,EAC7C,CAGA,OAAIE,GAAUuZ,EAAO,MAAQvZ,GAAUuZ,EAAO,MAAQtZ,GAAUsZ,EAAO,MAAQtZ,GAAUsZ,EAAO,KACvF,CAAE,KAAM,aAAA,EAGV,CAAE,KAAM,MAAA,CACjB,CAiBO,SAASK,GAA0Bn5B,EAAkD,CAC1F,KAAM,CAAE,eAAA6N,EAAgB,SAAAF,EAAU,OAAAmrB,EAAQ,OAAA5X,EAAQ,OAAA0P,EAAQ,EAAA3jB,EAAG,EAAAC,EAAG,aAAA+iB,CAAA,EAAiBjwB,EAG3EywB,EAAmB5iB,EACtB,IAAK3P,GAAOyP,EAAS,KAAMiH,GAAMA,EAAE,KAAO1W,CAAE,CAAC,EAC7C,OAAQ0W,GAA0BA,IAAM,QAAa,CAACA,EAAE,MAAM,EAGjE,OAAI6b,EAAiB,SAAW,EACvB,IAGRR,EAAuC,6BAA+B,CACrE,OAAA/O,EACA,YAAa,CAAE,GAAG4X,CAAA,EAClB,kBAAmBrI,EAAiB,IAAK3kB,IAAa,CACpD,QAAAA,EACA,UAAWA,EAAQ,sBAAA,CAAsB,EACzC,CAAA,EAGJmkB,EAAa,YACXQ,EAAiB,CAAC,EAClB,CACE,OAAAvP,EACA,EAAG0P,EAAO,EACV,EAAGA,EAAO,EACV,OAAQ,cACR,KAAM,QAAA,EAER3jB,EACAC,EACA,CACE,EAAG4rB,EAAO,KACV,EAAGA,EAAO,KACV,MAAOA,EAAO,KAAOA,EAAO,KAC5B,OAAQA,EAAO,KAAOA,EAAO,IAAA,EAE/B,CACE,EAAGA,EAAO,KACV,EAAGA,EAAO,KACV,MAAOA,EAAO,KAAOA,EAAO,KAC5B,OAAQA,EAAO,KAAOA,EAAO,IAAA,CAC/B,EAGK,GACT,CAkBO,SAASM,GAA0Bp5B,EAAkD,CAC1F,KAAM,CACJ,eAAA6N,EACA,SAAAF,EACA,OAAAmrB,EACA,EAAA7rB,EACA,EAAAC,EACA,aAAA+iB,EACA,cAAA3H,EACA,sBAAAE,EACA,mBAAAE,CAAA,EACE1oB,EAGEywB,EAAmB5iB,EACtB,IAAK3P,GAAOyP,EAAS,KAAMiH,GAAMA,EAAE,KAAO1W,CAAE,CAAC,EAC7C,OAAQ0W,GAA0BA,IAAM,QAAa,CAACA,EAAE,MAAM,EAGjE,GAAI6b,EAAiB,SAAW,EAC9B,MAAO,GAGT,MAAM4I,EAAaC,EAAAA,eAAeR,EAAO,QAASA,EAAO,QAAS7rB,EAAGC,CAAC,EAErE,OAAA+iB,EAAuC,+BAAiC,CACvE,YAAa,CAAE,GAAG6I,CAAA,EAClB,kBAAmBrI,EAAiB,IAAK3kB,IAAa,CACpD,QAAAA,EACA,UAAWA,EAAQ,sBAAA,CAAsB,EACzC,CAAA,EAGJwc,EAAc,EAAI,EAClBE,EAAsB6Q,CAAU,EAChC3Q,EAAmB,CAAC,EACpBuH,EAAa,YACXQ,EAAiB,CAAC,EAClBxjB,EACAC,EACAmsB,EACA,CAAA,EAGK,EACT,CAgBO,SAASE,GAAwBv5B,EAAgD,CACtF,KAAM,CAAE,eAAA6N,EAAgB,SAAAF,EAAU,EAAAV,EAAG,EAAAC,EAAG,aAAA+iB,EAAc,wBAAAuJ,EAAyB,cAAA/a,GAAkBze,EAG3FywB,EAAmB5iB,EACtB,IAAK3P,GAAOyP,EAAS,KAAMiH,GAAMA,EAAE,KAAO1W,CAAE,CAAC,EAC7C,OAAQ0W,GAA0BA,IAAM,QAAa,CAACA,EAAE,MAAM,EAEjE,OAAI6b,EAAiB,SAAW,EACvB,IAIRR,EAAuC,2BAA6B,CACnE,OAAQhjB,EACR,OAAQC,EACR,kBAAmBujB,EAAiB,IAAK3kB,IAAa,CACpD,QAAAA,EACA,UAAWA,EAAQ,sBAAA,CAAsB,EACzC,EACF,oBAAqB,GAAI,EAI3B0tB,EAAwB,YAAc,IACtC/I,EAAiB,QAAS3kB,GAAY,CACpC0tB,EAAwB,QAAS,IAAI1tB,EAAQ,GAAIA,EAAQ,OAAO,CAClE,CAAC,EAGDmkB,EAAa,UAAUQ,EAAiB,CAAC,EAAGxjB,EAAGC,CAAC,EAChDuR,EAAc,CAAE,KAAM,KAAM,KAAM,KAAM,EAEjC,GACT,CCrRO,SAASgb,GAAkBz5B,EAAyD,CACzF,KAAM,CAAE,gBAAA+T,EAAiB,mBAAAC,CAAA,EAAuBhU,EAEhD,IAAI05B,EAAqC,KACrCC,EAAqB,GAGzB,GAAI3lB,aAA8BC,EAAAA,cAAgBD,EAAmB,WACnE0lB,EAAgB1lB,EAEZD,aAA2B7H,EAAAA,eAC7BytB,EAAqB5lB,EAAgB,SAAS,UAAWd,GAAMA,EAAE,KAAOe,EAAmB,EAAE,WAEtFD,aAA2B7H,eAEpC,QAASsL,EAAI,EAAGA,EAAIzD,EAAgB,SAAS,OAAQyD,IAAK,CACxD,MAAMpf,EAAQ2b,EAAgB,SAASyD,CAAC,EAExC,GAAIpf,EAAM,UAAY,IAElBA,aAAiB6b,EAAAA,cAAgB7b,EAAM,WAAY,CACrDshC,EAAgBthC,EAChBuhC,EAAqBniB,EACrB,KACF,CACF,CAIF,MAAMoiB,EACJF,IAAkB3lB,aAA2BE,EAAAA,cAAgBF,EAAgB,WAAaA,EAAkB,MAE9G,OAAK6lB,EAIE,CACL,cAAAA,EACA,cAAAF,EACA,mBAAAC,EACA,eAAgBD,IAAkB,IAAA,EAP3B,IASX,CAqBO,SAASG,GAAgB75B,EAAkD,CAChF,KAAM,CAAE,EAAAiN,EAAG,EAAAC,EAAG,YAAA4sB,EAAa,iBAAA/b,EAAkB,KAAAkI,EAAO,GAAQjmB,EACtD,CAAE,cAAA45B,GAAkBE,EAGpBC,EAAaH,EAAc,kBAAkB3sB,EAAGC,EAAG+Y,CAAI,EAC7D,GAAI8T,EACF,MAAO,CAAE,KAAM,cAAe,OAAQA,CAAA,EAIxC,MAAMC,EAAcjc,EAAiB,cAAc9Q,EAAGC,EAAG+Y,CAAI,EAC7D,OAAI+T,EACK,CAAE,KAAM,eAAgB,OAAQA,CAAA,EAIrCJ,EAAc,QAAQ3sB,EAAGC,CAAC,EACrB,CAAE,KAAM,YAAA,EAIV,CAAE,KAAM,WAAA,CACjB,CAeO,SAAS+sB,GAAsBj6B,EAA2C,CAC/E,KAAM,CAAE,YAAA85B,EAAa,WAAAC,EAAY,EAAA9sB,EAAG,EAAAC,EAAG,gBAAA6G,EAAiB,mBAAAC,EAAoB,aAAAic,GAAiBjwB,EACvF,CAAE,cAAA45B,EAAe,cAAAF,EAAe,mBAAAC,CAAA,EAAuBG,EAEvDI,EAAUN,EAAc,iBAAA,EACzBM,IAELjK,EAAa,YACX2J,EACA,CAIE,OAAS,QAAUG,EAAW,OAC9B,EAAG,EACH,EAAG,EACH,OAAQ,OACR,KAAMA,EAAW,IAAA,EAEnB9sB,EACAC,EACAgtB,EACAA,CAAA,EAIER,IAEE1lB,GAAsBA,EAAmB,KAAO0lB,EAAc,GAChEzJ,EAAa,oBAAsB,CACjC,MAAOlc,EACP,QAAS2lB,EAAc,EAAA,EAGzBzJ,EAAa,kBAAoB,CAC/B,MAAOlc,EACP,WAAY4lB,CAAA,GAIpB,CAeO,SAASQ,GAAuBn6B,EAA4C,CACjF,KAAM,CAAE,YAAA85B,EAAa,YAAAE,EAAa,EAAA/sB,EAAG,EAAAC,EAAG,gBAAA6G,EAAiB,mBAAAC,EAAoB,aAAAic,GAAiBjwB,EACxF,CAAE,cAAA45B,EAAe,cAAAF,EAAe,mBAAAC,CAAA,EAAuBG,EAEvDztB,EAAOutB,EAAc,eAAA,EACrBQ,EAAaR,EAAc,qBAAA,EACjC3J,EAAa,YAAY2J,EAAeI,EAAa/sB,EAAGC,EAAGb,EAAM+tB,CAAU,EAGvEV,IAEE1lB,GAAsBA,EAAmB,KAAO0lB,EAAc,GAChEzJ,EAAa,oBAAsB,CACjC,MAAOlc,EACP,QAAS2lB,EAAc,EAAA,EAGzBzJ,EAAa,kBAAoB,CAC/B,MAAOlc,EACP,WAAY4lB,CAAA,EAIpB,CAgBO,SAASU,GAAqBr6B,EAA0C,CAC7E,KAAM,CAAE,YAAA85B,EAAa,EAAA7sB,EAAG,EAAAC,EAAG,gBAAA6G,EAAiB,mBAAAC,EAAoB,aAAAic,EAAc,wBAAAuJ,EAAyB,cAAA/a,CAAA,EAAkBze,EACnH,CAAE,cAAA45B,EAAe,cAAAF,EAAe,mBAAAC,CAAA,EAAuBG,EAG7DN,EAAwB,YAAc,IAClCE,GAAiB3lB,EAEnBylB,EAAwB,QAAQ,IAAIzlB,EAAgB,GAAIA,EAAgB,OAAO,EAE/EylB,EAAwB,QAAQ,IAAII,EAAc,GAAIA,EAAc,OAAO,EAG7E3J,EAAa,UAAU2J,EAAe3sB,EAAGC,EAAG,iBAAiB,EAC7DuR,EAAc,CAAE,KAAM,KAAM,KAAM,KAAM,EAGpCib,IAEE1lB,GAAsBA,EAAmB,KAAO0lB,EAAc,GAChEzJ,EAAa,oBAAsB,CACjC,MAAOlc,EACP,QAAS2lB,EAAc,EAAA,EAGzBzJ,EAAa,kBAAoB,CAC/B,MAAOlc,EACP,WAAY4lB,CAAA,EAIpB,CAcO,SAASW,GAAat6B,EAAkC,CAC7D,KAAM,CAAE,YAAA85B,EAAa,gBAAA/lB,EAAiB,gBAAAwmB,EAAiB,iBAAAxc,EAAkB,kBAAAI,EAAmB,KAAA8H,GAASjmB,EAC/F,CAAE,cAAA45B,EAAe,cAAAF,EAAe,mBAAAC,CAAA,EAAuBG,EAE7D,GAAIJ,GAAiB3lB,aAA2B7H,eAAc,CAE5D,MAAMkS,EAAesb,EAAc,MAAA,EACnCtb,EAAa,aAAA,EACb,MAAMoc,EAAezmB,EAAgB,MAAA,EACrCymB,EAAa,SAASb,CAAkB,EAAIvb,EAC5Cmc,EAAgBC,CAAY,EAC5Bzc,EAAiB,OAAOyc,EAAcvU,CAAI,EAC1C9H,EAAkB,IAAI,CACxB,KAAO,CAEL,MAAMnJ,EAAiB4kB,EAAc,MAAA,EACrC5kB,EAAe,aAAA,EACfulB,EAAgBvlB,CAAc,EAC9B+I,EAAiB,OAAO/I,EAAgBiR,CAAI,CAC9C,CACF,CCvQO,SAASwU,GAAmBz6B,EAA2C,CAC5E,KAAM,CACJ,EAAAiN,EACA,EAAAC,EACA,mBAAA8G,EACA,gBAAAD,EACA,kBAAAoK,EACA,iBAAAJ,EACA,kBAAAgL,EACA,aAAAkH,EACA,wBAAAuJ,EACA,cAAA/a,EACA,YAAAoK,EACA,KAAA5C,CAAA,EACEjmB,EAGJ,QAASwX,EAAIzD,EAAgB,SAAS,OAAS,EAAGyD,GAAK,EAAGA,IAAK,CAC7D,MAAMpf,EAAQ2b,EAAgB,SAASyD,CAAC,EAGxC,GAAI,EAAApf,EAAM,UAAY,IAASA,EAAM,KAAO4b,EAAmB,KAE3D5b,EAAM,QAAQ6U,EAAGC,CAAC,EAGpB,OAAAiR,EAAkB/lB,CAAmC,EACrD2lB,EAAiB,OAAO3lB,EAAO6tB,CAAI,EACnC8C,EAAmBjZ,GAAMA,EAAI,CAAC,EAG9BmgB,EAAa,UAAU73B,EAAO6U,EAAGC,CAAC,EAGlCssB,EAAwB,YAAc,IACtCA,EAAwB,QAAQ,IAAIphC,EAAM,GAAIA,EAAM,OAAO,EAG3DqmB,EAAc,CAAE,KAAM,KAAM,KAAM,KAAM,EAGvCwR,EAAoD,oBAAsB,CACzE,MAAOlc,EACP,QAAS3b,EAAM,EAAA,EAGjBywB,EAAY,CAAA,CAAE,EACP,EAEX,CAEA,MAAO,EACT,CAcO,SAAS6R,GAAoB16B,EAAyC,CAC3E,KAAM,CAAE,kBAAAme,EAAmB,gBAAApK,EAAiB,iBAAAgK,EAAkB,kBAAAgL,EAAmB,KAAA9C,GAASjmB,EAE1Fme,EAAkB,IAAI,EAClBpK,IACFgK,EAAiB,OAAOhK,EAAiBkS,CAAI,EAC7C8C,EAAmBjZ,GAAMA,EAAI,CAAC,EAElC,CAsBO,SAAS6qB,GAAuB36B,EAA+C,CACpF,KAAM,CACJ,EAAAiN,EACA,EAAAC,EACA,SAAA0tB,EACA,uBAAAC,EACA,gBAAA9mB,EACA,eAAAlG,EACA,kBAAAitB,EACA,uBAAApgB,EACA,aAAAuV,EACA,wBAAAuJ,EACA,cAAA/a,EACA,YAAAoK,CAAA,EACE7oB,EAEJ,QAASwX,EAAIqjB,EAAuB,OAAS,EAAGrjB,GAAK,EAAGA,IAMtD,GAAIqjB,EAAuBrjB,CAAC,EAAE,UAAY,IAEtCqjB,EAAuBrjB,CAAC,EAAE,QAAQvK,EAAGC,CAAC,EAAG,CAE3C,MAAM6tB,EAAYF,EAAuBrjB,CAAC,EAAE,GAG5C,GAAIojB,EAAU,CAGZ,GAAI7mB,GAAmBlG,EAAe,SAAW,EAC/C6M,EAAuB,CAAC3G,EAAgB,GAAIgnB,CAAS,CAAC,EACtDD,EAAkB,IAAI,UAGlBjtB,EAAe,SAASktB,CAAS,EAAG,CAEtC,MAAMC,EAAentB,EAAe,OAAQ3P,GAAOA,IAAO68B,CAAS,EACnErgB,EAAuBsgB,CAAY,CACrC,MAEEtgB,EAAuB,CAAC,GAAG7M,EAAgBktB,CAAS,CAAC,EAGzD,MAAO,EACT,CAGA,GAAIltB,EAAe,OAAS,GAAKA,EAAe,SAASktB,CAAS,EAAG,CAGnE,MAAMtK,EAAmB5iB,EACtB,IAAK3P,GAAO28B,EAAuB,KAAMjmB,GAAMA,EAAE,KAAO1W,CAAE,CAAC,EAC3D,OAAQ0W,GAA0BA,IAAM,QAAa,CAACA,EAAE,MAAM,EAEjE,GAAI6b,EAAiB,OAAS,EAE3B,OAAAR,EAAoD,2BAA6B,CAChF,OAAQhjB,EACR,OAAQC,EACR,kBAAmBujB,EAAiB,IAAK3kB,IAAa,CACpD,QAAAA,EACA,UAAWA,EAAQ,sBAAA,CAAsB,EACzC,EACF,oBAAqB,GAAI,EAI3B0tB,EAAwB,YAAc,IACtC/I,EAAiB,QAAS3kB,GAAY,CACpC0tB,EAAwB,QAAS,IAAI1tB,EAAQ,GAAIA,EAAQ,OAAO,CAClE,CAAC,EAGDmkB,EAAa,UAAUQ,EAAiB,CAAC,EAAGxjB,EAAGC,CAAC,EAChDuR,EAAc,CAAE,KAAM,KAAM,KAAM,KAAM,EACxCoK,EAAY,CAAA,CAAE,EACP,EAEX,CAGA,OAAAiS,EAAkBC,CAAS,EAC3BrgB,EAAuB,CAAA,CAAE,EAGzB8e,EAAwB,YAAc,IACtCA,EAAwB,QAAQ,IAAIuB,EAAWF,EAAuBrjB,CAAC,EAAE,OAAO,EAEhFyY,EAAa,UAAU4K,EAAuBrjB,CAAC,EAAGvK,EAAGC,CAAC,EACtDuR,EAAc,CAAE,KAAM,KAAM,KAAM,KAAM,EAGpCoc,EAAuBrjB,CAAC,YAAatL,iBACtC+jB,EAAoD,0BAA4B4K,EAAuBrjB,CAAC,EAAE,MAAA,GAG7GqR,EAAY,CAAA,CAAE,EACP,EACT,CAGF,MAAO,EACT,CC9IA,MAAMoS,GAA8B,IAuB7B,SAASC,GACdpvB,EACAqvB,EACAC,EACA5U,EAIS,CACT,GAAI,CAACA,EAAkB,MAAO,GAC9B,MAAMna,EAAOP,EAAQ,qBAAA,EACfuvB,EAAK,KAAK,IAAIhvB,EAAK,EAAGma,EAAiB,CAAC,EACxC8U,EAAK,KAAK,IAAIjvB,EAAK,EAAGma,EAAiB,CAAC,EACxC+U,EAAM,KAAK,IACflvB,EAAK,EAAIA,EAAK,MACdma,EAAiB,EAAIA,EAAiB,KAAA,EAElCgV,EAAM,KAAK,IACfnvB,EAAK,EAAIA,EAAK,OACdma,EAAiB,EAAIA,EAAiB,MAAA,EAGxC,OADoB,KAAK,IAAI,EAAG+U,EAAMF,CAAE,EAAI,KAAK,IAAI,EAAGG,EAAMF,CAAE,EAC9CL,GAAoC,GAIpDE,GAAU3U,EAAiB,GAC3B2U,GAAU3U,EAAiB,EAAIA,EAAiB,OAChD4U,GAAU5U,EAAiB,GAC3B4U,GAAU5U,EAAiB,EAAIA,EAAiB,MAEpD,CAkMO,SAASiV,GAAqBz7B,EAA6D,CAChG,KAAM,CACJ,UAAAy0B,EACA,SAAAiH,EACA,uBAAAC,EACA,iBAAAC,EACA,cAAAC,EACA,0BAAAC,EACA,sBAAA/G,EACA,uBAAAD,EACA,qBAAAiH,EACA,2BAAAC,EACA,4BAAAnH,EACA,gBAAAoH,EACA,wBAAAzC,EACA,mBAAA0C,EACA,kBAAAhe,EACA,gBAAAoH,EACA,qBAAA6W,EACA,6BAAAtY,EACA,sBAAAuY,EACA,qBAAApH,EACA,sBAAAqH,EACA,UAAAnY,EACA,SAAAE,EACA,cAAAqC,EACA,eAAA/B,EACA,aAAAE,EACA,WAAAyD,GACA,mBAAA1O,EACA,gBAAAwO,EACA,gBAAApU,EACA,mBAAAC,GACA,SAAArG,GACA,eAAAE,GACA,qBAAAiW,GACA,KAAAmC,EACA,eAAA0B,GACA,eAAAC,GACA,kBAAAK,GACA,iBAAAzB,GACA,kBAAA/B,GACA,kBAAAE,GACA,gBAAAE,GACA,cAAAyD,GACA,sBAAAE,GACA,mBAAAE,GACA,0BAAAC,GACA,mBAAAP,EACA,cAAAJ,EACA,qBAAAE,GACA,kBAAAa,GACA,cAAAtK,GACA,YAAA6d,GACA,YAAAzT,EACA,aAAAoH,EACA,iBAAAlS,EACA,WAAAwe,EACA,cAAAC,GACA,QAAAlf,EACA,eAAAqG,GACA,gBAAA4W,GACA,qBAAAkC,GACA,kBAAA3B,GACA,uBAAApgB,EACA,sBAAAgiB,EACA,gBAAAC,GACA,kBAAAxe,GACA,0BAAAye,GACA,sBAAAC,GACA,uBAAAC,GACA,uBAAAC,GACA,kBAAAC,GACA,gBAAAzY,GACA,YAAAF,GACA,gBAAAc,GACA,aAAAhB,GACA,0BAAAkB,GACA,yBAAA4X,EAAA,EACEj9B,EAMEk9B,GAAkB3tB,EAAAA,OAAuB,IAAI,EAe7C4tB,GAAoB5tB,EAAAA,OAExB,IAAI,GAAK,EACL6tB,GAAqB7tB,EAAAA,OAAwC,IAAI,EAIjE8tB,GAA0B9tB,EAAAA,OAA2B,IAAI,EAUzD+tB,GAA8B/tB,EAAAA,OAAsB,IAAI,EAQxDguB,GAAuBhuB,EAAAA,OAAsB,IAAI,EACjDiuB,GAA6BjuB,EAAAA,OAAwC,IAAI,EAMzEkuB,GAAqBluB,EAAAA,OAYjB,IAAI,EAMRmuB,GAAqBnuB,EAAAA,OAmBjB,IAAI,EAER,CAAE,SAAAouB,GAAU,YAAAC,GAAa,UAAAC,GAAW,aAAAC,EAAA,EAAiBC,GAAAA,mBAAA,EACrDC,GAAczuB,EAAAA,OAAOouB,EAAQ,EACnCK,GAAY,QAAUL,GACtB,MAAMM,GAAe1uB,EAAAA,OAAOsuB,EAAS,EACrCI,GAAa,QAAUJ,GAEvBp0B,EAAAA,UAAU,IAAM,CACd,MAAMwC,EAASwoB,EAAU,QACzB,GAAI,CAACxoB,EAAQ,OACb,MAAMiyB,GAAe,IAAM,CACzBhB,GAAgB,QAAUjxB,EAAO,sBAAA,CACnC,EACA,OAAAiyB,GAAA,EACA,OAAO,iBAAiB,SAAUA,GAAc,CAAE,QAAS,GAAM,QAAS,GAAM,EAChF,OAAO,iBAAiB,SAAUA,GAAc,CAAE,QAAS,GAAM,EAC1D,IAAM,CACX,OAAO,oBAAoB,SAAUA,GAAc,CAAE,QAAS,GAAM,EACpE,OAAO,oBAAoB,SAAUA,EAAY,CACnD,CACF,EAAG,CAACzJ,CAAS,CAAC,EAWdhrB,EAAAA,UAAU,IAAM,CACd,MAAMwC,EAASwoB,EAAU,QACpBxoB,IACLixB,GAAgB,QAAUjxB,EAAO,sBAAA,EACnC,EAAG,CAACga,EAAMwO,CAAS,CAAC,EAKpB,MAAM0J,GAAoBhzB,EAAAA,YACvByJ,GAA6C,mBAC5C,MAAM3I,GAASwoB,EAAU,QACzB,GAAI,CAACxoB,GAAQ,OAEb,MAAMqJ,GAAOrJ,GAAO,sBAAA,EAKpBixB,GAAgB,QAAU5nB,GAC1B,MAAMrI,GAAK2H,EAAE,QAAUU,GAAK,MAAQ2Q,EAAO0B,GACrCza,GAAK0H,EAAE,QAAUU,GAAK,KAAO2Q,EAAO2B,GAkBtCuV,GAAkB,QAAQ,OAAS,IACrCG,GAA4B,SAAUvpB,GAAA,YAAAA,EAAiB,KAAM,MAE/DopB,GAAkB,QAAQ,IAAIvoB,EAAE,UAAW,CACzC,EAAA3H,EACA,EAAAC,EACA,QAAS0H,EAAE,QACX,QAASA,EAAE,OAAA,CACZ,EAED,MAAMwpB,GACJla,GACCnQ,aAA2BE,EAAAA,cAAgBF,EAAgB,YAC5DuJ,EAAQ,SAAA,EAWV,GAAI6f,GAAkB,QAAQ,OAAS,GAAK,CAACiB,GAAa,CACxD,MAAMrgC,EAAM,MAAM,KAAKo/B,GAAkB,QAAQ,MAAM,EACjD/7B,EAAI+7B,GAAkB,QAAQ,IAAIp/B,EAAI,CAAC,CAAC,EACxCsD,EAAI87B,GAAkB,QAAQ,IAAIp/B,EAAI,CAAC,CAAC,EACxCsgC,EAAoBf,GAA4B,UAAY,KAOlE,GAAI,EALFe,GACAtqB,GACA,CAACA,EAAgB,SAChBA,EAAgB,QAAQ3S,EAAE,EAAGA,EAAE,CAAC,GAAK2S,EAAgB,QAAQ1S,EAAE,EAAGA,EAAE,CAAC,IAKjE,CAKL,GAAIm4B,EAAwB,SAAWA,EAAwB,QAAQ,KAAO,EAAG,CAC/E,MAAM8E,GAAY9E,EAAwB,QAC1C8C,GAAaiC,IACXA,GAAK,IAAKrwB,IAAO,CACf,MAAMswB,GAAWF,GAAU,IAAIpwB,GAAG,EAAE,EACpC,OAAOswB,GAAYA,GAAS,MAAA,EAAwBtwB,EACtD,CAAC,CAAA,EAEHsrB,EAAwB,QAAU,IACpC,CACI,CAAC6E,GAAqBtqB,GACxB+mB,GAAkB,IAAI,EAEpB7K,EAAa,iBACfA,EAAa,IAAA,EAOfyN,GAAmB,QAAU,KAC7BH,GAAqB,QAAU,KAC/BC,GAA2B,QAAU,KACrC,OAAOvN,EAAa,qBACpB,OAAOA,EAAa,uBAEpB,MAAMnb,GAAKzT,EAAE,QAAUD,EAAE,QACnB2T,GAAK1T,EAAE,QAAUD,EAAE,QACnBq9B,GAAW,KAAK,MAAM3pB,GAAIC,EAAE,EAG5B2pB,MADHl1B,GAAAyC,GAAO,gBAAP,YAAAzC,GAAsB,gBAAwCyC,IACjC,sBAAA,EAChCwxB,GAAmB,QAAU,CAC3B,SAAU1/B,EAAI,CAAC,EACf,SAAUA,EAAI,CAAC,EACf,cAAeigC,GAAY,QAC3B,eAAgB,CAAE,GAAGC,GAAa,OAAA,EAClC,oBAAqB,CACnB,GAAI78B,EAAE,QAAUC,EAAE,SAAW,EAC7B,GAAID,EAAE,QAAUC,EAAE,SAAW,CAAA,EAE/B,cAAe,KAAK,IAAIo9B,GAAU,CAAC,EACnC,cAAe,CAAE,KAAMC,GAAc,KAAM,IAAKA,GAAc,GAAA,CAAI,EAEpE,GAAI,CACFzyB,GAAO,kBAAkB2I,EAAE,SAAS,CACtC,MAAQ,CAER,CACAA,EAAE,eAAA,EACF,MACF,CACF,CAEA,GACEuoB,GAAkB,QAAQ,OAAS,GACnCppB,GACA,CAACA,EAAgB,QACjB,CAACqqB,GACD,CAMA,GAAInO,EAAa,gBAAiB,CAChC,MAAM1kB,EAAM0kB,EAAa,WAAA,EACzB,GAAI1kB,EAAI,QAAS,CAIf,MAAMizB,GAAW5B,KAA4B,KAC1C1uB,IAAOA,GAAG,KAAO3C,EAAI,QAAS,EAAA,EAEjCkxB,GAAqB+B,GAAUjzB,EAAI,OAAwB,CAC7D,CACA0kB,EAAa,IAAA,CACf,CAEA,MAAMlyB,EAAM,MAAM,KAAKo/B,GAAkB,QAAQ,MAAM,EACvDC,GAAmB,QAAU,CAAE,EAAGr/B,EAAI,CAAC,EAAG,EAAGA,EAAI,CAAC,CAAA,EAClD,MAAM4gC,EAAKxB,GAAkB,QAAQ,IAAIp/B,EAAI,CAAC,CAAC,EACzC6gC,EAAKzB,GAAkB,QAAQ,IAAIp/B,EAAI,CAAC,CAAC,EAM/Cw/B,GAAqB,QAAU,KAC/BC,GAA2B,QAAU,KACrCE,GAAmB,QAAU,KAE7BL,GAAwB,QAAUtpB,EAAgB,MAAA,EAClDkc,EAAa,WAAWlc,EAAiB4qB,EAAIC,CAAE,EAE/C,GAAI,CACF3yB,GAAO,kBAAkB2I,EAAE,SAAS,CACtC,MAAQ,CAIR,CACAA,EAAE,eAAA,EACF,MACF,CAIA,GAAIsP,GAAanQ,aAA2BiQ,cAAa,CAEvD,GAAIyC,GAAiB/B,IAAmBE,EAAc,CACpD,MAAMia,EAAkBhK,EAA4B,QACpD,GAAIgK,EAAgB,OAASA,EAAgB,IAAK,CAChD,MAAMlO,EAAcvR,EAAAA,cAAc,iBAAiBrL,EAAgB,QAAQ,EACrE+qB,EAAS/qB,EAAgB,kBAAA,EACzBe,EAAK7H,EAAI6xB,EAAO,EAChB/pB,GAAK7H,EAAI4xB,EAAO,EAChBzf,GAAM,KAAK,IAAIsR,CAAW,EAC1BrR,GAAM,KAAK,IAAIqR,CAAW,EAC1BoO,GAAgBjqB,EAAKuK,GAAMtK,GAAKuK,GAChC0f,GAAgBlqB,EAAKwK,GAAMvK,GAAKsK,GAChCE,GAASwf,GACTvf,GAASwf,GAETjG,GAAY,GAAK9S,EAEvB,GAAI4Y,EAAgB,OACA,KAAK,KACrB,KAAK,IAAItf,GAASsf,EAAgB,MAAM,OAAO,EAAG,CAAC,EACnD,KAAK,IAAIrf,GAASqf,EAAgB,MAAM,OAAO,EAAG,CAAC,CAAA,GAEpC9F,GAAW,CAC1BnkB,EAAE,eAAA,EACFonB,EAA2B,QAAU,SACrC/f,GAAAyf,EAAS,UAAT,MAAAzf,GAAkB,QAClB,MACF,CAGF,GAAI4iB,EAAgB,KACF,KAAK,KACnB,KAAK,IAAItf,GAASsf,EAAgB,IAAI,OAAO,EAAG,CAAC,EACjD,KAAK,IAAIrf,GAASqf,EAAgB,IAAI,OAAO,EAAG,CAAC,CAAA,GAEpC9F,GAAW,CACxBnkB,EAAE,eAAA,EACFonB,EAA2B,QAAU,OACrC7f,EAAAuf,EAAS,UAAT,MAAAvf,EAAkB,QAClB,MACF,CAEJ,CACF,CAGA,GAAIpI,EAAgB,QAAQ9G,EAAGC,CAAC,EAAG,CACjC0H,EAAE,eAAA,EACF+mB,EAAuB,QAAU,GAEjC,MAAMvB,EAAarmB,EAAgB,qBAAA,EAC7B4c,EAAcvR,EAAAA,cAAc,iBAAiBrL,EAAgB,QAAQ,EACrE+qB,EAAS/qB,EAAgB,kBAAA,EACzBe,EAAK7H,EAAI6xB,EAAO,EAChB/pB,GAAK7H,EAAI4xB,EAAO,EAChBzf,GAAM,KAAK,IAAIsR,CAAW,EAC1BrR,GAAM,KAAK,IAAIqR,CAAW,EAC1BoO,GAAgBjqB,EAAKuK,GAAMtK,GAAKuK,GAChC0f,GAAgBlqB,EAAKwK,GAAMvK,GAAKsK,GAChC9F,GAAYwlB,GAAgB3E,EAAW,MAAQ,EAC/C6E,GAAYD,GAAgB5E,EAAW,OAAS,EAEhD8E,GAAa9lB,GAAwBrF,EAAiB,CAAE,EAAGwF,GAAW,EAAG0lB,GAAW,EAEpFppB,GAAM,KAAK,IAAA,EAEjB,GAAIA,GAAMimB,EAA0B,QAAUO,EAAuB,EACnEhgB,GAAAqf,EAAS,UAAT,MAAArf,GAAkB,QAClB,MACF,CACAyf,EAA0B,QAAUjmB,GAEhCA,GAAMkf,EAAsB,SAAWC,IACzCF,EAAuB,QAAU,MAGRjf,GAAM+lB,EAAiB,QAEzBQ,EACvBP,EAAc,SAAW,EAEzBA,EAAc,QAAU,EAE1BD,EAAiB,QAAU/lB,GAE3B,MAAMspB,GAAc/a,EAEpB,GAAIyX,EAAc,SAAW,EAC3BjnB,EAAE,eAAA,EACFmgB,EAAsB,QAAUlf,GAChCif,EAAuB,QAAU,CAAE,MAAO,EAAG,IAAKqK,GAAY,OAAQ,OAAQA,GAAY,MAAA,EAC1FpD,EAAqB,QAAU,GAC/BF,EAAc,QAAU,EACxBpX,GAAkB0a,GAAY,MAAM,EACpCxa,GAAkB,CAAC,EACnBE,GAAgBsa,GAAY,MAAM,EAC9BzD,EAAS,UACXA,EAAS,QAAQ,MAAA,EACjBA,EAAS,QAAQ,kBAAkB,EAAGyD,GAAY,MAAM,WAEjDtD,EAAc,UAAY,EAAG,CACtCjnB,EAAE,eAAA,EACFmgB,EAAsB,QAAUlf,GAChCkmB,EAAqB,QAAU,GAC/B,MAAMqD,GAAappB,GAAkBmpB,GAAaD,EAAU,EAC5DpK,EAAuB,QAAU,CAAE,MAAOsK,GAAW,MAAO,IAAKA,GAAW,IAAK,OAAQA,GAAW,GAAA,EACpG3a,GAAkB2a,GAAW,GAAG,EAChCza,GAAkBya,GAAW,KAAK,EAClCva,GAAgBua,GAAW,GAAG,EAC1B1D,EAAS,UACXA,EAAS,QAAQ,MAAA,EACjBA,EAAS,QAAQ,kBAAkB0D,GAAW,MAAOA,GAAW,GAAG,EAEvE,MACEtK,EAAuB,QAAU,KACjCrQ,GAAkBya,EAAU,EAC5Bva,GAAkBua,EAAU,EAC5Bra,GAAgBqa,EAAU,EACtBxD,EAAS,UACXA,EAAS,QAAQ,MAAA,EACjBA,EAAS,QAAQ,kBAAkBwD,GAAYA,EAAU,GAE3DnD,EAAqB,QAAU,GAEjC,MACF,CACAjH,EAAuB,QAAU,MACjCtY,GAAAkf,EAAS,UAAT,MAAAlf,GAAkB,OAClB,MACF,CAEA,GAAI0H,EAAW,OAKf,GAHCtP,EAAE,OAA6B,kBAAkBA,EAAE,SAAS,EAGzD0I,EAAQ,YAAcvJ,aAA2B8J,EAAAA,YAAa,CAChE,MAAMwhB,EAAOpyB,EAAI8G,EAAgB,EAC3BurB,EAAOpyB,EAAI6G,EAAgB,EAEjC,GAAIuJ,EAAQ,aAAa+hB,EAAMC,EAAM,EAAE,EAAG,CACxChiB,EAAQ,aAAa+hB,EAAMC,CAAI,EAC/BhiB,EAAQ,oBAAA,EACRyL,GAAmBjZ,GAAMA,EAAI,CAAC,EAC9B,MACF,CAEAwN,EAAQ,SAAS+hB,EAAMC,CAAI,EAC3BhiB,EAAQ,oBAAA,EACRyL,GAAmBjZ,GAAMA,EAAI,CAAC,EAC9B,MACF,CAGA,GAAIjC,GAAe,OAAS,GAAKgW,EAA6B,QAAS,CACrE,MAAMiV,EAASjV,EAA6B,QAEtC0b,EAAY1G,GAAsB,CAAE,EAAA5rB,EAAG,EAAAC,EAAG,OAAA4rB,EAAkC,KAAA7S,EAAM,EAExF,GAAIsZ,EAAU,OAAS,iBAWrB,GAVgBpG,GAA0B,CACxC,eAAAtrB,GACA,SAAAF,GACA,OAAAmrB,EACA,OAAQyG,EAAU,OAClB,OAAQA,EAAU,OAClB,EAAAtyB,EACA,EAAAC,EACA,aAAA+iB,CAAA,CACD,EACY,CACXpH,EAAY,CAAA,CAAE,EACd,MACF,UACS0W,EAAU,OAAS,mBAY5B,GAXgBnG,GAA0B,CACxC,eAAAvrB,GACA,SAAAF,GACA,OAAAmrB,EACA,EAAA7rB,EACA,EAAAC,EACA,aAAA+iB,EACA,cAAA3H,GACA,sBAAAE,GACA,mBAAAE,EAAA,CACD,EACY,CACXG,EAAY,CAAA,CAAE,EACd,MACF,UACS0W,EAAU,OAAS,eACZhG,GAAwB,CACtC,eAAA1rB,GACA,SAAAF,GACA,EAAAV,EACA,EAAAC,EACA,aAAA+iB,EACA,wBAAAuJ,EACA,cAAA/a,EAAA,CACD,EACY,CACXoK,EAAY,CAAA,CAAE,EACd,MACF,CAEJ,CAEA,MAAM2W,EAAiBxrB,IAAsBD,EAS7C,GAAIyrB,GAAkBA,EAAe,UAAY,IAAS,CAACA,EAAe,SAAW3xB,GAAe,SAAW,GAAKmG,IAAqB,CAEvI,MAAM8lB,EAAcL,GAAkB,CAAE,gBAAiB1lB,GAAmB,KAAM,mBAAAC,GAAoB,EACtG,GAAI8lB,EAAa,CACf,MAAM2F,EAAgB5F,GAAgB,CAAE,EAAA5sB,EAAG,EAAAC,EAAG,YAAA4sB,EAAa,iBAAA/b,EAAkB,KAAAkI,EAAM,EAEnF,GAAIwZ,EAAc,OAAS,cAAe,CACxCxF,GAAsB,CACpB,YAAAH,EACA,WAAY2F,EAAc,OAC1B,EAAAxyB,EACA,EAAAC,EACA,gBAAiB6G,GAAmB,KACpC,mBAAAC,GACA,aAAAic,CAAA,CACD,EACDpH,EAAY,CAAA,CAAE,EACd,MACF,SAAW4W,EAAc,OAAS,eAAgB,CAChDtF,GAAuB,CACrB,YAAAL,EACA,YAAa2F,EAAc,OAC3B,EAAAxyB,EACA,EAAAC,EACA,gBAAiB6G,GAAmB,KACpC,mBAAAC,GACA,aAAAic,CAAA,CACD,EACDpH,EAAY,CAAA,CAAE,EACd,MACF,SAAW4W,EAAc,OAAS,aAAc,CAC9CpF,GAAqB,CACnB,YAAAP,EACA,EAAA7sB,EACA,EAAAC,EACA,gBAAiB6G,GAAmB,KACpC,mBAAAC,GACA,aAAAic,EACA,wBAAAuJ,EACA,cAAA/a,EAAA,CACD,EACDoK,EAAY,CAAA,CAAE,EACd,MACF,SAAW4W,EAAc,OAAS,YAAa,CAC7CnF,GAAa,CACX,YAAAR,EACA,gBAAiB/lB,GAAmB,KACpC,gBAAAwmB,GACA,iBAAAxc,EACA,kBAAAI,GACA,KAAA8H,CAAA,CACD,EACD8C,GAAmBjZ,GAAMA,EAAI,CAAC,EAC9B,MACF,CACF,CAGA,GAAIiO,EAAiB,gBAAgB9Q,EAAGC,EAAG+Y,CAAI,EAAG,CAChD,GAAIuZ,EAAe,OAAQ,OAE3B,MAAMnzB,EAAOmzB,EAAe,eAAA,EACtBV,EAASY,EAAAA,qBAAqBrzB,CAAI,EAClCgtB,GAAaC,EAAAA,eAAewF,EAAO,EAAGA,EAAO,EAAG7xB,EAAGC,CAAC,EAE1Dob,GAAc,EAAI,EAClBE,GAAsB6Q,EAAU,EAChC3Q,GAAmB8W,EAAe,QAAQ,EAC1CvP,EAAa,YAAYuP,EAAgBvyB,EAAGC,EAAGmsB,GAAYmG,EAAe,QAAQ,EAE9ExrB,IAAsBD,aAA2B7H,iBACnD+jB,EAAa,oBAAsB,CACjC,MAAOlc,EACP,QAASC,GAAmB,EAAA,GAUhCupB,GAAqB,QAAUiC,EAAe,GAC9ChC,GAA2B,QAAU,CAAE,EAAAvwB,EAAG,EAAAC,CAAA,EAE1C2b,EAAY,CAAA,CAAE,EACd,MACF,CAGA,MAAM8W,EAAS5hB,EAAiB,cAAc9Q,EAAGC,EAAG+Y,CAAI,EACxD,GAAI0Z,EAAQ,CACV,GAAIH,EAAe,OAAQ,OAE3B,MAAMnzB,EAAOmzB,EAAe,eAAA,EACtBpF,EAAaoF,EAAe,qBAAA,EAClCvP,EAAa,YAAYuP,EAAgBG,EAAQ1yB,EAAGC,EAAGb,EAAM+tB,CAAU,EAMvEmD,GAAqB,QAAUiC,EAAe,GAC9ChC,GAA2B,QAAU,CAAE,EAAAvwB,EAAG,EAAAC,CAAA,EAEtC8G,IAAsBD,aAA2B7H,iBACnD+jB,EAAa,oBAAsB,CACjC,MAAOlc,EACP,QAASC,GAAmB,EAAA,GAIhC6U,EAAY,CAAA,CAAE,EACd,MACF,CAUA,GAAI2W,EAAe,QAAQvyB,EAAGC,CAAC,EAAG,CAChC,GAAIsyB,EAAe,OAAQ,OAa3B,GAAIzrB,aAA2B7H,gBAAgB,CAAC8H,GAAoB,CAClE,MAAM4rB,EAAe7rB,EAAgB,aAAa9G,EAAGC,CAAC,EAClD0yB,IACF3P,EAAa,uBAAyB2P,EAE1C,CAOE7rB,GACAyrB,EAAe,KAAOzrB,EAAgB,IACtC,EAAEyrB,aAA0Bxb,EAAAA,eAE5BuZ,GAAqB,QAAUiC,EAAe,GAC9ChC,GAA2B,QAAU,CAAE,EAAAvwB,EAAG,EAAAC,CAAA,GAK1CsyB,aAA0Bxb,EAAAA,aAC1B,EAAEwb,aAA0BtzB,EAAAA,eAC5B6H,GACAyrB,EAAe,KAAOzrB,EAAgB,IACtC,CAACmQ,IAED+L,EAAa,qBAAuB,CAClC,UAAWuP,EAAe,GAC1B,UAAWA,EAAe,EAC1B,UAAWA,EAAe,EAC1B,OAAQvyB,EACR,OAAQC,CAAA,GAIZ+iB,EAAa,UAAUuP,EAAgBvyB,EAAGC,CAAC,EAE3CssB,EAAwB,YAAc,IACtCA,EAAwB,QAAQ,IAAIgG,EAAe,GAAIA,EAAe,OAAO,EAE7E/gB,GAAc,CAAE,KAAM,KAAM,KAAM,KAAM,EAEpC+gB,aAA0BtzB,EAAAA,eAC5B+jB,EAAa,0BAA4BuP,EAAe,MAAA,GAGtDxrB,IAAsBD,aAA2B7H,iBACnD+jB,EAAa,oBAAsB,CACjC,MAAOlc,EACP,QAASC,GAAmB,EAAA,GAIhC6U,EAAY,CAAA,CAAE,EACd,MACF,CACF,CAGA,GAAI7U,IAAsBD,aAA2B7H,eAAc,CAejE,GAduBuuB,GAAmB,CACxC,EAAAxtB,EACA,EAAAC,EACA,mBAAA8G,GACA,gBAAAD,EACA,kBAAAoK,GACA,iBAAAJ,EACA,kBAAAgL,GACA,aAAAkH,EACA,wBAAAuJ,EACA,cAAA/a,GACA,YAAAoK,EACA,KAAA5C,CAAA,CACD,EACmB,OAEpByU,GAAoB,CAAE,kBAAAvc,GAAmB,gBAAiBpK,GAAmB,KAAM,iBAAAgK,EAAkB,kBAAAgL,GAAmB,KAAA9C,EAAM,EAC9H,MACF,CAEA,GAAIjS,GAAoB,CACtB0mB,GAAoB,CAAE,kBAAAvc,GAAmB,gBAAiBpK,GAAmB,KAAM,iBAAAgK,EAAkB,kBAAAgL,GAAmB,KAAA9C,EAAM,EAC9H,MACF,CAiBA,GAAI,CAACrR,EAAE,SAAU,CAMf,MAAMimB,EAAyB+B,GAAA,EACzBiD,EAAsC,CAAA,EAC5C,QAASroB,EAAIqjB,EAAuB,OAAS,EAAGrjB,GAAK,EAAGA,IAAK,CAC3D,MAAMtJ,GAAK2sB,EAAuBrjB,CAAC,EAC/BtJ,GAAG,UAAY,IACdA,GAAG,QAAQjB,EAAGC,CAAC,GACfguB,GAAoBhtB,GAAIjB,EAAGC,EAAGsZ,EAAgB,GACnDqZ,EAAK,KAAK3xB,EAAE,CACd,CAUA,IAAI4xB,EAA2E,KAC/E,GAAID,EAAK,SAAW,EACd9rB,IAAiB+rB,EAAa,CAAE,KAAM,UAAA,OACrC,CACL,MAAMC,EAAahsB,EACf8rB,EAAK,UAAW3xB,IAAOA,GAAG,KAAO6F,EAAgB,EAAE,EACnD,GACAgsB,IAAe,GACjBD,EAAa,CACX,KAAM,iBACN,UAAWD,EAAK,CAAC,EAAE,GACnB,EAAA5yB,EACA,EAAAC,EACA,SAAU0H,EAAE,QAAA,EAELmrB,EAAa,EAAIF,EAAK,OAC/BC,EAAa,CACX,KAAM,iBACN,UAAWD,EAAKE,EAAa,CAAC,EAAE,GAChC,EAAA9yB,EACA,EAAAC,EACA,SAAU0H,EAAE,QAAA,EAGdkrB,EAAa,CAAE,KAAM,UAAA,CAEzB,CACApC,GAAmB,QAAU,CAC3B,UAAW9oB,EAAE,UACb,aAAcA,EAAE,QAChB,aAAcA,EAAE,QAChB,eAAgB,CAAE,GAAGqpB,GAAa,OAAA,EAClC,mBAAoB,GACpB,WAAA6B,CAAA,EAEF,GAAI,CACF7zB,GAAO,kBAAkB2I,EAAE,SAAS,CACtC,MAAQ,CAER,CACAA,EAAE,eAAA,EACF,MACF,CAGA,MAAMimB,GAAyB+B,GAAA,EAe/B,GAduBjC,GAAuB,CAC5C,EAAA1tB,EACA,EAAAC,EACA,SAAU0H,EAAE,SACZ,uBAAAimB,GACA,gBAAiB9mB,GAAmB,KACpC,eAAAlG,GACA,kBAAAitB,GACA,uBAAApgB,EACA,aAAAuV,EACA,wBAAAuJ,EACA,cAAA/a,GACA,YAAAoK,CAAA,CACD,EACmB,OASpB,MAAMmX,EAAcpH,GAAgB,mBAAA,EAC9BqH,EAAiBC,GAAAA,wBAAwBtrB,EAAE,MAAM,EACvD,GAAI,CAACorB,GAAe,CAACC,EACnB,GAAIjC,GAAY,QAAU,MAAO,CAK/BN,GAAmB,QAAU,CAC3B,UAAW9oB,EAAE,UACb,aAAcA,EAAE,QAChB,aAAcA,EAAE,QAChB,eAAgB,CAAE,GAAGqpB,GAAa,OAAA,EAClC,mBAAoB,GACpB,WAAY,IAAA,EAEd,GAAI,CACFhyB,GAAO,kBAAkB2I,EAAE,SAAS,CACtC,MAAQ,CAER,CACAA,EAAE,eAAA,CACJ,MACEkmB,GAAkB,IAAI,EACtBpgB,EAAuB,CAAA,CAAE,EACzBmiB,GAAsB5vB,EAAGC,CAAC,CAGhC,EACA,CACEgX,EACAnQ,EACA6oB,GACA9B,GACApgB,EACAqD,EACA/J,GACAnG,GACAoiB,EACA3S,EACAmf,GACA1T,GACA9C,EACAxB,GACAE,GACAE,GAEA4P,EACA6H,GACA9V,EAAA,CACF,EAMI2Z,GAAoBh1B,EAAAA,YACvByJ,GAA6C,QAC5C,MAAM3I,GAASwoB,EAAU,QACzB,GAAI,CAACxoB,GAAQ,OAEb,MAAMqJ,GAAO4nB,GAAgB,SAAWjxB,GAAO,sBAAA,EACzCgB,GAAK2H,EAAE,QAAUU,GAAK,MAAQ2Q,EAAO0B,GACrCza,GAAK0H,EAAE,QAAUU,GAAK,KAAO2Q,EAAO2B,GA2B1C,GAzBAqU,EAAgB,QAAU,CAAE,EAAAhvB,EAAG,EAAAC,CAAA,EAS3BiwB,GAAkB,QAAQ,IAAIvoB,EAAE,SAAS,GAC3CuoB,GAAkB,QAAQ,IAAIvoB,EAAE,UAAW,CACzC,EAAA3H,EACA,EAAAC,EACA,QAAS0H,EAAE,QACX,QAASA,EAAE,OAAA,CACZ,EAUC6oB,GAAmB,QAAS,CAC9B,MAAM9Q,EAAI8Q,GAAmB,QACvBr8B,GAAI+7B,GAAkB,QAAQ,IAAIxQ,EAAE,QAAQ,EAC5CtrB,EAAI87B,GAAkB,QAAQ,IAAIxQ,EAAE,QAAQ,EAClD,GAAIvrB,IAAKC,EAAG,CACV,MAAMyT,EAAKzT,EAAE,QAAUD,GAAE,QACnB2T,EAAK1T,EAAE,QAAUD,GAAE,QAEnBuL,GADkB,KAAK,MAAMmI,EAAIC,CAAE,EACT4X,EAAE,cAC5ByT,EAAc,KAAK,IACvB,KAAK,IAAIzT,EAAE,cAAgBhgB,GAAO,GAAI,EACtC,CAAA,EAKI0zB,GACJ1T,EAAE,oBAAoB,EAAIA,EAAE,cAAc,KACtC2T,GACJ3T,EAAE,oBAAoB,EAAIA,EAAE,cAAc,IACtC4T,GACHn/B,GAAE,QAAUC,EAAE,SAAW,EAAIsrB,EAAE,cAAc,KAC1C6T,GACHp/B,GAAE,QAAUC,EAAE,SAAW,EAAIsrB,EAAE,cAAc,IAI1CxxB,EAAQilC,EAAczT,EAAE,cAKxB8T,EACJF,GACCF,GAA0B1T,EAAE,eAAe,GAAKxxB,EAC7CulC,GACJF,GACCF,GAA0B3T,EAAE,eAAe,GAAKxxB,EAY7CwlC,GAAUlM,EAAU,QAC1B,GAAIkM,GAAS,CACX,MAAMC,GAAaD,GAAQ,sBAAA,EACrBE,IAAcr3B,GAAAm3B,GAAQ,gBAAR,YAAAn3B,GAAuB,cACrCs3B,IAAiBD,IAAA,YAAAA,GAAa,cAAeD,GAAW,MACxDG,IAAkBF,IAAA,YAAAA,GAAa,eAAgBD,GAAW,OAC1DI,GAAa,GACbC,GAAcb,EAAc,KAAK,IAAIpC,GAAY,QAAS,IAAM,EAChEkD,GAAuBN,GAAW,MAAQK,GAC1CE,GAAwBP,GAAW,OAASK,GAG5CG,GAAcnD,GAAa,QAAQ,EACnCoD,GAAcpD,GAAa,QAAQ,EACnCqD,GAAyBV,GAAW,KAAOjU,EAAE,cAAc,KAC3D4U,GAAwBX,GAAW,IAAMjU,EAAE,cAAc,IACzD6U,GAAgBF,IAA0Bb,EAAaW,IACvDK,GAAeF,IAAyBb,GAAaW,IACrDK,GAAc,KAAK,IACvB,KAAK,IAAIF,GAAe,CAACN,GAAuBF,EAAU,EAC1DF,GAAiBE,EAAA,EAEbW,GAAa,KAAK,IACtB,KAAK,IAAIF,GAAc,CAACN,GAAwBH,EAAU,EAC1DD,GAAkBC,EAAA,EAEpBpD,GAAYwC,CAAW,EACvBtC,GAAa,CACX,EAAG2C,GAAciB,GAAcF,IAC/B,EAAGd,IAAciB,GAAaF,GAAA,CAC/B,CACH,MACE7D,GAAYwC,CAAW,EACvBtC,GAAa,CAAE,EAAG2C,EAAY,EAAGC,GAAY,CAEjD,CACA,MACF,CAOA,GACEhD,GAAmB,SACnBA,GAAmB,QAAQ,YAAc9oB,EAAE,UAC3C,CACA,MAAMkG,EAAI4iB,GAAmB,QACvB5oB,GAAKF,EAAE,QAAUkG,EAAE,aACnB/F,EAAKH,EAAE,QAAUkG,EAAE,aACzB,GAAI,CAACA,EAAE,mBACL,GAAI,KAAK,MAAMhG,GAAIC,CAAE,EAAI,EACvB+F,EAAE,mBAAqB,GAEvBA,EAAE,WAAa,SAGf,QAQJ,GAAIkjB,GAAY,SAAW,MAAO,OAClCF,GAAa,CACX,EAAGhjB,EAAE,eAAe,EAAIhG,GACxB,EAAGgG,EAAE,eAAe,EAAI/F,CAAA,CACzB,EACD,MACF,CAEA,GACEkb,EAAa,QAAA,IAAc,SAC3BmN,GAAmB,QACnB,CACA,KAAM,CAAE,EAAAh8B,EAAG,EAAAC,EAAA,EAAM+7B,GAAmB,QAC9BuB,EAAKxB,GAAkB,QAAQ,IAAI/7B,CAAC,EACpCw9B,EAAKzB,GAAkB,QAAQ,IAAI97B,EAAC,EAC1C,GAAIs9B,GAAMC,EAAI,CACZ,MAAMgD,EAAU3R,EAAa,YAAY0O,EAAIC,CAAE,EACzC,CAAE,QAAA9yB,GAAS,UAAAksB,IAAc/H,EAAa,gBAAA,EAC5C8H,GAAa,eAAe,CAC1B,QAAAjsB,GACA,UAAAksB,GACA,MAAO4J,EAAQ,MACf,cAAeA,EAAQ,cACvB,cAAeA,EAAQ,cACvB,gBAAiBA,EAAQ,gBACzB,aAAcrF,EACV,CAAC1I,EAAK3lB,KAAOquB,EAAW,aAAa1I,EAAK3lB,EAAE,EAC5C,MAAA,CACL,EAIDouB,GAAaiC,GACXA,EAAK,IAAKrwB,IACRA,GAAG,KAAOpC,GAAQ,GAAMA,GAA4BoC,EAAA,CACtD,CAEJ,CACA0G,EAAE,eAAA,EACF,MACF,CAIA,GAAIonB,EAA2B,SAAWjoB,aAA2BiQ,cAAa,CAChF,MAAMoW,EAAarmB,EAAgB,qBAAA,EAC7B+qB,GAAS/qB,EAAgB,kBAAA,EACzB4c,EAAcvR,EAAAA,cAAc,iBAAiBrL,EAAgB,QAAQ,EACrEe,EAAK7H,EAAI6xB,GAAO,EAChB/pB,EAAK7H,EAAI4xB,GAAO,EAChBzf,GAAM,KAAK,IAAIsR,CAAW,EAC1BrR,GAAM,KAAK,IAAIqR,CAAW,EAC1BoO,EAAgBjqB,EAAKuK,GAAMtK,EAAKuK,GAChC0f,GAAgBlqB,EAAKwK,GAAMvK,EAAKsK,GAEhCwf,GAAkBhK,EAA4B,QACpD,IAAIgN,EAAU,EACV7F,EAA2B,UAAY,UAAW6C,IAAA,MAAAA,GAAiB,OACrEgD,EAAUhD,GAAgB,MAAM,SAAS,EAAIA,GAAgB,MAAM,OAAO,EACjE7C,EAA2B,UAAY,QAAS6C,IAAA,MAAAA,GAAiB,OAC1EgD,EAAUhD,GAAgB,IAAI,SAAS,EAAIA,GAAgB,IAAI,OAAO,GAGxE,MAAMtlB,EAAYwlB,EAAgB3E,EAAW,MAAQ,EAC/C6E,EAAaD,GAAgB6C,EAAWzH,EAAW,OAAS,EAE5D0H,EAAY1oB,GAAwBrF,EAAiB,CAAE,EAAGwF,EAAW,EAAG0lB,EAAW,EAwBzF,GAtBIjD,EAA2B,UAAY,QACrC8F,GAAald,GACfD,GAAkBmd,CAAS,EAC3Brd,GAAkBqd,CAAS,IAE3Bnd,GAAkBC,CAAY,EAC9BC,GAAgBid,CAAS,EACzBrd,GAAkBqd,CAAS,EAC3B9F,EAA2B,QAAU,OAGnC8F,GAAapd,GACfG,GAAgBid,CAAS,EACzBrd,GAAkBqd,CAAS,IAE3Bjd,GAAgBH,CAAc,EAC9BC,GAAkBmd,CAAS,EAC3Brd,GAAkBqd,CAAS,EAC3B9F,EAA2B,QAAU,SAIrCN,EAAS,QAAS,CACpB,MAAM3/B,GAAQ,KAAK,IAAI2oB,EAAgBE,CAAY,EAC7C5oB,GAAM,KAAK,IAAI0oB,EAAgBE,CAAY,EACjD8W,EAAS,QAAQ,kBAAkB3/B,GAAOC,EAAG,CAC/C,CACA,MACF,CAGA,GAAIshB,EAAQ,YAAcvJ,aAA2B8J,EAAAA,YAAa,CAC5DP,EAAQ,oBACVA,EAAQ,aAAarQ,EAAI8G,EAAgB,EAAG7G,EAAI6G,EAAgB,CAAC,EACjEgV,GAAmBjZ,GAAMA,EAAI,CAAC,IAE9BwN,EAAQ,gBAAgBrQ,EAAI8G,EAAgB,EAAG7G,EAAI6G,EAAgB,CAAC,EACpEgV,GAAmBjZ,GAAMA,EAAI,CAAC,GAEhC,MACF,CAQA,GALI8E,EAAE,SAAWuT,GACfC,EAAmBxT,EAAE,MAAM,EAIzB+E,EAAoB,CACtBmjB,GAAuB7vB,EAAGC,EAAGS,EAAQ,EACrC,MACF,CAGA,GAAIsiB,EAAa,QAAA,IAAc,OAAQ,CACrC,MAAMG,EAAmBH,EAAa,2BAEtC,GAAIG,GAAoBviB,GAAe,OAAS,EAAG,CACjD,MAAMiH,GAAK7H,EAAImjB,EAAiB,OAC1Brb,EAAK7H,EAAIkjB,EAAiB,OAE3BA,EAAiB,kBACpBA,EAAiB,oBAAsB,KAGzC,MAAM2R,EAAmC,CAAA,EACzC3R,EAAiB,kBAAkB,QAAS4R,GAAuC,CACjF,MAAMl2B,GAAUk2B,EAAY,QACtBhK,GAAYgK,EAAY,UAExBhtB,EAAiBlJ,GAAQ,MAAA,EAC/BkJ,EAAe,EAAIgjB,GAAU,EAAIljB,GACjCE,EAAe,EAAIgjB,GAAU,EAAIjjB,EAEjCqb,EAAiB,gBAAiB,IAAIpb,EAAe,GAAIA,CAAc,EACvE+sB,EAAgB,KAAK/sB,CAAc,CACrC,CAAC,EAEDsnB,GAAa2F,GAAiBA,EAAa,IAAK/zB,IAAO6zB,EAAgB,KAAMG,IAAOA,GAAG,KAAOh0B,GAAG,EAAE,GAAKA,EAAE,CAAC,EAE3GkiB,EAAiB,aAAe,CAAE,GAAAtb,GAAI,GAAAC,CAAA,EAElC,CAACqb,EAAiB,UAAYtM,GAAqB,UACrDsM,EAAiB,SAAW,CAAE,GAAGtM,GAAqB,OAAA,GAGxDiF,GAAmBjZ,GAAMA,EAAI,CAAC,EAC9B+Y,EAAY,CAAA,CAAE,EACd,MACF,CACF,CAEA,GAAIoH,EAAa,YAAc,QAAUlc,EAAiB,CAIxD,GAHiBkc,EAAa,YAAA,IAGb,kBAAmB,CAClC,MAAMnQ,EAAUmQ,EAAa,WAAA,EACvBkS,GAAeriB,EAAQ,QAE7B,GAAIqiB,cAAwBluB,EAAAA,cAAgBkuB,GAAa,WAAY,CACnE,MAAMlqC,GAAS0rB,GAAe,oBAAoBwe,GAAcriB,EAAS7S,EAAGC,CAAC,EAEvEk1B,EAAmBnS,EAAa,kBAChCoS,EAAqBpS,EAAa,oBAClCqS,EAAeF,GAAoBC,EAEzC,GAAIC,GAAgBvuB,aAA2B7H,eAAc,CAC3D,MAAMkS,EAAe+jB,GAAa,MAAA,EAClC/jB,EAAa,EAAInmB,GAAO,EACxBmmB,EAAa,EAAInmB,GAAO,EACxBmmB,EAAa,WAAWnmB,GAAO,MAAOA,GAAO,MAAOA,GAAO,UAAWA,GAAO,WAAY,EAAK,EAE9F,MAAMuiC,GAAezmB,EAAgB,MAAA,EAC/BwuB,GAAaH,EACfA,EAAiB,WACjB5H,GAAa,SAAS,UAAWvnB,IAAMA,GAAE,KAAQqvB,EAAoC,OAAO,EAC5FC,IAAc,IAChB/H,GAAa,SAAS+H,EAAU,EAAInkB,EACpCoc,GAAa,yBAAyB,EAAI,GAG5C8B,GAAa2F,IAAiBA,GAAa,IAAK/zB,IAAQA,GAAG,KAAOssB,GAAa,GAAKA,GAAetsB,EAAG,CAAC,EAEvG,MAAM8iB,GAAcf,EAAa,WAAA,EAC5Be,GAAY,iBACfA,GAAY,eAAiB,CAAA,GAE/BA,GAAY,eAAewJ,GAAa,EAAE,EAAIA,GAE1C6H,GAAsBE,IAAc,IACtCpkB,GAAkBqc,GAAa,SAAS+H,EAAU,CAAC,EACnDxkB,EAAiB,OAAOK,EAAc6H,CAAI,GAG5C8C,GAAmBjZ,IAAMA,GAAI,CAAC,CAChC,KAAO,CACL,MAAMkF,EAAiBmtB,GAAa,MAAA,EACpCntB,EAAe,EAAI/c,GAAO,EAC1B+c,EAAe,EAAI/c,GAAO,EAC1B+c,EAAe,WAAW/c,GAAO,MAAOA,GAAO,MAAOA,GAAO,UAAWA,GAAO,WAAY,EAAK,EAEhGqkC,GAAa2F,IACXA,GAAa,IAAK/zB,IAAQA,GAAG,KAAO8G,EAAe,GAAKA,EAAiB9G,EAAG,CAAA,EAG9E,MAAM8iB,GAAcf,EAAa,WAAA,EAC5Be,GAAY,iBACfA,GAAY,eAAiB,CAAA,GAE/BA,GAAY,eAAehc,EAAe,EAAE,EAAIA,EAEhD+I,EAAiB,OAAO/I,EAAgBiR,CAAI,EAC5C8C,GAAmBjZ,IAAMA,GAAI,CAAC,CAChC,CACA,MACF,CACF,CAGA,MAAMuyB,GAAqBpS,EAAa,oBAElC4K,EAAyB+B,GAAA,EACzB4F,EAAqC,CAAA,EAC3C3H,EAAuB,QAAS1zB,GAAM,CAChCA,aAAa+E,EAAAA,aACfs2B,EAAkB,KAAK,GAAGr7B,EAAE,QAAQ,EAEpCq7B,EAAkB,KAAKr7B,CAAC,CAE5B,CAAC,EAED,IAAIs7B,EAASxS,EAAa,WAAWhjB,EAAGC,EAAGs1B,CAAiB,EAE5D,MAAME,GACJL,IAAsBtuB,aAA2B7H,EAAAA,aAC7C6H,EAAgB,SAAS,KAAMd,GAAMA,EAAE,KAAOovB,GAAmB,OAAO,EACxEtuB,EAMN,GAJI2uB,KACFD,EAASjG,GAAc,cAAciG,EAAQC,GAAuBF,CAAiB,GAGnFH,IAAsBtuB,aAA2B7H,eAAc,CACjE,MAAMsuB,EAAezmB,EAAgB,MAAA,EAC/BwuB,GAAa/H,EAAa,SAAS,UAAWvnB,GAAMA,EAAE,KAAOovB,GAAmB,OAAO,EAC7F,GAAIE,IAAc,EAAG,CACnB,MAAMnkB,EAAeoc,EAAa,SAAS+H,EAAU,EAAE,MAAA,EACvDnkB,EAAa,EAAIqkB,EAAO,EACxBrkB,EAAa,EAAIqkB,EAAO,EACxBjI,EAAa,SAAS+H,EAAU,EAAInkB,EACpCoc,EAAa,yBAAyB,EAAI,CAC5C,CAEA8B,GAAa2F,GAAiBA,EAAa,IAAK/zB,GAAQA,EAAG,KAAOssB,EAAa,GAAKA,EAAetsB,CAAG,CAAC,EAEvG,MAAM8iB,GAAcf,EAAa,WAAA,EAMjC,GALKe,GAAY,iBACfA,GAAY,eAAiB,CAAA,GAE/BA,GAAY,eAAewJ,EAAa,EAAE,EAAIA,EAE1C+H,IAAc,EAAG,CACnB,MAAMI,EAAiBnI,EAAa,SAAS+H,EAAU,EACvDpkB,GAAkBwkB,CAAc,EAChC5kB,EAAiB,OAAO4kB,EAAgB1c,CAAI,CAC9C,CAEA8C,GAAmBjZ,GAAMA,EAAI,CAAC,EAC9B+Y,EAAY,CAAA,CAAE,CAChB,KAAO,CACL,MAAM+Z,EAAoB3S,EAAa,0BACjCjb,GACJjB,aAA2B7H,gBAAgB02B,EACvCA,EAAkB,MAAA,EAClB7uB,EAAgB,MAAA,EAEtB,GAAIiB,cAA0B9I,EAAAA,aAAc,CAE1C,MAAM22B,EADU5S,EAAa,WAAA,EACC,UACxBnb,EAAK2tB,EAAO,EAAII,EAAc,EAC9B9tB,EAAK0tB,EAAO,EAAII,EAAc,EACpC7tB,GAAe,KAAKF,EAAIC,CAAE,CAC5B,MACEC,GAAe,EAAIytB,EAAO,EAC1BztB,GAAe,EAAIytB,EAAO,EAG5BnG,GAAa2F,GAAiBA,EAAa,IAAK/zB,GAAQA,EAAG,KAAO8G,GAAe,GAAKA,GAAiB9G,CAAG,CAAC,EAC3G6P,EAAiB,OAAO/I,GAAgBiR,CAAI,EAE5C,MAAM+K,GAAcf,EAAa,WAAA,EAC5Be,GAAY,iBACfA,GAAY,eAAiB,CAAA,GAE/BA,GAAY,eAAehc,GAAe,EAAE,EAAIA,EAClD,CAEA+T,GAAmBjZ,GAAMA,EAAI,CAAC,EAC9BkY,EAAcuU,EAAW,WAAW,EAEpC,IAAIuG,GACJ,GAAIT,IAAsBtuB,aAA2B7H,eACnD42B,GAAgB/uB,EAAgB,SAAS,KAAMd,GAAMA,EAAE,KAAOovB,GAAmB,OAAO,MACnF,CACL,MAAMrR,EAAcf,EAAa,WAAA,EAC7Be,EAAY,gBAAkBjd,EAChC+uB,GAAgB9R,EAAY,eAAejd,EAAgB,EAAE,EAE7D+uB,GAAgB/uB,GAAmB,MAEvC,CAEA,GAAI+uB,GAAe,CACjB,MAAMC,EAAavG,GAAc,cAAcsG,GAAeN,EAAmBra,CAAe,EAChGD,GAAqB6a,CAAU,CACjC,CACF,SAAW9S,EAAa,QAAA,IAAc,SAAU,CAC9C,KAAM,CAAE,GAAAnb,EAAI,GAAAC,EAAA,EAAOkb,EAAa,aAAahjB,EAAGC,CAAC,EAC3CiU,EAAgB8O,EAAa,iBAAA,EAG7B+S,EAAqB/S,EAAa,6BACxC,GAAI+S,GAAsBn1B,GAAe,OAAS,EAAG,CACnD,MAAMo1B,EAAcD,EAAmB,YACjC9hB,EAAS8hB,EAAmB,OAElC,IAAIE,EAAgBC,EAChBC,GAAuBC,GAE3B,OAAQniB,EAAA,CACN,IAAK,WACHgiB,EAASD,EAAY,KAAME,EAASF,EAAY,KAChDG,GAAgBH,EAAY,KAAMI,GAAgBJ,EAAY,KAC9D,MACF,IAAK,YACHC,EAASD,EAAY,KAAME,EAASF,EAAY,KAChDG,GAAgBH,EAAY,KAAMI,GAAgBJ,EAAY,KAC9D,MACF,IAAK,cACHC,EAASD,EAAY,KAAME,EAASF,EAAY,KAChDG,GAAgBH,EAAY,KAAMI,GAAgBJ,EAAY,KAC9D,MACF,IAAK,eACHC,EAASD,EAAY,KAAME,EAASF,EAAY,KAChDG,GAAgBH,EAAY,KAAMI,GAAgBJ,EAAY,KAC9D,MACF,QACEC,EAASD,EAAY,KAAME,EAASF,EAAY,KAChDG,GAAgBH,EAAY,KAAMI,GAAgBJ,EAAY,IAAA,CAGlE,MAAMK,GAAkBF,GAAgBtuB,EAClCyuB,GAAkBF,GAAgBtuB,GAElCyuB,GAAa,KAAK,IAAIJ,GAAgBF,CAAM,EAC5CO,GAAa,KAAK,IAAIJ,GAAgBF,CAAM,EAC5CO,GAAW,KAAK,IAAIJ,GAAkBJ,CAAM,EAC5CS,GAAW,KAAK,IAAIJ,GAAkBJ,CAAM,EAE5CS,GAASJ,GAAa,EAAIE,GAAWF,GAAa,EAClDK,GAASJ,GAAa,EAAIE,GAAWF,GAAa,EAClD92B,IAASi3B,GAASC,IAAU,EAE5B3jB,GAAa+iB,EAAY,KAAOA,EAAY,KAC5C9iB,GAAc8iB,EAAY,KAAOA,EAAY,KAC7C5K,GAAWnY,GAAavT,GACxB2rB,GAAYnY,GAAcxT,GAEhC,IAAIm3B,GAAkBZ,EAAQa,GAAkBZ,EAC5Ca,GAAkBd,EAAQe,GAAkBd,EAEhD,OAAQjiB,EAAA,CACN,IAAK,WACH8iB,GAAUd,EAAQe,GAAUd,EAC5BW,GAAUZ,EAAS7K,GAAU0L,GAAUZ,EAAS7K,GAChD,MACF,IAAK,YACHwL,GAAUZ,EAAQe,GAAUd,EAC5Ba,GAAUd,EAAS7K,GAAU0L,GAAUZ,EAAS7K,GAChD,MACF,IAAK,cACH0L,GAAUd,EAAQa,GAAUZ,EAC5BW,GAAUZ,EAAS7K,GAAU4L,GAAUd,EAAS7K,GAChD,MACF,IAAK,eACHwL,GAAUZ,EAAQa,GAAUZ,EAC5Ba,GAAUd,EAAS7K,GAAU4L,GAAUd,EAAS7K,GAChD,KAAA,CAuCJ,GApCA0K,EAAmB,kBAAkB,QAAShB,IAAuC,CACnF,MAAMl2B,GAAUk2B,GAAY,QACtBhK,GAAYgK,GAAY,UAExBl1B,GAAUkrB,GAAU,EAAIkL,EACxBn2B,GAAUirB,GAAU,EAAImL,EAExBe,GAAOhB,EAASp2B,GAAUH,GAC1Bw3B,GAAOhB,EAASp2B,GAAUJ,GAE1BqI,GAAiBlJ,GAAQ,MAAA,EAE/B,GAAIkJ,cAA0Bf,EAAAA,aAAc,CAC1Ce,GAAe,EAAIkvB,GACnBlvB,GAAe,EAAImvB,GACnB,MAAMC,GAAYpvB,GAAe,cAC3BqvB,GAAiBrM,GAAU,cACjCoM,GAAU,MAAQC,GAAe,MAAQ13B,GACzCy3B,GAAU,OAASC,GAAe,OAAS13B,EAC7C,SAAWqI,cAA0B9I,eAAc,CACjD8I,GAAe,EAAIkvB,GACnBlvB,GAAe,EAAImvB,GACnB,MAAMG,GAAgBtM,GAAU,MAAQrrB,GAClC43B,GAAiBvM,GAAU,OAASrrB,GAC1CqI,GAAe,OAAOkM,EAAwBojB,GAAeC,GAAgBvM,EAA0C,CACzH,KAAO,CACL,MAAMwM,GAAkBxM,GAAU,MAAQrrB,GACpC83B,GAAmBzM,GAAU,OAASrrB,GAC5CqI,GAAe,OAAOkM,EAAwBsjB,GAAiBC,GAAkBzM,EAA0C,EAC3HhjB,GAAe,EAAIkvB,GACnBlvB,GAAe,EAAImvB,EACrB,CAEA5J,GAAgBvlB,EAAc,CAChC,CAAC,EAEG8O,GAAqB,QAAS,CAChC,MAAM4gB,IAAcZ,GAAUE,IAAW,EACnCW,IAAcZ,GAAUE,IAAW,EACzCngB,GAAqB,QAAU,CAC7B,QAAS4gB,GACT,QAASC,GACT,MAAOX,GAAUF,GACjB,OAAQG,GAAUF,GAClB,SAAUjgB,GAAqB,QAAQ,QAAA,CAE3C,CAEA,MACF,CAGA,MAAM8gB,EAAezjB,EAAc,QACnC,GACEyjB,aAAwB3wB,EAAAA,cACxB2wB,EAAa,YACbzjB,EAAc,OAAO,QACrBA,EAAc,OAAO,OAAO,WAAW,OAAO,EAC9C,CACA,MAAM0jB,EAAa1jB,EAAc,OAAO,OAAO,QAAQ,QAAS,EAAE,EAC5DrB,EAAUmQ,EAAa,WAAA,EAEvBh4B,EAAS0rB,GAAe,oBAAoBihB,EAAcC,EAAY1jB,EAAerB,EAAS7S,EAAGC,CAAC,EAExGjB,GAAO,MAAM,OAAShU,EAAO,OAE7B,MAAMmqC,EAAmBnS,EAAa,kBAChC6U,GAAiB7U,EAAa,oBAC9BqS,GAAeF,GAAoB0C,GAEzC,GAAIxC,IAAgBvuB,aAA2B7H,eAAc,CAC3D,MAAMkS,GAAewmB,EAAa,MAAA,EAClCxmB,GAAa,EAAInmB,EAAO,EACxBmmB,GAAa,EAAInmB,EAAO,EACxBmmB,GAAa,SAAWnmB,EAAO,SAC/BmmB,GAAa,cAAc,MAAQnmB,EAAO,MAC1CmmB,GAAa,cAAc,OAASnmB,EAAO,OAC3CmmB,GAAa,WAAWnmB,EAAO,MAAOA,EAAO,MAAOA,EAAO,UAAWA,EAAO,WAAY,EAAK,EAE9F,MAAMuiC,GAAezmB,EAAgB,MAAA,EAC/BwuB,GAAaH,EACfA,EAAiB,WACjB5H,GAAa,SAAS,UAAWvnB,IAAMA,GAAE,KAAQqvB,GAAoC,OAAO,EAC5FC,IAAc,IAChB/H,GAAa,SAAS+H,EAAU,EAAInkB,GACpCoc,GAAa,yBAAyB,EAAI,GAE5CD,GAAgBC,EAAY,EAExBsK,IAAkBvC,IAAc,GAClCpkB,GAAkBqc,GAAa,SAAS+H,EAAU,CAAC,CAEvD,KAAO,CACL,MAAMvtB,GAAiB4vB,EAAa,MAAA,EACpC5vB,GAAe,EAAI/c,EAAO,EAC1B+c,GAAe,EAAI/c,EAAO,EAC1B+c,GAAe,SAAW/c,EAAO,SACjC+c,GAAe,cAAc,MAAQ/c,EAAO,MAC5C+c,GAAe,cAAc,OAAS/c,EAAO,OAC7C+c,GAAe,WAAW/c,EAAO,MAAOA,EAAO,MAAOA,EAAO,UAAWA,EAAO,WAAY,EAAK,EAChGsiC,GAAgBvlB,EAAc,CAChC,CACA,MACF,CAEA,GAAI,CAACjB,EAAiB,OAEtB,MAAM+wB,GAAiB7U,EAAa,oBAC9B8U,GAAyBD,IAAkB/wB,aAA2B7H,EAAAA,aAEtE84B,EAAmC,CAAA,EACzCr3B,GAAS,QAASxG,GAAM,CAClBA,aAAa+E,EAAAA,aACf84B,EAAgB,KAAK,GAAG79B,EAAE,QAAQ,EAElC69B,EAAgB,KAAK79B,CAAC,CAE1B,CAAC,EAYD,IAAI6N,GAVWiwB,GAAAA,qBAAqB,cAAc,CAChD,QAAS9jB,EAAc,QACvB,OAAQA,EAAc,OACtB,GAAArM,EACA,GAAAC,GACA,UAAWoM,EAAc,UACzB,YAAa6jB,EACb,WAAAzI,CAAA,CACD,EAE2B,QAI5B,GAFAtM,EAAa,oBAAoBjb,EAAc,EAE3C+vB,GAAwB,CAC1B,MAAMvK,EAAezmB,EAAgB,MAAA,EAC/BwuB,EAAa/H,EAAa,SAAS,UAAWvnB,GAAMA,EAAE,KAAO6xB,GAAe,OAAO,EACrFvC,GAAc,IAChB/H,EAAa,SAAS+H,CAAU,EAAIvtB,GACpCwlB,EAAa,yBAAyB,EAAI,GAE5CD,GAAgBC,CAAY,EAExB+H,GAAc,IAChBpkB,GAAkBqc,EAAa,SAAS+H,CAAU,CAAC,EACnDxkB,EAAiB,OAAOyc,EAAa,SAAS+H,CAAU,EAAGtc,CAAI,EAEnE,MACEsU,GAAgBvlB,EAAc,EAC9B+I,EAAiB,OAAO/I,GAAgBiR,CAAI,EAG9C8C,GAAmBjZ,GAAMA,EAAI,CAAC,EAC9BkY,EAAcuU,EAAW,WAAW,CACtC,SAAWtM,EAAa,QAAA,IAAc,SAAU,CAC9ChkB,GAAO,MAAM,OAAS,WAEjBoc,KACHC,GAAc,EAAI,EAClBoU,GAAA,MAAAA,EAAwB,KAI1B,MAAMwI,EAAuBjV,EAAa,+BAC1C,GAAIiV,GAAwBr3B,GAAe,OAAS,EAAG,CACrD,MAAMirB,GAASoM,EAAqB,YAC9BC,EAAgBlV,EAAa,aAAahjB,EAAGC,EAAG4rB,GAAO,QAASA,GAAO,OAAO,EACpFpQ,GAAmByc,CAAa,EAEhCD,EAAqB,kBAAkB,QAASlD,GAAuC,CACrF,MAAMl2B,EAAUk2B,EAAY,QACtBhK,EAAYgK,EAAY,UAExBoD,GAAa3kB,EAAAA,UAAU,wBAC3BuX,EAAU,EAAGA,EAAU,EACvBc,GAAO,QAASA,GAAO,QACvBqM,CAAA,EAGInwB,GAAiBlJ,EAAQ,MAAA,EAC/BkJ,GAAe,EAAIowB,GAAW,EAC9BpwB,GAAe,EAAIowB,GAAW,EAC9BpwB,GAAe,YAAYgjB,EAAU,SAAWmN,CAAa,EAE7D5K,GAAgBvlB,EAAc,CAChC,CAAC,EACD,MACF,CAEA,MAAM8vB,GAAiB7U,EAAa,oBAC9BoV,EAAYP,GAAiB9wB,GAAqBD,EAExD,GAAI,CAACsxB,EAAW,OAEhB,MAAMh5B,EAAOg5B,EAAU,eAAA,EACjBvG,EAASY,EAAAA,qBAAqBrzB,CAAI,EAClCi5B,GAAgBxG,EAAO,EACvByG,GAAgBzG,EAAO,EAEvBrG,EAAcxI,EAAa,aAAahjB,EAAGC,EAAG4xB,EAAO,EAAGA,EAAO,CAAC,EAEhE9pB,GAAiBqwB,EAAU,MAAA,EAGjC,GAFArwB,GAAe,YAAYyjB,CAAW,EAElC,EAAEzjB,cAA0B9I,EAAAA,cAAe,CAC7C,MAAMs5B,GAAUxwB,GAAe,eAAA,EACzBywB,EAAY/F,EAAAA,qBAAqB8F,EAAO,EACxC14B,EAAUw4B,GAAgBG,EAAU,EACpC14B,EAAUw4B,GAAgBE,EAAU,EAC1CzwB,GAAe,GAAKlI,EACpBkI,GAAe,GAAKjI,CACtB,CAMA,GAJAmvB,EAAmB,QAAUlnB,GAC7B0T,GAAmB1T,GAAe,QAAQ,EAC1C2T,GAA0B7Y,IAAKA,GAAI,CAAC,EAEhCg1B,IAAkB/wB,aAA2B7H,eAAc,CAC7D,MAAMsuB,GAAezmB,EAAgB,MAAA,EAC/BwuB,EAAa/H,GAAa,SAAS,UAAWvnB,GAAMA,EAAE,KAAO6xB,GAAe,OAAO,EACrFvC,GAAc,IAChB/H,GAAa,SAAS+H,CAAU,EAAIvtB,GACpCwlB,GAAa,yBAAyB,EAAI,GAE5CD,GAAgBC,EAAY,EAExB+H,GAAc,IAChBpkB,GAAkBqc,GAAa,SAAS+H,CAAU,CAAC,EACnDxkB,EAAiB,OAAOyc,GAAa,SAAS+H,CAAU,EAAGtc,CAAI,EAEnE,MACEsU,GAAgBvlB,EAAc,EAC9B+I,EAAiB,OAAO/I,GAAgBiR,CAAI,EAG9C8C,GAAmBjZ,IAAMA,GAAI,CAAC,CAChC,KAAO,CAIL,GAAIjC,GAAe,OAAS,GAAKgW,EAA6B,QAAS,CACrE,MAAMiV,EAASjV,EAA6B,QACtCkV,EAAY,GAAK9S,EAEjByK,EAAU,CACd,CAAE,EAAGoI,EAAO,KAAM,EAAGA,EAAO,KAAM,OAAQ,WAAY,OAAQ,aAAA,EAC9D,CAAE,EAAGA,EAAO,KAAM,EAAGA,EAAO,KAAM,OAAQ,YAAa,OAAQ,aAAA,EAC/D,CAAE,EAAGA,EAAO,KAAM,EAAGA,EAAO,KAAM,OAAQ,cAAe,OAAQ,aAAA,EACjE,CAAE,EAAGA,EAAO,KAAM,EAAGA,EAAO,KAAM,OAAQ,eAAgB,OAAQ,aAAA,CAAc,EAG5E7H,GAAgB6H,EAAO,UAAY,EACnCG,GAAiBvI,EAAQ,IAAKE,GAAW,CAC7C,GAAIK,KAAkB,EAAG,CACvB,MAAMN,EAAe,CAACM,GAAgB,KAAK,GAAM,IAC3C5R,EAAM,KAAK,IAAIsR,CAAW,EAC1BrR,GAAM,KAAK,IAAIqR,CAAW,EAC1B+U,GAAM9U,EAAO,EAAIkI,EAAO,QACxB6M,GAAM/U,EAAO,EAAIkI,EAAO,QAC9B,MAAO,CACL,EAAGA,EAAO,SAAW4M,GAAMrmB,EAAMsmB,GAAMrmB,IACvC,EAAGwZ,EAAO,SAAW4M,GAAMpmB,GAAMqmB,GAAMtmB,GACvC,OAAQuR,EAAO,OACf,OAAQA,EAAO,MAAA,CAEnB,CACA,OAAOA,CACT,CAAC,EAED,UAAWA,KAAUqI,GAAgB,CACnC,MAAMyM,EAAMz4B,EAAI2jB,EAAO,EACjB+U,EAAMz4B,EAAI0jB,EAAO,EAGvB,GAFiB,KAAK,KAAK8U,EAAMA,EAAMC,EAAMA,CAAG,GAEhC5M,EAAW,CACzB9sB,GAAO,MAAM,OAAS2kB,EAAO,OAC7BnS,GAAc,CAAE,KAAM,yBAA0B,KAAMmS,EAAQ,EAC9D,MACF,CACF,CAEA,MAAMsI,EAAqB,GAAUjT,EAKrC,GAJ+B,KAAK,KAClC,KAAK,IAAIhZ,EAAI6rB,EAAO,gBAAiB,CAAC,EAAI,KAAK,IAAI5rB,EAAI4rB,EAAO,gBAAiB,CAAC,CAAA,GAGpDI,EAAmB,CAC/CjtB,GAAO,MAAM,OAAS,OACtBwS,GAAc,CAAE,KAAM,kCAAmC,KAAM,KAAM,EACrE,MACF,CAEA,IAAIc,GAAStS,EACTuS,EAAStS,EAEb,GAAI+jB,KAAkB,EAAG,CACvB,MAAMN,EAAeM,GAAgB,KAAK,GAAM,IAC1C5R,EAAM,KAAK,IAAIsR,CAAW,EAC1BrR,EAAM,KAAK,IAAIqR,CAAW,EAC1B+U,GAAMz4B,EAAI6rB,EAAO,QACjB6M,GAAMz4B,EAAI4rB,EAAO,QACvBvZ,GAASuZ,EAAO,SAAW4M,GAAMrmB,EAAMsmB,GAAMrmB,GAC7CE,EAASsZ,EAAO,SAAW4M,GAAMpmB,EAAMqmB,GAAMtmB,EAC/C,CAEA,GAAIE,IAAUuZ,EAAO,MAAQvZ,IAAUuZ,EAAO,MAAQtZ,GAAUsZ,EAAO,MAAQtZ,GAAUsZ,EAAO,KAAM,CACpG7sB,GAAO,MAAM,OAAS,OACtBwS,GAAc,CAAE,KAAM,yBAA0B,KAAMqa,EAAQ,EAC9D,MACF,CACF,CAGA,GAAIlkB,EAAE,OAAQ,CACZ,IAAIiK,EAAuC,KAC3C,MAAMgc,EAAyB+B,GAAA,EAC/B,QAASplB,EAAIqjB,EAAuB,OAAS,EAAGrjB,GAAK,EAAGA,IAAK,CAC3D,MAAM1L,GAAU+uB,EAAuBrjB,CAAC,EACxC,GAAI1L,GAAQ,UAAY,GAExB,IAAIA,cAAmBI,EAAAA,aACrB,QAAS05B,GAAI95B,GAAQ,SAAS,OAAS,EAAG85B,IAAK,EAAGA,KAAK,CACrD,MAAMxtC,EAAQ0T,GAAQ,SAAS85B,EAAC,EAChC,GAAIxtC,EAAM,UAAY,GAAO,SAC7B,MAAMiU,GAAOjU,EAAM,qBAAA,EACnB,GAAI6U,GAAKZ,GAAK,GAAKY,GAAKZ,GAAK,EAAIA,GAAK,OAASa,GAAKb,GAAK,GAAKa,GAAKb,GAAK,EAAIA,GAAK,OAAQ,CACvFwS,EAAiBzmB,EACjB,KACF,CACF,KACK,CACL,MAAMiU,GAAOP,GAAQ,qBAAA,EACrB,GAAImB,GAAKZ,GAAK,GAAKY,GAAKZ,GAAK,EAAIA,GAAK,OAASa,GAAKb,GAAK,GAAKa,GAAKb,GAAK,EAAIA,GAAK,OAAQ,CACvFwS,EAAiB/S,GACjB,KACF,CACF,CACA,GAAI+S,EAAgB,MACtB,CAEA,GAAIA,EAAgB,CAClB,MAAM2jB,EAAqC,CAAA,EAC3C3H,EAAuB,QAAS1zB,IAAM,CAChCA,cAAa+E,EAAAA,aACfs2B,EAAkB,KAAK,GAAGr7B,GAAE,QAAQ,EAEpCq7B,EAAkB,KAAKr7B,EAAC,CAE5B,CAAC,EAED,MAAM47B,GAAavG,GAAc,cAAc3d,EAAgB2jB,EAAmB,EAAI,EACtFta,GAAqB6a,EAAU,CACjC,MACE7a,GAAqB,CAAA,CAAE,CAE3B,MACMD,GAAkB,OAAS,GAC7BC,GAAqB,CAAA,CAAE,EAK3B,MAAM2d,EAAoB7xB,IAAsBD,EAEhD,GAAI8xB,GAAqBA,EAAkB,UAAY,GACrD,GAAIA,aAA6B5xB,EAAAA,cAAgB4xB,EAAkB,WAAY,CAC7E,MAAM9L,EAAa8L,EAAkB,kBAAkB54B,EAAGC,EAAG+Y,CAAI,EACjE,GAAI8T,EAAY,CACd,MAAM1Y,EAASykB,EAAAA,0BACb/L,EAAW,OAAQA,EAAW,OAC9B8L,EAAkB,EAAGA,EAAkB,EACvC9L,EAAW,KAAM8L,EAAkB,SAAU9L,EAAW,MAAA,EAE1D9tB,GAAO,MAAM,OAASoV,EACtB5C,GAAc,CAAE,KAAM,cAAe,KAAMsb,EAAY,EACvD,MACF,CAEA,MAAMC,EAAcjc,EAAiB,cAAc9Q,EAAGC,EAAG+Y,CAAI,EAC7D,GAAI+T,EAAa,CACf/tB,GAAO,MAAM,OAAS+tB,EAAY,OAClCvb,GAAc,CAAE,KAAM,gBAAiB,KAAMub,EAAa,EAC1D,MACF,CAEA,GAAI6L,EAAkB,QAAQ54B,EAAGC,CAAC,EAAG,CACnCjB,GAAO,MAAM,OAAS,OACtBwS,GAAc,CAAE,KAAM,UAAW,KAAMonB,EAAmB,EAC1D,MACF,CACF,KAAO,CACL,GAAI9nB,EAAiB,gBAAgB9Q,EAAGC,EAAG+Y,CAAI,EAAG,CAChDha,GAAO,MAAM,OAAS,OACtBwS,GAAc,CAAE,KAAM,kBAAmB,KAAM,KAAM,EACrD,MACF,CAEA,MAAMsnB,EAAehoB,EAAiB,cAAc9Q,EAAGC,EAAG+Y,CAAI,EAC9D,GAAI8f,EAAc,CAChB95B,GAAO,MAAM,OAAS85B,EAAa,OACnCtnB,GAAc,CAAE,KAAM,gBAAiB,KAAMsnB,EAAc,EAC3D,MACF,CAEA,GAAIF,EAAkB,QAAQ54B,EAAGC,CAAC,EAAG,CACnCjB,GAAO,MAAM,OAAS,OACtBwS,GAAc,CAAE,KAAM,UAAW,KAAMonB,EAAmB,EAC1D,MACF,CACF,CAIF,GAAI7xB,IAAsBD,aAA2B7H,eACnD,QAASsL,EAAIzD,EAAgB,SAAS,OAAS,EAAGyD,GAAK,EAAGA,IAAK,CAC7D,MAAMpf,EAAQ2b,EAAgB,SAASyD,CAAC,EACxC,GAAI,EAAApf,EAAM,UAAY,IAASA,EAAM,KAAO4b,GAAmB,KAC3D5b,EAAM,QAAQ6U,EAAGC,CAAC,EAAG,CACvBjB,GAAO,MAAM,OAAS,UACtBwS,GAAc,CAAE,KAAM,gBAAiB,KAAMrmB,EAAO,EACpD,MACF,CACF,CAcF,MAAMyiC,GAAyB+B,GAAA,EAC/B,QAASplB,EAAIqjB,GAAuB,OAAS,EAAGrjB,GAAK,EAAGA,IACtD,GAAIqjB,GAAuBrjB,CAAC,EAAE,UAAY,IACtC,CAAAqjB,GAAuBrjB,CAAC,EAAE,QAC1BqjB,GAAuBrjB,CAAC,EAAE,QAAQvK,EAAGC,CAAC,EAAG,CAC3C,GAAI,CAACguB,GAAoBL,GAAuBrjB,CAAC,EAAGvK,EAAGC,EAAGsZ,EAAgB,EACxE,SAEFva,GAAO,MAAM,OAAS,UACtBwS,GAAc,CAAE,KAAM,UAAW,KAAMoc,GAAuBrjB,CAAC,EAAG,EAClE,MACF,CAGFvL,GAAO,MAAM,OAAS,UACtBwS,GAAc,CAAE,KAAM,KAAM,KAAM,KAAM,CAC1C,CACF,EACA,CACEwR,EACAlc,EACA6oB,GACArC,GACAxc,EACApE,EACA4iB,EACA1uB,GACAiW,GACA9P,GACAsJ,EACAyL,GACA9C,EACA0B,GACAC,GACAnJ,GACA+d,GACAtU,GACAD,GACAtE,GAEAia,GACAE,EAAA,CACF,EAMIkI,GAAkB76B,cAAayJ,GAA6C,iBAWhF,GAVCA,EAAE,OAA6B,sBAAsBA,EAAE,SAAS,EAKjEuoB,GAAkB,QAAQ,OAAOvoB,EAAE,SAAS,EAM1C6oB,GAAmB,UAClB7oB,EAAE,YAAc6oB,GAAmB,QAAQ,UAC1C7oB,EAAE,YAAc6oB,GAAmB,QAAQ,UAC7C,CACAA,GAAmB,QAAU,KAC7BN,GAAkB,QAAQ,MAAA,EAC1B,MACF,CAQA,GACEO,GAAmB,SACnBA,GAAmB,QAAQ,YAAc9oB,EAAE,UAC3C,CACA,MAAMkG,EAAI4iB,GAAmB,QAC7BA,GAAmB,QAAU,KACxB5iB,EAAE,uBAQDtR,EAAAsR,EAAE,aAAF,YAAAtR,EAAc,QAAS,kBACzBsxB,GAAkBhgB,EAAE,WAAW,SAAS,EACxCJ,EAAuB,CAAA,CAAE,KAChBuB,EAAAnB,EAAE,aAAF,YAAAmB,EAAc,QAAS,aAChC6e,GAAkB,IAAI,EACtBpgB,EAAuB,CAAA,CAAE,IAG7B,MACF,CAEA,GACEuV,EAAa,QAAA,IAAc,SAC3BmN,GAAmB,QACnB,CACA,KAAM,CAAE,EAAAh8B,EAAG,EAAAC,CAAA,EAAM+7B,GAAmB,QAOpC,GAAIxoB,EAAE,YAAcxT,GAAKwT,EAAE,YAAcvT,EAAG,CAE1C,MAAM4kC,GADMhW,EAAa,WAAA,EACA,QACnBiW,GAAe7I,GAAwB,SAAW,OACpD4I,IACFxJ,GACEyJ,GACAD,EAAA,EAGJhW,EAAa,IAAA,EACbmN,GAAmB,QAAU,KAC7BC,GAAwB,QAAU,KAIlCF,GAAkB,QAAQ,MAAA,EAC1B,MACF,CACF,CAQA,GALInB,EAA2B,UAC7BA,EAA2B,QAAU,MAInC1e,EAAQ,SAAA,GAAcA,EAAQ,mBAAoB,CACpD,MAAM6oB,EAAa7oB,EAAQ,cAAA,EAG3B,GAFAA,EAAQ,qBAAA,EAEJ6oB,GAAcpyB,aAA2B8J,cAAa,CACxD,MAAM7I,EAAiBjB,EAAgB,MAAA,EACvC0oB,GAAqB1oB,EAAiBiB,CAAc,CACtD,CAEA+T,GAAmBjZ,GAAMA,EAAI,CAAC,EAC9B,MACF,CAGA,GAAI6J,EAAoB,CACtBojB,GAAuBriB,CAAsB,EAC7C,MACF,CAGI8e,EAAwB,SAAWvJ,EAAa,QAAA,IAAc,SACvCuJ,EAAwB,QAEhC,QAAQ,CAAC4M,EAAiBx2B,IAAc,CACvD,MAAMq2B,GAAet4B,GAAS,KAAMO,IAAOA,GAAG,KAAO0B,CAAS,EAC1Dq2B,KAEAG,EAAgB,IAAMH,GAAa,GACnCG,EAAgB,IAAMH,GAAa,GAClCG,aAA2Bl6B,EAAAA,cAC1B+5B,cAAwB/5B,gBACxB,KAAK,UAAUk6B,EAAgB,SAAS,IAAKnzB,IAAO,CAAE,EAAGA,EAAE,EAAG,EAAGA,EAAE,CAAA,EAAI,CAAC,IACtE,KAAK,UAAUgzB,GAAa,SAAS,IAAKhzB,IAAO,CAAE,EAAGA,EAAE,EAAG,EAAGA,EAAE,CAAA,EAAI,CAAC,IAGzEwpB,GAAqB2J,EAAiBH,EAAY,CAGxD,CAAC,EAEDzM,EAAwB,QAAU,MAGpC,MAAMvtB,GAASwoB,EAAU,QAgBzB,GAfIxoB,KACFA,GAAO,MAAM,OAAS,WAGxBswB,EAAW,YAAA,EACXA,EAAW,eAAA,EACXvU,EAAc,CAAA,CAAE,EAEhBwU,GAAc,gBAAA,EACdtU,GAAqB,CAAA,CAAE,EAEnB+H,EAAa,mBACf,OAAOA,EAAa,kBAGlBA,EAAa,oBAAqB,CACpC,GAAIjc,IAAsBD,aAA2B7H,eAAc,CACjE,MAAM8kB,EAAcf,EAAa,WAAA,EAI3B7R,IAHc4S,EAAY,gBAAkBA,EAAY,eAAejd,EAAgB,EAAE,GAC3CA,GAEpB,SAAS,KAAMd,IAAMA,GAAE,KAAOe,GAAmB,EAAE,EAC/EoK,KACFD,GAAkBC,EAA0C,EAC5DL,EAAiB,OAAOK,GAAc6H,CAAI,EAE9C,CACA,OAAOgK,EAAa,mBACtB,CAcA,GAZIA,EAAa,8BACf,OAAOA,EAAa,6BAGtBtiB,GAAS,QAAS7B,GAAY,CAC5B,GAAIA,aAAmBu6B,EAAAA,iBAAmBv6B,EAAQ,mBAAqB,OAAW,CAChF,MAAMkJ,EAAiBlJ,EAAQ,MAAA,EAC/BkJ,EAAe,iBAAmB,OAClCulB,GAAgBvlB,CAAc,CAChC,CACF,CAAC,EAEGib,EAAa,2BAA4B,CAC3C,MAAMG,EAAmBH,EAAa,2BAEtC,GAAIG,EAAiB,UAAYA,EAAiB,aAAc,CAC9D,KAAM,CAAE,GAAAtb,EAAI,GAAAC,CAAA,EAAOqb,EAAiB,aAC9BkW,GAAWlW,EAAiB,SAClCtM,GAAqB,QAAU,CAC7B,GAAGwiB,GACH,QAASA,GAAS,QAAUxxB,EAC5B,QAASwxB,GAAS,QAAUvxB,CAAA,CAEhC,CAEA,OAAOkb,EAAa,0BACtB,CAEA,GAAIA,EAAa,+BAAgC,CAC/C,GAAIA,EAAa,YAAc,UAAYpiB,GAAe,OAAS,GAAKiW,GAAqB,QAAS,CACpG,MAAMyiB,EAAUziB,GAAqB,QACrCA,GAAqB,QAAU,CAC7B,GAAGyiB,EACH,SAAUA,EAAQ,SAAWtW,EAAa,+BAA+B,iBAAmBsW,EAAQ,QAAA,CAExG,CACA,OAAOtW,EAAa,8BACtB,CAEA,MAAMe,GAAcf,EAAa,WAAA,EAMjC,GALIe,GAAY,gBACd,OAAOA,GAAY,eAIjBf,EAAa,uBAAwB,CACvC,MAAMuW,EAAevW,EAAa,uBAClC,IAAIwW,EAAoB,GAExB,GAAI1yB,aAA2B7H,EAAAA,aAAc,CAC3C,MAAMw6B,EAAgBzW,EAAa,0BAC/ByW,IAIFD,EAAoB,EAFlB,KAAK,IAAI1yB,EAAgB,EAAI2yB,EAAc,CAAC,EAAI,IAChD,KAAK,IAAI3yB,EAAgB,EAAI2yB,EAAc,CAAC,EAAI,IAGtD,CAEID,IACFtoB,GAAkBqoB,CAAY,EAC9BzoB,EAAiB,OAAOyoB,EAAcvgB,CAAI,EAC1C8C,GAAmBjZ,GAAMA,EAAI,CAAC,GAGhC,OAAOmgB,EAAa,sBACtB,CAGA,GAAIA,EAAa,qBAAsB,CACrC,MAAMnrB,EAAUmrB,EAAa,qBACvB0W,EAAiBh5B,GAAS,KAAMO,IAAOA,GAAG,KAAOpJ,EAAQ,SAAS,EAOpE,EAJF6hC,IACC,KAAK,IAAIA,EAAe,EAAI7hC,EAAQ,SAAS,EAAI,IAChD,KAAK,IAAI6hC,EAAe,EAAI7hC,EAAQ,SAAS,EAAI,MAEvC6hC,aAA0B3iB,eAAe,EAAE2iB,aAA0B1yB,EAAAA,eAAiB,EAAE0yB,aAA0BC,EAAAA,eAC9H5J,GAAkB2J,EAAgB7hC,EAAQ,OAAQA,EAAQ,MAAM,EAGlE,OAAOmrB,EAAa,oBACtB,CAQA,GAAIsN,GAAqB,QAAS,CAChC,MAAMr/B,EAAKq/B,GAAqB,QAC1BsJ,EAAWrJ,GAA2B,QACtCmJ,EAAiBh5B,GAAS,KAAMO,IAAOA,GAAG,KAAOhQ,CAAE,EACnDsgC,IAAWriB,GAAAqd,EAAwB,UAAxB,YAAArd,GAAiC,IAAIje,GAChD4oC,GAAQ,CAAC,EACbH,GACAnI,KACC,KAAK,IAAImI,EAAe,EAAInI,GAAS,CAAC,EAAI,IACzC,KAAK,IAAImI,EAAe,EAAInI,GAAS,CAAC,EAAI,KAKxCuI,EACJF,EACI,KAAK,OACFjyB,EAAE,WAAWyH,EAAA6gB,GAAgB,UAAhB,YAAA7gB,EAAyB,OAAQ,IAAM4J,EACnD0B,GACAkf,EAAS,GACVjyB,EAAE,WAAW4H,GAAA0gB,GAAgB,UAAhB,YAAA1gB,GAAyB,MAAO,IAAMyJ,EAClD2B,GACAif,EAAS,CAAA,EACT,EAAI,KAAK,IAAI5gB,EAAM,IAAM,EAC7B,GACN,GAAI,CAAC6gB,IAAS,CAACC,GAAe7oC,KAAO6V,GAAA,YAAAA,EAAiB,KAAM8yB,EAAU,CASpE,MAAMhM,GAAyB+B,GAAA,EACzBiD,GAAsC,CAAA,EAC5C,QAASroB,EAAIqjB,GAAuB,OAAS,EAAGrjB,GAAK,EAAGA,IAAK,CAC3D,MAAMtJ,EAAK2sB,GAAuBrjB,CAAC,EAC/BtJ,EAAG,UAAY,IACdA,EAAG,QAAQ24B,EAAS,EAAGA,EAAS,CAAC,GACjC3L,GAAoBhtB,EAAI24B,EAAS,EAAGA,EAAS,EAAGrgB,EAAgB,GAErEqZ,GAAK,KAAK3xB,CAAE,CACd,CACA,MAAM6xB,EAAaF,GAAK,UAAW3xB,GAAOA,EAAG,KAAOhQ,CAAE,EAClD6hC,IAAe,IAAMA,EAAa,EAAIF,GAAK,OAC7C/E,GAAkB+E,GAAKE,EAAa,CAAC,EAAE,EAAE,EAEzCjF,GAAkB,IAAI,CAE1B,CACAyC,GAAqB,QAAU,KAC/BC,GAA2B,QAAU,IACvC,CAEIvN,EAAa,2BACf,OAAOA,EAAa,0BAGtBA,EAAa,IAAA,EACb3H,GAAc,EAAK,EACnBoU,GAAA,MAAAA,EAAwB,IACxBR,EAAmB,QAAU,KAC7BvT,GAA0B,CAAC,EAC3BE,EAAY,CAAA,CAAE,CAChB,EAAG,CACDoH,EACAsM,EACA5iB,EACAe,EACA7M,GACAiW,GACA9P,GACAD,EACAoK,GACAJ,EACA0W,EACAnX,EACAyL,GACA9C,EACA0B,GACAC,GACA8U,EACAM,GACArvB,GACAmtB,GACA2B,GACAjW,EAAA,CACD,EAKKwgB,GAAoB77B,EAAAA,YACvByJ,GAA2C,CAC1C,GAAI,CAACb,EAAiB,OAEtB,MAAM9H,GAASwoB,EAAU,QACzB,GAAI,CAACxoB,GAAQ,OAEb,MAAMqJ,GAAO4nB,GAAgB,SAAWjxB,GAAO,sBAAA,EACzCgB,GAAK2H,EAAE,QAAUU,GAAK,MAAQ2Q,EAAO0B,GACrCza,GAAK0H,EAAE,QAAUU,GAAK,KAAO2Q,EAAO2B,GAE1C,GAAI7T,EAAgB,QAAQ9G,EAAGC,CAAC,EAAG,CAEjC,GAAI6G,aAA2B7H,EAAAA,aAAc,CAC3C,MAAM0zB,GAAe7rB,EAAgB,aAAa9G,EAAGC,CAAC,EACtD,GAAI0yB,GAAc,CAEhB,GAAI,EADyB5rB,IAAsBA,GAAmB,KAAO4rB,GAAa,IAC/D,OAO3B,GAAIA,cAAwB5b,EAAAA,aAAe,EAAE4b,cAAwB1zB,EAAAA,eAAiB,EAAE0zB,cAAwBgH,EAAAA,cAAe,CAC7H,MAAMxM,GAAawF,GAAa,qBAAA,EAE1BjP,GAAcvR,EAAAA,cAAc,iBAAiBwgB,GAAa,QAAQ,EAClEd,GAASc,GAAa,kBAAA,EACtB8F,GAAMz4B,EAAI6xB,GAAO,EACjB6G,GAAMz4B,EAAI4xB,GAAO,EACjBzf,GAAM,KAAK,IAAIsR,EAAW,EAC1BrR,GAAM,KAAK,IAAIqR,EAAW,EAC1BoO,GAAgB2G,GAAMrmB,GAAMsmB,GAAMrmB,GAClC0f,GAAgB0G,GAAMpmB,GAAMqmB,GAAMtmB,GAClC9F,GAAYwlB,GAAgB3E,GAAW,MAAQ,EAC/C6E,GAAYD,GAAgB5E,GAAW,OAAS,EAEhD8E,GAAa9lB,GAAwBwmB,GAAc,CAAE,EAAGrmB,GAAW,EAAG0lB,GAAW,EAEjFgI,GAAgB7M,GAAW,OAAS,GACpC8M,GAAS,EAEf3iB,GAAgB,CACd,EAAGjP,GAAK,MAAQ8kB,GAAW,EAAIzS,IAAkB1B,EAAOihB,GACxD,EAAG5xB,GAAK,KAAO8kB,GAAW,EAAIxS,IAAkB3B,EAAOihB,GACvD,MAAO,KAAK,IAAI,GAAI9M,GAAW,MAAQnU,CAAI,EAC3C,OAAQ,KAAK,IAAIghB,GAAgBhhB,EAAM,EAAE,CAAA,CAC1C,EACD5B,GAAY4Y,GAAyB2C,EAAY,CAAC,EAClD,MAAMuH,GAAkBvH,GAAa,YAAA,EAAc,MAAA,EACnDta,EAAgB,QAAU6hB,GAC1BhiB,GAAgBgiB,EAAe,EAC/B,MAAMC,GAAaxH,GAAa,YAAA,EAAc,WAAW,KAAK,IAAI,EAAGA,GAAa,KAAK,OAAS,CAAC,CAAC,EAClGva,GAA0B,CAAE,GAAGua,GAAa,kBAAmB,GAAGwH,GAAY,EAE9E3iB,GAAkBya,EAAU,EAC5Bva,GAAkBua,EAAU,EAC5Bra,GAAgBqa,EAAU,EAE1B/a,GAAa,EAAI,EACjBgY,EAAqB,QAAU,KAAK,IAAA,EACpCje,EAAkB,QAAU0hB,GAAa,GACzC,MACF,CACF,CACA,MACF,CAOA,GAAI7rB,aAA2B8J,EAAAA,YAAa,CAC1CP,EAAQ,SAASvJ,CAAe,EAChCgV,GAAmBjZ,IAAMA,GAAI,CAAC,EAC9B,MACF,CAWA,GARIiE,aAA2B6yB,EAAAA,cAQ3B,EAAE7yB,aAA2BiQ,EAAAA,aAC/B,OAIF,MAAMoW,GAAarmB,EAAgB,qBAAA,EAE7B4c,EAAcvR,EAAAA,cAAc,iBAAiBrL,EAAgB,QAAQ,EACrE+qB,GAAS/qB,EAAgB,kBAAA,EACzB2xB,EAAMz4B,EAAI6xB,GAAO,EACjB6G,EAAMz4B,EAAI4xB,GAAO,EACjBzf,EAAM,KAAK,IAAIsR,CAAW,EAC1BrR,GAAM,KAAK,IAAIqR,CAAW,EAC1BoO,GAAgB2G,EAAMrmB,EAAMsmB,EAAMrmB,GAClC0f,EAAgB0G,EAAMpmB,GAAMqmB,EAAMtmB,EAClC9F,GAAYwlB,GAAgB3E,GAAW,MAAQ,EAC/C6E,GAAYD,EAAgB5E,GAAW,OAAS,EAEhD8E,EAAa9lB,GAAwBrF,EAAgC,CAAE,EAAGwF,GAAW,EAAG0lB,GAAW,EAEnGgI,EAAgB7M,GAAW,OAAS,GACpC8M,EAAS,EAEf3iB,GAAgB,CACd,EAAGjP,GAAK,MAAQ8kB,GAAW,EAAIzS,IAAkB1B,EAAOihB,EACxD,EAAG5xB,GAAK,KAAO8kB,GAAW,EAAIxS,IAAkB3B,EAAOihB,EACvD,MAAO,KAAK,IAAI,GAAI9M,GAAW,MAAQnU,CAAI,EAC3C,OAAQ,KAAK,IAAIghB,EAAgBhhB,EAAM,EAAE,CAAA,CAC1C,EACD5B,GAAY4Y,GAAyBlpB,CAAe,CAAC,EACrD,MAAMozB,EAAkBpzB,EAAgB,YAAA,EAAc,MAAA,EACtDuR,EAAgB,QAAU6hB,EAC1BhiB,GAAgBgiB,CAAe,EAC/B,MAAMC,GAAarzB,EAAgB,YAAA,EAAc,WAAW,KAAK,IAAI,EAAGA,EAAgB,KAAK,OAAS,CAAC,CAAC,EACxGsR,GAA0B,CAAE,GAAGtR,EAAgB,kBAAmB,GAAGqzB,GAAY,EACjF3iB,GAAkBya,CAAU,EAC5Bva,GAAkBua,CAAU,EAC5Bra,GAAgBqa,CAAU,EAC1B/a,GAAa,EAAI,EACjBgY,EAAqB,QAAU,KAAK,IAAA,CACtC,CACF,EACA,CACEpoB,EACAwmB,GACAvmB,GACA+J,EACAT,EACAyL,GACA9C,EACA0B,GACAC,GACArD,GACAF,GACAc,GACAE,GACAZ,GACAE,GACAE,GACAV,GACAhG,GACAwe,GACAM,EAAA,CACF,EAQIoK,GAA0Bl8B,EAAAA,YAC7ByJ,GAAkB,CACjB,MAAM3I,GAASwoB,EAAU,QAIzB,GAHI,CAACxoB,IAEQgkB,EAAa,QAAA,IACb,OAAQ,OAErB,MAAM3a,EAAO4nB,GAAgB,SAAWjxB,GAAO,sBAAA,EAO/C,GALE2I,EAAE,SAAWU,EAAK,MAClBV,EAAE,SAAWU,EAAK,OAClBV,EAAE,SAAWU,EAAK,KAClBV,EAAE,SAAWU,EAAK,OAEA,OAEpB,MAAMgyB,GAAiB,CACrB,QAAS1yB,EAAE,QACX,QAASA,EAAE,QACX,OAAQA,EAAE,OACV,SAAUA,EAAE,SACZ,QAASA,EAAE,QACX,QAASA,EAAE,QACX,QAAS,GACT,WAAY,GACZ,cAAe3I,GACf,OAAQA,GACR,YAAa2I,EACb,KAAM,cACN,UAAYA,EAAmB,WAAa,EAC5C,YAAcA,EAAmB,aAAe,QAChD,eAAgB,IAAMA,EAAE,eAAA,EACxB,gBAAiB,IAAMA,EAAE,gBAAA,EACzB,qBAAsB,IAAM,GAC5B,mBAAoB,IAAMA,EAAE,gBAAA,EAG9BurB,GAAkBmH,EAAc,CAClC,EACA,CAACnH,GAAmB1L,EAAWxE,CAAY,CAAA,EAcvCsX,GAA0Bp8B,EAAAA,YAC7ByJ,GAAoB,CACnB,MAAM3I,GAASwoB,EAAU,QACzB,GAAI,CAACxoB,GAAQ,OAMb,MAAMu7B,GAAa5yB,EAAE,OACf6yB,EAAeD,GAAav7B,GAAO,SAASu7B,EAAU,EAAI,GAC1DE,EACJvK,GAAkB,QAAQ,IAAIvoB,EAAE,SAAS,GACzC6oB,GAAmB,UAAY,MAC/BC,GAAmB,UAAY,MAC/BN,GAAmB,UAAY,MAC/BnN,EAAa,cAAA,EACf,GAAI,CAACwX,GAAgB,CAACC,EAAkB,OAExC,MAAMJ,GAAiB,CACrB,QAAS1yB,EAAE,QACX,QAASA,EAAE,QACX,cAAe3I,GACf,OAAQA,GACR,UAAW2I,EAAE,WAAa,EAC1B,YAAaA,EAAE,aAAe,OAAA,EAGhCoxB,GAAgBsB,EAAc,CAChC,EACA,CAACtB,GAAiBvR,EAAWxE,CAAY,CAAA,EAI3CxmB,OAAAA,EAAAA,UAAU,KACR,SAAS,iBAAiB,cAAe49B,EAAuB,EAChE,SAAS,iBAAiB,YAAaE,EAAuB,EAEvD,IAAM,CACX,SAAS,oBAAoB,cAAeF,EAAuB,EACnE,SAAS,oBAAoB,YAAaE,EAAuB,CACnE,GACC,CAACF,GAAyBE,EAAuB,CAAC,EAE9C,CACL,kBAAApJ,GACA,kBAAAgC,GACA,gBAAA6F,GACA,kBAAAgB,EAAA,CAEJ,CC14FA,SAAS/J,GAAyBnxB,EAA8B,CAG9D,GAAIA,EAAQ,gBAAkB,SAC5B,OAAOA,EAAQ,KAKjB,MAAMkL,EADalL,EAAQ,qBAAA,EACC,MAGtB67B,EAAc77B,EAAQ,QAAA,EAa5B,OAVcuL,EAAAA,SACZswB,EACA3wB,EACAlL,EAAQ,SACRA,EAAQ,WACRA,EAAQ,KACRA,EAAQ,MAAA,EAIG,KAAK;AAAA,CAAI,CACxB,CAmEO,SAAS87B,GAAuB5nC,EAAiE,CACtG,KAAM,CACJ,UAAAy0B,EACA,SAAAiH,EACA,kBAAAxd,EACA,gBAAAoH,EACA,mBAAAN,EACA,mBAAAD,EACA,sBAAAgQ,EACA,uBAAAD,EACA,2BAAA+S,EACA,2BAAA7L,EACA,uBAAAL,EACA,qBAAAQ,EACA,mBAAAvV,EACA,qBAAAoO,EACA,wBAAA8S,EACA,UAAA5jB,EACA,SAAAE,EACA,aAAAc,EACA,eAAAV,EACA,eAAAE,EACA,aAAAE,EACA,WAAAhX,EACA,gBAAAmG,EACA,mBAAAC,EACA,SAAArG,EACA,KAAAsY,EACA,eAAA0B,GACA,eAAAC,EACA,aAAAzD,EACA,YAAAE,EACA,gBAAAc,GACA,kBAAAV,GACA,kBAAAE,GACA,gBAAAE,GACA,gBAAAN,EACA,0BAAAc,GACA,wBAAA8D,GACA,kBAAAJ,GACA,YAAAuT,GACA,gBAAA/B,GACA,sBAAAwN,GACA,iBAAAhqB,GACA,kBAAAI,EAAA,EACEne,EAGEg9B,GAAoB7xB,EAAAA,YACxB,CAACW,EAAsBmT,EAAgBC,IAAmB,CACxD,MAAMjT,GAASwoB,EAAU,QACzB,GAAI,CAACxoB,GAAQ,OAEb,MAAMqJ,EAAOrJ,GAAO,sBAAA,EACdmuB,GAAatuB,EAAQ,qBAAA,EAGrB6kB,GAAcvR,EAAAA,cAAc,iBAAiBtT,EAAQ,QAAQ,EAC7DgzB,GAAShzB,EAAQ,kBAAA,EACjBgJ,GAAKmK,EAAS6f,GAAO,EACrB/pB,EAAKmK,EAAS4f,GAAO,EACrBzf,EAAM,KAAK,IAAIsR,EAAW,EAC1BrR,GAAM,KAAK,IAAIqR,EAAW,EAC1BoO,GAAgBjqB,GAAKuK,EAAMtK,EAAKuK,GAChC0f,GAAgBlqB,GAAKwK,GAAMvK,EAAKsK,EAChC9F,GAAYwlB,GAAgB3E,GAAW,MAAQ,EAC/C6E,GAAYD,GAAgB5E,GAAW,OAAS,EAEhD8E,GAAa9lB,GAAwBtN,EAAS,CAAE,EAAGyN,GAAW,EAAG0lB,GAAW,EAG5EgI,GAAgB7M,GAAW,OAAS,GAGpC8M,GAAS,EAEf3iB,EAAgB,CACd,EAAGjP,EAAK,MAAQ8kB,GAAW,EAAIzS,IAAkB1B,EAAOihB,GACxD,EAAG5xB,EAAK,KAAO8kB,GAAW,EAAIxS,GAAkB3B,EAAOihB,GACvD,MAAO,KAAK,IAAI,GAAI9M,GAAW,MAAQnU,CAAI,EAC3C,OAAQ,KAAK,IAAIghB,GAAgBhhB,EAAM,EAAE,CAAA,CAC1C,EACD5B,EAAY4Y,GAAyBnxB,CAAO,CAAC,EAC7C,MAAMq7B,GAAkBr7B,EAAQ,YAAA,EAAc,MAAA,EAC9CwZ,EAAgB,QAAU6hB,GAC1BhiB,GAAgBgiB,EAAe,EAE/B,MAAMC,GAAat7B,EAAQ,YAAA,EAAc,WAAW,KAAK,IAAI,EAAGA,EAAQ,KAAK,OAAS,CAAC,CAAC,EACxFuZ,GAA0B,CAAE,GAAGvZ,EAAQ,kBAAmB,GAAGs7B,GAAY,EACzE3iB,GAAkBya,EAAU,EAC5Bva,GAAkBua,EAAU,EAC5Bra,GAAgBqa,EAAU,EAC1B/a,EAAa,EAAI,EAEjBgY,EAAqB,QAAU,KAAK,IAAA,CACtC,EACA,CAAClW,EAAM1B,EAAiBF,EAAac,GAAiBE,GAA2BZ,GAAmBE,GAAmBE,GAAiBV,CAAY,CAAA,EAIhJoB,GAA6Bpa,EAAAA,YAChCjD,GAA0B,CACzB,GAAI,CAACgc,GAAa,CAACgB,EACjB,OAGF,MAAMnpB,EAAQ,KAAK,IAAI2oB,EAAgBE,CAAY,EAC7C5oB,EAAM,KAAK,IAAI0oB,EAAgBE,CAAY,EAEjD,GAAI7oB,IAAUC,EAAK,CAEjB,MAAM0U,GAAUwU,EAAa,MAAA,EACvBM,EAAa9U,GAAQ,QAAA,EAAU,OACrCA,GAAQ,WAAW,EAAG8U,EAAYtd,CAAK,EAEvCod,EAAgB,QAAU5U,GAC1ByU,GAAgBzU,EAAO,EACvB2T,EAAY3T,GAAQ,SAAS,EAE7B2U,GAA2BI,KACR,CAAE,GAAGA,GAAW,GAAGvd,CAAA,EAErC,CACH,KAAO,CAEL,MAAMwI,GAAUwU,EAAa,MAAA,EAC7BxU,GAAQ,WAAW3U,EAAOC,EAAKkM,CAAK,EAEpCod,EAAgB,QAAU5U,GAC1ByU,GAAgBzU,EAAO,EACvB2T,EAAY3T,GAAQ,SAAS,EAE7B2U,GAA2BI,IACR,CAAE,GAAGA,EAAW,GAAGvd,CAAA,EAErC,CACH,CACF,EACA,CAACgc,EAAWgB,EAAcR,EAAgBE,CAAY,CAAA,EAIlDc,GAAoBva,EAAAA,YAAY,IAA6B,CACjE,GAAI,CAAC+Y,GAAa,CAACgB,GAAgB,CAACnR,EAAiB,OAAO,KAE5D,MAAMhY,EAAQ,KAAK,IAAI2oB,EAAgBE,CAAY,EAC7C5oB,EAAM,KAAK,IAAI0oB,EAAgBE,CAAY,EAEjD,OAAI7oB,IAAUC,EACRD,IAAU,EACLgY,aAA2BiQ,EAAAA,YAAcjQ,EAAgB,gBAAA,EAAoB,CAAA,EAE/EmR,EAAa,WAAW,KAAK,IAAI,EAAGnpB,EAAQ,CAAC,CAAC,EAGhDmpB,EAAa,WAAWnpB,CAAK,CACtC,EAAG,CAACmoB,EAAWgB,EAAcR,EAAgBE,EAAc7Q,CAAe,CAAC,EAGrE4R,EAA2Bxa,EAAAA,YAC9Bya,GAAgE,CAC/D,GAAI,CAAC1B,GAAa,CAACgB,GAAgB,CAACnR,EAAiB,OAErD,MAAM8R,EAAeH,GAAA,EAGfI,EAAW,CADM,CAAE,GADJ/R,aAA2BiQ,EAAAA,YAAcjQ,EAAgB,gBAAA,EAAoB,CAAA,EACxD,GAAG8R,CAAA,EACZD,CAAQ,EAEzCL,GAA2B,CAAE,CAACK,CAAQ,EAAGE,EAAU,CACrD,EACA,CAAC5B,EAAWgB,EAAcnR,EAAiB2R,GAAmBH,EAA0B,CAAA,EAIpFyiB,EAAyB78B,EAAAA,YAAY,IAAM,CAC/C,GAAI,CAAC+Y,EAAW,OAEhB,MAAM+jB,EAAiB3iB,EAAgB,SAAWJ,EAElD,GAAInR,GAAmBk0B,EAAgB,CACrC,MAAMC,EAAiBhqB,EAAkB,QAEzC,GAAIgqB,GAAkBn0B,aAA2B7H,eAAc,CAC7D,MAAMsuB,EAAezmB,EAAgB,MAAA,EAC/BwuB,GAAa/H,EAAa,SAAS,UAAWvnB,IAAMA,GAAE,KAAOi1B,CAAc,EACjF,GAAI3F,IAAc,EAAG,CACnB,MAAMnkB,GAAeoc,EAAa,SAAS+H,EAAU,EACjDnkB,cAAwB4F,EAAAA,aAC1B5F,GAAa,YAAY6pB,CAAc,EAEzCzN,EAAa,yBAAyB,EAAI,CAC5C,CACAD,GAAgBC,CAAY,EAE5B,MAAMpc,EAAeoc,EAAa,SAAS+H,EAAU,EACjDnkB,IACFD,GAAkBC,CAAY,EAC9BL,GAAiB,OAAOK,EAAc6H,CAAI,EAC1C8C,GAAmBjZ,IAAMA,GAAI,CAAC,EAElC,SAAWiE,aAA2BiQ,cAAa,CACjD,MAAMhP,EAAiBjB,EAAgB,MAAA,EACvCiB,EAAe,YAAYizB,CAAc,EACzC1N,GAAgBvlB,CAAc,EAE9B+I,GAAiB,OAAO/I,EAAgBiR,CAAI,EAC5C8C,GAAmBjZ,IAAMA,GAAI,CAAC,CAChC,CACF,CAEAglB,EAAuB,QAAU,KACjC3Q,EAAa,EAAK,EAClBE,EAAY,EAAE,EACdc,GAAgB,IAAI,EACpBG,EAAgB,QAAU,KAC1BD,GAA0B,IAAI,EAC9BnH,EAAkB,QAAU,IAC9B,EAAG,CAACgG,EAAWE,EAAUc,EAAcI,EAAiBvR,EAAiBwmB,GAAiBxc,GAAkBI,GAAmB8H,CAAI,CAAC,EAG9HkiB,GAAqBh9B,EAAAA,YAAY,IAAM,CAG3C,GAFY,KAAK,IAAA,EACiB4pB,EAAsB,QAC9BC,EAAsB,CAC9C,GAAI0G,EAAS,QAAS,CACpBA,EAAS,QAAQ,MAAA,EACjB,MAAM1E,EAAiBlC,EAAuB,QAC1CkC,GACF0E,EAAS,QAAQ,kBAAkB1E,EAAe,MAAOA,EAAe,GAAG,CAE/E,CACA,MACF,CAEA,WAAW,IAAM,cAEf,GAD2B,KAAK,IAAA,EAAQmF,EAAqB,QACpC2L,EAAyB,EAChDt+B,GAAAkyB,EAAS,UAAT,MAAAlyB,GAAkB,QAClB,MACF,CAEA,GAAImyB,EAAuB,QAAS,CAClCA,EAAuB,QAAU,IACjC1f,GAAAyf,EAAS,UAAT,MAAAzf,GAAkB,QACdyf,EAAS,SACXA,EAAS,QAAQ,kBAAkBhX,EAAgBE,CAAY,EAEjE,MACF,CAGA,IAAI9Y,EADkB,SAAS,cAE3Bs8B,GAAiB,GAErB,KAAOt8B,GAAS,CACd,MAAMu8B,EAAYv8B,EAAQ,UACpBw8B,EAAWx8B,EAAQ,aAAa,WAAW,EACjD,GAAIw8B,IAAaA,EAAS,SAAS,SAAS,GAAKA,EAAS,SAAS,QAAQ,GAAI,CAC7EF,GAAiB,GACjB,KACF,CACA,GAAIt8B,EAAQ,aAAa,yBAAyB,EAAG,CACnDs8B,GAAiB,GACjB,KACF,CACA,GAAIC,IAEAA,EAAU,SAAS,SAAS,GAC5BA,EAAU,SAAS,eAAe,GAClCA,EAAU,SAAS,aAAa,GAChCA,EAAU,SAAS,gBAAgB,GACnCA,EAAU,SAAS,kBAAkB,GACrCA,EAAU,SAAS,eAAe,GAClCA,EAAU,SAAS,eAAe,GAClCA,EAAU,SAAS,gBAAgB,GACnCA,EAAU,SAAS,mBAAmB,GACtCA,EAAU,SAAS,eAAe,GAClCA,EAAU,SAAS,gBAAgB,GACnCA,EAAU,SAAS,qBAAqB,GACxCA,EAAU,SAAS,sBAAsB,GACzCA,EAAU,SAAS,uBAAuB,GAC1CA,EAAU,SAAS,kBAAkB,GACrCA,EAAU,SAAS,iBAAiB,GACpCA,EAAU,SAAS,0BAA0B,GAC7CA,EAAU,SAAS,2BAA2B,GAC9CA,EAAU,SAAS,eAAe,GAClCA,EAAU,SAAS,mBAAmB,GACtCA,EAAU,SAAS,qBAAqB,GACxCA,EAAU,SAAS,qBAAqB,GACxCA,EAAU,SAAS,oBAAoB,GACvCA,EAAU,SAAS,wBAAwB,GAC3CA,EAAU,SAAS,8BAA8B,GACjDA,EAAU,SAAS,sBAAsB,GACzCA,EAAU,SAAS,mBAAmB,GACtCA,EAAU,SAAS,uBAAuB,GAC1CA,EAAU,SAAS,mBAAmB,GACtCA,EAAU,SAAS,kBAAkB,GACrCA,EAAU,SAAS,cAAc,GACjCA,EAAU,SAAS,qBAAqB,GACxCA,EAAU,SAAS,mBAAmB,GACtCA,EAAU,SAAS,0BAA0B,GAC7CA,EAAU,SAAS,yBAAyB,GAC5CA,EAAU,SAAS,8BAA8B,GACjDA,EAAU,SAAS,cAAc,GACjCA,EAAU,SAAS,iBAAiB,GACpCA,EAAU,SAAS,4BAA4B,GAC/CA,EAAU,SAAS,kBAAkB,GACrCA,EAAU,SAAS,sBAAsB,GACzCA,EAAU,SAAS,sBAAsB,GACzCA,EAAU,SAAS,kBAAkB,GACrCA,EAAU,SAAS,qBAAqB,GACxCA,EAAU,SAAS,qBAAqB,GACxCA,EAAU,SAAS,cAAc,GACjC,CACAD,GAAiB,GACjB,KACF,CAEFt8B,EAAUA,EAAQ,aACpB,CAGA,GAAI,CAACs8B,IAAkBxhB,EAAmB,QAExC,IADA9a,EAAU8a,EAAmB,QACtB9a,GAAS,CACd,MAAMu8B,EAAYv8B,EAAQ,UACpBw8B,EAAWx8B,EAAQ,aAAa,WAAW,EACjD,GAAIw8B,IAAaA,EAAS,SAAS,SAAS,GAAKA,EAAS,SAAS,QAAQ,GAAI,CAC7EF,GAAiB,GACjB,KACF,CACA,GAAIt8B,EAAQ,aAAa,yBAAyB,EAAG,CACnDs8B,GAAiB,GACjB,KACF,CACA,GAAIC,IAEAA,EAAU,SAAS,SAAS,GAC5BA,EAAU,SAAS,eAAe,GAClCA,EAAU,SAAS,aAAa,GAChCA,EAAU,SAAS,gBAAgB,GACnCA,EAAU,SAAS,kBAAkB,GACrCA,EAAU,SAAS,eAAe,GAClCA,EAAU,SAAS,eAAe,GAClCA,EAAU,SAAS,gBAAgB,GACnCA,EAAU,SAAS,mBAAmB,GACtCA,EAAU,SAAS,eAAe,GAClCA,EAAU,SAAS,gBAAgB,GACnCA,EAAU,SAAS,qBAAqB,GACxCA,EAAU,SAAS,sBAAsB,GACzCA,EAAU,SAAS,uBAAuB,GAC1CA,EAAU,SAAS,kBAAkB,GACrCA,EAAU,SAAS,iBAAiB,GACpCA,EAAU,SAAS,0BAA0B,GAC7CA,EAAU,SAAS,2BAA2B,GAC9CA,EAAU,SAAS,eAAe,GAClCA,EAAU,SAAS,mBAAmB,GACtCA,EAAU,SAAS,qBAAqB,GACxCA,EAAU,SAAS,cAAc,GACjC,CACAD,GAAiB,GACjB,KACF,CAEFt8B,EAAUA,EAAQ,aACpB,CAGF,GAAI,CAACs8B,GACHJ,EAAA,MACK,CACL,MAAMO,EAAa7jB,EACb8jB,EAAW5jB,GACjBzI,GAAAuf,EAAS,UAAT,MAAAvf,GAAkB,QACduf,EAAS,SACXA,EAAS,QAAQ,kBAAkB6M,EAAYC,CAAQ,CAE3D,CACF,EAAG,CAAC,CACN,EAAG,CAACR,EAAwBtjB,EAAgBE,CAAY,CAAC,EAGnD6jB,GAAoBt9B,EAAAA,YACvByJ,GAAgD,CAC/C,MAAM8zB,EAAW9zB,EAAE,cAEnB,GAAIA,EAAE,MAAQ,UAAYA,EAAE,SAAWA,EAAE,SACvCA,EAAE,eAAA,EACFozB,EAAA,UACSpzB,EAAE,MAAQ,SACnBA,EAAE,eAAA,EACFA,EAAE,gBAAA,EACFuP,EAAa,EAAK,EAClBE,EAAY,EAAE,EACdc,GAAgB,IAAI,EACpBG,EAAgB,QAAU,KAC1BD,GAA0B,IAAI,EAC9BnH,EAAkB,QAAU,cAEpBtJ,EAAE,SAAWA,EAAE,UAAYA,EAAE,IAAI,YAAA,IAAkB,IAC3DA,EAAE,eAAA,EACF+Q,EAAyB,MAAM,WACrB/Q,EAAE,SAAWA,EAAE,UAAYA,EAAE,IAAI,YAAA,IAAkB,IAC7DA,EAAE,eAAA,EACF+Q,EAAyB,QAAQ,WACvB/Q,EAAE,SAAWA,EAAE,UAAYA,EAAE,IAAI,YAAA,IAAkB,IAC7DA,EAAE,eAAA,EACF+Q,EAAyB,WAAW,UAE7B/Q,EAAE,MAAQ,aAAeA,EAAE,MAAQ,aAC1C,WAAW,IAAM,CACf,MAAM6tB,EAASiG,EAAS,eACxBjkB,GAAkBge,CAAM,EACxB9d,GAAkB8d,CAAM,EACxB5d,GAAgB6jB,EAAS,YAAY,EACrC1jB,EAAmB,QAAUyd,CAC/B,EAAG,CAAC,UAEG7tB,EAAE,MAAQ,WAAaA,EAAE,MAAQ,aACxC,GAAIb,GAAmBA,aAA2BiQ,cAAa,CAC7DpP,EAAE,eAAA,EAEF,MAAMmE,EAAYnE,EAAE,MAAQ,UAAY,KAAO,OACzC6tB,GAAS3pB,GAAqB/E,EAAiByQ,EAAgBzL,CAAS,EAE9E0L,GAAkBge,EAAM,EACxB9d,GAAkB8d,EAAM,EACxB5d,GAAgB4d,EAAM,EACtBzd,EAAmB,QAAUyd,GAE7BiG,EAAS,kBAAkBjG,GAAQA,EAAM,CAC3C,OAEO7tB,EAAE,MAAQ,QAAUA,EAAE,MAAQ,QACrC,WAAW,IAAM,CACf,MAAM6tB,EAASiG,EAAS,eACxBjkB,GAAkBge,CAAM,EACxB9d,GAAkB8d,CAAM,EACxB5d,GAAgB6jB,EAAS,YAAY,EACrC1jB,EAAmB,QAAUyd,CAC/B,EAAG,CAAC,CAER,EACA,CAACuF,EAAwBriB,EAA0BlB,GAAmBE,GAAmBE,GAAiBG,EAAoBR,EAAgBzQ,CAAe,CAAA,EAQ/JtK,EAAAA,UAAU,IAAM,CACVya,IACFa,EAAmB,QAAU,YAAY,IAAA,EAE7C,EAAG,CAACb,EAAWM,EAAgBO,CAAkB,CAAC,EAGlDtb,EAAAA,UAAU,IAAM,CACd,GAAI,CAACya,EAAW,OAEhB,IAAIykB,EACAC,EAAa,EACjB,MAAMC,EAAiB,GAEjBC,GAAWC,GAAsB,CACjCA,EAAYH,GAAcC,IAC5B1f,GAAyBoV,KAAUA,GAAO,GAAK,GAAI,EACnDqK,EAAaG,GAEfJ,EAAQ,sBAAsBG,EAAO,CACvC,EAEA,OAAAH,EAAQ,sBAAsBG,EAAO,EAC9B,IAAM,qBAAqBH,CAAK,CACzC,EAAG,CAACzkB,CAAS,CAAC,EAGd,MAAM8kB,GAAmBz5B,EAAAA,OAAO,EAAK,EACrC9F,EAAAA,UAAU,IAAM,CACd,GAAIya,GAAa,CAAC8kB,GAAiB,SAAWtN,EAAS,QAAS,CAC9DA,EAAS,QAAQ,MAAA,EACjB,MAAMuN,EAAS7kB,EAAS,OACxBK,GAAkBwkB,CAAM,EACxBtkB,GAAkBskB,CAAM,EACxBpkB,GAAgBokB,CAAM,EACtBvN,EAAS,QAAQ,kBAAkBuN,EAAQA,CAAM,CACnD,CACAD,GAAiB,QAAU9kB,CAC7B,EAAG,CAACA,EAAWE,CAAQ,CAAC,EAGxB,MAAM8kB,GAAyB35B,EAAAA,OAA8B,IAAI,EAE3D45B,EAAiB,CAAC/nC,EAA0BC,IAC5CD,IAAMC,EAAU,GAChB,CAACD,GAAK,CAACC,EAAU,GAEnBD,EAAE,QAAUC,EAAE,OACdD,EAAE,aAAeC,EAAE,YACnBD,EAAE,WAAaC,EAAE,UACjBD,EAAE,OAASC,EAAE,MACbD,EAAE,SAAWC,EAAE,QACfD,EAAE,YAAcC,EAAE,WAClBD,EAAE,gBAAkBC,EAAE,cAK1BoI,OAAAA,EAAAA,UAAU,IAAM,CACd,GAAI,CAACya,GAAa,CAACgB,EAAc,OAEjC,MAAMnpB,EAAQ,KAAK,IAAI2oB,EAAgBE,CAAY,EAC7C5oB,EAAM,KAAK,IAAI0oB,EAAgBE,CAAY,EAEjD,GAAI7oB,IAAUC,EAAK,CACjB,IAAIotC,EAAW,KAEf,GAAIrtC,EAAQ,EACVqtC,EAAWlkB,EAAa,WAAWnpB,EAAQ,CAAC,MACvC,CACL,MAAM+P,GAAU6B,EAAS,KAAMiH,GAAMA,EAAE,KAAOhH,CAAU,EACpD9B,cAAmBkY,EAAAA,cACrBolB,EAAWt9B,GAAQ,gBAAA,EAEvB,CAEIs9B,GAAY,CAACD,EAAeC,EAAUF,GAAuB,OAAO,IACtEA,GAAuB,QAAUE,EACjC/jB,GAA0B+jB,CAAQ,EAEtC,CACF,EAAG,CAACllB,EAAWgB,EAAcR,EAAgBE,EAAchX,CAAU,CAAC,EAGtEnE,EAAAA,UAAU,IAAM,CACd,GAAI,CAACya,GAAa,CAACwX,EAAS,QAAS,OAErC,MAAMgN,EAAWhN,EAAS,SAEtBgN,EAAS,iBAAmBhkB,GAAkBgkB,EAAS,eAAiB9jB,KACtEF,IAAmBE,EACrB8jB,EAAS,kBAAkBhkB,EAAgBE,CAAY,EAEvD8jB,EAAS,kBAAkBlkB,EAAgBA,CAAc,EAG/D,EAAG,CAACJ,EAAUF,EAAWM,EAAgBE,EAAgBE,CAAY,CAAC,EAGtEnb,EAAAA,UAAU,IAAM,CACd,MAAM4/B,EAAa17B,EAAS,KAAMiH,GAAMA,EAAE,KAAOhH,CAAU,EAC3D,GAAI,GAACsW,GAAa,CAACmlB,GAAc,CAAC5U,EAAU,SAAW,CAACiH,EAAS,SAEjE,OAAImM,EAA2B,SAC7B,aAAaA,EAA2B,OAAO,EAGjDA,EAA2B,QAAU,WAAW,IAAM,CACpD,IAAIyB,EAAyFD,EAC7F,GAAInrB,EAAkB,SAAWmrB,aAAsBn9B,eAAc,CACnE,MAAM9T,GAAQixC,EAAW,SAAS,KAAMp2B,IAAMA,GAAE,KAAOiL,EAAkB,OAAO,EAC5E9lB,KACFkxC,EAAiBlxC,GAErB,CAEA,GAAI,EAAEkxC,aAA0BtlB,EAAAA,aAAc,OAE9C,MAAMulB,EAAcD,EAAe,MAAA,EAC/BpkB,GACFqkB,EAAY,YAAYrkB,CAAY,EAGtC,MAAMkV,GAAamP,EAAY,qBAAA,EACzBj0B,EAAOmf,EAAU,QAAS,sBAAA,EAC1BwS,GAAgB7M,GAAW,OAAS,GACpC8M,GAAS,EASf,GAPA3iB,EAAgB,CACd,EAAGjP,EAAK,MAAQ8kB,GAAW,EAAIzS,IAAkB1B,EAAOihB,GACxD,EAAG5xB,EAAK,KAAO8kB,GAAW,EAAIxS,GAAkB3B,EAAOihB,GACvD,MAAO,KAAK,IAAI,GAAI9M,GAAW,MAAQnU,CAAI,EAC3C,OAAQ,KAAK,IAAIghB,GAAgBhhB,EAAM,EAAE,CAAA,CAC1C,EAEG/H,EAAkB,SAAWmrB,aAAsBn9B,eAAc,CACnE,MAAMsuB,GAAe6O,EAAW,MAAA,EAC1B9G,GAAa/H,GAAa,SAAS,UAAWvnB,GAAMA,EAAE,KAAOiL,EAAkB,OAAO,EACxFqkB,IAAc,IAChB/H,GAAa,SAAS+H,EAAU,EAAIgH,EACpC/O,GAAa,yBAAyB,EAAI,GAE5C8B,GAAa2F,GAAiBA,EAAa,IAAKrtB,GAAOA,EAAE,KAAO4lB,GAAa,GAAKA,GAAe5lB,CAAE,CAAC,EACpGmJ,GAAiB,OAAOwrB,EAAatjB,CAAI,CAC3C,MACEqW,GAAa2F,IAAiBA,GAAa,IAAKrtB,IAAOA,GAAE,KAAO20B,EAAY,GAAKA,EAAc30B,EAAE,CAAC,EAClGmJ,GAAiB,OAAOwrB,EAAatjB,CAAI,EAG3C8C,GAAmBjZ,IAAMA,GAAI,CAAC,CAChC,EAAG,EAAE,EAEE,IAAM,CACP+3B,EAA2B,SAC7B,aAAaA,EAA2B,OAAO,CAEnD,CAEF,EAAG,CAACzjB,EAAUc,EAAchB,EAAWtW,EAAYqY,CAAI,CAAC,EAGxDxc,EAAAA,UAAU,IAAM,CACd,GAAI,CAACya,GAAa,CAACwX,EAAS,QAAS,OAErC,MAAMgN,EAAWhN,EAAS,QACpB8N,EAAe,IAAM,CAMzB,GALIxN,EAA2B,SAIH,KAAK,IAAA,EAAQjH,EAAsB,QACrCC,EACxB,OAGF,MAAMj5B,EAAQ2sC,EAAS,gBAAkB,EACnC1sC,GAAM0sC,EAAS,cAAgB,EACrCjkB,GAAmB8Z,IAASA,KAASviC,GAAMuiC,GAAOviC,EAAG,EACrD2oB,GAAmB4Z,IAASA,KAASxiC,EAAQwiC,GAAOxiC,CAAK,EACzD8oB,GAAiB0Z,IAASA,KAASviC,GAAMuiC,GAAOviC,EAAG,EAEnD+rC,IAAA,MAAAA,IACF,EAEAW,EAAS,iBAAiB,QAASc,CAAY,EAC/Cd,EAAS,iBAAiB,QAASc,CAAY,EAC/Cd,EAAS,iBAAiB,SAAUc,CAAY,EAChDd,EAAS,iBAAiB,UAAWc,CAAY,EAEjD,MAAMx6B,EAAwB,IAAM,CAC9B,SAAS,gBAAkB05B,GAC7Bc,EAAA,CAEJ,EACA,gBAAS,iBAAiB,kBAAmBx6B,CAAqB,EAElEw6B,EAAA,EAEO,IAAM,CACXd,EAAS,oBAAoB,QAASc,CAAY,EAClDd,EAAS,oBAAoB,QAASc,CAAY,EAClDd,EAAS,oBAAoB,SAAUc,CAAY,EACnDd,EAAS,oBAAoB,UAAWc,CAAY,EACpD,SAAS,oBAAoB,kBAAmBx6B,CAAqB,CACvE,CACF,EAAG,CAACkV,CAAS,CAAC,EAGdza,EAAAA,UAAU,IAAM,CACd,MAAMkL,EAAiBC,GAAqB,CAC1C,MAAMzQ,EAASyQ,EAAE,OAIjB,GAHIzQ,EAAO,UAAY,SAAWA,EAAO,UAAY,YACjDyQ,EAAE,MAAQ,SACVsP,GACA,CAACuQ,EAAU,QAAS,OAExB,MAAMnf,GAAOmf,EAAU,QAAQ,sBAAA,EAG/B,GACEzgB,GACAA,aAA8BgQ,eAC9B,EAAEhQ,aAA8B9H,EAAAA,eAChC,EAAE8H,aAA8BC,EAAAA,eAChC,EAAED,aAA8B4yB,EAAAA,cAChC,CACAhyB,EAAE,eAAA,EACF,MAAMwlB,EAAapmB,EAAmB,qBAAA,EAChCizB,GAAgB7M,EAAW,OAAS,GACpC8M,GAAS,EAEf3iB,EAAgB,CACd,EAAGjP,GAAK,MAAQ8kB,EAAW,EAAIzS,IAAkB1B,EAAOihB,GACxD,EAAG5xB,GAAK,KAAO8kB,EAAW,EAAIxS,GAAkB3B,EAAOihB,GACvD,MAAO,KAAK,IAAI,IAAK9M,EAAW,MAAQnU,CAAI,EAC5C,OAAQ,KAAK,IAAIghB,GAAgBhhB,EAAM,EAAE,CAAA,CAC1C,EACD5B,EAAY4Y,GAAyBjpB,CAAkB,CAAC,EACxDmQ,EAAa,EAAI,EACjBgY,EAAqB,QAAU,KAAK,IAAA,EACpCje,EAAkB,QAAUlK,EAAmB,GAC/C,MACF,CAGA,GACED,GACAA,aAA2BiQ,eAC3B,EAAEjQ,aAA2B7H,EAAAA,eAC7B,EAAE6H,aAA2BE,EAAAA,eAC7B,EAAEF,aAA2B6yB,EAAAA,cAC7B,CACAhyB,EAAE,eAAA,EACF,MAAMwlB,EAAarmB,EAAgB,qBAAA,EAC7BkzB,GAAgB7M,EAAW,OAAS,GACpC8M,GAAS,EAEf3iB,EAAgB,CACd,EAAGjP,GAAK,MAAQ8kB,EAAW,EAAIzS,IAAkB1B,EAAOihB,GACxD,EAAG5xB,GAAK,KAAO8kB,EAAW,EAAIxS,GAAkB3B,EAAOihB,GACvD,MAAO,KAAK,IAAI,IAAK9M,EAAW,MAAQnU,CAAI,EAC5C,OAAQ,KAAK,IAAIghB,GAAgBhhB,EAAM,EAAE,CAAA,CAC1C,EACD5B,EAAY4Y,GAAyBlpB,CAAe,CAAC,EACrDoQ,EAAa,EAAI,EACjBgY,EAAqB,QAAU,KAAK,IAAA,CACtC,CACF,EAEA,cAAO,iBAAiB,UAAWxnB,CAAa,EACzC,IAAM,OAAO,oBAAoB,UAAWA,CAAa,CAClE,EAAG,CAACZ,EAAiBC,EAAoBkQ,CAAS,CAAC,EAE5C,CACL,uBAAA8jB,EACA,mBAAAG,GACA,kBAAAM,GACA,2BAAAljB,GACA,kBAAAG,GACA,yBAAAC,EACA,kBAAAqX,GACA,yBAAAC,EAAA,CAEJ,CCvtBA,MAAMwM,GAAe9/B,EAAAA,WACnB,CACE,CACE,SAAAgE,EACA,UAAA4Y,EACA,gBAAAxX,EACA,WAAAnB,EACA,eAAAC,EACA,gBAAA0sB,EACA,YAAA+B,EACA,qBAAAG,EACA,kBAAA3B,EACA,uBAAApgB,EACA,oBAAAsD,EACA,sBAAA+pB,EACA,cAAAzpB,EACA,sBAAAoe,EACA,gBAAAC,EACA,YAAA1H,EAAc,GACd,UAAWyU,EACX,MAAApsC,EACA,OAAAC,EACA,KAAA0oB,EAAO,EACP,QAAAnZ,EAAU,EACV,QAAAC,EAAU,EACV,YAAAmZ,EAAc,EACd,qBAAAC,EAAuB,EACvB,YAAAC,EACA,aAAAC,EACA,aAAAC,EACA,mBAAAsO,GAAqB,GACrB,cAAA+U,EACA,YAAA3d,EACA,WAAA4d,EACA,iBAAkBC,EAAA,EAEpBjgC,KACG,CAIH,KAAM,CAAE,cAAAkgC,EAAA,EAAkBC,YAAA,EAKpBC,GAAoBz6B,EAAAA,OAA0B,IAAI,EAClDklB,EAAYiV,GAAqBM,GACjCtO,GAAWnsB,EAAAA,OAA4B,IAAI,EAG3CosB,GAAyBpsB,EAAAA,OAAO,EAAK,EACrCqsB,GAAmBrsB,EAAAA,OAAe,CAAC,EACnCssB,GAAgBtsB,EAAAA,OAAe,CAAC,EAChC4sB,GAAuB5sB,EAAAA,OAAe,CAAC,EACvCwsB,GAAuBxsB,EAAAA,OAAgB,EAAK,EAC5CwlB,GAAwBxlB,EAAAA,OAAe,CAAC,EACxCulB,GAAyBvlB,EAAAA,OAA8D,IAAI,EAC3FusB,GAA4BvsB,EAAAA,OAAe,CAAC,EAC5CysB,GAA6BzsB,EAAAA,OAA+B,IAAI,EAChEslB,GAA8BtlB,EAAAA,OAGjC,CAAE,MAAO,KAAM,IAAK,KAAM,EACvB0sB,EAAkB1sB,EAAAA,OAAwC,IAAI,EAC9DiqB,EAA0BjqB,EAAAA,OAA0C,IAAI,EACxE2sB,GAAqB3sB,EAAAA,OAA6B,IAAI,EACtDs4B,GAA6Bt4B,EAAAA,OAA6C,IAAI,EAG9E6sB,GAAwB,IACxBpH,GAAuB,IACvBqH,EAAwB,GACxByL,EAA0B,IAa1BmC,EAA0BC,EAAM,QAAQ,IAAM,CAClD,GAAI,CAACN,GAAcA,IAAe,SAAU,OAAO,KACnD,MAAM7d,IAASC,GAAA,YAAAA,EAAa,SAAU,CAAA,EAGtC,GAAID,GAAO,QAAU,EAAG,OAAO,KAC/B,MAAM/tB,GAAQ+tB,GAAO,KAAMjR,IAAMA,GAAE,KAAO8uB,EAAW,OAAO,EAC5D,OAAK5rC,GACE,CAAE,EAAGA,GAAM,EAAG,EAAGA,GAAM,EAAG,MAAOA,GAAM,MAAO,OAAQA,GAAM,MAAA,EADhD,IAErB,EAAG,CAAC4rC,EAAY5d,CAAW,CAAC,EACtBxF,EACJqjB,KAA6B,OACzBA,GACAI,EAMAE,GAAoBD,EAAM,QAAQ,IAAM,CAC5C,GAAI1jB,EAAkB,OAAOA,EAC7B,MAAMuF,IAASC,GAAA,YAAAA,EAAa,SAAU,CAAA,EACtC,GAAID,GAAO,SAAW,EAAG,CACvB,MAAMjR,GAAIiR,GAAO,CAAC,EAClB,MAAO,CAAE,EAAGjR,GAAE,EAAG,EAAGA,GAAE,EAAG,MAAOA,GAAE,MAAO,OAAQA,GAAE,MAAA,CACrD,CACA,OAAO,IACT,EAAG,CAAC0L,EAAkBwF,CAAW,CAAC,EAE5B,CACJ,cAAAvF,EACA,WAAAK,GACA,mBAAAF,GACA,qBAAAM,GACA,kBAAAK,GACA,mBAAAC,EACA,eAAAG,EACA,eAAAC,GACA,0BAAAC,EAAA,EACE7B,GAAgB,CAClB,MAAA1oB,EAAO,OAAAC,EAAQ,KAAA0oB,EAAM,YAAAC,EAAa,qBAAAC,EAClC,YAAAC,EAAa,aAAAC,EAAc,aAAAC,EAC3B,UAAAC,EAAW,gBAAAxX,EACX,iBAAAyX,CAAA,CACD,EAEK,CACJ,WAAAuB,GAAY,cAAAC,GACZ,kBAAAC,GAAmB,qBAAAC,GACnB,gBAAAC,GAAiB,mBAAAC,GACjB,WAAAC,GAAY,cAAAC,GACZ,sBAAAE,GACA,gBAAAC,GAAiB,mBAAAC,GACjB,0BAAAC,EAAA,EACEb,GAAA,EAEE,CACJ,cAAAkB,GACA,YAAAI,GACA,eAAAN,GAAgB,kBAAAC,GAChB,YAAAF,GACA,qBAAAK,GAAsB,wBAAAC,EAAA,EACpBP,GAAe,CAAE,SAAAjb,EAAU,EAKzB,CAACoQ,EAAgB,EAAI/U,EAAAA,SAAS,IAAM,IAAIohC,GAAAA,gBAAkB,EAC1D,CAACzV,EAAgB,EAAI3rB,EAAAA,SAAS,IAAM,IAAIqhC,GAAAA,iBAAiB,CAAE,WAAY,GAAO,UAAW,EAAA,CAAO,CAAC,EACjG,CAAC9N,EAAU,EAAIvzB,EAAAA,SAAS,IAAM,IAAIshC,GAAAA,oBAAoB,CAAE,QAAS,GAAM,WAAY,EAAA,CAAM,CAAC,EAC1F,CAAC9N,EAAa,EAAIxzB,EAAAA,SAAS,IAAM,IAAIuhC,GAAAA,cAAc,CAAE,QAAS,GAAM,WAAY,EAAA,CAAM,CAAC,EACvF,CAACta,EAAY,EAAIjnB,EAAAA,SAAS,IAAM,CACpC,MAAMwhC,GAAU,IAAIC,2BACpB,OAAAD,GAAQ,kBAAkBvF,uBAAoB,EAC9CuF,GAAQ,eAAejO,EAAU,EAC1BiO,EACT,CAAC,EAGD/gC,EAAAA,UAAU,IAAM,CACdkrB,GAAiB,WAAW,CAAE,aAAc9M,EAAA,CAA2B,CACzE,EAAG,CAACA,GAA2B8M,EAAgB,CAAC,EAUhDlrB,EAAAA,UAAU,IAAM,CACd,MAAMmqB,GAAUiW,KAA6B,OACzCA,GACAI,EACJ1N,GAAW,kBACT3I,GACI,CAAC,CAAE,GAAI,gBAAiB,GAAGA,EAAA,CAAS,EACpC,CAAA,CAAC,CAET,EAAG,CAAC2I,GAAYsN,GAA0BI,CAAuB,CAAC,EAKlE,MAAMS,GAAcn7B,EAAAA,OAAO5B,CAAQ,EACnC+8B,GAAY,QAAU/8B,EAEtB,MAAMoG,GAAkBpG,EAAS,KAAMiH,IAAMA,GAAE,KAAOhH,CAAU,EAC1D+8B,GAAqBp7B,EAAAA,OAAOwE,EAAe,EACjD42B,GAAmB,QAAU52B,GAK7B,KAAM,CAAC62B,GAAkBC,EAAmB,EAAI7hC,EAAAA,SAAS,EAAE,EAE3DS,EAAAA,UAAU,IAAM,QACd,GAAI,CAACmE,EAAY,OACjB,MAAMM,GAAKP,EAAS,KAAMiH,IAAMA,GAAE,KAAOhH,CAAU,EACnD,GAAKM,GACL,GAAIA,cAAc8V,EAAAA,YAAa,CAC7B,MAAMhX,KAASxD,GAAA0E,GAAmB,OAAnB,YAAA1E,GAAyB,MAAM,EAAG,MAAO,QACxDqhC,GAAoB,0BAA0B79B,EAAK,EAAE,CACvD,MAAWkB,cAAc+F,eACvB42B,GAAoB,wBAAwB,EACnC38B,cAAchC,eACvB2+B,GAAoB,gBAAgB,EAEpCA,GAAoB,kBAAkB,CAE1C,EAAG,CAACj9B,EAAYD,CAAQ,CAAC,EAEzB,MAAMm9B,GAAqBv7B,EAAAA,OAAOR,CAAe,EACjD+7B,GAAmB,QAAU/7B,EAK7B,MAAMg8B,GAAwBx7B,EAAAA,OAAO,IAAI,GAAqC,EAE9E9F,EAAAA,UAAU,IAAM,CACd,MAAMuhC,GAAY/yC,IAAoB,CACpC,MAAMgzC,GAAehzC,GACfyX,GAAmBo7B,GAAmB,QAAQ,oBAAA,EAC9CjQ,GAAyBkQ,GAAsB,QAAQ,IAAIr7B,EAAgB,GAAK,CAAA,EAChFw7B,GAA+B,CAAA,EACrC,OAAArQ,GAAuB,QAASjmB,IAAM,CAChCA,cAAa1I,EAAAA,aACfg/B,GAAY,KAAK,GAAGt2B,GAAE,QAAQ,EAE9Bs2B,GAAY,KAAKt2B,EAAC,CAEtB,CAAC,EAEM2nB,GAAW,eAAe0O,GAAqBC,GAAcD,GAAqB,SAAS,CACpG,EACAhG,OAAAA,wBAAqB,QAAQ,cAAe+F,EAAQ,EAC7C,IAAM,CAAE/F,wBAAqB,WAAW,cAAe+F,EAAQ,CAAG,CAC3E,EAAG,CAACzO,EAAU,CAAC,EAKf,KAAM,CAAE,eAAA5Y,EAAA,EAAmBD,GAAA,EACrB,CAAE,QAAApG,EAAS,UAAAC,EAAA,EAAcF,GAAW,CAAE,gBAAAtJ,GAAiB,EAEvD,CACJ,UAAAmQ,GAAW,aAAAC,EACX,SAAAC,EAAU,YAAAC,GACV,aAAAC,EAAc,gBAAAC,GACd,eAAAC,EAAgB,kBAAAC,EAChB,eAAAC,EAAgB,kBAAAC,GAChB,aAAAC,GAAc,gBAAAC,EACd,iBAAAC,GAAkB,mBAAAC,GAClB,mBAAAC,EAAoB,iBAAAC,EACpB,aAAAC,EAAc,gBAAAC,EAAiB,gBAAAG,GAC/B,uBAAwB6lB,GACxB,0BAAA9lB,EAAA,EACEpB,GAAe,CAAE,gBAAAlQ,GAAiB,IAAAnK,GAAK,EAG3CH,EAAAA,UAAU,IAAM,CACd,MAAM2hC,GAAsB,KAAK,IAAA,EAAQrW,GAAsB,QACzDiC,GAAiBlC,GAAuB,QAC1CkC,IAAkBoU,GAAsBpW,IACtCtQ,IAAmBsS,GAAe,OAASpS,KAAiBoS,GAAe,OAC7EvS,EAAkBuS,GAAe,MAAM,EACvCrS,GAAkBqS,GAAe,KAAK,EACtCnS,EAAgBmS,GAAe,GAAG,EAC9B0E,GAAS,SACXA,GAAS,QAAQ,kBAAkB1E,GAAe,MAAOA,GAAe,GAAG,GAGtEA,IAAkBoU,IAAuBpW,KAClDF,GAAuB,QAAU,KAErC,EAAG,CAACpQ,EAAgBE,GAAcJ,CAAc,CAAC,EAEjD,KAAM,CACJ,mBAAA7K,GAAoB,aAAAE,GAAc,WAAAE,GAAY,wBAAAE,GAC9C,sBAAA4iB,GAAuB,uBAAAC,GAAwB,uBAAAC,EAAA,EAC7CrjB,GAAA,EAEE,CACJ,6BAAAmK,GAA8B,qBAAAC,EAAA,EAC5BF,GAAkB/V,CAAc,EAE9B,CACJ,mBAAAmG,GAAoB,kBAAAmK,GAAmB,kBAAAD,EAAA,EACrCJ,GAAe,CAAE,SAAAnQ,EAAU,gBAAAoG,GAAiB,iBAAAgK,GAAkB,oBAAAC,EAAqB,EAEjF,CAAE,WAAAO,GAAY,cAAAE,EAAA,EAAkBJ,GAAc,CAAE,cAAAC,EAAe,EAK/DoW,GAAqBtpB,EAAAA,QAAQ,IAAM,CACvC,MAAMorB,OAAY,IAClB,OAAA7oB,EAAS,QAAS7B,IAAY,CAC5B,MAAMoF,GAAanC,EAAgB,wBAAwBjD,GAAQ,EAAE,EAChE0qB,GAAM,IAAItlB,EAAU,GAAKslB,GAAM,IAAItlB,GAAY,EAAE,EACtDslB,GAAM,IAAItlB,EAAU,EAAG,KAAKpF,EAAO,CACrC,CAAC,EACM0qB,EACT,EAAG,CAAC7oB,EAAUoB,CAAe,CAAC,EAE9Bg8B,GAAsB,QAAUrW,GAEhC,MAAMkI,GAA4BzxB,EAAAA,YAAY,IAAM,CAClD,MAAMuE,GAAmBX,EAAgB,oBAAA,EACzC,OAAO2lB,GAAmB,IAAIhlB,EAAgB,GAAK,CAAA,CACrD,EAAG,CAACglB,GAAoB3lB,CAAe,CAAC,EAKxCtF,EAAAA,UAAU,IAAM,CACd,MAAMkL,GAAiBC,IAAqB,CAC1C,GAAI,CAAAsP,GAEJ,IAAI5G,EAAQ,YAAcvJ,cAA2B8J,EAAAA,aACnD,GAAIjJ,GAAE,MAAQ,SAAU,CACtBA,GAAE,eAAA,EACF0I,EAAQ,WAAA,EACR,MAAMtI,GAAiBjB,GAAgB,MAAA,EACvC0oB,EAAqB1oB,GAAiBiB,EAAc,EACpD+T,GAAmBjZ,IAAMA,GAAI,CAAC,EAC9B,MACF,SAAW8E,GAAE,MAAQ,aAAeA,GAAE,MAAQ,SAAU,CACtDA,GAAE,eAAA,EACF0I,EAAQ,gBAAA,EACRyL,GAAmBjZ,IAAMA,GAAI,CAAC,EAC9B,MACF,EAGE8E,GAAE,QAAUwT,GAAmB,EAAI,EACzC,EAEMnT,GAAeL,IAAqB,CACnCA,GAAE,QAAUwT,GAAmB,EAAK,CAC3C,EAEMijB,GAAcz2B,IAAkB,EAChC,CAACA,GAAE,eAAiB,CAAEA,GAAE,cAAuB,gBACjDwT,GAAmB,EAAK,CAE5B,EAEA,cAAO,iBAAiB,UAAWzT,EAAa,EAChD,OAAO,iBAAiB,QAASM,EAAW,EAC5C,OAAO,iBAAiB,OAAQo2B,EAA2B,EACpD,IAAM,CACX,OAAO,oBAAoB,UAAW12B,EAAa,EACnD,OAAO,oBAAoB,QAASM,EAAW,EAC/C,OAAO,oBAAoB,OAAQo2B,EAAU,CAC/C,CACF,EAAG,CAAC/tB,EAASvJ,GAAiB0oB,EAAsB1T,GAAmB7E,EAAS,CAAC,EAGjFza,EAAAA,UAAU,IAAM,CAEd,GAAI,CADWgrB,EAAU,SACV,CAACwH,EAAgB,SAC9BhM,GAAa,QAAA,IAAc,QAAUA,GAAa,QAAA,IAAc,SAChE,OAGF,KAAM,CAAE,EAAAhjB,GAAG,EAAAC,EAAA,EAAM+uB,EAAgB,QAEjC,GAAI9T,GAAiB,CACnB,IAAItJ,GAAuC,KAC3C,MAAMgc,GAAyB+B,GAAA,EAC/B,QAASplB,GAAIqjB,GAAuB,OAAS,EAAGrjB,IAAK,EAAGA,KAAK,CAC3D,MAAM1L,GAAU+uB,GAAuBrjB,EAAC,EACxC,GAAI1L,GAAQ,UAAY,GACxB,IAAIA,cAAmBI,EAAAA,aACrB,QAAS05B,GAAI95B,GAAQ,SAAS,OAAS,EAAG85B,IAAK,EAAGA,KAAK,CACrD,MAAMxtC,GAAQ0T,GAAQ,SAAS85B,EAAC,EAChC,GAAIxtC,GAAM,UAAY,GAAO,SAC7B,MAAMiU,GAAOjU,GAAM,qBAAA,EACnB,GAAI6U,IAAKZ,GAAK,GAAKY,IAAKZ,GAAK,EAAIA,GAAK,OAASa,IAAKb,GAAK,GAAKa,IAAKb,GAAK,EAAIA,GAAK,OAAQ,CACvFwS,GAAiBzmB,GACjB,KACF,CACF,KACK,CACL,MAAMiU,GAAOP,GAAQ,qBAAA,EACrB,GAAImB,IAAKZ,GAAK,GAAKY,IAAKZ,GAAK,EAAIA,GAAK,OAASa,IAAKb,GAAK,GAAKa,IAAKb,GAAK,EAAIA,GAAK,OAAQ,CACvFwS,GAAiB/S,GACjB,KACF,CACF,CACA,GAAI+S,GAAgB,MACtB,CAEA,GAAIA,GAAgB,CAClB,MAAM2jB,GAAqC,CAAA,EAC3C3H,GAAuB,QAASjmB,IAAM,CAChCA,cAAa1I,EAAAA,aAAgBs2B,GAAkB,KAAK,GAAG5tB,GAAE,QAAQ,EAC9D4tB,GAAkB,KAAK5tB,EAAC,CACjC,CAAC,EACD,MAAMmuB,GAAavG,GAAc,cAAc3d,GAAgB2jB,GAAmB,EAAI,EACtFta,GAAqB6a,EAAU,CACjC,CACF,MACE7a,GAAqB,CAAA,CAAE,CAE3B,EAAG,CAACC,GAAiByU,GAA2BJ,GAAevM,EAAY,CAAC,EAK5E,MAAMqb,GAAuB/7B,EAAAA,OAAsB,IAAI,EAEvD9F,EAAAA,UAAU,IAAM,CACd,MAAM4/B,GAAa17B,EAAS,KAAMiH,IAAMA,GAAE,KAAOhH,CAAU,EACrD29B,GAAkBv3B,IAAsBq1B,GAE9C,GAAIkC,GAAiB,CACnB,MAAMl/B,GAAOk/B,GAAgB,eAAA,EACvBC,GAAa,GAAGD,GAAgB,EAAE,IAAIl/B,GAAK,CAAC,IAAIA,GAAK,CAAC,IAAIA,GAAK,KAAK,IAAIA,GAAK,MAAM,IAAIk/B,GAAgB,QAAQ,GAEjHD,GAAqB,UAAYE,KACnCF,GAAqB,QAAUE,GAC/BztB,GAAiB,OAAOwtB,GAAiBtlB,CAAI,EAC7C8C,GAAmBjZ,IAAMA,GAAI,CAAC,EAElC,CAEF,EAAG,CAAClC,EAAYoG,GAAoB+J,GAAkBkI,EAAMtY,CAAQ,CAAC,EAOrE,KAAM,CACJ,mBAAAw6B,GACA,kBAAAM,GACA,2BAAAljB,GACA,kBAAAG,GACA,yBAAAC,GACA,kBAAAqX,GACA,yBAAAC,EAAA,EACE2K,GAAuB,CACzB,UAAAnT,EACA,SAAAiH,GACA,kBAAAxd,GACA,gBAAAoH,GACA,mBAAAN,EACA,mBAAAD,GACA,sBAAAgQ,GACA,uBAAAD,GACA,2BAAA+S,GACA,2BAAA7L,GACA,uBAAAL,GACA,qBAAAQ,GACA,mBAAAvV,GACA,qBAAAoO,GACA,wBAAA8S,EACA,UAAA5jB,GACA,SAAAE,EACA,aAAAc,EACA,eAAAV,EACA,eAAAE,EACA,aAAAE,GACA,WAAAhX,EACA,gBAAAmG,GACA,mBAAAC,GACA,SAAArG,EACA,KAAAsY,EACA,eAAA0B,EACA,eAAAC,GACA,aAAAzD,EACA,YAAAE,GACA,gBAAAc,EACA,kBAAAV,EACA,kBAAAE,GACA,gBAAAE,EACA,gBAAAN,GACA,0BAAAc,GACA,wBAAA8D,GACA,kBAAAJ,GACA,YAAAuT,EACA,gBAAA/B,EACA,sBAAAwN,EACA,iBAAAhqB,GACA,kBAAAI,EAAA,CACD,EAGK,CACJ,kBAAAggB,GACA,kBAAAgC,GACA,gBAAA6F,GACA,kBAAAgB,EAAA,EACEvL,GAAqB,CACvB,UAAAhH,EACA,SAAAiH,GACA,uBAAAC,GACA,iBAAAC,GACA,cAAAC,GACA,0BAAAC,GACA,sBAAA/G,GACA,uBAAAD,GACA,qBAAAiH,GACA,2BAAAC,GACA,4BAAAnH,GACA,gBAAAoH,EACA,wBAAAzC,EACA,mBAAA0C,GACA,kBAAAhe,GACA,gBAAAoH,GACA,qBAAA6W,GACA,6BAAAtY,GAEA,sBAAAuY,GACA,qBAAApH,GACA,sBAAAqH,EACA,UAAAnY,GACA,SAAAE,EACA,cAAAqC,EACA,eAAA/B,EACA,aAAAE,GACA,WAAAyD,GACA,mBAAA1O,GACA,gBAAAwO,GAEA,gBAAApU,GACA,mBAAAC,GACA,SAAArG,EACA,eAAAE,EACA,qBAAAiW,GACA,KAAAmC,EACA,eAAA0B,EACA,eAAAC,GACA,kBAAAK,GACA,iBAAkBkiB,GAClB,kBAAA1lB,EACA,kBAAAE,GACA,gBAAAE,EACA,cAAAyD,GACA,sBAAAE,GACA,mBAAAE,GACA,0BAAAC,GACA,mBAAAP,GACA,cAAAJ,GACA,qBAAAE,GACA,kBAAAa,GACA,cAAAtK,GACA,YAAA6d,EACA,YAAAzT,GACA,aAAAoH,GACA,iBAAAlS,GACA,WAAAwe,GACA,cAAAC,GACA,QAAAlf,EACA,eAAAqG,GACA,gBAAA4W,EACA,qBAAAkC,EACA,kBAAA3B,EACA,uBAAApgB,EACA,sBAAAgiB,EACA,gBAAAC,EACA,kBAAAxe,GACA,0BAAAye,GACA,sBAAAC,GACA,uBAAAC,GACA,uBAAAC,GACA,kBAAAC,GACA,gBAAAzY,GACA,YAAAF,GACA,gBAAAc,EACA,aAAAhB,EACA,0BAAAkB,GACA,yBAAA4X,EAAA,CACD,EAGDlJ,GAAoB,CAClB,UAAAU,EACA,SAAA9mB,EACA,mBAAA+mB,GACA,UAAAnO,EACA,gBAAAxX,EACA,iBAAA4lB,GACA,WAAA/mB,EACA,gBAAAmG,GACA,mBAAAC,GACA,kBAAAkK,GACA,iBAAAH,GACA,aAAAkS,GACA,WAAAnJ,GACA,kBAAAS,GACA,mBAAAC,EACA,eAAAG,EACA,eAAAC,GACA,KAAA3B,EACA,0BAAA4B,GACA,qBAAAX,GACA,mBAAA0N,GACA,WAAAvM,GACA,gBAAAI,GACA,WAAAlK,GACA,UAAA2F,GACA,SAAAE,EACA,aAAAc,EACA,eAAAV,EACA,eAAAE,EACA,aAAAE,GACA,iBAAAE,GACA,mBAAAC,GACA,cAAA0B,EACA,4BAAAoO,GACA,uBAAAC,GACA,sBAAAC,GACA,qBAAAC,GACA,WAAAjN,GACA,kBAAAE,GACA,gBAAAE,GACA,eAAAta,EACA,qBAAAiW,GACA,6BAAAD,GACA,mBAAAlK,GACA,aAAAE,GACA,WAAAE,GACA,wBAAAE,GACA,QAAAqD,EACA,eAAAwL,GACA,YAAAmM,EACA,cAAA6U,GACA,qBAAA5gB,GACA,cAAAF,GACA,YAAAgD,EACA,eACE4d,GAAcA,IAAe,SAAWA,EAAW,QAAU,KAC/D,iBAAApjB,CAAA,CACD,EAKDT,EAAAA,oBACEnc,GACA,KAAO,CACL,oBAAsB1B,IAA0B,CAC9Cqd,GAA2Brd,EAAK,CAClC,EACA,yBAA2B0d,IAAgE,CACzFD,GAAyBC,EAAQ,CACnC,EACA,cAAe,IAAM1B,GACrB,WAAY,IAAMmE,GAClB,kBAAmB,IAAM3C,GAAA,EACzB,QAAS,IAAMO,EACf,YAAAmD,GACA,gBAAiB,KAAO,CACtB,UAAAlF,GACA,SAAAE,EACA,eAAAI,EACA,eAAAE,EACA,aAAAE,EAAA,GAEF,aAAc,KAAO,CACnB,WAAY7Q,cAA2BE,gBAAiBF,GAAiC,WACzF,WAAWA,IAAA,YAAAA,GAAiB,KAAM,IAAA,GAEpC,iBAAkB,KAAO,CAAE,EAAG4T,EAAgB,EAAGC,EAAA,EAAe,GAElE,CAACrC,GAA4BI,GAA0BzB,GAAWmE,GAAY3C,GAAmBO,EAAMmD,GAAahF,EAAUI,EAAgBE,EAAgBE,GAAc7Q,GAAiB4T,EAAgBC,EAAc,CAAA,EAc7N,MAAM6jB,GAAsBrgC,EAAAA,QAAQ,IAAM,CACxC,GAAI,CAACu+B,GAAiBA,EAAc,SAAW,EAAG,OAClD,MAAM+B,GAAOnkB,GAAoBtB,EAC3B0lB,GAAOnkB,EAAqBvB,EAC5BoR,GAAQ,WAAWqU,EAAI,MAAMC,EAAI,MAAM,CAACD,EAAI,KAC5CE,GAAUjC,EACb,IAAK12B,IAAM,CACV,GAAIA,GAAE,OAAS,QAAUA,GAAE,OAAS,cAAe,CACjD,MAAMhG,IAAK0a,EAAiB1U,GAAE,GAAKgT,EAC7B/Y,IAAK0a,GAAiB3U,GAAE,GAAKgT,EAC7BwD,GAAIxW,GAAE,MAAQgT,EACdyD,GAAIzW,GAAE,OAASgT,EACrB,GAAIhT,GAAE,OAAS,cAAe,CAC5B,MAAM8X,IAAM9X,GAAE,IAAMA,GAAE,IAAM,GAAKgT,EAC3B+E,IAAM/X,GAAE,IAAMA,GAAE,IAAM,GAAKgT,EACjC,GAAI8E,GAAK,GAAKC,GAAK,EACjB,MAAO,CACL,KAAK/d,GAAI8d,EAAE,IAAI7d,EAAC,GAChB,KAAKuc,GAAI,EAAIsB,EAAE,GACf,KAAKA,EAAE,IAAIC,EAAE,UAAUD,EAAE,IAAIC,EAAE,GAC/B,KAAKtB,GAAI,EAAIsB,EAAE,GACf,KAAKD,EAAE,IAAIC,EAAE,UAAU,CAACD,EAAE,IAAIC,EAAE,GAChC,KAAK,EAAEvB,GAAI,EAAIsB,GAAG,GAClB,KAAKA,EAAE,IAAIC,EAAE,UAAU,CAACD,EAAE,IAAI,CAACC,EAAE,GACjC,KAAK,EAAEtB,GAAI,EAAIsB,GAAG,GAClB,KAAKD,EAAE,IAAIC,EAAE,UAAUD,EAAE,IAAI,CAACC,EAAE,GAChC,GAAA,EACA,KAAK,GAAG,CAEd,CACA,MAAO,KAAK/d,EAAC,IAAIC,EAAC,MAAMuc,EAAC,MAAMC,EAAC,MAAM,CAACD,EAAC,IAC1C,CAMA,MAAO,EACT,CAAC,EACA,OAAO,OAAO,EACjB,GAAImiB,GAAQ,SAAW,EACvB,MAAO,kBAAkBvU,EAAK,IAAIuU,GAAQ,KAAK,GAAG,CAAC,IACrD,EAAG,CAACjC,EAAepiB,GAAmBC,EAAoBG,EAAgBC,GAAgB3B,CAAI,CAAC,EAK/F,OACE4lB,GAAAA,KAAAC,YAAA,CACE,SAAA,CAAAzgC,GAAAA,IAAC,MAAA,CACC,KAAK,SACL,YAAU,SACV,cAAY,OACZ,MAAO,CAAE,SAAU,WAAY,MAAO,EAAG,OAAQ,EAAG,OAAQ,GAAI,QAAS,EAAG,SAAU,SAAU,KAAM,gBAAiB,WAAY,SAAU,YAAa,CAAA,EAEzJ,SAAAu/B,EAAA,CAAA,EAEHv/B,GAAAA,IAAC,SAAA,CACC,KAAK,cACL,aAAW,iFACX,IAAKopB,EACL,cAAe0J,GACf,cAAegC,GACf,YAAa6F,GACb,gBAAiBA,GACjB,cAAegB,GACf,MAAO,CACL,QAAS,QAYT,WAAYhb,EAAc,cAAgB,yBAC1C,MAAO,GAAGzE,GAAoBtB,CAAI,KAClC,OAAQ,GAAGuB,EAAqBvB,CAAI,KACpC,OAAQ3I,EAAQ,SAAA,GAAcvJ,cAA2B8J,EAAAA,YAAcN,GAAY,OACnF,SAAU,WACV,KAAM,GAAGzQ,CAAO,KAChB,IAAK,GAAGC,CAAO,KACf,YAAa,OACb,SAAU0+B,EAAA,CACZ,CAAA,EAIDvnB,KACE,IAAM,CAEL,IAAIolB,GAAmDv1B,GACvD,GAAImK,GAAkB,SAAWnK,cAA2B7H,eAAc,CACxE,MAAM9T,GAAQ2b,GAAgB,SAAS,KAAMd,IAAMA,GAAE,KAAOiL,GAAkB,OAAO,EACjF9lB,KAASkxC,GAAiBlxC,GAChC,CAEA,MAAM2zC,GAASzC,cAA0BtlB,EAAAA,YAAcslB,GAAiB,KAClE5yB,IAAWq1B,IAAA,YAAAA,GAAQ,WAAY,GAC/Bp1B,IAAao1B,IAAA,YAAAA,GAAQ,aAAc,QACnCn1B,IAAOm1B,IAAA,YAAAA,GAAQ,OAAQ,GACvBl1B,IAASk1B,IAAA,YAAAA,GAAQ,SAAU,GAC3Bj1B,IAAYi1B,IAAA,YAAAA,GAAQ,YAAa,SAEjCC,GACJ3gC,GAAAA,IAAC,WAAA,CACC,IAAKqwB,GACL,0BAAwB,GACxB,MAAOtX,EACP,SAAWxP,IAAM,CACf,MAAMkR,GAAYlR,GAAE,OAA+B,MAEnD,GAAI,CAACsQ,EAAc,CACjB,MAAM+mB,GAAcC,EAAAA,SAAS,cAAcpmB,GAAU,CAAA,CAAE,EACvDR,GAAgB,QAAU2mB,GAC1B9mB,EAAgB8mB,EAAW,EAC3B5nB,GAAYyB,EAAQ,EACpB,MAAMqmB,GAAev3B,GAAE,OAAO,aAC9B6P,EAAkB0nB,EAAY,EAC9BxnB,GAAkB/P,GAAE,OAAO,cAAc,EACzCiQ,EAAgBjQ,GAAE,OAAO,YAAY,EACrC,MACF,CAEA,MAAMw3B,GAAUhoB,EACVioB,GAAkBnnB,EAAa,MAAA,EAErC,IAAIonB,GAAY,EAChB,KAAOA,GAAYF,GAAQ,QAAUE,GAAYxmB,GAAS,QAAUsmB,GAAQE,EAAS,IAAMxmB,GAASwmB,EAAS,GAC3GA,KAGF,IAAIC,GAAY,EAChB,KAAOA,GAAYH,GAAQ,OAASE,IAAaC,GAAYzmB,GAAS,OAASwmB,IAC7EF,GAAQA,GAAQ,OAAS,EAAIG,EAAS,IAAMzmB,GAASA,GAAS,OAAS,EAAIymB,EAAS,GACpFA,KAGF,MAAMC,GAAcF,GACdG,GAAYL,GAAQ,OAASG,GAC7BG,GAAa5mB,GAAS,UAAUwmB,GAAWxmB,GAAS,OAASymB,EAAS,EAM5E,GAJIE,GAAYD,IACdH,GAAgB,OAAOG,GAAaC,EAAS,EAG3CC,GAAW,OAAS,EAAG,CACzB,IAAIC,GACAxB,GACFwB,GAAcxB,GACLqB,GAAc,EACvBG,GAAcN,GAAgB,WAAWG,GAAc,CAAC,EAC/Cz4B,cAA2BiQ,cACpC2oB,GAAc54B,GAAgB,gBAAA,EAE9B44B,GAAc,CAAA,EAEhBN,GAAgB,OAAOG,GAAaE,GAAYC,EAAW,CAC7D,CAEArnB,GAAgB,QAAU+mB,GAC1BlnB,EAAgBknB,EAAe,EAC/BhoB,GAAYyB,EAAQ,EAEpB,MAAMqmB,GAAev3B,GAAE,OAAO,aAC9B6P,EAAkB0nB,EAAY,EAC9BxnB,GAAkB/P,GAAE,OAAO,cAAc,EACzCiQ,EAAgBjQ,GAAE,OAAO,YAAY,EACrCoQ,EAAmB,QAAUmnB,GAE7B,MAAMzD,GAAW9zB,GAAE,OACnB,WAAW,IAAM,CACX8zB,IAAY,SAAS,gBAAkBA,IACzCA,GAAS,kBAAkByD,GAAcA,EAAY,CAEzD,EAAG,CAAC,CACN,EACA,OAAQhE,GACR,UAAWM,GACX,cAAgB7zB,IAAM,CAEpB,GADAA,GAAE,eAAA,EACE,CAACb,IAAmB,EAAEA,cAA2BiQ,EAAAA,cAAgB,CAACyQ,EAAU,QAAS,OAEzF,MAAM5e,GAAM,KAAK,IAAA,EACjB,GAAIA,GAAMimB,GAA0B,QAAUO,EAAuB,OACrEP,GAA0B,QAAUjmB,GAEhCA,GAAMkf,GAAsB,SAAWC,KACzCF,GAAuB,QAAU,MAGnC,MAAM8L,GAAanM,EAAU,QAAQ,sBAAA,EAC/BmY,IAAWh4B,GAAE,QAAUgsB,GAAW,MAAQ3a,EAAO0B,EACjDklB,IAAWj4B,GAAE,QAAUgsB,GAAW,KAAO3a,EAAO2B,GAEhDwS,GAAarmB,GAAgB,qBAAA,EAC7B+qB,GAAS/qB,GAAgB,kBAAA,EACzB4c,GAAcvR,EAAAA,cAAc,iBAAiBrL,GAAgB,QAAQ,EACrEe,GAAK83B,GAAU9N,GAAO,EACtB/pB,GAAK83B,GAAU/N,GAAO,EACtBzf,GAAM,KAAK,IAAIsR,EAAW,EAC1BrR,GAAM,KAAK,IAAIqR,EAAW,EAC1BoO,GAAgBjqB,GAAKuK,GAAMtK,GAAKuK,GAChC0f,GAAgBlqB,GAAKwK,GAAMvK,GAAKsK,GAChC9F,GAAYwlB,GAAgB3E,GAAW,MAAQ,EAC/C6E,GAAYD,GAAgB5E,GAAW,OAAS,EAEhD8E,GAAa9lB,GAAwBrF,GAAiB,CAAE,EAAGwF,GAAW,EAAG0lB,GAAW,EAE/DppB,GAAM+lB,GAAiB,QACzBQ,GACvBP,GAAc,SAAW,EAEzBA,GAAc,QAAU,EAE1BD,GAAiB,QAAU/lB,GAE3B,MAAMspB,GAAc/a,EAGpB,GAFAa,EAAiB,QAAU,GAEvB4W,GAAc,SAAW,EAC3BjnB,GAAE,eAAA,EACFmgB,GAAsB,QAAUlf,GAChCif,GAAuB,QAAU,CAAE,MAAO,EAAG,IAAKqK,GAAY,OAAQ,OAAQA,GAAY,MAAA,EAC1FpD,GAAqB,QAAU,GAC/BF,GAAc,QAAU,EACxB7W,EAAmB,QAAU,EAC7BP,EAAkB0a,GAAY,MAAM,EACpCxa,GAAkB,CAAC,EACnBE,EAAgBsa,GAAY,MAAM,EAC9BzD,GAAS,UACXA,GAAS,QAAQ,MAAA,EACjBA,GAAS,QAAQ,kBAAkB,EAAGyD,GAAY,MAAM,WAEjDtD,GAAc,UAAY,EAAG,CACtCjnB,GAAE,eAAA,EACF,MAAMwqB,GAAappB,GAAkBmpB,GAAaD,EAAU,EAC5DnK,GAAsB,QAAUlf,GAChCif,GAAuB,QAAU,CAAE,MAAOsK,GAAW,MAAO,IAAKA,GAAW,IAAK,OAAQA,GAAW,GAAA,EACpGrD,GAAqB,QAAU,GAC/B/W,EAAmB,QAAUoa,GAAW,MACxC3a,EAAkB2a,GAAW,GAAG,EAChCza,GAAkBya,GAAW,KAAK,EAClCva,EAAgBua,GAAW,GAAG,EAC1B1D,GAAS,UACXA,GAAS,QAAQ,MAAA,EACjBA,GAAS,QAAQ,kBAAkB0D,GAAW,MAAOA,GAAW,GAAG,EAEvE,MACEtK,GAAuB,QAAU,KACjC9P,EAAmB,QAAUka,GAC7Bva,GAAkBua,EAAU,EAC5Bza,EAAkBya,EAAU,EAC5Bra,EAAgBqa,EAAU,EACtBxD,GAAS,UACXA,GAAS,QAAQ,MAAA,EACjBA,GAAS,QAAQ,kBAAkBwD,GAAYA,EAAU,GAE3DnD,GAAqB,QAAU,EAEnC,EACA,cAAgBnnB,IAAM,CACpB,GAAIA,GAAE,UAAY,EAAG,OAGrB,GAAIonB,GAA2B,SAAWjoB,cAA2BiQ,EAAAA,aAAeyQ,EAAU,QAAS,CACrG,MAAMmM,GAAanM,EAAU,QAAQ,sBAAA,EAC/BmY,IAAWh4B,GAAE,QAAUgsB,GAAW,MAAQ3a,EAAO0B,EACjDklB,IAAWj4B,GAAE,QAAUgsB,GAAW,KAAO3a,EAAO2B,GAEhDwS,GAAarmB,GAAgB,qBAAA,EAC7B+qB,GAAS/qB,GAAgB,kBAAA,EACzB4c,GAAcvR,EAAAA,cAAc,iBAAiBrL,GAAgB,QAAQ,EACrEe,GAAK83B,GAAU9N,GAAO,EACtB/pB,GAAK83B,GAAU/N,GAAO,EACtBzf,GAAM,KAAK,IAAIsR,EAAW,EAC1BrR,GAAM,KAAK,IAAIqR,EAAW,EAC1BoO,GAAgBjqB,GAAKuK,GAAMtK,GAAKuK,GAChC0f,GAAgBlqB,GAAKwK,GAAMvK,GAAKsK,GAEhCwf,GAAkBhK,GAA4B,QACpD,IAAIgN,GAAU,EACV7F,GAA2B,UAAY,UAAW6C,IAAA,MAAAA,GAAiB,OACrEgD,GAAUhD,GAAgB,MAAM,SAAS,EAAIA,GAAgB,MAAM,OAAO,EACjE7C,GAA2B,UAAY,QAAS6C,IAAA,MAAAA,GAAiB,OAC1EgD,GAAUhD,GAAgB,IAAI,SAAS,EAAIA,GAAgB,IAAI,OAAO,GAGxE,MAAMtlB,GAAYwlB,GAAgB3E,GAAW,MAAQ,EAC/C6E,GAAaD,GAAgB6C,GAAWzH,GAAW,OAAS,EAE5D0H,GAAY1oB,GAAwBrF,GAAiB,CAAE,EAAGwF,GAAW,EAAG0lB,GAAW,EAwBzF,GAtBIjD,GAA2B,UAAY,QACrC8F,IAAald,IACfD,GAAkBmd,EAAS,EAC3Brd,EAAkBqd,EAAS,IAE3Bnd,GAAkBC,EAAY,EAC9BC,EAAgBid,EAAS,EACzBrd,EAAkBqd,EAAS,EAC3B9F,GAA2B,QAAU,OAGnC8F,IAAapd,GACfG,EAAgBid,EAAS,EACzBrd,EAAkBqd,EAAS,IAE3Bjd,EAAgBH,CAAc,EAC9BC,GAAkBmd,EAAS,EAC3Brd,EAAkBqd,EAAS,EAC3B9F,GAA2B,QAAU,SAIrCN,GAAS,QAAS,CACpB,MAAM3/B,GAAQ,KAAK,IAAI2oB,EAAgBE,EAAY,EAC7C5oB,GAAM,KAAK,IAAI0oB,EAAgBE,EAAY,EACjD8W,GAAS,QAAQ,kBAAkB3/B,GAAOC,EAAG,CAC/C,CACA,MACF,CAGA,GADI+/B,GAAqB,SACrB,CAAChoB,IAAmB,EAAEA,cAA2BiQ,EAAAA,cAAgB,CAACyQ,EAAU,QAAS,OAEzF,MAAMmM,GAAanM,EAAU,QAAQ,sBAAA,EAC/BmY,IAAWh4B,GAAE,QAAUgsB,GAAW,MAAQ3a,EAAO0B,EACjDklB,IAAWj4B,GAAE,QAAUgsB,GAAW,KAAO3a,EAAO2B,GAEhDwS,GAAarmB,GAAgB,qBAAA,EAC7B+qB,GAAS/qB,GAAgB,kBAAA,EACzB4c,GAAcvR,EAAAA,cAAc,iBAAiBrL,GAAgB,QAAQ,EACrEe,GAAK83B,GAAU9N,GAAO,EACtB/pB,GAAK83B,GAAU/N,GAAO,EACtBzf,GAAM,KAAK,IAAIsR,EAAW,EAC1BrR,GAAM,KAAK,IAAIqR,EAAW,EAC1BoO,GAAgBjqB,GAAKuK,GAAMtK,GAAKuK,GAChC0f,GAAgBlqB,GAAKwK,GAAMvK,GAAKsK,GAChC9F,GAAYwlB,GAAgB3E,GAAW,MAAQ,EAC/C6E,GAAYD,GAAgB5E,GAAW,OAAS,EAEhD0H,GAAY1oB,GAAwBrF,GAAiB,CAAE,EAAGwF,GAAW,EAAG0lB,GAAW,EACnF/d,GAAS8D,EAAmB,QAUlC,GARI9D,KAAW4gB,KACb7c,EAAiB,QAAU,IAG7BJ,EAAgBid,EAAS,EACzBrd,EAAkBqd,EAAS,EAC3Bnd,GAAkBzD,EAAM,EAEpBwa,GAAS,QAAS,CACpB,MAAM3/B,GAAQ,KAAK,IAAImlB,GAAQ4gB,EAAS,EAClC9lC,GAAM,KAAK,IAAIklB,GAAQ4gB,EAAS,EACtCpG,GAAS,QAAQ,kBAAkB3/B,GAAOC,EAAG,CAC/C,CACF,EACA,YAAa,IAAM,CACbggC,GAA2B,UAC7BA,GAA2B,QAAU,KAEzC,EACA,QAAS,IAAM,CACb/W,EAAiB,QAAU,GAC3B8W,GAAqB,QAAU,EACjC,EACA,SAAU,IAAM,CAEhB,EACA,UAAS,GACT,WAAY,GACZ,MAAO,CACL,SAAU,QACV,KAAM,GAAGzX,EAAa,CAAC,KACvB,IAAK,GAAGA,EAAa,CAAC,KACtB,MAAO,GAAGA,EAAa,KAAK,KAC5B,OAAQ,GAAGA,EAAa,QAAU,EAAE,KACpC,SAAU,GAAG5N,GAAWuP,CAAI,KAC5B,WAAY,IAAItP,EAAU,uBAC1B,WAAYC,GAAO,IAAM,IACzB,UAAWC,GAAS,SAAW,SAC/B,UAAAC,GACA,WAAY,IACZ,QAAS,EACT,OAAQ,KACR,OAAQ,OACR,OAAQ,wBACR,QAAS,OACT,QAAS,MACT,OAAQ,IACR,SAAU,SACV,MAAO,cACP,WAAY,cACZ,WAAY,cACZ,WAAY,WACZ,UAAW,SACX,aAAc,QAAA,CAChB,CAAA,EAIJ,OAAOg2B,gBAAad,GAAiB,SAAS,IAAI,CACpD,GAAA,CAAG,EACP,CAEJ,CACF,EAEAvC,GAAa,YAAc,eCjsC3B,MAAMsD,GAAgB,IAChBC,GAAgB,EAGhBC,GAAW,GACXC,GAAW,EACXC,GAAmB,KAuEZC,GAAgC,CAAC,CAC5C,UAAAC,EACA,MAAAnlC,EACA,MAAA5K,EACA,OAAAC,EACA,KAAM+vC,EACN,WAAAC,EAAa,IACb,YAAAC,EAAc,GACd,qBAAArnB,EAAuB,EACvB,YAAAC,EACA,UAAAqnB,EACA,mBAAA7Y,EAAqB,GACrB,gBAAA8Y,EAAkB,GAClB,cAAA/D,EACA,YAAA3d,EACA,WAAA4d,CACF,IAAM,QAGJ,KAAM,CACJ,gBAAA+D,EACA,UAAAlZ,EACA,SAAA9mB,EACA,UAAA4Y,EACA,gBAAAxX,EACA,oBAAAE,EACA,YAAAqtB,EACA,qBAAAG,CAAA,EACEntB,aAAA,EAGE,CACJ,KAAMs+B,EACN,QAAAC,EACA,UAAAhQ,EACA,aAAAC,EACA,UAAAgQ,GACA,aAAAC,EACA,SAAApQ,EACA,YAAAC,EACA,cAAAoQ,EAAA,EACEjQ,sBAAA,EAGE,CACJ,WAAAnwB,GACA,eAAAC,GACA,sBAAAmB,GACA,kBAAAE,EACA,wBAAA++B,GACA,oBAAAC,GACA,YAAAjZ,EAAA,EACEkZ,uBAAA,EAGE,CACJ,wBAAAC,GACA,qBAAAC,EAAA,EACEC,uBAAA,EAGEC,GAAsBrE,EAAM,YAAY,IAAM,CAClDmE,GAAqB,MAAM,CAC7B,EAAG,CAACA,EAAoB,CAAC,EAEnBG,GAAetE,EAAM,OAAuB,IAAI,EAEhD,CAACuE,GAAgBC,EAAiB,EAAIxE,EAAM,SAAiB,CAAC,EAE9D,CAACpJ,GAAgB6N,EAAiB,EAAIzE,EAAM,SAAiB,CAAC,EAE9D,CAAC0E,EAAqBC,CAAsB,EAAI3E,EAAM,SAAkB,EAAK,EAE7E4E,GAAoB5E,EAAM,OAAe,CAAC,EAK1C6E,GAAgB7E,EAAM,OAAO,EAAK,EAGlC,CAAC8E,GAAaC,EAAc,EAAI/E,EAAM,SAAS,EAAK,EAEpDgF,EAAchF,EAAM,OAAoE,IAAI,EAGlGh2B,GAAqB,CAAE,QAASw5B,EAAiB,EAGjD,MAAM1mB,EAAiBjY,EAAgB,kBAAA,EAOjCogC,EAAcjF,EAAM,YACvBt1B,IAAkB,CAMjB,GAAI,EADkB44B,GAAe54B,GAAE,SAAWA,GAAE,SAChC,OAGpBA,GAAE,eAAA,EAEF,MAAMw6B,GAAYZ,GAAa,QAC/B,GAAI,CAACY,GAAW,OAGhB,MAAM95B,GAAO85B,GAAU,sBAAA,EACjBC,GAAUz6B,GAAE,QAAUU,GAAK,KAC3Bg6B,GAAU16B,GAAE,QAAUU,GAAK,IAG3Bi6B,GAAQ,CAAC36B,GAAE,OAASu4B,GAE1B,GAAIK,EAAa,CAGf,MAAMgC,GAAUf,GACVgB,GAAU,KAAK,IAAI,KAAK,IAAID,IAAW,EAAID,IAAQtC,EAAQ,EAAGC,EAAQ,EAC5E,GAAIuC,KAAYD,GAAS,OACzB,MAAME,GAAYD,GAAUD,GACtBG,GAAUN,IAAWA,GAAUxR,EAAU,GAAK6R,GAC9CE,GAAUN,IAAWA,GAAUzR,EAAU,GAAK6R,GACpDhB,GAAkBe,EAAO,EACzB3R,EAAa,CAAE,EAAG6R,GAAS,EAAGC,GAAS,CACzC,KAAO,CAKL,MAAMC,GAAclS,EACdyC,GAAc,KAAK,IACvB,KAAK,IAAIyP,IAAe,EAAIN,IAAQxC,EAAa,EACjDC,EAAA,EAEF,GAAI5M,KAAgByP,GAAa,OAGjC,MAAM10C,GAAQilC,GAAcyP,GACtBF,GAAUN,IAAWA,GAAUxR,EAAU,GAAK1iC,GAC9Cy0C,GAAUN,IAAWA,GAAUzR,EAAU,GAAK1iC,GACpDyiC,EAAYwC,EAAW,EACvBtC,EAAa,CAAE,EAAG6R,GAAS,EAAGC,GAAS,CACzC,CACF,EACA,CAACpC,EAAaiB,GAAgB5Q,EAAWC,EAAcH,EAAUC,CAAW,CAAA,EAM9EsM,EAAM,UAAU,IAAM,CACpB,MAAMkF,GAAYZ,GAAa,QAC/B,GAAKY,GAEL,OAAAA,GAAU,iBAAiB,QAASD,EAAa,CAAE,QAAS,GAAO,EAC5D,IAAMC,GAAU,oBAAoB,QAASD,CAAW,CACjE,EAAG,CAACA,CAAW,CAAC,EAGhBjF,EAAM,UAAU,IAAM,CACpB,GAAI,CAACsD,EAAa,OAElB,MAAM74B,GAAiBC,IAAqB,CACtCA,GAAE,OAAS,SAAW,CAACA,GAAE,QAC3Bq6B,GAAe,EAAI,CAEvB,EAEMh6B,GAAeL,IAAqB,CACpCA,GAAE,OAAS,UACbq6B,GAAe,EAAK,EACpBC,EAAY,QAAU,KAE1B,EAEA,cAAO,iBAAiB,UAAWv6B,EAAa,EAChD,OAAO,iBAAiB,QAASM,EAAW,EAErC,IAAM,CACX,OAAO,oBAAoB,UAAWN,EAAa,EACnD,OAAO,oBAAoB,QAASM,EAAW,CACjD,CACF,EAAG,CAACu4B,CAAW,CAAC,EAGhB,MAAMsC,EAAiB5F,EAAM,YAC1Bt1B,IAA0B,CACzB,GAAI,CAAC44B,EAAa,OAIlB,MAAMuC,GAAgBn7B,GAAE,SAAW,EAC7Bo7B,GAAahB,IAAep6B,GAAE,SAAW,GAE3Cm7B,IAAiBC,MACnBp7B,GAAE,eAAA,EACDA,GAAE,OAAuB,kBAAkBA,GAAE,SAAS,EACvDm5B,EAAa,EAAI,EACjBmB,EAAY,QAAU,CACpB,EAAGt6B,GAAE,QACL,EAAGA,GAAE,QACL,KAAMipB,EAAU,EAChB,KAAMA,EAAU,CAAA,EAGtB,EACA,CAAC2P,EAAawB,GAAanR,EAAWkQ,CAAY,CAAA,EAG9CkC,GAAgB/F,EAAM,YACzBt1B,IAA0B,CACzB,GAAI,CAACk5B,IAAa,CAACoB,EAAY,QAAS,OAExC,MAAMp6B,GAAKF,GAAE,QAAUs6B,EAAY,QAAQ,EACrCn6B,GAAKH,GAAE,QAAUs6B,EAAY,QAAQ,EAE3CpR,EAAa,CACX,EAAGoR,EAAY,QAAQ,KAAOp6B,GAC9B,EAAGo6B,EAAY,QAAQ,KAAOn6B,EAAA,CAC/B,CACH,EACA,CAAC+4B,GAAWhQ,CAAY,CAAA,EAGpBoS,EAAehG,EAAM,YAAat1B,IAA2B,CAC7Dk5B,KACEl5B,IACDA,GAAE,OAAuB,sBAAsBA,GAAE,SAAS,EAE7Dm5B,EAAa,EAAK,EAClBmB,EAAY,QAAU,KAE1B,EAAG,CAACpB,GAAWC,CAAY,CAAC,EAG5B7D,EAAM,UAAU,IAAM,CACpB,GAAI,CAACsD,GAAe,CAACM,GAAW,OAEhC,MAAMqC,GAAwB,IAAM,CAClCpC,EAAa,EAAK,EAClBmB,EAAY,QAAU,IACxB,EAEA,cAAO,iBAAiB,YAAaiB,EAAqB,EACnD,IAAM,OAAO,oBAAoB,YAAaA,EAAqB,CAC5E,EAAG,CAAC3C,EAAaM,GAAWC,CAAY,CAAC,EAYzC,MAAMqC,GAAoBlG,EAAM,QAAQ,IAAM,CAC5C,GAAI,CAACN,GAAc,CAAC5d,EAAa,OAAO,KACxC,MAAMD,GAASC,EAAY,QAAU,CAAA,EACrC,GAAID,GAAO,QAAU,EAAG,OAAO,KAM/B,GAAI6d,IAAe,SACjB,OAAK5iB,EACE,CACL,EAAG,EACH,EAAG,EACH,MAAOA,EAAe,MACtB,OAAQA,EAAe,MAAA,EALG,KAQ9B,MAAMhpB,GAAQ+tB,GAAO,KAAMjR,IAAMA,GAAE,KAAO8uB,EAAW,OAAO,EAC5D,OAAK5rC,GACE,CAAE,EAAGA,GAAM,EAAG,EAAGA,GAAM,EAAG,MAAOA,GAAM,MAAO,OAAQA,GAAM,MAAA,EADhD,IAErB,EAAG,CAAC4rC,EAAY5d,EAAahF,CAAc,CAAC,EACtCqpB,GAAej7B,GAAqBg7B,EAAiB,EAI3DlG,EAAM,UAAU,IAAM,CACpB,GAAIoD,IAAa,OAAW,CAE1BoB,GAAkBpB,CAAQ,EAC1B,MACF,CAEA,GAAI,CAACkB,GAAa,SAAW,CAACxnB,EAC5B,OAGF,IAAI2hB,GAAgB,EAEpB,MAAM2H,GAAmB,IAAM,CAC7B,GAAI,CAAC9B,GAAa,SAAW,CAACxnB,EAAgB,OAG9C,MAAM8Z,GAAiB0N,GAAa,QAAQ,YAG5C,GAAI1N,KAAmB,EACrB,OAUF,IAAIyP,GAAWvpB,EAAe,MAC1BwpB,GAAYxpB,EAAe,OAC/B,GAAI4iB,GAAcA,IAAe,UAAY5d,EAAa,CACxD,MAAMD,GAASC,EAAY,QAAU,CAAA,EACrC,GAAID,GAAO,OAAS,EAAG,CACrB,MAAM/tB,GAAQ+tB,GAAO,KAAMjR,IAAMA,GAAE,KAAO8uB,EAAW,OAAO,EACxD5rC,KACFuyC,GAAWvyC,GAAM,MACjBwyC,GAAYxyC,GAAM,OAEtB,CACF,CAKA,IAAIyyC,GACAC,GAAsB,GAE1B,GAAItqB,IAAgB,OAAW,CAE7B,MAAM3Z,GAAiBq0B,GAAkB1a,EAAc,EACjDuqB,GAAe,KAAK,IAAIlkC,GAAiB8jC,GAAU,CAAG,EAE5D,GAAIF,IAAgB5C,IAAc,OAAW,CAS3C,MAAM/gC,GAAkB+gC,EAAarnB,EAAc,EAC7CwqB,GAAgB,KAAK,IAAIlkC,GAAkB8jC,GAAW,CAAG,EAC/DC,GAAU,KAAK,IAAIE,GAAcC,EAAa,EAC9CF,GAAsBE,IAAiBD,EACzC,SACEF,GAAUE,GACNlD,IAAc,QACa+C,GAAYC,GACSrqB,EAAc,EACxCqnB,EAAW,CAEjC,MAAMoD,IADkBpD,EAAarnB,EAAc,GACHoqB,GAChDC,GAAU,KAAK,IAAIA,GAASI,GAAuB,CAAG,EACtDH,GAAsB,EACxB,CAGN,MAEED,GAAU,KAAK,IAAK3P,GAAiByM,EAAcgD,GAAU,CAAG,EAGlE7B,GAAkB+B,EAAO,EACzB9B,GAAkB7N,EAAc,EAChC+N,EAAuB6B,EAAmB,CAC5C,EAMA,IAAII,GAAY,GACZ/B,GAAc,QAChBuB,GAAA,EAEA3H,GAAQ,sBAAsB,IAAM,CAClCA,GAAQ,sBAAsB,IAAM,CAC9BmI,KACJR,GAAA,EACAvB,GAAc,QAAU,GAC1B,CAAC,CACH,CAAC,EAIH,MAAMgC,GAAiB,IAAI,eAAe,IAAM,CAC9CT,GAAA,CACF,CAAC,EAED,OAAAS,GAAe,QAAQvC,GAAa,OAAO,EAEpC,IAAM,CACXsC,GAAY,GACZ,qBAAqBnI,EAAK,EAC1BoI,GAAe,WAAA,CACjB,CACF,EAAG,CAACzD,EAAUtmB,EAAgBumB,EAAYnnB,EAAaqnB,EAAW7D,EAAY5d,EAAaqkB,EAAY,CAAC,EAOxG,MAAMW,GACJvC,GAAiB,GAAK9Q,EAAW,EAAI8Q,GAAiB9Q,EAAW8Q,GAGnEvE,EAAM,UAAU,IAAM,CACpB4E,GAAkB,QAAUkC,GAC5BnD,EAAQmD,EAAa,CACvB,EAAG,CAACA,GAAenD,CAAO,CAAC,EAQ3B,MAAMoD,GAAwB/G,EAAM,OAAsB,IAAI,EAC9DA,EAAM,UAAU,IAAM,CACpB,MAAMhsC,GAAKmyC,IAAgB,OAAQA,GAAgBA,GAAiC,IAAM,KAAO,KAC7FnyC,KAAO+yC,GAAsB,UAC3BA,GAAsB,UAAY,MAEpCjD,GAAA,EAEFiD,GAAsB,QAAU/yC,GAEpC,EAAG,CAACmyC,GAAcrC,EAAa,CAAC,EAIhC9D,EAAM,UAAU,IAAM,CACfsD,GAGDI,IAAgBkB,GAAkB,SAAWlB,EAAc,IAC7Dc,GAAkBd,CAAW,EAC7BkB,GAAkB,QAAUlB,EAEhC,EAAG,CAACJ,EAAaI,CAAW,CAAC,EAK7B,MAAMsD,EAAeb,GAAeA,GAAa,OAAQrpB,GAAA,YAAAA,EAAgB,QAAS,EAC5EmqB,EAAgBd,GAAeA,GAAa,QAASrpB,GAAA,YAAAA,EAAgB,SAAU,EAE/EoqB,GAAcF,EAAeF,GAC7BK,GAAeF,EAAgBH,GAS/BM,GAAiBJ,EAAezC,GAKtC,IAAIjnB,GAAqBR,EACpBZ,IAAgB,OAAYirB,GAAgBjrB,EAAc,EAAKirB,GAAe9D,EAC/E,EAGAE,IAAc,QAAajmB,GAAqBimB,IAClDjmB,GAAqBimB,GAMvB,IAAItmB,GAAmBf,EACnBgB,GAAmBhB,EAGvB,MAAMmrB,KAAwB/nC,GAAAglC,GAAa,UAAb,YAAAhlC,GAAsB,cAAes3B,GAE/D1a,IAAgB,QAAawoB,GAAuB2C,GAAwB,GAAKD,GAAiB,IAKpGnqB,IAAoBoqB,GAAwBD,IAAkB,GAIhE,MAAMjc,GAAgB7N,GAQhBgqB,GAAmBtH,EAAM,OAAO,CAAE,EAAG,EAAG,EAAG,EAAG,EACpDA,EAAM,UAAU,IAAM,CACpB,MAAMkF,GAAYZ,GAAa,QAC/B,GAAI,CAACY,GAAW,OAChBoC,GAAiB,QAAU,CACzB,EAAGpC,GAAU,YACb,EAAGA,GAAU,YAAA,EAEf,MAAMqC,GAAW,IAAI,eAAgBC,IAAY,CAC/C,MAAMC,GAAQD,GAAQ,CAAC,EAClBC,KACLH,GAAiB,QAAU,CACzB,EAAGG,GAAM,YAAY,MACrB,EAAGA,GAAM,YAAY,MAAA,EAEzB,CAAC,EACD,OAAAF,GAAS,QAAQrC,EAAS,EACnB,IAAMqC,GAAS,WAAA,CACxB,EAAG,CAAA,CAAE,EAULvH,EAAM,UAAU,IAAM,CACpB,GAAIsD,EAAa,OAQjB,GAAI7P,GAAY,MAAO,EACjBE,EAAU,IAAM,GAAKA,EAAU,IAAM,IACvCC,EAAa,CAAE,EAAG,EAAG,EAAG,EAAG,EAE7B,MACF,CAEA,MAAM8T,GAAaJ,GAAiB,QAAQ,EACtCK,GAAaL,GAAiB,QAAQ,EAE5C,GADII,KAAe,GAAKC,KAAe,GACnCT,KAAgB,GAAKC,KAAiB,EAAG,OAE7C,MAAMS,GAAc,IAAO,KAAK,IAAIV,GAAaQ,EAAU,EACrDG,GAAc,IAAO,KAAK,IAAIV,GAAcQ,EAAU,EAMtDG,GAAUF,IAAe3qB,IAAoB,GAAKiqB,GAClDa,GAAUL,GAAaE,IAAe3qB,IAAoB,GAC1D+qB,GAAUH,IAAe3qB,IAAoB,GAAKiqB,GAClDc,GAAUN,GAAaE,IAAe3qB,IAAoB,GAE1DgrB,GAAW,KAAK,IAAIJ,GAAS,KAAK,IAAIC,GAASpU,EAAU,CAAC,CAAC,EAC3DwU,GAAW,KAAK,IAAIH,GAAS,KAAK,IAAIC,GAAStU,EAAU,CAAC,CAAC,GAE7DuU,KAAavU,EAAU,GAAKwU,KAAaxU,EAAU,IACrDC,EAAa,CAAE,EAAGsU,GAAU,EAAGC,GAAU,CAE7C,EAAG,CACD7E,EACA7P,EACAE,EACAuT,GACAC,GACAlqB,GACAC,GACA0W,CAAA,CACD,EAID,MAAMwU,GAAsC9E,EACxC,CAEE,SAAU,WACV,MAAOlwC,EAAQ,GAAGA,CAAK,KAAO,OAC9B,OAAQC,EAAS,GAAGA,CAAM,KAAO,OACjC,SAAU,SACV,OAAQuwC,GAAY,WAAakB,GAAc,OAAS,UACxD,YAAa,OACb,oBAAqB,OACrB,GAAG9mC,CAAA,EAEL,CAGE,SAAU,WACV,MAAO5K,EAAQ,GAAGA,CAAK,KAAO,OAC9B,OAAQC,EAAS,GAAGA,CAAM,KAAO83B,GAAgB,EAAI,GAAGA,EAAa,KAAO,OAC5E,SAAU,SACV,YAAa,OACb,oBAAqB,OACrB,GAAGntB,CAAA,EAOHqqC,GAAyC/E,EAC3C,CACE,SAAU,WACV,KAAM,EACN,IAAK,EACL,UAAW,aAAa3P,EAAU,CAAC,OAAOA,EAAU,CAAC,MACrD,gBAAiB,MAEjB,cAAeiQ,GAAY,OAAS,MAAA,EAEtC,CACE,UACEjQ,EAAU,IAAM,GAAKA,EAAU,IAAM,EACjC,aAAaA,EAAU,CAAC,OAAOA,EAAU,CAAC,MAC1C,OACN,gBAAiB,MAIjB,WAAYA,EAAU,IAAM,GAAKA,EAAU,IAAM,EAAI,YAAc,MAAA,EAGzE,OACEgO,GAAAA,KAAC,MAAA,CACC,IAAK2C,GACL,UAAAnB,EACA,MAAOiF,GACP,cAAe9E,EAAcsC,EAAiB,OAC9C,cAAetC,EAAcyC,GAAgB,OAC7C,YAAazC,EAAc0C,EAAe,OAC1C,eAAgB1C,EAAc0C,EAAe,OAC7C,gBAAiB1C,EAAc0C,EAAe,OAE9C,SAAA,CAAA7kC,GAAAA,IAAC,MAAA,CAAI,MAAOknC,GACV,SAAAlnC,GAAAA,IAACo+B,GAAA,CACC,IAAKkE,EACL,SAAAhgC,EACA,UAAA4Y,EACA,gBAAAxX,EACA,WAAAnB,GACA,eAAAC,GACA,gBAAiBoB,EACjB,YAAAqtB,EACA,qBAAAG,EACA,kBAAmBztB,GACnB,uBAAwBE,EACxB,oBAAqB++B,GACrB,sBAAuB,IAAMG,GAAyBt+B,IAAMA,GAAI,CAAC,EACjE,cAAeo+B,GACf,gBAAiBK,GACjB,YAAAtZ,GACA,UAAAR,EACA,MAAOzN,GAAA,YAAAA,EAAgB,MACvB,OAAQA,GAAA,YAAAA,EAAgB,OACxB,KAAMgqB,GACN,YAAazD,EACb,qBAAApnB,EACA,YAAAC,EACA,aAAce,GACd,aAAcC,GACd,mBAAAwN,EACA,cAAA+U,EACA,YAAA3d,EACA,WAAA4d,EACA,iBAAkByG,EAAA,CAAA,EAEtB,EACC,CAAC7C,IAAgB7P,EAAW,MAASA,EAAW,QAC/CtyB,GAAAA,IAACmnC,GAAA,CACC,SAAA7U,EACA,UAAYx5B,IAAW,CAGrB,GAAIA,IAAU,MAAO,CACnB6pC,GAAA,EACA,MACF,CACA,MAAMoB,GAAYZ,GAAa,QAC/B,GAAI,CAACY,GAAW,CACdxR,EAAYz5B,EAAM,EAClB,MACF,CACA,MAAMmR,GAAO85B,GAAU,sBAAA,EAGjBqD,GAAKn9B,GAAK,MAAQ,EAClBo9B,GAAKp9B,GAAK,OAAS,EACnBna,GAAQgJ,GAAS,KAAK,IAAIw5B,EAAU,IAAM,EAChDC,EAAYz5B,EAAM,EAClB25B,EAAa,CACX,EAAG2U,IAAMA,GAAK5U,EAAU,GAAK1iC,GAC7B,EAAGu3C,IAAMA,GAAK7U,EAAU,GAAK1iC,EAAA,CAC9B,CACH,CAAA,CAAA,CACF,CAAA,CAAA,CAIR,EASMw3C,GAAe,CAAC,EAAG,EAAG,EAAG,CAAC,EAEhC,SAASH,GAAS,CAChB,SAAA7U,EACA,UAAAiV,CACF,EAGG,CACD,MAAMC,EAAM,KAAK,MAAMlV,EAAW,GAAG,EAC/B,CAACmV,EAAUC,CAAW,EAAI7I,EAAM,SAAS,EAAK,EACpD,OACE2B,GAAAA,KAAC,MAAA,CACC,MAAO,CACL,SAAU,WACV,OAAQ,GACR,MAAO,GACP,OAAQ,GACR,cAAe,MAAA,EAIjB,0BAAuB,GAEtB,SAAA,CAAAiH,GACCjH,GAAAA,KAAAC,YAAA,CAIE,SAAA,CAAAzgC,GAAAA,IAAC,MAAA,CACC,QAAS,IAAM0nC,EAAY,EAAK,EAChC,MAAO,CACL,SAAU,QACV,MAAO,EACP,OAAQ,EAAA,CACV,CAAA,EAGF1nC,GAAAA,IAAC,MAAA,CACC,KAAK,OACL,MAAO,CACL,SAAU,WACV,OAAQ,mBACR,MAAO,EACP,OAAQ,GACR,SAAU,GACV,eAAgB,WAAA,EAElB,UAAU,gFAET,SAAAsnC,GAAa,IAAKloC,GAAW,CAC5B,MAAMuoC,EAAYvoC,EAAS,IACrBwoC,EAAS,KAAK,IAAItV,EAAWlzB,CAAM,EAAI,IAC7C,OACEohC,GAAAA,KAAC,SAAA,CAEC,KAAK,SACL,KAAK,WACL,QAAS,IAAM,CACbkH,EAAY,EAAK,EACjBH,EAAUnoC,CAAM,CAClB,EACA,UACEwoC,EACI,sGACA,oIAGL,SAAA,CAAAD,EAAU,GAAA,CAAA,EAbNvoC,CAAA,CAgBX,CAAC,CAAA,CAAA,CACH,EACF,EAEFY,GAAAA,IAAC,MAAA,CACC,UAAU,2FACV,MAAO,CAAE,eAAgB,WAAA,EAEzB,SAAAwgC,GAAAA,KAAC,SAAA,CACC,KAAK,SACL,QAAS,IAAMkH,EAAaG,GAAS,CAACA,CAAI,EAC1C,gBAAc,OACd,gBAAeJ,EACf,aAAW,aACX,UAAU,uGACV,MAAO,CAAE,SAAU,EAAA,EAElB,SAAA,CAAAD,EAAI,GAAA,CAAA,CAAA,CACP,CAAA,CACF,CAAA,CAAA,CAGN,CC34BA,MAAMnnC,GAASC,EAAAA,aAAa,cAAc,EAiF7BwnC,GAAe,IAA0B,CACpD,KAAM,CACJ,UAAA5sB,EACA,gBAAAxX,EACA,iBAAAqkC,EACA,cAAAC,EACA,kBAAAnkC,CAAA,EACEI,aAAA,EAEEI,EAAmBX,EAAgB,oBAAA,EACnCiY,EAAiBjY,EAAgB,kBAAA,EAGjCukC,EAAgBloC,EAAAA,QAAQ,IACrBmb,EAAU,IAAKgtB,GAAa,CAGjC,MAAMC,EADazkC,EAAgB,sBAAsBwkC,EAAS,EAAE,EACpC,OAEhC,MAAO,CACL,GAAIA,EAAS,GACb,KAAMA,EAAS,KACf,EAAGA,EAAS,EACZ,EAAGA,EAAS,EACZ,MAAOA,EAAS,MAChB,OAAQA,EAAS,OACjB,gBAAiBA,EAAS,gBAC1B,UAAWA,EAAS,UACpB,SAAUA,EAAS,KAAO7jC,EAC1B,aAAA8jC,CAAA,CAEJ,CAAC,EACA,CAACjtB,EAAWxX,EAAiBW,CAAgB,CAAC,EAG3C+jC,EAAqBroC,EAAAA,QAAQ,IAAM,CACvC,GAAI,CAAC4b,EAAgB,OAAO,KAE5B,MAAMwsB,EADazkC,EAAgB,sBAAsBiY,EAAe,EAAE,EAC1C,OAChC,MAAO,CACL,GAAIA,EAAe,GACnB,KAAMA,EAAe,KACrB,EAAGA,EAAe,EAClB,EAAGA,EAAe,EAClB,MAAOA,EAAe,MACtB,OAAQA,EAAe,OACvB,gBAAiBA,EAAe,gBAChC,UAAWA,EAAe,UAC1B,SAAU,GACV,aAAAwsB,CAAA,CAEJ,EAAG,CAACxsB,EAAgBjY,CAAe,CAAC,EAG9B2kC,EAAiBvoC,EAAAA,YAAY,CACjC7N,EAAQ,KACRC,EAAS,KACTwB,IAOG,CAEH,MAAM40C,EAAiB,CACrB,MAAAr2C,EACA,OAAAC,EACA,GAAGwB,CAAA,EAGLgQ,EAAgB,eAAe4kC,CAAc,EAC7CP,EAAA,CACF,EAAG,CAACrkC,EAAiBqkC,CAAgB,CAAC,EAGhCQ,EAAiBzoC,cAAa+F,GAAuB,CACzDnC,EAAgB,eAAemC,CAAU,EACzCkiC,EAAA,CACF,EAAG,CAACrkC,EAAiBqkC,CAAgB,CAAC,EAGhCS,EAAoB1oC,cAAa+F,GAAuB,CAC5D,MAAMqiC,EAAWhtB,EAAU,KAAKutB,GAAMA,EAAG,KAAO5iC,CAAU,EAC1D,GAAI,CAACqiC,EAAU,CACb7nC,GAAO,KAAK,2BAA2BwF,CAAU,YAAY,EAC7D,MACF,CAGAnC,EAAgB,eAAe,CAC7B,MAAOwkC,EAAS,MAChB,OAAQA,EAAS,OACjB,KAAM,GAAGA,EAAS,IAAI,QACtB,gBAAiBA,EAAS,gBAC1B,UAAWA,EAAS,SAAA,CACrB,EAKDH,EAAA,CACF,EAAG,CAAC7sB,EAAWxX,EAAiBqkC,CAAgB,CAAC,EAG3CW,EAAiB5oC,EAAAA,YAAY,CAAC+F,EAAoB8iC,IAAoB,CAC1E,MAAMT,EAAWhtB,EAAU,KAAKutB,GAAMA,EAAG,KAAO5iC,CAAU,EAC1D,GAAI,CAACqiC,EAAU,CACb7nC,GAAO,KAAK,2BAA2BwF,CAAU,YAAY,EAC7D,MACF,CAEAqiC,EAAS,KAAOS,EAChBZ,EAAA,CACF,EAAG,CAAC7sB,EAAW6sB,CAAgB,CAAC,EAG1Ba,EAAiB9oC,EAAAA,YAAY,CACjC+F,EACAgjC,IAQG,CACH,MAAMX,EAAWhtB,EAAU,KAAKutB,GAAMA,EAAG,KAAO5iC,CAAU,EAC1D,GAAI,CAACqiC,EAAU,CACb7nC,GAAO,KAAK,2BAA2BwF,CAAU,YAAY,EAC7D,MACF,CAEIgjC,EAAQ,OAAS,SAAWX,EAAS,KAAOW,EAAQ,MACpDA,EAAQ,QAAU,SAAWX,EAAS,MAAQW,EAAQ,OACtDA,EAAQ,SAAW,SAAWX,EAAS,OAASW,EAAQ,QACxDA,EAAQ,kBAAoB,SAAWX,EAAS,gBAAkBW,EAAQ,iBAC1EA,EAAQ,YAAc,SAAWX,EAAS,UAAYW,EAAQ,WAC9D,oBAAqBA,IAASX,EAAS,gBAAkBW,EAAQ,gBAAkB,CAAE,GAAGA,EAAQ,eAAA,EAAoB,QAExHd,EAAA,CACF,EAAG,CAAC7sB,EAAW6sB,CAAgB,CAAC,EAG1Be,EAAiBhpC,cAAa+F,GAAuB,CAIzDmiC,EAAc,IAAI,EAClBnkC,EAAkB,CAAA,CAAE,EAEpBH,EAAgB,kBAAkBmC,CAAU,EAC5CkiC,EAAA,CACF,EAAG,CAACrkC,EAAiBqkC,EAAkBC,EAAenkC,CAAiB,CAAC,EAGlEklC,EAAmBjpC,EAAAA,YAAY,CAACiG,EAAmBC,IAAoB,CACvED,IAAcC,IACdD,EAAY,GAAKA,GAAamV,EAAU,QACxClV,EAAU,GAAKA,GAAWkV,EAAU,SAEtB,CAAC,GAAGA,CAAS,EAI/BxX,EAAgB,iBAAiBqC,EAAWC,CAAO,EACnD+hC,EAAA,GACF,EAAG,CAAC7sB,EAAWxX,EAAiBqkC,CAAgB,CAAC,EAEjD,MAAO,CACL,UAAA7sB,EACA,cAAA+sB,EACA,eAAAtsB,EACA,iBAAAtX,EACA,mBAAA+jC,EACA,eAAAC,EACA,eAAAE,EACA,kBAAAC,EACA,eAAAE,EACA,eAAAE,EACA,eAAAE,EACA,iBAAAC,EACA,MAAO7tB,EAAU,MAAA,CAErB,EC3QM7a,GAASC,EAAAA,aAAa,WAAW,EAgHhC,SAAS0oC,IAA6B,CAC3C,KAAM,CACJ,UAAA9tB,EACA,gBAAAxX,EACA,SAAApB,EACA,UAAA8mB,CAAA,EACEnlB,aAAA,EAEE,CAACglC,EAAaC,CAAc,EAAIvrC,EAAAA,SAAS,EAAK,EAC9C,CAACwrC,EAAuBC,CAAwB,EAAIzrC,EAAAA,SAAS,EAAK,EAClE,CAACmsB,EAAUuf,CAAW,EAAI1rC,EAAAA,SAA2B,CAAA,CAAE,EACvD,CAAC2rC,EAAeC,CAAgB,EAAI5rC,EAAAA,SAAiC,CAAA,CAAE,EACvE,CAAC6rC,EAAaC,CAAc,EAAI9rC,WAAS,CAC7C,aAAc,EACd,cAAe,EACf,eAAgB,EAAA,CACjB,EAKK+rC,EAA6BxlC,EAAAA,OAAO,CAAC,EACrCylC,EAA0BzlC,EAAAA,OAAuC,IAAI,EAGrE0lC,EAAuBC,GAAAA,cAAc,qBAAA,EAKrCC,EAAiBhqC,EAAAA,YAAY,MACjC+F,EACAnF,EAA8B,CAAA,IACV,CACpB,MAAMwnC,EAAWhtB,EAAU,KAAKutB,GAAMA,EAAG,KAAO5iC,CAAU,EAC1D,GAAI,CAACqiC,EACH,MAAM,IAAI,MAAM,wBAAwBriC,CAAU,YAAY,EAGhE,GAAI,CAACujB,EAAU,QACb,MAAM,IAAI,MAAM,sCAAsC,EAGxD8f,EAAe,EAAI,EAGnB,MAAMa,GAAgC,CACpC,WAAAlkC,EACA,aAAcqiC,EAAS,KACvB,OAAQ,YACR,SAAU,EACV,UAAW,KAAK,IAAA,CAAI,EAEtBmB,EAAYnW,GAAQ,CAAC,GAAGA,EAAM6W,EAAa,CAAC,EAE5C,GAAI,CACF,MAAMlzC,EAAY,KAAK,IAAA,EAGjBmzC,EAAkB3K,EAAY,QAC9Bz6B,EAAalB,EAAgB,sBAAsBmC,CAAU,EAC7DokC,GAAmBD,EAAgB,OAAOnnC,IAAM+B,EAAW,SAAS/B,GAAG,EAAE,CAAC,EAG1E0P,GAAU,MAAMs3B,GAAAA,cAAc,wBAClC3B,EACA+B,GACA7gB,EAAU,QACV,CACE,OAAQ1oB,EAAQ,QAAU,MAC1B,MAAOA,EAAQ,OAAS,EACxB,sBAAuBA,EAAQ,uBAAyB,GACxD,GAAGA,CAAA,CACL,EAGIwpC,GAAa,KAAK,IAAA,EAAQrzC,EAGhC,OAAAwyC,MAAoBnW,GAAK,OACvBzjB,EAAE,aAAe5J,EACb,CAAE,GAAG4J,EAAG,OAAQ,WAAY,SAAU,IAAK,QAAA8C,GAAS,UAAW,KAAK,IAAA,GACpE9C,CAAA,CACe,EAGrB85B,EAAiBrW,KAAS,CAAE,GAAGA,GAAM,CAACrtB,CAAU,EAAG0M,IAAU,EAG7Dk3B,EAAevW,KAAS,CACtB,aAAcA,GAAK,aAAe,EAClC,eAAgBA,GAAK,cAAgBA,GAAK,aAAegX,KAAehX,GAAK,aAAe,GAC5F,eAAgB0W,CAAA,EAChB,EAEKr3B,EACT,OAASja,EAAO,CAEd,MAAA+wC,KAAoBnW,EAAK,IAAIzjB,GAC3BA,EAAE,aAAe5J,EACb,CAAE,GAAG4J,EAAG,OAAQ,QAAS,MAAAnX,EAAuB,UAAW,KAAK,IAAA,GAChEmX,CAAA,CACL,EACKnX,CACR,QAAA,CACE4wC,EAAe,EAAK,CACtB,CACF,EAAG,CAAChuB,EAAWxX,EAAiBpB,EAAU8mB,EAAWwgB,CAAoB,CAAC,EAKpEO,EAAqBrqC,EAAAA,YAAY,MACrCY,EAA8B,CAAA,IACM,CACpCwoC,EAAe,EAAI,EAEnB,GAAI,CAEF,MAAMkB,EAAiBlvB,EAAU,IAAI,MAAOgtB,GAAa,CACvD,MAAM31B,EAAU,MAAMu3B,EAAe5B,EAAS,GAAIxnC,CAAO,EACzD,MAAO,CAAE,GAAIwnC,EAAS,GAAI,QAAA31B,CAAA,CAC5B,CAAC,EAEK83B,EAAgB,MAAM,QAAQ,IAAID,CAAc,EAGhDh2C,GAAkC,CAAA,EACxC,SAAW,CAAE,GAAAvB,EAAI,QAAA0f,CAAA,IAAa83B,EAC5Bj2C,GAAQvB,CAAE,EAAI0f,EAGhB,OAAOne,EACT,QAAA,CACE80C,EAAe,EAAK,CACtB,CACF,EAAG,CAAChuB,EAAW4uB,CAAc,CAAC,EAQxBzK,EAAcn7B,EAAAA,OAAO5B,CAAQ,EACnC+8B,EAAY,QAAU/8B,EAEtB,MAAMgoC,EAAuBxqC,EAAAA,YAAY,MACvC+F,EACAnF,EAA8B,CAAA,IACZ,CAClB,MAAMwnC,EAAWhtB,EAAU,KAAKutB,IAAMA,GAAG,KAAO5iC,CAAU,EAC1D,GAAI,CAACqiC,EACH,MAAM,IAAI,MAAM,wBAAwBriC,CAAU,YAAY,EAGhE,GAAI,CAACujB,EAAU,QACb,MAAM,IAAI,MAAM,sCAAsC,EAGxD8f,EAAe,EAAI,EAEnB,GAAI,CAEF,MAAMc,GAAkB3K,EAAY,QAC9Bz6B,EAAalB,EAAgB,sBAAsBmC,CAAU,EAC7DokC,EAAmBD,GAAgB,OAAOnnC,IAAM+B,EAAW,SAAS/B,GAAG,EAAE,CAAC,EAehF,OAZa,MAAMgnC,GAAAA,cAAc,qBAC/B3B,EACA+B,EACA7gB,EAAU,QACV,CACE,OAAQ1oB,EAAQ,QAAU,MAC1B,MAAOA,EAAQ,OAAS,EACxB,sBAAuBA,EAAQ,uBAAyB,GACxD,GAAGA,CAAA,CACL,CAIJ,QAAA,CACEwoC,EAAe,EAAK,CACtB,CACF,EAAG,CAAChuB,EAAWxX,EAAiBpB,EAAU8mB,CAAS,CAAC,EAK9CmhB,EAA4BzqC,EAAAA,YAAY,MAC5CY,EAA8B,CAAA,IACI,CAClCwoC,EAAe,EAAI,EAEnB,GAAI,CAEF,MAAMkB,EAAiBlvB,EAAU,IAAI,MAAOgtB,GAAa,CACvD,MAAMsC,EAAO,MAAMF,EAAqBpC,EAAS,GAAIxnC,CAAO,EAC5D,MAAO,CAAE,GAAIwnC,EAAS,GAAI,KAAAsC,CAAA,CAC5B,CAAC,EAEKH,EAAgB,MAAM,QAAQ,IAAID,CAAc,EAGhDh2C,GAAgC,CAAA,EACtC,SAAW,CAAE,GAAAvB,EAAI,KAAA23C,CAAA,IAAUH,EACzBj2C,GAAQvB,CAAE,EAAI23C,EAGhB,OAAOp2C,EACT,QAAA,CACE80C,EAAe,EAAK,CACtB,CACF,EAAG,CAAChuB,EAAWovB,CAAoB,CAAC,EAK9BG,EAAuB3qC,EAAAA,YAAY,IAAM,CAE7C4pC,EAA2B,UAC3BN,EAAyB,EAAK,EAC9BO,EAAwB,QAAU,IACpC,EAAG,CAAA,CAAE,EAQCe,EAAwB5qC,cAAaY,GAAqC,CAE9EgpC,EAA2B,UAC3B,MAAMiB,EAAejB,EAA2B,QAGhDC,EAAwB,QAAUjpC,EAClC0oC,EAAyB,EAAI,EAE7B,KAAM,CACJ,YAAA3iC,EACA,SAAAmkC,GAAW,IACX,MAAAtpC,EAAQ,EACR,OAAAupC,EAAS,MACT,SAAAC,CAAA,EACEpqC,EAGEqqC,GAAkBtkC,EACpByU,EAAU,OAAOutB,GAAMhiC,EAAY,SAASgiC,EAAG,EAAE,CAAC,EAClDvtB,EAGE8vB,GAAsB,IAC1BtB,EAA2B,UAAYiB,EAGnCM,GAAgB,SAAY,CAChC,GAAI,CACF,MAAMb,EAAiBW,GAAgB,IAAI,MAAO7C,IAAa,CAC7D,MAAM31B,GAAU,MAAMu3B,EAAe5B,GAAS,GAAI,CAChD,MAAA5mC,EACA,OAAAupC,EACA,sBAAuB,EAAA,CACxB,EACD,MAAO,CAAE,GAAI3C,GAAS,GAAI,QAAA31B,EAAA,CAC5B,CAAC,EAEK83B,GAAgB,MAAM,QAAQ,IAAID,CAAc,EAEhDh2C,GAAkC,CAAA,EACxC,SAAW,CAAE,GAAAvB,GAAI,QAAA0f,EAAA,IAAa83B,GAC5Bj2C,GAAQvB,EAAE,EAAI0f,GAGhBg3B,OAA0B,CAAE,GAAGrW,GAAM,GAAG9+B,IAAU,EAE9C02C,GACFA,EAAS12C,EAAO,CAEpB,OAASkE,EAAO,CACd+H,GAAO,MAAM,uCAAwC/H,CAAK,CAC5D,CACF,GAKgB,SAAY,CAE1B,IADA,MAAM2yC,GAAA,EACCD,OACL,MAAM,IAAI,QAAc/+C,GAAW,WAAWA,EAAS2+C,EAAQ,CAAC,EAC5D,EAACI,OACL,MAAMC,GAAA,CAEV,GAEA,CACF,EAAG,CAAC/vB,EAAW4uB,CAAc,CAAC,EAG9B1rC,OAAAA,EAAAA,UAAU,IACD,IAAM,CACXqsC,EAAA,CACF,EACC,CAACA,CAAoB,CAAC,EAElB,CACL,eAAAX,EACA,qBAAAQ,EACA,mBAAAH,EACA,0BAAAI,EACA,sBAAAG,EACA,qBAAAD,EACA,YAAAxB,EACA,sBAAAE,EACA,SAAArf,EACA,cAAAwf,EACA,qBAAAM,EACA,YAAAJ,CAAA,CAEJ,CC3bA,MAAMnpC,GAASC,EAAAA,aAAa,mBAAmB,EAmBlC4qC,GAA+C,CAC1D,QAAS,GACT,WAAY,IACZ,UAAW,GACb,EAoCO,MAAMC,EAAkB,CAoB7B,YAAYz3C,EAAoC,GAAI,CAjBpD,KAAQ,iBAAwD,KAChE,KAAQ,0BAAiD,KAIzD,KAAQ,SAAW,EACnB,KAAQ,qBAAuB,GAG/B,KAAQ,MAAyB,CAC/B,aAAc,EACd,eAAgB,EAChB,eAAgB,KAChB,kBAAmB,CAAA,EAErB,KAAQ,iBAA6B,CAAA,EAGnC,KAAK,OAAS,CAAE,GAAGw3C,GAA4B,GAAGx3C,CAAA,EAGlD,KAAK,gBAAkB03C,GAAAA,SACrB,IAAM,KAAK,cAAA,EACX,KAAK,OAAO,WACZ,CACE,QAAS,KAAK,OAAO,UACrB,QAAS,GACT,SAAU,EAAA,CACZ,CAEJ,CAMA,gBAAuB,CACrB,MAAM1N,EAAY,KAAK,IAAA,EAEnB,CAAC,KAAK,OAAO,UAKjB,KAAK,YAKuB,KAAK,MAAM,eAAiBA,EAAY,KAAK,MAAM,eAAiB,KACtE,OAOtB,KAAK,2BACP,KAAK,0BAAA,EAIP,KAAK,gBAAA,EACP,CAKA,MAAM,aAA6B,CAEjC,KAAK,gBAAgB,OAAA,EAGrB,KAAK,WAGL,MAAM,KAAK,cAAA,CACb,CAaA,MAAc,eAA+B,CAI3C,GAAI,CAAC,KAAK,OAAO,QACf,OAIF,GAAI,KAAK,WAAa,KAAK,qBAAsB,CAC/C,KAAK,MAAM,iBACX,MACF,CAGA,MAAM2N,EAAoB,KAAK,SAEzB7gC,EAAM,KAAK,IAAA,EAWjB,GARA,KAAK,iBAAiB,KAAKA,CAAG,EAG1B,KAAK,iBAAiB,OAAS,IACjC,KAAK,iBAAiB,MAAA,EAIpB,KAAK,iBAAiB,OAAS,EAAG,CACpC,MAAM8gC,EAAY,CAAA,EAClB,QAASn/B,EAAI,EAAGA,EAAI,KAAK,iBAAiB,OAAQA,IAChDm/B,EAAU,KAAK,KAAK,iBAAiBn/B,CAAC,EAAI,KAAK,iBAAiBA,EAAI,CAAC,CAAC,EAExE,KAAK,MAAM,kBAAoBm/B,EAAU,OAAO,CAACv1C,EAAGC,IAAMD,EAAIC,EAAG,CAAC,EAAIs1C,EAAU,MAClF,CAEA,KAAK,MAAM,eACX,KAAK,MAAM,eAAiB9gC,EAMxB,KAAK,iBAKP,WAAW,SAAY,CACrB,MAAMjd,EAAW,KAAK,iBACtB,GAAKA,EACL,IAAI,CACF,MAAMA,EAAA,CACR,OAAS+K,EAAO,CACd+H,GAAO,MAAM,iBAAkB/H,CAAK,CACtC,CACA,KAAK,qBAAuB+yC,EAExB,KAAK,SAAWA,GAAqB,KAAK,OAAO,SACnD,KAAK,gBAAA,EAET,EAAG,CAAC,EAEJhrC,GAAO,KAAK,+BAA+B,CAE/C,CAQA,SAAS9S,EAA4C,CACnD,KAAK,iBAAmBA,CAC1B,CAOA,kBAAkBA,EAA4B,CAC5C,KAAK,0BAA4BA,CACnC,CAOA,aAAag+C,EAA4C,CACvD,MAAMC,EAAY,CAAE,GAAG,KAAK,MAAA,EAOxBA,EAAU,SAAWD,EAAU,UAAY,IAC7C,KAAK,gBAAgB,MAAA,EAGvB,KAAK,OAAS,CAAE,GAAG,KAAK,OAAQ,GAAGA,CAAA,GAG/BC,EAAU,aAAe,KAAK,OAAO,YAAcA,EAAU,YAAc,KAAK,OAAO,aAEzF,KAAK,gBAAgB,OAAA,EAGrB,KAAK,gBAAkBJ,GAAAA,SACrB,IAAM,KAAK,cAAA,EACX,KAAK,OAAO,WACZ,CACE,QAAS,KAAK,OAAO,UACrB,QAAS,GACT,SAAU,EAAA,CACZ,EAIN,CAKA,UAA4B,CAC1B,MAAO,CAAE,GAAG,KAAK,KAAA,CACnB,CAKA,YAAmB,CACjB,KAAK,MAAQ,CACX,aAAc,EACd,eAAgB,EAChB,eAAgB,KAChB,kBAAmB,CAAA,EAErB,KAAK,iBAAmB,CAAA,CAC1B,CAKA,sBAA6B,CAC3B,KAAK,qBAAuB,EAC9B,CAKA,SAAgB,CAEd,KAAK,gBAAgB,OAAA,EAGrB,KAAK,iBAAmB,KACxB,KAAK,0BAA4B,KAGjC,KAAK,qBAAuB,EAE9B,CACF,CCrRO,SAASK,GAAe,CAAE,MAAAx5C,EAAQ,IAAK,OAAAC,EAAS,IAAK,MAAA2K,GAA8B,CACxF,OACE2jC,GAAAA,KAAC,MAAA,CACC,UAAU,kBACV,MAAO,CACL,MAAAvuC,EACA,OAAAC,EACA,gBAAiB,iBACjB,aAAc,MACd,QAAS,OACT,WAAY,SACZ,eAAgB,SAChB,SAAU,WACV,SAAU,SACV,GAAG2K,CAAA,EAIL,SAAA,CAAAmD,GAAAA,IAAC,MAAA,CACC,MAAO,CACL,SAAU,WACV,IAAK,EACL,KAAM,QACN,MAAO,OACP,OAAQ,OACR,WAAY,0EACZ,UAAW,qBAAA,CACb,CAAA,EAIFA,GAAAA,IAAC,MAAA,CAAI,MAAO,CAAE,SAAU,OAAQ,MAAO,eAAgB,WAAY,uBAAA,EAA2B,SAAA,mBAAA,CAE9F,SAEC,QAAA,CAAO,SAAA;AAAA;AAAA;AAAA;AAAA;AAAA,OAAA,CAKN,CAAA,CAAA,CAAA,CAGR,CAKO,SAAS0rC,GAAoB,CAAE,UAAAC,EAAY,EAAG,MAAA9uC,GAAmC,CACtF,OACE2jC,GAAAA,KAAC,MAAA,CACC,UAAU,wBACV,MAAO,CACL,MAAO,OACP,QAAS,OACT,gBAAiB,iBACjB,aAAc,MACd,GAAG3jC,CAAA,EAGJ,SAAA,CAAA,MAAM,KAAK,CAAE,OAAQ8uC,CAAA,CAAW,EAAE,IAAI,CAACC,EAAGn7C,IACzCuP,GAAAA,IAAC,MAAA,CAEC,UAAU,sBACV,MAAO,CACL,OAAQ,OACR,gBAAiB,iBACjB,aAAc,MACd,aAAcvP,EAAQk7C,EAAY,EAAI,MAAQ,EAC9C,SAAU,WACV,SAAU,QAAA,EAIZ,SAAA3rC,GAAAA,IAAC,MAAA,CACC,MAAO,CACL,SAAU,WACV,IAAK,EACL,KAAM,QACN,MAAO,OACP,OAAQ,OACR,WAAY,0EACZ,UAAW,sBACX,eAAgB,GAAGvP,EAAQ,EAAG,GAAA,CAChC,CAAA,CACF,EAvBKA,CAAA,CAyBR,SAEA,QAAA,CAAO,SAAA;AAAA;AAAA;AAAA;AAAA;AAAA,OAAA,CAKN,CAAA,CAAA,CAAA,CAGR,CAKO,SAASo7C,GAAgB,CAAE,MAAAhvC,GAA+B,CAC/D,OACE2jC,GAAAA,KAAC,MAAA,CACC,UAAU,mBACV,MAAO,CACL,OAAQ,OACR,QAAS,OACT,WAAY,SACZ,IAAK,MACL,QAAS,SACT,gBAAiB,iBACjB,aAAc,2BACd,GAAG3jC,CAAA,EAIJ,SAAA,CAAA,CAAC,GAAI,GAAI,GAAI,IAAK,GAAI,EAAE,EAAE,IAAI,CAAC5K,EAAOxB,IACrCuP,GAAAA,IAAC,MAAA,CAEC,MAAO,CACL,MAAO,GAAG/N,CAAK,KACf,OAAQ,OACR,gBAAiB,iBACjB,aAAc,MACd,SAAU,WACV,SAAU,QAAA,EAIZ,SAAA+N,GAAAA,IAAC,MAAA,CACC,MAAO,CACL,SAAU,WACV,IAAK,EACL,KAAM,QACN,MAAO,OACP,OAAQ,OACR,WAAY,0EACZ,UAAW,sBACX,eAAgB,GAAGvP,EAAQ,GAAI,GAAA,CACjC,CAAA,CACF,EAtBKA,CAAA,CAwBR,SAEA,QAAA,CAAO,SAAA;AAAA;AAAA;AAAA;AAAA;AAAA,OAAA,CAKN,CAAA,CAAA,CAAA,CAGR,CAKO,SAASq7C,GAAQ,CAAE,KAAAj8C,EAAO,GAAI,MAAAk5B,EAAQ,kBAAuD,CAClG,OACE/oB,GAAAA,IAAC,MAAA,CACC,UAAU,UACV,MAAO,CACL,MAAO,GAAGnQ,CAAI,KACd,OAAQ,GAAGA,CAAI,KACf,OAAQ,2BACR,eAAgBk5B,EAChB,aAAc,MACd,UAAW,2BAAA,EAGb,gBAAC,QAAA,CAAO,SAAA;AAAA;AAAA;AAAA;AAAA,OAAA,CAIN,CAAA,CAAA,CAGR,CCzLO,SAASgjB,GAAY5sC,EAAyC,OACnE,MAAM6sC,EAAmB,CAAA,EACnBC,EAAqB,CAAA,GAGvB,CAAC9sC,EAAI,MAAQA,EAAI,KAAK,KAAA,IAAW,KACnC6sC,EAAO,KAAK,sBAAsB,EAIhC7sC,EAAI,SAAS,UAAY,UAAUhB,EAAAgB,EAAI,OAAO,MAAM,SAAjB,MAAAhB,EAAyB,SAC9D8tC,EAAS,KAAK,kFAAkF,EAI9F9sC,EAAI,SAAS,YAAc,SAAW,CAACA,EAAI,aAAa,MAAM,SAAS,QAAQ,GACjF8sC,EAAS,KAAK,+DAA+D,EAI3E9sC,EAAI,aAAa,SAAS,SAAW,GACvC6sC,EAAO,KAAK,oEAAoE,EAG9E7sC,EAAI,aAAa,WAAW,SAAW,GACzC6sC,EAAO,KAAK,wEAAwE,EAIlF7sC,EAAI,SAAS,YAAc,CAACA,EAAI,aAAa,SAAS,QACxD8sC,EAAS,KAAK,mFAAmF,EAInG,MAAMC,EAAa,CAAC,GAAI/sC,EAAI,OAAO,MAAM,MAAQ,CAAA,EAAK,GAAIA,EAAI,OAAO,MAAM,OAAS,CAAA,CAAG,EACjFgtC,EAAqB,CAAC,eAAgB,gBAAiB,eAAgB,eAAgB,eAAe,EAC5G,UAAWC,KAAWF,EACpB,GAAIC,EAAmB,SAASC,CAAO,EAAG,CASxC,MAAMC,EAP6C,CACjD,eAAgB,SAChB,gBAAiB,UACjB,eAAgB,SAChB,eAAgB,SAChB,gBAAiB,WAAA,EAEkBD,CAAO,EACxCC,GAAa,CAACltC,EAAI,aAAa,OAAO,SAASktC,CAAsB,GACvEJ,EAAS,KAAK,sBAAsBG,CAAO,UAAUC,CAAS,iCAAiC,CAEnG,CAGF,MAAO,CAAE,MAAOL,EAAO,SAAW,EAAG,OAAAA,EAAQ,SAAAC,CAAA,CAC/C,CCvCA,SAASK,GAAeC,EAAWC,EAAoB,CACrD,MAAMC,EAAO,IAAI,IAAIF,CAAI,EACnB3/C,EAAS,CAAC,GAAG2/C,CAAI,EACvB,UAAW/+C,KAAQg/C,EACZC,EAAK,IAAIj/C,CAAI,IAChBi/C,EAAK,IAAIj/C,CAAI,EACbZ,EAAO,KAAKY,CAAI,GAGpB,OAAOZ,CACT,CAMA,SAAS8/C,GACPH,EACAC,EACG,CACH,MAAO,CAAE,GAAGD,EAAM,GAAGC,CAAA,CACvB,CAMA,MAAMG,GAAY,CAAC,SAAU,OAAQ,SAAU,QAAS,WAAW,EA6B5D,SAASC,GACdL,EACAM,EACe,OAEf,MAAM3gD,EAAO2gD,EAAU,MAAQN,EAAK,KAG9BO,EAAYP,EAAK,OAAO,MACxBQ,GAAgB5uC,EAAA0uC,EAAU,SAAV,YAAA1uC,EAAkB,MAClC6uC,EAAgD,CAAA,EAEtD,UAAWhgD,KAAO2/C,GAAW,CAC3B,MAAMM,EAAUH,EAAU9/C,CAAG,EACvBkgD,EAAcH,GAAA,YAAAA,EAAgB//C,GAChCigD,GAAWC,EACbF,EAAYhgD,CAAG,EAAIs/C,GAAYW,EAASC,CAAW,EAC1CA,EACTF,EAAYhgD,CAAG,EAAI,CAAC,GAAGkgD,CAAW,EACzBD,IACTD,EAAYhgD,CAAG,EAAI,CAAC,GAAGigD,CAAO,EAElC,CAGA,MAAME,EAAWZ,EAAK,aAChBa,EAAeP,EAAU,aAEzBQ,EAAoD,CACxD,SAAUF,EAAS,SACnB,WAAYA,EAAS,WACrB,QAASA,EAAS,QAClB,OAAQA,EAAS,OACjB,MAAOA,EAAS,MAChB,SAAU,CAAE,GAAGA,EAAS,QAAA,CAAS,EAG/BC,IACEA,EAAa,WACfC,EAAmB,SAAWf,GAAYa,EAAS,SAAUC,EAAa,QAAQ,GAEhFA,EAAa,aACfC,EAAmB,WAAaf,GAAYa,EAAS,WAAYC,EAAa,UAAU,GAEtFA,EAAa,UACfC,EAAmB,QAAUf,GAAYa,EAAS,QAASC,EAAa,OAAO,GAE7EA,EAAa,SACfC,EAAmB,OAASf,GAAYa,EAAS,OAAQC,EAAa,MAAM,GAE1EA,EAAa,QACfC,EAAmB,MAAQf,GAAYa,EAAS,MAAOC,EAAa,KAAK,GAEvEA,EAAa,WACfC,EAAmB,SAAWX,GAC5BS,EAAS,SACTC,EAAa,QAAA,IAMnB,MAAME,EAAiBT,EAAU,SAC7BH,GAAaH,EAAK,SAAUM,EAAU,QAA8C,EACpF,CAAE,GAAGN,EAAK,QAAA,EAGRgB,EAAcV,EAAU,MAC1BH,GAAaH,EAAK,MAAOM,EAAU,KAAwC,EAC3E,CAAE,GAAGN,EAAK,KAAA,EAGRiB,EAAcjB,EAAK,SAAW,CAAA,EAC9BkB,EAAgBZ,EAAU,QAC5BH,GAAac,EAAaX,EAAU,OAAyD,EAC7F,CAAE,GAAGW,CAAA,EAET,MAAO,CACL,KAAAthD,EACA,OAAQ,CAAE,MAAO8gD,CAAA,EACjB,aAAcK,EACd,SAAUC,EACV,MAAOC,EACP,QAASE,CAAA,CAEb,CAMA,MAAMC,GAAoD,CACxD,OAAQ,CACN,MAAO,CACL,OAAQ,CAAC,QAAQ,CAAA,CACnB,EAEF,aAAc,CACZ,SAAU,CAAC,OAAQ,OAAO,EAC1B,WAAY,CAAC,SAAU,OAAO,EAC9B,QAAS,CAAA,EACT,OAAQ,CAAA,EACR,MAAO,CAAC,QAAQ,EAChB,SAAU,CACR,OAAQ,GACR,cAAe,GACf,SAAU,GACV,KAAM,GACN,kBAAmB,EAAA,CACrB,EAEF,SAAU,CACR,QAAS,aACT,UAAW,SACX,UAAW,SACX,aAAc,WACd,WAAY,EAAA,EAEd,MAAO,CACL,QAAS,UACT,eAAgB,OAChB,aAAc,UAAA,EAEhB,QAAS,CACP,WAAY,YACZ,kBAAmB,SAAA,CAEvB,EA0CO,SAASC,GACdC,EACe,CACf,KAAM,CAAE,KAAA1hD,EAAM,GAAGyyB,CAAA,EAASivB,EACpBrB,EAAsB,CAAE,KAAArgD,EAAM,GAAGwhD,EAAA,EACvC,OAAOd,GAAUL,EAAM5tB,CAAI,CAC7B","x_google_ignoreList":[0]}