@remyxjs/core 1.0.0-beta → 1.0.4-beta

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 (112) hide show
  1. package/dist/convertCsv-DRxJY6iq.js +2 -0
  2. package/dist/{convertCsv-CKzZjzLJ.js.map → convertCsv-DRxJY6iq.js.map} +1 -1
  3. package/dist/{convertCsv-B8RVtdcs.cjs → convertCsv-_-qbSNir.cjs} +2 -2
  4. package/dist/{convertCsv-B8RVtdcs.cjs.map → convertCsv-_-qbSNir.cjs.map} +1 -1
  5. package/dist/convertDocx-D-V0dfFd.js +2 -0
  6. package/dist/{convertDocx-Dmx88twM.js.map → convertDocx-D-V0dfFd.js.map} +1 -1
  7. package/dist/{convertDocx-4q89XLLv.cjs → convertDocx-Dpd5BG-G.cjs} +2 -2
  8. package/dist/{convertDocx-4q89XLLv.cjs.map → convertDocx-Dpd5BG-G.cjs.map} +1 -1
  9. package/dist/{convertHtml-DbHrdrD3.cjs → convertHtml-DmfodLsz.cjs} +2 -2
  10. package/dist/{convertHtml-DbHrdrD3.cjs.map → convertHtml-DmfodLsz.cjs.map} +1 -1
  11. package/dist/convertHtml-gycStuZn.js +2 -0
  12. package/dist/{convertHtml-CtYVhiTh.js.map → convertHtml-gycStuZn.js.map} +1 -1
  13. package/dist/convertMarkdown-1BmsjWXP.js +2 -0
  14. package/dist/{convertMarkdown-Di239Gtn.js.map → convertMarkdown-1BmsjWXP.js.map} +1 -1
  15. package/dist/{convertMarkdown-eJ9Nkoid.cjs → convertMarkdown-CelFJJT9.cjs} +2 -2
  16. package/dist/{convertMarkdown-eJ9Nkoid.cjs.map → convertMarkdown-CelFJJT9.cjs.map} +1 -1
  17. package/dist/convertPdf-D4SNUyBk.js +2 -0
  18. package/dist/{convertPdf-CFA1eNNH.js.map → convertPdf-D4SNUyBk.js.map} +1 -1
  19. package/dist/{convertPdf-CSLmTrB8.cjs → convertPdf-DSZy--TD.cjs} +2 -2
  20. package/dist/{convertPdf-CSLmTrB8.cjs.map → convertPdf-DSZy--TD.cjs.map} +1 -1
  21. package/dist/{convertRtf-BfiBLMig.cjs → convertRtf-DK-4c2fe.cjs} +2 -2
  22. package/dist/{convertRtf-BfiBLMig.cjs.map → convertRtf-DK-4c2fe.cjs.map} +1 -1
  23. package/dist/convertRtf-JWu514-h.js +2 -0
  24. package/dist/{convertRtf-08CoScGD.js.map → convertRtf-JWu514-h.js.map} +1 -1
  25. package/dist/{convertText-BpgzHRuh.cjs → convertText-BHUD1tLm.cjs} +2 -2
  26. package/dist/{convertText-BpgzHRuh.cjs.map → convertText-BHUD1tLm.cjs.map} +1 -1
  27. package/dist/convertText-DCGItleK.js +2 -0
  28. package/dist/{convertText-sa7PxKTe.js.map → convertText-DCGItleK.js.map} +1 -1
  29. package/dist/index-C3_cH6Zy.cjs +2 -0
  30. package/dist/index-C3_cH6Zy.cjs.map +1 -0
  31. package/dist/index-Cv62E14a.js +2 -0
  32. package/dist/index-Cv62E14a.js.map +1 -0
  33. package/dist/remyx-core.cjs +1 -1
  34. package/dist/remyx-core.css +1 -1
  35. package/dist/remyx-core.js +1 -1
  36. package/package.json +1 -1
  37. package/dist/convertCsv-CKzZjzLJ.js +0 -2
  38. package/dist/convertDocx-Dmx88twM.js +0 -2
  39. package/dist/convertHtml-CtYVhiTh.js +0 -2
  40. package/dist/convertMarkdown-Di239Gtn.js +0 -2
  41. package/dist/convertPdf-CFA1eNNH.js +0 -2
  42. package/dist/convertRtf-08CoScGD.js +0 -2
  43. package/dist/convertText-sa7PxKTe.js +0 -2
  44. package/dist/index-4syk9eEO.js +0 -2
  45. package/dist/index-4syk9eEO.js.map +0 -1
  46. package/dist/index-B25zSs0W.js +0 -2
  47. package/dist/index-B25zSs0W.js.map +0 -1
  48. package/dist/index-B7VT6ZLa.cjs +0 -2
  49. package/dist/index-B7VT6ZLa.cjs.map +0 -1
  50. package/dist/index-BCpytFKJ.js +0 -2
  51. package/dist/index-BCpytFKJ.js.map +0 -1
  52. package/dist/index-BNKANY5i.cjs +0 -2
  53. package/dist/index-BNKANY5i.cjs.map +0 -1
  54. package/dist/index-B_g_579T.cjs +0 -2
  55. package/dist/index-B_g_579T.cjs.map +0 -1
  56. package/dist/index-BvwyeoMb.js +0 -3
  57. package/dist/index-BvwyeoMb.js.map +0 -1
  58. package/dist/index-Bw7mlUQo.js +0 -2
  59. package/dist/index-Bw7mlUQo.js.map +0 -1
  60. package/dist/index-Byatzd-A.js +0 -2
  61. package/dist/index-Byatzd-A.js.map +0 -1
  62. package/dist/index-C0z9eZLm.cjs +0 -2
  63. package/dist/index-C0z9eZLm.cjs.map +0 -1
  64. package/dist/index-C88XPqjX.js +0 -2
  65. package/dist/index-C88XPqjX.js.map +0 -1
  66. package/dist/index-CI6FPF49.cjs +0 -2
  67. package/dist/index-CI6FPF49.cjs.map +0 -1
  68. package/dist/index-CLZF5_GB.cjs +0 -2
  69. package/dist/index-CLZF5_GB.cjs.map +0 -1
  70. package/dist/index-CXSwYlG4.cjs +0 -2
  71. package/dist/index-CXSwYlG4.cjs.map +0 -1
  72. package/dist/index-Ch9gotLk.js +0 -2
  73. package/dist/index-Ch9gotLk.js.map +0 -1
  74. package/dist/index-CifDpN1Y.js +0 -2
  75. package/dist/index-CifDpN1Y.js.map +0 -1
  76. package/dist/index-D5o8VpWJ.cjs +0 -2
  77. package/dist/index-D5o8VpWJ.cjs.map +0 -1
  78. package/dist/index-DKT1bABL.js +0 -2
  79. package/dist/index-DKT1bABL.js.map +0 -1
  80. package/dist/index-DWcn72PW.js +0 -2
  81. package/dist/index-DWcn72PW.js.map +0 -1
  82. package/dist/index-DjCGzPEv.cjs +0 -2
  83. package/dist/index-DjCGzPEv.cjs.map +0 -1
  84. package/dist/index-Dq0Jr1Ae.js +0 -2
  85. package/dist/index-Dq0Jr1Ae.js.map +0 -1
  86. package/dist/index-Dw0MVypb.cjs +0 -2
  87. package/dist/index-Dw0MVypb.cjs.map +0 -1
  88. package/dist/index-FEo3LShh.cjs +0 -2
  89. package/dist/index-FEo3LShh.cjs.map +0 -1
  90. package/dist/index-O1hzAUzi.cjs +0 -2
  91. package/dist/index-O1hzAUzi.cjs.map +0 -1
  92. package/dist/index-T1ZyLzeF.cjs +0 -2
  93. package/dist/index-T1ZyLzeF.cjs.map +0 -1
  94. package/dist/index-iRikoCdK.cjs +0 -2
  95. package/dist/index-iRikoCdK.cjs.map +0 -1
  96. package/dist/index-l6Yddj6x.js +0 -2
  97. package/dist/index-l6Yddj6x.js.map +0 -1
  98. package/dist/index-rD8LZENp.js +0 -2
  99. package/dist/index-rD8LZENp.js.map +0 -1
  100. package/dist/themes/callouts.css +0 -79
  101. package/dist/themes/collaboration.css +0 -117
  102. package/dist/themes/comments.css +0 -198
  103. package/dist/themes/dark.css +0 -109
  104. package/dist/themes/forest.css +0 -109
  105. package/dist/themes/light.css +0 -4
  106. package/dist/themes/links.css +0 -115
  107. package/dist/themes/math-toc-analytics.css +0 -129
  108. package/dist/themes/ocean.css +0 -109
  109. package/dist/themes/rose.css +0 -109
  110. package/dist/themes/spellcheck.css +0 -173
  111. package/dist/themes/sunset.css +0 -109
  112. package/dist/themes/templates.css +0 -87
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-Cv62E14a.js","sources":["../src/core/EventBus.js","../src/utils/dom.js","../src/core/Selection.js","../src/core/CommandRegistry.js","../src/utils/hash.js","../src/core/History.js","../src/utils/platform.js","../src/core/KeyboardManager.js","../src/constants/schema.js","../src/core/Sanitizer.js","../src/utils/pasteClean.js","../src/utils/escapeHTML.js","../src/utils/documentConverter/shared.js","../src/utils/documentConverter/index.js","../src/constants/defaults.js","../src/utils/fileValidation.js","../src/utils/markdownConverter.js","../src/utils/insertPlainText.js","../src/core/Clipboard.js","../src/core/DragDrop.js","../src/plugins/PluginManager.js","../src/core/EditorEngine.js","../src/core/EditorBus.js","../src/utils/toolbarConfig.js","../src/constants/commands.js","../src/constants/keybindings.js","../src/core/SharedResources.js","../src/commands/formatting.js","../src/commands/headings.js","../src/commands/alignment.js","../src/commands/lists.js","../src/commands/links.js","../src/commands/images.js","../src/commands/tables.js","../src/commands/blocks.js","../src/commands/blockConvert.js","../src/commands/fontControls.js","../src/commands/media.js","../src/commands/findReplace.js","../src/commands/sourceMode.js","../src/commands/fullscreen.js","../src/commands/distractionFree.js","../src/commands/splitView.js","../src/commands/colorPresets.js","../src/commands/markdownToggle.js","../src/commands/attachments.js","../src/commands/importDocument.js","../src/commands/slashCommands.js","../src/autosave/providers.js","../src/core/AutosaveManager.js","../src/plugins/createPlugin.js","../src/plugins/builtins/WordCountPlugin.js","../src/plugins/builtins/AutolinkPlugin.js","../src/plugins/builtins/PlaceholderPlugin.js","../src/workers/WorkerPool.js","../src/core/VirtualScroller.js","../src/utils/exportUtils.js","../src/utils/fontConfig.js","../src/utils/formatHTML.js","../src/utils/rtl.js","../src/i18n/locales/en.js","../src/i18n/index.js","../src/utils/performance.js","../src/utils/themeConfig.js","../src/utils/themePresets.js","../src/utils/toolbarItemTheme.js","../src/config/defineConfig.js","../src/config/loadConfig.js","../src/config/pluginResolver.js"],"sourcesContent":["/**\n * A simple publish/subscribe event system.\n */\nexport class EventBus {\n /**\n * Creates a new EventBus instance with an empty listener map.\n */\n constructor() {\n this._listeners = new Map()\n this._keyedHandlers = new Map() // Maps \"event:key\" to handler for keyed subscriptions\n // Task 275: Emit depth counter to prevent infinite recursion\n this._emitDepth = 0\n }\n\n /**\n * Subscribes a handler to an event.\n * @param {string} event - The event name to listen for\n * @param {Function} handler - The callback function to invoke when the event fires\n * @param {Object} [options] - Optional configuration\n * @param {string} [options.key] - If provided, replaces any existing handler with the same key\n * @returns {Function} An unsubscribe function that removes this listener when called\n */\n on(event, handler, options) {\n if (!this._listeners.has(event)) {\n this._listeners.set(event, new Set())\n }\n\n // Keyed handler: replace existing handler with same key\n if (options?.key) {\n const compositeKey = `${event}:${options.key}`\n const existing = this._keyedHandlers.get(compositeKey)\n if (existing) {\n this.off(event, existing)\n }\n this._keyedHandlers.set(compositeKey, handler)\n }\n\n this._listeners.get(event).add(handler)\n return () => this.off(event, handler)\n }\n\n /**\n * Removes a specific handler from an event.\n * @param {string} event - The event name to unsubscribe from\n * @param {Function} handler - The handler function to remove\n * @returns {void}\n */\n off(event, handler) {\n const handlers = this._listeners.get(event)\n if (handlers) {\n handlers.delete(handler)\n if (handlers.size === 0) {\n this._listeners.delete(event)\n }\n }\n }\n\n /**\n * Subscribes a handler that will be called at most once, then automatically removed.\n * @param {string} event - The event name to listen for\n * @param {Function} handler - The callback function to invoke once\n * @returns {Function} An unsubscribe function that removes this listener when called\n */\n once(event, handler) {\n const wrapper = (...args) => {\n this.off(event, wrapper)\n handler(...args)\n }\n return this.on(event, wrapper)\n }\n\n /**\n * Emits an event, invoking all registered handlers with the given data.\n * Errors in individual handlers are caught and logged without interrupting other handlers.\n * @param {string} event - The event name to emit\n * @param {*} [data] - Optional data to pass to each handler\n * @returns {void}\n */\n emit(event, data) {\n const handlers = this._listeners.get(event)\n if (handlers) {\n // Task 275: Track emit depth to prevent infinite recursion\n this._emitDepth++\n try {\n handlers.forEach((handler) => {\n try {\n handler(data)\n } catch (err) {\n console.error(`EventBus error in \"${event}\" handler:`, err)\n // Forward handler errors to 'error' event (with recursion guard)\n if (event !== 'error' && this._emitDepth < 3) {\n this.emit('error', { event, error: err, data })\n }\n }\n })\n } finally {\n this._emitDepth--\n }\n }\n }\n\n /**\n * Removes all listeners for a specific event, or all listeners entirely.\n * @param {string} [event] - The event name to clear. If omitted, all listeners are removed.\n * @returns {void}\n */\n removeAllListeners(event) {\n if (event) {\n this._listeners.delete(event)\n } else {\n this._listeners.clear()\n }\n }\n}\n","/** Item 11: Shared block-level tags set used by walkUpToBlock and closestBlock */\nconst BLOCK_LEVEL_TAGS = new Set(['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'DIV', 'BLOCKQUOTE', 'PRE', 'LI', 'TD', 'TH'])\n\n/**\n * Item 11: Walk up the DOM from `node` to `boundary`, returning the first\n * block-level ancestor element. Shared helper for dom.js and Selection.js.\n * @param {Node} node - Starting node\n * @param {Node} boundary - Stop walking at this element (exclusive)\n * @returns {HTMLElement|null}\n */\nexport function walkUpToBlock(node, boundary) {\n let el = node.nodeType === Node.TEXT_NODE ? node.parentElement : node\n while (el && el !== boundary) {\n if (BLOCK_LEVEL_TAGS.has(el.tagName)) return el\n el = el.parentElement\n }\n return null\n}\n\nexport function closestBlock(node, root) {\n return walkUpToBlock(node, root)\n}\n\nexport function closestTag(node, tagName, root) {\n const tag = tagName.toUpperCase()\n let el = node.nodeType === Node.TEXT_NODE ? node.parentElement : node\n while (el && el !== root) {\n if (el.tagName === tag) return el\n el = el.parentElement\n }\n return null\n}\n\nexport function wrapInTag(range, tagName, attrs = {}) {\n const el = document.createElement(tagName)\n Object.entries(attrs).forEach(([key, value]) => el.setAttribute(key, value))\n try {\n range.surroundContents(el)\n } catch {\n const fragment = range.extractContents()\n el.appendChild(fragment)\n range.insertNode(el)\n }\n return el\n}\n\nexport function unwrapTag(element) {\n const parent = element.parentNode\n while (element.firstChild) {\n parent.insertBefore(element.firstChild, element)\n }\n parent.removeChild(element)\n}\n\n/** Length of the random suffix in generated IDs */\nconst GENERATED_ID_LENGTH = 9\n\n/**\n * Generates a non-cryptographic random ID for internal DOM element tracking.\n * WARNING: Uses Math.random() which is NOT cryptographically secure.\n * Do NOT use these IDs for security tokens, session IDs, or any\n * security-critical purpose. Use crypto.getRandomValues() instead.\n * @returns {string} A prefixed random ID like 'rmx-abc123def'\n */\nexport function generateId() {\n return 'rmx-' + Math.random().toString(36).substr(2, GENERATED_ID_LENGTH)\n}\n\nexport function isBlockEmpty(block) {\n if (!block) return true\n const text = block.textContent.trim()\n return text === '' && !block.querySelector('img, iframe, hr, input')\n}\n","import { walkUpToBlock } from '../utils/dom.js'\n\n// Pre-compiled regex patterns (avoid recompilation on every selection change)\nconst HEADING_REGEX = /^h[1-6]$/\n\n// Pre-compiled format tag map for O(1) lookups (avoids repeated string comparisons)\nconst FORMAT_TAG_MAP = {\n bold: new Set(['STRONG', 'B']),\n italic: new Set(['EM', 'I']),\n underline: new Set(['U']),\n strikethrough: new Set(['S', 'DEL']),\n subscript: new Set(['SUB']),\n superscript: new Set(['SUP']),\n}\n\n/**\n * @typedef {Object} SelectionBookmark\n * @property {number} startOffset - Character offset of the selection start within the editor\n * @property {number} endOffset - Character offset of the selection end within the editor\n * @property {boolean} collapsed - Whether the selection is collapsed (cursor with no range)\n */\n\n/**\n * @typedef {Object} LinkInfo\n * @property {string} href - The link URL\n * @property {string} text - The link text content\n * @property {string} target - The link target attribute\n */\n\n/**\n * @typedef {Object} ActiveFormats\n * @property {boolean} bold - Whether bold formatting is active\n * @property {boolean} italic - Whether italic formatting is active\n * @property {boolean} underline - Whether underline formatting is active\n * @property {boolean} strikethrough - Whether strikethrough formatting is active\n * @property {boolean} subscript - Whether subscript formatting is active\n * @property {boolean} superscript - Whether superscript formatting is active\n * @property {string|null} heading - The heading level tag (e.g., 'h1', 'h2') or null\n * @property {string} alignment - Text alignment ('left', 'center', 'right', 'justify')\n * @property {boolean} orderedList - Whether inside an ordered list\n * @property {boolean} unorderedList - Whether inside an unordered list\n * @property {boolean} blockquote - Whether inside a blockquote\n * @property {boolean} codeBlock - Whether inside a code block (pre)\n * @property {LinkInfo|null} link - Link info if inside a link, or null\n * @property {string|null} fontFamily - Current font family or null\n * @property {string|null} fontSize - Current font size or null\n * @property {string|null} foreColor - Current text color or null\n * @property {string|null} backColor - Current background color or null\n */\n\nexport class Selection {\n /**\n * Creates a new Selection manager.\n * @param {HTMLElement} editorElement - The editor DOM element to manage selections within\n */\n constructor(editorElement) {\n this.editor = editorElement\n this._cachedRange = null\n this._cacheGeneration = 0\n }\n\n /**\n * Invalidates the cached range. Call this when the selection may have changed\n * (e.g. after a command execution or DOM mutation).\n */\n invalidateCache() {\n this._cacheGeneration++\n this._cachedRange = null\n }\n\n /**\n * Returns the current window Selection object.\n * @returns {globalThis.Selection} The browser's Selection object\n */\n getSelection() {\n return window.getSelection()\n }\n\n /**\n * Returns the current Range if it is within the editor, or null.\n * @returns {Range|null} The current selection range within the editor, or null\n */\n getRange() {\n // Return cached range if still valid within same synchronous cycle\n if (this._cachedRange !== null) {\n return this._cachedRange\n }\n const sel = this.getSelection()\n if (!sel || sel.rangeCount === 0) {\n this._cachedRange = null\n return null\n }\n const range = sel.getRangeAt(0)\n if (!this.isWithinEditor(range.commonAncestorContainer)) {\n this._cachedRange = null\n return null\n }\n this._cachedRange = range\n // Schedule cache invalidation at end of current synchronous cycle\n const gen = ++this._cacheGeneration\n Promise.resolve().then(() => {\n if (this._cacheGeneration === gen) {\n this._cachedRange = null\n }\n })\n return range\n }\n\n /**\n * Sets the current selection to the given range.\n * Silently ignores errors from detached nodes or invalid offsets.\n * @param {Range} range - The Range object to apply as the current selection\n * @returns {void}\n */\n setRange(range) {\n this._cachedRange = null\n try {\n const sel = this.getSelection()\n sel.removeAllRanges()\n sel.addRange(range)\n } catch {\n // Range may reference detached nodes or invalid offsets — silently ignore\n }\n }\n\n /**\n * Checks whether a given node is within the editor element.\n * @param {Node|null} node - The DOM node to check\n * @returns {boolean} True if the node is contained within the editor\n */\n isWithinEditor(node) {\n if (!node) return false\n const el = node.nodeType === Node.TEXT_NODE ? node.parentElement : node\n return this.editor.contains(el)\n }\n\n /**\n * Checks whether the current selection is collapsed (a caret with no range).\n * @returns {boolean} True if the selection is collapsed or no selection exists\n */\n isCollapsed() {\n const sel = this.getSelection()\n return sel ? sel.isCollapsed : true\n }\n\n /**\n * Returns the selected text content as a string.\n * @returns {string} The selected text, or an empty string if nothing is selected\n */\n getSelectedText() {\n const sel = this.getSelection()\n return sel ? sel.toString() : ''\n }\n\n /**\n * Returns the selected content as an HTML string.\n * @returns {string} The selected HTML, or an empty string if nothing is selected\n */\n getSelectedHTML() {\n const range = this.getRange()\n if (!range) return ''\n const fragment = range.cloneContents()\n const div = document.createElement('div')\n div.appendChild(fragment)\n return div.innerHTML\n }\n\n /**\n * Saves the current selection as a character-offset bookmark that can\n * survive DOM mutations. Returns null if no range is active.\n * @returns {SelectionBookmark|null} The bookmark object, or null\n */\n save() {\n const range = this.getRange()\n if (!range) return null\n\n const preRange = document.createRange()\n preRange.selectNodeContents(this.editor)\n preRange.setEnd(range.startContainer, range.startOffset)\n const startOffset = preRange.toString().length\n\n return {\n startOffset,\n endOffset: startOffset + range.toString().length,\n collapsed: range.collapsed,\n }\n }\n\n /**\n * Restores the selection from a previously saved bookmark.\n * Falls back to the end of the editor if the exact position cannot be restored.\n * @param {SelectionBookmark|null} bookmark - The bookmark to restore, or null to do nothing\n * @returns {void}\n */\n restore(bookmark) {\n if (!bookmark) return\n const textWalker = document.createTreeWalker(\n this.editor,\n NodeFilter.SHOW_TEXT,\n null\n )\n\n let charCount = 0\n let startNode = null\n let startNodeOffset = 0\n let endNode = null\n let endNodeOffset = 0\n\n while (textWalker.nextNode()) {\n const node = textWalker.currentNode\n const nodeLength = node.textContent.length\n const nextCount = charCount + nodeLength\n\n if (!startNode && nextCount >= bookmark.startOffset) {\n startNode = node\n startNodeOffset = bookmark.startOffset - charCount\n }\n if (!endNode && nextCount >= bookmark.endOffset) {\n endNode = node\n endNodeOffset = bookmark.endOffset - charCount\n break\n }\n charCount = nextCount\n }\n\n // Note: offset may be imprecise if DOM changed significantly between save() and restore(),\n // but the try/catch fallback below handles this by moving cursor to end of editor.\n if (startNode) {\n try {\n const range = document.createRange()\n range.setStart(startNode, Math.min(startNodeOffset, startNode.textContent.length))\n if (endNode) {\n range.setEnd(endNode, Math.min(endNodeOffset, endNode.textContent.length))\n } else {\n range.collapse(true)\n }\n this.setRange(range)\n } catch {\n // DOM structure changed between save and restore — fall back to end of editor\n try {\n const fallbackRange = document.createRange()\n fallbackRange.selectNodeContents(this.editor)\n fallbackRange.collapse(false)\n this.setRange(fallbackRange)\n } catch {\n // Editor element may not be in the DOM — nothing we can do\n }\n }\n }\n }\n\n /**\n * Collapses the current selection to the start or end.\n * @param {boolean} [toEnd=false] - If true, collapse to the end; otherwise collapse to the start\n * @returns {void}\n */\n collapse(toEnd = false) {\n const sel = this.getSelection()\n if (sel && sel.rangeCount > 0) {\n if (toEnd) {\n sel.collapseToEnd()\n } else {\n sel.collapseToStart()\n }\n }\n }\n\n /**\n * Returns the nearest element containing the current selection.\n * If the selection is in a text node, returns its parent element.\n * @returns {HTMLElement|null} The parent element, or null if no range exists\n */\n getParentElement() {\n const range = this.getRange()\n if (!range) return null\n const container = range.commonAncestorContainer\n return container.nodeType === Node.TEXT_NODE ? container.parentElement : container\n }\n\n /**\n * Returns the nearest block-level ancestor element of the current selection.\n * @returns {HTMLElement|null} The parent block element (p, h1-h6, div, blockquote, pre, li, td, th), or null\n */\n getParentBlock() {\n const el = this.getParentElement()\n if (!el) return null\n // Item 11: Use shared walkUpToBlock helper\n return walkUpToBlock(el, this.editor)\n }\n\n /**\n * Finds the closest ancestor element with the given tag name.\n * @param {string} tagName - The HTML tag name to search for (case-insensitive)\n * @returns {HTMLElement|null} The matching ancestor element, or null if not found\n */\n getClosestElement(tagName) {\n let el = this.getParentElement()\n const tag = tagName.toUpperCase()\n while (el && el !== this.editor) {\n if (el.tagName === tag) return el\n el = el.parentElement\n }\n return null\n }\n\n /**\n * Inserts HTML at the current selection using Range-based DOM manipulation.\n * CSP-compatible — does not use document.execCommand.\n * Automatically sanitizes input through the editor's Sanitizer if available\n * to prevent XSS. Falls back to unsanitized insertion only if no sanitizer\n * is configured (e.g. in unit tests with standalone Selection instances).\n * @param {string} html - The HTML string to insert\n * @returns {void}\n */\n insertHTML(html) {\n const safeHtml = this._sanitizer ? this._sanitizer.sanitize(html) : html\n const sel = this.getSelection()\n if (!sel || sel.rangeCount === 0) return\n const range = sel.getRangeAt(0)\n range.deleteContents()\n const template = document.createElement('template')\n template.innerHTML = safeHtml\n const fragment = template.content\n const lastChild = fragment.lastChild\n range.insertNode(fragment)\n // Move cursor after the inserted content\n if (lastChild) {\n const newRange = document.createRange()\n newRange.setStartAfter(lastChild)\n newRange.collapse(true)\n sel.removeAllRanges()\n sel.addRange(newRange)\n }\n }\n\n /**\n * Attaches a Sanitizer instance for automatic HTML sanitization in insertHTML().\n * Called by EditorEngine during initialization.\n * @param {import('./Sanitizer.js').Sanitizer} sanitizer\n * @returns {void}\n */\n setSanitizer(sanitizer) {\n this._sanitizer = sanitizer\n }\n\n /**\n * Inserts a DOM node at the current selection, replacing any selected content.\n * Moves the cursor to after the inserted node.\n * @param {Node} node - The DOM node to insert\n * @returns {void}\n */\n insertNode(node) {\n const range = this.getRange()\n if (!range) return\n range.deleteContents()\n range.insertNode(node)\n range.setStartAfter(node)\n range.collapse(true)\n this.setRange(range)\n }\n\n /**\n * Wraps the current selection with a new element of the given tag name.\n * Does nothing if the selection is collapsed.\n * @param {string} tagName - The HTML tag name for the wrapper element\n * @param {Object} [attrs={}] - Key-value pairs of attributes to set on the wrapper\n * @returns {HTMLElement|null} The created wrapper element, or null if the selection was collapsed\n */\n wrapWith(tagName, attrs = {}) {\n const range = this.getRange()\n if (!range || range.collapsed) return null\n const el = document.createElement(tagName)\n Object.entries(attrs).forEach(([key, value]) => {\n el.setAttribute(key, value)\n })\n try {\n range.surroundContents(el)\n } catch {\n const fragment = range.extractContents()\n el.appendChild(fragment)\n range.insertNode(el)\n }\n this.setRange(range)\n return el\n }\n\n /**\n * Unwraps the closest ancestor matching the given tag name, preserving its children.\n * @param {string} tagName - The HTML tag name of the element to unwrap (case-insensitive)\n * @returns {void}\n */\n unwrap(tagName) {\n const el = this.getClosestElement(tagName)\n if (!el) return\n const parent = el.parentNode\n while (el.firstChild) {\n parent.insertBefore(el.firstChild, el)\n }\n parent.removeChild(el)\n }\n\n /**\n * Detects and returns all active formatting states at the current selection.\n * @returns {ActiveFormats} An object describing all active formats\n */\n getActiveFormats() {\n const formats = {\n bold: false,\n italic: false,\n underline: false,\n strikethrough: false,\n subscript: false,\n superscript: false,\n heading: null,\n alignment: 'left',\n orderedList: false,\n unorderedList: false,\n blockquote: false,\n codeBlock: false,\n link: null,\n fontFamily: null,\n fontSize: null,\n foreColor: null,\n backColor: null,\n }\n\n // Detect inline formats via DOM traversal using pre-compiled FORMAT_TAG_MAP\n // Task 243: Removed queryCommandState fallback — DOM traversal handles all format detection\n try {\n const sel = window.getSelection()\n if (sel && sel.rangeCount > 0) {\n let node = sel.anchorNode\n // Walk up from the anchor node to the editor root, checking tag names against Sets\n while (node && node !== this.editor) {\n if (node.nodeType === Node.ELEMENT_NODE) {\n const tag = node.tagName\n for (const format in FORMAT_TAG_MAP) {\n if (!formats[format] && FORMAT_TAG_MAP[format].has(tag)) {\n formats[format] = true\n }\n }\n }\n node = node.parentNode\n }\n }\n } catch {\n // DOM traversal can fail with detached nodes\n }\n\n const block = this.getParentBlock()\n if (block) {\n const tag = block.tagName.toLowerCase()\n if (HEADING_REGEX.test(tag)) {\n formats.heading = tag\n }\n const align = block.style.textAlign || window.getComputedStyle(block).textAlign\n if (align) {\n formats.alignment = align === 'start' ? 'left' : align === 'end' ? 'right' : align\n }\n }\n\n let el = this.getParentElement()\n while (el && el !== this.editor) {\n const tag = el.tagName\n if (tag === 'OL') formats.orderedList = true\n if (tag === 'UL') formats.unorderedList = true\n if (tag === 'BLOCKQUOTE') formats.blockquote = true\n if (tag === 'PRE') formats.codeBlock = true\n if (tag === 'A') formats.link = { href: el.href, text: el.textContent, target: el.target }\n el = el.parentElement\n }\n\n try {\n formats.fontFamily = document.queryCommandValue('fontName') || null\n formats.fontSize = document.queryCommandValue('fontSize') || null\n formats.foreColor = document.queryCommandValue('foreColor') || null\n formats.backColor = document.queryCommandValue('backColor') || null\n } catch {\n // ignore\n }\n\n return formats\n }\n\n /**\n * Returns the bounding rectangle of the current selection range.\n * @returns {DOMRect|null} The bounding client rect, or null if no range exists\n */\n getBoundingRect() {\n const range = this.getRange()\n if (!range) return null\n return range.getBoundingClientRect()\n }\n}\n","/**\n * @typedef {Object} CommandDefinition\n * @property {Function} execute - The function to run when the command is executed, receives (engine, ...args)\n * @property {Function} [isActive] - Returns true if the command's effect is currently active, receives (engine)\n * @property {Function} [isEnabled] - Returns true if the command can be executed, receives (engine)\n * @property {string|null} [shortcut] - Keyboard shortcut string (e.g., 'Ctrl+B')\n * @property {Object} [meta] - Arbitrary metadata for the command\n */\n\n/**\n * @typedef {Object} RegisteredCommand\n * @property {string} name - The command name\n * @property {Function} execute - The execute function\n * @property {Function} isActive - Returns whether the command is currently active\n * @property {Function} isEnabled - Returns whether the command is enabled\n * @property {string|null} shortcut - The keyboard shortcut, or null\n * @property {Object} meta - Arbitrary metadata\n */\n\n/**\n * Registry for editor commands. Commands are named operations that can be\n * executed, queried for active/enabled state, and optionally bound to keyboard shortcuts.\n */\nexport class CommandRegistry {\n /**\n * Creates a new CommandRegistry.\n * @param {import('./EditorEngine.js').EditorEngine} engine - The editor engine instance\n */\n constructor(engine) {\n this.engine = engine\n this._commands = new Map()\n }\n\n /**\n * Registers a command with the given name. If the command defines a shortcut,\n * it is also registered with the keyboard manager.\n * @param {string} name - Unique name for the command\n * @param {CommandDefinition} command - The command definition\n * @returns {void}\n */\n register(name, command) {\n this._commands.set(name, {\n name,\n execute: command.execute,\n isActive: command.isActive || (() => false),\n isEnabled: command.isEnabled || (() => true),\n shortcut: command.shortcut || null,\n meta: command.meta || {},\n // Task 251: Support skipSnapshot option\n skipSnapshot: command.skipSnapshot || false,\n })\n\n if (command.shortcut) {\n this.engine.keyboard.register(command.shortcut, name)\n }\n\n // Register alternate shortcuts as fallback bindings for cross-platform support\n if (command.alternateShortcuts) {\n for (const alt of command.alternateShortcuts) {\n this.engine.keyboard.register(alt, name)\n }\n }\n }\n\n /**\n * Executes a registered command by name. Takes a history snapshot before\n * execution and emits command:executed and content:change events.\n * @param {string} name - The name of the command to execute\n * @param {...*} args - Additional arguments passed to the command's execute function\n * @returns {*} The result of the command's execute function, or false if the command is not found or disabled\n */\n execute(name, ...args) {\n const command = this._commands.get(name)\n if (!command) {\n console.warn(`Command \"${name}\" not found`)\n return false\n }\n if (!command.isEnabled(this.engine)) {\n return false\n }\n\n // Task 251: Skip snapshot if command has skipSnapshot: true\n if (!command.skipSnapshot) {\n this.engine.history.snapshot()\n }\n const result = command.execute(this.engine, ...args)\n this.engine.eventBus.emit('command:executed', { name, args, result })\n this.engine.eventBus.emit('content:change')\n return result\n }\n\n /**\n * Checks whether a command is currently in an active state (e.g., bold is active when text is bold).\n * @param {string} name - The command name\n * @returns {boolean} True if the command exists and is active\n */\n isActive(name) {\n const command = this._commands.get(name)\n if (!command) return false\n return command.isActive(this.engine)\n }\n\n /**\n * Checks whether a command is currently enabled and can be executed.\n * @param {string} name - The command name\n * @returns {boolean} True if the command exists and is enabled\n */\n isEnabled(name) {\n const command = this._commands.get(name)\n if (!command) return false\n return command.isEnabled(this.engine)\n }\n\n /**\n * Returns the registered command object for the given name.\n * @param {string} name - The command name\n * @returns {RegisteredCommand|undefined} The command object, or undefined if not found\n */\n get(name) {\n return this._commands.get(name)\n }\n\n /**\n * Returns an array of all registered command names.\n * @returns {string[]} Array of command names\n */\n getAll() {\n return Array.from(this._commands.keys())\n }\n\n /**\n * Checks whether a command with the given name is registered.\n * @param {string} name - The command name\n * @returns {boolean} True if the command exists\n */\n has(name) {\n return this._commands.has(name)\n }\n}\n","/**\n * Lightweight djb2 hash function for fast string comparison.\n * Returns a 32-bit integer hash.\n * @param {string} str\n * @returns {number}\n */\nexport function djb2Hash(str) {\n let hash = 5381\n for (let i = 0, len = str.length; i < len; i++) {\n hash = ((hash << 5) + hash + str.charCodeAt(i)) | 0\n }\n return hash\n}\n","// Item 8: djb2Hash moved to utils/hash.js, re-exported here for backward compat\nimport { djb2Hash } from '../utils/hash.js'\nexport { djb2Hash }\n\n/**\n * Threshold in characters above which diff-based storage is used\n * instead of full snapshots, to reduce memory usage for large documents.\n * @type {number}\n */\nconst DIFF_THRESHOLD = 5000\n\n/**\n * Compute a simple character-level diff between two strings.\n * Finds the common prefix and suffix, then stores only the middle\n * replacement segment.\n *\n * @param {string} oldStr - The previous HTML string\n * @param {string} newStr - The new HTML string\n * @returns {{ prefixLen: number, suffixLen: number, insert: string }}\n */\nfunction computeDiff(oldStr, newStr) {\n const minLen = Math.min(oldStr.length, newStr.length)\n\n // Find common prefix length\n let prefixLen = 0\n while (prefixLen < minLen && oldStr[prefixLen] === newStr[prefixLen]) {\n prefixLen++\n }\n\n // Find common suffix length (not overlapping with prefix)\n let suffixLen = 0\n const maxSuffix = minLen - prefixLen\n while (\n suffixLen < maxSuffix &&\n oldStr[oldStr.length - 1 - suffixLen] === newStr[newStr.length - 1 - suffixLen]\n ) {\n suffixLen++\n }\n\n // The inserted/replacement segment from the new string\n const insert = newStr.slice(prefixLen, newStr.length - suffixLen || undefined)\n\n return { prefixLen, suffixLen, insert }\n}\n\n/**\n * Apply a diff to reconstruct the new string from the old string.\n *\n * @param {string} baseStr - The base HTML string\n * @param {{ prefixLen: number, suffixLen: number, insert: string }} diff\n * @returns {string} The reconstructed HTML string\n */\nfunction applyDiff(baseStr, diff) {\n const { prefixLen, suffixLen, insert } = diff\n const prefix = baseStr.slice(0, prefixLen)\n const suffix = suffixLen > 0 ? baseStr.slice(baseStr.length - suffixLen) : ''\n return prefix + insert + suffix\n}\n\n/**\n * @typedef {Object} HistoryOptions\n * @property {number} [maxSize=100] - Maximum number of undo states to retain\n * @property {number} [debounceMs=300] - Debounce interval in milliseconds for automatic snapshots\n * @property {number} [coalesceMs=1000] - Window in which rapid keystrokes are coalesced into a single undo step\n */\n\n/**\n * @typedef {Object} HistoryEntry\n * @property {'full'|'diff'} type - Whether this is a full snapshot or a diff\n * @property {string} [html] - Full HTML content (when type === 'full')\n * @property {{ prefixLen: number, suffixLen: number, insert: string }} [patch] - Diff patch (when type === 'diff')\n * @property {import('./Selection.js').SelectionBookmark|null} bookmark - Selection bookmark\n */\n\n/**\n * @typedef {Object} HistoryState\n * @property {string} html - The HTML content at the time of the snapshot\n * @property {import('./Selection.js').SelectionBookmark|null} bookmark - The selection bookmark at the time of the snapshot\n */\n\n/**\n * Manages undo/redo history for the editor using DOM mutation observation\n * and debounced snapshots.\n *\n * For documents larger than 5000 characters, uses diff-based compression\n * to store only the changes between states, significantly reducing memory\n * usage for large documents.\n */\nexport class History {\n /**\n * Creates a new History manager.\n * @param {import('./EditorEngine.js').EditorEngine} engine - The editor engine instance\n * @param {HistoryOptions} [options={}] - History configuration options\n */\n constructor(engine, options = {}) {\n this.engine = engine\n this.maxSize = options.maxSize || 100\n this.debounceMs = options.debounceMs || 300\n this.coalesceMs = options.coalesceMs || 1000\n this._undoStack = []\n this._redoStack = []\n this._observer = null\n this._debounceTimer = null\n this._coalesceTimer = null\n this._isPerformingUndoRedo = false\n this._isCoalescing = false\n this._lastSnapshot = null\n this._lastHash = null\n }\n\n /**\n * Initializes history tracking by taking an initial snapshot and\n * starting a MutationObserver on the editor element.\n * Task 249: Removed attributes: true from observer options.\n * @returns {void}\n */\n init() {\n this._takeSnapshot()\n this._observer = new MutationObserver(() => {\n if (this._isPerformingUndoRedo) return\n this._debouncedSnapshot()\n })\n this._observer.observe(this.engine.element, {\n childList: true,\n characterData: true,\n subtree: true,\n })\n }\n\n /**\n * Destroys the history manager by disconnecting the MutationObserver\n * and clearing the debounce timer.\n * @returns {void}\n */\n destroy() {\n if (this._observer) {\n this._observer.disconnect()\n this._observer = null\n }\n if (this._debounceTimer) {\n clearTimeout(this._debounceTimer)\n this._debounceTimer = null\n }\n if (this._coalesceTimer) {\n clearTimeout(this._coalesceTimer)\n this._coalesceTimer = null\n }\n this._isCoalescing = false\n }\n\n /**\n * Takes an immediate snapshot of the current editor state, cancelling\n * any pending debounced snapshot.\n * @returns {void}\n */\n snapshot() {\n if (this._debounceTimer) {\n clearTimeout(this._debounceTimer)\n this._debounceTimer = null\n }\n this._takeSnapshot()\n }\n\n /**\n * Schedules a debounced snapshot with operation coalescing.\n *\n * Rapid keystrokes within the coalesce window (default 1000ms) are\n * batched into a single undo step. The debounce timer (default 300ms)\n * fires first, but if the coalesce window hasn't expired yet, the\n * snapshot updates the top of the undo stack instead of pushing a\n * new entry. This gives the user natural undo boundaries at typing\n * pauses rather than one entry per character.\n *\n * @private\n * @returns {void}\n */\n _debouncedSnapshot() {\n if (this._debounceTimer) {\n clearTimeout(this._debounceTimer)\n }\n this._debounceTimer = setTimeout(() => {\n if (this._isCoalescing) {\n // Still within the coalesce window — update the top of the stack\n this._updateTopSnapshot()\n } else {\n // Start a new coalesce window\n this._isCoalescing = true\n this._takeSnapshot()\n if (this._coalesceTimer) clearTimeout(this._coalesceTimer)\n this._coalesceTimer = setTimeout(() => {\n this._isCoalescing = false\n }, this.coalesceMs)\n }\n }, this.debounceMs)\n }\n\n /**\n * Creates a history entry, using diff compression for large documents.\n *\n * @private\n * @param {string} html - The HTML to store\n * @param {import('./Selection.js').SelectionBookmark|null} bookmark - The selection bookmark\n * @returns {HistoryEntry}\n */\n _createEntry(html, bookmark) {\n // For short content, or when there's no previous snapshot, store full HTML\n if (html.length < DIFF_THRESHOLD || !this._lastSnapshot) {\n return { type: 'full', html, bookmark }\n }\n\n // For large content, compute and store a diff against the last snapshot\n const patch = computeDiff(this._lastSnapshot, html)\n\n // If the diff is larger than the full HTML (rare edge case), store full\n if (patch.insert.length >= html.length * 0.8) {\n return { type: 'full', html, bookmark }\n }\n\n return { type: 'diff', patch, bookmark }\n }\n\n /**\n * Resolves a history entry to its full HTML string.\n * For full entries, returns html directly. For diff entries,\n * walks backward through the stack to find the base snapshot\n * and applies diffs forward.\n *\n * @private\n * @param {HistoryEntry[]} stack - The stack (undo or redo) containing the entry\n * @param {number} index - Index of the entry in the stack\n * @returns {string} The full HTML string\n */\n _resolveEntry(stack, index) {\n const entry = stack[index]\n if (entry.type === 'full') {\n return entry.html\n }\n\n // Walk backward to find the nearest full snapshot\n let baseIndex = index - 1\n while (baseIndex >= 0 && stack[baseIndex].type !== 'full') {\n baseIndex--\n }\n\n // Start from the base full snapshot\n let html\n if (baseIndex >= 0) {\n html = stack[baseIndex].html\n } else {\n // Fallback: use lastSnapshot if no full entry found in stack\n // This shouldn't normally happen since the first entry is always full\n html = this._lastSnapshot || ''\n }\n\n // Apply diffs forward from baseIndex+1 to index\n for (let i = baseIndex + 1; i <= index; i++) {\n if (stack[i].type === 'diff') {\n html = applyDiff(html, stack[i].patch)\n } else {\n html = stack[i].html\n }\n }\n\n return html\n }\n\n /**\n * Updates the top entry on the undo stack with the current state\n * instead of pushing a new entry. Used during coalescing to batch\n * rapid keystrokes.\n * Task 237: Hash raw innerHTML directly instead of normalizing.\n * @private\n * @returns {void}\n */\n _updateTopSnapshot() {\n const html = this.engine.element.innerHTML\n const hash = djb2Hash(html)\n\n if (hash === this._lastHash && html === this._lastSnapshot) return\n\n const bookmark = this.engine.selection.save()\n\n if (this._undoStack.length > 0) {\n // Replace the top entry — always store full for the top during coalescing\n // since we're overwriting frequently\n this._undoStack[this._undoStack.length - 1] = this._createEntry(html, bookmark)\n } else {\n this._undoStack.push({ type: 'full', html, bookmark })\n }\n\n this._lastSnapshot = html\n this._lastHash = hash\n }\n\n /**\n * Captures the current editor HTML and selection bookmark, pushing it\n * onto the undo stack. Clears the redo stack. Skips if content is unchanged.\n * Task 237: Hash raw innerHTML directly instead of normalizing.\n * @private\n * @returns {void}\n */\n _takeSnapshot() {\n const html = this.engine.element.innerHTML\n\n // Fast path: compare hashes before full string comparison\n const hash = djb2Hash(html)\n if (hash === this._lastHash && html === this._lastSnapshot) return\n\n const bookmark = this.engine.selection.save()\n const entry = this._createEntry(html, bookmark)\n\n this._undoStack.push(entry)\n if (this._undoStack.length > this.maxSize) {\n this._undoStack.shift()\n }\n this._redoStack = []\n this._lastSnapshot = html\n this._lastHash = hash\n }\n\n /**\n * Temporarily disconnects the MutationObserver to prevent recursive snapshots.\n * @private\n * @returns {void}\n */\n _disconnectObserver() {\n if (this._observer) {\n this._observer.disconnect()\n }\n }\n\n /**\n * Reconnects the MutationObserver after an undo/redo operation.\n * Task 249: Removed attributes: true from observer options.\n * @private\n * @returns {void}\n */\n _reconnectObserver() {\n if (this._observer) {\n this._observer.observe(this.engine.element, {\n childList: true,\n characterData: true,\n subtree: true,\n })\n }\n }\n\n /**\n * Undoes the last change by restoring the previous state from the undo stack.\n * The current state is pushed onto the redo stack. Emits history:undo and content:change events.\n * Task 238: Skip re-sanitize and normalize+hash since content was already sanitized when snapshotted.\n * @returns {void}\n */\n undo() {\n if (!this.canUndo()) return\n\n this._isPerformingUndoRedo = true\n this._disconnectObserver()\n\n const currentHtml = this.engine.element.innerHTML\n const currentBookmark = this.engine.selection.save()\n this._redoStack.push({ type: 'full', html: currentHtml, bookmark: currentBookmark })\n\n const stateIndex = this._undoStack.length - 1\n const stateHtml = this._resolveEntry(this._undoStack, stateIndex)\n const stateBookmark = this._undoStack[stateIndex].bookmark\n this._undoStack.pop()\n\n // Task 238: Skip re-sanitize — content was already sanitized when snapshotted.\n // Skip normalize+hash since _isPerformingUndoRedo prevents observer from snapshotting.\n this.engine.element.innerHTML = stateHtml\n this._lastSnapshot = stateHtml\n this._lastHash = djb2Hash(stateHtml)\n\n if (stateBookmark) {\n this.engine.selection.restore(stateBookmark)\n }\n\n this._reconnectObserver()\n this._isPerformingUndoRedo = false\n this.engine.eventBus.emit('history:undo')\n this.engine.eventBus.emit('content:change')\n }\n\n /**\n * Redoes the last undone change by restoring state from the redo stack.\n * The current state is pushed onto the undo stack. Emits history:redo and content:change events.\n * Task 238: Skip re-sanitize and normalize+hash since content was already sanitized when snapshotted.\n * @returns {void}\n */\n redo() {\n if (!this.canRedo()) return\n\n this._isPerformingUndoRedo = true\n this._disconnectObserver()\n\n const currentHtml = this.engine.element.innerHTML\n const currentBookmark = this.engine.selection.save()\n this._undoStack.push({ type: 'full', html: currentHtml, bookmark: currentBookmark })\n\n const stateIndex = this._redoStack.length - 1\n const stateHtml = this._resolveEntry(this._redoStack, stateIndex)\n const stateBookmark = this._redoStack[stateIndex].bookmark\n this._redoStack.pop()\n\n // Task 238: Skip re-sanitize — content was already sanitized when snapshotted.\n // Skip normalize+hash since _isPerformingUndoRedo prevents observer from snapshotting.\n this.engine.element.innerHTML = stateHtml\n this._lastSnapshot = stateHtml\n this._lastHash = djb2Hash(stateHtml)\n\n if (stateBookmark) {\n this.engine.selection.restore(stateBookmark)\n }\n\n this._reconnectObserver()\n this._isPerformingUndoRedo = false\n this.engine.eventBus.emit('history:redo')\n this.engine.eventBus.emit('content:change')\n }\n\n /**\n * Checks whether there are states available to undo.\n * @returns {boolean} True if the undo stack is not empty\n */\n canUndo() {\n return this._undoStack.length > 0\n }\n\n /**\n * Checks whether there are states available to redo.\n * @returns {boolean} True if the redo stack is not empty\n */\n canRedo() {\n return this._redoStack.length > 0\n }\n\n /**\n * Clears all undo and redo history.\n * @returns {void}\n */\n clear() {\n this._undoStack = []\n this._redoStack = []\n this._lastSnapshot = null\n this._lastHash = null\n }\n}\n","let _isMac = null\n\nexport function isMac() {\n if (_isMac === null) {\n _isMac = typeof navigator !== 'undefined' && /Mac|iPod|iPhone|iPad/.test(navigator.platform)\n }\n return _isMac\n}\n\nexport function getModKey() {\n return isMac() ? '⌘' : 'Ctrl'\n}\n","import { isMac } from '../utils/platform.js'\n\nexport class KeyboardManager {\n constructor(engine) {\n this.engine = engine\n this._shortcuts = new Map()\n /** Item 17: Cache for normalized shortcuts to avoid re-computing on every keydown */\n this._normalizeCache = new Map()\n this._handleKeyDown = this._handleKeyDown.bind(this)\n }\n\n init() {\n this.engine.element.addEventListener('keydown', this._handleKeyDown)\n }\n\n destroy() {\n this.engine.element.removeEventListener('keydown', this._handleKeyDown)\n }\n\n register(shortcut, commandName) {\n const normalized = this._normalizeShortcut(shortcut)\n this._shortcuts.set(normalized, commandName)\n }\n\n unregister(shortcut) {\n const normalized = this._normalizeShortcut(shortcut)\n this._shortcuts.delete(normalized)\n }\n\n getShortcutForCommand(commandName) {\n for (const [shortcut, name] of this._shortcuts) {\n if (name === commandName) return shortcut\n }\n return null\n }\n\n getShortcutLabel(shortcut) {\n if (!shortcut) return ''\n const parts = shortcut.split('+')\n return parts.map((p) => {\n if (p === 'mod') return isMac() ? '\\u2318' : 'Ctrl'\n if (p === 'shift') return isMac() ? '\\u21E7' : 'Shift'\n if (p === 'alt') return isMac() ? '\\u2325' : 'Alt'\n return p.toUpperCase()\n }).join(isMac() ? '' : '+')\n }\n\n _normalizeShortcut(shortcut) {\n // Item 17: Use cache for normalized shortcuts\n if (this._normalizeCache.has(shortcut)) {\n return this._normalizeCache.get(shortcut)\n }\n const normalized = shortcut.toLowerCase().split('+').sort().join('+')\n this._normalizeCache.set(shortcut, normalized)\n return normalized\n }\n\n _handleKeyDown(e) {\n const parts = []\n if (isMac() ? e.metaKey : e.ctrlKey) parts.push('mod')\n if (e.shiftKey) parts.push('shift')\n if (e.altKey) parts.push('alt')\n\n const key = e.key.toLowerCase()\n if (!['control', 'meta', 'shift', 'alt'].includes(key)) {\n parts.push(key)\n }\n\n const normalized = parts.sort().join('+')\n const commandName = this._shortcuts.get(normalized)\n\n if (commandName) {\n e.preventDefault()\n e.stopPropagation()\n this.engine.commands.execute(commandName)\n }\n }\n}\n","export const ALLOWED_TAGS = {\n p: ['class', 'style'],\n h1: ['class'], h2: ['class'], h3: ['class'], h4: ['class'], h5: ['class'], h6: ['class'],\n strong: [], b: [], em: [], i: [], u: [], s: [], del: [],\n sub: [], sup: [],\n a: ['href', 'target', 'rel', 'title', 'class', 'data-attachment', 'data-filename', 'data-filesize'],\n img: ['src', 'alt', 'width', 'height', 'style', 'class'],\n ul: ['class'], ol: ['class', 'start', 'type'], li: ['class'],\n table: ['class', 'data-sort-state'], thead: [], tbody: [], tr: ['class'],\n th: ['colspan', 'rowspan', 'class', 'style', 'data-sort-dir', 'data-sort-priority', 'data-filter-value', 'data-cell-format', 'data-raw-value', 'data-formula'],\n td: ['colspan', 'rowspan', 'class', 'style', 'data-cell-format', 'data-raw-value', 'data-formula', 'data-col-width'],\n blockquote: ['class'],\n pre: ['class', 'data-language'], code: ['class', 'data-language'],\n hr: [],\n br: [],\n div: ['class', 'style', 'data-embed-url'],\n span: ['style', 'class'],\n input: ['type', 'checked', 'disabled', 'class'],\n label: ['class'],\n mark: ['class'],\n iframe: ['src', 'width', 'height', 'frameborder', 'allowfullscreen', 'sandbox', 'class', 'style'],\n}\n\nexport const ALLOWED_STYLES = [\n 'color', 'background-color', 'font-family', 'font-size',\n 'text-align', 'text-decoration', 'font-weight', 'font-style',\n 'width', 'height', 'max-width', 'float', 'margin', 'margin-left', 'margin-right',\n 'display', 'padding', 'border', 'min-width',\n]\n","import { ALLOWED_TAGS, ALLOWED_STYLES } from '../constants/schema.js'\nimport { djb2Hash } from '../utils/hash.js'\n\n// Pre-compiled regex (avoid recompilation on every href check during sanitization)\nconst JS_PROTOCOL_REGEX = /^\\s*javascript\\s*:/i\n\n// CSS value injection patterns (expression(), @import, behavior:, javascript:)\nconst CSS_INJECTION_REGEX = /expression\\s*\\(|@import|behavior\\s*:|javascript\\s*:/i\n\n// Tags whose children should be removed entirely (not just unwrapped)\nconst DANGEROUS_REMOVE_TAGS = new Set([\n 'script', 'style', 'svg', 'math', 'form', 'object', 'embed', 'applet', 'template',\n])\n\n/**\n * Default iframe src domain allowlist.\n * Only iframes whose src matches one of these hostnames (or their subdomains)\n * are permitted. All others are removed during sanitization.\n */\nconst DEFAULT_IFRAME_ALLOWED_DOMAINS = [\n 'www.youtube.com',\n 'youtube.com',\n 'www.youtube-nocookie.com',\n 'player.vimeo.com',\n 'www.dailymotion.com',\n 'geo.dailymotion.com',\n]\n\n/**\n * Check whether an iframe src URL is on an allowed domain.\n * Returns true if the URL's hostname matches or is a subdomain of an allowed domain.\n */\nfunction isAllowedIframeDomain(src, allowedDomains) {\n if (!src) return false\n try {\n const url = new URL(src)\n // Only allow https (and protocol-relative URLs which the browser resolves)\n if (url.protocol !== 'https:') return false\n const hostname = url.hostname.toLowerCase()\n return allowedDomains.some(domain => {\n const d = domain.toLowerCase()\n return hostname === d || hostname.endsWith('.' + d)\n })\n } catch {\n return false\n }\n}\n\n/**\n * @typedef {Object} SanitizerOptions\n * @property {Object} [allowedTags] - Map of allowed tag names to arrays of allowed attribute names\n * @property {string[]} [allowedStyles] - List of allowed CSS property names\n * @property {string[]} [iframeAllowedDomains] - List of allowed iframe src hostnames\n */\n\n/**\n * Sanitizes HTML content by removing disallowed tags, attributes, and styles.\n * Prevents XSS through event handler attributes, javascript: URLs, CSS injection,\n * and iframe src domain restrictions.\n */\n// LRU cache max size for sanitize results\nconst SANITIZE_CACHE_MAX = 50\n\nexport class Sanitizer {\n /**\n * Creates a new Sanitizer instance.\n * @param {SanitizerOptions} [options={}] - Sanitizer configuration\n */\n constructor(options = {}) {\n this.allowedTags = options.allowedTags || ALLOWED_TAGS\n this.allowedStyles = options.allowedStyles || ALLOWED_STYLES\n this.iframeAllowedDomains = options.iframeAllowedDomains || DEFAULT_IFRAME_ALLOWED_DOMAINS\n this._cache = new Map()\n // Task 250: Hoist DOMParser as a class property for reuse\n this._parser = new DOMParser()\n }\n\n /**\n * Sanitizes an HTML string by parsing it and removing disallowed elements,\n * attributes, event handlers, javascript: URLs, and dangerous CSS values.\n * Dangerous tags (script, style, svg, etc.) are removed with all children;\n * other disallowed tags are unwrapped (children preserved).\n * Results are cached in a simple LRU cache (max 50 entries).\n * @param {string} html - The HTML string to sanitize\n * @returns {string} The sanitized HTML string, or empty string if input is falsy\n */\n sanitize(html) {\n if (!html) return ''\n\n // Item 3: Use hash for fast lookup but verify with full string comparison on hit\n const cacheKey = djb2Hash(html)\n\n // Check LRU cache — verify full string to avoid hash collisions\n if (this._cache.has(cacheKey)) {\n const cached = this._cache.get(cacheKey)\n if (cached.input === html) {\n // Move to end (most recently used)\n this._cache.delete(cacheKey)\n this._cache.set(cacheKey, cached)\n return cached.output\n }\n // Hash collision — fall through to recompute\n }\n\n // Task 250: Use hoisted DOMParser instance\n const doc = this._parser.parseFromString(`<body>${html}</body>`, 'text/html')\n this._cleanNode(doc.body)\n const result = doc.body.innerHTML\n\n // Store in cache with both hash and original string, evict oldest if over limit\n if (this._cache.size >= SANITIZE_CACHE_MAX) {\n const firstKey = this._cache.keys().next().value\n this._cache.delete(firstKey)\n }\n this._cache.set(cacheKey, { input: html, output: result })\n\n return result\n }\n\n /**\n * Recursively cleans a DOM node by removing disallowed children, attributes,\n * and sanitizing styles and href values.\n * @private\n * @param {Node} node - The DOM node to clean\n * @returns {void}\n */\n _cleanNode(node) {\n // Task 271: Reverse iteration to avoid Array.from() allocation\n for (let i = node.childNodes.length - 1; i >= 0; i--) {\n const child = node.childNodes[i]\n if (child.nodeType === Node.TEXT_NODE) continue\n if (child.nodeType === Node.COMMENT_NODE) {\n node.removeChild(child)\n continue\n }\n if (child.nodeType !== Node.ELEMENT_NODE) {\n node.removeChild(child)\n continue\n }\n\n const tagName = child.tagName.toLowerCase()\n const allowedAttrs = this.allowedTags[tagName]\n\n if (!allowedAttrs) {\n if (DANGEROUS_REMOVE_TAGS.has(tagName)) {\n // Remove entirely including children — these tags can contain harmful structures\n node.removeChild(child)\n } else {\n // Unwrap: keep children but remove the tag\n while (child.firstChild) {\n node.insertBefore(child.firstChild, child)\n }\n node.removeChild(child)\n }\n continue\n }\n\n // Remove disallowed attributes + explicitly block on* event handlers\n const attrs = Array.from(child.attributes)\n for (const attr of attrs) {\n // Defense-in-depth: block all event handler attributes regardless of allowlist\n if (attr.name.startsWith('on')) {\n child.removeAttribute(attr.name)\n continue\n }\n if (attr.name === 'style') {\n if (allowedAttrs.includes('style')) {\n this._cleanStyles(child)\n } else {\n child.removeAttribute('style')\n }\n } else if (!allowedAttrs.includes(attr.name)) {\n child.removeAttribute(attr.name)\n }\n }\n\n // Sanitize href to prevent javascript: URLs\n if (child.hasAttribute('href')) {\n const href = child.getAttribute('href')\n if (href && JS_PROTOCOL_REGEX.test(href)) {\n child.setAttribute('href', '#')\n }\n }\n\n // Restrict <input> to type=\"checkbox\" only (prevent phishing via hidden/password/submit inputs)\n if (tagName === 'input') {\n const inputType = (child.getAttribute('type') || '').toLowerCase()\n if (inputType !== 'checkbox') {\n node.removeChild(child)\n continue\n }\n }\n\n // Restrict <iframe> src to allowed domains only (YouTube, Vimeo, Dailymotion by default)\n if (tagName === 'iframe') {\n const src = child.getAttribute('src')\n if (!isAllowedIframeDomain(src, this.iframeAllowedDomains)) {\n node.removeChild(child)\n continue\n }\n }\n\n this._cleanNode(child)\n }\n }\n\n /**\n * Cleans inline styles on an element, keeping only allowed CSS properties\n * and blocking CSS injection vectors.\n * @private\n * @param {HTMLElement} element - The element whose styles should be cleaned\n * @returns {void}\n */\n _cleanStyles(element) {\n const style = element.style\n const cleanedStyles = []\n\n for (const prop of this.allowedStyles) {\n const value = style.getPropertyValue(prop)\n if (value) {\n // Block CSS value injection vectors (expression(), @import, behavior:, javascript:)\n if (CSS_INJECTION_REGEX.test(value)) continue\n cleanedStyles.push(`${prop}: ${value}`)\n }\n }\n\n if (cleanedStyles.length > 0) {\n element.setAttribute('style', cleanedStyles.join('; '))\n } else {\n element.removeAttribute('style')\n }\n }\n}\n","/**\n * Paste cleaning pipeline for rich text from various sources.\n * Handles Microsoft Word, Google Docs, LibreOffice, Apple Pages,\n * and other rich text editors.\n *\n * Source detection runs only the relevant cleaner pipeline,\n * avoiding unnecessary regex passes for unrelated sources.\n */\n\n// Reusable DOMParser instance (avoid creating a new one per paste clean call)\nconst _parser = typeof DOMParser !== 'undefined' ? new DOMParser() : null\n\n// Pre-compiled regex patterns for list detection (hoisted from inner loop)\nconst BULLET_PATTERN = /^[\\s]*[·•●○◦▪▫–—-]\\s*/\nconst NUMBER_PATTERN = /^[\\s]*\\d+[.)]\\s*/\nconst LETTER_PATTERN = /^[\\s]*[a-zA-Z][.)]\\s*/\nconst INDENT_PATTERN = /margin-left|padding-left|text-indent/i\n// Combined list-prefix strip pattern (single regex instead of 3 separate .replace calls)\nconst LIST_PREFIX_PATTERN = /^[\\s]*(?:[·•●○◦▪▫–—-]|\\d+[.)]|[a-zA-Z][.)])\\s*/\n\nexport function cleanPastedHTML(html) {\n if (!html) return ''\n\n let cleaned = html\n\n // ── Strip meta/head wrappers that some sources include (always run) ──\n cleaned = cleaned.replace(/<meta[^>]*>/gi, '')\n cleaned = cleaned.replace(/<\\/?html[^>]*>/gi, '')\n cleaned = cleaned.replace(/<head[\\s\\S]*?<\\/head>/gi, '')\n cleaned = cleaned.replace(/<\\/?body[^>]*>/gi, '')\n cleaned = cleaned.replace(/<style[\\s\\S]*?<\\/style>/gi, '')\n cleaned = cleaned.replace(/<title[\\s\\S]*?<\\/title>/gi, '')\n // Strip dangerous embedded content (SVG can contain scripts, MathML can contain exploits)\n cleaned = cleaned.replace(/<svg[\\s\\S]*?<\\/svg>/gi, '')\n cleaned = cleaned.replace(/<math[\\s\\S]*?<\\/math>/gi, '')\n cleaned = cleaned.replace(/<object[\\s\\S]*?<\\/object>/gi, '')\n cleaned = cleaned.replace(/<link[^>]*>/gi, '')\n\n // ── Detect paste source ──\n const isWord = /mso-|class=\"Mso|<o:p|<w:/.test(html)\n const isGoogleDocs = /docs-internal|id=\"h\\.[a-z0-9]+\"/.test(html)\n const isGoogleSheets = /google-sheets-html-origin/.test(html)\n const isLibreOffice = /<text:|<office:|class=\"P\\d+\"/.test(html)\n const isApplePages = /apple-content-edited|class=\"[sp]\\d+\"/.test(html)\n\n // ── Microsoft Word cleanup (only if Word detected) ──\n if (isWord) {\n cleaned = cleaned.replace(/<!--\\[if[\\s\\S]*?endif\\]-->/gi, '')\n cleaned = cleaned.replace(/<!--[\\s\\S]*?-->/g, '')\n cleaned = cleaned.replace(/<o:p[\\s\\S]*?<\\/o:p>/gi, '')\n cleaned = cleaned.replace(/<w:[\\s\\S]*?<\\/w:[\\s\\S]*?>/gi, '')\n cleaned = cleaned.replace(/<m:[\\s\\S]*?<\\/m:[\\s\\S]*?>/gi, '')\n cleaned = cleaned.replace(/<\\/?(xml|st1|v:|o:)[^>]*>/gi, '')\n\n // Remove mso-* styles (Word-specific CSS)\n cleaned = cleaned.replace(/\\s*mso-[^:]+:[^;\"]+;?/gi, '')\n\n // Remove class=\"Mso*\" (Word paragraph classes)\n cleaned = cleaned.replace(/\\s*class=\"Mso[^\"]*\"/gi, '')\n\n // Remove Word-specific attributes\n cleaned = cleaned.replace(/\\s*lang=\"[^\"]*\"/gi, '')\n }\n\n // ── Google Docs cleanup (only if Google Docs detected) ──\n if (isGoogleDocs) {\n cleaned = cleaned.replace(/<b\\s+id=\"docs-internal[^\"]*\"[^>]*>([\\s\\S]*?)<\\/b>/gi, '$1')\n cleaned = cleaned.replace(/\\s*id=\"docs-internal[^\"]*\"/gi, '')\n cleaned = cleaned.replace(/\\s*id=\"h\\.[a-z0-9]+\"/gi, '')\n cleaned = cleaned.replace(/\\s*class=\"c\\d+\"/gi, '')\n cleaned = cleaned.replace(/\\s*dir=\"ltr\"/gi, '')\n cleaned = cleaned.replace(/\\s*role=\"presentation\"/gi, '')\n\n // Google Docs wraps content in span with inline styles for weight/style\n // Convert <span style=\"font-weight: 700\"> to <strong>\n cleaned = cleaned.replace(\n /<span\\s+style=\"[^\"]*font-weight:\\s*(700|bold)[^\"]*\">([\\s\\S]*?)<\\/span>/gi,\n '<strong>$2</strong>'\n )\n cleaned = cleaned.replace(\n /<span\\s+style=\"[^\"]*font-style:\\s*italic[^\"]*\">([\\s\\S]*?)<\\/span>/gi,\n '<em>$2</em>'\n )\n cleaned = cleaned.replace(\n /<span\\s+style=\"[^\"]*text-decoration:\\s*line-through[^\"]*\">([\\s\\S]*?)<\\/span>/gi,\n '<s>$2</s>'\n )\n }\n\n // ── LibreOffice / OpenOffice cleanup (only if LibreOffice detected) ──\n if (isLibreOffice) {\n cleaned = cleaned.replace(/<\\/?(text|office|table|draw|style|number|fo|svg):[^>]*>/gi, '')\n cleaned = cleaned.replace(/\\s*class=\"P\\d+\"/gi, '')\n cleaned = cleaned.replace(/\\s*class=\"T\\d+\"/gi, '')\n cleaned = cleaned.replace(/\\s*class=\"Table\\d+\"/gi, '')\n }\n\n // ── Google Sheets cleanup (only if Google Sheets detected) ──\n if (isGoogleSheets) {\n cleaned = cleaned.replace(/<google-sheets-html-origin[\\s\\S]*?\\/>/gi, '')\n cleaned = cleaned.replace(/<\\/?google-sheets-html-origin[^>]*>/gi, '')\n cleaned = cleaned.replace(/<col[^>]*>/gi, '')\n cleaned = cleaned.replace(/<\\/?colgroup[^>]*>/gi, '')\n }\n\n // ── Excel cleanup (Excel tables use mso- styles like Word) ──\n if (isWord && /<table/i.test(html)) {\n // Strip Excel-specific xmlns attributes\n cleaned = cleaned.replace(/\\s*xmlns:[a-z]=\"[^\"]*\"/gi, '')\n // Remove <col> and <colgroup> tags from Excel tables\n cleaned = cleaned.replace(/<col[^>]*>/gi, '')\n cleaned = cleaned.replace(/<\\/?colgroup[^>]*>/gi, '')\n }\n\n // ── Apple Pages / iWork cleanup (only if Apple Pages detected) ──\n if (isApplePages) {\n cleaned = cleaned.replace(/\\s*class=\"(s|p)\\d+\"/gi, '')\n cleaned = cleaned.replace(/<div\\s+apple-content-edited=\"true\"[^>]*>/gi, '<div>')\n }\n\n // ── Common cleanup (always run) ──\n\n // Remove @font-face declarations (may contain tracking URLs)\n cleaned = cleaned.replace(/@font-face\\s*\\{[^}]*\\}/gi, '')\n\n // Remove empty style attributes\n cleaned = cleaned.replace(/\\s*style=\"\\s*\"/gi, '')\n\n // Remove empty class attributes\n cleaned = cleaned.replace(/\\s*class=\"\\s*\"/gi, '')\n\n // Remove empty spans (no attributes)\n cleaned = cleaned.replace(/<span\\s*>([\\s\\S]*?)<\\/span>/gi, '$1')\n\n // Normalize whitespace in tags\n cleaned = cleaned.replace(/<(\\w+)\\s+>/g, '<$1>')\n\n // Remove empty paragraphs (except intentional line breaks)\n cleaned = cleaned.replace(/<p[^>]*>\\s*<\\/p>/gi, '')\n\n // Remove empty divs\n cleaned = cleaned.replace(/<div[^>]*>\\s*<\\/div>/gi, '')\n\n // ── Font tag conversion (always run) ──\n // Use flexible regexes that match attributes anywhere in the tag (not just first position)\n cleaned = cleaned.replace(/<font\\s[^>]*?face=\"([^\"]*)\"[^>]*>([\\s\\S]*?)<\\/font>/gi,\n '<span style=\"font-family: $1\">$2</span>')\n cleaned = cleaned.replace(/<font\\s[^>]*?color=\"([^\"]*)\"[^>]*>([\\s\\S]*?)<\\/font>/gi,\n '<span style=\"color: $1\">$2</span>')\n cleaned = cleaned.replace(/<font\\s[^>]*?size=\"([^\"]*)\"[^>]*>([\\s\\S]*?)<\\/font>/gi, '$2')\n cleaned = cleaned.replace(/<\\/?font[^>]*>/gi, '')\n\n // ── Normalize semantic tags (always run) ──\n cleaned = cleaned.replace(/<b(\\s|>)/gi, '<strong$1')\n cleaned = cleaned.replace(/<\\/b>/gi, '</strong>')\n cleaned = cleaned.replace(/<i(\\s|>)/gi, '<em$1')\n cleaned = cleaned.replace(/<\\/i>/gi, '</em>')\n\n // ── Convert Word-style list paragraphs to real lists (only if Word detected) ──\n if (isWord) {\n cleaned = convertWordListParagraphs(cleaned)\n }\n\n // ── Final whitespace cleanup ──\n cleaned = cleaned.replace(/(<br\\s*\\/?\\s*>){3,}/gi, '<br><br>')\n\n return cleaned.trim()\n}\n\n\n/**\n * Detect Word-style list paragraphs and convert them to proper HTML lists.\n * Word often exports lists as:\n * <p style=\"margin-left:36pt;text-indent:-18pt\">· Item text</p>\n * <p style=\"margin-left:36pt;text-indent:-18pt\">1. Item text</p>\n */\nfunction convertWordListParagraphs(html) {\n if (!_parser) return html\n const doc = _parser.parseFromString(`<body>${html}</body>`, 'text/html')\n const paragraphs = doc.querySelectorAll('p')\n\n let inList = false\n let listType = null\n let listEl = null\n\n for (const p of paragraphs) {\n const text = p.textContent.trim()\n const style = p.getAttribute('style') || ''\n const hasIndent = INDENT_PATTERN.test(style)\n\n const isBullet = BULLET_PATTERN.test(text)\n const isNumber = NUMBER_PATTERN.test(text) || LETTER_PATTERN.test(text)\n\n if ((isBullet || isNumber) && hasIndent) {\n const type = isBullet ? 'ul' : 'ol'\n\n if (!inList || listType !== type) {\n listEl = doc.createElement(type)\n p.parentNode.insertBefore(listEl, p)\n inList = true\n listType = type\n }\n\n const li = doc.createElement('li')\n // Single combined regex instead of 3 separate .replace() calls\n li.innerHTML = p.innerHTML.replace(LIST_PREFIX_PATTERN, '')\n listEl.appendChild(li)\n p.parentNode.removeChild(p)\n } else {\n inList = false\n listType = null\n listEl = null\n }\n }\n\n return doc.body.innerHTML\n}\n\n\n/**\n * Detect whether plain text looks like Markdown content.\n * Returns true if the text contains enough markdown patterns\n * to justify converting it rather than treating it as plain text.\n *\n * @param {string} text - Plain text to check\n * @returns {boolean}\n */\n// Pre-compiled markdown detection patterns (avoid recompilation per line)\nconst MD_HEADING = /^#{1,6}\\s+\\S/\nconst MD_UNORDERED = /^[-*+]\\s+\\S/\nconst MD_ORDERED = /^\\d+[.)]\\s+\\S/\nconst MD_TASK = /^[-*+]\\s+\\[[ xX]\\]\\s/\nconst MD_BLOCKQUOTE = /^>\\s/\nconst MD_HR = /^([-*_])\\1{2,}$/\nconst MD_CODE_FENCE = /^```/\nconst MD_BOLD = /\\*\\*[^*]+\\*\\*/\nconst MD_BOLD_ALT = /__[^_]+__/\nconst MD_ITALIC = /(?<!\\w)\\*[^*\\s][^*]*\\*(?!\\w)/\nconst MD_LINK = /\\[.+\\]\\(.+\\)/\nconst MD_IMAGE = /!\\[.*\\]\\(.+\\)/\nconst MD_TABLE = /^\\|.+\\|/\nconst MD_TABLE_SEP = /^\\|[\\s-:|]+\\|$/\nconst MD_INLINE_CODE = /`[^`]+`/\n\nexport function looksLikeMarkdown(text) {\n if (!text || text.length < 3) return false\n\n const lines = text.split('\\n')\n let markdownSignals = 0\n let totalNonEmptyLines = 0\n\n for (const line of lines) {\n const trimmed = line.trim()\n if (!trimmed) continue\n totalNonEmptyLines++\n\n if (MD_HEADING.test(trimmed)) { markdownSignals += 2; continue }\n if (MD_UNORDERED.test(trimmed)) { markdownSignals++; continue }\n if (MD_ORDERED.test(trimmed)) { markdownSignals++; continue }\n if (MD_TASK.test(trimmed)) { markdownSignals += 2; continue }\n if (MD_BLOCKQUOTE.test(trimmed)) { markdownSignals++; continue }\n if (MD_HR.test(trimmed)) { markdownSignals++; continue }\n if (MD_CODE_FENCE.test(trimmed)) { markdownSignals += 2; continue }\n if (MD_BOLD.test(trimmed) || MD_BOLD_ALT.test(trimmed)) { markdownSignals++; continue }\n if (MD_ITALIC.test(trimmed)) { markdownSignals++; continue }\n if (MD_LINK.test(trimmed)) { markdownSignals++; continue }\n if (MD_IMAGE.test(trimmed)) { markdownSignals += 2; continue }\n if (MD_TABLE.test(trimmed)) { markdownSignals++; continue }\n if (MD_TABLE_SEP.test(trimmed)) { markdownSignals++; continue }\n if (MD_INLINE_CODE.test(trimmed)) { markdownSignals++; continue }\n }\n\n if (totalNonEmptyLines === 0) return false\n\n // If at least 30% of non-empty lines have markdown signals,\n // or if there are 2+ signals total, it's probably markdown\n const ratio = markdownSignals / totalNonEmptyLines\n return ratio >= 0.3 || markdownSignals >= 2\n}\n","/**\n * Escapes HTML special characters in a string to prevent XSS.\n * Shared utility to avoid duplicating the pattern across modules (Task 261).\n * @param {string} str - The string to escape\n * @returns {string} The escaped string\n */\nexport function escapeHTML(str) {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n}\n\n/**\n * Escapes HTML special characters including double quotes.\n * Used in contexts where attribute values need escaping.\n * @param {string} str - The string to escape\n * @returns {string} The escaped string\n */\nexport function escapeHTMLAttr(str) {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n}\n","import { escapeHTMLAttr } from '../escapeHTML.js'\n\n/**\n * Shared utilities for document converters.\n */\n\n/**\n * Get the file extension from a filename.\n */\nexport function getExtension(filename) {\n const dot = filename.lastIndexOf('.')\n return dot >= 0 ? filename.slice(dot).toLowerCase() : ''\n}\n\n/**\n * Read a file as text.\n */\nexport function readAsText(file) {\n return new Promise((resolve, reject) => {\n const reader = new FileReader()\n reader.onload = (e) => resolve(e.target.result)\n reader.onerror = () => reject(new Error(`Failed to read file: ${file.name}`))\n reader.readAsText(file)\n })\n}\n\n/**\n * Read a file as ArrayBuffer.\n */\nexport function readAsArrayBuffer(file) {\n return new Promise((resolve, reject) => {\n const reader = new FileReader()\n reader.onload = (e) => resolve(e.target.result)\n reader.onerror = () => reject(new Error(`Failed to read file: ${file.name}`))\n reader.readAsArrayBuffer(file)\n })\n}\n\n/**\n * Escape HTML special characters.\n * Task 261: Re-exports shared escapeHTMLAttr as escapeHtml for backward compatibility.\n */\nexport function escapeHtml(str) {\n return escapeHTMLAttr(str)\n}\n","import { getExtension } from './shared.js'\nimport { cleanPastedHTML } from '../pasteClean.js'\n\n/**\n * Supported document formats with their MIME types and extensions.\n */\nconst FORMAT_MAP = {\n // DOCX\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'docx',\n // PDF\n 'application/pdf': 'pdf',\n // Markdown\n 'text/markdown': 'markdown',\n 'text/x-markdown': 'markdown',\n // HTML\n 'text/html': 'html',\n // Plain text\n 'text/plain': 'text',\n // CSV\n 'text/csv': 'csv',\n 'application/csv': 'csv',\n // RTF\n 'text/rtf': 'rtf',\n 'application/rtf': 'rtf',\n}\n\nconst EXTENSION_MAP = {\n '.docx': 'docx',\n '.pdf': 'pdf',\n '.md': 'markdown',\n '.markdown': 'markdown',\n '.html': 'html',\n '.htm': 'html',\n '.txt': 'text',\n '.csv': 'csv',\n '.tsv': 'csv',\n '.rtf': 'rtf',\n}\n\n/**\n * Detect the format of a file by MIME type and extension.\n */\nfunction detectFormat(file) {\n // Try MIME type first\n if (file.type && FORMAT_MAP[file.type]) {\n return FORMAT_MAP[file.type]\n }\n // Fall back to extension\n const ext = getExtension(file.name || '')\n return EXTENSION_MAP[ext] || null\n}\n\n/**\n * Check if a file can be converted/imported.\n */\nexport function isImportableFile(file) {\n return detectFormat(file) !== null\n}\n\n/**\n * Get the accept string for file inputs (supported extensions).\n */\nexport function getSupportedExtensions() {\n return Object.keys(EXTENSION_MAP).join(',')\n}\n\n/**\n * Get a human-readable list of supported format names.\n */\nexport function getSupportedFormatNames() {\n return ['PDF', 'DOCX', 'Markdown', 'HTML', 'TXT', 'CSV', 'TSV', 'RTF']\n}\n\n/**\n * Convert a document file to HTML.\n * Each format converter is dynamically imported for tree-shaking.\n *\n * @param {File} file - The file to convert\n * @returns {Promise<string>} The converted HTML content\n * @throws {Error} If the format is unsupported or conversion fails\n */\nexport async function convertDocument(file) {\n const format = detectFormat(file)\n\n if (!format) {\n throw new Error(`Unsupported file format: ${file.name}`)\n }\n\n const converters = {\n docx: () => import('./convertDocx.js'),\n pdf: () => import('./convertPdf.js'),\n markdown: () => import('./convertMarkdown.js'),\n html: () => import('./convertHtml.js'),\n text: () => import('./convertText.js'),\n csv: () => import('./convertCsv.js'),\n rtf: () => import('./convertRtf.js'),\n }\n\n const mod = await converters[format]()\n const raw = await mod.default(file)\n // Pre-clean imported HTML to strip script/style/object tags\n return typeof raw === 'string' ? cleanPastedHTML(raw) : raw\n}\n","export const DEFAULT_TOOLBAR = [\n ['undo', 'redo'],\n ['headings', 'fontFamily', 'fontSize'],\n ['bold', 'italic', 'underline', 'strikethrough'],\n ['foreColor', 'backColor'],\n ['alignLeft', 'alignCenter', 'alignRight', 'alignJustify'],\n ['orderedList', 'unorderedList', 'taskList'],\n ['outdent', 'indent'],\n ['link', 'image', 'attachment', 'importDocument', 'table', 'embedMedia', 'blockquote', 'codeBlock', 'horizontalRule'],\n ['subscript', 'superscript', 'removeFormat'],\n ['insertCallout', 'insertMath', 'insertToc', 'insertBookmark', 'insertMergeTag'],\n ['findReplace', 'toggleMarkdown', 'sourceMode', 'toggleAnalytics', 'toggleSpellcheck', 'distractionFree', 'toggleSplitView', 'export', 'commandPalette', 'fullscreen'],\n]\n\nexport const DEFAULT_FONTS = [\n 'Arial',\n 'Arial Black',\n 'Courier New',\n 'Georgia',\n 'Helvetica',\n 'Impact',\n 'Lucida Console',\n 'Palatino Linotype',\n 'Tahoma',\n 'Times New Roman',\n 'Trebuchet MS',\n 'Verdana',\n 'Comic Sans MS',\n 'Segoe UI',\n 'Roboto',\n 'Open Sans',\n]\n\nexport const DEFAULT_FONT_SIZES = [\n { label: '8px', value: '8px' },\n { label: '10px', value: '10px' },\n { label: '12px', value: '12px' },\n { label: '14px', value: '14px' },\n { label: '16px', value: '16px' },\n { label: '18px', value: '18px' },\n { label: '20px', value: '20px' },\n { label: '24px', value: '24px' },\n { label: '28px', value: '28px' },\n { label: '32px', value: '32px' },\n { label: '36px', value: '36px' },\n { label: '48px', value: '48px' },\n { label: '64px', value: '64px' },\n { label: '72px', value: '72px' },\n]\n\nexport const DEFAULT_COLORS = [\n '#000000', '#434343', '#666666', '#999999', '#b7b7b7', '#cccccc', '#d9d9d9', '#efefef', '#f3f3f3', '#ffffff',\n '#980000', '#ff0000', '#ff9900', '#ffff00', '#00ff00', '#00ffff', '#4a86e8', '#0000ff', '#9900ff', '#ff00ff',\n '#e6b8af', '#f4cccc', '#fce5cd', '#fff2cc', '#d9ead3', '#d0e0e3', '#c9daf8', '#cfe2f3', '#d9d2e9', '#ead1dc',\n '#dd7e6b', '#ea9999', '#f9cb9c', '#ffe599', '#b6d7a8', '#a2c4c9', '#a4c2f4', '#9fc5e8', '#b4a7d6', '#d5a6bd',\n '#cc4125', '#e06666', '#f6b26b', '#ffd966', '#93c47d', '#76a5af', '#6d9eeb', '#6fa8dc', '#8e7cc3', '#c27ba0',\n '#a61c00', '#cc0000', '#e69138', '#f1c232', '#6aa84f', '#45818e', '#3c78d8', '#3d85c6', '#674ea7', '#a64d79',\n '#85200c', '#990000', '#b45f06', '#bf9000', '#38761d', '#134f5c', '#1155cc', '#0b5394', '#351c75', '#741b47',\n '#5b0f00', '#660000', '#783f04', '#7f6000', '#274e13', '#0c343d', '#1c4587', '#073763', '#20124d', '#4c1130',\n]\n\nexport const DEFAULT_MENU_BAR = [\n { label: 'File', items: ['importDocument', 'export'] },\n { label: 'Edit', items: ['undo', 'redo', '---', 'findReplace'] },\n { label: 'View', items: ['fullscreen', 'distractionFree', 'toggleSplitView', '---', 'toggleMarkdown', 'sourceMode', '---', 'toggleAnalytics', 'toggleSpellcheck'] },\n { label: 'Insert', items: [\n 'link', 'image', 'table', 'attachment', 'embedMedia',\n '---', 'blockquote', 'codeBlock', 'horizontalRule',\n '---', 'insertCallout', 'insertMath', 'insertToc', 'insertBookmark', 'insertMergeTag',\n '---', 'addComment',\n ]},\n { label: 'Format', items: [\n 'bold', 'italic', 'underline', 'strikethrough', '---',\n 'subscript', 'superscript', '---',\n { label: 'Alignment', items: ['alignLeft', 'alignCenter', 'alignRight', 'alignJustify'] },\n '---', 'orderedList', 'unorderedList', 'taskList',\n '---', 'foreColor', 'backColor',\n '---', { label: 'Typography', items: ['lineHeight', 'letterSpacing', 'paragraphSpacing'] },\n '---', 'removeFormat',\n ]},\n]\n\n/**\n * Default maximum file size for pasted/dropped images and documents (10 MB).\n * Files exceeding this limit will be rejected with a warning.\n * Can be overridden via `options.maxFileSize` on the editor instance.\n */\nexport const DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024\n\nexport const HEADING_OPTIONS = [\n { label: 'Normal', value: 'p', tag: 'p' },\n { label: 'Heading 1', value: 'h1', tag: 'h1' },\n { label: 'Heading 2', value: 'h2', tag: 'h2' },\n { label: 'Heading 3', value: 'h3', tag: 'h3' },\n { label: 'Heading 4', value: 'h4', tag: 'h4' },\n { label: 'Heading 5', value: 'h5', tag: 'h5' },\n { label: 'Heading 6', value: 'h6', tag: 'h6' },\n]\n","import { DEFAULT_MAX_FILE_SIZE } from '../constants/defaults.js'\n\n/**\n * Check if a file exceeds the given maximum size.\n * Emits a 'file:too-large' event on the provided eventBus and logs a warning.\n *\n * @param {File} file - The file to check\n * @param {number} maxBytes - Maximum allowed file size in bytes (0 = no limit)\n * @param {{ eventBus?: { emit: Function } }} [context] - Optional context with eventBus for emitting events\n * @returns {boolean} true if the file exceeds the limit\n */\nexport function exceedsMaxFileSize(file, maxBytes, context) {\n const maxSize = maxBytes ?? DEFAULT_MAX_FILE_SIZE\n if (maxSize > 0 && file.size > maxSize) {\n const sizeMB = (file.size / (1024 * 1024)).toFixed(1)\n const limitMB = (maxSize / (1024 * 1024)).toFixed(0)\n console.warn(`[Remyx] File \"${file.name}\" (${sizeMB} MB) exceeds the ${limitMB} MB limit.`)\n if (context?.eventBus) {\n context.eventBus.emit('file:too-large', { file, maxSize })\n }\n return true\n }\n return false\n}\n","import TurndownService from 'turndown'\nimport { gfm } from 'turndown-plugin-gfm'\nimport { marked } from 'marked'\nimport { escapeHTML } from './escapeHTML.js'\n\n// Lazy singletons — zero overhead when outputFormat is 'html'\nlet turndownInstance = null\nlet markedConfigured = false\n\nfunction getTurndown() {\n if (!turndownInstance) {\n turndownInstance = new TurndownService({\n headingStyle: 'atx',\n hr: '---',\n bulletListMarker: '-',\n codeBlockStyle: 'fenced',\n fence: '```',\n emDelimiter: '*',\n strongDelimiter: '**',\n linkStyle: 'inlined',\n })\n turndownInstance.use(gfm)\n\n // Preserve <br> as markdown line breaks\n turndownInstance.addRule('lineBreak', {\n filter: 'br',\n replacement: () => ' \\n',\n })\n\n // Handle editor-specific task list checkboxes\n turndownInstance.addRule('taskCheckbox', {\n filter(node) {\n return (\n node.nodeName === 'INPUT' &&\n node.type === 'checkbox' &&\n node.classList.contains('rmx-task-checkbox')\n )\n },\n replacement(content, node) {\n return node.checked ? '[x] ' : '[ ] '\n },\n })\n\n // Preserve language identifier on fenced code blocks (data-language attribute)\n turndownInstance.addRule('fencedCodeBlockWithLanguage', {\n filter(node) {\n return (\n node.nodeName === 'PRE' &&\n node.firstChild &&\n node.firstChild.nodeName === 'CODE'\n )\n },\n replacement(content, node) {\n const code = node.firstChild\n const language = code.getAttribute('data-language') || ''\n const text = code.textContent\n return `\\n\\n\\`\\`\\`${language}\\n${text}\\n\\`\\`\\`\\n\\n`\n },\n })\n\n // Preserve underline as <u> tag in markdown (non-standard but useful)\n turndownInstance.addRule('underline', {\n filter: ['u'],\n replacement(content) {\n return `<u>${content}</u>`\n },\n })\n }\n return turndownInstance\n}\n\n// Safe URL protocol allowlist for markdown-generated links and images\nconst SAFE_PROTOCOLS = new Set(['http:', 'https:', 'mailto:', 'tel:'])\n\nfunction isSafeUrl(href) {\n if (!href) return false\n // Allow relative URLs and anchors\n if (/^(\\/|#)/.test(href)) return true\n try {\n // Decode percent-encoding to prevent bypasses like java%73cript:\n const decoded = decodeURIComponent(href)\n const url = new URL(decoded, 'https://placeholder.invalid')\n return SAFE_PROTOCOLS.has(url.protocol)\n } catch {\n return false\n }\n}\n\nfunction ensureMarkedConfigured() {\n if (!markedConfigured) {\n marked.setOptions({\n gfm: true,\n breaks: false,\n })\n\n // Override link and image renderers to block dangerous protocols (javascript:, vbscript:, data:text/html)\n const renderer = new marked.Renderer()\n renderer.link = ({ href, title, tokens }) => {\n const text = marked.Parser.parseInline(tokens)\n if (href && !isSafeUrl(href)) {\n return text\n }\n const titleAttr = title ? ` title=\"${title.replace(/\"/g, '&quot;')}\"` : ''\n return `<a href=\"${href}\"${titleAttr}>${text}</a>`\n }\n renderer.image = ({ href, title, text }) => {\n if (href && !isSafeUrl(href) && !href.startsWith('data:image/')) {\n return text || ''\n }\n const titleAttr = title ? ` title=\"${title.replace(/\"/g, '&quot;')}\"` : ''\n return `<img src=\"${href}\" alt=\"${text || ''}\"${titleAttr} />`\n }\n // Preserve language identifiers on fenced code blocks as data-language attributes\n renderer.code = ({ text, lang }) => {\n const langAttr = lang ? ` data-language=\"${lang.replace(/\"/g, '&quot;')}\"` : ''\n const escaped = escapeHTML(text)\n return `<pre${langAttr}><code${langAttr}>${escaped}</code></pre>\\n`\n }\n\n marked.use({ renderer })\n\n markedConfigured = true\n }\n}\n\n/**\n * Convert HTML to Markdown (GFM)\n * @param {string} html\n * @returns {string}\n */\nexport function htmlToMarkdown(html) {\n if (!html || html === '<p><br></p>') return ''\n return getTurndown().turndown(html)\n}\n\n/**\n * Convert Markdown to HTML\n * @param {string} md\n * @returns {string}\n */\nexport function markdownToHtml(md) {\n if (!md) return ''\n ensureMarkedConfigured()\n return marked.parse(md)\n}\n","import { looksLikeMarkdown } from './pasteClean.js'\nimport { markdownToHtml } from './markdownConverter.js'\nimport { escapeHTML } from './escapeHTML.js'\n\n/**\n * Insert plain text into the editor with smart markdown detection.\n * Shared utility used by Clipboard, DragDrop, and useContextMenu.\n * @param {import('../core/EditorEngine.js').EditorEngine} engine\n * @param {string} text\n */\nexport function insertPlainText(engine, text) {\n if (engine.outputFormat === 'markdown' || looksLikeMarkdown(text)) {\n let parsedHtml = markdownToHtml(text)\n parsedHtml = engine.sanitizer.sanitize(parsedHtml)\n engine.selection.insertHTML(parsedHtml)\n } else {\n const escaped = escapeHTML(text)\n const formatted = escaped\n .split(/\\n\\n+/)\n .map((para) => `<p>${para.replace(/\\n/g, '<br>')}</p>`)\n .join('')\n engine.selection.insertHTML(formatted || '<p><br></p>')\n }\n}\n","import { cleanPastedHTML } from '../utils/pasteClean.js'\nimport { isImportableFile, convertDocument } from '../utils/documentConverter/index.js'\nimport { exceedsMaxFileSize } from '../utils/fileValidation.js'\nimport { insertPlainText } from '../utils/insertPlainText.js'\n\nexport class Clipboard {\n constructor(engine) {\n this.engine = engine\n this._handlePaste = this._handlePaste.bind(this)\n this._handleCopy = this._handleCopy.bind(this)\n this._handleCut = this._handleCut.bind(this)\n }\n\n init() {\n this.engine.element.addEventListener('paste', this._handlePaste)\n this.engine.element.addEventListener('copy', this._handleCopy)\n this.engine.element.addEventListener('cut', this._handleCut)\n }\n\n destroy() {\n this.engine.element.removeEventListener('paste', this._handlePaste)\n this.engine.element.removeEventListener('copy', this._handleCopy)\n this.engine.element.removeEventListener('cut', this._handleCut)\n }\n\n _handlePaste(e) {\n e.preventDefault()\n this.engine.history.snapshot()\n\n const clipboardData = e.clipboardData || window.clipboardData\n let html = clipboardData.getData('text/html')\n const text = clipboardData.getData('text/plain')\n\n // Handle file paste (images and attachments)\n const files = Array.from(clipboardData.files || [])\n const imageFile = files.find((f) => f.type.startsWith('image/'))\n if (imageFile) {\n this._handleImagePaste(imageFile)\n return\n }\n\n // Handle importable document files (PDF, DOCX, etc.)\n const importableFiles = files.filter((f) => !f.type.startsWith('image/') && isImportableFile(f))\n if (importableFiles.length > 0) {\n // Serialize async conversions to prevent race conditions with insertHTML\n const validFiles = importableFiles.filter((f) => !this._exceedsMaxFileSize(f))\n let chain = Promise.resolve()\n for (const file of validFiles) {\n chain = chain.then(() =>\n convertDocument(file)\n .then((html) => {\n const sanitized = this.engine.sanitizer.sanitize(html)\n this.engine.selection.insertHTML(sanitized)\n this.engine.eventBus.emit('content:change')\n })\n .catch((err) => {\n console.warn('Document import failed on paste:', err.message)\n })\n )\n }\n return\n }\n\n // Handle non-image file paste as attachment\n const nonImageFiles = files.filter((f) => !f.type.startsWith('image/'))\n if (nonImageFiles.length > 0 && this.engine.options.uploadHandler) {\n // Serialize uploads to prevent race conditions with concurrent insertAttachment calls\n let chain = Promise.resolve()\n for (const file of nonImageFiles) {\n chain = chain.then(() =>\n this.engine.options.uploadHandler(file).then((url) => {\n this.engine.commands.execute('insertAttachment', {\n url,\n filename: file.name,\n filesize: file.size,\n })\n }).catch((err) => {\n console.error(`File upload failed for \"${file.name}\":`, err)\n this.engine.eventBus.emit('upload:error', { file, error: err })\n })\n )\n }\n return\n }\n\n // Table-aware paste: if caret is in a table cell and clipboard has TSV or table HTML\n const caretCell = this._getCaretCell()\n if (caretCell) {\n const table = caretCell.closest('table')\n if (table) {\n // Try TSV paste first\n if (text && this._looksLikeTSV(text)) {\n this._pasteIntoTable(table, caretCell, text)\n this.engine.eventBus.emit('paste', { html, text })\n this.engine.eventBus.emit('content:change')\n return\n }\n // Try HTML table paste\n if (html && /<table/i.test(html)) {\n const cleaned = cleanPastedHTML(html)\n const tsvFromHtml = this._htmlTableToTSV(cleaned)\n if (tsvFromHtml) {\n this._pasteIntoTable(table, caretCell, tsvFromHtml)\n this.engine.eventBus.emit('paste', { html, text })\n this.engine.eventBus.emit('content:change')\n return\n }\n }\n }\n }\n\n if (html) {\n // Rich text paste — clean source-specific markup, then sanitize\n html = cleanPastedHTML(html)\n html = this.engine.sanitizer.sanitize(html)\n this.engine.selection.insertHTML(html)\n } else if (text) {\n this._handleTextPaste(text)\n }\n\n this.engine.eventBus.emit('paste', { html, text })\n this.engine.eventBus.emit('content:change')\n }\n\n /**\n * Handle plain-text paste with smart format detection.\n * Task 256: Uses shared insertPlainText utility.\n */\n _handleTextPaste(text) {\n insertPlainText(this.engine, text)\n }\n\n _handleCopy(e) {\n // Table-aware copy: if selection is within a table, generate clean HTML + TSV\n const sel = window.getSelection()\n if (!sel || sel.rangeCount === 0) return\n\n const anchor = sel.anchorNode?.nodeType === Node.TEXT_NODE\n ? sel.anchorNode.parentElement : sel.anchorNode\n const focus = sel.focusNode?.nodeType === Node.TEXT_NODE\n ? sel.focusNode.parentElement : sel.focusNode\n\n const anchorTable = anchor?.closest?.('table.rmx-table')\n const focusTable = focus?.closest?.('table.rmx-table')\n\n if (anchorTable && focusTable && anchorTable === focusTable) {\n // Selection is within a single table — produce TSV + clean HTML\n const table = anchorTable\n const rows = table.querySelectorAll('thead tr, tbody tr')\n const tsvLines = []\n let htmlRows = ''\n\n for (const row of rows) {\n if (row.classList.contains('rmx-row-hidden')) continue\n const cells = row.querySelectorAll('td, th')\n const tsvCells = []\n let htmlCells = ''\n for (const cell of cells) {\n const text = cell.textContent.trim()\n // Escape TSV: if text contains tab or newline, quote it\n if (text.includes('\\t') || text.includes('\\n') || text.includes('\"')) {\n tsvCells.push('\"' + text.replace(/\"/g, '\"\"') + '\"')\n } else {\n tsvCells.push(text)\n }\n const tag = cell.tagName.toLowerCase()\n htmlCells += `<${tag}>${cell.innerHTML}</${tag}>`\n }\n tsvLines.push(tsvCells.join('\\t'))\n htmlRows += `<tr>${htmlCells}</tr>`\n }\n\n const tsv = tsvLines.join('\\n')\n const html = `<table><tbody>${htmlRows}</tbody></table>`\n\n e.preventDefault()\n e.clipboardData.setData('text/plain', tsv)\n e.clipboardData.setData('text/html', html)\n }\n // Otherwise let browser handle default copy\n }\n\n _handleCut() {\n // Capture state before the cut happens\n this.engine.history.snapshot()\n // Let browser handle default cut, just record for undo\n setTimeout(() => {\n this.engine.eventBus.emit('content:change')\n }, 0)\n }\n\n /**\n * Check if a file exceeds the configured maximum size.\n * @param {File} file\n * @returns {boolean} true if the file is too large\n */\n _exceedsMaxFileSize(file) {\n return exceedsMaxFileSize(file, this.engine.options.maxFileSize, { eventBus: this.engine.eventBus })\n }\n\n _getCaretCell() {\n const sel = window.getSelection()\n if (!sel || !sel.anchorNode) return null\n const node = sel.anchorNode.nodeType === Node.TEXT_NODE\n ? sel.anchorNode.parentElement : sel.anchorNode\n return node?.closest?.('td, th') || null\n }\n\n _looksLikeTSV(text) {\n if (!text.includes('\\t')) return false\n const lines = text.trim().split('\\n')\n if (lines.length < 1) return false\n // Check if at least first line has tabs\n return lines[0].includes('\\t')\n }\n\n _htmlTableToTSV(html) {\n const parser = new DOMParser()\n const doc = parser.parseFromString(html, 'text/html')\n const table = doc.querySelector('table')\n if (!table) return null\n const lines = []\n const rows = table.querySelectorAll('tr')\n for (const row of rows) {\n const cells = row.querySelectorAll('td, th')\n lines.push(Array.from(cells).map(c => c.textContent.trim()).join('\\t'))\n }\n return lines.join('\\n')\n }\n\n _pasteIntoTable(table, startCell, tsv) {\n const tbody = table.querySelector('tbody') || table\n const rows = tsv.trim().split('\\n').map(line => line.split('\\t'))\n const allRows = Array.from(table.querySelectorAll('tbody tr'))\n const startRow = startCell.closest('tr')\n let startRowIdx = allRows.indexOf(startRow)\n if (startRowIdx < 0) startRowIdx = 0\n let startColIdx = 0\n let prev = startCell.previousElementSibling\n while (prev) {\n startColIdx += prev.colSpan || 1\n prev = prev.previousElementSibling\n }\n\n for (let r = 0; r < rows.length; r++) {\n const rowIdx = startRowIdx + r\n // Add rows if needed\n while (allRows.length <= rowIdx) {\n const firstRow = allRows[0] || startRow\n const colCount = firstRow ? firstRow.cells.length : rows[0].length\n const newRow = document.createElement('tr')\n for (let c = 0; c < colCount; c++) {\n const td = document.createElement('td')\n td.innerHTML = '<br>'\n newRow.appendChild(td)\n }\n tbody.appendChild(newRow)\n allRows.push(newRow)\n }\n\n const tr = allRows[rowIdx]\n for (let c = 0; c < rows[r].length; c++) {\n const colIdx = startColIdx + c\n // Add columns if needed\n while (tr.cells.length <= colIdx) {\n const td = document.createElement('td')\n td.innerHTML = '<br>'\n tr.appendChild(td)\n // Also add to other rows for consistency\n allRows.forEach((otherRow, idx) => {\n if (idx !== rowIdx && otherRow.cells.length <= colIdx) {\n const otherTd = document.createElement('td')\n otherTd.innerHTML = '<br>'\n otherRow.appendChild(otherTd)\n }\n })\n }\n const cell = tr.cells[colIdx]\n if (cell) {\n cell.textContent = rows[r][c]\n }\n }\n }\n }\n\n _handleImagePaste(file) {\n if (this._exceedsMaxFileSize(file)) return\n\n if (this.engine.options.uploadHandler) {\n this.engine.options.uploadHandler(file).then((url) => {\n this.engine.commands.execute('insertImage', { src: url, alt: file.name })\n }).catch((err) => {\n console.error(`Image upload failed for \"${file.name}\":`, err)\n this.engine.eventBus.emit('upload:error', { file, error: err })\n })\n } else {\n const reader = new FileReader()\n reader.onprogress = (e) => {\n if (e.lengthComputable) {\n this.engine.eventBus.emit('upload:progress', {\n loaded: e.loaded,\n total: e.total,\n percent: Math.round((e.loaded / e.total) * 100),\n })\n }\n }\n reader.onload = (e) => {\n const src = e.target.result\n this.engine.commands.execute('insertImage', { src, alt: file.name })\n }\n reader.readAsDataURL(file)\n }\n }\n}\n","import { cleanPastedHTML } from '../utils/pasteClean.js'\nimport { isImportableFile, convertDocument } from '../utils/documentConverter/index.js'\nimport { exceedsMaxFileSize } from '../utils/fileValidation.js'\nimport { insertPlainText } from '../utils/insertPlainText.js'\n\n/** Custom MIME type for inter-editor content transfer */\nconst REMYX_MIME = 'application/x-remyx-content'\n\n/** Block-level tags that can be dragged/reordered */\nconst DRAGGABLE_BLOCKS = new Set([\n 'P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6',\n 'BLOCKQUOTE', 'PRE', 'UL', 'OL', 'TABLE', 'HR', 'DIV',\n])\n\n/** Tags whose direct children can be reordered via drag */\nconst REORDERABLE_CHILDREN = new Set(['UL', 'OL', 'TBODY', 'TABLE'])\n\n/**\n * Global registry of active DragDrop instances for inter-editor dragging.\n * Task 266: Changed from Map to WeakMap to avoid memory leaks.\n * @type {WeakMap<HTMLElement, DragDrop>}\n */\nconst _editorRegistry = new WeakMap()\n\n/**\n * Track all active DragDrop instances for iteration (WeakMap is not iterable).\n * @type {Set<DragDrop>}\n */\nconst _activeInstances = new Set()\n\nexport class DragDrop {\n constructor(engine) {\n this.engine = engine\n\n // Drag state\n this._dragSource = null // The block element being dragged\n this._dragSourceEditor = null // The DragDrop instance that owns the drag source\n this._dropTarget = null // The element we'd drop before/after\n this._dropPosition = null // 'before' | 'after'\n this._ghostEl = null // Ghost preview element\n this._dropIndicator = null // Drop indicator line element\n this._isExternalDrag = false // True when dragging from outside the editor\n this._enterCount = 0 // Counter to handle nested dragenter/dragleave\n // Task 253: Cached block positions for drag operations\n this._blockRects = null\n this._scrollHandler = null\n\n this._handleDragOver = this._handleDragOver.bind(this)\n this._handleDrop = this._handleDrop.bind(this)\n this._handleDragEnter = this._handleDragEnter.bind(this)\n this._handleDragLeave = this._handleDragLeave.bind(this)\n this._handleDragStart = this._handleDragStart.bind(this)\n this._handleDragEnd = this._handleDragEnd.bind(this)\n }\n\n init() {\n const el = this.engine.element\n el.addEventListener('dragover', this._handleDragOver)\n el.addEventListener('drop', this._handleDrop)\n el.addEventListener('dragenter', this._handleDragEnter)\n el.addEventListener('dragleave', this._handleDragLeave)\n el.addEventListener('dragstart', this._handleDragStart)\n el.addEventListener('dragend', this._handleDragEnd)\n _editorRegistry.set(el, this)\n _activeInstances.add(this)\n }\n\n destroy() {\n const el = this.engine.element\n el.removeEventListener('dragover', this._handleDragOver)\n el.removeEventListener('drop', this._handleDrop)\n el.removeEventListener('dragenter', this._handleDragEnter)\n el.removeEventListener('dragleave', this._handleDragLeave)\n el.removeEventListener('dragstart', this._handleDragStart)\n el.removeEventListener('dragend', this._handleDragEnd)\n _editorRegistry.delete(el)\n _activeInstances.delete(this)\n this._cleanupDrag()\n }\n\n // ── Public API for React layer ──────────────────────────────────────\n\n /**\n * Returns the closest draggable block element for a given DOM node.\n * Used by the React BlockDragHandle to identify what to drag.\n * @param {Node} node\n * @returns {HTMLElement|null}\n */\n getDraggableBlock(node) {\n return this._closestDraggableBlock(node)\n }\n\n /**\n * Make a block element draggable. Called by the React layer when\n * a drag handle is attached to a block.\n * @param {HTMLElement} block\n */\n makeBlockDraggable(block) {\n if (block && !block.hasAttribute('draggable')) {\n block.setAttribute('draggable', 'true')\n }\n }\n\n /**\n * Remove draggable attribute from a block element.\n * @param {HTMLElement} block\n */\n unmakeBlockDraggable(block) {\n if (block) {\n block.removeAttribute('draggable')\n }\n }\n\n /**\n * Returns true if a block drag is currently in progress.\n * @returns {boolean}\n */\n isDragging() {\n return !!this._dragSource\n }\n\n /**\n * Get the current drag state for the React layer to render overlays.\n * @returns {{ isDragging: boolean, isExternalDrag: boolean, dropTarget: HTMLElement|null, dropPosition: string|null }}\n */\n getDragState() {\n return {\n isDragging: !!this._dragSource || this._isExternalDrag,\n isExternalDrag: this._isExternalDrag,\n dropTarget: this._dropTarget,\n dropPosition: this._dropPosition,\n }\n }\n\n // ── Drag Start (internal block drags) ───────────────────────────────\n\n _handleDragStart(e) {\n // Check for reorderable sub-block items (LI, TR) first\n const reorderItem = this._getReorderableItem(e.target)\n const block = reorderItem || this._closestDraggableBlock(e.target)\n if (!block) return\n\n this._dragSource = block\n this._dragSourceEditor = this\n\n // Capture content before adding visual classes\n const html = block.outerHTML\n const text = block.textContent\n\n // Set data for inter-editor transfer\n e.dataTransfer.setData(REMYX_MIME, html)\n e.dataTransfer.setData('text/html', html)\n e.dataTransfer.setData('text/plain', text)\n e.dataTransfer.effectAllowed = 'move'\n\n // Create ghost preview\n this._createGhostPreview(block, e)\n\n // Task 253: Cache block positions at drag start\n this._cacheBlockRects()\n\n // Add dragging class to source (after capturing content)\n block.classList.add('rmx-block-dragging')\n\n // Notify all editors about the drag source\n for (const instance of _activeInstances) {\n instance._dragSourceEditor = this\n instance._dragSource = block\n }\n\n this.engine.eventBus.emit('drag:start', { block })\n }\n\n _handleDragEnd(e) {\n // Remove dragging class\n if (this._dragSource) {\n this._dragSource.classList.remove('rmx-block-dragging')\n }\n\n // Notify all editors to clean up\n for (const instance of _activeInstances) {\n instance._cleanupDrag()\n }\n\n this.engine.eventBus.emit('drag:end')\n }\n\n // ── Drag Over ───────────────────────────────────────────────────────\n\n _handleDragOver(e) {\n e.preventDefault()\n\n // Determine drop effect\n if (this._dragSource) {\n e.dataTransfer.dropEffect = 'move'\n } else {\n e.dataTransfer.dropEffect = 'copy'\n }\n\n // Calculate drop position for block reordering / insertion\n if (this._dragSource || this._isExternalDrag) {\n this._updateDropTarget(e)\n }\n }\n\n // ── Drag Enter / Leave ──────────────────────────────────────────────\n\n _handleDragEnter(e) {\n e.preventDefault()\n this._enterCount++\n\n if (this._enterCount === 1) {\n // Determine if this is an external drag (files from desktop, etc.)\n if (!this._dragSource) {\n this._isExternalDrag = true\n }\n this.engine.element.classList.add('rmx-drag-over')\n this.engine.eventBus.emit('drag:enter', {\n isExternal: this._isExternalDrag,\n types: Array.from(e.dataTransfer.types || []),\n })\n }\n }\n\n _handleDragLeave(e) {\n this._enterCount--\n\n if (this._enterCount <= 0) {\n this._enterCount = 0\n this.engine.element.classList.remove('rmx-drag-over')\n this._removeDropIndicator()\n this._dropTarget = null\n this._dropPosition = null\n\n if (this._isExternalDrag && !this._dragSource) {\n this._isExternalDrag = false\n }\n\n this.engine.eventBus.emit('drag:leave')\n }\n }\n\n // ── Drop ────────────────────────────────────────────────────────────\n\n _handleDrop(e) {\n e.preventDefault()\n this._enterCount = 0\n this.engine.element.classList.remove('rmx-drag-over')\n this._removeDropIndicator()\n\n // ── Internal block reorder / inter-editor move ──\n if (this._dragSource && this._dropTarget) {\n this._handleBlockDrop(e)\n this._cleanupDrag()\n return\n }\n\n // ── File drops ──\n const files = Array.from(e.dataTransfer.files)\n const imageFiles = files.filter((f) => f.type.startsWith('image/'))\n\n if (imageFiles.length > 0) {\n this._handleImageDrop(e, imageFiles)\n this._cleanupDrag()\n return\n }\n\n const importableFiles = files.filter((f) => !f.type.startsWith('image/') && isImportableFile(f))\n if (importableFiles.length > 0) {\n this._handleDocumentDrop(e, importableFiles)\n this._cleanupDrag()\n return\n }\n\n const nonImageFiles = files.filter((f) => !f.type.startsWith('image/'))\n if (nonImageFiles.length > 0 && this.engine.options.uploadHandler) {\n this._handleFileDrop(e, nonImageFiles)\n this._cleanupDrag()\n return\n }\n\n // ── Inter-editor content (no drag source in this editor) ──\n const remyxContent = e.dataTransfer.getData(REMYX_MIME)\n if (remyxContent && !this._dragSource) {\n this._handleInterEditorDrop(e, remyxContent)\n this._cleanupDrag()\n return\n }\n\n // ── HTML / text content drops ──\n this._setCursorAtDropPoint(e)\n this.engine.history.snapshot()\n\n const html = e.dataTransfer.getData('text/html')\n if (html) {\n let cleaned = cleanPastedHTML(html)\n cleaned = this.engine.sanitizer.sanitize(cleaned)\n this.engine.selection.insertHTML(cleaned)\n this.engine.eventBus.emit('content:change')\n } else {\n const text = e.dataTransfer.getData('text/plain')\n if (text) {\n this._handleTextDrop(text)\n this.engine.eventBus.emit('content:change')\n }\n }\n\n this.engine.eventBus.emit('drop', { files, html })\n this._cleanupDrag()\n }\n\n // ── Block drop (reorder / inter-editor move) ───────────────────────\n\n _handleBlockDrop(e) {\n const source = this._dragSource\n const target = this._dropTarget\n const position = this._dropPosition\n const sourceEditor = this._dragSourceEditor\n\n if (!source || !target || !position) return\n if (source === target) return\n\n // Take snapshots in both editors\n this.engine.history.snapshot()\n if (sourceEditor && sourceEditor !== this) {\n sourceEditor.engine.history.snapshot()\n }\n\n // Handle list item reordering\n const sourceReorderable = this._getReorderableItem(source)\n const targetReorderable = this._getReorderableItem(target)\n\n if (sourceReorderable && targetReorderable &&\n sourceReorderable.parentElement === targetReorderable.parentElement) {\n // Reordering within the same list/table\n if (position === 'before') {\n targetReorderable.parentElement.insertBefore(sourceReorderable, targetReorderable)\n } else {\n targetReorderable.parentElement.insertBefore(sourceReorderable, targetReorderable.nextSibling)\n }\n } else {\n // Block-level move\n const insertionParent = target.parentElement\n if (!insertionParent) return\n\n if (position === 'before') {\n insertionParent.insertBefore(source, target)\n } else {\n insertionParent.insertBefore(source, target.nextSibling)\n }\n }\n\n // If source was from a different editor, notify that editor\n if (sourceEditor && sourceEditor !== this) {\n sourceEditor.engine.eventBus.emit('content:change')\n }\n\n this.engine.eventBus.emit('content:change')\n this.engine.eventBus.emit('drag:reorder', { source, target, position })\n }\n\n /**\n * Handle content dropped from another Remyx editor instance.\n */\n _handleInterEditorDrop(e, html) {\n this._setCursorAtDropPoint(e)\n this.engine.history.snapshot()\n\n const sanitized = this.engine.sanitizer.sanitize(html)\n this.engine.selection.insertHTML(sanitized)\n this.engine.eventBus.emit('content:change')\n this.engine.eventBus.emit('drag:inter-editor', { html: sanitized })\n }\n\n // ── Block rect caching (Task 253) ──────────────────────────────────\n\n /**\n * Cache block positions at drag start. Invalidate on scroll.\n */\n _cacheBlockRects() {\n this._blockRects = new Map()\n const blocks = this._getTopLevelBlocks()\n for (const block of blocks) {\n this._blockRects.set(block, block.getBoundingClientRect())\n }\n // Invalidate cache on scroll\n this._scrollHandler = () => { this._blockRects = null }\n this.engine.element.addEventListener('scroll', this._scrollHandler, { passive: true })\n }\n\n /**\n * Get cached rect for a block, or compute fresh if cache invalidated.\n * @param {HTMLElement} block\n * @returns {DOMRect}\n */\n _getBlockRect(block) {\n if (this._blockRects && this._blockRects.has(block)) {\n return this._blockRects.get(block)\n }\n return block.getBoundingClientRect()\n }\n\n // ── Drop target calculation ─────────────────────────────────────────\n\n /**\n * Calculate and update the drop target based on cursor position.\n * Emits drag:indicator event for the React layer to position the indicator.\n * Task 253: Uses cached block rects instead of calling getBoundingClientRect() on every block.\n */\n _updateDropTarget(e) {\n const editorEl = this.engine.element\n\n // If dragging a reorderable item (LI/TR), iterate siblings instead of top-level blocks\n const sourceReorderable = this._dragSource ? this._getReorderableItem(this._dragSource) : null\n const blocks = sourceReorderable && sourceReorderable.parentElement\n ? Array.from(sourceReorderable.parentElement.children).filter(\n c => c.tagName === sourceReorderable.tagName\n )\n : this._getTopLevelBlocks()\n\n if (blocks.length === 0) {\n this._dropTarget = null\n this._dropPosition = null\n this._removeDropIndicator()\n return\n }\n\n const mouseY = e.clientY\n let closestBlock = null\n let closestPosition = null\n let closestDistance = Infinity\n\n for (const block of blocks) {\n // Skip the block being dragged\n if (block === this._dragSource) continue\n\n const rect = this._getBlockRect(block)\n const midY = rect.top + rect.height / 2\n\n const distTop = Math.abs(mouseY - rect.top)\n const distBottom = Math.abs(mouseY - rect.bottom)\n\n if (distTop < closestDistance) {\n closestDistance = distTop\n closestBlock = block\n closestPosition = 'before'\n }\n if (distBottom < closestDistance) {\n closestDistance = distBottom\n closestBlock = block\n closestPosition = 'after'\n }\n\n // Also check if mouse is within block — pick top or bottom half\n if (mouseY >= rect.top && mouseY <= rect.bottom) {\n closestBlock = block\n closestPosition = mouseY < midY ? 'before' : 'after'\n closestDistance = 0\n break\n }\n }\n\n if (closestBlock !== this._dropTarget || closestPosition !== this._dropPosition) {\n this._dropTarget = closestBlock\n this._dropPosition = closestPosition\n\n if (closestBlock) {\n this._showDropIndicator(closestBlock, closestPosition)\n } else {\n this._removeDropIndicator()\n }\n }\n }\n\n /**\n * Get all top-level block elements in the editor.\n * @returns {HTMLElement[]}\n */\n _getTopLevelBlocks() {\n const blocks = []\n const el = this.engine.element\n for (let i = 0; i < el.children.length; i++) {\n const child = el.children[i]\n if (child.nodeType === Node.ELEMENT_NODE &&\n !child.classList.contains('rmx-drop-indicator') &&\n !child.classList.contains('rmx-drag-ghost')) {\n blocks.push(child)\n }\n }\n return blocks\n }\n\n // ── Drop indicator ──────────────────────────────────────────────────\n\n _showDropIndicator(targetBlock, position) {\n if (!this._dropIndicator) {\n this._dropIndicator = document.createElement('div')\n this._dropIndicator.className = 'rmx-drop-indicator'\n this._dropIndicator.setAttribute('aria-hidden', 'true')\n }\n\n const editorEl = this.engine.element\n const editorRect = editorEl.getBoundingClientRect()\n const blockRect = targetBlock.getBoundingClientRect()\n\n // Position the indicator as a horizontal line\n const top = position === 'before'\n ? blockRect.top - editorRect.top + editorEl.scrollTop\n : blockRect.bottom - editorRect.top + editorEl.scrollTop\n\n this._dropIndicator.style.top = `${top}px`\n\n if (!this._dropIndicator.parentElement) {\n editorEl.appendChild(this._dropIndicator)\n }\n\n this.engine.eventBus.emit('drag:indicator', {\n target: targetBlock,\n position,\n top,\n })\n }\n\n _removeDropIndicator() {\n if (this._dropIndicator && this._dropIndicator.parentElement) {\n this._dropIndicator.parentElement.removeChild(this._dropIndicator)\n }\n this._dropIndicator = null\n }\n\n // ── Ghost preview ───────────────────────────────────────────────────\n\n _createGhostPreview(block, e) {\n const ghost = block.cloneNode(true)\n ghost.className = 'rmx-drag-ghost'\n ghost.style.position = 'absolute'\n ghost.style.top = '-9999px'\n ghost.style.left = '-9999px'\n ghost.style.width = `${block.offsetWidth}px`\n ghost.style.pointerEvents = 'none'\n\n document.body.appendChild(ghost)\n this._ghostEl = ghost\n\n // Use the ghost as the drag image\n const rect = block.getBoundingClientRect()\n const offsetX = e.clientX - rect.left\n const offsetY = e.clientY - rect.top\n e.dataTransfer.setDragImage(ghost, offsetX, offsetY)\n\n // The browser captures the ghost image synchronously during dragstart,\n // so we keep it in the DOM until drag end (_removeGhostPreview in _cleanupDrag).\n // No further action needed here per frame.\n }\n\n _removeGhostPreview() {\n if (this._ghostEl && this._ghostEl.parentElement) {\n this._ghostEl.parentElement.removeChild(this._ghostEl)\n }\n this._ghostEl = null\n }\n\n // ── Helpers ─────────────────────────────────────────────────────────\n\n /**\n * Find the closest draggable block from a given node.\n * @param {Node} node\n * @returns {HTMLElement|null}\n */\n _closestDraggableBlock(node) {\n let el = node.nodeType === Node.TEXT_NODE ? node.parentElement : node\n const editorEl = this.engine.element\n while (el && el !== editorEl) {\n if (el.parentElement === editorEl && DRAGGABLE_BLOCKS.has(el.tagName)) {\n return el\n }\n el = el.parentElement\n }\n return null\n }\n\n /**\n * For reordering, get the reorderable item (LI or TR) from a node.\n * @param {Node} node\n * @returns {HTMLElement|null}\n */\n _getReorderableItem(node) {\n let el = node.nodeType === Node.TEXT_NODE ? node.parentElement : node\n const editorEl = this.engine.element\n while (el && el !== editorEl) {\n if (el.tagName === 'LI' || el.tagName === 'TR') {\n // Verify parent is a reorderable container\n if (el.parentElement && REORDERABLE_CHILDREN.has(el.parentElement.tagName)) {\n return el\n }\n }\n el = el.parentElement\n }\n return null\n }\n\n /**\n * Clean up all drag state.\n */\n _cleanupDrag() {\n if (this._dragSource) {\n this._dragSource.classList.remove('rmx-block-dragging')\n }\n this._dragSource = null\n this._dragSourceEditor = null\n this._dropTarget = null\n this._dropPosition = null\n this._isExternalDrag = false\n this._enterCount = 0\n // Task 253: Clean up block rect cache\n this._blockRects = null\n if (this._scrollHandler) {\n this.engine.element.removeEventListener('scroll', this._scrollHandler)\n this._scrollHandler = null\n }\n this._removeDropIndicator()\n this._removeGhostPreview()\n this.engine.element.classList.remove('rmx-drag-over')\n }\n\n /**\n * Check if a file exceeds the configured maximum size.\n * @param {File} file\n * @returns {boolean} true if the file is too large\n */\n _exceedsMaxFileSize(file) {\n return exceedsMaxFileSize(file, this.engine.options.maxFileSize, { eventBus: this.engine.eventBus })\n }\n\n _handleImageDrop(e, imageFiles) {\n this._setCursorAtDropPoint(e)\n this.engine.history.snapshot()\n\n const validFiles = imageFiles.filter((f) => !this._exceedsMaxFileSize(f))\n // Serialize uploads to prevent race conditions with concurrent insertImage calls\n let chain = Promise.resolve()\n for (const file of validFiles) {\n chain = chain.then(() => {\n if (this.engine.options.uploadHandler) {\n return this.engine.options.uploadHandler(file).then((url) => {\n this.engine.commands.execute('insertImage', { src: url, alt: file.name })\n }).catch((err) => {\n console.error(`Image upload failed for \"${file.name}\":`, err)\n this.engine.eventBus.emit('upload:error', { file, error: err })\n })\n } else {\n return new Promise((resolve) => {\n const reader = new FileReader()\n reader.onprogress = (ev) => {\n if (ev.lengthComputable) {\n this.engine.eventBus.emit('upload:progress', {\n loaded: ev.loaded,\n total: ev.total,\n percent: Math.round((ev.loaded / ev.total) * 100),\n })\n }\n }\n reader.onload = (ev) => {\n this.engine.commands.execute('insertImage', { src: ev.target.result, alt: file.name })\n resolve()\n }\n reader.onerror = () => resolve()\n reader.readAsDataURL(file)\n })\n }\n })\n }\n }\n\n _handleDocumentDrop(e, files) {\n this._setCursorAtDropPoint(e)\n this.engine.history.snapshot()\n\n files.filter((f) => !this._exceedsMaxFileSize(f)).forEach((file) => {\n convertDocument(file)\n .then((html) => {\n const sanitized = this.engine.sanitizer.sanitize(html)\n this.engine.selection.insertHTML(sanitized)\n this.engine.eventBus.emit('content:change')\n })\n .catch((err) => {\n console.warn('Document import failed on drop:', err.message)\n })\n })\n }\n\n _handleFileDrop(e, files) {\n this._setCursorAtDropPoint(e)\n this.engine.history.snapshot()\n\n // Serialize uploads to prevent race conditions with concurrent insertAttachment calls\n let chain = Promise.resolve()\n for (const file of files) {\n chain = chain.then(() =>\n this.engine.options.uploadHandler(file).then((url) => {\n this.engine.commands.execute('insertAttachment', {\n url,\n filename: file.name,\n filesize: file.size,\n })\n }).catch((err) => {\n console.error(`File upload failed for \"${file.name}\":`, err)\n this.engine.eventBus.emit('upload:error', { file, error: err })\n })\n )\n }\n }\n\n _setCursorAtDropPoint(e) {\n const range = document.caretRangeFromPoint\n ? document.caretRangeFromPoint(e.clientX, e.clientY)\n : null\n\n if (range) {\n const sel = window.getSelection()\n sel.removeAllRanges()\n sel.addRange(range)\n }\n }\n\n /**\n * Handle dropped plain text with smart format detection.\n * Task 256: Uses shared insertPlainText utility.\n */\n _handleTextDrop(text) {\n insertPlainText(this.engine, text)\n }\n}\n\n/**\n * Expose the editor registry for testing purposes.\n * @returns {Map<HTMLElement, DragDrop>}\n */\nDragDrop._getRegistry = () => _editorRegistry\nDragDrop._getActiveInstances = () => _activeInstances\n","/**\n * @typedef {Object} PluginAPI\n * @property {HTMLElement} element - The editor DOM element (read-only)\n * @property {Function} executeCommand - Execute a registered command by name\n * @property {Function} on - Subscribe to an editor event\n * @property {Function} off - Unsubscribe from an editor event\n * @property {Function} getSelection - Get the current window Selection\n * @property {Function} getRange - Get the current selection Range\n * @property {Function} getActiveFormats - Get current active formatting states\n * @property {Function} getHTML - Get sanitized HTML content\n * @property {Function} getText - Get plain text content\n * @property {Function} isEmpty - Check if editor is empty\n * @property {Object} options - Editor options (read-only copy)\n * @property {Function} getSetting - Get a plugin-scoped setting value\n * @property {Function} setSetting - Set a plugin-scoped setting value\n */\n\n/**\n * @typedef {Object} PluginDefinition\n * @property {string} name - Unique plugin name\n * @property {string} [version='0.0.0'] - Plugin version\n * @property {string} [description=''] - Human-readable description\n * @property {string} [author=''] - Author\n * @property {string[]} [dependencies=[]] - Plugin names this plugin depends on\n * @property {boolean} [requiresFullAccess=false] - If true, receives full engine instead of restricted API\n * @property {boolean} [lazy=false] - If true, plugin is not initialized in initAll() but on first use\n * @property {Function} [init] - Initialization function, receives the plugin API or full engine\n * @property {Function} [destroy] - Cleanup function, receives the plugin API or full engine\n * @property {Function|null} [onContentChange] - Called on content:change events\n * @property {Function|null} [onSelectionChange] - Called on selectionchange events\n * @property {Array<import('../core/CommandRegistry.js').CommandDefinition>} [commands] - Commands to register\n * @property {Array} [toolbarItems] - Toolbar item definitions\n * @property {Array} [statusBarItems] - Status bar item definitions\n * @property {Array} [contextMenuItems] - Context menu item definitions\n * @property {Array} [settingsSchema] - Schema for plugin-specific settings\n * @property {Object} [defaultSettings] - Default setting values\n */\n\n/**\n * Creates a restricted API facade for plugins.\n * Plugins receive this facade instead of the full engine reference,\n * limiting what they can access to prevent accidental or malicious\n * bypass of sanitization, history corruption, or content exfiltration.\n *\n * @param {import('../core/EditorEngine.js').EditorEngine} engine - The full editor engine\n * @param {string} pluginName - The plugin name (for scoped settings)\n * @param {PluginManager} manager - The plugin manager (for settings access)\n * @returns {PluginAPI} A restricted API surface\n */\nfunction createPluginAPI(engine, pluginName, manager) {\n return {\n /** The editor DOM element (read-only access) */\n get element() { return engine.element },\n\n /**\n * Execute a registered command by name.\n * @param {string} name - The command name\n * @param {...*} args - Additional arguments for the command\n * @returns {*} The command result\n */\n executeCommand(name, ...args) { return engine.commands.execute(name, ...args) },\n\n /**\n * Subscribe to an editor event.\n * @param {string} event - The event name\n * @param {Function} handler - The event handler\n * @returns {Function} An unsubscribe function\n */\n on(event, handler) { return engine.eventBus.on(event, handler) },\n\n /**\n * Unsubscribe from an editor event.\n * @param {string} event - The event name\n * @param {Function} handler - The handler to remove\n * @returns {void}\n */\n off(event, handler) { engine.eventBus.off(event, handler) },\n\n /**\n * Get the current window Selection object.\n * @returns {globalThis.Selection} The browser Selection\n */\n getSelection() { return engine.selection.getSelection() },\n\n /**\n * Get the current selection Range within the editor.\n * @returns {Range|null} The current range, or null\n */\n getRange() { return engine.selection.getRange() },\n\n /**\n * Get current active formatting states at the selection.\n * @returns {import('../core/Selection.js').ActiveFormats} Active format states\n */\n getActiveFormats() { return engine.selection.getActiveFormats() },\n\n /**\n * Get sanitized HTML content of the editor.\n * @returns {string} The sanitized HTML\n */\n getHTML() { return engine.getHTML() },\n\n /**\n * Get plain text content of the editor.\n * @returns {string} The text content\n */\n getText() { return engine.getText() },\n\n /**\n * Check if the editor content is empty.\n * @returns {boolean} True if empty\n */\n isEmpty() { return engine.isEmpty() },\n\n /** Editor options (read-only copy) */\n get options() { return { ...engine.options } },\n\n /**\n * Get a plugin-scoped setting value.\n * @param {string} key - Setting key\n * @returns {*} The setting value\n */\n getSetting(key) { return manager.getPluginSetting(pluginName, key) },\n\n /**\n * Set a plugin-scoped setting value.\n * @param {string} key - Setting key\n * @param {*} value - The value to set\n */\n setSetting(key, value) { manager.setPluginSetting(pluginName, key, value) },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Plugin Registry — discover and list available plugins\n// ---------------------------------------------------------------------------\n\n/**\n * @typedef {Object} PluginRegistryEntry\n * @property {string} name - Plugin identifier\n * @property {string} version - Version string\n * @property {string} description - What the plugin does\n * @property {string} author - Author name\n * @property {string[]} [tags] - Search tags\n * @property {Function} [factory] - Factory function that returns the plugin definition\n */\n\n/** @type {Map<string, PluginRegistryEntry>} */\nconst _globalRegistry = new Map()\n\n/**\n * Register a plugin in the global registry for discovery.\n * This does NOT install the plugin — it makes it discoverable via `listRegisteredPlugins`.\n * @param {PluginRegistryEntry} entry\n */\nexport function registerPluginInRegistry(entry) {\n if (!entry || !entry.name) return\n _globalRegistry.set(entry.name, entry)\n}\n\n/**\n * Remove a plugin from the global registry.\n * @param {string} name\n * @returns {boolean}\n */\nexport function unregisterPluginFromRegistry(name) {\n return _globalRegistry.delete(name)\n}\n\n/**\n * List all plugins in the global registry.\n * @returns {PluginRegistryEntry[]}\n */\nexport function listRegisteredPlugins() {\n return Array.from(_globalRegistry.values())\n}\n\n/**\n * Search the global registry by name, description, or tags.\n * @param {string} query\n * @returns {PluginRegistryEntry[]}\n */\nexport function searchPluginRegistry(query) {\n if (!query) return listRegisteredPlugins()\n const q = query.toLowerCase()\n return listRegisteredPlugins().filter(e =>\n e.name.toLowerCase().includes(q) ||\n (e.description || '').toLowerCase().includes(q) ||\n (e.tags || []).some(t => t.toLowerCase().includes(q))\n )\n}\n\n// ---------------------------------------------------------------------------\n// PluginManager\n// ---------------------------------------------------------------------------\n\n/**\n * Manages editor plugins.\n *\n * **Security notice:** Plugins receive a restricted API facade by default,\n * which limits access to safe operations (executing commands, subscribing\n * to events, reading content). The full engine reference is NOT exposed.\n *\n * If a plugin requires full engine access (e.g., built-in plugins), it can\n * declare `requiresFullAccess: true` in its definition — but third-party\n * plugins should be audited before granting this level of access.\n *\n * **Lazy loading:** Plugins can declare `lazy: true` to defer initialization\n * until first use. Lazy plugins are registered and their commands are\n * available, but init() is not called until the plugin is explicitly\n * activated or one of its commands is executed.\n *\n * **Dependencies:** Plugins can declare `dependencies: ['otherPlugin']` to\n * ensure they are initialized after their dependencies. Circular dependencies\n * are detected and reported.\n *\n * **Lifecycle hooks:** Beyond `init`/`destroy`, plugins can declare\n * `onContentChange` and `onSelectionChange` callbacks. These are wired\n * automatically during initialization.\n *\n * **Scoped settings:** Plugins can declare a `settingsSchema` and\n * `defaultSettings`. Settings are stored per-plugin and accessible via\n * `getSetting`/`setSetting` on the plugin API, or `getPluginSetting`/\n * `setPluginSetting` on the manager.\n *\n * **Sandboxing:** Each plugin's `init`, `destroy`, `onContentChange`, and\n * `onSelectionChange` calls are wrapped in try/catch. A single failing\n * plugin cannot crash the editor or other plugins.\n */\nexport class PluginManager {\n /**\n * Creates a new PluginManager.\n * @param {import('../core/EditorEngine.js').EditorEngine} engine - The editor engine instance\n */\n constructor(engine) {\n this.engine = engine\n this._plugins = new Map()\n /** @private Set of plugin names that have been initialized */\n this._initialized = new Set()\n /** @private Map of command name -> plugin name for lazy activation */\n this._commandPluginMap = new Map()\n /** @private Per-plugin settings store: Map<pluginName, Map<key, value>> */\n this._settings = new Map()\n /** @private Unsub functions for lifecycle hooks */\n this._lifecycleUnsubs = new Map()\n }\n\n // -----------------------------------------------------------------------\n // Plugin Settings\n // -----------------------------------------------------------------------\n\n /**\n * Get a plugin-scoped setting value.\n * Falls back to the plugin's defaultSettings if not explicitly set.\n * @param {string} pluginName\n * @param {string} key\n * @returns {*}\n */\n getPluginSetting(pluginName, key) {\n const settings = this._settings.get(pluginName)\n if (settings && settings.has(key)) return settings.get(key)\n const plugin = this._plugins.get(pluginName)\n if (plugin?.defaultSettings && key in plugin.defaultSettings) {\n return plugin.defaultSettings[key]\n }\n return undefined\n }\n\n /**\n * Set a plugin-scoped setting value.\n * Validates against the settingsSchema if present.\n * @param {string} pluginName\n * @param {string} key\n * @param {*} value\n * @returns {boolean} true if the setting was accepted\n */\n setPluginSetting(pluginName, key, value) {\n const plugin = this._plugins.get(pluginName)\n if (!plugin) return false\n\n // Validate against schema if available\n if (plugin.settingsSchema && plugin.settingsSchema.length > 0) {\n const schema = plugin.settingsSchema.find(s => s.key === key)\n if (schema) {\n // Type check\n if (schema.type === 'boolean' && typeof value !== 'boolean') return false\n if (schema.type === 'number' && typeof value !== 'number') return false\n if (schema.type === 'string' && typeof value !== 'string') return false\n if (schema.type === 'select') {\n const validValues = (schema.options || []).map(o => o.value)\n if (!validValues.includes(value)) return false\n }\n // Custom validation\n if (schema.validate && !schema.validate(value)) return false\n }\n }\n\n if (!this._settings.has(pluginName)) {\n this._settings.set(pluginName, new Map())\n }\n this._settings.get(pluginName).set(key, value)\n this.engine.eventBus.emit('plugin:settingChanged', { pluginName, key, value })\n return true\n }\n\n /**\n * Get all settings for a plugin as a plain object.\n * @param {string} pluginName\n * @returns {Object}\n */\n getPluginSettings(pluginName) {\n const plugin = this._plugins.get(pluginName)\n const result = { ...(plugin?.defaultSettings || {}) }\n const settings = this._settings.get(pluginName)\n if (settings) {\n for (const [k, v] of settings) result[k] = v\n }\n return result\n }\n\n // -----------------------------------------------------------------------\n // Dependency Resolution\n // -----------------------------------------------------------------------\n\n /**\n * Resolve plugin initialization order based on dependencies.\n * Uses topological sort. Detects circular dependencies.\n * @param {PluginDefinition[]} plugins - Plugins to sort\n * @returns {{ sorted: PluginDefinition[], circular: string[][] }} sorted list and any circular deps\n */\n _resolveDependencyOrder(plugins) {\n const nameSet = new Set(plugins.map(p => p.name))\n const graph = new Map() // name -> [dependency names]\n const inDegree = new Map()\n\n for (const p of plugins) {\n // Only include dependencies that are actually registered\n const deps = (p.dependencies || []).filter(d => nameSet.has(d))\n graph.set(p.name, deps)\n inDegree.set(p.name, deps.length)\n }\n\n // Kahn's algorithm for topological sort\n const queue = []\n for (const [name, degree] of inDegree) {\n if (degree === 0) queue.push(name)\n }\n\n const sorted = []\n while (queue.length > 0) {\n const name = queue.shift()\n sorted.push(name)\n // For each plugin that depends on `name`, decrement its in-degree\n for (const [other, deps] of graph) {\n if (deps.includes(name)) {\n inDegree.set(other, inDegree.get(other) - 1)\n if (inDegree.get(other) === 0) queue.push(other)\n }\n }\n }\n\n // Detect circular dependencies\n const circular = []\n if (sorted.length < plugins.length) {\n const remaining = plugins.filter(p => !sorted.includes(p.name)).map(p => p.name)\n circular.push(remaining)\n }\n\n const pluginMap = new Map(plugins.map(p => [p.name, p]))\n return {\n sorted: sorted.map(name => pluginMap.get(name)),\n circular,\n }\n }\n\n // -----------------------------------------------------------------------\n // Registration\n // -----------------------------------------------------------------------\n\n /**\n * Registers a plugin and its commands. Does nothing if the plugin has no\n * name or is already registered.\n *\n * For lazy plugins, commands are wrapped to trigger plugin initialization\n * on first execution.\n *\n * @param {PluginDefinition} plugin - The plugin definition to register\n * @returns {void}\n */\n register(plugin) {\n if (!plugin || !plugin.name) {\n console.warn('Plugin must have a name')\n return\n }\n if (this._plugins.has(plugin.name)) {\n console.warn(`Plugin \"${plugin.name}\" already registered`)\n return\n }\n this._plugins.set(plugin.name, plugin)\n\n // Initialize default settings\n if (plugin.defaultSettings) {\n if (!this._settings.has(plugin.name)) {\n this._settings.set(plugin.name, new Map())\n }\n }\n\n // Register any commands the plugin provides\n if (plugin.commands) {\n plugin.commands.forEach((cmd) => {\n if (plugin.lazy) {\n // Track which plugin owns this command for lazy activation\n this._commandPluginMap.set(cmd.name, plugin.name)\n\n // Wrap the command execute to auto-init the plugin on first use\n const originalExecute = cmd.execute\n const self = this\n const wrappedCmd = {\n ...cmd,\n execute(...args) {\n self.activatePlugin(plugin.name)\n // After activation, re-register with original execute\n // so subsequent calls skip the wrapper\n self.engine.commands.register(cmd.name, { ...cmd, execute: originalExecute })\n return originalExecute.apply(this, args)\n }\n }\n this.engine.commands.register(cmd.name, wrappedCmd)\n } else {\n this.engine.commands.register(cmd.name, cmd)\n }\n })\n }\n\n this.engine.eventBus.emit('plugin:registered', { name: plugin.name })\n }\n\n // -----------------------------------------------------------------------\n // Initialization\n // -----------------------------------------------------------------------\n\n /**\n * Initializes a single plugin by calling its init function and\n * wiring lifecycle hooks (onContentChange, onSelectionChange).\n * Trusted plugins (requiresFullAccess) receive the full engine;\n * others receive the restricted API facade.\n *\n * @private\n * @param {PluginDefinition} plugin - The plugin to initialize\n * @returns {void}\n */\n _initPlugin(plugin) {\n if (this._initialized.has(plugin.name)) return\n\n try {\n const api = plugin.requiresFullAccess\n ? this.engine\n : createPluginAPI(this.engine, plugin.name, this)\n\n if (plugin.init) {\n plugin.init(api)\n }\n\n // Wire lifecycle hooks\n const unsubs = []\n\n if (typeof plugin.onContentChange === 'function') {\n const handler = () => {\n try { plugin.onContentChange(api) }\n catch (err) {\n console.error(`Plugin \"${plugin.name}\" onContentChange error:`, err)\n this.engine.eventBus.emit('plugin:error', { name: plugin.name, error: err, hook: 'onContentChange' })\n }\n }\n unsubs.push(this.engine.eventBus.on('content:change', handler))\n }\n\n if (typeof plugin.onSelectionChange === 'function') {\n const handler = () => {\n try { plugin.onSelectionChange(api) }\n catch (err) {\n console.error(`Plugin \"${plugin.name}\" onSelectionChange error:`, err)\n this.engine.eventBus.emit('plugin:error', { name: plugin.name, error: err, hook: 'onSelectionChange' })\n }\n }\n unsubs.push(this.engine.eventBus.on('selection:change', handler))\n }\n\n if (unsubs.length > 0) {\n this._lifecycleUnsubs.set(plugin.name, unsubs)\n }\n\n this._initialized.add(plugin.name)\n this.engine.eventBus.emit('plugin:initialized', { name: plugin.name })\n } catch (err) {\n console.error(`Error initializing plugin \"${plugin.name}\":`, err)\n this.engine.eventBus.emit('plugin:error', { name: plugin.name, error: err })\n }\n }\n\n /**\n * Activates a lazy plugin by initializing it. Has no effect if the plugin\n * is already initialized or does not exist.\n *\n * @param {string} name - The plugin name to activate\n * @returns {void}\n */\n activatePlugin(name) {\n const plugin = this._plugins.get(name)\n if (!plugin) {\n console.warn(`Plugin \"${name}\" not found`)\n return\n }\n if (this._initialized.has(name)) return\n\n // Initialize dependencies first\n for (const dep of (plugin.dependencies || [])) {\n if (this._plugins.has(dep) && !this._initialized.has(dep)) {\n this.activatePlugin(dep)\n }\n }\n\n this._initPlugin(plugin)\n }\n\n /**\n * Initializes all registered plugins by calling their init functions.\n * Plugins are initialized in dependency order. Lazy plugins are skipped\n * and will be initialized on first use. Circular dependencies are\n * detected, reported, and the involved plugins are still initialized\n * (in registration order) with a warning.\n *\n * @returns {void}\n */\n initAll() {\n const nonLazy = []\n this._plugins.forEach((plugin) => {\n if (!plugin.lazy) nonLazy.push(plugin)\n })\n\n const { sorted, circular } = this._resolveDependencyOrder(nonLazy)\n\n // Report circular dependencies\n if (circular.length > 0) {\n for (const cycle of circular) {\n console.warn(`Circular plugin dependencies detected: ${cycle.join(' → ')}`)\n this.engine.eventBus.emit('plugin:circularDependency', { plugins: cycle })\n }\n }\n\n // Initialize in dependency-resolved order\n for (const plugin of sorted) {\n this._initPlugin(plugin)\n }\n\n // Initialize any plugins not in sorted (circular deps) in registration order\n for (const plugin of nonLazy) {\n if (!this._initialized.has(plugin.name)) {\n this._initPlugin(plugin)\n }\n }\n }\n\n // -----------------------------------------------------------------------\n // Destruction\n // -----------------------------------------------------------------------\n\n /**\n * Destroys all registered plugins by calling their destroy functions,\n * cleaning up lifecycle hook subscriptions, and clearing all state.\n * Errors are caught, logged, and emitted as plugin:error events.\n * @returns {void}\n */\n destroyAll() {\n this._plugins.forEach((plugin) => {\n try {\n // For lazy plugins, only call destroy if they were initialized.\n // For non-lazy plugins, always call destroy (original behavior).\n const shouldDestroy = plugin.lazy\n ? this._initialized.has(plugin.name)\n : true\n if (shouldDestroy && plugin.destroy) {\n const api = plugin.requiresFullAccess\n ? this.engine\n : createPluginAPI(this.engine, plugin.name, this)\n plugin.destroy(api)\n }\n\n // Clean up lifecycle hook subscriptions\n const unsubs = this._lifecycleUnsubs.get(plugin.name)\n if (unsubs) {\n for (const unsub of unsubs) unsub?.()\n }\n } catch (err) {\n console.error(`Error destroying plugin \"${plugin.name}\":`, err)\n this.engine.eventBus.emit('plugin:error', { name: plugin.name, error: err })\n }\n })\n this._plugins.clear()\n this._initialized.clear()\n this._commandPluginMap.clear()\n this._settings.clear()\n this._lifecycleUnsubs.clear()\n }\n\n // -----------------------------------------------------------------------\n // Query methods\n // -----------------------------------------------------------------------\n\n /**\n * Returns a registered plugin by name.\n * @param {string} name - The plugin name\n * @returns {PluginDefinition|undefined} The plugin definition, or undefined if not found\n */\n get(name) {\n return this._plugins.get(name)\n }\n\n /**\n * Returns all registered plugins as an array.\n * @returns {PluginDefinition[]} Array of all registered plugin definitions\n */\n getAll() {\n return Array.from(this._plugins.values())\n }\n\n /**\n * Checks whether a plugin with the given name is registered.\n * @param {string} name - The plugin name\n * @returns {boolean} True if the plugin is registered\n */\n has(name) {\n return this._plugins.has(name)\n }\n\n /**\n * Checks whether a plugin has been initialized.\n * @param {string} name - The plugin name\n * @returns {boolean} True if the plugin has been initialized\n */\n isInitialized(name) {\n return this._initialized.has(name)\n }\n}\n","import { EventBus } from './EventBus.js'\nimport { Selection } from './Selection.js'\nimport { CommandRegistry } from './CommandRegistry.js'\nimport { History } from './History.js'\nimport { KeyboardManager } from './KeyboardManager.js'\nimport { Sanitizer } from './Sanitizer.js'\nimport { Clipboard } from './Clipboard.js'\nimport { DragDrop } from './DragDrop.js'\nimport { PluginManager } from '../plugins/PluginManager.js'\n\n/**\n * @typedef {Object} EditorOptions\n * @property {string} [outputFormat='html'] - Output format ('html' or 'markdown')\n * @property {Object} [history] - History configuration options\n * @property {number} [history.maxSize=100] - Maximum number of undo states\n * @property {number} [history.debounceMs=300] - Debounce interval for snapshots in ms\n * @property {Object} [sanitize] - Sanitizer configuration options\n * @property {Object} [sanitize.allowedTags] - Map of allowed HTML tags to their allowed attributes\n * @property {string[]} [sanitize.allowedStyles] - List of allowed CSS style properties\n */\n\n/**\n * Shared static handler for selectionchange events (Task 236).\n * A single document-level listener dispatches to the correct engine instance.\n * @type {WeakMap<HTMLElement, EditorEngine>}\n */\nconst _selectionHandlers = new WeakMap()\nlet _selectionListenerAttached = false\n\nfunction _sharedSelectionChangeHandler() {\n // Try activeElement first (most common case)\n const activeElement = document.activeElement\n if (activeElement) {\n let el = activeElement\n while (el) {\n const engine = _selectionHandlers.get(el)\n if (engine) {\n engine._handleSelectionChange()\n return\n }\n el = el.parentElement\n }\n }\n\n // Fallback: check selection's anchor node to find the editor\n const sel = window.getSelection()\n if (sel && sel.anchorNode) {\n let node = sel.anchorNode\n while (node) {\n const engine = _selectionHandlers.get(node)\n if (engine) {\n engine._handleSelectionChange()\n return\n }\n node = node.parentNode\n }\n }\n}\n\nfunction _ensureSelectionListener() {\n if (!_selectionListenerAttached) {\n document.addEventListener('selectionchange', _sharedSelectionChangeHandler)\n _selectionListenerAttached = true\n }\n}\n\nexport class EditorEngine {\n /**\n * Creates a new EditorEngine instance.\n * @param {HTMLElement} element - The DOM element to use as the editor\n * @param {EditorOptions} [options={}] - Configuration options\n */\n constructor(element, options = {}) {\n this.element = element\n this.options = options\n this.outputFormat = options.outputFormat || 'html'\n this.isSourceMode = false\n this.isMarkdownMode = false\n this._isDestroyed = false\n\n // Task 272: HTML dirty flag for caching getHTML() results\n this._htmlDirty = true\n this._lastHTML = ''\n\n // Task 273: Text cache for shared getText() calls in getWordCount/getCharCount\n this._textCache = null\n this._textCacheDirty = true\n\n this.eventBus = new EventBus()\n this.selection = new Selection(element)\n this.keyboard = new KeyboardManager(this)\n this.commands = new CommandRegistry(this)\n this.history = new History(this, options.history)\n this.sanitizer = new Sanitizer(options.sanitize)\n this.selection.setSanitizer(this.sanitizer)\n this.clipboard = new Clipboard(this)\n this.dragDrop = new DragDrop(this)\n this.plugins = new PluginManager(this)\n\n this._handleInput = this._handleInput.bind(this)\n this._handleSelectionChange = this._handleSelectionChange.bind(this)\n this._handleFocus = this._handleFocus.bind(this)\n this._handleBlur = this._handleBlur.bind(this)\n this._handleClick = this._handleClick.bind(this)\n }\n\n /**\n * Initializes the editor by setting up contenteditable attributes,\n * event listeners, and all subsystems (keyboard, history, clipboard, drag/drop, plugins).\n * @throws {Error} If initialization fails\n * @returns {void}\n */\n init() {\n try {\n this.element.setAttribute('contenteditable', 'true')\n this.element.setAttribute('role', 'textbox')\n this.element.setAttribute('aria-multiline', 'true')\n this.element.setAttribute('spellcheck', 'true')\n\n // Ensure there's at least one paragraph\n if (!this.element.innerHTML.trim()) {\n this.element.innerHTML = '<p><br></p>'\n }\n\n this.element.addEventListener('input', this._handleInput)\n this.element.addEventListener('focus', this._handleFocus)\n this.element.addEventListener('blur', this._handleBlur)\n this.element.addEventListener('click', this._handleClick)\n\n // Task 236: Use shared static selectionchange handler\n _selectionHandlers.set(this.element, this)\n _ensureSelectionListener()\n\n this.keyboard.init()\n this.history.init()\n this.clipboard.init()\n this.dragDrop.init()\n this.plugins.initAll()\n } catch (err) {\n console.error('EditorEngine init failed:', err)\n this.eventBus.emit('editor:error', { phase: 'init', error: err })\n throw err\n }\n }\n\n /**\n * Destroys the editor by removing all event listeners, cleaning up subsystems,\n * and removing the contenteditable attribute. Safe to call multiple times.\n * @returns {void}\n */\n destroy() {\n if (this._isDestroyed) return\n this._isDestroyed = true\n\n this.element.removeEventListener('input', this._handleInput)\n this.element.removeEventListener('focus', this._handleFocus)\n this.element.removeEventListener('blur', this._handleBlur)\n this.element.removeEventListener('click', this._handleClick)\n\n // Task 236: Remove from shared selection handler registry\n _selectionHandlers.delete(this.element)\n\n this.eventBus.emit('destroy')\n this.keyboard.destroy()\n this.history.destroy()\n this.clipboard.destroy()\n this.dragDrop.destroy()\n this.plugins.destroyAll()\n this.eventBus.removeAllListeners()\n\n this.element.removeAttribute('contenteditable')\n }\n\n /**\n * Returns the sanitized HTML content of the editor.\n * Task 272: Uses dirty flag to cache results.\n * @returns {string} The sanitized HTML content\n */\n getHTML() {\n if (!this._htmlDirty) return this._lastHTML\n this._lastHTML = this.sanitizer.sanitize(this.element.innerHTML)\n this._htmlDirty = false\n return this._lastHTML\n }\n\n /**\n * Sets the editor content to the given HTML after sanitizing it.\n * If the sanitized result is empty, inserts an empty paragraph.\n * @param {string} html - The HTML content to set\n * @returns {void}\n */\n setHTML(html) {\n const sanitized = this.sanitizer.sanitize(html)\n this.element.innerHTML = sanitized || '<p><br></p>'\n this._htmlDirty = true\n this._textCacheDirty = true\n }\n\n /**\n * Returns the plain text content of the editor.\n * @returns {string} The text content\n */\n getText() {\n if (!this._textCacheDirty && this._textCache !== null) return this._textCache\n this._textCache = this.element.textContent || ''\n this._textCacheDirty = false\n return this._textCache\n }\n\n /**\n * Checks whether the editor content is empty.\n * @returns {boolean} True if the editor contains no meaningful text\n */\n isEmpty() {\n const text = this.getText().trim()\n return text === '' || text === '\\n'\n }\n\n /**\n * Focuses the editor element.\n * @returns {void}\n */\n focus() {\n this.element.focus()\n }\n\n /**\n * Blurs (unfocuses) the editor element.\n * @returns {void}\n */\n blur() {\n this.element.blur()\n }\n\n /**\n * Executes a registered command by name.\n * @param {string} name - The name of the command to execute\n * @param {...*} args - Additional arguments to pass to the command\n * @returns {*} The result of the command execution, or false if the command was not found or disabled\n */\n executeCommand(name, ...args) {\n return this.commands.execute(name, ...args)\n }\n\n /**\n * Subscribes to an editor event.\n * @param {string} event - The event name to listen for\n * @param {Function} handler - The callback function to invoke when the event fires\n * @returns {Function} An unsubscribe function that removes the listener when called\n */\n on(event, handler) {\n return this.eventBus.on(event, handler)\n }\n\n /**\n * Unsubscribes from an editor event.\n * @param {string} event - The event name to stop listening for\n * @param {Function} handler - The handler to remove\n * @returns {void}\n */\n off(event, handler) {\n this.eventBus.off(event, handler)\n }\n\n /**\n * Returns the word count of the editor content.\n * Task 273: Shares getText() call with getCharCount() via cached _textCache.\n * @returns {number} The number of words in the editor\n */\n getWordCount() {\n const text = this.getText().trim()\n if (!text) return 0\n return text.split(/\\s+/).length\n }\n\n /**\n * Returns the character count of the editor content.\n * Task 273: Shares getText() call with getWordCount() via cached _textCache.\n * @returns {number} The number of characters in the editor\n */\n getCharCount() {\n return this.getText().length\n }\n\n /**\n * Handles input events on the editor element. Ensures at least one\n * block element exists and emits a content:change event.\n * @private\n * @returns {void}\n */\n _handleInput() {\n // Task 272: Mark HTML as dirty on input\n this._htmlDirty = true\n // Task 273: Invalidate text cache on input\n this._textCacheDirty = true\n\n // Ensure we always have at least one block element\n if (this.element.innerHTML === '' || this.element.innerHTML === '<br>') {\n this.element.innerHTML = '<p><br></p>'\n const range = document.createRange()\n range.setStart(this.element.firstChild, 0)\n range.collapse(true)\n this.selection.setRange(range)\n }\n this.eventBus.emit('content:change')\n }\n\n /**\n * Handles document selectionchange events. Checks if the selection is\n * within the editor and emits selection:change with active formats.\n * @private\n * @returns {void}\n */\n _handleSelectionChange() {\n if (!this.selection.isWithinEditor(document.activeElement)) return\n const range = this.selection.getRange()\n if (!range) return\n const formats = this.selection.getActiveFormats()\n this.eventBus.emit('selection:change', formats)\n }\n\n /**\n * Handles focus events on the editor element.\n * @private\n * @returns {void}\n */\n _handleFocus() {\n this.eventBus.emit('focus')\n }\n\n /**\n * Handles blur events on the editor element.\n * @private\n * @returns {void}\n */\n _handleBlur() {\n this.eventBus.emit('blur')\n }\n\n /**\n * Handles click events on the editor element.\n * Toggles task list checkboxes on click.\n * @private\n * @param {MouseEvent} e - The click event\n * @returns {void}\n */\n _handleClick(e) {\n // Handle task list checkbox clicks\n if (e.target.type === 'checkbox' && e.target.classList.contains('rmx-task-checkbox')) {\n e.target.checked = !e.target.checked\n this._htmlDirty = true\n this._textCacheDirty = true\n this.eventBus.emit('content:change')\n }\n }\n}\n","import { EventBus } from './EventBus.js'\n\n/**\n * A global inter-editor communication bus.\n *\n * EditorBus is a process-wide singleton that lets multiple EditorEngine\n * instances on the same page communicate without direct references to\n * each other.\n *\n * Typical use-cases:\n * - Source + preview linked editors (content sync)\n * - Master/detail layouts where selecting in one editor loads content in another\n * - Broadcasting \"save\" or \"theme change\" to all editors at once\n *\n * @example\n * import { EditorBus } from '@remyxjs/core';\n *\n * // In editor A — broadcast content on every change\n * engineA.on('content:change', () => {\n * EditorBus.emit('sync:content', { id: 'editor-a', html: engineA.getHTML() });\n * });\n *\n * // In editor B — listen for updates from A\n * EditorBus.on('sync:content', ({ id, html }) => {\n * if (id !== 'editor-b') engineB.setHTML(html);\n * });\n */\nclass EditorBusImpl {\n constructor() {\n /** @private */\n this._bus = new EventBus()\n /** @private @type {Map<string, import('./EditorEngine.js').EditorEngine>} */\n this._registry = new Map()\n }\n\n // ── Registry ──────────────────────────────────────────────────────\n\n /**\n * Register an editor instance so other editors can look it up by ID.\n * @param {string} id - A unique identifier for the editor\n * @param {import('./EditorEngine.js').EditorEngine} engine - The editor engine instance\n * @returns {void}\n */\n register(id, engine) {\n this._registry.set(id, engine)\n this._bus.emit('editor:registered', { id, engine })\n }\n\n /**\n * Unregister an editor (typically called during destroy).\n * @param {string} id - The identifier of the editor to remove\n * @returns {void}\n */\n unregister(id) {\n this._registry.delete(id)\n this._bus.emit('editor:unregistered', { id })\n }\n\n /**\n * Get a registered editor by ID.\n * @param {string} id - The editor identifier\n * @returns {import('./EditorEngine.js').EditorEngine | undefined}\n */\n getEditor(id) {\n return this._registry.get(id)\n }\n\n /**\n * Get all registered editor IDs.\n * @returns {string[]}\n */\n getEditorIds() {\n return [...this._registry.keys()]\n }\n\n /**\n * Get the count of currently registered editors.\n * @returns {number}\n */\n get editorCount() {\n return this._registry.size\n }\n\n // ── Pub/Sub (delegates to EventBus) ───────────────────────────────\n\n /**\n * Subscribe to an inter-editor event.\n * @param {string} event - Event name (e.g. 'sync:content', 'theme:change')\n * @param {Function} handler - Callback\n * @param {Object} [options] - Options forwarded to EventBus.on\n * @returns {Function} Unsubscribe function\n */\n on(event, handler, options) {\n return this._bus.on(event, handler, options)\n }\n\n /**\n * Unsubscribe from an inter-editor event.\n * @param {string} event\n * @param {Function} handler\n */\n off(event, handler) {\n this._bus.off(event, handler)\n }\n\n /**\n * Subscribe to an event that fires at most once.\n * @param {string} event\n * @param {Function} handler\n * @returns {Function} Unsubscribe function\n */\n once(event, handler) {\n return this._bus.once(event, handler)\n }\n\n /**\n * Emit an event to all subscribers.\n * @param {string} event\n * @param {*} [data]\n */\n emit(event, data) {\n this._bus.emit(event, data)\n }\n\n /**\n * Broadcast an event to all registered editors by calling engine.eventBus.emit\n * on each one. Useful for pushing a global event (e.g. theme change) into every\n * editor's local event loop.\n *\n * @param {string} event - Event name to emit on each editor's eventBus\n * @param {*} [data] - Data to pass\n * @param {Object} [options]\n * @param {string} [options.exclude] - Editor ID to skip (e.g. the sender)\n */\n broadcast(event, data, options) {\n for (const [id, engine] of this._registry) {\n if (options?.exclude && id === options.exclude) continue\n engine.eventBus.emit(event, data)\n }\n }\n\n /**\n * Remove all listeners and clear the registry.\n * Primarily useful for test teardown.\n */\n reset() {\n this._bus.removeAllListeners()\n this._registry.clear()\n }\n}\n\n/**\n * Singleton inter-editor communication bus.\n * Import this directly — all editors on the page share the same instance.\n *\n * @type {EditorBusImpl}\n */\nexport const EditorBus = new EditorBusImpl()\n","import { DEFAULT_TOOLBAR } from '../constants/defaults.js'\n\n/**\n * Pre-built toolbar presets for common use cases.\n */\nexport const TOOLBAR_PRESETS = {\n /** All available toolbar items */\n full: DEFAULT_TOOLBAR,\n\n /** Standard editing toolbar without advanced features */\n standard: [\n ['undo', 'redo'],\n ['headings', 'fontFamily', 'fontSize'],\n ['bold', 'italic', 'underline', 'strikethrough'],\n ['foreColor', 'backColor'],\n ['alignLeft', 'alignCenter', 'alignRight', 'alignJustify'],\n ['orderedList', 'unorderedList', 'taskList'],\n ['outdent', 'indent'],\n ['link', 'image', 'table', 'blockquote', 'codeBlock', 'horizontalRule'],\n ['fullscreen'],\n ],\n\n /** Minimal toolbar for simple text editing */\n minimal: [\n ['headings'],\n ['bold', 'italic', 'underline'],\n ['orderedList', 'unorderedList'],\n ['link', 'image', 'blockquote'],\n ],\n\n /** Bare-bones formatting only */\n bare: [\n ['bold', 'italic', 'underline'],\n ],\n\n /** Rich document editing with all plugin features */\n rich: [\n ['undo', 'redo'],\n ['headings', 'fontFamily', 'fontSize', 'typography'],\n ['bold', 'italic', 'underline', 'strikethrough'],\n ['foreColor', 'backColor'],\n ['alignLeft', 'alignCenter', 'alignRight'],\n ['orderedList', 'unorderedList', 'taskList'],\n ['outdent', 'indent'],\n ['link', 'image', 'attachment', 'table', 'embedMedia', 'blockquote', 'codeBlock', 'horizontalRule'],\n ['insertCallout', 'insertMath', 'insertToc', 'insertBookmark', 'insertMergeTag'],\n ['subscript', 'superscript', 'removeFormat'],\n ['findReplace', 'toggleMarkdown', 'sourceMode', 'toggleAnalytics', 'distractionFree', 'toggleSplitView', 'export', 'commandPalette', 'fullscreen'],\n ],\n}\n\n/**\n * Remove specific items from a toolbar config.\n *\n * @param {string[][]} config - Toolbar config (array of groups)\n * @param {string[]} itemsToRemove - Command names to remove\n * @returns {string[][]} New toolbar config with items removed (empty groups are excluded)\n *\n * @example\n * removeToolbarItems(DEFAULT_TOOLBAR, ['image', 'table', 'embedMedia'])\n */\nexport function removeToolbarItems(config, itemsToRemove) {\n const removeSet = new Set(itemsToRemove)\n return config\n .map(group => group.filter(item => !removeSet.has(item)))\n .filter(group => group.length > 0)\n}\n\n/**\n * Add items to a toolbar config.\n *\n * @param {string[][]} config - Toolbar config (array of groups)\n * @param {string|string[]} items - Command name(s) to add\n * @param {Object} [options]\n * @param {number} [options.group] - Group index to insert into. If omitted, appends a new group.\n * @param {string} [options.after] - Insert after this command within the group\n * @param {string} [options.before] - Insert before this command within the group\n * @returns {string[][]} New toolbar config with items added\n *\n * @example\n * // Add 'export' to the last group\n * addToolbarItems(myToolbar, 'export', { group: -1 })\n *\n * // Add items as a new group at the end\n * addToolbarItems(myToolbar, ['subscript', 'superscript'])\n *\n * // Insert after a specific item\n * addToolbarItems(myToolbar, 'taskList', { after: 'unorderedList' })\n */\nexport function addToolbarItems(config, items, options = {}) {\n const itemsArray = Array.isArray(items) ? items : [items]\n const result = config.map(group => [...group])\n\n if (options.after || options.before) {\n const target = options.after || options.before\n for (let i = 0; i < result.length; i++) {\n const idx = result[i].indexOf(target)\n if (idx !== -1) {\n const insertAt = options.after ? idx + 1 : idx\n result[i].splice(insertAt, 0, ...itemsArray)\n return result\n }\n }\n // Target not found — fall through to append as new group\n }\n\n if (options.group !== undefined) {\n const gi = options.group < 0 ? result.length + options.group : options.group\n if (gi >= 0 && gi < result.length) {\n result[gi].push(...itemsArray)\n return result\n }\n }\n\n // Default: append as a new group\n result.push(itemsArray)\n return result\n}\n\n/**\n * Create a toolbar config from a flat list of command names.\n * Commands are automatically grouped by category.\n *\n * @param {string[]} items - Command names to include\n * @returns {string[][]} Toolbar config grouped by category\n *\n * @example\n * createToolbar(['bold', 'italic', 'underline', 'link', 'orderedList', 'unorderedList'])\n */\nexport function createToolbar(items) {\n const CATEGORY_ORDER = [\n { commands: ['undo', 'redo'] },\n { commands: ['headings', 'fontFamily', 'fontSize'] },\n { commands: ['bold', 'italic', 'underline', 'strikethrough'] },\n { commands: ['foreColor', 'backColor'] },\n { commands: ['alignLeft', 'alignCenter', 'alignRight', 'alignJustify'] },\n { commands: ['orderedList', 'unorderedList', 'taskList'] },\n { commands: ['outdent', 'indent'] },\n { commands: ['link', 'image', 'attachment', 'importDocument', 'table', 'embedMedia', 'blockquote', 'codeBlock', 'horizontalRule'] },\n { commands: ['subscript', 'superscript'] },\n { commands: ['findReplace', 'toggleMarkdown', 'sourceMode', 'export', 'fullscreen'] },\n ]\n\n const itemSet = new Set(items)\n const result = []\n\n for (const category of CATEGORY_ORDER) {\n const group = category.commands.filter(cmd => itemSet.has(cmd))\n if (group.length > 0) {\n result.push(group)\n group.forEach(cmd => itemSet.delete(cmd))\n }\n }\n\n // Any remaining items that didn't match a category go in their own group\n if (itemSet.size > 0) {\n result.push([...itemSet])\n }\n\n return result\n}\n","import { isMac } from '../utils/platform.js'\n\n/**\n * Item 20: IMPORTANT — BUTTON_COMMANDS and TOOLTIP_MAP must be kept in sync manually.\n * When adding a new command to BUTTON_COMMANDS, also add a corresponding entry to TOOLTIP_MAP below.\n * When adding a new entry to TOOLTIP_MAP for a button command, ensure it is also in BUTTON_COMMANDS.\n * SHORTCUT_MAP should also be updated if the command has a keyboard shortcut.\n * MODAL_COMMANDS should be updated if the command triggers a modal instead of direct execution.\n */\nexport const BUTTON_COMMANDS = new Set([\n 'bold', 'italic', 'underline', 'strikethrough', 'subscript', 'superscript',\n 'alignLeft', 'alignCenter', 'alignRight', 'alignJustify',\n 'orderedList', 'unorderedList', 'taskList',\n 'blockquote', 'codeBlock', 'horizontalRule',\n 'undo', 'redo', 'removeFormat',\n 'indent', 'outdent',\n 'fullscreen', 'sourceMode', 'toggleMarkdown',\n 'distractionFree', 'toggleSplitView',\n // Plugin commands exposed as toolbar buttons\n 'insertCallout', 'insertMath', 'insertToc', 'insertBookmark', 'insertMergeTag',\n 'toggleAnalytics', 'toggleSpellcheck', 'checkGrammar',\n 'startCollaboration', 'stopCollaboration',\n 'addComment', 'removeFormat',\n 'lineHeight', 'letterSpacing', 'paragraphSpacing',\n])\n\nexport const TOOLTIP_MAP = {\n bold: 'Bold', italic: 'Italic', underline: 'Underline', strikethrough: 'Strikethrough',\n subscript: 'Subscript', superscript: 'Superscript',\n alignLeft: 'Align Left', alignCenter: 'Align Center', alignRight: 'Align Right', alignJustify: 'Justify',\n orderedList: 'Numbered List', unorderedList: 'Bulleted List', taskList: 'Task List',\n blockquote: 'Blockquote', codeBlock: 'Code Block', horizontalRule: 'Horizontal Rule',\n undo: 'Undo', redo: 'Redo', removeFormat: 'Remove Formatting',\n indent: 'Indent', outdent: 'Outdent',\n link: 'Insert Link', image: 'Insert Image', attachment: 'Attach File', table: 'Insert Table',\n importDocument: 'Import Document',\n embedMedia: 'Embed Media', findReplace: 'Find & Replace',\n fullscreen: 'Fullscreen', sourceMode: 'Source Code',\n toggleMarkdown: 'Toggle Markdown', export: 'Export Document',\n distractionFree: 'Distraction-Free Mode', toggleSplitView: 'Split View',\n lineHeight: 'Line Height', letterSpacing: 'Letter Spacing', paragraphSpacing: 'Paragraph Spacing',\n saveColorPreset: 'Save Color Preset', typography: 'Typography',\n foreColor: 'Text Color', backColor: 'Background Color',\n headings: 'Block Type', fontFamily: 'Font Family', fontSize: 'Font Size',\n commandPalette: 'Command Palette',\n // Plugin commands\n insertCallout: 'Insert Callout',\n insertMath: 'Insert Math',\n insertToc: 'Insert Table of Contents',\n insertBookmark: 'Insert Bookmark',\n insertMergeTag: 'Insert Merge Tag',\n addComment: 'Add Comment',\n scanBrokenLinks: 'Scan Broken Links',\n getAnalytics: 'Content Analytics',\n toggleAnalytics: 'Toggle Analytics',\n toggleSpellcheck: 'Toggle Spellcheck',\n checkGrammar: 'Check Grammar',\n startCollaboration: 'Start Collaboration',\n stopCollaboration: 'Stop Collaboration',\n}\n\nexport const SHORTCUT_MAP = {\n bold: 'mod+B', italic: 'mod+I', underline: 'mod+U',\n strikethrough: 'mod+Shift+X', undo: 'mod+Z', redo: 'mod+Shift+Z',\n insertLink: 'mod+K', findReplace: 'mod+F',\n fullscreen: 'mod+Shift+F', sourceMode: 'mod+Shift+U',\n commandPalette: 'mod+Shift+P',\n distractionFree: 'mod+Shift+D', toggleSplitView: 'mod+Shift+V',\n}\n\n/** Commands that trigger modals instead of direct execution */\nexport const MODAL_COMMANDS = {\n link: 'link',\n image: 'image',\n attachment: 'attachment',\n importDocument: 'importDocument',\n table: 'table',\n embedMedia: 'embed',\n findReplace: 'findReplace',\n export: 'export',\n}\n\nexport function getShortcutLabel(command) {\n const shortcut = SHORTCUT_MAP[command]\n if (!shortcut) return ''\n return shortcut.replace(/mod/g, isMac() ? '⌘' : 'Ctrl')\n}\n\n/**\n * Get the active state for a command from selectionState + engine.\n */\nexport function getCommandActiveState(command, selectionState, engine) {\n switch (command) {\n case 'bold': return selectionState.bold\n case 'italic': return selectionState.italic\n case 'underline': return selectionState.underline\n case 'strikethrough': return selectionState.strikethrough\n case 'subscript': return selectionState.subscript\n case 'superscript': return selectionState.superscript\n case 'alignLeft': return selectionState.alignment === 'left'\n case 'alignCenter': return selectionState.alignment === 'center'\n case 'alignRight': return selectionState.alignment === 'right'\n case 'alignJustify': return selectionState.alignment === 'justify'\n case 'orderedList': return selectionState.orderedList\n case 'unorderedList': return selectionState.unorderedList\n case 'blockquote': return selectionState.blockquote\n case 'codeBlock': return selectionState.codeBlock\n case 'sourceMode': return engine?.isSourceMode\n case 'toggleMarkdown': return engine?.isMarkdownMode\n case 'fullscreen': return engine?.element?.closest('.rmx-editor')?.classList.contains('rmx-fullscreen')\n case 'distractionFree': return engine?.element?.closest('.rmx-editor')?.classList.contains('rmx-distraction-free')\n case 'toggleSplitView': return engine?.element?.closest('.rmx-editor')?.classList.contains('rmx-split-view')\n default: return false\n }\n}\n","export const DEFAULT_KEYBINDINGS = {\n bold: 'mod+b',\n italic: 'mod+i',\n underline: 'mod+u',\n strikethrough: 'mod+shift+x',\n undo: 'mod+z',\n redo: 'mod+shift+z',\n selectAll: 'mod+a',\n insertLink: 'mod+k',\n findReplace: 'mod+f',\n orderedList: 'mod+shift+7',\n unorderedList: 'mod+shift+8',\n blockquote: 'mod+shift+9',\n codeBlock: 'mod+shift+c',\n indent: 'tab',\n outdent: 'shift+tab',\n fullscreen: 'mod+shift+f',\n sourceMode: 'mod+shift+u',\n subscript: 'mod+,',\n superscript: 'mod+.',\n}\n","import { ALLOWED_TAGS, ALLOWED_STYLES } from '../constants/schema.js'\nimport { TOOLBAR_PRESETS } from '../utils/toolbarConfig.js'\nimport { DEFAULT_TOOLBAR, DEFAULT_MENU_BAR, DEFAULT_FONTS, DEFAULT_FONT_SIZES, DEFAULT_COLORS, HEADING_OPTIONS } from '../constants/defaults.js'\nimport { BUTTON_COMMANDS, TOOLTIP_MAP, SHORTCUT_MAP, MODAL_COMMANDS } from '../constants/commands.js'\nimport { DEFAULT_KEYBINDINGS } from '../constants/keybindings.js'\n\n/**\n * A memory-efficient shared resource singleton.\n *\n * When running 10+ EditorEngine instances on a single page, each engine\n * would normally allocate its own copy of the sanitizer schema, toolbar\n * presets, icon map, default config objects, and keybinding tables.\n * SharedResources provides a single frozen copy of these large, immutable\n * data structures so every engine can reference the same objects in memory.\n *\n * The singleton is lazily initialized on first access and the frozen data\n * is reused for the lifetime of the page.\n *\n * @example\n * import { SharedResources } from '@remyxjs/core';\n *\n * // Access shared sanitizer schema (same object for all editors)\n * const tags = SharedResources.sanitizerSchema.allowedTags;\n *\n * // Access shared toolbar presets\n * const fullToolbar = SharedResources.toolbarPresets.full;\n *\n * // Register a custom icon once, available to all editors\n * SharedResources.registerIcon('myIcon', '<svg>...</svg>');\n */\nclass SharedResourcesImpl {\n constructor() {\n /** @private */\n this._initialized = false\n /** @private */\n this._sanitizerSchema = null\n /** @private */\n this._toolbarPresets = null\n /** @private */\n this._defaults = null\n /** @private */\n this._keybindings = null\n /** @private */\n this._commands = null\n /** @private @type {Map<string, string>} */\n this._icons = new Map()\n }\n\n /** @private */\n _ensureInit() {\n if (this._initialized) return\n this._initialized = true\n\n // Freeze the sanitizer schema so all engines share the same immutable copy\n this._sanitizerSchema = Object.freeze({\n allowedTags: Object.freeze(\n Object.fromEntries(\n Object.entries(ALLOWED_TAGS).map(([tag, attrs]) => [tag, Object.freeze([...attrs])])\n )\n ),\n allowedStyles: Object.freeze([...ALLOWED_STYLES]),\n })\n\n // Freeze toolbar presets (deep-freeze the nested arrays)\n this._toolbarPresets = Object.freeze(\n Object.fromEntries(\n Object.entries(TOOLBAR_PRESETS).map(([name, groups]) => [\n name,\n Object.freeze(groups.map(g => Object.freeze([...g]))),\n ])\n )\n )\n\n // Freeze default configuration objects\n this._defaults = Object.freeze({\n toolbar: Object.freeze(DEFAULT_TOOLBAR.map(g => Object.freeze([...g]))),\n menuBar: Object.freeze(DEFAULT_MENU_BAR),\n fonts: Object.freeze([...DEFAULT_FONTS]),\n fontSizes: Object.freeze([...DEFAULT_FONT_SIZES]),\n colors: Object.freeze([...DEFAULT_COLORS]),\n headingOptions: Object.freeze([...HEADING_OPTIONS]),\n })\n\n // Freeze keybinding table\n this._keybindings = Object.freeze({ ...DEFAULT_KEYBINDINGS })\n\n // Freeze command metadata maps\n this._commands = Object.freeze({\n buttons: Object.freeze({ ...BUTTON_COMMANDS }),\n tooltips: Object.freeze({ ...TOOLTIP_MAP }),\n shortcuts: Object.freeze({ ...SHORTCUT_MAP }),\n modals: Object.freeze({ ...MODAL_COMMANDS }),\n })\n }\n\n // ── Accessors ──────────────────────────────────────────────────────\n\n /**\n * Shared, frozen sanitizer schema.\n * @returns {{ allowedTags: Object, allowedStyles: string[] }}\n */\n get sanitizerSchema() {\n this._ensureInit()\n return this._sanitizerSchema\n }\n\n /**\n * Shared, frozen toolbar presets (full, standard, minimal, bare).\n * @returns {Object}\n */\n get toolbarPresets() {\n this._ensureInit()\n return this._toolbarPresets\n }\n\n /**\n * Shared, frozen default configuration values.\n * @returns {{ toolbar, menuBar, fonts, fontSizes, colors, headingOptions }}\n */\n get defaults() {\n this._ensureInit()\n return this._defaults\n }\n\n /**\n * Shared, frozen keybinding table.\n * @returns {Object}\n */\n get keybindings() {\n this._ensureInit()\n return this._keybindings\n }\n\n /**\n * Shared, frozen command metadata maps.\n * @returns {{ buttons, tooltips, shortcuts, modals }}\n */\n get commands() {\n this._ensureInit()\n return this._commands\n }\n\n // ── Icon registry ──────────────────────────────────────────────────\n\n /**\n * Register a shared icon (SVG string or URL) by name.\n * Once registered, any editor on the page can use it via toolbar config.\n *\n * @param {string} name - Icon identifier (e.g. 'myCustomAction')\n * @param {string} svgOrUrl - SVG markup string or URL to icon asset\n */\n registerIcon(name, svgOrUrl) {\n this._icons.set(name, svgOrUrl)\n }\n\n /**\n * Unregister a previously registered icon.\n * @param {string} name - Icon identifier to remove\n */\n unregisterIcon(name) {\n this._icons.delete(name)\n }\n\n /**\n * Retrieve a registered icon by name.\n * @param {string} name - Icon identifier\n * @returns {string | undefined} SVG markup or URL, or undefined if not registered\n */\n getIcon(name) {\n return this._icons.get(name)\n }\n\n /**\n * Get all registered icon names.\n * @returns {string[]}\n */\n getIconNames() {\n return [...this._icons.keys()]\n }\n\n /**\n * Total approximate memory savings info.\n * @returns {{ registeredIcons: number, frozenSchemas: boolean }}\n */\n get stats() {\n this._ensureInit()\n return {\n registeredIcons: this._icons.size,\n frozenSchemas: true,\n }\n }\n}\n\n/**\n * Singleton shared resource pool.\n * Import this directly — all editors on the page share the same instance.\n *\n * @type {SharedResourcesImpl}\n */\nexport const SharedResources = new SharedResourcesImpl()\n","/**\n * CSP-compatible inline formatting commands.\n *\n * All commands use Selection/Range-based DOM manipulation instead of the\n * deprecated `document.execCommand()` API. This eliminates CSP violations\n * from `unsafe-inline` requirements and ensures forward compatibility.\n */\n\n/**\n * Toggle an inline tag (e.g., <strong>, <em>) on the current selection.\n * If the selection is already wrapped in the tag, remove it. Otherwise wrap it.\n */\nfunction toggleInlineTag(engine, tagName) {\n const sel = engine.selection.getSelection()\n if (!sel || sel.rangeCount === 0) return\n\n const range = sel.getRangeAt(0)\n if (range.collapsed) return\n\n // Check if already wrapped in this tag\n const parent = engine.selection.getParentElement()\n if (parent && parent.tagName === tagName.toUpperCase()) {\n // Unwrap: move children out and remove the tag\n const docFrag = document.createDocumentFragment()\n while (parent.firstChild) {\n docFrag.appendChild(parent.firstChild)\n }\n parent.parentNode.replaceChild(docFrag, parent)\n return\n }\n\n // Also check if selection is inside a nested instance\n let ancestor = parent\n while (ancestor && ancestor !== engine.element) {\n if (ancestor.tagName === tagName.toUpperCase()) {\n const docFrag = document.createDocumentFragment()\n while (ancestor.firstChild) {\n docFrag.appendChild(ancestor.firstChild)\n }\n ancestor.parentNode.replaceChild(docFrag, ancestor)\n return\n }\n ancestor = ancestor.parentNode\n }\n\n // Wrap selection in the tag\n const wrapper = document.createElement(tagName)\n try {\n range.surroundContents(wrapper)\n } catch {\n const fragment = range.extractContents()\n wrapper.appendChild(fragment)\n range.insertNode(wrapper)\n }\n}\n\n/**\n * Check if the selection is currently inside a given tag.\n */\nfunction isInsideTag(engine, tagName) {\n const parent = engine.selection.getParentElement()\n if (!parent) return false\n let el = parent\n while (el && el !== engine.element) {\n if (el.tagName === tagName.toUpperCase()) return true\n el = el.parentNode\n }\n return false\n}\n\nexport function registerFormattingCommands(engine) {\n engine.commands.register('bold', {\n execute(eng) { toggleInlineTag(eng, 'strong') },\n isActive(eng) { return isInsideTag(eng, 'STRONG') || isInsideTag(eng, 'B') },\n shortcut: 'mod+b',\n meta: { icon: 'bold', tooltip: 'Bold' },\n })\n\n engine.commands.register('italic', {\n execute(eng) { toggleInlineTag(eng, 'em') },\n isActive(eng) { return isInsideTag(eng, 'EM') || isInsideTag(eng, 'I') },\n shortcut: 'mod+i',\n meta: { icon: 'italic', tooltip: 'Italic' },\n })\n\n engine.commands.register('underline', {\n execute(eng) { toggleInlineTag(eng, 'u') },\n isActive(eng) { return isInsideTag(eng, 'U') },\n shortcut: 'mod+u',\n meta: { icon: 'underline', tooltip: 'Underline' },\n })\n\n engine.commands.register('strikethrough', {\n execute(eng) { toggleInlineTag(eng, 's') },\n isActive(eng) { return isInsideTag(eng, 'S') || isInsideTag(eng, 'DEL') },\n shortcut: 'mod+shift+x',\n meta: { icon: 'strikethrough', tooltip: 'Strikethrough' },\n })\n\n engine.commands.register('subscript', {\n execute(eng) { toggleInlineTag(eng, 'sub') },\n isActive(eng) { return isInsideTag(eng, 'SUB') },\n shortcut: 'mod+,',\n alternateShortcuts: ['mod+shift+,'],\n meta: { icon: 'subscript', tooltip: 'Subscript' },\n })\n\n engine.commands.register('superscript', {\n execute(eng) { toggleInlineTag(eng, 'sup') },\n isActive(eng) { return isInsideTag(eng, 'SUP') },\n shortcut: 'mod+.',\n alternateShortcuts: ['mod+shift+.'],\n meta: { icon: 'superscript', tooltip: 'Superscript' },\n })\n\n engine.commands.register('highlight', {\n execute(eng, { color = 'yellow' } = {}) {\n const sel = eng.selection.getSelection()\n if (!sel || sel.rangeCount === 0) return\n const range = sel.getRangeAt(0)\n if (range.collapsed) return\n\n // Check if already inside a <mark> — toggle off\n const parent = eng.selection.getParentElement()\n let ancestor = parent\n while (ancestor && ancestor !== eng.element) {\n if (ancestor.tagName === 'MARK') {\n const docFrag = document.createDocumentFragment()\n while (ancestor.firstChild) {\n docFrag.appendChild(ancestor.firstChild)\n }\n ancestor.parentNode.replaceChild(docFrag, ancestor)\n return\n }\n ancestor = ancestor.parentNode\n }\n\n // Wrap in <mark> with the specified background color\n const colorMap = {\n yellow: 'rgba(255, 235, 59, 0.4)',\n green: 'rgba(76, 175, 80, 0.3)',\n blue: 'rgba(33, 150, 243, 0.3)',\n pink: 'rgba(233, 30, 99, 0.3)',\n orange: 'rgba(255, 152, 0, 0.3)',\n purple: 'rgba(156, 39, 176, 0.3)',\n }\n const bg = colorMap[color] || colorMap.yellow\n const mark = document.createElement('mark')\n mark.style.backgroundColor = bg\n mark.setAttribute('data-highlight-color', color)\n try {\n range.surroundContents(mark)\n } catch {\n const fragment = range.extractContents()\n mark.appendChild(fragment)\n range.insertNode(mark)\n }\n },\n isActive(eng) { return isInsideTag(eng, 'MARK') },\n meta: { icon: 'highlight', tooltip: 'Highlight' },\n })\n\n engine.commands.register('removeFormat', {\n execute(eng) {\n const sel = eng.selection.getSelection()\n if (!sel || sel.rangeCount === 0) return\n const range = sel.getRangeAt(0)\n if (range.collapsed) return\n\n // Walk the selection and remove all inline formatting tags\n const inlineTags = new Set(['STRONG', 'B', 'EM', 'I', 'U', 'S', 'DEL', 'SUB', 'SUP', 'MARK', 'SPAN'])\n const container = range.commonAncestorContainer\n const root = container.nodeType === Node.TEXT_NODE ? container.parentElement : container\n\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT)\n const toUnwrap = []\n let node\n while ((node = walker.nextNode())) {\n if (inlineTags.has(node.tagName) && range.intersectsNode(node)) {\n toUnwrap.push(node)\n }\n }\n\n // Unwrap in reverse order to avoid DOM mutation issues\n for (let i = toUnwrap.length - 1; i >= 0; i--) {\n const el = toUnwrap[i]\n const parent = el.parentNode\n while (el.firstChild) {\n parent.insertBefore(el.firstChild, el)\n }\n parent.removeChild(el)\n }\n },\n shortcut: 'mod+\\\\',\n meta: { icon: 'removeFormat', tooltip: 'Remove Formatting' },\n })\n}\n","/**\n * CSP-compatible heading commands using Range-based DOM manipulation.\n *\n * Compute the effective heading tag, applying a baseHeadingLevel offset.\n * If baseHeadingLevel is 2, then logical H1 renders as <h2>, H2 as <h3>, etc.\n * Levels are clamped to 1-6.\n */\nfunction getEffectiveLevel(logicalLevel, baseHeadingLevel) {\n if (!baseHeadingLevel || baseHeadingLevel <= 1) return logicalLevel\n const offset = baseHeadingLevel - 1\n return Math.min(logicalLevel + offset, 6)\n}\n\n/**\n * Convert the current block element to a different block tag (e.g., <p> → <h2>).\n */\nfunction convertBlock(engine, newTag) {\n const block = engine.selection.getParentBlock()\n if (!block) return\n\n // If already this tag, do nothing\n if (block.tagName === newTag.toUpperCase()) return\n\n const newEl = document.createElement(newTag)\n // Copy over children\n while (block.firstChild) {\n newEl.appendChild(block.firstChild)\n }\n // Copy class attribute if present\n if (block.className) newEl.className = block.className\n\n block.parentNode.replaceChild(newEl, block)\n\n // Restore cursor inside the new element\n const range = document.createRange()\n range.selectNodeContents(newEl)\n range.collapse(false)\n engine.selection.setRange(range)\n}\n\nexport function registerHeadingCommands(engine) {\n const baseLevel = engine.options.baseHeadingLevel || 1\n\n engine.commands.register('heading', {\n execute(eng, level) {\n if (level === 'p') {\n convertBlock(eng, 'p')\n } else {\n const effective = getEffectiveLevel(Number(level), baseLevel)\n convertBlock(eng, `h${effective}`)\n }\n },\n isActive(eng) {\n const block = eng.selection.getParentBlock()\n if (!block) return false\n return /^H[1-6]$/.test(block.tagName) ? block.tagName.toLowerCase() : false\n },\n meta: { icon: 'heading', tooltip: 'Heading' },\n })\n\n // Individual heading commands for convenience\n for (let i = 1; i <= 6; i++) {\n const effectiveLevel = getEffectiveLevel(i, baseLevel)\n engine.commands.register(`h${i}`, {\n execute(eng) { convertBlock(eng, `h${effectiveLevel}`) },\n isActive(eng) {\n const block = eng.selection.getParentBlock()\n return block && block.tagName === `H${effectiveLevel}`\n },\n meta: { tooltip: `Heading ${i}` },\n })\n }\n\n engine.commands.register('paragraph', {\n execute(eng) { convertBlock(eng, 'p') },\n isActive(eng) {\n const block = eng.selection.getParentBlock()\n return block && block.tagName === 'P'\n },\n meta: { tooltip: 'Normal text' },\n })\n}\n","/**\n * CSP-compatible text alignment commands using direct style manipulation.\n * Task 255: isActive() reads block.style.textAlign only (no getComputedStyle).\n */\nexport function registerAlignmentCommands(engine) {\n engine.commands.register('alignLeft', {\n execute(eng) {\n const block = eng.selection.getParentBlock()\n if (block) block.style.textAlign = 'left'\n },\n isActive(eng) {\n const block = eng.selection.getParentBlock()\n if (!block) return false\n const align = block.style.textAlign\n return !align || align === 'left' || align === 'start'\n },\n meta: { icon: 'alignLeft', tooltip: 'Align Left' },\n })\n\n engine.commands.register('alignCenter', {\n execute(eng) {\n const block = eng.selection.getParentBlock()\n if (block) block.style.textAlign = 'center'\n },\n isActive(eng) {\n const block = eng.selection.getParentBlock()\n if (!block) return false\n const align = block.style.textAlign\n return align === 'center'\n },\n meta: { icon: 'alignCenter', tooltip: 'Align Center' },\n })\n\n engine.commands.register('alignRight', {\n execute(eng) {\n const block = eng.selection.getParentBlock()\n if (block) block.style.textAlign = 'right'\n },\n isActive(eng) {\n const block = eng.selection.getParentBlock()\n if (!block) return false\n const align = block.style.textAlign\n return align === 'right' || align === 'end'\n },\n meta: { icon: 'alignRight', tooltip: 'Align Right' },\n })\n\n engine.commands.register('alignJustify', {\n execute(eng) {\n const block = eng.selection.getParentBlock()\n if (block) block.style.textAlign = 'justify'\n },\n isActive(eng) {\n const block = eng.selection.getParentBlock()\n if (!block) return false\n const align = block.style.textAlign\n return align === 'justify'\n },\n meta: { icon: 'alignJustify', tooltip: 'Justify' },\n })\n}\n","/**\n * CSP-compatible list commands using Range-based DOM manipulation.\n */\nexport function registerListCommands(engine) {\n engine.commands.register('orderedList', {\n execute(eng) {\n const existingOl = eng.selection.getClosestElement('ol')\n if (existingOl) {\n // Toggle off: unwrap list items to paragraphs\n unwrapList(existingOl)\n return\n }\n // Convert from ul to ol if in an unordered list\n const existingUl = eng.selection.getClosestElement('ul')\n if (existingUl) {\n convertListType(existingUl, 'ol')\n return\n }\n // Wrap current block in <ol><li>\n wrapInList(eng, 'ol')\n },\n isActive(eng) {\n return !!eng.selection.getClosestElement('ol')\n },\n shortcut: 'mod+shift+7',\n meta: { icon: 'orderedList', tooltip: 'Numbered List' },\n })\n\n engine.commands.register('unorderedList', {\n execute(eng) {\n const existingUl = eng.selection.getClosestElement('ul')\n if (existingUl && !existingUl.classList.contains('rmx-task-list')) {\n unwrapList(existingUl)\n return\n }\n const existingOl = eng.selection.getClosestElement('ol')\n if (existingOl) {\n convertListType(existingOl, 'ul')\n return\n }\n wrapInList(eng, 'ul')\n },\n isActive(eng) {\n const ul = eng.selection.getClosestElement('ul')\n if (!ul) return false\n return !ul.classList.contains('rmx-task-list')\n },\n shortcut: 'mod+shift+8',\n meta: { icon: 'unorderedList', tooltip: 'Bulleted List' },\n })\n\n engine.commands.register('taskList', {\n execute(eng) {\n const existingUl = eng.selection.getClosestElement('ul')\n if (existingUl && existingUl.classList.contains('rmx-task-list')) {\n // Remove task list - convert back to regular list\n existingUl.classList.remove('rmx-task-list')\n existingUl.querySelectorAll('.rmx-task-checkbox').forEach((cb) => cb.remove())\n return\n }\n\n // If not in a list, create one\n if (!existingUl) {\n wrapInList(eng, 'ul')\n }\n\n const ul = eng.selection.getClosestElement('ul')\n if (!ul) return\n\n ul.classList.add('rmx-task-list')\n ul.querySelectorAll('li').forEach((li) => {\n if (!li.querySelector('.rmx-task-checkbox')) {\n const checkbox = document.createElement('input')\n checkbox.type = 'checkbox'\n checkbox.className = 'rmx-task-checkbox'\n checkbox.setAttribute('contenteditable', 'false')\n li.insertBefore(checkbox, li.firstChild)\n }\n })\n },\n isActive(eng) {\n const ul = eng.selection.getClosestElement('ul')\n return ul && ul.classList.contains('rmx-task-list')\n },\n meta: { icon: 'taskList', tooltip: 'Task List' },\n })\n\n engine.commands.register('indent', {\n execute(eng) {\n const li = eng.selection.getClosestElement('li')\n if (!li) return\n const prevLi = li.previousElementSibling\n if (!prevLi) return // Can't indent first item\n\n // Check current nesting depth and enforce max\n const maxDepth = eng.options?.maxListNestingDepth ?? 5\n let depth = 0\n let el = li.parentElement\n while (el && el !== eng.element) {\n if (el.tagName === 'UL' || el.tagName === 'OL') depth++\n el = el.parentElement\n }\n if (depth >= maxDepth) return // Already at max nesting depth\n\n // Find or create a sub-list inside the previous li\n const listTag = li.parentElement.tagName.toLowerCase()\n let subList = prevLi.querySelector(listTag)\n if (!subList) {\n subList = document.createElement(listTag)\n prevLi.appendChild(subList)\n }\n subList.appendChild(li)\n },\n meta: { icon: 'indent', tooltip: 'Increase Indent' },\n })\n\n engine.commands.register('outdent', {\n execute(eng) {\n const li = eng.selection.getClosestElement('li')\n if (!li) return\n const parentList = li.parentElement\n if (!parentList) return\n const grandParentLi = parentList.parentElement\n if (!grandParentLi || grandParentLi.tagName !== 'LI') return // Already at top level\n\n // Move the li after the grandparent li\n const grandParentList = grandParentLi.parentElement\n grandParentList.insertBefore(li, grandParentLi.nextSibling)\n\n // Clean up empty sub-list\n if (parentList.children.length === 0) {\n parentList.remove()\n }\n },\n meta: { icon: 'outdent', tooltip: 'Decrease Indent' },\n })\n}\n\nfunction wrapInList(engine, listTag) {\n const block = engine.selection.getParentBlock()\n if (!block) return\n\n const list = document.createElement(listTag)\n const li = document.createElement('li')\n\n // Move block content into the li\n while (block.firstChild) {\n li.appendChild(block.firstChild)\n }\n list.appendChild(li)\n block.parentNode.replaceChild(list, block)\n\n // Restore cursor\n const range = document.createRange()\n range.selectNodeContents(li)\n range.collapse(false)\n engine.selection.setRange(range)\n}\n\nfunction unwrapList(list) {\n const parent = list.parentNode\n const items = Array.from(list.querySelectorAll(':scope > li'))\n for (const li of items) {\n const p = document.createElement('p')\n while (li.firstChild) {\n p.appendChild(li.firstChild)\n }\n parent.insertBefore(p, list)\n }\n parent.removeChild(list)\n}\n\nfunction convertListType(list, newTag) {\n const newList = document.createElement(newTag)\n // Copy attributes\n for (const attr of list.attributes) {\n newList.setAttribute(attr.name, attr.value)\n }\n while (list.firstChild) {\n newList.appendChild(list.firstChild)\n }\n list.parentNode.replaceChild(newList, list)\n}\n","import { escapeHTMLAttr as escapeHTML } from '../utils/escapeHTML.js'\n\nconst DANGEROUS_PROTOCOL = /^\\s*(javascript|vbscript|data\\s*:\\s*text\\/html)\\s*:/i\n\nfunction validateUrl(url) {\n if (!url) return url\n if (DANGEROUS_PROTOCOL.test(url)) return '#'\n return url\n}\n\nexport function registerLinkCommands(engine) {\n engine.commands.register('insertLink', {\n execute(eng, { href, text, target = '_blank' }) {\n if (!href) return\n\n const selection = eng.selection\n const selectedText = selection.getSelectedText()\n\n if (selectedText) {\n const link = selection.wrapWith('a', {\n href,\n target,\n rel: target === '_blank' ? 'noopener noreferrer' : undefined,\n })\n if (link && text && text !== selectedText) {\n link.textContent = text\n }\n } else {\n const displayText = text || href\n const safeHref = validateUrl(href)\n const html = `<a href=\"${escapeAttr(safeHref)}\" target=\"${escapeAttr(target)}\"${target === '_blank' ? ' rel=\"noopener noreferrer\"' : ''}>${escapeHTML(displayText)}</a>`\n selection.insertHTML(html)\n }\n },\n shortcut: 'mod+k',\n meta: { icon: 'link', tooltip: 'Insert Link' },\n })\n\n engine.commands.register('editLink', {\n execute(eng, { href, text, target }) {\n const linkEl = eng.selection.getClosestElement('a')\n if (!linkEl) return\n if (href !== undefined) linkEl.href = validateUrl(href)\n if (text !== undefined) linkEl.textContent = text\n if (target !== undefined) {\n linkEl.target = target\n if (target === '_blank') {\n linkEl.rel = 'noopener noreferrer'\n } else {\n linkEl.removeAttribute('rel')\n }\n }\n },\n meta: { tooltip: 'Edit Link' },\n })\n\n engine.commands.register('removeLink', {\n execute(eng) {\n eng.selection.unwrap('a')\n },\n meta: { icon: 'unlink', tooltip: 'Remove Link' },\n })\n}\n\n// Task 261: escapeHTML imported from shared utils/escapeHTML.js\n\nfunction escapeAttr(str) {\n return str.replace(/\"/g, '&quot;').replace(/'/g, '&#39;')\n}\n","/**\n * Image commands for the editor.\n *\n * **Privacy notice:** Images inserted via external URL (`http://`, `https://`) will\n * make GET requests when rendered in the editor, revealing the viewer's IP address\n * and user-agent to the image host. This can be exploited as a tracking pixel.\n * For privacy-sensitive deployments, use an upload handler (`options.uploadHandler`)\n * to proxy images through your own server, or restrict to data URIs only.\n */\nexport function registerImageCommands(engine) {\n engine.commands.register('insertImage', {\n execute(eng, { src, alt = '', width, height }) {\n if (!src) return\n\n // Block SVG data URIs — can contain executable JavaScript\n if (/^data:image\\/svg/i.test(src)) return\n\n // Block external SVG URLs — SVGs loaded via <img src> can still\n // exploit CSS-based attacks; only raster formats are safe over URL\n try {\n const url = new URL(src, window.location.href)\n if (url.pathname.toLowerCase().endsWith('.svg')) return\n } catch {\n // relative URLs that fail parsing are checked via extension\n if (/\\.svg(\\?|#|$)/i.test(src)) return\n }\n\n // Ensure editor has focus (e.g. when called from a modal)\n eng.element.focus()\n\n // If no selection range exists, place cursor at the end\n if (!eng.selection.getRange()) {\n const range = document.createRange()\n range.selectNodeContents(eng.element)\n range.collapse(false)\n eng.selection.setRange(range)\n }\n\n const img = document.createElement('img')\n img.src = src\n img.alt = alt\n if (width) img.style.width = typeof width === 'number' ? `${width}px` : width\n if (height) img.style.height = typeof height === 'number' ? `${height}px` : height\n img.style.maxWidth = '100%'\n img.className = 'rmx-image'\n\n eng.selection.insertNode(img)\n\n // Add a paragraph after the image if it's the last element\n if (!img.nextSibling || img.parentElement === eng.element) {\n const p = document.createElement('p')\n p.innerHTML = '<br>'\n img.parentElement.insertBefore(p, img.nextSibling)\n }\n },\n meta: { icon: 'image', tooltip: 'Insert Image' },\n })\n\n engine.commands.register('resizeImage', {\n execute(eng, { element, width, height }) {\n if (!element || element.tagName !== 'IMG') return\n element.style.width = typeof width === 'number' ? `${width}px` : width\n if (height) {\n element.style.height = typeof height === 'number' ? `${height}px` : height\n } else {\n element.style.height = 'auto'\n }\n },\n meta: { tooltip: 'Resize Image' },\n })\n\n engine.commands.register('alignImage', {\n execute(eng, { element, alignment }) {\n if (!element || element.tagName !== 'IMG') return\n element.style.float = ''\n element.style.margin = ''\n element.style.display = ''\n\n switch (alignment) {\n case 'left':\n element.style.float = 'left'\n element.style.margin = '0 16px 16px 0'\n break\n case 'right':\n element.style.float = 'right'\n element.style.margin = '0 0 16px 16px'\n break\n case 'center':\n element.style.display = 'block'\n element.style.margin = '16px auto'\n break\n default:\n break\n }\n },\n meta: { tooltip: 'Align Image' },\n })\n\n engine.commands.register('removeImage', {\n execute(eng, { element }) {\n if (!element || element.tagName !== 'IMG') return\n element.parentNode.removeChild(element)\n },\n meta: { tooltip: 'Remove Image' },\n })\n}\n","export function registerTableCommands(engine) {\n engine.commands.register('insertTable', {\n execute(eng, { rows = 3, cols = 3 } = {}) {\n let html = '<table class=\"rmx-table\"><thead><tr>'\n for (let c = 0; c < cols; c++) {\n html += '<th><br></th>'\n }\n html += '</tr></thead><tbody>'\n for (let r = 1; r < rows; r++) {\n html += '<tr>'\n for (let c = 0; c < cols; c++) {\n html += '<td><br></td>'\n }\n html += '</tr>'\n }\n html += '</tbody></table><p><br></p>'\n eng.selection.insertHTML(html)\n },\n meta: { icon: 'table', tooltip: 'Insert Table' },\n })\n\n engine.commands.register('toggleHeaderRow', {\n execute(eng) {\n const table = eng.selection.getClosestElement('table')\n if (!table) return\n const thead = table.querySelector('thead')\n if (thead) {\n // Remove thead — move row to start of tbody, convert th → td\n const headerRow = thead.querySelector('tr')\n if (!headerRow) return\n const newRow = document.createElement('tr')\n for (const th of Array.from(headerRow.cells)) {\n const td = document.createElement('td')\n td.innerHTML = th.innerHTML\n if (th.colSpan > 1) td.colSpan = th.colSpan\n if (th.rowSpan > 1) td.rowSpan = th.rowSpan\n newRow.appendChild(td)\n }\n let tbody = table.querySelector('tbody')\n if (!tbody) {\n tbody = document.createElement('tbody')\n table.appendChild(tbody)\n }\n tbody.insertBefore(newRow, tbody.firstChild)\n thead.remove()\n } else {\n // Create thead from first row of tbody\n let tbody = table.querySelector('tbody')\n if (!tbody) return\n const firstRow = tbody.querySelector('tr')\n if (!firstRow) return\n const newThead = document.createElement('thead')\n const newRow = document.createElement('tr')\n for (const td of Array.from(firstRow.cells)) {\n const th = document.createElement('th')\n th.innerHTML = td.innerHTML\n if (td.colSpan > 1) th.colSpan = td.colSpan\n if (td.rowSpan > 1) th.rowSpan = td.rowSpan\n newRow.appendChild(th)\n }\n newThead.appendChild(newRow)\n table.insertBefore(newThead, table.firstChild)\n firstRow.remove()\n }\n },\n meta: { tooltip: 'Toggle Header Row' },\n })\n\n engine.commands.register('alignCell', {\n execute(eng, { direction = 'left' } = {}) {\n const td = eng.selection.getClosestElement('td') || eng.selection.getClosestElement('th')\n if (!td) return\n if (!['left', 'center', 'right'].includes(direction)) return\n td.style.textAlign = direction\n },\n meta: { tooltip: 'Align Cell Content' },\n })\n\n engine.commands.register('addRowBefore', {\n execute(eng) {\n const td = eng.selection.getClosestElement('td') || eng.selection.getClosestElement('th')\n if (!td) return\n const tr = td.parentElement\n const newRow = createEmptyRow(getColumnCount(tr))\n tr.parentElement.insertBefore(newRow, tr)\n },\n meta: { tooltip: 'Insert Row Before' },\n })\n\n engine.commands.register('addRowAfter', {\n execute(eng) {\n const td = eng.selection.getClosestElement('td') || eng.selection.getClosestElement('th')\n if (!td) return\n const tr = td.parentElement\n const newRow = createEmptyRow(getColumnCount(tr))\n tr.parentElement.insertBefore(newRow, tr.nextSibling)\n },\n meta: { tooltip: 'Insert Row After' },\n })\n\n engine.commands.register('addColBefore', {\n execute(eng) {\n const td = eng.selection.getClosestElement('td') || eng.selection.getClosestElement('th')\n if (!td) return\n const colIndex = getCellIndex(td)\n const table = eng.selection.getClosestElement('table')\n if (!table) return\n table.querySelectorAll('tr').forEach((row) => {\n const cell = document.createElement(row.parentElement.tagName === 'THEAD' ? 'th' : 'td')\n cell.innerHTML = '<br>'\n const refCell = row.cells[colIndex]\n row.insertBefore(cell, refCell)\n })\n },\n meta: { tooltip: 'Insert Column Before' },\n })\n\n engine.commands.register('addColAfter', {\n execute(eng) {\n const td = eng.selection.getClosestElement('td') || eng.selection.getClosestElement('th')\n if (!td) return\n const colIndex = getCellIndex(td)\n const table = eng.selection.getClosestElement('table')\n if (!table) return\n table.querySelectorAll('tr').forEach((row) => {\n const cell = document.createElement(row.parentElement.tagName === 'THEAD' ? 'th' : 'td')\n cell.innerHTML = '<br>'\n const refCell = row.cells[colIndex]\n row.insertBefore(cell, refCell ? refCell.nextSibling : null)\n })\n },\n meta: { tooltip: 'Insert Column After' },\n })\n\n engine.commands.register('deleteRow', {\n execute(eng) {\n const td = eng.selection.getClosestElement('td') || eng.selection.getClosestElement('th')\n if (!td) return\n const tr = td.parentElement\n const tbody = tr.parentElement\n if (tbody.rows.length <= 1) {\n // If last row, remove the entire table\n const table = eng.selection.getClosestElement('table')\n if (table) {\n const p = document.createElement('p')\n p.innerHTML = '<br>'\n table.parentElement.replaceChild(p, table)\n }\n } else {\n tr.remove()\n }\n },\n meta: { tooltip: 'Delete Row' },\n })\n\n engine.commands.register('deleteCol', {\n execute(eng) {\n const td = eng.selection.getClosestElement('td') || eng.selection.getClosestElement('th')\n if (!td) return\n const colIndex = getCellIndex(td)\n const table = eng.selection.getClosestElement('table')\n if (!table) return\n\n const firstRow = table.querySelector('tr')\n if (firstRow && firstRow.cells.length <= 1) {\n // If last column, remove the entire table\n const p = document.createElement('p')\n p.innerHTML = '<br>'\n table.parentElement.replaceChild(p, table)\n } else {\n table.querySelectorAll('tr').forEach((row) => {\n if (row.cells[colIndex]) {\n row.cells[colIndex].remove()\n }\n })\n }\n },\n meta: { tooltip: 'Delete Column' },\n })\n\n engine.commands.register('deleteTable', {\n execute(eng) {\n const table = eng.selection.getClosestElement('table')\n if (!table) return\n const p = document.createElement('p')\n p.innerHTML = '<br>'\n table.parentElement.replaceChild(p, table)\n },\n meta: { tooltip: 'Delete Table' },\n })\n\n engine.commands.register('mergeCells', {\n execute(eng, { cells }) {\n if (!cells || cells.length < 2) return\n\n const firstCell = cells[0]\n const rows = new Set()\n const cols = new Set()\n\n cells.forEach((cell) => {\n rows.add(cell.parentElement.rowIndex)\n cols.add(getCellIndex(cell))\n })\n\n const rowSpan = rows.size\n const colSpan = cols.size\n\n // Collect content from all cells, then apply once to avoid repeated innerHTML concat\n const fragments = []\n cells.slice(1).forEach((cell) => {\n if (cell.textContent.trim()) {\n fragments.push(cell.innerHTML)\n }\n cell.remove()\n })\n if (fragments.length > 0) {\n firstCell.innerHTML += '<br>' + fragments.join('<br>')\n }\n\n if (rowSpan > 1) firstCell.rowSpan = rowSpan\n if (colSpan > 1) firstCell.colSpan = colSpan\n },\n meta: { tooltip: 'Merge Cells' },\n })\n\n engine.commands.register('sortTable', {\n execute(eng, { columnIndex, direction, dataType, keys, comparator } = {}) {\n const table = eng.selection.getClosestElement('table')\n if (!table) return\n const tbody = table.querySelector('tbody')\n if (!tbody) return\n const rows = Array.from(tbody.querySelectorAll('tr'))\n if (rows.length === 0) return\n\n // Support multi-column sort via keys array, or single-column via columnIndex/direction\n const sortKeys = keys || [{ columnIndex, direction: direction || 'asc', dataType }]\n\n // Build sort comparator\n const getCell = (row, idx) => {\n const cells = row.querySelectorAll('td, th')\n return cells[idx]?.textContent?.trim() || ''\n }\n\n const customComparator = comparator || eng.options?.tableSortComparator\n\n const chainedCompare = (a, b) => {\n for (const key of sortKeys) {\n const valA = getCell(a, key.columnIndex)\n const valB = getCell(b, key.columnIndex)\n const type = key.dataType || detectSortDataType(rows, key.columnIndex)\n let result = 0\n\n if (customComparator) {\n result = customComparator(valA, valB, type, key.columnIndex)\n } else if (type === 'numeric') {\n const numA = parseFloat(valA) || 0\n const numB = parseFloat(valB) || 0\n result = numA - numB\n } else if (type === 'date') {\n const dateA = new Date(valA).getTime() || 0\n const dateB = new Date(valB).getTime() || 0\n result = dateA - dateB\n } else {\n result = valA.localeCompare(valB, undefined, { sensitivity: 'base' })\n }\n\n if (key.direction === 'desc') result = -result\n if (result !== 0) return result\n }\n return 0\n }\n\n rows.sort(chainedCompare)\n rows.forEach(row => tbody.appendChild(row))\n\n // Update sort indicators on thead\n const thead = table.querySelector('thead')\n if (thead) {\n const ths = thead.querySelectorAll('th')\n ths.forEach(th => {\n th.removeAttribute('data-sort-dir')\n th.removeAttribute('data-sort-priority')\n })\n sortKeys.forEach((key, idx) => {\n if (ths[key.columnIndex]) {\n ths[key.columnIndex].setAttribute('data-sort-dir', key.direction || 'asc')\n if (sortKeys.length > 1) {\n ths[key.columnIndex].setAttribute('data-sort-priority', String(idx + 1))\n }\n }\n })\n }\n },\n meta: { tooltip: 'Sort Table' },\n })\n\n engine.commands.register('filterTable', {\n execute(eng, { columnIndex, filterValue } = {}) {\n const table = eng.selection.getClosestElement('table')\n if (!table) return\n const thead = table.querySelector('thead')\n const tbody = table.querySelector('tbody')\n if (!tbody) return\n\n // Store filter value on the header cell\n if (thead) {\n const ths = thead.querySelectorAll('th')\n if (ths[columnIndex]) {\n if (filterValue) {\n ths[columnIndex].setAttribute('data-filter-value', filterValue)\n } else {\n ths[columnIndex].removeAttribute('data-filter-value')\n }\n }\n }\n\n // Collect all active filters\n const filters = []\n if (thead) {\n thead.querySelectorAll('th').forEach((th, idx) => {\n const val = th.getAttribute('data-filter-value')\n if (val) filters.push({ columnIndex: idx, value: val.toLowerCase() })\n })\n }\n\n // Apply filters to rows\n const rows = tbody.querySelectorAll('tr')\n rows.forEach(row => {\n const cells = row.querySelectorAll('td, th')\n const hidden = filters.some(f => {\n const cellText = (cells[f.columnIndex]?.textContent || '').toLowerCase()\n return !cellText.includes(f.value)\n })\n if (hidden) {\n row.classList.add('rmx-row-hidden')\n } else {\n row.classList.remove('rmx-row-hidden')\n }\n })\n },\n meta: { tooltip: 'Filter Table' },\n })\n\n engine.commands.register('clearTableFilters', {\n execute(eng) {\n const table = eng.selection.getClosestElement('table')\n if (!table) return\n const thead = table.querySelector('thead')\n if (thead) {\n thead.querySelectorAll('th').forEach(th => {\n th.removeAttribute('data-filter-value')\n })\n }\n const tbody = table.querySelector('tbody')\n if (tbody) {\n tbody.querySelectorAll('tr.rmx-row-hidden').forEach(row => {\n row.classList.remove('rmx-row-hidden')\n })\n }\n },\n meta: { tooltip: 'Clear Table Filters' },\n })\n\n engine.commands.register('formatCell', {\n execute(eng, { format, options = {} } = {}) {\n const td = eng.selection.getClosestElement('td') || eng.selection.getClosestElement('th')\n if (!td || !format) return\n\n const rawValue = td.getAttribute('data-raw-value') || td.textContent.trim()\n td.setAttribute('data-raw-value', rawValue)\n td.setAttribute('data-cell-format', format)\n\n const num = parseFloat(rawValue)\n\n if (format === 'number' && !isNaN(num)) {\n td.textContent = new Intl.NumberFormat(undefined, {\n minimumFractionDigits: options.decimals ?? 2,\n maximumFractionDigits: options.decimals ?? 2,\n }).format(num)\n } else if (format === 'currency' && !isNaN(num)) {\n td.textContent = new Intl.NumberFormat(undefined, {\n style: 'currency',\n currency: options.currency || 'USD',\n minimumFractionDigits: options.decimals ?? 2,\n maximumFractionDigits: options.decimals ?? 2,\n }).format(num)\n } else if (format === 'percentage' && !isNaN(num)) {\n const decimals = options.decimals ?? 1\n td.textContent = (num * 100).toFixed(decimals) + '%'\n } else if (format === 'date') {\n const date = new Date(rawValue)\n if (!isNaN(date.getTime())) {\n td.textContent = new Intl.DateTimeFormat(undefined, {\n dateStyle: options.dateStyle || 'short',\n }).format(date)\n }\n }\n },\n meta: { tooltip: 'Format Cell' },\n })\n\n engine.commands.register('evaluateFormulas', {\n execute(eng) {\n const table = eng.selection.getClosestElement('table')\n if (!table) return\n evaluateTableFormulas(table)\n },\n meta: { tooltip: 'Evaluate Formulas' },\n })\n\n engine.commands.register('splitCell', {\n execute(eng) {\n const td = eng.selection.getClosestElement('td') || eng.selection.getClosestElement('th')\n if (!td) return\n const colspan = td.colSpan || 1\n const rowspan = td.rowSpan || 1\n if (colspan <= 1 && rowspan <= 1) return\n\n td.colSpan = 1\n td.rowSpan = 1\n\n // Add missing cells in the same row\n const tr = td.parentElement\n const cellTag = tr.closest('thead') ? 'th' : td.tagName.toLowerCase()\n for (let c = 1; c < colspan; c++) {\n const newCell = document.createElement(cellTag)\n newCell.innerHTML = '<br>'\n tr.insertBefore(newCell, td.nextSibling)\n }\n\n // Add missing cells in subsequent rows\n if (rowspan > 1) {\n // Compute the visual column index once from the original row\n const targetColIndex = getCellIndex(td)\n let currentRow = tr.nextElementSibling\n for (let r = 1; r < rowspan; r++) {\n if (!currentRow) break\n const rowCellTag = currentRow.closest('thead') ? 'th' : 'td'\n // Find the correct insertion point in this row by walking its cells\n let insertRef = null\n let colAccum = 0\n for (const cell of currentRow.cells) {\n if (colAccum >= targetColIndex) {\n insertRef = cell\n break\n }\n colAccum += cell.colSpan || 1\n }\n for (let c = 0; c < colspan; c++) {\n const newCell = document.createElement(rowCellTag)\n newCell.innerHTML = '<br>'\n currentRow.insertBefore(newCell, insertRef)\n }\n currentRow = currentRow.nextElementSibling\n }\n }\n },\n meta: { tooltip: 'Split Cell' },\n })\n}\n\nfunction getColumnCount(tr) {\n let count = 0\n for (const cell of tr.cells) {\n count += cell.colSpan || 1\n }\n return count\n}\n\nfunction getCellIndex(cell) {\n let index = 0\n let prev = cell.previousElementSibling\n while (prev) {\n index += prev.colSpan || 1\n prev = prev.previousElementSibling\n }\n return index\n}\n\nfunction createEmptyRow(colCount) {\n const tr = document.createElement('tr')\n for (let i = 0; i < colCount; i++) {\n const td = document.createElement('td')\n td.innerHTML = '<br>'\n tr.appendChild(td)\n }\n return tr\n}\n\n/**\n * Detect the data type of a column by sampling its values.\n */\nfunction detectSortDataType(rows, columnIndex) {\n let numericCount = 0\n let dateCount = 0\n let total = 0\n\n for (const row of rows) {\n const cells = row.querySelectorAll('td, th')\n const text = (cells[columnIndex]?.textContent || '').trim()\n if (!text) continue\n total++\n if (!isNaN(parseFloat(text)) && isFinite(text)) numericCount++\n else if (!isNaN(new Date(text).getTime()) && text.length > 4) dateCount++\n }\n\n if (total === 0) return 'alphabetical'\n if (numericCount / total > 0.7) return 'numeric'\n if (dateCount / total > 0.7) return 'date'\n return 'alphabetical'\n}\n\n// ── Formula Engine ──\n\nconst FORMULA_FUNCTIONS = {\n SUM: (args) => args.flat().reduce((a, b) => a + (parseFloat(b) || 0), 0),\n AVERAGE: (args) => {\n const flat = args.flat().map(v => parseFloat(v)).filter(v => !isNaN(v))\n return flat.length ? flat.reduce((a, b) => a + b, 0) / flat.length : 0\n },\n COUNT: (args) => args.flat().filter(v => v !== '' && v != null).length,\n MIN: (args) => Math.min(...args.flat().map(v => parseFloat(v)).filter(v => !isNaN(v))),\n MAX: (args) => Math.max(...args.flat().map(v => parseFloat(v)).filter(v => !isNaN(v))),\n IF: (args) => args[0] ? args[1] : (args[2] ?? ''),\n CONCAT: (args) => args.flat().join(''),\n}\n\n/**\n * Parse a cell reference like \"A1\" into { col, row } (0-indexed).\n */\nfunction parseCellRef(ref) {\n const match = ref.match(/^([A-Z]+)(\\d+)$/)\n if (!match) return null\n let col = 0\n for (let i = 0; i < match[1].length; i++) {\n col = col * 26 + (match[1].charCodeAt(i) - 64)\n }\n return { col: col - 1, row: parseInt(match[2], 10) - 1 }\n}\n\n/**\n * Expand a range like \"A1:B3\" into an array of { col, row } refs.\n */\nfunction expandRange(rangeStr) {\n const parts = rangeStr.split(':')\n if (parts.length !== 2) return null\n const start = parseCellRef(parts[0])\n const end = parseCellRef(parts[1])\n if (!start || !end) return null\n const refs = []\n for (let r = start.row; r <= end.row; r++) {\n for (let c = start.col; c <= end.col; c++) {\n refs.push({ col: c, row: r })\n }\n }\n return refs\n}\n\n/**\n * Get cell value from table by col/row indices.\n * Row 0 = first row of thead (if exists) or first row of tbody.\n */\nfunction getTableCellValue(table, col, row) {\n const allRows = table.querySelectorAll('thead tr, tbody tr')\n const tr = allRows[row]\n if (!tr) return ''\n const cells = tr.querySelectorAll('td, th')\n if (!cells[col]) return ''\n // Use raw value if available, otherwise text content\n return cells[col].getAttribute('data-raw-value') || cells[col].textContent.trim()\n}\n\n/**\n * Tokenize a formula string into tokens.\n */\nfunction tokenizeFormula(formula) {\n const tokens = []\n let i = 0\n while (i < formula.length) {\n const ch = formula[i]\n if (/\\s/.test(ch)) { i++; continue }\n if ('+-*/(),'.includes(ch)) {\n tokens.push({ type: 'op', value: ch })\n i++\n continue\n }\n if (ch === '>' || ch === '<' || ch === '=') {\n if (formula[i + 1] === '=') {\n tokens.push({ type: 'op', value: ch + '=' })\n i += 2\n } else {\n tokens.push({ type: 'op', value: ch })\n i++\n }\n continue\n }\n if (/\\d/.test(ch) || (ch === '.' && /\\d/.test(formula[i + 1]))) {\n let num = ''\n while (i < formula.length && (/\\d/.test(formula[i]) || formula[i] === '.')) {\n num += formula[i++]\n }\n tokens.push({ type: 'number', value: parseFloat(num) })\n continue\n }\n if (ch === '\"') {\n let str = ''\n i++\n while (i < formula.length && formula[i] !== '\"') str += formula[i++]\n i++ // skip closing quote\n tokens.push({ type: 'string', value: str })\n continue\n }\n if (/[A-Z]/i.test(ch)) {\n let ident = ''\n while (i < formula.length && /[A-Z0-9]/i.test(formula[i])) {\n ident += formula[i++]\n }\n // Check if it's a range (e.g., A1:B3)\n if (formula[i] === ':') {\n i++ // skip colon\n let end = ''\n while (i < formula.length && /[A-Z0-9]/i.test(formula[i])) {\n end += formula[i++]\n }\n tokens.push({ type: 'range', value: ident.toUpperCase() + ':' + end.toUpperCase() })\n } else if (/^[A-Z]+\\d+$/i.test(ident)) {\n tokens.push({ type: 'cellref', value: ident.toUpperCase() })\n } else {\n tokens.push({ type: 'func', value: ident.toUpperCase() })\n }\n continue\n }\n i++ // skip unknown\n }\n return tokens\n}\n\n/**\n * Recursive-descent parser and evaluator for formulas.\n */\nfunction evaluateFormula(formulaStr, table, evalStack = new Set()) {\n // Strip leading '=' that marks a cell as a formula\n const cleanFormula = formulaStr.startsWith('=') ? formulaStr.slice(1) : formulaStr\n const tokens = tokenizeFormula(cleanFormula)\n let pos = 0\n\n const peek = () => tokens[pos] || null\n const next = () => tokens[pos++]\n\n function parseExpression() {\n let left = parseTerm()\n while (peek() && (peek().value === '+' || peek().value === '-')) {\n const op = next().value\n const right = parseTerm()\n left = op === '+' ? left + right : left - right\n }\n return left\n }\n\n function parseTerm() {\n let left = parseComparison()\n while (peek() && (peek().value === '*' || peek().value === '/')) {\n const op = next().value\n const right = parseComparison()\n left = op === '*' ? left * right : (right !== 0 ? left / right : NaN)\n }\n return left\n }\n\n function parseComparison() {\n let left = parseFactor()\n if (peek() && ['>', '<', '>=', '<=', '=='].includes(peek().value)) {\n const op = next().value\n const right = parseFactor()\n if (op === '>') return left > right ? 1 : 0\n if (op === '<') return left < right ? 1 : 0\n if (op === '>=') return left >= right ? 1 : 0\n if (op === '<=') return left <= right ? 1 : 0\n if (op === '==') return left === right ? 1 : 0\n }\n return left\n }\n\n function parseFactor() {\n const token = peek()\n if (!token) return 0\n\n if (token.type === 'number') {\n next()\n return token.value\n }\n if (token.type === 'string') {\n next()\n return token.value\n }\n if (token.type === 'cellref') {\n next()\n const ref = parseCellRef(token.value)\n if (!ref) return 0\n const cellKey = token.value\n if (evalStack.has(cellKey)) return '#CIRC!'\n evalStack.add(cellKey)\n const raw = getTableCellValue(table, ref.col, ref.row)\n // If the cell itself has a formula, evaluate it\n const allRows = table.querySelectorAll('thead tr, tbody tr')\n const cell = allRows[ref.row]?.querySelectorAll('td, th')[ref.col]\n const cellFormula = cell?.getAttribute('data-formula')\n let val\n if (cellFormula) {\n val = evaluateFormula(cellFormula, table, evalStack)\n } else {\n val = parseFloat(raw)\n if (isNaN(val)) val = raw\n }\n evalStack.delete(cellKey)\n return val\n }\n if (token.type === 'range') {\n next()\n const refs = expandRange(token.value)\n if (!refs) return []\n return refs.map(r => {\n const raw = getTableCellValue(table, r.col, r.row)\n const num = parseFloat(raw)\n return isNaN(num) ? raw : num\n })\n }\n if (token.type === 'func') {\n const funcName = next().value\n const fn = FORMULA_FUNCTIONS[funcName]\n if (!fn) return '#NAME!'\n // Expect '('\n if (peek()?.value === '(') next()\n const args = []\n while (peek() && peek().value !== ')') {\n if (peek().value === ',') { next(); continue }\n const arg = peek()?.type === 'range' ? parseFactor() : parseExpression()\n args.push(arg)\n }\n if (peek()?.value === ')') next()\n return fn(args)\n }\n if (token.value === '(') {\n next()\n const val = parseExpression()\n if (peek()?.value === ')') next()\n return val\n }\n if (token.value === '-') {\n next()\n return -parseFactor()\n }\n\n next() // skip unknown\n return 0\n }\n\n return parseExpression()\n}\n\n/**\n * Evaluate all formula cells in a table.\n */\nexport function evaluateTableFormulas(table) {\n const cells = table.querySelectorAll('td[data-formula], th[data-formula]')\n for (const cell of cells) {\n const formula = cell.getAttribute('data-formula')\n if (!formula) continue\n const result = evaluateFormula(formula, table)\n if (typeof result === 'string' && result.startsWith('#')) {\n cell.textContent = result\n } else if (typeof result === 'number') {\n // Round to 10 decimal places to avoid floating point display artifacts\n cell.textContent = String(Math.round(result * 1e10) / 1e10)\n } else {\n cell.textContent = result\n }\n }\n}\n","/** Block-level tags considered top-level blocks */\nconst BLOCK_TAGS = new Set([\n 'P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6',\n 'BLOCKQUOTE', 'PRE', 'UL', 'OL', 'TABLE', 'HR', 'DIV', 'DETAILS',\n])\n\n/**\n * Get the focused top-level block element (direct child of editor root).\n * @param {object} eng - Editor engine\n * @returns {HTMLElement|null}\n */\nfunction getTopLevelBlock(eng) {\n const block = eng.selection.getParentBlock()\n if (!block) return null\n let el = block\n while (el && el.parentElement !== eng.element) {\n el = el.parentElement\n }\n return (el && BLOCK_TAGS.has(el.tagName)) ? el : null\n}\n\n/**\n * Ensure the editor always has at least one empty paragraph.\n * @param {object} eng - Editor engine\n */\nfunction ensureMinimumContent(eng) {\n if (eng.element.children.length === 0) {\n const p = document.createElement('p')\n p.innerHTML = '<br>'\n eng.element.appendChild(p)\n const range = document.createRange()\n range.selectNodeContents(p)\n range.collapse(true)\n eng.selection.setRange(range)\n }\n}\n\n/**\n * CSP-compatible block-level commands using Range-based DOM manipulation.\n */\nexport function registerBlockCommands(engine) {\n engine.commands.register('blockquote', {\n execute(eng) {\n const existing = eng.selection.getClosestElement('blockquote')\n if (existing) {\n // Toggle off: unwrap blockquote\n const parent = existing.parentNode\n while (existing.firstChild) {\n parent.insertBefore(existing.firstChild, existing)\n }\n parent.removeChild(existing)\n } else {\n // Wrap current block in <blockquote>\n const block = eng.selection.getParentBlock()\n if (!block) return\n const bq = document.createElement('blockquote')\n block.parentNode.replaceChild(bq, block)\n bq.appendChild(block)\n }\n },\n isActive(eng) {\n return !!eng.selection.getClosestElement('blockquote')\n },\n shortcut: 'mod+shift+9',\n meta: { icon: 'blockquote', tooltip: 'Blockquote' },\n })\n\n engine.commands.register('codeBlock', {\n execute(eng, { language } = {}) {\n const existing = eng.selection.getClosestElement('pre')\n if (existing) {\n // Toggle off: unwrap pre/code\n const text = existing.textContent\n const p = document.createElement('p')\n p.textContent = text\n existing.parentNode.replaceChild(p, existing)\n // Move cursor into the new paragraph\n const range = document.createRange()\n range.selectNodeContents(p)\n range.collapse(false)\n eng.selection.setRange(range)\n } else {\n const range = eng.selection.getRange()\n if (!range) return\n const text = range.collapsed ? '\\n' : range.toString()\n const pre = document.createElement('pre')\n const code = document.createElement('code')\n code.textContent = text\n if (language) {\n code.setAttribute('data-language', language)\n pre.setAttribute('data-language', language)\n }\n pre.appendChild(code)\n\n if (!range.collapsed) {\n range.deleteContents()\n }\n range.insertNode(pre)\n\n // Add paragraph after if needed\n if (!pre.nextSibling) {\n const p = document.createElement('p')\n p.innerHTML = '<br>'\n pre.parentNode.insertBefore(p, pre.nextSibling)\n }\n\n const newRange = document.createRange()\n newRange.selectNodeContents(code)\n newRange.collapse(false)\n eng.selection.setRange(newRange)\n\n // Emit event so the syntax highlight plugin can pick it up\n eng.eventBus.emit('codeblock:created', { element: pre, language })\n }\n },\n isActive(eng) {\n return !!eng.selection.getClosestElement('pre')\n },\n shortcut: 'mod+shift+c',\n meta: { icon: 'codeBlock', tooltip: 'Code Block' },\n })\n\n engine.commands.register('horizontalRule', {\n execute(eng) {\n const range = eng.selection.getRange()\n if (!range) return\n\n const hr = document.createElement('hr')\n range.deleteContents()\n range.insertNode(hr)\n\n // Add a paragraph after the hr for continued editing\n if (!hr.nextSibling || hr.nextSibling.tagName !== 'P') {\n const p = document.createElement('p')\n p.innerHTML = '<br>'\n hr.parentNode.insertBefore(p, hr.nextSibling)\n }\n\n // Move cursor after the hr\n const newRange = document.createRange()\n newRange.setStartAfter(hr)\n newRange.collapse(true)\n eng.selection.setRange(newRange)\n },\n meta: { icon: 'horizontalRule', tooltip: 'Horizontal Rule' },\n })\n\n // ── Block Operations ────────────────────────────────────────────────\n\n engine.commands.register('moveBlockUp', {\n execute(eng) {\n const block = getTopLevelBlock(eng)\n if (!block || !block.previousElementSibling) return\n eng.history.snapshot()\n block.parentNode.insertBefore(block, block.previousElementSibling)\n eng.eventBus.emit('content:change')\n },\n meta: { tooltip: 'Move Block Up' },\n })\n\n engine.commands.register('moveBlockDown', {\n execute(eng) {\n const block = getTopLevelBlock(eng)\n if (!block || !block.nextElementSibling) return\n eng.history.snapshot()\n block.parentNode.insertBefore(block, block.nextElementSibling.nextSibling)\n eng.eventBus.emit('content:change')\n },\n meta: { tooltip: 'Move Block Down' },\n })\n\n engine.commands.register('duplicateBlock', {\n execute(eng) {\n const block = getTopLevelBlock(eng)\n if (!block) return\n eng.history.snapshot()\n const clone = block.cloneNode(true)\n block.parentNode.insertBefore(clone, block.nextSibling)\n // Place cursor in the cloned block\n const range = document.createRange()\n range.selectNodeContents(clone)\n range.collapse(false)\n eng.selection.setRange(range)\n eng.eventBus.emit('content:change')\n },\n meta: { tooltip: 'Duplicate Block' },\n })\n\n engine.commands.register('deleteBlock', {\n execute(eng) {\n const block = getTopLevelBlock(eng)\n if (!block) return\n eng.history.snapshot()\n // Determine where to place cursor after deletion\n const next = block.nextElementSibling\n const prev = block.previousElementSibling\n block.parentNode.removeChild(block)\n ensureMinimumContent(eng)\n // Move cursor to adjacent block if available\n const target = next || prev || eng.element.firstElementChild\n if (target) {\n const range = document.createRange()\n range.selectNodeContents(target)\n range.collapse(true)\n eng.selection.setRange(range)\n }\n eng.eventBus.emit('content:change')\n },\n meta: { tooltip: 'Delete Block' },\n })\n\n engine.commands.register('selectBlocks', {\n execute(eng, { blocks } = {}) {\n if (!blocks || !Array.isArray(blocks)) return\n blocks.forEach((el) => {\n if (el && el.classList) {\n el.classList.add('rmx-block-selected')\n }\n })\n },\n meta: { tooltip: 'Select Blocks' },\n })\n\n engine.commands.register('clearBlockSelection', {\n execute(eng) {\n const selected = eng.element.querySelectorAll('.rmx-block-selected')\n selected.forEach((el) => el.classList.remove('rmx-block-selected'))\n },\n meta: { tooltip: 'Clear Block Selection' },\n })\n\n // ── Nested Blocks: Collapsible Sections ─────────────────────────────\n\n engine.commands.register('toggleCollapse', {\n execute(eng) {\n const block = getTopLevelBlock(eng)\n if (!block) return\n eng.history.snapshot()\n\n if (block.tagName === 'DETAILS') {\n // Unwrap: move children out of details, remove the summary\n const parent = block.parentNode\n const summary = block.querySelector('summary')\n // Move all children except summary before the details element\n const children = Array.from(block.childNodes).filter((n) => n !== summary)\n children.forEach((child) => parent.insertBefore(child, block))\n parent.removeChild(block)\n } else {\n // Wrap in <details><summary>\n const details = document.createElement('details')\n details.className = 'rmx-collapsible'\n details.setAttribute('open', '')\n const summary = document.createElement('summary')\n summary.textContent = block.textContent.slice(0, 50) || 'Section'\n block.parentNode.replaceChild(details, block)\n details.appendChild(summary)\n details.appendChild(block)\n }\n eng.eventBus.emit('content:change')\n },\n meta: { tooltip: 'Toggle Collapsible Section' },\n })\n\n // ── Block Grouping ──────────────────────────────────────────────────\n\n engine.commands.register('groupBlocks', {\n execute(eng) {\n const selected = Array.from(eng.element.querySelectorAll('.rmx-block-selected'))\n if (selected.length === 0) return\n eng.history.snapshot()\n\n const group = document.createElement('div')\n group.className = 'rmx-block-group'\n\n // Insert group before the first selected block\n selected[0].parentNode.insertBefore(group, selected[0])\n\n // Move all selected blocks into the group\n selected.forEach((el) => {\n el.classList.remove('rmx-block-selected')\n group.appendChild(el)\n })\n\n eng.eventBus.emit('content:change')\n },\n meta: { tooltip: 'Group Selected Blocks' },\n })\n\n engine.commands.register('ungroupBlocks', {\n execute(eng) {\n const block = getTopLevelBlock(eng)\n if (!block) return\n\n // Find the closest group div\n let group = block\n if (!group.classList.contains('rmx-block-group')) {\n group = block.closest('.rmx-block-group')\n }\n if (!group || !group.classList.contains('rmx-block-group')) return\n\n eng.history.snapshot()\n const parent = group.parentNode\n while (group.firstChild) {\n parent.insertBefore(group.firstChild, group)\n }\n parent.removeChild(group)\n eng.eventBus.emit('content:change')\n },\n meta: { tooltip: 'Ungroup Blocks' },\n })\n\n engine.commands.register('moveGroup', {\n execute(eng, { direction } = {}) {\n const block = getTopLevelBlock(eng)\n if (!block) return\n let group = block.classList.contains('rmx-block-group')\n ? block\n : block.closest('.rmx-block-group')\n if (!group || !group.classList.contains('rmx-block-group')) return\n // Ensure group is a direct child of editor\n while (group.parentElement !== eng.element) {\n group = group.parentElement\n if (!group) return\n }\n\n eng.history.snapshot()\n if (direction === 'up' && group.previousElementSibling) {\n group.parentNode.insertBefore(group, group.previousElementSibling)\n } else if (direction === 'down' && group.nextElementSibling) {\n group.parentNode.insertBefore(group, group.nextElementSibling.nextSibling)\n }\n eng.eventBus.emit('content:change')\n },\n meta: { tooltip: 'Move Group' },\n })\n\n engine.commands.register('duplicateGroup', {\n execute(eng) {\n const block = getTopLevelBlock(eng)\n if (!block) return\n let group = block.classList.contains('rmx-block-group')\n ? block\n : block.closest('.rmx-block-group')\n if (!group || !group.classList.contains('rmx-block-group')) return\n // Ensure group is a direct child of editor\n while (group.parentElement !== eng.element) {\n group = group.parentElement\n if (!group) return\n }\n\n eng.history.snapshot()\n const clone = group.cloneNode(true)\n group.parentNode.insertBefore(clone, group.nextSibling)\n eng.eventBus.emit('content:change')\n },\n meta: { tooltip: 'Duplicate Group' },\n })\n}\n","/**\n * Block type conversion commands.\n * Converts block-level elements between types (paragraph, headings, blockquote, code block, lists)\n * while preserving text content.\n */\n\n/** Map of conversion target names to their corresponding tag info */\nconst BLOCK_TYPE_MAP = {\n paragraph: { tag: 'P' },\n heading1: { tag: 'H1' },\n heading2: { tag: 'H2' },\n heading3: { tag: 'H3' },\n heading4: { tag: 'H4' },\n heading5: { tag: 'H5' },\n heading6: { tag: 'H6' },\n blockquote: { tag: 'BLOCKQUOTE' },\n codeBlock: { tag: 'PRE' },\n unorderedList: { tag: 'UL' },\n orderedList: { tag: 'OL' },\n}\n\n/**\n * Get the focused block element in the editor.\n * @param {object} eng - Editor engine\n * @returns {HTMLElement|null}\n */\nfunction getFocusedBlock(eng) {\n const block = eng.selection.getParentBlock()\n if (!block) return null\n // Walk up to find the direct child of the editor element\n let el = block\n while (el && el.parentElement !== eng.element) {\n el = el.parentElement\n }\n return el || null\n}\n\n/**\n * Extract the inner text/HTML content from a block, stripping wrappers.\n * For lists, extracts LI contents. For pre>code, extracts text.\n * @param {HTMLElement} block\n * @returns {string} innerHTML suitable for insertion into a new block\n */\nfunction extractBlockContent(block) {\n const tag = block.tagName\n\n if (tag === 'UL' || tag === 'OL') {\n // Collect text from all list items\n const items = Array.from(block.querySelectorAll('li'))\n if (items.length === 1) {\n return items[0].innerHTML\n }\n // Join multiple items with line breaks\n return items.map((li) => li.innerHTML).join('<br>')\n }\n\n if (tag === 'PRE') {\n const code = block.querySelector('code')\n return code ? code.textContent : block.textContent\n }\n\n if (tag === 'BLOCKQUOTE') {\n // If blockquote wraps a single element, use its innerHTML\n if (block.children.length === 1) {\n return block.children[0].innerHTML\n }\n return block.innerHTML\n }\n\n return block.innerHTML\n}\n\n/**\n * Create the new block element for the target type.\n * @param {string} to - Target type name\n * @param {string} content - Inner HTML content\n * @returns {HTMLElement}\n */\nfunction createTargetBlock(to, content) {\n const info = BLOCK_TYPE_MAP[to]\n if (!info) return null\n\n if (to === 'unorderedList' || to === 'orderedList') {\n const list = document.createElement(info.tag)\n const li = document.createElement('li')\n li.innerHTML = content || '<br>'\n list.appendChild(li)\n return list\n }\n\n if (to === 'codeBlock') {\n const pre = document.createElement('pre')\n const code = document.createElement('code')\n // For code blocks, use text content only (strip HTML)\n const temp = document.createElement('div')\n temp.innerHTML = content\n code.textContent = temp.textContent || '\\n'\n pre.appendChild(code)\n return pre\n }\n\n if (to === 'blockquote') {\n const bq = document.createElement('blockquote')\n const p = document.createElement('p')\n p.innerHTML = content || '<br>'\n bq.appendChild(p)\n return bq\n }\n\n // Simple block: p, h1-h6\n const el = document.createElement(info.tag)\n el.innerHTML = content || '<br>'\n return el\n}\n\nexport function registerBlockConvertCommands(engine) {\n engine.commands.register('convertBlock', {\n execute(eng, { to } = {}) {\n if (!to || !BLOCK_TYPE_MAP[to]) return\n\n const block = getFocusedBlock(eng)\n if (!block) return\n\n // Don't convert if already the target type\n const targetTag = BLOCK_TYPE_MAP[to].tag\n if (block.tagName === targetTag) return\n\n eng.history.snapshot()\n\n const content = extractBlockContent(block)\n const newBlock = createTargetBlock(to, content)\n if (!newBlock) return\n\n block.parentNode.replaceChild(newBlock, block)\n\n // Place cursor in the new block\n const range = document.createRange()\n let cursorTarget = newBlock\n\n if (to === 'unorderedList' || to === 'orderedList') {\n cursorTarget = newBlock.querySelector('li') || newBlock\n } else if (to === 'codeBlock') {\n cursorTarget = newBlock.querySelector('code') || newBlock\n } else if (to === 'blockquote') {\n cursorTarget = newBlock.querySelector('p') || newBlock\n }\n\n range.selectNodeContents(cursorTarget)\n range.collapse(false)\n eng.selection.setRange(range)\n\n eng.eventBus.emit('content:change')\n },\n meta: { tooltip: 'Convert Block Type' },\n })\n}\n","/**\n * CSP-compatible font formatting commands.\n *\n * All commands use Selection/Range-based span wrapping instead of the\n * deprecated `document.execCommand()` API. This eliminates CSP violations\n * and ensures forward compatibility with browsers removing execCommand.\n */\n\n/**\n * Wraps the current selection in a span with the given style property/value.\n * If already wrapped in a span with that property, updates the value.\n */\nfunction wrapWithStyle(engine, property, value) {\n if (!value) return\n const sel = engine.selection.getSelection()\n if (!sel || sel.rangeCount === 0) return\n const range = sel.getRangeAt(0)\n if (range.collapsed) return\n\n // Check if already wrapped in a styled span\n const parent = engine.selection.getParentElement()\n if (parent && parent.tagName === 'SPAN' && parent.style[property]) {\n parent.style[property] = value\n return\n }\n\n const span = document.createElement('span')\n span.style[property] = value\n\n try {\n range.surroundContents(span)\n } catch {\n const fragment = range.extractContents()\n span.appendChild(fragment)\n range.insertNode(span)\n }\n}\n\n/**\n * Get the computed style value for a CSS property from the current selection.\n */\nfunction getSelectionStyle(engine, property) {\n const parent = engine.selection.getParentElement()\n if (!parent) return false\n try {\n return window.getComputedStyle(parent)[property] || false\n } catch {\n return false\n }\n}\n\nexport function registerFontCommands(engine) {\n engine.commands.register('fontFamily', {\n execute(eng, family) {\n wrapWithStyle(eng, 'fontFamily', family)\n },\n isActive(eng) {\n return getSelectionStyle(eng, 'fontFamily')\n },\n meta: { icon: 'fontFamily', tooltip: 'Font Family' },\n })\n\n engine.commands.register('fontSize', {\n execute(eng, size) {\n if (!size) return\n // Validate numeric value\n if (!/^\\d+(\\.\\d+)?(px|pt|em|rem|%)$/.test(size)) return\n wrapWithStyle(eng, 'fontSize', size)\n },\n meta: { icon: 'fontSize', tooltip: 'Font Size' },\n })\n\n engine.commands.register('foreColor', {\n execute(eng, color) {\n wrapWithStyle(eng, 'color', color)\n },\n isActive(eng) {\n return getSelectionStyle(eng, 'color')\n },\n meta: { icon: 'foreColor', tooltip: 'Text Color' },\n })\n\n engine.commands.register('backColor', {\n execute(eng, color) {\n wrapWithStyle(eng, 'backgroundColor', color)\n },\n isActive(eng) {\n return getSelectionStyle(eng, 'backgroundColor')\n },\n meta: { icon: 'backColor', tooltip: 'Background Color' },\n })\n\n // Typography controls — line height, letter spacing, paragraph spacing\n engine.commands.register('lineHeight', {\n execute(eng, value) {\n if (!value) return\n wrapWithStyle(eng, 'lineHeight', value)\n },\n isActive(eng) {\n return getSelectionStyle(eng, 'lineHeight')\n },\n meta: { icon: 'lineHeight', tooltip: 'Line Height' },\n })\n\n engine.commands.register('letterSpacing', {\n execute(eng, value) {\n if (!value) return\n wrapWithStyle(eng, 'letterSpacing', value)\n },\n isActive(eng) {\n return getSelectionStyle(eng, 'letterSpacing')\n },\n meta: { icon: 'letterSpacing', tooltip: 'Letter Spacing' },\n })\n\n engine.commands.register('paragraphSpacing', {\n execute(eng, value) {\n if (!value) return\n // Apply margin-bottom to the parent block element\n const parent = eng.selection.getParentElement()\n if (!parent) return\n const block = parent.closest('p, h1, h2, h3, h4, h5, h6, li, blockquote, pre, div')\n if (block) {\n block.style.marginBottom = value\n }\n },\n isActive(eng) {\n const parent = eng.selection.getParentElement()\n if (!parent) return false\n const block = parent.closest('p, h1, h2, h3, h4, h5, h6, li, blockquote, pre, div')\n if (!block) return false\n return block.style.marginBottom || false\n },\n meta: { icon: 'paragraphSpacing', tooltip: 'Paragraph Spacing' },\n })\n}\n","export function registerMediaCommands(engine) {\n engine.commands.register('embedMedia', {\n execute(eng, { url }) {\n if (!url) return\n\n const embedUrl = getEmbedUrl(url)\n if (!embedUrl) return\n\n const wrapper = document.createElement('div')\n wrapper.className = 'rmx-embed-wrapper'\n wrapper.setAttribute('contenteditable', 'false')\n wrapper.setAttribute('data-embed-url', url)\n\n const iframe = document.createElement('iframe')\n iframe.src = embedUrl\n iframe.setAttribute('frameborder', '0')\n iframe.setAttribute('allowfullscreen', 'true')\n iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-presentation allow-popups')\n iframe.setAttribute('allow', 'autoplay; picture-in-picture')\n iframe.style.width = '100%'\n iframe.style.height = '100%'\n\n wrapper.appendChild(iframe)\n eng.selection.insertNode(wrapper)\n\n // Add paragraph after embed\n const p = document.createElement('p')\n p.innerHTML = '<br>'\n wrapper.parentNode.insertBefore(p, wrapper.nextSibling)\n },\n meta: { icon: 'embedMedia', tooltip: 'Embed Media' },\n })\n\n engine.commands.register('removeEmbed', {\n execute(eng, { element }) {\n if (!element) return\n const wrapper = element.closest('.rmx-embed-wrapper') || element\n const p = document.createElement('p')\n p.innerHTML = '<br>'\n wrapper.parentNode.replaceChild(p, wrapper)\n },\n meta: { tooltip: 'Remove Embed' },\n })\n}\n\nfunction getEmbedUrl(url) {\n // YouTube\n let match = url.match(/(?:youtube\\.com\\/(?:watch\\?v=|embed\\/|v\\/)|youtu\\.be\\/)([a-zA-Z0-9_-]{11})/)\n if (match) {\n return `https://www.youtube.com/embed/${match[1]}`\n }\n\n // Vimeo\n match = url.match(/(?:vimeo\\.com\\/)(\\d+)/)\n if (match) {\n return `https://player.vimeo.com/video/${match[1]}`\n }\n\n // Dailymotion\n match = url.match(/dailymotion\\.com\\/video\\/([a-zA-Z0-9]+)/)\n if (match) {\n return `https://www.dailymotion.com/embed/video/${match[1]}`\n }\n\n return null\n}\n","export function registerFindReplaceCommands(engine) {\n let currentMatches = []\n let currentIndex = -1\n\n function clearHighlights(editorEl) {\n editorEl.querySelectorAll('mark.rmx-find-highlight').forEach((mark) => {\n const parent = mark.parentNode\n parent.replaceChild(document.createTextNode(mark.textContent), mark)\n parent.normalize()\n })\n }\n\n function findMatches(editorEl, searchText, caseSensitive = false) {\n clearHighlights(editorEl)\n if (!searchText) return []\n\n const matches = []\n const walker = document.createTreeWalker(editorEl, NodeFilter.SHOW_TEXT, null)\n const textNodes = []\n\n while (walker.nextNode()) {\n textNodes.push(walker.currentNode)\n }\n\n const flags = caseSensitive ? 'g' : 'gi'\n const regex = new RegExp(escapeRegex(searchText), flags)\n\n // Process text nodes in reverse to avoid offset issues\n for (let i = textNodes.length - 1; i >= 0; i--) {\n const node = textNodes[i]\n const text = node.textContent\n const nodeMatches = []\n let m\n while ((m = regex.exec(text)) !== null) {\n nodeMatches.push({ index: m.index, length: m[0].length })\n }\n\n // Wrap matches in reverse order within this node\n for (let j = nodeMatches.length - 1; j >= 0; j--) {\n const match = nodeMatches[j]\n const range = document.createRange()\n range.setStart(node, match.index)\n range.setEnd(node, match.index + match.length)\n\n const mark = document.createElement('mark')\n mark.className = 'rmx-find-highlight'\n range.surroundContents(mark)\n matches.push(mark)\n }\n }\n\n // Reverse to restore document order (collected in reverse for DOM safety)\n matches.reverse()\n return matches\n }\n\n engine.commands.register('find', {\n execute(eng, { text, caseSensitive }) {\n currentMatches = findMatches(eng.element, text, caseSensitive)\n currentIndex = currentMatches.length > 0 ? 0 : -1\n if (currentIndex >= 0) {\n highlightCurrent()\n }\n eng.eventBus.emit('find:results', {\n total: currentMatches.length,\n current: currentIndex + 1,\n })\n return { total: currentMatches.length, current: currentIndex + 1 }\n },\n shortcut: 'mod+f',\n meta: { icon: 'findReplace', tooltip: 'Find & Replace' },\n })\n\n engine.commands.register('findNext', {\n execute(eng) {\n if (currentMatches.length === 0) return\n currentIndex = (currentIndex + 1) % currentMatches.length\n highlightCurrent()\n eng.eventBus.emit('find:results', {\n total: currentMatches.length,\n current: currentIndex + 1,\n })\n },\n meta: { tooltip: 'Find Next' },\n })\n\n engine.commands.register('findPrev', {\n execute(eng) {\n if (currentMatches.length === 0) return\n currentIndex = (currentIndex - 1 + currentMatches.length) % currentMatches.length\n highlightCurrent()\n eng.eventBus.emit('find:results', {\n total: currentMatches.length,\n current: currentIndex + 1,\n })\n },\n meta: { tooltip: 'Find Previous' },\n })\n\n engine.commands.register('replace', {\n execute(eng, { replaceText }) {\n pruneStaleMatches()\n if (currentMatches.length === 0 || currentIndex < 0 || currentIndex >= currentMatches.length) return\n const mark = currentMatches[currentIndex]\n const textNode = document.createTextNode(replaceText)\n mark.parentNode.replaceChild(textNode, mark)\n currentMatches.splice(currentIndex, 1)\n if (currentMatches.length > 0) {\n currentIndex = Math.min(currentIndex, currentMatches.length - 1)\n highlightCurrent()\n } else {\n currentIndex = -1\n }\n eng.eventBus.emit('find:results', {\n total: currentMatches.length,\n current: currentIndex + 1,\n })\n eng.eventBus.emit('content:change')\n },\n meta: { tooltip: 'Replace' },\n })\n\n engine.commands.register('replaceAll', {\n execute(eng, { replaceText }) {\n pruneStaleMatches()\n currentMatches.forEach((mark) => {\n const textNode = document.createTextNode(replaceText)\n mark.parentNode.replaceChild(textNode, mark)\n })\n const count = currentMatches.length\n currentMatches = []\n currentIndex = -1\n eng.eventBus.emit('find:results', { total: 0, current: 0 })\n eng.eventBus.emit('content:change')\n return count\n },\n meta: { tooltip: 'Replace All' },\n })\n\n engine.commands.register('clearFind', {\n execute(eng) {\n clearHighlights(eng.element)\n currentMatches = []\n currentIndex = -1\n },\n meta: { tooltip: 'Clear Find' },\n })\n\n /** Remove stale mark references that are no longer in the document */\n function pruneStaleMatches() {\n currentMatches = currentMatches.filter((m) => m.isConnected)\n if (currentIndex >= currentMatches.length) {\n currentIndex = currentMatches.length > 0 ? currentMatches.length - 1 : -1\n }\n }\n\n function highlightCurrent() {\n pruneStaleMatches()\n currentMatches.forEach((m, i) => {\n m.className = i === currentIndex ? 'rmx-find-highlight rmx-find-current' : 'rmx-find-highlight'\n })\n if (currentMatches[currentIndex]) {\n currentMatches[currentIndex].scrollIntoView({ block: 'center', behavior: 'smooth' })\n }\n }\n}\n\nfunction escapeRegex(str) {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n","export function registerSourceModeCommands(engine) {\n engine.commands.register('sourceMode', {\n execute(eng) {\n eng.isSourceMode = !eng.isSourceMode\n eng.eventBus.emit('mode:change', { sourceMode: eng.isSourceMode })\n },\n isActive(eng) {\n return eng.isSourceMode\n },\n shortcut: 'mod+shift+u',\n meta: { icon: 'sourceMode', tooltip: 'Source Code' },\n })\n}\n","// Track how many editors are currently fullscreen so we only restore\n// document.body.style.overflow when the last one exits.\nlet fullscreenCount = 0\n\nexport function registerFullscreenCommands(engine) {\n engine.commands.register('fullscreen', {\n execute(eng) {\n const root = eng.element.closest('.rmx-editor')\n if (!root) return\n\n const isFullscreen = root.classList.contains('rmx-fullscreen')\n if (isFullscreen) {\n root.classList.remove('rmx-fullscreen')\n fullscreenCount = Math.max(0, fullscreenCount - 1)\n if (fullscreenCount === 0) {\n document.body.style.overflow = ''\n }\n } else {\n root.classList.add('rmx-fullscreen')\n fullscreenCount++\n document.body.style.overflow = 'hidden'\n }\n eng.eventBus.emit('fullscreen:toggle', { fullscreen: !isFullscreen })\n },\n isActive(eng) {\n const root = eng.element.closest('.rmx-editor')\n return root ? root.classList.contains('rmx-fullscreen') : false\n },\n shortcut: 'mod+shift+f',\n meta: { icon: 'fullscreen', tooltip: 'Fullscreen' },\n })\n}\n","/**\n * Distraction-free mode — hides toolbar, status bar, and menu bar.\n * Chrome elements reappear on mouse movement (3s timeout to re-hide).\n * Toggle via command or Mod+Shift+D shortcut.\n */\n\nlet hideTimer = null\n\nexport function registerDistractionFreeCommands(engine) {\n engine.commands.register('distractionFree', {\n execute(eng) {\n const root = eng.element.closest('.rmx-editor')\n if (!root) return\n\n const isActive = root.classList.contains('rmx-distraction-free')\n if (isActive) {\n root.classList.remove('rmx-distraction-free')\n root.classList.remove('rmx-df-show-chrome')\n root.removeEventListener('mousemove', showChrome)\n clearTimeout(hideTimer)\n } else {\n root.classList.add('rmx-distraction-free')\n root.addEventListener('mousemove', showChrome)\n }\n eng.eventBus.emit('distractionFree:toggle', { active: !isActive })\n },\n isActive(eng) {\n const root = eng.element.closest('.rmx-editor')\n return root ? root.classList.contains('rmx-distraction-free') : false\n },\n shortcut: 'mod+shift+d',\n meta: { icon: 'distractionFree', tooltip: 'Distraction-Free Mode' },\n })\n}\n\nfunction showChrome(e) {\n const root = e.currentTarget\n if (!root.classList.contains('rmx-distraction-free')) return\n\n root.classList.add('rmx-df-show-chrome')\n clearTimeout(hideTimer)\n hideTimer = setTimeout(() => {\n root.classList.remove('rmx-df-show-chrome')\n }, 3000)\n}\n","/**\n * Split view — side-by-side preview pane showing rendered HTML or markdown.\n * Toggle via command or Mod+Shift+V shortcut.\n */\n\nexport function registerSplitViewCommands(engine) {\n engine.commands.register('toggleSplitView', {\n execute(eng) {\n const root = eng.element.closest('.rmx-editor')\n if (!root) return\n\n const isActive = root.classList.contains('rmx-split-view')\n if (isActive) {\n root.classList.remove('rmx-split-view')\n } else {\n root.classList.add('rmx-split-view')\n }\n eng.eventBus.emit('splitView:toggle', { active: !isActive })\n },\n isActive(eng) {\n const root = eng.element.closest('.rmx-editor')\n return root ? root.classList.contains('rmx-split-view') : false\n },\n shortcut: 'mod+shift+v',\n meta: { icon: 'splitView', tooltip: 'Split View' },\n })\n}\n","/**\n * Color palette presets — save and load named color palettes in localStorage.\n */\n\nconst STORAGE_KEY = 'rmx-color-presets'\n\n/**\n * Get all saved color presets from localStorage.\n * @returns {{ name: string, colors: string[] }[]}\n */\nexport function loadColorPresets() {\n try {\n const raw = localStorage.getItem(STORAGE_KEY)\n if (!raw) return []\n const parsed = JSON.parse(raw)\n return Array.isArray(parsed) ? parsed : []\n } catch {\n return []\n }\n}\n\n/**\n * Save a named color preset to localStorage.\n * @param {string} name\n * @param {string[]} colors\n */\nexport function saveColorPreset(name, colors) {\n if (!name || !Array.isArray(colors)) return\n try {\n const presets = loadColorPresets().filter(p => p.name !== name)\n presets.push({ name, colors })\n localStorage.setItem(STORAGE_KEY, JSON.stringify(presets))\n } catch {\n // localStorage unavailable\n }\n}\n\n/**\n * Delete a named color preset from localStorage.\n * @param {string} name\n */\nexport function deleteColorPreset(name) {\n try {\n const presets = loadColorPresets().filter(p => p.name !== name)\n localStorage.setItem(STORAGE_KEY, JSON.stringify(presets))\n } catch {\n // noop\n }\n}\n\nexport function registerColorPresetCommands(engine) {\n engine.commands.register('saveColorPreset', {\n execute(_eng, name, colors) {\n saveColorPreset(name, colors)\n engine.eventBus.emit('colorPresets:change', { presets: loadColorPresets() })\n },\n meta: { icon: 'colorPreset', tooltip: 'Save Color Preset' },\n })\n\n engine.commands.register('loadColorPresets', {\n execute() {\n return loadColorPresets()\n },\n meta: { icon: 'colorPreset', tooltip: 'Load Color Presets' },\n })\n\n engine.commands.register('deleteColorPreset', {\n execute(_eng, name) {\n deleteColorPreset(name)\n engine.eventBus.emit('colorPresets:change', { presets: loadColorPresets() })\n },\n meta: { icon: 'colorPreset', tooltip: 'Delete Color Preset' },\n })\n}\n","import { htmlToMarkdown, markdownToHtml } from '../utils/markdownConverter.js'\n\nexport function registerMarkdownToggleCommands(engine) {\n engine.commands.register('toggleMarkdown', {\n execute(eng) {\n eng.history.snapshot()\n\n if (eng.isMarkdownMode) {\n // Markdown → HTML: get raw text, convert to rich HTML\n const markdown = eng.element.textContent\n const html = markdownToHtml(markdown)\n eng.setHTML(html)\n eng.isMarkdownMode = false\n } else {\n // HTML → Markdown: get HTML, convert to markdown, show as text\n const html = eng.getHTML()\n const markdown = htmlToMarkdown(html)\n eng.element.textContent = markdown\n eng.isMarkdownMode = true\n }\n\n eng.element.classList.toggle('rmx-markdown-mode', eng.isMarkdownMode)\n eng.eventBus.emit('mode:change:markdown', { markdownMode: eng.isMarkdownMode })\n eng.eventBus.emit('content:change')\n },\n isActive(eng) {\n return eng.isMarkdownMode\n },\n meta: { icon: 'toggleMarkdown', tooltip: 'Toggle Markdown' },\n })\n}\n","const DANGEROUS_PROTOCOL = /^\\s*(javascript|vbscript|data\\s*:\\s*text\\/html)\\s*:/i\n\n/**\n * Format bytes into a human-readable file size string.\n */\nfunction formatFileSize(bytes) {\n if (!bytes || bytes === 0) return ''\n const units = ['B', 'KB', 'MB', 'GB']\n let size = bytes\n let unitIndex = 0\n while (size >= 1024 && unitIndex < units.length - 1) {\n size /= 1024\n unitIndex++\n }\n return `${size % 1 === 0 ? size : size.toFixed(1)} ${units[unitIndex]}`\n}\n\nexport function registerAttachmentCommands(engine) {\n engine.commands.register('insertAttachment', {\n execute(eng, { url, filename = 'file', filesize }) {\n if (!url) return\n\n // Ensure editor has focus (e.g. when called from a modal)\n eng.element.focus()\n\n // If no selection range exists, place cursor at the end\n if (!eng.selection.getRange()) {\n const range = document.createRange()\n range.selectNodeContents(eng.element)\n range.collapse(false)\n eng.selection.setRange(range)\n }\n\n const a = document.createElement('a')\n a.href = DANGEROUS_PROTOCOL.test(url) ? '#' : url\n a.className = 'rmx-attachment'\n a.setAttribute('data-attachment', 'true')\n a.setAttribute('data-filename', filename)\n if (filesize) a.setAttribute('data-filesize', String(filesize))\n a.target = '_blank'\n a.rel = 'noopener noreferrer'\n\n const sizeLabel = filesize ? ` (${formatFileSize(filesize)})` : ''\n a.textContent = `\\u{1F4CE} ${filename}${sizeLabel}`\n\n eng.selection.insertNode(a)\n\n // Add a paragraph after the attachment if it's the last element\n if (!a.nextSibling || a.parentElement === eng.element) {\n const p = document.createElement('p')\n p.innerHTML = '<br>'\n a.parentElement.insertBefore(p, a.nextSibling)\n }\n },\n meta: { icon: 'attachment', tooltip: 'Insert Attachment' },\n })\n\n engine.commands.register('removeAttachment', {\n execute(eng, { element }) {\n if (!element || !element.classList?.contains('rmx-attachment')) return\n element.parentNode.removeChild(element)\n },\n meta: { tooltip: 'Remove Attachment' },\n })\n}\n","export function registerImportDocumentCommands(engine) {\n engine.commands.register('importDocument', {\n execute(eng, { html, mode = 'insert' }) {\n if (!html) return\n\n eng.element.focus()\n\n if (mode === 'replace') {\n eng.setHTML(html)\n } else {\n // Insert at cursor\n if (!eng.selection.getRange()) {\n const range = document.createRange()\n range.selectNodeContents(eng.element)\n range.collapse(false)\n eng.selection.setRange(range)\n }\n eng.selection.insertHTML(eng.sanitizer.sanitize(html))\n }\n\n eng.eventBus.emit('content:change')\n },\n meta: { icon: 'importDocument', tooltip: 'Import Document' },\n })\n}\n","/**\n * Slash Commands — inline command palette triggered by Mod+/ (Cmd+/ on Mac, Ctrl+/ on Windows/Linux)\n *\n * Provides a default catalog of slash command items, a filter function,\n * recently-used command tracking, custom command registration,\n * and a registration function that hooks into the editor's keyboard events.\n */\n\nimport { isMac } from '../utils/platform.js'\n\n/** @typedef {{ id: string, label: string, description: string, icon: string, keywords: string[], category: string, action: (engine: any, openModal?: (name: string, data?: any) => void) => void }} SlashCommandItem */\n\n// ---------------------------------------------------------------------------\n// Recently-used commands\n// ---------------------------------------------------------------------------\n\nconst RECENT_STORAGE_KEY = 'rmx-recent-commands'\nconst MAX_RECENT = 5\n\n/**\n * Get the list of recently-used command IDs (most recent first).\n * @returns {string[]}\n */\nexport function getRecentCommands() {\n try {\n const raw = localStorage.getItem(RECENT_STORAGE_KEY)\n if (!raw) return []\n const parsed = JSON.parse(raw)\n return Array.isArray(parsed) ? parsed.slice(0, MAX_RECENT) : []\n } catch {\n return []\n }\n}\n\n/**\n * Record a command execution so it appears in the recently-used list.\n * @param {string} commandId\n */\nexport function recordRecentCommand(commandId) {\n try {\n const recent = getRecentCommands().filter(id => id !== commandId)\n recent.unshift(commandId)\n localStorage.setItem(RECENT_STORAGE_KEY, JSON.stringify(recent.slice(0, MAX_RECENT)))\n } catch {\n // localStorage unavailable — silently ignore\n }\n}\n\n/**\n * Clear the recently-used command history.\n */\nexport function clearRecentCommands() {\n try {\n localStorage.removeItem(RECENT_STORAGE_KEY)\n } catch {\n // noop\n }\n}\n\n// ---------------------------------------------------------------------------\n// Custom command items registry\n// ---------------------------------------------------------------------------\n\n/** @type {SlashCommandItem[]} */\nconst _customItems = []\n\n/**\n * Register one or more custom command items that appear in the command palette.\n * Items are deduplicated by `id` — re-registering the same id replaces the previous item.\n * @param {SlashCommandItem | SlashCommandItem[]} items\n */\nexport function registerCommandItems(items) {\n const arr = Array.isArray(items) ? items : [items]\n for (const item of arr) {\n const idx = _customItems.findIndex(i => i.id === item.id)\n if (idx >= 0) {\n _customItems[idx] = item\n } else {\n _customItems.push(item)\n }\n }\n}\n\n/**\n * Unregister a custom command item by id.\n * @param {string} id\n * @returns {boolean} true if an item was removed\n */\nexport function unregisterCommandItem(id) {\n const idx = _customItems.findIndex(i => i.id === id)\n if (idx >= 0) {\n _customItems.splice(idx, 1)\n return true\n }\n return false\n}\n\n/**\n * Get all registered custom command items.\n * @returns {SlashCommandItem[]}\n */\nexport function getCustomCommandItems() {\n return [..._customItems]\n}\n\n/**\n * Default catalog of slash command items.\n * Each item defines: id, label, description, icon (emoji), keywords for search,\n * category for grouping, and an action function that receives the engine.\n * @type {SlashCommandItem[]}\n */\nexport const SLASH_COMMAND_ITEMS = [\n // Text\n { id: 'heading1', label: 'Heading 1', description: 'Large section heading', icon: 'H1', keywords: ['h1', 'title', 'heading', 'large'], category: 'Text', action: (engine) => engine.executeCommand('heading', 1) },\n { id: 'heading2', label: 'Heading 2', description: 'Medium section heading', icon: 'H2', keywords: ['h2', 'subtitle', 'heading', 'medium'], category: 'Text', action: (engine) => engine.executeCommand('heading', 2) },\n { id: 'heading3', label: 'Heading 3', description: 'Small section heading', icon: 'H3', keywords: ['h3', 'heading', 'small'], category: 'Text', action: (engine) => engine.executeCommand('heading', 3) },\n { id: 'paragraph', label: 'Paragraph', description: 'Plain text block', icon: '\\u00b6', keywords: ['text', 'normal', 'body', 'plain'], category: 'Text', action: (engine) => engine.executeCommand('heading', 'p') },\n { id: 'blockquote', label: 'Blockquote', description: 'Indented quote block', icon: '\\u201c', keywords: ['quote', 'citation', 'indent'], category: 'Text', action: (engine) => engine.executeCommand('blockquote') },\n { id: 'codeBlock', label: 'Code Block', description: 'Monospace code snippet', icon: '<>', keywords: ['code', 'pre', 'monospace', 'snippet', 'programming'], category: 'Text', action: (engine) => engine.executeCommand('codeBlock') },\n\n // Lists\n { id: 'unorderedList', label: 'Bulleted List', description: 'Unordered bullet list', icon: '\\u2022', keywords: ['bullet', 'ul', 'list', 'unordered'], category: 'Lists', action: (engine) => engine.executeCommand('unorderedList') },\n { id: 'orderedList', label: 'Numbered List', description: 'Ordered numbered list', icon: '1.', keywords: ['number', 'ol', 'list', 'ordered'], category: 'Lists', action: (engine) => engine.executeCommand('orderedList') },\n { id: 'taskList', label: 'Task List', description: 'Checklist with toggleable items', icon: '\\u2611', keywords: ['todo', 'checkbox', 'checklist', 'task'], category: 'Lists', action: (engine) => engine.executeCommand('taskList') },\n\n // Media\n { id: 'image', label: 'Image', description: 'Insert an image', icon: '\\ud83d\\uddbc', keywords: ['img', 'photo', 'picture', 'upload'], category: 'Media', action: (_engine, openModal) => openModal?.('image') },\n { id: 'attachment', label: 'Attachment', description: 'Attach a file', icon: '\\ud83d\\udcce', keywords: ['file', 'attach', 'upload', 'document'], category: 'Media', action: (_engine, openModal) => openModal?.('attachment') },\n { id: 'embedMedia', label: 'Embed Media', description: 'Embed a YouTube or Vimeo video', icon: '\\u25b6', keywords: ['video', 'youtube', 'vimeo', 'embed', 'media'], category: 'Media', action: (_engine, openModal) => openModal?.('embed') },\n\n // Layout\n { id: 'horizontalRule', label: 'Horizontal Rule', description: 'Visual divider line', icon: '\\u2015', keywords: ['hr', 'divider', 'line', 'separator', 'rule'], category: 'Layout', action: (engine) => engine.executeCommand('horizontalRule') },\n { id: 'table', label: 'Table', description: 'Insert a data table', icon: '\\u2637', keywords: ['table', 'grid', 'spreadsheet', 'cells'], category: 'Layout', action: (_engine, openModal) => openModal?.('table') },\n\n // Insert — plugin features\n { id: 'insertCallout', label: 'Callout', description: 'Insert a styled callout block', icon: '\\u2139\\ufe0f', keywords: ['callout', 'alert', 'admonition', 'info', 'warning', 'tip', 'note'], category: 'Insert', action: (engine) => engine.commands.has('insertCallout') && engine.executeCommand('insertCallout') },\n { id: 'insertMath', label: 'Math Equation', description: 'Insert a LaTeX math expression', icon: '\\u{1D453}', keywords: ['math', 'equation', 'latex', 'formula', 'katex'], category: 'Insert', action: (engine) => engine.commands.has('insertMath') && engine.executeCommand('insertMath', { latex: '', displayMode: true }) },\n { id: 'insertToc', label: 'Table of Contents', description: 'Insert an auto-generated table of contents', icon: '\\ud83d\\udcdc', keywords: ['toc', 'contents', 'outline', 'navigation', 'index'], category: 'Insert', action: (engine) => engine.commands.has('insertToc') && engine.executeCommand('insertToc') },\n { id: 'insertBookmark', label: 'Bookmark', description: 'Insert a bookmark anchor for linking', icon: '\\ud83d\\udd16', keywords: ['bookmark', 'anchor', 'link', 'target', 'reference'], category: 'Insert', action: (engine) => engine.commands.has('insertBookmark') && engine.executeCommand('insertBookmark', { name: 'bookmark' }) },\n { id: 'insertMergeTag', label: 'Merge Tag', description: 'Insert a template merge tag', icon: '\\ud83c\\udff7\\ufe0f', keywords: ['merge', 'tag', 'template', 'variable', 'placeholder', 'dynamic'], category: 'Insert', action: (engine) => engine.commands.has('insertMergeTag') && engine.executeCommand('insertMergeTag', { tag: 'name' }) },\n { id: 'addComment', label: 'Comment', description: 'Add a comment to the selected text', icon: '\\ud83d\\udcac', keywords: ['comment', 'annotate', 'annotation', 'note', 'review', 'feedback'], category: 'Insert', action: (engine) => engine.commands.has('addComment') && engine.executeCommand('addComment') },\n\n // Advanced\n { id: 'findReplace', label: 'Find & Replace', description: 'Search and replace text', icon: '\\ud83d\\udd0d', keywords: ['find', 'search', 'replace'], category: 'Advanced', action: (_engine, openModal) => openModal?.('findReplace') },\n { id: 'sourceMode', label: 'Source Code', description: 'View and edit raw HTML', icon: '</>', keywords: ['html', 'source', 'code', 'raw'], category: 'Advanced', action: (engine) => engine.executeCommand('sourceMode') },\n { id: 'export', label: 'Export', description: 'Export as PDF, DOCX, or Markdown', icon: '\\ud83d\\udce4', keywords: ['export', 'download', 'pdf', 'docx', 'save'], category: 'Advanced', action: (_engine, openModal) => openModal?.('export') },\n { id: 'importDocument', label: 'Import Document', description: 'Import from file', icon: '\\ud83d\\udce5', keywords: ['import', 'upload', 'open', 'file'], category: 'Advanced', action: (_engine, openModal) => openModal?.('importDocument') },\n { id: 'toggleAnalytics', label: 'Toggle Analytics', description: 'Show or hide content analytics panel', icon: '\\ud83d\\udcca', keywords: ['analytics', 'readability', 'stats', 'word count', 'reading time'], category: 'Advanced', action: (engine) => engine.commands.has('toggleAnalytics') && engine.executeCommand('toggleAnalytics') },\n { id: 'toggleSpellcheck', label: 'Toggle Spellcheck', description: 'Enable or disable spelling and grammar checking', icon: '\\u2713', keywords: ['spellcheck', 'spelling', 'grammar', 'proofread', 'writing'], category: 'Advanced', action: (engine) => engine.commands.has('toggleSpellcheck') && engine.executeCommand('toggleSpellcheck') },\n { id: 'checkGrammar', label: 'Check Grammar', description: 'Run grammar and style analysis on content', icon: 'Aa', keywords: ['grammar', 'check', 'proofread', 'style', 'writing', 'passive'], category: 'Advanced', action: (engine) => engine.commands.has('checkGrammar') && engine.executeCommand('checkGrammar') },\n { id: 'startCollaboration', label: 'Start Collaboration', description: 'Connect to collaboration server and start co-editing', icon: '\\ud83d\\udc65', keywords: ['collaborate', 'realtime', 'share', 'multiplayer', 'co-edit'], category: 'Advanced', action: (engine) => engine.commands.has('startCollaboration') && engine.executeCommand('startCollaboration') },\n { id: 'stopCollaboration', label: 'Stop Collaboration', description: 'Disconnect from collaboration session', icon: '\\ud83d\\udeab', keywords: ['disconnect', 'leave', 'stop', 'collaboration'], category: 'Advanced', action: (engine) => engine.commands.has('stopCollaboration') && engine.executeCommand('stopCollaboration') },\n { id: 'toggleMarkdown', label: 'Toggle Markdown', description: 'Switch between WYSIWYG and Markdown editing', icon: 'M\\u2193', keywords: ['markdown', 'toggle', 'wysiwyg', 'mode'], category: 'Advanced', action: (engine) => engine.executeCommand('toggleMarkdown') },\n { id: 'fullscreen', label: 'Fullscreen', description: 'Enter distraction-free fullscreen mode', icon: '\\u26f6', keywords: ['fullscreen', 'expand', 'zen', 'focus', 'distraction'], category: 'Advanced', action: (engine) => engine.executeCommand('fullscreen') },\n { id: 'removeFormat', label: 'Clear Formatting', description: 'Remove all formatting from selection', icon: '\\ud83d\\udeab', keywords: ['clear', 'remove', 'format', 'plain', 'reset'], category: 'Advanced', action: (engine) => engine.executeCommand('removeFormat') },\n { id: 'distractionFree', label: 'Distraction-Free', description: 'Hide toolbar and status bar, show on mouse move', icon: '\\ud83e\\uddd8', keywords: ['distraction', 'zen', 'focus', 'minimal', 'clean'], category: 'Advanced', action: (engine) => engine.executeCommand('distractionFree') },\n { id: 'toggleSplitView', label: 'Split View', description: 'Side-by-side edit and preview pane', icon: '\\u25eb', keywords: ['split', 'preview', 'side', 'dual', 'pane'], category: 'Advanced', action: (engine) => engine.executeCommand('toggleSplitView') },\n]\n\n/**\n * Filter slash command items by query string.\n * Matches against label, description, and keywords. Label matches are prioritized.\n *\n * When `options.pinRecent` is true (default) and there is no query, recently-used\n * commands are placed at the top under a \"Recent\" category.\n *\n * @param {SlashCommandItem[]} items\n * @param {string} query\n * @param {{ pinRecent?: boolean }} [options]\n * @returns {SlashCommandItem[]}\n */\nexport function filterSlashItems(items, query, options = {}) {\n const { pinRecent = true } = options\n\n if (!query) {\n if (!pinRecent) return items\n const recentIds = getRecentCommands()\n if (recentIds.length === 0) return items\n\n const recentItems = []\n for (const id of recentIds) {\n const item = items.find(i => i.id === id)\n if (item) {\n recentItems.push({ ...item, category: 'Recent' })\n }\n }\n if (recentItems.length === 0) return items\n return [...recentItems, ...items]\n }\n\n const q = query.toLowerCase()\n const labelMatches = []\n const otherMatches = []\n for (const item of items) {\n if (item.label.toLowerCase().includes(q)) {\n labelMatches.push(item)\n } else if (\n item.description.toLowerCase().includes(q) ||\n item.keywords.some(k => k.includes(q))\n ) {\n otherMatches.push(item)\n }\n }\n return [...labelMatches, ...otherMatches]\n}\n\n/**\n * Register slash command support on the engine.\n *\n * Triggered by Mod+/ (Cmd+/ on Mac, Ctrl+/ on Windows/Linux). Opens a floating\n * palette at the caret. While active, typing filters the list without inserting\n * text into the editor. The React layer handles the palette UI.\n *\n * @param {import('../core/EditorEngine.js').EditorEngine} engine\n */\nexport function registerSlashCommands(engine) {\n let slashActive = false\n let query = ''\n\n function getCaretRect() {\n const sel = window.getSelection()\n if (!sel || sel.rangeCount === 0) return null\n const range = sel.getRangeAt(0).cloneRange()\n range.collapse(true)\n const rect = range.getBoundingClientRect()\n if (rect.width === 0 && rect.height === 0) {\n // Fallback: insert a zero-width space to measure\n const span = document.createElement('span')\n span.textContent = '\\u200b'\n range.insertNode(span)\n const spanRect = span.getBoundingClientRect()\n span.parentNode.removeChild(span)\n sel.getRangeAt(0).startContainer.parentNode?.normalize()\n return spanRect\n }\n return rect\n }\n\n function open() {\n const rect = getCaretRect()\n if (!rect) return\n slashActive = true\n query = ''\n engine.eventBus.emit('slash:open', { rect, query: '' })\n }\n\n function close() {\n if (slashActive) {\n slashActive = false\n query = ''\n engine.eventBus.emit('slash:close')\n }\n }\n\n function handleKeyDown(e) {\n // Mod+/ toggles the slash palette\n const mod = isMac() ? e.metaKey : e.ctrlKey\n if (mod && e.key === '/') {\n e.preventDefault()\n e.stopPropagation()\n if (slashActive) {\n close()\n } else {\n open()\n }\n return\n }\n\n if (!slashActive) return\n\n // Navigation and selection keys\n if (e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.key === 'Enter' || e.key === 'Tab') {\n e.preventDefault()\n e.stopPropagation()\n engine.eventBus.emit('slash:keydown', { key: e.key })\n return\n }\n\n if (e.key === 'Escape') {\n e.preventDefault()\n e.stopPropagation()\n close()\n return\n }\n\n // Backspace removes last query character\n if (e.key === 'Backspace') {\n e.preventDefault()\n e.stopPropagation()\n if (query.length > 0) {\n query = query.slice(0, -1)\n engine.eventBus.emit('slash:query', { query })\n } else {\n close()\n }\n return\n }\n\n // Printable characters → filter query (don't insert into editor)\n if (e.key.length === 1 && !e.metaKey && !e.ctrlKey && !e.altKey) {\n e.preventDefault()\n e.stopPropagation()\n query += e.key\n engine.eventBus.emit('slash:query', { query })\n }\n }\n\n // Listen for execute events from the React layer\n const unsubExecute = engine.eventBus.on('slash:execute', ({ item, openModal }) => {\n slashActive = false\n query = ''\n recordRecentCommand(item.id)\n item.action(engine, openModal)\n engine.eventBus.emit('content:change')\n })\n\n engine.element.addEventListener('keydown', handleKeyDown, true)\n\n // Register cleanup on engine destroy event\n const cleanup = () => {\n engine.element?.removeEventListener('keydown', handleKeyDown, true)\n unsubExecute()\n }\n engine.eventBus.on('destroy', cleanup)\n}\n","/**\n * Autosave Storage Providers\n *\n * Pluggable adapters for persisting editor content to different backends.\n * Each provider implements: save(key, content, metadata), load(key), clear(key).\n */\n\nconst ENVELOPE_VERSION = 1\n\n/** Validates that a URL from a callback uses http or https protocol */\nfunction validateUrl(url) {\n try {\n const parsed = new URL(url)\n if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {\n throw new Error('URL must use http or https protocol')\n }\n return url\n } catch (err) {\n throw new Error(`Invalid URL from callback: ${err.message}`)\n }\n}\n\n/** Wraps content in a versioned JSON envelope with timestamp */\nfunction createEnvelope(content, metadata = {}) {\n return JSON.stringify({\n content,\n timestamp: Date.now(),\n version: ENVELOPE_VERSION,\n ...metadata,\n })\n}\n\n/** Parses a JSON envelope, returns null on failure */\nfunction parseEnvelope(json) {\n if (!json) return null\n try {\n const data = JSON.parse(json)\n if (!data || typeof data.content !== 'string') return null\n return data\n } catch {\n return null\n }\n}\n\n// ── LocalStorageProvider ─────────────────────────────────────────\n\nexport class LocalStorageProvider {\n constructor({ prefix = 'rmx-autosave' } = {}) {\n this.prefix = prefix\n }\n\n _key(key) {\n return `${this.prefix}:${key}`\n }\n\n async save(key, content, metadata) {\n try {\n localStorage.setItem(this._key(key), createEnvelope(content, metadata))\n } catch (err) {\n throw new Error(`LocalStorage save failed: ${err.message}`)\n }\n }\n\n async load(key) {\n try {\n const raw = localStorage.getItem(this._key(key))\n return parseEnvelope(raw)\n } catch {\n return null\n }\n }\n\n async clear(key) {\n try {\n localStorage.removeItem(this._key(key))\n } catch {\n // Ignore removal errors\n }\n }\n\n /**\n * Synchronous save for beforeunload — localStorage is synchronous.\n * Returns true on success, false on failure.\n */\n saveSync(key, content, metadata) {\n try {\n localStorage.setItem(this._key(key), createEnvelope(content, metadata))\n return true\n } catch {\n return false\n }\n }\n}\n\n// ── SessionStorageProvider ───────────────────────────────────────\n\nexport class SessionStorageProvider {\n constructor({ prefix = 'rmx-autosave' } = {}) {\n this.prefix = prefix\n }\n\n _key(key) {\n return `${this.prefix}:${key}`\n }\n\n async save(key, content, metadata) {\n try {\n sessionStorage.setItem(this._key(key), createEnvelope(content, metadata))\n } catch (err) {\n throw new Error(`SessionStorage save failed: ${err.message}`)\n }\n }\n\n async load(key) {\n try {\n const raw = sessionStorage.getItem(this._key(key))\n return parseEnvelope(raw)\n } catch {\n return null\n }\n }\n\n async clear(key) {\n try {\n sessionStorage.removeItem(this._key(key))\n } catch {\n // Ignore removal errors\n }\n }\n\n saveSync(key, content, metadata) {\n try {\n sessionStorage.setItem(this._key(key), createEnvelope(content, metadata))\n return true\n } catch {\n return false\n }\n }\n}\n\n// ── FileSystemProvider ──────────────────────────────────────────\n\nexport class FileSystemProvider {\n /**\n * @param {Object} opts\n * @param {(key: string, data: string) => Promise<void>} opts.writeFn\n * @param {(key: string) => Promise<string|null>} opts.readFn\n * @param {(key: string) => Promise<void>} opts.deleteFn\n */\n constructor({ writeFn, readFn, deleteFn }) {\n if (typeof writeFn !== 'function') throw new Error('FileSystemProvider requires a writeFn')\n if (typeof readFn !== 'function') throw new Error('FileSystemProvider requires a readFn')\n if (typeof deleteFn !== 'function') throw new Error('FileSystemProvider requires a deleteFn')\n this.writeFn = writeFn\n this.readFn = readFn\n this.deleteFn = deleteFn\n }\n\n async save(key, content, metadata) {\n const envelope = createEnvelope(content, metadata)\n await this.writeFn(key, envelope)\n }\n\n async load(key) {\n const raw = await this.readFn(key)\n return parseEnvelope(raw)\n }\n\n async clear(key) {\n await this.deleteFn(key)\n }\n}\n\n// ── CloudProvider ───────────────────────────────────────────────\n\nexport class CloudProvider {\n /**\n * @param {Object} opts\n * @param {string} opts.endpoint - Base URL for the cloud API\n * @param {Record<string, string>} [opts.headers] - Auth/content headers\n * @param {string} [opts.method='PUT'] - HTTP method for save\n * @param {typeof fetch} [opts.fetchFn] - Custom fetch implementation\n * @param {(key: string) => string} [opts.buildUrl] - Custom URL builder (e.g. for S3 presigned URLs)\n * @param {(key: string, content: string) => string|FormData} [opts.buildBody] - Custom body builder\n * @param {(key: string) => string} [opts.buildLoadUrl] - Custom URL for load/GET requests\n * @param {(key: string) => string} [opts.buildDeleteUrl] - Custom URL for delete requests\n *\n * **CSRF Protection:** When using cloud autosave with session-based authentication,\n * include a CSRF token in the `headers` option. For example:\n * ```js\n * new CloudProvider({\n * endpoint: 'https://api.example.com/autosave',\n * headers: {\n * 'X-CSRF-Token': getCsrfToken(),\n * 'Authorization': 'Bearer ...',\n * },\n * })\n * ```\n * For token-based auth (Bearer tokens), CSRF protection is typically not needed\n * since the token itself proves the request origin.\n */\n constructor({\n endpoint,\n headers = {},\n method = 'PUT',\n fetchFn,\n buildUrl,\n buildBody,\n buildLoadUrl,\n buildDeleteUrl,\n }) {\n if (!endpoint && !buildUrl) throw new Error('CloudProvider requires an endpoint or buildUrl')\n // Validate endpoint URL to prevent injection via user-supplied strings\n if (endpoint) {\n try {\n const parsed = new URL(endpoint)\n if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {\n throw new Error('CloudProvider endpoint must use http or https protocol')\n }\n } catch (err) {\n if (err.message.includes('protocol')) throw err\n throw new Error(`CloudProvider endpoint is not a valid URL: ${endpoint}`)\n }\n }\n this.endpoint = endpoint\n this.headers = headers\n this.method = method\n this._fetch = fetchFn || (typeof fetch !== 'undefined' ? fetch.bind(globalThis) : null)\n this.buildUrl = buildUrl\n this.buildBody = buildBody\n this.buildLoadUrl = buildLoadUrl || buildUrl\n this.buildDeleteUrl = buildDeleteUrl || buildUrl\n if (!this._fetch) throw new Error('CloudProvider requires fetch or a custom fetchFn')\n }\n\n _saveUrl(key) {\n return this.buildUrl ? validateUrl(this.buildUrl(key)) : this.endpoint\n }\n\n _loadUrl(key) {\n if (this.buildLoadUrl) return validateUrl(this.buildLoadUrl(key))\n const sep = this.endpoint.includes('?') ? '&' : '?'\n return `${this.endpoint}${sep}key=${encodeURIComponent(key)}`\n }\n\n _deleteUrl(key) {\n return this.buildDeleteUrl ? validateUrl(this.buildDeleteUrl(key)) : this._loadUrl(key)\n }\n\n async _fetchWithRetry(url, opts, retries = 1) {\n try {\n const res = await this._fetch(url, opts)\n if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`)\n return res\n } catch (err) {\n if (retries > 0) {\n await new Promise(r => setTimeout(r, 1000))\n return this._fetchWithRetry(url, opts, retries - 1)\n }\n throw err\n }\n }\n\n async save(key, content, metadata) {\n const body = this.buildBody\n ? this.buildBody(key, content)\n : createEnvelope(content, metadata)\n\n const headers = { ...this.headers }\n if (!this.buildBody && !headers['Content-Type']) {\n headers['Content-Type'] = 'application/json'\n }\n\n await this._fetchWithRetry(this._saveUrl(key), {\n method: this.method,\n headers,\n body,\n })\n }\n\n async load(key) {\n try {\n const res = await this._fetch(this._loadUrl(key), {\n method: 'GET',\n headers: this.headers,\n })\n if (!res.ok) {\n if (res.status === 404) return null\n throw new Error(`HTTP ${res.status}`)\n }\n const text = await res.text()\n return parseEnvelope(text)\n } catch {\n return null\n }\n }\n\n async clear(key) {\n try {\n await this._fetch(this._deleteUrl(key), {\n method: 'DELETE',\n headers: this.headers,\n })\n } catch {\n // Best-effort delete\n }\n }\n}\n\n// ── CustomProvider ──────────────────────────────────────────────\n\nexport class CustomProvider {\n /**\n * @param {Object} opts\n * @param {(key: string, content: string, metadata?: object) => Promise<void>} opts.save\n * @param {(key: string) => Promise<{content: string, timestamp: number}|null>} opts.load\n * @param {(key: string) => Promise<void>} opts.clear\n */\n constructor({ save, load, clear }) {\n if (typeof save !== 'function') throw new Error('CustomProvider requires a save function')\n if (typeof load !== 'function') throw new Error('CustomProvider requires a load function')\n if (typeof clear !== 'function') throw new Error('CustomProvider requires a clear function')\n this._save = save\n this._load = load\n this._clear = clear\n }\n\n async save(key, content, metadata) {\n await this._save(key, content, metadata)\n }\n\n async load(key) {\n return this._load(key)\n }\n\n async clear(key) {\n await this._clear(key)\n }\n}\n\n// ── Factory ─────────────────────────────────────────────────────\n\n/**\n * Resolves a provider config shorthand into a StorageProvider instance.\n *\n * @param {string|object|undefined} config\n * - undefined / 'localStorage' → LocalStorageProvider\n * - 'sessionStorage' → SessionStorageProvider\n * - { save, load, clear } → CustomProvider (or already a provider instance)\n * - { endpoint, ... } → CloudProvider\n * - { writeFn, readFn, deleteFn } → FileSystemProvider\n * @returns {StorageProvider}\n */\nexport function createStorageProvider(config) {\n if (!config || config === 'localStorage') {\n return new LocalStorageProvider()\n }\n if (config === 'sessionStorage') {\n return new SessionStorageProvider()\n }\n if (typeof config !== 'object') {\n throw new Error(`Unknown storage provider: ${config}`)\n }\n\n // Already a provider instance (has save/load/clear methods)\n if (typeof config.save === 'function' && typeof config.load === 'function' && typeof config.clear === 'function') {\n // Check if it's a plain config object (CustomProvider) vs an existing instance\n if (config.constructor === Object) {\n return new CustomProvider(config)\n }\n // Already instantiated provider\n return config\n }\n\n // Cloud provider (has endpoint or buildUrl)\n if (config.endpoint || config.buildUrl) {\n return new CloudProvider(config)\n }\n\n // FileSystem provider (has writeFn)\n if (config.writeFn) {\n return new FileSystemProvider(config)\n }\n\n // LocalStorageProvider with custom prefix\n if (config.prefix) {\n return new LocalStorageProvider(config)\n }\n\n throw new Error('Unable to resolve storage provider from config')\n}\n","/**\n * AutosaveManager — framework-agnostic autosave engine.\n *\n * Subscribes to `content:change` events, debounces saves, runs periodic\n * interval saves, and checks for crash-recoverable content on init.\n *\n * Emits via engine.eventBus:\n * - autosave:saving — save started\n * - autosave:saved — save succeeded, payload: { timestamp }\n * - autosave:error — save failed, payload: { error }\n * - autosave:recovered — recovery data found, payload: { recoveredContent, timestamp }\n */\n\nimport { createStorageProvider } from '../autosave/providers.js'\n\n/**\n * Module-level registry of active AutosaveManager instances, keyed by config.key.\n * Prevents duplicate managers for the same storage key.\n * @type {Map<string, AutosaveManager>}\n */\nconst _managerRegistry = new Map()\n\nexport class AutosaveManager {\n /**\n * @param {import('./EditorEngine.js').EditorEngine} engine\n * @param {Object} [options]\n * @param {string|object} [options.provider] - Storage provider config (see createStorageProvider)\n * @param {string} [options.key='rmx-default'] - Storage key for this editor instance\n * @param {number} [options.interval=30000] - Periodic save interval in ms\n * @param {number} [options.debounce=2000] - Debounce delay after content change in ms\n * @param {boolean} [options.enabled=true] - Whether autosave is active\n */\n constructor(engine, options = {}) {\n this.engine = engine\n this.provider = createStorageProvider(options.provider)\n this.key = options.key || 'rmx-default'\n\n // Deduplication: destroy existing manager for the same key\n const existing = _managerRegistry.get(this.key)\n if (existing) {\n console.warn(`[Remyx] AutosaveManager for key \"${this.key}\" already exists. Destroying old instance.`)\n existing.destroy()\n }\n _managerRegistry.set(this.key, this)\n this.interval = options.interval ?? 30000\n this.debounceMs = options.debounce ?? 2000\n this.enabled = options.enabled !== false\n\n this._debounceTimer = null\n this._intervalTimer = null\n this._isSaving = false\n this._pendingSave = false\n this._lastSavedContent = null\n this._contentChangeHandler = null\n this._beforeUnloadHandler = null\n this._destroyed = false\n this._consecutiveErrors = 0\n this._maxRetries = options.maxRetries ?? 5\n }\n\n /**\n * Start listening for content changes and begin periodic saves.\n * Subscribes to `content:change` events on the engine's EventBus,\n * starts a periodic save interval, and registers a `beforeunload`\n * handler for last-chance saves. Call this after the engine is fully initialized.\n * @returns {void}\n */\n init() {\n if (!this.enabled || this._destroyed) return\n\n // Subscribe to content changes\n this._contentChangeHandler = () => {\n this._scheduleDebouncedSave()\n }\n this._unsubContentChange = this.engine.eventBus.on('content:change', this._contentChangeHandler)\n\n // Start periodic interval saves\n if (this.interval > 0) {\n this._intervalTimer = setInterval(() => {\n this.save()\n }, this.interval)\n }\n\n // Register beforeunload for last-chance save\n if (typeof window !== 'undefined') {\n this._beforeUnloadHandler = () => {\n this._attemptSyncSave()\n }\n window.addEventListener('beforeunload', this._beforeUnloadHandler)\n }\n }\n\n /**\n * Stop all timers, remove event listeners, and perform a final save.\n * Clears the debounce timer, periodic interval, content:change subscription,\n * and beforeunload handler. A fire-and-forget save is attempted before the\n * instance is marked as destroyed.\n * @returns {void}\n */\n destroy() {\n // Clear timers\n if (this._debounceTimer) {\n clearTimeout(this._debounceTimer)\n this._debounceTimer = null\n }\n if (this._intervalTimer) {\n clearInterval(this._intervalTimer)\n this._intervalTimer = null\n }\n\n // Remove content:change listener\n if (this._unsubContentChange) {\n this._unsubContentChange()\n this._unsubContentChange = null\n }\n\n // Remove beforeunload\n if (this._beforeUnloadHandler && typeof window !== 'undefined') {\n window.removeEventListener('beforeunload', this._beforeUnloadHandler)\n this._beforeUnloadHandler = null\n }\n\n // Remove from registry\n if (_managerRegistry.get(this.key) === this) {\n _managerRegistry.delete(this.key)\n }\n\n // Final save attempt (fire-and-forget), then mark as destroyed\n this.save().catch(() => {}).finally(() => { this._destroyed = true })\n }\n\n /**\n * Save the current editor content to the storage provider.\n * Deduplicates by comparing against the last saved content to avoid\n * redundant writes. Prevents concurrent saves; if a save is requested\n * while one is in progress, it is queued and retried with exponential\n * backoff on failure.\n * @returns {Promise<void>}\n */\n async save() {\n if (!this.engine || this._destroyed) return\n\n const content = this.engine.getHTML()\n\n // Skip if content hasn't changed since last save\n if (content === this._lastSavedContent) return\n\n // Prevent concurrent saves\n if (this._isSaving) {\n this._pendingSave = true\n return\n }\n\n this._isSaving = true\n this.engine.eventBus.emit('autosave:saving')\n\n try {\n await this.provider.save(this.key, content)\n this._lastSavedContent = content\n this._consecutiveErrors = 0\n\n const timestamp = Date.now()\n this.engine.eventBus.emit('autosave:saved', { timestamp })\n } catch (error) {\n this._consecutiveErrors++\n this.engine.eventBus.emit('autosave:error', { error, retryCount: this._consecutiveErrors })\n } finally {\n this._isSaving = false\n\n // If a save was requested while we were saving, retry with exponential backoff\n if (this._pendingSave) {\n this._pendingSave = false\n if (this._consecutiveErrors >= this._maxRetries) {\n this.engine.eventBus.emit('autosave:error', {\n error: new Error(`Autosave failed after ${this._maxRetries} consecutive attempts`),\n fatal: true,\n })\n } else if (this._consecutiveErrors > 0) {\n // Exponential backoff: 1s, 2s, 4s, 8s, 16s\n const delay = Math.min(1000 * Math.pow(2, this._consecutiveErrors - 1), 30000)\n setTimeout(() => this.save(), delay)\n } else {\n this.save()\n }\n }\n }\n }\n\n /**\n * Check the storage provider for recoverable content from a previous session.\n * Compares stored content against the current editor HTML and returns\n * recovery data only when the stored version differs meaningfully.\n * @param {string} currentContent - The editor's current HTML content to compare against\n * @returns {Promise<{recoveredContent: string, timestamp: number}|null>} Recovery data if available, or null\n */\n async checkRecovery(currentContent) {\n try {\n const stored = await this.provider.load(this.key)\n if (!stored || !stored.content) return null\n\n // Normalize for comparison (trim whitespace)\n const storedNorm = stored.content.trim()\n const currentNorm = (currentContent || '').trim()\n\n // Only offer recovery if content actually differs and stored content is non-empty\n if (storedNorm && storedNorm !== currentNorm) {\n return {\n recoveredContent: stored.content,\n timestamp: stored.timestamp || Date.now(),\n }\n }\n\n return null\n } catch {\n return null\n }\n }\n\n /**\n * Clear the stored recovery content from the provider.\n * Typically called after the user dismisses a recovery banner\n * or restores the recovered content.\n * @returns {Promise<void>}\n */\n async clearRecovery() {\n try {\n await this.provider.clear(this.key)\n } catch {\n // Best-effort clear\n }\n }\n\n /**\n * Schedule a debounced save after content changes.\n * @private\n */\n _scheduleDebouncedSave() {\n if (this._debounceTimer) {\n clearTimeout(this._debounceTimer)\n }\n this._debounceTimer = setTimeout(() => {\n this._debounceTimer = null\n this.save()\n }, this.debounceMs)\n }\n\n /**\n * Attempt a synchronous save for beforeunload.\n * Only works for providers that support saveSync (localStorage/sessionStorage).\n * For async providers, uses navigator.sendBeacon as fallback.\n * @private\n */\n _attemptSyncSave() {\n if (!this.engine || this._destroyed) return\n\n const content = this.engine.getHTML()\n if (content === this._lastSavedContent) return\n\n // Try sync save (localStorage/sessionStorage providers)\n if (typeof this.provider.saveSync === 'function') {\n this.provider.saveSync(this.key, content)\n return\n }\n\n // Fallback: sendBeacon for async providers\n if (typeof navigator !== 'undefined' && navigator.sendBeacon && this.provider.endpoint) {\n try {\n const body = JSON.stringify({\n key: this.key,\n content,\n timestamp: Date.now(),\n version: 1,\n })\n navigator.sendBeacon(this.provider.endpoint, body)\n } catch {\n // Best-effort\n }\n }\n }\n}\n","/**\n * @typedef {Object} PluginSettingsSchema\n * @property {string} key - Setting key name\n * @property {string} type - Value type: 'string' | 'number' | 'boolean' | 'select'\n * @property {string} label - Human-readable label\n * @property {*} defaultValue - Default value\n * @property {string} [description] - Description for documentation/UI\n * @property {Array<{label: string, value: *}>} [options] - Options for 'select' type\n * @property {Function} [validate] - Custom validation function: (value) => boolean\n */\n\n/**\n * @typedef {Object} CreatePluginDefinition\n * @property {string} name - Unique plugin name\n * @property {string} [version] - Plugin version string (e.g., '1.0.0')\n * @property {string} [description] - Human-readable description\n * @property {string} [author] - Plugin author\n * @property {string[]} [dependencies] - Plugin names this plugin depends on (loaded first)\n * @property {Function} [init] - Called with the plugin API (or full engine if requiresFullAccess) on initialization\n * @property {Function} [destroy] - Called on cleanup with the same API as init\n * @property {Function} [onContentChange] - Called on every content:change event (debounced by the manager)\n * @property {Function} [onSelectionChange] - Called on every selectionchange event\n * @property {boolean} [requiresFullAccess=false] - If true, receives the full engine reference\n * instead of the restricted API facade. Only set this for trusted plugins that need\n * direct DOM/sanitizer/history access.\n * @property {boolean} [lazy=false] - If true, defers initialization until first command use\n * @property {Array<import('../core/CommandRegistry.js').CommandDefinition>} [commands] - Commands to register with the editor\n * @property {Array} [toolbarItems] - Toolbar item definitions for the UI layer\n * @property {Array} [statusBarItems] - Status bar item definitions for the UI layer\n * @property {Array} [contextMenuItems] - Context menu item definitions for the UI layer\n * @property {PluginSettingsSchema[]} [settingsSchema] - Schema for plugin-specific settings\n * @property {Object} [defaultSettings] - Default values for plugin settings\n */\n\n/**\n * Creates a normalized plugin definition with default values for optional properties.\n *\n * Enhanced plugin architecture supports:\n * - **Lifecycle hooks**: `onContentChange` and `onSelectionChange` in addition to `init`/`destroy`\n * - **Dependencies**: declare `dependencies: ['otherPlugin']` to ensure load ordering\n * - **Scoped settings**: `settingsSchema` + `defaultSettings` for per-plugin configuration\n * - **Metadata**: `version`, `description`, `author` for registry/marketplace\n *\n * @param {CreatePluginDefinition} definition - Plugin configuration\n * @returns {import('./PluginManager.js').PluginDefinition} A fully normalized plugin definition object\n */\nexport function createPlugin(definition) {\n return {\n name: definition.name,\n version: definition.version || '0.0.0',\n description: definition.description || '',\n author: definition.author || '',\n dependencies: definition.dependencies || [],\n requiresFullAccess: definition.requiresFullAccess || false,\n lazy: definition.lazy || false,\n init: definition.init || (() => {}),\n destroy: definition.destroy || (() => {}),\n onContentChange: definition.onContentChange || null,\n onSelectionChange: definition.onSelectionChange || null,\n commands: definition.commands || [],\n toolbarItems: definition.toolbarItems || [],\n statusBarItems: definition.statusBarItems || [],\n contextMenuItems: definition.contextMenuItems || [],\n settingsSchema: definition.settingsSchema || [],\n defaultSettings: definition.defaultSettings || {},\n }\n}\n","import { createPlugin } from '../createPlugin.js'\n\nexport function WordCountPlugin() {\n let debounceTimer = null\n let unsubContentChange = null\n\n return createPlugin({\n name: 'wordCount',\n requiresFullAccess: true, // Writes to engine._wordCount\n init(engine) {\n const update = () => {\n const text = engine.getText().trim()\n const wordCount = text ? text.split(/\\s+/).length : 0\n const charCount = engine.getText().length\n const data = { wordCount, charCount }\n engine._wordCount = data\n engine.eventBus.emit('wordcount:update', data)\n }\n\n // Task 248: Removed MutationObserver. Debounce content:change handler with 100ms timeout.\n const debouncedUpdate = () => {\n clearTimeout(debounceTimer)\n debounceTimer = setTimeout(update, 100)\n }\n\n // Listen for content:change events from the engine (debounced)\n unsubContentChange = engine.eventBus.on('content:change', debouncedUpdate)\n\n update()\n },\n\n destroy() {\n clearTimeout(debounceTimer)\n if (unsubContentChange) {\n unsubContentChange()\n unsubContentChange = null\n }\n },\n })\n}\n","import { createPlugin } from '../createPlugin.js'\n\n// Maximum length for a URL match to prevent ReDoS on extremely long strings\nconst MAX_URL_LENGTH = 2048\n\n// Combined regex that matches all three URL patterns in a single pass:\n// 1. Protocol URLs: https://example.com/path\n// 2. www. prefixed: www.example.com/path\n// 3. Bare domains: example.com/path\n// Uses alternation groups to eliminate redundant text scans\nconst COMBINED_URL_REGEX = /(?:https?:\\/\\/[^\\s<]{1,2000}[^\\s<.,:;\"')\\]!?])|(?:www\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,62}[a-zA-Z0-9])?(?:\\.[a-zA-Z]{2,20})+(?:\\/[^\\s<]{0,2000}[^\\s<.,:;\"')\\]!?])?)|(?:(?<![a-zA-Z0-9@/:.])(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,62}[a-zA-Z0-9])?\\.){1,10}[a-zA-Z]{2,20}(?:\\/[^\\s<]{0,2000}[^\\s<.,:;\"')\\]!?])?)/g\n\n// Pre-compiled regexes for checking if a match is part of a larger URL\nconst PROTOCOL_BEFORE_REGEX = /https?:\\/\\/$/\nconst PROTOCOL_OR_WWW_BEFORE_REGEX = /https?:\\/\\/[^\\s]*$/\n\n// Common TLDs to validate bare domains against (avoids false positives like \"file.txt\")\nconst COMMON_TLDS = new Set([\n 'com', 'org', 'net', 'edu', 'gov', 'mil', 'int',\n 'io', 'co', 'us', 'uk', 'ca', 'au', 'de', 'fr', 'es', 'it', 'nl', 'be', 'at', 'ch',\n 'jp', 'cn', 'kr', 'in', 'br', 'mx', 'ru', 'za', 'nz', 'se', 'no', 'fi', 'dk', 'pl',\n 'dev', 'app', 'ai', 'me', 'tv', 'cc', 'info', 'biz', 'pro', 'name', 'museum',\n 'xyz', 'online', 'site', 'tech', 'store', 'blog', 'cloud', 'design', 'agency',\n])\n\n/**\n * Extract the TLD from a matched domain string.\n * e.g. \"example.com/path\" → \"com\", \"docs.github.io\" → \"io\"\n */\nfunction extractTLD(domain) {\n // Remove any path component\n const hostPart = domain.split('/')[0]\n const parts = hostPart.split('.')\n return parts[parts.length - 1].toLowerCase()\n}\n\nexport function AutolinkPlugin() {\n // Store handler reference for cleanup\n let _handler = null\n\n return createPlugin({\n name: 'autolink',\n requiresFullAccess: true, // Needs direct DOM event listener access\n init(engine) {\n _handler = (e) => {\n if (e.key !== ' ' && e.key !== 'Enter') return\n\n const sel = window.getSelection()\n if (!sel || !sel.focusNode) return\n const textNode = sel.focusNode\n if (textNode.nodeType !== Node.TEXT_NODE) return\n\n // Check if already inside a link\n if (textNode.parentElement.closest('a')) return\n\n const text = textNode.textContent\n const match = findLastURLMatch(text)\n if (!match) return\n\n const { href, startIdx, endIdx } = match\n\n // Ensure the matched range is before the cursor position\n const cursorOffset = sel.focusOffset\n if (endIdx > cursorOffset) return\n\n // Create the link\n const range = document.createRange()\n range.setStart(textNode, startIdx)\n range.setEnd(textNode, endIdx)\n\n const link = document.createElement('a')\n link.href = href\n link.target = '_blank'\n link.rel = 'noopener noreferrer'\n range.surroundContents(link)\n\n // Move cursor after the link\n const newRange = document.createRange()\n newRange.setStartAfter(link)\n newRange.collapse(true)\n sel.removeAllRanges()\n sel.addRange(newRange)\n }\n\n engine.element.addEventListener('keydown', _handler)\n },\n destroy(engine) {\n if (_handler) {\n engine.element.removeEventListener('keydown', _handler)\n _handler = null\n }\n },\n })\n}\n\n/**\n * Find the last URL-like match in a text string.\n * Checks protocol URLs first, then www. domains, then bare domains.\n * Returns { url, href, startIdx, endIdx } or null.\n */\nfunction findLastURLMatch(text) {\n // Guard against extremely long text nodes that could cause regex performance issues\n if (!text || text.length > MAX_URL_LENGTH * 2) return null\n\n // Task 270: Limit search to the last 200 characters of the text node\n const searchStart = Math.max(0, text.length - 200)\n if (searchStart > 0) {\n text = text.slice(searchStart)\n }\n\n let best = null\n\n // Single pass using combined regex with alternation groups\n for (const m of text.matchAll(COMBINED_URL_REGEX)) {\n const url = m[0]\n const isProtocol = url.startsWith('http://') || url.startsWith('https://')\n const isWww = !isProtocol && url.startsWith('www.')\n const isBare = !isProtocol && !isWww\n\n let href = url\n let candidate = null\n\n if (isProtocol) {\n candidate = { url, href: url, startIdx: m.index, endIdx: m.index + url.length }\n } else if (isWww) {\n // Make sure this isn't part of a protocol URL already matched\n const before = text.slice(Math.max(0, m.index - 8), m.index)\n if (PROTOCOL_BEFORE_REGEX.test(before)) continue\n candidate = { url, href: 'https://' + url, startIdx: m.index, endIdx: m.index + url.length }\n } else if (isBare) {\n const tld = extractTLD(url)\n // Only auto-link if the TLD is a known one (avoids false positives)\n if (!COMMON_TLDS.has(tld)) continue\n // Make sure this isn't part of a www. or protocol URL already matched\n const before = text.slice(Math.max(0, m.index - 12), m.index)\n if (PROTOCOL_OR_WWW_BEFORE_REGEX.test(before) || /www\\.$/.test(before)) continue\n candidate = { url, href: 'https://' + url, startIdx: m.index, endIdx: m.index + url.length }\n }\n\n if (candidate && (!best || candidate.startIdx >= best.startIdx)) {\n best = candidate\n }\n }\n\n // Task 270: Adjust indices back to original text positions\n if (best && searchStart > 0) {\n best.startIdx += searchStart\n best.endIdx += searchStart\n }\n\n return best\n}\n","import { createPlugin } from '../createPlugin.js'\n\nexport function PlaceholderPlugin(placeholderText = 'Start typing...') {\n return createPlugin({\n name: 'placeholder',\n requiresFullAccess: true, // Needs direct eventBus and element access\n init(engine) {\n const update = () => {\n if (engine.isEmpty()) {\n engine.element.setAttribute('data-placeholder', placeholderText)\n engine.element.classList.add('rmx-empty')\n } else {\n engine.element.classList.remove('rmx-empty')\n }\n }\n engine.eventBus.on('content:change', update)\n engine.eventBus.on('focus', update)\n engine.eventBus.on('blur', update)\n update()\n },\n })\n}\n","/**\n * Optional Web Worker pool for offloading expensive operations.\n * Falls back to synchronous execution when workers are unavailable.\n *\n * @example\n * const pool = new WorkerPool({ maxWorkers: 4 })\n * const html = await pool.execute('sanitize', dirtyHtml)\n * pool.destroy()\n */\nexport class WorkerPool {\n /**\n * @param {Object} [options]\n * @param {number} [options.maxWorkers] - Maximum number of workers (defaults to hardwareConcurrency or 2)\n * @param {boolean} [options.enabled] - Set false to force synchronous fallback\n * @param {string} [options.workerURL] - URL to the worker script (defaults to editorWorker.js)\n */\n constructor(options = {}) {\n this._maxWorkers = options.maxWorkers || (typeof navigator !== 'undefined' ? navigator.hardwareConcurrency : 2) || 2\n this._workers = []\n this._queue = []\n this._nextId = 1\n this._pending = new Map() // id -> { resolve, reject }\n this._roundRobin = 0\n this._enabled = typeof Worker !== 'undefined' && options.enabled !== false\n this._workerURL = options.workerURL || null\n }\n\n /**\n * Execute a task in a worker (or synchronously if workers are unavailable).\n *\n * @param {string} taskType - One of: 'sanitize', 'markdown', 'convert'\n * @param {*} data - The data to send to the worker\n * @returns {Promise<*>} The result from the worker\n */\n async execute(taskType, data) {\n if (!this._enabled) {\n return this._executeFallback(taskType, data)\n }\n\n // Lazily spawn workers\n if (this._workers.length < this._maxWorkers) {\n this._spawnWorker()\n }\n\n const id = this._nextId++\n const worker = this._workers[this._roundRobin % this._workers.length]\n this._roundRobin++\n\n return new Promise((resolve, reject) => {\n this._pending.set(id, { resolve, reject })\n worker.postMessage({ id, type: taskType, data })\n })\n }\n\n /**\n * Synchronous fallback for environments without Worker support.\n *\n * @private\n * @param {string} taskType\n * @param {*} data\n * @returns {*}\n */\n _executeFallback(taskType, data) {\n switch (taskType) {\n case 'sanitize':\n // Minimal strip for fallback — main-thread sanitizer handles the rest\n return data\n .replace(/<script\\b[^<]*(?:(?!<\\/script>)<[^<]*)*<\\/script>/gi, '')\n .replace(/\\s+on\\w+\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s>]+)/gi, '')\n case 'markdown':\n // Return as-is; caller should use markdownToHtml from utils\n return data\n case 'convert':\n return { format: data.format, html: null, fallback: true }\n default:\n throw new Error(`Unknown task type: ${taskType}`)\n }\n }\n\n /**\n * Spawn a new worker and wire up the message handler.\n *\n * @private\n */\n _spawnWorker() {\n try {\n const workerURL = this._workerURL || new URL('./editorWorker.js', import.meta.url).href\n const worker = new Worker(workerURL, { type: 'module' })\n\n worker.onmessage = (e) => {\n const { id, result, error } = e.data\n const handler = this._pending.get(id)\n if (handler) {\n this._pending.delete(id)\n if (error) {\n handler.reject(new Error(error))\n } else {\n handler.resolve(result)\n }\n }\n }\n\n worker.onerror = (err) => {\n console.error('[WorkerPool] Worker error:', err)\n }\n\n this._workers.push(worker)\n } catch (err) {\n // Worker creation failed — disable pool and use fallback\n console.warn('[WorkerPool] Failed to spawn worker, falling back to sync:', err.message)\n this._enabled = false\n }\n }\n\n /**\n * Returns whether the pool is using real workers.\n * @returns {boolean}\n */\n get isEnabled() {\n return this._enabled\n }\n\n /**\n * Returns the number of currently active workers.\n * @returns {number}\n */\n get workerCount() {\n return this._workers.length\n }\n\n /**\n * Terminates all workers and rejects any pending tasks.\n */\n destroy() {\n this._workers.forEach(w => w.terminate())\n this._workers = []\n\n // Reject any pending tasks\n this._pending.forEach(({ reject }) => {\n reject(new Error('WorkerPool destroyed'))\n })\n this._pending.clear()\n this._queue = []\n }\n}\n","/**\n * Virtualized rendering for long documents.\n *\n * Uses IntersectionObserver to track which block-level elements are visible.\n * For documents exceeding a configurable threshold of block elements,\n * off-screen blocks are collapsed to lightweight placeholder divs with\n * fixed height, reducing DOM complexity and improving scroll performance.\n *\n * Content is restored when blocks scroll back into view.\n *\n * Opt-in via `engine.options.virtualizeThreshold` (default: 200, set 0 to disable).\n *\n * @example\n * const scroller = new VirtualScroller(editorElement, { threshold: 200 })\n * scroller.init()\n * // ... later\n * scroller.destroy()\n */\n\n/** Block-level tag names that are eligible for virtualization */\nconst BLOCK_TAGS = new Set([\n 'P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6',\n 'BLOCKQUOTE', 'PRE', 'UL', 'OL', 'LI', 'TABLE',\n 'FIGURE', 'SECTION', 'ARTICLE', 'ASIDE', 'HR',\n 'DL', 'DD', 'DT', 'DETAILS', 'SUMMARY',\n])\n\nexport class VirtualScroller {\n /**\n * @param {HTMLElement} container - The editor content element\n * @param {Object} [options]\n * @param {number} [options.threshold=200] - Minimum block count to activate virtualization\n * @param {string} [options.rootMargin='500px'] - IntersectionObserver root margin for pre-loading\n */\n constructor(container, options = {}) {\n this._container = container\n this._threshold = options.threshold ?? 200\n this._rootMargin = options.rootMargin || '500px'\n this._observer = null\n this._active = false\n /** @private Map<HTMLElement, { html: string, height: number }> */\n this._collapsed = new Map()\n /** @private Set<HTMLElement> placeholder elements currently in DOM */\n this._placeholders = new Set()\n }\n\n /**\n * Initialize the virtual scroller. Sets up the IntersectionObserver\n * and performs an initial scan of block elements.\n *\n * @returns {void}\n */\n init() {\n if (this._threshold === 0) return\n if (typeof IntersectionObserver === 'undefined') return\n\n this._observer = new IntersectionObserver(\n (entries) => this._handleIntersection(entries),\n {\n root: null, // viewport\n rootMargin: this._rootMargin,\n threshold: 0,\n }\n )\n\n this._scan()\n }\n\n /**\n * Scan the container for block-level elements and begin observing\n * them if the count exceeds the threshold.\n *\n * @private\n * @returns {void}\n */\n _scan() {\n const blocks = this._getBlockElements()\n\n if (blocks.length < this._threshold) {\n this._active = false\n return\n }\n\n this._active = true\n blocks.forEach((block) => {\n this._observer.observe(block)\n })\n }\n\n /**\n * Returns all direct and nested block-level children of the container.\n *\n * @private\n * @returns {HTMLElement[]}\n */\n _getBlockElements() {\n const blocks = []\n const children = this._container.children\n for (let i = 0; i < children.length; i++) {\n const child = children[i]\n if (child.nodeType === 1 && BLOCK_TAGS.has(child.tagName)) {\n blocks.push(child)\n }\n }\n return blocks\n }\n\n /**\n * Handle IntersectionObserver callbacks. Collapse blocks that leave\n * the viewport and restore blocks that enter it.\n *\n * @private\n * @param {IntersectionObserverEntry[]} entries\n */\n _handleIntersection(entries) {\n for (const entry of entries) {\n const el = entry.target\n\n if (entry.isIntersecting) {\n // Restore collapsed block\n this._restore(el)\n } else {\n // Collapse off-screen block\n this._collapse(el)\n }\n }\n }\n\n /**\n * Collapse a block element to a placeholder div with fixed height.\n * Stores the original HTML and measured height for later restoration.\n *\n * @private\n * @param {HTMLElement} el\n */\n _collapse(el) {\n // Don't collapse elements that are already placeholders\n if (this._placeholders.has(el)) return\n // Don't collapse if already tracked as collapsed\n if (this._collapsed.has(el)) return\n\n const rect = el.getBoundingClientRect()\n const height = rect.height\n if (height === 0) return // skip zero-height elements\n\n const html = el.innerHTML\n const tagName = el.tagName\n\n // Store original content\n this._collapsed.set(el, { html, height, tagName })\n\n // Replace content with empty placeholder maintaining height\n el.innerHTML = ''\n el.style.minHeight = `${height}px`\n el.setAttribute('data-virtualized', 'true')\n this._placeholders.add(el)\n }\n\n /**\n * Restore a collapsed block element to its original content.\n *\n * @private\n * @param {HTMLElement} el\n */\n _restore(el) {\n const data = this._collapsed.get(el)\n if (!data) return\n\n el.innerHTML = data.html\n el.style.minHeight = ''\n el.removeAttribute('data-virtualized')\n this._collapsed.delete(el)\n this._placeholders.delete(el)\n }\n\n /**\n * Force-restore all collapsed elements. Call before operations that\n * need to read/write the full document content (e.g., getHTML, save).\n *\n * @returns {void}\n */\n restoreAll() {\n this._collapsed.forEach((data, el) => {\n this._restore(el)\n })\n }\n\n /**\n * Rescan the container for new/removed blocks. Call after major\n * content changes (paste, import, etc.).\n *\n * @returns {void}\n */\n refresh() {\n if (!this._observer) return\n\n // Unobserve everything\n this._observer.disconnect()\n\n // Restore all collapsed blocks first\n this.restoreAll()\n\n // Re-scan\n this._scan()\n }\n\n /**\n * Whether virtualization is currently active (block count exceeds threshold).\n * @returns {boolean}\n */\n get isActive() {\n return this._active\n }\n\n /**\n * Number of currently collapsed (off-screen) blocks.\n * @returns {number}\n */\n get collapsedCount() {\n return this._collapsed.size\n }\n\n /**\n * Destroy the virtual scroller and restore all collapsed content.\n *\n * @returns {void}\n */\n destroy() {\n this.restoreAll()\n\n if (this._observer) {\n this._observer.disconnect()\n this._observer = null\n }\n\n this._collapsed.clear()\n this._placeholders.clear()\n this._active = false\n }\n}\n","import { htmlToMarkdown } from './markdownConverter.js'\nimport { Sanitizer } from '../core/Sanitizer.js'\n\n// Module-level singleton to avoid creating a new Sanitizer on every export call\nconst _sanitizer = new Sanitizer()\n\nfunction triggerDownload(blob, filename) {\n const url = URL.createObjectURL(blob)\n const a = document.createElement('a')\n a.href = url\n a.download = filename\n document.body.appendChild(a)\n a.click()\n document.body.removeChild(a)\n URL.revokeObjectURL(url)\n}\n\nexport function exportAsMarkdown(html, filename = 'document.md') {\n const md = htmlToMarkdown(html)\n const blob = new Blob([md], { type: 'text/markdown;charset=utf-8' })\n triggerDownload(blob, filename)\n}\n\nexport function exportAsPDF(html, title = 'Document') {\n const iframe = document.createElement('iframe')\n iframe.style.cssText = 'position:fixed;left:-9999px;top:-9999px;width:0;height:0;'\n // Sandbox the iframe but allow scripts (needed for window.print()) and same-origin access\n iframe.setAttribute('sandbox', 'allow-same-origin allow-modals')\n document.body.appendChild(iframe)\n\n // Re-sanitize HTML and escape title to prevent XSS in export iframe\n const safeHtml = _sanitizer.sanitize(html)\n const safeTitle = title.replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;')\n\n // Build iframe content via srcdoc (CSP-compatible, no document.write)\n const htmlContent = `<!DOCTYPE html>\n<html>\n<head>\n <title>${safeTitle}</title>\n <style>\n body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #1a1a1a; max-width: 800px; margin: 0 auto; padding: 20px; }\n h1, h2, h3, h4, h5, h6 { margin-top: 1.2em; margin-bottom: 0.4em; }\n img { max-width: 100%; height: auto; }\n table { border-collapse: collapse; width: 100%; }\n th, td { border: 1px solid #ccc; padding: 8px; text-align: left; }\n pre { background: #f5f5f5; padding: 12px; border-radius: 4px; overflow-x: auto; }\n code { font-family: 'SFMono-Regular', Consolas, monospace; font-size: 0.9em; }\n blockquote { border-left: 3px solid #ccc; margin-left: 0; padding-left: 16px; color: #555; }\n @media print { body { padding: 0; } }\n </style>\n</head>\n<body>${safeHtml}</body>\n</html>`\n iframe.srcdoc = htmlContent\n\n // Guard against double-cleanup from concurrent onafterprint and timeout\n let cleaned = false\n const cleanup = () => {\n if (cleaned) return\n cleaned = true\n clearTimeout(fallbackTimer)\n if (iframe.parentNode) document.body.removeChild(iframe)\n }\n\n iframe.contentWindow.onafterprint = cleanup\n\n // Fallback cleanup if onafterprint doesn't fire\n let fallbackTimer = setTimeout(cleanup, 60000)\n\n iframe.onload = () => {\n iframe.contentWindow.focus()\n iframe.contentWindow.print()\n // Reset fallback to a shorter window after print dialog closes\n clearTimeout(fallbackTimer)\n fallbackTimer = setTimeout(cleanup, 1000)\n }\n}\n\nexport function exportAsDocx(html, filename = 'document.doc') {\n const wordHtml = `<html xmlns:o=\"urn:schemas-microsoft-com:office:office\" xmlns:w=\"urn:schemas-microsoft-com:office:word\" xmlns=\"http://www.w3.org/TR/REC-html40\">\n<head><meta charset=\"utf-8\"><style>\nbody { font-family: Calibri, sans-serif; line-height: 1.5; color: #1a1a1a; }\nh1, h2, h3, h4, h5, h6 { margin-top: 1em; margin-bottom: 0.4em; }\ntable { border-collapse: collapse; width: 100%; }\nth, td { border: 1px solid #999; padding: 6px 10px; }\npre { background: #f5f5f5; padding: 10px; font-family: Consolas, monospace; font-size: 0.9em; }\ncode { font-family: Consolas, monospace; font-size: 0.9em; }\nblockquote { border-left: 3px solid #ccc; margin-left: 0; padding-left: 14px; color: #555; }\nimg { max-width: 100%; }\n</style></head>\n<body>${html}</body></html>`\n const blob = new Blob(['\\ufeff', wordHtml], { type: 'application/msword' })\n triggerDownload(blob, filename)\n}\n","import { DEFAULT_FONTS } from '../constants/defaults.js'\n\n/**\n * Remove fonts from a font list.\n *\n * @param {string[]} fonts - Font list to modify\n * @param {string[]} fontsToRemove - Font names to remove\n * @returns {string[]} New font list with specified fonts removed\n *\n * @example\n * removeFonts(DEFAULT_FONTS, ['Comic Sans MS', 'Impact'])\n */\nexport function removeFonts(fonts, fontsToRemove) {\n const removeSet = new Set(fontsToRemove.map(f => f.toLowerCase()))\n return fonts.filter(f => !removeSet.has(f.toLowerCase()))\n}\n\n/**\n * Add fonts to a font list.\n *\n * @param {string[]} fonts - Font list to modify\n * @param {string[]} fontsToAdd - Font names to add\n * @param {Object} [options]\n * @param {'start' | 'end'} [options.position='end'] - Where to insert the new fonts\n * @returns {string[]} New font list with fonts added (duplicates are skipped)\n *\n * @example\n * // Append fonts\n * addFonts(DEFAULT_FONTS, ['Lato', 'Poppins'])\n *\n * // Prepend fonts\n * addFonts(DEFAULT_FONTS, ['Lato', 'Poppins'], { position: 'start' })\n */\nexport function addFonts(fonts, fontsToAdd, options = {}) {\n const existing = new Set(fonts.map(f => f.toLowerCase()))\n const newFonts = fontsToAdd.filter(f => !existing.has(f.toLowerCase()))\n\n if (options.position === 'start') {\n return [...newFonts, ...fonts]\n }\n return [...fonts, ...newFonts]\n}\n\n/**\n * Load Google Fonts by injecting a stylesheet link into the document head.\n * Fonts that are already loaded will not be loaded again.\n *\n * **Privacy notice:** This function makes external requests to `fonts.googleapis.com`\n * and `fonts.gstatic.com`, which reveals the user's IP address, browser user-agent,\n * and which fonts are being loaded to Google. For privacy-sensitive deployments,\n * consider self-hosting fonts instead. See: https://google-webfonts-helper.herokuapp.com/\n *\n * **CSP note:** Requires `font-src fonts.gstatic.com` and `style-src fonts.googleapis.com`\n * in your Content-Security-Policy header.\n *\n * @param {string[]} fontFamilies - Google Font family names (e.g. ['Roboto', 'Open Sans', 'Lato:wght@400;700'])\n * @returns {HTMLLinkElement | null} The injected link element, or null if all fonts were already loaded\n *\n * @example\n * // Basic usage\n * loadGoogleFonts(['Roboto', 'Open Sans', 'Lato'])\n *\n * // With specific weights\n * loadGoogleFonts(['Roboto:wght@400;700', 'Open Sans:ital,wght@0,400;1,400'])\n */\n/**\n * @typedef {Object} LoadGoogleFontsOptions\n * @property {string} [integrity] - Subresource Integrity (SRI) hash for the stylesheet\n * (e.g. 'sha384-abc123...'). When provided, the browser verifies the fetched\n * stylesheet matches this hash before applying it. Generate with:\n * `openssl dgst -sha384 -binary <file> | openssl base64 -A`\n * @property {string} [crossOrigin='anonymous'] - CORS attribute for the link element.\n * Must be set when using SRI. Defaults to 'anonymous'.\n */\n\nexport function loadGoogleFonts(fontFamilies, options = {}) {\n if (!fontFamilies || fontFamilies.length === 0) return null\n if (typeof document === 'undefined') return null\n\n // Build the Google Fonts URL\n const families = fontFamilies.map(f => {\n // If the user already provided a spec with weights, use as-is\n if (f.includes(':')) return `family=${f.replace(/ /g, '+')}`\n // Otherwise default to requesting the font family\n return `family=${f.replace(/ /g, '+')}`\n })\n\n const url = `https://fonts.googleapis.com/css2?${families.join('&')}&display=swap`\n\n // Check if this URL is already loaded\n const existing = document.querySelector(`link[href=\"${url}\"]`)\n if (existing) return existing\n\n const link = document.createElement('link')\n link.rel = 'stylesheet'\n link.href = url\n link.dataset.remyxFonts = 'true'\n\n // Subresource Integrity (SRI) — verify stylesheet hasn't been tampered with\n if (options.integrity) {\n link.integrity = options.integrity\n link.crossOrigin = options.crossOrigin || 'anonymous'\n } else {\n // Always set crossorigin for Google Fonts (required for CORS font loading)\n link.crossOrigin = options.crossOrigin || 'anonymous'\n }\n\n document.head.appendChild(link)\n\n return link\n}\n","import { escapeHTMLAttr } from './escapeHTML.js'\n\n/**\n * Lightweight HTML formatter / prettifier.\n * Produces indented, readable HTML similar to VS Code's formatting.\n * Zero dependencies — uses the browser's DOMParser for correctness.\n */\n\n// Block-level elements that get their own line\nconst BLOCK_ELEMENTS = new Set([\n 'html', 'head', 'body',\n 'div', 'section', 'article', 'aside', 'nav', 'header', 'footer', 'main',\n 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',\n 'ul', 'ol', 'li',\n 'table', 'thead', 'tbody', 'tfoot', 'tr', 'th', 'td', 'caption', 'colgroup', 'col',\n 'blockquote', 'pre', 'figure', 'figcaption',\n 'form', 'fieldset', 'legend',\n 'details', 'summary',\n 'hr', 'br',\n 'iframe', 'video', 'audio', 'canvas',\n])\n\n// Void elements (self-closing, no end tag)\nconst VOID_ELEMENTS = new Set([\n 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',\n 'link', 'meta', 'param', 'source', 'track', 'wbr',\n])\n\n// Elements whose content should not be reformatted (preserve whitespace)\nconst PREFORMATTED = new Set(['pre', 'code', 'script', 'style', 'textarea'])\n\n// Inline elements that should stay on the same line as their parent text\nconst INLINE_ELEMENTS = new Set([\n 'a', 'abbr', 'b', 'bdi', 'bdo', 'cite', 'code', 'data', 'del', 'dfn',\n 'em', 'i', 'ins', 'kbd', 'mark', 'q', 's', 'samp', 'small', 'span',\n 'strong', 'sub', 'sup', 'time', 'u', 'var', 'wbr', 'label', 'input',\n])\n\n/**\n * Format raw HTML into a prettified, indented string.\n *\n * @param {string} html - The raw HTML string\n * @param {number} [indentSize=2] - Number of spaces per indent level\n * @returns {string} Formatted HTML\n */\nexport function formatHTML(html) {\n if (!html || !html.trim()) return ''\n\n const indent = ' ' // 2 spaces\n\n // Parse the HTML into a DOM tree\n const parser = new DOMParser()\n const doc = parser.parseFromString(`<body>${html}</body>`, 'text/html')\n const body = doc.body\n\n const lines = []\n formatNode(body, 0, lines, indent)\n\n return lines.join('\\n').trim()\n}\n\n/**\n * Recursively format a DOM node and its children.\n */\nfunction formatNode(node, level, lines, indent) {\n const children = Array.from(node.childNodes)\n\n for (const child of children) {\n if (child.nodeType === Node.COMMENT_NODE) {\n lines.push(`${indent.repeat(Math.max(0, level))}<!--${child.textContent}-->`)\n continue\n }\n\n if (child.nodeType === Node.TEXT_NODE) {\n const text = child.textContent\n // Skip pure-whitespace text nodes between block elements\n if (!text.trim()) continue\n const trimmed = text.replace(/\\s+/g, ' ').trim()\n if (trimmed) {\n lines.push(`${indent.repeat(Math.max(0, level))}${trimmed}`)\n }\n continue\n }\n\n if (child.nodeType !== Node.ELEMENT_NODE) continue\n\n const tag = child.tagName.toLowerCase()\n\n // Preformatted elements — output as-is, no reformatting\n if (PREFORMATTED.has(tag)) {\n lines.push(`${indent.repeat(Math.max(0, level))}${child.outerHTML}`)\n continue\n }\n\n const isVoid = VOID_ELEMENTS.has(tag)\n const currentIndent = indent.repeat(Math.max(0, level))\n\n // Build the opening tag with attributes\n const openTag = buildOpenTag(child, tag)\n\n if (isVoid) {\n // Self-closing element\n lines.push(`${currentIndent}${openTag}`)\n continue\n }\n\n // Check if this element only contains inline content (text + inline elements)\n const hasOnlyInlineContent = isInlineContent(child)\n\n if (hasOnlyInlineContent) {\n // Render the entire element on one line\n const innerContent = serializeInlineContent(child)\n lines.push(`${currentIndent}${openTag}${innerContent}</${tag}>`)\n } else {\n // Block element with block children — indent children\n lines.push(`${currentIndent}${openTag}`)\n formatNode(child, level + 1, lines, indent)\n lines.push(`${currentIndent}</${tag}>`)\n }\n }\n}\n\n/**\n * Build an opening tag string from a DOM element, including attributes.\n */\nfunction buildOpenTag(el, tag) {\n const attrs = Array.from(el.attributes)\n if (attrs.length === 0) return `<${tag}>`\n\n const attrStr = attrs.map(a => {\n if (a.value === '') return a.name\n return `${a.name}=\"${escapeAttr(a.value)}\"`\n }).join(' ')\n\n return `<${tag} ${attrStr}>`\n}\n\n/**\n * Check if an element only contains inline content\n * (text nodes and inline elements, no block-level children).\n */\nfunction isInlineContent(el) {\n for (const child of el.childNodes) {\n if (child.nodeType === Node.TEXT_NODE) continue\n if (child.nodeType === Node.ELEMENT_NODE) {\n const tag = child.tagName.toLowerCase()\n if (BLOCK_ELEMENTS.has(tag)) return false\n // Recursively check inline children\n if (!isInlineContent(child)) return false\n }\n }\n return true\n}\n\n/**\n * Serialize inline content (text + inline tags) into a single string,\n * preserving inner tags but collapsing whitespace.\n */\nfunction serializeInlineContent(el) {\n let result = ''\n for (const child of el.childNodes) {\n if (child.nodeType === Node.TEXT_NODE) {\n result += child.textContent.replace(/\\s+/g, ' ')\n } else if (child.nodeType === Node.ELEMENT_NODE) {\n const tag = child.tagName.toLowerCase()\n const openTag = buildOpenTag(child, tag)\n if (VOID_ELEMENTS.has(tag)) {\n result += openTag\n } else {\n result += `${openTag}${serializeInlineContent(child)}</${tag}>`\n }\n }\n }\n return result\n}\n\n/**\n * Escape HTML attribute values.\n */\n// Task 261: Uses shared escapeHTMLAttr utility\nfunction escapeAttr(val) {\n return escapeHTMLAttr(val)\n}\n","/**\n * RTL (right-to-left) language support utilities.\n *\n * Provides automatic text direction detection and helpers for\n * bidirectional content within the editor.\n */\n\n// Unicode ranges for RTL scripts\nconst RTL_REGEX = /[\\u0591-\\u07FF\\u200F\\u202B\\u202E\\uFB1D-\\uFDFD\\uFE70-\\uFEFC]/\n\n// Unicode ranges for LTR scripts (Latin, CJK, etc.)\nconst LTR_REGEX = /[A-Za-z\\u00C0-\\u00FF\\u0100-\\u024F\\u0400-\\u04FF\\u1100-\\u11FF\\u3040-\\u309F\\u30A0-\\u30FF\\u3400-\\u4DBF\\u4E00-\\u9FFF]/\n\n/**\n * Detect the dominant text direction of a string.\n *\n * Counts RTL vs LTR character occurrences and returns the direction\n * of whichever has more characters. Returns 'auto' if the string\n * contains no directional characters (e.g., numbers only).\n *\n * @param {string} text - The text to analyze\n * @returns {'ltr' | 'rtl' | 'auto'} The detected text direction\n */\nexport function detectTextDirection(text) {\n if (!text || typeof text !== 'string') return 'auto'\n\n let rtlCount = 0\n let ltrCount = 0\n\n for (let i = 0; i < text.length; i++) {\n const char = text[i]\n if (RTL_REGEX.test(char)) rtlCount++\n else if (LTR_REGEX.test(char)) ltrCount++\n }\n\n if (rtlCount === 0 && ltrCount === 0) return 'auto'\n return rtlCount > ltrCount ? 'rtl' : 'ltr'\n}\n\n/**\n * Apply the `dir` attribute to a block element based on its text content.\n *\n * This function is designed to be called on each block-level element\n * (paragraph, heading, list item, etc.) to set the appropriate\n * text direction.\n *\n * @param {HTMLElement} element - The block element to update\n */\nexport function applyAutoDirection(element) {\n if (!element || !element.textContent) return\n const dir = detectTextDirection(element.textContent)\n if (dir === 'auto') {\n element.removeAttribute('dir')\n } else {\n element.setAttribute('dir', dir)\n }\n}\n\n/**\n * Apply auto-direction to all block-level elements in a container.\n *\n * @param {HTMLElement} container - The container element (typically the editor root)\n */\nexport function applyAutoDirectionAll(container) {\n if (!container) return\n const blocks = container.querySelectorAll('p, h1, h2, h3, h4, h5, h6, li, blockquote, td, th')\n for (const block of blocks) {\n applyAutoDirection(block)\n }\n}\n\n/**\n * RTL-aware CSS logical property mappings.\n * Use these constants to reference CSS logical properties in JavaScript\n * for consistent layout in both LTR and RTL contexts.\n */\nexport const LOGICAL_PROPERTIES = {\n marginStart: 'margin-inline-start',\n marginEnd: 'margin-inline-end',\n paddingStart: 'padding-inline-start',\n paddingEnd: 'padding-inline-end',\n borderStart: 'border-inline-start',\n borderEnd: 'border-inline-end',\n insetStart: 'inset-inline-start',\n insetEnd: 'inset-inline-end',\n}\n","/**\n * English (default) locale pack for Remyx Editor.\n *\n * This file defines all translatable strings in the editor.\n * Keys are flat dot-separated paths for simple lookup.\n */\nexport const en = {\n // ── Toolbar tooltips ──\n 'toolbar.bold': 'Bold',\n 'toolbar.italic': 'Italic',\n 'toolbar.underline': 'Underline',\n 'toolbar.strikethrough': 'Strikethrough',\n 'toolbar.subscript': 'Subscript',\n 'toolbar.superscript': 'Superscript',\n 'toolbar.removeFormat': 'Remove Formatting',\n 'toolbar.heading': 'Heading',\n 'toolbar.paragraph': 'Normal text',\n 'toolbar.orderedList': 'Numbered List',\n 'toolbar.unorderedList': 'Bulleted List',\n 'toolbar.taskList': 'Task List',\n 'toolbar.indent': 'Increase Indent',\n 'toolbar.outdent': 'Decrease Indent',\n 'toolbar.alignLeft': 'Align Left',\n 'toolbar.alignCenter': 'Align Center',\n 'toolbar.alignRight': 'Align Right',\n 'toolbar.alignJustify': 'Justify',\n 'toolbar.blockquote': 'Blockquote',\n 'toolbar.codeBlock': 'Code Block',\n 'toolbar.horizontalRule': 'Horizontal Rule',\n 'toolbar.link': 'Insert Link',\n 'toolbar.image': 'Insert Image',\n 'toolbar.table': 'Insert Table',\n 'toolbar.fontFamily': 'Font Family',\n 'toolbar.fontSize': 'Font Size',\n 'toolbar.foreColor': 'Text Color',\n 'toolbar.backColor': 'Background Color',\n 'toolbar.undo': 'Undo',\n 'toolbar.redo': 'Redo',\n 'toolbar.source': 'Source Code',\n 'toolbar.markdown': 'Markdown',\n 'toolbar.fullscreen': 'Fullscreen',\n 'toolbar.commandPalette': 'Command Palette',\n 'toolbar.print': 'Print',\n 'toolbar.findReplace': 'Find & Replace',\n 'toolbar.lineNumbers': 'Toggle Line Numbers',\n 'toolbar.copyCode': 'Copy Code',\n\n // ── Menu bar ──\n 'menu.file': 'File',\n 'menu.edit': 'Edit',\n 'menu.view': 'View',\n 'menu.insert': 'Insert',\n 'menu.format': 'Format',\n\n // ── Context menu ──\n 'context.cut': 'Cut',\n 'context.copy': 'Copy',\n 'context.paste': 'Paste',\n 'context.selectAll': 'Select All',\n 'context.toggleHeaderRow': 'Toggle Header Row',\n 'context.formatCell': 'Format Cell',\n 'context.clearFilters': 'Clear Filters',\n\n // ── Modals ──\n 'modal.link.title': 'Insert Link',\n 'modal.link.url': 'URL',\n 'modal.link.text': 'Link Text',\n 'modal.link.newTab': 'Open in new tab',\n 'modal.link.insert': 'Insert',\n 'modal.link.cancel': 'Cancel',\n 'modal.image.title': 'Insert Image',\n 'modal.image.url': 'Image URL',\n 'modal.image.alt': 'Alt Text',\n 'modal.image.insert': 'Insert',\n 'modal.image.cancel': 'Cancel',\n 'modal.source.title': 'Edit Source',\n 'modal.source.apply': 'Apply',\n 'modal.source.cancel': 'Cancel',\n 'modal.table.title': 'Insert Table',\n 'modal.table.rows': 'Rows',\n 'modal.table.cols': 'Columns',\n 'modal.table.insert': 'Insert',\n 'modal.table.cancel': 'Cancel',\n\n // ── Find & Replace ──\n 'find.placeholder': 'Find...',\n 'find.replace': 'Replace...',\n 'find.replaceAll': 'Replace All',\n 'find.matchCase': 'Match Case',\n 'find.noResults': 'No results',\n 'find.results': '{{current}} of {{total}}',\n\n // ── Command Palette ──\n 'palette.placeholder': 'Type a command...',\n 'palette.noResults': 'No matching commands',\n\n // ── Autosave ──\n 'autosave.saved': 'Saved',\n 'autosave.saving': 'Saving...',\n 'autosave.unsaved': 'Unsaved changes',\n 'autosave.error': 'Save failed',\n 'autosave.recovery': 'Unsaved content was recovered.',\n 'autosave.restore': 'Restore',\n 'autosave.dismiss': 'Dismiss',\n\n // ── Accessibility ──\n 'a11y.skipToEditor': 'Skip to editor',\n 'a11y.toolbar': 'Editor toolbar',\n 'a11y.menuBar': 'Editor menu bar',\n 'a11y.editor': 'Rich text editor',\n\n // ── Headings (for command palette / dropdown) ──\n 'heading.h1': 'Heading 1',\n 'heading.h2': 'Heading 2',\n 'heading.h3': 'Heading 3',\n 'heading.h4': 'Heading 4',\n 'heading.h5': 'Heading 5',\n 'heading.h6': 'Heading 6',\n 'heading.paragraph': 'Normal text',\n\n // ── Cell formatting ──\n 'format.number': 'Number',\n 'format.currency': 'Currency',\n 'format.percentage': 'Percentage',\n 'format.date': 'Date',\n\n // ── Status bar ──\n 'status.words': '{{count}} words',\n 'status.chars': '{{count}} characters',\n}\n","/**\n * Internationalization (i18n) system for the Remyx Editor.\n *\n * Provides a simple, lightweight locale system with:\n * - Default English locale included\n * - Runtime locale switching via setLocale()\n * - String lookup via t() with fallback to key\n * - Interpolation support: t('greeting', { name: 'World' }) → 'Hello, World!'\n * - Custom locale registration via registerLocale()\n */\n\nimport { en } from './locales/en.js'\n\n/** Currently active locale strings */\nlet _currentLocale = { ...en }\n\n/** Registered locale packs keyed by language code */\nconst _locales = { en }\n\n/** Current language code */\nlet _currentLang = 'en'\n\n/**\n * Translate a key using the current locale.\n *\n * Supports interpolation with `{{variable}}` placeholders:\n * ```js\n * t('save.status', { status: 'saved' }) // \"Document saved\"\n * ```\n *\n * Returns the key itself if no translation is found (safe fallback).\n *\n * @param {string} key - Dot-separated translation key (e.g., 'toolbar.bold')\n * @param {Object} [vars] - Interpolation variables\n * @returns {string} The translated string, or the key if not found\n */\nexport function t(key, vars) {\n let str = _currentLocale[key]\n if (str === undefined) return key\n\n if (vars) {\n for (const [k, v] of Object.entries(vars)) {\n str = str.replace(new RegExp(`\\\\{\\\\{${k}\\\\}\\\\}`, 'g'), String(v))\n }\n }\n\n return str\n}\n\n/**\n * Set the active locale by language code.\n *\n * @param {string} lang - Language code (e.g., 'en', 'ar', 'ja')\n * @throws {Error} If the locale has not been registered\n */\nexport function setLocale(lang) {\n const locale = _locales[lang]\n if (!locale) {\n throw new Error(`Locale \"${lang}\" is not registered. Use registerLocale() first.`)\n }\n _currentLang = lang\n // Merge with English fallback so missing keys fall through\n _currentLocale = { ...en, ...locale }\n}\n\n/**\n * Get the current language code.\n *\n * @returns {string} The active language code\n */\nexport function getLocale() {\n return _currentLang\n}\n\n/**\n * Register a locale pack.\n *\n * A locale pack is a flat object mapping translation keys to strings.\n * Partial packs are supported — missing keys fall back to English.\n *\n * @param {string} lang - Language code (e.g., 'fr', 'de', 'ar')\n * @param {Object} strings - Translation key-value pairs\n *\n * @example\n * registerLocale('fr', {\n * 'toolbar.bold': 'Gras',\n * 'toolbar.italic': 'Italique',\n * 'toolbar.underline': 'Souligné',\n * })\n */\nexport function registerLocale(lang, strings) {\n if (!lang || typeof lang !== 'string') throw new Error('registerLocale: lang is required')\n if (!strings || typeof strings !== 'object') throw new Error('registerLocale: strings object is required')\n _locales[lang] = strings\n}\n\n/**\n * Unregister a locale pack. Cannot unregister 'en' (default).\n *\n * @param {string} lang - Language code to remove\n */\nexport function unregisterLocale(lang) {\n if (lang === 'en') return // Cannot remove default\n delete _locales[lang]\n if (_currentLang === lang) {\n setLocale('en')\n }\n}\n\n/**\n * Get all registered locale codes.\n *\n * @returns {string[]} Array of registered language codes\n */\nexport function getRegisteredLocales() {\n return Object.keys(_locales)\n}\n","/**\n * Performance utilities for the Remyx Editor.\n *\n * Provides DOM mutation batching, idle-time processing, and\n * performance measurement helpers for large-document optimization.\n */\n\n/**\n * Batch multiple DOM mutations into a single requestAnimationFrame callback\n * to prevent layout thrash from rapid sequential changes.\n *\n * @param {Function[]} mutations - Array of functions that perform DOM mutations\n * @returns {Promise<void>} Resolves after all mutations are applied\n *\n * @example\n * batchDOMMutations([\n * () => el.style.width = '100px',\n * () => el.style.height = '200px',\n * () => el.classList.add('active'),\n * ])\n */\nexport function batchDOMMutations(mutations) {\n return new Promise(resolve => {\n requestAnimationFrame(() => {\n for (const fn of mutations) {\n fn()\n }\n resolve()\n })\n })\n}\n\n/**\n * Schedule a non-critical task to run during browser idle time.\n * Falls back to setTimeout(fn, 16) if requestIdleCallback is not available.\n *\n * @param {Function} fn - The task to run during idle time\n * @param {Object} [options]\n * @param {number} [options.timeout=2000] - Maximum wait time in ms before forcing execution\n * @returns {number} The idle callback ID (can be passed to cancelIdleCallback)\n *\n * @example\n * scheduleIdleTask(() => {\n * // Non-critical work: word count, readability, analytics\n * updateWordCount(engine)\n * })\n */\nexport function scheduleIdleTask(fn, options = {}) {\n const timeout = options.timeout || 2000\n if (typeof requestIdleCallback === 'function') {\n return requestIdleCallback(fn, { timeout })\n }\n // Fallback for Safari (no requestIdleCallback)\n return setTimeout(fn, 16)\n}\n\n/**\n * Cancel a previously scheduled idle task.\n *\n * @param {number} id - The ID returned by scheduleIdleTask\n */\nexport function cancelIdleTask(id) {\n if (typeof cancelIdleCallback === 'function') {\n cancelIdleCallback(id)\n } else {\n clearTimeout(id)\n }\n}\n\n/**\n * Create a throttled version of a function that fires at most once per\n * animation frame. Useful for scroll and resize handlers.\n *\n * @param {Function} fn - The function to throttle\n * @returns {Function} The throttled function\n */\nexport function rafThrottle(fn) {\n let frameId = null\n return function throttled(...args) {\n if (frameId !== null) return\n frameId = requestAnimationFrame(() => {\n fn.apply(this, args)\n frameId = null\n })\n }\n}\n\n/**\n * Measure the execution time of a function in milliseconds.\n * Uses performance.now() for high-resolution timing.\n *\n * @param {string} label - A label for the measurement (logged to console)\n * @param {Function} fn - The function to measure\n * @returns {*} The return value of the measured function\n *\n * @example\n * const html = measurePerformance('getHTML', () => engine.getHTML())\n */\nexport function measurePerformance(label, fn) {\n const start = performance.now()\n const result = fn()\n const duration = performance.now() - start\n if (typeof console !== 'undefined' && console.debug) {\n console.debug(`[Remyx perf] ${label}: ${duration.toFixed(2)}ms`)\n }\n return result\n}\n\n/**\n * Benchmark a function by running it multiple times and reporting stats.\n *\n * @param {string} label - Label for the benchmark\n * @param {Function} fn - The function to benchmark\n * @param {number} [iterations=100] - Number of iterations\n * @returns {{ label: string, mean: number, median: number, min: number, max: number, p95: number }}\n */\nexport function benchmark(label, fn, iterations = 100) {\n const times = []\n for (let i = 0; i < iterations; i++) {\n const start = performance.now()\n fn()\n times.push(performance.now() - start)\n }\n\n times.sort((a, b) => a - b)\n const sum = times.reduce((a, b) => a + b, 0)\n const mean = sum / times.length\n const median = times[Math.floor(times.length / 2)]\n const min = times[0]\n const max = times[times.length - 1]\n const p95 = times[Math.floor(times.length * 0.95)]\n\n return { label, mean, median, min, max, p95 }\n}\n\n/**\n * Creates an input batcher that coalesces rapid DOM updates.\n * Collects mutations during a frame and applies them in a single rAF.\n *\n * @param {Function} applyFn - Function that receives an array of batched mutations\n * @param {Object} [options]\n * @param {number} [options.flushMs=16] - Flush interval (one frame by default)\n * @returns {{ queue: Function, flush: Function, destroy: Function }}\n *\n * @example\n * const batcher = createInputBatcher((mutations) => {\n * mutations.forEach(m => applyMutation(m))\n * })\n * batcher.queue({ type: 'insert', text: 'a' })\n * batcher.queue({ type: 'insert', text: 'b' })\n * // Both applied in a single rAF\n */\nexport function createInputBatcher(applyFn, options = {}) {\n const flushMs = options.flushMs || 16 // one frame\n let pending = []\n let timer = null\n\n return {\n /**\n * Queue a mutation to be applied in the next batch.\n * @param {*} mutation - The mutation data to queue\n */\n queue(mutation) {\n pending.push(mutation)\n if (!timer) {\n timer = (typeof requestAnimationFrame === 'function')\n ? requestAnimationFrame(() => {\n const batch = pending\n pending = []\n timer = null\n applyFn(batch)\n })\n : setTimeout(() => {\n const batch = pending\n pending = []\n timer = null\n applyFn(batch)\n }, flushMs)\n }\n },\n\n /**\n * Immediately flush any pending mutations without waiting for rAF.\n */\n flush() {\n if (timer) {\n if (typeof cancelAnimationFrame === 'function') {\n cancelAnimationFrame(timer)\n } else {\n clearTimeout(timer)\n }\n timer = null\n }\n if (pending.length) {\n const batch = pending\n pending = []\n applyFn(batch)\n }\n },\n\n /**\n * Destroy the batcher, cancelling any pending flush.\n */\n destroy() {\n if (timer) {\n if (typeof cancelAnimationFrame === 'function') {\n cancelAnimationFrame(timer)\n } else {\n clearTimeout(timer)\n }\n }\n pending = []\n timer = null\n }\n }\n}\n","/**\n * All available theme variables with descriptions.\n * Keys are the camelCase names used with createTheme().\n * Values are objects with the CSS variable name and a description.\n */\nexport const THEME_VARIABLES = {\n // Color Palette\n bg: { var: '--rmx-bg', description: 'Editor background color' },\n text: { var: '--rmx-text', description: 'Primary text color' },\n textSecondary: { var: '--rmx-text-secondary', description: 'Secondary/muted text color' },\n border: { var: '--rmx-border', description: 'Border color' },\n borderSubtle: { var: '--rmx-border-subtle', description: 'Subtle/light border color' },\n\n // Toolbar\n toolbarBg: { var: '--rmx-toolbar-bg', description: 'Toolbar background color' },\n toolbarBorder: { var: '--rmx-toolbar-border', description: 'Toolbar border color' },\n toolbarButtonHover: { var: '--rmx-toolbar-button-hover', description: 'Toolbar button hover background' },\n toolbarButtonActive: { var: '--rmx-toolbar-button-active', description: 'Toolbar button active background' },\n toolbarIcon: { var: '--rmx-toolbar-icon', description: 'Toolbar icon color' },\n toolbarIconActive: { var: '--rmx-toolbar-icon-active', description: 'Toolbar icon active color' },\n\n // Accent / Primary\n primary: { var: '--rmx-primary', description: 'Primary accent color' },\n primaryHover: { var: '--rmx-primary-hover', description: 'Primary color on hover' },\n primaryLight: { var: '--rmx-primary-light', description: 'Light variant of primary (backgrounds)' },\n focusRing: { var: '--rmx-focus-ring', description: 'Focus ring outline color' },\n selection: { var: '--rmx-selection', description: 'Text selection highlight color' },\n\n // Feedback\n danger: { var: '--rmx-danger', description: 'Error/danger color' },\n dangerLight: { var: '--rmx-danger-light', description: 'Light variant of danger' },\n\n // UI Elements\n placeholder: { var: '--rmx-placeholder', description: 'Placeholder text color' },\n modalBg: { var: '--rmx-modal-bg', description: 'Modal background color' },\n modalOverlay: { var: '--rmx-modal-overlay', description: 'Modal overlay background' },\n statusbarBg: { var: '--rmx-statusbar-bg', description: 'Status bar background' },\n statusbarText: { var: '--rmx-statusbar-text', description: 'Status bar text color' },\n hoverBg: { var: '--rmx-hover-bg', description: 'Generic hover background' },\n\n // Shadows\n shadowSm: { var: '--rmx-shadow-sm', description: 'Small shadow' },\n shadowMd: { var: '--rmx-shadow-md', description: 'Medium shadow' },\n shadowLg: { var: '--rmx-shadow-lg', description: 'Large shadow' },\n shadowFloat: { var: '--rmx-shadow-float', description: 'Floating element shadow' },\n\n // Typography\n fontFamily: { var: '--rmx-font-family', description: 'UI font stack' },\n fontSize: { var: '--rmx-font-size', description: 'UI font size' },\n contentFontSize: { var: '--rmx-content-font-size', description: 'Content area font size' },\n contentLineHeight: { var: '--rmx-content-line-height', description: 'Content area line height' },\n\n // Spacing & Shape\n radius: { var: '--rmx-radius', description: 'Standard border radius' },\n radiusSm: { var: '--rmx-radius-sm', description: 'Small border radius' },\n radiusInner: { var: '--rmx-radius-inner', description: 'Inner element border radius' },\n spacingXs: { var: '--rmx-spacing-xs', description: 'Extra small spacing (4px)' },\n spacingSm: { var: '--rmx-spacing-sm', description: 'Small spacing (8px)' },\n spacingMd: { var: '--rmx-spacing-md', description: 'Medium spacing (12px)' },\n\n // Transitions\n transitionFast: { var: '--rmx-transition-fast', description: 'Fast transition duration' },\n transitionNormal: { var: '--rmx-transition-normal', description: 'Normal transition duration' },\n}\n\n// Build a quick lookup: camelCase key → CSS variable name\nconst KEY_TO_VAR = {}\nfor (const [key, meta] of Object.entries(THEME_VARIABLES)) {\n KEY_TO_VAR[key] = meta.var\n}\n\n/**\n * Create a theme object from friendly camelCase overrides.\n * The returned object can be passed directly to the `customTheme` prop.\n *\n * Accepts either camelCase keys (e.g. `bg`, `primary`, `toolbarBg`) or\n * raw CSS variable names (e.g. `--rmx-bg`). Both formats can be mixed.\n *\n * @param {Object} overrides - Theme variable overrides\n * @returns {Object} CSS custom property object for use as `customTheme`\n *\n * @example\n * const myTheme = createTheme({\n * bg: '#1a1a2e',\n * primary: '#e94560',\n * toolbarBg: '#16213e',\n * radius: '12px',\n * })\n *\n * <RemyxEditor customTheme={myTheme} />\n */\nexport function createTheme(overrides) {\n const result = {}\n for (const [key, value] of Object.entries(overrides)) {\n if (key.startsWith('--rmx-')) {\n // Raw CSS variable — pass through\n result[key] = value\n } else if (KEY_TO_VAR[key]) {\n // camelCase key → CSS variable\n result[KEY_TO_VAR[key]] = value\n } else {\n // Unknown key — try as a CSS variable anyway\n result[`--rmx-${key.replace(/([A-Z])/g, '-$1').toLowerCase()}`] = value\n }\n }\n return result\n}\n","import { createTheme } from './themeConfig.js'\n\n/**\n * Built-in theme presets.\n *\n * **Recommended:** Use the `theme` prop directly instead of `customTheme`:\n * ```jsx\n * <RemyxEditor theme=\"ocean\" />\n * <RemyxEditor theme=\"forest\" />\n * ```\n *\n * These presets are also available as CSS custom property objects for use with\n * `customTheme` when you need to override individual variables on top of a theme:\n * ```jsx\n * <RemyxEditor theme=\"ocean\" customTheme={createTheme({ primary: '#ff0000' })} />\n * ```\n */\nexport const THEME_PRESETS = {\n /** Deep blue ocean palette */\n ocean: createTheme({\n bg: '#0f172a',\n text: '#e2e8f0',\n textSecondary: '#94a3b8',\n border: '#1e3a5f',\n borderSubtle: '#1e3a5f',\n toolbarBg: '#0c1426',\n toolbarBorder: '#1e3a5f',\n toolbarButtonHover: '#1e3a5f',\n toolbarButtonActive: '#1e4976',\n toolbarIcon: '#94a3b8',\n toolbarIconActive: '#38bdf8',\n primary: '#0ea5e9',\n primaryHover: '#0284c7',\n primaryLight: 'rgba(14, 165, 233, 0.15)',\n focusRing: '#0ea5e9',\n selection: '#0c4a6e',\n placeholder: '#475569',\n modalBg: '#0c1426',\n modalOverlay: 'rgba(0, 0, 0, 0.6)',\n statusbarBg: '#0c1426',\n statusbarText: '#64748b',\n }),\n\n /** Green earth-tone forest palette */\n forest: createTheme({\n bg: '#14201a',\n text: '#d1e7dd',\n textSecondary: '#8fbc8f',\n border: '#2d4a3e',\n borderSubtle: '#2d4a3e',\n toolbarBg: '#0f1a14',\n toolbarBorder: '#2d4a3e',\n toolbarButtonHover: '#2d4a3e',\n toolbarButtonActive: '#1a5c3a',\n toolbarIcon: '#8fbc8f',\n toolbarIconActive: '#4ade80',\n primary: '#22c55e',\n primaryHover: '#16a34a',\n primaryLight: 'rgba(34, 197, 94, 0.15)',\n focusRing: '#22c55e',\n selection: '#14532d',\n placeholder: '#4a7c5f',\n modalBg: '#0f1a14',\n modalOverlay: 'rgba(0, 0, 0, 0.6)',\n statusbarBg: '#0f1a14',\n statusbarText: '#4a7c5f',\n }),\n\n /** Warm orange/amber sunset palette */\n sunset: createTheme({\n bg: '#1c1210',\n text: '#fde8d0',\n textSecondary: '#d4a574',\n border: '#5c3a28',\n borderSubtle: '#5c3a28',\n toolbarBg: '#181010',\n toolbarBorder: '#5c3a28',\n toolbarButtonHover: '#5c3a28',\n toolbarButtonActive: '#7c4a2a',\n toolbarIcon: '#d4a574',\n toolbarIconActive: '#fb923c',\n primary: '#f97316',\n primaryHover: '#ea580c',\n primaryLight: 'rgba(249, 115, 22, 0.15)',\n focusRing: '#f97316',\n selection: '#7c2d12',\n placeholder: '#8b6f47',\n modalBg: '#181010',\n modalOverlay: 'rgba(0, 0, 0, 0.6)',\n statusbarBg: '#181010',\n statusbarText: '#8b6f47',\n }),\n\n /** Soft pink/rose palette */\n rose: createTheme({\n bg: '#1c1018',\n text: '#fde2ee',\n textSecondary: '#d4809f',\n border: '#5c2848',\n borderSubtle: '#5c2848',\n toolbarBg: '#18101a',\n toolbarBorder: '#5c2848',\n toolbarButtonHover: '#5c2848',\n toolbarButtonActive: '#7c2858',\n toolbarIcon: '#d4809f',\n toolbarIconActive: '#fb7185',\n primary: '#f43f5e',\n primaryHover: '#e11d48',\n primaryLight: 'rgba(244, 63, 94, 0.15)',\n focusRing: '#f43f5e',\n selection: '#881337',\n placeholder: '#8b4766',\n modalBg: '#18101a',\n modalOverlay: 'rgba(0, 0, 0, 0.6)',\n statusbarBg: '#18101a',\n statusbarText: '#8b4766',\n }),\n}\n","// ---------------------------------------------------------------------------\n// Per-item toolbar theming\n// ---------------------------------------------------------------------------\n\n/**\n * Maps friendly camelCase keys to CSS custom property names for per-item\n * toolbar button styling. These variables are scoped to individual toolbar\n * elements via inline styles, so they only affect the element they're set on.\n */\nexport const TOOLBAR_ITEM_STYLE_KEYS = {\n color: { var: '--rmx-tb-color', description: 'Icon / text color' },\n background: { var: '--rmx-tb-bg', description: 'Default background' },\n hoverColor: { var: '--rmx-tb-hover-color', description: 'Color on hover' },\n hoverBackground: { var: '--rmx-tb-hover-bg', description: 'Background on hover' },\n activeColor: { var: '--rmx-tb-active-color', description: 'Color when active / pressed' },\n activeBackground: { var: '--rmx-tb-active-bg', description: 'Background when active' },\n border: { var: '--rmx-tb-border', description: 'Border shorthand' },\n borderRadius: { var: '--rmx-tb-radius', description: 'Border radius' },\n size: { var: '--rmx-tb-size', description: 'Button width & height' },\n iconSize: { var: '--rmx-tb-icon-size', description: 'Icon size inside button' },\n padding: { var: '--rmx-tb-padding', description: 'Button padding' },\n opacity: { var: '--rmx-tb-opacity', description: 'Button opacity' },\n}\n\nconst ITEM_KEY_TO_VAR = {}\nfor (const [key, meta] of Object.entries(TOOLBAR_ITEM_STYLE_KEYS)) {\n ITEM_KEY_TO_VAR[key] = meta.var\n}\n\nconst SEPARATOR_KEY_TO_VAR = {\n color: '--rmx-tb-sep-color',\n width: '--rmx-tb-sep-width',\n height: '--rmx-tb-sep-height',\n margin: '--rmx-tb-sep-margin',\n}\n\n/**\n * Convert a per-item overrides object (friendly keys or raw CSS vars) into\n * an inline style object of CSS custom properties.\n *\n * @param {Object} overrides - e.g. { color: '#e11d48', borderRadius: '50%' }\n * @returns {Object|null} Style object or null if empty\n */\nexport function resolveToolbarItemStyle(overrides) {\n if (!overrides || typeof overrides !== 'object') return null\n const style = {}\n let hasKeys = false\n for (const [key, value] of Object.entries(overrides)) {\n if (key.startsWith('--rmx-')) {\n style[key] = value\n hasKeys = true\n } else if (ITEM_KEY_TO_VAR[key]) {\n style[ITEM_KEY_TO_VAR[key]] = value\n hasKeys = true\n }\n }\n return hasKeys ? style : null\n}\n\n/**\n * Convert separator overrides into an inline style object.\n *\n * @param {Object} overrides - e.g. { color: '#ccc', width: '2px' }\n * @returns {Object|null}\n */\nexport function resolveSeparatorStyle(overrides) {\n if (!overrides || typeof overrides !== 'object') return null\n const style = {}\n let hasKeys = false\n for (const [key, value] of Object.entries(overrides)) {\n if (key.startsWith('--rmx-')) {\n style[key] = value\n hasKeys = true\n } else if (SEPARATOR_KEY_TO_VAR[key]) {\n style[SEPARATOR_KEY_TO_VAR[key]] = value\n hasKeys = true\n }\n }\n return hasKeys ? style : null\n}\n\n/**\n * Create a toolbar item theme config. The returned object can be passed\n * directly to the `toolbarItemTheme` prop on `<RemyxEditor>`.\n *\n * Keys are toolbar command names (e.g. `bold`, `italic`, `headings`) or the\n * special `_separator` key. Values are objects of per-item style overrides.\n *\n * @param {Object} config - Map of command → style overrides\n * @returns {Object} Resolved config ready for the prop\n *\n * @example\n * const itemTheme = createToolbarItemTheme({\n * bold: { color: '#e11d48', activeColor: '#be123c', borderRadius: '50%' },\n * italic: { background: '#f0f9ff', hoverBackground: '#e0f2fe' },\n * _separator: { color: '#e2e8f0', width: '2px' },\n * })\n *\n * <RemyxEditor toolbarItemTheme={itemTheme} />\n */\nexport function createToolbarItemTheme(config) {\n if (!config || typeof config !== 'object') return {}\n const resolved = {}\n for (const [command, overrides] of Object.entries(config)) {\n if (command === '_separator') {\n resolved._separator = resolveSeparatorStyle(overrides)\n } else {\n const style = resolveToolbarItemStyle(overrides)\n if (style) resolved[command] = style\n }\n }\n return resolved\n}\n","/**\n * Define a RemyxEditor configuration.\n *\n * Top-level keys act as defaults for all editors.\n * The `editors` key contains named configurations that can be\n * attached to specific <RemyxEditor config=\"name\" /> instances.\n *\n * @param {object} config\n * @returns {object} The validated configuration object\n */\nexport function defineConfig(config) {\n if (!config || typeof config !== 'object') {\n throw new Error('defineConfig expects a configuration object')\n }\n\n if (config.editors && typeof config.editors !== 'object') {\n throw new Error('defineConfig: \"editors\" must be an object mapping names to editor configurations')\n }\n\n return config\n}\n","/**\n * Load a RemyxEditor configuration from a URL (JSON or YAML).\n *\n * Supports:\n * - JSON files (.json or any URL returning application/json)\n * - YAML files (.yml, .yaml) — parsed via a lightweight inline parser\n * - Environment-based config merging via `env` key\n * - Fetching from local file paths or remote URLs\n *\n * @param {string} url - URL or path to a JSON/YAML configuration file\n * @param {object} [options]\n * @param {string} [options.env] - Environment name for config merging (e.g., 'production', 'development')\n * @param {Record<string, string>} [options.headers] - Custom fetch headers (e.g., Authorization)\n * @param {AbortSignal} [options.signal] - AbortController signal for cancellation\n * @returns {Promise<object>} The resolved configuration object\n */\nexport async function loadConfig(url, options = {}) {\n const { env, headers, signal } = options\n\n const response = await fetch(url, {\n headers: { Accept: 'application/json, text/yaml, text/plain', ...headers },\n signal,\n })\n\n if (!response.ok) {\n throw new Error(`loadConfig: Failed to fetch \"${url}\" (HTTP ${response.status})`)\n }\n\n const text = await response.text()\n let config\n\n // Detect format: YAML if extension is .yml/.yaml, or if content doesn't start with { or [\n const isYaml = /\\.ya?ml(\\?.*)?$/i.test(url)\n if (isYaml) {\n config = parseSimpleYaml(text)\n } else {\n try {\n config = JSON.parse(text)\n } catch {\n // Fallback: try YAML parse if JSON fails\n config = parseSimpleYaml(text)\n }\n }\n\n if (!config || typeof config !== 'object') {\n throw new Error('loadConfig: Configuration must be a JSON/YAML object')\n }\n\n // Environment-based merging\n if (env && config.env && typeof config.env === 'object') {\n const envOverrides = config.env[env]\n if (envOverrides && typeof envOverrides === 'object') {\n const { env: _envKey, ...baseConfig } = config\n config = deepMerge(baseConfig, envOverrides)\n } else {\n const { env: _envKey, ...baseConfig } = config\n config = baseConfig\n }\n } else if (config.env) {\n // Strip the env key if no env option was provided\n const { env: _envKey, ...baseConfig } = config\n config = baseConfig\n }\n\n return config\n}\n\n/**\n * Deep merge two objects. Arrays are replaced, not merged.\n * @param {object} target\n * @param {object} source\n * @returns {object}\n */\nfunction deepMerge(target, source) {\n const result = { ...target }\n for (const key of Object.keys(source)) {\n if (\n source[key] &&\n typeof source[key] === 'object' &&\n !Array.isArray(source[key]) &&\n target[key] &&\n typeof target[key] === 'object' &&\n !Array.isArray(target[key])\n ) {\n result[key] = deepMerge(target[key], source[key])\n } else {\n result[key] = source[key]\n }\n }\n return result\n}\n\n/**\n * Lightweight YAML parser for simple key-value and nested object configs.\n * Handles: strings, numbers, booleans, null, arrays (inline [...] and block - items),\n * nested objects via indentation, and quoted strings.\n *\n * This is NOT a full YAML parser — it covers the subset used in editor configs.\n * For complex YAML, use the `js-yaml` library and pass the parsed result to defineConfig().\n *\n * @param {string} text\n * @returns {object}\n */\nfunction parseSimpleYaml(text) {\n const lines = text.split('\\n')\n const root = {}\n const stack = [{ obj: root, indent: -1 }]\n\n for (let i = 0; i < lines.length; i++) {\n const raw = lines[i]\n // Skip empty lines and comments\n if (/^\\s*(#.*)?$/.test(raw)) continue\n\n const match = raw.match(/^(\\s*)(.+)$/)\n if (!match) continue\n\n const indent = match[1].length\n const content = match[2]\n\n // Pop stack to find parent at correct indent level\n while (stack.length > 1 && stack[stack.length - 1].indent >= indent) {\n stack.pop()\n }\n const parent = stack[stack.length - 1].obj\n\n // Array item (block style)\n const arrayMatch = content.match(/^-\\s+(.*)$/)\n if (arrayMatch) {\n // Find the key that this array belongs to — it's the parent\n const val = parseYamlValue(arrayMatch[1])\n if (Array.isArray(parent)) {\n parent.push(val)\n }\n continue\n }\n\n // Key: value pair\n const kvMatch = content.match(/^([^:]+?):\\s*(.*)$/)\n if (kvMatch) {\n const key = kvMatch[1].trim()\n const rawValue = kvMatch[2].trim()\n\n if (rawValue === '' || rawValue === '|' || rawValue === '>') {\n // Could be a nested object or a block array — check next line\n const nextLine = lines[i + 1]\n if (nextLine && /^\\s+-\\s/.test(nextLine)) {\n // Block array\n const arr = []\n parent[key] = arr\n stack.push({ obj: arr, indent })\n } else {\n // Nested object\n const nested = {}\n parent[key] = nested\n stack.push({ obj: nested, indent })\n }\n } else {\n parent[key] = parseYamlValue(rawValue)\n }\n }\n }\n\n return root\n}\n\n/**\n * Parse a simple YAML scalar value.\n */\nfunction parseYamlValue(str) {\n if (str === 'true') return true\n if (str === 'false') return false\n if (str === 'null' || str === '~') return null\n\n // Inline array: [a, b, c]\n if (str.startsWith('[') && str.endsWith(']')) {\n return str.slice(1, -1).split(',').map(s => parseYamlValue(s.trim()))\n }\n\n // Quoted string\n if ((str.startsWith('\"') && str.endsWith('\"')) || (str.startsWith(\"'\") && str.endsWith(\"'\"))) {\n return str.slice(1, -1)\n }\n\n // Number\n const num = Number(str)\n if (!isNaN(num) && str !== '') return num\n\n return str\n}\n","/**\n * Built-in plugin registry for resolving plugin names from config files.\n *\n * Item 21: Uses lazy Map with dynamic import() on first use instead of\n * eagerly importing all 17 plugins at module load time.\n *\n * When plugins are specified as strings in a JSON/YAML config (e.g., `\"plugins\": [\"TablePlugin\", \"MathPlugin\"]`),\n * this resolver maps those names to the actual plugin factory functions.\n *\n * Users can also register custom plugins so they can be referenced by name in config files.\n */\n\n/**\n * Lazy-loaded built-in plugin factories. Each value is an async function\n * that dynamically imports the plugin module and returns the factory result.\n * @type {Map<string, (opts?: object) => Promise<object>>}\n */\nconst BUILTIN_PLUGINS = new Map([\n ['WordCountPlugin', async () => {\n const { WordCountPlugin } = await import('../plugins/builtins/WordCountPlugin.js')\n return WordCountPlugin\n }],\n ['AutolinkPlugin', async () => {\n const { AutolinkPlugin } = await import('../plugins/builtins/AutolinkPlugin.js')\n return AutolinkPlugin\n }],\n ['PlaceholderPlugin', async (opts) => {\n const { PlaceholderPlugin } = await import('../plugins/builtins/PlaceholderPlugin.js')\n return PlaceholderPlugin(opts?.placeholder)\n }],\n])\n\n/**\n * Custom plugin registry for user-defined plugins that can be referenced by name.\n * @type {Map<string, Function>}\n */\nconst _customPluginFactories = new Map()\n\n/**\n * Register a custom plugin factory so it can be referenced by name in config files.\n *\n * @param {string} name - The plugin name (used in config files)\n * @param {Function} factory - A factory function that returns a plugin instance.\n * Called with `(options)` from the config. If no options, called with `undefined`.\n *\n * @example\n * ```js\n * import { registerPluginFactory } from '@remyxjs/core';\n *\n * registerPluginFactory('MyCustomPlugin', (opts) => MyCustomPlugin(opts));\n *\n * // Now usable in config files:\n * // { \"plugins\": [{ \"name\": \"MyCustomPlugin\", \"options\": { \"color\": \"red\" } }] }\n * ```\n */\nexport function registerPluginFactory(name, factory) {\n if (typeof name !== 'string' || !name) {\n throw new Error('registerPluginFactory: name must be a non-empty string')\n }\n if (typeof factory !== 'function') {\n throw new Error('registerPluginFactory: factory must be a function')\n }\n _customPluginFactories.set(name, factory)\n}\n\n/**\n * Unregister a previously registered custom plugin factory.\n * @param {string} name - The plugin name to remove\n */\nexport function unregisterPluginFactory(name) {\n _customPluginFactories.delete(name)\n}\n\n/**\n * Resolve a plugins config value into an array of plugin instances.\n *\n * Accepts:\n * - An array of strings: `[\"TablePlugin\", \"MathPlugin\"]`\n * - An array of objects: `[{ \"name\": \"TablePlugin\" }, { \"name\": \"CollaborationPlugin\", \"options\": { \"roomId\": \"doc-1\" } }]`\n * - A mixed array of strings, objects, and already-instantiated plugins\n * - `undefined` or `null` -> returns `undefined` (no plugins override)\n *\n * @param {Array|undefined|null} pluginsConfig - The plugins value from a config file\n * @returns {Promise<Array>|undefined} An array of resolved plugin instances, or undefined\n */\nexport function resolvePlugins(pluginsConfig) {\n if (!pluginsConfig || !Array.isArray(pluginsConfig)) {\n return undefined\n }\n\n return Promise.all(pluginsConfig.map((entry, index) => {\n // Already a plugin instance (function/object with init) -- pass through\n if (typeof entry === 'function' || (typeof entry === 'object' && entry !== null && typeof entry.init === 'function')) {\n return entry\n }\n\n // String name: \"TablePlugin\"\n if (typeof entry === 'string') {\n return resolveByName(entry)\n }\n\n // Object with name and optional options: { name: \"TablePlugin\", options: { ... } }\n if (typeof entry === 'object' && entry !== null && typeof entry.name === 'string') {\n return resolveByName(entry.name, entry.options)\n }\n\n throw new Error(`resolvePlugins: invalid plugin entry at index ${index} -- expected string, { name, options }, or plugin instance`)\n }))\n}\n\n/**\n * Look up a plugin by name and instantiate it with optional options.\n * @param {string} name\n * @param {object} [options]\n * @returns {Promise<*>} The plugin instance\n */\nasync function resolveByName(name, options) {\n // Check custom registry first (allows overriding built-ins)\n const customFactory = _customPluginFactories.get(name)\n if (customFactory) {\n return customFactory(options)\n }\n\n const builtinFactory = BUILTIN_PLUGINS.get(name)\n if (builtinFactory) {\n return builtinFactory(options)\n }\n\n throw new Error(`resolvePlugins: unknown plugin \"${name}\". Register it with registerPluginFactory() or import it directly.`)\n}\n"],"names":["EventBus","constructor","this","_listeners","Map","_keyedHandlers","_emitDepth","on","event","handler","options","has","set","Set","key","compositeKey","existing","get","off","add","handlers","delete","size","once","wrapper","args","emit","data","forEach","err","error","removeAllListeners","clear","BLOCK_LEVEL_TAGS","walkUpToBlock","node","boundary","el","nodeType","Node","TEXT_NODE","parentElement","tagName","closestBlock","root","closestTag","tag","toUpperCase","wrapInTag","range","attrs","document","createElement","Object","entries","value","setAttribute","surroundContents","fragment","extractContents","appendChild","insertNode","unwrapTag","element","parent","parentNode","firstChild","insertBefore","removeChild","generateId","Math","random","toString","substr","isBlockEmpty","block","textContent","trim","querySelector","HEADING_REGEX","FORMAT_TAG_MAP","bold","italic","underline","strikethrough","subscript","superscript","Selection","editorElement","editor","_cachedRange","_cacheGeneration","invalidateCache","getSelection","window","getRange","sel","rangeCount","getRangeAt","isWithinEditor","commonAncestorContainer","gen","Promise","resolve","then","setRange","removeAllRanges","addRange","contains","isCollapsed","getSelectedText","getSelectedHTML","cloneContents","div","innerHTML","save","preRange","createRange","selectNodeContents","setEnd","startContainer","startOffset","length","endOffset","collapsed","restore","bookmark","textWalker","createTreeWalker","NodeFilter","SHOW_TEXT","charCount","startNode","startNodeOffset","endNode","endNodeOffset","nextNode","currentNode","nextCount","setStart","min","collapse","fallbackRange","toEnd","collapseToEnd","collapseToStart","getParentElement","container","getParentBlock","getClosestElement","insertHTML","html","safeHtml","_sanitizer","sanitize","deleteContents","template","content","lastChild","newRange","setStartAfter","setSanitizer","sanitizer","wrapWith","unwrap","getActiveFormats","formats","heading","alignment","orderedList","unorderedList","blockquote","codeBlock","link","fontFamily","fontSize","foreColor","backColor","anchorNode","ELEMENT_NODE","format","toLowerCase","test","align","style","textAlign","getComputedStyle","href","text","target","queryCommandValue","getBoundingRect","getBoundingClientRect","CommandRegistry","engine","_commands","register","name","command","execute","isActive","isEnabled","shortcut","meta","skipSnapshot","keyboard","alternateShortcuts","alt","history","snapshot","result","eventBus","getAll","Array","from","keys","djb2Hash","str","hash","i","len","charCodeAt","applyDiff","baseStr","diff","prefixLen","suffixLen","insert","slice","History","maxSize","debounceMs","coalesceMs","_undoStack","_redoStack","_observer","_debounceTimer","_coalesceTimer","_isPerformingUndoRedo","_isCoalescing","_lastSnapshot","_lastHash","init","_takeSnapshot","MutationObserver","_debouncedSnapshot","observe","childList","characterData","subtree","destroy","disconnect","clearTimeout","setTimeout","_updateTopSnapshot","_createEntry","type","patch","oldStr","newStr","minLen","maxSuffix","computeDiff","_resolveEntry","stack","index","entry","baseIndex","selection","push","shift","_disconnectObserver","_reconnectObserver","undo","canUndo","currentHtml","currentBookmark","stateIndex","stateHtml","stateBookmark","pop","redo","canRedo","_isMac","isMac","navigator","platform","getModKey","KeyboardManager","_shortcuts","_normalizeCache","_handleKeyDown","bind","addEventListener","removeEventListener","commandName","normalized","_normalizeShortcut","unregister","getShortcutForCommand","getShortcutLabel","split","map","p","join","sort","e","parts","metaKey","ctrlKey","shiftKey","altKey","includes","preventDefault","stopPropagation","commands","ALLOWED_TAGS","h1","h2","h3","h4","h5","h6","strong","b","em","u","s","del","sub","sup","a","img","ul","ol","li","table","thead","tbody","tr","th","td","pre","code","hr","br","span","input","label","mark","iframe","ALLOWED_STYLES","JS_PROTOCOL_REGEX","CSS_INJECTION_REGEX","DANGEROUS_REMOVE_TAGS","DEFAULT_IFRAME_ALLOWED_DOMAINS","isAllowedIframeDomain","src","allowedDomains","url","URL","protocol","hostname","some","domain","d","endsWith","Sanitizer","allowedTags","allowedStyles","iframeAllowedDomains","_cache","_parser","DOMParser","cacheKey","cached","output","doc","parseFromString","_cleanNode","body","firstKey","next","childNodes","child","COMMENT_NODE","allowedAttrs","attributes","attr","startsWith","removeAttribute","_cleanStyles","hasAttribute","getAttribute","cleanedStyles","prop","getPropertyValue","BULLET_PATTERN","NUMBER_PATTERN","LETTER_PATTERN","INDENT_PATTERN","LIST_PREFIX_PATTERN","cleanPastedHTML","cleaned","replace","isWord","isGoogleDocs","isGoogleSheets","isLibreOffice","isApplePages","paragraphs","querySelectorAll","inList","listType","listEl","hasIndent","isBullet","isNumber","convertWordListParagraphs","MD_HEADING","MD_UNORDERED","MD_ORDERED","MD_TASK","MD_BLOCKQUOTE","MD_HR","MD_CODE_FENCE","MD_BOLD","MD_BOLD_ALT","MD_ITALIC","MD_LINK","MD_IMAGE","MD_TABLE","MD_TABLE_SEP","MD_INLINE_CODE","looksLikeMarkdown","lines","markdownSignals","totalNonEmptyLines","line","trimmed","escapeHTML","escapeHTMLAttr","getExtension","filename","dot","lastIndexOf","readAsText","file","reject","reader","FileReader","onload","onerror","Error","readAsArrayBuffer","escapeHtml","FORMAT_MAP","EXTENSION_MAP","detectFormat","ext","isImportableFile","getSupportedExtensions","getSupportedFormatNames","async","convertDocument","converters","docx","import","pdf","markdown","csv","rtf","mod","raw","default","DEFAULT_TOOLBAR","DEFAULT_FONTS","DEFAULT_FONT_SIZES","DEFAULT_COLORS","DEFAULT_MENU_BAR","items","HEADING_OPTIONS","exceedsMaxFileSize","maxBytes","context","toFixed","turndownInstance","markedConfigured","SAFE_PROTOCOLS","isSafeUrl","decoded","decodeURIComponent","htmlToMarkdown","TurndownService","headingStyle","bulletListMarker","codeBlockStyle","fence","emDelimiter","strongDelimiter","linkStyle","use","gfm","addRule","filter","replacement","nodeName","classList","checked","turndown","markdownToHtml","md","marked","setOptions","breaks","renderer","Renderer","title","tokens","Parser","parseInline","image","lang","langAttr","ensureMarkedConfigured","parse","insertPlainText","outputFormat","parsedHtml","formatted","para","Clipboard","_handlePaste","_handleCopy","_handleCut","clipboardData","getData","files","imageFile","find","f","_handleImagePaste","importableFiles","validFiles","_exceedsMaxFileSize","chain","sanitized","catch","nonImageFiles","uploadHandler","filesize","caretCell","_getCaretCell","closest","_looksLikeTSV","_pasteIntoTable","tsvFromHtml","_htmlTableToTSV","_handleTextPaste","anchor","focus","focusNode","anchorTable","focusTable","rows","tsvLines","htmlRows","row","cells","tsvCells","htmlCells","cell","tsv","setData","maxFileSize","c","startCell","allRows","startRow","startRowIdx","indexOf","startColIdx","prev","previousElementSibling","colSpan","r","rowIdx","firstRow","colCount","newRow","colIdx","otherRow","idx","otherTd","onprogress","lengthComputable","loaded","total","percent","round","readAsDataURL","REMYX_MIME","DRAGGABLE_BLOCKS","REORDERABLE_CHILDREN","_editorRegistry","WeakMap","_activeInstances","DragDrop","_dragSource","_dragSourceEditor","_dropTarget","_dropPosition","_ghostEl","_dropIndicator","_isExternalDrag","_enterCount","_blockRects","_scrollHandler","_handleDragOver","_handleDrop","_handleDragEnter","_handleDragLeave","_handleDragStart","_handleDragEnd","_cleanupDrag","getDraggableBlock","_closestDraggableBlock","makeBlockDraggable","unmakeBlockDraggable","isDragging","getDragState","isExternalDrag","dropTarget","dropPosition","_getReorderableItem","outerHTML","dataTransfer","effectAllowed","_createGhostPreview","_cacheBlockRects","instance","remove","dropEffect","_updateDropTarget","isExternal","types","_removeDropIndicator","_handleBlockDrop","imageFiles","_handleImageDrop","_handleDocumentDrop","_handleFileDrop","remyxContent","_handleInterEditorDrop","_setCursorAtDropPoint","_handleTextDrop","source","position","sourceEditor","sourceReorderable","targetReorderable","nextSibling","insertionParent","blocks","_getTopLevelBlocks","passive","_getBlockRect","children","mouseY","clientY","closestPosition","closestDistance","Infinity","rect","midY","top","height","distTop","abs","distBottom","bottom","_showDropIndicator","targetBlock","className","editorEl","editorRect","blockRect","scrollTop","ghost","cloneNode","left","width","offsetWidth","pointerEvents","offsetX","clientX","offsetY","setDragImage","_removeGhostPreview","ev","caretRangeFromPoint","createPluginAPI","pluginName","manager","executeCommand","getHTML","getText","isEmpty","getSetting","getPluginSetting","setSetting","setPluginSetting","_getRegistry","_getActiveInstances","_globalRegistry","registerPluginInRegistry","unregisterPluginFromRegistry","listRegisteredPlugins","values","searchPluginRegistry","query","q","description","tags","t","PluginManager","_plugins","_initialized","_commandPluginMap","_settings","_lifecycleUnsubs","settings","plugin","defaultSettings","settingsSchema","schema","o","validate","getPluginSettings","k","v","_resolveDependencyOrder","plugins","nameSet","graph","inDegree","deps","dependencies","queue","degree","sorted","other","circular","remaining","pluginMap","cmd","lazy","originalExecute","self","wrappedCmd","activatePlugin","apply","_initPlugin","api","requiresFullAccess","unsubs","onContentChange","hook","onSelectionChange","dep","initAll","nonLazy","cycle","destroyAll","unsub","isInitialized","_selectionHandlers","_selectionListenerAttached","_sharedSelectionChangeHandler","activeElement","_handleSelectionChange","EditorEngine","isSourceMode","isMarkdownMode","_isDestroyed","_htmlDirty","_lastHTML","_textCache","_textCacheDirty","clipboard","dragDrop","_handleInput","_handleFocus","_handleBlur","_handleClick","phase","setHTML","blur","getWordCount","getCharCount","EditorBus","_bus","_registry","id","getEditor","getEditorIds","editorCount","broadcast","exclude","reset","TOOLBAR_PRESETS","full","standard","minimal","bare","rich","removeToolbarItems","config","itemsToRemove","removeSet","group","item","addToolbarItems","itemsArray","isArray","after","before","insertAt","splice","gi","createToolbar","CATEGORY_ORDER","itemSet","category","BUTTON_COMMANDS","TOOLTIP_MAP","alignLeft","alignCenter","alignRight","alignJustify","taskList","horizontalRule","removeFormat","indent","outdent","attachment","importDocument","embedMedia","findReplace","fullscreen","sourceMode","toggleMarkdown","export","distractionFree","toggleSplitView","lineHeight","letterSpacing","paragraphSpacing","saveColorPreset","typography","headings","commandPalette","insertCallout","insertMath","insertToc","insertBookmark","insertMergeTag","addComment","scanBrokenLinks","getAnalytics","toggleAnalytics","toggleSpellcheck","checkGrammar","startCollaboration","stopCollaboration","SHORTCUT_MAP","insertLink","MODAL_COMMANDS","getCommandActiveState","selectionState","DEFAULT_KEYBINDINGS","selectAll","SharedResources","_sanitizerSchema","_toolbarPresets","_defaults","_keybindings","_icons","_ensureInit","freeze","fromEntries","groups","g","toolbar","menuBar","fonts","fontSizes","colors","headingOptions","buttons","tooltips","shortcuts","modals","sanitizerSchema","toolbarPresets","defaults","keybindings","registerIcon","svgOrUrl","unregisterIcon","getIcon","getIconNames","stats","registeredIcons","frozenSchemas","toggleInlineTag","docFrag","createDocumentFragment","replaceChild","ancestor","isInsideTag","registerFormattingCommands","eng","icon","tooltip","color","colorMap","yellow","green","blue","pink","orange","purple","bg","backgroundColor","inlineTags","walker","SHOW_ELEMENT","toUnwrap","intersectsNode","getEffectiveLevel","logicalLevel","baseHeadingLevel","offset","convertBlock","newTag","newEl","registerHeadingCommands","baseLevel","level","Number","effectiveLevel","registerAlignmentCommands","registerListCommands","existingOl","unwrapList","existingUl","convertListType","wrapInList","cb","checkbox","prevLi","maxDepth","maxListNestingDepth","depth","listTag","subList","parentList","grandParentLi","list","newList","DANGEROUS_PROTOCOL","validateUrl","registerLinkCommands","selectedText","rel","displayText","escapeAttr","linkEl","registerImageCommands","location","pathname","maxWidth","float","margin","display","registerTableCommands","cols","headerRow","rowSpan","newThead","direction","createEmptyRow","getColumnCount","colIndex","getCellIndex","refCell","firstCell","rowIndex","fragments","columnIndex","dataType","comparator","sortKeys","getCell","customComparator","tableSortComparator","valA","valB","detectSortDataType","parseFloat","Date","getTime","localeCompare","sensitivity","ths","String","filterValue","filters","val","rawValue","num","isNaN","date","Intl","DateTimeFormat","dateStyle","decimals","NumberFormat","currency","minimumFractionDigits","maximumFractionDigits","evaluateTableFormulas","colspan","rowspan","cellTag","newCell","targetColIndex","currentRow","nextElementSibling","rowCellTag","insertRef","colAccum","count","numericCount","dateCount","isFinite","FORMULA_FUNCTIONS","SUM","flat","reduce","AVERAGE","COUNT","MIN","MAX","max","IF","CONCAT","parseCellRef","ref","match","col","parseInt","getTableCellValue","evaluateFormula","formulaStr","evalStack","formula","ch","ident","end","tokenizeFormula","pos","peek","parseExpression","parseTerm","op","right","parseComparison","NaN","parseFactor","token","cellKey","cellFormula","refs","rangeStr","start","expandRange","funcName","fn","arg","BLOCK_TAGS","getTopLevelBlock","registerBlockCommands","bq","language","clone","ensureMinimumContent","firstElementChild","summary","n","details","selected","BLOCK_TYPE_MAP","paragraph","heading1","heading2","heading3","heading4","heading5","heading6","registerBlockConvertCommands","to","getFocusedBlock","targetTag","extractBlockContent","newBlock","info","temp","createTargetBlock","cursorTarget","wrapWithStyle","property","getSelectionStyle","registerFontCommands","family","marginBottom","registerMediaCommands","embedUrl","getEmbedUrl","registerFindReplaceCommands","currentMatches","currentIndex","clearHighlights","createTextNode","normalize","pruneStaleMatches","m","isConnected","highlightCurrent","scrollIntoView","behavior","caseSensitive","searchText","matches","textNodes","flags","regex","RegExp","nodeMatches","exec","j","reverse","findMatches","current","replaceText","textNode","registerSourceModeCommands","fullscreenCount","registerFullscreenCommands","isFullscreen","overflow","hideTimer","registerDistractionFreeCommands","showChrome","active","currentTarget","registerSplitViewCommands","STORAGE_KEY","loadColorPresets","localStorage","getItem","parsed","JSON","presets","setItem","stringify","deleteColorPreset","registerColorPresetCommands","_eng","registerMarkdownToggleCommands","toggle","markdownMode","registerAttachmentCommands","sizeLabel","bytes","units","unitIndex","formatFileSize","registerImportDocumentCommands","mode","RECENT_STORAGE_KEY","getRecentCommands","recordRecentCommand","commandId","recent","unshift","clearRecentCommands","removeItem","_customItems","registerCommandItems","arr","findIndex","unregisterCommandItem","getCustomCommandItems","SLASH_COMMAND_ITEMS","keywords","action","_engine","openModal","latex","displayMode","filterSlashItems","pinRecent","recentIds","recentItems","labelMatches","otherMatches","message","createEnvelope","metadata","timestamp","now","version","parseEnvelope","json","LocalStorageProvider","prefix","_key","load","saveSync","SessionStorageProvider","sessionStorage","FileSystemProvider","writeFn","readFn","deleteFn","envelope","CloudProvider","endpoint","headers","method","fetchFn","buildUrl","buildBody","buildLoadUrl","buildDeleteUrl","_fetch","fetch","globalThis","_saveUrl","_loadUrl","sep","encodeURIComponent","_deleteUrl","_fetchWithRetry","opts","retries","res","ok","status","statusText","CustomProvider","_save","_load","_clear","createStorageProvider","_managerRegistry","AutosaveManager","provider","interval","debounce","enabled","_intervalTimer","_isSaving","_pendingSave","_lastSavedContent","_contentChangeHandler","_beforeUnloadHandler","_destroyed","_consecutiveErrors","_maxRetries","maxRetries","_scheduleDebouncedSave","_unsubContentChange","setInterval","_attemptSyncSave","clearInterval","finally","retryCount","fatal","delay","pow","checkRecovery","currentContent","stored","storedNorm","currentNorm","recoveredContent","clearRecovery","sendBeacon","createPlugin","definition","author","toolbarItems","statusBarItems","contextMenuItems","WordCountPlugin","debounceTimer","unsubContentChange","update","wordCount","_wordCount","COMBINED_URL_REGEX","PROTOCOL_BEFORE_REGEX","PROTOCOL_OR_WWW_BEFORE_REGEX","COMMON_TLDS","extractTLD","AutolinkPlugin","_handler","MAX_URL_LENGTH","searchStart","best","matchAll","isProtocol","isWww","isBare","candidate","startIdx","endIdx","tld","findLastURLMatch","focusOffset","PlaceholderPlugin","placeholderText","WorkerPool","_maxWorkers","maxWorkers","hardwareConcurrency","_workers","_queue","_nextId","_pending","_roundRobin","_enabled","Worker","_workerURL","workerURL","taskType","_executeFallback","_spawnWorker","worker","postMessage","fallback","onmessage","workerCount","w","terminate","VirtualScroller","_container","_threshold","threshold","_rootMargin","rootMargin","_active","_collapsed","_placeholders","IntersectionObserver","_handleIntersection","_scan","_getBlockElements","isIntersecting","_restore","_collapse","minHeight","restoreAll","refresh","collapsedCount","triggerDownload","blob","createObjectURL","download","click","revokeObjectURL","exportAsMarkdown","Blob","exportAsPDF","cssText","htmlContent","srcdoc","cleanup","fallbackTimer","contentWindow","onafterprint","print","exportAsDocx","removeFonts","fontsToRemove","addFonts","fontsToAdd","newFonts","loadGoogleFonts","fontFamilies","dataset","remyxFonts","integrity","crossOrigin","head","BLOCK_ELEMENTS","VOID_ELEMENTS","PREFORMATTED","formatHTML","formatNode","repeat","isVoid","currentIndent","openTag","buildOpenTag","isInlineContent","innerContent","serializeInlineContent","RTL_REGEX","LTR_REGEX","detectTextDirection","rtlCount","ltrCount","char","applyAutoDirection","dir","applyAutoDirectionAll","LOGICAL_PROPERTIES","marginStart","marginEnd","paddingStart","paddingEnd","borderStart","borderEnd","insetStart","insetEnd","en","_currentLocale","_locales","_currentLang","vars","setLocale","locale","getLocale","registerLocale","strings","unregisterLocale","getRegisteredLocales","batchDOMMutations","mutations","requestAnimationFrame","scheduleIdleTask","timeout","requestIdleCallback","cancelIdleTask","cancelIdleCallback","rafThrottle","frameId","measurePerformance","performance","console","debug","benchmark","iterations","times","mean","median","floor","p95","createInputBatcher","applyFn","flushMs","pending","timer","mutation","batch","flush","cancelAnimationFrame","THEME_VARIABLES","var","textSecondary","border","borderSubtle","toolbarBg","toolbarBorder","toolbarButtonHover","toolbarButtonActive","toolbarIcon","toolbarIconActive","primary","primaryHover","primaryLight","focusRing","danger","dangerLight","placeholder","modalBg","modalOverlay","statusbarBg","statusbarText","hoverBg","shadowSm","shadowMd","shadowLg","shadowFloat","contentFontSize","contentLineHeight","radius","radiusSm","radiusInner","spacingXs","spacingSm","spacingMd","transitionFast","transitionNormal","KEY_TO_VAR","createTheme","overrides","THEME_PRESETS","ocean","forest","sunset","rose","TOOLBAR_ITEM_STYLE_KEYS","background","hoverColor","hoverBackground","activeColor","activeBackground","borderRadius","iconSize","padding","opacity","ITEM_KEY_TO_VAR","SEPARATOR_KEY_TO_VAR","resolveToolbarItemStyle","hasKeys","resolveSeparatorStyle","createToolbarItemTheme","resolved","_separator","defineConfig","editors","loadConfig","env","signal","response","Accept","parseSimpleYaml","envOverrides","_envKey","baseConfig","deepMerge","obj","arrayMatch","parseYamlValue","kvMatch","nextLine","nested","BUILTIN_PLUGINS","WordCountPlugin$1","AutolinkPlugin$1","PlaceholderPlugin$1","_customPluginFactories","registerPluginFactory","factory","unregisterPluginFactory","resolvePlugins","pluginsConfig","all","resolveByName","customFactory","builtinFactory"],"mappings":"kGAGO,MAAMA,EAIX,WAAAC,GACEC,KAAKC,8BAAiBC,IACtBF,KAAKG,kCAAqBD,IAE1BF,KAAKI,WAAa,CACpB,CAUA,EAAAC,CAAGC,EAAOC,EAASC,GAMjB,GALKR,KAAKC,WAAWQ,IAAIH,IACvBN,KAAKC,WAAWS,IAAIJ,iBAAO,IAAIK,KAI7BH,GAASI,IAAK,CAChB,MAAMC,EAAe,GAAGP,KAASE,EAAQI,MACnCE,EAAWd,KAAKG,eAAeY,IAAIF,GACrCC,GACFd,KAAKgB,IAAIV,EAAOQ,GAElBd,KAAKG,eAAeO,IAAIG,EAAcN,EACxC,CAGA,OADAP,KAAKC,WAAWc,IAAIT,GAAOW,IAAIV,GACxB,IAAMP,KAAKgB,IAAIV,EAAOC,EAC/B,CAQA,GAAAS,CAAIV,EAAOC,GACT,MAAMW,EAAWlB,KAAKC,WAAWc,IAAIT,GACjCY,IACFA,EAASC,OAAOZ,GACM,IAAlBW,EAASE,MACXpB,KAAKC,WAAWkB,OAAOb,GAG7B,CAQA,IAAAe,CAAKf,EAAOC,GACV,MAAMe,EAAU,IAAIC,KAClBvB,KAAKgB,IAAIV,EAAOgB,GAChBf,KAAWgB,EAAI,EAEjB,OAAOvB,KAAKK,GAAGC,EAAOgB,EACxB,CASA,IAAAE,CAAKlB,EAAOmB,GACV,MAAMP,EAAWlB,KAAKC,WAAWc,IAAIT,GACrC,GAAIY,EAAU,CAEZlB,KAAKI,aACL,IACEc,EAASQ,SAASnB,IAChB,IACEA,EAAQkB,EACV,OAASE,GAGO,UAAVrB,GAAqBN,KAAKI,WAAa,GACzCJ,KAAKwB,KAAK,QAAS,CAAElB,QAAOsB,MAAOD,EAAKF,QAE5C,IAEJ,CAAA,QACEzB,KAAKI,YACP,CACF,CACF,CAOA,kBAAAyB,CAAmBvB,GACbA,EACFN,KAAKC,WAAWkB,OAAOb,GAEvBN,KAAKC,WAAW6B,OAEpB,EC/GF,MAAMC,iBAAmB,IAAIpB,IAAI,CAAC,IAAK,KAAM,KAAM,KAAM,KAAM,KAAM,KAAM,MAAO,aAAc,MAAO,KAAM,KAAM,OAS5G,SAASqB,EAAcC,EAAMC,GAClC,IAAIC,EAAKF,EAAKG,WAAaC,KAAKC,UAAYL,EAAKM,cAAgBN,EACjE,KAAOE,GAAMA,IAAOD,GAAU,CAC5B,GAAIH,EAAiBtB,IAAI0B,EAAGK,SAAU,OAAOL,EAC7CA,EAAKA,EAAGI,aACV,CACA,OAAO,IACT,CAEO,SAASE,EAAaR,EAAMS,GACjC,OAAOV,EAAcC,EAAMS,EAC7B,CAEO,SAASC,EAAWV,EAAMO,EAASE,GACxC,MAAME,EAAMJ,EAAQK,cACpB,IAAIV,EAAKF,EAAKG,WAAaC,KAAKC,UAAYL,EAAKM,cAAgBN,EACjE,KAAOE,GAAMA,IAAOO,GAAM,CACxB,GAAIP,EAAGK,UAAYI,EAAK,OAAOT,EAC/BA,EAAKA,EAAGI,aACV,CACA,OAAO,IACT,CAEO,SAASO,EAAUC,EAAOP,EAASQ,EAAQ,CAAA,GAChD,MAAMb,EAAKc,SAASC,cAAcV,GAClCW,OAAOC,QAAQJ,GAAOtB,SAAQ,EAAEd,EAAKyC,KAAWlB,EAAGmB,aAAa1C,EAAKyC,KACrE,IACEN,EAAMQ,iBAAiBpB,EACzB,CAAA,MACE,MAAMqB,EAAWT,EAAMU,kBACvBtB,EAAGuB,YAAYF,GACfT,EAAMY,WAAWxB,EACnB,CACA,OAAOA,CACT,CAEO,SAASyB,EAAUC,GACxB,MAAMC,EAASD,EAAQE,WACvB,KAAOF,EAAQG,YACbF,EAAOG,aAAaJ,EAAQG,WAAYH,GAE1CC,EAAOI,YAAYL,EACrB,CAYO,SAASM,IACd,MAAO,OAASC,KAAKC,SAASC,SAAS,IAAIC,OAAO,EAVxB,EAW5B,CAEO,SAASC,EAAaC,GAC3B,IAAKA,EAAO,OAAO,EAEnB,MAAgB,KADHA,EAAMC,YAAYC,SACRF,EAAMG,cAAc,yBAC7C,CCrEA,MAAMC,EAAgB,WAGhBC,EAAiB,CACrBC,oBAAM,IAAIpE,IAAI,CAAC,SAAU,MACzBqE,sBAAQ,IAAIrE,IAAI,CAAC,KAAM,MACvBsE,yBAAW,IAAItE,IAAI,CAAC,MACpBuE,6BAAe,IAAIvE,IAAI,CAAC,IAAK,QAC7BwE,yBAAW,IAAIxE,IAAI,CAAC,QACpByE,2BAAa,IAAIzE,IAAI,CAAC,SAsCjB,MAAM0E,EAKX,WAAAtF,CAAYuF,GACVtF,KAAKuF,OAASD,EACdtF,KAAKwF,aAAe,KACpBxF,KAAKyF,iBAAmB,CAC1B,CAMA,eAAAC,GACE1F,KAAKyF,mBACLzF,KAAKwF,aAAe,IACtB,CAMA,YAAAG,GACE,OAAOC,OAAOD,cAChB,CAMA,QAAAE,GAEE,GAA0B,OAAtB7F,KAAKwF,aACP,OAAOxF,KAAKwF,aAEd,MAAMM,EAAM9F,KAAK2F,eACjB,IAAKG,GAA0B,IAAnBA,EAAIC,WAEd,OADA/F,KAAKwF,aAAe,KACb,KAET,MAAMzC,EAAQ+C,EAAIE,WAAW,GAC7B,IAAKhG,KAAKiG,eAAelD,EAAMmD,yBAE7B,OADAlG,KAAKwF,aAAe,KACb,KAETxF,KAAKwF,aAAezC,EAEpB,MAAMoD,IAAQnG,KAAKyF,iBAMnB,OALAW,QAAQC,UAAUC,MAAK,KACjBtG,KAAKyF,mBAAqBU,IAC5BnG,KAAKwF,aAAe,KACtB,IAEKzC,CACT,CAQA,QAAAwD,CAASxD,GACP/C,KAAKwF,aAAe,KACpB,IACE,MAAMM,EAAM9F,KAAK2F,eACjBG,EAAIU,kBACJV,EAAIW,SAAS1D,EACf,CAAA,MAEA,CACF,CAOA,cAAAkD,CAAehE,GACb,IAAKA,EAAM,OAAO,EAClB,MAAME,EAAKF,EAAKG,WAAaC,KAAKC,UAAYL,EAAKM,cAAgBN,EACnE,OAAOjC,KAAKuF,OAAOmB,SAASvE,EAC9B,CAMA,WAAAwE,GACE,MAAMb,EAAM9F,KAAK2F,eACjB,OAAOG,GAAMA,EAAIa,WACnB,CAMA,eAAAC,GACE,MAAMd,EAAM9F,KAAK2F,eACjB,OAAOG,EAAMA,EAAIxB,WAAa,EAChC,CAMA,eAAAuC,GACE,MAAM9D,EAAQ/C,KAAK6F,WACnB,IAAK9C,EAAO,MAAO,GACnB,MAAMS,EAAWT,EAAM+D,gBACjBC,EAAM9D,SAASC,cAAc,OAEnC,OADA6D,EAAIrD,YAAYF,GACTuD,EAAIC,SACb,CAOA,IAAAC,GACE,MAAMlE,EAAQ/C,KAAK6F,WACnB,IAAK9C,EAAO,OAAO,KAEnB,MAAMmE,EAAWjE,SAASkE,cAC1BD,EAASE,mBAAmBpH,KAAKuF,QACjC2B,EAASG,OAAOtE,EAAMuE,eAAgBvE,EAAMwE,aAC5C,MAAMA,EAAcL,EAAS5C,WAAWkD,OAExC,MAAO,CACLD,cACAE,UAAWF,EAAcxE,EAAMuB,WAAWkD,OAC1CE,UAAW3E,EAAM2E,UAErB,CAQA,OAAAC,CAAQC,GACN,IAAKA,EAAU,OACf,MAAMC,EAAa5E,SAAS6E,iBAC1B9H,KAAKuF,OACLwC,WAAWC,UACX,MAGF,IAAIC,EAAY,EACZC,EAAY,KACZC,EAAkB,EAClBC,EAAU,KACVC,EAAgB,EAEpB,KAAOR,EAAWS,YAAY,CAC5B,MAAMrG,EAAO4F,EAAWU,YAElBC,EAAYP,EADChG,EAAKyC,YAAY8C,OAOpC,IAJKU,GAAaM,GAAaZ,EAASL,cACtCW,EAAYjG,EACZkG,EAAkBP,EAASL,YAAcU,IAEtCG,GAAWI,GAAaZ,EAASH,UAAW,CAC/CW,EAAUnG,EACVoG,EAAgBT,EAASH,UAAYQ,EACrC,KACF,CACAA,EAAYO,CACd,CAIA,GAAIN,EACF,IACE,MAAMnF,EAAQE,SAASkE,cACvBpE,EAAM0F,SAASP,EAAW9D,KAAKsE,IAAIP,EAAiBD,EAAUxD,YAAY8C,SACtEY,EACFrF,EAAMsE,OAAOe,EAAShE,KAAKsE,IAAIL,EAAeD,EAAQ1D,YAAY8C,SAElEzE,EAAM4F,UAAS,GAEjB3I,KAAKuG,SAASxD,EAChB,CAAA,MAEE,IACE,MAAM6F,EAAgB3F,SAASkE,cAC/ByB,EAAcxB,mBAAmBpH,KAAKuF,QACtCqD,EAAcD,UAAS,GACvB3I,KAAKuG,SAASqC,EAChB,CAAA,MAEA,CACF,CAEJ,CAOA,QAAAD,CAASE,GAAQ,GACf,MAAM/C,EAAM9F,KAAK2F,eACbG,GAAOA,EAAIC,WAAa,IACtB8C,EACF/C,EAAIgD,gBAEJhD,EAAIiD,kBAGV,CAOA,gBAAAC,GACE,MAAMjG,EAAQ/C,KAAK6F,WACnB,IAAK9C,EAAO,OAAO,KACnB,MAAMkG,EAAYlG,EAAMmD,wBACxB,OAAO+C,EAAU7G,WAAaC,KAAKC,UAAY2G,EAAU1G,cAAgB0G,CAC3E,CAMA,cAAAC,GACE,MAAM/G,EAAKnC,KAAKgJ,mBAChB,OAAK7G,EAEEH,EAAcG,EAAInC,KAAKuF,QAFd,IAGlB,CAOA,iBAAA4D,CAAkB3G,GAChB,IAAIL,EAAKnC,KAAKgJ,mBACd,MAAMpG,EAAMJ,EAAQK,cACpB,KAAOV,GAAMA,IAAOnC,KAAKuF,QAAQ,CAC/B,GAAIpD,EAAGK,UAAYI,EAAK,OAAOT,EAC/BA,EAAKA,EAAGI,aACV,CACA,OAAO,IACT,CAWA,UAAA6G,CAAWC,GACT,MAAMC,EAAWtJ,KAAKuJ,WAAavJ,KAAKuJ,WAAWC,SAASH,GAAQA,EAC9DvD,EAAM9F,KAAK2F,eACjB,IAAKG,GAA0B,IAAnBA,EAAIC,WAAkB,OAClC,MAAMhD,EAAQ+C,EAAIE,WAAW,GAC7BjD,EAAM0G,iBACN,MAAMC,EAAWzG,SAASC,cAAc,YACxCwG,EAAS1C,UAAYsC,EACrB,MAAM9F,EAAWkG,EAASC,QACpBC,EAAYpG,EAASoG,UAG3B,GAFA7G,EAAMY,WAAWH,GAEboG,EAAW,CACb,MAAMC,EAAW5G,SAASkE,cAC1B0C,EAASC,cAAcF,GACvBC,EAASlB,UAAS,GAClB7C,EAAIU,kBACJV,EAAIW,SAASoD,EACf,CACF,CAQA,YAAAE,CAAaC,GACXhK,KAAKuJ,WAAaS,CACpB,CAQA,UAAArG,CAAW1B,GACT,MAAMc,EAAQ/C,KAAK6F,WACd9C,IACLA,EAAM0G,iBACN1G,EAAMY,WAAW1B,GACjBc,EAAM+G,cAAc7H,GACpBc,EAAM4F,UAAS,GACf3I,KAAKuG,SAASxD,GAChB,CASA,QAAAkH,CAASzH,EAASQ,EAAQ,IACxB,MAAMD,EAAQ/C,KAAK6F,WACnB,IAAK9C,GAASA,EAAM2E,UAAW,OAAO,KACtC,MAAMvF,EAAKc,SAASC,cAAcV,GAClCW,OAAOC,QAAQJ,GAAOtB,SAAQ,EAAEd,EAAKyC,MACnClB,EAAGmB,aAAa1C,EAAKyC,EAAK,IAE5B,IACEN,EAAMQ,iBAAiBpB,EACzB,CAAA,MACE,MAAMqB,EAAWT,EAAMU,kBACvBtB,EAAGuB,YAAYF,GACfT,EAAMY,WAAWxB,EACnB,CAEA,OADAnC,KAAKuG,SAASxD,GACPZ,CACT,CAOA,MAAA+H,CAAO1H,GACL,MAAML,EAAKnC,KAAKmJ,kBAAkB3G,GAClC,IAAKL,EAAI,OACT,MAAM2B,EAAS3B,EAAG4B,WAClB,KAAO5B,EAAG6B,YACRF,EAAOG,aAAa9B,EAAG6B,WAAY7B,GAErC2B,EAAOI,YAAY/B,EACrB,CAMA,gBAAAgI,GACE,MAAMC,EAAU,CACdrF,MAAM,EACNC,QAAQ,EACRC,WAAW,EACXC,eAAe,EACfC,WAAW,EACXC,aAAa,EACbiF,QAAS,KACTC,UAAW,OACXC,aAAa,EACbC,eAAe,EACfC,YAAY,EACZC,WAAW,EACXC,KAAM,KACNC,WAAY,KACZC,SAAU,KACVC,UAAW,KACXC,UAAW,MAKb,IACE,MAAMjF,EAAMF,OAAOD,eACnB,GAAIG,GAAOA,EAAIC,WAAa,EAAG,CAC7B,IAAI9D,EAAO6D,EAAIkF,WAEf,KAAO/I,GAAQA,IAASjC,KAAKuF,QAAQ,CACnC,GAAItD,EAAKG,WAAaC,KAAK4I,aAAc,CACvC,MAAMrI,EAAMX,EAAKO,QACjB,IAAA,MAAW0I,KAAUpG,GACdsF,EAAQc,IAAWpG,EAAeoG,GAAQzK,IAAImC,KACjDwH,EAAQc,IAAU,EAGxB,CACAjJ,EAAOA,EAAK8B,UACd,CACF,CACF,CAAA,MAEA,CAEA,MAAMU,EAAQzE,KAAKkJ,iBACnB,GAAIzE,EAAO,CACT,MAAM7B,EAAM6B,EAAMjC,QAAQ2I,cACtBtG,EAAcuG,KAAKxI,KACrBwH,EAAQC,QAAUzH,GAEpB,MAAMyI,EAAQ5G,EAAM6G,MAAMC,WAAa3F,OAAO4F,iBAAiB/G,GAAO8G,UAClEF,IACFjB,EAAQE,UAAsB,UAAVe,EAAoB,OAAmB,QAAVA,EAAkB,QAAUA,EAEjF,CAEA,IAAIlJ,EAAKnC,KAAKgJ,mBACd,KAAO7G,GAAMA,IAAOnC,KAAKuF,QAAQ,CAC/B,MAAM3C,EAAMT,EAAGK,QACH,OAARI,IAAcwH,EAAQG,aAAc,GAC5B,OAAR3H,IAAcwH,EAAQI,eAAgB,GAC9B,eAAR5H,IAAsBwH,EAAQK,YAAa,GACnC,QAAR7H,IAAewH,EAAQM,WAAY,GAC3B,MAAR9H,IAAawH,EAAQO,KAAO,CAAEc,KAAMtJ,EAAGsJ,KAAMC,KAAMvJ,EAAGuC,YAAaiH,OAAQxJ,EAAGwJ,SAClFxJ,EAAKA,EAAGI,aACV,CAEA,IACE6H,EAAQQ,WAAa3H,SAAS2I,kBAAkB,aAAe,KAC/DxB,EAAQS,SAAW5H,SAAS2I,kBAAkB,aAAe,KAC7DxB,EAAQU,UAAY7H,SAAS2I,kBAAkB,cAAgB,KAC/DxB,EAAQW,UAAY9H,SAAS2I,kBAAkB,cAAgB,IACjE,CAAA,MAEA,CAEA,OAAOxB,CACT,CAMA,eAAAyB,GACE,MAAM9I,EAAQ/C,KAAK6F,WACnB,OAAK9C,EACEA,EAAM+I,wBADM,IAErB,ECrdK,MAAMC,EAKX,WAAAhM,CAAYiM,GACVhM,KAAKgM,OAASA,EACdhM,KAAKiM,6BAAgB/L,GACvB,CASA,QAAAgM,CAASC,EAAMC,GAiBb,GAhBApM,KAAKiM,UAAUvL,IAAIyL,EAAM,CACvBA,OACAE,QAASD,EAAQC,QACjBC,SAAUF,EAAQE,UAAA,MAAmB,GACrCC,UAAWH,EAAQG,WAAA,MAAoB,GACvCC,SAAUJ,EAAQI,UAAY,KAC9BC,KAAML,EAAQK,MAAQ,CAAA,EAEtBC,aAAcN,EAAQM,eAAgB,IAGpCN,EAAQI,UACVxM,KAAKgM,OAAOW,SAAST,SAASE,EAAQI,SAAUL,GAI9CC,EAAQQ,mBACV,IAAA,MAAWC,KAAOT,EAAQQ,mBACxB5M,KAAKgM,OAAOW,SAAST,SAASW,EAAKV,EAGzC,CASA,OAAAE,CAAQF,KAAS5K,GACf,MAAM6K,EAAUpM,KAAKiM,UAAUlL,IAAIoL,GACnC,IAAKC,EAEH,OAAO,EAET,IAAKA,EAAQG,UAAUvM,KAAKgM,QAC1B,OAAO,EAIJI,EAAQM,cACX1M,KAAKgM,OAAOc,QAAQC,WAEtB,MAAMC,EAASZ,EAAQC,QAAQrM,KAAKgM,UAAWzK,GAG/C,OAFAvB,KAAKgM,OAAOiB,SAASzL,KAAK,mBAAoB,CAAE2K,OAAM5K,OAAMyL,WAC5DhN,KAAKgM,OAAOiB,SAASzL,KAAK,kBACnBwL,CACT,CAOA,QAAAV,CAASH,GACP,MAAMC,EAAUpM,KAAKiM,UAAUlL,IAAIoL,GACnC,QAAKC,GACEA,EAAQE,SAAStM,KAAKgM,OAC/B,CAOA,SAAAO,CAAUJ,GACR,MAAMC,EAAUpM,KAAKiM,UAAUlL,IAAIoL,GACnC,QAAKC,GACEA,EAAQG,UAAUvM,KAAKgM,OAChC,CAOA,GAAAjL,CAAIoL,GACF,OAAOnM,KAAKiM,UAAUlL,IAAIoL,EAC5B,CAMA,MAAAe,GACE,OAAOC,MAAMC,KAAKpN,KAAKiM,UAAUoB,OACnC,CAOA,GAAA5M,CAAI0L,GACF,OAAOnM,KAAKiM,UAAUxL,IAAI0L,EAC5B,ECnIK,SAASmB,EAASC,GACvB,IAAIC,EAAO,KACX,IAAA,IAASC,EAAI,EAAGC,EAAMH,EAAI/F,OAAQiG,EAAIC,EAAKD,IACzCD,GAASA,GAAQ,GAAKA,EAAOD,EAAII,WAAWF,GAAM,EAEpD,OAAOD,CACT,CCwCA,SAASI,EAAUC,EAASC,GAC1B,MAAMC,UAAEA,EAAAC,UAAWA,EAAAC,OAAWA,GAAWH,EAGzC,OAFeD,EAAQK,MAAM,EAAGH,GAEhBE,GADDD,EAAY,EAAIH,EAAQK,MAAML,EAAQrG,OAASwG,GAAa,GAE7E,CA+BO,MAAMG,EAMX,WAAApO,CAAYiM,EAAQxL,EAAU,IAC5BR,KAAKgM,OAASA,EACdhM,KAAKoO,QAAU5N,EAAQ4N,SAAW,IAClCpO,KAAKqO,WAAa7N,EAAQ6N,YAAc,IACxCrO,KAAKsO,WAAa9N,EAAQ8N,YAAc,IACxCtO,KAAKuO,WAAa,GAClBvO,KAAKwO,WAAa,GAClBxO,KAAKyO,UAAY,KACjBzO,KAAK0O,eAAiB,KACtB1O,KAAK2O,eAAiB,KACtB3O,KAAK4O,uBAAwB,EAC7B5O,KAAK6O,eAAgB,EACrB7O,KAAK8O,cAAgB,KACrB9O,KAAK+O,UAAY,IACnB,CAQA,IAAAC,GACEhP,KAAKiP,gBACLjP,KAAKyO,UAAY,IAAIS,kBAAiB,KAChClP,KAAK4O,uBACT5O,KAAKmP,oBAAkB,IAEzBnP,KAAKyO,UAAUW,QAAQpP,KAAKgM,OAAOnI,QAAS,CAC1CwL,WAAW,EACXC,eAAe,EACfC,SAAS,GAEb,CAOA,OAAAC,GACMxP,KAAKyO,YACPzO,KAAKyO,UAAUgB,aACfzP,KAAKyO,UAAY,MAEfzO,KAAK0O,iBACPgB,aAAa1P,KAAK0O,gBAClB1O,KAAK0O,eAAiB,MAEpB1O,KAAK2O,iBACPe,aAAa1P,KAAK2O,gBAClB3O,KAAK2O,eAAiB,MAExB3O,KAAK6O,eAAgB,CACvB,CAOA,QAAA9B,GACM/M,KAAK0O,iBACPgB,aAAa1P,KAAK0O,gBAClB1O,KAAK0O,eAAiB,MAExB1O,KAAKiP,eACP,CAeA,kBAAAE,GACMnP,KAAK0O,gBACPgB,aAAa1P,KAAK0O,gBAEpB1O,KAAK0O,eAAiBiB,YAAW,KAC3B3P,KAAK6O,cAEP7O,KAAK4P,sBAGL5P,KAAK6O,eAAgB,EACrB7O,KAAKiP,gBACDjP,KAAK2O,gBAAgBe,aAAa1P,KAAK2O,gBAC3C3O,KAAK2O,eAAiBgB,YAAW,KAC/B3P,KAAK6O,eAAgB,CAAA,GACpB7O,KAAKsO,YACV,GACCtO,KAAKqO,WACV,CAUA,YAAAwB,CAAaxG,EAAMzB,GAEjB,GAAIyB,EAAK7B,OArMU,MAqMkBxH,KAAK8O,cACxC,MAAO,CAAEgB,KAAM,OAAQzG,OAAMzB,YAI/B,MAAMmI,EA/LV,SAAqBC,EAAQC,GAC3B,MAAMC,EAAS9L,KAAKsE,IAAIsH,EAAOxI,OAAQyI,EAAOzI,QAG9C,IAAIuG,EAAY,EAChB,KAAOA,EAAYmC,GAAUF,EAAOjC,KAAekC,EAAOlC,IACxDA,IAIF,IAAIC,EAAY,EAChB,MAAMmC,EAAYD,EAASnC,EAC3B,KACEC,EAAYmC,GACZH,EAAOA,EAAOxI,OAAS,EAAIwG,KAAeiC,EAAOA,EAAOzI,OAAS,EAAIwG,IAErEA,IAMF,MAAO,CAAED,YAAWC,YAAWC,OAFhBgC,EAAO/B,MAAMH,EAAWkC,EAAOzI,OAASwG,QAAa,GAGtE,CAwKkBoC,CAAYpQ,KAAK8O,cAAezF,GAG9C,OAAI0G,EAAM9B,OAAOzG,QAAwB,GAAd6B,EAAK7B,OACvB,CAAEsI,KAAM,OAAQzG,OAAMzB,YAGxB,CAAEkI,KAAM,OAAQC,QAAOnI,WAChC,CAaA,aAAAyI,CAAcC,EAAOC,GACnB,MAAMC,EAAQF,EAAMC,GACpB,GAAmB,SAAfC,EAAMV,KACR,OAAOU,EAAMnH,KAIf,IAMIA,EANAoH,EAAYF,EAAQ,EACxB,KAAOE,GAAa,GAA+B,SAA1BH,EAAMG,GAAWX,MACxCW,IAMApH,EADEoH,GAAa,EACRH,EAAMG,GAAWpH,KAIjBrJ,KAAK8O,eAAiB,GAI/B,IAAA,IAASrB,EAAIgD,EAAY,EAAGhD,GAAK8C,EAAO9C,IAEpCpE,EADoB,SAAlBiH,EAAM7C,GAAGqC,KACJlC,EAAUvE,EAAMiH,EAAM7C,GAAGsC,OAEzBO,EAAM7C,GAAGpE,KAIpB,OAAOA,CACT,CAUA,kBAAAuG,GACE,MAAMvG,EAAOrJ,KAAKgM,OAAOnI,QAAQmD,UAC3BwG,EAAOF,EAASjE,GAEtB,GAAImE,IAASxN,KAAK+O,WAAa1F,IAASrJ,KAAK8O,cAAe,OAE5D,MAAMlH,EAAW5H,KAAKgM,OAAO0E,UAAUzJ,OAEnCjH,KAAKuO,WAAW/G,OAAS,EAG3BxH,KAAKuO,WAAWvO,KAAKuO,WAAW/G,OAAS,GAAKxH,KAAK6P,aAAaxG,EAAMzB,GAEtE5H,KAAKuO,WAAWoC,KAAK,CAAEb,KAAM,OAAQzG,OAAMzB,aAG7C5H,KAAK8O,cAAgBzF,EACrBrJ,KAAK+O,UAAYvB,CACnB,CASA,aAAAyB,GACE,MAAM5F,EAAOrJ,KAAKgM,OAAOnI,QAAQmD,UAG3BwG,EAAOF,EAASjE,GACtB,GAAImE,IAASxN,KAAK+O,WAAa1F,IAASrJ,KAAK8O,cAAe,OAE5D,MAAMlH,EAAW5H,KAAKgM,OAAO0E,UAAUzJ,OACjCuJ,EAAQxQ,KAAK6P,aAAaxG,EAAMzB,GAEtC5H,KAAKuO,WAAWoC,KAAKH,GACjBxQ,KAAKuO,WAAW/G,OAASxH,KAAKoO,SAChCpO,KAAKuO,WAAWqC,QAElB5Q,KAAKwO,WAAa,GAClBxO,KAAK8O,cAAgBzF,EACrBrJ,KAAK+O,UAAYvB,CACnB,CAOA,mBAAAqD,GACM7Q,KAAKyO,WACPzO,KAAKyO,UAAUgB,YAEnB,CAQA,kBAAAqB,GACM9Q,KAAKyO,WACPzO,KAAKyO,UAAUW,QAAQpP,KAAKgM,OAAOnI,QAAS,CAC1CwL,WAAW,EACXC,eAAe,EACfC,SAAS,GAGf,CAQA,IAAAwB,GACE,IAAK/Q,KAAKgR,UAAW,OAErBhR,KAAK4O,uBAAwB,EAC7B5O,KAAK6Q,sBAEL,MAAMI,EAAcjR,KAAKgM,OAAOnI,QAAQmD,UAClCkK,EAAkBlR,KAAKgM,OAAO0E,UAAUzJ,OAC9CjH,KAAKwO,WAAWmC,KAAK,CAAEb,KAAM,OAAQzG,KAAM4H,EAAarJ,SAAUsJ,IAElE,MAAMC,EAAanR,KAAKuO,WAAW/G,OAAS,EACtC4J,EAAYpR,KAAKqQ,cAAcrQ,KAAKuO,WAAY4C,GAChDE,EAAgBrR,KAAKuO,WAAW4C,GAAYvJ,SAClD5H,KAAKuO,WAAW+C,MAIhBtR,KAAKgM,OAAOnI,QAAQmD,UAAYoK,EAChCpR,KAAK8O,cAAgBsC,EACrBpR,KAAK+O,UAAYzB,EAAS8D,GAEtBC,GACFrR,KAAKgM,OAAO0E,UAAU/I,QAAQ0J,GAGhCrR,KAAK8Q,qBACL9Q,KAAK4O,uBAAwB,EAC7B5O,KAAKgM,OAAOiB,SAASzL,KAAK,gBAC1BxB,KAAKgM,OAAOiB,SAASzL,KAAK,iBAC5B,CAQA,IAAA+P,GACE,IAAKvR,KAAKwR,UAAW,OAErBxR,KAAK4O,uBAAwB,EAC7B5O,KAAK6Q,sBAEL,MAAMI,EAAcjR,KAAKgM,OAAOnI,QAAQmD,UAClCkK,EAAkBlR,KAAKgM,OAAO0E,UAAUzJ,OAC9CjH,KAAKuO,WAAWoC,KAAK,CAAEb,KAAM,OAAQzG,KAAM4H,EAAarJ,SAAUsJ,IAElE,MAAMC,EAAanR,KAAKwO,WAAWhH,OAAS,EACtC4J,EAAYpR,KAAKqQ,cAAcrQ,KAAKwO,WAAY2C,GAChDE,EAAgBrR,KAAKwO,WAAW2C,GAAYvJ,SAClD5H,KAAKwO,WAAW8C,MAIhBtR,KAAKgM,OAAOnI,QAAQmD,UAAYoK,EAChCpR,KAAK8O,cAAgBsC,EACrBpR,KAAK+O,UAAYzB,EAAS8D,GAEtBC,GACFrR,KAAKgM,OAAO0E,UAAU/I,QAAQ0J,GAGhCrR,KAAK8Q,qBACL9Q,KAAK4O,uBAAwB,EAC7B5O,KAAKgM,OAAOiB,SAASzL,KAAK,gBAC1BxB,KAAKgM,OAAOiB,SAASzL,KAAK,iBAC5B,CAMA,OAAAwP,GACE,OAAOhR,KAAKuO,WAAW/G,OAAS,CAClC,CAMA,OAAAgK,GACE,OAAOxR,KAAKwO,WAAWhH,OAAS,CAClC,CAMA,KAAA1F,GACE9B,KAAKuO,WAAa,GAClBvO,KAAKwO,WAAa,GAClBxO,KAAK8O,cAAgB,KACrB9O,KAAK+O,UAAY,IACnB,EC9bF,IAAI0C,EAAS,KAEN,SAASC,IAId,OAHe,OAAXD,IACFA,EAA8B,oBAAdE,WAA6B,uBAAuBvG,KAAKuG,UAAUC,WAE9EH,CACT,CAEO,SAASI,IACd,OAAOH,IAAU,IAAM,MACzB,CCTO,MAAMI,EACX,WAAA/R,CAAYiM,GACVhM,KAAKgM,OAASA,EACdhM,KAAK+R,8BAAiB7R,IAEtBF,KAAKgS,mCAAsB9R,IAC3BF,KAAKiS,eAAiBjS,KAAKiS,eAAeC,KAAKlS,KACjD,CAEA,IAAAgP,GACEhP,KAAKgM,OAAOnI,QAAQsO,iBAAiB,UAAWnS,KAAKiS,eACvD,CAEA,OAAAzC,GACExP,KAAKgM,OAAOnI,QAAQuO,oBAAoB,UAAWpS,KAAKiS,eAC1D,CAEA,QAAA/F,CAASM,EAAU6F,GACjB,MAAMC,EAAatS,KAAKuS,mBAAmB/F,GAC3CxM,KAAK+R,WAAWrR,IAAI4R,EAAYD,EAClC,CAEA,UAAAG,CAAWhG,GACT,MAAM8F,EAAatS,KAAKuS,mBAAmB/F,GAC3CxM,KAAK+R,WAAW5Q,OAAOmR,EACzB,CAEA,qBAAAG,CAAsBJ,GACpB,IAAA,MAAY7F,EAAUL,KAASnM,KAAK+R,WAClC,GAAI5F,IAASkG,EAAa,OAAO7F,EAEnC,OAAO,IACT,CAEA,gBAAAkG,CAAiBlG,GACf,IAAKA,EAAU,MAAO,GAEtB,OADcA,EAASmG,MAAM,KAChBC,KAAKC,GACN,QAANA,EAAoBnB,IAAU,IAAW,OACnC,UAANmB,EAAsBnB,IAAU,IAAW,QACrC,QAANmB,EAAoBnB,IAAU,IAAW,MACtCmB,EAAEhQ,gBACRiQ,KAAKpB,IAAU,GAAK,IACzB,CAEA,kBAAAa,CAAmB/F,GAEjB,GAAIxM,KAAKgS,gBAAgBvR,IAAI+L,GAC3B,OAAOxM,KAAKgS,gBAAgBjR,IAAIyL,GAElC,MAAM8F,EAAa9F,EAASrB,cAAcwH,MAAM,KAAKI,OAAOD,KAAK,KAEjE,OADA9S,KAAKgS,gBAAgBtR,IAAI8L,EAAU8F,GAC5BA,CACT,CAEA,cAAAL,CAAee,GACb,MAAMC,EAAQ,IACVvB,IAAUsB,EAAEE,QAAUF,EAAEG,UAASF,EAAMtC,KAAK,OAC5CqC,EAAEI,UAAUH,EAAMtC,KAAK,SACvBqC,EAAEK,QAAQJ,EAAMtC,KAAK,OAEzB,MAAM/P,EAAMoS,EAAEpS,IAAIuK,cACb,CAAC,UAAW,OAAQ,QAAS,OAAOmI,SAAS1S,IAChDqS,EAAMtC,KAAK/P,GAGb,MAAM0R,EAAaW,EAAMF,OAAOD,KAAK,KAC/BT,EAAcrS,KAAK+R,WAAWhR,IAAIuR,GAEpCD,IACFW,EAAEO,iBACFP,EAAEQ,kBACFxT,KAAKgM,OAAOyH,SAASpH,QAAQgG,GAEjC,EC5EU,MAACqB,EAAe,CAC1Bb,EAAG,CAAC,QAAS,SACbc,GAAI,CAAC,SAAUC,GAAI,CAAC,SAAUC,GAAI,CAAC,SAAUC,GAAI,CAAC,SAAUC,GAAI,CAAC,SAAUC,GAAI,CAAC,SAChFC,OAAQ,GAAIC,EAAG,GAAIC,GAAI,GAAI1G,EAAG,GAAI2G,EAAG,GAAIC,EAAG,GAAIC,IAAK,GACrDC,IAAK,GAAIC,IAAK,GACdC,EAAG,CAAC,OAAQ,SAAU,MAAO,QAAS,QAAS,kBAAmB,gBAAiB,iBACnFC,IAAK,CAAC,MAAO,MAAO,QAAS,SAAU,QAAS,SAChDC,GAAI,CAAC,SAAUC,GAAI,CAAC,QAAS,QAAS,QAASC,GAAI,CAAC,SACpDC,MAAO,CAAC,QAAS,mBAAoBC,MAAO,GAAIC,MAAO,GAAIC,GAAI,CAAC,SAChEC,GAAI,CAAC,UAAW,UAAW,QAAS,QAAS,gBAAiB,qBAAsB,oBAAqB,mBAAoB,iBAAkB,gBAC/IC,GAAI,CAAC,UAAW,UAAW,QAAS,QAAS,mBAAoB,iBAAkB,eAAgB,kBACnG1K,WAAY,CAAC,SACb2K,IAAK,CAAC,QAAS,iBAAkBC,KAAM,CAAC,QAAS,iBACjDC,GAAI,GACJC,GAAI,GACJxO,IAAK,CAAC,QAAS,QAAS,kBACxByO,KAAM,CAAC,QAAS,SAChBC,MAAO,CAAC,OAAQ,UAAW,WAAY,SACvCC,MAAO,CAAC,SACRC,KAAM,CAAC,SACPC,OAAQ,CAAC,MAAO,QAAS,SAAU,cAAe,kBAAmB,UAAW,QAAS,UAG9EC,EAAiB,CAC5B,QAAS,mBAAoB,cAAe,YAC5C,aAAc,kBAAmB,cAAe,aAChD,QAAS,SAAU,YAAa,QAAS,SAAU,cAAe,eAClE,UAAW,UAAW,SAAU,aCvB5BC,EAAoB,sBAGpBC,EAAsB,uDAGtBC,qBAA4BrV,IAAI,CACpC,SAAU,QAAS,MAAO,OAAQ,OAAQ,SAAU,QAAS,SAAU,aAQnEsV,EAAiC,CACrC,kBACA,cACA,2BACA,mBACA,sBACA,uBAOF,SAASC,EAAsBC,EAAKC,GAClC,IAAKD,EAAK,OAAO,EACjB,IACE,MAAME,EAAM,IAAIC,IAAIH,GAEpB,GAAqB,WAAjBE,EAAIE,SAAuB,OAAO,EACtC,MAAMC,EAAWH,EAAIG,SAASrL,cAC9B,OAAOiL,EAAeK,MAAKC,IACzB,MAAMC,EAAID,EAAOvL,cACjB,OAAOqL,IAAaG,GAAKH,EAASI,SAAS,IAAMD,EAAC,GAEtD,CAAA,MACE,OAAO,CACT,CACF,CAiBO,MAAME,EAKX,WAAA9W,CAAYS,EAAU,IACpBR,KAAK8W,YAActW,EAAQsW,aAAepD,EAC1C1T,KAAK+W,cAAgBvW,EAAQuW,eAAiBlB,EAC9C7V,KAAKgX,qBAAuBxW,EAAQwW,sBAAwBf,EAC5DjW,KAAKiX,0BAAa/W,IAElBF,KAAKkX,QAAU,IAAIC,SACrB,CAWA,QAAA3N,CAASH,GACP,IAAKA,EAAM,MAAO,GAGlB,MAAM+N,EAAW9J,EAASjE,GAG1B,GAAIrJ,KAAKiX,OAAOxW,IAAI2W,GAAW,CAC7B,MAAMC,EAASrX,KAAKiX,OAAOlW,IAAIqW,GAC/B,GAAIC,EAAO5B,QAAUpM,EAInB,OAFArJ,KAAKiX,OAAO9V,OAAOiW,GACnBpX,KAAKiX,OAAOvW,IAAI0W,EAAUC,GACnBA,EAAOC,MAGlB,CAGA,MAAMC,EAAMvX,KAAKkX,QAAQM,gBAAgB,SAASnO,WAAe,aACjErJ,KAAKyX,WAAWF,EAAIG,MACpB,MAAM1K,EAASuK,EAAIG,KAAK1Q,UAGxB,GAAIhH,KAAKiX,OAAO7V,MAjDO,GAiDqB,CAC1C,MAAMuW,EAAW3X,KAAKiX,OAAO5J,OAAOuK,OAAOvU,MAC3CrD,KAAKiX,OAAO9V,OAAOwW,EACrB,CAGA,OAFA3X,KAAKiX,OAAOvW,IAAI0W,EAAU,CAAE3B,MAAOpM,EAAMiO,OAAQtK,IAE1CA,CACT,CASA,UAAAyK,CAAWxV,GAET,IAAA,IAASwL,EAAIxL,EAAK4V,WAAWrQ,OAAS,EAAGiG,GAAK,EAAGA,IAAK,CACpD,MAAMqK,EAAQ7V,EAAK4V,WAAWpK,GAC9B,GAAIqK,EAAM1V,WAAaC,KAAKC,UAAW,SACvC,GAAIwV,EAAM1V,WAAaC,KAAK0V,aAAc,CACxC9V,EAAKiC,YAAY4T,GACjB,QACF,CACA,GAAIA,EAAM1V,WAAaC,KAAK4I,aAAc,CACxChJ,EAAKiC,YAAY4T,GACjB,QACF,CAEA,MAAMtV,EAAUsV,EAAMtV,QAAQ2I,cACxB6M,EAAehY,KAAK8W,YAAYtU,GAEtC,IAAKwV,EAAc,CACjB,GAAIhC,EAAsBvV,IAAI+B,GAE5BP,EAAKiC,YAAY4T,OACZ,CAEL,KAAOA,EAAM9T,YACX/B,EAAKgC,aAAa6T,EAAM9T,WAAY8T,GAEtC7V,EAAKiC,YAAY4T,EACnB,CACA,QACF,CAGA,MAAM9U,EAAQmK,MAAMC,KAAK0K,EAAMG,YAC/B,IAAA,MAAWC,KAAQlV,EAEbkV,EAAK/L,KAAKgM,WAAW,MACvBL,EAAMM,gBAAgBF,EAAK/L,MAGX,UAAd+L,EAAK/L,KACH6L,EAAa1E,SAAS,SACxBtT,KAAKqY,aAAaP,GAElBA,EAAMM,gBAAgB,SAEdJ,EAAa1E,SAAS4E,EAAK/L,OACrC2L,EAAMM,gBAAgBF,EAAK/L,MAK/B,GAAI2L,EAAMQ,aAAa,QAAS,CAC9B,MAAM7M,EAAOqM,EAAMS,aAAa,QAC5B9M,GAAQqK,EAAkB1K,KAAKK,IACjCqM,EAAMxU,aAAa,OAAQ,IAE/B,CAGA,GAAgB,UAAZd,EAAqB,CAEvB,GAAkB,cADCsV,EAAMS,aAAa,SAAW,IAAIpN,cACvB,CAC5BlJ,EAAKiC,YAAY4T,GACjB,QACF,CACF,CAGA,GAAgB,WAAZtV,EAAsB,CAExB,IAAK0T,EADO4B,EAAMS,aAAa,OACCvY,KAAKgX,sBAAuB,CAC1D/U,EAAKiC,YAAY4T,GACjB,QACF,CACF,CAEA9X,KAAKyX,WAAWK,EAClB,CACF,CASA,YAAAO,CAAaxU,GACX,MAAMyH,EAAQzH,EAAQyH,MAChBkN,EAAgB,GAEtB,IAAA,MAAWC,KAAQzY,KAAK+W,cAAe,CACrC,MAAM1T,EAAQiI,EAAMoN,iBAAiBD,GACrC,GAAIpV,EAAO,CAET,GAAI0S,EAAoB3K,KAAK/H,GAAQ,SACrCmV,EAAc7H,KAAK,GAAG8H,MAASpV,IACjC,CACF,CAEImV,EAAchR,OAAS,EACzB3D,EAAQP,aAAa,QAASkV,EAAc1F,KAAK,OAEjDjP,EAAQuU,gBAAgB,QAE5B,EC7NF,MAAMlB,EAA+B,oBAAdC,UAA4B,IAAIA,UAAc,KAG/DwB,EAAiB,wBACjBC,EAAiB,mBACjBC,EAAiB,wBACjBC,EAAiB,wCAEjBC,EAAsB,iDAErB,SAASC,EAAgB3P,GAC9B,IAAKA,EAAM,MAAO,GAElB,IAAI4P,EAAU5P,EAGd4P,EAAUA,EAAQC,QAAQ,gBAAiB,IAC3CD,EAAUA,EAAQC,QAAQ,mBAAoB,IAC9CD,EAAUA,EAAQC,QAAQ,0BAA2B,IACrDD,EAAUA,EAAQC,QAAQ,mBAAoB,IAC9CD,EAAUA,EAAQC,QAAQ,4BAA6B,IACvDD,EAAUA,EAAQC,QAAQ,4BAA6B,IAEvDD,EAAUA,EAAQC,QAAQ,wBAAyB,IACnDD,EAAUA,EAAQC,QAAQ,0BAA2B,IACrDD,EAAUA,EAAQC,QAAQ,8BAA+B,IACzDD,EAAUA,EAAQC,QAAQ,gBAAiB,IAG3C,MAAMC,EAAS,2BAA2B/N,KAAK/B,GACzC+P,EAAe,kCAAkChO,KAAK/B,GACtDgQ,EAAiB,4BAA4BjO,KAAK/B,GAClDiQ,EAAgB,+BAA+BlO,KAAK/B,GACpDkQ,EAAe,uCAAuCnO,KAAK/B,GA2HjE,OAxHI8P,IACFF,EAAUA,EAAQC,QAAQ,+BAAgC,IAC1DD,EAAUA,EAAQC,QAAQ,mBAAoB,IAC9CD,EAAUA,EAAQC,QAAQ,wBAAyB,IACnDD,EAAUA,EAAQC,QAAQ,8BAA+B,IACzDD,EAAUA,EAAQC,QAAQ,8BAA+B,IACzDD,EAAUA,EAAQC,QAAQ,8BAA+B,IAGzDD,EAAUA,EAAQC,QAAQ,0BAA2B,IAGrDD,EAAUA,EAAQC,QAAQ,wBAAyB,IAGnDD,EAAUA,EAAQC,QAAQ,oBAAqB,KAI7CE,IACFH,EAAUA,EAAQC,QAAQ,sDAAuD,MACjFD,EAAUA,EAAQC,QAAQ,+BAAgC,IAC1DD,EAAUA,EAAQC,QAAQ,yBAA0B,IACpDD,EAAUA,EAAQC,QAAQ,oBAAqB,IAC/CD,EAAUA,EAAQC,QAAQ,iBAAkB,IAC5CD,EAAUA,EAAQC,QAAQ,2BAA4B,IAItDD,EAAUA,EAAQC,QAChB,2EACA,uBAEFD,EAAUA,EAAQC,QAChB,sEACA,eAEFD,EAAUA,EAAQC,QAChB,iFACA,cAKAI,IACFL,EAAUA,EAAQC,QAAQ,4DAA6D,IACvFD,EAAUA,EAAQC,QAAQ,oBAAqB,IAC/CD,EAAUA,EAAQC,QAAQ,oBAAqB,IAC/CD,EAAUA,EAAQC,QAAQ,wBAAyB,KAIjDG,IACFJ,EAAUA,EAAQC,QAAQ,0CAA2C,IACrED,EAAUA,EAAQC,QAAQ,wCAAyC,IACnED,EAAUA,EAAQC,QAAQ,eAAgB,IAC1CD,EAAUA,EAAQC,QAAQ,uBAAwB,KAIhDC,GAAU,UAAU/N,KAAK/B,KAE3B4P,EAAUA,EAAQC,QAAQ,2BAA4B,IAEtDD,EAAUA,EAAQC,QAAQ,eAAgB,IAC1CD,EAAUA,EAAQC,QAAQ,uBAAwB,KAIhDK,IACFN,EAAUA,EAAQC,QAAQ,wBAAyB,IACnDD,EAAUA,EAAQC,QAAQ,6CAA8C,UAM1ED,EAAUA,EAAQC,QAAQ,2BAA4B,IAGtDD,EAAUA,EAAQC,QAAQ,mBAAoB,IAG9CD,EAAUA,EAAQC,QAAQ,mBAAoB,IAG9CD,EAAUA,EAAQC,QAAQ,gCAAiC,MAG3DD,EAAUA,EAAQC,QAAQ,cAAe,QAGzCD,EAAUA,EAAQC,QAAQ,qBAAsB,IAGhDD,EAAUA,EAAQC,QAAQ,yBAA0B,IAIpDD,EAAUA,EAAQC,QAAQ,wDACxB,2CACFD,EAAUA,EAAQC,QAAQ,yDACxB,qCACFD,EAAUA,EAAQC,QAAQ,wDAAyD,MACnFD,EAAUA,EAAQC,QAAQ,mBAAoB,IAG9CD,EAAUA,EAAQC,QAAQ,aAAc,aACxCD,EAAUA,EAAQC,QAAQ,UAAW,aACrCD,EAAUA,EAAQC,QAAQ,aAAc,SACxCD,EAAUA,EAAQC,QAAQ,UAAW,SAGjCC,IACFF,EAgBJ,SAAmC5P,GACjC,IAAK6N,EAAS,OAAO7N,EACrB,MAAMkO,EAAML,EAAQM,gBAAgB,SAASnO,WAAe,aACtDmQ,EAAajC,EAAIkC,iBAAiB,KAExC,IAAIC,GAAS,EACTC,EAAW,KACXC,EAAS,KAEb,IAAA,MAAW/G,KAAK2G,EAAY,CAC1B,MAAM9N,EAAOmH,EAAEnO,YAAYC,OACrB2G,EAAQuH,EAAE0F,aAAa,UAAY,GACnCsB,EAAYf,EAAe1N,KAAKE,GAEhCwO,EAAWnB,EAAevN,KAAKM,GAC/BqO,EAAWnB,EAAexN,KAAKM,IAASmN,EAAezN,KAAKM,GAElE,IAAKoO,GAAYC,IAAaF,EAAW,CACvC,MAAM/J,EAAOgK,EAAW,KAAO,KAE1BJ,GAAUC,IAAa7J,IAC1B8J,EAASrC,EAAIrU,cAAc4M,GAC3B+C,EAAE9O,WAAWE,aAAa2V,EAAQ/G,GAClC6G,GAAS,EACTC,EAAW7J,GAGb,MAAM+E,EAAK0C,EAAIrU,cAAc,MAE7B2R,EAAG7N,UAAY6L,EAAE7L,UAAUkS,QAAQH,EAAqB,IACxDa,EAAOlW,YAAYmR,GACnBhC,EAAE9O,WAAWG,YAAY2O,EAC3B,MACE6G,GAAS,EACTC,EAAW,KACXC,EAAS,IAEb,CAEA,OAAOrC,EAAIG,KAAK1Q,SAClB,CAxDcgT,CAA0Bf,IAItCA,EAAUA,EAAQC,QAAQ,wBAAyB,YAE5CD,EAAQtU,MACjB,CA6DA,MAAMsV,EAAa,eACbC,EAAe,cACfC,EAAa,gBACbC,EAAU,uBACVC,EAAgB,OAChBC,EAAQ,kBACRC,EAAgB,OAChBC,EAAU,gBACVC,EAAc,YACdC,EAAY,WAAA,qCACZC,EAAU,eACVC,EAAW,gBACXC,EAAW,UACXC,EAAe,iBACfC,EAAiB,UAEhB,SAASC,EAAkBtP,GAChC,IAAKA,GAAQA,EAAKlE,OAAS,EAAG,OAAO,EAErC,MAAMyT,EAAQvP,EAAKiH,MAAM,MACzB,IAAIuI,EAAkB,EAClBC,EAAqB,EAEzB,IAAA,MAAWC,KAAQH,EAAO,CACxB,MAAMI,EAAUD,EAAKzW,OAChB0W,IACLF,IAEIlB,EAAW7O,KAAKiQ,GAAYH,GAAmB,EAC/ChB,EAAa9O,KAAKiQ,IAClBlB,EAAW/O,KAAKiQ,GADcH,IAE9Bd,EAAQhP,KAAKiQ,GAAYH,GAAmB,EAC5Cb,EAAcjP,KAAKiQ,IACnBf,EAAMlP,KAAKiQ,GADoBH,IAE/BX,EAAcnP,KAAKiQ,GAAYH,GAAmB,EAClDV,EAAQpP,KAAKiQ,IAAYZ,EAAYrP,KAAKiQ,IAC1CX,EAAUtP,KAAKiQ,IACfV,EAAQvP,KAAKiQ,GAFyCH,IAGtDN,EAASxP,KAAKiQ,GAAYH,GAAmB,GAC7CL,EAASzP,KAAKiQ,IACdP,EAAa1P,KAAKiQ,IAClBN,EAAe3P,KAAKiQ,KAFMH,IAGhC,CAEA,GAA2B,IAAvBC,EAA0B,OAAO,EAKrC,OADcD,EAAkBC,GAChB,IAAOD,GAAmB,CAC5C,CChRO,SAASI,GAAW/N,GACzB,OAAOA,EACJ2L,QAAQ,KAAM,SACdA,QAAQ,KAAM,QACdA,QAAQ,KAAM,OACnB,CAQO,SAASqC,GAAehO,GAC7B,OAAOA,EACJ2L,QAAQ,KAAM,SACdA,QAAQ,KAAM,QACdA,QAAQ,KAAM,QACdA,QAAQ,KAAM,SACnB,CChBO,SAASsC,GAAaC,GAC3B,MAAMC,EAAMD,EAASE,YAAY,KACjC,OAAOD,GAAO,EAAID,EAASvN,MAAMwN,GAAKvQ,cAAgB,EACxD,CAKO,SAASyQ,GAAWC,GACzB,OAAO,IAAIzV,SAAQ,CAACC,EAASyV,KAC3B,MAAMC,EAAS,IAAIC,WACnBD,EAAOE,OAAUjJ,GAAM3M,EAAQ2M,EAAErH,OAAOqB,QACxC+O,EAAOG,QAAU,IAAMJ,EAAO,IAAIK,MAAM,wBAAwBN,EAAK1P,SACrE4P,EAAOH,WAAWC,EAAI,GAE1B,CAKO,SAASO,GAAkBP,GAChC,OAAO,IAAIzV,SAAQ,CAACC,EAASyV,KAC3B,MAAMC,EAAS,IAAIC,WACnBD,EAAOE,OAAUjJ,GAAM3M,EAAQ2M,EAAErH,OAAOqB,QACxC+O,EAAOG,QAAU,IAAMJ,EAAO,IAAIK,MAAM,wBAAwBN,EAAK1P,SACrE4P,EAAOK,kBAAkBP,EAAI,GAEjC,CAMO,SAASQ,GAAW9O,GACzB,OAAOgO,GAAehO,EACxB,CCtCA,MAAM+O,GAAa,CAEjB,0EAA2E,OAE3E,kBAAmB,MAEnB,gBAAiB,WACjB,kBAAmB,WAEnB,YAAa,OAEb,aAAc,OAEd,WAAY,MACZ,kBAAmB,MAEnB,WAAY,MACZ,kBAAmB,OAGfC,GAAgB,CACpB,QAAS,OACT,OAAQ,MACR,MAAO,WACP,YAAa,WACb,QAAS,OACT,OAAQ,OACR,OAAQ,OACR,OAAQ,MACR,OAAQ,MACR,OAAQ,OAMV,SAASC,GAAaX,GAEpB,GAAIA,EAAK/L,MAAQwM,GAAWT,EAAK/L,MAC/B,OAAOwM,GAAWT,EAAK/L,MAGzB,MAAM2M,EAAMjB,GAAaK,EAAK1P,MAAQ,IACtC,OAAOoQ,GAAcE,IAAQ,IAC/B,CAKO,SAASC,GAAiBb,GAC/B,OAA8B,OAAvBW,GAAaX,EACtB,CAKO,SAASc,KACd,OAAOxZ,OAAOkK,KAAKkP,IAAezJ,KAAK,IACzC,CAKO,SAAS8J,KACd,MAAO,CAAC,MAAO,OAAQ,WAAY,OAAQ,MAAO,MAAO,MAAO,MAClE,CAUOC,eAAeC,GAAgBjB,GACpC,MAAM3Q,EAASsR,GAAaX,GAE5B,IAAK3Q,EACH,MAAM,IAAIiR,MAAM,4BAA4BN,EAAK1P,QAGnD,MAAM4Q,EAAa,CACjBC,KAAM,IAAMC,OAAO,6BACnBC,IAAK,IAAMD,OAAO,4BAClBE,SAAU,IAAMF,OAAO,iCACvB5T,KAAM,IAAM4T,OAAO,6BACnBvR,KAAM,IAAMuR,OAAO,6BACnBG,IAAK,IAAMH,OAAO,4BAClBI,IAAK,IAAMJ,OAAO,6BAGdK,QAAYP,EAAW7R,KACvBqS,QAAYD,EAAIE,QAAQ3B,GAE9B,MAAsB,iBAAR0B,EAAmBvE,EAAgBuE,GAAOA,CAC1D,CCtGY,MAACE,GAAkB,CAC7B,CAAC,OAAQ,QACT,CAAC,WAAY,aAAc,YAC3B,CAAC,OAAQ,SAAU,YAAa,iBAChC,CAAC,YAAa,aACd,CAAC,YAAa,cAAe,aAAc,gBAC3C,CAAC,cAAe,gBAAiB,YACjC,CAAC,UAAW,UACZ,CAAC,OAAQ,QAAS,aAAc,iBAAkB,QAAS,aAAc,aAAc,YAAa,kBACpG,CAAC,YAAa,cAAe,gBAC7B,CAAC,gBAAiB,aAAc,YAAa,iBAAkB,kBAC/D,CAAC,cAAe,iBAAkB,aAAc,kBAAmB,mBAAoB,kBAAmB,kBAAmB,SAAU,iBAAkB,eAG9IC,GAAgB,CAC3B,QACA,cACA,cACA,UACA,YACA,SACA,iBACA,oBACA,SACA,kBACA,eACA,UACA,gBACA,WACA,SACA,aAGWC,GAAqB,CAChC,CAAEjI,MAAO,MAAOrS,MAAO,OACvB,CAAEqS,MAAO,OAAQrS,MAAO,QACxB,CAAEqS,MAAO,OAAQrS,MAAO,QACxB,CAAEqS,MAAO,OAAQrS,MAAO,QACxB,CAAEqS,MAAO,OAAQrS,MAAO,QACxB,CAAEqS,MAAO,OAAQrS,MAAO,QACxB,CAAEqS,MAAO,OAAQrS,MAAO,QACxB,CAAEqS,MAAO,OAAQrS,MAAO,QACxB,CAAEqS,MAAO,OAAQrS,MAAO,QACxB,CAAEqS,MAAO,OAAQrS,MAAO,QACxB,CAAEqS,MAAO,OAAQrS,MAAO,QACxB,CAAEqS,MAAO,OAAQrS,MAAO,QACxB,CAAEqS,MAAO,OAAQrS,MAAO,QACxB,CAAEqS,MAAO,OAAQrS,MAAO,SAGbua,GAAiB,CAC5B,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UACnG,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UACnG,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UACnG,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UACnG,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UACnG,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UACnG,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UACnG,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,WAGxFC,GAAmB,CAC9B,CAAEnI,MAAO,OAAQoI,MAAO,CAAC,iBAAkB,WAC3C,CAAEpI,MAAO,OAAQoI,MAAO,CAAC,OAAQ,OAAQ,MAAO,gBAChD,CAAEpI,MAAO,OAAQoI,MAAO,CAAC,aAAc,kBAAmB,kBAAmB,MAAO,iBAAkB,aAAc,MAAO,kBAAmB,qBAC9I,CAAEpI,MAAO,SAAUoI,MAAO,CACxB,OAAQ,QAAS,QAAS,aAAc,aACxC,MAAO,aAAc,YAAa,iBAClC,MAAO,gBAAiB,aAAc,YAAa,iBAAkB,iBACrE,MAAO,eAET,CAAEpI,MAAO,SAAUoI,MAAO,CACxB,OAAQ,SAAU,YAAa,gBAAiB,MAChD,YAAa,cAAe,MAC5B,CAAEpI,MAAO,YAAaoI,MAAO,CAAC,YAAa,cAAe,aAAc,iBACxE,MAAO,cAAe,gBAAiB,WACvC,MAAO,YAAa,YACpB,MAAO,CAAEpI,MAAO,aAAcoI,MAAO,CAAC,aAAc,gBAAiB,qBACrE,MAAO,kBAWEC,GAAkB,CAC7B,CAAErI,MAAO,SAAUrS,MAAO,IAAKT,IAAK,KACpC,CAAE8S,MAAO,YAAarS,MAAO,KAAMT,IAAK,MACxC,CAAE8S,MAAO,YAAarS,MAAO,KAAMT,IAAK,MACxC,CAAE8S,MAAO,YAAarS,MAAO,KAAMT,IAAK,MACxC,CAAE8S,MAAO,YAAarS,MAAO,KAAMT,IAAK,MACxC,CAAE8S,MAAO,YAAarS,MAAO,KAAMT,IAAK,MACxC,CAAE8S,MAAO,YAAarS,MAAO,KAAMT,IAAK,OCrFnC,SAASob,GAAmBnC,EAAMoC,EAAUC,GACjD,MAAM9P,EAAU6P,GD2EmB,SC1EnC,GAAI7P,EAAU,GAAKyN,EAAKza,KAAOgN,EAAS,EACtByN,EAAKza,KAAA,SAAsB+c,QAAQ,IAClC/P,EAAA,SAAyB+P,QAAQ,GAKlD,OAHID,GAASjR,UACXiR,EAAQjR,SAASzL,KAAK,iBAAkB,CAAEqa,OAAMzN,aAE3C,CACT,CACA,OAAO,CACT,CCjBA,IAAIgQ,GAAmB,KACnBC,IAAmB,EAiEvB,MAAMC,sBAAqB3d,IAAI,CAAC,QAAS,SAAU,UAAW,SAE9D,SAAS4d,GAAU9S,GACjB,IAAKA,EAAM,OAAO,EAElB,GAAI,UAAUL,KAAKK,GAAO,OAAO,EACjC,IAEE,MAAM+S,EAAUC,mBAAmBhT,GAC7B4K,EAAM,IAAIC,IAAIkI,EAAS,+BAC7B,OAAOF,GAAe7d,IAAI4V,EAAIE,SAChC,CAAA,MACE,OAAO,CACT,CACF,CA4CO,SAASmI,GAAerV,GAC7B,OAAKA,GAAiB,gBAATA,GAzHR+U,KACHA,GAAmB,IAAIO,EAAgB,CACrCC,aAAc,MACdtJ,GAAI,MACJuJ,iBAAkB,IAClBC,eAAgB,SAChBC,MAAO,MACPC,YAAa,IACbC,gBAAiB,KACjBC,UAAW,YAEbd,GAAiBe,IAAIC,GAGrBhB,GAAiBiB,QAAQ,YAAa,CACpCC,OAAQ,KACRC,YAAa,IAAM,SAIrBnB,GAAiBiB,QAAQ,eAAgB,CACvCC,OAAOrd,GAEe,UAAlBA,EAAKud,UACS,aAAdvd,EAAK6N,MACL7N,EAAKwd,UAAU/Y,SAAS,qBAG5B6Y,YAAA,CAAY5V,EAAS1H,IACZA,EAAKyd,QAAU,OAAS,SAKnCtB,GAAiBiB,QAAQ,8BAA+B,CACtDC,OAAOrd,GAEe,QAAlBA,EAAKud,UACLvd,EAAK+B,YACwB,SAA7B/B,EAAK+B,WAAWwb,SAGpB,WAAAD,CAAY5V,EAAS1H,GACnB,MAAMoT,EAAOpT,EAAK+B,WAGlB,MAAO,aAFUqR,EAAKkD,aAAa,kBAAoB,OAC1ClD,EAAK3Q,yBAEpB,IAIF0Z,GAAiBiB,QAAQ,YAAa,CACpCC,OAAQ,CAAC,KACTC,YAAY5V,GACH,MAAMA,WAIZyU,IAgEcuB,SAAStW,GADc,EAE9C,CAOO,SAASuW,GAAeC,GAC7B,OAAKA,GArDP,WACE,IAAKxB,GAAkB,CACrByB,EAAOC,WAAW,CAChBX,KAAK,EACLY,QAAQ,IAIV,MAAMC,EAAW,IAAIH,EAAOI,SAC5BD,EAAStV,KAAO,EAAGc,OAAM0U,QAAOC,aAC9B,MAAM1U,EAAOoU,EAAOO,OAAOC,YAAYF,GACvC,OAAI3U,IAAS8S,GAAU9S,GACdC,EAGF,YAAYD,KADD0U,EAAQ,WAAWA,EAAMjH,QAAQ,KAAM,aAAe,MAChCxN,OAAI,EAE9CuU,EAASM,MAAQ,EAAG9U,OAAM0U,QAAOzU,WAC3BD,GAAS8S,GAAU9S,IAAUA,EAAK0M,WAAW,eAI1C,aAAa1M,WAAcC,GAAQ,MADxByU,EAAQ,WAAWA,EAAMjH,QAAQ,KAAM,aAAe,QAF/DxN,GAAQ,GAMnBuU,EAAS5K,KAAO,EAAG3J,OAAM8U,WACvB,MAAMC,EAAWD,EAAO,mBAAmBA,EAAKtH,QAAQ,KAAM,aAAe,GAE7E,MAAO,OAAOuH,UAAiBA,KADfnF,GAAW5P,mBACuB,EAGpDoU,EAAOX,IAAI,CAAEc,aAEb5B,IAAmB,CACrB,CACF,CAmBEqC,GACOZ,EAAOa,MAAMd,IAFJ,EAGlB,CCtIO,SAASe,GAAgB5U,EAAQN,GACtC,GAA4B,aAAxBM,EAAO6U,cAA+B7F,EAAkBtP,GAAO,CACjE,IAAIoV,EAAalB,GAAelU,GAChCoV,EAAa9U,EAAOhC,UAAUR,SAASsX,GACvC9U,EAAO0E,UAAUtH,WAAW0X,EAC9B,KAAO,CACL,MACMC,EADUzF,GAAW5P,GAExBiH,MAAM,SACNC,KAAKoO,GAAS,MAAMA,EAAK9H,QAAQ,MAAO,gBACxCpG,KAAK,IACR9G,EAAO0E,UAAUtH,WAAW2X,GAAa,cAC3C,CACF,CClBO,MAAME,GACX,WAAAlhB,CAAYiM,GACVhM,KAAKgM,OAASA,EACdhM,KAAKkhB,aAAelhB,KAAKkhB,aAAahP,KAAKlS,MAC3CA,KAAKmhB,YAAcnhB,KAAKmhB,YAAYjP,KAAKlS,MACzCA,KAAKohB,WAAaphB,KAAKohB,WAAWlP,KAAKlS,KACzC,CAEA,IAAAgP,GACEhP,KAAKgM,OAAOnI,QAAQsO,iBAAiB,QAASnS,KAAKkhB,cACnDlhB,KAAKgM,OAAOnI,QAAQsO,iBAAiB,OAAQnS,KAAKmhB,aAClDnhB,KAAKgM,OAAOnI,QAAQsO,iBAAiB,MAAOnS,KAAKohB,WACnD,CAEA,OAAA5R,GACExP,KAAKgM,OAAOnI,QAAQuO,oBAAoB,QAASpS,KAAKkhB,cACtDlhB,KAAKgM,OAAOnI,QAAQuO,oBAAoB,OAAQpS,KAAKmhB,aACrDnhB,KAAKgM,OAAOnI,QAAQuO,oBAAoB,MAAOpS,KAAKohB,WACtD,CAEA,YAAAF,CAAalO,GACXA,EAAEO,iBACFvT,KAAKgM,OAAOc,QAAQC,WAEpB,MAAMsU,EAAgBrO,EAAEqO,eAAiBzb,OAAOyb,cAChD,IAAIhY,EAAOgY,EAAcC,QAAQ,aACjC,MAAM5V,EAAO2V,EAAcC,QAAQ,cAG7BC,EAAQpU,MAAMC,KAAKiU,EAAcE,OAAS,IAC1CC,EAAYD,EAAME,MAAMC,GAAMA,EAAE5R,KAAKqI,WAAW,YACtD,GAAIqJ,EAEF,YADAxhB,KAAK2hB,kBAAkBH,GAKzB,MAAMI,EAAkBL,EAAMjC,QAAQoC,IAAOA,EAAE5R,KAAKqI,WAAW,WAAauE,GAAiBgF,KAC7F,GAAIE,EAAgBpa,OAAS,EAAG,CAE9B,MAAMqa,EAAaD,EAAgBtC,QAAQoC,IAAO1hB,KAAK8hB,oBAAoBJ,KAC3E,IAAIK,EAAQ3b,QAAQC,UACpB,IAAA,MAAWwV,KAAQgG,EACjBE,EAAQA,EAAMzb,MAAK,IACjBwW,GAAgBjB,GACbvV,MAAM+C,IACL,MAAM2Y,EAAYhiB,KAAKgM,OAAOhC,UAAUR,SAASH,GACjDrJ,KAAKgM,OAAO0E,UAAUtH,WAAW4Y,GACjChiB,KAAKgM,OAAOiB,SAASzL,KAAK,iBAAgB,IAE3CygB,OAAOtgB,IACsD,MAIpE,MACF,CAGA,MAAMugB,EAAgBX,EAAMjC,QAAQoC,IAAOA,EAAE5R,KAAKqI,WAAW,YAC7D,GAAI+J,EAAc1a,OAAS,GAAKxH,KAAKgM,OAAOxL,QAAQ2hB,cAAe,CAEjE,IAAIJ,EAAQ3b,QAAQC,UACpB,IAAA,MAAWwV,KAAQqG,EACjBH,EAAQA,EAAMzb,MAAK,IACjBtG,KAAKgM,OAAOxL,QAAQ2hB,cAActG,GAAMvV,MAAM+P,IAC5CrW,KAAKgM,OAAOyH,SAASpH,QAAQ,mBAAoB,CAC/CgK,MACAoF,SAAUI,EAAK1P,KACfiW,SAAUvG,EAAKza,MAChB,IACA6gB,OAAOtgB,IAER3B,KAAKgM,OAAOiB,SAASzL,KAAK,eAAgB,CAAEqa,OAAMja,MAAOD,GAAK,MAIpE,MACF,CAGA,MAAM0gB,EAAYriB,KAAKsiB,gBACvB,GAAID,EAAW,CACb,MAAMvN,EAAQuN,EAAUE,QAAQ,SAChC,GAAIzN,EAAO,CAET,GAAIpJ,GAAQ1L,KAAKwiB,cAAc9W,GAI7B,OAHA1L,KAAKyiB,gBAAgB3N,EAAOuN,EAAW3W,GACvC1L,KAAKgM,OAAOiB,SAASzL,KAAK,QAAS,CAAE6H,OAAMqC,cAC3C1L,KAAKgM,OAAOiB,SAASzL,KAAK,kBAI5B,GAAI6H,GAAQ,UAAU+B,KAAK/B,GAAO,CAChC,MAAM4P,EAAUD,EAAgB3P,GAC1BqZ,EAAc1iB,KAAK2iB,gBAAgB1J,GACzC,GAAIyJ,EAIF,OAHA1iB,KAAKyiB,gBAAgB3N,EAAOuN,EAAWK,GACvC1iB,KAAKgM,OAAOiB,SAASzL,KAAK,QAAS,CAAE6H,OAAMqC,cAC3C1L,KAAKgM,OAAOiB,SAASzL,KAAK,iBAG9B,CACF,CACF,CAEI6H,GAEFA,EAAO2P,EAAgB3P,GACvBA,EAAOrJ,KAAKgM,OAAOhC,UAAUR,SAASH,GACtCrJ,KAAKgM,OAAO0E,UAAUtH,WAAWC,IACxBqC,GACT1L,KAAK4iB,iBAAiBlX,GAGxB1L,KAAKgM,OAAOiB,SAASzL,KAAK,QAAS,CAAE6H,OAAMqC,SAC3C1L,KAAKgM,OAAOiB,SAASzL,KAAK,iBAC5B,CAMA,gBAAAohB,CAAiBlX,GACfkV,GAAgB5gB,KAAKgM,OAAQN,EAC/B,CAEA,WAAAyV,CAAYnO,GAEV,MAAMlN,EAAMF,OAAOD,eACnB,IAAKG,GAA0B,IAAnBA,EAAIC,WAAkB,OAElC,MAAM8c,EAAS/c,EAAIkF,YAAY5I,WAAaC,KAAKC,UAC7CwD,EAAIkF,WAAWzI,cAAgBuD,EAAIkF,WACjC8X,EAAQhd,EAAIid,WAAW3gB,WAAaC,KAAKC,UAC3CwD,EAAIid,UAAUxgB,cAAgBuD,EAAIid,UAEhCC,EAAcH,GAAQN,UAAU,mBAChCU,EAAaH,GAAOP,UAAU,mBAEpC,GAAIS,GAAeC,GAAcD,IAAgBC,EAAY,CAE3D,MACMC,EADQF,EACKvJ,iBAAiB,sBAC9B0J,EAAW,GACjB,IAAIC,EAAW,GAEf,IAAA,MAAWC,KAAOH,EAAM,CACtB,GAAIG,EAAI5D,UAAU/Y,SAAS,kBAAmB,SAC9C,MAAM4c,EAAQD,EAAI5J,iBAAiB,UAC7B8J,EAAW,GACjB,IAAIC,EAAY,GAChB,IAAA,MAAWC,KAAQH,EAAO,CACxB,MAAM5X,EAAO+X,EAAK/e,YAAYC,OAE1B+G,EAAK4H,SAAS,OAAS5H,EAAK4H,SAAS,OAAS5H,EAAK4H,SAAS,KAC9DiQ,EAAS5S,KAAK,IAAMjF,EAAKwN,QAAQ,KAAM,MAAQ,KAE/CqK,EAAS5S,KAAKjF,GAEhB,MAAM9I,EAAM6gB,EAAKjhB,QAAQ2I,cACzBqY,GAAa,IAAI5gB,KAAO6gB,EAAKzc,cAAcpE,IAC7C,CACAugB,EAASxS,KAAK4S,EAASzQ,KAAK,OAC5BsQ,GAAY,OAAOI,QACrB,CAEA,MAAME,EAAMP,EAASrQ,KAAK,MACpBzJ,EAAO,iBAAiB+Z,oBAE9BpQ,EAAEO,iBACFP,EAAEqO,cAAcsC,QAAQ,aAAcD,GACtC1Q,EAAEqO,cAAcsC,QAAQ,YAAata,EACvC,CAEF,CAEA,UAAA+X,GAEEphB,KAAKgM,OAAOc,QAAQC,WAEpB4C,YAAW,KACT3P,KAAKgM,OAAOiB,SAASzL,KAAK,iBAAgB,GACzC,EACL,CAOA,mBAAAsgB,CAAoBjG,GAClB,OAAOmC,GAAmBnC,EAAM7b,KAAKgM,OAAOxL,QAAQojB,YAAa,CAAE3W,SAAUjN,KAAKgM,OAAOiB,UAC3F,CAEA,aAAAqV,GACE,MAAMxc,EAAMF,OAAOD,eACnB,IAAKG,IAAQA,EAAIkF,WAAY,OAAO,KACpC,MAAM/I,EAAO6D,EAAIkF,WAAW5I,WAAaC,KAAKC,UAC1CwD,EAAIkF,WAAWzI,cAAgBuD,EAAIkF,WACvC,OAAO/I,GAAMsgB,UAAU,WAAa,IACtC,CAEA,aAAAC,CAAc9W,GACZ,IAAKA,EAAK4H,SAAS,MAAO,OAAO,EACjC,MAAM2H,EAAQvP,EAAK/G,OAAOgO,MAAM,MAChC,QAAIsI,EAAMzT,OAAS,IAEZyT,EAAM,GAAG3H,SAAS,KAC3B,CAEA,eAAAqP,CAAgBtZ,GACd,MAEMyL,GAFS,IAAIqC,WACAK,gBAAgBnO,EAAM,aACvBzE,cAAc,SAChC,IAAKkQ,EAAO,OAAO,KACnB,MAAMmG,EAAQ,GACRiI,EAAOpO,EAAM2E,iBAAiB,MACpC,IAAA,MAAW4J,KAAOH,EAAM,CACtB,MAAMI,EAAQD,EAAI5J,iBAAiB,UACnCwB,EAAMtK,KAAKxD,MAAMC,KAAKkW,GAAO1Q,KAAIiR,GAAKA,EAAEnf,YAAYC,SAAQmO,KAAK,MACnE,CACA,OAAOmI,EAAMnI,KAAK,KACpB,CAEA,eAAA2P,CAAgB3N,EAAOgP,EAAWJ,GAChC,MAAM1O,EAAQF,EAAMlQ,cAAc,UAAYkQ,EACxCoO,EAAOQ,EAAI/e,OAAOgO,MAAM,MAAMC,KAAIwI,GAAQA,EAAKzI,MAAM,QACrDoR,EAAU5W,MAAMC,KAAK0H,EAAM2E,iBAAiB,aAC5CuK,EAAWF,EAAUvB,QAAQ,MACnC,IAAI0B,EAAcF,EAAQG,QAAQF,GAC9BC,EAAc,IAAGA,EAAc,GACnC,IAAIE,EAAc,EACdC,EAAON,EAAUO,uBACrB,KAAOD,GACLD,GAAeC,EAAKE,SAAW,EAC/BF,EAAOA,EAAKC,uBAGd,IAAA,IAASE,EAAI,EAAGA,EAAIrB,EAAK1b,OAAQ+c,IAAK,CACpC,MAAMC,EAASP,EAAcM,EAE7B,KAAOR,EAAQvc,QAAUgd,GAAQ,CAC/B,MAAMC,EAAWV,EAAQ,IAAMC,EACzBU,EAAWD,EAAWA,EAASnB,MAAM9b,OAAS0b,EAAK,GAAG1b,OACtDmd,EAAS1hB,SAASC,cAAc,MACtC,IAAA,IAAS2gB,EAAI,EAAGA,EAAIa,EAAUb,IAAK,CACjC,MAAM1O,EAAKlS,SAASC,cAAc,MAClCiS,EAAGnO,UAAY,OACf2d,EAAOjhB,YAAYyR,EACrB,CACAH,EAAMtR,YAAYihB,GAClBZ,EAAQpT,KAAKgU,EACf,CAEA,MAAM1P,EAAK8O,EAAQS,GACnB,IAAA,IAASX,EAAI,EAAGA,EAAIX,EAAKqB,GAAG/c,OAAQqc,IAAK,CACvC,MAAMe,EAAST,EAAcN,EAE7B,KAAO5O,EAAGqO,MAAM9b,QAAUod,GAAQ,CAChC,MAAMzP,EAAKlS,SAASC,cAAc,MAClCiS,EAAGnO,UAAY,OACfiO,EAAGvR,YAAYyR,GAEf4O,EAAQriB,SAAQ,CAACmjB,EAAUC,KACzB,GAAIA,IAAQN,GAAUK,EAASvB,MAAM9b,QAAUod,EAAQ,CACrD,MAAMG,EAAU9hB,SAASC,cAAc,MACvC6hB,EAAQ/d,UAAY,OACpB6d,EAASnhB,YAAYqhB,EACvB,IAEJ,CACA,MAAMtB,EAAOxO,EAAGqO,MAAMsB,GAClBnB,IACFA,EAAK/e,YAAcwe,EAAKqB,GAAGV,GAE/B,CACF,CACF,CAEA,iBAAAlC,CAAkB9F,GAChB,IAAI7b,KAAK8hB,oBAAoBjG,GAE7B,GAAI7b,KAAKgM,OAAOxL,QAAQ2hB,cACtBniB,KAAKgM,OAAOxL,QAAQ2hB,cAActG,GAAMvV,MAAM+P,IAC5CrW,KAAKgM,OAAOyH,SAASpH,QAAQ,cAAe,CAAE8J,IAAKE,EAAKxJ,IAAKgP,EAAK1P,MAAM,IACvE8V,OAAOtgB,IAER3B,KAAKgM,OAAOiB,SAASzL,KAAK,eAAgB,CAAEqa,OAAMja,MAAOD,GAAK,QAE3D,CACL,MAAMoa,EAAS,IAAIC,WACnBD,EAAOiJ,WAAchS,IACfA,EAAEiS,kBACJjlB,KAAKgM,OAAOiB,SAASzL,KAAK,kBAAmB,CAC3C0jB,OAAQlS,EAAEkS,OACVC,MAAOnS,EAAEmS,MACTC,QAAShhB,KAAKihB,MAAOrS,EAAEkS,OAASlS,EAAEmS,MAAS,MAE/C,EAEFpJ,EAAOE,OAAUjJ,IACf,MAAMmD,EAAMnD,EAAErH,OAAOqB,OACrBhN,KAAKgM,OAAOyH,SAASpH,QAAQ,cAAe,CAAE8J,MAAKtJ,IAAKgP,EAAK1P,MAAM,EAErE4P,EAAOuJ,cAAczJ,EACvB,CACF,EClTF,MAAM0J,GAAa,8BAGbC,sBAAuB7kB,IAAI,CAC/B,IAAK,KAAM,KAAM,KAAM,KAAM,KAAM,KACnC,aAAc,MAAO,KAAM,KAAM,QAAS,KAAM,QAI5C8kB,sBAA2B9kB,IAAI,CAAC,KAAM,KAAM,QAAS,UAOrD+kB,sBAAsBC,QAMtBC,sBAAuBjlB,IAEtB,MAAMklB,GACX,WAAA9lB,CAAYiM,GACVhM,KAAKgM,OAASA,EAGdhM,KAAK8lB,YAAc,KACnB9lB,KAAK+lB,kBAAoB,KACzB/lB,KAAKgmB,YAAc,KACnBhmB,KAAKimB,cAAgB,KACrBjmB,KAAKkmB,SAAW,KAChBlmB,KAAKmmB,eAAiB,KACtBnmB,KAAKomB,iBAAkB,EACvBpmB,KAAKqmB,YAAc,EAEnBrmB,KAAKsmB,YAAc,KACnBtmB,KAAKumB,eAAiB,KAEtBvmB,KAAKwmB,gBAAkBxmB,KAAKwmB,gBAAgBtU,KAAKlS,MACjDA,KAAKymB,YAAczmB,KAAKymB,YAAYvU,KAAKlS,MACzCA,KAAK0mB,iBAAmB1mB,KAAK0mB,iBAAiBxU,KAAKlS,MACnDA,KAAK2mB,iBAAmB3mB,KAAK2mB,iBAAiBzU,KAAKlS,MACnDA,KAAK4mB,iBAAmB5mB,KAAK4mB,iBAAiB1U,KAAKlS,MACnDA,KAAK6mB,eAAiB7mB,KAAK6mB,eAAe3U,KAAKlS,KACjD,CAEA,IAAAgP,GACE,MAAM7M,EAAKnC,KAAKgM,OAAOnI,QACvB1B,EAAGgQ,iBAAiB,WAAYnS,KAAKwmB,iBACrCrkB,EAAGgQ,iBAAiB,OAAQnS,KAAKymB,aACjCtkB,EAAGgQ,iBAAiB,YAAanS,KAAK0mB,kBACtCvkB,EAAGgQ,iBAAiB,YAAanS,KAAK2mB,kBACtCxkB,EAAGgQ,iBAAiB,YAAanS,KAAK4mB,kBACtCzkB,EAAGgQ,iBAAiB,UAAWnS,KAAK6mB,gBACpCnB,GAAgBhlB,IAAIyB,EAAInC,MACxB4lB,GAAiB3kB,IAAIjB,KACvB,CAEA,OAAAwP,GACE,MAAMrN,EAAKnC,KAAKgM,OAAOnI,QACvB1B,EAAGiQ,oBAAoB,WAAYpS,KAAKwmB,iBACxCrkB,EAAGiQ,oBAAoB,OAAQpS,KAAKymB,aACpCtkB,EAAGiQ,oBAAoB,YAAapS,KAAK0mB,kBACzCvkB,EAAGiQ,oBAAoB,YAAapS,KAAK2mB,kBACzCxkB,EAAGiQ,oBAAoB,YAAapS,KAAK4mB,kBACzCzkB,EAAGiQ,oBAAoB,UAAWpS,KAAK6mB,gBACvCnB,GAAgBvkB,OAAOgB,GACvByjB,GAAiBzkB,OAAOnB,MACxBA,KAAK8mB,cACP,CAUA,iBAAAC,CAAkB9kB,GAChB,OAAOjC,KAAKgnB,uBAAuB/kB,EACrC,CAOA,kBAAAglB,CAAmBxiB,GACbA,IAAUA,EAAM6T,aAAa,cAC/B7T,EAAMnB,aAAa,YAAa,OAEpC,CAMA,oBAAA4jB,CAAqBziB,GACfA,GACFA,EAAM2T,gBAAgB,YAE1B,CAMA,UAAA+O,GACE,QAASnnB,KAAK8lB,WAChB,CAMA,YAAAsB,GACE,MAAO,CACLD,aAAcnnB,KAAK8lB,aAAe9lB,KAAKomB,gBACvCiB,eAAgBrnB,KAAKomB,gBACrBkB,WAAYtnB,KAAKgmB,YACjBuB,aAAcvnB,KAAKimB,cAEvB,CAIA,gBAAAW,CAAiB5T,GAEf,MACMvO,EADczE,KAAKwnB,oBAAoBxU,EAAErH,SAClB3L,KAAKgnB,uBAAuBhU,EAAErH,QAC3D,IAAKlH,EAAO,OAEZzE,KAAK8lB,YAAcrhB,EACnBzE,KAAK+lB,kBAAoB/lB,KAGzB,MAAMqJ,EAAO5E,EAAMgjB,UACb/b,EAAOjH,EAAMC,YAGnBsO,EAAE0U,aAAa/D,QAAQ4B,GAAYlc,GACnC2J,EAAE0U,aAAa/D,QAAQ,YAAata,GACpC2J,EAAE0U,aAAa/D,QAAQ,aAAcjY,GACrCsH,EAAE0U,aAAaC,cAAgB,OAG/B3nB,KAAK4nB,oBAAoBnjB,EAAOuO,GAGhChT,KAAK6nB,mBAGLpjB,EAAMgb,UAAUxe,IAAI,sBAGpB,IAAA,MAAW6mB,KAAYlC,GACrBkC,EAAS/B,kBAAoB/lB,KAC7B8nB,EAAShC,YAAcrhB,EAGzBzE,KAAKgM,OAAOiB,SAASzL,KAAK,aAAc,CAAEiD,SAC5C,CAEA,cAAAoiB,CAAe7T,GAEThT,KAAK8lB,aACP9lB,KAAK8lB,YAAYrG,UAAUsI,OAAO,sBAIpC,IAAA,MAAWD,KAAYlC,GACrBkC,EAAShB,eAGX9mB,KAAKgM,OAAOiB,SAASzL,KAAK,WAC5B,CAIA,eAAAglB,CAAgBxT,GACdA,EAAEO,iBAGEvT,KAAK8lB,YACP9S,EAAE0U,aAAaM,WAAa,OAE5BhV,EAAE0U,aAAaM,WAAa,QAI1BhoB,KAAK8lB,aAAe9lB,KAAKomB,kBAC3BpmB,KAAKioB,kBAAkBjV,EAE3B,CAIA,gBAAA0T,CAAiB1T,GACfA,EAAEO,iBACFvT,KAAKqmB,cAEoB,IAArBrmB,KAAKqmB,cAEFrmB,KAAK8lB,cACR9lB,KAAKomB,iBAAkB,GAEzBpmB,KAAKgM,OAAOnI,QAAQ4b,UAAUxe,IAAI,iBAClCjB,KAAKgM,OAAOiB,SAASzL,KAAK,aAAc,CACtC0mB,WAAYloB,KAAKomB,gBACjB+B,MAAOhb,MAAMC,KAAK4F,EAAE0U,aAAaS,OAAS,MAGhD,CAEA,gBAAAxB,CAAiB3T,GACfhT,KAAKqmB,cAEDrmB,KAAKqmB,aAAe,IACtBrmB,KAAKqmB,YAAc,EACnBrmB,KAAKgM,OAAOnI,QAAQ4b,UAAUsI,OAAO,iBACrC/nB,KAAKooB,uBACLpoB,KAAKgmB,YAAc,KACnBhmB,KAAKimB,cAAgB,KAEjBjmB,KAAKomB,kBAAoBpmB,KAAK8lB,cAChC9lB,KAAKomB,iBAAkB,GAGzBpmB,KAAKgM,OAAOiB,SAASzL,KAAK,cAE9B,CAIA,WAAAilB,CAAYzT,GAOV,GANAA,EAAEO,iBACFvT,KAAKqmB,YAAc,EACnBrmB,KAAKgM,OAAOnI,QAAQ4b,UAAUsI,OAAO,iBACrC/nB,KAAKooB,uBAGDpoB,KAAK8lB,aAAe9lB,KAAKgmB,YAG3B,OAFAhmB,KAAKqoB,iBAAiBrV,QACtBhT,KAAK8mB,eAKP,MAAMvF,EAAQpU,MAAMC,KAAK4F,EAAE0U,aAAanG,OAClC+G,EAAa/G,EAAMjC,QAAQoC,GAAMA,EAAE5R,KAAKqI,WAAW,YAEzD,GAAImQ,EAAW9gB,OAAS,EAGtB,OAFAxH,KAAKuoB,iBAAiBvV,EAAGsV,QACzBtoB,KAAK8mB,eAIP,MAAMlF,EAAkBL,EAAMjC,QAAQoC,IAAOA,EAAE5R,KAAKqI,WAAW,WAAauE,GAAiBgF,KAC7F,GAAIE,EAAgBpa,OAAS,EAG3B,OAFAxH,KAAKwoB,oBAAoBxV,EAAG4O,QAC5B5hB,KAAK8mB,eAIP,MAAM5E,EAAgBX,EAAMjC,QAAQoC,IAAOA,EAAE5R,KAAKqI,WAAW,YAC7D,GAAI+J,EAAc1a,OAAS,GAAKxH,KAAKgM,OAAOxL,QAAQ2hB,cAGlD,OAFAniB,KAAKyoB,gBAAgBzV,EAAGkP,QACxBliB,KAAK8mB,eAKP,MAAM4B,EAAe1V,EAAE0U,aAAapG,QAAQiE,IAC5C,GAAImD,IAAiB1oB,KAAK8lB,YAGxB,OAFA9lB,KAAK2oB,uBAAuB3V,EAAG0V,QAC/B1oB,KAAK8mB,eAKP9mB,KAAK4oB,sBAAsB5V,GAC3BhT,KAAKgM,OAAOc,QAAQC,WAEpB,MAAM1D,EAAO2J,EAAE0U,aAAapG,QAAQ,aACpC,GAAIjY,EAAM,CACR,IAAI4P,EAAUD,EAAgB3P,GAC9B4P,EAAUjZ,KAAKgM,OAAOhC,UAAUR,SAASyP,GACzCjZ,KAAKgM,OAAO0E,UAAUtH,WAAW6P,GACjCjZ,KAAKgM,OAAOiB,SAASzL,KAAK,iBAC5B,KAAO,CACL,MAAMkK,EAAOsH,EAAE0U,aAAapG,QAAQ,cAChC5V,IACF1L,KAAK6oB,gBAAgBnd,GACrB1L,KAAKgM,OAAOiB,SAASzL,KAAK,kBAE9B,CAEAxB,KAAKgM,OAAOiB,SAASzL,KAAK,OAAQ,CAAE+f,QAAOlY,SAC3CrJ,KAAK8mB,cACP,CAIA,gBAAAuB,CAAiBrV,GACf,MAAM8V,EAAS9oB,KAAK8lB,YACdna,EAAS3L,KAAKgmB,YACd+C,EAAW/oB,KAAKimB,cAChB+C,EAAehpB,KAAK+lB,kBAE1B,IAAK+C,IAAWnd,IAAWod,EAAU,OACrC,GAAID,IAAWnd,EAAQ,OAGvB3L,KAAKgM,OAAOc,QAAQC,WAChBic,GAAgBA,IAAiBhpB,MACnCgpB,EAAahd,OAAOc,QAAQC,WAI9B,MAAMkc,EAAoBjpB,KAAKwnB,oBAAoBsB,GAC7CI,EAAoBlpB,KAAKwnB,oBAAoB7b,GAEnD,GAAIsd,GAAqBC,GACrBD,EAAkB1mB,gBAAkB2mB,EAAkB3mB,cAEvC,WAAbwmB,EACFG,EAAkB3mB,cAAc0B,aAAaglB,EAAmBC,GAEhEA,EAAkB3mB,cAAc0B,aAAaglB,EAAmBC,EAAkBC,iBAE/E,CAEL,MAAMC,EAAkBzd,EAAOpJ,cAC/B,IAAK6mB,EAAiB,OAEL,WAAbL,EACFK,EAAgBnlB,aAAa6kB,EAAQnd,GAErCyd,EAAgBnlB,aAAa6kB,EAAQnd,EAAOwd,YAEhD,CAGIH,GAAgBA,IAAiBhpB,MACnCgpB,EAAahd,OAAOiB,SAASzL,KAAK,kBAGpCxB,KAAKgM,OAAOiB,SAASzL,KAAK,kBAC1BxB,KAAKgM,OAAOiB,SAASzL,KAAK,eAAgB,CAAEsnB,SAAQnd,SAAQod,YAC9D,CAKA,sBAAAJ,CAAuB3V,EAAG3J,GACxBrJ,KAAK4oB,sBAAsB5V,GAC3BhT,KAAKgM,OAAOc,QAAQC,WAEpB,MAAMiV,EAAYhiB,KAAKgM,OAAOhC,UAAUR,SAASH,GACjDrJ,KAAKgM,OAAO0E,UAAUtH,WAAW4Y,GACjChiB,KAAKgM,OAAOiB,SAASzL,KAAK,kBAC1BxB,KAAKgM,OAAOiB,SAASzL,KAAK,oBAAqB,CAAE6H,KAAM2Y,GACzD,CAOA,gBAAA6F,GACE7nB,KAAKsmB,+BAAkBpmB,IACvB,MAAMmpB,EAASrpB,KAAKspB,qBACpB,IAAA,MAAW7kB,KAAS4kB,EAClBrpB,KAAKsmB,YAAY5lB,IAAI+D,EAAOA,EAAMqH,yBAGpC9L,KAAKumB,eAAiB,KAAQvmB,KAAKsmB,YAAc,IAAA,EACjDtmB,KAAKgM,OAAOnI,QAAQsO,iBAAiB,SAAUnS,KAAKumB,eAAgB,CAAEgD,SAAS,GACjF,CAOA,aAAAC,CAAc/kB,GACZ,OAAIzE,KAAKsmB,aAAetmB,KAAKsmB,YAAY7lB,IAAIgE,GACpCzE,KAAKsmB,YAAYvlB,IAAI0D,GAEvBA,EAAMqH,uBACf,CASA,iBAAAmc,CAAkBjV,GACChT,KAAKgM,OAAOnI,QAG7B,MAAMolB,EAAoBjpB,KAAK8lB,YAAc9lB,KAAKwnB,oBAAoBxnB,KAAK8lB,aAAe,KACpFuD,EAASJ,GAAqBA,EAAkB1mB,cAClD4K,MAAMC,KAAK6b,EAAkB1mB,cAAcknB,UAAUnK,QACnDuE,GAAKA,EAAErhB,UAAYymB,EAAkBzmB,UAEvCxC,KAAKspB,qBAET,GAAsB,IAAlBD,EAAO7hB,OAIT,OAHAxH,KAAKgmB,YAAc,KACnBhmB,KAAKimB,cAAgB,UACrBjmB,KAAKooB,uBAIP,MAAMsB,EAAS1W,EAAE2W,QACjB,IAAIlnB,EAAe,KACfmnB,EAAkB,KAClBC,EAAkBC,IAEtB,IAAA,MAAWrlB,KAAS4kB,EAAQ,CAE1B,GAAI5kB,IAAUzE,KAAK8lB,YAAa,SAEhC,MAAMiE,EAAO/pB,KAAKwpB,cAAc/kB,GAC1BulB,EAAOD,EAAKE,IAAMF,EAAKG,OAAS,EAEhCC,EAAU/lB,KAAKgmB,IAAIV,EAASK,EAAKE,KACjCI,EAAajmB,KAAKgmB,IAAIV,EAASK,EAAKO,QAc1C,GAZIH,EAAUN,IACZA,EAAkBM,EAClB1nB,EAAegC,EACfmlB,EAAkB,UAEhBS,EAAaR,IACfA,EAAkBQ,EAClB5nB,EAAegC,EACfmlB,EAAkB,SAIhBF,GAAUK,EAAKE,KAAOP,GAAUK,EAAKO,OAAQ,CAC/C7nB,EAAegC,EACfmlB,EAAkBF,EAASM,EAAO,SAAW,QAC7CH,EAAkB,EAClB,KACF,CACF,CAEIpnB,IAAiBzC,KAAKgmB,aAAe4D,IAAoB5pB,KAAKimB,gBAChEjmB,KAAKgmB,YAAcvjB,EACnBzC,KAAKimB,cAAgB2D,EAEjBnnB,EACFzC,KAAKuqB,mBAAmB9nB,EAAcmnB,GAEtC5pB,KAAKooB,uBAGX,CAMA,kBAAAkB,GACE,MAAMD,EAAS,GACTlnB,EAAKnC,KAAKgM,OAAOnI,QACvB,IAAA,IAAS4J,EAAI,EAAGA,EAAItL,EAAGsnB,SAASjiB,OAAQiG,IAAK,CAC3C,MAAMqK,EAAQ3V,EAAGsnB,SAAShc,GACtBqK,EAAM1V,WAAaC,KAAK4I,cACvB6M,EAAM2H,UAAU/Y,SAAS,uBACzBoR,EAAM2H,UAAU/Y,SAAS,mBAC5B2iB,EAAO1Y,KAAKmH,EAEhB,CACA,OAAOuR,CACT,CAIA,kBAAAkB,CAAmBC,EAAazB,GACzB/oB,KAAKmmB,iBACRnmB,KAAKmmB,eAAiBljB,SAASC,cAAc,OAC7ClD,KAAKmmB,eAAesE,UAAY,qBAChCzqB,KAAKmmB,eAAe7iB,aAAa,cAAe,SAGlD,MAAMonB,EAAW1qB,KAAKgM,OAAOnI,QACvB8mB,EAAaD,EAAS5e,wBACtB8e,EAAYJ,EAAY1e,wBAGxBme,EAAmB,WAAblB,EACR6B,EAAUX,IAAMU,EAAWV,IAAMS,EAASG,UAC1CD,EAAUN,OAASK,EAAWV,IAAMS,EAASG,UAEjD7qB,KAAKmmB,eAAe7a,MAAM2e,IAAM,GAAGA,MAE9BjqB,KAAKmmB,eAAe5jB,eACvBmoB,EAAShnB,YAAY1D,KAAKmmB,gBAG5BnmB,KAAKgM,OAAOiB,SAASzL,KAAK,iBAAkB,CAC1CmK,OAAQ6e,EACRzB,WACAkB,OAEJ,CAEA,oBAAA7B,GACMpoB,KAAKmmB,gBAAkBnmB,KAAKmmB,eAAe5jB,eAC7CvC,KAAKmmB,eAAe5jB,cAAc2B,YAAYlE,KAAKmmB,gBAErDnmB,KAAKmmB,eAAiB,IACxB,CAIA,mBAAAyB,CAAoBnjB,EAAOuO,GACzB,MAAM8X,EAAQrmB,EAAMsmB,WAAU,GAC9BD,EAAML,UAAY,iBAClBK,EAAMxf,MAAMyd,SAAW,WACvB+B,EAAMxf,MAAM2e,IAAM,UAClBa,EAAMxf,MAAM0f,KAAO,UACnBF,EAAMxf,MAAM2f,MAAQ,GAAGxmB,EAAMymB,gBAC7BJ,EAAMxf,MAAM6f,cAAgB,OAE5BloB,SAASyU,KAAKhU,YAAYonB,GAC1B9qB,KAAKkmB,SAAW4E,EAGhB,MAAMf,EAAOtlB,EAAMqH,wBACbsf,EAAUpY,EAAEqY,QAAUtB,EAAKiB,KAC3BM,EAAUtY,EAAE2W,QAAUI,EAAKE,IACjCjX,EAAE0U,aAAa6D,aAAaT,EAAOM,EAASE,EAK9C,CAEA,mBAAAE,GACMxrB,KAAKkmB,UAAYlmB,KAAKkmB,SAAS3jB,eACjCvC,KAAKkmB,SAAS3jB,cAAc2B,YAAYlE,KAAKkmB,UAE/ClmB,KAAKkmB,SAAW,IAClB,CASA,sBAAAc,CAAuB/kB,GACrB,IAAIE,EAAKF,EAAKG,WAAaC,KAAKC,UAAYL,EAAKM,cAAgBN,EACjE,MAAMyoB,EAAW1qB,KAAKgM,OAAOnI,QAC7B,KAAO1B,GAAMA,IAAOuoB,GAAU,CAC5B,GAAIvoB,EAAGI,gBAAkBmoB,GAAYlF,GAAiB/kB,IAAI0B,EAAGK,SAC3D,OAAOL,EAETA,EAAKA,EAAGI,aACV,CACA,OAAO,IACT,CAOA,mBAAAilB,CAAoBvlB,GAClB,IAAIE,EAAKF,EAAKG,WAAaC,KAAKC,UAAYL,EAAKM,cAAgBN,EACjE,MAAMyoB,EAAW1qB,KAAKgM,OAAOnI,QAC7B,KAAO1B,GAAMA,IAAOuoB,GAAU,CAC5B,IAAmB,OAAfvoB,EAAGK,SAAmC,OAAfL,EAAGK,UAExBL,EAAGI,eAAiBkjB,GAAqBhlB,IAAI0B,EAAGI,cAAcC,SAChE,OAAOL,EAGXA,EAAKA,EAAGI,aACV,CACA,OAAO,IACT,CAKA,YAAAukB,GACM9mB,KAAK8lB,aACP9lB,KAAK8lB,YAAYrG,UAAUsI,OAAO,sBAEpC/nB,KAAK8lB,YAAc,KACnB9lB,KAAK+lB,kBAAoB,KACzB/lB,KAAKgmB,YAAc,KACnBhmB,KAAKimB,cAAgB,KACrBjmB,KAAKomB,iBAAkB,EACvBpmB,KAAKqmB,YAAc,EAEnBrmB,KAAKsmB,YAAc,KACftmB,KAAKumB,iBACPvmB,KAAKgM,OAAOnI,QAAQuO,oBAAoB,SAAUpS,KAAKumB,gBACvDvmB,KAAKumB,eAAiB,MAExBvmB,KAAKooB,uBACLpoB,KAAKwrB,sBACLxrB,KAAKgM,OAAOnI,QAAQ4b,UAAUsI,OAAO,gBACvC,CAOA,mBAAAjG,CAAoBjG,GAClB,OAAOmC,GAAmBnC,EAAM7b,KAAKgM,OAAOxL,QAAQojB,YAAa,CAAE3W,SAAUjN,KAAKgM,OAAOiB,UAC3F,CAEA,gBAAAsb,CAAiBvV,EAAGsV,GAClBtoB,KAAK4oB,sBAAsB5V,GAC3BhT,KAAKgM,OAAOc,QAAQC,WAEpB,MAAM8U,EAAayG,EAAWhJ,QAAQoC,IAAO1hB,KAAK8hB,oBAAoBJ,KAEtE,IAAIK,EAAQ3b,QAAQC,UACpB,IAAA,MAAWwV,KAAQgG,EACjBE,EAAQA,EAAMzb,MAAK,IACbtG,KAAKgM,OAAOxL,QAAQ2hB,cACfniB,KAAKgM,OAAOxL,QAAQ2hB,cAActG,GAAMvV,MAAM+P,IACnDrW,KAAKgM,OAAOyH,SAASpH,QAAQ,cAAe,CAAE8J,IAAKE,EAAKxJ,IAAKgP,EAAK1P,MAAM,IACvE8V,OAAOtgB,IAER3B,KAAKgM,OAAOiB,SAASzL,KAAK,eAAgB,CAAEqa,OAAMja,MAAOD,GAAK,IAGzD,IAAIyE,SAASC,IAClB,MAAM0V,EAAS,IAAIC,WACnBD,EAAOiJ,WAAcyG,IACfA,EAAGxG,kBACLjlB,KAAKgM,OAAOiB,SAASzL,KAAK,kBAAmB,CAC3C0jB,OAAQuG,EAAGvG,OACXC,MAAOsG,EAAGtG,MACVC,QAAShhB,KAAKihB,MAAOoG,EAAGvG,OAASuG,EAAGtG,MAAS,MAEjD,EAEFpJ,EAAOE,OAAUwP,IACfzrB,KAAKgM,OAAOyH,SAASpH,QAAQ,cAAe,CAAE8J,IAAKsV,EAAG9f,OAAOqB,OAAQH,IAAKgP,EAAK1P,OAC/E9F,GAAO,EAET0V,EAAOG,QAAU,IAAM7V,IACvB0V,EAAOuJ,cAAczJ,EAAI,KAKnC,CAEA,mBAAA2M,CAAoBxV,EAAGuO,GACrBvhB,KAAK4oB,sBAAsB5V,GAC3BhT,KAAKgM,OAAOc,QAAQC,WAEpBwU,EAAMjC,QAAQoC,IAAO1hB,KAAK8hB,oBAAoBJ,KAAIhgB,SAASma,IACzDiB,GAAgBjB,GACbvV,MAAM+C,IACL,MAAM2Y,EAAYhiB,KAAKgM,OAAOhC,UAAUR,SAASH,GACjDrJ,KAAKgM,OAAO0E,UAAUtH,WAAW4Y,GACjChiB,KAAKgM,OAAOiB,SAASzL,KAAK,iBAAgB,IAE3CygB,OAAOtgB,IACqD,GAC5D,GAEP,CAEA,eAAA8mB,CAAgBzV,EAAGuO,GACjBvhB,KAAK4oB,sBAAsB5V,GAC3BhT,KAAKgM,OAAOc,QAAQC,WAGpB,IAAIgV,EAAQ3b,QAAQC,UACpB,IAAA,MAAWwV,KAAQ0F,EACjBQ,EAAQA,EAAMzb,MAAK,IACjBtG,KAAKgM,OAAOxL,QAAQ2hB,cAActG,GAAMvV,MAAM+P,IAC5CrW,KAAKgM,OAAOyH,SAASpH,QAAQ,mBAAoB,CAC/CgK,MACAoF,SAAUI,EAAK1P,KACfiW,SAAUvG,EAAKza,MAChB,IACA6gB,OAAOtgB,IAER3B,KAAKgM,OAAOiB,SAASzL,KAAK,eAAgB,CAAEqa,OAAMja,MAAOD,GAAK,KAItE,CAEA,qBAAAinB,CAAsB5V,GACpB,MAAMjQ,EAAQE,SAASyoB,oBACnBzoB,SAASyoB,oBAAoB1Y,EAAEqY,QAASrY,EAAE2W,SAC1C,KAEJ,GAAI5mB,EAAO,CACT,MAAM+C,EAAMF,OAAOD,eACnBG,EAAIU,kBACJV,EAAIW,SAAS1D,EACf,CACF,CAMA,eAAA8lB,CAAgBnd,GACdkV,GAAgB5gB,KAAKgM,OAAQN,EAC/B,EC1qBF,SAASigB,GAAgB3f,EAAQ4f,EAAYC,GAC3C,MAAO,CAEL,WAAIhoB,GAAY,OAAOmI,EAAOnI,OAAQ,EAQtCioB,eAAA,CAAe3f,KAAS5K,IAAeyK,EAAOyH,SAASpH,QAAQF,KAAS5K,GAQxElB,GAAA,CAAGC,EAAOC,IAAkByL,EAAOiB,SAAS5M,GAAGC,EAAOC,GAQtD,GAAAS,CAAIV,EAAOC,GAAWyL,EAAOiB,SAASjM,IAAIV,EAAOC,EAAS,EAM1DoF,aAAA,IAAwBqG,EAAO0E,UAAU/K,eAMzCE,SAAA,IAAoBmG,EAAO0E,UAAU7K,WAMrCsE,iBAAA,IAA4B6B,EAAO0E,UAAUvG,mBAM7C4hB,QAAA,IAAmB/f,EAAO+f,UAM1BC,QAAA,IAAmBhgB,EAAOggB,UAM1BC,QAAA,IAAmBjgB,EAAOigB,UAG1B,WAAIzrB,GAAY,MAAO,IAAKwL,EAAOxL,QAAU,EAO7C0rB,WAAWtrB,GAAcirB,EAAQM,iBAAiBP,EAAYhrB,GAO9D,UAAAwrB,CAAWxrB,EAAKyC,GAASwoB,EAAQQ,iBAAiBT,EAAYhrB,EAAKyC,EAAO,EAE9E,CD+lBAwiB,GAASyG,aAAe,IAAM5G,GAC9BG,GAAS0G,oBAAsB,IAAM3G,GC/kBrC,MAAM4G,sBAAsBtsB,IAOrB,SAASusB,GAAyBjc,GAClCA,GAAUA,EAAMrE,MACrBqgB,GAAgB9rB,IAAI8P,EAAMrE,KAAMqE,EAClC,CAOO,SAASkc,GAA6BvgB,GAC3C,OAAOqgB,GAAgBrrB,OAAOgL,EAChC,CAMO,SAASwgB,KACd,OAAOxf,MAAMC,KAAKof,GAAgBI,SACpC,CAOO,SAASC,GAAqBC,GACnC,IAAKA,EAAO,OAAOH,KACnB,MAAMI,EAAID,EAAM3hB,cAChB,OAAOwhB,KAAwBrN,QAAOtM,GACpCA,EAAE7G,KAAKhB,cAAcmI,SAASyZ,KAC7B/Z,EAAEga,aAAe,IAAI7hB,cAAcmI,SAASyZ,KAC5C/Z,EAAEia,MAAQ,IAAIxW,MAAKyW,GAAKA,EAAE/hB,cAAcmI,SAASyZ,MAEtD,CAuCO,MAAMI,GAKX,WAAAptB,CAAYiM,GACVhM,KAAKgM,OAASA,EACdhM,KAAKotB,4BAAeltB,IAEpBF,KAAKqtB,gCAAmB1sB,IAExBX,KAAKstB,qCAAwBptB,IAE7BF,KAAKutB,6BAAgBrtB,IAErBF,KAAKwtB,oCAAuBttB,GAC9B,CAaA,gBAAAisB,CAAiBP,EAAYhrB,GAC3B,MAAM6sB,EAAWztB,KAAKutB,UAAUxsB,IAAI6qB,GACpC,GAAI6B,GAAYA,EAAShtB,IAAIG,GAAM,OAAO6sB,EAAS1sB,IAAIH,GACvD,MAAM8sB,EAAS1tB,KAAKotB,SAASrsB,IAAI6qB,GACjC,OAAI8B,GAAQC,iBAAmB/sB,KAAO8sB,EAAOC,gBACpCD,EAAOC,gBAAgB/sB,QADhC,CAIF,CAUA,gBAAAyrB,CAAiBT,EAAYhrB,EAAKyC,GAChC,MAAMqqB,EAAS1tB,KAAKotB,SAASrsB,IAAI6qB,GACjC,IAAK8B,EAAQ,OAAO,EAGpB,GAAIA,EAAOE,gBAAkBF,EAAOE,eAAepmB,OAAS,EAAG,CAC7D,MAAMqmB,EAASH,EAAOE,eAAenM,MAAKpN,GAAKA,EAAEzT,MAAQA,IACzD,GAAIitB,EAAQ,CAEV,GAAoB,YAAhBA,EAAO/d,MAAuC,kBAAVzM,EAAqB,OAAO,EACpE,GAAoB,WAAhBwqB,EAAO/d,MAAsC,iBAAVzM,EAAoB,OAAO,EAClE,GAAoB,WAAhBwqB,EAAO/d,MAAsC,iBAAVzM,EAAoB,OAAO,EAClE,GAAoB,WAAhBwqB,EAAO/d,KAAmB,CAE5B,KADqB+d,EAAOrtB,SAAW,IAAIoS,KAAIkb,GAAKA,EAAEzqB,QACrCiQ,SAASjQ,GAAQ,OAAO,CAC3C,CAEA,GAAIwqB,EAAOE,WAAaF,EAAOE,SAAS1qB,GAAQ,OAAO,CACzD,CACF,CAOA,OALKrD,KAAKutB,UAAU9sB,IAAImrB,IACtB5rB,KAAKutB,UAAU7sB,IAAIkrB,iBAAY,IAAI1rB,KAErCF,KAAKutB,UAAUxsB,IAAI6qB,GAAYlrB,IAAIE,EAAKyC,GACxCrD,KAAKgM,OAAOiB,SAASzL,KAAK,wBAAyB,CAAEoqB,aAAYhrB,MAAKyC,WAC/D,CACT,CAOA,iBAAA2qB,CAAkBpC,GAChB,MAAM8B,EAAS1tB,KAAKotB,SAASrsB,IAAI6qB,GAC3B5e,EAAS,IAAM0gB,GAAQC,iBAAmB,CAAA,GAC1CF,EAAWztB,KAAKutB,UAAUxsB,IAAI6qB,GACpC,GAAI6B,EACF,IAAA,MAAYQ,EAAGC,KAAMT,EAAUzgB,EAAOihB,GAAKC,EAE7C,OAAOlhB,CACT,CAYA,uBAAAmhB,CAAwBC,GACtB,MAAMC,EAAU,IAAI1tB,IAAIytB,EAAQxb,KAAIC,GAAKA,EAAE1G,QACrCmiB,qBAAYpuB,IACZquB,qBAAeruB,IAErB,IAAA,MAAW2S,KAAKub,EAAS,CAEvB,MAAMI,GAAQ3b,EAAE4b,cAAgB,IAAInP,QAAO3I,GAAK0X,EAAQ5tB,IAAIkW,KAC5D2X,EAAM5tB,IAAImS,EAAE1G,KAAMqiB,GAClBD,EAAS7tB,IAAImS,EAAE1G,KAAMqiB,EAAKhnB,OAC5B,CAGA,MAAMknB,EAAQ,GACd,IAAA,MAAYviB,EAAMwiB,KAAWJ,EACZ,IAAXI,GAAcD,EAAM/d,KAAKxE,GAG/B,MAAMyiB,EAAS,GACf,KAAOF,EAAMlnB,OAAS,GAAG,CACvB,MAAM2E,EAAOuiB,EAAM9d,QACnBge,EAAOje,KAAKxE,GAEZ,IAAA,MAAY0iB,EAAOL,KAASF,EACtBE,EAAKlb,SAASnH,KAChBoiB,EAAS7tB,IAAImuB,EAAON,EAASxtB,IAAI8tB,GAAS,GACd,IAAxBN,EAASxtB,IAAI8tB,IAAcH,EAAM/d,KAAKke,GAGhD,CAGA,MAAMC,EAAW,GACjB,GAAIF,EAAOpnB,OAAS4mB,EAAQ5mB,OAAQ,CAClC,MAAMunB,EAAYX,EAAQ9O,QAAOzM,IAAM+b,EAAOtb,SAAST,EAAE1G,QAAOyG,KAAIC,GAAKA,EAAE1G,OAC3E2iB,EAASne,KAAKoe,EAChB,CAEA,MAAMC,EAAY,IAAI9uB,IAAIkuB,EAAQxb,KAAIC,GAAK,CAACA,EAAE1G,KAAM0G,MACpD,MAAO,CACL+b,OAAQA,EAAOhc,QAAYoc,EAAUjuB,IAAIoL,KACzC2iB,WAEJ,CAgBA,QAAA5iB,CAASwhB,GACFA,GAAWA,EAAOvhB,OAInBnM,KAAKotB,SAAS3sB,IAAIitB,EAAOvhB,QAI7BnM,KAAKotB,SAAS1sB,IAAIgtB,EAAOvhB,KAAMuhB,GAG3BA,EAAOC,kBACJ3tB,KAAKutB,UAAU9sB,IAAIitB,EAAOvhB,OAC7BnM,KAAKutB,UAAU7sB,IAAIgtB,EAAOvhB,oBAAM,IAAIjM,MAKpCwtB,EAAOja,UACTia,EAAOja,SAAS/R,SAASutB,IACvB,GAAIvB,EAAOwB,KAAM,CAEflvB,KAAKstB,kBAAkB5sB,IAAIuuB,EAAI9iB,KAAMuhB,EAAOvhB,MAG5C,MAAMgjB,EAAkBF,EAAI5iB,QACtB+iB,EAAOpvB,KACPqvB,EAAa,IACdJ,EACH,OAAA5iB,IAAW9K,GAKT,OAJA6tB,EAAKE,eAAe5B,EAAOvhB,MAG3BijB,EAAKpjB,OAAOyH,SAASvH,SAAS+iB,EAAI9iB,KAAM,IAAK8iB,EAAK5iB,QAAS8iB,IACpDA,EAAgBI,MAAMvvB,KAAMuB,EACrC,GAEFvB,KAAKgM,OAAOyH,SAASvH,SAAS+iB,EAAI9iB,KAAMkjB,EAC1C,MACErvB,KAAKgM,OAAOyH,SAASvH,SAAS+iB,EAAI9iB,KAAM8iB,EAC1C,IAIJjvB,KAAKgM,OAAOiB,SAASzL,KAAK,oBAAqB,CAAE2K,KAAMuhB,EAAOvhB,QAChE,CAgBA,WAAAqjB,CAAY9B,GACV,IAAI1tB,KAAKqtB,aAAa5sB,IAAIitB,EAAOvhB,MAEjC,IACE,MAAMsjB,EAAM/B,EAAOgC,mBACf1vB,KAAKgM,OACL2f,GAAgB3rB,KAAKgM,OAAQ0hB,EAAOvhB,KAAMnM,MAE1C0tB,EAAO1e,MACT0e,EAAO1e,KAAKygB,GAId,MAAME,EAAS,GAEf,GAAsC,mBAA3BjC,EAAOkC,gBAAgC,CAChD,MAAMrvB,EAAU,KACd,IAAMmtB,EAAOkC,gBAAgBH,EAAK,OAC3B9tB,GAEL3B,KAAKgM,OAAOiB,SAASzL,KAAK,eAAgB,CAAE2K,KAAMuhB,EAAOvhB,KAAMvK,MAAOD,EAAKkuB,KAAM,mBACnF,GAEFF,EAAOhf,KAAK3Q,KAAKgM,OAAOiB,SAAS5M,GAAG,iBAAkBE,GACxD,CAEA,GAAwC,mBAA7BmtB,EAAOoC,kBAAkC,CAClD,MAAMvvB,EAAU,KACd,IAAMmtB,EAAOoC,kBAAkBL,EAAK,OAC7B9tB,GAEL3B,KAAKgM,OAAOiB,SAASzL,KAAK,eAAgB,CAAE2K,KAAMuhB,EAAOvhB,KAAMvK,MAAOD,EAAKkuB,KAAM,qBACnF,GAEFF,EAAOhf,KAAK3Q,KAAKgM,OAAOiB,SAAS5M,GAAG,mBAAoBE,GAC1D,CAEIovB,EAAOnoB,OAAS,GAClBxH,KAAKwtB,iBAAiB9sB,IAAIgtB,EAAOvhB,KAAMwjB,GAGzC3vB,KAAKqtB,aAAapsB,IAAIysB,EAAOvhB,MAC7BnM,KAAKgM,OAAOiB,SAASzL,KAAK,qBAAsB,CAAE2K,KAAMuhB,EAAOvhB,MACjE,OAASxK,GAEP3B,KAAKgM,OAAOiB,SAASzL,KAAK,eAAgB,CAAE2K,KAAMuhB,EAAOvhB,KAAMvK,MAAOD,GACxE,CACF,CASA,cAAA2tB,CAAenjB,GACb,MAAMuhB,EAAS1tB,KAAKotB,SAASrsB,IAAIoL,GACjC,GAAKuhB,IAID1tB,KAAKqtB,aAAa5sB,IAAI0L,GAA1B,CAGA,IAAA,MAAW4jB,KAAQrC,EAAOe,cAAgB,GACpCzuB,KAAKotB,SAAS3sB,IAAIsvB,KAAS/vB,KAAKqtB,aAAa5sB,IAAIsvB,IACnD/vB,KAAKsvB,eAAeS,GAIxB/vB,KAAKwvB,YAAY9B,EATgB,CAUnC,CAWA,OAAAsC,GACE,MAAMC,EAAU,GAChBjwB,KAAKotB,SAAS1rB,SAASgsB,IAChBA,EAAOwB,MAAMe,EAAQtf,KAAK+c,EAAM,IAGvC,MAAMkB,OAAEA,EAAAE,SAAQA,GAAa9uB,KAAKmuB,wBAAwB8B,GAG1D,GAAInB,EAAStnB,OAAS,EACpB,IAAA,MAAW0oB,KAASpB,EAElB9uB,KAAKgM,OAAOiB,SAASzL,KAAK,4BAA6B,CAAE4sB,QAAS8B,IAKtE,IAAA,MAAWxC,KAAUkB,EACnB5uB,KAAKwvB,YAAY9B,GAInB,IAAA,MAAWA,KAAUuC,EACdjwB,KAAKqtB,aAAa5sB,IAAIitB,EAAOvhB,OAChCnM,KAAKwvB,YAAY9B,EAGvB,CAYA,UAAAyC,GACEnwB,KAAKotB,SAAS1rB,SAASgsB,IACrB,IAME,KAHsBA,EAAOwB,MACzBlvB,KAAKqtB,aAAa5sB,IAAIitB,EAAOvhB,QAEZuhB,EAAOle,QAAS,CACnC,MAAMigB,EAAM/B,EAAOgC,mBACf1vB,KAAKgM,OACL2f,GAAgB3rB,KAAKgM,OAAQ0hB,EAAOvhB,KAAMnM,MAC9C0tB,EAAOle,QAAQigB,EACjB,CAGA,MAAME,EAAS3vB,KAAKwtB,iBAAiBzsB,IAAI2sB,EAAOvhB,MAChD,GAAIwjB,EACF,IAAA,MAAWS,KAAST,EAAQS,KAEhC,OAASzuB,GAEP3B,KAAKgM,OAAOiB,SAASzL,KAAK,eAAgB,CAAE2K,KAAMuhB,EAAOvhB,KAAMvK,MAAOD,GACxE,KAEF3B,KAAKotB,SAAStrB,QACd9B,KAAKqtB,aAAavrB,QAClB9B,KAAKstB,kBAAkBxrB,QACvB9B,KAAKutB,UAAUzrB,QACf9B,KAAKwtB,iBAAiB1rB,OACxB,CAWA,GAAAf,CAAIoL,GACF,OAAOnM,KAAKotB,SAASrsB,IAAIoL,EAC3B,CAMA,MAAAe,GACE,OAAOC,MAAMC,KAAKpN,KAAKotB,SAASR,SAClC,CAOA,GAAAnsB,CAAI0L,GACF,OAAOnM,KAAKotB,SAAS3sB,IAAI0L,EAC3B,CAOA,aAAAkkB,CAAclkB,GACZ,OAAOnM,KAAKqtB,aAAa5sB,IAAI0L,EAC/B,ECxmBF,MAAMmkB,sBAAyB3K,QAC/B,IAAI4K,IAA6B,EAEjC,SAASC,KAEP,MAAMC,EAAgBxtB,SAASwtB,cAC/B,GAAIA,EAAe,CACjB,IAAItuB,EAAKsuB,EACT,KAAOtuB,GAAI,CACT,MAAM6J,EAASskB,GAAmBvvB,IAAIoB,GACtC,GAAI6J,EAEF,YADAA,EAAO0kB,yBAGTvuB,EAAKA,EAAGI,aACV,CACF,CAGA,MAAMuD,EAAMF,OAAOD,eACnB,GAAIG,GAAOA,EAAIkF,WAAY,CACzB,IAAI/I,EAAO6D,EAAIkF,WACf,KAAO/I,GAAM,CACX,MAAM+J,EAASskB,GAAmBvvB,IAAIkB,GACtC,GAAI+J,EAEF,YADAA,EAAO0kB,yBAGTzuB,EAAOA,EAAK8B,UACd,CACF,CACF,CASO,MAAM4sB,GAMX,WAAA5wB,CAAY8D,EAASrD,EAAU,IAC7BR,KAAK6D,QAAUA,EACf7D,KAAKQ,QAAUA,EACfR,KAAK6gB,aAAergB,EAAQqgB,cAAgB,OAC5C7gB,KAAK4wB,cAAe,EACpB5wB,KAAK6wB,gBAAiB,EACtB7wB,KAAK8wB,cAAe,EAGpB9wB,KAAK+wB,YAAa,EAClB/wB,KAAKgxB,UAAY,GAGjBhxB,KAAKixB,WAAa,KAClBjxB,KAAKkxB,iBAAkB,EAEvBlxB,KAAKiN,SAAW,IAAInN,EACpBE,KAAK0Q,UAAY,IAAIrL,EAAUxB,GAC/B7D,KAAK2M,SAAW,IAAImF,EAAgB9R,MACpCA,KAAKyT,SAAW,IAAI1H,EAAgB/L,MACpCA,KAAK8M,QAAU,IAAIqB,EAAQnO,KAAMQ,EAAQsM,SACzC9M,KAAKgK,UAAY,IAAI6M,EAAUrW,EAAQgJ,UACvCxJ,KAAK0Q,UAAU3G,aAAa/J,KAAKgK,WACjChK,KAAKmxB,UAAY,IAAIlQ,GAAUjhB,MAC/BA,KAAKoxB,SAAW,IAAIvL,GAAS7lB,MAC7BA,KAAKouB,QAAU,IAAIjB,GAAcntB,MAEjCA,KAAKqxB,aAAerxB,KAAKqxB,aAAanf,KAAKlS,MAC3CA,KAAK0wB,uBAAyB1wB,KAAK0wB,uBAAuBxe,KAAKlS,MAC/DA,KAAKsxB,aAAetxB,KAAKsxB,aAAapf,KAAKlS,MAC3CA,KAAKuxB,YAAcvxB,KAAKuxB,YAAYrf,KAAKlS,MACzCA,KAAKwxB,aAAexxB,KAAKwxB,aAAatf,KAAKlS,KAC7C,CAQA,IAAAgP,GACE,IACEhP,KAAK6D,QAAQP,aAAa,kBAAmB,QAC7CtD,KAAK6D,QAAQP,aAAa,OAAQ,WAClCtD,KAAK6D,QAAQP,aAAa,iBAAkB,QAC5CtD,KAAK6D,QAAQP,aAAa,aAAc,QAGnCtD,KAAK6D,QAAQmD,UAAUrC,SAC1B3E,KAAK6D,QAAQmD,UAAY,eAG3BhH,KAAK6D,QAAQsO,iBAAiB,QAASnS,KAAKqxB,cAC5CrxB,KAAK6D,QAAQsO,iBAAiB,QAASnS,KAAKsxB,cAC5CtxB,KAAK6D,QAAQsO,iBAAiB,OAAQnS,KAAKuxB,aAC3CvxB,KAAK6D,QAAQsO,iBAAiB,QAASnS,KAAKwxB,cAG5ClB,GAAmB5vB,IAAIV,KAAK6D,QAAS7D,MAtEpCuwB,KACHttB,SAASkP,iBAAiB,kBAAmBqe,IAC7CD,IAA6B,GAuE3BvwB,KAAK2M,SAASqC,OACdhP,KAAK8M,QAAQkC,OACbhP,KAAKmxB,UAAUniB,OACfhP,KAAKoxB,SAASpiB,OACdhP,KAAKouB,QAAQ4B,SACf,OAASruB,GAGP,MADA3B,KAAKiN,SAASzL,KAAK,eAAgB,CAAEiwB,MAAO,OAAQ7vB,MAAOD,IACrDA,CACR,CACF,CAOA,OAAA6N,GACMxP,KAAK8wB,eACT9wB,KAAK8wB,cAAe,EAEpB9wB,KAAK6D,QAAQuO,oBAAoB,QAASpS,KAAKqxB,cAC/CrxB,KAAK6D,QAAQuO,oBAAoB,QAASpS,KAAKsxB,cAC/CtxB,KAAK6D,QAAQuO,oBAAoB,OAAQpS,KAAKuxB,aAC9CvxB,KAAK6D,QAAQuO,oBAAoB,QAASpS,KAAKwxB,cAG/ClB,GAAmBnvB,OAAOnB,KAAK6D,SAE/B7D,KAAKiN,SAASzL,KAAK,WACnBxB,KAAK2M,SAAS6C,UACdxP,KAAK8M,QAAQ0C,UACbxP,KAAKmxB,UAAU3hB,UACfxP,KAAKoxB,SAAS5hB,UACdxP,KAAKouB,QAAQ+B,aACbnwB,KAAKiN,SAASpL,qBAEd7B,KAAK6D,QAAQuU,gBAAgB,mBAC/B,CAOA,OAAA2T,GACE,OAAK/rB,KAAK+wB,YACV/wB,KAAKgxB,UAAYhxB,KAAKgK,UAAUR,SAASxJ,KAAK6D,QAAQmD,WACtDhH,KAAK+wB,YAAa,EACX/wB,KAAKgxB,WAHiBhxB,KAAKgxB,SAIpC,CAQA,OAAAU,CAAQroB,GACN,MAAM2Y,EAAYhiB,KAAKgK,UAAUR,SAASH,GAC1CrJ,KAAK6D,QAAQmD,UAAYgb,GAAa,cACtChiB,KAAK+wB,YAAa,EAClB/wB,KAAKkxB,iBAAkB,CACzB,CAMA,OAAAlF,GACE,OAAKhsB,KAAKkxB,iBAAuC,OAApBlxB,KAAKixB,YAClCjxB,KAAKixB,WAAajxB,KAAK6D,QAAQa,aAAe,GAC9C1E,KAAKkxB,iBAAkB,EAChBlxB,KAAKixB,YAHkDjxB,KAAKixB,UAIrE,CAMA,OAAAhF,GACE,MAAMvgB,EAAO1L,KAAKgsB,UAAUrnB,OAC5B,MAAgB,KAAT+G,GAAwB,OAATA,CACxB,CAMA,KAAAoX,GACE9iB,KAAK6D,QAAQif,OACf,CAMA,IAAA6O,GACE3xB,KAAK6D,QAAQ8tB,MACf,CAQA,cAAA7F,CAAe3f,KAAS5K,GACtB,OAAOvB,KAAKyT,SAASpH,QAAQF,KAAS5K,EACxC,CAQA,EAAAlB,CAAGC,EAAOC,GACR,OAAOP,KAAKiN,SAAS5M,GAAGC,EAAOC,EACjC,CAQA,GAAAS,CAAIV,EAAOC,GACTP,KAAKiN,SAASjM,IAAIV,EAAOC,EAC3B,CAOA,YAAAqxB,GACE,MAAMlmB,EAAO1L,KAAKgsB,UAAUrnB,OAC5B,OAAK+G,EACEA,EAAKiH,MAAM,OAAOnL,OADP,CAEpB,CAOA,YAAAqqB,GACE,OAAO7xB,KAAKgsB,UAAUxkB,MACxB,CAQA,YAAA6pB,GAOE,GALArxB,KAAK+wB,YAAa,EAElB/wB,KAAKkxB,iBAAkB,EAGQ,KAA3BlxB,KAAK6D,QAAQmD,WAA+C,SAA3BhH,KAAK6D,QAAQmD,UAAsB,CACtEhH,KAAK6D,QAAQmD,UAAY,cACzB,MAAMjE,EAAQE,SAASkE,cACvBpE,EAAM0F,SAASzI,KAAK6D,QAAQG,WAAY,GACxCjB,EAAM4F,UAAS,GACf3I,KAAK0Q,UAAUnK,SAASxD,EAC1B,CACA/C,KAAKiN,SAASzL,KAAK,iBACrB,CAQA,sBAAAkvB,GACE,IAAK1wB,KAAK0Q,UAAUzK,eAAehD,SAASwtB,eAAgB,OAE5D,IADczwB,KAAK0Q,UAAU7K,WACjB,OACZ,MAAMuE,EAAUpK,KAAK0Q,UAAUvG,mBAC/BnK,KAAKiN,SAASzL,KAAK,mBAAoB4I,EACzC,CAOA,YAAAknB,GACEtxB,KAAKiN,SAASzL,KAAK,QACrB,CAOA,WAAA+vB,GACEvxB,KAAKiN,SAASzL,KAAK,OACrB,CASA,YAAAgwB,CAAaxe,GAEW,aAAlBA,EAAErH,OAAOmE,MAAuBkD,EAAErH,OAAO8T,UAAU/Y,SAAS,uBAC9DsM,EAAErH,OAAO+T,SAAW1M,EAAErH,OAAO+T,QAC7B1f,KAAK+wB,YAAa,EAClB/wB,KAAKkxB,iBAAkB,EACvBlxB,KAAKiN,SAASzL,KAAK,kBAEvB,ECrMU,MAACswB,GAAY,IAlIzB,MACE,WAAA/xB,GAEEC,KAAK+xB,KAAO,IAAIjyB,EAEhBE,KAAKgyB,6BAAgB9xB,GACvB,CAUA,QAAAgM,CAAS+lB,EAAIjmB,GACXhM,KAAKgyB,UAAUtxB,IAAIuxB,EAAIjmB,GACvBhM,KAAK+xB,KAAKvwB,KAAK,oBAAqB,CAAEywB,KAAIjmB,UAC5C,CAOA,UAAAwG,CAAWyf,GACTjyB,KAAKgyB,UAAU7wB,OAAO8wB,GACtBjyB,KAAK+xB,KAAKvwB,KAAK,sBAAuB,CAAEywB,MAC1C,CAOA,SAAAC,CAAUD,GACR,OAAOjyB,KAAKgyB,UAAUjxB,IAAIkxB,EAC5B,CAMA,YAAAE,GACE,MAAO,IAAInyB,KAAKgyB,UAAU3kB,OAC5B,CAMA,eAAI+kB,GACF,OAAOpyB,KAAKgyB,UAAU5wB,IACxB,CAWA,EAAAf,CAAGC,EAAOC,EAASC,GACjB,OAAOR,KAAK+xB,KAAK1xB,GAAGC,EAAOC,EAASC,EACtC,CAOA,GAAAQ,CAAIV,EAAOC,GACTP,KAAK+xB,KAAK/wB,IAAIV,EAAOC,EACvB,CAQA,IAAAc,CAAKf,EAAOC,GACV,OAAOP,KAAK+xB,KAAK1wB,KAAKf,EAAOC,EAC/B,CAOA,IAAAiB,CAAKlB,EAAOmB,GACVzB,KAAK+xB,KAAKvwB,KAAKlB,EAAOmB,EACxB,CAYA,SAAA4wB,CAAU/xB,EAAOmB,EAAMjB,GACrB,IAAA,MAAYyxB,EAAIjmB,KAAWhM,KAAKgyB,UAC1BxxB,GAAS8xB,SAAWL,IAAOzxB,EAAQ8xB,SACvCtmB,EAAOiB,SAASzL,KAAKlB,EAAOmB,EAEhC,CAMA,KAAA8wB,GACEvyB,KAAK+xB,KAAKlwB,qBACV7B,KAAKgyB,UAAUlwB,OACjB,GC/IW0wB,GAAkB,CAE7BC,KAAMhV,GAGNiV,SAAU,CACR,CAAC,OAAQ,QACT,CAAC,WAAY,aAAc,YAC3B,CAAC,OAAQ,SAAU,YAAa,iBAChC,CAAC,YAAa,aACd,CAAC,YAAa,cAAe,aAAc,gBAC3C,CAAC,cAAe,gBAAiB,YACjC,CAAC,UAAW,UACZ,CAAC,OAAQ,QAAS,QAAS,aAAc,YAAa,kBACtD,CAAC,eAIHC,QAAS,CACP,CAAC,YACD,CAAC,OAAQ,SAAU,aACnB,CAAC,cAAe,iBAChB,CAAC,OAAQ,QAAS,eAIpBC,KAAM,CACJ,CAAC,OAAQ,SAAU,cAIrBC,KAAM,CACJ,CAAC,OAAQ,QACT,CAAC,WAAY,aAAc,WAAY,cACvC,CAAC,OAAQ,SAAU,YAAa,iBAChC,CAAC,YAAa,aACd,CAAC,YAAa,cAAe,cAC7B,CAAC,cAAe,gBAAiB,YACjC,CAAC,UAAW,UACZ,CAAC,OAAQ,QAAS,aAAc,QAAS,aAAc,aAAc,YAAa,kBAClF,CAAC,gBAAiB,aAAc,YAAa,iBAAkB,kBAC/D,CAAC,YAAa,cAAe,gBAC7B,CAAC,cAAe,iBAAkB,aAAc,kBAAmB,kBAAmB,kBAAmB,SAAU,iBAAkB,gBAclI,SAASC,GAAmBC,EAAQC,GACzC,MAAMC,EAAY,IAAItyB,IAAIqyB,GAC1B,OAAOD,EACJngB,KAAIsgB,GAASA,EAAM5T,YAAgB2T,EAAUxyB,IAAI0yB,OACjD7T,QAAO4T,GAASA,EAAM1rB,OAAS,GACpC,CAuBO,SAAS4rB,GAAgBL,EAAQjV,EAAOtd,EAAU,CAAA,GACvD,MAAM6yB,EAAalmB,MAAMmmB,QAAQxV,GAASA,EAAQ,CAACA,GAC7C9Q,EAAS+lB,EAAOngB,QAAa,IAAIsgB,KAEvC,GAAI1yB,EAAQ+yB,OAAS/yB,EAAQgzB,OAAQ,CACnC,MAAM7nB,EAASnL,EAAQ+yB,OAAS/yB,EAAQgzB,OACxC,IAAA,IAAS/lB,EAAI,EAAGA,EAAIT,EAAOxF,OAAQiG,IAAK,CACtC,MAAMqX,EAAM9X,EAAOS,GAAGyW,QAAQvY,GAC9B,IAAY,IAARmZ,EAAY,CACd,MAAM2O,EAAWjzB,EAAQ+yB,MAAQzO,EAAM,EAAIA,EAE3C,OADA9X,EAAOS,GAAGimB,OAAOD,EAAU,KAAMJ,GAC1BrmB,CACT,CACF,CAEF,CAEA,QAAsB,IAAlBxM,EAAQ0yB,MAAqB,CAC/B,MAAMS,EAAKnzB,EAAQ0yB,MAAQ,EAAIlmB,EAAOxF,OAAShH,EAAQ0yB,MAAQ1yB,EAAQ0yB,MACvE,GAAIS,GAAM,GAAKA,EAAK3mB,EAAOxF,OAEzB,OADAwF,EAAO2mB,GAAIhjB,QAAQ0iB,GACZrmB,CAEX,CAIA,OADAA,EAAO2D,KAAK0iB,GACLrmB,CACT,CAYO,SAAS4mB,GAAc9V,GAC5B,MAAM+V,EAAiB,CACrB,CAAEpgB,SAAU,CAAC,OAAQ,SACrB,CAAEA,SAAU,CAAC,WAAY,aAAc,aACvC,CAAEA,SAAU,CAAC,OAAQ,SAAU,YAAa,kBAC5C,CAAEA,SAAU,CAAC,YAAa,cAC1B,CAAEA,SAAU,CAAC,YAAa,cAAe,aAAc,iBACvD,CAAEA,SAAU,CAAC,cAAe,gBAAiB,aAC7C,CAAEA,SAAU,CAAC,UAAW,WACxB,CAAEA,SAAU,CAAC,OAAQ,QAAS,aAAc,iBAAkB,QAAS,aAAc,aAAc,YAAa,mBAChH,CAAEA,SAAU,CAAC,YAAa,gBAC1B,CAAEA,SAAU,CAAC,cAAe,iBAAkB,aAAc,SAAU,gBAGlEqgB,EAAU,IAAInzB,IAAImd,GAClB9Q,EAAS,GAEf,IAAA,MAAW+mB,KAAYF,EAAgB,CACrC,MAAMX,EAAQa,EAAStgB,SAAS6L,WAAcwU,EAAQrzB,IAAIwuB,KACtDiE,EAAM1rB,OAAS,IACjBwF,EAAO2D,KAAKuiB,GACZA,EAAMxxB,SAAQutB,GAAO6E,EAAQ3yB,OAAO8tB,KAExC,CAOA,OAJI6E,EAAQ1yB,KAAO,GACjB4L,EAAO2D,KAAK,IAAImjB,IAGX9mB,CACT,CCvJY,MAACgnB,sBAAsBrzB,IAAI,CACrC,OAAQ,SAAU,YAAa,gBAAiB,YAAa,cAC7D,YAAa,cAAe,aAAc,eAC1C,cAAe,gBAAiB,WAChC,aAAc,YAAa,iBAC3B,OAAQ,OAAQ,eAChB,SAAU,UACV,aAAc,aAAc,iBAC5B,kBAAmB,kBAEnB,gBAAiB,aAAc,YAAa,iBAAkB,iBAC9D,kBAAmB,mBAAoB,eACvC,qBAAsB,oBACtB,aAAc,eACd,aAAc,gBAAiB,qBAGpBszB,GAAc,CACzBlvB,KAAM,OAAQC,OAAQ,SAAUC,UAAW,YAAaC,cAAe,gBACvEC,UAAW,YAAaC,YAAa,cACrC8uB,UAAW,aAAcC,YAAa,eAAgBC,WAAY,cAAeC,aAAc,UAC/F9pB,YAAa,gBAAiBC,cAAe,gBAAiB8pB,SAAU,YACxE7pB,WAAY,aAAcC,UAAW,aAAc6pB,eAAgB,kBACnExjB,KAAM,OAAQQ,KAAM,OAAQijB,aAAc,oBAC1CC,OAAQ,SAAUC,QAAS,UAC3B/pB,KAAM,cAAe4V,MAAO,eAAgBoU,WAAY,cAAe7f,MAAO,eAC9E8f,eAAgB,kBAChBC,WAAY,cAAeC,YAAa,iBACxCC,WAAY,aAAcC,WAAY,cACtCC,eAAgB,kBAAmBC,OAAQ,kBAC3CC,gBAAiB,wBAAyBC,gBAAiB,aAC3DC,WAAY,cAAeC,cAAe,iBAAkBC,iBAAkB,oBAC9EC,gBAAiB,oBAAqBC,WAAY,aAClD3qB,UAAW,aAAcC,UAAW,mBACpC2qB,SAAU,aAAc9qB,WAAY,cAAeC,SAAU,YAC7D8qB,eAAgB,kBAEhBC,cAAe,iBACfC,WAAY,cACZC,UAAW,2BACXC,eAAgB,kBAChBC,eAAgB,mBAChBC,WAAY,cACZC,gBAAiB,oBACjBC,aAAc,oBACdC,gBAAiB,mBACjBC,iBAAkB,oBAClBC,aAAc,gBACdC,mBAAoB,sBACpBC,kBAAmB,sBAGRC,GAAe,CAC1B1xB,KAAM,QAASC,OAAQ,QAASC,UAAW,QAC3CC,cAAe,cAAe6L,KAAM,QAASQ,KAAM,cACnDmlB,WAAY,QAAS5B,YAAa,QAClCC,WAAY,cAAeC,WAAY,cACvCW,eAAgB,cAChBR,gBAAiB,cAAeC,gBAAiB,eAItCuB,GAAiB,CAC5BhsB,KAAM,OACN4V,MAAO,QACPoU,WAAY,aACZC,eAAgB,iBAChB9f,MAAO,QACP+f,WAAY,QACZC,YAAa,cACbI,OAAQ,UAGH,SAASxiB,GAAiBtG,GAC/B,MAAMI,EAAWiqB,GAAarqB,GAC9B,OAAKI,EACEA,EAAS0M,QAAQ,OAAQxH,IAAU,IAAM,QAD1B,EAExB,CAKO,SAASklB,GAAsBxqB,EAASyqB,EAAgB7qB,GAC7D,OAAQI,GACN,IAAK,OAAQ,OAAOyqB,EAAe9xB,KACnC,IAAK,SAAU,OAAO8xB,EAAe7xB,OACrC,IAAK,YAAa,OAAO6xB,EAAe5xB,UACxC,IAAK,gBAAiB,OAAO4xB,EAAe3xB,cAC5C,IAAK,YAAa,OAAO2xB,EAAe1xB,UACxC,IAAK,cAAe,OAAO0xB,EAAezxB,YAC1C,IAAK,YAAa,MAAoC,SAA7ByxB,EAAevsB,UACxC,IAAK,cAAe,MAAoC,WAA7BusB,EAAevsB,UAC1C,IAAK,aAAc,MAAoC,UAA7BusB,EAAevsB,UACzC,IAAK,eAAgB,MAAoC,YAA7BusB,EAAevsB,UAC3C,IAAK,cAAe,OAAOusB,EAAetsB,YAC1C,IAAK,gBAAiB,OAAOssB,EAAersB,cAC5C,IAAK,aAAc,OAAOqsB,EAAepsB,WACzC,IAAK,YAAa,OAAOosB,EAAensB,UACxC,IAAK,aAAc,OAAOsB,GAAQ4kB,aAClC,IAAK,iBAAkB,OAAO5kB,GAAQ6kB,eACtC,IAAK,aAAc,OAAO7kB,GAAQnI,SAAS0e,QAAQ,gBAAgB9C,UAAU/Y,SAAS,kBACtF,IAAK,kBAAmB,OAAOsF,GAAQnI,SAAS0e,QAAQ,gBAAgB9C,UAAU/Y,SAAS,wBAC3F,IAAK,kBAAmB,OAAOsF,GAAQnI,SAAS0e,QAAQ,gBAAgB9C,UAAU/Y,SAAS,kBAC3F,QAAS,OAAO,EAEpB,CClHY,MAACowB,GAAsB,CACjC/xB,KAAM,QACNC,OAAQ,QACRC,UAAW,QACXC,cAAe,cACf6L,KAAM,QACNQ,KAAM,cACNwlB,UAAW,QACXL,WAAY,QACZ5B,YAAa,QACbvqB,YAAa,cACbC,cAAe,cACfC,WAAY,cACZC,UAAW,cACX+pB,OAAQ,MACRC,QAAS,YACTK,WAAY,cACZC,WAAY,cACZ7vB,UAAW,QACXC,YAAa,SCoLH,MAAC4xB,GAAkB,IAzK/B,MACE,WAAAj3B,GAEEC,KAAKqtB,cAAe,EAEpBrtB,KAAKi3B,iBAAmB,KAExBj3B,KAAKk3B,gBAAkB,KAEvBl3B,KAAKm3B,UAAY,KAEjBn3B,KAAKo3B,aAAe,KAEpBp3B,KAAKiM,UAAY,KAEjBjM,KAAKq3B,0BAAan3B,GACpB,CAGA,WAAAo3B,GACMt3B,KAAKqtB,eACTrtB,KAAKqtB,cAAe,EAGpBrtB,KAAKi3B,iBAAmB9zB,OAAOo0B,OAAO,CACpCzgB,YAAa3T,OAAOo0B,OAClBp0B,OAAOq0B,YACLr0B,OAAOC,QAAQsQ,GAAcd,KAAI,EAAEhQ,EAAKI,KAAW,CAACJ,EAAKO,OAAOo0B,OAAO,IAAIv0B,SAG/E+T,cAAe5T,OAAOo0B,OAAO,IAAI1hB,MAInC7V,KAAKk3B,gBAAkB/zB,OAAOo0B,OAC5Bp0B,OAAOq0B,YACLr0B,OAAOC,QAAQovB,IAAiB5f,KAAI,EAAEzG,EAAMsrB,KAAY,CACtDtrB,EACAhJ,OAAOo0B,OAAOE,EAAO7kB,KAAI8kB,GAAKv0B,OAAOo0B,OAAO,IAAIG,YAMtD13B,KAAKm3B,UAAYh0B,OAAOo0B,OAAO,CAC7BI,QAASx0B,OAAOo0B,OAAO9Z,GAAgB7K,KAAI8kB,GAAKv0B,OAAOo0B,OAAO,IAAIG,OAClEE,QAASz0B,OAAOo0B,OAAO1Z,IACvBga,MAAO10B,OAAOo0B,OAAO,IAAI7Z,KACzBoa,UAAW30B,OAAOo0B,OAAO,IAAI5Z,KAC7Boa,OAAQ50B,OAAOo0B,OAAO,IAAI3Z,KAC1Boa,eAAgB70B,OAAOo0B,OAAO,IAAIxZ,OAIpC/d,KAAKo3B,aAAej0B,OAAOo0B,OAAO,IAAKT,KAGvC92B,KAAKiM,UAAY9I,OAAOo0B,OAAO,CAC7BU,QAAS90B,OAAOo0B,OAAO,IAAKvD,KAC5BkE,SAAU/0B,OAAOo0B,OAAO,IAAKtD,KAC7BkE,UAAWh1B,OAAOo0B,OAAO,IAAKd,KAC9B2B,OAAQj1B,OAAOo0B,OAAO,IAAKZ,OAE/B,CAQA,mBAAI0B,GAEF,OADAr4B,KAAKs3B,cACEt3B,KAAKi3B,gBACd,CAMA,kBAAIqB,GAEF,OADAt4B,KAAKs3B,cACEt3B,KAAKk3B,eACd,CAMA,YAAIqB,GAEF,OADAv4B,KAAKs3B,cACEt3B,KAAKm3B,SACd,CAMA,eAAIqB,GAEF,OADAx4B,KAAKs3B,cACEt3B,KAAKo3B,YACd,CAMA,YAAI3jB,GAEF,OADAzT,KAAKs3B,cACEt3B,KAAKiM,SACd,CAWA,YAAAwsB,CAAatsB,EAAMusB,GACjB14B,KAAKq3B,OAAO32B,IAAIyL,EAAMusB,EACxB,CAMA,cAAAC,CAAexsB,GACbnM,KAAKq3B,OAAOl2B,OAAOgL,EACrB,CAOA,OAAAysB,CAAQzsB,GACN,OAAOnM,KAAKq3B,OAAOt2B,IAAIoL,EACzB,CAMA,YAAA0sB,GACE,MAAO,IAAI74B,KAAKq3B,OAAOhqB,OACzB,CAMA,SAAIyrB,GAEF,OADA94B,KAAKs3B,cACE,CACLyB,gBAAiB/4B,KAAKq3B,OAAOj2B,KAC7B43B,eAAe,EAEnB,GClLF,SAASC,GAAgBjtB,EAAQxJ,GAC/B,MAAMsD,EAAMkG,EAAO0E,UAAU/K,eAC7B,IAAKG,GAA0B,IAAnBA,EAAIC,WAAkB,OAElC,MAAMhD,EAAQ+C,EAAIE,WAAW,GAC7B,GAAIjD,EAAM2E,UAAW,OAGrB,MAAM5D,EAASkI,EAAO0E,UAAU1H,mBAChC,GAAIlF,GAAUA,EAAOtB,UAAYA,EAAQK,cAAe,CAEtD,MAAMq2B,EAAUj2B,SAASk2B,yBACzB,KAAOr1B,EAAOE,YACZk1B,EAAQx1B,YAAYI,EAAOE,YAG7B,YADAF,EAAOC,WAAWq1B,aAAaF,EAASp1B,EAE1C,CAGA,IAAIu1B,EAAWv1B,EACf,KAAOu1B,GAAYA,IAAartB,EAAOnI,SAAS,CAC9C,GAAIw1B,EAAS72B,UAAYA,EAAQK,cAAe,CAC9C,MAAMq2B,EAAUj2B,SAASk2B,yBACzB,KAAOE,EAASr1B,YACdk1B,EAAQx1B,YAAY21B,EAASr1B,YAG/B,YADAq1B,EAASt1B,WAAWq1B,aAAaF,EAASG,EAE5C,CACAA,EAAWA,EAASt1B,UACtB,CAGA,MAAMzC,EAAU2B,SAASC,cAAcV,GACvC,IACEO,EAAMQ,iBAAiBjC,EACzB,CAAA,MACE,MAAMkC,EAAWT,EAAMU,kBACvBnC,EAAQoC,YAAYF,GACpBT,EAAMY,WAAWrC,EACnB,CACF,CAKA,SAASg4B,GAAYttB,EAAQxJ,GAC3B,MAAMsB,EAASkI,EAAO0E,UAAU1H,mBAChC,IAAKlF,EAAQ,OAAO,EACpB,IAAI3B,EAAK2B,EACT,KAAO3B,GAAMA,IAAO6J,EAAOnI,SAAS,CAClC,GAAI1B,EAAGK,UAAYA,EAAQK,cAAe,OAAO,EACjDV,EAAKA,EAAG4B,UACV,CACA,OAAO,CACT,CAEO,SAASw1B,GAA2BvtB,GACzCA,EAAOyH,SAASvH,SAAS,OAAQ,CAC/B,OAAAG,CAAQmtB,GAAOP,GAAgBO,EAAK,SAAU,EAC9CltB,SAASktB,GAAcF,GAAYE,EAAK,WAAaF,GAAYE,EAAK,KACtEhtB,SAAU,QACVC,KAAM,CAAEgtB,KAAM,OAAQC,QAAS,UAGjC1tB,EAAOyH,SAASvH,SAAS,SAAU,CACjC,OAAAG,CAAQmtB,GAAOP,GAAgBO,EAAK,KAAM,EAC1CltB,SAASktB,GAAcF,GAAYE,EAAK,OAASF,GAAYE,EAAK,KAClEhtB,SAAU,QACVC,KAAM,CAAEgtB,KAAM,SAAUC,QAAS,YAGnC1tB,EAAOyH,SAASvH,SAAS,YAAa,CACpC,OAAAG,CAAQmtB,GAAOP,GAAgBO,EAAK,IAAK,EACzCltB,SAASktB,GAAcF,GAAYE,EAAK,KACxChtB,SAAU,QACVC,KAAM,CAAEgtB,KAAM,YAAaC,QAAS,eAGtC1tB,EAAOyH,SAASvH,SAAS,gBAAiB,CACxC,OAAAG,CAAQmtB,GAAOP,GAAgBO,EAAK,IAAK,EACzCltB,SAASktB,GAAcF,GAAYE,EAAK,MAAQF,GAAYE,EAAK,OACjEhtB,SAAU,cACVC,KAAM,CAAEgtB,KAAM,gBAAiBC,QAAS,mBAG1C1tB,EAAOyH,SAASvH,SAAS,YAAa,CACpC,OAAAG,CAAQmtB,GAAOP,GAAgBO,EAAK,MAAO,EAC3CltB,SAASktB,GAAcF,GAAYE,EAAK,OACxChtB,SAAU,QACVI,mBAAoB,CAAC,eACrBH,KAAM,CAAEgtB,KAAM,YAAaC,QAAS,eAGtC1tB,EAAOyH,SAASvH,SAAS,cAAe,CACtC,OAAAG,CAAQmtB,GAAOP,GAAgBO,EAAK,MAAO,EAC3CltB,SAASktB,GAAcF,GAAYE,EAAK,OACxChtB,SAAU,QACVI,mBAAoB,CAAC,eACrBH,KAAM,CAAEgtB,KAAM,cAAeC,QAAS,iBAGxC1tB,EAAOyH,SAASvH,SAAS,YAAa,CACpC,OAAAG,CAAQmtB,GAAKG,MAAEA,EAAQ,UAAa,CAAA,GAClC,MAAM7zB,EAAM0zB,EAAI9oB,UAAU/K,eAC1B,IAAKG,GAA0B,IAAnBA,EAAIC,WAAkB,OAClC,MAAMhD,EAAQ+C,EAAIE,WAAW,GAC7B,GAAIjD,EAAM2E,UAAW,OAIrB,IAAI2xB,EADWG,EAAI9oB,UAAU1H,mBAE7B,KAAOqwB,GAAYA,IAAaG,EAAI31B,SAAS,CAC3C,GAAyB,SAArBw1B,EAAS72B,QAAoB,CAC/B,MAAM02B,EAAUj2B,SAASk2B,yBACzB,KAAOE,EAASr1B,YACdk1B,EAAQx1B,YAAY21B,EAASr1B,YAG/B,YADAq1B,EAASt1B,WAAWq1B,aAAaF,EAASG,EAE5C,CACAA,EAAWA,EAASt1B,UACtB,CAGA,MAAM61B,EAAW,CACfC,OAAQ,0BACRC,MAAO,yBACPC,KAAM,0BACNC,KAAM,yBACNC,OAAQ,yBACRC,OAAQ,2BAEJC,EAAKP,EAASD,IAAUC,EAASC,OACjClkB,EAAO1S,SAASC,cAAc,QACpCyS,EAAKrK,MAAM8uB,gBAAkBD,EAC7BxkB,EAAKrS,aAAa,uBAAwBq2B,GAC1C,IACE52B,EAAMQ,iBAAiBoS,EACzB,CAAA,MACE,MAAMnS,EAAWT,EAAMU,kBACvBkS,EAAKjS,YAAYF,GACjBT,EAAMY,WAAWgS,EACnB,CACF,EACArJ,SAASktB,GAAcF,GAAYE,EAAK,QACxC/sB,KAAM,CAAEgtB,KAAM,YAAaC,QAAS,eAGtC1tB,EAAOyH,SAASvH,SAAS,eAAgB,CACvC,OAAAG,CAAQmtB,GACN,MAAM1zB,EAAM0zB,EAAI9oB,UAAU/K,eAC1B,IAAKG,GAA0B,IAAnBA,EAAIC,WAAkB,OAClC,MAAMhD,EAAQ+C,EAAIE,WAAW,GAC7B,GAAIjD,EAAM2E,UAAW,OAGrB,MAAM2yB,iBAAa,IAAI15B,IAAI,CAAC,SAAU,IAAK,KAAM,IAAK,IAAK,IAAK,MAAO,MAAO,MAAO,OAAQ,SACvFsI,EAAYlG,EAAMmD,wBAClBxD,EAAOuG,EAAU7G,WAAaC,KAAKC,UAAY2G,EAAU1G,cAAgB0G,EAEzEqxB,EAASr3B,SAAS6E,iBAAiBpF,EAAMqF,WAAWwyB,cACpDC,EAAW,GACjB,IAAIv4B,EACJ,KAAQA,EAAOq4B,EAAOhyB,YAChB+xB,EAAW55B,IAAIwB,EAAKO,UAAYO,EAAM03B,eAAex4B,IACvDu4B,EAAS7pB,KAAK1O,GAKlB,IAAA,IAASwL,EAAI+sB,EAAShzB,OAAS,EAAGiG,GAAK,EAAGA,IAAK,CAC7C,MAAMtL,EAAKq4B,EAAS/sB,GACd3J,EAAS3B,EAAG4B,WAClB,KAAO5B,EAAG6B,YACRF,EAAOG,aAAa9B,EAAG6B,WAAY7B,GAErC2B,EAAOI,YAAY/B,EACrB,CACF,EACAqK,SAAU,SACVC,KAAM,CAAEgtB,KAAM,eAAgBC,QAAS,sBAE3C,CC7LA,SAASgB,GAAkBC,EAAcC,GACvC,GAAyBA,GAAoB,EAAG,OAAOD,EACvD,MAAME,EAASD,EAAmB,EAClC,OAAOx2B,KAAKsE,IAAIiyB,EAAeE,EAAQ,EACzC,CAKA,SAASC,GAAa9uB,EAAQ+uB,GAC5B,MAAMt2B,EAAQuH,EAAO0E,UAAUxH,iBAC/B,IAAKzE,EAAO,OAGZ,GAAIA,EAAMjC,UAAYu4B,EAAOl4B,cAAe,OAE5C,MAAMm4B,EAAQ/3B,SAASC,cAAc63B,GAErC,KAAOt2B,EAAMT,YACXg3B,EAAMt3B,YAAYe,EAAMT,YAGtBS,EAAMgmB,YAAWuQ,EAAMvQ,UAAYhmB,EAAMgmB,WAE7ChmB,EAAMV,WAAWq1B,aAAa4B,EAAOv2B,GAGrC,MAAM1B,EAAQE,SAASkE,cACvBpE,EAAMqE,mBAAmB4zB,GACzBj4B,EAAM4F,UAAS,GACfqD,EAAO0E,UAAUnK,SAASxD,EAC5B,CAEO,SAASk4B,GAAwBjvB,GACtC,MAAMkvB,EAAYlvB,EAAOxL,QAAQo6B,kBAAoB,EAErD5uB,EAAOyH,SAASvH,SAAS,UAAW,CAClC,OAAAG,CAAQmtB,EAAK2B,GACX,GAAc,MAAVA,EACFL,GAAatB,EAAK,SACb,CAELsB,GAAatB,EAAK,IADAkB,GAAkBU,OAAOD,GAAQD,KAErD,CACF,EACA,QAAA5uB,CAASktB,GACP,MAAM/0B,EAAQ+0B,EAAI9oB,UAAUxH,iBAC5B,QAAKzE,MACE,WAAW2G,KAAK3G,EAAMjC,UAAWiC,EAAMjC,QAAQ2I,cACxD,EACAsB,KAAM,CAAEgtB,KAAM,UAAWC,QAAS,aAIpC,IAAA,IAASjsB,EAAI,EAAGA,GAAK,EAAGA,IAAK,CAC3B,MAAM4tB,EAAiBX,GAAkBjtB,EAAGytB,GAC5ClvB,EAAOyH,SAASvH,SAAS,IAAIuB,IAAK,CAChC,OAAApB,CAAQmtB,GAAOsB,GAAatB,EAAK,IAAI6B,IAAkB,EACvD,QAAA/uB,CAASktB,GACP,MAAM/0B,EAAQ+0B,EAAI9oB,UAAUxH,iBAC5B,OAAOzE,GAASA,EAAMjC,UAAY,IAAI64B,GACxC,EACA5uB,KAAM,CAAEitB,QAAS,WAAWjsB,MAEhC,CAEAzB,EAAOyH,SAASvH,SAAS,YAAa,CACpC,OAAAG,CAAQmtB,GAAOsB,GAAatB,EAAK,IAAK,EACtC,QAAAltB,CAASktB,GACP,MAAM/0B,EAAQ+0B,EAAI9oB,UAAUxH,iBAC5B,OAAOzE,GAA2B,MAAlBA,EAAMjC,OACxB,EACAiK,KAAM,CAAEitB,QAAS,gBAErB,CC7EO,SAAS4B,GAA0BtvB,GACxCA,EAAOyH,SAASvH,SAAS,YAAa,CACpC,OAAAG,CAAQmtB,GACN,MAAM/0B,EAAQ+0B,EAAI9oB,UAAUxH,iBACxBzE,IAAOA,EAAM6G,MAAMC,UAAY,OACrC,EACA,QAAAe,CAASktB,GACP,MAAM/0B,EAAQ+0B,EAAI9oB,UAAUxH,iBAC5B,IAAKzE,EAAO,OAAO,EACnB,MAAM4G,EAAQ5G,EAAM6G,MAAMC,UAC1B,OAAQF,GAAmB,SAAVA,GAA8B,UAAVA,CACvC,EACAoB,KAAM,CAAEgtB,KAAM,YAAaC,QAAS,gBAGtC1tB,EAAOyH,SAASvH,SAAS,cAAe,CACtC,OAAAG,CAAQmtB,GACN,MAAM/0B,EAAQ+0B,EAAI9oB,UAAUxH,iBACxBzE,IAAOA,EAAM6G,MAAMC,UAAY,SACrC,EACA,QAAAe,CAASktB,GACP,MAAM/0B,EAAQ+0B,EAAI9oB,UAAUxH,iBAC5B,IAAKzE,EAAO,OAAO,EAEnB,MAAiB,WADHA,EAAM6G,MAAMC,SAE5B,EACAkB,KAAM,CAAEgtB,KAAM,cAAeC,QAAS,kBAGxC1tB,EAAOyH,SAASvH,SAAS,aAAc,CACrC,OAAAG,CAAQmtB,GACN,MAAM/0B,EAAQ+0B,EAAI9oB,UAAUxH,iBACxBzE,IAAOA,EAAM6G,MAAMC,UAAY,QACrC,EACA,QAAAe,CAASktB,GACP,MAAM/0B,EAAQ+0B,EAAI9oB,UAAUxH,iBAC5B,IAAKzE,EAAO,OAAO,EACnB,MAAM4G,EAAQ5G,EAAM6G,MAAMC,UAC1B,MAAiB,UAAVF,GAA+B,QAAVA,CAC9B,EACAoB,KAAM,CAAEgtB,KAAM,aAAcC,QAAS,iBAGvC1tB,EAAOyH,SAASvH,SAAS,eAAgB,CACvC,OAAAG,CAAQmtB,GACN,MAAM/0B,EAAQ+0B,EAAI9oB,UAAUxH,iBACxBzE,IAAOA,EAAM6G,MAAMC,UAAY,UACrC,EACA,QAAAe,CAASktB,GACP,MAAM/0B,EAAQ+0B,EAAI9oB,UAAUxH,iBAC5B,IAAKzE,EAAO,OAAO,EAEnB,MAAiB,YADHA,EAAM6G,MAAMC,SAE5B,EACAkB,KAAM,CAAEgtB,KAAM,eAAgBC,QAAS,YAE3C,CCzDO,SAAS6B,GAAqBvvB,GACnCA,EAAOyH,SAASvH,SAAS,cAAe,CACtC,OAAAG,CAAQmtB,GACN,MAAMgC,EAAahC,EAAI9oB,UAAUvH,kBAAkB,MACnD,GAAIqyB,EAGF,YADAC,GAAWD,GAIb,MAAME,EAAalC,EAAI9oB,UAAUvH,kBAAkB,MAC/CuyB,EACFC,GAAgBD,EAAY,MAI9BE,GAAWpC,EAAK,KAClB,EACAltB,SAASktB,KACEA,EAAI9oB,UAAUvH,kBAAkB,MAE3CqD,SAAU,cACVC,KAAM,CAAEgtB,KAAM,cAAeC,QAAS,mBAGxC1tB,EAAOyH,SAASvH,SAAS,gBAAiB,CACxC,OAAAG,CAAQmtB,GACN,MAAMkC,EAAalC,EAAI9oB,UAAUvH,kBAAkB,MACnD,GAAIuyB,IAAeA,EAAWjc,UAAU/Y,SAAS,iBAE/C,YADA+0B,GAAWC,GAGb,MAAMF,EAAahC,EAAI9oB,UAAUvH,kBAAkB,MAC/CqyB,EACFG,GAAgBH,EAAY,MAG9BI,GAAWpC,EAAK,KAClB,EACA,QAAAltB,CAASktB,GACP,MAAM7kB,EAAK6kB,EAAI9oB,UAAUvH,kBAAkB,MAC3C,QAAKwL,IACGA,EAAG8K,UAAU/Y,SAAS,gBAChC,EACA8F,SAAU,cACVC,KAAM,CAAEgtB,KAAM,gBAAiBC,QAAS,mBAG1C1tB,EAAOyH,SAASvH,SAAS,WAAY,CACnC,OAAAG,CAAQmtB,GACN,MAAMkC,EAAalC,EAAI9oB,UAAUvH,kBAAkB,MACnD,GAAIuyB,GAAcA,EAAWjc,UAAU/Y,SAAS,iBAI9C,OAFAg1B,EAAWjc,UAAUsI,OAAO,sBAC5B2T,EAAWjiB,iBAAiB,sBAAsB/X,SAASm6B,GAAOA,EAAG9T,WAKlE2T,GACHE,GAAWpC,EAAK,MAGlB,MAAM7kB,EAAK6kB,EAAI9oB,UAAUvH,kBAAkB,MACtCwL,IAELA,EAAG8K,UAAUxe,IAAI,iBACjB0T,EAAG8E,iBAAiB,MAAM/X,SAASmT,IACjC,IAAKA,EAAGjQ,cAAc,sBAAuB,CAC3C,MAAMk3B,EAAW74B,SAASC,cAAc,SACxC44B,EAAShsB,KAAO,WAChBgsB,EAASrR,UAAY,oBACrBqR,EAASx4B,aAAa,kBAAmB,SACzCuR,EAAG5Q,aAAa63B,EAAUjnB,EAAG7Q,WAC/B,KAEJ,EACA,QAAAsI,CAASktB,GACP,MAAM7kB,EAAK6kB,EAAI9oB,UAAUvH,kBAAkB,MAC3C,OAAOwL,GAAMA,EAAG8K,UAAU/Y,SAAS,gBACrC,EACA+F,KAAM,CAAEgtB,KAAM,WAAYC,QAAS,eAGrC1tB,EAAOyH,SAASvH,SAAS,SAAU,CACjC,OAAAG,CAAQmtB,GACN,MAAM3kB,EAAK2kB,EAAI9oB,UAAUvH,kBAAkB,MAC3C,IAAK0L,EAAI,OACT,MAAMknB,EAASlnB,EAAGwP,uBAClB,IAAK0X,EAAQ,OAGb,MAAMC,EAAWxC,EAAIh5B,SAASy7B,qBAAuB,EACrD,IAAIC,EAAQ,EACR/5B,EAAK0S,EAAGtS,cACZ,KAAOJ,GAAMA,IAAOq3B,EAAI31B,SACH,OAAf1B,EAAGK,SAAmC,OAAfL,EAAGK,SAAkB05B,IAChD/5B,EAAKA,EAAGI,cAEV,GAAI25B,GAASF,EAAU,OAGvB,MAAMG,EAAUtnB,EAAGtS,cAAcC,QAAQ2I,cACzC,IAAIixB,EAAUL,EAAOn3B,cAAcu3B,GAC9BC,IACHA,EAAUn5B,SAASC,cAAci5B,GACjCJ,EAAOr4B,YAAY04B,IAErBA,EAAQ14B,YAAYmR,EACtB,EACApI,KAAM,CAAEgtB,KAAM,SAAUC,QAAS,qBAGnC1tB,EAAOyH,SAASvH,SAAS,UAAW,CAClC,OAAAG,CAAQmtB,GACN,MAAM3kB,EAAK2kB,EAAI9oB,UAAUvH,kBAAkB,MAC3C,IAAK0L,EAAI,OACT,MAAMwnB,EAAaxnB,EAAGtS,cACtB,IAAK85B,EAAY,OACjB,MAAMC,EAAgBD,EAAW95B,cACjC,IAAK+5B,GAA2C,OAA1BA,EAAc95B,QAAkB,OAG9B85B,EAAc/5B,cACtB0B,aAAa4Q,EAAIynB,EAAcnT,aAGZ,IAA/BkT,EAAW5S,SAASjiB,QACtB60B,EAAWtU,QAEf,EACAtb,KAAM,CAAEgtB,KAAM,UAAWC,QAAS,oBAEtC,CAEA,SAASkC,GAAW5vB,EAAQmwB,GAC1B,MAAM13B,EAAQuH,EAAO0E,UAAUxH,iBAC/B,IAAKzE,EAAO,OAEZ,MAAM83B,EAAOt5B,SAASC,cAAci5B,GAC9BtnB,EAAK5R,SAASC,cAAc,MAGlC,KAAOuB,EAAMT,YACX6Q,EAAGnR,YAAYe,EAAMT,YAEvBu4B,EAAK74B,YAAYmR,GACjBpQ,EAAMV,WAAWq1B,aAAamD,EAAM93B,GAGpC,MAAM1B,EAAQE,SAASkE,cACvBpE,EAAMqE,mBAAmByN,GACzB9R,EAAM4F,UAAS,GACfqD,EAAO0E,UAAUnK,SAASxD,EAC5B,CAEA,SAAS04B,GAAWc,GAClB,MAAMz4B,EAASy4B,EAAKx4B,WACd+Z,EAAQ3Q,MAAMC,KAAKmvB,EAAK9iB,iBAAiB,gBAC/C,IAAA,MAAW5E,KAAMiJ,EAAO,CACtB,MAAMjL,EAAI5P,SAASC,cAAc,KACjC,KAAO2R,EAAG7Q,YACR6O,EAAEnP,YAAYmR,EAAG7Q,YAEnBF,EAAOG,aAAa4O,EAAG0pB,EACzB,CACAz4B,EAAOI,YAAYq4B,EACrB,CAEA,SAASZ,GAAgBY,EAAMxB,GAC7B,MAAMyB,EAAUv5B,SAASC,cAAc63B,GAEvC,IAAA,MAAW7iB,KAAQqkB,EAAKtkB,WACtBukB,EAAQl5B,aAAa4U,EAAK/L,KAAM+L,EAAK7U,OAEvC,KAAOk5B,EAAKv4B,YACVw4B,EAAQ94B,YAAY64B,EAAKv4B,YAE3Bu4B,EAAKx4B,WAAWq1B,aAAaoD,EAASD,EACxC,CCpLA,MAAME,GAAqB,uDAE3B,SAASC,GAAYrmB,GACnB,OAAKA,GACDomB,GAAmBrxB,KAAKiL,GAAa,IADxBA,CAGnB,CAEO,SAASsmB,GAAqB3wB,GACnCA,EAAOyH,SAASvH,SAAS,aAAc,CACrC,OAAAG,CAAQmtB,GAAK/tB,KAAEA,OAAMC,EAAAC,OAAMA,EAAS,WAClC,IAAKF,EAAM,OAEX,MAAMiF,EAAY8oB,EAAI9oB,UAChBksB,EAAelsB,EAAU9J,kBAE/B,GAAIg2B,EAAc,CAChB,MAAMjyB,EAAO+F,EAAUzG,SAAS,IAAK,CACnCwB,OACAE,SACAkxB,IAAgB,WAAXlxB,EAAsB,2BAAwB,IAEjDhB,GAAQe,GAAQA,IAASkxB,IAC3BjyB,EAAKjG,YAAcgH,EAEvB,KAAO,CACL,MAAMoxB,EAAcpxB,GAAQD,EAEtBpC,EAAO,YAAY0zB,GADRL,GAAYjxB,gBAC6BsxB,GAAWpxB,MAAsB,WAAXA,EAAsB,6BAA+B,MAAM2P,GAAWwhB,SACtJpsB,EAAUtH,WAAWC,EACvB,CACF,EACAmD,SAAU,QACVC,KAAM,CAAEgtB,KAAM,OAAQC,QAAS,iBAGjC1tB,EAAOyH,SAASvH,SAAS,WAAY,CACnC,OAAAG,CAAQmtB,GAAK/tB,KAAEA,EAAAC,KAAMA,EAAAC,OAAMA,IACzB,MAAMqxB,EAASxD,EAAI9oB,UAAUvH,kBAAkB,KAC1C6zB,SACQ,IAATvxB,IAAoBuxB,EAAOvxB,KAAOixB,GAAYjxB,SACrC,IAATC,IAAoBsxB,EAAOt4B,YAAcgH,QAC9B,IAAXC,IACFqxB,EAAOrxB,OAASA,EACD,WAAXA,EACFqxB,EAAOH,IAAM,sBAEbG,EAAO5kB,gBAAgB,QAG7B,EACA3L,KAAM,CAAEitB,QAAS,eAGnB1tB,EAAOyH,SAASvH,SAAS,aAAc,CACrC,OAAAG,CAAQmtB,GACNA,EAAI9oB,UAAUxG,OAAO,IACvB,EACAuC,KAAM,CAAEgtB,KAAM,SAAUC,QAAS,gBAErC,CAIA,SAASqD,GAAWxvB,GAClB,OAAOA,EAAI2L,QAAQ,KAAM,UAAUA,QAAQ,KAAM,QACnD,CC3DO,SAAS+jB,GAAsBjxB,GACpCA,EAAOyH,SAASvH,SAAS,cAAe,CACtC,OAAAG,CAAQmtB,GAAKrjB,IAAEA,EAAAtJ,IAAKA,EAAM,GAAAoe,MAAIA,EAAAf,OAAOA,IACnC,IAAK/T,EAAK,OAGV,GAAI,oBAAoB/K,KAAK+K,GAAM,OAInC,IAEE,GADY,IAAIG,IAAIH,EAAKvQ,OAAOs3B,SAASzxB,MACjC0xB,SAAShyB,cAAcyL,SAAS,QAAS,MACnD,CAAA,MAEE,GAAI,iBAAiBxL,KAAK+K,GAAM,MAClC,CAMA,GAHAqjB,EAAI31B,QAAQif,SAGP0W,EAAI9oB,UAAU7K,WAAY,CAC7B,MAAM9C,EAAQE,SAASkE,cACvBpE,EAAMqE,mBAAmBoyB,EAAI31B,SAC7Bd,EAAM4F,UAAS,GACf6wB,EAAI9oB,UAAUnK,SAASxD,EACzB,CAEA,MAAM2R,EAAMzR,SAASC,cAAc,OAWnC,GAVAwR,EAAIyB,IAAMA,EACVzB,EAAI7H,IAAMA,EACNoe,MAAW3f,MAAM2f,MAAyB,iBAAVA,EAAqB,GAAGA,MAAYA,GACpEf,MAAY5e,MAAM4e,OAA2B,iBAAXA,EAAsB,GAAGA,MAAaA,GAC5ExV,EAAIpJ,MAAM8xB,SAAW,OACrB1oB,EAAI+V,UAAY,YAEhB+O,EAAI9oB,UAAU/M,WAAW+Q,IAGpBA,EAAIyU,aAAezU,EAAInS,gBAAkBi3B,EAAI31B,QAAS,CACzD,MAAMgP,EAAI5P,SAASC,cAAc,KACjC2P,EAAE7L,UAAY,OACd0N,EAAInS,cAAc0B,aAAa4O,EAAG6B,EAAIyU,YACxC,CACF,EACA1c,KAAM,CAAEgtB,KAAM,QAASC,QAAS,kBAGlC1tB,EAAOyH,SAASvH,SAAS,cAAe,CACtC,OAAAG,CAAQmtB,GAAK31B,QAAEA,EAAAonB,MAASA,EAAAf,OAAOA,IACxBrmB,GAA+B,QAApBA,EAAQrB,UACxBqB,EAAQyH,MAAM2f,MAAyB,iBAAVA,EAAqB,GAAGA,MAAYA,EAE/DpnB,EAAQyH,MAAM4e,OADZA,EACuC,iBAAXA,EAAsB,GAAGA,MAAaA,EAE7C,OAE3B,EACAzd,KAAM,CAAEitB,QAAS,kBAGnB1tB,EAAOyH,SAASvH,SAAS,aAAc,CACrC,OAAAG,CAAQmtB,GAAK31B,QAAEA,EAAAyG,UAASA,IACtB,GAAKzG,GAA+B,QAApBA,EAAQrB,QAKxB,OAJAqB,EAAQyH,MAAM+xB,MAAQ,GACtBx5B,EAAQyH,MAAMgyB,OAAS,GACvBz5B,EAAQyH,MAAMiyB,QAAU,GAEhBjzB,GACN,IAAK,OACHzG,EAAQyH,MAAM+xB,MAAQ,OACtBx5B,EAAQyH,MAAMgyB,OAAS,gBACvB,MACF,IAAK,QACHz5B,EAAQyH,MAAM+xB,MAAQ,QACtBx5B,EAAQyH,MAAMgyB,OAAS,gBACvB,MACF,IAAK,SACHz5B,EAAQyH,MAAMiyB,QAAU,QACxB15B,EAAQyH,MAAMgyB,OAAS,YAK7B,EACA7wB,KAAM,CAAEitB,QAAS,iBAGnB1tB,EAAOyH,SAASvH,SAAS,cAAe,CACtC,OAAAG,CAAQmtB,GAAK31B,QAAEA,IACRA,GAA+B,QAApBA,EAAQrB,SACxBqB,EAAQE,WAAWG,YAAYL,EACjC,EACA4I,KAAM,CAAEitB,QAAS,iBAErB,CCzGO,SAAS8D,GAAsBxxB,GACpCA,EAAOyH,SAASvH,SAAS,cAAe,CACtC,OAAAG,CAAQmtB,GAAKtW,KAAEA,EAAO,OAAGua,EAAO,GAAM,IACpC,IAAIp0B,EAAO,uCACX,IAAA,IAASwa,EAAI,EAAGA,EAAI4Z,EAAM5Z,IACxBxa,GAAQ,gBAEVA,GAAQ,uBACR,IAAA,IAASkb,EAAI,EAAGA,EAAIrB,EAAMqB,IAAK,CAC7Blb,GAAQ,OACR,IAAA,IAASwa,EAAI,EAAGA,EAAI4Z,EAAM5Z,IACxBxa,GAAQ,gBAEVA,GAAQ,OACV,CACAA,GAAQ,8BACRmwB,EAAI9oB,UAAUtH,WAAWC,EAC3B,EACAoD,KAAM,CAAEgtB,KAAM,QAASC,QAAS,kBAGlC1tB,EAAOyH,SAASvH,SAAS,kBAAmB,CAC1C,OAAAG,CAAQmtB,GACN,MAAM1kB,EAAQ0kB,EAAI9oB,UAAUvH,kBAAkB,SAC9C,IAAK2L,EAAO,OACZ,MAAMC,EAAQD,EAAMlQ,cAAc,SAClC,GAAImQ,EAAO,CAET,MAAM2oB,EAAY3oB,EAAMnQ,cAAc,MACtC,IAAK84B,EAAW,OAChB,MAAM/Y,EAAS1hB,SAASC,cAAc,MACtC,IAAA,MAAWgS,KAAM/H,MAAMC,KAAKswB,EAAUpa,OAAQ,CAC5C,MAAMnO,EAAKlS,SAASC,cAAc,MAClCiS,EAAGnO,UAAYkO,EAAGlO,UACdkO,EAAGoP,QAAU,IAAGnP,EAAGmP,QAAUpP,EAAGoP,SAChCpP,EAAGyoB,QAAU,IAAGxoB,EAAGwoB,QAAUzoB,EAAGyoB,SACpChZ,EAAOjhB,YAAYyR,EACrB,CACA,IAAIH,EAAQF,EAAMlQ,cAAc,SAC3BoQ,IACHA,EAAQ/R,SAASC,cAAc,SAC/B4R,EAAMpR,YAAYsR,IAEpBA,EAAM/Q,aAAa0gB,EAAQ3P,EAAMhR,YACjC+Q,EAAMgT,QACR,KAAO,CAEL,IAAI/S,EAAQF,EAAMlQ,cAAc,SAChC,IAAKoQ,EAAO,OACZ,MAAMyP,EAAWzP,EAAMpQ,cAAc,MACrC,IAAK6f,EAAU,OACf,MAAMmZ,EAAW36B,SAASC,cAAc,SAClCyhB,EAAS1hB,SAASC,cAAc,MACtC,IAAA,MAAWiS,KAAMhI,MAAMC,KAAKqX,EAASnB,OAAQ,CAC3C,MAAMpO,EAAKjS,SAASC,cAAc,MAClCgS,EAAGlO,UAAYmO,EAAGnO,UACdmO,EAAGmP,QAAU,IAAGpP,EAAGoP,QAAUnP,EAAGmP,SAChCnP,EAAGwoB,QAAU,IAAGzoB,EAAGyoB,QAAUxoB,EAAGwoB,SACpChZ,EAAOjhB,YAAYwR,EACrB,CACA0oB,EAASl6B,YAAYihB,GACrB7P,EAAM7Q,aAAa25B,EAAU9oB,EAAM9Q,YACnCygB,EAASsD,QACX,CACF,EACAtb,KAAM,CAAEitB,QAAS,uBAGnB1tB,EAAOyH,SAASvH,SAAS,YAAa,CACpC,OAAAG,CAAQmtB,GAAKqE,UAAEA,EAAY,QAAW,CAAA,GACpC,MAAM1oB,EAAKqkB,EAAI9oB,UAAUvH,kBAAkB,OAASqwB,EAAI9oB,UAAUvH,kBAAkB,MAC/EgM,GACA,CAAC,OAAQ,SAAU,SAAS7B,SAASuqB,KAC1C1oB,EAAG7J,MAAMC,UAAYsyB,EACvB,EACApxB,KAAM,CAAEitB,QAAS,wBAGnB1tB,EAAOyH,SAASvH,SAAS,eAAgB,CACvC,OAAAG,CAAQmtB,GACN,MAAMrkB,EAAKqkB,EAAI9oB,UAAUvH,kBAAkB,OAASqwB,EAAI9oB,UAAUvH,kBAAkB,MACpF,IAAKgM,EAAI,OACT,MAAMF,EAAKE,EAAG5S,cACRoiB,EAASmZ,GAAeC,GAAe9oB,IAC7CA,EAAG1S,cAAc0B,aAAa0gB,EAAQ1P,EACxC,EACAxI,KAAM,CAAEitB,QAAS,uBAGnB1tB,EAAOyH,SAASvH,SAAS,cAAe,CACtC,OAAAG,CAAQmtB,GACN,MAAMrkB,EAAKqkB,EAAI9oB,UAAUvH,kBAAkB,OAASqwB,EAAI9oB,UAAUvH,kBAAkB,MACpF,IAAKgM,EAAI,OACT,MAAMF,EAAKE,EAAG5S,cACRoiB,EAASmZ,GAAeC,GAAe9oB,IAC7CA,EAAG1S,cAAc0B,aAAa0gB,EAAQ1P,EAAGkU,YAC3C,EACA1c,KAAM,CAAEitB,QAAS,sBAGnB1tB,EAAOyH,SAASvH,SAAS,eAAgB,CACvC,OAAAG,CAAQmtB,GACN,MAAMrkB,EAAKqkB,EAAI9oB,UAAUvH,kBAAkB,OAASqwB,EAAI9oB,UAAUvH,kBAAkB,MACpF,IAAKgM,EAAI,OACT,MAAM6oB,EAAWC,GAAa9oB,GACxBL,EAAQ0kB,EAAI9oB,UAAUvH,kBAAkB,SACzC2L,GACLA,EAAM2E,iBAAiB,MAAM/X,SAAS2hB,IACpC,MAAMI,EAAOxgB,SAASC,cAA4C,UAA9BmgB,EAAI9gB,cAAcC,QAAsB,KAAO,MACnFihB,EAAKzc,UAAY,OACjB,MAAMk3B,EAAU7a,EAAIC,MAAM0a,GAC1B3a,EAAIpf,aAAawf,EAAMya,EAAO,GAElC,EACAzxB,KAAM,CAAEitB,QAAS,0BAGnB1tB,EAAOyH,SAASvH,SAAS,cAAe,CACtC,OAAAG,CAAQmtB,GACN,MAAMrkB,EAAKqkB,EAAI9oB,UAAUvH,kBAAkB,OAASqwB,EAAI9oB,UAAUvH,kBAAkB,MACpF,IAAKgM,EAAI,OACT,MAAM6oB,EAAWC,GAAa9oB,GACxBL,EAAQ0kB,EAAI9oB,UAAUvH,kBAAkB,SACzC2L,GACLA,EAAM2E,iBAAiB,MAAM/X,SAAS2hB,IACpC,MAAMI,EAAOxgB,SAASC,cAA4C,UAA9BmgB,EAAI9gB,cAAcC,QAAsB,KAAO,MACnFihB,EAAKzc,UAAY,OACjB,MAAMk3B,EAAU7a,EAAIC,MAAM0a,GAC1B3a,EAAIpf,aAAawf,EAAMya,EAAUA,EAAQ/U,YAAc,KAAI,GAE/D,EACA1c,KAAM,CAAEitB,QAAS,yBAGnB1tB,EAAOyH,SAASvH,SAAS,YAAa,CACpC,OAAAG,CAAQmtB,GACN,MAAMrkB,EAAKqkB,EAAI9oB,UAAUvH,kBAAkB,OAASqwB,EAAI9oB,UAAUvH,kBAAkB,MACpF,IAAKgM,EAAI,OACT,MAAMF,EAAKE,EAAG5S,cAEd,GADc0S,EAAG1S,cACP2gB,KAAK1b,QAAU,EAAG,CAE1B,MAAMsN,EAAQ0kB,EAAI9oB,UAAUvH,kBAAkB,SAC9C,GAAI2L,EAAO,CACT,MAAMjC,EAAI5P,SAASC,cAAc,KACjC2P,EAAE7L,UAAY,OACd8N,EAAMvS,cAAc62B,aAAavmB,EAAGiC,EACtC,CACF,MACEG,EAAG8S,QAEP,EACAtb,KAAM,CAAEitB,QAAS,gBAGnB1tB,EAAOyH,SAASvH,SAAS,YAAa,CACpC,OAAAG,CAAQmtB,GACN,MAAMrkB,EAAKqkB,EAAI9oB,UAAUvH,kBAAkB,OAASqwB,EAAI9oB,UAAUvH,kBAAkB,MACpF,IAAKgM,EAAI,OACT,MAAM6oB,EAAWC,GAAa9oB,GACxBL,EAAQ0kB,EAAI9oB,UAAUvH,kBAAkB,SAC9C,IAAK2L,EAAO,OAEZ,MAAM2P,EAAW3P,EAAMlQ,cAAc,MACrC,GAAI6f,GAAYA,EAASnB,MAAM9b,QAAU,EAAG,CAE1C,MAAMqL,EAAI5P,SAASC,cAAc,KACjC2P,EAAE7L,UAAY,OACd8N,EAAMvS,cAAc62B,aAAavmB,EAAGiC,EACtC,MACEA,EAAM2E,iBAAiB,MAAM/X,SAAS2hB,IAChCA,EAAIC,MAAM0a,IACZ3a,EAAIC,MAAM0a,GAAUjW,QACtB,GAGN,EACAtb,KAAM,CAAEitB,QAAS,mBAGnB1tB,EAAOyH,SAASvH,SAAS,cAAe,CACtC,OAAAG,CAAQmtB,GACN,MAAM1kB,EAAQ0kB,EAAI9oB,UAAUvH,kBAAkB,SAC9C,IAAK2L,EAAO,OACZ,MAAMjC,EAAI5P,SAASC,cAAc,KACjC2P,EAAE7L,UAAY,OACd8N,EAAMvS,cAAc62B,aAAavmB,EAAGiC,EACtC,EACArI,KAAM,CAAEitB,QAAS,kBAGnB1tB,EAAOyH,SAASvH,SAAS,aAAc,CACrC,OAAAG,CAAQmtB,GAAKlW,MAAEA,IACb,IAAKA,GAASA,EAAM9b,OAAS,EAAG,OAEhC,MAAM22B,EAAY7a,EAAM,GAClBJ,qBAAWviB,IACX88B,qBAAW98B,IAEjB2iB,EAAM5hB,SAAS+hB,IACbP,EAAKjiB,IAAIwiB,EAAKlhB,cAAc67B,UAC5BX,EAAKx8B,IAAIg9B,GAAaxa,GAAK,IAG7B,MAAMka,EAAUza,EAAK9hB,KACfkjB,EAAUmZ,EAAKr8B,KAGfi9B,EAAY,GAClB/a,EAAMpV,MAAM,GAAGxM,SAAS+hB,IAClBA,EAAK/e,YAAYC,QACnB05B,EAAU1tB,KAAK8S,EAAKzc,WAEtByc,EAAKsE,QAAM,IAETsW,EAAU72B,OAAS,IACrB22B,EAAUn3B,WAAa,OAASq3B,EAAUvrB,KAAK,SAG7C6qB,EAAU,IAAGQ,EAAUR,QAAUA,GACjCrZ,EAAU,IAAG6Z,EAAU7Z,QAAUA,EACvC,EACA7X,KAAM,CAAEitB,QAAS,iBAGnB1tB,EAAOyH,SAASvH,SAAS,YAAa,CACpC,OAAAG,CAAQmtB,GAAK8E,YAAEA,EAAAT,UAAaA,EAAAU,SAAWA,OAAUlxB,EAAAmxB,WAAMA,GAAe,IACpE,MAAM1pB,EAAQ0kB,EAAI9oB,UAAUvH,kBAAkB,SAC9C,IAAK2L,EAAO,OACZ,MAAME,EAAQF,EAAMlQ,cAAc,SAClC,IAAKoQ,EAAO,OACZ,MAAMkO,EAAO/V,MAAMC,KAAK4H,EAAMyE,iBAAiB,OAC/C,GAAoB,IAAhByJ,EAAK1b,OAAc,OAGvB,MAAMi3B,EAAWpxB,GAAQ,CAAC,CAAEixB,cAAaT,UAAWA,GAAa,MAAOU,aAGlEG,EAAU,CAACrb,EAAKyB,KACpB,MAAMxB,EAAQD,EAAI5J,iBAAiB,UACnC,OAAO6J,EAAMwB,IAAMpgB,aAAaC,QAAU,EAAA,EAGtCg6B,EAAmBH,GAAchF,EAAIh5B,SAASo+B,oBA6BpD1b,EAAKnQ,MA3BkB,CAAC0B,EAAGP,KACzB,IAAA,MAAWtT,KAAO69B,EAAU,CAC1B,MAAMI,EAAOH,EAAQjqB,EAAG7T,EAAI09B,aACtBQ,EAAOJ,EAAQxqB,EAAGtT,EAAI09B,aACtBxuB,EAAOlP,EAAI29B,UAAYQ,GAAmB7b,EAAMtiB,EAAI09B,aAC1D,IAAItxB,EAAS,EAEb,GAAI2xB,EACF3xB,EAAS2xB,EAAiBE,EAAMC,EAAMhvB,EAAMlP,EAAI09B,kBAClD,GAAoB,YAATxuB,EAAoB,CAG7B9C,GAFagyB,WAAWH,IAAS,IACpBG,WAAWF,IAAS,EAEnC,MAAA,GAAoB,SAAThvB,EAAiB,CAG1B9C,GAFc,IAAIiyB,KAAKJ,GAAMK,WAAa,IAC5B,IAAID,KAAKH,GAAMI,WAAa,EAE5C,MACElyB,EAAS6xB,EAAKM,cAAcL,OAAM,EAAW,CAAEM,YAAa,SAI9D,GADsB,SAAlBx+B,EAAIi9B,YAAsB7wB,GAAUA,GACzB,IAAXA,EAAc,OAAOA,CAC3B,CACA,OAAO,CAAA,IAITkW,EAAKxhB,SAAQ2hB,GAAOrO,EAAMtR,YAAY2f,KAGtC,MAAMtO,EAAQD,EAAMlQ,cAAc,SAClC,GAAImQ,EAAO,CACT,MAAMsqB,EAAMtqB,EAAM0E,iBAAiB,MACnC4lB,EAAI39B,SAAQwT,IACVA,EAAGkD,gBAAgB,iBACnBlD,EAAGkD,gBAAgB,qBAAoB,IAEzCqmB,EAAS/8B,SAAQ,CAACd,EAAKkkB,KACjBua,EAAIz+B,EAAI09B,eACVe,EAAIz+B,EAAI09B,aAAah7B,aAAa,gBAAiB1C,EAAIi9B,WAAa,OAChEY,EAASj3B,OAAS,GACpB63B,EAAIz+B,EAAI09B,aAAah7B,aAAa,qBAAsBg8B,OAAOxa,EAAM,IAEzE,GAEJ,CACF,EACArY,KAAM,CAAEitB,QAAS,gBAGnB1tB,EAAOyH,SAASvH,SAAS,cAAe,CACtC,OAAAG,CAAQmtB,GAAK8E,YAAEA,cAAaiB,GAAgB,CAAA,GAC1C,MAAMzqB,EAAQ0kB,EAAI9oB,UAAUvH,kBAAkB,SAC9C,IAAK2L,EAAO,OACZ,MAAMC,EAAQD,EAAMlQ,cAAc,SAC5BoQ,EAAQF,EAAMlQ,cAAc,SAClC,IAAKoQ,EAAO,OAGZ,GAAID,EAAO,CACT,MAAMsqB,EAAMtqB,EAAM0E,iBAAiB,MAC/B4lB,EAAIf,KACFiB,EACFF,EAAIf,GAAah7B,aAAa,oBAAqBi8B,GAEnDF,EAAIf,GAAalmB,gBAAgB,qBAGvC,CAGA,MAAMonB,EAAU,GACZzqB,GACFA,EAAM0E,iBAAiB,MAAM/X,SAAQ,CAACwT,EAAI4P,KACxC,MAAM2a,EAAMvqB,EAAGqD,aAAa,qBACxBknB,GAAKD,EAAQ7uB,KAAK,CAAE2tB,YAAaxZ,EAAKzhB,MAAOo8B,EAAIt0B,eAAe,IAK3D6J,EAAMyE,iBAAiB,MAC/B/X,SAAQ2hB,IACX,MAAMC,EAAQD,EAAI5J,iBAAiB,UACpB+lB,EAAQ/oB,MAAKiL,KACR4B,EAAM5B,EAAE4c,cAAc55B,aAAe,IAAIyG,cAC1CmI,SAASoO,EAAEre,SAG5BggB,EAAI5D,UAAUxe,IAAI,kBAElBoiB,EAAI5D,UAAUsI,OAAO,iBACvB,GAEJ,EACAtb,KAAM,CAAEitB,QAAS,kBAGnB1tB,EAAOyH,SAASvH,SAAS,oBAAqB,CAC5C,OAAAG,CAAQmtB,GACN,MAAM1kB,EAAQ0kB,EAAI9oB,UAAUvH,kBAAkB,SAC9C,IAAK2L,EAAO,OACZ,MAAMC,EAAQD,EAAMlQ,cAAc,SAC9BmQ,GACFA,EAAM0E,iBAAiB,MAAM/X,SAAQwT,IACnCA,EAAGkD,gBAAgB,oBAAmB,IAG1C,MAAMpD,EAAQF,EAAMlQ,cAAc,SAC9BoQ,GACFA,EAAMyE,iBAAiB,qBAAqB/X,SAAQ2hB,IAClDA,EAAI5D,UAAUsI,OAAO,iBAAgB,GAG3C,EACAtb,KAAM,CAAEitB,QAAS,yBAGnB1tB,EAAOyH,SAASvH,SAAS,aAAc,CACrC,OAAAG,CAAQmtB,GAAKtuB,OAAEA,EAAA1K,QAAQA,EAAU,CAAA,GAAO,IACtC,MAAM2U,EAAKqkB,EAAI9oB,UAAUvH,kBAAkB,OAASqwB,EAAI9oB,UAAUvH,kBAAkB,MACpF,IAAKgM,IAAOjK,EAAQ,OAEpB,MAAMw0B,EAAWvqB,EAAGoD,aAAa,mBAAqBpD,EAAGzQ,YAAYC,OACrEwQ,EAAG7R,aAAa,iBAAkBo8B,GAClCvqB,EAAG7R,aAAa,mBAAoB4H,GAEpC,MAAMy0B,EAAMX,WAAWU,GAEvB,GAAe,WAAXx0B,GAAwB00B,MAAMD,MAKZ,aAAXz0B,GAA0B00B,MAAMD,MAOrB,eAAXz0B,GAA4B00B,MAAMD,IAG7C,GAAsB,SAAXz0B,EAAmB,CAC5B,MAAM20B,EAAO,IAAIZ,KAAKS,GACjBE,MAAMC,EAAKX,aACd/pB,EAAGzQ,YAAc,IAAIo7B,KAAKC,oBAAe,EAAW,CAClDC,UAAWx/B,EAAQw/B,WAAa,UAC/B90B,OAAO20B,GAEd,MAVmD,CACjD,MAAMI,EAAWz/B,EAAQy/B,UAAY,EACrC9qB,EAAGzQ,aAAqB,IAANi7B,GAAWxhB,QAAQ8hB,GAAY,GACnD,MATE9qB,EAAGzQ,YAAc,IAAIo7B,KAAKI,kBAAa,EAAW,CAChD50B,MAAO,WACP60B,SAAU3/B,EAAQ2/B,UAAY,MAC9BC,sBAAuB5/B,EAAQy/B,UAAY,EAC3CI,sBAAuB7/B,EAAQy/B,UAAY,IAC1C/0B,OAAOy0B,QAVVxqB,EAAGzQ,YAAc,IAAIo7B,KAAKI,kBAAa,EAAW,CAChDE,sBAAuB5/B,EAAQy/B,UAAY,EAC3CI,sBAAuB7/B,EAAQy/B,UAAY,IAC1C/0B,OAAOy0B,EAmBd,EACAlzB,KAAM,CAAEitB,QAAS,iBAGnB1tB,EAAOyH,SAASvH,SAAS,mBAAoB,CAC3C,OAAAG,CAAQmtB,GACN,MAAM1kB,EAAQ0kB,EAAI9oB,UAAUvH,kBAAkB,SACzC2L,GACLwrB,GAAsBxrB,EACxB,EACArI,KAAM,CAAEitB,QAAS,uBAGnB1tB,EAAOyH,SAASvH,SAAS,YAAa,CACpC,OAAAG,CAAQmtB,GACN,MAAMrkB,EAAKqkB,EAAI9oB,UAAUvH,kBAAkB,OAASqwB,EAAI9oB,UAAUvH,kBAAkB,MACpF,IAAKgM,EAAI,OACT,MAAMorB,EAAUprB,EAAGmP,SAAW,EACxBkc,EAAUrrB,EAAGwoB,SAAW,EAC9B,GAAI4C,GAAW,GAAKC,GAAW,EAAG,OAElCrrB,EAAGmP,QAAU,EACbnP,EAAGwoB,QAAU,EAGb,MAAM1oB,EAAKE,EAAG5S,cACRk+B,EAAUxrB,EAAGsN,QAAQ,SAAW,KAAOpN,EAAG3S,QAAQ2I,cACxD,IAAA,IAAS0Y,EAAI,EAAGA,EAAI0c,EAAS1c,IAAK,CAChC,MAAM6c,EAAUz9B,SAASC,cAAcu9B,GACvCC,EAAQ15B,UAAY,OACpBiO,EAAGhR,aAAay8B,EAASvrB,EAAGgU,YAC9B,CAGA,GAAIqX,EAAU,EAAG,CAEf,MAAMG,EAAiB1C,GAAa9oB,GACpC,IAAIyrB,EAAa3rB,EAAG4rB,mBACpB,IAAA,IAAStc,EAAI,EAAGA,EAAIic,GACbI,EADsBrc,IAAK,CAEhC,MAAMuc,EAAaF,EAAWre,QAAQ,SAAW,KAAO,KAExD,IAAIwe,EAAY,KACZC,EAAW,EACf,IAAA,MAAWvd,KAAQmd,EAAWtd,MAAO,CACnC,GAAI0d,GAAYL,EAAgB,CAC9BI,EAAYtd,EACZ,KACF,CACAud,GAAYvd,EAAKa,SAAW,CAC9B,CACA,IAAA,IAAST,EAAI,EAAGA,EAAI0c,EAAS1c,IAAK,CAChC,MAAM6c,EAAUz9B,SAASC,cAAc49B,GACvCJ,EAAQ15B,UAAY,OACpB45B,EAAW38B,aAAay8B,EAASK,EACnC,CACAH,EAAaA,EAAWC,kBAC1B,CACF,CACF,EACAp0B,KAAM,CAAEitB,QAAS,eAErB,CAEA,SAASqE,GAAe9oB,GACtB,IAAIgsB,EAAQ,EACZ,IAAA,MAAWxd,KAAQxO,EAAGqO,MACpB2d,GAASxd,EAAKa,SAAW,EAE3B,OAAO2c,CACT,CAEA,SAAShD,GAAaxa,GACpB,IAAIlT,EAAQ,EACR6T,EAAOX,EAAKY,uBAChB,KAAOD,GACL7T,GAAS6T,EAAKE,SAAW,EACzBF,EAAOA,EAAKC,uBAEd,OAAO9T,CACT,CAEA,SAASutB,GAAepZ,GACtB,MAAMzP,EAAKhS,SAASC,cAAc,MAClC,IAAA,IAASuK,EAAI,EAAGA,EAAIiX,EAAUjX,IAAK,CACjC,MAAM0H,EAAKlS,SAASC,cAAc,MAClCiS,EAAGnO,UAAY,OACfiO,EAAGvR,YAAYyR,EACjB,CACA,OAAOF,CACT,CAKA,SAAS8pB,GAAmB7b,EAAMob,GAChC,IAAI4C,EAAe,EACfC,EAAY,EACZhc,EAAQ,EAEZ,IAAA,MAAW9B,KAAOH,EAAM,CACtB,MAAMI,EAAQD,EAAI5J,iBAAiB,UAC7B/N,GAAQ4X,EAAMgb,IAAc55B,aAAe,IAAIC,OAChD+G,IACLyZ,KACKya,MAAMZ,WAAWtzB,KAAU01B,SAAS11B,GAAOw1B,KACtCtB,MAAM,IAAIX,KAAKvzB,GAAMwzB,YAAcxzB,EAAKlE,OAAS,GAAG25B,IAChE,CAEA,OAAc,IAAVhc,EAAoB,eACpB+b,EAAe/b,EAAQ,GAAY,UACnCgc,EAAYhc,EAAQ,GAAY,OAC7B,cACT,CAIA,MAAMkc,GAAoB,CACxBC,IAAM//B,GAASA,EAAKggC,OAAOC,QAAO,CAAC/sB,EAAGP,IAAMO,GAAKuqB,WAAW9qB,IAAM,IAAI,GACtEutB,QAAUlgC,IACR,MAAMggC,EAAOhgC,EAAKggC,OAAO3uB,KAAIsb,GAAK8Q,WAAW9Q,KAAI5O,QAAO4O,IAAM0R,MAAM1R,KACpE,OAAOqT,EAAK/5B,OAAS+5B,EAAKC,QAAO,CAAC/sB,EAAGP,IAAMO,EAAIP,GAAG,GAAKqtB,EAAK/5B,OAAS,CAAA,EAEvEk6B,MAAQngC,GAASA,EAAKggC,OAAOjiB,QAAO4O,GAAW,KAANA,GAAiB,MAALA,IAAW1mB,OAChEm6B,IAAMpgC,GAAS6C,KAAKsE,OAAOnH,EAAKggC,OAAO3uB,QAASosB,WAAW9Q,KAAI5O,YAAasgB,MAAM1R,MAClF0T,IAAMrgC,GAAS6C,KAAKy9B,OAAOtgC,EAAKggC,OAAO3uB,QAASosB,WAAW9Q,KAAI5O,YAAasgB,MAAM1R,MAClF4T,GAAKvgC,GAASA,EAAK,GAAKA,EAAK,GAAMA,EAAK,IAAM,GAC9CwgC,OAASxgC,GAASA,EAAKggC,OAAOzuB,KAAK,KAMrC,SAASkvB,GAAaC,GACpB,MAAMC,EAAQD,EAAIC,MAAM,mBACxB,IAAKA,EAAO,OAAO,KACnB,IAAIC,EAAM,EACV,IAAA,IAAS10B,EAAI,EAAGA,EAAIy0B,EAAM,GAAG16B,OAAQiG,IACnC00B,EAAY,GAANA,GAAYD,EAAM,GAAGv0B,WAAWF,GAAK,IAE7C,MAAO,CAAE00B,IAAKA,EAAM,EAAG9e,IAAK+e,SAASF,EAAM,GAAI,IAAM,EACvD,CAwBA,SAASG,GAAkBvtB,EAAOqtB,EAAK9e,GACrC,MACMpO,EADUH,EAAM2E,iBAAiB,sBACpB4J,GACnB,IAAKpO,EAAI,MAAO,GAChB,MAAMqO,EAAQrO,EAAGwE,iBAAiB,UAClC,OAAK6J,EAAM6e,GAEJ7e,EAAM6e,GAAK5pB,aAAa,mBAAqB+K,EAAM6e,GAAKz9B,YAAYC,OAFnD,EAG1B,CAsEA,SAAS29B,GAAgBC,EAAYztB,EAAO0tB,iBAAY,IAAI7hC,KAE1D,MACMyf,EApER,SAAyBqiB,GACvB,MAAMriB,EAAS,GACf,IAAI3S,EAAI,EACR,KAAOA,EAAIg1B,EAAQj7B,QAAQ,CACzB,MAAMk7B,EAAKD,EAAQh1B,GACnB,GAAI,KAAKrC,KAAKs3B,GAAOj1B,SACrB,GAAI,UAAU6F,SAASovB,GACrBtiB,EAAOzP,KAAK,CAAEb,KAAM,KAAMzM,MAAOq/B,IACjCj1B,SAGF,GAAW,MAAPi1B,GAAqB,MAAPA,GAAqB,MAAPA,EAUhC,GAAI,KAAKt3B,KAAKs3B,IAAe,MAAPA,GAAc,KAAKt3B,KAAKq3B,EAAQh1B,EAAI,IAA1D,CACE,IAAIkyB,EAAM,GACV,KAAOlyB,EAAIg1B,EAAQj7B,SAAW,KAAK4D,KAAKq3B,EAAQh1B,KAAsB,MAAfg1B,EAAQh1B,KAC7DkyB,GAAO8C,EAAQh1B,KAEjB2S,EAAOzP,KAAK,CAAEb,KAAM,SAAUzM,MAAO27B,WAAWW,IAElD,MACA,GAAW,MAAP+C,EAQJ,GAAI,SAASt3B,KAAKs3B,GAAlB,CACE,IAAIC,EAAQ,GACZ,KAAOl1B,EAAIg1B,EAAQj7B,QAAU,YAAY4D,KAAKq3B,EAAQh1B,KACpDk1B,GAASF,EAAQh1B,KAGnB,GAAmB,MAAfg1B,EAAQh1B,GAAY,CACtBA,IACA,IAAIm1B,EAAM,GACV,KAAOn1B,EAAIg1B,EAAQj7B,QAAU,YAAY4D,KAAKq3B,EAAQh1B,KACpDm1B,GAAOH,EAAQh1B,KAEjB2S,EAAOzP,KAAK,CAAEb,KAAM,QAASzM,MAAOs/B,EAAM9/B,cAAgB,IAAM+/B,EAAI//B,eACtE,KAAW,eAAeuI,KAAKu3B,GAC7BviB,EAAOzP,KAAK,CAAEb,KAAM,UAAWzM,MAAOs/B,EAAM9/B,gBAE5Cud,EAAOzP,KAAK,CAAEb,KAAM,OAAQzM,MAAOs/B,EAAM9/B,eAG7C,MACA4K,QA5BA,CACE,IAAIF,EAAM,GAEV,IADAE,IACOA,EAAIg1B,EAAQj7B,QAAyB,MAAfi7B,EAAQh1B,IAAYF,GAAOk1B,EAAQh1B,KAChEA,IACA2S,EAAOzP,KAAK,CAAEb,KAAM,SAAUzM,MAAOkK,GAEvC,KAxByB,MAAnBk1B,EAAQh1B,EAAI,IACd2S,EAAOzP,KAAK,CAAEb,KAAM,KAAMzM,MAAOq/B,EAAK,MACtCj1B,GAAK,IAEL2S,EAAOzP,KAAK,CAAEb,KAAM,KAAMzM,MAAOq/B,IACjCj1B,IAyCN,CACA,OAAO2S,CACT,CAQiByiB,CADMN,EAAWpqB,WAAW,KAAOoqB,EAAWr0B,MAAM,GAAKq0B,GAExE,IAAIO,EAAM,EAEV,MAAMC,EAAO,IAAM3iB,EAAO0iB,IAAQ,KAC5BlrB,EAAO,IAAMwI,EAAO0iB,KAE1B,SAASE,IACP,IAAIhY,EAAOiY,IACX,KAAOF,MAA4B,MAAjBA,IAAO1/B,OAAkC,MAAjB0/B,IAAO1/B,QAAgB,CAC/D,MAAM6/B,EAAKtrB,IAAOvU,MACZ8/B,EAAQF,IACdjY,EAAc,MAAPkY,EAAalY,EAAOmY,EAAQnY,EAAOmY,CAC5C,CACA,OAAOnY,CACT,CAEA,SAASiY,IACP,IAAIjY,EAAOoY,IACX,KAAOL,MAA4B,MAAjBA,IAAO1/B,OAAkC,MAAjB0/B,IAAO1/B,QAAgB,CAC/D,MAAM6/B,EAAKtrB,IAAOvU,MACZ8/B,EAAQC,IACdpY,EAAc,MAAPkY,EAAalY,EAAOmY,EAAmB,IAAVA,EAAcnY,EAAOmY,EAAQE,GACnE,CACA,OAAOrY,CACT,CAEA,SAASoY,IACP,IAAIpY,EAAOsY,IACX,GAAIP,KAAU,CAAC,IAAK,IAAK,KAAM,KAAM,MAAMzvB,SAASyvB,IAAO1/B,OAAQ,CACjE,MAAM6/B,EAAKtrB,IAAOvU,MACZ8/B,EAAQG,IACd,GAAW,MAAPJ,EAAY,OAAOlY,EAAOmY,EAAQ,EAAI,EAC1C,GAAW,MAAPD,EAAY,OAAOlY,EAAOmY,EAAQ,EAAI,EAC1C,GAAW,OAAPD,EAAa,OAAOlY,GAAQmY,EAAQ,EAAI,EAC5C,GAAW,OAAPD,EAAa,OAAOlY,GAAQmY,EAAQ,EAAI,EAC5C,GAAW,OAAPD,EAAa,OAAOlY,IAASmY,EAAQ,EAAI,CAC/C,CACA,OAAOnY,CACT,CAEA,SAASsY,IACP,MAAMC,EAAQR,IACd,IAAKQ,EAAO,OAAO,EAEnB,GAAmB,WAAfA,EAAMzzB,KAER,OADA8H,IACO2rB,EAAMlgC,MAEf,GAAmB,WAAfkgC,EAAMzzB,KAER,OADA8H,IACO2rB,EAAMlgC,MAEf,GAAmB,YAAfkgC,EAAMzzB,KAAoB,CAC5B8H,IACA,MAAMqqB,EAAMD,GAAauB,EAAMlgC,OAC/B,IAAK4+B,EAAK,OAAO,EACjB,MAAMuB,EAAUD,EAAMlgC,MACtB,GAAIm/B,EAAU/hC,IAAI+iC,GAAU,MAAO,SACnChB,EAAUvhC,IAAIuiC,GACd,MAAMjmB,EAAM8kB,GAAkBvtB,EAAOmtB,EAAIE,IAAKF,EAAI5e,KAE5CU,EAAUjP,EAAM2E,iBAAiB,sBACjCgK,EAAOM,EAAQke,EAAI5e,MAAM5J,iBAAiB,UAAUwoB,EAAIE,KACxDsB,EAAchgB,GAAMlL,aAAa,gBACvC,IAAIknB,EAQJ,OAPIgE,EACFhE,EAAM6C,GAAgBmB,EAAa3uB,EAAO0tB,IAE1C/C,EAAMT,WAAWzhB,GACbqiB,MAAMH,KAAMA,EAAMliB,IAExBilB,EAAUrhC,OAAOqiC,GACV/D,CACT,CACA,GAAmB,UAAf8D,EAAMzzB,KAAkB,CAC1B8H,IACA,MAAM8rB,EAhLZ,SAAqBC,GACnB,MAAM1wB,EAAQ0wB,EAAShxB,MAAM,KAC7B,GAAqB,IAAjBM,EAAMzL,OAAc,OAAO,KAC/B,MAAMo8B,EAAQ5B,GAAa/uB,EAAM,IAC3B2vB,EAAMZ,GAAa/uB,EAAM,IAC/B,IAAK2wB,IAAUhB,EAAK,OAAO,KAC3B,MAAMc,EAAO,GACb,IAAA,IAASnf,EAAIqf,EAAMvgB,IAAKkB,GAAKqe,EAAIvf,IAAKkB,IACpC,IAAA,IAASV,EAAI+f,EAAMzB,IAAKte,GAAK+e,EAAIT,IAAKte,IACpC6f,EAAK/yB,KAAK,CAAEwxB,IAAKte,EAAGR,IAAKkB,IAG7B,OAAOmf,CACT,CAmKmBG,CAAYN,EAAMlgC,OAC/B,OAAKqgC,EACEA,EAAK9wB,KAAI2R,IACd,MAAMhH,EAAM8kB,GAAkBvtB,EAAOyP,EAAE4d,IAAK5d,EAAElB,KACxCsc,EAAMX,WAAWzhB,GACvB,OAAOqiB,MAAMD,GAAOpiB,EAAMoiB,CAAA,IAJV,EAMpB,CACA,GAAmB,SAAf4D,EAAMzzB,KAAiB,CACzB,MAAMg0B,EAAWlsB,IAAOvU,MAClB0gC,EAAK1C,GAAkByC,GAC7B,IAAKC,EAAI,MAAO,SAEM,MAAlBhB,KAAQ1/B,OAAeuU,IAC3B,MAAMrW,EAAO,GACb,KAAOwhC,KAA2B,MAAjBA,IAAO1/B,OAAe,CACrC,GAAqB,MAAjB0/B,IAAO1/B,MAAe,CAAEuU,IAAQ,QAAS,CAC7C,MAAMosB,EAAuB,UAAjBjB,KAAQjzB,KAAmBwzB,IAAgBN,IACvDzhC,EAAKoP,KAAKqzB,EACZ,CAEA,MADsB,MAAlBjB,KAAQ1/B,OAAeuU,IACpBmsB,EAAGxiC,EACZ,CACA,GAAoB,MAAhBgiC,EAAMlgC,MAAe,CACvBuU,IACA,MAAM6nB,EAAMuD,IAEZ,MADsB,MAAlBD,KAAQ1/B,OAAeuU,IACpB6nB,CACT,CACA,MAAoB,MAAhB8D,EAAMlgC,OACRuU,KACQ0rB,MAGV1rB,IACO,EACT,CAEA,OAAOorB,GACT,CAKO,SAAS1C,GAAsBxrB,GACpC,MAAMwO,EAAQxO,EAAM2E,iBAAiB,sCACrC,IAAA,MAAWgK,KAAQH,EAAO,CACxB,MAAMmf,EAAUhf,EAAKlL,aAAa,gBAClC,IAAKkqB,EAAS,SACd,MAAMz1B,EAASs1B,GAAgBG,EAAS3tB,GAClB,iBAAX9H,GAAuBA,EAAOmL,WAAW,KAClDsL,EAAK/e,YAAcsI,EAGnByW,EAAK/e,YAFsB,iBAAXsI,EAEGsyB,OAAOl7B,KAAKihB,MAAe,KAATrY,GAAiB,MAEnCA,CAEvB,CACF,CCzwBA,MAAMi3B,sBAAiBtjC,IAAI,CACzB,IAAK,KAAM,KAAM,KAAM,KAAM,KAAM,KACnC,aAAc,MAAO,KAAM,KAAM,QAAS,KAAM,MAAO,YAQzD,SAASujC,GAAiB1K,GACxB,MAAM/0B,EAAQ+0B,EAAI9oB,UAAUxH,iBAC5B,IAAKzE,EAAO,OAAO,KACnB,IAAItC,EAAKsC,EACT,KAAOtC,GAAMA,EAAGI,gBAAkBi3B,EAAI31B,SACpC1B,EAAKA,EAAGI,cAEV,OAAQJ,GAAM8hC,GAAWxjC,IAAI0B,EAAGK,SAAYL,EAAK,IACnD,CAqBO,SAASgiC,GAAsBn4B,GACpCA,EAAOyH,SAASvH,SAAS,aAAc,CACrC,OAAAG,CAAQmtB,GACN,MAAM14B,EAAW04B,EAAI9oB,UAAUvH,kBAAkB,cACjD,GAAIrI,EAAU,CAEZ,MAAMgD,EAAShD,EAASiD,WACxB,KAAOjD,EAASkD,YACdF,EAAOG,aAAanD,EAASkD,WAAYlD,GAE3CgD,EAAOI,YAAYpD,EACrB,KAAO,CAEL,MAAM2D,EAAQ+0B,EAAI9oB,UAAUxH,iBAC5B,IAAKzE,EAAO,OACZ,MAAM2/B,EAAKnhC,SAASC,cAAc,cAClCuB,EAAMV,WAAWq1B,aAAagL,EAAI3/B,GAClC2/B,EAAG1gC,YAAYe,EACjB,CACF,EACA6H,SAASktB,KACEA,EAAI9oB,UAAUvH,kBAAkB,cAE3CqD,SAAU,cACVC,KAAM,CAAEgtB,KAAM,aAAcC,QAAS,gBAGvC1tB,EAAOyH,SAASvH,SAAS,YAAa,CACpC,OAAAG,CAAQmtB,GAAK6K,SAAEA,GAAa,CAAA,GAC1B,MAAMvjC,EAAW04B,EAAI9oB,UAAUvH,kBAAkB,OACjD,GAAIrI,EAAU,CAEZ,MAAM4K,EAAO5K,EAAS4D,YAChBmO,EAAI5P,SAASC,cAAc,KACjC2P,EAAEnO,YAAcgH,EAChB5K,EAASiD,WAAWq1B,aAAavmB,EAAG/R,GAEpC,MAAMiC,EAAQE,SAASkE,cACvBpE,EAAMqE,mBAAmByL,GACzB9P,EAAM4F,UAAS,GACf6wB,EAAI9oB,UAAUnK,SAASxD,EACzB,KAAO,CACL,MAAMA,EAAQy2B,EAAI9oB,UAAU7K,WAC5B,IAAK9C,EAAO,OACZ,MAAM2I,EAAO3I,EAAM2E,UAAY,KAAO3E,EAAMuB,WACtC8Q,EAAMnS,SAASC,cAAc,OAC7BmS,EAAOpS,SAASC,cAAc,QAcpC,GAbAmS,EAAK3Q,YAAcgH,EACf24B,IACFhvB,EAAK/R,aAAa,gBAAiB+gC,GACnCjvB,EAAI9R,aAAa,gBAAiB+gC,IAEpCjvB,EAAI1R,YAAY2R,GAEXtS,EAAM2E,WACT3E,EAAM0G,iBAER1G,EAAMY,WAAWyR,IAGZA,EAAI+T,YAAa,CACpB,MAAMtW,EAAI5P,SAASC,cAAc,KACjC2P,EAAE7L,UAAY,OACdoO,EAAIrR,WAAWE,aAAa4O,EAAGuC,EAAI+T,YACrC,CAEA,MAAMtf,EAAW5G,SAASkE,cAC1B0C,EAASzC,mBAAmBiO,GAC5BxL,EAASlB,UAAS,GAClB6wB,EAAI9oB,UAAUnK,SAASsD,GAGvB2vB,EAAIvsB,SAASzL,KAAK,oBAAqB,CAAEqC,QAASuR,EAAKivB,YACzD,CACF,EACA/3B,SAASktB,KACEA,EAAI9oB,UAAUvH,kBAAkB,OAE3CqD,SAAU,cACVC,KAAM,CAAEgtB,KAAM,YAAaC,QAAS,gBAGtC1tB,EAAOyH,SAASvH,SAAS,iBAAkB,CACzC,OAAAG,CAAQmtB,GACN,MAAMz2B,EAAQy2B,EAAI9oB,UAAU7K,WAC5B,IAAK9C,EAAO,OAEZ,MAAMuS,EAAKrS,SAASC,cAAc,MAKlC,GAJAH,EAAM0G,iBACN1G,EAAMY,WAAW2R,IAGZA,EAAG6T,aAA0C,MAA3B7T,EAAG6T,YAAY3mB,QAAiB,CACrD,MAAMqQ,EAAI5P,SAASC,cAAc,KACjC2P,EAAE7L,UAAY,OACdsO,EAAGvR,WAAWE,aAAa4O,EAAGyC,EAAG6T,YACnC,CAGA,MAAMtf,EAAW5G,SAASkE,cAC1B0C,EAASC,cAAcwL,GACvBzL,EAASlB,UAAS,GAClB6wB,EAAI9oB,UAAUnK,SAASsD,EACzB,EACA4C,KAAM,CAAEgtB,KAAM,iBAAkBC,QAAS,qBAK3C1tB,EAAOyH,SAASvH,SAAS,cAAe,CACtC,OAAAG,CAAQmtB,GACN,MAAM/0B,EAAQy/B,GAAiB1K,GAC1B/0B,GAAUA,EAAM4f,yBACrBmV,EAAI1sB,QAAQC,WACZtI,EAAMV,WAAWE,aAAaQ,EAAOA,EAAM4f,wBAC3CmV,EAAIvsB,SAASzL,KAAK,kBACpB,EACAiL,KAAM,CAAEitB,QAAS,mBAGnB1tB,EAAOyH,SAASvH,SAAS,gBAAiB,CACxC,OAAAG,CAAQmtB,GACN,MAAM/0B,EAAQy/B,GAAiB1K,GAC1B/0B,GAAUA,EAAMo8B,qBACrBrH,EAAI1sB,QAAQC,WACZtI,EAAMV,WAAWE,aAAaQ,EAAOA,EAAMo8B,mBAAmB1X,aAC9DqQ,EAAIvsB,SAASzL,KAAK,kBACpB,EACAiL,KAAM,CAAEitB,QAAS,qBAGnB1tB,EAAOyH,SAASvH,SAAS,iBAAkB,CACzC,OAAAG,CAAQmtB,GACN,MAAM/0B,EAAQy/B,GAAiB1K,GAC/B,IAAK/0B,EAAO,OACZ+0B,EAAI1sB,QAAQC,WACZ,MAAMu3B,EAAQ7/B,EAAMsmB,WAAU,GAC9BtmB,EAAMV,WAAWE,aAAaqgC,EAAO7/B,EAAM0kB,aAE3C,MAAMpmB,EAAQE,SAASkE,cACvBpE,EAAMqE,mBAAmBk9B,GACzBvhC,EAAM4F,UAAS,GACf6wB,EAAI9oB,UAAUnK,SAASxD,GACvBy2B,EAAIvsB,SAASzL,KAAK,iBACpB,EACAiL,KAAM,CAAEitB,QAAS,qBAGnB1tB,EAAOyH,SAASvH,SAAS,cAAe,CACtC,OAAAG,CAAQmtB,GACN,MAAM/0B,EAAQy/B,GAAiB1K,GAC/B,IAAK/0B,EAAO,OACZ+0B,EAAI1sB,QAAQC,WAEZ,MAAM6K,EAAOnT,EAAMo8B,mBACbzc,EAAO3f,EAAM4f,uBACnB5f,EAAMV,WAAWG,YAAYO,GA3KnC,SAA8B+0B,GAC5B,GAAoC,IAAhCA,EAAI31B,QAAQ4lB,SAASjiB,OAAc,CACrC,MAAMqL,EAAI5P,SAASC,cAAc,KACjC2P,EAAE7L,UAAY,OACdwyB,EAAI31B,QAAQH,YAAYmP,GACxB,MAAM9P,EAAQE,SAASkE,cACvBpE,EAAMqE,mBAAmByL,GACzB9P,EAAM4F,UAAS,GACf6wB,EAAI9oB,UAAUnK,SAASxD,EACzB,CACF,CAkKMwhC,CAAqB/K,GAErB,MAAM7tB,EAASiM,GAAQwM,GAAQoV,EAAI31B,QAAQ2gC,kBAC3C,GAAI74B,EAAQ,CACV,MAAM5I,EAAQE,SAASkE,cACvBpE,EAAMqE,mBAAmBuE,GACzB5I,EAAM4F,UAAS,GACf6wB,EAAI9oB,UAAUnK,SAASxD,EACzB,CACAy2B,EAAIvsB,SAASzL,KAAK,iBACpB,EACAiL,KAAM,CAAEitB,QAAS,kBAGnB1tB,EAAOyH,SAASvH,SAAS,eAAgB,CACvC,OAAAG,CAAQmtB,GAAKnQ,OAAEA,GAAW,CAAA,GACnBA,GAAWlc,MAAMmmB,QAAQjK,IAC9BA,EAAO3nB,SAASS,IACVA,GAAMA,EAAGsd,WACXtd,EAAGsd,UAAUxe,IAAI,qBACnB,GAEJ,EACAwL,KAAM,CAAEitB,QAAS,mBAGnB1tB,EAAOyH,SAASvH,SAAS,sBAAuB,CAC9C,OAAAG,CAAQmtB,GACWA,EAAI31B,QAAQ4V,iBAAiB,uBACrC/X,SAASS,GAAOA,EAAGsd,UAAUsI,OAAO,uBAC/C,EACAtb,KAAM,CAAEitB,QAAS,2BAKnB1tB,EAAOyH,SAASvH,SAAS,iBAAkB,CACzC,OAAAG,CAAQmtB,GACN,MAAM/0B,EAAQy/B,GAAiB1K,GAC/B,GAAK/0B,EAAL,CAGA,GAFA+0B,EAAI1sB,QAAQC,WAEU,YAAlBtI,EAAMjC,QAAuB,CAE/B,MAAMsB,EAASW,EAAMV,WACf0gC,EAAUhgC,EAAMG,cAAc,WAEnBuI,MAAMC,KAAK3I,EAAMoT,YAAYyH,QAAQolB,GAAMA,IAAMD,IACzD/iC,SAASoW,GAAUhU,EAAOG,aAAa6T,EAAOrT,KACvDX,EAAOI,YAAYO,EACrB,KAAO,CAEL,MAAMkgC,EAAU1hC,SAASC,cAAc,WACvCyhC,EAAQla,UAAY,kBACpBka,EAAQrhC,aAAa,OAAQ,IAC7B,MAAMmhC,EAAUxhC,SAASC,cAAc,WACvCuhC,EAAQ//B,YAAcD,EAAMC,YAAYwJ,MAAM,EAAG,KAAO,UACxDzJ,EAAMV,WAAWq1B,aAAauL,EAASlgC,GACvCkgC,EAAQjhC,YAAY+gC,GACpBE,EAAQjhC,YAAYe,EACtB,CACA+0B,EAAIvsB,SAASzL,KAAK,iBAtBN,CAuBd,EACAiL,KAAM,CAAEitB,QAAS,gCAKnB1tB,EAAOyH,SAASvH,SAAS,cAAe,CACtC,OAAAG,CAAQmtB,GACN,MAAMoL,EAAWz3B,MAAMC,KAAKosB,EAAI31B,QAAQ4V,iBAAiB,wBACzD,GAAwB,IAApBmrB,EAASp9B,OAAc,OAC3BgyB,EAAI1sB,QAAQC,WAEZ,MAAMmmB,EAAQjwB,SAASC,cAAc,OACrCgwB,EAAMzI,UAAY,kBAGlBma,EAAS,GAAG7gC,WAAWE,aAAaivB,EAAO0R,EAAS,IAGpDA,EAASljC,SAASS,IAChBA,EAAGsd,UAAUsI,OAAO,sBACpBmL,EAAMxvB,YAAYvB,EAAE,IAGtBq3B,EAAIvsB,SAASzL,KAAK,iBACpB,EACAiL,KAAM,CAAEitB,QAAS,2BAGnB1tB,EAAOyH,SAASvH,SAAS,gBAAiB,CACxC,OAAAG,CAAQmtB,GACN,MAAM/0B,EAAQy/B,GAAiB1K,GAC/B,IAAK/0B,EAAO,OAGZ,IAAIyuB,EAAQzuB,EAIZ,GAHKyuB,EAAMzT,UAAU/Y,SAAS,qBAC5BwsB,EAAQzuB,EAAM8d,QAAQ,sBAEnB2Q,IAAUA,EAAMzT,UAAU/Y,SAAS,mBAAoB,OAE5D8yB,EAAI1sB,QAAQC,WACZ,MAAMjJ,EAASovB,EAAMnvB,WACrB,KAAOmvB,EAAMlvB,YACXF,EAAOG,aAAaivB,EAAMlvB,WAAYkvB,GAExCpvB,EAAOI,YAAYgvB,GACnBsG,EAAIvsB,SAASzL,KAAK,iBACpB,EACAiL,KAAM,CAAEitB,QAAS,oBAGnB1tB,EAAOyH,SAASvH,SAAS,YAAa,CACpC,OAAAG,CAAQmtB,GAAKqE,UAAEA,GAAc,CAAA,GAC3B,MAAMp5B,EAAQy/B,GAAiB1K,GAC/B,IAAK/0B,EAAO,OACZ,IAAIyuB,EAAQzuB,EAAMgb,UAAU/Y,SAAS,mBACjCjC,EACAA,EAAM8d,QAAQ,oBAClB,GAAK2Q,GAAUA,EAAMzT,UAAU/Y,SAAS,mBAAxC,CAEA,KAAOwsB,EAAM3wB,gBAAkBi3B,EAAI31B,SAEjC,GADAqvB,EAAQA,EAAM3wB,eACT2wB,EAAO,OAGdsG,EAAI1sB,QAAQC,WACM,OAAd8wB,GAAsB3K,EAAM7O,uBAC9B6O,EAAMnvB,WAAWE,aAAaivB,EAAOA,EAAM7O,wBACpB,SAAdwZ,GAAwB3K,EAAM2N,oBACvC3N,EAAMnvB,WAAWE,aAAaivB,EAAOA,EAAM2N,mBAAmB1X,aAEhEqQ,EAAIvsB,SAASzL,KAAK,iBAb0C,CAc9D,EACAiL,KAAM,CAAEitB,QAAS,gBAGnB1tB,EAAOyH,SAASvH,SAAS,iBAAkB,CACzC,OAAAG,CAAQmtB,GACN,MAAM/0B,EAAQy/B,GAAiB1K,GAC/B,IAAK/0B,EAAO,OACZ,IAAIyuB,EAAQzuB,EAAMgb,UAAU/Y,SAAS,mBACjCjC,EACAA,EAAM8d,QAAQ,oBAClB,IAAK2Q,IAAUA,EAAMzT,UAAU/Y,SAAS,mBAAoB,OAE5D,KAAOwsB,EAAM3wB,gBAAkBi3B,EAAI31B,SAEjC,GADAqvB,EAAQA,EAAM3wB,eACT2wB,EAAO,OAGdsG,EAAI1sB,QAAQC,WACZ,MAAMu3B,EAAQpR,EAAMnI,WAAU,GAC9BmI,EAAMnvB,WAAWE,aAAaqgC,EAAOpR,EAAM/J,aAC3CqQ,EAAIvsB,SAASzL,KAAK,iBACpB,EACAiL,KAAM,CAAEitB,QAAS,oBAErB,CC9VA,MAAMmL,GAAiB,CACrBC,UAAW,CAAEliC,IAAK,KAClBmiC,SAAU,CAAEniC,IAAK,MACjBoiC,SAAU,CAAEpiC,IAAK,MACjBqiC,SAAU,CAAEriC,IAAK,MACjBsiC,SAAU,CAAEtiC,IAAK,MACjBuiC,SAAU,CAAEviC,IAAK,MACjBwiC,SAAU,CAAExiC,IAAK,MACjB6H,WAAY,CAAE7H,IAAK,cACnB8H,UAAW,CAAE9H,IAAK,OAClB4H,cAAe,CAAE5H,IAAK,MACtB2H,YAAa,CAAE3H,IAAK,OAiGf,SAASyiC,GAA6Br5B,GAC3CA,EAAOyH,SAASvH,SAAS,eAAgB,CACvC,OAAAG,CAAQmtB,GAAK8L,GAAEA,GAAO,CAAA,GACpB,IAAKA,IAAOT,GAAeS,GAAK,OAEhC,MAAM7gC,EA9FZ,SAAyB+0B,GACvB,MAAM/0B,EAAQ+0B,EAAI9oB,UAAUxH,iBAC5B,IAAKzE,EAAO,OAAO,KAEnB,IAAItC,EAAKsC,EACT,KAAOtC,GAAMA,EAAGI,gBAAkBi3B,EAAI31B,SACpC1B,EAAKA,EAAGI,cAEV,OAAOJ,GAAM,IACf,CAqFoBojC,CAAgB/L,GAC9B,IAAK/0B,EAAO,OAGZ,MAAM+gC,EAAYX,GAAeS,GAAI1iC,IACrC,GAAI6B,EAAMjC,UAAYgjC,EAAW,OAEjChM,EAAI1sB,QAAQC,WAEZ,MAAMpD,EAtFZ,SAA6BlF,GAC3B,MAAM7B,EAAM6B,EAAMjC,QAElB,GAAY,OAARI,GAAwB,OAARA,EAAc,CAEhC,MAAMkb,EAAQ3Q,MAAMC,KAAK3I,EAAMgV,iBAAiB,OAChD,OAAqB,IAAjBqE,EAAMtW,OACDsW,EAAM,GAAG9W,UAGX8W,EAAMlL,KAAKiC,GAAOA,EAAG7N,YAAW8L,KAAK,OAC9C,CAEA,GAAY,QAARlQ,EAAe,CACjB,MAAMyS,EAAO5Q,EAAMG,cAAc,QACjC,OAAOyQ,EAAOA,EAAK3Q,YAAcD,EAAMC,WACzC,CAEA,MAAY,eAAR9B,GAE4B,IAA1B6B,EAAMglB,SAASjiB,OACV/C,EAAMglB,SAAS,GAAGziB,UAKtBvC,EAAMuC,SACf,CA2DsBy+B,CAAoBhhC,GAC9BihC,EApDZ,SAA2BJ,EAAI37B,GAC7B,MAAMg8B,EAAOd,GAAeS,GAC5B,IAAKK,EAAM,OAAO,KAElB,GAAW,kBAAPL,GAAiC,gBAAPA,EAAsB,CAClD,MAAM/I,EAAOt5B,SAASC,cAAcyiC,EAAK/iC,KACnCiS,EAAK5R,SAASC,cAAc,MAGlC,OAFA2R,EAAG7N,UAAY2C,GAAW,OAC1B4yB,EAAK74B,YAAYmR,GACV0nB,CACT,CAEA,GAAW,cAAP+I,EAAoB,CACtB,MAAMlwB,EAAMnS,SAASC,cAAc,OAC7BmS,EAAOpS,SAASC,cAAc,QAE9B0iC,EAAO3iC,SAASC,cAAc,OAIpC,OAHA0iC,EAAK5+B,UAAY2C,EACjB0L,EAAK3Q,YAAckhC,EAAKlhC,aAAe,KACvC0Q,EAAI1R,YAAY2R,GACTD,CACT,CAEA,GAAW,eAAPkwB,EAAqB,CACvB,MAAMlB,EAAKnhC,SAASC,cAAc,cAC5B2P,EAAI5P,SAASC,cAAc,KAGjC,OAFA2P,EAAE7L,UAAY2C,GAAW,OACzBy6B,EAAG1gC,YAAYmP,GACRuxB,CACT,CAGA,MAAMjiC,EAAKc,SAASC,cAAcyiC,EAAK/iC,KAEvC,OADAT,EAAG6E,UAAY2C,GAAW,OACnBxH,CACT,CAiBuB0jC,CAAkBP,EAAI37B,GACvC,IAAK+7B,EAAU,OAEfjhC,EAAMV,WAAWq1B,aAAasM,EAAUjhC,GAGxC,MAAM1B,EAAQE,SAASkE,cACvB,IAAI2+B,EAAeJ,EAER,kBAAPJ,GAAiC,gBAAPA,EAC5BQ,EAAeJ,EAAS9gC,cAAc,OAAS8gC,EAC/B,cAAPJ,EACTQ,EAAeJ,EAAS9gC,cAAc,SAAW8gC,EACjC,eAAPJ,IACTQ,EAAeJ,EAAS9gC,cAAc,MAAQ8gC,GAGhD3iC,EAAMqE,mBAAmB0+B,GACzB/iC,EAAM4F,UAAS,GACf6wB,EAAI9oB,UAAUnK,SAASxD,GAEvBy2B,EAAIvsB,SAASzL,KAAK,iBACpB,EACAiL,KAAM,CAAEitB,QAAS,uBAErB,CC/IA,SAASqM,GAAc/5B,EAAQg6B,EAAU3iC,GACvC,IAAKA,EAAO,OACZ,MAAMyC,EAAMkG,EAAO0E,UAAU/K,eAC7B,IAAKG,GAA0B,IAAnBA,EAAIC,WAAkB,OAClC,MAAMhD,EAAQ+C,EAAIE,WAAW,GAC7B,GAAIjD,EAAM2E,UAAW,OAGrB,MAAM5D,EAASkI,EAAO0E,UAAU1H,mBAChC,GAAIlF,GAA6B,SAAnBA,EAAOtB,SAAsBsB,EAAOwH,MAAM06B,GAEtD,YADAliC,EAAOwH,MAAM06B,GAAY3iC,GAI3B,MAAMmS,EAAOvS,SAASC,cAAc,QACpCsS,EAAKlK,MAAM06B,GAAY3iC,EAEvB,IACEN,EAAMQ,iBAAiBiS,EACzB,CAAA,MACE,MAAMhS,EAAWT,EAAMU,kBACvB+R,EAAK9R,YAAYF,GACjBT,EAAMY,WAAW6R,EACnB,CACF,CAKA,SAASywB,GAAkBj6B,EAAQg6B,GACjC,MAAMliC,EAASkI,EAAO0E,UAAU1H,mBAChC,IAAKlF,EAAQ,OAAO,EACpB,IACE,OAAO8B,OAAO4F,iBAAiB1H,GAAQkiC,KAAa,CACtD,CAAA,MACE,OAAO,CACT,CACF,CAEO,SAASE,GAAqBl6B,GACnCA,EAAOyH,SAASvH,SAAS,aAAc,CACrC,OAAAG,CAAQmtB,EAAK2M,GACXJ,GAAcvM,EAAK,aAAc2M,EACnC,EACA75B,SAASktB,GACAyM,GAAkBzM,EAAK,cAEhC/sB,KAAM,CAAEgtB,KAAM,aAAcC,QAAS,iBAGvC1tB,EAAOyH,SAASvH,SAAS,WAAY,CACnC,OAAAG,CAAQmtB,EAAKp4B,GACNA,GAEA,gCAAgCgK,KAAKhK,IAC1C2kC,GAAcvM,EAAK,WAAYp4B,EACjC,EACAqL,KAAM,CAAEgtB,KAAM,WAAYC,QAAS,eAGrC1tB,EAAOyH,SAASvH,SAAS,YAAa,CACpC,OAAAG,CAAQmtB,EAAKG,GACXoM,GAAcvM,EAAK,QAASG,EAC9B,EACArtB,SAASktB,GACAyM,GAAkBzM,EAAK,SAEhC/sB,KAAM,CAAEgtB,KAAM,YAAaC,QAAS,gBAGtC1tB,EAAOyH,SAASvH,SAAS,YAAa,CACpC,OAAAG,CAAQmtB,EAAKG,GACXoM,GAAcvM,EAAK,kBAAmBG,EACxC,EACArtB,SAASktB,GACAyM,GAAkBzM,EAAK,mBAEhC/sB,KAAM,CAAEgtB,KAAM,YAAaC,QAAS,sBAItC1tB,EAAOyH,SAASvH,SAAS,aAAc,CACrC,OAAAG,CAAQmtB,EAAKn2B,GACNA,GACL0iC,GAAcvM,EAAK,aAAcn2B,EACnC,EACAiJ,SAASktB,GACAyM,GAAkBzM,EAAK,cAEhC/sB,KAAM,CAAEgtB,KAAM,aAAcC,QAAS,iBAGvC1tB,EAAOyH,SAASvH,SAAS,gBAAiB,CACxC,OAAAG,CAAQmtB,EAAKn2B,GACNA,GACL0iC,GAAcvM,EAAK,gBAAiBn2B,EACtC,EACAiJ,SAASktB,GACAyM,GAAkBzM,EAAK,iBAEhC/sB,KAAM,CAAEgtB,KAAM,gBAAiBC,QAAS,oBAG1C1tB,EAAOyH,SAASvH,SAAS,mBAAoB,CAC3C,OAAAG,CAAQmtB,EAAKn2B,GACX,IAAKA,EAAO,OAEZ,MAAMS,EAAS01B,EAAI9oB,UAAU1H,mBAC7B,IAAKlF,EAAQ,OACb,MAAMW,EAAQX,EAAOye,QAAQ,uDACzB9d,IACFA,EAAM6G,MAAM86B,aAAe/iC,EAE/B,EACA,QAAAiJ,CAASktB,GACP,MAAM11B,EAAS01B,EAAI9oB,UAAU1H,mBAC7B,IAAKlF,EAAQ,OAAO,EACpB,MAAMW,EAAQX,EAAOye,QAAQ,uDAC7B,OAAK9d,GACEA,EAAM6G,MAAM86B,eADA,CAErB,EACA35B,KAAM,CAAEgtB,KAAM,mBAAoBC,QAAS,sBAE/C,CCvIO,SAAS2M,GAAsBr6B,GACpCA,EAAOyH,SAASvH,SAAS,aAAc,CACrC,OAAAG,CAAQmtB,GAAKnjB,IAAEA,IACb,IAAKA,EAAK,OAEV,MAAMiwB,EAwCZ,SAAqBjwB,GAEnB,IAAI6rB,EAAQ7rB,EAAI6rB,MAAM,8EACtB,GAAIA,EACF,MAAO,iCAAiCA,EAAM,KAKhD,GADAA,EAAQ7rB,EAAI6rB,MAAM,yBACdA,EACF,MAAO,kCAAkCA,EAAM,KAKjD,GADAA,EAAQ7rB,EAAI6rB,MAAM,2CACdA,EACF,MAAO,2CAA2CA,EAAM,KAG1D,OAAO,IACT,CA5DuBqE,CAAYlwB,GAC7B,IAAKiwB,EAAU,OAEf,MAAMhlC,EAAU2B,SAASC,cAAc,OACvC5B,EAAQmpB,UAAY,oBACpBnpB,EAAQgC,aAAa,kBAAmB,SACxChC,EAAQgC,aAAa,iBAAkB+S,GAEvC,MAAMT,EAAS3S,SAASC,cAAc,UACtC0S,EAAOO,IAAMmwB,EACb1wB,EAAOtS,aAAa,cAAe,KACnCsS,EAAOtS,aAAa,kBAAmB,QACvCsS,EAAOtS,aAAa,UAAW,mEAC/BsS,EAAOtS,aAAa,QAAS,gCAC7BsS,EAAOtK,MAAM2f,MAAQ,OACrBrV,EAAOtK,MAAM4e,OAAS,OAEtB5oB,EAAQoC,YAAYkS,GACpB4jB,EAAI9oB,UAAU/M,WAAWrC,GAGzB,MAAMuR,EAAI5P,SAASC,cAAc,KACjC2P,EAAE7L,UAAY,OACd1F,EAAQyC,WAAWE,aAAa4O,EAAGvR,EAAQ6nB,YAC7C,EACA1c,KAAM,CAAEgtB,KAAM,aAAcC,QAAS,iBAGvC1tB,EAAOyH,SAASvH,SAAS,cAAe,CACtC,OAAAG,CAAQmtB,GAAK31B,QAAEA,IACb,IAAKA,EAAS,OACd,MAAMvC,EAAUuC,EAAQ0e,QAAQ,uBAAyB1e,EACnDgP,EAAI5P,SAASC,cAAc,KACjC2P,EAAE7L,UAAY,OACd1F,EAAQyC,WAAWq1B,aAAavmB,EAAGvR,EACrC,EACAmL,KAAM,CAAEitB,QAAS,iBAErB,CC3CO,SAAS8M,GAA4Bx6B,GAC1C,IAAIy6B,EAAiB,GACjBC,GAAe,EAEnB,SAASC,EAAgBjc,GACvBA,EAASjR,iBAAiB,2BAA2B/X,SAASiU,IAC5D,MAAM7R,EAAS6R,EAAK5R,WACpBD,EAAOs1B,aAAan2B,SAAS2jC,eAAejxB,EAAKjR,aAAciR,GAC/D7R,EAAO+iC,WAAS,GAEpB,CA2IA,SAASC,IACPL,EAAiBA,EAAennB,QAAQynB,GAAMA,EAAEC,cAC5CN,GAAgBD,EAAej/B,SACjCk/B,EAAeD,EAAej/B,OAAS,EAAIi/B,EAAej/B,OAAS,GAAI,EAE3E,CAEA,SAASy/B,IACPH,IACAL,EAAe/kC,SAAQ,CAACqlC,EAAGt5B,KACzBs5B,EAAEtc,UAAYhd,IAAMi5B,EAAe,sCAAwC,oBAAA,IAEzED,EAAeC,IACjBD,EAAeC,GAAcQ,eAAe,CAAEziC,MAAO,SAAU0iC,SAAU,UAE7E,CA5GAn7B,EAAOyH,SAASvH,SAAS,OAAQ,CAC/BG,QAAA,CAAQmtB,GAAK9tB,KAAEA,EAAA07B,cAAMA,MACnBX,EA9CJ,SAAqB/b,EAAU2c,EAAYD,GAAgB,GAEzD,GADAT,EAAgBjc,IACX2c,EAAY,MAAO,GAExB,MAAMC,EAAU,GACVhN,EAASr3B,SAAS6E,iBAAiB4iB,EAAU3iB,WAAWC,UAAW,MACnEu/B,EAAY,GAElB,KAAOjN,EAAOhyB,YACZi/B,EAAU52B,KAAK2pB,EAAO/xB,aAGxB,MAAMi/B,EAAQJ,EAAgB,IAAM,KAC9BK,EAAQ,IAAIC,OAAmBL,EA+I5BnuB,QAAQ,sBAAuB,QA/IUsuB,GAGlD,IAAA,IAAS/5B,EAAI85B,EAAU//B,OAAS,EAAGiG,GAAK,EAAGA,IAAK,CAC9C,MAAMxL,EAAOslC,EAAU95B,GACjB/B,EAAOzJ,EAAKyC,YACZijC,EAAc,GACpB,IAAIZ,EACJ,KAAkC,QAA1BA,EAAIU,EAAMG,KAAKl8B,KACrBi8B,EAAYh3B,KAAK,CAAEJ,MAAOw2B,EAAEx2B,MAAO/I,OAAQu/B,EAAE,GAAGv/B,SAIlD,IAAA,IAASqgC,EAAIF,EAAYngC,OAAS,EAAGqgC,GAAK,EAAGA,IAAK,CAChD,MAAM3F,EAAQyF,EAAYE,GACpB9kC,EAAQE,SAASkE,cACvBpE,EAAM0F,SAASxG,EAAMigC,EAAM3xB,OAC3BxN,EAAMsE,OAAOpF,EAAMigC,EAAM3xB,MAAQ2xB,EAAM16B,QAEvC,MAAMmO,EAAO1S,SAASC,cAAc,QACpCyS,EAAK8U,UAAY,qBACjB1nB,EAAMQ,iBAAiBoS,GACvB2xB,EAAQ32B,KAAKgF,EACf,CACF,CAIA,OADA2xB,EAAQQ,UACDR,CACT,CAIqBS,CAAYvO,EAAI31B,QAAS6H,EAAM07B,GAChDV,EAAeD,EAAej/B,OAAS,EAAI,GAAI,EAC3Ck/B,GAAgB,GAClBO,IAEFzN,EAAIvsB,SAASzL,KAAK,eAAgB,CAChC2jB,MAAOshB,EAAej/B,OACtBwgC,QAAStB,EAAe,IAEnB,CAAEvhB,MAAOshB,EAAej/B,OAAQwgC,QAAStB,EAAe,IAEjEl6B,SAAU,QACVC,KAAM,CAAEgtB,KAAM,cAAeC,QAAS,oBAGxC1tB,EAAOyH,SAASvH,SAAS,WAAY,CACnC,OAAAG,CAAQmtB,GACwB,IAA1BiN,EAAej/B,SACnBk/B,GAAgBA,EAAe,GAAKD,EAAej/B,OACnDy/B,IACAzN,EAAIvsB,SAASzL,KAAK,eAAgB,CAChC2jB,MAAOshB,EAAej/B,OACtBwgC,QAAStB,EAAe,IAE5B,EACAj6B,KAAM,CAAEitB,QAAS,eAGnB1tB,EAAOyH,SAASvH,SAAS,WAAY,CACnC,OAAAG,CAAQmtB,GACwB,IAA1BiN,EAAej/B,SACnBk/B,GAAgBA,EAAe,EAAID,EAAej/B,QAAUi/B,EAAej/B,OAC3Ey/B,IACAzN,EAAIvsB,SAASzL,KAAK,eAAgB,CAChC2jB,MAAOshB,EAAej/B,OACtBwgC,QAAStB,EAAe,IAE5B,EACAj6B,KAAM,CAAEitB,QAAS,mBAGnB1tB,EAAOyH,SAASvH,SAAS,UAAW,CAClC,OAAAG,CAAQmtB,GAAKyO,YAAEA,IAEb,GADAnB,IAC8B,IAA1BL,EAAej/B,QAAgBk/B,EAAe,GAAKA,GAAgBD,EAAej/B,OAAQ,OAC9F,MAAMmO,EAAO8wB,EAAeC,GACtBwB,EAAWjlC,SAAS2jC,eAAeqB,GACzCtyB,EAAK5R,WAAWq1B,aAAa8O,EAAUvyB,GACvC8wB,EAAe/S,OAAOgT,EAAc,GAChCD,EAAej/B,OAAS,GAC1Bk/B,EAAetiC,KAAKsE,IAAIg+B,EAAcD,EAAej/B,OAAS,GAC9Dy/B,KAEAP,GAAe,EAEjBlN,EAAIvsB,SAASzL,KAAK,eAAgB,CAChC2jB,MAAOshB,EAAej/B,OACtBwgC,QAAStB,EAAe,IAE1BlN,EAAIvsB,SAASzL,KAAK,iBACpB,EACAiL,KAAM,CAAEitB,QAAS,aAGnB1tB,EAAOyH,SAASvH,SAAS,aAAc,CACrC,OAAAG,CAAQmtB,GAAKyO,YAAEA,IACbnB,IACAL,EAAe/kC,SAASiU,IACtB,MAAMuyB,EAAWjlC,SAAS2jC,eAAeqB,GACzCtyB,EAAK5R,WAAWq1B,aAAa8O,EAAUvyB,EAAI,IAE7C,MAAMsrB,EAAQwF,EAAej/B,OAK7B,OAJAi/B,EAAiB,GACjBC,GAAe,EACflN,EAAIvsB,SAASzL,KAAK,eAAgB,CAAE2jB,MAAO,EAAG6iB,QAAS,IACvDxO,EAAIvsB,SAASzL,KAAK,kBACXy/B,CACT,EACAx0B,KAAM,CAAEitB,QAAS,iBAGnB1tB,EAAOyH,SAASvH,SAAS,YAAa,CACpC,OAAAG,CAAQmtB,GACNmN,EAAgBnN,EAAI31B,SACpB4iC,EAAiB,GACjBC,GAAe,CACjB,EACAj6B,KAAM,CAAEitB,QAAS,eAoBrB,CCrKO,SAASyO,GAA2Bn8B,GACzCA,EAAOyH,SAASvH,SAAS,aAAc,CACrC,OAAAG,CAAQmtB,GACNA,EAAI5I,cAAgB4I,EAAI5I,aACxB4I,EAAIvsB,SAASzL,KAAK,cAAe,CAAEwzB,WAAYwE,EAAI5I,cACrD,EACAtkB,SAASktB,GACAA,EAAI5I,aAEbpkB,SAAU,cACVC,KAAM,CAAEgtB,KAAM,aAAcC,QAAS,gBAEzC,CCVA,IAAI0O,GAAkB,EAEf,SAASC,GAA2Br8B,GACzCA,EAAOyH,SAASvH,SAAS,aAAc,CACrC,OAAAG,CAAQmtB,GACN,MAAM92B,EAAO82B,EAAI31B,QAAQ0e,QAAQ,eACjC,IAAK7f,EAAM,OAEX,MAAM4lC,EAAe5lC,EAAK+c,UAAU/Y,SAAS,kBACzC4hC,GACF5lC,EAAK+c,UAAUsI,OAAO,kBACtBqgB,GAAkBhkC,KAAKy9B,IAAI,EAAGuG,GAAkB,GACxB,IAApBA,KACFnlC,SAASyU,KAAKpM,MAAMi9B,SAAW,MAGjC7lC,EAAK+c,UAAUxe,IAAI,kBACnBmnC,KACAnlC,SAASyU,KAAKpM,MAAMi9B,SAAW,UAEjC/O,EAAIvsB,SAASzL,KAAK,oBAAqB,CAAEuzB,YAAauT,GACxD,EACA,QAAAh8B,CAASktB,GACP,MAAM92B,EAAO82B,EAAI31B,QAAQ0e,QAAQ,eACjC,QAAO7f,GAAOA,EAAK+c,UAAU/Y,SAAS,iBACxC,EACA8F,SAAU,cACVC,KAAM,CAAEgtB,KAAM,aAAcC,QAAS,eAEzC,CCzBA,IAAI8O,GAAY,KAET,SAASC,GAAgCz8B,GAC9CA,EAAOyH,SAASvH,SAAS,kBAAmB,CAC1C,OAAAG,CAAQmtB,GACN,MAAM92B,EAAO82B,EAAI31B,QAAQ0e,QAAQ,eACjC,IAAK7f,EAAM,OAEX,MAAM4J,EAAW5J,EAAK+c,UAAU/Y,SAAS,wBACrC4F,GACF5J,EAAK+c,UAAUsI,OAAO,wBACtBrlB,EAAK+c,UAAUsI,OAAO,sBACtBrlB,EAAK0P,oBAAoB,YAAas2B,IACtCh5B,aAAa84B,MAEb9lC,EAAK+c,UAAUxe,IAAI,wBACnByB,EAAKyP,iBAAiB,YAAau2B,KAErClP,EAAIvsB,SAASzL,KAAK,yBAA0B,CAAEmnC,QAASr8B,GACzD,EACA,QAAAA,CAASktB,GACP,MAAM92B,EAAO82B,EAAI31B,QAAQ0e,QAAQ,eACjC,QAAO7f,GAAOA,EAAK+c,UAAU/Y,SAAS,uBACxC,EACA8F,SAAU,cACVC,KAAM,CAAEgtB,KAAM,kBAAmBC,QAAS,0BAE9C,CAEA,SAASgP,GAAW11B,GAClB,MAAMtQ,EAAOsQ,EAAE41B,cACVlmC,EAAK+c,UAAU/Y,SAAS,0BAE7BhE,EAAK+c,UAAUxe,IAAI,sBACnByO,aAAa84B,IACbA,GAAY74B,YAAW,KACrBjN,EAAK+c,UAAUsI,OAAO,qBAAoB,GACzC,KACL,CCvCO,SAAS8gB,GAA0B78B,GACxCA,EAAOyH,SAASvH,SAAS,kBAAmB,CAC1C,OAAAG,CAAQmtB,GACN,MAAM92B,EAAO82B,EAAI31B,QAAQ0e,QAAQ,eACjC,IAAK7f,EAAM,OAEX,MAAM4J,EAAW5J,EAAK+c,UAAU/Y,SAAS,kBACrC4F,EACF5J,EAAK+c,UAAUsI,OAAO,kBAEtBrlB,EAAK+c,UAAUxe,IAAI,kBAErBu4B,EAAIvsB,SAASzL,KAAK,mBAAoB,CAAEmnC,QAASr8B,GACnD,EACA,QAAAA,CAASktB,GACP,MAAM92B,EAAO82B,EAAI31B,QAAQ0e,QAAQ,eACjC,QAAO7f,GAAOA,EAAK+c,UAAU/Y,SAAS,iBACxC,EACA8F,SAAU,cACVC,KAAM,CAAEgtB,KAAM,YAAaC,QAAS,eAExC,CCtBA,MAAMoP,GAAc,oBAMb,SAASC,KACd,IACE,MAAMxrB,EAAMyrB,aAAaC,QAAQH,IACjC,IAAKvrB,EAAK,MAAO,GACjB,MAAM2rB,EAASC,KAAKxoB,MAAMpD,GAC1B,OAAOpQ,MAAMmmB,QAAQ4V,GAAUA,EAAS,EAC1C,CAAA,MACE,MAAO,EACT,CACF,CAOO,SAAS1T,GAAgBrpB,EAAM4rB,GACpC,GAAK5rB,GAASgB,MAAMmmB,QAAQyE,GAC5B,IACE,MAAMqR,EAAUL,KAAmBzpB,QAAOzM,GAAKA,EAAE1G,OAASA,IAC1Di9B,EAAQz4B,KAAK,CAAExE,OAAM4rB,WACrBiR,aAAaK,QAAQP,GAAaK,KAAKG,UAAUF,GACnD,CAAA,MAEA,CACF,CAMO,SAASG,GAAkBp9B,GAChC,IACE,MAAMi9B,EAAUL,KAAmBzpB,QAAOzM,GAAKA,EAAE1G,OAASA,IAC1D68B,aAAaK,QAAQP,GAAaK,KAAKG,UAAUF,GACnD,CAAA,MAEA,CACF,CAEO,SAASI,GAA4Bx9B,GAC1CA,EAAOyH,SAASvH,SAAS,kBAAmB,CAC1C,OAAAG,CAAQo9B,EAAMt9B,EAAM4rB,GAClBvC,GAAgBrpB,EAAM4rB,GACtB/rB,EAAOiB,SAASzL,KAAK,sBAAuB,CAAE4nC,QAASL,MACzD,EACAt8B,KAAM,CAAEgtB,KAAM,cAAeC,QAAS,uBAGxC1tB,EAAOyH,SAASvH,SAAS,mBAAoB,CAC3CG,QAAA,IACS08B,KAETt8B,KAAM,CAAEgtB,KAAM,cAAeC,QAAS,wBAGxC1tB,EAAOyH,SAASvH,SAAS,oBAAqB,CAC5C,OAAAG,CAAQo9B,EAAMt9B,GACZo9B,GAAkBp9B,GAClBH,EAAOiB,SAASzL,KAAK,sBAAuB,CAAE4nC,QAASL,MACzD,EACAt8B,KAAM,CAAEgtB,KAAM,cAAeC,QAAS,wBAE1C,CCvEO,SAASgQ,GAA+B19B,GAC7CA,EAAOyH,SAASvH,SAAS,iBAAkB,CACzC,OAAAG,CAAQmtB,GAGN,GAFAA,EAAI1sB,QAAQC,WAERysB,EAAI3I,eAAgB,CAEtB,MACMxnB,EAAOuW,GADI4Z,EAAI31B,QAAQa,aAE7B80B,EAAI9H,QAAQroB,GACZmwB,EAAI3I,gBAAiB,CACvB,KAAO,CAEL,MACM1T,EAAWuB,GADJ8a,EAAIzN,WAEjByN,EAAI31B,QAAQa,YAAcyY,EAC1Bqc,EAAI3I,gBAAiB,CACvB,CAEA2I,EAAI31B,QAAQ4b,UAAUkqB,OAAO,oBAAqBnQ,EAAI3I,gBACtD2I,EAAIvsB,SAASzL,KAAK,uBAAwB,CAAEooC,aAAcpQ,EAAI3I,iBAC9D2I,EAAIvsB,SAASzL,KAAK,iBACpB,EACA8K,SAASktB,GACAA,EAAI3I,eAEbpkB,KAAM,CAAEgtB,KAAM,iBAAkBC,QAAS,oBAE7C,CC9BA,MAAM+C,GAAqB,uDAiBpB,SAASoN,GAA2B79B,GACzCA,EAAOyH,SAASvH,SAAS,mBAAoB,CAC3C,OAAAG,CAAQmtB,GAAKnjB,IAAEA,WAAKoF,EAAW,OAAA2G,SAAQA,IACrC,IAAK/L,EAAK,OAMV,GAHAmjB,EAAI31B,QAAQif,SAGP0W,EAAI9oB,UAAU7K,WAAY,CAC7B,MAAM9C,EAAQE,SAASkE,cACvBpE,EAAMqE,mBAAmBoyB,EAAI31B,SAC7Bd,EAAM4F,UAAS,GACf6wB,EAAI9oB,UAAUnK,SAASxD,EACzB,CAEA,MAAM0R,EAAIxR,SAASC,cAAc,KACjCuR,EAAEhJ,KAAOgxB,GAAmBrxB,KAAKiL,GAAO,IAAMA,EAC9C5B,EAAEgW,UAAY,iBACdhW,EAAEnR,aAAa,kBAAmB,QAClCmR,EAAEnR,aAAa,gBAAiBmY,GAC5B2G,GAAU3N,EAAEnR,aAAa,gBAAiBg8B,OAAOld,IACrD3N,EAAE9I,OAAS,SACX8I,EAAEooB,IAAM,sBAER,MAAMiN,EAAY1nB,EAAW,KArCnC,SAAwB2nB,GACtB,IAAKA,GAAmB,IAAVA,EAAa,MAAO,GAClC,MAAMC,EAAQ,CAAC,IAAK,KAAM,KAAM,MAChC,IAAI5oC,EAAO2oC,EACPE,EAAY,EAChB,KAAO7oC,GAAQ,MAAQ6oC,EAAYD,EAAMxiC,OAAS,GAChDpG,GAAQ,KACR6oC,IAEF,MAAO,GAAG7oC,EAAO,GAAM,EAAIA,EAAOA,EAAK+c,QAAQ,MAAM6rB,EAAMC,IAC7D,CA2BwCC,CAAe9nB,MAAe,GAMhE,GALA3N,EAAE/P,YAAc,MAAa+W,IAAWquB,IAExCtQ,EAAI9oB,UAAU/M,WAAW8Q,IAGpBA,EAAE0U,aAAe1U,EAAElS,gBAAkBi3B,EAAI31B,QAAS,CACrD,MAAMgP,EAAI5P,SAASC,cAAc,KACjC2P,EAAE7L,UAAY,OACdyN,EAAElS,cAAc0B,aAAa4O,EAAG4B,EAAE0U,YACpC,CACF,EACA1c,KAAM,CAAEgtB,KAAM,aAAcC,QAAS,uBAGvC1tB,EAAOyH,SAASvH,SAAS,mBAAoB,CAC3C,OAAAG,CAAQmtB,GAAK31B,QAAEA,IACRA,GAAYA,EAAQ4b,WAAW/Y,SAAS,mBAC7C7C,EAAQE,WAAWG,YAAYL,EACjC,EACA4I,KAAM,CAAEitB,QAAS,sBAErB,CChEO,SAASyQ,GAA+Bn+B,GAC7CA,EAAOyH,SAASvH,SAAS,iBAAkB,CACzC,OAAAG,CAAQmtB,GAAKnwB,KAAEA,EAAA+gC,KAAMA,EAAO,WAC1B,GAAK/gC,EAAL,CAIA,GAFAmwB,EAAI31B,QAAQif,QAEC,YAATsnB,EACF5Q,EAAI9H,QAAQroB,OACP,CAEL,IAAKmwB,EAAI9oB,UAAU7K,WAAY,CAC7B,MAAM9C,EAAQE,SAASkE,cACvBpE,EAAMqE,mBAAmBoyB,EAAI31B,SAC7Bd,EAAM4F,UAAS,GACf6wB,EAAI9oB,UAAUnK,SAASxD,EACzB,CACAy2B,EAAI9oB,UAAUtH,WAAWowB,EAAIxvB,UAAUR,SAASH,GAClD,CAEAmwB,EAAIvsB,SAASzL,KAAK,iBAjBP,CAkBb,EACAiL,KAAM,CAAEgtB,KAAM,iBAAkBC,QAAS,oBAE7C,CCRA,MAAM2Q,GAAqB,sBAOpB,SAASC,KACd,IACE,MAAM/sB,EAAMyrB,aAAaC,QAAQoB,IACjC,IAAK9sB,EAAK,MAAO,GACjB,MAAM2rB,EAASC,KAAKxoB,MAAMpD,GAC1B,OAAOpQ,MAAMmmB,QAAQ4V,GAAUA,EAAOh7B,MAAM,EAX7B,GAW8C,EAC/D,CAAA,MACE,MAAO,EACT,CACF,CAMO,SAASq8B,GAAoBC,GAClC,IACE,MAAMC,EAASH,KAAoBhrB,QAAO2S,GAAMA,IAAOuY,IACvDC,EAAOC,QAAQF,GACfxB,aAAaK,QAAQgB,GAAoBlB,KAAKG,UAAUmB,EAAOv8B,MAAM,EAzBtD,IA0BjB,CAAA,MAEA,CACF,CAKO,SAASy8B,KACd,IACE3B,aAAa4B,WAAWP,GAC1B,CAAA,MAEA,CACF,CAOA,MAAMQ,GAAe,GAOd,SAASC,GAAqBhtB,GACnC,MAAMitB,EAAM59B,MAAMmmB,QAAQxV,GAASA,EAAQ,CAACA,GAC5C,IAAA,MAAWqV,KAAQ4X,EAAK,CACtB,MAAMjmB,EAAM+lB,GAAaG,cAAev9B,EAAEwkB,KAAOkB,EAAKlB,KAClDnN,GAAO,EACT+lB,GAAa/lB,GAAOqO,EAEpB0X,GAAal6B,KAAKwiB,EAEtB,CACF,CAOO,SAAS8X,GAAsBhZ,GACpC,MAAMnN,EAAM+lB,GAAaG,WAAUv9B,GAAKA,EAAEwkB,KAAOA,IACjD,OAAInN,GAAO,IACT+lB,GAAanX,OAAO5O,EAAK,IAClB,EAGX,CAMO,SAASomB,KACd,MAAO,IAAIL,GACb,CAQY,MAACM,GAAsB,CAEjC,CAAElZ,GAAI,WAAYvc,MAAO,YAAasX,YAAa,wBAAyByM,KAAM,KAAM2R,SAAU,CAAC,KAAM,QAAS,UAAW,SAAUrX,SAAU,OAAQsX,OAASr/B,GAAWA,EAAO8f,eAAe,UAAW,IAC9M,CAAEmG,GAAI,WAAYvc,MAAO,YAAasX,YAAa,yBAA0ByM,KAAM,KAAM2R,SAAU,CAAC,KAAM,WAAY,UAAW,UAAWrX,SAAU,OAAQsX,OAASr/B,GAAWA,EAAO8f,eAAe,UAAW,IACnN,CAAEmG,GAAI,WAAYvc,MAAO,YAAasX,YAAa,wBAAyByM,KAAM,KAAM2R,SAAU,CAAC,KAAM,UAAW,SAAUrX,SAAU,OAAQsX,OAASr/B,GAAWA,EAAO8f,eAAe,UAAW,IACrM,CAAEmG,GAAI,YAAavc,MAAO,YAAasX,YAAa,mBAAoByM,KAAM,IAAU2R,SAAU,CAAC,OAAQ,SAAU,OAAQ,SAAUrX,SAAU,OAAQsX,OAASr/B,GAAWA,EAAO8f,eAAe,UAAW,MAC9M,CAAEmG,GAAI,aAAcvc,MAAO,aAAcsX,YAAa,uBAAwByM,KAAM,IAAU2R,SAAU,CAAC,QAAS,WAAY,UAAWrX,SAAU,OAAQsX,OAASr/B,GAAWA,EAAO8f,eAAe,eACrM,CAAEmG,GAAI,YAAavc,MAAO,aAAcsX,YAAa,yBAA0ByM,KAAM,KAAM2R,SAAU,CAAC,OAAQ,MAAO,YAAa,UAAW,eAAgBrX,SAAU,OAAQsX,OAASr/B,GAAWA,EAAO8f,eAAe,cAGzN,CAAEmG,GAAI,gBAAiBvc,MAAO,gBAAiBsX,YAAa,wBAAyByM,KAAM,IAAU2R,SAAU,CAAC,SAAU,KAAM,OAAQ,aAAcrX,SAAU,QAASsX,OAASr/B,GAAWA,EAAO8f,eAAe,kBACnN,CAAEmG,GAAI,cAAevc,MAAO,gBAAiBsX,YAAa,wBAAyByM,KAAM,KAAM2R,SAAU,CAAC,SAAU,KAAM,OAAQ,WAAYrX,SAAU,QAASsX,OAASr/B,GAAWA,EAAO8f,eAAe,gBAC3M,CAAEmG,GAAI,WAAYvc,MAAO,YAAasX,YAAa,kCAAmCyM,KAAM,IAAU2R,SAAU,CAAC,OAAQ,WAAY,YAAa,QAASrX,SAAU,QAASsX,OAASr/B,GAAWA,EAAO8f,eAAe,aAGxN,CAAEmG,GAAI,QAASvc,MAAO,QAASsX,YAAa,kBAAmByM,KAAM,KAAgB2R,SAAU,CAAC,MAAO,QAAS,UAAW,UAAWrX,SAAU,QAASsX,OAAQ,CAACC,EAASC,IAAcA,IAAY,UACrM,CAAEtZ,GAAI,aAAcvc,MAAO,aAAcsX,YAAa,gBAAiByM,KAAM,KAAgB2R,SAAU,CAAC,OAAQ,SAAU,SAAU,YAAarX,SAAU,QAASsX,OAAQ,CAACC,EAASC,IAAcA,IAAY,eAChN,CAAEtZ,GAAI,aAAcvc,MAAO,cAAesX,YAAa,iCAAkCyM,KAAM,IAAU2R,SAAU,CAAC,QAAS,UAAW,QAAS,QAAS,SAAUrX,SAAU,QAASsX,OAAQ,CAACC,EAASC,IAAcA,IAAY,UAGnO,CAAEtZ,GAAI,iBAAkBvc,MAAO,kBAAmBsX,YAAa,sBAAuByM,KAAM,IAAU2R,SAAU,CAAC,KAAM,UAAW,OAAQ,YAAa,QAASrX,SAAU,SAAUsX,OAASr/B,GAAWA,EAAO8f,eAAe,mBAC9N,CAAEmG,GAAI,QAASvc,MAAO,QAASsX,YAAa,sBAAuByM,KAAM,IAAU2R,SAAU,CAAC,QAAS,OAAQ,cAAe,SAAUrX,SAAU,SAAUsX,OAAQ,CAACC,EAASC,IAAcA,IAAY,UAGxM,CAAEtZ,GAAI,gBAAiBvc,MAAO,UAAWsX,YAAa,gCAAiCyM,KAAM,KAAgB2R,SAAU,CAAC,UAAW,QAAS,aAAc,OAAQ,UAAW,MAAO,QAASrX,SAAU,SAAUsX,OAASr/B,GAAWA,EAAOyH,SAAShT,IAAI,kBAAoBuL,EAAO8f,eAAe,kBACnS,CAAEmG,GAAI,aAAcvc,MAAO,gBAAiBsX,YAAa,iCAAkCyM,KAAM,KAAa2R,SAAU,CAAC,OAAQ,WAAY,QAAS,UAAW,SAAUrX,SAAU,SAAUsX,OAASr/B,GAAWA,EAAOyH,SAAShT,IAAI,eAAiBuL,EAAO8f,eAAe,aAAc,CAAE0f,MAAO,GAAIC,aAAa,KACtT,CAAExZ,GAAI,YAAavc,MAAO,oBAAqBsX,YAAa,6CAA8CyM,KAAM,KAAgB2R,SAAU,CAAC,MAAO,WAAY,UAAW,aAAc,SAAUrX,SAAU,SAAUsX,OAASr/B,GAAWA,EAAOyH,SAAShT,IAAI,cAAgBuL,EAAO8f,eAAe,cACnS,CAAEmG,GAAI,iBAAkBvc,MAAO,WAAYsX,YAAa,uCAAwCyM,KAAM,KAAgB2R,SAAU,CAAC,WAAY,SAAU,OAAQ,SAAU,aAAcrX,SAAU,SAAUsX,OAASr/B,GAAWA,EAAOyH,SAAShT,IAAI,mBAAqBuL,EAAO8f,eAAe,iBAAkB,CAAE3f,KAAM,cACxT,CAAE8lB,GAAI,iBAAkBvc,MAAO,YAAasX,YAAa,8BAA+ByM,KAAM,MAAsB2R,SAAU,CAAC,QAAS,MAAO,WAAY,WAAY,cAAe,WAAYrX,SAAU,SAAUsX,OAASr/B,GAAWA,EAAOyH,SAAShT,IAAI,mBAAqBuL,EAAO8f,eAAe,iBAAkB,CAAElpB,IAAK,UAClU,CAAEqvB,GAAI,aAAcvc,MAAO,UAAWsX,YAAa,qCAAsCyM,KAAM,KAAgB2R,SAAU,CAAC,UAAW,WAAY,aAAc,OAAQ,SAAU,YAAarX,SAAU,SAAUsX,OAASr/B,GAAWA,EAAOyH,SAAShT,IAAI,eAAiBuL,EAAO8f,eAAe,eAGjS,CAAEmG,GAAI,cAAevc,MAAO,iBAAkBsX,YAAa,0BAA2ByM,KAAM,KAAgB2R,SAAU,CAAC,OAAQ,SAAU,WAAYrX,SAAU,WAAYsX,OAAQ,CAACC,EAASC,IAAcA,IAAY,gBACvN,CAAEtZ,GAAI,aAAcvc,MAAO,cAAesX,YAAa,yBAA0ByM,KAAM,MAAO2R,SAAU,CAAC,OAAQ,SAAU,OAAQ,OAAQrX,SAAU,WAAYsX,OAASr/B,GAAWA,EAAO8f,eAAe,eAC3M,CAAEmG,GAAI,SAAUvc,MAAO,SAAUsX,YAAa,mCAAoCyM,KAAM,KAAgB2R,SAAU,CAAC,SAAU,WAAY,MAAO,OAAQ,QAASrX,SAAU,WAAYsX,OAAQ,CAACC,EAASC,IAAcA,IAAY,WACnO,CAAEtZ,GAAI,iBAAkBvc,MAAO,kBAAmBsX,YAAa,mBAAoByM,KAAM,KAAgB2R,SAAU,CAAC,SAAU,SAAU,OAAQ,QAASrX,SAAU,WAAYsX,OAAQ,CAACC,EAASC,IAAcA,IAAY,mBAC3N,CAAEtZ,GAAI,kBAAmBvc,MAAO,mBAAoBsX,YAAa,uCAAwCyM,KAAM,KAAgB2R,SAAU,CAAC,YAAa,cAAe,QAAS,aAAc,gBAAiBrX,SAAU,WAAYsX,OAASr/B,GAAWA,EAAOyH,SAAShT,IAAI,oBAAsBuL,EAAO8f,eAAe,oBACxT,CAAEmG,GAAI,mBAAoBvc,MAAO,oBAAqBsX,YAAa,kDAAmDyM,KAAM,IAAU2R,SAAU,CAAC,aAAc,WAAY,UAAW,YAAa,WAAYrX,SAAU,WAAYsX,OAASr/B,GAAWA,EAAOyH,SAAShT,IAAI,qBAAuBuL,EAAO8f,eAAe,qBAC1T,CAAEmG,GAAI,eAAgBvc,MAAO,gBAAiBsX,YAAa,4CAA6CyM,KAAM,KAAM2R,SAAU,CAAC,UAAW,QAAS,YAAa,QAAS,UAAW,WAAYrX,SAAU,WAAYsX,OAASr/B,GAAWA,EAAOyH,SAAShT,IAAI,iBAAmBuL,EAAO8f,eAAe,iBACvS,CAAEmG,GAAI,qBAAsBvc,MAAO,sBAAuBsX,YAAa,uDAAwDyM,KAAM,KAAgB2R,SAAU,CAAC,cAAe,WAAY,QAAS,cAAe,WAAYrX,SAAU,WAAYsX,OAASr/B,GAAWA,EAAOyH,SAAShT,IAAI,uBAAyBuL,EAAO8f,eAAe,uBAC5U,CAAEmG,GAAI,oBAAqBvc,MAAO,qBAAsBsX,YAAa,wCAAyCyM,KAAM,KAAgB2R,SAAU,CAAC,aAAc,QAAS,OAAQ,iBAAkBrX,SAAU,WAAYsX,OAASr/B,GAAWA,EAAOyH,SAAShT,IAAI,sBAAwBuL,EAAO8f,eAAe,sBAC5S,CAAEmG,GAAI,iBAAkBvc,MAAO,kBAAmBsX,YAAa,8CAA+CyM,KAAM,KAAW2R,SAAU,CAAC,WAAY,SAAU,UAAW,QAASrX,SAAU,WAAYsX,OAASr/B,GAAWA,EAAO8f,eAAe,mBACpP,CAAEmG,GAAI,aAAcvc,MAAO,aAAcsX,YAAa,yCAA0CyM,KAAM,IAAU2R,SAAU,CAAC,aAAc,SAAU,MAAO,QAAS,eAAgBrX,SAAU,WAAYsX,OAASr/B,GAAWA,EAAO8f,eAAe,eACnP,CAAEmG,GAAI,eAAgBvc,MAAO,mBAAoBsX,YAAa,uCAAwCyM,KAAM,KAAgB2R,SAAU,CAAC,QAAS,SAAU,SAAU,QAAS,SAAUrX,SAAU,WAAYsX,OAASr/B,GAAWA,EAAO8f,eAAe,iBACvP,CAAEmG,GAAI,kBAAmBvc,MAAO,mBAAoBsX,YAAa,kDAAmDyM,KAAM,KAAgB2R,SAAU,CAAC,cAAe,MAAO,QAAS,UAAW,SAAUrX,SAAU,WAAYsX,OAASr/B,GAAWA,EAAO8f,eAAe,oBACzQ,CAAEmG,GAAI,kBAAmBvc,MAAO,aAAcsX,YAAa,qCAAsCyM,KAAM,IAAU2R,SAAU,CAAC,QAAS,UAAW,OAAQ,OAAQ,QAASrX,SAAU,WAAYsX,OAASr/B,GAAWA,EAAO8f,eAAe,qBAepO,SAAS4f,GAAiB5tB,EAAOgP,EAAOtsB,EAAU,CAAA,GACvD,MAAMmrC,UAAEA,GAAY,GAASnrC,EAE7B,IAAKssB,EAAO,CACV,IAAK6e,EAAW,OAAO7tB,EACvB,MAAM8tB,EAAYtB,KAClB,GAAyB,IAArBsB,EAAUpkC,OAAc,OAAOsW,EAEnC,MAAM+tB,EAAc,GACpB,IAAA,MAAW5Z,KAAM2Z,EAAW,CAC1B,MAAMzY,EAAOrV,EAAM2D,MAAKhU,GAAKA,EAAEwkB,KAAOA,IAClCkB,GACF0Y,EAAYl7B,KAAK,IAAKwiB,EAAMY,SAAU,UAE1C,CACA,OAA2B,IAAvB8X,EAAYrkC,OAAqBsW,EAC9B,IAAI+tB,KAAgB/tB,EAC7B,CAEA,MAAMiP,EAAID,EAAM3hB,cACV2gC,EAAe,GACfC,EAAe,GACrB,IAAA,MAAW5Y,KAAQrV,EACbqV,EAAKzd,MAAMvK,cAAcmI,SAASyZ,GACpC+e,EAAan7B,KAAKwiB,IAElBA,EAAKnG,YAAY7hB,cAAcmI,SAASyZ,IACxCoG,EAAKiY,SAAS30B,MAAKwX,GAAKA,EAAE3a,SAASyZ,OAEnCgf,EAAap7B,KAAKwiB,GAGtB,MAAO,IAAI2Y,KAAiBC,EAC9B,CClMA,SAASrP,GAAYrmB,GACnB,IACE,MAAM6yB,EAAS,IAAI5yB,IAAID,GACvB,GAAwB,WAApB6yB,EAAO3yB,UAA6C,UAApB2yB,EAAO3yB,SACzC,MAAM,IAAI4F,MAAM,uCAElB,OAAO9F,CACT,OAAS1U,GACP,MAAM,IAAIwa,MAAM,8BAA8Bxa,EAAIqqC,UACpD,CACF,CAGA,SAASC,GAAetiC,EAASuiC,EAAW,IAC1C,OAAO/C,KAAKG,UAAU,CACpB3/B,UACAwiC,UAAWlN,KAAKmN,MAChBC,QApBqB,KAqBlBH,GAEP,CAGA,SAASI,GAAcC,GACrB,IAAKA,EAAM,OAAO,KAClB,IACE,MAAM9qC,EAAO0nC,KAAKxoB,MAAM4rB,GACxB,OAAK9qC,GAAgC,iBAAjBA,EAAKkI,QAClBlI,EAD+C,IAExD,CAAA,MACE,OAAO,IACT,CACF,CAIO,MAAM+qC,GACX,WAAAzsC,EAAY0sC,OAAEA,EAAS,gBAAmB,CAAA,GACxCzsC,KAAKysC,OAASA,CAChB,CAEA,IAAAC,CAAK9rC,GACH,MAAO,GAAGZ,KAAKysC,UAAU7rC,GAC3B,CAEA,UAAMqG,CAAKrG,EAAK+I,EAASuiC,GACvB,IACElD,aAAaK,QAAQrpC,KAAK0sC,KAAK9rC,GAAMqrC,GAAetiC,EAASuiC,GAC/D,OAASvqC,GACP,MAAM,IAAIwa,MAAM,6BAA6Bxa,EAAIqqC,UACnD,CACF,CAEA,UAAMW,CAAK/rC,GACT,IAEE,OAAO0rC,GADKtD,aAAaC,QAAQjpC,KAAK0sC,KAAK9rC,IAE7C,CAAA,MACE,OAAO,IACT,CACF,CAEA,WAAMkB,CAAMlB,GACV,IACEooC,aAAa4B,WAAW5qC,KAAK0sC,KAAK9rC,GACpC,CAAA,MAEA,CACF,CAMA,QAAAgsC,CAAShsC,EAAK+I,EAASuiC,GACrB,IAEE,OADAlD,aAAaK,QAAQrpC,KAAK0sC,KAAK9rC,GAAMqrC,GAAetiC,EAASuiC,KACtD,CACT,CAAA,MACE,OAAO,CACT,CACF,EAKK,MAAMW,GACX,WAAA9sC,EAAY0sC,OAAEA,EAAS,gBAAmB,CAAA,GACxCzsC,KAAKysC,OAASA,CAChB,CAEA,IAAAC,CAAK9rC,GACH,MAAO,GAAGZ,KAAKysC,UAAU7rC,GAC3B,CAEA,UAAMqG,CAAKrG,EAAK+I,EAASuiC,GACvB,IACEY,eAAezD,QAAQrpC,KAAK0sC,KAAK9rC,GAAMqrC,GAAetiC,EAASuiC,GACjE,OAASvqC,GACP,MAAM,IAAIwa,MAAM,+BAA+Bxa,EAAIqqC,UACrD,CACF,CAEA,UAAMW,CAAK/rC,GACT,IAEE,OAAO0rC,GADKQ,eAAe7D,QAAQjpC,KAAK0sC,KAAK9rC,IAE/C,CAAA,MACE,OAAO,IACT,CACF,CAEA,WAAMkB,CAAMlB,GACV,IACEksC,eAAelC,WAAW5qC,KAAK0sC,KAAK9rC,GACtC,CAAA,MAEA,CACF,CAEA,QAAAgsC,CAAShsC,EAAK+I,EAASuiC,GACrB,IAEE,OADAY,eAAezD,QAAQrpC,KAAK0sC,KAAK9rC,GAAMqrC,GAAetiC,EAASuiC,KACxD,CACT,CAAA,MACE,OAAO,CACT,CACF,EAKK,MAAMa,GAOX,WAAAhtC,EAAYitC,QAAEA,EAAAC,OAASA,EAAAC,SAAQA,IAC7B,GAAuB,mBAAZF,EAAwB,MAAM,IAAI7wB,MAAM,yCACnD,GAAsB,mBAAX8wB,EAAuB,MAAM,IAAI9wB,MAAM,wCAClD,GAAwB,mBAAb+wB,EAAyB,MAAM,IAAI/wB,MAAM,0CACpDnc,KAAKgtC,QAAUA,EACfhtC,KAAKitC,OAASA,EACdjtC,KAAKktC,SAAWA,CAClB,CAEA,UAAMjmC,CAAKrG,EAAK+I,EAASuiC,GACvB,MAAMiB,EAAWlB,GAAetiC,EAASuiC,SACnClsC,KAAKgtC,QAAQpsC,EAAKusC,EAC1B,CAEA,UAAMR,CAAK/rC,GAET,OAAO0rC,SADWtsC,KAAKitC,OAAOrsC,GAEhC,CAEA,WAAMkB,CAAMlB,SACJZ,KAAKktC,SAAStsC,EACtB,EAKK,MAAMwsC,GA0BX,WAAArtC,EAAYstC,SACVA,EAAAC,QACAA,EAAU,CAAA,EAAAC,OACVA,EAAS,MAAAC,QACTA,EAAAC,SACAA,EAAAC,UACAA,EAAAC,aACAA,EAAAC,eACAA,IAEA,IAAKP,IAAaI,EAAU,MAAM,IAAItxB,MAAM,kDAE5C,GAAIkxB,EACF,IACE,MAAMnE,EAAS,IAAI5yB,IAAI+2B,GACvB,GAAwB,WAApBnE,EAAO3yB,UAA6C,UAApB2yB,EAAO3yB,SACzC,MAAM,IAAI4F,MAAM,yDAEpB,OAASxa,GACP,GAAIA,EAAIqqC,QAAQ14B,SAAS,YAAa,MAAM3R,EAC5C,MAAM,IAAIwa,MAAM,8CAA8CkxB,IAChE,CAUF,GARArtC,KAAKqtC,SAAWA,EAChBrtC,KAAKstC,QAAUA,EACfttC,KAAKutC,OAASA,EACdvtC,KAAK6tC,OAASL,IAA6B,oBAAVM,MAAwBA,MAAM57B,KAAK67B,YAAc,MAClF/tC,KAAKytC,SAAWA,EAChBztC,KAAK0tC,UAAYA,EACjB1tC,KAAK2tC,aAAeA,GAAgBF,EACpCztC,KAAK4tC,eAAiBA,GAAkBH,GACnCztC,KAAK6tC,OAAQ,MAAM,IAAI1xB,MAAM,mDACpC,CAEA,QAAA6xB,CAASptC,GACP,OAAOZ,KAAKytC,SAAW/Q,GAAY18B,KAAKytC,SAAS7sC,IAAQZ,KAAKqtC,QAChE,CAEA,QAAAY,CAASrtC,GACP,GAAIZ,KAAK2tC,aAAc,OAAOjR,GAAY18B,KAAK2tC,aAAa/sC,IAC5D,MAAMstC,EAAMluC,KAAKqtC,SAAS/5B,SAAS,KAAO,IAAM,IAChD,MAAO,GAAGtT,KAAKqtC,WAAWa,QAAUC,mBAAmBvtC,IACzD,CAEA,UAAAwtC,CAAWxtC,GACT,OAAOZ,KAAK4tC,eAAiBlR,GAAY18B,KAAK4tC,eAAehtC,IAAQZ,KAAKiuC,SAASrtC,EACrF,CAEA,qBAAMytC,CAAgBh4B,EAAKi4B,EAAMC,EAAU,GACzC,IACE,MAAMC,QAAYxuC,KAAK6tC,OAAOx3B,EAAKi4B,GACnC,IAAKE,EAAIC,GAAI,MAAM,IAAItyB,MAAM,QAAQqyB,EAAIE,WAAWF,EAAIG,cACxD,OAAOH,CACT,OAAS7sC,GACP,GAAI4sC,EAAU,EAEZ,aADM,IAAInoC,SAAQme,GAAK5U,WAAW4U,EAAG,OAC9BvkB,KAAKquC,gBAAgBh4B,EAAKi4B,EAAMC,EAAU,GAEnD,MAAM5sC,CACR,CACF,CAEA,UAAMsF,CAAKrG,EAAK+I,EAASuiC,GACvB,MAAMx0B,EAAO1X,KAAK0tC,UACd1tC,KAAK0tC,UAAU9sC,EAAK+I,GACpBsiC,GAAetiC,EAASuiC,GAEtBoB,EAAU,IAAKttC,KAAKstC,SACrBttC,KAAK0tC,WAAcJ,EAAQ,kBAC9BA,EAAQ,gBAAkB,0BAGtBttC,KAAKquC,gBAAgBruC,KAAKguC,SAASptC,GAAM,CAC7C2sC,OAAQvtC,KAAKutC,OACbD,UACA51B,QAEJ,CAEA,UAAMi1B,CAAK/rC,GACT,IACE,MAAM4tC,QAAYxuC,KAAK6tC,OAAO7tC,KAAKiuC,SAASrtC,GAAM,CAChD2sC,OAAQ,MACRD,QAASttC,KAAKstC,UAEhB,IAAKkB,EAAIC,GAAI,CACX,GAAmB,MAAfD,EAAIE,OAAgB,OAAO,KAC/B,MAAM,IAAIvyB,MAAM,QAAQqyB,EAAIE,SAC9B,CAEA,OAAOpC,SADYkC,EAAI9iC,OAEzB,CAAA,MACE,OAAO,IACT,CACF,CAEA,WAAM5J,CAAMlB,GACV,UACQZ,KAAK6tC,OAAO7tC,KAAKouC,WAAWxtC,GAAM,CACtC2sC,OAAQ,SACRD,QAASttC,KAAKstC,SAElB,CAAA,MAEA,CACF,EAKK,MAAMsB,GAOX,WAAA7uC,EAAYkH,KAAEA,EAAA0lC,KAAMA,EAAA7qC,MAAMA,IACxB,GAAoB,mBAATmF,EAAqB,MAAM,IAAIkV,MAAM,2CAChD,GAAoB,mBAATwwB,EAAqB,MAAM,IAAIxwB,MAAM,2CAChD,GAAqB,mBAAVra,EAAsB,MAAM,IAAIqa,MAAM,4CACjDnc,KAAK6uC,MAAQ5nC,EACbjH,KAAK8uC,MAAQnC,EACb3sC,KAAK+uC,OAASjtC,CAChB,CAEA,UAAMmF,CAAKrG,EAAK+I,EAASuiC,SACjBlsC,KAAK6uC,MAAMjuC,EAAK+I,EAASuiC,EACjC,CAEA,UAAMS,CAAK/rC,GACT,OAAOZ,KAAK8uC,MAAMluC,EACpB,CAEA,WAAMkB,CAAMlB,SACJZ,KAAK+uC,OAAOnuC,EACpB,EAgBK,SAASouC,GAAsBjc,GACpC,IAAKA,GAAqB,iBAAXA,EACb,OAAO,IAAIyZ,GAEb,GAAe,mBAAXzZ,EACF,OAAO,IAAI8Z,GAEb,GAAsB,iBAAX9Z,EACT,MAAM,IAAI5W,MAAM,6BAA6B4W,KAI/C,GAA2B,mBAAhBA,EAAO9rB,MAA8C,mBAAhB8rB,EAAO4Z,MAA+C,mBAAjB5Z,EAAOjxB,MAE1F,OAAIixB,EAAOhzB,cAAgBoD,OAClB,IAAIyrC,GAAe7b,GAGrBA,EAIT,GAAIA,EAAOsa,UAAYta,EAAO0a,SAC5B,OAAO,IAAIL,GAAcra,GAI3B,GAAIA,EAAOia,QACT,OAAO,IAAID,GAAmBha,GAIhC,GAAIA,EAAO0Z,OACT,OAAO,IAAID,GAAqBzZ,GAGlC,MAAM,IAAI5W,MAAM,iDAClB,CClXA,MAAM8yB,sBAAuB/uC,IAEtB,MAAMgvC,GAUX,WAAAnvC,CAAYiM,EAAQxL,EAAU,IAC5BR,KAAKgM,OAASA,EACdhM,KAAKmvC,SAAWH,GAAsBxuC,EAAQ2uC,UAC9CnvC,KAAKY,IAAMJ,EAAQI,KAAO,cAG1B,MAAME,EAAWmuC,GAAiBluC,IAAIf,KAAKY,KACvCE,GAEFA,EAAS0O,UAEXy/B,GAAiBvuC,IAAIV,KAAKY,IAAKZ,MAC/BA,KAAKovC,SAAW5uC,EAAQ4uC,UAAY,IACpCpvC,KAAKqO,WAAa7N,EAAQ6uC,UAAY,IACtCrvC,KAAKsvC,SAA8B,IAApB9uC,EAAQ8uC,QAEvBtvC,KAAK0O,eAAiB,KACtB1O,KAAKuvC,eAAiB,KACtBvvC,KAAKwvC,WAAY,EACjBxvC,KAAKyvC,cAAe,EACpBzvC,KAAK0vC,kBAAoB,KACzB1vC,KAAK2vC,sBAAwB,KAC7B3vC,KAAK4vC,qBAAuB,KAC5B5vC,KAAK6vC,YAAa,EAClB7vC,KAAK8vC,mBAAqB,EAC1B9vC,KAAK+vC,YAAcvvC,EAAQwvC,YAAc,CAC3C,CASA,IAAAhhC,GACOhP,KAAKsvC,UAAWtvC,KAAK6vC,aAG1B7vC,KAAK2vC,sBAAwB,KAC3B3vC,KAAKiwC,wBAAsB,EAE7BjwC,KAAKkwC,oBAAsBlwC,KAAKgM,OAAOiB,SAAS5M,GAAG,iBAAkBL,KAAK2vC,uBAGtE3vC,KAAKovC,SAAW,IAClBpvC,KAAKuvC,eAAiBY,aAAY,KAChCnwC,KAAKiH,MAAI,GACRjH,KAAKovC,WAIY,oBAAXxpC,SACT5F,KAAK4vC,qBAAuB,KAC1B5vC,KAAKowC,kBAAgB,EAEvBxqC,OAAOuM,iBAAiB,eAAgBnS,KAAK4vC,uBAEjD,CASA,OAAApgC,GAEMxP,KAAK0O,iBACPgB,aAAa1P,KAAK0O,gBAClB1O,KAAK0O,eAAiB,MAEpB1O,KAAKuvC,iBACPc,cAAcrwC,KAAKuvC,gBACnBvvC,KAAKuvC,eAAiB,MAIpBvvC,KAAKkwC,sBACPlwC,KAAKkwC,sBACLlwC,KAAKkwC,oBAAsB,MAIzBlwC,KAAK4vC,sBAA0C,oBAAXhqC,SACtCA,OAAOwM,oBAAoB,eAAgBpS,KAAK4vC,sBAChD5vC,KAAK4vC,qBAAuB,MAI1BX,GAAiBluC,IAAIf,KAAKY,OAASZ,MACrCivC,GAAiB9tC,OAAOnB,KAAKY,KAI/BZ,KAAKiH,OAAOgb,OAAM,SAAUquB,SAAQ,KAAQtwC,KAAK6vC,YAAa,CAAA,GAChE,CAUA,UAAM5oC,GACJ,IAAKjH,KAAKgM,QAAUhM,KAAK6vC,WAAY,OAErC,MAAMlmC,EAAU3J,KAAKgM,OAAO+f,UAG5B,GAAIpiB,IAAY3J,KAAK0vC,kBAGrB,GAAI1vC,KAAKwvC,UACPxvC,KAAKyvC,cAAe,MADtB,CAKAzvC,KAAKwvC,WAAY,EACjBxvC,KAAKgM,OAAOiB,SAASzL,KAAK,mBAE1B,UACQxB,KAAKmvC,SAASloC,KAAKjH,KAAKY,IAAK+I,GACnC3J,KAAK0vC,kBAAoB/lC,EACzB3J,KAAK8vC,mBAAqB,EAE1B,MAAM3D,EAAYlN,KAAKmN,MACvBpsC,KAAKgM,OAAOiB,SAASzL,KAAK,iBAAkB,CAAE2qC,aAChD,OAASvqC,GACP5B,KAAK8vC,qBACL9vC,KAAKgM,OAAOiB,SAASzL,KAAK,iBAAkB,CAAEI,QAAO2uC,WAAYvwC,KAAK8vC,oBACxE,CAAA,QAIE,GAHA9vC,KAAKwvC,WAAY,EAGbxvC,KAAKyvC,aAEP,GADAzvC,KAAKyvC,cAAe,EAChBzvC,KAAK8vC,oBAAsB9vC,KAAK+vC,YAClC/vC,KAAKgM,OAAOiB,SAASzL,KAAK,iBAAkB,CAC1CI,MAAO,IAAIua,MAAM,yBAAyBnc,KAAK+vC,oCAC/CS,OAAO,SAEX,GAAWxwC,KAAK8vC,mBAAqB,EAAG,CAEtC,MAAMW,EAAQrsC,KAAKsE,IAAI,IAAOtE,KAAKssC,IAAI,EAAG1wC,KAAK8vC,mBAAqB,GAAI,KACxEngC,YAAW,IAAM3P,KAAKiH,QAAQwpC,EAChC,MACEzwC,KAAKiH,MAGX,CAlCA,CAmCF,CASA,mBAAM0pC,CAAcC,GAClB,IACE,MAAMC,QAAe7wC,KAAKmvC,SAASxC,KAAK3sC,KAAKY,KAC7C,IAAKiwC,IAAWA,EAAOlnC,QAAS,OAAO,KAGvC,MAAMmnC,EAAaD,EAAOlnC,QAAQhF,OAC5BosC,GAAeH,GAAkB,IAAIjsC,OAG3C,OAAImsC,GAAcA,IAAeC,EACxB,CACLC,iBAAkBH,EAAOlnC,QACzBwiC,UAAW0E,EAAO1E,WAAalN,KAAKmN,OAIjC,IACT,CAAA,MACE,OAAO,IACT,CACF,CAQA,mBAAM6E,GACJ,UACQjxC,KAAKmvC,SAASrtC,MAAM9B,KAAKY,IACjC,CAAA,MAEA,CACF,CAMA,sBAAAqvC,GACMjwC,KAAK0O,gBACPgB,aAAa1P,KAAK0O,gBAEpB1O,KAAK0O,eAAiBiB,YAAW,KAC/B3P,KAAK0O,eAAiB,KACtB1O,KAAKiH,MAAI,GACRjH,KAAKqO,WACV,CAQA,gBAAA+hC,GACE,IAAKpwC,KAAKgM,QAAUhM,KAAK6vC,WAAY,OAErC,MAAMlmC,EAAU3J,KAAKgM,OAAO+f,UAC5B,GAAIpiB,IAAY3J,KAAK0vC,kBAGrB,GAAsC,mBAA3B1vC,KAAKmvC,SAASvC,UAMzB,GAAyB,oBAAdj7B,WAA6BA,UAAUu/B,YAAclxC,KAAKmvC,SAAS9B,SAC5E,IACE,MAAM31B,EAAOyxB,KAAKG,UAAU,CAC1B1oC,IAAKZ,KAAKY,IACV+I,UACAwiC,UAAWlN,KAAKmN,MAChBC,QAAS,IAEX16B,UAAUu/B,WAAWlxC,KAAKmvC,SAAS9B,SAAU31B,EAC/C,CAAA,MAEA,OAhBA1X,KAAKmvC,SAASvC,SAAS5sC,KAAKY,IAAK+I,EAkBrC,ECxOK,SAASwnC,GAAaC,GAC3B,MAAO,CACLjlC,KAAMilC,EAAWjlC,KACjBkgC,QAAS+E,EAAW/E,SAAW,QAC/Brf,YAAaokB,EAAWpkB,aAAe,GACvCqkB,OAAQD,EAAWC,QAAU,GAC7B5iB,aAAc2iB,EAAW3iB,cAAgB,GACzCiB,mBAAoB0hB,EAAW1hB,qBAAsB,EACrDR,KAAMkiB,EAAWliB,OAAQ,EACzBlgB,KAAMoiC,EAAWpiC,MAAA,MAAgB,GACjCQ,QAAS4hC,EAAW5hC,SAAA,MAAmB,GACvCogB,gBAAiBwhB,EAAWxhB,iBAAmB,KAC/CE,kBAAmBshB,EAAWthB,mBAAqB,KACnDrc,SAAU29B,EAAW39B,UAAY,GACjC69B,aAAcF,EAAWE,cAAgB,GACzCC,eAAgBH,EAAWG,gBAAkB,GAC7CC,iBAAkBJ,EAAWI,kBAAoB,GACjD5jB,eAAgBwjB,EAAWxjB,gBAAkB,GAC7CD,gBAAiByjB,EAAWzjB,iBAAmB,CAAA,EAEnD,CChEO,SAAS8jB,KACd,IAAIC,EAAgB,KAChBC,EAAqB,KAEzB,OAAOR,GAAa,CAClBhlC,KAAM,YACNujB,oBAAoB,EACpB,IAAA1gB,CAAKhD,GACH,MAAM4lC,EAAS,KACb,MAAMlmC,EAAOM,EAAOggB,UAAUrnB,OAGxBlD,EAAO,CAAEowC,UAFGnmC,EAAOA,EAAKiH,MAAM,OAAOnL,OAAS,EAE1BS,UADR+D,EAAOggB,UAAUxkB,QAEnCwE,EAAO8lC,WAAarwC,EACpBuK,EAAOiB,SAASzL,KAAK,mBAAoBC,EAAI,EAU/CkwC,EAAqB3lC,EAAOiB,SAAS5M,GAAG,kBANhB,KACtBqP,aAAagiC,GACbA,EAAgB/hC,WAAWiiC,EAAQ,IAAG,IAMxCA,GACF,EAEA,OAAApiC,GACEE,aAAagiC,GACTC,IACFA,IACAA,EAAqB,KAEzB,GAEJ,sJC7BMI,GAAqB,IAAArK,OAAA,4TAAA,KAGrBsK,GAAwB,eACxBC,GAA+B,qBAG/BC,sBAAkBvxC,IAAI,CAC1B,MAAO,MAAO,MAAO,MAAO,MAAO,MAAO,MAC1C,KAAM,KAAM,KAAM,KAAM,KAAM,KAAM,KAAM,KAAM,KAAM,KAAM,KAAM,KAAM,KAAM,KAC9E,KAAM,KAAM,KAAM,KAAM,KAAM,KAAM,KAAM,KAAM,KAAM,KAAM,KAAM,KAAM,KAAM,KAC9E,MAAO,MAAO,KAAM,KAAM,KAAM,KAAM,OAAQ,MAAO,MAAO,OAAQ,SACpE,MAAO,SAAU,OAAQ,OAAQ,QAAS,OAAQ,QAAS,SAAU,WAOvE,SAASwxC,GAAWz7B,GAElB,MACMzD,EADWyD,EAAO/D,MAAM,KAAK,GACZA,MAAM,KAC7B,OAAOM,EAAMA,EAAMzL,OAAS,GAAG2D,aACjC,CAEO,SAASinC,KAEd,IAAIC,EAAW,KAEf,OAAOlB,GAAa,CAClBhlC,KAAM,WACNujB,oBAAoB,EACpB,IAAA1gB,CAAKhD,GACHqmC,EAAYr/B,IACV,GAAc,MAAVA,EAAEpS,KAAyB,UAAVoS,EAAEpS,IAAiB,OAExC,MAAMkF,EAAMF,OAAOD,eACnB,IAAKG,IAAQA,EAAIid,UAAW,OAC5B,MAAMmlB,EAAWpiC,EAAIid,UACrB,GAAImlB,EAAS9lC,WAAaC,KAAKC,UAAW,OAG1C,GAAI4lC,EAAS3lC,cAAcggB,QAAQ,KAAM,OAEzC,MACM2f,EA4Cd,SAA0Bx2B,GAExB,IAAKA,GAAQA,EAAKlE,OAAS8qC,KAAoB,OAAO,KAGtD,MAAMC,EAAcnuC,KAAKy9B,IAAI,EAAGn2B,EAAKlE,OAAS,KAC1C+qC,EAAc,IAChB7mC,EAAOA,EAAKwC,MAAMqkC,IAGpB,IAAIC,EAAO,KAGX,IAAA,MAAWzL,KAAKr7B,EAAK+mC,SAASV,IAAqB,CACjD,MAAM17B,EAAM0wB,EAAE,GACR2L,EAAar8B,EAAI8B,WAAW,YAAc9B,EAAI8B,WAAW,YACzDw6B,GAASD,GAAcr8B,EAAI8B,WAAW,QACtCy6B,GAAUF,IAAeC,EAG/B,IAAIE,EAAY,KAEhB,GAAIH,EACFG,EAAY,CAAEx8B,MAAK5K,KAAM4K,EAAKy8B,SAAU/L,EAAEx2B,MAAOwiC,OAAQhM,EAAEx2B,MAAQ8F,EAAI7O,gBAC9DmrC,EAAO,CAEhB,MAAMnf,EAAS9nB,EAAKwC,MAAM9J,KAAKy9B,IAAI,EAAGkF,EAAEx2B,MAAQ,GAAIw2B,EAAEx2B,OACtD,GAAIyhC,GAAsB5mC,KAAKooB,GAAS,SACxCqf,EAAY,CAAEx8B,MAAK5K,KAAM,WAAa4K,EAAKy8B,SAAU/L,EAAEx2B,MAAOwiC,OAAQhM,EAAEx2B,MAAQ8F,EAAI7O,OACtF,SAAWorC,EAAQ,CACjB,MAAMI,EAAMb,GAAW97B,GAEvB,IAAK67B,GAAYzxC,IAAIuyC,GAAM,SAE3B,MAAMxf,EAAS9nB,EAAKwC,MAAM9J,KAAKy9B,IAAI,EAAGkF,EAAEx2B,MAAQ,IAAKw2B,EAAEx2B,OACvD,GAAI0hC,GAA6B7mC,KAAKooB,IAAW,SAASpoB,KAAKooB,GAAS,SACxEqf,EAAY,CAAEx8B,MAAK5K,KAAM,WAAa4K,EAAKy8B,SAAU/L,EAAEx2B,MAAOwiC,OAAQhM,EAAEx2B,MAAQ8F,EAAI7O,OACtF,CAEIqrC,KAAeL,GAAQK,EAAUC,UAAYN,EAAKM,YACpDN,EAAOK,EAEX,CAGIL,GAAQD,EAAc,IACxBC,EAAKM,UAAYP,EACjBC,EAAKO,QAAUR,GAGjB,OAAOC,CACT,CA/FsBS,CADD/K,EAASxjC,aAEtB,IAAKw9B,EAAO,OAEZ,MAAMz2B,KAAEA,EAAAqnC,SAAMA,EAAAC,OAAUA,GAAW7Q,EAInC,GAAI6Q,EADiBjtC,EAAIotC,YACE,OAG3B,MAAMnwC,EAAQE,SAASkE,cACvBpE,EAAM0F,SAASy/B,EAAU4K,GACzB/vC,EAAMsE,OAAO6gC,EAAU6K,GAEvB,MAAMpoC,EAAO1H,SAASC,cAAc,KACpCyH,EAAKc,KAAOA,EACZd,EAAKgB,OAAS,SACdhB,EAAKkyB,IAAM,sBACX95B,EAAMQ,iBAAiBoH,GAGvB,MAAMd,EAAW5G,SAASkE,cAC1B0C,EAASC,cAAca,GACvBd,EAASlB,UAAS,GAClB7C,EAAIU,kBACJV,EAAIW,SAASoD,EAAQ,EAGvBmC,EAAOnI,QAAQsO,iBAAiB,UAAWkgC,EAC7C,EACA,OAAA7iC,CAAQxD,GACFqmC,IACFrmC,EAAOnI,QAAQuO,oBAAoB,UAAWigC,GAC9CA,EAAW,KAEf,GAEJ,qJC3FO,SAASc,GAAkBC,EAAkB,mBAClD,OAAOjC,GAAa,CAClBhlC,KAAM,cACNujB,oBAAoB,EACpB,IAAA1gB,CAAKhD,GACH,MAAM4lC,EAAS,KACT5lC,EAAOigB,WACTjgB,EAAOnI,QAAQP,aAAa,mBAAoB8vC,GAChDpnC,EAAOnI,QAAQ4b,UAAUxe,IAAI,cAE7B+K,EAAOnI,QAAQ4b,UAAUsI,OAAO,YAClC,EAEF/b,EAAOiB,SAAS5M,GAAG,iBAAkBuxC,GACrC5lC,EAAOiB,SAAS5M,GAAG,QAASuxC,GAC5B5lC,EAAOiB,SAAS5M,GAAG,OAAQuxC,GAC3BA,GACF,GAEJ,wJCZO,MAAMyB,GAOX,WAAAtzC,CAAYS,EAAU,IACpBR,KAAKszC,YAAc9yC,EAAQ+yC,aAAoC,oBAAd5hC,UAA4BA,UAAU6hC,oBAAsB,IAAM,EACnHxzC,KAAKyzC,SAAW,GAChBzzC,KAAK0zC,OAAS,GACd1zC,KAAK2zC,QAAU,EACf3zC,KAAK4zC,4BAAe1zC,IACpBF,KAAK6zC,YAAc,EACnB7zC,KAAK8zC,SAA6B,oBAAXC,SAA8C,IAApBvzC,EAAQ8uC,QACzDtvC,KAAKg0C,WAAaxzC,EAAQyzC,WAAa,IACzC,CASA,aAAM5nC,CAAQ6nC,EAAUzyC,GACtB,IAAKzB,KAAK8zC,SACR,OAAO9zC,KAAKm0C,iBAAiBD,EAAUzyC,GAIrCzB,KAAKyzC,SAASjsC,OAASxH,KAAKszC,aAC9BtzC,KAAKo0C,eAGP,MAAMniB,EAAKjyB,KAAK2zC,UACVU,EAASr0C,KAAKyzC,SAASzzC,KAAK6zC,YAAc7zC,KAAKyzC,SAASjsC,QAG9D,OAFAxH,KAAK6zC,cAEE,IAAIztC,SAAQ,CAACC,EAASyV,KAC3B9b,KAAK4zC,SAASlzC,IAAIuxB,EAAI,CAAE5rB,UAASyV,WACjCu4B,EAAOC,YAAY,CAAEriB,KAAIniB,KAAMokC,EAAUzyC,QAAM,GAEnD,CAUA,gBAAA0yC,CAAiBD,EAAUzyC,GACzB,OAAQyyC,GACN,IAAK,WAEH,OAAOzyC,EACJyX,QAAQ,sDAAuD,IAC/DA,QAAQ,+CAAgD,IAC7D,IAAK,WAEH,OAAOzX,EACT,IAAK,UACH,MAAO,CAAEyJ,OAAQzJ,EAAKyJ,OAAQ7B,KAAM,KAAMkrC,UAAU,GACtD,QACE,MAAM,IAAIp4B,MAAM,sBAAsB+3B,KAE5C,CAOA,YAAAE,GACE,IACE,MAAMH,EAAYj0C,KAAKg0C,YAAc,o+MAA8CvoC,KAC7E4oC,EAAS,IAAIN,OAAOE,EAAW,CAAEnkC,KAAM,WAE7CukC,EAAOG,UAAaxhC,IAClB,MAAMif,GAAEA,EAAAjlB,OAAIA,EAAApL,MAAQA,GAAUoR,EAAEvR,KAC1BlB,EAAUP,KAAK4zC,SAAS7yC,IAAIkxB,GAC9B1xB,IACFP,KAAK4zC,SAASzyC,OAAO8wB,GACjBrwB,EACFrB,EAAQub,OAAO,IAAIK,MAAMva,IAEzBrB,EAAQ8F,QAAQ2G,GAEpB,EAGFqnC,EAAOn4B,QAAWva,IAC+B,EAGjD3B,KAAKyzC,SAAS9iC,KAAK0jC,EACrB,OAAS1yC,GAGP3B,KAAK8zC,UAAW,CAClB,CACF,CAMA,aAAIvnC,GACF,OAAOvM,KAAK8zC,QACd,CAMA,eAAIW,GACF,OAAOz0C,KAAKyzC,SAASjsC,MACvB,CAKA,OAAAgI,GACExP,KAAKyzC,SAAS/xC,SAAQgzC,GAAKA,EAAEC,cAC7B30C,KAAKyzC,SAAW,GAGhBzzC,KAAK4zC,SAASlyC,SAAQ,EAAGoa,aACvBA,EAAO,IAAIK,MAAM,wBAAuB,IAE1Cnc,KAAK4zC,SAAS9xC,QACd9B,KAAK0zC,OAAS,EAChB,EC3HF,MAAMzP,sBAAiBtjC,IAAI,CACzB,IAAK,MAAO,KAAM,KAAM,KAAM,KAAM,KAAM,KAC1C,aAAc,MAAO,KAAM,KAAM,KAAM,QACvC,SAAU,UAAW,UAAW,QAAS,KACzC,KAAM,KAAM,KAAM,UAAW,YAGxB,MAAMi0C,GAOX,WAAA70C,CAAYkJ,EAAWzI,EAAU,IAC/BR,KAAK60C,WAAa5rC,EAClBjJ,KAAK80C,WAAat0C,EAAQu0C,WAAa,IACvC/0C,KAAKg1C,YAAcx0C,EAAQy0C,YAAc,QACzCj1C,KAAKyO,UAAY,KACjBzO,KAAKk1C,SAAU,EAEfl1C,KAAKm1C,8BAAiBj1C,IAEtBF,KAAKo1C,iCAAoBz0C,GAC3B,CAQA,IAAAqO,GAC0B,IAApBhP,KAAK80C,YAC2B,oBAAzBO,uBAEXr1C,KAAKyO,UAAY,IAAI4mC,sBAClBjyC,GAAYpD,KAAKs1C,oBAAoBlyC,IACtC,CACEV,KAAM,KACNuyC,WAAYj1C,KAAKg1C,YACjBD,UAAW,IAIf/0C,KAAKu1C,QACP,CASA,KAAAA,GACE,MAAMlsB,EAASrpB,KAAKw1C,oBAEhBnsB,EAAO7hB,OAASxH,KAAK80C,WACvB90C,KAAKk1C,SAAU,GAIjBl1C,KAAKk1C,SAAU,EACf7rB,EAAO3nB,SAAS+C,IACdzE,KAAKyO,UAAUW,QAAQ3K,EAAK,IAEhC,CAQA,iBAAA+wC,GACE,MAAMnsB,EAAS,GACTI,EAAWzpB,KAAK60C,WAAWprB,SACjC,IAAA,IAAShc,EAAI,EAAGA,EAAIgc,EAASjiB,OAAQiG,IAAK,CACxC,MAAMqK,EAAQ2R,EAAShc,GACA,IAAnBqK,EAAM1V,UAAkB6hC,GAAWxjC,IAAIqX,EAAMtV,UAC/C6mB,EAAO1Y,KAAKmH,EAEhB,CACA,OAAOuR,CACT,CASA,mBAAAisB,CAAoBlyC,GAClB,IAAA,MAAWoN,KAASpN,EAAS,CAC3B,MAAMjB,EAAKqO,EAAM7E,OAEb6E,EAAMilC,eAERz1C,KAAK01C,SAASvzC,GAGdnC,KAAK21C,UAAUxzC,EAEnB,CACF,CASA,SAAAwzC,CAAUxzC,GAER,GAAInC,KAAKo1C,cAAc30C,IAAI0B,GAAK,OAEhC,GAAInC,KAAKm1C,WAAW10C,IAAI0B,GAAK,OAE7B,MACM+nB,EADO/nB,EAAG2J,wBACIoe,OACpB,GAAe,IAAXA,EAAc,OAElB,MAAM7gB,EAAOlH,EAAG6E,UACVxE,EAAUL,EAAGK,QAGnBxC,KAAKm1C,WAAWz0C,IAAIyB,EAAI,CAAEkH,OAAM6gB,SAAQ1nB,YAGxCL,EAAG6E,UAAY,GACf7E,EAAGmJ,MAAMsqC,UAAY,GAAG1rB,MACxB/nB,EAAGmB,aAAa,mBAAoB,QACpCtD,KAAKo1C,cAAcn0C,IAAIkB,EACzB,CAQA,QAAAuzC,CAASvzC,GACP,MAAMV,EAAOzB,KAAKm1C,WAAWp0C,IAAIoB,GAC5BV,IAELU,EAAG6E,UAAYvF,EAAK4H,KACpBlH,EAAGmJ,MAAMsqC,UAAY,GACrBzzC,EAAGiW,gBAAgB,oBACnBpY,KAAKm1C,WAAWh0C,OAAOgB,GACvBnC,KAAKo1C,cAAcj0C,OAAOgB,GAC5B,CAQA,UAAA0zC,GACE71C,KAAKm1C,WAAWzzC,SAAQ,CAACD,EAAMU,KAC7BnC,KAAK01C,SAASvzC,EAAE,GAEpB,CAQA,OAAA2zC,GACO91C,KAAKyO,YAGVzO,KAAKyO,UAAUgB,aAGfzP,KAAK61C,aAGL71C,KAAKu1C,QACP,CAMA,YAAIjpC,GACF,OAAOtM,KAAKk1C,OACd,CAMA,kBAAIa,GACF,OAAO/1C,KAAKm1C,WAAW/zC,IACzB,CAOA,OAAAoO,GACExP,KAAK61C,aAED71C,KAAKyO,YACPzO,KAAKyO,UAAUgB,aACfzP,KAAKyO,UAAY,MAGnBzO,KAAKm1C,WAAWrzC,QAChB9B,KAAKo1C,cAActzC,QACnB9B,KAAKk1C,SAAU,CACjB,EC1OF,MAAM3rC,GAAa,IAAIsN,EAEvB,SAASm/B,GAAgBC,EAAMx6B,GAC7B,MAAMpF,EAAMC,IAAI4/B,gBAAgBD,GAC1BxhC,EAAIxR,SAASC,cAAc,KACjCuR,EAAEhJ,KAAO4K,EACT5B,EAAE0hC,SAAW16B,EACbxY,SAASyU,KAAKhU,YAAY+Q,GAC1BA,EAAE2hC,QACFnzC,SAASyU,KAAKxT,YAAYuQ,GAC1B6B,IAAI+/B,gBAAgBhgC,EACtB,CAEO,SAASigC,GAAiBjtC,EAAMoS,EAAW,eAChD,MAAMoE,EAAKnB,GAAerV,GAE1B2sC,GADa,IAAIO,KAAK,CAAC12B,GAAK,CAAE/P,KAAM,gCACd2L,EACxB,CAEO,SAAS+6B,GAAYntC,EAAM8W,EAAQ,YACxC,MAAMvK,EAAS3S,SAASC,cAAc,UACtC0S,EAAOtK,MAAMmrC,QAAU,4DAEvB7gC,EAAOtS,aAAa,UAAW,kCAC/BL,SAASyU,KAAKhU,YAAYkS,GAG1B,MAAMtM,EAAWC,GAAWC,SAASH,GAI/BqtC,EAAc,6CAHFv2B,EAAMjH,QAAQ,KAAM,QAAQA,QAAQ,KAAM,QAAQA,QAAQ,KAAM,yxBAmB5E5P,oBAENsM,EAAO+gC,OAASD,EAGhB,IAAIz9B,GAAU,EACd,MAAM29B,EAAU,KACV39B,IACJA,GAAU,EACVvJ,aAAamnC,GACTjhC,EAAO7R,YAAYd,SAASyU,KAAKxT,YAAY0R,GAAM,EAGzDA,EAAOkhC,cAAcC,aAAeH,EAGpC,IAAIC,EAAgBlnC,WAAWinC,EAAS,KAExChhC,EAAOqG,OAAS,KACdrG,EAAOkhC,cAAch0B,QACrBlN,EAAOkhC,cAAcE,QAErBtnC,aAAamnC,GACbA,EAAgBlnC,WAAWinC,EAAS,IAAI,CAE5C,CAEO,SAASK,GAAa5tC,EAAMoS,EAAW,gBAc5Cu6B,GADa,IAAIO,KAAK,CAAC,SAZN,muBAWXltC,mBACsC,CAAEyG,KAAM,uBAC9B2L,EACxB,CCjFO,SAASy7B,GAAYrf,EAAOsf,GACjC,MAAMlkB,EAAY,IAAItyB,IAAIw2C,EAAcvkC,KAAI8O,GAAKA,EAAEvW,iBACnD,OAAO0sB,EAAMvY,QAAOoC,IAAMuR,EAAUxyB,IAAIihB,EAAEvW,gBAC5C,CAkBO,SAASisC,GAASvf,EAAOwf,EAAY72C,EAAU,CAAA,GACpD,MAAMM,EAAW,IAAIH,IAAIk3B,EAAMjlB,KAAI8O,GAAKA,EAAEvW,iBACpCmsC,EAAWD,EAAW/3B,QAAOoC,IAAM5gB,EAASL,IAAIihB,EAAEvW,iBAExD,MAAyB,UAArB3K,EAAQuoB,SACH,IAAIuuB,KAAazf,GAEnB,IAAIA,KAAUyf,EACvB,CAkCO,SAASC,GAAgBC,EAAch3C,EAAU,IACtD,IAAKg3C,GAAwC,IAAxBA,EAAahwC,OAAc,OAAO,KACvD,GAAwB,oBAAbvE,SAA0B,OAAO,KAG5C,MAOMoT,EAAM,qCAPKmhC,EAAa5kC,KAAI8O,IAE5BA,EAAEpO,SAAS,KAAa,UAAUoO,EAAExI,QAAQ,KAAM,UAKEpG,KAAK,oBAGzDhS,EAAWmC,SAAS2B,cAAc,cAAcyR,OACtD,GAAIvV,EAAU,OAAOA,EAErB,MAAM6J,EAAO1H,SAASC,cAAc,QAgBpC,OAfAyH,EAAKkyB,IAAM,aACXlyB,EAAKc,KAAO4K,EACZ1L,EAAK8sC,QAAQC,WAAa,OAGtBl3C,EAAQm3C,WACVhtC,EAAKgtC,UAAYn3C,EAAQm3C,UACzBhtC,EAAKitC,YAAcp3C,EAAQo3C,aAAe,aAG1CjtC,EAAKitC,YAAcp3C,EAAQo3C,aAAe,YAG5C30C,SAAS40C,KAAKn0C,YAAYiH,GAEnBA,CACT,CCrGA,MAAMmtC,sBAAqBn3C,IAAI,CAC7B,OAAQ,OAAQ,OAChB,MAAO,UAAW,UAAW,QAAS,MAAO,SAAU,SAAU,OACjE,IAAK,KAAM,KAAM,KAAM,KAAM,KAAM,KACnC,KAAM,KAAM,KACZ,QAAS,QAAS,QAAS,QAAS,KAAM,KAAM,KAAM,UAAW,WAAY,MAC7E,aAAc,MAAO,SAAU,aAC/B,OAAQ,WAAY,SACpB,UAAW,UACX,KAAM,KACN,SAAU,QAAS,QAAS,WAIxBo3C,sBAAoBp3C,IAAI,CAC5B,OAAQ,OAAQ,KAAM,MAAO,QAAS,KAAM,MAAO,QACnD,OAAQ,OAAQ,QAAS,SAAU,QAAS,QAIxCq3C,sBAAmBr3C,IAAI,CAAC,MAAO,OAAQ,SAAU,QAAS,aAgBzD,SAASs3C,GAAW5uC,GACzB,IAAKA,IAASA,EAAK1E,OAAQ,MAAO,GAElC,MAOMsW,EAAQ,GAGd,OAFAi9B,IALe,IAAI/gC,WACAK,gBAAgB,SAASnO,WAAe,aAC1CqO,KAGA,EAAGuD,EARL,MAURA,EAAMnI,KAAK,MAAMnO,MAC1B,CAKA,SAASuzC,GAAWj2C,EAAMk5B,EAAOlgB,EAAOwZ,GACtC,MAAMhL,EAAWtc,MAAMC,KAAKnL,EAAK4V,YAEjC,IAAA,MAAWC,KAAS2R,EAAU,CAC5B,GAAI3R,EAAM1V,WAAaC,KAAK0V,aAAc,CACxCkD,EAAMtK,KAAK,GAAG8jB,EAAO0jB,OAAO/zC,KAAKy9B,IAAI,EAAG1G,aAAcrjB,EAAMpT,qBAC5D,QACF,CAEA,GAAIoT,EAAM1V,WAAaC,KAAKC,UAAW,CACrC,MAAMoJ,EAAOoM,EAAMpT,YAEnB,IAAKgH,EAAK/G,OAAQ,SAClB,MAAM0W,EAAU3P,EAAKwN,QAAQ,OAAQ,KAAKvU,OACtC0W,GACFJ,EAAMtK,KAAK,GAAG8jB,EAAO0jB,OAAO/zC,KAAKy9B,IAAI,EAAG1G,MAAU9f,KAEpD,QACF,CAEA,GAAIvD,EAAM1V,WAAaC,KAAK4I,aAAc,SAE1C,MAAMrI,EAAMkV,EAAMtV,QAAQ2I,cAG1B,GAAI6sC,GAAav3C,IAAImC,GAAM,CACzBqY,EAAMtK,KAAK,GAAG8jB,EAAO0jB,OAAO/zC,KAAKy9B,IAAI,EAAG1G,MAAUrjB,EAAM2P,aACxD,QACF,CAEA,MAAM2wB,EAASL,GAAct3C,IAAImC,GAC3By1C,EAAgB5jB,EAAO0jB,OAAO/zC,KAAKy9B,IAAI,EAAG1G,IAG1Cmd,EAAUC,GAAazgC,EAAOlV,GAEpC,GAAIw1C,EAAQ,CAEVn9B,EAAMtK,KAAK,GAAG0nC,IAAgBC,KAC9B,QACF,CAKA,GAF6BE,GAAgB1gC,GAEnB,CAExB,MAAM2gC,EAAeC,GAAuB5gC,GAC5CmD,EAAMtK,KAAK,GAAG0nC,IAAgBC,IAAUG,MAAiB71C,KAC3D,MAEEqY,EAAMtK,KAAK,GAAG0nC,IAAgBC,KAC9BJ,GAAWpgC,EAAOqjB,EAAQ,EAAGlgB,EAAOwZ,GACpCxZ,EAAMtK,KAAK,GAAG0nC,MAAkBz1C,KAEpC,CACF,CAKA,SAAS21C,GAAap2C,EAAIS,GACxB,MAAMI,EAAQmK,MAAMC,KAAKjL,EAAG8V,YAC5B,GAAqB,IAAjBjV,EAAMwE,OAAc,MAAO,IAAI5E,KAOnC,MAAO,IAAIA,KALKI,EAAM4P,KAAI6B,IACxB,MAAgB,KAAZA,EAAEpR,MAAqBoR,EAAEtI,KACtB,GAAGsI,EAAEtI,SAiDIszB,EAjDgBhrB,EAAEpR,MAkD7BkY,GAAekkB,MADxB,IAAoBA,CAjDwB,IACvC3sB,KAAK,OAGV,CAMA,SAAS0lC,GAAgBr2C,GACvB,IAAA,MAAW2V,KAAS3V,EAAG0V,WACrB,GAAIC,EAAM1V,WAAaC,KAAKC,WACxBwV,EAAM1V,WAAaC,KAAK4I,aAAc,CACxC,MAAMrI,EAAMkV,EAAMtV,QAAQ2I,cAC1B,GAAI2sC,GAAer3C,IAAImC,GAAM,OAAO,EAEpC,IAAK41C,GAAgB1gC,GAAQ,OAAO,CACtC,CAEF,OAAO,CACT,CAMA,SAAS4gC,GAAuBv2C,GAC9B,IAAI6K,EAAS,GACb,IAAA,MAAW8K,KAAS3V,EAAG0V,WACrB,GAAIC,EAAM1V,WAAaC,KAAKC,UAC1B0K,GAAU8K,EAAMpT,YAAYwU,QAAQ,OAAQ,UAC9C,GAAWpB,EAAM1V,WAAaC,KAAK4I,aAAc,CAC/C,MAAMrI,EAAMkV,EAAMtV,QAAQ2I,cACpBmtC,EAAUC,GAAazgC,EAAOlV,GAChCm1C,GAAct3C,IAAImC,GACpBoK,GAAUsrC,EAEVtrC,GAAU,GAAGsrC,IAAUI,GAAuB5gC,OAAWlV,IAE7D,CAEF,OAAOoK,CACT,CCtKA,MAAM2rC,GAAY,8DAGZC,GAAY,mHAYX,SAASC,GAAoBntC,GAClC,IAAKA,GAAwB,iBAATA,EAAmB,MAAO,OAE9C,IAAIotC,EAAW,EACXC,EAAW,EAEf,IAAA,IAAStrC,EAAI,EAAGA,EAAI/B,EAAKlE,OAAQiG,IAAK,CACpC,MAAMurC,EAAOttC,EAAK+B,GACdkrC,GAAUvtC,KAAK4tC,GAAOF,IACjBF,GAAUxtC,KAAK4tC,IAAOD,GACjC,CAEA,OAAiB,IAAbD,GAA+B,IAAbC,EAAuB,OACtCD,EAAWC,EAAW,MAAQ,KACvC,CAWO,SAASE,GAAmBp1C,GACjC,IAAKA,IAAYA,EAAQa,YAAa,OACtC,MAAMw0C,EAAML,GAAoBh1C,EAAQa,aAC5B,SAARw0C,EACFr1C,EAAQuU,gBAAgB,OAExBvU,EAAQP,aAAa,MAAO41C,EAEhC,CAOO,SAASC,GAAsBlwC,GACpC,IAAKA,EAAW,OAChB,MAAMogB,EAASpgB,EAAUwQ,iBAAiB,qDAC1C,IAAA,MAAWhV,KAAS4kB,EAClB4vB,GAAmBx0C,EAEvB,CAOY,MAAC20C,GAAqB,CAChCC,YAAa,sBACbC,UAAW,oBACXC,aAAc,uBACdC,WAAY,qBACZC,YAAa,sBACbC,UAAW,oBACXC,WAAY,qBACZC,SAAU,oBC9ECC,GAAK,CAEhB,eAAgB,OAChB,iBAAkB,SAClB,oBAAqB,YACrB,wBAAyB,gBACzB,oBAAqB,YACrB,sBAAuB,cACvB,uBAAwB,oBACxB,kBAAmB,UACnB,oBAAqB,cACrB,sBAAuB,gBACvB,wBAAyB,gBACzB,mBAAoB,YACpB,iBAAkB,kBAClB,kBAAmB,kBACnB,oBAAqB,aACrB,sBAAuB,eACvB,qBAAsB,cACtB,uBAAwB,UACxB,qBAAsB,aACtB,oBAAqB,aACrB,yBAA0B,kBAC1B,eAAgB,cAChB,gBAAiB,eACjB,gBAAiB,eACjB,qBAAsB,cACtB,mBAAoB,YACpB,oBAAqB,aACrB,oBAAqB,mBACrB,eAAgB,OAChB,eAAgB,OAChB,iBAAkB,cAClB,mBAAoB,WACpB,qBAAsB,aACtB,yBAA0B,kBAC1B,gBAAiB,QACjB,sBAAuB,iBACvB,sBAAuB,sBACvB,mBAAoB,YAGpB,YAAa,OACb,YAAa,OACb,YAAa,OACb,cAAe,SACf,cAAe,SAGf,cAAe,MACf,eAAgB,OAChB,gBAAiB,QACjB,oBAAqB,aACrB,0BAA2B,oBAC3B,qBAAsB,cACtB,uBAAwB,gBAGxB,mBAAoB,cACpB,iBAAkB,MAClB,kBAAmB,YACnB,oBAAqB,kBACrB,oBAAqB,SACrB,oBAAqB,SACrB,oBAAqB,eACrB,kBAAmB,YACnB,kBAAmB,WACnB,qBAAsB,SACtB,qBAAsB,SACtB,qBAAsB,cACtB,qBAAsB,QACtB,sBAAuB,SACvB,oBAAqB,eACrB,mBAAoB,OACpB,mBAAoB,UACpB,qBAAsB,SACtB,qBAAsB,SAGtB,mBAAoB,UACpB,eAAgB,aAChB,kBAAmB,cACnB,iBAAkB,aAClB,iBAAkB,aAClB,eAAgB,2BAGhB,sBAAuB,oBACvB,oBAAqB,uBAGrB,iBAAkB,QAClB,kBAAmB,YACnB,mBAAoB,kBACpB,iBAAkB,cAClB,oBAAqB,iCACrB,mBAAoB,UACpB,mBAAoB,UAGpB,oBAAqB,iBACrB,eAAgB,iBAChB,eAAgB,kBAChB,cAAe,mBAGf,aAAc,YACd,aAAc,YACd,aAAc,YACd,aAAc,YACd,aAAc,YACd,aAAc,YACd,oBAAqB,cAGrB,gBAAiB,SACjB,kBAAmB,WACnB,oBAAqB,aACrB,cAAe,OAGf,eAAgB,kBAChB,eAAgB,wBClHlB,IAAIC,GAAiB,IAAKD,IAG1B,MAAME,GAAW,CAAEF,OAGnB,IAAIG,GAAe,KAgBZ,SAAS9sB,GAAEtsB,EAAKq5C,GACrB,IAAI1sC,EAAMusC,GAAel5C,GACzB,QAAY,IAAR2M,EAAmB,OAAO3M,EAE9B,GAAIq5C,EACF,IAAA,MAAYhsB,EAAGC,KAAM/qB,OAAOC,QAAQ62C,GAClC1sC,EAAMA,EAAI2L,QAAQ,IAAIwuB,OAAO,SAASzZ,UAAW,KAAMqR,OAAOpR,IAIlE,OAAO3gB,CACT,CAQO,SAAS2sC,GAAU15B,GACxB,MAAM25B,EAASJ,GAASv5B,GACxB,IAAK25B,EACH,MAAM,IAAIh+B,MAAM,WAAWqE,qDAE7Bw5B,GAAex5B,EAEfs5B,GAAiB,IAAKD,MAAOM,EAC/B,CAOO,SAASC,KACd,OAAOJ,EACT,CAkBO,SAASK,GAAe75B,EAAM85B,GACnC,IAAK95B,GAAwB,iBAATA,EAAmB,MAAM,IAAIrE,MAAM,oCACvD,IAAKm+B,GAA8B,iBAAZA,EAAsB,MAAM,IAAIn+B,MAAM,8CAC7D49B,GAASv5B,GAAQ85B,CACnB,CAOO,SAASC,GAAiB/5B,GAClB,OAATA,WACGu5B,GAASv5B,GACZw5B,KAAiBx5B,GACnB05B,GAAU,MAEd,CAOO,SAASM,KACd,OAAOr3C,OAAOkK,KAAK0sC,GACrB,CC/FO,SAASU,GAAkBC,GAChC,OAAO,IAAIt0C,SAAQC,IACjBs0C,uBAAsB,KACpB,IAAA,MAAW5W,KAAM2W,EACf3W,IAEF19B,GAAO,GACR,GAEL,CAiBO,SAASu0C,GAAiB7W,EAAIvjC,EAAU,IAC7C,MAAMq6C,EAAUr6C,EAAQq6C,SAAW,IACnC,MAAmC,mBAAxBC,oBACFA,oBAAoB/W,EAAI,CAAE8W,YAG5BlrC,WAAWo0B,EAAI,GACxB,CAOO,SAASgX,GAAe9oB,GACK,mBAAvB+oB,mBACTA,mBAAmB/oB,GAEnBviB,aAAauiB,EAEjB,CASO,SAASgpB,GAAYlX,GAC1B,IAAImX,EAAU,KACd,OAAO,YAAsB35C,GACX,OAAZ25C,IACJA,EAAUP,uBAAsB,KAC9B5W,EAAGxU,MAAMvvB,KAAMuB,GACf25C,EAAU,IAAA,IAEd,CACF,CAaO,SAASC,GAAmBzlC,EAAOquB,GACxC,MAAMH,EAAQwX,YAAYhP,MACpBp/B,EAAS+2B,IACEqX,YAAYhP,MAI7B,MAHuB,oBAAZiP,SAA2BA,QAAQC,MAGvCtuC,CACT,CAUO,SAASuuC,GAAU7lC,EAAOquB,EAAIyX,EAAa,KAChD,MAAMC,EAAQ,GACd,IAAA,IAAShuC,EAAI,EAAGA,EAAI+tC,EAAY/tC,IAAK,CACnC,MAAMm2B,EAAQwX,YAAYhP,MAC1BrI,IACA0X,EAAM9qC,KAAKyqC,YAAYhP,MAAQxI,EACjC,CAEA6X,EAAM1oC,MAAK,CAAC0B,EAAGP,IAAMO,EAAIP,IAQzB,MAAO,CAAEwB,QAAOgmC,KAPJD,EAAMja,QAAO,CAAC/sB,EAAGP,IAAMO,EAAIP,GAAG,GACvBunC,EAAMj0C,OAMHm0C,OALPF,EAAMr3C,KAAKw3C,MAAMH,EAAMj0C,OAAS,IAKjBkB,IAJlB+yC,EAAM,GAIiB5Z,IAHvB4Z,EAAMA,EAAMj0C,OAAS,GAGOq0C,IAF5BJ,EAAMr3C,KAAKw3C,MAAqB,IAAfH,EAAMj0C,SAGrC,CAmBO,SAASs0C,GAAmBC,EAASv7C,EAAU,IACpD,MAAMw7C,EAAUx7C,EAAQw7C,SAAW,GACnC,IAAIC,EAAU,GACVC,EAAQ,KAEZ,MAAO,CAKL,KAAAxtB,CAAMytB,GACJF,EAAQtrC,KAAKwrC,GACRD,IACHA,EAA0C,mBAA1BvB,sBACZA,uBAAsB,KACpB,MAAMyB,EAAQH,EACdA,EAAU,GACVC,EAAQ,KACRH,EAAQK,EAAK,IAEfzsC,YAAW,KACT,MAAMysC,EAAQH,EACdA,EAAU,GACVC,EAAQ,KACRH,EAAQK,EAAK,GACZJ,GAEX,EAKA,KAAAK,GASE,GARIH,IACkC,mBAAzBI,qBACTA,qBAAqBJ,GAErBxsC,aAAawsC,GAEfA,EAAQ,MAEND,EAAQz0C,OAAQ,CAClB,MAAM40C,EAAQH,EACdA,EAAU,GACVF,EAAQK,EACV,CACF,EAKA,OAAA5sC,GACM0sC,IACkC,mBAAzBI,qBACTA,qBAAqBJ,GAErBxsC,aAAawsC,IAGjBD,EAAU,GACVC,EAAQ,IACV,EAEJ,CClNY,MAACK,GAAkB,CAE7BpiB,GAAiB,CAAEqiB,IAAK,WAAyBxvB,YAAa,2BAC9DthB,KAAiB,CAAE8wC,IAAK,aAAyBxvB,YAAa,sBAC9DyvB,cAAiB,CAAED,IAAK,uBAAyBxvB,YAAa,8BAC9D0vB,OAAiB,CAAEF,IAAK,eAAyBxvB,YAAa,gBAC9D2vB,aAAiB,CAAEH,IAAK,sBAAyBxvB,YAAa,6BAG9D4vB,UAAqB,CAAEJ,IAAK,mBAA+BxvB,YAAa,4BACxE6vB,cAAqB,CAAEL,IAAK,uBAA+BxvB,YAAa,wBACxE8vB,mBAAqB,CAAEN,IAAK,6BAA+BxvB,YAAa,mCACxE+vB,oBAAqB,CAAEP,IAAK,8BAA+BxvB,YAAa,oCACxEgwB,YAAqB,CAAER,IAAK,qBAA+BxvB,YAAa,sBACxEiwB,kBAAqB,CAAET,IAAK,4BAA+BxvB,YAAa,6BAGxEkwB,QAAc,CAAEV,IAAK,gBAAuBxvB,YAAa,wBACzDmwB,aAAc,CAAEX,IAAK,sBAAuBxvB,YAAa,0BACzDowB,aAAc,CAAEZ,IAAK,sBAAuBxvB,YAAa,0CACzDqwB,UAAc,CAAEb,IAAK,mBAAuBxvB,YAAa,4BACzDtc,UAAc,CAAE8rC,IAAK,kBAAwBxvB,YAAa,kCAG1DswB,OAAa,CAAEd,IAAK,eAAsBxvB,YAAa,sBACvDuwB,YAAa,CAAEf,IAAK,qBAAsBxvB,YAAa,2BAGvDwwB,YAAe,CAAEhB,IAAK,oBAAwBxvB,YAAa,0BAC3DywB,QAAe,CAAEjB,IAAK,iBAAwBxvB,YAAa,0BAC3D0wB,aAAe,CAAElB,IAAK,sBAAwBxvB,YAAa,4BAC3D2wB,YAAe,CAAEnB,IAAK,qBAAwBxvB,YAAa,yBAC3D4wB,cAAe,CAAEpB,IAAK,uBAAwBxvB,YAAa,yBAC3D6wB,QAAe,CAAErB,IAAK,iBAAwBxvB,YAAa,4BAG3D8wB,SAAa,CAAEtB,IAAK,kBAAsBxvB,YAAa,gBACvD+wB,SAAa,CAAEvB,IAAK,kBAAsBxvB,YAAa,iBACvDgxB,SAAa,CAAExB,IAAK,kBAAsBxvB,YAAa,gBACvDixB,YAAa,CAAEzB,IAAK,qBAAsBxvB,YAAa,2BAGvDpiB,WAAmB,CAAE4xC,IAAK,oBAA6BxvB,YAAa,iBACpEniB,SAAmB,CAAE2xC,IAAK,kBAA6BxvB,YAAa,gBACpEkxB,gBAAmB,CAAE1B,IAAK,0BAA6BxvB,YAAa,0BACpEmxB,kBAAmB,CAAE3B,IAAK,4BAA6BxvB,YAAa,4BAGpEoxB,OAAa,CAAE5B,IAAK,eAAqBxvB,YAAa,0BACtDqxB,SAAa,CAAE7B,IAAK,kBAAqBxvB,YAAa,uBACtDsxB,YAAa,CAAE9B,IAAK,qBAAsBxvB,YAAa,+BACvDuxB,UAAa,CAAE/B,IAAK,mBAAqBxvB,YAAa,6BACtDwxB,UAAa,CAAEhC,IAAK,mBAAqBxvB,YAAa,uBACtDyxB,UAAa,CAAEjC,IAAK,mBAAqBxvB,YAAa,yBAGtD0xB,eAAkB,CAAElC,IAAK,wBAA2BxvB,YAAa,4BACjE2xB,iBAAkB,CAAEnC,IAAK,0BAA2BxvB,YAAa,+BAI7D4xB,GAAa,CAAA,EACnB,IAAA,MAAYh+C,GAAK6L,MAAStJ,OAAOC,QAAQm5C,IACvCqC,GAAWh+C,IAAO6L,GAAK+vC,IAuBlB,SAASqC,GAAYC,GAC1B,MAAM9xC,EAAS,CAAA,EACf,IAAA,MAAYpM,EAAKyC,KAAUF,OAAOC,QAAQ07C,GACpCl+C,EAAIuX,WAAW,UAEjBnL,EAAOpM,GAAOyC,EACLu7C,GAAWh+C,GAEpBoM,EAAO4xC,GAAWh+C,IAAQyC,EAG1B2J,EAAO,SAASpM,EAAIsY,QAAQ,WAAY,OAAO/N,iBAAmB9H,EAGtE,OAAO2J,CACT,CCzFY,MAAC+xC,GAAgB,CAE3BC,MAAOH,GAAY,CACjB1kB,GAAI,UACJzuB,KAAM,UACN+wC,cAAe,UACfC,OAAQ,UACRC,aAAc,UACdC,UAAW,UACXC,cAAe,UACfC,mBAAoB,UACpBC,oBAAqB,UACrBC,YAAa,UACbC,kBAAmB,UACnBC,QAAS,UACTC,aAAc,UACdC,aAAc,2BACdC,UAAW,UACX3sC,UAAW,UACX8sC,YAAa,UACbC,QAAS,UACTC,aAAc,qBACdC,YAAa,UACbC,cAAe,YAIjBqB,OAAQJ,GAAY,CAClB1kB,GAAI,UACJzuB,KAAM,UACN+wC,cAAe,UACfC,OAAQ,UACRC,aAAc,UACdC,UAAW,UACXC,cAAe,UACfC,mBAAoB,UACpBC,oBAAqB,UACrBC,YAAa,UACbC,kBAAmB,UACnBC,QAAS,UACTC,aAAc,UACdC,aAAc,0BACdC,UAAW,UACX3sC,UAAW,UACX8sC,YAAa,UACbC,QAAS,UACTC,aAAc,qBACdC,YAAa,UACbC,cAAe,YAIjBsB,OAAQL,GAAY,CAClB1kB,GAAI,UACJzuB,KAAM,UACN+wC,cAAe,UACfC,OAAQ,UACRC,aAAc,UACdC,UAAW,UACXC,cAAe,UACfC,mBAAoB,UACpBC,oBAAqB,UACrBC,YAAa,UACbC,kBAAmB,UACnBC,QAAS,UACTC,aAAc,UACdC,aAAc,2BACdC,UAAW,UACX3sC,UAAW,UACX8sC,YAAa,UACbC,QAAS,UACTC,aAAc,qBACdC,YAAa,UACbC,cAAe,YAIjBuB,KAAMN,GAAY,CAChB1kB,GAAI,UACJzuB,KAAM,UACN+wC,cAAe,UACfC,OAAQ,UACRC,aAAc,UACdC,UAAW,UACXC,cAAe,UACfC,mBAAoB,UACpBC,oBAAqB,UACrBC,YAAa,UACbC,kBAAmB,UACnBC,QAAS,UACTC,aAAc,UACdC,aAAc,0BACdC,UAAW,UACX3sC,UAAW,UACX8sC,YAAa,UACbC,QAAS,UACTC,aAAc,qBACdC,YAAa,UACbC,cAAe,aC1GNwB,GAA0B,CACrCzlB,MAAkB,CAAE6iB,IAAK,iBAAwBxvB,YAAa,qBAC9DqyB,WAAkB,CAAE7C,IAAK,cAAwBxvB,YAAa,sBAC9DsyB,WAAkB,CAAE9C,IAAK,uBAAwBxvB,YAAa,kBAC9DuyB,gBAAkB,CAAE/C,IAAK,oBAAwBxvB,YAAa,uBAC9DwyB,YAAkB,CAAEhD,IAAK,wBAAyBxvB,YAAa,+BAC/DyyB,iBAAkB,CAAEjD,IAAK,qBAAwBxvB,YAAa,0BAC9D0vB,OAAkB,CAAEF,IAAK,kBAAwBxvB,YAAa,oBAC9D0yB,aAAkB,CAAElD,IAAK,kBAAwBxvB,YAAa,iBAC9D5rB,KAAkB,CAAEo7C,IAAK,gBAAyBxvB,YAAa,yBAC/D2yB,SAAkB,CAAEnD,IAAK,qBAAwBxvB,YAAa,2BAC9D4yB,QAAkB,CAAEpD,IAAK,mBAAwBxvB,YAAa,kBAC9D6yB,QAAkB,CAAErD,IAAK,mBAAwBxvB,YAAa,mBAG1D8yB,GAAkB,CAAA,EACxB,IAAA,MAAYl/C,GAAK6L,MAAStJ,OAAOC,QAAQg8C,IACvCU,GAAgBl/C,IAAO6L,GAAK+vC,IAG9B,MAAMuD,GAAuB,CAC3BpmB,MAAQ,qBACR1O,MAAQ,qBACRf,OAAQ,sBACRoT,OAAQ,uBAUH,SAAS0iB,GAAwBlB,GACtC,IAAKA,GAAkC,iBAAdA,EAAwB,OAAO,KACxD,MAAMxzC,EAAQ,CAAA,EACd,IAAI20C,GAAU,EACd,IAAA,MAAYr/C,EAAKyC,KAAUF,OAAOC,QAAQ07C,GACpCl+C,EAAIuX,WAAW,WACjB7M,EAAM1K,GAAOyC,EACb48C,GAAU,GACDH,GAAgBl/C,KACzB0K,EAAMw0C,GAAgBl/C,IAAQyC,EAC9B48C,GAAU,GAGd,OAAOA,EAAU30C,EAAQ,IAC3B,CAQO,SAAS40C,GAAsBpB,GACpC,IAAKA,GAAkC,iBAAdA,EAAwB,OAAO,KACxD,MAAMxzC,EAAQ,CAAA,EACd,IAAI20C,GAAU,EACd,IAAA,MAAYr/C,EAAKyC,KAAUF,OAAOC,QAAQ07C,GACpCl+C,EAAIuX,WAAW,WACjB7M,EAAM1K,GAAOyC,EACb48C,GAAU,GACDF,GAAqBn/C,KAC9B0K,EAAMy0C,GAAqBn/C,IAAQyC,EACnC48C,GAAU,GAGd,OAAOA,EAAU30C,EAAQ,IAC3B,CAqBO,SAAS60C,GAAuBptB,GACrC,IAAKA,GAA4B,iBAAXA,QAA4B,CAAA,EAClD,MAAMqtB,EAAW,CAAA,EACjB,IAAA,MAAYh0C,EAAS0yC,KAAc37C,OAAOC,QAAQ2vB,GAChD,GAAgB,eAAZ3mB,EACFg0C,EAASC,WAAaH,GAAsBpB,OACvC,CACL,MAAMxzC,EAAQ00C,GAAwBlB,GAClCxzC,IAAO80C,EAASh0C,GAAWd,EACjC,CAEF,OAAO80C,CACT,CCtGO,SAASE,GAAavtB,GAC3B,IAAKA,GAA4B,iBAAXA,EACpB,MAAM,IAAI5W,MAAM,+CAGlB,GAAI4W,EAAOwtB,SAAqC,iBAAnBxtB,EAAOwtB,QAClC,MAAM,IAAIpkC,MAAM,oFAGlB,OAAO4W,CACT,CCJOlW,eAAe2jC,GAAWnqC,EAAK7V,EAAU,IAC9C,MAAMigD,IAAEA,EAAAnT,QAAKA,EAAAoT,OAASA,GAAWlgD,EAE3BmgD,QAAiB7S,MAAMz3B,EAAK,CAChCi3B,QAAS,CAAEsT,OAAQ,6CAA8CtT,GACjEoT,WAGF,IAAKC,EAASlS,GACZ,MAAM,IAAItyB,MAAM,gCAAgC9F,YAAcsqC,EAASjS,WAGzE,MAAMhjC,QAAai1C,EAASj1C,OAC5B,IAAIqnB,EAIJ,GADe,mBAAmB3nB,KAAKiL,GAErC0c,EAAS8tB,GAAgBn1C,QAEzB,IACEqnB,EAASoW,KAAKxoB,MAAMjV,EACtB,CAAA,MAEEqnB,EAAS8tB,GAAgBn1C,EAC3B,CAGF,IAAKqnB,GAA4B,iBAAXA,EACpB,MAAM,IAAI5W,MAAM,wDAIlB,GAAIskC,GAAO1tB,EAAO0tB,KAA6B,iBAAf1tB,EAAO0tB,IAAkB,CACvD,MAAMK,EAAe/tB,EAAO0tB,IAAIA,GAChC,GAAIK,GAAwC,iBAAjBA,EAA2B,CACpD,MAAQL,IAAKM,KAAYC,GAAejuB,EACxCA,EAASkuB,GAAUD,EAAYF,EACjC,KAAO,CACL,MAAQL,IAAKM,KAAYC,GAAejuB,EACxCA,EAASiuB,CACX,CACF,MAAA,GAAWjuB,EAAO0tB,IAAK,CAErB,MAAQA,IAAKM,KAAYC,GAAejuB,EACxCA,EAASiuB,CACX,CAEA,OAAOjuB,CACT,CAQA,SAASkuB,GAAUt1C,EAAQmd,GACzB,MAAM9b,EAAS,IAAKrB,GACpB,IAAA,MAAW/K,KAAOuC,OAAOkK,KAAKyb,GAE1BA,EAAOloB,IACgB,iBAAhBkoB,EAAOloB,KACbuM,MAAMmmB,QAAQxK,EAAOloB,KACtB+K,EAAO/K,IACgB,iBAAhB+K,EAAO/K,KACbuM,MAAMmmB,QAAQ3nB,EAAO/K,IAEtBoM,EAAOpM,GAAOqgD,GAAUt1C,EAAO/K,GAAMkoB,EAAOloB,IAE5CoM,EAAOpM,GAAOkoB,EAAOloB,GAGzB,OAAOoM,CACT,CAaA,SAAS6zC,GAAgBn1C,GACvB,MAAMuP,EAAQvP,EAAKiH,MAAM,MACnBjQ,EAAO,CAAA,EACP4N,EAAQ,CAAC,CAAE4wC,IAAKx+C,EAAM+xB,YAE5B,IAAA,IAAShnB,EAAI,EAAGA,EAAIwN,EAAMzT,OAAQiG,IAAK,CACrC,MAAM8P,EAAMtC,EAAMxN,GAElB,GAAI,cAAcrC,KAAKmS,GAAM,SAE7B,MAAM2kB,EAAQ3kB,EAAI2kB,MAAM,eACxB,IAAKA,EAAO,SAEZ,MAAMzN,EAASyN,EAAM,GAAG16B,OAClBmC,EAAUu4B,EAAM,GAGtB,KAAO5xB,EAAM9I,OAAS,GAAK8I,EAAMA,EAAM9I,OAAS,GAAGitB,QAAUA,GAC3DnkB,EAAMgB,MAER,MAAMxN,EAASwM,EAAMA,EAAM9I,OAAS,GAAG05C,IAGjCC,EAAax3C,EAAQu4B,MAAM,cACjC,GAAIif,EAAY,CAEd,MAAM1hB,EAAM2hB,GAAeD,EAAW,IAClCh0C,MAAMmmB,QAAQxvB,IAChBA,EAAO6M,KAAK8uB,GAEd,QACF,CAGA,MAAM4hB,EAAU13C,EAAQu4B,MAAM,sBAC9B,GAAImf,EAAS,CACX,MAAMzgD,EAAMygD,EAAQ,GAAG18C,OACjB+6B,EAAW2hB,EAAQ,GAAG18C,OAE5B,GAAiB,KAAb+6B,GAAgC,MAAbA,GAAiC,MAAbA,EAAkB,CAE3D,MAAM4hB,EAAWrmC,EAAMxN,EAAI,GAC3B,GAAI6zC,GAAY,UAAUl2C,KAAKk2C,GAAW,CAExC,MAAMvW,EAAM,GACZjnC,EAAOlD,GAAOmqC,EACdz6B,EAAMK,KAAK,CAAEuwC,IAAKnW,EAAKtW,UACzB,KAAO,CAEL,MAAM8sB,EAAS,CAAA,EACfz9C,EAAOlD,GAAO2gD,EACdjxC,EAAMK,KAAK,CAAEuwC,IAAKK,EAAQ9sB,UAC5B,CACF,MACE3wB,EAAOlD,GAAOwgD,GAAe1hB,EAEjC,CACF,CAEA,OAAOh9B,CACT,CAKA,SAAS0+C,GAAe7zC,GACtB,GAAY,SAARA,EAAgB,OAAO,EAC3B,GAAY,UAARA,EAAiB,OAAO,EAC5B,GAAY,SAARA,GAA0B,MAARA,EAAa,OAAO,KAG1C,GAAIA,EAAI4K,WAAW,MAAQ5K,EAAIqJ,SAAS,KACtC,OAAOrJ,EAAIW,MAAM,GAAG,GAAIyE,MAAM,KAAKC,KAAIyB,GAAK+sC,GAAe/sC,EAAE1P,UAI/D,GAAK4I,EAAI4K,WAAW,MAAQ5K,EAAIqJ,SAAS,MAAUrJ,EAAI4K,WAAW,MAAQ5K,EAAIqJ,SAAS,KACrF,OAAOrJ,EAAIW,MAAM,GAAG,GAItB,MAAMyxB,EAAMvE,OAAO7tB,GACnB,OAAKqyB,MAAMD,IAAgB,KAARpyB,EAEZA,EAF+BoyB,CAGxC,CC3KA,MAAM6hB,sBAAsBthD,IAAI,CAC9B,CAAC,kBAAmB2c,UAClB,MAAQ40B,gBAAAA,SAA0BrrC,QAAAC,UAAAC,MAAA,IAAAm7C,KAClC,OAAOhQ,CAAAA,GAET,CAAC,iBAAkB50B,UACjB,MAAQu1B,eAAAA,SAAyBhsC,QAAAC,UAAAC,MAAA,IAAAo7C,KACjC,OAAOtP,CAAAA,GAET,CAAC,oBAAqBv1B,MAAOyxB,IAC3B,MAAQ6E,kBAAAA,SAA4B/sC,QAAAC,UAAAC,MAAA,IAAAq7C,KACpC,OAAOxO,EAAkB7E,GAAMkP,YAAW,KAQxCoE,sBAA6B1hD,IAmB5B,SAAS2hD,GAAsB11C,EAAM21C,GAC1C,GAAoB,iBAAT31C,IAAsBA,EAC/B,MAAM,IAAIgQ,MAAM,0DAElB,GAAuB,mBAAZ2lC,EACT,MAAM,IAAI3lC,MAAM,qDAElBylC,GAAuBlhD,IAAIyL,EAAM21C,EACnC,CAMO,SAASC,GAAwB51C,GACtCy1C,GAAuBzgD,OAAOgL,EAChC,CAcO,SAAS61C,GAAeC,GAC7B,GAAKA,GAAkB90C,MAAMmmB,QAAQ2uB,GAIrC,OAAO77C,QAAQ87C,IAAID,EAAcrvC,KAAI,CAACpC,EAAOD,KAE3C,GAAqB,mBAAVC,GAA0C,iBAAVA,GAAgC,OAAVA,GAAwC,mBAAfA,EAAMxB,KAC9F,OAAOwB,EAIT,GAAqB,iBAAVA,EACT,OAAO2xC,GAAc3xC,GAIvB,GAAqB,iBAAVA,GAAgC,OAAVA,GAAwC,iBAAfA,EAAMrE,KAC9D,OAAOg2C,GAAc3xC,EAAMrE,KAAMqE,EAAMhQ,SAGzC,MAAM,IAAI2b,MAAM,iDAAiD5L,8DAAiE,IAEtI,CAQAsM,eAAeslC,GAAch2C,EAAM3L,GAEjC,MAAM4hD,EAAgBR,GAAuB7gD,IAAIoL,GACjD,GAAIi2C,EACF,OAAOA,EAAc5hD,GAGvB,MAAM6hD,EAAiBb,GAAgBzgD,IAAIoL,GAC3C,GAAIk2C,EACF,OAAOA,EAAe7hD,GAGxB,MAAM,IAAI2b,MAAM,mCAAmChQ,sEACrD"}