helldots 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/helldots.esm.js +4 -4
- package/dist/helldots.esm.js.map +2 -2
- package/dist/helldots.umd.js +6 -6
- package/package.json +1 -1
package/dist/helldots.esm.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/root-element.js", "../src/yield-to-paint.js", "../src/capture-style-props.js", "../src/canvas-limits.js", "../src/capture.js", "../src/constants.js", "../src/capture-flow.js", "../src/metadata.js", "../src/styles.js", "../src/style-mount.js", "../src/locales/en.js", "../src/locales/es.js", "../src/i18n.js", "../src/anchor.js", "../src/storage.js", "../node_modules/nanoid/url-alphabet/index.js", "../node_modules/nanoid/index.browser.js", "../src/id.js", "../src/menus.js", "../src/reactions.js", "../src/permissions.js", "../src/link.js", "../src/audit.js", "../src/confirm-dialog.js", "../src/comment-actions.js", "../src/inline-editor.js", "../src/components.js", "../src/context-block.js", "../src/agent-context.js", "../src/popover-controller.js", "../src/marker-engine.js", "../src/audit-timeline.js", "../src/metrics.js", "../src/metrics-view.js", "../src/inbox.js", "../src/csv.js", "../src/metrics-report.js", "../src/overlay.js", "../src/index.js"],
|
|
4
|
-
"sourcesContent": ["const TAG_NAME = \"helldots-root\";\n\n// Defined lazily rather than at module scope: `extends HTMLElement` is\n// evaluated when the class expression runs, so a top-level declaration makes\n// a bare `import \"helldots\"` throw on any server renderer (Next.js, Remix,\n// Astro) long before the app calls anything. Deferring it keeps the module\n// import-safe everywhere and only touches the DOM when we actually mount.\nconst ensureDefined = () => {\n if (customElements.get(TAG_NAME)) return;\n\n customElements.define(\n TAG_NAME,\n class HelldotsRoot extends HTMLElement {\n constructor() {\n super();\n this.attachShadow({ mode: \"open\" });\n }\n }\n );\n};\n\n/**\n * Returns the shared shadow root used to render all HellDots UI, creating\n * the host element and mounting it on document.body on first call.\n * @returns {ShadowRoot}\n */\nexport function getShadowRoot() {\n ensureDefined();\n\n let host = document.querySelector(TAG_NAME);\n if (!host) {\n host = document.createElement(TAG_NAME);\n document.body.appendChild(host);\n }\n\n return host.shadowRoot;\n}\n\nexport { TAG_NAME };\n", "// Handing the main thread back to the browser in the middle of a render.\n//\n// `modern-screenshot`'s API is asynchronous, but its clone traversal awaits\n// promises that are already resolved. Those settle as MICROtasks, and the\n// microtask queue drains completely before the browser gets to paint or to\n// deliver a keystroke \u2014 so a 1.5 s render is 1.5 s of frozen page even\n// though not one call inside it is synchronous. Only a MACROtask breaks\n// that up. This module is that macrotask, on a time budget.\n\n/** Half a 60 Hz frame: enough headroom left for the browser to paint. */\nconst YIELD_BUDGET_MS = 8;\n\nconst now = () =>\n typeof performance?.now === \"function\" ? performance.now() : Date.now();\n\n/**\n * One macrotask turn.\n *\n * `setTimeout` would also be a task, but every browser clamps a nested\n * timeout to 4 ms, and on a heavy page this runs a couple of hundred times\n * \u2014 the clamp alone would add most of a second to the render it is meant\n * to make bearable. A `MessageChannel` message is a task with no clamp.\n * @returns {Promise<void>}\n */\nconst nextTask = () =>\n new Promise((resolve) => {\n if (typeof MessageChannel !== \"function\") {\n setTimeout(resolve);\n return;\n }\n const channel = new MessageChannel();\n channel.port1.onmessage = () => {\n channel.port1.close();\n resolve();\n };\n channel.port2.postMessage(null);\n });\n\n/**\n * Yields to the browser, preferring the API built for exactly this.\n *\n * `scheduler.yield()` resumes at continuation priority, so the render keeps\n * its place ahead of unrelated work the page may have queued; the\n * `MessageChannel` fallback goes to the back of the task queue instead.\n * @returns {Promise<void>}\n */\nconst yieldToBrowser = () => {\n const scheduler = /** @type {any} */ (globalThis).scheduler;\n if (typeof scheduler?.yield === \"function\") return scheduler.yield();\n return nextTask();\n};\n\n/**\n * Builds a per-render callback that yields once the time budget is spent.\n *\n * Time, not a node count: the cost of a node is not a constant. A synthetic\n * `<div>` clones in ~0.14 ms and a styled application node in ~0.44 ms, so\n * any fixed \"every N nodes\" is either a stutter on one page or pointless\n * overhead on another. A budget adapts to whatever it is actually walking.\n *\n * The returned function is the hot path \u2014 it runs once per cloned node \u2014 so\n * the common case returns `undefined` synchronously rather than allocating\n * a promise the caller would await for nothing.\n *\n * A rejection is treated as a completed yield, not propagated: this hook is\n * awaited inside the clone traversal, so throwing here would take the whole\n * capture down. Failing to pause is worth strictly less than failing to\n * produce the screenshot the widget exists to collect.\n * @param {{ budgetMs?: number }} [options]\n * @returns {() => Promise<void> | undefined}\n */\nexport function createPaintYielder({ budgetMs = YIELD_BUDGET_MS } = {}) {\n let last = now();\n const resume = () => {\n // Stamped after the yield resolves, not before: the time spent parked\n // in the task queue is the browser's, and charging it to the next\n // budget would make every following slice shorter than asked for.\n last = now();\n };\n return () => {\n if (now() - last < budgetMs) return undefined;\n return yieldToBrowser().then(resume, resume);\n };\n}\n", "// The style properties a capture actually needs.\n//\n// `modern-screenshot` reproduces an element by reading its computed style\n// and inlining it on the clone. Left alone it enumerates everything the\n// browser exposes \u2014 ~527 properties per element on a modern engine \u2014 and\n// that enumeration is the render: profiling a host's page put 116 467\n// property reads at 97 ms against 1 ms of cloning and 7 ms of rasterising.\n//\n// The clone is re-parented into a fresh document inside a `<foreignObject>`\n// with no cascade of its own, so anything omitted here is simply not there.\n// That makes this list a fidelity contract, not a preference: it has to\n// carry every property that changes a pixel, and it is opt-in precisely\n// because \"every property that changes a pixel\" is not decidable for a page\n// this library has never seen.\n//\n// Verified by rendering the same page with and without the list and\n// comparing the two canvases pixel for pixel \u2014 see DECISIONS.md.\n\n/**\n * Properties `modern-screenshot` reads back out of the map it just built,\n * to drive behaviour rather than appearance: scrollbar cloning, its Chrome\n * ellipsis workaround, the `background-clip: text` class hack, and the font\n * subsetting that decides which web fonts get embedded at all.\n *\n * Dropping one of these does not degrade an image, it changes what the\n * renderer does \u2014 which is why they are called out instead of being left to\n * blend into the list below.\n */\nexport const RENDERER_READS_BACK = [\n \"background-clip\",\n \"font-family\",\n \"font-kerning\",\n \"overflow-x\",\n \"overflow-y\",\n \"text-overflow\",\n \"text-transform\",\n];\n\n/**\n * The curated allow-list handed to `includeStyleProperties`.\n *\n * Longhands only. The renderer sets each name it is given straight onto the\n * clone's inline style, so a shorthand would work \u2014 but the browser\n * enumerates computed styles as longhands, and asking for `margin` when the\n * engine only answers to `margin-top` costs a lookup that returns nothing.\n * @type {string[]}\n */\nexport const CAPTURE_STYLE_PROPERTIES = [\n ...RENDERER_READS_BACK,\n\n // Box and flow.\n \"aspect-ratio\",\n \"border-collapse\",\n \"border-spacing\",\n \"bottom\",\n \"box-sizing\",\n \"caption-side\",\n \"clear\",\n \"display\",\n \"empty-cells\",\n \"float\",\n \"height\",\n \"isolation\",\n \"left\",\n \"margin-bottom\",\n \"margin-left\",\n \"margin-right\",\n \"margin-top\",\n \"max-height\",\n \"max-width\",\n \"min-height\",\n \"min-width\",\n \"padding-bottom\",\n \"padding-left\",\n \"padding-right\",\n \"padding-top\",\n \"position\",\n \"right\",\n \"table-layout\",\n \"top\",\n \"vertical-align\",\n \"visibility\",\n \"width\",\n \"z-index\",\n\n // Borders and outlines.\n \"border-bottom-color\",\n \"border-bottom-left-radius\",\n \"border-bottom-right-radius\",\n \"border-bottom-style\",\n \"border-bottom-width\",\n \"border-image-outset\",\n \"border-image-repeat\",\n \"border-image-slice\",\n \"border-image-source\",\n \"border-image-width\",\n \"border-left-color\",\n \"border-left-style\",\n \"border-left-width\",\n \"border-right-color\",\n \"border-right-style\",\n \"border-right-width\",\n \"border-top-color\",\n \"border-top-left-radius\",\n \"border-top-right-radius\",\n \"border-top-style\",\n \"border-top-width\",\n \"outline-color\",\n \"outline-offset\",\n \"outline-style\",\n \"outline-width\",\n\n // Flexbox, grid and multi-column.\n \"align-content\",\n \"align-items\",\n \"align-self\",\n \"column-count\",\n \"column-fill\",\n \"column-gap\",\n \"column-rule-color\",\n \"column-rule-style\",\n \"column-rule-width\",\n \"column-span\",\n \"column-width\",\n \"flex-basis\",\n \"flex-direction\",\n \"flex-grow\",\n \"flex-shrink\",\n \"flex-wrap\",\n \"grid-auto-columns\",\n \"grid-auto-flow\",\n \"grid-auto-rows\",\n \"grid-column-end\",\n \"grid-column-start\",\n \"grid-row-end\",\n \"grid-row-start\",\n \"grid-template-areas\",\n \"grid-template-columns\",\n \"grid-template-rows\",\n \"justify-content\",\n \"justify-items\",\n \"justify-self\",\n \"order\",\n \"row-gap\",\n\n // Typography.\n \"color\",\n \"direction\",\n \"font-feature-settings\",\n \"font-size\",\n \"font-stretch\",\n \"font-style\",\n \"font-variant\",\n \"font-variation-settings\",\n \"font-weight\",\n \"hyphens\",\n \"letter-spacing\",\n \"line-height\",\n \"list-style-image\",\n \"list-style-position\",\n \"list-style-type\",\n \"overflow-wrap\",\n \"tab-size\",\n \"text-align\",\n \"text-align-last\",\n \"text-decoration-color\",\n \"text-decoration-line\",\n \"text-decoration-style\",\n \"text-decoration-thickness\",\n \"text-indent\",\n \"text-orientation\",\n \"text-shadow\",\n \"text-underline-offset\",\n \"text-underline-position\",\n \"unicode-bidi\",\n \"white-space\",\n \"word-break\",\n \"word-spacing\",\n \"writing-mode\",\n \"-webkit-box-orient\",\n \"-webkit-line-clamp\",\n \"-webkit-text-fill-color\",\n \"-webkit-text-stroke-color\",\n \"-webkit-text-stroke-width\",\n\n // Paint.\n \"backdrop-filter\",\n \"backface-visibility\",\n \"background-attachment\",\n \"background-blend-mode\",\n \"background-color\",\n \"background-image\",\n \"background-origin\",\n \"background-position-x\",\n \"background-position-y\",\n \"background-repeat\",\n \"background-size\",\n \"box-shadow\",\n \"clip-path\",\n \"filter\",\n \"mask-image\",\n \"mask-mode\",\n \"mask-position\",\n \"mask-repeat\",\n \"mask-size\",\n \"mix-blend-mode\",\n \"object-fit\",\n \"object-position\",\n \"opacity\",\n \"perspective\",\n \"perspective-origin\",\n \"rotate\",\n \"scale\",\n \"transform\",\n \"transform-origin\",\n \"transform-style\",\n \"translate\",\n\n // Form controls, which the UA paints from these rather than from a\n // background: an unstyled checkbox with no `accent-color` comes out as an\n // empty box.\n \"accent-color\",\n \"appearance\",\n\n // SVG. Presentation attributes resolve into computed style, so a chart or\n // an icon set is invisible without them.\n \"dominant-baseline\",\n \"fill\",\n \"fill-opacity\",\n \"fill-rule\",\n \"paint-order\",\n \"shape-rendering\",\n \"stop-color\",\n \"stop-opacity\",\n \"stroke\",\n \"stroke-dasharray\",\n \"stroke-dashoffset\",\n \"stroke-linecap\",\n \"stroke-linejoin\",\n \"stroke-opacity\",\n \"stroke-width\",\n \"text-anchor\",\n];\n", "// How large a canvas this browser will actually paint.\n//\n// Every engine caps both a canvas's single dimension and its total area, and\n// neither cap is reported anywhere. Worse, going past one is not an error: the\n// assignment is accepted, `canvas.width`/`.height` read back exactly what was\n// set, `getContext(\"2d\")` hands out a context, every draw call succeeds \u2014 and\n// the canvas holds no pixels. A render of a long page comes back completely\n// blank with nothing on the console to say why.\n//\n// Measured in Chromium 1265px wide: 65 535 tall holds paint, 65 536 does not;\n// 16384x16384 (268 Mpx) holds, 20000x20000 (400 Mpx) does not. Firefox caps\n// the dimension at 32 767 and mobile Safari caps the area far lower, so the\n// numbers here are a starting point and `paintsPixels` is the thing that\n// actually decides.\n\n/**\n * Chromium's measured area cap, used as the opening guess.\n *\n * Only a guess: engines differ by more than an order of magnitude, so a\n * render is verified afterwards rather than trusted to this.\n */\nconst AREA_LIMIT = 16384 * 16384;\n\n/** Fallback when even the smallest probe fails \u2014 pathological, but finite. */\nconst MIN_DIMENSION = 4096;\n\n/**\n * Whether a canvas of this size holds what is painted into it.\n * @param {number} width\n * @param {number} height\n * @returns {boolean}\n */\nconst holdsPaint = (width, height) => {\n try {\n const canvas = document.createElement(\"canvas\");\n canvas.width = width;\n canvas.height = height;\n if (canvas.width !== width || canvas.height !== height) return false;\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return false;\n ctx.fillStyle = \"#ffffff\";\n ctx.fillRect(0, 0, 1, 1);\n return ctx.getImageData(0, 0, 1, 1).data[3] !== 0;\n } catch {\n return false;\n }\n};\n\n/** @type {number | null} */\nlet measured = null;\n\n/**\n * The largest single dimension this browser paints.\n *\n * Probed one pixel wide, so it measures the dimension cap on its own: a\n * 1x65535 canvas is 256 KB and tells us nothing about the area cap, which is\n * exactly what is wanted here. Measured once and remembered \u2014 the answer is a\n * property of the engine, not of the page.\n * @returns {number}\n */\nexport const maxCanvasDimension = () => {\n if (measured !== null) return measured;\n measured =\n [65535, 32767, 16384, 8192, MIN_DIMENSION].find((d) => holdsPaint(1, d)) ??\n MIN_DIMENSION;\n return measured;\n};\n\n/**\n * The largest scale at or below `wanted` that should produce a painted canvas.\n *\n * Area is bounded by a square root because scaling touches both axes: halving\n * the scale quarters the pixel count.\n *\n * The limits are parameters so this stays a pure function of four numbers.\n * Callers pass none \u2014 the defaults are the measured dimension and the area\n * guess \u2014 but a test can pin every branch without a browser, and the area\n * branch is otherwise unreachable wherever the dimension probe floors out.\n * @param {number} width CSS pixels of the node being rendered\n * @param {number} height\n * @param {number} wanted the scale the caller asked for\n * @param {{ maxDimension?: number, maxArea?: number }} [limits]\n * @returns {number}\n */\nexport const fittingScale = (width, height, wanted, limits = {}) => {\n if (!(width > 0) || !(height > 0)) return wanted;\n const { maxDimension = maxCanvasDimension(), maxArea = AREA_LIMIT } = limits;\n return Math.min(\n wanted,\n maxDimension / width,\n maxDimension / height,\n Math.sqrt(maxArea / (width * height))\n );\n};\n\n/**\n * Whether a finished render actually holds pixels.\n *\n * Reads one pixel's alpha, which works only because every render is given an\n * opaque `backgroundColor` and the renderer fills the whole canvas with it\n * before drawing. A render allowed to stay transparent would read as failed\n * here \u2014 that coupling is deliberate and is why `effectiveBackgroundColor`\n * falls back to white rather than returning null.\n * @param {any} canvas\n * @returns {boolean}\n */\nexport const paintsPixels = (canvas) => {\n try {\n const ctx = canvas?.getContext?.(\"2d\");\n return Boolean(ctx) && ctx.getImageData(0, 0, 1, 1).data[3] !== 0;\n } catch {\n return false;\n }\n};\n", "// Screenshot primitives. The page render is the expensive part, so it's\n// split out from the cropping: a drag capture and the automatic context\n// capture of the same comment share ONE render instead of paying for two.\n//\n// WE own the crop in page coordinates \u2014 html2canvas used to own it and\n// silently shifted it by the window scroll (double-counting: the hero\n// showed up in captures taken further down the page). Owning the crop\n// makes that whole bug class impossible.\n\nimport { TAG_NAME } from \"./root-element.js\";\nimport { createPaintYielder } from \"./yield-to-paint.js\";\nimport { CAPTURE_STYLE_PROPERTIES } from \"./capture-style-props.js\";\nimport { fittingScale, paintsPixels } from \"./canvas-limits.js\";\n\n/** Automatic captures render and encode small \u2014 they live in localStorage. */\nexport const AUTO_SCALE = 0.5;\nconst AUTO_QUALITY = 0.7;\n\n// The renderer is the single heaviest thing this package pulls in (~10 KB\n// gzip), and most page views never take a capture \u2014 so it loads on the first\n// render, not with the host's initial bundle. A failed load is forgotten\n// rather than cached: a transient network error at capture time must not\n// poison every capture after it.\n/** @type {Promise<typeof import(\"modern-screenshot\")> | undefined} */\nlet rendererPromise;\nconst loadRenderer = () => {\n rendererPromise ??= import(\"modern-screenshot\").catch((error) => {\n rendererPromise = undefined;\n throw error;\n });\n return rendererPromise;\n};\n\nconst isUnpainted = (color) =>\n !color || color === \"transparent\" || color === \"rgba(0, 0, 0, 0)\";\n\n// What the user visually perceives as the page background: the html/body\n// CSS color when one is painted, else white \u2014 browsers paint their own\n// white canvas under a transparent document, but that canvas is not part\n// of the DOM, so a DOM-based render would come out as a transparent PNG\n// (invisible against the dark inbox UI).\nconst effectiveBackgroundColor = () => {\n const htmlBg = getComputedStyle(document.documentElement).backgroundColor;\n if (!isUnpainted(htmlBg)) return htmlBg;\n const bodyBg = getComputedStyle(document.body).backgroundColor;\n if (!isUnpainted(bodyBg)) return bodyBg;\n return \"#ffffff\";\n};\n\n/**\n * Whether `modern-screenshot` can embed the page's web fonts.\n *\n * It reads `@font-face` rules by parking a `<style>` in a detached document\n * and reading back `.sheet`. That element inherits the host page's CSP, so a\n * policy with a strict `style-src` refuses to parse it, `.sheet` comes back\n * null, and the render dies on `null.cssRules` \u2014 taking the entire capture\n * with it, not just the fonts. Probing costs one detached document; the\n * render it guards costs orders of magnitude more.\n * @returns {boolean}\n */\nexport const canEmbedWebFonts = () => {\n try {\n const probe = document.implementation.createHTMLDocument(\"\");\n const style = probe.createElement(\"style\");\n probe.head.appendChild(style);\n return style.sheet !== null;\n } catch {\n return false;\n }\n};\n\n/**\n * Pulls the `@font-face` blocks out of a stylesheet's source text.\n *\n * Only those: the sheet is a third party's, and appending the whole thing to\n * the host's `<head>` would put its layout rules last in the cascade and\n * restyle the page for the duration of the capture.\n * @param {string} css\n * @returns {string}\n */\nexport const extractFontFaceRules = (css) => {\n const blocks = [];\n let at = css.indexOf(\"@font-face\");\n while (at !== -1) {\n const open = css.indexOf(\"{\", at);\n const close = open === -1 ? -1 : css.indexOf(\"}\", open);\n if (close === -1) break; // truncated sheet \u2014 keep what parsed cleanly\n blocks.push(css.slice(at, close + 1));\n at = css.indexOf(\"@font-face\", close);\n }\n return blocks.join(\"\\n\");\n};\n\n/** Same sheet, same session, one request. @type {Map<string, Promise<string>>} */\nconst fontRuleCache = new Map();\n\nconst fetchFontRules = (href) => {\n if (!fontRuleCache.has(href)) {\n fontRuleCache.set(\n href,\n fetch(href, { mode: \"cors\", credentials: \"omit\" })\n .then((res) => (res.ok ? res.text() : \"\"))\n .then(extractFontFaceRules)\n .catch(() => \"\")\n );\n }\n return fontRuleCache.get(href);\n};\n\nconst isReadable = (sheet) => {\n try {\n return Boolean(sheet.cssRules);\n } catch {\n return false;\n }\n};\n\n/**\n * Makes a cross-origin stylesheet's web fonts reachable by the renderer, and\n * returns the undo.\n *\n * `cssRules` throws `SecurityError` on a cross-origin sheet, so the renderer\n * never finds its `@font-face` rules and never inlines the font files. What\n * it produces is an SVG rendered as an image \u2014 an isolated document with no\n * network of its own \u2014 so a font that was not inlined is simply absent and\n * the text reflows into a fallback. Fallback metrics differ, which moves\n * every glyph sideways: the page still *looks* about right, but a drag crop\n * taken at live coordinates comes back holding the wrong glyphs.\n *\n * `fetch` succeeds where `cssRules` does not \u2014 font CDNs serve\n * `Access-Control-Allow-Origin: *` \u2014 and a same-origin `<style>` carrying\n * those rules is readable, so the renderer inlines the binaries itself\n * rather than us reimplementing that. A host that refuses the fetch (no\n * CORS, a `connect-src` policy) lands exactly where it is today.\n *\n * Off unless asked for: requesting a third party's stylesheet is network a\n * host did not sign up for by mounting a comment widget, so that call is\n * theirs to make.\n * @param {boolean} enabled\n * @returns {Promise<() => void>}\n */\nconst shimUnreadableFontRules = async (enabled) => {\n const noop = () => {};\n if (!enabled || !canEmbedWebFonts()) return noop;\n\n const hrefs = Array.from(document.styleSheets)\n .filter((sheet) => sheet.href && !isReadable(sheet))\n .map((sheet) => sheet.href);\n if (!hrefs.length) return noop;\n\n const css = (await Promise.all(hrefs.map(fetchFontRules)))\n .filter(Boolean)\n .join(\"\\n\");\n if (!css) return noop;\n\n const style = document.createElement(\"style\");\n style.textContent = css;\n document.head.appendChild(style);\n return () => style.remove();\n};\n\n/**\n * Builds the clone filter.\n *\n * `skipIframeContent` drops what lives inside an iframe while keeping the\n * `<iframe>` element itself, and that distinction is the whole point.\n * Filtering the iframe out by tag name \u2014 the obvious reading \u2014 removes its\n * BOX, so everything below it slides up by the frame's height: measured at\n * a 260px shift on a 260px frame, with the crop still taken at live page\n * coordinates. That is the misalignment class `capture.js` exists to make\n * impossible. Matching on `ownerDocument` leaves the box, its border and\n * its space exactly where the page put them, and blanks only the interior.\n *\n * Nodes in a shadow root keep the host's `ownerDocument`, so the widget's\n * own shadow content is unaffected by this test.\n * @param {boolean} skipIframeContent\n * @returns {(node: Node) => boolean}\n */\nconst captureFilter = (skipIframeContent) => (node) => {\n // nodeName, not tagName: the filter also receives text nodes, which must\n // be kept (and have no tagName).\n if (node.nodeName?.toLowerCase() === TAG_NAME) return false;\n if (skipIframeContent && node.ownerDocument !== document) return false;\n return true;\n};\n\n/**\n * Renders the whole page to a canvas. This is the expensive call \u2014 callers\n * that need more than one image should render once and crop repeatedly.\n *\n * The widget must never render into its own screenshot: the host node is\n * filtered out of the clone. Filtering replaced the old hide-during-render\n * approach (withHiddenOverlay), which took the whole UI off screen for the\n * duration of the render and therefore forced callers to await the capture\n * before showing anything \u2014 with the filter, a capture can run in the\n * background while the comment box is already on screen.\n * @param {{ scale?: number, embedCrossOriginFonts?: boolean,\n * fastCapture?: boolean, skipIframeContent?: boolean,\n * captureTimeout?: number }} [options]\n * scale 1 keeps the canvas in CSS pixels so crop rects map 1:1 to page\n * coordinates. `embedCrossOriginFonts` opts into fetching stylesheets the\n * renderer cannot read, so their web fonts survive into the capture.\n * `fastCapture` narrows the computed-style enumeration to a curated list\n * (see capture-style-props.js) \u2014 roughly 2.7x off the dominant phase, at\n * the cost of any property that list does not name. `skipIframeContent`\n * blanks embedded documents instead of cloning them. `captureTimeout`\n * bounds how long a single remote asset may hold the render up.\n * @returns {Promise<{ canvas: any, scale: number }>} the render and the scale\n * it was ACTUALLY produced at, which is not always the one asked for \u2014 see\n * the canvas ceiling below. Every crop has to map through this rather than\n * assume the requested scale.\n */\nexport async function renderPage({\n scale = 1,\n embedCrossOriginFonts = false,\n fastCapture = false,\n skipIframeContent = false,\n captureTimeout,\n} = {}) {\n const { domToCanvas } = await loadRenderer();\n const unshim = await shimUnreadableFontRules(embedCrossOriginFonts);\n const { width, height } = document.documentElement.getBoundingClientRect();\n // A page taller than the browser's canvas ceiling used to render to a\n // canvas that reported the right size and held nothing, so every crop off\n // it was blank and nothing said so. Fitting the scale to the ceiling turns\n // that into a capture that is correct and progressively softer.\n let attempt = fittingScale(width, height, scale);\n try {\n // The ceiling differs by more than an order of magnitude between\n // engines, so the fitted scale is a guess and the render is checked\n // rather than trusted. Halving quarters the pixel count, so three\n // attempts cover a 64x overshoot; past that, throwing is the honest\n // outcome \u2014 it reaches the host through onError, where a blank image\n // never would.\n for (let left = 3; ; left--) {\n // documentElement, not body. The clone is re-parented into a document\n // where the UA's `body { margin: 8px }` applies again, even on a page\n // that zeroed it \u2014 so rendering <body> pushed every flow element 8px\n // right and down inside a canvas that did not grow, losing 8px off the\n // right edge and putting every crop 8px out. <html> carries no such\n // margin, so page coordinates and canvas pixels line up 1:1, which is\n // exactly what the crops below assume.\n const canvas = await domToCanvas(document.documentElement, {\n scale: attempt,\n backgroundColor: effectiveBackgroundColor(),\n // Dropping web fonts costs one font substitution inside the image;\n // keeping them where they cannot be read costs the image entirely.\n ...(canEmbedWebFonts() ? {} : { font: false }),\n filter: captureFilter(skipIframeContent),\n // The clone traversal awaits this hook once per node, which makes it\n // the one place a caller can get the main thread back mid-render \u2014\n // see yield-to-paint.js for why awaiting anything else does not.\n onCloneEachNode: createPaintYielder(),\n // Spread rather than a null: passing `includeStyleProperties: null`\n // is the renderer's own \"enumerate everything\" default, so the two\n // branches would be indistinguishable to a test reading the options.\n ...(fastCapture\n ? { includeStyleProperties: CAPTURE_STYLE_PROPERTIES }\n : {}),\n // Omitted rather than defaulted: the renderer has its own 30 000 ms,\n // and repeating that number here would pin us to one that is theirs\n // to change.\n //\n // Finite AND positive, both load-bearing, because the two values a\n // host would reach for to mean \"no deadline\" each do the opposite.\n // The renderer reads 0 as \"never give up\" and hangs; `Infinity`\n // reaches `setTimeout`, which coerces it to 0 and aborts on the\n // spot. Neither is a deadline, so neither is honoured \u2014 and a\n // string that merely compares as a number is not one either.\n ...(Number.isFinite(captureTimeout) && captureTimeout > 0\n ? { timeout: captureTimeout }\n : {}),\n });\n\n if (paintsPixels(canvas)) return { canvas, scale: attempt };\n if (left <= 0) {\n throw new Error(\n `HellDots: the page render came back holding no pixels. ` +\n `${Math.round(width)}x${Math.round(height)} CSS pixels is most ` +\n `likely past this browser's canvas limit.`\n );\n }\n attempt /= 2;\n }\n } finally {\n unshim();\n }\n}\n\n/**\n * Lays the page's background down across the whole output before the render\n * goes on top.\n *\n * The render covers the BODY's box, which on a page shorter than the\n * viewport is shorter than the crop. Whatever the render does not reach\n * keeps the canvas's initial transparent black \u2014 invisible in a PNG, and a\n * solid black band once JPEG flattens it. The browser paints html/body\n * across the entire viewport, so the background is what is really there.\n * @param {CanvasRenderingContext2D} ctx\n * @param {number} width\n * @param {number} height\n */\nconst paintBackdrop = (ctx, width, height) => {\n ctx.fillStyle = effectiveBackgroundColor();\n ctx.fillRect(0, 0, width, height);\n};\n\n/**\n * Crops a viewport-relative region out of a scale-1 page render.\n * @param {any} canvas full-page render from `renderPage`\n * @param {{ left: number, top: number, width: number, height: number }} region\n * Viewport (client) coordinates of the drag selection.\n * @param {{ sourceScale?: number }} [options] the scale `canvas` was actually\n * produced at \u2014 `renderPage` reports it, and it is not always the one asked\n * for. The output stays sized in CSS pixels either way, so a render the\n * canvas ceiling forced down comes back soft rather than the wrong size.\n * @returns {string | null} PNG data-URL, or null with no 2d context.\n */\nexport function cropRegion(\n canvas,\n { left, top, width, height },\n { sourceScale = 1 } = {}\n) {\n const out = document.createElement(\"canvas\");\n out.width = width;\n out.height = height;\n const ctx = out.getContext(\"2d\");\n if (!ctx) return null;\n\n paintBackdrop(ctx, width, height);\n ctx.drawImage(\n canvas,\n (left + window.scrollX) * sourceScale,\n (top + window.scrollY) * sourceScale,\n width * sourceScale,\n height * sourceScale,\n 0,\n 0,\n width,\n height\n );\n return out.toDataURL(\"image/png\");\n}\n\n/**\n * Crops the current viewport out of a page render and encodes it small.\n * @param {any} canvas full-page render\n * @param {{ sourceScale?: number, outputScale?: number, quality?: number }} [options]\n * `sourceScale` is the scale `canvas` was rendered at \u2014 the source rect is\n * mapped through it. `outputScale` is the final size in CSS pixels.\n * @returns {string | null} JPEG data-URL, or null with no 2d context.\n */\nexport function cropViewport(\n canvas,\n { sourceScale = 1, outputScale = AUTO_SCALE, quality = AUTO_QUALITY } = {}\n) {\n const out = document.createElement(\"canvas\");\n out.width = Math.round(window.innerWidth * outputScale);\n out.height = Math.round(window.innerHeight * outputScale);\n const ctx = out.getContext(\"2d\");\n if (!ctx) return null;\n\n paintBackdrop(ctx, out.width, out.height);\n ctx.drawImage(\n canvas,\n window.scrollX * sourceScale,\n window.scrollY * sourceScale,\n window.innerWidth * sourceScale,\n window.innerHeight * sourceScale,\n 0,\n 0,\n out.width,\n out.height\n );\n return out.toDataURL(\"image/jpeg\", quality);\n}\n", "export const CLASSES = {\n CIRCLE: \"comment-circle\",\n CIRCLE_ACTIVE: \"comment-circle--active\",\n TOOLTIP: \"comment-tooltip\",\n TOOLBAR_TEXT: \"toolbar-text\",\n SHORTCUT_HINT: \"shortcut-hint\",\n COMMENT_INPUT_AREA: \"comment-input-area\",\n CLOSE_TOOLTIP: \"close-tooltip\",\n ACTIVE: \"active\",\n COMMENT_CURSOR: \"comment-cursor\",\n COMMENT_OVERLAY: \"comment-overlay\",\n THREAD_POPOVER: \"comment-thread-popover\",\n THREAD_HEADER: \"thread-header\",\n THREAD_BODY: \"thread-body\",\n THREAD_REPLIES: \"thread-replies\",\n THREAD_REPLY: \"thread-reply\",\n THREAD_REPLY_ACTIONS: \"thread-reply-actions\",\n THREAD_INPUT_AREA: \"thread-input-area\",\n THREAD_INPUT: \"thread-input\",\n THREAD_SUBMIT: \"thread-submit\",\n THREAD_ACTIONS_ROW: \"thread-actions-row\",\n THREAD_SCROLL: \"thread-scroll\",\n THREAD_META: \"thread-meta\",\n THREAD_AUTHOR: \"thread-author\",\n THREAD_AUTHOR_NAME: \"thread-author-name\",\n INBOX_HEADER_ACTIONS: \"inbox-header-actions\",\n INBOX_METRICS_BTN: \"inbox-metrics-btn\",\n METRICS_VIEW: \"metrics-view\",\n METRICS_TILES: \"metrics-tiles\",\n METRICS_TILE: \"metrics-tile\",\n METRICS_TILE_VALUE: \"metrics-tile-value\",\n METRICS_TILE_LABEL: \"metrics-tile-label\",\n METRICS_GROUP: \"metrics-group\",\n METRICS_HEADING: \"metrics-heading\",\n METRICS_ROW: \"metrics-row\",\n METRICS_ROW_LABEL: \"metrics-row-label\",\n METRICS_TRACK: \"metrics-track\",\n METRICS_BAR: \"metrics-bar\",\n METRICS_ROW_COUNT: \"metrics-row-count\",\n METRICS_CHART: \"metrics-chart\",\n METRICS_AXIS: \"metrics-axis\",\n METRICS_EXPORTS: \"metrics-exports\",\n METRICS_EXPORT_BTN: \"metrics-export-btn\",\n METRICS_EMPTY: \"metrics-empty\",\n AUDIT_BLOCK: \"audit-block\",\n AUDIT_TOGGLE: \"audit-toggle\",\n AUDIT_BODY: \"audit-body\",\n AUDIT_LIST: \"audit-list\",\n AUDIT_ROW: \"audit-row\",\n AUDIT_ACTION: \"audit-action\",\n AUDIT_ACTOR: \"audit-actor\",\n AUDIT_TIME: \"audit-time\",\n AUDIT_HEADING: \"audit-heading\",\n AUDIT_RESOLUTIONS: \"audit-resolutions\",\n THREAD_TIME: \"thread-time\",\n THREAD_EDITED: \"thread-edited\",\n EDITOR: \"helldots-editor\",\n EDITOR_INPUT: \"helldots-editor-input\",\n EDITOR_ACTIONS: \"helldots-editor-actions\",\n EDITOR_SAVE: \"helldots-editor-save\",\n EDITOR_CANCEL: \"helldots-editor-cancel\",\n INBOX_NOTICE: \"inbox-notice\",\n PREVIEW_CIRCLE: \"preview-circle\",\n SELECTION_RECT: \"selection-rect\",\n SCREENSHOT_IMG: \"screenshot-img\",\n SCREENSHOT_REMOVE: \"screenshot-remove\",\n SCREENSHOTS_CONTAINER: \"screenshots-container\",\n SCREENSHOT_ITEM: \"screenshot-item\",\n SCREENSHOT_PENDING: \"screenshot-pending\",\n CONFIRM: \"helldots-confirm\",\n CONFIRM_PANEL: \"helldots-confirm-panel\",\n CONFIRM_TITLE: \"helldots-confirm-title\",\n CONFIRM_MESSAGE: \"helldots-confirm-message\",\n CONFIRM_ACTIONS: \"helldots-confirm-actions\",\n CONFIRM_CANCEL: \"helldots-confirm-cancel\",\n CONFIRM_ACCEPT: \"helldots-confirm-accept\",\n LIGHTBOX: \"helldots-lightbox\",\n LIGHTBOX_IMG: \"helldots-lightbox-img\",\n LIGHTBOX_CLOSE: \"helldots-lightbox-close\",\n COMMENT_ACTIONS_BAR: \"comment-actions-bar\",\n ATTACH_IMAGE_BTN: \"attach-image-btn\",\n TOOLBAR_ACTIONS: \"toolbar-actions\",\n TOOLBAR_ACTION_BTN: \"toolbar-action-btn\",\n TOOLBAR_ACTION_WRAPPER: \"toolbar-action-wrapper\",\n TOOLBAR_ACTION_TOOLTIP: \"toolbar-action-tooltip\",\n TOOLBAR_COMMENT_BTN: \"toolbar-comment-btn\",\n TOOLBAR_MENU_BTN: \"toolbar-menu-btn\",\n TOOLBAR_VISIBILITY: \"toolbar-visibility\",\n TOOLBAR_EYE_BTN: \"toolbar-eye-btn\",\n MARKERS_HIDDEN: \"markers-hidden\",\n INBOX_PANEL: \"inbox-panel\",\n INBOX_HEADER: \"inbox-header\",\n INBOX_FILTER: \"inbox-filter\",\n INBOX_FILTER_MENU: \"inbox-filter-menu\",\n INBOX_FILTER_MENU_HEADER: \"inbox-filter-menu-header\",\n INBOX_FILTER_CLEAR: \"inbox-filter-clear\",\n INBOX_FILTER_GROUP: \"inbox-filter-group\",\n INBOX_FILTER_CHIPS: \"inbox-filter-chips\",\n INBOX_FILTER_CHIP: \"inbox-filter-chip\",\n INBOX_FILTER_SECTION: \"inbox-filter-section\",\n INBOX_CLOSE: \"inbox-close\",\n INBOX_LIST: \"inbox-list\",\n INBOX_CARD: \"inbox-card\",\n INBOX_CARD_HEADER: \"inbox-card-header\",\n INBOX_CARD_ACTIONS: \"inbox-card-actions\",\n INBOX_CARD_TEXT: \"inbox-card-text\",\n INBOX_CARD_TAG: \"inbox-card-tag\",\n INBOX_CARD_REPLY_LINK: \"inbox-card-reply-link\",\n INBOX_ACTION_BTN: \"inbox-action-btn\",\n INBOX_ACTION_BTN_LABELED: \"inbox-action-btn--labeled\",\n INBOX_ACTION_LABEL: \"inbox-action-label\",\n INBOX_STATUS_DOT: \"inbox-status-dot\",\n INBOX_MENU: \"inbox-menu\",\n // Set by menus.js when a dropdown has to open upward to stay unclipped.\n INBOX_MENU_UP: \"inbox-menu--up\",\n // The horizontal counterpart: set when a dropdown has to align to its\n // button's left edge instead of its right one to stay unclipped.\n INBOX_MENU_START: \"inbox-menu--start\",\n INBOX_MENU_ITEM: \"inbox-menu-item\",\n INBOX_DETAIL: \"inbox-detail\",\n INBOX_DETAIL_HEADER: \"inbox-detail-header\",\n INBOX_BACK: \"inbox-back\",\n INBOX_NAV_BTN: \"inbox-nav-btn\",\n INBOX_REPLIES: \"inbox-replies\",\n TOOLTIP_REPLY_COUNT: \"comment-tooltip-reply-count\",\n INBOX_EMPTY: \"inbox-empty\",\n INBOX_EMPTY_ICON: \"inbox-empty-icon\",\n INBOX_EMPTY_TITLE: \"inbox-empty-title\",\n INBOX_EMPTY_TEXT: \"inbox-empty-text\",\n INBOX_EMPTY_KBD: \"inbox-empty-kbd\",\n INBOX_EMPTY_ACTION: \"inbox-empty-action\",\n CLASSIFY_ROW: \"classify-row\",\n INBOX_BADGES: \"inbox-badges\",\n BADGE: \"helldots-badge\",\n BADGE_STATUS: \"helldots-badge--status\",\n BADGE_TYPE: \"helldots-badge--type\",\n BADGE_PRIORITY: \"helldots-badge--priority\",\n BADGE_TAG: \"helldots-badge--tag\",\n BADGE_DURATION: \"helldots-badge--duration\",\n CONTEXT_BLOCK: \"inbox-context\",\n CONTEXT_TITLE: \"inbox-context-title\",\n CONTEXT_BODY: \"inbox-context-body\",\n CONTEXT_ROW: \"inbox-context-row\",\n CONTEXT_SCREENSHOT_CAPTION: \"inbox-context-screenshot-caption\",\n CONTEXT_TOGGLE: \"inbox-context-toggle\",\n HIGHLIGHT: \"helldots-highlight\",\n REACTION_BAR: \"reaction-bar\",\n REACTION_PILL: \"reaction-pill\",\n REACTION_PILL_MINE: \"reaction-pill--mine\",\n REACTION_PILL_EMOJI: \"reaction-pill-emoji\",\n REACTION_PILL_COUNT: \"reaction-pill-count\",\n REACTION_ADD: \"reaction-add\",\n REACTION_TRIGGER: \"reaction-trigger\",\n REACTION_PALETTE: \"reaction-palette\",\n REACTION_PALETTE_ITEM: \"reaction-palette-item\",\n // The action strip splits in two: classification on the left, icon buttons\n // (react, copy, \u22EF) on the right.\n ACTIONS_GROUP: \"actions-group\",\n ACTIONS_GROUP_END: \"actions-group--end\",\n};\n\n// The only classes HellDots puts on elements of the host page \u2014 everything\n// else it owns lives inside the shadow root. Anchors must never bake these\n// into a selector: they are transient widget state, so `body.comment-cursor`\n// stops matching the instant comment mode ends, killing the anchor's fast\n// path. Deliberately an explicit list rather than every value of CLASSES:\n// generic names like `active` belong to host pages too, and filtering those\n// would weaken anchors instead of protecting them.\nexport const HOST_PAGE_CLASSES = [CLASSES.COMMENT_CURSOR];\n\n// The eye toggle's persisted preference. Its own key, independent of the\n// widget's `persistence` option: it is a viewer preference, not comment data.\nexport const MARKERS_HIDDEN_STORAGE_KEY = \"helldots-markers-hidden\";\n\nexport const IDS = {\n TOOLBAR: \"comment-toolbar\",\n COMMENT_BOX: \"comment-box\",\n COMMENT_INPUT: \"comment-input\",\n SUBMIT_COMMENT: \"submit-comment\",\n STYLES: \"comment-overlay-styles\",\n GLOBAL_STYLES: \"comment-overlay-global-styles\",\n ATTACH_IMAGE_INPUT: \"attach-image-input\",\n};\n\n// Marker circle size in px. The stylesheet's .comment-circle rule and the\n// positioning math (center offsets, edge clamps) must agree on this number.\nexport const MARKER_SIZE = 28;\n\n// Cap on user-attached screenshots per comment or reply \u2014 enforced by every\n// attachment surface (comment box, thread popover, inbox reply input).\nexport const MAX_SCREENSHOTS = 5;\n\n// RF09 \u2014 comment lifecycle. Order matters: it's the order shown in the\n// status picker menu and in the inbox's status filter. Nothing enforces the\n// transitions \u2014 setCommentStatus accepts any state from any state.\nexport const STATUSES = [\"open\", \"in_progress\", \"in_review\", \"resolved\"];\n\n// Every lifecycle state is painted \u2014 status is never \"unset\", so an empty\n// ring would read as missing rather than as new. `open` takes an off-white\n// grey: present and legible, but the only unsaturated entry, so the three\n// states somebody actually moved a comment into are the ones that carry\n// colour. That frees the blue for `in_review`.\nexport const STATUS_COLORS = {\n open: \"#D1D1D6\",\n in_progress: \"#FF9F0A\",\n in_review: \"#2E90FA\",\n resolved: \"#30D158\",\n};\n\n// RF3 \u2014 comment category. Order matters: it's the order shown in the picker.\nexport const COMMENT_TYPES = [\"bug\", \"suggestion\", \"question\", \"improvement\"];\n\nexport const TYPE_COLORS = {\n bug: \"#FF453A\",\n suggestion: \"#BF5AF2\",\n question: \"#64D2FF\",\n improvement: \"#5E5CE6\",\n};\n\n// RF4 \u2014 priority, ordered high\u2192low so the picker reads as a scale.\nexport const PRIORITIES = [\"high\", \"medium\", \"low\"];\n\n// Deliberate red/orange/grey ramp: it reads as urgency at a glance. `high`\n// sharing red with `bug` (and `medium` sharing orange with `in_progress`) is\n// fine \u2014 they're different dimensions in different UI slots, and no badge\n// ever conveys meaning by colour alone (WCAG 1.4.1).\nexport const PRIORITY_COLORS = {\n high: \"#FF453A\",\n medium: \"#FF9F0A\",\n low: \"#8E8E93\",\n};\n\n// Emoji reactions. The order is load-bearing twice over: it is the order of\n// the palette AND of the pills, so a pill never moves out from under the\n// pointer when a count changes. Fixed rather than host-configurable, and\n// deliberately small enough to need no emoji dataset \u2014 see DECISIONS.md.\nexport const REACTION_EMOJIS = [\"\uD83D\uDC4D\", \"\uD83D\uDC4E\", \"\u2764\uFE0F\", \"\uD83C\uDF89\", \"\uD83D\uDC40\", \"\uD83D\uDE80\"];\n\nexport const SELECTORS = {\n CONTAINER: 'section, div[class*=\"container\"], div[class*=\"content\"]',\n};\n\nexport const Z_INDEX = {\n CIRCLE: 9997,\n TOOLTIP: 10000,\n TOOLBAR: 9998,\n COMMENT_BOX: 9999,\n LIGHTBOX: 10001,\n // Above the lightbox: a screenshot can be open full-screen when the \u22EF menu\n // behind it is used, and a confirmation nobody can see is worse than none.\n CONFIRM: 10002,\n};\n\n// 32x32 is a hard ceiling, not a design preference: Chromium drops a custom\n// cursor larger than that as soon as it can intersect native UI, which is\n// exactly what happens as the pointer nears the page edges \u2014 the marker\n// silently reverted to the default arrow there.\n// https://chromestatus.com/feature/5825971391299584\n//\n// The artwork itself is unchanged and still 28px; only the canvas shrank,\n// from 48 to 32, by translating the art from (6,6) to (2,2). That is why\n// CURSOR_HOTSPOT moved with it \u2014 it names the teardrop's sharp tip, which is\n// what the pointer must actually point at.\n//\n// The original blue drop shadow is gone with the canvas: at `dx=4 dy=4` and\n// `stdDeviation=5` it needed ~15px of margin the 32px canvas does not have.\n// The white 2px stroke is what carries contrast against any background; the\n// shadow was a 16%-opacity blue glow that barely registered.\nexport const CURSOR_SVG = `data:image/svg+xml;utf8,<svg width=\"32\" height=\"32\" viewBox=\"0 0 32 32\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><g transform=\"translate(-4,-4)\"><path d=\"M6 8C6 6.89543 6.89543 6 8 6H20C27.732 6 34 12.268 34 20V20C34 27.732 27.732 34 20 34V34C12.268 34 6 27.732 6 20V8Z\" fill=\"%232E90FA\"/><path d=\"M8 7H20C27.1797 7 33 12.8203 33 20C33 27.1797 27.1797 33 20 33C12.8203 33 7 27.1797 7 20V8C7 7.44772 7.44772 7 8 7Z\" stroke=\"white\" stroke-width=\"2\"/></g></svg>`;\n\n// Where the pointer actually points: the teardrop's sharp top-left tip.\nexport const CURSOR_HOTSPOT = \"2 2\";\n", "// Drag-selection and screenshot-capture orchestration.\n//\n// Extracted from CommentOverlay as part of splitting the god object\n// (DECISIONS.md, Fase 5). This module owns the drag rectangle, the one\n// render each gesture pays for, and the pending automatic capture the\n// click path kicks off in the background. Placement \u2014 anchoring, the\n// comment box \u2014 stays with the overlay and is reached through `onPlace`;\n// the pending-attachments array stays with the comment box that previews\n// it and is fed through `onRegionCaptured`.\n\nimport { renderPage, cropRegion, cropViewport, AUTO_SCALE } from \"./capture.js\";\nimport { CLASSES } from \"./constants.js\";\n\nexport class CaptureFlow {\n /**\n * @param {{\n * host: ShadowRoot,\n * autoScreenshot: boolean,\n * embedCrossOriginFonts?: boolean,\n * fastCapture?: boolean,\n * skipIframeContent?: boolean,\n * captureTimeout?: number,\n * onRegionCaptured: (dataUrl: string) => void,\n * onRegionPending?: (pending: boolean) => void,\n * onPlace: (x: number, y: number, region?: { left: number, top: number, width: number, height: number }) => Promise<void>,\n * onError?: (error: unknown) => void,\n * }} deps `host` is where the selection rectangle mounts. `onError` is\n * how a failed render reaches the host: a capture that silently comes\n * back null leaves a feedback tool without the thing it exists to\n * collect, and the console is the only place that says so today.\n */\n constructor({\n host,\n autoScreenshot,\n embedCrossOriginFonts = false,\n fastCapture = false,\n skipIframeContent = false,\n captureTimeout,\n onRegionCaptured,\n onRegionPending,\n onPlace,\n onError,\n }) {\n this.host = host;\n this.autoScreenshot = autoScreenshot;\n this.embedCrossOriginFonts = embedCrossOriginFonts;\n this.fastCapture = fastCapture;\n this.skipIframeContent = skipIframeContent;\n this.captureTimeout = captureTimeout;\n this.onRegionCaptured = onRegionCaptured;\n this.onRegionPending = onRegionPending;\n this.onPlace = onPlace;\n this.onError = onError;\n\n /**\n * The in-flight automatic capture, resolving to a JPEG data-URL or\n * null. A promise rather than the value: the render kicks off when the\n * comment box opens and the save path awaits it, so the render never\n * gates the box.\n * @type {Promise<string | null> | null}\n */\n this.pendingCapture = null;\n\n /**\n * The in-flight region crop. Nothing reads its value \u2014 the crop reaches\n * the box through `onRegionCaptured` \u2014 but the save path has to be able\n * to wait for it.\n * @type {Promise<void> | null}\n */\n this.pendingRegion = null;\n\n /**\n * Identity of the gesture that owns the in-flight region crop.\n *\n * An object rather than a boolean, and compared by identity: what has to\n * be caught is not only \"the draft was dismissed\" but \"dismissed and a\n * different one opened while the render ran\" \u2014 a window that is now\n * seconds long, because the box no longer waits. A flag cannot tell\n * those apart, and the crop would land on the wrong draft.\n * @type {object | null}\n */\n this._regionToken = null;\n\n /** @type {{ x: number, y: number } | null} */\n this._dragStart = null;\n this._isDragging = false;\n /** @type {HTMLElement | null} */\n this._selectionRect = null;\n this._boundDragMove = (/** @type {MouseEvent} */ e) => this.onDragMove(e);\n this._boundDragEnd = (/** @type {MouseEvent} */ e) => this.onDragEnd(e);\n }\n\n /** Starts tracking a possible drag from a mousedown in comment mode. */\n beginDrag(/** @type {MouseEvent} */ e) {\n this._dragStart = { x: e.clientX, y: e.clientY };\n this._isDragging = false;\n document.addEventListener(\"mousemove\", this._boundDragMove);\n document.addEventListener(\"mouseup\", this._boundDragEnd);\n }\n\n onDragMove(/** @type {MouseEvent} */ e) {\n const dx = e.clientX - this._dragStart.x;\n const dy = e.clientY - this._dragStart.y;\n\n if (!this._isDragging && Math.hypot(dx, dy) < 5) return;\n\n this._isDragging = true;\n\n const left = Math.min(this._dragStart.x, e.clientX);\n const top = Math.min(this._dragStart.y, e.clientY);\n const width = Math.abs(dx);\n const height = Math.abs(dy);\n\n if (!this._selectionRect) {\n this._selectionRect = document.createElement(\"div\");\n this._selectionRect.className = CLASSES.SELECTION_RECT;\n this.host.appendChild(this._selectionRect);\n }\n\n this._selectionRect.style.left = `${left}px`;\n this._selectionRect.style.top = `${top}px`;\n this._selectionRect.style.width = `${width}px`;\n this._selectionRect.style.height = `${height}px`;\n }\n\n async onDragEnd(/** @type {MouseEvent} */ e) {\n document.removeEventListener(\"mousemove\", this._boundDragMove);\n document.removeEventListener(\"mouseup\", this._boundDragEnd);\n\n if (this._isDragging) {\n const left = Math.min(this._dragStart.x, e.clientX);\n const top = Math.min(this._dragStart.y, e.clientY);\n const width = Math.abs(e.clientX - this._dragStart.x);\n const height = Math.abs(e.clientY - this._dragStart.y);\n\n this._selectionRect?.remove();\n this._selectionRect = null;\n\n const region =\n width > 10 && height > 10 ? { left, top, width, height } : undefined;\n if (region) this.startRegionCapture(region);\n\n // NOT awaited any more. This used to sit behind the render, which on\n // a heavy page meant a second or more between releasing the mouse and\n // the box appearing \u2014 the gesture read as having been ignored. The\n // crop arrives through `onRegionCaptured` and drops into the slot the\n // box is already showing.\n //\n // The point is the region's CENTER, not the mouseup pixel: the gesture\n // names the rectangle, and anchoring to wherever the mouse happened to\n // be released let a transient overlay under that one pixel claim the\n // comment (see the region-anchoring design doc).\n await this.onPlace(left + width / 2, top + height / 2, region);\n } else {\n await this.onPlace(this._dragStart.x, this._dragStart.y);\n }\n\n this._isDragging = false;\n this._dragStart = null;\n }\n\n /**\n * Starts the render a drag gesture pays for, and returns immediately.\n *\n * One render still feeds both images \u2014 the PNG region the user selected\n * and the automatic JPEG context shot \u2014 because rendering the page is\n * practically the whole cost of a capture and doing it twice for one\n * comment was never defensible.\n *\n * `pendingCapture` is claimed synchronously, before this returns: the very\n * next thing the caller does is `onPlace`, which runs `armClickCapture`,\n * and that starts a SECOND render unless the slot is already taken.\n * @param {{ left: number, top: number, width: number, height: number }} region\n * Viewport coordinates of the selection.\n */\n startRegionCapture(region) {\n const token = {};\n this._regionToken = token;\n const stillMine = () => this._regionToken === token;\n\n const render = renderPage({\n scale: 1,\n embedCrossOriginFonts: this.embedCrossOriginFonts,\n fastCapture: this.fastCapture,\n skipIframeContent: this.skipIframeContent,\n captureTimeout: this.captureTimeout,\n });\n\n if (this.autoScreenshot) {\n this.pendingCapture = render\n .then(({ canvas, scale }) =>\n stillMine() ? cropViewport(canvas, { sourceScale: scale }) : null\n )\n // Reported through the region chain below, which owns the error for\n // this render \u2014 one failure should not reach the host twice.\n .catch(() => null);\n }\n\n this.onRegionPending?.(true);\n this.pendingRegion = render\n .then(({ canvas, scale }) => {\n if (!stillMine()) return;\n // The render's real scale, not the requested 1: on a page past the\n // canvas ceiling those differ, and cropping at 1 would cut the\n // wrong rectangle out of a smaller image.\n const dataUrl = cropRegion(canvas, region, { sourceScale: scale });\n if (dataUrl) this.onRegionCaptured(dataUrl);\n })\n .catch((err) => {\n console.warn(\"HellDots: screenshot capture failed:\", err);\n this.onError?.(err);\n })\n .finally(() => {\n // Only if this gesture still owns the slot: a newer draft has its\n // own placeholder, and clearing it here would blank that one.\n if (stillMine()) this.onRegionPending?.(false);\n });\n }\n\n /**\n * Kicks off the click path's background capture. Half scale because the\n * output is half scale anyway \u2014 that is ~4x off the RASTER, which is a\n * small share of the total; the clone and the style reads cost the same\n * at either scale. Deliberately NOT awaited: on heavy pages the render\n * takes hundreds of ms, and gating the comment box on it made every\n * click feel broken. The save path awaits the promise, by which time it\n * has almost always resolved.\n */\n armClickCapture() {\n if (!this.autoScreenshot || this.pendingCapture) return;\n this.pendingCapture = renderPage({\n scale: AUTO_SCALE,\n embedCrossOriginFonts: this.embedCrossOriginFonts,\n fastCapture: this.fastCapture,\n skipIframeContent: this.skipIframeContent,\n captureTimeout: this.captureTimeout,\n })\n .then(({ canvas, scale }) => cropViewport(canvas, { sourceScale: scale }))\n .catch((err) => {\n console.warn(\"HellDots: automatic screenshot failed\", err);\n this.onError?.(err);\n return null;\n });\n }\n\n /**\n * The capture the save path attaches \u2014 null when none is in flight.\n *\n * Waits on the region crop too, even though that is not what it returns.\n * Both come off the same render, and the save path reads the attachments\n * array immediately after this resolves; awaiting only the context shot\n * would let a Send land in the window before the crop was pushed into\n * that array, silently dropping the thing the user deliberately selected.\n * Folded in here rather than left as a second call for the caller to\n * remember, because forgetting it fails silently.\n * @returns {Promise<string | null>}\n */\n async consumePending() {\n await this.pendingRegion;\n return this.pendingCapture ? await this.pendingCapture : null;\n }\n\n /** Dismissing the comment box must not leak its capture into the next. */\n clearPending() {\n this.pendingCapture = null;\n this.pendingRegion = null;\n // Orphans whatever render is still running: its crop now belongs to a\n // draft that is gone, and the promise cannot be cancelled.\n this._regionToken = null;\n }\n\n /** Drops listeners and the selection rectangle, even mid-gesture. */\n destroy() {\n document.removeEventListener(\"mousemove\", this._boundDragMove);\n document.removeEventListener(\"mouseup\", this._boundDragEnd);\n this._selectionRect?.remove();\n this._selectionRect = null;\n this.pendingCapture = null;\n this.pendingRegion = null;\n this._regionToken = null;\n this._dragStart = null;\n this._isDragging = false;\n }\n}\n", "// RF2 \u2014 environment snapshot attached to every comment at creation time.\n// Kept as a pure function over an injectable `window` so the UA parsing\n// paths are testable without touching jsdom's real navigator.\n\n// Order is load-bearing: Edge's UA contains \"Chrome\", and Chrome's UA\n// contains \"Safari\". First match wins, so the more specific entries lead.\nconst BROWSERS = [\n { name: \"Edge\", re: /Edg\\/([\\d.]+)/ },\n { name: \"Chrome\", re: /Chrome\\/([\\d.]+)/ },\n { name: \"Firefox\", re: /Firefox\\/([\\d.]+)/ },\n { name: \"Safari\", re: /Version\\/([\\d.]+).*Safari/ },\n];\n\n// iOS before macOS: an iPhone UA also carries \"Mac OS X\".\nconst OPERATING_SYSTEMS = [\n { name: \"iOS\", re: /(?:iPhone|iPad).*OS ([\\d_]+) like Mac OS X/ },\n { name: \"Android\", re: /Android ([\\d.]+)/ },\n { name: \"Windows\", re: /Windows NT ([\\d.]+)/ },\n { name: \"macOS\", re: /Mac OS X ([\\d_.]+)/ },\n { name: \"Linux\", re: /Linux/ },\n];\n\nconst UNKNOWN = { name: \"unknown\", version: \"\" };\n\n// Chromium pads its brand list with a randomised \"GREASE\" entry to stop\n// consumers hardcoding brand positions. It is never the real browser.\nconst isGreaseBrand = (brand) => /not[\\W_]*a[\\W_]*brand/i.test(brand);\n\nconst matchFirst = (table, ua) => {\n for (const { name, re } of table) {\n const match = ua.match(re);\n if (match) {\n return { name, version: (match[1] || \"\").replace(/_/g, \".\") };\n }\n }\n return { ...UNKNOWN };\n};\n\n/**\n * Snapshots the browsing environment of the current page.\n * @param {any} [win] Injectable window \u2014 defaults to the real one.\n * @returns {import('./index.d.ts').CommentContext}\n */\nexport function captureContext(win = window) {\n const nav = win.navigator || {};\n const ua = nav.userAgent || \"\";\n const uaData = nav.userAgentData;\n\n let browser = matchFirst(BROWSERS, ua);\n const brand = uaData?.brands?.find((b) => !isGreaseBrand(b.brand));\n if (brand) {\n browser = { name: brand.brand, version: brand.version || \"\" };\n }\n\n const os = matchFirst(OPERATING_SYSTEMS, ua);\n if (uaData?.platform) os.name = uaData.platform;\n\n return {\n version: 1,\n url: win.location?.href || \"\",\n viewport: { width: win.innerWidth, height: win.innerHeight },\n screen: {\n width: win.screen?.width ?? 0,\n height: win.screen?.height ?? 0,\n },\n devicePixelRatio: win.devicePixelRatio ?? 1,\n userAgent: ua,\n browser,\n os,\n language: nav.language || \"\",\n };\n}\n", "import {\n CLASSES,\n IDS,\n Z_INDEX,\n CURSOR_SVG,\n CURSOR_HOTSPOT,\n MARKER_SIZE,\n} from \"./constants.js\";\n\n// Every scrollable surface in the widget sits on a #1C1C1E panel, so the\n// scrollbar has to be dark too. It was not: Chromium >= 121 ignores all\n// ::-webkit-scrollbar-* rules on an element that also declares\n// scrollbar-width or scrollbar-color, so the popover's styled thumb was\n// dropped and the platform default took over \u2014 a light thumb on a white\n// track, painted straight over the panel.\n//\n// The standard properties are the ones that win there, so they carry the\n// colour. The webkit block stays for Safari, which only shipped\n// scrollbar-color in 18.2 and still needs the pseudo-elements before that.\nconst SCROLLBAR = ` scrollbar-width:thin;scrollbar-color:rgba(255,255,255,0.22) transparent;`;\n\nconst webkitScrollbar = (...selectors) =>\n selectors\n .map(\n (selector) => ` ${selector}::-webkit-scrollbar{width:8px;height:8px;}${selector}::-webkit-scrollbar-track{background:transparent;}${selector}::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.22);border-radius:4px;}${selector}::-webkit-scrollbar-thumb:hover{background:rgba(255,255,255,0.35);}`\n )\n .join(\"\");\n\nexport const getStyles = () => ` :host{all:initial;display:block;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif;line-height:1.5;color-scheme:light;}:host *,:host *::before,:host *::after{box-sizing:border-box;font-family:inherit;}button{padding:0;font:inherit;color:inherit;}#${IDS.TOOLBAR}{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);z-index:${Z_INDEX.TOOLBAR};}.${CLASSES.TOOLBAR_ACTION_WRAPPER}{position:relative;}.${CLASSES.TOOLBAR_ACTION_TOOLTIP}{position:absolute;bottom:calc(100% + 10px);left:50%;transform:translateX(-50%) translateY(4px);display:flex;align-items:center;gap:8px;background:rgba(20,20,23,0.95);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);padding:8px 12px;border-radius:10px;border:1px solid rgba(255,255,255,0.08);box-shadow:0 4px 24px rgba(0,0,0,0.35);color:white;white-space:nowrap;opacity:0;pointer-events:none;transition:opacity 0.15s ease,transform 0.15s ease;}.${CLASSES.TOOLBAR_ACTION_WRAPPER}:hover .${\n CLASSES.TOOLBAR_ACTION_TOOLTIP\n }{opacity:1;pointer-events:auto;transform:translateX(-50%) translateY(0);}.${CLASSES.TOOLBAR_TEXT}{font-size:13px;font-weight:500;letter-spacing:-0.01em;}.${CLASSES.SHORTCUT_HINT}{font-size:11px;font-weight:500;color:rgba(255,255,255,0.5);background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.1);padding:2px 6px;border-radius:5px;line-height:1;white-space:nowrap;}.${CLASSES.TOOLBAR_ACTIONS},.${CLASSES.TOOLBAR_VISIBILITY}{display:flex;flex-direction:row;background:rgba(20,20,23,0.95);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);border-radius:12px;box-shadow:0 4px 24px rgba(0,0,0,0.35);}.${CLASSES.TOOLBAR_VISIBILITY}{position:absolute;left:calc(100% + 5px);top:0;}.${CLASSES.TOOLBAR_ACTION_BTN}{width:42px;height:42px;display:flex;align-items:center;justify-content:center;background:none;border:none;outline:none;color:rgba(255,255,255,0.65);cursor:pointer;transition:background 0.2s,color 0.2s;padding:0;}.${CLASSES.TOOLBAR_ACTION_WRAPPER}:first-child .${\n CLASSES.TOOLBAR_ACTION_BTN\n }{border-radius:12px 0 0 12px;}.${CLASSES.TOOLBAR_ACTION_WRAPPER}:last-child .${\n CLASSES.TOOLBAR_ACTION_BTN\n }{border-radius:0 12px 12px 0;}.${CLASSES.TOOLBAR_ACTION_WRAPPER}:only-child .${\n CLASSES.TOOLBAR_ACTION_BTN\n }{border-radius:12px;}.${CLASSES.TOOLBAR_ACTION_BTN}:hover{background:rgba(255,255,255,0.08);color:white;}.${CLASSES.TOOLBAR_COMMENT_BTN}.${CLASSES.ACTIVE}{color:#2E90FA;background:rgba(46,144,250,0.1);}#${IDS.COMMENT_BOX}{position:fixed;background:#1C1C1E;border-radius:12px;box-shadow:0 4px 20px rgba(0,0,0,0.4);padding:16px;z-index:${Z_INDEX.COMMENT_BOX};width:min(400px,calc(100vw - 24px));display:none;box-sizing:border-box;}#${IDS.COMMENT_BOX} .${CLASSES.COMMENT_INPUT_AREA}{display:flex;flex-direction:column;gap:0;}.${CLASSES.CLASSIFY_ROW}{display:flex;align-items:center;flex-wrap:wrap;gap:8px;padding:0 0 10px;border-bottom:1px solid rgba(255,255,255,0.08);}.${CLASSES.CLASSIFY_ROW} .${CLASSES.INBOX_ACTION_BTN}{height:26px;border:1px solid rgba(255,255,255,0.12);border-radius:8px;}.${CLASSES.CLASSIFY_ROW} .${CLASSES.INBOX_ACTION_BTN}:hover{border-color:rgba(255,255,255,0.28);}#${IDS.COMMENT_INPUT}{flex:1;min-height:20px;background:#1C1C1E;border:none;resize:none;font-family:inherit;color:white;font-size:14px;line-height:1.4;box-sizing:border-box;field-sizing:content;padding:8px 0;}#${IDS.COMMENT_INPUT}::placeholder{color:rgba(255,255,255,0.5);}#${IDS.COMMENT_INPUT}:focus{outline:none;box-shadow:none;}.${CLASSES.COMMENT_ACTIONS_BAR}{display:flex;align-items:center;justify-content:space-between;padding-top:12px;}.${CLASSES.ATTACH_IMAGE_BTN}{background:none;border:none;color:rgba(255,255,255,0.5);cursor:pointer;display:flex;align-items:center;justify-content:center;border-radius:6px;transition:background 0.2s,color 0.2s;}.${CLASSES.ATTACH_IMAGE_BTN}:hover{background:rgba(255,255,255,0.1);color:rgba(255,255,255,0.8);}.${CLASSES.CIRCLE}{position:absolute;width:${MARKER_SIZE}px;height:${MARKER_SIZE}px;background:#2E90FA;border-radius:0% 100% 100% 100%;border:2px solid #FFF;cursor:pointer;box-shadow:0 1px 5px rgba(0,0,0,0.2);transition:transform 0.2s,background 0.2s;z-index:${Z_INDEX.CIRCLE};transform:translate(-50%,-50%);}.${CLASSES.CIRCLE}:hover{transform:translate(-50%,-50%) scale(1.2) !important;background:rgb(0,123,255);}.${CLASSES.CIRCLE}.${CLASSES.HIGHLIGHT}{transform:translate(-50%,-50%) scale(1.2) !important;background:rgb(0,123,255);box-shadow:0 0 0 4px rgba(46,144,250,0.35),0 1px 5px rgba(0,0,0,0.2);}.${CLASSES.CIRCLE}.${CLASSES.CIRCLE_ACTIVE}{transform:translate(-50%,-50%) scale(1.2) !important;background:rgb(0,123,255);box-shadow:0 0 0 5px rgba(46,144,250,0.5),0 1px 5px rgba(0,0,0,0.2);}.${CLASSES.MARKERS_HIDDEN} .${CLASSES.CIRCLE}{display:none !important;}.${CLASSES.TOOLTIP}{position:fixed;background:#1C1C1E;border-radius:12px;padding:16px;box-shadow:0 4px 20px rgba(0,0,0,0.4);width:min(400px,calc(100vw - 24px));max-height:calc(100vh - 20px);overflow-y:auto;overscroll-behavior:contain;${SCROLLBAR} z-index:${Z_INDEX.TOOLTIP};color:white;font-size:14px;line-height:1.5;box-sizing:border-box;}.${CLASSES.TOOLTIP} .${CLASSES.THREAD_BODY}{padding:8px 0;}.${CLASSES.THREAD_POPOVER}{position:fixed;background:#1C1C1E;border-radius:12px;padding:16px;box-shadow:0 4px 20px rgba(0,0,0,0.4);width:min(400px,calc(100vw - 24px));max-height:calc(100vh - 20px);display:flex;flex-direction:column;z-index:${Z_INDEX.TOOLTIP};color:white;font-size:14px;line-height:1.5;box-sizing:border-box;}.${CLASSES.THREAD_POPOVER}>.${CLASSES.THREAD_HEADER},.${CLASSES.THREAD_POPOVER}>.${CLASSES.THREAD_ACTIONS_ROW},.${CLASSES.THREAD_POPOVER}>.${CLASSES.THREAD_INPUT_AREA}{flex:none;}.${CLASSES.THREAD_SCROLL}{flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain;${SCROLLBAR}}${webkitScrollbar(\n `.${CLASSES.THREAD_SCROLL}`,\n `.${CLASSES.TOOLTIP}`,\n `.${CLASSES.INBOX_LIST}`,\n `.${CLASSES.INBOX_DETAIL}`,\n `.${CLASSES.EDITOR_INPUT}`\n)} .${CLASSES.INBOX_PANEL}{position:fixed;top:16px;right:16px;bottom:16px;width:380px;display:flex;flex-direction:column;background:#1C1C1E;border:1px solid rgba(255,255,255,0.08);border-radius:14px;box-shadow:0 8px 32px rgba(0,0,0,0.5);z-index:${Z_INDEX.COMMENT_BOX};color:white;font-size:14px;line-height:1.5;box-sizing:border-box;overflow:hidden;}.${CLASSES.INBOX_PANEL}:focus,.${CLASSES.INBOX_PANEL}:focus-visible{outline:none;}@media (max-width:480px){.${CLASSES.INBOX_PANEL}{left:16px;width:auto;}}.${CLASSES.INBOX_HEADER},.${CLASSES.INBOX_DETAIL_HEADER}{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:12px 14px;border-bottom:1px solid rgba(255,255,255,0.08);flex:none;}.${CLASSES.INBOX_FILTER}-wrapper{position:relative;}.${CLASSES.INBOX_FILTER}{display:flex;align-items:center;gap:6px;background:transparent;border:none;color:white;font-size:13px;font-weight:600;cursor:pointer;padding:4px 6px;border-radius:6px;}.${CLASSES.INBOX_FILTER}:hover{background:rgba(255,255,255,0.08);}.${CLASSES.INBOX_FILTER_MENU}{position:absolute;top:calc(100% + 6px);left:0;background:#2C2C2E;border:1px solid rgba(255,255,255,0.1);border-radius:12px;padding:14px;width:300px;max-width:calc(100vw - 40px);z-index:1;box-shadow:0 8px 28px rgba(0,0,0,0.5);}.${CLASSES.INBOX_FILTER_MENU_HEADER}{display:flex;align-items:baseline;justify-content:space-between;gap:12px;font-size:13px;font-weight:600;margin-bottom:12px;}.${CLASSES.INBOX_FILTER_CLEAR}{background:transparent;border:none;color:rgba(255,255,255,0.55);font-size:12px;font-weight:500;cursor:pointer;padding:0;}.${CLASSES.INBOX_FILTER_CLEAR}:hover:not(:disabled){color:white;}.${CLASSES.INBOX_FILTER_CLEAR}:disabled{opacity:0.35;cursor:default;}.${CLASSES.INBOX_FILTER_GROUP} + .${CLASSES.INBOX_FILTER_GROUP}{margin-top:14px;}.${CLASSES.INBOX_FILTER_SECTION}{font-size:11px;font-weight:600;color:rgba(255,255,255,0.45);margin-bottom:8px;}.${CLASSES.INBOX_FILTER_CHIPS}{display:flex;flex-wrap:wrap;gap:6px;}.${CLASSES.INBOX_FILTER_CHIP}{background:transparent;border:1px solid rgba(255,255,255,0.18);border-radius:999px;color:rgba(255,255,255,0.75);font-size:12px;line-height:1;padding:7px 12px;cursor:pointer;transition:background 0.15s,border-color 0.15s,color 0.15s;}.${CLASSES.INBOX_FILTER_CHIP}:hover{border-color:rgba(255,255,255,0.4);color:white;}.${CLASSES.INBOX_FILTER_CHIP}[aria-checked=\"true\"]{background:rgba(255,255,255,0.92);border-color:rgba(255,255,255,0.92);color:#1C1C1E;font-weight:600;}.${CLASSES.INBOX_MENU_ITEM}{display:block;width:100%;text-align:left;background:transparent;border:none;color:white;font-size:13px;padding:7px 10px;border-radius:6px;cursor:pointer;}.${CLASSES.INBOX_MENU_ITEM}:hover{background:rgba(255,255,255,0.08);}.${CLASSES.INBOX_CLOSE},.${CLASSES.INBOX_NAV_BTN},.${CLASSES.INBOX_BACK}{display:flex;align-items:center;gap:4px;background:transparent;border:none;color:rgba(255,255,255,0.75);cursor:pointer;padding:4px 6px;border-radius:6px;font-size:14px;}.${CLASSES.INBOX_CLOSE}{font-size:20px;line-height:1;}.${CLASSES.INBOX_CLOSE}:hover,.${CLASSES.INBOX_NAV_BTN}:not(:disabled):hover,.${CLASSES.INBOX_BACK}:hover{background:rgba(255,255,255,0.08);color:white;}.${CLASSES.INBOX_NAV_BTN}:disabled{opacity:0.35;cursor:default;}.${CLASSES.INBOX_LIST},.${CLASSES.INBOX_DETAIL}{flex:1;overflow-y:auto;${SCROLLBAR} padding:12px;display:flex;flex-direction:column;gap:12px;}.${CLASSES.INBOX_CARD}{border:1px solid rgba(255,255,255,0.1);border-radius:10px;padding:12px;display:flex;flex-direction:column;gap:8px;}.${CLASSES.INBOX_LIST} .${CLASSES.INBOX_CARD}{cursor:pointer;}.${CLASSES.INBOX_LIST} .${CLASSES.INBOX_CARD}:hover{border-color:rgba(255,255,255,0.22);}.${CLASSES.INBOX_CARD}--resolved{border-color:rgba(48,209,88,0.4);opacity:0.75;}.${CLASSES.INBOX_LIST} .${CLASSES.INBOX_CARD}--resolved:hover{border-color:rgba(48,209,88,0.7);opacity:1;}.${CLASSES.INBOX_CARD}--resolved:has([aria-expanded=\"true\"]){opacity:1;}.${CLASSES.INBOX_CARD_HEADER}{display:flex;align-items:center;justify-content:space-between;gap:8px;}.${CLASSES.INBOX_CARD_ACTIONS}{display:flex;align-items:center;justify-content:space-between;width:100%;gap:8px;}.${CLASSES.INBOX_DETAIL_HEADER} .${CLASSES.INBOX_CARD_ACTIONS}{justify-content:flex-end;gap:2px;}.${CLASSES.ACTIONS_GROUP}{display:flex;align-items:center;flex-wrap:wrap;gap:5px;}.${CLASSES.ACTIONS_GROUP_END}{flex-wrap:nowrap;gap:2px;}.${CLASSES.INBOX_ACTION_BTN}{display:flex;align-items:center;justify-content:center;width:24px;height:24px;background:transparent;border:none;border-radius:6px;color:rgba(255,255,255,0.65);cursor:pointer;}.${CLASSES.INBOX_ACTION_BTN}:hover{background:rgba(255,255,255,0.08);color:white;}.${CLASSES.INBOX_ACTION_BTN_LABELED}{width:auto;padding:0 8px 0 6px;gap:5px;justify-content:flex-start;flex:none;}.${CLASSES.INBOX_ACTION_LABEL}{font-size:11px;line-height:1;white-space:nowrap;}.${CLASSES.INBOX_STATUS_DOT}{width:12px;height:12px;border-radius:50%;border:1.5px solid rgba(255,255,255,0.45);display:inline-block;flex:none;}.${CLASSES.INBOX_MENU_ITEM} .${CLASSES.INBOX_STATUS_DOT}{width:9px;height:9px;border:none;margin-right:8px;vertical-align:baseline;}.${CLASSES.INBOX_MENU_ITEM}[aria-checked=\"true\"]{background:rgba(255,255,255,0.08);}.${CLASSES.THREAD_ACTIONS_ROW}{display:flex;justify-content:flex-end;}.${CLASSES.THREAD_POPOVER}>.${CLASSES.THREAD_ACTIONS_ROW}{padding-top:8px;}[data-hd-tooltip]{position:relative;}[data-hd-tooltip]::after{content:attr(data-hd-tooltip);position:absolute;bottom:calc(100% + 6px);left:50%;transform:translateX(-50%);background:#000;color:white;padding:4px 8px;border-radius:6px;font-size:11px;white-space:nowrap;opacity:0;pointer-events:none;transition:opacity 0.12s ease;z-index:2;}[data-hd-tooltip]:hover::after{opacity:1;}.${CLASSES.INBOX_MENU}{position:absolute;top:calc(100% + 4px);right:0;background:#2C2C2E;border:1px solid rgba(255,255,255,0.1);border-radius:8px;padding:4px;min-width:130px;z-index:1;box-shadow:0 4px 16px rgba(0,0,0,0.4);}.${CLASSES.INBOX_MENU}.${CLASSES.INBOX_MENU_UP}{top:auto;bottom:calc(100% + 4px);}.${CLASSES.INBOX_MENU}.${CLASSES.INBOX_MENU_START}{right:auto;left:0;}.${CLASSES.INBOX_CARD_TEXT}{white-space:pre-wrap;word-break:break-word;}.${CLASSES.INBOX_CARD_TAG}{align-self:flex-start;padding:1px 8px;border-radius:999px;background:rgba(255,159,10,0.2);color:#FF9F0A;font-size:11px;font-weight:600;}.${CLASSES.INBOX_BADGES}{display:flex;flex-wrap:wrap;gap:4px;}.${CLASSES.INBOX_CARD} .${CLASSES.INBOX_BADGES}{margin-top:6px;}.${CLASSES.TOOLTIP} .${CLASSES.INBOX_BADGES}{margin-bottom:4px;}.${CLASSES.BADGE}{display:inline-flex;align-items:center;padding:1px 6px;border:1px solid rgba(255,255,255,0.18);border-radius:10px;font-size:10px;line-height:1.6;letter-spacing:0.01em;white-space:nowrap;}.${CLASSES.BADGE_STATUS},.${CLASSES.BADGE_TYPE},.${CLASSES.BADGE_PRIORITY}{font-weight:600;}.${CLASSES.BADGE_TAG}{opacity:0.75;}.${CLASSES.BADGE_DURATION}{opacity:0.75;border-style:dashed;}.${CLASSES.REACTION_BAR}{display:flex;align-items:center;flex-wrap:wrap;gap:6px;margin-top:2px;margin-bottom:12px;}.${CLASSES.REACTION_BAR}[hidden]{display:none;}.${CLASSES.REACTION_TRIGGER}{position:relative;display:inline-flex;}.${CLASSES.REACTION_PILL}{display:inline-flex;align-items:center;gap:5px;height:24px;padding:0 8px;border:1px solid rgba(255,255,255,0.18);border-radius:8px;background:transparent;color:#F2F2F7;font-family:inherit;font-size:12px;line-height:1;cursor:pointer;transition:background 0.12s ease,border-color 0.12s ease;}.${CLASSES.REACTION_PILL}:hover{background:rgba(255,255,255,0.08);}.${CLASSES.REACTION_PILL_MINE}{border-color:#2E90FA;background:rgba(46,144,250,0.16);}.${CLASSES.REACTION_PILL_MINE}:hover{background:rgba(46,144,250,0.24);}.${CLASSES.REACTION_PILL_EMOJI}{font-family:\"Apple Color Emoji\",\"Segoe UI Emoji\",\"Noto Color Emoji\",sans-serif;font-size:13px;}.${CLASSES.REACTION_PILL_COUNT}{font-variant-numeric:tabular-nums;}.${CLASSES.REACTION_ADD}{display:inline-flex;align-items:center;justify-content:center;width:28px;height:24px;padding:0;border:1px solid rgba(255,255,255,0.18);border-radius:8px;background:transparent;color:#8E8E93;cursor:pointer;}.${CLASSES.REACTION_ADD}:hover{color:#F2F2F7;border-color:rgba(255,255,255,0.35);}.${CLASSES.REACTION_PALETTE}{position:absolute;top:calc(100% + 4px);left:0;white-space:nowrap;background:#2C2C2E;border:1px solid rgba(255,255,255,0.1);border-radius:10px;padding:4px;z-index:1;box-shadow:0 4px 16px rgba(0,0,0,0.4);}.${CLASSES.ACTIONS_GROUP_END} .${CLASSES.REACTION_PALETTE},.${CLASSES.THREAD_REPLY_ACTIONS} .${CLASSES.REACTION_PALETTE}{left:auto;right:0;}.${CLASSES.REACTION_PALETTE}.${CLASSES.INBOX_MENU_UP}{top:auto;bottom:calc(100% + 4px);}.${CLASSES.REACTION_PALETTE_ITEM}{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;padding:0;border:0;border-radius:6px;background:transparent;font-family:\"Apple Color Emoji\",\"Segoe UI Emoji\",\"Noto Color Emoji\",sans-serif;font-size:15px;cursor:pointer;}.${CLASSES.REACTION_PALETTE_ITEM}:hover{background:rgba(255,255,255,0.12);}.${CLASSES.CONTEXT_BLOCK}{display:flex;flex-direction:column;padding:10px 12px;border-top:1px solid rgba(255,255,255,0.08);font-size:11px;}.${CLASSES.CONTEXT_BODY}{display:flex;flex-direction:column;gap:4px;}.${CLASSES.CONTEXT_TITLE}{font-size:11px;font-weight:600;color:rgba(255,255,255,0.45);padding-bottom:4px;}.${CLASSES.CONTEXT_TOGGLE}{display:flex;align-items:center;justify-content:space-between;width:100%;background:transparent;border:none;color:rgba(255,255,255,0.45);font-size:11px;font-weight:600;cursor:pointer;padding:2px 0;}.${CLASSES.CONTEXT_TOGGLE}:hover{color:rgba(255,255,255,0.75);}.${CLASSES.CONTEXT_TOGGLE} svg{flex:none;transition:transform 0.15s ease;}.${CLASSES.CONTEXT_TOGGLE}[aria-expanded=\"true\"] svg{transform:rotate(180deg);}.${CLASSES.CONTEXT_TOGGLE}[aria-expanded=\"true\"] + .${CLASSES.CONTEXT_BODY}{padding-top:8px;}.${CLASSES.CONTEXT_BLOCK} img{width:100%;border-radius:6px;margin-bottom:6px;cursor:zoom-in;}.${CLASSES.CONTEXT_SCREENSHOT_CAPTION}{opacity:0.75;}.${CLASSES.CONTEXT_ROW}{display:flex;justify-content:space-between;gap:12px;opacity:0.75;}.${CLASSES.CONTEXT_ROW} span:last-child{text-align:right;word-break:break-all;}.${CLASSES.INBOX_CARD_REPLY_LINK}{align-self:flex-start;background:transparent;border:none;color:rgba(255,255,255,0.55);font-size:13px;cursor:pointer;padding:0;}.${CLASSES.INBOX_CARD_REPLY_LINK}:hover{color:white;}.${CLASSES.INBOX_REPLIES}{display:flex;flex-direction:column;gap:10px;padding:0 4px;}.${CLASSES.INBOX_REPLIES}:empty{display:none;}.${CLASSES.TOOLTIP_REPLY_COUNT}{font-size:12px;color:rgba(255,255,255,0.45);padding-top:4px;}.${CLASSES.INBOX_EMPTY}{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:32px 24px;color:rgba(255,255,255,0.55);text-align:center;}.${CLASSES.INBOX_EMPTY_ICON}{width:44px;height:44px;margin-bottom:6px;border:2px dashed rgba(255,255,255,0.25);border-radius:0% 100% 100% 100%;flex:none;}.${CLASSES.INBOX_EMPTY_TITLE}{color:white;font-size:14px;font-weight:600;}.${CLASSES.INBOX_EMPTY_TEXT}{font-size:13px;line-height:1.5;max-width:30ch;}.${CLASSES.INBOX_EMPTY_KBD}{font-family:inherit;font-size:12px;font-weight:500;color:rgba(255,255,255,0.8);background:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.14);border-radius:5px;padding:1px 5px;white-space:nowrap;}.${CLASSES.INBOX_EMPTY_ACTION}{margin-top:6px;background:transparent;border:1px solid rgba(255,255,255,0.2);border-radius:8px;color:white;font-size:13px;font-weight:500;padding:8px 16px;cursor:pointer;transition:background 0.15s,border-color 0.15s;}.${CLASSES.INBOX_EMPTY_ACTION}:hover{background:rgba(255,255,255,0.08);border-color:rgba(255,255,255,0.35);}.${CLASSES.THREAD_HEADER}{display:flex;justify-content:space-between;align-items:center;padding:0;}.${CLASSES.THREAD_META}{display:flex;align-items:center;gap:6px;flex:1;min-width:0;}.${CLASSES.THREAD_AUTHOR}{font-weight:600;font-size:13px;display:flex;min-width:0;max-width:280px;}.${CLASSES.THREAD_AUTHOR_NAME}{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}.${CLASSES.THREAD_AUTHOR}[data-hd-tooltip]::after{white-space:normal;max-width:240px;width:max-content;text-align:center;}.${CLASSES.THREAD_TIME}{font-size:12px;color:rgba(255,255,255,0.5);cursor:default;position:relative;flex:none;}.${CLASSES.THREAD_TIME}::after{content:attr(data-full-date);position:absolute;bottom:calc(100% + 6px);left:50%;transform:translateX(-50%);background:#000;color:white;padding:4px 8px;border-radius:6px;font-size:11px;white-space:nowrap;opacity:0;pointer-events:none;transition:opacity 0.15s ease;z-index:1;}.${CLASSES.THREAD_TIME}:hover::after{opacity:1;}.${CLASSES.THREAD_BODY}{padding:8px 0;white-space:pre-wrap;word-break:break-word;}.${CLASSES.THREAD_REPLIES}{padding:0;}.${CLASSES.THREAD_REPLIES}:empty{display:none;}.${CLASSES.THREAD_REPLY}{padding:16px 0 0 0;border-top:1px solid rgba(255,255,255,0.1);white-space:pre-wrap;word-break:break-word;font-size:14px;color:rgba(255,255,255,0.85);}.${CLASSES.THREAD_REPLY} .${CLASSES.THREAD_META}{margin-bottom:2px;}.${CLASSES.THREAD_REPLY_ACTIONS}{margin-left:auto;flex:none;}.${CLASSES.THREAD_REPLY} .${CLASSES.SCREENSHOT_IMG}{width:144px;height:100px;object-fit:cover;border-radius:8px;margin-top:4px;cursor:pointer;display:block;}.${CLASSES.THREAD_INPUT_AREA}{display:flex;flex-direction:column;gap:0;padding:12px 0 0;border-top:1px solid rgba(255,255,255,0.1);}.${CLASSES.THREAD_INPUT}{width:100%;background:transparent;border:none;padding:0;color:white;font-size:14px;font-family:inherit;outline:none;box-sizing:border-box;}.${CLASSES.THREAD_INPUT}::placeholder{color:rgba(255,255,255,0.5);}.${CLASSES.THREAD_INPUT}:focus{outline:none;box-shadow:none;}.${CLASSES.THREAD_SUBMIT}{background:none;border:none;color:#2E90FA;cursor:pointer;display:flex;align-items:center;justify-content:center;border-radius:6px;transition:background 0.2s;}.${CLASSES.THREAD_SUBMIT}:hover{background:rgba(46,144,250,0.15);color:#1570D6;}.${CLASSES.CLOSE_TOOLTIP}{background:none;border:none;font-size:18px;cursor:pointer;color:rgba(255,255,255,0.5);line-height:1;}.${CLASSES.CLOSE_TOOLTIP}:hover{color:white;}.${CLASSES.PREVIEW_CIRCLE}{animation:helldots-pulse 1.5s ease-in-out infinite;}@keyframes helldots-pulse{0%,100%{box-shadow:0 0 0 0 rgba(46,144,250,0.4),0 1px 5px rgba(0,0,0,0.2);}50%{box-shadow:0 0 0 8px rgba(46,144,250,0),0 1px 5px rgba(0,0,0,0.2);}}.${CLASSES.COMMENT_OVERLAY}{position:fixed;top:0;left:0;right:0;bottom:0;background:transparent;pointer-events:none;z-index:${Z_INDEX.TOOLBAR - 1};}.${CLASSES.COMMENT_OVERLAY}.${CLASSES.ACTIVE}{pointer-events:auto;background:rgba(0,0,0,0.1);}.${CLASSES.SELECTION_RECT}{position:fixed;border:2px solid #2E90FA;background:rgba(46,144,250,0.1);pointer-events:none;z-index:${Z_INDEX.TOOLTIP};box-sizing:border-box;}.${CLASSES.SCREENSHOTS_CONTAINER}{display:none;overflow-x:auto;gap:8px;margin-top:4px;padding:4px 0;scrollbar-width:none;-ms-overflow-style:none;margin-bottom:8px;}.${CLASSES.SCREENSHOTS_CONTAINER}::-webkit-scrollbar{display:none;}.${CLASSES.SCREENSHOTS_CONTAINER}.${CLASSES.ACTIVE}{display:flex;}.${CLASSES.SCREENSHOT_ITEM}{position:relative;flex-shrink:0;}.${CLASSES.SCREENSHOT_PENDING}{min-width:50px;height:50px;padding:0 8px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;text-align:center;border:1px dashed rgba(255,255,255,0.35);border-radius:8px;font-size:10px;line-height:1.2;color:rgba(255,255,255,0.7);white-space:nowrap;}.${CLASSES.SCREENSHOT_ITEM} .${CLASSES.SCREENSHOT_IMG}{width:50px;height:50px;object-fit:cover;border-radius:8px;cursor:pointer;display:block;margin:0;}.${CLASSES.SCREENSHOT_ITEM} .${CLASSES.SCREENSHOT_IMG}:hover{opacity:0.85;}.${CLASSES.SCREENSHOT_ITEM} .${CLASSES.SCREENSHOT_REMOVE}{position:absolute;top:-5px;right:-5px;width:18px;height:18px;background:rgba(0,0,0,0.7);border:none;border-radius:50%;color:white;font-size:12px;line-height:1;cursor:pointer;display:none;align-items:center;justify-content:center;z-index:1;}.${CLASSES.SCREENSHOT_ITEM}:hover .${CLASSES.SCREENSHOT_REMOVE}{display:flex;}.${CLASSES.SCREENSHOT_ITEM} .${CLASSES.SCREENSHOT_REMOVE}:hover{background:rgba(0,0,0,0.9);}.${CLASSES.TOOLTIP}>.${CLASSES.SCREENSHOTS_CONTAINER} .${\n CLASSES.SCREENSHOT_ITEM\n } .${CLASSES.SCREENSHOT_IMG},.${CLASSES.THREAD_SCROLL}>.${CLASSES.SCREENSHOTS_CONTAINER} .${\n CLASSES.SCREENSHOT_ITEM\n } .${CLASSES.SCREENSHOT_IMG},.${CLASSES.THREAD_REPLY} .${CLASSES.SCREENSHOT_ITEM} .${\n CLASSES.SCREENSHOT_IMG\n }{width:144px;height:100px;}.${CLASSES.CONFIRM}{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.6);display:flex;align-items:center;justify-content:center;padding:16px;animation:helldots-fade-in 0.15s ease;}.${CLASSES.CONFIRM_PANEL}{width:min(360px,100%);background:#1C1C1E;border:1px solid rgba(255,255,255,0.1);border-radius:14px;box-shadow:0 8px 32px rgba(0,0,0,0.5);padding:20px;color:white;font-size:14px;line-height:1.5;box-sizing:border-box;}.${CLASSES.CONFIRM_TITLE}{margin:0 0 8px;font-size:15px;font-weight:600;}.${CLASSES.CONFIRM_MESSAGE}{margin:0 0 20px;font-size:13px;color:rgba(255,255,255,0.65);}.${CLASSES.CONFIRM_ACTIONS}{display:flex;justify-content:flex-end;gap:8px;}.${CLASSES.CONFIRM_CANCEL},.${CLASSES.CONFIRM_ACCEPT}{padding:7px 14px;border:1px solid transparent;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;}.${CLASSES.CONFIRM_CANCEL}:focus-visible,.${CLASSES.CONFIRM_ACCEPT}:focus-visible{outline:2px solid #2E90FA;outline-offset:2px;}.${CLASSES.CONFIRM_CANCEL}{background:rgba(255,255,255,0.08);border-color:rgba(255,255,255,0.12);color:white;}.${CLASSES.CONFIRM_CANCEL}:hover{background:rgba(255,255,255,0.14);}.${CLASSES.CONFIRM_ACCEPT}{background:#FF453A;color:white;}.${CLASSES.CONFIRM_ACCEPT}:hover{background:#FF6961;}.${CLASSES.LIGHTBOX}{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.92);z-index:${Z_INDEX.LIGHTBOX};display:flex;align-items:center;justify-content:center;animation:helldots-fade-in 0.2s ease;}@keyframes helldots-fade-in{from{opacity:0;}to{opacity:1;}}.${CLASSES.LIGHTBOX_IMG}{max-width:90vw;max-height:90vh;object-fit:contain;border-radius:8px;}.${CLASSES.LIGHTBOX_CLOSE}{position:absolute;top:16px;right:16px;background:rgba(255,255,255,0.15);border:none;color:white;font-size:24px;width:40px;height:40px;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background 0.2s;}.${CLASSES.LIGHTBOX_CLOSE}:hover{background:rgba(255,255,255,0.3);}.${CLASSES.EDITOR}{display:flex;flex-direction:column;gap:8px;margin:4px 0 2px;}.${CLASSES.EDITOR_INPUT}{width:100%;box-sizing:border-box;resize:vertical;min-height:60px;background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.14);border-radius:8px;padding:8px 10px;color:white;font-size:14px;font-family:inherit;line-height:1.45;outline:none;${SCROLLBAR}}.${CLASSES.EDITOR_INPUT}:focus{border-color:rgba(46,144,250,0.7);}.${CLASSES.EDITOR_ACTIONS}{display:flex;justify-content:flex-end;gap:8px;}.${CLASSES.EDITOR_CANCEL},.${CLASSES.EDITOR_SAVE}{padding:5px 12px;border:1px solid transparent;border-radius:7px;font-size:13px;font-weight:600;font-family:inherit;cursor:pointer;}.${CLASSES.EDITOR_CANCEL}{background:rgba(255,255,255,0.08);border-color:rgba(255,255,255,0.12);color:white;}.${CLASSES.EDITOR_CANCEL}:hover{background:rgba(255,255,255,0.14);}.${CLASSES.EDITOR_SAVE}{background:#2E90FA;color:white;}.${CLASSES.EDITOR_SAVE}:hover:not(:disabled){background:#57A6FB;}.${CLASSES.EDITOR_SAVE}:disabled{background:rgba(255,255,255,0.10);color:rgba(255,255,255,0.4);cursor:not-allowed;}.${CLASSES.THREAD_EDITED}{font-size:12px;color:rgba(255,255,255,0.4);cursor:default;position:relative;flex:none;}.${CLASSES.THREAD_EDITED}::before{content:\"\u00B7\";margin-right:4px;}.${CLASSES.THREAD_EDITED}::after{content:attr(data-full-date);position:absolute;bottom:calc(100% + 6px);left:50%;transform:translateX(-50%);background:#000;color:white;padding:4px 8px;border-radius:6px;font-size:11px;white-space:nowrap;opacity:0;pointer-events:none;transition:opacity 0.15s ease;}.${CLASSES.THREAD_EDITED}:hover::after{opacity:1;}.${CLASSES.AUDIT_BLOCK}{margin-top:10px;border-top:1px solid rgba(255,255,255,0.08);padding-top:8px;}.${CLASSES.AUDIT_TOGGLE}{display:flex;align-items:center;gap:6px;width:100%;padding:4px 0;border:0;background:none;color:rgba(255,255,255,0.55);font-family:inherit;font-size:12px;text-align:left;cursor:pointer;}.${CLASSES.AUDIT_TOGGLE}:hover{color:rgba(255,255,255,0.85);}.${CLASSES.AUDIT_TOGGLE}::before{content:\"\";width:0;height:0;border-left:4px solid currentColor;border-top:4px solid transparent;border-bottom:4px solid transparent;transition:transform 0.15s ease;}.${CLASSES.AUDIT_TOGGLE}[aria-expanded=\"true\"]::before{transform:rotate(90deg);}.${CLASSES.AUDIT_BODY}{padding:4px 0 2px;}.${CLASSES.AUDIT_LIST}{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:7px;}.${CLASSES.AUDIT_ROW}{display:grid;grid-template-columns:1fr auto;align-items:baseline;column-gap:8px;font-size:12px;line-height:1.4;}.${CLASSES.AUDIT_ACTION}{grid-column:1;color:rgba(255,255,255,0.78);}.${CLASSES.AUDIT_ACTOR}{grid-column:1;color:rgba(255,255,255,0.48);font-size:11px;}.${CLASSES.AUDIT_TIME}{grid-row:1;grid-column:2;color:rgba(255,255,255,0.42);font-size:11px;white-space:nowrap;}.${CLASSES.AUDIT_HEADING}{margin:0 0 6px;color:rgba(255,255,255,0.5);font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:0.05em;}.${CLASSES.AUDIT_RESOLUTIONS}{margin-top:10px;padding-top:8px;border-top:1px solid rgba(255,255,255,0.06);}.${CLASSES.INBOX_HEADER_ACTIONS}{display:flex;align-items:center;gap:12px;}.${CLASSES.INBOX_METRICS_BTN}{padding:5px 10px;border-radius:7px;border:1px solid rgba(255,255,255,0.12);background:rgba(255,255,255,0.06);color:rgba(255,255,255,0.75);font-family:inherit;font-size:12px;white-space:nowrap;cursor:pointer;}.${CLASSES.INBOX_METRICS_BTN}:hover{background:rgba(255,255,255,0.11);color:#fff;}.${CLASSES.METRICS_VIEW}{flex:1;overflow-y:auto;padding:14px 16px;display:flex;flex-direction:column;gap:16px;scrollbar-width:thin;scrollbar-color:rgba(255,255,255,0.22) transparent;}.${CLASSES.METRICS_VIEW}::-webkit-scrollbar{width:8px;}.${CLASSES.METRICS_VIEW}::-webkit-scrollbar-track{background:transparent;}.${CLASSES.METRICS_VIEW}::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.22);border-radius:4px;}.${CLASSES.METRICS_TILES}{display:grid;grid-template-columns:repeat(auto-fit,minmax(96px,1fr));gap:8px;}.${CLASSES.METRICS_TILE}{display:flex;flex-direction:column;gap:2px;padding:10px;border-radius:9px;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.08);}.${CLASSES.METRICS_TILE_VALUE}{color:#fff;font-size:18px;font-weight:600;line-height:1.1;}.${CLASSES.METRICS_TILE_LABEL}{color:rgba(255,255,255,0.5);font-size:11px;line-height:1.3;}.${CLASSES.METRICS_GROUP}{display:flex;flex-direction:column;gap:7px;}.${CLASSES.METRICS_HEADING}{margin:0;color:rgba(255,255,255,0.5);font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:0.05em;}.${CLASSES.METRICS_ROW}{display:grid;grid-template-columns:76px 1fr 26px;align-items:center;gap:8px;font-size:12px;}.${CLASSES.METRICS_ROW_LABEL}{color:rgba(255,255,255,0.72);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}.${CLASSES.METRICS_TRACK}{height:8px;border-radius:4px;background:rgba(255,255,255,0.07);overflow:hidden;}.${CLASSES.METRICS_BAR}{height:100%;border-radius:4px;background:rgba(255,255,255,0.42);min-width:2px;}.${CLASSES.METRICS_ROW_COUNT}{color:rgba(255,255,255,0.85);font-variant-numeric:tabular-nums;text-align:right;}.${CLASSES.METRICS_CHART}{width:100%;height:96px;display:block;fill:rgba(255,255,255,0.42);}.${CLASSES.METRICS_AXIS}{display:flex;justify-content:space-between;color:rgba(255,255,255,0.42);font-size:10px;font-variant-numeric:tabular-nums;}.${CLASSES.METRICS_EXPORTS}{display:flex;flex-wrap:wrap;gap:6px;padding-top:4px;border-top:1px solid rgba(255,255,255,0.08);}.${CLASSES.METRICS_EXPORT_BTN}{padding:6px 10px;border-radius:7px;border:1px solid rgba(255,255,255,0.12);background:rgba(255,255,255,0.06);color:rgba(255,255,255,0.78);font-family:inherit;font-size:12px;cursor:pointer;}.${CLASSES.METRICS_EXPORT_BTN}:hover{background:rgba(255,255,255,0.12);color:#fff;}.${CLASSES.METRICS_EMPTY}{margin:0;padding:24px 0;color:rgba(255,255,255,0.5);font-size:13px;text-align:center;}.${CLASSES.INBOX_NOTICE}{margin:0 0 10px;padding:9px 11px;border-radius:8px;background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.12);color:rgba(255,255,255,0.75);font-size:13px;line-height:1.4;}`;\n\n/**\n * Styles that must apply to the host page itself (outside the shadow root),\n * because they target elements HellDots doesn't own \u2014 e.g. `document.body`\n * while in comment mode. A shadow root's stylesheet never reaches outside\n * it, so these can't live in `getStyles()`; they're injected separately\n * into `document.head` instead (see `CommentOverlay.injectStyles`).\n */\nexport const getGlobalStyles = () => ` .${CLASSES.COMMENT_CURSOR},.${CLASSES.COMMENT_CURSOR} *{cursor:url('${CURSOR_SVG}') ${CURSOR_HOTSPOT},auto !important;}`;\n\n/**\n * Styles for the printable metrics report. A separate sheet from getStyles()\n * because it dresses a different document \u2014 the report's own frame \u2014 and\n * because paper is white: printing the widget's dark surface would put a\n * black slab through the printer and render the text unreadable in\n * greyscale. Delivered through mountStyles like everything else, so a strict\n * `style-src` cannot blank it.\n */\nexport const getReportStyles = () => ` .report{margin:0;padding:24px;background:#fff;color:#111;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,sans-serif;font-size:12px;line-height:1.45;}.report-title{margin:0 0 4px;font-size:20px;}.report-meta{margin:0 0 2px;color:#555;font-size:11px;}.report-table{width:100%;margin-top:18px;border-collapse:collapse;break-inside:avoid;}.report-table caption{margin-bottom:4px;font-size:12px;font-weight:600;text-align:left;}.report-table th,.report-table td{padding:5px 8px;border:1px solid #d5d5d5;text-align:left;font-weight:400;}.report-table thead th{background:#f2f2f2;font-weight:600;}.report-table td{text-align:right;font-variant-numeric:tabular-nums;}@page{margin:14mm;}`;\n", "// How the widget's CSS reaches the page.\n//\n// Injecting a <style> element is what a strict `style-src` Content Security\n// Policy blocks \u2014 and a blocked stylesheet is not a cosmetic problem here:\n// markers are positioned by CSS, so the widget becomes unusable rather than\n// merely ugly. Constructed stylesheets (`new CSSStyleSheet` + `replaceSync`\n// + `adoptedStyleSheets`) are not subject to style-src, because nothing is\n// parsed from document markup.\n//\n// The <style> path stays as the fallback for platforms without constructed\n// sheets (and for jsdom, where the whole test suite runs).\n\n/**\n * Adopts or injects `css` into `target`.\n *\n * @param {ShadowRoot | Document} target where the styles apply \u2014 a shadow\n * root for the widget's own UI, the document for the few rules that\n * target the host page (the comment-mode cursor on <body>).\n * @param {string} css\n * @param {string} fallbackId id given to the injected <style>, so the\n * fallback path stays inspectable and idempotent.\n * @returns {() => void} detaches exactly what this call mounted\n */\nexport function mountStyles(target, css, fallbackId) {\n const sheet = constructSheet(css, target);\n if (sheet) {\n // Appended, never assigned over: a host app (Lit, or anything else\n // using constructed sheets) adopts onto the document too, and\n // replacing the array would delete its styles.\n target.adoptedStyleSheets = [...(target.adoptedStyleSheets ?? []), sheet];\n return () => {\n target.adoptedStyleSheets = (target.adoptedStyleSheets ?? []).filter(\n (candidate) => candidate !== sheet\n );\n };\n }\n\n // A Document mounts into <head>; a shadow root takes the element itself.\n const parent = /** @type {any} */ (target).head ?? target;\n /** @type {any} */ (parent).querySelector?.(`#${fallbackId}`)?.remove();\n\n const style = document.createElement(\"style\");\n style.id = fallbackId;\n style.textContent = css;\n parent.appendChild(style);\n return () => style.remove();\n}\n\n/**\n * A constructed stylesheet, or null where the platform cannot provide one.\n *\n * Both halves are checked: Safari shipped `CSSStyleSheet` for years without\n * making it constructible, and jsdom constructs sheets happily while not\n * implementing `adoptedStyleSheets` at all \u2014 so a sheet nobody can adopt\n * would silently style nothing.\n */\nfunction constructSheet(css, target) {\n // The constructor has to come from the TARGET's realm, not this module's: a\n // sheet built in one document and adopted into another throws. That only\n // started mattering once the metrics report began mounting into an iframe,\n // which is a different realm from the page that builds it.\n const view =\n /** @type {any} */ (target).defaultView ??\n target.ownerDocument?.defaultView ??\n globalThis;\n const Sheet = view.CSSStyleSheet;\n if (typeof Sheet !== \"function\") return null;\n if (!(\"adoptedStyleSheets\" in target)) return null;\n try {\n const sheet = new Sheet();\n sheet.replaceSync(css);\n return sheet;\n } catch {\n return null;\n }\n}\n", "export default {\n metricsTitle: \"Metrics\",\n metricsOpen: \"Metrics\",\n metricsTotal: \"Total comments\",\n metricsAverageResolution: \"Average resolution\",\n metricsMedianResolution: \"Median resolution\",\n metricsReopened: \"Reopened\",\n metricsByStatus: \"By status\",\n metricsByType: \"By type\",\n metricsByPriority: \"By priority\",\n metricsOverTime: \"Comments per day\",\n metricsEmpty: \"No comments to measure yet\",\n metricsCategory: \"Category\",\n metricsCount: \"Count\",\n metricsDate: \"Date\",\n metricsExportComments: \"Comments (CSV)\",\n metricsExportMetrics: \"Metrics (CSV)\",\n metricsPrint: \"Print / Save as PDF\",\n metricsExportLabel: \"Export\",\n metricsGeneratedTemplate: \"Generated {n}\",\n metricsScope: \"Scope\",\n\n auditToggleTemplate: \"History ({n})\",\n auditTrailLabel: \"Activity history\",\n auditCreated: \"Created the comment\",\n auditEdited: \"Edited the text\",\n auditTagsChanged: \"Updated the tags\",\n auditPreviousResolutions: \"Previous resolutions\",\n auditResolvedInTemplate: \"Resolved in {n}\",\n\n commentAriaLabelPrefix: \"Comment: \",\n anonymous: \"Anonymous\",\n justNow: \"Just now\",\n minutesAgoTemplate: \"{n}m\",\n hoursAgoTemplate: \"{n}h\",\n daysAgoTemplate: \"{n}d\",\n toolbarComment: \"Comment\",\n toolbarInbox: \"Inbox\",\n toolbarHideComments: \"Hide comments\",\n toolbarShowComments: \"Show comments\",\n modifierAlt: \"Alt\",\n modifierCtrl: \"Ctrl\",\n modifierShift: \"Shift\",\n commentBoxAriaLabel: \"New comment\",\n commentPlaceholder: \"Type your comment...\",\n attachImage: \"Attach image\",\n send: \"Send\",\n tooltipAriaLabel: \"Comment preview\",\n close: \"Close\",\n popoverAriaLabel: \"Comment thread\",\n replyPlaceholder: \"Reply...\",\n attachedScreenshot: \"Attached screenshot\",\n screenshotPreview: \"Screenshot preview\",\n removeScreenshot: \"Remove screenshot\",\n capturingScreenshot: \"Capturing\u2026\",\n inboxAriaLabel: \"Comments inbox\",\n inboxEmptyTitle: \"No comments yet\",\n inboxEmptyHintTemplate: \"Press {n} and click anywhere on the page to start.\",\n inboxEmptyAction: \"Turn on comment mode\",\n inboxNoMatches: \"No comments match these filters\",\n orphanedBadge: \"Unanchored\",\n hiddenBadge: \"Hidden\",\n filterAll: \"All pages\",\n filterCurrentPage: \"Current page\",\n filterTitle: \"Filter\",\n filterClear: \"Clear\",\n filterByPage: \"Page\",\n filterByStatus: \"Status\",\n back: \"Back\",\n deleteComment: \"Delete\",\n deleteReply: \"Delete reply\",\n replyOptions: \"Reply options\",\n editComment: \"Edit\",\n editReply: \"Edit reply\",\n copyLink: \"Copy link\",\n linkCopied: \"Link copied\",\n editorAriaLabel: \"Edit text\",\n editSave: \"Save\",\n editCancel: \"Cancel\",\n editedMark: \"edited\",\n editedAtPrefix: \"Edited \",\n confirmDiscardTitle: \"Discard changes?\",\n confirmDiscardMessage:\n \"The changes you made to this text have not been saved and will be lost.\",\n confirmDiscard: \"Discard\",\n confirmKeepEditing: \"Keep editing\",\n commentNotFound: \"That comment is not on this page.\",\n confirmDelete: \"Delete\",\n confirmCancel: \"Cancel\",\n confirmDeleteCommentTitle: \"Delete this comment?\",\n confirmDeleteCommentMessage:\n \"This comment will be permanently deleted. This action cannot be undone.\",\n confirmDeleteThreadMessage:\n \"This comment and all of its replies will be permanently deleted. This action cannot be undone.\",\n confirmDeleteReplyTitle: \"Delete this reply?\",\n confirmDeleteReplyMessage:\n \"This reply will be permanently deleted. This action cannot be undone.\",\n copyAgentContext: \"Copy agent context\",\n copied: \"Copied\",\n statusLabel: \"Status\",\n prevComment: \"Previous comment\",\n nextComment: \"Next comment\",\n replyLink: \"Reply\",\n replyCountOne: \"1 reply\",\n replyCountTemplate: \"{n} replies\",\n commentOptions: \"Comment options\",\n moreOptions: \"More\",\n statusOpen: \"Open\",\n statusInProgress: \"In progress\",\n statusInReview: \"In review\",\n statusResolved: \"Resolved\",\n durationLessThanMinute: \"<1m\",\n resolvedInTemplate: \"Resolved in {n}\",\n typeLabel: \"Type\",\n priorityLabel: \"Priority\",\n unset: \"Unset\",\n typeBug: \"Bug\",\n typeSuggestion: \"Suggestion\",\n typeQuestion: \"Question\",\n typeImprovement: \"Improvement\",\n priorityHigh: \"High\",\n priorityMedium: \"Medium\",\n priorityLow: \"Low\",\n filterByType: \"Type\",\n filterByPriority: \"Priority\",\n contextSection: \"Context\",\n autoScreenshotLabel: \"Automatic context\",\n contextUrl: \"URL\",\n contextViewport: \"Viewport\",\n contextScreen: \"Screen\",\n contextBrowser: \"Browser\",\n contextOs: \"OS\",\n reactionsLabel: \"Reactions\",\n addReaction: \"Add reaction\",\n reactionToggleOn: \"Add your reaction\",\n reactionToggleOff: \"Remove your reaction\",\n reactionPickerLabel: \"Choose a reaction\",\n};\n", "export default {\n metricsTitle: \"M\u00E9tricas\",\n metricsOpen: \"M\u00E9tricas\",\n metricsTotal: \"Comentarios totales\",\n metricsAverageResolution: \"Resoluci\u00F3n media\",\n metricsMedianResolution: \"Resoluci\u00F3n mediana\",\n metricsReopened: \"Reabiertos\",\n metricsByStatus: \"Por estado\",\n metricsByType: \"Por tipo\",\n metricsByPriority: \"Por prioridad\",\n metricsOverTime: \"Comentarios por d\u00EDa\",\n metricsEmpty: \"A\u00FAn no hay comentarios que medir\",\n metricsCategory: \"Categor\u00EDa\",\n metricsCount: \"Cantidad\",\n metricsDate: \"Fecha\",\n metricsExportComments: \"Comentarios (CSV)\",\n metricsExportMetrics: \"M\u00E9tricas (CSV)\",\n metricsPrint: \"Imprimir / Guardar como PDF\",\n metricsExportLabel: \"Exportar\",\n metricsGeneratedTemplate: \"Generado {n}\",\n metricsScope: \"Alcance\",\n\n auditToggleTemplate: \"Historial ({n})\",\n auditTrailLabel: \"Historial de actividad\",\n auditCreated: \"Cre\u00F3 el comentario\",\n auditEdited: \"Edit\u00F3 el texto\",\n auditTagsChanged: \"Actualiz\u00F3 las etiquetas\",\n auditPreviousResolutions: \"Resoluciones anteriores\",\n auditResolvedInTemplate: \"Resuelto en {n}\",\n\n commentAriaLabelPrefix: \"Comentario: \",\n anonymous: \"An\u00F3nimo\",\n justNow: \"Justo ahora\",\n minutesAgoTemplate: \"{n}m\",\n hoursAgoTemplate: \"{n}h\",\n daysAgoTemplate: \"{n}d\",\n toolbarComment: \"Comentar\",\n toolbarInbox: \"Bandeja\",\n toolbarHideComments: \"Ocultar comentarios\",\n toolbarShowComments: \"Mostrar comentarios\",\n modifierAlt: \"Alt\",\n modifierCtrl: \"Ctrl\",\n modifierShift: \"May\u00FAs\",\n commentBoxAriaLabel: \"Nuevo comentario\",\n commentPlaceholder: \"Escribe tu comentario...\",\n attachImage: \"Adjuntar imagen\",\n send: \"Enviar\",\n tooltipAriaLabel: \"Vista previa del comentario\",\n close: \"Cerrar\",\n popoverAriaLabel: \"Hilo de comentarios\",\n replyPlaceholder: \"Responder...\",\n attachedScreenshot: \"Captura de pantalla adjunta\",\n screenshotPreview: \"Vista previa de la captura\",\n removeScreenshot: \"Quitar captura de pantalla\",\n capturingScreenshot: \"Capturando\u2026\",\n inboxAriaLabel: \"Bandeja de comentarios\",\n inboxEmptyTitle: \"Todav\u00EDa no hay comentarios\",\n inboxEmptyHintTemplate:\n \"Pulsa {n} y haz clic en cualquier parte de la p\u00E1gina para empezar.\",\n inboxEmptyAction: \"Activar modo\",\n inboxNoMatches: \"Ning\u00FAn comentario coincide con estos filtros\",\n orphanedBadge: \"Desanclado\",\n hiddenBadge: \"Oculto\",\n filterAll: \"Todas las p\u00E1ginas\",\n filterCurrentPage: \"P\u00E1gina actual\",\n filterTitle: \"Filtrar\",\n filterClear: \"Limpiar\",\n filterByPage: \"P\u00E1gina\",\n filterByStatus: \"Estado\",\n back: \"Volver\",\n deleteComment: \"Eliminar\",\n deleteReply: \"Eliminar respuesta\",\n replyOptions: \"Opciones de la respuesta\",\n editComment: \"Editar\",\n editReply: \"Editar respuesta\",\n copyLink: \"Copiar enlace\",\n linkCopied: \"Enlace copiado\",\n editorAriaLabel: \"Editar texto\",\n editSave: \"Guardar\",\n editCancel: \"Cancelar\",\n editedMark: \"editado\",\n editedAtPrefix: \"Editado el \",\n confirmDiscardTitle: \"\u00BFDescartar los cambios?\",\n confirmDiscardMessage:\n \"Los cambios que hiciste en este texto no se han guardado y se perder\u00E1n.\",\n confirmDiscard: \"Descartar\",\n confirmKeepEditing: \"Seguir editando\",\n commentNotFound: \"Ese comentario no est\u00E1 en esta p\u00E1gina.\",\n confirmDelete: \"Eliminar\",\n confirmCancel: \"Cancelar\",\n confirmDeleteCommentTitle: \"\u00BFEliminar comentario?\",\n confirmDeleteCommentMessage:\n \"Este comentario se eliminar\u00E1 de forma permanente. Esta acci\u00F3n no se puede deshacer.\",\n confirmDeleteThreadMessage:\n \"Este comentario y todas sus respuestas se eliminar\u00E1n de forma permanente. Esta acci\u00F3n no se puede deshacer.\",\n confirmDeleteReplyTitle: \"\u00BFEliminar respuesta?\",\n confirmDeleteReplyMessage:\n \"Esta respuesta se eliminar\u00E1 de forma permanente. Esta acci\u00F3n no se puede deshacer.\",\n copyAgentContext: \"Copiar contexto de agente\",\n copied: \"Copiado\",\n statusLabel: \"Estado\",\n prevComment: \"Comentario anterior\",\n nextComment: \"Comentario siguiente\",\n replyLink: \"Responder\",\n replyCountOne: \"1 respuesta\",\n replyCountTemplate: \"{n} respuestas\",\n commentOptions: \"Opciones del comentario\",\n moreOptions: \"M\u00E1s\",\n statusOpen: \"Abierto\",\n statusInProgress: \"En progreso\",\n statusInReview: \"En revisi\u00F3n\",\n statusResolved: \"Resuelto\",\n durationLessThanMinute: \"<1m\",\n resolvedInTemplate: \"Resuelto en {n}\",\n typeLabel: \"Tipo\",\n priorityLabel: \"Prioridad\",\n unset: \"Sin definir\",\n typeBug: \"Bug\",\n typeSuggestion: \"Sugerencia\",\n typeQuestion: \"Pregunta\",\n typeImprovement: \"Mejora\",\n priorityHigh: \"Alta\",\n priorityMedium: \"Media\",\n priorityLow: \"Baja\",\n filterByType: \"Tipo\",\n filterByPriority: \"Prioridad\",\n contextSection: \"Contexto\",\n autoScreenshotLabel: \"Contexto autom\u00E1tico\",\n contextUrl: \"URL\",\n contextViewport: \"Viewport\",\n contextScreen: \"Pantalla\",\n contextBrowser: \"Navegador\",\n contextOs: \"SO\",\n reactionsLabel: \"Reacciones\",\n addReaction: \"A\u00F1adir reacci\u00F3n\",\n reactionToggleOn: \"A\u00F1adir tu reacci\u00F3n\",\n reactionToggleOff: \"Quitar tu reacci\u00F3n\",\n reactionPickerLabel: \"Elegir una reacci\u00F3n\",\n};\n", "import en from \"./locales/en.js\";\nimport es from \"./locales/es.js\";\n\nconst LOCALES = { en, es };\nconst DEFAULT_LOCALE = \"en\";\n\n/**\n * Picks a supported locale code from the browser's language, falling back\n * to English for anything HellDots doesn't ship a translation for.\n * @returns {\"en\" | \"es\"}\n */\nexport function detectLocale() {\n const lang = (navigator.language || DEFAULT_LOCALE).slice(0, 2).toLowerCase();\n return lang in LOCALES ? /** @type {\"en\" | \"es\"} */ (lang) : DEFAULT_LOCALE;\n}\n\n/**\n * Resolves the UI strings dictionary for a given locale code. An unknown\n * code falls back to English wholesale; a known locale falls back to\n * English PER KEY, so a translation added to en.js but not yet to a sibling\n * locale degrades to English instead of rendering literal \"undefined\".\n * @param {string} [localeCode]\n * @returns {typeof en}\n */\nexport function getStrings(localeCode) {\n const selected = LOCALES[localeCode];\n if (!selected || localeCode === DEFAULT_LOCALE) {\n return LOCALES[DEFAULT_LOCALE];\n }\n return { ...LOCALES[DEFAULT_LOCALE], ...selected };\n}\n\n/**\n * Substitutes `{n}` in a template string, used for relative time labels and\n * resolution-duration badges (where the substitution is already a string,\n * e.g. \"2d 4h\" or the \"\u2014\" fallback).\n * @param {string} template\n * @param {number | string} n\n */\nexport function formatTemplate(template, n) {\n return template.replace(\"{n}\", String(n));\n}\n\nconst MINUTE_MS = 60_000;\n\n/**\n * RF5 \u2014 human-readable elapsed time (\"<1m\", \"45m\", \"3h 12m\", \"2d 4h\").\n * Reuses the same {n}-templates the relative timestamps already use.\n * @param {number} ms\n * @param {ReturnType<typeof getStrings>} strings\n * @returns {string} empty string when `ms` isn't a usable duration\n */\nexport function formatDuration(ms, strings) {\n if (!Number.isFinite(ms) || ms < 0) return \"\";\n\n const totalMinutes = Math.floor(ms / MINUTE_MS);\n if (totalMinutes < 1) return strings.durationLessThanMinute;\n if (totalMinutes < 60) {\n return formatTemplate(strings.minutesAgoTemplate, totalMinutes);\n }\n\n const totalHours = Math.floor(totalMinutes / 60);\n if (totalHours < 24) {\n const minutes = totalMinutes % 60;\n const hours = formatTemplate(strings.hoursAgoTemplate, totalHours);\n return minutes\n ? `${hours} ${formatTemplate(strings.minutesAgoTemplate, minutes)}`\n : hours;\n }\n\n const totalDays = Math.floor(totalHours / 24);\n const hours = totalHours % 24;\n const days = formatTemplate(strings.daysAgoTemplate, totalDays);\n return hours\n ? `${days} ${formatTemplate(strings.hoursAgoTemplate, hours)}`\n : days;\n}\n", "// Serializable comment anchors. An anchor pairs a best-effort CSS selector\n// (fast path) with a content fingerprint (verification + rescue path) so a\n// comment can be re-attached to its element after a page reload. Design:\n// docs/superpowers/specs/2026-07-02-comment-anchoring-design.md\n\nimport { HOST_PAGE_CLASSES } from \"./constants.js\";\n\nconst TEXT_SNIPPET_MAX = 64;\nconst MAX_CLASS_PATH_DEPTH = 3;\nconst MAX_STRUCTURAL_DEPTH = 5;\n\n// A selector match is verified against the fingerprint before being trusted;\n// the rescue path (fingerprint-only, no structural signal) demands more.\nconst SELECTOR_THRESHOLD = 0.6;\nconst RESCUE_THRESHOLD = 0.7;\n\nconst GENERATED_CLASS_PREFIX_RE = /^(css|sc|jsx|emotion)-/i;\nconst FRAMEWORK_DATA_ATTR_RE = /^data-(reactid|react-|v-|svelte-)/;\nconst STABLE_ATTR_NAMES = [\"id\", \"name\", \"role\", \"aria-label\"];\nconst SELECTOR_ATTR_NAMES = [\"data-testid\", \"name\", \"aria-label\"];\n\nconst escapeCss = (value) => {\n if (typeof CSS !== \"undefined\" && CSS.escape) return CSS.escape(value);\n return String(value).replace(/[^a-zA-Z0-9_-]/g, \"\\\\$&\");\n};\n\nconst escapeAttrValue = (value) => String(value).replace(/[\"\\\\]/g, \"\\\\$&\");\n\nconst isUnique = (selector, doc) => {\n try {\n return doc.querySelectorAll(selector).length === 1;\n } catch {\n return false;\n }\n};\n\nconst normalizeText = (text) =>\n (text || \"\").replace(/\\s+/g, \" \").trim().slice(0, TEXT_SNIPPET_MAX);\n\n// Heuristic: tooling-generated class names (CSS-in-JS, scoped-CSS hashes)\n// either carry a known prefix or contain a long token with digits in it.\n// HellDots' own host-page classes are excluded outright \u2014 they are our\n// transient state, not the page's structure, and anchoring to them produces\n// a selector that stops matching as soon as the state clears.\nconst isStableClass = (cls) => {\n if (HOST_PAGE_CLASSES.includes(cls)) return false;\n if (GENERATED_CLASS_PREFIX_RE.test(cls)) return false;\n return !cls.split(/[-_]/).some((part) => part.length >= 5 && /\\d/.test(part));\n};\n\nconst stableClassesOf = (element) =>\n [...element.classList].filter(isStableClass);\n\nconst stableAttributes = (element) => {\n /** @type {Record<string, string>} */\n const attrs = {};\n for (const { name, value } of element.attributes) {\n const isStable =\n STABLE_ATTR_NAMES.includes(name) ||\n (name.startsWith(\"data-\") && !FRAMEWORK_DATA_ATTR_RE.test(name));\n if (isStable && value) attrs[name] = value.slice(0, TEXT_SNIPPET_MAX);\n }\n return attrs;\n};\n\nconst siblingPosition = (element) => {\n const parent = element.parentElement;\n if (!parent) return { index: 0, count: 1 };\n const sameTag = [...parent.children].filter(\n (child) => child.tagName === element.tagName\n );\n return { index: sameTag.indexOf(element), count: sameTag.length };\n};\n\nconst idSelector = (element, doc) => {\n if (!element.id) return null;\n const selector = `#${escapeCss(element.id)}`;\n return isUnique(selector, doc) ? selector : null;\n};\n\nconst attributeSelector = (element, doc) => {\n const tag = element.tagName.toLowerCase();\n for (const name of SELECTOR_ATTR_NAMES) {\n const value = element.getAttribute(name);\n if (!value) continue;\n const selector = `${tag}[${name}=\"${escapeAttrValue(value)}\"]`;\n if (isUnique(selector, doc)) return selector;\n }\n return null;\n};\n\nconst classPathSelector = (element, doc) => {\n const segments = [];\n let current = element;\n for (let depth = 0; depth < MAX_CLASS_PATH_DEPTH && current; depth++) {\n const classes = stableClassesOf(current);\n const tag = current.tagName.toLowerCase();\n segments.unshift(\n classes.length ? `${tag}.${classes.map(escapeCss).join(\".\")}` : tag\n );\n // The element's own segment must carry at least one stable class for\n // this strategy to say anything a structural path wouldn't.\n if (depth === 0 && !classes.length) return null;\n const selector = segments.join(\" > \");\n if (isUnique(selector, doc)) return selector;\n current = current.parentElement;\n }\n return null;\n};\n\nconst structuralSelector = (element, doc) => {\n if (element === doc.body) return \"body\";\n const segments = [];\n let current = element;\n for (let depth = 0; depth < MAX_STRUCTURAL_DEPTH && current; depth++) {\n if (current === doc.body) {\n segments.unshift(\"body\");\n break;\n }\n if (current.id) {\n const rooted = [`#${escapeCss(current.id)}`, ...segments].join(\" > \");\n if (isUnique(rooted, doc)) return rooted;\n }\n const { index } = siblingPosition(current);\n segments.unshift(\n `${current.tagName.toLowerCase()}:nth-of-type(${index + 1})`\n );\n current = current.parentElement;\n }\n const selector = segments.join(\" > \");\n return isUnique(selector, doc) ? selector : null;\n};\n\nconst generateSelector = (element, doc) =>\n idSelector(element, doc) ||\n attributeSelector(element, doc) ||\n classPathSelector(element, doc) ||\n structuralSelector(element, doc);\n\n/**\n * Best-effort unique CSS selector for any element (or null). Exposed for\n * the overlay's target-visibility tracking; same cascade used by anchors.\n * @param {HTMLElement} element\n * @returns {string | null}\n */\nexport function generateElementSelector(element) {\n return generateSelector(element, element.ownerDocument);\n}\n\n/**\n * Captures a serializable anchor for `element` at creation time.\n * @param {HTMLElement} element\n * @param {number} relativeX\n * @param {number} relativeY\n * @returns {import('./index.d.ts').CommentAnchor}\n */\nexport function createAnchor(element, relativeX, relativeY) {\n const doc = element.ownerDocument;\n const { index, count } = siblingPosition(element);\n return {\n version: 1,\n selector: generateSelector(element, doc),\n fingerprint: {\n tagName: element.tagName,\n textSnippet: normalizeText(element.textContent),\n attributes: stableAttributes(element),\n siblingIndex: index,\n siblingCount: count,\n },\n relativeX,\n relativeY,\n };\n}\n\nconst textSimilarity = (a, b) => {\n if (!a && !b) return 1;\n if (!a || !b) return 0;\n if (a === b) return 1;\n if (a.startsWith(b) || b.startsWith(a)) return 0.8;\n const tokensA = new Set(a.split(\" \"));\n const tokensB = new Set(b.split(\" \"));\n let common = 0;\n for (const token of tokensA) if (tokensB.has(token)) common++;\n return (2 * common) / (tokensA.size + tokensB.size);\n};\n\nconst attributeSimilarity = (element, attrs) => {\n const names = Object.keys(attrs);\n if (!names.length) return 1;\n let matched = 0;\n for (const name of names) {\n if (\n (element.getAttribute(name) || \"\").slice(0, TEXT_SNIPPET_MAX) ===\n attrs[name]\n ) {\n matched++;\n }\n }\n return matched / names.length;\n};\n\nconst positionSimilarity = (element, fingerprint) => {\n const { index, count } = siblingPosition(element);\n const delta = Math.abs(index - fingerprint.siblingIndex);\n const span = Math.max(fingerprint.siblingCount, count, 1);\n return Math.max(0, 1 - delta / span);\n};\n\nconst scoreElement = (element, fingerprint) => {\n if (element.tagName !== fingerprint.tagName) return 0;\n\n const hasText = Boolean(fingerprint.textSnippet);\n const hasAttrs = Object.keys(fingerprint.attributes || {}).length > 0;\n\n // Base weights (text 0.5 / attrs 0.3 / position 0.2); a missing signal's\n // weight shifts to the other content signal so the scale stays 0\u20131.\n let textWeight = 0.5;\n let attrWeight = 0.3;\n const posWeight = 0.2;\n if (!hasAttrs) {\n textWeight += attrWeight;\n attrWeight = 0;\n } else if (!hasText) {\n attrWeight += textWeight;\n textWeight = 0;\n }\n\n // Degenerate fingerprint: only structural signal remains.\n if (!hasText && !hasAttrs) {\n return positionSimilarity(element, fingerprint);\n }\n\n return (\n textWeight *\n textSimilarity(\n normalizeText(element.textContent),\n fingerprint.textSnippet\n ) +\n attrWeight * attributeSimilarity(element, fingerprint.attributes || {}) +\n posWeight * positionSimilarity(element, fingerprint)\n );\n};\n\nconst bestMatch = (candidates, fingerprint) => {\n let best = null;\n for (const element of candidates) {\n const confidence = scoreElement(element, fingerprint);\n if (\n !best ||\n confidence > best.confidence ||\n // On an exact tie prefer the deepest candidate. Document order yields\n // ancestors first, and with the text snippet truncated to 64 chars a\n // parent and its child tie systematically \u2014 the most specific element\n // that scores the same is the better anchor (the selector cascade's\n // intuition). Ties between unrelated elements keep document order.\n (confidence === best.confidence && best.element.contains(element))\n ) {\n best = { element, confidence };\n }\n }\n return best;\n};\n\n/**\n * Re-locates the element an anchor points at, or null if no candidate is\n * trustworthy (orphaned comment). Never throws.\n * @param {import('./index.d.ts').CommentAnchor} anchor\n * @param {Document} [doc]\n * @returns {{ element: HTMLElement, confidence: number } | null}\n */\nexport function resolveAnchor(anchor, doc = document) {\n // An anchor written by a newer schema than this code understands is\n // treated as orphaned rather than half-interpreted: the comment stays\n // listed but is never positioned over a guessed element.\n if (anchor?.version != null && anchor.version > 1) return null;\n\n const fingerprint = anchor?.fingerprint;\n if (!fingerprint || !fingerprint.tagName) return null;\n\n if (anchor.selector) {\n let candidates = [];\n try {\n candidates = [...doc.querySelectorAll(anchor.selector)];\n } catch {\n // Corrupt selector \u2014 the rescue search below still applies.\n }\n const best = bestMatch(candidates, fingerprint);\n if (best && best.confidence >= SELECTOR_THRESHOLD) return best;\n }\n\n // Rescue search: only meaningful when the fingerprint carries content\n // signal \u2014 anonymous elements would make any tag-wide match a guess.\n const hasSignal =\n Boolean(fingerprint.textSnippet) ||\n Object.keys(fingerprint.attributes || {}).length > 0;\n if (!hasSignal) return null;\n\n let candidates;\n try {\n candidates = [...doc.querySelectorAll(fingerprint.tagName)];\n } catch {\n return null;\n }\n const best = bestMatch(candidates, fingerprint);\n return best && best.confidence >= RESCUE_THRESHOLD ? best : null;\n}\n", "// localStorage adapter for the optional `persistence: \"localStorage\"` mode.\n// One key holds the serialized comments of EVERY page (the inbox \"all\n// comments\" filter needs them); merge logic keeps other pages' entries\n// intact while treating in-memory state as the source of truth for ids it\n// knows about. Storage failures (quota, disabled, corrupt JSON) never\n// throw \u2014 the widget just runs without persistence.\n\nexport const STORAGE_KEY = \"helldots-comments\";\n\n// sessionStorage handoff: set right before navigating to another page so\n// the overlay there opens the inbox directly on that comment's detail.\nexport const PENDING_DETAIL_KEY = \"helldots-pending-detail\";\n\n/**\n * @returns {import('./index.d.ts').SerializedComment[]}\n */\nexport function readStoredComments() {\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 (err) {\n console.warn(\"HellDots: could not read stored comments\", err);\n return [];\n }\n}\n\nfunction tryWriteStoredComments(comments) {\n try {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(comments));\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Writes the full cross-page corpus. Each comment can carry a base64 JPEG\n * `contextScreenshot` (~33KB) captured automatically (RF1/RF2); a growing\n * corpus of those eventually blows through the ~5MB localStorage quota. On\n * a failed write, the automatic screenshots are the only thing sacrificed \u2014\n * dropped one at a time starting with the oldest comment \u2014 so the comments\n * themselves (and any deliberate, user-attached `screenshots[]`) survive.\n * Never throws: a hostile or disabled localStorage just means no\n * persistence, not a broken widget.\n * @param {import('./index.d.ts').SerializedComment[]} comments\n * @returns {boolean} true once the write succeeded, possibly after shedding\n * automatic screenshots; false if it still failed with nothing left to shed\n */\nexport function writeStoredComments(comments) {\n if (tryWriteStoredComments(comments)) return true;\n\n // Oldest first (by createdAt; ties/missing dates fall back to array\n // order), and only entries that actually have something to shed.\n const shedOrder = comments\n .map((comment, index) => ({ comment, index }))\n .filter(({ comment }) => comment?.contextScreenshot)\n .sort((a, b) => {\n const timeA = Date.parse(a.comment.createdAt);\n const timeB = Date.parse(b.comment.createdAt);\n if (Number.isFinite(timeA) && Number.isFinite(timeB) && timeA !== timeB) {\n return timeA - timeB;\n }\n return a.index - b.index;\n });\n\n if (shedOrder.length === 0) {\n console.warn(\n \"HellDots: could not persist comments (storage quota exceeded, nothing left to shed)\"\n );\n return false;\n }\n\n const working = [...comments];\n let shed = 0;\n for (const { index } of shedOrder) {\n working[index] = { ...working[index], contextScreenshot: null };\n shed++;\n if (tryWriteStoredComments(working)) {\n console.warn(\n `HellDots: localStorage quota exceeded \u2014 dropped the automatic ` +\n `context screenshot from the ${shed} oldest comment(s) to keep ` +\n `all comments persisted. Comment text, replies and user-attached ` +\n `screenshots were not touched.`\n );\n return true;\n }\n }\n\n console.warn(\n `HellDots: could not persist comments even after dropping all ${shed} ` +\n `automatic context screenshot(s); storage quota exceeded`\n );\n return false;\n}\n\n/**\n * Merges the in-memory snapshot into what's already stored: entries the\n * memory knows about (by id) and entries of the current page are replaced\n * by the snapshot; entries from other pages are preserved.\n * @param {import('./index.d.ts').SerializedComment[]} stored\n * @param {import('./index.d.ts').SerializedComment[]} current\n * @param {string} currentPage\n * @returns {import('./index.d.ts').SerializedComment[]}\n */\nexport function mergeForStorage(stored, current, currentPage) {\n // Ids are compared on their string form (see id.js) \u2014 a numeric legacy id\n // and its string spelling are the same comment, never two entries.\n const currentIds = new Set(current.map((c) => String(c.id)));\n const kept = stored.filter(\n (c) => !currentIds.has(String(c.id)) && c.page !== currentPage\n );\n return [...kept, ...current];\n}\n", "export let urlAlphabet =\n 'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict'\n", "\n\nimport { urlAlphabet } from './url-alphabet/index.js'\n\nexport { urlAlphabet }\n\nexport let random = bytes => crypto.getRandomValues(new Uint8Array(bytes))\n\nexport let customRandom = (alphabet, defaultSize, getRandom) => {\n let safeByteCutoff = 256 - (256 % alphabet.length)\n\n if (safeByteCutoff === 256) {\n let mask = alphabet.length - 1\n\n return (size = defaultSize) => {\n if (!size) return ''\n let id = ''\n while (true) {\n let bytes = getRandom(size)\n let j = size\n while (j--) {\n id += alphabet[bytes[j] & mask]\n if (id.length >= size) return id\n }\n }\n }\n }\n\n let step = Math.ceil((1.6 * 256 * defaultSize) / safeByteCutoff)\n\n return (size = defaultSize) => {\n if (!size) return ''\n let id = ''\n while (true) {\n let bytes = getRandom(step)\n let j = step\n while (j--) {\n if (bytes[j] < safeByteCutoff) {\n id += alphabet[bytes[j] % alphabet.length]\n if (id.length >= size) return id\n }\n }\n }\n }\n}\n\nexport let customAlphabet = (alphabet, size = 21) =>\n customRandom(alphabet, size | 0, random)\n\nexport let nanoid = (size = 21) => {\n let id = ''\n let bytes = crypto.getRandomValues(new Uint8Array((size |= 0)))\n while (size--) {\n id += urlAlphabet[bytes[size] & 63]\n }\n return id\n}\n", "// The single source of ids for comments and replies.\n//\n// This used to be `Date.now()`, which is not an id \u2014 it is a timestamp that\n// usually happens not to repeat. Two things it was already breaking:\n// `mergeForStorage` deduplicates by id, and every lookup is a `find()` that\n// returns the first match, so a collision means one comment silently\n// overwrites another. Two people commenting on the same millisecond from\n// different machines, or any programmatic import, is enough to hit it.\n//\n// nanoid gives ~126 bits from a 64-symbol URL-safe alphabet in 21 chars \u2014\n// stronger than a UUIDv4's 122 bits, and short enough to sit in the\n// `?helldotsComment=` link without looking like a mistake. It reads its\n// randomness from `crypto.getRandomValues`, which \u2014 unlike\n// `crypto.randomUUID` \u2014 is NOT restricted to secure contexts, so a widget\n// dropped into a dev server on plain http://192.168.x.x still works.\n//\n// It is a devDependency bundled into both artifacts rather than a runtime\n// dependency: nanoid 6 requires Node 22+, and this package promises >=18.\n// Bundling keeps that promise honest for hosts importing us under SSR, and\n// costs ~516 B gzip against a 50 KB budget.\nimport { nanoid } from \"nanoid\";\n\n/**\n * @returns {string} a fresh id for a comment or a reply\n */\nexport const createId = () => nanoid();\n\n/**\n * The one way to read an author's identifier off whatever the host declared.\n *\n * It lands in four places \u2014 `comment.authorId`, `reply.authorId`, every audit\n * entry's `actor.id`, and the key a reaction is stored under \u2014 and a host\n * joining its own records against any two of them has to get the same string\n * back. Each of those sites used to normalise it its own way, so one padded\n * or over-long id arrived in three different spellings inside one payload.\n *\n * Trimmed but never truncated. A clipped display name is ugly; a clipped id\n * is wrong in silence, because two ids sharing a prefix collapse into one\n * person \u2014 and this is the only field anything can be reconciled on. What the\n * id means, how long it is and whether it is safe to store is the host's\n * call: HellDots treats it as opaque.\n *\n * @param {unknown} value\n * @returns {string} the trimmed id, or \"\" when there is not one\n */\nexport const normalizeActorId = (value) =>\n typeof value === \"string\" ? value.trim() : \"\";\n\n/**\n * The one way to compare ids. New ids are strings, but Date.now()-era\n * records still hold numbers, and either spelling may have crossed a JSON\n * or URL boundary since \u2014 so equality is defined on the string form, as\n * `index.d.ts` promises callers.\n *\n * @param {string | number} a\n * @param {string | number} b\n * @returns {boolean}\n */\nexport const sameId = (a, b) => String(a) === String(b);\n", "// Shared open/close rule for every dropdown in the widget: the status, type\n// and priority pickers, the \u22EF menu and the inbox filter.\n//\n// Each menu used to own its state in isolation *and* stop the click from\n// propagating, so nothing could ever close one except its own button. Opening\n// a second picker left the first hanging open (three menus could overlap at\n// once), and clicking elsewhere inside the panel closed none of them.\n//\n// The registry gives them one rule instead: at most one menu open, and any\n// mousedown outside the open menu closes it. The outside listener is on\n// `document` in the CAPTURE phase precisely because the toggles call\n// stopPropagation() \u2014 a bubble-phase listener would never hear the click.\n\nimport { CLASSES } from \"./constants.js\";\n\n/**\n * @typedef {{ button: HTMLElement, menu: HTMLElement, close: () => void }} MenuEntry\n */\n\n/** @type {Set<MenuEntry>} */\nconst openMenus = new Set();\n\n/** @type {((e: MouseEvent) => void) | null} */\nlet outsideListener = null;\n\n/** @type {((e: KeyboardEvent) => void) | null} */\nlet keyListener = null;\n\nconst menuItems = (menu) => [\n ...menu.querySelectorAll('[role=\"menuitem\"], [role=\"menuitemradio\"]'),\n];\n\n/** Vertical gap between a menu and the button it belongs to (matches the CSS). */\nconst MENU_GAP = 4;\n\n/**\n * The nearest ancestor that would clip the menu, or null when only the\n * viewport bounds it. Anything with a non-visible overflow clips: the thread's\n * scroll container and the inbox list are the two that matter, and both are\n * exactly where a dropdown near the bottom edge used to become unreachable.\n *\n * Walking up stops at the shadow root, whose overflow is not ours to read.\n *\n * @param {HTMLElement} menu\n * @returns {DOMRect | null}\n */\nconst clipperRectOf = (menu) => {\n for (let el = menu.parentElement; el; el = el.parentElement) {\n const { overflowX, overflowY } = getComputedStyle(el);\n if (overflowX !== \"visible\" || overflowY !== \"visible\") {\n return el.getBoundingClientRect();\n }\n }\n return null;\n};\n\n/**\n * Keeps the menu inside whatever clips it, on both axes.\n *\n * Vertically it opens upward when it would otherwise be clipped below \u2014 and\n * only when it actually fits up there. Flipping a menu taller than its\n * container would just clip the other end while also reversing the position\n * the user reaches for, so in that case it stays put.\n *\n * Horizontally the same rule, mirrored: the menus hang off their button's\n * right edge, which is what the tools at the end of the action strip want,\n * but the status picker is the strip's *first* control, so its 130px menu\n * reached ~45px past the left edge of the inbox panel \u2014 where the panel's\n * `overflow: hidden` cut it in half. It aligns to the button's left edge\n * instead, and only when the menu fits that way.\n *\n * The overflow test reads the menu's measured position rather than deriving\n * it from the button: which edge a menu hangs off is a CSS decision that\n * differs per surface (the reaction palette opens leftward only inside the\n * tools group), and a rule that assumed one of them would misjudge the other.\n *\n * Measured on every open, never cached: a menu on a row that scrolled since\n * last time has different room than it did.\n *\n * @param {HTMLElement} button\n * @param {HTMLElement} menu the menu, already displayed and placed downward\n */\nconst placeMenu = (button, menu) => {\n menu.classList.remove(CLASSES.INBOX_MENU_UP);\n menu.classList.remove(CLASSES.INBOX_MENU_START);\n\n const clipper = clipperRectOf(menu);\n const floor = Math.min(clipper?.bottom ?? Infinity, window.innerHeight);\n const ceiling = Math.max(clipper?.top ?? 0, 0);\n const leftWall = Math.max(clipper?.left ?? 0, 0);\n const rightWall = Math.min(clipper?.right ?? Infinity, window.innerWidth);\n\n const { height, width, left } = menu.getBoundingClientRect();\n const anchor = button.getBoundingClientRect();\n const bottomIfDown = anchor.bottom + MENU_GAP + height;\n const topIfUp = anchor.top - MENU_GAP - height;\n\n if (bottomIfDown > floor && topIfUp >= ceiling) {\n menu.classList.add(CLASSES.INBOX_MENU_UP);\n }\n\n if (left < leftWall && anchor.left + width <= rightWall) {\n menu.classList.add(CLASSES.INBOX_MENU_START);\n }\n};\n\n// Menus live inside the shadow root, so `e.target` on a document listener is\n// retargeted to the host. composedPath() is the only way to see the element\n// that was really clicked.\nconst eventHits = (el, e) => {\n const path = typeof e.composedPath === \"function\" ? e.composedPath() : [];\n return path.includes(el) || el.contains(/** @type {Node} */ (e.target));\n};\n\nconst startWatching = () => {\n if (outsideListener) return;\n outsideListener = (e) => {\n for (const entry of [...openMenus]) {\n if (eventHits(entry.menu, e) || eventHits(entry.button, e)) continue;\n // A menu detached by a re-render is never in the click path, so this\n // also drops stale entries \u2014 which is what eventually releases this\n // very listener.\n entry.close();\n }\n };\n document.addEventListener(\"mousedown\", outsideListener, true);\n\n // role=\"menu\"/\"menuitem\" promises keyboard behavior (ARIA menu pattern):\n // Escape closes THIS layer only \u2014 never the popover behind it, which is\n // why the listener runs in the capture phase and stops propagation \u2014 and\n // the arrow keys walk the items. Registered while a menu is open, exactly\n // like the mousedown watcher.\n keyListener = (e) => {\n const entry = [...openMenus].pop();\n if (!entry) return;\n\n if (e.key === \"Escape\") {\n e.preventDefault();\n e.stopPropagation();\n entry.close();\n entry.button.focus();\n return;\n }\n\n if ([\"ArrowDown\", \"ArrowUp\", \"Home\", \"End\"].includes(e.key)) {\n const items = menuItems(entry.menu);\n if (items.length === 0) return;\n e.preventDefault();\n e.stopPropagation();\n // Menus live in a shadow root, where document.activeElement reports\n // the host \u2014 the root's own activeElement is the real one.\n const root = /** @type {Document | ShadowRoot} */ (\n entry.menu.getRootNode()\n );\n const index = items.indexOf(root.activeElement);\n let next;\n if (e.key === \"Home\") next = 0;\n else if (e.key === \"End\") next = items.length - 1;\n else if (index === -1)\n next = e.key === \"ArrowDown\" ? 0 : items.length - 1;\n else {\n const step = e.key === \"ArrowDown\" ? 1 : -1;\n next = (index + step + items.length) % items.length;\n }\n items[next].focus();\n }\n };\n document.addEventListener(\"keydown\", keyListener, true);\n};\n\nconst stopWatching = () => {\n if (!outsideListener) return;\n document.removeEventListener(\"mousedown\", outsideListener, true);\n outsideListener = null;\n document.removeEventListener(\"keydown\", keyListener, true);\n keyListener = null;\n};\n\n/** Closes every open menu. Safe to call when none are open. */\nexport const closeOpenMenus = () => {\n for (const entry of [...openMenus]) entry.close();\n};\n\n/**\n * Wires a button to its dropdown so it participates in the single-open rule.\n * The menu's `display` stays the source of truth, so callers that hide it by\n * hand stay consistent with the registry.\n *\n * @param {HTMLElement} button\n * @param {HTMLElement} menu\n * @returns {{ open: () => void, close: () => void, isOpen: () => boolean }}\n */\nexport const attachMenuToggle = (button, menu) => {\n /** @type {MenuEntry} */\n const entry = {\n button,\n menu,\n close: () => {\n menu.style.display = \"none\";\n button.setAttribute(\"aria-expanded\", \"false\");\n openMenus.delete(entry);\n if (openMenus.size === 0) stopWatching();\n },\n };\n\n const open = () => {\n closeOpenMenus();\n menu.style.display = \"block\";\n // Displayed before measuring: a menu still at display:none has no box to\n // measure, so its height would read as zero and it would never flip.\n placeMenu(button, menu);\n button.setAttribute(\"aria-expanded\", \"true\");\n openMenus.add(entry);\n startWatching();\n };\n\n const isOpen = () => menu.style.display !== \"none\";\n\n menu.style.display = \"none\";\n button.setAttribute(\"aria-expanded\", \"false\");\n\n button.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n // Read the state off the DOM, not off the set: a menu hidden directly by\n // a caller (or detached by a re-render) would otherwise be stuck\n // \"open\" in the registry and refuse to reopen.\n const wasOpen = isOpen();\n closeOpenMenus();\n if (!wasOpen) open();\n });\n\n return { open, close: entry.close, isOpen };\n};\n", "// Emoji reactions on comments and replies. One module holds the identity\n// resolver, the reaction-map helpers and the bar component, because all three\n// have to agree on what \"mine\" means \u2014 split across files, the toggle and the\n// render drift apart and pills stop matching the actor who owns them.\n\nimport { CLASSES, REACTION_EMOJIS } from \"./constants.js\";\nimport { attachMenuToggle } from \"./menus.js\";\nimport { normalizeActorId } from \"./id.js\";\n\n/**\n * The key a reaction is stored under.\n *\n * One resolver, used by both the toggle and the \"this one is mine\" render:\n * resolved separately in two places, a host that swaps `user` at runtime\n * would paint pills nobody can switch off.\n *\n * `id` is identity only and is never rendered \u2014 the display name stays what\n * gets shown as an author.\n *\n * @param {{ name?: string, id?: string } | undefined} user\n * @param {{ anonymous: string }} strings\n * @returns {string}\n */\nexport const actorKeyOf = (user, strings) =>\n normalizeActorId(user?.id) || user?.name || strings.anonymous;\n\n/**\n * Reads a reaction map into a stable, ordered list, dropping emoji nobody\n * holds any more. The author arrays are copied: a caller sorting or pushing\n * into what it got back must not reach into stored state.\n *\n * @param {{ reactions?: Record<string, string[]> } | undefined} target\n * @returns {Array<{ emoji: string, authors: string[] }>}\n */\nexport const reactionEntriesOf = (target) => {\n const map = target?.reactions;\n if (!map || typeof map !== \"object\") return [];\n return REACTION_EMOJIS.filter((emoji) => map[emoji]?.length > 0).map(\n (emoji) => ({ emoji, authors: [...map[emoji]] })\n );\n};\n\n/**\n * Persisted reactions arrive from localStorage or from the host's backend, so\n * nothing about their shape can be trusted: an unknown glyph would render a\n * pill this build cannot toggle, and a duplicated actor key would inflate a\n * count that looks like consensus. Returns null when there is nothing worth\n * keeping, so callers leave the field absent instead of storing `{}`.\n *\n * @param {unknown} raw\n * @returns {Record<string, string[]> | null}\n */\nexport const normalizeReactions = (raw) => {\n if (!raw || typeof raw !== \"object\") return null;\n const out = /** @type {Record<string, string[]>} */ ({});\n for (const emoji of REACTION_EMOJIS) {\n const authors = raw[emoji];\n if (!Array.isArray(authors)) continue;\n const kept = [];\n for (const author of authors) {\n if (typeof author !== \"string\") continue;\n const clean = author.trim();\n if (clean && !kept.includes(clean)) kept.push(clean);\n }\n if (kept.length > 0) out[emoji] = kept;\n }\n return Object.keys(out).length > 0 ? out : null;\n};\n\n/**\n * Flips one actor's reaction on a comment or a reply, in place, and reports\n * whether anything changed so the caller decides what to persist and emit.\n *\n * @param {{ reactions?: Record<string, string[]> }} target\n * @param {string} emoji\n * @param {string} actorKey\n * @returns {boolean} false when the emoji is not in the set, or there is no\n * actor to attribute the reaction to\n */\nexport const toggleReactionOn = (target, emoji, actorKey) => {\n if (!REACTION_EMOJIS.includes(emoji) || !actorKey) return false;\n if (!target.reactions) target.reactions = {};\n const authors = target.reactions[emoji] || [];\n const index = authors.indexOf(actorKey);\n if (index >= 0) authors.splice(index, 1);\n else authors.push(actorKey);\n // Never store an empty array: absent and \"nobody\" are the same state, and\n // keeping one spelling of it is what lets the serializer omit the field.\n if (authors.length > 0) target.reactions[emoji] = authors;\n else delete target.reactions[emoji];\n return true;\n};\n\n/**\n * Serializer half. Copied rather than referenced, like `tags`: a host mutating\n * serializeComments() output must not be able to reach back into overlay\n * internals. Null (not `{}`) when there is nothing, so a corpus nobody reacted\n * to costs no bytes.\n *\n * @param {Record<string, string[]> | undefined | null} reactions\n * @returns {Record<string, string[]> | null}\n */\nexport const serializeReactions = (reactions) => {\n const entries = Object.entries(reactions || {}).filter(\n ([, authors]) => authors?.length > 0\n );\n return entries.length > 0\n ? Object.fromEntries(\n entries.map(([emoji, authors]) => [emoji, [...authors]])\n )\n : null;\n};\n\n// The \"add reaction\" affordance, in both places it appears: the action row at\n// the top of a comment (or a reply's meta line) and, once something has been\n// reacted to, at the end of the pill row. A smiley with a plus rather than a\n// bare plus \u2014 the bar sits among pills that are already emoji, and a lone \"+\"\n// there read as \"add something\", not \"add a reaction\".\nconst EMOJI_ICON_SVG = `<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M20.94 11.08A9 9 0 1 1 12.92 3.06\"/><path d=\"M8.5 14.2a4.6 4.6 0 0 0 7 0\"/><path d=\"M9 9.5h.01M15 9.5h.01\"/><path d=\"M19 2.6v4M17 4.6h4\"/></svg>`;\n\n/**\n * @typedef {Object} ReactionsUi\n * @property {(target: any) => HTMLElement} bar the pill row for one target\n * @property {(target: any, config: { className: string, tooltip?: boolean }) => HTMLElement} trigger\n * a button that opens the emoji palette\n * @property {(target: any) => void} refresh repaint the target's live rows\n */\n\n/**\n * A reaction UI bound to one thread: it hands out the palette buttons and the\n * pill rows, and keeps every row it created for a given target in step.\n *\n * It exists because a reaction can now be added from a control that does not\n * own the row it changes \u2014 the action row's button sits above the pills, and\n * on a comment those two are built by different modules (the popover header is\n * assembled after `createThreadPopover` returns). Rather than thread refresh\n * handles through both, each row registers itself here and any pick repaints\n * whatever rows for that target are still on screen.\n *\n * `actorKey` is a getter, not a value: a host may swap `user` while the widget\n * is mounted, and a key captured at build time would leave pills nobody can\n * switch off.\n *\n * @param {{\n * actorKey: () => string,\n * strings: Record<string, string>,\n * onToggle: (target: any, emoji: string) => void,\n * }} config\n * @returns {ReactionsUi}\n */\nexport const createReactionsUi = ({ actorKey, strings, onToggle }) => {\n /**\n * target \u2192 the repaints of its live rows. A WeakMap so entries die with the\n * comments they belong to; detached rows are dropped on the next repaint,\n * since the inbox rebuilds its detail view on every refresh.\n * @type {WeakMap<object, Set<{ el: HTMLElement, repaint: () => void }>>}\n */\n const rows = new WeakMap();\n\n const refresh = (target) => {\n const set = rows.get(target);\n if (!set) return;\n for (const entry of set) {\n if (entry.el.isConnected) entry.repaint();\n else set.delete(entry);\n }\n };\n\n const pick = (target, emoji) => {\n onToggle(target, emoji);\n refresh(target);\n };\n\n /**\n * A button that opens the emoji palette. `className` decides which of the\n * two looks it takes: the action row's icon button or the pill row's\n * trailing one.\n * @param {any} target\n * @param {{ className: string, tooltip?: boolean }} config\n * @returns {HTMLElement} a positioned wrapper holding the button and its menu\n */\n const trigger = (target, { className, tooltip = true }) => {\n const wrapper = document.createElement(\"div\");\n wrapper.className = CLASSES.REACTION_TRIGGER;\n\n const btn = document.createElement(\"button\");\n btn.type = \"button\";\n btn.className = className;\n btn.dataset.action = \"react\";\n if (tooltip) btn.dataset.hdTooltip = strings.addReaction;\n btn.setAttribute(\"aria-label\", strings.addReaction);\n btn.innerHTML = EMOJI_ICON_SVG;\n\n const palette = document.createElement(\"div\");\n palette.className = CLASSES.REACTION_PALETTE;\n palette.setAttribute(\"role\", \"menu\");\n palette.setAttribute(\"aria-label\", strings.reactionPickerLabel);\n\n // The same helper every other dropdown uses, so the palette inherits the\n // single-open rule, aria-expanded, the upward flip when it would be\n // clipped, and outside-click close \u2014 and the resolved-card dim, which\n // keys off a generic [aria-expanded=\"true\"], then covers it for free.\n const toggle = attachMenuToggle(btn, palette);\n\n for (const emoji of REACTION_EMOJIS) {\n const item = document.createElement(\"button\");\n item.type = \"button\";\n item.className = CLASSES.REACTION_PALETTE_ITEM;\n item.setAttribute(\"role\", \"menuitem\");\n item.dataset.reactionEmoji = emoji;\n item.textContent = emoji;\n item.setAttribute(\"aria-label\", emoji);\n item.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n // Closed before mutating: the inbox detail rebuilds itself on every\n // refresh, and a palette still open through that rebuild is left\n // orphaned mid-click.\n toggle.close();\n pick(target, emoji);\n });\n palette.appendChild(item);\n }\n\n wrapper.appendChild(btn);\n wrapper.appendChild(palette);\n return wrapper;\n };\n\n /**\n * The pill row for one target. Always returns an element \u2014 hidden while\n * nothing has been reacted to, because the first reaction arrives from a\n * control outside this row and the row has to be there to receive it.\n *\n * A pill's accessible name is composed from the action, the emoji and the\n * count (\"Remove your reaction: \uD83D\uDC4D (3)\") rather than translated as a\n * sentence: `formatTemplate` has no plural forms, and the repo already\n * decided against pluralising counts in copy. Stored actor keys never reach\n * the UI \u2014 they are display names when the host passes no `user.id` and\n * opaque ids when it does, so rendering them would be inconsistent at best.\n *\n * @param {any} target\n * @returns {HTMLElement}\n */\n const bar = (target) => {\n const el = document.createElement(\"div\");\n el.className = CLASSES.REACTION_BAR;\n el.setAttribute(\"role\", \"group\");\n el.setAttribute(\"aria-label\", strings.reactionsLabel);\n\n const repaint = () => {\n el.replaceChildren();\n const entries = reactionEntriesOf(target);\n const me = actorKey();\n // Nothing reacted to means no row at all: the only way in is the\n // trigger in the action row above.\n el.hidden = entries.length === 0;\n if (el.hidden) return;\n\n for (const { emoji, authors } of entries) {\n const mine = authors.includes(me);\n const pill = document.createElement(\"button\");\n pill.type = \"button\";\n pill.className = CLASSES.REACTION_PILL;\n if (mine) pill.classList.add(CLASSES.REACTION_PILL_MINE);\n pill.dataset.reactionEmoji = emoji;\n\n const action = mine\n ? strings.reactionToggleOff\n : strings.reactionToggleOn;\n // A toggle has to say which way it is about to flip; the count alone\n // does not, and the highlight is colour, which never stands alone.\n pill.setAttribute(\"aria-pressed\", String(mine));\n // No hover bubble on any pill: the emoji and count are already there,\n // and a bubble on every one of them turned a dense row into a wall of\n // popups. The trigger beside the row keeps its tooltip \u2014 it is the\n // only control whose icon does not say what it does. The accessible\n // name still carries the action for assistive tech.\n pill.setAttribute(\n \"aria-label\",\n `${action}: ${emoji} (${authors.length})`\n );\n pill.addEventListener(\"click\", (e) => {\n // The inbox list card navigates on click, and the popover closes on\n // an outside click \u2014 neither may fire because a pill was pressed.\n e.stopPropagation();\n pick(target, emoji);\n });\n\n const emojiEl = document.createElement(\"span\");\n emojiEl.className = CLASSES.REACTION_PILL_EMOJI;\n emojiEl.textContent = emoji;\n // Decorative: the accessible name above already spells out the\n // reaction and its count, and screen readers announce emoji unevenly.\n emojiEl.setAttribute(\"aria-hidden\", \"true\");\n\n const count = document.createElement(\"span\");\n count.className = CLASSES.REACTION_PILL_COUNT;\n count.textContent = String(authors.length);\n\n pill.appendChild(emojiEl);\n pill.appendChild(count);\n el.appendChild(pill);\n }\n\n // Trailing \"one more\" affordance, only ever next to existing pills.\n el.appendChild(trigger(target, { className: CLASSES.REACTION_ADD }));\n };\n\n let set = rows.get(target);\n if (!set) rows.set(target, (set = new Set()));\n set.add({ el, repaint });\n\n repaint();\n return el;\n };\n\n return { bar, trigger, refresh };\n};\n", "// Who may edit or delete what.\n//\n// Until this module existed the widget had identity but never consulted it:\n// every comment and reply carried `authorId`, and the \u22EF menu offered \"Delete\"\n// on all of them to everybody. One person's comment was one stranger's click\n// away from being gone.\n//\n// The scope is deliberately narrow \u2014 editing and deleting a comment or a\n// reply. Status, type, priority and reactions stay open to everyone: those\n// are triage, they are reversible, and a team that cannot re-classify each\n// other's reports has lost the point of a shared inbox. Deleting is neither\n// reversible nor shared.\n//\n// What this is NOT: enforcement. HellDots runs in the page, so a determined\n// visitor reaches `deleteComment()` from the console no matter what this file\n// says. The guard removes the accidental path \u2014 the button that should never\n// have been there \u2014 and hands the host a vocabulary to mirror. Authorization\n// proper belongs in the host's backend, checking `authorId` against its own\n// session when the `comment:deleted` event arrives.\n\nimport { normalizeActorId } from \"./id.js\";\nimport { actorKeyOf } from \"./reactions.js\";\n\n/**\n * The actions a host can veto, and the only strings `can` is ever called\n * with. Exported so a host can assert against the list instead of typing the\n * four literals from memory.\n * @type {import('./index.d.ts').PermissionAction[]}\n */\nexport const PERMISSION_ACTIONS = [\n \"edit:comment\",\n \"delete:comment\",\n \"edit:reply\",\n \"delete:reply\",\n];\n\n/**\n * The identity a stored record was written under, resolved exactly the way\n * `actorKeyOf` resolves the live one.\n *\n * Mirror images on purpose, and that is why this imports from reactions.js\n * rather than growing a second resolver: ownership and \"this reaction is\n * mine\" are the same question asked twice, and the day they disagree is the\n * day someone loses the delete button on their own comment.\n *\n * @param {{ author?: string, authorId?: string | null }} record\n * @param {{ anonymous: string }} strings\n * @returns {string}\n */\nexport const recordKeyOf = (record, strings) =>\n normalizeActorId(record?.authorId) ||\n (typeof record?.author === \"string\" ? record.author.trim() : \"\") ||\n strings.anonymous;\n\n/**\n * The rule that applies when the host declares no `can`: you own what carries\n * your identity.\n *\n * A host that never sets `user` gets today's behaviour back unchanged \u2014 every\n * record is written by \"Anonymous\" and so is every reader, the keys match, and\n * nothing is hidden. That is the point: the playground, the localStorage demo\n * and every single-user setup must not have to opt out of a rule that has\n * nobody to protect them from.\n *\n * Known limit, inherited from `actorKeyOf` and shared with reactions: with no\n * `user.id` anywhere the comparison falls back to the display name, so the\n * anonymous fallback is the *localised* one. A corpus written under `en` and\n * read under `es` reads as somebody else's. It fails closed (the button is\n * hidden, nothing is destroyed) and any host that passes `user.id` never\n * reaches that branch.\n *\n * @param {import('./index.d.ts').PermissionTarget} target\n * @param {{ name?: string, id?: string } | undefined} user\n * @param {{ anonymous: string }} strings\n * @returns {boolean}\n */\nexport const isOwnRecord = (target, user, strings) =>\n recordKeyOf(target, strings) === actorKeyOf(user, strings);\n\n/**\n * The one place the answer is decided, so the menu that hides an item and the\n * mutator that refuses it can never disagree.\n *\n * A host `can` must return literal `true` to allow. Anything else denies \u2014\n * including the `undefined` of a branch that forgot to return. A permission\n * predicate is the wrong place to be generous with coercion: guessing wrong\n * in the permissive direction reintroduces exactly the hole this module\n * closes, and guessing wrong the other way costs a hidden button and a bug\n * found in a minute.\n *\n * A `can` that throws denies for the same reason, and says so loudly: falling\n * back to the default rule would silently run a policy the host thinks it has\n * replaced.\n *\n * @param {{\n * can: unknown,\n * action: import('./index.d.ts').PermissionAction,\n * target: import('./index.d.ts').PermissionTarget,\n * user: { name?: string, id?: string } | undefined,\n * strings: { anonymous: string },\n * }} config\n * @returns {boolean}\n */\nexport const resolvePermission = ({ can, action, target, user, strings }) => {\n if (typeof can !== \"function\") return isOwnRecord(target, user, strings);\n try {\n return can(action, target) === true;\n } catch (err) {\n console.warn(\"HellDots: can() threw, denying\", action, err);\n return false;\n }\n};\n\n/**\n * The shape `can` receives for a comment. Built rather than passed whole: the\n * host gets what an authorization decision needs and no live reference into\n * overlay state, and a comment's screenshots \u2014 data-URLs, one per attachment \u2014\n * stay out of a predicate that runs on every card the inbox renders.\n *\n * @param {any} comment\n * @returns {import('./index.d.ts').PermissionTarget}\n */\nexport const commentTargetOf = (comment) => ({\n id: comment.id,\n author: comment.author,\n authorId: comment.authorId || null,\n});\n\n/**\n * Same, one level down. `commentId` rides along because a reply's id is only\n * unique inside its thread, so it is not enough to look the record up with.\n *\n * @param {any} reply\n * @param {import('./index.d.ts').CommentId} commentId\n * @returns {import('./index.d.ts').PermissionTarget}\n */\nexport const replyTargetOf = (reply, commentId) => ({\n id: reply.id,\n author: reply.author,\n authorId: reply.authorId || null,\n commentId,\n});\n", "// Deep links to a single comment.\n//\n// There is no redirect hop here on purpose. Hosted tools can afford one\n// because their back end knows which deployment a thread belongs to and has\n// to resolve that before it can send you anywhere. HellDots has no server:\n// the page a comment lives on is already recorded on the comment, so the\n// link can point straight at its final destination.\n\nexport const DEFAULT_LINK_PARAM = \"helldotsComment\";\n\n/**\n * The shareable URL for one comment.\n *\n * For a comment on the page the user is currently looking at, this keeps the\n * rest of the current URL \u2014 query and hash included. That matters more than\n * it looks: a comment left on `/products?filter=archived` is *about* that\n * filtered view, and a link that drops the filter lands the reader somewhere\n * the comment does not make sense. For other pages only `comment.page` is\n * known (it stores `location.pathname`), so that is all the link can carry.\n *\n * @param {{ id: import('./index.d.ts').CommentId, page?: string }} comment\n * @param {string} [param]\n * @param {string} [href] current document URL; injectable for tests\n * @returns {string}\n */\nexport const buildCommentLink = (\n comment,\n param = DEFAULT_LINK_PARAM,\n href = location.href\n) => {\n const current = new URL(href);\n const page = comment.page || current.pathname;\n const url = page === current.pathname ? current : new URL(page, current);\n url.searchParams.set(param, String(comment.id));\n return url.href;\n};\n\n/**\n * The comment id requested by the current URL, if any.\n * @param {string} [param]\n * @param {string} [href]\n * @returns {string | null}\n */\nexport const readCommentLinkParam = (\n param = DEFAULT_LINK_PARAM,\n href = location.href\n) => {\n try {\n return new URL(href).searchParams.get(param);\n } catch {\n // A malformed URL is not worth breaking startup over \u2014 the widget just\n // opens without honouring a link it could not read.\n return null;\n }\n};\n", "// The append-only trail behind \"who created, changed or resolved this\n// comment, and when\".\n//\n// One record per action worth auditing: creation, a text edit, a status move\n// and a classification change. Replies already carry their own author and\n// timestamp, and reactions are high-frequency signal with no audit value \u2014\n// neither enters the log. That bound is what keeps it at three to five\n// entries per comment instead of twenty, and it is why the quota-shedding\n// path in storage.js needs no change: a hundred comments' worth of history\n// costs about what two automatic screenshots cost.\n//\n// Nothing here is stored twice. The resolution figures RF5 renders are\n// derived from the log on read rather than kept beside it, so they cannot\n// go stale when a comment is reopened.\n\nimport { normalizeActorId } from \"./id.js\";\n\n/** The four auditable actions. */\nexport const AUDIT_EVENTS = [\"created\", \"edited\", \"status\", \"classified\"];\n\n/** The classification fields a \"classified\" event can name. */\nexport const AUDIT_FIELDS = [\"type\", \"priority\", \"tags\"];\n\n// A display name comes from the host and is repeated on every entry, so a\n// pathological one is capped. The actor's id is deliberately NOT capped \u2014\n// see normalizeActorId: it is the only thing a host can reconcile on, and a\n// truncated key joins wrongly instead of failing loudly.\nconst FIELD_MAX = 64;\n\nconst clean = (value) =>\n typeof value === \"string\" ? value.trim().slice(0, FIELD_MAX) : \"\";\n\n// `null` is a value here, not an absence: it is how type and priority read\n// when they are deliberately unset, so a transition to it has to survive.\nconst transition = (value) => {\n if (value === null) return { present: true, value: null };\n if (typeof value === \"string\") return { present: true, value: clean(value) };\n return { present: false, value: undefined };\n};\n\n/**\n * The actor of an action: the stable id the host supplied, plus the name it\n * displays.\n *\n * Resolved in one place for the same reason `actorKeyOf` is \u2014 the log and the\n * author line must never disagree about who acted. The two are siblings, not\n * duplicates: that one produces a key for de-duplication, this one a record\n * for display.\n *\n * @param {{ name?: string, id?: string } | undefined} user\n * @param {{ anonymous: string }} strings\n * @returns {{ id?: string, name: string }}\n */\nexport function actorOf(user, strings) {\n const name = clean(user?.name) || strings.anonymous;\n const id = normalizeActorId(user?.id);\n return id ? { id, name } : { name };\n}\n\n/**\n * Appends one entry, creating the array on first use so an untouched corpus\n * carries no extra bytes.\n *\n * Callers record AFTER their own no-op guard: `setCommentStatus` returns\n * early when the status is unchanged, and an entry appended above that line\n * would log a change that never happened.\n *\n * @param {object} comment\n * @param {string} type one of AUDIT_EVENTS\n * @param {{ id?: string, name: string }} actor\n * @param {{ field?: string, from?: string | null, to?: string | null }} [detail]\n * @returns {object | null} the entry, or null for an unknown event type\n */\nexport function recordEvent(comment, type, actor, detail) {\n if (!AUDIT_EVENTS.includes(type)) return null;\n\n const entry = { type, at: new Date().toISOString(), actor };\n if (detail?.field && AUDIT_FIELDS.includes(detail.field)) {\n entry.field = detail.field;\n }\n const from = transition(detail?.from);\n if (from.present) entry.from = from.value;\n const to = transition(detail?.to);\n if (to.present) entry.to = to.value;\n\n if (!Array.isArray(comment.history)) comment.history = [];\n comment.history.push(entry);\n return entry;\n}\n\n/**\n * Defensive read of a loaded log, at the same level as the existing\n * malformed-reply filter and `normalizeReactions`. A hostile backend or a\n * corrupt localStorage must not be able to inject an event type the timeline\n * has no label for, or a timestamp that poisons every average built on it.\n *\n * @param {unknown} raw\n * @returns {object[] | null} null rather than [], so the serializer can omit\n * the field entirely\n */\nexport function normalizeHistory(raw) {\n if (!Array.isArray(raw)) return null;\n\n const out = [];\n for (const item of raw) {\n if (!item || typeof item !== \"object\") continue;\n if (!AUDIT_EVENTS.includes(item.type)) continue;\n\n const at = clean(item.at);\n if (!Number.isFinite(Date.parse(at))) continue;\n\n const name = clean(item.actor?.name);\n const id = normalizeActorId(item.actor?.id);\n const entry = { type: item.type, at, actor: id ? { id, name } : { name } };\n\n if (AUDIT_FIELDS.includes(item.field)) entry.field = item.field;\n const from = transition(item.from);\n if (from.present) entry.from = from.value;\n const to = transition(item.to);\n if (to.present) entry.to = to.value;\n\n out.push(entry);\n }\n\n if (out.length === 0) return null;\n // Chronological regardless of the order they arrived in: a host merging two\n // devices' corpora can hand us an interleaving, and every reader below\n // walks this array assuming it is ordered.\n out.sort((a, b) => Date.parse(a.at) - Date.parse(b.at));\n return out;\n}\n\n/**\n * Copies the log out, actor included, so a host mutating what\n * `serializeComments()` returned cannot reach back into overlay state \u2014 the\n * same rule `tags` and `reactions` already follow.\n * @param {object[] | null | undefined} history\n * @returns {object[] | null}\n */\nexport function serializeHistory(history) {\n if (!Array.isArray(history) || history.length === 0) return null;\n return history.map((entry) => ({ ...entry, actor: { ...entry.actor } }));\n}\n\n/**\n * Every resolution the comment has had, oldest first, derived from the log\n * rather than stored beside it. A status event landing on `resolved` opens\n * one; the next status event closes it.\n *\n * Each duration is measured from creation, not from the reopen \u2014 \"time to\n * resolve\" answers how long the reporter waited, and restarting the clock on\n * a reopen would make a comment that bounced twice look faster than one that\n * was fixed on the first attempt.\n *\n * @param {object} comment\n * @returns {Array<{ resolvedAt: string, reopenedAt: string | null, ms: number }>}\n */\nexport function resolutionsOf(comment) {\n if (!Array.isArray(comment?.history)) return [];\n\n const out = [];\n let openedAt = null;\n for (const entry of comment.history) {\n if (entry.type !== \"status\") continue;\n if (entry.to === \"resolved\") {\n openedAt = entry.at;\n } else if (openedAt) {\n out.push({ resolvedAt: openedAt, reopenedAt: entry.at, ms: 0 });\n openedAt = null;\n }\n }\n if (openedAt) out.push({ resolvedAt: openedAt, reopenedAt: null, ms: 0 });\n\n // Clocks belong to the client. Merge two devices whose clocks disagree and\n // a resolution can land before the creation it resolves \u2014 clamp rather than\n // render a negative duration.\n const createdAt = Date.parse(comment.createdAt);\n for (const item of out) {\n const resolved = Date.parse(item.resolvedAt);\n item.ms =\n Number.isFinite(createdAt) && Number.isFinite(resolved)\n ? Math.max(0, resolved - createdAt)\n : 0;\n }\n return out;\n}\n\n/**\n * Elapsed time of the resolution currently in force, or null when the comment\n * is not resolved. A function rather than a stored figure, so it can never\n * disagree with the log it comes from.\n *\n * @param {object} comment\n * @returns {number | null}\n */\nexport function currentResolutionMs(comment) {\n if (comment?.status !== \"resolved\") return null;\n\n const resolutions = resolutionsOf(comment);\n const last = resolutions[resolutions.length - 1];\n if (last && !last.reopenedAt) return last.ms;\n\n // Resolved before the log existed: fall back to the stored stamp so an\n // older corpus still renders a duration instead of an em dash.\n const resolved = Date.parse(comment.resolvedAt);\n const created = Date.parse(comment.createdAt);\n if (!Number.isFinite(resolved) || !Number.isFinite(created)) return null;\n return Math.max(0, resolved - created);\n}\n", "// A modal confirmation for the destructive actions. Deleting a comment or a\n// reply is the only thing in the widget that cannot be undone \u2014 there is no\n// trash and no history \u2014 and until now a single click on a menu item did it.\n//\n// Deliberately promise-based rather than callback-based: every call site\n// reads as \"ask, then act\", and the \"user said no\" path is a plain early\n// return instead of a second callback that does nothing.\n\nimport { CLASSES, Z_INDEX } from \"./constants.js\";\n\n/**\n * @typedef {{\n * title: string,\n * message: string,\n * confirmLabel: string,\n * cancelLabel: string,\n * }} ConfirmStrings\n */\n\n/**\n * Open dialogs, so teardown can settle them. Without this, unmounting the\n * widget mid-question would take the DOM away and leave the capture-phase\n * keydown listener on `document` swallowing Escape for the whole page.\n * @type {Set<(result: boolean) => void>}\n */\nconst openDialogs = new Set();\n\n/** Dismisses every open dialog as if the user had cancelled. */\nexport const closeOpenConfirmDialogs = () => {\n for (const dismiss of [...openDialogs]) dismiss(false);\n};\n\n/**\n * Opens the dialog and resolves once the user answers.\n *\n * @param {ShadowRoot | HTMLElement} host where to mount \u2014 pass the widget's\n * shadow root so the dialog inherits its styles and stacking context\n * @param {ConfirmStrings} strings all pre-localized, like every other view\n * builder here\n * @returns {Promise<boolean>} true only when the user confirms\n */\nexport const confirmDialog = (\n host,\n { title, message, confirmLabel, cancelLabel }\n) =>\n new Promise((resolve) => {\n const backdrop = document.createElement(\"div\");\n backdrop.className = CLASSES.CONFIRM;\n backdrop.style.zIndex = String(Z_INDEX.CONFIRM);\n\n const panel = document.createElement(\"div\");\n panel.className = CLASSES.CONFIRM_PANEL;\n panel.setAttribute(\"role\", \"alertdialog\");\n panel.setAttribute(\"aria-modal\", \"true\");\n\n const titleEl = document.createElement(\"h2\");\n titleEl.className = CLASSES.CONFIRM_TITLE;\n titleEl.textContent = title;\n // Generated rather than a constant id: two dialogs must never claim the\n // same one, and the shadow root is shared with everything else.\n titleEl.id = `hd-confirm-title-${Math.random().toString(36).slice(2, 9)}`;\n panel.setAttribute(\"aria-labelledby\", titleEl.id);\n\n const messageEl = document.createElement(\"p\");\n messageEl.className = CLASSES.CONFIRM_MESSAGE;\n messageEl.textContent = message;\n // The title alone doesn't say \"cannot be undone\" \u2014 the message does,\n // and some AT won't announce it without describedby.\n messageEl.id = `hd-confirm-message-${Math.random().toString(36).slice(2, 9)}`;\n panel.setAttribute(\"aria-describedby\", messageEl.id);\n\n const actions = document.createElement(\"div\");\n actions.className = CLASSES.CONFIRM_ACTIONS;\n\n const cancelBtn = document.createElement(\"button\");\n cancelBtn.type = \"button\";\n cancelBtn.className = CLASSES.CONFIRM_CANCEL;\n cancelBtn.textContent = cancelLabel;\n\n const acceptBtn = document.createElement(\"button\");\n acceptBtn.type = \"button\";\n acceptBtn.className = CLASSES.CONFIRM_ACCEPT;\n acceptBtn.textContent = confirmLabel;\n\n actions.appendChild(cancelBtn);\n actions.appendChild(acceptBtn);\n panel.appendChild(titleEl);\n panel.appendChild(messageEl);\n panel.appendChild(actions);\n backdrop.appendChild(panel);\n\n // Restored on close: the menu item that opened this is gone by then, so\n // without it focus would fall back to <body> and the keyboard user would\n // lose their place entirely.\n const previouslyFocused = /** @type {any} */ (\n /** @type {any} */ (host).activeElement || document.activeElement\n );\n\n let settled = false;\n const settle = (result) => {\n if (settled) return;\n settled = true;\n openDialogs.delete(settle);\n document.removeEventListener(\"keydown\", onKeydown, true);\n backdrop.remove();\n previouslyFocused?.focus?.();\n resolve(result);\n };\n\n // Capture phase, and it stops the event: the overlay's own Escape handler\n // is on document and would otherwise close the thread popover behind the\n // dialog while the question was still on screen.\n const onKeydown = (/** @type {KeyboardEvent} */ e) => {\n if (e.key === \"Escape\") {\n e.stopPropagation();\n e.preventDefault();\n settle(false);\n return;\n }\n if (e.key !== \"Tab\") return;\n // aria-modal is a claim about focus, so it has to be true. Only two\n // stops, which makes the trap a swap rather than a ring walk.\n const focusables = [cancelBtn, acceptBtn];\n const active = /** @type {any} */ (\n /** @type {any} */ (host).activeElement || document.activeElement\n );\n const index = focusables.indexOf(active);\n e.preventDefault();\n const next = e.shiftKey\n ? focusables[(index <= 0 ? focusables.length : index) - 1]\n : focusables[(index + 1) % focusables.length];\n next.focus();\n };\n\n cancelBtn.addEventListener(\"click\", () => settle(false));\n acceptBtn.addEventListener(\"click\", () => settle(true));\n // Only a press that both starts and ends on the backdrop dismisses, so a\n // drag that happens to release outside the panel does not answer for the\n // user.\n let pressedBackdrop = false;\n backdrop.addEventListener(\"mousedown\", (e) => {\n pressedBackdrop = e.target === backdrop;\n // The inbox and the thread popover both close on any mousedown outside\n // themselves; without this they tear down behind the dialog.\n e.stopPropagation();\n });\n backdrop.addEventListener(\"click\", (e) => {\n if (e.target === backdrop && pressedBackdrop) settle(false);\n pressedBackdrop = false;\n });\n\n openDialogs.add(settle);\n document.addEventListener(\"keydown\", onKeydown, true);\n // Callers reach us through getRootNode(), which is the Document \u2014 not a\n // shadow root \u2014 for anything mounted in the light DOM. A Document cannot\n // take a second element child, so mount into its body instead.\n const mountPoint = /** @type {any} */ (host).body || host;\n mountPoint.appendChild(backdrop);\n // Cancel, not confirm: the destructive button should never be one stray\n // Enter away.\n cancelBtn.focus();\n });\n", "// Shared per-comment action strip (copy agent context / lifecycle status /\n// more menu) used by both the inbox cards and the thread popover header.\n// Pure view: every mutation goes through the callbacks; the component only\n// keeps its own dot color and tooltips in sync after a selection.\n\nimport {\n CLASSES,\n STATUSES,\n STATUS_COLORS,\n COMMENT_TYPES,\n TYPE_COLORS,\n PRIORITIES,\n PRIORITY_COLORS,\n} from \"./constants.js\";\nimport { attachMenuToggle } from \"./menus.js\";\nimport { confirmDialog } from \"./confirm-dialog.js\";\nimport { commentTargetOf } from \"./permissions.js\";\n\nconst COPY_ICON_SVG = `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\"/><path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"/></svg>`;\nconst CHECK_ICON_SVG = `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"20 6 9 17 4 12\"/></svg>`;\nconst DOTS_ICON_SVG = `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><circle cx=\"5\" cy=\"12\" r=\"1.6\"/><circle cx=\"12\" cy=\"12\" r=\"1.6\"/><circle cx=\"19\" cy=\"12\" r=\"1.6\"/></svg>`;\n\nexport const copyToClipboard = (text) => {\n if (navigator.clipboard?.writeText) {\n return navigator.clipboard.writeText(text).catch(() => {});\n }\n const textarea = document.createElement(\"textarea\");\n textarea.value = text;\n textarea.style.position = \"fixed\";\n textarea.style.opacity = \"0\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n document.execCommand(\"copy\");\n } catch {}\n textarea.remove();\n return Promise.resolve();\n};\n\nexport const statusLabelOf = (status, strings) =>\n ({\n open: strings.statusOpen,\n in_progress: strings.statusInProgress,\n in_review: strings.statusInReview,\n resolved: strings.statusResolved,\n })[status] || strings.statusOpen;\n\nexport const typeLabelOf = (type, strings) =>\n ({\n bug: strings.typeBug,\n suggestion: strings.typeSuggestion,\n question: strings.typeQuestion,\n improvement: strings.typeImprovement,\n })[type] || strings.unset;\n\nexport const priorityLabelOf = (priority, strings) =>\n ({\n high: strings.priorityHigh,\n medium: strings.priorityMedium,\n low: strings.priorityLow,\n })[priority] || strings.unset;\n\n/**\n * Dot-and-menu picker shared by the status, type and priority controls.\n * Keeps its own copy of the selection so the UI stays correct even when the\n * consumer's onSelect is async or doesn't mutate the comment in place.\n * @param {{\n * action: string,\n * options: Array<string|null>,\n * value: string|null,\n * colorOf: (option: string|null) => string,\n * labelOf: (option: string|null) => string,\n * tooltipLabel: string,\n * onSelect: (option: string|null) => void,\n * showLabel?: boolean,\n * }} config\n * @returns {HTMLElement}\n */\nexport const createPicker = ({\n action,\n options,\n value,\n colorOf,\n labelOf,\n tooltipLabel,\n onSelect,\n showLabel = false,\n}) => {\n const wrapper = document.createElement(\"div\");\n wrapper.style.position = \"relative\";\n\n const btn = document.createElement(\"button\");\n btn.type = \"button\";\n btn.className = CLASSES.INBOX_ACTION_BTN;\n if (showLabel) btn.classList.add(CLASSES.INBOX_ACTION_BTN_LABELED);\n btn.dataset.action = action;\n btn.setAttribute(\"aria-haspopup\", \"true\");\n\n const dot = document.createElement(\"span\");\n dot.className = CLASSES.INBOX_STATUS_DOT;\n btn.appendChild(dot);\n\n // Type and priority share exact colours (bug === high === #FF453A), and\n // unset shares \"no colour\" with unset \u2014 the dot alone can't tell them\n // apart, and hover-only disambiguation doesn't exist on touch. A short\n // text label next to the dot makes the current value legible without it.\n let labelEl = null;\n if (showLabel) {\n labelEl = document.createElement(\"span\");\n labelEl.className = CLASSES.INBOX_ACTION_LABEL;\n btn.appendChild(labelEl);\n }\n\n const menu = document.createElement(\"div\");\n menu.className = CLASSES.INBOX_MENU;\n menu.setAttribute(\"role\", \"menu\");\n\n const toggle = attachMenuToggle(btn, menu);\n\n let current = value;\n\n const syncUi = () => {\n const label = `${tooltipLabel}: ${labelOf(current)}`;\n dot.style.backgroundColor = colorOf(current);\n btn.dataset.hdTooltip = label;\n btn.setAttribute(\"aria-label\", label);\n if (labelEl) labelEl.textContent = labelOf(current);\n menu\n .querySelectorAll(\"[data-picker-option]\")\n .forEach((/** @type {HTMLElement} */ item) => {\n const raw = item.dataset.pickerOption;\n const option = raw === \"\" ? null : raw;\n item.setAttribute(\"aria-checked\", String(option === current));\n });\n };\n\n for (const option of options) {\n const item = document.createElement(\"button\");\n item.type = \"button\";\n item.className = CLASSES.INBOX_MENU_ITEM;\n // \"\" is how a null option round-trips through a dataset string.\n item.dataset.pickerOption = option === null ? \"\" : option;\n item.setAttribute(\"role\", \"menuitemradio\");\n\n const itemDot = document.createElement(\"span\");\n itemDot.className = CLASSES.INBOX_STATUS_DOT;\n itemDot.style.backgroundColor = colorOf(option);\n item.appendChild(itemDot);\n item.appendChild(document.createTextNode(labelOf(option)));\n\n item.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n toggle.close();\n // No-op: re-picking the option that's already selected must not fire\n // onSelect \u2014 for the status picker that would re-stamp resolvedAt on\n // every redundant \"Resolved\" click, destroying RF5's elapsed time.\n if (option === current) return;\n current = option;\n onSelect(option);\n syncUi();\n });\n menu.appendChild(item);\n }\n\n wrapper.appendChild(btn);\n wrapper.appendChild(menu);\n syncUi();\n return wrapper;\n};\n\n/**\n * The \u22EF dropdown, shared by the comment action strip and by each reply row.\n * One builder so both stay identical \u2014 same button, same menu chrome, same\n * single-open rule from menus.js \u2014 instead of two copies drifting apart.\n *\n * `tooltip` is optional, and the reply rows deliberately go without one: the\n * hover bubble is an absolutely positioned ::after on a button flush against\n * the right edge of `.thread-scroll`, and it stuck ~9px past it \u2014 enough to\n * give the thread a horizontal scrollbar it had no other reason to have. The\n * aria-label still names the control, and the menu it opens says the rest.\n *\n * An item may carry a `confirm` factory, and then nothing happens until the\n * user answers the modal. It lives here rather than at each call site so a\n * destructive item cannot be added without one being considered.\n *\n * A factory, not a plain object: the wording depends on the comment's state\n * (\"and all of its replies\"), and the menu is built once when the popover\n * opens. Reading it up front described the thread as it was minutes ago.\n *\n * @param {{\n * label: string,\n * tooltip?: string,\n * items: Array<{\n * label: string,\n * onSelect: () => void,\n * confirm?: () => import(\"./confirm-dialog.js\").ConfirmStrings,\n * feedbackLabel?: string,\n * }>,\n * }} config\n * @returns {HTMLElement}\n */\nexport const createMoreMenu = ({ label, tooltip, items }) => {\n const wrapper = document.createElement(\"div\");\n wrapper.style.position = \"relative\";\n\n const btn = document.createElement(\"button\");\n btn.type = \"button\";\n btn.className = CLASSES.INBOX_ACTION_BTN;\n btn.dataset.action = \"menu\";\n if (tooltip) btn.dataset.hdTooltip = tooltip;\n btn.setAttribute(\"aria-label\", label);\n btn.innerHTML = DOTS_ICON_SVG;\n\n const menu = document.createElement(\"div\");\n menu.className = CLASSES.INBOX_MENU;\n menu.setAttribute(\"role\", \"menu\");\n\n const toggle = attachMenuToggle(btn, menu);\n\n for (const entry of items) {\n const item = document.createElement(\"button\");\n item.type = \"button\";\n item.className = CLASSES.INBOX_MENU_ITEM;\n item.setAttribute(\"role\", \"menuitem\");\n item.textContent = entry.label;\n item.addEventListener(\"click\", async (e) => {\n e.stopPropagation();\n // Copying to the clipboard succeeds invisibly. Closing the menu at once\n // would leave the user with no evidence it happened, and the icon-swap\n // trick the copy button uses needs a control that stays on screen \u2014 so\n // the item says so itself and the menu waits before closing.\n if (entry.feedbackLabel) {\n entry.onSelect();\n item.textContent = entry.feedbackLabel;\n setTimeout(() => {\n item.textContent = entry.label;\n toggle.close();\n }, 1200);\n return;\n }\n toggle.close();\n if (entry.confirm) {\n // Read before awaiting: onSelect may detach the row this button\n // lives in, and a detached node has no shadow root to mount into.\n const host = /** @type {any} */ (item.getRootNode());\n if (!(await confirmDialog(host, entry.confirm()))) return;\n }\n entry.onSelect();\n });\n menu.appendChild(item);\n }\n\n wrapper.appendChild(btn);\n wrapper.appendChild(menu);\n return wrapper;\n};\n\n/**\n * The per-comment action strip, in two groups: what the comment *is* on the\n * left (status, type, priority) and what you can *do* with it on the right\n * (react, copy its context, the \u22EF menu). The split is why the copy button\n * moved: mixed in among the pickers it read as a fourth classification.\n *\n * `can` decides which of the destructive items the \u22EF menu is built with. It\n * is optional and defaults to allowing both, so a caller that renders a strip\n * without a policy \u2014 the style tests, a host embedding the component \u2014 gets\n * the whole menu rather than a silently crippled one.\n *\n * @param {Object} comment\n * @param {{ strings: Object, reactions?: { trigger: Function }, can?: (action: import(\"./index.d.ts\").PermissionAction, target: import(\"./index.d.ts\").PermissionTarget) => boolean, onCopy: Function, onCopyLink?: Function, onEdit?: Function, onSetStatus: Function, onSetType: Function, onSetPriority: Function, onDelete: Function }} deps\n * @returns {HTMLElement}\n */\nexport const createCommentActions = (\n comment,\n {\n strings,\n reactions,\n can,\n onCopy,\n onCopyLink,\n onEdit,\n onSetStatus,\n onSetType,\n onSetPriority,\n onDelete,\n }\n) => {\n const actions = document.createElement(\"div\");\n actions.className = CLASSES.INBOX_CARD_ACTIONS;\n\n const classification = document.createElement(\"div\");\n classification.className = CLASSES.ACTIONS_GROUP;\n const tools = document.createElement(\"div\");\n tools.className = `${CLASSES.ACTIONS_GROUP} ${CLASSES.ACTIONS_GROUP_END}`;\n actions.appendChild(classification);\n actions.appendChild(tools);\n\n // --- add reaction ---\n // The only way to leave the FIRST reaction: the pill row below appears with\n // the reaction, not before it, so it cannot be the entry point.\n if (reactions) {\n tools.appendChild(\n reactions.trigger(comment, { className: CLASSES.INBOX_ACTION_BTN })\n );\n }\n\n // --- copy agent context ---\n const copyBtn = document.createElement(\"button\");\n copyBtn.type = \"button\";\n copyBtn.className = CLASSES.INBOX_ACTION_BTN;\n copyBtn.dataset.action = \"copy\";\n copyBtn.dataset.hdTooltip = strings.copyAgentContext;\n copyBtn.setAttribute(\"aria-label\", strings.copyAgentContext);\n copyBtn.innerHTML = COPY_ICON_SVG;\n copyBtn.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n onCopy(comment);\n copyBtn.innerHTML = CHECK_ICON_SVG;\n copyBtn.dataset.hdTooltip = strings.copied;\n setTimeout(() => {\n copyBtn.innerHTML = COPY_ICON_SVG;\n copyBtn.dataset.hdTooltip = strings.copyAgentContext;\n }, 1500);\n });\n tools.appendChild(copyBtn);\n\n // --- lifecycle status picker (RF09) ---\n classification.appendChild(\n createPicker({\n action: \"status\",\n options: STATUSES,\n value: comment.status || \"open\",\n // Every STATUSES entry has a colour; the fallback only catches a status\n // this build doesn't know, which loadComments already coerces to `open`.\n colorOf: (status) => STATUS_COLORS[status] || STATUS_COLORS.open,\n labelOf: (status) => statusLabelOf(status, strings),\n tooltipLabel: strings.statusLabel,\n onSelect: (status) => onSetStatus(comment, status),\n // Labelled like type and priority: the strip is on its own row now, so\n // there is room, and a lone coloured dot needed a hover to be read \u2014\n // which touch never provides.\n showLabel: true,\n })\n );\n\n // --- category picker (RF3) ---\n classification.appendChild(\n createPicker({\n action: \"type\",\n // `null` first: returning to the neutral state must be reachable.\n options: [null, ...COMMENT_TYPES],\n value: comment.type || null,\n colorOf: (type) => TYPE_COLORS[type] || \"transparent\",\n labelOf: (type) => typeLabelOf(type, strings),\n tooltipLabel: strings.typeLabel,\n onSelect: (type) => onSetType?.(comment, type),\n showLabel: true,\n })\n );\n\n // --- priority picker (RF4) ---\n classification.appendChild(\n createPicker({\n action: \"priority\",\n options: [null, ...PRIORITIES],\n value: comment.priority || null,\n colorOf: (priority) => PRIORITY_COLORS[priority] || \"transparent\",\n labelOf: (priority) => priorityLabelOf(priority, strings),\n tooltipLabel: strings.priorityLabel,\n onSelect: (priority) => onSetPriority?.(comment, priority),\n showLabel: true,\n })\n );\n\n // --- more (\u22EF) menu ---\n // Hidden, not disabled. A greyed-out \"Delete\" on every one of a\n // colleague's comments is a row of dead controls telling you the same\n // thing over and over; the item simply is not yours to see. \"Copy link\"\n // is never gated, so the menu can never come out empty.\n const target = commentTargetOf(comment);\n const allow = (/** @type {any} */ action) =>\n can ? can(action, target) : true;\n\n // Annotated because the first element alone would fix the element type,\n // and the two conditional pushes carry fields it does not have.\n /** @type {Parameters<typeof createMoreMenu>[0][\"items\"]} */\n const items = [\n {\n label: strings.copyLink,\n feedbackLabel: strings.linkCopied,\n onSelect: () => onCopyLink?.(comment),\n },\n ];\n if (allow(\"edit:comment\")) {\n items.push({\n label: strings.editComment,\n onSelect: () => onEdit?.(comment),\n });\n }\n if (allow(\"delete:comment\")) {\n items.push({\n label: strings.deleteComment,\n onSelect: () => onDelete(comment),\n confirm: () => ({\n title: strings.confirmDeleteCommentTitle,\n // Two wordings rather than a reply count: what matters is that a\n // discussion is about to go with the comment, and saying so\n // avoids pluralising a number in every locale.\n message: comment.replies?.length\n ? strings.confirmDeleteThreadMessage\n : strings.confirmDeleteCommentMessage,\n confirmLabel: strings.confirmDelete,\n cancelLabel: strings.confirmCancel,\n }),\n });\n }\n\n tools.appendChild(\n createMoreMenu({\n label: strings.commentOptions,\n tooltip: strings.moreOptions,\n items,\n })\n );\n\n return actions;\n};\n", "// The inline editor that replaces a comment or reply body while it is being\n// edited, plus the one question asked before an unsaved draft is thrown away.\n//\n// This component is deliberately dumb: it renders a draft and reports every\n// keystroke back. It does NOT own the draft. The panels re-render constantly\n// \u2014 ten `render()` call sites in the inbox alone, plus seven `refresh()`\n// calls from the overlay \u2014 and a draft living in this DOM would be destroyed\n// by any of them, silently, mid-sentence. So the owner keeps the draft as\n// state (the same reason `detailId` is state) and hands it back on rebuild.\n\nimport { CLASSES } from \"./constants.js\";\nimport { confirmDialog } from \"./confirm-dialog.js\";\n\n/**\n * @param {Object} config\n * @param {string} config.value current draft text\n * @param {Object} config.strings\n * @param {(text: string) => void} config.onInput fired on every keystroke\n * @param {(text: string) => void} config.onSave\n * @param {() => void} config.onCancel\n * @returns {HTMLElement}\n */\nexport const createInlineEditor = ({\n value,\n strings,\n onInput,\n onSave,\n onCancel,\n}) => {\n const wrapper = document.createElement(\"div\");\n wrapper.className = CLASSES.EDITOR;\n\n const input = document.createElement(\"textarea\");\n input.className = CLASSES.EDITOR_INPUT;\n input.value = value;\n input.rows = 3;\n input.setAttribute(\"aria-label\", strings.editorAriaLabel);\n\n const actions = document.createElement(\"div\");\n actions.className = CLASSES.EDITOR_ACTIONS;\n\n const cancel = document.createElement(\"button\");\n cancel.type = \"button\";\n cancel.className = CLASSES.EDITOR_CANCEL;\n cancel.textContent = strings.editCancel;\n\n const save = document.createElement(\"button\");\n save.type = \"button\";\n save.className = CLASSES.EDITOR_SAVE;\n save.textContent = strings.editSave;\n\n // An empty body is not a way to delete: the comment would keep its marker,\n // its replies and its row in the inbox while saying nothing. Deleting is\n // its own action, and it asks first.\n const syncSave = () => {\n save.disabled = input.value.trim().length === 0;\n };\n syncSave();\n\n input.addEventListener(\"input\", () => {\n syncSave();\n onInput(input.value);\n });\n\n input.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Escape\") {\n // The overlay closes the thread popover on Escape from a bubble-phase\n // listener on `document`. Without this the panel would go too, and the\n // question about the unsaved draft would be asked about something the\n // user can no longer see.\n e.stopPropagation();\n e.preventDefault();\n onCancel();\n return;\n }\n if (e.key === \"Enter\" && (e.metaKey || e.ctrlKey) && !save.disabled) {\n e.preventDefault();\n onSave(input.value);\n }\n });\n\n cancel.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n onCancel();\n });\n save.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n if (!save.disabled) onSave(input.value);\n });\n\n actions.appendChild(cancel);\n actions.appendChild(save);\n wrapper.appendChild(input);\n wrapper.appendChild(actions);\n\n // Re-rendered panels rebuild this element from the stored draft, so the\n // caret has to be put back where a typist expects it rather than at 0.\n queueMicrotask(() => {\n input.focus();\n input.setSelectionRange(input.value.length, input.value.length);\n });\n\n return wrapper;\n};\n\n/**\n * Asks before an unsaved draft is thrown away. Callers gate on dirtiness\n * themselves \u2014 an untouched editor closes without a question, because there\n * is nothing to lose and a dialog nobody needs teaches people to dismiss\n * dialogs without reading them.\n *\n * @param {any} host node whose root the dialog mounts into\n * @param {Object} strings\n * @returns {Promise<boolean>} true when the draft may be discarded\n */\nexport const confirmDiscard = (host, strings) =>\n confirmDialog(host, {\n title: strings.confirmDiscardTitle,\n message: strings.confirmDiscardMessage,\n confirmLabel: strings.confirmDiscard,\n cancelLabel: strings.confirmKeepEditing,\n });\n", "import {\n CLASSES,\n IDS,\n COMMENT_TYPES,\n TYPE_COLORS,\n PRIORITIES,\n PRIORITY_COLORS,\n STATUS_COLORS,\n MAX_SCREENSHOTS,\n} from \"./constants.js\";\nimport { formatDuration, formatTemplate } from \"./i18n.js\";\nimport { currentResolutionMs } from \"./audit.js\";\nimport defaultStrings from \"./locales/en.js\";\nimport {\n createPicker,\n createMoreMenu,\n statusLabelOf,\n typeLabelOf,\n priorityLabelOf,\n} from \"./comment-actions.js\";\nimport { createInlineEditor } from \"./inline-editor.js\";\nimport { replyTargetOf } from \"./permissions.js\";\n\nconst formatRelativeTime = (date, strings) => {\n const diff = Date.now() - new Date(date).getTime();\n const minutes = Math.floor(diff / 60000);\n const hours = Math.floor(diff / 3600000);\n const days = Math.floor(diff / 86400000);\n\n if (minutes < 1) return strings.justNow;\n if (minutes < 60) return formatTemplate(strings.minutesAgoTemplate, minutes);\n if (hours < 24) return formatTemplate(strings.hoursAgoTemplate, hours);\n return formatTemplate(strings.daysAgoTemplate, days);\n};\n\nconst formatFullDate = (date, locale) => {\n return new Intl.DateTimeFormat(locale, {\n month: \"short\",\n day: \"numeric\",\n hour: \"numeric\",\n minute: \"2-digit\",\n }).format(new Date(date));\n};\n\n/**\n * The author name, clipped to the meta row, with a hover tooltip that appears\n * only when the clipping actually hid something.\n *\n * Two boxes for one name because the ellipsis needs `overflow: hidden`, and an\n * overflow-hidden box clips its own `::after` \u2014 which is exactly how the\n * generic `[data-hd-tooltip]` draws its bubble. The outer box stays visible and\n * carries the tooltip; the inner one truncates.\n *\n * The measurement runs on hover rather than at build time: nothing here is in\n * the document yet when this function returns, so there is no layout to read,\n * and a name that fits today stops fitting after a window resize or a page\n * zoom. One read of `scrollWidth` on entering a name the pointer is already\n * resting on is cheaper than watching every meta row for resizes.\n *\n * @param {string} name\n * @returns {HTMLElement}\n */\nconst createAuthorElement = (name) => {\n const el = document.createElement(\"span\");\n el.className = CLASSES.THREAD_AUTHOR;\n\n const nameEl = document.createElement(\"span\");\n nameEl.className = CLASSES.THREAD_AUTHOR_NAME;\n nameEl.textContent = name;\n el.appendChild(nameEl);\n\n el.addEventListener(\"mouseenter\", () => {\n // A tooltip that repeats a name already fully on screen is the noise the\n // reaction pills were just stripped of; only truncation earns one.\n if (nameEl.scrollWidth > nameEl.clientWidth) el.dataset.hdTooltip = name;\n else delete el.dataset.hdTooltip;\n });\n\n return el;\n};\n\nexport const createMetaElement = (\n author,\n createdAt,\n strings,\n locale,\n editedAt = null\n) => {\n const meta = document.createElement(\"div\");\n meta.className = CLASSES.THREAD_META;\n\n const authorEl = createAuthorElement(author || strings.anonymous);\n\n const timeEl = document.createElement(\"span\");\n timeEl.className = CLASSES.THREAD_TIME;\n timeEl.textContent = formatRelativeTime(createdAt, strings);\n timeEl.dataset.fullDate = formatFullDate(createdAt, locale);\n\n meta.appendChild(authorEl);\n meta.appendChild(timeEl);\n\n if (editedAt) meta.appendChild(createEditedMark(editedAt, strings, locale));\n\n return meta;\n};\n\n/**\n * The \"edited\" mark for a meta line.\n *\n * Someone can answer \"the button is blue\", watch the text they answered get\n * rewritten, and have no way to know it happened \u2014 their reply is left\n * arguing with a sentence that no longer exists. Text rather than a colour,\n * so it holds up under WCAG 1.4.1 like every other badge here, and the exact\n * time hangs off the same `data-full-date` hover the timestamp uses.\n *\n * Exported because the open thread popover is mutated in place rather than\n * re-rendered, so the overlay has to build this same mark after a save.\n *\n * @param {string} editedAt\n * @param {object} strings\n * @param {string} [locale]\n * @returns {HTMLElement}\n */\nexport const createEditedMark = (editedAt, strings, locale) => {\n const editedEl = document.createElement(\"span\");\n editedEl.className = CLASSES.THREAD_EDITED;\n editedEl.textContent = strings.editedMark;\n editedEl.dataset.fullDate =\n strings.editedAtPrefix + formatFullDate(editedAt, locale);\n return editedEl;\n};\n\nexport const isMacPlatform = () =>\n /Mac|iPod|iPhone|iPad/.test(navigator.userAgent);\n\n/**\n * The comment shortcut as the user's platform spells it. Exported so the\n * inbox's empty state teaches the same chord the toolbar tooltip shows \u2014\n * two different renderings of one shortcut is how they drift apart.\n * @param {{ shortcutModifier?: string, shortcutKey?: string }} options\n * @param {object} strings\n */\nexport const getShortcutText = (options, strings) => {\n const isMac = isMacPlatform();\n const modifierMap = {\n alt: isMac ? \"\u2325\" : strings.modifierAlt,\n ctrl: isMac ? \"\u2318\" : strings.modifierCtrl,\n shift: isMac ? \"\u21E7\" : strings.modifierShift,\n };\n\n const modifier = modifierMap[options.shortcutModifier] || modifierMap.alt;\n const key = options.shortcutKey?.toUpperCase() || \"C\";\n\n return `${modifier} + ${key}`;\n};\n\n// Shared with the inbox and the context block \u2014 one caret, not three copies.\nexport const CARET_ICON_SVG = `<svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"6 9 12 15 18 9\"/></svg>`;\n\nconst ATTACH_ICON_SVG = `<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\"/><circle cx=\"8.5\" cy=\"8.5\" r=\"1.5\"/><polyline points=\"21 15 16 10 5 21\"/></svg>`;\n\nconst SEND_ICON_SVG = `<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M22 2L11 13M22 2L15 22L11 13M11 13L2 9L22 2\"/></svg>`;\n\nconst COMMENT_BUBBLE_SVG = `<svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" stroke-linejoin=\"round\" fill=\"currentColor\"><path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M2.8914 10.4028L2.98327 10.6318C3.22909 11.2445 3.5 12.1045 3.5 13C3.5 13.3588 3.4564 13.7131 3.38773 14.0495C3.69637 13.9446 4.01409 13.8159 4.32918 13.6584C4.87888 13.3835 5.33961 13.0611 5.70994 12.7521L6.22471 12.3226L6.88809 12.4196C7.24851 12.4724 7.61994 12.5 8 12.5C11.7843 12.5 14.5 9.85569 14.5 7C14.5 4.14431 11.7843 1.5 8 1.5C4.21574 1.5 1.5 4.14431 1.5 7C1.5 8.18175 1.94229 9.29322 2.73103 10.2153L2.8914 10.4028ZM2.8135 15.7653C1.76096 16 1 16 1 16C1 16 1.43322 15.3097 1.72937 14.4367C1.88317 13.9834 2 13.4808 2 13C2 12.3826 1.80733 11.7292 1.59114 11.1903C0.591845 10.0221 0 8.57152 0 7C0 3.13401 3.58172 0 8 0C12.4183 0 16 3.13401 16 7C16 10.866 12.4183 14 8 14C7.54721 14 7.10321 13.9671 6.67094 13.9038C6.22579 14.2753 5.66881 14.6656 5 15C4.23366 15.3832 3.46733 15.6195 2.8135 15.7653Z\"/></svg>`;\n\nconst MENU_ICON_SVG = `<svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" stroke-linejoin=\"round\" fill=\"currentColor\"><path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M1.67705 7.5L3.92705 3H12.0729L14.3229 7.5H10H9.25V8.25C9.25 8.94036 8.69036 9.5 8 9.5C7.30964 9.5 6.75 8.94036 6.75 8.25V7.5H6H1.67705ZM1.5 9V12C1.5 12.5523 1.94772 13 2.5 13H13.5C14.0523 13 14.5 12.5523 14.5 12V9H10.6465C10.32 10.1543 9.25878 11 8 11C6.74122 11 5.67998 10.1543 5.35352 9H1.5ZM3 1.5H13L15.8944 7.28885C15.9639 7.42771 16 7.58082 16 7.73607V12C16 13.3807 14.8807 14.5 13.5 14.5H2.5C1.11929 14.5 0 13.3807 0 12V7.73607C0 7.58082 0.0361451 7.42771 0.105573 7.28885L3 1.5Z\"/></svg>`;\n\nexport const EYE_ICON_SVG = `<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/></svg>`;\n\nexport const EYE_OFF_ICON_SVG = `<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24\"/><line x1=\"1\" y1=\"1\" x2=\"23\" y2=\"23\"/></svg>`;\n\n/**\n * Shared input area component used by both the comment box and the thread popover.\n * Returns the container element and references to key child elements.\n * @param {Object} options\n * @param {string} options.areaClassName\n * @param {\"textarea\" | \"input\"} [options.inputTag]\n * @param {string} [options.inputClassName]\n * @param {string} [options.inputId]\n * @param {string} options.inputPlaceholder\n * @param {string} [options.submitBtnId]\n * @param {string} [options.fileInputId]\n * @param {typeof defaultStrings} strings\n */\nexport const createInputArea = (\n {\n areaClassName,\n inputTag = \"textarea\",\n inputClassName,\n inputId,\n inputPlaceholder,\n submitBtnId,\n fileInputId,\n },\n strings\n) => {\n const container = document.createElement(\"div\");\n container.className = areaClassName;\n\n /** @type {HTMLInputElement | HTMLTextAreaElement} */\n const inputEl = document.createElement(inputTag);\n if (inputId) inputEl.id = inputId;\n if (inputClassName) inputEl.className = inputClassName;\n inputEl.placeholder = inputPlaceholder;\n inputEl.setAttribute(\"aria-label\", inputPlaceholder);\n if (inputTag === \"input\")\n /** @type {HTMLInputElement} */ (inputEl).type = \"text\";\n\n const screenshotsContainer = document.createElement(\"div\");\n screenshotsContainer.className = CLASSES.SCREENSHOTS_CONTAINER;\n\n const actionsBar = document.createElement(\"div\");\n actionsBar.className = CLASSES.COMMENT_ACTIONS_BAR;\n\n const attachBtn = document.createElement(\"button\");\n attachBtn.className = CLASSES.ATTACH_IMAGE_BTN;\n attachBtn.type = \"button\";\n attachBtn.setAttribute(\"aria-label\", strings.attachImage);\n attachBtn.innerHTML = ATTACH_ICON_SVG;\n\n const fileInput = document.createElement(\"input\");\n fileInput.type = \"file\";\n if (fileInputId) fileInput.id = fileInputId;\n fileInput.accept = \"image/*\";\n fileInput.style.display = \"none\";\n\n const submitBtn = document.createElement(\"button\");\n if (submitBtnId) submitBtn.id = submitBtnId;\n submitBtn.className = CLASSES.THREAD_SUBMIT;\n submitBtn.type = \"button\";\n submitBtn.setAttribute(\"aria-label\", strings.send);\n submitBtn.innerHTML = SEND_ICON_SVG;\n\n actionsBar.appendChild(attachBtn);\n actionsBar.appendChild(fileInput);\n actionsBar.appendChild(submitBtn);\n\n container.appendChild(inputEl);\n container.appendChild(screenshotsContainer);\n container.appendChild(actionsBar);\n\n return {\n container,\n inputEl,\n screenshotsContainer,\n attachBtn,\n fileInput,\n submitBtn,\n };\n};\n\nconst createActionWithTooltip = (btnClass, btnSvg, tooltipContent, label) => {\n const wrapper = document.createElement(\"div\");\n wrapper.className = CLASSES.TOOLBAR_ACTION_WRAPPER;\n\n const tooltip = document.createElement(\"div\");\n tooltip.className = CLASSES.TOOLBAR_ACTION_TOOLTIP;\n tooltipContent.forEach((el) => tooltip.appendChild(el));\n\n const btn = document.createElement(\"button\");\n btn.type = \"button\";\n btn.className = `${CLASSES.TOOLBAR_ACTION_BTN} ${btnClass}`;\n btn.setAttribute(\"aria-label\", label);\n btn.innerHTML = btnSvg;\n\n wrapper.appendChild(tooltip);\n wrapper.appendChild(btn);\n return wrapper;\n};\n\nexport const createToolbar = (options = {}, strings = defaultStrings) => {\n const toolbar = document.createElement(\"div\");\n toolbar.id = IDS.TOOLBAR;\n\n const actions = document.createElement(\"div\");\n actions.className = CLASSES.TOOLBAR_ACTIONS;\n\n const commentLabel = document.createElement(\"span\");\n commentLabel.className = CLASSES.TOOLBAR_TEXT;\n commentLabel.textContent = strings.toolbarComment;\n\n const shortcutKey = document.createElement(\"span\");\n shortcutKey.className = CLASSES.SHORTCUT_HINT;\n shortcutKey.textContent = getShortcutText(options, strings);\n\n const commentWrapper = createActionWithTooltip(\n CLASSES.TOOLBAR_COMMENT_BTN,\n COMMENT_BUBBLE_SVG,\n [commentLabel, shortcutKey],\n strings.toolbarComment\n );\n commentWrapper\n .querySelector(`.${CLASSES.TOOLBAR_COMMENT_BTN}`)\n ?.setAttribute(\"aria-pressed\", \"false\");\n\n const inboxLabel = document.createElement(\"span\");\n inboxLabel.className = CLASSES.TOOLBAR_TEXT;\n inboxLabel.textContent = strings.toolbarInbox;\n\n const inboxWrapper = createActionWithTooltip(\n CLASSES.TOOLBAR_MENU_BTN,\n MENU_ICON_SVG,\n [inboxLabel],\n strings.toolbarInbox\n );\n\n actions.appendChild(commentWrapper);\n actions.appendChild(inboxWrapper);\n toolbar.appendChild(actions);\n\n const visibilityLabel = document.createElement(\"span\");\n visibilityLabel.className = CLASSES.TOOLBAR_TEXT;\n visibilityLabel.textContent = strings.toolbarHideComments;\n\n const visibilityWrapper = createActionWithTooltip(\n CLASSES.TOOLBAR_EYE_BTN,\n EYE_ICON_SVG,\n [visibilityLabel],\n strings.toolbarHideComments\n );\n\n // Its own pill, out of flow: the main pill keeps its exact centered\n // position, and this one hangs off its right edge (the mockup's layout).\n // The button's toggle behavior will apply CLASSES.MARKERS_HIDDEN to indicate\n // when comments are hidden.\n const visibility = document.createElement(\"div\");\n visibility.className = CLASSES.TOOLBAR_VISIBILITY;\n visibility.appendChild(visibilityWrapper);\n toolbar.appendChild(visibility);\n\n return toolbar;\n};\n\n/**\n * RF3/RF4/RF5 \u2014 the classification and resolution-time badge strip. Every\n * badge carries text: colour alone must never be the only signal\n * (WCAG 1.4.1), so the colour only ever tints the border.\n *\n * Both flags exist because a badge is only worth showing where nothing else\n * already says it. The tooltip is a read-only preview and needs all of it.\n * Inbox cards carry labelled status/type/priority pickers, so repeating\n * those three as badges is pure duplication \u2014 but tags and the resolution\n * time have no control anywhere, so they stay either way.\n *\n * @param {any} comment\n * @param {object} strings\n * @param {{ includeStatus?: boolean, includeClassification?: boolean }} [options]\n * @returns {HTMLElement | null} null when there's nothing to show\n */\nexport const createBadgeRow = (\n comment,\n strings,\n { includeStatus = false, includeClassification = true } = {}\n) => {\n const row = document.createElement(\"div\");\n row.className = CLASSES.INBOX_BADGES;\n\n const addBadge = (text, modifier, color) => {\n const badge = document.createElement(\"span\");\n badge.className = `${CLASSES.BADGE} ${modifier}`;\n badge.textContent = text;\n if (color) badge.style.borderColor = color;\n row.appendChild(badge);\n };\n\n if (includeStatus) {\n const status = comment.status || \"open\";\n addBadge(\n statusLabelOf(status, strings),\n CLASSES.BADGE_STATUS,\n STATUS_COLORS[status]\n );\n }\n if (includeClassification && comment.type) {\n addBadge(\n typeLabelOf(comment.type, strings),\n CLASSES.BADGE_TYPE,\n TYPE_COLORS[comment.type]\n );\n }\n if (includeClassification && comment.priority) {\n addBadge(\n priorityLabelOf(comment.priority, strings),\n CLASSES.BADGE_PRIORITY,\n PRIORITY_COLORS[comment.priority]\n );\n }\n // Tags are no longer authored in the widget, but comments saved before\n // that (or set through setCommentTags) still carry them.\n for (const tag of comment.tags || []) {\n addBadge(tag, CLASSES.BADGE_TAG, null);\n }\n\n if (comment.status === \"resolved\") {\n // Derived from the audit log rather than read off a stored figure, so a\n // reopened-and-resolved-again comment cannot show the duration of a\n // resolution that no longer applies. Comments predating the log fall back\n // to their stamp inside currentResolutionMs, and one with neither shows a\n // dash rather than a duration computed from data we do not have.\n const elapsedMs = currentResolutionMs(comment);\n const elapsed =\n elapsedMs === null ? \"\" : formatDuration(elapsedMs, strings);\n addBadge(\n formatTemplate(strings.resolvedInTemplate, elapsed || \"\u2014\"),\n CLASSES.BADGE_DURATION,\n null\n );\n }\n\n return row.children.length ? row : null;\n};\n\n/**\n * RF3 + RF4 \u2014 the classification strip inside the new-comment box: type and\n * priority, both starting neutral.\n *\n * The comment box is built once and reused for every comment, so this\n * exposes reset(): without it the previous comment's selections would leak\n * into the next one.\n *\n * @param {object} strings\n * @returns {{ container: HTMLElement, getType: () => string|null,\n * getPriority: () => string|null, reset: () => void }}\n */\nexport const createClassifyRow = (strings) => {\n const container = document.createElement(\"div\");\n container.className = CLASSES.CLASSIFY_ROW;\n\n let type = null;\n let priority = null;\n\n // Pickers keep their selection internally, so returning them to neutral\n // means rebuilding them \u2014 hence mount() rather than a one-shot append.\n const mount = () => {\n container.replaceChildren();\n container.appendChild(\n createPicker({\n action: \"type\",\n options: [null, ...COMMENT_TYPES],\n value: null,\n colorOf: (value) => TYPE_COLORS[value] || \"transparent\",\n labelOf: (value) => typeLabelOf(value, strings),\n tooltipLabel: strings.typeLabel,\n onSelect: (value) => (type = value),\n showLabel: true,\n })\n );\n container.appendChild(\n createPicker({\n action: \"priority\",\n options: [null, ...PRIORITIES],\n value: null,\n colorOf: (value) => PRIORITY_COLORS[value] || \"transparent\",\n labelOf: (value) => priorityLabelOf(value, strings),\n tooltipLabel: strings.priorityLabel,\n onSelect: (value) => (priority = value),\n showLabel: true,\n })\n );\n };\n\n mount();\n\n return {\n container,\n getType: () => type,\n getPriority: () => priority,\n reset: () => {\n type = null;\n priority = null;\n mount();\n },\n };\n};\n\nexport const createCommentBox = (strings = defaultStrings) => {\n const commentBox = document.createElement(\"div\");\n commentBox.id = IDS.COMMENT_BOX;\n commentBox.setAttribute(\"role\", \"dialog\");\n commentBox.setAttribute(\"aria-label\", strings.commentBoxAriaLabel);\n\n const { container: inputArea } = createInputArea(\n {\n areaClassName: CLASSES.COMMENT_INPUT_AREA,\n inputTag: \"textarea\",\n inputId: IDS.COMMENT_INPUT,\n inputPlaceholder: strings.commentPlaceholder,\n submitBtnId: IDS.SUBMIT_COMMENT,\n fileInputId: IDS.ATTACH_IMAGE_INPUT,\n },\n strings\n );\n\n const classify = createClassifyRow(strings);\n\n commentBox.appendChild(classify.container);\n commentBox.appendChild(inputArea);\n commentBox.style.display = \"none\";\n // Exposed so the overlay can read the selections at save time without\n // re-querying the DOM.\n /** @type {any} */ (commentBox).classify = classify;\n return commentBox;\n};\n\n/**\n * Escapes a value for interpolation inside a double-quoted CSS attribute\n * selector. loadComments accepts arbitrary host ids, so a quote or backslash\n * in one would otherwise make every querySelector throw. Escaped by hand\n * because jsdom (where the whole test suite runs) does not implement\n * CSS.escape.\n * @param {string | number} value\n * @returns {string}\n */\nexport const cssAttrValue = (value) => String(value).replace(/[\\\\\"]/g, \"\\\\$&\");\n\n/**\n * Attribute selector for a comment's marker circle.\n * @param {string | number} id\n * @returns {string}\n */\nexport const circleSelector = (id) => `[data-comment-id=\"${cssAttrValue(id)}\"]`;\n\n/**\n * A comment's (or reply's) attached screenshots, tolerating the singular\n * `screenshot` field records persisted before the array existed still carry.\n * @param {{ screenshots?: string[], screenshot?: string }} entry\n * @returns {string[]}\n */\nexport const screenshotsOf = (entry) =>\n entry.screenshots || (entry.screenshot ? [entry.screenshot] : []);\n\n/**\n * The pending-attachment preview strip. One builder for the three surfaces\n * that show it \u2014 comment box, thread popover reply, inbox reply \u2014 which had\n * drifted apart once already (the popover copy lost its remove button's\n * aria-label and type).\n * @param {Element} container\n * @param {string[]} screenshots the pending array; remove splices it in place\n * @param {{ strings: typeof defaultStrings, onShow: (dataUrl: string) => void,\n * rerender: () => void, pending?: number }} deps `pending` is how many\n * crops are still rendering \u2014 only the comment box passes it, because it\n * is the only surface that opens before its attachment exists.\n */\nexport const renderScreenshotsPreview = (\n container,\n screenshots,\n { strings, onShow, rerender, pending = 0 }\n) => {\n container.innerHTML = \"\";\n container.classList.toggle(\n CLASSES.ACTIVE,\n screenshots.length > 0 || pending > 0\n );\n\n screenshots.forEach((dataUrl, i) => {\n const item = document.createElement(\"div\");\n item.className = CLASSES.SCREENSHOT_ITEM;\n\n const img = document.createElement(\"img\");\n img.className = CLASSES.SCREENSHOT_IMG;\n img.src = dataUrl;\n img.alt = strings.attachedScreenshot;\n makeThumbnailOperable(img, () => onShow(dataUrl));\n\n const removeBtn = document.createElement(\"button\");\n removeBtn.type = \"button\";\n removeBtn.className = CLASSES.SCREENSHOT_REMOVE;\n removeBtn.setAttribute(\"aria-label\", strings.removeScreenshot);\n removeBtn.innerHTML = \"×\";\n removeBtn.onclick = (e) => {\n e.stopPropagation();\n screenshots.splice(i, 1);\n rerender();\n };\n\n item.appendChild(img);\n item.appendChild(removeBtn);\n container.appendChild(item);\n });\n\n // A slot for a crop that has not landed yet. The comment box no longer\n // waits for the render, so without this the box opens with nothing where\n // the user's selection should be and reads as having lost it.\n //\n // It carries the word, not just a shape: a placeholder distinguished only\n // by its dashed outline says nothing to a screen reader and nothing to\n // anyone who cannot separate it from a dark thumbnail (WCAG 1.4.1). The\n // live region is what announces it arriving and being replaced.\n for (let i = 0; i < pending; i++) {\n const slot = document.createElement(\"div\");\n slot.className = `${CLASSES.SCREENSHOT_ITEM} ${CLASSES.SCREENSHOT_PENDING}`;\n slot.setAttribute(\"role\", \"status\");\n slot.setAttribute(\"aria-live\", \"polite\");\n slot.textContent = strings.capturingScreenshot;\n container.appendChild(slot);\n }\n};\n\n/**\n * FileReader as a promise, so the attachment path can await a host's\n * transform after the read without nesting two callbacks. Resolves to null\n * on a read error rather than rejecting \u2014 a file the browser could not read\n * is not an exception, it is just nothing to attach.\n * @param {File} file\n * @returns {Promise<string | null>}\n */\nconst readAsDataUrl = (file) =>\n new Promise((resolve) => {\n const reader = new FileReader();\n reader.onload = (ev) => resolve(/** @type {string} */ (ev.target.result));\n reader.onerror = () => resolve(null);\n reader.readAsDataURL(file);\n });\n\n/**\n * Wires the hidden file input that feeds a pending-screenshots array,\n * enforcing MAX_SCREENSHOTS the same way on every attachment surface.\n * @param {HTMLInputElement} input\n * @param {() => string[]} getScreenshots\n * @param {() => void} rerender\n * @param {(dataUrl: string) => Promise<string>} [transform] the host's\n * screenshot transform, with the comment id already bound by the caller.\n * Omitted by the comment box, whose array is transformed at save instead.\n */\nexport const wireScreenshotInput = (\n input,\n getScreenshots,\n rerender,\n transform\n) => {\n input.addEventListener(\"change\", async (e) => {\n const file = /** @type {HTMLInputElement} */ (e.target).files[0];\n if (!file) return;\n // A non-image read into a data URL renders a broken <img> and bloats\n // the stored payload for nothing.\n if (file.type && !file.type.startsWith(\"image/\")) return;\n if (getScreenshots().length >= MAX_SCREENSHOTS) return;\n\n const pending = readAsDataUrl(file);\n // Cleared while the read is in flight, exactly as before: otherwise\n // picking the same file twice in a row fires no second change event.\n input.value = \"\";\n const dataUrl = await pending;\n if (!dataUrl) return;\n\n // Re-checked after the read, which is long enough for two quick picks to\n // both pass the check above and push past the cap together.\n const screenshots = getScreenshots();\n if (screenshots.length >= MAX_SCREENSHOTS) return;\n\n // The data URL goes in first, so the thumbnail appears the moment the\n // file is readable rather than when the host's upload finishes, and so a\n // reply sent mid-upload carries the image instead of nothing. The array\n // reference is held from here on: a submit reassigns the surface's\n // pending array, and the replacement below has to land in the array this\n // attachment was actually pushed into.\n screenshots.push(dataUrl);\n rerender();\n\n if (!transform) return;\n const value = await transform(dataUrl);\n // Located by value rather than by index: the user may have removed an\n // earlier thumbnail while the upload was in flight. Not found means it\n // was removed \u2014 or that a submit took the array with it, in which case\n // this mutates a detached array and the reply that went out keeps the\n // data URL. Degraded, never lost.\n const at = screenshots.indexOf(dataUrl);\n if (at === -1) return;\n screenshots[at] = value;\n rerender();\n });\n};\n\n/**\n * Every rendered screenshot thumbnail opens the lightbox the same way;\n * wired in one place so the five surfaces that render them cannot drift.\n * @param {ParentNode} root\n * @param {(src: string) => void} onShow\n */\nexport const wireScreenshotLightbox = (root, onShow) => {\n root\n .querySelectorAll(`.${CLASSES.SCREENSHOT_IMG}`)\n .forEach((/** @type {HTMLImageElement} */ img) => {\n makeThumbnailOperable(img, () => onShow(img.src));\n });\n};\n\n/**\n * A thumbnail that opens the lightbox is a control, not decoration: same\n * role=\"button\" + tabindex + Enter/Space pattern the marker circles use\n * (see DECISIONS.md, Accessibility). The img's alt is its accessible name.\n * @param {HTMLImageElement} img\n * @param {() => void} activate\n */\nconst makeThumbnailOperable = (img, activate) => {\n img.setAttribute(\"role\", \"button\");\n img.setAttribute(\"tabindex\", \"0\");\n img.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n activate();\n });\n img.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n activate();\n }\n });\n};\n\nexport const createCommentCircle = (comment, strings = defaultStrings) => {\n const circle = document.createElement(\"div\");\n circle.className = CLASSES.CIRCLE;\n circle.dataset.commentId = comment.id;\n circle.setAttribute(\"role\", \"button\");\n circle.setAttribute(\"tabindex\", \"0\");\n circle.setAttribute(\n \"aria-label\",\n `${strings.commentAriaLabelPrefix}${comment.text}`\n );\n\n // Basic positioning - will be updated by position validation system\n circle.style.cssText = `\n position: absolute;\n pointer-events: auto;\n `;\n\n return circle;\n};\n\nexport const createScreenshotsDisplay = (screenshots, strings) => {\n const container = document.createElement(\"div\");\n container.className = CLASSES.SCREENSHOTS_CONTAINER;\n container.classList.add(CLASSES.ACTIVE);\n\n screenshots.forEach((src) => {\n const item = document.createElement(\"div\");\n item.className = CLASSES.SCREENSHOT_ITEM;\n\n const img = document.createElement(\"img\");\n img.className = CLASSES.SCREENSHOT_IMG;\n img.src = src;\n img.alt = strings.attachedScreenshot;\n\n item.appendChild(img);\n container.appendChild(item);\n });\n\n return container;\n};\n\nexport const createTooltip = (comment, strings = defaultStrings, locale) => {\n const tooltip = document.createElement(\"div\");\n tooltip.className = CLASSES.TOOLTIP;\n tooltip.dataset.for = comment.id;\n tooltip.setAttribute(\"role\", \"dialog\");\n tooltip.setAttribute(\"aria-label\", strings.tooltipAriaLabel);\n\n const header = document.createElement(\"div\");\n header.className = CLASSES.THREAD_HEADER;\n\n const meta = createMetaElement(\n comment.author,\n comment.createdAt,\n strings,\n locale,\n comment.editedAt\n );\n const closeButton = document.createElement(\"button\");\n closeButton.type = \"button\";\n closeButton.className = CLASSES.CLOSE_TOOLTIP;\n closeButton.setAttribute(\"aria-label\", strings.close);\n closeButton.innerHTML = \"×\";\n\n header.appendChild(meta);\n header.appendChild(closeButton);\n\n const body = document.createElement(\"div\");\n body.className = CLASSES.THREAD_BODY;\n body.textContent = comment.text;\n\n tooltip.appendChild(header);\n tooltip.appendChild(body);\n // The tooltip is a read-only preview with no pickers, so the badges are\n // the only place its status/type/priority can be read at all.\n const badges = createBadgeRow(comment, strings, { includeStatus: true });\n if (badges) tooltip.appendChild(badges);\n const tooltipScreenshots = screenshotsOf(comment);\n if (tooltipScreenshots.length > 0) {\n tooltip.appendChild(createScreenshotsDisplay(tooltipScreenshots, strings));\n }\n\n // The preview shows the root comment only, so a thread with replies would\n // otherwise look like a lone remark. Omitted at zero rather than shown as\n // \"0 replies\": absence already says it, and a count of nothing is noise.\n const replyCount = comment.replies?.length || 0;\n if (replyCount > 0) {\n const replies = document.createElement(\"div\");\n replies.className = CLASSES.TOOLTIP_REPLY_COUNT;\n replies.textContent =\n replyCount === 1\n ? strings.replyCountOne\n : formatTemplate(strings.replyCountTemplate, replyCount);\n tooltip.appendChild(replies);\n }\n\n return tooltip;\n};\n\n/**\n * One reply inside a thread. `onDelete` and `onEdit` are optional so\n * read-only renderings (and any host that never wires them) keep the plain\n * row: the \u22EF menu is only built when there is something for it to do.\n *\n * `editing`, when present, replaces the body with the inline editor. The\n * draft it renders belongs to the caller \u2014 see `inline-editor.js` for why.\n *\n * `reactions`, when present, is the thread's reaction UI: it puts the palette\n * trigger on the meta line, next to the \u22EF, and the pill row under the text.\n * Optional for the same reason as the handlers above: a caller that never\n * wires it gets the plain row rather than controls that do nothing.\n *\n * `can`, with the `commentId` the reply hangs off, decides which of the two\n * the row offers. Optional and allow-all by default, matching the comment\n * strip: a caller with no policy gets the menu it always got.\n *\n * @param {any} reply\n * @param {object} [strings]\n * @param {string} [locale]\n * @param {{\n * onDelete?: (reply: any, replyEl: HTMLElement) => void,\n * onEdit?: (reply: any) => void,\n * commentId?: import(\"./index.d.ts\").CommentId,\n * can?: (action: import(\"./index.d.ts\").PermissionAction, target: import(\"./index.d.ts\").PermissionTarget) => boolean,\n * editing?: {\n * draft: string,\n * onInput: (text: string) => void,\n * onSave: (text: string) => void,\n * onCancel: () => void,\n * } | null,\n * reactions?: import(\"./reactions.js\").ReactionsUi | null,\n * }} [handlers]\n */\nexport const createReplyElement = (\n reply,\n strings = defaultStrings,\n locale,\n { onDelete, onEdit, commentId, can, editing = null, reactions = null } = {}\n) => {\n const replyEl = document.createElement(\"div\");\n replyEl.className = CLASSES.THREAD_REPLY;\n // The popover is built once and mutated in place, so anything that has to\n // find one reply again later \u2014 the editor, a text refresh \u2014 needs a handle.\n replyEl.dataset.replyId = String(reply.id);\n\n const meta = createMetaElement(\n reply.author,\n reply.timestamp,\n strings,\n locale,\n reply.editedAt\n );\n\n // Same \u22EF builder the comment strip uses, so a reply is edited and deleted\n // through the control the user already learned one level up.\n const items = [];\n const target = replyTargetOf(reply, commentId);\n const allow = (/** @type {any} */ action) =>\n can ? can(action, target) : true;\n if (onEdit && allow(\"edit:reply\")) {\n items.push({ label: strings.editReply, onSelect: () => onEdit(reply) });\n }\n if (onDelete && allow(\"delete:reply\")) {\n items.push({\n label: strings.deleteReply,\n onSelect: () => onDelete(reply, replyEl),\n confirm: () => ({\n title: strings.confirmDeleteReplyTitle,\n message: strings.confirmDeleteReplyMessage,\n confirmLabel: strings.confirmDelete,\n cancelLabel: strings.confirmCancel,\n }),\n });\n }\n // The reply's own tools, mirroring the comment's action row one level down:\n // react, then the \u22EF. Wrapped so `margin-left: auto` pushes the pair right\n // as one unit instead of only the first of them.\n const replyTools = document.createElement(\"div\");\n replyTools.className = `${CLASSES.ACTIONS_GROUP} ${CLASSES.THREAD_REPLY_ACTIONS}`;\n if (reactions) {\n replyTools.appendChild(\n reactions.trigger(reply, { className: CLASSES.INBOX_ACTION_BTN })\n );\n }\n if (items.length > 0) {\n replyTools.appendChild(\n createMoreMenu({ label: strings.replyOptions, items })\n );\n }\n if (replyTools.children.length > 0) meta.appendChild(replyTools);\n\n let text;\n if (editing) {\n text = createInlineEditor({\n value: editing.draft,\n strings,\n onInput: editing.onInput,\n onSave: editing.onSave,\n onCancel: editing.onCancel,\n });\n } else {\n text = document.createElement(\"div\");\n text.className = CLASSES.THREAD_BODY;\n text.textContent = reply.text;\n }\n\n replyEl.appendChild(meta);\n replyEl.appendChild(text);\n const replyScreenshots = screenshotsOf(reply);\n if (replyScreenshots.length > 0) {\n replyEl.appendChild(createScreenshotsDisplay(replyScreenshots, strings));\n }\n // Last child of the block it belongs to \u2014 after the text and after the\n // thumbnails. One rule, every surface.\n if (reactions) replyEl.appendChild(reactions.bar(reply));\n return replyEl;\n};\n\n/**\n * @param {any} comment\n * @param {object} [strings]\n * @param {string} [locale]\n * @param {{\n * onDeleteReply?: (reply: any, replyEl: HTMLElement) => void,\n * onEditReply?: (reply: any) => void,\n * can?: (action: import(\"./index.d.ts\").PermissionAction, target: import(\"./index.d.ts\").PermissionTarget) => boolean,\n * reactions?: import(\"./reactions.js\").ReactionsUi | null,\n * }} [handlers]\n */\nexport const createThreadPopover = (\n comment,\n strings = defaultStrings,\n locale,\n { onDeleteReply, onEditReply, can, reactions = null } = {}\n) => {\n const popover = document.createElement(\"div\");\n popover.className = CLASSES.THREAD_POPOVER;\n popover.dataset.for = comment.id;\n popover.setAttribute(\"role\", \"dialog\");\n popover.setAttribute(\"aria-label\", strings.popoverAriaLabel);\n\n const header = document.createElement(\"div\");\n header.className = CLASSES.THREAD_HEADER;\n\n const meta = createMetaElement(\n comment.author,\n comment.createdAt,\n strings,\n locale,\n comment.editedAt\n );\n const closeButton = document.createElement(\"button\");\n closeButton.type = \"button\";\n closeButton.className = CLASSES.CLOSE_TOOLTIP;\n closeButton.setAttribute(\"aria-label\", strings.close);\n closeButton.innerHTML = \"×\";\n\n header.appendChild(meta);\n header.appendChild(closeButton);\n\n const body = document.createElement(\"div\");\n body.className = CLASSES.THREAD_BODY;\n body.textContent = comment.text;\n\n const replies = document.createElement(\"div\");\n replies.className = CLASSES.THREAD_REPLIES;\n if (comment.replies) {\n comment.replies.forEach((reply) => {\n replies.appendChild(\n createReplyElement(reply, strings, locale, {\n onDelete: onDeleteReply,\n onEdit: onEditReply,\n commentId: comment.id,\n can,\n reactions,\n })\n );\n });\n }\n\n const { container: inputArea } = createInputArea(\n {\n areaClassName: CLASSES.THREAD_INPUT_AREA,\n inputTag: \"input\",\n inputClassName: CLASSES.THREAD_INPUT,\n inputPlaceholder: strings.replyPlaceholder,\n },\n strings\n );\n\n // The header (and the action row the overlay inserts after it) and the\n // reply box stay put; everything between them scrolls. Without this the\n // popover just grew past the viewport \u2014 expanding the context block or\n // adding replies made content unreachable, because the wheel event fell\n // through to the page.\n const scroll = document.createElement(\"div\");\n scroll.className = CLASSES.THREAD_SCROLL;\n\n popover.appendChild(header);\n scroll.appendChild(body);\n const popoverScreenshots = screenshotsOf(comment);\n if (popoverScreenshots.length > 0) {\n scroll.appendChild(createScreenshotsDisplay(popoverScreenshots, strings));\n }\n // The root comment's own bar, before the replies: it belongs to the text\n // above it, not to the conversation below.\n if (reactions) scroll.appendChild(reactions.bar(comment));\n scroll.appendChild(replies);\n popover.appendChild(scroll);\n popover.appendChild(inputArea);\n\n return popover;\n};\n", "// RF2 \u2014 the environment a comment was reported from, plus the automatic\n// capture taken at that moment.\n//\n// Two surfaces render it as a disclosure, differing only in where it starts:\n// the thread popover collapses it so the popover stays a conversation first\n// and a bug report second, while the inbox detail opens expanded because that\n// view is the one you go to in order to read everything.\n//\n// The caller owns the open/closed state \u2014 `expanded` in, `onToggle` out \u2014\n// because the inbox rebuilds its detail from scratch on every refresh, and a\n// block that remembered its own state would spring back open on the next\n// mutation.\n\nimport { CLASSES } from \"./constants.js\";\nimport { CARET_ICON_SVG, wireScreenshotLightbox } from \"./components.js\";\n\n/**\n * @param {any} comment\n * @param {{ strings: object, onShowLightbox: (src: string) => void,\n * collapsible?: boolean, expanded?: boolean,\n * onToggle?: (expanded: boolean) => void }} deps\n * @returns {HTMLElement | null} null for comments created before RF1/RF2\n */\nexport const createContextBlock = (\n comment,\n { strings, onShowLightbox, collapsible = false, expanded = false, onToggle }\n) => {\n const { context, contextScreenshot } = comment;\n if (!context && !contextScreenshot) return null;\n\n const block = document.createElement(\"div\");\n block.className = CLASSES.CONTEXT_BLOCK;\n\n const body = document.createElement(\"div\");\n body.className = CLASSES.CONTEXT_BODY;\n\n if (collapsible) {\n const toggle = document.createElement(\"button\");\n toggle.type = \"button\";\n toggle.className = CLASSES.CONTEXT_TOGGLE;\n toggle.setAttribute(\"aria-expanded\", String(expanded));\n toggle.innerHTML = `<span>${strings.contextSection}</span>${CARET_ICON_SVG}`;\n body.style.display = expanded ? \"\" : \"none\";\n toggle.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n const isOpen = toggle.getAttribute(\"aria-expanded\") === \"true\";\n toggle.setAttribute(\"aria-expanded\", String(!isOpen));\n body.style.display = isOpen ? \"none\" : \"\";\n onToggle?.(!isOpen);\n });\n block.appendChild(toggle);\n } else {\n const title = document.createElement(\"div\");\n title.className = CLASSES.CONTEXT_TITLE;\n title.textContent = strings.contextSection;\n block.appendChild(title);\n }\n\n if (contextScreenshot) {\n const caption = document.createElement(\"div\");\n caption.className = CLASSES.CONTEXT_SCREENSHOT_CAPTION;\n caption.textContent = strings.autoScreenshotLabel;\n body.appendChild(caption);\n\n const img = document.createElement(\"img\");\n img.className = CLASSES.SCREENSHOT_IMG;\n img.src = contextScreenshot;\n img.alt = strings.autoScreenshotLabel;\n body.appendChild(img);\n\n // Through the shared helper rather than a click listener of its own:\n // this thumbnail opens the lightbox like every other one, so it needs\n // the same role=\"button\" + tabindex + Enter/Space treatment. Wiring it\n // by hand here is exactly the drift the helper exists to prevent.\n wireScreenshotLightbox(body, onShowLightbox);\n }\n\n if (context) {\n const addRow = (label, value) => {\n if (!value) return;\n const row = document.createElement(\"div\");\n row.className = CLASSES.CONTEXT_ROW;\n const key = document.createElement(\"span\");\n key.textContent = label;\n const val = document.createElement(\"span\");\n val.textContent = value;\n row.appendChild(key);\n row.appendChild(val);\n body.appendChild(row);\n };\n\n const size = (dimensions) =>\n dimensions ? `${dimensions.width}\u00D7${dimensions.height}` : \"\";\n const named = (entry) =>\n entry?.name ? `${entry.name} ${entry.version || \"\"}`.trim() : \"\";\n\n addRow(strings.contextUrl, context.url);\n addRow(strings.contextViewport, size(context.viewport));\n addRow(strings.contextScreen, size(context.screen));\n addRow(strings.contextBrowser, named(context.browser));\n addRow(strings.contextOs, named(context.os));\n }\n\n block.appendChild(body);\n return block;\n};\n", "// Builds the plain-text context block the inbox \"copy\" action puts on the\n// clipboard \u2014 enough for a coding agent to locate the element and understand\n// the reported issue (page, viewport, selector, DOM path, thread).\n\nimport { formatDuration } from \"./i18n.js\";\n\nconst openingTagOf = (element) => {\n const attrs = [...element.attributes]\n .map(({ name, value }) => `${name}=\"${value}\"`)\n .join(\" \");\n return `<${element.tagName.toLowerCase()}${attrs ? ` ${attrs}` : \"\"}>`;\n};\n\nconst openingTagFromFingerprint = (fingerprint) => {\n if (!fingerprint?.tagName) return \"(unknown)\";\n const attrs = Object.entries(fingerprint.attributes || {})\n .map(([name, value]) => `${name}=\"${value}\"`)\n .join(\" \");\n return `<${fingerprint.tagName.toLowerCase()}${attrs ? ` ${attrs}` : \"\"}>`;\n};\n\n// body > main.flex.layout > section#pricing.plans \u2014 tag + id + up to two\n// classes per level, from body down to the element.\nconst domPathOf = (element) => {\n const segments = [];\n let current = element;\n while (current && current !== document.documentElement) {\n const tag = current.tagName.toLowerCase();\n const id = current.id ? `#${current.id}` : \"\";\n const classes = [...current.classList]\n .slice(0, 2)\n .map((cls) => `.${cls}`)\n .join(\"\");\n segments.unshift(`${tag}${id}${classes}`);\n if (current === document.body) break;\n current = current.parentElement;\n }\n return segments.join(\" > \");\n};\n\n/**\n * @param {import('./index.d.ts').Comment} comment\n * @param {{ viewportWidth: number, viewportHeight: number, strings?: object }} env\n * @returns {string}\n */\nexport function buildAgentContext(\n comment,\n { viewportWidth, viewportHeight, strings }\n) {\n const anchor = comment.anchor;\n const fingerprint = anchor?.fingerprint;\n const live = comment.container?.isConnected ? comment.container : null;\n\n const state = comment.hidden ? \"hidden\" : comment.anchorState;\n const element = live\n ? openingTagOf(live)\n : openingTagFromFingerprint(fingerprint);\n const path = live ? domPathOf(live) : \"(unavailable)\";\n\n // The reporter's viewport, not the reader's: `comment.context.viewport`\n // was captured at report time (RF2) and travels with the comment. Live\n // `viewportWidth`/`viewportHeight` (the copying browser's window) is only\n // a fallback for legacy records persisted before RF1/RF2 had no context.\n const capturedViewport = comment.context?.viewport;\n const reportedViewportWidth = capturedViewport?.width ?? viewportWidth;\n const reportedViewportHeight = capturedViewport?.height ?? viewportHeight;\n\n const lines = [\n `Page: ${comment.page}`,\n `Viewport: ${reportedViewportWidth}x${reportedViewportHeight}`,\n `Anchor state: ${state}`,\n `Status: ${comment.status || \"open\"}`,\n `Selector: ${anchor?.selector || \"(none)\"}`,\n `Element: ${element}`,\n `DOM path: ${path}`,\n `Nearby text: \"${fingerprint?.textSnippet ?? \"\"}\"`,\n `Comment by ${comment.author} (${comment.createdAt}):`,\n `\"${comment.text}\"`,\n ];\n\n // Neutral classification fields are omitted rather than printed as\n // \"(none)\" \u2014 the agent shouldn't read noise for data nobody filled in.\n if (comment.type) lines.push(`Type: ${comment.type}`);\n if (comment.priority) lines.push(`Priority: ${comment.priority}`);\n if (comment.tags?.length) lines.push(`Tags: ${comment.tags.join(\", \")}`);\n\n const context = comment.context;\n if (context) {\n if (context.url) lines.push(`URL: ${context.url}`);\n if (context.screen) {\n lines.push(`Screen: ${context.screen.width}x${context.screen.height}`);\n }\n if (context.browser?.name) {\n lines.push(\n `Browser: ${`${context.browser.name} ${context.browser.version || \"\"}`.trim()}`\n );\n }\n if (context.os?.name) {\n lines.push(\n `OS: ${`${context.os.name} ${context.os.version || \"\"}`.trim()}`\n );\n }\n }\n\n if (strings && comment.status === \"resolved\" && comment.resolvedAt) {\n const elapsed = formatDuration(\n new Date(comment.resolvedAt).getTime() -\n new Date(comment.createdAt).getTime(),\n strings\n );\n if (elapsed) lines.push(`Resolution time: ${elapsed}`);\n }\n\n const replies = comment.replies || [];\n if (replies.length > 0) {\n lines.push(`Replies (${replies.length}):`);\n for (const reply of replies) {\n lines.push(`- ${reply.author}: \"${reply.text}\"`);\n }\n }\n\n return lines.join(\"\\n\");\n}\n", "// The thread popover: lifecycle, in-place editing state, and the placement\n// math that keeps a floating panel pinned beside its marker.\n//\n// Extracted from CommentOverlay as part of splitting the god object\n// (DECISIONS.md, Fase 5). Pure view-controller: every data mutation flows\n// back through the `actions` contract, so this module never touches\n// storage, callbacks to the host app, or the markers themselves.\n\nimport { CLASSES, MARKER_SIZE } from \"./constants.js\";\nimport {\n createThreadPopover,\n createReplyElement,\n createEditedMark,\n cssAttrValue,\n renderScreenshotsPreview,\n wireScreenshotInput,\n wireScreenshotLightbox,\n} from \"./components.js\";\nimport { createCommentActions, copyToClipboard } from \"./comment-actions.js\";\nimport { createContextBlock } from \"./context-block.js\";\nimport { createReactionsUi } from \"./reactions.js\";\nimport { buildAgentContext } from \"./agent-context.js\";\nimport { buildCommentLink } from \"./link.js\";\nimport { createInlineEditor, confirmDiscard } from \"./inline-editor.js\";\nimport { sameId } from \"./id.js\";\n\n/**\n * Places a floating panel (tooltip or thread popover) beside a marker,\n * clamped to the viewport. Exported on its own because the hover tooltip\n * shares this exact placement without going through the controller.\n * @param {HTMLElement} el\n * @param {HTMLElement} circle\n */\nexport const positionPopoverAtCircle = (el, circle) => {\n const circleRect = circle.getBoundingClientRect();\n const centerX = circleRect.left + circleRect.width / 2;\n const centerY = circleRect.top + circleRect.height / 2;\n const circleBaseSize = MARKER_SIZE;\n const offset = circleBaseSize / 2 + 10;\n\n // Same reasoning as showCommentBox(): the tooltip and popover are\n // `min(400px, 100vw - 24px)` wide, so their real width has to be read.\n const elRect = el.getBoundingClientRect();\n const elWidth = elRect.width || 400;\n\n let x = centerX + offset;\n\n if (x + elWidth > window.innerWidth) {\n x = centerX - offset - elWidth;\n }\n x = Math.min(x, window.innerWidth - elWidth - 10);\n x = Math.max(10, x);\n el.style.left = `${x}px`;\n\n // Vertically the popover is anchored by whichever edge keeps it on\n // screen, and that choice is what makes it grow in the right direction.\n //\n // Pinning `top` alone was not enough: `max-height` caps the height but\n // says nothing about where the box starts, so a marker low on the page\n // put the top at, say, 600px and the popover simply ran off the bottom \u2014\n // taking the reply input with it, so you could not see what you were\n // typing. Re-clamping `top` on every growth would fight the user, since\n // each new reply would shift the whole thread upward under the cursor.\n //\n // Anchoring `bottom` instead makes the browser do it: the reply box stays\n // put and the thread extends upward until `max-height` takes over and\n // `.thread-scroll` starts scrolling.\n const margin = 10;\n const preferredTop = centerY - circleBaseSize / 2;\n const spaceBelow = window.innerHeight - margin - preferredTop;\n\n if (elRect.height > spaceBelow) {\n el.style.top = \"auto\";\n el.style.bottom = `${margin}px`;\n } else {\n el.style.bottom = \"auto\";\n el.style.top = `${Math.max(margin, preferredTop)}px`;\n }\n};\n\n/**\n * Centers a panel in the viewport \u2014 the placement for popovers with no\n * marker to pin to (orphaned comments opened from the inbox).\n * @param {HTMLElement} el\n */\nexport const centerPopover = (el) => {\n const elRect = el.getBoundingClientRect();\n const x = Math.max(10, (window.innerWidth - (elRect.width || 400)) / 2);\n const y = Math.max(10, (window.innerHeight - elRect.height) / 2);\n el.style.left = `${x}px`;\n // Explicitly cleared: this element may have been bottom-anchored by\n // positionPopoverAtCircle, and `top` alone would not win over it.\n el.style.bottom = \"auto\";\n el.style.top = `${y}px`;\n};\n\nexport class PopoverController {\n /**\n * @param {{\n * shadowRoot: ShadowRoot,\n * strings: Object,\n * locale: string,\n * findComment: (id: any) => any,\n * removeTooltip: (id: any) => void,\n * onShowLightbox: (src: string) => void,\n * isInsideLightbox: (target: any) => boolean,\n * linkParam: () => string,\n * refreshInbox: () => void,\n * actorKey: () => string,\n * can: (action: import(\"./index.d.ts\").PermissionAction, target: import(\"./index.d.ts\").PermissionTarget) => boolean,\n * transformScreenshot: Function,\n * actions: {\n * addReply: Function, deleteReply: Function,\n * editComment: Function, editReply: Function,\n * setStatus: Function, setType: Function, setPriority: Function,\n * deleteComment: Function,\n * toggleCommentReaction: Function, toggleReplyReaction: Function,\n * },\n * }} deps\n */\n constructor(deps) {\n this.deps = deps;\n /** The open popover element, or null. @type {HTMLElement | null} */\n this.active = null;\n /**\n * The marker the popover follows on scroll. Null for orphaned comments\n * opened from the inbox \u2014 those get centered instead.\n * @type {HTMLElement | null}\n */\n this._activeCircle = null;\n /**\n * The popover's open editor. Tracked as state even though the popover\n * mounts it straight into the DOM (it is built once and never\n * re-rendered): Escape, the close button and a click on the page all\n * need to know whether there is unsaved text before they act.\n * @type {{ commentId: any, replyId: any | null, draft: string } | null}\n */\n this.editing = null;\n /** @type {ResizeObserver | null} */\n this._resizeObserver = null;\n /** @type {((e: MouseEvent) => void) | null} */\n this._clickHandler = null;\n /**\n * Pending arm of `_clickHandler`. Held so `close()` can cancel it: the\n * listener goes on `document`, so a timer that outlives teardown installs\n * one nothing is left to remove.\n * @type {ReturnType<typeof setTimeout> | null}\n */\n this._armClickTimer = null;\n }\n\n /**\n * The body element of the root comment, or of one reply, inside the open\n * popover. Returns whatever is currently there \u2014 the text node or the\n * editor that replaced it.\n */\n _bodyEl(replyId = null) {\n const popover = this.active;\n if (!popover) return null;\n if (replyId == null) {\n return popover.querySelector(\n `.${CLASSES.THREAD_SCROLL} > .${CLASSES.THREAD_BODY}, .${CLASSES.THREAD_SCROLL} > .${CLASSES.EDITOR}`\n );\n }\n const row = popover.querySelector(\n `.${CLASSES.THREAD_REPLY}[data-reply-id=\"${cssAttrValue(replyId)}\"]`\n );\n return (\n row?.querySelector(`.${CLASSES.THREAD_BODY}, .${CLASSES.EDITOR}`) || null\n );\n }\n\n /**\n * Puts the edited text back on screen where the panels do not rebuild\n * themselves: the open thread quotes the text that just changed, and the\n * hover tooltip is thrown away on mouseleave so it needs nothing.\n */\n refreshCommentViews(id, replyId = null) {\n if (this.active?.dataset.for !== String(id)) return;\n const comment = this.deps.findComment(id);\n if (!comment) return;\n\n const source =\n replyId == null\n ? comment\n : (comment.replies || []).find((r) => sameId(r.id, replyId));\n if (!source) return;\n\n const body = document.createElement(\"div\");\n body.className = CLASSES.THREAD_BODY;\n body.textContent = source.text;\n this._bodyEl(replyId)?.replaceWith(body);\n\n // The \"edited\" mark belongs to the same meta line the author and time\n // are on, and it is absent until the first edit.\n const meta =\n replyId == null\n ? this.active.querySelector(`.${CLASSES.THREAD_META}`)\n : this.active\n .querySelector(\n `.${CLASSES.THREAD_REPLY}[data-reply-id=\"${cssAttrValue(replyId)}\"]`\n )\n ?.querySelector(`.${CLASSES.THREAD_META}`);\n if (\n meta &&\n source.editedAt &&\n !meta.querySelector(`.${CLASSES.THREAD_EDITED}`)\n ) {\n const editedEl = createEditedMark(\n source.editedAt,\n this.deps.strings,\n this.deps.locale\n );\n // Before the \u22EF, which `margin-left: auto` has pushed to the far right.\n const actions = meta.querySelector(`.${CLASSES.THREAD_REPLY_ACTIONS}`);\n if (actions) meta.insertBefore(editedEl, actions);\n else meta.appendChild(editedEl);\n }\n }\n\n /** True while the popover holds an editor at all. */\n isEditing() {\n return this.editing != null;\n }\n\n /** True while the popover holds an editor with unsaved text. */\n editorDirty() {\n if (!this.editing) return false;\n const { commentId, replyId, draft } = this.editing;\n const comment = this.deps.findComment(commentId);\n const source =\n replyId == null\n ? comment\n : (comment?.replies || []).find((r) => sameId(r.id, replyId));\n return draft.trim() !== String(source?.text || \"\").trim();\n }\n\n /**\n * Single gate in front of everything that would take the popover's editor\n * off screen. Mirrors InboxView.releaseEditor so the two panels answer the\n * same question the same way.\n * @returns {Promise<boolean>} true when the caller may proceed\n */\n async releaseEditor() {\n if (!this.editing) return true;\n if (this.editorDirty()) {\n const host = /** @type {any} */ (this.deps.shadowRoot);\n if (!(await confirmDiscard(host, this.deps.strings))) return false;\n }\n const { replyId } = this.editing;\n this.editing = null;\n this._restoreBody(replyId);\n return true;\n }\n\n _restoreBody(replyId) {\n const comment = this.deps.findComment(this.active?.dataset.for);\n if (!comment) return;\n const source =\n replyId == null\n ? comment\n : (comment.replies || []).find((r) => sameId(r.id, replyId));\n if (!source) return;\n\n const body = document.createElement(\"div\");\n body.className = CLASSES.THREAD_BODY;\n body.textContent = source.text;\n this._bodyEl(replyId)?.replaceWith(body);\n }\n\n /**\n * Opens the editor inside the popover. Unlike the inbox this mounts into\n * the DOM directly: the popover is built once and never re-rendered, so\n * there is nothing to survive. The draft is still tracked as state, because\n * Escape, the close button and a click outside all have to know whether\n * there is anything to lose.\n */\n async startEditing(commentId, replyId = null) {\n if (!(await this.releaseEditor())) return;\n\n const comment = this.deps.findComment(commentId);\n const source =\n replyId == null\n ? comment\n : (comment?.replies || []).find((r) => sameId(r.id, replyId));\n if (!source) return;\n\n this.editing = { commentId, replyId, draft: source.text };\n\n const editor = createInlineEditor({\n value: source.text,\n strings: this.deps.strings,\n onInput: (text) => {\n this.editing.draft = text;\n },\n onSave: (text) => {\n const saved =\n replyId == null\n ? this.deps.actions.editComment(commentId, text)\n : this.deps.actions.editReply(commentId, replyId, text);\n this.editing = null;\n if (saved) {\n this.refreshCommentViews(commentId, replyId);\n this.deps.refreshInbox();\n } else {\n this._restoreBody(replyId);\n }\n },\n onCancel: () => {\n this.releaseEditor();\n },\n });\n\n this._bodyEl(replyId)?.replaceWith(editor);\n }\n\n // `circle` may be null for orphaned comments (opened from the inbox):\n // the popover is centered in the viewport instead of pinned to a marker.\n show(circle, comment) {\n this.close();\n\n const { strings, locale } = this.deps;\n this.deps.removeTooltip(comment.id);\n\n // Keeps the selected marker visibly picked out while its thread is open\n // \u2014 same growth as hover, plus a ring, so it survives the pointer\n // leaving the circle to reach the popover.\n circle?.classList.add(CLASSES.CIRCLE_ACTIVE);\n this._activeCircle = circle || null;\n\n const onDeleteReply = (reply, replyEl) => {\n if (!this.deps.actions.deleteReply(comment.id, reply.id)) return;\n replyEl.remove();\n this.deps.refreshInbox();\n };\n\n // Named rather than inlined below: `submitReply` builds a reply row too,\n // and when this handler lived only in the call site there, the row the\n // user had just created came out with a \u22EF menu that could delete but not\n // edit \u2014 until the popover was reopened and the full render wired both.\n const onEditReply = (reply) => this.startEditing(comment.id, reply.id);\n\n // One reaction UI for the whole thread: it reports which target was\n // clicked, so the root comment and any reply route to their own toggle,\n // and it keeps the pill rows in step with the triggers above them \u2014 the\n // comment's trigger lives in the action row, which is assembled below,\n // after createThreadPopover has already built its pill row.\n //\n // Reused by submitReply too, so a reply created in this session gets the\n // same controls a reopened popover would render.\n const reactions = createReactionsUi({\n actorKey: this.deps.actorKey,\n strings,\n onToggle: (target, emoji) =>\n target === comment\n ? this.deps.actions.toggleCommentReaction(comment.id, emoji)\n : this.deps.actions.toggleReplyReaction(comment.id, target.id, emoji),\n });\n\n const popover = createThreadPopover(comment, strings, locale, {\n can: this.deps.can,\n onDeleteReply,\n onEditReply,\n reactions,\n });\n this.deps.shadowRoot.appendChild(popover);\n\n // Same action strip as the inbox cards: copy agent context, lifecycle\n // status picker (RF09) and the \u22EF menu.\n const headerEl = popover.querySelector(`.${CLASSES.THREAD_HEADER}`);\n const actionsEl = createCommentActions(comment, {\n can: this.deps.can,\n strings,\n reactions,\n onCopy: (c) =>\n copyToClipboard(\n buildAgentContext(c, {\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n strings,\n })\n ),\n onCopyLink: (c) =>\n copyToClipboard(buildCommentLink(c, this.deps.linkParam())),\n onEdit: (c) => this.startEditing(c.id),\n onSetStatus: (c, status) => this.deps.actions.setStatus(c.id, status),\n onSetType: (c, type) => this.deps.actions.setType(c.id, type),\n onSetPriority: (c, priority) =>\n this.deps.actions.setPriority(c.id, priority),\n onDelete: (c) => {\n this.close();\n this.deps.actions.deleteComment(c.id);\n this.deps.refreshInbox();\n },\n });\n // Its own row under the header, for the same reason the inbox card has\n // a footer: five controls sharing the header left the author ~90px and\n // truncated it mid-name.\n const actionsRow = document.createElement(\"div\");\n actionsRow.className = CLASSES.THREAD_ACTIONS_ROW;\n actionsRow.appendChild(actionsEl);\n headerEl.insertAdjacentElement(\"afterend\", actionsRow);\n\n // The root comment's gallery, not the reply box's pending previews.\n const mainScreenshotsContainer = popover.querySelector(\n `.${CLASSES.THREAD_SCROLL} > .${CLASSES.SCREENSHOTS_CONTAINER}`\n );\n if (mainScreenshotsContainer) {\n wireScreenshotLightbox(mainScreenshotsContainer, (src) =>\n this.deps.onShowLightbox(src)\n );\n }\n\n // RF2 \u2014 the automatic capture used to be reachable only from the inbox\n // detail. Collapsed by default so the popover stays a conversation\n // first; built here because it needs the lightbox callback.\n const contextBlock = createContextBlock(comment, {\n strings,\n onShowLightbox: (src) => this.deps.onShowLightbox(src),\n collapsible: true,\n });\n if (contextBlock) {\n // `.before()` rather than popover.insertBefore(): the replies live\n // inside the scroll container, not directly under the popover.\n popover.querySelector(`.${CLASSES.THREAD_REPLIES}`).before(contextBlock);\n }\n\n setTimeout(() => {\n if (circle) {\n positionPopoverAtCircle(popover, circle);\n } else {\n centerPopover(popover);\n }\n }, 10);\n\n popover\n .querySelector(`.${CLASSES.CLOSE_TOOLTIP}`)\n .addEventListener(\"click\", async (e) => {\n e.stopPropagation();\n // Unlike a click on the page, pressing \u00D7 is an unambiguous request\n // to close, so an unsaved draft is worth one question. The guard\n // short-circuits before the await, keeping the no-editor path\n // synchronous.\n if (this.editing && !(await this.releaseEditor())) return;\n this.close();\n });\n\n /** @type {HTMLInputElement} */\n const input = /** @type {any} */ (\n popover.querySelector(`.${CLASSES.THREAD_INPUT}`)\n );\n const submitBtn = popover.querySelector(`.${CLASSES.THREAD_SUBMIT}`);\n const threadAttachBtn = popover.querySelector(\n `.${CLASSES.THREAD_INPUT_AREA} .${CLASSES.ATTACH_IMAGE_BTN}`\n );\n /** @type {HTMLInputElement} */\n const threadFileInput = /** @type {any} */ (\n popover.querySelector(`.${CLASSES.THREAD_INPUT_AREA} input[type=\"file\"]`)\n );\n const threadScreenshotsContainer = popover.querySelector(\n `.${CLASSES.THREAD_INPUT_AREA} .${CLASSES.SCREENSHOTS_CONTAINER}`\n );\n\n let pendingReplyScreenshots = [];\n\n const updateReplyScreenshotsPreview = () => {\n renderScreenshotsPreview(\n threadScreenshotsContainer,\n pendingReplyScreenshots,\n {\n strings,\n onShow: (dataUrl) => this.deps.onShowLightbox(dataUrl),\n rerender: () => updateReplyScreenshotsPreview(),\n }\n );\n };\n\n threadAttachBtn.addEventListener(\"click\", () => {\n threadFileInput.click();\n });\n\n wireScreenshotInput(\n threadFileInput,\n () => pendingReplyScreenshots,\n updateReplyScreenshotsPreview,\n (dataUrl) => this.deps.transformScreenshot(dataUrl, comment.id)\n );\n\n const submitReply = () => {\n const text = input.value.trim();\n if (!text && pendingReplyScreenshots.length === 0) return;\n\n const reply = this.deps.actions.addReply(\n comment,\n text,\n pendingReplyScreenshots.length > 0 ? [...pendingReplyScreenshots] : []\n );\n\n const repliesContainer = popover.querySelector(\n `.${CLASSES.THREAD_REPLIES}`\n );\n const replyEl = createReplyElement(reply, strings, locale, {\n onDelete: onDeleteReply,\n onEdit: onEditReply,\n commentId: comment.id,\n can: this.deps.can,\n reactions,\n });\n repliesContainer.appendChild(replyEl);\n\n wireScreenshotLightbox(replyEl, (src) => this.deps.onShowLightbox(src));\n\n // Once the thread is taller than the popover the new reply lands below\n // the fold, so sending would look like nothing happened.\n const scrollEl = popover.querySelector(`.${CLASSES.THREAD_SCROLL}`);\n if (scrollEl) scrollEl.scrollTop = scrollEl.scrollHeight;\n\n input.value = \"\";\n pendingReplyScreenshots = [];\n updateReplyScreenshotsPreview();\n input.focus();\n };\n\n submitBtn.addEventListener(\"click\", submitReply);\n input.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n submitReply();\n }\n });\n\n this.active = popover;\n\n // Sending a reply, expanding the context block or a screenshot finishing\n // its decode all change the popover's height after it was placed. Without\n // re-running the anchor decision the box keeps the top it was given and\n // grows straight off the bottom of the viewport. Watching the element is\n // the one hook that covers every cause; guarded because jsdom has no\n // ResizeObserver.\n if (typeof ResizeObserver !== \"undefined\") {\n this._resizeObserver = new ResizeObserver(() => this.reposition());\n this._resizeObserver.observe(popover);\n }\n\n setTimeout(() => input.focus(), 50);\n\n // Deferred so the gesture that opened this popover cannot immediately\n // close it. Cancellable, because `close()` may run first.\n this._armClickTimer = setTimeout(() => {\n this._armClickTimer = null;\n this._clickHandler = (e) => {\n const target = /** @type {Node} */ (e.composedPath()[0] || e.target);\n if (\n !popover.contains(target) &&\n !circle?.contains(target) &&\n !this.deps.isInsideLightbox(target)\n ) {\n // A click on the page while text is unsaved is an ambiguous\n // gesture \u2014 maybe the user went to look at the thing they are\n // describing. Answering it with a modal would interrupt them, and\n // interrupting often teaches people to dismiss without reading. So\n // the panel simply stays put; the textarea still on screen says\n // everything the dialog would have.\n if (this.editorDirty()) return;\n this.close();\n }\n };\n document.addEventListener(\"mousedown\", this._clickHandler);\n }, 0);\n }\n\n close() {\n // Queried rather than remembered: the marker can be re-rendered while\n // its popover is open, and the stale reference would keep the class.\n this.deps.shadowRoot\n ?.querySelectorAll(`.${CLASSES.CIRCLE_ACTIVE}`)\n .forEach((/** @type {HTMLElement} */ el) =>\n el.classList.remove(CLASSES.CIRCLE_ACTIVE)\n );\n this._resizeObserver?.disconnect();\n this._resizeObserver = null;\n // Every route here has already answered for the draft, and the DOM it\n // lived in is about to go.\n this.editing = null;\n if (this.active) {\n this.active.remove();\n this.active = null;\n }\n this._activeCircle = null;\n if (this._armClickTimer) {\n clearTimeout(this._armClickTimer);\n this._armClickTimer = null;\n }\n if (this._clickHandler) {\n document.removeEventListener(\"mousedown\", this._clickHandler);\n this._clickHandler = null;\n }\n }\n\n /**\n * Re-runs the open popover's placement against its current size. Split\n * from syncToMarker because that one also decides visibility from the\n * marker, which is wrong here: a popover resizing while its marker is\n * off-screen must stay hidden, not reappear.\n */\n reposition() {\n const popover = this.active;\n if (!popover || popover.style.display === \"none\") return;\n\n if (this._activeCircle) {\n positionPopoverAtCircle(popover, this._activeCircle);\n } else {\n centerPopover(popover);\n }\n }\n\n /**\n * Keeps the open thread popover pinned beside its marker while the page\n * scrolls, and hides it while the marker is off-screen.\n *\n * Hidden, not closed: a half-typed reply must survive scrolling the marker\n * out of view and back. Closing here would also fight the outside-click\n * handler, which is the thing that legitimately dismisses the popover.\n */\n syncToMarker() {\n const popover = this.active;\n const circle = this._activeCircle;\n // A popover with no marker is the centered variant (orphaned comment\n // opened from the inbox); it has nothing to track.\n if (!popover || !circle) return;\n\n const rect = circle.getBoundingClientRect();\n const onScreen =\n circle.isConnected &&\n circle.style.display !== \"none\" &&\n rect.bottom > 0 &&\n rect.right > 0 &&\n rect.top < window.innerHeight &&\n rect.left < window.innerWidth;\n\n if (!onScreen) {\n popover.style.display = \"none\";\n return;\n }\n\n // Un-hide before measuring: a `display: none` element reports a zero\n // rect, and positionPopoverAtCircle sizes itself from that measurement.\n popover.style.display = \"\";\n positionPopoverAtCircle(popover, circle);\n }\n}\n", "// The marker engine: circle rendering, position math, occlusion hit-testing,\n// the batched rAF update loop, and every observer/listener that feeds it.\n//\n// Extracted from CommentOverlay as part of splitting the god object\n// (DECISIONS.md, Fase 5). The engine owns WHERE markers are and WHETHER they\n// are visible; what a marker click or hover opens (tooltip, thread popover)\n// belongs to the overlay and comes in through `wireMarker`, so this module\n// never learns about panels, storage or callbacks to the host app.\n\nimport { MARKER_SIZE } from \"./constants.js\";\nimport { createCommentCircle } from \"./components.js\";\nimport { TAG_NAME } from \"./root-element.js\";\n\n// How long a batched position pass may reuse the previous occlusion verdict\n// before hit-testing again. Scrolling schedules a pass per frame; occlusion\n// rarely changes mid-scroll, and the trailing pass settles the end state.\nconst OCCLUSION_INTERVAL_MS = 150;\n\n/**\n * Keeps a marker's point inside its container's box.\n *\n * Clamped to the box itself, not to `box - MARKER_SIZE`: reserving room for\n * the whole marker moved the point the user clicked whenever the container\n * was shorter or narrower than the marker (a 36px navbar row pulled every\n * marker up to 8px from its top). Only the marker's tip carries meaning, so\n * the point stays put and the marker's body overhangs instead.\n *\n * The one place this math lives \u2014 `updatePosition` and `scrollMarkerIntoView`\n * both derive from it, and they must never disagree about where a marker is.\n *\n * @param {number} offset position along one axis, in px from the box's edge\n * @param {number} size the box's length along that axis\n */\nconst clampToBox = (offset, size) => Math.max(0, Math.min(offset, size));\n\nexport class MarkerEngine {\n /**\n * @param {{\n * container: HTMLElement,\n * strings: Object,\n * getComments: () => any[],\n * wireMarker: (circle: HTMLElement, comment: any) => void,\n * onMarkerHidden: (comment: any) => void,\n * onVisibilityFlip: () => void,\n * onAfterPass: () => void,\n * }} deps `container` is the overlay element the circles mount into;\n * `wireMarker` is where the overlay attaches its tooltip/popover\n * handlers; `onMarkerHidden` dismisses UI floating over a marker that\n * just went away; `onAfterPass` runs after every rAF pass (the thread\n * popover follows its marker there).\n */\n constructor(deps) {\n this.deps = deps;\n\n /**\n * Marker circles by String(comment.id). The per-frame position loop\n * used to querySelector each one \u2014 a full shadow-tree scan per comment\n * per frame, O(n\u00B2) on scroll.\n * @type {Map<string, HTMLElement>}\n */\n this.circles = new Map();\n /** @type {Map<string, { circle: HTMLElement, observer: any, container: HTMLElement }>} */\n this.resizeObservers = new Map();\n /** Position validation gate \u2014 off means passes only sync the popover. */\n this.enabled = true;\n\n // Occlusion hit-testing (elementsFromPoint + getComputedStyle per\n // marker) is the expensive part of a position pass, and scrolling is\n // when passes are hottest \u2014 so batched passes run it at most once per\n // OCCLUSION_INTERVAL_MS, with a trailing pass to settle the final state.\n this._lastOcclusionPass = 0;\n this._occlusionTrailingTimer = null;\n\n // rAF scheduling flag for bulk updates\n this._pendingRaf = null;\n\n this._globalMutationObserver = null;\n this._resizeHandler = null;\n this._scrollHandler = null;\n this._loadHandler = null;\n }\n\n /** Attaches the window listeners and the page-wide mutation observer. */\n start() {\n this._resizeHandler = () => this.scheduleUpdate();\n window.addEventListener(\"resize\", this._resizeHandler, { passive: true });\n\n // Capture scroll on any scrolling ancestor\n this._scrollHandler = () => this.scheduleUpdate();\n window.addEventListener(\"scroll\", this._scrollHandler, {\n capture: true,\n passive: true,\n });\n\n // Update after resources load (images, fonts)\n this._loadHandler = () => this.scheduleUpdate();\n window.addEventListener(\"load\", this._loadHandler);\n\n // Modals open/close outside any comment's container (backdrops are\n // usually appended to <body> or toggled via style/class), so the\n // per-comment observers never see them. One page-wide observer keeps\n // the occlusion check honest; shadow-root internals don't bubble into\n // it, so our own marker updates can't retrigger it.\n if (window.MutationObserver) {\n this._globalMutationObserver = new MutationObserver(() => {\n this.scheduleUpdate();\n });\n this._globalMutationObserver.observe(document.body, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: [\"style\", \"class\", \"hidden\", \"open\"],\n });\n }\n }\n\n /** Creates, mounts, positions and observes one comment's marker. */\n render(comment) {\n const circle = createCommentCircle(comment, this.deps.strings);\n this.deps.wireMarker(circle, comment);\n\n this.deps.container.appendChild(circle);\n this.circles.set(String(comment.id), circle);\n this.updatePosition(comment, circle);\n\n // Size changes of the container are watched per comment (ResizeObserver\n // below); DOM mutations are watched once for the whole page by the\n // global observer in start() \u2014 a per-comment MutationObserver here\n // would fire N redundant callbacks per mutation batch on top of it.\n this.createResizeObserver(comment, circle);\n }\n\n /** The marker circle for a comment id, however the caller spells it. */\n circleOf(id) {\n return this.circles.get(String(id)) ?? null;\n }\n\n /** Removes one comment's marker and its observer. */\n remove(id) {\n this.cleanupResizeObserver(id);\n this.circles.get(String(id))?.remove();\n this.circles.delete(String(id));\n }\n\n /** Removes every marker and observer at once (bulk reset). */\n clear() {\n this.resizeObservers.forEach(({ observer }) => observer?.disconnect());\n this.resizeObservers.clear();\n this.circles.forEach((circle) => circle.remove());\n this.circles.clear();\n }\n\n /**\n * Validates and recalculates comment position based on container dimensions\n * @param {Object} comment - The comment object with position data\n * @param {HTMLElement} circle - The comment circle element\n * @returns {Object} - Validated position data\n */\n validateAndCalculatePosition(comment, circle) {\n if (!comment.container || !circle) return null;\n\n const containerRect = comment.container.getBoundingClientRect();\n const containerWidth = containerRect.width;\n const containerHeight = containerRect.height;\n\n // Zero size isn't an anomaly: it's what display:none (e.g. responsive\n // media queries) looks like. The caller hides the marker until the\n // element gets its size back.\n if (containerWidth <= 0 || containerHeight <= 0) {\n return null;\n }\n\n // Use simple relative positioning for consistent results\n const absoluteX = comment.relativeX * containerWidth;\n const absoluteY = comment.relativeY * containerHeight;\n\n const validatedX = clampToBox(absoluteX, containerWidth);\n const validatedY = clampToBox(absoluteY, containerHeight);\n\n // Recalculate relative position for future calculations\n const validatedRelativeX = validatedX / containerWidth;\n const validatedRelativeY = validatedY / containerHeight;\n\n return {\n absoluteX: validatedX,\n absoluteY: validatedY,\n relativeX: validatedRelativeX,\n relativeY: validatedRelativeY,\n containerWidth,\n containerHeight,\n containerLeft: containerRect.left,\n containerTop: containerRect.top,\n };\n }\n\n /**\n * The exact element the user clicked on can vanish (responsive\n * display:none) while its coarse anchor container stays visible. When we\n * have a live target \u2014 or can re-derive one from the serialized\n * targetSelector \u2014 the marker follows ITS visibility too.\n */\n _isAnchorTargetVisible(comment) {\n let target = comment.target;\n if ((!target || !target.isConnected) && comment.anchor?.targetSelector) {\n try {\n target = document.querySelector(comment.anchor.targetSelector);\n } catch {\n target = null;\n }\n comment.target = target || null;\n }\n if (!target || !target.isConnected) return true; // no signal \u2014 assume visible\n const rect = target.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n }\n\n /**\n * A marker floats above the whole page (own shadow host, high z-index),\n * so a host-page modal overlay can never cover it with CSS alone. Hit-test\n * the marker's point instead: if the topmost page element there is\n * unrelated to the comment's anchor (neither ancestor nor descendant),\n * something like a modal backdrop is covering it and the marker should\n * hide with it.\n */\n _isMarkerOccluded(comment, x, y) {\n if (typeof document.elementsFromPoint !== \"function\") return false;\n // Off-viewport points can't be hit-tested; the marker isn't visible\n // there anyway, so keep the current (visible) state.\n if (x < 0 || y < 0 || x >= window.innerWidth || y >= window.innerHeight) {\n return false;\n }\n const stack = document.elementsFromPoint(x, y);\n // Our own shadow host shows up first (the marker itself, toolbar, \u2026).\n const top = stack.find(\n (el) => el.tagName.toLowerCase() !== TAG_NAME.toLowerCase()\n );\n if (!top) return false;\n\n const target = comment.target?.isConnected ? comment.target : null;\n // The precise element the comment was left on (or its subtree /\n // ancestors) is what should be under the marker \u2014 never an occluder.\n if (target && (target.contains(top) || top.contains(target))) {\n return false;\n }\n\n const container = comment.container;\n if (!container?.isConnected) return false;\n // Something entirely unrelated to the anchor sits on top of it.\n if (!container.contains(top) && !top.contains(container)) return true;\n\n // `top` lives inside the anchor container \u2014 usually normal content of\n // the anchored subtree. But broad containers (body, page wrappers) also\n // contain the page's modals, so walk the chain up to the container: if\n // it crosses a modal-looking layer the anchor doesn't belong to, the\n // marker is covered after all.\n if (container.contains(top) && top !== container) {\n for (let el = top; el && el !== container; el = el.parentElement) {\n if (this._looksLikeModalLayer(el, target)) return true;\n }\n }\n return false;\n }\n\n /**\n * Heuristic for \"this element is a modal/backdrop layer\": explicit dialog\n * semantics, or a hit-testable fixed element covering most of the\n * viewport. Elements that contain the comment's own target are the layer\n * the comment lives in, never an occluder.\n */\n _looksLikeModalLayer(el, target) {\n if (target && el.contains(target)) return false;\n if (el.matches?.('dialog, [aria-modal=\"true\"], [role=\"dialog\"]')) {\n return true;\n }\n if (getComputedStyle(el).position !== \"fixed\") return false;\n const rect = el.getBoundingClientRect();\n return (\n rect.width >= window.innerWidth * 0.5 &&\n rect.height >= window.innerHeight * 0.5\n );\n }\n\n /**\n * Read half of a position update: layout reads only, no DOM writes, so a\n * batched pass can measure every marker before touching any style (an\n * interleaved read-write loop forces a fresh layout per marker).\n * @param {Object} comment\n * @param {HTMLElement} circle\n * @param {{ checkOcclusion?: boolean }} [options] when false, the pass\n * reuses the marker's previous occlusion verdict instead of hit-testing.\n * @returns {{ kind: \"resolved\" } | { kind: \"noop\" } | { kind: \"hidden\" }\n * | { kind: \"visible\", viewportX: number, viewportY: number,\n * relativeX: number, relativeY: number }}\n */\n _computeMarkerState(comment, circle, { checkOcclusion = true } = {}) {\n // Resolved comments have no on-page marker (RF09). This is not the\n // \"hidden\" state \u2014 the anchor is fine, the issue is just done.\n if (comment.status === \"resolved\") {\n return { kind: \"resolved\" };\n }\n\n let positionData = this.validateAndCalculatePosition(comment, circle);\n if (positionData && !this._isAnchorTargetVisible(comment)) {\n positionData = null;\n }\n if (!positionData) {\n // Anchor element currently invisible (zero-size container): hide the\n // marker; it comes back automatically when the observers fire again.\n return comment.container ? { kind: \"hidden\" } : { kind: \"noop\" };\n }\n\n // Offset so the circle's top-left tip (sharp corner) aligns with the stored position\n const circleRadius = MARKER_SIZE / 2;\n const viewportX =\n positionData.containerLeft + positionData.absoluteX + circleRadius;\n const viewportY =\n positionData.containerTop + positionData.absoluteY + circleRadius;\n\n // A host-page modal (or any unrelated overlay) covering the anchor also\n // hides the marker \u2014 it must not float above the modal's backdrop.\n if (checkOcclusion) {\n comment._occluded = this._isMarkerOccluded(comment, viewportX, viewportY);\n }\n if (comment._occluded) {\n return { kind: \"hidden\" };\n }\n\n return {\n kind: \"visible\",\n viewportX,\n viewportY,\n relativeX: positionData.relativeX,\n relativeY: positionData.relativeY,\n };\n }\n\n /**\n * Write half of a position update: styles and state only, no layout\n * reads.\n * @param {Object} comment\n * @param {HTMLElement} circle\n * @param {ReturnType<MarkerEngine[\"_computeMarkerState\"]>} state\n * @returns {boolean} true when the marker's hidden flag flipped \u2014 the\n * caller decides how to refresh the inbox (once per batch in the rAF\n * loop, immediately on direct calls).\n */\n _applyMarkerState(comment, circle, state) {\n if (state.kind === \"resolved\") {\n if (circle) circle.style.display = \"none\";\n return false;\n }\n if (state.kind === \"noop\") return false;\n\n const wasHidden = comment.hidden === true;\n if (state.kind === \"hidden\") {\n comment.hidden = true;\n circle.style.display = \"none\";\n // A marker that just went away must not leave its hover tooltip or\n // its open thread popover floating on the page.\n this.deps.onMarkerHidden(comment);\n return !wasHidden;\n }\n\n comment.hidden = false;\n circle.style.display = \"\";\n circle.style.left = `${state.viewportX}px`;\n circle.style.top = `${state.viewportY}px`;\n circle.style.transform = \"translate(-50%, -50%)\";\n circle.style.position = \"absolute\";\n\n comment.relativeX = state.relativeX;\n comment.relativeY = state.relativeY;\n return wasHidden;\n }\n\n /** One marker's position, refreshed now. */\n updatePosition(comment, circle = this.circles.get(String(comment.id))) {\n if (!circle) return;\n const state = this._computeMarkerState(comment, circle);\n const flipped = this._applyMarkerState(comment, circle, state);\n if (flipped) this.deps.onVisibilityFlip();\n }\n\n /**\n * One batched pass over every marker: measure everything, then write\n * everything, then refresh the inbox at most once \u2014 flipping N markers in\n * one frame used to rebuild the inbox N times from inside the loop.\n */\n _updateAllPositions() {\n const now = Date.now();\n const checkOcclusion =\n now - this._lastOcclusionPass >= OCCLUSION_INTERVAL_MS;\n if (checkOcclusion) {\n this._lastOcclusionPass = now;\n } else {\n // This pass reuses stale occlusion verdicts; make sure one more pass\n // runs after the burst settles so the end state is honest.\n this._armOcclusionTrailingPass();\n }\n\n const plans = [];\n for (const comment of this.deps.getComments()) {\n const circle = this.circles.get(String(comment.id));\n if (!circle) continue;\n plans.push([\n comment,\n circle,\n this._computeMarkerState(comment, circle, { checkOcclusion }),\n ]);\n }\n\n let anyFlipped = false;\n for (const [comment, circle, state] of plans) {\n if (this._applyMarkerState(comment, circle, state)) anyFlipped = true;\n }\n if (anyFlipped) this.deps.onVisibilityFlip();\n }\n\n _armOcclusionTrailingPass() {\n if (this._occlusionTrailingTimer) {\n clearTimeout(this._occlusionTrailingTimer);\n }\n this._occlusionTrailingTimer = setTimeout(() => {\n this._occlusionTrailingTimer = null;\n this.scheduleUpdate();\n }, OCCLUSION_INTERVAL_MS);\n }\n\n /** Coalesces any number of triggers into one rAF pass. */\n scheduleUpdate() {\n if (this._pendingRaf) return;\n this._pendingRaf = requestAnimationFrame(() => {\n this._pendingRaf = null;\n if (this.enabled) {\n this._updateAllPositions();\n }\n // Runs even with position validation off: the markers are placed in\n // viewport coordinates either way, so the popover has to follow.\n this.deps.onAfterPass();\n });\n }\n\n /**\n * Creates a ResizeObserver for a specific comment container\n * @param {Object} comment - The comment object\n * @param {HTMLElement} circle - The comment circle element\n */\n createResizeObserver(comment, circle) {\n if (!window.ResizeObserver) {\n console.warn(\n \"ResizeObserver not supported, position validation will be limited\"\n );\n return;\n }\n\n const observer = new ResizeObserver((entries) => {\n if (!this.enabled) return;\n\n for (const entry of entries) {\n // Only update if the container size actually changed\n if (entry.target === comment.container) {\n this.updatePosition(comment, circle);\n }\n }\n });\n\n // Start observing the container\n observer.observe(comment.container);\n\n // Store the observer for cleanup, keyed by String(id) like circles.\n this.resizeObservers.set(String(comment.id), {\n circle,\n observer,\n container: comment.container,\n });\n }\n\n cleanupResizeObserver(commentId) {\n // Keyed by String(id), exactly like the circles map: the caller may\n // hold the other spelling of a legacy numeric id, and a missed lookup\n // here leaks a live observer pointed at a detached circle.\n const key = String(commentId);\n if (this.resizeObservers.has(key)) {\n const { circle, observer } = this.resizeObservers.get(key);\n if (observer) {\n observer.disconnect();\n }\n if (circle && circle.parentNode) {\n circle.parentNode.removeChild(circle);\n }\n this.resizeObservers.delete(key);\n }\n }\n\n /**\n * Brings a comment's marker into view, centred vertically.\n *\n * Deliberately not `comment.container.scrollIntoView()`: the container is\n * the coarse anchor box (`section, div[class*=container|content]`), which\n * falls back to `<body>` whenever the commented element has no such\n * ancestor. Centring `<body>` lands halfway down the document \u2014 nowhere\n * near the marker, which is what made opening a comment from the inbox\n * jump to an unrelated section.\n *\n * @param {any} comment\n */\n scrollMarkerIntoView(comment) {\n const y = this._markerViewportY(comment);\n if (y == null) return;\n window.scrollTo({\n top: Math.max(0, window.scrollY + y - window.innerHeight / 2),\n });\n }\n\n /**\n * The marker's centre in viewport coordinates, derived from the anchor the\n * same way `updatePosition` derives it \u2014 through the same `clampToBox`, so\n * the two cannot disagree about where a marker sits.\n *\n * Deliberately not read off the rendered circle: the circle's coordinates\n * are only refreshed inside a rAF on scroll, so they are stale for any\n * caller that runs in the same tick as a scroll. The container's rect is\n * live, which makes this correct whenever it is asked.\n *\n * @param {any} comment\n * @returns {number | null} null when there is no anchor to resolve\n */\n _markerViewportY(comment) {\n const container = comment.container;\n if (!container?.isConnected) return null;\n const rect = container.getBoundingClientRect();\n if (rect.height <= 0) return null;\n\n const offsetY = clampToBox(comment.relativeY * rect.height, rect.height);\n return rect.top + offsetY + MARKER_SIZE / 2;\n }\n\n /** Cancels every scheduled pass, listener and observer. */\n destroy() {\n // A pass scheduled before teardown must not run against a destroyed\n // widget \u2014 cancel the pending frame and the trailing occlusion timer.\n if (this._pendingRaf) {\n cancelAnimationFrame(this._pendingRaf);\n this._pendingRaf = null;\n }\n if (this._occlusionTrailingTimer) {\n clearTimeout(this._occlusionTrailingTimer);\n this._occlusionTrailingTimer = null;\n }\n if (this._resizeHandler) {\n window.removeEventListener(\"resize\", this._resizeHandler);\n this._resizeHandler = null;\n }\n if (this._scrollHandler) {\n window.removeEventListener(\"scroll\", this._scrollHandler, {\n capture: true,\n });\n this._scrollHandler = null;\n }\n if (this._loadHandler) {\n window.removeEventListener(\"load\", this._loadHandler);\n this._loadHandler = null;\n }\n if (this._globalMutationObserver) {\n this._globalMutationObserver.disconnect();\n this._globalMutationObserver = null;\n }\n this.clear();\n }\n}\n", "// The audit trail as a disclosure in the inbox detail \u2014 closed by default,\n// with its open/closed state owned by the panel rather than the DOM, for the\n// same reason the context block's is: the detail is rebuilt on every\n// refresh(), and a flag living in the markup would fold itself back shut.\n\nimport { CLASSES } from \"./constants.js\";\nimport { formatTemplate, formatDuration } from \"./i18n.js\";\nimport {\n statusLabelOf,\n typeLabelOf,\n priorityLabelOf,\n} from \"./comment-actions.js\";\nimport { resolutionsOf } from \"./audit.js\";\n\nconst formatStamp = (iso, locale) =>\n new Intl.DateTimeFormat(locale, {\n month: \"short\",\n day: \"numeric\",\n hour: \"numeric\",\n minute: \"2-digit\",\n }).format(new Date(iso));\n\n// A move reads \"Status: Open \u2192 Resolved\" \u2014 the field name and both values\n// come from the dictionaries the pickers already use, so a state is never\n// called one thing in the picker and another in the trail.\nconst MOVES = {\n status: (strings) => [strings.statusLabel, (v) => statusLabelOf(v, strings)],\n type: (strings) => [strings.typeLabel, (v) => typeLabelOf(v, strings)],\n priority: (strings) => [\n strings.priorityLabel,\n (v) => priorityLabelOf(v, strings),\n ],\n};\n\nconst moveLabel = (field, entry, strings) => {\n const move = MOVES[field];\n if (!move) return \"\";\n const [name, valueOf] = move(strings);\n return `${name}: ${valueOf(entry.from ?? null)} \u2192 ${valueOf(entry.to ?? null)}`;\n};\n\nconst labelFor = (entry, strings) => {\n switch (entry.type) {\n case \"created\":\n return strings.auditCreated;\n case \"edited\":\n return strings.auditEdited;\n case \"status\":\n return moveLabel(\"status\", entry, strings);\n case \"classified\":\n // Tags are a list, so there is no two-value transition to render \u2014 the\n // change gets its own sentence instead of a malformed arrow.\n return entry.field === \"tags\"\n ? strings.auditTagsChanged\n : moveLabel(entry.field, entry, strings);\n default:\n return \"\";\n }\n};\n\nconst buildRow = (entry, strings, locale) => {\n const row = document.createElement(\"li\");\n row.className = CLASSES.AUDIT_ROW;\n\n const action = document.createElement(\"span\");\n action.className = CLASSES.AUDIT_ACTION;\n action.textContent = labelFor(entry, strings);\n\n // The display name, never the id: the id is identity, not copy \u2014 the rule\n // the reaction pills already follow.\n const actor = document.createElement(\"span\");\n actor.className = CLASSES.AUDIT_ACTOR;\n actor.textContent = entry.actor?.name || strings.anonymous;\n\n const time = document.createElement(\"time\");\n time.className = CLASSES.AUDIT_TIME;\n time.dateTime = entry.at;\n time.textContent = formatStamp(entry.at, locale);\n\n row.append(action, actor, time);\n return row;\n};\n\n/**\n * The resolutions a reopen superseded. Rendered only when one exists: a\n * comment resolved once already carries its elapsed time in the badge strip,\n * and repeating it here would be a third place for one fact.\n */\nconst buildResolutions = (comment, strings, locale) => {\n const superseded = resolutionsOf(comment).filter((r) => r.reopenedAt);\n if (superseded.length === 0) return null;\n\n const section = document.createElement(\"div\");\n section.className = CLASSES.AUDIT_RESOLUTIONS;\n section.dataset.auditResolutions = \"\";\n\n const heading = document.createElement(\"h4\");\n heading.className = CLASSES.AUDIT_HEADING;\n heading.textContent = strings.auditPreviousResolutions;\n section.appendChild(heading);\n\n const list = document.createElement(\"ul\");\n list.className = CLASSES.AUDIT_LIST;\n for (const resolution of superseded) {\n const item = document.createElement(\"li\");\n item.className = CLASSES.AUDIT_ROW;\n\n const action = document.createElement(\"span\");\n action.className = CLASSES.AUDIT_ACTION;\n action.textContent = formatTemplate(\n strings.auditResolvedInTemplate,\n formatDuration(resolution.ms, strings) || \"\u2014\"\n );\n\n const time = document.createElement(\"time\");\n time.className = CLASSES.AUDIT_TIME;\n time.dateTime = resolution.resolvedAt;\n time.textContent = formatStamp(resolution.resolvedAt, locale);\n\n item.append(action, time);\n list.appendChild(item);\n }\n section.appendChild(list);\n return section;\n};\n\n/**\n * @param {object} comment\n * @param {{\n * strings: Record<string, string>,\n * locale: string,\n * open: boolean,\n * onToggle: (open: boolean) => void,\n * }} deps\n * @returns {HTMLElement | null} null for a comment that predates the log, so\n * an older corpus shows nothing rather than an empty box\n */\nexport function createAuditTrail(comment, { strings, locale, open, onToggle }) {\n const history = comment?.history;\n if (!Array.isArray(history) || history.length === 0) return null;\n\n const wrapper = document.createElement(\"div\");\n wrapper.className = CLASSES.AUDIT_BLOCK;\n\n const toggle = document.createElement(\"button\");\n toggle.type = \"button\";\n toggle.className = CLASSES.AUDIT_TOGGLE;\n toggle.setAttribute(\"aria-expanded\", String(open));\n toggle.textContent = formatTemplate(\n strings.auditToggleTemplate,\n history.length\n );\n\n const body = document.createElement(\"div\");\n body.className = CLASSES.AUDIT_BODY;\n body.hidden = !open;\n\n const list = document.createElement(\"ul\");\n list.className = CLASSES.AUDIT_LIST;\n list.setAttribute(\"aria-label\", strings.auditTrailLabel);\n // Newest first: the question a trail answers is almost always \"what just\n // happened\", not \"how did this start\".\n for (const entry of [...history].reverse()) {\n list.appendChild(buildRow(entry, strings, locale));\n }\n body.appendChild(list);\n\n const resolutions = buildResolutions(comment, strings, locale);\n if (resolutions) body.appendChild(resolutions);\n\n toggle.addEventListener(\"click\", () => {\n const next = toggle.getAttribute(\"aria-expanded\") !== \"true\";\n toggle.setAttribute(\"aria-expanded\", String(next));\n body.hidden = !next;\n onToggle?.(next);\n });\n\n wrapper.append(toggle, body);\n return wrapper;\n}\n", "// Aggregate figures over a corpus of comments: counts by status, type and\n// priority, a temporal distribution, and the resolution times derived from\n// the audit log.\n//\n// Pure and synchronous. The corpus is already in memory and measured in tens\n// or hundreds, so nothing here is cached or incremental \u2014 recomputing on\n// every open is cheaper than keeping a second copy of the truth in sync.\n\nimport { STATUSES, COMMENT_TYPES, PRIORITIES } from \"./constants.js\";\nimport { currentResolutionMs, resolutionsOf } from \"./audit.js\";\n\n/** Comments carry `null` for a deliberately unset type or priority. */\nexport const UNSET = \"unset\";\n\nconst HOUR_MS = 3_600_000;\n\nconst countInto = (keys, extraKey) => {\n const out = {};\n for (const key of keys) out[key] = 0;\n if (extraKey) out[extraKey] = 0;\n return out;\n};\n\nconst median = (sorted) => {\n if (sorted.length === 0) return null;\n const middle = Math.floor(sorted.length / 2);\n return sorted.length % 2\n ? sorted[middle]\n : (sorted[middle - 1] + sorted[middle]) / 2;\n};\n\n/**\n * @param {import('./index.d.ts').SerializedComment[]} comments\n * @returns {import('./index.d.ts').CommentMetrics}\n */\nexport function computeMetrics(comments) {\n const list = Array.isArray(comments) ? comments : [];\n\n const byStatus = countInto(STATUSES);\n const byType = countInto(COMMENT_TYPES, UNSET);\n const byPriority = countInto(PRIORITIES, UNSET);\n const perDay = new Map();\n const durations = [];\n let reopenedCount = 0;\n\n for (const comment of list) {\n const status = STATUSES.includes(comment.status) ? comment.status : \"open\";\n byStatus[status]++;\n\n byType[COMMENT_TYPES.includes(comment.type) ? comment.type : UNSET]++;\n byPriority[\n PRIORITIES.includes(comment.priority) ? comment.priority : UNSET\n ]++;\n\n // Only the days that saw activity get a bucket. Filling the gaps would\n // put 365 empty bars between two comments a year apart, which is a chart\n // nobody can read \u2014 the axis labels say which days these are.\n const day = String(comment.createdAt || \"\").slice(0, 10);\n if (day) perDay.set(day, (perDay.get(day) || 0) + 1);\n\n const elapsed = currentResolutionMs(comment);\n if (elapsed !== null) durations.push(elapsed);\n if (resolutionsOf(comment).length > 1) reopenedCount++;\n }\n\n durations.sort((a, b) => a - b);\n const total = durations.reduce((sum, ms) => sum + ms, 0);\n\n return {\n total: list.length,\n // Built by walking STATUSES / COMMENT_TYPES / PRIORITIES, so every key the\n // public type promises is present \u2014 which the checker cannot see through\n // a loop over a string[].\n byStatus:\n /** @type {Record<import('./index.d.ts').CommentStatus, number>} */ (\n byStatus\n ),\n byType:\n /** @type {Record<import('./index.d.ts').CommentType | \"unset\", number>} */ (\n byType\n ),\n byPriority:\n /** @type {Record<import('./index.d.ts').CommentPriority | \"unset\", number>} */ (\n byPriority\n ),\n overTime: [...perDay.entries()]\n .sort(([a], [b]) => (a < b ? -1 : 1))\n .map(([date, count]) => ({ date, count })),\n resolution: {\n resolvedCount: durations.length,\n // Both, because they answer different questions: one comment left open\n // for a month drags the mean somewhere no real comment lives, and the\n // median says what the team's typical turnaround actually is.\n averageMs: durations.length ? total / durations.length : null,\n medianMs: median(durations),\n reopenedCount,\n },\n };\n}\n\n/** Exported for the report, which prints hours rather than raw milliseconds. */\nexport const toHours = (ms) =>\n ms === null || ms === undefined ? null : Math.round((ms / HOUR_MS) * 10) / 10;\n", "// The metrics dashboard, rendered inside the inbox panel.\n//\n// Every chart here is hand-drawn: horizontal bars are two divs and a width,\n// and the daily distribution is a handful of <rect>s. The lightest charting\n// library measured 22.66 KB gzip against ~13 KB of headroom, and none of them\n// would have satisfied this repo's own rule anyway \u2014 a <canvas> carries no\n// text, and no figure here is allowed to communicate through length or colour\n// alone (WCAG 1.4.1). Each bar states its count beside it.\n\nimport {\n CLASSES,\n STATUSES,\n COMMENT_TYPES,\n PRIORITIES,\n STATUS_COLORS,\n TYPE_COLORS,\n PRIORITY_COLORS,\n} from \"./constants.js\";\nimport { formatDuration } from \"./i18n.js\";\nimport {\n statusLabelOf,\n typeLabelOf,\n priorityLabelOf,\n} from \"./comment-actions.js\";\nimport { UNSET } from \"./metrics.js\";\n\n// The bucket for comments left deliberately unclassified. The pickers paint\n// its dot `transparent` \u2014 there is nothing to show \u2014 but a bar still has a\n// count to draw, so it takes the neutral the bars used before they were\n// coloured at all rather than vanishing.\nconst UNSET_COLOR = \"rgba(255,255,255,0.42)\";\n\nconst CHART_WIDTH = 300;\nconst CHART_HEIGHT = 96;\nconst SVG_NS = \"http://www.w3.org/2000/svg\";\n\nconst el = (tag, className, text) => {\n const node = document.createElement(tag);\n if (className) node.className = className;\n if (text !== undefined) node.textContent = String(text);\n return node;\n};\n\nconst svgEl = (tag, attrs = {}) => {\n const node = document.createElementNS(SVG_NS, tag);\n for (const [name, value] of Object.entries(attrs)) {\n node.setAttribute(name, String(value));\n }\n return node;\n};\n\nconst tile = (label, value) => {\n const box = el(\"div\", CLASSES.METRICS_TILE);\n box.appendChild(el(\"span\", CLASSES.METRICS_TILE_VALUE, value));\n box.appendChild(el(\"span\", CLASSES.METRICS_TILE_LABEL, label));\n return box;\n};\n\n/**\n * One dimension as labelled horizontal bars. Bars are scaled against the\n * busiest bucket rather than the total: against the total, a corpus spread\n * evenly across four statuses would draw four slivers.\n *\n * Each bar takes the colour its own picker already uses for that value, so a\n * chip and its bar are recognisably the same thing. The colour is\n * reinforcement, never the signal: the row states its label and its count as\n * text either side of the bar, so nothing here is lost to a reader who cannot\n * tell the hues apart (WCAG 1.4.1).\n *\n * @param {Array<{ label: string, count: number, color: string }>} entries\n */\nconst barGroup = (name, heading, entries) => {\n const group = el(\"div\", CLASSES.METRICS_GROUP);\n group.dataset.metricsGroup = name;\n group.appendChild(el(\"h4\", CLASSES.METRICS_HEADING, heading));\n\n const max = Math.max(1, ...entries.map((entry) => entry.count));\n for (const { label, count, color } of entries) {\n const row = el(\"div\", CLASSES.METRICS_ROW);\n row.dataset.metricsRow = \"\";\n\n row.appendChild(el(\"span\", CLASSES.METRICS_ROW_LABEL, label));\n\n const track = el(\"div\", CLASSES.METRICS_TRACK);\n const bar = el(\"div\", CLASSES.METRICS_BAR);\n bar.dataset.metricsBar = \"\";\n bar.style.width = `${Math.round((count / max) * 100)}%`;\n bar.style.background = color;\n track.appendChild(bar);\n row.appendChild(track);\n\n row.appendChild(el(\"span\", CLASSES.METRICS_ROW_COUNT, count));\n group.appendChild(row);\n }\n return group;\n};\n\n/**\n * The daily distribution. An <svg role=\"img\"> with an aria-label carrying the\n * summary, and a <title> per column so a pointer \u2014 and an accessibility tree \u2014\n * can read each day without the chart having to become a table.\n */\nconst dailyChart = (overTime, strings) => {\n const group = el(\"div\", CLASSES.METRICS_GROUP);\n group.dataset.metricsGroup = \"overTime\";\n group.appendChild(el(\"h4\", CLASSES.METRICS_HEADING, strings.metricsOverTime));\n\n const max = Math.max(1, ...overTime.map(({ count }) => count));\n const svg = svgEl(\"svg\", {\n class: CLASSES.METRICS_CHART,\n viewBox: `0 0 ${CHART_WIDTH} ${CHART_HEIGHT}`,\n preserveAspectRatio: \"none\",\n role: \"img\",\n \"aria-label\": `${strings.metricsOverTime}: ${overTime\n .map(({ date, count }) => `${date} ${count}`)\n .join(\", \")}`,\n });\n\n const slot = CHART_WIDTH / overTime.length;\n const barWidth = Math.max(2, Math.min(slot - 3, 28));\n overTime.forEach(({ date, count }, index) => {\n const height = Math.max(2, (count / max) * (CHART_HEIGHT - 6));\n const rect = svgEl(\"rect\", {\n x: index * slot + (slot - barWidth) / 2,\n y: CHART_HEIGHT - height,\n width: barWidth,\n height,\n rx: 2,\n });\n rect.appendChild(\n Object.assign(svgEl(\"title\"), { textContent: `${date}: ${count}` })\n );\n svg.appendChild(rect);\n });\n group.appendChild(svg);\n\n // Only the ends are labelled: a tick per day turns into overlapping text\n // the moment a corpus spans more than a fortnight.\n const axis = el(\"div\", CLASSES.METRICS_AXIS);\n axis.appendChild(el(\"span\", null, overTime[0].date));\n if (overTime.length > 1) {\n axis.appendChild(el(\"span\", null, overTime[overTime.length - 1].date));\n }\n group.appendChild(axis);\n\n return group;\n};\n\nconst exportBar = (strings, handlers) => {\n const bar = el(\"div\", CLASSES.METRICS_EXPORTS);\n bar.setAttribute(\"aria-label\", strings.metricsExportLabel);\n\n const button = (key, label, onClick) => {\n const btn = el(\"button\", CLASSES.METRICS_EXPORT_BTN, label);\n btn.type = \"button\";\n btn.dataset.export = key;\n btn.addEventListener(\"click\", onClick);\n return btn;\n };\n\n bar.appendChild(\n button(\"comments\", strings.metricsExportComments, handlers.onExportComments)\n );\n bar.appendChild(\n button(\"metrics\", strings.metricsExportMetrics, handlers.onExportMetrics)\n );\n bar.appendChild(button(\"print\", strings.metricsPrint, handlers.onPrint));\n return bar;\n};\n\n/**\n * @param {import('./index.d.ts').CommentMetrics} metrics\n * @param {{\n * strings: ReturnType<typeof import('./i18n.js').getStrings>,\n * locale: string,\n * onExportComments: () => void,\n * onExportMetrics: () => void,\n * onPrint: () => void,\n * }} deps\n * @returns {HTMLElement}\n */\nexport function createMetricsView(metrics, deps) {\n const { strings } = deps;\n const view = el(\"div\", CLASSES.METRICS_VIEW);\n\n if (metrics.total === 0) {\n // Nothing to export either: three buttons that would hand back an empty\n // file are worse than no buttons.\n view.appendChild(el(\"p\", CLASSES.METRICS_EMPTY, strings.metricsEmpty));\n return view;\n }\n\n const duration = (ms) => (ms === null ? \"\u2014\" : formatDuration(ms, strings));\n\n const tiles = el(\"div\", CLASSES.METRICS_TILES);\n tiles.appendChild(tile(strings.metricsTotal, metrics.total));\n tiles.appendChild(\n tile(strings.statusResolved, metrics.resolution.resolvedCount)\n );\n tiles.appendChild(\n tile(strings.metricsReopened, metrics.resolution.reopenedCount)\n );\n tiles.appendChild(\n tile(\n strings.metricsAverageResolution,\n duration(metrics.resolution.averageMs)\n )\n );\n tiles.appendChild(\n tile(strings.metricsMedianResolution, duration(metrics.resolution.medianMs))\n );\n view.appendChild(tiles);\n\n view.appendChild(\n barGroup(\n \"status\",\n strings.metricsByStatus,\n STATUSES.map((key) => ({\n label: statusLabelOf(key, strings),\n count: metrics.byStatus[key],\n color: STATUS_COLORS[key],\n }))\n )\n );\n view.appendChild(\n barGroup(\"type\", strings.metricsByType, [\n ...COMMENT_TYPES.map((key) => ({\n label: typeLabelOf(key, strings),\n count: metrics.byType[key],\n color: TYPE_COLORS[key],\n })),\n {\n label: strings.unset,\n count: metrics.byType[UNSET],\n color: UNSET_COLOR,\n },\n ])\n );\n view.appendChild(\n barGroup(\"priority\", strings.metricsByPriority, [\n ...PRIORITIES.map((key) => ({\n label: priorityLabelOf(key, strings),\n count: metrics.byPriority[key],\n color: PRIORITY_COLORS[key],\n })),\n {\n label: strings.unset,\n count: metrics.byPriority[UNSET],\n color: UNSET_COLOR,\n },\n ])\n );\n\n if (metrics.overTime.length) {\n view.appendChild(dailyChart(metrics.overTime, strings));\n }\n\n view.appendChild(exportBar(strings, deps));\n return view;\n}\n", "// Right-side inbox sidebar: a filterable list of every comment and a detail\n// view with the full thread. Pure view layer \u2014 all data mutations flow back\n// to CommentOverlay through the callbacks contract passed to the\n// constructor, so this module never touches storage or the page markers.\n\nimport { CLASSES, COMMENT_TYPES, PRIORITIES, STATUSES } from \"./constants.js\";\nimport { buildAgentContext } from \"./agent-context.js\";\nimport { attachMenuToggle } from \"./menus.js\";\nimport { createContextBlock } from \"./context-block.js\";\nimport { createAuditTrail } from \"./audit-timeline.js\";\nimport { createMetricsView } from \"./metrics-view.js\";\nimport { computeMetrics } from \"./metrics.js\";\nimport {\n createCommentActions,\n copyToClipboard,\n statusLabelOf,\n typeLabelOf,\n priorityLabelOf,\n} from \"./comment-actions.js\";\nimport {\n CARET_ICON_SVG,\n circleSelector,\n createMetaElement,\n renderScreenshotsPreview,\n wireScreenshotInput,\n wireScreenshotLightbox,\n createScreenshotsDisplay,\n createInputArea,\n createReplyElement,\n createBadgeRow,\n getShortcutText,\n} from \"./components.js\";\nimport { sameId } from \"./id.js\";\nimport { createReactionsUi, reactionEntriesOf } from \"./reactions.js\";\nimport { createInlineEditor, confirmDiscard } from \"./inline-editor.js\";\nimport { buildCommentLink } from \"./link.js\";\n\nconst CHEVRON_LEFT_SVG = `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"15 18 9 12 15 6\"/></svg>`;\nconst ARROW_UP_SVG = `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"18 15 12 9 6 15\"/></svg>`;\nconst ARROW_DOWN_SVG = `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"6 9 12 15 18 9\"/></svg>`;\n\nexport class InboxView {\n /**\n * @param {Object} deps\n * @param {ShadowRoot} deps.shadowRoot\n * @param {Object} deps.strings\n * @param {string} deps.locale\n * @param {string} deps.currentPage\n * @param {() => Array<Object>} deps.getComments\n * @param {{ shortcutKey?: string, shortcutModifier?: string, linkParam?: string }} [deps.options]\n * @param {{ onOpenDetailScroll: Function, onOpenDetail?: Function, onTransformScreenshot?: Function, onReply: Function, onDelete: Function, onDeleteReply: Function, onEditComment: Function, onEditReply: Function, onSetStatus: Function, onSetType: Function, onSetPriority: Function, onNavigateToPage: Function, onShowLightbox: Function, onActivateCommentMode: Function, onClose: Function, actorKey: () => string, can: (action: import(\"./index.d.ts\").PermissionAction, target: import(\"./index.d.ts\").PermissionTarget) => boolean, onToggleCommentReaction: Function, onToggleReplyReaction: Function, onExportComments: Function, onExportMetrics: Function, onPrintReport: Function }} deps.callbacks\n */\n constructor({\n shadowRoot,\n strings,\n locale,\n currentPage,\n getComments,\n callbacks,\n options = {},\n }) {\n this.shadowRoot = shadowRoot;\n this.strings = strings;\n this.locale = locale;\n this.currentPage = currentPage;\n this.getComments = getComments;\n this.callbacks = callbacks;\n // Only the shortcut config is read, and only to teach the chord in the\n // empty state.\n this.options = options;\n this.pageFilter = \"page\"; // \"all\" | \"page\"\n this.statusFilter = \"all\"; // \"all\" | STATUSES\n this.typeFilter = \"all\"; // \"all\" | COMMENT_TYPES\n this.priorityFilter = \"all\"; // \"all\" | PRIORITIES\n this.detailId = null;\n /**\n * Whether the detail view's context disclosure is open. State rather than\n * DOM for the same reason as the editor below \u2014 the detail is rebuilt on\n * every refresh \u2014 and one flag for the panel rather than one per comment,\n * so folding it away stays folded while stepping through comments with\n * prev/next.\n */\n this.contextExpanded = true;\n /**\n * Whether the detail view's audit trail is open. Closed on arrival,\n * unlike the context block: the trail answers a question you go looking\n * for, while the context is what you came to read. State for the same\n * reason as its sibling \u2014 the detail is rebuilt on every refresh.\n */\n this.auditExpanded = false;\n /**\n * Whether the panel is showing the metrics dashboard instead of the list.\n * A mode rather than a second panel: the sidebar is already where every\n * comment is looked at, and the figures are about those comments.\n */\n this.showMetrics = false;\n /**\n * The one open editor, as state rather than DOM.\n *\n * This panel re-renders from ten places, and the overlay refreshes it\n * from seven more. A draft living only in a textarea would be wiped by\n * any of them \u2014 changing a comment's priority mid-sentence would eat the\n * sentence. Keeping it here means every one of those rebuilds is\n * harmless, and leaves only the deliberate exits to ask about.\n * @type {{ commentId: any, replyId: any | null, draft: string } | null}\n */\n this.editing = null;\n /** @type {string | null} */\n this.notice = null;\n /**\n * Keyed card cache: String(id) \u2192 the live comment object, the\n * fingerprint of what its card renders, and the card node itself. A\n * refresh reuses a node whose comment and fingerprint both still match\n * \u2014 which is what keeps thumbnails decoded and the list's scroll\n * position intact across the many refreshes this panel receives.\n * @type {Map<string, { comment: any, fingerprint: string, card: HTMLElement }>}\n */\n this._cardBindings = new Map();\n /** @type {HTMLElement | null} */\n this.el = null;\n /** @type {HTMLElement | null} */\n this._highlightedEl = null;\n /** @type {HTMLElement | null} */\n this._activeMarkerEl = null;\n /**\n * Built on first use by _reactionsUi().\n * @type {import(\"./reactions.js\").ReactionsUi | null}\n */\n this._reactions = null;\n }\n\n isOpen() {\n return Boolean(this.el);\n }\n\n open() {\n if (this.el) return;\n this.el = document.createElement(\"div\");\n this.el.className = CLASSES.INBOX_PANEL;\n this.el.setAttribute(\"role\", \"dialog\");\n this.el.setAttribute(\"aria-label\", this.strings.inboxAriaLabel);\n // Announced as a dialog, so keyboard focus has to arrive with it \u2014\n // otherwise the user tabs across the whole page to reach the panel.\n this.el.setAttribute(\"tabindex\", \"-1\");\n this.shadowRoot.appendChild(this.el);\n this.render();\n this.el.focus();\n }\n\n close() {\n this._clearHighlight();\n this._setActiveMarker(null);\n this.el?.remove();\n this.el = null;\n this._cardBindings.clear();\n this.detailId = null;\n // Every route to here already went through releaseEditor(), so anything\n // still sitting in the draft has been answered for.\n this.editing = null;\n this.notice = null;\n }\n\n refresh() {\n if (this.el) this.render();\n }\n\n /** Back to the default view: current page, no status/type/priority. */\n _resetFilters() {\n this.pageFilter = \"page\";\n this.statusFilter = \"all\";\n this.typeFilter = \"all\";\n this.priorityFilter = \"all\";\n this.render();\n }\n\n /**\n * A line at the top of the list. Exists for one case: someone followed a\n * \"Copy link\" URL to a comment this page cannot show them. It stays until\n * the comment arrives (the overlay retries on every loadComments) or the\n * user navigates away from it.\n * @param {string} text\n */\n showNotice(text) {\n this.notice = text;\n this.refresh();\n }\n\n clearNotice() {\n if (!this.notice) return;\n this.notice = null;\n this.refresh();\n }\n\n /** True while an editor holds text the user has not saved. */\n isDirty() {\n if (!this.editing) return false;\n return this.editing.draft.trim() !== this._editingOriginalText().trim();\n }\n\n _editingOriginalText() {\n if (!this.editing) return \"\";\n const comment = this.getComments().find((c) =>\n sameId(c.id, this.editing.commentId)\n );\n if (!comment) return \"\";\n if (this.editing.replyId == null) return comment.text || \"\";\n const reply = (comment.replies || []).find((r) =>\n sameId(r.id, this.editing.replyId)\n );\n return reply?.text || \"\";\n }\n\n /**\n * Every path that would take the editor off screen funnels through here,\n * so the question is asked once and in one place instead of at each of the\n * exits (Cancel, Escape, the \u22EF of another comment, the close button, the\n * prev/next arrows, Back).\n * @returns {Promise<boolean>} true when the caller may proceed\n */\n async releaseEditor() {\n if (!this.editing) return true;\n if (this.isDirty()) {\n const host = /** @type {any} */ (this.el || this.shadowRoot);\n if (!(await confirmDiscard(host, this.strings))) return false;\n }\n this.editing = null;\n return true;\n }\n\n /**\n * The handlers every editor in this panel shares. Split out because the\n * comment body and a reply body are built by different components but must\n * behave identically \u2014 a draft that saved from one place and discarded\n * from the other would be two features wearing one look.\n */\n _editorHandlers() {\n return {\n draft: this.editing.draft,\n onInput: (text) => {\n this.editing.draft = text;\n },\n onSave: (text) => {\n const { commentId, replyId } = this.editing;\n if (replyId == null) this.callbacks.onEditComment(commentId, text);\n else this.callbacks.onEditReply(commentId, replyId, text);\n this.editing = null;\n this.render();\n },\n onCancel: async () => {\n if (await this.releaseEditor()) this.render();\n },\n };\n }\n\n _buildEditor() {\n const handlers = this._editorHandlers();\n return createInlineEditor({\n value: handlers.draft,\n strings: this.strings,\n onInput: handlers.onInput,\n onSave: handlers.onSave,\n onCancel: handlers.onCancel,\n });\n }\n\n /** Opens the editor on a comment body, or on one of its replies. */\n async startEditing(commentId, replyId = null) {\n if (!(await this.releaseEditor())) return;\n this.detailId = commentId;\n this.editing = { commentId, replyId, draft: \"\" };\n this.editing.draft = this._editingOriginalText();\n this.render();\n }\n\n /**\n * The on-page marker for a comment, when there is one to decorate.\n * Resolved, orphaned and hidden comments render no circle at all.\n * @param {any} comment\n * @returns {HTMLElement | null}\n */\n _markerFor(comment) {\n if (\n comment.anchorState !== \"anchored\" ||\n comment.hidden ||\n comment.status === \"resolved\"\n ) {\n return null;\n }\n return /** @type {any} */ (\n this.shadowRoot.querySelector(circleSelector(comment.id))\n );\n }\n\n _highlight(comment) {\n this._clearHighlight();\n const circle = this._markerFor(comment);\n if (!circle) return;\n circle.classList.add(CLASSES.HIGHLIGHT);\n this._highlightedEl = circle;\n }\n\n _clearHighlight() {\n this._highlightedEl?.classList.remove(CLASSES.HIGHLIGHT);\n this._highlightedEl = null;\n }\n\n /**\n * Opening a comment's detail selects it just as clicking its marker does,\n * so the marker gets the same active state the thread popover gives it.\n * Passing null clears it \u2014 the list view has nothing selected.\n * @param {any} comment\n */\n _setActiveMarker(comment) {\n this._activeMarkerEl?.classList.remove(CLASSES.CIRCLE_ACTIVE);\n this._activeMarkerEl = null;\n if (!comment) return;\n const circle = this._markerFor(comment);\n if (!circle) return;\n circle.classList.add(CLASSES.CIRCLE_ACTIVE);\n this._activeMarkerEl = circle;\n }\n\n filteredComments() {\n let comments = this.getComments();\n if (this.pageFilter === \"page\") {\n comments = comments.filter(\n (comment) => comment.page === this.currentPage\n );\n }\n if (this.statusFilter !== \"all\") {\n // `open` is the implicit default: comments saved before RF09 have no\n // status at all and must still match the \"open\" chip.\n comments = comments.filter(\n (comment) => (comment.status || \"open\") === this.statusFilter\n );\n }\n if (this.typeFilter !== \"all\") {\n comments = comments.filter((comment) => comment.type === this.typeFilter);\n }\n if (this.priorityFilter !== \"all\") {\n comments = comments.filter(\n (comment) => comment.priority === this.priorityFilter\n );\n }\n // Resolved sink to the bottom; both partitions keep their original order.\n return [\n ...comments.filter((comment) => comment.status !== \"resolved\"),\n ...comments.filter((comment) => comment.status === \"resolved\"),\n ];\n }\n\n render() {\n if (!this.el) return;\n this._clearHighlight();\n const comments = this.filteredComments();\n const detail =\n this.detailId != null\n ? comments.find((comment) => sameId(comment.id, this.detailId))\n : null;\n // Set before rendering so a detail reached by any route \u2014 a card click,\n // the prev/next nav, the cross-page handoff \u2014 marks its marker.\n this._setActiveMarker(detail);\n if (this.showMetrics) {\n this._cardBindings.clear();\n this.el.innerHTML = \"\";\n this._renderMetrics(comments);\n return;\n }\n if (detail) {\n // The detail shows one comment: a full rebuild is cheap and keeps the\n // editing and reply wiring simple. Leaving the list view drops its\n // keyed state; the skeleton is rebuilt on the way back.\n this._cardBindings.clear();\n this.el.innerHTML = \"\";\n this._renderDetail(detail, comments);\n } else {\n this.detailId = null;\n this._renderList(comments);\n }\n }\n\n /**\n * Opens the panel (if needed) directly on a comment's detail. Used by\n * the overlay for the cross-page handoff on startup.\n * @param {import('./index.d.ts').CommentId} id\n */\n openDetail(id) {\n if (!this.el) this.open();\n const comment = this.getComments().find((c) => sameId(c.id, id));\n if (comment) this._openDetail(comment);\n }\n\n _openDetail(comment) {\n this.detailId = comment.id;\n if (comment.anchorState === \"anchored\" && !comment.hidden) {\n this.callbacks.onOpenDetailScroll(comment);\n }\n this.render();\n // Reported after the render, so a host acting on it sees a settled\n // panel. Only genuine opens reach here \u2014 refresh() and render() read\n // `detailId` rather than going through this.\n this.callbacks.onOpenDetail?.(comment);\n }\n\n _closeButton() {\n const btn = document.createElement(\"button\");\n btn.type = \"button\";\n btn.className = CLASSES.INBOX_CLOSE;\n btn.setAttribute(\"aria-label\", this.strings.close);\n btn.innerHTML = \"×\";\n // `this.editing &&` short-circuits before the await, so with no editor\n // open this handler stays synchronous \u2014 closing the panel must not\n // become a microtask later just because editing exists as a feature.\n btn.addEventListener(\"click\", async () => {\n if (this.editing && !(await this.releaseEditor())) return;\n this.callbacks.onClose();\n });\n return btn;\n }\n\n _metricsButton() {\n const btn = document.createElement(\"button\");\n btn.type = \"button\";\n btn.className = CLASSES.INBOX_METRICS_BTN;\n btn.textContent = this.strings.metricsOpen;\n btn.setAttribute(\"aria-label\", this.strings.metricsTitle);\n btn.addEventListener(\"click\", async () => {\n if (this.editing && !(await this.releaseEditor())) return;\n this.showMetrics = true;\n this.detailId = null;\n this.render();\n });\n return btn;\n }\n\n /**\n * The dashboard measures the comments the panel is currently showing, not\n * the whole corpus: the filter summary sits right above it, so the figures\n * answer \"what am I looking at\". A host wanting the unfiltered aggregate\n * calls `overlay.getMetrics()`.\n */\n _renderMetrics(comments) {\n const header = document.createElement(\"div\");\n header.className = CLASSES.INBOX_DETAIL_HEADER;\n\n const backBtn = document.createElement(\"button\");\n backBtn.type = \"button\";\n backBtn.className = CLASSES.INBOX_BACK;\n backBtn.innerHTML = `${CHEVRON_LEFT_SVG}<span>${this.strings.back}</span>`;\n backBtn.addEventListener(\"click\", () => {\n this.showMetrics = false;\n this.render();\n });\n header.appendChild(backBtn);\n\n const nav = document.createElement(\"div\");\n nav.className = CLASSES.INBOX_CARD_ACTIONS;\n nav.appendChild(this._closeButton());\n header.appendChild(nav);\n this.el.appendChild(header);\n\n const scope = this._filterSummaryLabel();\n this.el.appendChild(\n createMetricsView(computeMetrics(comments), {\n strings: this.strings,\n locale: this.locale,\n onExportComments: () => this.callbacks.onExportComments(comments),\n onExportMetrics: () => this.callbacks.onExportMetrics(comments),\n onPrint: () => this.callbacks.onPrintReport(comments, scope),\n })\n );\n }\n\n _renderList(comments) {\n // Persistent skeleton: the header and the scrolling list are built once\n // and survive every refresh. Replacing the list wholesale (the old\n // innerHTML = \"\" render) reset its scroll position and re-decoded every\n // thumbnail whenever anything anywhere changed.\n let list = [...this.el.children].find((el) =>\n el.classList.contains(CLASSES.INBOX_LIST)\n );\n if (!list) {\n this.el.innerHTML = \"\";\n this._cardBindings.clear();\n const header = document.createElement(\"div\");\n header.className = CLASSES.INBOX_HEADER;\n this.el.appendChild(header);\n list = document.createElement(\"div\");\n list.className = CLASSES.INBOX_LIST;\n this.el.appendChild(list);\n }\n\n // The header is label-driven (the filter summary changes with every\n // selection) and holds no scroll or image state \u2014 rebuilt each pass.\n const header = [...this.el.children].find((el) =>\n el.classList.contains(CLASSES.INBOX_HEADER)\n );\n // One cluster, not three loose children: under the header's\n // `space-between`, a third child lands adrift in the middle instead of\n // beside the control it belongs with \u2014 the same scattering the detail\n // header already had to group its way out of.\n const actions = document.createElement(\"div\");\n actions.className = CLASSES.INBOX_HEADER_ACTIONS;\n actions.append(this._metricsButton(), this._closeButton());\n header.replaceChildren(this._buildFilter(), actions);\n\n this._reconcileCards(list, comments);\n }\n\n /**\n * Everything a list card renders, captured as a comparable string. An\n * equal fingerprint for the same live comment object means the existing\n * node can be reused as-is \u2014 listeners, decoded thumbnails and all. The\n * object identity check matters because loadComments REPLACES comment\n * objects: a reused card whose closures held the stale object would\n * mutate a comment the overlay no longer owns.\n */\n /**\n * The panel's reaction UI, built once so a toggle in the detail view also\n * repaints the list card's pill row when that card is still on screen.\n *\n * The parent comment of a reply is looked up rather than captured: one UI\n * then serves the list, the detail and every reply row in it, and the rows\n * stay registered against the same targets across refreshes.\n */\n _reactionsUi() {\n if (!this._reactions) {\n this._reactions = createReactionsUi({\n actorKey: () => this.callbacks.actorKey(),\n strings: this.strings,\n onToggle: (target, emoji) => {\n const parent = this.getComments().find((comment) =>\n (comment.replies || []).includes(target)\n );\n if (parent) {\n this.callbacks.onToggleReplyReaction(parent.id, target.id, emoji);\n } else {\n this.callbacks.onToggleCommentReaction(target.id, emoji);\n }\n },\n });\n }\n return this._reactions;\n }\n\n _cardFingerprint(comment) {\n return JSON.stringify([\n comment.text,\n comment.editedAt ?? null,\n comment.status ?? \"open\",\n comment.type ?? null,\n comment.priority ?? null,\n comment.tags ?? [],\n comment.resolvedAt ?? null,\n // The resolution badge is derived from the log, so a card whose log\n // grew has to repaint. Length plus the last stamp rather than the whole\n // array: appending is the only thing that happens to it.\n comment.history?.length ?? 0,\n comment.history?.at(-1)?.at ?? null,\n comment.anchorState,\n comment.hidden === true,\n comment.page,\n comment.screenshots?.length ?? 0,\n // Counts, not actor keys: what the card renders is the pill and its\n // number. Without this entry a card whose reactions changed keeps its\n // cached node and the counts freeze on screen.\n reactionEntriesOf(comment).map(({ emoji, authors }) => [\n emoji,\n authors.length,\n ]),\n ]);\n }\n\n _reconcileCards(list, comments) {\n // The notice and the empty state are stateless one-offs \u2014 always\n // rebuilt, and removed up front so they never count as \"out of place\"\n // cards during the ordering walk below.\n for (const el of [...list.children]) {\n if (\n el.classList.contains(CLASSES.INBOX_NOTICE) ||\n el.classList.contains(CLASSES.INBOX_EMPTY)\n ) {\n el.remove();\n }\n }\n\n const desired = [];\n if (this.notice) {\n const notice = document.createElement(\"div\");\n notice.className = CLASSES.INBOX_NOTICE;\n notice.setAttribute(\"role\", \"status\");\n notice.textContent = this.notice;\n desired.push(notice);\n }\n\n const seen = new Set();\n if (comments.length === 0) {\n desired.push(this._buildEmptyState());\n } else {\n for (const comment of comments) {\n const key = String(comment.id);\n const fingerprint = this._cardFingerprint(comment);\n const binding = this._cardBindings.get(key);\n let card;\n if (\n binding &&\n binding.comment === comment &&\n binding.fingerprint === fingerprint\n ) {\n card = binding.card;\n } else {\n card = this._buildCard(comment, { interactive: true });\n this._cardBindings.set(key, { comment, fingerprint, card });\n }\n seen.add(key);\n desired.push(card);\n }\n }\n\n for (const key of [...this._cardBindings.keys()]) {\n if (!seen.has(key)) this._cardBindings.delete(key);\n }\n\n // Minimal-move ordering: only nodes that are out of place are touched,\n // so an untouched tail keeps its position \u2014 and the container, which is\n // never replaced, keeps its scroll.\n desired.forEach((node, index) => {\n if (list.children[index] !== node) {\n list.insertBefore(node, list.children[index] ?? null);\n }\n });\n while (list.children.length > desired.length) {\n list.lastElementChild.remove();\n }\n }\n\n /**\n * Two different nothings, and telling a user the wrong one wastes their\n * time: an inbox with no comments at all needs teaching (what the shortcut\n * is, how to place the first one), while an inbox whose filters happen to\n * exclude everything needs the filters relaxed. Offering \"turn on comment\n * mode\" to someone who already has twenty comments would be nonsense.\n */\n _buildEmptyState() {\n const empty = document.createElement(\"div\");\n empty.className = CLASSES.INBOX_EMPTY;\n\n // An outline of the marker the user is about to place, not a generic\n // placeholder \u2014 same teardrop silhouette the circles use.\n const icon = document.createElement(\"div\");\n icon.className = CLASSES.INBOX_EMPTY_ICON;\n icon.setAttribute(\"aria-hidden\", \"true\");\n empty.appendChild(icon);\n\n const hasAnyComment = this.getComments().length > 0;\n\n const title = document.createElement(\"div\");\n title.className = CLASSES.INBOX_EMPTY_TITLE;\n title.textContent = hasAnyComment\n ? this.strings.inboxNoMatches\n : this.strings.inboxEmptyTitle;\n empty.appendChild(title);\n\n if (hasAnyComment) {\n const clear = document.createElement(\"button\");\n clear.type = \"button\";\n clear.className = CLASSES.INBOX_EMPTY_ACTION;\n clear.textContent = this.strings.filterClear;\n clear.addEventListener(\"click\", () => {\n this._resetFilters();\n });\n empty.appendChild(clear);\n return empty;\n }\n\n const text = document.createElement(\"div\");\n text.className = CLASSES.INBOX_EMPTY_TEXT;\n // Split on the placeholder so the chord can be a real <kbd> rather than\n // bare text, without taking the sentence apart in the locale files.\n const [before, after] = String(this.strings.inboxEmptyHintTemplate).split(\n \"{n}\"\n );\n const kbd = document.createElement(\"kbd\");\n kbd.className = CLASSES.INBOX_EMPTY_KBD;\n kbd.textContent = getShortcutText(this.options, this.strings);\n text.appendChild(document.createTextNode(before ?? \"\"));\n text.appendChild(kbd);\n text.appendChild(document.createTextNode(after ?? \"\"));\n empty.appendChild(text);\n\n const action = document.createElement(\"button\");\n action.type = \"button\";\n action.className = CLASSES.INBOX_EMPTY_ACTION;\n action.textContent = this.strings.inboxEmptyAction;\n action.addEventListener(\"click\", () =>\n this.callbacks.onActivateCommentMode()\n );\n empty.appendChild(action);\n\n return empty;\n }\n\n _pageFilterLabel(value) {\n return value === \"all\"\n ? this.strings.filterAll\n : this.strings.filterCurrentPage;\n }\n\n /**\n * Summary label for the collapsed filter button. The page filter always\n * contributes (it's either \"All pages\" or \"Current page\"); status, type,\n * and priority only join in when active, so an active filter is never\n * hidden from a user who hasn't opened the menu.\n */\n _filterSummaryLabel() {\n const parts = [this._pageFilterLabel(this.pageFilter)];\n if (this.statusFilter !== \"all\") {\n parts.push(statusLabelOf(this.statusFilter, this.strings));\n }\n if (this.typeFilter !== \"all\") {\n parts.push(typeLabelOf(this.typeFilter, this.strings));\n }\n if (this.priorityFilter !== \"all\") {\n parts.push(priorityLabelOf(this.priorityFilter, this.strings));\n }\n return parts.join(\" \u00B7 \");\n }\n\n _isFilterActive() {\n return (\n this.pageFilter !== \"page\" ||\n this.statusFilter !== \"all\" ||\n this.typeFilter !== \"all\" ||\n this.priorityFilter !== \"all\"\n );\n }\n\n /**\n * One chip group. Status, type and priority chips toggle: activating the\n * chip that is already on clears the group back to \"all\", which is why\n * they carry no explicit \"All\" chip. The page group does \u2014 it has no\n * neutral state, it's always one of two answers.\n *\n * @param {{ title: string, dataAttr: string, values: string[],\n * labelOf: (value: string) => string, selected: string,\n * toggles?: boolean, onSelect: (value: string) => void }} config\n */\n _buildFilterGroup({\n title,\n dataAttr,\n values,\n labelOf,\n selected,\n toggles = true,\n onSelect,\n }) {\n const group = document.createElement(\"div\");\n group.className = CLASSES.INBOX_FILTER_GROUP;\n\n const heading = document.createElement(\"div\");\n heading.className = CLASSES.INBOX_FILTER_SECTION;\n heading.textContent = title;\n group.appendChild(heading);\n\n const chips = document.createElement(\"div\");\n chips.className = CLASSES.INBOX_FILTER_CHIPS;\n // The page chips are role=\"radio\" (exactly one active) and radios must\n // sit in a radiogroup; the toggling groups are switches, plain group.\n chips.setAttribute(\"role\", toggles ? \"group\" : \"radiogroup\");\n chips.setAttribute(\"aria-label\", title);\n\n for (const value of values) {\n const checked = selected === value;\n const chip = document.createElement(\"button\");\n chip.type = \"button\";\n chip.className = CLASSES.INBOX_FILTER_CHIP;\n chip.dataset[dataAttr] = value;\n chip.setAttribute(\"role\", toggles ? \"switch\" : \"radio\");\n chip.setAttribute(\"aria-checked\", String(checked));\n chip.textContent = labelOf(value);\n chip.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n onSelect(toggles && checked ? \"all\" : value);\n this.render();\n });\n chips.appendChild(chip);\n }\n\n group.appendChild(chips);\n return group;\n }\n\n _buildFilter() {\n const wrapper = document.createElement(\"div\");\n wrapper.className = CLASSES.INBOX_FILTER + \"-wrapper\";\n\n const btn = document.createElement(\"button\");\n btn.type = \"button\";\n btn.className = CLASSES.INBOX_FILTER;\n btn.setAttribute(\"aria-haspopup\", \"true\");\n btn.setAttribute(\"aria-expanded\", \"false\");\n btn.innerHTML = `<span>${this._filterSummaryLabel()}</span>${CARET_ICON_SVG}`;\n\n const menu = document.createElement(\"div\");\n menu.className = CLASSES.INBOX_FILTER_MENU;\n menu.setAttribute(\"role\", \"group\");\n menu.setAttribute(\"aria-label\", this.strings.filterTitle);\n\n attachMenuToggle(btn, menu);\n\n const header = document.createElement(\"div\");\n header.className = CLASSES.INBOX_FILTER_MENU_HEADER;\n\n const title = document.createElement(\"span\");\n title.textContent = this.strings.filterTitle;\n header.appendChild(title);\n\n const clear = document.createElement(\"button\");\n clear.type = \"button\";\n clear.className = CLASSES.INBOX_FILTER_CLEAR;\n clear.textContent = this.strings.filterClear;\n clear.disabled = !this._isFilterActive();\n clear.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n this._resetFilters();\n });\n header.appendChild(clear);\n menu.appendChild(header);\n\n menu.appendChild(\n this._buildFilterGroup({\n title: this.strings.filterByPage,\n dataAttr: \"filterPage\",\n values: [\"page\", \"all\"],\n labelOf: (value) => this._pageFilterLabel(value),\n selected: this.pageFilter,\n toggles: false,\n onSelect: (value) => (this.pageFilter = value),\n })\n );\n\n menu.appendChild(\n this._buildFilterGroup({\n title: this.strings.filterByStatus,\n dataAttr: \"filterStatus\",\n values: [...STATUSES],\n labelOf: (value) => statusLabelOf(value, this.strings),\n selected: this.statusFilter,\n onSelect: (value) => (this.statusFilter = value),\n })\n );\n\n menu.appendChild(\n this._buildFilterGroup({\n title: this.strings.filterByType,\n dataAttr: \"filterType\",\n values: [...COMMENT_TYPES],\n labelOf: (value) => typeLabelOf(value, this.strings),\n selected: this.typeFilter,\n onSelect: (value) => (this.typeFilter = value),\n })\n );\n\n menu.appendChild(\n this._buildFilterGroup({\n title: this.strings.filterByPriority,\n dataAttr: \"filterPriority\",\n values: [...PRIORITIES],\n labelOf: (value) => priorityLabelOf(value, this.strings),\n selected: this.priorityFilter,\n onSelect: (value) => (this.priorityFilter = value),\n })\n );\n\n wrapper.appendChild(btn);\n wrapper.appendChild(menu);\n return wrapper;\n }\n\n _buildCard(comment, { interactive }) {\n const card = document.createElement(\"div\");\n card.className = CLASSES.INBOX_CARD;\n if (comment.status === \"resolved\") {\n card.classList.add(`${CLASSES.INBOX_CARD}--resolved`);\n }\n card.dataset.commentId = comment.id;\n\n // Meta alone on its row, action strip on the next one \u2014 the same split\n // the thread popover makes. Sharing a row squeezed the author into\n // ~90px and wrapped the name onto two lines.\n const header = document.createElement(\"div\");\n header.className = CLASSES.INBOX_CARD_HEADER;\n header.appendChild(\n createMetaElement(\n comment.author,\n comment.createdAt,\n this.strings,\n this.locale,\n comment.editedAt\n )\n );\n card.appendChild(header);\n\n const actionsRow = document.createElement(\"div\");\n actionsRow.className = CLASSES.THREAD_ACTIONS_ROW;\n actionsRow.appendChild(this._buildCardActions(comment));\n card.appendChild(actionsRow);\n\n // The editor only ever replaces the body in the detail view. On a list\n // card it would sit inside a control that navigates on click, so the \u22EF\n // there routes through startEditing(), which opens the detail first.\n const editingThis =\n !interactive &&\n this.editing &&\n this.editing.replyId == null &&\n String(this.editing.commentId) === String(comment.id);\n\n if (editingThis) {\n card.appendChild(this._buildEditor());\n } else {\n const text = document.createElement(\"div\");\n text.className = CLASSES.INBOX_CARD_TEXT;\n text.textContent = comment.text;\n card.appendChild(text);\n }\n\n if (comment.screenshots?.length) {\n const shots = createScreenshotsDisplay(comment.screenshots, this.strings);\n wireScreenshotLightbox(shots, (src) =>\n this.callbacks.onShowLightbox(src)\n );\n card.appendChild(shots);\n }\n\n // Status, type and priority already sit in the action strip above as\n // labelled pickers; repeating them here was the same fact twice. Tags\n // and the resolution time have no control anywhere, so they remain \u2014\n // the row simply disappears when there is neither.\n const badges = createBadgeRow(comment, this.strings, {\n includeClassification: false,\n });\n if (badges) card.appendChild(badges);\n\n // Hidden until something has been reacted to; the trigger in the action\n // strip above is the only way in. Identical on the list card and in the\n // detail view \u2014 the strip is shared, so a card that could add a reaction\n // but not remove one would be the odd surface out.\n card.appendChild(this._reactionsUi().bar(comment));\n\n const tag = this._buildTag(comment);\n if (tag) card.appendChild(tag);\n\n if (interactive) {\n card.setAttribute(\"role\", \"button\");\n card.setAttribute(\"tabindex\", \"0\");\n\n // Inactive comments belong to another page: activating them hands\n // off to that page (the detail opens there after the redirect).\n const activate = () =>\n comment.anchorState === \"inactive\"\n ? this.callbacks.onNavigateToPage(comment)\n : this._openDetail(comment);\n\n const replyLink = document.createElement(\"button\");\n replyLink.type = \"button\";\n replyLink.className = CLASSES.INBOX_CARD_REPLY_LINK;\n replyLink.textContent = this.strings.replyLink;\n replyLink.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n activate();\n });\n card.appendChild(replyLink);\n\n card.addEventListener(\"click\", activate);\n card.addEventListener(\"keydown\", (/** @type {KeyboardEvent} */ e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n activate();\n }\n });\n\n // Hovering a card spotlights its marker on the page \u2014 only when the\n // marker is actually there (anchored, visible, not resolved).\n card.addEventListener(\"mouseenter\", () => this._highlight(comment));\n card.addEventListener(\"mouseleave\", () => this._clearHighlight());\n }\n\n return card;\n }\n\n _buildTag(comment) {\n let label = null;\n if (comment.anchorState === \"orphaned\") label = this.strings.orphanedBadge;\n else if (comment.hidden) label = this.strings.hiddenBadge;\n else if (comment.anchorState === \"inactive\") label = comment.page;\n if (!label) return null;\n\n const tag = document.createElement(\"span\");\n tag.className = CLASSES.INBOX_CARD_TAG;\n tag.textContent = label;\n return tag;\n }\n\n _buildCardActions(comment) {\n return createCommentActions(comment, {\n strings: this.strings,\n can: this.callbacks.can,\n reactions: this._reactionsUi(),\n onCopy: (c) =>\n copyToClipboard(\n buildAgentContext(c, {\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n strings: this.strings,\n })\n ),\n onCopyLink: (c) =>\n copyToClipboard(buildCommentLink(c, this.options.linkParam)),\n // From a list card this opens the detail with the editor already up:\n // a textarea inside a card that navigates on click, and highlights a\n // marker on hover, would be fighting three behaviours at once.\n onEdit: (c) => this.startEditing(c.id),\n onSetStatus: (c, status) => this.callbacks.onSetStatus(c.id, status),\n onSetType: (c, type) => this.callbacks.onSetType(c.id, type),\n onSetPriority: (c, priority) =>\n this.callbacks.onSetPriority(c.id, priority),\n onDelete: (c) => {\n if (this.detailId != null && sameId(this.detailId, c.id)) {\n this.detailId = null;\n }\n this.callbacks.onDelete(c.id);\n this.render();\n },\n });\n }\n\n _renderDetail(comment, comments) {\n const index = comments.indexOf(comment);\n\n const header = document.createElement(\"div\");\n header.className = CLASSES.INBOX_DETAIL_HEADER;\n\n const backBtn = document.createElement(\"button\");\n backBtn.type = \"button\";\n backBtn.className = CLASSES.INBOX_BACK;\n backBtn.innerHTML = `${CHEVRON_LEFT_SVG}<span>${this.strings.back}</span>`;\n backBtn.addEventListener(\"click\", async () => {\n if (this.editing && !(await this.releaseEditor())) return;\n this.detailId = null;\n this.render();\n });\n header.appendChild(backBtn);\n\n const nav = document.createElement(\"div\");\n nav.className = CLASSES.INBOX_CARD_ACTIONS;\n\n const navBtn = (svg, label, targetIndex) => {\n const btn = document.createElement(\"button\");\n btn.type = \"button\";\n btn.className = CLASSES.INBOX_NAV_BTN;\n btn.setAttribute(\"aria-label\", label);\n btn.title = label;\n btn.innerHTML = svg;\n const target = comments[targetIndex];\n btn.disabled = !target;\n if (target) {\n // Navigating to another comment takes the edited text off screen. A\n // draft that survived that invisibly and reappeared later would be\n // worse than being asked about it here.\n btn.addEventListener(\"click\", async () => {\n if (this.editing && !(await this.releaseEditor())) return;\n this._openDetail(target);\n });\n }\n return btn;\n };\n\n nav.appendChild(navBtn(ARROW_UP_SVG, this.strings.prevComment, index - 1));\n nav.appendChild(\n navBtn(ARROW_DOWN_SVG, this.strings.nextComment, index + 1)\n );\n nav.appendChild(this._closeButton());\n header.appendChild(nav);\n\n this.el.appendChild(header);\n\n const detail = document.createElement(\"div\");\n detail.className = CLASSES.INBOX_DETAIL;\n\n detail.appendChild(this._buildCard(comment, { interactive: false }));\n\n // Open on arrival \u2014 the detail view is where you go to read everything \u2014\n // but foldable, and the choice outlives the rebuilds this view does on\n // every refresh.\n const context = createContextBlock(comment, {\n strings: this.strings,\n onShowLightbox: (src) => this.callbacks.onShowLightbox(src),\n collapsible: true,\n expanded: this.contextExpanded,\n onToggle: (expanded) => {\n this.contextExpanded = expanded;\n },\n });\n if (context) detail.appendChild(context);\n\n // Beside the context block rather than below the thread: both are folded\n // metadata about the comment, and keeping them together leaves the\n // conversation as one uninterrupted block underneath.\n const audit = createAuditTrail(comment, {\n strings: this.strings,\n locale: this.locale,\n open: this.auditExpanded,\n onToggle: (expanded) => {\n this.auditExpanded = expanded;\n },\n });\n if (audit) detail.appendChild(audit);\n\n const replies = document.createElement(\"div\");\n replies.className = CLASSES.INBOX_REPLIES;\n for (const reply of comment.replies || []) {\n const editingThisReply =\n this.editing &&\n String(this.editing.commentId) === String(comment.id) &&\n String(this.editing.replyId) === String(reply.id);\n\n const replyEl = createReplyElement(reply, this.strings, this.locale, {\n commentId: comment.id,\n can: this.callbacks.can,\n // Drops the row instead of re-rendering the detail: a full render\n // would also throw away whatever the user has half-typed in the\n // reply box below.\n onDelete: (r, el) => {\n if (this.callbacks.onDeleteReply(comment.id, r.id)) el.remove();\n },\n onEdit: (r) => this.startEditing(comment.id, r.id),\n editing: editingThisReply ? this._editorHandlers() : null,\n reactions: this._reactionsUi(),\n });\n wireScreenshotLightbox(replyEl, (src) =>\n this.callbacks.onShowLightbox(src)\n );\n replies.appendChild(replyEl);\n }\n detail.appendChild(replies);\n\n detail.appendChild(this._buildReplyInput(comment));\n this.el.appendChild(detail);\n }\n\n _buildReplyInput(comment) {\n const {\n container,\n inputEl,\n screenshotsContainer,\n attachBtn,\n fileInput,\n submitBtn,\n } = createInputArea(\n {\n areaClassName: CLASSES.THREAD_INPUT_AREA,\n inputTag: \"input\",\n inputClassName: CLASSES.THREAD_INPUT,\n inputPlaceholder: this.strings.replyPlaceholder,\n },\n this.strings\n );\n\n let pendingScreenshots = [];\n\n const updatePreview = () => {\n renderScreenshotsPreview(screenshotsContainer, pendingScreenshots, {\n strings: this.strings,\n onShow: (dataUrl) => this.callbacks.onShowLightbox(dataUrl),\n rerender: () => updatePreview(),\n });\n };\n\n attachBtn.addEventListener(\"click\", () => fileInput.click());\n wireScreenshotInput(\n fileInput,\n () => pendingScreenshots,\n updatePreview,\n (dataUrl) => this.callbacks.onTransformScreenshot(dataUrl, comment.id)\n );\n\n const submit = () => {\n const text = inputEl.value.trim();\n if (!text && pendingScreenshots.length === 0) return;\n this.callbacks.onReply(comment, text, [...pendingScreenshots]);\n pendingScreenshots = [];\n this.render();\n };\n\n submitBtn.addEventListener(\"click\", submit);\n inputEl.addEventListener(\"keydown\", (/** @type {KeyboardEvent} */ e) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n submit();\n }\n });\n\n return container;\n }\n}\n", "// CSV export: the comment corpus flattened one row per comment, and the\n// aggregate figures in long format.\n//\n// Hand-written rather than pulled from a library. The whole of RFC 4180 that\n// matters here is \"quote a field containing a delimiter, a quote or a\n// newline, and double the quotes inside it\" \u2014 thirty lines against the 7 KB\n// gzip a parser library costs, which is half the budget headroom for\n// something this file does in full.\n\nimport { toHours } from \"./metrics.js\";\nimport { currentResolutionMs, resolutionsOf } from \"./audit.js\";\n\nconst DELIMITER = \",\";\nconst NEWLINE = \"\\r\\n\";\n\n// Excel evaluates a cell opening with any of these, so a comment reading\n// \"=1+1\" becomes a formula the moment somebody double-clicks the file. The\n// leading apostrophe is the standard defusing and survives a round trip\n// through pandas and R.\nconst FORMULA_LEAD = /^[=+\\-@\\t\\r]/;\n\nconst escape = (value) => {\n if (value === null || value === undefined) return \"\";\n const raw = String(value);\n const safe = FORMULA_LEAD.test(raw) ? `'${raw}` : raw;\n return /[\"\\n\\r,]/.test(safe) ? `\"${safe.replace(/\"/g, '\"\"')}\"` : safe;\n};\n\n/**\n * @param {Array<Record<string, unknown>>} rows\n * @param {Array<{ key: string, label: string }>} columns\n * @returns {string}\n */\nexport function toCsv(rows, columns) {\n const header = columns.map((column) => escape(column.label)).join(DELIMITER);\n const body = rows.map((row) =>\n columns.map((column) => escape(row[column.key])).join(DELIMITER)\n );\n return [header, ...body].join(NEWLINE);\n}\n\n/** Columns of the comment export, in the order they are written. */\nexport const COMMENT_COLUMNS = [\n \"id\",\n \"page\",\n \"author\",\n \"authorId\",\n \"text\",\n \"status\",\n \"type\",\n \"priority\",\n \"tags\",\n \"createdAt\",\n \"resolvedAt\",\n \"resolutionHours\",\n \"reopened\",\n \"replies\",\n];\n\n/**\n * Pairs bare keys with themselves as headers. The header row deliberately\n * carries the internal names rather than translated labels: the file is an\n * interchange format, and a column whose spelling follows the widget's locale\n * cannot be joined against the export somebody else produced.\n * @param {string[]} keys\n */\nexport const columnsOf = (keys) => keys.map((key) => ({ key, label: key }));\n\n/** Columns of the aggregate export. */\nexport const METRIC_COLUMNS = [\"section\", \"key\", \"value\"];\n\n/**\n * One row per comment. Screenshots and the automatic context capture are\n * deliberately absent: a 33 KB base64 string in a spreadsheet cell is not\n * data, it is a file that has lost its name.\n *\n * @param {import('./index.d.ts').SerializedComment[]} comments\n */\nexport function commentRows(comments) {\n return (comments || []).map((comment) => {\n return {\n id: String(comment.id),\n page: comment.page || \"\",\n author: comment.author || \"\",\n authorId: comment.authorId || \"\",\n text: comment.text || \"\",\n status: comment.status || \"open\",\n type: comment.type || \"\",\n priority: comment.priority || \"\",\n // Space-joined rather than comma-joined: a comma inside a field is\n // legal but forces quoting on a column that is otherwise clean.\n tags: (comment.tags || []).join(\" \"),\n createdAt: comment.createdAt || \"\",\n resolvedAt: comment.resolvedAt || \"\",\n resolutionHours: toHours(currentResolutionMs(comment)),\n reopened: resolutionsOf(comment).length > 1 ? \"yes\" : \"no\",\n replies: (comment.replies || []).length,\n };\n });\n}\n\n/**\n * The aggregate figures in long format \u2014 `section, key, value` \u2014 rather than\n * one wide row. Buckets differ in number between corpora (one row per active\n * day), so a wide shape would change its column count from export to export\n * and stop being joinable against the previous one.\n *\n * Keys are the stable internal names, not translated labels: a column whose\n * spelling follows the widget's locale cannot be joined against anything.\n *\n * @param {import('./index.d.ts').CommentMetrics} metrics\n */\nexport function metricRows(metrics) {\n const rows = [{ section: \"total\", key: \"\", value: metrics.total }];\n const push = (section, table) => {\n for (const [key, value] of Object.entries(table)) {\n rows.push({ section, key, value });\n }\n };\n push(\"status\", metrics.byStatus);\n push(\"type\", metrics.byType);\n push(\"priority\", metrics.byPriority);\n for (const { date, count } of metrics.overTime) {\n rows.push({ section: \"perDay\", key: date, value: count });\n }\n rows.push(\n {\n section: \"resolution\",\n key: \"resolvedCount\",\n value: metrics.resolution.resolvedCount,\n },\n {\n section: \"resolution\",\n key: \"reopenedCount\",\n value: metrics.resolution.reopenedCount,\n },\n {\n section: \"resolution\",\n key: \"averageHours\",\n value: toHours(metrics.resolution.averageMs),\n },\n {\n section: \"resolution\",\n key: \"medianHours\",\n value: toHours(metrics.resolution.medianMs),\n }\n );\n return rows;\n}\n\n/**\n * Hands the browser a file. The BOM is not decoration: without it Excel reads\n * the bytes as its local codepage and every accented name comes back as\n * mojibake.\n *\n * @param {string} filename\n * @param {string} text\n */\nexport function downloadCsv(filename, text) {\n const blob = new Blob([\"\\uFEFF\", text], {\n type: \"text/csv;charset=utf-8\",\n });\n const url = URL.createObjectURL(blob);\n const link = document.createElement(\"a\");\n link.href = url;\n link.download = filename;\n link.click();\n URL.revokeObjectURL(url);\n}\n", "// The printable metrics report \u2014 the \"PDF\" half of the export requirement.\n//\n// No PDF library. The lightest one measured 133 KB gzip against a 50 KB\n// budget with ~13 KB of headroom left, so the browser's own print-to-PDF does\n// the job for zero bytes: build the report in its own document and ask that\n// document to print. What the user saves is a real PDF, produced by the\n// engine that already knows how to lay out the page.\n//\n// Its own document, not the host page: printing the page would print whatever\n// the host has on screen. And the styles go in through mountStyles rather\n// than an inline <style>, because an iframe inherits the embedder's Content\n// Security Policy \u2014 under a strict `style-src` an inline sheet is dropped and\n// the report prints unstyled, which is the exact failure the widget's own\n// stylesheet already had to solve.\n\nimport { mountStyles } from \"./style-mount.js\";\nimport { formatTemplate } from \"./i18n.js\";\nimport {\n statusLabelOf,\n typeLabelOf,\n priorityLabelOf,\n} from \"./comment-actions.js\";\nimport { STATUSES, COMMENT_TYPES, PRIORITIES } from \"./constants.js\";\nimport { toHours } from \"./metrics.js\";\n\nconst REPORT_STYLE_ID = \"helldots-report-styles\";\n\nconst el = (doc, tag, className, text) => {\n const node = doc.createElement(tag);\n if (className) node.className = className;\n if (text !== undefined) node.textContent = String(text);\n return node;\n};\n\nconst buildTable = (doc, caption, rows, headers) => {\n const table = el(doc, \"table\", \"report-table\");\n table.appendChild(el(doc, \"caption\", null, caption));\n\n const thead = doc.createElement(\"thead\");\n const headRow = doc.createElement(\"tr\");\n for (const header of headers)\n headRow.appendChild(el(doc, \"th\", null, header));\n thead.appendChild(headRow);\n table.appendChild(thead);\n\n const tbody = doc.createElement(\"tbody\");\n for (const [label, value] of rows) {\n const tr = doc.createElement(\"tr\");\n tr.appendChild(el(doc, \"th\", null, label));\n tr.appendChild(el(doc, \"td\", null, value));\n tbody.appendChild(tr);\n }\n table.appendChild(tbody);\n return table;\n};\n\n// Hours rather than the widget's \"3h 12m\" shorthand: a printed report is\n// read next to other reports, and a single unit is what you can compare.\nconst duration = (ms) =>\n ms === null ? \"\u2014\" : formatTemplate(\"{n} h\", toHours(ms));\n\nconst buildReport = (doc, metrics, { strings, locale, scope }) => {\n const body = doc.body;\n body.className = \"report\";\n\n body.appendChild(el(doc, \"h1\", \"report-title\", strings.metricsTitle));\n\n const meta = el(doc, \"p\", \"report-meta\");\n meta.textContent = formatTemplate(\n strings.metricsGeneratedTemplate,\n new Intl.DateTimeFormat(locale, {\n dateStyle: \"long\",\n timeStyle: \"short\",\n }).format(new Date())\n );\n body.appendChild(meta);\n\n if (scope) {\n const scopeEl = el(doc, \"p\", \"report-meta\");\n scopeEl.textContent = `${strings.metricsScope}: ${scope}`;\n body.appendChild(scopeEl);\n }\n\n body.appendChild(\n buildTable(\n doc,\n strings.metricsTitle,\n [\n [strings.metricsTotal, metrics.total],\n [strings.statusResolved, metrics.resolution.resolvedCount],\n [strings.metricsReopened, metrics.resolution.reopenedCount],\n [\n strings.metricsAverageResolution,\n duration(metrics.resolution.averageMs),\n ],\n [\n strings.metricsMedianResolution,\n duration(metrics.resolution.medianMs),\n ],\n ],\n [strings.metricsCategory, strings.metricsCount]\n )\n );\n\n const dimension = (caption, keys, table, labelOf) =>\n buildTable(\n doc,\n caption,\n keys.map((key) => [labelOf(key), table[key] ?? 0]),\n [strings.metricsCategory, strings.metricsCount]\n );\n\n body.appendChild(\n dimension(strings.metricsByStatus, STATUSES, metrics.byStatus, (key) =>\n statusLabelOf(key, strings)\n )\n );\n body.appendChild(\n dimension(\n strings.metricsByType,\n [...COMMENT_TYPES, \"unset\"],\n metrics.byType,\n (key) => (key === \"unset\" ? strings.unset : typeLabelOf(key, strings))\n )\n );\n body.appendChild(\n dimension(\n strings.metricsByPriority,\n [...PRIORITIES, \"unset\"],\n metrics.byPriority,\n (key) => (key === \"unset\" ? strings.unset : priorityLabelOf(key, strings))\n )\n );\n\n if (metrics.overTime.length) {\n body.appendChild(\n buildTable(\n doc,\n strings.metricsOverTime,\n metrics.overTime.map(({ date, count }) => [date, count]),\n [strings.metricsDate, strings.metricsCount]\n )\n );\n }\n};\n\n/**\n * Builds the report in a hidden same-origin frame and asks it to print.\n *\n * @param {import('./index.d.ts').CommentMetrics} metrics\n * @param {{\n * strings: ReturnType<typeof import('./i18n.js').getStrings>,\n * locale: string,\n * css: string,\n * scope?: string,\n * }} deps\n * @returns {HTMLIFrameElement} the frame, which takes itself down after printing\n */\nexport function printMetricsReport(metrics, { strings, locale, css, scope }) {\n const frame = document.createElement(\"iframe\");\n frame.setAttribute(\"aria-hidden\", \"true\");\n frame.setAttribute(\"title\", strings.metricsTitle);\n // Off-screen rather than display:none \u2014 a frame that is not rendered has no\n // layout, and printing one prints nothing.\n frame.style.position = \"absolute\";\n frame.style.width = \"0\";\n frame.style.height = \"0\";\n frame.style.border = \"0\";\n frame.style.left = \"-9999px\";\n document.body.appendChild(frame);\n\n const doc = frame.contentDocument;\n const view = frame.contentWindow;\n doc.title = strings.metricsTitle;\n mountStyles(doc, css, REPORT_STYLE_ID);\n buildReport(doc, metrics, { strings, locale, scope });\n\n const teardown = () => frame.remove();\n view.addEventListener(\"afterprint\", teardown, { once: true });\n\n // Deferred by a tick: printing before the frame has laid out its content is\n // how a blank page comes out. Scheduled from this realm so the frame can be\n // taken down even if its own timers never run.\n setTimeout(() => view.print?.(), 0);\n\n return frame;\n}\n", "import { CaptureFlow } from \"./capture-flow.js\";\nimport { captureContext } from \"./metadata.js\";\nimport {\n CLASSES,\n IDS,\n SELECTORS,\n STATUSES,\n COMMENT_TYPES,\n PRIORITIES,\n MARKER_SIZE,\n MAX_SCREENSHOTS,\n MARKERS_HIDDEN_STORAGE_KEY,\n} from \"./constants.js\";\nimport { getStyles, getGlobalStyles } from \"./styles.js\";\nimport { mountStyles } from \"./style-mount.js\";\nimport { getShadowRoot, TAG_NAME } from \"./root-element.js\";\nimport { getStrings, detectLocale } from \"./i18n.js\";\nimport {\n createAnchor,\n resolveAnchor,\n generateElementSelector,\n} from \"./anchor.js\";\nimport {\n readStoredComments,\n writeStoredComments,\n mergeForStorage,\n STORAGE_KEY,\n PENDING_DETAIL_KEY,\n} from \"./storage.js\";\nimport { createId, sameId } from \"./id.js\";\nimport {\n actorKeyOf,\n toggleReactionOn,\n normalizeReactions,\n serializeReactions,\n} from \"./reactions.js\";\nimport {\n resolvePermission,\n commentTargetOf,\n replyTargetOf,\n} from \"./permissions.js\";\nimport {\n buildCommentLink,\n readCommentLinkParam,\n DEFAULT_LINK_PARAM,\n} from \"./link.js\";\nimport {\n createToolbar,\n cssAttrValue,\n isMacPlatform,\n renderScreenshotsPreview,\n wireScreenshotInput,\n wireScreenshotLightbox,\n createCommentBox,\n createTooltip,\n EYE_ICON_SVG,\n EYE_OFF_ICON_SVG,\n} from \"./components.js\";\nimport {\n PopoverController,\n positionPopoverAtCircle,\n} from \"./popover-controller.js\";\nimport { MarkerEngine } from \"./marker-engine.js\";\nimport { InboxView } from \"./inbox.js\";\nimport {\n actorOf,\n recordEvent,\n normalizeHistory,\n serializeHistory,\n} from \"./audit.js\";\nimport { normalizeActorId } from \"./id.js\";\nimport { computeMetrics } from \"./metrics.js\";\nimport {\n toCsv,\n columnsOf,\n commentRows,\n metricRows,\n downloadCsv,\n COMMENT_COLUMNS,\n METRIC_COLUMNS,\n} from \"./csv.js\";\nimport { printMetricsReport } from \"./metrics-report.js\";\nimport { getReportStyles } from \"./styles.js\";\nimport { closeOpenMenus } from \"./menus.js\";\nimport { closeOpenConfirmDialogs } from \"./confirm-dialog.js\";\n\n// Tags are user-typed, so they arrive with stray case and whitespace.\n// Normalising here (rather than at each entry point) is what makes\n// \"Checkout\" and \"checkout \" the same tag for filtering.\nconst normalizeTags = (tags) => {\n const seen = new Set();\n for (const tag of tags) {\n const clean = String(tag).trim().toLowerCase();\n if (clean) seen.add(clean);\n }\n return [...seen];\n};\n\n// Screenshots are data-URLs rendered straight into <img src>; anything else\n// in a persisted array is a silently broken thumbnail waiting to happen.\nconst onlyStrings = (values) => values.filter((v) => typeof v === \"string\");\n\n// A drag names a region, and the region means the element that shows (most\n// of) it \u2014 not whatever sits on top of its center pixel. Coverage rather\n// than strict containment because human selections overshoot by a few\n// pixels; 60% tolerates the overshoot while still rejecting a partial\n// overlay (a floating panel, a dropdown) hovering above the framed content.\nconst REGION_COVERAGE_MIN = 0.6;\n\n// Every change the host can hear about, as `type` \u2192 the specific callback\n// that has always carried it. One table so a new event cannot be added to\n// the stream while forgetting the callback (or the other way round), and so\n// the two can never disagree about when they fire.\nconst CHANGE_CALLBACKS = {\n \"comment:created\": \"onCommentCreated\",\n \"comment:edited\": \"onCommentEdited\",\n \"comment:deleted\": \"onCommentDeleted\",\n \"comment:status-changed\": \"onCommentStatusChanged\",\n \"comment:updated\": \"onCommentUpdated\",\n \"comment:anchor-lost\": \"onAnchorLost\",\n \"reply:added\": \"onReplyAdded\",\n \"reply:deleted\": \"onReplyDeleted\",\n \"reply:edited\": \"onReplyEdited\",\n \"reaction:toggled\": \"onReactionToggled\",\n};\n\nclass CommentOverlay {\n /**\n * @param {import('./index.d.ts').CommentOverlayOptions} [options]\n */\n constructor(options = {}) {\n this.comments = [];\n this.commentMode = false;\n this.isMac = isMacPlatform();\n this.options = {\n shortcutKey: options.shortcutKey || (this.isMac ? \"c\" : \"C\"),\n shortcutModifier: options.shortcutModifier || \"alt\",\n autoScreenshot: options.autoScreenshot !== false,\n embedCrossOriginFonts: options.embedCrossOriginFonts === true,\n fastCapture: options.fastCapture === true,\n skipIframeContent: options.skipIframeContent === true,\n ...options,\n };\n this.locale = this.options.locale || detectLocale();\n this.strings = getStrings(this.locale);\n\n /**\n * Marker positioning, occlusion and observers (see marker-engine.js).\n * Created in initOverlay \u2014 its circles mount into the overlay element.\n * @type {MarkerEngine | null}\n */\n this.markers = null;\n /**\n * Drag selection + screenshot orchestration (see capture-flow.js).\n * Created in initOverlay \u2014 it mounts the selection rect into the\n * shadow root. @type {CaptureFlow | null}\n */\n this._captureFlow = null;\n\n /** Whether a drag's region crop is still rendering. @type {boolean} */\n this._regionCapturePending = false;\n\n /**\n * Parsed cross-page corpus, so every mutation does not pay a full\n * getItem + JSON.parse (megabytes once screenshots accumulate). Kept in\n * step with what this instance writes; dropped when another tab writes\n * (the `storage` listener in initOverlay).\n * @type {import('./index.d.ts').SerializedComment[] | null}\n */\n this._storedCache = null;\n\n /**\n * Thread-popover lifecycle and editing state (see\n * popover-controller.js). Created in initOverlay \u2014 it mounts into the\n * shadow root. @type {PopoverController | null}\n */\n this._popover = null;\n /**\n * A comment someone asked to open \u2014 from a \"Copy link\" URL or the\n * cross-page handoff \u2014 that has not been found yet.\n * @type {string | null}\n */\n this._pendingDetailId = null;\n\n /**\n * The pending id the host was already asked to fetch. A link pointing at\n * a comment that never arrives must ask once, not once per load.\n * @type {string | null}\n */\n this._requestedDetailId = null;\n\n /**\n * Where the mutation being applied right now came from. \"host\" is the\n * default because a public method reached directly IS the host calling\n * it; the widget's own UI goes through `_asUser`, which flips this for\n * the duration of the call.\n * @type {import('./index.d.ts').ChangeOrigin}\n */\n this._origin = \"host\";\n\n /**\n * Comments handed to loadComments() before the widget mounted, replayed\n * by initOverlay() once the marker engine exists.\n * @type {import('./index.d.ts').SerializedComment[] | null}\n */\n this._deferredLoad = null;\n\n if (document.readyState === \"loading\") {\n // Kept on the instance so cleanup() can cancel it \u2014 an instance\n // destroyed while the document is still loading must not mount a\n // zombie UI when DOMContentLoaded fires.\n this._onDomReady = () => this.initOverlay();\n document.addEventListener(\"DOMContentLoaded\", this._onDomReady);\n } else {\n this.initOverlay();\n }\n }\n\n initOverlay() {\n // Mount inside a dedicated shadow root so widget styles/markup stay\n // isolated from the host page in both directions.\n this.shadowRoot = getShadowRoot();\n\n // Create and append UI elements\n this.toolbar = createToolbar(this.options, this.strings);\n this.commentBox = createCommentBox(this.strings);\n this.overlay = document.createElement(\"div\");\n this.overlay.className = CLASSES.COMMENT_OVERLAY;\n\n this.shadowRoot.appendChild(this.overlay);\n this.shadowRoot.appendChild(this.toolbar);\n this.shadowRoot.appendChild(this.commentBox);\n\n this.commentBtn = this.toolbar.querySelector(\n `.${CLASSES.TOOLBAR_COMMENT_BTN}`\n );\n this.inboxBtn = this.toolbar.querySelector(`.${CLASSES.TOOLBAR_MENU_BTN}`);\n this.eyeBtn = this.toolbar.querySelector(`.${CLASSES.TOOLBAR_EYE_BTN}`);\n /** Whether the on-page marker layer is hidden (the eye toggle). */\n this.markersHidden = false;\n /** @type {HTMLButtonElement} */\n this.submitButton = /** @type {any} */ (\n this.shadowRoot.getElementById(IDS.SUBMIT_COMMENT)\n );\n /** @type {HTMLTextAreaElement} */\n this.commentInput = /** @type {any} */ (\n this.shadowRoot.getElementById(IDS.COMMENT_INPUT)\n );\n this.attachImageBtn = this.commentBox.querySelector(\n `.${CLASSES.ATTACH_IMAGE_BTN}`\n );\n /** @type {HTMLInputElement} */\n this.attachImageInput = /** @type {any} */ (\n this.shadowRoot.getElementById(IDS.ATTACH_IMAGE_INPUT)\n );\n\n this._captureFlow = new CaptureFlow({\n host: this.shadowRoot,\n autoScreenshot: this.options.autoScreenshot,\n embedCrossOriginFonts: this.options.embedCrossOriginFonts,\n fastCapture: this.options.fastCapture,\n skipIframeContent: this.options.skipIframeContent,\n captureTimeout: this.options.captureTimeout,\n // The pending-attachments array stays here, next to the comment box\n // that previews it \u2014 the flow only reports what a drag captured.\n onRegionCaptured: (dataUrl) => {\n if (!this._pendingScreenshots) this._pendingScreenshots = [];\n if (this._pendingScreenshots.length < MAX_SCREENSHOTS) {\n this._pendingScreenshots.push(dataUrl);\n }\n },\n // The box opens before the crop exists now, so it has to show that\n // one is coming \u2014 an empty attachment strip after a deliberate drag\n // reads as the selection having been thrown away.\n onRegionPending: (pending) => {\n this._regionCapturePending = pending;\n this._updateScreenshotsPreview();\n },\n onPlace: (x, y, region) => this._placeCommentAtPoint(x, y, region),\n onError: (err) => this._reportError(err, \"capture\"),\n });\n\n this._popover = new PopoverController({\n shadowRoot: this.shadowRoot,\n strings: this.strings,\n locale: this.locale,\n findComment: (id) => this._findComment(id),\n removeTooltip: (id) => this._tooltipEl(id)?.remove(),\n onShowLightbox: (src) => this.showLightbox(src),\n isInsideLightbox: (target) => this._isInsideLightbox(target),\n linkParam: () => this._linkParam(),\n refreshInbox: () => {\n if (this.inboxView?.isOpen()) this.inboxView.refresh();\n },\n actorKey: () => this._actorKey(),\n can: (action, target) => this.can(action, target),\n transformScreenshot: (dataUrl, commentId) =>\n this._transformScreenshot(dataUrl, \"attachment\", commentId),\n // Every action below is a person clicking inside the widget, so the\n // events they emit carry origin \"user\" (see _asUser).\n actions: this._userActions({\n addReply: (comment, text, screenshots) =>\n this.addReply(comment, text, screenshots),\n deleteReply: (commentId, replyId) =>\n this.deleteReply(commentId, replyId),\n editComment: (id, text) => this.editComment(id, text),\n editReply: (commentId, replyId, text) =>\n this.editReply(commentId, replyId, text),\n setStatus: (id, status) => this.setCommentStatus(id, status),\n setType: (id, type) => this.setCommentType(id, type),\n setPriority: (id, priority) => this.setCommentPriority(id, priority),\n deleteComment: (id) => this.deleteComment(id),\n toggleCommentReaction: (id, emoji) =>\n this.toggleCommentReaction(id, emoji),\n toggleReplyReaction: (commentId, replyId, emoji) =>\n this.toggleReplyReaction(commentId, replyId, emoji),\n }),\n });\n\n this.markers = new MarkerEngine({\n container: this.overlay,\n strings: this.strings,\n getComments: () => this.comments,\n wireMarker: (circle, comment) => this._wireMarker(circle, comment),\n onMarkerHidden: (comment) => this._dismissMarkerUi(comment),\n onVisibilityFlip: () => {\n if (this.inboxView?.isOpen()) this.inboxView.refresh();\n },\n // Runs after every rAF pass: the markers are placed in viewport\n // coordinates, so the open thread popover has to follow.\n onAfterPass: () => this.syncThreadPopoverToMarker(),\n });\n this.markers.start();\n\n // The eye toggle's preference survives reloads; a blocked localStorage\n // just means the layer starts visible.\n try {\n if (localStorage.getItem(MARKERS_HIDDEN_STORAGE_KEY) === \"true\") {\n this._setMarkersHidden(true);\n }\n } catch {\n /* storage unavailable \u2014 stay visible */\n }\n\n // Bind event listeners\n this.bindEventListeners();\n this.setupKeyboardShortcut();\n this.injectStyles();\n\n this._pendingDetailId = this._readPendingDetailId();\n\n if (this.options.persistence === \"localStorage\") {\n this._storedCache = readStoredComments();\n this.loadComments(this._storedCache);\n // Another tab writing the key makes this instance's parsed copy\n // stale \u2014 drop it so the next sync re-reads before merging, instead\n // of clobbering what the other tab persisted.\n this._storageHandler = (e) => {\n if (e.key === STORAGE_KEY || e.key === null) this._storedCache = null;\n };\n window.addEventListener(\"storage\", this._storageHandler);\n }\n\n // A host whose fetch resolved while the document was still parsing\n // called loadComments() before any of this existed. Applied here, after\n // the localStorage restore, so explicit data still wins by id over\n // whatever was cached.\n if (this._deferredLoad) {\n const deferred = this._deferredLoad;\n this._deferredLoad = null;\n this.loadComments(deferred);\n }\n\n // Also outside localStorage mode: a host that persists comments itself\n // still deserves to have the link honoured, and until its loadComments()\n // arrives the inbox is what tells the user the link was understood.\n this._openPendingDetail();\n\n // Opt-in, never default: popstate only covers back/forward, and MPA\n // hosts should not inherit listeners for navigations they don't do.\n // pushState routing still needs an explicit notifyNavigation() call.\n if (this.options.autoDetectNavigation) {\n this._popstateHandler = () => this.notifyNavigation();\n window.addEventListener(\"popstate\", this._popstateHandler);\n }\n\n this._notifyReady();\n }\n\n _navigateTo(url) {\n // A host router can take over (SPA): a full-page load throws away the\n // app's state just to show another route it could render itself.\n if (typeof this.options.navigate === \"function\") {\n this.options.navigate(url);\n return;\n }\n location.assign(url);\n }\n\n /**\n * Where a request to open one comment can come from. Two sources, one\n * slot: an inactive card clicked on the previous page (sessionStorage), or\n * a \"Copy link\" URL someone was sent. The URL wins when both are present \u2014\n * it is the one the user acted on just now.\n * @returns {string | null}\n */\n _readPendingDetailId() {\n const fromLink = readCommentLinkParam(this._linkParam());\n let fromHandoff = null;\n try {\n fromHandoff = sessionStorage.getItem(PENDING_DETAIL_KEY);\n // One-shot: read it and it is spent, whether or not it resolves.\n if (fromHandoff != null) sessionStorage.removeItem(PENDING_DETAIL_KEY);\n } catch {\n // A blocked sessionStorage only costs the handoff, not the link.\n }\n return fromLink ?? fromHandoff;\n }\n\n _linkParam() {\n return this.options.linkParam || DEFAULT_LINK_PARAM;\n }\n\n /**\n * Opens the inbox on the pending comment, if there is one.\n *\n * Deliberately does NOT give up when the id fails to resolve: a host that\n * fetches its comments from its own back end has not called loadComments()\n * yet at startup, and that is precisely the setup where a link is worth\n * sending to another person. The id is kept and this runs again after\n * every load, so the inbox switches from \"not on this page\" to the comment\n * the moment the data lands.\n */\n _openPendingDetail() {\n const id = this._pendingDetailId;\n if (!id) return;\n\n const comment = this._findComment(id);\n if (!comment) {\n // Opening the inbox anyway is the point: clicking a link and having\n // nothing at all happen is indistinguishable from a broken widget.\n this.showInbox();\n this.inboxView?.showNotice(this.strings.commentNotFound);\n this._requestPendingDetail(id);\n return;\n }\n\n this._pendingDetailId = null;\n this._requestedDetailId = null;\n this.showInbox();\n this.inboxView.clearNotice();\n this.inboxView.openDetail(comment.id);\n }\n\n /**\n * Asks the host for a comment a link points at that the widget does not\n * hold.\n *\n * This is what makes \"load only the comment in the link\" implementable. A\n * host that fetches per page otherwise has no way to learn which id the\n * URL asked for except re-parsing the query string with its own copy of\n * `linkParam` \u2014 a second spelling of the same setting, free to drift from\n * the one the widget actually uses.\n *\n * Asked once per id rather than once per attempt: `_openPendingDetail`\n * runs again after every load and after every navigation, and an id the\n * host cannot produce must not become a request loop.\n *\n * A handler returning a promise is awaited and the link retried once it\n * settles \u2014 on rejection too, since the comment may have arrived by\n * another route while the fetch was failing.\n *\n * @param {string} id the pending id, as the URL or the handoff spelled it\n */\n _requestPendingDetail(id) {\n const handler = this.options.onCommentRequested;\n if (typeof handler !== \"function\") return;\n if (this._requestedDetailId !== null && sameId(this._requestedDetailId, id))\n return;\n this._requestedDetailId = id;\n\n let result;\n try {\n result = handler(id);\n } catch (err) {\n this._reportError(err, \"link\");\n return;\n }\n if (!result || typeof (/** @type {any} */ (result).then) !== \"function\") {\n return;\n }\n /** @type {Promise<unknown>} */ (result).then(\n () => this._openPendingDetail(),\n (err) => {\n this._reportError(err, \"link\");\n this._openPendingDetail();\n }\n );\n }\n\n /**\n * Announces that the widget is mounted and every method on it is safe to\n * call. Fires once, at the end of initOverlay \u2014 synchronously inside the\n * constructor when the document was already parsed, on DOMContentLoaded\n * when it was not. The instance is handed over because in the synchronous\n * case the host does not have the return value of createCommentOverlay()\n * yet.\n */\n _notifyReady() {\n const handler = this.options.onReady;\n if (typeof handler !== \"function\") return;\n try {\n handler(this);\n } catch (err) {\n console.warn(\"HellDots: onReady handler threw\", err);\n }\n }\n\n /**\n * Runs a mutation performed by the widget's own UI, so everything emitted\n * inside it is stamped `origin: \"user\"`. A call arriving from the host's\n * code never passes through here and stays `\"host\"` \u2014 which is the whole\n * of how the two are told apart, since the inbox and the thread popover\n * drive the very same public methods a host does.\n *\n * Restores the previous value rather than resetting to \"host\": the inbox\n * calls a public method that itself reaches another one, and the inner\n * call must not downgrade the outer one's origin.\n *\n * @template T\n * @param {() => T} fn\n * @returns {T}\n */\n _asUser(fn) {\n const previous = this._origin;\n this._origin = \"user\";\n try {\n return fn();\n } finally {\n this._origin = previous;\n }\n }\n\n /**\n * Wraps every function of an adapter object so the UI that calls it is\n * recorded as the origin. One call site per adapter instead of one per\n * action: a new action added to the inbox or the popover is stamped\n * without anyone having to remember to stamp it.\n *\n * @template {Record<string, any>} T\n * @param {T} actions\n * @returns {T}\n */\n _userActions(actions) {\n /** @type {Record<string, any>} */\n const wrapped = {};\n for (const [key, value] of Object.entries(actions)) {\n wrapped[key] =\n typeof value === \"function\"\n ? (/** @type {any[]} */ ...args) => this._asUser(() => value(...args))\n : value;\n }\n return /** @type {T} */ (wrapped);\n }\n\n /**\n * Tells the host about a failure it would otherwise only find in the\n * console. Every one of these is already survivable \u2014 the widget carries\n * on regardless \u2014 but \"the screenshot pipeline is broken\" is not something\n * a feedback tool should keep to itself.\n *\n * The console warning stays: a host without an `onError` must not lose the\n * diagnostic, and one with it is usually logging rather than replacing.\n *\n * @param {unknown} error\n * @param {import('./index.d.ts').ErrorContext} context\n */\n _reportError(error, context) {\n const handler = this.options.onError;\n if (typeof handler !== \"function\") return;\n try {\n handler(error, context);\n } catch (err) {\n console.warn(\"HellDots: onError handler threw\", err);\n }\n }\n\n /**\n * Hands one image to the host's `transformScreenshot`, so what ends up\n * stored can be a URL into its own storage instead of ~33KB of base64 in\n * every record.\n *\n * Never rejects, and never returns something a renderer cannot use: a\n * bucket that is down must not cost the user their comment, so a failed\n * transform degrades to the data URL the widget already holds and reports\n * itself through onError instead. The host receives a fat record rather\n * than none \u2014 the better of two bad outcomes.\n *\n * @param {string | null} dataUrl null passes straight through: no capture\n * was taken, and there is nothing to transform.\n * @param {\"context\" | \"attachment\"} kind\n * @param {import('./index.d.ts').CommentId} commentId\n * @returns {Promise<string | null>}\n */\n async _transformScreenshot(dataUrl, kind, commentId) {\n const transform = this.options.transformScreenshot;\n if (typeof transform !== \"function\" || !dataUrl) return dataUrl;\n try {\n const result = await transform(dataUrl, { kind, commentId });\n // A handler resolving to nothing usable is a failed handler; storing\n // it would put a broken <img> where the screenshot was.\n if (typeof result !== \"string\" || !result) {\n throw new Error(\n \"HellDots: transformScreenshot resolved to no usable string\"\n );\n }\n return result;\n } catch (err) {\n this._reportError(err, \"transform\");\n return dataUrl;\n }\n }\n\n /**\n * The one place a change leaves the widget. Fires the specific callback\n * that has always carried this event and then the onChange stream, so a\n * host can subscribe either way \u2014 or both \u2014 and never sees the two\n * disagree about what happened or when.\n *\n * Both shapes also receive the same `meta`: the specific callback takes it\n * as one extra trailing argument (existing handlers ignore it \u2014 that is\n * what makes this additive), and `onChange` gets its fields flattened onto\n * the event, alongside `comment`/`reply`/`id`.\n *\n * Host handlers are isolated: a subscriber that throws must not roll back\n * a mutation that already happened, and must not stop its sibling from\n * hearing about it either.\n *\n * @param {keyof typeof CHANGE_CALLBACKS} type\n * @param {any[]} callbackArgs arguments for the specific callback, in the\n * order it has always taken them\n * @param {Object} payload the event's own fields, minus `type`\n * @param {Object} [detail] event-specific metadata (`field`, `from`, `to`)\n * to travel next to `origin`\n */\n _emit(type, callbackArgs, payload, detail) {\n const meta = { origin: this._origin, ...detail };\n const name = CHANGE_CALLBACKS[type];\n const callback = this.options[name];\n if (typeof callback === \"function\") {\n try {\n callback(...callbackArgs, meta);\n } catch (err) {\n console.warn(`HellDots: ${name} handler threw`, err);\n }\n }\n if (typeof this.options.onChange === \"function\") {\n try {\n this.options.onChange({ type, ...payload, ...meta });\n } catch (err) {\n console.warn(\"HellDots: onChange handler threw\", err);\n }\n }\n }\n\n /** The shareable URL for a comment, as \"Copy link\" builds it. */\n commentLink(id) {\n const comment = this._findComment(id);\n return comment ? buildCommentLink(comment, this._linkParam()) : null;\n }\n\n /** The parsed corpus, re-read only after another tab invalidated it. */\n _readStoredCached() {\n if (!this._storedCache) this._storedCache = readStoredComments();\n return this._storedCache;\n }\n\n _syncStorage() {\n if (this.options.persistence !== \"localStorage\") return;\n const merged = mergeForStorage(\n this._readStoredCached(),\n this.serializeComments(),\n location.pathname\n );\n if (!writeStoredComments(merged)) {\n // Already warned about in detail by the writer, which shed what it\n // could before giving up. Worth surfacing anyway: from here on this\n // browser's copy silently diverges from what the user can see.\n this._reportError(\n new Error(\"HellDots: comments could not be persisted to localStorage\"),\n \"storage\"\n );\n }\n // The merge IS the new stored state (quota shedding only nulls\n // contextScreenshot in the written copy, which the next merge would\n // reattempt from memory anyway \u2014 same as before the cache existed).\n this._storedCache = merged;\n }\n\n bindEventListeners() {\n this.commentBtn.addEventListener(\"click\", () => this.toggleCommentMode());\n this.inboxBtn.addEventListener(\"click\", () => this.toggleInbox());\n this.eyeBtn?.addEventListener(\"click\", () =>\n this._setMarkersHidden(!this.markersHidden)\n );\n this.submitButton.addEventListener(\"click\", () => this.saveComment());\n\n this.commentInput.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n this.saveComment();\n }\n });\n\n this.attachImageBtn.addEventListener(\"click\", () => {\n this.attachImageInput.click();\n });\n\n wireScreenshotInput(\n this.attachImageInput,\n () => {\n if (!this._pendingScreenshots) this._pendingScreenshots = [];\n return this._pendingScreenshots;\n },\n () => this._updateScreenshotsPreview()\n );\n\n this._handleDocumentClickBound = (e) => this.handleDocumentClick(e);\n document.addEventListener(\"mousedown\", this._handleDocumentClickBound);\n }\n\n setupKeyboardShortcut() {\n // Remove any existing event listeners\n if (this.keydownHandler) {\n document.removeEventListener(\"keydown\", this.keydownHandler);\n }\n\n // Create a new handler with proper binding\n this.keydownHandler = (e) => {\n if (e.key === \"Escape\") {\n if (this._activeLightbox) {\n this.closeLightbox();\n } else if (this.activeThreadPopover) {\n // An open editor answers Escape first, and closes only itself. The\n // editor's own textarea stops the event before it reaches here, so\n // this branch is for an Escape pressed with focus somewhere else in\n // the popover \u2014 which must not take the panel down either.\n if (this._popover.isEditing()) this._popover.releaseEditor();\n else this.closeThreadPopover();\n } else if (this.inboxView?.isOpen()) {\n if (this.inboxView.editing) {\n this.inboxView\n .releaseEditor()\n .then((released) => released && this.inboxView?.refresh());\n } else {\n this.closeInbox();\n }\n } else if (this.commentBox.style.display !== \"none\") {\n this.hideCommentBox();\n this.toggleCommentMode();\n } else if (this.commentMode) {\n this.toggleCommentMode();\n }\n return;\n }\n\n // One matcher for default and custom chords alike \u2014 the old hardcoded\n // Alt+C fallbacks fired unconditionally, so a host that configured its\n // own shortcut got Alt+C on top of it with no way to turn it off.\n const key = this.options.shortcutKey.toLowerCase();\n const keyMatches =\n e.key.toLowerCase() === key ||\n // Option+letter on macOS (and AltGr layouts) types a dead or special\n // character (\"\u00E7\" for Option+C, \"\u02DA\" for Option+K), so e.key never\n // spells the configured letter there. e.code names the physical key\n // and is what makes Alt chords matchable at all.\n (e.altKey &&\n /^[a-z]$/.test(key) &&\n e.code === `Key${key.toUpperCase()}`);\n const modifierMatches =\n (this.options.shortcutModifier === \"alt\" && e.altKey) ||\n (this.options.shortcutModifier === \"ctrl\" &&\n (e.ctrlKey || e.metaKey)) ||\n (this.options.shortcutModifier === \"shift\" && e.shiftKey);\n\n if (keyMatches && modifierMatches) {\n e.preventDefault();\n e.stopPropagation();\n this.toggleCommentMode();\n }\n };\n\n // Add the event listener\n document.addEventListener(\"keydown\", this.keydownHandler);\n }\n\n handleDocumentClick(e) {\n if (!this.commentMode) return;\n\n // Listener is attached on `document`, outside the shadow boundary, so\n // `e.target` gets retargeted to the shadow host. Use composedPath() to\n // recover the real, deepest target inside the shadow tree.\n const target = e.composedPath()[0] || e.target;\n\n if (\n this.toolbar.contains(target) ||\n target?.closest?.(`.${CLASSES.CIRCLE}`) ||\n target?.closest?.(`.${CLASSES.TOOLTIP}`) ||\n target?.closest?.(`.${CLASSES.THREAD_POPOVER}`) ||\n target?.closest?.(`.${CLASSES.INBOX_PANEL}`) ||\n target?.closest?.(`.${CLASSES.LIGHTBOX}`)\n ) {\n return;\n }\n\n if (this.commentBox.contains(target)) {\n return;\n }\n\n if (this.commentBox.style.display !== \"none\") {\n this.hideCommentBox();\n this.toggleCommentMode();\n return;\n }\n\n if (e.button !== 0) return;\n e.preventDefault();\n\n this._captureFlow.beginDrag(e);\n }\n\n /**\n * The element a drag-selected region should anchor to: the topmost element\n * in the hit-test stack at the region's center whose intersection with the\n * region covers at least REGION_COVERAGE_MIN of its area. Reading the\n * stack (rather than walking ancestors) also handles overlays rendered in\n * portals, which are not ancestors of anything useful. Null when nothing\n * qualifies or the environment has no `elementsFromPoint` (jsdom).\n * @param {{ left: number, top: number, width: number, height: number }} region\n * @param {number} centerX\n * @param {number} centerY\n * @returns {Element | null}\n */\n _regionTarget(region, centerX, centerY) {\n if (typeof document.elementsFromPoint !== \"function\") return null;\n const area = region.width * region.height;\n if (!(area > 0)) return null;\n\n for (const el of document.elementsFromPoint(centerX, centerY)) {\n // Our own shadow host can appear in the stack even with the overlay's\n // pointer-events off (the toolbar, an open panel) \u2014 never a target.\n if (el.tagName.toLowerCase() === TAG_NAME.toLowerCase()) continue;\n const rect = el.getBoundingClientRect();\n const overlapX =\n Math.min(rect.right, region.left + region.width) -\n Math.max(rect.left, region.left);\n const overlapY =\n Math.min(rect.bottom, region.top + region.height) -\n Math.max(rect.top, region.top);\n if (overlapX <= 0 || overlapY <= 0) continue;\n if ((overlapX * overlapY) / area >= REGION_COVERAGE_MIN) return el;\n }\n return null;\n }\n\n /**\n * @param {number} clientX\n * @param {number} clientY\n * @param {{ left: number, top: number, width: number, height: number }} [region]\n * Present when the placement comes from a drag: the selected rectangle,\n * in viewport coordinates. `clientX/clientY` is then its center.\n */\n async _placeCommentAtPoint(clientX, clientY, region) {\n // The no-drag path has no render yet \u2014 kick the background capture off\n // now so it resolves while the user types (see capture-flow.js).\n this._captureFlow.armClickCapture();\n\n const prevPointerEvents = this.overlay.style.pointerEvents;\n this.overlay.style.pointerEvents = \"none\";\n const underlying =\n (region ? this._regionTarget(region, clientX, clientY) : null) ||\n document.elementFromPoint(clientX, clientY);\n this.overlay.style.pointerEvents = prevPointerEvents || \"\";\n\n const container =\n underlying?.closest?.(SELECTORS.CONTAINER) || document.body;\n const containerRect = container.getBoundingClientRect();\n\n // Zero-size containers (display:none, not yet laid out) would make the\n // division blow up to Infinity \u2014 which isn't JSON-serializable either.\n const relativeX =\n containerRect.width > 0\n ? (clientX - containerRect.left) / containerRect.width\n : 0;\n const relativeY =\n containerRect.height > 0\n ? (clientY - containerRect.top) / containerRect.height\n : 0;\n\n const anchor = createAnchor(\n /** @type {HTMLElement} */ (container),\n relativeX,\n relativeY\n );\n // The clicked element can disappear (responsive display:none) while the\n // coarse anchor container stays visible \u2014 track it separately so the\n // marker hides with what the user actually commented on.\n anchor.targetSelector =\n underlying && underlying !== container\n ? generateElementSelector(/** @type {HTMLElement} */ (underlying))\n : null;\n\n this.currentPosition = {\n container,\n relativeX,\n relativeY,\n anchor,\n target: /** @type {HTMLElement} */ (underlying || container),\n };\n\n this.createPreviewCircle(clientX, clientY);\n document.body.classList.remove(CLASSES.COMMENT_CURSOR);\n\n if (this._pendingScreenshots?.length > 0 || this._regionCapturePending) {\n this._updateScreenshotsPreview();\n }\n\n this.showCommentBox(clientX, clientY);\n }\n\n _updateScreenshotsPreview() {\n const container = this.commentBox.querySelector(\n `.${CLASSES.SCREENSHOTS_CONTAINER}`\n );\n if (!container) return;\n renderScreenshotsPreview(container, this._pendingScreenshots || [], {\n strings: this.strings,\n onShow: (dataUrl) => this.showLightbox(dataUrl),\n rerender: () => this._updateScreenshotsPreview(),\n pending: this._regionCapturePending ? 1 : 0,\n });\n }\n\n _clearScreenshotPreview() {\n this._pendingScreenshots = [];\n this._regionCapturePending = false;\n const container = this.commentBox.querySelector(\n `.${CLASSES.SCREENSHOTS_CONTAINER}`\n );\n if (container) {\n container.innerHTML = \"\";\n container.classList.remove(CLASSES.ACTIVE);\n }\n }\n\n showCommentBox(x, y) {\n this.commentBox.style.display = \"block\";\n\n const circleBaseSize = MARKER_SIZE;\n const circleRadius = circleBaseSize / 2;\n const offset = circleRadius + 10;\n const windowWidth = window.innerWidth;\n const windowHeight = window.innerHeight;\n\n // Measured, not assumed: the box is 400px wide on a roomy viewport but\n // narrows to `100vw - 24px` on a phone. A hardcoded width here is what\n // used to push it off the right edge on mobile.\n const boxRect = this.commentBox.getBoundingClientRect();\n const boxWidth = boxRect.width || 400;\n\n const centerX = x + circleRadius;\n const centerY = y + circleRadius;\n\n let adjustedX = centerX + offset;\n let adjustedY = centerY - circleRadius;\n\n if (adjustedX + boxWidth > windowWidth) {\n adjustedX = centerX - offset - boxWidth;\n }\n // Clamp both edges: on a viewport narrower than the box plus its\n // margins, flipping to the other side isn't enough on its own.\n adjustedX = Math.min(adjustedX, windowWidth - boxWidth - 10);\n adjustedX = Math.max(10, adjustedX);\n\n if (adjustedY + boxRect.height > windowHeight) {\n adjustedY = windowHeight - boxRect.height - 10;\n }\n adjustedY = Math.max(10, adjustedY);\n\n this.commentBox.style.left = `${adjustedX}px`;\n this.commentBox.style.top = `${adjustedY}px`;\n\n this.commentInput.value = \"\";\n setTimeout(() => this.commentInput.focus(), 50);\n }\n\n hideCommentBox() {\n this.commentBox.style.display = \"none\";\n this.commentInput.style.height = \"auto\";\n this.currentPosition = null;\n this.removePreviewCircle();\n this._clearScreenshotPreview();\n this._captureFlow?.clearPending();\n /** @type {any} */ (this.commentBox).classify?.reset();\n\n if (this.commentMode) {\n document.body.classList.add(CLASSES.COMMENT_CURSOR);\n }\n }\n\n /**\n * The eye toggle's single entry point: the button, the mount read, and\n * both auto-reshow paths all land here. The circles hide via a CSS\n * class on the mount container (the marker engine keeps running, so\n * re-showing is instant and correctly placed); the thread popover and\n * any open hover tooltip are dismissed imperatively instead, since\n * neither mounts inside that container.\n * @param {boolean} hidden\n */\n _setMarkersHidden(hidden) {\n this.markersHidden = hidden;\n this.overlay.classList.toggle(CLASSES.MARKERS_HIDDEN, hidden);\n if (hidden) {\n this.closeThreadPopover();\n // Tooltips mount on the shadow root, a sibling of `this.overlay`, so\n // the CSS hide rule never reaches them \u2014 an open one would otherwise\n // survive a keyboard-activated hide, orphaned over a page with no\n // visible marker to anchor it. No new tooltip can appear while\n // hidden: a display:none circle gets no hover events.\n this.shadowRoot\n .querySelectorAll(`.${CLASSES.TOOLTIP}`)\n .forEach((tooltip) => tooltip.remove());\n }\n\n if (this.eyeBtn) {\n // No aria-pressed here: the button's name swaps (Hide comments \u2194 Show\n // comments), and a swapping name plus a pressed state contradict each\n // other for screen-reader users (see DECISIONS.md). The name itself\n // carries the state, same as the icon and tooltip below.\n const label = hidden\n ? this.strings.toolbarShowComments\n : this.strings.toolbarHideComments;\n this.eyeBtn.setAttribute(\"aria-label\", label);\n this.eyeBtn.innerHTML = hidden ? EYE_OFF_ICON_SVG : EYE_ICON_SVG;\n const text = this.eyeBtn\n .closest(`.${CLASSES.TOOLBAR_ACTION_WRAPPER}`)\n ?.querySelector(`.${CLASSES.TOOLBAR_TEXT}`);\n if (text) text.textContent = label;\n }\n\n // Written on every change \u2014 including the automatic re-shows \u2014 so a\n // later reload matches what the viewer last saw.\n try {\n localStorage.setItem(MARKERS_HIDDEN_STORAGE_KEY, String(hidden));\n } catch {\n /* preference just does not persist */\n }\n }\n\n toggleCommentMode() {\n this.commentMode = !this.commentMode;\n // The inbox is a full-height panel over the page; leaving it open would\n // cover the very content the user now has to click on. Clicking the\n // toolbar button already closed it as an outside click \u2014 this is what\n // covers the keyboard shortcut and the empty state's own button.\n if (this.commentMode) this.closeInbox();\n // Someone about to comment wants to see the existing comments; the\n // shortcut funnels through here too. Leaving the mode does not re-hide.\n if (this.commentMode && this.markersHidden) this._setMarkersHidden(false);\n this.commentBtn?.classList.toggle(CLASSES.ACTIVE, this.commentMode);\n this.commentBtn?.setAttribute(\"aria-pressed\", String(this.commentMode));\n this.overlay.classList.toggle(CLASSES.ACTIVE, this.commentMode);\n document.body.classList.toggle(CLASSES.COMMENT_CURSOR, this.commentMode);\n\n if (!this.commentMode) {\n this.hideCommentBox();\n }\n\n // Every path lands here \u2014 the toolbar button, the keyboard shortcut, the\n // inbox empty state, and the automatic switch-off after a save \u2014 so the\n // host hears about the mode however it was flipped, including by the\n // shortcut it never sees.\n this._notify(\"onCommentModeChanged\", [this.commentMode]);\n }\n\n async saveComment() {\n // Two Enters while the capture resolves must not save twice.\n if (this._saving) return;\n if (!this.commentInput.value.trim() || !this.currentPosition) return;\n this._saving = true;\n // The guard above already made the second click a no-op; disabling says\n // so. With a host's upload behind the save this is no longer instant,\n // and a button that looks live but does nothing reads as broken.\n if (this.submitButton) this.submitButton.disabled = true;\n try {\n await this._saveCommentNow();\n } finally {\n this._saving = false;\n if (this.submitButton) this.submitButton.disabled = false;\n }\n }\n\n async _saveCommentNow() {\n // Everything this save is about, read before the first await: the draft\n // it belongs to and the text that was in the box at that moment. What\n // has to be caught is not only \"the box was dismissed\" but \"dismissed\n // and a *different* one opened\" \u2014 a window now seconds long, because a\n // host's upload sits inside it. A truthiness check misses the second\n // case and would write this comment onto the next draft's anchor.\n const position = this.currentPosition;\n const text = this.commentInput.value;\n // The capture kicked off when the box opened; by save time it has\n // usually resolved and this await costs nothing.\n const captured = await this._captureFlow.consumePending();\n // The box may have been dismissed (Escape) while awaiting \u2014 a save that\n // lands after that would contradict what the user sees on screen.\n if (this.currentPosition !== position) return;\n\n // Generated ahead of the transform rather than inside the object below,\n // so a host can name its blobs after the comment they belong to.\n const id = createId();\n const attachments = this._pendingScreenshots\n ? [...this._pendingScreenshots]\n : [];\n\n // In parallel: up to six images, one wait rather than six.\n const [contextScreenshot, screenshots] = await Promise.all([\n this._transformScreenshot(captured, \"context\", id),\n Promise.all(\n attachments.map((dataUrl) =>\n this._transformScreenshot(dataUrl, \"attachment\", id)\n )\n ),\n ]);\n\n // Checked again: unlike the capture above, the transform is the host's\n // network, so the box has had a real chance to be dismissed under it \u2014\n // and replaced by a draft somewhere else on the page.\n if (this.currentPosition !== position) return;\n\n const comment = {\n text,\n container: position.container,\n relativeX: position.relativeX,\n relativeY: position.relativeY,\n anchor: position.anchor,\n anchorState: \"anchored\",\n target: position.target,\n hidden: false,\n status: \"open\",\n page: location.pathname,\n id,\n replies: [],\n author: this.options.user?.name || this.strings.anonymous,\n authorId: normalizeActorId(this.options.user?.id) || null,\n createdAt: new Date().toISOString(),\n screenshots,\n type: /** @type {any} */ (this.commentBox).classify?.getType() ?? null,\n priority:\n /** @type {any} */ (this.commentBox).classify?.getPriority() ?? null,\n // No longer authored in the widget \u2014 kept on the model for\n // setCommentTags() and for comments imported through loadComments().\n tags: [],\n resolvedAt: null,\n context: captureContext(),\n contextScreenshot,\n };\n\n recordEvent(comment, \"created\", this._actor());\n\n this.comments.push(comment);\n this._syncStorage();\n const created = this._serializeComment(comment);\n // The comment box is widget UI like any other, but it reaches _emit\n // directly instead of through an adapter, so it stamps its own origin.\n this._asUser(() =>\n this._emit(\"comment:created\", [created], { comment: created })\n );\n this.renderCommentCircle(comment);\n this.hideCommentBox();\n this.toggleCommentMode();\n\n const circle = this._circles.get(String(comment.id));\n if (circle) {\n this.showThreadPopover(circle, comment);\n }\n }\n\n renderCommentCircle(comment) {\n this.markers.render(comment);\n }\n\n /**\n * What a marker opens when interacted with \u2014 tooltip on hover, thread\n * popover on activation. UI wiring only; the engine calls this once per\n * circle it creates and owns everything about position and visibility.\n */\n _wireMarker(circle, comment) {\n circle.addEventListener(\"mouseenter\", () =>\n this.showCommentTooltip(circle, comment)\n );\n circle.addEventListener(\"mouseleave\", () => {\n setTimeout(() => {\n const tooltip = this._tooltipEl(comment.id);\n if (tooltip && !tooltip.matches(\":hover\")) {\n tooltip.remove();\n }\n }, 250);\n });\n\n circle.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n this._tooltipEl(comment.id)?.remove();\n // The marker toggles its own thread: clicking the active marker\n // closes it rather than tearing the popover down and rebuilding an\n // identical one. Only its own \u2014 clicking a different marker still\n // switches to that thread.\n if (this.activeThreadPopover?.dataset.for === String(comment.id)) {\n this.closeThreadPopover();\n return;\n }\n this.showThreadPopover(circle, comment);\n });\n\n // The circle is a <div role=\"button\">, so unlike a real <button> it\n // doesn't get Enter/Space-activates-click for free.\n circle.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n circle.click();\n }\n });\n }\n\n showCommentTooltip(circle, comment) {\n const existingPopover = this.shadowRoot.querySelector(\n `.${CLASSES.THREAD_POPOVER}[data-for=\"${cssAttrValue(comment.id)}\"]`\n );\n if (existingPopover) return;\n\n if (this._tooltipEl(comment.id)) return;\n\n const tooltip = createTooltip(comment, this.strings, this.locale);\n this.shadowRoot.appendChild(tooltip);\n\n wireScreenshotLightbox(tooltip, (src) => this.showLightbox(src));\n\n setTimeout(() => {\n positionPopoverAtCircle(tooltip, circle);\n }, 10);\n\n tooltip\n .querySelector(`.${CLASSES.CLOSE_TOOLTIP}`)\n .addEventListener(\"click\", (e) => {\n e.stopPropagation();\n tooltip.remove();\n });\n\n tooltip.addEventListener(\"mouseleave\", () => tooltip.remove());\n }\n\n toggleInbox() {\n if (this.inboxView?.isOpen()) {\n this.closeInbox();\n } else {\n this.showInbox();\n }\n }\n\n showInbox() {\n this.closeThreadPopover();\n\n if (!this.inboxView) {\n this.inboxView = new InboxView({\n shadowRoot: this.shadowRoot,\n strings: this.strings,\n locale: this.locale,\n currentPage: location.pathname,\n getComments: () => this.comments,\n options: this.options,\n // Same as the popover's: everything here is user-driven.\n callbacks: this._userActions({\n onActivateCommentMode: () => {\n this.closeInbox();\n // Never a toggle: the button reads \"turn on comment mode\", so\n // pressing it while the mode is already on must not turn it off.\n if (!this.commentMode) this.toggleCommentMode();\n },\n onOpenDetailScroll: (comment) => this.scrollMarkerIntoView(comment),\n onOpenDetail: (comment) => this._notifyCommentOpened(comment),\n onTransformScreenshot: (dataUrl, commentId) =>\n this._transformScreenshot(dataUrl, \"attachment\", commentId),\n onReply: (comment, text, screenshots) =>\n this.addReply(comment, text, screenshots),\n onDelete: (id) => this.deleteComment(id),\n onDeleteReply: (commentId, replyId) =>\n this.deleteReply(commentId, replyId),\n onEditComment: (id, text) => {\n if (!this.editComment(id, text)) return;\n // The marker's hover tooltip and the open thread both quote the\n // text that just changed, and neither rebuilds on its own.\n this._popover.refreshCommentViews(id);\n },\n onEditReply: (commentId, replyId, text) => {\n if (!this.editReply(commentId, replyId, text)) return;\n this._popover.refreshCommentViews(commentId);\n },\n actorKey: () => this._actorKey(),\n can: (action, target) => this.can(action, target),\n onToggleCommentReaction: (id, emoji) =>\n this.toggleCommentReaction(id, emoji),\n onToggleReplyReaction: (commentId, replyId, emoji) =>\n this.toggleReplyReaction(commentId, replyId, emoji),\n onExportComments: (comments) => this.exportCommentsCsv(comments),\n onExportMetrics: (comments) => this.exportMetricsCsv(comments),\n onPrintReport: (comments, scope) =>\n this.printMetricsReport(comments, scope),\n onSetStatus: (id, status) => this.setCommentStatus(id, status),\n onSetType: (id, type) => this.setCommentType(id, type),\n onSetPriority: (id, priority) =>\n this.setCommentPriority(id, priority),\n onNavigateToPage: (comment) => {\n try {\n sessionStorage.setItem(PENDING_DETAIL_KEY, String(comment.id));\n } catch {}\n this._navigateTo(comment.page);\n },\n onShowLightbox: (src) => this.showLightbox(src),\n onClose: () => this.closeInbox(),\n }),\n });\n }\n this.inboxView.open();\n\n // Both fields hold one thing, so whatever is already in them has to go\n // first: re-opening an open inbox (notifyNavigation re-reads the deep\n // link on every route change) otherwise orphaned the previous timer and\n // handler where closeInbox could no longer reach them.\n this._disarmInboxOutsideClick();\n\n // Deferred like the thread popover's, and cancellable for the same\n // reason: closeInbox() (via cleanup(), or a host that opens and unmounts\n // in one tick) may run before the timer, and a listener installed after\n // that has nothing left to remove it.\n this._inboxClickTimer = setTimeout(() => {\n this._inboxClickTimer = null;\n this._inboxClickHandler = (e) => {\n const target = e.composedPath()[0] || e.target;\n if (\n !this.inboxView.el?.contains(target) &&\n !this.inboxBtn.contains(target) &&\n !this._isInsideLightbox(target)\n ) {\n // Same reasoning as the thread popover: an unsaved draft turns a\n // click outside into \"stay open\", not into a question.\n if (this.inboxView.isDirty()) return;\n this.closeInbox();\n }\n };\n document.addEventListener(\"mousedown\", this._inboxClickHandler);\n }, 0);\n }\n\n closeInbox() {\n this.inboxView?.close();\n this._disarmInboxOutsideClick();\n }\n\n /**\n * Drops the inbox's outside-click listener, whether it is already on\n * `document` or still sitting in a pending timer. One place, so re-opening\n * and closing cannot each remember a different half of it.\n */\n _disarmInboxOutsideClick() {\n if (this._inboxClickTimer) {\n clearTimeout(this._inboxClickTimer);\n this._inboxClickTimer = null;\n }\n if (this._inboxClickHandler) {\n document.removeEventListener(\"mousedown\", this._inboxClickHandler);\n this._inboxClickHandler = null;\n }\n }\n\n /**\n * The open thread popover element, or null. Lives on the controller;\n * surfaced under its historical name because the inbox, the marker\n * engine paths and the test suite all read it here.\n */\n get activeThreadPopover() {\n return this._popover?.active ?? null;\n }\n\n // `circle` may be null for orphaned comments (opened from the inbox):\n // the popover is centered in the viewport instead of pinned to a marker.\n showThreadPopover(circle, comment) {\n this._popover.show(circle, comment);\n this._notifyCommentOpened(comment);\n }\n\n /**\n * Someone is now looking at a comment's full thread \u2014 from its marker or\n * from the inbox detail, which are the only two places the replies are\n * readable. This is the signal an unread count is built on; the widget\n * keeps no read state of its own, because whose \"read\" it is depends on an\n * identity only the host can persist.\n *\n * @param {any} comment the live comment, serialized on the way out like\n * every other payload that crosses this boundary\n */\n _notifyCommentOpened(comment) {\n if (!comment) return;\n this._notify(\"onCommentOpened\", [this._serializeComment(comment)]);\n }\n\n /**\n * Calls one of the options that is not part of the change stream, with the\n * same isolation `_emit` gives the ones that are: a subscriber that throws\n * must not take down the operation that was reporting to it.\n *\n * @param {\"onCommentModeChanged\" | \"onCommentOpened\"} name\n * @param {any[]} args\n */\n _notify(name, args) {\n // Cast: the two options have different signatures, so the union of them\n // takes no spread. The call sites below are the only ones, and each\n // passes what its own option declares.\n const handler = /** @type {any} */ (this.options[name]);\n if (typeof handler !== \"function\") return;\n try {\n handler(...args);\n } catch (err) {\n console.warn(`HellDots: ${name} handler threw`, err);\n }\n }\n\n closeThreadPopover() {\n // cleanup() reaches here before initOverlay() has run when the document\n // was still loading, so there may be no controller yet.\n this._popover?.close();\n }\n\n syncThreadPopoverToMarker() {\n this._popover?.syncToMarker();\n }\n\n showLightbox(imageSrc) {\n this.closeLightbox();\n\n // Whoever opened the lightbox (a thumbnail in the shadow tree, or a\n // host-page element) gets focus back when it closes.\n this._lightboxReturnFocus =\n this.shadowRoot.activeElement || document.activeElement;\n\n const lightbox = document.createElement(\"div\");\n lightbox.className = CLASSES.LIGHTBOX;\n lightbox.setAttribute(\"role\", \"dialog\");\n lightbox.setAttribute(\"aria-modal\", \"true\");\n lightbox.setAttribute(\"aria-label\", this.strings.screenshotPreview);\n\n const img = document.createElement(\"img\");\n img.className = CLASSES.LIGHTBOX_IMG;\n img.src = imageSrc;\n img.alt = this.strings.screenshotPreview;\n\n const closeBtn = document.createElement(\"button\");\n closeBtn.type = \"button\";\n closeBtn.className = CLASSES.LIGHTBOX_CLOSE;\n closeBtn.setAttribute(\"aria-label\", this.strings.close);\n closeBtn.innerHTML = \"×\";\n closeBtn.addEventListener(\"click\", () => this.closeLightbox());\n\n lightbox.appendChild(img);\n lightbox.appendChild(closeBtn);\n\n lightbox.addEventListener(\"click\", (e) => {\n if (e.target === lightbox) this.closeLightbox();\n });\n\n this.shadowRoot.appendChild(lightbox);\n this._activeLightbox = lightbox;\n\n // aria-modal is a promise about focus: the page behind the backdrop must\n // be unreachable. The close button is the only stop, so the trap is a\n // re-focus rather than a ring walk \u2014 same reasoning as confirm-dialog,\n // and on document in the capture phase for the same reason.\n this._lightboxKeydownHandler = (e) => {\n if (e.key !== \"Tab\") return;\n e.preventDefault();\n closeBtn.focus();\n };\n document.addEventListener(\"keydown\", this._lightboxKeydownHandler, true);\n\n closeBtn.focus();\n }\n\n closeLightbox() {\n if (!this._activeLightbox) return;\n if (this._lightboxKeydownHandler) {\n document.removeEventListener(\n \"keydown\",\n this._lightboxKeydownHandler,\n true\n );\n this._lightboxKeydownHandler = null;\n }\n this._activeLightbox.remove();\n this._activeLightbox = null;\n const returnFocus = /** @type {HTMLElement | null} */ (\n this._lightboxReturnFocus\n );\n this._lightboxReturnFocus = null;\n if (returnFocus?.isConnected) returnFocus.focus?.();\n }\n\n // The lightbox is opened *from* the inbox and the thread popover but lives\n // as their sibling in the shadow root, so a naive \"is this click outside my\n // element?\" test reads every click on it \u2014 including its own close button \u2014\n // as a click away from the panel, and tears the panel down behind it.\n _isInsideLightbox(target) {\n return Boolean(target?.closest?.(`.${CLASSES.LIGHTBOX}`));\n }\n\n /**\n * The one lookup every id-taking method goes through. Uses sameId so a\n * legacy numeric id resolves no matter which spelling the caller holds \u2014\n * index.d.ts promises exactly that.\n * @param {import('./index.d.ts').CommentId} id\n */\n _findComment(id) {\n return this.comments.find((c) => sameId(c.id, id));\n }\n\n /**\n * The hover tooltip currently open for a comment, if any. The one place\n * the `[data-for]` selector is built, so a host id carrying a quote is\n * escaped once instead of at five call sites.\n * @param {import('./index.d.ts').CommentId} id\n * @returns {HTMLElement | null}\n */\n _tooltipEl(id) {\n return (\n this.shadowRoot?.querySelector(\n `.${CLASSES.TOOLTIP}[data-for=\"${cssAttrValue(id)}\"]`\n ) ?? null\n );\n }\n\n /**\n * @param {import('./index.d.ts').Comment | import('./index.d.ts').CommentId} commentOrId\n * the live comment, or its id \u2014 every sibling mutator takes an id, so\n * this one stopped being the exception.\n * @param {string} text\n * @param {string[]} [screenshots]\n * @returns {import('./index.d.ts').CommentReply | null} null when an id\n * does not resolve\n */\n addReply(commentOrId, text, screenshots = []) {\n const comment =\n typeof commentOrId === \"object\" && commentOrId !== null\n ? commentOrId\n : this._findComment(\n /** @type {import('./index.d.ts').CommentId} */ (commentOrId)\n );\n if (!comment) return null;\n if (!comment.replies) comment.replies = [];\n const reply = {\n id: createId(),\n editedAt: null,\n text,\n author: this.options.user?.name || this.strings.anonymous,\n authorId: normalizeActorId(this.options.user?.id) || null,\n timestamp: new Date().toISOString(),\n screenshots,\n };\n comment.replies.push(reply);\n this._syncStorage();\n const serialized = this._serializeComment(comment);\n const serializedReply = this._serializeReply(reply);\n this._emit(\"reply:added\", [serialized, serializedReply], {\n comment: serialized,\n reply: serializedReply,\n });\n return reply;\n }\n\n /**\n * Removes one reply from a thread. The root comment is untouched \u2014 deleting\n * the last reply leaves the comment itself standing, which is why this is\n * separate from deleteComment rather than a special case of it.\n *\n * @param {import('./index.d.ts').CommentId} commentId\n * @param {import('./index.d.ts').CommentId} replyId\n * @returns {boolean} false when either id does not resolve\n */\n deleteReply(commentId, replyId) {\n const comment = this._findComment(commentId);\n const index =\n comment?.replies?.findIndex((r) => sameId(r.id, replyId)) ?? -1;\n if (index < 0) return false;\n const target = replyTargetOf(comment.replies[index], comment.id);\n if (!this._permits(\"delete:reply\", target)) return false;\n\n const [reply] = comment.replies.splice(index, 1);\n this._syncStorage();\n const serialized = this._serializeComment(comment);\n const serializedReply = this._serializeReply(reply);\n this._emit(\"reply:deleted\", [serialized, serializedReply], {\n comment: serialized,\n reply: serializedReply,\n });\n return true;\n }\n\n /**\n * Rewrites a comment's text and stamps `editedAt`.\n *\n * Refuses an empty body: a comment with no text keeps its marker, its\n * replies and its inbox row while saying nothing, so blanking is not a\n * back door to deletion \u2014 deleting is its own action and it asks first.\n * Refuses a no-op too, so opening the editor and saving without typing\n * does not brand the comment as edited.\n *\n * @param {import('./index.d.ts').CommentId} id\n * @param {string} text\n * @returns {boolean} false when the id does not resolve, or nothing changed\n */\n editComment(id, text) {\n const comment = this._findComment(id);\n const next = String(text ?? \"\").trim();\n if (!comment || !next || next === comment.text) return false;\n if (!this._permits(\"edit:comment\", commentTargetOf(comment))) return false;\n\n comment.text = next;\n comment.editedAt = new Date().toISOString();\n // The text itself is deliberately not recorded: the log says who changed\n // what and when, and keeping every superseded revision would turn it into\n // a second copy of the corpus.\n recordEvent(comment, \"edited\", this._actor());\n // The marker's accessible name is the comment text \u2014 a screen-reader\n // user tabbing to it must hear the current sentence, not the old one.\n this._circles\n .get(String(comment.id))\n ?.setAttribute(\n \"aria-label\",\n `${this.strings.commentAriaLabelPrefix}${comment.text}`\n );\n this._syncStorage();\n const edited = this._serializeComment(comment);\n this._emit(\"comment:edited\", [edited], { comment: edited });\n return true;\n }\n\n /**\n * Same contract as editComment, one level down.\n *\n * @param {import('./index.d.ts').CommentId} commentId\n * @param {import('./index.d.ts').CommentId} replyId\n * @param {string} text\n * @returns {boolean} false when either id does not resolve, or nothing changed\n */\n editReply(commentId, replyId, text) {\n const comment = this._findComment(commentId);\n const reply = comment?.replies?.find((r) => sameId(r.id, replyId));\n const next = String(text ?? \"\").trim();\n if (!reply || !next || next === reply.text) return false;\n if (!this._permits(\"edit:reply\", replyTargetOf(reply, comment.id))) {\n return false;\n }\n\n reply.text = next;\n reply.editedAt = new Date().toISOString();\n this._syncStorage();\n const serialized = this._serializeComment(comment);\n const serializedReply = this._serializeReply(reply);\n this._emit(\"reply:edited\", [serialized, serializedReply], {\n comment: serialized,\n reply: serializedReply,\n });\n return true;\n }\n\n _serializeReply({\n id,\n text,\n author,\n authorId,\n timestamp,\n screenshots,\n editedAt,\n // Defaulted, not just optional: addReply builds a reply without the field,\n // and a fresh reply has nothing to serialize yet.\n reactions = null,\n }) {\n return {\n id,\n text,\n author,\n authorId: authorId || null,\n timestamp,\n screenshots: screenshots || [],\n editedAt: editedAt || null,\n reactions: serializeReactions(reactions),\n };\n }\n\n /**\n * Serializable snapshot of one comment: the live `container` element is\n * replaced by its `anchor`. Screenshots (data-URLs) are included \u2014 the\n * localStorage mode and the inbox cards need them.\n */\n _serializeComment(comment) {\n return {\n // The anchor and context sub-objects always carried a version; the\n // comment gets one too so future breaking changes have a hinge \u2014\n // purely additive, loadComments ignores it today.\n schemaVersion: 1,\n id: comment.id,\n text: comment.text,\n editedAt: comment.editedAt || null,\n anchor: comment.anchor || null,\n page: comment.page || location.pathname,\n replies: (comment.replies || []).map((reply) =>\n this._serializeReply(reply)\n ),\n author: comment.author,\n // Identity, not copy: the display name is what any renderer shows, and\n // this is what a host correlates against its own user table.\n authorId: comment.authorId || null,\n // Copied out for the same reason as tags and reactions, and null rather\n // than [] when nothing was recorded, so an untouched corpus costs no\n // extra bytes.\n history: serializeHistory(comment.history),\n createdAt: comment.createdAt,\n screenshots: comment.screenshots || [],\n status: comment.status || \"open\",\n type: comment.type || null,\n priority: comment.priority || null,\n // Copied rather than referenced: a host mutating serializeComments()\n // output must not be able to reach back into overlay internals.\n tags: comment.tags ? [...comment.tags] : [],\n // Copied for the same reason as `tags`, and null rather than `{}` when\n // nobody reacted, so an untouched corpus costs no extra bytes.\n reactions: serializeReactions(comment.reactions),\n resolvedAt: comment.resolvedAt || null,\n context: comment.context ? { ...comment.context } : null,\n contextScreenshot: comment.contextScreenshot || null,\n };\n }\n\n /**\n * RF09 \u2014 moves a comment through its lifecycle\n * (open \u2192 in_progress \u2192 in_review \u2192 resolved, in any order).\n * @param {import('./index.d.ts').CommentId} id\n * @param {import('./index.d.ts').CommentStatus} status\n * @returns {boolean} false when the id or status is unknown\n */\n setCommentStatus(id, status) {\n if (!STATUSES.includes(status)) return false;\n const comment = this._findComment(id);\n if (!comment) return false;\n // No-op: picking the status the comment is already in must not re-stamp\n // resolvedAt (that would reset RF5's elapsed time to \"<1m\") or trigger a\n // storage write / inbox refresh / callback for nothing having changed.\n if (comment.status === status) return true;\n const previous = comment.status;\n comment.status = status;\n // RF5 \u2014 the timestamp always describes the CURRENT resolution: a\n // reopened comment loses it, and resolving again re-stamps it.\n comment.resolvedAt =\n status === \"resolved\" ? new Date().toISOString() : null;\n // After the no-op guard above, never before it: an entry recorded there\n // would log a change that did not happen.\n recordEvent(comment, \"status\", this._actor(), {\n from: previous,\n to: status,\n });\n // Resolving removes the on-page marker; reopening restores it. The\n // lookup goes through the comment's own id, not the caller's spelling.\n const circle = this._circles.get(String(comment.id));\n if (circle) this.updateCommentPosition(comment, circle);\n this._syncStorage();\n // Re-render the inbox so the card picks up the new status right away\n // (resolved styling + sink-to-bottom sorting) no matter where the\n // change came from \u2014 card, detail or thread popover.\n if (this.inboxView?.isOpen()) this.inboxView.refresh();\n const changed = this._serializeComment(comment);\n // Both ends of the move, the same pair the audit entry just recorded: a\n // host routing \"reopened\" or \"resolved\" differently should not have to\n // diff against its own previous copy to find out which one happened.\n this._emit(\n \"comment:status-changed\",\n [changed],\n { comment: changed },\n {\n from: previous,\n to: status,\n }\n );\n return true;\n }\n\n /**\n * Replaces the identity new comments, replies and reactions are attributed\n * to. Everything already recorded keeps the author it was written with \u2014\n * this is a change of who is acting now, not a rewrite of history.\n *\n * Exists because identity commonly arrives *after* the widget: the overlay\n * mounts, the session resolves 200ms later, and the alternative was\n * `cleanup()` plus a rebuild \u2014 which throws away every loaded comment and\n * whatever panel was open. Passing `null` returns to the anonymous author.\n *\n * @param {{ name: string, id?: string } | null} [user]\n * @returns {boolean} false when the argument is neither null nor an object\n * carrying a usable name\n */\n setUser(user) {\n if (user != null) {\n if (typeof user !== \"object\") return false;\n if (typeof user.name !== \"string\" || !user.name.trim()) return false;\n }\n this.options.user = user ?? undefined;\n // Both panels read the actor through a function rather than a captured\n // value, so all they need is a re-render: which reactions are shown as\n // the current user's own changes with the identity.\n this._popover?.close();\n if (this.inboxView?.isOpen()) this.inboxView.refresh();\n return true;\n }\n\n /**\n * The current actor as the audit log records them: id when the host\n * supplies one, plus the display name. Sibling of _actorKey \u2014 one produces\n * a de-duplication key, the other a record meant to be read back.\n * @returns {{ id?: string, name: string }}\n */\n _actor() {\n return actorOf(this.options.user, this.strings);\n }\n\n /**\n * The key the current actor's reactions are stored under. `user.id` when the\n * host supplies one, the display name otherwise \u2014 see actorKeyOf, which is\n * the only place this is decided so the toggle and the \"mine\" render can\n * never disagree.\n * @returns {string}\n */\n _actorKey() {\n return actorKeyOf(this.options.user, this.strings);\n }\n\n /**\n * Whether the current actor may edit or delete the record `target` names.\n *\n * Public because the answer has to be askable from outside: the widget's\n * own menus use it to decide what to render, and a host putting a delete\n * button in its own chrome needs the same verdict from the same rule\n * rather than a second copy of it that drifts.\n *\n * @param {import('./index.d.ts').PermissionAction} action\n * @param {import('./index.d.ts').PermissionTarget} target\n * @returns {boolean}\n */\n can(action, target) {\n return resolvePermission({\n can: this.options.can,\n action,\n target,\n user: this.options.user,\n strings: this.strings,\n });\n }\n\n /**\n * The guard the four mutators are held to \u2014 and only when the click came\n * from inside the widget.\n *\n * A call from the host's own code is never refused. That is the whole\n * reason this reads `_origin` instead of calling `can` directly: the host\n * drives the very same public methods the inbox does, and a backend that\n * has just authorized a moderator's delete must be able to complete it\n * without arguing with a client-side rule it already outranks.\n *\n * @param {import('./index.d.ts').PermissionAction} action\n * @param {import('./index.d.ts').PermissionTarget} target\n * @returns {boolean}\n */\n _permits(action, target) {\n return this._origin !== \"user\" || this.can(action, target);\n }\n\n /**\n * Shared tail of both reaction toggles: persist, keep an open inbox in step,\n * and hand the host the comment plus whichever reply carried the reaction.\n * @param {any} comment\n * @param {any | null} reply\n * @returns {true}\n */\n _commitReaction(comment, reply) {\n this._syncStorage();\n // A pill lives on the card, in the detail and in the popover at once, so a\n // toggle anywhere has to reach the copies it did not repaint itself.\n if (this.inboxView?.isOpen()) this.inboxView.refresh();\n const serialized = this._serializeComment(comment);\n const serializedReply = reply ? this._serializeReply(reply) : null;\n this._emit(\"reaction:toggled\", [serialized, serializedReply], {\n comment: serialized,\n reply: serializedReply,\n });\n return true;\n }\n\n /**\n * Flips the current actor's reaction on a comment: present, it is removed;\n * absent, it is added.\n * @param {import('./index.d.ts').CommentId} id\n * @param {string} emoji one of REACTION_EMOJIS\n * @returns {boolean} false when the id or the emoji is unknown\n */\n toggleCommentReaction(id, emoji) {\n const comment = this._findComment(id);\n if (!comment) return false;\n if (!toggleReactionOn(comment, emoji, this._actorKey())) return false;\n return this._commitReaction(comment, null);\n }\n\n /**\n * Same contract as toggleCommentReaction, one level down.\n * @param {import('./index.d.ts').CommentId} commentId\n * @param {import('./index.d.ts').CommentId} replyId\n * @param {string} emoji one of REACTION_EMOJIS\n * @returns {boolean} false when either id, or the emoji, is unknown\n */\n toggleReplyReaction(commentId, replyId, emoji) {\n const comment = this._findComment(commentId);\n const reply = comment?.replies?.find((r) => sameId(r.id, replyId));\n if (!reply) return false;\n if (!toggleReactionOn(reply, emoji, this._actorKey())) return false;\n return this._commitReaction(comment, reply);\n }\n\n /**\n * Shared tail of the classification setters: persist, re-render the\n * inbox if it's showing, and notify the host app.\n * @param {any} comment\n * @returns {true}\n */\n /**\n * @param {any} comment\n * @param {{ field: \"type\" | \"priority\" | \"tags\", from?: any, to?: any }} detail\n * which field moved, and both ends of the move where there are two. The\n * setters already compute this for the audit trail; passing it on costs\n * nothing and saves every host the same diff.\n */\n _commitUpdate(comment, detail) {\n this._syncStorage();\n if (this.inboxView?.isOpen()) this.inboxView.refresh();\n const updated = this._serializeComment(comment);\n this._emit(\"comment:updated\", [updated], { comment: updated }, detail);\n return true;\n }\n\n /**\n * RF3 \u2014 categorises a comment. `null` returns it to the neutral state.\n * @param {import('./index.d.ts').CommentId} id\n * @param {import('./index.d.ts').CommentType | null} type\n * @returns {boolean} false when the id or type is unknown\n */\n setCommentType(id, type) {\n if (type !== null && !COMMENT_TYPES.includes(type)) return false;\n const comment = this._findComment(id);\n if (!comment) return false;\n const previousType = comment.type ?? null;\n // The same no-op guard setCommentStatus has always had. Without it,\n // re-applying the value a comment already holds wrote to storage,\n // refreshed the inbox and emitted comment:updated for nothing \u2014 which a\n // host mirroring a remote change hears as its own echo.\n if (previousType === type) return true;\n comment.type = type;\n recordEvent(comment, \"classified\", this._actor(), {\n field: \"type\",\n from: previousType,\n to: type,\n });\n return this._commitUpdate(comment, {\n field: \"type\",\n from: previousType,\n to: type,\n });\n }\n\n /**\n * RF4 \u2014 prioritises a comment. `null` returns it to the neutral state.\n * @param {import('./index.d.ts').CommentId} id\n * @param {import('./index.d.ts').CommentPriority | null} priority\n * @returns {boolean} false when the id or priority is unknown\n */\n setCommentPriority(id, priority) {\n if (priority !== null && !PRIORITIES.includes(priority)) return false;\n const comment = this._findComment(id);\n if (!comment) return false;\n const previousPriority = comment.priority ?? null;\n if (previousPriority === priority) return true;\n comment.priority = priority;\n recordEvent(comment, \"classified\", this._actor(), {\n field: \"priority\",\n from: previousPriority,\n to: priority,\n });\n return this._commitUpdate(comment, {\n field: \"priority\",\n from: previousPriority,\n to: priority,\n });\n }\n\n /**\n * RF3 \u2014 replaces a comment's free-form labels. Values are trimmed,\n * lowercased and de-duplicated.\n * @param {import('./index.d.ts').CommentId} id\n * @param {string[]} tags\n * @returns {boolean} false when the id is unknown or tags isn't an array\n */\n setCommentTags(id, tags) {\n if (!Array.isArray(tags)) return false;\n const comment = this._findComment(id);\n if (!comment) return false;\n // Joined on a character no tag can contain, rather than compared with\n // JSON: the list is already normalised on both sides, so this is the\n // whole of \"did anything actually change\".\n const previous = [...(comment.tags || [])];\n const previousKey = previous.join(\"\\u0000\");\n const next = normalizeTags(tags);\n if (next.join(\"\\u0000\") === previousKey) return true;\n comment.tags = next;\n recordEvent(comment, \"classified\", this._actor(), { field: \"tags\" });\n // Tags are a list, so unlike type and priority they have no two-value\n // transition to record in the audit trail \u2014 but a host diffing \"which\n // label was added\" still wants both sides, and here they are.\n return this._commitUpdate(comment, {\n field: \"tags\",\n from: previous,\n to: [...next],\n });\n }\n\n /**\n * Aggregate figures over every comment the widget holds \u2014 counts by\n * status, type and priority, the daily distribution, and the resolution\n * times derived from the audit log.\n *\n * Unfiltered on purpose: a host has no notion of the panel's filters. The\n * dashboard inside the inbox measures whatever that panel is showing.\n * @returns {import('./index.d.ts').CommentMetrics}\n */\n getMetrics() {\n return computeMetrics(this.serializeComments());\n }\n\n /**\n * Downloads the corpus as CSV, one row per comment. Screenshots stay out:\n * a 33 KB base64 string in a spreadsheet cell is not data.\n * @param {import('./index.d.ts').SerializedComment[]} [comments] defaults to all\n */\n exportCommentsCsv(comments) {\n const rows = commentRows(comments || this.serializeComments());\n const csv = toCsv(rows, columnsOf(COMMENT_COLUMNS));\n downloadCsv(\"helldots-comments.csv\", csv);\n // Returned as well as downloaded: a browser download is a dead end for a\n // host that wanted to POST the same rows to its own endpoint or attach\n // them to a message, and building the CSV twice is the only alternative.\n return csv;\n }\n\n /**\n * Downloads the aggregate figures as CSV in long format \u2014 one row per\n * bucket, so the shape stays the same however many days the corpus spans.\n * @param {import('./index.d.ts').SerializedComment[]} [comments] defaults to all\n */\n exportMetricsCsv(comments) {\n const metrics = computeMetrics(comments || this.serializeComments());\n const csv = toCsv(metricRows(metrics), columnsOf(METRIC_COLUMNS));\n downloadCsv(\"helldots-metrics.csv\", csv);\n return csv;\n }\n\n /**\n * Opens the browser's print dialog on a report of the figures \u2014 which is\n * where \"save as PDF\" lives, at no cost in bundle size. The report is\n * built in its own document, so what prints is the report and not the\n * host page.\n * @param {import('./index.d.ts').SerializedComment[]} [comments] defaults to all\n * @param {string} [scope] a label describing what was measured\n */\n printMetricsReport(comments, scope) {\n const metrics = computeMetrics(comments || this.serializeComments());\n printMetricsReport(metrics, {\n strings: this.strings,\n locale: this.locale,\n css: getReportStyles(),\n scope,\n });\n }\n\n /**\n * @returns {import('./index.d.ts').SerializedComment[]}\n */\n serializeComments() {\n return this.comments.map((comment) => this._serializeComment(comment));\n }\n\n /**\n * Removes a comment everywhere: page marker, memory and (when the\n * localStorage mode is on) persisted storage.\n * @param {import('./index.d.ts').CommentId} id\n * @returns {boolean} false when the id is unknown\n */\n deleteComment(id) {\n const comment = this._findComment(id);\n if (!comment) return false;\n if (!this._permits(\"delete:comment\", commentTargetOf(comment))) {\n return false;\n }\n this._removeComment(id);\n if (this.options.persistence === \"localStorage\") {\n // The merge preserves other-page entries missing from memory, which\n // would resurrect a deleted inactive comment \u2014 drop the id explicitly.\n const merged = mergeForStorage(\n this._readStoredCached().filter((comment) => !sameId(comment.id, id)),\n this.serializeComments(),\n location.pathname\n );\n writeStoredComments(merged);\n this._storedCache = merged;\n }\n this._emit(\"comment:deleted\", [id], { id });\n return true;\n }\n\n _removeComment(id) {\n this.markers?.remove(id);\n this.comments = this.comments.filter((comment) => !sameId(comment.id, id));\n }\n\n /**\n * Removes every comment at once \u2014 markers, memory and (in localStorage\n * mode) their persisted entries. This is the bulk reset a host needs to\n * reconcile against its backend before a fresh loadComments, so it\n * deliberately fires no per-comment onCommentDeleted callbacks: the host\n * initiated it and would only hear its own action echoed back.\n */\n clearComments() {\n this.closeThreadPopover();\n const cleared = this.comments;\n this.markers?.clear();\n this.comments = [];\n\n if (this.options.persistence === \"localStorage\" && cleared.length > 0) {\n const clearedIds = new Set(cleared.map((comment) => String(comment.id)));\n const merged = this._readStoredCached().filter(\n (comment) => !clearedIds.has(String(comment.id))\n );\n writeStoredComments(merged);\n this._storedCache = merged;\n }\n if (this.inboxView?.isOpen()) this.inboxView.refresh();\n }\n\n /**\n * Restores serialized comments: each anchor is resolved back to a live\n * element (circle re-rendered) or the comment is kept as an orphan \u2014\n * present in the list/inbox but never positioned over the wrong element.\n * Loading the same id again replaces the previous copy (idempotent).\n * @param {import('./index.d.ts').SerializedComment[]} data\n * @returns {{ anchored: number, orphaned: number, inactive: number }}\n */\n loadComments(data) {\n let anchored = 0;\n let orphaned = 0;\n let inactive = 0;\n if (!Array.isArray(data)) return { anchored, orphaned, inactive };\n\n // Called before the widget mounted \u2014 a host whose fetch resolved while\n // the document was still parsing. Resolving anchors against a half-built\n // DOM would report orphans that are not orphans and fire onAnchorLost\n // for each of them, so the data is held and replayed by initOverlay.\n // Zeroes are all this can honestly return at that point: nothing has\n // been resolved yet. A host that needs the counts should load from\n // onReady, where the widget is up and they mean something.\n if (!this.markers) {\n this._deferredLoad = [...(this._deferredLoad || []), ...data];\n return { anchored, orphaned, inactive };\n }\n\n for (const item of data) {\n if (!item || item.id == null || typeof item.text !== \"string\") {\n console.warn(\"HellDots: skipping malformed serialized comment\", item);\n // A record the widget drops is a record the host still believes it\n // is showing \u2014 which is exactly the kind of divergence that goes\n // unnoticed until someone asks where their comment went.\n this._reportError(\n new Error(\"HellDots: skipping malformed serialized comment\"),\n \"load\"\n );\n continue;\n }\n this._removeComment(item.id);\n\n const comment = {\n id: item.id,\n text: item.text,\n editedAt: item.editedAt || null,\n anchor: item.anchor || null,\n anchorState: \"orphaned\",\n target: null,\n hidden: false,\n page: item.page || location.pathname,\n container: null,\n relativeX: 0,\n relativeY: 0,\n // Same minimal gate the top-level comment passes (id + text):\n // a malformed reply would otherwise flow into every renderer.\n replies: Array.isArray(item.replies)\n ? item.replies\n .filter(\n (reply) =>\n reply &&\n typeof reply === \"object\" &&\n reply.id != null &&\n typeof reply.text === \"string\"\n )\n .map((reply) => ({\n ...reply,\n authorId: normalizeActorId(reply.authorId) || null,\n ...(Array.isArray(reply.screenshots)\n ? { screenshots: onlyStrings(reply.screenshots) }\n : {}),\n reactions: normalizeReactions(reply.reactions),\n }))\n : [],\n author: item.author || this.strings.anonymous,\n // Scrubbed like every other field crossing this boundary: the id\n // reaches a host that may look it up, so a non-string must not\n // survive a round trip through localStorage or a backend.\n authorId: normalizeActorId(item.authorId) || null,\n // Scrubbed on the way in like the reaction map: an unknown event type\n // has no label, and an unparseable timestamp poisons every duration\n // derived from it.\n history: normalizeHistory(item.history),\n createdAt: item.createdAt || new Date().toISOString(),\n // Screenshots land in <img src> as-is \u2014 a non-string entry renders\n // a silently broken thumbnail.\n screenshots: Array.isArray(item.screenshots)\n ? onlyStrings(item.screenshots)\n : [],\n // \"closed\" existed briefly and was folded into \"resolved\".\n status:\n /** @type {string} */ (item.status) === \"closed\"\n ? \"resolved\"\n : STATUSES.includes(item.status)\n ? item.status\n : \"open\",\n // Records persisted before RF1-RF5 have none of these \u2014 every\n // reader downstream may assume they exist after this point.\n type: COMMENT_TYPES.includes(item.type) ? item.type : null,\n priority: PRIORITIES.includes(item.priority) ? item.priority : null,\n tags: Array.isArray(item.tags) ? [...item.tags] : [],\n resolvedAt: item.resolvedAt || null,\n // Reactions come from localStorage or from the host's backend, so the\n // map is scrubbed before any renderer sees it: unknown glyphs and\n // duplicated actor keys both survive a round trip otherwise.\n reactions: normalizeReactions(item.reactions),\n context: item.context || null,\n contextScreenshot: item.contextScreenshot || null,\n };\n\n // Comments from other pages aren't broken \u2014 their elements just\n // don't exist here. They stay listed (inbox \"all\" filter) without a\n // marker and without an onAnchorLost false alarm.\n if (item.page && item.page !== location.pathname) {\n comment.anchorState = \"inactive\";\n this.comments.push(comment);\n inactive++;\n continue;\n }\n\n const resolved = item.anchor ? resolveAnchor(item.anchor) : null;\n if (resolved) {\n comment.container = resolved.element;\n comment.relativeX = item.anchor.relativeX;\n comment.relativeY = item.anchor.relativeY;\n comment.anchorState = \"anchored\";\n this.comments.push(comment);\n this.renderCommentCircle(comment);\n anchored++;\n } else {\n this.comments.push(comment);\n orphaned++;\n const lost = this._serializeComment(comment);\n this._emit(\"comment:anchor-lost\", [lost], { comment: lost });\n }\n }\n\n // A link the host's data had not arrived for yet may be waiting on\n // exactly the comments that just landed.\n this._openPendingDetail();\n\n return { anchored, orphaned, inactive };\n }\n\n /**\n * Re-syncs the widget after a client-side navigation: reclassifies every\n * comment against the new `location.pathname`, re-resolves anchors\n * against the new DOM, rebuilds markers, and moves the inbox onto the\n * new page. Call it from the router's \"after navigation\" hook; with\n * `autoDetectNavigation` it also runs on popstate (back/forward).\n *\n * Same-path calls are useful too: an SPA that re-rendered its route\n * swapped every node, and this is the \"re-anchor now\" primitive.\n *\n * @returns {{ anchored: number, orphaned: number, inactive: number }}\n */\n notifyNavigation() {\n const page = location.pathname;\n let anchored = 0;\n let orphaned = 0;\n let inactive = 0;\n\n // Nothing is mounted and nothing is loaded yet (loadComments defers too),\n // so there is no state to re-sync \u2014 and every panel this touches below\n // is still null.\n if (!this.markers) return { anchored, orphaned, inactive };\n\n // Panels pinned to the old DOM don't survive a route change; the inbox\n // does \u2014 it is cross-page by design and refreshes below.\n this.closeThreadPopover();\n this.hideCommentBox();\n if (this.inboxView) this.inboxView.currentPage = page;\n\n for (const comment of this.comments) {\n this.markers.remove(comment.id);\n comment.hidden = false;\n comment.target = null;\n comment._occluded = false;\n\n if (comment.page && comment.page !== page) {\n comment.anchorState = \"inactive\";\n comment.container = null;\n inactive++;\n continue;\n }\n\n const resolved = comment.anchor ? resolveAnchor(comment.anchor) : null;\n if (resolved) {\n comment.container = resolved.element;\n comment.relativeX = comment.anchor.relativeX;\n comment.relativeY = comment.anchor.relativeY;\n comment.anchorState = \"anchored\";\n this.renderCommentCircle(comment);\n anchored++;\n } else {\n // Same contract as loadComments: kept and listed, never positioned\n // over a guessed element \u2014 and the host is told, each time, because\n // \"the element is gone on this visit\" is fresh information.\n comment.container = null;\n comment.anchorState = \"orphaned\";\n orphaned++;\n const lost = this._serializeComment(comment);\n this._emit(\"comment:anchor-lost\", [lost], { comment: lost });\n }\n }\n\n // The new URL may itself carry a deep link (a copy-link opened through\n // the SPA's router) or the cross-page handoff written just before the\n // host navigated.\n this._pendingDetailId = this._readPendingDetailId();\n if (this.inboxView?.isOpen()) this.inboxView.refresh();\n this._openPendingDetail();\n\n return { anchored, orphaned, inactive };\n }\n\n scrollMarkerIntoView(comment) {\n // Scrolling to an invisible marker would scroll to nothing.\n if (this.markersHidden) this._setMarkersHidden(false);\n this.markers.scrollMarkerIntoView(comment);\n }\n\n createPreviewCircle(x, y) {\n this.removePreviewCircle();\n\n const circle = document.createElement(\"div\");\n circle.className = `${CLASSES.CIRCLE} ${CLASSES.PREVIEW_CIRCLE}`;\n circle.style.position = \"absolute\";\n const circleRadius = MARKER_SIZE / 2;\n circle.style.left = `${x + circleRadius}px`;\n circle.style.top = `${y + circleRadius}px`;\n circle.style.transform = \"translate(-50%, -50%)\";\n circle.style.pointerEvents = \"none\";\n\n this.overlay.appendChild(circle);\n this.previewCircle = circle;\n }\n\n removePreviewCircle() {\n this.previewCircle?.remove();\n this.previewCircle = null;\n }\n\n // ------------------------------------------------------------------\n // Marker facade \u2014 the engine owns the logic (marker-engine.js); these\n // keep the overlay-level names every internal caller and the test suite\n // grew around. State fields surface as accessors for the same reason.\n // ------------------------------------------------------------------\n\n cleanupResizeObserver(commentId) {\n this.markers.cleanupResizeObserver(commentId);\n }\n\n validateAndCalculatePosition(comment, circle) {\n return this.markers.validateAndCalculatePosition(comment, circle);\n }\n\n updateCommentPosition(comment, circle) {\n this.markers.updatePosition(comment, circle);\n }\n\n scheduleUpdatePositions() {\n this.markers.scheduleUpdate();\n }\n\n /** Marker circles by String(id) \u2014 lives on the engine. */\n get _circles() {\n return this.markers?.circles;\n }\n\n /** Per-comment ResizeObservers \u2014 live on the engine. */\n get resizeObservers() {\n return this.markers?.resizeObservers;\n }\n\n get positionValidationEnabled() {\n return this.markers?.enabled ?? true;\n }\n\n set positionValidationEnabled(value) {\n if (this.markers) this.markers.enabled = value;\n }\n\n get _globalMutationObserver() {\n return this.markers?._globalMutationObserver ?? null;\n }\n\n // A marker that just went away must not leave its hover tooltip or its\n // open thread popover floating on the page (e.g. above the modal that\n // now covers the marker).\n _dismissMarkerUi(comment) {\n this._tooltipEl(comment.id)?.remove();\n if (this.activeThreadPopover?.dataset.for === String(comment.id)) {\n this.closeThreadPopover();\n }\n }\n\n /**\n * Cleanup method to remove all event listeners and observers\n */\n cleanup() {\n // An instance destroyed while the document is still loading must not\n // mount when DOMContentLoaded eventually fires.\n if (this._onDomReady) {\n document.removeEventListener(\"DOMContentLoaded\", this._onDomReady);\n this._onDomReady = null;\n }\n // Cancels every scheduled pass, listener, observer and circle the\n // marker engine owns \u2014 including a rAF armed before teardown.\n this.markers?.destroy();\n if (this._storageHandler) {\n window.removeEventListener(\"storage\", this._storageHandler);\n this._storageHandler = null;\n }\n if (this._popstateHandler) {\n window.removeEventListener(\"popstate\", this._popstateHandler);\n this._popstateHandler = null;\n }\n this._storedCache = null;\n // An instance torn down before it mounted must not hold on to data it\n // will never replay.\n this._deferredLoad = null;\n // Dropping the panels leaves their dropdowns detached-but-open, which\n // would keep the menu registry's document listener alive until the next\n // stray mousedown.\n closeOpenMenus();\n // Same reasoning: an unanswered confirmation holds a capture-phase\n // keydown listener on document, which would go on eating Escape for the\n // whole page after the widget is gone.\n closeOpenConfirmDialogs();\n this.closeThreadPopover();\n this.closeInbox();\n this.closeLightbox();\n this.removePreviewCircle();\n // Covers the selection rect, the drag listeners and the pending capture.\n this._captureFlow?.destroy();\n this._pendingScreenshots = [];\n\n if (this._handleDocumentClickBound) {\n document.removeEventListener(\"mousedown\", this._handleDocumentClickBound);\n }\n\n // Remove keyboard shortcut handler\n if (this.keydownHandler) {\n document.removeEventListener(\"keydown\", this.keydownHandler);\n }\n\n // Remove DOM elements\n if (this.toolbar && this.toolbar.parentNode) {\n this.toolbar.parentNode.removeChild(this.toolbar);\n }\n if (this.commentBox && this.commentBox.parentNode) {\n this.commentBox.parentNode.removeChild(this.commentBox);\n }\n if (this.overlay && this.overlay.parentNode) {\n this.overlay.parentNode.removeChild(this.overlay);\n }\n\n document.body.classList.remove(CLASSES.COMMENT_CURSOR);\n // Covers both paths: removes the injected <style> or drops our adopted\n // sheet from the document. Leaving the latter behind would keep styling\n // the host page \u2014 the comment-mode cursor included \u2014 after teardown.\n this._detachStyles();\n\n // The now-empty shadow host itself: leaving <helldots-root> dangling\n // from <body> is half a cleanup. getShadowRoot() recreates it if a new\n // instance mounts later.\n document.querySelector(TAG_NAME)?.remove();\n }\n\n injectStyles() {\n // Re-injecting replaces rather than accumulates, whichever path is in\n // use \u2014 mountStyles hands back the undo for exactly what it mounted.\n this._detachStyles();\n this._styleDetachers = [\n mountStyles(this.shadowRoot, getStyles(), IDS.STYLES),\n // A few rules (e.g. the comment-mode cursor on document.body) target\n // the host page itself, which a shadow root's stylesheet cannot\n // reach \u2014 those go on the document instead.\n mountStyles(document, getGlobalStyles(), IDS.GLOBAL_STYLES),\n ];\n }\n\n _detachStyles() {\n for (const detach of this._styleDetachers ?? []) detach();\n this._styleDetachers = [];\n }\n}\n\nexport default CommentOverlay;\n", "import CommentOverlay from \"./overlay.js\";\n\n/**\n * Creates a CommentOverlay.\n *\n * Safe to call before the document is ready: `CommentOverlay`'s constructor\n * already defers its own DOM work to `DOMContentLoaded`, so callers always\n * get a real instance back and never have to branch on `readyState`. An\n * earlier version duplicated that same check here and, while the document\n * was loading, both registered a listener AND returned the uninvoked\n * initializer \u2014 so a caller who invoked it (reasonably, since the type said\n * it might be a function) ended up with two overlays, and the one the\n * listener built had no handle to call `cleanup()` on.\n *\n * @overload\n * @param {import('./index.d.ts').CommentOverlayOptions & { autoInit?: true }} [options]\n * @returns {CommentOverlay}\n */\n/**\n * @overload\n * @param {import('./index.d.ts').CommentOverlayOptions & { autoInit: false }} options\n * @returns {() => CommentOverlay}\n */\n/**\n * @param {import('./index.d.ts').CommentOverlayOptions} [options]\n * @returns {CommentOverlay | (() => CommentOverlay)}\n */\nexport function createCommentOverlay(options = {}) {\n const { autoInit = true, ...overlayOptions } = options;\n const initialize = () => new CommentOverlay(overlayOptions);\n return autoInit ? initialize() : initialize;\n}\n\n// Export the class for advanced usage\nexport { CommentOverlay };\n\n// Deep-link helpers. A host that loads comments lazily has to read the id\n// out of the URL *before* it can fetch anything \u2014 which means before the\n// widget can tell it. Exporting these is what keeps that read from becoming\n// a second, hand-written copy of the `linkParam` setting, free to drift from\n// the one the widget honours. See also the `onCommentRequested` option,\n// which covers the case where the widget is already up.\nexport { DEFAULT_LINK_PARAM, readCommentLinkParam } from \"./link.js\";\n\n// Export a default instance creator for simple usage\nexport default createCommentOverlay;\n"],
|
|
5
|
-
"mappings": ";AAAA,IAAMA,EAAW,gBAOXC,GAAgB,IAAM,CACtB,eAAe,IAAID,CAAQ,GAE/B,eAAe,OACbA,EACA,cAA2B,WAAY,CACrC,aAAc,CACZ,MAAM,EACN,KAAK,aAAa,CAAE,KAAM,MAAO,CAAC,CACpC,CACF,CACF,CACF,EAOO,SAASE,IAAgB,CAC9BD,GAAc,EAEd,IAAIE,EAAO,SAAS,cAAcH,CAAQ,EAC1C,OAAKG,IACHA,EAAO,SAAS,cAAcH,CAAQ,EACtC,SAAS,KAAK,YAAYG,CAAI,GAGzBA,EAAK,UACd,CCxBA,IAAMC,GAAM,IACV,OAAO,aAAa,KAAQ,WAAa,YAAY,IAAI,EAAI,KAAK,IAAI,EAWlEC,GAAW,IACf,IAAI,QAASC,GAAY,CACvB,GAAI,OAAO,gBAAmB,WAAY,CACxC,WAAWA,CAAO,EAClB,MACF,CACA,IAAMC,EAAU,IAAI,eACpBA,EAAQ,MAAM,UAAY,IAAM,CAC9BA,EAAQ,MAAM,MAAM,EACpBD,EAAQ,CACV,EACAC,EAAQ,MAAM,YAAY,IAAI,CAChC,CAAC,EAUGC,GAAiB,IAAM,CAC3B,IAAMC,EAAgC,WAAY,UAClD,OAAI,OAAOA,GAAW,OAAU,WAAmBA,EAAU,MAAM,EAC5DJ,GAAS,CAClB,EAqBO,SAASK,GAAmB,CAAE,SAAAC,EAAW,CAAgB,EAAI,CAAC,EAAG,CACtE,IAAIC,EAAOR,GAAI,EACTS,EAAS,IAAM,CAInBD,EAAOR,GAAI,CACb,EACA,MAAO,IAAM,CACX,GAAI,EAAAA,GAAI,EAAIQ,EAAOD,GACnB,OAAOH,GAAe,EAAE,KAAKK,EAAQA,CAAM,CAC7C,CACF,CCvDO,IAAMC,GAAsB,CACjC,kBACA,cACA,eACA,aACA,aACA,gBACA,gBACF,EAWaC,GAA2B,CACtC,GAAGD,GAGH,eACA,kBACA,iBACA,SACA,aACA,eACA,QACA,UACA,cACA,QACA,SACA,YACA,OACA,gBACA,cACA,eACA,aACA,aACA,YACA,aACA,YACA,iBACA,eACA,gBACA,cACA,WACA,QACA,eACA,MACA,iBACA,aACA,QACA,UAGA,sBACA,4BACA,6BACA,sBACA,sBACA,sBACA,sBACA,qBACA,sBACA,qBACA,oBACA,oBACA,oBACA,qBACA,qBACA,qBACA,mBACA,yBACA,0BACA,mBACA,mBACA,gBACA,iBACA,gBACA,gBAGA,gBACA,cACA,aACA,eACA,cACA,aACA,oBACA,oBACA,oBACA,cACA,eACA,aACA,iBACA,YACA,cACA,YACA,oBACA,iBACA,iBACA,kBACA,oBACA,eACA,iBACA,sBACA,wBACA,qBACA,kBACA,gBACA,eACA,QACA,UAGA,QACA,YACA,wBACA,YACA,eACA,aACA,eACA,0BACA,cACA,UACA,iBACA,cACA,mBACA,sBACA,kBACA,gBACA,WACA,aACA,kBACA,wBACA,uBACA,wBACA,4BACA,cACA,mBACA,cACA,wBACA,0BACA,eACA,cACA,aACA,eACA,eACA,qBACA,qBACA,0BACA,4BACA,4BAGA,kBACA,sBACA,wBACA,wBACA,mBACA,mBACA,oBACA,wBACA,wBACA,oBACA,kBACA,aACA,YACA,SACA,aACA,YACA,gBACA,cACA,YACA,iBACA,aACA,kBACA,UACA,cACA,qBACA,SACA,QACA,YACA,mBACA,kBACA,YAKA,eACA,aAIA,oBACA,OACA,eACA,YACA,cACA,kBACA,aACA,eACA,SACA,mBACA,oBACA,iBACA,kBACA,iBACA,eACA,aACF,EClNA,IAAME,GAAa,CAACC,EAAOC,IAAW,CACpC,GAAI,CACF,IAAMC,EAAS,SAAS,cAAc,QAAQ,EAG9C,GAFAA,EAAO,MAAQF,EACfE,EAAO,OAASD,EACZC,EAAO,QAAUF,GAASE,EAAO,SAAWD,EAAQ,MAAO,GAC/D,IAAME,EAAMD,EAAO,WAAW,IAAI,EAClC,OAAKC,GACLA,EAAI,UAAY,UAChBA,EAAI,SAAS,EAAG,EAAG,EAAG,CAAC,EAChBA,EAAI,aAAa,EAAG,EAAG,EAAG,CAAC,EAAE,KAAK,CAAC,IAAM,GAH/B,EAInB,MAAQ,CACN,MAAO,EACT,CACF,EAGIC,GAAW,KAWFC,GAAqB,KAC5BD,KAAa,OACjBA,GACE,CAAC,MAAO,MAAO,MAAO,KAAM,IAAa,E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",
|
|
4
|
+
"sourcesContent": ["const TAG_NAME = \"helldots-root\";\n\n// Defined lazily rather than at module scope: `extends HTMLElement` is\n// evaluated when the class expression runs, so a top-level declaration makes\n// a bare `import \"helldots\"` throw on any server renderer (Next.js, Remix,\n// Astro) long before the app calls anything. Deferring it keeps the module\n// import-safe everywhere and only touches the DOM when we actually mount.\nconst ensureDefined = () => {\n if (customElements.get(TAG_NAME)) return;\n\n customElements.define(\n TAG_NAME,\n class HelldotsRoot extends HTMLElement {\n constructor() {\n super();\n this.attachShadow({ mode: \"open\" });\n }\n }\n );\n};\n\n/**\n * Returns the shared shadow root used to render all HellDots UI, creating\n * the host element and mounting it on document.body on first call.\n * @returns {ShadowRoot}\n */\nexport function getShadowRoot() {\n ensureDefined();\n\n let host = document.querySelector(TAG_NAME);\n if (!host) {\n host = document.createElement(TAG_NAME);\n document.body.appendChild(host);\n }\n\n return host.shadowRoot;\n}\n\nexport { TAG_NAME };\n", "// Handing the main thread back to the browser in the middle of a render.\n//\n// `modern-screenshot`'s API is asynchronous, but its clone traversal awaits\n// promises that are already resolved. Those settle as MICROtasks, and the\n// microtask queue drains completely before the browser gets to paint or to\n// deliver a keystroke \u2014 so a 1.5 s render is 1.5 s of frozen page even\n// though not one call inside it is synchronous. Only a MACROtask breaks\n// that up. This module is that macrotask, on a time budget.\n\n/** Half a 60 Hz frame: enough headroom left for the browser to paint. */\nconst YIELD_BUDGET_MS = 8;\n\nconst now = () =>\n typeof performance?.now === \"function\" ? performance.now() : Date.now();\n\n/**\n * One macrotask turn.\n *\n * `setTimeout` would also be a task, but every browser clamps a nested\n * timeout to 4 ms, and on a heavy page this runs a couple of hundred times\n * \u2014 the clamp alone would add most of a second to the render it is meant\n * to make bearable. A `MessageChannel` message is a task with no clamp.\n * @returns {Promise<void>}\n */\nconst nextTask = () =>\n new Promise((resolve) => {\n if (typeof MessageChannel !== \"function\") {\n setTimeout(resolve);\n return;\n }\n const channel = new MessageChannel();\n channel.port1.onmessage = () => {\n channel.port1.close();\n resolve();\n };\n channel.port2.postMessage(null);\n });\n\n/**\n * Yields to the browser, preferring the API built for exactly this.\n *\n * `scheduler.yield()` resumes at continuation priority, so the render keeps\n * its place ahead of unrelated work the page may have queued; the\n * `MessageChannel` fallback goes to the back of the task queue instead.\n * @returns {Promise<void>}\n */\nconst yieldToBrowser = () => {\n const scheduler = /** @type {any} */ (globalThis).scheduler;\n if (typeof scheduler?.yield === \"function\") return scheduler.yield();\n return nextTask();\n};\n\n/**\n * Builds a per-render callback that yields once the time budget is spent.\n *\n * Time, not a node count: the cost of a node is not a constant. A synthetic\n * `<div>` clones in ~0.14 ms and a styled application node in ~0.44 ms, so\n * any fixed \"every N nodes\" is either a stutter on one page or pointless\n * overhead on another. A budget adapts to whatever it is actually walking.\n *\n * The returned function is the hot path \u2014 it runs once per cloned node \u2014 so\n * the common case returns `undefined` synchronously rather than allocating\n * a promise the caller would await for nothing.\n *\n * A rejection is treated as a completed yield, not propagated: this hook is\n * awaited inside the clone traversal, so throwing here would take the whole\n * capture down. Failing to pause is worth strictly less than failing to\n * produce the screenshot the widget exists to collect.\n * @param {{ budgetMs?: number }} [options]\n * @returns {() => Promise<void> | undefined}\n */\nexport function createPaintYielder({ budgetMs = YIELD_BUDGET_MS } = {}) {\n let last = now();\n const resume = () => {\n // Stamped after the yield resolves, not before: the time spent parked\n // in the task queue is the browser's, and charging it to the next\n // budget would make every following slice shorter than asked for.\n last = now();\n };\n return () => {\n if (now() - last < budgetMs) return undefined;\n return yieldToBrowser().then(resume, resume);\n };\n}\n", "// The style properties a capture actually needs.\n//\n// `modern-screenshot` reproduces an element by reading its computed style\n// and inlining it on the clone. Left alone it enumerates everything the\n// browser exposes \u2014 ~527 properties per element on a modern engine \u2014 and\n// that enumeration is the render: profiling a host's page put 116 467\n// property reads at 97 ms against 1 ms of cloning and 7 ms of rasterising.\n//\n// The clone is re-parented into a fresh document inside a `<foreignObject>`\n// with no cascade of its own, so anything omitted here is simply not there.\n// That makes this list a fidelity contract, not a preference: it has to\n// carry every property that changes a pixel, and it is opt-in precisely\n// because \"every property that changes a pixel\" is not decidable for a page\n// this library has never seen.\n//\n// Verified by rendering the same page with and without the list and\n// comparing the two canvases pixel for pixel \u2014 see DECISIONS.md.\n\n/**\n * Properties `modern-screenshot` reads back out of the map it just built,\n * to drive behaviour rather than appearance: scrollbar cloning, its Chrome\n * ellipsis workaround, the `background-clip: text` class hack, and the font\n * subsetting that decides which web fonts get embedded at all.\n *\n * Dropping one of these does not degrade an image, it changes what the\n * renderer does \u2014 which is why they are called out instead of being left to\n * blend into the list below.\n */\nexport const RENDERER_READS_BACK = [\n \"background-clip\",\n \"font-family\",\n \"font-kerning\",\n \"overflow-x\",\n \"overflow-y\",\n \"text-overflow\",\n \"text-transform\",\n];\n\n/**\n * The curated allow-list handed to `includeStyleProperties`.\n *\n * Longhands only. The renderer sets each name it is given straight onto the\n * clone's inline style, so a shorthand would work \u2014 but the browser\n * enumerates computed styles as longhands, and asking for `margin` when the\n * engine only answers to `margin-top` costs a lookup that returns nothing.\n * @type {string[]}\n */\nexport const CAPTURE_STYLE_PROPERTIES = [\n ...RENDERER_READS_BACK,\n\n // Box and flow.\n \"aspect-ratio\",\n \"border-collapse\",\n \"border-spacing\",\n \"bottom\",\n \"box-sizing\",\n \"caption-side\",\n \"clear\",\n \"display\",\n \"empty-cells\",\n \"float\",\n \"height\",\n \"isolation\",\n \"left\",\n \"margin-bottom\",\n \"margin-left\",\n \"margin-right\",\n \"margin-top\",\n \"max-height\",\n \"max-width\",\n \"min-height\",\n \"min-width\",\n \"padding-bottom\",\n \"padding-left\",\n \"padding-right\",\n \"padding-top\",\n \"position\",\n \"right\",\n \"table-layout\",\n \"top\",\n \"vertical-align\",\n \"visibility\",\n \"width\",\n \"z-index\",\n\n // Borders and outlines.\n \"border-bottom-color\",\n \"border-bottom-left-radius\",\n \"border-bottom-right-radius\",\n \"border-bottom-style\",\n \"border-bottom-width\",\n \"border-image-outset\",\n \"border-image-repeat\",\n \"border-image-slice\",\n \"border-image-source\",\n \"border-image-width\",\n \"border-left-color\",\n \"border-left-style\",\n \"border-left-width\",\n \"border-right-color\",\n \"border-right-style\",\n \"border-right-width\",\n \"border-top-color\",\n \"border-top-left-radius\",\n \"border-top-right-radius\",\n \"border-top-style\",\n \"border-top-width\",\n \"outline-color\",\n \"outline-offset\",\n \"outline-style\",\n \"outline-width\",\n\n // Flexbox, grid and multi-column.\n \"align-content\",\n \"align-items\",\n \"align-self\",\n \"column-count\",\n \"column-fill\",\n \"column-gap\",\n \"column-rule-color\",\n \"column-rule-style\",\n \"column-rule-width\",\n \"column-span\",\n \"column-width\",\n \"flex-basis\",\n \"flex-direction\",\n \"flex-grow\",\n \"flex-shrink\",\n \"flex-wrap\",\n \"grid-auto-columns\",\n \"grid-auto-flow\",\n \"grid-auto-rows\",\n \"grid-column-end\",\n \"grid-column-start\",\n \"grid-row-end\",\n \"grid-row-start\",\n \"grid-template-areas\",\n \"grid-template-columns\",\n \"grid-template-rows\",\n \"justify-content\",\n \"justify-items\",\n \"justify-self\",\n \"order\",\n \"row-gap\",\n\n // Typography.\n \"color\",\n \"direction\",\n \"font-feature-settings\",\n \"font-size\",\n \"font-stretch\",\n \"font-style\",\n \"font-variant\",\n \"font-variation-settings\",\n \"font-weight\",\n \"hyphens\",\n \"letter-spacing\",\n \"line-height\",\n \"list-style-image\",\n \"list-style-position\",\n \"list-style-type\",\n \"overflow-wrap\",\n \"tab-size\",\n \"text-align\",\n \"text-align-last\",\n \"text-decoration-color\",\n \"text-decoration-line\",\n \"text-decoration-style\",\n \"text-decoration-thickness\",\n \"text-indent\",\n \"text-orientation\",\n \"text-shadow\",\n \"text-underline-offset\",\n \"text-underline-position\",\n \"unicode-bidi\",\n \"white-space\",\n \"word-break\",\n \"word-spacing\",\n \"writing-mode\",\n \"-webkit-box-orient\",\n \"-webkit-line-clamp\",\n \"-webkit-text-fill-color\",\n \"-webkit-text-stroke-color\",\n \"-webkit-text-stroke-width\",\n\n // Paint.\n \"backdrop-filter\",\n \"backface-visibility\",\n \"background-attachment\",\n \"background-blend-mode\",\n \"background-color\",\n \"background-image\",\n \"background-origin\",\n \"background-position-x\",\n \"background-position-y\",\n \"background-repeat\",\n \"background-size\",\n \"box-shadow\",\n \"clip-path\",\n \"filter\",\n \"mask-image\",\n \"mask-mode\",\n \"mask-position\",\n \"mask-repeat\",\n \"mask-size\",\n \"mix-blend-mode\",\n \"object-fit\",\n \"object-position\",\n \"opacity\",\n \"perspective\",\n \"perspective-origin\",\n \"rotate\",\n \"scale\",\n \"transform\",\n \"transform-origin\",\n \"transform-style\",\n \"translate\",\n\n // Form controls, which the UA paints from these rather than from a\n // background: an unstyled checkbox with no `accent-color` comes out as an\n // empty box.\n \"accent-color\",\n \"appearance\",\n\n // SVG. Presentation attributes resolve into computed style, so a chart or\n // an icon set is invisible without them.\n \"dominant-baseline\",\n \"fill\",\n \"fill-opacity\",\n \"fill-rule\",\n \"paint-order\",\n \"shape-rendering\",\n \"stop-color\",\n \"stop-opacity\",\n \"stroke\",\n \"stroke-dasharray\",\n \"stroke-dashoffset\",\n \"stroke-linecap\",\n \"stroke-linejoin\",\n \"stroke-opacity\",\n \"stroke-width\",\n \"text-anchor\",\n];\n", "// How large a canvas this browser will actually paint.\n//\n// Every engine caps both a canvas's single dimension and its total area, and\n// neither cap is reported anywhere. Worse, going past one is not an error: the\n// assignment is accepted, `canvas.width`/`.height` read back exactly what was\n// set, `getContext(\"2d\")` hands out a context, every draw call succeeds \u2014 and\n// the canvas holds no pixels. A render of a long page comes back completely\n// blank with nothing on the console to say why.\n//\n// Measured in Chromium 1265px wide: 65 535 tall holds paint, 65 536 does not;\n// 16384x16384 (268 Mpx) holds, 20000x20000 (400 Mpx) does not. Firefox caps\n// the dimension at 32 767 and mobile Safari caps the area far lower, so the\n// numbers here are a starting point and `paintsPixels` is the thing that\n// actually decides.\n\n/**\n * Chromium's measured area cap, used as the opening guess.\n *\n * Only a guess: engines differ by more than an order of magnitude, so a\n * render is verified afterwards rather than trusted to this.\n */\nconst AREA_LIMIT = 16384 * 16384;\n\n/** Fallback when even the smallest probe fails \u2014 pathological, but finite. */\nconst MIN_DIMENSION = 4096;\n\n/**\n * Whether a canvas of this size holds what is painted into it.\n * @param {number} width\n * @param {number} height\n * @returns {boolean}\n */\nconst holdsPaint = (width, height) => {\n try {\n const canvas = document.createElement(\"canvas\");\n canvas.width = width;\n canvas.height = height;\n if (canvas.width !== width || canvas.height !== height) return false;\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return false;\n ctx.fillStyle = \"#ffffff\";\n ctx.fillRect(0, 0, 1, 1);\n return ctx.getImageData(0, 0, 1, 1).data[3] !== 0;\n } catch {\n return false;\n }\n};\n\n/** @type {number | null} */\nlet measured = null;\n\n/**\n * The largest single dimension this browser paints.\n *\n * Probed one pixel wide, so it measures the dimension cap on its own: a\n * 1x65535 canvas is 256 KB and tells us nothing about the area cap, which is\n * exactly what is wanted here. Measured once and remembered \u2014 the answer is a\n * property of the engine, not of the page.\n * @returns {number}\n */\nexport const maxCanvasDimension = () => {\n if (measured !== null) return measured;\n measured =\n [65535, 32767, 16384, 8192, MIN_DIMENSION].find((d) => holdsPaint(1, d)) ??\n MIN_DIMENSION;\n return measured;\n};\n\n/**\n * The largest scale at or below `wanted` that should produce a painted canvas.\n *\n * Area is bounded by a square root because scaling touches both axes: halving\n * the scale quarters the pixel count.\n *\n * The limits are parameters so this stays a pure function of four numbers.\n * Callers pass none \u2014 the defaults are the measured dimension and the area\n * guess \u2014 but a test can pin every branch without a browser, and the area\n * branch is otherwise unreachable wherever the dimension probe floors out.\n * @param {number} width CSS pixels of the node being rendered\n * @param {number} height\n * @param {number} wanted the scale the caller asked for\n * @param {{ maxDimension?: number, maxArea?: number }} [limits]\n * @returns {number}\n */\nexport const fittingScale = (width, height, wanted, limits = {}) => {\n if (!(width > 0) || !(height > 0)) return wanted;\n const { maxDimension = maxCanvasDimension(), maxArea = AREA_LIMIT } = limits;\n return Math.min(\n wanted,\n maxDimension / width,\n maxDimension / height,\n Math.sqrt(maxArea / (width * height))\n );\n};\n\n/**\n * Whether a finished render actually holds pixels.\n *\n * Reads one pixel's alpha, which works only because every render is given an\n * opaque `backgroundColor` and the renderer fills the whole canvas with it\n * before drawing. A render allowed to stay transparent would read as failed\n * here \u2014 that coupling is deliberate and is why `effectiveBackgroundColor`\n * falls back to white rather than returning null.\n * @param {any} canvas\n * @returns {boolean}\n */\nexport const paintsPixels = (canvas) => {\n try {\n const ctx = canvas?.getContext?.(\"2d\");\n return Boolean(ctx) && ctx.getImageData(0, 0, 1, 1).data[3] !== 0;\n } catch {\n return false;\n }\n};\n", "// Screenshot primitives. The page render is the expensive part, so it's\n// split out from the cropping: a drag capture and the automatic context\n// capture of the same comment share ONE render instead of paying for two.\n//\n// WE own the crop in page coordinates \u2014 html2canvas used to own it and\n// silently shifted it by the window scroll (double-counting: the hero\n// showed up in captures taken further down the page). Owning the crop\n// makes that whole bug class impossible.\n\nimport { TAG_NAME } from \"./root-element.js\";\nimport { createPaintYielder } from \"./yield-to-paint.js\";\nimport { CAPTURE_STYLE_PROPERTIES } from \"./capture-style-props.js\";\nimport { fittingScale, paintsPixels } from \"./canvas-limits.js\";\n\n/** Automatic captures render and encode small \u2014 they live in localStorage. */\nexport const AUTO_SCALE = 0.5;\nconst AUTO_QUALITY = 0.7;\n\n// The renderer is the single heaviest thing this package pulls in (~10 KB\n// gzip), and most page views never take a capture \u2014 so it loads on the first\n// render, not with the host's initial bundle. A failed load is forgotten\n// rather than cached: a transient network error at capture time must not\n// poison every capture after it.\n/** @type {Promise<typeof import(\"modern-screenshot\")> | undefined} */\nlet rendererPromise;\nconst loadRenderer = () => {\n rendererPromise ??= import(\"modern-screenshot\").catch((error) => {\n rendererPromise = undefined;\n throw error;\n });\n return rendererPromise;\n};\n\nconst isUnpainted = (color) =>\n !color || color === \"transparent\" || color === \"rgba(0, 0, 0, 0)\";\n\n// What the user visually perceives as the page background: the html/body\n// CSS color when one is painted, else white \u2014 browsers paint their own\n// white canvas under a transparent document, but that canvas is not part\n// of the DOM, so a DOM-based render would come out as a transparent PNG\n// (invisible against the dark inbox UI).\nconst effectiveBackgroundColor = () => {\n const htmlBg = getComputedStyle(document.documentElement).backgroundColor;\n if (!isUnpainted(htmlBg)) return htmlBg;\n const bodyBg = getComputedStyle(document.body).backgroundColor;\n if (!isUnpainted(bodyBg)) return bodyBg;\n return \"#ffffff\";\n};\n\n/**\n * Whether `modern-screenshot` can embed the page's web fonts.\n *\n * It reads `@font-face` rules by parking a `<style>` in a detached document\n * and reading back `.sheet`. That element inherits the host page's CSP, so a\n * policy with a strict `style-src` refuses to parse it, `.sheet` comes back\n * null, and the render dies on `null.cssRules` \u2014 taking the entire capture\n * with it, not just the fonts. Probing costs one detached document; the\n * render it guards costs orders of magnitude more.\n * @returns {boolean}\n */\nexport const canEmbedWebFonts = () => {\n try {\n const probe = document.implementation.createHTMLDocument(\"\");\n const style = probe.createElement(\"style\");\n probe.head.appendChild(style);\n return style.sheet !== null;\n } catch {\n return false;\n }\n};\n\n/**\n * Pulls the `@font-face` blocks out of a stylesheet's source text.\n *\n * Only those: the sheet is a third party's, and appending the whole thing to\n * the host's `<head>` would put its layout rules last in the cascade and\n * restyle the page for the duration of the capture.\n * @param {string} css\n * @returns {string}\n */\nexport const extractFontFaceRules = (css) => {\n const blocks = [];\n let at = css.indexOf(\"@font-face\");\n while (at !== -1) {\n const open = css.indexOf(\"{\", at);\n const close = open === -1 ? -1 : css.indexOf(\"}\", open);\n if (close === -1) break; // truncated sheet \u2014 keep what parsed cleanly\n blocks.push(css.slice(at, close + 1));\n at = css.indexOf(\"@font-face\", close);\n }\n return blocks.join(\"\\n\");\n};\n\n/** Same sheet, same session, one request. @type {Map<string, Promise<string>>} */\nconst fontRuleCache = new Map();\n\nconst fetchFontRules = (href) => {\n if (!fontRuleCache.has(href)) {\n fontRuleCache.set(\n href,\n fetch(href, { mode: \"cors\", credentials: \"omit\" })\n .then((res) => (res.ok ? res.text() : \"\"))\n .then(extractFontFaceRules)\n .catch(() => \"\")\n );\n }\n return fontRuleCache.get(href);\n};\n\nconst isReadable = (sheet) => {\n try {\n return Boolean(sheet.cssRules);\n } catch {\n return false;\n }\n};\n\n/**\n * Makes a cross-origin stylesheet's web fonts reachable by the renderer, and\n * returns the undo.\n *\n * `cssRules` throws `SecurityError` on a cross-origin sheet, so the renderer\n * never finds its `@font-face` rules and never inlines the font files. What\n * it produces is an SVG rendered as an image \u2014 an isolated document with no\n * network of its own \u2014 so a font that was not inlined is simply absent and\n * the text reflows into a fallback. Fallback metrics differ, which moves\n * every glyph sideways: the page still *looks* about right, but a drag crop\n * taken at live coordinates comes back holding the wrong glyphs.\n *\n * `fetch` succeeds where `cssRules` does not \u2014 font CDNs serve\n * `Access-Control-Allow-Origin: *` \u2014 and a same-origin `<style>` carrying\n * those rules is readable, so the renderer inlines the binaries itself\n * rather than us reimplementing that. A host that refuses the fetch (no\n * CORS, a `connect-src` policy) lands exactly where it is today.\n *\n * Off unless asked for: requesting a third party's stylesheet is network a\n * host did not sign up for by mounting a comment widget, so that call is\n * theirs to make.\n * @param {boolean} enabled\n * @returns {Promise<() => void>}\n */\nconst shimUnreadableFontRules = async (enabled) => {\n const noop = () => {};\n if (!enabled || !canEmbedWebFonts()) return noop;\n\n const hrefs = Array.from(document.styleSheets)\n .filter((sheet) => sheet.href && !isReadable(sheet))\n .map((sheet) => sheet.href);\n if (!hrefs.length) return noop;\n\n const css = (await Promise.all(hrefs.map(fetchFontRules)))\n .filter(Boolean)\n .join(\"\\n\");\n if (!css) return noop;\n\n const style = document.createElement(\"style\");\n style.textContent = css;\n document.head.appendChild(style);\n return () => style.remove();\n};\n\n/**\n * Builds the clone filter.\n *\n * `skipIframeContent` drops what lives inside an iframe while keeping the\n * `<iframe>` element itself, and that distinction is the whole point.\n * Filtering the iframe out by tag name \u2014 the obvious reading \u2014 removes its\n * BOX, so everything below it slides up by the frame's height: measured at\n * a 260px shift on a 260px frame, with the crop still taken at live page\n * coordinates. That is the misalignment class `capture.js` exists to make\n * impossible. Matching on `ownerDocument` leaves the box, its border and\n * its space exactly where the page put them, and blanks only the interior.\n *\n * Nodes in a shadow root keep the host's `ownerDocument`, so the widget's\n * own shadow content is unaffected by this test.\n * @param {boolean} skipIframeContent\n * @returns {(node: Node) => boolean}\n */\nconst captureFilter = (skipIframeContent) => (node) => {\n // nodeName, not tagName: the filter also receives text nodes, which must\n // be kept (and have no tagName).\n if (node.nodeName?.toLowerCase() === TAG_NAME) return false;\n if (skipIframeContent && node.ownerDocument !== document) return false;\n return true;\n};\n\n/**\n * Renders the whole page to a canvas. This is the expensive call \u2014 callers\n * that need more than one image should render once and crop repeatedly.\n *\n * The widget must never render into its own screenshot: the host node is\n * filtered out of the clone. Filtering replaced the old hide-during-render\n * approach (withHiddenOverlay), which took the whole UI off screen for the\n * duration of the render and therefore forced callers to await the capture\n * before showing anything \u2014 with the filter, a capture can run in the\n * background while the comment box is already on screen.\n * @param {{ scale?: number, embedCrossOriginFonts?: boolean,\n * fastCapture?: boolean, skipIframeContent?: boolean,\n * captureTimeout?: number }} [options]\n * scale 1 keeps the canvas in CSS pixels so crop rects map 1:1 to page\n * coordinates. `embedCrossOriginFonts` opts into fetching stylesheets the\n * renderer cannot read, so their web fonts survive into the capture.\n * `fastCapture` narrows the computed-style enumeration to a curated list\n * (see capture-style-props.js) \u2014 roughly 2.7x off the dominant phase, at\n * the cost of any property that list does not name. `skipIframeContent`\n * blanks embedded documents instead of cloning them. `captureTimeout`\n * bounds how long a single remote asset may hold the render up.\n * @returns {Promise<{ canvas: any, scale: number }>} the render and the scale\n * it was ACTUALLY produced at, which is not always the one asked for \u2014 see\n * the canvas ceiling below. Every crop has to map through this rather than\n * assume the requested scale.\n */\nexport async function renderPage({\n scale = 1,\n embedCrossOriginFonts = false,\n fastCapture = false,\n skipIframeContent = false,\n captureTimeout,\n} = {}) {\n const { domToCanvas } = await loadRenderer();\n const unshim = await shimUnreadableFontRules(embedCrossOriginFonts);\n const { width, height } = document.documentElement.getBoundingClientRect();\n // A page taller than the browser's canvas ceiling used to render to a\n // canvas that reported the right size and held nothing, so every crop off\n // it was blank and nothing said so. Fitting the scale to the ceiling turns\n // that into a capture that is correct and progressively softer.\n let attempt = fittingScale(width, height, scale);\n try {\n // The ceiling differs by more than an order of magnitude between\n // engines, so the fitted scale is a guess and the render is checked\n // rather than trusted. Halving quarters the pixel count, so three\n // attempts cover a 64x overshoot; past that, throwing is the honest\n // outcome \u2014 it reaches the host through onError, where a blank image\n // never would.\n for (let left = 3; ; left--) {\n // documentElement, not body. The clone is re-parented into a document\n // where the UA's `body { margin: 8px }` applies again, even on a page\n // that zeroed it \u2014 so rendering <body> pushed every flow element 8px\n // right and down inside a canvas that did not grow, losing 8px off the\n // right edge and putting every crop 8px out. <html> carries no such\n // margin, so page coordinates and canvas pixels line up 1:1, which is\n // exactly what the crops below assume.\n const canvas = await domToCanvas(document.documentElement, {\n scale: attempt,\n backgroundColor: effectiveBackgroundColor(),\n // Dropping web fonts costs one font substitution inside the image;\n // keeping them where they cannot be read costs the image entirely.\n ...(canEmbedWebFonts() ? {} : { font: false }),\n filter: captureFilter(skipIframeContent),\n // The clone traversal awaits this hook once per node, which makes it\n // the one place a caller can get the main thread back mid-render \u2014\n // see yield-to-paint.js for why awaiting anything else does not.\n onCloneEachNode: createPaintYielder(),\n // Spread rather than a null: passing `includeStyleProperties: null`\n // is the renderer's own \"enumerate everything\" default, so the two\n // branches would be indistinguishable to a test reading the options.\n ...(fastCapture\n ? { includeStyleProperties: CAPTURE_STYLE_PROPERTIES }\n : {}),\n // Omitted rather than defaulted: the renderer has its own 30 000 ms,\n // and repeating that number here would pin us to one that is theirs\n // to change.\n //\n // Finite AND positive, both load-bearing, because the two values a\n // host would reach for to mean \"no deadline\" each do the opposite.\n // The renderer reads 0 as \"never give up\" and hangs; `Infinity`\n // reaches `setTimeout`, which coerces it to 0 and aborts on the\n // spot. Neither is a deadline, so neither is honoured \u2014 and a\n // string that merely compares as a number is not one either.\n ...(Number.isFinite(captureTimeout) && captureTimeout > 0\n ? { timeout: captureTimeout }\n : {}),\n });\n\n if (paintsPixels(canvas)) return { canvas, scale: attempt };\n if (left <= 0) {\n throw new Error(\n `HellDots: the page render came back holding no pixels. ` +\n `${Math.round(width)}x${Math.round(height)} CSS pixels is most ` +\n `likely past this browser's canvas limit.`\n );\n }\n attempt /= 2;\n }\n } finally {\n unshim();\n }\n}\n\n/**\n * Lays the page's background down across the whole output before the render\n * goes on top.\n *\n * The render covers the BODY's box, which on a page shorter than the\n * viewport is shorter than the crop. Whatever the render does not reach\n * keeps the canvas's initial transparent black \u2014 invisible in a PNG, and a\n * solid black band once JPEG flattens it. The browser paints html/body\n * across the entire viewport, so the background is what is really there.\n * @param {CanvasRenderingContext2D} ctx\n * @param {number} width\n * @param {number} height\n */\nconst paintBackdrop = (ctx, width, height) => {\n ctx.fillStyle = effectiveBackgroundColor();\n ctx.fillRect(0, 0, width, height);\n};\n\n/**\n * Crops a viewport-relative region out of a scale-1 page render.\n * @param {any} canvas full-page render from `renderPage`\n * @param {{ left: number, top: number, width: number, height: number }} region\n * Viewport (client) coordinates of the drag selection.\n * @param {{ sourceScale?: number }} [options] the scale `canvas` was actually\n * produced at \u2014 `renderPage` reports it, and it is not always the one asked\n * for. The output stays sized in CSS pixels either way, so a render the\n * canvas ceiling forced down comes back soft rather than the wrong size.\n * @returns {string | null} PNG data-URL, or null with no 2d context.\n */\nexport function cropRegion(\n canvas,\n { left, top, width, height },\n { sourceScale = 1 } = {}\n) {\n const out = document.createElement(\"canvas\");\n out.width = width;\n out.height = height;\n const ctx = out.getContext(\"2d\");\n if (!ctx) return null;\n\n paintBackdrop(ctx, width, height);\n ctx.drawImage(\n canvas,\n (left + window.scrollX) * sourceScale,\n (top + window.scrollY) * sourceScale,\n width * sourceScale,\n height * sourceScale,\n 0,\n 0,\n width,\n height\n );\n return out.toDataURL(\"image/png\");\n}\n\n/**\n * Crops the current viewport out of a page render and encodes it small.\n * @param {any} canvas full-page render\n * @param {{ sourceScale?: number, outputScale?: number, quality?: number }} [options]\n * `sourceScale` is the scale `canvas` was rendered at \u2014 the source rect is\n * mapped through it. `outputScale` is the final size in CSS pixels.\n * @returns {string | null} JPEG data-URL, or null with no 2d context.\n */\nexport function cropViewport(\n canvas,\n { sourceScale = 1, outputScale = AUTO_SCALE, quality = AUTO_QUALITY } = {}\n) {\n const out = document.createElement(\"canvas\");\n out.width = Math.round(window.innerWidth * outputScale);\n out.height = Math.round(window.innerHeight * outputScale);\n const ctx = out.getContext(\"2d\");\n if (!ctx) return null;\n\n paintBackdrop(ctx, out.width, out.height);\n ctx.drawImage(\n canvas,\n window.scrollX * sourceScale,\n window.scrollY * sourceScale,\n window.innerWidth * sourceScale,\n window.innerHeight * sourceScale,\n 0,\n 0,\n out.width,\n out.height\n );\n return out.toDataURL(\"image/jpeg\", quality);\n}\n", "export const CLASSES = {\n CIRCLE: \"comment-circle\",\n CIRCLE_ACTIVE: \"comment-circle--active\",\n TOOLTIP: \"comment-tooltip\",\n TOOLBAR_TEXT: \"toolbar-text\",\n SHORTCUT_HINT: \"shortcut-hint\",\n COMMENT_INPUT_AREA: \"comment-input-area\",\n CLOSE_TOOLTIP: \"close-tooltip\",\n ACTIVE: \"active\",\n COMMENT_CURSOR: \"comment-cursor\",\n COMMENT_OVERLAY: \"comment-overlay\",\n THREAD_POPOVER: \"comment-thread-popover\",\n THREAD_HEADER: \"thread-header\",\n THREAD_BODY: \"thread-body\",\n THREAD_REPLIES: \"thread-replies\",\n THREAD_REPLY: \"thread-reply\",\n THREAD_REPLY_ACTIONS: \"thread-reply-actions\",\n THREAD_INPUT_AREA: \"thread-input-area\",\n THREAD_INPUT: \"thread-input\",\n THREAD_SUBMIT: \"thread-submit\",\n THREAD_ACTIONS_ROW: \"thread-actions-row\",\n THREAD_SCROLL: \"thread-scroll\",\n THREAD_META: \"thread-meta\",\n THREAD_AUTHOR: \"thread-author\",\n THREAD_AUTHOR_NAME: \"thread-author-name\",\n INBOX_HEADER_ACTIONS: \"inbox-header-actions\",\n INBOX_METRICS_BTN: \"inbox-metrics-btn\",\n METRICS_VIEW: \"metrics-view\",\n METRICS_TILES: \"metrics-tiles\",\n METRICS_TILE: \"metrics-tile\",\n METRICS_TILE_VALUE: \"metrics-tile-value\",\n METRICS_TILE_LABEL: \"metrics-tile-label\",\n METRICS_GROUP: \"metrics-group\",\n METRICS_HEADING: \"metrics-heading\",\n METRICS_ROW: \"metrics-row\",\n METRICS_ROW_LABEL: \"metrics-row-label\",\n METRICS_TRACK: \"metrics-track\",\n METRICS_BAR: \"metrics-bar\",\n METRICS_ROW_COUNT: \"metrics-row-count\",\n METRICS_CHART: \"metrics-chart\",\n METRICS_AXIS: \"metrics-axis\",\n METRICS_EXPORTS: \"metrics-exports\",\n METRICS_EXPORT_BTN: \"metrics-export-btn\",\n METRICS_EMPTY: \"metrics-empty\",\n AUDIT_BLOCK: \"audit-block\",\n AUDIT_TOGGLE: \"audit-toggle\",\n AUDIT_BODY: \"audit-body\",\n AUDIT_LIST: \"audit-list\",\n AUDIT_ROW: \"audit-row\",\n AUDIT_ACTION: \"audit-action\",\n AUDIT_ACTOR: \"audit-actor\",\n AUDIT_TIME: \"audit-time\",\n AUDIT_HEADING: \"audit-heading\",\n AUDIT_RESOLUTIONS: \"audit-resolutions\",\n THREAD_TIME: \"thread-time\",\n THREAD_EDITED: \"thread-edited\",\n EDITOR: \"helldots-editor\",\n EDITOR_INPUT: \"helldots-editor-input\",\n EDITOR_ACTIONS: \"helldots-editor-actions\",\n EDITOR_SAVE: \"helldots-editor-save\",\n EDITOR_CANCEL: \"helldots-editor-cancel\",\n INBOX_NOTICE: \"inbox-notice\",\n PREVIEW_CIRCLE: \"preview-circle\",\n SELECTION_RECT: \"selection-rect\",\n SCREENSHOT_IMG: \"screenshot-img\",\n SCREENSHOT_REMOVE: \"screenshot-remove\",\n SCREENSHOTS_CONTAINER: \"screenshots-container\",\n SCREENSHOT_ITEM: \"screenshot-item\",\n SCREENSHOT_PENDING: \"screenshot-pending\",\n CONFIRM: \"helldots-confirm\",\n CONFIRM_PANEL: \"helldots-confirm-panel\",\n CONFIRM_TITLE: \"helldots-confirm-title\",\n CONFIRM_MESSAGE: \"helldots-confirm-message\",\n CONFIRM_ACTIONS: \"helldots-confirm-actions\",\n CONFIRM_CANCEL: \"helldots-confirm-cancel\",\n CONFIRM_ACCEPT: \"helldots-confirm-accept\",\n LIGHTBOX: \"helldots-lightbox\",\n LIGHTBOX_IMG: \"helldots-lightbox-img\",\n LIGHTBOX_CLOSE: \"helldots-lightbox-close\",\n COMMENT_ACTIONS_BAR: \"comment-actions-bar\",\n ATTACH_IMAGE_BTN: \"attach-image-btn\",\n TOOLBAR_ACTIONS: \"toolbar-actions\",\n TOOLBAR_ACTION_BTN: \"toolbar-action-btn\",\n TOOLBAR_ACTION_WRAPPER: \"toolbar-action-wrapper\",\n TOOLBAR_ACTION_TOOLTIP: \"toolbar-action-tooltip\",\n TOOLBAR_COMMENT_BTN: \"toolbar-comment-btn\",\n TOOLBAR_MENU_BTN: \"toolbar-menu-btn\",\n TOOLBAR_VISIBILITY: \"toolbar-visibility\",\n TOOLBAR_EYE_BTN: \"toolbar-eye-btn\",\n MARKERS_HIDDEN: \"markers-hidden\",\n INBOX_PANEL: \"inbox-panel\",\n INBOX_HEADER: \"inbox-header\",\n INBOX_FILTER: \"inbox-filter\",\n INBOX_FILTER_MENU: \"inbox-filter-menu\",\n INBOX_FILTER_MENU_HEADER: \"inbox-filter-menu-header\",\n INBOX_FILTER_CLEAR: \"inbox-filter-clear\",\n INBOX_FILTER_GROUP: \"inbox-filter-group\",\n INBOX_FILTER_CHIPS: \"inbox-filter-chips\",\n INBOX_FILTER_CHIP: \"inbox-filter-chip\",\n INBOX_FILTER_SECTION: \"inbox-filter-section\",\n INBOX_CLOSE: \"inbox-close\",\n INBOX_LIST: \"inbox-list\",\n INBOX_CARD: \"inbox-card\",\n INBOX_CARD_HEADER: \"inbox-card-header\",\n INBOX_CARD_ACTIONS: \"inbox-card-actions\",\n INBOX_CARD_TEXT: \"inbox-card-text\",\n INBOX_CARD_TAG: \"inbox-card-tag\",\n INBOX_CARD_REPLY_LINK: \"inbox-card-reply-link\",\n INBOX_ACTION_BTN: \"inbox-action-btn\",\n INBOX_ACTION_BTN_LABELED: \"inbox-action-btn--labeled\",\n INBOX_ACTION_LABEL: \"inbox-action-label\",\n INBOX_STATUS_DOT: \"inbox-status-dot\",\n INBOX_MENU: \"inbox-menu\",\n // Set by menus.js when a dropdown has to open upward to stay unclipped.\n INBOX_MENU_UP: \"inbox-menu--up\",\n // The horizontal counterpart: set when a dropdown has to align to its\n // button's left edge instead of its right one to stay unclipped.\n INBOX_MENU_START: \"inbox-menu--start\",\n INBOX_MENU_ITEM: \"inbox-menu-item\",\n INBOX_DETAIL: \"inbox-detail\",\n INBOX_DETAIL_HEADER: \"inbox-detail-header\",\n INBOX_BACK: \"inbox-back\",\n INBOX_NAV_BTN: \"inbox-nav-btn\",\n INBOX_REPLIES: \"inbox-replies\",\n TOOLTIP_REPLY_COUNT: \"comment-tooltip-reply-count\",\n INBOX_EMPTY: \"inbox-empty\",\n INBOX_EMPTY_ICON: \"inbox-empty-icon\",\n INBOX_EMPTY_TITLE: \"inbox-empty-title\",\n INBOX_EMPTY_TEXT: \"inbox-empty-text\",\n INBOX_EMPTY_KBD: \"inbox-empty-kbd\",\n INBOX_EMPTY_ACTION: \"inbox-empty-action\",\n CLASSIFY_ROW: \"classify-row\",\n INBOX_BADGES: \"inbox-badges\",\n BADGE: \"helldots-badge\",\n BADGE_STATUS: \"helldots-badge--status\",\n BADGE_TYPE: \"helldots-badge--type\",\n BADGE_PRIORITY: \"helldots-badge--priority\",\n BADGE_TAG: \"helldots-badge--tag\",\n BADGE_DURATION: \"helldots-badge--duration\",\n CONTEXT_BLOCK: \"inbox-context\",\n CONTEXT_TITLE: \"inbox-context-title\",\n CONTEXT_BODY: \"inbox-context-body\",\n CONTEXT_ROW: \"inbox-context-row\",\n CONTEXT_SCREENSHOT_CAPTION: \"inbox-context-screenshot-caption\",\n CONTEXT_TOGGLE: \"inbox-context-toggle\",\n HIGHLIGHT: \"helldots-highlight\",\n REACTION_BAR: \"reaction-bar\",\n REACTION_PILL: \"reaction-pill\",\n REACTION_PILL_MINE: \"reaction-pill--mine\",\n REACTION_PILL_EMOJI: \"reaction-pill-emoji\",\n REACTION_PILL_COUNT: \"reaction-pill-count\",\n REACTION_ADD: \"reaction-add\",\n REACTION_TRIGGER: \"reaction-trigger\",\n REACTION_PALETTE: \"reaction-palette\",\n REACTION_PALETTE_ITEM: \"reaction-palette-item\",\n // The action strip splits in two: classification on the left, icon buttons\n // (react, copy, \u22EF) on the right.\n ACTIONS_GROUP: \"actions-group\",\n ACTIONS_GROUP_END: \"actions-group--end\",\n};\n\n// The only classes HellDots puts on elements of the host page \u2014 everything\n// else it owns lives inside the shadow root. Anchors must never bake these\n// into a selector: they are transient widget state, so `body.comment-cursor`\n// stops matching the instant comment mode ends, killing the anchor's fast\n// path. Deliberately an explicit list rather than every value of CLASSES:\n// generic names like `active` belong to host pages too, and filtering those\n// would weaken anchors instead of protecting them.\nexport const HOST_PAGE_CLASSES = [CLASSES.COMMENT_CURSOR];\n\n// The eye toggle's persisted preference. Its own key, independent of the\n// widget's `persistence` option: it is a viewer preference, not comment data.\nexport const MARKERS_HIDDEN_STORAGE_KEY = \"helldots-markers-hidden\";\n\nexport const IDS = {\n TOOLBAR: \"comment-toolbar\",\n COMMENT_BOX: \"comment-box\",\n COMMENT_INPUT: \"comment-input\",\n SUBMIT_COMMENT: \"submit-comment\",\n STYLES: \"comment-overlay-styles\",\n GLOBAL_STYLES: \"comment-overlay-global-styles\",\n ATTACH_IMAGE_INPUT: \"attach-image-input\",\n};\n\n// Marker circle size in px. The stylesheet's .comment-circle rule and the\n// positioning math (center offsets, edge clamps) must agree on this number.\nexport const MARKER_SIZE = 28;\n\n// Cap on user-attached screenshots per comment or reply \u2014 enforced by every\n// attachment surface (comment box, thread popover, inbox reply input).\nexport const MAX_SCREENSHOTS = 5;\n\n// RF09 \u2014 comment lifecycle. Order matters: it's the order shown in the\n// status picker menu and in the inbox's status filter. Nothing enforces the\n// transitions \u2014 setCommentStatus accepts any state from any state.\nexport const STATUSES = [\"open\", \"in_progress\", \"in_review\", \"resolved\"];\n\n// Every lifecycle state is painted \u2014 status is never \"unset\", so an empty\n// ring would read as missing rather than as new. `open` takes an off-white\n// grey: present and legible, but the only unsaturated entry, so the three\n// states somebody actually moved a comment into are the ones that carry\n// colour. That frees the blue for `in_review`.\nexport const STATUS_COLORS = {\n open: \"#D1D1D6\",\n in_progress: \"#FF9F0A\",\n in_review: \"#2E90FA\",\n resolved: \"#30D158\",\n};\n\n// RF3 \u2014 comment category. Order matters: it's the order shown in the picker.\nexport const COMMENT_TYPES = [\"bug\", \"suggestion\", \"question\", \"improvement\"];\n\nexport const TYPE_COLORS = {\n bug: \"#FF453A\",\n suggestion: \"#BF5AF2\",\n question: \"#64D2FF\",\n improvement: \"#5E5CE6\",\n};\n\n// RF4 \u2014 priority, ordered high\u2192low so the picker reads as a scale.\nexport const PRIORITIES = [\"high\", \"medium\", \"low\"];\n\n// Deliberate red/orange/grey ramp: it reads as urgency at a glance. `high`\n// sharing red with `bug` (and `medium` sharing orange with `in_progress`) is\n// fine \u2014 they're different dimensions in different UI slots, and no badge\n// ever conveys meaning by colour alone (WCAG 1.4.1).\nexport const PRIORITY_COLORS = {\n high: \"#FF453A\",\n medium: \"#FF9F0A\",\n low: \"#8E8E93\",\n};\n\n// Emoji reactions. The order is load-bearing twice over: it is the order of\n// the palette AND of the pills, so a pill never moves out from under the\n// pointer when a count changes. Fixed rather than host-configurable, and\n// deliberately small enough to need no emoji dataset \u2014 see DECISIONS.md.\nexport const REACTION_EMOJIS = [\"\uD83D\uDC4D\", \"\uD83D\uDC4E\", \"\u2764\uFE0F\", \"\uD83C\uDF89\", \"\uD83D\uDC40\", \"\uD83D\uDE80\"];\n\nexport const SELECTORS = {\n CONTAINER: 'section, div[class*=\"container\"], div[class*=\"content\"]',\n};\n\nexport const Z_INDEX = {\n CIRCLE: 9997,\n TOOLTIP: 10000,\n TOOLBAR: 9998,\n COMMENT_BOX: 9999,\n LIGHTBOX: 10001,\n // Above the lightbox: a screenshot can be open full-screen when the \u22EF menu\n // behind it is used, and a confirmation nobody can see is worse than none.\n CONFIRM: 10002,\n};\n\n// 32x32 is a hard ceiling, not a design preference: Chromium drops a custom\n// cursor larger than that as soon as it can intersect native UI, which is\n// exactly what happens as the pointer nears the page edges \u2014 the marker\n// silently reverted to the default arrow there.\n// https://chromestatus.com/feature/5825971391299584\n//\n// The artwork itself is unchanged and still 28px; only the canvas shrank,\n// from 48 to 32, by translating the art from (6,6) to (2,2). That is why\n// CURSOR_HOTSPOT moved with it \u2014 it names the teardrop's sharp tip, which is\n// what the pointer must actually point at.\n//\n// The original blue drop shadow is gone with the canvas: at `dx=4 dy=4` and\n// `stdDeviation=5` it needed ~15px of margin the 32px canvas does not have.\n// The white 2px stroke is what carries contrast against any background; the\n// shadow was a 16%-opacity blue glow that barely registered.\nexport const CURSOR_SVG = `data:image/svg+xml;utf8,<svg width=\"32\" height=\"32\" viewBox=\"0 0 32 32\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><g transform=\"translate(-4,-4)\"><path d=\"M6 8C6 6.89543 6.89543 6 8 6H20C27.732 6 34 12.268 34 20V20C34 27.732 27.732 34 20 34V34C12.268 34 6 27.732 6 20V8Z\" fill=\"%232E90FA\"/><path d=\"M8 7H20C27.1797 7 33 12.8203 33 20C33 27.1797 27.1797 33 20 33C12.8203 33 7 27.1797 7 20V8C7 7.44772 7.44772 7 8 7Z\" stroke=\"white\" stroke-width=\"2\"/></g></svg>`;\n\n// Where the pointer actually points: the teardrop's sharp top-left tip.\nexport const CURSOR_HOTSPOT = \"2 2\";\n", "// Drag-selection and screenshot-capture orchestration.\n//\n// Extracted from CommentOverlay as part of splitting the god object\n// (DECISIONS.md, Fase 5). This module owns the drag rectangle, the one\n// render each gesture pays for, and the pending automatic capture the\n// click path kicks off in the background. Placement \u2014 anchoring, the\n// comment box \u2014 stays with the overlay and is reached through `onPlace`;\n// the pending-attachments array stays with the comment box that previews\n// it and is fed through `onRegionCaptured`.\n\nimport { renderPage, cropRegion, cropViewport, AUTO_SCALE } from \"./capture.js\";\nimport { CLASSES } from \"./constants.js\";\n\nexport class CaptureFlow {\n /**\n * @param {{\n * host: ShadowRoot,\n * autoScreenshot: boolean,\n * embedCrossOriginFonts?: boolean,\n * fastCapture?: boolean,\n * skipIframeContent?: boolean,\n * captureTimeout?: number,\n * onRegionCaptured: (dataUrl: string) => void,\n * onRegionPending?: (pending: boolean) => void,\n * onPlace: (x: number, y: number, region?: { left: number, top: number, width: number, height: number }) => Promise<void>,\n * onError?: (error: unknown) => void,\n * }} deps `host` is where the selection rectangle mounts. `onError` is\n * how a failed render reaches the host: a capture that silently comes\n * back null leaves a feedback tool without the thing it exists to\n * collect, and the console is the only place that says so today.\n */\n constructor({\n host,\n autoScreenshot,\n embedCrossOriginFonts = false,\n fastCapture = false,\n skipIframeContent = false,\n captureTimeout,\n onRegionCaptured,\n onRegionPending,\n onPlace,\n onError,\n }) {\n this.host = host;\n this.autoScreenshot = autoScreenshot;\n this.embedCrossOriginFonts = embedCrossOriginFonts;\n this.fastCapture = fastCapture;\n this.skipIframeContent = skipIframeContent;\n this.captureTimeout = captureTimeout;\n this.onRegionCaptured = onRegionCaptured;\n this.onRegionPending = onRegionPending;\n this.onPlace = onPlace;\n this.onError = onError;\n\n /**\n * The in-flight automatic capture, resolving to a JPEG data-URL or\n * null. A promise rather than the value: the render kicks off when the\n * comment box opens and the save path awaits it, so the render never\n * gates the box.\n * @type {Promise<string | null> | null}\n */\n this.pendingCapture = null;\n\n /**\n * The in-flight region crop. Nothing reads its value \u2014 the crop reaches\n * the box through `onRegionCaptured` \u2014 but the save path has to be able\n * to wait for it.\n * @type {Promise<void> | null}\n */\n this.pendingRegion = null;\n\n /**\n * Identity of the gesture that owns the in-flight region crop.\n *\n * An object rather than a boolean, and compared by identity: what has to\n * be caught is not only \"the draft was dismissed\" but \"dismissed and a\n * different one opened while the render ran\" \u2014 a window that is now\n * seconds long, because the box no longer waits. A flag cannot tell\n * those apart, and the crop would land on the wrong draft.\n * @type {object | null}\n */\n this._regionToken = null;\n\n /** @type {{ x: number, y: number } | null} */\n this._dragStart = null;\n this._isDragging = false;\n /** @type {HTMLElement | null} */\n this._selectionRect = null;\n this._boundDragMove = (/** @type {MouseEvent} */ e) => this.onDragMove(e);\n this._boundDragEnd = (/** @type {MouseEvent} */ e) => this.onDragEnd(e);\n }\n\n /** Starts tracking a possible drag from a mousedown in comment mode. */\n beginDrag(/** @type {MouseEvent} */ e) {\n this._dragStart = { x: e.clientX, y: e.clientY };\n this._isDragging = false;\n document.addEventListener(\"mousemove\", this._boundDragMove);\n document.addEventListener(\"mouseup\", this._boundDragEnd);\n }\n\n onDragMove(/** @type {MouseEvent} */ e) {\n const dx = e.clientX - this._dragStart.x;\n const dy = e.clientY - this._dragStart.y;\n\n if (!this._isDragging && Math.hypot(dx, dy) < 5) return;\n\n this._isDragging = true;\n\n const left = Math.min(this._dragStart.x, e.clientX);\n const top = Math.min(this._dragStart.y, e.clientY);\n const width = Math.abs(dx);\n const height = Math.abs(dy);\n\n if (!this._selectionRect) {\n this._selectionRect = document.createElement(\"div\");\n this._selectionRect.className = CLASSES.SELECTION_RECT;\n this.host.appendChild(this._selectionRect);\n }\n\n this._selectionRect.style.left = `${left}px`;\n this._selectionRect.style.top = `${top}px`;\n this._selectionRect.style.width = `${width}px`;\n this._selectionRect.style.height = `${height}px`;\n }\n\n async onDragEnd(/** @type {MouseEvent} */ e) {\n document.removeEventListener(\"mousemove\", this._boundDragMove);\n document.removeEventListener(\"mouseup\", this._boundDragEnd);\n\n if (this._isDragging) {\n const left = Math.min(this._dragStart.x, e.clientX);\n const top = Math.min(this._dragStart.y, e.clientY);\n const width = Math.abs(e.clientX - this._dragStart.x);\n const height = Math.abs(e.clientY - this._dragStart.y);\n\n this._selectionRect?.remove();\n this._selectionRect = null;\n\n const region =\n width > 10 && height > 10 ? { left, top, width, height } : undefined;\n if (region) this.startRegionCapture(region);\n\n // NOT awaited any more. This used to sit behind the render, which on\n // a heavy page meant a second or more between releasing the mouse and\n // the box appearing \u2014 the gesture read as having been ignored. The\n // crop arrives through `onRegionCaptured` and drops into the slot the\n // box is already showing.\n //\n // The point is the region's CENTER, not the mouseup pixel: the gesture\n // names the rectangle, and anchoring to wherever the mouse happened to\n // be released let a transient overlay under that one pixel claim the\n // comment (see the region-anchoring design doc).\n await this.onPlace(left + width / 2, top + height / 2, region);\n } else {\n await this.onPlace(this._dragStart.x, this._dragStart.y);\n }\n\n this._isDragging = false;\n this._dragStart = null;\n }\n\n /**\n * Starts the render a drag gesture pays for, and returns immediately.\n *\n * One render still feeds both images \u2014 the PNG region the user selected\n * and the automatic JPEG context shot \u2014 because rendering the page is\n * practically the whole cost of a capture and doing it twice for one\n * comment was never defensible.\n *\n * `pendingCapture` is claimed synchronously, before this returns: the very\n * next thing the caller does is `onPlace`, which runs `armClickCapture`,\n * and that starts a SECOND render unless the slot is already taken.\n * @param {{ left: number, top: number, width: number, height: number }} region\n * Viewport coordinates of the selection.\n */\n startRegionCapture(region) {\n const token = {};\n this._regionToken = token;\n const stillMine = () => this._regionToken === token;\n\n const render = renderPage({\n scale: 1,\n embedCrossOriginFonts: this.embedCrossOriginFonts,\n fastCapture: this.fastCapture,\n skipIframeContent: this.skipIframeContent,\n captureTimeout: this.captureTimeout,\n });\n\n if (this.autoScreenshot) {\n this.pendingCapture = render\n .then(({ canvas, scale }) =>\n stillMine() ? cropViewport(canvas, { sourceScale: scale }) : null\n )\n // Reported through the region chain below, which owns the error for\n // this render \u2014 one failure should not reach the host twice.\n .catch(() => null);\n }\n\n this.onRegionPending?.(true);\n this.pendingRegion = render\n .then(({ canvas, scale }) => {\n if (!stillMine()) return;\n // The render's real scale, not the requested 1: on a page past the\n // canvas ceiling those differ, and cropping at 1 would cut the\n // wrong rectangle out of a smaller image.\n const dataUrl = cropRegion(canvas, region, { sourceScale: scale });\n if (dataUrl) this.onRegionCaptured(dataUrl);\n })\n .catch((err) => {\n console.warn(\"HellDots: screenshot capture failed:\", err);\n this.onError?.(err);\n })\n .finally(() => {\n // Only if this gesture still owns the slot: a newer draft has its\n // own placeholder, and clearing it here would blank that one.\n if (stillMine()) this.onRegionPending?.(false);\n });\n }\n\n /**\n * Kicks off the click path's background capture. Half scale because the\n * output is half scale anyway \u2014 that is ~4x off the RASTER, which is a\n * small share of the total; the clone and the style reads cost the same\n * at either scale. Deliberately NOT awaited: on heavy pages the render\n * takes hundreds of ms, and gating the comment box on it made every\n * click feel broken. The save path awaits the promise, by which time it\n * has almost always resolved.\n */\n armClickCapture() {\n if (!this.autoScreenshot || this.pendingCapture) return;\n this.pendingCapture = renderPage({\n scale: AUTO_SCALE,\n embedCrossOriginFonts: this.embedCrossOriginFonts,\n fastCapture: this.fastCapture,\n skipIframeContent: this.skipIframeContent,\n captureTimeout: this.captureTimeout,\n })\n .then(({ canvas, scale }) => cropViewport(canvas, { sourceScale: scale }))\n .catch((err) => {\n console.warn(\"HellDots: automatic screenshot failed\", err);\n this.onError?.(err);\n return null;\n });\n }\n\n /**\n * The capture the save path attaches \u2014 null when none is in flight.\n *\n * Waits on the region crop too, even though that is not what it returns.\n * Both come off the same render, and the save path reads the attachments\n * array immediately after this resolves; awaiting only the context shot\n * would let a Send land in the window before the crop was pushed into\n * that array, silently dropping the thing the user deliberately selected.\n * Folded in here rather than left as a second call for the caller to\n * remember, because forgetting it fails silently.\n * @returns {Promise<string | null>}\n */\n async consumePending() {\n await this.pendingRegion;\n return this.pendingCapture ? await this.pendingCapture : null;\n }\n\n /** Dismissing the comment box must not leak its capture into the next. */\n clearPending() {\n this.pendingCapture = null;\n this.pendingRegion = null;\n // Orphans whatever render is still running: its crop now belongs to a\n // draft that is gone, and the promise cannot be cancelled.\n this._regionToken = null;\n }\n\n /** Drops listeners and the selection rectangle, even mid-gesture. */\n destroy() {\n document.removeEventListener(\"mousemove\", this._boundDragMove);\n document.removeEventListener(\"mouseup\", this._boundDragEnd);\n this._selectionRect?.remove();\n this._selectionRect = null;\n this.pendingCapture = null;\n this.pendingRegion = null;\n this._regionToken = null;\n this._dragStart = null;\n this._isDragging = false;\n }\n}\n", "// RF2 \u2014 environment snapshot attached to every comment at creation time.\n// Kept as a pure function over an injectable `window` so the UA parsing\n// paths are testable without touching jsdom's real navigator.\n\n// Order is load-bearing: Edge's UA contains \"Chrome\", and Chrome's UA\n// contains \"Safari\". First match wins, so the more specific entries lead.\nconst BROWSERS = [\n { name: \"Edge\", re: /Edg\\/([\\d.]+)/ },\n { name: \"Chrome\", re: /Chrome\\/([\\d.]+)/ },\n { name: \"Firefox\", re: /Firefox\\/([\\d.]+)/ },\n { name: \"Safari\", re: /Version\\/([\\d.]+).*Safari/ },\n];\n\n// iOS before macOS: an iPhone UA also carries \"Mac OS X\".\nconst OPERATING_SYSTEMS = [\n { name: \"iOS\", re: /(?:iPhone|iPad).*OS ([\\d_]+) like Mac OS X/ },\n { name: \"Android\", re: /Android ([\\d.]+)/ },\n { name: \"Windows\", re: /Windows NT ([\\d.]+)/ },\n { name: \"macOS\", re: /Mac OS X ([\\d_.]+)/ },\n { name: \"Linux\", re: /Linux/ },\n];\n\nconst UNKNOWN = { name: \"unknown\", version: \"\" };\n\n// Chromium pads its brand list with a randomised \"GREASE\" entry to stop\n// consumers hardcoding brand positions. It is never the real browser.\nconst isGreaseBrand = (brand) => /not[\\W_]*a[\\W_]*brand/i.test(brand);\n\nconst matchFirst = (table, ua) => {\n for (const { name, re } of table) {\n const match = ua.match(re);\n if (match) {\n return { name, version: (match[1] || \"\").replace(/_/g, \".\") };\n }\n }\n return { ...UNKNOWN };\n};\n\n/**\n * Snapshots the browsing environment of the current page.\n * @param {any} [win] Injectable window \u2014 defaults to the real one.\n * @returns {import('./index.d.ts').CommentContext}\n */\nexport function captureContext(win = window) {\n const nav = win.navigator || {};\n const ua = nav.userAgent || \"\";\n const uaData = nav.userAgentData;\n\n let browser = matchFirst(BROWSERS, ua);\n const brand = uaData?.brands?.find((b) => !isGreaseBrand(b.brand));\n if (brand) {\n browser = { name: brand.brand, version: brand.version || \"\" };\n }\n\n const os = matchFirst(OPERATING_SYSTEMS, ua);\n if (uaData?.platform) os.name = uaData.platform;\n\n return {\n version: 1,\n url: win.location?.href || \"\",\n viewport: { width: win.innerWidth, height: win.innerHeight },\n screen: {\n width: win.screen?.width ?? 0,\n height: win.screen?.height ?? 0,\n },\n devicePixelRatio: win.devicePixelRatio ?? 1,\n userAgent: ua,\n browser,\n os,\n language: nav.language || \"\",\n };\n}\n", "import {\n CLASSES,\n IDS,\n Z_INDEX,\n CURSOR_SVG,\n CURSOR_HOTSPOT,\n MARKER_SIZE,\n} from \"./constants.js\";\n\n// Every scrollable surface in the widget sits on a #1C1C1E panel, so the\n// scrollbar has to be dark too. It was not: Chromium >= 121 ignores all\n// ::-webkit-scrollbar-* rules on an element that also declares\n// scrollbar-width or scrollbar-color, so the popover's styled thumb was\n// dropped and the platform default took over \u2014 a light thumb on a white\n// track, painted straight over the panel.\n//\n// The standard properties are the ones that win there, so they carry the\n// colour. The webkit block stays for Safari, which only shipped\n// scrollbar-color in 18.2 and still needs the pseudo-elements before that.\nconst SCROLLBAR = ` scrollbar-width:thin;scrollbar-color:rgba(255,255,255,0.22) transparent;`;\n\nconst webkitScrollbar = (...selectors) =>\n selectors\n .map(\n (selector) => ` ${selector}::-webkit-scrollbar{width:8px;height:8px;}${selector}::-webkit-scrollbar-track{background:transparent;}${selector}::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.22);border-radius:4px;}${selector}::-webkit-scrollbar-thumb:hover{background:rgba(255,255,255,0.35);}`\n )\n .join(\"\");\n\nexport const getStyles = () => ` :host{all:initial;display:block;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif;line-height:1.5;color-scheme:light;}:host *,:host *::before,:host *::after{box-sizing:border-box;font-family:inherit;}button{padding:0;font:inherit;color:inherit;}#${IDS.TOOLBAR}{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);z-index:${Z_INDEX.TOOLBAR};}.${CLASSES.TOOLBAR_ACTION_WRAPPER}{position:relative;}.${CLASSES.TOOLBAR_ACTION_TOOLTIP}{position:absolute;bottom:calc(100% + 10px);left:50%;transform:translateX(-50%) translateY(4px);display:flex;align-items:center;gap:8px;background:rgba(20,20,23,0.95);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);padding:8px 12px;border-radius:10px;border:1px solid rgba(255,255,255,0.08);box-shadow:0 4px 24px rgba(0,0,0,0.35);color:white;white-space:nowrap;opacity:0;pointer-events:none;transition:opacity 0.15s ease,transform 0.15s ease;}.${CLASSES.TOOLBAR_ACTION_WRAPPER}:hover .${\n CLASSES.TOOLBAR_ACTION_TOOLTIP\n }{opacity:1;pointer-events:auto;transform:translateX(-50%) translateY(0);}.${CLASSES.TOOLBAR_TEXT}{font-size:13px;font-weight:500;letter-spacing:-0.01em;}.${CLASSES.SHORTCUT_HINT}{font-size:11px;font-weight:500;color:rgba(255,255,255,0.5);background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.1);padding:2px 6px;border-radius:5px;line-height:1;white-space:nowrap;}.${CLASSES.TOOLBAR_ACTIONS},.${CLASSES.TOOLBAR_VISIBILITY}{display:flex;flex-direction:row;background:rgba(20,20,23,0.95);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);border-radius:12px;box-shadow:0 4px 24px rgba(0,0,0,0.35);}.${CLASSES.TOOLBAR_VISIBILITY}{position:absolute;left:calc(100% + 5px);top:0;}.${CLASSES.TOOLBAR_ACTION_BTN}{width:42px;height:42px;display:flex;align-items:center;justify-content:center;background:none;border:none;outline:none;color:rgba(255,255,255,0.65);cursor:pointer;transition:background 0.2s,color 0.2s;padding:0;}.${CLASSES.TOOLBAR_ACTION_WRAPPER}:first-child .${\n CLASSES.TOOLBAR_ACTION_BTN\n }{border-radius:12px 0 0 12px;}.${CLASSES.TOOLBAR_ACTION_WRAPPER}:last-child .${\n CLASSES.TOOLBAR_ACTION_BTN\n }{border-radius:0 12px 12px 0;}.${CLASSES.TOOLBAR_ACTION_WRAPPER}:only-child .${\n CLASSES.TOOLBAR_ACTION_BTN\n }{border-radius:12px;}.${CLASSES.TOOLBAR_ACTION_BTN}:hover{background:rgba(255,255,255,0.08);color:white;}.${CLASSES.TOOLBAR_COMMENT_BTN}.${CLASSES.ACTIVE}{color:#2E90FA;background:rgba(46,144,250,0.1);}#${IDS.COMMENT_BOX}{position:fixed;background:#1C1C1E;border-radius:12px;box-shadow:0 4px 20px rgba(0,0,0,0.4);padding:16px;z-index:${Z_INDEX.COMMENT_BOX};width:min(400px,calc(100vw - 24px));display:none;box-sizing:border-box;}#${IDS.COMMENT_BOX} .${CLASSES.COMMENT_INPUT_AREA}{display:flex;flex-direction:column;gap:0;}.${CLASSES.CLASSIFY_ROW}{display:flex;align-items:center;flex-wrap:wrap;gap:8px;padding:0 0 10px;border-bottom:1px solid rgba(255,255,255,0.08);}.${CLASSES.CLASSIFY_ROW} .${CLASSES.INBOX_ACTION_BTN}{height:26px;border:1px solid rgba(255,255,255,0.12);border-radius:8px;}.${CLASSES.CLASSIFY_ROW} .${CLASSES.INBOX_ACTION_BTN}:hover{border-color:rgba(255,255,255,0.28);}#${IDS.COMMENT_INPUT}{flex:1;min-height:20px;background:#1C1C1E;border:none;resize:none;font-family:inherit;color:white;font-size:14px;line-height:1.4;box-sizing:border-box;field-sizing:content;padding:8px 0;}#${IDS.COMMENT_INPUT}::placeholder{color:rgba(255,255,255,0.5);}#${IDS.COMMENT_INPUT}:focus{outline:none;box-shadow:none;}.${CLASSES.COMMENT_ACTIONS_BAR}{display:flex;align-items:center;justify-content:space-between;padding-top:12px;}.${CLASSES.ATTACH_IMAGE_BTN}{background:none;border:none;color:rgba(255,255,255,0.5);cursor:pointer;display:flex;align-items:center;justify-content:center;border-radius:6px;transition:background 0.2s,color 0.2s;}.${CLASSES.ATTACH_IMAGE_BTN}:hover{background:rgba(255,255,255,0.1);color:rgba(255,255,255,0.8);}.${CLASSES.CIRCLE}{position:absolute;width:${MARKER_SIZE}px;height:${MARKER_SIZE}px;background:#2E90FA;border-radius:0% 100% 100% 100%;border:2px solid #FFF;cursor:pointer;box-shadow:0 1px 5px rgba(0,0,0,0.2);transition:transform 0.2s,background 0.2s;z-index:${Z_INDEX.CIRCLE};transform:translate(-50%,-50%);}.${CLASSES.CIRCLE}:hover{transform:translate(-50%,-50%) scale(1.2) !important;background:rgb(0,123,255);}.${CLASSES.CIRCLE}.${CLASSES.HIGHLIGHT}{transform:translate(-50%,-50%) scale(1.2) !important;background:rgb(0,123,255);box-shadow:0 0 0 4px rgba(46,144,250,0.35),0 1px 5px rgba(0,0,0,0.2);}.${CLASSES.CIRCLE}.${CLASSES.CIRCLE_ACTIVE}{transform:translate(-50%,-50%) scale(1.2) !important;background:rgb(0,123,255);box-shadow:0 0 0 5px rgba(46,144,250,0.5),0 1px 5px rgba(0,0,0,0.2);}.${CLASSES.MARKERS_HIDDEN} .${CLASSES.CIRCLE}{display:none !important;}.${CLASSES.TOOLTIP}{position:fixed;background:#1C1C1E;border-radius:12px;padding:16px;box-shadow:0 4px 20px rgba(0,0,0,0.4);width:min(400px,calc(100vw - 24px));max-height:calc(100vh - 20px);overflow-y:auto;overscroll-behavior:contain;${SCROLLBAR} z-index:${Z_INDEX.TOOLTIP};color:white;font-size:14px;line-height:1.5;box-sizing:border-box;}.${CLASSES.TOOLTIP} .${CLASSES.THREAD_BODY}{padding:8px 0;}.${CLASSES.THREAD_POPOVER}{position:fixed;background:#1C1C1E;border-radius:12px;padding:16px;box-shadow:0 4px 20px rgba(0,0,0,0.4);width:min(400px,calc(100vw - 24px));max-height:calc(100vh - 20px);display:flex;flex-direction:column;z-index:${Z_INDEX.TOOLTIP};color:white;font-size:14px;line-height:1.5;box-sizing:border-box;}.${CLASSES.THREAD_POPOVER}>.${CLASSES.THREAD_HEADER},.${CLASSES.THREAD_POPOVER}>.${CLASSES.THREAD_ACTIONS_ROW},.${CLASSES.THREAD_POPOVER}>.${CLASSES.THREAD_INPUT_AREA}{flex:none;}.${CLASSES.THREAD_SCROLL}{flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain;${SCROLLBAR}}${webkitScrollbar(\n `.${CLASSES.THREAD_SCROLL}`,\n `.${CLASSES.TOOLTIP}`,\n `.${CLASSES.INBOX_LIST}`,\n `.${CLASSES.INBOX_DETAIL}`,\n `.${CLASSES.EDITOR_INPUT}`\n)} .${CLASSES.INBOX_PANEL}{position:fixed;top:16px;right:16px;bottom:16px;width:380px;display:flex;flex-direction:column;background:#1C1C1E;border:1px solid rgba(255,255,255,0.08);border-radius:14px;box-shadow:0 8px 32px rgba(0,0,0,0.5);z-index:${Z_INDEX.COMMENT_BOX};color:white;font-size:14px;line-height:1.5;box-sizing:border-box;overflow:hidden;}.${CLASSES.INBOX_PANEL}:focus,.${CLASSES.INBOX_PANEL}:focus-visible{outline:none;}@media (max-width:480px){.${CLASSES.INBOX_PANEL}{left:16px;width:auto;}}.${CLASSES.INBOX_HEADER},.${CLASSES.INBOX_DETAIL_HEADER}{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:12px 14px;border-bottom:1px solid rgba(255,255,255,0.08);flex:none;}.${CLASSES.INBOX_FILTER}-wrapper{position:relative;}.${CLASSES.INBOX_FILTER}{display:flex;align-items:center;gap:6px;background:transparent;border:none;color:white;font-size:13px;font-weight:600;cursor:pointer;padding:4px 6px;border-radius:6px;}.${CLASSES.INBOX_FILTER}:hover{background:rgba(255,255,255,0.08);}.${CLASSES.INBOX_FILTER_MENU}{position:absolute;top:calc(100% + 6px);left:0;background:#2C2C2E;border:1px solid rgba(255,255,255,0.1);border-radius:12px;padding:14px;width:300px;max-width:calc(100vw - 40px);z-index:1;box-shadow:0 8px 28px rgba(0,0,0,0.5);}.${CLASSES.INBOX_FILTER_MENU_HEADER}{display:flex;align-items:baseline;justify-content:space-between;gap:12px;font-size:13px;font-weight:600;margin-bottom:12px;}.${CLASSES.INBOX_FILTER_CLEAR}{background:transparent;border:none;color:rgba(255,255,255,0.55);font-size:12px;font-weight:500;cursor:pointer;padding:0;}.${CLASSES.INBOX_FILTER_CLEAR}:hover:not(:disabled){color:white;}.${CLASSES.INBOX_FILTER_CLEAR}:disabled{opacity:0.35;cursor:default;}.${CLASSES.INBOX_FILTER_GROUP} + .${CLASSES.INBOX_FILTER_GROUP}{margin-top:14px;}.${CLASSES.INBOX_FILTER_SECTION}{font-size:11px;font-weight:600;color:rgba(255,255,255,0.45);margin-bottom:8px;}.${CLASSES.INBOX_FILTER_CHIPS}{display:flex;flex-wrap:wrap;gap:6px;}.${CLASSES.INBOX_FILTER_CHIP}{background:transparent;border:1px solid rgba(255,255,255,0.18);border-radius:999px;color:rgba(255,255,255,0.75);font-size:12px;line-height:1;padding:7px 12px;cursor:pointer;transition:background 0.15s,border-color 0.15s,color 0.15s;}.${CLASSES.INBOX_FILTER_CHIP}:hover{border-color:rgba(255,255,255,0.4);color:white;}.${CLASSES.INBOX_FILTER_CHIP}[aria-checked=\"true\"]{background:rgba(255,255,255,0.92);border-color:rgba(255,255,255,0.92);color:#1C1C1E;font-weight:600;}.${CLASSES.INBOX_MENU_ITEM}{display:block;width:100%;text-align:left;background:transparent;border:none;color:white;font-size:13px;padding:7px 10px;border-radius:6px;cursor:pointer;}.${CLASSES.INBOX_MENU_ITEM}:hover{background:rgba(255,255,255,0.08);}.${CLASSES.INBOX_CLOSE},.${CLASSES.INBOX_NAV_BTN},.${CLASSES.INBOX_BACK}{display:flex;align-items:center;gap:4px;background:transparent;border:none;color:rgba(255,255,255,0.75);cursor:pointer;padding:4px 6px;border-radius:6px;font-size:14px;}.${CLASSES.INBOX_CLOSE}{font-size:20px;line-height:1;}.${CLASSES.INBOX_CLOSE}:hover,.${CLASSES.INBOX_NAV_BTN}:not(:disabled):hover,.${CLASSES.INBOX_BACK}:hover{background:rgba(255,255,255,0.08);color:white;}.${CLASSES.INBOX_NAV_BTN}:disabled{opacity:0.35;cursor:default;}.${CLASSES.INBOX_LIST},.${CLASSES.INBOX_DETAIL}{flex:1;overflow-y:auto;${SCROLLBAR} padding:12px;display:flex;flex-direction:column;gap:12px;}.${CLASSES.INBOX_CARD}{border:1px solid rgba(255,255,255,0.1);border-radius:10px;padding:12px;display:flex;flex-direction:column;gap:8px;}.${CLASSES.INBOX_LIST} .${CLASSES.INBOX_CARD}{cursor:pointer;}.${CLASSES.INBOX_LIST} .${CLASSES.INBOX_CARD}:hover{border-color:rgba(255,255,255,0.22);}.${CLASSES.INBOX_CARD}--resolved{border-color:rgba(48,209,88,0.4);opacity:0.75;}.${CLASSES.INBOX_LIST} .${CLASSES.INBOX_CARD}--resolved:hover{border-color:rgba(48,209,88,0.7);opacity:1;}.${CLASSES.INBOX_CARD}--resolved:has([aria-expanded=\"true\"]){opacity:1;}.${CLASSES.INBOX_CARD_HEADER}{display:flex;align-items:center;justify-content:space-between;gap:8px;}.${CLASSES.INBOX_CARD_ACTIONS}{display:flex;align-items:center;justify-content:space-between;width:100%;gap:8px;}.${CLASSES.INBOX_DETAIL_HEADER} .${CLASSES.INBOX_CARD_ACTIONS}{justify-content:flex-end;gap:2px;}.${CLASSES.ACTIONS_GROUP}{display:flex;align-items:center;flex-wrap:wrap;gap:5px;}.${CLASSES.ACTIONS_GROUP_END}{flex-wrap:nowrap;gap:2px;}.${CLASSES.INBOX_ACTION_BTN}{display:flex;align-items:center;justify-content:center;width:24px;height:24px;background:transparent;border:none;border-radius:6px;color:rgba(255,255,255,0.65);cursor:pointer;}.${CLASSES.INBOX_ACTION_BTN}:hover{background:rgba(255,255,255,0.08);color:white;}.${CLASSES.INBOX_ACTION_BTN_LABELED}{width:auto;padding:0 8px 0 6px;gap:5px;justify-content:flex-start;flex:none;}.${CLASSES.INBOX_ACTION_LABEL}{font-size:11px;line-height:1;white-space:nowrap;}.${CLASSES.INBOX_STATUS_DOT}{width:12px;height:12px;border-radius:50%;border:1.5px solid rgba(255,255,255,0.45);display:inline-block;flex:none;}.${CLASSES.INBOX_MENU_ITEM} .${CLASSES.INBOX_STATUS_DOT}{width:9px;height:9px;border:none;margin-right:8px;vertical-align:baseline;}.${CLASSES.INBOX_MENU_ITEM}[aria-checked=\"true\"]{background:rgba(255,255,255,0.08);}.${CLASSES.THREAD_ACTIONS_ROW}{display:flex;justify-content:flex-end;}.${CLASSES.THREAD_POPOVER}>.${CLASSES.THREAD_ACTIONS_ROW}{padding-top:8px;}[data-hd-tooltip]{position:relative;}[data-hd-tooltip]::after{content:attr(data-hd-tooltip);position:absolute;bottom:calc(100% + 6px);left:50%;transform:translateX(-50%);background:#000;color:white;padding:4px 8px;border-radius:6px;font-size:11px;white-space:nowrap;opacity:0;pointer-events:none;transition:opacity 0.12s ease;z-index:2;}[data-hd-tooltip]:hover::after{opacity:1;}.${CLASSES.INBOX_MENU}{position:absolute;top:calc(100% + 4px);right:0;background:#2C2C2E;border:1px solid rgba(255,255,255,0.1);border-radius:8px;padding:4px;min-width:130px;z-index:1;box-shadow:0 4px 16px rgba(0,0,0,0.4);}.${CLASSES.INBOX_MENU}.${CLASSES.INBOX_MENU_UP}{top:auto;bottom:calc(100% + 4px);}.${CLASSES.INBOX_MENU}.${CLASSES.INBOX_MENU_START}{right:auto;left:0;}.${CLASSES.INBOX_CARD_TEXT}{white-space:pre-wrap;word-break:break-word;}.${CLASSES.INBOX_CARD_TAG}{align-self:flex-start;padding:1px 8px;border-radius:999px;background:rgba(255,159,10,0.2);color:#FF9F0A;font-size:11px;font-weight:600;}.${CLASSES.INBOX_BADGES}{display:flex;flex-wrap:wrap;gap:4px;}.${CLASSES.INBOX_CARD} .${CLASSES.INBOX_BADGES}{margin-top:6px;}.${CLASSES.TOOLTIP} .${CLASSES.INBOX_BADGES}{margin-bottom:4px;}.${CLASSES.BADGE}{display:inline-flex;align-items:center;padding:1px 6px;border:1px solid rgba(255,255,255,0.18);border-radius:10px;font-size:10px;line-height:1.6;letter-spacing:0.01em;white-space:nowrap;}.${CLASSES.BADGE_STATUS},.${CLASSES.BADGE_TYPE},.${CLASSES.BADGE_PRIORITY}{font-weight:600;}.${CLASSES.BADGE_TAG}{opacity:0.75;}.${CLASSES.BADGE_DURATION}{opacity:0.75;border-style:dashed;}.${CLASSES.REACTION_BAR}{display:flex;align-items:center;flex-wrap:wrap;gap:6px;margin-top:2px;margin-bottom:12px;}.${CLASSES.REACTION_BAR}[hidden]{display:none;}.${CLASSES.REACTION_TRIGGER}{position:relative;display:inline-flex;}.${CLASSES.REACTION_PILL}{display:inline-flex;align-items:center;gap:5px;height:24px;padding:0 8px;border:1px solid rgba(255,255,255,0.18);border-radius:8px;background:transparent;color:#F2F2F7;font-family:inherit;font-size:12px;line-height:1;cursor:pointer;transition:background 0.12s ease,border-color 0.12s ease;}.${CLASSES.REACTION_PILL}:hover{background:rgba(255,255,255,0.08);}.${CLASSES.REACTION_PILL_MINE}{border-color:#2E90FA;background:rgba(46,144,250,0.16);}.${CLASSES.REACTION_PILL_MINE}:hover{background:rgba(46,144,250,0.24);}.${CLASSES.REACTION_PILL_EMOJI}{font-family:\"Apple Color Emoji\",\"Segoe UI Emoji\",\"Noto Color Emoji\",sans-serif;font-size:13px;}.${CLASSES.REACTION_PILL_COUNT}{font-variant-numeric:tabular-nums;}.${CLASSES.REACTION_ADD}{display:inline-flex;align-items:center;justify-content:center;width:28px;height:24px;padding:0;border:1px solid rgba(255,255,255,0.18);border-radius:8px;background:transparent;color:#8E8E93;cursor:pointer;}.${CLASSES.REACTION_ADD}:hover{color:#F2F2F7;border-color:rgba(255,255,255,0.35);}.${CLASSES.REACTION_PALETTE}{position:absolute;top:calc(100% + 4px);left:0;white-space:nowrap;background:#2C2C2E;border:1px solid rgba(255,255,255,0.1);border-radius:10px;padding:4px;z-index:1;box-shadow:0 4px 16px rgba(0,0,0,0.4);}.${CLASSES.ACTIONS_GROUP_END} .${CLASSES.REACTION_PALETTE},.${CLASSES.THREAD_REPLY_ACTIONS} .${CLASSES.REACTION_PALETTE}{left:auto;right:0;}.${CLASSES.REACTION_PALETTE}.${CLASSES.INBOX_MENU_UP}{top:auto;bottom:calc(100% + 4px);}.${CLASSES.REACTION_PALETTE_ITEM}{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;padding:0;border:0;border-radius:6px;background:transparent;font-family:\"Apple Color Emoji\",\"Segoe UI Emoji\",\"Noto Color Emoji\",sans-serif;font-size:15px;cursor:pointer;}.${CLASSES.REACTION_PALETTE_ITEM}:hover{background:rgba(255,255,255,0.12);}.${CLASSES.CONTEXT_BLOCK}{display:flex;flex-direction:column;padding:10px 12px;border-top:1px solid rgba(255,255,255,0.08);font-size:11px;}.${CLASSES.CONTEXT_BODY}{display:flex;flex-direction:column;gap:4px;}.${CLASSES.CONTEXT_TITLE}{font-size:11px;font-weight:600;color:rgba(255,255,255,0.45);padding-bottom:4px;}.${CLASSES.CONTEXT_TOGGLE}{display:flex;align-items:center;justify-content:space-between;width:100%;background:transparent;border:none;color:rgba(255,255,255,0.45);font-size:11px;font-weight:600;cursor:pointer;padding:2px 0;}.${CLASSES.CONTEXT_TOGGLE}:hover{color:rgba(255,255,255,0.75);}.${CLASSES.CONTEXT_TOGGLE} svg{flex:none;transition:transform 0.15s ease;}.${CLASSES.CONTEXT_TOGGLE}[aria-expanded=\"true\"] svg{transform:rotate(180deg);}.${CLASSES.CONTEXT_TOGGLE}[aria-expanded=\"true\"] + .${CLASSES.CONTEXT_BODY}{padding-top:8px;}.${CLASSES.CONTEXT_BLOCK} img{width:100%;border-radius:6px;margin-bottom:6px;cursor:zoom-in;}.${CLASSES.CONTEXT_SCREENSHOT_CAPTION}{opacity:0.75;}.${CLASSES.CONTEXT_ROW}{display:flex;justify-content:space-between;gap:12px;opacity:0.75;}.${CLASSES.CONTEXT_ROW} span:last-child{text-align:right;word-break:break-all;}.${CLASSES.INBOX_CARD_REPLY_LINK}{align-self:flex-start;background:transparent;border:none;color:rgba(255,255,255,0.55);font-size:13px;cursor:pointer;padding:0;}.${CLASSES.INBOX_CARD_REPLY_LINK}:hover{color:white;}.${CLASSES.INBOX_REPLIES}{display:flex;flex-direction:column;gap:10px;padding:0 4px;}.${CLASSES.INBOX_REPLIES}:empty{display:none;}.${CLASSES.TOOLTIP_REPLY_COUNT}{font-size:12px;color:rgba(255,255,255,0.45);padding-top:4px;}.${CLASSES.INBOX_EMPTY}{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:32px 24px;color:rgba(255,255,255,0.55);text-align:center;}.${CLASSES.INBOX_EMPTY_ICON}{width:44px;height:44px;margin-bottom:6px;border:2px dashed rgba(255,255,255,0.25);border-radius:0% 100% 100% 100%;flex:none;}.${CLASSES.INBOX_EMPTY_TITLE}{color:white;font-size:14px;font-weight:600;}.${CLASSES.INBOX_EMPTY_TEXT}{font-size:13px;line-height:1.5;max-width:30ch;}.${CLASSES.INBOX_EMPTY_KBD}{font-family:inherit;font-size:12px;font-weight:500;color:rgba(255,255,255,0.8);background:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.14);border-radius:5px;padding:1px 5px;white-space:nowrap;}.${CLASSES.INBOX_EMPTY_ACTION}{margin-top:6px;background:transparent;border:1px solid rgba(255,255,255,0.2);border-radius:8px;color:white;font-size:13px;font-weight:500;padding:8px 16px;cursor:pointer;transition:background 0.15s,border-color 0.15s;}.${CLASSES.INBOX_EMPTY_ACTION}:hover{background:rgba(255,255,255,0.08);border-color:rgba(255,255,255,0.35);}.${CLASSES.THREAD_HEADER}{display:flex;justify-content:space-between;align-items:center;padding:0;}.${CLASSES.THREAD_META}{display:flex;align-items:center;gap:6px;flex:1;min-width:0;}.${CLASSES.THREAD_AUTHOR}{font-weight:600;font-size:13px;display:flex;min-width:0;max-width:280px;}.${CLASSES.THREAD_AUTHOR_NAME}{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}.${CLASSES.THREAD_AUTHOR}[data-hd-tooltip]::after{white-space:normal;max-width:240px;width:max-content;text-align:center;}.${CLASSES.THREAD_TIME}{font-size:12px;color:rgba(255,255,255,0.5);cursor:default;position:relative;flex:none;}.${CLASSES.THREAD_TIME}::after{content:attr(data-full-date);position:absolute;bottom:calc(100% + 6px);left:50%;transform:translateX(-50%);background:#000;color:white;padding:4px 8px;border-radius:6px;font-size:11px;white-space:nowrap;opacity:0;pointer-events:none;transition:opacity 0.15s ease;z-index:1;}.${CLASSES.THREAD_TIME}:hover::after{opacity:1;}.${CLASSES.THREAD_BODY}{padding:8px 0;white-space:pre-wrap;word-break:break-word;}.${CLASSES.THREAD_REPLIES}{padding:0;}.${CLASSES.THREAD_REPLIES}:empty{display:none;}.${CLASSES.THREAD_REPLY}{padding:16px 0 0 0;border-top:1px solid rgba(255,255,255,0.1);white-space:pre-wrap;word-break:break-word;font-size:14px;color:rgba(255,255,255,0.85);}.${CLASSES.THREAD_REPLY} .${CLASSES.THREAD_META}{margin-bottom:2px;}.${CLASSES.THREAD_REPLY_ACTIONS}{margin-left:auto;flex:none;}.${CLASSES.THREAD_REPLY} .${CLASSES.SCREENSHOT_IMG}{width:144px;height:100px;object-fit:cover;border-radius:8px;margin-top:4px;cursor:pointer;display:block;}.${CLASSES.THREAD_INPUT_AREA}{display:flex;flex-direction:column;gap:0;padding:12px 0 0;border-top:1px solid rgba(255,255,255,0.1);}.${CLASSES.THREAD_INPUT}{width:100%;background:transparent;border:none;padding:0;color:white;font-size:14px;font-family:inherit;outline:none;box-sizing:border-box;}.${CLASSES.THREAD_INPUT}::placeholder{color:rgba(255,255,255,0.5);}.${CLASSES.THREAD_INPUT}:focus{outline:none;box-shadow:none;}.${CLASSES.THREAD_SUBMIT}{background:none;border:none;color:#2E90FA;cursor:pointer;display:flex;align-items:center;justify-content:center;border-radius:6px;transition:background 0.2s;}.${CLASSES.THREAD_SUBMIT}:hover{background:rgba(46,144,250,0.15);color:#1570D6;}.${CLASSES.CLOSE_TOOLTIP}{background:none;border:none;font-size:18px;cursor:pointer;color:rgba(255,255,255,0.5);line-height:1;}.${CLASSES.CLOSE_TOOLTIP}:hover{color:white;}.${CLASSES.PREVIEW_CIRCLE}{animation:helldots-pulse 1.5s ease-in-out infinite;}@keyframes helldots-pulse{0%,100%{box-shadow:0 0 0 0 rgba(46,144,250,0.4),0 1px 5px rgba(0,0,0,0.2);}50%{box-shadow:0 0 0 8px rgba(46,144,250,0),0 1px 5px rgba(0,0,0,0.2);}}.${CLASSES.COMMENT_OVERLAY}{position:fixed;top:0;left:0;right:0;bottom:0;background:transparent;pointer-events:none;z-index:${Z_INDEX.TOOLBAR - 1};}.${CLASSES.COMMENT_OVERLAY}.${CLASSES.ACTIVE}{pointer-events:auto;background:rgba(0,0,0,0.1);}.${CLASSES.SELECTION_RECT}{position:fixed;border:2px solid #2E90FA;background:rgba(46,144,250,0.1);pointer-events:none;z-index:${Z_INDEX.TOOLTIP};box-sizing:border-box;}.${CLASSES.SCREENSHOTS_CONTAINER}{display:none;overflow-x:auto;gap:8px;margin-top:4px;padding:4px;margin-inline:-4px;scrollbar-width:none;-ms-overflow-style:none;margin-bottom:8px;}.${CLASSES.SCREENSHOTS_CONTAINER}::-webkit-scrollbar{display:none;}.${CLASSES.SCREENSHOTS_CONTAINER}.${CLASSES.ACTIVE}{display:flex;}.${CLASSES.SCREENSHOT_ITEM}{position:relative;flex-shrink:0;}.${CLASSES.SCREENSHOT_PENDING}{min-width:50px;height:50px;padding:0 8px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;text-align:center;border:1px dashed rgba(255,255,255,0.35);border-radius:8px;font-size:10px;line-height:1.2;color:rgba(255,255,255,0.7);white-space:nowrap;}.${CLASSES.SCREENSHOT_ITEM} .${CLASSES.SCREENSHOT_IMG}{width:50px;height:50px;object-fit:cover;border-radius:8px;cursor:pointer;display:block;margin:0;}.${CLASSES.SCREENSHOT_ITEM} .${CLASSES.SCREENSHOT_IMG}:hover{opacity:0.85;}.${CLASSES.SCREENSHOT_ITEM} .${CLASSES.SCREENSHOT_REMOVE}{position:absolute;top:-5px;right:-5px;width:18px;height:18px;background:rgba(0,0,0,0.7);border:none;border-radius:50%;color:white;font-size:12px;line-height:1;cursor:pointer;display:none;align-items:center;justify-content:center;z-index:1;}.${CLASSES.SCREENSHOT_ITEM}:hover .${CLASSES.SCREENSHOT_REMOVE}{display:flex;}.${CLASSES.SCREENSHOT_ITEM} .${CLASSES.SCREENSHOT_REMOVE}:hover{background:rgba(0,0,0,0.9);}.${CLASSES.TOOLTIP}>.${CLASSES.SCREENSHOTS_CONTAINER} .${\n CLASSES.SCREENSHOT_ITEM\n } .${CLASSES.SCREENSHOT_IMG},.${CLASSES.THREAD_SCROLL}>.${CLASSES.SCREENSHOTS_CONTAINER} .${\n CLASSES.SCREENSHOT_ITEM\n } .${CLASSES.SCREENSHOT_IMG},.${CLASSES.THREAD_REPLY} .${CLASSES.SCREENSHOT_ITEM} .${\n CLASSES.SCREENSHOT_IMG\n }{width:144px;height:100px;}.${CLASSES.CONFIRM}{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.6);display:flex;align-items:center;justify-content:center;padding:16px;animation:helldots-fade-in 0.15s ease;}.${CLASSES.CONFIRM_PANEL}{width:min(360px,100%);background:#1C1C1E;border:1px solid rgba(255,255,255,0.1);border-radius:14px;box-shadow:0 8px 32px rgba(0,0,0,0.5);padding:20px;color:white;font-size:14px;line-height:1.5;box-sizing:border-box;}.${CLASSES.CONFIRM_TITLE}{margin:0 0 8px;font-size:15px;font-weight:600;}.${CLASSES.CONFIRM_MESSAGE}{margin:0 0 20px;font-size:13px;color:rgba(255,255,255,0.65);}.${CLASSES.CONFIRM_ACTIONS}{display:flex;justify-content:flex-end;gap:8px;}.${CLASSES.CONFIRM_CANCEL},.${CLASSES.CONFIRM_ACCEPT}{padding:7px 14px;border:1px solid transparent;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;}.${CLASSES.CONFIRM_CANCEL}:focus-visible,.${CLASSES.CONFIRM_ACCEPT}:focus-visible{outline:2px solid #2E90FA;outline-offset:2px;}.${CLASSES.CONFIRM_CANCEL}{background:rgba(255,255,255,0.08);border-color:rgba(255,255,255,0.12);color:white;}.${CLASSES.CONFIRM_CANCEL}:hover{background:rgba(255,255,255,0.14);}.${CLASSES.CONFIRM_ACCEPT}{background:#FF453A;color:white;}.${CLASSES.CONFIRM_ACCEPT}:hover{background:#FF6961;}.${CLASSES.LIGHTBOX}{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.92);z-index:${Z_INDEX.LIGHTBOX};display:flex;align-items:center;justify-content:center;animation:helldots-fade-in 0.2s ease;}@keyframes helldots-fade-in{from{opacity:0;}to{opacity:1;}}.${CLASSES.LIGHTBOX_IMG}{max-width:90vw;max-height:90vh;object-fit:contain;border-radius:8px;}.${CLASSES.LIGHTBOX_CLOSE}{position:absolute;top:16px;right:16px;background:rgba(255,255,255,0.15);border:none;color:white;font-size:24px;width:40px;height:40px;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background 0.2s;}.${CLASSES.LIGHTBOX_CLOSE}:hover{background:rgba(255,255,255,0.3);}.${CLASSES.EDITOR}{display:flex;flex-direction:column;gap:8px;margin:4px 0 2px;}.${CLASSES.EDITOR_INPUT}{width:100%;box-sizing:border-box;resize:vertical;min-height:60px;background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.14);border-radius:8px;padding:8px 10px;color:white;font-size:14px;font-family:inherit;line-height:1.45;outline:none;${SCROLLBAR}}.${CLASSES.EDITOR_INPUT}:focus{border-color:rgba(46,144,250,0.7);}.${CLASSES.EDITOR_ACTIONS}{display:flex;justify-content:flex-end;gap:8px;}.${CLASSES.EDITOR_CANCEL},.${CLASSES.EDITOR_SAVE}{padding:5px 12px;border:1px solid transparent;border-radius:7px;font-size:13px;font-weight:600;font-family:inherit;cursor:pointer;}.${CLASSES.EDITOR_CANCEL}{background:rgba(255,255,255,0.08);border-color:rgba(255,255,255,0.12);color:white;}.${CLASSES.EDITOR_CANCEL}:hover{background:rgba(255,255,255,0.14);}.${CLASSES.EDITOR_SAVE}{background:#2E90FA;color:white;}.${CLASSES.EDITOR_SAVE}:hover:not(:disabled){background:#57A6FB;}.${CLASSES.EDITOR_SAVE}:disabled{background:rgba(255,255,255,0.10);color:rgba(255,255,255,0.4);cursor:not-allowed;}.${CLASSES.THREAD_EDITED}{font-size:12px;color:rgba(255,255,255,0.4);cursor:default;position:relative;flex:none;}.${CLASSES.THREAD_EDITED}::before{content:\"\u00B7\";margin-right:4px;}.${CLASSES.THREAD_EDITED}::after{content:attr(data-full-date);position:absolute;bottom:calc(100% + 6px);left:50%;transform:translateX(-50%);background:#000;color:white;padding:4px 8px;border-radius:6px;font-size:11px;white-space:nowrap;opacity:0;pointer-events:none;transition:opacity 0.15s ease;}.${CLASSES.THREAD_EDITED}:hover::after{opacity:1;}.${CLASSES.AUDIT_BLOCK}{margin-top:10px;border-top:1px solid rgba(255,255,255,0.08);padding-top:8px;}.${CLASSES.AUDIT_TOGGLE}{display:flex;align-items:center;gap:6px;width:100%;padding:4px 0;border:0;background:none;color:rgba(255,255,255,0.55);font-family:inherit;font-size:12px;text-align:left;cursor:pointer;}.${CLASSES.AUDIT_TOGGLE}:hover{color:rgba(255,255,255,0.85);}.${CLASSES.AUDIT_TOGGLE}::before{content:\"\";width:0;height:0;border-left:4px solid currentColor;border-top:4px solid transparent;border-bottom:4px solid transparent;transition:transform 0.15s ease;}.${CLASSES.AUDIT_TOGGLE}[aria-expanded=\"true\"]::before{transform:rotate(90deg);}.${CLASSES.AUDIT_BODY}{padding:4px 0 2px;}.${CLASSES.AUDIT_LIST}{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:7px;}.${CLASSES.AUDIT_ROW}{display:grid;grid-template-columns:1fr auto;align-items:baseline;column-gap:8px;font-size:12px;line-height:1.4;}.${CLASSES.AUDIT_ACTION}{grid-column:1;color:rgba(255,255,255,0.78);}.${CLASSES.AUDIT_ACTOR}{grid-column:1;color:rgba(255,255,255,0.48);font-size:11px;}.${CLASSES.AUDIT_TIME}{grid-row:1;grid-column:2;color:rgba(255,255,255,0.42);font-size:11px;white-space:nowrap;}.${CLASSES.AUDIT_HEADING}{margin:0 0 6px;color:rgba(255,255,255,0.5);font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:0.05em;}.${CLASSES.AUDIT_RESOLUTIONS}{margin-top:10px;padding-top:8px;border-top:1px solid rgba(255,255,255,0.06);}.${CLASSES.INBOX_HEADER_ACTIONS}{display:flex;align-items:center;gap:12px;}.${CLASSES.INBOX_METRICS_BTN}{padding:5px 10px;border-radius:7px;border:1px solid rgba(255,255,255,0.12);background:rgba(255,255,255,0.06);color:rgba(255,255,255,0.75);font-family:inherit;font-size:12px;white-space:nowrap;cursor:pointer;}.${CLASSES.INBOX_METRICS_BTN}:hover{background:rgba(255,255,255,0.11);color:#fff;}.${CLASSES.METRICS_VIEW}{flex:1;overflow-y:auto;padding:14px 16px;display:flex;flex-direction:column;gap:16px;scrollbar-width:thin;scrollbar-color:rgba(255,255,255,0.22) transparent;}.${CLASSES.METRICS_VIEW}::-webkit-scrollbar{width:8px;}.${CLASSES.METRICS_VIEW}::-webkit-scrollbar-track{background:transparent;}.${CLASSES.METRICS_VIEW}::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.22);border-radius:4px;}.${CLASSES.METRICS_TILES}{display:grid;grid-template-columns:repeat(auto-fit,minmax(96px,1fr));gap:8px;}.${CLASSES.METRICS_TILE}{display:flex;flex-direction:column;gap:2px;padding:10px;border-radius:9px;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.08);}.${CLASSES.METRICS_TILE_VALUE}{color:#fff;font-size:18px;font-weight:600;line-height:1.1;}.${CLASSES.METRICS_TILE_LABEL}{color:rgba(255,255,255,0.5);font-size:11px;line-height:1.3;}.${CLASSES.METRICS_GROUP}{display:flex;flex-direction:column;gap:7px;}.${CLASSES.METRICS_HEADING}{margin:0;color:rgba(255,255,255,0.5);font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:0.05em;}.${CLASSES.METRICS_ROW}{display:grid;grid-template-columns:76px 1fr 26px;align-items:center;gap:8px;font-size:12px;}.${CLASSES.METRICS_ROW_LABEL}{color:rgba(255,255,255,0.72);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}.${CLASSES.METRICS_TRACK}{height:8px;border-radius:4px;background:rgba(255,255,255,0.07);overflow:hidden;}.${CLASSES.METRICS_BAR}{height:100%;border-radius:4px;background:rgba(255,255,255,0.42);min-width:2px;}.${CLASSES.METRICS_ROW_COUNT}{color:rgba(255,255,255,0.85);font-variant-numeric:tabular-nums;text-align:right;}.${CLASSES.METRICS_CHART}{width:100%;height:96px;display:block;fill:rgba(255,255,255,0.42);}.${CLASSES.METRICS_AXIS}{display:flex;justify-content:space-between;color:rgba(255,255,255,0.42);font-size:10px;font-variant-numeric:tabular-nums;}.${CLASSES.METRICS_EXPORTS}{display:flex;flex-wrap:wrap;gap:6px;padding-top:4px;border-top:1px solid rgba(255,255,255,0.08);}.${CLASSES.METRICS_EXPORT_BTN}{padding:6px 10px;border-radius:7px;border:1px solid rgba(255,255,255,0.12);background:rgba(255,255,255,0.06);color:rgba(255,255,255,0.78);font-family:inherit;font-size:12px;cursor:pointer;}.${CLASSES.METRICS_EXPORT_BTN}:hover{background:rgba(255,255,255,0.12);color:#fff;}.${CLASSES.METRICS_EMPTY}{margin:0;padding:24px 0;color:rgba(255,255,255,0.5);font-size:13px;text-align:center;}.${CLASSES.INBOX_NOTICE}{margin:0 0 10px;padding:9px 11px;border-radius:8px;background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.12);color:rgba(255,255,255,0.75);font-size:13px;line-height:1.4;}/* WCAG 2.1 AA,2.4.7 Focus Visible. Bound to :focus-visible,never :focus \u2014 that is what lets the rings exist again after being reverted for visual parity,since :focus fired on a mouse click too. Full reasoning in DECISIONS.md. Three things are load-bearing:- This block stays LAST. :focus-visible is a subset of :focus,so both match at once and the suppressors above are equally specific. - [tabindex=\"0\"],not [tabindex]:the inbox panel is -1 and its ring is suppressed on purpose. Selecting by element type rather than by class is what keeps the next control covered without an edit. - Text fields take the underline,not the ring:browsers match :focus-visible on them even on a click. .${CLASSES.EDITOR_INPUT} is left out,already carrying a blue border on focus. */ button:focus-visible,[tabindex=\"0\"]:focus-visible{outline:2px solid #2E90FA;outline-offset:2px;}#${IDS.COMMENT_INPUT}:focus-visible,.${CLASSES.THREAD_INPUT}:focus-visible{box-shadow:inset 0 -2px 0 #2E90FA;}`;\n\n/**\n * Styles that must apply to the host page itself (outside the shadow root),\n * because they target elements HellDots doesn't own \u2014 e.g. `document.body`\n * while in comment mode. A shadow root's stylesheet never reaches outside\n * it, so these can't live in `getStyles()`; they're injected separately\n * into `document.head` instead (see `CommentOverlay.injectStyles`).\n */\nexport const getGlobalStyles = () => ` .${CLASSES.COMMENT_CURSOR},.${CLASSES.COMMENT_CURSOR} *{cursor:url('${CURSOR_SVG}') ${CURSOR_HOTSPOT},auto !important;}`;\n\n/**\n * Styles for the printable metrics report. A separate sheet from getStyles()\n * because it dresses a different document \u2014 the report's own frame \u2014 and\n * because paper is white: printing the widget's dark surface would put a\n * black slab through the printer and render the text unreadable in\n * greyscale. Delivered through mountStyles like everything else, so a strict\n * `style-src` cannot blank it.\n */\nexport const getReportStyles = () => ` .report{margin:0;padding:24px;background:#fff;color:#111;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,sans-serif;font-size:12px;line-height:1.45;}.report-title{margin:0 0 4px;font-size:20px;}.report-meta{margin:0 0 2px;color:#555;font-size:11px;}.report-table{width:100%;margin-top:18px;border-collapse:collapse;break-inside:avoid;}.report-table caption{margin-bottom:4px;font-size:12px;font-weight:600;text-align:left;}.report-table th,.report-table td{padding:5px 8px;border:1px solid #d5d5d5;text-align:left;font-weight:400;}.report-table thead th{background:#f2f2f2;font-weight:600;}.report-table td{text-align:right;font-variant-numeric:tabular-nums;}@page{margin:14mm;}`;\n", "// How the widget's CSS reaches the page.\n//\n// Injecting a <style> element is what a strict `style-src` Content Security\n// Policy blocks \u2014 and a blocked stylesheet is not a cosmetic problem here:\n// markers are positioned by CSS, so the widget becomes unusable rather than\n// merely ugly. Constructed stylesheets (`new CSSStyleSheet` + `replaceSync`\n// + `adoptedStyleSheets`) are not subject to style-src, because nothing is\n// parsed from document markup.\n//\n// The <style> path stays as the fallback for platforms without constructed\n// sheets (and for jsdom, where the whole test suite runs).\n\n/**\n * Adopts or injects `css` into `target`.\n *\n * @param {ShadowRoot | Document} target where the styles apply \u2014 a shadow\n * root for the widget's own UI, the document for the few rules that\n * target the host page (the comment-mode cursor on <body>).\n * @param {string} css\n * @param {string} fallbackId id given to the injected <style>, so the\n * fallback path stays inspectable and idempotent.\n * @returns {() => void} detaches exactly what this call mounted\n */\nexport function mountStyles(target, css, fallbackId) {\n const sheet = constructSheet(css, target);\n if (sheet) {\n // Appended, never assigned over: a host app (Lit, or anything else\n // using constructed sheets) adopts onto the document too, and\n // replacing the array would delete its styles.\n target.adoptedStyleSheets = [...(target.adoptedStyleSheets ?? []), sheet];\n return () => {\n target.adoptedStyleSheets = (target.adoptedStyleSheets ?? []).filter(\n (candidate) => candidate !== sheet\n );\n };\n }\n\n // A Document mounts into <head>; a shadow root takes the element itself.\n const parent = /** @type {any} */ (target).head ?? target;\n /** @type {any} */ (parent).querySelector?.(`#${fallbackId}`)?.remove();\n\n const style = document.createElement(\"style\");\n style.id = fallbackId;\n style.textContent = css;\n parent.appendChild(style);\n return () => style.remove();\n}\n\n/**\n * A constructed stylesheet, or null where the platform cannot provide one.\n *\n * Both halves are checked: Safari shipped `CSSStyleSheet` for years without\n * making it constructible, and jsdom constructs sheets happily while not\n * implementing `adoptedStyleSheets` at all \u2014 so a sheet nobody can adopt\n * would silently style nothing.\n */\nfunction constructSheet(css, target) {\n // The constructor has to come from the TARGET's realm, not this module's: a\n // sheet built in one document and adopted into another throws. That only\n // started mattering once the metrics report began mounting into an iframe,\n // which is a different realm from the page that builds it.\n const view =\n /** @type {any} */ (target).defaultView ??\n target.ownerDocument?.defaultView ??\n globalThis;\n const Sheet = view.CSSStyleSheet;\n if (typeof Sheet !== \"function\") return null;\n if (!(\"adoptedStyleSheets\" in target)) return null;\n try {\n const sheet = new Sheet();\n sheet.replaceSync(css);\n return sheet;\n } catch {\n return null;\n }\n}\n", "export default {\n metricsTitle: \"Metrics\",\n metricsOpen: \"Metrics\",\n metricsTotal: \"Total comments\",\n metricsAverageResolution: \"Average resolution\",\n metricsMedianResolution: \"Median resolution\",\n metricsReopened: \"Reopened\",\n metricsByStatus: \"By status\",\n metricsByType: \"By type\",\n metricsByPriority: \"By priority\",\n metricsOverTime: \"Comments per day\",\n metricsEmpty: \"No comments to measure yet\",\n metricsCategory: \"Category\",\n metricsCount: \"Count\",\n metricsDate: \"Date\",\n metricsExportComments: \"Comments (CSV)\",\n metricsExportMetrics: \"Metrics (CSV)\",\n metricsPrint: \"Print / Save as PDF\",\n metricsExportLabel: \"Export\",\n metricsGeneratedTemplate: \"Generated {n}\",\n metricsScope: \"Scope\",\n\n auditToggleTemplate: \"History ({n})\",\n auditTrailLabel: \"Activity history\",\n auditCreated: \"Created the comment\",\n auditEdited: \"Edited the text\",\n auditTagsChanged: \"Updated the tags\",\n auditPreviousResolutions: \"Previous resolutions\",\n auditResolvedInTemplate: \"Resolved in {n}\",\n\n commentAriaLabelPrefix: \"Comment: \",\n anonymous: \"Anonymous\",\n justNow: \"Just now\",\n minutesAgoTemplate: \"{n}m\",\n hoursAgoTemplate: \"{n}h\",\n daysAgoTemplate: \"{n}d\",\n toolbarComment: \"Comment\",\n toolbarInbox: \"Inbox\",\n toolbarHideComments: \"Hide comments\",\n toolbarShowComments: \"Show comments\",\n modifierAlt: \"Alt\",\n modifierCtrl: \"Ctrl\",\n modifierShift: \"Shift\",\n commentBoxAriaLabel: \"New comment\",\n commentPlaceholder: \"Type your comment...\",\n attachImage: \"Attach image\",\n send: \"Send\",\n tooltipAriaLabel: \"Comment preview\",\n close: \"Close\",\n popoverAriaLabel: \"Comment thread\",\n replyPlaceholder: \"Reply...\",\n attachedScreenshot: \"Attached screenshot\",\n screenshotPreview: \"Screenshot preview\",\n removeScreenshot: \"Remove screenshot\",\n capturingScreenshot: \"Capturing\u2026\",\n inboxAriaLabel: \"Comments inbox\",\n inboxEmptyTitle: \"No comments yet\",\n inboxEmptyHintTemplate: \"Press {n} and click anywhere on the page to start.\",\n inboxEmptyAction: \"Turn on comment mode\",\n inboxNoMatches: \"No comments match these filters\",\n orphanedBadge: \"Unanchored\",\n hiddenBadge: \"Hidden\",\n filterAll: \"All pages\",\n filterCurrentPage: \"Current page\",\n filterTitle: \"Filter\",\n filterClear: \"Clear\",\n filterByPage: \"Page\",\n filterByStatus: \"Status\",\n back: \"Back\",\n deleteComment: \"Delete\",\n deleteReply: \"Delete reply\",\n replyOptions: \"Reply options\",\n editComment: \"Edit\",\n editReply: \"Edit reply\",\n copyLink: \"Copy link\",\n linkCopied: \"Link copied\",\n editorAriaLabel: \"Edit text\",\n editSave: \"Save\",\n editCancel: \"Cancel\",\n editedMark: \"edited\",\n editedAtPrefix: \"Edited \",\n confirmDiscardTitle: \"Discard changes?\",\n confirmDiscardMessage:\n \"The changes you made to this text have not been saved and will be lost.\",\n confirmDiscard: \"Discard\",\n confirmKeepEditing: \"Keep editing\",\n commentNotFound: \"That comment is not on this page.\",\n confirmDelete: \"Delete\",\n confirmCancel: \"Cancel\",\n confirmDeleteCommentTitle: \"Delete this comment?\",\n confirmDeleteCommentMessage:\n \"This comment will be permanently deleted. This action cannot be undone.\",\n confirmDeleteThreadMessage:\n \"This comment and all of its replies will be permanently deleted. This action cannot be undone.\",\n confirmDeleteReplyTitle: \"Delete this reply?\",\n confirmDeleteReplyMessage:\n \"This reply will be permanently deleted. This action cannot be undone.\",\n copyAgentContext: \"Copy agent context\",\n copied: \"Copied\",\n statusLabel: \"Status\",\n prevComment: \"Previous comment\",\n nextComment: \"Next comment\",\n replyLink: \"Reply\",\n replyCountOne: \"1 reply\",\n replyCountTemplate: \"{n} replies\",\n commentOptions: \"Comment options\",\n moreOptions: \"More\",\n statusOpen: \"Open\",\n statusInProgress: \"In progress\",\n statusInReview: \"In review\",\n statusResolved: \"Resolved\",\n durationLessThanMinute: \"<1m\",\n resolvedInTemplate: \"Resolved in {n}\",\n typeLabel: \"Type\",\n priorityLabel: \"Priority\",\n unset: \"Unset\",\n typeBug: \"Bug\",\n typeSuggestion: \"Suggestion\",\n typeQuestion: \"Question\",\n typeImprovement: \"Improvement\",\n priorityHigh: \"High\",\n priorityMedium: \"Medium\",\n priorityLow: \"Low\",\n filterByType: \"Type\",\n filterByPriority: \"Priority\",\n contextSection: \"Context\",\n autoScreenshotLabel: \"Automatic context\",\n contextUrl: \"URL\",\n contextViewport: \"Viewport\",\n contextScreen: \"Screen\",\n contextBrowser: \"Browser\",\n contextOs: \"OS\",\n reactionsLabel: \"Reactions\",\n addReaction: \"Add reaction\",\n reactionToggleOn: \"Add your reaction\",\n reactionToggleOff: \"Remove your reaction\",\n reactionPickerLabel: \"Choose a reaction\",\n};\n", "export default {\n metricsTitle: \"M\u00E9tricas\",\n metricsOpen: \"M\u00E9tricas\",\n metricsTotal: \"Comentarios totales\",\n metricsAverageResolution: \"Resoluci\u00F3n media\",\n metricsMedianResolution: \"Resoluci\u00F3n mediana\",\n metricsReopened: \"Reabiertos\",\n metricsByStatus: \"Por estado\",\n metricsByType: \"Por tipo\",\n metricsByPriority: \"Por prioridad\",\n metricsOverTime: \"Comentarios por d\u00EDa\",\n metricsEmpty: \"A\u00FAn no hay comentarios que medir\",\n metricsCategory: \"Categor\u00EDa\",\n metricsCount: \"Cantidad\",\n metricsDate: \"Fecha\",\n metricsExportComments: \"Comentarios (CSV)\",\n metricsExportMetrics: \"M\u00E9tricas (CSV)\",\n metricsPrint: \"Imprimir / Guardar como PDF\",\n metricsExportLabel: \"Exportar\",\n metricsGeneratedTemplate: \"Generado {n}\",\n metricsScope: \"Alcance\",\n\n auditToggleTemplate: \"Historial ({n})\",\n auditTrailLabel: \"Historial de actividad\",\n auditCreated: \"Cre\u00F3 el comentario\",\n auditEdited: \"Edit\u00F3 el texto\",\n auditTagsChanged: \"Actualiz\u00F3 las etiquetas\",\n auditPreviousResolutions: \"Resoluciones anteriores\",\n auditResolvedInTemplate: \"Resuelto en {n}\",\n\n commentAriaLabelPrefix: \"Comentario: \",\n anonymous: \"An\u00F3nimo\",\n justNow: \"Justo ahora\",\n minutesAgoTemplate: \"{n}m\",\n hoursAgoTemplate: \"{n}h\",\n daysAgoTemplate: \"{n}d\",\n toolbarComment: \"Comentar\",\n toolbarInbox: \"Bandeja\",\n toolbarHideComments: \"Ocultar comentarios\",\n toolbarShowComments: \"Mostrar comentarios\",\n modifierAlt: \"Alt\",\n modifierCtrl: \"Ctrl\",\n modifierShift: \"May\u00FAs\",\n commentBoxAriaLabel: \"Nuevo comentario\",\n commentPlaceholder: \"Escribe tu comentario...\",\n attachImage: \"Adjuntar imagen\",\n send: \"Enviar\",\n tooltipAriaLabel: \"Vista previa del comentario\",\n close: \"Cerrar\",\n popoverAriaLabel: \"Hilo de comentarios\",\n replyPlaceholder: \"Responder...\",\n attachedScreenshot: \"Captura de pantalla adjunta\",\n screenshotPreview: \"Vista previa de la captura\",\n removeScreenshot: \"Quitar captura de pantalla\",\n capturingScreenshot: \"Capturando\u2026\",\n inboxAriaLabel: \"Bandeja de comentarios\",\n inboxEmptyTitle: \"Todav\u00EDa no hay comentarios\",\n inboxEmptyHintTemplate:\n \"Pulsa {n} y haz clic en cualquier parte de la p\u00E1gina para empezar.\",\n inboxEmptyAction: \"Activar modo\",\n inboxNoMatches: \"Ning\u00FAn comentario coincide con estos filtros\",\n orphanedBadge: \"Desanclado\",\n hiddenBadge: \"Oculto\",\n filterAll: \"Todas las p\u00E1ginas\",\n filterCurrentPage: \"P\u00E1gina actual\",\n filterTitle: \"Filtrar\",\n filterClear: \"Limpiar\",\n filterByPage: \"P\u00E1gina\",\n filterByStatus: \"Estado\",\n back: \"Volver\",\n deleteComment: \"Eliminar\",\n deleteReply: \"Eliminar respuesta\",\n replyOptions: \"Opciones de la respuesta\",\n editComment: \"Editar\",\n editReply: \"Editar respuesta\",\n copyLink: \"Copiar enlace\",\n linkCopied: \"Enlace copiado\",\n editorAriaLabel: \"Editar texto\",\n editSave: \"Guardar\",\n editCancel: \"Cancelar\",\n editedMark: \"editado\",\n editedAtPrefix: \"Editado el \",\n confirmDiscardTitle: \"\u00BFDescartar los cambios?\",\n confirmDiscardMessage:\n \"Los cambios que hiciste en este texto no se han guardado y se perder\u00E1n.\",\n confirmDiscard: \"Descartar\",\n confirmKeepEditing: \"Seguir editando\",\n commentNotFound: \"Ese comentario no est\u00E1 en esta p\u00E1gina.\",\n confirmDelete: \"Eliminar\",\n confirmCancel: \"Cancelar\",\n confirmDeleteCommentTitle: \"\u00BFEliminar comentario?\",\n confirmDeleteCommentMessage:\n \"Este comentario se eliminar\u00E1 de forma permanente. Esta acci\u00F3n no se puede deshacer.\",\n confirmDeleteThreadMessage:\n \"Este comentario y todas sus respuestas se eliminar\u00E1n de forma permanente. Esta acci\u00F3n no se puede deshacer.\",\n confirmDeleteReplyTitle: \"\u00BFEliminar respuesta?\",\n confirmDeleteReplyMessage:\n \"Esta respuesta se eliminar\u00E1 de forma permanente. Esta acci\u00F3n no se puede deshacer.\",\n copyAgentContext: \"Copiar contexto de agente\",\n copied: \"Copiado\",\n statusLabel: \"Estado\",\n prevComment: \"Comentario anterior\",\n nextComment: \"Comentario siguiente\",\n replyLink: \"Responder\",\n replyCountOne: \"1 respuesta\",\n replyCountTemplate: \"{n} respuestas\",\n commentOptions: \"Opciones del comentario\",\n moreOptions: \"M\u00E1s\",\n statusOpen: \"Abierto\",\n statusInProgress: \"En progreso\",\n statusInReview: \"En revisi\u00F3n\",\n statusResolved: \"Resuelto\",\n durationLessThanMinute: \"<1m\",\n resolvedInTemplate: \"Resuelto en {n}\",\n typeLabel: \"Tipo\",\n priorityLabel: \"Prioridad\",\n unset: \"Sin definir\",\n typeBug: \"Bug\",\n typeSuggestion: \"Sugerencia\",\n typeQuestion: \"Pregunta\",\n typeImprovement: \"Mejora\",\n priorityHigh: \"Alta\",\n priorityMedium: \"Media\",\n priorityLow: \"Baja\",\n filterByType: \"Tipo\",\n filterByPriority: \"Prioridad\",\n contextSection: \"Contexto\",\n autoScreenshotLabel: \"Contexto autom\u00E1tico\",\n contextUrl: \"URL\",\n contextViewport: \"Viewport\",\n contextScreen: \"Pantalla\",\n contextBrowser: \"Navegador\",\n contextOs: \"SO\",\n reactionsLabel: \"Reacciones\",\n addReaction: \"A\u00F1adir reacci\u00F3n\",\n reactionToggleOn: \"A\u00F1adir tu reacci\u00F3n\",\n reactionToggleOff: \"Quitar tu reacci\u00F3n\",\n reactionPickerLabel: \"Elegir una reacci\u00F3n\",\n};\n", "import en from \"./locales/en.js\";\nimport es from \"./locales/es.js\";\n\nconst LOCALES = { en, es };\nconst DEFAULT_LOCALE = \"en\";\n\n/**\n * Picks a supported locale code from the browser's language, falling back\n * to English for anything HellDots doesn't ship a translation for.\n * @returns {\"en\" | \"es\"}\n */\nexport function detectLocale() {\n const lang = (navigator.language || DEFAULT_LOCALE).slice(0, 2).toLowerCase();\n return lang in LOCALES ? /** @type {\"en\" | \"es\"} */ (lang) : DEFAULT_LOCALE;\n}\n\n/**\n * Resolves the UI strings dictionary for a given locale code. An unknown\n * code falls back to English wholesale; a known locale falls back to\n * English PER KEY, so a translation added to en.js but not yet to a sibling\n * locale degrades to English instead of rendering literal \"undefined\".\n * @param {string} [localeCode]\n * @returns {typeof en}\n */\nexport function getStrings(localeCode) {\n const selected = LOCALES[localeCode];\n if (!selected || localeCode === DEFAULT_LOCALE) {\n return LOCALES[DEFAULT_LOCALE];\n }\n return { ...LOCALES[DEFAULT_LOCALE], ...selected };\n}\n\n/**\n * Substitutes `{n}` in a template string, used for relative time labels and\n * resolution-duration badges (where the substitution is already a string,\n * e.g. \"2d 4h\" or the \"\u2014\" fallback).\n * @param {string} template\n * @param {number | string} n\n */\nexport function formatTemplate(template, n) {\n return template.replace(\"{n}\", String(n));\n}\n\nconst MINUTE_MS = 60_000;\n\n/**\n * RF5 \u2014 human-readable elapsed time (\"<1m\", \"45m\", \"3h 12m\", \"2d 4h\").\n * Reuses the same {n}-templates the relative timestamps already use.\n * @param {number} ms\n * @param {ReturnType<typeof getStrings>} strings\n * @returns {string} empty string when `ms` isn't a usable duration\n */\nexport function formatDuration(ms, strings) {\n if (!Number.isFinite(ms) || ms < 0) return \"\";\n\n const totalMinutes = Math.floor(ms / MINUTE_MS);\n if (totalMinutes < 1) return strings.durationLessThanMinute;\n if (totalMinutes < 60) {\n return formatTemplate(strings.minutesAgoTemplate, totalMinutes);\n }\n\n const totalHours = Math.floor(totalMinutes / 60);\n if (totalHours < 24) {\n const minutes = totalMinutes % 60;\n const hours = formatTemplate(strings.hoursAgoTemplate, totalHours);\n return minutes\n ? `${hours} ${formatTemplate(strings.minutesAgoTemplate, minutes)}`\n : hours;\n }\n\n const totalDays = Math.floor(totalHours / 24);\n const hours = totalHours % 24;\n const days = formatTemplate(strings.daysAgoTemplate, totalDays);\n return hours\n ? `${days} ${formatTemplate(strings.hoursAgoTemplate, hours)}`\n : days;\n}\n", "// Serializable comment anchors. An anchor pairs a best-effort CSS selector\n// (fast path) with a content fingerprint (verification + rescue path) so a\n// comment can be re-attached to its element after a page reload. Design:\n// docs/superpowers/specs/2026-07-02-comment-anchoring-design.md\n\nimport { HOST_PAGE_CLASSES } from \"./constants.js\";\n\nconst TEXT_SNIPPET_MAX = 64;\nconst MAX_CLASS_PATH_DEPTH = 3;\nconst MAX_STRUCTURAL_DEPTH = 5;\n\n// A selector match is verified against the fingerprint before being trusted;\n// the rescue path (fingerprint-only, no structural signal) demands more.\nconst SELECTOR_THRESHOLD = 0.6;\nconst RESCUE_THRESHOLD = 0.7;\n\nconst GENERATED_CLASS_PREFIX_RE = /^(css|sc|jsx|emotion)-/i;\nconst FRAMEWORK_DATA_ATTR_RE = /^data-(reactid|react-|v-|svelte-)/;\nconst STABLE_ATTR_NAMES = [\"id\", \"name\", \"role\", \"aria-label\"];\nconst SELECTOR_ATTR_NAMES = [\"data-testid\", \"name\", \"aria-label\"];\n\nconst escapeCss = (value) => {\n if (typeof CSS !== \"undefined\" && CSS.escape) return CSS.escape(value);\n return String(value).replace(/[^a-zA-Z0-9_-]/g, \"\\\\$&\");\n};\n\nconst escapeAttrValue = (value) => String(value).replace(/[\"\\\\]/g, \"\\\\$&\");\n\nconst isUnique = (selector, doc) => {\n try {\n return doc.querySelectorAll(selector).length === 1;\n } catch {\n return false;\n }\n};\n\nconst normalizeText = (text) =>\n (text || \"\").replace(/\\s+/g, \" \").trim().slice(0, TEXT_SNIPPET_MAX);\n\n// Heuristic: tooling-generated class names (CSS-in-JS, scoped-CSS hashes)\n// either carry a known prefix or contain a long token with digits in it.\n// HellDots' own host-page classes are excluded outright \u2014 they are our\n// transient state, not the page's structure, and anchoring to them produces\n// a selector that stops matching as soon as the state clears.\nconst isStableClass = (cls) => {\n if (HOST_PAGE_CLASSES.includes(cls)) return false;\n if (GENERATED_CLASS_PREFIX_RE.test(cls)) return false;\n return !cls.split(/[-_]/).some((part) => part.length >= 5 && /\\d/.test(part));\n};\n\nconst stableClassesOf = (element) =>\n [...element.classList].filter(isStableClass);\n\nconst stableAttributes = (element) => {\n /** @type {Record<string, string>} */\n const attrs = {};\n for (const { name, value } of element.attributes) {\n const isStable =\n STABLE_ATTR_NAMES.includes(name) ||\n (name.startsWith(\"data-\") && !FRAMEWORK_DATA_ATTR_RE.test(name));\n if (isStable && value) attrs[name] = value.slice(0, TEXT_SNIPPET_MAX);\n }\n return attrs;\n};\n\nconst siblingPosition = (element) => {\n const parent = element.parentElement;\n if (!parent) return { index: 0, count: 1 };\n const sameTag = [...parent.children].filter(\n (child) => child.tagName === element.tagName\n );\n return { index: sameTag.indexOf(element), count: sameTag.length };\n};\n\nconst idSelector = (element, doc) => {\n if (!element.id) return null;\n const selector = `#${escapeCss(element.id)}`;\n return isUnique(selector, doc) ? selector : null;\n};\n\nconst attributeSelector = (element, doc) => {\n const tag = element.tagName.toLowerCase();\n for (const name of SELECTOR_ATTR_NAMES) {\n const value = element.getAttribute(name);\n if (!value) continue;\n const selector = `${tag}[${name}=\"${escapeAttrValue(value)}\"]`;\n if (isUnique(selector, doc)) return selector;\n }\n return null;\n};\n\nconst classPathSelector = (element, doc) => {\n const segments = [];\n let current = element;\n for (let depth = 0; depth < MAX_CLASS_PATH_DEPTH && current; depth++) {\n const classes = stableClassesOf(current);\n const tag = current.tagName.toLowerCase();\n segments.unshift(\n classes.length ? `${tag}.${classes.map(escapeCss).join(\".\")}` : tag\n );\n // The element's own segment must carry at least one stable class for\n // this strategy to say anything a structural path wouldn't.\n if (depth === 0 && !classes.length) return null;\n const selector = segments.join(\" > \");\n if (isUnique(selector, doc)) return selector;\n current = current.parentElement;\n }\n return null;\n};\n\nconst structuralSelector = (element, doc) => {\n if (element === doc.body) return \"body\";\n const segments = [];\n let current = element;\n for (let depth = 0; depth < MAX_STRUCTURAL_DEPTH && current; depth++) {\n if (current === doc.body) {\n segments.unshift(\"body\");\n break;\n }\n if (current.id) {\n const rooted = [`#${escapeCss(current.id)}`, ...segments].join(\" > \");\n if (isUnique(rooted, doc)) return rooted;\n }\n const { index } = siblingPosition(current);\n segments.unshift(\n `${current.tagName.toLowerCase()}:nth-of-type(${index + 1})`\n );\n current = current.parentElement;\n }\n const selector = segments.join(\" > \");\n return isUnique(selector, doc) ? selector : null;\n};\n\nconst generateSelector = (element, doc) =>\n idSelector(element, doc) ||\n attributeSelector(element, doc) ||\n classPathSelector(element, doc) ||\n structuralSelector(element, doc);\n\n/**\n * Best-effort unique CSS selector for any element (or null). Exposed for\n * the overlay's target-visibility tracking; same cascade used by anchors.\n * @param {HTMLElement} element\n * @returns {string | null}\n */\nexport function generateElementSelector(element) {\n return generateSelector(element, element.ownerDocument);\n}\n\n/**\n * Captures a serializable anchor for `element` at creation time.\n * @param {HTMLElement} element\n * @param {number} relativeX\n * @param {number} relativeY\n * @returns {import('./index.d.ts').CommentAnchor}\n */\nexport function createAnchor(element, relativeX, relativeY) {\n const doc = element.ownerDocument;\n const { index, count } = siblingPosition(element);\n return {\n version: 1,\n selector: generateSelector(element, doc),\n fingerprint: {\n tagName: element.tagName,\n textSnippet: normalizeText(element.textContent),\n attributes: stableAttributes(element),\n siblingIndex: index,\n siblingCount: count,\n },\n relativeX,\n relativeY,\n };\n}\n\nconst textSimilarity = (a, b) => {\n if (!a && !b) return 1;\n if (!a || !b) return 0;\n if (a === b) return 1;\n if (a.startsWith(b) || b.startsWith(a)) return 0.8;\n const tokensA = new Set(a.split(\" \"));\n const tokensB = new Set(b.split(\" \"));\n let common = 0;\n for (const token of tokensA) if (tokensB.has(token)) common++;\n return (2 * common) / (tokensA.size + tokensB.size);\n};\n\nconst attributeSimilarity = (element, attrs) => {\n const names = Object.keys(attrs);\n if (!names.length) return 1;\n let matched = 0;\n for (const name of names) {\n if (\n (element.getAttribute(name) || \"\").slice(0, TEXT_SNIPPET_MAX) ===\n attrs[name]\n ) {\n matched++;\n }\n }\n return matched / names.length;\n};\n\nconst positionSimilarity = (element, fingerprint) => {\n const { index, count } = siblingPosition(element);\n const delta = Math.abs(index - fingerprint.siblingIndex);\n const span = Math.max(fingerprint.siblingCount, count, 1);\n return Math.max(0, 1 - delta / span);\n};\n\nconst scoreElement = (element, fingerprint) => {\n if (element.tagName !== fingerprint.tagName) return 0;\n\n const hasText = Boolean(fingerprint.textSnippet);\n const hasAttrs = Object.keys(fingerprint.attributes || {}).length > 0;\n\n // Base weights (text 0.5 / attrs 0.3 / position 0.2); a missing signal's\n // weight shifts to the other content signal so the scale stays 0\u20131.\n let textWeight = 0.5;\n let attrWeight = 0.3;\n const posWeight = 0.2;\n if (!hasAttrs) {\n textWeight += attrWeight;\n attrWeight = 0;\n } else if (!hasText) {\n attrWeight += textWeight;\n textWeight = 0;\n }\n\n // Degenerate fingerprint: only structural signal remains.\n if (!hasText && !hasAttrs) {\n return positionSimilarity(element, fingerprint);\n }\n\n return (\n textWeight *\n textSimilarity(\n normalizeText(element.textContent),\n fingerprint.textSnippet\n ) +\n attrWeight * attributeSimilarity(element, fingerprint.attributes || {}) +\n posWeight * positionSimilarity(element, fingerprint)\n );\n};\n\nconst bestMatch = (candidates, fingerprint) => {\n let best = null;\n for (const element of candidates) {\n const confidence = scoreElement(element, fingerprint);\n if (\n !best ||\n confidence > best.confidence ||\n // On an exact tie prefer the deepest candidate. Document order yields\n // ancestors first, and with the text snippet truncated to 64 chars a\n // parent and its child tie systematically \u2014 the most specific element\n // that scores the same is the better anchor (the selector cascade's\n // intuition). Ties between unrelated elements keep document order.\n (confidence === best.confidence && best.element.contains(element))\n ) {\n best = { element, confidence };\n }\n }\n return best;\n};\n\n/**\n * Re-locates the element an anchor points at, or null if no candidate is\n * trustworthy (orphaned comment). Never throws.\n * @param {import('./index.d.ts').CommentAnchor} anchor\n * @param {Document} [doc]\n * @returns {{ element: HTMLElement, confidence: number } | null}\n */\nexport function resolveAnchor(anchor, doc = document) {\n // An anchor written by a newer schema than this code understands is\n // treated as orphaned rather than half-interpreted: the comment stays\n // listed but is never positioned over a guessed element.\n if (anchor?.version != null && anchor.version > 1) return null;\n\n const fingerprint = anchor?.fingerprint;\n if (!fingerprint || !fingerprint.tagName) return null;\n\n if (anchor.selector) {\n let candidates = [];\n try {\n candidates = [...doc.querySelectorAll(anchor.selector)];\n } catch {\n // Corrupt selector \u2014 the rescue search below still applies.\n }\n const best = bestMatch(candidates, fingerprint);\n if (best && best.confidence >= SELECTOR_THRESHOLD) return best;\n }\n\n // Rescue search: only meaningful when the fingerprint carries content\n // signal \u2014 anonymous elements would make any tag-wide match a guess.\n const hasSignal =\n Boolean(fingerprint.textSnippet) ||\n Object.keys(fingerprint.attributes || {}).length > 0;\n if (!hasSignal) return null;\n\n let candidates;\n try {\n candidates = [...doc.querySelectorAll(fingerprint.tagName)];\n } catch {\n return null;\n }\n const best = bestMatch(candidates, fingerprint);\n return best && best.confidence >= RESCUE_THRESHOLD ? best : null;\n}\n", "// localStorage adapter for the optional `persistence: \"localStorage\"` mode.\n// One key holds the serialized comments of EVERY page (the inbox \"all\n// comments\" filter needs them); merge logic keeps other pages' entries\n// intact while treating in-memory state as the source of truth for ids it\n// knows about. Storage failures (quota, disabled, corrupt JSON) never\n// throw \u2014 the widget just runs without persistence.\n\nexport const STORAGE_KEY = \"helldots-comments\";\n\n// sessionStorage handoff: set right before navigating to another page so\n// the overlay there opens the inbox directly on that comment's detail.\nexport const PENDING_DETAIL_KEY = \"helldots-pending-detail\";\n\n/**\n * @returns {import('./index.d.ts').SerializedComment[]}\n */\nexport function readStoredComments() {\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 (err) {\n console.warn(\"HellDots: could not read stored comments\", err);\n return [];\n }\n}\n\nfunction tryWriteStoredComments(comments) {\n try {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(comments));\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Writes the full cross-page corpus. Each comment can carry a base64 JPEG\n * `contextScreenshot` (~33KB) captured automatically (RF1/RF2); a growing\n * corpus of those eventually blows through the ~5MB localStorage quota. On\n * a failed write, the automatic screenshots are the only thing sacrificed \u2014\n * dropped one at a time starting with the oldest comment \u2014 so the comments\n * themselves (and any deliberate, user-attached `screenshots[]`) survive.\n * Never throws: a hostile or disabled localStorage just means no\n * persistence, not a broken widget.\n * @param {import('./index.d.ts').SerializedComment[]} comments\n * @returns {boolean} true once the write succeeded, possibly after shedding\n * automatic screenshots; false if it still failed with nothing left to shed\n */\nexport function writeStoredComments(comments) {\n if (tryWriteStoredComments(comments)) return true;\n\n // Oldest first (by createdAt; ties/missing dates fall back to array\n // order), and only entries that actually have something to shed.\n const shedOrder = comments\n .map((comment, index) => ({ comment, index }))\n .filter(({ comment }) => comment?.contextScreenshot)\n .sort((a, b) => {\n const timeA = Date.parse(a.comment.createdAt);\n const timeB = Date.parse(b.comment.createdAt);\n if (Number.isFinite(timeA) && Number.isFinite(timeB) && timeA !== timeB) {\n return timeA - timeB;\n }\n return a.index - b.index;\n });\n\n if (shedOrder.length === 0) {\n console.warn(\n \"HellDots: could not persist comments (storage quota exceeded, nothing left to shed)\"\n );\n return false;\n }\n\n const working = [...comments];\n let shed = 0;\n for (const { index } of shedOrder) {\n working[index] = { ...working[index], contextScreenshot: null };\n shed++;\n if (tryWriteStoredComments(working)) {\n console.warn(\n `HellDots: localStorage quota exceeded \u2014 dropped the automatic ` +\n `context screenshot from the ${shed} oldest comment(s) to keep ` +\n `all comments persisted. Comment text, replies and user-attached ` +\n `screenshots were not touched.`\n );\n return true;\n }\n }\n\n console.warn(\n `HellDots: could not persist comments even after dropping all ${shed} ` +\n `automatic context screenshot(s); storage quota exceeded`\n );\n return false;\n}\n\n/**\n * Merges the in-memory snapshot into what's already stored: entries the\n * memory knows about (by id) and entries of the current page are replaced\n * by the snapshot; entries from other pages are preserved.\n * @param {import('./index.d.ts').SerializedComment[]} stored\n * @param {import('./index.d.ts').SerializedComment[]} current\n * @param {string} currentPage\n * @returns {import('./index.d.ts').SerializedComment[]}\n */\nexport function mergeForStorage(stored, current, currentPage) {\n // Ids are compared on their string form (see id.js) \u2014 a numeric legacy id\n // and its string spelling are the same comment, never two entries.\n const currentIds = new Set(current.map((c) => String(c.id)));\n const kept = stored.filter(\n (c) => !currentIds.has(String(c.id)) && c.page !== currentPage\n );\n return [...kept, ...current];\n}\n", "export let urlAlphabet =\n 'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict'\n", "\n\nimport { urlAlphabet } from './url-alphabet/index.js'\n\nexport { urlAlphabet }\n\nexport let random = bytes => crypto.getRandomValues(new Uint8Array(bytes))\n\nexport let customRandom = (alphabet, defaultSize, getRandom) => {\n let safeByteCutoff = 256 - (256 % alphabet.length)\n\n if (safeByteCutoff === 256) {\n let mask = alphabet.length - 1\n\n return (size = defaultSize) => {\n if (!size) return ''\n let id = ''\n while (true) {\n let bytes = getRandom(size)\n let j = size\n while (j--) {\n id += alphabet[bytes[j] & mask]\n if (id.length >= size) return id\n }\n }\n }\n }\n\n let step = Math.ceil((1.6 * 256 * defaultSize) / safeByteCutoff)\n\n return (size = defaultSize) => {\n if (!size) return ''\n let id = ''\n while (true) {\n let bytes = getRandom(step)\n let j = step\n while (j--) {\n if (bytes[j] < safeByteCutoff) {\n id += alphabet[bytes[j] % alphabet.length]\n if (id.length >= size) return id\n }\n }\n }\n }\n}\n\nexport let customAlphabet = (alphabet, size = 21) =>\n customRandom(alphabet, size | 0, random)\n\nexport let nanoid = (size = 21) => {\n let id = ''\n let bytes = crypto.getRandomValues(new Uint8Array((size |= 0)))\n while (size--) {\n id += urlAlphabet[bytes[size] & 63]\n }\n return id\n}\n", "// The single source of ids for comments and replies.\n//\n// This used to be `Date.now()`, which is not an id \u2014 it is a timestamp that\n// usually happens not to repeat. Two things it was already breaking:\n// `mergeForStorage` deduplicates by id, and every lookup is a `find()` that\n// returns the first match, so a collision means one comment silently\n// overwrites another. Two people commenting on the same millisecond from\n// different machines, or any programmatic import, is enough to hit it.\n//\n// nanoid gives ~126 bits from a 64-symbol URL-safe alphabet in 21 chars \u2014\n// stronger than a UUIDv4's 122 bits, and short enough to sit in the\n// `?helldotsComment=` link without looking like a mistake. It reads its\n// randomness from `crypto.getRandomValues`, which \u2014 unlike\n// `crypto.randomUUID` \u2014 is NOT restricted to secure contexts, so a widget\n// dropped into a dev server on plain http://192.168.x.x still works.\n//\n// It is a devDependency bundled into both artifacts rather than a runtime\n// dependency: nanoid 6 requires Node 22+, and this package promises >=18.\n// Bundling keeps that promise honest for hosts importing us under SSR, and\n// costs ~516 B gzip against a 50 KB budget.\nimport { nanoid } from \"nanoid\";\n\n/**\n * @returns {string} a fresh id for a comment or a reply\n */\nexport const createId = () => nanoid();\n\n/**\n * The one way to read an author's identifier off whatever the host declared.\n *\n * It lands in four places \u2014 `comment.authorId`, `reply.authorId`, every audit\n * entry's `actor.id`, and the key a reaction is stored under \u2014 and a host\n * joining its own records against any two of them has to get the same string\n * back. Each of those sites used to normalise it its own way, so one padded\n * or over-long id arrived in three different spellings inside one payload.\n *\n * Trimmed but never truncated. A clipped display name is ugly; a clipped id\n * is wrong in silence, because two ids sharing a prefix collapse into one\n * person \u2014 and this is the only field anything can be reconciled on. What the\n * id means, how long it is and whether it is safe to store is the host's\n * call: HellDots treats it as opaque.\n *\n * @param {unknown} value\n * @returns {string} the trimmed id, or \"\" when there is not one\n */\nexport const normalizeActorId = (value) =>\n typeof value === \"string\" ? value.trim() : \"\";\n\n/**\n * The one way to compare ids. New ids are strings, but Date.now()-era\n * records still hold numbers, and either spelling may have crossed a JSON\n * or URL boundary since \u2014 so equality is defined on the string form, as\n * `index.d.ts` promises callers.\n *\n * @param {string | number} a\n * @param {string | number} b\n * @returns {boolean}\n */\nexport const sameId = (a, b) => String(a) === String(b);\n", "// Shared open/close rule for every dropdown in the widget: the status, type\n// and priority pickers, the \u22EF menu and the inbox filter.\n//\n// Each menu used to own its state in isolation *and* stop the click from\n// propagating, so nothing could ever close one except its own button. Opening\n// a second picker left the first hanging open (three menus could overlap at\n// once), and clicking elsewhere inside the panel closed none of them.\n//\n// The registry gives them one rule instead: at most one menu open, and any\n// mousedown outside the open menu closes it. The outside listener is on\n// `document` in the CAPTURE phase precisely because the toggles call\n// stopPropagation() \u2014 a bubble-phase listener would never hear the click.\n\nimport { CLASSES } from \"./constants.js\";\n\n/**\n * @typedef {{ button: HTMLElement, menu: HTMLElement, close: () => void }} MenuEntry\n */\n\n/** @type {Set<MenuEntry>} */\nconst openMenus = new Set();\n\n/** @type {((e: MouseEvent) => void) | null} */\nlet outsideListener = null;\n\n/** @type {((e: KeyboardEvent) => void) | null} */\nlet keyListener = null;\n\nconst menuItems = (menu) => [\n ...menu.querySelectorAll('[role=\"menuitem\"], [role=\"menuitemradio\"]'),\n];\n\n/** Vertical gap between a menu and the button it belongs to (matches the CSS). */\nconst MENU_GAP = 4;\n\n/**\n * The nearest ancestor that would clip the menu, or null when only the\n * viewport bounds it. Anything with a non-visible overflow clips: the thread's\n * scroll container and the inbox list are the two that matter, and both are\n * exactly where a dropdown near the bottom edge used to become unreachable.\n *\n * Walking up stops at the shadow root, whose overflow is not ours to read.\n *\n * @param {HTMLElement} menu\n * @returns {DOMRect | null}\n */\nconst clipperRectOf = (menu) => {\n for (let el = menu.parentElement; el; el = el.parentElement) {\n const { overflowX, overflowY } = getComputedStyle(el);\n if (overflowX !== \"visible\" || overflowY !== \"visible\") {\n return el.getBoundingClientRect();\n }\n }\n return null;\n};\n\n/**\n * Keeps the menu inside whatever clips it, on both axes.\n *\n * Vertically it opens upward when it would otherwise be clipped below \u2014 and\n * only when it actually fits up there. Flipping a menu taller than its\n * container would just clip the other end while also reversing the position\n * the user reaches for, so in that case it stays put.\n *\n * Horizontally the same rule, mirrored: the menus hang off their button's\n * right edge, which is what the tools at the end of the action strip want,\n * but the status picker is the strip's *first* control, so its 130px menu\n * reached ~45px past the left edge of the inbox panel \u2014 where the panel's\n * `overflow: hidden` cut it in half. It aligns to the button's left edge\n * instead, and only when the menu fits that way.\n *\n * The overflow test reads the menu's measured position rather than deriving\n * it from the button: which edge a menu hangs off is a CSS decision that\n * differs per surface (the reaction palette opens leftward only inside the\n * tools group), and a rule that assumed one of them would misjudge the other.\n *\n * Measured on every open, never cached: a menu on a row that scrolled since\n * last time has different room than it did.\n *\n * @param {HTMLElement} button\n * @param {HTMLElement} menu the menu, already displayed and placed downward\n */\nconst placeMenu = (button, menu) => {\n menu.classList.remove(CLASSES.INBOX_MENU_UP);\n menu.classList.remove(CLASSES.INBOX_MENU_START);\n\n const clipper = clipperRectOf(menu);\n const floor = Math.min(clipper?.bottom ?? Infinity, window.innerHeight);\n const ceiling = Math.max(clipper?.top ?? 0, 0);\n const leftWall = Math.max(clipper?.left ?? 0, 0);\n const rightWall = Math.min(clipper?.right ?? Infinity, window.innerWidth);\n\n const { height, width, left } = menu.getBoundingClientRect();\n const anchor = button.getBoundingClientRect();\n const bottomIfDown = anchor.bottom + MENU_GAP + height;\n const topIfUp = anchor.top - MENU_GAP - height;\n\n if (bottomIfDown > floor && topIfUp >= ceiling) {\n menu.classList.add(CLASSES.INBOX_MENU_UP);\n }\n\n if (left < leftWall && anchor.left + width <= rightWall) {\n menu.classList.add(CLASSES.INBOX_MENU_START);\n }\n};\n\n// Menus live inside the shadow root, so `e.target` on a document listener is\n// retargeted to the host. composedPath() is the only way to see the element\n// that was really clicked.\nconst eventHits = (el, e) => {\n const path = typeof e.composedPath === \"function\" ? e.composedPath() : [];\n return path.includes(el) || el.contains(/** @type {Node} */ (e.target));\n};\n\nconst startWatching = () => {\n if (outsideListener) return;\n outsideListener = (e) => {\n for (const entry of [...openMenus]) {\n if (eventHits(entry.menu, e) || eventHits(entry.button, e)) continue;\n // A menu detached by a re-render is never in the click path, so this\n // also drops stale entries \u2014 which is what eventually releases this\n // very listener.\n entry.close();\n }\n };\n document.addEventListener(\"mousedown\", outsideListener, true);\n\n // role=\"menu\"/\"menuitem\" promises keyboard behavior (ARIA menu pattern):\n // Escape closes THIS layer only \u2014 never the popover behind it, which is\n // why the listener runs in the capture phase and stops propagation \u2014 and\n // the arrow keys walk the items. Registered while a menu is open, exactly\n // like the mousedown watcher.\n keyListener = (e) => {\n const entry = [...openMenus].pop();\n if (!entry) return;\n\n if (e.key === \"Escape\") {\n e.preventDefault();\n e.stopPropagation();\n entry.close();\n entry.button.focus();\n return;\n }\n\n if ([\"ArrowDown\", \"ArrowUp\", \"Home\", \"End\"].includes(e.key)) {\n const items = menuItems(entry.menu);\n if (items.length === 0) return;\n e.preventDefault();\n e.stopPropagation();\n // Menus live in a shadow root, where document.activeElement reports\n // the host \u2014 the root's own activeElement is the real one.\n const root = /** @type {Document | ShadowRoot} */ (\n entry.menu.getRootNode()\n );\n const index = items.indexOf(root.activeElement);\n let next;\n if (e.key === \"Home\") next = 0;\n else if (e.key === \"End\") next = items.length - 1;\n else if (index === -1)\n next = e.key === \"ArrowDown\" ? 0 : items.length - 1;\n else {\n const step = e.key === \"ArrowDown\" ? 1 : -1;\n next = (index + step + items.length) % items.length;\n }\n items[next].focus();\n }\n };\n document.addEventListener(\"keydown\", keyListener, true);\n};\n\nconst stopWatching = () => {\n if (!outsideListener) return;\n document.removeEventListener(\"mousedown\", outsideListener, true);\n outsideListener = null;\n document.removeEventListener(\"keydown\", keyListener, true);\n keyListener = null;\n};\n\n/** Closes every open menu. Safe to call when none are open. */\nexport const closeOpenMenus = () => {\n for (const entry of [...openMenus]) entry.close();\n};\n\n/**\n * Wires a button to its dropdown so it participates in the single-open rule.\n * The menu's `display` stays the source of truth, so callers that hide it by\n * hand stay consistent with the registry.\n *\n * @param {HTMLElement} button\n * @param {HTMLElement} menu\n * @returns {{ open: () => void, close: () => void, isOpen: () => boolean }}\n */\nexport const attachMenuToggle = (button, menu) => {\n /** @type {MenuEntry} */\n const entry = {\n button,\n menu,\n close: () => {\n menu.style.display = \"none\";\n button.setAttribute(\"aria-expanded\", \"false\");\n openMenus.delete(entry);\n if (openMenus.size === 0) stopWatching();\n },\n };\n\n const open = () => {\n closeOpenMenus();\n menu.style.display = \"block\";\n // Displayed before measuring: a menu still at display:none has no box to\n // measure, so its height would read as zero and it would never flip.\n placeMenu(button, menu);\n button.setAttribute(\"aria-expanded\", \"true\");\n openMenus.add(entry);\n startWatching();\n };\n\n const isOpen = () => menu.style.display !== \"none\";\n\n menu.style.display = \"none\";\n button.setAttribute(\"aria-expanded\", \"false\");\n\n button.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n // Read the state off the DOM, not off the set: a menu hidden directly by\n // a caller (or detached by a re-render) would otherwise be stuck\n // \"open\" in the registry and refuse to reopen.\n const wasOpen = isOpen();\n closeOpenMenus();\n if (!wasOpen) open();\n });\n\n return { open, close: entry.close, isOpen };\n};\n", "// Emoji reactions on comments and replies. One module holds the identity\n// resolver, the reaction-map helpers and the bar component, because all three\n// have to agree on what \"mine\" means \u2014 split across files, the toggle and the\n// render drift apart and pills stop matching the actor who owns them.\n\nimport { CLASSES, REACTION_EMOJIS } from \"./constants.js\";\nimport { attachMenuToggle } from \"./menus.js\";\nimport { normalizeActorId } from \"./id.js\";\n\n/**\n * The key a reaction is stored under.\n *\n * One resolver, used by both the toggle and the \"this one is mine\" render:\n * resolved separately in two places, a host that swaps `user` at runtime\n * would paint pills nobody can switch off.\n *\n * `id` is identity only and is never rendered \u2014 the display name stays what\n * gets shown as an author.\n *\n * @param {{ name?: string, id?: string } | undefined} user\n * @param {{ anonymous: string }} strings\n * @returns {string}\n */\nexport const actorKeyOf = (user, strings) =>\n normalizeActorId(user?.id) || user?.name || strings.anonymous;\n\n/**\n * Reads a reaction map into a stable, ordered list, dropping emoji nobody\n * holds any more. The author arrays are copied: a caller sorting or pushing\n * into what it got back must not reach into stored state.\n *\n * @param {{ reactions?: Record<string, string[]> } | undefined} target\n * @returns {Array<{ emoji: string, authors: string[] }>}\n */\nexport const reactionEntriesOf = (target) => {\n const map = target?.reactions;\n if (!map || typeof map !== \"object\") return [];\n return REACTION_EMOJIS.filter((emoji) => map[emoji]?.length > 0).map(\n (emoji) => ({ emoji, authors: [...map[emoji]] })\n );\n};\n\n/**\n * Persisted reactions arrive from localStorage or from the host's backend, so\n * nothing about their shape can be trusted: an unknown glyph would render a\n * pill this build cannot toggle, and a duplicated actor key would inflate a\n * count that looks like consensus. Returns null when there is nothing worth\n * keeping, so callers leave the field absent instead of storing `{}`.\n *\n * @param {unknown} raw\n * @returns {Record<string, string[]> | null}\n */\nexport const normalizeReactions = (raw) => {\n if (!raw || typeof raw !== \"object\") return null;\n const out = /** @type {Record<string, string[]>} */ ({});\n for (const emoji of REACTION_EMOJIS) {\n const authors = raw[emoji];\n if (!Array.isArray(authors)) continue;\n const kept = [];\n for (const author of authors) {\n if (typeof author !== \"string\") continue;\n const clean = author.trim();\n if (clean && !kept.includes(clean)) kept.push(clean);\n }\n if (kept.length > 0) out[emoji] = kept;\n }\n return Object.keys(out).length > 0 ? out : null;\n};\n\n/**\n * Flips one actor's reaction on a comment or a reply, in place, and reports\n * whether anything changed so the caller decides what to persist and emit.\n *\n * @param {{ reactions?: Record<string, string[]> }} target\n * @param {string} emoji\n * @param {string} actorKey\n * @returns {boolean} false when the emoji is not in the set, or there is no\n * actor to attribute the reaction to\n */\nexport const toggleReactionOn = (target, emoji, actorKey) => {\n if (!REACTION_EMOJIS.includes(emoji) || !actorKey) return false;\n if (!target.reactions) target.reactions = {};\n const authors = target.reactions[emoji] || [];\n const index = authors.indexOf(actorKey);\n if (index >= 0) authors.splice(index, 1);\n else authors.push(actorKey);\n // Never store an empty array: absent and \"nobody\" are the same state, and\n // keeping one spelling of it is what lets the serializer omit the field.\n if (authors.length > 0) target.reactions[emoji] = authors;\n else delete target.reactions[emoji];\n return true;\n};\n\n/**\n * Serializer half. Copied rather than referenced, like `tags`: a host mutating\n * serializeComments() output must not be able to reach back into overlay\n * internals. Null (not `{}`) when there is nothing, so a corpus nobody reacted\n * to costs no bytes.\n *\n * @param {Record<string, string[]> | undefined | null} reactions\n * @returns {Record<string, string[]> | null}\n */\nexport const serializeReactions = (reactions) => {\n const entries = Object.entries(reactions || {}).filter(\n ([, authors]) => authors?.length > 0\n );\n return entries.length > 0\n ? Object.fromEntries(\n entries.map(([emoji, authors]) => [emoji, [...authors]])\n )\n : null;\n};\n\n// The \"add reaction\" affordance, in both places it appears: the action row at\n// the top of a comment (or a reply's meta line) and, once something has been\n// reacted to, at the end of the pill row. A smiley with a plus rather than a\n// bare plus \u2014 the bar sits among pills that are already emoji, and a lone \"+\"\n// there read as \"add something\", not \"add a reaction\".\nconst EMOJI_ICON_SVG = `<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M20.94 11.08A9 9 0 1 1 12.92 3.06\"/><path d=\"M8.5 14.2a4.6 4.6 0 0 0 7 0\"/><path d=\"M9 9.5h.01M15 9.5h.01\"/><path d=\"M19 2.6v4M17 4.6h4\"/></svg>`;\n\n/**\n * @typedef {Object} ReactionsUi\n * @property {(target: any) => HTMLElement} bar the pill row for one target\n * @property {(target: any, config: { className: string, tooltip?: boolean }) => HTMLElement} trigger\n * a button that opens the emoji palette\n * @property {(target: any) => void} refresh repaint the target's live rows\n */\n\n/**\n * A reaction UI bound to one thread: it hands out the palette buttons and the\n * pill rows, and keeps every row it created for a given target in step.\n *\n * It exists because a reaction can now be added from a control that does not\n * own the row it changes \u2014 the action row's button sits above the pills, and\n * on a comment those two are built by different modules (the popover header is\n * assembled after `createThreadPopover` returns). Rather than thread refresh\n * handles through both, each row registers itself here and any pick repaints\n * whatever rows for that target are still on screen.\n *\n * `actorKey` is a getter, not a value: a host may swap `user` while the widget\n * is mounted, and a key captured at build time would leave pills nobody can\n * switch off.\n *\n * @param {{\n * actorKey: () => string,\n * strings: Record<string, string>,\n * onToggle: (target: any, emoji: string) => void,\n * }} config\n * @returns {ReactionsUi}\n */\nexport const createReactionsUi = ({ actorKey, strings, onToggle }) => {\n /**\n * target \u2192 the repaints of its live rows. A WeakMap so entries die with the\n * comments they belong to; detached rows are dropped on the next repaint,\n * since the inbox rebuilds its detail view on every refresh.\n * @type {WeakMap<object, Set<{ el: HTMLElement, repaint: () => void }>>}\n */\n const rows = new WeakMap();\n\n const refresh = (target) => {\n const set = rows.get(target);\n if (!set) return;\n for (const entry of set) {\n if (entry.el.isConnected) entry.repaint();\n else set.delete(entry);\n }\n };\n\n const pick = (target, emoji) => {\n onToggle(target, emoji);\n refresh(target);\n };\n\n /**\n * A button that opens the emoji palette. `className` decides which of the\n * two looks it takes: the action row's icon button or the pill row's\n * trailing one.\n * @param {any} target\n * @param {{ className: string, tooltip?: boolean }} config\n * @returns {HTMLElement} a positioned wrapper holding the button and its menu\n */\n const trigger = (target, { className, tooltip = true }) => {\n const wrapper = document.createElement(\"div\");\n wrapper.className = CLASSES.REACTION_TRIGGER;\n\n const btn = document.createElement(\"button\");\n btn.type = \"button\";\n btn.className = className;\n btn.dataset.action = \"react\";\n if (tooltip) btn.dataset.hdTooltip = strings.addReaction;\n btn.setAttribute(\"aria-label\", strings.addReaction);\n btn.innerHTML = EMOJI_ICON_SVG;\n\n const palette = document.createElement(\"div\");\n palette.className = CLASSES.REACTION_PALETTE;\n palette.setAttribute(\"role\", \"menu\");\n palette.setAttribute(\"aria-label\", strings.reactionPickerLabel);\n\n // The same helper every other dropdown uses, so the palette inherits the\n // single-open rule, aria-expanded, the upward flip when it would be\n // clipped, and outside-click close \u2014 and the resolved-card dim, which\n // keys off a generic [aria-expanded=\"true\"], then covers it for free.\n const toggle = attachMenuToggle(btn, palette);\n\n for (const emoji of REACTION_EMOJIS) {\n const item = document.createElement(\"button\");\n item.type = \"button\";\n item.className = CLASSES.REACTION_PALETTE_ITEM;\n item.setAttribute(\"role\", \"menuitem\");\n item.dataset.reactionEmoji = emoji;\n item.textContent = emoji;\n item.setAttribute(\"aria-label\", emoji);\n item.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n // Closed before mutating: the inbox detail rebuilds itself on every\n // refresh, and a palette still open through that rebuild is left\n // orphaned mid-click.\n toggle.close();\n pick(target, emoji);\n });\n palette.appendChild(item);\n }\n\n wrapper.appendChild(btn);\n wrapper.appendChild(palette);\n return wrapper;\n };\n\n /**\n * The pill row for one target. Always returns an element \u2014 hidden while\n * nothing has been reacted to, because the first reaction arrives from a\n * control outside this row and the row has to be there to receive it.\n *\n * A pill's accessible name is composed from the action, the emoji and the\n * count (\"Remove your reaction: \uD83D\uDC4D (3)\") rather than translated as a\n * sentence: `formatTemplate` has no plural forms, and the repo already\n * decided against pluralising counts in copy. Stored actor keys never reach\n * the UI \u2014 they are display names when the host passes no `user.id` and\n * opaque ids when it does, so rendering them would be inconsistent at best.\n *\n * @param {any} target\n * @returns {HTMLElement}\n */\n const bar = (target) => {\n const el = document.createElement(\"div\");\n el.className = CLASSES.REACTION_BAR;\n el.setAttribute(\"role\", \"group\");\n el.setAttribute(\"aria-label\", strings.reactionsLabel);\n\n const repaint = () => {\n el.replaceChildren();\n const entries = reactionEntriesOf(target);\n const me = actorKey();\n // Nothing reacted to means no row at all: the only way in is the\n // trigger in the action row above.\n el.hidden = entries.length === 0;\n if (el.hidden) return;\n\n for (const { emoji, authors } of entries) {\n const mine = authors.includes(me);\n const pill = document.createElement(\"button\");\n pill.type = \"button\";\n pill.className = CLASSES.REACTION_PILL;\n if (mine) pill.classList.add(CLASSES.REACTION_PILL_MINE);\n pill.dataset.reactionEmoji = emoji;\n\n const action = mine\n ? strings.reactionToggleOff\n : strings.reactionToggleOn;\n // A toggle has to say which way it is about to flip; the count alone\n // does not, and the highlight is colour, which never stands alone.\n pill.setAttribute(\"aria-pressed\", String(mine));\n // No hover bubble on any pill: the emoji and count are already there,\n // and a bubble on every one of them turned a dense row into a wall of\n // popups. The trigger beside the row keeps its tooltip \u2014 it is the\n // only control whose icon does not say what it does. The accessible\n // name still carries the action for assistive tech.\n pill.setAttribute(\n \"aria-label\",\n `${action}: ${emoji} (${authors.length})`\n );\n pill.addEventListener(\"click\", (e) => {\n // The inbox list card navigates on click, and the popover closes on\n // an outside click \u2014 neither may fire because a pill was pressed.\n e.stopPropagation();\n pick(target, emoji);\n });\n\n const emojiEl = document.createElement(\"span\");\n emojiEl.className = CLASSES.REACTION_PILL_EMOJI;\n emojiEl.textContent = emoji;\n // Decorative: the accessible name above already spells out the\n // reaction and its count, and screen readers announce emoji unevenly.\n emojiEl.setAttribute(\"aria-hidden\", \"true\");\n\n const count = document.createElement(\"span\");\n count.className = CLASSES.REACTION_PILL_COUNT;\n count.textContent = String(authors.length);\n\n pill.appendChild(emojiEl);\n pill.appendChild(count);\n el.appendChild(pill);\n }\n\n // Trailing \"one more\" affordance, only ever next to existing pills.\n el.appendChild(trigger(target, { className: CLASSES.REACTION_ADD }));\n };\n\n let set = rows.get(target);\n if (!set) rows.set(target, (set = new Set()));\n set.add({ el, repaint });\n\n repaint();\n return el;\n };\n\n return { bar, trigger, refresh };\n};\n", "// Who may edit or delete what.\n//\n// Until this module existed the widget had identity but never consulted it:\n// every comment and reply carried `authorId`, and the \u22EF menu offered \"Delete\"\n// on all of them to everybody. One person's comment was one stranger's click\n// away from being gone.\n//\n// The scope is deliberately narrow \u2014 editing and deleting a comment or a\n// reply. Status, type, priority and reactions stay open to everyone: those\n// are triage, they are reversible, and a team that cannot re-classify each\n// other's reports has lost the point of a shared inbox. Deleting is neither\n// reversible nor shared.\n//\n// What this is NOT: enforcement. HellDots runs in the page, so a determined\n// visitor reaches `deleteComment()` from the console no matter what this file\n// says. The guard removes the accidental path \u2014 the button that should never\n// have been there \u2014 and hands the host a vocabulary to mirror. Authorization\n// proper belongs in the host's backend, checking `authorId` against its own\n// session when the `comment:deleted` event arrives.\n\nimport { normalizeActorId } from \"./id.js\";\nimport { actorKeyOf } from \"./reactions.js\";\n\n/**\n * The actions a host can veto, and the only strings `can` is ever called\n * with. Exported so a host can assert against the list instead of typing the\n * four literals from memory.\n * @type {import('./index.d.ts').PermissionAction[]}\n */\nexport const PERMISSION_ACTIONS = [\n \"edit:comment\",\n \"delete:comment\",\n \"edit:reply\",\n \"delete:reply\",\n];\n\n/**\n * The identity a stored record was written under, resolved exactly the way\n * `actorKeyOf` resolves the live one.\n *\n * Mirror images on purpose, and that is why this imports from reactions.js\n * rather than growing a second resolver: ownership and \"this reaction is\n * mine\" are the same question asked twice, and the day they disagree is the\n * day someone loses the delete button on their own comment.\n *\n * @param {{ author?: string, authorId?: string | null }} record\n * @param {{ anonymous: string }} strings\n * @returns {string}\n */\nexport const recordKeyOf = (record, strings) =>\n normalizeActorId(record?.authorId) ||\n (typeof record?.author === \"string\" ? record.author.trim() : \"\") ||\n strings.anonymous;\n\n/**\n * The rule that applies when the host declares no `can`: you own what carries\n * your identity.\n *\n * A host that never sets `user` gets today's behaviour back unchanged \u2014 every\n * record is written by \"Anonymous\" and so is every reader, the keys match, and\n * nothing is hidden. That is the point: the playground, the localStorage demo\n * and every single-user setup must not have to opt out of a rule that has\n * nobody to protect them from.\n *\n * Known limit, inherited from `actorKeyOf` and shared with reactions: with no\n * `user.id` anywhere the comparison falls back to the display name, so the\n * anonymous fallback is the *localised* one. A corpus written under `en` and\n * read under `es` reads as somebody else's. It fails closed (the button is\n * hidden, nothing is destroyed) and any host that passes `user.id` never\n * reaches that branch.\n *\n * @param {import('./index.d.ts').PermissionTarget} target\n * @param {{ name?: string, id?: string } | undefined} user\n * @param {{ anonymous: string }} strings\n * @returns {boolean}\n */\nexport const isOwnRecord = (target, user, strings) =>\n recordKeyOf(target, strings) === actorKeyOf(user, strings);\n\n/**\n * The one place the answer is decided, so the menu that hides an item and the\n * mutator that refuses it can never disagree.\n *\n * A host `can` must return literal `true` to allow. Anything else denies \u2014\n * including the `undefined` of a branch that forgot to return. A permission\n * predicate is the wrong place to be generous with coercion: guessing wrong\n * in the permissive direction reintroduces exactly the hole this module\n * closes, and guessing wrong the other way costs a hidden button and a bug\n * found in a minute.\n *\n * A `can` that throws denies for the same reason, and says so loudly: falling\n * back to the default rule would silently run a policy the host thinks it has\n * replaced.\n *\n * @param {{\n * can: unknown,\n * action: import('./index.d.ts').PermissionAction,\n * target: import('./index.d.ts').PermissionTarget,\n * user: { name?: string, id?: string } | undefined,\n * strings: { anonymous: string },\n * }} config\n * @returns {boolean}\n */\nexport const resolvePermission = ({ can, action, target, user, strings }) => {\n if (typeof can !== \"function\") return isOwnRecord(target, user, strings);\n try {\n return can(action, target) === true;\n } catch (err) {\n console.warn(\"HellDots: can() threw, denying\", action, err);\n return false;\n }\n};\n\n/**\n * The shape `can` receives for a comment. Built rather than passed whole: the\n * host gets what an authorization decision needs and no live reference into\n * overlay state, and a comment's screenshots \u2014 data-URLs, one per attachment \u2014\n * stay out of a predicate that runs on every card the inbox renders.\n *\n * @param {any} comment\n * @returns {import('./index.d.ts').PermissionTarget}\n */\nexport const commentTargetOf = (comment) => ({\n id: comment.id,\n author: comment.author,\n authorId: comment.authorId || null,\n});\n\n/**\n * Same, one level down. `commentId` rides along because a reply's id is only\n * unique inside its thread, so it is not enough to look the record up with.\n *\n * @param {any} reply\n * @param {import('./index.d.ts').CommentId} commentId\n * @returns {import('./index.d.ts').PermissionTarget}\n */\nexport const replyTargetOf = (reply, commentId) => ({\n id: reply.id,\n author: reply.author,\n authorId: reply.authorId || null,\n commentId,\n});\n", "// Deep links to a single comment.\n//\n// There is no redirect hop here on purpose. Hosted tools can afford one\n// because their back end knows which deployment a thread belongs to and has\n// to resolve that before it can send you anywhere. HellDots has no server:\n// the page a comment lives on is already recorded on the comment, so the\n// link can point straight at its final destination.\n\nexport const DEFAULT_LINK_PARAM = \"helldotsComment\";\n\n/**\n * The shareable URL for one comment.\n *\n * For a comment on the page the user is currently looking at, this keeps the\n * rest of the current URL \u2014 query and hash included. That matters more than\n * it looks: a comment left on `/products?filter=archived` is *about* that\n * filtered view, and a link that drops the filter lands the reader somewhere\n * the comment does not make sense. For other pages only `comment.page` is\n * known (it stores `location.pathname`), so that is all the link can carry.\n *\n * @param {{ id: import('./index.d.ts').CommentId, page?: string }} comment\n * @param {string} [param]\n * @param {string} [href] current document URL; injectable for tests\n * @returns {string}\n */\nexport const buildCommentLink = (\n comment,\n param = DEFAULT_LINK_PARAM,\n href = location.href\n) => {\n const current = new URL(href);\n const page = comment.page || current.pathname;\n const url = page === current.pathname ? current : new URL(page, current);\n url.searchParams.set(param, String(comment.id));\n return url.href;\n};\n\n/**\n * The comment id requested by the current URL, if any.\n * @param {string} [param]\n * @param {string} [href]\n * @returns {string | null}\n */\nexport const readCommentLinkParam = (\n param = DEFAULT_LINK_PARAM,\n href = location.href\n) => {\n try {\n return new URL(href).searchParams.get(param);\n } catch {\n // A malformed URL is not worth breaking startup over \u2014 the widget just\n // opens without honouring a link it could not read.\n return null;\n }\n};\n", "// The append-only trail behind \"who created, changed or resolved this\n// comment, and when\".\n//\n// One record per action worth auditing: creation, a text edit, a status move\n// and a classification change. Replies already carry their own author and\n// timestamp, and reactions are high-frequency signal with no audit value \u2014\n// neither enters the log. That bound is what keeps it at three to five\n// entries per comment instead of twenty, and it is why the quota-shedding\n// path in storage.js needs no change: a hundred comments' worth of history\n// costs about what two automatic screenshots cost.\n//\n// Nothing here is stored twice. The resolution figures RF5 renders are\n// derived from the log on read rather than kept beside it, so they cannot\n// go stale when a comment is reopened.\n\nimport { normalizeActorId } from \"./id.js\";\n\n/** The four auditable actions. */\nexport const AUDIT_EVENTS = [\"created\", \"edited\", \"status\", \"classified\"];\n\n/** The classification fields a \"classified\" event can name. */\nexport const AUDIT_FIELDS = [\"type\", \"priority\", \"tags\"];\n\n// A display name comes from the host and is repeated on every entry, so a\n// pathological one is capped. The actor's id is deliberately NOT capped \u2014\n// see normalizeActorId: it is the only thing a host can reconcile on, and a\n// truncated key joins wrongly instead of failing loudly.\nconst FIELD_MAX = 64;\n\nconst clean = (value) =>\n typeof value === \"string\" ? value.trim().slice(0, FIELD_MAX) : \"\";\n\n// `null` is a value here, not an absence: it is how type and priority read\n// when they are deliberately unset, so a transition to it has to survive.\nconst transition = (value) => {\n if (value === null) return { present: true, value: null };\n if (typeof value === \"string\") return { present: true, value: clean(value) };\n return { present: false, value: undefined };\n};\n\n/**\n * The actor of an action: the stable id the host supplied, plus the name it\n * displays.\n *\n * Resolved in one place for the same reason `actorKeyOf` is \u2014 the log and the\n * author line must never disagree about who acted. The two are siblings, not\n * duplicates: that one produces a key for de-duplication, this one a record\n * for display.\n *\n * @param {{ name?: string, id?: string } | undefined} user\n * @param {{ anonymous: string }} strings\n * @returns {{ id?: string, name: string }}\n */\nexport function actorOf(user, strings) {\n const name = clean(user?.name) || strings.anonymous;\n const id = normalizeActorId(user?.id);\n return id ? { id, name } : { name };\n}\n\n/**\n * Appends one entry, creating the array on first use so an untouched corpus\n * carries no extra bytes.\n *\n * Callers record AFTER their own no-op guard: `setCommentStatus` returns\n * early when the status is unchanged, and an entry appended above that line\n * would log a change that never happened.\n *\n * @param {object} comment\n * @param {string} type one of AUDIT_EVENTS\n * @param {{ id?: string, name: string }} actor\n * @param {{ field?: string, from?: string | null, to?: string | null }} [detail]\n * @returns {object | null} the entry, or null for an unknown event type\n */\nexport function recordEvent(comment, type, actor, detail) {\n if (!AUDIT_EVENTS.includes(type)) return null;\n\n const entry = { type, at: new Date().toISOString(), actor };\n if (detail?.field && AUDIT_FIELDS.includes(detail.field)) {\n entry.field = detail.field;\n }\n const from = transition(detail?.from);\n if (from.present) entry.from = from.value;\n const to = transition(detail?.to);\n if (to.present) entry.to = to.value;\n\n if (!Array.isArray(comment.history)) comment.history = [];\n comment.history.push(entry);\n return entry;\n}\n\n/**\n * Defensive read of a loaded log, at the same level as the existing\n * malformed-reply filter and `normalizeReactions`. A hostile backend or a\n * corrupt localStorage must not be able to inject an event type the timeline\n * has no label for, or a timestamp that poisons every average built on it.\n *\n * @param {unknown} raw\n * @returns {object[] | null} null rather than [], so the serializer can omit\n * the field entirely\n */\nexport function normalizeHistory(raw) {\n if (!Array.isArray(raw)) return null;\n\n const out = [];\n for (const item of raw) {\n if (!item || typeof item !== \"object\") continue;\n if (!AUDIT_EVENTS.includes(item.type)) continue;\n\n const at = clean(item.at);\n if (!Number.isFinite(Date.parse(at))) continue;\n\n const name = clean(item.actor?.name);\n const id = normalizeActorId(item.actor?.id);\n const entry = { type: item.type, at, actor: id ? { id, name } : { name } };\n\n if (AUDIT_FIELDS.includes(item.field)) entry.field = item.field;\n const from = transition(item.from);\n if (from.present) entry.from = from.value;\n const to = transition(item.to);\n if (to.present) entry.to = to.value;\n\n out.push(entry);\n }\n\n if (out.length === 0) return null;\n // Chronological regardless of the order they arrived in: a host merging two\n // devices' corpora can hand us an interleaving, and every reader below\n // walks this array assuming it is ordered.\n out.sort((a, b) => Date.parse(a.at) - Date.parse(b.at));\n return out;\n}\n\n/**\n * Copies the log out, actor included, so a host mutating what\n * `serializeComments()` returned cannot reach back into overlay state \u2014 the\n * same rule `tags` and `reactions` already follow.\n * @param {object[] | null | undefined} history\n * @returns {object[] | null}\n */\nexport function serializeHistory(history) {\n if (!Array.isArray(history) || history.length === 0) return null;\n return history.map((entry) => ({ ...entry, actor: { ...entry.actor } }));\n}\n\n/**\n * Every resolution the comment has had, oldest first, derived from the log\n * rather than stored beside it. A status event landing on `resolved` opens\n * one; the next status event closes it.\n *\n * Each duration is measured from creation, not from the reopen \u2014 \"time to\n * resolve\" answers how long the reporter waited, and restarting the clock on\n * a reopen would make a comment that bounced twice look faster than one that\n * was fixed on the first attempt.\n *\n * @param {object} comment\n * @returns {Array<{ resolvedAt: string, reopenedAt: string | null, ms: number }>}\n */\nexport function resolutionsOf(comment) {\n if (!Array.isArray(comment?.history)) return [];\n\n const out = [];\n let openedAt = null;\n for (const entry of comment.history) {\n if (entry.type !== \"status\") continue;\n if (entry.to === \"resolved\") {\n openedAt = entry.at;\n } else if (openedAt) {\n out.push({ resolvedAt: openedAt, reopenedAt: entry.at, ms: 0 });\n openedAt = null;\n }\n }\n if (openedAt) out.push({ resolvedAt: openedAt, reopenedAt: null, ms: 0 });\n\n // Clocks belong to the client. Merge two devices whose clocks disagree and\n // a resolution can land before the creation it resolves \u2014 clamp rather than\n // render a negative duration.\n const createdAt = Date.parse(comment.createdAt);\n for (const item of out) {\n const resolved = Date.parse(item.resolvedAt);\n item.ms =\n Number.isFinite(createdAt) && Number.isFinite(resolved)\n ? Math.max(0, resolved - createdAt)\n : 0;\n }\n return out;\n}\n\n/**\n * Elapsed time of the resolution currently in force, or null when the comment\n * is not resolved. A function rather than a stored figure, so it can never\n * disagree with the log it comes from.\n *\n * @param {object} comment\n * @returns {number | null}\n */\nexport function currentResolutionMs(comment) {\n if (comment?.status !== \"resolved\") return null;\n\n const resolutions = resolutionsOf(comment);\n const last = resolutions[resolutions.length - 1];\n if (last && !last.reopenedAt) return last.ms;\n\n // Resolved before the log existed: fall back to the stored stamp so an\n // older corpus still renders a duration instead of an em dash.\n const resolved = Date.parse(comment.resolvedAt);\n const created = Date.parse(comment.createdAt);\n if (!Number.isFinite(resolved) || !Number.isFinite(created)) return null;\n return Math.max(0, resolved - created);\n}\n", "// A modal confirmation for the destructive actions. Deleting a comment or a\n// reply is the only thing in the widget that cannot be undone \u2014 there is no\n// trash and no history \u2014 and until now a single click on a menu item did it.\n//\n// Deliberately promise-based rather than callback-based: every call site\n// reads as \"ask, then act\", and the \"user said no\" path is a plain early\n// return instead of a second callback that does nothing.\n\nimport { CLASSES, Z_INDEX } from \"./constants.js\";\n\n/**\n * @typedef {{\n * title: string,\n * message: string,\n * confirmLabel: string,\n * cancelLabel: string,\n * }} ConfirmStrings\n */\n\n/**\n * Open dialogs, so teardown can settle them. Without this, unmounting the\n * widget mid-question would take the DOM away and leave the capture-phase\n * keydown listener on `document` swallowing Escape for the whole page.\n * @type {Set<(result: boolean) => void>}\n */\nconst openDialogs = new Set();\n\n/** Dismisses every open dialog as if the user had cancelled. */\nexport const closeOpenConfirmDialogs = () => {\n for (const dismiss of [...openDialogs]) dismiss(false);\n};\n\n/**\n * Opens the dialog and resolves once the user answers.\n *\n * @param {ShadowRoot | HTMLElement} host where to mount \u2014 pass the widget's\n * shadow root so the dialog inherits its styles and stacking context\n * @param {ConfirmStrings} strings all pre-localized, like every other view\n * builder here\n * @returns {Promise<boolean>} true only when the user confirms\n */\nexport const confirmDialog = (\n host,\n { title, message, confirmLabel, cancelLabel }\n) =>\n new Promise((resolve) => {\n const backdrop = document.createElement(\"div\");\n backdrop.className = CLASSES.CONFIRM;\n backdrop.style.zIndex = String(Z_INDEX.CONFIRM);\n\n const panel = document.createElement(\"div\");\n panel.className = CLASSES.CONFIRM_PANEL;\n panel.setAttribute(\"role\", \"alertdialog\");\n panel.setAttribute(\"aria-modal\", \"true\");\n\n const titleEl = document.createElement(\"h2\");\n titleEl.className = CLASSES.CONFIRM_TITLE;\n titleEl.textContent = title;\n // Generated rather than a constant id: two dialogs must never claim the\n // same one, and the shadow root is shared with everything else.\n titleEl.id = `hd-confirm-title-${Math.random().toString(36).slice(2, 9)}`;\n panel.setAttribute(\"aria-labelledby\", titleEl.id);\n\n const messageEl = document.createElement(\"p\");\n messageEl.className = CLASSES.CONFIRM_MESSAGE;\n messageEl.textContent = message;\n // The title alone doesn't say \"cannot be undone\" \u2014 the message does,\n // and some AT won't announce it without describedby.\n messageEl.id = `hd-confirm-message-${Math.random().toString(36).slice(2, 9)}`;\n panel.setAttribute(\"aria-describedby\", messageEl.id);\n\n const actions = document.createElement(\"div\");\n actions.className = CLASSES.CONFIRM_ACTIONS;\n\n const cancelBtn = document.createElement(\"button\");\n cancelBtn.type = \"button\";\n cancelBtn.className = CLASSES.CONFIRM_CANCEL;\n cancelBtn.textContent = cancelLabel;\n\n const acceptBtn = document.createElement(\"button\");\n acceptBtn.type = \"button\";\n acceptBtn.className = CLASSES.CONFIRM_ACCEPT;\n acceptBtn.textContent = confirmLabel;\n\n actions.appendChild(cancelBtn);\n actions.appendChild(acceptBtn);\n panel.appendChild(titleEl);\n panel.appendChild(messageEl);\n panel.appendChild(actions);\n backdrop.appendChild(panel);\n\n // Restored on close: the menu item that opened this is gone by then, so\n // without it focus would fall back to <body> and the keyboard user would\n // lose their place entirely.\n const previouslyFocused = /** @type {any} */ (\n /** @type {any} */ (host).activeElement || document.activeElement\n );\n\n let settled = false;\n const settle = (result) => {\n if (settled) return;\n settled = true;\n openDialogs.delete(settle);\n document.removeEventListener(\"keydown\", onKeydown, true);\n backdrop.remove();\n previouslyFocused?.focus?.();\n resolve(result);\n };\n\n // Capture phase, and it stops the event: the overlay's own Escape handler\n // is on document and would otherwise close the thread popover behind the\n // dialog while the question was still on screen.\n const onKeydown = (/** @type {KeyboardEvent} */ e) => {\n if (e.key === \"Escape\") {\n e.stopPropagation();\n e.preventDefault();\n settle(false);\n return;\n }\n if (e.key !== \"Tab\") return;\n // aria-modal is a claim about focus, so it has to be true. Only two\n // stops, which makes the trap a swap rather than a ring walk.\n const focusables = [cancelBtn, acceptBtn];\n const active = /** @type {any} */ (\n /** @type {any} */ (host).activeElement || document.activeElement\n );\n const index = focusables.indexOf(active);\n e.preventDefault();\n const next = e.shiftKey\n ? focusables[(index <= 0 ? focusables.length : index) - 1]\n : focusables[(index + 1) % focusables.length];\n next.focus();\n };\n\n cancelBtn.addEventListener(\"click\", () => settle(false));\n acceptBtn.addEventListener(\"click\", () => settle(true));\n // Only a press that both starts and ends on the backdrop dismisses, so a\n // drag that happens to release outside the panel does not answer for the\n // user.\n let pressedBackdrop = false;\n backdrop.addEventListener(\"mousedown\", (e) => {\n pressedBackdrop = e.target === backdrop;\n // The inbox and the thread popover both close on any mousedown outside\n // themselves; without this they tear down behind the dialog.\n e.stopPropagation();\n });\n backdrop.addEventListener(\"click\", (e) => {\n if (e.target === backdrop && pressedBackdrop) settle(false);\n pressedBackdrop = false;\n });\n\n openDialogs.add(settle);\n document.addEventListener(\"keydown\", onKeydown, true);\n // Callers reach us through getRootNode(), which is the Document \u2014 not a\n // shadow root \u2014 for anything mounted in the light DOM. A Document cannot\n // take a second element child, so mount into its body instead.\n const mountPoint = /** @type {any} */ (host).body || host;\n mountPoint.appendChild(backdrop);\n // Cancel, not confirm: the destructive button should never be one stray\n // Enter away.\n cancelBtn.focus();\n });\n", "// Shared per-comment action strip (copy agent context / lifecycle status /\n// more menu) used by both the inbox cards and the thread popover header.\n// Pure view: every mutation goes through the callbacks; the component only\n// keeps its own dot color and tooltips in sync after a selection.\n\nimport {\n CLASSES,\n STATUSES,\n STATUS_COLORS,\n COMMENT_TYPES,\n TYPE_COLORS,\n PRIORITIES,\n PRIORITY_COLORS,\n} from \"./constants.js\";\nimport { attachMenuToggle } from \"./menus.js\";\nimport { confirmDialog } from \"./confirm-dialog.js\";\nimport { commentTargetOf } from \"./permissions.js\";\n\nconst COPY_ICON_SVG = `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\"/><path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"/></svg>`;\nconst CHECK_ICON_SVG = `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"20 6 9 17 4 12\"/></svg>`;\nconst DOTS_ICON_SVG = `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><circle cx=\"5\" cy=\"12\" r=\"1.6\"/><circle cx=\"12\" cy=\"12\" r=\"1.6\"/><circle cx=\"19\" cy=\"12\" r=\"1.6\"/></svg>`;\n\nexport const copyToClipboard = (text) => {\n if (navigator.clipboard?.writeText) {\n return navigator.clipboard.writeText(text).catch(() => {});\n }\n const textarea = document.createElement(\"textarea\");\n textarea.value = text;\n textarea.style.position = \"fixed\";\n textarea.style.opacity = \"0\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n document.execCommand(\"copy\");\n } catch {}\n textarea.remove();\n return Promise.resolve();\n};\n\nexport const statusLabelOf = (status, strings) =>\n ({\n open: strings.statusOpen,\n in_progress: strings.statusInProgress,\n in_review: strings.statusInReview,\n resolved: strings.statusResolved,\n })[status] || strings.statusOpen;\n\nexport const typeLabelOf = (type, strings) =>\n ({\n bug: strings.typeBug,\n suggestion: strings.typeSuggestion,\n question: strings.typeQuestion,\n improvement: strings.typeImprovement,\n })[type] || strings.unset;\n\nexport const priorityLabelOf = (priority, strings) =>\n ({\n high: strings.priorityHigh,\n medium: strings.priorityMedium,\n low: strings.priorityLow,\n })[priority] || strings.unset;\n\n/**\n * Dot-and-menu picker shared by the status, type and priority controls.\n * Keeps its own copy of the selection so the UI stays correct even when the\n * consumer's onSelect is async or doesn't mutate the comment in place.\n * @param {{\n * action: string,\n * options: Array<string|null>,\n * value: string|null,\n * colorOf: (option: string|null) => string,\n * labelOf: (option: string|null) => string,\n * tooltipLabel: string,\n * onSelect: (option: string|null) => void,\n * showLabel?: boolean,\n * }} config\n * @returns {HTMLElement}\n */\nexport const createPicker = ({\n action,\n options,\n value,\n colorOf,\n labelOf,\n tooltipLabel,\n onSelect,\n showLabel = false,\n}) => {\n const wrapper = document.createElement(\"div\");\n wrapper.style.position = \"relative\";\n\n const btn = document.createElement(\"button\");\n btn.type = \"button\";\n btn.className = CLASSES.INBOX_ACTION_BTN;\n if (showLabel) btn.classList.add(CLASSES.INBOX_ACTION_BTN_LABELED);\n btn.dataset.action = action;\n btn.setAttribute(\"aria-haspopup\", \"true\");\n\n const dot = document.createElement(\"span\");\n dot.className = CLASSES.INBOX_STATUS_DOT;\n btn.appendChild(dot);\n\n // Type and priority share exact colours (bug === high === #FF453A), and\n // unset shares \"no colour\" with unset \u2014 the dot alone can't tell them\n // apart, and hover-only disambiguation doesn't exist on touch. A short\n // text label next to the dot makes the current value legible without it.\n let labelEl = null;\n if (showLabel) {\n labelEl = document.createElement(\"span\");\n labelEl.className = CLASSES.INBOX_ACTION_LABEL;\n btn.appendChild(labelEl);\n }\n\n const menu = document.createElement(\"div\");\n menu.className = CLASSES.INBOX_MENU;\n menu.setAttribute(\"role\", \"menu\");\n\n const toggle = attachMenuToggle(btn, menu);\n\n let current = value;\n\n const syncUi = () => {\n const label = `${tooltipLabel}: ${labelOf(current)}`;\n dot.style.backgroundColor = colorOf(current);\n btn.dataset.hdTooltip = label;\n btn.setAttribute(\"aria-label\", label);\n if (labelEl) labelEl.textContent = labelOf(current);\n menu\n .querySelectorAll(\"[data-picker-option]\")\n .forEach((/** @type {HTMLElement} */ item) => {\n const raw = item.dataset.pickerOption;\n const option = raw === \"\" ? null : raw;\n item.setAttribute(\"aria-checked\", String(option === current));\n });\n };\n\n for (const option of options) {\n const item = document.createElement(\"button\");\n item.type = \"button\";\n item.className = CLASSES.INBOX_MENU_ITEM;\n // \"\" is how a null option round-trips through a dataset string.\n item.dataset.pickerOption = option === null ? \"\" : option;\n item.setAttribute(\"role\", \"menuitemradio\");\n\n const itemDot = document.createElement(\"span\");\n itemDot.className = CLASSES.INBOX_STATUS_DOT;\n itemDot.style.backgroundColor = colorOf(option);\n item.appendChild(itemDot);\n item.appendChild(document.createTextNode(labelOf(option)));\n\n item.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n toggle.close();\n // No-op: re-picking the option that's already selected must not fire\n // onSelect \u2014 for the status picker that would re-stamp resolvedAt on\n // every redundant \"Resolved\" click, destroying RF5's elapsed time.\n if (option === current) return;\n current = option;\n onSelect(option);\n syncUi();\n });\n menu.appendChild(item);\n }\n\n wrapper.appendChild(btn);\n wrapper.appendChild(menu);\n syncUi();\n return wrapper;\n};\n\n/**\n * The \u22EF dropdown, shared by the comment action strip and by each reply row.\n * One builder so both stay identical \u2014 same button, same menu chrome, same\n * single-open rule from menus.js \u2014 instead of two copies drifting apart.\n *\n * `tooltip` is optional, and the reply rows deliberately go without one: the\n * hover bubble is an absolutely positioned ::after on a button flush against\n * the right edge of `.thread-scroll`, and it stuck ~9px past it \u2014 enough to\n * give the thread a horizontal scrollbar it had no other reason to have. The\n * aria-label still names the control, and the menu it opens says the rest.\n *\n * An item may carry a `confirm` factory, and then nothing happens until the\n * user answers the modal. It lives here rather than at each call site so a\n * destructive item cannot be added without one being considered.\n *\n * A factory, not a plain object: the wording depends on the comment's state\n * (\"and all of its replies\"), and the menu is built once when the popover\n * opens. Reading it up front described the thread as it was minutes ago.\n *\n * @param {{\n * label: string,\n * tooltip?: string,\n * items: Array<{\n * label: string,\n * onSelect: () => void,\n * confirm?: () => import(\"./confirm-dialog.js\").ConfirmStrings,\n * feedbackLabel?: string,\n * }>,\n * }} config\n * @returns {HTMLElement}\n */\nexport const createMoreMenu = ({ label, tooltip, items }) => {\n const wrapper = document.createElement(\"div\");\n wrapper.style.position = \"relative\";\n\n const btn = document.createElement(\"button\");\n btn.type = \"button\";\n btn.className = CLASSES.INBOX_ACTION_BTN;\n btn.dataset.action = \"menu\";\n if (tooltip) btn.dataset.hdTooltip = tooltip;\n btn.setAttribute(\"aria-label\", label);\n btn.innerHTML = DOTS_ICON_SVG;\n\n const menu = document.createElement(\"div\");\n menu.className = CLASSES.INBOX_MENU;\n menu.setAttribute(\"role\", \"menu\");\n\n const toggle = attachMenuToggle(btn, menu);\n\n for (const entry of items) {\n const item = document.createElement(\"button\");\n item.type = \"button\";\n item.className = CLASSES.INBOX_MENU_ITEM;\n item.setAttribute(\"role\", \"menuitem\");\n item.textContent = entry.label;\n item.addEventListener(\"click\", async (e) => {\n e.stopPropagation();\n // Copying to the clipboard succeeds invisibly. Closing the menu at once\n // would leave the user with no evidence it happened, and the icon-swap\n // trick the copy button uses needs a control that stays on screen \u2014 so\n // the item says so itself and the menu waits before closing.\n if (entry.feedbackLabel) {\n entry.onSelect();\n item.textContent = entry.feedbackLabel;\n setTimeout(() => {\n item.textContent = entry.label;\n toggle.close();\n }, 1200);\n return;\n }\n toggle.close();\n if (entry.confirm) {\n // Read before awaiting: onSelect may detach the row this button\n // lives in, and a detached node has no shadow root to mount into.\n const host = /** @type {any} */ (item.getRootNode());\n if (!(await confirmDialog(host, entry.confirm()))) return;\n }\n entry.onSelect();\n });\n menu.appendChild(item);\n }\n\n wrapper.appendChild(btn);\n wrapper.appendChild(menu);\n return wrapper;\n};\n\n/**\n * The per-comment action strip, in two groups: what the comment *is* on the\n * left (status, type, priority) and what you can *do* with it on the right\n * (react, copy its context, the \u22EF menu). The split is why the copy button\n * moved: mixed in among the pickers it read as a fourth classification.\n *\n * `can` decides which of the destructive items the \u22EF menu is built with. It\n * is optional and defaults to allowing both, so a caller that renders a strip\n * without a policy \u2014 the style tests, a host embedding the component \u2014 gets\n * the whole menu rather than a silently crippled one.\n *\n * @param {Object} comment\n * @param {{ strings: Object, reactions?: { trigger: Function }, can?: (action: import(\"./index.d.ts\").PermissionAction, target: import(\"./index.d.ts\").PermissionTarget) => boolean, onCopy: Function, onCopyLink?: Function, onEdit?: Function, onSetStatus: Function, onSetType: Function, onSetPriority: Function, onDelete: Function }} deps\n * @returns {HTMLElement}\n */\nexport const createCommentActions = (\n comment,\n {\n strings,\n reactions,\n can,\n onCopy,\n onCopyLink,\n onEdit,\n onSetStatus,\n onSetType,\n onSetPriority,\n onDelete,\n }\n) => {\n const actions = document.createElement(\"div\");\n actions.className = CLASSES.INBOX_CARD_ACTIONS;\n\n const classification = document.createElement(\"div\");\n classification.className = CLASSES.ACTIONS_GROUP;\n const tools = document.createElement(\"div\");\n tools.className = `${CLASSES.ACTIONS_GROUP} ${CLASSES.ACTIONS_GROUP_END}`;\n actions.appendChild(classification);\n actions.appendChild(tools);\n\n // --- add reaction ---\n // The only way to leave the FIRST reaction: the pill row below appears with\n // the reaction, not before it, so it cannot be the entry point.\n if (reactions) {\n tools.appendChild(\n reactions.trigger(comment, { className: CLASSES.INBOX_ACTION_BTN })\n );\n }\n\n // --- copy agent context ---\n const copyBtn = document.createElement(\"button\");\n copyBtn.type = \"button\";\n copyBtn.className = CLASSES.INBOX_ACTION_BTN;\n copyBtn.dataset.action = \"copy\";\n copyBtn.dataset.hdTooltip = strings.copyAgentContext;\n copyBtn.setAttribute(\"aria-label\", strings.copyAgentContext);\n copyBtn.innerHTML = COPY_ICON_SVG;\n copyBtn.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n onCopy(comment);\n copyBtn.innerHTML = CHECK_ICON_SVG;\n copyBtn.dataset.hdTooltip = strings.copied;\n setTimeout(() => {\n copyBtn.innerHTML = COPY_ICON_SVG;\n copyBtn.dataset.hdTooltip = strings.copyAgentContext;\n }, 1500);\n });\n tools.appendChild(copyBtn);\n\n // --- lifecycle status picker (RF09) ---\n classification.appendChild(\n createPicker({\n action: \"status\",\n options: STATUSES,\n value: comment.status || \"open\",\n // Every STATUSES entry has a colour; the fallback only catches a status\n // this build doesn't know, which loadComments already coerces to `open`.\n colorOf: (status) => STATUS_COLORS[status] || STATUS_COLORS.open,\n labelOf: (status) => statusLabelOf(status, strings),\n tooltipLabel: strings.statusLabel,\n onSelect: (status) => onSetStatus(comment, status),\n // Labelled like type and priority: the strip is on its own row now, so\n // there is room, and a lone coloured dot needed a hover to be read \u2014\n // which touch never provides.\n showLabel: true,\n })\n );\n\n // --- category picker (RF3) ---\n classification.appendChild(\n createPicker({\n action: \"type\",\n // `null` first: returning to the neutral state must be reachable.\n options: [null, ...COMMENT_TYPES],\n value: comment.type || null,\n colorOf: (type) => TYPE_COLORS[type] || \"transparent\",\n labelOf: (type) => typeLabelOf(type, strings),\n tooltipLabel: strings.typeLabel,\n onSelect: (type) => onSetType?.(comment, type),\n showLabel: true,\n })\n );\n\n // --- priority picker (RF4) ---\n classification.appendChild(\n createPicker({\n action: \"priority\",\n options: [null, ...PRIORITIES],\n value: comment.priority || null,\n colorOf: (priority) => PRIORITY_COLORS[priority] || \"transparent\",\n labelOf: (priority) => priorityLabelOf(priority, strings),\n tooltipLabel: strings.priorityLabel,\n onSelect: (priority) => onSetPriority?.(comment, priority),\n showLabel: true,\n })\n );\n\n // --- more (\u22EF) menu ---\n // Hidden, not disabled. A greyed-out \"Delete\" on every one of a\n // colleague's comments is a row of dead controls telling you the same\n // thing over and over; the item simply is not yours to see. \"Copy link\"\n // is never gated, so the menu can never come out empty.\n const target = commentTargetOf(comment);\n const allow = (/** @type {any} */ action) =>\n can ? can(action, target) : true;\n\n // Annotated because the first element alone would fix the element type,\n // and the two conditional pushes carry fields it does not have.\n /** @type {Parameters<typeof createMoreMenu>[0][\"items\"]} */\n const items = [\n {\n label: strings.copyLink,\n feedbackLabel: strings.linkCopied,\n onSelect: () => onCopyLink?.(comment),\n },\n ];\n if (allow(\"edit:comment\")) {\n items.push({\n label: strings.editComment,\n onSelect: () => onEdit?.(comment),\n });\n }\n if (allow(\"delete:comment\")) {\n items.push({\n label: strings.deleteComment,\n onSelect: () => onDelete(comment),\n confirm: () => ({\n title: strings.confirmDeleteCommentTitle,\n // Two wordings rather than a reply count: what matters is that a\n // discussion is about to go with the comment, and saying so\n // avoids pluralising a number in every locale.\n message: comment.replies?.length\n ? strings.confirmDeleteThreadMessage\n : strings.confirmDeleteCommentMessage,\n confirmLabel: strings.confirmDelete,\n cancelLabel: strings.confirmCancel,\n }),\n });\n }\n\n tools.appendChild(\n createMoreMenu({\n label: strings.commentOptions,\n tooltip: strings.moreOptions,\n items,\n })\n );\n\n return actions;\n};\n", "// The inline editor that replaces a comment or reply body while it is being\n// edited, plus the one question asked before an unsaved draft is thrown away.\n//\n// This component is deliberately dumb: it renders a draft and reports every\n// keystroke back. It does NOT own the draft. The panels re-render constantly\n// \u2014 ten `render()` call sites in the inbox alone, plus seven `refresh()`\n// calls from the overlay \u2014 and a draft living in this DOM would be destroyed\n// by any of them, silently, mid-sentence. So the owner keeps the draft as\n// state (the same reason `detailId` is state) and hands it back on rebuild.\n\nimport { CLASSES } from \"./constants.js\";\nimport { confirmDialog } from \"./confirm-dialog.js\";\n\n/**\n * @param {Object} config\n * @param {string} config.value current draft text\n * @param {Object} config.strings\n * @param {(text: string) => void} config.onInput fired on every keystroke\n * @param {(text: string) => void} config.onSave\n * @param {() => void} config.onCancel\n * @returns {HTMLElement}\n */\nexport const createInlineEditor = ({\n value,\n strings,\n onInput,\n onSave,\n onCancel,\n}) => {\n const wrapper = document.createElement(\"div\");\n wrapper.className = CLASSES.EDITOR;\n\n const input = document.createElement(\"textarea\");\n input.className = CLASSES.EDITOR_INPUT;\n input.value = value;\n input.rows = 3;\n input.setAttribute(\"aria-label\", strings.editorAriaLabel);\n\n const actions = document.createElement(\"div\");\n actions.className = CLASSES.EDITOR_ACTIONS;\n\n const cancel = document.createElement(\"button\");\n cancel.type = \"button\";\n cancel.className = CLASSES.EDITOR_CANCEL;\n cancel.textContent = strings.editCancel;\n\n const save = document.createElement(\"button\");\n save.type = \"button\";\n save.className = CLASSES.EDITOR_SAVE;\n save.textContent = strings.editSave;\n\n // An empty body is not a way to delete: the comment would keep its marker,\n // its replies and its row in the inbox while saying nothing. Deleting is\n // its own action, and it asks first.\n const syncSave = () => {\n save.disabled = input.value.trim().length === 0;\n };\n syncSave();\n\n input.addEventListener(\"input\", () => {\n syncSave();\n onInput(input.value);\n });\n\n input.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Escape\") {\n // The overlay closes the thread popover on Escape from a bubble-phase\n // listener on `document`. Without this the panel would go too, and the\n // question about the unsaved draft would be asked about something the\n // user can no longer see.\n e.stopPropagation();\n e.preventDefault();\n onCancel();\n return;\n }\n if (e.key === \"Enter\" && (e.metaKey || e.ctrlKey) && !save.disabled) {\n e.preventDefault();\n onSave(input.value);\n }\n });\n\n cancel.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n onCancel();\n });\n save.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n if (!save.disabled) onSave(input.value);\n });\n\n actions.appendChild(cancel);\n actions.appendChild(save);\n wrapper.appendChild(input);\n wrapper.appendChild(actions);\n\n // Re-rendered panels rebuild this element from the stored draft, so the\n // caret has to be put back where a typist expects it rather than at 0.\n queueMicrotask(() => {\n input.focus();\n input.setSelectionRange(input.value.length, input.value.length);\n });\n\n return wrapper;\n};\n\n/**\n * Asks before an unsaved draft is thrown away. Callers gate on dirtiness\n * themselves \u2014 an untouched editor closes without a question, because there\n * is nothing to lose and a dialog nobody needs teaches people to dismiss\n * dialogs without reading them.\n *\n * @param {any} host node whose root the dialog mounts into\n * @param {Object} strings\n * @returns {Promise<boolean>} true when the draft may be discarded\n */\nexport const confirmDiscard = (host, strings) =>\n confirmDialog(host, {\n title: strings.confirmDiscardTitle,\n message: strings.confirmDiscardMessage,\n confirmLabel: strings.confirmDiscard,\n cancelLabel: strings.confirmKeepEditing,\n });\n", "import {\n CLASSES,\n IDS,\n COMMENT_TYPES,\n TYPE_COLORS,\n PRIORITIES,\n PRIORITY_COLORS,\n STATUS_COLORS,\n MAX_SCREENSHOTS,\n} from \"./constants.js\";\nimport { formatDuration, formatTemplate } from \"./i18n.js\";\nimport { currentResolutionMs } from \"./audit.js\";\nimport defaultStrings from \"./locales/en.js\";\nimport {\n createPicker,\n createMoreMenu,\n statusLabelOf,\n typeLabelOf,\n priorityLabelOf,\n} from \"./comment-actions.js\";\nimport { createInlineEditor } from \"./inline-editor.js\";\nimport { replyTargetOf } from \"./permissions.js\";\n\nconst formatRelativeTime = (date, strings) => {\n const diff = Date.now() - new Date(date).getTime();\n const minutes = Math.floor(diff / 60000);\n const hours = Math.floor(diff / 3600000);\n const days = Math.floor(diff / 86400000);\n\n if (minutes < 1) return strings.justNow;\n if (minutes < 60) return formatTemplate(strings.minutesAgoTemplate, minutes);\n if (hours < 24) return formatTemplate(strings.hoursAgoTemplate, hours);\n return formatTemplate(strings.daysAgoTemplate, days);\n};\n\nconst formatFullDate = (date, locale) => {\n return new Intl.DateTimeFormat(locale, {\n month: \"short\",\n day: \"numeric\",\n hour: \"numeric\",\n minute: \"2-digit\",\n }).format(new Date(date));\n};\n\n/**\n * The author name, clipped to the meta row, with a hover tooltip that appears\n * only when the clipping actually hid something.\n *\n * Two boxes for one name because the ellipsis needs `overflow: hidden`, and an\n * overflow-hidden box clips its own `::after` \u2014 which is exactly how the\n * generic `[data-hd-tooltip]` draws its bubble. The outer box stays visible and\n * carries the tooltip; the inner one truncates.\n *\n * The measurement runs on hover rather than at build time: nothing here is in\n * the document yet when this function returns, so there is no layout to read,\n * and a name that fits today stops fitting after a window resize or a page\n * zoom. One read of `scrollWidth` on entering a name the pointer is already\n * resting on is cheaper than watching every meta row for resizes.\n *\n * @param {string} name\n * @returns {HTMLElement}\n */\nconst createAuthorElement = (name) => {\n const el = document.createElement(\"span\");\n el.className = CLASSES.THREAD_AUTHOR;\n\n const nameEl = document.createElement(\"span\");\n nameEl.className = CLASSES.THREAD_AUTHOR_NAME;\n nameEl.textContent = name;\n el.appendChild(nameEl);\n\n el.addEventListener(\"mouseenter\", () => {\n // A tooltip that repeats a name already fully on screen is the noise the\n // reaction pills were just stripped of; only truncation earns one.\n if (nameEl.scrollWidth > nameEl.clientWidth) el.dataset.hdTooltip = name;\n else delete el.dataset.hdTooltip;\n });\n\n return el;\n};\n\nexport const createMetaElement = (\n author,\n createdAt,\n strings,\n locale,\n editedAt = null\n) => {\n const meta = document.createElement(\"div\");\n meta.className = CLASSES.THREAD_META;\n\n const authorEl = createAuthorElement(author || strings.anonymous);\n\n const timeEl = document.createElement(\"span\");\n timeEl.className = CLASSES.THREAD_TIME;\n timeEl.textContent = formatRelativeTime(createdAt, strings);\n timeEl.dataset.fullDate = formatFullDate(createdAt, locale);\n\n meta.appendChild(authorEl);\n meta.appendChild(timeEl);\n\n if (editedAt) meta.appendChild(createEditedMark(editedAt, strings, locale));\n\n return meta;\n};\n\n/**\n * The \"edited\" mark for a meta line.\n *\n * Someone can answer \"the button is blue\", watch the text they answered get\n * rewritten, and have no way to know it happened \u2014 their reply is left\n * arguing with a sentence that no longer exists. Text rather than a colour,\n * so it holds up under WCAG 1.4.1 like every other badge here, and the exact\n * time hangs off the same `data-full-date` hover the timestamp uses.\n *\n * Exported because the open thread popover is mutated in place rather than\n * re-rendered, so the overlay has to build this same mark after a save.\n *\n * @param {string} editedAt\n * @param {object} strings\n * @param {string} [locale]\n * @returns {HTMLElement}\n */\nexport const createEditedMark = (editedAt, strings, locale) => {\n const editedEl = document.createElement(\"span\");\n editedEl.className = CLASSES.THREAD_EDITED;\n editedEl.textContent = strings.editedMark;\n editedEl.dataset.fullDate =\n strings.editedAtPrefix + formatFullDate(editedAt, locale);\n return editedEl;\n};\n\nexport const isMacPlatform = () =>\n /Mac|iPod|iPhone|iPad/.test(navigator.userAgent);\n\n/**\n * The comment shortcut as the user's platform spells it. Exported so the\n * inbox's empty state teaches the same chord the toolbar tooltip shows \u2014\n * two different renderings of one shortcut is how they drift apart.\n * @param {{ shortcutModifier?: string, shortcutKey?: string }} options\n * @param {object} strings\n */\nexport const getShortcutText = (options, strings) => {\n const isMac = isMacPlatform();\n const modifierMap = {\n alt: isMac ? \"\u2325\" : strings.modifierAlt,\n ctrl: isMac ? \"\u2318\" : strings.modifierCtrl,\n shift: isMac ? \"\u21E7\" : strings.modifierShift,\n };\n\n const modifier = modifierMap[options.shortcutModifier] || modifierMap.alt;\n const key = options.shortcutKey?.toUpperCase() || \"C\";\n\n return `${modifier} + ${key}`;\n};\n\n// Shared with the inbox and the context block \u2014 one caret, not three copies.\nexport const CARET_ICON_SVG = `<svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"6 9 12 15 18 9\"/></svg>`;\n\nconst ATTACH_ICON_SVG = `<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\"/><circle cx=\"8.5\" cy=\"8.5\" r=\"1.5\"/><polyline points=\"21 15 16 10 5 21\"/></svg>`;\n\nconst SEND_ICON_SVG = `<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M22 2L11 13M22 2L15 22L11 13M11 13L2 9L22 2\"/></svg>`;\n\nconst COMMENT_BUBBLE_SVG = `<svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" stroke-linejoin=\"round\" fill=\"currentColor\"><path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M2.8914 10.4028L2.98327 10.6318C3.22909 11.2445 3.5 12.1045 3.5 13C3.5 13.3588 3.4564 13.7131 3.38773 14.0495C3.69637 13.9446 4.01409 13.8159 4.32918 13.6584C4.87888 13.3835 5.33961 13.0611 5.70994 12.7521L6.22471 12.3226L6.88809 12.4196C7.24851 12.4724 7.61994 12.5 8 12.5C11.7843 12.5 14.5 9.85569 14.5 7C14.5 4.14431 11.7843 1.5 8 1.5C4.21574 1.5 1.5 4.14431 1.5 7C1.5 8.18175 1.94229 9.29322 2.73103 10.2153L2.8914 10.4028ZM2.8135 15.7653C1.76096 16 1 16 1 16C1 16 1.43322 15.3097 1.72937 14.4367C1.88317 13.9834 2 13.4808 2 13C2 12.3826 1.80733 11.7292 1.59114 11.1903C0.591845 10.0221 0 8.57152 0 7C0 3.13401 3.58172 0 8 0C12.4183 0 16 3.13401 16 7C16 10.866 12.4183 14 8 14C7.54721 14 7.10321 13.9671 6.67094 13.9038C6.22579 14.2753 5.66881 14.6656 5 15C4.23366 15.3832 3.46733 15.6195 2.8135 15.7653Z\"/></svg>`;\n\nconst MENU_ICON_SVG = `<svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" stroke-linejoin=\"round\" fill=\"currentColor\"><path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M1.67705 7.5L3.92705 3H12.0729L14.3229 7.5H10H9.25V8.25C9.25 8.94036 8.69036 9.5 8 9.5C7.30964 9.5 6.75 8.94036 6.75 8.25V7.5H6H1.67705ZM1.5 9V12C1.5 12.5523 1.94772 13 2.5 13H13.5C14.0523 13 14.5 12.5523 14.5 12V9H10.6465C10.32 10.1543 9.25878 11 8 11C6.74122 11 5.67998 10.1543 5.35352 9H1.5ZM3 1.5H13L15.8944 7.28885C15.9639 7.42771 16 7.58082 16 7.73607V12C16 13.3807 14.8807 14.5 13.5 14.5H2.5C1.11929 14.5 0 13.3807 0 12V7.73607C0 7.58082 0.0361451 7.42771 0.105573 7.28885L3 1.5Z\"/></svg>`;\n\nexport const EYE_ICON_SVG = `<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/></svg>`;\n\nexport const EYE_OFF_ICON_SVG = `<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24\"/><line x1=\"1\" y1=\"1\" x2=\"23\" y2=\"23\"/></svg>`;\n\n/**\n * Shared input area component used by both the comment box and the thread popover.\n * Returns the container element and references to key child elements.\n * @param {Object} options\n * @param {string} options.areaClassName\n * @param {\"textarea\" | \"input\"} [options.inputTag]\n * @param {string} [options.inputClassName]\n * @param {string} [options.inputId]\n * @param {string} options.inputPlaceholder\n * @param {string} [options.submitBtnId]\n * @param {string} [options.fileInputId]\n * @param {typeof defaultStrings} strings\n */\nexport const createInputArea = (\n {\n areaClassName,\n inputTag = \"textarea\",\n inputClassName,\n inputId,\n inputPlaceholder,\n submitBtnId,\n fileInputId,\n },\n strings\n) => {\n const container = document.createElement(\"div\");\n container.className = areaClassName;\n\n /** @type {HTMLInputElement | HTMLTextAreaElement} */\n const inputEl = document.createElement(inputTag);\n if (inputId) inputEl.id = inputId;\n if (inputClassName) inputEl.className = inputClassName;\n inputEl.placeholder = inputPlaceholder;\n inputEl.setAttribute(\"aria-label\", inputPlaceholder);\n if (inputTag === \"input\")\n /** @type {HTMLInputElement} */ (inputEl).type = \"text\";\n\n const screenshotsContainer = document.createElement(\"div\");\n screenshotsContainer.className = CLASSES.SCREENSHOTS_CONTAINER;\n\n const actionsBar = document.createElement(\"div\");\n actionsBar.className = CLASSES.COMMENT_ACTIONS_BAR;\n\n const attachBtn = document.createElement(\"button\");\n attachBtn.className = CLASSES.ATTACH_IMAGE_BTN;\n attachBtn.type = \"button\";\n attachBtn.setAttribute(\"aria-label\", strings.attachImage);\n attachBtn.innerHTML = ATTACH_ICON_SVG;\n\n const fileInput = document.createElement(\"input\");\n fileInput.type = \"file\";\n if (fileInputId) fileInput.id = fileInputId;\n fileInput.accept = \"image/*\";\n fileInput.style.display = \"none\";\n\n const submitBtn = document.createElement(\"button\");\n if (submitBtnId) submitBtn.id = submitBtnId;\n submitBtn.className = CLASSES.THREAD_SUBMIT;\n submitBtn.type = \"button\";\n submitBtn.setAttribute(\"aria-label\", strings.send);\n submitBtn.innerHTML = SEND_ICON_SVG;\n\n actionsBar.appendChild(attachBtn);\n actionsBar.appendChild(fileInput);\n actionsBar.appendChild(submitBtn);\n\n container.appendChild(inputEl);\n container.appendChild(screenshotsContainer);\n container.appendChild(actionsBar);\n\n return {\n container,\n inputEl,\n screenshotsContainer,\n attachBtn,\n fileInput,\n submitBtn,\n };\n};\n\nconst createActionWithTooltip = (btnClass, btnSvg, tooltipContent, label) => {\n const wrapper = document.createElement(\"div\");\n wrapper.className = CLASSES.TOOLBAR_ACTION_WRAPPER;\n\n const tooltip = document.createElement(\"div\");\n tooltip.className = CLASSES.TOOLBAR_ACTION_TOOLTIP;\n tooltipContent.forEach((el) => tooltip.appendChild(el));\n\n const btn = document.createElement(\"button\");\n btn.type = \"button\";\n btn.className = `${CLASSES.TOOLBAR_ACTION_BTN} ${btnClass}`;\n btn.setAttribute(\"aria-label\", label);\n btn.innerHTML = btnSvg;\n\n wrapper.appendChild(tooltip);\n wrapper.appendChild(btn);\n return wrapper;\n};\n\nexport const createToolbar = (options = {}, strings = defaultStrings) => {\n const toolbar = document.createElement(\"div\");\n toolbar.id = IDS.TOOLBAR;\n\n const actions = document.createElement(\"div\");\n actions.className = CLASSES.TOOLBAR_ACTIONS;\n\n const commentLabel = document.createElement(\"span\");\n commentLabel.className = CLASSES.TOOLBAR_TEXT;\n commentLabel.textContent = strings.toolbarComment;\n\n const shortcutKey = document.createElement(\"span\");\n shortcutKey.className = CLASSES.SHORTCUT_HINT;\n shortcutKey.textContent = getShortcutText(options, strings);\n\n const commentWrapper = createActionWithTooltip(\n CLASSES.TOOLBAR_COMMENT_BTN,\n COMMENT_BUBBLE_SVG,\n [commentLabel, shortcutKey],\n strings.toolbarComment\n );\n commentWrapper\n .querySelector(`.${CLASSES.TOOLBAR_COMMENT_BTN}`)\n ?.setAttribute(\"aria-pressed\", \"false\");\n\n const inboxLabel = document.createElement(\"span\");\n inboxLabel.className = CLASSES.TOOLBAR_TEXT;\n inboxLabel.textContent = strings.toolbarInbox;\n\n const inboxWrapper = createActionWithTooltip(\n CLASSES.TOOLBAR_MENU_BTN,\n MENU_ICON_SVG,\n [inboxLabel],\n strings.toolbarInbox\n );\n\n actions.appendChild(commentWrapper);\n actions.appendChild(inboxWrapper);\n toolbar.appendChild(actions);\n\n const visibilityLabel = document.createElement(\"span\");\n visibilityLabel.className = CLASSES.TOOLBAR_TEXT;\n visibilityLabel.textContent = strings.toolbarHideComments;\n\n const visibilityWrapper = createActionWithTooltip(\n CLASSES.TOOLBAR_EYE_BTN,\n EYE_ICON_SVG,\n [visibilityLabel],\n strings.toolbarHideComments\n );\n\n // Its own pill, out of flow: the main pill keeps its exact centered\n // position, and this one hangs off its right edge (the mockup's layout).\n // The button's toggle behavior will apply CLASSES.MARKERS_HIDDEN to indicate\n // when comments are hidden.\n const visibility = document.createElement(\"div\");\n visibility.className = CLASSES.TOOLBAR_VISIBILITY;\n visibility.appendChild(visibilityWrapper);\n toolbar.appendChild(visibility);\n\n return toolbar;\n};\n\n/**\n * RF3/RF4/RF5 \u2014 the classification and resolution-time badge strip. Every\n * badge carries text: colour alone must never be the only signal\n * (WCAG 1.4.1), so the colour only ever tints the border.\n *\n * Both flags exist because a badge is only worth showing where nothing else\n * already says it. The tooltip is a read-only preview and needs all of it.\n * Inbox cards carry labelled status/type/priority pickers, so repeating\n * those three as badges is pure duplication \u2014 but tags and the resolution\n * time have no control anywhere, so they stay either way.\n *\n * @param {any} comment\n * @param {object} strings\n * @param {{ includeStatus?: boolean, includeClassification?: boolean }} [options]\n * @returns {HTMLElement | null} null when there's nothing to show\n */\nexport const createBadgeRow = (\n comment,\n strings,\n { includeStatus = false, includeClassification = true } = {}\n) => {\n const row = document.createElement(\"div\");\n row.className = CLASSES.INBOX_BADGES;\n\n const addBadge = (text, modifier, color) => {\n const badge = document.createElement(\"span\");\n badge.className = `${CLASSES.BADGE} ${modifier}`;\n badge.textContent = text;\n if (color) badge.style.borderColor = color;\n row.appendChild(badge);\n };\n\n if (includeStatus) {\n const status = comment.status || \"open\";\n addBadge(\n statusLabelOf(status, strings),\n CLASSES.BADGE_STATUS,\n STATUS_COLORS[status]\n );\n }\n if (includeClassification && comment.type) {\n addBadge(\n typeLabelOf(comment.type, strings),\n CLASSES.BADGE_TYPE,\n TYPE_COLORS[comment.type]\n );\n }\n if (includeClassification && comment.priority) {\n addBadge(\n priorityLabelOf(comment.priority, strings),\n CLASSES.BADGE_PRIORITY,\n PRIORITY_COLORS[comment.priority]\n );\n }\n // Tags are no longer authored in the widget, but comments saved before\n // that (or set through setCommentTags) still carry them.\n for (const tag of comment.tags || []) {\n addBadge(tag, CLASSES.BADGE_TAG, null);\n }\n\n if (comment.status === \"resolved\") {\n // Derived from the audit log rather than read off a stored figure, so a\n // reopened-and-resolved-again comment cannot show the duration of a\n // resolution that no longer applies. Comments predating the log fall back\n // to their stamp inside currentResolutionMs, and one with neither shows a\n // dash rather than a duration computed from data we do not have.\n const elapsedMs = currentResolutionMs(comment);\n const elapsed =\n elapsedMs === null ? \"\" : formatDuration(elapsedMs, strings);\n addBadge(\n formatTemplate(strings.resolvedInTemplate, elapsed || \"\u2014\"),\n CLASSES.BADGE_DURATION,\n null\n );\n }\n\n return row.children.length ? row : null;\n};\n\n/**\n * RF3 + RF4 \u2014 the classification strip inside the new-comment box: type and\n * priority, both starting neutral.\n *\n * The comment box is built once and reused for every comment, so this\n * exposes reset(): without it the previous comment's selections would leak\n * into the next one.\n *\n * @param {object} strings\n * @returns {{ container: HTMLElement, getType: () => string|null,\n * getPriority: () => string|null, reset: () => void }}\n */\nexport const createClassifyRow = (strings) => {\n const container = document.createElement(\"div\");\n container.className = CLASSES.CLASSIFY_ROW;\n\n let type = null;\n let priority = null;\n\n // Pickers keep their selection internally, so returning them to neutral\n // means rebuilding them \u2014 hence mount() rather than a one-shot append.\n const mount = () => {\n container.replaceChildren();\n container.appendChild(\n createPicker({\n action: \"type\",\n options: [null, ...COMMENT_TYPES],\n value: null,\n colorOf: (value) => TYPE_COLORS[value] || \"transparent\",\n labelOf: (value) => typeLabelOf(value, strings),\n tooltipLabel: strings.typeLabel,\n onSelect: (value) => (type = value),\n showLabel: true,\n })\n );\n container.appendChild(\n createPicker({\n action: \"priority\",\n options: [null, ...PRIORITIES],\n value: null,\n colorOf: (value) => PRIORITY_COLORS[value] || \"transparent\",\n labelOf: (value) => priorityLabelOf(value, strings),\n tooltipLabel: strings.priorityLabel,\n onSelect: (value) => (priority = value),\n showLabel: true,\n })\n );\n };\n\n mount();\n\n return {\n container,\n getType: () => type,\n getPriority: () => priority,\n reset: () => {\n type = null;\n priority = null;\n mount();\n },\n };\n};\n\nexport const createCommentBox = (strings = defaultStrings) => {\n const commentBox = document.createElement(\"div\");\n commentBox.id = IDS.COMMENT_BOX;\n commentBox.setAttribute(\"role\", \"dialog\");\n commentBox.setAttribute(\"aria-label\", strings.commentBoxAriaLabel);\n\n const { container: inputArea } = createInputArea(\n {\n areaClassName: CLASSES.COMMENT_INPUT_AREA,\n inputTag: \"textarea\",\n inputId: IDS.COMMENT_INPUT,\n inputPlaceholder: strings.commentPlaceholder,\n submitBtnId: IDS.SUBMIT_COMMENT,\n fileInputId: IDS.ATTACH_IMAGE_INPUT,\n },\n strings\n );\n\n const classify = createClassifyRow(strings);\n\n commentBox.appendChild(classify.container);\n commentBox.appendChild(inputArea);\n commentBox.style.display = \"none\";\n // Exposed so the overlay can read the selections at save time without\n // re-querying the DOM.\n /** @type {any} */ (commentBox).classify = classify;\n return commentBox;\n};\n\n/**\n * Escapes a value for interpolation inside a double-quoted CSS attribute\n * selector. loadComments accepts arbitrary host ids, so a quote or backslash\n * in one would otherwise make every querySelector throw. Escaped by hand\n * because jsdom (where the whole test suite runs) does not implement\n * CSS.escape.\n * @param {string | number} value\n * @returns {string}\n */\nexport const cssAttrValue = (value) => String(value).replace(/[\\\\\"]/g, \"\\\\$&\");\n\n/**\n * Attribute selector for a comment's marker circle.\n * @param {string | number} id\n * @returns {string}\n */\nexport const circleSelector = (id) => `[data-comment-id=\"${cssAttrValue(id)}\"]`;\n\n/**\n * A comment's (or reply's) attached screenshots, tolerating the singular\n * `screenshot` field records persisted before the array existed still carry.\n * @param {{ screenshots?: string[], screenshot?: string }} entry\n * @returns {string[]}\n */\nexport const screenshotsOf = (entry) =>\n entry.screenshots || (entry.screenshot ? [entry.screenshot] : []);\n\n/**\n * The pending-attachment preview strip. One builder for the three surfaces\n * that show it \u2014 comment box, thread popover reply, inbox reply \u2014 which had\n * drifted apart once already (the popover copy lost its remove button's\n * aria-label and type).\n * @param {Element} container\n * @param {string[]} screenshots the pending array; remove splices it in place\n * @param {{ strings: typeof defaultStrings, onShow: (dataUrl: string) => void,\n * rerender: () => void, pending?: number }} deps `pending` is how many\n * crops are still rendering \u2014 only the comment box passes it, because it\n * is the only surface that opens before its attachment exists.\n */\nexport const renderScreenshotsPreview = (\n container,\n screenshots,\n { strings, onShow, rerender, pending = 0 }\n) => {\n container.innerHTML = \"\";\n container.classList.toggle(\n CLASSES.ACTIVE,\n screenshots.length > 0 || pending > 0\n );\n\n screenshots.forEach((dataUrl, i) => {\n const item = document.createElement(\"div\");\n item.className = CLASSES.SCREENSHOT_ITEM;\n\n const img = document.createElement(\"img\");\n img.className = CLASSES.SCREENSHOT_IMG;\n img.src = dataUrl;\n img.alt = strings.attachedScreenshot;\n makeThumbnailOperable(img, () => onShow(dataUrl));\n\n const removeBtn = document.createElement(\"button\");\n removeBtn.type = \"button\";\n removeBtn.className = CLASSES.SCREENSHOT_REMOVE;\n removeBtn.setAttribute(\"aria-label\", strings.removeScreenshot);\n removeBtn.innerHTML = \"×\";\n removeBtn.onclick = (e) => {\n e.stopPropagation();\n screenshots.splice(i, 1);\n rerender();\n };\n\n item.appendChild(img);\n item.appendChild(removeBtn);\n container.appendChild(item);\n });\n\n // A slot for a crop that has not landed yet. The comment box no longer\n // waits for the render, so without this the box opens with nothing where\n // the user's selection should be and reads as having lost it.\n //\n // It carries the word, not just a shape: a placeholder distinguished only\n // by its dashed outline says nothing to a screen reader and nothing to\n // anyone who cannot separate it from a dark thumbnail (WCAG 1.4.1). The\n // live region is what announces it arriving and being replaced.\n for (let i = 0; i < pending; i++) {\n const slot = document.createElement(\"div\");\n slot.className = `${CLASSES.SCREENSHOT_ITEM} ${CLASSES.SCREENSHOT_PENDING}`;\n slot.setAttribute(\"role\", \"status\");\n slot.setAttribute(\"aria-live\", \"polite\");\n slot.textContent = strings.capturingScreenshot;\n container.appendChild(slot);\n }\n};\n\n/**\n * FileReader as a promise, so the attachment path can await a host's\n * transform after the read without nesting two callbacks. Resolves to null\n * on a read error rather than rejecting \u2014 a file the browser could not read\n * is not an exception, it is just nothing to attach.\n * @param {File} file\n * @returns {Promise<string | null>}\n */\nconst readAsDataUrl = (file) =>\n new Promise((resolve) => {\n const reader = new FileReader();\n reader.onload = (ev) => resolve(/** @type {string} */ (ev.target.result));\n reader.onerror = () => resolve(null);\n reader.readAsDataURL(file);\n });\n\n/**\n * Wires the hidden file input that feeds a pending-screenshots array,\n * enforcing MAX_SCREENSHOTS the same way on every attachment surface.\n * @param {HTMLInputElement} input\n * @param {() => string[]} getScreenshots\n * @param {() => void} rerender\n * @param {(dataUrl: string) => Promise<string>} [transform] the host's\n * screenshot transform, with the comment id already bound by the caller.\n * Omitted by the comment box, whose array is transformed at save instead.\n */\nexport const wireScreenshotInput = (\n input,\n getScreenshots,\n rerender,\n transform\n) => {\n input.addEventListener(\"change\", async (e) => {\n const file = /** @type {HTMLInputElement} */ (e.target).files[0];\n if (!file) return;\n // A non-image read into a data URL renders a broken <img> and bloats\n // the stored payload for nothing.\n if (file.type && !file.type.startsWith(\"image/\")) return;\n if (getScreenshots().length >= MAX_SCREENSHOTS) return;\n\n const pending = readAsDataUrl(file);\n // Cleared while the read is in flight, exactly as before: otherwise\n // picking the same file twice in a row fires no second change event.\n input.value = \"\";\n const dataUrl = await pending;\n if (!dataUrl) return;\n\n // Re-checked after the read, which is long enough for two quick picks to\n // both pass the check above and push past the cap together.\n const screenshots = getScreenshots();\n if (screenshots.length >= MAX_SCREENSHOTS) return;\n\n // The data URL goes in first, so the thumbnail appears the moment the\n // file is readable rather than when the host's upload finishes, and so a\n // reply sent mid-upload carries the image instead of nothing. The array\n // reference is held from here on: a submit reassigns the surface's\n // pending array, and the replacement below has to land in the array this\n // attachment was actually pushed into.\n screenshots.push(dataUrl);\n rerender();\n\n if (!transform) return;\n const value = await transform(dataUrl);\n // Located by value rather than by index: the user may have removed an\n // earlier thumbnail while the upload was in flight. Not found means it\n // was removed \u2014 or that a submit took the array with it, in which case\n // this mutates a detached array and the reply that went out keeps the\n // data URL. Degraded, never lost.\n const at = screenshots.indexOf(dataUrl);\n if (at === -1) return;\n screenshots[at] = value;\n rerender();\n });\n};\n\n/**\n * Every rendered screenshot thumbnail opens the lightbox the same way;\n * wired in one place so the five surfaces that render them cannot drift.\n * @param {ParentNode} root\n * @param {(src: string) => void} onShow\n */\nexport const wireScreenshotLightbox = (root, onShow) => {\n root\n .querySelectorAll(`.${CLASSES.SCREENSHOT_IMG}`)\n .forEach((/** @type {HTMLImageElement} */ img) => {\n makeThumbnailOperable(img, () => onShow(img.src));\n });\n};\n\n/**\n * A thumbnail that opens the lightbox is a control, not decoration: same\n * role=\"button\" + tabindex + Enter/Space pattern the marker circles use\n * (see DECISIONS.md, Accessibility). The img's alt is its accessible name.\n * @param {HTMLImageElement} img\n * @param {() => void} activate\n */\nconst makeThumbnailOperable = (img, activate) => {\n img.setAttribute(\"role\", \"button\");\n img.setAttribute(\"tabindex\", \"0\");\n img.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n activate();\n });\n img.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n activate();\n }\n });\n};\n\nexport const createCommentCircle = (comment, strings = defaultStrings) => {\n const circle = document.createElement(\"div\");\n circle.className = CLASSES.CIRCLE;\n circle.dataset.commentId = comment.id;\n circle.setAttribute(\"role\", \"button\");\n circle.setAttribute(\"tabindex\", \"0\");\n circle.setAttribute(\n \"aria-label\",\n `${strings.commentAriaLabelPrefix}${comment.text}`\n );\n\n // Basic positioning - will be updated by position validation system\n circle.style.cssText = `\n position: absolute;\n pointer-events: auto;\n `;\n\n return circle;\n};\n\nexport const createScreenshotsDisplay = (screenshots, strings) => {\n const container = document.createElement(\"div\");\n container.className = CLASSES.SCREENSHOTS_CONTAINER;\n container.classList.add(CLASSES.ACTIVE);\n\n screenshots.forEach((src) => {\n const item = document.createElement(\"div\");\n item.className = CLASSES.SCREENSHOT_ITEM;\n\n const img = document.createElement(\"img\");\n img.className = CLASSES.SCREENSHOT_IMG;\n img.src = src;\n img.alt = strings.attachedScreenshot;\n\n item.appendChild(img);\n container.appendChild(item);\n });\n\n return container;\n};\n\nexport const createTooltip = (comment, strings = defaultStrings, locale) => {\n const tooltip = document.createElement(\"div\");\n tooltip.className = CLASSES.TOOLTIP;\n tooltip.dataset.for = comment.id;\n tooltip.setAttribute(\"role\", \"dialog\");\n tooltip.setAttribute(\"aria-label\", strings.tooltipAriaLabel);\n\n const header = document.createElement(\"div\");\n header.className = CLASSES.THREAD_HEADER;\n\n const meta = createMetaElement(\n comment.author,\n comment.createdAt,\n strings,\n locale,\n comment.editedAt\n );\n const closeButton = document.createElement(\"button\");\n closeButton.type = \"button\";\n closeButton.className = CLASSES.CLOSE_TOOLTIP;\n closeButton.setAttribute(\"aria-label\", strings.close);\n closeButton.innerHTML = \"×\";\n\n header.appendChild(meta);\n header.appendChild(closeButton);\n\n const body = document.createElement(\"div\");\n body.className = CLASSES.THREAD_BODY;\n body.textContent = comment.text;\n\n tooltip.appendChild(header);\n tooltip.appendChild(body);\n // The tooltip is a read-only preview with no pickers, so the badges are\n // the only place its status/type/priority can be read at all.\n const badges = createBadgeRow(comment, strings, { includeStatus: true });\n if (badges) tooltip.appendChild(badges);\n const tooltipScreenshots = screenshotsOf(comment);\n if (tooltipScreenshots.length > 0) {\n tooltip.appendChild(createScreenshotsDisplay(tooltipScreenshots, strings));\n }\n\n // The preview shows the root comment only, so a thread with replies would\n // otherwise look like a lone remark. Omitted at zero rather than shown as\n // \"0 replies\": absence already says it, and a count of nothing is noise.\n const replyCount = comment.replies?.length || 0;\n if (replyCount > 0) {\n const replies = document.createElement(\"div\");\n replies.className = CLASSES.TOOLTIP_REPLY_COUNT;\n replies.textContent =\n replyCount === 1\n ? strings.replyCountOne\n : formatTemplate(strings.replyCountTemplate, replyCount);\n tooltip.appendChild(replies);\n }\n\n return tooltip;\n};\n\n/**\n * One reply inside a thread. `onDelete` and `onEdit` are optional so\n * read-only renderings (and any host that never wires them) keep the plain\n * row: the \u22EF menu is only built when there is something for it to do.\n *\n * `editing`, when present, replaces the body with the inline editor. The\n * draft it renders belongs to the caller \u2014 see `inline-editor.js` for why.\n *\n * `reactions`, when present, is the thread's reaction UI: it puts the palette\n * trigger on the meta line, next to the \u22EF, and the pill row under the text.\n * Optional for the same reason as the handlers above: a caller that never\n * wires it gets the plain row rather than controls that do nothing.\n *\n * `can`, with the `commentId` the reply hangs off, decides which of the two\n * the row offers. Optional and allow-all by default, matching the comment\n * strip: a caller with no policy gets the menu it always got.\n *\n * @param {any} reply\n * @param {object} [strings]\n * @param {string} [locale]\n * @param {{\n * onDelete?: (reply: any, replyEl: HTMLElement) => void,\n * onEdit?: (reply: any) => void,\n * commentId?: import(\"./index.d.ts\").CommentId,\n * can?: (action: import(\"./index.d.ts\").PermissionAction, target: import(\"./index.d.ts\").PermissionTarget) => boolean,\n * editing?: {\n * draft: string,\n * onInput: (text: string) => void,\n * onSave: (text: string) => void,\n * onCancel: () => void,\n * } | null,\n * reactions?: import(\"./reactions.js\").ReactionsUi | null,\n * }} [handlers]\n */\nexport const createReplyElement = (\n reply,\n strings = defaultStrings,\n locale,\n { onDelete, onEdit, commentId, can, editing = null, reactions = null } = {}\n) => {\n const replyEl = document.createElement(\"div\");\n replyEl.className = CLASSES.THREAD_REPLY;\n // The popover is built once and mutated in place, so anything that has to\n // find one reply again later \u2014 the editor, a text refresh \u2014 needs a handle.\n replyEl.dataset.replyId = String(reply.id);\n\n const meta = createMetaElement(\n reply.author,\n reply.timestamp,\n strings,\n locale,\n reply.editedAt\n );\n\n // Same \u22EF builder the comment strip uses, so a reply is edited and deleted\n // through the control the user already learned one level up.\n const items = [];\n const target = replyTargetOf(reply, commentId);\n const allow = (/** @type {any} */ action) =>\n can ? can(action, target) : true;\n if (onEdit && allow(\"edit:reply\")) {\n items.push({ label: strings.editReply, onSelect: () => onEdit(reply) });\n }\n if (onDelete && allow(\"delete:reply\")) {\n items.push({\n label: strings.deleteReply,\n onSelect: () => onDelete(reply, replyEl),\n confirm: () => ({\n title: strings.confirmDeleteReplyTitle,\n message: strings.confirmDeleteReplyMessage,\n confirmLabel: strings.confirmDelete,\n cancelLabel: strings.confirmCancel,\n }),\n });\n }\n // The reply's own tools, mirroring the comment's action row one level down:\n // react, then the \u22EF. Wrapped so `margin-left: auto` pushes the pair right\n // as one unit instead of only the first of them.\n const replyTools = document.createElement(\"div\");\n replyTools.className = `${CLASSES.ACTIONS_GROUP} ${CLASSES.THREAD_REPLY_ACTIONS}`;\n if (reactions) {\n replyTools.appendChild(\n reactions.trigger(reply, { className: CLASSES.INBOX_ACTION_BTN })\n );\n }\n if (items.length > 0) {\n replyTools.appendChild(\n createMoreMenu({ label: strings.replyOptions, items })\n );\n }\n if (replyTools.children.length > 0) meta.appendChild(replyTools);\n\n let text;\n if (editing) {\n text = createInlineEditor({\n value: editing.draft,\n strings,\n onInput: editing.onInput,\n onSave: editing.onSave,\n onCancel: editing.onCancel,\n });\n } else {\n text = document.createElement(\"div\");\n text.className = CLASSES.THREAD_BODY;\n text.textContent = reply.text;\n }\n\n replyEl.appendChild(meta);\n replyEl.appendChild(text);\n const replyScreenshots = screenshotsOf(reply);\n if (replyScreenshots.length > 0) {\n replyEl.appendChild(createScreenshotsDisplay(replyScreenshots, strings));\n }\n // Last child of the block it belongs to \u2014 after the text and after the\n // thumbnails. One rule, every surface.\n if (reactions) replyEl.appendChild(reactions.bar(reply));\n return replyEl;\n};\n\n/**\n * @param {any} comment\n * @param {object} [strings]\n * @param {string} [locale]\n * @param {{\n * onDeleteReply?: (reply: any, replyEl: HTMLElement) => void,\n * onEditReply?: (reply: any) => void,\n * can?: (action: import(\"./index.d.ts\").PermissionAction, target: import(\"./index.d.ts\").PermissionTarget) => boolean,\n * reactions?: import(\"./reactions.js\").ReactionsUi | null,\n * }} [handlers]\n */\nexport const createThreadPopover = (\n comment,\n strings = defaultStrings,\n locale,\n { onDeleteReply, onEditReply, can, reactions = null } = {}\n) => {\n const popover = document.createElement(\"div\");\n popover.className = CLASSES.THREAD_POPOVER;\n popover.dataset.for = comment.id;\n popover.setAttribute(\"role\", \"dialog\");\n popover.setAttribute(\"aria-label\", strings.popoverAriaLabel);\n\n const header = document.createElement(\"div\");\n header.className = CLASSES.THREAD_HEADER;\n\n const meta = createMetaElement(\n comment.author,\n comment.createdAt,\n strings,\n locale,\n comment.editedAt\n );\n const closeButton = document.createElement(\"button\");\n closeButton.type = \"button\";\n closeButton.className = CLASSES.CLOSE_TOOLTIP;\n closeButton.setAttribute(\"aria-label\", strings.close);\n closeButton.innerHTML = \"×\";\n\n header.appendChild(meta);\n header.appendChild(closeButton);\n\n const body = document.createElement(\"div\");\n body.className = CLASSES.THREAD_BODY;\n body.textContent = comment.text;\n\n const replies = document.createElement(\"div\");\n replies.className = CLASSES.THREAD_REPLIES;\n if (comment.replies) {\n comment.replies.forEach((reply) => {\n replies.appendChild(\n createReplyElement(reply, strings, locale, {\n onDelete: onDeleteReply,\n onEdit: onEditReply,\n commentId: comment.id,\n can,\n reactions,\n })\n );\n });\n }\n\n const { container: inputArea } = createInputArea(\n {\n areaClassName: CLASSES.THREAD_INPUT_AREA,\n inputTag: \"input\",\n inputClassName: CLASSES.THREAD_INPUT,\n inputPlaceholder: strings.replyPlaceholder,\n },\n strings\n );\n\n // The header (and the action row the overlay inserts after it) and the\n // reply box stay put; everything between them scrolls. Without this the\n // popover just grew past the viewport \u2014 expanding the context block or\n // adding replies made content unreachable, because the wheel event fell\n // through to the page.\n const scroll = document.createElement(\"div\");\n scroll.className = CLASSES.THREAD_SCROLL;\n\n popover.appendChild(header);\n scroll.appendChild(body);\n const popoverScreenshots = screenshotsOf(comment);\n if (popoverScreenshots.length > 0) {\n scroll.appendChild(createScreenshotsDisplay(popoverScreenshots, strings));\n }\n // The root comment's own bar, before the replies: it belongs to the text\n // above it, not to the conversation below.\n if (reactions) scroll.appendChild(reactions.bar(comment));\n scroll.appendChild(replies);\n popover.appendChild(scroll);\n popover.appendChild(inputArea);\n\n return popover;\n};\n", "// RF2 \u2014 the environment a comment was reported from, plus the automatic\n// capture taken at that moment.\n//\n// Two surfaces render it as a disclosure, differing only in where it starts:\n// the thread popover collapses it so the popover stays a conversation first\n// and a bug report second, while the inbox detail opens expanded because that\n// view is the one you go to in order to read everything.\n//\n// The caller owns the open/closed state \u2014 `expanded` in, `onToggle` out \u2014\n// because the inbox rebuilds its detail from scratch on every refresh, and a\n// block that remembered its own state would spring back open on the next\n// mutation.\n\nimport { CLASSES } from \"./constants.js\";\nimport { CARET_ICON_SVG, wireScreenshotLightbox } from \"./components.js\";\n\n/**\n * @param {any} comment\n * @param {{ strings: object, onShowLightbox: (src: string) => void,\n * collapsible?: boolean, expanded?: boolean,\n * onToggle?: (expanded: boolean) => void }} deps\n * @returns {HTMLElement | null} null for comments created before RF1/RF2\n */\nexport const createContextBlock = (\n comment,\n { strings, onShowLightbox, collapsible = false, expanded = false, onToggle }\n) => {\n const { context, contextScreenshot } = comment;\n if (!context && !contextScreenshot) return null;\n\n const block = document.createElement(\"div\");\n block.className = CLASSES.CONTEXT_BLOCK;\n\n const body = document.createElement(\"div\");\n body.className = CLASSES.CONTEXT_BODY;\n\n if (collapsible) {\n const toggle = document.createElement(\"button\");\n toggle.type = \"button\";\n toggle.className = CLASSES.CONTEXT_TOGGLE;\n toggle.setAttribute(\"aria-expanded\", String(expanded));\n toggle.innerHTML = `<span>${strings.contextSection}</span>${CARET_ICON_SVG}`;\n body.style.display = expanded ? \"\" : \"none\";\n toggle.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n const isOpen = toggle.getAttribute(\"aria-expanded\") === \"true\";\n toggle.setAttribute(\"aria-expanded\", String(!isOpen));\n body.style.display = isOpen ? \"none\" : \"\";\n onToggle?.(!isOpen);\n });\n block.appendChild(toggle);\n } else {\n const title = document.createElement(\"div\");\n title.className = CLASSES.CONTEXT_TITLE;\n title.textContent = strings.contextSection;\n block.appendChild(title);\n }\n\n if (contextScreenshot) {\n const caption = document.createElement(\"div\");\n caption.className = CLASSES.CONTEXT_SCREENSHOT_CAPTION;\n caption.textContent = strings.autoScreenshotLabel;\n body.appendChild(caption);\n\n const img = document.createElement(\"img\");\n img.className = CLASSES.SCREENSHOT_IMG;\n img.src = contextScreenshot;\n img.alt = strings.autoScreenshotLabel;\n body.appendChild(img);\n\n // Through the shared helper rather than a click listener of its own:\n // this thumbnail opens the lightbox like every other one, so it needs\n // the same role=\"button\" + tabindex + Enter/Space treatment. Wiring it\n // by hand here is exactly the drift the helper exists to prevent.\n wireScreenshotLightbox(body, onShowLightbox);\n }\n\n if (context) {\n const addRow = (label, value) => {\n if (!value) return;\n const row = document.createElement(\"div\");\n row.className = CLASSES.CONTEXT_ROW;\n const key = document.createElement(\"span\");\n key.textContent = label;\n const val = document.createElement(\"span\");\n val.textContent = value;\n row.appendChild(key);\n row.appendChild(val);\n body.appendChild(row);\n };\n\n const size = (dimensions) =>\n dimensions ? `${dimensions.width}\u00D7${dimensions.height}` : \"\";\n const named = (entry) =>\n entry?.name ? `${entry.name} ${entry.version || \"\"}`.trim() : \"\";\n\n addRow(strings.contextUrl, context.url);\n addRow(strings.contextViewport, size(context.viewport));\n addRow(strings.contextScreen, size(context.screen));\n addRow(strings.contextBrowser, named(context.browser));\n addRow(strings.contextOs, named(context.os));\n }\n\n block.appendChild(body);\n return block;\n};\n", "// Builds the plain-text context block the inbox \"copy\" action puts on the\n// clipboard \u2014 enough for a coding agent to locate the element and understand\n// the reported issue (page, viewport, selector, DOM path, thread).\n\nimport { formatDuration } from \"./i18n.js\";\n\nconst openingTagOf = (element) => {\n const attrs = [...element.attributes]\n .map(({ name, value }) => `${name}=\"${value}\"`)\n .join(\" \");\n return `<${element.tagName.toLowerCase()}${attrs ? ` ${attrs}` : \"\"}>`;\n};\n\nconst openingTagFromFingerprint = (fingerprint) => {\n if (!fingerprint?.tagName) return \"(unknown)\";\n const attrs = Object.entries(fingerprint.attributes || {})\n .map(([name, value]) => `${name}=\"${value}\"`)\n .join(\" \");\n return `<${fingerprint.tagName.toLowerCase()}${attrs ? ` ${attrs}` : \"\"}>`;\n};\n\n// body > main.flex.layout > section#pricing.plans \u2014 tag + id + up to two\n// classes per level, from body down to the element.\nconst domPathOf = (element) => {\n const segments = [];\n let current = element;\n while (current && current !== document.documentElement) {\n const tag = current.tagName.toLowerCase();\n const id = current.id ? `#${current.id}` : \"\";\n const classes = [...current.classList]\n .slice(0, 2)\n .map((cls) => `.${cls}`)\n .join(\"\");\n segments.unshift(`${tag}${id}${classes}`);\n if (current === document.body) break;\n current = current.parentElement;\n }\n return segments.join(\" > \");\n};\n\n/**\n * @param {import('./index.d.ts').Comment} comment\n * @param {{ viewportWidth: number, viewportHeight: number, strings?: object }} env\n * @returns {string}\n */\nexport function buildAgentContext(\n comment,\n { viewportWidth, viewportHeight, strings }\n) {\n const anchor = comment.anchor;\n const fingerprint = anchor?.fingerprint;\n const live = comment.container?.isConnected ? comment.container : null;\n\n const state = comment.hidden ? \"hidden\" : comment.anchorState;\n const element = live\n ? openingTagOf(live)\n : openingTagFromFingerprint(fingerprint);\n const path = live ? domPathOf(live) : \"(unavailable)\";\n\n // The reporter's viewport, not the reader's: `comment.context.viewport`\n // was captured at report time (RF2) and travels with the comment. Live\n // `viewportWidth`/`viewportHeight` (the copying browser's window) is only\n // a fallback for legacy records persisted before RF1/RF2 had no context.\n const capturedViewport = comment.context?.viewport;\n const reportedViewportWidth = capturedViewport?.width ?? viewportWidth;\n const reportedViewportHeight = capturedViewport?.height ?? viewportHeight;\n\n const lines = [\n `Page: ${comment.page}`,\n `Viewport: ${reportedViewportWidth}x${reportedViewportHeight}`,\n `Anchor state: ${state}`,\n `Status: ${comment.status || \"open\"}`,\n `Selector: ${anchor?.selector || \"(none)\"}`,\n `Element: ${element}`,\n `DOM path: ${path}`,\n `Nearby text: \"${fingerprint?.textSnippet ?? \"\"}\"`,\n `Comment by ${comment.author} (${comment.createdAt}):`,\n `\"${comment.text}\"`,\n ];\n\n // Neutral classification fields are omitted rather than printed as\n // \"(none)\" \u2014 the agent shouldn't read noise for data nobody filled in.\n if (comment.type) lines.push(`Type: ${comment.type}`);\n if (comment.priority) lines.push(`Priority: ${comment.priority}`);\n if (comment.tags?.length) lines.push(`Tags: ${comment.tags.join(\", \")}`);\n\n const context = comment.context;\n if (context) {\n if (context.url) lines.push(`URL: ${context.url}`);\n if (context.screen) {\n lines.push(`Screen: ${context.screen.width}x${context.screen.height}`);\n }\n if (context.browser?.name) {\n lines.push(\n `Browser: ${`${context.browser.name} ${context.browser.version || \"\"}`.trim()}`\n );\n }\n if (context.os?.name) {\n lines.push(\n `OS: ${`${context.os.name} ${context.os.version || \"\"}`.trim()}`\n );\n }\n }\n\n if (strings && comment.status === \"resolved\" && comment.resolvedAt) {\n const elapsed = formatDuration(\n new Date(comment.resolvedAt).getTime() -\n new Date(comment.createdAt).getTime(),\n strings\n );\n if (elapsed) lines.push(`Resolution time: ${elapsed}`);\n }\n\n const replies = comment.replies || [];\n if (replies.length > 0) {\n lines.push(`Replies (${replies.length}):`);\n for (const reply of replies) {\n lines.push(`- ${reply.author}: \"${reply.text}\"`);\n }\n }\n\n return lines.join(\"\\n\");\n}\n", "// The thread popover: lifecycle, in-place editing state, and the placement\n// math that keeps a floating panel pinned beside its marker.\n//\n// Extracted from CommentOverlay as part of splitting the god object\n// (DECISIONS.md, Fase 5). Pure view-controller: every data mutation flows\n// back through the `actions` contract, so this module never touches\n// storage, callbacks to the host app, or the markers themselves.\n\nimport { CLASSES, MARKER_SIZE } from \"./constants.js\";\nimport {\n createThreadPopover,\n createReplyElement,\n createEditedMark,\n cssAttrValue,\n renderScreenshotsPreview,\n wireScreenshotInput,\n wireScreenshotLightbox,\n} from \"./components.js\";\nimport { createCommentActions, copyToClipboard } from \"./comment-actions.js\";\nimport { createContextBlock } from \"./context-block.js\";\nimport { createReactionsUi } from \"./reactions.js\";\nimport { buildAgentContext } from \"./agent-context.js\";\nimport { buildCommentLink } from \"./link.js\";\nimport { createInlineEditor, confirmDiscard } from \"./inline-editor.js\";\nimport { sameId } from \"./id.js\";\n\n/**\n * Places a floating panel (tooltip or thread popover) beside a marker,\n * clamped to the viewport. Exported on its own because the hover tooltip\n * shares this exact placement without going through the controller.\n * @param {HTMLElement} el\n * @param {HTMLElement} circle\n */\nexport const positionPopoverAtCircle = (el, circle) => {\n const circleRect = circle.getBoundingClientRect();\n const centerX = circleRect.left + circleRect.width / 2;\n const centerY = circleRect.top + circleRect.height / 2;\n const circleBaseSize = MARKER_SIZE;\n const offset = circleBaseSize / 2 + 10;\n\n // Same reasoning as showCommentBox(): the tooltip and popover are\n // `min(400px, 100vw - 24px)` wide, so their real width has to be read.\n const elRect = el.getBoundingClientRect();\n const elWidth = elRect.width || 400;\n\n let x = centerX + offset;\n\n if (x + elWidth > window.innerWidth) {\n x = centerX - offset - elWidth;\n }\n x = Math.min(x, window.innerWidth - elWidth - 10);\n x = Math.max(10, x);\n el.style.left = `${x}px`;\n\n // Vertically the popover is anchored by whichever edge keeps it on\n // screen, and that choice is what makes it grow in the right direction.\n //\n // Pinning `top` alone was not enough: `max-height` caps the height but\n // says nothing about where the box starts, so a marker low on the page\n // put the top at, say, 600px and the popover simply ran off the bottom \u2014\n // taking the reply input with it, so you could not see what you were\n // typing. Re-clamping `top` on every growth would fight the user, since\n // each new reply would shift the whole thread upward under the cursor.\n //\n // Anchoring `bottom` instead makes the browser do it: the reply box stays\n // put and the thread extends upward until `max-height` takes over and\n // `.thread-scroll` starts scrolling.\n const margin = 10;\n const preferredTop = centerY - circleBaseSize / 2;\n const spaceBelow = window.innerHeight - margin - preferredTop;\n\n if (elRect.height > spaceBelow) {\n el.style.top = \"auto\";\n el.style.bottom = `${margin}px`;\n } else {\n el.style.bottom = \"auto\";\n el.style.top = `${Math.max(margin, preferredTop)}px`;\n }\n};\n\n/**\n * Centers a panel in the viewport \u2014 the placement for popovers with no\n * marker to pin to (orphaned comments opened from the inbox).\n * @param {HTMLElement} el\n */\nexport const centerPopover = (el) => {\n const elRect = el.getBoundingClientRect();\n const x = Math.max(10, (window.innerWidth - (elRect.width || 400)) / 2);\n const y = Math.max(10, (window.innerHeight - elRect.height) / 2);\n el.style.left = `${x}px`;\n // Explicitly cleared: this element may have been bottom-anchored by\n // positionPopoverAtCircle, and `top` alone would not win over it.\n el.style.bottom = \"auto\";\n el.style.top = `${y}px`;\n};\n\nexport class PopoverController {\n /**\n * @param {{\n * shadowRoot: ShadowRoot,\n * strings: Object,\n * locale: string,\n * findComment: (id: any) => any,\n * removeTooltip: (id: any) => void,\n * onShowLightbox: (src: string) => void,\n * isInsideLightbox: (target: any) => boolean,\n * linkParam: () => string,\n * refreshInbox: () => void,\n * actorKey: () => string,\n * can: (action: import(\"./index.d.ts\").PermissionAction, target: import(\"./index.d.ts\").PermissionTarget) => boolean,\n * transformScreenshot: Function,\n * actions: {\n * addReply: Function, deleteReply: Function,\n * editComment: Function, editReply: Function,\n * setStatus: Function, setType: Function, setPriority: Function,\n * deleteComment: Function,\n * toggleCommentReaction: Function, toggleReplyReaction: Function,\n * },\n * }} deps\n */\n constructor(deps) {\n this.deps = deps;\n /** The open popover element, or null. @type {HTMLElement | null} */\n this.active = null;\n /**\n * The marker the popover follows on scroll. Null for orphaned comments\n * opened from the inbox \u2014 those get centered instead.\n * @type {HTMLElement | null}\n */\n this._activeCircle = null;\n /**\n * The popover's open editor. Tracked as state even though the popover\n * mounts it straight into the DOM (it is built once and never\n * re-rendered): Escape, the close button and a click on the page all\n * need to know whether there is unsaved text before they act.\n * @type {{ commentId: any, replyId: any | null, draft: string } | null}\n */\n this.editing = null;\n /** @type {ResizeObserver | null} */\n this._resizeObserver = null;\n /** @type {((e: MouseEvent) => void) | null} */\n this._clickHandler = null;\n /**\n * Pending arm of `_clickHandler`. Held so `close()` can cancel it: the\n * listener goes on `document`, so a timer that outlives teardown installs\n * one nothing is left to remove.\n * @type {ReturnType<typeof setTimeout> | null}\n */\n this._armClickTimer = null;\n }\n\n /**\n * The body element of the root comment, or of one reply, inside the open\n * popover. Returns whatever is currently there \u2014 the text node or the\n * editor that replaced it.\n */\n _bodyEl(replyId = null) {\n const popover = this.active;\n if (!popover) return null;\n if (replyId == null) {\n return popover.querySelector(\n `.${CLASSES.THREAD_SCROLL} > .${CLASSES.THREAD_BODY}, .${CLASSES.THREAD_SCROLL} > .${CLASSES.EDITOR}`\n );\n }\n const row = popover.querySelector(\n `.${CLASSES.THREAD_REPLY}[data-reply-id=\"${cssAttrValue(replyId)}\"]`\n );\n return (\n row?.querySelector(`.${CLASSES.THREAD_BODY}, .${CLASSES.EDITOR}`) || null\n );\n }\n\n /**\n * Puts the edited text back on screen where the panels do not rebuild\n * themselves: the open thread quotes the text that just changed, and the\n * hover tooltip is thrown away on mouseleave so it needs nothing.\n */\n refreshCommentViews(id, replyId = null) {\n if (this.active?.dataset.for !== String(id)) return;\n const comment = this.deps.findComment(id);\n if (!comment) return;\n\n const source =\n replyId == null\n ? comment\n : (comment.replies || []).find((r) => sameId(r.id, replyId));\n if (!source) return;\n\n const body = document.createElement(\"div\");\n body.className = CLASSES.THREAD_BODY;\n body.textContent = source.text;\n this._bodyEl(replyId)?.replaceWith(body);\n\n // The \"edited\" mark belongs to the same meta line the author and time\n // are on, and it is absent until the first edit.\n const meta =\n replyId == null\n ? this.active.querySelector(`.${CLASSES.THREAD_META}`)\n : this.active\n .querySelector(\n `.${CLASSES.THREAD_REPLY}[data-reply-id=\"${cssAttrValue(replyId)}\"]`\n )\n ?.querySelector(`.${CLASSES.THREAD_META}`);\n if (\n meta &&\n source.editedAt &&\n !meta.querySelector(`.${CLASSES.THREAD_EDITED}`)\n ) {\n const editedEl = createEditedMark(\n source.editedAt,\n this.deps.strings,\n this.deps.locale\n );\n // Before the \u22EF, which `margin-left: auto` has pushed to the far right.\n const actions = meta.querySelector(`.${CLASSES.THREAD_REPLY_ACTIONS}`);\n if (actions) meta.insertBefore(editedEl, actions);\n else meta.appendChild(editedEl);\n }\n }\n\n /** True while the popover holds an editor at all. */\n isEditing() {\n return this.editing != null;\n }\n\n /** True while the popover holds an editor with unsaved text. */\n editorDirty() {\n if (!this.editing) return false;\n const { commentId, replyId, draft } = this.editing;\n const comment = this.deps.findComment(commentId);\n const source =\n replyId == null\n ? comment\n : (comment?.replies || []).find((r) => sameId(r.id, replyId));\n return draft.trim() !== String(source?.text || \"\").trim();\n }\n\n /**\n * Single gate in front of everything that would take the popover's editor\n * off screen. Mirrors InboxView.releaseEditor so the two panels answer the\n * same question the same way.\n * @returns {Promise<boolean>} true when the caller may proceed\n */\n async releaseEditor() {\n if (!this.editing) return true;\n if (this.editorDirty()) {\n const host = /** @type {any} */ (this.deps.shadowRoot);\n if (!(await confirmDiscard(host, this.deps.strings))) return false;\n }\n const { replyId } = this.editing;\n this.editing = null;\n this._restoreBody(replyId);\n return true;\n }\n\n _restoreBody(replyId) {\n const comment = this.deps.findComment(this.active?.dataset.for);\n if (!comment) return;\n const source =\n replyId == null\n ? comment\n : (comment.replies || []).find((r) => sameId(r.id, replyId));\n if (!source) return;\n\n const body = document.createElement(\"div\");\n body.className = CLASSES.THREAD_BODY;\n body.textContent = source.text;\n this._bodyEl(replyId)?.replaceWith(body);\n }\n\n /**\n * Opens the editor inside the popover. Unlike the inbox this mounts into\n * the DOM directly: the popover is built once and never re-rendered, so\n * there is nothing to survive. The draft is still tracked as state, because\n * Escape, the close button and a click outside all have to know whether\n * there is anything to lose.\n */\n async startEditing(commentId, replyId = null) {\n if (!(await this.releaseEditor())) return;\n\n const comment = this.deps.findComment(commentId);\n const source =\n replyId == null\n ? comment\n : (comment?.replies || []).find((r) => sameId(r.id, replyId));\n if (!source) return;\n\n this.editing = { commentId, replyId, draft: source.text };\n\n const editor = createInlineEditor({\n value: source.text,\n strings: this.deps.strings,\n onInput: (text) => {\n this.editing.draft = text;\n },\n onSave: (text) => {\n const saved =\n replyId == null\n ? this.deps.actions.editComment(commentId, text)\n : this.deps.actions.editReply(commentId, replyId, text);\n this.editing = null;\n if (saved) {\n this.refreshCommentViews(commentId, replyId);\n this.deps.refreshInbox();\n } else {\n this._restoreBody(replyId);\n }\n },\n onCancel: () => {\n this.releaseEditor();\n },\n });\n\n this._bodyEl(replyId)?.replaceWith(editor);\n }\n\n // `circle` may be null for orphaned comments (opened from the inbox):\n // the popover is centered in the viewport instead of pinned to a marker.\n show(circle, comment) {\n this.close();\n\n const { strings, locale } = this.deps;\n this.deps.removeTooltip(comment.id);\n\n // Keeps the selected marker visibly picked out while its thread is open\n // \u2014 same growth as hover, plus a ring, so it survives the pointer\n // leaving the circle to reach the popover.\n circle?.classList.add(CLASSES.CIRCLE_ACTIVE);\n this._activeCircle = circle || null;\n\n const onDeleteReply = (reply, replyEl) => {\n if (!this.deps.actions.deleteReply(comment.id, reply.id)) return;\n replyEl.remove();\n this.deps.refreshInbox();\n };\n\n // Named rather than inlined below: `submitReply` builds a reply row too,\n // and when this handler lived only in the call site there, the row the\n // user had just created came out with a \u22EF menu that could delete but not\n // edit \u2014 until the popover was reopened and the full render wired both.\n const onEditReply = (reply) => this.startEditing(comment.id, reply.id);\n\n // One reaction UI for the whole thread: it reports which target was\n // clicked, so the root comment and any reply route to their own toggle,\n // and it keeps the pill rows in step with the triggers above them \u2014 the\n // comment's trigger lives in the action row, which is assembled below,\n // after createThreadPopover has already built its pill row.\n //\n // Reused by submitReply too, so a reply created in this session gets the\n // same controls a reopened popover would render.\n const reactions = createReactionsUi({\n actorKey: this.deps.actorKey,\n strings,\n onToggle: (target, emoji) =>\n target === comment\n ? this.deps.actions.toggleCommentReaction(comment.id, emoji)\n : this.deps.actions.toggleReplyReaction(comment.id, target.id, emoji),\n });\n\n const popover = createThreadPopover(comment, strings, locale, {\n can: this.deps.can,\n onDeleteReply,\n onEditReply,\n reactions,\n });\n this.deps.shadowRoot.appendChild(popover);\n\n // Same action strip as the inbox cards: copy agent context, lifecycle\n // status picker (RF09) and the \u22EF menu.\n const headerEl = popover.querySelector(`.${CLASSES.THREAD_HEADER}`);\n const actionsEl = createCommentActions(comment, {\n can: this.deps.can,\n strings,\n reactions,\n onCopy: (c) =>\n copyToClipboard(\n buildAgentContext(c, {\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n strings,\n })\n ),\n onCopyLink: (c) =>\n copyToClipboard(buildCommentLink(c, this.deps.linkParam())),\n onEdit: (c) => this.startEditing(c.id),\n onSetStatus: (c, status) => this.deps.actions.setStatus(c.id, status),\n onSetType: (c, type) => this.deps.actions.setType(c.id, type),\n onSetPriority: (c, priority) =>\n this.deps.actions.setPriority(c.id, priority),\n onDelete: (c) => {\n this.close();\n this.deps.actions.deleteComment(c.id);\n this.deps.refreshInbox();\n },\n });\n // Its own row under the header, for the same reason the inbox card has\n // a footer: five controls sharing the header left the author ~90px and\n // truncated it mid-name.\n const actionsRow = document.createElement(\"div\");\n actionsRow.className = CLASSES.THREAD_ACTIONS_ROW;\n actionsRow.appendChild(actionsEl);\n headerEl.insertAdjacentElement(\"afterend\", actionsRow);\n\n // The root comment's gallery, not the reply box's pending previews.\n const mainScreenshotsContainer = popover.querySelector(\n `.${CLASSES.THREAD_SCROLL} > .${CLASSES.SCREENSHOTS_CONTAINER}`\n );\n if (mainScreenshotsContainer) {\n wireScreenshotLightbox(mainScreenshotsContainer, (src) =>\n this.deps.onShowLightbox(src)\n );\n }\n\n // RF2 \u2014 the automatic capture used to be reachable only from the inbox\n // detail. Collapsed by default so the popover stays a conversation\n // first; built here because it needs the lightbox callback.\n const contextBlock = createContextBlock(comment, {\n strings,\n onShowLightbox: (src) => this.deps.onShowLightbox(src),\n collapsible: true,\n });\n if (contextBlock) {\n // `.before()` rather than popover.insertBefore(): the replies live\n // inside the scroll container, not directly under the popover.\n popover.querySelector(`.${CLASSES.THREAD_REPLIES}`).before(contextBlock);\n }\n\n setTimeout(() => {\n if (circle) {\n positionPopoverAtCircle(popover, circle);\n } else {\n centerPopover(popover);\n }\n }, 10);\n\n popover\n .querySelector(`.${CLASSES.CLOSE_TOOLTIP}`)\n .addEventListener(\"click\", async (e) => {\n e.stopPropagation();\n // Unlike a click on the page, pressing \u00D7 is an unambiguous request\n // to close, so an unsaved draft is worth one question. The guard\n // short-circuits before the await, keeping the no-editor path\n // synchronous.\n if (this.editing && !(await this.releaseEditor())) return;\n this.close();\n });\n\n /** @type {HTMLInputElement} */\n const input = /** @type {any} */ (\n popover.querySelector(`.${CLASSES.THREAD_INPUT}`)\n );\n const submitBtn = popover.querySelector(`.${CLASSES.THREAD_SUBMIT}`);\n const threadAttachBtn = popover.querySelector(\n `.${CLASSES.THREAD_INPUT_AREA} .${CLASSES.ATTACH_IMAGE_BTN}`\n );\n /** @type {HTMLInputElement} */\n const threadFileInput = /** @type {any} */ (\n popover.querySelector(`.${CLASSES.THREAD_INPUT_AREA} input[type=\"file\"]`)\n );\n const threadScreenshotsContainer = popover.querySelector(\n `.${CLASSES.THREAD_INPUT_AREA} .${CLASSES.SCREENSHOTS_CONTAINER}`\n );\n\n let pendingReplyScreenshots = [];\n\n const updateReplyScreenshotsPreview = () => {\n renderScreenshotsPreview(\n threadScreenshotsContainer,\n pendingReplyScreenshots,\n {\n strings,\n onShow: (dataUrl) => this.deps.onShowLightbox(dataUrl),\n rerender: () => updateReplyScreenshotsPreview(),\n }\n );\n };\n\n threadAttachBtn.addEventListener(\"click\", () => {\n threadFileInput.click();\n });\n\n wireScreenshotInput(\n threadFileInput,\n () => pendingReplyScreenshots,\n updateReplyScreenshotsPreview,\n (dataUrl) => this.deps.transformScreenshot(dataUrl, comment.id)\n );\n\n const submitReply = () => {\n const text = input.value.trim();\n if (!text && pendingReplyScreenshots.length === 0) return;\n\n const reply = this.deps.actions.addReply(\n comment,\n text,\n pendingReplyScreenshots.length > 0 ? [...pendingReplyScreenshots] : []\n );\n\n const repliesContainer = popover.querySelector(\n `.${CLASSES.THREAD_REPLIES}`\n );\n const replyEl = createReplyElement(reply, strings, locale, {\n onDelete: onDeleteReply,\n onEdit: onEditReply,\n commentId: comment.id,\n can: this.deps.can,\n reactions,\n });\n repliesContainer.appendChild(replyEl);\n\n wireScreenshotLightbox(replyEl, (src) => this.deps.onShowLightbox(src));\n\n // Once the thread is taller than the popover the new reply lands below\n // the fold, so sending would look like nothing happened.\n const scrollEl = popover.querySelector(`.${CLASSES.THREAD_SCROLL}`);\n if (scrollEl) scrollEl.scrollTop = scrollEl.scrollHeight;\n\n input.value = \"\";\n pendingReplyScreenshots = [];\n updateReplyScreenshotsPreview();\n input.focus();\n };\n\n submitBtn.addEventListener(\"click\", submitReply);\n input.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n submitReply();\n }\n });\n\n this.active = popover;\n\n // Sending a reply, expanding the context block or a screenshot finishing\n // its decode all change the popover's height after it was placed. Without\n // re-running the anchor decision the box keeps the top it was given and\n // grows straight off the bottom of the viewport. Watching the element is\n // the one hook that covers every cause; guarded because jsdom has no\n // ResizeObserver.\n if (typeof ResizeObserver !== \"undefined\") {\n this._resizeObserver = new ResizeObserver(() => this.reposition());\n this._resizeObserver.observe(popover);\n }\n\n setTimeout(() => input.focus(), 50);\n\n // Deferred so the gesture that opened this popover cannot immediately\n // close it. Cancellable, because `close()` may run first.\n this._armClickTimer = setTimeout(() => {\n this._armClickTimer = null;\n this._clickHandler = (e) => {\n const target = /** @type {Node} */ (e.composedPath()[0] || e.target);\n if (\n !popover.contains(target) &&\n !circle?.contains(target) &&\n !this.deps.isInsideLightbox(target)\n ) {\n // A click on the page while text is unsaved is an ambiguous\n // gesture \u2014 maybe the user went to look at the thing they are\n // describing. Answering it with a modal would interrupt them, and\n // interrupting often teaches people to dismiss without reading. So\n // the panel simply stays put; the textarea still on screen says\n // everything the dialog would have.\n if (this.editorDirty()) return;\n this.close();\n }\n };\n document.addEventListener(\"mousedown\", this._clickHandler);\n }, 0);\n }\n\n close() {\n // Queried rather than remembered: the marker can be re-rendered while\n // its popover is open, and the stale reference would keep the class.\n this.deps.shadowRoot\n ?.querySelectorAll(`.${CLASSES.CIRCLE_ACTIVE}`)\n .forEach((/** @type {HTMLElement} */ el) =>\n el.classList.remove(CLASSES.CIRCLE_ACTIVE)\n );\n this._resizeObserver?.disconnect();\n this._resizeObserver = null;\n // Every route here has already answered for the draft, and the DOM it\n // lived in is about to go.\n this.editing = null;\n if (this.active) {\n this.active.remove();\n this.active = null;\n }\n this._activeCircle = null;\n if (this._armClickTimer) {\n clearTimeout(this._armClickTimer);\n this._armClickTimer = null;\n }\n if (this._clickHandler) {\n document.removeEventListener(\"mousedown\", this._clickHandler);\n this._clickHandler = null;\n }\n }\n\n /**\n * Re-runs the open popover's placement against its current size. Split\n * from syncToMarker because that one also decides visibility from the\n * marker, which is wrong here: a popover resizing while its marker is\n * off-screen must stay hidden, not reappear.\n */\n reposition() {\n const popover = this.active;\n if (!popover || popover.style.display === \"none\") return;\n\n if (this._activeCircle) {\n positionPopoverAtCircle(popover, this._activeCircle);\n } else {\n centerPopover(popover);\n }\n }\n\n /**\n * Keeps the open thread popover pinned beside its marker while the page\n * scrolls, and hides it while the marker is off-screen.\n *\n * Hidden, not closed: a half-typed reply must survive scrolling the marker\n * out of view and back. Closing here would also fight the outside-click\n * handler, which is the thing that legitimately dismisses the popover.\n */\n syncToMarker() {\n const popover = this.active;\n const circle = this._activeCircle;\n // A popover with no marker is the centered variant (orphaned comment\n // opened from the inbox); it has nothing to track.\n if (!popover || !circle) return;\n\n const rect = circle.getBoundingClientRect();\n const onScreen =\n circle.isConnected &&\n circle.style.display !== \"none\" &&\n rect.bottom > 0 &&\n rect.right > 0 &&\n rect.top < window.innerHeight &&\n rect.left < window.innerWidth;\n\n if (!onScreen) {\n popover.style.display = \"none\";\n return;\n }\n\n // Un-hide before measuring: a `display: none` element reports a zero\n // rect, and positionPopoverAtCircle sizes itself from that measurement.\n popover.style.display = \"\";\n positionPopoverAtCircle(popover, circle);\n }\n}\n", "// The marker engine: circle rendering, position math, occlusion hit-testing,\n// the batched rAF update loop, and every observer/listener that feeds it.\n//\n// Extracted from CommentOverlay as part of splitting the god object\n// (DECISIONS.md, Fase 5). The engine owns WHERE markers are and WHETHER they\n// are visible; what a marker click or hover opens (tooltip, thread popover)\n// belongs to the overlay and comes in through `wireMarker`, so this module\n// never learns about panels, storage or callbacks to the host app.\n\nimport { MARKER_SIZE } from \"./constants.js\";\nimport { createCommentCircle } from \"./components.js\";\nimport { TAG_NAME } from \"./root-element.js\";\n\n// How long a batched position pass may reuse the previous occlusion verdict\n// before hit-testing again. Scrolling schedules a pass per frame; occlusion\n// rarely changes mid-scroll, and the trailing pass settles the end state.\nconst OCCLUSION_INTERVAL_MS = 150;\n\n/**\n * Keeps a marker's point inside its container's box.\n *\n * Clamped to the box itself, not to `box - MARKER_SIZE`: reserving room for\n * the whole marker moved the point the user clicked whenever the container\n * was shorter or narrower than the marker (a 36px navbar row pulled every\n * marker up to 8px from its top). Only the marker's tip carries meaning, so\n * the point stays put and the marker's body overhangs instead.\n *\n * The one place this math lives \u2014 `updatePosition` and `scrollMarkerIntoView`\n * both derive from it, and they must never disagree about where a marker is.\n *\n * @param {number} offset position along one axis, in px from the box's edge\n * @param {number} size the box's length along that axis\n */\nconst clampToBox = (offset, size) => Math.max(0, Math.min(offset, size));\n\nexport class MarkerEngine {\n /**\n * @param {{\n * container: HTMLElement,\n * strings: Object,\n * getComments: () => any[],\n * wireMarker: (circle: HTMLElement, comment: any) => void,\n * onMarkerHidden: (comment: any) => void,\n * onVisibilityFlip: () => void,\n * onAfterPass: () => void,\n * }} deps `container` is the overlay element the circles mount into;\n * `wireMarker` is where the overlay attaches its tooltip/popover\n * handlers; `onMarkerHidden` dismisses UI floating over a marker that\n * just went away; `onAfterPass` runs after every rAF pass (the thread\n * popover follows its marker there).\n */\n constructor(deps) {\n this.deps = deps;\n\n /**\n * Marker circles by String(comment.id). The per-frame position loop\n * used to querySelector each one \u2014 a full shadow-tree scan per comment\n * per frame, O(n\u00B2) on scroll.\n * @type {Map<string, HTMLElement>}\n */\n this.circles = new Map();\n /** @type {Map<string, { circle: HTMLElement, observer: any, container: HTMLElement }>} */\n this.resizeObservers = new Map();\n /** Position validation gate \u2014 off means passes only sync the popover. */\n this.enabled = true;\n\n // Occlusion hit-testing (elementsFromPoint + getComputedStyle per\n // marker) is the expensive part of a position pass, and scrolling is\n // when passes are hottest \u2014 so batched passes run it at most once per\n // OCCLUSION_INTERVAL_MS, with a trailing pass to settle the final state.\n this._lastOcclusionPass = 0;\n this._occlusionTrailingTimer = null;\n\n // rAF scheduling flag for bulk updates\n this._pendingRaf = null;\n\n this._globalMutationObserver = null;\n this._resizeHandler = null;\n this._scrollHandler = null;\n this._loadHandler = null;\n }\n\n /** Attaches the window listeners and the page-wide mutation observer. */\n start() {\n this._resizeHandler = () => this.scheduleUpdate();\n window.addEventListener(\"resize\", this._resizeHandler, { passive: true });\n\n // Capture scroll on any scrolling ancestor\n this._scrollHandler = () => this.scheduleUpdate();\n window.addEventListener(\"scroll\", this._scrollHandler, {\n capture: true,\n passive: true,\n });\n\n // Update after resources load (images, fonts)\n this._loadHandler = () => this.scheduleUpdate();\n window.addEventListener(\"load\", this._loadHandler);\n\n // Modals open/close outside any comment's container (backdrops are\n // usually appended to <body> or toggled via style/class), so the\n // per-comment observers never see them. One page-wide observer keeps\n // the occlusion check honest; shadow-root internals don't bubble into\n // it, so our own marker updates can't retrigger it.\n if (window.MutationObserver) {\n this._globalMutationObserver = new MutationObserver(() => {\n this.scheduleUpdate();\n });\n this._globalMutationObserver.observe(document.body, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: [\"style\", \"class\", \"hidden\", \"open\"],\n });\n }\n }\n\n /** Creates, mounts, positions and observes one comment's marker. */\n render(comment) {\n const circle = createCommentCircle(comment, this.deps.strings);\n this.deps.wireMarker(circle, comment);\n\n this.deps.container.appendChild(circle);\n this.circles.set(String(comment.id), circle);\n this.updatePosition(comment, circle);\n\n // Size changes of the container are watched per comment (ResizeObserver\n // below); DOM mutations are watched once for the whole page by the\n // global observer in start() \u2014 a per-comment MutationObserver here\n // would fire N redundant callbacks per mutation batch on top of it.\n this.createResizeObserver(comment, circle);\n }\n\n /** The marker circle for a comment id, however the caller spells it. */\n circleOf(id) {\n return this.circles.get(String(id)) ?? null;\n }\n\n /** Removes one comment's marker and its observer. */\n remove(id) {\n this.cleanupResizeObserver(id);\n this.circles.get(String(id))?.remove();\n this.circles.delete(String(id));\n }\n\n /** Removes every marker and observer at once (bulk reset). */\n clear() {\n this.resizeObservers.forEach(({ observer }) => observer?.disconnect());\n this.resizeObservers.clear();\n this.circles.forEach((circle) => circle.remove());\n this.circles.clear();\n }\n\n /**\n * Validates and recalculates comment position based on container dimensions\n * @param {Object} comment - The comment object with position data\n * @param {HTMLElement} circle - The comment circle element\n * @returns {Object} - Validated position data\n */\n validateAndCalculatePosition(comment, circle) {\n if (!comment.container || !circle) return null;\n\n const containerRect = comment.container.getBoundingClientRect();\n const containerWidth = containerRect.width;\n const containerHeight = containerRect.height;\n\n // Zero size isn't an anomaly: it's what display:none (e.g. responsive\n // media queries) looks like. The caller hides the marker until the\n // element gets its size back.\n if (containerWidth <= 0 || containerHeight <= 0) {\n return null;\n }\n\n // Use simple relative positioning for consistent results\n const absoluteX = comment.relativeX * containerWidth;\n const absoluteY = comment.relativeY * containerHeight;\n\n const validatedX = clampToBox(absoluteX, containerWidth);\n const validatedY = clampToBox(absoluteY, containerHeight);\n\n // Recalculate relative position for future calculations\n const validatedRelativeX = validatedX / containerWidth;\n const validatedRelativeY = validatedY / containerHeight;\n\n return {\n absoluteX: validatedX,\n absoluteY: validatedY,\n relativeX: validatedRelativeX,\n relativeY: validatedRelativeY,\n containerWidth,\n containerHeight,\n containerLeft: containerRect.left,\n containerTop: containerRect.top,\n };\n }\n\n /**\n * The exact element the user clicked on can vanish (responsive\n * display:none) while its coarse anchor container stays visible. When we\n * have a live target \u2014 or can re-derive one from the serialized\n * targetSelector \u2014 the marker follows ITS visibility too.\n */\n _isAnchorTargetVisible(comment) {\n let target = comment.target;\n if ((!target || !target.isConnected) && comment.anchor?.targetSelector) {\n try {\n target = document.querySelector(comment.anchor.targetSelector);\n } catch {\n target = null;\n }\n comment.target = target || null;\n }\n if (!target || !target.isConnected) return true; // no signal \u2014 assume visible\n const rect = target.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n }\n\n /**\n * A marker floats above the whole page (own shadow host, high z-index),\n * so a host-page modal overlay can never cover it with CSS alone. Hit-test\n * the marker's point instead: if the topmost page element there is\n * unrelated to the comment's anchor (neither ancestor nor descendant),\n * something like a modal backdrop is covering it and the marker should\n * hide with it.\n */\n _isMarkerOccluded(comment, x, y) {\n if (typeof document.elementsFromPoint !== \"function\") return false;\n // Off-viewport points can't be hit-tested; the marker isn't visible\n // there anyway, so keep the current (visible) state.\n if (x < 0 || y < 0 || x >= window.innerWidth || y >= window.innerHeight) {\n return false;\n }\n const stack = document.elementsFromPoint(x, y);\n // Our own shadow host shows up first (the marker itself, toolbar, \u2026).\n const top = stack.find(\n (el) => el.tagName.toLowerCase() !== TAG_NAME.toLowerCase()\n );\n if (!top) return false;\n\n const target = comment.target?.isConnected ? comment.target : null;\n // The precise element the comment was left on (or its subtree /\n // ancestors) is what should be under the marker \u2014 never an occluder.\n if (target && (target.contains(top) || top.contains(target))) {\n return false;\n }\n\n const container = comment.container;\n if (!container?.isConnected) return false;\n // Something entirely unrelated to the anchor sits on top of it.\n if (!container.contains(top) && !top.contains(container)) return true;\n\n // `top` lives inside the anchor container \u2014 usually normal content of\n // the anchored subtree. But broad containers (body, page wrappers) also\n // contain the page's modals, so walk the chain up to the container: if\n // it crosses a modal-looking layer the anchor doesn't belong to, the\n // marker is covered after all.\n if (container.contains(top) && top !== container) {\n for (let el = top; el && el !== container; el = el.parentElement) {\n if (this._looksLikeModalLayer(el, target)) return true;\n }\n }\n return false;\n }\n\n /**\n * Heuristic for \"this element is a modal/backdrop layer\": explicit dialog\n * semantics, or a hit-testable fixed element covering most of the\n * viewport. Elements that contain the comment's own target are the layer\n * the comment lives in, never an occluder.\n */\n _looksLikeModalLayer(el, target) {\n if (target && el.contains(target)) return false;\n if (el.matches?.('dialog, [aria-modal=\"true\"], [role=\"dialog\"]')) {\n return true;\n }\n if (getComputedStyle(el).position !== \"fixed\") return false;\n const rect = el.getBoundingClientRect();\n return (\n rect.width >= window.innerWidth * 0.5 &&\n rect.height >= window.innerHeight * 0.5\n );\n }\n\n /**\n * Read half of a position update: layout reads only, no DOM writes, so a\n * batched pass can measure every marker before touching any style (an\n * interleaved read-write loop forces a fresh layout per marker).\n * @param {Object} comment\n * @param {HTMLElement} circle\n * @param {{ checkOcclusion?: boolean }} [options] when false, the pass\n * reuses the marker's previous occlusion verdict instead of hit-testing.\n * @returns {{ kind: \"resolved\" } | { kind: \"noop\" } | { kind: \"hidden\" }\n * | { kind: \"visible\", viewportX: number, viewportY: number,\n * relativeX: number, relativeY: number }}\n */\n _computeMarkerState(comment, circle, { checkOcclusion = true } = {}) {\n // Resolved comments have no on-page marker (RF09). This is not the\n // \"hidden\" state \u2014 the anchor is fine, the issue is just done.\n if (comment.status === \"resolved\") {\n return { kind: \"resolved\" };\n }\n\n let positionData = this.validateAndCalculatePosition(comment, circle);\n if (positionData && !this._isAnchorTargetVisible(comment)) {\n positionData = null;\n }\n if (!positionData) {\n // Anchor element currently invisible (zero-size container): hide the\n // marker; it comes back automatically when the observers fire again.\n return comment.container ? { kind: \"hidden\" } : { kind: \"noop\" };\n }\n\n // Offset so the circle's top-left tip (sharp corner) aligns with the stored position\n const circleRadius = MARKER_SIZE / 2;\n const viewportX =\n positionData.containerLeft + positionData.absoluteX + circleRadius;\n const viewportY =\n positionData.containerTop + positionData.absoluteY + circleRadius;\n\n // A host-page modal (or any unrelated overlay) covering the anchor also\n // hides the marker \u2014 it must not float above the modal's backdrop.\n if (checkOcclusion) {\n comment._occluded = this._isMarkerOccluded(comment, viewportX, viewportY);\n }\n if (comment._occluded) {\n return { kind: \"hidden\" };\n }\n\n return {\n kind: \"visible\",\n viewportX,\n viewportY,\n relativeX: positionData.relativeX,\n relativeY: positionData.relativeY,\n };\n }\n\n /**\n * Write half of a position update: styles and state only, no layout\n * reads.\n * @param {Object} comment\n * @param {HTMLElement} circle\n * @param {ReturnType<MarkerEngine[\"_computeMarkerState\"]>} state\n * @returns {boolean} true when the marker's hidden flag flipped \u2014 the\n * caller decides how to refresh the inbox (once per batch in the rAF\n * loop, immediately on direct calls).\n */\n _applyMarkerState(comment, circle, state) {\n if (state.kind === \"resolved\") {\n if (circle) circle.style.display = \"none\";\n return false;\n }\n if (state.kind === \"noop\") return false;\n\n const wasHidden = comment.hidden === true;\n if (state.kind === \"hidden\") {\n comment.hidden = true;\n circle.style.display = \"none\";\n // A marker that just went away must not leave its hover tooltip or\n // its open thread popover floating on the page.\n this.deps.onMarkerHidden(comment);\n return !wasHidden;\n }\n\n comment.hidden = false;\n circle.style.display = \"\";\n circle.style.left = `${state.viewportX}px`;\n circle.style.top = `${state.viewportY}px`;\n circle.style.transform = \"translate(-50%, -50%)\";\n circle.style.position = \"absolute\";\n\n comment.relativeX = state.relativeX;\n comment.relativeY = state.relativeY;\n return wasHidden;\n }\n\n /** One marker's position, refreshed now. */\n updatePosition(comment, circle = this.circles.get(String(comment.id))) {\n if (!circle) return;\n const state = this._computeMarkerState(comment, circle);\n const flipped = this._applyMarkerState(comment, circle, state);\n if (flipped) this.deps.onVisibilityFlip();\n }\n\n /**\n * One batched pass over every marker: measure everything, then write\n * everything, then refresh the inbox at most once \u2014 flipping N markers in\n * one frame used to rebuild the inbox N times from inside the loop.\n */\n _updateAllPositions() {\n const now = Date.now();\n const checkOcclusion =\n now - this._lastOcclusionPass >= OCCLUSION_INTERVAL_MS;\n if (checkOcclusion) {\n this._lastOcclusionPass = now;\n } else {\n // This pass reuses stale occlusion verdicts; make sure one more pass\n // runs after the burst settles so the end state is honest.\n this._armOcclusionTrailingPass();\n }\n\n const plans = [];\n for (const comment of this.deps.getComments()) {\n const circle = this.circles.get(String(comment.id));\n if (!circle) continue;\n plans.push([\n comment,\n circle,\n this._computeMarkerState(comment, circle, { checkOcclusion }),\n ]);\n }\n\n let anyFlipped = false;\n for (const [comment, circle, state] of plans) {\n if (this._applyMarkerState(comment, circle, state)) anyFlipped = true;\n }\n if (anyFlipped) this.deps.onVisibilityFlip();\n }\n\n _armOcclusionTrailingPass() {\n if (this._occlusionTrailingTimer) {\n clearTimeout(this._occlusionTrailingTimer);\n }\n this._occlusionTrailingTimer = setTimeout(() => {\n this._occlusionTrailingTimer = null;\n this.scheduleUpdate();\n }, OCCLUSION_INTERVAL_MS);\n }\n\n /** Coalesces any number of triggers into one rAF pass. */\n scheduleUpdate() {\n if (this._pendingRaf) return;\n this._pendingRaf = requestAnimationFrame(() => {\n this._pendingRaf = null;\n if (this.enabled) {\n this._updateAllPositions();\n }\n // Runs even with position validation off: the markers are placed in\n // viewport coordinates either way, so the popover has to follow.\n this.deps.onAfterPass();\n });\n }\n\n /**\n * Creates a ResizeObserver for a specific comment container\n * @param {Object} comment - The comment object\n * @param {HTMLElement} circle - The comment circle element\n */\n createResizeObserver(comment, circle) {\n if (!window.ResizeObserver) {\n console.warn(\n \"ResizeObserver not supported, position validation will be limited\"\n );\n return;\n }\n\n const observer = new ResizeObserver((entries) => {\n if (!this.enabled) return;\n\n for (const entry of entries) {\n // Only update if the container size actually changed\n if (entry.target === comment.container) {\n this.updatePosition(comment, circle);\n }\n }\n });\n\n // Start observing the container\n observer.observe(comment.container);\n\n // Store the observer for cleanup, keyed by String(id) like circles.\n this.resizeObservers.set(String(comment.id), {\n circle,\n observer,\n container: comment.container,\n });\n }\n\n cleanupResizeObserver(commentId) {\n // Keyed by String(id), exactly like the circles map: the caller may\n // hold the other spelling of a legacy numeric id, and a missed lookup\n // here leaks a live observer pointed at a detached circle.\n const key = String(commentId);\n if (this.resizeObservers.has(key)) {\n const { circle, observer } = this.resizeObservers.get(key);\n if (observer) {\n observer.disconnect();\n }\n if (circle && circle.parentNode) {\n circle.parentNode.removeChild(circle);\n }\n this.resizeObservers.delete(key);\n }\n }\n\n /**\n * Brings a comment's marker into view, centred vertically.\n *\n * Deliberately not `comment.container.scrollIntoView()`: the container is\n * the coarse anchor box (`section, div[class*=container|content]`), which\n * falls back to `<body>` whenever the commented element has no such\n * ancestor. Centring `<body>` lands halfway down the document \u2014 nowhere\n * near the marker, which is what made opening a comment from the inbox\n * jump to an unrelated section.\n *\n * @param {any} comment\n */\n scrollMarkerIntoView(comment) {\n const y = this._markerViewportY(comment);\n if (y == null) return;\n window.scrollTo({\n top: Math.max(0, window.scrollY + y - window.innerHeight / 2),\n });\n }\n\n /**\n * The marker's centre in viewport coordinates, derived from the anchor the\n * same way `updatePosition` derives it \u2014 through the same `clampToBox`, so\n * the two cannot disagree about where a marker sits.\n *\n * Deliberately not read off the rendered circle: the circle's coordinates\n * are only refreshed inside a rAF on scroll, so they are stale for any\n * caller that runs in the same tick as a scroll. The container's rect is\n * live, which makes this correct whenever it is asked.\n *\n * @param {any} comment\n * @returns {number | null} null when there is no anchor to resolve\n */\n _markerViewportY(comment) {\n const container = comment.container;\n if (!container?.isConnected) return null;\n const rect = container.getBoundingClientRect();\n if (rect.height <= 0) return null;\n\n const offsetY = clampToBox(comment.relativeY * rect.height, rect.height);\n return rect.top + offsetY + MARKER_SIZE / 2;\n }\n\n /** Cancels every scheduled pass, listener and observer. */\n destroy() {\n // A pass scheduled before teardown must not run against a destroyed\n // widget \u2014 cancel the pending frame and the trailing occlusion timer.\n if (this._pendingRaf) {\n cancelAnimationFrame(this._pendingRaf);\n this._pendingRaf = null;\n }\n if (this._occlusionTrailingTimer) {\n clearTimeout(this._occlusionTrailingTimer);\n this._occlusionTrailingTimer = null;\n }\n if (this._resizeHandler) {\n window.removeEventListener(\"resize\", this._resizeHandler);\n this._resizeHandler = null;\n }\n if (this._scrollHandler) {\n window.removeEventListener(\"scroll\", this._scrollHandler, {\n capture: true,\n });\n this._scrollHandler = null;\n }\n if (this._loadHandler) {\n window.removeEventListener(\"load\", this._loadHandler);\n this._loadHandler = null;\n }\n if (this._globalMutationObserver) {\n this._globalMutationObserver.disconnect();\n this._globalMutationObserver = null;\n }\n this.clear();\n }\n}\n", "// The audit trail as a disclosure in the inbox detail \u2014 closed by default,\n// with its open/closed state owned by the panel rather than the DOM, for the\n// same reason the context block's is: the detail is rebuilt on every\n// refresh(), and a flag living in the markup would fold itself back shut.\n\nimport { CLASSES } from \"./constants.js\";\nimport { formatTemplate, formatDuration } from \"./i18n.js\";\nimport {\n statusLabelOf,\n typeLabelOf,\n priorityLabelOf,\n} from \"./comment-actions.js\";\nimport { resolutionsOf } from \"./audit.js\";\n\nconst formatStamp = (iso, locale) =>\n new Intl.DateTimeFormat(locale, {\n month: \"short\",\n day: \"numeric\",\n hour: \"numeric\",\n minute: \"2-digit\",\n }).format(new Date(iso));\n\n// A move reads \"Status: Open \u2192 Resolved\" \u2014 the field name and both values\n// come from the dictionaries the pickers already use, so a state is never\n// called one thing in the picker and another in the trail.\nconst MOVES = {\n status: (strings) => [strings.statusLabel, (v) => statusLabelOf(v, strings)],\n type: (strings) => [strings.typeLabel, (v) => typeLabelOf(v, strings)],\n priority: (strings) => [\n strings.priorityLabel,\n (v) => priorityLabelOf(v, strings),\n ],\n};\n\nconst moveLabel = (field, entry, strings) => {\n const move = MOVES[field];\n if (!move) return \"\";\n const [name, valueOf] = move(strings);\n return `${name}: ${valueOf(entry.from ?? null)} \u2192 ${valueOf(entry.to ?? null)}`;\n};\n\nconst labelFor = (entry, strings) => {\n switch (entry.type) {\n case \"created\":\n return strings.auditCreated;\n case \"edited\":\n return strings.auditEdited;\n case \"status\":\n return moveLabel(\"status\", entry, strings);\n case \"classified\":\n // Tags are a list, so there is no two-value transition to render \u2014 the\n // change gets its own sentence instead of a malformed arrow.\n return entry.field === \"tags\"\n ? strings.auditTagsChanged\n : moveLabel(entry.field, entry, strings);\n default:\n return \"\";\n }\n};\n\nconst buildRow = (entry, strings, locale) => {\n const row = document.createElement(\"li\");\n row.className = CLASSES.AUDIT_ROW;\n\n const action = document.createElement(\"span\");\n action.className = CLASSES.AUDIT_ACTION;\n action.textContent = labelFor(entry, strings);\n\n // The display name, never the id: the id is identity, not copy \u2014 the rule\n // the reaction pills already follow.\n const actor = document.createElement(\"span\");\n actor.className = CLASSES.AUDIT_ACTOR;\n actor.textContent = entry.actor?.name || strings.anonymous;\n\n const time = document.createElement(\"time\");\n time.className = CLASSES.AUDIT_TIME;\n time.dateTime = entry.at;\n time.textContent = formatStamp(entry.at, locale);\n\n row.append(action, actor, time);\n return row;\n};\n\n/**\n * The resolutions a reopen superseded. Rendered only when one exists: a\n * comment resolved once already carries its elapsed time in the badge strip,\n * and repeating it here would be a third place for one fact.\n */\nconst buildResolutions = (comment, strings, locale) => {\n const superseded = resolutionsOf(comment).filter((r) => r.reopenedAt);\n if (superseded.length === 0) return null;\n\n const section = document.createElement(\"div\");\n section.className = CLASSES.AUDIT_RESOLUTIONS;\n section.dataset.auditResolutions = \"\";\n\n const heading = document.createElement(\"h4\");\n heading.className = CLASSES.AUDIT_HEADING;\n heading.textContent = strings.auditPreviousResolutions;\n section.appendChild(heading);\n\n const list = document.createElement(\"ul\");\n list.className = CLASSES.AUDIT_LIST;\n for (const resolution of superseded) {\n const item = document.createElement(\"li\");\n item.className = CLASSES.AUDIT_ROW;\n\n const action = document.createElement(\"span\");\n action.className = CLASSES.AUDIT_ACTION;\n action.textContent = formatTemplate(\n strings.auditResolvedInTemplate,\n formatDuration(resolution.ms, strings) || \"\u2014\"\n );\n\n const time = document.createElement(\"time\");\n time.className = CLASSES.AUDIT_TIME;\n time.dateTime = resolution.resolvedAt;\n time.textContent = formatStamp(resolution.resolvedAt, locale);\n\n item.append(action, time);\n list.appendChild(item);\n }\n section.appendChild(list);\n return section;\n};\n\n/**\n * @param {object} comment\n * @param {{\n * strings: Record<string, string>,\n * locale: string,\n * open: boolean,\n * onToggle: (open: boolean) => void,\n * }} deps\n * @returns {HTMLElement | null} null for a comment that predates the log, so\n * an older corpus shows nothing rather than an empty box\n */\nexport function createAuditTrail(comment, { strings, locale, open, onToggle }) {\n const history = comment?.history;\n if (!Array.isArray(history) || history.length === 0) return null;\n\n const wrapper = document.createElement(\"div\");\n wrapper.className = CLASSES.AUDIT_BLOCK;\n\n const toggle = document.createElement(\"button\");\n toggle.type = \"button\";\n toggle.className = CLASSES.AUDIT_TOGGLE;\n toggle.setAttribute(\"aria-expanded\", String(open));\n toggle.textContent = formatTemplate(\n strings.auditToggleTemplate,\n history.length\n );\n\n const body = document.createElement(\"div\");\n body.className = CLASSES.AUDIT_BODY;\n body.hidden = !open;\n\n const list = document.createElement(\"ul\");\n list.className = CLASSES.AUDIT_LIST;\n list.setAttribute(\"aria-label\", strings.auditTrailLabel);\n // Newest first: the question a trail answers is almost always \"what just\n // happened\", not \"how did this start\".\n for (const entry of [...history].reverse()) {\n list.appendChild(buildRow(entry, strings, locale));\n }\n body.appendChild(list);\n\n const resolutions = buildResolutions(comment, strings, locale);\n if (resolutions) body.appendChild(resolutions);\n\n toggle.addEventListener(\"click\", () => {\n const next = toggle.getAttribute(\"aria-expanded\") !== \"true\";\n toggle.setAttribute(\"aria-expanded\", String(next));\n body.hidden = !next;\n onToggle?.(next);\n });\n\n wrapper.append(toggle, body);\n return wrapper;\n}\n", "// Aggregate figures over a corpus of comments: counts by status, type and\n// priority, a temporal distribution, and the resolution times derived from\n// the audit log.\n//\n// Pure and synchronous. The corpus is already in memory and measured in tens\n// or hundreds, so nothing here is cached or incremental \u2014 recomputing on\n// every open is cheaper than keeping a second copy of the truth in sync.\n\nimport { STATUSES, COMMENT_TYPES, PRIORITIES } from \"./constants.js\";\nimport { currentResolutionMs, resolutionsOf } from \"./audit.js\";\n\n/** Comments carry `null` for a deliberately unset type or priority. */\nexport const UNSET = \"unset\";\n\nconst HOUR_MS = 3_600_000;\n\nconst countInto = (keys, extraKey) => {\n const out = {};\n for (const key of keys) out[key] = 0;\n if (extraKey) out[extraKey] = 0;\n return out;\n};\n\nconst median = (sorted) => {\n if (sorted.length === 0) return null;\n const middle = Math.floor(sorted.length / 2);\n return sorted.length % 2\n ? sorted[middle]\n : (sorted[middle - 1] + sorted[middle]) / 2;\n};\n\n/**\n * @param {import('./index.d.ts').SerializedComment[]} comments\n * @returns {import('./index.d.ts').CommentMetrics}\n */\nexport function computeMetrics(comments) {\n const list = Array.isArray(comments) ? comments : [];\n\n const byStatus = countInto(STATUSES);\n const byType = countInto(COMMENT_TYPES, UNSET);\n const byPriority = countInto(PRIORITIES, UNSET);\n const perDay = new Map();\n const durations = [];\n let reopenedCount = 0;\n\n for (const comment of list) {\n const status = STATUSES.includes(comment.status) ? comment.status : \"open\";\n byStatus[status]++;\n\n byType[COMMENT_TYPES.includes(comment.type) ? comment.type : UNSET]++;\n byPriority[\n PRIORITIES.includes(comment.priority) ? comment.priority : UNSET\n ]++;\n\n // Only the days that saw activity get a bucket. Filling the gaps would\n // put 365 empty bars between two comments a year apart, which is a chart\n // nobody can read \u2014 the axis labels say which days these are.\n const day = String(comment.createdAt || \"\").slice(0, 10);\n if (day) perDay.set(day, (perDay.get(day) || 0) + 1);\n\n const elapsed = currentResolutionMs(comment);\n if (elapsed !== null) durations.push(elapsed);\n if (resolutionsOf(comment).length > 1) reopenedCount++;\n }\n\n durations.sort((a, b) => a - b);\n const total = durations.reduce((sum, ms) => sum + ms, 0);\n\n return {\n total: list.length,\n // Built by walking STATUSES / COMMENT_TYPES / PRIORITIES, so every key the\n // public type promises is present \u2014 which the checker cannot see through\n // a loop over a string[].\n byStatus:\n /** @type {Record<import('./index.d.ts').CommentStatus, number>} */ (\n byStatus\n ),\n byType:\n /** @type {Record<import('./index.d.ts').CommentType | \"unset\", number>} */ (\n byType\n ),\n byPriority:\n /** @type {Record<import('./index.d.ts').CommentPriority | \"unset\", number>} */ (\n byPriority\n ),\n overTime: [...perDay.entries()]\n .sort(([a], [b]) => (a < b ? -1 : 1))\n .map(([date, count]) => ({ date, count })),\n resolution: {\n resolvedCount: durations.length,\n // Both, because they answer different questions: one comment left open\n // for a month drags the mean somewhere no real comment lives, and the\n // median says what the team's typical turnaround actually is.\n averageMs: durations.length ? total / durations.length : null,\n medianMs: median(durations),\n reopenedCount,\n },\n };\n}\n\n/** Exported for the report, which prints hours rather than raw milliseconds. */\nexport const toHours = (ms) =>\n ms === null || ms === undefined ? null : Math.round((ms / HOUR_MS) * 10) / 10;\n", "// The metrics dashboard, rendered inside the inbox panel.\n//\n// Every chart here is hand-drawn: horizontal bars are two divs and a width,\n// and the daily distribution is a handful of <rect>s. The lightest charting\n// library measured 22.66 KB gzip against ~13 KB of headroom, and none of them\n// would have satisfied this repo's own rule anyway \u2014 a <canvas> carries no\n// text, and no figure here is allowed to communicate through length or colour\n// alone (WCAG 1.4.1). Each bar states its count beside it.\n\nimport {\n CLASSES,\n STATUSES,\n COMMENT_TYPES,\n PRIORITIES,\n STATUS_COLORS,\n TYPE_COLORS,\n PRIORITY_COLORS,\n} from \"./constants.js\";\nimport { formatDuration } from \"./i18n.js\";\nimport {\n statusLabelOf,\n typeLabelOf,\n priorityLabelOf,\n} from \"./comment-actions.js\";\nimport { UNSET } from \"./metrics.js\";\n\n// The bucket for comments left deliberately unclassified. The pickers paint\n// its dot `transparent` \u2014 there is nothing to show \u2014 but a bar still has a\n// count to draw, so it takes the neutral the bars used before they were\n// coloured at all rather than vanishing.\nconst UNSET_COLOR = \"rgba(255,255,255,0.42)\";\n\nconst CHART_WIDTH = 300;\nconst CHART_HEIGHT = 96;\nconst SVG_NS = \"http://www.w3.org/2000/svg\";\n\nconst el = (tag, className, text) => {\n const node = document.createElement(tag);\n if (className) node.className = className;\n if (text !== undefined) node.textContent = String(text);\n return node;\n};\n\nconst svgEl = (tag, attrs = {}) => {\n const node = document.createElementNS(SVG_NS, tag);\n for (const [name, value] of Object.entries(attrs)) {\n node.setAttribute(name, String(value));\n }\n return node;\n};\n\nconst tile = (label, value) => {\n const box = el(\"div\", CLASSES.METRICS_TILE);\n box.appendChild(el(\"span\", CLASSES.METRICS_TILE_VALUE, value));\n box.appendChild(el(\"span\", CLASSES.METRICS_TILE_LABEL, label));\n return box;\n};\n\n/**\n * One dimension as labelled horizontal bars. Bars are scaled against the\n * busiest bucket rather than the total: against the total, a corpus spread\n * evenly across four statuses would draw four slivers.\n *\n * Each bar takes the colour its own picker already uses for that value, so a\n * chip and its bar are recognisably the same thing. The colour is\n * reinforcement, never the signal: the row states its label and its count as\n * text either side of the bar, so nothing here is lost to a reader who cannot\n * tell the hues apart (WCAG 1.4.1).\n *\n * @param {Array<{ label: string, count: number, color: string }>} entries\n */\nconst barGroup = (name, heading, entries) => {\n const group = el(\"div\", CLASSES.METRICS_GROUP);\n group.dataset.metricsGroup = name;\n group.appendChild(el(\"h4\", CLASSES.METRICS_HEADING, heading));\n\n const max = Math.max(1, ...entries.map((entry) => entry.count));\n for (const { label, count, color } of entries) {\n const row = el(\"div\", CLASSES.METRICS_ROW);\n row.dataset.metricsRow = \"\";\n\n row.appendChild(el(\"span\", CLASSES.METRICS_ROW_LABEL, label));\n\n const track = el(\"div\", CLASSES.METRICS_TRACK);\n const bar = el(\"div\", CLASSES.METRICS_BAR);\n bar.dataset.metricsBar = \"\";\n bar.style.width = `${Math.round((count / max) * 100)}%`;\n bar.style.background = color;\n track.appendChild(bar);\n row.appendChild(track);\n\n row.appendChild(el(\"span\", CLASSES.METRICS_ROW_COUNT, count));\n group.appendChild(row);\n }\n return group;\n};\n\n/**\n * The daily distribution. An <svg role=\"img\"> with an aria-label carrying the\n * summary, and a <title> per column so a pointer \u2014 and an accessibility tree \u2014\n * can read each day without the chart having to become a table.\n */\nconst dailyChart = (overTime, strings) => {\n const group = el(\"div\", CLASSES.METRICS_GROUP);\n group.dataset.metricsGroup = \"overTime\";\n group.appendChild(el(\"h4\", CLASSES.METRICS_HEADING, strings.metricsOverTime));\n\n const max = Math.max(1, ...overTime.map(({ count }) => count));\n const svg = svgEl(\"svg\", {\n class: CLASSES.METRICS_CHART,\n viewBox: `0 0 ${CHART_WIDTH} ${CHART_HEIGHT}`,\n preserveAspectRatio: \"none\",\n role: \"img\",\n \"aria-label\": `${strings.metricsOverTime}: ${overTime\n .map(({ date, count }) => `${date} ${count}`)\n .join(\", \")}`,\n });\n\n const slot = CHART_WIDTH / overTime.length;\n const barWidth = Math.max(2, Math.min(slot - 3, 28));\n overTime.forEach(({ date, count }, index) => {\n const height = Math.max(2, (count / max) * (CHART_HEIGHT - 6));\n const rect = svgEl(\"rect\", {\n x: index * slot + (slot - barWidth) / 2,\n y: CHART_HEIGHT - height,\n width: barWidth,\n height,\n rx: 2,\n });\n rect.appendChild(\n Object.assign(svgEl(\"title\"), { textContent: `${date}: ${count}` })\n );\n svg.appendChild(rect);\n });\n group.appendChild(svg);\n\n // Only the ends are labelled: a tick per day turns into overlapping text\n // the moment a corpus spans more than a fortnight.\n const axis = el(\"div\", CLASSES.METRICS_AXIS);\n axis.appendChild(el(\"span\", null, overTime[0].date));\n if (overTime.length > 1) {\n axis.appendChild(el(\"span\", null, overTime[overTime.length - 1].date));\n }\n group.appendChild(axis);\n\n return group;\n};\n\nconst exportBar = (strings, handlers) => {\n const bar = el(\"div\", CLASSES.METRICS_EXPORTS);\n bar.setAttribute(\"aria-label\", strings.metricsExportLabel);\n\n const button = (key, label, onClick) => {\n const btn = el(\"button\", CLASSES.METRICS_EXPORT_BTN, label);\n btn.type = \"button\";\n btn.dataset.export = key;\n btn.addEventListener(\"click\", onClick);\n return btn;\n };\n\n bar.appendChild(\n button(\"comments\", strings.metricsExportComments, handlers.onExportComments)\n );\n bar.appendChild(\n button(\"metrics\", strings.metricsExportMetrics, handlers.onExportMetrics)\n );\n bar.appendChild(button(\"print\", strings.metricsPrint, handlers.onPrint));\n return bar;\n};\n\n/**\n * @param {import('./index.d.ts').CommentMetrics} metrics\n * @param {{\n * strings: ReturnType<typeof import('./i18n.js').getStrings>,\n * locale: string,\n * onExportComments: () => void,\n * onExportMetrics: () => void,\n * onPrint: () => void,\n * }} deps\n * @returns {HTMLElement}\n */\nexport function createMetricsView(metrics, deps) {\n const { strings } = deps;\n const view = el(\"div\", CLASSES.METRICS_VIEW);\n\n if (metrics.total === 0) {\n // Nothing to export either: three buttons that would hand back an empty\n // file are worse than no buttons.\n view.appendChild(el(\"p\", CLASSES.METRICS_EMPTY, strings.metricsEmpty));\n return view;\n }\n\n const duration = (ms) => (ms === null ? \"\u2014\" : formatDuration(ms, strings));\n\n const tiles = el(\"div\", CLASSES.METRICS_TILES);\n tiles.appendChild(tile(strings.metricsTotal, metrics.total));\n tiles.appendChild(\n tile(strings.statusResolved, metrics.resolution.resolvedCount)\n );\n tiles.appendChild(\n tile(strings.metricsReopened, metrics.resolution.reopenedCount)\n );\n tiles.appendChild(\n tile(\n strings.metricsAverageResolution,\n duration(metrics.resolution.averageMs)\n )\n );\n tiles.appendChild(\n tile(strings.metricsMedianResolution, duration(metrics.resolution.medianMs))\n );\n view.appendChild(tiles);\n\n view.appendChild(\n barGroup(\n \"status\",\n strings.metricsByStatus,\n STATUSES.map((key) => ({\n label: statusLabelOf(key, strings),\n count: metrics.byStatus[key],\n color: STATUS_COLORS[key],\n }))\n )\n );\n view.appendChild(\n barGroup(\"type\", strings.metricsByType, [\n ...COMMENT_TYPES.map((key) => ({\n label: typeLabelOf(key, strings),\n count: metrics.byType[key],\n color: TYPE_COLORS[key],\n })),\n {\n label: strings.unset,\n count: metrics.byType[UNSET],\n color: UNSET_COLOR,\n },\n ])\n );\n view.appendChild(\n barGroup(\"priority\", strings.metricsByPriority, [\n ...PRIORITIES.map((key) => ({\n label: priorityLabelOf(key, strings),\n count: metrics.byPriority[key],\n color: PRIORITY_COLORS[key],\n })),\n {\n label: strings.unset,\n count: metrics.byPriority[UNSET],\n color: UNSET_COLOR,\n },\n ])\n );\n\n if (metrics.overTime.length) {\n view.appendChild(dailyChart(metrics.overTime, strings));\n }\n\n view.appendChild(exportBar(strings, deps));\n return view;\n}\n", "// Right-side inbox sidebar: a filterable list of every comment and a detail\n// view with the full thread. Pure view layer \u2014 all data mutations flow back\n// to CommentOverlay through the callbacks contract passed to the\n// constructor, so this module never touches storage or the page markers.\n\nimport { CLASSES, COMMENT_TYPES, PRIORITIES, STATUSES } from \"./constants.js\";\nimport { buildAgentContext } from \"./agent-context.js\";\nimport { attachMenuToggle } from \"./menus.js\";\nimport { createContextBlock } from \"./context-block.js\";\nimport { createAuditTrail } from \"./audit-timeline.js\";\nimport { createMetricsView } from \"./metrics-view.js\";\nimport { computeMetrics } from \"./metrics.js\";\nimport {\n createCommentActions,\n copyToClipboard,\n statusLabelOf,\n typeLabelOf,\n priorityLabelOf,\n} from \"./comment-actions.js\";\nimport {\n CARET_ICON_SVG,\n circleSelector,\n createMetaElement,\n renderScreenshotsPreview,\n wireScreenshotInput,\n wireScreenshotLightbox,\n createScreenshotsDisplay,\n createInputArea,\n createReplyElement,\n createBadgeRow,\n getShortcutText,\n} from \"./components.js\";\nimport { sameId } from \"./id.js\";\nimport { createReactionsUi, reactionEntriesOf } from \"./reactions.js\";\nimport { createInlineEditor, confirmDiscard } from \"./inline-editor.js\";\nimport { buildCommentLink } from \"./link.js\";\n\nconst CHEVRON_LEFT_SVG = `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"15 18 9 12 15 6\"/></svg>`;\nconst ARROW_UP_SVG = `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"18 15 12 9 6 15\"/></svg>`;\nconst ARROW_DOWN_SVG = `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"6 9 12 15 18 9\"/></svg>`;\n\nexport class InboxView {\n /**\n * @param {Object} deps\n * @param {ShadowRoot} deps.shadowRoot\n * @param {Object} deps.strings\n * @param {string} deps.locale\n * @param {string} deps.currentPage\n * @param {() => Array<Object>} deps.getComments\n * @param {{ shortcutKey?: string, shortcutModifier?: string, linkParam?: string }} [deps.options]\n * @param {{ onOpenDetailScroll: Function, onOpenDetail?: Function, onTransformScreenshot?: Function, onReply: Function, onDelete: Function, onDeleteReply: Function, onEditComment: Function, onEditReply: Function, onSetStatus: Function, onSetType: Function, onSetPriority: Function, onNavigateToPage: Function, onShowLightbox: Function, onActivateCommentMode: Function, onClose: Function, actorKey: () => string, can: (action: import(\"./index.d.ts\").PermissionAction, target: import(\"./index.d.ts\").PermissionTarget) => boolean, onToggleCommentReaction: Function, onToggleReplyReaction: Function, onExportComments: Function, onExportMetrics: Function, onPrintReport: Function }} deps.callbacks\n */\n constructor({\n shadowRoot,\n strings,\n locale,\n currentPage,\n getComments,\n callbacks,\n options = {},\n }) {\n this.shadowRoot = shadowRoot;\n this.strings = strings;\n this.locale = locale;\n this.currentPage = currentPage;\n this.getComments = getComments;\n this.callbacks = callbacks;\n // Only the shortcut config is read, and only to teach the chord in the\n // empty state.\n this.options = options;\n this.pageFilter = \"page\"; // \"all\" | \"page\"\n this.statusFilter = \"all\"; // \"all\" | STATUSES\n this.typeFilter = \"all\"; // \"all\" | COMMENT_TYPES\n this.priorityFilter = \"all\"; // \"all\" | PRIORITIES\n this.detailId = null;\n /**\n * Whether the detail view's context disclosure is open. State rather than\n * DOM for the same reason as the editor below \u2014 the detail is rebuilt on\n * every refresh \u2014 and one flag for the panel rather than one per comment,\n * so folding it away stays folded while stepping through comments with\n * prev/next.\n */\n this.contextExpanded = true;\n /**\n * Whether the detail view's audit trail is open. Closed on arrival,\n * unlike the context block: the trail answers a question you go looking\n * for, while the context is what you came to read. State for the same\n * reason as its sibling \u2014 the detail is rebuilt on every refresh.\n */\n this.auditExpanded = false;\n /**\n * Whether the panel is showing the metrics dashboard instead of the list.\n * A mode rather than a second panel: the sidebar is already where every\n * comment is looked at, and the figures are about those comments.\n */\n this.showMetrics = false;\n /**\n * The one open editor, as state rather than DOM.\n *\n * This panel re-renders from ten places, and the overlay refreshes it\n * from seven more. A draft living only in a textarea would be wiped by\n * any of them \u2014 changing a comment's priority mid-sentence would eat the\n * sentence. Keeping it here means every one of those rebuilds is\n * harmless, and leaves only the deliberate exits to ask about.\n * @type {{ commentId: any, replyId: any | null, draft: string } | null}\n */\n this.editing = null;\n /** @type {string | null} */\n this.notice = null;\n /**\n * Keyed card cache: String(id) \u2192 the live comment object, the\n * fingerprint of what its card renders, and the card node itself. A\n * refresh reuses a node whose comment and fingerprint both still match\n * \u2014 which is what keeps thumbnails decoded and the list's scroll\n * position intact across the many refreshes this panel receives.\n * @type {Map<string, { comment: any, fingerprint: string, card: HTMLElement }>}\n */\n this._cardBindings = new Map();\n /** @type {HTMLElement | null} */\n this.el = null;\n /** @type {HTMLElement | null} */\n this._highlightedEl = null;\n /** @type {HTMLElement | null} */\n this._activeMarkerEl = null;\n /**\n * Built on first use by _reactionsUi().\n * @type {import(\"./reactions.js\").ReactionsUi | null}\n */\n this._reactions = null;\n }\n\n isOpen() {\n return Boolean(this.el);\n }\n\n open() {\n if (this.el) return;\n this.el = document.createElement(\"div\");\n this.el.className = CLASSES.INBOX_PANEL;\n this.el.setAttribute(\"role\", \"dialog\");\n this.el.setAttribute(\"aria-label\", this.strings.inboxAriaLabel);\n // Announced as a dialog, so keyboard focus has to arrive with it \u2014\n // otherwise the user tabs across the whole page to reach the panel.\n this.el.setAttribute(\"tabindex\", \"-1\");\n this.shadowRoot.appendChild(this.el);\n this.render();\n this.el.focus();\n }\n\n close() {\n this._clearHighlight();\n this._setActiveMarker(null);\n this.el?.remove();\n this.el = null;\n this._cardBindings.clear();\n this.detailId = null;\n // Every route to here already went through releaseEditor(), so anything\n // still sitting in the draft has been answered for.\n this.editing = null;\n this.notice = null;\n }\n\n refresh() {\n if (this.el) this.render();\n }\n\n /** Back to the default view: current page, no status/type/priority. */\n _resetFilters() {\n this.pageFilter = \"page\";\n this.statusFilter = \"all\";\n this.typeFilter = \"all\";\n this.priorityFilter = \"all\";\n this.render();\n }\n\n /**\n * A line at the top of the list. Exists for one case: someone followed a\n * \"Copy link\" URL to a comment this page cannot show them. It stays until\n * the comment arrives (the overlay retries on every loadComments) or the\n * user navigates away from it.\n * @param {string} text\n */\n showNotice(text) {\n this.notice = text;\n this.refresh();\n }\n\n clearNotice() {\n if (!this.notice) return;\n this.notice = null;\n this.refresh();\n }\n\n /** True while an editor holds text the user has not saved. */\n isDirty() {\n if (!this.editing) return false;\n return this.editing.draft.trim() !== this._editingOriginalText().trim();\n }\n\n _editingOriginalText() {\n if (!this.editing) return \"\";\n const comment = this.getComments().find((c) =>\n sameId(c.id, this.editing.commentId)\n );\n if (!comment) return \"\";\n if (this.editing.replyId == null) return comment.text || \"\";\n const reply = (comment.replies || []).find((r) =>\n sameId(r.id, this.editing.replyId)\n );\n return reply?.text || \"\";\n }\n\n /**\n * Every path that would take the editor off screen funnels through here,\n * so the question is asked once and in one place instead of at each of the\n * exits (Cancel, Escape, the \u22EF of another comment, the close button, the\n * prev/next arrows, Back).\n * @returns {Promise<boolean>} true when the caller may proceed\n */\n async releaseEditor() {\n if (!this.editing) return true;\n if (this.isDirty()) {\n const host = /** @type {any} */ (this.el || this.shadowRoot);\n if (!(await confirmDiscard(host, this.strings))) return false;\n }\n this.editing = null;\n return true;\n }\n\n /**\n * The handlers every editor in this panel shares. Split out because the\n * comment body and a reply body are built by different components but must\n * behave identically \u2014 a draft that saved from one place and discarded\n * from the other would be two features wearing one look.\n */\n _editorHandlers() {\n return {\n draft: this.editing.draft,\n onInput: (text) => {\n this.editing.draft = text;\n },\n onSave: (text) => {\n const { commentId, replyId } = this.editing;\n if (replyId == null) this.callbacks.onEditComment(commentId, text);\n else this.callbacks.onEditReply(commentId, replyId, text);\n this.editing = null;\n this.render();\n },\n onCancel: async () => {\n if (await this.releaseEditor()) this.render();\n },\n };\n }\n\n _buildEditor() {\n const handlers = this._editorHandlers();\n return createInlineEditor({\n value: handlers.draft,\n strings: this.strings,\n onInput: handlers.onInput,\n onSave: handlers.onSave,\n onCancel: handlers.onCancel,\n });\n }\n\n /** Opens the editor on a comment body, or on one of its replies. */\n async startEditing(commentId, replyId = null) {\n if (!(await this.releaseEditor())) return;\n this.detailId = commentId;\n this.editing = { commentId, replyId, draft: \"\" };\n this.editing.draft = this._editingOriginalText();\n this.render();\n }\n\n /**\n * The on-page marker for a comment, when there is one to decorate.\n * Resolved, orphaned and hidden comments render no circle at all.\n * @param {any} comment\n * @returns {HTMLElement | null}\n */\n _markerFor(comment) {\n if (\n comment.anchorState !== \"anchored\" ||\n comment.hidden ||\n comment.status === \"resolved\"\n ) {\n return null;\n }\n return /** @type {any} */ (\n this.shadowRoot.querySelector(circleSelector(comment.id))\n );\n }\n\n _highlight(comment) {\n this._clearHighlight();\n const circle = this._markerFor(comment);\n if (!circle) return;\n circle.classList.add(CLASSES.HIGHLIGHT);\n this._highlightedEl = circle;\n }\n\n _clearHighlight() {\n this._highlightedEl?.classList.remove(CLASSES.HIGHLIGHT);\n this._highlightedEl = null;\n }\n\n /**\n * Opening a comment's detail selects it just as clicking its marker does,\n * so the marker gets the same active state the thread popover gives it.\n * Passing null clears it \u2014 the list view has nothing selected.\n * @param {any} comment\n */\n _setActiveMarker(comment) {\n this._activeMarkerEl?.classList.remove(CLASSES.CIRCLE_ACTIVE);\n this._activeMarkerEl = null;\n if (!comment) return;\n const circle = this._markerFor(comment);\n if (!circle) return;\n circle.classList.add(CLASSES.CIRCLE_ACTIVE);\n this._activeMarkerEl = circle;\n }\n\n filteredComments() {\n let comments = this.getComments();\n if (this.pageFilter === \"page\") {\n comments = comments.filter(\n (comment) => comment.page === this.currentPage\n );\n }\n if (this.statusFilter !== \"all\") {\n // `open` is the implicit default: comments saved before RF09 have no\n // status at all and must still match the \"open\" chip.\n comments = comments.filter(\n (comment) => (comment.status || \"open\") === this.statusFilter\n );\n }\n if (this.typeFilter !== \"all\") {\n comments = comments.filter((comment) => comment.type === this.typeFilter);\n }\n if (this.priorityFilter !== \"all\") {\n comments = comments.filter(\n (comment) => comment.priority === this.priorityFilter\n );\n }\n // Resolved sink to the bottom; both partitions keep their original order.\n return [\n ...comments.filter((comment) => comment.status !== \"resolved\"),\n ...comments.filter((comment) => comment.status === \"resolved\"),\n ];\n }\n\n render() {\n if (!this.el) return;\n this._clearHighlight();\n const comments = this.filteredComments();\n const detail =\n this.detailId != null\n ? comments.find((comment) => sameId(comment.id, this.detailId))\n : null;\n // Set before rendering so a detail reached by any route \u2014 a card click,\n // the prev/next nav, the cross-page handoff \u2014 marks its marker.\n this._setActiveMarker(detail);\n if (this.showMetrics) {\n this._cardBindings.clear();\n this.el.innerHTML = \"\";\n this._renderMetrics(comments);\n return;\n }\n if (detail) {\n // The detail shows one comment: a full rebuild is cheap and keeps the\n // editing and reply wiring simple. Leaving the list view drops its\n // keyed state; the skeleton is rebuilt on the way back.\n this._cardBindings.clear();\n this.el.innerHTML = \"\";\n this._renderDetail(detail, comments);\n } else {\n this.detailId = null;\n this._renderList(comments);\n }\n }\n\n /**\n * Opens the panel (if needed) directly on a comment's detail. Used by\n * the overlay for the cross-page handoff on startup.\n * @param {import('./index.d.ts').CommentId} id\n */\n openDetail(id) {\n if (!this.el) this.open();\n const comment = this.getComments().find((c) => sameId(c.id, id));\n if (comment) this._openDetail(comment);\n }\n\n _openDetail(comment) {\n this.detailId = comment.id;\n if (comment.anchorState === \"anchored\" && !comment.hidden) {\n this.callbacks.onOpenDetailScroll(comment);\n }\n this.render();\n // Reported after the render, so a host acting on it sees a settled\n // panel. Only genuine opens reach here \u2014 refresh() and render() read\n // `detailId` rather than going through this.\n this.callbacks.onOpenDetail?.(comment);\n }\n\n _closeButton() {\n const btn = document.createElement(\"button\");\n btn.type = \"button\";\n btn.className = CLASSES.INBOX_CLOSE;\n btn.setAttribute(\"aria-label\", this.strings.close);\n btn.innerHTML = \"×\";\n // `this.editing &&` short-circuits before the await, so with no editor\n // open this handler stays synchronous \u2014 closing the panel must not\n // become a microtask later just because editing exists as a feature.\n btn.addEventListener(\"click\", async () => {\n if (this.editing && !(await this.releaseEditor())) return;\n this.callbacks.onClose();\n });\n return btn;\n }\n\n _metricsButton() {\n const btn = document.createElement(\"button\");\n btn.type = \"button\";\n btn.className = CLASSES.INBOX_METRICS_BTN;\n btn.textContent = this.strings.metricsOpen;\n btn.setAttribute(\"aria-label\", this.strings.metricsTitle);\n btn.addEventListener(\"click\", async () => {\n if (this.editing && !(await this.releaseEditor())) return;\n this.showMetrics = true;\n this.detailId = null;\n this.render();\n });\n return btn;\n }\n\n /**\n * The dashboard measures the comments the panel is currently showing, not\n * the whole corpus: the filter summary sits right above it, so the figures\n * answer \"what am I looking at\". A host wanting the unfiltered aggregate\n * calls `overlay.getMetrics()`.\n */\n _renderMetrics(comments) {\n const header = document.createElement(\"div\");\n header.className = CLASSES.INBOX_DETAIL_HEADER;\n\n const backBtn = document.createElement(\"button\");\n backBtn.type = \"button\";\n backBtn.className = CLASSES.INBOX_BACK;\n backBtn.innerHTML = `${CHEVRON_LEFT_SVG}<span>${this.strings.back}</span>`;\n backBtn.addEventListener(\"click\", () => {\n this.showMetrics = false;\n this.render();\n });\n header.appendChild(backBtn);\n\n const nav = document.createElement(\"div\");\n nav.className = CLASSES.INBOX_CARD_ACTIONS;\n nav.appendChild(this._closeButton());\n header.appendChild(nav);\n this.el.appendChild(header);\n\n const scope = this._filterSummaryLabel();\n this.el.appendChild(\n createMetricsView(computeMetrics(comments), {\n strings: this.strings,\n locale: this.locale,\n onExportComments: () => this.callbacks.onExportComments(comments),\n onExportMetrics: () => this.callbacks.onExportMetrics(comments),\n onPrint: () => this.callbacks.onPrintReport(comments, scope),\n })\n );\n }\n\n _renderList(comments) {\n // Persistent skeleton: the header and the scrolling list are built once\n // and survive every refresh. Replacing the list wholesale (the old\n // innerHTML = \"\" render) reset its scroll position and re-decoded every\n // thumbnail whenever anything anywhere changed.\n let list = [...this.el.children].find((el) =>\n el.classList.contains(CLASSES.INBOX_LIST)\n );\n if (!list) {\n this.el.innerHTML = \"\";\n this._cardBindings.clear();\n const header = document.createElement(\"div\");\n header.className = CLASSES.INBOX_HEADER;\n this.el.appendChild(header);\n list = document.createElement(\"div\");\n list.className = CLASSES.INBOX_LIST;\n this.el.appendChild(list);\n }\n\n // The header is label-driven (the filter summary changes with every\n // selection) and holds no scroll or image state \u2014 rebuilt each pass.\n const header = [...this.el.children].find((el) =>\n el.classList.contains(CLASSES.INBOX_HEADER)\n );\n // One cluster, not three loose children: under the header's\n // `space-between`, a third child lands adrift in the middle instead of\n // beside the control it belongs with \u2014 the same scattering the detail\n // header already had to group its way out of.\n const actions = document.createElement(\"div\");\n actions.className = CLASSES.INBOX_HEADER_ACTIONS;\n actions.append(this._metricsButton(), this._closeButton());\n header.replaceChildren(this._buildFilter(), actions);\n\n this._reconcileCards(list, comments);\n }\n\n /**\n * Everything a list card renders, captured as a comparable string. An\n * equal fingerprint for the same live comment object means the existing\n * node can be reused as-is \u2014 listeners, decoded thumbnails and all. The\n * object identity check matters because loadComments REPLACES comment\n * objects: a reused card whose closures held the stale object would\n * mutate a comment the overlay no longer owns.\n */\n /**\n * The panel's reaction UI, built once so a toggle in the detail view also\n * repaints the list card's pill row when that card is still on screen.\n *\n * The parent comment of a reply is looked up rather than captured: one UI\n * then serves the list, the detail and every reply row in it, and the rows\n * stay registered against the same targets across refreshes.\n */\n _reactionsUi() {\n if (!this._reactions) {\n this._reactions = createReactionsUi({\n actorKey: () => this.callbacks.actorKey(),\n strings: this.strings,\n onToggle: (target, emoji) => {\n const parent = this.getComments().find((comment) =>\n (comment.replies || []).includes(target)\n );\n if (parent) {\n this.callbacks.onToggleReplyReaction(parent.id, target.id, emoji);\n } else {\n this.callbacks.onToggleCommentReaction(target.id, emoji);\n }\n },\n });\n }\n return this._reactions;\n }\n\n _cardFingerprint(comment) {\n return JSON.stringify([\n comment.text,\n comment.editedAt ?? null,\n comment.status ?? \"open\",\n comment.type ?? null,\n comment.priority ?? null,\n comment.tags ?? [],\n comment.resolvedAt ?? null,\n // The resolution badge is derived from the log, so a card whose log\n // grew has to repaint. Length plus the last stamp rather than the whole\n // array: appending is the only thing that happens to it.\n comment.history?.length ?? 0,\n comment.history?.at(-1)?.at ?? null,\n comment.anchorState,\n comment.hidden === true,\n comment.page,\n comment.screenshots?.length ?? 0,\n // Counts, not actor keys: what the card renders is the pill and its\n // number. Without this entry a card whose reactions changed keeps its\n // cached node and the counts freeze on screen.\n reactionEntriesOf(comment).map(({ emoji, authors }) => [\n emoji,\n authors.length,\n ]),\n ]);\n }\n\n _reconcileCards(list, comments) {\n // The notice and the empty state are stateless one-offs \u2014 always\n // rebuilt, and removed up front so they never count as \"out of place\"\n // cards during the ordering walk below.\n for (const el of [...list.children]) {\n if (\n el.classList.contains(CLASSES.INBOX_NOTICE) ||\n el.classList.contains(CLASSES.INBOX_EMPTY)\n ) {\n el.remove();\n }\n }\n\n const desired = [];\n if (this.notice) {\n const notice = document.createElement(\"div\");\n notice.className = CLASSES.INBOX_NOTICE;\n notice.setAttribute(\"role\", \"status\");\n notice.textContent = this.notice;\n desired.push(notice);\n }\n\n const seen = new Set();\n if (comments.length === 0) {\n desired.push(this._buildEmptyState());\n } else {\n for (const comment of comments) {\n const key = String(comment.id);\n const fingerprint = this._cardFingerprint(comment);\n const binding = this._cardBindings.get(key);\n let card;\n if (\n binding &&\n binding.comment === comment &&\n binding.fingerprint === fingerprint\n ) {\n card = binding.card;\n } else {\n card = this._buildCard(comment, { interactive: true });\n this._cardBindings.set(key, { comment, fingerprint, card });\n }\n seen.add(key);\n desired.push(card);\n }\n }\n\n for (const key of [...this._cardBindings.keys()]) {\n if (!seen.has(key)) this._cardBindings.delete(key);\n }\n\n // Minimal-move ordering: only nodes that are out of place are touched,\n // so an untouched tail keeps its position \u2014 and the container, which is\n // never replaced, keeps its scroll.\n desired.forEach((node, index) => {\n if (list.children[index] !== node) {\n list.insertBefore(node, list.children[index] ?? null);\n }\n });\n while (list.children.length > desired.length) {\n list.lastElementChild.remove();\n }\n }\n\n /**\n * Two different nothings, and telling a user the wrong one wastes their\n * time: an inbox with no comments at all needs teaching (what the shortcut\n * is, how to place the first one), while an inbox whose filters happen to\n * exclude everything needs the filters relaxed. Offering \"turn on comment\n * mode\" to someone who already has twenty comments would be nonsense.\n */\n _buildEmptyState() {\n const empty = document.createElement(\"div\");\n empty.className = CLASSES.INBOX_EMPTY;\n\n // An outline of the marker the user is about to place, not a generic\n // placeholder \u2014 same teardrop silhouette the circles use.\n const icon = document.createElement(\"div\");\n icon.className = CLASSES.INBOX_EMPTY_ICON;\n icon.setAttribute(\"aria-hidden\", \"true\");\n empty.appendChild(icon);\n\n const hasAnyComment = this.getComments().length > 0;\n\n const title = document.createElement(\"div\");\n title.className = CLASSES.INBOX_EMPTY_TITLE;\n title.textContent = hasAnyComment\n ? this.strings.inboxNoMatches\n : this.strings.inboxEmptyTitle;\n empty.appendChild(title);\n\n if (hasAnyComment) {\n const clear = document.createElement(\"button\");\n clear.type = \"button\";\n clear.className = CLASSES.INBOX_EMPTY_ACTION;\n clear.textContent = this.strings.filterClear;\n clear.addEventListener(\"click\", () => {\n this._resetFilters();\n });\n empty.appendChild(clear);\n return empty;\n }\n\n const text = document.createElement(\"div\");\n text.className = CLASSES.INBOX_EMPTY_TEXT;\n // Split on the placeholder so the chord can be a real <kbd> rather than\n // bare text, without taking the sentence apart in the locale files.\n const [before, after] = String(this.strings.inboxEmptyHintTemplate).split(\n \"{n}\"\n );\n const kbd = document.createElement(\"kbd\");\n kbd.className = CLASSES.INBOX_EMPTY_KBD;\n kbd.textContent = getShortcutText(this.options, this.strings);\n text.appendChild(document.createTextNode(before ?? \"\"));\n text.appendChild(kbd);\n text.appendChild(document.createTextNode(after ?? \"\"));\n empty.appendChild(text);\n\n const action = document.createElement(\"button\");\n action.type = \"button\";\n action.className = CLASSES.INBOX_EMPTY_ACTION;\n action.textContent = this.strings.inboxEmptyAction;\n action.addEventListener(\"click\", () =>\n this.callbacks.onActivateCommentMode()\n );\n empty.appendChild(action);\n\n return empty;\n }\n\n _pageFilterLabel(value) {\n return value === \"all\"\n ? this.strings.filterAll\n : this.strings.filterCurrentPage;\n }\n\n /**\n * Summary label for the collapsed filter button. The page filter always\n * contributes (it's either \"All pages\" or \"Current page\"); status, type,\n * and priority only join in when active, so an active filter is never\n * hidden from a user who hasn't opened the menu.\n */\n _filterSummaryLabel() {\n const parts = [this._pageFilterLabel(this.pageFilter)];\n if (this.statusFilter !== \"all\") {\n parts.push(statusLabelOf(this.statusFilter, this.strings));\n }\n if (this.typeFilter !== \"all\") {\n parts.push(typeLabelOf(this.typeFilter, this.strings));\n }\n if (this.priorityFilter !== \"all\") {\n parts.push(priorityLabelOf(this.priorityFilter, this.strings));\n }\n return parts.join(\" \u00B7 \");\n }\n\n _isFilterActive() {\n return (\n this.pageFilter !== \"page\" ||\n this.statusFilter !== \"all\" ||\n this.typeFilter !== \"all\" ||\n this.priorityFilter !== \"all\"\n );\n }\n\n /**\n * One chip group. Status, type and priority chips toggle: activating the\n * chip that is already on clears the group back to \"all\", which is why\n * they carry no explicit \"All\" chip. The page group does \u2014 it has no\n * neutral state, it's always one of two answers.\n *\n * @param {{ title: string, dataAttr: string, values: string[],\n * labelOf: (value: string) => string, selected: string,\n * toggles?: boolean, onSelect: (value: string) => void }} config\n */\n _buildFilterGroup({\n title,\n dataAttr,\n values,\n labelOf,\n selected,\n toggles = true,\n onSelect,\n }) {\n const group = document.createElement(\"div\");\n group.className = CLASSES.INBOX_FILTER_GROUP;\n\n const heading = document.createElement(\"div\");\n heading.className = CLASSES.INBOX_FILTER_SECTION;\n heading.textContent = title;\n group.appendChild(heading);\n\n const chips = document.createElement(\"div\");\n chips.className = CLASSES.INBOX_FILTER_CHIPS;\n // The page chips are role=\"radio\" (exactly one active) and radios must\n // sit in a radiogroup; the toggling groups are switches, plain group.\n chips.setAttribute(\"role\", toggles ? \"group\" : \"radiogroup\");\n chips.setAttribute(\"aria-label\", title);\n\n for (const value of values) {\n const checked = selected === value;\n const chip = document.createElement(\"button\");\n chip.type = \"button\";\n chip.className = CLASSES.INBOX_FILTER_CHIP;\n chip.dataset[dataAttr] = value;\n chip.setAttribute(\"role\", toggles ? \"switch\" : \"radio\");\n chip.setAttribute(\"aria-checked\", String(checked));\n chip.textContent = labelOf(value);\n chip.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n onSelect(toggles && checked ? \"all\" : value);\n this.render();\n });\n chips.appendChild(chip);\n }\n\n group.appendChild(chips);\n return group;\n }\n\n _buildFilter() {\n const wrapper = document.createElement(\"div\");\n wrapper.className = CLASSES.INBOX_FILTER + \"-wrapper\";\n\n const btn = document.createElement(\"button\");\n btn.type = \"button\";\n btn.className = CLASSES.INBOX_FILTER;\n btn.setAttribute(\"aria-haspopup\", \"true\");\n btn.setAttribute(\"aria-expanded\", \"false\");\n btn.innerHTML = `<span>${this._filterSummaryLabel()}</span>${CARET_ICON_SVG}`;\n\n const menu = document.createElement(\"div\");\n menu.className = CLASSES.INBOX_FILTER_MENU;\n menu.setAttribute(\"role\", \"group\");\n menu.setAttribute(\"aria-label\", this.strings.filterTitle);\n\n attachMenuToggle(btn, menu);\n\n const header = document.createElement(\"div\");\n header.className = CLASSES.INBOX_FILTER_MENU_HEADER;\n\n const title = document.createElement(\"span\");\n title.textContent = this.strings.filterTitle;\n header.appendChild(title);\n\n const clear = document.createElement(\"button\");\n clear.type = \"button\";\n clear.className = CLASSES.INBOX_FILTER_CLEAR;\n clear.textContent = this.strings.filterClear;\n clear.disabled = !this._isFilterActive();\n clear.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n this._resetFilters();\n });\n header.appendChild(clear);\n menu.appendChild(header);\n\n menu.appendChild(\n this._buildFilterGroup({\n title: this.strings.filterByPage,\n dataAttr: \"filterPage\",\n values: [\"page\", \"all\"],\n labelOf: (value) => this._pageFilterLabel(value),\n selected: this.pageFilter,\n toggles: false,\n onSelect: (value) => (this.pageFilter = value),\n })\n );\n\n menu.appendChild(\n this._buildFilterGroup({\n title: this.strings.filterByStatus,\n dataAttr: \"filterStatus\",\n values: [...STATUSES],\n labelOf: (value) => statusLabelOf(value, this.strings),\n selected: this.statusFilter,\n onSelect: (value) => (this.statusFilter = value),\n })\n );\n\n menu.appendChild(\n this._buildFilterGroup({\n title: this.strings.filterByType,\n dataAttr: \"filterType\",\n values: [...COMMENT_TYPES],\n labelOf: (value) => typeLabelOf(value, this.strings),\n selected: this.typeFilter,\n onSelect: (value) => (this.typeFilter = value),\n })\n );\n\n menu.appendChild(\n this._buildFilterGroup({\n title: this.strings.filterByPriority,\n dataAttr: \"filterPriority\",\n values: [...PRIORITIES],\n labelOf: (value) => priorityLabelOf(value, this.strings),\n selected: this.priorityFilter,\n onSelect: (value) => (this.priorityFilter = value),\n })\n );\n\n wrapper.appendChild(btn);\n wrapper.appendChild(menu);\n return wrapper;\n }\n\n _buildCard(comment, { interactive }) {\n const card = document.createElement(\"div\");\n card.className = CLASSES.INBOX_CARD;\n if (comment.status === \"resolved\") {\n card.classList.add(`${CLASSES.INBOX_CARD}--resolved`);\n }\n card.dataset.commentId = comment.id;\n\n // Meta alone on its row, action strip on the next one \u2014 the same split\n // the thread popover makes. Sharing a row squeezed the author into\n // ~90px and wrapped the name onto two lines.\n const header = document.createElement(\"div\");\n header.className = CLASSES.INBOX_CARD_HEADER;\n header.appendChild(\n createMetaElement(\n comment.author,\n comment.createdAt,\n this.strings,\n this.locale,\n comment.editedAt\n )\n );\n card.appendChild(header);\n\n const actionsRow = document.createElement(\"div\");\n actionsRow.className = CLASSES.THREAD_ACTIONS_ROW;\n actionsRow.appendChild(this._buildCardActions(comment));\n card.appendChild(actionsRow);\n\n // The editor only ever replaces the body in the detail view. On a list\n // card it would sit inside a control that navigates on click, so the \u22EF\n // there routes through startEditing(), which opens the detail first.\n const editingThis =\n !interactive &&\n this.editing &&\n this.editing.replyId == null &&\n String(this.editing.commentId) === String(comment.id);\n\n if (editingThis) {\n card.appendChild(this._buildEditor());\n } else {\n const text = document.createElement(\"div\");\n text.className = CLASSES.INBOX_CARD_TEXT;\n text.textContent = comment.text;\n card.appendChild(text);\n }\n\n if (comment.screenshots?.length) {\n const shots = createScreenshotsDisplay(comment.screenshots, this.strings);\n wireScreenshotLightbox(shots, (src) =>\n this.callbacks.onShowLightbox(src)\n );\n card.appendChild(shots);\n }\n\n // Status, type and priority already sit in the action strip above as\n // labelled pickers; repeating them here was the same fact twice. Tags\n // and the resolution time have no control anywhere, so they remain \u2014\n // the row simply disappears when there is neither.\n const badges = createBadgeRow(comment, this.strings, {\n includeClassification: false,\n });\n if (badges) card.appendChild(badges);\n\n // Hidden until something has been reacted to; the trigger in the action\n // strip above is the only way in. Identical on the list card and in the\n // detail view \u2014 the strip is shared, so a card that could add a reaction\n // but not remove one would be the odd surface out.\n card.appendChild(this._reactionsUi().bar(comment));\n\n const tag = this._buildTag(comment);\n if (tag) card.appendChild(tag);\n\n if (interactive) {\n card.setAttribute(\"role\", \"button\");\n card.setAttribute(\"tabindex\", \"0\");\n\n // Inactive comments belong to another page: activating them hands\n // off to that page (the detail opens there after the redirect).\n const activate = () =>\n comment.anchorState === \"inactive\"\n ? this.callbacks.onNavigateToPage(comment)\n : this._openDetail(comment);\n\n const replyLink = document.createElement(\"button\");\n replyLink.type = \"button\";\n replyLink.className = CLASSES.INBOX_CARD_REPLY_LINK;\n replyLink.textContent = this.strings.replyLink;\n replyLink.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n activate();\n });\n card.appendChild(replyLink);\n\n card.addEventListener(\"click\", activate);\n card.addEventListener(\"keydown\", (/** @type {KeyboardEvent} */ e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n activate();\n }\n });\n\n // Hovering a card spotlights its marker on the page \u2014 only when the\n // marker is actually there (anchored, visible, not resolved).\n card.addEventListener(\"mouseenter\", () => this._highlight(comment));\n card.addEventListener(\"mouseleave\", () => this._clearHighlight());\n }\n\n return card;\n }\n\n _buildTag(comment) {\n let label = null;\n if (comment.anchorState === \"orphaned\") label = this.strings.orphanedBadge;\n else if (comment.hidden) label = this.strings.hiddenBadge;\n else if (comment.anchorState === \"inactive\") label = comment.page;\n if (!label) return null;\n\n const tag = document.createElement(\"span\");\n tag.className = CLASSES.INBOX_CARD_TAG;\n tag.textContent = label;\n return tag;\n }\n\n _buildCardActions(comment) {\n return createCommentActions(comment, {\n strings: this.strings,\n can: this.callbacks.can,\n reactions: this._reactionsUi(),\n onCopy: (c) =>\n copyToClipboard(\n buildAgentContext(c, {\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n strings: this.strings,\n })\n ),\n onCopyLink: (c) =>\n copyToClipboard(buildCommentLink(c, this.options.linkParam)),\n // From a list card this opens the detail with the editor already up:\n // a textarea inside a card that navigates on click, and highlights a\n // marker on hover, would be fighting three behaviours at once.\n onEdit: (c) => this.startEditing(c.id),\n onSetStatus: (c, status) => this.callbacks.onSetStatus(c.id, status),\n onSetType: (c, type) => this.callbacks.onSetType(c.id, type),\n onSetPriority: (c, priority) =>\n this.callbacks.onSetPriority(c.id, priority),\n onDelete: (c) => {\n if (this.detailId != null && sameId(this.detailId, c.id)) {\n this.detailId = null;\n }\n this.callbacks.onDelete(c.id);\n this.render();\n },\n });\n }\n\n _renderDetail(comment, comments) {\n const index = comments.indexOf(comment);\n\n const header = document.createElement(\"div\");\n header.className = CLASSES.INBOX_DETAIL_HEADER;\n\n const backBtn = document.createElement(\"button\");\n backBtn.type = \"button\";\n backBtn.className = CLASSES.INBOX_BACK;\n backBtn.innerHTML = `${CHEVRON_LEFT_SVG}<span>${this.strings.back}</span>`;\n backBtn.addEventListener(\"click\", async () => {\n if (this.editing && !(await this.releaseEditor())) return;\n this.detailId = null;\n this.render();\n });\n header.appendChild(backBtn);\n\n const nav = document.createElement(\"div\");\n nav.className = CLASSES.INBOX_CARD_ACTIONS;\n\n const navBtn = (svg, label, targetIndex) => {\n const btn = document.createElement(\"button\");\n btn.type = \"button\";\n btn.className = CLASSES.INBOX_NAV_BTN;\n btn.setAttribute(\"aria-label\", label);\n btn.title = label;\n btn.innerHTML = svg;\n const target = comments[targetIndex];\n btn.disabled = !target;\n if (target) {\n // Navigating to another comment takes the edited text off screen. A\n // draft that survived that invisibly and reappeared later would be\n // worse than being asked about it here.\n btn.addEventListener(\"click\", async () => {\n if (this.editing && !(await this.releaseEditor())) return;\n this._openDetail(target);\n });\n }\n return btn;\n };\n\n nav.appendChild(navBtn(ARROW_UP_SVG, this.strings.prevComment, index - 1));\n nav.appendChild(\n navBtn(ARROW_DOWN_SVG, this.strings.nextComment, index + 1)\n );\n nav.appendChild(this._closeButton());\n header.appendChild(nav);\n\n this.el.appendChild(header);\n\n const detail = document.createElement(\"div\");\n detail.className = CLASSES.INBOX_DETAIL;\n\n detail.appendChild(this._buildCard(comment, { interactive: false }));\n\n // Open on arrival \u2014 the detail view is where you go to read everything \u2014\n // but foldable, and the choice outlives the rebuilds this view does on\n // every refresh.\n const context = createContextBlock(comment, {\n strings: this.strings,\n onShowLightbox: (src) => this.callbacks.onShowLightbox(src),\n collapsible: true,\n expanded: this.contextExpanded,\n onToggle: (expanded) => {\n this.contextExpanded = expanded;\n },\n });\n if (context) detail.appendChild(context);\n\n // Beside the context block rather than below the thread: both are folded\n // metadata about the comment, and keeping them together leaves the\n // conversation as one uninterrupted block underneath.\n const audit = createAuditTrail(comment, {\n strings: this.strings,\n locale: this.locale,\n open: this.auditExpanded,\n onToggle: (expanded) => {\n this.auditExpanded = expanded;\n },\n });\n if (audit) detail.appendChild(audit);\n\n const replies = document.createElement(\"div\");\n replies.className = CLASSES.INBOX_REPLIES;\n for (const reply of comment.replies || []) {\n const editingThisReply =\n this.editing &&\n String(this.editing.commentId) === String(comment.id) &&\n String(this.editing.replyId) === String(reply.id);\n\n const replyEl = createReplyElement(reply, this.strings, this.locale, {\n commentId: comment.id,\n can: this.callbacks.can,\n // Drops the row instead of re-rendering the detail: a full render\n // would also throw away whatever the user has half-typed in the\n // reply box below.\n onDelete: (r, el) => {\n if (this.callbacks.onDeleteReply(comment.id, r.id)) el.remove();\n },\n onEdit: (r) => this.startEditing(comment.id, r.id),\n editing: editingThisReply ? this._editorHandlers() : null,\n reactions: this._reactionsUi(),\n });\n wireScreenshotLightbox(replyEl, (src) =>\n this.callbacks.onShowLightbox(src)\n );\n replies.appendChild(replyEl);\n }\n detail.appendChild(replies);\n\n detail.appendChild(this._buildReplyInput(comment));\n this.el.appendChild(detail);\n }\n\n _buildReplyInput(comment) {\n const {\n container,\n inputEl,\n screenshotsContainer,\n attachBtn,\n fileInput,\n submitBtn,\n } = createInputArea(\n {\n areaClassName: CLASSES.THREAD_INPUT_AREA,\n inputTag: \"input\",\n inputClassName: CLASSES.THREAD_INPUT,\n inputPlaceholder: this.strings.replyPlaceholder,\n },\n this.strings\n );\n\n let pendingScreenshots = [];\n\n const updatePreview = () => {\n renderScreenshotsPreview(screenshotsContainer, pendingScreenshots, {\n strings: this.strings,\n onShow: (dataUrl) => this.callbacks.onShowLightbox(dataUrl),\n rerender: () => updatePreview(),\n });\n };\n\n attachBtn.addEventListener(\"click\", () => fileInput.click());\n wireScreenshotInput(\n fileInput,\n () => pendingScreenshots,\n updatePreview,\n (dataUrl) => this.callbacks.onTransformScreenshot(dataUrl, comment.id)\n );\n\n const submit = () => {\n const text = inputEl.value.trim();\n if (!text && pendingScreenshots.length === 0) return;\n this.callbacks.onReply(comment, text, [...pendingScreenshots]);\n pendingScreenshots = [];\n this.render();\n };\n\n submitBtn.addEventListener(\"click\", submit);\n inputEl.addEventListener(\"keydown\", (/** @type {KeyboardEvent} */ e) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n submit();\n }\n });\n\n return container;\n }\n}\n", "// CSV export: the comment corpus flattened one row per comment, and the\n// aggregate figures in long format.\n//\n// Hand-written rather than pulled from a library. The whole of RFC 4180 that\n// matters here is \"quote a field containing a delimiter, a quote or a\n// newline, and double the quotes inside it\" \u2014 thirty lines against the 7 KB\n// gzip a parser library costs, which is half the budget headroom for\n// something this file does in full.\n\nimport { toHours } from \"./metrics.js\";\nimport { currentResolutionMs, resolutionsOf } from \"./audit.js\";\n\nconst DELIMITER = \",\";\nconst NEWLINE = \"\\r\\n\";\n\n// Excel evaluates a cell opening with any of these, so a comment reading\n// \"=1+1\" becomes a formula the moment somebody double-clicks the file. The\n// leading apostrophe is the standard defusing and survives a round trip\n// through pandas and R.\nconst FORMULA_LEAD = /^[=+\\-@\\t\\r]/;\n\nconst escape = (value) => {\n if (value === null || value === undefined) return \"\";\n const raw = String(value);\n const safe = FORMULA_LEAD.test(raw) ? `'${raw}` : raw;\n return /[\"\\n\\r,]/.test(safe) ? `\"${safe.replace(/\"/g, '\"\"')}\"` : safe;\n};\n\n/**\n * @param {Array<Record<string, unknown>>} rows\n * @param {Array<{ key: string, label: string }>} columns\n * @returns {string}\n */\nexport function toCsv(rows, columns) {\n const header = columns.map((column) => escape(column.label)).join(DELIMITER);\n const body = rows.map((row) =>\n columns.map((column) => escape(row[column.key])).join(DELIMITER)\n );\n return [header, ...body].join(NEWLINE);\n}\n\n/** Columns of the comment export, in the order they are written. */\nexport const COMMENT_COLUMNS = [\n \"id\",\n \"page\",\n \"author\",\n \"authorId\",\n \"text\",\n \"status\",\n \"type\",\n \"priority\",\n \"tags\",\n \"createdAt\",\n \"resolvedAt\",\n \"resolutionHours\",\n \"reopened\",\n \"replies\",\n];\n\n/**\n * Pairs bare keys with themselves as headers. The header row deliberately\n * carries the internal names rather than translated labels: the file is an\n * interchange format, and a column whose spelling follows the widget's locale\n * cannot be joined against the export somebody else produced.\n * @param {string[]} keys\n */\nexport const columnsOf = (keys) => keys.map((key) => ({ key, label: key }));\n\n/** Columns of the aggregate export. */\nexport const METRIC_COLUMNS = [\"section\", \"key\", \"value\"];\n\n/**\n * One row per comment. Screenshots and the automatic context capture are\n * deliberately absent: a 33 KB base64 string in a spreadsheet cell is not\n * data, it is a file that has lost its name.\n *\n * @param {import('./index.d.ts').SerializedComment[]} comments\n */\nexport function commentRows(comments) {\n return (comments || []).map((comment) => {\n return {\n id: String(comment.id),\n page: comment.page || \"\",\n author: comment.author || \"\",\n authorId: comment.authorId || \"\",\n text: comment.text || \"\",\n status: comment.status || \"open\",\n type: comment.type || \"\",\n priority: comment.priority || \"\",\n // Space-joined rather than comma-joined: a comma inside a field is\n // legal but forces quoting on a column that is otherwise clean.\n tags: (comment.tags || []).join(\" \"),\n createdAt: comment.createdAt || \"\",\n resolvedAt: comment.resolvedAt || \"\",\n resolutionHours: toHours(currentResolutionMs(comment)),\n reopened: resolutionsOf(comment).length > 1 ? \"yes\" : \"no\",\n replies: (comment.replies || []).length,\n };\n });\n}\n\n/**\n * The aggregate figures in long format \u2014 `section, key, value` \u2014 rather than\n * one wide row. Buckets differ in number between corpora (one row per active\n * day), so a wide shape would change its column count from export to export\n * and stop being joinable against the previous one.\n *\n * Keys are the stable internal names, not translated labels: a column whose\n * spelling follows the widget's locale cannot be joined against anything.\n *\n * @param {import('./index.d.ts').CommentMetrics} metrics\n */\nexport function metricRows(metrics) {\n const rows = [{ section: \"total\", key: \"\", value: metrics.total }];\n const push = (section, table) => {\n for (const [key, value] of Object.entries(table)) {\n rows.push({ section, key, value });\n }\n };\n push(\"status\", metrics.byStatus);\n push(\"type\", metrics.byType);\n push(\"priority\", metrics.byPriority);\n for (const { date, count } of metrics.overTime) {\n rows.push({ section: \"perDay\", key: date, value: count });\n }\n rows.push(\n {\n section: \"resolution\",\n key: \"resolvedCount\",\n value: metrics.resolution.resolvedCount,\n },\n {\n section: \"resolution\",\n key: \"reopenedCount\",\n value: metrics.resolution.reopenedCount,\n },\n {\n section: \"resolution\",\n key: \"averageHours\",\n value: toHours(metrics.resolution.averageMs),\n },\n {\n section: \"resolution\",\n key: \"medianHours\",\n value: toHours(metrics.resolution.medianMs),\n }\n );\n return rows;\n}\n\n/**\n * Hands the browser a file. The BOM is not decoration: without it Excel reads\n * the bytes as its local codepage and every accented name comes back as\n * mojibake.\n *\n * @param {string} filename\n * @param {string} text\n */\nexport function downloadCsv(filename, text) {\n const blob = new Blob([\"\\uFEFF\", text], {\n type: \"text/csv;charset=utf-8\",\n });\n const url = URL.createObjectURL(blob);\n const link = document.createElement(\"a\");\n link.href = url;\n link.download = filename;\n link.click();\n URL.revokeObjectURL(url);\n}\n", "// The printable metrics report \u2014 the \"PDF\" half of the export requirement.\n//\n// No PDF library. The lightest one measured 133 KB gzip against a 50 KB\n// budget with ~13 KB of headroom left, so the browser's own print-to-PDF does\n// the job for zero bytes: build the report in its own document and ask that\n// document to print. What the user saves is a real PDF, produced by the\n// engine that already knows how to lay out the page.\n//\n// Its own document, not the host page: printing the page would print whatever\n// the host has on screen. And the styles go in through mountStyles rather\n// than an inline <style>, because an iframe inherits the embedder's Content\n// Security Policy \u2014 under a strict `style-src` an inline sheet is dropped and\n// the report prints unstyled, which is the exact failure the widget's own\n// stylesheet already had to solve.\n\nimport { mountStyles } from \"./style-mount.js\";\nimport { formatTemplate } from \"./i18n.js\";\nimport {\n statusLabelOf,\n typeLabelOf,\n priorityLabelOf,\n} from \"./comment-actions.js\";\nimport { STATUSES, COMMENT_TYPES, PRIORITIES } from \"./constants.js\";\nimport { toHours } from \"./metrics.js\";\n\nconst REPORT_STYLE_ID = \"helldots-report-styles\";\n\nconst el = (doc, tag, className, text) => {\n const node = doc.createElement(tag);\n if (className) node.className = className;\n if (text !== undefined) node.textContent = String(text);\n return node;\n};\n\nconst buildTable = (doc, caption, rows, headers) => {\n const table = el(doc, \"table\", \"report-table\");\n table.appendChild(el(doc, \"caption\", null, caption));\n\n const thead = doc.createElement(\"thead\");\n const headRow = doc.createElement(\"tr\");\n for (const header of headers)\n headRow.appendChild(el(doc, \"th\", null, header));\n thead.appendChild(headRow);\n table.appendChild(thead);\n\n const tbody = doc.createElement(\"tbody\");\n for (const [label, value] of rows) {\n const tr = doc.createElement(\"tr\");\n tr.appendChild(el(doc, \"th\", null, label));\n tr.appendChild(el(doc, \"td\", null, value));\n tbody.appendChild(tr);\n }\n table.appendChild(tbody);\n return table;\n};\n\n// Hours rather than the widget's \"3h 12m\" shorthand: a printed report is\n// read next to other reports, and a single unit is what you can compare.\nconst duration = (ms) =>\n ms === null ? \"\u2014\" : formatTemplate(\"{n} h\", toHours(ms));\n\nconst buildReport = (doc, metrics, { strings, locale, scope }) => {\n const body = doc.body;\n body.className = \"report\";\n\n body.appendChild(el(doc, \"h1\", \"report-title\", strings.metricsTitle));\n\n const meta = el(doc, \"p\", \"report-meta\");\n meta.textContent = formatTemplate(\n strings.metricsGeneratedTemplate,\n new Intl.DateTimeFormat(locale, {\n dateStyle: \"long\",\n timeStyle: \"short\",\n }).format(new Date())\n );\n body.appendChild(meta);\n\n if (scope) {\n const scopeEl = el(doc, \"p\", \"report-meta\");\n scopeEl.textContent = `${strings.metricsScope}: ${scope}`;\n body.appendChild(scopeEl);\n }\n\n body.appendChild(\n buildTable(\n doc,\n strings.metricsTitle,\n [\n [strings.metricsTotal, metrics.total],\n [strings.statusResolved, metrics.resolution.resolvedCount],\n [strings.metricsReopened, metrics.resolution.reopenedCount],\n [\n strings.metricsAverageResolution,\n duration(metrics.resolution.averageMs),\n ],\n [\n strings.metricsMedianResolution,\n duration(metrics.resolution.medianMs),\n ],\n ],\n [strings.metricsCategory, strings.metricsCount]\n )\n );\n\n const dimension = (caption, keys, table, labelOf) =>\n buildTable(\n doc,\n caption,\n keys.map((key) => [labelOf(key), table[key] ?? 0]),\n [strings.metricsCategory, strings.metricsCount]\n );\n\n body.appendChild(\n dimension(strings.metricsByStatus, STATUSES, metrics.byStatus, (key) =>\n statusLabelOf(key, strings)\n )\n );\n body.appendChild(\n dimension(\n strings.metricsByType,\n [...COMMENT_TYPES, \"unset\"],\n metrics.byType,\n (key) => (key === \"unset\" ? strings.unset : typeLabelOf(key, strings))\n )\n );\n body.appendChild(\n dimension(\n strings.metricsByPriority,\n [...PRIORITIES, \"unset\"],\n metrics.byPriority,\n (key) => (key === \"unset\" ? strings.unset : priorityLabelOf(key, strings))\n )\n );\n\n if (metrics.overTime.length) {\n body.appendChild(\n buildTable(\n doc,\n strings.metricsOverTime,\n metrics.overTime.map(({ date, count }) => [date, count]),\n [strings.metricsDate, strings.metricsCount]\n )\n );\n }\n};\n\n/**\n * Builds the report in a hidden same-origin frame and asks it to print.\n *\n * @param {import('./index.d.ts').CommentMetrics} metrics\n * @param {{\n * strings: ReturnType<typeof import('./i18n.js').getStrings>,\n * locale: string,\n * css: string,\n * scope?: string,\n * }} deps\n * @returns {HTMLIFrameElement} the frame, which takes itself down after printing\n */\nexport function printMetricsReport(metrics, { strings, locale, css, scope }) {\n const frame = document.createElement(\"iframe\");\n frame.setAttribute(\"aria-hidden\", \"true\");\n frame.setAttribute(\"title\", strings.metricsTitle);\n // Off-screen rather than display:none \u2014 a frame that is not rendered has no\n // layout, and printing one prints nothing.\n frame.style.position = \"absolute\";\n frame.style.width = \"0\";\n frame.style.height = \"0\";\n frame.style.border = \"0\";\n frame.style.left = \"-9999px\";\n document.body.appendChild(frame);\n\n const doc = frame.contentDocument;\n const view = frame.contentWindow;\n doc.title = strings.metricsTitle;\n mountStyles(doc, css, REPORT_STYLE_ID);\n buildReport(doc, metrics, { strings, locale, scope });\n\n const teardown = () => frame.remove();\n view.addEventListener(\"afterprint\", teardown, { once: true });\n\n // Deferred by a tick: printing before the frame has laid out its content is\n // how a blank page comes out. Scheduled from this realm so the frame can be\n // taken down even if its own timers never run.\n setTimeout(() => view.print?.(), 0);\n\n return frame;\n}\n", "import { CaptureFlow } from \"./capture-flow.js\";\nimport { captureContext } from \"./metadata.js\";\nimport {\n CLASSES,\n IDS,\n SELECTORS,\n STATUSES,\n COMMENT_TYPES,\n PRIORITIES,\n MARKER_SIZE,\n MAX_SCREENSHOTS,\n MARKERS_HIDDEN_STORAGE_KEY,\n} from \"./constants.js\";\nimport { getStyles, getGlobalStyles } from \"./styles.js\";\nimport { mountStyles } from \"./style-mount.js\";\nimport { getShadowRoot, TAG_NAME } from \"./root-element.js\";\nimport { getStrings, detectLocale } from \"./i18n.js\";\nimport {\n createAnchor,\n resolveAnchor,\n generateElementSelector,\n} from \"./anchor.js\";\nimport {\n readStoredComments,\n writeStoredComments,\n mergeForStorage,\n STORAGE_KEY,\n PENDING_DETAIL_KEY,\n} from \"./storage.js\";\nimport { createId, sameId } from \"./id.js\";\nimport {\n actorKeyOf,\n toggleReactionOn,\n normalizeReactions,\n serializeReactions,\n} from \"./reactions.js\";\nimport {\n resolvePermission,\n commentTargetOf,\n replyTargetOf,\n} from \"./permissions.js\";\nimport {\n buildCommentLink,\n readCommentLinkParam,\n DEFAULT_LINK_PARAM,\n} from \"./link.js\";\nimport {\n createToolbar,\n cssAttrValue,\n isMacPlatform,\n renderScreenshotsPreview,\n wireScreenshotInput,\n wireScreenshotLightbox,\n createCommentBox,\n createTooltip,\n EYE_ICON_SVG,\n EYE_OFF_ICON_SVG,\n} from \"./components.js\";\nimport {\n PopoverController,\n positionPopoverAtCircle,\n} from \"./popover-controller.js\";\nimport { MarkerEngine } from \"./marker-engine.js\";\nimport { InboxView } from \"./inbox.js\";\nimport {\n actorOf,\n recordEvent,\n normalizeHistory,\n serializeHistory,\n} from \"./audit.js\";\nimport { normalizeActorId } from \"./id.js\";\nimport { computeMetrics } from \"./metrics.js\";\nimport {\n toCsv,\n columnsOf,\n commentRows,\n metricRows,\n downloadCsv,\n COMMENT_COLUMNS,\n METRIC_COLUMNS,\n} from \"./csv.js\";\nimport { printMetricsReport } from \"./metrics-report.js\";\nimport { getReportStyles } from \"./styles.js\";\nimport { closeOpenMenus } from \"./menus.js\";\nimport { closeOpenConfirmDialogs } from \"./confirm-dialog.js\";\n\n// Tags are user-typed, so they arrive with stray case and whitespace.\n// Normalising here (rather than at each entry point) is what makes\n// \"Checkout\" and \"checkout \" the same tag for filtering.\nconst normalizeTags = (tags) => {\n const seen = new Set();\n for (const tag of tags) {\n const clean = String(tag).trim().toLowerCase();\n if (clean) seen.add(clean);\n }\n return [...seen];\n};\n\n// Screenshots are data-URLs rendered straight into <img src>; anything else\n// in a persisted array is a silently broken thumbnail waiting to happen.\nconst onlyStrings = (values) => values.filter((v) => typeof v === \"string\");\n\n// A drag names a region, and the region means the element that shows (most\n// of) it \u2014 not whatever sits on top of its center pixel. Coverage rather\n// than strict containment because human selections overshoot by a few\n// pixels; 60% tolerates the overshoot while still rejecting a partial\n// overlay (a floating panel, a dropdown) hovering above the framed content.\nconst REGION_COVERAGE_MIN = 0.6;\n\n// Every change the host can hear about, as `type` \u2192 the specific callback\n// that has always carried it. One table so a new event cannot be added to\n// the stream while forgetting the callback (or the other way round), and so\n// the two can never disagree about when they fire.\nconst CHANGE_CALLBACKS = {\n \"comment:created\": \"onCommentCreated\",\n \"comment:edited\": \"onCommentEdited\",\n \"comment:deleted\": \"onCommentDeleted\",\n \"comment:status-changed\": \"onCommentStatusChanged\",\n \"comment:updated\": \"onCommentUpdated\",\n \"comment:anchor-lost\": \"onAnchorLost\",\n \"reply:added\": \"onReplyAdded\",\n \"reply:deleted\": \"onReplyDeleted\",\n \"reply:edited\": \"onReplyEdited\",\n \"reaction:toggled\": \"onReactionToggled\",\n};\n\nclass CommentOverlay {\n /**\n * @param {import('./index.d.ts').CommentOverlayOptions} [options]\n */\n constructor(options = {}) {\n this.comments = [];\n this.commentMode = false;\n this.isMac = isMacPlatform();\n this.options = {\n shortcutKey: options.shortcutKey || (this.isMac ? \"c\" : \"C\"),\n shortcutModifier: options.shortcutModifier || \"alt\",\n autoScreenshot: options.autoScreenshot !== false,\n embedCrossOriginFonts: options.embedCrossOriginFonts === true,\n fastCapture: options.fastCapture === true,\n skipIframeContent: options.skipIframeContent === true,\n ...options,\n };\n this.locale = this.options.locale || detectLocale();\n this.strings = getStrings(this.locale);\n\n /**\n * Marker positioning, occlusion and observers (see marker-engine.js).\n * Created in initOverlay \u2014 its circles mount into the overlay element.\n * @type {MarkerEngine | null}\n */\n this.markers = null;\n /**\n * Drag selection + screenshot orchestration (see capture-flow.js).\n * Created in initOverlay \u2014 it mounts the selection rect into the\n * shadow root. @type {CaptureFlow | null}\n */\n this._captureFlow = null;\n\n /** Whether a drag's region crop is still rendering. @type {boolean} */\n this._regionCapturePending = false;\n\n /**\n * Parsed cross-page corpus, so every mutation does not pay a full\n * getItem + JSON.parse (megabytes once screenshots accumulate). Kept in\n * step with what this instance writes; dropped when another tab writes\n * (the `storage` listener in initOverlay).\n * @type {import('./index.d.ts').SerializedComment[] | null}\n */\n this._storedCache = null;\n\n /**\n * Thread-popover lifecycle and editing state (see\n * popover-controller.js). Created in initOverlay \u2014 it mounts into the\n * shadow root. @type {PopoverController | null}\n */\n this._popover = null;\n /**\n * A comment someone asked to open \u2014 from a \"Copy link\" URL or the\n * cross-page handoff \u2014 that has not been found yet.\n * @type {string | null}\n */\n this._pendingDetailId = null;\n\n /**\n * The pending id the host was already asked to fetch. A link pointing at\n * a comment that never arrives must ask once, not once per load.\n * @type {string | null}\n */\n this._requestedDetailId = null;\n\n /**\n * Where the mutation being applied right now came from. \"host\" is the\n * default because a public method reached directly IS the host calling\n * it; the widget's own UI goes through `_asUser`, which flips this for\n * the duration of the call.\n * @type {import('./index.d.ts').ChangeOrigin}\n */\n this._origin = \"host\";\n\n /**\n * Comments handed to loadComments() before the widget mounted, replayed\n * by initOverlay() once the marker engine exists.\n * @type {import('./index.d.ts').SerializedComment[] | null}\n */\n this._deferredLoad = null;\n\n if (document.readyState === \"loading\") {\n // Kept on the instance so cleanup() can cancel it \u2014 an instance\n // destroyed while the document is still loading must not mount a\n // zombie UI when DOMContentLoaded fires.\n this._onDomReady = () => this.initOverlay();\n document.addEventListener(\"DOMContentLoaded\", this._onDomReady);\n } else {\n this.initOverlay();\n }\n }\n\n initOverlay() {\n // Mount inside a dedicated shadow root so widget styles/markup stay\n // isolated from the host page in both directions.\n this.shadowRoot = getShadowRoot();\n\n // Create and append UI elements\n this.toolbar = createToolbar(this.options, this.strings);\n this.commentBox = createCommentBox(this.strings);\n this.overlay = document.createElement(\"div\");\n this.overlay.className = CLASSES.COMMENT_OVERLAY;\n\n this.shadowRoot.appendChild(this.overlay);\n this.shadowRoot.appendChild(this.toolbar);\n this.shadowRoot.appendChild(this.commentBox);\n\n this.commentBtn = this.toolbar.querySelector(\n `.${CLASSES.TOOLBAR_COMMENT_BTN}`\n );\n this.inboxBtn = this.toolbar.querySelector(`.${CLASSES.TOOLBAR_MENU_BTN}`);\n this.eyeBtn = this.toolbar.querySelector(`.${CLASSES.TOOLBAR_EYE_BTN}`);\n /** Whether the on-page marker layer is hidden (the eye toggle). */\n this.markersHidden = false;\n /** @type {HTMLButtonElement} */\n this.submitButton = /** @type {any} */ (\n this.shadowRoot.getElementById(IDS.SUBMIT_COMMENT)\n );\n /** @type {HTMLTextAreaElement} */\n this.commentInput = /** @type {any} */ (\n this.shadowRoot.getElementById(IDS.COMMENT_INPUT)\n );\n this.attachImageBtn = this.commentBox.querySelector(\n `.${CLASSES.ATTACH_IMAGE_BTN}`\n );\n /** @type {HTMLInputElement} */\n this.attachImageInput = /** @type {any} */ (\n this.shadowRoot.getElementById(IDS.ATTACH_IMAGE_INPUT)\n );\n\n this._captureFlow = new CaptureFlow({\n host: this.shadowRoot,\n autoScreenshot: this.options.autoScreenshot,\n embedCrossOriginFonts: this.options.embedCrossOriginFonts,\n fastCapture: this.options.fastCapture,\n skipIframeContent: this.options.skipIframeContent,\n captureTimeout: this.options.captureTimeout,\n // The pending-attachments array stays here, next to the comment box\n // that previews it \u2014 the flow only reports what a drag captured.\n onRegionCaptured: (dataUrl) => {\n if (!this._pendingScreenshots) this._pendingScreenshots = [];\n if (this._pendingScreenshots.length < MAX_SCREENSHOTS) {\n this._pendingScreenshots.push(dataUrl);\n }\n },\n // The box opens before the crop exists now, so it has to show that\n // one is coming \u2014 an empty attachment strip after a deliberate drag\n // reads as the selection having been thrown away.\n onRegionPending: (pending) => {\n this._regionCapturePending = pending;\n this._updateScreenshotsPreview();\n },\n onPlace: (x, y, region) => this._placeCommentAtPoint(x, y, region),\n onError: (err) => this._reportError(err, \"capture\"),\n });\n\n this._popover = new PopoverController({\n shadowRoot: this.shadowRoot,\n strings: this.strings,\n locale: this.locale,\n findComment: (id) => this._findComment(id),\n removeTooltip: (id) => this._tooltipEl(id)?.remove(),\n onShowLightbox: (src) => this.showLightbox(src),\n isInsideLightbox: (target) => this._isInsideLightbox(target),\n linkParam: () => this._linkParam(),\n refreshInbox: () => {\n if (this.inboxView?.isOpen()) this.inboxView.refresh();\n },\n actorKey: () => this._actorKey(),\n can: (action, target) => this.can(action, target),\n transformScreenshot: (dataUrl, commentId) =>\n this._transformScreenshot(dataUrl, \"attachment\", commentId),\n // Every action below is a person clicking inside the widget, so the\n // events they emit carry origin \"user\" (see _asUser).\n actions: this._userActions({\n addReply: (comment, text, screenshots) =>\n this.addReply(comment, text, screenshots),\n deleteReply: (commentId, replyId) =>\n this.deleteReply(commentId, replyId),\n editComment: (id, text) => this.editComment(id, text),\n editReply: (commentId, replyId, text) =>\n this.editReply(commentId, replyId, text),\n setStatus: (id, status) => this.setCommentStatus(id, status),\n setType: (id, type) => this.setCommentType(id, type),\n setPriority: (id, priority) => this.setCommentPriority(id, priority),\n deleteComment: (id) => this.deleteComment(id),\n toggleCommentReaction: (id, emoji) =>\n this.toggleCommentReaction(id, emoji),\n toggleReplyReaction: (commentId, replyId, emoji) =>\n this.toggleReplyReaction(commentId, replyId, emoji),\n }),\n });\n\n this.markers = new MarkerEngine({\n container: this.overlay,\n strings: this.strings,\n getComments: () => this.comments,\n wireMarker: (circle, comment) => this._wireMarker(circle, comment),\n onMarkerHidden: (comment) => this._dismissMarkerUi(comment),\n onVisibilityFlip: () => {\n if (this.inboxView?.isOpen()) this.inboxView.refresh();\n },\n // Runs after every rAF pass: the markers are placed in viewport\n // coordinates, so the open thread popover has to follow.\n onAfterPass: () => this.syncThreadPopoverToMarker(),\n });\n this.markers.start();\n\n // The eye toggle's preference survives reloads; a blocked localStorage\n // just means the layer starts visible.\n try {\n if (localStorage.getItem(MARKERS_HIDDEN_STORAGE_KEY) === \"true\") {\n this._setMarkersHidden(true);\n }\n } catch {\n /* storage unavailable \u2014 stay visible */\n }\n\n // Bind event listeners\n this.bindEventListeners();\n this.setupKeyboardShortcut();\n this.injectStyles();\n\n this._pendingDetailId = this._readPendingDetailId();\n\n if (this.options.persistence === \"localStorage\") {\n this._storedCache = readStoredComments();\n this.loadComments(this._storedCache);\n // Another tab writing the key makes this instance's parsed copy\n // stale \u2014 drop it so the next sync re-reads before merging, instead\n // of clobbering what the other tab persisted.\n this._storageHandler = (e) => {\n if (e.key === STORAGE_KEY || e.key === null) this._storedCache = null;\n };\n window.addEventListener(\"storage\", this._storageHandler);\n }\n\n // A host whose fetch resolved while the document was still parsing\n // called loadComments() before any of this existed. Applied here, after\n // the localStorage restore, so explicit data still wins by id over\n // whatever was cached.\n if (this._deferredLoad) {\n const deferred = this._deferredLoad;\n this._deferredLoad = null;\n this.loadComments(deferred);\n }\n\n // Also outside localStorage mode: a host that persists comments itself\n // still deserves to have the link honoured, and until its loadComments()\n // arrives the inbox is what tells the user the link was understood.\n this._openPendingDetail();\n\n // Opt-in, never default: popstate only covers back/forward, and MPA\n // hosts should not inherit listeners for navigations they don't do.\n // pushState routing still needs an explicit notifyNavigation() call.\n if (this.options.autoDetectNavigation) {\n this._popstateHandler = () => this.notifyNavigation();\n window.addEventListener(\"popstate\", this._popstateHandler);\n }\n\n this._notifyReady();\n }\n\n _navigateTo(url) {\n // A host router can take over (SPA): a full-page load throws away the\n // app's state just to show another route it could render itself.\n if (typeof this.options.navigate === \"function\") {\n this.options.navigate(url);\n return;\n }\n location.assign(url);\n }\n\n /**\n * Where a request to open one comment can come from. Two sources, one\n * slot: an inactive card clicked on the previous page (sessionStorage), or\n * a \"Copy link\" URL someone was sent. The URL wins when both are present \u2014\n * it is the one the user acted on just now.\n * @returns {string | null}\n */\n _readPendingDetailId() {\n const fromLink = readCommentLinkParam(this._linkParam());\n let fromHandoff = null;\n try {\n fromHandoff = sessionStorage.getItem(PENDING_DETAIL_KEY);\n // One-shot: read it and it is spent, whether or not it resolves.\n if (fromHandoff != null) sessionStorage.removeItem(PENDING_DETAIL_KEY);\n } catch {\n // A blocked sessionStorage only costs the handoff, not the link.\n }\n return fromLink ?? fromHandoff;\n }\n\n _linkParam() {\n return this.options.linkParam || DEFAULT_LINK_PARAM;\n }\n\n /**\n * Opens the inbox on the pending comment, if there is one.\n *\n * Deliberately does NOT give up when the id fails to resolve: a host that\n * fetches its comments from its own back end has not called loadComments()\n * yet at startup, and that is precisely the setup where a link is worth\n * sending to another person. The id is kept and this runs again after\n * every load, so the inbox switches from \"not on this page\" to the comment\n * the moment the data lands.\n */\n _openPendingDetail() {\n const id = this._pendingDetailId;\n if (!id) return;\n\n const comment = this._findComment(id);\n if (!comment) {\n // Opening the inbox anyway is the point: clicking a link and having\n // nothing at all happen is indistinguishable from a broken widget.\n this.showInbox();\n this.inboxView?.showNotice(this.strings.commentNotFound);\n this._requestPendingDetail(id);\n return;\n }\n\n this._pendingDetailId = null;\n this._requestedDetailId = null;\n this.showInbox();\n this.inboxView.clearNotice();\n this.inboxView.openDetail(comment.id);\n }\n\n /**\n * Asks the host for a comment a link points at that the widget does not\n * hold.\n *\n * This is what makes \"load only the comment in the link\" implementable. A\n * host that fetches per page otherwise has no way to learn which id the\n * URL asked for except re-parsing the query string with its own copy of\n * `linkParam` \u2014 a second spelling of the same setting, free to drift from\n * the one the widget actually uses.\n *\n * Asked once per id rather than once per attempt: `_openPendingDetail`\n * runs again after every load and after every navigation, and an id the\n * host cannot produce must not become a request loop.\n *\n * A handler returning a promise is awaited and the link retried once it\n * settles \u2014 on rejection too, since the comment may have arrived by\n * another route while the fetch was failing.\n *\n * @param {string} id the pending id, as the URL or the handoff spelled it\n */\n _requestPendingDetail(id) {\n const handler = this.options.onCommentRequested;\n if (typeof handler !== \"function\") return;\n if (this._requestedDetailId !== null && sameId(this._requestedDetailId, id))\n return;\n this._requestedDetailId = id;\n\n let result;\n try {\n result = handler(id);\n } catch (err) {\n this._reportError(err, \"link\");\n return;\n }\n if (!result || typeof (/** @type {any} */ (result).then) !== \"function\") {\n return;\n }\n /** @type {Promise<unknown>} */ (result).then(\n () => this._openPendingDetail(),\n (err) => {\n this._reportError(err, \"link\");\n this._openPendingDetail();\n }\n );\n }\n\n /**\n * Announces that the widget is mounted and every method on it is safe to\n * call. Fires once, at the end of initOverlay \u2014 synchronously inside the\n * constructor when the document was already parsed, on DOMContentLoaded\n * when it was not. The instance is handed over because in the synchronous\n * case the host does not have the return value of createCommentOverlay()\n * yet.\n */\n _notifyReady() {\n const handler = this.options.onReady;\n if (typeof handler !== \"function\") return;\n try {\n handler(this);\n } catch (err) {\n console.warn(\"HellDots: onReady handler threw\", err);\n }\n }\n\n /**\n * Runs a mutation performed by the widget's own UI, so everything emitted\n * inside it is stamped `origin: \"user\"`. A call arriving from the host's\n * code never passes through here and stays `\"host\"` \u2014 which is the whole\n * of how the two are told apart, since the inbox and the thread popover\n * drive the very same public methods a host does.\n *\n * Restores the previous value rather than resetting to \"host\": the inbox\n * calls a public method that itself reaches another one, and the inner\n * call must not downgrade the outer one's origin.\n *\n * @template T\n * @param {() => T} fn\n * @returns {T}\n */\n _asUser(fn) {\n const previous = this._origin;\n this._origin = \"user\";\n try {\n return fn();\n } finally {\n this._origin = previous;\n }\n }\n\n /**\n * Wraps every function of an adapter object so the UI that calls it is\n * recorded as the origin. One call site per adapter instead of one per\n * action: a new action added to the inbox or the popover is stamped\n * without anyone having to remember to stamp it.\n *\n * @template {Record<string, any>} T\n * @param {T} actions\n * @returns {T}\n */\n _userActions(actions) {\n /** @type {Record<string, any>} */\n const wrapped = {};\n for (const [key, value] of Object.entries(actions)) {\n wrapped[key] =\n typeof value === \"function\"\n ? (/** @type {any[]} */ ...args) => this._asUser(() => value(...args))\n : value;\n }\n return /** @type {T} */ (wrapped);\n }\n\n /**\n * Tells the host about a failure it would otherwise only find in the\n * console. Every one of these is already survivable \u2014 the widget carries\n * on regardless \u2014 but \"the screenshot pipeline is broken\" is not something\n * a feedback tool should keep to itself.\n *\n * The console warning stays: a host without an `onError` must not lose the\n * diagnostic, and one with it is usually logging rather than replacing.\n *\n * @param {unknown} error\n * @param {import('./index.d.ts').ErrorContext} context\n */\n _reportError(error, context) {\n const handler = this.options.onError;\n if (typeof handler !== \"function\") return;\n try {\n handler(error, context);\n } catch (err) {\n console.warn(\"HellDots: onError handler threw\", err);\n }\n }\n\n /**\n * Hands one image to the host's `transformScreenshot`, so what ends up\n * stored can be a URL into its own storage instead of ~33KB of base64 in\n * every record.\n *\n * Never rejects, and never returns something a renderer cannot use: a\n * bucket that is down must not cost the user their comment, so a failed\n * transform degrades to the data URL the widget already holds and reports\n * itself through onError instead. The host receives a fat record rather\n * than none \u2014 the better of two bad outcomes.\n *\n * @param {string | null} dataUrl null passes straight through: no capture\n * was taken, and there is nothing to transform.\n * @param {\"context\" | \"attachment\"} kind\n * @param {import('./index.d.ts').CommentId} commentId\n * @returns {Promise<string | null>}\n */\n async _transformScreenshot(dataUrl, kind, commentId) {\n const transform = this.options.transformScreenshot;\n if (typeof transform !== \"function\" || !dataUrl) return dataUrl;\n try {\n const result = await transform(dataUrl, { kind, commentId });\n // A handler resolving to nothing usable is a failed handler; storing\n // it would put a broken <img> where the screenshot was.\n if (typeof result !== \"string\" || !result) {\n throw new Error(\n \"HellDots: transformScreenshot resolved to no usable string\"\n );\n }\n return result;\n } catch (err) {\n this._reportError(err, \"transform\");\n return dataUrl;\n }\n }\n\n /**\n * The one place a change leaves the widget. Fires the specific callback\n * that has always carried this event and then the onChange stream, so a\n * host can subscribe either way \u2014 or both \u2014 and never sees the two\n * disagree about what happened or when.\n *\n * Both shapes also receive the same `meta`: the specific callback takes it\n * as one extra trailing argument (existing handlers ignore it \u2014 that is\n * what makes this additive), and `onChange` gets its fields flattened onto\n * the event, alongside `comment`/`reply`/`id`.\n *\n * Host handlers are isolated: a subscriber that throws must not roll back\n * a mutation that already happened, and must not stop its sibling from\n * hearing about it either.\n *\n * @param {keyof typeof CHANGE_CALLBACKS} type\n * @param {any[]} callbackArgs arguments for the specific callback, in the\n * order it has always taken them\n * @param {Object} payload the event's own fields, minus `type`\n * @param {Object} [detail] event-specific metadata (`field`, `from`, `to`)\n * to travel next to `origin`\n */\n _emit(type, callbackArgs, payload, detail) {\n const meta = { origin: this._origin, ...detail };\n const name = CHANGE_CALLBACKS[type];\n const callback = this.options[name];\n if (typeof callback === \"function\") {\n try {\n callback(...callbackArgs, meta);\n } catch (err) {\n console.warn(`HellDots: ${name} handler threw`, err);\n }\n }\n if (typeof this.options.onChange === \"function\") {\n try {\n this.options.onChange({ type, ...payload, ...meta });\n } catch (err) {\n console.warn(\"HellDots: onChange handler threw\", err);\n }\n }\n }\n\n /** The shareable URL for a comment, as \"Copy link\" builds it. */\n commentLink(id) {\n const comment = this._findComment(id);\n return comment ? buildCommentLink(comment, this._linkParam()) : null;\n }\n\n /** The parsed corpus, re-read only after another tab invalidated it. */\n _readStoredCached() {\n if (!this._storedCache) this._storedCache = readStoredComments();\n return this._storedCache;\n }\n\n _syncStorage() {\n if (this.options.persistence !== \"localStorage\") return;\n const merged = mergeForStorage(\n this._readStoredCached(),\n this.serializeComments(),\n location.pathname\n );\n if (!writeStoredComments(merged)) {\n // Already warned about in detail by the writer, which shed what it\n // could before giving up. Worth surfacing anyway: from here on this\n // browser's copy silently diverges from what the user can see.\n this._reportError(\n new Error(\"HellDots: comments could not be persisted to localStorage\"),\n \"storage\"\n );\n }\n // The merge IS the new stored state (quota shedding only nulls\n // contextScreenshot in the written copy, which the next merge would\n // reattempt from memory anyway \u2014 same as before the cache existed).\n this._storedCache = merged;\n }\n\n bindEventListeners() {\n this.commentBtn.addEventListener(\"click\", () => this.toggleCommentMode());\n this.inboxBtn.addEventListener(\"click\", () => this.toggleInbox());\n this.eyeBtn?.addEventListener(\"click\", () =>\n this._setMarkersHidden(!this.markersHidden)\n );\n this.submitButton.addEventListener(\"click\", () => this.saveComment());\n\n this.commentInput.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n this.saveComment();\n }\n });\n\n this.attachImageBtn.addEventListener(\"click\", () => {\n this.attachImageInput.click();\n });\n\n wireScreenshotInput(\n this.attachImageInput,\n () => {\n if (!this._pendingScreenshots) this._pendingScreenshots = [];\n return this._pendingScreenshots;\n },\n () => this._updateScreenshotsPreview()\n );\n\n this._handleDocumentClickBound = (e) => this.handleDocumentClick(e);\n document.addEventListener(\"mousedown\", this._handleDocumentClickBound);\n }\n\n setupKeyboardShortcut() {\n // Remove any existing event listeners\n if (this.keydownHandler) {\n document.removeEventListener(\"keydown\", this.keydownHandler);\n }\n\n // Create a new handler with proper binding\n this.keydownHandler = (e) => {\n if (e.key === \"Escape\") {\n if (this._activeLightbox) {\n this.closeLightbox();\n } else if (this.activeThreadPopover) {\n // An open editor answers Escape first, and closes only itself. The\n // editor's own textarea stops the event before it reaches here, so\n // this branch is for an Escape pressed with focus somewhere else in\n // the popover \u2014 which must not take the panel down either.\n if (this._popover.isEditing()) this._popover.releaseEditor();\n else this.closeThreadPopover();\n } else if (this.inboxView?.isOpen()) {\n if (this.inboxView.editing) {\n this.inboxView\n .releaseEditor()\n .then((released) => released && this.inboxView?.refresh());\n } else {\n this.closeInbox();\n }\n } else if (this.commentBox.style.display !== \"none\") {\n this.hideCommentBox();\n this.toggleCommentMode();\n } else if (this.commentMode) {\n this.toggleCommentMode();\n }\n return;\n }\n\n // One matcher for default and custom chords alike \u2014 the old hardcoded\n // Alt+C fallbacks fired unconditionally, so a host that configured its\n // own shortcut got Alt+C on top of it with no way to turn it off.\n const key = this.options.shortcutKey.toLowerCase();\n const keyMatches =\n e.key.toLowerCase() === key ||\n // Option+letter on macOS (and AltGr layouts) types a dead or special\n // character (\"\u00E7\" for Option+C, \"\u02DA\" for Option+K), so e.key never\n // spells the configured letter there. e.code names the physical key\n // and is what makes Alt chords matchable at all.\n (e.altKey &&\n /^[a-z]$/.test(key) &&\n e.code === `Key${key.toUpperCase()}`);\n const modifierMatches =\n (this.options.shortcutModifier === \"alt\" && e.altKey) ||\n (this.options.shortcutModifier === \"ctrl\" &&\n (e.ctrlKey || e.metaKey)) ||\n (this.options.shortcutModifier === \"shift\" && e.shiftKey);\n\n if (keyMatches && modifierMatches) {\n e.preventDefault();\n e.stopPropagation();\n this.toggleCommentMode();\n }\n };\n\n // Add the event listener\n document.addEventListener(\"keydown\", this.keydownHandler);\n }\n\n handleDocumentClick(e) {\n if (!this.commentMode) return;\n\n // Listener is attached on `document`, outside the shadow boundary, so\n // `e.target` gets retargeted to the shadow host. Use composedPath() to\n // recover the real, deepest target inside the shadow tree.\n const target = e.composedPath()[0] || e.target;\n\n if (\n this.toolbar.contains(target) ||\n target?.closest?.(`.${CLASSES.CIRCLE}`) ||\n target?.closest?.(`.${CLASSES.TOOLTIP}`) ||\n target?.closest?.(`.${CLASSES.THREAD_POPOVER}`) ||\n target?.closest?.(`.${CLASSES.INBOX_PANEL}`) ||\n target?.closest?.(`.${CLASSES.LIGHTBOX}`)\n ) {\n return;\n }\n\n if (this.commentBox.contains(target)) {\n return;\n }\n\n if (this.commentBox.style.display !== \"none\") {\n this.hideCommentBox();\n this.toggleCommentMode();\n return;\n }\n\n if (e.button !== 0) return;\n e.preventDefault();\n\n this._captureFlow.beginDrag(e);\n }\n\n /**\n * The element a drag-selected region should anchor to: the topmost element\n * in the hit-test stack at the region's center whose intersection with the\n * region covers at least REGION_COVERAGE_MIN of its area. Reading the\n * stack (rather than walking ancestors) also handles overlays rendered in\n * portals, which are not ancestors of anything useful. Null when nothing\n * qualifies or the environment has no `elementsFromPoint` (jsdom).\n * @param {{ left: number, top: number, width: number, height: number }} region\n * @param {number} centerX\n * @param {number} centerY\n * @returns {Element | null}\n */\n _regionTarget(region, centerX, centerY) {\n if (typeof document.elementsFromPoint !== \"function\") return null;\n const area = region.width * region.height;\n if (!(area > 0)) return null;\n\n for (const el of document.elementsFromPoint(centerX, centerY)) {\n // Our own shadow host can appear in the stack even with the overlay's\n // pointer-events off (the toolbar, an open panel) \u2014 never a target.\n if (el.tagName.toLowerCase() === TAG_NAME.toLowerCase()) continue;\n const rect = el.getBoundingClientRect();\n const overlapX =\n Math.min(rect.right, region.left + region.width) -\n Math.max(rect.left, region.left);\n const overlapY =\n Math.min(rect.bottom, region.top + region.height) -\n Math.max(rect.top, region.top);\n if (overlapX <= 0 || overlapY <= 0) continue;\n if ((overlapX * overlapY) / area >= REGION_COVERAGE_MIN) return el;\n }\n return null;\n }\n\n /**\n * @param {number} clientX\n * @param {number} clientY\n * @param {{ left: number, top: number, width: number, height: number }} [region]\n * Present when the placement comes from a drag: the selected rectangle,\n * in viewport coordinates. `clientX/clientY` is then its center.\n */\n async _placeCommentAtPoint(clientX, clientY, region) {\n // The no-drag path has no render yet \u2014 kick the background capture off\n // now so it resolves while the user types (see capture-flow.js).\n this._captureFlow.armClickCapture();\n\n const prevPointerEvents = this.overlay.style.pointerEvents;\n this.overlay.style.pointerEvents = \"none\";\n const underlying =\n (region ? this._regionTarget(region, clientX, clientY) : null) ||\n document.elementFromPoint(clientX, clientY);\n this.overlay.style.pointerEvents = prevPointerEvents || \"\";\n\n const container =\n underlying?.closest?.(SELECTORS.CONTAINER) || document.body;\n const containerRect = container.getBoundingClientRect();\n\n // Zero-size containers (display:none, not yet laid out) would make the\n // division blow up to Infinity \u2014 which isn't JSON-serializable either.\n const relativeX =\n containerRect.width > 0\n ? (clientX - containerRect.left) / containerRect.width\n : 0;\n const relativeY =\n containerRect.height > 0\n ? (clientY - containerRect.top) / containerRect.height\n : 0;\n\n const anchor = createAnchor(\n /** @type {HTMLElement} */ (container),\n relativeX,\n relativeY\n );\n // The clicked element can disappear (responsive display:none) while the\n // coarse anchor container stays visible \u2014 track it separately so the\n // marker hides with what the user actually commented on.\n anchor.targetSelector =\n underlying && underlying !== container\n ? generateElementSelector(/** @type {HTMLElement} */ (underlying))\n : null;\n\n this.currentPosition = {\n container,\n relativeX,\n relativeY,\n anchor,\n target: /** @type {HTMLElement} */ (underlying || container),\n };\n\n this.createPreviewCircle(clientX, clientY);\n document.body.classList.remove(CLASSES.COMMENT_CURSOR);\n\n if (this._pendingScreenshots?.length > 0 || this._regionCapturePending) {\n this._updateScreenshotsPreview();\n }\n\n this.showCommentBox(clientX, clientY);\n }\n\n _updateScreenshotsPreview() {\n const container = this.commentBox.querySelector(\n `.${CLASSES.SCREENSHOTS_CONTAINER}`\n );\n if (!container) return;\n renderScreenshotsPreview(container, this._pendingScreenshots || [], {\n strings: this.strings,\n onShow: (dataUrl) => this.showLightbox(dataUrl),\n rerender: () => this._updateScreenshotsPreview(),\n pending: this._regionCapturePending ? 1 : 0,\n });\n }\n\n _clearScreenshotPreview() {\n this._pendingScreenshots = [];\n this._regionCapturePending = false;\n const container = this.commentBox.querySelector(\n `.${CLASSES.SCREENSHOTS_CONTAINER}`\n );\n if (container) {\n container.innerHTML = \"\";\n container.classList.remove(CLASSES.ACTIVE);\n }\n }\n\n showCommentBox(x, y) {\n this.commentBox.style.display = \"block\";\n\n const circleBaseSize = MARKER_SIZE;\n const circleRadius = circleBaseSize / 2;\n const offset = circleRadius + 10;\n const windowWidth = window.innerWidth;\n const windowHeight = window.innerHeight;\n\n // Measured, not assumed: the box is 400px wide on a roomy viewport but\n // narrows to `100vw - 24px` on a phone. A hardcoded width here is what\n // used to push it off the right edge on mobile.\n const boxRect = this.commentBox.getBoundingClientRect();\n const boxWidth = boxRect.width || 400;\n\n const centerX = x + circleRadius;\n const centerY = y + circleRadius;\n\n let adjustedX = centerX + offset;\n let adjustedY = centerY - circleRadius;\n\n if (adjustedX + boxWidth > windowWidth) {\n adjustedX = centerX - offset - boxWidth;\n }\n // Clamp both edges: on a viewport narrower than the box plus its\n // margins, flipping to the other side isn't enough on its own.\n adjustedX = Math.min(adjustedX, windowWidth - boxWidth - 10);\n adjustedX = Math.max(10, adjustedX);\n\n if (adjustedY + boxRect.height > windowHeight) {\n adjustedY = windowHeight - boxRect.height - 10;\n }\n adjustedY = Math.max(10, adjustedY);\n\n this.commentBox.style.left = `${adjustedX}px`;\n this.commentBox.style.top = `${adjustedY}px`;\n\n this.commentInput.value = \"\";\n setTimeout(() => this.commentInput.focus(), 50);\n }\n\n hideCommentBox() {\n this.commentBox.style.display = \"none\";\n this.commentInput.style.height = \"auto\";\n this.currentPosition = null;\n this.removePreviewCircle();\n this._clearScreenshotPreview();\n this._captureFlow?.clearPending();\n /** @type {any} */ (this.commentBox).classify?.reset();\n\n if (this.commentMode) {\n document.body.classList.add(CLASSES.COMMENT_CURSOR);\n }\n }\n\n /**\n * The eye toggle's single entry point: the button, the mount read, and\n * both auto-reshow paths all land here. The circles hide via a CSS\n * class on the mount container (the marker engine keeps running, so\n * re-showing is instant and correctly placed); the thread popover and\n * any open hover tooltip are dismissed imperatively instead, since\n * neither mounts inside that container.\n * @param {boolean} hidden\n */\n _setMarkersHidden(hidden) {\n this.markersHidden = hidden;\n this.overlay.classList.toggle(CLASSES.MARKERS_HIDDEN, hidden);\n if (hidden) {\n this.closeThreadPopover();\n // Tooltips mount on the shadow root, a sibling of `this.overlay`, so\n // the CSS hide rule never reaches them \u2014 an open one would otherwise\n // survive a keyboard-activated hide, orphaned over a page with no\n // visible marker to anchor it. No new tooltip can appear while\n // hidden: a display:none circle gets no hover events.\n this.shadowRoot\n .querySelectorAll(`.${CLASSES.TOOLTIP}`)\n .forEach((tooltip) => tooltip.remove());\n }\n\n if (this.eyeBtn) {\n // No aria-pressed here: the button's name swaps (Hide comments \u2194 Show\n // comments), and a swapping name plus a pressed state contradict each\n // other for screen-reader users (see DECISIONS.md). The name itself\n // carries the state, same as the icon and tooltip below.\n const label = hidden\n ? this.strings.toolbarShowComments\n : this.strings.toolbarHideComments;\n this.eyeBtn.setAttribute(\"aria-label\", label);\n this.eyeBtn.innerHTML = hidden ? EYE_OFF_ICON_SVG : EYE_ICON_SVG;\n const text = this.eyeBtn\n .closest(`.${CLASSES.TOOLBAR_ACTION_WRAPPER}`)\n ?.querySelector(`.${CLASSES.TOOLBAR_TEXT}`);\n if (text) text.textContent = label;\n }\n\n // Written on every change \u2014 including the automatic re-shows \u2014 so a\n // later reload matches what the viewer last saw.\n try {\n localStorage.setItem(MARKERS_HIDDEN_STORAGE_KEY, String(hidden));\n } catch {\n /* preference just does not persist */\n }\n }\n\n toggleCommentMode() {\n this.commentMode = !this.commentMode;\n // The inbox is a full-height panel over the page; leaving it open would\n // cover the very content the user now has to click on. Clicking the\n // toolbar button already closed it as an outside click \u2014 this is what\n // covers the keyboard shortcut and the empty state's own button.\n if (this.commentMode) this.closeInbox();\n // Someone about to comment wants to see the existing comments; the\n // shortcut funnels through here too. Leaving the mode does not re-hide.\n if (this.commentMode && this.markersHidden) this._setMarkersHidden(false);\n this.commentBtn?.classList.toggle(CLASSES.ACTIVE, this.commentMode);\n this.commentBtn?.setAttribute(\"aria-pressed\", String(this.commentMode));\n this.overlay.classList.toggle(CLASSES.ACTIVE, this.commentMode);\n document.body.classList.toggle(CLASSES.COMMENT_CURSOR, this.commentMode);\n\n if (!this.commentMode) {\n this.hideCommentBox();\n }\n\n // Every path lands here \u2014 the toolbar button, the keyboard shortcut, the\n // inbox empty state, and the automatic switch-off after a save \u2014 so the\n // host hears about the mode however it was flipped, including by the\n // shortcut it never sees.\n this._notify(\"onCommentModeChanged\", [this.commentMode]);\n }\n\n async saveComment() {\n // Two Enters while the capture resolves must not save twice.\n if (this._saving) return;\n if (!this.commentInput.value.trim() || !this.currentPosition) return;\n this._saving = true;\n // The guard above already made the second click a no-op; disabling says\n // so. With a host's upload behind the save this is no longer instant,\n // and a button that looks live but does nothing reads as broken.\n if (this.submitButton) this.submitButton.disabled = true;\n try {\n await this._saveCommentNow();\n } finally {\n this._saving = false;\n if (this.submitButton) this.submitButton.disabled = false;\n }\n }\n\n async _saveCommentNow() {\n // Everything this save is about, read before the first await: the draft\n // it belongs to and the text that was in the box at that moment. What\n // has to be caught is not only \"the box was dismissed\" but \"dismissed\n // and a *different* one opened\" \u2014 a window now seconds long, because a\n // host's upload sits inside it. A truthiness check misses the second\n // case and would write this comment onto the next draft's anchor.\n const position = this.currentPosition;\n const text = this.commentInput.value;\n // The capture kicked off when the box opened; by save time it has\n // usually resolved and this await costs nothing.\n const captured = await this._captureFlow.consumePending();\n // The box may have been dismissed (Escape) while awaiting \u2014 a save that\n // lands after that would contradict what the user sees on screen.\n if (this.currentPosition !== position) return;\n\n // Generated ahead of the transform rather than inside the object below,\n // so a host can name its blobs after the comment they belong to.\n const id = createId();\n const attachments = this._pendingScreenshots\n ? [...this._pendingScreenshots]\n : [];\n\n // In parallel: up to six images, one wait rather than six.\n const [contextScreenshot, screenshots] = await Promise.all([\n this._transformScreenshot(captured, \"context\", id),\n Promise.all(\n attachments.map((dataUrl) =>\n this._transformScreenshot(dataUrl, \"attachment\", id)\n )\n ),\n ]);\n\n // Checked again: unlike the capture above, the transform is the host's\n // network, so the box has had a real chance to be dismissed under it \u2014\n // and replaced by a draft somewhere else on the page.\n if (this.currentPosition !== position) return;\n\n const comment = {\n text,\n container: position.container,\n relativeX: position.relativeX,\n relativeY: position.relativeY,\n anchor: position.anchor,\n anchorState: \"anchored\",\n target: position.target,\n hidden: false,\n status: \"open\",\n page: location.pathname,\n id,\n replies: [],\n author: this.options.user?.name || this.strings.anonymous,\n authorId: normalizeActorId(this.options.user?.id) || null,\n createdAt: new Date().toISOString(),\n screenshots,\n type: /** @type {any} */ (this.commentBox).classify?.getType() ?? null,\n priority:\n /** @type {any} */ (this.commentBox).classify?.getPriority() ?? null,\n // No longer authored in the widget \u2014 kept on the model for\n // setCommentTags() and for comments imported through loadComments().\n tags: [],\n resolvedAt: null,\n context: captureContext(),\n contextScreenshot,\n };\n\n recordEvent(comment, \"created\", this._actor());\n\n this.comments.push(comment);\n this._syncStorage();\n const created = this._serializeComment(comment);\n // The comment box is widget UI like any other, but it reaches _emit\n // directly instead of through an adapter, so it stamps its own origin.\n this._asUser(() =>\n this._emit(\"comment:created\", [created], { comment: created })\n );\n this.renderCommentCircle(comment);\n this.hideCommentBox();\n this.toggleCommentMode();\n\n const circle = this._circles.get(String(comment.id));\n if (circle) {\n this.showThreadPopover(circle, comment);\n }\n }\n\n renderCommentCircle(comment) {\n this.markers.render(comment);\n }\n\n /**\n * What a marker opens when interacted with \u2014 tooltip on hover, thread\n * popover on activation. UI wiring only; the engine calls this once per\n * circle it creates and owns everything about position and visibility.\n */\n _wireMarker(circle, comment) {\n circle.addEventListener(\"mouseenter\", () =>\n this.showCommentTooltip(circle, comment)\n );\n circle.addEventListener(\"mouseleave\", () => {\n setTimeout(() => {\n const tooltip = this._tooltipEl(comment.id);\n if (tooltip && !tooltip.matches(\":hover\")) {\n tooltip.remove();\n }\n }, 250);\n });\n\n circle.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n this._tooltipEl(comment.id)?.remove();\n // The marker toggles its own thread: clicking the active marker\n // closes it rather than tearing the popover down and rebuilding an\n // identical one. Only its own \u2014 clicking a different marker still\n // switches to that thread.\n if (this.activeThreadPopover?.dataset.for === String(comment.id)) {\n this.closeThreadPopover();\n return;\n }\n this.showThreadPopover(circle, comment);\n });\n\n // The circle is a <div role=\"button\">, so unlike a real <button> it\n // doesn't get Enter/Space-activates-click for free.\n circle.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n circle.click();\n }\n });\n }\n\n showCommentTooltip(circle, comment) {\n const existingPopover = this.shadowRoot.querySelector(\n `.${CLASSES.THREAD_POPOVER}[data-for=\"${cssAttrValue(comment.id)}\"]`\n );\n if (existingPopover) return;\n\n if (this._tooltipEl(comment.id)) return;\n\n const tooltip = createTooltip(comment, this.strings, this.locale);\n this.shadowRoot.appendChild(tooltip);\n\n wireScreenshotLightbox(tooltip, (src) => this.showLightbox(src));\n\n setTimeout(() => {\n positionPopoverAtCircle(tooltip, circle);\n }, 10);\n\n tooltip\n .querySelector(`.${CLASSES.CLOSE_TOOLTIP}`)\n .addEventListener(\"click\", (e) => {\n e.stopPropagation();\n tooltip.remove();\n });\n\n tooltip.addEventListener(\"mouseleave\", () => tooltip.remove());\n }\n\n toggleInbox() {\n if (this.inboxView?.isOpen()) {\n this.closeInbox();\n } else {\n this.showInbox();\n }\n }\n\n showInbox() {\n this.closeThreadPopover();\n\n if (!this.inboxView) {\n this.inboxView = new InboxView({\n shadowRoot: this.shadowRoot,\n strings: this.strings,\n locale: this.locale,\n currentPage: location.pathname,\n getComments: () => this.comments,\n options: this.options,\n // Same as the popover's: everything here is user-driven.\n callbacks: this._userActions({\n onActivateCommentMode: () => {\n this.closeInbox();\n // Never a toggle: the button reads \"turn on comment mode\", so\n // pressing it while the mode is already on must not turn it off.\n if (!this.commentMode) this.toggleCommentMode();\n },\n onOpenDetailScroll: (comment) => this.scrollMarkerIntoView(comment),\n onOpenDetail: (comment) => this._notifyCommentOpened(comment),\n onTransformScreenshot: (dataUrl, commentId) =>\n this._transformScreenshot(dataUrl, \"attachment\", commentId),\n onReply: (comment, text, screenshots) =>\n this.addReply(comment, text, screenshots),\n onDelete: (id) => this.deleteComment(id),\n onDeleteReply: (commentId, replyId) =>\n this.deleteReply(commentId, replyId),\n onEditComment: (id, text) => {\n if (!this.editComment(id, text)) return;\n // The marker's hover tooltip and the open thread both quote the\n // text that just changed, and neither rebuilds on its own.\n this._popover.refreshCommentViews(id);\n },\n onEditReply: (commentId, replyId, text) => {\n if (!this.editReply(commentId, replyId, text)) return;\n this._popover.refreshCommentViews(commentId);\n },\n actorKey: () => this._actorKey(),\n can: (action, target) => this.can(action, target),\n onToggleCommentReaction: (id, emoji) =>\n this.toggleCommentReaction(id, emoji),\n onToggleReplyReaction: (commentId, replyId, emoji) =>\n this.toggleReplyReaction(commentId, replyId, emoji),\n onExportComments: (comments) => this.exportCommentsCsv(comments),\n onExportMetrics: (comments) => this.exportMetricsCsv(comments),\n onPrintReport: (comments, scope) =>\n this.printMetricsReport(comments, scope),\n onSetStatus: (id, status) => this.setCommentStatus(id, status),\n onSetType: (id, type) => this.setCommentType(id, type),\n onSetPriority: (id, priority) =>\n this.setCommentPriority(id, priority),\n onNavigateToPage: (comment) => {\n try {\n sessionStorage.setItem(PENDING_DETAIL_KEY, String(comment.id));\n } catch {}\n this._navigateTo(comment.page);\n },\n onShowLightbox: (src) => this.showLightbox(src),\n onClose: () => this.closeInbox(),\n }),\n });\n }\n this.inboxView.open();\n\n // Both fields hold one thing, so whatever is already in them has to go\n // first: re-opening an open inbox (notifyNavigation re-reads the deep\n // link on every route change) otherwise orphaned the previous timer and\n // handler where closeInbox could no longer reach them.\n this._disarmInboxOutsideClick();\n\n // Deferred like the thread popover's, and cancellable for the same\n // reason: closeInbox() (via cleanup(), or a host that opens and unmounts\n // in one tick) may run before the timer, and a listener installed after\n // that has nothing left to remove it.\n this._inboxClickTimer = setTimeout(() => {\n this._inboxClickTimer = null;\n this._inboxClickHandler = (e) => {\n const target = e.composedPath()[0] || e.target;\n if (\n !this.inboxView.el?.contains(target) &&\n !this.inboxBtn.contains(target) &&\n !this._isInsideLightbox(target)\n ) {\n // Same reasoning as the thread popover: an unsaved draft turns a\n // click outside into \"stay open\", not into a question.\n if (this.inboxView.isDirty()) return;\n this.closeInbox();\n }\n };\n document.addEventListener(\"mousedown\", this._inboxClickHandler);\n }, 0);\n }\n\n closeInbox() {\n this.inboxView?.close();\n this._disarmInboxOutsideClick();\n }\n\n /**\n * Drops the inbox's outside-click listener, whether it is already on\n * `document` or still sitting in a pending timer. One place, so re-opening\n * and closing cannot each remember a different half of it.\n */\n _disarmInboxOutsideClick() {\n if (this._inboxClickTimer) {\n clearTimeout(this._inboxClickTimer);\n this._inboxClickTimer = null;\n }\n if (this._inboxClickHandler) {\n document.removeEventListener(\"mousedown\", this._inboxClickHandler);\n this._inboxClickHandler = null;\n }\n }\n\n /**\n * The open thread popover element, or null. Lives on the controller;\n * surfaced under its historical name because the inbox, the marker\n * engine paths and the test suite all read it here.\n */\n get activeThreadPopover() {\n return this._popover?.active ?? null;\n }\n\n // `circle` may be null for orphaned comments (opened from the inbox):\n // the popover is centered in the viewport instead of pinned to a marker.\n showThreadPopover(circle, comment) {\n this._popover.show(circle, comment);\n this._notifyCommentOpened(comment);\n }\n\n /**\n * Someone is now looking at a comment's full thread \u2014 from its marker or\n * from the inbox detail, which are the only two places the replies are\n * readable. This is the signal an unread count is built on; the widget\n * keeps no read state of its own, because whose \"read\" it is depends on an\n * identity only the host can persist.\n *\n * @param {any} comment the live comment, serialized on the way out like\n * every other payload that crosses this boundary\n */\n _notifyCommentOpened(comment) {\n if (!comment) return;\n this._notify(\"onCommentOpened\", [this._serializeComment(comment)]);\n }\n\n /**\n * Calls one of the options that is not part of the change stream, with the\n * same isolation `_emit` gives the ones that are: a subscriber that throws\n * must not take down the operation that was reporting to it.\n *\n * @param {\"onCommentModeChanged\" | \"onCommentOpened\"} name\n * @param {any[]} args\n */\n _notify(name, args) {\n // Cast: the two options have different signatures, so the union of them\n // takes no spread. The call sites below are the only ones, and each\n // passes what its own option declares.\n const handler = /** @type {any} */ (this.options[name]);\n if (typeof handler !== \"function\") return;\n try {\n handler(...args);\n } catch (err) {\n console.warn(`HellDots: ${name} handler threw`, err);\n }\n }\n\n closeThreadPopover() {\n // cleanup() reaches here before initOverlay() has run when the document\n // was still loading, so there may be no controller yet.\n this._popover?.close();\n }\n\n syncThreadPopoverToMarker() {\n this._popover?.syncToMarker();\n }\n\n showLightbox(imageSrc) {\n this.closeLightbox();\n\n // Whoever opened the lightbox (a thumbnail in the shadow tree, or a\n // host-page element) gets focus back when it closes.\n this._lightboxReturnFocus =\n this.shadowRoot.activeElement || document.activeElement;\n\n const lightbox = document.createElement(\"div\");\n lightbox.className = CLASSES.LIGHTBOX;\n lightbox.setAttribute(\"role\", \"dialog\");\n lightbox.setAttribute(\"aria-modal\", \"true\");\n lightbox.setAttribute(\"aria-label\", this.strings.screenshotPreview);\n\n const img = document.createElement(\"img\");\n img.className = CLASSES.LIGHTBOX_IMG;\n img.src = imageSrc;\n img.alt = this.strings.screenshotPreview;\n\n const closeBtn = document.createElement(\"button\");\n closeBtn.type = \"button\";\n closeBtn.className = CLASSES.LIGHTBOX_CLOSE;\n closeBtn.setAttribute(\"aria-label\", this.strings.close);\n closeBtn.innerHTML = \"×\";\n closeBtn.addEventListener(\"click\", () => this.closeLightbox());\n\n lightbox.appendChild(img);\n lightbox.appendChild(closeBtn);\n\n lightbox.addEventListener(\"click\", (e) => {\n if (e.target === lightbox) this.closeLightbox();\n });\n\n this.shadowRoot.appendChild(lightbox);\n this._activeLightbox = lightbox;\n\n // aria-modal is a promise about focus: the page behind the backdrop must\n // be unreachable. The close button is the only stop, so the trap is a\n // re-focus rather than a ring walk \u2014 same reasoning as confirm-dialog,\n // and on document in the capture phase for the same reason.\n this._lightboxKeydownHandler = (e) => {\n if (e.key !== \"Tab\") return;\n e.preventDefault();\n closeBtn.focus();\n };\n document.addEventListener(\"keydown\", this._lightboxKeydownHandler, true);\n\n closeBtn.focus();\n }\n\n closeLightbox() {\n if (!this._activeLightbox) return;\n if (this._lightboxKeydownHandler) {\n document.removeEventListener(\n \"keydown\",\n this._lightboxKeydownHandler,\n true\n );\n this._lightboxKeydownHandler = null;\n }\n this._activeLightbox.remove();\n this._activeLightbox = null;\n const returnFocus = /** @type {HTMLElement | null} */ (\n this._lightboxReturnFocus\n );\n this._lightboxReturnFocus = null;\n if (returnFocus?.isConnected) returnFocus.focus?.();\n }\n\n // The lightbox is opened *from* the inbox and the thread popover but lives\n // as their sibling in the shadow root, so a naive \"is this click outside my\n // element?\" test reads every click on it \u2014 including its own close button \u2014\n // as a click away from the panel, and tears the panel down behind it.\n _isInsideLightbox(target) {\n return Boolean(target?.closest?.(`.${CLASSES.LIGHTBOX}`));\n }\n\n /**\n * The one lookup every id-taking method goes through. Uses sameId so a\n * legacy numeric id resolves no matter which spelling the caller holds \u2014\n * index.d.ts promises exactly that.\n * @param {import('./index.d.ts').CommentId} id\n */\n _findComment(id) {\n return this.comments.find((c) => sameId(c.id, id));\n }\n\n /**\n * The hover tooltip currently open for a comment, if any. The one place\n * the `[data-for]` selector is built, so a host id carrying a quote is\n * escaped once instead of at five call sites.\n * @param {import('./index.d.ts').CommentId} id\n * @returns {HTMLElement | null}\n */\n _tooltipEl(id) {\n return (\n this.shadowRoot?.querySelector(\n `.${CLASSES.TOOLTIP}[data-for=\"${cssAttrValue(id)}\"]`\n ) ?? null\n );\n }\n\n /**\n * @param {import('./index.d.ts').Comment | import('./index.d.ts').CommentId} commentOrId\n * the live comment, or its id \u2014 every sibling mutator takes an id, so\n * this one stopped being the exception.\n * @param {string} text\n * @param {string[]} [screenshots]\n * @returns {import('./index.d.ts').CommentReply | null} null when an id\n * does not resolve\n */\n addReply(commentOrId, text, screenshots = []) {\n const comment =\n typeof commentOrId === \"object\" && commentOrId !== null\n ? commentOrId\n : this._findComment(\n /** @type {import('./index.d.ts').CommentId} */ (commentOrId)\n );\n if (!comment) return null;\n if (!comment.replies) comment.replies = [];\n const reply = {\n id: createId(),\n editedAt: null,\n text,\n author: this.options.user?.name || this.strings.anonymous,\n authorId: normalizeActorId(this.options.user?.id) || null,\n timestamp: new Date().toISOString(),\n screenshots,\n };\n comment.replies.push(reply);\n this._syncStorage();\n const serialized = this._serializeComment(comment);\n const serializedReply = this._serializeReply(reply);\n this._emit(\"reply:added\", [serialized, serializedReply], {\n comment: serialized,\n reply: serializedReply,\n });\n return reply;\n }\n\n /**\n * Removes one reply from a thread. The root comment is untouched \u2014 deleting\n * the last reply leaves the comment itself standing, which is why this is\n * separate from deleteComment rather than a special case of it.\n *\n * @param {import('./index.d.ts').CommentId} commentId\n * @param {import('./index.d.ts').CommentId} replyId\n * @returns {boolean} false when either id does not resolve\n */\n deleteReply(commentId, replyId) {\n const comment = this._findComment(commentId);\n const index =\n comment?.replies?.findIndex((r) => sameId(r.id, replyId)) ?? -1;\n if (index < 0) return false;\n const target = replyTargetOf(comment.replies[index], comment.id);\n if (!this._permits(\"delete:reply\", target)) return false;\n\n const [reply] = comment.replies.splice(index, 1);\n this._syncStorage();\n const serialized = this._serializeComment(comment);\n const serializedReply = this._serializeReply(reply);\n this._emit(\"reply:deleted\", [serialized, serializedReply], {\n comment: serialized,\n reply: serializedReply,\n });\n return true;\n }\n\n /**\n * Rewrites a comment's text and stamps `editedAt`.\n *\n * Refuses an empty body: a comment with no text keeps its marker, its\n * replies and its inbox row while saying nothing, so blanking is not a\n * back door to deletion \u2014 deleting is its own action and it asks first.\n * Refuses a no-op too, so opening the editor and saving without typing\n * does not brand the comment as edited.\n *\n * @param {import('./index.d.ts').CommentId} id\n * @param {string} text\n * @returns {boolean} false when the id does not resolve, or nothing changed\n */\n editComment(id, text) {\n const comment = this._findComment(id);\n const next = String(text ?? \"\").trim();\n if (!comment || !next || next === comment.text) return false;\n if (!this._permits(\"edit:comment\", commentTargetOf(comment))) return false;\n\n comment.text = next;\n comment.editedAt = new Date().toISOString();\n // The text itself is deliberately not recorded: the log says who changed\n // what and when, and keeping every superseded revision would turn it into\n // a second copy of the corpus.\n recordEvent(comment, \"edited\", this._actor());\n // The marker's accessible name is the comment text \u2014 a screen-reader\n // user tabbing to it must hear the current sentence, not the old one.\n this._circles\n .get(String(comment.id))\n ?.setAttribute(\n \"aria-label\",\n `${this.strings.commentAriaLabelPrefix}${comment.text}`\n );\n this._syncStorage();\n const edited = this._serializeComment(comment);\n this._emit(\"comment:edited\", [edited], { comment: edited });\n return true;\n }\n\n /**\n * Same contract as editComment, one level down.\n *\n * @param {import('./index.d.ts').CommentId} commentId\n * @param {import('./index.d.ts').CommentId} replyId\n * @param {string} text\n * @returns {boolean} false when either id does not resolve, or nothing changed\n */\n editReply(commentId, replyId, text) {\n const comment = this._findComment(commentId);\n const reply = comment?.replies?.find((r) => sameId(r.id, replyId));\n const next = String(text ?? \"\").trim();\n if (!reply || !next || next === reply.text) return false;\n if (!this._permits(\"edit:reply\", replyTargetOf(reply, comment.id))) {\n return false;\n }\n\n reply.text = next;\n reply.editedAt = new Date().toISOString();\n this._syncStorage();\n const serialized = this._serializeComment(comment);\n const serializedReply = this._serializeReply(reply);\n this._emit(\"reply:edited\", [serialized, serializedReply], {\n comment: serialized,\n reply: serializedReply,\n });\n return true;\n }\n\n _serializeReply({\n id,\n text,\n author,\n authorId,\n timestamp,\n screenshots,\n editedAt,\n // Defaulted, not just optional: addReply builds a reply without the field,\n // and a fresh reply has nothing to serialize yet.\n reactions = null,\n }) {\n return {\n id,\n text,\n author,\n authorId: authorId || null,\n timestamp,\n screenshots: screenshots || [],\n editedAt: editedAt || null,\n reactions: serializeReactions(reactions),\n };\n }\n\n /**\n * Serializable snapshot of one comment: the live `container` element is\n * replaced by its `anchor`. Screenshots (data-URLs) are included \u2014 the\n * localStorage mode and the inbox cards need them.\n */\n _serializeComment(comment) {\n return {\n // The anchor and context sub-objects always carried a version; the\n // comment gets one too so future breaking changes have a hinge \u2014\n // purely additive, loadComments ignores it today.\n schemaVersion: 1,\n id: comment.id,\n text: comment.text,\n editedAt: comment.editedAt || null,\n anchor: comment.anchor || null,\n page: comment.page || location.pathname,\n replies: (comment.replies || []).map((reply) =>\n this._serializeReply(reply)\n ),\n author: comment.author,\n // Identity, not copy: the display name is what any renderer shows, and\n // this is what a host correlates against its own user table.\n authorId: comment.authorId || null,\n // Copied out for the same reason as tags and reactions, and null rather\n // than [] when nothing was recorded, so an untouched corpus costs no\n // extra bytes.\n history: serializeHistory(comment.history),\n createdAt: comment.createdAt,\n screenshots: comment.screenshots || [],\n status: comment.status || \"open\",\n type: comment.type || null,\n priority: comment.priority || null,\n // Copied rather than referenced: a host mutating serializeComments()\n // output must not be able to reach back into overlay internals.\n tags: comment.tags ? [...comment.tags] : [],\n // Copied for the same reason as `tags`, and null rather than `{}` when\n // nobody reacted, so an untouched corpus costs no extra bytes.\n reactions: serializeReactions(comment.reactions),\n resolvedAt: comment.resolvedAt || null,\n context: comment.context ? { ...comment.context } : null,\n contextScreenshot: comment.contextScreenshot || null,\n };\n }\n\n /**\n * RF09 \u2014 moves a comment through its lifecycle\n * (open \u2192 in_progress \u2192 in_review \u2192 resolved, in any order).\n * @param {import('./index.d.ts').CommentId} id\n * @param {import('./index.d.ts').CommentStatus} status\n * @returns {boolean} false when the id or status is unknown\n */\n setCommentStatus(id, status) {\n if (!STATUSES.includes(status)) return false;\n const comment = this._findComment(id);\n if (!comment) return false;\n // No-op: picking the status the comment is already in must not re-stamp\n // resolvedAt (that would reset RF5's elapsed time to \"<1m\") or trigger a\n // storage write / inbox refresh / callback for nothing having changed.\n if (comment.status === status) return true;\n const previous = comment.status;\n comment.status = status;\n // RF5 \u2014 the timestamp always describes the CURRENT resolution: a\n // reopened comment loses it, and resolving again re-stamps it.\n comment.resolvedAt =\n status === \"resolved\" ? new Date().toISOString() : null;\n // After the no-op guard above, never before it: an entry recorded there\n // would log a change that did not happen.\n recordEvent(comment, \"status\", this._actor(), {\n from: previous,\n to: status,\n });\n // Resolving removes the on-page marker; reopening restores it. The\n // lookup goes through the comment's own id, not the caller's spelling.\n const circle = this._circles.get(String(comment.id));\n if (circle) this.updateCommentPosition(comment, circle);\n this._syncStorage();\n // Re-render the inbox so the card picks up the new status right away\n // (resolved styling + sink-to-bottom sorting) no matter where the\n // change came from \u2014 card, detail or thread popover.\n if (this.inboxView?.isOpen()) this.inboxView.refresh();\n const changed = this._serializeComment(comment);\n // Both ends of the move, the same pair the audit entry just recorded: a\n // host routing \"reopened\" or \"resolved\" differently should not have to\n // diff against its own previous copy to find out which one happened.\n this._emit(\n \"comment:status-changed\",\n [changed],\n { comment: changed },\n {\n from: previous,\n to: status,\n }\n );\n return true;\n }\n\n /**\n * Replaces the identity new comments, replies and reactions are attributed\n * to. Everything already recorded keeps the author it was written with \u2014\n * this is a change of who is acting now, not a rewrite of history.\n *\n * Exists because identity commonly arrives *after* the widget: the overlay\n * mounts, the session resolves 200ms later, and the alternative was\n * `cleanup()` plus a rebuild \u2014 which throws away every loaded comment and\n * whatever panel was open. Passing `null` returns to the anonymous author.\n *\n * @param {{ name: string, id?: string } | null} [user]\n * @returns {boolean} false when the argument is neither null nor an object\n * carrying a usable name\n */\n setUser(user) {\n if (user != null) {\n if (typeof user !== \"object\") return false;\n if (typeof user.name !== \"string\" || !user.name.trim()) return false;\n }\n this.options.user = user ?? undefined;\n // Both panels read the actor through a function rather than a captured\n // value, so all they need is a re-render: which reactions are shown as\n // the current user's own changes with the identity.\n this._popover?.close();\n if (this.inboxView?.isOpen()) this.inboxView.refresh();\n return true;\n }\n\n /**\n * The current actor as the audit log records them: id when the host\n * supplies one, plus the display name. Sibling of _actorKey \u2014 one produces\n * a de-duplication key, the other a record meant to be read back.\n * @returns {{ id?: string, name: string }}\n */\n _actor() {\n return actorOf(this.options.user, this.strings);\n }\n\n /**\n * The key the current actor's reactions are stored under. `user.id` when the\n * host supplies one, the display name otherwise \u2014 see actorKeyOf, which is\n * the only place this is decided so the toggle and the \"mine\" render can\n * never disagree.\n * @returns {string}\n */\n _actorKey() {\n return actorKeyOf(this.options.user, this.strings);\n }\n\n /**\n * Whether the current actor may edit or delete the record `target` names.\n *\n * Public because the answer has to be askable from outside: the widget's\n * own menus use it to decide what to render, and a host putting a delete\n * button in its own chrome needs the same verdict from the same rule\n * rather than a second copy of it that drifts.\n *\n * @param {import('./index.d.ts').PermissionAction} action\n * @param {import('./index.d.ts').PermissionTarget} target\n * @returns {boolean}\n */\n can(action, target) {\n return resolvePermission({\n can: this.options.can,\n action,\n target,\n user: this.options.user,\n strings: this.strings,\n });\n }\n\n /**\n * The guard the four mutators are held to \u2014 and only when the click came\n * from inside the widget.\n *\n * A call from the host's own code is never refused. That is the whole\n * reason this reads `_origin` instead of calling `can` directly: the host\n * drives the very same public methods the inbox does, and a backend that\n * has just authorized a moderator's delete must be able to complete it\n * without arguing with a client-side rule it already outranks.\n *\n * @param {import('./index.d.ts').PermissionAction} action\n * @param {import('./index.d.ts').PermissionTarget} target\n * @returns {boolean}\n */\n _permits(action, target) {\n return this._origin !== \"user\" || this.can(action, target);\n }\n\n /**\n * Shared tail of both reaction toggles: persist, keep an open inbox in step,\n * and hand the host the comment plus whichever reply carried the reaction.\n * @param {any} comment\n * @param {any | null} reply\n * @returns {true}\n */\n _commitReaction(comment, reply) {\n this._syncStorage();\n // A pill lives on the card, in the detail and in the popover at once, so a\n // toggle anywhere has to reach the copies it did not repaint itself.\n if (this.inboxView?.isOpen()) this.inboxView.refresh();\n const serialized = this._serializeComment(comment);\n const serializedReply = reply ? this._serializeReply(reply) : null;\n this._emit(\"reaction:toggled\", [serialized, serializedReply], {\n comment: serialized,\n reply: serializedReply,\n });\n return true;\n }\n\n /**\n * Flips the current actor's reaction on a comment: present, it is removed;\n * absent, it is added.\n * @param {import('./index.d.ts').CommentId} id\n * @param {string} emoji one of REACTION_EMOJIS\n * @returns {boolean} false when the id or the emoji is unknown\n */\n toggleCommentReaction(id, emoji) {\n const comment = this._findComment(id);\n if (!comment) return false;\n if (!toggleReactionOn(comment, emoji, this._actorKey())) return false;\n return this._commitReaction(comment, null);\n }\n\n /**\n * Same contract as toggleCommentReaction, one level down.\n * @param {import('./index.d.ts').CommentId} commentId\n * @param {import('./index.d.ts').CommentId} replyId\n * @param {string} emoji one of REACTION_EMOJIS\n * @returns {boolean} false when either id, or the emoji, is unknown\n */\n toggleReplyReaction(commentId, replyId, emoji) {\n const comment = this._findComment(commentId);\n const reply = comment?.replies?.find((r) => sameId(r.id, replyId));\n if (!reply) return false;\n if (!toggleReactionOn(reply, emoji, this._actorKey())) return false;\n return this._commitReaction(comment, reply);\n }\n\n /**\n * Shared tail of the classification setters: persist, re-render the\n * inbox if it's showing, and notify the host app.\n * @param {any} comment\n * @returns {true}\n */\n /**\n * @param {any} comment\n * @param {{ field: \"type\" | \"priority\" | \"tags\", from?: any, to?: any }} detail\n * which field moved, and both ends of the move where there are two. The\n * setters already compute this for the audit trail; passing it on costs\n * nothing and saves every host the same diff.\n */\n _commitUpdate(comment, detail) {\n this._syncStorage();\n if (this.inboxView?.isOpen()) this.inboxView.refresh();\n const updated = this._serializeComment(comment);\n this._emit(\"comment:updated\", [updated], { comment: updated }, detail);\n return true;\n }\n\n /**\n * RF3 \u2014 categorises a comment. `null` returns it to the neutral state.\n * @param {import('./index.d.ts').CommentId} id\n * @param {import('./index.d.ts').CommentType | null} type\n * @returns {boolean} false when the id or type is unknown\n */\n setCommentType(id, type) {\n if (type !== null && !COMMENT_TYPES.includes(type)) return false;\n const comment = this._findComment(id);\n if (!comment) return false;\n const previousType = comment.type ?? null;\n // The same no-op guard setCommentStatus has always had. Without it,\n // re-applying the value a comment already holds wrote to storage,\n // refreshed the inbox and emitted comment:updated for nothing \u2014 which a\n // host mirroring a remote change hears as its own echo.\n if (previousType === type) return true;\n comment.type = type;\n recordEvent(comment, \"classified\", this._actor(), {\n field: \"type\",\n from: previousType,\n to: type,\n });\n return this._commitUpdate(comment, {\n field: \"type\",\n from: previousType,\n to: type,\n });\n }\n\n /**\n * RF4 \u2014 prioritises a comment. `null` returns it to the neutral state.\n * @param {import('./index.d.ts').CommentId} id\n * @param {import('./index.d.ts').CommentPriority | null} priority\n * @returns {boolean} false when the id or priority is unknown\n */\n setCommentPriority(id, priority) {\n if (priority !== null && !PRIORITIES.includes(priority)) return false;\n const comment = this._findComment(id);\n if (!comment) return false;\n const previousPriority = comment.priority ?? null;\n if (previousPriority === priority) return true;\n comment.priority = priority;\n recordEvent(comment, \"classified\", this._actor(), {\n field: \"priority\",\n from: previousPriority,\n to: priority,\n });\n return this._commitUpdate(comment, {\n field: \"priority\",\n from: previousPriority,\n to: priority,\n });\n }\n\n /**\n * RF3 \u2014 replaces a comment's free-form labels. Values are trimmed,\n * lowercased and de-duplicated.\n * @param {import('./index.d.ts').CommentId} id\n * @param {string[]} tags\n * @returns {boolean} false when the id is unknown or tags isn't an array\n */\n setCommentTags(id, tags) {\n if (!Array.isArray(tags)) return false;\n const comment = this._findComment(id);\n if (!comment) return false;\n // Joined on a character no tag can contain, rather than compared with\n // JSON: the list is already normalised on both sides, so this is the\n // whole of \"did anything actually change\".\n const previous = [...(comment.tags || [])];\n const previousKey = previous.join(\"\\u0000\");\n const next = normalizeTags(tags);\n if (next.join(\"\\u0000\") === previousKey) return true;\n comment.tags = next;\n recordEvent(comment, \"classified\", this._actor(), { field: \"tags\" });\n // Tags are a list, so unlike type and priority they have no two-value\n // transition to record in the audit trail \u2014 but a host diffing \"which\n // label was added\" still wants both sides, and here they are.\n return this._commitUpdate(comment, {\n field: \"tags\",\n from: previous,\n to: [...next],\n });\n }\n\n /**\n * Aggregate figures over every comment the widget holds \u2014 counts by\n * status, type and priority, the daily distribution, and the resolution\n * times derived from the audit log.\n *\n * Unfiltered on purpose: a host has no notion of the panel's filters. The\n * dashboard inside the inbox measures whatever that panel is showing.\n * @returns {import('./index.d.ts').CommentMetrics}\n */\n getMetrics() {\n return computeMetrics(this.serializeComments());\n }\n\n /**\n * Downloads the corpus as CSV, one row per comment. Screenshots stay out:\n * a 33 KB base64 string in a spreadsheet cell is not data.\n * @param {import('./index.d.ts').SerializedComment[]} [comments] defaults to all\n */\n exportCommentsCsv(comments) {\n const rows = commentRows(comments || this.serializeComments());\n const csv = toCsv(rows, columnsOf(COMMENT_COLUMNS));\n downloadCsv(\"helldots-comments.csv\", csv);\n // Returned as well as downloaded: a browser download is a dead end for a\n // host that wanted to POST the same rows to its own endpoint or attach\n // them to a message, and building the CSV twice is the only alternative.\n return csv;\n }\n\n /**\n * Downloads the aggregate figures as CSV in long format \u2014 one row per\n * bucket, so the shape stays the same however many days the corpus spans.\n * @param {import('./index.d.ts').SerializedComment[]} [comments] defaults to all\n */\n exportMetricsCsv(comments) {\n const metrics = computeMetrics(comments || this.serializeComments());\n const csv = toCsv(metricRows(metrics), columnsOf(METRIC_COLUMNS));\n downloadCsv(\"helldots-metrics.csv\", csv);\n return csv;\n }\n\n /**\n * Opens the browser's print dialog on a report of the figures \u2014 which is\n * where \"save as PDF\" lives, at no cost in bundle size. The report is\n * built in its own document, so what prints is the report and not the\n * host page.\n * @param {import('./index.d.ts').SerializedComment[]} [comments] defaults to all\n * @param {string} [scope] a label describing what was measured\n */\n printMetricsReport(comments, scope) {\n const metrics = computeMetrics(comments || this.serializeComments());\n printMetricsReport(metrics, {\n strings: this.strings,\n locale: this.locale,\n css: getReportStyles(),\n scope,\n });\n }\n\n /**\n * @returns {import('./index.d.ts').SerializedComment[]}\n */\n serializeComments() {\n return this.comments.map((comment) => this._serializeComment(comment));\n }\n\n /**\n * Removes a comment everywhere: page marker, memory and (when the\n * localStorage mode is on) persisted storage.\n * @param {import('./index.d.ts').CommentId} id\n * @returns {boolean} false when the id is unknown\n */\n deleteComment(id) {\n const comment = this._findComment(id);\n if (!comment) return false;\n if (!this._permits(\"delete:comment\", commentTargetOf(comment))) {\n return false;\n }\n this._removeComment(id);\n if (this.options.persistence === \"localStorage\") {\n // The merge preserves other-page entries missing from memory, which\n // would resurrect a deleted inactive comment \u2014 drop the id explicitly.\n const merged = mergeForStorage(\n this._readStoredCached().filter((comment) => !sameId(comment.id, id)),\n this.serializeComments(),\n location.pathname\n );\n writeStoredComments(merged);\n this._storedCache = merged;\n }\n this._emit(\"comment:deleted\", [id], { id });\n return true;\n }\n\n _removeComment(id) {\n this.markers?.remove(id);\n this.comments = this.comments.filter((comment) => !sameId(comment.id, id));\n }\n\n /**\n * Removes every comment at once \u2014 markers, memory and (in localStorage\n * mode) their persisted entries. This is the bulk reset a host needs to\n * reconcile against its backend before a fresh loadComments, so it\n * deliberately fires no per-comment onCommentDeleted callbacks: the host\n * initiated it and would only hear its own action echoed back.\n */\n clearComments() {\n this.closeThreadPopover();\n const cleared = this.comments;\n this.markers?.clear();\n this.comments = [];\n\n if (this.options.persistence === \"localStorage\" && cleared.length > 0) {\n const clearedIds = new Set(cleared.map((comment) => String(comment.id)));\n const merged = this._readStoredCached().filter(\n (comment) => !clearedIds.has(String(comment.id))\n );\n writeStoredComments(merged);\n this._storedCache = merged;\n }\n if (this.inboxView?.isOpen()) this.inboxView.refresh();\n }\n\n /**\n * Restores serialized comments: each anchor is resolved back to a live\n * element (circle re-rendered) or the comment is kept as an orphan \u2014\n * present in the list/inbox but never positioned over the wrong element.\n * Loading the same id again replaces the previous copy (idempotent).\n * @param {import('./index.d.ts').SerializedComment[]} data\n * @returns {{ anchored: number, orphaned: number, inactive: number }}\n */\n loadComments(data) {\n let anchored = 0;\n let orphaned = 0;\n let inactive = 0;\n if (!Array.isArray(data)) return { anchored, orphaned, inactive };\n\n // Called before the widget mounted \u2014 a host whose fetch resolved while\n // the document was still parsing. Resolving anchors against a half-built\n // DOM would report orphans that are not orphans and fire onAnchorLost\n // for each of them, so the data is held and replayed by initOverlay.\n // Zeroes are all this can honestly return at that point: nothing has\n // been resolved yet. A host that needs the counts should load from\n // onReady, where the widget is up and they mean something.\n if (!this.markers) {\n this._deferredLoad = [...(this._deferredLoad || []), ...data];\n return { anchored, orphaned, inactive };\n }\n\n for (const item of data) {\n if (!item || item.id == null || typeof item.text !== \"string\") {\n console.warn(\"HellDots: skipping malformed serialized comment\", item);\n // A record the widget drops is a record the host still believes it\n // is showing \u2014 which is exactly the kind of divergence that goes\n // unnoticed until someone asks where their comment went.\n this._reportError(\n new Error(\"HellDots: skipping malformed serialized comment\"),\n \"load\"\n );\n continue;\n }\n this._removeComment(item.id);\n\n const comment = {\n id: item.id,\n text: item.text,\n editedAt: item.editedAt || null,\n anchor: item.anchor || null,\n anchorState: \"orphaned\",\n target: null,\n hidden: false,\n page: item.page || location.pathname,\n container: null,\n relativeX: 0,\n relativeY: 0,\n // Same minimal gate the top-level comment passes (id + text):\n // a malformed reply would otherwise flow into every renderer.\n replies: Array.isArray(item.replies)\n ? item.replies\n .filter(\n (reply) =>\n reply &&\n typeof reply === \"object\" &&\n reply.id != null &&\n typeof reply.text === \"string\"\n )\n .map((reply) => ({\n ...reply,\n authorId: normalizeActorId(reply.authorId) || null,\n ...(Array.isArray(reply.screenshots)\n ? { screenshots: onlyStrings(reply.screenshots) }\n : {}),\n reactions: normalizeReactions(reply.reactions),\n }))\n : [],\n author: item.author || this.strings.anonymous,\n // Scrubbed like every other field crossing this boundary: the id\n // reaches a host that may look it up, so a non-string must not\n // survive a round trip through localStorage or a backend.\n authorId: normalizeActorId(item.authorId) || null,\n // Scrubbed on the way in like the reaction map: an unknown event type\n // has no label, and an unparseable timestamp poisons every duration\n // derived from it.\n history: normalizeHistory(item.history),\n createdAt: item.createdAt || new Date().toISOString(),\n // Screenshots land in <img src> as-is \u2014 a non-string entry renders\n // a silently broken thumbnail.\n screenshots: Array.isArray(item.screenshots)\n ? onlyStrings(item.screenshots)\n : [],\n // \"closed\" existed briefly and was folded into \"resolved\".\n status:\n /** @type {string} */ (item.status) === \"closed\"\n ? \"resolved\"\n : STATUSES.includes(item.status)\n ? item.status\n : \"open\",\n // Records persisted before RF1-RF5 have none of these \u2014 every\n // reader downstream may assume they exist after this point.\n type: COMMENT_TYPES.includes(item.type) ? item.type : null,\n priority: PRIORITIES.includes(item.priority) ? item.priority : null,\n tags: Array.isArray(item.tags) ? [...item.tags] : [],\n resolvedAt: item.resolvedAt || null,\n // Reactions come from localStorage or from the host's backend, so the\n // map is scrubbed before any renderer sees it: unknown glyphs and\n // duplicated actor keys both survive a round trip otherwise.\n reactions: normalizeReactions(item.reactions),\n context: item.context || null,\n contextScreenshot: item.contextScreenshot || null,\n };\n\n // Comments from other pages aren't broken \u2014 their elements just\n // don't exist here. They stay listed (inbox \"all\" filter) without a\n // marker and without an onAnchorLost false alarm.\n if (item.page && item.page !== location.pathname) {\n comment.anchorState = \"inactive\";\n this.comments.push(comment);\n inactive++;\n continue;\n }\n\n const resolved = item.anchor ? resolveAnchor(item.anchor) : null;\n if (resolved) {\n comment.container = resolved.element;\n comment.relativeX = item.anchor.relativeX;\n comment.relativeY = item.anchor.relativeY;\n comment.anchorState = \"anchored\";\n this.comments.push(comment);\n this.renderCommentCircle(comment);\n anchored++;\n } else {\n this.comments.push(comment);\n orphaned++;\n const lost = this._serializeComment(comment);\n this._emit(\"comment:anchor-lost\", [lost], { comment: lost });\n }\n }\n\n // A link the host's data had not arrived for yet may be waiting on\n // exactly the comments that just landed.\n this._openPendingDetail();\n\n return { anchored, orphaned, inactive };\n }\n\n /**\n * Re-syncs the widget after a client-side navigation: reclassifies every\n * comment against the new `location.pathname`, re-resolves anchors\n * against the new DOM, rebuilds markers, and moves the inbox onto the\n * new page. Call it from the router's \"after navigation\" hook; with\n * `autoDetectNavigation` it also runs on popstate (back/forward).\n *\n * Same-path calls are useful too: an SPA that re-rendered its route\n * swapped every node, and this is the \"re-anchor now\" primitive.\n *\n * @returns {{ anchored: number, orphaned: number, inactive: number }}\n */\n notifyNavigation() {\n const page = location.pathname;\n let anchored = 0;\n let orphaned = 0;\n let inactive = 0;\n\n // Nothing is mounted and nothing is loaded yet (loadComments defers too),\n // so there is no state to re-sync \u2014 and every panel this touches below\n // is still null.\n if (!this.markers) return { anchored, orphaned, inactive };\n\n // Panels pinned to the old DOM don't survive a route change; the inbox\n // does \u2014 it is cross-page by design and refreshes below.\n this.closeThreadPopover();\n this.hideCommentBox();\n if (this.inboxView) this.inboxView.currentPage = page;\n\n for (const comment of this.comments) {\n this.markers.remove(comment.id);\n comment.hidden = false;\n comment.target = null;\n comment._occluded = false;\n\n if (comment.page && comment.page !== page) {\n comment.anchorState = \"inactive\";\n comment.container = null;\n inactive++;\n continue;\n }\n\n const resolved = comment.anchor ? resolveAnchor(comment.anchor) : null;\n if (resolved) {\n comment.container = resolved.element;\n comment.relativeX = comment.anchor.relativeX;\n comment.relativeY = comment.anchor.relativeY;\n comment.anchorState = \"anchored\";\n this.renderCommentCircle(comment);\n anchored++;\n } else {\n // Same contract as loadComments: kept and listed, never positioned\n // over a guessed element \u2014 and the host is told, each time, because\n // \"the element is gone on this visit\" is fresh information.\n comment.container = null;\n comment.anchorState = \"orphaned\";\n orphaned++;\n const lost = this._serializeComment(comment);\n this._emit(\"comment:anchor-lost\", [lost], { comment: lost });\n }\n }\n\n // The new URL may itself carry a deep link (a copy-link opened through\n // the SPA's router) or the cross-page handoff written just before the\n // host navigated.\n this._pendingDetailId = this._readPendingDetailId();\n if (this.inboxView?.isOpen()) this.inboxView.refresh();\n this._openPendingDetail();\n\n return { anchored, orphaned, inactive };\n }\n\n scrollMarkerIntoView(comment) {\n // Scrolling to an invisible marker would scroll to nothing.\n if (this.markersHidden) this._setMarkersHidden(false);\n this.markers.scrollMarkerIntoView(comment);\n }\n\n createPreviewCircle(x, y) {\n this.removePreviewCircle();\n\n const circle = document.createElement(\"div\");\n circle.className = `${CLASSES.CIRCLE} ${CLASSES.PREVIEW_CIRCLE}`;\n circle.style.position = \"absolute\";\n const circleRadius = MARKER_SIZE / 2;\n circle.style.left = `${x + circleRadius}px`;\n circle.style.top = `${y + circleRadius}px`;\n circle.style.transform = \"translate(-50%, -50%)\";\n circle.style.pointerEvents = \"none\";\n\n this.overlay.appendChild(circle);\n this.previewCircle = circle;\n }\n\n removePreviewCircle() {\n this.previewCircle?.remove();\n this.previewCircle = null;\n }\n\n // ------------------------------------------------------------------\n // Marker facade \u2014 the engine owns the logic (marker-engine.js); these\n // keep the overlay-level names every internal caller and the test suite\n // grew around. State fields surface as accessors for the same reason.\n // ------------------------------------------------------------------\n\n cleanupResizeObserver(commentId) {\n this.markers.cleanupResizeObserver(commentId);\n }\n\n validateAndCalculatePosition(comment, circle) {\n return this.markers.validateAndCalculatePosition(comment, circle);\n }\n\n updateCommentPosition(comment, circle) {\n this.markers.updatePosition(comment, circle);\n }\n\n scheduleUpdatePositions() {\n this.markers.scheduleUpdate();\n }\n\n /** Marker circles by String(id) \u2014 lives on the engine. */\n get _circles() {\n return this.markers?.circles;\n }\n\n /** Per-comment ResizeObservers \u2014 live on the engine. */\n get resizeObservers() {\n return this.markers?.resizeObservers;\n }\n\n get positionValidationEnabled() {\n return this.markers?.enabled ?? true;\n }\n\n set positionValidationEnabled(value) {\n if (this.markers) this.markers.enabled = value;\n }\n\n get _globalMutationObserver() {\n return this.markers?._globalMutationObserver ?? null;\n }\n\n // A marker that just went away must not leave its hover tooltip or its\n // open thread popover floating on the page (e.g. above the modal that\n // now covers the marker).\n _dismissMarkerUi(comment) {\n this._tooltipEl(comment.id)?.remove();\n if (this.activeThreadPopover?.dataset.for === String(comment.id)) {\n this.closeThreadPopover();\n }\n }\n\n /**\n * Cleanup method to remove all event listeners and observers\n */\n cleanup() {\n // An instance destroyed while the document is still loading must not\n // mount when DOMContentLoaded eventually fires.\n if (this._onDomReady) {\n document.removeEventListener(\"DOMContentLoaded\", this._onDomReady);\n this._onDomReady = null;\n }\n // Cancels every scheduled pass, listener, observer and circle the\n // marker engine owns \u2014 including a rAF armed before teardown.\n this.markers?.destroy();\n if (this._storageHandler) {\n window.removeEventListener(\"storage\", this._storageHandler);\n this._storageHandler = null;\n }\n if (this._popstateHandler) {\n window.removeEventListener(\"popstate\", this._popstateHandler);\n this._popstateHandler = null;\n }\n this._storedCache = null;\n // An instance torn down before it mounted must not hold on to data it\n // will never replay.\n this._deferredLoad = null;\n // Dropping the panels leaves their dropdowns detached-but-open, which\n // would keep the menu registry's document listener alive until the next\n // stray mousedown.\n closeOpenMenus();\n // Same reasoning: an unanswered confirmation holds a capture-phase\n // keydown listener on document, which would go on eating Escape for the\n // whole page after the widget is gone.\n closeOpenConfirmDialogs();\n this.closeThreadPopover();\n this.closeInbox();\n this.closeLightbox();\n this.removePreviewCircle();\n // Covers the selection rect, the drag listeners and the pending capture.\n this._captureFlow?.destroy();\n this._pendingScreenshots = [];\n\n if (this._handleDocumentClickBound) {\n document.removeEventListener(\"mousedown\", this._handleDocumentClickBound);\n }\n\n // Remove keyboard shortcut handler\n if (this.keydownHandler) {\n document.removeEventListener(\"keydown\", this.keydownHandler);\n }\n\n // Remove DOM elements\n if (this.toolbar && this.toolbar.parentNode) {\n this.toolbar.parentNode.removeChild(this.toolbar);\n }\n if (this.commentBox && this.commentBox.parentNode) {\n this.commentBox.parentNode.removeChild(this.commentBox);\n }\n if (this.overlay && this.overlay.parentNode) {\n this.overlay.parentNode.removeChild(this.overlay);\n }\n\n document.body.classList.remove(CLASSES.COMMENT_CURSOR);\n // Covers both paths: removes the injected <style> or drops our adopted\n // sheet from the document. Leaving the latter behind would keep styling\n // the host page \u2014 the comment-mode cursor included \u2014 after teardown.\n this._detachStyles();\n\n // The now-empty shadow host itself: leaving <helldots-root> dangling\n // from <body> is half a cleanup. getShadowRoot() recreates it if a new\n // instance mounts later.\n document.querySelector(TAG_NAME)?.remove();\n }\n\n injectStyles() {\n // Re-injecting replaces rather than accumulates, whichever path is in\n // use \u2014 mountStyles hands back the undo for exactly what it mounted.\n this._detachStyles();\n this._styleDetachers = [\n mountStyles(this.shadowRoot, getStyles(), IDS.STYLES),\n // A few rules (e.g. the comment-mode cursor on document.body) target\n // the host page itself, which a shadow root's stylesheet cannot\n // reach \u2014 those go on the document instead.\n mountStyles(document, getGlobalStyles(), IDS.GLOBAL_STYLES),\n ];\n }\n\n _detachStyles() {\n for (const detach of this._styleDetachers ?? []) detach();\n this._styleDetachers = [];\n }\n}\n\nexport default CommentOverlay;\n", "import CommentOverlay from \"./overlay.js\";\n\n/**\n * Creates a CommentOverlay.\n *\n * Safe to call before the document is ready: `CommentOverlay`'s constructor\n * already defers its own DOM work to `DOMContentLoaded`, so callers always\n * get a real instance back and never have to branch on `readyState`. An\n * earlier version duplicated that same check here and, while the document\n * was loading, both registered a listener AND returned the uninvoked\n * initializer \u2014 so a caller who invoked it (reasonably, since the type said\n * it might be a function) ended up with two overlays, and the one the\n * listener built had no handle to call `cleanup()` on.\n *\n * @overload\n * @param {import('./index.d.ts').CommentOverlayOptions & { autoInit?: true }} [options]\n * @returns {CommentOverlay}\n */\n/**\n * @overload\n * @param {import('./index.d.ts').CommentOverlayOptions & { autoInit: false }} options\n * @returns {() => CommentOverlay}\n */\n/**\n * @param {import('./index.d.ts').CommentOverlayOptions} [options]\n * @returns {CommentOverlay | (() => CommentOverlay)}\n */\nexport function createCommentOverlay(options = {}) {\n const { autoInit = true, ...overlayOptions } = options;\n const initialize = () => new CommentOverlay(overlayOptions);\n return autoInit ? initialize() : initialize;\n}\n\n// Export the class for advanced usage\nexport { CommentOverlay };\n\n// Deep-link helpers. A host that loads comments lazily has to read the id\n// out of the URL *before* it can fetch anything \u2014 which means before the\n// widget can tell it. Exporting these is what keeps that read from becoming\n// a second, hand-written copy of the `linkParam` setting, free to drift from\n// the one the widget honours. See also the `onCommentRequested` option,\n// which covers the case where the widget is already up.\nexport { DEFAULT_LINK_PARAM, readCommentLinkParam } from \"./link.js\";\n\n// Export a default instance creator for simple usage\nexport default createCommentOverlay;\n"],
|
|
5
|
+
"mappings": ";AAAA,IAAMA,EAAW,gBAOXC,GAAgB,IAAM,CACtB,eAAe,IAAID,CAAQ,GAE/B,eAAe,OACbA,EACA,cAA2B,WAAY,CACrC,aAAc,CACZ,MAAM,EACN,KAAK,aAAa,CAAE,KAAM,MAAO,CAAC,CACpC,CACF,CACF,CACF,EAOO,SAASE,IAAgB,CAC9BD,GAAc,EAEd,IAAIE,EAAO,SAAS,cAAcH,CAAQ,EAC1C,OAAKG,IACHA,EAAO,SAAS,cAAcH,CAAQ,EACtC,SAAS,KAAK,YAAYG,CAAI,GAGzBA,EAAK,UACd,CCxBA,IAAMC,GAAM,IACV,OAAO,aAAa,KAAQ,WAAa,YAAY,IAAI,EAAI,KAAK,IAAI,EAWlEC,GAAW,IACf,IAAI,QAASC,GAAY,CACvB,GAAI,OAAO,gBAAmB,WAAY,CACxC,WAAWA,CAAO,EAClB,MACF,CACA,IAAMC,EAAU,IAAI,eACpBA,EAAQ,MAAM,UAAY,IAAM,CAC9BA,EAAQ,MAAM,MAAM,EACpBD,EAAQ,CACV,EACAC,EAAQ,MAAM,YAAY,IAAI,CAChC,CAAC,EAUGC,GAAiB,IAAM,CAC3B,IAAMC,EAAgC,WAAY,UAClD,OAAI,OAAOA,GAAW,OAAU,WAAmBA,EAAU,MAAM,EAC5DJ,GAAS,CAClB,EAqBO,SAASK,GAAmB,CAAE,SAAAC,EAAW,CAAgB,EAAI,CAAC,EAAG,CACtE,IAAIC,EAAOR,GAAI,EACTS,EAAS,IAAM,CAInBD,EAAOR,GAAI,CACb,EACA,MAAO,IAAM,CACX,GAAI,EAAAA,GAAI,EAAIQ,EAAOD,GACnB,OAAOH,GAAe,EAAE,KAAKK,EAAQA,CAAM,CAC7C,CACF,CCvDO,IAAMC,GAAsB,CACjC,kBACA,cACA,eACA,aACA,aACA,gBACA,gBACF,EAWaC,GAA2B,CACtC,GAAGD,GAGH,eACA,kBACA,iBACA,SACA,aACA,eACA,QACA,UACA,cACA,QACA,SACA,YACA,OACA,gBACA,cACA,eACA,aACA,aACA,YACA,aACA,YACA,iBACA,eACA,gBACA,cACA,WACA,QACA,eACA,MACA,iBACA,aACA,QACA,UAGA,sBACA,4BACA,6BACA,sBACA,sBACA,sBACA,sBACA,qBACA,sBACA,qBACA,oBACA,oBACA,oBACA,qBACA,qBACA,qBACA,mBACA,yBACA,0BACA,mBACA,mBACA,gBACA,iBACA,gBACA,gBAGA,gBACA,cACA,aACA,eACA,cACA,aACA,oBACA,oBACA,oBACA,cACA,eACA,aACA,iBACA,YACA,cACA,YACA,oBACA,iBACA,iBACA,kBACA,oBACA,eACA,iBACA,sBACA,wBACA,qBACA,kBACA,gBACA,eACA,QACA,UAGA,QACA,YACA,wBACA,YACA,eACA,aACA,eACA,0BACA,cACA,UACA,iBACA,cACA,mBACA,sBACA,kBACA,gBACA,WACA,aACA,kBACA,wBACA,uBACA,wBACA,4BACA,cACA,mBACA,cACA,wBACA,0BACA,eACA,cACA,aACA,eACA,eACA,qBACA,qBACA,0BACA,4BACA,4BAGA,kBACA,sBACA,wBACA,wBACA,mBACA,mBACA,oBACA,wBACA,wBACA,oBACA,kBACA,aACA,YACA,SACA,aACA,YACA,gBACA,cACA,YACA,iBACA,aACA,kBACA,UACA,cACA,qBACA,SACA,QACA,YACA,mBACA,kBACA,YAKA,eACA,aAIA,oBACA,OACA,eACA,YACA,cACA,kBACA,aACA,eACA,SACA,mBACA,oBACA,iBACA,kBACA,iBACA,eACA,aACF,EClNA,IAAME,GAAa,CAACC,EAAOC,IAAW,CACpC,GAAI,CACF,IAAMC,EAAS,SAAS,cAAc,QAAQ,EAG9C,GAFAA,EAAO,MAAQF,EACfE,EAAO,OAASD,EACZC,EAAO,QAAUF,GAASE,EAAO,SAAWD,EAAQ,MAAO,GAC/D,IAAME,EAAMD,EAAO,WAAW,IAAI,EAClC,OAAKC,GACLA,EAAI,UAAY,UAChBA,EAAI,SAAS,EAAG,EAAG,EAAG,CAAC,EAChBA,EAAI,aAAa,EAAG,EAAG,EAAG,CAAC,EAAE,KAAK,CAAC,IAAM,GAH/B,EAInB,MAAQ,CACN,MAAO,EACT,CACF,EAGIC,GAAW,KAWFC,GAAqB,KAC5BD,KAAa,OACjBA,GACE,CAAC,MAAO,MAAO,MAAO,KAAM,IAAa,EAAE,KAAME,GAAMP,GAAW,EAAGO,CAAC,CAAC,GACvE,MACKF,IAmBIG,GAAe,CAACP,EAAOC,EAAQO,EAAQC,EAAS,CAAC,IAAM,CAClE,GAAI,EAAET,EAAQ,IAAM,EAAEC,EAAS,GAAI,OAAOO,EAC1C,GAAM,CAAE,aAAAE,EAAeL,GAAmB,EAAG,QAAAM,EAAU,SAAW,EAAIF,EACtE,OAAO,KAAK,IACVD,EACAE,EAAeV,EACfU,EAAeT,EACf,KAAK,KAAKU,GAAWX,EAAQC,EAAO,CACtC,CACF,EAaaW,GAAgBV,GAAW,CACtC,GAAI,CACF,IAAMC,EAAMD,GAAQ,aAAa,IAAI,EACrC,MAAO,EAAQC,GAAQA,EAAI,aAAa,EAAG,EAAG,EAAG,CAAC,EAAE,KAAK,CAAC,IAAM,CAClE,MAAQ,CACN,MAAO,EACT,CACF,EClGO,IAAMU,GAAa,GACpBC,GAAe,GAQjBC,GACEC,GAAe,KACnBD,KAAoB,OAAO,mBAAmB,EAAE,MAAOE,GAAU,CAC/D,MAAAF,GAAkB,OACZE,CACR,CAAC,EACMF,IAGHG,GAAeC,GACnB,CAACA,GAASA,IAAU,eAAiBA,IAAU,mBAO3CC,GAA2B,IAAM,CACrC,IAAMC,EAAS,iBAAiB,SAAS,eAAe,EAAE,gBAC1D,GAAI,CAACH,GAAYG,CAAM,EAAG,OAAOA,EACjC,IAAMC,EAAS,iBAAiB,SAAS,IAAI,EAAE,gBAC/C,OAAKJ,GAAYI,CAAM,EAChB,UAD0BA,CAEnC,EAaaC,GAAmB,IAAM,CACpC,GAAI,CACF,IAAMC,EAAQ,SAAS,eAAe,mBAAmB,EAAE,EACrDC,EAAQD,EAAM,cAAc,OAAO,EACzC,OAAAA,EAAM,KAAK,YAAYC,CAAK,EACrBA,EAAM,QAAU,IACzB,MAAQ,CACN,MAAO,EACT,CACF,EAWaC,GAAwBC,GAAQ,CAC3C,IAAMC,EAAS,CAAC,EACZC,EAAKF,EAAI,QAAQ,YAAY,EACjC,KAAOE,IAAO,IAAI,CAChB,IAAMC,EAAOH,EAAI,QAAQ,IAAKE,CAAE,EAC1BE,EAAQD,IAAS,GAAK,GAAKH,EAAI,QAAQ,IAAKG,CAAI,EACtD,GAAIC,IAAU,GAAI,MAClBH,EAAO,KAAKD,EAAI,MAAME,EAAIE,EAAQ,CAAC,CAAC,EACpCF,EAAKF,EAAI,QAAQ,aAAcI,CAAK,CACtC,CACA,OAAOH,EAAO,KAAK;AAAA,CAAI,CACzB,EAGMI,GAAgB,IAAI,IAEpBC,GAAkBC,IACjBF,GAAc,IAAIE,CAAI,GACzBF,GAAc,IACZE,EACA,MAAMA,EAAM,CAAE,KAAM,OAAQ,YAAa,MAAO,CAAC,EAC9C,KAAMC,GAASA,EAAI,GAAKA,EAAI,KAAK,EAAI,EAAG,EACxC,KAAKT,EAAoB,EACzB,MAAM,IAAM,EAAE,CACnB,EAEKM,GAAc,IAAIE,CAAI,GAGzBE,GAAcC,GAAU,CAC5B,GAAI,CACF,MAAO,EAAQA,EAAM,QACvB,MAAQ,CACN,MAAO,EACT,CACF,EA0BMC,GAA0B,MAAOC,GAAY,CACjD,IAAMC,EAAO,IAAM,CAAC,EACpB,GAAI,CAACD,GAAW,CAAChB,GAAiB,EAAG,OAAOiB,EAE5C,IAAMC,EAAQ,MAAM,KAAK,SAAS,WAAW,EAC1C,OAAQJ,GAAUA,EAAM,MAAQ,CAACD,GAAWC,CAAK,CAAC,EAClD,IAAKA,GAAUA,EAAM,IAAI,EAC5B,GAAI,CAACI,EAAM,OAAQ,OAAOD,EAE1B,IAAMb,GAAO,MAAM,QAAQ,IAAIc,EAAM,IAAIR,EAAc,CAAC,GACrD,OAAO,OAAO,EACd,KAAK;AAAA,CAAI,EACZ,GAAI,CAACN,EAAK,OAAOa,EAEjB,IAAMf,EAAQ,SAAS,cAAc,OAAO,EAC5C,OAAAA,EAAM,YAAcE,EACpB,SAAS,KAAK,YAAYF,CAAK,EACxB,IAAMA,EAAM,OAAO,CAC5B,EAmBMiB,GAAiBC,GAAuBC,GAGxC,EAAAA,EAAK,UAAU,YAAY,IAAMC,GACjCF,GAAqBC,EAAK,gBAAkB,UA8BlD,eAAsBE,GAAW,CAC/B,MAAAC,EAAQ,EACR,sBAAAC,EAAwB,GACxB,YAAAC,EAAc,GACd,kBAAAN,EAAoB,GACpB,eAAAO,CACF,EAAI,CAAC,EAAG,CACN,GAAM,CAAE,YAAAC,CAAY,EAAI,MAAMnC,GAAa,EACrCoC,EAAS,MAAMd,GAAwBU,CAAqB,EAC5D,CAAE,MAAAK,EAAO,OAAAC,CAAO,EAAI,SAAS,gBAAgB,sBAAsB,EAKrEC,EAAUC,GAAaH,EAAOC,EAAQP,CAAK,EAC/C,GAAI,CAOF,QAASU,EAAO,GAAKA,IAAQ,CAQ3B,IAAMC,EAAS,MAAMP,EAAY,SAAS,gBAAiB,CACzD,MAAOI,EACP,gBAAiBnC,GAAyB,EAG1C,GAAIG,GAAiB,EAAI,CAAC,EAAI,CAAE,KAAM,EAAM,EAC5C,OAAQmB,GAAcC,CAAiB,EAIvC,gBAAiBgB,GAAmB,EAIpC,GAAIV,EACA,CAAE,uBAAwBW,EAAyB,EACnD,CAAC,EAWL,GAAI,OAAO,SAASV,CAAc,GAAKA,EAAiB,EACpD,CAAE,QAASA,CAAe,EAC1B,CAAC,CACP,CAAC,EAED,GAAIW,GAAaH,CAAM,EAAG,MAAO,CAAE,OAAAA,EAAQ,MAAOH,CAAQ,EAC1D,GAAIE,GAAQ,EACV,MAAM,IAAI,MACR,0DACK,KAAK,MAAMJ,CAAK,CAAC,IAAI,KAAK,MAAMC,CAAM,CAAC,8DAE9C,EAEFC,GAAW,CACb,CACF,QAAE,CACAH,EAAO,CACT,CACF,CAeA,IAAMU,GAAgB,CAACC,EAAKV,EAAOC,IAAW,CAC5CS,EAAI,UAAY3C,GAAyB,EACzC2C,EAAI,SAAS,EAAG,EAAGV,EAAOC,CAAM,CAClC,EAaO,SAASU,GACdN,EACA,CAAE,KAAAD,EAAM,IAAAQ,EAAK,MAAAZ,EAAO,OAAAC,CAAO,EAC3B,CAAE,YAAAY,EAAc,CAAE,EAAI,CAAC,EACvB,CACA,IAAMC,EAAM,SAAS,cAAc,QAAQ,EAC3CA,EAAI,MAAQd,EACZc,EAAI,OAASb,EACb,IAAMS,EAAMI,EAAI,WAAW,IAAI,EAC/B,OAAKJ,GAELD,GAAcC,EAAKV,EAAOC,CAAM,EAChCS,EAAI,UACFL,GACCD,EAAO,OAAO,SAAWS,GACzBD,EAAM,OAAO,SAAWC,EACzBb,EAAQa,EACRZ,EAASY,EACT,EACA,EACAb,EACAC,CACF,EACOa,EAAI,UAAU,WAAW,GAdf,IAenB,CAUO,SAASC,GACdV,EACA,CAAE,YAAAQ,EAAc,EAAG,YAAAG,EAAcxD,GAAY,QAAAyD,EAAUxD,EAAa,EAAI,CAAC,EACzE,CACA,IAAMqD,EAAM,SAAS,cAAc,QAAQ,EAC3CA,EAAI,MAAQ,KAAK,MAAM,OAAO,WAAaE,CAAW,EACtDF,EAAI,OAAS,KAAK,MAAM,OAAO,YAAcE,CAAW,EACxD,IAAMN,EAAMI,EAAI,WAAW,IAAI,EAC/B,OAAKJ,GAELD,GAAcC,EAAKI,EAAI,MAAOA,EAAI,MAAM,EACxCJ,EAAI,UACFL,EACA,OAAO,QAAUQ,EACjB,OAAO,QAAUA,EACjB,OAAO,WAAaA,EACpB,OAAO,YAAcA,EACrB,EACA,EACAC,EAAI,MACJA,EAAI,MACN,EACOA,EAAI,UAAU,aAAcG,CAAO,GAdzB,IAenB,CCvXO,IAAMC,EAAU,CACrB,OAAQ,iBACR,cAAe,yBACf,QAAS,kBACT,aAAc,eACd,cAAe,gBACf,mBAAoB,qBACpB,cAAe,gBACf,OAAQ,SACR,eAAgB,iBAChB,gBAAiB,kBACjB,eAAgB,yBAChB,cAAe,gBACf,YAAa,cACb,eAAgB,iBAChB,aAAc,eACd,qBAAsB,uBACtB,kBAAmB,oBACnB,aAAc,eACd,cAAe,gBACf,mBAAoB,qBACpB,cAAe,gBACf,YAAa,cACb,cAAe,gBACf,mBAAoB,qBACpB,qBAAsB,uBACtB,kBAAmB,oBACnB,aAAc,eACd,cAAe,gBACf,aAAc,eACd,mBAAoB,qBACpB,mBAAoB,qBACpB,cAAe,gBACf,gBAAiB,kBACjB,YAAa,cACb,kBAAmB,oBACnB,cAAe,gBACf,YAAa,cACb,kBAAmB,oBACnB,cAAe,gBACf,aAAc,eACd,gBAAiB,kBACjB,mBAAoB,qBACpB,cAAe,gBACf,YAAa,cACb,aAAc,eACd,WAAY,aACZ,WAAY,aACZ,UAAW,YACX,aAAc,eACd,YAAa,cACb,WAAY,aACZ,cAAe,gBACf,kBAAmB,oBACnB,YAAa,cACb,cAAe,gBACf,OAAQ,kBACR,aAAc,wBACd,eAAgB,0BAChB,YAAa,uBACb,cAAe,yBACf,aAAc,eACd,eAAgB,iBAChB,eAAgB,iBAChB,eAAgB,iBAChB,kBAAmB,oBACnB,sBAAuB,wBACvB,gBAAiB,kBACjB,mBAAoB,qBACpB,QAAS,mBACT,cAAe,yBACf,cAAe,yBACf,gBAAiB,2BACjB,gBAAiB,2BACjB,eAAgB,0BAChB,eAAgB,0BAChB,SAAU,oBACV,aAAc,wBACd,eAAgB,0BAChB,oBAAqB,sBACrB,iBAAkB,mBAClB,gBAAiB,kBACjB,mBAAoB,qBACpB,uBAAwB,yBACxB,uBAAwB,yBACxB,oBAAqB,sBACrB,iBAAkB,mBAClB,mBAAoB,qBACpB,gBAAiB,kBACjB,eAAgB,iBAChB,YAAa,cACb,aAAc,eACd,aAAc,eACd,kBAAmB,oBACnB,yBAA0B,2BAC1B,mBAAoB,qBACpB,mBAAoB,qBACpB,mBAAoB,qBACpB,kBAAmB,oBACnB,qBAAsB,uBACtB,YAAa,cACb,WAAY,aACZ,WAAY,aACZ,kBAAmB,oBACnB,mBAAoB,qBACpB,gBAAiB,kBACjB,eAAgB,iBAChB,sBAAuB,wBACvB,iBAAkB,mBAClB,yBAA0B,4BAC1B,mBAAoB,qBACpB,iBAAkB,mBAClB,WAAY,aAEZ,cAAe,iBAGf,iBAAkB,oBAClB,gBAAiB,kBACjB,aAAc,eACd,oBAAqB,sBACrB,WAAY,aACZ,cAAe,gBACf,cAAe,gBACf,oBAAqB,8BACrB,YAAa,cACb,iBAAkB,mBAClB,kBAAmB,oBACnB,iBAAkB,mBAClB,gBAAiB,kBACjB,mBAAoB,qBACpB,aAAc,eACd,aAAc,eACd,MAAO,iBACP,aAAc,yBACd,WAAY,uBACZ,eAAgB,2BAChB,UAAW,sBACX,eAAgB,2BAChB,cAAe,gBACf,cAAe,sBACf,aAAc,qBACd,YAAa,oBACb,2BAA4B,mCAC5B,eAAgB,uBAChB,UAAW,qBACX,aAAc,eACd,cAAe,gBACf,mBAAoB,sBACpB,oBAAqB,sBACrB,oBAAqB,sBACrB,aAAc,eACd,iBAAkB,mBAClB,iBAAkB,mBAClB,sBAAuB,wBAGvB,cAAe,gBACf,kBAAmB,oBACrB,EASaC,GAAoB,CAACD,EAAQ,cAAc,EAI3CE,GAA6B,0BAE7BC,EAAM,CACjB,QAAS,kBACT,YAAa,cACb,cAAe,gBACf,eAAgB,iBAChB,OAAQ,yBACR,cAAe,gCACf,mBAAoB,oBACtB,EAIaC,EAAc,GAIdC,GAAkB,EAKlBC,EAAW,CAAC,OAAQ,cAAe,YAAa,UAAU,EAO1DC,EAAgB,CAC3B,KAAM,UACN,YAAa,UACb,UAAW,UACX,SAAU,SACZ,EAGaC,EAAgB,CAAC,MAAO,aAAc,WAAY,aAAa,EAE/DC,EAAc,CACzB,IAAK,UACL,WAAY,UACZ,SAAU,UACV,YAAa,SACf,EAGaC,EAAa,CAAC,OAAQ,SAAU,KAAK,EAMrCC,EAAkB,CAC7B,KAAM,UACN,OAAQ,UACR,IAAK,SACP,EAMaC,GAAkB,CAAC,YAAM,YAAM,eAAM,YAAM,YAAM,WAAI,EAErDC,GAAY,CACvB,UAAW,yDACb,EAEaC,EAAU,CACrB,OAAQ,KACR,QAAS,IACT,QAAS,KACT,YAAa,KACb,SAAU,MAGV,QAAS,KACX,EAiBaC,GAAa,mdAGbC,GAAiB,MClQvB,IAAMC,GAAN,KAAkB,CAkBvB,YAAY,CACV,KAAAC,EACA,eAAAC,EACA,sBAAAC,EAAwB,GACxB,YAAAC,EAAc,GACd,kBAAAC,EAAoB,GACpB,eAAAC,EACA,iBAAAC,EACA,gBAAAC,EACA,QAAAC,EACA,QAAAC,CACF,EAAG,CACD,KAAK,KAAOT,EACZ,KAAK,eAAiBC,EACtB,KAAK,sBAAwBC,EAC7B,KAAK,YAAcC,EACnB,KAAK,kBAAoBC,EACzB,KAAK,eAAiBC,EACtB,KAAK,iBAAmBC,EACxB,KAAK,gBAAkBC,EACvB,KAAK,QAAUC,EACf,KAAK,QAAUC,EASf,KAAK,eAAiB,KAQtB,KAAK,cAAgB,KAYrB,KAAK,aAAe,KAGpB,KAAK,WAAa,KAClB,KAAK,YAAc,GAEnB,KAAK,eAAiB,KACtB,KAAK,eAA4CC,GAAM,KAAK,WAAWA,CAAC,EACxE,KAAK,cAA2CA,GAAM,KAAK,UAAUA,CAAC,CACxE,CAGA,UAAoC,EAAG,CACrC,KAAK,WAAa,CAAE,EAAG,EAAE,QAAS,EAAG,EAAE,OAAQ,EAC/C,KAAK,YAAc,GACnB,SAAS,iBAAiB,YAAa,KAAK,cAAc,EAC1D,SAAS,iBAAiB,UAAW,KAAK,aAAa,CACzD,CAEA,WAAqC,EAAG,CACtC,IAAMC,EAAK,EAAE,QAAU,KAAK,WAAW,EACjCC,EAAK,EAAE,QAAU,KAAK,WAAW,EAEvC,GAAI,CAAC,KAAK,aAAe,KAAK,MAAMD,EAAIC,CAAE,EAAI,EAAG,OAEjD,KAAK,YAAc,GAEnB,IAAMC,EAAO,KAAK,IAAI,KAAK,WAAW,EAAG,EAAE,OAAO,EAC5CC,EAAM,KAAK,IAAI,KAAK,WAAW,EAAG,EAAE,OAAO,EAC3CC,EAAQ,KAAK,IAAIJ,CAAE,EACnBK,EAAS,KAAK,IAAIJ,CAAE,EAErB,KAAK,iBACR,KAAK,eAAiB,SAAS,cAAc,KAAK,EAClD,KAAK,eAAe,UAAYK,EAAQ,eACxC,KAAK,KAAK,YAAY,KAAK,cAAc,GAG3C,KAAK,eAAe,MAAM,KAAO,GAAGJ,CAAI,KACxC,KAAK,eAAe,MAAM,IAAM,GAAGC,CAAG,KACtC,KAAK,eAAe,MAAM,MAAQ,GAAGC,CAAK,KAC1C,KAAK,eAAe,MAAM,OAAS,GAAGC,CAAM,IAC9C,CAEA,MAAM,UAAoC,EAAG,CAI3C,GAHA,SAAS,oBAAoB,YAAa,KAAK,cAAc,EAC7D,SAAS,oBAAoB,UAAW,KAAK,aAAa,EAEtD,KAAK,YAAa,CACpB,IAAMH,EAAO,KAAK,IAAI,KAAK,WAAW,EAAG,EAAE,OAAO,EAC5CC,EAAM,KAAK,IAAI,KAAK,WAAW,EAAG,EAAE,OAAO,EAC3CC,EAAQ,KAAK,IAAI,EAAE,QAAU,KAAK,WAAW,CAAC,EAC9CC,EAAS,KAAK,IAAI,EAAE,QAAU,KAAK,WAAW,CAAC,EAErD,KAAK,gBAAgB,OAAO,EAC5B,KAAK,eAAiB,KAEtB,IAAME,EACJH,EAAQ,IAAMC,EAAS,GAAK,CAAE,KAAAH,EAAM,IAAAC,EAAK,MAAAC,EAAO,OAAAC,CAAO,EAAI,OACzDE,GAAQ,KAAK,mBAAmBA,CAAM,EAY1C,MAAM,KAAK,QAAQL,EAAOE,EAAQ,EAAGD,EAAME,EAAS,EAAGE,CAAM,CAC/D,MACE,MAAM,KAAK,QAAQ,KAAK,WAAW,EAAG,KAAK,WAAW,CAAC,EAGzD,KAAK,YAAc,GACnB,KAAK,WAAa,IACpB,CAgBA,mBAAmBA,EAAQ,CACzB,IAAMC,EAAQ,CAAC,EACf,KAAK,aAAeA,EACpB,IAAMC,EAAY,IAAM,KAAK,eAAiBD,EAExCE,EAASC,GAAW,CACxB,MAAO,EACP,sBAAuB,KAAK,sBAC5B,YAAa,KAAK,YAClB,kBAAmB,KAAK,kBACxB,eAAgB,KAAK,cACvB,CAAC,EAEG,KAAK,iBACP,KAAK,eAAiBD,EACnB,KAAK,CAAC,CAAE,OAAAE,EAAQ,MAAAC,CAAM,IACrBJ,EAAU,EAAIK,GAAaF,EAAQ,CAAE,YAAaC,CAAM,CAAC,EAAI,IAC/D,EAGC,MAAM,IAAM,IAAI,GAGrB,KAAK,kBAAkB,EAAI,EAC3B,KAAK,cAAgBH,EAClB,KAAK,CAAC,CAAE,OAAAE,EAAQ,MAAAC,CAAM,IAAM,CAC3B,GAAI,CAACJ,EAAU,EAAG,OAIlB,IAAMM,EAAUC,GAAWJ,EAAQL,EAAQ,CAAE,YAAaM,CAAM,CAAC,EAC7DE,GAAS,KAAK,iBAAiBA,CAAO,CAC5C,CAAC,EACA,MAAOE,GAAQ,CACd,QAAQ,KAAK,uCAAwCA,CAAG,EACxD,KAAK,UAAUA,CAAG,CACpB,CAAC,EACA,QAAQ,IAAM,CAGTR,EAAU,GAAG,KAAK,kBAAkB,EAAK,CAC/C,CAAC,CACL,CAWA,iBAAkB,CACZ,CAAC,KAAK,gBAAkB,KAAK,iBACjC,KAAK,eAAiBE,GAAW,CAC/B,MAAOO,GACP,sBAAuB,KAAK,sBAC5B,YAAa,KAAK,YAClB,kBAAmB,KAAK,kBACxB,eAAgB,KAAK,cACvB,CAAC,EACE,KAAK,CAAC,CAAE,OAAAN,EAAQ,MAAAC,CAAM,IAAMC,GAAaF,EAAQ,CAAE,YAAaC,CAAM,CAAC,CAAC,EACxE,MAAOI,IACN,QAAQ,KAAK,wCAAyCA,CAAG,EACzD,KAAK,UAAUA,CAAG,EACX,KACR,EACL,CAcA,MAAM,gBAAiB,CACrB,aAAM,KAAK,cACJ,KAAK,eAAiB,MAAM,KAAK,eAAiB,IAC3D,CAGA,cAAe,CACb,KAAK,eAAiB,KACtB,KAAK,cAAgB,KAGrB,KAAK,aAAe,IACtB,CAGA,SAAU,CACR,SAAS,oBAAoB,YAAa,KAAK,cAAc,EAC7D,SAAS,oBAAoB,UAAW,KAAK,aAAa,EAC1D,KAAK,gBAAgB,OAAO,EAC5B,KAAK,eAAiB,KACtB,KAAK,eAAiB,KACtB,KAAK,cAAgB,KACrB,KAAK,aAAe,KACpB,KAAK,WAAa,KAClB,KAAK,YAAc,EACrB,CACF,ECrRA,IAAME,GAAW,CACf,CAAE,KAAM,OAAQ,GAAI,eAAgB,EACpC,CAAE,KAAM,SAAU,GAAI,kBAAmB,EACzC,CAAE,KAAM,UAAW,GAAI,mBAAoB,EAC3C,CAAE,KAAM,SAAU,GAAI,2BAA4B,CACpD,EAGMC,GAAoB,CACxB,CAAE,KAAM,MAAO,GAAI,4CAA6C,EAChE,CAAE,KAAM,UAAW,GAAI,kBAAmB,EAC1C,CAAE,KAAM,UAAW,GAAI,qBAAsB,EAC7C,CAAE,KAAM,QAAS,GAAI,oBAAqB,EAC1C,CAAE,KAAM,QAAS,GAAI,OAAQ,CAC/B,EAEMC,GAAU,CAAE,KAAM,UAAW,QAAS,EAAG,EAIzCC,GAAiBC,GAAU,yBAAyB,KAAKA,CAAK,EAE9DC,GAAa,CAACC,EAAOC,IAAO,CAChC,OAAW,CAAE,KAAAC,EAAM,GAAAC,CAAG,IAAKH,EAAO,CAChC,IAAMI,EAAQH,EAAG,MAAME,CAAE,EACzB,GAAIC,EACF,MAAO,CAAE,KAAAF,EAAM,SAAUE,EAAM,CAAC,GAAK,IAAI,QAAQ,KAAM,GAAG,CAAE,CAEhE,CACA,MAAO,CAAE,GAAGR,EAAQ,CACtB,EAOO,SAASS,GAAeC,EAAM,OAAQ,CAC3C,IAAMC,EAAMD,EAAI,WAAa,CAAC,EACxBL,EAAKM,EAAI,WAAa,GACtBC,EAASD,EAAI,cAEfE,EAAUV,GAAWL,GAAUO,CAAE,EAC/BH,EAAQU,GAAQ,QAAQ,KAAME,GAAM,CAACb,GAAca,EAAE,KAAK,CAAC,EAC7DZ,IACFW,EAAU,CAAE,KAAMX,EAAM,MAAO,QAASA,EAAM,SAAW,EAAG,GAG9D,IAAMa,EAAKZ,GAAWJ,GAAmBM,CAAE,EAC3C,OAAIO,GAAQ,WAAUG,EAAG,KAAOH,EAAO,UAEhC,CACL,QAAS,EACT,IAAKF,EAAI,UAAU,MAAQ,GAC3B,SAAU,CAAE,MAAOA,EAAI,WAAY,OAAQA,EAAI,WAAY,EAC3D,OAAQ,CACN,MAAOA,EAAI,QAAQ,OAAS,EAC5B,OAAQA,EAAI,QAAQ,QAAU,CAChC,EACA,iBAAkBA,EAAI,kBAAoB,EAC1C,UAAWL,EACX,QAAAQ,EACA,GAAAE,EACA,SAAUJ,EAAI,UAAY,EAC5B,CACF,CCpDA,IAAMK,GAAY,4EAEZC,GAAkB,IAAIC,IAC1BA,EACG,IACEC,GAAa,IAAIA,CAAQ,6CAA6CA,CAAQ,qDAAqDA,CAAQ,kFAAkFA,CAAQ,qEACxO,EACC,KAAK,EAAE,EAECC,GAAY,IAAM,kSAAkSC,EAAI,OAAO,2EAA2EC,EAAQ,OAAO,MAAMC,EAAQ,sBAAsB,wBAAwBA,EAAQ,sBAAsB,6cAA6cA,EAAQ,sBAAsB,WACr+BA,EAAQ,sBACV,6EAA6EA,EAAQ,YAAY,4DAA4DA,EAAQ,aAAa,6MAA6MA,EAAQ,eAAe,KAAKA,EAAQ,kBAAkB,6LAA6LA,EAAQ,kBAAkB,oDAAoDA,EAAQ,kBAAkB,yNAAyNA,EAAQ,sBAAsB,iBAC/8BA,EAAQ,kBACV,kCAAkCA,EAAQ,sBAAsB,gBAC9DA,EAAQ,kBACV,kCAAkCA,EAAQ,sBAAsB,gBAC9DA,EAAQ,kBACV,yBAAyBA,EAAQ,kBAAkB,0DAA0DA,EAAQ,mBAAmB,IAAIA,EAAQ,MAAM,oDAAoDF,EAAI,WAAW,oHAAoHC,EAAQ,WAAW,6EAA6ED,EAAI,WAAW,KAAKE,EAAQ,kBAAkB,+CAA+CA,EAAQ,YAAY,6HAA6HA,EAAQ,YAAY,KAAKA,EAAQ,gBAAgB,4EAA4EA,EAAQ,YAAY,KAAKA,EAAQ,gBAAgB,gDAAgDF,EAAI,aAAa,gMAAgMA,EAAI,aAAa,+CAA+CA,EAAI,aAAa,yCAAyCE,EAAQ,mBAAmB,qFAAqFA,EAAQ,gBAAgB,4LAA4LA,EAAQ,gBAAgB,yEAAyEA,EAAQ,MAAM,4BAA4BC,CAAW,aAAaA,CAAW,qLAAqLF,EAAQ,MAAM,qCAAqCC,EAAQ,MAAM,2FAA2FA,EAAQ,MAAM,IAAIA,EAAQ,SAAS,0JAA0JA,EAAQ,MAAM,IAAIA,EAAQ,aAAa,yJAAyJA,EAAQ,cAAc,KAAKA,EAAQ,MAAM,8BAA8BA,EAAQ,OAAO,0NAA0NP,EAAS,YAAYM,EAAQ,OAAO,uEAAuEC,EAAQ,OAAO,KAAKA,EAAQ,WAAW,oBAAoBA,EAAQ,cAAc,yNAAyND,EAAQ,OAAO,uEAAuEC,EAAQ,cAAc,KAAKA,EAAQ,aAAa,KAAKA,EAAQ,cAAc,KAAKA,EAAQ,kBAAkB,KAAKA,EAAQ,cAAc,KAAKA,EAAQ,iBAAiB,gBAAgBA,EAAQ,aAAa,2EAA2EP,EAAS,IAAIC,GACr8G,IAAIM,EAAQ,aAAa,GACzB,IAAIA,EAAQ,OAAO,GACnB,IAAIA,EAAQ,UAAU,GACtB,IAAIA,EAAQ,YAAY,GACxB,IAAIA,EAAQ,YAAY,EAC1B,CAAC,KAAKA,EAAQ,WAAW,8NAA8ND,EAAQ,WAAW,uFAAuFC,EAAQ,WAAW,WAAWA,EAAQ,WAAW,0DAA0DA,EAAQ,WAAW,4BAA4BA,EAAQ,YAAY,KAAKA,EAAQ,mBAAmB,uJAAuJA,EAAQ,YAAY,gCAAgCA,EAAQ,YAAY,6KAA6KA,EAAQ,YAAY,8CAA8CA,EAAQ,iBAAiB,uOAAuOA,EAAQ,wBAAwB,iIAAiIA,EAAQ,kBAAkB,8HAA8HA,EAAQ,kBAAkB,uCAAuCA,EAAQ,kBAAkB,2CAA2CA,EAAQ,kBAAkB,OAAOA,EAAQ,kBAAkB,sBAAsBA,EAAQ,oBAAoB,oFAAoFA,EAAQ,kBAAkB,0CAA0CA,EAAQ,iBAAiB,8OAA8OA,EAAQ,iBAAiB,2DAA2DA,EAAQ,iBAAiB,+HAA+HA,EAAQ,eAAe,+JAA+JA,EAAQ,eAAe,8CAA8CA,EAAQ,WAAW,KAAKA,EAAQ,aAAa,KAAKA,EAAQ,UAAU,8KAA8KA,EAAQ,WAAW,mCAAmCA,EAAQ,WAAW,WAAWA,EAAQ,aAAa,0BAA0BA,EAAQ,UAAU,0DAA0DA,EAAQ,aAAa,2CAA2CA,EAAQ,UAAU,KAAKA,EAAQ,YAAY,2BAA2BP,EAAS,+DAA+DO,EAAQ,UAAU,wHAAwHA,EAAQ,UAAU,KAAKA,EAAQ,UAAU,qBAAqBA,EAAQ,UAAU,KAAKA,EAAQ,UAAU,gDAAgDA,EAAQ,UAAU,8DAA8DA,EAAQ,UAAU,KAAKA,EAAQ,UAAU,iEAAiEA,EAAQ,UAAU,sDAAsDA,EAAQ,iBAAiB,4EAA4EA,EAAQ,kBAAkB,uFAAuFA,EAAQ,mBAAmB,KAAKA,EAAQ,kBAAkB,uCAAuCA,EAAQ,aAAa,6DAA6DA,EAAQ,iBAAiB,+BAA+BA,EAAQ,gBAAgB,qLAAqLA,EAAQ,gBAAgB,0DAA0DA,EAAQ,wBAAwB,kFAAkFA,EAAQ,kBAAkB,sDAAsDA,EAAQ,gBAAgB,wHAAwHA,EAAQ,eAAe,KAAKA,EAAQ,gBAAgB,gFAAgFA,EAAQ,eAAe,6DAA6DA,EAAQ,kBAAkB,4CAA4CA,EAAQ,cAAc,KAAKA,EAAQ,kBAAkB,iZAAiZA,EAAQ,UAAU,6MAA6MA,EAAQ,UAAU,IAAIA,EAAQ,aAAa,uCAAuCA,EAAQ,UAAU,IAAIA,EAAQ,gBAAgB,wBAAwBA,EAAQ,eAAe,iDAAiDA,EAAQ,cAAc,6IAA6IA,EAAQ,YAAY,0CAA0CA,EAAQ,UAAU,KAAKA,EAAQ,YAAY,qBAAqBA,EAAQ,OAAO,KAAKA,EAAQ,YAAY,wBAAwBA,EAAQ,KAAK,gMAAgMA,EAAQ,YAAY,KAAKA,EAAQ,UAAU,KAAKA,EAAQ,cAAc,sBAAsBA,EAAQ,SAAS,mBAAmBA,EAAQ,cAAc,uCAAuCA,EAAQ,YAAY,+FAA+FA,EAAQ,YAAY,2BAA2BA,EAAQ,gBAAgB,4CAA4CA,EAAQ,aAAa,uSAAuSA,EAAQ,aAAa,8CAA8CA,EAAQ,kBAAkB,4DAA4DA,EAAQ,kBAAkB,6CAA6CA,EAAQ,mBAAmB,oGAAoGA,EAAQ,mBAAmB,wCAAwCA,EAAQ,YAAY,mNAAmNA,EAAQ,YAAY,8DAA8DA,EAAQ,gBAAgB,gNAAgNA,EAAQ,iBAAiB,KAAKA,EAAQ,gBAAgB,KAAKA,EAAQ,oBAAoB,KAAKA,EAAQ,gBAAgB,wBAAwBA,EAAQ,gBAAgB,IAAIA,EAAQ,aAAa,uCAAuCA,EAAQ,qBAAqB,oQAAoQA,EAAQ,qBAAqB,8CAA8CA,EAAQ,aAAa,sHAAsHA,EAAQ,YAAY,iDAAiDA,EAAQ,aAAa,qFAAqFA,EAAQ,cAAc,2MAA2MA,EAAQ,cAAc,yCAAyCA,EAAQ,cAAc,oDAAoDA,EAAQ,cAAc,yDAAyDA,EAAQ,cAAc,6BAA6BA,EAAQ,YAAY,sBAAsBA,EAAQ,aAAa,wEAAwEA,EAAQ,0BAA0B,mBAAmBA,EAAQ,WAAW,uEAAuEA,EAAQ,WAAW,4DAA4DA,EAAQ,qBAAqB,oIAAoIA,EAAQ,qBAAqB,wBAAwBA,EAAQ,aAAa,gEAAgEA,EAAQ,aAAa,yBAAyBA,EAAQ,mBAAmB,kEAAkEA,EAAQ,WAAW,oKAAoKA,EAAQ,gBAAgB,kIAAkIA,EAAQ,iBAAiB,iDAAiDA,EAAQ,gBAAgB,oDAAoDA,EAAQ,eAAe,oNAAoNA,EAAQ,kBAAkB,+NAA+NA,EAAQ,kBAAkB,kFAAkFA,EAAQ,aAAa,8EAA8EA,EAAQ,WAAW,iEAAiEA,EAAQ,aAAa,8EAA8EA,EAAQ,kBAAkB,gEAAgEA,EAAQ,aAAa,qGAAqGA,EAAQ,WAAW,4FAA4FA,EAAQ,WAAW,8RAA8RA,EAAQ,WAAW,6BAA6BA,EAAQ,WAAW,+DAA+DA,EAAQ,cAAc,gBAAgBA,EAAQ,cAAc,yBAAyBA,EAAQ,YAAY,2JAA2JA,EAAQ,YAAY,KAAKA,EAAQ,WAAW,wBAAwBA,EAAQ,oBAAoB,iCAAiCA,EAAQ,YAAY,KAAKA,EAAQ,cAAc,8GAA8GA,EAAQ,iBAAiB,2GAA2GA,EAAQ,YAAY,gJAAgJA,EAAQ,YAAY,+CAA+CA,EAAQ,YAAY,yCAAyCA,EAAQ,aAAa,mKAAmKA,EAAQ,aAAa,2DAA2DA,EAAQ,aAAa,0GAA0GA,EAAQ,aAAa,wBAAwBA,EAAQ,cAAc,sOAAsOA,EAAQ,eAAe,oGAAoGD,EAAQ,QAAU,CAAC,MAAMC,EAAQ,eAAe,IAAIA,EAAQ,MAAM,qDAAqDA,EAAQ,cAAc,wGAAwGD,EAAQ,OAAO,4BAA4BC,EAAQ,qBAAqB,wJAAwJA,EAAQ,qBAAqB,sCAAsCA,EAAQ,qBAAqB,IAAIA,EAAQ,MAAM,mBAAmBA,EAAQ,eAAe,sCAAsCA,EAAQ,kBAAkB,uRAAuRA,EAAQ,eAAe,KAAKA,EAAQ,cAAc,sGAAsGA,EAAQ,eAAe,KAAKA,EAAQ,cAAc,yBAAyBA,EAAQ,eAAe,KAAKA,EAAQ,iBAAiB,qPAAqPA,EAAQ,eAAe,WAAWA,EAAQ,iBAAiB,mBAAmBA,EAAQ,eAAe,KAAKA,EAAQ,iBAAiB,uCAAuCA,EAAQ,OAAO,KAAKA,EAAQ,qBAAqB,KAC73fA,EAAQ,eACV,KAAKA,EAAQ,cAAc,KAAKA,EAAQ,aAAa,KAAKA,EAAQ,qBAAqB,KACrFA,EAAQ,eACV,KAAKA,EAAQ,cAAc,KAAKA,EAAQ,YAAY,KAAKA,EAAQ,eAAe,KAC9EA,EAAQ,cACV,+BAA+BA,EAAQ,OAAO,wLAAwLA,EAAQ,aAAa,6NAA6NA,EAAQ,aAAa,oDAAoDA,EAAQ,eAAe,kEAAkEA,EAAQ,eAAe,oDAAoDA,EAAQ,cAAc,KAAKA,EAAQ,cAAc,oHAAoHA,EAAQ,cAAc,mBAAmBA,EAAQ,cAAc,iEAAiEA,EAAQ,cAAc,wFAAwFA,EAAQ,cAAc,8CAA8CA,EAAQ,cAAc,qCAAqCA,EAAQ,cAAc,+BAA+BA,EAAQ,QAAQ,qFAAqFD,EAAQ,QAAQ,6JAA6JC,EAAQ,YAAY,0EAA0EA,EAAQ,cAAc,+PAA+PA,EAAQ,cAAc,6CAA6CA,EAAQ,MAAM,kEAAkEA,EAAQ,YAAY,+PAA+PP,EAAS,KAAKO,EAAQ,YAAY,8CAA8CA,EAAQ,cAAc,oDAAoDA,EAAQ,aAAa,KAAKA,EAAQ,WAAW,wIAAwIA,EAAQ,aAAa,wFAAwFA,EAAQ,aAAa,8CAA8CA,EAAQ,WAAW,qCAAqCA,EAAQ,WAAW,8CAA8CA,EAAQ,WAAW,gGAAgGA,EAAQ,aAAa,4FAA4FA,EAAQ,aAAa,8CAA2CA,EAAQ,aAAa,oRAAoRA,EAAQ,aAAa,6BAA6BA,EAAQ,WAAW,kFAAkFA,EAAQ,YAAY,+LAA+LA,EAAQ,YAAY,yCAAyCA,EAAQ,YAAY,kLAAkLA,EAAQ,YAAY,4DAA4DA,EAAQ,UAAU,wBAAwBA,EAAQ,UAAU,oFAAoFA,EAAQ,SAAS,qHAAqHA,EAAQ,YAAY,iDAAiDA,EAAQ,WAAW,gEAAgEA,EAAQ,UAAU,8FAA8FA,EAAQ,aAAa,+HAA+HA,EAAQ,iBAAiB,kFAAkFA,EAAQ,oBAAoB,+CAA+CA,EAAQ,iBAAiB,qNAAqNA,EAAQ,iBAAiB,yDAAyDA,EAAQ,YAAY,mKAAmKA,EAAQ,YAAY,mCAAmCA,EAAQ,YAAY,sDAAsDA,EAAQ,YAAY,mFAAmFA,EAAQ,aAAa,mFAAmFA,EAAQ,YAAY,0JAA0JA,EAAQ,kBAAkB,gEAAgEA,EAAQ,kBAAkB,iEAAiEA,EAAQ,aAAa,iDAAiDA,EAAQ,eAAe,yHAAyHA,EAAQ,WAAW,iGAAiGA,EAAQ,iBAAiB,6FAA6FA,EAAQ,aAAa,qFAAqFA,EAAQ,WAAW,oFAAoFA,EAAQ,iBAAiB,sFAAsFA,EAAQ,aAAa,uEAAuEA,EAAQ,YAAY,+HAA+HA,EAAQ,eAAe,sGAAsGA,EAAQ,kBAAkB,kMAAkMA,EAAQ,kBAAkB,yDAAyDA,EAAQ,aAAa,2FAA2FA,EAAQ,YAAY,k3BAA62BA,EAAQ,YAAY,6JAA6JF,EAAI,aAAa,mBAAmBE,EAAQ,YAAY,qDASvzRE,GAAkB,IAAM,KAAKF,EAAQ,cAAc,KAAKA,EAAQ,cAAc,kBAAkBG,EAAU,MAAMC,EAAc,qBAU9HC,GAAkB,IAAM,yrBC5C9B,SAASC,GAAYC,EAAQC,EAAKC,EAAY,CACnD,IAAMC,EAAQC,GAAeH,EAAKD,CAAM,EACxC,GAAIG,EAIF,OAAAH,EAAO,mBAAqB,CAAC,GAAIA,EAAO,oBAAsB,CAAC,EAAIG,CAAK,EACjE,IAAM,CACXH,EAAO,oBAAsBA,EAAO,oBAAsB,CAAC,GAAG,OAC3DK,GAAcA,IAAcF,CAC/B,CACF,EAIF,IAAMG,EAA6BN,EAAQ,MAAQA,EAC/BM,EAAQ,gBAAgB,IAAIJ,CAAU,EAAE,GAAG,OAAO,EAEtE,IAAMK,EAAQ,SAAS,cAAc,OAAO,EAC5C,OAAAA,EAAM,GAAKL,EACXK,EAAM,YAAcN,EACpBK,EAAO,YAAYC,CAAK,EACjB,IAAMA,EAAM,OAAO,CAC5B,CAUA,SAASH,GAAeH,EAAKD,EAAQ,CASnC,IAAMQ,GAHgBR,EAAQ,aAC5BA,EAAO,eAAe,aACtB,YACiB,cAEnB,GADI,OAAOQ,GAAU,YACjB,EAAE,uBAAwBR,GAAS,OAAO,KAC9C,GAAI,CACF,IAAMG,EAAQ,IAAIK,EAClB,OAAAL,EAAM,YAAYF,CAAG,EACdE,CACT,MAAQ,CACN,OAAO,IACT,CACF,CC3EA,IAAOM,EAAQ,CACb,aAAc,UACd,YAAa,UACb,aAAc,iBACd,yBAA0B,qBAC1B,wBAAyB,oBACzB,gBAAiB,WACjB,gBAAiB,YACjB,cAAe,UACf,kBAAmB,cACnB,gBAAiB,mBACjB,aAAc,6BACd,gBAAiB,WACjB,aAAc,QACd,YAAa,OACb,sBAAuB,iBACvB,qBAAsB,gBACtB,aAAc,sBACd,mBAAoB,SACpB,yBAA0B,gBAC1B,aAAc,QAEd,oBAAqB,gBACrB,gBAAiB,mBACjB,aAAc,sBACd,YAAa,kBACb,iBAAkB,mBAClB,yBAA0B,uBAC1B,wBAAyB,kBAEzB,uBAAwB,YACxB,UAAW,YACX,QAAS,WACT,mBAAoB,OACpB,iBAAkB,OAClB,gBAAiB,OACjB,eAAgB,UAChB,aAAc,QACd,oBAAqB,gBACrB,oBAAqB,gBACrB,YAAa,MACb,aAAc,OACd,cAAe,QACf,oBAAqB,cACrB,mBAAoB,uBACpB,YAAa,eACb,KAAM,OACN,iBAAkB,kBAClB,MAAO,QACP,iBAAkB,iBAClB,iBAAkB,WAClB,mBAAoB,sBACpB,kBAAmB,qBACnB,iBAAkB,oBAClB,oBAAqB,kBACrB,eAAgB,iBAChB,gBAAiB,kBACjB,uBAAwB,qDACxB,iBAAkB,uBAClB,eAAgB,kCAChB,cAAe,aACf,YAAa,SACb,UAAW,YACX,kBAAmB,eACnB,YAAa,SACb,YAAa,QACb,aAAc,OACd,eAAgB,SAChB,KAAM,OACN,cAAe,SACf,YAAa,eACb,aAAc,gBACd,YAAa,OACb,UAAW,aACX,SAAU,YACV,WAAY,cACZ,gBAAiB,YACjB,SAAU,OACV,WAAY,SACZ,WAAY,SACZ,eAAgB,UAChB,oBAAqB,mBACrB,sBACE,0EACF,eAAgB,UAChB,mBAAoB,eACpB,gBAAiB,oCACjB,cAAe,SACf,cAAe,SACf,0BAA2B,uBAC3B,4BACE,0EACF,2BACE,iGACF,wBAAyB,qBACzB,0BACE,wEACF,iBAAkB,qBAClB,OAAQ,SACR,YAAa,SACb,YAAa,mBACb,YAAa,eACb,UAAW,QACX,cAAe,UACf,mBAAoB,cACpB,eAAgB,kBAChB,YAAa,OACb,WAAY,OACZ,iBAAkB,cAClB,eAAgB,YAChB,eAAgB,WAChB,uBAAwB,MACxB,mBAAoB,kBACpB,UAAW,OACX,cAAe,WACf,MAAO,QACP,QAAS,MACT,eAAgB,aAChB,aAAc,WACd,gBAAiB,cACjB,aAAc,OACd,eAAgB,SAChB,YAAa,MACb,aAAc,OACd,iBAAkB,WAClB,eAAgB,UAChB,oBAAqB,oBACrB,WAAY,MACZ,gBAAiB,WACjB,cAAe,SACf,eAAgB,UAChB,UAAW,KACX,eAAgB,YAChB,YAAa,eACb,iBAAkB,oBAClB,kBAAmB,uBACnB,oBAAqB,mBACvB,ECzIA,IAAOC,GAAQ,CACb,aAAc,cACd,YAAa,cACb,aAAc,sBACd,yBAA0B,sBAC1B,wBAAyB,wBACzB,gBAAiB,aACjB,gBAAiB,aACjB,cAAe,WACf,kBAAmB,gBACnB,gBAAiB,yBACjB,aAAc,sCACd,gBAAiB,eACjB,aAAc,WACd,YAAa,QACb,sBAAuB,oBACvB,qBAAsB,oBACtB,aAAc,8BACd,mBAAoB,WACpB,yBAA0B,eAC1B,aAAc,UAEd,oBAAqB,kBACrB,gBAAiB,yBACjB,aAAc,wBACd,YAAa,oBACb,iBAAkB,6BAClB,yBAA0B,0BAC1B,wBAAyB,kBAEzB,uBAAwB,eACxB,UAAW,aACX,QAAS,cACT,mBAAoB,OACpB,iBAAkB,OAClB,gBAAiB,OACjB,eAAgB,WAChB,aAAc,UACd,oBAAqB,sBACrB,oBAAqB,sBACrB,YAAa,MACb,aAAc,OACd,cAAe,WACf,oBAAqB,mBACrB,mBAAoB,2BACpB,YAAa,kBACb,KAAM,SACN,iBAAkB,8BAClB,MAAO,SACP,iBAAkB,sBAClB,iBAAkB,eAClB,mBAAoB,8BACpB,kBAAmB,6BACnB,iBAAkB,6BAClB,oBAAqB,mBACrB,eAAgB,yBAChB,gBAAiB,gCACjB,uBACE,wEACF,iBAAkB,eAClB,eAAgB,kDAChB,cAAe,aACf,YAAa,SACb,UAAW,uBACX,kBAAmB,mBACnB,YAAa,UACb,YAAa,UACb,aAAc,YACd,eAAgB,SAChB,KAAM,SACN,cAAe,WACf,YAAa,qBACb,aAAc,2BACd,YAAa,SACb,UAAW,mBACX,SAAU,gBACV,WAAY,iBACZ,gBAAiB,eACjB,SAAU,UACV,WAAY,WACZ,WAAY,UACZ,eAAgB,cAChB,oBAAqB,6BACrB,sBACE,6EACF,eAAgB,YAChB,mBAAoB,kBACpB,gBAAiB,+CACjB,cAAe,WACf,cAAe,WACf,0BAA2B,2BAC3B,4BACE,4FACF,2BACE,oHACF,wBAAyB,0BACzB,0BACE,2FACF,iBAAkB,4BAClB,OAAQ,UACR,YAAa,SACb,YAAa,sBACb,YAAa,uBACb,UAAW,YACX,cAAe,cACf,mBAAoB,iBACpB,eAAgB,0BAChB,YAAa,SACb,WAAY,UACZ,iBAAkB,cAClB,eAAgB,iBAChB,eAAgB,WAChB,uBAAwB,MACxB,mBAAoB,kBACpB,UAAW,OACX,cAAe,YACf,MAAO,cACP,QAAS,MACT,eAAgB,aAChB,aAAc,WACd,gBAAiB,SACjB,aAAc,OACd,eAAgB,QAChB,YAAa,OACb,aAAc,OACd,iBAAkB,YAClB,eAAgB,WAChB,oBAAqB,yBACrB,WAAY,MACZ,gBAAiB,WACjB,cAAe,WACf,eAAgB,YAChB,UAAW,KACX,eAAgB,aAChB,YAAa,wBACb,iBAAkB,2BAClB,kBAAmB,wBACnB,oBAAqB,wBACvB,ECvIA,IAAMC,GAAU,CAAE,GAAAC,EAAI,GAAAC,EAAG,EACnBC,GAAiB,KAOhB,SAASC,IAAe,CAC7B,IAAMC,GAAQ,UAAU,UAAYF,IAAgB,MAAM,EAAG,CAAC,EAAE,YAAY,EAC5E,OAAOE,KAAQL,GAAsCK,EAAQF,EAC/D,CAUO,SAASG,GAAWC,EAAY,CACrC,IAAMC,EAAWR,GAAQO,CAAU,EACnC,MAAI,CAACC,GAAYD,IAAeJ,GACvBH,GAAQG,EAAc,EAExB,CAAE,GAAGH,GAAQG,EAAc,EAAG,GAAGK,CAAS,CACnD,CASO,SAASC,EAAeC,EAAUC,EAAG,CAC1C,OAAOD,EAAS,QAAQ,MAAO,OAAOC,CAAC,CAAC,CAC1C,CAEA,IAAMC,GAAY,IASX,SAASC,EAAeC,EAAIC,EAAS,CAC1C,GAAI,CAAC,OAAO,SAASD,CAAE,GAAKA,EAAK,EAAG,MAAO,GAE3C,IAAME,EAAe,KAAK,MAAMF,EAAKF,EAAS,EAC9C,GAAII,EAAe,EAAG,OAAOD,EAAQ,uBACrC,GAAIC,EAAe,GACjB,OAAOP,EAAeM,EAAQ,mBAAoBC,CAAY,EAGhE,IAAMC,EAAa,KAAK,MAAMD,EAAe,EAAE,EAC/C,GAAIC,EAAa,GAAI,CACnB,IAAMC,EAAUF,EAAe,GACzBG,EAAQV,EAAeM,EAAQ,iBAAkBE,CAAU,EACjE,OAAOC,EACH,GAAGC,CAAK,IAAIV,EAAeM,EAAQ,mBAAoBG,CAAO,CAAC,GAC/DC,CACN,CAEA,IAAMC,EAAY,KAAK,MAAMH,EAAa,EAAE,EACtCE,EAAQF,EAAa,GACrBI,EAAOZ,EAAeM,EAAQ,gBAAiBK,CAAS,EAC9D,OAAOD,EACH,GAAGE,CAAI,IAAIZ,EAAeM,EAAQ,iBAAkBI,CAAK,CAAC,GAC1DE,CACN,CCrEA,IAAMC,GAAmB,GACnBC,GAAuB,EACvBC,GAAuB,EAIvBC,GAAqB,GACrBC,GAAmB,GAEnBC,GAA4B,0BAC5BC,GAAyB,oCACzBC,GAAoB,CAAC,KAAM,OAAQ,OAAQ,YAAY,EACvDC,GAAsB,CAAC,cAAe,OAAQ,YAAY,EAE1DC,GAAaC,GACb,OAAO,IAAQ,KAAe,IAAI,OAAe,IAAI,OAAOA,CAAK,EAC9D,OAAOA,CAAK,EAAE,QAAQ,kBAAmB,MAAM,EAGlDC,GAAmBD,GAAU,OAAOA,CAAK,EAAE,QAAQ,SAAU,MAAM,EAEnEE,GAAW,CAACC,EAAUC,IAAQ,CAClC,GAAI,CACF,OAAOA,EAAI,iBAAiBD,CAAQ,EAAE,SAAW,CACnD,MAAQ,CACN,MAAO,EACT,CACF,EAEME,GAAiBC,IACpBA,GAAQ,IAAI,QAAQ,OAAQ,GAAG,EAAE,KAAK,EAAE,MAAM,EAAGhB,EAAgB,EAO9DiB,GAAiBC,GACjBC,GAAkB,SAASD,CAAG,GAC9Bb,GAA0B,KAAKa,CAAG,EAAU,GACzC,CAACA,EAAI,MAAM,MAAM,EAAE,KAAME,GAASA,EAAK,QAAU,GAAK,KAAK,KAAKA,CAAI,CAAC,EAGxEC,GAAmBC,GACvB,CAAC,GAAGA,EAAQ,SAAS,EAAE,OAAOL,EAAa,EAEvCM,GAAoBD,GAAY,CAEpC,IAAME,EAAQ,CAAC,EACf,OAAW,CAAE,KAAAC,EAAM,MAAAf,CAAM,IAAKY,EAAQ,YAElCf,GAAkB,SAASkB,CAAI,GAC9BA,EAAK,WAAW,OAAO,GAAK,CAACnB,GAAuB,KAAKmB,CAAI,IAChDf,IAAOc,EAAMC,CAAI,EAAIf,EAAM,MAAM,EAAGV,EAAgB,GAEtE,OAAOwB,CACT,EAEME,GAAmBJ,GAAY,CACnC,IAAMK,EAASL,EAAQ,cACvB,GAAI,CAACK,EAAQ,MAAO,CAAE,MAAO,EAAG,MAAO,CAAE,EACzC,IAAMC,EAAU,CAAC,GAAGD,EAAO,QAAQ,EAAE,OAClCE,GAAUA,EAAM,UAAYP,EAAQ,OACvC,EACA,MAAO,CAAE,MAAOM,EAAQ,QAAQN,CAAO,EAAG,MAAOM,EAAQ,MAAO,CAClE,EAEME,GAAa,CAACR,EAASR,IAAQ,CACnC,GAAI,CAACQ,EAAQ,GAAI,OAAO,KACxB,IAAMT,EAAW,IAAIJ,GAAUa,EAAQ,EAAE,CAAC,GAC1C,OAAOV,GAASC,EAAUC,CAAG,EAAID,EAAW,IAC9C,EAEMkB,GAAoB,CAACT,EAASR,IAAQ,CAC1C,IAAMkB,EAAMV,EAAQ,QAAQ,YAAY,EACxC,QAAWG,KAAQjB,GAAqB,CACtC,IAAME,EAAQY,EAAQ,aAAaG,CAAI,EACvC,GAAI,CAACf,EAAO,SACZ,IAAMG,EAAW,GAAGmB,CAAG,IAAIP,CAAI,KAAKd,GAAgBD,CAAK,CAAC,KAC1D,GAAIE,GAASC,EAAUC,CAAG,EAAG,OAAOD,CACtC,CACA,OAAO,IACT,EAEMoB,GAAoB,CAACX,EAASR,IAAQ,CAC1C,IAAMoB,EAAW,CAAC,EACdC,EAAUb,EACd,QAASc,EAAQ,EAAGA,EAAQnC,IAAwBkC,EAASC,IAAS,CACpE,IAAMC,EAAUhB,GAAgBc,CAAO,EACjCH,EAAMG,EAAQ,QAAQ,YAAY,EAMxC,GALAD,EAAS,QACPG,EAAQ,OAAS,GAAGL,CAAG,IAAIK,EAAQ,IAAI5B,EAAS,EAAE,KAAK,GAAG,CAAC,GAAKuB,CAClE,EAGII,IAAU,GAAK,CAACC,EAAQ,OAAQ,OAAO,KAC3C,IAAMxB,EAAWqB,EAAS,KAAK,KAAK,EACpC,GAAItB,GAASC,EAAUC,CAAG,EAAG,OAAOD,EACpCsB,EAAUA,EAAQ,aACpB,CACA,OAAO,IACT,EAEMG,GAAqB,CAAChB,EAASR,IAAQ,CAC3C,GAAIQ,IAAYR,EAAI,KAAM,MAAO,OACjC,IAAMoB,EAAW,CAAC,EACdC,EAAUb,EACd,QAASc,EAAQ,EAAGA,EAAQlC,IAAwBiC,EAASC,IAAS,CACpE,GAAID,IAAYrB,EAAI,KAAM,CACxBoB,EAAS,QAAQ,MAAM,EACvB,KACF,CACA,GAAIC,EAAQ,GAAI,CACd,IAAMI,EAAS,CAAC,IAAI9B,GAAU0B,EAAQ,EAAE,CAAC,GAAI,GAAGD,CAAQ,EAAE,KAAK,KAAK,EACpE,GAAItB,GAAS2B,EAAQzB,CAAG,EAAG,OAAOyB,CACpC,CACA,GAAM,CAAE,MAAAC,CAAM,EAAId,GAAgBS,CAAO,EACzCD,EAAS,QACP,GAAGC,EAAQ,QAAQ,YAAY,CAAC,gBAAgBK,EAAQ,CAAC,GAC3D,EACAL,EAAUA,EAAQ,aACpB,CACA,IAAMtB,EAAWqB,EAAS,KAAK,KAAK,EACpC,OAAOtB,GAASC,EAAUC,CAAG,EAAID,EAAW,IAC9C,EAEM4B,GAAmB,CAACnB,EAASR,IACjCgB,GAAWR,EAASR,CAAG,GACvBiB,GAAkBT,EAASR,CAAG,GAC9BmB,GAAkBX,EAASR,CAAG,GAC9BwB,GAAmBhB,EAASR,CAAG,EAQ1B,SAAS4B,GAAwBpB,EAAS,CAC/C,OAAOmB,GAAiBnB,EAASA,EAAQ,aAAa,CACxD,CASO,SAASqB,GAAarB,EAASsB,EAAWC,EAAW,CAC1D,IAAM/B,EAAMQ,EAAQ,cACd,CAAE,MAAAkB,EAAO,MAAAM,CAAM,EAAIpB,GAAgBJ,CAAO,EAChD,MAAO,CACL,QAAS,EACT,SAAUmB,GAAiBnB,EAASR,CAAG,EACvC,YAAa,CACX,QAASQ,EAAQ,QACjB,YAAaP,GAAcO,EAAQ,WAAW,EAC9C,WAAYC,GAAiBD,CAAO,EACpC,aAAckB,EACd,aAAcM,CAChB,EACA,UAAAF,EACA,UAAAC,CACF,CACF,CAEA,IAAME,GAAiB,CAACC,EAAGC,IAAM,CAC/B,GAAI,CAACD,GAAK,CAACC,EAAG,MAAO,GACrB,GAAI,CAACD,GAAK,CAACC,EAAG,MAAO,GACrB,GAAID,IAAMC,EAAG,MAAO,GACpB,GAAID,EAAE,WAAWC,CAAC,GAAKA,EAAE,WAAWD,CAAC,EAAG,MAAO,IAC/C,IAAME,EAAU,IAAI,IAAIF,EAAE,MAAM,GAAG,CAAC,EAC9BG,EAAU,IAAI,IAAIF,EAAE,MAAM,GAAG,CAAC,EAChCG,EAAS,EACb,QAAWC,KAASH,EAAaC,EAAQ,IAAIE,CAAK,GAAGD,IACrD,MAAQ,GAAIA,GAAWF,EAAQ,KAAOC,EAAQ,KAChD,EAEMG,GAAsB,CAAChC,EAASE,IAAU,CAC9C,IAAM+B,EAAQ,OAAO,KAAK/B,CAAK,EAC/B,GAAI,CAAC+B,EAAM,OAAQ,MAAO,GAC1B,IAAIC,EAAU,EACd,QAAW/B,KAAQ8B,GAEdjC,EAAQ,aAAaG,CAAI,GAAK,IAAI,MAAM,EAAGzB,EAAgB,IAC5DwB,EAAMC,CAAI,GAEV+B,IAGJ,OAAOA,EAAUD,EAAM,MACzB,EAEME,GAAqB,CAACnC,EAASoC,IAAgB,CACnD,GAAM,CAAE,MAAAlB,EAAO,MAAAM,CAAM,EAAIpB,GAAgBJ,CAAO,EAC1CqC,EAAQ,KAAK,IAAInB,EAAQkB,EAAY,YAAY,EACjDE,EAAO,KAAK,IAAIF,EAAY,aAAcZ,EAAO,CAAC,EACxD,OAAO,KAAK,IAAI,EAAG,EAAIa,EAAQC,CAAI,CACrC,EAEMC,GAAe,CAACvC,EAASoC,IAAgB,CAC7C,GAAIpC,EAAQ,UAAYoC,EAAY,QAAS,MAAO,GAEpD,IAAMI,EAAU,EAAQJ,EAAY,YAC9BK,EAAW,OAAO,KAAKL,EAAY,YAAc,CAAC,CAAC,EAAE,OAAS,EAIhEM,EAAa,GACbC,EAAa,GACXC,EAAY,GAUlB,OATKH,EAGOD,IACVG,GAAcD,EACdA,EAAa,IAJbA,GAAcC,EACdA,EAAa,GAOX,CAACH,GAAW,CAACC,EACRN,GAAmBnC,EAASoC,CAAW,EAI9CM,EACEjB,GACEhC,GAAcO,EAAQ,WAAW,EACjCoC,EAAY,WACd,EACFO,EAAaX,GAAoBhC,EAASoC,EAAY,YAAc,CAAC,CAAC,EACtEQ,EAAYT,GAAmBnC,EAASoC,CAAW,CAEvD,EAEMS,GAAY,CAACC,EAAYV,IAAgB,CAC7C,IAAIW,EAAO,KACX,QAAW/C,KAAW8C,EAAY,CAChC,IAAME,EAAaT,GAAavC,EAASoC,CAAW,GAElD,CAACW,GACDC,EAAaD,EAAK,YAMjBC,IAAeD,EAAK,YAAcA,EAAK,QAAQ,SAAS/C,CAAO,KAEhE+C,EAAO,CAAE,QAAA/C,EAAS,WAAAgD,CAAW,EAEjC,CACA,OAAOD,CACT,EASO,SAASE,GAAcC,EAAQ1D,EAAM,SAAU,CAIpD,GAAI0D,GAAQ,SAAW,MAAQA,EAAO,QAAU,EAAG,OAAO,KAE1D,IAAMd,EAAcc,GAAQ,YAC5B,GAAI,CAACd,GAAe,CAACA,EAAY,QAAS,OAAO,KAEjD,GAAIc,EAAO,SAAU,CACnB,IAAIJ,EAAa,CAAC,EAClB,GAAI,CACFA,EAAa,CAAC,GAAGtD,EAAI,iBAAiB0D,EAAO,QAAQ,CAAC,CACxD,MAAQ,CAER,CACA,IAAMH,EAAOF,GAAUC,EAAYV,CAAW,EAC9C,GAAIW,GAAQA,EAAK,YAAclE,GAAoB,OAAOkE,CAC5D,CAOA,GAAI,EAFF,EAAQX,EAAY,aACpB,OAAO,KAAKA,EAAY,YAAc,CAAC,CAAC,EAAE,OAAS,GACrC,OAAO,KAEvB,IAAIU,EACJ,GAAI,CACFA,EAAa,CAAC,GAAGtD,EAAI,iBAAiB4C,EAAY,OAAO,CAAC,CAC5D,MAAQ,CACN,OAAO,IACT,CACA,IAAMW,EAAOF,GAAUC,EAAYV,CAAW,EAC9C,OAAOW,GAAQA,EAAK,YAAcjE,GAAmBiE,EAAO,IAC9D,CC1SO,IAAMI,GAAc,oBAIdC,GAAqB,0BAK3B,SAASC,IAAqB,CACnC,GAAI,CACF,IAAMC,EAAM,aAAa,QAAQH,EAAW,EAC5C,GAAI,CAACG,EAAK,MAAO,CAAC,EAClB,IAAMC,EAAS,KAAK,MAAMD,CAAG,EAC7B,OAAO,MAAM,QAAQC,CAAM,EAAIA,EAAS,CAAC,CAC3C,OAASC,EAAK,CACZ,eAAQ,KAAK,2CAA4CA,CAAG,EACrD,CAAC,CACV,CACF,CAEA,SAASC,GAAuBC,EAAU,CACxC,GAAI,CACF,oBAAa,QAAQP,GAAa,KAAK,UAAUO,CAAQ,CAAC,EACnD,EACT,MAAQ,CACN,MAAO,EACT,CACF,CAeO,SAASC,GAAoBD,EAAU,CAC5C,GAAID,GAAuBC,CAAQ,EAAG,MAAO,GAI7C,IAAME,EAAYF,EACf,IAAI,CAACG,EAASC,KAAW,CAAE,QAAAD,EAAS,MAAAC,CAAM,EAAE,EAC5C,OAAO,CAAC,CAAE,QAAAD,CAAQ,IAAMA,GAAS,iBAAiB,EAClD,KAAK,CAACE,EAAGC,IAAM,CACd,IAAMC,EAAQ,KAAK,MAAMF,EAAE,QAAQ,SAAS,EACtCG,EAAQ,KAAK,MAAMF,EAAE,QAAQ,SAAS,EAC5C,OAAI,OAAO,SAASC,CAAK,GAAK,OAAO,SAASC,CAAK,GAAKD,IAAUC,EACzDD,EAAQC,EAEVH,EAAE,MAAQC,EAAE,KACrB,CAAC,EAEH,GAAIJ,EAAU,SAAW,EACvB,eAAQ,KACN,qFACF,EACO,GAGT,IAAMO,EAAU,CAAC,GAAGT,CAAQ,EACxBU,EAAO,EACX,OAAW,CAAE,MAAAN,CAAM,IAAKF,EAGtB,GAFAO,EAAQL,CAAK,EAAI,CAAE,GAAGK,EAAQL,CAAK,EAAG,kBAAmB,IAAK,EAC9DM,IACIX,GAAuBU,CAAO,EAChC,eAAQ,KACN,kGACiCC,CAAI,0HAGvC,EACO,GAIX,eAAQ,KACN,gEAAgEA,CAAI,0DAEtE,EACO,EACT,CAWO,SAASC,GAAgBC,EAAQC,EAASC,EAAa,CAG5D,IAAMC,EAAa,IAAI,IAAIF,EAAQ,IAAKG,GAAM,OAAOA,EAAE,EAAE,CAAC,CAAC,EAI3D,MAAO,CAAC,GAHKJ,EAAO,OACjBI,GAAM,CAACD,EAAW,IAAI,OAAOC,EAAE,EAAE,CAAC,GAAKA,EAAE,OAASF,CACrD,EACiB,GAAGD,CAAO,CAC7B,CClHO,IAAII,GACT,mECgDK,IAAIC,GAAS,CAACC,EAAO,KAAO,CACjC,IAAIC,EAAK,GACLC,EAAQ,OAAO,gBAAgB,IAAI,WAAYF,GAAQ,CAAE,CAAC,EAC9D,KAAOA,KACLC,GAAME,GAAYD,EAAMF,CAAI,EAAI,EAAE,EAEpC,OAAOC,CACT,EC/BO,IAAMG,GAAW,IAAMC,GAAO,EAoBxBC,EAAoBC,GAC/B,OAAOA,GAAU,SAAWA,EAAM,KAAK,EAAI,GAYhCC,EAAS,CAACC,EAAGC,IAAM,OAAOD,CAAC,IAAM,OAAOC,CAAC,ECtCtD,IAAMC,EAAY,IAAI,IAGlBC,EAAkB,KAGlBC,GAAc,KAEZC,GAAaC,GAAS,CAC1B,GAAGA,EAAK,iBAAiB,2CAA2C,CACtE,EAGMC,GAAW,EAaXC,GAAiBF,GAAS,CAC9B,QAASG,EAAKH,EAAK,cAAeG,EAAIA,EAAKA,EAAG,cAAe,CAC3D,GAAM,CAAE,UAAAC,EAAW,UAAAC,CAAU,EAAI,iBAAiBF,CAAE,EACpD,GAAIC,IAAc,WAAaC,IAAc,UAC3C,OAAOF,EAAG,sBAAsB,CAEpC,CACA,OAAO,IACT,EA4BMG,GAAY,CAACC,EAAQP,IAAS,CAClCA,EAAK,UAAU,OAAOQ,EAAQ,aAAa,EAC3CR,EAAK,UAAU,OAAOQ,EAAQ,gBAAgB,EAE9C,IAAMC,EAAUP,GAAcF,CAAI,EAC5BU,EAAQ,KAAK,IAAID,GAAS,QAAU,IAAU,OAAO,WAAW,EAChEE,EAAU,KAAK,IAAIF,GAAS,KAAO,EAAG,CAAC,EACvCG,EAAW,KAAK,IAAIH,GAAS,MAAQ,EAAG,CAAC,EACzCI,EAAY,KAAK,IAAIJ,GAAS,OAAS,IAAU,OAAO,UAAU,EAElE,CAAE,OAAAK,EAAQ,MAAAC,EAAO,KAAAC,CAAK,EAAIhB,EAAK,sBAAsB,EACrDiB,EAASV,EAAO,sBAAsB,EACtCW,EAAeD,EAAO,OAAShB,GAAWa,EAC1CK,EAAUF,EAAO,IAAMhB,GAAWa,EAEpCI,EAAeR,GAASS,GAAWR,GACrCX,EAAK,UAAU,IAAIQ,EAAQ,aAAa,EAGtCQ,EAAOJ,GAAYK,EAAO,KAAOF,GAASF,GAC5Cb,EAAK,UAAU,IAAIQ,EAAQ,gBAAgB,CAE/C,EAKMY,GAAY,CAACjB,EAAI,KACR,OAAO,EAAE,cAAiB,WAAa,EAAE,aAAa,EAAI,CAAC,GAC5D,SAASA,CAAE,GAAKA,EAAG,SAA8B,EAAE,MAAO,EAGlEkB,GAAgB,IAAM,CACtBxB,IACJA,EAAmByB,GAAM,CACvB,QAAWC,IAAS,CAAC,GAAG3B,CAAS,EAC3BwB,GAAUG,EAAM,KAAMD,CAAC,GAAKF,GAAUG,EAAM,OAAQD,CAAC,GAIzDC,EAAM,MAAM,CAEhB,EACA,SAAS,iBAAiB,YAAa1B,EAAiB,EAAI,EAO5DC,GAAewB,GAAM,CACnB,IAAMC,EAAQ,CAAC,GAAG3B,CAAS,EAAE,IAAI,EACjC,GAAK2B,EAEL,IAAID,EAAE,MAAQ,SAAU,CACtBA,EAAE,eAAe,EACjBA,EAAE,gBAAgB,EAClBC,EAAM,MAAM,EACZA,EAAM,OAAO,MAAM,EACnB,MACF,CAEA,GAAI,CAAC,YAAa,UAAW,OAAQ,KAAK,EAAE,SAASD,EAAE,GAAG,EAAG,CAC3D,IAAME,EAAQzB,GAAUwB,EAAM,IAAI,EAClC,GAAIC,EAAM,SAAW,EAAG,OACxBF,EAAE,eAAe,EACjBA,EAAE,gBAAgB,EAGlB,IAAMG,EACJF,EAAM,KAAK,YAAY,EAEnBG,EAAQF,EAAM,QAAQC,EAAK,aAAa,EAC1CE,EACJ,GAAIL,EAAE,MAAQ,OAAQK,EAAO,UACpBL,EAAE,MAAQ,MAAOK,EAAOH,EAAM,OAAS,UACvCE,IAAU,GACjBC,EAAOL,EAAE,MAAQ,YAAc,EAAIE,EAAM,OAAS,MAC/C,CACH,IAAMI,EAAON,EAAE,MAAQ,YAAc,EAAI,GACzCK,GAAQD,EAAQE,EAAOJ,EAAM,QAAUA,EAAM,MAC/C,CACAA,EAAMG,CAAI,EAAE,MAAM,CACpB,EACF,EACA,SAAS,iBAAiB,UAAW7B,GAAa,EAAI,EACxD,EAEM+B,GAAe,IAAM,CACpBhC,IACL,SAAS,oBAAoB,YAAaA,EAAiB,EAAI,EAC/DA,EAAkB,KAClB,SAAS,oBAAoB,UAAWC,GAAa,EAAI,EACzDA,GAAc,KAChB,EAGagC,GAAiB,IAAM,CAClC,QAAWP,IAAS,CAAC,GAAG3B,CAAS,EAAG2B,EAAM,MAAM,CAClD,EAWaQ,EAAmB,CAACxB,EAAQP,IAAS,CAEhD,IAAMuB,EAAQ,CACZ,OAAAhB,EACA,KAAAP,EACA,MAAO,IAAM,CACXA,EAAK,MAAM,QAAU,OACrBO,EAAO,aAAa,gBAAiB,OAAO,EAC5CX,EAAU,OAAO2B,CAAK,EAClB3B,EAAU,OAAS,GAAGiC,GAAa,CACzC,CACF,EAEMG,EAAO,IAAM,CACjBF,GAAe,EACf9B,EAAK,MAAM,QAAU,QAGrBM,GAAUC,EAAQP,CAAI,EACtBO,EAAO,aAAa,gBAAiB,MAAM,EAC3CX,EAAU,IAAI2B,CAAK,EACnBF,GAAc,CAChB,EAEMY,EAAS,IAAMjC,EAAK,MAAM,UAAY,OAE5C,OAAAA,EAAK,MAAM,QAAU,OACrBO,EAAO,aAAa,gBAAiB,OAAO,EAE5CA,EAAO,iBAAiB,QAAUe,GAAM,CACtCA,EAAE,gBAAgB,EAIlB,IAAMY,EAAUD,EAAO,EACvBH,GAAe,EACVI,GAASF,EAAK,CACrB,CAAC,EAEM,CAAE,KAAAA,EAAM,MAAOT,EAAM,MAAO,OAAAU,CAAO,CAC5C,ECjNO,IAAME,GAAa,CAACC,EAAMC,IAC/BC,EAAiBF,GAAM,EAAE,GAAKA,GAAM,MAAQC,EAAQ,UAUzCE,GAAqBC,GAAW,CAC3C,IAAMC,EAAMD,GAAQ,UACpB,MAAI,CAACC,GAAO,OAAOA,GAAQ,SAAiB,CAAC,EACtCC,GAAgB,OAAQC,GAAUF,EAAIE,CAAK,GAAG,OAAS,CAAC,EAAE,IAC9DA,IAAW,CAAE,MAAAA,EAAO,QAAS,CAAC,GAAGF,EAAIE,CAAK,CAAC,CAAE,EAChD,CACF,EAYaC,GAAsBC,GAAQ,CACzC,GAAI,CAACA,GAAO,OAAOA,GAAQ,SAAU,OAAO,KAC5C,IAAMC,EAA+C,CAAC,EACtD,QAAWH,KAASD,GAAiB,CACnC,IAAMK,EAAUF,EAAIF,CAAK,EACzB,GAAI,CAAC,MAAM,QAAQI,CAAO,EAAG,SAC7B,IAAMC,EAAO,CAAC,EACd,QAAWC,KAAUF,EAAS,CAC5B,GAAI,OAAOE,GAAW,SAAU,SAChC,IAAMC,EAAQD,EAAO,KAAK,EACtBC,GAAS,CAACF,EAAK,SAASE,CAAK,GAAGF,EAAK,KAAKE,CAAK,CACrD,CACIF,EAAK,OAAS,IAAGF,EAAIH,CAAK,EAAIK,EACpC,CACA,OAAO,OAAO,KAAKF,CAAG,EAAE,OAAS,EAAIA,EAAM,IAC7C,EAYaK,GAAmB,CAACX,EAAQG,EAAOS,IAAa,CAC3D,GAAI,CAACV,GAAgB,SAASC,CAAK,GAAK,CAACS,EAAU,MAAO,GACrDZ,EAAO,YAAWA,EAAO,UAAY,CAAC,GAC3C,IAAMO,EAAUP,EAAO,UAAUG,CAAK,GAAK,CAAC,EACtCU,EAAQN,EAAQ,QAAQK,CAAQ,EACtC,OAAIC,GAAS,EAAGN,EAAQ,OAAOM,EAAO,CAAC,EAClCN,EAAQ,KAAKK,CAAQ,EAGtBL,EAAQ,OAAS,EAAGP,EAAO,UAAUG,CAAK,EAAII,EAC7C,OAAOP,EAAO,UAAUG,CAAK,EAC3B,EACT,EAWaW,GAAsBC,GAAc,CAC/C,IAAMC,EAAU,OAAO,QAAQD,GAAa,CAAC,CAAC,EAAE,OAC9C,CAAC,CAAC,CAAER,CAAO,IAAMA,GAAS,OAAS,CACrC,EACA,OAAOS,EAAQ,OAAS,EACpB,OAAO,YACLA,EAAQ,IAAI,CAAC,CAACb,EAAOI,CAAO,IAAM,CAACJ,EAAO,CAAC,GAAGI,CAAO,CAAC,CAAC,CACzD,EACA,IACN,EAOMU,GAAiB,gTAgCVC,GAAoB,CAAC,CAAE,SAAAN,EAAU,QAAAf,EAAS,SAAAsB,CAAS,IAAM,CAOpE,IAAMC,EAAO,IAAI,QAEXC,EAAWrB,GAAW,CAC1B,IAAMsB,EAAMF,EAAK,IAAIpB,CAAM,EAC3B,GAAKsB,EACL,QAAWC,KAASD,EACdC,EAAM,GAAG,YAAaA,EAAM,QAAQ,EACnCD,EAAI,OAAOC,CAAK,CAEzB,EAEMC,EAAO,CAACxB,EAAQG,IAAU,CAC9BgB,EAASnB,EAAQG,CAAK,EACtBkB,EAAQrB,CAAM,CAChB,EAUMyB,EAAU,CAACzB,EAAQ,CAAE,UAAA0B,EAAW,QAAAC,EAAU,EAAK,IAAM,CACzD,IAAMC,EAAU,SAAS,cAAc,KAAK,EAC5CA,EAAQ,UAAYC,EAAQ,iBAE5B,IAAMC,EAAM,SAAS,cAAc,QAAQ,EAC3CA,EAAI,KAAO,SACXA,EAAI,UAAYJ,EAChBI,EAAI,QAAQ,OAAS,QACjBH,IAASG,EAAI,QAAQ,UAAYjC,EAAQ,aAC7CiC,EAAI,aAAa,aAAcjC,EAAQ,WAAW,EAClDiC,EAAI,UAAYb,GAEhB,IAAMc,EAAU,SAAS,cAAc,KAAK,EAC5CA,EAAQ,UAAYF,EAAQ,iBAC5BE,EAAQ,aAAa,OAAQ,MAAM,EACnCA,EAAQ,aAAa,aAAclC,EAAQ,mBAAmB,EAM9D,IAAMmC,EAASC,EAAiBH,EAAKC,CAAO,EAE5C,QAAW5B,KAASD,GAAiB,CACnC,IAAMgC,EAAO,SAAS,cAAc,QAAQ,EAC5CA,EAAK,KAAO,SACZA,EAAK,UAAYL,EAAQ,sBACzBK,EAAK,aAAa,OAAQ,UAAU,EACpCA,EAAK,QAAQ,cAAgB/B,EAC7B+B,EAAK,YAAc/B,EACnB+B,EAAK,aAAa,aAAc/B,CAAK,EACrC+B,EAAK,iBAAiB,QAAUC,GAAM,CACpCA,EAAE,gBAAgB,EAIlBH,EAAO,MAAM,EACbR,EAAKxB,EAAQG,CAAK,CACpB,CAAC,EACD4B,EAAQ,YAAYG,CAAI,CAC1B,CAEA,OAAAN,EAAQ,YAAYE,CAAG,EACvBF,EAAQ,YAAYG,CAAO,EACpBH,CACT,EA0FA,MAAO,CAAE,IAzEI5B,GAAW,CACtB,IAAMoC,EAAK,SAAS,cAAc,KAAK,EACvCA,EAAG,UAAYP,EAAQ,aACvBO,EAAG,aAAa,OAAQ,OAAO,EAC/BA,EAAG,aAAa,aAAcvC,EAAQ,cAAc,EAEpD,IAAMwC,EAAU,IAAM,CACpBD,EAAG,gBAAgB,EACnB,IAAMpB,EAAUjB,GAAkBC,CAAM,EAClCsC,EAAK1B,EAAS,EAIpB,GADAwB,EAAG,OAASpB,EAAQ,SAAW,EAC3B,CAAAoB,EAAG,OAEP,QAAW,CAAE,MAAAjC,EAAO,QAAAI,CAAQ,IAAKS,EAAS,CACxC,IAAMuB,EAAOhC,EAAQ,SAAS+B,CAAE,EAC1BE,EAAO,SAAS,cAAc,QAAQ,EAC5CA,EAAK,KAAO,SACZA,EAAK,UAAYX,EAAQ,cACrBU,GAAMC,EAAK,UAAU,IAAIX,EAAQ,kBAAkB,EACvDW,EAAK,QAAQ,cAAgBrC,EAE7B,IAAMsC,EAASF,EACX1C,EAAQ,kBACRA,EAAQ,iBAGZ2C,EAAK,aAAa,eAAgB,OAAOD,CAAI,CAAC,EAM9CC,EAAK,aACH,aACA,GAAGC,CAAM,KAAKtC,CAAK,KAAKI,EAAQ,MAAM,GACxC,EACAiC,EAAK,iBAAiB,QAAUL,GAAM,CAGpCA,EAAE,gBAAgB,EAClBX,EAAKxB,EAAQG,CAAK,CACpB,CAAC,EAED,IAAMuC,EAAU,SAAS,cAAc,MAAM,EAC7CA,EAAQ,UAAYb,EAAQ,oBAC5Ba,EAAQ,YAAcvC,EAGtBuC,EAAQ,aAAa,cAAe,MAAM,EAE1C,IAAMC,EAAQ,SAAS,cAAc,MAAM,EAC3CA,EAAM,UAAYd,EAAQ,oBAC1Bc,EAAM,YAAc,OAAOpC,EAAQ,MAAM,EAEzCiC,EAAK,YAAYE,CAAO,EACxBF,EAAK,YAAYG,CAAK,EACtBP,EAAG,YAAYI,CAAI,CACrB,CAGAJ,EAAG,YAAYX,EAAQzB,EAAQ,CAAE,UAAW6B,EAAQ,YAAa,CAAC,CAAC,EACrE,EAEIP,EAAMF,EAAK,IAAIpB,CAAM,EACzB,OAAKsB,GAAKF,EAAK,IAAIpB,EAASsB,EAAM,IAAI,GAAM,EAC5CA,EAAI,IAAI,CAAE,GAAAc,EAAI,QAAAC,CAAQ,CAAC,EAEvBA,EAAQ,EACDD,CACT,EAEc,QAAAX,EAAS,QAAAJ,CAAQ,CACjC,EC5QO,IAAMuB,GAAc,CAACC,EAAQC,IAClCC,EAAiBF,GAAQ,QAAQ,IAChC,OAAOA,GAAQ,QAAW,SAAWA,EAAO,OAAO,KAAK,EAAI,KAC7DC,EAAQ,UAwBGE,GAAc,CAACC,EAAQC,EAAMJ,IACxCF,GAAYK,EAAQH,CAAO,IAAMK,GAAWD,EAAMJ,CAAO,EA0B9CM,GAAoB,CAAC,CAAE,IAAAC,EAAK,OAAAC,EAAQ,OAAAL,EAAQ,KAAAC,EAAM,QAAAJ,CAAQ,IAAM,CAC3E,GAAI,OAAOO,GAAQ,WAAY,OAAOL,GAAYC,EAAQC,EAAMJ,CAAO,EACvE,GAAI,CACF,OAAOO,EAAIC,EAAQL,CAAM,IAAM,EACjC,OAASM,EAAK,CACZ,eAAQ,KAAK,iCAAkCD,EAAQC,CAAG,EACnD,EACT,CACF,EAWaC,GAAmBC,IAAa,CAC3C,GAAIA,EAAQ,GACZ,OAAQA,EAAQ,OAChB,SAAUA,EAAQ,UAAY,IAChC,GAUaC,GAAgB,CAACC,EAAOC,KAAe,CAClD,GAAID,EAAM,GACV,OAAQA,EAAM,OACd,SAAUA,EAAM,UAAY,KAC5B,UAAAC,CACF,GCrIO,IAAMC,GAAqB,kBAiBrBC,EAAmB,CAC9BC,EACAC,EAAQH,GACRI,EAAO,SAAS,OACb,CACH,IAAMC,EAAU,IAAI,IAAID,CAAI,EACtBE,EAAOJ,EAAQ,MAAQG,EAAQ,SAC/BE,EAAMD,IAASD,EAAQ,SAAWA,EAAU,IAAI,IAAIC,EAAMD,CAAO,EACvE,OAAAE,EAAI,aAAa,IAAIJ,EAAO,OAAOD,EAAQ,EAAE,CAAC,EACvCK,EAAI,IACb,EAQaC,GAAuB,CAClCL,EAAQH,GACRI,EAAO,SAAS,OACb,CACH,GAAI,CACF,OAAO,IAAI,IAAIA,CAAI,EAAE,aAAa,IAAID,CAAK,CAC7C,MAAQ,CAGN,OAAO,IACT,CACF,ECpCO,IAAMM,GAAe,CAAC,UAAW,SAAU,SAAU,YAAY,EAG3DC,GAAe,CAAC,OAAQ,WAAY,MAAM,EAMjDC,GAAY,GAEZC,GAASC,GACb,OAAOA,GAAU,SAAWA,EAAM,KAAK,EAAE,MAAM,EAAGF,EAAS,EAAI,GAI3DG,GAAcD,GACdA,IAAU,KAAa,CAAE,QAAS,GAAM,MAAO,IAAK,EACpD,OAAOA,GAAU,SAAiB,CAAE,QAAS,GAAM,MAAOD,GAAMC,CAAK,CAAE,EACpE,CAAE,QAAS,GAAO,MAAO,MAAU,EAgBrC,SAASE,GAAQC,EAAMC,EAAS,CACrC,IAAMC,EAAON,GAAMI,GAAM,IAAI,GAAKC,EAAQ,UACpCE,EAAKC,EAAiBJ,GAAM,EAAE,EACpC,OAAOG,EAAK,CAAE,GAAAA,EAAI,KAAAD,CAAK,EAAI,CAAE,KAAAA,CAAK,CACpC,CAgBO,SAASG,EAAYC,EAASC,EAAMC,EAAOC,EAAQ,CACxD,GAAI,CAAChB,GAAa,SAASc,CAAI,EAAG,OAAO,KAEzC,IAAMG,EAAQ,CAAE,KAAAH,EAAM,GAAI,IAAI,KAAK,EAAE,YAAY,EAAG,MAAAC,CAAM,EACtDC,GAAQ,OAASf,GAAa,SAASe,EAAO,KAAK,IACrDC,EAAM,MAAQD,EAAO,OAEvB,IAAME,EAAOb,GAAWW,GAAQ,IAAI,EAChCE,EAAK,UAASD,EAAM,KAAOC,EAAK,OACpC,IAAMC,EAAKd,GAAWW,GAAQ,EAAE,EAChC,OAAIG,EAAG,UAASF,EAAM,GAAKE,EAAG,OAEzB,MAAM,QAAQN,EAAQ,OAAO,IAAGA,EAAQ,QAAU,CAAC,GACxDA,EAAQ,QAAQ,KAAKI,CAAK,EACnBA,CACT,CAYO,SAASG,GAAiBC,EAAK,CACpC,GAAI,CAAC,MAAM,QAAQA,CAAG,EAAG,OAAO,KAEhC,IAAMC,EAAM,CAAC,EACb,QAAWC,KAAQF,EAAK,CAEtB,GADI,CAACE,GAAQ,OAAOA,GAAS,UACzB,CAACvB,GAAa,SAASuB,EAAK,IAAI,EAAG,SAEvC,IAAMC,EAAKrB,GAAMoB,EAAK,EAAE,EACxB,GAAI,CAAC,OAAO,SAAS,KAAK,MAAMC,CAAE,CAAC,EAAG,SAEtC,IAAMf,EAAON,GAAMoB,EAAK,OAAO,IAAI,EAC7Bb,EAAKC,EAAiBY,EAAK,OAAO,EAAE,EACpCN,EAAQ,CAAE,KAAMM,EAAK,KAAM,GAAAC,EAAI,MAAOd,EAAK,CAAE,GAAAA,EAAI,KAAAD,CAAK,EAAI,CAAE,KAAAA,CAAK,CAAE,EAErER,GAAa,SAASsB,EAAK,KAAK,IAAGN,EAAM,MAAQM,EAAK,OAC1D,IAAML,EAAOb,GAAWkB,EAAK,IAAI,EAC7BL,EAAK,UAASD,EAAM,KAAOC,EAAK,OACpC,IAAMC,EAAKd,GAAWkB,EAAK,EAAE,EACzBJ,EAAG,UAASF,EAAM,GAAKE,EAAG,OAE9BG,EAAI,KAAKL,CAAK,CAChB,CAEA,OAAIK,EAAI,SAAW,EAAU,MAI7BA,EAAI,KAAK,CAACG,EAAGC,IAAM,KAAK,MAAMD,EAAE,EAAE,EAAI,KAAK,MAAMC,EAAE,EAAE,CAAC,EAC/CJ,EACT,CASO,SAASK,GAAiBC,EAAS,CACxC,MAAI,CAAC,MAAM,QAAQA,CAAO,GAAKA,EAAQ,SAAW,EAAU,KACrDA,EAAQ,IAAKX,IAAW,CAAE,GAAGA,EAAO,MAAO,CAAE,GAAGA,EAAM,KAAM,CAAE,EAAE,CACzE,CAeO,SAASY,EAAchB,EAAS,CACrC,GAAI,CAAC,MAAM,QAAQA,GAAS,OAAO,EAAG,MAAO,CAAC,EAE9C,IAAMS,EAAM,CAAC,EACTQ,EAAW,KACf,QAAWb,KAASJ,EAAQ,QACtBI,EAAM,OAAS,WACfA,EAAM,KAAO,WACfa,EAAWb,EAAM,GACRa,IACTR,EAAI,KAAK,CAAE,WAAYQ,EAAU,WAAYb,EAAM,GAAI,GAAI,CAAE,CAAC,EAC9Da,EAAW,OAGXA,GAAUR,EAAI,KAAK,CAAE,WAAYQ,EAAU,WAAY,KAAM,GAAI,CAAE,CAAC,EAKxE,IAAMC,EAAY,KAAK,MAAMlB,EAAQ,SAAS,EAC9C,QAAWU,KAAQD,EAAK,CACtB,IAAMU,EAAW,KAAK,MAAMT,EAAK,UAAU,EAC3CA,EAAK,GACH,OAAO,SAASQ,CAAS,GAAK,OAAO,SAASC,CAAQ,EAClD,KAAK,IAAI,EAAGA,EAAWD,CAAS,EAChC,CACR,CACA,OAAOT,CACT,CAUO,SAASW,GAAoBpB,EAAS,CAC3C,GAAIA,GAAS,SAAW,WAAY,OAAO,KAE3C,IAAMqB,EAAcL,EAAchB,CAAO,EACnCsB,EAAOD,EAAYA,EAAY,OAAS,CAAC,EAC/C,GAAIC,GAAQ,CAACA,EAAK,WAAY,OAAOA,EAAK,GAI1C,IAAMH,EAAW,KAAK,MAAMnB,EAAQ,UAAU,EACxCuB,EAAU,KAAK,MAAMvB,EAAQ,SAAS,EAC5C,MAAI,CAAC,OAAO,SAASmB,CAAQ,GAAK,CAAC,OAAO,SAASI,CAAO,EAAU,KAC7D,KAAK,IAAI,EAAGJ,EAAWI,CAAO,CACvC,CCvLA,IAAMC,GAAc,IAAI,IAGXC,GAA0B,IAAM,CAC3C,QAAWC,IAAW,CAAC,GAAGF,EAAW,EAAGE,EAAQ,EAAK,CACvD,EAWaC,GAAgB,CAC3BC,EACA,CAAE,MAAAC,EAAO,QAAAC,EAAS,aAAAC,EAAc,YAAAC,CAAY,IAE5C,IAAI,QAASC,GAAY,CACvB,IAAMC,EAAW,SAAS,cAAc,KAAK,EAC7CA,EAAS,UAAYC,EAAQ,QAC7BD,EAAS,MAAM,OAAS,OAAOE,EAAQ,OAAO,EAE9C,IAAMC,EAAQ,SAAS,cAAc,KAAK,EAC1CA,EAAM,UAAYF,EAAQ,cAC1BE,EAAM,aAAa,OAAQ,aAAa,EACxCA,EAAM,aAAa,aAAc,MAAM,EAEvC,IAAMC,EAAU,SAAS,cAAc,IAAI,EAC3CA,EAAQ,UAAYH,EAAQ,cAC5BG,EAAQ,YAAcT,EAGtBS,EAAQ,GAAK,oBAAoB,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,EAAG,CAAC,CAAC,GACvED,EAAM,aAAa,kBAAmBC,EAAQ,EAAE,EAEhD,IAAMC,EAAY,SAAS,cAAc,GAAG,EAC5CA,EAAU,UAAYJ,EAAQ,gBAC9BI,EAAU,YAAcT,EAGxBS,EAAU,GAAK,sBAAsB,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,EAAG,CAAC,CAAC,GAC3EF,EAAM,aAAa,mBAAoBE,EAAU,EAAE,EAEnD,IAAMC,EAAU,SAAS,cAAc,KAAK,EAC5CA,EAAQ,UAAYL,EAAQ,gBAE5B,IAAMM,EAAY,SAAS,cAAc,QAAQ,EACjDA,EAAU,KAAO,SACjBA,EAAU,UAAYN,EAAQ,eAC9BM,EAAU,YAAcT,EAExB,IAAMU,EAAY,SAAS,cAAc,QAAQ,EACjDA,EAAU,KAAO,SACjBA,EAAU,UAAYP,EAAQ,eAC9BO,EAAU,YAAcX,EAExBS,EAAQ,YAAYC,CAAS,EAC7BD,EAAQ,YAAYE,CAAS,EAC7BL,EAAM,YAAYC,CAAO,EACzBD,EAAM,YAAYE,CAAS,EAC3BF,EAAM,YAAYG,CAAO,EACzBN,EAAS,YAAYG,CAAK,EAK1B,IAAMM,EACgBf,EAAM,eAAiB,SAAS,cAGlDgB,EAAU,GACRC,EAAUC,GAAW,CACrBF,IACJA,EAAU,GACVpB,GAAY,OAAOqB,CAAM,EACzB,SAAS,oBAAoB,UAAWE,EAAW,EAAI,EACvDb,EAAS,OAAO,EAChBS,GAAmB,QAAQ,EAC3BV,EAAQa,CAAM,EAChB,EAKMC,EAA0CC,GAAM,CACpD,GAAIA,EAAE,MAAQ,SAAU,CACtBA,EAAE,gBAAgB,EAClBA,EAAE,eAAe,EACjBH,EAAO,EAAK,EACZ,MACF,CACA,GAAIG,EAAE,MAAQ,MAAO,OAGrB,IAAMC,EAAa,CAACR,EAAWC,CAAS,EAClCQ,EACgBtB,EAAM,eAAiB,SAAS,cAEhDuB,EAAQF,EAAW,QAAQC,CAAM,EACvCF,EAAE,eAAe,GACJA,EAAE,SACXC,GAAYE,GAAS,EAAIF,EAAW,OAASE,GAAS,CAAC,EACvDF,GAAYE,EAAQ,GAAKF,EAAW,MAAM,GACzC,MAAM,CACb,EAEAR,EAAU,iBAAiB,QAAS,IAAMI,EAAO,EAAK,CAAC,EACvDH,EAAU,iBAAiB,QAAS,IAAMG,EAAO,EAAI,CAAC,EAItD,IAAIO,EAAkB,GACtBlB,EAAS,iBAAiB,YAAcc,GAAM,CAC5CI,EAAkBJ,EAAE,SAAWd,EAG/Bc,EAAE,gBAAgB,CACpB,CAAC,EACDd,EAAS,iBAAiB,QAAUc,GAAM,CACpCA,EAAE,SAAWd,GAAYkB,GAAiBP,EAAO,EAAK,EAC1DO,EAAkB,EACpB,CAAC,EAED5B,GAAY,IAAIqB,CAAM,EACtB,SAAS,iBAAiB,UAAWE,EAAW,EAAI,GAIbnB,EAAM,MAAQA,GAC1C,YAAYM,CAAQ,EAG/BO,EAAU,MAAM,CAClB,CAAC,EC/IH,IAAMY,GAAgB,sRAChBC,GAAiB,8LACjBC,GAAgB,+KAETC,GAAmBC,GAAS,CACvC,GAAI,UAAU,WAAW,UACvB,OAAO,UAAU,UAAU,UAAUA,CAAI,EAAE,MAAM,IAAM,CAAC,CAAC,EAE3D,IAAMC,EAAW,SAAS,cAAc,UAAU,EAClDA,EAAS,MAAQD,EACjBC,EAAS,MAAM,SAAW,QAC1BA,EAAS,MAAM,QAAU,IACzB,SAAS,KAAK,YAAYA,CAAQ,EAClCA,EAAS,OAAO,EAChB,GAAI,CACF,SAAS,YAAY,MAAM,CAC7B,MAAQ,CAAC,CACT,OAAAA,EAAS,OAAO,EACT,QAAQ,QAAQ,CACzB,EAEaC,EAAgB,CAACC,EAAQC,KACnC,CACC,KAAMA,EAAQ,WACd,YAAaA,EAAQ,iBACrB,UAAWA,EAAQ,eACnB,SAAUA,EAAQ,cACpB,GAAGD,CAAM,GAAKC,EAAQ,WAEXC,EAAc,CAACC,EAAMF,KAC/B,CACC,IAAKA,EAAQ,QACb,WAAYA,EAAQ,eACpB,SAAUA,EAAQ,aAClB,YAAaA,EAAQ,eACvB,GAAGE,CAAI,GAAKF,EAAQ,MAETG,EAAkB,CAACC,EAAUJ,KACvC,CACC,KAAMA,EAAQ,aACd,OAAQA,EAAQ,eAChB,IAAKA,EAAQ,WACf,GAAGI,CAAQ,GAAKJ,EAAQ,MAkBbK,GAAe,CAAC,CAC3B,OAAAC,EACA,QAAAC,EACA,MAAAC,EACA,QAAAC,EACA,QAAAC,EACA,aAAAC,EACA,SAAAC,EACA,UAAAC,EAAY,EACd,IAAM,CACJ,IAAMC,EAAU,SAAS,cAAc,KAAK,EAC5CA,EAAQ,MAAM,SAAW,WAEzB,IAAMC,EAAM,SAAS,cAAc,QAAQ,EAC3CA,EAAI,KAAO,SACXA,EAAI,UAAYC,EAAQ,iBACpBH,GAAWE,EAAI,UAAU,IAAIC,EAAQ,wBAAwB,EACjED,EAAI,QAAQ,OAAST,EACrBS,EAAI,aAAa,gBAAiB,MAAM,EAExC,IAAME,EAAM,SAAS,cAAc,MAAM,EACzCA,EAAI,UAAYD,EAAQ,iBACxBD,EAAI,YAAYE,CAAG,EAMnB,IAAIC,EAAU,KACVL,IACFK,EAAU,SAAS,cAAc,MAAM,EACvCA,EAAQ,UAAYF,EAAQ,mBAC5BD,EAAI,YAAYG,CAAO,GAGzB,IAAMC,EAAO,SAAS,cAAc,KAAK,EACzCA,EAAK,UAAYH,EAAQ,WACzBG,EAAK,aAAa,OAAQ,MAAM,EAEhC,IAAMC,EAASC,EAAiBN,EAAKI,CAAI,EAErCG,EAAUd,EAERe,EAAS,IAAM,CACnB,IAAMC,EAAQ,GAAGb,CAAY,KAAKD,EAAQY,CAAO,CAAC,GAClDL,EAAI,MAAM,gBAAkBR,EAAQa,CAAO,EAC3CP,EAAI,QAAQ,UAAYS,EACxBT,EAAI,aAAa,aAAcS,CAAK,EAChCN,IAASA,EAAQ,YAAcR,EAAQY,CAAO,GAClDH,EACG,iBAAiB,sBAAsB,EACvC,QAAoCM,GAAS,CAC5C,IAAMC,EAAMD,EAAK,QAAQ,aACnBE,EAASD,IAAQ,GAAK,KAAOA,EACnCD,EAAK,aAAa,eAAgB,OAAOE,IAAWL,CAAO,CAAC,CAC9D,CAAC,CACL,EAEA,QAAWK,KAAUpB,EAAS,CAC5B,IAAMkB,EAAO,SAAS,cAAc,QAAQ,EAC5CA,EAAK,KAAO,SACZA,EAAK,UAAYT,EAAQ,gBAEzBS,EAAK,QAAQ,aAAeE,IAAW,KAAO,GAAKA,EACnDF,EAAK,aAAa,OAAQ,eAAe,EAEzC,IAAMG,EAAU,SAAS,cAAc,MAAM,EAC7CA,EAAQ,UAAYZ,EAAQ,iBAC5BY,EAAQ,MAAM,gBAAkBnB,EAAQkB,CAAM,EAC9CF,EAAK,YAAYG,CAAO,EACxBH,EAAK,YAAY,SAAS,eAAef,EAAQiB,CAAM,CAAC,CAAC,EAEzDF,EAAK,iBAAiB,QAAUI,GAAM,CACpCA,EAAE,gBAAgB,EAClBT,EAAO,MAAM,EAITO,IAAWL,IACfA,EAAUK,EACVf,EAASe,CAAM,EACfJ,EAAO,EACT,CAAC,EACDJ,EAAK,YAAYM,CAAI,CACvB,CAEA,OAAAX,EAAQ,YAAYC,CAAG,EACvBD,EAAQ,YAAYK,CAAI,EACxBI,EAAO,EACAT,CACT,EAiCagB,GAAiB,CAAC,CAAE,MAAAN,EAAO,QAAAO,EAAS,MAAAC,CAAM,IAAM,CAC3D,IAAMlB,EAAU,SAAS,cAAc,KAAK,EAC5CA,EAAQ,MAAM,SAAW,WAEzB,IAAMC,EAAM,SAAS,cAAc,QAAQ,EAC3CA,EAAI,KAAO,SACXA,EAAI,UAAYC,EAAQ,iBACxBD,EAAI,QAAQ,OAAS,OACjBgB,IAAShB,EAAI,QAAQ,UAAYgB,GACrChB,EAAI,aAAa,aAAcS,CAAK,EACpCT,EAAI,UAAYrB,GAEhB,IAAMyB,EAAO,SAAS,cAAc,KAAK,EACzCA,EAAK,UAAYH,EAAQ,WACzBG,EAAK,aAAa,OAAQ,MAAM,EAEhC,IAAMC,EAASC,EAAiBN,EAAKI,CAAI,EAEzC,QAAWc,KAASD,EAAO,CACzB,IAAMP,EAAO,SAAS,cAAc,QAAQ,EAC5CA,EAAK,KAAO,SACZA,EAAK,UAAYT,EAAQ,gBACzBS,EAAK,aAAa,OAAQ,UAAU,EACpCA,EAAK,YAAcQ,EAAM,MACzBR,EAAK,iBAAiB,QAAS,MAAOI,GAAM,CAM1C,GALAA,EAAE,gBAAgB,EAKdI,EAAM,cAAe,CACvBA,EAAM,SAAS,EACfR,EAAK,YAAcQ,EAAM,cACzB,WAAW,IAAM,CACfR,EAAK,YAAcQ,EAAM,MACzBb,EAAO,MAAM,CACf,EAAG,IAAI,EACP,MACF,CAEA,GADAA,EAAO,MAAM,EACTa,EAAM,QAAS,CAGjB,IAAMC,EAA2BT,EAAK,YAAY,EAClD,GAAI,CAAE,MAAMU,GAAcD,EAAMD,EAAM,QAAQ,CAAC,EAAI,MACrD,CACAA,EAAM,SAAS,CACjB,CAAC,EACDd,EAAK,YAAYM,CAAI,CACvB,CAEA,OAAAX,EAAQ,YAAYC,CAAG,EACvBD,EAAQ,YAAYK,CAAI,EACjBL,CACT,EAiBasB,GAAuB,CAClCC,EACA,CACE,QAAArC,EACA,UAAAsC,EACA,IAAAC,EACA,OAAAC,EACA,WAAAC,EACA,OAAAC,EACA,YAAAC,EACA,UAAAC,EACA,cAAAC,EACA,SAAAC,CACF,IACG,CACH,IAAMC,EAAU,SAAS,cAAc,KAAK,EAC5CA,EAAQ,UAAY/B,EAAQ,mBAE5B,IAAMgC,EAAiB,SAAS,cAAc,KAAK,EACnDA,EAAe,UAAYhC,EAAQ,cACnC,IAAMiC,EAAQ,SAAS,cAAc,KAAK,EAC1CA,EAAM,UAAY,GAAGjC,EAAQ,aAAa,IAAIA,EAAQ,iBAAiB,GACvE+B,EAAQ,YAAYC,CAAc,EAClCD,EAAQ,YAAYE,CAAK,EAKrBX,GACFW,EAAM,YACJX,EAAU,QAAQD,EAAS,CAAE,UAAWrB,EAAQ,gBAAiB,CAAC,CACpE,EAIF,IAAMkC,EAAU,SAAS,cAAc,QAAQ,EAC/CA,EAAQ,KAAO,SACfA,EAAQ,UAAYlC,EAAQ,iBAC5BkC,EAAQ,QAAQ,OAAS,OACzBA,EAAQ,QAAQ,UAAYlD,EAAQ,iBACpCkD,EAAQ,aAAa,aAAclD,EAAQ,gBAAgB,EAC3DkD,EAAQ,UAAY1D,GACpB0D,EAAQ,iBAAiB,QAAUrB,GAAM,CACvCA,EAAE,gBAAgB,EAClBW,EAAOH,CAAO,EACda,EAAQ,UAAYzD,GACpByD,EAAQ,QAAQ,UAAYlD,EAAQ,OACpC,WAAW,IAAM,CACfkD,EAAQ,UAAY1D,GACpB0D,EAAQ,QAAQ,UAAYlD,EAAQ,gBACtC,EAAG,IAAI,CACT,CAAC,EACDiD,EAAM,YAAYC,CAAO,EAGzBF,EAAe,YACb3C,GAAa,CACX,OAAQ,SACR,QAAS8C,EACT,MAAOd,EAAQ,QAAU,OAGzB,QAAUtC,GAAWqD,EAAcrD,CAAM,GAAKqD,EAAc,KAC5D,QAAUrD,GAAWD,EAAcC,EAAQC,CAAO,EAClD,aAAcA,EAAQ,YACtB,SAAWD,GAAW4C,EAAYN,EAAStC,CAAM,EAIjD,UAAW,EACb,CAAC,CACH,EAGAiD,EAAe,YACb3C,GAAa,CACX,OAAQ,OAER,QAAS,CAAC,KAAM,GAAGgD,CAAa,EAChC,MAAOhB,EAAQ,MAAQ,KACvB,QAAUnC,GAASoD,EAAYpD,CAAI,GAAK,cACxC,QAAUA,GAASD,EAAYC,EAAMF,CAAO,EAC5C,aAAcA,EAAQ,UACtB,SAAWE,GAAS0C,IAAYP,EAASnC,CAAI,EAC7C,UAAW,EACb,CAAC,CACH,EAGA8C,EAAe,YACb3C,GAAa,CACX,OAAQ,WACR,QAAS,CAAC,KAAM,GAAGkD,CAAU,EAC7B,MAAOlB,EAAQ,UAAY,KAC3B,QAAUjC,GAAaoD,EAAgBpD,CAAQ,GAAK,cACpD,QAAUA,GAAaD,EAAgBC,EAAUJ,CAAO,EACxD,aAAcA,EAAQ,cACtB,SAAWI,GAAayC,IAAgBR,EAASjC,CAAQ,EACzD,UAAW,EACb,CAAC,CACH,EAOA,IAAMqD,EAASC,GAAgBrB,CAAO,EAChCsB,EAA4BrD,GAChCiC,EAAMA,EAAIjC,EAAQmD,CAAM,EAAI,GAKxBzB,EAAQ,CACZ,CACE,MAAOhC,EAAQ,SACf,cAAeA,EAAQ,WACvB,SAAU,IAAMyC,IAAaJ,CAAO,CACtC,CACF,EACA,OAAIsB,EAAM,cAAc,GACtB3B,EAAM,KAAK,CACT,MAAOhC,EAAQ,YACf,SAAU,IAAM0C,IAASL,CAAO,CAClC,CAAC,EAECsB,EAAM,gBAAgB,GACxB3B,EAAM,KAAK,CACT,MAAOhC,EAAQ,cACf,SAAU,IAAM8C,EAAST,CAAO,EAChC,QAAS,KAAO,CACd,MAAOrC,EAAQ,0BAIf,QAASqC,EAAQ,SAAS,OACtBrC,EAAQ,2BACRA,EAAQ,4BACZ,aAAcA,EAAQ,cACtB,YAAaA,EAAQ,aACvB,EACF,CAAC,EAGHiD,EAAM,YACJnB,GAAe,CACb,MAAO9B,EAAQ,eACf,QAASA,EAAQ,YACjB,MAAAgC,CACF,CAAC,CACH,EAEOe,CACT,ECpZO,IAAMa,GAAqB,CAAC,CACjC,MAAAC,EACA,QAAAC,EACA,QAAAC,EACA,OAAAC,EACA,SAAAC,CACF,IAAM,CACJ,IAAMC,EAAU,SAAS,cAAc,KAAK,EAC5CA,EAAQ,UAAYC,EAAQ,OAE5B,IAAMC,EAAQ,SAAS,cAAc,UAAU,EAC/CA,EAAM,UAAYD,EAAQ,aAC1BC,EAAM,MAAQP,EACdO,EAAM,KAAO,EACbA,EAAM,aAAa,aAAcN,EAAQ,eAAe,EAExD,IAAMO,EAAU,SAAS,cAAc,KAAK,EAC5CA,EAAQ,UAAYF,EAAQ,eAE5B,IAAMG,EAAS,SAAS,cAAc,QAAQ,EAC9CA,EAAO,KAAO,SACdA,EAAO,UAAYH,EAAQ,cAC3BG,EAAO,YAAcR,EAAQ,WAE7B,IAAMS,EAAO,SAAS,cAAc,QAAQ,EAC5CA,EAAK,KAAO,SACZA,EAAK,UAAYJ,EAAQ,YACzBI,EAAK,YAAcT,EAAQ,SAK3B,IAAMU,EAAW,IAAM,CACrBD,EAAK,SAAWH,EAAM,MAAM,KAAK,EAAE,SAAW,CAChD,EACA,OAAAI,EAAS,EAETJ,EAAM,iBAAiB,QAAS,IAAM,CACpCI,EAAS,EACTT,EAAQK,EAAM,KAAK,CACrB,CAAC,EAEDA,EAAM,iBAAiB,UAAYK,GAAM,CACvC,GAAIA,EAAE,MAAQ,SAAU,CAKtBA,EAAE,gBAAgB,EAClBA,EAAE,eAAe,EACjBR,EAAS,EACT,MACF,CACIQ,EAAE,MAAQ,UAAYA,EAAE,SAAWA,EAAE,UAAY,CAACF,EAAK,WACzDE,EAAE,eAAe,EACjBT,EAAOI,EAAM,KAAK,EAEtB,CAAC,EAEDE,EAAO,iBAAiB,QAAUG,GAAM,CACtCA,EAAE,gBAAgB,EAClBR,EAAS,CACX,CAAC,EACDM,EAAK,iBAAiB,QAAUE,GAAM,CACpCA,EAAE,gBAAgB,EACbF,EAAK,UAAUP,EAAOI,EAAM,KAAK,CACxC,CAAC,EAEDC,EAAQ,YAAYC,CAAM,EAC1BD,EAAQ,YAAYE,CAAI,EACxBL,EAAQ,YAAYE,CAAK,EACzBF,EAAQ,YAAYG,CAAO,EAI3B,eAAe,IAAM,CACnBD,EAAM,MAAM,EACZA,EAAM,kBAAkBA,EAAM,MAAM,OAAQA,EAAM,MAAM,MAAM,CAChE,CAAC,EAEMF,CACT,EAYaQ,GAAiB,CAACC,EAAMb,IACnCc,GAAcD,EAAM,CAClB,MAAOb,EAAQ,oBACf,QAASA,EAAQ,sBACjB,aAAcA,EAAQ,eACtB,YAAaA,EAAQ,kBACvB,CAAC,EClGH,IAAMe,GAAqB,CAACC,EAAMC,IAAY,CAC5C,IAAMC,EAAO,KAAK,IAAI,EAAI,IAAI,KAAKF,CAAI,EAAE,QAAQ,EAC3CG,EAAU,KAAK,MAAMD,EAAO,GAAK,EACjCE,EAAQ,KAAK,MAAMF,EAAO,IAAO,EACjCG,EAAO,KAAK,MAAMH,EAAO,KAAQ,EAEvC,OAAIC,EAAU,EAAUF,EAAQ,QAC5BE,EAAU,GAAWG,EAAeL,EAAQ,mBAAoBE,CAAO,EACvEC,EAAQ,GAAWE,EAAeL,EAAQ,iBAAkBG,CAAK,EAC9DE,EAAeL,EAAQ,gBAAiBI,CAAI,CACrD,EAEME,GAAiB,CAACP,EAAMQ,IACrB,IAAI,KAAK,eAAeA,EAAQ,CACrC,MAAO,QACP,IAAK,UACL,KAAM,UACN,OAAQ,SACV,CAAC,EAAE,OAAO,IAAI,KAAKR,CAAI,CAAC,EAqBpBS,GAAuBC,GAAS,CACpC,IAAMC,EAAK,SAAS,cAAc,MAAM,EACxCA,EAAG,UAAYC,EAAQ,cAEvB,IAAMC,EAAS,SAAS,cAAc,MAAM,EAC5C,OAAAA,EAAO,UAAYD,EAAQ,mBAC3BC,EAAO,YAAcH,EACrBC,EAAG,YAAYE,CAAM,EAErBF,EAAG,iBAAiB,aAAc,IAAM,CAGlCE,EAAO,YAAcA,EAAO,YAAaF,EAAG,QAAQ,UAAYD,EAC/D,OAAOC,EAAG,QAAQ,SACzB,CAAC,EAEMA,CACT,EAEaG,GAAoB,CAC/BC,EACAC,EACAf,EACAO,EACAS,EAAW,OACR,CACH,IAAMC,EAAO,SAAS,cAAc,KAAK,EACzCA,EAAK,UAAYN,EAAQ,YAEzB,IAAMO,EAAWV,GAAoBM,GAAUd,EAAQ,SAAS,EAE1DmB,EAAS,SAAS,cAAc,MAAM,EAC5C,OAAAA,EAAO,UAAYR,EAAQ,YAC3BQ,EAAO,YAAcrB,GAAmBiB,EAAWf,CAAO,EAC1DmB,EAAO,QAAQ,SAAWb,GAAeS,EAAWR,CAAM,EAE1DU,EAAK,YAAYC,CAAQ,EACzBD,EAAK,YAAYE,CAAM,EAEnBH,GAAUC,EAAK,YAAYG,GAAiBJ,EAAUhB,EAASO,CAAM,CAAC,EAEnEU,CACT,EAmBaG,GAAmB,CAACJ,EAAUhB,EAASO,IAAW,CAC7D,IAAMc,EAAW,SAAS,cAAc,MAAM,EAC9C,OAAAA,EAAS,UAAYV,EAAQ,cAC7BU,EAAS,YAAcrB,EAAQ,WAC/BqB,EAAS,QAAQ,SACfrB,EAAQ,eAAiBM,GAAeU,EAAUT,CAAM,EACnDc,CACT,EAEaC,GAAgB,IAC3B,uBAAuB,KAAK,UAAU,SAAS,EASpCC,GAAkB,CAACC,EAASxB,IAAY,CACnD,IAAMyB,EAAQH,GAAc,EACtBI,EAAc,CAClB,IAAKD,EAAQ,SAAMzB,EAAQ,YAC3B,KAAMyB,EAAQ,SAAMzB,EAAQ,aAC5B,MAAOyB,EAAQ,SAAMzB,EAAQ,aAC/B,EAEM2B,EAAWD,EAAYF,EAAQ,gBAAgB,GAAKE,EAAY,IAChEE,EAAMJ,EAAQ,aAAa,YAAY,GAAK,IAElD,MAAO,GAAGG,CAAQ,MAAMC,CAAG,EAC7B,EAGaC,GAAiB,8LAExBC,GAAkB,2RAElBC,GAAgB,mKAEhBC,GAAqB,i8BAErBC,GAAgB,+nBAETC,GAAe,kPAEfC,GAAmB,gYAenBC,GAAkB,CAC7B,CACE,cAAAC,EACA,SAAAC,EAAW,WACX,eAAAC,EACA,QAAAC,EACA,iBAAAC,EACA,YAAAC,EACA,YAAAC,CACF,EACA3C,IACG,CACH,IAAM4C,EAAY,SAAS,cAAc,KAAK,EAC9CA,EAAU,UAAYP,EAGtB,IAAMQ,EAAU,SAAS,cAAcP,CAAQ,EAC3CE,IAASK,EAAQ,GAAKL,GACtBD,IAAgBM,EAAQ,UAAYN,GACxCM,EAAQ,YAAcJ,EACtBI,EAAQ,aAAa,aAAcJ,CAAgB,EAC/CH,IAAa,UACkBO,EAAS,KAAO,QAEnD,IAAMC,EAAuB,SAAS,cAAc,KAAK,EACzDA,EAAqB,UAAYnC,EAAQ,sBAEzC,IAAMoC,EAAa,SAAS,cAAc,KAAK,EAC/CA,EAAW,UAAYpC,EAAQ,oBAE/B,IAAMqC,EAAY,SAAS,cAAc,QAAQ,EACjDA,EAAU,UAAYrC,EAAQ,iBAC9BqC,EAAU,KAAO,SACjBA,EAAU,aAAa,aAAchD,EAAQ,WAAW,EACxDgD,EAAU,UAAYlB,GAEtB,IAAMmB,EAAY,SAAS,cAAc,OAAO,EAChDA,EAAU,KAAO,OACbN,IAAaM,EAAU,GAAKN,GAChCM,EAAU,OAAS,UACnBA,EAAU,MAAM,QAAU,OAE1B,IAAMC,EAAY,SAAS,cAAc,QAAQ,EACjD,OAAIR,IAAaQ,EAAU,GAAKR,GAChCQ,EAAU,UAAYvC,EAAQ,cAC9BuC,EAAU,KAAO,SACjBA,EAAU,aAAa,aAAclD,EAAQ,IAAI,EACjDkD,EAAU,UAAYnB,GAEtBgB,EAAW,YAAYC,CAAS,EAChCD,EAAW,YAAYE,CAAS,EAChCF,EAAW,YAAYG,CAAS,EAEhCN,EAAU,YAAYC,CAAO,EAC7BD,EAAU,YAAYE,CAAoB,EAC1CF,EAAU,YAAYG,CAAU,EAEzB,CACL,UAAAH,EACA,QAAAC,EACA,qBAAAC,EACA,UAAAE,EACA,UAAAC,EACA,UAAAC,CACF,CACF,EAEMC,GAA0B,CAACC,EAAUC,EAAQC,EAAgBC,IAAU,CAC3E,IAAMC,EAAU,SAAS,cAAc,KAAK,EAC5CA,EAAQ,UAAY7C,EAAQ,uBAE5B,IAAM8C,EAAU,SAAS,cAAc,KAAK,EAC5CA,EAAQ,UAAY9C,EAAQ,uBAC5B2C,EAAe,QAAS5C,GAAO+C,EAAQ,YAAY/C,CAAE,CAAC,EAEtD,IAAMgD,EAAM,SAAS,cAAc,QAAQ,EAC3C,OAAAA,EAAI,KAAO,SACXA,EAAI,UAAY,GAAG/C,EAAQ,kBAAkB,IAAIyC,CAAQ,GACzDM,EAAI,aAAa,aAAcH,CAAK,EACpCG,EAAI,UAAYL,EAEhBG,EAAQ,YAAYC,CAAO,EAC3BD,EAAQ,YAAYE,CAAG,EAChBF,CACT,EAEaG,GAAgB,CAACnC,EAAU,CAAC,EAAGxB,EAAU4D,IAAmB,CACvE,IAAMC,EAAU,SAAS,cAAc,KAAK,EAC5CA,EAAQ,GAAKC,EAAI,QAEjB,IAAMC,EAAU,SAAS,cAAc,KAAK,EAC5CA,EAAQ,UAAYpD,EAAQ,gBAE5B,IAAMqD,EAAe,SAAS,cAAc,MAAM,EAClDA,EAAa,UAAYrD,EAAQ,aACjCqD,EAAa,YAAchE,EAAQ,eAEnC,IAAMiE,EAAc,SAAS,cAAc,MAAM,EACjDA,EAAY,UAAYtD,EAAQ,cAChCsD,EAAY,YAAc1C,GAAgBC,EAASxB,CAAO,EAE1D,IAAMkE,EAAiBf,GACrBxC,EAAQ,oBACRqB,GACA,CAACgC,EAAcC,CAAW,EAC1BjE,EAAQ,cACV,EACAkE,EACG,cAAc,IAAIvD,EAAQ,mBAAmB,EAAE,GAC9C,aAAa,eAAgB,OAAO,EAExC,IAAMwD,EAAa,SAAS,cAAc,MAAM,EAChDA,EAAW,UAAYxD,EAAQ,aAC/BwD,EAAW,YAAcnE,EAAQ,aAEjC,IAAMoE,EAAejB,GACnBxC,EAAQ,iBACRsB,GACA,CAACkC,CAAU,EACXnE,EAAQ,YACV,EAEA+D,EAAQ,YAAYG,CAAc,EAClCH,EAAQ,YAAYK,CAAY,EAChCP,EAAQ,YAAYE,CAAO,EAE3B,IAAMM,EAAkB,SAAS,cAAc,MAAM,EACrDA,EAAgB,UAAY1D,EAAQ,aACpC0D,EAAgB,YAAcrE,EAAQ,oBAEtC,IAAMsE,EAAoBnB,GACxBxC,EAAQ,gBACRuB,GACA,CAACmC,CAAe,EAChBrE,EAAQ,mBACV,EAMMuE,EAAa,SAAS,cAAc,KAAK,EAC/C,OAAAA,EAAW,UAAY5D,EAAQ,mBAC/B4D,EAAW,YAAYD,CAAiB,EACxCT,EAAQ,YAAYU,CAAU,EAEvBV,CACT,EAkBaW,GAAiB,CAC5BC,EACAzE,EACA,CAAE,cAAA0E,EAAgB,GAAO,sBAAAC,EAAwB,EAAK,EAAI,CAAC,IACxD,CACH,IAAMC,EAAM,SAAS,cAAc,KAAK,EACxCA,EAAI,UAAYjE,EAAQ,aAExB,IAAMkE,EAAW,CAACC,EAAMnD,EAAUoD,IAAU,CAC1C,IAAMC,EAAQ,SAAS,cAAc,MAAM,EAC3CA,EAAM,UAAY,GAAGrE,EAAQ,KAAK,IAAIgB,CAAQ,GAC9CqD,EAAM,YAAcF,EAChBC,IAAOC,EAAM,MAAM,YAAcD,GACrCH,EAAI,YAAYI,CAAK,CACvB,EAEA,GAAIN,EAAe,CACjB,IAAMO,EAASR,EAAQ,QAAU,OACjCI,EACEK,EAAcD,EAAQjF,CAAO,EAC7BW,EAAQ,aACRwE,EAAcF,CAAM,CACtB,CACF,CACIN,GAAyBF,EAAQ,MACnCI,EACEO,EAAYX,EAAQ,KAAMzE,CAAO,EACjCW,EAAQ,WACR0E,EAAYZ,EAAQ,IAAI,CAC1B,EAEEE,GAAyBF,EAAQ,UACnCI,EACES,EAAgBb,EAAQ,SAAUzE,CAAO,EACzCW,EAAQ,eACR4E,EAAgBd,EAAQ,QAAQ,CAClC,EAIF,QAAWe,KAAOf,EAAQ,MAAQ,CAAC,EACjCI,EAASW,EAAK7E,EAAQ,UAAW,IAAI,EAGvC,GAAI8D,EAAQ,SAAW,WAAY,CAMjC,IAAMgB,EAAYC,GAAoBjB,CAAO,EACvCkB,EACJF,IAAc,KAAO,GAAKG,EAAeH,EAAWzF,CAAO,EAC7D6E,EACExE,EAAeL,EAAQ,mBAAoB2F,GAAW,QAAG,EACzDhF,EAAQ,eACR,IACF,CACF,CAEA,OAAOiE,EAAI,SAAS,OAASA,EAAM,IACrC,EAcaiB,GAAqB7F,GAAY,CAC5C,IAAM4C,EAAY,SAAS,cAAc,KAAK,EAC9CA,EAAU,UAAYjC,EAAQ,aAE9B,IAAImF,EAAO,KACPC,EAAW,KAITC,EAAQ,IAAM,CAClBpD,EAAU,gBAAgB,EAC1BA,EAAU,YACRqD,GAAa,CACX,OAAQ,OACR,QAAS,CAAC,KAAM,GAAGC,CAAa,EAChC,MAAO,KACP,QAAUC,GAAUd,EAAYc,CAAK,GAAK,cAC1C,QAAUA,GAAUf,EAAYe,EAAOnG,CAAO,EAC9C,aAAcA,EAAQ,UACtB,SAAWmG,GAAWL,EAAOK,EAC7B,UAAW,EACb,CAAC,CACH,EACAvD,EAAU,YACRqD,GAAa,CACX,OAAQ,WACR,QAAS,CAAC,KAAM,GAAGG,CAAU,EAC7B,MAAO,KACP,QAAUD,GAAUZ,EAAgBY,CAAK,GAAK,cAC9C,QAAUA,GAAUb,EAAgBa,EAAOnG,CAAO,EAClD,aAAcA,EAAQ,cACtB,SAAWmG,GAAWJ,EAAWI,EACjC,UAAW,EACb,CAAC,CACH,CACF,EAEA,OAAAH,EAAM,EAEC,CACL,UAAApD,EACA,QAAS,IAAMkD,EACf,YAAa,IAAMC,EACnB,MAAO,IAAM,CACXD,EAAO,KACPC,EAAW,KACXC,EAAM,CACR,CACF,CACF,EAEaK,GAAmB,CAACrG,EAAU4D,IAAmB,CAC5D,IAAM0C,EAAa,SAAS,cAAc,KAAK,EAC/CA,EAAW,GAAKxC,EAAI,YACpBwC,EAAW,aAAa,OAAQ,QAAQ,EACxCA,EAAW,aAAa,aAActG,EAAQ,mBAAmB,EAEjE,GAAM,CAAE,UAAWuG,CAAU,EAAInE,GAC/B,CACE,cAAezB,EAAQ,mBACvB,SAAU,WACV,QAASmD,EAAI,cACb,iBAAkB9D,EAAQ,mBAC1B,YAAa8D,EAAI,eACjB,YAAaA,EAAI,kBACnB,EACA9D,CACF,EAEMwG,EAAWX,GAAkB7F,CAAO,EAE1C,OAAAsG,EAAW,YAAYE,EAAS,SAAS,EACzCF,EAAW,YAAYC,CAAS,EAChCD,EAAW,MAAM,QAAU,OAGPA,EAAY,SAAWE,EACpCF,CACT,EAWaG,EAAgBN,GAAU,OAAOA,CAAK,EAAE,QAAQ,SAAU,MAAM,EAOhEO,GAAkBC,GAAO,qBAAqBF,EAAaE,CAAE,CAAC,KAQ9DC,GAAiBC,GAC5BA,EAAM,cAAgBA,EAAM,WAAa,CAACA,EAAM,UAAU,EAAI,CAAC,GAcpDC,GAA2B,CACtClE,EACAmE,EACA,CAAE,QAAA/G,EAAS,OAAAgH,EAAQ,SAAAC,EAAU,QAAAC,EAAU,CAAE,IACtC,CACHtE,EAAU,UAAY,GACtBA,EAAU,UAAU,OAClBjC,EAAQ,OACRoG,EAAY,OAAS,GAAKG,EAAU,CACtC,EAEAH,EAAY,QAAQ,CAACI,EAASC,IAAM,CAClC,IAAMC,EAAO,SAAS,cAAc,KAAK,EACzCA,EAAK,UAAY1G,EAAQ,gBAEzB,IAAM2G,EAAM,SAAS,cAAc,KAAK,EACxCA,EAAI,UAAY3G,EAAQ,eACxB2G,EAAI,IAAMH,EACVG,EAAI,IAAMtH,EAAQ,mBAClBuH,GAAsBD,EAAK,IAAMN,EAAOG,CAAO,CAAC,EAEhD,IAAMK,EAAY,SAAS,cAAc,QAAQ,EACjDA,EAAU,KAAO,SACjBA,EAAU,UAAY7G,EAAQ,kBAC9B6G,EAAU,aAAa,aAAcxH,EAAQ,gBAAgB,EAC7DwH,EAAU,UAAY,UACtBA,EAAU,QAAWC,GAAM,CACzBA,EAAE,gBAAgB,EAClBV,EAAY,OAAOK,EAAG,CAAC,EACvBH,EAAS,CACX,EAEAI,EAAK,YAAYC,CAAG,EACpBD,EAAK,YAAYG,CAAS,EAC1B5E,EAAU,YAAYyE,CAAI,CAC5B,CAAC,EAUD,QAASD,EAAI,EAAGA,EAAIF,EAASE,IAAK,CAChC,IAAMM,EAAO,SAAS,cAAc,KAAK,EACzCA,EAAK,UAAY,GAAG/G,EAAQ,eAAe,IAAIA,EAAQ,kBAAkB,GACzE+G,EAAK,aAAa,OAAQ,QAAQ,EAClCA,EAAK,aAAa,YAAa,QAAQ,EACvCA,EAAK,YAAc1H,EAAQ,oBAC3B4C,EAAU,YAAY8E,CAAI,CAC5B,CACF,EAUMC,GAAiBC,GACrB,IAAI,QAASC,GAAY,CACvB,IAAMC,EAAS,IAAI,WACnBA,EAAO,OAAUC,GAAOF,EAA+BE,EAAG,OAAO,MAAO,EACxED,EAAO,QAAU,IAAMD,EAAQ,IAAI,EACnCC,EAAO,cAAcF,CAAI,CAC3B,CAAC,EAYUI,GAAsB,CACjCC,EACAC,EACAjB,EACAkB,IACG,CACHF,EAAM,iBAAiB,SAAU,MAAOR,GAAM,CAC5C,IAAMG,EAAwCH,EAAE,OAAQ,MAAM,CAAC,EAK/D,GAJI,CAACG,GAGDA,EAAK,MAAQ,CAACA,EAAK,KAAK,WAAW,QAAQ,GAC3CM,EAAe,EAAE,QAAUE,GAAiB,OAEhD,IAAMlB,EAAUS,GAAcC,CAAI,EAGlCK,EAAM,MAAQ,GACd,IAAMd,EAAU,MAAMD,EACtB,GAAI,CAACC,EAAS,OAId,IAAMJ,EAAcmB,EAAe,EAYnC,GAXInB,EAAY,QAAUqB,KAQ1BrB,EAAY,KAAKI,CAAO,EACxBF,EAAS,EAEL,CAACkB,GAAW,OAChB,IAAMhC,EAAQ,MAAMgC,EAAUhB,CAAO,EAM/BkB,EAAKtB,EAAY,QAAQI,CAAO,EAClCkB,IAAO,KACXtB,EAAYsB,CAAE,EAAIlC,EAClBc,EAAS,EACX,CAAC,CACH,EAQaqB,EAAyB,CAACC,EAAMvB,IAAW,CACtDuB,EACG,iBAAiB,IAAI5H,EAAQ,cAAc,EAAE,EAC7C,QAAyC2G,GAAQ,CAChDC,GAAsBD,EAAK,IAAMN,EAAOM,EAAI,GAAG,CAAC,CAClD,CAAC,CACL,EASMC,GAAwB,CAACD,EAAKkB,IAAa,CAC/ClB,EAAI,aAAa,OAAQ,QAAQ,EACjCA,EAAI,aAAa,WAAY,GAAG,EAChCA,EAAI,iBAAiB,QAAUG,GAAM,CACnCA,EAAE,gBAAgB,EAClBe,EAAS,CACX,CAAC,EACDlB,EAAI,iBAAiB,UAAYG,GAAM,EACjCA,EAAE,MAAQ,SAAWA,EAAE,MAAQ,OACjCA,EAAE,eAAe,EACjBe,EAAS,EAEb,CAAC,CACH,EAEaC,GAAsB,CAAChE,EAASzE,EAAU4D,IAAmB,CACxE,IAAM8E,EAAS,SAAS,cAAc,KAAK,EAC3C,OAAAA,EAAO,UAAY/H,EAAQ,OAC3B+H,EAAO,QAAQ,UAAYjE,EAAQ,GACnCiE,EAAO,aAAa,OAAQ,QAAQ,EACpCA,EAAO,aAAa,WAAY,GAAG,EACnCA,EAAO,aACL,aACA,GAAG1I,EAAQ,sBAAsB,GAAGyE,EAAQ,IAAI,EAClD,EAGAiE,EAAO,MAAM,QAAU;AAAA;AAAA;AAAA,MAKhBA,CACT,EAEaC,GAA2B,CAAC5B,EAAa/G,IAAY,CAChE,IAAM4C,EAAY,SAAS,cAAc,KAAK,EAC9C,OAAAA,EAAU,UAAYjC,EAAQ,sBAC9BiC,EAAU,UAAU,IAAIjC,EAAQ,MAAM,EAEtCoG,EAAY,QAAS6B,GAAQ,CAC3B,IAAMvB,EAAO,SAAS,cAAc,KAAK,EACzCA,EAAK,UAAY1G,EAAQ,gBAEzB,IAAM2G,EAAM,SAAS,cAAc,KAAK,EACxCA,EAAI,UAAY3G,EAAQ,eACxB2G,EAAI,IAAMsB,EACVtB,EAAI,IAAMtH,EAAQ,mBAElBqH,EAAK,YAAYC,CAAG,EACpB1E,EAAU,YAAYyE,CAAI,CAC5B,CAAC,EAEMzE,CACT,EAEaiG,GAAgB,CAACpE,EAASzE,EAAU4D,EAAgBrD,IAAW,CAC1E,IAAMkD,EAAU,SAAS,cAAc,KAAK,EAC5CA,EAAQ,UAAY9C,EAAQ,QAC5B8C,EAAQ,QAAQ,IAAMgB,EAAQ,GAC9BhB,EAAQ,aAAa,OAAQ,QAAQ,EACrCA,EAAQ,aAAa,aAAczD,EAAQ,gBAAgB,EAE3D,IAAM8I,EAAS,SAAS,cAAc,KAAK,EAC3CA,EAAO,UAAYnI,EAAQ,cAE3B,IAAMM,EAAOJ,GACX4D,EAAQ,OACRA,EAAQ,UACRzE,EACAO,EACAkE,EAAQ,QACV,EACMsE,EAAc,SAAS,cAAc,QAAQ,EACnDA,EAAY,KAAO,SACnBA,EAAY,UAAYpI,EAAQ,cAChCoI,EAAY,aAAa,aAAc/I,EAAQ,KAAK,EACpD+I,EAAY,UAAY,UAExBD,EAAO,YAAY7H,CAAI,EACvB6H,EAAO,YAAYC,CAAW,EAE9B,IAAMC,EAAO,SAAS,cAAc,KAAK,EACzCA,EAAK,UAAYrI,EAAQ,YACzBqI,EAAK,YAAcvE,EAAQ,KAE3BhB,EAAQ,YAAYqF,CAAM,EAC1BrF,EAAQ,YAAYuF,CAAI,EAGxB,IAAMC,EAASzE,GAAeC,EAASzE,EAAS,CAAE,cAAe,EAAK,CAAC,EACnEiJ,GAAQxF,EAAQ,YAAYwF,CAAM,EACtC,IAAMC,EAAqBtC,GAAcnC,CAAO,EAC5CyE,EAAmB,OAAS,GAC9BzF,EAAQ,YAAYkF,GAAyBO,EAAoBlJ,CAAO,CAAC,EAM3E,IAAMmJ,EAAa1E,EAAQ,SAAS,QAAU,EAC9C,GAAI0E,EAAa,EAAG,CAClB,IAAMC,EAAU,SAAS,cAAc,KAAK,EAC5CA,EAAQ,UAAYzI,EAAQ,oBAC5ByI,EAAQ,YACND,IAAe,EACXnJ,EAAQ,cACRK,EAAeL,EAAQ,mBAAoBmJ,CAAU,EAC3D1F,EAAQ,YAAY2F,CAAO,CAC7B,CAEA,OAAO3F,CACT,EAoCa4F,GAAqB,CAChCC,EACAtJ,EAAU4D,EACVrD,EACA,CAAE,SAAAgJ,EAAU,OAAAC,EAAQ,UAAAC,EAAW,IAAAC,EAAK,QAAAC,EAAU,KAAM,UAAAC,EAAY,IAAK,EAAI,CAAC,IACvE,CACH,IAAMC,EAAU,SAAS,cAAc,KAAK,EAC5CA,EAAQ,UAAYlJ,EAAQ,aAG5BkJ,EAAQ,QAAQ,QAAU,OAAOP,EAAM,EAAE,EAEzC,IAAMrI,EAAOJ,GACXyI,EAAM,OACNA,EAAM,UACNtJ,EACAO,EACA+I,EAAM,QACR,EAIMQ,EAAQ,CAAC,EACTC,EAASC,GAAcV,EAAOG,CAAS,EACvCQ,EAA4BC,GAChCR,EAAMA,EAAIQ,EAAQH,CAAM,EAAI,GAC1BP,GAAUS,EAAM,YAAY,GAC9BH,EAAM,KAAK,CAAE,MAAO9J,EAAQ,UAAW,SAAU,IAAMwJ,EAAOF,CAAK,CAAE,CAAC,EAEpEC,GAAYU,EAAM,cAAc,GAClCH,EAAM,KAAK,CACT,MAAO9J,EAAQ,YACf,SAAU,IAAMuJ,EAASD,EAAOO,CAAO,EACvC,QAAS,KAAO,CACd,MAAO7J,EAAQ,wBACf,QAASA,EAAQ,0BACjB,aAAcA,EAAQ,cACtB,YAAaA,EAAQ,aACvB,EACF,CAAC,EAKH,IAAMmK,EAAa,SAAS,cAAc,KAAK,EAC/CA,EAAW,UAAY,GAAGxJ,EAAQ,aAAa,IAAIA,EAAQ,oBAAoB,GAC3EiJ,GACFO,EAAW,YACTP,EAAU,QAAQN,EAAO,CAAE,UAAW3I,EAAQ,gBAAiB,CAAC,CAClE,EAEEmJ,EAAM,OAAS,GACjBK,EAAW,YACTC,GAAe,CAAE,MAAOpK,EAAQ,aAAc,MAAA8J,CAAM,CAAC,CACvD,EAEEK,EAAW,SAAS,OAAS,GAAGlJ,EAAK,YAAYkJ,CAAU,EAE/D,IAAIrF,EACA6E,EACF7E,EAAOuF,GAAmB,CACxB,MAAOV,EAAQ,MACf,QAAA3J,EACA,QAAS2J,EAAQ,QACjB,OAAQA,EAAQ,OAChB,SAAUA,EAAQ,QACpB,CAAC,GAED7E,EAAO,SAAS,cAAc,KAAK,EACnCA,EAAK,UAAYnE,EAAQ,YACzBmE,EAAK,YAAcwE,EAAM,MAG3BO,EAAQ,YAAY5I,CAAI,EACxB4I,EAAQ,YAAY/E,CAAI,EACxB,IAAMwF,EAAmB1D,GAAc0C,CAAK,EAC5C,OAAIgB,EAAiB,OAAS,GAC5BT,EAAQ,YAAYlB,GAAyB2B,EAAkBtK,CAAO,CAAC,EAIrE4J,GAAWC,EAAQ,YAAYD,EAAU,IAAIN,CAAK,CAAC,EAChDO,CACT,EAaaU,GAAsB,CACjC9F,EACAzE,EAAU4D,EACVrD,EACA,CAAE,cAAAiK,EAAe,YAAAC,EAAa,IAAAf,EAAK,UAAAE,EAAY,IAAK,EAAI,CAAC,IACtD,CACH,IAAMc,EAAU,SAAS,cAAc,KAAK,EAC5CA,EAAQ,UAAY/J,EAAQ,eAC5B+J,EAAQ,QAAQ,IAAMjG,EAAQ,GAC9BiG,EAAQ,aAAa,OAAQ,QAAQ,EACrCA,EAAQ,aAAa,aAAc1K,EAAQ,gBAAgB,EAE3D,IAAM8I,EAAS,SAAS,cAAc,KAAK,EAC3CA,EAAO,UAAYnI,EAAQ,cAE3B,IAAMM,EAAOJ,GACX4D,EAAQ,OACRA,EAAQ,UACRzE,EACAO,EACAkE,EAAQ,QACV,EACMsE,EAAc,SAAS,cAAc,QAAQ,EACnDA,EAAY,KAAO,SACnBA,EAAY,UAAYpI,EAAQ,cAChCoI,EAAY,aAAa,aAAc/I,EAAQ,KAAK,EACpD+I,EAAY,UAAY,UAExBD,EAAO,YAAY7H,CAAI,EACvB6H,EAAO,YAAYC,CAAW,EAE9B,IAAMC,EAAO,SAAS,cAAc,KAAK,EACzCA,EAAK,UAAYrI,EAAQ,YACzBqI,EAAK,YAAcvE,EAAQ,KAE3B,IAAM2E,EAAU,SAAS,cAAc,KAAK,EAC5CA,EAAQ,UAAYzI,EAAQ,eACxB8D,EAAQ,SACVA,EAAQ,QAAQ,QAAS6E,GAAU,CACjCF,EAAQ,YACNC,GAAmBC,EAAOtJ,EAASO,EAAQ,CACzC,SAAUiK,EACV,OAAQC,EACR,UAAWhG,EAAQ,GACnB,IAAAiF,EACA,UAAAE,CACF,CAAC,CACH,CACF,CAAC,EAGH,GAAM,CAAE,UAAWrD,CAAU,EAAInE,GAC/B,CACE,cAAezB,EAAQ,kBACvB,SAAU,QACV,eAAgBA,EAAQ,aACxB,iBAAkBX,EAAQ,gBAC5B,EACAA,CACF,EAOM2K,EAAS,SAAS,cAAc,KAAK,EAC3CA,EAAO,UAAYhK,EAAQ,cAE3B+J,EAAQ,YAAY5B,CAAM,EAC1B6B,EAAO,YAAY3B,CAAI,EACvB,IAAM4B,EAAqBhE,GAAcnC,CAAO,EAChD,OAAImG,EAAmB,OAAS,GAC9BD,EAAO,YAAYhC,GAAyBiC,EAAoB5K,CAAO,CAAC,EAItE4J,GAAWe,EAAO,YAAYf,EAAU,IAAInF,CAAO,CAAC,EACxDkG,EAAO,YAAYvB,CAAO,EAC1BsB,EAAQ,YAAYC,CAAM,EAC1BD,EAAQ,YAAYnE,CAAS,EAEtBmE,CACT,ECt+BO,IAAMG,GAAqB,CAChCC,EACA,CAAE,QAAAC,EAAS,eAAAC,EAAgB,YAAAC,EAAc,GAAO,SAAAC,EAAW,GAAO,SAAAC,CAAS,IACxE,CACH,GAAM,CAAE,QAAAC,EAAS,kBAAAC,CAAkB,EAAIP,EACvC,GAAI,CAACM,GAAW,CAACC,EAAmB,OAAO,KAE3C,IAAMC,EAAQ,SAAS,cAAc,KAAK,EAC1CA,EAAM,UAAYC,EAAQ,cAE1B,IAAMC,EAAO,SAAS,cAAc,KAAK,EAGzC,GAFAA,EAAK,UAAYD,EAAQ,aAErBN,EAAa,CACf,IAAMQ,EAAS,SAAS,cAAc,QAAQ,EAC9CA,EAAO,KAAO,SACdA,EAAO,UAAYF,EAAQ,eAC3BE,EAAO,aAAa,gBAAiB,OAAOP,CAAQ,CAAC,EACrDO,EAAO,UAAY,SAASV,EAAQ,cAAc,UAAUW,EAAc,GAC1EF,EAAK,MAAM,QAAUN,EAAW,GAAK,OACrCO,EAAO,iBAAiB,QAAUE,GAAM,CACtCA,EAAE,gBAAgB,EAClB,IAAMC,EAASH,EAAO,aAAa,eAAe,IAAM,OACxDA,EAAO,aAAa,gBAAiB,OAAO,CAACG,CAAM,CAAC,EACpDJ,EAAK,MAAM,QAAUI,EAAS,OAAS,GACvCT,IAAW,CAACS,CAAM,CACpB,CAAC,EACDN,EAAM,YAAYG,CAAM,CAC1B,KAAO,CACL,IAAMI,EAAQ,SAAS,cAAc,KAAK,EAC1CA,EAAM,UAAYN,EAAQ,cAC1BM,EAAM,YAAcd,EAAQ,eAC5BO,EAAM,YAAYO,CAAK,CACzB,CAEA,GAAIR,EAAmB,CACrB,IAAMS,EAAU,SAAS,cAAc,KAAK,EAC5CA,EAAQ,UAAYP,EAAQ,2BAC5BO,EAAQ,YAAcf,EAAQ,oBAC9BS,EAAK,YAAYM,CAAO,EAExB,IAAMC,EAAM,SAAS,cAAc,KAAK,EACxCA,EAAI,UAAYR,EAAQ,eACxBQ,EAAI,IAAMV,EACVU,EAAI,IAAMhB,EAAQ,oBAClBS,EAAK,YAAYO,CAAG,EAMpBC,EAAuBR,EAAMR,CAAc,CAC7C,CAEA,GAAII,EAAS,CACX,IAAMa,EAAS,CAACC,EAAOC,IAAU,CAC/B,GAAI,CAACA,EAAO,OACZ,IAAMC,EAAM,SAAS,cAAc,KAAK,EACxCA,EAAI,UAAYb,EAAQ,YACxB,IAAMc,EAAM,SAAS,cAAc,MAAM,EACzCA,EAAI,YAAcH,EAClB,IAAMI,EAAM,SAAS,cAAc,MAAM,EACzCA,EAAI,YAAcH,EAClBC,EAAI,YAAYC,CAAG,EACnBD,EAAI,YAAYE,CAAG,EACnBd,EAAK,YAAYY,CAAG,CACtB,EAEMG,EAAQC,GACZA,EAAa,GAAGA,EAAW,KAAK,OAAIA,EAAW,MAAM,GAAK,GACtDC,EAASC,GACbA,GAAO,KAAO,GAAGA,EAAM,IAAI,IAAIA,EAAM,SAAW,EAAE,GAAG,KAAK,EAAI,GAEhET,EAAOlB,EAAQ,WAAYK,EAAQ,GAAG,EACtCa,EAAOlB,EAAQ,gBAAiBwB,EAAKnB,EAAQ,QAAQ,CAAC,EACtDa,EAAOlB,EAAQ,cAAewB,EAAKnB,EAAQ,MAAM,CAAC,EAClDa,EAAOlB,EAAQ,eAAgB0B,EAAMrB,EAAQ,OAAO,CAAC,EACrDa,EAAOlB,EAAQ,UAAW0B,EAAMrB,EAAQ,EAAE,CAAC,CAC7C,CAEA,OAAAE,EAAM,YAAYE,CAAI,EACfF,CACT,ECnGA,IAAMqB,GAAgBC,GAAY,CAChC,IAAMC,EAAQ,CAAC,GAAGD,EAAQ,UAAU,EACjC,IAAI,CAAC,CAAE,KAAAE,EAAM,MAAAC,CAAM,IAAM,GAAGD,CAAI,KAAKC,CAAK,GAAG,EAC7C,KAAK,GAAG,EACX,MAAO,IAAIH,EAAQ,QAAQ,YAAY,CAAC,GAAGC,EAAQ,IAAIA,CAAK,GAAK,EAAE,GACrE,EAEMG,GAA6BC,GAAgB,CACjD,GAAI,CAACA,GAAa,QAAS,MAAO,YAClC,IAAMJ,EAAQ,OAAO,QAAQI,EAAY,YAAc,CAAC,CAAC,EACtD,IAAI,CAAC,CAACH,EAAMC,CAAK,IAAM,GAAGD,CAAI,KAAKC,CAAK,GAAG,EAC3C,KAAK,GAAG,EACX,MAAO,IAAIE,EAAY,QAAQ,YAAY,CAAC,GAAGJ,EAAQ,IAAIA,CAAK,GAAK,EAAE,GACzE,EAIMK,GAAaN,GAAY,CAC7B,IAAMO,EAAW,CAAC,EACdC,EAAUR,EACd,KAAOQ,GAAWA,IAAY,SAAS,iBAAiB,CACtD,IAAMC,EAAMD,EAAQ,QAAQ,YAAY,EAClCE,EAAKF,EAAQ,GAAK,IAAIA,EAAQ,EAAE,GAAK,GACrCG,EAAU,CAAC,GAAGH,EAAQ,SAAS,EAClC,MAAM,EAAG,CAAC,EACV,IAAKI,GAAQ,IAAIA,CAAG,EAAE,EACtB,KAAK,EAAE,EAEV,GADAL,EAAS,QAAQ,GAAGE,CAAG,GAAGC,CAAE,GAAGC,CAAO,EAAE,EACpCH,IAAY,SAAS,KAAM,MAC/BA,EAAUA,EAAQ,aACpB,CACA,OAAOD,EAAS,KAAK,KAAK,CAC5B,EAOO,SAASM,GACdC,EACA,CAAE,cAAAC,EAAe,eAAAC,EAAgB,QAAAC,CAAQ,EACzC,CACA,IAAMC,EAASJ,EAAQ,OACjBT,EAAca,GAAQ,YACtBC,EAAOL,EAAQ,WAAW,YAAcA,EAAQ,UAAY,KAE5DM,EAAQN,EAAQ,OAAS,SAAWA,EAAQ,YAC5Cd,EAAUmB,EACZpB,GAAaoB,CAAI,EACjBf,GAA0BC,CAAW,EACnCgB,EAAOF,EAAOb,GAAUa,CAAI,EAAI,gBAMhCG,EAAmBR,EAAQ,SAAS,SACpCS,EAAwBD,GAAkB,OAASP,EACnDS,EAAyBF,GAAkB,QAAUN,EAErDS,EAAQ,CACZ,SAASX,EAAQ,IAAI,GACrB,aAAaS,CAAqB,IAAIC,CAAsB,GAC5D,iBAAiBJ,CAAK,GACtB,WAAWN,EAAQ,QAAU,MAAM,GACnC,aAAaI,GAAQ,UAAY,QAAQ,GACzC,YAAYlB,CAAO,GACnB,aAAaqB,CAAI,GACjB,iBAAiBhB,GAAa,aAAe,EAAE,IAC/C,cAAcS,EAAQ,MAAM,KAAKA,EAAQ,SAAS,KAClD,IAAIA,EAAQ,IAAI,GAClB,EAIIA,EAAQ,MAAMW,EAAM,KAAK,SAASX,EAAQ,IAAI,EAAE,EAChDA,EAAQ,UAAUW,EAAM,KAAK,aAAaX,EAAQ,QAAQ,EAAE,EAC5DA,EAAQ,MAAM,QAAQW,EAAM,KAAK,SAASX,EAAQ,KAAK,KAAK,IAAI,CAAC,EAAE,EAEvE,IAAMY,EAAUZ,EAAQ,QAkBxB,GAjBIY,IACEA,EAAQ,KAAKD,EAAM,KAAK,QAAQC,EAAQ,GAAG,EAAE,EAC7CA,EAAQ,QACVD,EAAM,KAAK,WAAWC,EAAQ,OAAO,KAAK,IAAIA,EAAQ,OAAO,MAAM,EAAE,EAEnEA,EAAQ,SAAS,MACnBD,EAAM,KACJ,YAAY,GAAGC,EAAQ,QAAQ,IAAI,IAAIA,EAAQ,QAAQ,SAAW,EAAE,GAAG,KAAK,CAAC,EAC/E,EAEEA,EAAQ,IAAI,MACdD,EAAM,KACJ,OAAO,GAAGC,EAAQ,GAAG,IAAI,IAAIA,EAAQ,GAAG,SAAW,EAAE,GAAG,KAAK,CAAC,EAChE,GAIAT,GAAWH,EAAQ,SAAW,YAAcA,EAAQ,WAAY,CAClE,IAAMa,EAAUC,EACd,IAAI,KAAKd,EAAQ,UAAU,EAAE,QAAQ,EACnC,IAAI,KAAKA,EAAQ,SAAS,EAAE,QAAQ,EACtCG,CACF,EACIU,GAASF,EAAM,KAAK,oBAAoBE,CAAO,EAAE,CACvD,CAEA,IAAME,EAAUf,EAAQ,SAAW,CAAC,EACpC,GAAIe,EAAQ,OAAS,EAAG,CACtBJ,EAAM,KAAK,YAAYI,EAAQ,MAAM,IAAI,EACzC,QAAWC,KAASD,EAClBJ,EAAM,KAAK,KAAKK,EAAM,MAAM,MAAMA,EAAM,IAAI,GAAG,CAEnD,CAEA,OAAOL,EAAM,KAAK;AAAA,CAAI,CACxB,CCzFO,IAAMM,GAA0B,CAACC,EAAIC,IAAW,CACrD,IAAMC,EAAaD,EAAO,sBAAsB,EAC1CE,EAAUD,EAAW,KAAOA,EAAW,MAAQ,EAC/CE,EAAUF,EAAW,IAAMA,EAAW,OAAS,EAC/CG,EAAiBC,EACjBC,EAASF,EAAiB,EAAI,GAI9BG,EAASR,EAAG,sBAAsB,EAClCS,EAAUD,EAAO,OAAS,IAE5BE,EAAIP,EAAUI,EAEdG,EAAID,EAAU,OAAO,aACvBC,EAAIP,EAAUI,EAASE,GAEzBC,EAAI,KAAK,IAAIA,EAAG,OAAO,WAAaD,EAAU,EAAE,EAChDC,EAAI,KAAK,IAAI,GAAIA,CAAC,EAClBV,EAAG,MAAM,KAAO,GAAGU,CAAC,KAepB,IAAMC,EAAS,GACTC,EAAeR,EAAUC,EAAiB,EAC1CQ,EAAa,OAAO,YAAcF,EAASC,EAE7CJ,EAAO,OAASK,GAClBb,EAAG,MAAM,IAAM,OACfA,EAAG,MAAM,OAAS,GAAGW,CAAM,OAE3BX,EAAG,MAAM,OAAS,OAClBA,EAAG,MAAM,IAAM,GAAG,KAAK,IAAIW,EAAQC,CAAY,CAAC,KAEpD,EAOaE,GAAiBd,GAAO,CACnC,IAAMQ,EAASR,EAAG,sBAAsB,EAClCU,EAAI,KAAK,IAAI,IAAK,OAAO,YAAcF,EAAO,OAAS,MAAQ,CAAC,EAChEO,EAAI,KAAK,IAAI,IAAK,OAAO,YAAcP,EAAO,QAAU,CAAC,EAC/DR,EAAG,MAAM,KAAO,GAAGU,CAAC,KAGpBV,EAAG,MAAM,OAAS,OAClBA,EAAG,MAAM,IAAM,GAAGe,CAAC,IACrB,EAEaC,GAAN,KAAwB,CAwB7B,YAAYC,EAAM,CAChB,KAAK,KAAOA,EAEZ,KAAK,OAAS,KAMd,KAAK,cAAgB,KAQrB,KAAK,QAAU,KAEf,KAAK,gBAAkB,KAEvB,KAAK,cAAgB,KAOrB,KAAK,eAAiB,IACxB,CAOA,QAAQC,EAAU,KAAM,CACtB,IAAMC,EAAU,KAAK,OACrB,OAAKA,EACDD,GAAW,KACNC,EAAQ,cACb,IAAIC,EAAQ,aAAa,OAAOA,EAAQ,WAAW,MAAMA,EAAQ,aAAa,OAAOA,EAAQ,MAAM,EACrG,EAEUD,EAAQ,cAClB,IAAIC,EAAQ,YAAY,mBAAmBC,EAAaH,CAAO,CAAC,IAClE,GAEO,cAAc,IAAIE,EAAQ,WAAW,MAAMA,EAAQ,MAAM,EAAE,GAAK,KAVlD,IAYvB,CAOA,oBAAoBE,EAAIJ,EAAU,KAAM,CACtC,GAAI,KAAK,QAAQ,QAAQ,MAAQ,OAAOI,CAAE,EAAG,OAC7C,IAAMC,EAAU,KAAK,KAAK,YAAYD,CAAE,EACxC,GAAI,CAACC,EAAS,OAEd,IAAMC,EACJN,GAAW,KACPK,GACCA,EAAQ,SAAW,CAAC,GAAG,KAAME,GAAMC,EAAOD,EAAE,GAAIP,CAAO,CAAC,EAC/D,GAAI,CAACM,EAAQ,OAEb,IAAMG,EAAO,SAAS,cAAc,KAAK,EACzCA,EAAK,UAAYP,EAAQ,YACzBO,EAAK,YAAcH,EAAO,KAC1B,KAAK,QAAQN,CAAO,GAAG,YAAYS,CAAI,EAIvC,IAAMC,EACJV,GAAW,KACP,KAAK,OAAO,cAAc,IAAIE,EAAQ,WAAW,EAAE,EACnD,KAAK,OACF,cACC,IAAIA,EAAQ,YAAY,mBAAmBC,EAAaH,CAAO,CAAC,IAClE,GACE,cAAc,IAAIE,EAAQ,WAAW,EAAE,EACjD,GACEQ,GACAJ,EAAO,UACP,CAACI,EAAK,cAAc,IAAIR,EAAQ,aAAa,EAAE,EAC/C,CACA,IAAMS,EAAWC,GACfN,EAAO,SACP,KAAK,KAAK,QACV,KAAK,KAAK,MACZ,EAEMO,EAAUH,EAAK,cAAc,IAAIR,EAAQ,oBAAoB,EAAE,EACjEW,EAASH,EAAK,aAAaC,EAAUE,CAAO,EAC3CH,EAAK,YAAYC,CAAQ,CAChC,CACF,CAGA,WAAY,CACV,OAAO,KAAK,SAAW,IACzB,CAGA,aAAc,CACZ,GAAI,CAAC,KAAK,QAAS,MAAO,GAC1B,GAAM,CAAE,UAAAG,EAAW,QAAAd,EAAS,MAAAe,CAAM,EAAI,KAAK,QACrCV,EAAU,KAAK,KAAK,YAAYS,CAAS,EACzCR,EACJN,GAAW,KACPK,GACCA,GAAS,SAAW,CAAC,GAAG,KAAME,GAAMC,EAAOD,EAAE,GAAIP,CAAO,CAAC,EAChE,OAAOe,EAAM,KAAK,IAAM,OAAOT,GAAQ,MAAQ,EAAE,EAAE,KAAK,CAC1D,CAQA,MAAM,eAAgB,CACpB,GAAI,CAAC,KAAK,QAAS,MAAO,GAC1B,GAAI,KAAK,YAAY,EAAG,CACtB,IAAMU,EAA2B,KAAK,KAAK,WAC3C,GAAI,CAAE,MAAMC,GAAeD,EAAM,KAAK,KAAK,OAAO,EAAI,MAAO,EAC/D,CACA,GAAM,CAAE,QAAAhB,CAAQ,EAAI,KAAK,QACzB,YAAK,QAAU,KACf,KAAK,aAAaA,CAAO,EAClB,EACT,CAEA,aAAaA,EAAS,CACpB,IAAMK,EAAU,KAAK,KAAK,YAAY,KAAK,QAAQ,QAAQ,GAAG,EAC9D,GAAI,CAACA,EAAS,OACd,IAAMC,EACJN,GAAW,KACPK,GACCA,EAAQ,SAAW,CAAC,GAAG,KAAM,GAAMG,EAAO,EAAE,GAAIR,CAAO,CAAC,EAC/D,GAAI,CAACM,EAAQ,OAEb,IAAMG,EAAO,SAAS,cAAc,KAAK,EACzCA,EAAK,UAAYP,EAAQ,YACzBO,EAAK,YAAcH,EAAO,KAC1B,KAAK,QAAQN,CAAO,GAAG,YAAYS,CAAI,CACzC,CASA,MAAM,aAAaK,EAAWd,EAAU,KAAM,CAC5C,GAAI,CAAE,MAAM,KAAK,cAAc,EAAI,OAEnC,IAAMK,EAAU,KAAK,KAAK,YAAYS,CAAS,EACzCR,EACJN,GAAW,KACPK,GACCA,GAAS,SAAW,CAAC,GAAG,KAAME,GAAMC,EAAOD,EAAE,GAAIP,CAAO,CAAC,EAChE,GAAI,CAACM,EAAQ,OAEb,KAAK,QAAU,CAAE,UAAAQ,EAAW,QAAAd,EAAS,MAAOM,EAAO,IAAK,EAExD,IAAMY,EAASC,GAAmB,CAChC,MAAOb,EAAO,KACd,QAAS,KAAK,KAAK,QACnB,QAAUc,GAAS,CACjB,KAAK,QAAQ,MAAQA,CACvB,EACA,OAASA,GAAS,CAChB,IAAMC,EACJrB,GAAW,KACP,KAAK,KAAK,QAAQ,YAAYc,EAAWM,CAAI,EAC7C,KAAK,KAAK,QAAQ,UAAUN,EAAWd,EAASoB,CAAI,EAC1D,KAAK,QAAU,KACXC,GACF,KAAK,oBAAoBP,EAAWd,CAAO,EAC3C,KAAK,KAAK,aAAa,GAEvB,KAAK,aAAaA,CAAO,CAE7B,EACA,SAAU,IAAM,CACd,KAAK,cAAc,CACrB,CACF,CAAC,EAED,KAAK,QAAQA,CAAO,GAAG,YAAYkB,CAAM,CAC3C,CAIA,KAAKnC,EAAQsB,EAAS,CACpB,KAAK,MAAM,EAEX,GAAM,CAAE,QAAAiB,EAAS,OAAAC,CAAO,EAAI,KAAK,KACjC,KAAK,KAAK,cAAclB,EAAQ,EAAE,EAKlCtB,GAAQ,UAAU,IAAImB,EAAQ,aAAa,EAC3C,KAAK,cAAgBnB,GAAU,KAE/B,IAAMyC,EAAgB,CAACC,EAAOC,IAAY,CACnC,KAAK,KAAK,QAAQ,YAAYrB,EAAQ,GAAIoB,EAAM,EAAE,IACvDC,EAAQ,OAAO,EACf,KAAK,KAAK,aAAa,EACzB,EAMMC,EAAeF,GAAU,KAAK,aAAapB,EAAQ,GAAIoB,EAAM,EAAE,EAU/DG,EAAYC,GAAkB,CAClC,SAAU,KAAK,KAAK,SACpB,QAAAP,EACA,SAAU,CAACQ,EAAQC,IACjBD,IAAWzB,EACP,KAAK,KAAK,QAAQ,sBAAsBA,EAAQ,GAAI0B,CAAK,EACzD,KAAK,KAAK,QAAQ,oBAAoB1B,EAAQ,GAAIyB,EAAO,GAAIC,CAAK,CAC1E,CAAC,EAEK9B,EAAU+B,GAAoB3B,EAASiB,EAASC,EAAQ,CAC5D,IAAK,KAAK,KAAK,IACf,cAAAC,EACA,YAAAG,EACA,UAAAC,CACF,CAAC,EACD,KAAK,KAAK,WAAW,YAAY3B,CAAO,EAIxC,IAAMgC,EAAWhC,EAAQ,cAAc,IAAIC,EAAQ,aAAa,EAAE,EAC5DgC,EAAYC,GAAqB9B,EAAS,CAC9C,IAAK,KAAK,KAAK,IACf,QAAAiB,EACA,UAAAM,EACA,OAASQ,GACPC,GACEC,GAAkBF,EAAG,CACnB,cAAe,OAAO,WACtB,eAAgB,OAAO,YACvB,QAAAd,CACF,CAAC,CACH,EACF,WAAac,GACXC,GAAgBE,EAAiBH,EAAG,KAAK,KAAK,UAAU,CAAC,CAAC,EAC5D,OAASA,GAAM,KAAK,aAAaA,EAAE,EAAE,EACrC,YAAa,CAACA,EAAGI,IAAW,KAAK,KAAK,QAAQ,UAAUJ,EAAE,GAAII,CAAM,EACpE,UAAW,CAACJ,EAAGK,IAAS,KAAK,KAAK,QAAQ,QAAQL,EAAE,GAAIK,CAAI,EAC5D,cAAe,CAACL,EAAGM,IACjB,KAAK,KAAK,QAAQ,YAAYN,EAAE,GAAIM,CAAQ,EAC9C,SAAWN,GAAM,CACf,KAAK,MAAM,EACX,KAAK,KAAK,QAAQ,cAAcA,EAAE,EAAE,EACpC,KAAK,KAAK,aAAa,CACzB,CACF,CAAC,EAIKO,EAAa,SAAS,cAAc,KAAK,EAC/CA,EAAW,UAAYzC,EAAQ,mBAC/ByC,EAAW,YAAYT,CAAS,EAChCD,EAAS,sBAAsB,WAAYU,CAAU,EAGrD,IAAMC,EAA2B3C,EAAQ,cACvC,IAAIC,EAAQ,aAAa,OAAOA,EAAQ,qBAAqB,EAC/D,EACI0C,GACFC,EAAuBD,EAA2BE,GAChD,KAAK,KAAK,eAAeA,CAAG,CAC9B,EAMF,IAAMC,EAAeC,GAAmB3C,EAAS,CAC/C,QAAAiB,EACA,eAAiBwB,GAAQ,KAAK,KAAK,eAAeA,CAAG,EACrD,YAAa,EACf,CAAC,EACGC,GAGF9C,EAAQ,cAAc,IAAIC,EAAQ,cAAc,EAAE,EAAE,OAAO6C,CAAY,EAGzE,WAAW,IAAM,CACXhE,EACFF,GAAwBoB,EAASlB,CAAM,EAEvCa,GAAcK,CAAO,CAEzB,EAAG,EAAE,EAELA,EACG,cAAc,IAAIC,EAAQ,aAAa,EAAE,EACzC,iBAAiB,QAAS,MAAO+C,GAAM,CACtCA,EAAE,gBAAgB,EAKd,OAAK,SAAW,CAAE,MAAM,KAAK,cAAc,IAC/C,KAAK,MAAM,CACb,CAAC,EAGH,IAAMC,EACJjD,EAAQ,cAAc,IAAIC,EAAQ,YAAY,EAAE,EAE5CiD,EAAYlD,EAAQ,cAAc,IAAIC,EAAQ,aAAa,EAAE,EAC7DkD,EAAkBnD,EAAQ,cAC9B,IAAIC,EAAQ,iBAAiB,KAAKA,EAAQ,gBAAgB,EAC5D,EAEMmD,EACJpD,EAAQ,cAAc,IAAIC,EAAQ,iBAAiB,qBAAqB,EAEpEoD,EAA6BrD,EAAQ,cACzC,IAAIC,EAAQ,iBAAiB,KAAKA,EAAQ,qBAAqB,EACjE,EAEIqD,EAA0B,CAAC,EAEzBC,EAAgC,IAAM,CAC1CC,GACEH,EACAC,EACA,CACE,QAAAjC,EACA,OAASoC,GAAY,KAAK,KAAK,eAAeA,CAAO,EACrD,SAAU,IAAMF,EAA8B,CAChD,CACF,CACF,EAEAJ,EAAgB,iBAAiB,QAAS,IAAM,CAC9CC,EAAgB,MAAM,CACxB,CAAC,EAEDM,GACEN,EACA,IAAME,EACNC,EACCE,GAAY,KAAK,KAAK,oBAAoBA,EAASrD,EAAQ,EAAE,CAChE,EAEA,IAAMuD,EAAc,IAAM,CACxB,IAAMxC,EAAO8B,EAAM,MAAM,KAAK,EAC9B,GAAI,CAAC9B,GAAQmC,EAAwB,SAAW,EAAG,OAEnD,IAAM9B,EAAQ,KAAK,KAAK,QAAQ,SAC9BpB,EACAe,EACAmC,EAAwB,OAAS,EAAI,CAAC,GAAGA,CAAuB,EAAI,CAAC,CACvE,EAEMM,GAAmB5D,EAAQ,cAC/B,IAAIC,EAAQ,cAAc,EAC5B,EACMwB,GAAUoC,GAAmBrC,EAAOH,EAASC,EAAQ,CACzD,SAAUC,EACV,OAAQG,EACR,UAAWtB,EAAQ,GACnB,IAAK,KAAK,KAAK,IACf,UAAAuB,CACF,CAAC,EACDiC,GAAiB,YAAYnC,EAAO,EAEpCmB,EAAuBnB,GAAUoB,IAAQ,KAAK,KAAK,eAAeA,EAAG,CAAC,EAItE,IAAMiB,GAAW9D,EAAQ,cAAc,IAAIC,EAAQ,aAAa,EAAE,EAC9D6D,KAAUA,GAAS,UAAYA,GAAS,cAE5Cb,EAAM,MAAQ,GACdK,EAA0B,CAAC,EAC3BC,EAA8B,EAC9BN,EAAM,MAAM,CACd,EAEAC,EAAU,iBAAiB,QAASS,CAAW,EAC/CV,EAAM,iBAAiB,UAAYD,GAAM,CACnCA,EAAE,MAAQ,SAAW,CAACA,EAAE,WAC1BA,EAAE,eAAe,EACjBW,EAAY,EAEhB,CAAC,EAED,KAAK,OAAS3D,EAQV,OAAO,eAAmB,MAC5B,KAAK,gBAAkB,IAAI,eAAe,IAAM,KAAK,WAAW,CAAC,EACjE,KAAK,gBAAgB,QAAQA,CAAO,GAGtC,WAAW,IAAMiD,EAAM,MAAM,EAAG,EAAE,EAIlC,KAAK,eAAiB,WAAW,IAAM,CACrC,KAAK,eAAiB,KACtB,KAAK,cAAiBD,GAAM,CAC1B,IAAMnB,EAA8BmB,EAAE,aAAa,EAAE,CAAC,GAAKA,EAAE,OAC7D,GACE,CAAChD,EAAQ,SAAS6B,CAAM,GACxB,CAAC/C,GAAQ,SAAS+C,CAAM,GACxB,CAAC,KAAK,KAAK,iBAAiBA,CAAM,EAClC,CAOA,GAAI,KAAK,YAAY,EAAG,OACxB,KAAK,MAAM,CACb,CACF,EACA,SAAS,iBAAiB,YAAa,KAAK,aAAa,CAC3D,EAAG,CAAC,CACN,CAEA,OAAQ,CAGN,KAAK,KAAK,YACN,iBAAiB,IAAI5B,EAAQ,aAAa,EAAE,EAC7C,QAAoCpB,GACnCA,EAAG,UAAU,OAAOoB,EAAQ,aAAa,CAC3C,EACF,KAAK,iBAAiB,WAAW,EACjC,KAAK,gBAAkB,KAGvB,KAAK,QAAU,KACX,KAAK,SACP,KAAK,OAAO,OAAO,EACnB,KAAK,OAAS,MAEhB,KAAK,cAAgB,KACjB,KAAK,iBACP,aAAa,KAAK,cAAc,EAChC,KAAK,eAAiB,MAEpB,KAAK,gBACP,SAAS,oBAAoB,YAAa,KAAK,aAAa,EAC5D,KAAK,cAAgB,KAEzB,CAQA,YAAa,CACX,IAAMD,EAAU,KAAK,OACjB,CAACA,GAAWA,EAAQ,MAAM,UAAY,SAEtC,KAAK,cACPpB,GAAwBoB,EAAS,KAAK,aAAa,EAEnDL,GAAcK,CAAO,EAEzB,CAUA,cAAe,CACb,IAAMA,EAAU,KAAK,OACflB,EAAS,KAAK,cAGpB,GAAI,CAACkB,GAAW,CAAClB,EAAQ,OAEzB,IAAMiF,EAAOjF,EAAO,sBAAsB,EAS1C,GAAI,EAPFA,EAAO,aACPA,EAAO,MAAM,UAAY,QACzBiF,EAAK,OAAS,GACdA,EAAK,MAAQ,GACbA,EAAK,IAAM,OAAO,aAClBA,EAAK,KAAO,OAAO,YAEN,CACb/D,EAAQ,MAAM,QAAU,OACxB,MACF,CAIAA,EAAQ,MAAM,QAAU,GACxBpB,GAAwBoB,EAASlB,CAAM,CACzC,CACF,EC1nBA,IAAMkF,GAAwB,IAiBxBC,GAAa,CAACC,EAAQC,IAAS,KAAK,IAAI,EAAG,KAAK,IAAID,EAAQC,CAAI,CAAC,EAE1DC,GAAN,KAAmB,CAgBxB,YAAYC,EAAM,CAChB,KAAK,KAAOA,EAQZ,KAAK,QAAU,IAAI,IAEnB,KAAK,gBAAkB,IAAI,IAE3B,KAAK,QAAU,GAMf,KAAK,mBAAqB,EAC1B,KAAK,wBAA0B,KAG/B,KAAK,YAAc,KAEnB,KAAK,wBAA0B,KAC/B,KAAK,eAAiB,KACtB,KAAK,eAAiB,KACtB,KAAK,aAAe,IACtB,CAGA,OAAQ,CACN,KAAK,eAAiB,IAAM,KAAK,eAAe,EAChD,OAAO,iBAAiB,SAAU,KAAK,eAAgB,CAAE,QAAS,EAAK,CAAC,EAGxE,KAAK,eAAiB,IAAM,KAAK,eAAe,EAChD,OAAO,iBAAiB,SAAU,KAAK,eAAgB,CACrD,QAAS,GACT,QAAS,EACX,CAAC,EAGD,KAAK,aAAe,IAAM,KAAK,eAAe,EAC9C,OAAO,iBAAiB,OAAQ,KAAK,YAAY,EAO7C,OAAO,mBACT,KAAK,wBAA0B,IAAI,iBAAiB,IAAM,CACxD,KAAK,eAAe,CACtB,CAAC,EACD,KAAK,wBAAwB,QAAQ,SAAS,KAAM,CAClD,UAAW,GACX,QAAS,GACT,WAAY,GACZ,gBAAiB,CAAC,QAAS,QAAS,SAAU,MAAM,CACtD,CAAC,EAEL,CAGA,OAAOC,EAAS,CACd,IAAMC,EAASC,GAAoBF,EAAS,KAAK,KAAK,OAAO,EAC7D,KAAK,KAAK,WAAWC,EAAQD,CAAO,EAEpC,KAAK,KAAK,UAAU,YAAYC,CAAM,EACtC,KAAK,QAAQ,IAAI,OAAOD,EAAQ,EAAE,EAAGC,CAAM,EAC3C,KAAK,eAAeD,EAASC,CAAM,EAMnC,KAAK,qBAAqBD,EAASC,CAAM,CAC3C,CAGA,SAASE,EAAI,CACX,OAAO,KAAK,QAAQ,IAAI,OAAOA,CAAE,CAAC,GAAK,IACzC,CAGA,OAAOA,EAAI,CACT,KAAK,sBAAsBA,CAAE,EAC7B,KAAK,QAAQ,IAAI,OAAOA,CAAE,CAAC,GAAG,OAAO,EACrC,KAAK,QAAQ,OAAO,OAAOA,CAAE,CAAC,CAChC,CAGA,OAAQ,CACN,KAAK,gBAAgB,QAAQ,CAAC,CAAE,SAAAC,CAAS,IAAMA,GAAU,WAAW,CAAC,EACrE,KAAK,gBAAgB,MAAM,EAC3B,KAAK,QAAQ,QAASH,GAAWA,EAAO,OAAO,CAAC,EAChD,KAAK,QAAQ,MAAM,CACrB,CAQA,6BAA6BD,EAASC,EAAQ,CAC5C,GAAI,CAACD,EAAQ,WAAa,CAACC,EAAQ,OAAO,KAE1C,IAAMI,EAAgBL,EAAQ,UAAU,sBAAsB,EACxDM,EAAiBD,EAAc,MAC/BE,EAAkBF,EAAc,OAKtC,GAAIC,GAAkB,GAAKC,GAAmB,EAC5C,OAAO,KAIT,IAAMC,EAAYR,EAAQ,UAAYM,EAChCG,EAAYT,EAAQ,UAAYO,EAEhCG,EAAaf,GAAWa,EAAWF,CAAc,EACjDK,EAAahB,GAAWc,EAAWF,CAAe,EAGlDK,EAAqBF,EAAaJ,EAClCO,EAAqBF,EAAaJ,EAExC,MAAO,CACL,UAAWG,EACX,UAAWC,EACX,UAAWC,EACX,UAAWC,EACX,eAAAP,EACA,gBAAAC,EACA,cAAeF,EAAc,KAC7B,aAAcA,EAAc,GAC9B,CACF,CAQA,uBAAuBL,EAAS,CAC9B,IAAIc,EAASd,EAAQ,OACrB,IAAK,CAACc,GAAU,CAACA,EAAO,cAAgBd,EAAQ,QAAQ,eAAgB,CACtE,GAAI,CACFc,EAAS,SAAS,cAAcd,EAAQ,OAAO,cAAc,CAC/D,MAAQ,CACNc,EAAS,IACX,CACAd,EAAQ,OAASc,GAAU,IAC7B,CACA,GAAI,CAACA,GAAU,CAACA,EAAO,YAAa,MAAO,GAC3C,IAAMC,EAAOD,EAAO,sBAAsB,EAC1C,OAAOC,EAAK,MAAQ,GAAKA,EAAK,OAAS,CACzC,CAUA,kBAAkBf,EAASgB,EAAGC,EAAG,CAI/B,GAHI,OAAO,SAAS,mBAAsB,YAGtCD,EAAI,GAAKC,EAAI,GAAKD,GAAK,OAAO,YAAcC,GAAK,OAAO,YAC1D,MAAO,GAIT,IAAMC,EAFQ,SAAS,kBAAkBF,EAAGC,CAAC,EAE3B,KACfE,GAAOA,EAAG,QAAQ,YAAY,IAAMC,EAAS,YAAY,CAC5D,EACA,GAAI,CAACF,EAAK,MAAO,GAEjB,IAAMJ,EAASd,EAAQ,QAAQ,YAAcA,EAAQ,OAAS,KAG9D,GAAIc,IAAWA,EAAO,SAASI,CAAG,GAAKA,EAAI,SAASJ,CAAM,GACxD,MAAO,GAGT,IAAMO,EAAYrB,EAAQ,UAC1B,GAAI,CAACqB,GAAW,YAAa,MAAO,GAEpC,GAAI,CAACA,EAAU,SAASH,CAAG,GAAK,CAACA,EAAI,SAASG,CAAS,EAAG,MAAO,GAOjE,GAAIA,EAAU,SAASH,CAAG,GAAKA,IAAQG,GACrC,QAASF,EAAKD,EAAKC,GAAMA,IAAOE,EAAWF,EAAKA,EAAG,cACjD,GAAI,KAAK,qBAAqBA,EAAIL,CAAM,EAAG,MAAO,GAGtD,MAAO,EACT,CAQA,qBAAqBK,EAAIL,EAAQ,CAC/B,GAAIA,GAAUK,EAAG,SAASL,CAAM,EAAG,MAAO,GAC1C,GAAIK,EAAG,UAAU,8CAA8C,EAC7D,MAAO,GAET,GAAI,iBAAiBA,CAAE,EAAE,WAAa,QAAS,MAAO,GACtD,IAAMJ,EAAOI,EAAG,sBAAsB,EACtC,OACEJ,EAAK,OAAS,OAAO,WAAa,IAClCA,EAAK,QAAU,OAAO,YAAc,EAExC,CAcA,oBAAoBf,EAASC,EAAQ,CAAE,eAAAqB,EAAiB,EAAK,EAAI,CAAC,EAAG,CAGnE,GAAItB,EAAQ,SAAW,WACrB,MAAO,CAAE,KAAM,UAAW,EAG5B,IAAIuB,EAAe,KAAK,6BAA6BvB,EAASC,CAAM,EAIpE,GAHIsB,GAAgB,CAAC,KAAK,uBAAuBvB,CAAO,IACtDuB,EAAe,MAEb,CAACA,EAGH,OAAOvB,EAAQ,UAAY,CAAE,KAAM,QAAS,EAAI,CAAE,KAAM,MAAO,EAIjE,IAAMwB,EAAeC,EAAc,EAC7BC,EACJH,EAAa,cAAgBA,EAAa,UAAYC,EAClDG,EACJJ,EAAa,aAAeA,EAAa,UAAYC,EAOvD,OAHIF,IACFtB,EAAQ,UAAY,KAAK,kBAAkBA,EAAS0B,EAAWC,CAAS,GAEtE3B,EAAQ,UACH,CAAE,KAAM,QAAS,EAGnB,CACL,KAAM,UACN,UAAA0B,EACA,UAAAC,EACA,UAAWJ,EAAa,UACxB,UAAWA,EAAa,SAC1B,CACF,CAYA,kBAAkBvB,EAASC,EAAQ2B,EAAO,CACxC,GAAIA,EAAM,OAAS,WACjB,OAAI3B,IAAQA,EAAO,MAAM,QAAU,QAC5B,GAET,GAAI2B,EAAM,OAAS,OAAQ,MAAO,GAElC,IAAMC,EAAY7B,EAAQ,SAAW,GACrC,OAAI4B,EAAM,OAAS,UACjB5B,EAAQ,OAAS,GACjBC,EAAO,MAAM,QAAU,OAGvB,KAAK,KAAK,eAAeD,CAAO,EACzB,CAAC6B,IAGV7B,EAAQ,OAAS,GACjBC,EAAO,MAAM,QAAU,GACvBA,EAAO,MAAM,KAAO,GAAG2B,EAAM,SAAS,KACtC3B,EAAO,MAAM,IAAM,GAAG2B,EAAM,SAAS,KACrC3B,EAAO,MAAM,UAAY,wBACzBA,EAAO,MAAM,SAAW,WAExBD,EAAQ,UAAY4B,EAAM,UAC1B5B,EAAQ,UAAY4B,EAAM,UACnBC,EACT,CAGA,eAAe7B,EAASC,EAAS,KAAK,QAAQ,IAAI,OAAOD,EAAQ,EAAE,CAAC,EAAG,CACrE,GAAI,CAACC,EAAQ,OACb,IAAM2B,EAAQ,KAAK,oBAAoB5B,EAASC,CAAM,EACtC,KAAK,kBAAkBD,EAASC,EAAQ2B,CAAK,GAChD,KAAK,KAAK,iBAAiB,CAC1C,CAOA,qBAAsB,CACpB,IAAME,EAAM,KAAK,IAAI,EACfR,EACJQ,EAAM,KAAK,oBAAsBpC,GAC/B4B,EACF,KAAK,mBAAqBQ,EAI1B,KAAK,0BAA0B,EAGjC,IAAMC,EAAQ,CAAC,EACf,QAAW/B,KAAW,KAAK,KAAK,YAAY,EAAG,CAC7C,IAAMC,EAAS,KAAK,QAAQ,IAAI,OAAOD,EAAQ,EAAE,CAAC,EAC7CC,GACL8B,EAAM,KAAK,CACT/B,EACAC,EACA,KAAK,oBAAoBD,EAASC,EAAQ,CAAE,eAAAqB,CAAe,CAAC,CAC9D,CAAC,CACH,CAEA,IAAIU,EAAa,GACjB,OAAW,CAAChC,EAASC,EAAQ2B,CAAK,IAAKG,EACjC,KAAK,kBAAkB/B,EAASC,EAAQ2B,CAAK,IAAGI,EAAa,IAE/DA,GAAY,KAAK,KAAK,iBAAiB,CAC7C,CAEA,2BAA4B,CACtB,KAAK,yBACP,aAAa,KAAK,uBAAuB,EAE3C,KAAK,wBAA0B,WAAW,IAAM,CAC9C,KAAK,wBAA0B,KAC/B,KAAK,eAAe,CACtB,EAAGtC,EAAqB,CAC1B,CAGA,gBAAiB,CACX,KAAK,cACT,KAAK,YAAc,sBAAsB,IAAM,CAC7C,KAAK,YAAc,KACf,KAAK,SACP,KAAK,oBAAoB,EAI3B,KAAK,KAAK,YAAY,CACxB,CAAC,EACH,CAOA,qBAAqBM,EAASC,EAAQ,CACpC,GAAI,CAAC,OAAO,eAAgB,CAC1B,QAAQ,KACN,mEACF,EACA,MACF,CAEA,IAAMG,EAAW,IAAI,eAAgB6B,GAAY,CAC/C,GAAK,KAAK,QAEV,QAAWC,KAASD,EAEdC,EAAM,SAAWlC,EAAQ,WAC3B,KAAK,eAAeA,EAASC,CAAM,CAGzC,CAAC,EAGDG,EAAS,QAAQJ,EAAQ,SAAS,EAGlC,KAAK,gBAAgB,IAAI,OAAOA,EAAQ,EAAE,EAAG,CAC3C,OAAAC,EACA,SAAAG,EACA,UAAWJ,EAAQ,SACrB,CAAC,CACH,CAEA,sBAAsBmC,EAAW,CAI/B,IAAMC,EAAM,OAAOD,CAAS,EAC5B,GAAI,KAAK,gBAAgB,IAAIC,CAAG,EAAG,CACjC,GAAM,CAAE,OAAAnC,EAAQ,SAAAG,CAAS,EAAI,KAAK,gBAAgB,IAAIgC,CAAG,EACrDhC,GACFA,EAAS,WAAW,EAElBH,GAAUA,EAAO,YACnBA,EAAO,WAAW,YAAYA,CAAM,EAEtC,KAAK,gBAAgB,OAAOmC,CAAG,CACjC,CACF,CAcA,qBAAqBpC,EAAS,CAC5B,IAAMiB,EAAI,KAAK,iBAAiBjB,CAAO,EACnCiB,GAAK,MACT,OAAO,SAAS,CACd,IAAK,KAAK,IAAI,EAAG,OAAO,QAAUA,EAAI,OAAO,YAAc,CAAC,CAC9D,CAAC,CACH,CAeA,iBAAiBjB,EAAS,CACxB,IAAMqB,EAAYrB,EAAQ,UAC1B,GAAI,CAACqB,GAAW,YAAa,OAAO,KACpC,IAAMN,EAAOM,EAAU,sBAAsB,EAC7C,GAAIN,EAAK,QAAU,EAAG,OAAO,KAE7B,IAAMsB,EAAU1C,GAAWK,EAAQ,UAAYe,EAAK,OAAQA,EAAK,MAAM,EACvE,OAAOA,EAAK,IAAMsB,EAAUZ,EAAc,CAC5C,CAGA,SAAU,CAGJ,KAAK,cACP,qBAAqB,KAAK,WAAW,EACrC,KAAK,YAAc,MAEjB,KAAK,0BACP,aAAa,KAAK,uBAAuB,EACzC,KAAK,wBAA0B,MAE7B,KAAK,iBACP,OAAO,oBAAoB,SAAU,KAAK,cAAc,EACxD,KAAK,eAAiB,MAEpB,KAAK,iBACP,OAAO,oBAAoB,SAAU,KAAK,eAAgB,CACxD,QAAS,EACX,CAAC,EACD,KAAK,eAAiB,MAEpB,KAAK,eACP,OAAO,oBAAoB,OAAQ,KAAK,YAAY,EACpD,KAAK,aAAe,MAElB,KAAK,0BACP,KAAK,wBAAwB,WAAW,EACxC,KAAK,wBAA0B,MAEjC,KAAK,MAAM,CACb,CACF,EC3iBA,IAAMa,GAAc,CAACC,EAAKC,IACxB,IAAI,KAAK,eAAeA,EAAQ,CAC9B,MAAO,QACP,IAAK,UACL,KAAM,UACN,OAAQ,SACV,CAAC,EAAE,OAAO,IAAI,KAAKD,CAAG,CAAC,EAKnBE,GAAQ,CACZ,OAASC,GAAY,CAACA,EAAQ,YAAcC,GAAMC,EAAcD,EAAGD,CAAO,CAAC,EAC3E,KAAOA,GAAY,CAACA,EAAQ,UAAYC,GAAME,EAAYF,EAAGD,CAAO,CAAC,EACrE,SAAWA,GAAY,CACrBA,EAAQ,cACPC,GAAMG,EAAgBH,EAAGD,CAAO,CACnC,CACF,EAEMK,GAAY,CAACC,EAAOC,EAAOP,IAAY,CAC3C,IAAMQ,EAAOT,GAAMO,CAAK,EACxB,GAAI,CAACE,EAAM,MAAO,GAClB,GAAM,CAACC,EAAMC,CAAO,EAAIF,EAAKR,CAAO,EACpC,MAAO,GAAGS,CAAI,KAAKC,EAAQH,EAAM,MAAQ,IAAI,CAAC,WAAMG,EAAQH,EAAM,IAAM,IAAI,CAAC,EAC/E,EAEMI,GAAW,CAACJ,EAAOP,IAAY,CACnC,OAAQO,EAAM,KAAM,CAClB,IAAK,UACH,OAAOP,EAAQ,aACjB,IAAK,SACH,OAAOA,EAAQ,YACjB,IAAK,SACH,OAAOK,GAAU,SAAUE,EAAOP,CAAO,EAC3C,IAAK,aAGH,OAAOO,EAAM,QAAU,OACnBP,EAAQ,iBACRK,GAAUE,EAAM,MAAOA,EAAOP,CAAO,EAC3C,QACE,MAAO,EACX,CACF,EAEMY,GAAW,CAACL,EAAOP,EAASF,IAAW,CAC3C,IAAMe,EAAM,SAAS,cAAc,IAAI,EACvCA,EAAI,UAAYC,EAAQ,UAExB,IAAMC,EAAS,SAAS,cAAc,MAAM,EAC5CA,EAAO,UAAYD,EAAQ,aAC3BC,EAAO,YAAcJ,GAASJ,EAAOP,CAAO,EAI5C,IAAMgB,EAAQ,SAAS,cAAc,MAAM,EAC3CA,EAAM,UAAYF,EAAQ,YAC1BE,EAAM,YAAcT,EAAM,OAAO,MAAQP,EAAQ,UAEjD,IAAMiB,EAAO,SAAS,cAAc,MAAM,EAC1C,OAAAA,EAAK,UAAYH,EAAQ,WACzBG,EAAK,SAAWV,EAAM,GACtBU,EAAK,YAAcrB,GAAYW,EAAM,GAAIT,CAAM,EAE/Ce,EAAI,OAAOE,EAAQC,EAAOC,CAAI,EACvBJ,CACT,EAOMK,GAAmB,CAACC,EAASnB,EAASF,IAAW,CACrD,IAAMsB,EAAaC,EAAcF,CAAO,EAAE,OAAQG,GAAMA,EAAE,UAAU,EACpE,GAAIF,EAAW,SAAW,EAAG,OAAO,KAEpC,IAAMG,EAAU,SAAS,cAAc,KAAK,EAC5CA,EAAQ,UAAYT,EAAQ,kBAC5BS,EAAQ,QAAQ,iBAAmB,GAEnC,IAAMC,EAAU,SAAS,cAAc,IAAI,EAC3CA,EAAQ,UAAYV,EAAQ,cAC5BU,EAAQ,YAAcxB,EAAQ,yBAC9BuB,EAAQ,YAAYC,CAAO,EAE3B,IAAMC,EAAO,SAAS,cAAc,IAAI,EACxCA,EAAK,UAAYX,EAAQ,WACzB,QAAWY,KAAcN,EAAY,CACnC,IAAMO,EAAO,SAAS,cAAc,IAAI,EACxCA,EAAK,UAAYb,EAAQ,UAEzB,IAAMC,EAAS,SAAS,cAAc,MAAM,EAC5CA,EAAO,UAAYD,EAAQ,aAC3BC,EAAO,YAAca,EACnB5B,EAAQ,wBACR6B,EAAeH,EAAW,GAAI1B,CAAO,GAAK,QAC5C,EAEA,IAAMiB,EAAO,SAAS,cAAc,MAAM,EAC1CA,EAAK,UAAYH,EAAQ,WACzBG,EAAK,SAAWS,EAAW,WAC3BT,EAAK,YAAcrB,GAAY8B,EAAW,WAAY5B,CAAM,EAE5D6B,EAAK,OAAOZ,EAAQE,CAAI,EACxBQ,EAAK,YAAYE,CAAI,CACvB,CACA,OAAAJ,EAAQ,YAAYE,CAAI,EACjBF,CACT,EAaO,SAASO,GAAiBX,EAAS,CAAE,QAAAnB,EAAS,OAAAF,EAAQ,KAAAiC,EAAM,SAAAC,CAAS,EAAG,CAC7E,IAAMC,EAAUd,GAAS,QACzB,GAAI,CAAC,MAAM,QAAQc,CAAO,GAAKA,EAAQ,SAAW,EAAG,OAAO,KAE5D,IAAMC,EAAU,SAAS,cAAc,KAAK,EAC5CA,EAAQ,UAAYpB,EAAQ,YAE5B,IAAMqB,EAAS,SAAS,cAAc,QAAQ,EAC9CA,EAAO,KAAO,SACdA,EAAO,UAAYrB,EAAQ,aAC3BqB,EAAO,aAAa,gBAAiB,OAAOJ,CAAI,CAAC,EACjDI,EAAO,YAAcP,EACnB5B,EAAQ,oBACRiC,EAAQ,MACV,EAEA,IAAMG,EAAO,SAAS,cAAc,KAAK,EACzCA,EAAK,UAAYtB,EAAQ,WACzBsB,EAAK,OAAS,CAACL,EAEf,IAAMN,EAAO,SAAS,cAAc,IAAI,EACxCA,EAAK,UAAYX,EAAQ,WACzBW,EAAK,aAAa,aAAczB,EAAQ,eAAe,EAGvD,QAAWO,IAAS,CAAC,GAAG0B,CAAO,EAAE,QAAQ,EACvCR,EAAK,YAAYb,GAASL,EAAOP,EAASF,CAAM,CAAC,EAEnDsC,EAAK,YAAYX,CAAI,EAErB,IAAMY,EAAcnB,GAAiBC,EAASnB,EAASF,CAAM,EAC7D,OAAIuC,GAAaD,EAAK,YAAYC,CAAW,EAE7CF,EAAO,iBAAiB,QAAS,IAAM,CACrC,IAAMG,EAAOH,EAAO,aAAa,eAAe,IAAM,OACtDA,EAAO,aAAa,gBAAiB,OAAOG,CAAI,CAAC,EACjDF,EAAK,OAAS,CAACE,EACfN,IAAWM,CAAI,CACjB,CAAC,EAEDJ,EAAQ,OAAOC,EAAQC,CAAI,EACpBF,CACT,CCvKO,IAAMK,EAAQ,QAEfC,GAAU,KAEVC,GAAY,CAACC,EAAMC,IAAa,CACpC,IAAMC,EAAM,CAAC,EACb,QAAWC,KAAOH,EAAME,EAAIC,CAAG,EAAI,EACnC,OAAIF,IAAUC,EAAID,CAAQ,EAAI,GACvBC,CACT,EAEME,GAAUC,GAAW,CACzB,GAAIA,EAAO,SAAW,EAAG,OAAO,KAChC,IAAMC,EAAS,KAAK,MAAMD,EAAO,OAAS,CAAC,EAC3C,OAAOA,EAAO,OAAS,EACnBA,EAAOC,CAAM,GACZD,EAAOC,EAAS,CAAC,EAAID,EAAOC,CAAM,GAAK,CAC9C,EAMO,SAASC,GAAeC,EAAU,CACvC,IAAMC,EAAO,MAAM,QAAQD,CAAQ,EAAIA,EAAW,CAAC,EAE7CE,EAAWX,GAAUY,CAAQ,EAC7BC,EAASb,GAAUc,EAAehB,CAAK,EACvCiB,EAAaf,GAAUgB,EAAYlB,CAAK,EACxCmB,EAAS,IAAI,IACbC,EAAY,CAAC,EACfC,EAAgB,EAEpB,QAAWC,KAAWV,EAAM,CAC1B,IAAMW,EAAST,EAAS,SAASQ,EAAQ,MAAM,EAAIA,EAAQ,OAAS,OACpET,EAASU,CAAM,IAEfR,EAAOC,EAAc,SAASM,EAAQ,IAAI,EAAIA,EAAQ,KAAOtB,CAAK,IAClEiB,EACEC,EAAW,SAASI,EAAQ,QAAQ,EAAIA,EAAQ,SAAWtB,CAC7D,IAKA,IAAMwB,EAAM,OAAOF,EAAQ,WAAa,EAAE,EAAE,MAAM,EAAG,EAAE,EACnDE,GAAKL,EAAO,IAAIK,GAAML,EAAO,IAAIK,CAAG,GAAK,GAAK,CAAC,EAEnD,IAAMC,EAAUC,GAAoBJ,CAAO,EACvCG,IAAY,MAAML,EAAU,KAAKK,CAAO,EACxCE,EAAcL,CAAO,EAAE,OAAS,GAAGD,GACzC,CAEAD,EAAU,KAAK,CAACQ,EAAGC,IAAMD,EAAIC,CAAC,EAC9B,IAAMC,EAAQV,EAAU,OAAO,CAACW,EAAKC,IAAOD,EAAMC,EAAI,CAAC,EAEvD,MAAO,CACL,MAAOpB,EAAK,OAIZ,SAEIC,EAEJ,OAEIE,EAEJ,WAEIE,EAEJ,SAAU,CAAC,GAAGE,EAAO,QAAQ,CAAC,EAC3B,KAAK,CAAC,CAACS,CAAC,EAAG,CAACC,CAAC,IAAOD,EAAIC,EAAI,GAAK,CAAE,EACnC,IAAI,CAAC,CAACI,EAAMC,CAAK,KAAO,CAAE,KAAAD,EAAM,MAAAC,CAAM,EAAE,EAC3C,WAAY,CACV,cAAed,EAAU,OAIzB,UAAWA,EAAU,OAASU,EAAQV,EAAU,OAAS,KACzD,SAAUb,GAAOa,CAAS,EAC1B,cAAAC,CACF,CACF,CACF,CAGO,IAAMc,GAAWH,GACtBA,GAAO,KAA2B,KAAO,KAAK,MAAOA,EAAK/B,GAAW,EAAE,EAAI,GCxE7E,IAAMmC,GAAc,yBAEdC,GAAc,IACdC,GAAe,GACfC,GAAS,6BAETC,EAAK,CAACC,EAAKC,EAAWC,IAAS,CACnC,IAAMC,EAAO,SAAS,cAAcH,CAAG,EACvC,OAAIC,IAAWE,EAAK,UAAYF,GAC5BC,IAAS,SAAWC,EAAK,YAAc,OAAOD,CAAI,GAC/CC,CACT,EAEMC,GAAQ,CAACJ,EAAKK,EAAQ,CAAC,IAAM,CACjC,IAAMF,EAAO,SAAS,gBAAgBL,GAAQE,CAAG,EACjD,OAAW,CAACM,EAAMC,CAAK,IAAK,OAAO,QAAQF,CAAK,EAC9CF,EAAK,aAAaG,EAAM,OAAOC,CAAK,CAAC,EAEvC,OAAOJ,CACT,EAEMK,GAAO,CAACC,EAAOF,IAAU,CAC7B,IAAMG,EAAMX,EAAG,MAAOY,EAAQ,YAAY,EAC1C,OAAAD,EAAI,YAAYX,EAAG,OAAQY,EAAQ,mBAAoBJ,CAAK,CAAC,EAC7DG,EAAI,YAAYX,EAAG,OAAQY,EAAQ,mBAAoBF,CAAK,CAAC,EACtDC,CACT,EAeME,GAAW,CAACN,EAAMO,EAASC,IAAY,CAC3C,IAAMC,EAAQhB,EAAG,MAAOY,EAAQ,aAAa,EAC7CI,EAAM,QAAQ,aAAeT,EAC7BS,EAAM,YAAYhB,EAAG,KAAMY,EAAQ,gBAAiBE,CAAO,CAAC,EAE5D,IAAMG,EAAM,KAAK,IAAI,EAAG,GAAGF,EAAQ,IAAKG,GAAUA,EAAM,KAAK,CAAC,EAC9D,OAAW,CAAE,MAAAR,EAAO,MAAAS,EAAO,MAAAC,CAAM,IAAKL,EAAS,CAC7C,IAAMM,EAAMrB,EAAG,MAAOY,EAAQ,WAAW,EACzCS,EAAI,QAAQ,WAAa,GAEzBA,EAAI,YAAYrB,EAAG,OAAQY,EAAQ,kBAAmBF,CAAK,CAAC,EAE5D,IAAMY,EAAQtB,EAAG,MAAOY,EAAQ,aAAa,EACvCW,EAAMvB,EAAG,MAAOY,EAAQ,WAAW,EACzCW,EAAI,QAAQ,WAAa,GACzBA,EAAI,MAAM,MAAQ,GAAG,KAAK,MAAOJ,EAAQF,EAAO,GAAG,CAAC,IACpDM,EAAI,MAAM,WAAaH,EACvBE,EAAM,YAAYC,CAAG,EACrBF,EAAI,YAAYC,CAAK,EAErBD,EAAI,YAAYrB,EAAG,OAAQY,EAAQ,kBAAmBO,CAAK,CAAC,EAC5DH,EAAM,YAAYK,CAAG,CACvB,CACA,OAAOL,CACT,EAOMQ,GAAa,CAACC,EAAUC,IAAY,CACxC,IAAMV,EAAQhB,EAAG,MAAOY,EAAQ,aAAa,EAC7CI,EAAM,QAAQ,aAAe,WAC7BA,EAAM,YAAYhB,EAAG,KAAMY,EAAQ,gBAAiBc,EAAQ,eAAe,CAAC,EAE5E,IAAMT,EAAM,KAAK,IAAI,EAAG,GAAGQ,EAAS,IAAI,CAAC,CAAE,MAAAN,CAAM,IAAMA,CAAK,CAAC,EACvDQ,EAAMtB,GAAM,MAAO,CACvB,MAAOO,EAAQ,cACf,QAAS,OAAOf,EAAW,IAAIC,EAAY,GAC3C,oBAAqB,OACrB,KAAM,MACN,aAAc,GAAG4B,EAAQ,eAAe,KAAKD,EAC1C,IAAI,CAAC,CAAE,KAAAG,EAAM,MAAAT,CAAM,IAAM,GAAGS,CAAI,IAAIT,CAAK,EAAE,EAC3C,KAAK,IAAI,CAAC,EACf,CAAC,EAEKU,EAAOhC,GAAc4B,EAAS,OAC9BK,EAAW,KAAK,IAAI,EAAG,KAAK,IAAID,EAAO,EAAG,EAAE,CAAC,EACnDJ,EAAS,QAAQ,CAAC,CAAE,KAAAG,EAAM,MAAAT,CAAM,EAAGY,IAAU,CAC3C,IAAMC,EAAS,KAAK,IAAI,EAAIb,EAAQF,GAAQnB,GAAe,EAAE,EACvDmC,EAAO5B,GAAM,OAAQ,CACzB,EAAG0B,EAAQF,GAAQA,EAAOC,GAAY,EACtC,EAAGhC,GAAekC,EAClB,MAAOF,EACP,OAAAE,EACA,GAAI,CACN,CAAC,EACDC,EAAK,YACH,OAAO,OAAO5B,GAAM,OAAO,EAAG,CAAE,YAAa,GAAGuB,CAAI,KAAKT,CAAK,EAAG,CAAC,CACpE,EACAQ,EAAI,YAAYM,CAAI,CACtB,CAAC,EACDjB,EAAM,YAAYW,CAAG,EAIrB,IAAMO,EAAOlC,EAAG,MAAOY,EAAQ,YAAY,EAC3C,OAAAsB,EAAK,YAAYlC,EAAG,OAAQ,KAAMyB,EAAS,CAAC,EAAE,IAAI,CAAC,EAC/CA,EAAS,OAAS,GACpBS,EAAK,YAAYlC,EAAG,OAAQ,KAAMyB,EAASA,EAAS,OAAS,CAAC,EAAE,IAAI,CAAC,EAEvET,EAAM,YAAYkB,CAAI,EAEflB,CACT,EAEMmB,GAAY,CAACT,EAASU,IAAa,CACvC,IAAMb,EAAMvB,EAAG,MAAOY,EAAQ,eAAe,EAC7CW,EAAI,aAAa,aAAcG,EAAQ,kBAAkB,EAEzD,IAAMW,EAAS,CAACC,EAAK5B,EAAO6B,IAAY,CACtC,IAAMC,EAAMxC,EAAG,SAAUY,EAAQ,mBAAoBF,CAAK,EAC1D,OAAA8B,EAAI,KAAO,SACXA,EAAI,QAAQ,OAASF,EACrBE,EAAI,iBAAiB,QAASD,CAAO,EAC9BC,CACT,EAEA,OAAAjB,EAAI,YACFc,EAAO,WAAYX,EAAQ,sBAAuBU,EAAS,gBAAgB,CAC7E,EACAb,EAAI,YACFc,EAAO,UAAWX,EAAQ,qBAAsBU,EAAS,eAAe,CAC1E,EACAb,EAAI,YAAYc,EAAO,QAASX,EAAQ,aAAcU,EAAS,OAAO,CAAC,EAChEb,CACT,EAaO,SAASkB,GAAkBC,EAASC,EAAM,CAC/C,GAAM,CAAE,QAAAjB,CAAQ,EAAIiB,EACdC,EAAO5C,EAAG,MAAOY,EAAQ,YAAY,EAE3C,GAAI8B,EAAQ,QAAU,EAGpB,OAAAE,EAAK,YAAY5C,EAAG,IAAKY,EAAQ,cAAec,EAAQ,YAAY,CAAC,EAC9DkB,EAGT,IAAMC,EAAYC,GAAQA,IAAO,KAAO,SAAMC,EAAeD,EAAIpB,CAAO,EAElEsB,EAAQhD,EAAG,MAAOY,EAAQ,aAAa,EAC7C,OAAAoC,EAAM,YAAYvC,GAAKiB,EAAQ,aAAcgB,EAAQ,KAAK,CAAC,EAC3DM,EAAM,YACJvC,GAAKiB,EAAQ,eAAgBgB,EAAQ,WAAW,aAAa,CAC/D,EACAM,EAAM,YACJvC,GAAKiB,EAAQ,gBAAiBgB,EAAQ,WAAW,aAAa,CAChE,EACAM,EAAM,YACJvC,GACEiB,EAAQ,yBACRmB,EAASH,EAAQ,WAAW,SAAS,CACvC,CACF,EACAM,EAAM,YACJvC,GAAKiB,EAAQ,wBAAyBmB,EAASH,EAAQ,WAAW,QAAQ,CAAC,CAC7E,EACAE,EAAK,YAAYI,CAAK,EAEtBJ,EAAK,YACH/B,GACE,SACAa,EAAQ,gBACRuB,EAAS,IAAKX,IAAS,CACrB,MAAOY,EAAcZ,EAAKZ,CAAO,EACjC,MAAOgB,EAAQ,SAASJ,CAAG,EAC3B,MAAOa,EAAcb,CAAG,CAC1B,EAAE,CACJ,CACF,EACAM,EAAK,YACH/B,GAAS,OAAQa,EAAQ,cAAe,CACtC,GAAG0B,EAAc,IAAKd,IAAS,CAC7B,MAAOe,EAAYf,EAAKZ,CAAO,EAC/B,MAAOgB,EAAQ,OAAOJ,CAAG,EACzB,MAAOgB,EAAYhB,CAAG,CACxB,EAAE,EACF,CACE,MAAOZ,EAAQ,MACf,MAAOgB,EAAQ,OAAOa,CAAK,EAC3B,MAAO3D,EACT,CACF,CAAC,CACH,EACAgD,EAAK,YACH/B,GAAS,WAAYa,EAAQ,kBAAmB,CAC9C,GAAG8B,EAAW,IAAKlB,IAAS,CAC1B,MAAOmB,EAAgBnB,EAAKZ,CAAO,EACnC,MAAOgB,EAAQ,WAAWJ,CAAG,EAC7B,MAAOoB,EAAgBpB,CAAG,CAC5B,EAAE,EACF,CACE,MAAOZ,EAAQ,MACf,MAAOgB,EAAQ,WAAWa,CAAK,EAC/B,MAAO3D,EACT,CACF,CAAC,CACH,EAEI8C,EAAQ,SAAS,QACnBE,EAAK,YAAYpB,GAAWkB,EAAQ,SAAUhB,CAAO,CAAC,EAGxDkB,EAAK,YAAYT,GAAUT,EAASiB,CAAI,CAAC,EAClCC,CACT,CC9NA,IAAMe,GAAmB,+LACnBC,GAAe,+LACfC,GAAiB,8LAEVC,GAAN,KAAgB,CAWrB,YAAY,CACV,WAAAC,EACA,QAAAC,EACA,OAAAC,EACA,YAAAC,EACA,YAAAC,EACA,UAAAC,EACA,QAAAC,EAAU,CAAC,CACb,EAAG,CACD,KAAK,WAAaN,EAClB,KAAK,QAAUC,EACf,KAAK,OAASC,EACd,KAAK,YAAcC,EACnB,KAAK,YAAcC,EACnB,KAAK,UAAYC,EAGjB,KAAK,QAAUC,EACf,KAAK,WAAa,OAClB,KAAK,aAAe,MACpB,KAAK,WAAa,MAClB,KAAK,eAAiB,MACtB,KAAK,SAAW,KAQhB,KAAK,gBAAkB,GAOvB,KAAK,cAAgB,GAMrB,KAAK,YAAc,GAWnB,KAAK,QAAU,KAEf,KAAK,OAAS,KASd,KAAK,cAAgB,IAAI,IAEzB,KAAK,GAAK,KAEV,KAAK,eAAiB,KAEtB,KAAK,gBAAkB,KAKvB,KAAK,WAAa,IACpB,CAEA,QAAS,CACP,MAAO,EAAQ,KAAK,EACtB,CAEA,MAAO,CACD,KAAK,KACT,KAAK,GAAK,SAAS,cAAc,KAAK,EACtC,KAAK,GAAG,UAAYC,EAAQ,YAC5B,KAAK,GAAG,aAAa,OAAQ,QAAQ,EACrC,KAAK,GAAG,aAAa,aAAc,KAAK,QAAQ,cAAc,EAG9D,KAAK,GAAG,aAAa,WAAY,IAAI,EACrC,KAAK,WAAW,YAAY,KAAK,EAAE,EACnC,KAAK,OAAO,EACZ,KAAK,GAAG,MAAM,EAChB,CAEA,OAAQ,CACN,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,IAAI,EAC1B,KAAK,IAAI,OAAO,EAChB,KAAK,GAAK,KACV,KAAK,cAAc,MAAM,EACzB,KAAK,SAAW,KAGhB,KAAK,QAAU,KACf,KAAK,OAAS,IAChB,CAEA,SAAU,CACJ,KAAK,IAAI,KAAK,OAAO,CAC3B,CAGA,eAAgB,CACd,KAAK,WAAa,OAClB,KAAK,aAAe,MACpB,KAAK,WAAa,MAClB,KAAK,eAAiB,MACtB,KAAK,OAAO,CACd,CASA,WAAWC,EAAM,CACf,KAAK,OAASA,EACd,KAAK,QAAQ,CACf,CAEA,aAAc,CACP,KAAK,SACV,KAAK,OAAS,KACd,KAAK,QAAQ,EACf,CAGA,SAAU,CACR,OAAK,KAAK,QACH,KAAK,QAAQ,MAAM,KAAK,IAAM,KAAK,qBAAqB,EAAE,KAAK,EAD5C,EAE5B,CAEA,sBAAuB,CACrB,GAAI,CAAC,KAAK,QAAS,MAAO,GAC1B,IAAMC,EAAU,KAAK,YAAY,EAAE,KAAMC,GACvCC,EAAOD,EAAE,GAAI,KAAK,QAAQ,SAAS,CACrC,EACA,OAAKD,EACD,KAAK,QAAQ,SAAW,KAAaA,EAAQ,MAAQ,IAC1CA,EAAQ,SAAW,CAAC,GAAG,KAAMG,GAC1CD,EAAOC,EAAE,GAAI,KAAK,QAAQ,OAAO,CACnC,GACc,MAAQ,GALD,EAMvB,CASA,MAAM,eAAgB,CACpB,GAAI,CAAC,KAAK,QAAS,MAAO,GAC1B,GAAI,KAAK,QAAQ,EAAG,CAClB,IAAMC,EAA2B,KAAK,IAAM,KAAK,WACjD,GAAI,CAAE,MAAMC,GAAeD,EAAM,KAAK,OAAO,EAAI,MAAO,EAC1D,CACA,YAAK,QAAU,KACR,EACT,CAQA,iBAAkB,CAChB,MAAO,CACL,MAAO,KAAK,QAAQ,MACpB,QAAUL,GAAS,CACjB,KAAK,QAAQ,MAAQA,CACvB,EACA,OAASA,GAAS,CAChB,GAAM,CAAE,UAAAO,EAAW,QAAAC,CAAQ,EAAI,KAAK,QAChCA,GAAW,KAAM,KAAK,UAAU,cAAcD,EAAWP,CAAI,EAC5D,KAAK,UAAU,YAAYO,EAAWC,EAASR,CAAI,EACxD,KAAK,QAAU,KACf,KAAK,OAAO,CACd,EACA,SAAU,SAAY,CAChB,MAAM,KAAK,cAAc,GAAG,KAAK,OAAO,CAC9C,CACF,CACF,CAEA,cAAe,CACb,IAAMS,EAAW,KAAK,gBAAgB,EACtC,OAAOC,GAAmB,CACxB,MAAOD,EAAS,MAChB,QAAS,KAAK,QACd,QAASA,EAAS,QAClB,OAAQA,EAAS,OACjB,SAAUA,EAAS,QACrB,CAAC,CACH,CAGA,MAAM,aAAaF,EAAWC,EAAU,KAAM,CACtC,MAAM,KAAK,cAAc,IAC/B,KAAK,SAAWD,EAChB,KAAK,QAAU,CAAE,UAAAA,EAAW,QAAAC,EAAS,MAAO,EAAG,EAC/C,KAAK,QAAQ,MAAQ,KAAK,qBAAqB,EAC/C,KAAK,OAAO,EACd,CAQA,WAAWP,EAAS,CAClB,OACEA,EAAQ,cAAgB,YACxBA,EAAQ,QACRA,EAAQ,SAAW,WAEZ,KAGP,KAAK,WAAW,cAAcU,GAAeV,EAAQ,EAAE,CAAC,CAE5D,CAEA,WAAWA,EAAS,CAClB,KAAK,gBAAgB,EACrB,IAAMW,EAAS,KAAK,WAAWX,CAAO,EACjCW,IACLA,EAAO,UAAU,IAAIb,EAAQ,SAAS,EACtC,KAAK,eAAiBa,EACxB,CAEA,iBAAkB,CAChB,KAAK,gBAAgB,UAAU,OAAOb,EAAQ,SAAS,EACvD,KAAK,eAAiB,IACxB,CAQA,iBAAiBE,EAAS,CAGxB,GAFA,KAAK,iBAAiB,UAAU,OAAOF,EAAQ,aAAa,EAC5D,KAAK,gBAAkB,KACnB,CAACE,EAAS,OACd,IAAMW,EAAS,KAAK,WAAWX,CAAO,EACjCW,IACLA,EAAO,UAAU,IAAIb,EAAQ,aAAa,EAC1C,KAAK,gBAAkBa,EACzB,CAEA,kBAAmB,CACjB,IAAIC,EAAW,KAAK,YAAY,EAChC,OAAI,KAAK,aAAe,SACtBA,EAAWA,EAAS,OACjBZ,GAAYA,EAAQ,OAAS,KAAK,WACrC,GAEE,KAAK,eAAiB,QAGxBY,EAAWA,EAAS,OACjBZ,IAAaA,EAAQ,QAAU,UAAY,KAAK,YACnD,GAEE,KAAK,aAAe,QACtBY,EAAWA,EAAS,OAAQZ,GAAYA,EAAQ,OAAS,KAAK,UAAU,GAEtE,KAAK,iBAAmB,QAC1BY,EAAWA,EAAS,OACjBZ,GAAYA,EAAQ,WAAa,KAAK,cACzC,GAGK,CACL,GAAGY,EAAS,OAAQZ,GAAYA,EAAQ,SAAW,UAAU,EAC7D,GAAGY,EAAS,OAAQZ,GAAYA,EAAQ,SAAW,UAAU,CAC/D,CACF,CAEA,QAAS,CACP,GAAI,CAAC,KAAK,GAAI,OACd,KAAK,gBAAgB,EACrB,IAAMY,EAAW,KAAK,iBAAiB,EACjCC,EACJ,KAAK,UAAY,KACbD,EAAS,KAAMZ,GAAYE,EAAOF,EAAQ,GAAI,KAAK,QAAQ,CAAC,EAC5D,KAIN,GADA,KAAK,iBAAiBa,CAAM,EACxB,KAAK,YAAa,CACpB,KAAK,cAAc,MAAM,EACzB,KAAK,GAAG,UAAY,GACpB,KAAK,eAAeD,CAAQ,EAC5B,MACF,CACIC,GAIF,KAAK,cAAc,MAAM,EACzB,KAAK,GAAG,UAAY,GACpB,KAAK,cAAcA,EAAQD,CAAQ,IAEnC,KAAK,SAAW,KAChB,KAAK,YAAYA,CAAQ,EAE7B,CAOA,WAAWE,EAAI,CACR,KAAK,IAAI,KAAK,KAAK,EACxB,IAAMd,EAAU,KAAK,YAAY,EAAE,KAAMC,GAAMC,EAAOD,EAAE,GAAIa,CAAE,CAAC,EAC3Dd,GAAS,KAAK,YAAYA,CAAO,CACvC,CAEA,YAAYA,EAAS,CACnB,KAAK,SAAWA,EAAQ,GACpBA,EAAQ,cAAgB,YAAc,CAACA,EAAQ,QACjD,KAAK,UAAU,mBAAmBA,CAAO,EAE3C,KAAK,OAAO,EAIZ,KAAK,UAAU,eAAeA,CAAO,CACvC,CAEA,cAAe,CACb,IAAMe,EAAM,SAAS,cAAc,QAAQ,EAC3C,OAAAA,EAAI,KAAO,SACXA,EAAI,UAAYjB,EAAQ,YACxBiB,EAAI,aAAa,aAAc,KAAK,QAAQ,KAAK,EACjDA,EAAI,UAAY,UAIhBA,EAAI,iBAAiB,QAAS,SAAY,CACpC,KAAK,SAAW,CAAE,MAAM,KAAK,cAAc,GAC/C,KAAK,UAAU,QAAQ,CACzB,CAAC,EACMA,CACT,CAEA,gBAAiB,CACf,IAAMA,EAAM,SAAS,cAAc,QAAQ,EAC3C,OAAAA,EAAI,KAAO,SACXA,EAAI,UAAYjB,EAAQ,kBACxBiB,EAAI,YAAc,KAAK,QAAQ,YAC/BA,EAAI,aAAa,aAAc,KAAK,QAAQ,YAAY,EACxDA,EAAI,iBAAiB,QAAS,SAAY,CACpC,KAAK,SAAW,CAAE,MAAM,KAAK,cAAc,IAC/C,KAAK,YAAc,GACnB,KAAK,SAAW,KAChB,KAAK,OAAO,EACd,CAAC,EACMA,CACT,CAQA,eAAeH,EAAU,CACvB,IAAMI,EAAS,SAAS,cAAc,KAAK,EAC3CA,EAAO,UAAYlB,EAAQ,oBAE3B,IAAMmB,EAAU,SAAS,cAAc,QAAQ,EAC/CA,EAAQ,KAAO,SACfA,EAAQ,UAAYnB,EAAQ,WAC5BmB,EAAQ,UAAY,GAAG9B,EAAgB,SAAS,KAAK,QAAQ,IAAI,UACjE8B,EAAQ,iBAAiB,QAAS,IAAM,CACtC,KAAK,YAAc,GACnB,KAAK,OAAO,CACd,CAAC,EACDD,EAAO,YAAYC,CAAO,EAE1B,IAAMC,EAAM,SAAS,cAAc,KAAK,EACxCA,EAAI,UAAYpB,EAAQ,mBACxBoB,EAAI,YAAY,KAAK,aAAa,CAAC,EACnCF,EAAO,YAAYE,CAAG,EACtB,KAAK,GAAG,YAAYF,CAAM,EAE1B,IAAMG,EAAQ,KAAK,oBAAoB,EACvC,KAAK,GAAG,YACNC,GAAkBC,GAAeT,CAAQ,EAAG,CAC1C,QAAS,KAAK,QACd,OAAQ,KAAK,OACb,iBAAkB,IAAM,KAAK,UAAU,iBAAiBA,CAAQ,EAChE,gBAAiB,IAAM,KAAK,UAAU,gBAAgBA,CAAQ,EAC9D,QAAS,IAAM,KAAK,UAAU,cAAcA,EAAUO,CAAK,CAC7D,CAAC,CACH,CACF,CAEA,YAAYP,EAAU,CAKpB,IAAIU,EAAO,CAAC,GAAG,KAAK,GAAG,QAAQ,EAAE,KAAMC,GACrCA,EAAG,UAAU,SAASzB,EAAQ,UAAU,CAC1C,EACA,GAAI,CAACwB,EAAM,CACT,KAAK,GAAG,UAAY,GACpB,KAAK,cAAc,MAAM,EACzB,IAAMN,EAAS,SAAS,cAAc,KAAK,EAC3CA,EAAO,UAAYlB,EAAQ,aAC3B,KAAK,GAAG,YAAYkB,CAAM,EAC1BM,EAAO,SAAS,cAAc,KAAK,EACnCA,EAAK,UAAYxB,EAAQ,WACzB,KAAK,GAAG,YAAYwB,CAAI,CAC1B,CAIA,IAAMN,EAAS,CAAC,GAAG,KAAK,GAAG,QAAQ,EAAE,KAAMO,GACzCA,EAAG,UAAU,SAASzB,EAAQ,YAAY,CAC5C,EAKM0B,EAAU,SAAS,cAAc,KAAK,EAC5CA,EAAQ,UAAY1B,EAAQ,qBAC5B0B,EAAQ,OAAO,KAAK,eAAe,EAAG,KAAK,aAAa,CAAC,EACzDR,EAAO,gBAAgB,KAAK,aAAa,EAAGQ,CAAO,EAEnD,KAAK,gBAAgBF,EAAMV,CAAQ,CACrC,CAkBA,cAAe,CACb,OAAK,KAAK,aACR,KAAK,WAAaa,GAAkB,CAClC,SAAU,IAAM,KAAK,UAAU,SAAS,EACxC,QAAS,KAAK,QACd,SAAU,CAACC,EAAQC,IAAU,CAC3B,IAAMC,EAAS,KAAK,YAAY,EAAE,KAAM5B,IACrCA,EAAQ,SAAW,CAAC,GAAG,SAAS0B,CAAM,CACzC,EACIE,EACF,KAAK,UAAU,sBAAsBA,EAAO,GAAIF,EAAO,GAAIC,CAAK,EAEhE,KAAK,UAAU,wBAAwBD,EAAO,GAAIC,CAAK,CAE3D,CACF,CAAC,GAEI,KAAK,UACd,CAEA,iBAAiB3B,EAAS,CACxB,OAAO,KAAK,UAAU,CACpBA,EAAQ,KACRA,EAAQ,UAAY,KACpBA,EAAQ,QAAU,OAClBA,EAAQ,MAAQ,KAChBA,EAAQ,UAAY,KACpBA,EAAQ,MAAQ,CAAC,EACjBA,EAAQ,YAAc,KAItBA,EAAQ,SAAS,QAAU,EAC3BA,EAAQ,SAAS,GAAG,EAAE,GAAG,IAAM,KAC/BA,EAAQ,YACRA,EAAQ,SAAW,GACnBA,EAAQ,KACRA,EAAQ,aAAa,QAAU,EAI/B6B,GAAkB7B,CAAO,EAAE,IAAI,CAAC,CAAE,MAAA2B,EAAO,QAAAG,CAAQ,IAAM,CACrDH,EACAG,EAAQ,MACV,CAAC,CACH,CAAC,CACH,CAEA,gBAAgBR,EAAMV,EAAU,CAI9B,QAAWW,IAAM,CAAC,GAAGD,EAAK,QAAQ,GAE9BC,EAAG,UAAU,SAASzB,EAAQ,YAAY,GAC1CyB,EAAG,UAAU,SAASzB,EAAQ,WAAW,IAEzCyB,EAAG,OAAO,EAId,IAAMQ,EAAU,CAAC,EACjB,GAAI,KAAK,OAAQ,CACf,IAAMC,EAAS,SAAS,cAAc,KAAK,EAC3CA,EAAO,UAAYlC,EAAQ,aAC3BkC,EAAO,aAAa,OAAQ,QAAQ,EACpCA,EAAO,YAAc,KAAK,OAC1BD,EAAQ,KAAKC,CAAM,CACrB,CAEA,IAAMC,EAAO,IAAI,IACjB,GAAIrB,EAAS,SAAW,EACtBmB,EAAQ,KAAK,KAAK,iBAAiB,CAAC,MAEpC,SAAW/B,KAAWY,EAAU,CAC9B,IAAMsB,EAAM,OAAOlC,EAAQ,EAAE,EACvBmC,EAAc,KAAK,iBAAiBnC,CAAO,EAC3CoC,EAAU,KAAK,cAAc,IAAIF,CAAG,EACtCG,EAEFD,GACAA,EAAQ,UAAYpC,GACpBoC,EAAQ,cAAgBD,EAExBE,EAAOD,EAAQ,MAEfC,EAAO,KAAK,WAAWrC,EAAS,CAAE,YAAa,EAAK,CAAC,EACrD,KAAK,cAAc,IAAIkC,EAAK,CAAE,QAAAlC,EAAS,YAAAmC,EAAa,KAAAE,CAAK,CAAC,GAE5DJ,EAAK,IAAIC,CAAG,EACZH,EAAQ,KAAKM,CAAI,CACnB,CAGF,QAAWH,IAAO,CAAC,GAAG,KAAK,cAAc,KAAK,CAAC,EACxCD,EAAK,IAAIC,CAAG,GAAG,KAAK,cAAc,OAAOA,CAAG,EAWnD,IALAH,EAAQ,QAAQ,CAACO,EAAMC,IAAU,CAC3BjB,EAAK,SAASiB,CAAK,IAAMD,GAC3BhB,EAAK,aAAagB,EAAMhB,EAAK,SAASiB,CAAK,GAAK,IAAI,CAExD,CAAC,EACMjB,EAAK,SAAS,OAASS,EAAQ,QACpCT,EAAK,iBAAiB,OAAO,CAEjC,CASA,kBAAmB,CACjB,IAAMkB,EAAQ,SAAS,cAAc,KAAK,EAC1CA,EAAM,UAAY1C,EAAQ,YAI1B,IAAM2C,EAAO,SAAS,cAAc,KAAK,EACzCA,EAAK,UAAY3C,EAAQ,iBACzB2C,EAAK,aAAa,cAAe,MAAM,EACvCD,EAAM,YAAYC,CAAI,EAEtB,IAAMC,EAAgB,KAAK,YAAY,EAAE,OAAS,EAE5CC,EAAQ,SAAS,cAAc,KAAK,EAO1C,GANAA,EAAM,UAAY7C,EAAQ,kBAC1B6C,EAAM,YAAcD,EAChB,KAAK,QAAQ,eACb,KAAK,QAAQ,gBACjBF,EAAM,YAAYG,CAAK,EAEnBD,EAAe,CACjB,IAAME,EAAQ,SAAS,cAAc,QAAQ,EAC7C,OAAAA,EAAM,KAAO,SACbA,EAAM,UAAY9C,EAAQ,mBAC1B8C,EAAM,YAAc,KAAK,QAAQ,YACjCA,EAAM,iBAAiB,QAAS,IAAM,CACpC,KAAK,cAAc,CACrB,CAAC,EACDJ,EAAM,YAAYI,CAAK,EAChBJ,CACT,CAEA,IAAMzC,EAAO,SAAS,cAAc,KAAK,EACzCA,EAAK,UAAYD,EAAQ,iBAGzB,GAAM,CAAC+C,EAAQC,CAAK,EAAI,OAAO,KAAK,QAAQ,sBAAsB,EAAE,MAClE,KACF,EACMC,EAAM,SAAS,cAAc,KAAK,EACxCA,EAAI,UAAYjD,EAAQ,gBACxBiD,EAAI,YAAcC,GAAgB,KAAK,QAAS,KAAK,OAAO,EAC5DjD,EAAK,YAAY,SAAS,eAAe8C,GAAU,EAAE,CAAC,EACtD9C,EAAK,YAAYgD,CAAG,EACpBhD,EAAK,YAAY,SAAS,eAAe+C,GAAS,EAAE,CAAC,EACrDN,EAAM,YAAYzC,CAAI,EAEtB,IAAMkD,EAAS,SAAS,cAAc,QAAQ,EAC9C,OAAAA,EAAO,KAAO,SACdA,EAAO,UAAYnD,EAAQ,mBAC3BmD,EAAO,YAAc,KAAK,QAAQ,iBAClCA,EAAO,iBAAiB,QAAS,IAC/B,KAAK,UAAU,sBAAsB,CACvC,EACAT,EAAM,YAAYS,CAAM,EAEjBT,CACT,CAEA,iBAAiBU,EAAO,CACtB,OAAOA,IAAU,MACb,KAAK,QAAQ,UACb,KAAK,QAAQ,iBACnB,CAQA,qBAAsB,CACpB,IAAMC,EAAQ,CAAC,KAAK,iBAAiB,KAAK,UAAU,CAAC,EACrD,OAAI,KAAK,eAAiB,OACxBA,EAAM,KAAKC,EAAc,KAAK,aAAc,KAAK,OAAO,CAAC,EAEvD,KAAK,aAAe,OACtBD,EAAM,KAAKE,EAAY,KAAK,WAAY,KAAK,OAAO,CAAC,EAEnD,KAAK,iBAAmB,OAC1BF,EAAM,KAAKG,EAAgB,KAAK,eAAgB,KAAK,OAAO,CAAC,EAExDH,EAAM,KAAK,QAAK,CACzB,CAEA,iBAAkB,CAChB,OACE,KAAK,aAAe,QACpB,KAAK,eAAiB,OACtB,KAAK,aAAe,OACpB,KAAK,iBAAmB,KAE5B,CAYA,kBAAkB,CAChB,MAAAR,EACA,SAAAY,EACA,OAAAC,EACA,QAAAC,EACA,SAAAC,EACA,QAAAC,EAAU,GACV,SAAAC,CACF,EAAG,CACD,IAAMC,EAAQ,SAAS,cAAc,KAAK,EAC1CA,EAAM,UAAY/D,EAAQ,mBAE1B,IAAMgE,EAAU,SAAS,cAAc,KAAK,EAC5CA,EAAQ,UAAYhE,EAAQ,qBAC5BgE,EAAQ,YAAcnB,EACtBkB,EAAM,YAAYC,CAAO,EAEzB,IAAMC,EAAQ,SAAS,cAAc,KAAK,EAC1CA,EAAM,UAAYjE,EAAQ,mBAG1BiE,EAAM,aAAa,OAAQJ,EAAU,QAAU,YAAY,EAC3DI,EAAM,aAAa,aAAcpB,CAAK,EAEtC,QAAWO,KAASM,EAAQ,CAC1B,IAAMQ,EAAUN,IAAaR,EACvBe,EAAO,SAAS,cAAc,QAAQ,EAC5CA,EAAK,KAAO,SACZA,EAAK,UAAYnE,EAAQ,kBACzBmE,EAAK,QAAQV,CAAQ,EAAIL,EACzBe,EAAK,aAAa,OAAQN,EAAU,SAAW,OAAO,EACtDM,EAAK,aAAa,eAAgB,OAAOD,CAAO,CAAC,EACjDC,EAAK,YAAcR,EAAQP,CAAK,EAChCe,EAAK,iBAAiB,QAAUC,GAAM,CACpCA,EAAE,gBAAgB,EAClBN,EAASD,GAAWK,EAAU,MAAQd,CAAK,EAC3C,KAAK,OAAO,CACd,CAAC,EACDa,EAAM,YAAYE,CAAI,CACxB,CAEA,OAAAJ,EAAM,YAAYE,CAAK,EAChBF,CACT,CAEA,cAAe,CACb,IAAMM,EAAU,SAAS,cAAc,KAAK,EAC5CA,EAAQ,UAAYrE,EAAQ,aAAe,WAE3C,IAAMiB,EAAM,SAAS,cAAc,QAAQ,EAC3CA,EAAI,KAAO,SACXA,EAAI,UAAYjB,EAAQ,aACxBiB,EAAI,aAAa,gBAAiB,MAAM,EACxCA,EAAI,aAAa,gBAAiB,OAAO,EACzCA,EAAI,UAAY,SAAS,KAAK,oBAAoB,CAAC,UAAUqD,EAAc,GAE3E,IAAMC,EAAO,SAAS,cAAc,KAAK,EACzCA,EAAK,UAAYvE,EAAQ,kBACzBuE,EAAK,aAAa,OAAQ,OAAO,EACjCA,EAAK,aAAa,aAAc,KAAK,QAAQ,WAAW,EAExDC,EAAiBvD,EAAKsD,CAAI,EAE1B,IAAMrD,EAAS,SAAS,cAAc,KAAK,EAC3CA,EAAO,UAAYlB,EAAQ,yBAE3B,IAAM6C,EAAQ,SAAS,cAAc,MAAM,EAC3CA,EAAM,YAAc,KAAK,QAAQ,YACjC3B,EAAO,YAAY2B,CAAK,EAExB,IAAMC,EAAQ,SAAS,cAAc,QAAQ,EAC7C,OAAAA,EAAM,KAAO,SACbA,EAAM,UAAY9C,EAAQ,mBAC1B8C,EAAM,YAAc,KAAK,QAAQ,YACjCA,EAAM,SAAW,CAAC,KAAK,gBAAgB,EACvCA,EAAM,iBAAiB,QAAUsB,GAAM,CACrCA,EAAE,gBAAgB,EAClB,KAAK,cAAc,CACrB,CAAC,EACDlD,EAAO,YAAY4B,CAAK,EACxByB,EAAK,YAAYrD,CAAM,EAEvBqD,EAAK,YACH,KAAK,kBAAkB,CACrB,MAAO,KAAK,QAAQ,aACpB,SAAU,aACV,OAAQ,CAAC,OAAQ,KAAK,EACtB,QAAUnB,GAAU,KAAK,iBAAiBA,CAAK,EAC/C,SAAU,KAAK,WACf,QAAS,GACT,SAAWA,GAAW,KAAK,WAAaA,CAC1C,CAAC,CACH,EAEAmB,EAAK,YACH,KAAK,kBAAkB,CACrB,MAAO,KAAK,QAAQ,eACpB,SAAU,eACV,OAAQ,CAAC,GAAGE,CAAQ,EACpB,QAAUrB,GAAUE,EAAcF,EAAO,KAAK,OAAO,EACrD,SAAU,KAAK,aACf,SAAWA,GAAW,KAAK,aAAeA,CAC5C,CAAC,CACH,EAEAmB,EAAK,YACH,KAAK,kBAAkB,CACrB,MAAO,KAAK,QAAQ,aACpB,SAAU,aACV,OAAQ,CAAC,GAAGG,CAAa,EACzB,QAAUtB,GAAUG,EAAYH,EAAO,KAAK,OAAO,EACnD,SAAU,KAAK,WACf,SAAWA,GAAW,KAAK,WAAaA,CAC1C,CAAC,CACH,EAEAmB,EAAK,YACH,KAAK,kBAAkB,CACrB,MAAO,KAAK,QAAQ,iBACpB,SAAU,iBACV,OAAQ,CAAC,GAAGI,CAAU,EACtB,QAAUvB,GAAUI,EAAgBJ,EAAO,KAAK,OAAO,EACvD,SAAU,KAAK,eACf,SAAWA,GAAW,KAAK,eAAiBA,CAC9C,CAAC,CACH,EAEAiB,EAAQ,YAAYpD,CAAG,EACvBoD,EAAQ,YAAYE,CAAI,EACjBF,CACT,CAEA,WAAWnE,EAAS,CAAE,YAAA0E,CAAY,EAAG,CACnC,IAAMrC,EAAO,SAAS,cAAc,KAAK,EACzCA,EAAK,UAAYvC,EAAQ,WACrBE,EAAQ,SAAW,YACrBqC,EAAK,UAAU,IAAI,GAAGvC,EAAQ,UAAU,YAAY,EAEtDuC,EAAK,QAAQ,UAAYrC,EAAQ,GAKjC,IAAMgB,EAAS,SAAS,cAAc,KAAK,EAC3CA,EAAO,UAAYlB,EAAQ,kBAC3BkB,EAAO,YACL2D,GACE3E,EAAQ,OACRA,EAAQ,UACR,KAAK,QACL,KAAK,OACLA,EAAQ,QACV,CACF,EACAqC,EAAK,YAAYrB,CAAM,EAEvB,IAAM4D,EAAa,SAAS,cAAc,KAAK,EAc/C,GAbAA,EAAW,UAAY9E,EAAQ,mBAC/B8E,EAAW,YAAY,KAAK,kBAAkB5E,CAAO,CAAC,EACtDqC,EAAK,YAAYuC,CAAU,EAMzB,CAACF,GACD,KAAK,SACL,KAAK,QAAQ,SAAW,MACxB,OAAO,KAAK,QAAQ,SAAS,IAAM,OAAO1E,EAAQ,EAAE,EAGpDqC,EAAK,YAAY,KAAK,aAAa,CAAC,MAC/B,CACL,IAAMtC,EAAO,SAAS,cAAc,KAAK,EACzCA,EAAK,UAAYD,EAAQ,gBACzBC,EAAK,YAAcC,EAAQ,KAC3BqC,EAAK,YAAYtC,CAAI,CACvB,CAEA,GAAIC,EAAQ,aAAa,OAAQ,CAC/B,IAAM6E,EAAQC,GAAyB9E,EAAQ,YAAa,KAAK,OAAO,EACxE+E,EAAuBF,EAAQG,GAC7B,KAAK,UAAU,eAAeA,CAAG,CACnC,EACA3C,EAAK,YAAYwC,CAAK,CACxB,CAMA,IAAMI,EAASC,GAAelF,EAAS,KAAK,QAAS,CACnD,sBAAuB,EACzB,CAAC,EACGiF,GAAQ5C,EAAK,YAAY4C,CAAM,EAMnC5C,EAAK,YAAY,KAAK,aAAa,EAAE,IAAIrC,CAAO,CAAC,EAEjD,IAAMmF,EAAM,KAAK,UAAUnF,CAAO,EAGlC,GAFImF,GAAK9C,EAAK,YAAY8C,CAAG,EAEzBT,EAAa,CACfrC,EAAK,aAAa,OAAQ,QAAQ,EAClCA,EAAK,aAAa,WAAY,GAAG,EAIjC,IAAM+C,EAAW,IACfpF,EAAQ,cAAgB,WACpB,KAAK,UAAU,iBAAiBA,CAAO,EACvC,KAAK,YAAYA,CAAO,EAExBqF,EAAY,SAAS,cAAc,QAAQ,EACjDA,EAAU,KAAO,SACjBA,EAAU,UAAYvF,EAAQ,sBAC9BuF,EAAU,YAAc,KAAK,QAAQ,UACrCA,EAAU,iBAAiB,QAAUnB,GAAM,CACzCA,EAAE,gBAAgB,EAClBkB,EAAS,CACX,CAAC,EACD/C,EAAK,YAAYgD,CAAS,EAE1BhD,EAAK,iBAAiB,QAAS+C,CAAQ,EACvC/C,EAAK,iBAAiB,UAAyC6B,GAAM,EAC/DA,EAAE,MAAQ,SAAWA,EAAE,MAAQ,OACjCA,EAAE,eAAe,EACjBkB,EAAS,EAEb,CAAC,EAID/C,EAAK,iBAAiB,aAAc,IAAM,KAAK,WAAWrC,CAAO,CAAC,EAClEqC,EAAK,iBAAiB,aAAc,IAAM,KAAK,gBAAgB,CAAC,CAClE,CAEA,OAAOA,CACT,CAEA,UAAUrC,EAAS,CACjB,IAAIsF,EAAQ,KAIZ,GAHItF,EAAQ,cAAgB,WAAYsF,EAAQ,KAAK,QAAQ,cACpDtF,EAAQ,OAAQsF,EAAQ,KAAK,QAAQ,YACrCtF,EAAQ,cAAgB,aAAYsF,EAAQtF,EAAQ,MACzD,CAACsF,EAAO,OAAO,KAEnB,IAAMH,EAAM,SAAS,cAAc,MAAM,EACzC,OAAAA,EAAI,UAAYrF,EAAQ,eACxBqF,EAAI,YAAcG,EACXH,CACT,CAEA,kBAAkBnF,EAAS,CACzB,OAAOuF,GAAqBvF,EAAS,CACnC,QAAS,KAAK,QACd,IAAK,KAAK,UAAU,IACpB,UAAW,KAAK,aAAa,EAC7B,OAASC,GACPuF,GACEC,GAAkBxF,EAAG,CACnB,cAAe,OAAO,WACtB,eAAgB,OAAO,YACvB,QAAS,KAAK,OAChB,CAAC,CACH,EACF,WAAaA,GACXuF,GAAgBE,EAAiBzF,EAAG,KAAK,QAAQ,SAAS,CAAC,EAI7D,OAASA,GAAM,KAAK,aAAaA,EAAE,EAAE,EACrC,YAAa,CAACA,EAAG0F,IAAW,KAAK,UAAU,YAAY1F,EAAE,GAAI0F,CAAM,EACnE,UAAW,CAAC1F,EAAG2F,IAAS,KAAK,UAAU,UAAU3F,EAAE,GAAI2F,CAAI,EAC3D,cAAe,CAAC3F,EAAG4F,IACjB,KAAK,UAAU,cAAc5F,EAAE,GAAI4F,CAAQ,EAC7C,SAAW5F,GAAM,CACX,KAAK,UAAY,MAAQC,EAAO,KAAK,SAAUD,EAAE,EAAE,IACrD,KAAK,SAAW,MAElB,KAAK,UAAU,SAASA,EAAE,EAAE,EAC5B,KAAK,OAAO,CACd,CACF,CAAC,CACH,CAEA,cAAcD,EAASY,EAAU,CAC/B,IAAM2B,EAAQ3B,EAAS,QAAQZ,CAAO,EAEhCgB,EAAS,SAAS,cAAc,KAAK,EAC3CA,EAAO,UAAYlB,EAAQ,oBAE3B,IAAMmB,EAAU,SAAS,cAAc,QAAQ,EAC/CA,EAAQ,KAAO,SACfA,EAAQ,UAAYnB,EAAQ,WAC5BmB,EAAQ,UAAY,GAAG9B,EAAgB,SAAS,KAAK,QAAQ,IAAI,UACjE8B,EAAQ,iBAAiB,QAAS,SAAY,CACxC,KAAK,SAAW,CAAE,MAAM,KAAK,cAAc,IAC/C,KAAK,SAAW,KAChB,KAAK,OAAO,EACd,CAAC,EACDD,EAAO,YAAYC,CAAO,EAE1B,IAAMC,EAAM,SAAS,cAAc,KAAK,EACxCA,EAAI,UAAYpB,EAAQ,mBAExB,IAAMgG,EAAS,CAACC,EAAKT,EAAOU,IAAgB,CAC1C,IAAMjF,EAAM,SAAS,cAAc,QAAQ,EAC3CA,EAAI,KAAO,SACXA,EAAI,UAAYjB,EAAQ,cACxBiB,EAAI,aAAa,aAAcuE,CAAK,EACpCvE,EAAI,MAAQuE,EACZvE,EAAI,UAAYgF,EAChB,IAAMrE,EAASd,EAASoF,CAAW,EACnC,OAAAjF,EAAI,SAAW,CAACW,EACZA,GAIFX,EAAI,iBAAiB,QAAS,SAAY,CACpC,KAAK,SAAW,CAAE,MAAM,KAAK,cAAc,GAC/C,KAAK,YAAYW,CAAM,CACzB,CAAC,EAEIX,CACT,EAEAG,EAAI,YAAY4E,EAAO1G,GAAc,KAAK,QAAQ,YAAamD,EAAQ,CAAC,CAAC,EACzErB,EAAI,YACF4E,EAAOzG,GAAgB,KAAK,QAAQ,YAAakD,EAAQ,CAAC,CAC5D,EACArB,EAAI,YAAY,KAAK,aAAa,CAAC,EACnCF,EAAO,YAAYE,CAAG,EAEtB,KAAK,GAAG,YAAYF,CAAM,EAE1B,IAAMH,EAAS,SAAS,cAAc,KAAK,EAC3CA,EAAO,UAAYf,EAAQ,aAE3Be,EAAO,YAAY,KAAK,WAAWb,EAAS,CAAE,YAAa,EAAM,CAAC,CAAC,EAKnE,IAAMiG,EAAUC,GAAmBlG,EAAS,CAC1C,QAAS,KAAK,QACd,eAAiBgF,GAAQ,KAAK,UAAU,eAAeA,CAAG,EAC1D,YAAa,GACb,SAAU,KAAK,gBACf,SAAWmB,GAAa,CACtB,KAAK,gBAAkBA,CACzB,CACF,CAAC,EACGF,GAASpF,EAAO,YAAYoF,CAAO,EAKvC,IAAMG,EAAQC,GAAiBrG,EAAS,CACtC,QAAS,KAAK,QACd,OAAQ,KAAK,OACb,KAAM,KAAK,cACX,SAAWmG,GAAa,CACtB,KAAK,cAAgBA,CACvB,CACF,CAAC,EACGC,GAAOvF,EAAO,YAAYuF,CAAK,EAEnC,IAAME,EAAU,SAAS,cAAc,KAAK,EAC5CA,EAAQ,UAAYxG,EAAQ,cAC5B,QAAWyG,KAASvG,EAAQ,SAAW,CAAC,EAAG,CACzC,IAAMwG,EACJ,KAAK,SACL,OAAO,KAAK,QAAQ,SAAS,IAAM,OAAOxG,EAAQ,EAAE,GACpD,OAAO,KAAK,QAAQ,OAAO,IAAM,OAAOuG,EAAM,EAAE,EAE5CE,EAAUC,GAAmBH,EAAO,KAAK,QAAS,KAAK,OAAQ,CACnE,UAAWvG,EAAQ,GACnB,IAAK,KAAK,UAAU,IAIpB,SAAU,CAACG,EAAGoB,IAAO,CACf,KAAK,UAAU,cAAcvB,EAAQ,GAAIG,EAAE,EAAE,GAAGoB,EAAG,OAAO,CAChE,EACA,OAASpB,GAAM,KAAK,aAAaH,EAAQ,GAAIG,EAAE,EAAE,EACjD,QAASqG,EAAmB,KAAK,gBAAgB,EAAI,KACrD,UAAW,KAAK,aAAa,CAC/B,CAAC,EACDzB,EAAuB0B,EAAUzB,GAC/B,KAAK,UAAU,eAAeA,CAAG,CACnC,EACAsB,EAAQ,YAAYG,CAAO,CAC7B,CACA5F,EAAO,YAAYyF,CAAO,EAE1BzF,EAAO,YAAY,KAAK,iBAAiBb,CAAO,CAAC,EACjD,KAAK,GAAG,YAAYa,CAAM,CAC5B,CAEA,iBAAiBb,EAAS,CACxB,GAAM,CACJ,UAAA2G,EACA,QAAAC,EACA,qBAAAC,EACA,UAAAC,EACA,UAAAC,EACA,UAAAC,CACF,EAAIC,GACF,CACE,cAAenH,EAAQ,kBACvB,SAAU,QACV,eAAgBA,EAAQ,aACxB,iBAAkB,KAAK,QAAQ,gBACjC,EACA,KAAK,OACP,EAEIoH,EAAqB,CAAC,EAEpBC,EAAgB,IAAM,CAC1BC,GAAyBP,EAAsBK,EAAoB,CACjE,QAAS,KAAK,QACd,OAASG,GAAY,KAAK,UAAU,eAAeA,CAAO,EAC1D,SAAU,IAAMF,EAAc,CAChC,CAAC,CACH,EAEAL,EAAU,iBAAiB,QAAS,IAAMC,EAAU,MAAM,CAAC,EAC3DO,GACEP,EACA,IAAMG,EACNC,EACCE,GAAY,KAAK,UAAU,sBAAsBA,EAASrH,EAAQ,EAAE,CACvE,EAEA,IAAMuH,EAAS,IAAM,CACnB,IAAMxH,EAAO6G,EAAQ,MAAM,KAAK,EAC5B,CAAC7G,GAAQmH,EAAmB,SAAW,IAC3C,KAAK,UAAU,QAAQlH,EAASD,EAAM,CAAC,GAAGmH,CAAkB,CAAC,EAC7DA,EAAqB,CAAC,EACtB,KAAK,OAAO,EACd,EAEA,OAAAF,EAAU,iBAAiB,QAASO,CAAM,EAC1CX,EAAQ,iBAAiB,UAAyC1C,GAAM,CAClEA,EAAE,MAAQ,SAAW,CAACA,EAAE,WAC1BA,EAAE,eAAe,EACjBqD,EAAO,EAEX,CAAC,EAEMZ,CACT,CACF,ECxqCA,IAAMa,GAAY,IACZC,GAAU;AAAA,EAMVC,GAAe,eAEfC,GAAUC,GAAU,CACxB,GAAIA,GAAU,KAA6B,MAAO,GAClD,IAAMC,EAAM,OAAOD,CAAK,EAClBE,EAAOJ,GAAa,KAAKG,CAAG,EAAI,IAAIA,CAAG,GAAKA,EAClD,MAAO,WAAW,KAAKC,CAAI,EAAI,IAAIA,EAAK,QAAQ,KAAM,IAAI,CAAC,IAAMA,CACnE,EAOO,SAASC,GAAMC,EAAMC,EAAS,CACnC,IAAMC,EAASD,EAAQ,IAAKE,GAAWR,GAAOQ,EAAO,KAAK,CAAC,EAAE,KAAKX,EAAS,EACrEY,EAAOJ,EAAK,IAAKK,GACrBJ,EAAQ,IAAKE,GAAWR,GAAOU,EAAIF,EAAO,GAAG,CAAC,CAAC,EAAE,KAAKX,EAAS,CACjE,EACA,MAAO,CAACU,EAAQ,GAAGE,CAAI,EAAE,KAAKX,EAAO,CACvC,CAGO,IAAMa,GAAkB,CAC7B,KACA,OACA,SACA,WACA,OACA,SACA,OACA,WACA,OACA,YACA,aACA,kBACA,WACA,SACF,EASaC,GAAaC,GAASA,EAAK,IAAKC,IAAS,CAAE,IAAAA,EAAK,MAAOA,CAAI,EAAE,EAG7DC,GAAiB,CAAC,UAAW,MAAO,OAAO,EASjD,SAASC,GAAYC,EAAU,CACpC,OAAQA,GAAY,CAAC,GAAG,IAAKC,IACpB,CACL,GAAI,OAAOA,EAAQ,EAAE,EACrB,KAAMA,EAAQ,MAAQ,GACtB,OAAQA,EAAQ,QAAU,GAC1B,SAAUA,EAAQ,UAAY,GAC9B,KAAMA,EAAQ,MAAQ,GACtB,OAAQA,EAAQ,QAAU,OAC1B,KAAMA,EAAQ,MAAQ,GACtB,SAAUA,EAAQ,UAAY,GAG9B,MAAOA,EAAQ,MAAQ,CAAC,GAAG,KAAK,GAAG,EACnC,UAAWA,EAAQ,WAAa,GAChC,WAAYA,EAAQ,YAAc,GAClC,gBAAiBC,GAAQC,GAAoBF,CAAO,CAAC,EACrD,SAAUG,EAAcH,CAAO,EAAE,OAAS,EAAI,MAAQ,KACtD,SAAUA,EAAQ,SAAW,CAAC,GAAG,MACnC,EACD,CACH,CAaO,SAASI,GAAWC,EAAS,CAClC,IAAMlB,EAAO,CAAC,CAAE,QAAS,QAAS,IAAK,GAAI,MAAOkB,EAAQ,KAAM,CAAC,EAC3DC,EAAO,CAACC,EAASC,IAAU,CAC/B,OAAW,CAACZ,EAAKb,CAAK,IAAK,OAAO,QAAQyB,CAAK,EAC7CrB,EAAK,KAAK,CAAE,QAAAoB,EAAS,IAAAX,EAAK,MAAAb,CAAM,CAAC,CAErC,EACAuB,EAAK,SAAUD,EAAQ,QAAQ,EAC/BC,EAAK,OAAQD,EAAQ,MAAM,EAC3BC,EAAK,WAAYD,EAAQ,UAAU,EACnC,OAAW,CAAE,KAAAI,EAAM,MAAAC,CAAM,IAAKL,EAAQ,SACpClB,EAAK,KAAK,CAAE,QAAS,SAAU,IAAKsB,EAAM,MAAOC,CAAM,CAAC,EAE1D,OAAAvB,EAAK,KACH,CACE,QAAS,aACT,IAAK,gBACL,MAAOkB,EAAQ,WAAW,aAC5B,EACA,CACE,QAAS,aACT,IAAK,gBACL,MAAOA,EAAQ,WAAW,aAC5B,EACA,CACE,QAAS,aACT,IAAK,eACL,MAAOJ,GAAQI,EAAQ,WAAW,SAAS,CAC7C,EACA,CACE,QAAS,aACT,IAAK,cACL,MAAOJ,GAAQI,EAAQ,WAAW,QAAQ,CAC5C,CACF,EACOlB,CACT,CAUO,SAASwB,GAAYC,EAAUC,EAAM,CAC1C,IAAMC,EAAO,IAAI,KAAK,CAAC,SAAUD,CAAI,EAAG,CACtC,KAAM,wBACR,CAAC,EACKE,EAAM,IAAI,gBAAgBD,CAAI,EAC9BE,EAAO,SAAS,cAAc,GAAG,EACvCA,EAAK,KAAOD,EACZC,EAAK,SAAWJ,EAChBI,EAAK,MAAM,EACX,IAAI,gBAAgBD,CAAG,CACzB,CC/IA,IAAME,GAAkB,yBAElBC,EAAK,CAACC,EAAKC,EAAKC,EAAWC,IAAS,CACxC,IAAMC,EAAOJ,EAAI,cAAcC,CAAG,EAClC,OAAIC,IAAWE,EAAK,UAAYF,GAC5BC,IAAS,SAAWC,EAAK,YAAc,OAAOD,CAAI,GAC/CC,CACT,EAEMC,GAAa,CAACL,EAAKM,EAASC,EAAMC,IAAY,CAClD,IAAMC,EAAQV,EAAGC,EAAK,QAAS,cAAc,EAC7CS,EAAM,YAAYV,EAAGC,EAAK,UAAW,KAAMM,CAAO,CAAC,EAEnD,IAAMI,EAAQV,EAAI,cAAc,OAAO,EACjCW,EAAUX,EAAI,cAAc,IAAI,EACtC,QAAWY,KAAUJ,EACnBG,EAAQ,YAAYZ,EAAGC,EAAK,KAAM,KAAMY,CAAM,CAAC,EACjDF,EAAM,YAAYC,CAAO,EACzBF,EAAM,YAAYC,CAAK,EAEvB,IAAMG,EAAQb,EAAI,cAAc,OAAO,EACvC,OAAW,CAACc,EAAOC,CAAK,IAAKR,EAAM,CACjC,IAAMS,EAAKhB,EAAI,cAAc,IAAI,EACjCgB,EAAG,YAAYjB,EAAGC,EAAK,KAAM,KAAMc,CAAK,CAAC,EACzCE,EAAG,YAAYjB,EAAGC,EAAK,KAAM,KAAMe,CAAK,CAAC,EACzCF,EAAM,YAAYG,CAAE,CACtB,CACA,OAAAP,EAAM,YAAYI,CAAK,EAChBJ,CACT,EAIMQ,GAAYC,GAChBA,IAAO,KAAO,SAAMC,EAAe,QAASC,GAAQF,CAAE,CAAC,EAEnDG,GAAc,CAACrB,EAAKsB,EAAS,CAAE,QAAAC,EAAS,OAAAC,EAAQ,MAAAC,CAAM,IAAM,CAChE,IAAMC,EAAO1B,EAAI,KACjB0B,EAAK,UAAY,SAEjBA,EAAK,YAAY3B,EAAGC,EAAK,KAAM,eAAgBuB,EAAQ,YAAY,CAAC,EAEpE,IAAMI,EAAO5B,EAAGC,EAAK,IAAK,aAAa,EAUvC,GATA2B,EAAK,YAAcR,EACjBI,EAAQ,yBACR,IAAI,KAAK,eAAeC,EAAQ,CAC9B,UAAW,OACX,UAAW,OACb,CAAC,EAAE,OAAO,IAAI,IAAM,CACtB,EACAE,EAAK,YAAYC,CAAI,EAEjBF,EAAO,CACT,IAAMG,EAAU7B,EAAGC,EAAK,IAAK,aAAa,EAC1C4B,EAAQ,YAAc,GAAGL,EAAQ,YAAY,KAAKE,CAAK,GACvDC,EAAK,YAAYE,CAAO,CAC1B,CAEAF,EAAK,YACHrB,GACEL,EACAuB,EAAQ,aACR,CACE,CAACA,EAAQ,aAAcD,EAAQ,KAAK,EACpC,CAACC,EAAQ,eAAgBD,EAAQ,WAAW,aAAa,EACzD,CAACC,EAAQ,gBAAiBD,EAAQ,WAAW,aAAa,EAC1D,CACEC,EAAQ,yBACRN,GAASK,EAAQ,WAAW,SAAS,CACvC,EACA,CACEC,EAAQ,wBACRN,GAASK,EAAQ,WAAW,QAAQ,CACtC,CACF,EACA,CAACC,EAAQ,gBAAiBA,EAAQ,YAAY,CAChD,CACF,EAEA,IAAMM,EAAY,CAACvB,EAASwB,EAAMrB,EAAOsB,IACvC1B,GACEL,EACAM,EACAwB,EAAK,IAAKE,GAAQ,CAACD,EAAQC,CAAG,EAAGvB,EAAMuB,CAAG,GAAK,CAAC,CAAC,EACjD,CAACT,EAAQ,gBAAiBA,EAAQ,YAAY,CAChD,EAEFG,EAAK,YACHG,EAAUN,EAAQ,gBAAiBU,EAAUX,EAAQ,SAAWU,GAC9DE,EAAcF,EAAKT,CAAO,CAC5B,CACF,EACAG,EAAK,YACHG,EACEN,EAAQ,cACR,CAAC,GAAGY,EAAe,OAAO,EAC1Bb,EAAQ,OACPU,GAASA,IAAQ,QAAUT,EAAQ,MAAQa,EAAYJ,EAAKT,CAAO,CACtE,CACF,EACAG,EAAK,YACHG,EACEN,EAAQ,kBACR,CAAC,GAAGc,EAAY,OAAO,EACvBf,EAAQ,WACPU,GAASA,IAAQ,QAAUT,EAAQ,MAAQe,EAAgBN,EAAKT,CAAO,CAC1E,CACF,EAEID,EAAQ,SAAS,QACnBI,EAAK,YACHrB,GACEL,EACAuB,EAAQ,gBACRD,EAAQ,SAAS,IAAI,CAAC,CAAE,KAAAiB,EAAM,MAAAC,CAAM,IAAM,CAACD,EAAMC,CAAK,CAAC,EACvD,CAACjB,EAAQ,YAAaA,EAAQ,YAAY,CAC5C,CACF,CAEJ,EAcO,SAASkB,GAAmBnB,EAAS,CAAE,QAAAC,EAAS,OAAAC,EAAQ,IAAAkB,EAAK,MAAAjB,CAAM,EAAG,CAC3E,IAAMkB,EAAQ,SAAS,cAAc,QAAQ,EAC7CA,EAAM,aAAa,cAAe,MAAM,EACxCA,EAAM,aAAa,QAASpB,EAAQ,YAAY,EAGhDoB,EAAM,MAAM,SAAW,WACvBA,EAAM,MAAM,MAAQ,IACpBA,EAAM,MAAM,OAAS,IACrBA,EAAM,MAAM,OAAS,IACrBA,EAAM,MAAM,KAAO,UACnB,SAAS,KAAK,YAAYA,CAAK,EAE/B,IAAM3C,EAAM2C,EAAM,gBACZC,EAAOD,EAAM,cACnB3C,EAAI,MAAQuB,EAAQ,aACpBsB,GAAY7C,EAAK0C,EAAK5C,EAAe,EACrCuB,GAAYrB,EAAKsB,EAAS,CAAE,QAAAC,EAAS,OAAAC,EAAQ,MAAAC,CAAM,CAAC,EAEpD,IAAMqB,EAAW,IAAMH,EAAM,OAAO,EACpC,OAAAC,EAAK,iBAAiB,aAAcE,EAAU,CAAE,KAAM,EAAK,CAAC,EAK5D,WAAW,IAAMF,EAAK,QAAQ,EAAG,CAAC,EAE3BD,CACT,CCjGA,IAAMI,GAAiBC,GAAS,CAC9B,IAAMC,EAAO,IAAI,IACjB,QAAWC,KAAOF,EAAM,CACtB,IAAMG,EAAQ,OAAOD,CAAG,EAAE,KAAK,EAAE,YAAY,EACzCC,GAAOF,EAAK,IAAIE,CAAK,CAC3B,CACA,MAAO,CAAC,GAAGF,CAAI,CACjB,EAIMG,GAAeC,GAAWA,EAAO,OAAQC,GAAM,OAAOA,GAAM,QAAQ,EAOpEC,GAAsB,GAMtBC,GAAmB,CACvB,kBAAmB,mBACnB,iBAAkB,kBAClB,kBAAmB,mBACnB,yBAA0B,yBAC1B,kBAAmB,mBACnB,sBAAuB,eACvB,cAAe,eACf,gBAAiB,iBACjB,eAAgB,gBAChB,mBAAoB,mBACtB,EAEMC,GAAN,KAAqB,CAInB,YAAYC,EAAU,CAAC,EAAG,CACxB,KAAK,SAAW,CAAC,EACjB,KAAK,YAAc,GACnB,KAAK,MAAQC,GAAc,EAC3B,KAAK,QAAU,CACb,YAAaD,EAAQ,cAAgB,KAAK,MAAQ,IAAM,KACxD,iBAAkBA,EAAQ,kBAAoB,MAC9C,eAAgBA,EAAQ,iBAAmB,GAC3C,sBAAuBA,EAAQ,wBAA0B,GACzD,YAAaA,EAAQ,cAAgB,GACrC,kBAAmBA,EAAQ,oBAAsB,GACjD,GAAGA,CACL,EACA,KAAK,OAAS,KAAK,QAAQ,QAAUE,GAAa,EAClD,KAAK,QAAUC,GAAW,KAAK,MAAM,EAOrC,KAAK,QAAU,KAMf,KAAK,aAAe,KAGpB,KAAK,sBAAwB,GAS7B,KAAK,aAAe,KAOpB,KAAK,SAAW,KAMhB,KAAK,iBAAmB,KAOxB,KAAK,mBAAqB,KAS1B,KAAK,QAAU,OAOf,KAAK,cAAgB,KAEjB,SAAS,aAAe,WAI1B,KAAK,YAAc,IAAM,KAAK,YAAY,EAC1C,SAAS,iBAAiB,mBAAoB,KAAK,WAAW,GAE9D,KAAK,YAAY,CAErB,CAEA,aAAc,CAGZ,KAAK,WAAaC,GAAc,EAGhC,KAAK,QAAUC,GAAc,KAAK,QAAS,KAAK,OAAO,EACvD,KAAK,WAAaC,GAAiB,KAAK,OAAO,EAC/C,KAAK,QAAU,SAAS,cAAc,KAAK,EAC3C,KAAK,QAAQ,UAAYC,EAAQ,gBAEjC,KAAK,WAAW,YAAY,KAAK,OAAO,EACxC,KAAK,WAAW,YAAY,KAAK,OAAO,EACxC,KAAK,WAAW,YAAY,KAAK,UAAU,EAE3C,KAAK,WAAa,KAAK,QAAQ,cAC7B,IAAIA,EAAQ,mBAAmB,EACjC,EACA,KAAK,SAAW,KAAK,QAAQ,cAAc,IAAIA,EAAQ,gBAAgB,EAAE,EACzE,KAAK,OAAS,KAAK,QAAQ,cAAc,IAAIA,EAAQ,eAAe,EAAE,EAEtE,KAAK,cAAgB,GAErB,KAAK,aACH,KAAK,WAAW,eAAeC,EAAI,cAAc,EAGnD,KAAK,aACH,KAAK,WAAW,eAAeA,EAAI,aAAa,EAElD,KAAK,eAAiB,KAAK,WAAW,cACpC,IAAID,EAAQ,gBAAgB,EAC9B,EAEA,KAAK,iBACH,KAAK,WAAW,eAAeC,EAAI,kBAAkB,EAGvD,KAAK,aAAe,IAAIC,GAAY,CAClC,KAAM,KAAK,WACX,eAAgB,KAAK,QAAQ,eAC7B,sBAAuB,KAAK,QAAQ,sBACpC,YAAa,KAAK,QAAQ,YAC1B,kBAAmB,KAAK,QAAQ,kBAChC,eAAgB,KAAK,QAAQ,eAG7B,iBAAmBC,GAAY,CACxB,KAAK,sBAAqB,KAAK,oBAAsB,CAAC,GACvD,KAAK,oBAAoB,OAASC,IACpC,KAAK,oBAAoB,KAAKD,CAAO,CAEzC,EAIA,gBAAkBE,GAAY,CAC5B,KAAK,sBAAwBA,EAC7B,KAAK,0BAA0B,CACjC,EACA,QAAS,CAACC,EAAGC,EAAGC,IAAW,KAAK,qBAAqBF,EAAGC,EAAGC,CAAM,EACjE,QAAUC,GAAQ,KAAK,aAAaA,EAAK,SAAS,CACpD,CAAC,EAED,KAAK,SAAW,IAAIC,GAAkB,CACpC,WAAY,KAAK,WACjB,QAAS,KAAK,QACd,OAAQ,KAAK,OACb,YAAcC,GAAO,KAAK,aAAaA,CAAE,EACzC,cAAgBA,GAAO,KAAK,WAAWA,CAAE,GAAG,OAAO,EACnD,eAAiBC,GAAQ,KAAK,aAAaA,CAAG,EAC9C,iBAAmBC,GAAW,KAAK,kBAAkBA,CAAM,EAC3D,UAAW,IAAM,KAAK,WAAW,EACjC,aAAc,IAAM,CACd,KAAK,WAAW,OAAO,GAAG,KAAK,UAAU,QAAQ,CACvD,EACA,SAAU,IAAM,KAAK,UAAU,EAC/B,IAAK,CAACC,EAAQD,IAAW,KAAK,IAAIC,EAAQD,CAAM,EAChD,oBAAqB,CAACV,EAASY,IAC7B,KAAK,qBAAqBZ,EAAS,aAAcY,CAAS,EAG5D,QAAS,KAAK,aAAa,CACzB,SAAU,CAACC,EAASC,EAAMC,IACxB,KAAK,SAASF,EAASC,EAAMC,CAAW,EAC1C,YAAa,CAACH,EAAWI,IACvB,KAAK,YAAYJ,EAAWI,CAAO,EACrC,YAAa,CAACR,EAAIM,IAAS,KAAK,YAAYN,EAAIM,CAAI,EACpD,UAAW,CAACF,EAAWI,EAASF,IAC9B,KAAK,UAAUF,EAAWI,EAASF,CAAI,EACzC,UAAW,CAACN,EAAIS,IAAW,KAAK,iBAAiBT,EAAIS,CAAM,EAC3D,QAAS,CAACT,EAAIU,IAAS,KAAK,eAAeV,EAAIU,CAAI,EACnD,YAAa,CAACV,EAAIW,IAAa,KAAK,mBAAmBX,EAAIW,CAAQ,EACnE,cAAgBX,GAAO,KAAK,cAAcA,CAAE,EAC5C,sBAAuB,CAACA,EAAIY,IAC1B,KAAK,sBAAsBZ,EAAIY,CAAK,EACtC,oBAAqB,CAACR,EAAWI,EAASI,IACxC,KAAK,oBAAoBR,EAAWI,EAASI,CAAK,CACtD,CAAC,CACH,CAAC,EAED,KAAK,QAAU,IAAIC,GAAa,CAC9B,UAAW,KAAK,QAChB,QAAS,KAAK,QACd,YAAa,IAAM,KAAK,SACxB,WAAY,CAACC,EAAQT,IAAY,KAAK,YAAYS,EAAQT,CAAO,EACjE,eAAiBA,GAAY,KAAK,iBAAiBA,CAAO,EAC1D,iBAAkB,IAAM,CAClB,KAAK,WAAW,OAAO,GAAG,KAAK,UAAU,QAAQ,CACvD,EAGA,YAAa,IAAM,KAAK,0BAA0B,CACpD,CAAC,EACD,KAAK,QAAQ,MAAM,EAInB,GAAI,CACE,aAAa,QAAQU,EAA0B,IAAM,QACvD,KAAK,kBAAkB,EAAI,CAE/B,MAAQ,CAER,CAyBA,GAtBA,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,aAAa,EAElB,KAAK,iBAAmB,KAAK,qBAAqB,EAE9C,KAAK,QAAQ,cAAgB,iBAC/B,KAAK,aAAeC,GAAmB,EACvC,KAAK,aAAa,KAAK,YAAY,EAInC,KAAK,gBAAmB,GAAM,EACxB,EAAE,MAAQC,IAAe,EAAE,MAAQ,QAAM,KAAK,aAAe,KACnE,EACA,OAAO,iBAAiB,UAAW,KAAK,eAAe,GAOrD,KAAK,cAAe,CACtB,IAAMC,EAAW,KAAK,cACtB,KAAK,cAAgB,KACrB,KAAK,aAAaA,CAAQ,CAC5B,CAKA,KAAK,mBAAmB,EAKpB,KAAK,QAAQ,uBACf,KAAK,iBAAmB,IAAM,KAAK,iBAAiB,EACpD,OAAO,iBAAiB,WAAY,KAAK,gBAAgB,GAG3D,KAAK,aAAa,CACpB,CAEA,YAAYC,EAAK,CAGf,GAAI,OAAO,KAAK,QAAQ,UAAa,WAAY,CAC/C,KAAK,QAAQ,SAASA,CAAG,EACzB,MACF,CACA,SAAS,OAAOA,CAAG,CACrB,CASA,sBAAuB,CACrB,IAAMC,EAAWC,GAAqB,KAAK,WAAW,CAAC,EACnDC,EAAc,KAClB,GAAI,CACFA,EAAc,eAAe,QAAQC,EAAkB,EAEnDD,GAAe,MAAM,eAAe,WAAWC,EAAkB,CACvE,MAAQ,CAER,CACA,OAAOH,GAAYE,CACrB,CAEA,YAAa,CACX,OAAO,KAAK,QAAQ,WAAaE,EACnC,CAYA,oBAAqB,CACnB,IAAMxB,EAAK,KAAK,iBAChB,GAAI,CAACA,EAAI,OAET,IAAMK,EAAU,KAAK,aAAaL,CAAE,EACpC,GAAI,CAACK,EAAS,CAGZ,KAAK,UAAU,EACf,KAAK,WAAW,WAAW,KAAK,QAAQ,eAAe,EACvD,KAAK,sBAAsBL,CAAE,EAC7B,MACF,CAEA,KAAK,iBAAmB,KACxB,KAAK,mBAAqB,KAC1B,KAAK,UAAU,EACf,KAAK,UAAU,YAAY,EAC3B,KAAK,UAAU,WAAWK,EAAQ,EAAE,CACtC,CAsBA,sBAAsBL,EAAI,CACxB,IAAMyB,EAAU,KAAK,QAAQ,mBAE7B,GADI,OAAOA,GAAY,YACnB,KAAK,qBAAuB,MAAQC,EAAO,KAAK,mBAAoB1B,CAAE,EACxE,OACF,KAAK,mBAAqBA,EAE1B,IAAI2B,EACJ,GAAI,CACFA,EAASF,EAAQzB,CAAE,CACrB,OAASF,EAAK,CACZ,KAAK,aAAaA,EAAK,MAAM,EAC7B,MACF,CACI,CAAC6B,GAAU,OAA4BA,EAAQ,MAAU,YAG5BA,EAAQ,KACvC,IAAM,KAAK,mBAAmB,EAC7B7B,GAAQ,CACP,KAAK,aAAaA,EAAK,MAAM,EAC7B,KAAK,mBAAmB,CAC1B,CACF,CACF,CAUA,cAAe,CACb,IAAM2B,EAAU,KAAK,QAAQ,QAC7B,GAAI,OAAOA,GAAY,WACvB,GAAI,CACFA,EAAQ,IAAI,CACd,OAAS3B,EAAK,CACZ,QAAQ,KAAK,kCAAmCA,CAAG,CACrD,CACF,CAiBA,QAAQ8B,EAAI,CACV,IAAMC,EAAW,KAAK,QACtB,KAAK,QAAU,OACf,GAAI,CACF,OAAOD,EAAG,CACZ,QAAE,CACA,KAAK,QAAUC,CACjB,CACF,CAYA,aAAaC,EAAS,CAEpB,IAAMC,EAAU,CAAC,EACjB,OAAW,CAACC,EAAKC,CAAK,IAAK,OAAO,QAAQH,CAAO,EAC/CC,EAAQC,CAAG,EACT,OAAOC,GAAU,WACb,IAAyBC,IAAS,KAAK,QAAQ,IAAMD,EAAM,GAAGC,CAAI,CAAC,EACnED,EAER,OAAyBF,CAC3B,CAcA,aAAaI,EAAOC,EAAS,CAC3B,IAAMX,EAAU,KAAK,QAAQ,QAC7B,GAAI,OAAOA,GAAY,WACvB,GAAI,CACFA,EAAQU,EAAOC,CAAO,CACxB,OAAStC,EAAK,CACZ,QAAQ,KAAK,kCAAmCA,CAAG,CACrD,CACF,CAmBA,MAAM,qBAAqBN,EAAS6C,EAAMjC,EAAW,CACnD,IAAMkC,EAAY,KAAK,QAAQ,oBAC/B,GAAI,OAAOA,GAAc,YAAc,CAAC9C,EAAS,OAAOA,EACxD,GAAI,CACF,IAAMmC,EAAS,MAAMW,EAAU9C,EAAS,CAAE,KAAA6C,EAAM,UAAAjC,CAAU,CAAC,EAG3D,GAAI,OAAOuB,GAAW,UAAY,CAACA,EACjC,MAAM,IAAI,MACR,4DACF,EAEF,OAAOA,CACT,OAAS7B,EAAK,CACZ,YAAK,aAAaA,EAAK,WAAW,EAC3BN,CACT,CACF,CAwBA,MAAMkB,EAAM6B,EAAcC,EAASC,EAAQ,CACzC,IAAMC,EAAO,CAAE,OAAQ,KAAK,QAAS,GAAGD,CAAO,EACzCE,EAAO/D,GAAiB8B,CAAI,EAC5BkC,EAAW,KAAK,QAAQD,CAAI,EAClC,GAAI,OAAOC,GAAa,WACtB,GAAI,CACFA,EAAS,GAAGL,EAAcG,CAAI,CAChC,OAAS5C,EAAK,CACZ,QAAQ,KAAK,aAAa6C,CAAI,iBAAkB7C,CAAG,CACrD,CAEF,GAAI,OAAO,KAAK,QAAQ,UAAa,WACnC,GAAI,CACF,KAAK,QAAQ,SAAS,CAAE,KAAAY,EAAM,GAAG8B,EAAS,GAAGE,CAAK,CAAC,CACrD,OAAS5C,EAAK,CACZ,QAAQ,KAAK,mCAAoCA,CAAG,CACtD,CAEJ,CAGA,YAAYE,EAAI,CACd,IAAMK,EAAU,KAAK,aAAaL,CAAE,EACpC,OAAOK,EAAUwC,EAAiBxC,EAAS,KAAK,WAAW,CAAC,EAAI,IAClE,CAGA,mBAAoB,CAClB,OAAK,KAAK,eAAc,KAAK,aAAeW,GAAmB,GACxD,KAAK,YACd,CAEA,cAAe,CACb,GAAI,KAAK,QAAQ,cAAgB,eAAgB,OACjD,IAAM8B,EAASC,GACb,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,SAAS,QACX,EACKC,GAAoBF,CAAM,GAI7B,KAAK,aACH,IAAI,MAAM,2DAA2D,EACrE,SACF,EAKF,KAAK,aAAeA,CACtB,CAEA,oBAAqB,CACnB,KAAK,WAAW,iBAAiB,QAAS,IAAM,KAAK,kBAAkB,CAAC,EACxE,KAAK,SAAS,iBAAiB,QAAS,IAAM,KAAK,YAAY,CAAC,EAChE,KAAK,QAAQ,iBAAiB,QAAS,IACrC,KAAK,kBAAkB,CAAC,KAAK,aAAa,CAC5C,EACA,KAAK,aAAa,iBAAiB,QAAS,IAAM,KAAK,YAAY,CAAC,EAEpE,KAAK,aAAa,iBAAiB,UAAY,GAAM,CAC/C,EAAE,MAAQ,SAAW,CAAC,EAAE,WAC1B,EAAE,eAAe,EACjB,KAAK,YAAY,EAErB,CAAC,EAED,KAAK,eAAe,iBAAiB,QAAS,IAAM,CAClD,KAAK,iBAAiB,MAAM,CAC9B,CAAC,EAEDG,GACE,KAAK,iBACL,KACO,KAAK,sBAAqB,KAAK,oBAAsB,CAAC,GACpD,KAAK,qBAEd,IAAM,KAAK,0BAA0B,CACvC,EAEA,KAAK,0BAA6B,GAAM,KAAK,oBAAoB,CAAC,EAClE,SAAS,iBAAiB,YAAa,KAAK,yBAAyB,CACvE,CAEA,uBAAwB,CAElB,KAAK,gBACP,SAAS,oBAAoB,UAAW,KAAK,cAAc,EAI7D,KAAK,eAAkB,GAAM,CAC3B,GAAI,EAAE,MAAQ,SAAU,CAClB,KAAK,gBACP,KAAK,cAAc,EACV,KAAK,oBAKV,KAAK,SAAS,UAAU,EAAG,KAAK,SAAS,cAAc,EACtD,KAAK,mBAAmB,EACpB,KAAK,WAAW,OAAO,EAC5B,KAAK,UAAU,QACjB,KAAK,UACF,cAAc,EACd,KAAMC,GAAaA,GAAY,KAAK,WAAW,QAAQ,CAAC,EAE3D,KAAK,WAAW,EAET,KAAK,WAAW,MAAM,UAAY,QAC3C,KAAK,eAAe,EACpB,KAAK,kBAAkB,GACd,KAAK,aACd,KAAK,kBAAkB,EAEzB,MACF,CAKA,IAAMlB,EAAM,KAAK,QAAQ,YAAY,YAAY,EAC3CmB,EACJ,EAAE,IAAI,YAAY,IAAMnB,GAKvB,EAAE,QACD,UAAU,KAAKA,CAAG,GAClB,EAAE,OAAS,MAAMA,EAAI,YAAY,CAAC,GAChCoB,EACH,KAAK,QAAQ,mBAAqB,OAAS,EAAE,QAC7C,KAAK,QAAQ,mBAAqB,SAChC,EAAE,SAAW,EAAE,UACjB,KAAK,QAAQ,mBAAqB,SAAW,EAAE,SAE9CD,GAAcC,IAChB,EAAE,eAAe,EACjB,EAAE,gBAAgB,EAClB,KAAK,kBAAkB,EAE3B,EAGA,SAAS,iBAAiB,UAAW,KAAK,cAAc,CAC1D,CAEA,oBAAoB,EAAG,CACrB,GAAI,CAAC,KAAK,YAAa,OAKvB,IAAMlD,EAAS,EAAE,aAAa,EAAE,CAAC,GAAK,EAAE,OAExC,GACE,OAAK,QAAQ,SAASA,CAAM,GAC5BA,GAAQ,UAAU,IAAIb,EAAQ,MAAM,EAAE,GACtCa,GAAQ,UAAU,IAAIb,EAAQ,OAAO,EAAE,GACvCa,GAAQ,UAAU,IAAIb,EAAQ,cAAc,EAAE,GAC9Ca,GAAQ,UAAU,IAAIb,EAAQ,WAAW,EAAE,GAC3Ca,GAAQ,UAAU,IAAIb,EAAQ,QAAQ,EAAE,IAKtC,MAAK,WAAW,SAASa,CAAM,EAInC,IAAI,KAAK,WAAW,MAAM,UAAY,OAAQ,CAC5C,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,MACF,CAEI,EAAE,SAAW,IACjB,EAAE,eAAe,EAEjB,KAAK,aAAa,UAAU,CAAC,GAC/B,CAcA,cAAcL,EAAQwD,EAASC,EAAS,CACtC,GAAI,OAAO,SAAS,mBAAsB,WAAY,OAAO,KAC7D,IAAMC,EAAO1D,EAAO,MAAQA,EAAO,OACnC,GAAI,EAAE0D,EAAO,GAAI,OAAO,KAExB,QAAWC,KAAM,SAAS,kBAAkBH,EAASC,CAAO,EAAG,CAG7D,GAAIE,EAAG,QAAQ,YAAY,IAAMC,EAAS,YAAY,EAAG,SACzD,IAAMC,EAAOF,EAAG,sBAAsB,EAChCG,EACJ,KAAK,IAAID,EAAK,MAAO7D,EAAO,KAAOA,EAAO,KAAK,EAC/C,KAAK,IAAI6D,EAAK,KAAM7D,EAAO,IAAI,EAC3B+D,EACJ,KAAK,IAAIF,EAAK,OAAQ7D,EAAO,IAAMA,EAAO,MAAM,EAChD,KAAK,IAAI6D,EAAK,IAAK7D,EAAO,GAAG,EAC/B,GAAI,EAAA8D,GAAY,GAAKC,GAAY,IAC5BD,EAAWC,EAAYL,GAAQ5E,GAAqB,OAAO6E,CAClE,CACA,OAAO,IACT,CASA,MAAM,qBAAqBK,EAASC,EAASjE,EAAQ,CAGnD,KAAK,aAAa,gBAAgB,EAElC,IAAMkE,EAAoB,KAAK,QAAQ,MAAM,cAC7C,KAAK,QAAQ,MAAM,cAAgB,OACnC,IAAMC,GACHnE,EAAS,KAAK,cAAcA,EAAQgE,EAASC,CAAO,EAAI,OACzD,SAAS,iBAAiBD,EAASC,CAAO,EAC5C,KAAK,QAAQ,MAAM,cAAgBC,GAAqB,GAExD,IAAME,EACJD,GAAY,UAAUE,GAAU,SAAS,GAAK,SAAS,KACnDC,EAAgBF,EAAU,sBAAsB,EAIhDG,EACJD,EAAc,MAAQ,GACjBN,EAAUM,EAAc,MAAQA,EAAc,MAC/C,EACAE,EACJF,EAAc,OAAS,GAClBL,EAAUK,EAAc,KAAOA,EAAc,OAC9C,EAEAG,EAASC,GACeN,EAC5BG,EACAC,CACF,EAIAC,EAAO,eACLN,GAAcA,IAAeC,EACzBO,GAAoDR,CAAW,EAC/D,KAEN,KAAK,gBAAkB,CACrB,UAAAC,EACA,UAAAG,EACA,UAAAC,EACA,OAAAC,EACA,OAAoCN,GAAcC,CACpD,EAEA,KAAK,oBAAoBJ,EAASC,CAAO,EACzC,SAAS,KAAK,UAAU,OAAOzE,EAAQ,cAAc,GAEjD,KAAK,qBAAqB,OAAS,GAAK,KAAK,wBAC/C,KAAK,0BAA0B,EAGjC,KAAK,eAAewE,EAASC,CAAO,CACtC,CAEA,2BAA4B,CAC1B,IAAMG,EAAY,KAAK,WAAW,cAChC,IAAI5E,EAAQ,qBAAqB,EACnC,EACK4E,GACLQ,GAAyBR,EAAW,KAAK,qBAAuB,CAAC,EAAG,CAClE,QAAS,KAAK,QACd,OAASzE,GAAY,KAAK,aAAaA,CAAO,EAC9C,SAAU,IAAM,KAAK,0BAA0B,EAC/C,QAAS,KAAK,sBAAwB,EAAI,CAC5C,CAAC,CACH,CAEA,yBAA0B,CACxB,KAAK,oBAAsB,CAAC,EAC5B,KAAK,sBAAwB,GAC7B,IAAMyE,EAAY,KAAK,WAAW,cAChC,IAAI5E,EAAQ,qBAAqB,EACnC,EACI4E,IACFA,EAAU,UAAY,GACtBA,EAAU,UAAU,OAAO5E,EAAQ,MAAM,EAE7C,CAEA,eAAeM,EAAGC,EAAG,CACnB,KAAK,WAAW,MAAM,QAAU,QAGhC,IAAM8E,EADiBC,EACe,EAChCC,EAASF,EAAe,GACxBG,EAAc,OAAO,WACrBC,EAAe,OAAO,YAKtBC,EAAU,KAAK,WAAW,sBAAsB,EAChDC,EAAWD,EAAQ,OAAS,IAE5B1B,EAAU1D,EAAI+E,EACdpB,EAAU1D,EAAI8E,EAEhBO,EAAY5B,EAAUuB,EACtBM,EAAY5B,EAAUoB,EAEtBO,EAAYD,EAAWH,IACzBI,EAAY5B,EAAUuB,EAASI,GAIjCC,EAAY,KAAK,IAAIA,EAAWJ,EAAcG,EAAW,EAAE,EAC3DC,EAAY,KAAK,IAAI,GAAIA,CAAS,EAE9BC,EAAYH,EAAQ,OAASD,IAC/BI,EAAYJ,EAAeC,EAAQ,OAAS,IAE9CG,EAAY,KAAK,IAAI,GAAIA,CAAS,EAElC,KAAK,WAAW,MAAM,KAAO,GAAGD,CAAS,KACzC,KAAK,WAAW,MAAM,IAAM,GAAGC,CAAS,KAExC,KAAK,aAAa,MAAQ,GAC1B,WAAW,IAAM,KAAK,aAAa,MAAM,EAAG,EAAE,CAChD,CAEA,gBAAiB,CACf,KAAK,WAAW,MAAM,QAAU,OAChC,KAAK,aAAa,MAAM,OAAS,OACjC,KAAK,gBAAkB,KACvB,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,cAAc,aAAa,EACZ,KAAK,WAAY,UAAU,MAAM,EAEjD,KAAK,aACP,SAAS,KAAK,UAAU,IAAI7F,EAAQ,cAAc,CAEtD,CAWA,kBAAkB8F,EAAQ,CAexB,GAdA,KAAK,cAAgBA,EACrB,KAAK,QAAQ,UAAU,OAAO9F,EAAQ,eAAgB8F,CAAM,EACxDA,IACF,KAAK,mBAAmB,EAMxB,KAAK,WACF,iBAAiB,IAAI9F,EAAQ,OAAO,EAAE,EACtC,QAAS+F,GAAYA,EAAQ,OAAO,CAAC,GAGtC,KAAK,OAAQ,CAKf,IAAMC,EAAQF,EACV,KAAK,QAAQ,oBACb,KAAK,QAAQ,oBACjB,KAAK,OAAO,aAAa,aAAcE,CAAK,EAC5C,KAAK,OAAO,UAAYF,EAASG,GAAmBC,GACpD,IAAMjF,EAAO,KAAK,OACf,QAAQ,IAAIjB,EAAQ,sBAAsB,EAAE,GAC3C,cAAc,IAAIA,EAAQ,YAAY,EAAE,EACxCiB,IAAMA,EAAK,YAAc+E,EAC/B,CAIA,GAAI,CACF,aAAa,QAAQtE,GAA4B,OAAOoE,CAAM,CAAC,CACjE,MAAQ,CAER,CACF,CAEA,mBAAoB,CAClB,KAAK,YAAc,CAAC,KAAK,YAKrB,KAAK,aAAa,KAAK,WAAW,EAGlC,KAAK,aAAe,KAAK,eAAe,KAAK,kBAAkB,EAAK,EACxE,KAAK,YAAY,UAAU,OAAO9F,EAAQ,OAAQ,KAAK,WAAW,EAClE,KAAK,YAAY,aAAa,eAAgB,OAAO,KAAK,WAAW,CAAC,EACtE,KAAK,QAAQ,UAAU,OAAOA,EAAQ,OAAQ,KAAK,WAAW,EAC9D,SAAS,KAAK,UAAU,OAAOA,EAAQ,eAAgB,KAAK,WAAW,EAElE,KAAK,aACR,KAAK,eAAe,EAOtB,KAAK,QAAQ,uBAAwB,CAAC,KAAK,WAAW,CAAC,CACzD,CAEA,MAAM,aAAc,CAElB,GAAI,MAAK,SACL,GAAC,KAAK,aAAa,MAAM,KAAK,GAAK,CAAC,KAAK,iBAC7C,MAAK,QAAU,GAIX,KAAK,eAAc,KAAK,aAAa,SAAW,IACpD,GAAI,CACF,MAAM,KAAK,gBAAgB,CAC7B,QAAE,CACA,KAAK,QAAU,GACX,KAAK,eAAc,KAAK,aAAa,SAAW,GACtD,EACF,CAEA,MAAM,iBAAkB,CAOtB,IAAMmG,EAAW,KAAK,gBAChBlF,EAAO,KAAK,aAAa,MAGzBmF,EAAW,MAAM,KAAK,aAAa,eAAe,EAGxD,GAAI,KAAK,kBAAoBD,EAAU,OAIvC,IAAMxF,EAAK0F,GAAS,EACdC,EAAc,KAAK,oBACrB,CAAC,GAAG,KAAK,mBAAmB,EAC5B,CAAC,EAGC,CAACC,EAAmBrF,CAAW,EAAI,MAAM,QAAQ,IAAI,CACzD,KAAK,qBAAqBkF,EAAU,UAAWzF,CAAE,EACjD,QAAQ,IACN2F,EAAY,IAAKnG,GACf,KAAK,qBAAqBA,EAAS,aAAcQ,CAAE,CACrD,CACF,CACF,CAAC,EAKD,GAAI,KAAK,kBAAoBwF,EAAU,OAEvC,IAAMnF,EAAU,CACd,KAAAC,EACA,UAAWkF,EAAS,UACpB,UAAWA,EAAS,UACpB,UAAWA,EAAS,UACpB,OAAQA,EAAS,OACjB,YAAa,WACb,OAAQA,EAAS,OACjB,OAAQ,GACR,OAAQ,OACR,KAAM,SAAS,SACf,GAAAxF,EACA,QAAS,CAAC,EACV,OAAQ,KAAK,QAAQ,MAAM,MAAQ,KAAK,QAAQ,UAChD,SAAU6F,EAAiB,KAAK,QAAQ,MAAM,EAAE,GAAK,KACrD,UAAW,IAAI,KAAK,EAAE,YAAY,EAClC,YAAAtF,EACA,KAA0B,KAAK,WAAY,UAAU,QAAQ,GAAK,KAClE,SACsB,KAAK,WAAY,UAAU,YAAY,GAAK,KAGlE,KAAM,CAAC,EACP,WAAY,KACZ,QAASuF,GAAe,EACxB,kBAAAF,CACF,EAEAG,EAAY1F,EAAS,UAAW,KAAK,OAAO,CAAC,EAE7C,KAAK,SAAS,KAAKA,CAAO,EAC1B,KAAK,aAAa,EAClB,IAAM2F,EAAU,KAAK,kBAAkB3F,CAAO,EAG9C,KAAK,QAAQ,IACX,KAAK,MAAM,kBAAmB,CAAC2F,CAAO,EAAG,CAAE,QAASA,CAAQ,CAAC,CAC/D,EACA,KAAK,oBAAoB3F,CAAO,EAChC,KAAK,eAAe,EACpB,KAAK,kBAAkB,EAEvB,IAAMS,EAAS,KAAK,SAAS,IAAI,OAAOT,EAAQ,EAAE,CAAC,EAC/CS,GACF,KAAK,kBAAkBA,EAAQT,CAAO,CAE1C,CAEA,oBAAoBA,EAAS,CAC3B,KAAK,QAAQ,OAAOA,CAAO,CAC7B,CAOA,YAAYS,EAAQT,EAAS,CAC3BS,EAAO,iBAAiB,aAAc,IACpC,KAAK,mBAAmBA,EAAQT,CAAO,CACzC,EACAS,EAAO,iBAAiB,aAAc,IAAM,CAC1C,WAAW,IAAM,CACf,IAAMsE,EAAU,KAAK,WAAW/E,EAAQ,EAAE,EACtC+E,GAAW,CAACA,EAAQ,QAAQ,QAAQ,GACtCA,EAAQ,OAAO,CAEnB,EAAG,GAAG,CACR,CAAC,EAEDtE,EAAO,iBAAiB,QAAUmF,GAAM,CAOtC,GANAA,EAAE,gBAAgB,EAClB,KAAK,WAAW5F,EAAQ,EAAE,GAAG,OAAO,EAKhC,KAAK,qBAAqB,QAAQ,MAAQ,OAAOA,EAAQ,EAAE,EAAG,CAChE,KAAK,mBAAmB,EACxB,MACF,CACA,KAAK,kBAAkBS,EAAQT,CAAO,CACxC,CAAC,EAIDS,EAAO,iBAAiB,UAAYmF,GAAM,EACpCA,EAAE,MAAQ,SAAWA,EAAE,MAAQ,OACjCA,EAAE,eAAe,EACjBnF,EAAO,MAAM,EAEjB,CAAC,CACH,CAEA,mBAAmBA,EAAQT,EAAS,CAMlC,GALwB,KAAK,WAAW,cACtC,IAAIhB,EAAQ,cAAc,cAAc6G,EAAa7F,EAAQ,EAAE,CAAC,IAClE,GAGI,KAAK,WAAWA,EAAQ,EAAE,EAAG,OAEjC,IAAM+E,EAAUe,GAAc9F,EAAS,KAAK,QAAS,KAAK,MAAM,EAChE,KAAK,WAAW,YAAY+E,CAAO,EAEnCgB,EAAuBhB,EAAUnF,GAAQ,KAAK,aAAaA,CAAG,CAAC,EAE/D,WAAW,IAAM,CACfoG,GAAwBjB,EAAStE,CAAM,CACzC,EAAG,EAAE,EAELsE,EACG,cAAc,IAAI/F,EAAQ,aAAa,EAAE,EACzC,iBAAiB,QAAU4G,GAAM,CAChCA,EAAE,gBAAgB,EAClBb,EAAQ,OAAO,CACjB,CAAC,EAEHA,EAAQ,iBAAiB,aAAc,IAAMA,EAAQ,OAAO,CAAC,CAC/D,CAEA,aAAc,CACR,KAAK,WAAW,OAAO,EACzB,KAAK,WAAW,EAEhB,KAAK,UAAU,CAEnB,CAEA,WAAY,CACV,KAAK,mBAAmB,EAEnB,KAAK,YACR,KAAK,UAAY,IAAIkB,GAAU,CAC7B,WAAY,KAAK,WACjB,QAAS,KAAK,QACd,OAAQ,KAAK,OACb,YAAa,SAAS,SACtB,YAAa,IAAM,KAAK,SACxB,QAAS,KAAK,QAEd,UAAW,KAAK,aAAa,CAC3B,sBAAuB,IAAM,CAC3B,KAAK,WAAW,EAGX,KAAK,aAAa,KAAK,kBAAkB,CAChD,EACA,mBAAqBjG,GAAY,KAAK,qBAAqBA,CAAO,EAClE,aAAeA,GAAY,KAAK,qBAAqBA,CAAO,EAC5D,sBAAuB,CAACb,EAASY,IAC/B,KAAK,qBAAqBZ,EAAS,aAAcY,CAAS,EAC5D,QAAS,CAACC,EAASC,EAAMC,IACvB,KAAK,SAASF,EAASC,EAAMC,CAAW,EAC1C,SAAWP,GAAO,KAAK,cAAcA,CAAE,EACvC,cAAe,CAACI,EAAWI,IACzB,KAAK,YAAYJ,EAAWI,CAAO,EACrC,cAAe,CAACR,EAAIM,IAAS,CACtB,KAAK,YAAYN,EAAIM,CAAI,GAG9B,KAAK,SAAS,oBAAoBN,CAAE,CACtC,EACA,YAAa,CAACI,EAAWI,EAASF,IAAS,CACpC,KAAK,UAAUF,EAAWI,EAASF,CAAI,GAC5C,KAAK,SAAS,oBAAoBF,CAAS,CAC7C,EACA,SAAU,IAAM,KAAK,UAAU,EAC/B,IAAK,CAACD,EAAQD,IAAW,KAAK,IAAIC,EAAQD,CAAM,EAChD,wBAAyB,CAACF,EAAIY,IAC5B,KAAK,sBAAsBZ,EAAIY,CAAK,EACtC,sBAAuB,CAACR,EAAWI,EAASI,IAC1C,KAAK,oBAAoBR,EAAWI,EAASI,CAAK,EACpD,iBAAmB2F,GAAa,KAAK,kBAAkBA,CAAQ,EAC/D,gBAAkBA,GAAa,KAAK,iBAAiBA,CAAQ,EAC7D,cAAe,CAACA,EAAUC,IACxB,KAAK,mBAAmBD,EAAUC,CAAK,EACzC,YAAa,CAACxG,EAAIS,IAAW,KAAK,iBAAiBT,EAAIS,CAAM,EAC7D,UAAW,CAACT,EAAIU,IAAS,KAAK,eAAeV,EAAIU,CAAI,EACrD,cAAe,CAACV,EAAIW,IAClB,KAAK,mBAAmBX,EAAIW,CAAQ,EACtC,iBAAmBN,GAAY,CAC7B,GAAI,CACF,eAAe,QAAQkB,GAAoB,OAAOlB,EAAQ,EAAE,CAAC,CAC/D,MAAQ,CAAC,CACT,KAAK,YAAYA,EAAQ,IAAI,CAC/B,EACA,eAAiBJ,GAAQ,KAAK,aAAaA,CAAG,EAC9C,QAAS,IAAM,KAAK,WAAW,CACjC,CAAC,CACH,CAAC,GAEH,KAAK,UAAU,KAAK,EAMpB,KAAK,yBAAyB,EAM9B,KAAK,iBAAmB,WAAW,IAAM,CACvC,KAAK,iBAAmB,KACxB,KAAK,mBAAsB,GAAM,CAC/B,IAAMC,EAAS,EAAE,aAAa,EAAE,CAAC,GAAK,EAAE,OACxC,GACE,CAAC,KAAK,UAAU,IAAI,SAASA,CAAM,GACnC,CAAC,KAAK,SAAS,SAASA,CAAM,GAC9B,CAAC,KAAK,kBAAkBA,CAAM,EAC9B,CAGA,GAAI,KAAK,UAAU,QAAQ,EAAG,OAC9B,KAAK,WAAW,CAClB,CACF,EACA,SAAS,iBAAiB,YAAa,KAAK,kBAAkB,CAChE,EAAG,CAAC,CACN,CAEA,YAAa,CACX,KAAK,WAAW,MAAM,EACtB,KAAK,yBAAyB,CAChC,CAOA,0BAA2B,CACrB,KAAK,mBACP,aAAa,KAAK,gBAAgB,EAClC,KAAK,iBAAmB,MAEtB,KAAK,qBACP,SAAS,oBAAoB,YAAa,KAAK,kBAAkB,EACjE,KAAK,mBAAqB,KAE9B,CAOA,IAAI,qBAAsB,CACxB,OAAO,KAAK,UAAU,QAAU,IAClC,CAIA,kBAAkBY,EAAQT,EAAS,CACjC,KAAK,SAAS,KAAKS,EAAQT,CAAO,EAClC,KAAK,qBAAqBA,CAAO,CACnC,CAYA,qBAAqBA,EAAS,CACvBA,GACL,KAAK,QAAQ,kBAAmB,CAAC,KAAK,kBAAkBA,CAAO,CAAC,CAAC,CACnE,CAUA,QAAQsC,EAAMT,EAAM,CAIlB,IAAMT,EAA8B,KAAK,QAAQkB,CAAI,EACrD,GAAI,OAAOlB,GAAY,WACvB,GAAI,CACFA,EAAQ,GAAGS,CAAI,CACjB,OAASpC,EAAK,CACZ,QAAQ,KAAK,aAAa6C,CAAI,iBAAkB7C,CAAG,CACrD,CACF,CAEA,oBAAqB,CAGnB,KAAK,UAAU,MAAM,CACvB,CAEA,2BAA4B,CAC1B,KAAK,UAAU,aAAa,CAC9B,CAEA,aAAa2G,EAAU,CACrB,KAAK,cAAc,EAInB,KAAK,qBACH,KAAK,WAAW,eAAiB,SAAS,cAE5C,IAAMC,EAAW,SAAS,cAAc,KAAK,EAC7CA,EAAS,UAAYrH,EAAQ,SAC7BqH,EAAS,aAAa,OAAQ,QAAQ,EACtCA,EAAS,aAAa,aAAc,MAAM,EAC1CA,EAAS,aAAa,aAAc,KAAK,QAAQ,iBAAiB,EAElE,IAAMC,EAAM,SAAS,cAAc,KAAK,EACxCA,EAAI,UAAYtH,EAAQ,aACxBsH,EAAI,IAAMF,EACVE,EAAI,IAAM,KAAK,QAAQ,kBAEvB,IAAMC,EAAW,SAAS,cAAc,QAAQ,EAChDA,EAAS,KAAO,SAChBA,EAAS,UAAYvH,EAAQ,eAC7BuH,EAAS,aAAa,aAAc,KAAK,QAAQ,KAAK,EACtDA,EAAS,UAAY,UACrBA,EAAS,iBAAiB,QAAS,IAAM,KAAK,cAAc,CAAC,EAE7DF,EAAS,YAAYC,CAAG,EACxBD,EAAS,YAAYE,CAAQ,EAE7BF,EAAS,iBAAiB,QAAUT,GAAM,CACpCA,EAAE,SAAWS,GAAU,KAAK,cAAc,CAChD,CAAC,EAED,KAAK,WAAW,YAAYA,CAAQ,EACpC,KAAK,gBAAkBA,EAMvB,KAAK,wBAA2BT,GAAM,CAChCA,EAAE,MAAQ,QACdA,EAAE,eAAe,EACjBW,EAAS,MAAM,EACjB,EACA,SAAS,iBAAiB,UAAW,KAAK,wBAAyB,EAAI,EAEvEA,EAAS,MAAM,CACjB,CAEA,eAAgB,CACd,GAAI,CAAC,KAAK,gBAAiB,OACvB,KAAK,0BACP,SAAS,oBACP,UACA,KAAK,wBACL,EACF,EACA,KAAK,wBAA0B,MAEjC,KAAK,gBAAgB,OAAO,EAC5B,KAAK,gBAAkB,KACvB,IAAMC,EACJ,KAAK,qBAEP,KAAK,qBAAuB,KACxBA,GAAa,aAAaA,EAAY,QAAQ,CACpD,CAMA,kBAAkB3G,EAAQ,CACxB,MAAO,EAAQA,GAAQ,UAAU,IAAIb,EAAQ,QAAQ,EAAE,CACzD,CAQA,aAAaW,EAAI,CACf,OAAO,KAAK,SAAS,KAAM8G,GAAMpF,EAAOoF,EAAE,GAAI9G,CAAE,CAAC,CACnD,CASA,WAAWA,EAAI,CACb,OACE,KAAK,YAAY,cACf,IAAIX,EAAQ,OAAO,cAAc6G,EAAalG,CAAE,CAAC,IACnD,GAAK,IAET,CAWA,SAAS+G,EAAazG,EAAMC,EAAc,CAAC,EAAG,CAC5C,IAAMF,EACJ,OAAO0G,GAAgB,UAAYA,IAAgB,KAC/CA,EACA,KAAK,aAC8CA,CACnD,EACN,GAAI,CAAC1G,EAAS,OAAO,KAChBA,EAAQ,UAASA,EAAQ,QAAU,CAAC,GACzC,IAAM2G,EAAQ,CACZ,GAAItB,GAAS,EACb,SAAU,KACV,KAAApF,EACA,OAAQ,KAAK,QAAQ,MAAM,MAAQ,KAAK,QAAQ,UAChD,SAAUuF,EAAiB,KAAK,QAAQ,MAAM,EAAE,GAAK,KACrD,UAAW,IAAI,KAAK,EAAE,YAAY,EAClC,YAAAtF,CACF,EACAF,EAAQ,QAAQ,KAAK2G,CAAK,EAC1B,KAAK,aAAa,EAClB,IAAMC,EAAa,KAAK,kBAAkB5G,CAAO,EAC3C6G,EAAkB,KAAK,gBAAgBF,CAAK,EAClD,YAAK,MAAM,cAAe,CAACC,EAAYC,CAAe,EAAG,CACvD,QAASD,EACT,MAAOC,CACT,CAAC,EACMF,CACT,CAWA,YAAY5G,EAAWI,EAAS,CAC9B,IAAMH,EAAU,KAAK,aAAaD,CAAS,EACrC+G,EACJ9G,GAAS,SAAS,UAAW+G,GAAM1F,EAAO0F,EAAE,GAAI5G,CAAO,CAAC,GAAK,GAC/D,GAAI2G,EAAQ,EAAG,MAAO,GACtB,IAAMjH,EAASmH,GAAchH,EAAQ,QAAQ8G,CAAK,EAAG9G,EAAQ,EAAE,EAC/D,GAAI,CAAC,KAAK,SAAS,eAAgBH,CAAM,EAAG,MAAO,GAEnD,GAAM,CAAC8G,CAAK,EAAI3G,EAAQ,QAAQ,OAAO8G,EAAO,CAAC,EAC/C,KAAK,aAAa,EAClB,IAAMF,EAAa,KAAK,kBAAkB5G,CAAO,EAC3C6G,EAAkB,KAAK,gBAAgBF,CAAK,EAClD,YAAK,MAAM,gBAAiB,CAACC,EAAYC,CAAe,EAAG,CACzD,QAASD,EACT,MAAOC,CACT,CAAC,EACM,EACT,CAeA,YAAYlH,EAAIM,EAAM,CACpB,IAAMD,EAAU,KAAK,aAAaL,CAAE,EAC9BsH,EAAO,OAAOhH,GAAQ,EAAE,EAAE,KAAK,EAErC,GADI,CAACD,GAAW,CAACiH,GAAQA,IAASjH,EAAQ,MACtC,CAAC,KAAK,SAAS,eAAgBkH,GAAgBlH,CAAO,CAAC,EAAG,MAAO,GAErEA,EAAQ,KAAOiH,EACfjH,EAAQ,SAAW,IAAI,KAAK,EAAE,YAAY,EAI1C0F,EAAY1F,EAAS,SAAU,KAAK,OAAO,CAAC,EAG5C,KAAK,SACF,IAAI,OAAOA,EAAQ,EAAE,CAAC,GACrB,aACA,aACA,GAAG,KAAK,QAAQ,sBAAsB,GAAGA,EAAQ,IAAI,EACvD,EACF,KAAK,aAAa,EAClB,IAAMmH,EAAS,KAAK,kBAAkBnH,CAAO,EAC7C,YAAK,MAAM,iBAAkB,CAACmH,CAAM,EAAG,CAAE,QAASA,CAAO,CAAC,EACnD,EACT,CAUA,UAAUpH,EAAWI,EAASF,EAAM,CAClC,IAAMD,EAAU,KAAK,aAAaD,CAAS,EACrC4G,EAAQ3G,GAAS,SAAS,KAAM+G,GAAM1F,EAAO0F,EAAE,GAAI5G,CAAO,CAAC,EAC3D8G,EAAO,OAAOhH,GAAQ,EAAE,EAAE,KAAK,EAErC,GADI,CAAC0G,GAAS,CAACM,GAAQA,IAASN,EAAM,MAClC,CAAC,KAAK,SAAS,aAAcK,GAAcL,EAAO3G,EAAQ,EAAE,CAAC,EAC/D,MAAO,GAGT2G,EAAM,KAAOM,EACbN,EAAM,SAAW,IAAI,KAAK,EAAE,YAAY,EACxC,KAAK,aAAa,EAClB,IAAMC,EAAa,KAAK,kBAAkB5G,CAAO,EAC3C6G,EAAkB,KAAK,gBAAgBF,CAAK,EAClD,YAAK,MAAM,eAAgB,CAACC,EAAYC,CAAe,EAAG,CACxD,QAASD,EACT,MAAOC,CACT,CAAC,EACM,EACT,CAEA,gBAAgB,CACd,GAAAlH,EACA,KAAAM,EACA,OAAAmH,EACA,SAAAC,EACA,UAAAC,EACA,YAAApH,EACA,SAAAqH,EAGA,UAAAC,EAAY,IACd,EAAG,CACD,MAAO,CACL,GAAA7H,EACA,KAAAM,EACA,OAAAmH,EACA,SAAUC,GAAY,KACtB,UAAAC,EACA,YAAapH,GAAe,CAAC,EAC7B,SAAUqH,GAAY,KACtB,UAAWE,GAAmBD,CAAS,CACzC,CACF,CAOA,kBAAkBxH,EAAS,CACzB,MAAO,CAIL,cAAe,EACf,GAAIA,EAAQ,GACZ,KAAMA,EAAQ,KACd,SAAUA,EAAQ,UAAY,KAC9B,OAAQA,EAAQ,QAAU,KAC1B,KAAMA,EAAQ,MAAQ,SAAS,SAC/B,SAAUA,EAAQ,SAAW,CAAC,GAAG,IAAK2G,GACpC,KAAK,gBAAgBA,CAAK,CAC5B,EACA,OAAQ3G,EAAQ,OAGhB,SAAUA,EAAQ,UAAY,KAI9B,QAAS0H,GAAiB1H,EAAQ,OAAO,EACzC,UAAWA,EAAQ,UACnB,YAAaA,EAAQ,aAAe,CAAC,EACrC,OAAQA,EAAQ,QAAU,OAC1B,KAAMA,EAAQ,MAAQ,KACtB,SAAUA,EAAQ,UAAY,KAG9B,KAAMA,EAAQ,KAAO,CAAC,GAAGA,EAAQ,IAAI,EAAI,CAAC,EAG1C,UAAWyH,GAAmBzH,EAAQ,SAAS,EAC/C,WAAYA,EAAQ,YAAc,KAClC,QAASA,EAAQ,QAAU,CAAE,GAAGA,EAAQ,OAAQ,EAAI,KACpD,kBAAmBA,EAAQ,mBAAqB,IAClD,CACF,CASA,iBAAiBL,EAAIS,EAAQ,CAC3B,GAAI,CAACuH,EAAS,SAASvH,CAAM,EAAG,MAAO,GACvC,IAAMJ,EAAU,KAAK,aAAaL,CAAE,EACpC,GAAI,CAACK,EAAS,MAAO,GAIrB,GAAIA,EAAQ,SAAWI,EAAQ,MAAO,GACtC,IAAMoB,EAAWxB,EAAQ,OACzBA,EAAQ,OAASI,EAGjBJ,EAAQ,WACNI,IAAW,WAAa,IAAI,KAAK,EAAE,YAAY,EAAI,KAGrDsF,EAAY1F,EAAS,SAAU,KAAK,OAAO,EAAG,CAC5C,KAAMwB,EACN,GAAIpB,CACN,CAAC,EAGD,IAAMK,EAAS,KAAK,SAAS,IAAI,OAAOT,EAAQ,EAAE,CAAC,EAC/CS,GAAQ,KAAK,sBAAsBT,EAASS,CAAM,EACtD,KAAK,aAAa,EAId,KAAK,WAAW,OAAO,GAAG,KAAK,UAAU,QAAQ,EACrD,IAAMmH,EAAU,KAAK,kBAAkB5H,CAAO,EAI9C,YAAK,MACH,yBACA,CAAC4H,CAAO,EACR,CAAE,QAASA,CAAQ,EACnB,CACE,KAAMpG,EACN,GAAIpB,CACN,CACF,EACO,EACT,CAgBA,QAAQyH,EAAM,CACZ,OAAIA,GAAQ,OACN,OAAOA,GAAS,UAChB,OAAOA,EAAK,MAAS,UAAY,CAACA,EAAK,KAAK,KAAK,GAAU,IAEjE,KAAK,QAAQ,KAAOA,GAAQ,OAI5B,KAAK,UAAU,MAAM,EACjB,KAAK,WAAW,OAAO,GAAG,KAAK,UAAU,QAAQ,EAC9C,GACT,CAQA,QAAS,CACP,OAAOC,GAAQ,KAAK,QAAQ,KAAM,KAAK,OAAO,CAChD,CASA,WAAY,CACV,OAAOC,GAAW,KAAK,QAAQ,KAAM,KAAK,OAAO,CACnD,CAcA,IAAIjI,EAAQD,EAAQ,CAClB,OAAOmI,GAAkB,CACvB,IAAK,KAAK,QAAQ,IAClB,OAAAlI,EACA,OAAAD,EACA,KAAM,KAAK,QAAQ,KACnB,QAAS,KAAK,OAChB,CAAC,CACH,CAgBA,SAASC,EAAQD,EAAQ,CACvB,OAAO,KAAK,UAAY,QAAU,KAAK,IAAIC,EAAQD,CAAM,CAC3D,CASA,gBAAgBG,EAAS2G,EAAO,CAC9B,KAAK,aAAa,EAGd,KAAK,WAAW,OAAO,GAAG,KAAK,UAAU,QAAQ,EACrD,IAAMC,EAAa,KAAK,kBAAkB5G,CAAO,EAC3C6G,EAAkBF,EAAQ,KAAK,gBAAgBA,CAAK,EAAI,KAC9D,YAAK,MAAM,mBAAoB,CAACC,EAAYC,CAAe,EAAG,CAC5D,QAASD,EACT,MAAOC,CACT,CAAC,EACM,EACT,CASA,sBAAsBlH,EAAIY,EAAO,CAC/B,IAAMP,EAAU,KAAK,aAAaL,CAAE,EAEpC,MADI,CAACK,GACD,CAACiI,GAAiBjI,EAASO,EAAO,KAAK,UAAU,CAAC,EAAU,GACzD,KAAK,gBAAgBP,EAAS,IAAI,CAC3C,CASA,oBAAoBD,EAAWI,EAASI,EAAO,CAC7C,IAAMP,EAAU,KAAK,aAAaD,CAAS,EACrC4G,EAAQ3G,GAAS,SAAS,KAAM+G,GAAM1F,EAAO0F,EAAE,GAAI5G,CAAO,CAAC,EAEjE,MADI,CAACwG,GACD,CAACsB,GAAiBtB,EAAOpG,EAAO,KAAK,UAAU,CAAC,EAAU,GACvD,KAAK,gBAAgBP,EAAS2G,CAAK,CAC5C,CAeA,cAAc3G,EAASoC,EAAQ,CAC7B,KAAK,aAAa,EACd,KAAK,WAAW,OAAO,GAAG,KAAK,UAAU,QAAQ,EACrD,IAAM8F,EAAU,KAAK,kBAAkBlI,CAAO,EAC9C,YAAK,MAAM,kBAAmB,CAACkI,CAAO,EAAG,CAAE,QAASA,CAAQ,EAAG9F,CAAM,EAC9D,EACT,CAQA,eAAezC,EAAIU,EAAM,CACvB,GAAIA,IAAS,MAAQ,CAAC8H,EAAc,SAAS9H,CAAI,EAAG,MAAO,GAC3D,IAAML,EAAU,KAAK,aAAaL,CAAE,EACpC,GAAI,CAACK,EAAS,MAAO,GACrB,IAAMoI,EAAepI,EAAQ,MAAQ,KAKrC,OAAIoI,IAAiB/H,EAAa,IAClCL,EAAQ,KAAOK,EACfqF,EAAY1F,EAAS,aAAc,KAAK,OAAO,EAAG,CAChD,MAAO,OACP,KAAMoI,EACN,GAAI/H,CACN,CAAC,EACM,KAAK,cAAcL,EAAS,CACjC,MAAO,OACP,KAAMoI,EACN,GAAI/H,CACN,CAAC,EACH,CAQA,mBAAmBV,EAAIW,EAAU,CAC/B,GAAIA,IAAa,MAAQ,CAAC+H,EAAW,SAAS/H,CAAQ,EAAG,MAAO,GAChE,IAAMN,EAAU,KAAK,aAAaL,CAAE,EACpC,GAAI,CAACK,EAAS,MAAO,GACrB,IAAMsI,EAAmBtI,EAAQ,UAAY,KAC7C,OAAIsI,IAAqBhI,EAAiB,IAC1CN,EAAQ,SAAWM,EACnBoF,EAAY1F,EAAS,aAAc,KAAK,OAAO,EAAG,CAChD,MAAO,WACP,KAAMsI,EACN,GAAIhI,CACN,CAAC,EACM,KAAK,cAAcN,EAAS,CACjC,MAAO,WACP,KAAMsI,EACN,GAAIhI,CACN,CAAC,EACH,CASA,eAAeX,EAAI5B,EAAM,CACvB,GAAI,CAAC,MAAM,QAAQA,CAAI,EAAG,MAAO,GACjC,IAAMiC,EAAU,KAAK,aAAaL,CAAE,EACpC,GAAI,CAACK,EAAS,MAAO,GAIrB,IAAMwB,EAAW,CAAC,GAAIxB,EAAQ,MAAQ,CAAC,CAAE,EACnCuI,EAAc/G,EAAS,KAAK,IAAQ,EACpCyF,EAAOnJ,GAAcC,CAAI,EAC/B,OAAIkJ,EAAK,KAAK,IAAQ,IAAMsB,EAAoB,IAChDvI,EAAQ,KAAOiH,EACfvB,EAAY1F,EAAS,aAAc,KAAK,OAAO,EAAG,CAAE,MAAO,MAAO,CAAC,EAI5D,KAAK,cAAcA,EAAS,CACjC,MAAO,OACP,KAAMwB,EACN,GAAI,CAAC,GAAGyF,CAAI,CACd,CAAC,EACH,CAWA,YAAa,CACX,OAAOuB,GAAe,KAAK,kBAAkB,CAAC,CAChD,CAOA,kBAAkBtC,EAAU,CAC1B,IAAMuC,EAAOC,GAAYxC,GAAY,KAAK,kBAAkB,CAAC,EACvDyC,EAAMC,GAAMH,EAAMI,GAAUC,EAAe,CAAC,EAClD,OAAAC,GAAY,wBAAyBJ,CAAG,EAIjCA,CACT,CAOA,iBAAiBzC,EAAU,CACzB,IAAM8C,EAAUR,GAAetC,GAAY,KAAK,kBAAkB,CAAC,EAC7DyC,EAAMC,GAAMK,GAAWD,CAAO,EAAGH,GAAUK,EAAc,CAAC,EAChE,OAAAH,GAAY,uBAAwBJ,CAAG,EAChCA,CACT,CAUA,mBAAmBzC,EAAUC,EAAO,CAClC,IAAM6C,EAAUR,GAAetC,GAAY,KAAK,kBAAkB,CAAC,EACnEiD,GAAmBH,EAAS,CAC1B,QAAS,KAAK,QACd,OAAQ,KAAK,OACb,IAAKI,GAAgB,EACrB,MAAAjD,CACF,CAAC,CACH,CAKA,mBAAoB,CAClB,OAAO,KAAK,SAAS,IAAKnG,GAAY,KAAK,kBAAkBA,CAAO,CAAC,CACvE,CAQA,cAAcL,EAAI,CAChB,IAAMK,EAAU,KAAK,aAAaL,CAAE,EAEpC,GADI,CAACK,GACD,CAAC,KAAK,SAAS,iBAAkBkH,GAAgBlH,CAAO,CAAC,EAC3D,MAAO,GAGT,GADA,KAAK,eAAeL,CAAE,EAClB,KAAK,QAAQ,cAAgB,eAAgB,CAG/C,IAAM8C,EAASC,GACb,KAAK,kBAAkB,EAAE,OAAQ1C,GAAY,CAACqB,EAAOrB,EAAQ,GAAIL,CAAE,CAAC,EACpE,KAAK,kBAAkB,EACvB,SAAS,QACX,EACAgD,GAAoBF,CAAM,EAC1B,KAAK,aAAeA,CACtB,CACA,YAAK,MAAM,kBAAmB,CAAC9C,CAAE,EAAG,CAAE,GAAAA,CAAG,CAAC,EACnC,EACT,CAEA,eAAeA,EAAI,CACjB,KAAK,SAAS,OAAOA,CAAE,EACvB,KAAK,SAAW,KAAK,SAAS,OAAQK,GAAY,CAACqB,EAAOrB,EAAQ,GAAIL,CAAE,CAAC,CAC3E,CASA,eAAgB,CACd,KAAK,mBAAmB,EACxB,IAAM0J,EAAU,KAAK,SAIrB,GAHA,KAAK,SAAS,MAAM,EACpB,KAAK,SAAW,CAAC,EAEb,KAAK,QAAQ,cAAgB,gBAAkBA,EAAQ,OAAS,EAAG,CACrE,IAAMC,EAAa,IAAI,IAAID,EAAQ,IAAKrJ,GAAY,OAAOA,EAAQ,EAAE,CAAC,CAAC,EACjEyC,EAAS,KAAK,kBAAkB,EAAE,OACrCzC,GAAY,CAACsJ,EAAW,IAAI,OAAOtJ,EAAQ,EAAE,CAAC,CACjD,EACA2C,GAAoBF,CAAM,EAC1B,KAAK,aAAeA,CACtB,CACI,KAAK,WAAW,OAAO,GAAG,KAAK,UAAU,QAAQ,CACvD,CAUA,aAAa8G,EAAM,CACjB,IAAIC,EAAW,EACXC,EAAW,EACXC,EAAW,EACf,GAAI,CAAC,MAAM,QAAQH,CAAI,EAAG,MAAO,CAAE,SAAAC,EAAU,SAAAC,EAAU,SAAAC,CAAS,EAShE,GAAI,CAAC,KAAK,QACR,YAAK,cAAgB,CAAC,GAAI,KAAK,eAAiB,CAAC,EAAI,GAAGH,CAAI,EACrD,CAAE,SAAAC,EAAU,SAAAC,EAAU,SAAAC,CAAS,EAGxC,QAAWC,KAAQJ,EAAM,CACvB,GAAI,CAACI,GAAQA,EAAK,IAAM,MAAQ,OAAOA,EAAK,MAAS,SAAU,CAC7D,QAAQ,KAAK,kDAAmDA,CAAI,EAIpE,KAAK,aACH,IAAI,MAAM,iDAAiD,EAC3D,MACF,EACA,QACF,CACA,KAAK,eAAeA,EAAK,EAAE,EAE3B,IAAM3J,EAAU,CACd,GAAI2J,EAAK,GACT,KAAMA,EAAK,KACX,SAAUA,EAAK,UAAY,KAC3B,OAAQA,EAAK,QAAU,KACvB,YAAa,WACb,OAAQ,KACR,OAAQ,GACR,KAAMA,EAAK,MAAQ,SAAS,SAC5B,UAAW,KACX,UAAW,EACX,UAAW,EAGX,QAAS,MAAM,QAAQA,EAAK,OAAO,EAC/BA,EAAK,QACF,OACEhD,GACCA,GACA,OAAOA,GAAU,UACjBA,EAAM,IAAM,MACZ,OAAOA,EAAM,MAAS,QAC1B,EACC,IAAKA,IAAW,CACf,GAAGA,EACH,SAAUnB,EAAiBmB,EAAM,QAAQ,GAAK,KAC9C,GAAI,MAAM,QAAQA,EAAM,WAAW,EAC/B,CAAE,YAAaxI,GAAYwI,EAAM,WAAW,CAAE,EAC9C,CAAC,EACL,UAAWiD,GAAmBjD,EAAM,SAAS,CAC/C,EAAE,EACJ,CAAC,EACL,OAAQgD,EAAK,QAAU,KAAK,QAAQ,UAIpC,SAAUnE,EAAiBmE,EAAK,QAAQ,GAAK,KAI7C,QAASE,GAAiBF,EAAK,OAAO,EACtC,UAAWA,EAAK,WAAa,IAAI,KAAK,EAAE,YAAY,EAGpD,YAAa,MAAM,QAAQA,EAAK,WAAW,EACvCxL,GAAYwL,EAAK,WAAW,EAC5B,CAAC,EAEL,OACyBA,EAAK,SAAY,SACpC,WACAhC,EAAS,SAASgC,EAAK,MAAM,EAC3BA,EAAK,OACL,OAGR,KAAMxB,EAAc,SAASwB,EAAK,IAAI,EAAIA,EAAK,KAAO,KACtD,SAAUtB,EAAW,SAASsB,EAAK,QAAQ,EAAIA,EAAK,SAAW,KAC/D,KAAM,MAAM,QAAQA,EAAK,IAAI,EAAI,CAAC,GAAGA,EAAK,IAAI,EAAI,CAAC,EACnD,WAAYA,EAAK,YAAc,KAI/B,UAAWC,GAAmBD,EAAK,SAAS,EAC5C,QAASA,EAAK,SAAW,KACzB,kBAAmBA,EAAK,mBAAqB,IAC/C,EAKA,GAAIA,EAAK,MAAQA,EAAK,OAAS,SAAS,SAAU,CAChD3J,EAAQ,YAAc,WACtB,KAAK,SAAS,KAAKA,CAAO,EAC1B0J,IACA,QACF,CAEA,IAAMI,EAAWH,EAAK,OAASI,GAAcJ,EAAK,MAAM,EAAI,KAC5D,GAAIG,EACF9J,EAAQ,UAAY8J,EAAS,QAC7B9J,EAAQ,UAAY2J,EAAK,OAAO,UAChC3J,EAAQ,UAAY2J,EAAK,OAAO,UAChC3J,EAAQ,YAAc,WACtB,KAAK,SAAS,KAAKA,CAAO,EAC1B,KAAK,oBAAoBA,CAAO,EAChCwJ,QACK,CACL,KAAK,SAAS,KAAKxJ,CAAO,EAC1ByJ,IACA,IAAMO,EAAO,KAAK,kBAAkBhK,CAAO,EAC3C,KAAK,MAAM,sBAAuB,CAACgK,CAAI,EAAG,CAAE,QAASA,CAAK,CAAC,CAC7D,CACF,CAIA,YAAK,mBAAmB,EAEjB,CAAE,SAAAR,EAAU,SAAAC,EAAU,SAAAC,CAAS,CACxC,CAcA,kBAAmB,CACjB,IAAMO,EAAO,SAAS,SAClBT,EAAW,EACXC,EAAW,EACXC,EAAW,EAKf,GAAI,CAAC,KAAK,QAAS,MAAO,CAAE,SAAAF,EAAU,SAAAC,EAAU,SAAAC,CAAS,EAIzD,KAAK,mBAAmB,EACxB,KAAK,eAAe,EAChB,KAAK,YAAW,KAAK,UAAU,YAAcO,GAEjD,QAAWjK,KAAW,KAAK,SAAU,CAMnC,GALA,KAAK,QAAQ,OAAOA,EAAQ,EAAE,EAC9BA,EAAQ,OAAS,GACjBA,EAAQ,OAAS,KACjBA,EAAQ,UAAY,GAEhBA,EAAQ,MAAQA,EAAQ,OAASiK,EAAM,CACzCjK,EAAQ,YAAc,WACtBA,EAAQ,UAAY,KACpB0J,IACA,QACF,CAEA,IAAMI,EAAW9J,EAAQ,OAAS+J,GAAc/J,EAAQ,MAAM,EAAI,KAClE,GAAI8J,EACF9J,EAAQ,UAAY8J,EAAS,QAC7B9J,EAAQ,UAAYA,EAAQ,OAAO,UACnCA,EAAQ,UAAYA,EAAQ,OAAO,UACnCA,EAAQ,YAAc,WACtB,KAAK,oBAAoBA,CAAO,EAChCwJ,QACK,CAILxJ,EAAQ,UAAY,KACpBA,EAAQ,YAAc,WACtByJ,IACA,IAAMO,EAAO,KAAK,kBAAkBhK,CAAO,EAC3C,KAAK,MAAM,sBAAuB,CAACgK,CAAI,EAAG,CAAE,QAASA,CAAK,CAAC,CAC7D,CACF,CAKA,YAAK,iBAAmB,KAAK,qBAAqB,EAC9C,KAAK,WAAW,OAAO,GAAG,KAAK,UAAU,QAAQ,EACrD,KAAK,mBAAmB,EAEjB,CAAE,SAAAR,EAAU,SAAAC,EAAU,SAAAC,CAAS,CACxC,CAEA,qBAAqB1J,EAAS,CAExB,KAAK,eAAe,KAAK,kBAAkB,EAAK,EACpD,KAAK,QAAQ,qBAAqBA,CAAO,CAC3C,CAEA,oBAAoBV,EAAGC,EAAG,CACxB,KAAK,oBAAoB,EAEzB,IAAMkB,EAAS,SAAS,cAAc,KAAK,EAC3CA,EAAO,UAAY,GAAGzB,EAAQ,MAAM,IAAIA,EAAQ,cAAc,GAC9DyB,EAAO,MAAM,SAAW,WACxB,IAAM4D,EAAeC,EAAc,EACnC7D,EAAO,MAAM,KAAO,GAAGnB,EAAI+E,CAAY,KACvC5D,EAAO,MAAM,IAAM,GAAGlB,EAAI8E,CAAY,KACtC5D,EAAO,MAAM,UAAY,wBACzBA,EAAO,MAAM,cAAgB,OAE7B,KAAK,QAAQ,YAAYA,CAAM,EAC/B,KAAK,cAAgBA,CACvB,CAEA,qBAAsB,CACpB,KAAK,eAAe,OAAO,EAC3B,KAAK,cAAgB,IACvB,CAQA,sBAAsBV,EAAW,CAC/B,KAAK,QAAQ,sBAAsBA,CAAS,CAC9C,CAEA,6BAA6BC,EAASS,EAAQ,CAC5C,OAAO,KAAK,QAAQ,6BAA6BT,EAASS,CAAM,CAClE,CAEA,sBAAsBT,EAASS,EAAQ,CACrC,KAAK,QAAQ,eAAeT,EAASS,CAAM,CAC7C,CAEA,yBAA0B,CACxB,KAAK,QAAQ,eAAe,CAC9B,CAGA,IAAI,UAAW,CACb,OAAO,KAAK,SAAS,OACvB,CAGA,IAAI,iBAAkB,CACpB,OAAO,KAAK,SAAS,eACvB,CAEA,IAAI,2BAA4B,CAC9B,OAAO,KAAK,SAAS,SAAW,EAClC,CAEA,IAAI,0BAA0BmB,EAAO,CAC/B,KAAK,UAAS,KAAK,QAAQ,QAAUA,EAC3C,CAEA,IAAI,yBAA0B,CAC5B,OAAO,KAAK,SAAS,yBAA2B,IAClD,CAKA,iBAAiB5B,EAAS,CACxB,KAAK,WAAWA,EAAQ,EAAE,GAAG,OAAO,EAChC,KAAK,qBAAqB,QAAQ,MAAQ,OAAOA,EAAQ,EAAE,GAC7D,KAAK,mBAAmB,CAE5B,CAKA,SAAU,CAGJ,KAAK,cACP,SAAS,oBAAoB,mBAAoB,KAAK,WAAW,EACjE,KAAK,YAAc,MAIrB,KAAK,SAAS,QAAQ,EAClB,KAAK,kBACP,OAAO,oBAAoB,UAAW,KAAK,eAAe,EAC1D,KAAK,gBAAkB,MAErB,KAAK,mBACP,OAAO,oBAAoB,WAAY,KAAK,gBAAgB,EAC5D,KAAK,iBAAmB,MAE1B,KAAK,aAAe,KAGpB,KAAK,cAAgB,KAIrBkK,GAAe,EAIfC,GAAwB,EACxB,KAAK,mBAAmB,EACxB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,oBAAoB,EAEzB,KAAK,cAAc,QAAQ,EAC3B,KAAK,oBAAsB,CAAC,EAExB,KAAK,2BACP,SAAS,oBAAoB,YAAa,KAAK,yBAAyB,EAItE,KAAK,gBACP,SAAS,oBAAoB,UAAW,KAAK,cAAc,EAIzD,KAAK,SAAW,KAAK,QAAQ,YAC/B,KAAK,QAAQ,WAAW,YAAY,KAAK,OAAO,EAE9C,KAAK,YAAc,KAAK,WAAW,YACrC,KAAK,WAAW,WAAW,YAAY,KAAK,UAAU,EAEpD,KAAK,SAAW,KAAK,QAAQ,YAC/B,KAAK,QAAQ,WAAW,YAAY,KAAK,OAAO,EAGlD,SAAS,KAAK,UAAU,OAAOnL,EAAQ,cAAc,EAIrD,KAAK,cAAc,EAKnB,SAAS,cAAcoE,CAAQ,GAAG,OAAO,CAC3C,CAEA,cAAe,CAGb,KAAK,cAAc,EACnB,KAAK,gBAAkB,CACrBgH,GAAY,KAAK,WAAYC,GAAU,EAAGpL,EAAI,MAAM,EAIpDmL,GAAY,SAAUE,GAAgB,EAAGrL,EAAI,aAAa,CAC5D,CACF,CAEA,eAAgB,CACd,QAAWsL,KAAU,KAAK,iBAAmB,CAAC,EAAGA,EAAO,EACxD,KAAK,gBAAkB,CAAC,CAC1B,CACF,EAEOC,GAAQhM,GCh+ER,SAASiM,GAAqBC,EAAU,CAAC,EAAG,CACjD,GAAM,CAAE,SAAAC,EAAW,GAAM,GAAGC,CAAe,EAAIF,EACzCG,EAAa,IAAM,IAAIC,GAAeF,CAAc,EAC1D,OAAOD,EAAWE,EAAW,EAAIA,CACnC,CAcA,IAAOE,GAAQC",
|
|
6
6
|
"names": ["TAG_NAME", "ensureDefined", "getShadowRoot", "host", "now", "nextTask", "resolve", "channel", "yieldToBrowser", "scheduler", "createPaintYielder", "budgetMs", "last", "resume", "RENDERER_READS_BACK", "CAPTURE_STYLE_PROPERTIES", "holdsPaint", "width", "height", "canvas", "ctx", "measured", "maxCanvasDimension", "d", "fittingScale", "wanted", "limits", "maxDimension", "maxArea", "paintsPixels", "AUTO_SCALE", "AUTO_QUALITY", "rendererPromise", "loadRenderer", "error", "isUnpainted", "color", "effectiveBackgroundColor", "htmlBg", "bodyBg", "canEmbedWebFonts", "probe", "style", "extractFontFaceRules", "css", "blocks", "at", "open", "close", "fontRuleCache", "fetchFontRules", "href", "res", "isReadable", "sheet", "shimUnreadableFontRules", "enabled", "noop", "hrefs", "captureFilter", "skipIframeContent", "node", "TAG_NAME", "renderPage", "scale", "embedCrossOriginFonts", "fastCapture", "captureTimeout", "domToCanvas", "unshim", "width", "height", "attempt", "fittingScale", "left", "canvas", "createPaintYielder", "CAPTURE_STYLE_PROPERTIES", "paintsPixels", "paintBackdrop", "ctx", "cropRegion", "top", "sourceScale", "out", "cropViewport", "outputScale", "quality", "CLASSES", "HOST_PAGE_CLASSES", "MARKERS_HIDDEN_STORAGE_KEY", "IDS", "MARKER_SIZE", "MAX_SCREENSHOTS", "STATUSES", "STATUS_COLORS", "COMMENT_TYPES", "TYPE_COLORS", "PRIORITIES", "PRIORITY_COLORS", "REACTION_EMOJIS", "SELECTORS", "Z_INDEX", "CURSOR_SVG", "CURSOR_HOTSPOT", "CaptureFlow", "host", "autoScreenshot", "embedCrossOriginFonts", "fastCapture", "skipIframeContent", "captureTimeout", "onRegionCaptured", "onRegionPending", "onPlace", "onError", "e", "dx", "dy", "left", "top", "width", "height", "CLASSES", "region", "token", "stillMine", "render", "renderPage", "canvas", "scale", "cropViewport", "dataUrl", "cropRegion", "err", "AUTO_SCALE", "BROWSERS", "OPERATING_SYSTEMS", "UNKNOWN", "isGreaseBrand", "brand", "matchFirst", "table", "ua", "name", "re", "match", "captureContext", "win", "nav", "uaData", "browser", "b", "os", "SCROLLBAR", "webkitScrollbar", "selectors", "selector", "getStyles", "IDS", "Z_INDEX", "CLASSES", "MARKER_SIZE", "getGlobalStyles", "CURSOR_SVG", "CURSOR_HOTSPOT", "getReportStyles", "mountStyles", "target", "css", "fallbackId", "sheet", "constructSheet", "candidate", "parent", "style", "Sheet", "en_default", "es_default", "LOCALES", "en_default", "es_default", "DEFAULT_LOCALE", "detectLocale", "lang", "getStrings", "localeCode", "selected", "formatTemplate", "template", "n", "MINUTE_MS", "formatDuration", "ms", "strings", "totalMinutes", "totalHours", "minutes", "hours", "totalDays", "days", "TEXT_SNIPPET_MAX", "MAX_CLASS_PATH_DEPTH", "MAX_STRUCTURAL_DEPTH", "SELECTOR_THRESHOLD", "RESCUE_THRESHOLD", "GENERATED_CLASS_PREFIX_RE", "FRAMEWORK_DATA_ATTR_RE", "STABLE_ATTR_NAMES", "SELECTOR_ATTR_NAMES", "escapeCss", "value", "escapeAttrValue", "isUnique", "selector", "doc", "normalizeText", "text", "isStableClass", "cls", "HOST_PAGE_CLASSES", "part", "stableClassesOf", "element", "stableAttributes", "attrs", "name", "siblingPosition", "parent", "sameTag", "child", "idSelector", "attributeSelector", "tag", "classPathSelector", "segments", "current", "depth", "classes", "structuralSelector", "rooted", "index", "generateSelector", "generateElementSelector", "createAnchor", "relativeX", "relativeY", "count", "textSimilarity", "a", "b", "tokensA", "tokensB", "common", "token", "attributeSimilarity", "names", "matched", "positionSimilarity", "fingerprint", "delta", "span", "scoreElement", "hasText", "hasAttrs", "textWeight", "attrWeight", "posWeight", "bestMatch", "candidates", "best", "confidence", "resolveAnchor", "anchor", "STORAGE_KEY", "PENDING_DETAIL_KEY", "readStoredComments", "raw", "parsed", "err", "tryWriteStoredComments", "comments", "writeStoredComments", "shedOrder", "comment", "index", "a", "b", "timeA", "timeB", "working", "shed", "mergeForStorage", "stored", "current", "currentPage", "currentIds", "c", "urlAlphabet", "nanoid", "size", "id", "bytes", "urlAlphabet", "createId", "nanoid", "normalizeActorId", "value", "sameId", "a", "b", "openMenus", "outsideListener", "keyListener", "menuItems", "menu", "MENU_GAP", "clipperRectOf", "el", "overflowX", "overflowY", "placeMenu", "button", "CLASSES", "clipper", "floor", "ceiling", "leftWall", "rightWall", "height", "width", "left", "anchor", "bottomIfDown", "topIfUp", "eventHits", "startWatching", "e", "entry", "items", "root", "index", "next", "step", "stopWatching", "closeOpenMenus", "attachMenuToggle", "open", "isOpen", "wasOpen", "actorKeyOf", "user", "strings", "normalizeActorId", "reactionEntriesOf", "target", "map", "REACTION_EMOJIS", "emoji", "normalizeReactions", "raw", "out", "authors", "kept", "author", "clean", "toggleReactionOn", "actorKey", "index", "serializeReactions", "reactions", "entries", "EMOJI_ICON_SVG", "createReactionsUi", "onToggle", "rows", "refresh", "set", "entry", "pick", "trigger", "className", "tooltip", "wrapper", "CLASSES", "btn", "palette", "toggle", "attachMenuToggle", "item", "e", "el", "repaint", "me", "mine", "pill", "action", "emojiEl", "count", "recordKeyOf", "record", "strings", "normalizeActorId", "isOwnRecord", "target", "user", "actorKeyOf", "resolvePermission", "can", "action", "err", "commentTargetOf", "comment", "replyTargetOf", "reply", "commentId", "DEFAULT_LINK_PARAM", "buildCommentLink", "comment", "param", "href", "current", "page", "url", "readCommentLinkParam", "AUDIT_EVENTS", "AUDIT_FIELDS", "FIELD_MAX", "clean", "value", "transition", "actorOf", "user", "strings", "name", "id", "normalizeActorId", "recordEvent", "comment", "type", "actor", "detail", "entry", "from", "to", "normalizeHistory", "raw", "out", "item", "at", "a", "b", "serializeHistory", "history", "resolutionsOf", "openedAt", "createdAt", "resolved", "currentResolutionMs", "resolutions", "last", "created", "openDialogs", "closeOpenConfirmDialogs", "dismiss", "confirmDialog", "host", "title", "message", "confirmLabel", "cancelLabel", "resolve", "backdrop", "CLASSES", "Z_INDEX", "panel", "titleEl", "messageEl", "actions", "cancelBtn", "acceptBtn", "previouslyFocused", "settled", "settle", "result", "onKeydown", "e", "focusables", "active", "index", "pressedBackdrop", "COPY_ICON_SVG", "CHECK_ICON_SVG", "DOTS_ICON_SVG", "copyToClipboard", "text", "textarea", "statusLabelOf", "status", "strings", "typeLabelOf", "type", "priorityLabelOf", "priority", "createPicker", "action", "options", "value", "colorOf", "labelOf", "tooltipLabel", "onSelect", "showLabel", "wrapper", "btn", "CLASSES", "dot", "labelEl", "menu", "toggle", "attachMenuToggle", "current", "syncUi", "label", "item", "raw", "option", "itemDot", "e", "createMoreMenu", "tooltip", "items", "entry", "host", "confirmDialog", "createCommentActions", "comment", "reactions", "can", "onCopy", "onCopyLink", "onEdit", "onSetStatus", "onSetType", "onSetPriority", "onDelete", "actions", "classification", "tools", "copyBtn", "STATUSES", "STATUS_COLORS", "COMMENT_TYPES", "TYPE_COLORS", "PRIORITIES", "PRIORITY_COLORS", "target", "commentTargetOf", "allow", "createInlineEditor", "value", "strings", "onInput", "onSave", "onCancel", "wrapper", "CLASSES", "input", "actions", "cancel", "save", "syncSave", "e", "confirmDiscard", "host", "confirmDialog", "formatRelativeTime", "date", "strings", "diff", "minutes", "hours", "days", "formatTemplate", "formatFullDate", "locale", "createAuthorElement", "name", "el", "CLASSES", "nameEl", "createMetaElement", "author", "createdAt", "editedAt", "meta", "authorEl", "timeEl", "createEditedMark", "editedEl", "isMacPlatform", "getShortcutText", "options", "isMac", "modifierMap", "modifier", "key", "CARET_ICON_SVG", "ATTACH_ICON_SVG", "SEND_ICON_SVG", "COMMENT_BUBBLE_SVG", "MENU_ICON_SVG", "EYE_ICON_SVG", "EYE_OFF_ICON_SVG", "createInputArea", "areaClassName", "inputTag", "inputClassName", "inputId", "inputPlaceholder", "submitBtnId", "fileInputId", "container", "inputEl", "screenshotsContainer", "actionsBar", "attachBtn", "fileInput", "submitBtn", "createActionWithTooltip", "btnClass", "btnSvg", "tooltipContent", "label", "wrapper", "tooltip", "btn", "createToolbar", "en_default", "toolbar", "IDS", "actions", "commentLabel", "shortcutKey", "commentWrapper", "inboxLabel", "inboxWrapper", "visibilityLabel", "visibilityWrapper", "visibility", "createBadgeRow", "comment", "includeStatus", "includeClassification", "row", "addBadge", "text", "color", "badge", "status", "statusLabelOf", "STATUS_COLORS", "typeLabelOf", "TYPE_COLORS", "priorityLabelOf", "PRIORITY_COLORS", "tag", "elapsedMs", "currentResolutionMs", "elapsed", "formatDuration", "createClassifyRow", "type", "priority", "mount", "createPicker", "COMMENT_TYPES", "value", "PRIORITIES", "createCommentBox", "commentBox", "inputArea", "classify", "cssAttrValue", "circleSelector", "id", "screenshotsOf", "entry", "renderScreenshotsPreview", "screenshots", "onShow", "rerender", "pending", "dataUrl", "i", "item", "img", "makeThumbnailOperable", "removeBtn", "e", "slot", "readAsDataUrl", "file", "resolve", "reader", "ev", "wireScreenshotInput", "input", "getScreenshots", "transform", "MAX_SCREENSHOTS", "at", "wireScreenshotLightbox", "root", "activate", "createCommentCircle", "circle", "createScreenshotsDisplay", "src", "createTooltip", "header", "closeButton", "body", "badges", "tooltipScreenshots", "replyCount", "replies", "createReplyElement", "reply", "onDelete", "onEdit", "commentId", "can", "editing", "reactions", "replyEl", "items", "target", "replyTargetOf", "allow", "action", "replyTools", "createMoreMenu", "createInlineEditor", "replyScreenshots", "createThreadPopover", "onDeleteReply", "onEditReply", "popover", "scroll", "popoverScreenshots", "createContextBlock", "comment", "strings", "onShowLightbox", "collapsible", "expanded", "onToggle", "context", "contextScreenshot", "block", "CLASSES", "body", "toggle", "CARET_ICON_SVG", "e", "isOpen", "title", "caption", "img", "wireScreenshotLightbox", "addRow", "label", "value", "row", "key", "val", "size", "dimensions", "named", "entry", "openingTagOf", "element", "attrs", "name", "value", "openingTagFromFingerprint", "fingerprint", "domPathOf", "segments", "current", "tag", "id", "classes", "cls", "buildAgentContext", "comment", "viewportWidth", "viewportHeight", "strings", "anchor", "live", "state", "path", "capturedViewport", "reportedViewportWidth", "reportedViewportHeight", "lines", "context", "elapsed", "formatDuration", "replies", "reply", "positionPopoverAtCircle", "el", "circle", "circleRect", "centerX", "centerY", "circleBaseSize", "MARKER_SIZE", "offset", "elRect", "elWidth", "x", "margin", "preferredTop", "spaceBelow", "centerPopover", "y", "PopoverController", "deps", "replyId", "popover", "CLASSES", "cssAttrValue", "id", "comment", "source", "r", "sameId", "body", "meta", "editedEl", "createEditedMark", "actions", "commentId", "draft", "host", "confirmDiscard", "editor", "createInlineEditor", "text", "saved", "strings", "locale", "onDeleteReply", "reply", "replyEl", "onEditReply", "reactions", "createReactionsUi", "target", "emoji", "createThreadPopover", "headerEl", "actionsEl", "createCommentActions", "c", "copyToClipboard", "buildAgentContext", "buildCommentLink", "status", "type", "priority", "actionsRow", "mainScreenshotsContainer", "wireScreenshotLightbox", "src", "contextBlock", "createContextBlock", "e", "input", "submitBtn", "threadAttachBtn", "threadFileInput", "threadScreenshotsContainer", "pendingReplyScreenshots", "updateReplyScreenshotsPreview", "renderScreenshotsPreview", "dataUrl", "wireScreenshotInput", "submitReply", "repliesContainer", "createReplyElement", "scrollEl", "rect", "OCCLUSION_INTERVAL_MS", "clampToBox", "offset", "size", "MarkerEngine", "deps", "comment", "circle", "createCommentCircle", "id", "observer", "containerRect", "containerWidth", "containerHeight", "absoluteX", "absoluteY", "validatedX", "validatedY", "validatedRelativeX", "validatedRelativeY", "target", "rect", "x", "y", "top", "el", "TAG_NAME", "container", "checkOcclusion", "positionData", "circleRadius", "MARKER_SIZE", "viewportX", "viewportY", "state", "wasHidden", "now", "plans", "anyFlipped", "entries", "entry", "commentId", "key", "offsetY", "formatStamp", "iso", "locale", "MOVES", "strings", "v", "statusLabelOf", "typeLabelOf", "priorityLabelOf", "moveLabel", "field", "entry", "move", "name", "valueOf", "labelFor", "buildRow", "row", "CLASSES", "action", "actor", "time", "buildResolutions", "comment", "superseded", "resolutionsOf", "r", "section", "heading", "list", "resolution", "item", "formatTemplate", "formatDuration", "createAuditTrail", "open", "onToggle", "history", "wrapper", "toggle", "body", "resolutions", "next", "UNSET", "HOUR_MS", "countInto", "keys", "extraKey", "out", "key", "median", "sorted", "middle", "computeMetrics", "comments", "list", "byStatus", "STATUSES", "byType", "COMMENT_TYPES", "byPriority", "PRIORITIES", "perDay", "durations", "reopenedCount", "comment", "status", "day", "elapsed", "currentResolutionMs", "resolutionsOf", "a", "b", "total", "sum", "ms", "date", "count", "toHours", "UNSET_COLOR", "CHART_WIDTH", "CHART_HEIGHT", "SVG_NS", "el", "tag", "className", "text", "node", "svgEl", "attrs", "name", "value", "tile", "label", "box", "CLASSES", "barGroup", "heading", "entries", "group", "max", "entry", "count", "color", "row", "track", "bar", "dailyChart", "overTime", "strings", "svg", "date", "slot", "barWidth", "index", "height", "rect", "axis", "exportBar", "handlers", "button", "key", "onClick", "btn", "createMetricsView", "metrics", "deps", "view", "duration", "ms", "formatDuration", "tiles", "STATUSES", "statusLabelOf", "STATUS_COLORS", "COMMENT_TYPES", "typeLabelOf", "TYPE_COLORS", "UNSET", "PRIORITIES", "priorityLabelOf", "PRIORITY_COLORS", "CHEVRON_LEFT_SVG", "ARROW_UP_SVG", "ARROW_DOWN_SVG", "InboxView", "shadowRoot", "strings", "locale", "currentPage", "getComments", "callbacks", "options", "CLASSES", "text", "comment", "c", "sameId", "r", "host", "confirmDiscard", "commentId", "replyId", "handlers", "createInlineEditor", "circleSelector", "circle", "comments", "detail", "id", "btn", "header", "backBtn", "nav", "scope", "createMetricsView", "computeMetrics", "list", "el", "actions", "createReactionsUi", "target", "emoji", "parent", "reactionEntriesOf", "authors", "desired", "notice", "seen", "key", "fingerprint", "binding", "card", "node", "index", "empty", "icon", "hasAnyComment", "title", "clear", "before", "after", "kbd", "getShortcutText", "action", "value", "parts", "statusLabelOf", "typeLabelOf", "priorityLabelOf", "dataAttr", "values", "labelOf", "selected", "toggles", "onSelect", "group", "heading", "chips", "checked", "chip", "e", "wrapper", "CARET_ICON_SVG", "menu", "attachMenuToggle", "STATUSES", "COMMENT_TYPES", "PRIORITIES", "interactive", "createMetaElement", "actionsRow", "shots", "createScreenshotsDisplay", "wireScreenshotLightbox", "src", "badges", "createBadgeRow", "tag", "activate", "replyLink", "label", "createCommentActions", "copyToClipboard", "buildAgentContext", "buildCommentLink", "status", "type", "priority", "navBtn", "svg", "targetIndex", "context", "createContextBlock", "expanded", "audit", "createAuditTrail", "replies", "reply", "editingThisReply", "replyEl", "createReplyElement", "container", "inputEl", "screenshotsContainer", "attachBtn", "fileInput", "submitBtn", "createInputArea", "pendingScreenshots", "updatePreview", "renderScreenshotsPreview", "dataUrl", "wireScreenshotInput", "submit", "DELIMITER", "NEWLINE", "FORMULA_LEAD", "escape", "value", "raw", "safe", "toCsv", "rows", "columns", "header", "column", "body", "row", "COMMENT_COLUMNS", "columnsOf", "keys", "key", "METRIC_COLUMNS", "commentRows", "comments", "comment", "toHours", "currentResolutionMs", "resolutionsOf", "metricRows", "metrics", "push", "section", "table", "date", "count", "downloadCsv", "filename", "text", "blob", "url", "link", "REPORT_STYLE_ID", "el", "doc", "tag", "className", "text", "node", "buildTable", "caption", "rows", "headers", "table", "thead", "headRow", "header", "tbody", "label", "value", "tr", "duration", "ms", "formatTemplate", "toHours", "buildReport", "metrics", "strings", "locale", "scope", "body", "meta", "scopeEl", "dimension", "keys", "labelOf", "key", "STATUSES", "statusLabelOf", "COMMENT_TYPES", "typeLabelOf", "PRIORITIES", "priorityLabelOf", "date", "count", "printMetricsReport", "css", "frame", "view", "mountStyles", "teardown", "normalizeTags", "tags", "seen", "tag", "clean", "onlyStrings", "values", "v", "REGION_COVERAGE_MIN", "CHANGE_CALLBACKS", "CommentOverlay", "options", "isMacPlatform", "detectLocale", "getStrings", "getShadowRoot", "createToolbar", "createCommentBox", "CLASSES", "IDS", "CaptureFlow", "dataUrl", "MAX_SCREENSHOTS", "pending", "x", "y", "region", "err", "PopoverController", "id", "src", "target", "action", "commentId", "comment", "text", "screenshots", "replyId", "status", "type", "priority", "emoji", "MarkerEngine", "circle", "MARKERS_HIDDEN_STORAGE_KEY", "readStoredComments", "STORAGE_KEY", "deferred", "url", "fromLink", "readCommentLinkParam", "fromHandoff", "PENDING_DETAIL_KEY", "DEFAULT_LINK_PARAM", "handler", "sameId", "result", "fn", "previous", "actions", "wrapped", "key", "value", "args", "error", "context", "kind", "transform", "callbackArgs", "payload", "detail", "meta", "name", "callback", "buildCommentLink", "merged", "mergeForStorage", "writeStoredComments", "wireScreenshotInput", "released", "keyMatches", "modifierMatches", "centerX", "centerY", "area", "el", "TAG_NAME", "rect", "overlapX", "overlapY", "clientX", "clientY", "prevPointerEvents", "underlying", "container", "SELECTORS", "containerRect", "relativeX", "relativeY", "anchor", "createAnchor", "generateElementSelector", "renderScreenshotsPreview", "circleRadius", "MARKER_SIZE", "offset", "windowWidth", "windowHeight", "boxRect", "boxWidth", "adjustedX", "adjustedY", "hidden", "tooltip", "label", "EYE_OFF_ICON_SVG", "EYE_ICON_SVG", "position", "captured", "createId", "attachments", "contextScreenshot", "normalizeActorId", "captureContext", "recordEvent", "created", "e", "cssAttrValue", "createTooltip", "wireScreenshotLightbox", "positionPopoverAtCircle", "InboxView", "comments", "scope", "imageSrc", "lightbox", "img", "closeBtn", "returnFocus", "c", "commentOrId", "reply", "serialized", "serializedReply", "index", "r", "replyTargetOf", "next", "commentTargetOf", "edited", "author", "authorId", "timestamp", "editedAt", "reactions", "serializeReactions", "serializeHistory", "STATUSES", "changed", "user", "actorOf", "actorKeyOf", "resolvePermission", "toggleReactionOn", "updated", "COMMENT_TYPES", "previousType", "PRIORITIES", "previousPriority", "previousKey", "computeMetrics", "rows", "commentRows", "csv", "toCsv", "columnsOf", "COMMENT_COLUMNS", "downloadCsv", "metrics", "metricRows", "METRIC_COLUMNS", "printMetricsReport", "getReportStyles", "cleared", "clearedIds", "data", "anchored", "orphaned", "inactive", "item", "normalizeReactions", "normalizeHistory", "resolved", "resolveAnchor", "lost", "page", "closeOpenMenus", "closeOpenConfirmDialogs", "mountStyles", "getStyles", "getGlobalStyles", "detach", "overlay_default", "createCommentOverlay", "options", "autoInit", "overlayOptions", "initialize", "overlay_default", "index_default", "createCommentOverlay"]
|
|
7
7
|
}
|