@siteping/widget 0.9.4 → 0.9.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.global.js +1944 -102
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +1944 -102
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +2 -2
- package/package.json +1 -1
package/dist/index.global.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../../../node_modules/.bun/@medv+finder@3.2.0/node_modules/@medv/finder/finder.js","../src/dom/fingerprint.ts","../src/dom/text-context.ts","../src/dom/xpath.ts","../src/dom/anchor.ts","../src/dom-utils.ts","../src/icons.ts","../src/styles/theme.ts","../src/popup.ts","../src/annotator.ts","../src/api-client.ts","../src/events.ts","../src/fab.ts","../src/i18n/en.ts","../src/i18n/fr.ts","../src/i18n/index.ts","../src/identity.ts","../src/dom/fuzzy.ts","../src/dom/resolver.ts","../src/markers.ts","../src/panel.ts","../../core/src/types.ts","../src/store-client.ts","../src/styles/animations.ts","../src/styles/base.ts","../src/tooltip.ts","../src/launcher.ts"],"sourcesContent":["import type { SitepingConfig, SitepingInstance } from \"@siteping/core\";\nimport { launch } from \"./launcher.js\";\n\nexport type {\n AnchorData,\n AnnotationPayload,\n AnnotationResponse,\n FeedbackPayload,\n FeedbackResponse,\n FeedbackStatus,\n FeedbackType,\n RectData,\n SitepingConfig,\n SitepingInstance,\n SitepingPublicEvents,\n SitepingStore,\n} from \"@siteping/core\";\n\nexport type { Identity } from \"./identity.js\";\n\n/**\n * Initialize the Siteping feedback widget.\n *\n * @example\n * ```ts\n * import { initSiteping } from '@siteping/widget'\n *\n * const { destroy } = initSiteping({\n * endpoint: '/api/siteping',\n * projectName: 'my-project',\n * })\n * ```\n */\nexport function initSiteping(config: SitepingConfig): SitepingInstance {\n return launch(config);\n}\n","// License: MIT\n// Author: Anton Medvedev <anton@medv.io>\n// Source: https://github.com/antonmedv/finder\nlet config;\nlet rootDocument;\nlet start;\nexport function finder(input, options) {\n start = new Date();\n if (input.nodeType !== Node.ELEMENT_NODE) {\n throw new Error(`Can't generate CSS selector for non-element node type.`);\n }\n if ('html' === input.tagName.toLowerCase()) {\n return 'html';\n }\n const defaults = {\n root: document.body,\n idName: (name) => true,\n className: (name) => true,\n tagName: (name) => true,\n attr: (name, value) => false,\n seedMinLength: 1,\n optimizedMinLength: 2,\n threshold: 1000,\n maxNumberOfTries: 10000,\n timeoutMs: undefined,\n };\n config = { ...defaults, ...options };\n rootDocument = findRootDocument(config.root, defaults);\n let path = bottomUpSearch(input, 'all', () => bottomUpSearch(input, 'two', () => bottomUpSearch(input, 'one', () => bottomUpSearch(input, 'none'))));\n if (path) {\n const optimized = sort(optimize(path, input));\n if (optimized.length > 0) {\n path = optimized[0];\n }\n return selector(path);\n }\n else {\n throw new Error(`Selector was not found.`);\n }\n}\nfunction findRootDocument(rootNode, defaults) {\n if (rootNode.nodeType === Node.DOCUMENT_NODE) {\n return rootNode;\n }\n if (rootNode === defaults.root) {\n return rootNode.ownerDocument;\n }\n return rootNode;\n}\nfunction bottomUpSearch(input, limit, fallback) {\n let path = null;\n let stack = [];\n let current = input;\n let i = 0;\n while (current) {\n const elapsedTime = new Date().getTime() - start.getTime();\n if (config.timeoutMs !== undefined && elapsedTime > config.timeoutMs) {\n throw new Error(`Timeout: Can't find a unique selector after ${elapsedTime}ms`);\n }\n let level = maybe(id(current)) ||\n maybe(...attr(current)) ||\n maybe(...classNames(current)) ||\n maybe(tagName(current)) || [any()];\n const nth = index(current);\n if (limit == 'all') {\n if (nth) {\n level = level.concat(level.filter(dispensableNth).map((node) => nthChild(node, nth)));\n }\n }\n else if (limit == 'two') {\n level = level.slice(0, 1);\n if (nth) {\n level = level.concat(level.filter(dispensableNth).map((node) => nthChild(node, nth)));\n }\n }\n else if (limit == 'one') {\n const [node] = (level = level.slice(0, 1));\n if (nth && dispensableNth(node)) {\n level = [nthChild(node, nth)];\n }\n }\n else if (limit == 'none') {\n level = [any()];\n if (nth) {\n level = [nthChild(level[0], nth)];\n }\n }\n for (let node of level) {\n node.level = i;\n }\n stack.push(level);\n if (stack.length >= config.seedMinLength) {\n path = findUniquePath(stack, fallback);\n if (path) {\n break;\n }\n }\n current = current.parentElement;\n i++;\n }\n if (!path) {\n path = findUniquePath(stack, fallback);\n }\n if (!path && fallback) {\n return fallback();\n }\n return path;\n}\nfunction findUniquePath(stack, fallback) {\n const paths = sort(combinations(stack));\n if (paths.length > config.threshold) {\n return fallback ? fallback() : null;\n }\n for (let candidate of paths) {\n if (unique(candidate)) {\n return candidate;\n }\n }\n return null;\n}\nfunction selector(path) {\n let node = path[0];\n let query = node.name;\n for (let i = 1; i < path.length; i++) {\n const level = path[i].level || 0;\n if (node.level === level - 1) {\n query = `${path[i].name} > ${query}`;\n }\n else {\n query = `${path[i].name} ${query}`;\n }\n node = path[i];\n }\n return query;\n}\nfunction penalty(path) {\n return path.map((node) => node.penalty).reduce((acc, i) => acc + i, 0);\n}\nfunction unique(path) {\n const css = selector(path);\n switch (rootDocument.querySelectorAll(css).length) {\n case 0:\n throw new Error(`Can't select any node with this selector: ${css}`);\n case 1:\n return true;\n default:\n return false;\n }\n}\nfunction id(input) {\n const elementId = input.getAttribute('id');\n if (elementId && config.idName(elementId)) {\n return {\n name: '#' + CSS.escape(elementId),\n penalty: 0,\n };\n }\n return null;\n}\nfunction attr(input) {\n const attrs = Array.from(input.attributes).filter((attr) => config.attr(attr.name, attr.value));\n return attrs.map((attr) => ({\n name: `[${CSS.escape(attr.name)}=\"${CSS.escape(attr.value)}\"]`,\n penalty: 0.5,\n }));\n}\nfunction classNames(input) {\n const names = Array.from(input.classList).filter(config.className);\n return names.map((name) => ({\n name: '.' + CSS.escape(name),\n penalty: 1,\n }));\n}\nfunction tagName(input) {\n const name = input.tagName.toLowerCase();\n if (config.tagName(name)) {\n return {\n name,\n penalty: 2,\n };\n }\n return null;\n}\nfunction any() {\n return {\n name: '*',\n penalty: 3,\n };\n}\nfunction index(input) {\n const parent = input.parentNode;\n if (!parent) {\n return null;\n }\n let child = parent.firstChild;\n if (!child) {\n return null;\n }\n let i = 0;\n while (child) {\n if (child.nodeType === Node.ELEMENT_NODE) {\n i++;\n }\n if (child === input) {\n break;\n }\n child = child.nextSibling;\n }\n return i;\n}\nfunction nthChild(node, i) {\n return {\n name: node.name + `:nth-child(${i})`,\n penalty: node.penalty + 1,\n };\n}\nfunction dispensableNth(node) {\n return node.name !== 'html' && !node.name.startsWith('#');\n}\nfunction maybe(...level) {\n const list = level.filter(notEmpty);\n if (list.length > 0) {\n return list;\n }\n return null;\n}\nfunction notEmpty(value) {\n return value !== null && value !== undefined;\n}\nfunction* combinations(stack, path = []) {\n if (stack.length > 0) {\n for (let node of stack[0]) {\n yield* combinations(stack.slice(1, stack.length), path.concat(node));\n }\n }\n else {\n yield path;\n }\n}\nfunction sort(paths) {\n return [...paths].sort((a, b) => penalty(a) - penalty(b));\n}\nfunction* optimize(path, input, scope = {\n counter: 0,\n visited: new Map(),\n}) {\n if (path.length > 2 && path.length > config.optimizedMinLength) {\n for (let i = 1; i < path.length - 1; i++) {\n if (scope.counter > config.maxNumberOfTries) {\n return; // Okay At least I tried!\n }\n scope.counter += 1;\n const newPath = [...path];\n newPath.splice(i, 1);\n const newPathKey = selector(newPath);\n if (scope.visited.has(newPathKey)) {\n return;\n }\n if (unique(newPath) && same(newPath, input)) {\n yield newPath;\n scope.visited.set(newPathKey, true);\n yield* optimize(newPath, input, scope);\n }\n }\n }\n}\nfunction same(path, input) {\n return rootDocument.querySelector(selector(path)) === input;\n}\n","/**\n * Element fingerprinting for robust DOM re-anchoring.\n *\n * Captures structural properties (child count, sibling index, stable attributes)\n * that survive CSS class changes and minor DOM reshuffling.\n * Inspired by Similo (academic state-of-the-art, 98.8% accuracy).\n */\n\nconst STABLE_ATTRS = [\"role\", \"aria-label\", \"type\", \"name\", \"href\", \"src\", \"data-testid\", \"data-id\"] as const;\n\n/** Simple 32-bit hash (djb2). */\nfunction djb2(str: string): string {\n let hash = 5381;\n for (let i = 0; i < str.length; i++) {\n hash = ((hash << 5) + hash + str.charCodeAt(i)) | 0;\n }\n return (hash >>> 0).toString(36);\n}\n\n/**\n * Generate a compact structural fingerprint for a DOM element.\n *\n * Format: `\"childCount:siblingIdx:attrHash\"`\n * - `childCount` — number of direct child elements\n * - `siblingIdx` — position among same-tag siblings (0-based)\n * - `attrHash` — djb2 hash of stable attributes (role, aria-label, type, etc.)\n *\n * Tag name is NOT included — it's stored separately in `AnchorData.elementTag`.\n */\nexport function generateFingerprint(element: Element): string {\n const childCount = element.children.length;\n\n // Position among same-tag siblings\n let siblingIdx = 0;\n const parent = element.parentElement;\n if (parent) {\n for (const child of parent.children) {\n if (child === element) break;\n if (child.tagName === element.tagName) siblingIdx++;\n }\n }\n\n // Hash stable attributes\n const attrs: string[] = [];\n for (const attr of STABLE_ATTRS) {\n const val = element.getAttribute(attr);\n if (val) attrs.push(`${attr}=${val}`);\n }\n const attrHash = attrs.length > 0 ? djb2(attrs.join(\",\")) : \"0\";\n\n return `${childCount}:${siblingIdx}:${attrHash}`;\n}\n\n/**\n * Score how well a candidate element matches a stored fingerprint.\n * Returns 0–1.\n *\n * Weights:\n * - Child count match: 0.2 (tolerant — ±2 gets partial credit)\n * - Sibling index match: 0.4 (positional — most discriminating)\n * - Attribute hash match: 0.4 (identity — exact or nothing)\n */\nexport function scoreFingerprint(candidate: Element, storedFingerprint: string): number {\n const parts = storedFingerprint.split(\":\");\n if (parts.length !== 3) return 0;\n\n const [storedChildren, storedSibIdx, storedAttrHash] = parts;\n const storedChildCount = Number(storedChildren);\n const storedSibIndex = Number(storedSibIdx);\n if (Number.isNaN(storedChildCount) || Number.isNaN(storedSibIndex)) return 0;\n\n const candidateFp = generateFingerprint(candidate);\n const [candChildren, candSibIdx, candAttrHash] = candidateFp.split(\":\");\n\n let score = 0;\n\n // Child count (0.2)\n const childDiff = Math.abs(Number(candChildren) - storedChildCount);\n if (childDiff === 0) score += 0.2;\n else if (childDiff <= 2) score += 0.1;\n else if (childDiff <= 5) score += 0.03;\n\n // Sibling index (0.4)\n const sibDiff = Math.abs(Number(candSibIdx) - storedSibIndex);\n if (sibDiff === 0) score += 0.4;\n else if (sibDiff === 1) score += 0.2;\n else if (sibDiff <= 3) score += 0.08;\n\n // Attribute hash (0.4)\n if (candAttrHash === storedAttrHash) score += 0.4;\n\n return score;\n}\n","/**\n * Shared text-context helpers for DOM anchoring.\n * Used by both anchor generation (anchor.ts) and resolution (resolver.ts).\n */\n\n/**\n * Extract ~32 chars of text from the nearest sibling with content.\n * Walks up to 3 siblings in the given direction.\n */\nexport function adjacentText(element: Element, direction: \"before\" | \"after\"): string {\n const prop = direction === \"before\" ? \"previousElementSibling\" : \"nextElementSibling\";\n let sibling: Element | null = element[prop];\n let attempts = 3;\n\n while (sibling && attempts > 0) {\n const text = sibling.textContent?.trim();\n if (text) {\n return direction === \"before\" ? text.slice(-32) : text.slice(0, 32);\n }\n sibling = sibling[prop];\n attempts--;\n }\n\n return \"\";\n}\n\n/** Collect text from immediate siblings for disambiguation context. */\nexport function neighborText(element: Element): string {\n const prev = element.previousElementSibling?.textContent?.trim().slice(0, 40) ?? \"\";\n const next = element.nextElementSibling?.textContent?.trim().slice(0, 40) ?? \"\";\n return [prev, next].filter(Boolean).join(\" | \");\n}\n","/**\n * Generate an optimized XPath for a DOM element.\n *\n * Strategy:\n * - If the element has a unique id → //tag[@id='value']\n * - Otherwise, walk up the tree building /tag[position] segments\n * until we hit an ancestor with an id or reach <body>\n * - Cap depth at 6 levels to keep paths short\n */\nexport function generateXPath(element: Element): string {\n if (element.id) {\n const safeId = element.id.includes(\"'\") ? `concat('${element.id.replace(/'/g, \"',\\\"'\\\",'\")}')` : `'${element.id}'`;\n return `//${element.localName}[@id=${safeId}]`;\n }\n\n const segments: string[] = [];\n let current: Element | null = element;\n\n while (current && current !== document.body && segments.length < 6) {\n const tag = current.localName;\n const parent: Element | null = current.parentElement;\n\n if (current.id) {\n const safeId = current.id.includes(\"'\")\n ? `concat('${current.id.replace(/'/g, \"',\\\"'\\\",'\")}')`\n : `'${current.id}'`;\n segments.unshift(`/${tag}[@id=${safeId}]`);\n return \"/\" + segments.join(\"\");\n }\n\n // Compute position among same-tag siblings\n let position = 1;\n if (parent) {\n for (const sibling of parent.children) {\n if (sibling === current) break;\n if (sibling.localName === tag) position++;\n }\n }\n\n segments.unshift(`/${tag}[${position}]`);\n current = parent;\n }\n\n return \"/html/body\" + segments.join(\"\");\n}\n","import { finder } from \"@medv/finder\";\nimport type { AnchorData, RectData } from \"@siteping/core\";\nimport { generateFingerprint } from \"./fingerprint.js\";\nimport { adjacentText, neighborText } from \"./text-context.js\";\nimport { generateXPath } from \"./xpath.js\";\n\n/**\n * Generate a multi-selector anchor for a DOM element.\n *\n * Uses three complementary strategies (Hypothesis-inspired):\n * 1. CSS selector via @medv/finder (primary — fast, compact)\n * 2. XPath (fallback — survives class changes)\n * 3. Text snippet (fallback — survives structural changes)\n */\nexport function generateAnchor(element: Element): AnchorData {\n const cssSelector = finder(element, {\n // Filter out CSS-in-JS hashed class names\n className: (name: string) => !/^(css|sc|emotion|styled)-/.test(name) && !/^[a-z]{1,3}[A-Za-z0-9]{4,8}$/.test(name),\n // Prefer stable attributes\n attr: (name: string) => [\"data-testid\", \"data-id\", \"role\", \"aria-label\"].includes(name),\n // Exclude framework-generated dynamic IDs\n idName: (name: string) => !name.startsWith(\"radix-\") && !/^:r[0-9]+:$/.test(name),\n seedMinLength: 3,\n optimizedMinLength: 2,\n });\n\n const xpath = generateXPath(element);\n\n const rawText = element.textContent?.trim() ?? \"\";\n const textSnippet = rawText.slice(0, 120);\n\n const textPrefix = adjacentText(element, \"before\");\n const textSuffix = adjacentText(element, \"after\");\n const fingerprint = generateFingerprint(element);\n const neighbor = neighborText(element);\n\n return {\n cssSelector,\n xpath,\n textSnippet,\n textPrefix,\n textSuffix,\n fingerprint,\n neighborText: neighbor,\n elementTag: element.tagName,\n elementId: element.id || undefined,\n };\n}\n\n/**\n * Find the deepest DOM element that fully contains the drawn rectangle.\n * Walks from the center of the rect down through overlapping elements.\n */\nexport function findAnchorElement(rect: DOMRect, root: Element = document.documentElement): Element {\n const centerX = rect.x + rect.width / 2;\n const centerY = rect.y + rect.height / 2;\n\n // Get the element at the center point\n const elementAtCenter = document.elementFromPoint(centerX, centerY);\n if (!elementAtCenter || elementAtCenter === root) return document.body;\n\n // Walk up to find the smallest element whose bounding box contains the full rect\n let candidate: Element = elementAtCenter;\n let current: Element | null = elementAtCenter;\n\n while (current && current !== document.body) {\n const bounds = current.getBoundingClientRect();\n if (\n bounds.left <= rect.x &&\n bounds.top <= rect.y &&\n bounds.right >= rect.x + rect.width &&\n bounds.bottom >= rect.y + rect.height\n ) {\n candidate = current;\n break;\n }\n current = current.parentElement;\n }\n\n return candidate;\n}\n\n/**\n * Convert absolute rectangle coordinates to percentages\n * relative to an anchor element's bounding box.\n */\nexport function rectToPercentages(rect: DOMRect, anchorBounds: DOMRect): RectData {\n // Guard against zero-dimension anchors (collapsed/hidden elements)\n if (anchorBounds.width <= 0 || anchorBounds.height <= 0) {\n return { xPct: 0, yPct: 0, wPct: 1, hPct: 1 };\n }\n return {\n xPct: (rect.x - anchorBounds.x) / anchorBounds.width,\n yPct: (rect.y - anchorBounds.y) / anchorBounds.height,\n wPct: rect.width / anchorBounds.width,\n hPct: rect.height / anchorBounds.height,\n };\n}\n","/**\n * Safe DOM creation utilities.\n * All user content is set via textContent (never innerHTML).\n * SVG icons use a DOMParser for trusted static strings.\n */\n\n/**\n * Parse a trusted SVG string into an SVGElement.\n * Only use with hardcoded icon constants — never with user input.\n * Uses createContextualFragment for native document-context parsing\n * (DOMParser creates nodes in a foreign document that don't render in Shadow DOM).\n */\nexport function parseSvg(svgString: string): SVGSVGElement {\n const range = document.createRange();\n const fragment = range.createContextualFragment(svgString);\n const svg = fragment.firstElementChild;\n if (!svg || svg.nodeName.toLowerCase() !== \"svg\") {\n throw new Error(\"[siteping] Invalid SVG string\");\n }\n // Safety: strip any event handlers in case of accidental misuse\n for (const attr of [...svg.attributes]) {\n if (attr.name.startsWith(\"on\")) svg.removeAttribute(attr.name);\n }\n // Also strip from all descendants\n for (const el of svg.querySelectorAll(\"*\")) {\n for (const attr of [...el.attributes]) {\n if (attr.name.startsWith(\"on\")) el.removeAttribute(attr.name);\n }\n }\n return svg as SVGSVGElement;\n}\n\n/** Create an element with optional class and style */\nexport function el(tag: string, attrs?: Record<string, string>): HTMLElement {\n const element = document.createElement(tag);\n if (attrs) {\n for (const [key, value] of Object.entries(attrs)) {\n if (key === \"class\") {\n element.className = value;\n } else if (key === \"style\") {\n element.style.cssText = value;\n } else {\n element.setAttribute(key, value);\n }\n }\n }\n return element;\n}\n\n/** Set text content safely (no HTML injection possible) */\nexport function setText(element: HTMLElement | SVGElement, text: string): void {\n element.textContent = text;\n}\n\n/** Format a relative date string using Intl.RelativeTimeFormat for locale support */\nexport function formatRelativeDate(isoString: string, locale = \"fr\"): string {\n const diff = Date.now() - new Date(isoString).getTime();\n const seconds = Math.floor(diff / 1000);\n\n if (seconds < 60) {\n return new Intl.RelativeTimeFormat(locale, { numeric: \"auto\" }).format(0, \"second\");\n }\n\n const rtf = new Intl.RelativeTimeFormat(locale, { numeric: \"always\", style: \"narrow\" });\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) return rtf.format(-minutes, \"minute\");\n\n const hours = Math.floor(minutes / 60);\n if (hours < 24) return rtf.format(-hours, \"hour\");\n\n const days = Math.floor(hours / 24);\n if (days < 7) return rtf.format(-days, \"day\");\n\n return new Date(isoString).toLocaleDateString(locale);\n}\n","/** SVG icon strings for the widget UI. Kept as template strings to avoid DOM parsing overhead. */\n\nexport const ICON_SITEPING = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/><circle cx=\"12\" cy=\"10\" r=\"1\" fill=\"currentColor\" stroke=\"none\"/><circle cx=\"8\" cy=\"10\" r=\"1\" fill=\"currentColor\" stroke=\"none\"/><circle cx=\"16\" cy=\"10\" r=\"1\" fill=\"currentColor\" stroke=\"none\"/></svg>`;\n\nexport const ICON_CHAT = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/></svg>`;\n\nexport const ICON_ANNOTATE = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/><path d=\"M3 9h18\"/><path d=\"M9 3v18\"/></svg>`;\n\nexport const ICON_EYE = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><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 ICON_EYE_OFF = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><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.94\"/><path d=\"M9.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.19\"/><line x1=\"1\" y1=\"1\" x2=\"23\" y2=\"23\"/></svg>`;\n\nexport const ICON_CLOSE = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/></svg>`;\n\nexport const ICON_SEARCH = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><circle cx=\"11\" cy=\"11\" r=\"8\"/><line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"/></svg>`;\n\nexport const ICON_CHECK = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"20 6 9 17 4 12\"/></svg>`;\n\nexport const ICON_QUESTION = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3\"/><line x1=\"12\" y1=\"17\" x2=\"12.01\" y2=\"17\"/></svg>`;\n\nexport const ICON_CHANGE = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\"/><path d=\"M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z\"/></svg>`;\n\nexport const ICON_BUG = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"8\" y=\"6\" width=\"8\" height=\"14\" rx=\"4\"/><path d=\"M19 9h2\"/><path d=\"M3 9h2\"/><path d=\"M19 13h2\"/><path d=\"M3 13h2\"/><path d=\"M19 17h2\"/><path d=\"M3 17h2\"/><path d=\"M10 2h4\"/></svg>`;\n\nexport const ICON_OTHER = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"1\"/><circle cx=\"19\" cy=\"12\" r=\"1\"/><circle cx=\"5\" cy=\"12\" r=\"1\"/></svg>`;\n\nexport const ICON_UNDO = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"1 4 1 10 7 10\"/><path d=\"M3.51 15a9 9 0 1 0 2.13-9.36L1 10\"/></svg>`;\n\nexport const ICON_TRASH = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"3 6 5 6 21 6\"/><path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/><line x1=\"10\" y1=\"11\" x2=\"10\" y2=\"17\"/><line x1=\"14\" y1=\"11\" x2=\"14\" y2=\"17\"/></svg>`;\n","/** Color palette and glassmorphism tokens derived from the accent color */\nexport interface ThemeColors {\n accent: string;\n accentLight: string;\n accentDark: string;\n accentGlow: string;\n accentGradient: string;\n bg: string;\n bgHover: string;\n text: string;\n textSecondary: string;\n textTertiary: string;\n border: string;\n shadow: string;\n // Glass tokens\n glassBg: string;\n glassBgHeavy: string;\n glassBorder: string;\n glassBorderSubtle: string;\n // Feedback type colors\n typeQuestion: string;\n typeChange: string;\n typeBug: string;\n typeOther: string;\n // Soft type backgrounds (pastel)\n typeQuestionBg: string;\n typeChangeBg: string;\n typeBugBg: string;\n typeOtherBg: string;\n}\n\nconst DEFAULT_ACCENT = \"#0066ff\";\nconst HEX6_RE = /^#[0-9a-fA-F]{6}$/;\nconst HEX3_RE = /^#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])$/;\nconst HEX8_RE = /^#[0-9a-fA-F]{8}$/;\n\n/**\n * Normalize an accent color to a 6-digit hex string.\n *\n * **Only hex formats are accepted:**\n * - `#RGB` (3-digit shorthand, expanded to 6-digit)\n * - `#RRGGBB` (standard 6-digit)\n * - `#RRGGBBAA` (8-digit with alpha, alpha is stripped)\n *\n * Any other CSS color format (named colors like `\"red\"`, `hsl()`, `rgb()`,\n * `oklch()`, etc.) is **not** supported and will fall back to the default\n * accent color with a console warning.\n */\nfunction normalizeHex(raw: string): string {\n if (HEX6_RE.test(raw)) return raw;\n const short = HEX3_RE.test(raw) ? raw.match(HEX3_RE) : null;\n if (short) return `#${short[1]}${short[1]}${short[2]}${short[2]}${short[3]}${short[3]}`;\n if (HEX8_RE.test(raw)) return raw.slice(0, 7);\n\n console.warn(\n `[siteping] Invalid accentColor \"${raw}\" — only hex colors (#RGB, #RRGGBB, #RRGGBBAA) are supported. Using default.`,\n );\n return DEFAULT_ACCENT;\n}\n\n/** Darken a hex color by a percentage (0-1) */\nfunction darkenHex(hex: string, amount: number): string {\n const r = Math.max(0, Math.round(parseInt(hex.slice(1, 3), 16) * (1 - amount)));\n const g = Math.max(0, Math.round(parseInt(hex.slice(3, 5), 16) * (1 - amount)));\n const b = Math.max(0, Math.round(parseInt(hex.slice(5, 7), 16) * (1 - amount)));\n return `#${r.toString(16).padStart(2, \"0\")}${g.toString(16).padStart(2, \"0\")}${b.toString(16).padStart(2, \"0\")}`;\n}\n\n/** Detect if user prefers dark mode via media query */\nfunction prefersDark(): boolean {\n if (typeof window === \"undefined\") return false;\n return window.matchMedia(\"(prefers-color-scheme: dark)\").matches;\n}\n\n/** Resolve 'auto' theme to 'light' or 'dark' based on system preference */\nexport function resolveTheme(theme?: \"light\" | \"dark\" | \"auto\"): \"light\" | \"dark\" {\n if (theme === \"dark\") return \"dark\";\n if (theme === \"auto\") return prefersDark() ? \"dark\" : \"light\";\n return \"light\";\n}\n\nexport function buildThemeColors(accent: string = DEFAULT_ACCENT, theme?: \"light\" | \"dark\" | \"auto\"): ThemeColors {\n const hex = normalizeHex(accent);\n const dark = darkenHex(hex, 0.15);\n const resolved = resolveTheme(theme);\n\n if (resolved === \"dark\") {\n return {\n accent: hex,\n accentLight: hex + \"22\", // slightly more visible on dark bg\n accentDark: dark,\n accentGlow: hex + \"44\",\n accentGradient: `linear-gradient(135deg, ${hex}, ${dark})`,\n bg: \"#0f172a\",\n bgHover: \"#1e293b\",\n text: \"#f1f5f9\",\n textSecondary: \"#94a3b8\",\n textTertiary: \"#64748b\",\n border: \"#334155\",\n shadow: \"rgba(0, 0, 0, 0.3)\",\n // Glass tokens — dark frosted glass\n glassBg: \"rgba(15, 23, 42, 0.78)\",\n glassBgHeavy: \"rgba(15, 23, 42, 0.88)\",\n glassBorder: \"rgba(51, 65, 85, 0.5)\",\n glassBorderSubtle: \"rgba(51, 65, 85, 0.3)\",\n // Type colors stay vibrant on dark\n typeQuestion: \"#60a5fa\",\n typeChange: \"#fbbf24\",\n typeBug: \"#f87171\",\n typeOther: \"#94a3b8\",\n // Dark pastel backgrounds\n typeQuestionBg: \"rgba(59, 130, 246, 0.15)\",\n typeChangeBg: \"rgba(245, 158, 11, 0.15)\",\n typeBugBg: \"rgba(239, 68, 68, 0.15)\",\n typeOtherBg: \"rgba(100, 116, 139, 0.15)\",\n };\n }\n\n return {\n accent: hex,\n accentLight: hex + \"14\", // 8% opacity\n accentDark: dark,\n accentGlow: hex + \"33\", // 20% opacity\n accentGradient: `linear-gradient(135deg, ${hex}, ${dark})`,\n bg: \"#ffffff\",\n bgHover: \"#f8f9fb\",\n text: \"#0f172a\",\n textSecondary: \"#475569\",\n textTertiary: \"#64748b\",\n border: \"#e2e8f0\",\n shadow: \"rgba(0, 0, 0, 0.06)\",\n // Glass tokens\n glassBg: \"rgba(255, 255, 255, 0.72)\",\n glassBgHeavy: \"rgba(255, 255, 255, 0.85)\",\n glassBorder: \"rgba(255, 255, 255, 0.35)\",\n glassBorderSubtle: \"rgba(255, 255, 255, 0.18)\",\n // Vibrant type colors\n typeQuestion: \"#3b82f6\",\n typeChange: \"#b45309\",\n typeBug: \"#ef4444\",\n typeOther: \"#64748b\",\n // Pastel backgrounds\n typeQuestionBg: \"#eff6ff\",\n typeChangeBg: \"#fffbeb\",\n typeBugBg: \"#fef2f2\",\n typeOtherBg: \"#f8fafc\",\n };\n}\n\nexport function getTypeColor(type: string, colors: ThemeColors): string {\n switch (type) {\n case \"question\":\n return colors.typeQuestion;\n case \"change\":\n return colors.typeChange;\n case \"bug\":\n return colors.typeBug;\n default:\n return colors.typeOther;\n }\n}\n\nexport function getTypeBgColor(type: string, colors: ThemeColors): string {\n switch (type) {\n case \"question\":\n return colors.typeQuestionBg;\n case \"change\":\n return colors.typeChangeBg;\n case \"bug\":\n return colors.typeBugBg;\n default:\n return colors.typeOtherBg;\n }\n}\n\nexport function cssVariables(colors: ThemeColors): string {\n return `\n --sp-accent: ${colors.accent};\n --sp-accent-light: ${colors.accentLight};\n --sp-accent-dark: ${colors.accentDark};\n --sp-accent-glow: ${colors.accentGlow};\n --sp-accent-gradient: ${colors.accentGradient};\n --sp-bg: ${colors.bg};\n --sp-bg-hover: ${colors.bgHover};\n --sp-text: ${colors.text};\n --sp-text-secondary: ${colors.textSecondary};\n --sp-text-tertiary: ${colors.textTertiary};\n --sp-border: ${colors.border};\n --sp-shadow: ${colors.shadow};\n --sp-glass-bg: ${colors.glassBg};\n --sp-glass-bg-heavy: ${colors.glassBgHeavy};\n --sp-glass-border: ${colors.glassBorder};\n --sp-glass-border-subtle: ${colors.glassBorderSubtle};\n --sp-type-question: ${colors.typeQuestion};\n --sp-type-change: ${colors.typeChange};\n --sp-type-bug: ${colors.typeBug};\n --sp-type-other: ${colors.typeOther};\n --sp-type-question-bg: ${colors.typeQuestionBg};\n --sp-type-change-bg: ${colors.typeChangeBg};\n --sp-type-bug-bg: ${colors.typeBugBg};\n --sp-type-other-bg: ${colors.typeOtherBg};\n --sp-radius: 12px;\n --sp-radius-lg: 16px;\n --sp-radius-xl: 20px;\n --sp-radius-full: 9999px;\n --sp-blur: 20px;\n --sp-blur-heavy: 32px;\n --sp-shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.04);\n --sp-shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.05), 0 1px 2px rgba(0, 0, 0, 0.04);\n --sp-shadow-md: 0 4px 16px rgba(0, 0, 0, 0.08), 0 2px 4px rgba(0, 0, 0, 0.04);\n --sp-shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.1), 0 4px 8px rgba(0, 0, 0, 0.04);\n --sp-shadow-xl: 0 16px 48px rgba(0, 0, 0, 0.12), 0 8px 16px rgba(0, 0, 0, 0.06);\n --sp-font: \"Inter\", system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif;\n `;\n}\n","import type { FeedbackType } from \"@siteping/core\";\nimport { el, parseSvg, setText } from \"./dom-utils.js\";\nimport type { TFunction } from \"./i18n/index.js\";\nimport { ICON_BUG, ICON_CHANGE, ICON_OTHER, ICON_QUESTION } from \"./icons.js\";\nimport { getTypeBgColor, getTypeColor, type ThemeColors } from \"./styles/theme.js\";\n\ninterface PopupResult {\n type: FeedbackType;\n message: string;\n}\n\ninterface TypeOption {\n type: FeedbackType;\n label: string;\n icon: string;\n}\n\n/**\n * Popup form shown after drawing an annotation rectangle.\n *\n * Glassmorphism design: frosted glass background, soft shadows,\n * pill-shaped type buttons, gradient submit button.\n * Lives outside Shadow DOM.\n */\nexport class Popup {\n private root: HTMLElement;\n private selectedType: FeedbackType | null = null;\n private textarea: HTMLTextAreaElement;\n private submitBtn: HTMLButtonElement;\n private resolve: ((result: PopupResult | null) => void) | null = null;\n private previouslyFocused: HTMLElement | null = null;\n private onKeydownTrap: ((e: KeyboardEvent) => void) | null = null;\n\n constructor(\n private readonly colors: ThemeColors,\n private readonly t: TFunction,\n ) {\n this.root = el(\"div\", {\n style: `\n position:fixed;\n z-index:2147483647;\n width:300px;\n padding:16px;\n border-radius:16px;\n background:${this.colors.glassBg};\n backdrop-filter:blur(24px);\n -webkit-backdrop-filter:blur(24px);\n border:1px solid ${this.colors.glassBorder};\n box-shadow:0 8px 32px ${this.colors.shadow}, 0 2px 8px ${this.colors.shadow};\n font-family:\"Inter\",system-ui,-apple-system,sans-serif;\n opacity:0;\n transform:translateY(8px) scale(0.98);\n transition:opacity 0.25s cubic-bezier(0.16, 1, 0.3, 1),transform 0.25s cubic-bezier(0.16, 1, 0.3, 1);\n display:none;\n -webkit-font-smoothing:antialiased;\n `,\n });\n\n this.root.setAttribute(\"role\", \"dialog\");\n this.root.setAttribute(\"aria-modal\", \"true\");\n this.root.setAttribute(\"aria-label\", this.t(\"popup.ariaLabel\"));\n\n // Type selector grid (2x2)\n const typeOptions: TypeOption[] = [\n { type: \"question\", label: this.t(\"type.question\"), icon: ICON_QUESTION },\n { type: \"change\", label: this.t(\"type.change\"), icon: ICON_CHANGE },\n { type: \"bug\", label: this.t(\"type.bug\"), icon: ICON_BUG },\n { type: \"other\", label: this.t(\"type.other\"), icon: ICON_OTHER },\n ];\n const typeRow = el(\"div\", { style: \"display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-bottom:12px;\" });\n for (const option of typeOptions) {\n const btn = document.createElement(\"button\");\n btn.style.cssText = `\n height:44px;\n border-radius:9999px;border:1px solid ${this.colors.border};\n background:${this.colors.glassBg};cursor:pointer;\n display:flex;align-items:center;justify-content:center;gap:5px;\n font-family:\"Inter\",system-ui,-apple-system,sans-serif;\n font-size:13px;font-weight:500;color:${this.colors.textTertiary};\n transition:all 0.2s ease;\n padding:0 12px;\n `;\n const icon = parseSvg(option.icon);\n icon.setAttribute(\"style\", \"width:13px;height:13px;flex-shrink:0;\");\n btn.appendChild(icon);\n const labelSpan = document.createElement(\"span\");\n setText(labelSpan, option.label);\n btn.appendChild(labelSpan);\n btn.dataset.type = option.type;\n btn.setAttribute(\"aria-pressed\", \"false\");\n\n btn.addEventListener(\"click\", () => {\n this.selectType(option.type, typeRow);\n });\n\n btn.addEventListener(\"mouseenter\", () => {\n if (btn.dataset.type !== this.selectedType) {\n const bgColor = getTypeBgColor(btn.dataset.type ?? \"\", this.colors);\n btn.style.background = bgColor;\n btn.style.borderColor = getTypeColor(btn.dataset.type ?? \"\", this.colors) + \"40\";\n }\n });\n\n btn.addEventListener(\"mouseleave\", () => {\n if (btn.dataset.type !== this.selectedType) {\n btn.style.background = this.colors.glassBg;\n btn.style.borderColor = this.colors.border;\n }\n });\n\n typeRow.appendChild(btn);\n }\n\n // Textarea\n this.textarea = document.createElement(\"textarea\");\n this.textarea.style.cssText = `\n width:100%;min-height:72px;max-height:152px;\n padding:10px 12px;border-radius:12px;\n border:1px solid ${this.colors.border};\n background:${this.colors.glassBgHeavy};\n color:${this.colors.text};font-family:\"Inter\",system-ui,-apple-system,sans-serif;\n font-size:13px;line-height:1.5;resize:vertical;\n outline:none;transition:all 0.2s ease;\n box-sizing:border-box;\n `;\n this.textarea.placeholder = this.t(\"popup.placeholder\");\n this.textarea.maxLength = 5000;\n this.textarea.setAttribute(\"aria-label\", this.t(\"popup.textareaAria\"));\n\n // Keyboard shortcut hint\n const hint = el(\"div\", {\n style: `\n font-size:11px;color:${this.colors.textTertiary};\n text-align:right;margin-top:4px;\n font-family:\"Inter\",system-ui,-apple-system,sans-serif;\n letter-spacing:0.01em;\n `,\n });\n // navigator.userAgentData is preferred; navigator.platform is deprecated\n // but still needed as fallback. If both are unavailable, fall back to user agent string parsing.\n const uaData = (navigator as Navigator & { userAgentData?: { platform?: string } }).userAgentData;\n const isMac = uaData\n ? uaData.platform === \"macOS\"\n : (navigator.platform?.includes(\"Mac\") ?? /Macintosh|Mac OS X/i.test(navigator.userAgent));\n setText(hint, isMac ? this.t(\"popup.submitHintMac\") : this.t(\"popup.submitHintOther\"));\n\n this.textarea.addEventListener(\"focus\", () => {\n this.textarea.style.borderColor = this.colors.accent;\n this.textarea.style.boxShadow = `0 0 0 3px ${this.colors.accent}14`;\n this.textarea.style.background = this.colors.bg;\n });\n this.textarea.addEventListener(\"blur\", () => {\n this.textarea.style.borderColor = this.colors.border;\n this.textarea.style.boxShadow = \"none\";\n this.textarea.style.background = this.colors.glassBgHeavy;\n });\n this.textarea.addEventListener(\"input\", () => {\n this.updateSubmitState();\n });\n this.textarea.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Enter\" && (e.ctrlKey || e.metaKey)) {\n e.preventDefault();\n this.submit();\n }\n if (e.key === \"Escape\") {\n this.cancel();\n }\n });\n\n // Button row\n const btnRow = el(\"div\", { style: \"display:flex;justify-content:flex-end;gap:8px;margin-top:12px;\" });\n\n const cancelBtn = document.createElement(\"button\");\n cancelBtn.style.cssText = `\n height:34px;padding:0 16px;border-radius:9999px;\n border:1px solid ${this.colors.border};\n background:${this.colors.glassBg};\n color:${this.colors.textTertiary};font-family:\"Inter\",system-ui,-apple-system,sans-serif;\n font-size:13px;font-weight:500;cursor:pointer;\n transition:all 0.2s ease;\n `;\n setText(cancelBtn, this.t(\"popup.cancel\"));\n cancelBtn.addEventListener(\"click\", () => this.cancel());\n cancelBtn.addEventListener(\"mouseenter\", () => {\n cancelBtn.style.borderColor = this.colors.accent;\n cancelBtn.style.color = this.colors.accent;\n });\n cancelBtn.addEventListener(\"mouseleave\", () => {\n cancelBtn.style.borderColor = this.colors.border;\n cancelBtn.style.color = this.colors.textTertiary;\n });\n\n this.submitBtn = document.createElement(\"button\");\n this.submitBtn.style.cssText = `\n height:34px;padding:0 18px;border-radius:9999px;\n border:none;background:${this.colors.accentGradient};\n color:#fff;font-family:\"Inter\",system-ui,-apple-system,sans-serif;\n font-size:13px;font-weight:600;cursor:pointer;\n opacity:0.35;pointer-events:none;\n transition:all 0.2s ease;\n box-shadow:0 2px 8px ${this.colors.accentGlow};\n `;\n setText(this.submitBtn, this.t(\"popup.submit\"));\n this.submitBtn.addEventListener(\"click\", () => this.submit());\n\n btnRow.appendChild(cancelBtn);\n btnRow.appendChild(this.submitBtn);\n\n this.root.appendChild(typeRow);\n this.root.appendChild(this.textarea);\n this.root.appendChild(hint);\n this.root.appendChild(btnRow);\n document.body.appendChild(this.root);\n }\n\n /**\n * Show the popup near a drawn rectangle and return the user's input.\n * Returns null if cancelled.\n */\n show(rectBounds: DOMRect): Promise<PopupResult | null> {\n return new Promise((resolve) => {\n this.resolve = resolve;\n this.selectedType = null;\n this.textarea.value = \"\";\n this.updateSubmitState();\n this.resetTypeButtons();\n\n // Save focus to restore on close\n this.previouslyFocused = document.activeElement as HTMLElement | null;\n\n // Position: bottom-left of rect, 8px below\n let top = rectBounds.bottom + 8;\n let left = rectBounds.left;\n\n // Collision: flip up if not enough space below\n if (top + 220 > window.innerHeight) {\n top = rectBounds.top - 220 - 8;\n }\n // Collision: flip right if not enough space on left\n if (left + 300 > window.innerWidth) {\n left = rectBounds.right - 300;\n }\n left = Math.max(8, left);\n top = Math.max(8, top);\n\n this.root.style.top = `${top}px`;\n this.root.style.left = `${left}px`;\n this.root.style.display = \"block\";\n\n // Install focus trap\n this.onKeydownTrap = (e: KeyboardEvent) => {\n if (e.key === \"Tab\") {\n const focusableEls = Array.from(\n this.root.querySelectorAll<HTMLElement>(\n 'button:not([disabled]), textarea, input, [tabindex]:not([tabindex=\"-1\"])',\n ),\n );\n if (focusableEls.length === 0) return;\n const first = focusableEls[0];\n const last = focusableEls[focusableEls.length - 1];\n if (!first || !last) return;\n if (e.shiftKey) {\n if (document.activeElement === first || !this.root.contains(document.activeElement)) {\n e.preventDefault();\n last.focus();\n }\n } else {\n if (document.activeElement === last || !this.root.contains(document.activeElement)) {\n e.preventDefault();\n first.focus();\n }\n }\n }\n };\n this.root.addEventListener(\"keydown\", this.onKeydownTrap);\n\n // Check prefers-reduced-motion live (not cached at construction time)\n const reduceMotion =\n typeof window !== \"undefined\" && window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n this.root.style.transition = reduceMotion ? \"none\" : \"\";\n\n // Trigger animation\n requestAnimationFrame(() => {\n this.root.style.opacity = \"1\";\n this.root.style.transform = \"translateY(0) scale(1)\";\n this.textarea.focus();\n });\n });\n }\n\n private selectType(type: FeedbackType, container: HTMLElement): void {\n this.selectedType = type;\n const buttons = container.querySelectorAll<HTMLButtonElement>(\"button\");\n for (const btn of buttons) {\n const isActive = btn.dataset.type === type;\n const color = getTypeColor(btn.dataset.type ?? \"\", this.colors);\n const bgColor = getTypeBgColor(btn.dataset.type ?? \"\", this.colors);\n btn.style.background = isActive ? bgColor : this.colors.glassBg;\n btn.style.borderColor = isActive ? color + \"60\" : this.colors.border;\n btn.style.color = isActive ? color : this.colors.textTertiary;\n btn.style.fontWeight = isActive ? \"600\" : \"500\";\n btn.setAttribute(\"aria-pressed\", String(isActive));\n }\n this.updateSubmitState();\n }\n\n private resetTypeButtons(): void {\n const buttons = this.root.querySelectorAll<HTMLButtonElement>(\"button[data-type]\");\n for (const btn of buttons) {\n btn.setAttribute(\"aria-pressed\", \"false\");\n btn.style.background = this.colors.glassBg;\n btn.style.borderColor = this.colors.border;\n btn.style.color = this.colors.textTertiary;\n btn.style.fontWeight = \"500\";\n }\n }\n\n private updateSubmitState(): void {\n const enabled = this.selectedType !== null && this.textarea.value.trim().length > 0;\n this.submitBtn.disabled = !enabled;\n this.submitBtn.style.opacity = enabled ? \"1\" : \"0.35\";\n this.submitBtn.style.pointerEvents = enabled ? \"auto\" : \"none\";\n }\n\n private submit(): void {\n if (!this.selectedType || !this.textarea.value.trim()) return;\n this.resolve?.({ type: this.selectedType, message: this.textarea.value.trim() });\n this.resolve = null;\n this.hideElement();\n }\n\n private cancel(): void {\n this.resolve?.(null);\n this.resolve = null;\n this.hideElement();\n }\n\n private hideElement(): void {\n // Remove focus trap\n if (this.onKeydownTrap) {\n this.root.removeEventListener(\"keydown\", this.onKeydownTrap);\n this.onKeydownTrap = null;\n }\n this.root.style.opacity = \"0\";\n this.root.style.transform = \"translateY(8px) scale(0.98)\";\n // Restore focus to the previously focused element\n this.previouslyFocused?.focus();\n this.previouslyFocused = null;\n setTimeout(() => {\n this.root.style.display = \"none\";\n }, 250);\n }\n\n destroy(): void {\n this.root.remove();\n }\n}\n","import type { AnnotationPayload, FeedbackType } from \"@siteping/core\";\nimport { findAnchorElement, generateAnchor, rectToPercentages } from \"./dom/anchor.js\";\nimport { el, setText } from \"./dom-utils.js\";\nimport type { EventBus, WidgetEvents } from \"./events.js\";\nimport type { TFunction } from \"./i18n/index.js\";\nimport { Popup } from \"./popup.js\";\nimport type { ThemeColors } from \"./styles/theme.js\";\n\nexport interface AnnotationComplete {\n annotation: AnnotationPayload;\n type: FeedbackType;\n message: string;\n}\n\n/**\n * Annotation mode: full-page overlay with rectangle drawing.\n *\n * Glassmorphism design:\n * - Frosted glass toolbar at top\n * - Subtle tinted overlay\n * - Accent-colored drawing rectangle with glow\n */\nexport class Annotator {\n private overlay: HTMLElement | null = null;\n private toolbar: HTMLElement | null = null;\n private drawingRect: HTMLElement | null = null;\n private startX = 0;\n private startY = 0;\n private isDrawing = false;\n private isActive = false;\n private popup: Popup;\n private savedOverflow = \"\";\n private preActiveFocusElement: Element | null = null;\n private rafId: number | null = null;\n private pendingMoveEvent: MouseEvent | Touch | null = null;\n\n constructor(\n private readonly colors: ThemeColors,\n private readonly bus: EventBus<WidgetEvents>,\n private readonly t: TFunction,\n ) {\n this.popup = new Popup(colors, t);\n\n this.bus.on(\"annotation:start\", () => this.activate());\n }\n\n private activate(): void {\n if (this.isActive) return;\n this.isActive = true;\n\n // Capture the focused element before activation for keyboard annotation\n this.preActiveFocusElement = document.activeElement;\n\n // Lock page scroll\n this.savedOverflow = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n\n // Overlay — subtle blue tint for depth\n this.overlay = el(\"div\", {\n style: `\n position:fixed;inset:0;\n z-index:2147483646;\n background:rgba(15, 23, 42, 0.04);\n cursor:crosshair;\n `,\n });\n this.overlay.setAttribute(\"aria-hidden\", \"true\");\n\n // Toolbar — glassmorphism bar\n this.toolbar = el(\"div\", {\n style: `\n position:fixed;top:0;left:0;right:0;\n z-index:2147483647;\n height:52px;\n background:${this.colors.glassBg};\n backdrop-filter:blur(24px);\n -webkit-backdrop-filter:blur(24px);\n border-bottom:1px solid ${this.colors.glassBorder};\n display:flex;align-items:center;justify-content:center;gap:16px;\n font-family:\"Inter\",system-ui,-apple-system,sans-serif;\n font-size:14px;color:${this.colors.text};\n box-shadow:0 4px 16px ${this.colors.shadow};\n -webkit-font-smoothing:antialiased;\n `,\n });\n\n const dot = el(\"span\", {\n style: `\n width:8px;height:8px;border-radius:50%;\n background:${this.colors.accent};\n box-shadow:0 0 8px ${this.colors.accentGlow};\n animation:pulse 1.5s ease-in-out infinite;\n `,\n });\n\n // Add pulse animation inline (respects prefers-reduced-motion)\n const style = document.createElement(\"style\");\n style.textContent = [\n \"@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.4}}\",\n \"@media(prefers-reduced-motion:reduce){@keyframes pulse{from,to{opacity:1}}}\",\n ].join(\"\");\n this.toolbar.appendChild(style);\n\n const instruction = el(\"span\", { style: \"font-weight:500;letter-spacing:-0.01em;\" });\n setText(instruction, this.t(\"annotator.instruction\"));\n\n const cancelBtn = document.createElement(\"button\");\n cancelBtn.style.cssText = `\n height:34px;padding:0 18px;border-radius:9999px;\n border:1px solid ${this.colors.border};\n background:${this.colors.glassBg};\n color:${this.colors.textTertiary};font-family:\"Inter\",system-ui,-apple-system,sans-serif;\n font-size:13px;font-weight:500;cursor:pointer;\n transition:all 0.2s ease;\n `;\n setText(cancelBtn, this.t(\"annotator.cancel\"));\n cancelBtn.addEventListener(\"click\", () => this.deactivate());\n cancelBtn.addEventListener(\"mouseenter\", () => {\n cancelBtn.style.borderColor = this.colors.typeBug;\n cancelBtn.style.color = this.colors.typeBug;\n cancelBtn.style.background = this.colors.typeBugBg;\n });\n cancelBtn.addEventListener(\"mouseleave\", () => {\n cancelBtn.style.borderColor = this.colors.border;\n cancelBtn.style.color = this.colors.textTertiary;\n cancelBtn.style.background = this.colors.glassBg;\n });\n\n this.toolbar.appendChild(dot);\n this.toolbar.appendChild(instruction);\n this.toolbar.appendChild(cancelBtn);\n\n // Mouse events\n this.overlay.addEventListener(\"mousedown\", this.onMouseDown);\n this.overlay.addEventListener(\"mousemove\", this.onMouseMove);\n this.overlay.addEventListener(\"mouseup\", this.onMouseUp);\n\n // Touch events (Surface Pro, iPad, etc.)\n this.overlay.addEventListener(\"touchstart\", this.onTouchStart, { passive: false });\n this.overlay.addEventListener(\"touchmove\", this.onTouchMove, { passive: false });\n this.overlay.addEventListener(\"touchend\", this.onTouchEnd);\n\n // Keyboard annotation: Enter selects the pre-activation focused element\n this.overlay.addEventListener(\"keydown\", this.onOverlayKeyDown);\n\n // Allow tab-through so keyboard users can reach underlying elements\n this.overlay.setAttribute(\"tabindex\", \"0\");\n\n // Escape to cancel\n document.addEventListener(\"keydown\", this.onKeyDown);\n\n document.body.appendChild(this.overlay);\n document.body.appendChild(this.toolbar);\n }\n\n private deactivate(): void {\n if (!this.isActive) return;\n this.isActive = false;\n this.isDrawing = false;\n this.preActiveFocusElement = null;\n\n // Cancel any pending rAF to prevent stale callbacks\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n this.pendingMoveEvent = null;\n\n document.body.style.overflow = this.savedOverflow;\n document.removeEventListener(\"keydown\", this.onKeyDown);\n\n this.overlay?.remove();\n this.toolbar?.remove();\n this.drawingRect?.remove();\n this.overlay = null;\n this.toolbar = null;\n this.drawingRect = null;\n\n this.bus.emit(\"annotation:end\");\n }\n\n private onKeyDown = (e: KeyboardEvent): void => {\n if (e.key === \"Escape\") this.deactivate();\n };\n\n /**\n * Keyboard annotation: pressing Enter while the overlay is active selects\n * the element that was focused before activation and creates a full-bounds\n * annotation covering that element (WCAG 2.1.1 Level A).\n */\n private onOverlayKeyDown = async (e: KeyboardEvent): Promise<void> => {\n if (e.key !== \"Enter\") return;\n e.preventDefault();\n\n const target = this.preActiveFocusElement;\n if (!target || !(target instanceof HTMLElement)) return;\n\n const bounds = target.getBoundingClientRect();\n if (bounds.width <= 0 || bounds.height <= 0) return;\n\n const rectBounds = new DOMRect(bounds.x, bounds.y, bounds.width, bounds.height);\n\n const result = await this.popup.show(rectBounds);\n if (!result) return;\n\n const anchor = generateAnchor(target);\n const annotation: AnnotationPayload = {\n anchor,\n rect: { xPct: 0, yPct: 0, wPct: 1, hPct: 1 },\n scrollX: window.scrollX,\n scrollY: window.scrollY,\n viewportW: window.innerWidth,\n viewportH: window.innerHeight,\n devicePixelRatio: window.devicePixelRatio,\n };\n\n this.deactivate();\n\n this.bus.emit(\"annotation:complete\", {\n annotation,\n type: result.type,\n message: result.message,\n });\n };\n\n private onMouseDown = (e: MouseEvent): void => {\n this.startDrawing(e.clientX, e.clientY);\n };\n\n private onTouchStart = (e: TouchEvent): void => {\n e.preventDefault();\n const touch = e.touches[0];\n if (touch) this.startDrawing(touch.clientX, touch.clientY);\n };\n\n private startDrawing(clientX: number, clientY: number): void {\n this.isDrawing = true;\n this.startX = clientX;\n this.startY = clientY;\n\n this.drawingRect?.remove();\n this.drawingRect = el(\"div\", {\n style: `\n position:fixed;\n border:2px solid ${this.colors.accent};\n background:${this.colors.accent}12;\n pointer-events:none;\n border-radius:8px;\n box-shadow:0 0 16px ${this.colors.accentGlow};\n transition:box-shadow 0.15s ease;\n `,\n });\n this.overlay?.appendChild(this.drawingRect);\n }\n\n private onMouseMove = (e: MouseEvent): void => {\n this.scheduleRectUpdate(e);\n };\n\n private onTouchMove = (e: TouchEvent): void => {\n e.preventDefault();\n if (e.touches[0]) this.scheduleRectUpdate(e.touches[0]);\n };\n\n private scheduleRectUpdate(source: MouseEvent | Touch): void {\n if (!this.isDrawing || !this.drawingRect) return;\n\n this.pendingMoveEvent = source;\n if (this.rafId !== null) return;\n\n this.rafId = requestAnimationFrame(() => {\n this.rafId = null;\n const evt = this.pendingMoveEvent;\n if (!evt || !this.drawingRect) return;\n\n const x = Math.min(evt.clientX, this.startX);\n const y = Math.min(evt.clientY, this.startY);\n const w = Math.abs(evt.clientX - this.startX);\n const h = Math.abs(evt.clientY - this.startY);\n\n this.drawingRect.style.left = `${x}px`;\n this.drawingRect.style.top = `${y}px`;\n this.drawingRect.style.width = `${w}px`;\n this.drawingRect.style.height = `${h}px`;\n });\n }\n\n private onTouchEnd = async (e: TouchEvent): Promise<void> => {\n const touch = e.changedTouches[0];\n if (touch) await this.finishDrawing(touch.clientX, touch.clientY);\n };\n\n private onMouseUp = async (e: MouseEvent): Promise<void> => {\n await this.finishDrawing(e.clientX, e.clientY);\n };\n\n private finishDrawing = async (clientX: number, clientY: number): Promise<void> => {\n if (!this.isDrawing || !this.drawingRect) return;\n this.isDrawing = false;\n\n const x = Math.min(clientX, this.startX);\n const y = Math.min(clientY, this.startY);\n const w = Math.abs(clientX - this.startX);\n const h = Math.abs(clientY - this.startY);\n\n // Ignore tiny rectangles (accidental clicks)\n if (w < 10 || h < 10) {\n this.drawingRect.remove();\n this.drawingRect = null;\n return;\n }\n\n const rectBounds = new DOMRect(x, y, w, h);\n\n // Show popup for type + message\n const result = await this.popup.show(rectBounds);\n\n if (!result) {\n this.drawingRect?.remove();\n this.drawingRect = null;\n return;\n }\n\n // Build annotation payload BEFORE deactivating (needs overlay for elementFromPoint)\n const annotation = this.buildAnnotation(rectBounds);\n this.drawingRect?.remove();\n this.drawingRect = null;\n this.deactivate();\n\n // Emit via event bus (not DOM — overlay is already null after deactivate)\n this.bus.emit(\"annotation:complete\", {\n annotation,\n type: result.type,\n message: result.message,\n });\n };\n\n /**\n * Build an AnnotationPayload from a drawn rectangle.\n * Temporarily hides the overlay to access the real DOM underneath.\n */\n private buildAnnotation(rectBounds: DOMRect): AnnotationPayload {\n // Temporarily hide overlay to find the real element underneath\n if (this.overlay) this.overlay.style.pointerEvents = \"none\";\n const anchorElement = findAnchorElement(rectBounds);\n if (this.overlay) this.overlay.style.pointerEvents = \"auto\";\n\n const anchor = generateAnchor(anchorElement);\n const anchorBounds = anchorElement.getBoundingClientRect();\n const rect = rectToPercentages(rectBounds, anchorBounds);\n\n return {\n anchor,\n rect,\n scrollX: window.scrollX,\n scrollY: window.scrollY,\n viewportW: window.innerWidth,\n viewportH: window.innerHeight,\n devicePixelRatio: window.devicePixelRatio,\n };\n }\n destroy(): void {\n this.deactivate();\n this.popup.destroy();\n }\n}\n","import type { FeedbackPayload, FeedbackResponse, FeedbackStatus, FeedbackType } from \"@siteping/core\";\n\n/**\n * Abstract client interface used by the widget internals.\n *\n * `ApiClient` (HTTP mode) and `StoreClient` (direct store mode) both satisfy\n * this interface, allowing the widget to work identically in either mode.\n */\nexport interface WidgetClient {\n sendFeedback(payload: FeedbackPayload): Promise<FeedbackResponse>;\n getFeedbacks(\n projectName: string,\n options?: { page?: number; limit?: number; type?: FeedbackType; status?: FeedbackStatus; search?: string },\n ): Promise<{ feedbacks: FeedbackResponse[]; total: number }>;\n resolveFeedback(id: string, resolved: boolean): Promise<FeedbackResponse>;\n deleteFeedback(id: string): Promise<void>;\n deleteAllFeedbacks(projectName: string): Promise<void>;\n}\n\nconst MAX_RETRIES = 3;\nconst TIMEOUT_MS = 10_000;\nconst RETRY_QUEUE_KEY = \"siteping_retry_queue\";\nconst MAX_QUEUE_SIZE = 20;\n\n// ---------------------------------------------------------------------------\n// Core fetch with retry + exponential backoff + jitter\n// ---------------------------------------------------------------------------\n\nasync function resilientFetch(url: string, init: RequestInit, retries = MAX_RETRIES): Promise<Response> {\n for (let attempt = 0; attempt <= retries; attempt++) {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS);\n\n try {\n const response = await fetch(url, {\n ...init,\n signal: controller.signal,\n });\n clearTimeout(timeout);\n\n // Don't retry client errors (4xx) — only server errors (5xx)\n if (response.ok || (response.status >= 400 && response.status < 500)) {\n return response;\n }\n\n if (attempt === retries) return response;\n } catch (error) {\n clearTimeout(timeout);\n if (attempt === retries) throw error;\n }\n\n // Exponential backoff with jitter: 1s, 2s, 4s + random ±500ms\n const baseDelay = 1000 * 2 ** attempt;\n const jitter = Math.random() * 1000 - 500;\n await new Promise((r) => setTimeout(r, baseDelay + jitter));\n }\n\n throw new Error(\"Max retries exceeded\");\n}\n\n// ---------------------------------------------------------------------------\n// Retry queue — persist failed feedbacks for retry on next page load\n// ---------------------------------------------------------------------------\n\nfunction queueForRetry(endpoint: string, payload: FeedbackPayload): void {\n try {\n const raw = localStorage.getItem(RETRY_QUEUE_KEY);\n const parsed: unknown = raw ? JSON.parse(raw) : [];\n const queue: Array<{ endpoint: string; payload: FeedbackPayload }> = Array.isArray(parsed)\n ? (parsed as Array<{ endpoint: string; payload: FeedbackPayload }>)\n : [];\n\n // Cap queue size to prevent unbounded localStorage growth\n if (queue.length >= MAX_QUEUE_SIZE) {\n queue.shift(); // Drop oldest entry\n }\n\n queue.push({ endpoint, payload });\n localStorage.setItem(RETRY_QUEUE_KEY, JSON.stringify(queue));\n } catch {\n // localStorage full or unavailable — silently drop\n }\n}\n\nexport async function flushRetryQueue(endpoint: string): Promise<void> {\n try {\n const raw = localStorage.getItem(RETRY_QUEUE_KEY);\n if (!raw) return;\n\n const parsed: unknown = JSON.parse(raw);\n const queue: Array<{ endpoint: string; payload: FeedbackPayload }> = Array.isArray(parsed)\n ? (parsed as Array<{ endpoint: string; payload: FeedbackPayload }>)\n : [];\n\n const toRetry = queue.filter((e) => e.endpoint === endpoint);\n if (toRetry.length === 0) return;\n\n // Process items sequentially to avoid overwhelming the server\n const failed: Array<{ endpoint: string; payload: FeedbackPayload }> = [];\n for (const entry of toRetry) {\n try {\n const res = await fetch(endpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(entry.payload),\n });\n if (!res.ok) {\n failed.push(entry);\n }\n } catch {\n failed.push(entry);\n }\n }\n\n // Rebuild queue: keep unrelated entries + failed retries\n const remaining = queue.filter((e) => e.endpoint !== endpoint).concat(failed);\n if (remaining.length > 0) {\n localStorage.setItem(RETRY_QUEUE_KEY, JSON.stringify(remaining));\n } else {\n localStorage.removeItem(RETRY_QUEUE_KEY);\n }\n } catch {\n // Ignore — localStorage may be unavailable\n }\n}\n\n// ---------------------------------------------------------------------------\n// API client\n// ---------------------------------------------------------------------------\n\nexport class ApiClient {\n constructor(\n private readonly endpoint: string,\n private readonly projectName: string,\n ) {}\n\n async sendFeedback(payload: FeedbackPayload): Promise<FeedbackResponse> {\n try {\n const response = await resilientFetch(this.endpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(payload),\n });\n\n if (!response.ok) {\n const text = await response.text().catch(() => \"Unknown error\");\n throw new Error(`Failed to send feedback: ${response.status} ${text}`);\n }\n\n return (await response.json()) as FeedbackResponse; // Server validates via Zod\n } catch (error) {\n queueForRetry(this.endpoint, payload);\n throw error;\n }\n }\n\n async getFeedbacks(\n projectName: string,\n options?: {\n page?: number;\n limit?: number;\n type?: FeedbackType;\n status?: FeedbackStatus;\n search?: string;\n },\n ): Promise<{ feedbacks: FeedbackResponse[]; total: number }> {\n const params = new URLSearchParams({ projectName });\n if (options?.page) params.set(\"page\", String(options.page));\n if (options?.limit) params.set(\"limit\", String(options.limit));\n if (options?.type) params.set(\"type\", options.type);\n if (options?.status) params.set(\"status\", options.status);\n if (options?.search) params.set(\"search\", options.search);\n\n const response = await resilientFetch(`${this.endpoint}?${params.toString()}`, { method: \"GET\" });\n\n if (!response.ok) {\n throw new Error(`Failed to fetch feedbacks: ${response.status}`);\n }\n\n return (await response.json()) as { feedbacks: FeedbackResponse[]; total: number }; // Server validates via Zod\n }\n\n async resolveFeedback(id: string, resolved: boolean): Promise<FeedbackResponse> {\n const response = await resilientFetch(this.endpoint, {\n method: \"PATCH\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ id, projectName: this.projectName, status: resolved ? \"resolved\" : \"open\" }),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to update feedback: ${response.status}`);\n }\n\n return (await response.json()) as FeedbackResponse; // Server validates via Zod\n }\n\n async deleteFeedback(id: string): Promise<void> {\n const response = await resilientFetch(this.endpoint, {\n method: \"DELETE\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ id, projectName: this.projectName }),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to delete feedback: ${response.status}`);\n }\n }\n\n async deleteAllFeedbacks(projectName: string): Promise<void> {\n const response = await resilientFetch(this.endpoint, {\n method: \"DELETE\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ projectName, deleteAll: true }),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to delete all feedbacks: ${response.status}`);\n }\n }\n}\n","type Listener = (...args: unknown[]) => void;\n\n/**\n * Lightweight typed EventEmitter — zero dependencies.\n */\nexport class EventBus<E extends { [K in keyof E]: unknown[] }> {\n private listeners = new Map<keyof E, Set<Listener>>();\n\n on<K extends keyof E>(event: K, listener: (...args: E[K]) => void): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n const set = this.listeners.get(event)!;\n set.add(listener as Listener);\n\n return () => {\n set.delete(listener as Listener);\n };\n }\n\n off<K extends keyof E>(event: K, listener: (...args: E[K]) => void): void {\n const set = this.listeners.get(event);\n if (set) {\n set.delete(listener as Listener);\n }\n }\n\n emit<K extends keyof E>(event: K, ...args: E[K]): void {\n const set = this.listeners.get(event);\n if (!set) return;\n for (const fn of set) {\n try {\n fn(...args);\n } catch (err) {\n // Isolate listener errors — one bad listener must not kill others\n console.error(`[siteping] Error in event listener for \"${String(event)}\":`, err);\n }\n }\n }\n\n removeAll(): void {\n this.listeners.clear();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Widget event types\n// ---------------------------------------------------------------------------\n\nexport interface WidgetEvents {\n open: [];\n close: [];\n \"feedback:sent\": [import(\"@siteping/core\").FeedbackResponse];\n \"feedback:deleted\": [string];\n \"feedback:all-deleted\": [];\n \"feedback:error\": [Error];\n \"annotation:start\": [];\n \"annotation:end\": [];\n \"annotation:complete\": [import(\"./annotator.js\").AnnotationComplete];\n \"annotations:toggle\": [boolean];\n \"panel:toggle\": [boolean];\n}\n\n/** Subset of WidgetEvents exposed to consumers via SitepingInstance */\nexport interface PublicWidgetEvents {\n \"feedback:sent\": [import(\"@siteping/core\").FeedbackResponse];\n \"feedback:deleted\": [string];\n \"panel:open\": [];\n \"panel:close\": [];\n}\n","import type { SitepingConfig } from \"@siteping/core\";\nimport { parseSvg, setText } from \"./dom-utils.js\";\nimport type { EventBus, WidgetEvents } from \"./events.js\";\nimport type { TFunction } from \"./i18n/index.js\";\nimport { ICON_ANNOTATE, ICON_CHAT, ICON_CLOSE, ICON_EYE, ICON_EYE_OFF, ICON_SITEPING } from \"./icons.js\";\n\ninterface RadialItem {\n id: string;\n icon: string;\n iconAlt?: string;\n label: string;\n}\n\nconst ITEM_GAP = 54;\n\n/**\n * Floating Action Button with radial menu and notification badge.\n *\n * Glassmorphism: gradient background, glow shadow, glass radial items.\n * Badge shows unresolved feedback count.\n */\nexport class Fab {\n private root: HTMLElement;\n private fab: HTMLButtonElement;\n private radialContainer: HTMLElement;\n private badgeEl: HTMLElement | null = null;\n private isOpen = false;\n private annotationsVisible = true;\n private items: RadialItem[];\n\n constructor(\n shadowRoot: ShadowRoot,\n config: SitepingConfig,\n private readonly bus: EventBus<WidgetEvents>,\n private readonly t: TFunction,\n ) {\n const position = config.position ?? \"bottom-right\";\n const isRight = position === \"bottom-right\";\n\n // Vertical stack above the FAB\n this.items = [\n { id: \"chat\", icon: ICON_CHAT, label: t(\"fab.messages\") },\n { id: \"annotate\", icon: ICON_ANNOTATE, label: t(\"fab.annotate\") },\n { id: \"toggle-annotations\", icon: ICON_EYE, iconAlt: ICON_EYE_OFF, label: t(\"fab.annotations\") },\n ];\n\n // FAB button — needs position:relative for badge positioning\n this.fab = document.createElement(\"button\");\n this.fab.className = `sp-fab sp-fab--${position} sp-anim-fab-in`;\n this.fab.style.position = \"fixed\"; // ensure fixed even with relative children\n this.fab.appendChild(parseSvg(ICON_SITEPING));\n this.fab.setAttribute(\"aria-label\", t(\"fab.aria\"));\n this.fab.setAttribute(\"aria-expanded\", \"false\");\n this.fab.addEventListener(\"click\", () => this.toggle());\n\n // Radial container\n this.radialContainer = document.createElement(\"div\");\n this.radialContainer.className = `sp-radial sp-radial--${position}`;\n this.radialContainer.setAttribute(\"role\", \"menu\");\n\n for (let i = 0; i < this.items.length; i++) {\n const item = this.items[i];\n if (!item) continue;\n const btn = document.createElement(\"button\");\n btn.className = \"sp-radial-item\";\n btn.style.setProperty(\"--sp-i\", String(i));\n btn.appendChild(parseSvg(item.icon));\n btn.setAttribute(\"role\", \"menuitem\");\n btn.setAttribute(\"aria-label\", item.label);\n btn.dataset.itemId = item.id;\n\n btn.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n this.handleItemClick(item.id);\n });\n\n const label = document.createElement(\"span\");\n label.className = \"sp-radial-label\";\n label.textContent = item.label;\n label.style.cssText = isRight\n ? \"position:absolute; right:54px; top:50%; transform:translateY(-50%); white-space:nowrap;\"\n : \"position:absolute; left:54px; top:50%; transform:translateY(-50%); white-space:nowrap;\";\n btn.appendChild(label);\n\n this.radialContainer.appendChild(btn);\n }\n\n this.root = document.createElement(\"div\");\n this.root.appendChild(this.radialContainer);\n this.root.appendChild(this.fab);\n shadowRoot.appendChild(this.root);\n\n // Close radial menu on click outside.\n const host = shadowRoot.host;\n this.onDocumentClick = (e: MouseEvent) => {\n if (this.isOpen && !e.composedPath().includes(host)) {\n this.close();\n }\n };\n document.addEventListener(\"click\", this.onDocumentClick);\n\n // Escape on FAB or menu container closes the menu\n const handleEscape = (e: KeyboardEvent) => {\n if (e.key === \"Escape\" && this.isOpen) {\n e.stopPropagation();\n this.close();\n }\n };\n this.fab.addEventListener(\"keydown\", handleEscape);\n this.radialContainer.addEventListener(\"keydown\", handleEscape);\n\n // Arrow key navigation within the radial menu\n this.radialContainer.addEventListener(\"keydown\", (e) => {\n const items = Array.from(this.radialContainer.querySelectorAll<HTMLButtonElement>(\".sp-radial-item\"));\n if (items.length === 0 || !this.isOpen) return;\n const activeEl = (shadowRoot.activeElement ?? document.activeElement) as HTMLElement;\n const currentIndex = items.indexOf(activeEl as HTMLButtonElement);\n\n switch (e.key) {\n case \"ArrowUp\": {\n e.preventDefault();\n const nextIndex = currentIndex <= 0 ? items.length - 1 : currentIndex - 1;\n items[nextIndex]?.focus();\n break;\n }\n case \"ArrowDown\": {\n e.preventDefault();\n const nextIndex = currentIndex >= items.length - 1 ? 0 : currentIndex + 1;\n items[nextIndex]?.focus();\n break;\n }\n case \"Home\": {\n e.preventDefault();\n items[0]?.focus();\n break;\n }\n case \"End\": {\n e.preventDefault();\n items[items.length - 1]?.focus();\n break;\n }\n }\n });\n }\n\n private onDocumentClick: (e: MouseEvent) => void;\n\n /** Update the badge count. Pass 0 to hide. */\n updateBadge(count: number): void {\n if (count <= 0) {\n this.badgeEl?.remove();\n this.badgeEl = null;\n return;\n }\n\n if (!this.badgeEl) {\n this.badgeEl = document.createElement(\"span\");\n this.badgeEl.className = \"sp-fab-badge\";\n this.badgeEl.setAttribute(\"role\", \"status\");\n this.badgeEl.setAttribute(\"aria-live\", \"polite\");\n this.fab.appendChild(this.badgeEl);\n }\n\n const displayText = count > 99 ? \"99+\" : String(count);\n setText(this.badgeEl, displayText);\n this.badgeEl.setAttribute(\"aria-label\", this.t(\"fab.badge\").replace(\"{count}\", String(count)));\n }\n\n private toggle(): void {\n this.isOpen ? this.close() : this.open();\n }\n\n private open(): void {\n this.isOpen = true;\n this.setFabIcon(ICON_CLOSE);\n this.fab.setAttribute(\"aria-expanded\", \"true\");\n\n const buttons = this.radialContainer.querySelectorAll<HTMLButtonElement>(\".sp-radial-item\");\n buttons.forEach((btn, i) => {\n // Stack vertically above the FAB with initial offset + gap\n const y = -(16 + ITEM_GAP * (i + 1));\n btn.style.transform = `translate(0px, ${y}px) scale(1)`;\n btn.classList.add(\"sp-radial-item--open\");\n });\n\n // Focus the first menu item after animation\n requestAnimationFrame(() => {\n const firstItem = this.radialContainer.querySelector<HTMLButtonElement>(\".sp-radial-item\");\n firstItem?.focus();\n });\n }\n\n private close(): void {\n this.isOpen = false;\n this.setFabIcon(ICON_SITEPING);\n this.fab.setAttribute(\"aria-expanded\", \"false\");\n\n const buttons = this.radialContainer.querySelectorAll<HTMLButtonElement>(\".sp-radial-item\");\n buttons.forEach((btn) => {\n btn.style.transform = \"translate(0, 0) scale(0.8)\";\n btn.classList.remove(\"sp-radial-item--open\");\n });\n\n // Return focus to FAB\n this.fab.focus();\n }\n\n private setFabIcon(svgStr: string): void {\n const badge = this.badgeEl;\n this.fab.replaceChildren(parseSvg(svgStr));\n // Re-append badge after icon swap\n if (badge) this.fab.appendChild(badge);\n }\n\n private handleItemClick(id: string): void {\n this.close();\n\n switch (id) {\n case \"chat\":\n this.bus.emit(\"panel:toggle\", true);\n break;\n case \"annotate\":\n this.bus.emit(\"annotation:start\");\n break;\n case \"toggle-annotations\": {\n this.annotationsVisible = !this.annotationsVisible;\n this.bus.emit(\"annotations:toggle\", this.annotationsVisible);\n const btn = this.radialContainer.querySelector('[data-item-id=\"toggle-annotations\"]');\n if (btn) {\n btn.replaceChildren(parseSvg(this.annotationsVisible ? ICON_EYE : ICON_EYE_OFF));\n }\n break;\n }\n }\n }\n\n destroy(): void {\n document.removeEventListener(\"click\", this.onDocumentClick);\n this.root.remove();\n }\n}\n","import type { Translations } from \"./types.js\";\n\nexport const en: Translations = {\n // Panel\n \"panel.title\": \"Feedbacks\",\n \"panel.ariaLabel\": \"Siteping feedback panel\",\n \"panel.feedbackList\": \"Feedback list\",\n \"panel.loading\": \"Loading feedbacks\",\n \"panel.close\": \"Close panel\",\n \"panel.deleteAll\": \"Delete all\",\n \"panel.deleteAllConfirmTitle\": \"Delete all\",\n \"panel.deleteAllConfirmMessage\": \"Delete all feedbacks for this project? This action cannot be undone.\",\n \"panel.search\": \"Search...\",\n \"panel.searchAria\": \"Search feedbacks\",\n \"panel.filterAll\": \"All\",\n \"panel.loadError\": \"Failed to load\",\n \"panel.retry\": \"Retry\",\n \"panel.empty\": \"No feedback yet\",\n \"panel.showMore\": \"Show more\",\n \"panel.showLess\": \"Show less\",\n \"panel.resolve\": \"Resolve\",\n \"panel.reopen\": \"Reopen\",\n \"panel.delete\": \"Delete\",\n \"panel.cancel\": \"Cancel\",\n \"panel.confirmDelete\": \"Delete\",\n\n // Feedback type labels\n \"type.question\": \"Question\",\n \"type.change\": \"Change\",\n \"type.bug\": \"Bug\",\n \"type.other\": \"Other\",\n\n // FAB menu\n \"fab.aria\": \"Siteping \\u2014 Feedback menu\",\n \"fab.messages\": \"Messages\",\n \"fab.annotate\": \"Annotate\",\n \"fab.annotations\": \"Annotations\",\n\n // Annotator\n \"annotator.instruction\": \"Draw a rectangle on the area to comment\",\n \"annotator.cancel\": \"Cancel\",\n\n // Popup\n \"popup.ariaLabel\": \"Feedback form\",\n \"popup.placeholder\": \"Describe your feedback...\",\n \"popup.textareaAria\": \"Feedback message\",\n \"popup.submitHintMac\": \"\\u2318+Enter to send\",\n \"popup.submitHintOther\": \"Ctrl+Enter to send\",\n \"popup.cancel\": \"Cancel\",\n \"popup.submit\": \"Send\",\n\n // Identity modal\n \"identity.title\": \"Identify yourself\",\n \"identity.nameLabel\": \"Name\",\n \"identity.namePlaceholder\": \"Your name\",\n \"identity.emailLabel\": \"Email\",\n \"identity.emailPlaceholder\": \"your@email.com\",\n \"identity.cancel\": \"Cancel\",\n \"identity.submit\": \"Continue\",\n\n // Markers\n \"marker.approximate\": \"Approximate position (confidence: {confidence}%)\",\n \"marker.aria\": \"Feedback #{number}: {type} — {message}\",\n\n // FAB badge\n \"fab.badge\": \"{count} unresolved feedbacks\",\n\n // Accessibility — screen reader announcements\n \"feedback.sent.confirmation\": \"Feedback sent successfully\",\n \"feedback.error.message\": \"Failed to send feedback\",\n \"feedback.deleted.confirmation\": \"Feedback deleted\",\n\n // Badge\n \"badge.count\": \"{count} unresolved feedbacks\",\n};\n","import type { Translations } from \"./types.js\";\n\nexport const fr: Translations = {\n // Panel\n \"panel.title\": \"Feedbacks\",\n \"panel.ariaLabel\": \"Panneau de feedback Siteping\",\n \"panel.feedbackList\": \"Liste des feedbacks\",\n \"panel.loading\": \"Chargement des feedbacks\",\n \"panel.close\": \"Fermer le panneau\",\n \"panel.deleteAll\": \"Tout supprimer\",\n \"panel.deleteAllConfirmTitle\": \"Tout supprimer\",\n \"panel.deleteAllConfirmMessage\": \"Supprimer tous les feedbacks de ce projet ? Cette action est irr\\u00e9versible.\",\n \"panel.search\": \"Rechercher...\",\n \"panel.searchAria\": \"Rechercher dans les feedbacks\",\n \"panel.filterAll\": \"Tous\",\n \"panel.loadError\": \"Erreur de chargement\",\n \"panel.retry\": \"R\\u00e9essayer\",\n \"panel.empty\": \"Aucun feedback pour le moment\",\n \"panel.showMore\": \"Voir plus\",\n \"panel.showLess\": \"Voir moins\",\n \"panel.resolve\": \"R\\u00e9soudre\",\n \"panel.reopen\": \"Rouvrir\",\n \"panel.delete\": \"Supprimer\",\n \"panel.cancel\": \"Annuler\",\n \"panel.confirmDelete\": \"Supprimer\",\n\n // Feedback type labels\n \"type.question\": \"Question\",\n \"type.change\": \"Changement\",\n \"type.bug\": \"Bug\",\n \"type.other\": \"Autre\",\n\n // FAB menu\n \"fab.aria\": \"Siteping \\u2014 Menu feedback\",\n \"fab.messages\": \"Messages\",\n \"fab.annotate\": \"Annoter\",\n \"fab.annotations\": \"Annotations\",\n\n // Annotator\n \"annotator.instruction\": \"Tracez un rectangle sur la zone \\u00e0 commenter\",\n \"annotator.cancel\": \"Annuler\",\n\n // Popup\n \"popup.ariaLabel\": \"Formulaire de feedback\",\n \"popup.placeholder\": \"D\\u00e9crivez votre retour...\",\n \"popup.textareaAria\": \"Message de feedback\",\n \"popup.submitHintMac\": \"\\u2318+Entr\\u00e9e pour envoyer\",\n \"popup.submitHintOther\": \"Ctrl+Entr\\u00e9e pour envoyer\",\n \"popup.cancel\": \"Annuler\",\n \"popup.submit\": \"Envoyer\",\n\n // Identity modal\n \"identity.title\": \"Identifiez-vous\",\n \"identity.nameLabel\": \"Nom\",\n \"identity.namePlaceholder\": \"Votre nom\",\n \"identity.emailLabel\": \"Email\",\n \"identity.emailPlaceholder\": \"votre@email.com\",\n \"identity.cancel\": \"Annuler\",\n \"identity.submit\": \"Continuer\",\n\n // Markers\n \"marker.approximate\": \"Position approximative (confiance : {confidence}%)\",\n \"marker.aria\": \"Feedback n°{number} : {type} — {message}\",\n\n // FAB badge\n \"fab.badge\": \"{count} feedbacks non résolus\",\n\n // Accessibility — screen reader announcements\n \"feedback.sent.confirmation\": \"Feedback envoyé avec succès\",\n \"feedback.error.message\": \"Échec de l'envoi du feedback\",\n \"feedback.deleted.confirmation\": \"Feedback supprimé\",\n\n // Badge\n \"badge.count\": \"{count} feedbacks non résolus\",\n};\n","import type { TFunction, Translations } from \"./types.js\";\n\nexport type { TFunction, Translations } from \"./types.js\";\n\n// Static imports — bundler (tsup) will include both.\n// For tree-shaking in consumer apps, use dynamic import() with a bundler plugin.\nimport { en } from \"./en.js\";\nimport { fr } from \"./fr.js\";\n\nconst LOCALES: Record<string, Translations> = { fr, en };\n\n/** Register a custom locale at runtime. */\nexport function registerLocale(code: string, translations: Translations): void {\n LOCALES[code] = translations;\n}\n\n/**\n * Create a translation function for the given locale.\n *\n * Locale resolution: exact match > language prefix > English fallback.\n */\nexport function createT(locale: string): TFunction {\n const lang = (locale.split(\"-\")[0] ?? locale).toLowerCase();\n if (!LOCALES[lang]) {\n console.warn(`[siteping] Unknown locale \"${locale}\", falling back to \"en\"`);\n }\n const dict = LOCALES[lang] ?? LOCALES.en ?? ({} as Translations);\n return (key) => dict[key] ?? key;\n}\n\n/**\n * Returns the type label for a FeedbackType value.\n * Maps API enum values (english) to localized display labels.\n */\nexport function getTypeLabel(type: string, t: TFunction): string {\n switch (type) {\n case \"question\":\n return t(\"type.question\");\n case \"change\":\n return t(\"type.change\");\n case \"bug\":\n return t(\"type.bug\");\n case \"other\":\n return t(\"type.other\");\n default:\n return type;\n }\n}\n","const STORAGE_KEY = \"siteping_identity\";\n\nexport interface Identity {\n name: string;\n email: string;\n}\n\nexport function getIdentity(): Identity | null {\n try {\n const raw = localStorage.getItem(STORAGE_KEY);\n if (!raw) return null;\n const parsed: unknown = JSON.parse(raw);\n if (\n typeof parsed === \"object\" &&\n parsed !== null &&\n \"name\" in parsed &&\n typeof (parsed as Record<string, unknown>).name === \"string\" &&\n \"email\" in parsed &&\n typeof (parsed as Record<string, unknown>).email === \"string\"\n ) {\n const identity = parsed as Identity;\n if (identity.name && identity.email) return identity;\n }\n return null;\n } catch {\n return null;\n }\n}\n\nexport function saveIdentity(identity: Identity): void {\n try {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(identity));\n } catch {\n // Quota exceeded or localStorage disabled — identity works for this session only\n }\n}\n","/**\n * Lightweight fuzzy text matching for DOM re-anchoring.\n * Zero dependencies — bundled into the widget.\n * Uses Levenshtein distance, optimized for short strings (~50 chars).\n */\n\n/**\n * Levenshtein edit distance.\n * O(n*m) time, O(min(n,m)) space.\n */\nexport function editDistance(a: string, b: string): number {\n if (a === b) return 0;\n if (a.length === 0) return b.length;\n if (b.length === 0) return a.length;\n\n // Ensure `a` is the shorter string for space optimization\n if (a.length > b.length) {\n const t = a;\n a = b;\n b = t;\n }\n\n const aLen = a.length;\n const bLen = b.length;\n let prev = new Array<number>(aLen + 1);\n for (let k = 0; k <= aLen; k++) prev[k] = k;\n let curr = new Array<number>(aLen + 1);\n\n for (let j = 1; j <= bLen; j++) {\n curr[0] = j;\n for (let i = 1; i <= aLen; i++) {\n // Indices are valid: i-1 in [0, aLen-1], j-1 in [0, bLen-1], loop bounds guarantee access\n const prevDiag = prev[i - 1] ?? 0;\n curr[i] = a[i - 1] === b[j - 1] ? prevDiag : 1 + Math.min(prevDiag, prev[i] ?? 0, curr[i - 1] ?? 0);\n }\n const tmp = prev;\n prev = curr;\n curr = tmp;\n }\n\n return prev[aLen] ?? 0; // aLen is within bounds — prev has aLen+1 entries\n}\n\n/**\n * Normalized similarity score (0–1, where 1 = identical).\n */\nexport function similarity(a: string, b: string): number {\n if (a === b) return 1;\n const maxLen = Math.max(a.length, b.length);\n if (maxLen === 0) return 1;\n return 1 - editDistance(a, b) / maxLen;\n}\n\n/**\n * Fuzzy substring search — checks if `needle` approximately exists in `haystack`.\n * Slides a window of `needle.length` over the haystack and returns the best\n * similarity score found. Returns 0 if below `minScore`.\n */\nexport function fuzzyIncludes(haystack: string, needle: string, minScore = 0.6): number {\n if (!needle || !haystack) return 0;\n if (haystack.includes(needle)) return 1;\n\n const nLen = needle.length;\n\n // If needle is longer than haystack, compare directly\n if (nLen > haystack.length) {\n const score = similarity(haystack, needle);\n return score >= minScore ? score : 0;\n }\n\n let best = 0;\n\n // Cap haystack to avoid O(n²) on huge text nodes\n const capped = haystack.length > 500 ? haystack.slice(0, 500) : haystack;\n const limit = capped.length - nLen;\n\n for (let i = 0; i <= limit; i++) {\n const window = capped.slice(i, i + nLen);\n const score = similarity(window, needle);\n if (score > best) best = score;\n if (best >= 0.95) break;\n }\n\n return best >= minScore ? best : 0;\n}\n","import type { AnchorData, RectData } from \"@siteping/core\";\nimport { scoreFingerprint } from \"./fingerprint.js\";\nimport { fuzzyIncludes, similarity } from \"./fuzzy.js\";\nimport { adjacentText, neighborText } from \"./text-context.js\";\n\nexport type ResolutionStrategy = \"id\" | \"css\" | \"xpath\" | \"scan\";\n\nexport interface AnchorResolution {\n element: Element;\n confidence: number;\n strategy: ResolutionStrategy;\n}\n\nexport interface ResolvedAnnotation {\n element: Element;\n rect: DOMRect;\n confidence: number;\n strategy: ResolutionStrategy;\n}\n\n/** Max elements to scan during smart fallback. */\nconst MAX_SCAN_CANDIDATES = 300;\n\n/** Minimum fuzzy text match score for CSS/XPath verification. */\nconst TEXT_MATCH_THRESHOLD = 0.3;\n\n/**\n * Verify that a resolved element's text content matches the stored snippet.\n * If no snippet is stored, returns true (no verification possible).\n * Uses fuzzy matching to tolerate minor text changes.\n */\nfunction textMatches(el: Element, anchor: AnchorData): boolean {\n if (!anchor.textSnippet) return true;\n const text = (el.textContent?.trim() ?? \"\").slice(0, 500);\n return fuzzyIncludes(text, anchor.textSnippet, 0.5) > TEXT_MATCH_THRESHOLD;\n}\n\n/**\n * Re-anchor an annotation using a multi-level fallback strategy\n * with text verification and confidence scoring.\n *\n * Resolution order:\n * 1. getElementById + text verification — confidence 1.0\n * 2. CSS selector + text verification — confidence 0.95\n * 3. XPath + text verification — confidence 0.9\n * 4. Smart scan (fingerprint + text + prefix/suffix + neighbor) — up to 0.85\n *\n * Text verification prevents false matches when DOM elements are reordered.\n * Returns null if all strategies fail (annotation is orphaned).\n */\nexport function resolveAnchor(anchor: AnchorData): AnchorResolution | null {\n // Level 1: Element ID (most stable, still verify text)\n if (anchor.elementId) {\n const el = document.getElementById(anchor.elementId);\n if (el && el.tagName === anchor.elementTag && textMatches(el, anchor)) {\n return { element: el, confidence: 1.0, strategy: \"id\" };\n }\n }\n\n // Level 2: CSS Selector (with text verification)\n try {\n const el = document.querySelector(anchor.cssSelector);\n if (el && el.tagName === anchor.elementTag && textMatches(el, anchor)) {\n return { element: el, confidence: 0.95, strategy: \"css\" };\n }\n } catch {\n // Invalid selector — skip\n }\n\n // Level 3: XPath (with text verification)\n try {\n const result = document.evaluate(anchor.xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);\n const el = result.singleNodeValue;\n if (el instanceof Element && el.tagName === anchor.elementTag && textMatches(el, anchor)) {\n return { element: el, confidence: 0.9, strategy: \"xpath\" };\n }\n } catch {\n // Invalid XPath — skip\n }\n\n // Level 4: Smart scan — combine all available signals\n return smartScan(anchor);\n}\n\n/**\n * Scan DOM elements by tag and score each candidate using multiple signals:\n * fingerprint, text similarity, prefix/suffix context, neighbor text.\n *\n * Returns the best candidate above a 0.4 threshold, capped at 0.85 confidence\n * (smart scan is never 100% certain).\n */\nfunction smartScan(anchor: AnchorData): AnchorResolution | null {\n const tag = anchor.elementTag.toLowerCase();\n const candidates = document.querySelectorAll(tag);\n if (candidates.length === 0) return null;\n\n let bestElement: Element | null = null;\n let bestScore = 0;\n\n const limit = Math.min(candidates.length, MAX_SCAN_CANDIDATES);\n\n for (let i = 0; i < limit; i++) {\n const el = candidates[i];\n if (!el) continue;\n const score = scoreCandidate(el, anchor);\n if (score > bestScore) {\n bestScore = score;\n bestElement = el;\n if (bestScore >= 0.85) break;\n }\n }\n\n if (!bestElement || bestScore < 0.4) return null;\n\n return {\n element: bestElement,\n confidence: Math.min(bestScore, 0.85),\n strategy: \"scan\",\n };\n}\n\n/**\n * Score a candidate element against all stored anchor signals.\n *\n * Dynamic weighting — only active signals contribute, then normalized:\n * - Text snippet (fuzzy substring match): weight 40 (most reliable for reordering)\n * - Fingerprint (structural match): weight 20\n * - Prefix/suffix context: weight 20\n * - Neighbor text: weight 20\n */\nfunction scoreCandidate(candidate: Element, anchor: AnchorData): number {\n let score = 0;\n let totalWeight = 0;\n\n // Truncate to avoid O(n*m) explosion on huge text nodes\n const candidateText = (candidate.textContent?.trim() ?? \"\").slice(0, 500);\n\n // --- Text snippet (weight 40 — most important for reordered elements) ---\n if (anchor.textSnippet) {\n totalWeight += 40;\n score += fuzzyIncludes(candidateText, anchor.textSnippet, 0.5) * 40;\n }\n\n // --- Fingerprint (weight 20) ---\n if (anchor.fingerprint) {\n totalWeight += 20;\n score += scoreFingerprint(candidate, anchor.fingerprint) * 20;\n }\n\n // --- Prefix/suffix context (weight 20) ---\n if (anchor.textPrefix || anchor.textSuffix) {\n totalWeight += 20;\n let contextScore = 0;\n let contextParts = 0;\n\n if (anchor.textPrefix) {\n const prevText = adjacentText(candidate, \"before\");\n contextScore += prevText ? similarity(prevText, anchor.textPrefix) : 0;\n contextParts++;\n }\n\n if (anchor.textSuffix) {\n const nextText = adjacentText(candidate, \"after\");\n contextScore += nextText ? similarity(nextText, anchor.textSuffix) : 0;\n contextParts++;\n }\n\n if (contextParts > 0) {\n score += (contextScore / contextParts) * 20;\n }\n }\n\n // --- Neighbor text (weight 20) ---\n if (anchor.neighborText) {\n totalWeight += 20;\n const candidateNeighbor = neighborText(candidate);\n score += candidateNeighbor ? similarity(candidateNeighbor, anchor.neighborText) * 20 : 0;\n }\n\n return totalWeight > 0 ? score / totalWeight : 0;\n}\n\n/**\n * Resolve an annotation's position on the page.\n * Converts stored percentage-based rect back to absolute coordinates\n * using the current bounding box of the resolved anchor element.\n */\nexport function resolveAnnotation(anchor: AnchorData, rect: RectData): ResolvedAnnotation | null {\n const resolution = resolveAnchor(anchor);\n\n if (!resolution) return null;\n\n const bounds = resolution.element.getBoundingClientRect();\n const absoluteRect = new DOMRect(\n bounds.x + rect.xPct * bounds.width,\n bounds.y + rect.yPct * bounds.height,\n rect.wPct * bounds.width,\n rect.hPct * bounds.height,\n );\n\n return {\n element: resolution.element,\n rect: absoluteRect,\n confidence: resolution.confidence,\n strategy: resolution.strategy,\n };\n}\n","import type { AnchorData, FeedbackResponse, RectData } from \"@siteping/core\";\nimport { resolveAnnotation } from \"./dom/resolver.js\";\nimport { el, setText } from \"./dom-utils.js\";\nimport type { EventBus, WidgetEvents } from \"./events.js\";\nimport { getTypeLabel, type TFunction } from \"./i18n/index.js\";\nimport { getTypeColor, type ThemeColors } from \"./styles/theme.js\";\nimport type { Tooltip } from \"./tooltip.js\";\n\ntype Annotation = FeedbackResponse[\"annotations\"][number];\n\nfunction toAnchorData(a: Annotation): AnchorData {\n return {\n cssSelector: a.cssSelector,\n xpath: a.xpath,\n textSnippet: a.textSnippet,\n elementTag: a.elementTag,\n elementId: a.elementId ?? undefined,\n textPrefix: a.textPrefix,\n textSuffix: a.textSuffix,\n fingerprint: a.fingerprint,\n neighborText: a.neighborText,\n };\n}\n\nfunction toRectData(a: Annotation): RectData {\n return { xPct: a.xPct, yPct: a.yPct, wPct: a.wPct, hPct: a.hPct };\n}\n\n/** Half of the 26px marker diameter — used for centering on anchor corner. */\nconst MARKER_OFFSET = 13;\n\n/** Convert a resolved rect to document-absolute marker position. */\nfunction markerPosition(rect: DOMRect): { top: number; left: number } {\n return {\n top: rect.top + window.scrollY - MARKER_OFFSET,\n left: rect.right + window.scrollX - MARKER_OFFSET,\n };\n}\n\ninterface MarkerEntry {\n feedback: FeedbackResponse;\n elements: HTMLElement[];\n baseTop: number;\n baseLeft: number;\n}\n\ninterface Cluster {\n entries: MarkerEntry[];\n elementIndices: number[];\n expanded: boolean;\n}\n\n/** Get the i-th marker element from a cluster. */\nfunction clusterMarker(cluster: Cluster, i: number): HTMLElement | undefined {\n const entry = cluster.entries[i];\n const elIdx = cluster.elementIndices[i];\n if (!entry || elIdx === undefined) return undefined;\n return entry.elements[elIdx];\n}\n\nconst HIGHLIGHT_FADE = 300;\nconst REPOSITION_DEBOUNCE = 200;\nconst LOW_CONFIDENCE_THRESHOLD = 0.7;\nconst CLUSTER_DISTANCE = 28;\nconst FAN_SPACING = 32;\n\n/**\n * Numbered markers on the page for each feedback annotation.\n *\n * Cluster system: click-to-expand (same pattern as Google Maps / Spiderfier).\n * Hover is only used for tooltip/scale on individual markers — never for expansion.\n */\nexport class MarkerManager {\n private container: HTMLElement;\n private entries: MarkerEntry[] = [];\n private highlightElements: HTMLElement[] = [];\n private pinnedFeedback: FeedbackResponse | null = null;\n private onDocumentClick: ((e: MouseEvent) => void) | null = null;\n private repositionTimer: number | null = null;\n private mutationObserver: MutationObserver | null = null;\n private scrollHandler: (() => void) | null = null;\n private resizeHandler: (() => void) | null = null;\n private anchorCache = new Map<string, WeakRef<Element>>();\n private clusters: Cluster[] = [];\n private onDocumentClickForClusters: ((e: MouseEvent) => void) | null = null;\n\n get count(): number {\n return this.entries.length;\n }\n\n constructor(\n private readonly colors: ThemeColors,\n private readonly tooltip: Tooltip,\n private readonly bus: EventBus<WidgetEvents>,\n private readonly t: TFunction,\n ) {\n this.container = el(\"div\", {\n style: \"position:absolute;top:0;left:0;pointer-events:none;z-index:2147483646;\",\n });\n this.container.id = \"siteping-markers\";\n document.body.appendChild(this.container);\n\n this.bus.on(\"annotations:toggle\", (visible) => {\n this.container.style.display = visible ? \"block\" : \"none\";\n });\n\n this.resizeHandler = () => this.scheduleReposition();\n window.addEventListener(\"resize\", this.resizeHandler, { passive: true });\n\n this.scrollHandler = () => this.scheduleReposition();\n window.addEventListener(\"scroll\", this.scrollHandler, { passive: true, capture: true });\n\n // Re-resolve after DOM changes (SPA, lazy-load).\n // Filter out widget-owned mutations and skip batches with only irrelevant changes.\n // Fast-path: large batches (>20 mutations) skip per-record filtering.\n this.mutationObserver = new MutationObserver((mutations) => {\n if (mutations.length > 20) {\n this.scheduleReposition();\n return;\n }\n let hasRelevantMutation = false;\n for (const m of mutations) {\n if (this.container.contains(m.target) || this.tooltip.contains(m.target)) continue;\n hasRelevantMutation = true;\n break;\n }\n if (hasRelevantMutation) this.scheduleReposition();\n });\n this.mutationObserver.observe(document.body, {\n childList: true,\n subtree: true,\n attributes: false,\n characterData: false,\n });\n\n this.onDocumentClickForClusters = (e: MouseEvent) => {\n if (this.container.contains(e.target as Node)) return;\n this.collapseAllClusters();\n };\n document.addEventListener(\"click\", this.onDocumentClickForClusters);\n }\n\n private scheduleReposition(): void {\n if (this.repositionTimer) return;\n if (\"requestIdleCallback\" in window) {\n this.repositionTimer = window.requestIdleCallback(\n () => {\n this.repositionTimer = null;\n this.repositionAll();\n },\n { timeout: REPOSITION_DEBOUNCE + 100 },\n );\n } else {\n this.repositionTimer = +setTimeout(() => {\n this.repositionTimer = null;\n this.repositionAll();\n }, REPOSITION_DEBOUNCE);\n }\n }\n\n private repositionAll(): void {\n for (const entry of this.entries) {\n for (let i = 0; i < entry.feedback.annotations.length; i++) {\n const markerEl = entry.elements[i];\n if (!markerEl) continue;\n\n const annotation = entry.feedback.annotations[i];\n if (!annotation) continue;\n const cacheKey = `${entry.feedback.id}:${i}`;\n\n // Try cached element first to avoid full resolution chain.\n const cachedRef = this.anchorCache.get(cacheKey);\n const cachedEl = cachedRef?.deref();\n let resolved: ReturnType<typeof resolveAnnotation>;\n\n if (cachedEl?.isConnected) {\n const anchorRect = cachedEl.getBoundingClientRect();\n const r = toRectData(annotation);\n resolved = {\n element: cachedEl,\n rect: new DOMRect(\n anchorRect.left + r.xPct * anchorRect.width,\n anchorRect.top + r.yPct * anchorRect.height,\n r.wPct * anchorRect.width,\n r.hPct * anchorRect.height,\n ),\n confidence: 1,\n strategy: \"css\",\n };\n } else {\n resolved = resolveAnnotation(toAnchorData(annotation), toRectData(annotation));\n if (resolved?.element) {\n this.anchorCache.set(cacheKey, new WeakRef(resolved.element));\n }\n }\n\n if (!resolved) {\n markerEl.style.display = \"none\";\n continue;\n }\n\n const pos = markerPosition(resolved.rect);\n entry.baseTop = pos.top;\n entry.baseLeft = pos.left;\n markerEl.style.display = \"flex\";\n this.applyConfidenceStyle(markerEl, resolved.confidence, entry.feedback);\n }\n }\n this.applyClusterPositions();\n }\n\n private applyClusterPositions(): void {\n for (const cluster of this.clusters) {\n if (cluster.expanded) {\n this.applyFanPositions(cluster);\n } else {\n this.applyStackPositions(cluster);\n }\n }\n }\n\n render(feedbacks: FeedbackResponse[]): void {\n this.clear();\n feedbacks.forEach((feedback, i) => {\n const entry = this.buildEntry(feedback, i + 1);\n this.entries.push(entry);\n });\n this.buildClusters();\n }\n\n addFeedback(feedback: FeedbackResponse, index: number): void {\n const entry = this.buildEntry(feedback, index);\n for (const m of entry.elements) {\n m.style.animation = \"sp-marker-in 0.35s cubic-bezier(0.34,1.56,0.64,1) both\";\n }\n this.entries.push(entry);\n this.buildClusters();\n }\n\n private buildEntry(feedback: FeedbackResponse, index: number): MarkerEntry {\n const entry: MarkerEntry = { feedback, elements: [], baseTop: 0, baseLeft: 0 };\n for (const annotation of feedback.annotations) {\n const resolved = resolveAnnotation(toAnchorData(annotation), toRectData(annotation));\n if (!resolved) continue;\n const pos = markerPosition(resolved.rect);\n entry.baseTop = pos.top;\n entry.baseLeft = pos.left;\n const marker = this.createMarker(index, feedback, pos);\n this.applyConfidenceStyle(marker, resolved.confidence, feedback);\n this.container.appendChild(marker);\n entry.elements.push(marker);\n }\n return entry;\n }\n\n private buildClusters(): void {\n for (const badge of this.container.querySelectorAll<HTMLElement>(\".sp-cluster-badge\")) {\n badge.remove();\n }\n\n const allItems: { entry: MarkerEntry; elIdx: number }[] = [];\n for (const entry of this.entries) {\n for (let i = 0; i < entry.elements.length; i++) {\n allItems.push({ entry, elIdx: i });\n }\n }\n\n const used = new Set<number>();\n this.clusters = [];\n\n for (let i = 0; i < allItems.length; i++) {\n if (used.has(i)) continue;\n const itemI = allItems[i];\n if (!itemI) continue;\n const cluster: Cluster = {\n entries: [itemI.entry],\n elementIndices: [itemI.elIdx],\n expanded: false,\n };\n used.add(i);\n\n for (let j = i + 1; j < allItems.length; j++) {\n if (used.has(j)) continue;\n const a = itemI.entry;\n const itemJ = allItems[j];\n if (!itemJ) continue;\n const b = itemJ.entry;\n const dist = Math.sqrt((a.baseLeft - b.baseLeft) ** 2 + (a.baseTop - b.baseTop) ** 2);\n if (dist < CLUSTER_DISTANCE) {\n cluster.entries.push(b);\n cluster.elementIndices.push(itemJ.elIdx);\n used.add(j);\n }\n }\n\n this.clusters.push(cluster);\n }\n\n for (const cluster of this.clusters) {\n if (cluster.entries.length <= 1) continue;\n this.applyStackPositions(cluster);\n this.addClusterBadge(cluster);\n }\n }\n\n private applyStackPositions(cluster: Cluster): void {\n const first = cluster.entries[0];\n if (!first) return;\n const { baseTop, baseLeft } = first;\n const isSolo = cluster.entries.length <= 1;\n for (let i = 0; i < cluster.entries.length; i++) {\n const m = clusterMarker(cluster, i);\n if (!m) continue;\n m.style.top = `${baseTop + (isSolo ? 0 : i * 3)}px`;\n m.style.left = `${baseLeft + (isSolo ? 0 : i * 3)}px`;\n m.style.zIndex = String(i + 1);\n }\n }\n\n private applyFanPositions(cluster: Cluster): void {\n const first = cluster.entries[0];\n if (!first) return;\n const { baseTop, baseLeft } = first;\n const count = cluster.entries.length;\n const totalWidth = (count - 1) * FAN_SPACING;\n const startLeft = baseLeft - totalWidth / 2;\n\n for (let i = 0; i < count; i++) {\n const m = clusterMarker(cluster, i);\n if (!m) continue;\n m.style.top = `${baseTop}px`;\n m.style.left = `${startLeft + i * FAN_SPACING}px`;\n m.style.zIndex = String(10 + i);\n }\n }\n\n private addClusterBadge(cluster: Cluster): void {\n const topMarker = clusterMarker(cluster, cluster.entries.length - 1);\n if (!topMarker) return;\n const badge = el(\"div\", {\n class: \"sp-cluster-badge\",\n style: `\n position:absolute;top:-6px;right:-6px;\n min-width:16px;height:16px;padding:0 4px;\n border-radius:9999px;\n background:${this.colors.accent};color:#fff;\n font-size:10px;font-weight:700;\n display:flex;align-items:center;justify-content:center;\n border:1.5px solid #fff;\n pointer-events:none;\n font-family:\"Inter\",system-ui,-apple-system,sans-serif;\n line-height:1;\n `,\n });\n setText(badge, String(cluster.entries.length));\n topMarker.appendChild(badge);\n }\n\n private setBadgesVisible(cluster: Cluster, visible: boolean): void {\n for (let i = 0; i < cluster.entries.length; i++) {\n const badge = clusterMarker(cluster, i)?.querySelector(\".sp-cluster-badge\") as HTMLElement | null;\n if (badge) badge.style.display = visible ? \"flex\" : \"none\";\n }\n }\n\n private findCluster(marker: HTMLElement): Cluster | null {\n for (const cluster of this.clusters) {\n if (cluster.entries.length <= 1) continue;\n for (let i = 0; i < cluster.entries.length; i++) {\n if (clusterMarker(cluster, i) === marker) return cluster;\n }\n }\n return null;\n }\n\n private handleClusterClick(marker: HTMLElement, e: MouseEvent): boolean {\n const cluster = this.findCluster(marker);\n if (!cluster) return false;\n if (!cluster.expanded) {\n e.stopPropagation();\n this.collapseAllClusters();\n cluster.expanded = true;\n this.applyFanPositions(cluster);\n this.setBadgesVisible(cluster, false);\n return true;\n }\n return false;\n }\n\n private collapseCluster(cluster: Cluster): void {\n if (!cluster.expanded) return;\n cluster.expanded = false;\n this.applyStackPositions(cluster);\n this.setBadgesVisible(cluster, true);\n }\n\n private collapseAllClusters(): void {\n for (const cluster of this.clusters) {\n this.collapseCluster(cluster);\n }\n }\n\n private applyConfidenceStyle(marker: HTMLElement, confidence: number, feedback: FeedbackResponse): void {\n const isResolved = feedback.status === \"resolved\";\n if (confidence < LOW_CONFIDENCE_THRESHOLD && !isResolved) {\n marker.style.borderStyle = \"dashed\";\n marker.style.opacity = \"0.7\";\n marker.title = this.t(\"marker.approximate\").replace(\"{confidence}\", String(Math.round(confidence * 100)));\n } else {\n marker.style.borderStyle = \"solid\";\n marker.style.opacity = \"1\";\n marker.title = \"\";\n }\n }\n\n private createMarker(number: number, feedback: FeedbackResponse, pos: { top: number; left: number }): HTMLElement {\n const typeColor = getTypeColor(feedback.type, this.colors);\n const isResolved = feedback.status === \"resolved\";\n\n const marker = el(\"div\", {\n style: `\n position:absolute;\n top:${pos.top}px;\n left:${pos.left}px;\n width:26px;height:26px;\n border-radius:50%;\n background:${isResolved ? \"rgba(241,245,249,0.9)\" : \"rgba(255,255,255,0.92)\"};\n border:2px solid ${isResolved ? \"#cbd5e1\" : typeColor};\n display:flex;align-items:center;justify-content:center;\n font-family:\"Inter\",system-ui,-apple-system,sans-serif;\n font-size:11px;font-weight:700;\n color:${isResolved ? \"#94a3b8\" : typeColor};\n cursor:pointer;pointer-events:auto;\n box-shadow:${isResolved ? \"0 2px 8px rgba(0,0,0,0.06)\" : `0 2px 12px ${typeColor}25, 0 2px 6px rgba(0,0,0,0.06)`};\n transition:top 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), left 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), transform 0.15s ease, box-shadow 0.15s ease;\n user-select:none;\n -webkit-font-smoothing:antialiased;\n `,\n });\n marker.dataset.feedbackId = feedback.id;\n marker.setAttribute(\"tabindex\", \"0\");\n marker.setAttribute(\"role\", \"button\");\n const truncatedMessage = feedback.message.length > 60 ? `${feedback.message.slice(0, 60)}...` : feedback.message;\n const ariaLabel = this.t(\"marker.aria\")\n .replace(\"{number}\", String(number))\n .replace(\"{type}\", getTypeLabel(feedback.type, this.t))\n .replace(\"{message}\", truncatedMessage);\n marker.setAttribute(\"aria-label\", ariaLabel);\n marker.setAttribute(\"aria-describedby\", this.tooltip.tooltipId);\n setText(marker, isResolved ? \"\\u2713\" : String(number));\n\n marker.addEventListener(\"mouseenter\", () => {\n marker.style.transform = \"scale(1.2)\";\n marker.style.boxShadow = isResolved\n ? \"0 4px 16px rgba(0,0,0,0.1)\"\n : `0 4px 20px ${typeColor}35, 0 4px 12px rgba(0,0,0,0.08)`;\n this.tooltip.show(feedback, marker.getBoundingClientRect());\n if (!this.pinnedFeedback) this.showHighlight(feedback);\n });\n\n marker.addEventListener(\"mouseleave\", () => {\n marker.style.transform = \"scale(1)\";\n marker.style.boxShadow = isResolved\n ? \"0 2px 8px rgba(0,0,0,0.06)\"\n : `0 2px 12px ${typeColor}25, 0 2px 6px rgba(0,0,0,0.06)`;\n this.tooltip.scheduleHide();\n if (!this.pinnedFeedback) this.clearHighlight();\n });\n\n const activateMarker = (e: MouseEvent | KeyboardEvent) => {\n if (e instanceof MouseEvent && this.handleClusterClick(marker, e)) return;\n this.pinHighlight(feedback);\n this.bus.emit(\"panel:toggle\", true);\n marker.dispatchEvent(\n new CustomEvent(\"sp-marker-click\", {\n detail: { feedbackId: feedback.id },\n bubbles: true,\n }),\n );\n };\n\n marker.addEventListener(\"click\", (e) => activateMarker(e));\n marker.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n activateMarker(e);\n }\n });\n\n return marker;\n }\n\n highlight(feedbackId: string): void {\n for (const entry of this.entries) {\n if (entry.feedback.id === feedbackId) {\n for (const markerEl of entry.elements) {\n markerEl.style.animation = \"sp-pulse-ring 0.7s ease-out\";\n markerEl.addEventListener(\n \"animationend\",\n () => {\n markerEl.style.animation = \"\";\n },\n { once: true },\n );\n }\n }\n }\n }\n\n showHighlight(feedback: FeedbackResponse): void {\n this.removeHighlightElements();\n for (const annotation of feedback.annotations) {\n const resolved = resolveAnnotation(toAnchorData(annotation), toRectData(annotation));\n if (!resolved) continue;\n\n const typeColor = getTypeColor(feedback.type, this.colors);\n const rect = resolved.rect;\n const highlight = el(\"div\", {\n style: `\n position:absolute;\n top:${rect.top + window.scrollY}px;\n left:${rect.left + window.scrollX}px;\n width:${rect.width}px;height:${rect.height}px;\n border:2px solid ${typeColor};\n background:${typeColor}0c;\n border-radius:8px;\n pointer-events:none;z-index:-1;\n opacity:0;\n box-shadow:0 0 16px ${typeColor}20;\n transition:opacity ${HIGHLIGHT_FADE}ms ease;\n `,\n });\n this.container.appendChild(highlight);\n this.highlightElements.push(highlight);\n void highlight.offsetHeight; // Force reflow for CSS transition\n highlight.style.opacity = \"1\";\n }\n }\n\n pinHighlight(feedback: FeedbackResponse): void {\n this.unpinHighlight();\n this.showHighlight(feedback);\n this.pinnedFeedback = feedback;\n this.onDocumentClick = (e: MouseEvent) => {\n if (this.container.contains(e.target as Node)) return;\n this.unpinHighlight();\n };\n document.addEventListener(\"click\", this.onDocumentClick, { capture: true });\n }\n\n private unpinHighlight(): void {\n if (this.onDocumentClick) {\n document.removeEventListener(\"click\", this.onDocumentClick, { capture: true });\n this.onDocumentClick = null;\n }\n this.pinnedFeedback = null;\n this.clearHighlight();\n }\n\n private clearHighlight(): void {\n for (const h of this.highlightElements) {\n h.style.opacity = \"0\";\n setTimeout(() => h.remove(), HIGHLIGHT_FADE);\n }\n this.highlightElements = [];\n }\n\n private removeHighlightElements(): void {\n for (const h of this.highlightElements) h.remove();\n this.highlightElements = [];\n }\n\n clear(): void {\n this.unpinHighlight();\n this.container.replaceChildren();\n this.entries = [];\n this.clusters = [];\n this.anchorCache.clear();\n }\n\n destroy(): void {\n this.unpinHighlight();\n if (this.repositionTimer) {\n if (\"cancelIdleCallback\" in window) {\n window.cancelIdleCallback(this.repositionTimer);\n }\n clearTimeout(this.repositionTimer);\n }\n if (this.resizeHandler) window.removeEventListener(\"resize\", this.resizeHandler);\n if (this.scrollHandler) window.removeEventListener(\"scroll\", this.scrollHandler, { capture: true });\n if (this.onDocumentClickForClusters) document.removeEventListener(\"click\", this.onDocumentClickForClusters);\n this.mutationObserver?.disconnect();\n this.container.remove();\n }\n}\n","import type { FeedbackResponse, FeedbackType } from \"@siteping/core\";\nimport type { WidgetClient } from \"./api-client.js\";\nimport { el, formatRelativeDate, parseSvg, setText } from \"./dom-utils.js\";\nimport type { EventBus, WidgetEvents } from \"./events.js\";\nimport { getTypeLabel, type TFunction } from \"./i18n/index.js\";\nimport { ICON_CHECK, ICON_CLOSE, ICON_SEARCH, ICON_TRASH, ICON_UNDO } from \"./icons.js\";\nimport type { MarkerManager } from \"./markers.js\";\nimport { getTypeBgColor, getTypeColor, type ThemeColors } from \"./styles/theme.js\";\n\n/**\n * Side panel (400px) with feedback history, filters, and search.\n *\n * Lives inside the Shadow DOM.\n * Glassmorphism: glass background, staggered card animations,\n * loading states, resolve feedback with disabled state.\n */\nexport class Panel {\n private root: HTMLElement;\n private listContainer: HTMLElement;\n private searchInput: HTMLInputElement;\n private closeBtn: HTMLButtonElement;\n private deleteAllBtn: HTMLButtonElement;\n private activeFilters = new Set<string>([\"all\"]);\n private feedbacks: FeedbackResponse[] = [];\n private isOpen = false;\n private searchTimeout: ReturnType<typeof setTimeout> | null = null;\n private loadController: AbortController | null = null;\n /** Tracks feedback IDs with in-flight mutations to prevent spam-click race conditions */\n private pendingMutations = new Set<string>();\n\n constructor(\n shadowRoot: ShadowRoot,\n private readonly colors: ThemeColors,\n private readonly bus: EventBus<WidgetEvents>,\n private readonly client: WidgetClient,\n private readonly projectName: string,\n private readonly markers: MarkerManager,\n private readonly t: TFunction,\n private readonly locale: string,\n ) {\n this.root = el(\"div\", { class: \"sp-panel\" });\n this.root.setAttribute(\"role\", \"complementary\");\n this.root.setAttribute(\"aria-label\", this.t(\"panel.ariaLabel\"));\n this.root.setAttribute(\"aria-hidden\", \"true\");\n\n // Header\n const header = el(\"div\", { class: \"sp-panel-header\" });\n const title = el(\"span\", { class: \"sp-panel-title\" });\n setText(title, this.t(\"panel.title\"));\n\n this.closeBtn = document.createElement(\"button\");\n this.closeBtn.className = \"sp-panel-close\";\n this.closeBtn.setAttribute(\"aria-label\", this.t(\"panel.close\"));\n this.closeBtn.appendChild(parseSvg(ICON_CLOSE));\n this.closeBtn.addEventListener(\"click\", () => this.close());\n\n this.deleteAllBtn = document.createElement(\"button\");\n this.deleteAllBtn.className = \"sp-btn-delete-all\";\n this.deleteAllBtn.setAttribute(\"aria-label\", this.t(\"panel.deleteAll\"));\n this.deleteAllBtn.appendChild(parseSvg(ICON_TRASH));\n const deleteAllLabel = document.createElement(\"span\");\n setText(deleteAllLabel, ` ${this.t(\"panel.deleteAll\")}`);\n this.deleteAllBtn.appendChild(deleteAllLabel);\n this.deleteAllBtn.addEventListener(\"click\", () => this.confirmDeleteAll());\n\n const headerRight = el(\"div\", { class: \"sp-panel-header-right\" });\n headerRight.appendChild(this.deleteAllBtn);\n headerRight.appendChild(this.closeBtn);\n\n header.appendChild(title);\n header.appendChild(headerRight);\n\n // Filters\n const filters = el(\"div\", { class: \"sp-filters\" });\n\n // Search\n const searchWrap = el(\"div\", { class: \"sp-search-wrap\" });\n const searchIcon = parseSvg(ICON_SEARCH);\n searchIcon.setAttribute(\"class\", \"sp-search-icon\");\n this.searchInput = document.createElement(\"input\");\n this.searchInput.type = \"text\";\n this.searchInput.className = \"sp-search\";\n this.searchInput.placeholder = this.t(\"panel.search\");\n this.searchInput.setAttribute(\"aria-label\", this.t(\"panel.searchAria\"));\n this.searchInput.addEventListener(\"input\", () => {\n if (this.searchTimeout) clearTimeout(this.searchTimeout);\n this.searchTimeout = setTimeout(() => this.loadFeedbacks().catch(() => {}), 200);\n });\n searchWrap.appendChild(searchIcon);\n searchWrap.appendChild(this.searchInput);\n\n // Chips\n const chips = el(\"div\", { class: \"sp-chips\" });\n const chipOptions = [\n { value: \"all\", label: this.t(\"panel.filterAll\") },\n { value: \"question\", label: this.t(\"type.question\") },\n { value: \"change\", label: this.t(\"type.change\") },\n { value: \"bug\", label: this.t(\"type.bug\") },\n { value: \"other\", label: this.t(\"type.other\") },\n ];\n\n for (const option of chipOptions) {\n const chip = document.createElement(\"button\");\n chip.className = `sp-chip ${option.value === \"all\" ? \"sp-chip--active\" : \"\"}`;\n if (option.value !== \"all\") {\n chip.style.borderColor = getTypeColor(option.value, this.colors);\n }\n setText(chip, option.label);\n chip.dataset.filter = option.value;\n chip.setAttribute(\"aria-pressed\", option.value === \"all\" ? \"true\" : \"false\");\n chip.addEventListener(\"click\", () => this.toggleFilter(option.value, chips));\n chips.appendChild(chip);\n }\n\n filters.appendChild(searchWrap);\n filters.appendChild(chips);\n\n // List\n this.listContainer = el(\"div\", { class: \"sp-list\" });\n this.listContainer.setAttribute(\"role\", \"list\");\n this.listContainer.setAttribute(\"aria-label\", this.t(\"panel.feedbackList\"));\n\n this.root.appendChild(header);\n this.root.appendChild(filters);\n this.root.appendChild(this.listContainer);\n shadowRoot.appendChild(this.root);\n\n // --- Event delegation on listContainer ---\n\n this.onListClick = (e: Event) => {\n const target = e.target as Element;\n\n // Action buttons (expand, resolve, delete)\n const actionEl = target.closest<HTMLElement>(\"[data-action]\");\n if (actionEl) {\n e.stopPropagation();\n const card = actionEl.closest<HTMLElement>(\".sp-card\");\n if (!card) return;\n const feedbackId = card.dataset.feedbackId;\n const feedback = this.feedbacks.find((f) => f.id === feedbackId);\n if (!feedback) return;\n\n const action = actionEl.dataset.action;\n if (action === \"expand\") {\n const message = card.querySelector<HTMLElement>(\".sp-card-message\");\n if (!message) return;\n const isExpanded = message.classList.toggle(\"sp-card-message--expanded\");\n setText(actionEl, isExpanded ? this.t(\"panel.showLess\") : this.t(\"panel.showMore\"));\n actionEl.setAttribute(\"aria-expanded\", String(isExpanded));\n } else if (action === \"resolve\") {\n if (this.pendingMutations.has(feedback.id)) return;\n const btn = actionEl as HTMLButtonElement;\n this.toggleResolve(feedback, btn).catch(() => {});\n } else if (action === \"delete\") {\n if (this.pendingMutations.has(feedback.id)) return;\n const btn = actionEl as HTMLButtonElement;\n this.deleteFeedback(feedback, btn).catch(() => {});\n }\n return;\n }\n\n // Card click (scroll to annotation)\n const card = target.closest<HTMLElement>(\".sp-card\");\n if (card) {\n const feedbackId = card.dataset.feedbackId;\n const feedback = this.feedbacks.find((f) => f.id === feedbackId);\n if (feedback && feedback.annotations.length > 0) {\n const ann = feedback.annotations[0];\n if (!ann) return;\n window.scrollTo({ left: ann.scrollX, top: ann.scrollY, behavior: \"smooth\" });\n this.markers.pinHighlight(feedback);\n }\n }\n };\n this.listContainer.addEventListener(\"click\", this.onListClick);\n\n this.onListKeydown = (e: Event) => {\n const ke = e as KeyboardEvent;\n if (ke.key !== \"Enter\" && ke.key !== \" \") return;\n const target = ke.target as Element;\n const card = target.closest<HTMLElement>(\".sp-card\");\n // Only activate if the card itself is focused, not a button inside it\n if (!card || target !== card) return;\n ke.preventDefault();\n const feedbackId = card.dataset.feedbackId;\n const feedback = this.feedbacks.find((f) => f.id === feedbackId);\n if (feedback && feedback.annotations.length > 0) {\n const ann = feedback.annotations[0];\n if (!ann) return;\n window.scrollTo({ left: ann.scrollX, top: ann.scrollY, behavior: \"smooth\" });\n this.markers.pinHighlight(feedback);\n }\n };\n this.listContainer.addEventListener(\"keydown\", this.onListKeydown);\n\n // mouseover/mouseout bubble (unlike mouseenter/mouseleave), enabling delegation\n this.onListMouseover = (e: Event) => {\n const target = (e as MouseEvent).target as Element;\n const card = target.closest<HTMLElement>(\".sp-card\");\n if (!card) return;\n const feedbackId = card.dataset.feedbackId;\n if (feedbackId) this.markers.highlight(feedbackId);\n };\n this.listContainer.addEventListener(\"mouseover\", this.onListMouseover);\n\n this.onListMouseout = (e: Event) => {\n const target = (e as MouseEvent).relatedTarget as Element | null;\n // Only clear highlight when leaving all cards (relatedTarget is outside listContainer)\n if (target && this.listContainer.contains(target)) return;\n this.markers.highlight(\"\");\n };\n this.listContainer.addEventListener(\"mouseout\", this.onListMouseout);\n\n // Events\n this.bus.on(\"panel:toggle\", (open) => {\n open ? this.open() : this.close();\n });\n\n // Keyboard handling: Escape to close + focus trap\n shadowRoot.addEventListener(\"keydown\", (e) => {\n const ke = e as KeyboardEvent;\n if (ke.key === \"Escape\" && this.isOpen) {\n this.close();\n return;\n }\n if (ke.key === \"Tab\" && this.isOpen) {\n const focusable = this.root.querySelectorAll<HTMLElement>(\n 'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])',\n );\n if (focusable.length === 0) return;\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n if (!first || !last) return;\n const active = (shadowRoot as ShadowRoot).activeElement;\n if (ke.shiftKey && active === first) {\n ke.preventDefault();\n last.focus();\n } else if (!ke.shiftKey && active === last) {\n ke.preventDefault();\n first.focus();\n }\n }\n });\n\n // Listen for marker clicks\n this.onMarkerClick = ((e: CustomEvent) => {\n this.scrollToFeedback(e.detail.feedbackId);\n }) as EventListener;\n document.addEventListener(\"sp-marker-click\", this.onMarkerClick);\n }\n\n private onMarkerClick: EventListener;\n private onListClick: (e: Event) => void;\n private onListKeydown: (e: Event) => void;\n private onListMouseover: (e: Event) => void;\n private onListMouseout: (e: Event) => void;\n\n async open(): Promise<void> {\n if (this.isOpen) return;\n this.isOpen = true;\n this.root.classList.add(\"sp-panel--open\");\n this.root.setAttribute(\"aria-hidden\", \"false\");\n this.bus.emit(\"open\");\n await this.loadFeedbacks();\n // Move focus into the panel (search input or close button)\n requestAnimationFrame(() => {\n if (this.searchInput) {\n this.searchInput.focus();\n } else {\n this.closeBtn.focus();\n }\n });\n }\n\n close(): void {\n if (!this.isOpen) return;\n this.isOpen = false;\n this.root.classList.remove(\"sp-panel--open\");\n this.root.setAttribute(\"aria-hidden\", \"true\");\n this.bus.emit(\"close\");\n // Restore focus to the FAB\n const fab = (this.root.getRootNode() as ShadowRoot).querySelector<HTMLButtonElement>(\".sp-fab\");\n fab?.focus();\n }\n\n private showLoading(): void {\n this.listContainer.replaceChildren();\n const loading = el(\"div\", { class: \"sp-loading\" });\n loading.setAttribute(\"role\", \"status\");\n loading.setAttribute(\"aria-live\", \"polite\");\n loading.setAttribute(\"aria-label\", this.t(\"panel.loading\"));\n const spinner = el(\"div\", { class: \"sp-spinner\" });\n loading.appendChild(spinner);\n this.listContainer.appendChild(loading);\n }\n\n private showError(): void {\n this.listContainer.replaceChildren();\n const empty = el(\"div\", { class: \"sp-empty\" });\n empty.setAttribute(\"role\", \"status\");\n empty.setAttribute(\"aria-live\", \"polite\");\n const text = el(\"div\", { class: \"sp-empty-text\" });\n setText(text, this.t(\"panel.loadError\"));\n const retryBtn = document.createElement(\"button\");\n retryBtn.className = \"sp-btn-ghost\";\n retryBtn.style.marginTop = \"8px\";\n setText(retryBtn, this.t(\"panel.retry\"));\n retryBtn.addEventListener(\"click\", () => this.loadFeedbacks().catch(() => {}));\n empty.appendChild(text);\n empty.appendChild(retryBtn);\n this.listContainer.appendChild(empty);\n }\n\n private async loadFeedbacks(): Promise<void> {\n // Cancel any in-flight request to prevent stale responses from overwriting newer results\n this.loadController?.abort();\n this.loadController = new AbortController();\n const { signal } = this.loadController;\n\n const search = this.searchInput.value.trim() || undefined;\n const typeFilter = this.activeFilters.has(\"all\") ? undefined : (Array.from(this.activeFilters)[0] as FeedbackType);\n\n const options: { limit: number; type?: FeedbackType; search?: string } = { limit: 50 };\n if (typeFilter) options.type = typeFilter;\n if (search) options.search = search;\n\n // Only show spinner on first load (empty list) — otherwise keep current content visible\n const hasContent = this.feedbacks.length > 0;\n if (!hasContent) this.showLoading();\n\n try {\n const { feedbacks } = await this.client.getFeedbacks(this.projectName, options);\n if (signal.aborted) return; // Stale response — a newer request superseded this one\n this.feedbacks = feedbacks;\n this.renderList();\n this.markers.render(feedbacks);\n } catch (error) {\n if (signal.aborted) return; // Expected abort, not a real error\n if (!hasContent) this.showError();\n this.bus.emit(\"feedback:error\", error instanceof Error ? error : new Error(String(error)));\n }\n }\n\n private renderList(): void {\n this.listContainer.replaceChildren();\n\n if (this.feedbacks.length === 0) {\n const empty = el(\"div\", { class: \"sp-empty\" });\n empty.setAttribute(\"role\", \"status\");\n empty.setAttribute(\"aria-live\", \"polite\");\n const emptyText = el(\"div\", { class: \"sp-empty-text\" });\n setText(emptyText, this.t(\"panel.empty\"));\n empty.appendChild(emptyText);\n this.listContainer.appendChild(empty);\n return;\n }\n\n this.feedbacks.forEach((feedback, index) => {\n const card = this.createCard(feedback, index + 1);\n // Stagger animation via CSS custom property\n card.style.setProperty(\"--sp-card-i\", String(index));\n this.listContainer.appendChild(card);\n });\n }\n\n private createCard(feedback: FeedbackResponse, number: number): HTMLElement {\n const isResolved = feedback.status === \"resolved\";\n const typeColor = getTypeColor(feedback.type, this.colors);\n\n const card = el(\"div\", {\n class: `sp-card ${isResolved ? \"sp-card--resolved\" : \"\"}`,\n });\n card.setAttribute(\"role\", \"listitem\");\n card.setAttribute(\"tabindex\", \"0\");\n card.setAttribute(\n \"aria-label\",\n `Feedback #${number}: ${getTypeLabel(feedback.type, this.t)} — ${feedback.message.slice(0, 80)}`,\n );\n card.dataset.feedbackId = feedback.id;\n\n // Color bar\n const bar = el(\"div\", { class: \"sp-card-bar\" });\n bar.style.background = isResolved ? \"#9ca3af\" : typeColor;\n\n // Body\n const body = el(\"div\", { class: \"sp-card-body\" });\n\n // Header: #number + badge + date\n const header = el(\"div\", { class: \"sp-card-header\" });\n\n const num = el(\"span\", { class: \"sp-card-number\" });\n setText(num, `#${number}`);\n\n const badge = el(\"span\", { class: \"sp-badge\" });\n const typeBg = getTypeBgColor(feedback.type, this.colors);\n badge.style.background = typeBg;\n badge.style.color = typeColor;\n setText(badge, getTypeLabel(feedback.type, this.t));\n\n const date = el(\"span\", { class: \"sp-card-date\" });\n setText(date, formatRelativeDate(feedback.createdAt, this.locale));\n\n header.appendChild(num);\n header.appendChild(badge);\n header.appendChild(date);\n\n // Message\n const message = el(\"div\", { class: \"sp-card-message\" });\n setText(message, feedback.message);\n\n // Expand button\n const expandBtn = document.createElement(\"button\");\n expandBtn.className = \"sp-card-expand\";\n expandBtn.dataset.action = \"expand\";\n setText(expandBtn, this.t(\"panel.showMore\"));\n expandBtn.style.display = \"none\";\n expandBtn.setAttribute(\"aria-expanded\", \"false\");\n\n // Check if text is clamped (after render)\n requestAnimationFrame(() => {\n if (message.scrollHeight > message.clientHeight) {\n expandBtn.style.display = \"block\";\n }\n });\n\n // Footer: resolve button\n const footer = el(\"div\", { class: \"sp-card-footer\" });\n\n const resolveBtn = document.createElement(\"button\");\n resolveBtn.className = \"sp-btn-resolve\";\n resolveBtn.dataset.action = \"resolve\";\n if (isResolved) {\n resolveBtn.appendChild(parseSvg(ICON_UNDO));\n const span = document.createElement(\"span\");\n setText(span, ` ${this.t(\"panel.reopen\")}`);\n resolveBtn.appendChild(span);\n } else {\n resolveBtn.appendChild(parseSvg(ICON_CHECK));\n const span = document.createElement(\"span\");\n setText(span, ` ${this.t(\"panel.resolve\")}`);\n resolveBtn.appendChild(span);\n }\n\n const deleteBtn = document.createElement(\"button\");\n deleteBtn.className = \"sp-btn-delete\";\n deleteBtn.dataset.action = \"delete\";\n deleteBtn.appendChild(parseSvg(ICON_TRASH));\n const deleteLabel = document.createElement(\"span\");\n setText(deleteLabel, ` ${this.t(\"panel.delete\")}`);\n deleteBtn.appendChild(deleteLabel);\n\n footer.appendChild(resolveBtn);\n footer.appendChild(deleteBtn);\n\n body.appendChild(header);\n body.appendChild(message);\n body.appendChild(expandBtn);\n body.appendChild(footer);\n\n card.appendChild(bar);\n card.appendChild(body);\n\n return card;\n }\n\n private async deleteFeedback(feedback: FeedbackResponse, btn: HTMLButtonElement): Promise<void> {\n this.pendingMutations.add(feedback.id);\n btn.disabled = true;\n try {\n await this.client.deleteFeedback(feedback.id);\n this.bus.emit(\"feedback:deleted\", feedback.id);\n await this.loadFeedbacks();\n } catch (error) {\n this.pendingMutations.delete(feedback.id);\n btn.disabled = false;\n this.bus.emit(\"feedback:error\", error instanceof Error ? error : new Error(String(error)));\n } finally {\n if (this.pendingMutations.has(feedback.id)) {\n setTimeout(() => this.pendingMutations.delete(feedback.id), 300);\n }\n }\n }\n\n private async confirmDeleteAll(): Promise<void> {\n const confirmed = await this.showConfirmDialog(\n this.t(\"panel.deleteAllConfirmTitle\"),\n this.t(\"panel.deleteAllConfirmMessage\"),\n );\n if (!confirmed) return;\n\n this.deleteAllBtn.disabled = true;\n try {\n await this.client.deleteAllFeedbacks(this.projectName);\n this.bus.emit(\"feedback:all-deleted\");\n await this.loadFeedbacks();\n } catch (error) {\n this.bus.emit(\"feedback:error\", error instanceof Error ? error : new Error(String(error)));\n } finally {\n this.deleteAllBtn.disabled = false;\n }\n }\n\n private showConfirmDialog(title: string, message: string): Promise<boolean> {\n return new Promise((resolve) => {\n const backdrop = el(\"div\", { class: \"sp-confirm-backdrop\" });\n\n const titleId = `sp-confirm-title-${Date.now()}`;\n const messageId = `sp-confirm-msg-${Date.now()}`;\n\n const dialog = el(\"div\", { class: \"sp-confirm-dialog\" });\n dialog.setAttribute(\"role\", \"alertdialog\");\n dialog.setAttribute(\"aria-modal\", \"true\");\n dialog.setAttribute(\"aria-labelledby\", titleId);\n dialog.setAttribute(\"aria-describedby\", messageId);\n\n const titleEl = el(\"div\", { class: \"sp-confirm-title\" });\n titleEl.id = titleId;\n setText(titleEl, title);\n\n const messageEl = el(\"div\", { class: \"sp-confirm-message\" });\n messageEl.id = messageId;\n setText(messageEl, message);\n\n const btnRow = el(\"div\", { class: \"sp-confirm-actions\" });\n\n const cancelBtn = document.createElement(\"button\");\n cancelBtn.type = \"button\";\n cancelBtn.className = \"sp-btn-ghost\";\n setText(cancelBtn, this.t(\"panel.cancel\"));\n\n const confirmBtn = document.createElement(\"button\");\n confirmBtn.type = \"button\";\n confirmBtn.className = \"sp-btn-danger\";\n setText(confirmBtn, this.t(\"panel.confirmDelete\"));\n\n let closed = false;\n const close = (result: boolean) => {\n if (closed) return;\n closed = true;\n backdrop.removeEventListener(\"keydown\", onKeydown);\n backdrop.style.opacity = \"0\";\n dialog.style.transform = \"translateY(8px) scale(0.97)\";\n setTimeout(() => {\n backdrop.remove();\n resolve(result);\n }, 200);\n };\n\n // Focus trap: Tab cycles between cancel and confirm\n const onKeydown = (e: Event) => {\n const ke = e as KeyboardEvent;\n if (ke.key === \"Escape\") {\n close(false);\n return;\n }\n if (ke.key === \"Tab\") {\n ke.preventDefault();\n const active = (backdrop.getRootNode() as ShadowRoot).activeElement;\n if (active === cancelBtn) {\n confirmBtn.focus();\n } else {\n cancelBtn.focus();\n }\n }\n };\n backdrop.addEventListener(\"keydown\", onKeydown);\n\n cancelBtn.addEventListener(\"click\", () => close(false));\n confirmBtn.addEventListener(\"click\", () => close(true));\n backdrop.addEventListener(\"click\", (e) => {\n if (e.target === backdrop) close(false);\n });\n\n btnRow.appendChild(cancelBtn);\n btnRow.appendChild(confirmBtn);\n dialog.appendChild(titleEl);\n dialog.appendChild(messageEl);\n dialog.appendChild(btnRow);\n backdrop.appendChild(dialog);\n\n this.root.getRootNode() instanceof ShadowRoot\n ? (this.root.getRootNode() as ShadowRoot).appendChild(backdrop)\n : this.root.appendChild(backdrop);\n\n requestAnimationFrame(() => {\n backdrop.style.opacity = \"1\";\n dialog.style.transform = \"translateY(0) scale(1)\";\n cancelBtn.focus();\n });\n });\n }\n\n private async toggleResolve(feedback: FeedbackResponse, btn: HTMLButtonElement): Promise<void> {\n this.pendingMutations.add(feedback.id);\n btn.disabled = true;\n try {\n const newResolved = feedback.status !== \"resolved\";\n await this.client.resolveFeedback(feedback.id, newResolved);\n await this.loadFeedbacks();\n } catch (error) {\n this.pendingMutations.delete(feedback.id);\n btn.disabled = false;\n this.bus.emit(\"feedback:error\", error instanceof Error ? error : new Error(String(error)));\n } finally {\n // Brief cooldown prevents spam-click from immediately toggling back\n if (this.pendingMutations.has(feedback.id)) {\n setTimeout(() => this.pendingMutations.delete(feedback.id), 300);\n }\n }\n }\n\n private toggleFilter(value: string, container: HTMLElement): void {\n // Single-select: only one filter active at a time\n this.activeFilters.clear();\n this.activeFilters.add(value);\n\n // Update chip styles\n const chips = container.querySelectorAll<HTMLButtonElement>(\".sp-chip\");\n for (const chip of chips) {\n const isActive = this.activeFilters.has(chip.dataset.filter ?? \"\");\n chip.classList.toggle(\"sp-chip--active\", isActive);\n chip.setAttribute(\"aria-pressed\", String(isActive));\n }\n\n this.loadFeedbacks().catch(() => {});\n }\n\n scrollToFeedback(feedbackId: string): void {\n const escapedId = CSS.escape(feedbackId);\n const card = this.listContainer.querySelector<HTMLElement>(`[data-feedback-id=\"${escapedId}\"]`);\n if (card) {\n card.scrollIntoView({ behavior: \"smooth\", block: \"center\" });\n card.classList.add(\"sp-anim-flash\");\n card.addEventListener(\n \"animationend\",\n () => {\n card.classList.remove(\"sp-anim-flash\");\n },\n { once: true },\n );\n }\n }\n\n /** Refresh the panel after a new feedback is submitted */\n async refresh(): Promise<void> {\n if (this.isOpen) {\n await this.loadFeedbacks();\n }\n }\n\n destroy(): void {\n this.loadController?.abort();\n if (this.searchTimeout) clearTimeout(this.searchTimeout);\n this.listContainer.removeEventListener(\"click\", this.onListClick);\n this.listContainer.removeEventListener(\"keydown\", this.onListKeydown);\n this.listContainer.removeEventListener(\"mouseover\", this.onListMouseover);\n this.listContainer.removeEventListener(\"mouseout\", this.onListMouseout);\n document.removeEventListener(\"sp-marker-click\", this.onMarkerClick);\n this.root.remove();\n }\n}\n","// ---------------------------------------------------------------------------\n// Config\n// ---------------------------------------------------------------------------\n\n/** Configuration options for the Siteping widget. */\nexport interface SitepingConfig {\n /** HTTP endpoint that receives feedbacks (e.g. '/api/siteping'). Required unless `store` is provided. */\n endpoint?: string | undefined;\n /** Required — project identifier used to scope feedbacks */\n projectName: string;\n /** Direct store for client-side mode. When set, bypasses HTTP and uses the store directly in the browser. */\n store?: SitepingStore | undefined;\n /** FAB position — defaults to 'bottom-right' */\n position?: \"bottom-right\" | \"bottom-left\";\n /** Accent color for the widget UI — defaults to '#0066ff' */\n accentColor?: string;\n /** Show the widget even in production — defaults to false */\n forceShow?: boolean;\n /** Enable debug logging of lifecycle events — defaults to false */\n debug?: boolean;\n /** Color theme — defaults to 'light' */\n theme?: \"light\" | \"dark\" | \"auto\";\n /** UI locale — defaults to 'en' */\n locale?: \"fr\" | \"en\" | (string & {}) | undefined;\n /** Called when the widget is skipped (production mode, mobile viewport) */\n onSkip?: (reason: \"production\" | \"mobile\") => void;\n\n // Events\n /** Called when the feedback panel is opened. */\n onOpen?: () => void;\n /** Called when the feedback panel is closed. */\n onClose?: () => void;\n onFeedbackSent?: (feedback: FeedbackResponse) => void;\n onError?: (error: Error) => void;\n /** Called when the user starts drawing an annotation. */\n onAnnotationStart?: () => void;\n /** Called when the user finishes drawing an annotation. */\n onAnnotationEnd?: () => void;\n}\n\n/** Instance returned by initSiteping() with lifecycle methods. */\nexport interface SitepingInstance {\n /** Remove the widget from the DOM and clean up all listeners. */\n destroy: () => void;\n /** Open the panel programmatically */\n open: () => void;\n /** Close the panel */\n close: () => void;\n /** Reload feedbacks from server */\n refresh: () => void;\n /** Subscribe to a public widget event */\n on: <K extends keyof SitepingPublicEvents>(\n event: K,\n listener: (...args: SitepingPublicEvents[K]) => void,\n ) => () => void;\n /** Unsubscribe from a public widget event */\n off: <K extends keyof SitepingPublicEvents>(event: K, listener: (...args: SitepingPublicEvents[K]) => void) => void;\n}\n\n/** Events exposed to consumers via SitepingInstance.on / .off */\nexport interface SitepingPublicEvents {\n \"feedback:sent\": [FeedbackResponse];\n \"feedback:deleted\": [string];\n \"panel:open\": [];\n \"panel:close\": [];\n}\n\n// ---------------------------------------------------------------------------\n// Feedback\n// ---------------------------------------------------------------------------\n\n/** Single source of truth for feedback types — used by both TS types and Zod schemas. */\nexport const FEEDBACK_TYPES = [\"question\", \"change\", \"bug\", \"other\"] as const;\nexport type FeedbackType = (typeof FEEDBACK_TYPES)[number];\n\n/** Single source of truth for feedback statuses. */\nexport const FEEDBACK_STATUSES = [\"open\", \"resolved\"] as const;\nexport type FeedbackStatus = (typeof FEEDBACK_STATUSES)[number];\n\n// ---------------------------------------------------------------------------\n// Abstract Store — adapter pattern\n// ---------------------------------------------------------------------------\n\n/** Input for creating a feedback record in the store. */\nexport interface FeedbackCreateInput {\n projectName: string;\n type: FeedbackType;\n message: string;\n status: FeedbackStatus;\n url: string;\n viewport: string;\n userAgent: string;\n authorName: string;\n authorEmail: string;\n clientId: string;\n annotations: AnnotationCreateInput[];\n}\n\n/** Input for a single annotation when creating a feedback. */\nexport interface AnnotationCreateInput {\n cssSelector: string;\n xpath: string;\n textSnippet: string;\n elementTag: string;\n elementId?: string | undefined;\n textPrefix: string;\n textSuffix: string;\n fingerprint: string;\n neighborText: string;\n xPct: number;\n yPct: number;\n wPct: number;\n hPct: number;\n scrollX: number;\n scrollY: number;\n viewportW: number;\n viewportH: number;\n devicePixelRatio: number;\n}\n\n/** Query parameters for fetching feedbacks. */\nexport interface FeedbackQuery {\n projectName: string;\n type?: FeedbackType | undefined;\n status?: FeedbackStatus | undefined;\n search?: string | undefined;\n page?: number | undefined;\n limit?: number | undefined;\n}\n\n/** Update payload for patching a feedback. */\nexport interface FeedbackUpdateInput {\n status: FeedbackStatus;\n resolvedAt: Date | null;\n}\n\n/** A persisted feedback record returned by the store. */\nexport interface FeedbackRecord {\n id: string;\n type: FeedbackType;\n message: string;\n status: FeedbackStatus;\n projectName: string;\n url: string;\n authorName: string;\n authorEmail: string;\n viewport: string;\n userAgent: string;\n clientId: string;\n resolvedAt: Date | null;\n createdAt: Date;\n updatedAt: Date;\n annotations: AnnotationRecord[];\n}\n\n/** A persisted annotation record returned by the store. */\nexport interface AnnotationRecord {\n id: string;\n feedbackId: string;\n cssSelector: string;\n xpath: string;\n textSnippet: string;\n elementTag: string;\n elementId: string | null;\n textPrefix: string;\n textSuffix: string;\n fingerprint: string;\n neighborText: string;\n xPct: number;\n yPct: number;\n wPct: number;\n hPct: number;\n scrollX: number;\n scrollY: number;\n viewportW: number;\n viewportH: number;\n devicePixelRatio: number;\n createdAt: Date;\n}\n\n// ---------------------------------------------------------------------------\n// Store errors — throw these from adapter implementations\n// ---------------------------------------------------------------------------\n\n/**\n * Thrown when a record is not found during update or delete.\n *\n * Handlers translate this to HTTP 404. Adapters MUST throw this (not\n * ORM-specific errors) so the handler layer remains ORM-agnostic.\n */\nexport class StoreNotFoundError extends Error {\n readonly code = \"STORE_NOT_FOUND\" as const;\n constructor(message = \"Record not found\") {\n super(message);\n this.name = \"StoreNotFoundError\";\n }\n}\n\n/**\n * Thrown when a unique constraint is violated (e.g. duplicate `clientId`).\n *\n * Handlers use this to return the existing record instead of failing.\n */\nexport class StoreDuplicateError extends Error {\n readonly code = \"STORE_DUPLICATE\" as const;\n constructor(message = \"Duplicate record\") {\n super(message);\n this.name = \"StoreDuplicateError\";\n }\n}\n\n/** Type guard — works for `StoreNotFoundError` and ORM-specific equivalents (e.g. Prisma P2025). */\nexport function isStoreNotFound(error: unknown): boolean {\n if (error instanceof StoreNotFoundError) return true;\n // Backwards compat: Prisma's P2025\n return typeof error === \"object\" && error !== null && \"code\" in error && (error as { code: string }).code === \"P2025\";\n}\n\n/** Type guard — works for `StoreDuplicateError` and ORM-specific equivalents (e.g. Prisma P2002). */\nexport function isStoreDuplicate(error: unknown): boolean {\n if (error instanceof StoreDuplicateError) return true;\n // Backwards compat: Prisma's P2002\n return typeof error === \"object\" && error !== null && \"code\" in error && (error as { code: string }).code === \"P2002\";\n}\n\n// ---------------------------------------------------------------------------\n// Store helpers — shared conversion logic for adapters\n// ---------------------------------------------------------------------------\n\n/** Flatten a widget `AnnotationPayload` (nested anchor + rect) into a flat `AnnotationCreateInput`. */\nexport function flattenAnnotation(ann: AnnotationPayload): AnnotationCreateInput {\n return {\n cssSelector: ann.anchor.cssSelector,\n xpath: ann.anchor.xpath,\n textSnippet: ann.anchor.textSnippet,\n elementTag: ann.anchor.elementTag,\n elementId: ann.anchor.elementId,\n textPrefix: ann.anchor.textPrefix,\n textSuffix: ann.anchor.textSuffix,\n fingerprint: ann.anchor.fingerprint,\n neighborText: ann.anchor.neighborText,\n xPct: ann.rect.xPct,\n yPct: ann.rect.yPct,\n wPct: ann.rect.wPct,\n hPct: ann.rect.hPct,\n scrollX: ann.scrollX,\n scrollY: ann.scrollY,\n viewportW: ann.viewportW,\n viewportH: ann.viewportH,\n devicePixelRatio: ann.devicePixelRatio,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Abstract Store — adapter pattern\n// ---------------------------------------------------------------------------\n\n/**\n * Abstract storage interface for Siteping.\n *\n * Any adapter (Prisma, Drizzle, raw SQL, localStorage, etc.) implements this\n * interface. The HTTP handler and widget `StoreClient` operate against\n * `SitepingStore`, decoupled from the storage backend.\n *\n * ## Error contract\n *\n * - **`updateFeedback` / `deleteFeedback`**: throw `StoreNotFoundError` when\n * the record does not exist.\n * - **`createFeedback`**: either return the existing record on duplicate\n * `clientId` (idempotent) or throw `StoreDuplicateError`. The handler\n * handles both patterns.\n * - Other methods should not throw on empty results — return empty arrays or `null`.\n */\nexport interface SitepingStore {\n /** Create a feedback with its annotations. Idempotent on `clientId` — return existing record on duplicate, or throw `StoreDuplicateError`. */\n createFeedback(data: FeedbackCreateInput): Promise<FeedbackRecord>;\n /** Paginated query with optional filters. Returns empty array (not error) when no results. */\n getFeedbacks(query: FeedbackQuery): Promise<{ feedbacks: FeedbackRecord[]; total: number }>;\n /** Lookup by client-generated UUID. Returns `null` (not error) when not found. */\n findByClientId(clientId: string): Promise<FeedbackRecord | null>;\n /** Update status/resolvedAt. Throws `StoreNotFoundError` if `id` does not exist. */\n updateFeedback(id: string, data: FeedbackUpdateInput): Promise<FeedbackRecord>;\n /** Delete a single record. Throws `StoreNotFoundError` if `id` does not exist. */\n deleteFeedback(id: string): Promise<void>;\n /** Bulk delete all feedbacks for a project. No-op (not error) if none exist. */\n deleteAllFeedbacks(projectName: string): Promise<void>;\n}\n\n/** Payload sent from the widget to the server when submitting feedback. */\nexport interface FeedbackPayload {\n projectName: string;\n type: FeedbackType;\n message: string;\n url: string;\n viewport: string;\n userAgent: string;\n authorName: string;\n authorEmail: string;\n annotations: AnnotationPayload[];\n /** Client-generated UUID for deduplication */\n clientId: string;\n}\n\n// ---------------------------------------------------------------------------\n// Annotation — multi-selector anchoring (Hypothesis / W3C Web Annotation)\n// ---------------------------------------------------------------------------\n\n/** DOM anchoring data for re-attaching annotations to page elements. */\nexport interface AnchorData {\n /** CSS selector generated by @medv/finder — primary anchor */\n cssSelector: string;\n /** XPath — fallback 1 */\n xpath: string;\n /** First ~120 chars of element innerText — empty string if none */\n textSnippet: string;\n /** Tag name for validation (e.g. \"DIV\", \"SECTION\") */\n elementTag: string;\n /** Element id attribute if available — most stable */\n elementId?: string | undefined;\n /** ~32 chars of text before this element in document flow (disambiguation) */\n textPrefix: string;\n /** ~32 chars of text after this element in document flow (disambiguation) */\n textSuffix: string;\n /** Structural fingerprint: \"childCount:siblingIdx:attrHash\" */\n fingerprint: string;\n /** Text content of adjacent sibling elements (context) */\n neighborText: string;\n}\n\n/** Drawn rectangle coordinates as percentages relative to the anchor element. */\nexport interface RectData {\n /** X offset as fraction of anchor element width — must be in range [0, 1] */\n xPct: number;\n /** Y offset as fraction of anchor element height — must be in range [0, 1] */\n yPct: number;\n /** Width as fraction of anchor element width — must be in range [0, 1] */\n wPct: number;\n /** Height as fraction of anchor element height — must be in range [0, 1] */\n hPct: number;\n}\n\n/** Annotation data sent as part of a feedback submission. */\nexport interface AnnotationPayload {\n anchor: AnchorData;\n rect: RectData;\n scrollX: number;\n scrollY: number;\n viewportW: number;\n viewportH: number;\n devicePixelRatio: number;\n}\n\n// ---------------------------------------------------------------------------\n// API responses\n// ---------------------------------------------------------------------------\n\n/** Feedback record as returned by the API (dates serialized as strings). */\nexport interface FeedbackResponse {\n id: string;\n projectName: string;\n type: FeedbackType;\n message: string;\n status: FeedbackStatus;\n url: string;\n viewport: string;\n userAgent: string;\n authorName: string;\n authorEmail: string;\n resolvedAt: string | null;\n createdAt: string;\n updatedAt: string;\n annotations: AnnotationResponse[];\n}\n\n/** Annotation record as returned by the API. */\nexport interface AnnotationResponse {\n id: string;\n feedbackId: string;\n cssSelector: string;\n xpath: string;\n textSnippet: string;\n elementTag: string;\n elementId: string | null;\n textPrefix: string;\n textSuffix: string;\n fingerprint: string;\n neighborText: string;\n xPct: number;\n yPct: number;\n wPct: number;\n hPct: number;\n scrollX: number;\n scrollY: number;\n viewportW: number;\n viewportH: number;\n devicePixelRatio: number;\n createdAt: string;\n}\n","import {\n type AnnotationRecord,\n type AnnotationResponse,\n type FeedbackPayload,\n type FeedbackRecord,\n type FeedbackResponse,\n type FeedbackStatus,\n type FeedbackType,\n flattenAnnotation,\n type SitepingStore,\n} from \"@siteping/core\";\nimport type { WidgetClient } from \"./api-client.js\";\n\n/**\n * `WidgetClient` implementation that delegates directly to a `SitepingStore`.\n *\n * Used in client-side mode — the widget calls the store in-process instead of\n * making HTTP requests. Handles the same conversions the HTTP handler normally\n * performs: flattening annotations and serializing dates.\n */\nexport class StoreClient implements WidgetClient {\n constructor(\n private readonly store: SitepingStore,\n private readonly projectName: string,\n ) {}\n\n async sendFeedback(payload: FeedbackPayload): Promise<FeedbackResponse> {\n const record = await this.store.createFeedback({\n projectName: payload.projectName,\n type: payload.type,\n message: payload.message,\n status: \"open\",\n url: payload.url,\n viewport: payload.viewport,\n userAgent: payload.userAgent,\n authorName: payload.authorName,\n authorEmail: payload.authorEmail,\n clientId: payload.clientId,\n annotations: payload.annotations.map(flattenAnnotation),\n });\n\n return toResponse(record);\n }\n\n async getFeedbacks(\n projectName: string,\n options?: { page?: number; limit?: number; type?: FeedbackType; status?: FeedbackStatus; search?: string },\n ): Promise<{ feedbacks: FeedbackResponse[]; total: number }> {\n const { feedbacks, total } = await this.store.getFeedbacks({\n projectName,\n page: options?.page,\n limit: options?.limit,\n type: options?.type,\n status: options?.status,\n search: options?.search,\n });\n\n return { feedbacks: feedbacks.map(toResponse), total };\n }\n\n async resolveFeedback(id: string, resolved: boolean): Promise<FeedbackResponse> {\n const record = await this.store.updateFeedback(id, {\n status: resolved ? \"resolved\" : \"open\",\n resolvedAt: resolved ? new Date() : null,\n });\n return toResponse(record);\n }\n\n async deleteFeedback(id: string): Promise<void> {\n await this.store.deleteFeedback(id);\n }\n\n async deleteAllFeedbacks(projectName: string): Promise<void> {\n await this.store.deleteAllFeedbacks(projectName);\n }\n}\n\n// ---------------------------------------------------------------------------\n// FeedbackRecord (Date) → FeedbackResponse (string) serialization\n// ---------------------------------------------------------------------------\n\nfunction toResponse(record: FeedbackRecord): FeedbackResponse {\n return {\n id: record.id,\n projectName: record.projectName,\n type: record.type,\n message: record.message,\n status: record.status,\n url: record.url,\n viewport: record.viewport,\n userAgent: record.userAgent,\n authorName: record.authorName,\n authorEmail: record.authorEmail,\n resolvedAt: record.resolvedAt?.toISOString() ?? null,\n createdAt: record.createdAt.toISOString(),\n updatedAt: record.updatedAt.toISOString(),\n annotations: record.annotations.map(toAnnotationResponse),\n };\n}\n\nfunction toAnnotationResponse(ann: AnnotationRecord): AnnotationResponse {\n return {\n id: ann.id,\n feedbackId: ann.feedbackId,\n cssSelector: ann.cssSelector,\n xpath: ann.xpath,\n textSnippet: ann.textSnippet,\n elementTag: ann.elementTag,\n elementId: ann.elementId,\n textPrefix: ann.textPrefix,\n textSuffix: ann.textSuffix,\n fingerprint: ann.fingerprint,\n neighborText: ann.neighborText,\n xPct: ann.xPct,\n yPct: ann.yPct,\n wPct: ann.wPct,\n hPct: ann.hPct,\n scrollX: ann.scrollX,\n scrollY: ann.scrollY,\n viewportW: ann.viewportW,\n viewportH: ann.viewportH,\n devicePixelRatio: ann.devicePixelRatio,\n createdAt: ann.createdAt.toISOString(),\n };\n}\n","/**\n * CSS keyframes and animation utilities — Glassmorphism edition.\n *\n * Uses CSS-only spring animations via linear() timing function\n * and refined easing curves for premium motion design.\n */\n\n// Spring easing — computed from a spring simulation (damping: 15, stiffness: 100)\nconst SPRING_LINEAR = `linear(0, 0.006, 0.025, 0.06, 0.11, 0.17, 0.25, 0.34, 0.45, 0.56, 0.67, 0.78, 0.88, 0.95, 1.01, 1.04, 1.05, 1.04, 1.02, 1, 0.99, 1)`;\n\n// Ease-out-expo — fast start, smooth deceleration\nconst EASE_OUT_EXPO = `cubic-bezier(0.16, 1, 0.3, 1)`;\n\n// Spring overshoot — bouncy entrance\nconst SPRING_OVERSHOOT = `cubic-bezier(0.34, 1.56, 0.64, 1)`;\n\n// Smooth decel — for glass transitions\nconst EASE_OUT_QUART = `cubic-bezier(0.25, 1, 0.5, 1)`;\n\nexport const ANIMATION_CSS = `\n /* ---- Keyframes ---- */\n\n @keyframes sp-fab-in {\n from {\n transform: scale(0) rotate(-180deg);\n opacity: 0;\n }\n to {\n transform: scale(1) rotate(0deg);\n opacity: 1;\n }\n }\n\n @keyframes sp-fab-glow {\n 0%, 100% { box-shadow: 0 4px 20px var(--sp-accent-glow), 0 2px 8px rgba(0, 0, 0, 0.08); }\n 50% { box-shadow: 0 4px 28px var(--sp-accent-glow), 0 2px 12px rgba(0, 0, 0, 0.1); }\n }\n\n @keyframes sp-marker-in {\n 0% {\n transform: scale(0);\n opacity: 0;\n }\n 60% {\n transform: scale(1.2);\n opacity: 1;\n }\n 100% {\n transform: scale(1);\n }\n }\n\n @keyframes sp-pulse-ring {\n 0% {\n box-shadow: 0 0 0 0 var(--sp-accent-glow);\n }\n 70% {\n box-shadow: 0 0 0 8px transparent;\n }\n 100% {\n box-shadow: 0 0 0 0 transparent;\n }\n }\n\n @keyframes sp-flash-bg {\n 0% { background-color: var(--sp-accent-light); }\n 100% { background-color: transparent; }\n }\n\n @keyframes sp-slide-up {\n from {\n transform: translateY(8px);\n opacity: 0;\n }\n to {\n transform: translateY(0);\n opacity: 1;\n }\n }\n\n @keyframes sp-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n }\n\n @keyframes sp-shimmer {\n 0% { background-position: -200% 0; }\n 100% { background-position: 200% 0; }\n }\n\n /* ---- Animation classes ---- */\n\n .sp-anim-fab-in {\n animation: sp-fab-in 0.5s ${SPRING_LINEAR} both;\n }\n\n .sp-anim-marker-in {\n animation: sp-marker-in 0.35s ${SPRING_OVERSHOOT} both;\n }\n\n .sp-anim-pulse {\n animation: sp-pulse-ring 0.7s ease-out;\n }\n\n .sp-anim-flash {\n animation: sp-flash-bg 0.5s ${EASE_OUT_QUART};\n }\n\n .sp-anim-slide-up {\n animation: sp-slide-up 0.3s ${EASE_OUT_EXPO} both;\n }\n\n .sp-anim-fade-in {\n animation: sp-fade-in 0.2s ease-out both;\n }\n\n /* ---- Transition utilities ---- */\n\n .sp-panel {\n transform: translateX(110%);\n transition: transform 0.4s ${EASE_OUT_EXPO};\n }\n\n .sp-panel.sp-panel--open {\n transform: translateX(0);\n }\n\n .sp-radial-item {\n opacity: 0;\n pointer-events: none;\n transform: translate(0, 0) scale(0.8);\n transition:\n transform 0.35s ${SPRING_OVERSHOOT},\n opacity 0.2s ease,\n background 0.2s ease,\n border-color 0.2s ease,\n box-shadow 0.2s ease;\n }\n\n .sp-radial-item.sp-radial-item--open {\n opacity: 1;\n pointer-events: auto;\n }\n\n /* Stagger delay via CSS custom property --sp-i */\n .sp-radial-item {\n transition-delay: calc(var(--sp-i, 0) * 50ms);\n }\n\n /* ---- Card stagger animation ---- */\n\n @keyframes sp-card-in {\n from {\n transform: translateY(12px);\n opacity: 0;\n }\n to {\n transform: translateY(0);\n opacity: 1;\n }\n }\n\n .sp-card {\n animation: sp-card-in 0.35s ${EASE_OUT_EXPO} both;\n animation-delay: calc(var(--sp-card-i, 0) * 40ms);\n }\n\n /* ---- Loading spinner ---- */\n\n @keyframes sp-spin {\n to { transform: rotate(360deg); }\n }\n\n .sp-spinner {\n width: 20px;\n height: 20px;\n border: 2px solid var(--sp-border);\n border-top-color: var(--sp-accent);\n border-radius: 50%;\n animation: sp-spin 0.6s linear infinite;\n }\n\n /* ---- Badge bounce ---- */\n\n @keyframes sp-badge-in {\n 0% { transform: scale(0); }\n 60% { transform: scale(1.3); }\n 100% { transform: scale(1); }\n }\n\n .sp-fab-badge {\n animation: sp-badge-in 0.4s ${SPRING_OVERSHOOT} both;\n }\n\n /* ---- Reduced motion ---- */\n\n @media (prefers-reduced-motion: reduce) {\n *, *::before, *::after {\n animation-duration: 0.01ms !important;\n animation-iteration-count: 1 !important;\n transition-duration: 0.01ms !important;\n }\n }\n\n`;\n","import { ANIMATION_CSS } from \"./animations.js\";\nimport { cssVariables, type ThemeColors } from \"./theme.js\";\n\n/**\n * Build the complete CSS stylesheet for the Shadow DOM.\n *\n * Design: Glassmorphism — frosted glass surfaces, soft depth,\n * accent gradients, premium micro-interactions.\n *\n * Principles:\n * - :host uses `all: initial` to block inherited styles\n * - All classes prefixed with sp- (defense in depth)\n * - CSS custom properties for theming\n * - No external fonts — system-ui stack (Inter if available)\n * - :focus-visible on all interactive elements\n * - prefers-reduced-motion support\n */\nexport function buildStyles(colors: ThemeColors): string {\n return `\n :host {\n all: initial;\n position: fixed;\n z-index: 2147483647;\n font-family: var(--sp-font);\n font-size: 14px;\n line-height: 1.5;\n color: var(--sp-text);\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n ${cssVariables(colors)}\n\n /* Identity modal — theme-aware backdrop + panel */\n --sp-identity-bg: ${colors.glassBgHeavy};\n --sp-identity-overlay: ${colors.bg === \"#ffffff\" ? \"rgba(15, 23, 42, 0.2)\" : \"rgba(0, 0, 0, 0.4)\"};\n }\n\n *, *::before, *::after {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n }\n\n /* ============================\n Focus visible (accessibility)\n ============================ */\n\n :focus-visible {\n outline: 2px solid var(--sp-accent);\n outline-offset: 2px;\n }\n\n /* ============================\n FAB (Floating Action Button)\n ============================ */\n\n .sp-fab {\n position: fixed;\n width: 52px;\n height: 52px;\n border-radius: var(--sp-radius-full);\n background: var(--sp-accent-gradient);\n color: #fff;\n border: none;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow:\n 0 4px 20px var(--sp-accent-glow),\n 0 2px 8px rgba(0, 0, 0, 0.08);\n transition:\n transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1),\n box-shadow 0.3s ease;\n outline: none;\n }\n\n .sp-fab:focus-visible {\n outline: 2px solid #fff;\n outline-offset: 3px;\n }\n\n .sp-fab:hover {\n transform: translateY(-2px) scale(1.05);\n box-shadow:\n 0 8px 28px var(--sp-accent-glow),\n 0 4px 12px rgba(0, 0, 0, 0.1);\n }\n\n .sp-fab:active {\n transform: translateY(0) scale(0.95);\n transition-duration: 0.1s;\n }\n\n .sp-fab--bottom-right {\n bottom: 24px;\n right: 24px;\n }\n\n .sp-fab--bottom-left {\n bottom: 24px;\n left: 24px;\n }\n\n .sp-fab svg {\n width: 22px;\n height: 22px;\n fill: currentColor;\n transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);\n }\n\n /* ---- FAB Badge ---- */\n\n .sp-fab-badge {\n position: absolute;\n top: -4px;\n right: -4px;\n min-width: 20px;\n height: 20px;\n padding: 0 6px;\n border-radius: var(--sp-radius-full);\n background: #ef4444;\n color: #fff;\n font-size: 11px;\n font-weight: 700;\n display: flex;\n align-items: center;\n justify-content: center;\n border: 2px solid #fff;\n pointer-events: none;\n font-family: var(--sp-font);\n line-height: 1;\n }\n\n /* ============================\n Radial Menu\n ============================ */\n\n .sp-radial {\n position: fixed;\n pointer-events: none;\n width: 52px;\n height: 52px;\n }\n\n .sp-radial--bottom-right {\n bottom: 24px;\n right: 24px;\n }\n\n .sp-radial--bottom-left {\n bottom: 24px;\n left: 24px;\n }\n\n .sp-radial-item {\n position: absolute;\n left: 4px;\n bottom: 4px;\n width: 44px;\n height: 44px;\n border-radius: var(--sp-radius-full);\n background: var(--sp-glass-bg-heavy);\n backdrop-filter: blur(var(--sp-blur));\n -webkit-backdrop-filter: blur(var(--sp-blur));\n color: var(--sp-text);\n border: 1px solid var(--sp-glass-border);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: var(--sp-shadow-md);\n font-size: 12px;\n font-weight: 600;\n }\n\n .sp-radial-item:hover,\n .sp-radial-item:focus-visible {\n background: rgba(255, 255, 255, 0.95);\n border-color: var(--sp-accent);\n color: var(--sp-accent);\n box-shadow:\n var(--sp-shadow-md),\n 0 0 0 3px var(--sp-accent-light);\n outline: none;\n }\n\n .sp-radial-item svg {\n width: 18px;\n height: 18px;\n flex-shrink: 0;\n stroke: currentColor;\n fill: none;\n }\n\n .sp-radial-label {\n white-space: nowrap;\n font-size: 12px;\n font-weight: 500;\n color: var(--sp-text);\n pointer-events: none;\n opacity: 0;\n padding: 4px 12px;\n border-radius: var(--sp-radius);\n background: var(--sp-glass-bg-heavy);\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n border: 1px solid var(--sp-glass-border);\n box-shadow: var(--sp-shadow-sm);\n transform: translateX(4px);\n transition: opacity 0.2s ease, transform 0.2s ease;\n }\n\n .sp-radial-item:hover .sp-radial-label,\n .sp-radial-item:focus-visible .sp-radial-label {\n opacity: 1;\n transform: translateX(0);\n }\n\n /* ============================\n Panel (Side drawer)\n ============================ */\n\n .sp-panel {\n position: fixed;\n top: 0;\n right: 0;\n width: 400px;\n max-width: 100vw;\n height: 100vh;\n height: 100dvh;\n background: var(--sp-glass-bg);\n backdrop-filter: blur(var(--sp-blur-heavy));\n -webkit-backdrop-filter: blur(var(--sp-blur-heavy));\n border-left: 1px solid var(--sp-glass-border);\n box-shadow: var(--sp-shadow-xl);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n @media (max-width: 480px) {\n .sp-panel {\n width: 100vw;\n border-left: none;\n }\n }\n\n .sp-panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 20px 24px;\n border-bottom: 1px solid var(--sp-border);\n background: var(--sp-glass-bg-heavy);\n backdrop-filter: blur(var(--sp-blur));\n -webkit-backdrop-filter: blur(var(--sp-blur));\n }\n\n .sp-panel-title {\n font-size: 17px;\n font-weight: 700;\n color: var(--sp-text);\n letter-spacing: -0.02em;\n }\n\n .sp-panel-close {\n width: 44px;\n height: 44px;\n border-radius: var(--sp-radius);\n border: none;\n background: transparent;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--sp-text-tertiary);\n transition: all 0.2s ease;\n }\n\n .sp-panel-close:hover {\n background: var(--sp-bg-hover);\n color: var(--sp-text);\n }\n\n .sp-panel-close svg {\n width: 16px;\n height: 16px;\n }\n\n /* ============================\n Filters & Search\n ============================ */\n\n .sp-filters {\n padding: 16px 24px;\n border-bottom: 1px solid var(--sp-border);\n background: var(--sp-glass-bg-heavy);\n backdrop-filter: blur(var(--sp-blur));\n -webkit-backdrop-filter: blur(var(--sp-blur));\n position: sticky;\n top: 0;\n z-index: 1;\n }\n\n .sp-search-wrap {\n position: relative;\n margin-bottom: 12px;\n }\n\n .sp-search {\n width: 100%;\n height: 40px;\n padding: 0 12px 0 38px;\n border-radius: var(--sp-radius);\n border: 1px solid var(--sp-border);\n background: var(--sp-glass-bg-heavy);\n color: var(--sp-text);\n font-family: var(--sp-font);\n font-size: 13px;\n outline: none;\n transition: all 0.2s ease;\n }\n\n .sp-search::placeholder {\n color: var(--sp-text-tertiary);\n }\n\n .sp-search:focus {\n border-color: var(--sp-accent);\n box-shadow: 0 0 0 3px var(--sp-accent-light);\n background: #fff;\n }\n\n .sp-search-icon {\n position: absolute;\n left: 12px;\n top: 50%;\n transform: translateY(-50%);\n color: var(--sp-text-tertiary);\n width: 16px;\n height: 16px;\n transition: color 0.2s ease;\n }\n\n .sp-search:focus ~ .sp-search-icon,\n .sp-search-wrap:focus-within .sp-search-icon {\n color: var(--sp-accent);\n }\n\n .sp-chips {\n display: flex;\n gap: 6px;\n flex-wrap: wrap;\n }\n\n .sp-chip {\n padding: 5px 14px;\n border-radius: var(--sp-radius-full);\n border: 1px solid var(--sp-border);\n background: var(--sp-glass-bg-heavy);\n color: var(--sp-text-secondary);\n font-family: var(--sp-font);\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.2s ease;\n white-space: nowrap;\n letter-spacing: 0.01em;\n }\n\n .sp-chip:hover {\n border-color: var(--sp-accent);\n color: var(--sp-accent);\n background: var(--sp-accent-light);\n }\n\n .sp-chip--active {\n background: var(--sp-accent-gradient);\n border-color: transparent;\n color: #fff;\n box-shadow: 0 2px 8px var(--sp-accent-glow);\n }\n\n .sp-chip--active:hover {\n background: var(--sp-accent-gradient);\n border-color: transparent;\n color: #fff;\n }\n\n /* ============================\n Feedback Cards\n ============================ */\n\n .sp-list {\n flex: 1;\n overflow-y: auto;\n padding: 8px 12px;\n }\n\n .sp-list::-webkit-scrollbar {\n width: 6px;\n }\n\n .sp-list::-webkit-scrollbar-track {\n background: transparent;\n }\n\n .sp-list::-webkit-scrollbar-thumb {\n background: var(--sp-border);\n border-radius: var(--sp-radius-full);\n }\n\n .sp-list::-webkit-scrollbar-thumb:hover {\n background: var(--sp-text-tertiary);\n }\n\n .sp-card {\n display: flex;\n padding: 14px 16px;\n margin-bottom: 6px;\n cursor: pointer;\n border-radius: var(--sp-radius);\n background: var(--sp-glass-bg-heavy);\n border: 1px solid var(--sp-glass-border);\n transition: all 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);\n }\n\n .sp-card:hover {\n background: #fff;\n border-color: var(--sp-border);\n box-shadow: var(--sp-shadow-md);\n transform: translateY(-2px);\n }\n\n .sp-card:active {\n transform: translateY(0) scale(0.99);\n transition-duration: 0.1s;\n }\n\n .sp-card-bar {\n width: 3px;\n border-radius: var(--sp-radius-full);\n margin-right: 14px;\n flex-shrink: 0;\n }\n\n .sp-card-body {\n flex: 1;\n min-width: 0;\n }\n\n .sp-card-header {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 6px;\n }\n\n .sp-card-number {\n font-size: 12px;\n font-weight: 700;\n color: var(--sp-text-tertiary);\n font-variant-numeric: tabular-nums;\n }\n\n .sp-badge {\n padding: 2px 10px;\n border-radius: var(--sp-radius-full);\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.02em;\n }\n\n .sp-card-date {\n font-size: 11px;\n color: var(--sp-text-tertiary);\n margin-left: auto;\n }\n\n .sp-card-message {\n font-size: 13px;\n line-height: 1.5;\n color: var(--sp-text);\n display: -webkit-box;\n -webkit-line-clamp: 3;\n -webkit-box-orient: vertical;\n overflow: hidden;\n }\n\n .sp-card-message--expanded {\n -webkit-line-clamp: unset;\n }\n\n .sp-card-expand {\n font-size: 12px;\n font-weight: 500;\n color: var(--sp-accent);\n cursor: pointer;\n background: none;\n border: none;\n padding: 4px 0;\n font-family: var(--sp-font);\n transition: opacity 0.15s ease;\n }\n\n .sp-card-expand:hover {\n opacity: 0.8;\n }\n\n .sp-card-footer {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 6px;\n margin-top: 10px;\n }\n\n .sp-btn-resolve,\n .sp-btn-delete {\n padding: 8px 14px;\n border-radius: var(--sp-radius-full);\n border: 1px solid var(--sp-border);\n background: transparent;\n color: var(--sp-text-secondary);\n font-family: var(--sp-font);\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n display: flex;\n align-items: center;\n gap: 4px;\n transition: all 0.2s ease;\n }\n\n .sp-btn-resolve svg,\n .sp-btn-delete svg {\n width: 14px;\n height: 14px;\n }\n\n .sp-btn-resolve:hover {\n border-color: #22c55e;\n color: #22c55e;\n background: rgba(34, 197, 94, 0.06);\n }\n\n .sp-btn-delete:hover {\n border-color: #ef4444;\n color: #ef4444;\n background: rgba(239, 68, 68, 0.06);\n }\n\n .sp-btn-resolve:disabled,\n .sp-btn-delete:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n pointer-events: none;\n }\n\n /* ---- Delete All (header) ---- */\n\n .sp-panel-header-right {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .sp-btn-delete-all {\n padding: 5px 12px;\n border-radius: var(--sp-radius-full);\n border: 1px solid var(--sp-border);\n background: transparent;\n color: var(--sp-text-tertiary);\n font-family: var(--sp-font);\n font-size: 11px;\n font-weight: 500;\n cursor: pointer;\n display: flex;\n align-items: center;\n gap: 4px;\n transition: all 0.2s ease;\n }\n\n .sp-btn-delete-all svg {\n width: 13px;\n height: 13px;\n }\n\n .sp-btn-delete-all:hover {\n border-color: #ef4444;\n color: #ef4444;\n background: rgba(239, 68, 68, 0.06);\n }\n\n .sp-btn-delete-all:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n pointer-events: none;\n }\n\n /* ---- Confirm Dialog ---- */\n\n .sp-confirm-backdrop {\n position: fixed;\n inset: 0;\n background: var(--sp-backdrop, rgba(15, 23, 42, 0.2));\n backdrop-filter: blur(var(--sp-blur));\n -webkit-backdrop-filter: blur(var(--sp-blur));\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 2147483647;\n opacity: 0;\n transition: opacity 0.2s ease;\n }\n\n .sp-confirm-dialog {\n width: 340px;\n padding: 28px;\n border-radius: 20px;\n background: var(--sp-glass-bg-heavy);\n backdrop-filter: blur(var(--sp-blur-heavy));\n -webkit-backdrop-filter: blur(var(--sp-blur-heavy));\n border: 1px solid var(--sp-glass-border);\n box-shadow: var(--sp-shadow-xl);\n font-family: var(--sp-font);\n transform: translateY(8px) scale(0.97);\n transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1);\n }\n\n .sp-confirm-title {\n font-size: 17px;\n font-weight: 700;\n color: var(--sp-text);\n letter-spacing: -0.02em;\n margin-bottom: 8px;\n }\n\n .sp-confirm-message {\n font-size: 14px;\n color: var(--sp-text-secondary);\n line-height: 1.5;\n margin-bottom: 20px;\n }\n\n .sp-confirm-actions {\n display: flex;\n gap: 8px;\n justify-content: flex-end;\n }\n\n .sp-btn-danger {\n height: 40px;\n padding: 0 22px;\n border-radius: var(--sp-radius);\n border: none;\n background: #ef4444;\n color: #fff;\n font-family: var(--sp-font);\n font-size: 14px;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.2s ease;\n box-shadow: 0 2px 8px rgba(239, 68, 68, 0.25);\n }\n\n .sp-btn-danger:hover {\n background: #dc2626;\n box-shadow: 0 4px 16px rgba(239, 68, 68, 0.3);\n transform: translateY(-1px);\n }\n\n .sp-btn-danger:active {\n transform: translateY(0) scale(0.98);\n transition-duration: 0.1s;\n }\n\n .sp-card--resolved {\n opacity: 0.5;\n }\n\n .sp-card--resolved .sp-card-message {\n text-decoration: line-through;\n text-decoration-color: var(--sp-text-tertiary);\n }\n\n /* ============================\n Loading State\n ============================ */\n\n .sp-loading {\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 48px 24px;\n }\n\n /* ============================\n Identity Form\n ============================ */\n\n .sp-identity-title {\n font-size: 17px;\n font-weight: 700;\n color: var(--sp-text);\n letter-spacing: -0.02em;\n }\n\n .sp-input {\n width: 100%;\n height: 42px;\n padding: 0 14px;\n border-radius: var(--sp-radius);\n border: 1px solid var(--sp-border);\n background: var(--sp-glass-bg-heavy);\n color: var(--sp-text);\n font-family: var(--sp-font);\n font-size: 14px;\n outline: none;\n transition: all 0.2s ease;\n }\n\n .sp-input::placeholder {\n color: var(--sp-text-tertiary);\n }\n\n .sp-input:focus {\n border-color: var(--sp-accent);\n box-shadow: 0 0 0 3px var(--sp-accent-light);\n background: #fff;\n }\n\n .sp-input-label {\n font-size: 13px;\n font-weight: 500;\n color: var(--sp-text-secondary);\n margin-bottom: 6px;\n display: block;\n }\n\n /* ============================\n Buttons\n ============================ */\n\n .sp-btn-primary {\n height: 40px;\n padding: 0 22px;\n border-radius: var(--sp-radius);\n border: none;\n background: var(--sp-accent-gradient);\n color: #fff;\n font-family: var(--sp-font);\n font-size: 14px;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.2s ease;\n box-shadow: 0 2px 8px var(--sp-accent-glow);\n }\n\n .sp-btn-primary:hover {\n box-shadow: 0 4px 16px var(--sp-accent-glow);\n transform: translateY(-1px);\n }\n\n .sp-btn-primary:active {\n transform: translateY(0) scale(0.98);\n transition-duration: 0.1s;\n }\n\n .sp-btn-primary:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n transform: none;\n box-shadow: none;\n }\n\n .sp-btn-ghost {\n height: 40px;\n padding: 0 22px;\n border-radius: var(--sp-radius);\n border: 1px solid var(--sp-border);\n background: var(--sp-glass-bg-heavy);\n color: var(--sp-text-secondary);\n font-family: var(--sp-font);\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.2s ease;\n }\n\n .sp-btn-ghost:hover {\n border-color: var(--sp-accent);\n color: var(--sp-accent);\n background: var(--sp-accent-light);\n }\n\n /* ============================\n Empty State\n ============================ */\n\n .sp-empty {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 56px 24px;\n color: var(--sp-text-tertiary);\n text-align: center;\n gap: 8px;\n animation: sp-fade-in 0.3s ease-out both;\n }\n\n .sp-empty-text {\n font-size: 14px;\n font-weight: 500;\n }\n\n /* ============================\n Forced Colors / High Contrast\n ============================ */\n\n @media (forced-colors: active) {\n .sp-fab,\n .sp-radial-item,\n .sp-chip,\n .sp-card,\n .sp-panel-close,\n .sp-search,\n .sp-btn-resolve,\n .sp-btn-delete,\n .sp-btn-delete-all,\n .sp-btn-primary,\n .sp-btn-ghost,\n .sp-btn-danger,\n .sp-card-expand,\n .sp-input,\n .sp-confirm-dialog {\n border: 2px solid ButtonText !important;\n background: Canvas !important;\n color: ButtonText !important;\n }\n\n .sp-fab:focus-visible,\n .sp-radial-item:focus-visible,\n .sp-chip:focus-visible,\n .sp-panel-close:focus-visible,\n .sp-btn-resolve:focus-visible,\n .sp-btn-delete:focus-visible,\n .sp-btn-delete-all:focus-visible,\n .sp-btn-primary:focus-visible,\n .sp-btn-ghost:focus-visible,\n .sp-btn-danger:focus-visible,\n .sp-card-expand:focus-visible,\n .sp-input:focus-visible,\n .sp-search:focus-visible {\n outline: 3px solid Highlight !important;\n }\n\n .sp-panel {\n border: 2px solid ButtonText !important;\n }\n\n .sp-fab-badge {\n border: 2px solid ButtonText !important;\n background: Canvas !important;\n color: ButtonText !important;\n }\n\n .sp-card-bar {\n background: ButtonText !important;\n }\n }\n\n ${ANIMATION_CSS}\n `;\n}\n","import type { FeedbackResponse } from \"@siteping/core\";\nimport { el, formatRelativeDate, setText } from \"./dom-utils.js\";\nimport { createT, getTypeLabel } from \"./i18n/index.js\";\nimport { getTypeBgColor, getTypeColor, type ThemeColors } from \"./styles/theme.js\";\n\nconst SHOW_DELAY = 120;\nconst HIDE_DELAY = 80;\n\n/**\n * Tooltip shown on annotation marker hover.\n *\n * Glassmorphism design: frosted glass with pastel badge,\n * smooth entrance animation, directional arrow.\n * Lives outside Shadow DOM.\n */\nexport class Tooltip {\n private root: HTMLElement;\n private arrow: HTMLElement;\n private showTimer: ReturnType<typeof setTimeout> | null = null;\n private hideTimer: ReturnType<typeof setTimeout> | null = null;\n private currentFeedbackId: string | null = null;\n\n readonly tooltipId = \"sp-tooltip\";\n\n constructor(\n private readonly colors: ThemeColors,\n private readonly locale: string = \"fr\",\n ) {\n this.root = el(\"div\", {\n style: `\n position: fixed;\n z-index: 2147483647;\n max-width: 280px;\n padding: 12px 14px;\n border-radius: 14px;\n background: ${this.colors.glassBgHeavy};\n backdrop-filter: blur(24px);\n -webkit-backdrop-filter: blur(24px);\n border: 1px solid ${this.colors.glassBorder};\n box-shadow: 0 8px 32px ${this.colors.shadow}, 0 2px 8px ${this.colors.shadow};\n font-family: \"Inter\", system-ui, -apple-system, sans-serif;\n pointer-events: auto;\n opacity: 0;\n transform: translateY(6px) scale(0.97);\n transition: opacity 0.2s cubic-bezier(0.16, 1, 0.3, 1), transform 0.2s cubic-bezier(0.16, 1, 0.3, 1);\n visibility: hidden;\n -webkit-font-smoothing: antialiased;\n `,\n });\n\n this.root.setAttribute(\"role\", \"tooltip\");\n this.root.id = this.tooltipId;\n\n // Arrow element\n this.arrow = el(\"div\", {\n style: `\n position: absolute;\n width: 12px;\n height: 12px;\n background: ${this.colors.glassBgHeavy};\n border: 1px solid ${this.colors.glassBorder};\n transform: rotate(45deg);\n pointer-events: none;\n `,\n });\n this.root.appendChild(this.arrow);\n\n this.root.addEventListener(\"mouseenter\", () => this.cancelHide());\n this.root.addEventListener(\"mouseleave\", () => this.scheduleHide());\n document.body.appendChild(this.root);\n }\n\n show(feedback: FeedbackResponse, anchorRect: DOMRect): void {\n if (this.currentFeedbackId === feedback.id) return;\n this.cancelHide();\n this.cancelShow();\n\n this.showTimer = setTimeout(() => {\n this.currentFeedbackId = feedback.id;\n this.render(feedback);\n this.position(anchorRect);\n\n // Check prefers-reduced-motion live (not cached at construction time)\n const reduceMotion =\n typeof window !== \"undefined\" && window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n this.root.style.transition = reduceMotion ? \"none\" : \"\";\n\n this.root.style.visibility = \"visible\";\n this.root.style.opacity = \"1\";\n this.root.style.transform = \"translateY(0) scale(1)\";\n }, SHOW_DELAY);\n }\n\n scheduleHide(): void {\n this.cancelHide();\n this.hideTimer = setTimeout(() => this.hide(), HIDE_DELAY);\n }\n\n hide(): void {\n this.cancelShow();\n this.currentFeedbackId = null;\n this.root.style.opacity = \"0\";\n this.root.style.transform = \"translateY(6px) scale(0.97)\";\n setTimeout(() => {\n if (!this.currentFeedbackId) {\n this.root.style.visibility = \"hidden\";\n }\n }, 200);\n }\n\n private cancelShow(): void {\n if (this.showTimer) {\n clearTimeout(this.showTimer);\n this.showTimer = null;\n }\n }\n\n private cancelHide(): void {\n if (this.hideTimer) {\n clearTimeout(this.hideTimer);\n this.hideTimer = null;\n }\n }\n\n private render(feedback: FeedbackResponse): void {\n // Clear previous content safely (except arrow)\n const children = Array.from(this.root.children);\n for (const child of children) {\n if (child !== this.arrow) child.remove();\n }\n\n const typeColor = getTypeColor(feedback.type, this.colors);\n const typeBg = getTypeBgColor(feedback.type, this.colors);\n const t = createT(this.locale);\n const typeLabel = getTypeLabel(feedback.type, t);\n\n // Header row: badge + date\n const header = el(\"div\", { style: \"display:flex;align-items:center;gap:8px;margin-bottom:8px;\" });\n\n const badge = el(\"span\", {\n style: `\n padding:3px 10px;border-radius:9999px;\n font-size:11px;font-weight:600;\n color:${typeColor};background:${typeBg};\n letter-spacing:0.02em;\n `,\n });\n setText(badge, typeLabel);\n\n const date = el(\"span\", { style: `font-size:11px;color:${this.colors.textSecondary};margin-left:auto;` });\n setText(date, formatRelativeDate(feedback.createdAt, this.locale));\n\n header.appendChild(badge);\n header.appendChild(date);\n\n // Message body (safe — textContent only)\n const body = el(\"div\", {\n style: `font-size:13px;line-height:1.55;color:${this.colors.text};display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;`,\n });\n setText(body, feedback.message);\n\n // Insert content before arrow\n this.root.insertBefore(header, this.arrow);\n this.root.insertBefore(body, this.arrow);\n }\n\n private position(anchorRect: DOMRect): void {\n const tooltipRect = this.root.getBoundingClientRect();\n const gap = 10;\n\n let top = anchorRect.top - tooltipRect.height - gap;\n let left = anchorRect.left + anchorRect.width / 2 - tooltipRect.width / 2;\n let isAbove = true;\n\n // Flip below if not enough space above\n if (top < 8) {\n top = anchorRect.bottom + gap;\n isAbove = false;\n }\n\n left = Math.max(8, Math.min(left, window.innerWidth - tooltipRect.width - 8));\n\n this.root.style.top = `${top}px`;\n this.root.style.left = `${left}px`;\n\n // Position arrow\n const arrowLeft = Math.max(16, Math.min(anchorRect.left + anchorRect.width / 2 - left - 6, tooltipRect.width - 22));\n\n if (isAbove) {\n // Arrow at bottom, pointing down\n this.arrow.style.cssText = `\n position:absolute;\n width:12px;height:12px;\n background:${this.colors.glassBgHeavy};\n border-right:1px solid ${this.colors.glassBorder};\n border-bottom:1px solid ${this.colors.glassBorder};\n transform:rotate(45deg);\n pointer-events:none;\n bottom:-6px;\n left:${arrowLeft}px;\n `;\n } else {\n // Arrow at top, pointing up\n this.arrow.style.cssText = `\n position:absolute;\n width:12px;height:12px;\n background:${this.colors.glassBgHeavy};\n border-left:1px solid ${this.colors.glassBorder};\n border-top:1px solid ${this.colors.glassBorder};\n transform:rotate(45deg);\n pointer-events:none;\n top:-6px;\n left:${arrowLeft}px;\n `;\n }\n }\n\n /** Check if a DOM node belongs to this tooltip (for MutationObserver filtering). */\n contains(node: Node): boolean {\n return this.root.contains(node);\n }\n\n destroy(): void {\n this.cancelShow();\n this.cancelHide();\n this.root.remove();\n }\n}\n","import type { FeedbackPayload, SitepingConfig, SitepingInstance, SitepingPublicEvents } from \"@siteping/core\";\nimport { Annotator } from \"./annotator.js\";\nimport { ApiClient, flushRetryQueue, type WidgetClient } from \"./api-client.js\";\nimport { EventBus, type PublicWidgetEvents, type WidgetEvents } from \"./events.js\";\nimport { Fab } from \"./fab.js\";\nimport { createT, type TFunction } from \"./i18n/index.js\";\nimport { getIdentity, type Identity, saveIdentity } from \"./identity.js\";\nimport { MarkerManager } from \"./markers.js\";\nimport { Panel } from \"./panel.js\";\nimport { StoreClient } from \"./store-client.js\";\nimport { buildStyles } from \"./styles/base.js\";\nimport { buildThemeColors } from \"./styles/theme.js\";\nimport { Tooltip } from \"./tooltip.js\";\n\n/** Singleton guard — prevents duplicate widgets from overlapping */\nlet instance: SitepingInstance | null = null;\n\n/** Build a no-op SitepingInstance for when the widget is skipped */\nfunction skippedInstance(): SitepingInstance {\n const noop = () => {};\n return {\n destroy: noop,\n open: noop,\n close: noop,\n refresh: noop,\n on: () => noop,\n off: noop,\n };\n}\n\n/**\n * Main widget launcher — orchestrates all UI components.\n *\n * Architecture:\n * - Creates a <siteping-widget> custom element in the document\n * - Attaches a closed Shadow DOM for CSS isolation\n * - FAB + Panel live inside the Shadow DOM\n * - Overlay, markers, tooltips live outside (appended to document.body)\n */\nexport function launch(config: SitepingConfig): SitepingInstance {\n // Debug helper — only logs when config.debug is true\n const log: (...args: unknown[]) => void = config.debug\n ? (...args: unknown[]) => console.debug(\"[siteping]\", ...args)\n : () => {};\n\n // Guard: prevent duplicate initSiteping() calls\n if (instance) {\n log(\"initSiteping() called more than once — returning existing instance\");\n return instance;\n }\n\n // Guard: only show in development (forceShow bypasses)\n if (!config.forceShow) {\n try {\n // Check for Node/bundler production environment — avoid import.meta\n // which causes \"Critical dependency\" warnings in Next.js webpack builds\n if (typeof process !== \"undefined\" && process.env?.NODE_ENV === \"production\") {\n const reason = \"production\";\n console.info(\"[siteping] Widget not loaded: production mode detected. Use forceShow: true to override.\");\n config.onSkip?.(reason);\n return skippedInstance();\n }\n } catch {\n // Silently ignore — browser or restricted environment\n }\n }\n\n // Guard: desktop only (< 768px = hidden)\n if (window.innerWidth < 768) {\n const reason = \"mobile\";\n console.info(\"[siteping] Widget not loaded: viewport width < 768px (mobile not supported).\");\n config.onSkip?.(reason);\n return skippedInstance();\n }\n\n // Guard: validate required config fields\n if (!config.store && (!config.endpoint || typeof config.endpoint !== \"string\")) {\n console.error(\n \"[siteping] Missing 'endpoint' or 'store' in config. Provide an endpoint like '/api/siteping' or a SitepingStore instance.\",\n );\n return skippedInstance();\n }\n if (!config.projectName || typeof config.projectName !== \"string\") {\n console.error(\"[siteping] Missing or invalid 'projectName' in config. Expected a non-empty string.\");\n return skippedInstance();\n }\n\n const locale = config.locale ?? \"en\";\n const t = createT(locale);\n\n log(\"Initializing widget\", { projectName: config.projectName, theme: config.theme ?? \"light\", locale });\n\n const colors = buildThemeColors(config.accentColor, config.theme);\n const bus = new EventBus<WidgetEvents>();\n const publicBus = new EventBus<PublicWidgetEvents>();\n\n // Client-side mode (store) vs HTTP mode (endpoint)\n const client: WidgetClient = config.store\n ? new StoreClient(config.store, config.projectName)\n : new ApiClient(config.endpoint as string, config.projectName);\n\n // Wire config callbacks to event bus\n if (config.onOpen) bus.on(\"open\", config.onOpen);\n if (config.onClose) bus.on(\"close\", config.onClose);\n if (config.onFeedbackSent) bus.on(\"feedback:sent\", config.onFeedbackSent);\n if (config.onError) bus.on(\"feedback:error\", config.onError);\n if (config.onAnnotationStart) bus.on(\"annotation:start\", config.onAnnotationStart);\n if (config.onAnnotationEnd) bus.on(\"annotation:end\", config.onAnnotationEnd);\n\n // Bridge internal events to public bus\n bus.on(\"feedback:sent\", (fb) => publicBus.emit(\"feedback:sent\", fb));\n bus.on(\"feedback:deleted\", (id) => publicBus.emit(\"feedback:deleted\", id));\n bus.on(\"open\", () => publicBus.emit(\"panel:open\"));\n bus.on(\"close\", () => publicBus.emit(\"panel:close\"));\n\n // Debug logging for key lifecycle events\n bus.on(\"open\", () => log(\"Panel opened\"));\n bus.on(\"close\", () => log(\"Panel closed\"));\n bus.on(\"feedback:sent\", (fb) => log(\"Feedback sent\", fb.id));\n bus.on(\"feedback:error\", (err) => log(\"Feedback failed\", err.message));\n bus.on(\"annotation:start\", () => log(\"Annotation started\"));\n bus.on(\"annotation:end\", () => log(\"Annotation ended\"));\n\n // Create host element + Shadow DOM\n const host = document.createElement(\"siteping-widget\");\n host.style.cssText = \"position:fixed;z-index:2147483647;\";\n // Use open mode only for testing — closed in production for CSS isolation.\n // Shadow DOM mode is determined by environment, never by public config.\n let isTestEnv = false;\n try {\n // Dynamic key prevents bundlers (tsup/esbuild) from statically replacing\n // process.env.NODE_ENV at build time — the widget needs runtime detection\n // so E2E tests can set globalThis.process = { env: { NODE_ENV: 'test' } }\n const envKey = \"NODE_\" + \"ENV\";\n if (typeof process !== \"undefined\" && process.env?.[envKey] === \"test\") {\n isTestEnv = true;\n }\n } catch {\n // Silently ignore — browser or restricted environment\n }\n const shadowMode = isTestEnv ? (\"open\" as const) : (\"closed\" as const);\n const shadow = host.attachShadow({ mode: shadowMode });\n\n // Inject styles into Shadow DOM — adoptedStyleSheets with fallback for Safari < 16.4\n const supportsAdoptedStyleSheets = \"adoptedStyleSheets\" in ShadowRoot.prototype;\n if (supportsAdoptedStyleSheets) {\n const sheet = new CSSStyleSheet();\n sheet.replaceSync(buildStyles(colors));\n shadow.adoptedStyleSheets = [sheet];\n } else {\n const style = document.createElement(\"style\");\n style.textContent = buildStyles(colors);\n (shadow as unknown as DocumentFragment).appendChild(style);\n }\n\n document.body.appendChild(host);\n\n // Screen reader live region for feedback submission announcements\n const liveRegion = document.createElement(\"div\");\n liveRegion.setAttribute(\"role\", \"status\");\n liveRegion.setAttribute(\"aria-live\", \"polite\");\n liveRegion.setAttribute(\"aria-atomic\", \"true\");\n liveRegion.style.cssText =\n \"position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;\";\n document.body.appendChild(liveRegion);\n\n // Components outside Shadow DOM\n const tooltip = new Tooltip(colors, locale);\n const markers = new MarkerManager(colors, tooltip, bus, t);\n\n // Components inside Shadow DOM\n const fab = new Fab(shadow, config, bus, t);\n const panel = new Panel(shadow, colors, bus, client, config.projectName, markers, t, locale);\n const annotator = new Annotator(colors, bus, t);\n\n // Handle annotation completion via event bus (not DOM events)\n // Concurrency guard: prevent duplicate submissions if user draws two annotations quickly\n let submitting = false;\n const unsubAnnotation = bus.on(\"annotation:complete\", async (data) => {\n if (submitting) return;\n submitting = true;\n try {\n const { annotation, type, message } = data;\n\n // Ensure identity\n let identity = getIdentity();\n if (!identity) {\n identity = await promptIdentity(shadow, t);\n if (!identity) return; // User cancelled\n saveIdentity(identity);\n }\n\n // Sanitize URL — strip sensitive query params before sending\n const rawUrl = new URL(window.location.href);\n for (const key of [...rawUrl.searchParams.keys()]) {\n if (/token|key|secret|auth|session|password|code/i.test(key)) {\n rawUrl.searchParams.delete(key);\n }\n }\n const sanitizedUrl = rawUrl.toString();\n\n // crypto.randomUUID() throws in non-secure contexts (plain HTTP)\n const clientId = (() => {\n try {\n return crypto.randomUUID();\n } catch {\n return `${Date.now()}-${Math.random().toString(36).slice(2)}`;\n }\n })();\n\n const payload: FeedbackPayload = {\n projectName: config.projectName,\n type,\n message,\n url: sanitizedUrl,\n viewport: `${window.innerWidth}x${window.innerHeight}`,\n userAgent: navigator.userAgent,\n authorName: identity.name,\n authorEmail: identity.email,\n annotations: [annotation],\n clientId,\n };\n\n try {\n const response = await client.sendFeedback(payload);\n bus.emit(\"feedback:sent\", response);\n markers.addFeedback(response, markers.count + 1);\n liveRegion.textContent = t(\"feedback.sent.confirmation\");\n await panel.refresh();\n } catch (error) {\n bus.emit(\"feedback:error\", error instanceof Error ? error : new Error(String(error)));\n liveRegion.textContent = t(\"feedback.error.message\");\n }\n } finally {\n submitting = false;\n }\n });\n\n // Load markers immediately on page load\n client\n .getFeedbacks(config.projectName, { limit: 50 })\n .then(({ feedbacks }) => {\n markers.render(feedbacks);\n })\n .catch((err) => {\n log(\"Failed to load initial markers:\", err);\n });\n\n // Flush retry queue on load (HTTP mode only — store mode has no retry queue)\n if (config.endpoint) {\n flushRetryQueue(config.endpoint)\n .then(() => log(\"Retry queue flushed\"))\n .catch(() => {});\n }\n\n instance = {\n destroy: () => {\n log(\"Destroying widget\");\n unsubAnnotation();\n fab.destroy();\n panel.destroy();\n annotator.destroy();\n markers.destroy();\n tooltip.destroy();\n bus.removeAll();\n publicBus.removeAll();\n liveRegion.remove();\n host.remove();\n instance = null;\n },\n open: () => {\n panel.open();\n },\n close: () => {\n panel.close();\n },\n refresh: () => {\n panel.refresh();\n },\n on: <K extends keyof SitepingPublicEvents>(event: K, listener: (...args: SitepingPublicEvents[K]) => void) => {\n // Safe cast: SitepingPublicEvents and PublicWidgetEvents have identical keys and value types\n type TargetKey = K & keyof PublicWidgetEvents;\n return publicBus.on(event as TargetKey, listener as unknown as (...args: PublicWidgetEvents[TargetKey]) => void);\n },\n off: <K extends keyof SitepingPublicEvents>(event: K, listener: (...args: SitepingPublicEvents[K]) => void) => {\n // Safe cast: SitepingPublicEvents and PublicWidgetEvents have identical keys and value types\n type TargetKey = K & keyof PublicWidgetEvents;\n publicBus.off(event as TargetKey, listener as unknown as (...args: PublicWidgetEvents[TargetKey]) => void);\n },\n };\n\n return instance;\n}\n\n/**\n * Show a modal identity form inside the Shadow DOM.\n * Glassmorphism: frosted backdrop, glass modal, gradient CTA.\n * Returns null if the user cancels.\n */\nfunction promptIdentity(shadowRoot: ShadowRoot, t: TFunction): Promise<Identity | null> {\n return new Promise((resolve) => {\n // Save the currently focused element to restore on close\n const previouslyFocused = (shadowRoot.activeElement ?? document.activeElement) as HTMLElement | null;\n\n const backdrop = document.createElement(\"div\");\n backdrop.style.cssText = `\n position:fixed;inset:0;\n background:var(--sp-identity-overlay);\n backdrop-filter:blur(8px);\n -webkit-backdrop-filter:blur(8px);\n display:flex;align-items:center;justify-content:center;\n z-index:2147483647;\n opacity:0;transition:opacity 0.25s ease;\n `;\n\n const modal = document.createElement(\"div\");\n modal.style.cssText = `\n width:340px;padding:28px;border-radius:var(--sp-radius-xl);\n background:var(--sp-identity-bg);\n backdrop-filter:blur(var(--sp-blur-heavy));\n -webkit-backdrop-filter:blur(var(--sp-blur-heavy));\n border:1px solid var(--sp-glass-border);\n box-shadow:0 16px 48px var(--sp-shadow), 0 8px 16px var(--sp-shadow);\n font-family:var(--sp-font, \"Inter\",system-ui,-apple-system,sans-serif);\n color:var(--sp-text);\n transform:translateY(12px) scale(0.97);\n transition:transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);\n -webkit-font-smoothing:antialiased;\n `;\n\n const titleId = `sp-identity-title-${Date.now()}`;\n modal.setAttribute(\"role\", \"dialog\");\n modal.setAttribute(\"aria-modal\", \"true\");\n modal.setAttribute(\"aria-labelledby\", titleId);\n\n const title = document.createElement(\"div\");\n title.className = \"sp-identity-title\";\n title.id = titleId;\n title.textContent = t(\"identity.title\");\n title.style.marginBottom = \"20px\";\n\n const nameInputId = `sp-identity-name-${Date.now()}`;\n const emailInputId = `sp-identity-email-${Date.now()}`;\n\n const nameLabel = document.createElement(\"label\");\n nameLabel.className = \"sp-input-label\";\n nameLabel.textContent = t(\"identity.nameLabel\");\n nameLabel.setAttribute(\"for\", nameInputId);\n const nameInput = document.createElement(\"input\");\n nameInput.className = \"sp-input\";\n nameInput.id = nameInputId;\n nameInput.type = \"text\";\n nameInput.placeholder = t(\"identity.namePlaceholder\");\n nameInput.style.marginBottom = \"14px\";\n\n const emailLabel = document.createElement(\"label\");\n emailLabel.className = \"sp-input-label\";\n emailLabel.textContent = t(\"identity.emailLabel\");\n emailLabel.setAttribute(\"for\", emailInputId);\n const emailInput = document.createElement(\"input\");\n emailInput.className = \"sp-input\";\n emailInput.id = emailInputId;\n emailInput.type = \"email\";\n emailInput.placeholder = t(\"identity.emailPlaceholder\");\n\n const btnRow = document.createElement(\"div\");\n btnRow.style.cssText = \"display:flex;gap:8px;justify-content:flex-end;margin-top:20px;\";\n\n const closeModal = (result: Identity | null) => {\n backdrop.removeEventListener(\"keydown\", onKeydown);\n backdrop.style.opacity = \"0\";\n modal.style.transform = \"translateY(12px) scale(0.97)\";\n setTimeout(() => {\n backdrop.remove();\n previouslyFocused?.focus();\n resolve(result);\n }, 250);\n };\n\n const cancelBtn = document.createElement(\"button\");\n cancelBtn.className = \"sp-btn-ghost\";\n cancelBtn.textContent = t(\"identity.cancel\");\n cancelBtn.addEventListener(\"click\", () => closeModal(null));\n\n const submitBtn = document.createElement(\"button\");\n submitBtn.className = \"sp-btn-primary\";\n submitBtn.textContent = t(\"identity.submit\");\n submitBtn.addEventListener(\"click\", () => {\n const name = nameInput.value.trim();\n const email = emailInput.value.trim();\n if (!name || !email) return;\n const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n if (!emailRegex.test(email)) {\n emailInput.style.borderColor = \"var(--sp-type-bug, #ef4444)\";\n return;\n }\n closeModal({ name, email });\n });\n\n // Focus trap: cycle Tab/Shift+Tab within the modal\n const focusableSelectors = 'input, button, [tabindex]:not([tabindex=\"-1\"])';\n const onKeydown = (e: Event) => {\n const ke = e as KeyboardEvent;\n if (ke.key === \"Escape\") {\n closeModal(null);\n return;\n }\n if (ke.key === \"Tab\") {\n const focusableEls = Array.from(modal.querySelectorAll<HTMLElement>(focusableSelectors));\n if (focusableEls.length === 0) return;\n const first = focusableEls[0];\n const last = focusableEls[focusableEls.length - 1];\n if (!first || !last) return;\n const active = shadowRoot.activeElement as HTMLElement | null;\n if (ke.shiftKey) {\n if (active === first || !modal.contains(active)) {\n ke.preventDefault();\n last.focus();\n }\n } else {\n if (active === last || !modal.contains(active)) {\n ke.preventDefault();\n first.focus();\n }\n }\n }\n };\n backdrop.addEventListener(\"keydown\", onKeydown);\n\n // Close on backdrop click\n backdrop.addEventListener(\"click\", (e) => {\n if (e.target === backdrop) closeModal(null);\n });\n\n btnRow.appendChild(cancelBtn);\n btnRow.appendChild(submitBtn);\n\n modal.appendChild(title);\n modal.appendChild(nameLabel);\n modal.appendChild(nameInput);\n modal.appendChild(emailLabel);\n modal.appendChild(emailInput);\n modal.appendChild(btnRow);\n backdrop.appendChild(modal);\n\n shadowRoot.appendChild(backdrop);\n\n // Animate in\n requestAnimationFrame(() => {\n backdrop.style.opacity = \"1\";\n modal.style.transform = \"translateY(0) scale(1)\";\n nameInput.focus();\n });\n });\n}\n"],"mappings":"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AA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA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","names":["index_exports","__export","initSiteping","config","rootDocument","start","finder","input","options","defaults","name","value","findRootDocument","path","bottomUpSearch","optimized","sort","optimize","selector","rootNode","limit","fallback","stack","current","i","elapsedTime","level","maybe","id","attr","classNames","tagName","any","nth","index","dispensableNth","node","nthChild","findUniquePath","paths","combinations","candidate","unique","query","penalty","acc","css","elementId","parent","child","list","notEmpty","a","b","scope","newPath","newPathKey","same","STABLE_ATTRS","djb2","str","hash","i","generateFingerprint","element","childCount","siblingIdx","parent","child","attrs","attr","val","attrHash","scoreFingerprint","candidate","storedFingerprint","parts","storedChildren","storedSibIdx","storedAttrHash","storedChildCount","storedSibIndex","candidateFp","candChildren","candSibIdx","candAttrHash","score","childDiff","sibDiff","adjacentText","element","direction","prop","sibling","attempts","text","neighborText","prev","next","generateXPath","element","safeId","segments","current","tag","parent","position","sibling","generateAnchor","element","cssSelector","finder","name","xpath","generateXPath","textSnippet","textPrefix","adjacentText","textSuffix","fingerprint","generateFingerprint","neighbor","neighborText","findAnchorElement","rect","root","centerX","centerY","elementAtCenter","candidate","current","bounds","rectToPercentages","anchorBounds","parseSvg","svgString","svg","attr","el","tag","attrs","element","key","value","setText","text","formatRelativeDate","isoString","locale","diff","seconds","rtf","minutes","hours","days","ICON_SITEPING","ICON_CHAT","ICON_ANNOTATE","ICON_EYE","ICON_EYE_OFF","ICON_CLOSE","ICON_SEARCH","ICON_CHECK","ICON_QUESTION","ICON_CHANGE","ICON_BUG","ICON_OTHER","ICON_UNDO","ICON_TRASH","DEFAULT_ACCENT","HEX6_RE","HEX3_RE","HEX8_RE","normalizeHex","raw","short","darkenHex","hex","amount","r","g","b","prefersDark","resolveTheme","theme","buildThemeColors","accent","dark","getTypeColor","type","colors","getTypeBgColor","cssVariables","Popup","colors","t","el","typeOptions","ICON_QUESTION","ICON_CHANGE","ICON_BUG","ICON_OTHER","typeRow","option","btn","icon","parseSvg","labelSpan","setText","bgColor","getTypeBgColor","getTypeColor","hint","uaData","isMac","e","btnRow","cancelBtn","rectBounds","resolve","top","left","focusableEls","first","last","reduceMotion","type","container","buttons","isActive","color","enabled","Annotator","colors","bus","t","Popup","el","dot","style","instruction","setText","cancelBtn","target","bounds","rectBounds","result","annotation","generateAnchor","touch","clientX","clientY","source","evt","x","y","w","h","anchorElement","findAnchorElement","anchor","anchorBounds","rect","rectToPercentages","RETRY_QUEUE_KEY","resilientFetch","url","init","retries","attempt","controller","timeout","response","error","baseDelay","jitter","r","queueForRetry","endpoint","payload","raw","RETRY_QUEUE_KEY","parsed","queue","flushRetryQueue","toRetry","e","failed","entry","remaining","ApiClient","projectName","text","options","params","id","resolved","EventBus","event","listener","set","args","fn","err","ITEM_GAP","Fab","shadowRoot","config","bus","t","position","isRight","ICON_CHAT","ICON_ANNOTATE","ICON_EYE","ICON_EYE_OFF","parseSvg","ICON_SITEPING","i","item","btn","e","label","host","handleEscape","items","activeEl","currentIndex","nextIndex","count","displayText","setText","ICON_CLOSE","y","svgStr","badge","id","en","fr","LOCALES","fr","en","createT","locale","lang","LOCALES","dict","key","getTypeLabel","type","t","STORAGE_KEY","getIdentity","raw","parsed","identity","saveIdentity","editDistance","a","b","t","aLen","bLen","prev","k","curr","j","i","prevDiag","tmp","similarity","maxLen","fuzzyIncludes","haystack","needle","minScore","nLen","score","best","capped","limit","window","MAX_SCAN_CANDIDATES","TEXT_MATCH_THRESHOLD","textMatches","el","anchor","text","fuzzyIncludes","resolveAnchor","smartScan","tag","candidates","bestElement","bestScore","limit","i","score","scoreCandidate","candidate","totalWeight","candidateText","scoreFingerprint","contextScore","contextParts","prevText","adjacentText","similarity","nextText","candidateNeighbor","neighborText","resolveAnnotation","rect","resolution","bounds","absoluteRect","toAnchorData","a","toRectData","MARKER_OFFSET","markerPosition","rect","clusterMarker","cluster","i","entry","elIdx","HIGHLIGHT_FADE","REPOSITION_DEBOUNCE","LOW_CONFIDENCE_THRESHOLD","CLUSTER_DISTANCE","FAN_SPACING","MarkerManager","colors","tooltip","bus","t","el","visible","mutations","hasRelevantMutation","m","e","markerEl","annotation","cacheKey","cachedEl","resolved","anchorRect","r","resolveAnnotation","pos","feedbacks","feedback","index","marker","badge","allItems","used","itemI","j","itemJ","b","first","baseTop","baseLeft","isSolo","count","totalWidth","startLeft","topMarker","setText","confidence","isResolved","number","typeColor","getTypeColor","truncatedMessage","ariaLabel","getTypeLabel","activateMarker","feedbackId","highlight","h","Panel","shadowRoot","colors","bus","client","projectName","markers","t","locale","el","header","title","setText","parseSvg","ICON_CLOSE","ICON_TRASH","deleteAllLabel","headerRight","filters","searchWrap","searchIcon","ICON_SEARCH","chips","chipOptions","option","chip","getTypeColor","e","target","actionEl","card","feedbackId","feedback","f","action","message","isExpanded","btn","ann","ke","open","focusable","first","last","active","loading","spinner","empty","text","retryBtn","signal","search","typeFilter","options","hasContent","feedbacks","error","emptyText","index","number","isResolved","typeColor","getTypeLabel","bar","body","num","badge","typeBg","getTypeBgColor","date","formatRelativeDate","expandBtn","footer","resolveBtn","ICON_UNDO","span","ICON_CHECK","deleteBtn","deleteLabel","resolve","backdrop","titleId","messageId","dialog","titleEl","messageEl","btnRow","cancelBtn","confirmBtn","closed","close","result","onKeydown","newResolved","value","container","isActive","escapedId","flattenAnnotation","ann","StoreClient","store","projectName","payload","record","flattenAnnotation","toResponse","options","feedbacks","total","id","resolved","toAnnotationResponse","ann","SPRING_LINEAR","EASE_OUT_EXPO","SPRING_OVERSHOOT","EASE_OUT_QUART","ANIMATION_CSS","buildStyles","colors","cssVariables","ANIMATION_CSS","SHOW_DELAY","HIDE_DELAY","Tooltip","colors","locale","el","feedback","anchorRect","reduceMotion","children","child","typeColor","getTypeColor","typeBg","getTypeBgColor","t","createT","typeLabel","getTypeLabel","header","badge","setText","date","formatRelativeDate","body","tooltipRect","gap","top","left","isAbove","arrowLeft","node","instance","skippedInstance","noop","launch","config","log","args","reason","locale","t","createT","colors","buildThemeColors","bus","EventBus","publicBus","client","StoreClient","ApiClient","fb","id","err","host","isTestEnv","shadowMode","shadow","sheet","buildStyles","style","liveRegion","tooltip","Tooltip","markers","MarkerManager","fab","Fab","panel","Panel","annotator","Annotator","submitting","unsubAnnotation","data","annotation","type","message","identity","getIdentity","promptIdentity","saveIdentity","rawUrl","key","sanitizedUrl","clientId","payload","response","error","feedbacks","flushRetryQueue","event","listener","shadowRoot","resolve","previouslyFocused","backdrop","modal","titleId","title","nameInputId","emailInputId","nameLabel","nameInput","emailLabel","emailInput","btnRow","closeModal","result","onKeydown","cancelBtn","submitBtn","name","email","focusableSelectors","e","ke","focusableEls","first","last","active","initSiteping","config","launch"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../../../node_modules/.bun/@medv+finder@3.2.0/node_modules/@medv/finder/finder.js","../src/dom/fingerprint.ts","../src/dom/text-context.ts","../src/dom/xpath.ts","../src/dom/anchor.ts","../src/dom-utils.ts","../src/icons.ts","../src/styles/theme.ts","../src/popup.ts","../src/annotator.ts","../src/api-client.ts","../src/events.ts","../src/fab.ts","../src/i18n/de.ts","../src/i18n/en.ts","../src/i18n/es.ts","../src/i18n/fr.ts","../src/i18n/it.ts","../src/i18n/pt.ts","../src/i18n/ru.ts","../src/i18n/index.ts","../src/identity.ts","../src/dom/fuzzy.ts","../src/dom/resolver.ts","../src/markers.ts","../src/export-utils.ts","../src/panel-bulk.ts","../src/panel-detail.ts","../src/panel-sort.ts","../src/panel-stats.ts","../src/shortcuts.ts","../src/panel.ts","../../core/src/types.ts","../src/store-client.ts","../src/styles/animations.ts","../src/styles/base.ts","../src/tooltip.ts","../src/launcher.ts"],"sourcesContent":["import type { SitepingConfig, SitepingInstance } from \"@siteping/core\";\nimport { launch } from \"./launcher.js\";\n\nexport type {\n AnchorData,\n AnnotationPayload,\n AnnotationResponse,\n FeedbackPayload,\n FeedbackResponse,\n FeedbackStatus,\n FeedbackType,\n RectData,\n SitepingConfig,\n SitepingInstance,\n SitepingPublicEvents,\n SitepingStore,\n} from \"@siteping/core\";\n\nexport type { Identity } from \"./identity.js\";\n\n/**\n * Initialize the Siteping feedback widget.\n *\n * @example\n * ```ts\n * import { initSiteping } from '@siteping/widget'\n *\n * const { destroy } = initSiteping({\n * endpoint: '/api/siteping',\n * projectName: 'my-project',\n * })\n * ```\n */\nexport function initSiteping(config: SitepingConfig): SitepingInstance {\n return launch(config);\n}\n","// License: MIT\n// Author: Anton Medvedev <anton@medv.io>\n// Source: https://github.com/antonmedv/finder\nlet config;\nlet rootDocument;\nlet start;\nexport function finder(input, options) {\n start = new Date();\n if (input.nodeType !== Node.ELEMENT_NODE) {\n throw new Error(`Can't generate CSS selector for non-element node type.`);\n }\n if ('html' === input.tagName.toLowerCase()) {\n return 'html';\n }\n const defaults = {\n root: document.body,\n idName: (name) => true,\n className: (name) => true,\n tagName: (name) => true,\n attr: (name, value) => false,\n seedMinLength: 1,\n optimizedMinLength: 2,\n threshold: 1000,\n maxNumberOfTries: 10000,\n timeoutMs: undefined,\n };\n config = { ...defaults, ...options };\n rootDocument = findRootDocument(config.root, defaults);\n let path = bottomUpSearch(input, 'all', () => bottomUpSearch(input, 'two', () => bottomUpSearch(input, 'one', () => bottomUpSearch(input, 'none'))));\n if (path) {\n const optimized = sort(optimize(path, input));\n if (optimized.length > 0) {\n path = optimized[0];\n }\n return selector(path);\n }\n else {\n throw new Error(`Selector was not found.`);\n }\n}\nfunction findRootDocument(rootNode, defaults) {\n if (rootNode.nodeType === Node.DOCUMENT_NODE) {\n return rootNode;\n }\n if (rootNode === defaults.root) {\n return rootNode.ownerDocument;\n }\n return rootNode;\n}\nfunction bottomUpSearch(input, limit, fallback) {\n let path = null;\n let stack = [];\n let current = input;\n let i = 0;\n while (current) {\n const elapsedTime = new Date().getTime() - start.getTime();\n if (config.timeoutMs !== undefined && elapsedTime > config.timeoutMs) {\n throw new Error(`Timeout: Can't find a unique selector after ${elapsedTime}ms`);\n }\n let level = maybe(id(current)) ||\n maybe(...attr(current)) ||\n maybe(...classNames(current)) ||\n maybe(tagName(current)) || [any()];\n const nth = index(current);\n if (limit == 'all') {\n if (nth) {\n level = level.concat(level.filter(dispensableNth).map((node) => nthChild(node, nth)));\n }\n }\n else if (limit == 'two') {\n level = level.slice(0, 1);\n if (nth) {\n level = level.concat(level.filter(dispensableNth).map((node) => nthChild(node, nth)));\n }\n }\n else if (limit == 'one') {\n const [node] = (level = level.slice(0, 1));\n if (nth && dispensableNth(node)) {\n level = [nthChild(node, nth)];\n }\n }\n else if (limit == 'none') {\n level = [any()];\n if (nth) {\n level = [nthChild(level[0], nth)];\n }\n }\n for (let node of level) {\n node.level = i;\n }\n stack.push(level);\n if (stack.length >= config.seedMinLength) {\n path = findUniquePath(stack, fallback);\n if (path) {\n break;\n }\n }\n current = current.parentElement;\n i++;\n }\n if (!path) {\n path = findUniquePath(stack, fallback);\n }\n if (!path && fallback) {\n return fallback();\n }\n return path;\n}\nfunction findUniquePath(stack, fallback) {\n const paths = sort(combinations(stack));\n if (paths.length > config.threshold) {\n return fallback ? fallback() : null;\n }\n for (let candidate of paths) {\n if (unique(candidate)) {\n return candidate;\n }\n }\n return null;\n}\nfunction selector(path) {\n let node = path[0];\n let query = node.name;\n for (let i = 1; i < path.length; i++) {\n const level = path[i].level || 0;\n if (node.level === level - 1) {\n query = `${path[i].name} > ${query}`;\n }\n else {\n query = `${path[i].name} ${query}`;\n }\n node = path[i];\n }\n return query;\n}\nfunction penalty(path) {\n return path.map((node) => node.penalty).reduce((acc, i) => acc + i, 0);\n}\nfunction unique(path) {\n const css = selector(path);\n switch (rootDocument.querySelectorAll(css).length) {\n case 0:\n throw new Error(`Can't select any node with this selector: ${css}`);\n case 1:\n return true;\n default:\n return false;\n }\n}\nfunction id(input) {\n const elementId = input.getAttribute('id');\n if (elementId && config.idName(elementId)) {\n return {\n name: '#' + CSS.escape(elementId),\n penalty: 0,\n };\n }\n return null;\n}\nfunction attr(input) {\n const attrs = Array.from(input.attributes).filter((attr) => config.attr(attr.name, attr.value));\n return attrs.map((attr) => ({\n name: `[${CSS.escape(attr.name)}=\"${CSS.escape(attr.value)}\"]`,\n penalty: 0.5,\n }));\n}\nfunction classNames(input) {\n const names = Array.from(input.classList).filter(config.className);\n return names.map((name) => ({\n name: '.' + CSS.escape(name),\n penalty: 1,\n }));\n}\nfunction tagName(input) {\n const name = input.tagName.toLowerCase();\n if (config.tagName(name)) {\n return {\n name,\n penalty: 2,\n };\n }\n return null;\n}\nfunction any() {\n return {\n name: '*',\n penalty: 3,\n };\n}\nfunction index(input) {\n const parent = input.parentNode;\n if (!parent) {\n return null;\n }\n let child = parent.firstChild;\n if (!child) {\n return null;\n }\n let i = 0;\n while (child) {\n if (child.nodeType === Node.ELEMENT_NODE) {\n i++;\n }\n if (child === input) {\n break;\n }\n child = child.nextSibling;\n }\n return i;\n}\nfunction nthChild(node, i) {\n return {\n name: node.name + `:nth-child(${i})`,\n penalty: node.penalty + 1,\n };\n}\nfunction dispensableNth(node) {\n return node.name !== 'html' && !node.name.startsWith('#');\n}\nfunction maybe(...level) {\n const list = level.filter(notEmpty);\n if (list.length > 0) {\n return list;\n }\n return null;\n}\nfunction notEmpty(value) {\n return value !== null && value !== undefined;\n}\nfunction* combinations(stack, path = []) {\n if (stack.length > 0) {\n for (let node of stack[0]) {\n yield* combinations(stack.slice(1, stack.length), path.concat(node));\n }\n }\n else {\n yield path;\n }\n}\nfunction sort(paths) {\n return [...paths].sort((a, b) => penalty(a) - penalty(b));\n}\nfunction* optimize(path, input, scope = {\n counter: 0,\n visited: new Map(),\n}) {\n if (path.length > 2 && path.length > config.optimizedMinLength) {\n for (let i = 1; i < path.length - 1; i++) {\n if (scope.counter > config.maxNumberOfTries) {\n return; // Okay At least I tried!\n }\n scope.counter += 1;\n const newPath = [...path];\n newPath.splice(i, 1);\n const newPathKey = selector(newPath);\n if (scope.visited.has(newPathKey)) {\n return;\n }\n if (unique(newPath) && same(newPath, input)) {\n yield newPath;\n scope.visited.set(newPathKey, true);\n yield* optimize(newPath, input, scope);\n }\n }\n }\n}\nfunction same(path, input) {\n return rootDocument.querySelector(selector(path)) === input;\n}\n","/**\n * Element fingerprinting for robust DOM re-anchoring.\n *\n * Captures structural properties (child count, sibling index, stable attributes)\n * that survive CSS class changes and minor DOM reshuffling.\n * Inspired by Similo (academic state-of-the-art, 98.8% accuracy).\n */\n\nconst STABLE_ATTRS = [\"role\", \"aria-label\", \"type\", \"name\", \"href\", \"src\", \"data-testid\", \"data-id\"] as const;\n\n/** Simple 32-bit hash (djb2). */\nfunction djb2(str: string): string {\n let hash = 5381;\n for (let i = 0; i < str.length; i++) {\n hash = ((hash << 5) + hash + str.charCodeAt(i)) | 0;\n }\n return (hash >>> 0).toString(36);\n}\n\n/**\n * Generate a compact structural fingerprint for a DOM element.\n *\n * Format: `\"childCount:siblingIdx:attrHash\"`\n * - `childCount` — number of direct child elements\n * - `siblingIdx` — position among same-tag siblings (0-based)\n * - `attrHash` — djb2 hash of stable attributes (role, aria-label, type, etc.)\n *\n * Tag name is NOT included — it's stored separately in `AnchorData.elementTag`.\n */\nexport function generateFingerprint(element: Element): string {\n const childCount = element.children.length;\n\n // Position among same-tag siblings\n let siblingIdx = 0;\n const parent = element.parentElement;\n if (parent) {\n for (const child of parent.children) {\n if (child === element) break;\n if (child.tagName === element.tagName) siblingIdx++;\n }\n }\n\n // Hash stable attributes\n const attrs: string[] = [];\n for (const attr of STABLE_ATTRS) {\n const val = element.getAttribute(attr);\n if (val) attrs.push(`${attr}=${val}`);\n }\n const attrHash = attrs.length > 0 ? djb2(attrs.join(\",\")) : \"0\";\n\n return `${childCount}:${siblingIdx}:${attrHash}`;\n}\n\n/**\n * Score how well a candidate element matches a stored fingerprint.\n * Returns 0–1.\n *\n * Weights:\n * - Child count match: 0.2 (tolerant — ±2 gets partial credit)\n * - Sibling index match: 0.4 (positional — most discriminating)\n * - Attribute hash match: 0.4 (identity — exact or nothing)\n */\nexport function scoreFingerprint(candidate: Element, storedFingerprint: string): number {\n const parts = storedFingerprint.split(\":\");\n if (parts.length !== 3) return 0;\n\n const [storedChildren, storedSibIdx, storedAttrHash] = parts;\n const storedChildCount = Number(storedChildren);\n const storedSibIndex = Number(storedSibIdx);\n if (Number.isNaN(storedChildCount) || Number.isNaN(storedSibIndex)) return 0;\n\n const candidateFp = generateFingerprint(candidate);\n const [candChildren, candSibIdx, candAttrHash] = candidateFp.split(\":\");\n\n let score = 0;\n\n // Child count (0.2)\n const childDiff = Math.abs(Number(candChildren) - storedChildCount);\n if (childDiff === 0) score += 0.2;\n else if (childDiff <= 2) score += 0.1;\n else if (childDiff <= 5) score += 0.03;\n\n // Sibling index (0.4)\n const sibDiff = Math.abs(Number(candSibIdx) - storedSibIndex);\n if (sibDiff === 0) score += 0.4;\n else if (sibDiff === 1) score += 0.2;\n else if (sibDiff <= 3) score += 0.08;\n\n // Attribute hash (0.4)\n if (candAttrHash === storedAttrHash) score += 0.4;\n\n return score;\n}\n","/**\n * Shared text-context helpers for DOM anchoring.\n * Used by both anchor generation (anchor.ts) and resolution (resolver.ts).\n */\n\n/**\n * Extract ~32 chars of text from the nearest sibling with content.\n * Walks up to 3 siblings in the given direction.\n */\nexport function adjacentText(element: Element, direction: \"before\" | \"after\"): string {\n const prop = direction === \"before\" ? \"previousElementSibling\" : \"nextElementSibling\";\n let sibling: Element | null = element[prop];\n let attempts = 3;\n\n while (sibling && attempts > 0) {\n const text = sibling.textContent?.trim();\n if (text) {\n return direction === \"before\" ? text.slice(-32) : text.slice(0, 32);\n }\n sibling = sibling[prop];\n attempts--;\n }\n\n return \"\";\n}\n\n/** Collect text from immediate siblings for disambiguation context. */\nexport function neighborText(element: Element): string {\n const prev = element.previousElementSibling?.textContent?.trim().slice(0, 40) ?? \"\";\n const next = element.nextElementSibling?.textContent?.trim().slice(0, 40) ?? \"\";\n return [prev, next].filter(Boolean).join(\" | \");\n}\n","/**\n * Generate an optimized XPath for a DOM element.\n *\n * Strategy:\n * - If the element has a unique id → //tag[@id='value']\n * - Otherwise, walk up the tree building /tag[position] segments\n * until we hit an ancestor with an id or reach <body>\n * - Cap depth at 6 levels to keep paths short\n */\nexport function generateXPath(element: Element): string {\n if (element.id) {\n const safeId = element.id.includes(\"'\") ? `concat('${element.id.replace(/'/g, \"',\\\"'\\\",'\")}')` : `'${element.id}'`;\n return `//${element.localName}[@id=${safeId}]`;\n }\n\n const segments: string[] = [];\n let current: Element | null = element;\n\n while (current && current !== document.body && segments.length < 6) {\n const tag = current.localName;\n const parent: Element | null = current.parentElement;\n\n if (current.id) {\n const safeId = current.id.includes(\"'\")\n ? `concat('${current.id.replace(/'/g, \"',\\\"'\\\",'\")}')`\n : `'${current.id}'`;\n segments.unshift(`/${tag}[@id=${safeId}]`);\n return \"/\" + segments.join(\"\");\n }\n\n // Compute position among same-tag siblings\n let position = 1;\n if (parent) {\n for (const sibling of parent.children) {\n if (sibling === current) break;\n if (sibling.localName === tag) position++;\n }\n }\n\n segments.unshift(`/${tag}[${position}]`);\n current = parent;\n }\n\n return \"/html/body\" + segments.join(\"\");\n}\n","import { finder } from \"@medv/finder\";\nimport type { AnchorData, RectData } from \"@siteping/core\";\nimport { generateFingerprint } from \"./fingerprint.js\";\nimport { adjacentText, neighborText } from \"./text-context.js\";\nimport { generateXPath } from \"./xpath.js\";\n\n/**\n * Generate a multi-selector anchor for a DOM element.\n *\n * Uses three complementary strategies (Hypothesis-inspired):\n * 1. CSS selector via @medv/finder (primary — fast, compact)\n * 2. XPath (fallback — survives class changes)\n * 3. Text snippet (fallback — survives structural changes)\n */\nexport function generateAnchor(element: Element): AnchorData {\n const cssSelector = finder(element, {\n // Filter out CSS-in-JS hashed class names\n className: (name: string) => !/^(css|sc|emotion|styled)-/.test(name) && !/^[a-z]{1,3}[A-Za-z0-9]{4,8}$/.test(name),\n // Prefer stable attributes\n attr: (name: string) => [\"data-testid\", \"data-id\", \"role\", \"aria-label\"].includes(name),\n // Exclude framework-generated dynamic IDs\n idName: (name: string) => !name.startsWith(\"radix-\") && !/^:r[0-9]+:$/.test(name),\n seedMinLength: 3,\n optimizedMinLength: 2,\n });\n\n const xpath = generateXPath(element);\n\n const rawText = element.textContent?.trim() ?? \"\";\n const textSnippet = rawText.slice(0, 120);\n\n const textPrefix = adjacentText(element, \"before\");\n const textSuffix = adjacentText(element, \"after\");\n const fingerprint = generateFingerprint(element);\n const neighbor = neighborText(element);\n\n return {\n cssSelector,\n xpath,\n textSnippet,\n textPrefix,\n textSuffix,\n fingerprint,\n neighborText: neighbor,\n elementTag: element.tagName,\n elementId: element.id || undefined,\n };\n}\n\n/**\n * Find the smallest DOM element whose bounding box fully contains the drawn rectangle.\n *\n * Walks up from the element at the rect's center until an ancestor contains the\n * full rectangle. Falls back to `document.body` when no ancestor matches — without\n * this fallback, callers compute percentages against a too-small anchor and produce\n * out-of-range values (negative / > 1).\n */\nexport function findAnchorElement(rect: DOMRect, root: Element = document.documentElement): Element {\n const centerX = rect.x + rect.width / 2;\n const centerY = rect.y + rect.height / 2;\n\n const elementAtCenter = document.elementFromPoint(centerX, centerY);\n if (!elementAtCenter || elementAtCenter === root) return document.body;\n\n let current: Element | null = elementAtCenter;\n while (current && current !== document.body) {\n const bounds = current.getBoundingClientRect();\n if (\n bounds.left <= rect.x &&\n bounds.top <= rect.y &&\n bounds.right >= rect.x + rect.width &&\n bounds.bottom >= rect.y + rect.height\n ) {\n return current;\n }\n current = current.parentElement;\n }\n\n return document.body;\n}\n\n/**\n * Convert absolute rectangle coordinates to percentages\n * relative to an anchor element's bounding box.\n */\nexport function rectToPercentages(rect: DOMRect, anchorBounds: DOMRect): RectData {\n // Guard against zero-dimension anchors (collapsed/hidden elements)\n if (anchorBounds.width <= 0 || anchorBounds.height <= 0) {\n return { xPct: 0, yPct: 0, wPct: 1, hPct: 1 };\n }\n return {\n xPct: (rect.x - anchorBounds.x) / anchorBounds.width,\n yPct: (rect.y - anchorBounds.y) / anchorBounds.height,\n wPct: rect.width / anchorBounds.width,\n hPct: rect.height / anchorBounds.height,\n };\n}\n","/**\n * Safe DOM creation utilities.\n * All user content is set via textContent (never innerHTML).\n * SVG icons use a DOMParser for trusted static strings.\n */\n\n/**\n * Parse a trusted SVG string into an SVGElement.\n * Only use with hardcoded icon constants — never with user input.\n * Uses createContextualFragment for native document-context parsing\n * (DOMParser creates nodes in a foreign document that don't render in Shadow DOM).\n */\nexport function parseSvg(svgString: string): SVGSVGElement {\n const range = document.createRange();\n const fragment = range.createContextualFragment(svgString);\n const svg = fragment.firstElementChild;\n if (!svg || svg.nodeName.toLowerCase() !== \"svg\") {\n throw new Error(\"[siteping] Invalid SVG string\");\n }\n // Safety: strip any event handlers in case of accidental misuse\n for (const attr of [...svg.attributes]) {\n if (attr.name.startsWith(\"on\")) svg.removeAttribute(attr.name);\n }\n // Also strip from all descendants\n for (const el of svg.querySelectorAll(\"*\")) {\n for (const attr of [...el.attributes]) {\n if (attr.name.startsWith(\"on\")) el.removeAttribute(attr.name);\n }\n }\n return svg as SVGSVGElement;\n}\n\n/** Create an element with optional class and style */\nexport function el(tag: string, attrs?: Record<string, string>): HTMLElement {\n const element = document.createElement(tag);\n if (attrs) {\n for (const [key, value] of Object.entries(attrs)) {\n if (key === \"class\") {\n element.className = value;\n } else if (key === \"style\") {\n element.style.cssText = value;\n } else {\n element.setAttribute(key, value);\n }\n }\n }\n return element;\n}\n\n/** Set text content safely (no HTML injection possible) */\nexport function setText(element: HTMLElement | SVGElement, text: string): void {\n element.textContent = text;\n}\n\n/** Format a relative date string using Intl.RelativeTimeFormat for locale support */\nexport function formatRelativeDate(isoString: string, locale = \"en\"): string {\n const diff = Date.now() - new Date(isoString).getTime();\n const seconds = Math.floor(diff / 1000);\n\n if (seconds < 60) {\n return new Intl.RelativeTimeFormat(locale, { numeric: \"auto\" }).format(0, \"second\");\n }\n\n const rtf = new Intl.RelativeTimeFormat(locale, { numeric: \"always\", style: \"narrow\" });\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) return rtf.format(-minutes, \"minute\");\n\n const hours = Math.floor(minutes / 60);\n if (hours < 24) return rtf.format(-hours, \"hour\");\n\n const days = Math.floor(hours / 24);\n if (days < 7) return rtf.format(-days, \"day\");\n\n return new Date(isoString).toLocaleDateString(locale);\n}\n","/** SVG icon strings for the widget UI. Kept as template strings to avoid DOM parsing overhead. */\n\nexport const ICON_SITEPING = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/><circle cx=\"12\" cy=\"10\" r=\"1\" fill=\"currentColor\" stroke=\"none\"/><circle cx=\"8\" cy=\"10\" r=\"1\" fill=\"currentColor\" stroke=\"none\"/><circle cx=\"16\" cy=\"10\" r=\"1\" fill=\"currentColor\" stroke=\"none\"/></svg>`;\n\nexport const ICON_CHAT = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/></svg>`;\n\nexport const ICON_ANNOTATE = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/><path d=\"M3 9h18\"/><path d=\"M9 3v18\"/></svg>`;\n\nexport const ICON_EYE = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><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 ICON_EYE_OFF = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><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.94\"/><path d=\"M9.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.19\"/><line x1=\"1\" y1=\"1\" x2=\"23\" y2=\"23\"/></svg>`;\n\nexport const ICON_CLOSE = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/></svg>`;\n\nexport const ICON_SEARCH = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><circle cx=\"11\" cy=\"11\" r=\"8\"/><line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"/></svg>`;\n\nexport const ICON_CHECK = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"20 6 9 17 4 12\"/></svg>`;\n\nexport const ICON_QUESTION = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3\"/><line x1=\"12\" y1=\"17\" x2=\"12.01\" y2=\"17\"/></svg>`;\n\nexport const ICON_CHANGE = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\"/><path d=\"M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z\"/></svg>`;\n\nexport const ICON_BUG = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"8\" y=\"6\" width=\"8\" height=\"14\" rx=\"4\"/><path d=\"M19 9h2\"/><path d=\"M3 9h2\"/><path d=\"M19 13h2\"/><path d=\"M3 13h2\"/><path d=\"M19 17h2\"/><path d=\"M3 17h2\"/><path d=\"M10 2h4\"/></svg>`;\n\nexport const ICON_OTHER = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"1\"/><circle cx=\"19\" cy=\"12\" r=\"1\"/><circle cx=\"5\" cy=\"12\" r=\"1\"/></svg>`;\n\nexport const ICON_LAYERS = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polygon points=\"12 2 2 7 12 12 22 7 12 2\"/><polyline points=\"2 17 12 22 22 17\"/><polyline points=\"2 12 12 17 22 12\"/></svg>`;\n\nexport const ICON_DOT_OPEN = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><circle cx=\"12\" cy=\"12\" r=\"3\" fill=\"currentColor\" stroke=\"none\"/></svg>`;\n\nexport const ICON_CHEVRON_DOWN = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"6 9 12 15 18 9\"/></svg>`;\n\nexport const ICON_UNDO = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"1 4 1 10 7 10\"/><path d=\"M3.51 15a9 9 0 1 0 2.13-9.36L1 10\"/></svg>`;\n\nexport const ICON_TRASH = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"3 6 5 6 21 6\"/><path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/><line x1=\"10\" y1=\"11\" x2=\"10\" y2=\"17\"/><line x1=\"14\" y1=\"11\" x2=\"14\" y2=\"17\"/></svg>`;\n","/** Color palette and glassmorphism tokens derived from the accent color */\nexport interface ThemeColors {\n accent: string;\n accentLight: string;\n accentDark: string;\n accentGlow: string;\n accentGradient: string;\n bg: string;\n bgHover: string;\n text: string;\n textSecondary: string;\n textTertiary: string;\n border: string;\n shadow: string;\n // Glass tokens\n glassBg: string;\n glassBgHeavy: string;\n glassBorder: string;\n glassBorderSubtle: string;\n // Feedback type colors\n typeQuestion: string;\n typeChange: string;\n typeBug: string;\n typeOther: string;\n // Soft type backgrounds (pastel)\n typeQuestionBg: string;\n typeChangeBg: string;\n typeBugBg: string;\n typeOtherBg: string;\n // Status filter colors\n statusOpen: string;\n statusOpenBg: string;\n statusResolved: string;\n statusResolvedBg: string;\n}\n\nconst DEFAULT_ACCENT = \"#0066ff\";\nconst HEX6_RE = /^#[0-9a-fA-F]{6}$/;\nconst HEX3_RE = /^#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])$/;\nconst HEX8_RE = /^#[0-9a-fA-F]{8}$/;\n\n/**\n * Normalize an accent color to a 6-digit hex string.\n *\n * **Only hex formats are accepted:**\n * - `#RGB` (3-digit shorthand, expanded to 6-digit)\n * - `#RRGGBB` (standard 6-digit)\n * - `#RRGGBBAA` (8-digit with alpha, alpha is stripped)\n *\n * Any other CSS color format (named colors like `\"red\"`, `hsl()`, `rgb()`,\n * `oklch()`, etc.) is **not** supported and will fall back to the default\n * accent color with a console warning.\n */\nfunction normalizeHex(raw: string): string {\n if (HEX6_RE.test(raw)) return raw;\n const short = HEX3_RE.test(raw) ? raw.match(HEX3_RE) : null;\n if (short) return `#${short[1]}${short[1]}${short[2]}${short[2]}${short[3]}${short[3]}`;\n if (HEX8_RE.test(raw)) return raw.slice(0, 7);\n\n console.warn(\n `[siteping] Invalid accentColor \"${raw}\" — only hex colors (#RGB, #RRGGBB, #RRGGBBAA) are supported. Using default.`,\n );\n return DEFAULT_ACCENT;\n}\n\n/** Darken a hex color by a percentage (0-1) */\nfunction darkenHex(hex: string, amount: number): string {\n const r = Math.max(0, Math.round(parseInt(hex.slice(1, 3), 16) * (1 - amount)));\n const g = Math.max(0, Math.round(parseInt(hex.slice(3, 5), 16) * (1 - amount)));\n const b = Math.max(0, Math.round(parseInt(hex.slice(5, 7), 16) * (1 - amount)));\n return `#${r.toString(16).padStart(2, \"0\")}${g.toString(16).padStart(2, \"0\")}${b.toString(16).padStart(2, \"0\")}`;\n}\n\n/** Detect if user prefers dark mode via media query */\nfunction prefersDark(): boolean {\n if (typeof window === \"undefined\") return false;\n return window.matchMedia(\"(prefers-color-scheme: dark)\").matches;\n}\n\n/** Resolve 'auto' theme to 'light' or 'dark' based on system preference */\nexport function resolveTheme(theme?: \"light\" | \"dark\" | \"auto\"): \"light\" | \"dark\" {\n if (theme === \"dark\") return \"dark\";\n if (theme === \"auto\") return prefersDark() ? \"dark\" : \"light\";\n return \"light\";\n}\n\nexport function buildThemeColors(accent: string = DEFAULT_ACCENT, theme?: \"light\" | \"dark\" | \"auto\"): ThemeColors {\n const hex = normalizeHex(accent);\n const dark = darkenHex(hex, 0.15);\n const resolved = resolveTheme(theme);\n\n if (resolved === \"dark\") {\n return {\n accent: hex,\n accentLight: hex + \"22\", // slightly more visible on dark bg\n accentDark: dark,\n accentGlow: hex + \"44\",\n accentGradient: `linear-gradient(135deg, ${hex}, ${dark})`,\n bg: \"#0f172a\",\n bgHover: \"#1e293b\",\n text: \"#f1f5f9\",\n textSecondary: \"#94a3b8\",\n textTertiary: \"#64748b\",\n border: \"#334155\",\n shadow: \"rgba(0, 0, 0, 0.3)\",\n // Glass tokens — dark frosted glass\n glassBg: \"rgba(15, 23, 42, 0.78)\",\n glassBgHeavy: \"rgba(15, 23, 42, 0.88)\",\n glassBorder: \"rgba(51, 65, 85, 0.5)\",\n glassBorderSubtle: \"rgba(51, 65, 85, 0.3)\",\n // Type colors stay vibrant on dark\n typeQuestion: \"#60a5fa\",\n typeChange: \"#fbbf24\",\n typeBug: \"#f87171\",\n typeOther: \"#94a3b8\",\n // Dark pastel backgrounds\n typeQuestionBg: \"rgba(59, 130, 246, 0.15)\",\n typeChangeBg: \"rgba(245, 158, 11, 0.15)\",\n typeBugBg: \"rgba(239, 68, 68, 0.15)\",\n typeOtherBg: \"rgba(100, 116, 139, 0.15)\",\n // Status colors — vivid green / cool gray on dark\n statusOpen: \"#4ade80\",\n statusOpenBg: \"rgba(74, 222, 128, 0.15)\",\n statusResolved: \"#94a3b8\",\n statusResolvedBg: \"rgba(148, 163, 184, 0.15)\",\n };\n }\n\n return {\n accent: hex,\n accentLight: hex + \"14\", // 8% opacity\n accentDark: dark,\n accentGlow: hex + \"33\", // 20% opacity\n accentGradient: `linear-gradient(135deg, ${hex}, ${dark})`,\n bg: \"#ffffff\",\n bgHover: \"#f8f9fb\",\n text: \"#0f172a\",\n textSecondary: \"#475569\",\n textTertiary: \"#64748b\",\n border: \"#e2e8f0\",\n shadow: \"rgba(0, 0, 0, 0.06)\",\n // Glass tokens\n glassBg: \"rgba(255, 255, 255, 0.72)\",\n glassBgHeavy: \"rgba(255, 255, 255, 0.85)\",\n glassBorder: \"rgba(255, 255, 255, 0.35)\",\n glassBorderSubtle: \"rgba(255, 255, 255, 0.18)\",\n // Vibrant type colors\n typeQuestion: \"#3b82f6\",\n typeChange: \"#b45309\",\n typeBug: \"#ef4444\",\n typeOther: \"#64748b\",\n // Pastel backgrounds\n typeQuestionBg: \"#eff6ff\",\n typeChangeBg: \"#fffbeb\",\n typeBugBg: \"#fef2f2\",\n typeOtherBg: \"#f8fafc\",\n // Status colors — saturated green / cool gray on light\n statusOpen: \"#16a34a\",\n statusOpenBg: \"#f0fdf4\",\n statusResolved: \"#64748b\",\n statusResolvedBg: \"#f1f5f9\",\n };\n}\n\nexport function getTypeColor(type: string, colors: ThemeColors): string {\n switch (type) {\n case \"question\":\n return colors.typeQuestion;\n case \"change\":\n return colors.typeChange;\n case \"bug\":\n return colors.typeBug;\n default:\n return colors.typeOther;\n }\n}\n\nexport function getTypeBgColor(type: string, colors: ThemeColors): string {\n switch (type) {\n case \"question\":\n return colors.typeQuestionBg;\n case \"change\":\n return colors.typeChangeBg;\n case \"bug\":\n return colors.typeBugBg;\n default:\n return colors.typeOtherBg;\n }\n}\n\nexport function cssVariables(colors: ThemeColors): string {\n return `\n --sp-accent: ${colors.accent};\n --sp-accent-light: ${colors.accentLight};\n --sp-accent-dark: ${colors.accentDark};\n --sp-accent-glow: ${colors.accentGlow};\n --sp-accent-gradient: ${colors.accentGradient};\n --sp-bg: ${colors.bg};\n --sp-bg-hover: ${colors.bgHover};\n --sp-text: ${colors.text};\n --sp-text-secondary: ${colors.textSecondary};\n --sp-text-tertiary: ${colors.textTertiary};\n --sp-border: ${colors.border};\n --sp-shadow: ${colors.shadow};\n --sp-glass-bg: ${colors.glassBg};\n --sp-glass-bg-heavy: ${colors.glassBgHeavy};\n --sp-glass-border: ${colors.glassBorder};\n --sp-glass-border-subtle: ${colors.glassBorderSubtle};\n --sp-type-question: ${colors.typeQuestion};\n --sp-type-change: ${colors.typeChange};\n --sp-type-bug: ${colors.typeBug};\n --sp-type-other: ${colors.typeOther};\n --sp-type-question-bg: ${colors.typeQuestionBg};\n --sp-type-change-bg: ${colors.typeChangeBg};\n --sp-type-bug-bg: ${colors.typeBugBg};\n --sp-type-other-bg: ${colors.typeOtherBg};\n --sp-radius: 12px;\n --sp-radius-lg: 16px;\n --sp-radius-xl: 20px;\n --sp-radius-full: 9999px;\n --sp-blur: 20px;\n --sp-blur-heavy: 32px;\n --sp-shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.04);\n --sp-shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.05), 0 1px 2px rgba(0, 0, 0, 0.04);\n --sp-shadow-md: 0 4px 16px rgba(0, 0, 0, 0.08), 0 2px 4px rgba(0, 0, 0, 0.04);\n --sp-shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.1), 0 4px 8px rgba(0, 0, 0, 0.04);\n --sp-shadow-xl: 0 16px 48px rgba(0, 0, 0, 0.12), 0 8px 16px rgba(0, 0, 0, 0.06);\n --sp-font: \"Inter\", system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif;\n `;\n}\n","import type { FeedbackType } from \"@siteping/core\";\nimport { Z_INDEX_MAX } from \"./constants.js\";\nimport { el, parseSvg, setText } from \"./dom-utils.js\";\nimport type { TFunction } from \"./i18n/index.js\";\nimport { ICON_BUG, ICON_CHANGE, ICON_OTHER, ICON_QUESTION } from \"./icons.js\";\nimport { getTypeBgColor, getTypeColor, type ThemeColors } from \"./styles/theme.js\";\n\ninterface PopupResult {\n type: FeedbackType;\n message: string;\n}\n\ninterface TypeOption {\n type: FeedbackType;\n label: string;\n icon: string;\n}\n\n/**\n * Popup form shown after drawing an annotation rectangle.\n *\n * Glassmorphism design: frosted glass background, soft shadows,\n * pill-shaped type buttons, gradient submit button.\n * Lives outside Shadow DOM.\n */\nexport class Popup {\n private root: HTMLElement;\n private selectedType: FeedbackType | null = null;\n private textarea: HTMLTextAreaElement;\n private submitBtn: HTMLButtonElement;\n private resolve: ((result: PopupResult | null) => void) | null = null;\n private previouslyFocused: HTMLElement | null = null;\n private onKeydownTrap: ((e: KeyboardEvent) => void) | null = null;\n\n constructor(\n private readonly colors: ThemeColors,\n private readonly t: TFunction,\n ) {\n this.root = el(\"div\", {\n style: `\n position:fixed;\n z-index:${Z_INDEX_MAX};\n width:300px;\n padding:16px;\n border-radius:16px;\n background:${this.colors.glassBg};\n backdrop-filter:blur(24px);\n -webkit-backdrop-filter:blur(24px);\n border:1px solid ${this.colors.glassBorder};\n box-shadow:0 8px 32px ${this.colors.shadow}, 0 2px 8px ${this.colors.shadow};\n font-family:\"Inter\",system-ui,-apple-system,sans-serif;\n opacity:0;\n transform:translateY(8px) scale(0.98);\n transition:opacity 0.25s cubic-bezier(0.16, 1, 0.3, 1),transform 0.25s cubic-bezier(0.16, 1, 0.3, 1);\n display:none;\n -webkit-font-smoothing:antialiased;\n `,\n });\n\n this.root.setAttribute(\"role\", \"dialog\");\n this.root.setAttribute(\"aria-modal\", \"true\");\n this.root.setAttribute(\"aria-label\", this.t(\"popup.ariaLabel\"));\n\n // Type selector grid (2x2)\n const typeOptions: TypeOption[] = [\n { type: \"question\", label: this.t(\"type.question\"), icon: ICON_QUESTION },\n { type: \"change\", label: this.t(\"type.change\"), icon: ICON_CHANGE },\n { type: \"bug\", label: this.t(\"type.bug\"), icon: ICON_BUG },\n { type: \"other\", label: this.t(\"type.other\"), icon: ICON_OTHER },\n ];\n const typeRow = el(\"div\", { style: \"display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-bottom:12px;\" });\n for (const option of typeOptions) {\n const btn = document.createElement(\"button\");\n btn.style.cssText = `\n height:44px;\n border-radius:9999px;border:1px solid ${this.colors.border};\n background:${this.colors.glassBg};cursor:pointer;\n display:flex;align-items:center;justify-content:center;gap:5px;\n font-family:\"Inter\",system-ui,-apple-system,sans-serif;\n font-size:13px;font-weight:500;color:${this.colors.textTertiary};\n transition:all 0.2s ease;\n padding:0 12px;\n `;\n const icon = parseSvg(option.icon);\n icon.setAttribute(\"style\", \"width:13px;height:13px;flex-shrink:0;\");\n btn.appendChild(icon);\n const labelSpan = document.createElement(\"span\");\n setText(labelSpan, option.label);\n btn.appendChild(labelSpan);\n btn.dataset.type = option.type;\n btn.setAttribute(\"aria-pressed\", \"false\");\n\n btn.addEventListener(\"click\", () => {\n this.selectType(option.type, typeRow);\n });\n\n btn.addEventListener(\"mouseenter\", () => {\n if (btn.dataset.type !== this.selectedType) {\n const bgColor = getTypeBgColor(btn.dataset.type ?? \"\", this.colors);\n btn.style.background = bgColor;\n btn.style.borderColor = getTypeColor(btn.dataset.type ?? \"\", this.colors) + \"40\";\n }\n });\n\n btn.addEventListener(\"mouseleave\", () => {\n if (btn.dataset.type !== this.selectedType) {\n btn.style.background = this.colors.glassBg;\n btn.style.borderColor = this.colors.border;\n }\n });\n\n typeRow.appendChild(btn);\n }\n\n // Textarea\n this.textarea = document.createElement(\"textarea\");\n this.textarea.style.cssText = `\n width:100%;min-height:72px;max-height:152px;\n padding:10px 12px;border-radius:12px;\n border:1px solid ${this.colors.border};\n background:${this.colors.glassBgHeavy};\n color:${this.colors.text};font-family:\"Inter\",system-ui,-apple-system,sans-serif;\n font-size:13px;line-height:1.5;resize:vertical;\n outline:none;transition:all 0.2s ease;\n box-sizing:border-box;\n `;\n this.textarea.placeholder = this.t(\"popup.placeholder\");\n this.textarea.maxLength = 5000;\n this.textarea.setAttribute(\"aria-label\", this.t(\"popup.textareaAria\"));\n\n // Keyboard shortcut hint\n const hint = el(\"div\", {\n style: `\n font-size:11px;color:${this.colors.textTertiary};\n text-align:right;margin-top:4px;\n font-family:\"Inter\",system-ui,-apple-system,sans-serif;\n letter-spacing:0.01em;\n `,\n });\n // navigator.userAgentData is preferred; navigator.platform is deprecated\n // but still needed as fallback. If both are unavailable, fall back to user agent string parsing.\n const uaData = (navigator as Navigator & { userAgentData?: { platform?: string } }).userAgentData;\n const isMac = uaData\n ? uaData.platform === \"macOS\"\n : (navigator.platform?.includes(\"Mac\") ?? /Macintosh|Mac OS X/i.test(navigator.userAgent));\n setText(hint, isMac ? this.t(\"popup.submitHintMac\") : this.t(\"popup.submitHintOther\"));\n\n this.textarea.addEventListener(\"focus\", () => {\n this.textarea.style.borderColor = this.colors.accent;\n this.textarea.style.boxShadow = `0 0 0 3px ${this.colors.accent}14`;\n this.textarea.style.background = this.colors.bg;\n });\n this.textarea.addEventListener(\"blur\", () => {\n this.textarea.style.borderColor = this.colors.border;\n this.textarea.style.boxShadow = \"none\";\n this.textarea.style.background = this.colors.glassBgHeavy;\n });\n this.textarea.addEventListener(\"input\", () => {\n this.updateSubmitState();\n });\n this.textarea.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Enter\" && (e.ctrlKey || e.metaKey)) {\n e.preventDefault();\n this.submit();\n }\n if (e.key === \"Escape\") {\n this.cancel();\n }\n });\n\n // Button row\n const btnRow = el(\"div\", { style: \"display:flex;justify-content:flex-end;gap:8px;margin-top:12px;\" });\n\n const cancelBtn = document.createElement(\"button\");\n cancelBtn.style.cssText = `\n height:34px;padding:0 16px;border-radius:9999px;\n border:1px solid ${this.colors.border};\n background:${this.colors.glassBg};\n color:${this.colors.textTertiary};font-family:\"Inter\",system-ui,-apple-system,sans-serif;\n font-size:13px;font-weight:500;cursor:pointer;\n transition:all 0.2s ease;\n `;\n setText(cancelBtn, this.t(\"popup.cancel\"));\n cancelBtn.addEventListener(\"click\", () => this.cancel());\n cancelBtn.addEventListener(\"mouseenter\", () => {\n cancelBtn.style.borderColor = this.colors.accent;\n cancelBtn.style.color = this.colors.accent;\n });\n cancelBtn.addEventListener(\"mouseleave\", () => {\n cancelBtn.style.borderColor = this.colors.border;\n cancelBtn.style.color = this.colors.textTertiary;\n });\n\n this.submitBtn = document.createElement(\"button\");\n this.submitBtn.style.cssText = `\n height:34px;padding:0 18px;border-radius:9999px;\n border:none;background:${this.colors.accentGradient};\n color:#fff;font-family:\"Inter\",system-ui,-apple-system,sans-serif;\n font-size:13px;font-weight:600;cursor:pointer;\n opacity:0.35;pointer-events:none;\n transition:all 0.2s ease;\n box-shadow:0 2px 8px ${this.colors.accentGlow};\n `;\n setText(this.submitBtn, this.t(\"popup.submit\"));\n this.submitBtn.addEventListener(\"click\", () => this.submit());\n\n btnRow.appendChild(cancelBtn);\n btnRow.appendChild(this.submitBtn);\n\n this.root.appendChild(typeRow);\n this.root.appendChild(this.textarea);\n this.root.appendChild(hint);\n this.root.appendChild(btnRow);\n document.body.appendChild(this.root);\n }\n\n /**\n * Show the popup near a drawn rectangle and return the user's input.\n * Returns null if cancelled.\n */\n show(rectBounds: DOMRect): Promise<PopupResult | null> {\n return new Promise((resolve) => {\n this.resolve = resolve;\n this.selectedType = null;\n this.textarea.value = \"\";\n this.updateSubmitState();\n this.resetTypeButtons();\n\n // Save focus to restore on close\n this.previouslyFocused = document.activeElement as HTMLElement | null;\n\n // Position: bottom-left of rect, 8px below\n let top = rectBounds.bottom + 8;\n let left = rectBounds.left;\n\n // Collision: flip up if not enough space below\n if (top + 220 > window.innerHeight) {\n top = rectBounds.top - 220 - 8;\n }\n // Collision: flip right if not enough space on left\n if (left + 300 > window.innerWidth) {\n left = rectBounds.right - 300;\n }\n left = Math.max(8, left);\n top = Math.max(8, top);\n\n this.root.style.top = `${top}px`;\n this.root.style.left = `${left}px`;\n this.root.style.display = \"block\";\n\n // Install focus trap\n this.onKeydownTrap = (e: KeyboardEvent) => {\n if (e.key === \"Tab\") {\n const focusableEls = Array.from(\n this.root.querySelectorAll<HTMLElement>(\n 'button:not([disabled]), textarea, input, [tabindex]:not([tabindex=\"-1\"])',\n ),\n );\n if (focusableEls.length === 0) return;\n const first = focusableEls[0];\n const last = focusableEls[focusableEls.length - 1];\n if (!first || !last) return;\n if (e.shiftKey) {\n if (document.activeElement === first || !this.root.contains(document.activeElement)) {\n e.preventDefault();\n last.focus();\n }\n } else {\n if (document.activeElement === last || !this.root.contains(document.activeElement)) {\n e.preventDefault();\n first.focus();\n }\n }\n }\n };\n this.root.addEventListener(\"keydown\", this.onKeydownTrap);\n\n // Check prefers-reduced-motion live (not cached at construction time)\n const reduceMotion =\n typeof window !== \"undefined\" && window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n this.root.style.transition = reduceMotion ? \"none\" : \"\";\n\n // Trigger animation\n requestAnimationFrame(() => {\n this.root.style.opacity = \"1\";\n this.root.style.transform = \"translateY(0) scale(1)\";\n this.textarea.focus();\n });\n });\n }\n\n private selectType(type: FeedbackType, container: HTMLElement): void {\n this.selectedType = type;\n const buttons = container.querySelectorAll<HTMLButtonElement>(\"button\");\n for (const btn of buttons) {\n const isActive = btn.dataset.type === type;\n const color = getTypeColor(btn.dataset.type ?? \"\", this.colors);\n const bgColor = getTypeBgColor(btn.dataset.type ?? \"\", this.colors);\n btn.style.background = isActive ? bgColor : this.colors.glassBg;\n btn.style.borderColor = isActive ? color + \"60\" : this.colors.border;\n btn.style.color = isActive ? color : this.colors.textTertiary;\n btn.style.fontWeight = isActive ? \"600\" : \"500\";\n btn.setAttribute(\"aria-pressed\", String(isActive));\n }\n this.updateSubmitState();\n }\n\n private resetTypeButtons(): void {\n const buttons = this.root.querySelectorAll<HTMLButtonElement>(\"button[data-type]\");\n for (const btn of buttons) {\n btn.setAttribute(\"aria-pressed\", \"false\");\n btn.style.background = this.colors.glassBg;\n btn.style.borderColor = this.colors.border;\n btn.style.color = this.colors.textTertiary;\n btn.style.fontWeight = \"500\";\n }\n }\n\n private updateSubmitState(): void {\n const enabled = this.selectedType !== null && this.textarea.value.trim().length > 0;\n this.submitBtn.disabled = !enabled;\n this.submitBtn.style.opacity = enabled ? \"1\" : \"0.35\";\n this.submitBtn.style.pointerEvents = enabled ? \"auto\" : \"none\";\n }\n\n private submit(): void {\n if (!this.selectedType || !this.textarea.value.trim()) return;\n this.resolve?.({ type: this.selectedType, message: this.textarea.value.trim() });\n this.resolve = null;\n this.hideElement();\n }\n\n private cancel(): void {\n this.resolve?.(null);\n this.resolve = null;\n this.hideElement();\n }\n\n private hideElement(): void {\n // Remove focus trap\n if (this.onKeydownTrap) {\n this.root.removeEventListener(\"keydown\", this.onKeydownTrap);\n this.onKeydownTrap = null;\n }\n this.root.style.opacity = \"0\";\n this.root.style.transform = \"translateY(8px) scale(0.98)\";\n // Restore focus to the previously focused element\n this.previouslyFocused?.focus();\n this.previouslyFocused = null;\n setTimeout(() => {\n this.root.style.display = \"none\";\n }, 250);\n }\n\n destroy(): void {\n this.root.remove();\n }\n}\n","import type { AnnotationPayload, FeedbackType } from \"@siteping/core\";\nimport { Z_INDEX_MAX } from \"./constants.js\";\nimport { findAnchorElement, generateAnchor, rectToPercentages } from \"./dom/anchor.js\";\nimport { el, setText } from \"./dom-utils.js\";\nimport type { EventBus, WidgetEvents } from \"./events.js\";\nimport type { TFunction } from \"./i18n/index.js\";\nimport { Popup } from \"./popup.js\";\nimport type { ThemeColors } from \"./styles/theme.js\";\n\nexport interface AnnotationComplete {\n annotation: AnnotationPayload;\n type: FeedbackType;\n message: string;\n}\n\n/**\n * Annotation mode: full-page overlay with rectangle drawing.\n *\n * Glassmorphism design:\n * - Frosted glass toolbar at top\n * - Subtle tinted overlay\n * - Accent-colored drawing rectangle with glow\n */\nexport class Annotator {\n private overlay: HTMLElement | null = null;\n private toolbar: HTMLElement | null = null;\n private drawingRect: HTMLElement | null = null;\n private startX = 0;\n private startY = 0;\n private isDrawing = false;\n private isActive = false;\n private popup: Popup;\n private savedOverflow = \"\";\n private preActiveFocusElement: Element | null = null;\n private rafId: number | null = null;\n private pendingMoveEvent: MouseEvent | Touch | null = null;\n\n constructor(\n private readonly colors: ThemeColors,\n private readonly bus: EventBus<WidgetEvents>,\n private readonly t: TFunction,\n ) {\n this.popup = new Popup(colors, t);\n\n this.bus.on(\"annotation:start\", () => this.activate());\n }\n\n private activate(): void {\n if (this.isActive) return;\n this.isActive = true;\n\n // Capture the focused element before activation for keyboard annotation\n this.preActiveFocusElement = document.activeElement;\n\n // Lock page scroll\n this.savedOverflow = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n\n // Overlay — subtle blue tint for depth\n this.overlay = el(\"div\", {\n style: `\n position:fixed;inset:0;\n z-index:${Z_INDEX_MAX - 1};\n background:rgba(15, 23, 42, 0.04);\n cursor:crosshair;\n `,\n });\n this.overlay.setAttribute(\"aria-hidden\", \"true\");\n\n // Toolbar — glassmorphism bar\n this.toolbar = el(\"div\", {\n style: `\n position:fixed;top:0;left:0;right:0;\n z-index:${Z_INDEX_MAX};\n height:52px;\n background:${this.colors.glassBg};\n backdrop-filter:blur(24px);\n -webkit-backdrop-filter:blur(24px);\n border-bottom:1px solid ${this.colors.glassBorder};\n display:flex;align-items:center;justify-content:center;gap:16px;\n font-family:\"Inter\",system-ui,-apple-system,sans-serif;\n font-size:14px;color:${this.colors.text};\n box-shadow:0 4px 16px ${this.colors.shadow};\n -webkit-font-smoothing:antialiased;\n `,\n });\n\n const dot = el(\"span\", {\n style: `\n width:8px;height:8px;border-radius:50%;\n background:${this.colors.accent};\n box-shadow:0 0 8px ${this.colors.accentGlow};\n animation:pulse 1.5s ease-in-out infinite;\n `,\n });\n\n // Add pulse animation inline (respects prefers-reduced-motion)\n const style = document.createElement(\"style\");\n style.textContent = [\n \"@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.4}}\",\n \"@media(prefers-reduced-motion:reduce){@keyframes pulse{from,to{opacity:1}}}\",\n ].join(\"\");\n this.toolbar.appendChild(style);\n\n const instruction = el(\"span\", { style: \"font-weight:500;letter-spacing:-0.01em;\" });\n setText(instruction, this.t(\"annotator.instruction\"));\n\n const cancelBtn = document.createElement(\"button\");\n cancelBtn.style.cssText = `\n height:34px;padding:0 18px;border-radius:9999px;\n border:1px solid ${this.colors.border};\n background:${this.colors.glassBg};\n color:${this.colors.textTertiary};font-family:\"Inter\",system-ui,-apple-system,sans-serif;\n font-size:13px;font-weight:500;cursor:pointer;\n transition:all 0.2s ease;\n `;\n setText(cancelBtn, this.t(\"annotator.cancel\"));\n cancelBtn.addEventListener(\"click\", () => this.deactivate());\n cancelBtn.addEventListener(\"mouseenter\", () => {\n cancelBtn.style.borderColor = this.colors.typeBug;\n cancelBtn.style.color = this.colors.typeBug;\n cancelBtn.style.background = this.colors.typeBugBg;\n });\n cancelBtn.addEventListener(\"mouseleave\", () => {\n cancelBtn.style.borderColor = this.colors.border;\n cancelBtn.style.color = this.colors.textTertiary;\n cancelBtn.style.background = this.colors.glassBg;\n });\n\n this.toolbar.appendChild(dot);\n this.toolbar.appendChild(instruction);\n this.toolbar.appendChild(cancelBtn);\n\n // Mouse events\n this.overlay.addEventListener(\"mousedown\", this.onMouseDown);\n this.overlay.addEventListener(\"mousemove\", this.onMouseMove);\n this.overlay.addEventListener(\"mouseup\", this.onMouseUp);\n\n // Touch events (Surface Pro, iPad, etc.)\n this.overlay.addEventListener(\"touchstart\", this.onTouchStart, { passive: false });\n this.overlay.addEventListener(\"touchmove\", this.onTouchMove, { passive: false });\n this.overlay.addEventListener(\"touchend\", this.onTouchEnd);\n\n // Keyboard annotation: Enter selects the pre-activation focused element\n this.overlay.addEventListener(\"keydown\", this.onOverlayKeyDown);\n\n // Allow tab-through so keyboard users can reach underlying elements\n this.overlay.setAttribute(\"tabindex\", \"0\");\n\n // Escape to cancel\n document.addEventListener(\"keydown\", this.onKeyDown);\n\n document.body.appendChild(this.overlay);\n document.body.appendChild(this.toolbar);\n }\n\n private deactivate(): void {\n if (!this.isActive) return;\n this.isActive = false;\n this.isDrawing = false;\n this.preActiveFocusElement = null;\n\n // Cancel any pending rAF to prevent stale callbacks\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n this.pendingMoveEvent = null;\n\n document.body.style.overflow = this.savedOverflow;\n document.removeEventListener(\"keydown\", this.onKeyDown);\n\n this.overlay?.remove();\n this.toolbar?.remove();\n this.drawingRect?.remove();\n this.overlay = null;\n this.toolbar = null;\n this.drawingRect = null;\n\n this.bus.emit(\"annotation:end\");\n }\n\n private onKeyDown = (e: KeyboardEvent): void => {\n if (e.key === \"Escape\") this.deactivate();\n };\n\n /**\n * Keyboard annotation: pressing Enter while the overlay is active selects\n * the element that was focused before activation and creates a full-bounds\n * annotation covering that element (WCAG 2.1.1 Level A).\n */\n private onOverlayKeyDown = async (e: KeyboardEvent): Promise<void> => {\n if (e.key !== \"Enter\") return;\n e.preventDefault();\n\n const target = this.preActiveFocusElement;\n if (!target || !(target instanceof HTMLElement)) return;\n\n const bounds = target.getBoundingClientRect();\n if (bounds.width <= 0 || bounds.height <= 0) return;\n\n const rectBounds = new DOMRect(bounds.x, bounds.y, bounds.width, bounds.height);\n\n const result = await this.popup.show(rectBounds);\n if (!result) return;\n\n const anchor = generateAnchor(target);\n const annotation: AnnotationPayload = {\n anchor,\n rect: { xPct: 0, yPct: 0, wPct: 1, hPct: 1 },\n scrollX: window.scrollX,\n scrollY: window.scrollY,\n viewportW: window.innerWidth,\n viewportH: window.innerHeight,\n devicePixelRatio: window.devicePixelRatio,\n };\n\n this.deactivate();\n\n this.bus.emit(\"annotation:complete\", {\n annotation,\n type: result.type,\n message: result.message,\n });\n };\n\n private onMouseDown = (e: MouseEvent): void => {\n this.startDrawing(e.clientX, e.clientY);\n };\n\n private onTouchStart = (e: TouchEvent): void => {\n e.preventDefault();\n const touch = e.touches[0];\n if (touch) this.startDrawing(touch.clientX, touch.clientY);\n };\n\n private startDrawing(clientX: number, clientY: number): void {\n this.isDrawing = true;\n this.startX = clientX;\n this.startY = clientY;\n\n this.drawingRect?.remove();\n this.drawingRect = el(\"div\", {\n style: `\n position:fixed;\n border:2px solid ${this.colors.accent};\n background:${this.colors.accent}12;\n pointer-events:none;\n border-radius:8px;\n box-shadow:0 0 16px ${this.colors.accentGlow};\n transition:box-shadow 0.15s ease;\n `,\n });\n this.overlay?.appendChild(this.drawingRect);\n }\n\n private onMouseMove = (e: MouseEvent): void => {\n this.scheduleRectUpdate(e);\n };\n\n private onTouchMove = (e: TouchEvent): void => {\n e.preventDefault();\n if (e.touches[0]) this.scheduleRectUpdate(e.touches[0]);\n };\n\n private scheduleRectUpdate(source: MouseEvent | Touch): void {\n if (!this.isDrawing || !this.drawingRect) return;\n\n this.pendingMoveEvent = source;\n if (this.rafId !== null) return;\n\n this.rafId = requestAnimationFrame(() => {\n this.rafId = null;\n const evt = this.pendingMoveEvent;\n if (!evt || !this.drawingRect) return;\n\n const x = Math.min(evt.clientX, this.startX);\n const y = Math.min(evt.clientY, this.startY);\n const w = Math.abs(evt.clientX - this.startX);\n const h = Math.abs(evt.clientY - this.startY);\n\n this.drawingRect.style.left = `${x}px`;\n this.drawingRect.style.top = `${y}px`;\n this.drawingRect.style.width = `${w}px`;\n this.drawingRect.style.height = `${h}px`;\n });\n }\n\n private onTouchEnd = async (e: TouchEvent): Promise<void> => {\n const touch = e.changedTouches[0];\n if (touch) await this.finishDrawing(touch.clientX, touch.clientY);\n };\n\n private onMouseUp = async (e: MouseEvent): Promise<void> => {\n await this.finishDrawing(e.clientX, e.clientY);\n };\n\n private finishDrawing = async (clientX: number, clientY: number): Promise<void> => {\n if (!this.isDrawing || !this.drawingRect) return;\n this.isDrawing = false;\n\n const x = Math.min(clientX, this.startX);\n const y = Math.min(clientY, this.startY);\n const w = Math.abs(clientX - this.startX);\n const h = Math.abs(clientY - this.startY);\n\n // Ignore tiny rectangles (accidental clicks)\n if (w < 10 || h < 10) {\n this.drawingRect.remove();\n this.drawingRect = null;\n return;\n }\n\n const rectBounds = new DOMRect(x, y, w, h);\n\n // Show popup for type + message\n const result = await this.popup.show(rectBounds);\n\n if (!result) {\n this.drawingRect?.remove();\n this.drawingRect = null;\n return;\n }\n\n // Build annotation payload BEFORE deactivating (needs overlay for elementFromPoint)\n const annotation = this.buildAnnotation(rectBounds);\n this.drawingRect?.remove();\n this.drawingRect = null;\n this.deactivate();\n\n // Emit via event bus (not DOM — overlay is already null after deactivate)\n this.bus.emit(\"annotation:complete\", {\n annotation,\n type: result.type,\n message: result.message,\n });\n };\n\n /**\n * Build an AnnotationPayload from a drawn rectangle.\n * Temporarily hides the overlay to access the real DOM underneath.\n */\n private buildAnnotation(rectBounds: DOMRect): AnnotationPayload {\n // Temporarily hide overlay to find the real element underneath\n if (this.overlay) this.overlay.style.pointerEvents = \"none\";\n const anchorElement = findAnchorElement(rectBounds);\n if (this.overlay) this.overlay.style.pointerEvents = \"auto\";\n\n const anchor = generateAnchor(anchorElement);\n const anchorBounds = anchorElement.getBoundingClientRect();\n const rect = rectToPercentages(rectBounds, anchorBounds);\n\n return {\n anchor,\n rect,\n scrollX: window.scrollX,\n scrollY: window.scrollY,\n viewportW: window.innerWidth,\n viewportH: window.innerHeight,\n devicePixelRatio: window.devicePixelRatio,\n };\n }\n destroy(): void {\n this.deactivate();\n this.popup.destroy();\n }\n}\n","import type { FeedbackPayload, FeedbackResponse, FeedbackStatus, FeedbackType } from \"@siteping/core\";\n\n/**\n * Abstract client interface used by the widget internals.\n *\n * `ApiClient` (HTTP mode) and `StoreClient` (direct store mode) both satisfy\n * this interface, allowing the widget to work identically in either mode.\n */\nexport interface WidgetClient {\n sendFeedback(payload: FeedbackPayload): Promise<FeedbackResponse>;\n getFeedbacks(\n projectName: string,\n options?: { page?: number; limit?: number; type?: FeedbackType; status?: FeedbackStatus; search?: string },\n ): Promise<{ feedbacks: FeedbackResponse[]; total: number }>;\n resolveFeedback(id: string, resolved: boolean): Promise<FeedbackResponse>;\n deleteFeedback(id: string): Promise<void>;\n deleteAllFeedbacks(projectName: string): Promise<void>;\n}\n\nconst MAX_RETRIES = 3;\nconst TIMEOUT_MS = 10_000;\nconst RETRY_QUEUE_KEY = \"siteping_retry_queue\";\nconst MAX_QUEUE_SIZE = 20;\n\n// ---------------------------------------------------------------------------\n// Core fetch with retry + exponential backoff + jitter\n// ---------------------------------------------------------------------------\n\nasync function resilientFetch(url: string, init: RequestInit, retries = MAX_RETRIES): Promise<Response> {\n for (let attempt = 0; attempt <= retries; attempt++) {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS);\n\n try {\n const response = await fetch(url, {\n ...init,\n signal: controller.signal,\n });\n clearTimeout(timeout);\n\n // Don't retry client errors (4xx) — only server errors (5xx)\n if (response.ok || (response.status >= 400 && response.status < 500)) {\n return response;\n }\n\n if (attempt === retries) return response;\n } catch (error) {\n clearTimeout(timeout);\n if (attempt === retries) throw error;\n }\n\n // Exponential backoff with jitter: 1s, 2s, 4s + random ±500ms\n const baseDelay = 1000 * 2 ** attempt;\n const jitter = Math.random() * 1000 - 500;\n await new Promise((r) => setTimeout(r, baseDelay + jitter));\n }\n\n throw new Error(\"Max retries exceeded\");\n}\n\n// ---------------------------------------------------------------------------\n// Retry queue — persist failed feedbacks for retry on next page load\n// ---------------------------------------------------------------------------\n\ntype RetryEntry = { endpoint: string; payload: FeedbackPayload };\n\nconst LOCK_NAME = \"siteping_retry_queue\";\n\n/**\n * Acquire a Web Lock to serialize cross-tab access to the retry queue.\n * Falls back to running the callback without locking on older browsers.\n */\nasync function withRetryLock<T>(callback: () => T | Promise<T>): Promise<T> {\n if (typeof navigator !== \"undefined\" && navigator.locks) {\n return navigator.locks.request(LOCK_NAME, () => callback());\n }\n return callback();\n}\n\nfunction queueForRetry(endpoint: string, payload: FeedbackPayload): void {\n // Fire-and-forget — we don't want to block the caller on the lock\n void withRetryLock(() => {\n try {\n const raw = localStorage.getItem(RETRY_QUEUE_KEY);\n const parsed: unknown = raw ? JSON.parse(raw) : [];\n const queue: RetryEntry[] = Array.isArray(parsed) ? (parsed as RetryEntry[]) : [];\n\n // Cap queue size to prevent unbounded localStorage growth\n if (queue.length >= MAX_QUEUE_SIZE) {\n queue.shift(); // Drop oldest entry\n }\n\n queue.push({ endpoint, payload });\n localStorage.setItem(RETRY_QUEUE_KEY, JSON.stringify(queue));\n } catch {\n // localStorage full or unavailable — silently drop\n }\n });\n}\n\nexport async function flushRetryQueue(endpoint: string): Promise<void> {\n await withRetryLock(async () => {\n try {\n const raw = localStorage.getItem(RETRY_QUEUE_KEY);\n if (!raw) return;\n\n const parsed: unknown = JSON.parse(raw);\n const queue: RetryEntry[] = Array.isArray(parsed) ? (parsed as RetryEntry[]) : [];\n\n const toRetry = queue.filter((e) => e.endpoint === endpoint);\n if (toRetry.length === 0) return;\n\n // Process items sequentially to avoid overwhelming the server\n const failed: RetryEntry[] = [];\n for (const entry of toRetry) {\n try {\n const res = await fetch(endpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(entry.payload),\n });\n if (!res.ok) {\n failed.push(entry);\n }\n } catch {\n failed.push(entry);\n }\n }\n\n // Rebuild queue: keep unrelated entries + failed retries\n const remaining = queue.filter((e) => e.endpoint !== endpoint).concat(failed);\n if (remaining.length > 0) {\n localStorage.setItem(RETRY_QUEUE_KEY, JSON.stringify(remaining));\n } else {\n localStorage.removeItem(RETRY_QUEUE_KEY);\n }\n } catch {\n // Ignore — localStorage may be unavailable\n }\n });\n}\n\n// ---------------------------------------------------------------------------\n// API client\n// ---------------------------------------------------------------------------\n\nexport class ApiClient {\n constructor(\n private readonly endpoint: string,\n private readonly projectName: string,\n ) {}\n\n async sendFeedback(payload: FeedbackPayload): Promise<FeedbackResponse> {\n try {\n const response = await resilientFetch(this.endpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(payload),\n });\n\n if (!response.ok) {\n const text = await response.text().catch(() => \"Unknown error\");\n throw new Error(`Failed to send feedback: ${response.status} ${text}`);\n }\n\n return (await response.json()) as FeedbackResponse; // Server validates via Zod\n } catch (error) {\n queueForRetry(this.endpoint, payload);\n throw error;\n }\n }\n\n async getFeedbacks(\n projectName: string,\n options?: {\n page?: number;\n limit?: number;\n type?: FeedbackType;\n status?: FeedbackStatus;\n search?: string;\n },\n ): Promise<{ feedbacks: FeedbackResponse[]; total: number }> {\n const params = new URLSearchParams({ projectName });\n if (options?.page) params.set(\"page\", String(options.page));\n if (options?.limit) params.set(\"limit\", String(options.limit));\n if (options?.type) params.set(\"type\", options.type);\n if (options?.status) params.set(\"status\", options.status);\n if (options?.search) params.set(\"search\", options.search);\n\n const response = await resilientFetch(`${this.endpoint}?${params.toString()}`, {\n method: \"GET\",\n cache: \"no-store\",\n });\n\n if (!response.ok) {\n throw new Error(`Failed to fetch feedbacks: ${response.status}`);\n }\n\n return (await response.json()) as { feedbacks: FeedbackResponse[]; total: number }; // Server validates via Zod\n }\n\n async resolveFeedback(id: string, resolved: boolean): Promise<FeedbackResponse> {\n const response = await resilientFetch(this.endpoint, {\n method: \"PATCH\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ id, projectName: this.projectName, status: resolved ? \"resolved\" : \"open\" }),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to update feedback: ${response.status}`);\n }\n\n return (await response.json()) as FeedbackResponse; // Server validates via Zod\n }\n\n async deleteFeedback(id: string): Promise<void> {\n const response = await resilientFetch(this.endpoint, {\n method: \"DELETE\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ id, projectName: this.projectName }),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to delete feedback: ${response.status}`);\n }\n }\n\n async deleteAllFeedbacks(projectName: string): Promise<void> {\n const response = await resilientFetch(this.endpoint, {\n method: \"DELETE\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ projectName, deleteAll: true }),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to delete all feedbacks: ${response.status}`);\n }\n }\n}\n","type Listener = (...args: unknown[]) => void;\n\n/**\n * Lightweight typed EventEmitter — zero dependencies.\n */\nexport class EventBus<E extends { [K in keyof E]: unknown[] }> {\n private listeners = new Map<keyof E, Set<Listener>>();\n\n on<K extends keyof E>(event: K, listener: (...args: E[K]) => void): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n const set = this.listeners.get(event)!;\n set.add(listener as Listener);\n\n return () => {\n set.delete(listener as Listener);\n };\n }\n\n off<K extends keyof E>(event: K, listener: (...args: E[K]) => void): void {\n const set = this.listeners.get(event);\n if (set) {\n set.delete(listener as Listener);\n }\n }\n\n emit<K extends keyof E>(event: K, ...args: E[K]): void {\n const set = this.listeners.get(event);\n if (!set) return;\n for (const fn of set) {\n try {\n fn(...args);\n } catch (err) {\n // Isolate listener errors — one bad listener must not kill others\n console.error(`[siteping] Error in event listener for \"${String(event)}\":`, err);\n }\n }\n }\n\n removeAll(): void {\n this.listeners.clear();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Widget event types\n// ---------------------------------------------------------------------------\n\nexport interface WidgetEvents {\n open: [];\n close: [];\n \"feedback:sent\": [import(\"@siteping/core\").FeedbackResponse];\n \"feedback:deleted\": [string];\n \"feedback:all-deleted\": [];\n \"feedback:error\": [Error];\n \"annotation:start\": [];\n \"annotation:end\": [];\n \"annotation:complete\": [import(\"./annotator.js\").AnnotationComplete];\n \"annotations:toggle\": [boolean];\n \"panel:toggle\": [boolean];\n}\n\n/** Subset of WidgetEvents exposed to consumers via SitepingInstance */\nexport interface PublicWidgetEvents {\n \"feedback:sent\": [import(\"@siteping/core\").FeedbackResponse];\n \"feedback:deleted\": [string];\n \"panel:open\": [];\n \"panel:close\": [];\n}\n","import type { SitepingConfig } from \"@siteping/core\";\nimport { parseSvg, setText } from \"./dom-utils.js\";\nimport type { EventBus, WidgetEvents } from \"./events.js\";\nimport type { TFunction } from \"./i18n/index.js\";\nimport { ICON_ANNOTATE, ICON_CHAT, ICON_CLOSE, ICON_EYE, ICON_EYE_OFF, ICON_SITEPING } from \"./icons.js\";\n\ninterface RadialItem {\n id: string;\n icon: string;\n iconAlt?: string;\n label: string;\n}\n\nconst ITEM_GAP = 54;\n\n/**\n * Floating Action Button with radial menu and notification badge.\n *\n * Glassmorphism: gradient background, glow shadow, glass radial items.\n * Badge shows unresolved feedback count.\n */\nexport class Fab {\n private root: HTMLElement;\n private fab: HTMLButtonElement;\n private radialContainer: HTMLElement;\n private badgeEl: HTMLElement | null = null;\n private isOpen = false;\n private annotationsVisible = true;\n private items: RadialItem[];\n\n constructor(\n shadowRoot: ShadowRoot,\n config: SitepingConfig,\n private readonly bus: EventBus<WidgetEvents>,\n private readonly t: TFunction,\n ) {\n const position = config.position ?? \"bottom-right\";\n const isRight = position === \"bottom-right\";\n\n // Vertical stack above the FAB\n this.items = [\n { id: \"chat\", icon: ICON_CHAT, label: t(\"fab.messages\") },\n { id: \"annotate\", icon: ICON_ANNOTATE, label: t(\"fab.annotate\") },\n { id: \"toggle-annotations\", icon: ICON_EYE, iconAlt: ICON_EYE_OFF, label: t(\"fab.annotations\") },\n ];\n\n // FAB button — needs position:relative for badge positioning\n this.fab = document.createElement(\"button\");\n this.fab.className = `sp-fab sp-fab--${position} sp-anim-fab-in`;\n this.fab.style.position = \"fixed\"; // ensure fixed even with relative children\n this.fab.appendChild(parseSvg(ICON_SITEPING));\n this.fab.setAttribute(\"aria-label\", t(\"fab.aria\"));\n this.fab.setAttribute(\"aria-expanded\", \"false\");\n this.fab.addEventListener(\"click\", () => this.toggle());\n\n // Radial container\n this.radialContainer = document.createElement(\"div\");\n this.radialContainer.className = `sp-radial sp-radial--${position}`;\n this.radialContainer.setAttribute(\"role\", \"menu\");\n\n for (let i = 0; i < this.items.length; i++) {\n const item = this.items[i];\n if (!item) continue;\n const btn = document.createElement(\"button\");\n btn.className = \"sp-radial-item\";\n btn.style.setProperty(\"--sp-i\", String(i));\n btn.appendChild(parseSvg(item.icon));\n btn.setAttribute(\"role\", \"menuitem\");\n btn.setAttribute(\"aria-label\", item.label);\n btn.dataset.itemId = item.id;\n\n btn.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n this.handleItemClick(item.id);\n });\n\n const label = document.createElement(\"span\");\n label.className = \"sp-radial-label\";\n label.textContent = item.label;\n label.style.cssText = isRight\n ? \"position:absolute; right:54px; top:50%; transform:translateY(-50%); white-space:nowrap;\"\n : \"position:absolute; left:54px; top:50%; transform:translateY(-50%); white-space:nowrap;\";\n btn.appendChild(label);\n\n this.radialContainer.appendChild(btn);\n }\n\n this.root = document.createElement(\"div\");\n this.root.appendChild(this.radialContainer);\n this.root.appendChild(this.fab);\n shadowRoot.appendChild(this.root);\n\n // Close radial menu on click outside.\n const host = shadowRoot.host;\n this.onDocumentClick = (e: MouseEvent) => {\n if (this.isOpen && !e.composedPath().includes(host)) {\n this.close();\n }\n };\n document.addEventListener(\"click\", this.onDocumentClick);\n\n // Escape on FAB or menu container closes the menu\n const handleEscape = (e: KeyboardEvent) => {\n if (e.key === \"Escape\" && this.isOpen) {\n e.stopPropagation();\n this.close();\n }\n };\n this.fab.addEventListener(\"keydown\", handleEscape);\n this.radialContainer.addEventListener(\"keydown\", handleEscape);\n\n // Arrow key navigation within the radial menu\n this.radialContainer.addEventListener(\"keydown\", (e) => {\n const items = Array.from(this.radialContainer.querySelectorAll<HTMLButtonElement>(\".sp-radial-item\"));\n if (items.length === 0 || !this.isOpen) return;\n const activeEl = (shadowRoot.activeElement ?? document.activeElement) as HTMLElement;\n const currentIndex = items.indexOf(activeEl as HTMLButtonElement);\n\n switch (e.key) {\n case \"ArrowUp\": {\n e.preventDefault();\n const nextIndex = currentIndex <= 0 ? items.length - 1 : currentIndex - 1;\n items[nextIndex]?.focus();\n break;\n }\n case \"ArrowDown\": {\n e.preventDefault();\n const nextIndex = currentIndex >= items.length - 1 ? 0 : currentIndex + 1;\n items[nextIndex]?.focus();\n break;\n }\n case \"Home\": {\n e.preventDefault();\n items[0]?.focus();\n break;\n }\n case \"End\": {\n e.preventDefault();\n items[items.length - 1]?.focus();\n break;\n }\n }\n });\n }\n\n private onDocumentClick: (e: MouseEvent) => void;\n\n /** Update the badge count. Pass 0 to hide. */\n updateBadge(count: number): void {\n if (count <= 0) {\n this.badgeEl?.remove();\n this.badgeEl = null;\n return;\n }\n\n if (!this.badgeEl) {\n this.badgeEl = document.createElement(\"span\");\n this.badgeEl.className = \"sp-fab-badge\";\n this.badgeEl.setAttribute(\"role\", \"status\");\n this.badgeEl.setAttribute(\"aria-live\", \"polite\");\n this.fab.appendChild(this.badgeEl);\n }\n\n const displayText = count > 99 ? \"99+\" : String(count);\n setText(this.badgeEl, displayText);\n this.badgeEl.setAttribute(\"aria-label\", this.t(\"fab.badge\").replace(\"{count}\", String(count)));\n }\n\n private toggle(): void {\n this.isOpen ? this.close() : this.open();\n }\n\n private open(): void {\n this.isOpen = true;\n this.setFabIcon(ICON_CLOSE);\n this.fab.setAttribute(\"aria-expanded\", \"true\");\n\n const buttons = this.radialContainer.querySelectorAll<HTMLButtonElement>(\".sp-radial-item\");\n buttons.forEach((btn, i) => {\n // Stack vertically above the FAB with initial offset + gap\n const y = -(16 + ITEM_GAP * (i + 1));\n btn.style.transform = `translate(0px, ${y}px) scale(1)`;\n btn.classList.add(\"sp-radial-item--open\");\n });\n\n // Focus the first menu item after animation\n requestAnimationFrame(() => {\n const firstItem = this.radialContainer.querySelector<HTMLButtonElement>(\".sp-radial-item\");\n firstItem?.focus();\n });\n }\n\n private close(): void {\n this.isOpen = false;\n this.setFabIcon(ICON_SITEPING);\n this.fab.setAttribute(\"aria-expanded\", \"false\");\n\n const buttons = this.radialContainer.querySelectorAll<HTMLButtonElement>(\".sp-radial-item\");\n buttons.forEach((btn) => {\n btn.style.transform = \"translate(0, 0) scale(0.8)\";\n btn.classList.remove(\"sp-radial-item--open\");\n });\n\n // Return focus to FAB\n this.fab.focus();\n }\n\n private setFabIcon(svgStr: string): void {\n const badge = this.badgeEl;\n this.fab.replaceChildren(parseSvg(svgStr));\n // Re-append badge after icon swap\n if (badge) this.fab.appendChild(badge);\n }\n\n private handleItemClick(id: string): void {\n this.close();\n\n switch (id) {\n case \"chat\":\n this.bus.emit(\"panel:toggle\", true);\n break;\n case \"annotate\":\n this.bus.emit(\"annotation:start\");\n break;\n case \"toggle-annotations\": {\n this.annotationsVisible = !this.annotationsVisible;\n this.bus.emit(\"annotations:toggle\", this.annotationsVisible);\n const btn = this.radialContainer.querySelector('[data-item-id=\"toggle-annotations\"]');\n if (btn) {\n btn.replaceChildren(parseSvg(this.annotationsVisible ? ICON_EYE : ICON_EYE_OFF));\n }\n break;\n }\n }\n }\n\n destroy(): void {\n document.removeEventListener(\"click\", this.onDocumentClick);\n this.root.remove();\n }\n}\n","import type { Translations } from \"./types.js\";\n\nexport const de: Translations = {\n // Panel\n \"panel.title\": \"Feedbacks\",\n \"panel.ariaLabel\": \"Siteping-Feedback-Panel\",\n \"panel.feedbackList\": \"Feedbackliste\",\n \"panel.loading\": \"Feedbacks werden geladen\",\n \"panel.close\": \"Panel schließen\",\n \"panel.deleteAll\": \"Alle löschen\",\n \"panel.deleteAllConfirmTitle\": \"Alle löschen\",\n \"panel.deleteAllConfirmMessage\":\n \"Alle Feedbacks für dieses Projekt löschen? Diese Aktion kann nicht rückgängig gemacht werden.\",\n \"panel.search\": \"Suchen...\",\n \"panel.searchAria\": \"Feedbacks suchen\",\n \"panel.filterAll\": \"Alle\",\n \"panel.loadError\": \"Laden fehlgeschlagen\",\n \"panel.retry\": \"Erneut versuchen\",\n \"panel.empty\": \"Noch kein Feedback\",\n \"panel.showMore\": \"Mehr anzeigen\",\n \"panel.showLess\": \"Weniger anzeigen\",\n \"panel.resolve\": \"Erledigen\",\n \"panel.reopen\": \"Wieder öffnen\",\n \"panel.delete\": \"Löschen\",\n \"panel.cancel\": \"Abbrechen\",\n \"panel.confirmDelete\": \"Löschen\",\n \"panel.loadMore\": \"Mehr laden ({remaining} verbleibend)\",\n\n // Status filter labels\n \"panel.statusAll\": \"Alle\",\n \"panel.statusOpen\": \"Offen\",\n \"panel.statusResolved\": \"Erledigt\",\n\n // Feedback type labels\n \"type.label\": \"Typ\",\n \"type.question\": \"Frage\",\n \"type.change\": \"Änderung\",\n \"type.bug\": \"Fehler\",\n \"type.other\": \"Sonstiges\",\n\n // Status segmented control label\n \"status.label\": \"Status\",\n\n // FAB menu\n \"fab.aria\": \"Siteping — Feedback-Menü\",\n \"fab.messages\": \"Nachrichten\",\n \"fab.annotate\": \"Kommentieren\",\n \"fab.annotations\": \"Anmerkungen\",\n\n // Annotator\n \"annotator.instruction\": \"Zeichne ein Rechteck um den Bereich, den du kommentieren möchtest\",\n \"annotator.cancel\": \"Abbrechen\",\n\n // Popup\n \"popup.ariaLabel\": \"Feedbackformular\",\n \"popup.placeholder\": \"Beschreibe dein Feedback...\",\n \"popup.textareaAria\": \"Feedbacknachricht\",\n \"popup.submitHintMac\": \"⌘+Enter zum Senden\",\n \"popup.submitHintOther\": \"Strg+Enter zum Senden\",\n \"popup.cancel\": \"Abbrechen\",\n \"popup.submit\": \"Senden\",\n\n // Identity modal\n \"identity.title\": \"Identifiziere dich\",\n \"identity.nameLabel\": \"Name\",\n \"identity.namePlaceholder\": \"Dein Name\",\n \"identity.emailLabel\": \"E-Mail\",\n \"identity.emailPlaceholder\": \"deine@email.de\",\n \"identity.cancel\": \"Abbrechen\",\n \"identity.submit\": \"Fortfahren\",\n\n // Markers\n \"marker.approximate\": \"Ungefähre Position (Konfidenz: {confidence}%)\",\n \"marker.aria\": \"Feedback #{number}: {type} — {message}\",\n\n // FAB badge\n \"fab.badge\": \"{count} unerledigte Feedbacks\",\n\n // Accessibility — screen reader announcements\n \"feedback.sent.confirmation\": \"Feedback erfolgreich gesendet\",\n \"feedback.error.message\": \"Feedback konnte nicht gesendet werden\",\n \"feedback.deleted.confirmation\": \"Feedback gelöscht\",\n\n // Badge\n \"badge.count\": \"{count} unerledigte Feedbacks\",\n};\n","import type { Translations } from \"./types.js\";\n\nexport const en: Translations = {\n // Panel\n \"panel.title\": \"Feedbacks\",\n \"panel.ariaLabel\": \"Siteping feedback panel\",\n \"panel.feedbackList\": \"Feedback list\",\n \"panel.loading\": \"Loading feedbacks\",\n \"panel.close\": \"Close panel\",\n \"panel.deleteAll\": \"Delete all\",\n \"panel.deleteAllConfirmTitle\": \"Delete all\",\n \"panel.deleteAllConfirmMessage\": \"Delete all feedbacks for this project? This action cannot be undone.\",\n \"panel.search\": \"Search...\",\n \"panel.searchAria\": \"Search feedbacks\",\n \"panel.filterAll\": \"All\",\n \"panel.loadError\": \"Failed to load\",\n \"panel.retry\": \"Retry\",\n \"panel.empty\": \"No feedback yet\",\n \"panel.showMore\": \"Show more\",\n \"panel.showLess\": \"Show less\",\n \"panel.resolve\": \"Resolve\",\n \"panel.reopen\": \"Reopen\",\n \"panel.delete\": \"Delete\",\n \"panel.cancel\": \"Cancel\",\n \"panel.confirmDelete\": \"Delete\",\n \"panel.loadMore\": \"Load more ({remaining} remaining)\",\n\n // Status filter labels\n \"panel.statusAll\": \"All\",\n \"panel.statusOpen\": \"Open\",\n \"panel.statusResolved\": \"Resolved\",\n\n // Feedback type labels\n \"type.label\": \"Type\",\n \"type.question\": \"Question\",\n \"type.change\": \"Change\",\n \"type.bug\": \"Bug\",\n \"type.other\": \"Other\",\n\n // Status segmented control label\n \"status.label\": \"Status\",\n\n // FAB menu\n \"fab.aria\": \"Siteping \\u2014 Feedback menu\",\n \"fab.messages\": \"Messages\",\n \"fab.annotate\": \"Annotate\",\n \"fab.annotations\": \"Annotations\",\n\n // Annotator\n \"annotator.instruction\": \"Draw a rectangle on the area to comment\",\n \"annotator.cancel\": \"Cancel\",\n\n // Popup\n \"popup.ariaLabel\": \"Feedback form\",\n \"popup.placeholder\": \"Describe your feedback...\",\n \"popup.textareaAria\": \"Feedback message\",\n \"popup.submitHintMac\": \"\\u2318+Enter to send\",\n \"popup.submitHintOther\": \"Ctrl+Enter to send\",\n \"popup.cancel\": \"Cancel\",\n \"popup.submit\": \"Send\",\n\n // Identity modal\n \"identity.title\": \"Identify yourself\",\n \"identity.nameLabel\": \"Name\",\n \"identity.namePlaceholder\": \"Your name\",\n \"identity.emailLabel\": \"Email\",\n \"identity.emailPlaceholder\": \"your@email.com\",\n \"identity.cancel\": \"Cancel\",\n \"identity.submit\": \"Continue\",\n\n // Markers\n \"marker.approximate\": \"Approximate position (confidence: {confidence}%)\",\n \"marker.aria\": \"Feedback #{number}: {type} — {message}\",\n\n // FAB badge\n \"fab.badge\": \"{count} unresolved feedbacks\",\n\n // Accessibility — screen reader announcements\n \"feedback.sent.confirmation\": \"Feedback sent successfully\",\n \"feedback.error.message\": \"Failed to send feedback\",\n \"feedback.deleted.confirmation\": \"Feedback deleted\",\n\n // Badge\n \"badge.count\": \"{count} unresolved feedbacks\",\n};\n","import type { Translations } from \"./types.js\";\n\nexport const es: Translations = {\n // Panel\n \"panel.title\": \"Comentarios\",\n \"panel.ariaLabel\": \"Panel de comentarios de Siteping\",\n \"panel.feedbackList\": \"Lista de comentarios\",\n \"panel.loading\": \"Cargando comentarios\",\n \"panel.close\": \"Cerrar panel\",\n \"panel.deleteAll\": \"Eliminar todo\",\n \"panel.deleteAllConfirmTitle\": \"Eliminar todo\",\n \"panel.deleteAllConfirmMessage\":\n \"¿Eliminar todos los comentarios de este proyecto? Esta acción no se puede deshacer.\",\n \"panel.search\": \"Buscar...\",\n \"panel.searchAria\": \"Buscar comentarios\",\n \"panel.filterAll\": \"Todos\",\n \"panel.loadError\": \"No se pudo cargar\",\n \"panel.retry\": \"Reintentar\",\n \"panel.empty\": \"Aún no hay comentarios\",\n \"panel.showMore\": \"Mostrar más\",\n \"panel.showLess\": \"Mostrar menos\",\n \"panel.resolve\": \"Resolver\",\n \"panel.reopen\": \"Reabrir\",\n \"panel.delete\": \"Eliminar\",\n \"panel.cancel\": \"Cancelar\",\n \"panel.confirmDelete\": \"Eliminar\",\n \"panel.loadMore\": \"Cargar más ({remaining} restantes)\",\n\n // Status filter labels\n \"panel.statusAll\": \"Todos\",\n \"panel.statusOpen\": \"Abiertos\",\n \"panel.statusResolved\": \"Resueltos\",\n\n // Feedback type labels\n \"type.label\": \"Tipo\",\n \"type.question\": \"Pregunta\",\n \"type.change\": \"Cambio\",\n \"type.bug\": \"Error\",\n \"type.other\": \"Otro\",\n\n // Status segmented control label\n \"status.label\": \"Estado\",\n\n // FAB menu\n \"fab.aria\": \"Siteping — Menú de comentarios\",\n \"fab.messages\": \"Mensajes\",\n \"fab.annotate\": \"Anotar\",\n \"fab.annotations\": \"Anotaciones\",\n\n // Annotator\n \"annotator.instruction\": \"Dibuja un rectángulo sobre el área que quieres comentar\",\n \"annotator.cancel\": \"Cancelar\",\n\n // Popup\n \"popup.ariaLabel\": \"Formulario de comentarios\",\n \"popup.placeholder\": \"Describe tu comentario...\",\n \"popup.textareaAria\": \"Mensaje de comentario\",\n \"popup.submitHintMac\": \"⌘+Enter para enviar\",\n \"popup.submitHintOther\": \"Ctrl+Enter para enviar\",\n \"popup.cancel\": \"Cancelar\",\n \"popup.submit\": \"Enviar\",\n\n // Identity modal\n \"identity.title\": \"Identifícate\",\n \"identity.nameLabel\": \"Nombre\",\n \"identity.namePlaceholder\": \"Tu nombre\",\n \"identity.emailLabel\": \"Correo electrónico\",\n \"identity.emailPlaceholder\": \"tu@email.com\",\n \"identity.cancel\": \"Cancelar\",\n \"identity.submit\": \"Continuar\",\n\n // Markers\n \"marker.approximate\": \"Posición aproximada (confianza: {confidence}%)\",\n \"marker.aria\": \"Comentario #{number}: {type} — {message}\",\n\n // FAB badge\n \"fab.badge\": \"{count} comentarios sin resolver\",\n\n // Accessibility — screen reader announcements\n \"feedback.sent.confirmation\": \"Comentario enviado correctamente\",\n \"feedback.error.message\": \"No se pudo enviar el comentario\",\n \"feedback.deleted.confirmation\": \"Comentario eliminado\",\n\n // Badge\n \"badge.count\": \"{count} comentarios sin resolver\",\n};\n","import type { Translations } from \"./types.js\";\n\nexport const fr: Translations = {\n // Panel\n \"panel.title\": \"Feedbacks\",\n \"panel.ariaLabel\": \"Panneau de feedback Siteping\",\n \"panel.feedbackList\": \"Liste des feedbacks\",\n \"panel.loading\": \"Chargement des feedbacks\",\n \"panel.close\": \"Fermer le panneau\",\n \"panel.deleteAll\": \"Tout supprimer\",\n \"panel.deleteAllConfirmTitle\": \"Tout supprimer\",\n \"panel.deleteAllConfirmMessage\": \"Supprimer tous les feedbacks de ce projet ? Cette action est irr\\u00e9versible.\",\n \"panel.search\": \"Rechercher...\",\n \"panel.searchAria\": \"Rechercher dans les feedbacks\",\n \"panel.filterAll\": \"Tous\",\n \"panel.loadError\": \"Erreur de chargement\",\n \"panel.retry\": \"R\\u00e9essayer\",\n \"panel.empty\": \"Aucun feedback pour le moment\",\n \"panel.showMore\": \"Voir plus\",\n \"panel.showLess\": \"Voir moins\",\n \"panel.resolve\": \"R\\u00e9soudre\",\n \"panel.reopen\": \"Rouvrir\",\n \"panel.delete\": \"Supprimer\",\n \"panel.cancel\": \"Annuler\",\n \"panel.confirmDelete\": \"Supprimer\",\n \"panel.loadMore\": \"Voir plus ({remaining} restants)\",\n\n // Status filter labels\n \"panel.statusAll\": \"Tous\",\n \"panel.statusOpen\": \"Ouvert\",\n \"panel.statusResolved\": \"Résolu\",\n\n // Feedback type labels\n \"type.label\": \"Type\",\n \"type.question\": \"Question\",\n \"type.change\": \"Changement\",\n \"type.bug\": \"Bug\",\n \"type.other\": \"Autre\",\n\n // Status segmented control label\n \"status.label\": \"Statut\",\n\n // FAB menu\n \"fab.aria\": \"Siteping \\u2014 Menu feedback\",\n \"fab.messages\": \"Messages\",\n \"fab.annotate\": \"Annoter\",\n \"fab.annotations\": \"Annotations\",\n\n // Annotator\n \"annotator.instruction\": \"Tracez un rectangle sur la zone \\u00e0 commenter\",\n \"annotator.cancel\": \"Annuler\",\n\n // Popup\n \"popup.ariaLabel\": \"Formulaire de feedback\",\n \"popup.placeholder\": \"D\\u00e9crivez votre retour...\",\n \"popup.textareaAria\": \"Message de feedback\",\n \"popup.submitHintMac\": \"\\u2318+Entr\\u00e9e pour envoyer\",\n \"popup.submitHintOther\": \"Ctrl+Entr\\u00e9e pour envoyer\",\n \"popup.cancel\": \"Annuler\",\n \"popup.submit\": \"Envoyer\",\n\n // Identity modal\n \"identity.title\": \"Identifiez-vous\",\n \"identity.nameLabel\": \"Nom\",\n \"identity.namePlaceholder\": \"Votre nom\",\n \"identity.emailLabel\": \"Email\",\n \"identity.emailPlaceholder\": \"votre@email.com\",\n \"identity.cancel\": \"Annuler\",\n \"identity.submit\": \"Continuer\",\n\n // Markers\n \"marker.approximate\": \"Position approximative (confiance : {confidence}%)\",\n \"marker.aria\": \"Feedback n°{number} : {type} — {message}\",\n\n // FAB badge\n \"fab.badge\": \"{count} feedbacks non résolus\",\n\n // Accessibility — screen reader announcements\n \"feedback.sent.confirmation\": \"Feedback envoyé avec succès\",\n \"feedback.error.message\": \"Échec de l'envoi du feedback\",\n \"feedback.deleted.confirmation\": \"Feedback supprimé\",\n\n // Badge\n \"badge.count\": \"{count} feedbacks non résolus\",\n};\n","import type { Translations } from \"./types.js\";\n\n/** Italian translations (it-IT). */\nexport const it: Translations = {\n // Panel\n \"panel.title\": \"Feedback\",\n \"panel.ariaLabel\": \"Pannello feedback di Siteping\",\n \"panel.feedbackList\": \"Elenco feedback\",\n \"panel.loading\": \"Caricamento feedback\",\n \"panel.close\": \"Chiudi pannello\",\n \"panel.deleteAll\": \"Elimina tutto\",\n \"panel.deleteAllConfirmTitle\": \"Elimina tutto\",\n \"panel.deleteAllConfirmMessage\":\n \"Eliminare tutti i feedback per questo progetto? Questa azione non può essere annullata.\",\n \"panel.search\": \"Cerca...\",\n \"panel.searchAria\": \"Cerca feedback\",\n \"panel.filterAll\": \"Tutti\",\n \"panel.loadError\": \"Caricamento non riuscito\",\n \"panel.retry\": \"Riprova\",\n \"panel.empty\": \"Nessun feedback ancora\",\n \"panel.showMore\": \"Mostra di più\",\n \"panel.showLess\": \"Mostra meno\",\n \"panel.resolve\": \"Risolvi\",\n \"panel.reopen\": \"Riapri\",\n \"panel.delete\": \"Elimina\",\n \"panel.cancel\": \"Annulla\",\n \"panel.confirmDelete\": \"Elimina\",\n \"panel.loadMore\": \"Carica altro ({remaining} rimanenti)\",\n\n // Status filter labels\n \"panel.statusAll\": \"Tutti\",\n \"panel.statusOpen\": \"Aperti\",\n \"panel.statusResolved\": \"Risolti\",\n\n // Feedback type labels\n \"type.label\": \"Tipo\",\n \"type.question\": \"Domanda\",\n \"type.change\": \"Modifica\",\n \"type.bug\": \"Bug\",\n \"type.other\": \"Altro\",\n\n // Status segmented control label\n \"status.label\": \"Stato\",\n\n // FAB menu\n \"fab.aria\": \"Siteping — Menu feedback\",\n \"fab.messages\": \"Messaggi\",\n \"fab.annotate\": \"Annota\",\n \"fab.annotations\": \"Annotazioni\",\n\n // Annotator\n \"annotator.instruction\": \"Disegna un rettangolo sull'area da commentare\",\n \"annotator.cancel\": \"Annulla\",\n\n // Popup\n \"popup.ariaLabel\": \"Modulo feedback\",\n \"popup.placeholder\": \"Descrivi il tuo feedback...\",\n \"popup.textareaAria\": \"Messaggio di feedback\",\n \"popup.submitHintMac\": \"⌘+Invio per inviare\",\n \"popup.submitHintOther\": \"Ctrl+Invio per inviare\",\n \"popup.cancel\": \"Annulla\",\n \"popup.submit\": \"Invia\",\n\n // Identity modal\n \"identity.title\": \"Identificati\",\n \"identity.nameLabel\": \"Nome\",\n \"identity.namePlaceholder\": \"Il tuo nome\",\n \"identity.emailLabel\": \"Email\",\n \"identity.emailPlaceholder\": \"tua@email.com\",\n \"identity.cancel\": \"Annulla\",\n \"identity.submit\": \"Continua\",\n\n // Markers\n \"marker.approximate\": \"Posizione approssimativa (confidenza: {confidence}%)\",\n \"marker.aria\": \"Feedback #{number}: {type} — {message}\",\n\n // FAB badge\n \"fab.badge\": \"{count} feedback non risolti\",\n\n // Accessibility — screen reader announcements\n \"feedback.sent.confirmation\": \"Feedback inviato con successo\",\n \"feedback.error.message\": \"Invio del feedback non riuscito\",\n \"feedback.deleted.confirmation\": \"Feedback eliminato\",\n\n // Badge\n \"badge.count\": \"{count} feedback non risolti\",\n};\n","import type { Translations } from \"./types.js\";\n\n/** Brazilian Portuguese translations (pt-BR). */\nexport const pt: Translations = {\n // Panel\n \"panel.title\": \"Feedbacks\",\n \"panel.ariaLabel\": \"Painel de feedback do Siteping\",\n \"panel.feedbackList\": \"Lista de feedbacks\",\n \"panel.loading\": \"Carregando feedbacks\",\n \"panel.close\": \"Fechar painel\",\n \"panel.deleteAll\": \"Excluir tudo\",\n \"panel.deleteAllConfirmTitle\": \"Excluir tudo\",\n \"panel.deleteAllConfirmMessage\": \"Excluir todos os feedbacks deste projeto? Esta ação não pode ser desfeita.\",\n \"panel.search\": \"Pesquisar...\",\n \"panel.searchAria\": \"Pesquisar feedbacks\",\n \"panel.filterAll\": \"Todos\",\n \"panel.loadError\": \"Falha ao carregar\",\n \"panel.retry\": \"Tentar novamente\",\n \"panel.empty\": \"Nenhum feedback ainda\",\n \"panel.showMore\": \"Mostrar mais\",\n \"panel.showLess\": \"Mostrar menos\",\n \"panel.resolve\": \"Resolver\",\n \"panel.reopen\": \"Reabrir\",\n \"panel.delete\": \"Excluir\",\n \"panel.cancel\": \"Cancelar\",\n \"panel.confirmDelete\": \"Excluir\",\n \"panel.loadMore\": \"Carregar mais ({remaining} restantes)\",\n\n // Status filter labels\n \"panel.statusAll\": \"Todos\",\n \"panel.statusOpen\": \"Abertos\",\n \"panel.statusResolved\": \"Resolvidos\",\n\n // Feedback type labels\n \"type.label\": \"Tipo\",\n \"type.question\": \"Pergunta\",\n \"type.change\": \"Alteração\",\n \"type.bug\": \"Bug\",\n \"type.other\": \"Outro\",\n\n // Status segmented control label\n \"status.label\": \"Status\",\n\n // FAB menu\n \"fab.aria\": \"Siteping — Menu de feedback\",\n \"fab.messages\": \"Mensagens\",\n \"fab.annotate\": \"Anotar\",\n \"fab.annotations\": \"Anotações\",\n\n // Annotator\n \"annotator.instruction\": \"Desenhe um retângulo na área que deseja comentar\",\n \"annotator.cancel\": \"Cancelar\",\n\n // Popup\n \"popup.ariaLabel\": \"Formulário de feedback\",\n \"popup.placeholder\": \"Descreva seu feedback...\",\n \"popup.textareaAria\": \"Mensagem de feedback\",\n \"popup.submitHintMac\": \"⌘+Enter para enviar\",\n \"popup.submitHintOther\": \"Ctrl+Enter para enviar\",\n \"popup.cancel\": \"Cancelar\",\n \"popup.submit\": \"Enviar\",\n\n // Identity modal\n \"identity.title\": \"Identifique-se\",\n \"identity.nameLabel\": \"Nome\",\n \"identity.namePlaceholder\": \"Seu nome\",\n \"identity.emailLabel\": \"E-mail\",\n \"identity.emailPlaceholder\": \"seu@email.com\",\n \"identity.cancel\": \"Cancelar\",\n \"identity.submit\": \"Continuar\",\n\n // Markers\n \"marker.approximate\": \"Posição aproximada (confiança: {confidence}%)\",\n \"marker.aria\": \"Feedback #{number}: {type} — {message}\",\n\n // FAB badge\n \"fab.badge\": \"{count} feedbacks não resolvidos\",\n\n // Accessibility — screen reader announcements\n \"feedback.sent.confirmation\": \"Feedback enviado com sucesso\",\n \"feedback.error.message\": \"Falha ao enviar feedback\",\n \"feedback.deleted.confirmation\": \"Feedback excluído\",\n\n // Badge\n \"badge.count\": \"{count} feedbacks não resolvidos\",\n};\n","import type { Translations } from \"./types.js\";\n\nexport const ru: Translations = {\n // Panel\n \"panel.title\": \"Обратная связь\",\n \"panel.ariaLabel\": \"Панель обратной связи Siteping\",\n \"panel.feedbackList\": \"Список отзывов\",\n \"panel.loading\": \"Загрузка отзывов\",\n \"panel.close\": \"Закрыть панель\",\n \"panel.deleteAll\": \"Удалить всё\",\n \"panel.deleteAllConfirmTitle\": \"Удалить всё\",\n \"panel.deleteAllConfirmMessage\": \"Удалить все отзывы этого проекта? Это действие необратимо.\",\n \"panel.search\": \"Поиск...\",\n \"panel.searchAria\": \"Поиск по отзывам\",\n \"panel.filterAll\": \"Все\",\n \"panel.loadError\": \"Ошибка загрузки\",\n \"panel.retry\": \"Повторить\",\n \"panel.empty\": \"Пока нет отзывов\",\n \"panel.showMore\": \"Показать больше\",\n \"panel.showLess\": \"Показать меньше\",\n \"panel.resolve\": \"Решено\",\n \"panel.reopen\": \"Открыть заново\",\n \"panel.delete\": \"Удалить\",\n \"panel.cancel\": \"Отмена\",\n \"panel.confirmDelete\": \"Удалить\",\n \"panel.loadMore\": \"Показать ещё ({remaining} осталось)\",\n\n // Status filter labels\n \"panel.statusAll\": \"Все\",\n \"panel.statusOpen\": \"Открытые\",\n \"panel.statusResolved\": \"Решённые\",\n\n // Feedback type labels\n \"type.label\": \"Тип\",\n \"type.question\": \"Вопрос\",\n \"type.change\": \"Улучшение\",\n \"type.bug\": \"Баг\",\n \"type.other\": \"Другое\",\n\n // Status segmented control label\n \"status.label\": \"Статус\",\n\n // FAB menu\n \"fab.aria\": \"Siteping — Меню обратной связи\",\n \"fab.messages\": \"Сообщения\",\n \"fab.annotate\": \"Аннотация\",\n \"fab.annotations\": \"Аннотации\",\n\n // Annotator\n \"annotator.instruction\": \"Выделите область для комментария\",\n \"annotator.cancel\": \"Отмена\",\n\n // Popup\n \"popup.ariaLabel\": \"Форма обратной связи\",\n \"popup.placeholder\": \"Опишите проблему или предложение...\",\n \"popup.textareaAria\": \"Сообщение\",\n \"popup.submitHintMac\": \"⌘+Enter — отправить\",\n \"popup.submitHintOther\": \"Ctrl+Enter — отправить\",\n \"popup.cancel\": \"Отмена\",\n \"popup.submit\": \"Отправить\",\n\n // Identity modal\n \"identity.title\": \"Представьтесь\",\n \"identity.nameLabel\": \"Имя\",\n \"identity.namePlaceholder\": \"Ваше имя\",\n \"identity.emailLabel\": \"Email\",\n \"identity.emailPlaceholder\": \"ваш@email.com\",\n \"identity.cancel\": \"Отмена\",\n \"identity.submit\": \"Продолжить\",\n\n // Markers\n \"marker.approximate\": \"Приблизительная позиция (точность: {confidence}%)\",\n \"marker.aria\": \"Отзыв #{number}: {type} — {message}\",\n\n // FAB badge\n \"fab.badge\": \"Нерешённых отзывов: {count}\",\n\n // Accessibility — screen reader announcements\n \"feedback.sent.confirmation\": \"Отзыв успешно отправлен\",\n \"feedback.error.message\": \"Не удалось отправить отзыв\",\n \"feedback.deleted.confirmation\": \"Отзыв удалён\",\n\n // Badge\n \"badge.count\": \"Нерешённых отзывов: {count}\",\n};\n","import type { TFunction, Translations } from \"./types.js\";\n\nexport type { TFunction, Translations } from \"./types.js\";\n\n// Static imports — bundler (tsup) will include them all.\n// For tree-shaking in consumer apps, use dynamic import() with a bundler plugin.\nimport { de } from \"./de.js\";\nimport { en } from \"./en.js\";\nimport { es } from \"./es.js\";\nimport { fr } from \"./fr.js\";\nimport { it } from \"./it.js\";\nimport { pt } from \"./pt.js\";\nimport { ru } from \"./ru.js\";\n\nconst LOCALES: Record<string, Translations> = { de, en, es, fr, it, pt, ru };\n\n/** Register a custom locale at runtime. */\nexport function registerLocale(code: string, translations: Translations): void {\n LOCALES[code] = translations;\n}\n\n/**\n * Create a translation function for the given locale.\n *\n * Locale resolution: exact match > language prefix > English fallback.\n */\nexport function createT(locale: string): TFunction {\n const lang = (locale.split(\"-\")[0] ?? locale).toLowerCase();\n if (!LOCALES[lang]) {\n console.warn(`[siteping] Unknown locale \"${locale}\", falling back to \"en\"`);\n }\n const dict = LOCALES[lang] ?? LOCALES.en ?? ({} as Translations);\n return (key) => dict[key] ?? key;\n}\n\n/**\n * Returns the type label for a FeedbackType value.\n * Maps API enum values (english) to localized display labels.\n */\nexport function getTypeLabel(type: string, t: TFunction): string {\n switch (type) {\n case \"question\":\n return t(\"type.question\");\n case \"change\":\n return t(\"type.change\");\n case \"bug\":\n return t(\"type.bug\");\n case \"other\":\n return t(\"type.other\");\n default:\n return type;\n }\n}\n","const STORAGE_KEY = \"siteping_identity\";\n\nexport interface Identity {\n name: string;\n email: string;\n}\n\nexport function getIdentity(): Identity | null {\n try {\n const raw = localStorage.getItem(STORAGE_KEY);\n if (!raw) return null;\n const parsed: unknown = JSON.parse(raw);\n if (\n typeof parsed === \"object\" &&\n parsed !== null &&\n \"name\" in parsed &&\n typeof (parsed as Record<string, unknown>).name === \"string\" &&\n \"email\" in parsed &&\n typeof (parsed as Record<string, unknown>).email === \"string\"\n ) {\n const identity = parsed as Identity;\n if (identity.name && identity.email) return identity;\n }\n return null;\n } catch {\n return null;\n }\n}\n\nexport function saveIdentity(identity: Identity): void {\n try {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(identity));\n } catch {\n // Quota exceeded or localStorage disabled — identity works for this session only\n }\n}\n","/**\n * Lightweight fuzzy text matching for DOM re-anchoring.\n * Zero dependencies — bundled into the widget.\n * Uses Levenshtein distance, optimized for short strings (~50 chars).\n */\n\n/**\n * Levenshtein edit distance.\n * O(n*m) time, O(min(n,m)) space.\n */\nexport function editDistance(a: string, b: string): number {\n if (a === b) return 0;\n if (a.length === 0) return b.length;\n if (b.length === 0) return a.length;\n\n // Ensure `a` is the shorter string for space optimization\n if (a.length > b.length) {\n const t = a;\n a = b;\n b = t;\n }\n\n const aLen = a.length;\n const bLen = b.length;\n let prev = new Array<number>(aLen + 1);\n for (let k = 0; k <= aLen; k++) prev[k] = k;\n let curr = new Array<number>(aLen + 1);\n\n for (let j = 1; j <= bLen; j++) {\n curr[0] = j;\n for (let i = 1; i <= aLen; i++) {\n // Indices are valid: i-1 in [0, aLen-1], j-1 in [0, bLen-1], loop bounds guarantee access\n const prevDiag = prev[i - 1] ?? 0;\n curr[i] = a[i - 1] === b[j - 1] ? prevDiag : 1 + Math.min(prevDiag, prev[i] ?? 0, curr[i - 1] ?? 0);\n }\n const tmp = prev;\n prev = curr;\n curr = tmp;\n }\n\n return prev[aLen] ?? 0; // aLen is within bounds — prev has aLen+1 entries\n}\n\n/**\n * Normalized similarity score (0–1, where 1 = identical).\n */\nexport function similarity(a: string, b: string): number {\n if (a === b) return 1;\n const maxLen = Math.max(a.length, b.length);\n if (maxLen === 0) return 1;\n return 1 - editDistance(a, b) / maxLen;\n}\n\n/**\n * Fuzzy substring search — checks if `needle` approximately exists in `haystack`.\n * Slides a window of `needle.length` over the haystack and returns the best\n * similarity score found. Returns 0 if below `minScore`.\n */\nexport function fuzzyIncludes(haystack: string, needle: string, minScore = 0.6): number {\n if (!needle || !haystack) return 0;\n if (haystack.includes(needle)) return 1;\n\n const nLen = needle.length;\n\n // If needle is longer than haystack, compare directly\n if (nLen > haystack.length) {\n const score = similarity(haystack, needle);\n return score >= minScore ? score : 0;\n }\n\n let best = 0;\n\n // Cap haystack to avoid O(n²) on huge text nodes\n const capped = haystack.length > 500 ? haystack.slice(0, 500) : haystack;\n const limit = capped.length - nLen;\n\n for (let i = 0; i <= limit; i++) {\n const window = capped.slice(i, i + nLen);\n const score = similarity(window, needle);\n if (score > best) best = score;\n if (best >= 0.95) break;\n }\n\n return best >= minScore ? best : 0;\n}\n","import type { AnchorData, RectData } from \"@siteping/core\";\nimport { scoreFingerprint } from \"./fingerprint.js\";\nimport { fuzzyIncludes, similarity } from \"./fuzzy.js\";\nimport { adjacentText, neighborText } from \"./text-context.js\";\n\nexport type ResolutionStrategy = \"id\" | \"css\" | \"xpath\" | \"scan\";\n\nexport interface AnchorResolution {\n element: Element;\n confidence: number;\n strategy: ResolutionStrategy;\n}\n\nexport interface ResolvedAnnotation {\n element: Element;\n rect: DOMRect;\n confidence: number;\n strategy: ResolutionStrategy;\n}\n\n/** Max elements to scan during smart fallback. */\nconst MAX_SCAN_CANDIDATES = 300;\n\n/** Minimum fuzzy text match score for CSS/XPath verification. */\nconst TEXT_MATCH_THRESHOLD = 0.3;\n\n/**\n * Verify that a resolved element's text content matches the stored snippet.\n * If no snippet is stored, returns true (no verification possible).\n * Uses fuzzy matching to tolerate minor text changes.\n */\nfunction textMatches(el: Element, anchor: AnchorData): boolean {\n if (!anchor.textSnippet) return true;\n const text = (el.textContent?.trim() ?? \"\").slice(0, 500);\n return fuzzyIncludes(text, anchor.textSnippet, 0.5) > TEXT_MATCH_THRESHOLD;\n}\n\n/**\n * Re-anchor an annotation using a multi-level fallback strategy\n * with text verification and confidence scoring.\n *\n * Resolution order:\n * 1. getElementById + text verification — confidence 1.0\n * 2. CSS selector + text verification — confidence 0.95\n * 3. XPath + text verification — confidence 0.9\n * 4. Smart scan (fingerprint + text + prefix/suffix + neighbor) — up to 0.85\n *\n * Text verification prevents false matches when DOM elements are reordered.\n * Returns null if all strategies fail (annotation is orphaned).\n */\nexport function resolveAnchor(anchor: AnchorData): AnchorResolution | null {\n // Level 1: Element ID (most stable, still verify text)\n if (anchor.elementId) {\n const el = document.getElementById(anchor.elementId);\n if (el && el.tagName === anchor.elementTag && textMatches(el, anchor)) {\n return { element: el, confidence: 1.0, strategy: \"id\" };\n }\n }\n\n // Level 2: CSS Selector (with text verification)\n try {\n const el = document.querySelector(anchor.cssSelector);\n if (el && el.tagName === anchor.elementTag && textMatches(el, anchor)) {\n return { element: el, confidence: 0.95, strategy: \"css\" };\n }\n } catch {\n // Invalid selector — skip\n }\n\n // Level 3: XPath (with text verification)\n try {\n const result = document.evaluate(anchor.xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);\n const el = result.singleNodeValue;\n if (el instanceof Element && el.tagName === anchor.elementTag && textMatches(el, anchor)) {\n return { element: el, confidence: 0.9, strategy: \"xpath\" };\n }\n } catch {\n // Invalid XPath — skip\n }\n\n // Level 4: Smart scan — combine all available signals\n return smartScan(anchor);\n}\n\n/**\n * Scan DOM elements by tag and score each candidate using multiple signals:\n * fingerprint, text similarity, prefix/suffix context, neighbor text.\n *\n * Returns the best candidate above a 0.4 threshold, capped at 0.85 confidence\n * (smart scan is never 100% certain).\n */\nfunction smartScan(anchor: AnchorData): AnchorResolution | null {\n const tag = anchor.elementTag.toLowerCase();\n const candidates = document.querySelectorAll(tag);\n if (candidates.length === 0) return null;\n\n let bestElement: Element | null = null;\n let bestScore = 0;\n\n const limit = Math.min(candidates.length, MAX_SCAN_CANDIDATES);\n\n for (let i = 0; i < limit; i++) {\n const el = candidates[i];\n if (!el) continue;\n const score = scoreCandidate(el, anchor);\n if (score > bestScore) {\n bestScore = score;\n bestElement = el;\n if (bestScore >= 0.85) break;\n }\n }\n\n if (!bestElement || bestScore < 0.4) return null;\n\n return {\n element: bestElement,\n confidence: Math.min(bestScore, 0.85),\n strategy: \"scan\",\n };\n}\n\n/**\n * Score a candidate element against all stored anchor signals.\n *\n * Dynamic weighting — only active signals contribute, then normalized:\n * - Text snippet (fuzzy substring match): weight 40 (most reliable for reordering)\n * - Fingerprint (structural match): weight 20\n * - Prefix/suffix context: weight 20\n * - Neighbor text: weight 20\n */\nfunction scoreCandidate(candidate: Element, anchor: AnchorData): number {\n let score = 0;\n let totalWeight = 0;\n\n // Truncate to avoid O(n*m) explosion on huge text nodes\n const candidateText = (candidate.textContent?.trim() ?? \"\").slice(0, 500);\n\n // --- Text snippet (weight 40 — most important for reordered elements) ---\n if (anchor.textSnippet) {\n totalWeight += 40;\n score += fuzzyIncludes(candidateText, anchor.textSnippet, 0.5) * 40;\n }\n\n // --- Fingerprint (weight 20) ---\n if (anchor.fingerprint) {\n totalWeight += 20;\n score += scoreFingerprint(candidate, anchor.fingerprint) * 20;\n }\n\n // --- Prefix/suffix context (weight 20) ---\n if (anchor.textPrefix || anchor.textSuffix) {\n totalWeight += 20;\n let contextScore = 0;\n let contextParts = 0;\n\n if (anchor.textPrefix) {\n const prevText = adjacentText(candidate, \"before\");\n contextScore += prevText ? similarity(prevText, anchor.textPrefix) : 0;\n contextParts++;\n }\n\n if (anchor.textSuffix) {\n const nextText = adjacentText(candidate, \"after\");\n contextScore += nextText ? similarity(nextText, anchor.textSuffix) : 0;\n contextParts++;\n }\n\n if (contextParts > 0) {\n score += (contextScore / contextParts) * 20;\n }\n }\n\n // --- Neighbor text (weight 20) ---\n if (anchor.neighborText) {\n totalWeight += 20;\n const candidateNeighbor = neighborText(candidate);\n score += candidateNeighbor ? similarity(candidateNeighbor, anchor.neighborText) * 20 : 0;\n }\n\n return totalWeight > 0 ? score / totalWeight : 0;\n}\n\n/**\n * Resolve an annotation's position on the page.\n * Converts stored percentage-based rect back to absolute coordinates\n * using the current bounding box of the resolved anchor element.\n */\nexport function resolveAnnotation(anchor: AnchorData, rect: RectData): ResolvedAnnotation | null {\n const resolution = resolveAnchor(anchor);\n\n if (!resolution) return null;\n\n const bounds = resolution.element.getBoundingClientRect();\n const absoluteRect = new DOMRect(\n bounds.x + rect.xPct * bounds.width,\n bounds.y + rect.yPct * bounds.height,\n rect.wPct * bounds.width,\n rect.hPct * bounds.height,\n );\n\n return {\n element: resolution.element,\n rect: absoluteRect,\n confidence: resolution.confidence,\n strategy: resolution.strategy,\n };\n}\n","import type { AnchorData, FeedbackResponse, RectData } from \"@siteping/core\";\nimport { Z_INDEX_MAX } from \"./constants.js\";\nimport { resolveAnnotation } from \"./dom/resolver.js\";\nimport { el, setText } from \"./dom-utils.js\";\nimport type { EventBus, WidgetEvents } from \"./events.js\";\nimport { getTypeLabel, type TFunction } from \"./i18n/index.js\";\nimport { getTypeColor, type ThemeColors } from \"./styles/theme.js\";\nimport type { Tooltip } from \"./tooltip.js\";\n\ntype Annotation = FeedbackResponse[\"annotations\"][number];\n\nfunction toAnchorData(a: Annotation): AnchorData {\n return {\n cssSelector: a.cssSelector,\n xpath: a.xpath,\n textSnippet: a.textSnippet,\n elementTag: a.elementTag,\n elementId: a.elementId ?? undefined,\n textPrefix: a.textPrefix,\n textSuffix: a.textSuffix,\n fingerprint: a.fingerprint,\n neighborText: a.neighborText,\n };\n}\n\nfunction toRectData(a: Annotation): RectData {\n return { xPct: a.xPct, yPct: a.yPct, wPct: a.wPct, hPct: a.hPct };\n}\n\n/** Half of the 26px marker diameter — used for centering on anchor corner. */\nconst MARKER_OFFSET = 13;\n\n/** Convert a resolved rect to document-absolute marker position. */\nfunction markerPosition(rect: DOMRect): { top: number; left: number } {\n return {\n top: rect.top + window.scrollY - MARKER_OFFSET,\n left: rect.right + window.scrollX - MARKER_OFFSET,\n };\n}\n\ninterface MarkerEntry {\n feedback: FeedbackResponse;\n elements: HTMLElement[];\n baseTop: number;\n baseLeft: number;\n}\n\ninterface Cluster {\n entries: MarkerEntry[];\n elementIndices: number[];\n expanded: boolean;\n}\n\n/** Get the i-th marker element from a cluster. */\nfunction clusterMarker(cluster: Cluster, i: number): HTMLElement | undefined {\n const entry = cluster.entries[i];\n const elIdx = cluster.elementIndices[i];\n if (!entry || elIdx === undefined) return undefined;\n return entry.elements[elIdx];\n}\n\nconst HIGHLIGHT_FADE = 300;\nconst REPOSITION_DEBOUNCE = 200;\nconst LOW_CONFIDENCE_THRESHOLD = 0.7;\nconst CLUSTER_DISTANCE = 28;\nconst FAN_SPACING = 32;\n\n/**\n * Numbered markers on the page for each feedback annotation.\n *\n * Cluster system: click-to-expand (same pattern as Google Maps / Spiderfier).\n * Hover is only used for tooltip/scale on individual markers — never for expansion.\n */\nexport class MarkerManager {\n private container: HTMLElement;\n private entries: MarkerEntry[] = [];\n private highlightElements: HTMLElement[] = [];\n private pinnedFeedback: FeedbackResponse | null = null;\n private onDocumentClick: ((e: MouseEvent) => void) | null = null;\n private repositionTimer: number | null = null;\n private mutationObserver: MutationObserver | null = null;\n private scrollHandler: (() => void) | null = null;\n private resizeHandler: (() => void) | null = null;\n private anchorCache = new Map<string, WeakRef<Element>>();\n private clusters: Cluster[] = [];\n private onDocumentClickForClusters: ((e: MouseEvent) => void) | null = null;\n\n get count(): number {\n return this.entries.length;\n }\n\n constructor(\n private readonly colors: ThemeColors,\n private readonly tooltip: Tooltip,\n private readonly bus: EventBus<WidgetEvents>,\n private readonly t: TFunction,\n ) {\n this.container = el(\"div\", {\n style: `position:absolute;top:0;left:0;pointer-events:none;z-index:${Z_INDEX_MAX - 1};`,\n });\n this.container.id = \"siteping-markers\";\n document.body.appendChild(this.container);\n\n this.bus.on(\"annotations:toggle\", (visible) => {\n this.container.style.display = visible ? \"block\" : \"none\";\n });\n\n this.resizeHandler = () => this.scheduleReposition();\n window.addEventListener(\"resize\", this.resizeHandler, { passive: true });\n\n this.scrollHandler = () => this.scheduleReposition();\n window.addEventListener(\"scroll\", this.scrollHandler, { passive: true, capture: true });\n\n // Re-resolve after DOM changes (SPA, lazy-load).\n // Filter out widget-owned mutations and skip batches with only irrelevant changes.\n // Fast-path: large batches (>20 mutations) skip per-record filtering.\n this.mutationObserver = new MutationObserver((mutations) => {\n if (mutations.length > 20) {\n this.scheduleReposition();\n return;\n }\n let hasRelevantMutation = false;\n for (const m of mutations) {\n if (this.container.contains(m.target) || this.tooltip.contains(m.target)) continue;\n hasRelevantMutation = true;\n break;\n }\n if (hasRelevantMutation) this.scheduleReposition();\n });\n this.mutationObserver.observe(document.body, {\n childList: true,\n subtree: true,\n attributes: false,\n characterData: false,\n });\n\n this.onDocumentClickForClusters = (e: MouseEvent) => {\n if (this.container.contains(e.target as Node)) return;\n this.collapseAllClusters();\n };\n document.addEventListener(\"click\", this.onDocumentClickForClusters);\n }\n\n private scheduleReposition(): void {\n if (this.repositionTimer) return;\n if (\"requestIdleCallback\" in window) {\n this.repositionTimer = window.requestIdleCallback(\n () => {\n this.repositionTimer = null;\n this.repositionAll();\n },\n { timeout: REPOSITION_DEBOUNCE + 100 },\n );\n } else {\n this.repositionTimer = +setTimeout(() => {\n this.repositionTimer = null;\n this.repositionAll();\n }, REPOSITION_DEBOUNCE);\n }\n }\n\n private repositionAll(): void {\n // Build set of valid keys to prune stale cache entries afterwards.\n const validKeys = new Set<string>();\n\n for (const entry of this.entries) {\n for (let i = 0; i < entry.feedback.annotations.length; i++) {\n const markerEl = entry.elements[i];\n if (!markerEl) continue;\n\n const annotation = entry.feedback.annotations[i];\n if (!annotation) continue;\n const cacheKey = `${entry.feedback.id}:${i}`;\n validKeys.add(cacheKey);\n\n // Try cached element first to avoid full resolution chain.\n const cachedRef = this.anchorCache.get(cacheKey);\n const cachedEl = cachedRef?.deref();\n let resolved: ReturnType<typeof resolveAnnotation>;\n\n if (cachedEl?.isConnected) {\n const anchorRect = cachedEl.getBoundingClientRect();\n const r = toRectData(annotation);\n resolved = {\n element: cachedEl,\n rect: new DOMRect(\n anchorRect.left + r.xPct * anchorRect.width,\n anchorRect.top + r.yPct * anchorRect.height,\n r.wPct * anchorRect.width,\n r.hPct * anchorRect.height,\n ),\n confidence: 1,\n strategy: \"css\",\n };\n } else {\n resolved = resolveAnnotation(toAnchorData(annotation), toRectData(annotation));\n if (resolved?.element) {\n this.anchorCache.set(cacheKey, new WeakRef(resolved.element));\n }\n }\n\n if (!resolved) {\n markerEl.style.display = \"none\";\n continue;\n }\n\n const pos = markerPosition(resolved.rect);\n entry.baseTop = pos.top;\n entry.baseLeft = pos.left;\n markerEl.style.display = \"flex\";\n this.applyConfidenceStyle(markerEl, resolved.confidence, entry.feedback);\n }\n }\n\n // Prune cache keys from deleted feedbacks to prevent memory leak.\n for (const key of this.anchorCache.keys()) {\n if (!validKeys.has(key)) this.anchorCache.delete(key);\n }\n\n this.applyClusterPositions();\n }\n\n private applyClusterPositions(): void {\n for (const cluster of this.clusters) {\n if (cluster.expanded) {\n this.applyFanPositions(cluster);\n } else {\n this.applyStackPositions(cluster);\n }\n }\n }\n\n render(feedbacks: FeedbackResponse[]): void {\n this.clear();\n feedbacks.forEach((feedback, i) => {\n const entry = this.buildEntry(feedback, i + 1);\n this.entries.push(entry);\n });\n this.buildClusters();\n }\n\n addFeedback(feedback: FeedbackResponse, index: number): void {\n const entry = this.buildEntry(feedback, index);\n for (const m of entry.elements) {\n m.style.animation = \"sp-marker-in 0.35s cubic-bezier(0.34,1.56,0.64,1) both\";\n }\n this.entries.push(entry);\n this.buildClusters();\n }\n\n private buildEntry(feedback: FeedbackResponse, index: number): MarkerEntry {\n const entry: MarkerEntry = { feedback, elements: [], baseTop: 0, baseLeft: 0 };\n for (const annotation of feedback.annotations) {\n const resolved = resolveAnnotation(toAnchorData(annotation), toRectData(annotation));\n if (!resolved) continue;\n const pos = markerPosition(resolved.rect);\n entry.baseTop = pos.top;\n entry.baseLeft = pos.left;\n const marker = this.createMarker(index, feedback, pos);\n this.applyConfidenceStyle(marker, resolved.confidence, feedback);\n this.container.appendChild(marker);\n entry.elements.push(marker);\n }\n return entry;\n }\n\n private buildClusters(): void {\n for (const badge of this.container.querySelectorAll<HTMLElement>(\".sp-cluster-badge\")) {\n badge.remove();\n }\n\n const allItems: { entry: MarkerEntry; elIdx: number }[] = [];\n for (const entry of this.entries) {\n for (let i = 0; i < entry.elements.length; i++) {\n allItems.push({ entry, elIdx: i });\n }\n }\n\n const used = new Set<number>();\n this.clusters = [];\n\n for (let i = 0; i < allItems.length; i++) {\n if (used.has(i)) continue;\n const itemI = allItems[i];\n if (!itemI) continue;\n const cluster: Cluster = {\n entries: [itemI.entry],\n elementIndices: [itemI.elIdx],\n expanded: false,\n };\n used.add(i);\n\n for (let j = i + 1; j < allItems.length; j++) {\n if (used.has(j)) continue;\n const a = itemI.entry;\n const itemJ = allItems[j];\n if (!itemJ) continue;\n const b = itemJ.entry;\n const dist = Math.sqrt((a.baseLeft - b.baseLeft) ** 2 + (a.baseTop - b.baseTop) ** 2);\n if (dist < CLUSTER_DISTANCE) {\n cluster.entries.push(b);\n cluster.elementIndices.push(itemJ.elIdx);\n used.add(j);\n }\n }\n\n this.clusters.push(cluster);\n }\n\n for (const cluster of this.clusters) {\n if (cluster.entries.length <= 1) continue;\n this.applyStackPositions(cluster);\n this.addClusterBadge(cluster);\n }\n }\n\n private applyStackPositions(cluster: Cluster): void {\n const first = cluster.entries[0];\n if (!first) return;\n const { baseTop, baseLeft } = first;\n const isSolo = cluster.entries.length <= 1;\n for (let i = 0; i < cluster.entries.length; i++) {\n const m = clusterMarker(cluster, i);\n if (!m) continue;\n m.style.top = `${baseTop + (isSolo ? 0 : i * 3)}px`;\n m.style.left = `${baseLeft + (isSolo ? 0 : i * 3)}px`;\n m.style.zIndex = String(i + 1);\n }\n }\n\n private applyFanPositions(cluster: Cluster): void {\n const first = cluster.entries[0];\n if (!first) return;\n const { baseTop, baseLeft } = first;\n const count = cluster.entries.length;\n const totalWidth = (count - 1) * FAN_SPACING;\n const startLeft = baseLeft - totalWidth / 2;\n\n for (let i = 0; i < count; i++) {\n const m = clusterMarker(cluster, i);\n if (!m) continue;\n m.style.top = `${baseTop}px`;\n m.style.left = `${startLeft + i * FAN_SPACING}px`;\n m.style.zIndex = String(10 + i);\n }\n }\n\n private addClusterBadge(cluster: Cluster): void {\n const topMarker = clusterMarker(cluster, cluster.entries.length - 1);\n if (!topMarker) return;\n const badge = el(\"div\", {\n class: \"sp-cluster-badge\",\n style: `\n position:absolute;top:-6px;right:-6px;\n min-width:16px;height:16px;padding:0 4px;\n border-radius:9999px;\n background:${this.colors.accent};color:#fff;\n font-size:10px;font-weight:700;\n display:flex;align-items:center;justify-content:center;\n border:1.5px solid #fff;\n pointer-events:none;\n font-family:\"Inter\",system-ui,-apple-system,sans-serif;\n line-height:1;\n `,\n });\n setText(badge, String(cluster.entries.length));\n topMarker.appendChild(badge);\n }\n\n private setBadgesVisible(cluster: Cluster, visible: boolean): void {\n for (let i = 0; i < cluster.entries.length; i++) {\n const badge = clusterMarker(cluster, i)?.querySelector(\".sp-cluster-badge\") as HTMLElement | null;\n if (badge) badge.style.display = visible ? \"flex\" : \"none\";\n }\n }\n\n private findCluster(marker: HTMLElement): Cluster | null {\n for (const cluster of this.clusters) {\n if (cluster.entries.length <= 1) continue;\n for (let i = 0; i < cluster.entries.length; i++) {\n if (clusterMarker(cluster, i) === marker) return cluster;\n }\n }\n return null;\n }\n\n private handleClusterClick(marker: HTMLElement, e: MouseEvent): boolean {\n const cluster = this.findCluster(marker);\n if (!cluster) return false;\n if (!cluster.expanded) {\n e.stopPropagation();\n this.collapseAllClusters();\n cluster.expanded = true;\n this.applyFanPositions(cluster);\n this.setBadgesVisible(cluster, false);\n return true;\n }\n return false;\n }\n\n private collapseCluster(cluster: Cluster): void {\n if (!cluster.expanded) return;\n cluster.expanded = false;\n this.applyStackPositions(cluster);\n this.setBadgesVisible(cluster, true);\n }\n\n private collapseAllClusters(): void {\n for (const cluster of this.clusters) {\n this.collapseCluster(cluster);\n }\n }\n\n private applyConfidenceStyle(marker: HTMLElement, confidence: number, feedback: FeedbackResponse): void {\n const isResolved = feedback.status === \"resolved\";\n if (confidence < LOW_CONFIDENCE_THRESHOLD && !isResolved) {\n marker.style.borderStyle = \"dashed\";\n marker.style.opacity = \"0.7\";\n marker.title = this.t(\"marker.approximate\").replace(\"{confidence}\", String(Math.round(confidence * 100)));\n } else {\n marker.style.borderStyle = \"solid\";\n marker.style.opacity = \"1\";\n marker.title = \"\";\n }\n }\n\n private createMarker(number: number, feedback: FeedbackResponse, pos: { top: number; left: number }): HTMLElement {\n const typeColor = getTypeColor(feedback.type, this.colors);\n const isResolved = feedback.status === \"resolved\";\n\n const marker = el(\"div\", {\n style: `\n position:absolute;\n top:${pos.top}px;\n left:${pos.left}px;\n width:26px;height:26px;\n border-radius:50%;\n background:${isResolved ? \"rgba(241,245,249,0.9)\" : \"rgba(255,255,255,0.92)\"};\n border:2px solid ${isResolved ? \"#cbd5e1\" : typeColor};\n display:flex;align-items:center;justify-content:center;\n font-family:\"Inter\",system-ui,-apple-system,sans-serif;\n font-size:11px;font-weight:700;\n color:${isResolved ? \"#94a3b8\" : typeColor};\n cursor:pointer;pointer-events:auto;\n box-shadow:${isResolved ? \"0 2px 8px rgba(0,0,0,0.06)\" : `0 2px 12px ${typeColor}25, 0 2px 6px rgba(0,0,0,0.06)`};\n transition:top 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), left 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), transform 0.15s ease, box-shadow 0.15s ease;\n user-select:none;\n -webkit-font-smoothing:antialiased;\n `,\n });\n marker.dataset.feedbackId = feedback.id;\n marker.setAttribute(\"tabindex\", \"0\");\n marker.setAttribute(\"role\", \"button\");\n const truncatedMessage = feedback.message.length > 60 ? `${feedback.message.slice(0, 60)}...` : feedback.message;\n const ariaLabel = this.t(\"marker.aria\")\n .replace(\"{number}\", String(number))\n .replace(\"{type}\", getTypeLabel(feedback.type, this.t))\n .replace(\"{message}\", truncatedMessage);\n marker.setAttribute(\"aria-label\", ariaLabel);\n marker.setAttribute(\"aria-describedby\", this.tooltip.tooltipId);\n setText(marker, isResolved ? \"\\u2713\" : String(number));\n\n marker.addEventListener(\"mouseenter\", () => {\n marker.style.transform = \"scale(1.2)\";\n marker.style.boxShadow = isResolved\n ? \"0 4px 16px rgba(0,0,0,0.1)\"\n : `0 4px 20px ${typeColor}35, 0 4px 12px rgba(0,0,0,0.08)`;\n this.tooltip.show(feedback, marker.getBoundingClientRect());\n if (!this.pinnedFeedback) this.showHighlight(feedback);\n });\n\n marker.addEventListener(\"mouseleave\", () => {\n marker.style.transform = \"scale(1)\";\n marker.style.boxShadow = isResolved\n ? \"0 2px 8px rgba(0,0,0,0.06)\"\n : `0 2px 12px ${typeColor}25, 0 2px 6px rgba(0,0,0,0.06)`;\n this.tooltip.scheduleHide();\n if (!this.pinnedFeedback) this.clearHighlight();\n });\n\n const activateMarker = (e: MouseEvent | KeyboardEvent) => {\n if (e instanceof MouseEvent && this.handleClusterClick(marker, e)) return;\n this.pinHighlight(feedback);\n this.bus.emit(\"panel:toggle\", true);\n marker.dispatchEvent(\n new CustomEvent(\"sp-marker-click\", {\n detail: { feedbackId: feedback.id },\n bubbles: true,\n }),\n );\n };\n\n marker.addEventListener(\"click\", (e) => activateMarker(e));\n marker.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n activateMarker(e);\n }\n });\n\n return marker;\n }\n\n highlight(feedbackId: string): void {\n for (const entry of this.entries) {\n if (entry.feedback.id === feedbackId) {\n for (const markerEl of entry.elements) {\n markerEl.style.animation = \"sp-pulse-ring 0.7s ease-out\";\n markerEl.addEventListener(\n \"animationend\",\n () => {\n markerEl.style.animation = \"\";\n },\n { once: true },\n );\n }\n }\n }\n }\n\n showHighlight(feedback: FeedbackResponse): void {\n this.removeHighlightElements();\n for (const annotation of feedback.annotations) {\n const resolved = resolveAnnotation(toAnchorData(annotation), toRectData(annotation));\n if (!resolved) continue;\n\n const typeColor = getTypeColor(feedback.type, this.colors);\n const rect = resolved.rect;\n const highlight = el(\"div\", {\n style: `\n position:absolute;\n top:${rect.top + window.scrollY}px;\n left:${rect.left + window.scrollX}px;\n width:${rect.width}px;height:${rect.height}px;\n border:2px solid ${typeColor};\n background:${typeColor}0c;\n border-radius:8px;\n pointer-events:none;z-index:-1;\n opacity:0;\n box-shadow:0 0 16px ${typeColor}20;\n transition:opacity ${HIGHLIGHT_FADE}ms ease;\n `,\n });\n this.container.appendChild(highlight);\n this.highlightElements.push(highlight);\n void highlight.offsetHeight; // Force reflow for CSS transition\n highlight.style.opacity = \"1\";\n }\n }\n\n pinHighlight(feedback: FeedbackResponse): void {\n this.unpinHighlight();\n this.showHighlight(feedback);\n this.pinnedFeedback = feedback;\n this.onDocumentClick = (e: MouseEvent) => {\n if (this.container.contains(e.target as Node)) return;\n this.unpinHighlight();\n };\n document.addEventListener(\"click\", this.onDocumentClick, { capture: true });\n }\n\n private unpinHighlight(): void {\n if (this.onDocumentClick) {\n document.removeEventListener(\"click\", this.onDocumentClick, { capture: true });\n this.onDocumentClick = null;\n }\n this.pinnedFeedback = null;\n this.clearHighlight();\n }\n\n private clearHighlight(): void {\n for (const h of this.highlightElements) {\n h.style.opacity = \"0\";\n setTimeout(() => h.remove(), HIGHLIGHT_FADE);\n }\n this.highlightElements = [];\n }\n\n private removeHighlightElements(): void {\n for (const h of this.highlightElements) h.remove();\n this.highlightElements = [];\n }\n\n clear(): void {\n this.unpinHighlight();\n this.container.replaceChildren();\n this.entries = [];\n this.clusters = [];\n this.anchorCache.clear();\n }\n\n destroy(): void {\n this.unpinHighlight();\n if (this.repositionTimer) {\n if (\"cancelIdleCallback\" in window) {\n window.cancelIdleCallback(this.repositionTimer);\n }\n clearTimeout(this.repositionTimer);\n }\n if (this.resizeHandler) window.removeEventListener(\"resize\", this.resizeHandler);\n if (this.scrollHandler) window.removeEventListener(\"scroll\", this.scrollHandler, { capture: true });\n if (this.onDocumentClickForClusters) document.removeEventListener(\"click\", this.onDocumentClickForClusters);\n this.mutationObserver?.disconnect();\n this.container.remove();\n }\n}\n","import type { FeedbackResponse } from \"@siteping/core\";\nimport { el, parseSvg, setText } from \"./dom-utils.js\";\nimport type { ThemeColors } from \"./styles/theme.js\";\n\n// ---------------------------------------------------------------------------\n// Icons\n// ---------------------------------------------------------------------------\n\nexport const ICON_EXPORT = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><polyline points=\"7 10 12 15 17 10\"/><line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"3\"/></svg>`;\n\nconst ICON_CSV = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/><line x1=\"3\" y1=\"9\" x2=\"21\" y2=\"9\"/><line x1=\"3\" y1=\"15\" x2=\"21\" y2=\"15\"/><line x1=\"9\" y1=\"3\" x2=\"9\" y2=\"21\"/><line x1=\"15\" y1=\"3\" x2=\"15\" y2=\"21\"/></svg>`;\n\nconst ICON_JSON = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M8 3H6a2 2 0 0 0-2 2v4a2 2 0 0 1-2 2 2 2 0 0 1 2 2v4a2 2 0 0 0 2 2h2\"/><path d=\"M16 3h2a2 2 0 0 1 2 2v4a2 2 0 0 0 2 2 2 2 0 0 0-2 2v4a2 2 0 0 1-2 2h-2\"/></svg>`;\n\n// ---------------------------------------------------------------------------\n// i18n\n// ---------------------------------------------------------------------------\n\nexport const EXPORT_I18N_EN = {\n \"export.label\": \"Export\",\n \"export.csv\": \"Export CSV\",\n \"export.json\": \"Export JSON\",\n};\n\nexport const EXPORT_I18N_FR = {\n \"export.label\": \"Exporter\",\n \"export.csv\": \"Exporter CSV\",\n \"export.json\": \"Exporter JSON\",\n};\n\n// ---------------------------------------------------------------------------\n// CSS\n// ---------------------------------------------------------------------------\n\nexport const EXPORT_CSS = `\n /* ============================\n Export Button & Menu\n ============================ */\n\n .sp-export-btn {\n padding: 5px 12px;\n border-radius: var(--sp-radius-full);\n border: 1px solid var(--sp-border);\n background: transparent;\n color: var(--sp-text-tertiary);\n font-family: var(--sp-font);\n font-size: 11px;\n font-weight: 500;\n cursor: pointer;\n display: flex;\n align-items: center;\n gap: 4px;\n transition: all 0.2s ease;\n position: relative;\n }\n\n .sp-export-btn svg {\n width: 13px;\n height: 13px;\n }\n\n .sp-export-btn:hover {\n border-color: var(--sp-accent);\n color: var(--sp-accent);\n background: var(--sp-accent-light);\n }\n\n .sp-export-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n pointer-events: none;\n }\n\n .sp-export-menu {\n position: absolute;\n top: calc(100% + 6px);\n right: 0;\n min-width: 180px;\n padding: 4px;\n border-radius: var(--sp-radius);\n background: var(--sp-glass-bg-heavy);\n backdrop-filter: blur(var(--sp-blur));\n -webkit-backdrop-filter: blur(var(--sp-blur));\n border: 1px solid var(--sp-glass-border);\n box-shadow: var(--sp-shadow-lg);\n z-index: 10;\n opacity: 0;\n transform: translateY(-4px) scale(0.97);\n transition: opacity 0.15s ease, transform 0.15s ease;\n pointer-events: none;\n }\n\n .sp-export-menu--open {\n opacity: 1;\n transform: translateY(0) scale(1);\n pointer-events: auto;\n }\n\n .sp-export-option {\n display: flex;\n align-items: center;\n gap: 10px;\n width: 100%;\n padding: 8px 16px;\n border: none;\n border-radius: 8px;\n background: transparent;\n color: var(--sp-text-secondary);\n font-family: var(--sp-font);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n text-align: left;\n }\n\n .sp-export-option:hover,\n .sp-export-option:focus-visible {\n background: var(--sp-accent-light);\n color: var(--sp-accent);\n }\n\n .sp-export-option-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n }\n\n .sp-export-option-icon svg {\n width: 16px;\n height: 16px;\n }\n\n .sp-export-option-label {\n flex: 1;\n }\n\n @media (forced-colors: active) {\n .sp-export-btn,\n .sp-export-option,\n .sp-export-menu {\n border: 2px solid ButtonText !important;\n background: Canvas !important;\n color: ButtonText !important;\n }\n\n .sp-export-btn:focus-visible,\n .sp-export-option:focus-visible {\n outline: 3px solid Highlight !important;\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// CSV / JSON conversion\n// ---------------------------------------------------------------------------\n\nconst CSV_COLUMNS = [\n \"id\",\n \"type\",\n \"status\",\n \"message\",\n \"url\",\n \"authorName\",\n \"authorEmail\",\n \"createdAt\",\n \"resolvedAt\",\n \"viewport\",\n] as const;\n\n/** Escape a value for CSV: wrap in double-quotes if it contains commas, newlines, or quotes. */\nfunction escapeCsvField(value: string): string {\n if (value.includes('\"') || value.includes(\",\") || value.includes(\"\\n\") || value.includes(\"\\r\")) {\n return `\"${value.replace(/\"/g, '\"\"')}\"`;\n }\n return value;\n}\n\n/** Convert feedbacks to CSV string */\nexport function feedbacksToCsv(feedbacks: FeedbackResponse[]): string {\n const header = CSV_COLUMNS.join(\",\");\n const rows = feedbacks.map((fb) =>\n CSV_COLUMNS.map((col) => {\n const raw = fb[col];\n return escapeCsvField(raw == null ? \"\" : String(raw));\n }).join(\",\"),\n );\n return [header, ...rows].join(\"\\n\");\n}\n\n/** Convert feedbacks to formatted JSON string */\nexport function feedbacksToJson(feedbacks: FeedbackResponse[]): string {\n return JSON.stringify(feedbacks, null, 2);\n}\n\n// ---------------------------------------------------------------------------\n// Download helper\n// ---------------------------------------------------------------------------\n\n/** Trigger browser download of a string as file */\nexport function downloadFile(content: string, filename: string, mimeType: string): void {\n const blob = new Blob([content], { type: mimeType });\n const url = URL.createObjectURL(blob);\n const anchor = document.createElement(\"a\");\n anchor.href = url;\n anchor.download = filename;\n anchor.style.display = \"none\";\n document.body.appendChild(anchor);\n anchor.click();\n // Clean up after a tick to ensure the download starts\n requestAnimationFrame(() => {\n URL.revokeObjectURL(url);\n anchor.remove();\n });\n}\n\n// ---------------------------------------------------------------------------\n// ExportButton component\n// ---------------------------------------------------------------------------\n\nexport class ExportButton {\n readonly element: HTMLElement;\n\n private menu: HTMLElement;\n private isOpen = false;\n private onDocumentClick: (e: MouseEvent) => void;\n\n constructor(\n _colors: ThemeColors,\n private readonly getFeedbacks: () => FeedbackResponse[],\n ) {\n // Wrapper for relative positioning of the menu\n this.element = el(\"div\", { style: \"position: relative; display: inline-flex;\" });\n\n // Trigger button — matches .sp-btn-delete-all pill style\n const btn = document.createElement(\"button\");\n btn.className = \"sp-export-btn\";\n btn.setAttribute(\"aria-haspopup\", \"true\");\n btn.setAttribute(\"aria-expanded\", \"false\");\n btn.appendChild(parseSvg(ICON_EXPORT));\n const label = document.createElement(\"span\");\n setText(label, EXPORT_I18N_EN[\"export.label\"]);\n btn.appendChild(label);\n btn.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n this.toggle();\n });\n\n // Dropdown menu\n this.menu = el(\"div\", { class: \"sp-export-menu\" });\n this.menu.setAttribute(\"role\", \"menu\");\n\n // CSV option\n const csvOption = this.createOption(ICON_CSV, EXPORT_I18N_EN[\"export.csv\"], () => {\n this.exportAs(\"csv\");\n });\n\n // JSON option\n const jsonOption = this.createOption(ICON_JSON, EXPORT_I18N_EN[\"export.json\"], () => {\n this.exportAs(\"json\");\n });\n\n this.menu.appendChild(csvOption);\n this.menu.appendChild(jsonOption);\n\n this.element.appendChild(btn);\n this.element.appendChild(this.menu);\n\n // Close on outside click\n this.onDocumentClick = (e: MouseEvent) => {\n if (this.isOpen && !this.element.contains(e.target as Node)) {\n this.close();\n }\n };\n document.addEventListener(\"click\", this.onDocumentClick, true);\n }\n\n /** Update the button label (used for i18n switching) */\n setLabels(labels: typeof EXPORT_I18N_EN): void {\n const btn = this.element.querySelector<HTMLElement>(\".sp-export-btn\");\n if (btn) {\n const span = btn.querySelector(\"span\");\n if (span) setText(span, labels[\"export.label\"]);\n }\n\n const options = this.menu.querySelectorAll<HTMLElement>(\".sp-export-option-label\");\n if (options[0]) setText(options[0], labels[\"export.csv\"]);\n if (options[1]) setText(options[1], labels[\"export.json\"]);\n }\n\n private createOption(iconSvg: string, labelText: string, onClick: () => void): HTMLButtonElement {\n const option = document.createElement(\"button\");\n option.className = \"sp-export-option\";\n option.setAttribute(\"role\", \"menuitem\");\n\n const iconWrap = el(\"span\", { class: \"sp-export-option-icon\" });\n iconWrap.appendChild(parseSvg(iconSvg));\n\n const labelEl = el(\"span\", { class: \"sp-export-option-label\" });\n setText(labelEl, labelText);\n\n option.appendChild(iconWrap);\n option.appendChild(labelEl);\n\n option.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n onClick();\n this.close();\n });\n\n return option;\n }\n\n private toggle(): void {\n this.isOpen ? this.close() : this.open();\n }\n\n private open(): void {\n this.isOpen = true;\n this.menu.classList.add(\"sp-export-menu--open\");\n const btn = this.element.querySelector<HTMLButtonElement>(\".sp-export-btn\");\n btn?.setAttribute(\"aria-expanded\", \"true\");\n }\n\n private close(): void {\n this.isOpen = false;\n this.menu.classList.remove(\"sp-export-menu--open\");\n const btn = this.element.querySelector<HTMLButtonElement>(\".sp-export-btn\");\n btn?.setAttribute(\"aria-expanded\", \"false\");\n }\n\n private exportAs(format: \"csv\" | \"json\"): void {\n const feedbacks = this.getFeedbacks();\n if (feedbacks.length === 0) return;\n\n const projectName = feedbacks[0]?.projectName ?? \"feedbacks\";\n const date = new Date().toISOString().slice(0, 10);\n const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, \"_\");\n\n if (format === \"csv\") {\n const content = feedbacksToCsv(feedbacks);\n downloadFile(content, `feedbacks-${safeName}-${date}.csv`, \"text/csv;charset=utf-8\");\n } else {\n const content = feedbacksToJson(feedbacks);\n downloadFile(content, `feedbacks-${safeName}-${date}.json`, \"application/json;charset=utf-8\");\n }\n }\n\n destroy(): void {\n document.removeEventListener(\"click\", this.onDocumentClick, true);\n this.element.remove();\n }\n}\n","/**\n * Bulk Actions system for the feedback panel.\n *\n * Allows multi-select on feedback cards with a floating action bar\n * for batch resolve/delete operations. Glassmorphism design with\n * spring animations and smooth transitions.\n */\n\nimport { el, parseSvg, setText } from \"./dom-utils.js\";\nimport type { ThemeColors } from \"./styles/theme.js\";\n\n// ---------------------------------------------------------------------------\n// SVG Icons\n// ---------------------------------------------------------------------------\n\nexport const ICON_CHECKBOX = `<svg viewBox=\"0 0 18 18\" fill=\"none\" aria-hidden=\"true\"><rect x=\"1\" y=\"1\" width=\"16\" height=\"16\" rx=\"4\" stroke=\"currentColor\" stroke-width=\"2\"/></svg>`;\n\nexport const ICON_CHECKBOX_CHECKED = `<svg viewBox=\"0 0 18 18\" fill=\"none\" aria-hidden=\"true\"><rect x=\"1\" y=\"1\" width=\"16\" height=\"16\" rx=\"4\" fill=\"url(#sp-cb-grad)\" stroke=\"none\"/><polyline points=\"5 9 8 12 13 6\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><defs><linearGradient id=\"sp-cb-grad\" x1=\"0\" y1=\"0\" x2=\"18\" y2=\"18\" gradientUnits=\"userSpaceOnUse\"><stop offset=\"0%\" stop-color=\"var(--sp-accent)\"/><stop offset=\"100%\" stop-color=\"var(--sp-accent-dark)\"/></linearGradient></defs></svg>`;\n\n// ---------------------------------------------------------------------------\n// I18N\n// ---------------------------------------------------------------------------\n\nexport const BULK_I18N_EN = {\n \"bulk.selectAll\": \"Select all\",\n \"bulk.selected\": \"{count} selected\",\n \"bulk.resolve\": \"Resolve\",\n \"bulk.delete\": \"Delete\",\n \"bulk.deselect\": \"Deselect\",\n};\n\nexport const BULK_I18N_FR = {\n \"bulk.selectAll\": \"Tout sélectionner\",\n \"bulk.selected\": \"{count} sélectionné(s)\",\n \"bulk.resolve\": \"Résoudre\",\n \"bulk.delete\": \"Supprimer\",\n \"bulk.deselect\": \"Désélectionner\",\n};\n\n// ---------------------------------------------------------------------------\n// CSS\n// ---------------------------------------------------------------------------\n\nexport const BULK_CSS = `\n /* ============================\n Bulk Checkbox\n ============================ */\n\n .sp-bulk-checkbox {\n position: relative;\n width: 16px;\n height: 16px;\n flex-shrink: 0;\n cursor: pointer;\n border-radius: 4px;\n color: var(--sp-border);\n opacity: 0;\n transition: opacity 0.15s ease, color 0.15s ease, transform 0.15s ease;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n }\n\n .sp-bulk-checkbox svg {\n width: 16px;\n height: 16px;\n display: block;\n }\n\n .sp-bulk-checkbox:hover {\n color: var(--sp-accent);\n transform: scale(1.1);\n }\n\n .sp-bulk-checkbox--checked {\n color: var(--sp-accent);\n opacity: 1 !important;\n filter: drop-shadow(0 0 4px var(--sp-accent-glow));\n }\n\n /* Show checkboxes when hovering a card */\n .sp-card:hover .sp-bulk-checkbox {\n opacity: 1;\n }\n\n /* When any card has selection, show ALL checkboxes */\n .sp-list--has-selection .sp-bulk-checkbox {\n opacity: 1;\n }\n\n /* ============================\n Card Selected State\n ============================ */\n\n .sp-card--selected {\n border-left: 3px solid var(--sp-accent) !important;\n background: var(--sp-accent-light) !important;\n }\n\n .sp-card--selected:hover {\n background: var(--sp-accent-light) !important;\n }\n\n /* ============================\n Select All Bar\n ============================ */\n\n .sp-bulk-select-all {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n margin-bottom: 4px;\n border-radius: var(--sp-radius);\n background: transparent;\n cursor: pointer;\n opacity: 0;\n transition: opacity 0.2s ease, background 0.2s ease;\n user-select: none;\n font-family: var(--sp-font);\n font-size: 12px;\n font-weight: 500;\n color: var(--sp-text-secondary);\n }\n\n .sp-bulk-select-all:hover {\n background: var(--sp-bg-hover);\n }\n\n /* Show select-all on list hover or when selections exist */\n .sp-list:hover .sp-bulk-select-all,\n .sp-list--has-selection .sp-bulk-select-all {\n opacity: 1;\n }\n\n .sp-bulk-select-all .sp-bulk-checkbox {\n opacity: 1;\n }\n\n /* ============================\n Floating Action Bar\n ============================ */\n\n @keyframes sp-bulk-bar-in {\n from {\n transform: translateY(100%) scale(0.95);\n opacity: 0;\n }\n to {\n transform: translateY(0) scale(1);\n opacity: 1;\n }\n }\n\n @keyframes sp-bulk-bar-out {\n from {\n transform: translateY(0) scale(1);\n opacity: 1;\n }\n to {\n transform: translateY(100%) scale(0.95);\n opacity: 0;\n }\n }\n\n .sp-bulk-bar {\n position: absolute;\n bottom: 16px;\n left: 16px;\n right: 16px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding: 12px 16px;\n border-radius: 16px;\n background: var(--sp-glass-bg-heavy);\n backdrop-filter: blur(var(--sp-blur-heavy));\n -webkit-backdrop-filter: blur(var(--sp-blur-heavy));\n border: 1px solid var(--sp-glass-border);\n box-shadow: var(--sp-shadow-xl);\n z-index: 10;\n pointer-events: none;\n opacity: 0;\n transform: translateY(100%) scale(0.95);\n transition: transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1),\n opacity 0.25s ease;\n font-family: var(--sp-font);\n }\n\n .sp-bulk-bar--visible {\n pointer-events: auto;\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n\n .sp-bulk-bar-count {\n font-size: 13px;\n font-weight: 600;\n color: var(--sp-text);\n white-space: nowrap;\n letter-spacing: -0.01em;\n }\n\n .sp-bulk-bar-actions {\n display: flex;\n align-items: center;\n gap: 6px;\n }\n\n .sp-bulk-btn-resolve,\n .sp-bulk-btn-delete {\n padding: 7px 14px;\n border-radius: var(--sp-radius-full);\n border: 1.5px solid transparent;\n background: transparent;\n font-family: var(--sp-font);\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n display: flex;\n align-items: center;\n gap: 5px;\n transition: all 0.2s ease;\n white-space: nowrap;\n }\n\n .sp-bulk-btn-resolve {\n color: #22c55e;\n border-color: #22c55e;\n }\n\n .sp-bulk-btn-resolve:hover {\n background: rgba(34, 197, 94, 0.1);\n box-shadow: 0 0 12px rgba(34, 197, 94, 0.15);\n }\n\n .sp-bulk-btn-resolve:active {\n transform: scale(0.96);\n transition-duration: 0.1s;\n }\n\n .sp-bulk-btn-delete {\n color: #ef4444;\n border-color: #ef4444;\n }\n\n .sp-bulk-btn-delete:hover {\n background: rgba(239, 68, 68, 0.1);\n box-shadow: 0 0 12px rgba(239, 68, 68, 0.15);\n }\n\n .sp-bulk-btn-delete:active {\n transform: scale(0.96);\n transition-duration: 0.1s;\n }\n\n .sp-bulk-btn-resolve:disabled,\n .sp-bulk-btn-delete:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n pointer-events: none;\n }\n\n .sp-bulk-btn-deselect {\n width: 28px;\n height: 28px;\n border-radius: var(--sp-radius-full);\n border: 1px solid var(--sp-border);\n background: transparent;\n color: var(--sp-text-tertiary);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.2s ease;\n flex-shrink: 0;\n padding: 0;\n }\n\n .sp-bulk-btn-deselect:hover {\n background: var(--sp-bg-hover);\n color: var(--sp-text);\n border-color: var(--sp-text-tertiary);\n }\n\n .sp-bulk-btn-deselect:active {\n transform: scale(0.92);\n transition-duration: 0.1s;\n }\n\n .sp-bulk-btn-deselect svg {\n width: 12px;\n height: 12px;\n }\n\n /* Spinner inside bulk bar buttons */\n .sp-bulk-btn-resolve .sp-spinner,\n .sp-bulk-btn-delete .sp-spinner {\n width: 14px;\n height: 14px;\n }\n\n /* ============================\n Forced Colors / High Contrast\n ============================ */\n\n @media (forced-colors: active) {\n .sp-bulk-checkbox,\n .sp-bulk-btn-resolve,\n .sp-bulk-btn-delete,\n .sp-bulk-btn-deselect,\n .sp-bulk-bar {\n border: 2px solid ButtonText !important;\n background: Canvas !important;\n color: ButtonText !important;\n }\n\n .sp-bulk-checkbox--checked {\n background: Highlight !important;\n color: HighlightText !important;\n }\n\n .sp-card--selected {\n border-left: 4px solid Highlight !important;\n }\n }\n\n /* ============================\n Reduced Motion\n ============================ */\n\n @media (prefers-reduced-motion: reduce) {\n .sp-bulk-bar {\n transition-duration: 0.01ms !important;\n }\n\n .sp-bulk-checkbox {\n transition-duration: 0.01ms !important;\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Callbacks\n// ---------------------------------------------------------------------------\n\nexport interface BulkActionCallbacks {\n onResolve: (ids: string[]) => Promise<void>;\n onDelete: (ids: string[]) => Promise<void>;\n}\n\n// ---------------------------------------------------------------------------\n// BulkActions Class\n// ---------------------------------------------------------------------------\n\nexport class BulkActions {\n /** The floating action bar element — append to panel root */\n readonly barElement: HTMLElement;\n\n private selected = new Set<string>();\n private checkboxMap = new Map<string, HTMLElement>();\n private countLabel: HTMLElement;\n private resolveBtn: HTMLButtonElement;\n private deleteBtn: HTMLButtonElement;\n private selectAllCheckbox: HTMLElement | null = null;\n private listContainer: HTMLElement | null = null;\n private isProcessing = false;\n private readonly i18n: typeof BULK_I18N_EN;\n\n constructor(\n _colors: ThemeColors,\n private readonly callbacks: BulkActionCallbacks,\n locale?: string,\n ) {\n this.i18n = locale === \"fr\" ? BULK_I18N_FR : BULK_I18N_EN;\n // ----- Build floating bar -----\n this.barElement = el(\"div\", { class: \"sp-bulk-bar\" });\n this.barElement.setAttribute(\"role\", \"toolbar\");\n this.barElement.setAttribute(\"aria-label\", \"Bulk actions\");\n\n // Left: count label\n this.countLabel = el(\"span\", { class: \"sp-bulk-bar-count\" });\n setText(this.countLabel, this.i18n[\"bulk.selected\"].replace(\"{count}\", \"0\"));\n\n // Right: action buttons\n const actions = el(\"div\", { class: \"sp-bulk-bar-actions\" });\n\n this.resolveBtn = document.createElement(\"button\");\n this.resolveBtn.className = \"sp-bulk-btn-resolve\";\n this.resolveBtn.type = \"button\";\n this.resolveBtn.addEventListener(\"click\", () => this.handleResolve());\n\n this.deleteBtn = document.createElement(\"button\");\n this.deleteBtn.className = \"sp-bulk-btn-delete\";\n this.deleteBtn.type = \"button\";\n this.deleteBtn.addEventListener(\"click\", () => this.handleDelete());\n\n const deselectBtn = document.createElement(\"button\");\n deselectBtn.className = \"sp-bulk-btn-deselect\";\n deselectBtn.type = \"button\";\n deselectBtn.setAttribute(\"aria-label\", this.i18n[\"bulk.deselect\"]);\n deselectBtn.appendChild(\n parseSvg(\n `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/></svg>`,\n ),\n );\n deselectBtn.addEventListener(\"click\", () => this.deselectAll());\n\n actions.appendChild(this.resolveBtn);\n actions.appendChild(this.deleteBtn);\n actions.appendChild(deselectBtn);\n\n this.barElement.appendChild(this.countLabel);\n this.barElement.appendChild(actions);\n\n // Initial button text\n this.updateButtonLabels();\n }\n\n /** Create a checkbox element for a card. Returns the checkbox wrapper. */\n createCheckbox(feedbackId: string): HTMLElement {\n const wrapper = el(\"div\", { class: \"sp-bulk-checkbox\" });\n wrapper.setAttribute(\"role\", \"checkbox\");\n wrapper.setAttribute(\"aria-checked\", \"false\");\n wrapper.setAttribute(\"tabindex\", \"0\");\n wrapper.setAttribute(\"aria-label\", `Select feedback ${feedbackId}`);\n\n // Render unchecked icon\n wrapper.appendChild(parseSvg(ICON_CHECKBOX));\n\n // Click handler\n wrapper.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n this.toggle(feedbackId);\n });\n\n // Keyboard: space/enter to toggle\n wrapper.addEventListener(\"keydown\", (e) => {\n if ((e as KeyboardEvent).key === \" \" || (e as KeyboardEvent).key === \"Enter\") {\n e.preventDefault();\n e.stopPropagation();\n this.toggle(feedbackId);\n }\n });\n\n this.checkboxMap.set(feedbackId, wrapper);\n return wrapper;\n }\n\n /**\n * Create a \"Select all\" bar element.\n * The caller should insert this at the top of the list container.\n */\n createSelectAllBar(feedbackIds: string[], label: string): HTMLElement {\n const wrapper = el(\"div\", { class: \"sp-bulk-select-all\" });\n\n const checkbox = el(\"div\", { class: \"sp-bulk-checkbox\" });\n checkbox.appendChild(parseSvg(ICON_CHECKBOX));\n this.selectAllCheckbox = checkbox;\n\n const labelEl = el(\"span\");\n setText(labelEl, label);\n\n wrapper.appendChild(checkbox);\n wrapper.appendChild(labelEl);\n\n wrapper.addEventListener(\"click\", () => {\n // If all selected, deselect; otherwise select all\n if (this.selected.size === feedbackIds.length && feedbackIds.length > 0) {\n this.deselectAll();\n } else {\n this.selectAll(feedbackIds);\n }\n });\n\n return wrapper;\n }\n\n /** Set the list container reference (for toggling .sp-list--has-selection) */\n setListContainer(container: HTMLElement): void {\n this.listContainer = container;\n }\n\n /** Toggle selection for a feedback */\n toggle(feedbackId: string): void {\n if (this.isProcessing) return;\n\n if (this.selected.has(feedbackId)) {\n this.selected.delete(feedbackId);\n } else {\n this.selected.add(feedbackId);\n }\n this.updateCheckbox(feedbackId);\n this.updateBar();\n this.updateSelectAllCheckbox();\n this.updateListSelectionClass();\n this.updateCardSelectedState(feedbackId);\n }\n\n /** Select all from the given list */\n selectAll(feedbackIds: string[]): void {\n if (this.isProcessing) return;\n\n for (const id of feedbackIds) {\n this.selected.add(id);\n this.updateCheckbox(id);\n this.updateCardSelectedState(id);\n }\n this.updateBar();\n this.updateSelectAllCheckbox();\n this.updateListSelectionClass();\n }\n\n /** Clear all selections */\n deselectAll(): void {\n const prevSelected = [...this.selected];\n this.selected.clear();\n for (const id of prevSelected) {\n this.updateCheckbox(id);\n this.updateCardSelectedState(id);\n }\n this.updateBar();\n this.updateSelectAllCheckbox();\n this.updateListSelectionClass();\n }\n\n /** Get currently selected IDs */\n get selectedIds(): string[] {\n return [...this.selected];\n }\n\n /** Get selection count */\n get count(): number {\n return this.selected.size;\n }\n\n /** Whether any items are selected */\n get hasSelection(): boolean {\n return this.selected.size > 0;\n }\n\n /** Reset state (e.g., after feedbacks reload) */\n reset(): void {\n this.selected.clear();\n this.checkboxMap.clear();\n this.selectAllCheckbox = null;\n\n this.isProcessing = false;\n this.updateBar();\n this.updateListSelectionClass();\n }\n\n /** Destroy / cleanup */\n destroy(): void {\n this.selected.clear();\n this.checkboxMap.clear();\n this.selectAllCheckbox = null;\n\n this.listContainer = null;\n this.barElement.remove();\n }\n\n // -----------------------------------------------------------------------\n // Private\n // -----------------------------------------------------------------------\n\n /** Update the bar visibility and counts */\n private updateBar(): void {\n const count = this.selected.size;\n const visible = count > 0;\n\n this.barElement.classList.toggle(\"sp-bulk-bar--visible\", visible);\n setText(this.countLabel, this.i18n[\"bulk.selected\"].replace(\"{count}\", String(count)));\n this.updateButtonLabels();\n }\n\n private updateButtonLabels(): void {\n const count = this.selected.size;\n\n // Resolve button\n this.resolveBtn.replaceChildren();\n const resolveLabel = document.createElement(\"span\");\n setText(resolveLabel, count > 0 ? `${this.i18n[\"bulk.resolve\"]} ${count}` : this.i18n[\"bulk.resolve\"]);\n this.resolveBtn.appendChild(resolveLabel);\n\n // Delete button\n this.deleteBtn.replaceChildren();\n const deleteLabel = document.createElement(\"span\");\n setText(deleteLabel, count > 0 ? `${this.i18n[\"bulk.delete\"]} ${count}` : this.i18n[\"bulk.delete\"]);\n this.deleteBtn.appendChild(deleteLabel);\n }\n\n private updateCheckbox(feedbackId: string): void {\n const checkbox = this.checkboxMap.get(feedbackId);\n if (!checkbox) return;\n\n const isChecked = this.selected.has(feedbackId);\n checkbox.classList.toggle(\"sp-bulk-checkbox--checked\", isChecked);\n checkbox.setAttribute(\"aria-checked\", String(isChecked));\n\n // Swap SVG icon\n checkbox.replaceChildren();\n checkbox.appendChild(parseSvg(isChecked ? ICON_CHECKBOX_CHECKED : ICON_CHECKBOX));\n }\n\n private updateSelectAllCheckbox(): void {\n if (!this.selectAllCheckbox) return;\n\n const allSelected = this.selected.size > 0 && this.selected.size === this.checkboxMap.size;\n this.selectAllCheckbox.classList.toggle(\"sp-bulk-checkbox--checked\", allSelected);\n this.selectAllCheckbox.setAttribute(\"aria-checked\", String(allSelected));\n\n this.selectAllCheckbox.replaceChildren();\n this.selectAllCheckbox.appendChild(parseSvg(allSelected ? ICON_CHECKBOX_CHECKED : ICON_CHECKBOX));\n }\n\n private updateListSelectionClass(): void {\n if (!this.listContainer) return;\n this.listContainer.classList.toggle(\"sp-list--has-selection\", this.selected.size > 0);\n }\n\n private updateCardSelectedState(feedbackId: string): void {\n if (!this.listContainer) return;\n const escapedId = CSS.escape(feedbackId);\n const card = this.listContainer.querySelector<HTMLElement>(`[data-feedback-id=\"${escapedId}\"]`);\n if (card) {\n card.classList.toggle(\"sp-card--selected\", this.selected.has(feedbackId));\n }\n }\n\n private setButtonLoading(btn: HTMLButtonElement): () => void {\n const snapshot = Array.from(btn.childNodes).map((n) => n.cloneNode(true));\n btn.disabled = true;\n btn.replaceChildren(el(\"div\", { class: \"sp-spinner sp-spinner--sm\" }));\n return () => {\n btn.replaceChildren(...snapshot);\n btn.disabled = false;\n };\n }\n\n private async handleResolve(): Promise<void> {\n if (this.isProcessing || this.selected.size === 0) return;\n this.isProcessing = true;\n\n const ids = [...this.selected];\n const restoreResolve = this.setButtonLoading(this.resolveBtn);\n this.deleteBtn.disabled = true;\n\n try {\n await this.callbacks.onResolve(ids);\n this.reset();\n } catch {\n restoreResolve();\n this.deleteBtn.disabled = false;\n } finally {\n this.isProcessing = false;\n }\n }\n\n private async handleDelete(): Promise<void> {\n if (this.isProcessing || this.selected.size === 0) return;\n this.isProcessing = true;\n\n const ids = [...this.selected];\n const restoreDelete = this.setButtonLoading(this.deleteBtn);\n this.resolveBtn.disabled = true;\n\n try {\n await this.callbacks.onDelete(ids);\n this.reset();\n } catch {\n restoreDelete();\n this.resolveBtn.disabled = false;\n } finally {\n this.isProcessing = false;\n }\n }\n}\n","/**\n * Detail View for the feedback panel.\n *\n * Slides in from the right (panel-in-panel pattern) when a user clicks\n * a feedback card, showing full details: message, metadata, annotation\n * info, status actions, and a \"Go to annotation\" button.\n *\n * Glassmorphism design — frosted glass surfaces, staggered section\n * animations, accent gradients, premium micro-interactions.\n */\n\nimport type { FeedbackResponse } from \"@siteping/core\";\nimport { el, parseSvg, setText } from \"./dom-utils.js\";\nimport { getTypeBgColor, getTypeColor, type ThemeColors } from \"./styles/theme.js\";\n\n// ---------------------------------------------------------------------------\n// SVG Icons\n// ---------------------------------------------------------------------------\n\nexport const ICON_ARROW_LEFT = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><line x1=\"19\" y1=\"12\" x2=\"5\" y2=\"12\"/><polyline points=\"12 19 5 12 12 5\"/></svg>`;\n\nexport const ICON_MAP_PIN = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z\"/><circle cx=\"12\" cy=\"10\" r=\"3\"/></svg>`;\n\nexport const ICON_LINK = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71\"/><path d=\"M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71\"/></svg>`;\n\nexport const ICON_USER = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2\"/><circle cx=\"12\" cy=\"7\" r=\"4\"/></svg>`;\n\nexport const ICON_CALENDAR = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"3\" y=\"4\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\"/><line x1=\"16\" y1=\"2\" x2=\"16\" y2=\"6\"/><line x1=\"8\" y1=\"2\" x2=\"8\" y2=\"6\"/><line x1=\"3\" y1=\"10\" x2=\"21\" y2=\"10\"/></svg>`;\n\nexport const ICON_MONITOR = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"2\" y=\"3\" width=\"20\" height=\"14\" rx=\"2\" ry=\"2\"/><line x1=\"8\" y1=\"21\" x2=\"16\" y2=\"21\"/><line x1=\"12\" y1=\"17\" x2=\"12\" y2=\"21\"/></svg>`;\n\nconst ICON_CHECK = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"20 6 9 17 4 12\"/></svg>`;\n\nconst ICON_UNDO = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"1 4 1 10 7 10\"/><path d=\"M3.51 15a9 9 0 1 0 2.13-9.36L1 10\"/></svg>`;\n\nconst ICON_TRASH = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"3 6 5 6 21 6\"/><path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/><line x1=\"10\" y1=\"11\" x2=\"10\" y2=\"17\"/><line x1=\"14\" y1=\"11\" x2=\"14\" y2=\"17\"/></svg>`;\n\nconst ICON_CODE = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"16 18 22 12 16 6\"/><polyline points=\"8 6 2 12 8 18\"/></svg>`;\n\nconst ICON_CROSSHAIR = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"22\" y1=\"12\" x2=\"18\" y2=\"12\"/><line x1=\"6\" y1=\"12\" x2=\"2\" y2=\"12\"/><line x1=\"12\" y1=\"6\" x2=\"12\" y2=\"2\"/><line x1=\"12\" y1=\"22\" x2=\"12\" y2=\"18\"/></svg>`;\n\n// ---------------------------------------------------------------------------\n// I18n\n// ---------------------------------------------------------------------------\n\nexport const DETAIL_I18N_EN = {\n \"detail.back\": \"Back\",\n \"detail.title\": \"Feedback #{number}\",\n \"detail.status\": \"Status\",\n \"detail.message\": \"Message\",\n \"detail.metadata\": \"Details\",\n \"detail.annotation\": \"Annotation\",\n \"detail.page\": \"Page\",\n \"detail.author\": \"Author\",\n \"detail.date\": \"Created\",\n \"detail.viewport\": \"Viewport\",\n \"detail.browser\": \"Browser\",\n \"detail.resolvedAt\": \"Resolved at\",\n \"detail.goToAnnotation\": \"Go to annotation\",\n \"detail.element\": \"Element\",\n \"detail.selector\": \"Selector\",\n \"detail.position\": \"Position\",\n \"detail.resolve\": \"Resolve\",\n \"detail.reopen\": \"Reopen\",\n \"detail.delete\": \"Delete\",\n};\n\nexport const DETAIL_I18N_FR = {\n \"detail.back\": \"Retour\",\n \"detail.title\": \"Feedback n\\u00b0{number}\",\n \"detail.status\": \"Statut\",\n \"detail.message\": \"Message\",\n \"detail.metadata\": \"D\\u00e9tails\",\n \"detail.annotation\": \"Annotation\",\n \"detail.page\": \"Page\",\n \"detail.author\": \"Auteur\",\n \"detail.date\": \"Cr\\u00e9\\u00e9 le\",\n \"detail.viewport\": \"Viewport\",\n \"detail.browser\": \"Navigateur\",\n \"detail.resolvedAt\": \"R\\u00e9solu le\",\n \"detail.goToAnnotation\": \"Aller \\u00e0 l'annotation\",\n \"detail.element\": \"\\u00c9l\\u00e9ment\",\n \"detail.selector\": \"S\\u00e9lecteur\",\n \"detail.position\": \"Position\",\n \"detail.resolve\": \"R\\u00e9soudre\",\n \"detail.reopen\": \"Rouvrir\",\n \"detail.delete\": \"Supprimer\",\n};\n\ntype DetailI18nKey = keyof typeof DETAIL_I18N_EN;\ntype DetailI18n = Record<DetailI18nKey, string>;\n\n// ---------------------------------------------------------------------------\n// CSS\n// ---------------------------------------------------------------------------\n\nexport const DETAIL_CSS = /* css */ `\n /* ============================\n Detail View — Panel-in-Panel\n ============================ */\n\n .sp-detail {\n position: absolute;\n inset: 0;\n display: flex;\n flex-direction: column;\n background: var(--sp-glass-bg);\n backdrop-filter: blur(var(--sp-blur-heavy));\n -webkit-backdrop-filter: blur(var(--sp-blur-heavy));\n z-index: 20;\n transform: translateX(100%);\n transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1);\n will-change: transform;\n overflow: hidden;\n }\n\n .sp-detail--visible {\n transform: translateX(0);\n }\n\n /* ---- Header ---- */\n\n .sp-detail-header {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 16px 20px;\n border-bottom: 1px solid var(--sp-border);\n background: var(--sp-glass-bg-heavy);\n backdrop-filter: blur(var(--sp-blur));\n -webkit-backdrop-filter: blur(var(--sp-blur));\n flex-shrink: 0;\n min-height: 64px;\n }\n\n .sp-detail-back {\n width: 40px;\n height: 40px;\n border-radius: var(--sp-radius);\n border: none;\n background: transparent;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--sp-text-tertiary);\n transition: all 0.2s ease;\n flex-shrink: 0;\n padding: 0;\n }\n\n .sp-detail-back:hover {\n background: var(--sp-bg-hover);\n color: var(--sp-text);\n }\n\n .sp-detail-back:active {\n transform: scale(0.92);\n transition-duration: 0.1s;\n }\n\n .sp-detail-back svg {\n width: 18px;\n height: 18px;\n }\n\n .sp-detail-title {\n font-size: 16px;\n font-weight: 700;\n color: var(--sp-text);\n letter-spacing: -0.02em;\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .sp-detail-header .sp-badge {\n flex-shrink: 0;\n }\n\n /* ---- Content ---- */\n\n .sp-detail-content {\n flex: 1;\n overflow-y: auto;\n overflow-x: hidden;\n padding: 0;\n }\n\n .sp-detail-content::-webkit-scrollbar {\n width: 6px;\n }\n\n .sp-detail-content::-webkit-scrollbar-track {\n background: transparent;\n }\n\n .sp-detail-content::-webkit-scrollbar-thumb {\n background: var(--sp-border);\n border-radius: var(--sp-radius-full);\n }\n\n .sp-detail-content::-webkit-scrollbar-thumb:hover {\n background: var(--sp-text-tertiary);\n }\n\n /* ---- Section ---- */\n\n .sp-detail-section {\n padding: 20px 24px;\n border-bottom: 1px solid var(--sp-border);\n opacity: 0;\n transform: translateY(8px);\n animation: sp-detail-section-in 0.35s cubic-bezier(0.16, 1, 0.3, 1) forwards;\n }\n\n @keyframes sp-detail-section-in {\n to {\n opacity: 1;\n transform: translateY(0);\n }\n }\n\n .sp-detail-section:last-child {\n border-bottom: none;\n }\n\n .sp-detail-section-title {\n font-size: 11px;\n font-weight: 600;\n color: var(--sp-text-tertiary);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n margin-bottom: 14px;\n display: flex;\n align-items: center;\n gap: 6px;\n }\n\n .sp-detail-section-title svg {\n width: 14px;\n height: 14px;\n opacity: 0.6;\n }\n\n /* ---- Status + Actions Section ---- */\n\n .sp-detail-status {\n display: flex;\n align-items: center;\n gap: 10px;\n margin-bottom: 16px;\n }\n\n .sp-detail-status-pill {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 5px 14px;\n border-radius: var(--sp-radius-full);\n font-size: 12px;\n font-weight: 600;\n letter-spacing: 0.02em;\n }\n\n .sp-detail-status-pill--open {\n background: rgba(34, 197, 94, 0.1);\n color: #22c55e;\n border: 1px solid rgba(34, 197, 94, 0.2);\n }\n\n .sp-detail-status-pill--resolved {\n background: rgba(156, 163, 175, 0.1);\n color: #9ca3af;\n border: 1px solid rgba(156, 163, 175, 0.2);\n }\n\n .sp-detail-status-dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n flex-shrink: 0;\n }\n\n .sp-detail-actions {\n display: flex;\n gap: 8px;\n }\n\n .sp-detail-actions button {\n flex: 1;\n height: 40px;\n padding: 0 16px;\n border-radius: var(--sp-radius);\n font-family: var(--sp-font);\n font-size: 13px;\n font-weight: 600;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n transition: all 0.2s ease;\n }\n\n .sp-detail-actions button svg {\n width: 15px;\n height: 15px;\n }\n\n .sp-detail-btn-resolve {\n border: 1.5px solid #22c55e;\n background: rgba(34, 197, 94, 0.06);\n color: #22c55e;\n }\n\n .sp-detail-btn-resolve:hover {\n background: rgba(34, 197, 94, 0.14);\n box-shadow: 0 0 16px rgba(34, 197, 94, 0.12);\n transform: translateY(-1px);\n }\n\n .sp-detail-btn-resolve:active {\n transform: translateY(0) scale(0.98);\n transition-duration: 0.1s;\n }\n\n .sp-detail-btn-reopen {\n border: 1.5px solid var(--sp-accent);\n background: var(--sp-accent-light);\n color: var(--sp-accent);\n }\n\n .sp-detail-btn-reopen:hover {\n background: rgba(var(--sp-accent), 0.14);\n box-shadow: 0 0 16px var(--sp-accent-glow);\n transform: translateY(-1px);\n }\n\n .sp-detail-btn-reopen:active {\n transform: translateY(0) scale(0.98);\n transition-duration: 0.1s;\n }\n\n .sp-detail-btn-delete {\n border: 1.5px solid #ef4444;\n background: rgba(239, 68, 68, 0.06);\n color: #ef4444;\n }\n\n .sp-detail-btn-delete:hover {\n background: rgba(239, 68, 68, 0.14);\n box-shadow: 0 0 16px rgba(239, 68, 68, 0.12);\n transform: translateY(-1px);\n }\n\n .sp-detail-btn-delete:active {\n transform: translateY(0) scale(0.98);\n transition-duration: 0.1s;\n }\n\n .sp-detail-actions button:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n pointer-events: none;\n transform: none;\n box-shadow: none;\n }\n\n /* ---- Message Section ---- */\n\n .sp-detail-message {\n font-size: 14px;\n line-height: 1.65;\n color: var(--sp-text);\n padding: 14px 16px;\n border-left: 3px solid var(--sp-accent);\n border-radius: 0 var(--sp-radius) var(--sp-radius) 0;\n background: var(--sp-glass-bg-heavy);\n white-space: pre-wrap;\n word-break: break-word;\n }\n\n /* ---- Metadata Section ---- */\n\n .sp-detail-meta {\n display: flex;\n flex-direction: column;\n gap: 14px;\n }\n\n .sp-detail-meta-row {\n display: flex;\n align-items: flex-start;\n gap: 12px;\n }\n\n .sp-detail-meta-row svg {\n width: 14px;\n height: 14px;\n color: var(--sp-text-tertiary);\n flex-shrink: 0;\n margin-top: 1px;\n }\n\n .sp-detail-meta-content {\n flex: 1;\n min-width: 0;\n }\n\n .sp-detail-meta-label {\n font-size: 10px;\n font-weight: 600;\n color: var(--sp-text-tertiary);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n line-height: 1;\n margin-bottom: 4px;\n }\n\n .sp-detail-meta-value {\n font-size: 13px;\n line-height: 1.4;\n color: var(--sp-text);\n word-break: break-all;\n }\n\n .sp-detail-meta-value--mono {\n font-family: \"SF Mono\", \"Cascadia Code\", \"Fira Code\", \"Consolas\", monospace;\n font-size: 12px;\n background: var(--sp-glass-bg-heavy);\n padding: 2px 6px;\n border-radius: 4px;\n border: 1px solid var(--sp-glass-border-subtle);\n }\n\n .sp-detail-meta-value--secondary {\n color: var(--sp-text-secondary);\n font-size: 12px;\n }\n\n /* ---- Annotation Section ---- */\n\n .sp-detail-annotation {\n display: flex;\n flex-direction: column;\n gap: 12px;\n }\n\n .sp-detail-annotation-info {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 14px;\n border-radius: var(--sp-radius);\n background: var(--sp-glass-bg-heavy);\n border: 1px solid var(--sp-glass-border-subtle);\n }\n\n .sp-detail-annotation-row {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n }\n\n .sp-detail-annotation-row svg {\n width: 13px;\n height: 13px;\n color: var(--sp-text-tertiary);\n flex-shrink: 0;\n margin-top: 2px;\n }\n\n .sp-detail-annotation-label {\n font-size: 10px;\n font-weight: 600;\n color: var(--sp-text-tertiary);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n line-height: 1;\n margin-bottom: 3px;\n }\n\n .sp-detail-annotation-value {\n font-size: 12px;\n line-height: 1.4;\n color: var(--sp-text);\n word-break: break-all;\n }\n\n .sp-detail-annotation-value--mono {\n font-family: \"SF Mono\", \"Cascadia Code\", \"Fira Code\", \"Consolas\", monospace;\n font-size: 11px;\n background: var(--sp-bg-hover);\n padding: 2px 6px;\n border-radius: 4px;\n display: inline-block;\n max-width: 100%;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .sp-detail-btn-goto {\n width: 100%;\n height: 44px;\n padding: 0 20px;\n border-radius: var(--sp-radius);\n border: none;\n background: var(--sp-accent-gradient);\n color: #fff;\n font-family: var(--sp-font);\n font-size: 14px;\n font-weight: 600;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n transition: all 0.25s ease;\n box-shadow: 0 2px 12px var(--sp-accent-glow);\n }\n\n .sp-detail-btn-goto svg {\n width: 16px;\n height: 16px;\n }\n\n .sp-detail-btn-goto:hover {\n box-shadow: 0 4px 20px var(--sp-accent-glow);\n transform: translateY(-2px);\n }\n\n .sp-detail-btn-goto:active {\n transform: translateY(0) scale(0.98);\n transition-duration: 0.1s;\n }\n\n /* ---- Forced Colors / High Contrast ---- */\n\n @media (forced-colors: active) {\n .sp-detail {\n border: 2px solid ButtonText !important;\n background: Canvas !important;\n }\n\n .sp-detail-back,\n .sp-detail-btn-goto,\n .sp-detail-btn-resolve,\n .sp-detail-btn-reopen,\n .sp-detail-btn-delete {\n border: 2px solid ButtonText !important;\n background: Canvas !important;\n color: ButtonText !important;\n }\n\n .sp-detail-back:focus-visible,\n .sp-detail-btn-goto:focus-visible,\n .sp-detail-btn-resolve:focus-visible,\n .sp-detail-btn-reopen:focus-visible,\n .sp-detail-btn-delete:focus-visible {\n outline: 3px solid Highlight !important;\n }\n\n .sp-detail-status-pill {\n border: 2px solid ButtonText !important;\n background: Canvas !important;\n color: ButtonText !important;\n }\n\n .sp-detail-message {\n border-left: 3px solid ButtonText !important;\n }\n }\n\n /* ---- Reduced Motion ---- */\n\n @media (prefers-reduced-motion: reduce) {\n .sp-detail {\n transition-duration: 0.01ms !important;\n }\n\n .sp-detail-section {\n animation-duration: 0.01ms !important;\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Parse a userAgent string to extract the browser name and version. */\nfunction parseBrowser(ua: string): string {\n // Order matters: Edge includes \"Edg/\", Chrome includes \"Chrome/\", etc.\n if (/Edg\\//i.test(ua)) {\n const m = ua.match(/Edg\\/([\\d.]+)/);\n return m ? `Edge ${m[1]}` : \"Edge\";\n }\n if (/OPR\\//i.test(ua) || /Opera/i.test(ua)) {\n const m = ua.match(/OPR\\/([\\d.]+)/);\n return m ? `Opera ${m[1]}` : \"Opera\";\n }\n if (/Firefox\\//i.test(ua)) {\n const m = ua.match(/Firefox\\/([\\d.]+)/);\n return m ? `Firefox ${m[1]}` : \"Firefox\";\n }\n if (/Chrome\\//i.test(ua) && !/Chromium/i.test(ua)) {\n const m = ua.match(/Chrome\\/([\\d.]+)/);\n return m ? `Chrome ${m[1]}` : \"Chrome\";\n }\n if (/Safari\\//i.test(ua) && !/Chrome/i.test(ua)) {\n const m = ua.match(/Version\\/([\\d.]+)/);\n return m ? `Safari ${m[1]}` : \"Safari\";\n }\n return \"Unknown\";\n}\n\n/** Format an ISO date string to a full locale-aware date/time. */\nfunction formatFullDate(isoString: string, locale: string): string {\n try {\n const d = new Date(isoString);\n return d.toLocaleString(locale, {\n year: \"numeric\",\n month: \"long\",\n day: \"numeric\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n });\n } catch {\n return isoString;\n }\n}\n\n/** Extract the pathname from a URL string, falling back to the raw string. */\nfunction extractPathname(url: string): string {\n try {\n return new URL(url).pathname;\n } catch {\n return url;\n }\n}\n\n/** Truncate a string to a max length with ellipsis. */\nfunction truncate(str: string, max: number): string {\n if (str.length <= max) return str;\n return str.slice(0, max - 1) + \"\\u2026\";\n}\n\n// ---------------------------------------------------------------------------\n// Callbacks\n// ---------------------------------------------------------------------------\n\nexport interface DetailCallbacks {\n onBack: () => void;\n onResolve: (feedback: FeedbackResponse) => Promise<void>;\n onDelete: (feedback: FeedbackResponse) => Promise<void>;\n onGoToAnnotation: (feedback: FeedbackResponse) => void;\n}\n\n// ---------------------------------------------------------------------------\n// DetailView Class\n// ---------------------------------------------------------------------------\n\nexport class DetailView {\n readonly element: HTMLElement;\n\n private _isVisible = false;\n private currentFeedback: FeedbackResponse | null = null;\n private readonly content: HTMLElement;\n private readonly i18n: DetailI18n;\n private resolveBtn: HTMLButtonElement | null = null;\n private deleteBtn: HTMLButtonElement | null = null;\n private isProcessing = false;\n\n constructor(\n private readonly colors: ThemeColors,\n private readonly callbacks: DetailCallbacks,\n locale: string,\n ) {\n this.i18n = locale.startsWith(\"fr\") ? DETAIL_I18N_FR : DETAIL_I18N_EN;\n\n // Root container\n this.element = el(\"div\", { class: \"sp-detail\" });\n this.element.setAttribute(\"role\", \"dialog\");\n this.element.setAttribute(\"aria-label\", \"Feedback detail\");\n this.element.setAttribute(\"aria-hidden\", \"true\");\n\n // Header (built once, title/badge updated on show())\n const header = el(\"div\", { class: \"sp-detail-header\" });\n\n const backBtn = document.createElement(\"button\");\n backBtn.type = \"button\";\n backBtn.className = \"sp-detail-back\";\n backBtn.setAttribute(\"aria-label\", this.i18n[\"detail.back\"]);\n backBtn.appendChild(parseSvg(ICON_ARROW_LEFT));\n backBtn.addEventListener(\"click\", () => {\n this.hide();\n this.callbacks.onBack();\n });\n\n this.element.appendChild(header);\n header.appendChild(backBtn);\n\n // Title and badge are appended in show()\n\n // Scrollable content area\n this.content = el(\"div\", { class: \"sp-detail-content\" });\n this.element.appendChild(this.content);\n }\n\n /** Show the detail view for a specific feedback. */\n show(feedback: FeedbackResponse, number: number): void {\n this.currentFeedback = feedback;\n this.isProcessing = false;\n\n // ---- Update header ----\n const header = this.element.querySelector<HTMLElement>(\".sp-detail-header\");\n if (!header) return;\n // Remove old title/badge but keep the back button\n const backBtn = header.querySelector<HTMLElement>(\".sp-detail-back\");\n if (!backBtn) return;\n header.replaceChildren(backBtn);\n\n const title = el(\"span\", { class: \"sp-detail-title\" });\n setText(title, this.i18n[\"detail.title\"].replace(\"{number}\", String(number)));\n header.appendChild(title);\n\n const badge = el(\"span\", { class: \"sp-badge\" });\n badge.style.background = getTypeBgColor(feedback.type, this.colors);\n badge.style.color = getTypeColor(feedback.type, this.colors);\n setText(badge, feedback.type);\n header.appendChild(badge);\n\n // ---- Build content sections ----\n this.content.replaceChildren();\n\n let sectionIndex = 0;\n\n // Section 1: Status + Actions\n const statusSection = this.buildSection(sectionIndex++);\n this.buildStatusActions(statusSection, feedback);\n this.content.appendChild(statusSection);\n\n // Section 2: Message\n const messageSection = this.buildSection(sectionIndex++);\n const messageSectionTitle = el(\"div\", { class: \"sp-detail-section-title\" });\n setText(messageSectionTitle, this.i18n[\"detail.message\"]);\n messageSection.appendChild(messageSectionTitle);\n\n const messageBlock = el(\"div\", { class: \"sp-detail-message\" });\n messageBlock.style.borderLeftColor = getTypeColor(feedback.type, this.colors);\n setText(messageBlock, feedback.message);\n messageSection.appendChild(messageBlock);\n this.content.appendChild(messageSection);\n\n // Section 3: Metadata\n const metaSection = this.buildSection(sectionIndex++);\n const metaSectionTitle = el(\"div\", { class: \"sp-detail-section-title\" });\n setText(metaSectionTitle, this.i18n[\"detail.metadata\"]);\n metaSection.appendChild(metaSectionTitle);\n this.buildMetadata(metaSection, feedback);\n this.content.appendChild(metaSection);\n\n // Section 4: Annotation (if any)\n if (feedback.annotations.length > 0) {\n const annSection = this.buildSection(sectionIndex++);\n const annSectionTitle = el(\"div\", { class: \"sp-detail-section-title\" });\n annSectionTitle.appendChild(parseSvg(ICON_MAP_PIN));\n const annTitleText = el(\"span\");\n setText(annTitleText, this.i18n[\"detail.annotation\"]);\n annSectionTitle.appendChild(annTitleText);\n annSection.appendChild(annSectionTitle);\n this.buildAnnotation(annSection, feedback);\n this.content.appendChild(annSection);\n }\n\n // ---- Show with animation ----\n this._isVisible = true;\n this.element.setAttribute(\"aria-hidden\", \"false\");\n\n // Force reflow before adding visible class to trigger CSS transition\n void this.element.offsetHeight;\n this.element.classList.add(\"sp-detail--visible\");\n\n // Focus the back button for keyboard users\n requestAnimationFrame(() => {\n backBtn.focus();\n });\n }\n\n /** Hide the detail view with slide-out animation. */\n hide(): void {\n if (!this._isVisible) return;\n this._isVisible = false;\n this.element.classList.remove(\"sp-detail--visible\");\n this.element.setAttribute(\"aria-hidden\", \"true\");\n this.currentFeedback = null;\n this.resolveBtn = null;\n this.deleteBtn = null;\n }\n\n /** Whether the detail view is currently visible. */\n get isVisible(): boolean {\n return this._isVisible;\n }\n\n /** Cleanup all references. */\n destroy(): void {\n this.hide();\n this.element.remove();\n }\n\n // -----------------------------------------------------------------------\n // Private — Section builders\n // -----------------------------------------------------------------------\n\n /** Create a section element with stagger delay. */\n private buildSection(index: number): HTMLElement {\n const section = el(\"div\", { class: \"sp-detail-section\" });\n section.style.animationDelay = `${index * 40}ms`;\n return section;\n }\n\n /** Build Status pill + Resolve/Delete action buttons. */\n private buildStatusActions(container: HTMLElement, feedback: FeedbackResponse): void {\n const isResolved = feedback.status === \"resolved\";\n\n // Section title\n const sectionTitle = el(\"div\", { class: \"sp-detail-section-title\" });\n setText(sectionTitle, this.i18n[\"detail.status\"]);\n container.appendChild(sectionTitle);\n\n // Status pill\n const statusRow = el(\"div\", { class: \"sp-detail-status\" });\n const pill = el(\"span\", {\n class: `sp-detail-status-pill ${isResolved ? \"sp-detail-status-pill--resolved\" : \"sp-detail-status-pill--open\"}`,\n });\n const dot = el(\"span\", { class: \"sp-detail-status-dot\" });\n dot.style.background = isResolved ? \"#9ca3af\" : \"#22c55e\";\n pill.appendChild(dot);\n const pillLabel = el(\"span\");\n setText(pillLabel, isResolved ? this.i18n[\"detail.reopen\"] : this.i18n[\"detail.resolve\"]);\n // Actually label the pill with Open/Resolved\n setText(pillLabel, isResolved ? \"Resolved\" : \"Open\");\n pill.appendChild(pillLabel);\n statusRow.appendChild(pill);\n container.appendChild(statusRow);\n\n // Action buttons\n const actions = el(\"div\", { class: \"sp-detail-actions\" });\n\n // Resolve / Reopen\n this.resolveBtn = document.createElement(\"button\");\n this.resolveBtn.type = \"button\";\n if (isResolved) {\n this.resolveBtn.className = \"sp-detail-btn-reopen\";\n this.resolveBtn.appendChild(parseSvg(ICON_UNDO));\n const span = document.createElement(\"span\");\n setText(span, this.i18n[\"detail.reopen\"]);\n this.resolveBtn.appendChild(span);\n } else {\n this.resolveBtn.className = \"sp-detail-btn-resolve\";\n this.resolveBtn.appendChild(parseSvg(ICON_CHECK));\n const span = document.createElement(\"span\");\n setText(span, this.i18n[\"detail.resolve\"]);\n this.resolveBtn.appendChild(span);\n }\n this.resolveBtn.addEventListener(\"click\", () => this.handleResolve());\n\n // Delete\n this.deleteBtn = document.createElement(\"button\");\n this.deleteBtn.type = \"button\";\n this.deleteBtn.className = \"sp-detail-btn-delete\";\n this.deleteBtn.appendChild(parseSvg(ICON_TRASH));\n const deleteSpan = document.createElement(\"span\");\n setText(deleteSpan, this.i18n[\"detail.delete\"]);\n this.deleteBtn.appendChild(deleteSpan);\n this.deleteBtn.addEventListener(\"click\", () => this.handleDelete());\n\n actions.appendChild(this.resolveBtn);\n actions.appendChild(this.deleteBtn);\n container.appendChild(actions);\n }\n\n /** Build the metadata grid. */\n private buildMetadata(container: HTMLElement, feedback: FeedbackResponse): void {\n const meta = el(\"div\", { class: \"sp-detail-meta\" });\n\n // Page\n this.addMetaRow(meta, ICON_LINK, this.i18n[\"detail.page\"], () => {\n const value = el(\"div\", { class: \"sp-detail-meta-value\" });\n const pathname = extractPathname(feedback.url);\n setText(value, truncate(pathname, 60));\n value.title = feedback.url;\n return value;\n });\n\n // Author\n this.addMetaRow(meta, ICON_USER, this.i18n[\"detail.author\"], () => {\n const value = el(\"div\", { class: \"sp-detail-meta-value\" });\n const name = feedback.authorName || \"Anonymous\";\n const email = feedback.authorEmail;\n setText(value, email ? `${name} (${email})` : name);\n return value;\n });\n\n // Date\n this.addMetaRow(meta, ICON_CALENDAR, this.i18n[\"detail.date\"], () => {\n const value = el(\"div\", { class: \"sp-detail-meta-value\" });\n setText(value, formatFullDate(feedback.createdAt, this.i18n === DETAIL_I18N_FR ? \"fr\" : \"en\"));\n return value;\n });\n\n // Viewport\n this.addMetaRow(meta, ICON_MONITOR, this.i18n[\"detail.viewport\"], () => {\n const value = el(\"div\", { class: \"sp-detail-meta-value sp-detail-meta-value--mono\" });\n setText(value, feedback.viewport || \"Unknown\");\n return value;\n });\n\n // Browser\n this.addMetaRow(\n meta,\n `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"2\" y1=\"12\" x2=\"22\" y2=\"12\"/><path d=\"M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z\"/></svg>`,\n this.i18n[\"detail.browser\"],\n () => {\n const value = el(\"div\", { class: \"sp-detail-meta-value\" });\n setText(value, parseBrowser(feedback.userAgent));\n return value;\n },\n );\n\n // Resolved at (only if resolved)\n if (feedback.resolvedAt) {\n const resolvedDate = feedback.resolvedAt;\n this.addMetaRow(meta, ICON_CHECK, this.i18n[\"detail.resolvedAt\"], () => {\n const value = el(\"div\", { class: \"sp-detail-meta-value sp-detail-meta-value--secondary\" });\n setText(value, formatFullDate(resolvedDate, this.i18n === DETAIL_I18N_FR ? \"fr\" : \"en\"));\n return value;\n });\n }\n\n container.appendChild(meta);\n }\n\n /** Add a single metadata row with icon, label, and custom value element. */\n private addMetaRow(container: HTMLElement, iconSvg: string, label: string, buildValue: () => HTMLElement): void {\n const row = el(\"div\", { class: \"sp-detail-meta-row\" });\n row.appendChild(parseSvg(iconSvg));\n\n const content = el(\"div\", { class: \"sp-detail-meta-content\" });\n const labelEl = el(\"div\", { class: \"sp-detail-meta-label\" });\n setText(labelEl, label);\n content.appendChild(labelEl);\n content.appendChild(buildValue());\n\n row.appendChild(content);\n container.appendChild(row);\n }\n\n /** Build the annotation detail section. */\n private buildAnnotation(container: HTMLElement, feedback: FeedbackResponse): void {\n const ann = feedback.annotations[0];\n if (!ann) return;\n\n const wrapper = el(\"div\", { class: \"sp-detail-annotation\" });\n\n // Info card\n const info = el(\"div\", { class: \"sp-detail-annotation-info\" });\n\n // Element tag\n this.addAnnotationRow(info, ICON_CODE, this.i18n[\"detail.element\"], () => {\n const value = el(\"span\", { class: \"sp-detail-annotation-value sp-detail-annotation-value--mono\" });\n const tagDisplay = ann.elementId ? `<${ann.elementTag}#${ann.elementId}>` : `<${ann.elementTag}>`;\n setText(value, tagDisplay);\n return value;\n });\n\n // CSS selector\n this.addAnnotationRow(info, ICON_CROSSHAIR, this.i18n[\"detail.selector\"], () => {\n const value = el(\"span\", { class: \"sp-detail-annotation-value sp-detail-annotation-value--mono\" });\n setText(value, truncate(ann.cssSelector, 60));\n value.title = ann.cssSelector;\n return value;\n });\n\n // Position\n this.addAnnotationRow(info, ICON_MAP_PIN, this.i18n[\"detail.position\"], () => {\n const value = el(\"span\", { class: \"sp-detail-annotation-value\" });\n setText(\n value,\n `${ann.xPct.toFixed(1)}%, ${ann.yPct.toFixed(1)}%` +\n (ann.wPct > 0 || ann.hPct > 0 ? ` (${ann.wPct.toFixed(1)}% \\u00d7 ${ann.hPct.toFixed(1)}%)` : \"\"),\n );\n return value;\n });\n\n wrapper.appendChild(info);\n\n // \"Go to annotation\" button\n const gotoBtn = document.createElement(\"button\");\n gotoBtn.type = \"button\";\n gotoBtn.className = \"sp-detail-btn-goto\";\n gotoBtn.appendChild(parseSvg(ICON_MAP_PIN));\n const gotoLabel = document.createElement(\"span\");\n setText(gotoLabel, this.i18n[\"detail.goToAnnotation\"]);\n gotoBtn.appendChild(gotoLabel);\n gotoBtn.addEventListener(\"click\", () => {\n if (this.currentFeedback) {\n this.callbacks.onGoToAnnotation(this.currentFeedback);\n }\n });\n\n wrapper.appendChild(gotoBtn);\n container.appendChild(wrapper);\n }\n\n /** Add a single annotation info row. */\n private addAnnotationRow(\n container: HTMLElement,\n iconSvg: string,\n label: string,\n buildValue: () => HTMLElement,\n ): void {\n const row = el(\"div\", { class: \"sp-detail-annotation-row\" });\n row.appendChild(parseSvg(iconSvg));\n\n const content = el(\"div\", { class: \"sp-detail-meta-content\" });\n const labelEl = el(\"div\", { class: \"sp-detail-annotation-label\" });\n setText(labelEl, label);\n content.appendChild(labelEl);\n content.appendChild(buildValue());\n\n row.appendChild(content);\n container.appendChild(row);\n }\n\n // -----------------------------------------------------------------------\n // Private — Action handlers\n // -----------------------------------------------------------------------\n\n private async handleResolve(): Promise<void> {\n if (this.isProcessing || !this.currentFeedback) return;\n this.isProcessing = true;\n\n if (this.resolveBtn) this.setButtonLoading(this.resolveBtn);\n if (this.deleteBtn) this.deleteBtn.disabled = true;\n\n try {\n await this.callbacks.onResolve(this.currentFeedback);\n // The parent will call hide() or re-show with updated data\n } catch {\n // Restore buttons on error\n this.isProcessing = false;\n if (this.resolveBtn) this.restoreResolveBtn(this.currentFeedback);\n if (this.deleteBtn) this.deleteBtn.disabled = false;\n }\n }\n\n private async handleDelete(): Promise<void> {\n if (this.isProcessing || !this.currentFeedback) return;\n this.isProcessing = true;\n\n if (this.deleteBtn) this.setButtonLoading(this.deleteBtn);\n if (this.resolveBtn) this.resolveBtn.disabled = true;\n\n try {\n await this.callbacks.onDelete(this.currentFeedback);\n // The parent will call hide() after deletion\n } catch {\n this.isProcessing = false;\n if (this.deleteBtn) this.restoreDeleteBtn();\n if (this.resolveBtn) this.resolveBtn.disabled = false;\n }\n }\n\n private setButtonLoading(btn: HTMLButtonElement): void {\n btn.disabled = true;\n btn.replaceChildren(el(\"div\", { class: \"sp-spinner sp-spinner--sm\" }));\n }\n\n private restoreResolveBtn(feedback: FeedbackResponse): void {\n if (!this.resolveBtn) return;\n this.resolveBtn.disabled = false;\n this.resolveBtn.replaceChildren();\n\n const isResolved = feedback.status === \"resolved\";\n this.resolveBtn.appendChild(parseSvg(isResolved ? ICON_UNDO : ICON_CHECK));\n const span = document.createElement(\"span\");\n setText(span, isResolved ? this.i18n[\"detail.reopen\"] : this.i18n[\"detail.resolve\"]);\n this.resolveBtn.appendChild(span);\n }\n\n private restoreDeleteBtn(): void {\n if (!this.deleteBtn) return;\n this.deleteBtn.disabled = false;\n this.deleteBtn.replaceChildren();\n this.deleteBtn.appendChild(parseSvg(ICON_TRASH));\n const span = document.createElement(\"span\");\n setText(span, this.i18n[\"detail.delete\"]);\n this.deleteBtn.appendChild(span);\n }\n}\n","/**\n * Sort and group-by-page controls for the feedback panel.\n *\n * Provides:\n * - Sort dropdown (newest, oldest, by-type, open-first)\n * - Group by page toggle (collapsible URL sections)\n * - Pure sort/group utility functions\n *\n * Glassmorphism design — glass surfaces, accent gradients,\n * smooth micro-interactions.\n */\n\nimport type { FeedbackResponse, FeedbackType } from \"@siteping/core\";\nimport { el, parseSvg, setText } from \"./dom-utils.js\";\nimport type { ThemeColors } from \"./styles/theme.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type SortMode = \"newest\" | \"oldest\" | \"by-type\" | \"open-first\";\n\n// ---------------------------------------------------------------------------\n// SVG Icons\n// ---------------------------------------------------------------------------\n\nexport const ICON_SORT = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M11 5h10\"/><path d=\"M11 9h7\"/><path d=\"M11 13h4\"/><path d=\"M3 17l3 3 3-3\"/><path d=\"M6 18V4\"/></svg>`;\n\nexport const ICON_PAGE = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\"/><polyline points=\"14 2 14 8 20 8\"/><line x1=\"16\" y1=\"13\" x2=\"8\" y2=\"13\"/><line x1=\"16\" y1=\"17\" x2=\"8\" y2=\"17\"/><polyline points=\"10 9 9 9 8 9\"/></svg>`;\n\nexport const ICON_CHEVRON = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"9 18 15 12 9 6\"/></svg>`;\n\n// ---------------------------------------------------------------------------\n// I18n\n// ---------------------------------------------------------------------------\n\nexport const SORT_I18N_EN = {\n \"sort.newest\": \"Newest first\",\n \"sort.oldest\": \"Oldest first\",\n \"sort.byType\": \"By type\",\n \"sort.openFirst\": \"Open first\",\n \"sort.label\": \"Sort\",\n \"group.byPage\": \"By page\",\n \"group.feedbacks\": \"{count} feedbacks\",\n} as const;\n\nexport const SORT_I18N_FR = {\n \"sort.newest\": \"Plus récents\",\n \"sort.oldest\": \"Plus anciens\",\n \"sort.byType\": \"Par type\",\n \"sort.openFirst\": \"Ouverts d'abord\",\n \"sort.label\": \"Trier\",\n \"group.byPage\": \"Par page\",\n \"group.feedbacks\": \"{count} feedbacks\",\n} as const;\n\nexport type SortI18nKey = keyof typeof SORT_I18N_EN;\nexport type SortI18n = Record<SortI18nKey, string>;\n\n// ---------------------------------------------------------------------------\n// Sort utilities\n// ---------------------------------------------------------------------------\n\n/** Type priority for \"by-type\" sort: question, change, bug, other */\nconst TYPE_ORDER: Record<FeedbackType, number> = {\n question: 0,\n change: 1,\n bug: 2,\n other: 3,\n};\n\n/** Sort feedbacks according to the given mode. Returns a new array. */\nexport function sortFeedbacks(feedbacks: FeedbackResponse[], mode: SortMode): FeedbackResponse[] {\n const sorted = [...feedbacks];\n\n switch (mode) {\n case \"newest\":\n sorted.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());\n break;\n\n case \"oldest\":\n sorted.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());\n break;\n\n case \"by-type\":\n sorted.sort((a, b) => {\n const typeA = TYPE_ORDER[a.type] ?? 99;\n const typeB = TYPE_ORDER[b.type] ?? 99;\n if (typeA !== typeB) return typeA - typeB;\n // Within same type: newest first\n return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();\n });\n break;\n\n case \"open-first\":\n sorted.sort((a, b) => {\n // Open (0) before resolved (1)\n const statusA = a.status === \"open\" ? 0 : 1;\n const statusB = b.status === \"open\" ? 0 : 1;\n if (statusA !== statusB) return statusA - statusB;\n // Within same status: newest first\n return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();\n });\n break;\n }\n\n return sorted;\n}\n\n// ---------------------------------------------------------------------------\n// Group by page\n// ---------------------------------------------------------------------------\n\n/**\n * Extract the pathname from a full URL string.\n * Falls back to the raw string if URL parsing fails.\n */\nfunction extractPathname(url: string): string {\n try {\n return new URL(url).pathname;\n } catch {\n return url;\n }\n}\n\n/** Truncate a path string, keeping the beginning and end visible. */\nfunction truncatePath(path: string, maxLength: number): string {\n if (path.length <= maxLength) return path;\n const ellipsis = \"\\u2026\";\n const keep = Math.floor((maxLength - 1) / 2);\n return path.slice(0, keep) + ellipsis + path.slice(-keep);\n}\n\n/**\n * Group feedbacks by URL pathname.\n * Returns a Map sorted by feedback count descending (most feedbacks first).\n * Each group's feedbacks maintain their original order.\n */\nexport function groupFeedbacksByPage(feedbacks: FeedbackResponse[]): Map<string, FeedbackResponse[]> {\n const groups = new Map<string, FeedbackResponse[]>();\n\n for (const fb of feedbacks) {\n const path = extractPathname(fb.url);\n const existing = groups.get(path);\n if (existing) {\n existing.push(fb);\n } else {\n groups.set(path, [fb]);\n }\n }\n\n // Sort by count descending\n const sorted = new Map([...groups.entries()].sort((a, b) => b[1].length - a[1].length));\n\n return sorted;\n}\n\n// ---------------------------------------------------------------------------\n// Page group header\n// ---------------------------------------------------------------------------\n\n/**\n * Create a collapsible page group header element.\n * Click toggles the associated `.sp-group-content` sibling.\n */\nexport function createPageGroupHeader(pagePath: string, count: number, colors: ThemeColors): HTMLElement {\n const header = el(\"div\", { class: \"sp-group-header\" });\n header.setAttribute(\"role\", \"button\");\n header.setAttribute(\"tabindex\", \"0\");\n header.setAttribute(\"aria-expanded\", \"true\");\n header.style.borderBottomColor = colors.border;\n\n // Chevron\n const chevronWrap = el(\"span\", { class: \"sp-group-header-chevron\" });\n chevronWrap.appendChild(parseSvg(ICON_CHEVRON));\n header.appendChild(chevronWrap);\n\n // Page icon\n const pageIcon = el(\"span\", { class: \"sp-group-header-icon\" });\n pageIcon.appendChild(parseSvg(ICON_PAGE));\n header.appendChild(pageIcon);\n\n // Path\n const pathEl = el(\"span\", { class: \"sp-group-header-path\" });\n const displayPath = truncatePath(pagePath, 40);\n setText(pathEl, displayPath);\n if (pagePath.length > 40) {\n pathEl.title = pagePath;\n }\n header.appendChild(pathEl);\n\n // Count badge\n const countEl = el(\"span\", { class: \"sp-group-header-count\" });\n countEl.style.background = colors.accentLight;\n countEl.style.color = colors.accent;\n setText(countEl, String(count));\n header.appendChild(countEl);\n\n // Toggle behavior\n const toggle = () => {\n const isExpanded = header.getAttribute(\"aria-expanded\") === \"true\";\n header.setAttribute(\"aria-expanded\", String(!isExpanded));\n header.classList.toggle(\"sp-group-header--collapsed\", isExpanded);\n\n const content = header.nextElementSibling;\n if (content?.classList.contains(\"sp-group-content\")) {\n content.classList.toggle(\"sp-group-content--collapsed\", isExpanded);\n }\n };\n\n header.addEventListener(\"click\", toggle);\n header.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n toggle();\n }\n });\n\n return header;\n}\n\n// ---------------------------------------------------------------------------\n// PanelSortControls\n// ---------------------------------------------------------------------------\n\nexport class PanelSortControls {\n readonly element: HTMLElement;\n\n private _sortMode: SortMode = \"newest\";\n private _groupByPage = false;\n private menuEl: HTMLElement | null = null;\n private sortBtn: HTMLButtonElement;\n private groupToggle: HTMLButtonElement;\n private readonly i18n: SortI18n;\n private readonly colors: ThemeColors;\n private readonly onChange: () => void;\n private outsideClickHandler: ((e: MouseEvent) => void) | null = null;\n\n constructor(colors: ThemeColors, onChange: () => void, locale?: string) {\n this.colors = colors;\n this.onChange = onChange;\n this.i18n = locale === \"fr\" ? SORT_I18N_FR : SORT_I18N_EN;\n\n this.element = el(\"div\", { class: \"sp-sort-controls\" });\n\n // Sort button\n this.sortBtn = document.createElement(\"button\");\n this.sortBtn.className = \"sp-sort-btn\";\n this.sortBtn.setAttribute(\"aria-haspopup\", \"listbox\");\n this.sortBtn.setAttribute(\"aria-expanded\", \"false\");\n this.sortBtn.setAttribute(\"aria-label\", this.i18n[\"sort.label\"]);\n\n const sortIcon = parseSvg(ICON_SORT);\n this.sortBtn.appendChild(sortIcon);\n\n const sortLabel = el(\"span\", { class: \"sp-sort-btn-label\" });\n setText(sortLabel, this.i18n[\"sort.newest\"]);\n this.sortBtn.appendChild(sortLabel);\n\n this.sortBtn.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n this.toggleMenu();\n });\n\n // Group by page toggle\n this.groupToggle = document.createElement(\"button\");\n this.groupToggle.className = \"sp-group-toggle\";\n this.groupToggle.setAttribute(\"aria-pressed\", \"false\");\n\n const groupIcon = parseSvg(ICON_PAGE);\n this.groupToggle.appendChild(groupIcon);\n\n const groupLabel = el(\"span\", { class: \"sp-group-toggle-label\" });\n setText(groupLabel, this.i18n[\"group.byPage\"]);\n this.groupToggle.appendChild(groupLabel);\n\n this.groupToggle.addEventListener(\"click\", () => {\n this._groupByPage = !this._groupByPage;\n this.groupToggle.classList.toggle(\"sp-group-toggle--active\", this._groupByPage);\n this.groupToggle.setAttribute(\"aria-pressed\", String(this._groupByPage));\n this.onChange();\n });\n\n this.element.appendChild(this.sortBtn);\n this.element.appendChild(this.groupToggle);\n }\n\n get sortMode(): SortMode {\n return this._sortMode;\n }\n\n get groupByPage(): boolean {\n return this._groupByPage;\n }\n\n private toggleMenu(): void {\n if (this.menuEl) {\n this.closeMenu();\n return;\n }\n this.openMenu();\n }\n\n private openMenu(): void {\n this.menuEl = el(\"div\", { class: \"sp-sort-menu\" });\n this.menuEl.setAttribute(\"role\", \"listbox\");\n this.menuEl.setAttribute(\"aria-label\", this.i18n[\"sort.label\"]);\n this.sortBtn.setAttribute(\"aria-expanded\", \"true\");\n\n const options: { mode: SortMode; label: string }[] = [\n { mode: \"newest\", label: this.i18n[\"sort.newest\"] },\n { mode: \"oldest\", label: this.i18n[\"sort.oldest\"] },\n { mode: \"by-type\", label: this.i18n[\"sort.byType\"] },\n { mode: \"open-first\", label: this.i18n[\"sort.openFirst\"] },\n ];\n\n for (const opt of options) {\n const item = document.createElement(\"button\");\n item.className = `sp-sort-option${opt.mode === this._sortMode ? \" sp-sort-option--active\" : \"\"}`;\n item.setAttribute(\"role\", \"option\");\n item.setAttribute(\"aria-selected\", String(opt.mode === this._sortMode));\n\n if (opt.mode === this._sortMode) {\n item.style.background = this.colors.accentLight;\n item.style.color = this.colors.accent;\n }\n\n setText(item, opt.label);\n\n item.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n this._sortMode = opt.mode;\n this.updateSortLabel();\n this.closeMenu();\n this.onChange();\n });\n\n this.menuEl.appendChild(item);\n }\n\n // Position relative to button\n this.element.appendChild(this.menuEl);\n\n // Close on outside click (next tick to avoid the current click)\n requestAnimationFrame(() => {\n this.outsideClickHandler = (e: MouseEvent) => {\n if (this.menuEl && !this.element.contains(e.target as Node)) {\n this.closeMenu();\n }\n };\n document.addEventListener(\"click\", this.outsideClickHandler, true);\n });\n\n // Close on Escape\n this.menuEl.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Escape\") {\n this.closeMenu();\n this.sortBtn.focus();\n }\n });\n }\n\n private closeMenu(): void {\n if (this.menuEl) {\n this.menuEl.remove();\n this.menuEl = null;\n }\n this.sortBtn.setAttribute(\"aria-expanded\", \"false\");\n if (this.outsideClickHandler) {\n document.removeEventListener(\"click\", this.outsideClickHandler, true);\n this.outsideClickHandler = null;\n }\n }\n\n private updateSortLabel(): void {\n const labelMap: Record<SortMode, string> = {\n newest: this.i18n[\"sort.newest\"],\n oldest: this.i18n[\"sort.oldest\"],\n \"by-type\": this.i18n[\"sort.byType\"],\n \"open-first\": this.i18n[\"sort.openFirst\"],\n };\n const label = this.sortBtn.querySelector(\".sp-sort-btn-label\");\n if (label) setText(label as HTMLElement, labelMap[this._sortMode]);\n }\n\n destroy(): void {\n this.closeMenu();\n }\n}\n\n// ---------------------------------------------------------------------------\n// CSS\n// ---------------------------------------------------------------------------\n\nexport const SORT_CSS = `\n /* ============================\n Sort Controls Container\n ============================ */\n\n .sp-sort-controls {\n display: flex;\n align-items: center;\n gap: 6px;\n margin-top: 4px;\n padding-top: 8px;\n border-top: 1px solid var(--sp-border);\n }\n\n /* ============================\n Sort Dropdown Button\n ============================ */\n\n .sp-sort-btn {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 5px 12px;\n border-radius: var(--sp-radius-full);\n border: 1px solid var(--sp-border);\n background: var(--sp-glass-bg-heavy);\n color: var(--sp-text-secondary);\n font-family: var(--sp-font);\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n white-space: nowrap;\n transition: all 0.2s ease;\n position: relative;\n }\n\n .sp-sort-btn svg {\n width: 14px;\n height: 14px;\n flex-shrink: 0;\n }\n\n .sp-sort-btn:hover {\n border-color: var(--sp-accent);\n color: var(--sp-accent);\n background: var(--sp-accent-light);\n }\n\n .sp-sort-btn[aria-expanded=\"true\"] {\n border-color: var(--sp-accent);\n color: var(--sp-accent);\n background: var(--sp-accent-light);\n }\n\n /* ============================\n Sort Floating Menu\n ============================ */\n\n .sp-sort-menu {\n position: absolute;\n top: calc(100% + 6px);\n left: 0;\n min-width: 170px;\n padding: 4px;\n border-radius: var(--sp-radius);\n background: var(--sp-glass-bg-heavy);\n backdrop-filter: blur(var(--sp-blur-heavy));\n -webkit-backdrop-filter: blur(var(--sp-blur-heavy));\n border: 1px solid var(--sp-glass-border);\n box-shadow: var(--sp-shadow-md);\n z-index: 10;\n animation: sp-sort-menu-in 0.15s ease-out both;\n }\n\n @keyframes sp-sort-menu-in {\n from {\n opacity: 0;\n transform: translateY(-4px) scale(0.97);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n }\n\n /* ============================\n Sort Menu Option\n ============================ */\n\n .sp-sort-option {\n display: block;\n width: 100%;\n padding: 8px 12px;\n border: none;\n border-radius: 8px;\n background: transparent;\n color: var(--sp-text-secondary);\n font-family: var(--sp-font);\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n text-align: left;\n transition: all 0.15s ease;\n }\n\n .sp-sort-option:hover {\n background: var(--sp-bg-hover);\n color: var(--sp-text);\n }\n\n .sp-sort-option--active {\n font-weight: 600;\n }\n\n .sp-sort-option--active:hover {\n background: var(--sp-accent-light);\n color: var(--sp-accent);\n }\n\n /* ============================\n Group by Page Toggle\n ============================ */\n\n .sp-group-toggle {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 5px 12px;\n border-radius: var(--sp-radius-full);\n border: 1px solid var(--sp-border);\n background: var(--sp-glass-bg-heavy);\n color: var(--sp-text-secondary);\n font-family: var(--sp-font);\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n white-space: nowrap;\n transition: all 0.2s ease;\n }\n\n .sp-group-toggle svg {\n width: 13px;\n height: 13px;\n flex-shrink: 0;\n }\n\n .sp-group-toggle:hover {\n border-color: var(--sp-accent);\n color: var(--sp-accent);\n background: var(--sp-accent-light);\n }\n\n .sp-group-toggle--active {\n background: var(--sp-accent-gradient);\n border-color: transparent;\n color: #fff;\n box-shadow: 0 2px 8px var(--sp-accent-glow);\n }\n\n .sp-group-toggle--active:hover {\n background: var(--sp-accent-gradient);\n border-color: transparent;\n color: #fff;\n }\n\n /* ============================\n Page Group Header\n ============================ */\n\n .sp-group-header {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n background: var(--sp-accent-light);\n border-bottom: 1px solid var(--sp-border);\n cursor: pointer;\n user-select: none;\n position: sticky;\n top: 0;\n z-index: 2;\n transition: background 0.2s ease;\n }\n\n .sp-group-header:hover {\n background: var(--sp-bg-hover);\n }\n\n .sp-group-header:focus-visible {\n outline: 2px solid var(--sp-accent);\n outline-offset: -2px;\n }\n\n .sp-group-header-chevron {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 16px;\n height: 16px;\n flex-shrink: 0;\n transition: transform 0.2s ease;\n transform: rotate(90deg);\n }\n\n .sp-group-header-chevron svg {\n width: 12px;\n height: 12px;\n color: var(--sp-text-tertiary);\n }\n\n .sp-group-header--collapsed .sp-group-header-chevron {\n transform: rotate(0deg);\n }\n\n .sp-group-header-icon {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n }\n\n .sp-group-header-icon svg {\n width: 14px;\n height: 14px;\n color: var(--sp-text-tertiary);\n }\n\n .sp-group-header-path {\n font-size: 12px;\n font-weight: 600;\n color: var(--sp-text-secondary);\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .sp-group-header-count {\n font-size: 11px;\n font-weight: 700;\n padding: 1px 8px;\n border-radius: var(--sp-radius-full);\n flex-shrink: 0;\n font-variant-numeric: tabular-nums;\n }\n\n /* ============================\n Page Group Content\n ============================ */\n\n .sp-group-content {\n overflow: hidden;\n transition: max-height 0.25s ease, opacity 0.2s ease;\n max-height: 5000px;\n opacity: 1;\n }\n\n .sp-group-content--collapsed {\n max-height: 0;\n opacity: 0;\n pointer-events: none;\n }\n\n /* ============================\n Forced Colors / High Contrast\n ============================ */\n\n @media (forced-colors: active) {\n .sp-sort-btn,\n .sp-group-toggle,\n .sp-sort-option,\n .sp-group-header {\n border: 2px solid ButtonText !important;\n background: Canvas !important;\n color: ButtonText !important;\n }\n\n .sp-sort-btn:focus-visible,\n .sp-group-toggle:focus-visible,\n .sp-sort-option:focus-visible,\n .sp-group-header:focus-visible {\n outline: 3px solid Highlight !important;\n }\n\n .sp-sort-menu {\n border: 2px solid ButtonText !important;\n background: Canvas !important;\n }\n }\n\n /* ============================\n Reduced Motion\n ============================ */\n\n @media (prefers-reduced-motion: reduce) {\n .sp-sort-menu {\n animation: none;\n }\n .sp-group-header-chevron {\n transition: none;\n }\n .sp-group-content {\n transition: none;\n }\n }\n`;\n","import type { FeedbackResponse } from \"@siteping/core\";\nimport { el, setText } from \"./dom-utils.js\";\nimport type { ThemeColors } from \"./styles/theme.js\";\n\n// ---------------------------------------------------------------------------\n// i18n — standalone constants, wired into the locale files at integration time\n// ---------------------------------------------------------------------------\n\nexport const STATS_I18N_EN = {\n \"stats.open\": \"Open\",\n \"stats.resolved\": \"Resolved\",\n \"stats.bugs\": \"Bugs\",\n \"stats.progress\": \"{percent}% resolved\",\n};\n\nexport const STATS_I18N_FR = {\n \"stats.open\": \"Ouverts\",\n \"stats.resolved\": \"Résolus\",\n \"stats.bugs\": \"Bugs\",\n \"stats.progress\": \"{percent}% résolus\",\n};\n\n// ---------------------------------------------------------------------------\n// CSS\n// ---------------------------------------------------------------------------\n\nexport const STATS_CSS = /* css */ `\n .sp-stats-bar {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px 24px;\n border-bottom: 1px solid var(--sp-border);\n user-select: none;\n }\n\n .sp-stats-bar[hidden] {\n display: none;\n }\n\n .sp-stats-row {\n display: flex;\n align-items: center;\n gap: 16px;\n }\n\n .sp-stats-item {\n display: flex;\n align-items: center;\n gap: 6px;\n }\n\n .sp-stats-dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n flex-shrink: 0;\n }\n\n .sp-stats-value {\n font-size: 16px;\n font-weight: 600;\n line-height: 1;\n color: var(--sp-text);\n font-variant-numeric: tabular-nums;\n font-feature-settings: \"tnum\";\n transition: opacity 0.3s ease;\n }\n\n .sp-stats-label {\n font-size: 11px;\n line-height: 1;\n color: var(--sp-text-tertiary);\n text-transform: uppercase;\n letter-spacing: 0.04em;\n }\n\n .sp-stats-progress {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .sp-stats-progress-track {\n flex: 1;\n height: 4px;\n border-radius: 2px;\n background: var(--sp-border);\n overflow: hidden;\n }\n\n .sp-stats-progress-fill {\n height: 100%;\n border-radius: 2px;\n background: linear-gradient(90deg, var(--sp-accent), #22c55e);\n width: 0%;\n transition: width 0.5s ease;\n }\n\n .sp-stats-progress-label {\n font-size: 10px;\n line-height: 1;\n color: var(--sp-text-tertiary);\n white-space: nowrap;\n font-variant-numeric: tabular-nums;\n font-feature-settings: \"tnum\";\n min-width: 64px;\n text-align: right;\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\n/**\n * Compact statistics bar displayed between filters and the feedback list.\n * Shows open/resolved/bug counts and a resolved-percentage progress bar.\n */\ntype StatsI18n = typeof STATS_I18N_EN;\n\nexport class PanelStats {\n readonly element: HTMLElement;\n\n private readonly valueOpen: HTMLElement;\n private readonly valueResolved: HTMLElement;\n private readonly valueBugs: HTMLElement;\n private readonly progressFill: HTMLElement;\n private readonly progressLabel: HTMLElement;\n private readonly i18n: StatsI18n;\n\n constructor(\n private readonly colors: ThemeColors,\n locale?: string,\n ) {\n this.i18n = locale === \"fr\" ? STATS_I18N_FR : STATS_I18N_EN;\n // Container\n this.element = el(\"div\", { class: \"sp-stats-bar\" });\n this.element.setAttribute(\"aria-label\", \"Feedback statistics\");\n this.element.hidden = true;\n\n // --- Stats row ---\n const row = el(\"div\", { class: \"sp-stats-row\" });\n\n // Open\n const itemOpen = el(\"div\", { class: \"sp-stats-item\" });\n const dotOpen = el(\"span\", { class: \"sp-stats-dot\" });\n dotOpen.style.background = \"#22c55e\";\n this.valueOpen = el(\"span\", { class: \"sp-stats-value\" });\n setText(this.valueOpen, \"0\");\n const labelOpen = el(\"span\", { class: \"sp-stats-label\" });\n setText(labelOpen, this.i18n[\"stats.open\"]);\n itemOpen.appendChild(dotOpen);\n itemOpen.appendChild(this.valueOpen);\n itemOpen.appendChild(labelOpen);\n\n // Resolved\n const itemResolved = el(\"div\", { class: \"sp-stats-item\" });\n const dotResolved = el(\"span\", { class: \"sp-stats-dot\" });\n dotResolved.style.background = \"#9ca3af\";\n this.valueResolved = el(\"span\", { class: \"sp-stats-value\" });\n setText(this.valueResolved, \"0\");\n const labelResolved = el(\"span\", { class: \"sp-stats-label\" });\n setText(labelResolved, this.i18n[\"stats.resolved\"]);\n itemResolved.appendChild(dotResolved);\n itemResolved.appendChild(this.valueResolved);\n itemResolved.appendChild(labelResolved);\n\n // Bugs\n const itemBugs = el(\"div\", { class: \"sp-stats-item\" });\n const dotBugs = el(\"span\", { class: \"sp-stats-dot\" });\n dotBugs.style.background = this.colors.typeBug;\n this.valueBugs = el(\"span\", { class: \"sp-stats-value\" });\n setText(this.valueBugs, \"0\");\n const labelBugs = el(\"span\", { class: \"sp-stats-label\" });\n setText(labelBugs, this.i18n[\"stats.bugs\"]);\n itemBugs.appendChild(dotBugs);\n itemBugs.appendChild(this.valueBugs);\n itemBugs.appendChild(labelBugs);\n\n row.appendChild(itemOpen);\n row.appendChild(itemResolved);\n row.appendChild(itemBugs);\n\n // --- Progress bar ---\n const progress = el(\"div\", { class: \"sp-stats-progress\" });\n const track = el(\"div\", { class: \"sp-stats-progress-track\" });\n this.progressFill = el(\"div\", { class: \"sp-stats-progress-fill\" });\n track.appendChild(this.progressFill);\n this.progressLabel = el(\"span\", { class: \"sp-stats-progress-label\" });\n setText(this.progressLabel, \"\");\n progress.appendChild(track);\n progress.appendChild(this.progressLabel);\n\n this.element.appendChild(row);\n this.element.appendChild(progress);\n }\n\n /** Update stats from current feedbacks array + total count. */\n update(feedbacks: FeedbackResponse[], total: number): void {\n // Hide when there are no feedbacks at all\n if (total === 0) {\n this.element.hidden = true;\n return;\n }\n this.element.hidden = false;\n\n let openCount = 0;\n let resolvedCount = 0;\n let bugCount = 0;\n\n for (const fb of feedbacks) {\n if (fb.status === \"open\") openCount++;\n if (fb.status === \"resolved\") resolvedCount++;\n if (fb.type === \"bug\") bugCount++;\n }\n\n setText(this.valueOpen, String(openCount));\n setText(this.valueResolved, String(resolvedCount));\n setText(this.valueBugs, String(bugCount));\n\n // Percentage resolved (relative to visible feedbacks, not total)\n const visible = feedbacks.length;\n const pct = visible > 0 ? Math.round((resolvedCount / visible) * 100) : 0;\n\n // Animate fill width via rAF so the transition kicks in after the DOM update\n requestAnimationFrame(() => {\n this.progressFill.style.width = `${pct}%`;\n });\n\n const progressText = this.i18n[\"stats.progress\"].replace(\"{percent}\", String(pct));\n setText(this.progressLabel, progressText);\n }\n}\n","/**\n * Keyboard shortcuts system for the feedback panel.\n *\n * Gmail/GitHub-style single-key shortcuts:\n * J/K — navigate cards, R — resolve, D — delete,\n * F or / — focus search, X — toggle select, ? — help overlay.\n *\n * Active only when the panel is open. Ignores keypresses inside\n * input/textarea elements. Uses a Map<string, handler> for O(1) lookup.\n */\n\nimport { el, parseSvg, setText } from \"./dom-utils.js\";\nimport type { ThemeColors } from \"./styles/theme.js\";\n\n// ---------------------------------------------------------------------------\n// Icon\n// ---------------------------------------------------------------------------\n\nexport const ICON_KEYBOARD = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"2\" y=\"4\" width=\"20\" height=\"16\" rx=\"2\"/><path d=\"M6 8h.01\"/><path d=\"M10 8h.01\"/><path d=\"M14 8h.01\"/><path d=\"M18 8h.01\"/><path d=\"M6 12h.01\"/><path d=\"M18 12h.01\"/><path d=\"M8 16h8\"/></svg>`;\n\n// ---------------------------------------------------------------------------\n// i18n\n// ---------------------------------------------------------------------------\n\nexport const SHORTCUTS_I18N_EN = {\n \"shortcuts.title\": \"Keyboard shortcuts\",\n \"shortcuts.navigate\": \"Navigate feedbacks\",\n \"shortcuts.resolve\": \"Resolve / Reopen\",\n \"shortcuts.delete\": \"Delete\",\n \"shortcuts.search\": \"Focus search\",\n \"shortcuts.select\": \"Toggle selection\",\n \"shortcuts.help\": \"Show shortcuts\",\n \"shortcuts.close\": \"Close\",\n \"shortcuts.hint\": \"Keyboard shortcuts\",\n} as const;\n\nexport const SHORTCUTS_I18N_FR = {\n \"shortcuts.title\": \"Raccourcis clavier\",\n \"shortcuts.navigate\": \"Naviguer les feedbacks\",\n \"shortcuts.resolve\": \"Résoudre / Rouvrir\",\n \"shortcuts.delete\": \"Supprimer\",\n \"shortcuts.search\": \"Rechercher\",\n \"shortcuts.select\": \"Sélectionner\",\n \"shortcuts.help\": \"Raccourcis\",\n \"shortcuts.close\": \"Fermer\",\n \"shortcuts.hint\": \"Raccourcis clavier\",\n} as const;\n\nexport type ShortcutsI18n = typeof SHORTCUTS_I18N_EN;\ntype I18nKey = keyof ShortcutsI18n;\n\n// ---------------------------------------------------------------------------\n// Callbacks\n// ---------------------------------------------------------------------------\n\nexport interface ShortcutCallbacks {\n onNavigate: (direction: \"up\" | \"down\") => void;\n onResolve: () => void;\n onDelete: () => void;\n onFocusSearch: () => void;\n onToggleSelect: () => void;\n}\n\n// ---------------------------------------------------------------------------\n// Card focus helpers\n// ---------------------------------------------------------------------------\n\n/** Get the currently focused card index from the list container (-1 if none). */\nexport function getFocusedCardIndex(listContainer: HTMLElement): number {\n const cards = listContainer.querySelectorAll<HTMLElement>(\".sp-card\");\n for (let i = 0; i < cards.length; i++) {\n if (cards[i]?.classList.contains(\"sp-card--focused\")) return i;\n }\n return -1;\n}\n\n/** Focus a card by index in the list container. Clamps to valid range. */\nexport function focusCardByIndex(listContainer: HTMLElement, index: number): void {\n const cards = listContainer.querySelectorAll<HTMLElement>(\".sp-card\");\n if (cards.length === 0) return;\n\n // Remove previous focus\n for (const card of cards) {\n card.classList.remove(\"sp-card--focused\");\n }\n\n const clamped = Math.max(0, Math.min(index, cards.length - 1));\n const target = cards[clamped];\n if (!target) return;\n target.classList.add(\"sp-card--focused\");\n target.scrollIntoView({ block: \"nearest\", behavior: \"smooth\" });\n target.focus({ preventScroll: true });\n}\n\n// ---------------------------------------------------------------------------\n// Shortcut definitions (used to build help grid)\n// ---------------------------------------------------------------------------\n\ninterface ShortcutDef {\n keys: string[];\n label: I18nKey;\n}\n\nconst SHORTCUT_DEFS: ShortcutDef[] = [\n { keys: [\"J\", \"K\"], label: \"shortcuts.navigate\" },\n { keys: [\"R\"], label: \"shortcuts.resolve\" },\n { keys: [\"D\"], label: \"shortcuts.delete\" },\n { keys: [\"F\", \"/\"], label: \"shortcuts.search\" },\n { keys: [\"X\"], label: \"shortcuts.select\" },\n { keys: [\"?\"], label: \"shortcuts.help\" },\n { keys: [\"Esc\"], label: \"shortcuts.close\" },\n];\n\n// ---------------------------------------------------------------------------\n// CSS\n// ---------------------------------------------------------------------------\n\nexport const SHORTCUTS_CSS = /* css */ `\n /* ---- Help overlay backdrop ---- */\n\n .sp-shortcuts-overlay {\n position: fixed;\n inset: 0;\n background: var(--sp-backdrop, rgba(15, 23, 42, 0.2));\n backdrop-filter: blur(var(--sp-blur));\n -webkit-backdrop-filter: blur(var(--sp-blur));\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 10;\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.2s ease;\n }\n\n .sp-shortcuts-overlay--visible {\n opacity: 1;\n pointer-events: auto;\n }\n\n /* ---- Glassmorphism card ---- */\n\n .sp-shortcuts-card {\n width: 380px;\n max-width: calc(100vw - 32px);\n padding: 24px 28px 20px;\n border-radius: 20px;\n background: var(--sp-glass-bg-heavy);\n backdrop-filter: blur(var(--sp-blur-heavy));\n -webkit-backdrop-filter: blur(var(--sp-blur-heavy));\n border: 1px solid var(--sp-glass-border);\n box-shadow: var(--sp-shadow-xl);\n font-family: var(--sp-font);\n position: relative;\n transform: scale(0.92) translateY(8px);\n transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1);\n }\n\n .sp-shortcuts-overlay--visible .sp-shortcuts-card {\n transform: scale(1) translateY(0);\n }\n\n /* ---- Title row ---- */\n\n .sp-shortcuts-title {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 16px;\n font-weight: 700;\n color: var(--sp-text);\n margin-bottom: 18px;\n }\n\n .sp-shortcuts-title svg {\n width: 18px;\n height: 18px;\n color: var(--sp-text-secondary);\n flex-shrink: 0;\n }\n\n /* ---- Close button ---- */\n\n .sp-shortcuts-close {\n position: absolute;\n top: 16px;\n right: 16px;\n width: 28px;\n height: 28px;\n border-radius: 8px;\n border: none;\n background: transparent;\n color: var(--sp-text-tertiary);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background 0.15s ease, color 0.15s ease;\n }\n\n .sp-shortcuts-close:hover {\n background: var(--sp-bg-hover);\n color: var(--sp-text);\n }\n\n .sp-shortcuts-close svg {\n width: 14px;\n height: 14px;\n }\n\n /* ---- Two-column grid ---- */\n\n .sp-shortcuts-grid {\n display: flex;\n flex-direction: column;\n gap: 10px;\n }\n\n .sp-shortcuts-row {\n display: flex;\n align-items: center;\n gap: 12px;\n }\n\n .sp-shortcuts-keys {\n display: flex;\n align-items: center;\n gap: 4px;\n min-width: 80px;\n justify-content: flex-end;\n }\n\n .sp-shortcuts-separator {\n font-size: 11px;\n color: var(--sp-text-tertiary);\n user-select: none;\n }\n\n /* ---- Key badge (<kbd> styling) ---- */\n\n .sp-kbd {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 28px;\n height: 26px;\n padding: 0 7px;\n border-radius: 6px;\n background: var(--sp-bg-hover);\n border: 1px solid var(--sp-border);\n box-shadow:\n inset 0 -1px 0 rgba(0, 0, 0, 0.08),\n 0 1px 2px rgba(0, 0, 0, 0.04);\n font-family: ui-monospace, \"SF Mono\", \"Cascadia Code\", \"Segoe UI Mono\", Menlo, monospace;\n font-size: 12px;\n font-weight: 600;\n color: var(--sp-text);\n text-align: center;\n line-height: 1;\n user-select: none;\n }\n\n /* ---- Description text ---- */\n\n .sp-shortcuts-desc {\n font-size: 13px;\n color: var(--sp-text-secondary);\n line-height: 1.3;\n }\n\n /* ---- Hint button (bottom-right of panel) ---- */\n\n .sp-shortcuts-hint {\n width: 24px;\n height: 24px;\n border-radius: var(--sp-radius-full);\n border: 1px solid var(--sp-border);\n background: var(--sp-bg-hover);\n color: var(--sp-text-tertiary);\n font-family: ui-monospace, \"SF Mono\", \"Cascadia Code\", \"Segoe UI Mono\", Menlo, monospace;\n font-size: 12px;\n font-weight: 700;\n line-height: 1;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;\n position: absolute;\n bottom: 12px;\n right: 12px;\n }\n\n .sp-shortcuts-hint:hover {\n background: var(--sp-accent-light);\n color: var(--sp-accent);\n border-color: var(--sp-accent);\n }\n\n .sp-shortcuts-hint::after {\n content: attr(aria-label);\n position: absolute;\n bottom: calc(100% + 6px);\n right: 0;\n padding: 4px 8px;\n border-radius: 6px;\n background: var(--sp-glass-bg-heavy);\n border: 1px solid var(--sp-glass-border);\n box-shadow: var(--sp-shadow-sm);\n font-family: var(--sp-font);\n font-size: 11px;\n font-weight: 500;\n color: var(--sp-text-secondary);\n white-space: nowrap;\n opacity: 0;\n pointer-events: none;\n transform: translateY(4px);\n transition: opacity 0.15s ease, transform 0.15s ease;\n }\n\n .sp-shortcuts-hint:hover::after {\n opacity: 1;\n transform: translateY(0);\n }\n\n /* ---- Card focus highlight (navigation) ---- */\n\n .sp-card--focused {\n outline: 2px solid var(--sp-accent);\n outline-offset: -2px;\n border-radius: inherit;\n }\n\n /* ---- Reduced motion ---- */\n\n @media (prefers-reduced-motion: reduce) {\n .sp-shortcuts-overlay,\n .sp-shortcuts-card,\n .sp-shortcuts-close,\n .sp-shortcuts-hint,\n .sp-shortcuts-hint::after {\n transition-duration: 0.01ms !important;\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Close icon (small X) — reused from icons.ts pattern\n// ---------------------------------------------------------------------------\n\nconst ICON_CLOSE_SM = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/></svg>`;\n\n// ---------------------------------------------------------------------------\n// KeyboardShortcuts\n// ---------------------------------------------------------------------------\n\nexport class KeyboardShortcuts {\n /** Help overlay element — append to shadow root. */\n readonly helpOverlay: HTMLElement;\n /** Small \"?\" hint button — append inside the panel. */\n readonly hintButton: HTMLButtonElement;\n\n private readonly keyMap: Map<string, () => void>;\n private readonly boundHandler: (e: KeyboardEvent) => void;\n private shadowRoot: ShadowRoot | HTMLElement | null = null;\n private enabled = false;\n private helpVisible = false;\n private destroyed = false;\n\n constructor(\n _colors: ThemeColors,\n callbacks: ShortcutCallbacks,\n private readonly i18n: ShortcutsI18n = SHORTCUTS_I18N_EN,\n ) {\n // Build key map (O(1) dispatch)\n this.keyMap = new Map<string, () => void>([\n [\"j\", () => callbacks.onNavigate(\"down\")],\n [\"k\", () => callbacks.onNavigate(\"up\")],\n [\"r\", () => callbacks.onResolve()],\n [\"d\", () => callbacks.onDelete()],\n [\"f\", () => callbacks.onFocusSearch()],\n [\"/\", () => callbacks.onFocusSearch()],\n [\"x\", () => callbacks.onToggleSelect()],\n [\"?\", () => this.toggleHelp()],\n ]);\n\n // Build DOM\n this.helpOverlay = this.buildOverlay();\n this.hintButton = this.buildHintButton();\n\n // Bind handler once\n this.boundHandler = (e: KeyboardEvent) => this.handleKeydown(e);\n }\n\n // -------------------------------------------------------------------------\n // Public API\n // -------------------------------------------------------------------------\n\n /** Enable shortcuts. Provide the shadow root (or document) to attach the listener. */\n enable(root?: ShadowRoot | HTMLElement): void {\n if (this.destroyed || this.enabled) return;\n if (root) this.shadowRoot = root;\n const target = this.shadowRoot ?? document;\n target.addEventListener(\"keydown\", this.boundHandler as EventListener);\n this.enabled = true;\n }\n\n /** Disable shortcuts (call when panel closes). */\n disable(): void {\n if (!this.enabled) return;\n const target = this.shadowRoot ?? document;\n target.removeEventListener(\"keydown\", this.boundHandler as EventListener);\n this.enabled = false;\n // Also hide help if visible\n if (this.helpVisible) this.hideHelp();\n }\n\n /** Show/hide help overlay. */\n toggleHelp(): void {\n if (this.helpVisible) {\n this.hideHelp();\n } else {\n this.showHelp();\n }\n }\n\n /** Destroy and clean up all listeners. */\n destroy(): void {\n if (this.destroyed) return;\n this.disable();\n this.helpOverlay.remove();\n this.hintButton.remove();\n this.destroyed = true;\n }\n\n // -------------------------------------------------------------------------\n // Keyboard handler\n // -------------------------------------------------------------------------\n\n private handleKeydown(e: KeyboardEvent): void {\n // Escape closes help overlay only (panel close handled elsewhere)\n if (e.key === \"Escape\") {\n if (this.helpVisible) {\n e.preventDefault();\n e.stopPropagation();\n this.hideHelp();\n }\n return;\n }\n\n // If help overlay is open, block all other shortcuts\n if (this.helpVisible) return;\n\n // Ignore when focus is in an input, textarea, or contenteditable\n const active = e.composedPath()[0] as HTMLElement | undefined;\n if (active) {\n const tag = active.tagName?.toLowerCase();\n if (tag === \"input\" || tag === \"textarea\" || tag === \"select\") return;\n if (active.isContentEditable) return;\n }\n\n // Ignore modified keys (Ctrl, Alt, Meta) — except Shift for \"?\"\n if (e.ctrlKey || e.altKey || e.metaKey) return;\n\n const handler = this.keyMap.get(e.key);\n if (handler) {\n e.preventDefault();\n e.stopPropagation();\n handler();\n }\n }\n\n // -------------------------------------------------------------------------\n // Help overlay visibility\n // -------------------------------------------------------------------------\n\n private showHelp(): void {\n this.helpVisible = true;\n this.helpOverlay.classList.add(\"sp-shortcuts-overlay--visible\");\n\n // Focus the close button for accessibility\n const closeBtn = this.helpOverlay.querySelector<HTMLButtonElement>(\".sp-shortcuts-close\");\n closeBtn?.focus();\n }\n\n private hideHelp(): void {\n this.helpVisible = false;\n this.helpOverlay.classList.remove(\"sp-shortcuts-overlay--visible\");\n }\n\n // -------------------------------------------------------------------------\n // DOM builders\n // -------------------------------------------------------------------------\n\n private buildOverlay(): HTMLElement {\n const overlay = el(\"div\", { class: \"sp-shortcuts-overlay\" });\n overlay.setAttribute(\"role\", \"dialog\");\n overlay.setAttribute(\"aria-modal\", \"true\");\n overlay.setAttribute(\"aria-label\", this.i18n[\"shortcuts.title\"]);\n\n // Click backdrop to close\n overlay.addEventListener(\"click\", (e) => {\n if (e.target === overlay) this.hideHelp();\n });\n\n const card = el(\"div\", { class: \"sp-shortcuts-card\" });\n\n // Title\n const title = el(\"div\", { class: \"sp-shortcuts-title\" });\n title.appendChild(parseSvg(ICON_KEYBOARD));\n const titleText = el(\"span\");\n setText(titleText, this.i18n[\"shortcuts.title\"]);\n title.appendChild(titleText);\n card.appendChild(title);\n\n // Close button\n const closeBtn = document.createElement(\"button\");\n closeBtn.className = \"sp-shortcuts-close\";\n closeBtn.setAttribute(\"aria-label\", this.i18n[\"shortcuts.close\"]);\n closeBtn.appendChild(parseSvg(ICON_CLOSE_SM));\n closeBtn.addEventListener(\"click\", () => this.hideHelp());\n card.appendChild(closeBtn);\n\n // Grid\n const grid = el(\"div\", { class: \"sp-shortcuts-grid\" });\n\n for (const def of SHORTCUT_DEFS) {\n const row = el(\"div\", { class: \"sp-shortcuts-row\" });\n\n const keysWrap = el(\"div\", { class: \"sp-shortcuts-keys\" });\n def.keys.forEach((key, i) => {\n if (i > 0) {\n const sep = el(\"span\", { class: \"sp-shortcuts-separator\" });\n setText(sep, \"/\");\n keysWrap.appendChild(sep);\n }\n const kbd = el(\"span\", { class: \"sp-kbd\" });\n setText(kbd, key);\n keysWrap.appendChild(kbd);\n });\n\n const desc = el(\"span\", { class: \"sp-shortcuts-desc\" });\n setText(desc, this.i18n[def.label]);\n\n row.appendChild(keysWrap);\n row.appendChild(desc);\n grid.appendChild(row);\n }\n\n card.appendChild(grid);\n overlay.appendChild(card);\n\n return overlay;\n }\n\n private buildHintButton(): HTMLButtonElement {\n const btn = document.createElement(\"button\");\n btn.className = \"sp-shortcuts-hint\";\n btn.setAttribute(\"aria-label\", this.i18n[\"shortcuts.hint\"]);\n setText(btn, \"?\");\n btn.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n this.toggleHelp();\n });\n return btn;\n }\n}\n","import type { FeedbackResponse, FeedbackStatus, FeedbackType } from \"@siteping/core\";\nimport type { WidgetClient } from \"./api-client.js\";\nimport { PAGE_SIZE } from \"./constants.js\";\nimport { el, formatRelativeDate, parseSvg, setText } from \"./dom-utils.js\";\nimport type { EventBus, WidgetEvents } from \"./events.js\";\nimport { EXPORT_I18N_FR, ExportButton } from \"./export-utils.js\";\nimport { getTypeLabel, type TFunction } from \"./i18n/index.js\";\nimport {\n ICON_BUG,\n ICON_CHANGE,\n ICON_CHECK,\n ICON_CHEVRON_DOWN,\n ICON_CLOSE,\n ICON_DOT_OPEN,\n ICON_LAYERS,\n ICON_OTHER,\n ICON_QUESTION,\n ICON_SEARCH,\n ICON_TRASH,\n ICON_UNDO,\n} from \"./icons.js\";\nimport type { MarkerManager } from \"./markers.js\";\nimport { BULK_I18N_EN, BULK_I18N_FR, BulkActions } from \"./panel-bulk.js\";\nimport { DetailView } from \"./panel-detail.js\";\nimport { createPageGroupHeader, groupFeedbacksByPage, PanelSortControls, sortFeedbacks } from \"./panel-sort.js\";\nimport { PanelStats } from \"./panel-stats.js\";\nimport {\n focusCardByIndex,\n getFocusedCardIndex,\n KeyboardShortcuts,\n SHORTCUTS_I18N_EN,\n SHORTCUTS_I18N_FR,\n} from \"./shortcuts.js\";\nimport { getTypeBgColor, getTypeColor, type ThemeColors } from \"./styles/theme.js\";\n\n/**\n * Side panel (400px) with feedback history, filters, search, stats,\n * sort/group, bulk actions, export, detail view, and keyboard shortcuts.\n *\n * Lives inside the Shadow DOM.\n * Glassmorphism: glass background, staggered card animations,\n * loading states, resolve feedback with disabled state.\n */\nexport class Panel {\n private root: HTMLElement;\n private listContainer: HTMLElement;\n private searchInput: HTMLInputElement;\n private closeBtn: HTMLButtonElement;\n private deleteAllBtn: HTMLButtonElement;\n private activeFilters = new Set<string>([\"all\"]);\n private activeStatusFilter: \"all\" | FeedbackStatus = \"all\";\n private typeDropdownBtn!: HTMLButtonElement;\n private typeDropdownContainer!: HTMLElement;\n private typeDropdownMenu: HTMLElement | null = null;\n private typeDropdownOutsideHandler: ((e: MouseEvent) => void) | null = null;\n private statusSegmented!: HTMLElement;\n private typeOptions!: ReadonlyArray<{ value: string; label: string; icon: string; color: string; bg: string }>;\n private statusOptions!: ReadonlyArray<{\n value: \"all\" | FeedbackStatus;\n label: string;\n icon: string;\n color: string;\n bg: string;\n }>;\n private feedbacks: FeedbackResponse[] = [];\n private currentPage = 1;\n private totalFeedbacks = 0;\n private isLoadingMore = false;\n private isOpen = false;\n private searchTimeout: ReturnType<typeof setTimeout> | null = null;\n private loadController: AbortController | null = null;\n /** Tracks feedback IDs with in-flight mutations to prevent spam-click race conditions */\n private pendingMutations = new Set<string>();\n\n // New feature modules\n private readonly stats: PanelStats;\n private readonly sortControls: PanelSortControls;\n private readonly bulk: BulkActions;\n private readonly exportBtn: ExportButton;\n private readonly shortcuts: KeyboardShortcuts;\n private readonly detail: DetailView;\n private readonly shadowRoot: ShadowRoot;\n\n // i18n helpers\n private readonly bulkI18n: typeof BULK_I18N_EN;\n\n constructor(\n shadowRoot: ShadowRoot,\n private readonly colors: ThemeColors,\n private readonly bus: EventBus<WidgetEvents>,\n private readonly client: WidgetClient,\n private readonly projectName: string,\n private readonly markers: MarkerManager,\n private readonly t: TFunction,\n private readonly locale: string,\n ) {\n this.shadowRoot = shadowRoot;\n this.bulkI18n = locale === \"fr\" ? BULK_I18N_FR : BULK_I18N_EN;\n\n this.root = el(\"div\", { class: \"sp-panel\" });\n this.root.setAttribute(\"role\", \"complementary\");\n this.root.setAttribute(\"aria-label\", this.t(\"panel.ariaLabel\"));\n this.root.setAttribute(\"aria-hidden\", \"true\");\n\n // --- Header ---\n const header = el(\"div\", { class: \"sp-panel-header\" });\n const title = el(\"span\", { class: \"sp-panel-title\" });\n setText(title, this.t(\"panel.title\"));\n\n this.closeBtn = document.createElement(\"button\");\n this.closeBtn.className = \"sp-panel-close\";\n this.closeBtn.setAttribute(\"aria-label\", this.t(\"panel.close\"));\n this.closeBtn.appendChild(parseSvg(ICON_CLOSE));\n this.closeBtn.addEventListener(\"click\", () => this.close());\n\n this.deleteAllBtn = document.createElement(\"button\");\n this.deleteAllBtn.className = \"sp-btn-delete-all\";\n this.deleteAllBtn.setAttribute(\"aria-label\", this.t(\"panel.deleteAll\"));\n this.deleteAllBtn.appendChild(parseSvg(ICON_TRASH));\n const deleteAllLabel = document.createElement(\"span\");\n setText(deleteAllLabel, ` ${this.t(\"panel.deleteAll\")}`);\n this.deleteAllBtn.appendChild(deleteAllLabel);\n this.deleteAllBtn.addEventListener(\"click\", () => this.confirmDeleteAll());\n\n // Export button\n this.exportBtn = new ExportButton(colors, () => this.feedbacks);\n if (locale === \"fr\") this.exportBtn.setLabels(EXPORT_I18N_FR);\n\n const headerRight = el(\"div\", { class: \"sp-panel-header-right\" });\n headerRight.appendChild(this.exportBtn.element);\n headerRight.appendChild(this.deleteAllBtn);\n headerRight.appendChild(this.closeBtn);\n\n header.appendChild(title);\n header.appendChild(headerRight);\n\n // --- Stats ---\n this.stats = new PanelStats(colors, locale);\n\n // --- Filters ---\n const filters = el(\"div\", { class: \"sp-filters\" });\n\n // Search\n const searchWrap = el(\"div\", { class: \"sp-search-wrap\" });\n const searchIcon = parseSvg(ICON_SEARCH);\n searchIcon.setAttribute(\"class\", \"sp-search-icon\");\n this.searchInput = document.createElement(\"input\");\n this.searchInput.type = \"text\";\n this.searchInput.className = \"sp-search\";\n this.searchInput.placeholder = this.t(\"panel.search\");\n this.searchInput.setAttribute(\"aria-label\", this.t(\"panel.searchAria\"));\n this.searchInput.addEventListener(\"input\", () => {\n if (this.searchTimeout) clearTimeout(this.searchTimeout);\n this.searchTimeout = setTimeout(() => this.loadFeedbacks().catch(() => {}), 200);\n });\n searchWrap.appendChild(searchIcon);\n searchWrap.appendChild(this.searchInput);\n\n // Filter bar (type dropdown + status segmented control)\n const filterBar = el(\"div\", { class: \"sp-filter-bar\" });\n filterBar.appendChild(this.buildTypeDropdown());\n filterBar.appendChild(this.buildStatusSegmented());\n\n // Sort controls\n this.sortControls = new PanelSortControls(colors, () => this.renderList(), locale);\n\n filters.appendChild(searchWrap);\n filters.appendChild(filterBar);\n filters.appendChild(this.sortControls.element);\n\n // --- List ---\n this.listContainer = el(\"div\", { class: \"sp-list\" });\n this.listContainer.setAttribute(\"role\", \"list\");\n this.listContainer.setAttribute(\"aria-label\", this.t(\"panel.feedbackList\"));\n\n // --- Bulk Actions ---\n this.bulk = new BulkActions(\n colors,\n {\n onResolve: (ids) => this.bulkResolve(ids),\n onDelete: (ids) => this.bulkDelete(ids),\n },\n locale,\n );\n this.bulk.setListContainer(this.listContainer);\n\n // --- Detail View ---\n this.detail = new DetailView(\n colors,\n {\n onBack: () => this.detail.hide(),\n onResolve: async (fb) => {\n const newResolved = fb.status !== \"resolved\";\n await this.client.resolveFeedback(fb.id, newResolved);\n await this.loadFeedbacks();\n this.detail.hide();\n },\n onDelete: async (fb) => {\n await this.client.deleteFeedback(fb.id);\n this.bus.emit(\"feedback:deleted\", fb.id);\n await this.loadFeedbacks();\n this.detail.hide();\n },\n onGoToAnnotation: (fb) => {\n if (fb.annotations.length > 0) {\n const ann = fb.annotations[0];\n if (!ann) return;\n window.scrollTo({ left: ann.scrollX, top: ann.scrollY, behavior: \"smooth\" });\n this.markers.pinHighlight(fb);\n }\n },\n },\n locale,\n );\n\n // --- Keyboard Shortcuts ---\n const shortcutsI18n = (locale === \"fr\" ? SHORTCUTS_I18N_FR : SHORTCUTS_I18N_EN) as typeof SHORTCUTS_I18N_EN;\n this.shortcuts = new KeyboardShortcuts(\n colors,\n {\n onNavigate: (dir) => {\n const idx = getFocusedCardIndex(this.listContainer);\n focusCardByIndex(this.listContainer, dir === \"down\" ? idx + 1 : idx - 1);\n },\n onResolve: () => {\n const fb = this.getFocusedFeedback();\n if (fb && !this.pendingMutations.has(fb.id)) {\n const card = this.listContainer.querySelector<HTMLElement>(`[data-feedback-id=\"${CSS.escape(fb.id)}\"]`);\n const btn = card?.querySelector<HTMLButtonElement>('[data-action=\"resolve\"]');\n if (btn) this.toggleResolve(fb, btn).catch(() => {});\n }\n },\n onDelete: () => {\n const fb = this.getFocusedFeedback();\n if (fb && !this.pendingMutations.has(fb.id)) {\n const card = this.listContainer.querySelector<HTMLElement>(`[data-feedback-id=\"${CSS.escape(fb.id)}\"]`);\n const btn = card?.querySelector<HTMLButtonElement>('[data-action=\"delete\"]');\n if (btn) this.deleteFeedback(fb, btn).catch(() => {});\n }\n },\n onFocusSearch: () => this.searchInput.focus(),\n onToggleSelect: () => {\n const fb = this.getFocusedFeedback();\n if (fb) this.bulk.toggle(fb.id);\n },\n },\n shortcutsI18n,\n );\n\n // --- Assemble DOM ---\n this.root.appendChild(header);\n this.root.appendChild(this.stats.element);\n this.root.appendChild(filters);\n this.root.appendChild(this.listContainer);\n this.root.appendChild(this.bulk.barElement);\n this.root.appendChild(this.detail.element);\n this.root.appendChild(this.shortcuts.helpOverlay);\n this.root.appendChild(this.shortcuts.hintButton);\n shadowRoot.appendChild(this.root);\n\n // --- Event delegation on listContainer ---\n\n this.onListClick = (e: Event) => {\n const target = e.target as Element;\n\n // Bulk checkbox clicks are handled by BulkActions, skip\n if (target.closest(\".sp-bulk-checkbox\")) return;\n\n // Action buttons (expand, resolve, delete)\n const actionEl = target.closest<HTMLElement>(\"[data-action]\");\n if (actionEl) {\n e.stopPropagation();\n const card = actionEl.closest<HTMLElement>(\".sp-card\");\n if (!card) return;\n const feedbackId = card.dataset.feedbackId;\n const feedback = this.feedbacks.find((f) => f.id === feedbackId);\n if (!feedback) return;\n\n const action = actionEl.dataset.action;\n if (action === \"expand\") {\n const message = card.querySelector<HTMLElement>(\".sp-card-message\");\n if (!message) return;\n const isExpanded = message.classList.toggle(\"sp-card-message--expanded\");\n setText(actionEl, isExpanded ? this.t(\"panel.showLess\") : this.t(\"panel.showMore\"));\n actionEl.setAttribute(\"aria-expanded\", String(isExpanded));\n } else if (action === \"resolve\") {\n if (this.pendingMutations.has(feedback.id)) return;\n const btn = actionEl as HTMLButtonElement;\n this.toggleResolve(feedback, btn).catch(() => {});\n } else if (action === \"delete\") {\n if (this.pendingMutations.has(feedback.id)) return;\n const btn = actionEl as HTMLButtonElement;\n this.deleteFeedback(feedback, btn).catch(() => {});\n }\n return;\n }\n\n // Card click → open detail view\n const card = target.closest<HTMLElement>(\".sp-card\");\n if (card) {\n const feedbackId = card.dataset.feedbackId;\n const feedback = this.feedbacks.find((f) => f.id === feedbackId);\n if (feedback) {\n const number = this.feedbacks.indexOf(feedback) + 1;\n this.detail.show(feedback, number);\n }\n }\n };\n this.listContainer.addEventListener(\"click\", this.onListClick);\n\n this.onListKeydown = (e: Event) => {\n const ke = e as KeyboardEvent;\n if (ke.key !== \"Enter\" && ke.key !== \" \") return;\n const target = ke.target as Element;\n const card = target.closest<HTMLElement>(\".sp-card\");\n // Only activate if the card itself is focused, not a button inside it\n if (!card || target !== card) return;\n ke.preventDefault();\n const feedbackId = card.dataset.feedbackId;\n const feedback = this.feedbacks.find((f) => f.id === feedbackId);\n if (feedback) {\n const number = this.feedbacks.indexOf(feedback) + 1;\n this.detail.show(feedback, number);\n }\n };\n this.listContainer.addEventListener(\"keydown\", this.onListKeydown);\n\n // mouseover/mouseout bubble (unlike mouseenter/mouseleave), enabling delegation\n this.onListMouseover = (e: Event) => {\n const target = (e as MouseEvent).target as Element;\n const card = target.closest<HTMLElement>(\".sp-card\");\n if (!card) return;\n const feedbackId = card.dataset.feedbackId;\n if (feedbackId) this.markers.highlight(feedbackId);\n };\n this.listContainer.addEventListener(\"mouseover\", this.onListMouseover);\n\n this.onListMouseout = (e: Event) => {\n const target = (e as MouseEvent).relatedTarget as Element | null;\n // Only clear highlight when leaving all cards (relatedTarget is outside listContainer)\n if (target && this.listContainer.contains(target)) return;\n this.markers.highlight(\"\");\n };\n this.listContainer.addEventListener(\"mouseout\", this.onListMouseout);\n\n // Events\n this.bus.on(\"panel:toggle\", (open) => {\n open ? this.open() : this.close();\n });\n\n // Keyboard handling: Escape to close + focus trap\n shadowRoot.addEventListener(\"keydown\", (e) => {\n const ke = e as KeyboardEvent;\n if (ke.key === \"Escape\" && this.isOpen) {\n // If detail view is open, close it instead\n if (this.detail.isVisible) {\n this.detail.hide();\n return;\n }\n this.close();\n return;\n }\n if (ke.key === \"Tab\" && this.isOpen) {\n const focusable = this.root.querySelectorAll<HTMLElement>(\n 'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])',\n );\n if (focusable.length === 0) return;\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n if (!first || !last) return;\n const active = (shadowRoot as ShadowRoot).activeElement;\n if (ke.shiftKey && active === first) {\n ke.preventDefault();\n last.focus();\n } else if (!ke.shiftKey && active === last) {\n ke.preventDefault();\n first.focus();\n }\n }\n });\n\n // Listen for marker clicks\n this.onMarkerClick = ((e: CustomEvent) => {\n this.scrollToFeedback(e.detail.feedbackId);\n }) as EventListener;\n document.addEventListener(\"sp-marker-click\", this.onMarkerClick);\n }\n\n private onMarkerClick: EventListener;\n private onListClick: (e: Event) => void;\n private onListKeydown: (e: Event) => void;\n private onListMouseover: (e: Event) => void;\n private onListMouseout: (e: Event) => void;\n\n async open(): Promise<void> {\n if (this.isOpen) return;\n this.isOpen = true;\n this.root.classList.add(\"sp-panel--open\");\n this.root.setAttribute(\"aria-hidden\", \"false\");\n this.bus.emit(\"open\");\n this.shortcuts.enable(this.shadowRoot);\n await this.loadFeedbacks();\n // Move focus into the panel (search input or close button)\n requestAnimationFrame(() => {\n if (this.searchInput) {\n this.searchInput.focus();\n } else {\n this.closeBtn.focus();\n }\n });\n }\n\n close(): void {\n if (!this.isOpen) return;\n this.isOpen = false;\n this.root.classList.remove(\"sp-panel--open\");\n this.root.setAttribute(\"aria-hidden\", \"true\");\n this.bus.emit(\"close\");\n this.shortcuts.disable();\n this.detail.hide();\n // Restore focus to the FAB\n const fab = (this.root.getRootNode() as ShadowRoot).querySelector<HTMLButtonElement>(\".sp-fab\");\n fab?.focus();\n }\n\n private showLoading(): void {\n this.listContainer.replaceChildren();\n const loading = el(\"div\", { class: \"sp-loading\" });\n loading.setAttribute(\"role\", \"status\");\n loading.setAttribute(\"aria-live\", \"polite\");\n loading.setAttribute(\"aria-label\", this.t(\"panel.loading\"));\n const spinner = el(\"div\", { class: \"sp-spinner\" });\n loading.appendChild(spinner);\n this.listContainer.appendChild(loading);\n }\n\n private showError(): void {\n this.listContainer.replaceChildren();\n const empty = el(\"div\", { class: \"sp-empty\" });\n empty.setAttribute(\"role\", \"status\");\n empty.setAttribute(\"aria-live\", \"polite\");\n const text = el(\"div\", { class: \"sp-empty-text\" });\n setText(text, this.t(\"panel.loadError\"));\n const retryBtn = document.createElement(\"button\");\n retryBtn.className = \"sp-btn-ghost\";\n retryBtn.style.marginTop = \"8px\";\n setText(retryBtn, this.t(\"panel.retry\"));\n retryBtn.addEventListener(\"click\", () => this.loadFeedbacks().catch(() => {}));\n empty.appendChild(text);\n empty.appendChild(retryBtn);\n this.listContainer.appendChild(empty);\n }\n\n private async loadFeedbacks(): Promise<void> {\n // Cancel any in-flight request to prevent stale responses from overwriting newer results\n this.loadController?.abort();\n this.loadController = new AbortController();\n const { signal } = this.loadController;\n\n // Reset to page 1 on fresh load (filter/search change)\n this.currentPage = 1;\n\n const search = this.searchInput.value.trim() || undefined;\n const typeFilter = this.activeFilters.has(\"all\") ? undefined : (Array.from(this.activeFilters)[0] as FeedbackType);\n const statusFilter = this.activeStatusFilter === \"all\" ? undefined : this.activeStatusFilter;\n\n const options: { page: number; limit: number; type?: FeedbackType; status?: FeedbackStatus; search?: string } = {\n page: 1,\n limit: PAGE_SIZE,\n };\n if (typeFilter) options.type = typeFilter;\n if (statusFilter) options.status = statusFilter;\n if (search) options.search = search;\n\n // Only show spinner on first load (empty list) — otherwise keep current content visible\n const hasContent = this.feedbacks.length > 0;\n if (!hasContent) this.showLoading();\n\n try {\n const { feedbacks, total } = await this.client.getFeedbacks(this.projectName, options);\n if (signal.aborted) return; // Stale response — a newer request superseded this one\n this.feedbacks = feedbacks;\n this.totalFeedbacks = total;\n this.stats.update(feedbacks, total);\n this.bulk.reset();\n this.renderList();\n this.markers.render(feedbacks);\n } catch (error) {\n if (signal.aborted) return; // Expected abort, not a real error\n if (!hasContent) this.showError();\n this.bus.emit(\"feedback:error\", error instanceof Error ? error : new Error(String(error)));\n }\n }\n\n private async loadMoreFeedbacks(): Promise<void> {\n if (this.isLoadingMore) return;\n this.isLoadingMore = true;\n\n // Capture current controller — if loadFeedbacks() runs while we're in-flight,\n // it replaces the controller, signaling that our results are stale.\n const controller = this.loadController;\n\n const nextPage = this.currentPage + 1;\n const search = this.searchInput.value.trim() || undefined;\n const typeFilter = this.activeFilters.has(\"all\") ? undefined : (Array.from(this.activeFilters)[0] as FeedbackType);\n const statusFilter = this.activeStatusFilter === \"all\" ? undefined : this.activeStatusFilter;\n\n const options: { page: number; limit: number; type?: FeedbackType; status?: FeedbackStatus; search?: string } = {\n page: nextPage,\n limit: PAGE_SIZE,\n };\n if (typeFilter) options.type = typeFilter;\n if (statusFilter) options.status = statusFilter;\n if (search) options.search = search;\n\n // Show spinner on the \"Load more\" button\n const loadMoreBtn = this.listContainer.querySelector<HTMLButtonElement>(\".sp-btn-load-more\");\n let restoreBtn: (() => void) | undefined;\n if (loadMoreBtn) restoreBtn = this.setButtonLoading(loadMoreBtn);\n\n try {\n const { feedbacks, total } = await this.client.getFeedbacks(this.projectName, options);\n if (controller !== this.loadController) return; // Filter/search changed — discard stale page\n this.currentPage = nextPage;\n this.totalFeedbacks = total;\n this.feedbacks = [...this.feedbacks, ...feedbacks];\n this.stats.update(this.feedbacks, total);\n this.renderList();\n this.markers.render(this.feedbacks);\n } catch (error) {\n if (restoreBtn) restoreBtn();\n this.bus.emit(\"feedback:error\", error instanceof Error ? error : new Error(String(error)));\n } finally {\n this.isLoadingMore = false;\n }\n }\n\n private renderList(): void {\n this.listContainer.replaceChildren();\n\n if (this.feedbacks.length === 0) {\n const empty = el(\"div\", { class: \"sp-empty\" });\n empty.setAttribute(\"role\", \"status\");\n empty.setAttribute(\"aria-live\", \"polite\");\n const emptyText = el(\"div\", { class: \"sp-empty-text\" });\n setText(emptyText, this.t(\"panel.empty\"));\n empty.appendChild(emptyText);\n this.listContainer.appendChild(empty);\n return;\n }\n\n // Apply sorting\n const sorted = sortFeedbacks(this.feedbacks, this.sortControls.sortMode);\n\n // Select all bar\n const feedbackIds = sorted.map((f) => f.id);\n const selectAllBar = this.bulk.createSelectAllBar(feedbackIds, this.bulkI18n[\"bulk.selectAll\"]);\n this.listContainer.appendChild(selectAllBar);\n\n if (this.sortControls.groupByPage) {\n // Group by page rendering\n const groups = groupFeedbacksByPage(sorted);\n let globalIndex = 0;\n for (const [pagePath, groupFeedbacks] of groups) {\n const groupHeader = createPageGroupHeader(pagePath, groupFeedbacks.length, this.colors);\n this.listContainer.appendChild(groupHeader);\n\n const groupContent = el(\"div\", { class: \"sp-group-content\" });\n for (const feedback of groupFeedbacks) {\n const card = this.createCard(feedback, globalIndex + 1);\n card.style.setProperty(\"--sp-card-i\", String(globalIndex));\n groupContent.appendChild(card);\n globalIndex++;\n }\n this.listContainer.appendChild(groupContent);\n }\n } else {\n // Flat list rendering\n sorted.forEach((feedback, index) => {\n const card = this.createCard(feedback, index + 1);\n card.style.setProperty(\"--sp-card-i\", String(index));\n this.listContainer.appendChild(card);\n });\n }\n\n // \"Load more\" button when there are remaining feedbacks\n const remaining = this.totalFeedbacks - this.feedbacks.length;\n if (remaining > 0) {\n const loadMoreWrap = el(\"div\", { class: \"sp-load-more-wrap\" });\n const loadMoreBtn = document.createElement(\"button\");\n loadMoreBtn.className = \"sp-btn-ghost sp-btn-load-more\";\n setText(loadMoreBtn, this.t(\"panel.loadMore\").replace(\"{remaining}\", String(remaining)));\n loadMoreBtn.addEventListener(\"click\", () => this.loadMoreFeedbacks().catch(() => {}));\n loadMoreWrap.appendChild(loadMoreBtn);\n this.listContainer.appendChild(loadMoreWrap);\n }\n }\n\n private createCard(feedback: FeedbackResponse, number: number): HTMLElement {\n const isResolved = feedback.status === \"resolved\";\n const typeColor = getTypeColor(feedback.type, this.colors);\n\n const card = el(\"div\", {\n class: `sp-card ${isResolved ? \"sp-card--resolved\" : \"\"}`,\n });\n card.setAttribute(\"role\", \"listitem\");\n card.setAttribute(\"tabindex\", \"0\");\n card.setAttribute(\n \"aria-label\",\n `Feedback #${number}: ${getTypeLabel(feedback.type, this.t)} — ${feedback.message.slice(0, 80)}`,\n );\n card.dataset.feedbackId = feedback.id;\n\n // Color bar\n const bar = el(\"div\", { class: \"sp-card-bar\" });\n bar.style.background = isResolved ? \"#9ca3af\" : typeColor;\n\n // Body\n const body = el(\"div\", { class: \"sp-card-body\" });\n\n // Header: checkbox + #number + badge + date\n const header = el(\"div\", { class: \"sp-card-header\" });\n\n // Bulk checkbox — inline in the header row\n const checkbox = this.bulk.createCheckbox(feedback.id);\n header.appendChild(checkbox);\n\n const num = el(\"span\", { class: \"sp-card-number\" });\n setText(num, `#${number}`);\n\n const badge = el(\"span\", { class: \"sp-badge\" });\n const typeBg = getTypeBgColor(feedback.type, this.colors);\n badge.style.background = typeBg;\n badge.style.color = typeColor;\n setText(badge, getTypeLabel(feedback.type, this.t));\n\n const date = el(\"span\", { class: \"sp-card-date\" });\n setText(date, formatRelativeDate(feedback.createdAt, this.locale));\n\n header.appendChild(num);\n header.appendChild(badge);\n header.appendChild(date);\n\n // Message\n const message = el(\"div\", { class: \"sp-card-message\" });\n setText(message, feedback.message);\n\n // Expand button\n const expandBtn = document.createElement(\"button\");\n expandBtn.className = \"sp-card-expand\";\n expandBtn.dataset.action = \"expand\";\n setText(expandBtn, this.t(\"panel.showMore\"));\n expandBtn.style.display = \"none\";\n expandBtn.setAttribute(\"aria-expanded\", \"false\");\n\n // Check if text is clamped (after render)\n requestAnimationFrame(() => {\n if (message.scrollHeight > message.clientHeight) {\n expandBtn.style.display = \"block\";\n }\n });\n\n // Footer: resolve button\n const footer = el(\"div\", { class: \"sp-card-footer\" });\n\n const resolveBtn = document.createElement(\"button\");\n resolveBtn.className = \"sp-btn-resolve\";\n resolveBtn.dataset.action = \"resolve\";\n if (isResolved) {\n resolveBtn.appendChild(parseSvg(ICON_UNDO));\n const span = document.createElement(\"span\");\n setText(span, ` ${this.t(\"panel.reopen\")}`);\n resolveBtn.appendChild(span);\n } else {\n resolveBtn.appendChild(parseSvg(ICON_CHECK));\n const span = document.createElement(\"span\");\n setText(span, ` ${this.t(\"panel.resolve\")}`);\n resolveBtn.appendChild(span);\n }\n\n const deleteBtn = document.createElement(\"button\");\n deleteBtn.className = \"sp-btn-delete\";\n deleteBtn.dataset.action = \"delete\";\n deleteBtn.appendChild(parseSvg(ICON_TRASH));\n const deleteBtnLabel = document.createElement(\"span\");\n setText(deleteBtnLabel, ` ${this.t(\"panel.delete\")}`);\n deleteBtn.appendChild(deleteBtnLabel);\n\n footer.appendChild(resolveBtn);\n footer.appendChild(deleteBtn);\n\n body.appendChild(header);\n body.appendChild(message);\n body.appendChild(expandBtn);\n body.appendChild(footer);\n\n card.appendChild(bar);\n card.appendChild(body);\n\n return card;\n }\n\n // ---------------------------------------------------------------------------\n // Bulk operations\n // ---------------------------------------------------------------------------\n\n private async bulkResolve(ids: string[]): Promise<void> {\n try {\n await Promise.all(ids.map((id) => this.client.resolveFeedback(id, true)));\n await this.loadFeedbacks();\n } catch (error) {\n this.bus.emit(\"feedback:error\", error instanceof Error ? error : new Error(String(error)));\n throw error;\n }\n }\n\n private async bulkDelete(ids: string[]): Promise<void> {\n try {\n await Promise.all(ids.map((id) => this.client.deleteFeedback(id)));\n for (const id of ids) this.bus.emit(\"feedback:deleted\", id);\n await this.loadFeedbacks();\n } catch (error) {\n this.bus.emit(\"feedback:error\", error instanceof Error ? error : new Error(String(error)));\n throw error;\n }\n }\n\n // ---------------------------------------------------------------------------\n // Existing methods (preserved)\n // ---------------------------------------------------------------------------\n\n private async confirmDeleteAll(): Promise<void> {\n const confirmed = await this.showConfirmDialog(\n this.t(\"panel.deleteAllConfirmTitle\"),\n this.t(\"panel.deleteAllConfirmMessage\"),\n );\n if (!confirmed) return;\n\n this.deleteAllBtn.disabled = true;\n try {\n await this.client.deleteAllFeedbacks(this.projectName);\n this.bus.emit(\"feedback:all-deleted\");\n await this.loadFeedbacks();\n } catch (error) {\n this.bus.emit(\"feedback:error\", error instanceof Error ? error : new Error(String(error)));\n } finally {\n this.deleteAllBtn.disabled = false;\n }\n }\n\n private showConfirmDialog(title: string, message: string): Promise<boolean> {\n return new Promise((resolve) => {\n const backdrop = el(\"div\", { class: \"sp-confirm-backdrop\" });\n\n const titleId = `sp-confirm-title-${Date.now()}`;\n const messageId = `sp-confirm-msg-${Date.now()}`;\n\n const dialog = el(\"div\", { class: \"sp-confirm-dialog\" });\n dialog.setAttribute(\"role\", \"alertdialog\");\n dialog.setAttribute(\"aria-modal\", \"true\");\n dialog.setAttribute(\"aria-labelledby\", titleId);\n dialog.setAttribute(\"aria-describedby\", messageId);\n\n const titleEl = el(\"div\", { class: \"sp-confirm-title\" });\n titleEl.id = titleId;\n setText(titleEl, title);\n\n const messageEl = el(\"div\", { class: \"sp-confirm-message\" });\n messageEl.id = messageId;\n setText(messageEl, message);\n\n const btnRow = el(\"div\", { class: \"sp-confirm-actions\" });\n\n const cancelBtn = document.createElement(\"button\");\n cancelBtn.type = \"button\";\n cancelBtn.className = \"sp-btn-ghost\";\n setText(cancelBtn, this.t(\"panel.cancel\"));\n\n const confirmBtn = document.createElement(\"button\");\n confirmBtn.type = \"button\";\n confirmBtn.className = \"sp-btn-danger\";\n setText(confirmBtn, this.t(\"panel.confirmDelete\"));\n\n let closed = false;\n const close = (result: boolean) => {\n if (closed) return;\n closed = true;\n backdrop.removeEventListener(\"keydown\", onKeydown);\n backdrop.style.opacity = \"0\";\n dialog.style.transform = \"translateY(8px) scale(0.97)\";\n setTimeout(() => {\n backdrop.remove();\n resolve(result);\n }, 200);\n };\n\n // Focus trap: Tab cycles between cancel and confirm\n const onKeydown = (e: Event) => {\n const ke = e as KeyboardEvent;\n if (ke.key === \"Escape\") {\n close(false);\n return;\n }\n if (ke.key === \"Tab\") {\n ke.preventDefault();\n const active = (backdrop.getRootNode() as ShadowRoot).activeElement;\n if (active === cancelBtn) {\n confirmBtn.focus();\n } else {\n cancelBtn.focus();\n }\n }\n };\n backdrop.addEventListener(\"keydown\", onKeydown);\n\n cancelBtn.addEventListener(\"click\", () => close(false));\n confirmBtn.addEventListener(\"click\", () => close(true));\n backdrop.addEventListener(\"click\", (e) => {\n if (e.target === backdrop) close(false);\n });\n\n btnRow.appendChild(cancelBtn);\n btnRow.appendChild(confirmBtn);\n dialog.appendChild(titleEl);\n dialog.appendChild(messageEl);\n dialog.appendChild(btnRow);\n backdrop.appendChild(dialog);\n\n this.root.getRootNode() instanceof ShadowRoot\n ? (this.root.getRootNode() as ShadowRoot).appendChild(backdrop)\n : this.root.appendChild(backdrop);\n\n requestAnimationFrame(() => {\n backdrop.style.opacity = \"1\";\n dialog.style.transform = \"translateY(0) scale(1)\";\n cancelBtn.focus();\n });\n });\n }\n\n private setButtonLoading(btn: HTMLButtonElement): () => void {\n const snapshot = Array.from(btn.childNodes).map((n) => n.cloneNode(true));\n btn.disabled = true;\n btn.replaceChildren(el(\"div\", { class: \"sp-spinner sp-spinner--sm\" }));\n return () => {\n btn.replaceChildren(...snapshot);\n btn.disabled = false;\n };\n }\n\n private async deleteFeedback(feedback: FeedbackResponse, btn: HTMLButtonElement): Promise<void> {\n this.pendingMutations.add(feedback.id);\n const restore = this.setButtonLoading(btn);\n try {\n await this.client.deleteFeedback(feedback.id);\n this.bus.emit(\"feedback:deleted\", feedback.id);\n await this.loadFeedbacks();\n } catch (error) {\n restore();\n this.bus.emit(\"feedback:error\", error instanceof Error ? error : new Error(String(error)));\n } finally {\n this.pendingMutations.delete(feedback.id);\n }\n }\n\n private async toggleResolve(feedback: FeedbackResponse, btn: HTMLButtonElement): Promise<void> {\n this.pendingMutations.add(feedback.id);\n const restore = this.setButtonLoading(btn);\n try {\n const newResolved = feedback.status !== \"resolved\";\n await this.client.resolveFeedback(feedback.id, newResolved);\n await this.loadFeedbacks();\n } catch (error) {\n restore();\n this.bus.emit(\"feedback:error\", error instanceof Error ? error : new Error(String(error)));\n } finally {\n this.pendingMutations.delete(feedback.id);\n }\n }\n\n private buildTypeDropdown(): HTMLElement {\n this.typeOptions = [\n {\n value: \"all\",\n label: this.t(\"panel.filterAll\"),\n icon: ICON_LAYERS,\n color: this.colors.accent,\n bg: this.colors.accentLight,\n },\n {\n value: \"question\",\n label: this.t(\"type.question\"),\n icon: ICON_QUESTION,\n color: this.colors.typeQuestion,\n bg: this.colors.typeQuestionBg,\n },\n {\n value: \"change\",\n label: this.t(\"type.change\"),\n icon: ICON_CHANGE,\n color: this.colors.typeChange,\n bg: this.colors.typeChangeBg,\n },\n {\n value: \"bug\",\n label: this.t(\"type.bug\"),\n icon: ICON_BUG,\n color: this.colors.typeBug,\n bg: this.colors.typeBugBg,\n },\n {\n value: \"other\",\n label: this.t(\"type.other\"),\n icon: ICON_OTHER,\n color: this.colors.typeOther,\n bg: this.colors.typeOtherBg,\n },\n ];\n\n this.typeDropdownContainer = el(\"div\", { class: \"sp-filter-dropdown\" });\n\n this.typeDropdownBtn = document.createElement(\"button\");\n this.typeDropdownBtn.type = \"button\";\n this.typeDropdownBtn.className = \"sp-filter-dropdown-btn\";\n this.typeDropdownBtn.setAttribute(\"aria-haspopup\", \"listbox\");\n this.typeDropdownBtn.setAttribute(\"aria-expanded\", \"false\");\n this.renderTypeDropdownTrigger();\n\n this.typeDropdownBtn.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n if (this.typeDropdownMenu) this.closeTypeDropdown();\n else this.openTypeDropdown();\n });\n\n this.typeDropdownContainer.appendChild(this.typeDropdownBtn);\n return this.typeDropdownContainer;\n }\n\n private renderTypeDropdownTrigger(): void {\n const active = this.typeOptions.find((o) => this.activeFilters.has(o.value)) ?? this.typeOptions[0];\n if (!active) return;\n\n this.typeDropdownBtn.replaceChildren();\n this.typeDropdownBtn.style.setProperty(\"--sp-chip-color\", active.color);\n this.typeDropdownBtn.style.setProperty(\"--sp-chip-bg\", active.bg);\n this.typeDropdownBtn.dataset.filter = active.value;\n this.typeDropdownBtn.classList.toggle(\"sp-filter-dropdown-btn--filtered\", active.value !== \"all\");\n this.typeDropdownBtn.setAttribute(\"aria-label\", `${this.t(\"type.label\")}: ${active.label}`);\n\n const iconWrap = el(\"span\", { class: \"sp-filter-dropdown-btn__icon\" });\n iconWrap.appendChild(parseSvg(active.icon));\n this.typeDropdownBtn.appendChild(iconWrap);\n\n const labelWrap = el(\"span\", { class: \"sp-filter-dropdown-btn__label\" });\n const prefix = el(\"span\", { class: \"sp-filter-dropdown-btn__prefix\" });\n setText(prefix, this.t(\"type.label\"));\n const value = el(\"span\", { class: \"sp-filter-dropdown-btn__value\" });\n setText(value, active.label);\n labelWrap.appendChild(prefix);\n labelWrap.appendChild(value);\n this.typeDropdownBtn.appendChild(labelWrap);\n\n const chevron = el(\"span\", { class: \"sp-filter-dropdown-btn__chevron\" });\n chevron.appendChild(parseSvg(ICON_CHEVRON_DOWN));\n this.typeDropdownBtn.appendChild(chevron);\n }\n\n private openTypeDropdown(): void {\n this.typeDropdownMenu = el(\"div\", { class: \"sp-filter-dropdown-menu\" });\n this.typeDropdownMenu.setAttribute(\"role\", \"listbox\");\n this.typeDropdownMenu.setAttribute(\"aria-label\", this.t(\"type.label\"));\n this.typeDropdownBtn.setAttribute(\"aria-expanded\", \"true\");\n\n for (const option of this.typeOptions) {\n const item = document.createElement(\"button\");\n item.type = \"button\";\n const isActive = this.activeFilters.has(option.value);\n item.className = `sp-filter-dropdown-option${isActive ? \" sp-filter-dropdown-option--active\" : \"\"}`;\n item.style.setProperty(\"--sp-chip-color\", option.color);\n item.style.setProperty(\"--sp-chip-bg\", option.bg);\n item.dataset.filter = option.value;\n item.setAttribute(\"role\", \"option\");\n item.setAttribute(\"aria-selected\", String(isActive));\n\n const iconWrap = el(\"span\", { class: \"sp-filter-dropdown-option__icon\" });\n iconWrap.appendChild(parseSvg(option.icon));\n item.appendChild(iconWrap);\n\n const labelEl = el(\"span\", { class: \"sp-filter-dropdown-option__label\" });\n setText(labelEl, option.label);\n item.appendChild(labelEl);\n\n if (isActive) {\n const checkWrap = el(\"span\", { class: \"sp-filter-dropdown-option__check\" });\n checkWrap.appendChild(parseSvg(ICON_CHECK));\n item.appendChild(checkWrap);\n }\n\n item.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n this.selectTypeFilter(option.value);\n });\n\n this.typeDropdownMenu.appendChild(item);\n }\n\n this.typeDropdownContainer.appendChild(this.typeDropdownMenu);\n\n requestAnimationFrame(() => {\n this.typeDropdownOutsideHandler = (e: MouseEvent) => {\n if (this.typeDropdownMenu && !this.typeDropdownContainer.contains(e.target as Node)) {\n this.closeTypeDropdown();\n }\n };\n document.addEventListener(\"click\", this.typeDropdownOutsideHandler, true);\n });\n\n this.typeDropdownMenu.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Escape\") {\n this.closeTypeDropdown();\n this.typeDropdownBtn.focus();\n }\n });\n }\n\n private closeTypeDropdown(): void {\n if (this.typeDropdownMenu) {\n this.typeDropdownMenu.remove();\n this.typeDropdownMenu = null;\n }\n this.typeDropdownBtn.setAttribute(\"aria-expanded\", \"false\");\n if (this.typeDropdownOutsideHandler) {\n document.removeEventListener(\"click\", this.typeDropdownOutsideHandler, true);\n this.typeDropdownOutsideHandler = null;\n }\n }\n\n private selectTypeFilter(value: string): void {\n this.activeFilters.clear();\n this.activeFilters.add(value);\n this.renderTypeDropdownTrigger();\n this.closeTypeDropdown();\n this.loadFeedbacks().catch(() => {});\n }\n\n private buildStatusSegmented(): HTMLElement {\n this.statusOptions = [\n {\n value: \"all\",\n label: this.t(\"panel.statusAll\"),\n icon: ICON_LAYERS,\n color: this.colors.accent,\n bg: this.colors.accentLight,\n },\n {\n value: \"open\",\n label: this.t(\"panel.statusOpen\"),\n icon: ICON_DOT_OPEN,\n color: this.colors.statusOpen,\n bg: this.colors.statusOpenBg,\n },\n {\n value: \"resolved\",\n label: this.t(\"panel.statusResolved\"),\n icon: ICON_CHECK,\n color: this.colors.statusResolved,\n bg: this.colors.statusResolvedBg,\n },\n ];\n\n this.statusSegmented = el(\"div\", { class: \"sp-segmented\", role: \"radiogroup\" });\n this.statusSegmented.setAttribute(\"aria-label\", this.t(\"status.label\"));\n\n for (const option of this.statusOptions) {\n const btn = document.createElement(\"button\");\n btn.type = \"button\";\n btn.className = `sp-segmented__btn sp-segmented__btn--${option.value}`;\n btn.dataset.statusFilter = option.value;\n btn.setAttribute(\"role\", \"radio\");\n const isActive = this.activeStatusFilter === option.value;\n btn.setAttribute(\"aria-checked\", String(isActive));\n btn.tabIndex = isActive ? 0 : -1;\n if (isActive) btn.classList.add(\"sp-segmented__btn--active\");\n btn.style.setProperty(\"--sp-chip-color\", option.color);\n btn.style.setProperty(\"--sp-chip-bg\", option.bg);\n\n const iconWrap = el(\"span\", { class: \"sp-segmented__icon\" });\n iconWrap.appendChild(parseSvg(option.icon));\n btn.appendChild(iconWrap);\n\n const labelEl = el(\"span\", { class: \"sp-segmented__label\" });\n setText(labelEl, option.label);\n btn.appendChild(labelEl);\n\n btn.addEventListener(\"click\", () => this.selectStatusFilter(option.value));\n btn.addEventListener(\"keydown\", (e) => this.handleSegmentedKey(e, option.value));\n\n this.statusSegmented.appendChild(btn);\n }\n\n return this.statusSegmented;\n }\n\n private handleSegmentedKey(e: KeyboardEvent, current: \"all\" | FeedbackStatus): void {\n const values = this.statusOptions.map((o) => o.value);\n const idx = values.indexOf(current);\n let nextIdx: number;\n switch (e.key) {\n case \"ArrowLeft\":\n nextIdx = (idx - 1 + values.length) % values.length;\n break;\n case \"ArrowRight\":\n nextIdx = (idx + 1) % values.length;\n break;\n case \"Home\":\n nextIdx = 0;\n break;\n case \"End\":\n nextIdx = values.length - 1;\n break;\n default:\n return;\n }\n e.preventDefault();\n const next = values[nextIdx];\n if (!next) return;\n this.selectStatusFilter(next);\n const btn = this.statusSegmented.querySelector<HTMLButtonElement>(`[data-status-filter=\"${next}\"]`);\n btn?.focus();\n }\n\n private selectStatusFilter(value: \"all\" | FeedbackStatus): void {\n this.activeStatusFilter = value;\n const buttons = this.statusSegmented.querySelectorAll<HTMLButtonElement>(\".sp-segmented__btn\");\n for (const btn of buttons) {\n const isActive = btn.dataset.statusFilter === value;\n btn.classList.toggle(\"sp-segmented__btn--active\", isActive);\n btn.setAttribute(\"aria-checked\", String(isActive));\n btn.tabIndex = isActive ? 0 : -1;\n }\n this.loadFeedbacks().catch(() => {});\n }\n\n /** Get the focused feedback (for keyboard shortcuts) */\n private getFocusedFeedback(): FeedbackResponse | undefined {\n const idx = getFocusedCardIndex(this.listContainer);\n if (idx < 0) return undefined;\n const card = this.listContainer.querySelectorAll<HTMLElement>(\".sp-card\")[idx];\n if (!card) return undefined;\n return this.feedbacks.find((f) => f.id === card.dataset.feedbackId);\n }\n\n scrollToFeedback(feedbackId: string): void {\n const escapedId = CSS.escape(feedbackId);\n const card = this.listContainer.querySelector<HTMLElement>(`[data-feedback-id=\"${escapedId}\"]`);\n if (card) {\n card.scrollIntoView({ behavior: \"smooth\", block: \"center\" });\n card.classList.add(\"sp-anim-flash\");\n card.addEventListener(\n \"animationend\",\n () => {\n card.classList.remove(\"sp-anim-flash\");\n },\n { once: true },\n );\n }\n }\n\n /** Refresh the panel after a new feedback is submitted */\n async refresh(): Promise<void> {\n if (this.isOpen) {\n await this.loadFeedbacks();\n }\n }\n\n destroy(): void {\n this.loadController?.abort();\n if (this.searchTimeout) clearTimeout(this.searchTimeout);\n this.listContainer.removeEventListener(\"click\", this.onListClick);\n this.listContainer.removeEventListener(\"keydown\", this.onListKeydown);\n this.listContainer.removeEventListener(\"mouseover\", this.onListMouseover);\n this.listContainer.removeEventListener(\"mouseout\", this.onListMouseout);\n document.removeEventListener(\"sp-marker-click\", this.onMarkerClick);\n this.closeTypeDropdown();\n this.sortControls.destroy();\n this.bulk.destroy();\n this.exportBtn.destroy();\n this.shortcuts.destroy();\n this.detail.destroy();\n this.root.remove();\n }\n}\n","// ---------------------------------------------------------------------------\n// Config\n// ---------------------------------------------------------------------------\n\n/** Configuration options for the Siteping widget. */\nexport interface SitepingConfig {\n /** HTTP endpoint that receives feedbacks (e.g. '/api/siteping'). Required unless `store` is provided. */\n endpoint?: string | undefined;\n /** Required — project identifier used to scope feedbacks */\n projectName: string;\n /** Direct store for client-side mode. When set, bypasses HTTP and uses the store directly in the browser. */\n store?: SitepingStore | undefined;\n /** FAB position — defaults to 'bottom-right' */\n position?: \"bottom-right\" | \"bottom-left\";\n /** Accent color for the widget UI — defaults to '#0066ff' */\n accentColor?: string;\n /** Show the widget even in production — defaults to false */\n forceShow?: boolean;\n /** Enable debug logging of lifecycle events — defaults to false */\n debug?: boolean;\n /** Color theme — defaults to 'light' */\n theme?: \"light\" | \"dark\" | \"auto\";\n /** UI locale — defaults to 'en'. Built-in: en, fr, de, es, it, pt (Brazilian), ru. Any other string falls back to English. */\n locale?: \"en\" | \"fr\" | \"de\" | \"es\" | \"it\" | \"pt\" | \"ru\" | (string & {}) | undefined;\n /** Called when the widget is skipped (production mode, mobile viewport) */\n onSkip?: (reason: \"production\" | \"mobile\") => void;\n\n // Events\n /** Called when the feedback panel is opened. */\n onOpen?: () => void;\n /** Called when the feedback panel is closed. */\n onClose?: () => void;\n onFeedbackSent?: (feedback: FeedbackResponse) => void;\n onError?: (error: Error) => void;\n /** Called when the user starts drawing an annotation. */\n onAnnotationStart?: () => void;\n /** Called when the user finishes drawing an annotation. */\n onAnnotationEnd?: () => void;\n}\n\n/** Instance returned by initSiteping() with lifecycle methods. */\nexport interface SitepingInstance {\n /** Remove the widget from the DOM and clean up all listeners. */\n destroy: () => void;\n /** Open the panel programmatically */\n open: () => void;\n /** Close the panel */\n close: () => void;\n /** Reload feedbacks from server */\n refresh: () => void;\n /** Subscribe to a public widget event */\n on: <K extends keyof SitepingPublicEvents>(\n event: K,\n listener: (...args: SitepingPublicEvents[K]) => void,\n ) => () => void;\n /** Unsubscribe from a public widget event */\n off: <K extends keyof SitepingPublicEvents>(event: K, listener: (...args: SitepingPublicEvents[K]) => void) => void;\n}\n\n/** Events exposed to consumers via SitepingInstance.on / .off */\nexport interface SitepingPublicEvents {\n \"feedback:sent\": [FeedbackResponse];\n \"feedback:deleted\": [string];\n \"panel:open\": [];\n \"panel:close\": [];\n}\n\n// ---------------------------------------------------------------------------\n// Feedback\n// ---------------------------------------------------------------------------\n\n/** Single source of truth for feedback types — used by both TS types and Zod schemas. */\nexport const FEEDBACK_TYPES = [\"question\", \"change\", \"bug\", \"other\"] as const;\nexport type FeedbackType = (typeof FEEDBACK_TYPES)[number];\n\n/** Single source of truth for feedback statuses. */\nexport const FEEDBACK_STATUSES = [\"open\", \"resolved\"] as const;\nexport type FeedbackStatus = (typeof FEEDBACK_STATUSES)[number];\n\n// ---------------------------------------------------------------------------\n// Abstract Store — adapter pattern\n// ---------------------------------------------------------------------------\n\n/** Input for creating a feedback record in the store. */\nexport interface FeedbackCreateInput {\n projectName: string;\n type: FeedbackType;\n message: string;\n status: FeedbackStatus;\n url: string;\n viewport: string;\n userAgent: string;\n authorName: string;\n authorEmail: string;\n clientId: string;\n annotations: AnnotationCreateInput[];\n}\n\n/** Input for a single annotation when creating a feedback. */\nexport interface AnnotationCreateInput {\n cssSelector: string;\n xpath: string;\n textSnippet: string;\n elementTag: string;\n elementId?: string | undefined;\n textPrefix: string;\n textSuffix: string;\n fingerprint: string;\n neighborText: string;\n xPct: number;\n yPct: number;\n wPct: number;\n hPct: number;\n scrollX: number;\n scrollY: number;\n viewportW: number;\n viewportH: number;\n devicePixelRatio: number;\n}\n\n/** Query parameters for fetching feedbacks. */\nexport interface FeedbackQuery {\n projectName: string;\n type?: FeedbackType | undefined;\n status?: FeedbackStatus | undefined;\n search?: string | undefined;\n page?: number | undefined;\n limit?: number | undefined;\n}\n\n/** Update payload for patching a feedback. */\nexport interface FeedbackUpdateInput {\n status: FeedbackStatus;\n resolvedAt: Date | null;\n}\n\n/** A persisted feedback record returned by the store. */\nexport interface FeedbackRecord {\n id: string;\n type: FeedbackType;\n message: string;\n status: FeedbackStatus;\n projectName: string;\n url: string;\n authorName: string;\n authorEmail: string;\n viewport: string;\n userAgent: string;\n clientId: string;\n resolvedAt: Date | null;\n createdAt: Date;\n updatedAt: Date;\n annotations: AnnotationRecord[];\n}\n\n/** A persisted annotation record returned by the store. */\nexport interface AnnotationRecord {\n id: string;\n feedbackId: string;\n cssSelector: string;\n xpath: string;\n textSnippet: string;\n elementTag: string;\n elementId: string | null;\n textPrefix: string;\n textSuffix: string;\n fingerprint: string;\n neighborText: string;\n xPct: number;\n yPct: number;\n wPct: number;\n hPct: number;\n scrollX: number;\n scrollY: number;\n viewportW: number;\n viewportH: number;\n devicePixelRatio: number;\n createdAt: Date;\n}\n\n// ---------------------------------------------------------------------------\n// Store errors — throw these from adapter implementations\n// ---------------------------------------------------------------------------\n\n/**\n * Thrown when a record is not found during update or delete.\n *\n * Handlers translate this to HTTP 404. Adapters MUST throw this (not\n * ORM-specific errors) so the handler layer remains ORM-agnostic.\n */\nexport class StoreNotFoundError extends Error {\n readonly code = \"STORE_NOT_FOUND\" as const;\n constructor(message = \"Record not found\") {\n super(message);\n this.name = \"StoreNotFoundError\";\n }\n}\n\n/**\n * Thrown when a unique constraint is violated (e.g. duplicate `clientId`).\n *\n * Handlers use this to return the existing record instead of failing.\n */\nexport class StoreDuplicateError extends Error {\n readonly code = \"STORE_DUPLICATE\" as const;\n constructor(message = \"Duplicate record\") {\n super(message);\n this.name = \"StoreDuplicateError\";\n }\n}\n\n/** Type guard — works for `StoreNotFoundError` and ORM-specific equivalents (e.g. Prisma P2025). */\nexport function isStoreNotFound(error: unknown): boolean {\n if (error instanceof StoreNotFoundError) return true;\n // Backwards compat: Prisma's P2025\n return typeof error === \"object\" && error !== null && \"code\" in error && (error as { code: string }).code === \"P2025\";\n}\n\n/** Type guard — works for `StoreDuplicateError` and ORM-specific equivalents (e.g. Prisma P2002). */\nexport function isStoreDuplicate(error: unknown): boolean {\n if (error instanceof StoreDuplicateError) return true;\n // Backwards compat: Prisma's P2002\n return typeof error === \"object\" && error !== null && \"code\" in error && (error as { code: string }).code === \"P2002\";\n}\n\n// ---------------------------------------------------------------------------\n// Store helpers — shared conversion logic for adapters\n// ---------------------------------------------------------------------------\n\n/** Flatten a widget `AnnotationPayload` (nested anchor + rect) into a flat `AnnotationCreateInput`. */\nexport function flattenAnnotation(ann: AnnotationPayload): AnnotationCreateInput {\n return {\n cssSelector: ann.anchor.cssSelector,\n xpath: ann.anchor.xpath,\n textSnippet: ann.anchor.textSnippet,\n elementTag: ann.anchor.elementTag,\n elementId: ann.anchor.elementId,\n textPrefix: ann.anchor.textPrefix,\n textSuffix: ann.anchor.textSuffix,\n fingerprint: ann.anchor.fingerprint,\n neighborText: ann.anchor.neighborText,\n xPct: ann.rect.xPct,\n yPct: ann.rect.yPct,\n wPct: ann.rect.wPct,\n hPct: ann.rect.hPct,\n scrollX: ann.scrollX,\n scrollY: ann.scrollY,\n viewportW: ann.viewportW,\n viewportH: ann.viewportH,\n devicePixelRatio: ann.devicePixelRatio,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Abstract Store — adapter pattern\n// ---------------------------------------------------------------------------\n\n/**\n * Abstract storage interface for Siteping.\n *\n * Any adapter (Prisma, Drizzle, raw SQL, localStorage, etc.) implements this\n * interface. The HTTP handler and widget `StoreClient` operate against\n * `SitepingStore`, decoupled from the storage backend.\n *\n * ## Error contract\n *\n * - **`updateFeedback` / `deleteFeedback`**: throw `StoreNotFoundError` when\n * the record does not exist.\n * - **`createFeedback`**: either return the existing record on duplicate\n * `clientId` (idempotent) or throw `StoreDuplicateError`. The handler\n * handles both patterns.\n * - Other methods should not throw on empty results — return empty arrays or `null`.\n */\nexport interface SitepingStore {\n /** Create a feedback with its annotations. Idempotent on `clientId` — return existing record on duplicate, or throw `StoreDuplicateError`. */\n createFeedback(data: FeedbackCreateInput): Promise<FeedbackRecord>;\n /** Paginated query with optional filters. Returns empty array (not error) when no results. */\n getFeedbacks(query: FeedbackQuery): Promise<{ feedbacks: FeedbackRecord[]; total: number }>;\n /** Lookup by client-generated UUID. Returns `null` (not error) when not found. */\n findByClientId(clientId: string): Promise<FeedbackRecord | null>;\n /** Update status/resolvedAt. Throws `StoreNotFoundError` if `id` does not exist. */\n updateFeedback(id: string, data: FeedbackUpdateInput): Promise<FeedbackRecord>;\n /** Delete a single record. Throws `StoreNotFoundError` if `id` does not exist. */\n deleteFeedback(id: string): Promise<void>;\n /** Bulk delete all feedbacks for a project. No-op (not error) if none exist. */\n deleteAllFeedbacks(projectName: string): Promise<void>;\n}\n\n/** Payload sent from the widget to the server when submitting feedback. */\nexport interface FeedbackPayload {\n projectName: string;\n type: FeedbackType;\n message: string;\n url: string;\n viewport: string;\n userAgent: string;\n authorName: string;\n authorEmail: string;\n annotations: AnnotationPayload[];\n /** Client-generated UUID for deduplication */\n clientId: string;\n}\n\n// ---------------------------------------------------------------------------\n// Annotation — multi-selector anchoring (Hypothesis / W3C Web Annotation)\n// ---------------------------------------------------------------------------\n\n/** DOM anchoring data for re-attaching annotations to page elements. */\nexport interface AnchorData {\n /** CSS selector generated by @medv/finder — primary anchor */\n cssSelector: string;\n /** XPath — fallback 1 */\n xpath: string;\n /** First ~120 chars of element innerText — empty string if none */\n textSnippet: string;\n /** Tag name for validation (e.g. \"DIV\", \"SECTION\") */\n elementTag: string;\n /** Element id attribute if available — most stable */\n elementId?: string | undefined;\n /** ~32 chars of text before this element in document flow (disambiguation) */\n textPrefix: string;\n /** ~32 chars of text after this element in document flow (disambiguation) */\n textSuffix: string;\n /** Structural fingerprint: \"childCount:siblingIdx:attrHash\" */\n fingerprint: string;\n /** Text content of adjacent sibling elements (context) */\n neighborText: string;\n}\n\n/** Drawn rectangle coordinates as percentages relative to the anchor element. */\nexport interface RectData {\n /** X offset as fraction of anchor element width — must be in range [0, 1] */\n xPct: number;\n /** Y offset as fraction of anchor element height — must be in range [0, 1] */\n yPct: number;\n /** Width as fraction of anchor element width — must be in range [0, 1] */\n wPct: number;\n /** Height as fraction of anchor element height — must be in range [0, 1] */\n hPct: number;\n}\n\n/** Annotation data sent as part of a feedback submission. */\nexport interface AnnotationPayload {\n anchor: AnchorData;\n rect: RectData;\n scrollX: number;\n scrollY: number;\n viewportW: number;\n viewportH: number;\n devicePixelRatio: number;\n}\n\n// ---------------------------------------------------------------------------\n// API responses\n// ---------------------------------------------------------------------------\n\n/** Feedback record as returned by the API (dates serialized as strings). */\nexport interface FeedbackResponse {\n id: string;\n projectName: string;\n type: FeedbackType;\n message: string;\n status: FeedbackStatus;\n url: string;\n viewport: string;\n userAgent: string;\n authorName: string;\n authorEmail: string;\n resolvedAt: string | null;\n createdAt: string;\n updatedAt: string;\n annotations: AnnotationResponse[];\n}\n\n/** Annotation record as returned by the API. */\nexport interface AnnotationResponse {\n id: string;\n feedbackId: string;\n cssSelector: string;\n xpath: string;\n textSnippet: string;\n elementTag: string;\n elementId: string | null;\n textPrefix: string;\n textSuffix: string;\n fingerprint: string;\n neighborText: string;\n xPct: number;\n yPct: number;\n wPct: number;\n hPct: number;\n scrollX: number;\n scrollY: number;\n viewportW: number;\n viewportH: number;\n devicePixelRatio: number;\n createdAt: string;\n}\n","import {\n type AnnotationRecord,\n type AnnotationResponse,\n type FeedbackPayload,\n type FeedbackRecord,\n type FeedbackResponse,\n type FeedbackStatus,\n type FeedbackType,\n flattenAnnotation,\n type SitepingStore,\n} from \"@siteping/core\";\nimport type { WidgetClient } from \"./api-client.js\";\n\n/**\n * `WidgetClient` implementation that delegates directly to a `SitepingStore`.\n *\n * Used in client-side mode — the widget calls the store in-process instead of\n * making HTTP requests. Handles the same conversions the HTTP handler normally\n * performs: flattening annotations and serializing dates.\n */\nexport class StoreClient implements WidgetClient {\n constructor(\n private readonly store: SitepingStore,\n private readonly projectName: string,\n ) {}\n\n async sendFeedback(payload: FeedbackPayload): Promise<FeedbackResponse> {\n const record = await this.store.createFeedback({\n projectName: payload.projectName,\n type: payload.type,\n message: payload.message,\n status: \"open\",\n url: payload.url,\n viewport: payload.viewport,\n userAgent: payload.userAgent,\n authorName: payload.authorName,\n authorEmail: payload.authorEmail,\n clientId: payload.clientId,\n annotations: payload.annotations.map(flattenAnnotation),\n });\n\n return toResponse(record);\n }\n\n async getFeedbacks(\n projectName: string,\n options?: { page?: number; limit?: number; type?: FeedbackType; status?: FeedbackStatus; search?: string },\n ): Promise<{ feedbacks: FeedbackResponse[]; total: number }> {\n const { feedbacks, total } = await this.store.getFeedbacks({\n projectName,\n page: options?.page,\n limit: options?.limit,\n type: options?.type,\n status: options?.status,\n search: options?.search,\n });\n\n return { feedbacks: feedbacks.map(toResponse), total };\n }\n\n async resolveFeedback(id: string, resolved: boolean): Promise<FeedbackResponse> {\n const record = await this.store.updateFeedback(id, {\n status: resolved ? \"resolved\" : \"open\",\n resolvedAt: resolved ? new Date() : null,\n });\n return toResponse(record);\n }\n\n async deleteFeedback(id: string): Promise<void> {\n await this.store.deleteFeedback(id);\n }\n\n async deleteAllFeedbacks(projectName: string): Promise<void> {\n await this.store.deleteAllFeedbacks(projectName);\n }\n}\n\n// ---------------------------------------------------------------------------\n// FeedbackRecord (Date) → FeedbackResponse (string) serialization\n// ---------------------------------------------------------------------------\n\nfunction toResponse(record: FeedbackRecord): FeedbackResponse {\n return {\n id: record.id,\n projectName: record.projectName,\n type: record.type,\n message: record.message,\n status: record.status,\n url: record.url,\n viewport: record.viewport,\n userAgent: record.userAgent,\n authorName: record.authorName,\n authorEmail: record.authorEmail,\n resolvedAt: record.resolvedAt?.toISOString() ?? null,\n createdAt: record.createdAt.toISOString(),\n updatedAt: record.updatedAt.toISOString(),\n annotations: record.annotations.map(toAnnotationResponse),\n };\n}\n\nfunction toAnnotationResponse(ann: AnnotationRecord): AnnotationResponse {\n return {\n id: ann.id,\n feedbackId: ann.feedbackId,\n cssSelector: ann.cssSelector,\n xpath: ann.xpath,\n textSnippet: ann.textSnippet,\n elementTag: ann.elementTag,\n elementId: ann.elementId,\n textPrefix: ann.textPrefix,\n textSuffix: ann.textSuffix,\n fingerprint: ann.fingerprint,\n neighborText: ann.neighborText,\n xPct: ann.xPct,\n yPct: ann.yPct,\n wPct: ann.wPct,\n hPct: ann.hPct,\n scrollX: ann.scrollX,\n scrollY: ann.scrollY,\n viewportW: ann.viewportW,\n viewportH: ann.viewportH,\n devicePixelRatio: ann.devicePixelRatio,\n createdAt: ann.createdAt.toISOString(),\n };\n}\n","/**\n * CSS keyframes and animation utilities — Glassmorphism edition.\n *\n * Uses CSS-only spring animations via linear() timing function\n * and refined easing curves for premium motion design.\n */\n\n// Spring easing — computed from a spring simulation (damping: 15, stiffness: 100)\nconst SPRING_LINEAR = `linear(0, 0.006, 0.025, 0.06, 0.11, 0.17, 0.25, 0.34, 0.45, 0.56, 0.67, 0.78, 0.88, 0.95, 1.01, 1.04, 1.05, 1.04, 1.02, 1, 0.99, 1)`;\n\n// Ease-out-expo — fast start, smooth deceleration\nconst EASE_OUT_EXPO = `cubic-bezier(0.16, 1, 0.3, 1)`;\n\n// Spring overshoot — bouncy entrance\nconst SPRING_OVERSHOOT = `cubic-bezier(0.34, 1.56, 0.64, 1)`;\n\n// Smooth decel — for glass transitions\nconst EASE_OUT_QUART = `cubic-bezier(0.25, 1, 0.5, 1)`;\n\nexport const ANIMATION_CSS = `\n /* ---- Keyframes ---- */\n\n @keyframes sp-fab-in {\n from {\n transform: scale(0) rotate(-180deg);\n opacity: 0;\n }\n to {\n transform: scale(1) rotate(0deg);\n opacity: 1;\n }\n }\n\n @keyframes sp-fab-glow {\n 0%, 100% { box-shadow: 0 4px 20px var(--sp-accent-glow), 0 2px 8px rgba(0, 0, 0, 0.08); }\n 50% { box-shadow: 0 4px 28px var(--sp-accent-glow), 0 2px 12px rgba(0, 0, 0, 0.1); }\n }\n\n @keyframes sp-marker-in {\n 0% {\n transform: scale(0);\n opacity: 0;\n }\n 60% {\n transform: scale(1.2);\n opacity: 1;\n }\n 100% {\n transform: scale(1);\n }\n }\n\n @keyframes sp-pulse-ring {\n 0% {\n box-shadow: 0 0 0 0 var(--sp-accent-glow);\n }\n 70% {\n box-shadow: 0 0 0 8px transparent;\n }\n 100% {\n box-shadow: 0 0 0 0 transparent;\n }\n }\n\n @keyframes sp-flash-bg {\n 0% { background-color: var(--sp-accent-light); }\n 100% { background-color: transparent; }\n }\n\n @keyframes sp-slide-up {\n from {\n transform: translateY(8px);\n opacity: 0;\n }\n to {\n transform: translateY(0);\n opacity: 1;\n }\n }\n\n @keyframes sp-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n }\n\n @keyframes sp-shimmer {\n 0% { background-position: -200% 0; }\n 100% { background-position: 200% 0; }\n }\n\n /* ---- Animation classes ---- */\n\n .sp-anim-fab-in {\n animation: sp-fab-in 0.5s ${SPRING_LINEAR} both;\n }\n\n .sp-anim-marker-in {\n animation: sp-marker-in 0.35s ${SPRING_OVERSHOOT} both;\n }\n\n .sp-anim-pulse {\n animation: sp-pulse-ring 0.7s ease-out;\n }\n\n .sp-anim-flash {\n animation: sp-flash-bg 0.5s ${EASE_OUT_QUART};\n }\n\n .sp-anim-slide-up {\n animation: sp-slide-up 0.3s ${EASE_OUT_EXPO} both;\n }\n\n .sp-anim-fade-in {\n animation: sp-fade-in 0.2s ease-out both;\n }\n\n /* ---- Transition utilities ---- */\n\n .sp-panel {\n transform: translateX(110%);\n transition: transform 0.4s ${EASE_OUT_EXPO};\n }\n\n .sp-panel.sp-panel--open {\n transform: translateX(0);\n }\n\n .sp-radial-item {\n opacity: 0;\n pointer-events: none;\n transform: translate(0, 0) scale(0.8);\n transition:\n transform 0.35s ${SPRING_OVERSHOOT},\n opacity 0.2s ease,\n background 0.2s ease,\n border-color 0.2s ease,\n box-shadow 0.2s ease;\n }\n\n .sp-radial-item.sp-radial-item--open {\n opacity: 1;\n pointer-events: auto;\n }\n\n /* Stagger delay via CSS custom property --sp-i */\n .sp-radial-item {\n transition-delay: calc(var(--sp-i, 0) * 50ms);\n }\n\n /* ---- Card stagger animation ---- */\n\n @keyframes sp-card-in {\n from {\n transform: translateY(12px);\n opacity: 0;\n }\n to {\n transform: translateY(0);\n opacity: 1;\n }\n }\n\n .sp-card {\n animation: sp-card-in 0.35s ${EASE_OUT_EXPO} both;\n animation-delay: calc(var(--sp-card-i, 0) * 40ms);\n }\n\n /* ---- Loading spinner ---- */\n\n @keyframes sp-spin {\n to { transform: rotate(360deg); }\n }\n\n .sp-spinner {\n width: 20px;\n height: 20px;\n border: 2px solid var(--sp-border);\n border-top-color: var(--sp-accent);\n border-radius: 50%;\n animation: sp-spin 0.6s linear infinite;\n }\n\n /* ---- Badge bounce ---- */\n\n @keyframes sp-badge-in {\n 0% { transform: scale(0); }\n 60% { transform: scale(1.3); }\n 100% { transform: scale(1); }\n }\n\n .sp-fab-badge {\n animation: sp-badge-in 0.4s ${SPRING_OVERSHOOT} both;\n }\n\n /* ---- Reduced motion ---- */\n\n @media (prefers-reduced-motion: reduce) {\n *, *::before, *::after {\n animation-duration: 0.01ms !important;\n animation-iteration-count: 1 !important;\n transition-duration: 0.01ms !important;\n }\n }\n\n`;\n","import { Z_INDEX_MAX } from \"../constants.js\";\nimport { EXPORT_CSS } from \"../export-utils.js\";\nimport { BULK_CSS } from \"../panel-bulk.js\";\nimport { DETAIL_CSS } from \"../panel-detail.js\";\nimport { SORT_CSS } from \"../panel-sort.js\";\nimport { STATS_CSS } from \"../panel-stats.js\";\nimport { SHORTCUTS_CSS } from \"../shortcuts.js\";\nimport { ANIMATION_CSS } from \"./animations.js\";\nimport { cssVariables, type ThemeColors } from \"./theme.js\";\n\n/**\n * Build the complete CSS stylesheet for the Shadow DOM.\n *\n * Design: Glassmorphism — frosted glass surfaces, soft depth,\n * accent gradients, premium micro-interactions.\n *\n * Principles:\n * - :host uses `all: initial` to block inherited styles\n * - All classes prefixed with sp- (defense in depth)\n * - CSS custom properties for theming\n * - No external fonts — system-ui stack (Inter if available)\n * - :focus-visible on all interactive elements\n * - prefers-reduced-motion support\n */\nexport function buildStyles(colors: ThemeColors): string {\n return `\n :host {\n all: initial;\n position: fixed;\n z-index: ${Z_INDEX_MAX};\n font-family: var(--sp-font);\n font-size: 14px;\n line-height: 1.5;\n color: var(--sp-text);\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n ${cssVariables(colors)}\n\n /* Identity modal — theme-aware backdrop + panel */\n --sp-identity-bg: ${colors.glassBgHeavy};\n --sp-identity-overlay: ${colors.bg === \"#ffffff\" ? \"rgba(15, 23, 42, 0.2)\" : \"rgba(0, 0, 0, 0.4)\"};\n }\n\n *, *::before, *::after {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n }\n\n /* ============================\n Focus visible (accessibility)\n ============================ */\n\n :focus-visible {\n outline: 2px solid var(--sp-accent);\n outline-offset: 2px;\n }\n\n /* ============================\n FAB (Floating Action Button)\n ============================ */\n\n .sp-fab {\n position: fixed;\n width: 52px;\n height: 52px;\n border-radius: var(--sp-radius-full);\n background: var(--sp-accent-gradient);\n color: #fff;\n border: none;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow:\n 0 4px 20px var(--sp-accent-glow),\n 0 2px 8px rgba(0, 0, 0, 0.08);\n transition:\n transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1),\n box-shadow 0.3s ease;\n outline: none;\n }\n\n .sp-fab:focus-visible {\n outline: 2px solid #fff;\n outline-offset: 3px;\n }\n\n .sp-fab:hover {\n transform: translateY(-2px) scale(1.05);\n box-shadow:\n 0 8px 28px var(--sp-accent-glow),\n 0 4px 12px rgba(0, 0, 0, 0.1);\n }\n\n .sp-fab:active {\n transform: translateY(0) scale(0.95);\n transition-duration: 0.1s;\n }\n\n .sp-fab--bottom-right {\n bottom: 24px;\n right: 24px;\n }\n\n .sp-fab--bottom-left {\n bottom: 24px;\n left: 24px;\n }\n\n .sp-fab svg {\n width: 22px;\n height: 22px;\n fill: currentColor;\n transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);\n }\n\n /* ---- FAB Badge ---- */\n\n .sp-fab-badge {\n position: absolute;\n top: -4px;\n right: -4px;\n min-width: 20px;\n height: 20px;\n padding: 0 6px;\n border-radius: var(--sp-radius-full);\n background: #ef4444;\n color: #fff;\n font-size: 11px;\n font-weight: 700;\n display: flex;\n align-items: center;\n justify-content: center;\n border: 2px solid #fff;\n pointer-events: none;\n font-family: var(--sp-font);\n line-height: 1;\n }\n\n /* ============================\n Radial Menu\n ============================ */\n\n .sp-radial {\n position: fixed;\n pointer-events: none;\n width: 52px;\n height: 52px;\n }\n\n .sp-radial--bottom-right {\n bottom: 24px;\n right: 24px;\n }\n\n .sp-radial--bottom-left {\n bottom: 24px;\n left: 24px;\n }\n\n .sp-radial-item {\n position: absolute;\n left: 4px;\n bottom: 4px;\n width: 44px;\n height: 44px;\n border-radius: var(--sp-radius-full);\n background: var(--sp-glass-bg-heavy);\n backdrop-filter: blur(var(--sp-blur));\n -webkit-backdrop-filter: blur(var(--sp-blur));\n color: var(--sp-text);\n border: 1px solid var(--sp-glass-border);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: var(--sp-shadow-md);\n font-size: 12px;\n font-weight: 600;\n }\n\n .sp-radial-item:hover,\n .sp-radial-item:focus-visible {\n background: rgba(255, 255, 255, 0.95);\n border-color: var(--sp-accent);\n color: var(--sp-accent);\n box-shadow:\n var(--sp-shadow-md),\n 0 0 0 3px var(--sp-accent-light);\n outline: none;\n }\n\n .sp-radial-item svg {\n width: 18px;\n height: 18px;\n flex-shrink: 0;\n stroke: currentColor;\n fill: none;\n }\n\n .sp-radial-label {\n white-space: nowrap;\n font-size: 12px;\n font-weight: 500;\n color: var(--sp-text);\n pointer-events: none;\n opacity: 0;\n padding: 4px 12px;\n border-radius: var(--sp-radius);\n background: var(--sp-glass-bg-heavy);\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n border: 1px solid var(--sp-glass-border);\n box-shadow: var(--sp-shadow-sm);\n transform: translateX(4px);\n transition: opacity 0.2s ease, transform 0.2s ease;\n }\n\n .sp-radial-item:hover .sp-radial-label,\n .sp-radial-item:focus-visible .sp-radial-label {\n opacity: 1;\n transform: translateX(0);\n }\n\n /* ============================\n Panel (Side drawer)\n ============================ */\n\n .sp-panel {\n position: fixed;\n top: 0;\n right: 0;\n width: 400px;\n max-width: 100vw;\n height: 100vh;\n height: 100dvh;\n background: var(--sp-glass-bg);\n backdrop-filter: blur(var(--sp-blur-heavy));\n -webkit-backdrop-filter: blur(var(--sp-blur-heavy));\n border-left: 1px solid var(--sp-glass-border);\n box-shadow: var(--sp-shadow-xl);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n @media (max-width: 480px) {\n .sp-panel {\n width: 100vw;\n border-left: none;\n }\n }\n\n .sp-panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 20px 24px;\n border-bottom: 1px solid var(--sp-border);\n background: var(--sp-glass-bg-heavy);\n backdrop-filter: blur(var(--sp-blur));\n -webkit-backdrop-filter: blur(var(--sp-blur));\n position: relative;\n z-index: 2;\n }\n\n .sp-panel-title {\n font-size: 17px;\n font-weight: 700;\n color: var(--sp-text);\n letter-spacing: -0.02em;\n }\n\n .sp-panel-close {\n width: 44px;\n height: 44px;\n border-radius: var(--sp-radius);\n border: none;\n background: transparent;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--sp-text-tertiary);\n transition: all 0.2s ease;\n }\n\n .sp-panel-close:hover {\n background: var(--sp-bg-hover);\n color: var(--sp-text);\n }\n\n .sp-panel-close svg {\n width: 16px;\n height: 16px;\n }\n\n /* ============================\n Filters & Search\n ============================ */\n\n .sp-filters {\n padding: 16px 24px;\n border-bottom: 1px solid var(--sp-border);\n background: var(--sp-glass-bg-heavy);\n backdrop-filter: blur(var(--sp-blur));\n -webkit-backdrop-filter: blur(var(--sp-blur));\n position: sticky;\n top: 0;\n z-index: 1;\n }\n\n .sp-search-wrap {\n position: relative;\n margin-bottom: 12px;\n }\n\n .sp-search {\n width: 100%;\n height: 40px;\n padding: 0 12px 0 38px;\n border-radius: var(--sp-radius);\n border: 1px solid var(--sp-border);\n background: var(--sp-glass-bg-heavy);\n color: var(--sp-text);\n font-family: var(--sp-font);\n font-size: 13px;\n outline: none;\n transition: all 0.2s ease;\n }\n\n .sp-search::placeholder {\n color: var(--sp-text-tertiary);\n }\n\n .sp-search:focus {\n border-color: var(--sp-accent);\n box-shadow: 0 0 0 3px var(--sp-accent-light);\n background: #fff;\n }\n\n .sp-search-icon {\n position: absolute;\n left: 12px;\n top: 50%;\n transform: translateY(-50%);\n color: var(--sp-text-tertiary);\n width: 16px;\n height: 16px;\n transition: color 0.2s ease;\n }\n\n .sp-search:focus ~ .sp-search-icon,\n .sp-search-wrap:focus-within .sp-search-icon {\n color: var(--sp-accent);\n }\n\n /* ============================\n Filter bar (type dropdown + status segmented)\n ============================ */\n\n .sp-filter-bar {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 8px;\n flex-wrap: wrap;\n }\n\n /* ============================\n Type filter dropdown\n ============================ */\n\n .sp-filter-dropdown {\n position: relative;\n flex: 1 1 auto;\n min-width: 0;\n }\n\n .sp-filter-dropdown-btn {\n --sp-chip-color: var(--sp-text-secondary);\n --sp-chip-bg: var(--sp-glass-bg-heavy);\n\n display: inline-flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n height: 32px;\n padding: 0 8px 0 10px;\n border-radius: var(--sp-radius-full);\n border: 1px solid var(--sp-border);\n background: var(--sp-glass-bg-heavy);\n color: var(--sp-text);\n font-family: var(--sp-font);\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n white-space: nowrap;\n transition: background 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease;\n }\n\n .sp-filter-dropdown-btn:hover {\n border-color: var(--sp-chip-color);\n background: var(--sp-chip-bg);\n }\n\n .sp-filter-dropdown-btn[aria-expanded=\"true\"] {\n border-color: var(--sp-chip-color);\n background: var(--sp-chip-bg);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--sp-chip-color) 14%, transparent);\n }\n\n .sp-filter-dropdown-btn--filtered {\n border-color: var(--sp-chip-color);\n background: var(--sp-chip-bg);\n }\n\n .sp-filter-dropdown-btn__icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 18px;\n height: 18px;\n flex-shrink: 0;\n color: var(--sp-chip-color);\n }\n\n .sp-filter-dropdown-btn__icon svg {\n width: 14px;\n height: 14px;\n }\n\n .sp-filter-dropdown-btn__label {\n display: inline-flex;\n align-items: baseline;\n gap: 6px;\n flex: 1;\n min-width: 0;\n overflow: hidden;\n }\n\n .sp-filter-dropdown-btn__prefix {\n color: var(--sp-text-tertiary);\n font-weight: 500;\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n }\n\n .sp-filter-dropdown-btn__value {\n color: var(--sp-chip-color);\n font-weight: 600;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .sp-filter-dropdown-btn__chevron {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 14px;\n height: 14px;\n flex-shrink: 0;\n color: var(--sp-text-tertiary);\n transition: transform 0.18s ease, color 0.18s ease;\n }\n\n .sp-filter-dropdown-btn__chevron svg {\n width: 12px;\n height: 12px;\n }\n\n .sp-filter-dropdown-btn[aria-expanded=\"true\"] .sp-filter-dropdown-btn__chevron {\n transform: rotate(180deg);\n color: var(--sp-chip-color);\n }\n\n .sp-filter-dropdown-menu {\n position: absolute;\n top: calc(100% + 6px);\n left: 0;\n right: 0;\n min-width: 180px;\n padding: 4px;\n border-radius: var(--sp-radius);\n background: var(--sp-glass-bg-heavy);\n backdrop-filter: blur(var(--sp-blur-heavy));\n -webkit-backdrop-filter: blur(var(--sp-blur-heavy));\n border: 1px solid var(--sp-glass-border);\n box-shadow: var(--sp-shadow-md);\n z-index: 10;\n animation: sp-filter-menu-in 0.15s ease-out both;\n }\n\n @keyframes sp-filter-menu-in {\n from { opacity: 0; transform: translateY(-4px) scale(0.98); }\n to { opacity: 1; transform: translateY(0) scale(1); }\n }\n\n .sp-filter-dropdown-option {\n --sp-chip-color: var(--sp-text-secondary);\n --sp-chip-bg: transparent;\n\n display: flex;\n align-items: center;\n gap: 10px;\n width: 100%;\n padding: 8px 10px;\n border: none;\n border-radius: 8px;\n background: transparent;\n color: var(--sp-text);\n font-family: var(--sp-font);\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n text-align: left;\n transition: background 0.12s ease, color 0.12s ease;\n }\n\n .sp-filter-dropdown-option__icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n flex-shrink: 0;\n border-radius: 6px;\n background: var(--sp-chip-bg);\n color: var(--sp-chip-color);\n }\n\n .sp-filter-dropdown-option__icon svg {\n width: 13px;\n height: 13px;\n }\n\n .sp-filter-dropdown-option__label {\n flex: 1;\n min-width: 0;\n }\n\n .sp-filter-dropdown-option__check {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 14px;\n height: 14px;\n flex-shrink: 0;\n color: var(--sp-chip-color);\n }\n\n .sp-filter-dropdown-option__check svg {\n width: 13px;\n height: 13px;\n }\n\n .sp-filter-dropdown-option:hover {\n background: var(--sp-bg-hover);\n }\n\n .sp-filter-dropdown-option--active {\n color: var(--sp-chip-color);\n font-weight: 600;\n }\n\n .sp-filter-dropdown-option--active:hover {\n background: var(--sp-chip-bg);\n }\n\n /* ============================\n Status segmented control\n ============================ */\n\n .sp-segmented {\n display: inline-flex;\n align-items: stretch;\n padding: 2px;\n border-radius: var(--sp-radius-full);\n border: 1px solid var(--sp-border);\n background: var(--sp-glass-bg-heavy);\n flex-shrink: 0;\n }\n\n .sp-segmented__btn {\n --sp-chip-color: var(--sp-text-tertiary);\n --sp-chip-bg: transparent;\n\n display: inline-flex;\n align-items: center;\n gap: 5px;\n height: 26px;\n padding: 0 10px;\n border: none;\n border-radius: var(--sp-radius-full);\n background: transparent;\n color: var(--sp-text-secondary);\n font-family: var(--sp-font);\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n white-space: nowrap;\n transition: background 0.18s ease, color 0.18s ease, box-shadow 0.18s ease;\n }\n\n .sp-segmented__icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 13px;\n height: 13px;\n flex-shrink: 0;\n color: var(--sp-chip-color);\n transition: color 0.18s ease, transform 0.18s ease;\n }\n\n .sp-segmented__icon svg {\n width: 13px;\n height: 13px;\n }\n\n .sp-segmented__btn:hover {\n color: var(--sp-chip-color);\n }\n\n .sp-segmented__btn:hover .sp-segmented__icon {\n color: var(--sp-chip-color);\n }\n\n .sp-segmented__btn--active {\n background: var(--sp-chip-bg);\n color: var(--sp-chip-color);\n font-weight: 600;\n box-shadow:\n inset 0 0 0 1px color-mix(in srgb, var(--sp-chip-color) 35%, transparent),\n 0 1px 2px rgba(0, 0, 0, 0.04);\n }\n\n .sp-segmented__btn--active .sp-segmented__icon {\n color: var(--sp-chip-color);\n }\n\n .sp-segmented__btn--open.sp-segmented__btn--active .sp-segmented__icon {\n animation: sp-segmented-pulse 2.4s ease-in-out infinite;\n }\n\n @keyframes sp-segmented-pulse {\n 0%, 100% { transform: scale(1); }\n 50% { transform: scale(0.85); }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .sp-filter-dropdown-btn,\n .sp-filter-dropdown-btn__chevron,\n .sp-filter-dropdown-option,\n .sp-segmented__btn,\n .sp-segmented__icon {\n transition: none;\n }\n .sp-filter-dropdown-menu {\n animation: none;\n }\n .sp-segmented__btn--open.sp-segmented__btn--active .sp-segmented__icon {\n animation: none;\n }\n }\n\n /* ============================\n Feedback Cards\n ============================ */\n\n .sp-list {\n flex: 1;\n overflow-y: auto;\n padding: 8px 12px;\n }\n\n .sp-list::-webkit-scrollbar {\n width: 6px;\n }\n\n .sp-list::-webkit-scrollbar-track {\n background: transparent;\n }\n\n .sp-list::-webkit-scrollbar-thumb {\n background: var(--sp-border);\n border-radius: var(--sp-radius-full);\n }\n\n .sp-list::-webkit-scrollbar-thumb:hover {\n background: var(--sp-text-tertiary);\n }\n\n .sp-card {\n display: flex;\n padding: 14px 16px;\n margin-bottom: 6px;\n cursor: pointer;\n border-radius: var(--sp-radius);\n background: var(--sp-glass-bg-heavy);\n border: 1px solid var(--sp-glass-border);\n box-shadow: var(--sp-shadow-xs);\n transition: all 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);\n }\n\n .sp-card:hover {\n background: #fff;\n border-color: var(--sp-border);\n box-shadow: var(--sp-shadow-md);\n transform: translateY(-2px);\n }\n\n .sp-card:active {\n transform: translateY(0) scale(0.99);\n transition-duration: 0.1s;\n }\n\n .sp-card-bar {\n width: 3px;\n border-radius: var(--sp-radius-full);\n margin-right: 14px;\n flex-shrink: 0;\n }\n\n .sp-card-body {\n flex: 1;\n min-width: 0;\n }\n\n .sp-card-header {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 6px;\n }\n\n .sp-card-number {\n font-size: 12px;\n font-weight: 700;\n color: var(--sp-text-tertiary);\n font-variant-numeric: tabular-nums;\n }\n\n .sp-badge {\n padding: 2px 10px;\n border-radius: var(--sp-radius-full);\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.02em;\n }\n\n .sp-card-date {\n font-size: 11px;\n color: var(--sp-text-tertiary);\n margin-left: auto;\n }\n\n .sp-card-message {\n font-size: 13px;\n line-height: 1.5;\n color: var(--sp-text);\n display: -webkit-box;\n -webkit-line-clamp: 3;\n -webkit-box-orient: vertical;\n overflow: hidden;\n }\n\n .sp-card-message--expanded {\n -webkit-line-clamp: unset;\n }\n\n .sp-card-expand {\n font-size: 12px;\n font-weight: 500;\n color: var(--sp-accent);\n cursor: pointer;\n background: none;\n border: none;\n padding: 4px 0;\n font-family: var(--sp-font);\n transition: opacity 0.15s ease;\n }\n\n .sp-card-expand:hover {\n opacity: 0.8;\n }\n\n .sp-card-footer {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 6px;\n margin-top: 10px;\n }\n\n .sp-btn-resolve,\n .sp-btn-delete {\n padding: 8px 14px;\n border-radius: var(--sp-radius-full);\n border: 1px solid var(--sp-border);\n background: transparent;\n color: var(--sp-text-secondary);\n font-family: var(--sp-font);\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n display: flex;\n align-items: center;\n gap: 4px;\n transition: all 0.2s ease;\n }\n\n .sp-btn-resolve svg,\n .sp-btn-delete svg {\n width: 14px;\n height: 14px;\n }\n\n .sp-btn-resolve:hover {\n border-color: #22c55e;\n color: #22c55e;\n background: rgba(34, 197, 94, 0.06);\n }\n\n .sp-btn-delete:hover {\n border-color: #ef4444;\n color: #ef4444;\n background: rgba(239, 68, 68, 0.06);\n }\n\n .sp-btn-resolve:disabled,\n .sp-btn-delete:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n pointer-events: none;\n }\n\n .sp-spinner--sm {\n width: 14px;\n height: 14px;\n }\n\n /* ---- Delete All (header) ---- */\n\n .sp-panel-header-right {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .sp-btn-delete-all {\n padding: 5px 12px;\n border-radius: var(--sp-radius-full);\n border: 1px solid var(--sp-border);\n background: transparent;\n color: var(--sp-text-tertiary);\n font-family: var(--sp-font);\n font-size: 11px;\n font-weight: 500;\n cursor: pointer;\n display: flex;\n align-items: center;\n gap: 4px;\n transition: all 0.2s ease;\n }\n\n .sp-btn-delete-all svg {\n width: 13px;\n height: 13px;\n }\n\n .sp-btn-delete-all:hover {\n border-color: #ef4444;\n color: #ef4444;\n background: rgba(239, 68, 68, 0.06);\n }\n\n .sp-btn-delete-all:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n pointer-events: none;\n }\n\n /* ---- Confirm Dialog ---- */\n\n .sp-confirm-backdrop {\n position: fixed;\n inset: 0;\n background: var(--sp-backdrop, rgba(15, 23, 42, 0.2));\n backdrop-filter: blur(var(--sp-blur));\n -webkit-backdrop-filter: blur(var(--sp-blur));\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: ${Z_INDEX_MAX};\n opacity: 0;\n transition: opacity 0.2s ease;\n }\n\n .sp-confirm-dialog {\n width: 340px;\n padding: 28px;\n border-radius: 20px;\n background: var(--sp-glass-bg-heavy);\n backdrop-filter: blur(var(--sp-blur-heavy));\n -webkit-backdrop-filter: blur(var(--sp-blur-heavy));\n border: 1px solid var(--sp-glass-border);\n box-shadow: var(--sp-shadow-xl);\n font-family: var(--sp-font);\n transform: translateY(8px) scale(0.97);\n transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1);\n }\n\n .sp-confirm-title {\n font-size: 17px;\n font-weight: 700;\n color: var(--sp-text);\n letter-spacing: -0.02em;\n margin-bottom: 8px;\n }\n\n .sp-confirm-message {\n font-size: 14px;\n color: var(--sp-text-secondary);\n line-height: 1.5;\n margin-bottom: 20px;\n }\n\n .sp-confirm-actions {\n display: flex;\n gap: 8px;\n justify-content: flex-end;\n }\n\n .sp-btn-danger {\n height: 40px;\n padding: 0 22px;\n border-radius: var(--sp-radius);\n border: none;\n background: #ef4444;\n color: #fff;\n font-family: var(--sp-font);\n font-size: 14px;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.2s ease;\n box-shadow: 0 2px 8px rgba(239, 68, 68, 0.25);\n }\n\n .sp-btn-danger:hover {\n background: #dc2626;\n box-shadow: 0 4px 16px rgba(239, 68, 68, 0.3);\n transform: translateY(-1px);\n }\n\n .sp-btn-danger:active {\n transform: translateY(0) scale(0.98);\n transition-duration: 0.1s;\n }\n\n .sp-card--resolved {\n opacity: 0.5;\n }\n\n .sp-card--resolved .sp-card-message {\n text-decoration: line-through;\n text-decoration-color: var(--sp-text-tertiary);\n }\n\n /* ============================\n Loading State\n ============================ */\n\n .sp-loading {\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 48px 24px;\n }\n\n /* ============================\n Identity Form\n ============================ */\n\n .sp-identity-title {\n font-size: 17px;\n font-weight: 700;\n color: var(--sp-text);\n letter-spacing: -0.02em;\n }\n\n .sp-input {\n width: 100%;\n height: 42px;\n padding: 0 14px;\n border-radius: var(--sp-radius);\n border: 1px solid var(--sp-border);\n background: var(--sp-glass-bg-heavy);\n color: var(--sp-text);\n font-family: var(--sp-font);\n font-size: 14px;\n outline: none;\n transition: all 0.2s ease;\n }\n\n .sp-input::placeholder {\n color: var(--sp-text-tertiary);\n }\n\n .sp-input:focus {\n border-color: var(--sp-accent);\n box-shadow: 0 0 0 3px var(--sp-accent-light);\n background: #fff;\n }\n\n .sp-input-label {\n font-size: 13px;\n font-weight: 500;\n color: var(--sp-text-secondary);\n margin-bottom: 6px;\n display: block;\n }\n\n /* ============================\n Buttons\n ============================ */\n\n .sp-btn-primary {\n height: 40px;\n padding: 0 22px;\n border-radius: var(--sp-radius);\n border: none;\n background: var(--sp-accent-gradient);\n color: #fff;\n font-family: var(--sp-font);\n font-size: 14px;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.2s ease;\n box-shadow: 0 2px 8px var(--sp-accent-glow);\n }\n\n .sp-btn-primary:hover {\n box-shadow: 0 4px 16px var(--sp-accent-glow);\n transform: translateY(-1px);\n }\n\n .sp-btn-primary:active {\n transform: translateY(0) scale(0.98);\n transition-duration: 0.1s;\n }\n\n .sp-btn-primary:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n transform: none;\n box-shadow: none;\n }\n\n .sp-btn-ghost {\n height: 40px;\n padding: 0 22px;\n border-radius: var(--sp-radius);\n border: 1px solid var(--sp-border);\n background: var(--sp-glass-bg-heavy);\n color: var(--sp-text-secondary);\n font-family: var(--sp-font);\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.2s ease;\n }\n\n .sp-btn-ghost:hover {\n border-color: var(--sp-accent);\n color: var(--sp-accent);\n background: var(--sp-accent-light);\n }\n\n /* ============================\n Empty State\n ============================ */\n\n .sp-empty {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 56px 24px;\n color: var(--sp-text-tertiary);\n text-align: center;\n gap: 8px;\n animation: sp-fade-in 0.3s ease-out both;\n }\n\n .sp-empty-text {\n font-size: 14px;\n font-weight: 500;\n }\n\n /* ============================\n Load More\n ============================ */\n\n .sp-load-more-wrap {\n display: flex;\n justify-content: center;\n padding: 12px 0 4px;\n }\n\n .sp-btn-load-more {\n width: 100%;\n }\n\n /* ============================\n Forced Colors / High Contrast\n ============================ */\n\n @media (forced-colors: active) {\n .sp-fab,\n .sp-radial-item,\n .sp-filter-dropdown-btn,\n .sp-segmented,\n .sp-segmented__btn,\n .sp-card,\n .sp-panel-close,\n .sp-search,\n .sp-btn-resolve,\n .sp-btn-delete,\n .sp-btn-delete-all,\n .sp-btn-primary,\n .sp-btn-ghost,\n .sp-btn-danger,\n .sp-card-expand,\n .sp-input,\n .sp-confirm-dialog {\n border: 2px solid ButtonText !important;\n background: Canvas !important;\n color: ButtonText !important;\n }\n\n .sp-segmented__btn--active {\n background: Highlight !important;\n color: HighlightText !important;\n }\n\n .sp-filter-dropdown-menu {\n border: 2px solid ButtonText !important;\n background: Canvas !important;\n }\n\n .sp-filter-dropdown-option--active {\n background: Highlight !important;\n color: HighlightText !important;\n }\n\n .sp-fab:focus-visible,\n .sp-radial-item:focus-visible,\n .sp-filter-dropdown-btn:focus-visible,\n .sp-segmented__btn:focus-visible,\n .sp-filter-dropdown-option:focus-visible,\n .sp-panel-close:focus-visible,\n .sp-btn-resolve:focus-visible,\n .sp-btn-delete:focus-visible,\n .sp-btn-delete-all:focus-visible,\n .sp-btn-primary:focus-visible,\n .sp-btn-ghost:focus-visible,\n .sp-btn-danger:focus-visible,\n .sp-card-expand:focus-visible,\n .sp-input:focus-visible,\n .sp-search:focus-visible {\n outline: 3px solid Highlight !important;\n }\n\n .sp-panel {\n border: 2px solid ButtonText !important;\n }\n\n .sp-fab-badge {\n border: 2px solid ButtonText !important;\n background: Canvas !important;\n color: ButtonText !important;\n }\n\n .sp-card-bar {\n background: ButtonText !important;\n }\n }\n\n ${ANIMATION_CSS}\n ${STATS_CSS}\n ${SORT_CSS}\n ${BULK_CSS}\n ${EXPORT_CSS}\n ${SHORTCUTS_CSS}\n ${DETAIL_CSS}\n `;\n}\n","import type { FeedbackResponse } from \"@siteping/core\";\nimport { Z_INDEX_MAX } from \"./constants.js\";\nimport { el, formatRelativeDate, setText } from \"./dom-utils.js\";\nimport { createT, getTypeLabel } from \"./i18n/index.js\";\nimport { getTypeBgColor, getTypeColor, type ThemeColors } from \"./styles/theme.js\";\n\nconst SHOW_DELAY = 120;\nconst HIDE_DELAY = 80;\n\n/**\n * Tooltip shown on annotation marker hover.\n *\n * Glassmorphism design: frosted glass with pastel badge,\n * smooth entrance animation, directional arrow.\n * Lives outside Shadow DOM.\n */\nexport class Tooltip {\n private root: HTMLElement;\n private arrow: HTMLElement;\n private showTimer: ReturnType<typeof setTimeout> | null = null;\n private hideTimer: ReturnType<typeof setTimeout> | null = null;\n private currentFeedbackId: string | null = null;\n\n readonly tooltipId = \"sp-tooltip\";\n\n constructor(\n private readonly colors: ThemeColors,\n private readonly locale: string = \"en\",\n ) {\n this.root = el(\"div\", {\n style: `\n position: fixed;\n z-index: ${Z_INDEX_MAX};\n max-width: 280px;\n padding: 12px 14px;\n border-radius: 14px;\n background: ${this.colors.glassBgHeavy};\n backdrop-filter: blur(24px);\n -webkit-backdrop-filter: blur(24px);\n border: 1px solid ${this.colors.glassBorder};\n box-shadow: 0 8px 32px ${this.colors.shadow}, 0 2px 8px ${this.colors.shadow};\n font-family: \"Inter\", system-ui, -apple-system, sans-serif;\n pointer-events: auto;\n opacity: 0;\n transform: translateY(6px) scale(0.97);\n transition: opacity 0.2s cubic-bezier(0.16, 1, 0.3, 1), transform 0.2s cubic-bezier(0.16, 1, 0.3, 1);\n visibility: hidden;\n -webkit-font-smoothing: antialiased;\n `,\n });\n\n this.root.setAttribute(\"role\", \"tooltip\");\n this.root.id = this.tooltipId;\n\n // Arrow element\n this.arrow = el(\"div\", {\n style: `\n position: absolute;\n width: 12px;\n height: 12px;\n background: ${this.colors.glassBgHeavy};\n border: 1px solid ${this.colors.glassBorder};\n transform: rotate(45deg);\n pointer-events: none;\n `,\n });\n this.root.appendChild(this.arrow);\n\n this.root.addEventListener(\"mouseenter\", () => this.cancelHide());\n this.root.addEventListener(\"mouseleave\", () => this.scheduleHide());\n document.body.appendChild(this.root);\n }\n\n show(feedback: FeedbackResponse, anchorRect: DOMRect): void {\n if (this.currentFeedbackId === feedback.id) return;\n this.cancelHide();\n this.cancelShow();\n\n this.showTimer = setTimeout(() => {\n this.currentFeedbackId = feedback.id;\n this.render(feedback);\n this.position(anchorRect);\n\n // Check prefers-reduced-motion live (not cached at construction time)\n const reduceMotion =\n typeof window !== \"undefined\" && window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n this.root.style.transition = reduceMotion ? \"none\" : \"\";\n\n this.root.style.visibility = \"visible\";\n this.root.style.opacity = \"1\";\n this.root.style.transform = \"translateY(0) scale(1)\";\n }, SHOW_DELAY);\n }\n\n scheduleHide(): void {\n this.cancelHide();\n this.hideTimer = setTimeout(() => this.hide(), HIDE_DELAY);\n }\n\n hide(): void {\n this.cancelShow();\n this.currentFeedbackId = null;\n this.root.style.opacity = \"0\";\n this.root.style.transform = \"translateY(6px) scale(0.97)\";\n setTimeout(() => {\n if (!this.currentFeedbackId) {\n this.root.style.visibility = \"hidden\";\n }\n }, 200);\n }\n\n private cancelShow(): void {\n if (this.showTimer) {\n clearTimeout(this.showTimer);\n this.showTimer = null;\n }\n }\n\n private cancelHide(): void {\n if (this.hideTimer) {\n clearTimeout(this.hideTimer);\n this.hideTimer = null;\n }\n }\n\n private render(feedback: FeedbackResponse): void {\n // Clear previous content safely (except arrow)\n const children = Array.from(this.root.children);\n for (const child of children) {\n if (child !== this.arrow) child.remove();\n }\n\n const typeColor = getTypeColor(feedback.type, this.colors);\n const typeBg = getTypeBgColor(feedback.type, this.colors);\n const t = createT(this.locale);\n const typeLabel = getTypeLabel(feedback.type, t);\n\n // Header row: badge + date\n const header = el(\"div\", { style: \"display:flex;align-items:center;gap:8px;margin-bottom:8px;\" });\n\n const badge = el(\"span\", {\n style: `\n padding:3px 10px;border-radius:9999px;\n font-size:11px;font-weight:600;\n color:${typeColor};background:${typeBg};\n letter-spacing:0.02em;\n `,\n });\n setText(badge, typeLabel);\n\n const date = el(\"span\", { style: `font-size:11px;color:${this.colors.textSecondary};margin-left:auto;` });\n setText(date, formatRelativeDate(feedback.createdAt, this.locale));\n\n header.appendChild(badge);\n header.appendChild(date);\n\n // Message body (safe — textContent only)\n const body = el(\"div\", {\n style: `font-size:13px;line-height:1.55;color:${this.colors.text};display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;`,\n });\n setText(body, feedback.message);\n\n // Insert content before arrow\n this.root.insertBefore(header, this.arrow);\n this.root.insertBefore(body, this.arrow);\n }\n\n private position(anchorRect: DOMRect): void {\n const tooltipRect = this.root.getBoundingClientRect();\n const gap = 10;\n\n let top = anchorRect.top - tooltipRect.height - gap;\n let left = anchorRect.left + anchorRect.width / 2 - tooltipRect.width / 2;\n let isAbove = true;\n\n // Flip below if not enough space above\n if (top < 8) {\n top = anchorRect.bottom + gap;\n isAbove = false;\n }\n\n left = Math.max(8, Math.min(left, window.innerWidth - tooltipRect.width - 8));\n\n this.root.style.top = `${top}px`;\n this.root.style.left = `${left}px`;\n\n // Position arrow\n const arrowLeft = Math.max(16, Math.min(anchorRect.left + anchorRect.width / 2 - left - 6, tooltipRect.width - 22));\n\n if (isAbove) {\n // Arrow at bottom, pointing down\n this.arrow.style.cssText = `\n position:absolute;\n width:12px;height:12px;\n background:${this.colors.glassBgHeavy};\n border-right:1px solid ${this.colors.glassBorder};\n border-bottom:1px solid ${this.colors.glassBorder};\n transform:rotate(45deg);\n pointer-events:none;\n bottom:-6px;\n left:${arrowLeft}px;\n `;\n } else {\n // Arrow at top, pointing up\n this.arrow.style.cssText = `\n position:absolute;\n width:12px;height:12px;\n background:${this.colors.glassBgHeavy};\n border-left:1px solid ${this.colors.glassBorder};\n border-top:1px solid ${this.colors.glassBorder};\n transform:rotate(45deg);\n pointer-events:none;\n top:-6px;\n left:${arrowLeft}px;\n `;\n }\n }\n\n /** Check if a DOM node belongs to this tooltip (for MutationObserver filtering). */\n contains(node: Node): boolean {\n return this.root.contains(node);\n }\n\n destroy(): void {\n this.cancelShow();\n this.cancelHide();\n this.root.remove();\n }\n}\n","import type { FeedbackPayload, SitepingConfig, SitepingInstance, SitepingPublicEvents } from \"@siteping/core\";\nimport { Annotator } from \"./annotator.js\";\nimport { ApiClient, flushRetryQueue, type WidgetClient } from \"./api-client.js\";\nimport { MOBILE_BREAKPOINT, PAGE_SIZE, Z_INDEX_MAX } from \"./constants.js\";\nimport { EventBus, type PublicWidgetEvents, type WidgetEvents } from \"./events.js\";\nimport { Fab } from \"./fab.js\";\nimport { createT, type TFunction } from \"./i18n/index.js\";\nimport { getIdentity, type Identity, saveIdentity } from \"./identity.js\";\nimport { MarkerManager } from \"./markers.js\";\nimport { Panel } from \"./panel.js\";\nimport { StoreClient } from \"./store-client.js\";\nimport { buildStyles } from \"./styles/base.js\";\nimport { buildThemeColors } from \"./styles/theme.js\";\nimport { Tooltip } from \"./tooltip.js\";\n\n/** Singleton guard — prevents duplicate widgets from overlapping */\nlet instance: SitepingInstance | null = null;\n\n/** Build a no-op SitepingInstance for when the widget is skipped */\nfunction skippedInstance(): SitepingInstance {\n const noop = () => {};\n return {\n destroy: noop,\n open: noop,\n close: noop,\n refresh: noop,\n on: () => noop,\n off: noop,\n };\n}\n\n/**\n * Main widget launcher — orchestrates all UI components.\n *\n * Architecture:\n * - Creates a <siteping-widget> custom element in the document\n * - Attaches a closed Shadow DOM for CSS isolation\n * - FAB + Panel live inside the Shadow DOM\n * - Overlay, markers, tooltips live outside (appended to document.body)\n */\nexport function launch(config: SitepingConfig): SitepingInstance {\n // Debug helper — only logs when config.debug is true\n const log: (...args: unknown[]) => void = config.debug\n ? (...args: unknown[]) => console.debug(\"[siteping]\", ...args)\n : () => {};\n\n // Guard: prevent duplicate initSiteping() calls\n if (instance) {\n log(\"initSiteping() called more than once — returning existing instance\");\n return instance;\n }\n\n // Guard: only show in development (forceShow bypasses)\n if (!config.forceShow) {\n try {\n // Check for Node/bundler production environment — avoid import.meta\n // which causes \"Critical dependency\" warnings in Next.js webpack builds\n if (typeof process !== \"undefined\" && process.env?.NODE_ENV === \"production\") {\n const reason = \"production\";\n console.info(\"[siteping] Widget not loaded: production mode detected. Use forceShow: true to override.\");\n config.onSkip?.(reason);\n return skippedInstance();\n }\n } catch {\n // Silently ignore — browser or restricted environment\n }\n }\n\n // Guard: desktop only (< MOBILE_BREAKPOINT = hidden)\n if (window.innerWidth < MOBILE_BREAKPOINT) {\n const reason = \"mobile\";\n console.info(`[siteping] Widget not loaded: viewport width < ${MOBILE_BREAKPOINT}px (mobile not supported).`);\n config.onSkip?.(reason);\n return skippedInstance();\n }\n\n // Guard: validate required config fields\n if (!config.store && (!config.endpoint || typeof config.endpoint !== \"string\")) {\n console.error(\n \"[siteping] Missing 'endpoint' or 'store' in config. Provide an endpoint like '/api/siteping' or a SitepingStore instance.\",\n );\n return skippedInstance();\n }\n if (!config.projectName || typeof config.projectName !== \"string\") {\n console.error(\"[siteping] Missing or invalid 'projectName' in config. Expected a non-empty string.\");\n return skippedInstance();\n }\n\n const locale = config.locale ?? \"en\";\n const t = createT(locale);\n\n log(\"Initializing widget\", { projectName: config.projectName, theme: config.theme ?? \"light\", locale });\n\n const colors = buildThemeColors(config.accentColor, config.theme);\n const bus = new EventBus<WidgetEvents>();\n const publicBus = new EventBus<PublicWidgetEvents>();\n\n // Client-side mode (store) vs HTTP mode (endpoint)\n const client: WidgetClient = config.store\n ? new StoreClient(config.store, config.projectName)\n : new ApiClient(config.endpoint as string, config.projectName);\n\n // Wire config callbacks to event bus\n if (config.onOpen) bus.on(\"open\", config.onOpen);\n if (config.onClose) bus.on(\"close\", config.onClose);\n if (config.onFeedbackSent) bus.on(\"feedback:sent\", config.onFeedbackSent);\n if (config.onError) bus.on(\"feedback:error\", config.onError);\n if (config.onAnnotationStart) bus.on(\"annotation:start\", config.onAnnotationStart);\n if (config.onAnnotationEnd) bus.on(\"annotation:end\", config.onAnnotationEnd);\n\n // Bridge internal events to public bus\n bus.on(\"feedback:sent\", (fb) => publicBus.emit(\"feedback:sent\", fb));\n bus.on(\"feedback:deleted\", (id) => publicBus.emit(\"feedback:deleted\", id));\n bus.on(\"open\", () => publicBus.emit(\"panel:open\"));\n bus.on(\"close\", () => publicBus.emit(\"panel:close\"));\n\n // Debug logging for key lifecycle events\n bus.on(\"open\", () => log(\"Panel opened\"));\n bus.on(\"close\", () => log(\"Panel closed\"));\n bus.on(\"feedback:sent\", (fb) => log(\"Feedback sent\", fb.id));\n bus.on(\"feedback:error\", (err) => log(\"Feedback failed\", err.message));\n bus.on(\"annotation:start\", () => log(\"Annotation started\"));\n bus.on(\"annotation:end\", () => log(\"Annotation ended\"));\n\n // Create host element + Shadow DOM\n const host = document.createElement(\"siteping-widget\");\n host.style.cssText = `position:fixed;z-index:${Z_INDEX_MAX};`;\n // Use open mode only for testing — closed in production for CSS isolation.\n // Shadow DOM mode is determined by environment, never by public config.\n let isTestEnv = false;\n try {\n // Dynamic key prevents bundlers (tsup/esbuild) from statically replacing\n // process.env.NODE_ENV at build time — the widget needs runtime detection\n // so E2E tests can set globalThis.process = { env: { NODE_ENV: 'test' } }\n const envKey = \"NODE_\" + \"ENV\";\n if (typeof process !== \"undefined\" && process.env?.[envKey] === \"test\") {\n isTestEnv = true;\n }\n } catch {\n // Silently ignore — browser or restricted environment\n }\n const shadowMode = isTestEnv ? (\"open\" as const) : (\"closed\" as const);\n const shadow = host.attachShadow({ mode: shadowMode });\n\n // Inject styles into Shadow DOM — adoptedStyleSheets with fallback for Safari < 16.4\n const supportsAdoptedStyleSheets = \"adoptedStyleSheets\" in ShadowRoot.prototype;\n if (supportsAdoptedStyleSheets) {\n const sheet = new CSSStyleSheet();\n sheet.replaceSync(buildStyles(colors));\n shadow.adoptedStyleSheets = [sheet];\n } else {\n const style = document.createElement(\"style\");\n style.textContent = buildStyles(colors);\n (shadow as unknown as DocumentFragment).appendChild(style);\n }\n\n document.body.appendChild(host);\n\n // Screen reader live region for feedback submission announcements\n const liveRegion = document.createElement(\"div\");\n liveRegion.setAttribute(\"role\", \"status\");\n liveRegion.setAttribute(\"aria-live\", \"polite\");\n liveRegion.setAttribute(\"aria-atomic\", \"true\");\n liveRegion.style.cssText =\n \"position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;\";\n document.body.appendChild(liveRegion);\n\n // Components outside Shadow DOM\n const tooltip = new Tooltip(colors, locale);\n const markers = new MarkerManager(colors, tooltip, bus, t);\n\n // Components inside Shadow DOM\n const fab = new Fab(shadow, config, bus, t);\n const panel = new Panel(shadow, colors, bus, client, config.projectName, markers, t, locale);\n const annotator = new Annotator(colors, bus, t);\n\n // Handle annotation completion via event bus (not DOM events)\n // Concurrency guard: prevent duplicate submissions if user draws two annotations quickly\n let submitting = false;\n const unsubAnnotation = bus.on(\"annotation:complete\", async (data) => {\n if (submitting) return;\n submitting = true;\n try {\n const { annotation, type, message } = data;\n\n // Ensure identity\n let identity = getIdentity();\n if (!identity) {\n identity = await promptIdentity(shadow, t);\n if (!identity) return; // User cancelled\n saveIdentity(identity);\n }\n\n // Sanitize URL — strip sensitive query params before sending\n const rawUrl = new URL(window.location.href);\n for (const key of [...rawUrl.searchParams.keys()]) {\n if (/token|key|secret|auth|session|password|code/i.test(key)) {\n rawUrl.searchParams.delete(key);\n }\n }\n const sanitizedUrl = rawUrl.toString();\n\n // crypto.randomUUID() throws in non-secure contexts (plain HTTP)\n const clientId = (() => {\n try {\n return crypto.randomUUID();\n } catch {\n return `${Date.now()}-${Math.random().toString(36).slice(2)}`;\n }\n })();\n\n const payload: FeedbackPayload = {\n projectName: config.projectName,\n type,\n message,\n url: sanitizedUrl,\n viewport: `${window.innerWidth}x${window.innerHeight}`,\n userAgent: navigator.userAgent,\n authorName: identity.name,\n authorEmail: identity.email,\n annotations: [annotation],\n clientId,\n };\n\n try {\n const response = await client.sendFeedback(payload);\n bus.emit(\"feedback:sent\", response);\n markers.addFeedback(response, markers.count + 1);\n liveRegion.textContent = t(\"feedback.sent.confirmation\");\n await panel.refresh();\n } catch (error) {\n bus.emit(\"feedback:error\", error instanceof Error ? error : new Error(String(error)));\n liveRegion.textContent = t(\"feedback.error.message\");\n }\n } finally {\n submitting = false;\n }\n });\n\n // Load markers immediately on page load\n client\n .getFeedbacks(config.projectName, { limit: PAGE_SIZE })\n .then(({ feedbacks }) => {\n markers.render(feedbacks);\n })\n .catch((err) => {\n log(\"Failed to load initial markers:\", err);\n });\n\n // Flush retry queue on load (HTTP mode only — store mode has no retry queue)\n if (config.endpoint) {\n flushRetryQueue(config.endpoint)\n .then(() => log(\"Retry queue flushed\"))\n .catch(() => {});\n }\n\n instance = {\n destroy: () => {\n log(\"Destroying widget\");\n unsubAnnotation();\n fab.destroy();\n panel.destroy();\n annotator.destroy();\n markers.destroy();\n tooltip.destroy();\n bus.removeAll();\n publicBus.removeAll();\n liveRegion.remove();\n host.remove();\n instance = null;\n },\n open: () => {\n panel.open();\n },\n close: () => {\n panel.close();\n },\n refresh: () => {\n panel.refresh();\n },\n on: <K extends keyof SitepingPublicEvents>(event: K, listener: (...args: SitepingPublicEvents[K]) => void) => {\n // Safe cast: SitepingPublicEvents and PublicWidgetEvents have identical keys and value types\n type TargetKey = K & keyof PublicWidgetEvents;\n return publicBus.on(event as TargetKey, listener as unknown as (...args: PublicWidgetEvents[TargetKey]) => void);\n },\n off: <K extends keyof SitepingPublicEvents>(event: K, listener: (...args: SitepingPublicEvents[K]) => void) => {\n // Safe cast: SitepingPublicEvents and PublicWidgetEvents have identical keys and value types\n type TargetKey = K & keyof PublicWidgetEvents;\n publicBus.off(event as TargetKey, listener as unknown as (...args: PublicWidgetEvents[TargetKey]) => void);\n },\n };\n\n return instance;\n}\n\n/**\n * Show a modal identity form inside the Shadow DOM.\n * Glassmorphism: frosted backdrop, glass modal, gradient CTA.\n * Returns null if the user cancels.\n */\nfunction promptIdentity(shadowRoot: ShadowRoot, t: TFunction): Promise<Identity | null> {\n return new Promise((resolve) => {\n // Save the currently focused element to restore on close\n const previouslyFocused = (shadowRoot.activeElement ?? document.activeElement) as HTMLElement | null;\n\n const backdrop = document.createElement(\"div\");\n backdrop.style.cssText = `\n position:fixed;inset:0;\n background:var(--sp-identity-overlay);\n backdrop-filter:blur(8px);\n -webkit-backdrop-filter:blur(8px);\n display:flex;align-items:center;justify-content:center;\n z-index:${Z_INDEX_MAX};\n opacity:0;transition:opacity 0.25s ease;\n `;\n\n const modal = document.createElement(\"div\");\n modal.style.cssText = `\n width:340px;padding:28px;border-radius:var(--sp-radius-xl);\n background:var(--sp-identity-bg);\n backdrop-filter:blur(var(--sp-blur-heavy));\n -webkit-backdrop-filter:blur(var(--sp-blur-heavy));\n border:1px solid var(--sp-glass-border);\n box-shadow:0 16px 48px var(--sp-shadow), 0 8px 16px var(--sp-shadow);\n font-family:var(--sp-font, \"Inter\",system-ui,-apple-system,sans-serif);\n color:var(--sp-text);\n transform:translateY(12px) scale(0.97);\n transition:transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);\n -webkit-font-smoothing:antialiased;\n `;\n\n const titleId = `sp-identity-title-${Date.now()}`;\n modal.setAttribute(\"role\", \"dialog\");\n modal.setAttribute(\"aria-modal\", \"true\");\n modal.setAttribute(\"aria-labelledby\", titleId);\n\n const title = document.createElement(\"div\");\n title.className = \"sp-identity-title\";\n title.id = titleId;\n title.textContent = t(\"identity.title\");\n title.style.marginBottom = \"20px\";\n\n const nameInputId = `sp-identity-name-${Date.now()}`;\n const emailInputId = `sp-identity-email-${Date.now()}`;\n\n const nameLabel = document.createElement(\"label\");\n nameLabel.className = \"sp-input-label\";\n nameLabel.textContent = t(\"identity.nameLabel\");\n nameLabel.setAttribute(\"for\", nameInputId);\n const nameInput = document.createElement(\"input\");\n nameInput.className = \"sp-input\";\n nameInput.id = nameInputId;\n nameInput.type = \"text\";\n nameInput.placeholder = t(\"identity.namePlaceholder\");\n nameInput.style.marginBottom = \"14px\";\n\n const emailLabel = document.createElement(\"label\");\n emailLabel.className = \"sp-input-label\";\n emailLabel.textContent = t(\"identity.emailLabel\");\n emailLabel.setAttribute(\"for\", emailInputId);\n const emailInput = document.createElement(\"input\");\n emailInput.className = \"sp-input\";\n emailInput.id = emailInputId;\n emailInput.type = \"email\";\n emailInput.placeholder = t(\"identity.emailPlaceholder\");\n\n const btnRow = document.createElement(\"div\");\n btnRow.style.cssText = \"display:flex;gap:8px;justify-content:flex-end;margin-top:20px;\";\n\n const closeModal = (result: Identity | null) => {\n backdrop.removeEventListener(\"keydown\", onKeydown);\n backdrop.style.opacity = \"0\";\n modal.style.transform = \"translateY(12px) scale(0.97)\";\n setTimeout(() => {\n backdrop.remove();\n previouslyFocused?.focus();\n resolve(result);\n }, 250);\n };\n\n const cancelBtn = document.createElement(\"button\");\n cancelBtn.className = \"sp-btn-ghost\";\n cancelBtn.textContent = t(\"identity.cancel\");\n cancelBtn.addEventListener(\"click\", () => closeModal(null));\n\n const submitBtn = document.createElement(\"button\");\n submitBtn.className = \"sp-btn-primary\";\n submitBtn.textContent = t(\"identity.submit\");\n submitBtn.addEventListener(\"click\", () => {\n const name = nameInput.value.trim();\n const email = emailInput.value.trim();\n if (!name || !email) return;\n const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n if (!emailRegex.test(email)) {\n emailInput.style.borderColor = \"var(--sp-type-bug, #ef4444)\";\n return;\n }\n closeModal({ name, email });\n });\n\n // Focus trap: cycle Tab/Shift+Tab within the modal\n const focusableSelectors = 'input, button, [tabindex]:not([tabindex=\"-1\"])';\n const onKeydown = (e: Event) => {\n const ke = e as KeyboardEvent;\n if (ke.key === \"Escape\") {\n closeModal(null);\n return;\n }\n if (ke.key === \"Tab\") {\n const focusableEls = Array.from(modal.querySelectorAll<HTMLElement>(focusableSelectors));\n if (focusableEls.length === 0) return;\n const first = focusableEls[0];\n const last = focusableEls[focusableEls.length - 1];\n if (!first || !last) return;\n const active = shadowRoot.activeElement as HTMLElement | null;\n if (ke.shiftKey) {\n if (active === first || !modal.contains(active)) {\n ke.preventDefault();\n last.focus();\n }\n } else {\n if (active === last || !modal.contains(active)) {\n ke.preventDefault();\n first.focus();\n }\n }\n }\n };\n backdrop.addEventListener(\"keydown\", onKeydown);\n\n // Close on backdrop click\n backdrop.addEventListener(\"click\", (e) => {\n if (e.target === backdrop) closeModal(null);\n });\n\n btnRow.appendChild(cancelBtn);\n btnRow.appendChild(submitBtn);\n\n modal.appendChild(title);\n modal.appendChild(nameLabel);\n modal.appendChild(nameInput);\n modal.appendChild(emailLabel);\n modal.appendChild(emailInput);\n modal.appendChild(btnRow);\n backdrop.appendChild(modal);\n\n shadowRoot.appendChild(backdrop);\n\n // Animate in\n requestAnimationFrame(() => {\n backdrop.style.opacity = \"1\";\n modal.style.transform = \"translateY(0) scale(1)\";\n nameInput.focus();\n });\n });\n}\n"],"mappings":"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AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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AA;AAAA,0BAGFA,EAAO,YAAY;AAAA,+BACdA,EAAO,KAAO,UAAY,wBAA0B,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBAw1BtF,UAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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","names":["index_exports","__export","initSiteping","config","rootDocument","start","finder","input","options","defaults","name","value","findRootDocument","path","bottomUpSearch","optimized","sort","optimize","selector","rootNode","limit","fallback","stack","current","i","elapsedTime","level","maybe","id","attr","classNames","tagName","any","nth","index","dispensableNth","node","nthChild","findUniquePath","paths","combinations","candidate","unique","query","penalty","acc","css","elementId","parent","child","list","notEmpty","a","b","scope","newPath","newPathKey","same","STABLE_ATTRS","djb2","str","hash","i","generateFingerprint","element","childCount","siblingIdx","parent","child","attrs","attr","val","attrHash","scoreFingerprint","candidate","storedFingerprint","parts","storedChildren","storedSibIdx","storedAttrHash","storedChildCount","storedSibIndex","candidateFp","candChildren","candSibIdx","candAttrHash","score","childDiff","sibDiff","adjacentText","element","direction","prop","sibling","attempts","text","neighborText","prev","next","generateXPath","element","safeId","segments","current","tag","parent","position","sibling","generateAnchor","element","cssSelector","finder","name","xpath","generateXPath","textSnippet","textPrefix","adjacentText","textSuffix","fingerprint","generateFingerprint","neighbor","neighborText","findAnchorElement","rect","root","centerX","centerY","elementAtCenter","current","bounds","rectToPercentages","anchorBounds","parseSvg","svgString","svg","attr","el","tag","attrs","element","key","value","setText","text","formatRelativeDate","isoString","locale","diff","seconds","rtf","minutes","hours","days","ICON_SITEPING","ICON_CHAT","ICON_ANNOTATE","ICON_EYE","ICON_EYE_OFF","ICON_CLOSE","ICON_SEARCH","ICON_CHECK","ICON_QUESTION","ICON_CHANGE","ICON_BUG","ICON_OTHER","ICON_LAYERS","ICON_DOT_OPEN","ICON_CHEVRON_DOWN","ICON_UNDO","ICON_TRASH","DEFAULT_ACCENT","HEX6_RE","HEX3_RE","HEX8_RE","normalizeHex","raw","short","darkenHex","hex","amount","r","g","b","prefersDark","resolveTheme","theme","buildThemeColors","accent","dark","getTypeColor","type","colors","getTypeBgColor","cssVariables","Popup","colors","el","typeOptions","ICON_QUESTION","ICON_CHANGE","ICON_BUG","ICON_OTHER","typeRow","option","btn","icon","parseSvg","labelSpan","setText","bgColor","getTypeBgColor","getTypeColor","hint","uaData","isMac","e","btnRow","cancelBtn","rectBounds","resolve","top","left","focusableEls","first","last","reduceMotion","type","container","buttons","isActive","color","enabled","Annotator","colors","bus","t","Popup","el","dot","style","instruction","setText","cancelBtn","target","bounds","rectBounds","result","annotation","generateAnchor","touch","clientX","clientY","source","evt","x","y","w","h","anchorElement","findAnchorElement","anchor","anchorBounds","rect","rectToPercentages","RETRY_QUEUE_KEY","resilientFetch","url","init","retries","attempt","controller","timeout","response","error","baseDelay","jitter","r","LOCK_NAME","withRetryLock","callback","queueForRetry","endpoint","payload","raw","RETRY_QUEUE_KEY","parsed","queue","flushRetryQueue","toRetry","e","failed","entry","remaining","ApiClient","projectName","text","options","params","id","resolved","EventBus","event","listener","set","args","fn","err","ITEM_GAP","Fab","shadowRoot","config","bus","t","position","isRight","ICON_CHAT","ICON_ANNOTATE","ICON_EYE","ICON_EYE_OFF","parseSvg","ICON_SITEPING","i","item","btn","e","label","host","handleEscape","items","activeEl","currentIndex","nextIndex","count","displayText","setText","ICON_CLOSE","y","svgStr","badge","id","de","en","es","fr","it","pt","ru","LOCALES","de","en","es","fr","it","pt","ru","createT","locale","lang","LOCALES","dict","key","getTypeLabel","type","t","STORAGE_KEY","getIdentity","raw","parsed","identity","saveIdentity","editDistance","a","b","t","aLen","bLen","prev","k","curr","j","i","prevDiag","tmp","similarity","maxLen","fuzzyIncludes","haystack","needle","minScore","nLen","score","best","capped","limit","window","MAX_SCAN_CANDIDATES","TEXT_MATCH_THRESHOLD","textMatches","el","anchor","text","fuzzyIncludes","resolveAnchor","smartScan","tag","candidates","bestElement","bestScore","limit","i","score","scoreCandidate","candidate","totalWeight","candidateText","scoreFingerprint","contextScore","contextParts","prevText","adjacentText","similarity","nextText","candidateNeighbor","neighborText","resolveAnnotation","rect","resolution","bounds","absoluteRect","toAnchorData","a","toRectData","MARKER_OFFSET","markerPosition","rect","clusterMarker","cluster","i","entry","elIdx","HIGHLIGHT_FADE","REPOSITION_DEBOUNCE","LOW_CONFIDENCE_THRESHOLD","CLUSTER_DISTANCE","FAN_SPACING","MarkerManager","colors","tooltip","bus","t","el","visible","mutations","hasRelevantMutation","m","e","validKeys","markerEl","annotation","cacheKey","cachedEl","resolved","anchorRect","r","resolveAnnotation","pos","key","feedbacks","feedback","index","marker","badge","allItems","used","itemI","j","itemJ","b","first","baseTop","baseLeft","isSolo","count","totalWidth","startLeft","topMarker","setText","confidence","isResolved","number","typeColor","getTypeColor","truncatedMessage","ariaLabel","getTypeLabel","activateMarker","feedbackId","highlight","h","ICON_EXPORT","ICON_CSV","ICON_JSON","EXPORT_I18N_EN","EXPORT_I18N_FR","EXPORT_CSS","CSV_COLUMNS","escapeCsvField","value","feedbacksToCsv","feedbacks","header","rows","fb","col","raw","feedbacksToJson","downloadFile","content","filename","mimeType","blob","url","anchor","ExportButton","_colors","getFeedbacks","el","btn","parseSvg","label","setText","e","csvOption","jsonOption","labels","span","options","iconSvg","labelText","onClick","option","iconWrap","labelEl","format","projectName","date","safeName","ICON_CHECKBOX","ICON_CHECKBOX_CHECKED","BULK_I18N_EN","BULK_I18N_FR","BULK_CSS","BulkActions","_colors","callbacks","locale","el","setText","actions","deselectBtn","parseSvg","feedbackId","wrapper","e","feedbackIds","label","checkbox","labelEl","container","id","prevSelected","count","visible","resolveLabel","deleteLabel","isChecked","allSelected","escapedId","card","btn","snapshot","n","ids","restoreResolve","restoreDelete","ICON_ARROW_LEFT","ICON_MAP_PIN","ICON_LINK","ICON_USER","ICON_CALENDAR","ICON_MONITOR","ICON_CHECK","ICON_UNDO","ICON_TRASH","ICON_CODE","ICON_CROSSHAIR","DETAIL_I18N_EN","DETAIL_I18N_FR","DETAIL_CSS","parseBrowser","ua","m","formatFullDate","isoString","locale","extractPathname","url","truncate","str","max","DetailView","colors","callbacks","el","header","backBtn","parseSvg","feedback","number","title","setText","badge","getTypeBgColor","getTypeColor","sectionIndex","statusSection","messageSection","messageSectionTitle","messageBlock","metaSection","metaSectionTitle","annSection","annSectionTitle","annTitleText","index","section","container","isResolved","sectionTitle","statusRow","pill","dot","pillLabel","actions","span","deleteSpan","meta","value","pathname","name","email","resolvedDate","iconSvg","label","buildValue","row","content","labelEl","ann","wrapper","info","tagDisplay","gotoBtn","gotoLabel","btn","ICON_SORT","ICON_PAGE","ICON_CHEVRON","SORT_I18N_EN","SORT_I18N_FR","TYPE_ORDER","sortFeedbacks","feedbacks","mode","sorted","a","b","typeA","typeB","statusA","statusB","extractPathname","url","truncatePath","path","maxLength","ellipsis","keep","groupFeedbacksByPage","groups","fb","existing","createPageGroupHeader","pagePath","count","colors","header","el","chevronWrap","parseSvg","pageIcon","pathEl","displayPath","setText","countEl","toggle","isExpanded","content","e","PanelSortControls","onChange","locale","sortIcon","sortLabel","groupIcon","groupLabel","options","opt","item","labelMap","label","SORT_CSS","STATS_I18N_EN","STATS_I18N_FR","STATS_CSS","PanelStats","colors","locale","el","row","itemOpen","dotOpen","setText","labelOpen","itemResolved","dotResolved","labelResolved","itemBugs","dotBugs","labelBugs","progress","track","feedbacks","total","openCount","resolvedCount","bugCount","fb","visible","pct","progressText","ICON_KEYBOARD","SHORTCUTS_I18N_EN","SHORTCUTS_I18N_FR","getFocusedCardIndex","listContainer","cards","i","focusCardByIndex","index","card","clamped","target","SHORTCUT_DEFS","SHORTCUTS_CSS","ICON_CLOSE_SM","KeyboardShortcuts","_colors","callbacks","i18n","e","root","active","tag","handler","overlay","el","title","parseSvg","titleText","setText","closeBtn","grid","def","row","keysWrap","key","sep","kbd","desc","btn","Panel","shadowRoot","colors","bus","client","projectName","markers","t","locale","BULK_I18N_FR","BULK_I18N_EN","el","header","title","setText","parseSvg","ICON_CLOSE","ICON_TRASH","deleteAllLabel","ExportButton","EXPORT_I18N_FR","headerRight","PanelStats","filters","searchWrap","searchIcon","ICON_SEARCH","filterBar","PanelSortControls","BulkActions","ids","DetailView","fb","newResolved","ann","shortcutsI18n","SHORTCUTS_I18N_FR","SHORTCUTS_I18N_EN","KeyboardShortcuts","dir","idx","getFocusedCardIndex","focusCardByIndex","btn","e","target","actionEl","card","feedbackId","feedback","f","action","message","isExpanded","number","ke","open","focusable","first","last","active","loading","spinner","empty","text","retryBtn","signal","search","typeFilter","statusFilter","options","hasContent","feedbacks","total","error","controller","nextPage","loadMoreBtn","restoreBtn","emptyText","sorted","sortFeedbacks","feedbackIds","selectAllBar","groups","groupFeedbacksByPage","globalIndex","pagePath","groupFeedbacks","groupHeader","createPageGroupHeader","groupContent","index","remaining","loadMoreWrap","isResolved","typeColor","getTypeColor","getTypeLabel","bar","body","checkbox","num","badge","typeBg","getTypeBgColor","date","formatRelativeDate","expandBtn","footer","resolveBtn","ICON_UNDO","span","ICON_CHECK","deleteBtn","deleteBtnLabel","id","resolve","backdrop","titleId","messageId","dialog","titleEl","messageEl","btnRow","cancelBtn","confirmBtn","closed","close","result","onKeydown","snapshot","n","restore","ICON_LAYERS","ICON_QUESTION","ICON_CHANGE","ICON_BUG","ICON_OTHER","o","iconWrap","labelWrap","prefix","value","chevron","ICON_CHEVRON_DOWN","option","item","isActive","labelEl","checkWrap","ICON_DOT_OPEN","current","values","nextIdx","next","buttons","escapedId","flattenAnnotation","ann","StoreClient","store","projectName","payload","record","flattenAnnotation","toResponse","options","feedbacks","total","id","resolved","toAnnotationResponse","ann","SPRING_LINEAR","EASE_OUT_EXPO","SPRING_OVERSHOOT","EASE_OUT_QUART","ANIMATION_CSS","buildStyles","colors","cssVariables","ANIMATION_CSS","STATS_CSS","SORT_CSS","BULK_CSS","EXPORT_CSS","SHORTCUTS_CSS","DETAIL_CSS","SHOW_DELAY","HIDE_DELAY","Tooltip","colors","locale","el","feedback","anchorRect","reduceMotion","children","child","typeColor","getTypeColor","typeBg","getTypeBgColor","t","createT","typeLabel","getTypeLabel","header","badge","setText","date","formatRelativeDate","body","tooltipRect","gap","top","left","isAbove","arrowLeft","node","instance","skippedInstance","noop","launch","config","log","args","reason","locale","t","createT","colors","buildThemeColors","bus","EventBus","publicBus","client","StoreClient","ApiClient","fb","id","err","host","isTestEnv","shadowMode","shadow","sheet","buildStyles","style","liveRegion","tooltip","Tooltip","markers","MarkerManager","fab","Fab","panel","Panel","annotator","Annotator","submitting","unsubAnnotation","data","annotation","type","message","identity","getIdentity","promptIdentity","saveIdentity","rawUrl","key","sanitizedUrl","clientId","payload","response","error","feedbacks","flushRetryQueue","event","listener","shadowRoot","resolve","previouslyFocused","backdrop","modal","titleId","title","nameInputId","emailInputId","nameLabel","nameInput","emailLabel","emailInput","btnRow","closeModal","result","onKeydown","cancelBtn","submitBtn","name","email","focusableSelectors","e","ke","focusableEls","first","last","active","initSiteping","config","launch"]}
|