@qaiddev/thumbs-embed 1.0.0 → 1.0.2
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 +393 -0
- package/dist/qaid.js +2 -2
- package/dist/qaid.js.map +1 -1
- package/dist/qaid.umd.cjs +1 -1
- package/dist/qaid.umd.cjs.map +1 -1
- package/package.json +1 -1
package/dist/qaid.umd.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"qaid.umd.cjs","sources":["../src/icons.ts","../src/styles.ts","../src/element-selector.ts","../src/modal-positioning.ts","../src/console-capture.ts","../src/dom-utils.ts","../src/screenshot.ts","../src/embed.ts","../src/index.ts"],"sourcesContent":["/**\n * SVG icon strings for thumbs up/down buttons\n */\n\nexport const THUMBS_UP_ICON = `<svg xmlns=\"http://www.w3.org/2000/svg\" class=\"qaid-icon\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M14 10h4.764a2 2 0 011.789 2.894l-3.5 7A2 2 0 0115.263 21h-4.017c-.163 0-.326-.02-.485-.06L7 20m7-10V5a2 2 0 00-2-2h-.095c-.5 0-.905.405-.905.905 0 .714-.211 1.412-.608 2.006L7 11v9m7-10h-2M7 20H5a2 2 0 01-2-2v-6a2 2 0 012-2h2.5\"/>\n</svg>`;\n\nexport const THUMBS_DOWN_ICON = `<svg xmlns=\"http://www.w3.org/2000/svg\" class=\"qaid-icon\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M10 14H5.236a2 2 0 01-1.789-2.894l3.5-7A2 2 0 018.737 3h4.018a2 2 0 01.485.06l3.76.94m-7 10v5a2 2 0 002 2h.095c.5 0 .905-.405.905-.905 0-.714.211-1.412.608-2.006L17 13V4m-7 10h2m5-10h2a2 2 0 012 2v6a2 2 0 01-2 2h-2.5\"/>\n</svg>`;\n","/**\n * CSS styles for the feedback embed\n * Uses .qaid- prefix to avoid conflicts\n */\n\n// Import CSS as minified string (vite handles minification in production)\nimport baseStyles from \"./styles.css?inline\";\n\nlet stylesInjected = false;\n\nexport interface InjectStylesOptions {\n positiveColor?: string;\n negativeColor?: string;\n /** Color for selected element marker */\n markerColor?: string;\n /** When true, skip default button visual styles (user provides via buttonClass) */\n skipButtonStyles?: boolean;\n /** Button size: small (36px), medium (48px), large (64px) */\n buttonSize?: \"small\" | \"medium\" | \"large\";\n /** Modal width in pixels */\n modalWidth?: number;\n /** Backdrop opacity (0-1) */\n backdropOpacity?: number;\n /** Font family */\n fontFamily?: string;\n /** Base font size in pixels */\n fontSize?: number;\n}\n\nconst BUTTON_SIZES = {\n small: 36,\n medium: 48,\n large: 64,\n} as const;\n\nconst ICON_SIZES = {\n small: 18,\n medium: 24,\n large: 32,\n} as const;\n\n/**\n * Calculate relative luminance of a color\n * Returns value between 0 (black) and 1 (white)\n */\nfunction getLuminance(r: number, g: number, b: number): number {\n const [rs, gs, bs] = [r, g, b].map((c) => {\n c = c / 255;\n return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);\n });\n return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;\n}\n\n/**\n * Parse a color string (hex or rgb) and return RGB values\n */\nfunction parseColor(color: string): { r: number; g: number; b: number } | null {\n // Hex format\n if (color.startsWith(\"#\")) {\n const hex = color.slice(1);\n const fullHex = hex.length === 3\n ? hex.split(\"\").map((c) => c + c).join(\"\")\n : hex;\n const num = parseInt(fullHex, 16);\n return {\n r: (num >> 16) & 255,\n g: (num >> 8) & 255,\n b: num & 255,\n };\n }\n // RGB format\n const match = color.match(/rgb\\((\\d+),\\s*(\\d+),\\s*(\\d+)\\)/);\n if (match) {\n return {\n r: parseInt(match[1]),\n g: parseInt(match[2]),\n b: parseInt(match[3]),\n };\n }\n return null;\n}\n\n/**\n * Get the best contrasting text color (black or white) for a background\n */\nfunction getContrastTextColor(bgColor: string): string {\n const rgb = parseColor(bgColor);\n if (!rgb) return \"white\";\n const luminance = getLuminance(rgb.r, rgb.g, rgb.b);\n // Use white text on dark backgrounds, black on light\n return luminance > 0.4 ? \"black\" : \"white\";\n}\n\nexport function injectStyles(options: InjectStylesOptions = {}): void {\n if (stylesInjected) return;\n stylesInjected = true;\n\n const {\n positiveColor = \"rgb(0, 200, 83)\",\n negativeColor = \"rgb(255, 0, 0)\",\n markerColor = \"#6366f1\",\n // skipButtonStyles kept for backwards compatibility but no longer used\n buttonSize = \"medium\",\n modalWidth = 400,\n backdropOpacity = 0.3,\n fontFamily = \"system-ui, -apple-system, sans-serif\",\n fontSize = 16,\n } = options;\n\n const btnSize = BUTTON_SIZES[buttonSize];\n const iconSize = ICON_SIZES[buttonSize];\n\n // Calculate contrast text color for marker (used on submit button)\n const markerTextColor = getContrastTextColor(markerColor);\n\n // CSS variables for colors and sizes\n const cssVars = `:root{--qaid-positive:${positiveColor};--qaid-negative:${negativeColor};--qaid-marker:${markerColor};--qaid-marker-text:${markerTextColor};--qaid-btn-size:${btnSize}px;--qaid-icon-size:${iconSize}px;--qaid-modal-width:${modalWidth}px;--qaid-backdrop-opacity:${backdropOpacity};--qaid-font-family:${fontFamily};--qaid-font-size:${fontSize}px}`;\n\n // Default button styles (always included - themed embeds use custom classes so these won't apply)\n const buttonStyles = `.qaid-btn{width:var(--qaid-btn-size);height:var(--qaid-btn-size);border-radius:50%;border:none;background:#f3f4f6;color:#374151;display:flex;align-items:center;justify-content:center;cursor:pointer;box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06);transition:background-color .2s,color .2s,transform .2s}.qaid-btn:hover{transform:scale(1.05)}.qaid-btn-up:hover{background:var(--qaid-positive);color:#fff}.qaid-btn-down:hover{background:var(--qaid-negative);color:#fff}.qaid-btn svg{width:var(--qaid-icon-size);height:var(--qaid-icon-size)}`;\n\n const style = document.createElement(\"style\");\n style.id = \"qaid-styles\";\n style.textContent = cssVars + baseStyles + buttonStyles;\n document.head.appendChild(style);\n}\n\nexport function removeStyles(): void {\n const style = document.getElementById(\"qaid-styles\");\n if (style) {\n style.remove();\n stylesInjected = false;\n }\n}\n","/**\n * Element selector generation utilities\n */\n\n/** Data attributes to look for (in priority order) */\nexport const DATA_ATTRS = [\"data-comp\", \"data-qa\", \"data-testid\", \"data-id\"];\n\nexport interface ElementInfo {\n selector: string;\n text: string;\n dataAttr: string | null;\n}\n\n/**\n * Find the first matching data attribute on an element or its ancestors\n */\nexport function findDataAttribute(\n element: Element,\n stopAt: Element = document.body\n): string | null {\n let current: Element | null = element;\n\n while (current && current !== stopAt) {\n for (const attr of DATA_ATTRS) {\n const value = current.getAttribute(attr);\n if (value) {\n return `${attr}=\"${value}\"`;\n }\n }\n current = current.parentElement;\n }\n\n return null;\n}\n\n/**\n * Get truncated text content from an element\n */\nexport function getTruncatedText(element: Element, maxLength = 100): string {\n const textContent = element.textContent?.trim().slice(0, maxLength) || \"\";\n return textContent.length === maxLength ? textContent + \"...\" : textContent;\n}\n\n/**\n * Get the nth-child index of an element among its siblings\n */\nfunction getNthChildIndex(element: Element): number {\n let index = 1;\n let sibling = element.previousElementSibling;\n while (sibling) {\n index++;\n sibling = sibling.previousElementSibling;\n }\n return index;\n}\n\n/**\n * Generate an nth-child path from body to the element\n * This is always a valid CSS selector and uniquely identifies the element\n * Exported for testing\n */\nexport function generateNthChildPath(element: Element): string {\n const path: string[] = [];\n let current: Element | null = element;\n\n while (current && current !== document.body && current !== document.documentElement) {\n const tag = current.tagName.toLowerCase();\n const index = getNthChildIndex(current);\n path.unshift(`${tag}:nth-child(${index})`);\n current = current.parentElement;\n }\n\n return path.length > 0 ? `body > ${path.join(\" > \")}` : \"body\";\n}\n\n/**\n * Escape special CSS characters in a string (for IDs with special chars)\n */\nfunction escapeCSSIdentifier(str: string): string {\n return str.replace(/([!\"#$%&'()*+,./:;<=>?@[\\\\\\]^`{|}~])/g, \"\\\\$1\");\n}\n\n/**\n * Generate a selector string for an element\n * Priority: data attribute > ID > nth-child path\n */\nexport function generateSelector(element: Element): string {\n // Try data attribute first (on element or ancestors) - most stable\n const dataAttr = findDataAttribute(element);\n if (dataAttr) {\n return `[${dataAttr}]`;\n }\n\n // Try ID (escaped for special characters)\n if (element.id) {\n return `#${escapeCSSIdentifier(element.id)}`;\n }\n\n // Fall back to nth-child path - always valid and unique\n return generateNthChildPath(element);\n}\n\n/**\n * Generate complete element info for feedback\n */\nexport function generateElementInfo(element: Element): ElementInfo {\n const text = getTruncatedText(element);\n const dataAttr = findDataAttribute(element);\n const selector = generateSelector(element);\n\n return { selector, text, dataAttr };\n}\n","/**\n * Modal positioning utilities\n */\n\nimport type { SelectedBounds } from \"./types\";\n\nexport interface ModalPosition {\n top: number;\n left: number;\n position: \"above\" | \"below\";\n}\n\nexport interface ArrowPosition {\n left: number;\n}\n\nexport interface ModalDimensions {\n width: number;\n height: number;\n arrowHeight: number;\n gap: number;\n viewportPadding: number;\n}\n\nconst DEFAULT_DIMENSIONS: ModalDimensions = {\n width: 400,\n height: 280,\n arrowHeight: 12,\n gap: 8,\n viewportPadding: 16,\n};\n\n/**\n * Calculate whether modal should go above or below the target element\n */\nexport function calculateVerticalPosition(\n bounds: SelectedBounds,\n viewportHeight: number,\n totalHeight: number,\n gap: number,\n viewportPadding: number\n): { top: number; position: \"above\" | \"below\" } {\n const spaceAbove = bounds.y;\n const spaceBelow = viewportHeight - (bounds.y + bounds.height);\n\n // Prefer below if there's enough space\n if (spaceBelow >= totalHeight + gap) {\n return {\n top: bounds.y + bounds.height + gap,\n position: \"below\",\n };\n }\n\n // Try above if there's enough space\n if (spaceAbove >= totalHeight + gap) {\n return {\n top: bounds.y - totalHeight - gap,\n position: \"above\",\n };\n }\n\n // Neither fits perfectly - clamp to viewport\n const position = spaceBelow > spaceAbove ? \"below\" : \"above\";\n let top: number;\n\n if (position === \"below\") {\n top = Math.min(\n bounds.y + bounds.height + gap,\n viewportHeight - totalHeight - viewportPadding\n );\n } else {\n top = Math.max(viewportPadding, bounds.y - totalHeight - gap);\n }\n\n return { top, position };\n}\n\n/**\n * Calculate horizontal position of modal, clamped to viewport\n */\nexport function calculateHorizontalPosition(\n bounds: SelectedBounds,\n viewportWidth: number,\n modalWidth: number,\n viewportPadding: number\n): number {\n const centerX = bounds.x + bounds.width / 2;\n let left = centerX - modalWidth / 2;\n\n // Clamp to viewport\n left = Math.max(viewportPadding, Math.min(left, viewportWidth - modalWidth - viewportPadding));\n\n return left;\n}\n\n/**\n * Calculate arrow position relative to modal\n * Points at the click position, not the element center\n */\nexport function calculateArrowPosition(\n bounds: SelectedBounds,\n modalLeft: number,\n modalWidth: number,\n minOffset = 24,\n arrowWidth = 24\n): number {\n // Use click position for arrow, not element center\n const targetX = bounds.clickX;\n // Subtract half the arrow width to center it on the target\n const arrowLeft = targetX - modalLeft - arrowWidth / 2;\n\n // Clamp arrow to stay within modal bounds\n return Math.max(minOffset, Math.min(arrowLeft, modalWidth - minOffset - arrowWidth / 2));\n}\n\n/**\n * Calculate complete modal position based on selected element bounds\n */\nexport function calculateModalPosition(\n bounds: SelectedBounds,\n viewportWidth: number,\n viewportHeight: number,\n dimensions: ModalDimensions = DEFAULT_DIMENSIONS\n): ModalPosition {\n const totalHeight = dimensions.height + dimensions.arrowHeight;\n\n const vertical = calculateVerticalPosition(\n bounds,\n viewportHeight,\n totalHeight,\n dimensions.gap,\n dimensions.viewportPadding\n );\n\n const left = calculateHorizontalPosition(\n bounds,\n viewportWidth,\n dimensions.width,\n dimensions.viewportPadding\n );\n\n return {\n top: vertical.top,\n left,\n position: vertical.position,\n };\n}\n\n/**\n * Calculate both modal and arrow positions\n */\nexport function calculateModalAndArrowPosition(\n bounds: SelectedBounds,\n viewportWidth: number,\n viewportHeight: number,\n dimensions: ModalDimensions = DEFAULT_DIMENSIONS\n): { modal: ModalPosition; arrow: ArrowPosition } {\n const modal = calculateModalPosition(bounds, viewportWidth, viewportHeight, dimensions);\n\n const arrowLeft = calculateArrowPosition(\n bounds,\n modal.left,\n dimensions.width\n );\n\n return {\n modal,\n arrow: { left: arrowLeft },\n };\n}\n","/**\n * Console capture utilities\n */\n\nimport type { ConsoleError } from \"./types\";\n\nconst MAX_MESSAGES = 20;\n\nexport interface ConsoleCapture {\n errors: ConsoleError[];\n restore: () => void;\n}\n\ntype ConsoleLevel = \"error\" | \"warn\" | \"log\";\n\n/**\n * Wrap console methods to capture messages\n */\nexport function captureConsoleErrors(\n onError?: (error: ConsoleError) => void\n): ConsoleCapture {\n const errors: ConsoleError[] = [];\n const originalError = console.error;\n const originalWarn = console.warn;\n const originalLog = console.log;\n\n const capture = (level: ConsoleLevel, args: unknown[]) => {\n const error: ConsoleError = {\n message: args.map((arg) => String(arg)).join(\" \"),\n timestamp: Date.now(),\n level,\n };\n\n // Keep max messages\n if (errors.length >= MAX_MESSAGES) {\n errors.shift();\n }\n errors.push(error);\n\n // Call callback if provided\n if (onError) {\n onError(error);\n }\n };\n\n console.error = function (...args: unknown[]) {\n capture(\"error\", args);\n originalError.apply(console, args);\n };\n\n console.warn = function (...args: unknown[]) {\n capture(\"warn\", args);\n originalWarn.apply(console, args);\n };\n\n console.log = function (...args: unknown[]) {\n capture(\"log\", args);\n originalLog.apply(console, args);\n };\n\n return {\n errors,\n restore: () => {\n console.error = originalError;\n console.warn = originalWarn;\n console.log = originalLog;\n },\n };\n}\n\n/**\n * Add an error to the captured errors array\n */\nexport function addError(errors: ConsoleError[], error: ConsoleError): void {\n if (errors.length >= MAX_MESSAGES) {\n errors.shift();\n }\n errors.push(error);\n}\n\n/**\n * Format console error for display\n */\nexport function formatError(error: ConsoleError): string {\n const date = new Date(error.timestamp);\n const time = date.toLocaleTimeString();\n const levelPrefix = error.level === \"error\" ? \"ERR\" : error.level === \"warn\" ? \"WRN\" : \"LOG\";\n return `[${time}] [${levelPrefix}] ${error.message}`;\n}\n","/**\n * DOM utility functions\n */\n\n/**\n * Create an element with attributes and optional children\n */\nexport function createElement<K extends keyof HTMLElementTagNameMap>(\n tag: K,\n attrs?: Record<string, string>,\n children?: (Node | string)[]\n): HTMLElementTagNameMap[K] {\n const el = document.createElement(tag);\n\n if (attrs) {\n for (const [key, value] of Object.entries(attrs)) {\n if (key === \"className\") {\n el.className = value;\n } else {\n el.setAttribute(key, value);\n }\n }\n }\n\n if (children) {\n for (const child of children) {\n if (typeof child === \"string\") {\n el.appendChild(document.createTextNode(child));\n } else {\n el.appendChild(child);\n }\n }\n }\n\n return el;\n}\n\n/**\n * Check if viewport width indicates mobile device\n */\nexport function isMobileViewport(breakpoint = 640): boolean {\n return typeof window !== \"undefined\" && window.innerWidth < breakpoint;\n}\n\n/**\n * Get element at point, temporarily hiding an overlay element\n */\nexport function getElementAtPointUnderOverlay(\n x: number,\n y: number,\n overlay: HTMLElement\n): Element | null {\n const originalPointerEvents = overlay.style.pointerEvents;\n overlay.style.pointerEvents = \"none\";\n const element = document.elementFromPoint(x, y);\n overlay.style.pointerEvents = originalPointerEvents;\n return element;\n}\n\n/**\n * Check if an element is part of the feedback embed\n */\nexport function isEmbedElement(element: Element | null): boolean {\n if (!element) return false;\n return !!(\n element.closest(\".qaid-widget\") ||\n element.closest(\".qaid-targeting-overlay\") ||\n element.closest(\".qaid-modal-container\") ||\n element.closest(\".qaid-bottom-sheet\")\n );\n}\n\n/**\n * Get element bounds with optional padding\n */\nexport function getElementBounds(\n element: Element,\n padding = 0\n): { x: number; y: number; width: number; height: number } {\n const rect = element.getBoundingClientRect();\n return {\n x: rect.left - padding,\n y: rect.top - padding,\n width: rect.width + padding * 2,\n height: rect.height + padding * 2,\n };\n}\n\n/**\n * Remove all elements with a specific class\n */\nexport function removeAllByClass(className: string): void {\n document.querySelectorAll(`.${className}`).forEach((el) => {\n el.classList.remove(className);\n });\n}\n","/**\n * Screenshot capture utility using native Screen Capture API\n */\n\nexport interface ScreenshotOptions {\n /** Quality of WebP compression (0-1). Default: 1.0 */\n quality?: number;\n /** Max width of the screenshot. Default: 1280 */\n maxWidth?: number;\n /** Max height of the screenshot. Default: 800 */\n maxHeight?: number;\n}\n\n/**\n * Captures a screenshot using the native Screen Capture API\n * Requires user permission but produces accurate results\n * Returns a base64-encoded WebP data URL\n */\nexport async function captureScreenshot(\n options: ScreenshotOptions = {}\n): Promise<string | null> {\n const { quality = 1.0, maxWidth = 1280, maxHeight = 800 } = options;\n\n try {\n // Check if API is available\n if (!navigator.mediaDevices?.getDisplayMedia) {\n console.warn('Screen Capture API not available');\n return null;\n }\n\n // Request screen capture - preferCurrentTab auto-selects current tab in supported browsers\n const stream = await navigator.mediaDevices.getDisplayMedia({\n preferCurrentTab: true,\n video: {\n displaySurface: 'browser',\n },\n } as DisplayMediaStreamOptions);\n\n // Get video track\n const track = stream.getVideoTracks()[0];\n const settings = track.getSettings();\n\n // Create video element\n const video = document.createElement('video');\n video.srcObject = stream;\n video.muted = true;\n\n await new Promise<void>((resolve) => {\n video.onloadedmetadata = () => {\n video.play();\n resolve();\n };\n });\n\n // Wait for video to have actual frame data\n await new Promise<void>((resolve) => {\n const checkFrame = () => {\n if (video.readyState >= 2) {\n resolve();\n } else {\n requestAnimationFrame(checkFrame);\n }\n };\n checkFrame();\n });\n\n // Extra delay to ensure frame is rendered\n await new Promise((resolve) => setTimeout(resolve, 100));\n\n // Calculate dimensions\n const srcWidth = settings.width || video.videoWidth;\n const srcHeight = settings.height || video.videoHeight;\n const scale = Math.min(maxWidth / srcWidth, maxHeight / srcHeight, 1);\n const width = Math.round(srcWidth * scale);\n const height = Math.round(srcHeight * scale);\n\n // Draw to canvas\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) {\n track.stop();\n return null;\n }\n\n ctx.drawImage(video, 0, 0, width, height);\n\n // Stop the stream\n track.stop();\n\n return canvas.toDataURL('image/webp', quality);\n } catch (error) {\n console.warn('Screenshot capture failed:', error);\n return null;\n }\n}\n","import type {\n FeedbackConfig,\n ResolvedFeedbackConfig,\n FeedbackData,\n SelectedBounds,\n EmbedState,\n FeedbackPayload,\n FeedbackResponse,\n} from \"./types\";\nimport { THUMBS_UP_ICON, THUMBS_DOWN_ICON } from \"./icons\";\nimport { injectStyles, removeStyles } from \"./styles\";\nimport { generateElementInfo } from \"./element-selector\";\nimport { calculateModalAndArrowPosition } from \"./modal-positioning\";\nimport { captureConsoleErrors, type ConsoleCapture } from \"./console-capture\";\nimport {\n getElementAtPointUnderOverlay,\n isEmbedElement,\n getElementBounds,\n removeAllByClass,\n isMobileViewport,\n} from \"./dom-utils\";\nimport { captureScreenshot } from \"./screenshot\";\n\nconst VISITOR_ID_KEY = \"qaid_visitor_id\";\n\n/**\n * Get or create a visitor ID stored in localStorage\n */\nfunction getOrCreateVisitorId(): string {\n try {\n let visitorId = localStorage.getItem(VISITOR_ID_KEY);\n if (!visitorId) {\n visitorId = crypto.randomUUID();\n localStorage.setItem(VISITOR_ID_KEY, visitorId);\n }\n return visitorId;\n } catch {\n // localStorage not available, generate a session-only ID\n return crypto.randomUUID();\n }\n}\n\n/**\n * FeedbackEmbed - Standalone feedback collection embed\n */\nexport class FeedbackEmbed {\n private config: ResolvedFeedbackConfig;\n private state: EmbedState = \"IDLE\";\n private feedbackData: FeedbackData = {\n feedbackType: null,\n elementSelector: null,\n elementText: null,\n consoleErrors: [],\n };\n private selectedBounds: SelectedBounds = {\n x: 0,\n y: 0,\n width: 0,\n height: 0,\n clickX: 0,\n clickY: 0,\n visible: false,\n };\n private feedbackId: number | null = null;\n private mousePos = { x: 0, y: 0 };\n private lastHighlighted: Element | null = null;\n private isMobile = false;\n private visitorId: string;\n\n // Console capture\n private consoleCapture: ConsoleCapture | null = null;\n\n // Server-side feature flags\n private allowedFeatures: { screenshots: boolean; consoleCapture: boolean } | null = null;\n\n // DOM elements\n private container: HTMLDivElement | null = null;\n private isUserProvidedContainer = false;\n private overlayContainer: HTMLDivElement | null = null;\n private captureLayer: HTMLDivElement | null = null;\n private crosshairH: HTMLDivElement | null = null;\n private crosshairV: HTMLDivElement | null = null;\n private scope: HTMLDivElement | null = null;\n private marker: HTMLDivElement | null = null;\n private modalContainer: HTMLDivElement | null = null;\n private backdrop: HTMLDivElement | null = null;\n\n // Bound event handlers\n private boundKeyDown: (e: KeyboardEvent) => void;\n private boundMouseMove: (e: MouseEvent) => void;\n private boundClick: (e: MouseEvent) => void;\n private boundResize: () => void;\n\n constructor(config: FeedbackConfig) {\n this.config = {\n endpoint: config.endpoint,\n apiKey: config.apiKey ?? \"\",\n container: config.container ?? \"\",\n buttonClass: config.buttonClass ?? \"\",\n position: config.position ?? \"bottom-right\",\n offset: {\n x: config.offset?.x ?? 16,\n y: config.offset?.y ?? 16,\n },\n zIndex: config.zIndex ?? 50,\n skipTargeting: config.skipTargeting ?? false,\n colors: {\n positive: config.colors?.positive ?? \"rgb(0, 200, 83)\",\n negative: config.colors?.negative ?? \"rgb(255, 0, 0)\",\n marker: config.colors?.marker ?? \"#6366f1\",\n },\n buttonSize: config.buttonSize ?? \"medium\",\n text: {\n tooltip: config.text?.tooltip ?? \"\",\n bannerText: config.text?.bannerText ?? \"Click on any element to target it with your feedback\",\n bannerHint: config.text?.bannerHint ?? \"(Press Escape to cancel)\",\n modalTitle: config.text?.modalTitle ?? \"Thank you for your feedback!\",\n modalSubtitle: config.text?.modalSubtitle ?? \"Would you like to add a message to help us understand your feedback better?\",\n placeholder: config.text?.placeholder ?? \"Optional: Tell us more about your experience...\",\n submitButton: config.text?.submitButton ?? \"Submit\",\n skipButton: config.text?.skipButton ?? \"Skip\",\n },\n modalWidth: config.modalWidth ?? 400,\n backdropOpacity: config.backdropOpacity ?? 0.3,\n fontFamily: config.fontFamily ?? \"system-ui, -apple-system, sans-serif\",\n fontSize: config.fontSize ?? 16,\n captureScreenshot: config.captureScreenshot ?? false,\n screenshotOptions: {\n quality: config.screenshotOptions?.quality ?? 0.8,\n maxWidth: config.screenshotOptions?.maxWidth ?? 1280,\n maxHeight: config.screenshotOptions?.maxHeight ?? 800,\n },\n incognito: config.incognito ?? false,\n positiveIcon: config.positiveIcon ?? \"\",\n negativeIcon: config.negativeIcon ?? \"\",\n };\n\n // Bind event handlers\n this.boundKeyDown = this.handleKeyDown.bind(this);\n this.boundMouseMove = this.handleMouseMove.bind(this);\n this.boundClick = this.handleClick.bind(this);\n this.boundResize = this.handleResize.bind(this);\n\n // Get or create visitor ID for anonymous feedback tracking\n this.visitorId = getOrCreateVisitorId();\n\n this.init();\n }\n\n private init(): void {\n // Inject styles\n injectStyles({\n positiveColor: this.config.colors.positive,\n negativeColor: this.config.colors.negative,\n markerColor: this.config.colors.marker,\n skipButtonStyles: !!this.config.buttonClass,\n buttonSize: this.config.buttonSize,\n modalWidth: this.config.modalWidth,\n backdropOpacity: this.config.backdropOpacity,\n fontFamily: this.config.fontFamily,\n fontSize: this.config.fontSize,\n });\n\n // Check mobile\n this.checkMobile();\n window.addEventListener(\"resize\", this.boundResize);\n\n // Create main embed container\n this.createEmbed();\n\n // Fetch allowed features from server\n this.fetchAllowedFeatures();\n\n // Capture console errors\n this.consoleCapture = captureConsoleErrors((error) => {\n this.feedbackData.consoleErrors = this.consoleCapture?.errors ?? [];\n });\n this.feedbackData.consoleErrors = this.consoleCapture.errors;\n }\n\n private async fetchAllowedFeatures(): Promise<void> {\n if (!this.config.apiKey) return;\n\n try {\n // Derive features endpoint from feedback endpoint\n const baseUrl = this.config.endpoint.replace(/\\/feedback\\/?$/, '');\n const featuresUrl = `${baseUrl}/features?apiKey=${encodeURIComponent(this.config.apiKey)}`;\n\n const response = await fetch(featuresUrl);\n if (response.ok) {\n this.allowedFeatures = await response.json();\n }\n } catch {\n // Silently fail - features will be determined server-side anyway\n }\n }\n\n private checkMobile(): void {\n this.isMobile = isMobileViewport();\n }\n\n private handleResize(): void {\n this.checkMobile();\n }\n\n private createEmbed(): void {\n // Check if user provided a container selector\n let userContainer: HTMLElement | null = null;\n if (this.config.container) {\n userContainer = document.querySelector(this.config.container);\n }\n\n if (userContainer) {\n // Use user-provided container\n this.container = userContainer as HTMLDivElement;\n this.container.classList.add(\"qaid-widget\", `qaid-${this.config.position}`);\n if (this.config.incognito) {\n this.container.classList.add(\"qaid-incognito\");\n }\n this.isUserProvidedContainer = true;\n } else {\n // Create our own fixed-position container\n this.container = document.createElement(\"div\");\n this.container.className = `qaid-widget qaid-auto-container qaid-${this.config.position}${this.config.incognito ? \" qaid-incognito\" : \"\"}`;\n this.container.style.zIndex = String(this.config.zIndex);\n // Apply custom offset\n const { x: offsetX, y: offsetY } = this.config.offset;\n if (this.config.position.includes(\"right\")) {\n this.container.style.right = `${offsetX}px`;\n } else {\n this.container.style.left = `${offsetX}px`;\n }\n if (this.config.position.includes(\"bottom\")) {\n this.container.style.bottom = `${offsetY}px`;\n } else {\n this.container.style.top = `${offsetY}px`;\n }\n document.body.appendChild(this.container);\n }\n\n // Apply custom fontFamily as inline style if not using default\n const customFont = this.config.fontFamily;\n const isCustomFont = customFont && customFont !== \"system-ui, -apple-system, sans-serif\";\n if (isCustomFont) {\n this.container.style.fontFamily = customFont;\n }\n\n // Determine button classes\n const useCustomClass = !!this.config.buttonClass;\n const btnBaseClass = useCustomClass\n ? `qaid-btn-structural ${this.config.buttonClass}`\n : \"qaid-btn\";\n\n // Determine tooltip text\n const defaultTooltip = this.config.skipTargeting\n ? \"Send feedback\"\n : \"Any feedback? Click to start, Esc to cancel\";\n const tooltipText = this.config.text.tooltip || defaultTooltip;\n\n // Create shared tooltip element\n const tooltip = document.createElement(\"div\");\n tooltip.className = \"qaid-tooltip-text\";\n tooltip.textContent = tooltipText;\n if (isCustomFont) {\n tooltip.style.fontFamily = customFont;\n }\n document.body.appendChild(tooltip);\n this.tooltipElement = tooltip;\n\n // Thumbs up button\n const upWrapper = document.createElement(\"div\");\n upWrapper.className = \"qaid-tooltip-wrapper\";\n\n const upBtn = document.createElement(\"button\");\n upBtn.type = \"button\";\n upBtn.className = useCustomClass ? `${btnBaseClass} qaid-btn-up` : \"qaid-btn qaid-btn-up\";\n upBtn.innerHTML = this.config.positiveIcon || THUMBS_UP_ICON;\n upBtn.addEventListener(\"click\", (e) => this.handleThumbClick(\"up\", e.currentTarget as HTMLElement));\n upBtn.addEventListener(\"mouseenter\", () => this.showTooltip(upBtn));\n upBtn.addEventListener(\"mouseleave\", () => this.hideTooltip());\n upWrapper.appendChild(upBtn);\n\n // Thumbs down button\n const downWrapper = document.createElement(\"div\");\n downWrapper.className = \"qaid-tooltip-wrapper\";\n\n const downBtn = document.createElement(\"button\");\n downBtn.type = \"button\";\n downBtn.className = useCustomClass ? `${btnBaseClass} qaid-btn-down` : \"qaid-btn qaid-btn-down\";\n downBtn.innerHTML = this.config.negativeIcon || THUMBS_DOWN_ICON;\n downBtn.addEventListener(\"click\", (e) => this.handleThumbClick(\"down\", e.currentTarget as HTMLElement));\n downBtn.addEventListener(\"mouseenter\", () => this.showTooltip(downBtn));\n downBtn.addEventListener(\"mouseleave\", () => this.hideTooltip());\n downWrapper.appendChild(downBtn);\n\n this.container.appendChild(upWrapper);\n this.container.appendChild(downWrapper);\n }\n\n private tooltipElement: HTMLElement | null = null;\n\n private showTooltip(anchor: HTMLElement): void {\n if (!this.tooltipElement) return;\n\n const tooltip = this.tooltipElement;\n const gap = 8;\n\n // Make visible to measure\n tooltip.style.visibility = \"hidden\";\n tooltip.classList.add(\"qaid-tooltip-visible\");\n\n const anchorRect = anchor.getBoundingClientRect();\n const tooltipRect = tooltip.getBoundingClientRect();\n const viewportWidth = window.innerWidth;\n const viewportHeight = window.innerHeight;\n\n let top: number;\n let left: number;\n\n // Always position below the button\n top = anchorRect.bottom + gap;\n\n // If below would overflow viewport, try above\n if (top + tooltipRect.height > viewportHeight - gap) {\n top = anchorRect.top - tooltipRect.height - gap;\n }\n\n // Align to left edge of anchor\n left = anchorRect.left;\n\n // Clamp to viewport horizontally\n if (left < gap) {\n left = gap;\n } else if (left + tooltipRect.width > viewportWidth - gap) {\n left = viewportWidth - tooltipRect.width - gap;\n }\n\n // Clamp to viewport vertically\n if (top < gap) {\n top = gap;\n } else if (top + tooltipRect.height > viewportHeight - gap) {\n top = viewportHeight - tooltipRect.height - gap;\n }\n\n tooltip.style.top = `${top}px`;\n tooltip.style.left = `${left}px`;\n tooltip.style.visibility = \"visible\";\n }\n\n private hideTooltip(): void {\n if (this.tooltipElement) {\n this.tooltipElement.classList.remove(\"qaid-tooltip-visible\");\n }\n }\n\n private handleThumbClick(type: \"up\" | \"down\", buttonEl: HTMLElement): void {\n if (this.config.skipTargeting) {\n this.submitDirectFeedback(type, buttonEl);\n } else {\n this.startTargeting(type);\n }\n }\n\n private submitDirectFeedback(type: \"up\" | \"down\", buttonEl: HTMLElement): void {\n this.feedbackData.feedbackType = type;\n this.feedbackData.elementSelector = null;\n this.feedbackData.elementText = null;\n\n // Use the button's position for modal placement\n const rect = buttonEl.getBoundingClientRect();\n this.selectedBounds = {\n x: rect.left,\n y: rect.top,\n width: rect.width,\n height: rect.height,\n clickX: rect.left + rect.width / 2,\n clickY: rect.top + rect.height / 2,\n visible: false,\n };\n this.submitFeedback();\n }\n\n private startTargeting(type: \"up\" | \"down\"): void {\n this.state = \"TARGETING\";\n this.feedbackData.feedbackType = type;\n this.feedbackData.elementSelector = null;\n this.feedbackData.elementText = null;\n this.selectedBounds.visible = false;\n\n // Add class to body\n document.body.classList.add(\"qaid-targeting\");\n if (type === \"up\") {\n document.body.classList.add(\"qaid-type-up\");\n } else {\n document.body.classList.remove(\"qaid-type-up\");\n }\n\n // Create targeting overlay\n this.createTargetingOverlay();\n\n // Add event listeners\n document.addEventListener(\"keydown\", this.boundKeyDown);\n }\n\n private createTargetingOverlay(): void {\n this.overlayContainer = document.createElement(\"div\");\n this.overlayContainer.className = `qaid-targeting-overlay qaid-type-${this.feedbackData.feedbackType}`;\n\n // Capture layer\n this.captureLayer = document.createElement(\"div\");\n this.captureLayer.className = \"qaid-capture-layer\";\n this.captureLayer.addEventListener(\"mousemove\", this.boundMouseMove);\n this.captureLayer.addEventListener(\"click\", this.boundClick);\n\n // Banner\n const banner = document.createElement(\"div\");\n banner.className = `qaid-banner qaid-banner-${this.feedbackData.feedbackType}`;\n // Apply custom font if configured\n const customFont = this.config.fontFamily;\n if (customFont && customFont !== \"system-ui, -apple-system, sans-serif\") {\n banner.style.fontFamily = customFont;\n }\n banner.innerHTML = `\n <span class=\"qaid-banner-text\">${this.config.text.bannerText}</span>\n <span class=\"qaid-banner-hint\">${this.config.text.bannerHint}</span>\n `;\n\n // Vignette\n const vignette = document.createElement(\"div\");\n vignette.className = \"qaid-vignette\";\n\n // Crosshairs\n this.crosshairH = document.createElement(\"div\");\n this.crosshairH.className = \"qaid-crosshair-h\";\n\n this.crosshairV = document.createElement(\"div\");\n this.crosshairV.className = \"qaid-crosshair-v\";\n\n // Scope\n this.scope = document.createElement(\"div\");\n this.scope.className = \"qaid-scope\";\n this.scope.innerHTML = `\n <div class=\"qaid-scope-ring\"></div>\n <div class=\"qaid-scope-ring-inner\"></div>\n <div class=\"qaid-scope-dot\"></div>\n `;\n\n this.overlayContainer.appendChild(this.captureLayer);\n this.overlayContainer.appendChild(banner);\n this.overlayContainer.appendChild(vignette);\n this.overlayContainer.appendChild(this.crosshairH);\n this.overlayContainer.appendChild(this.crosshairV);\n this.overlayContainer.appendChild(this.scope);\n\n document.body.appendChild(this.overlayContainer);\n }\n\n private handleKeyDown(e: KeyboardEvent): void {\n if (e.key === \"Escape\") {\n if (this.state === \"TARGETING\") {\n this.cancelTargeting();\n } else if (this.state === \"MODAL_OPEN\") {\n this.closeModal();\n }\n }\n }\n\n private handleMouseMove(e: MouseEvent): void {\n this.mousePos.x = e.clientX;\n this.mousePos.y = e.clientY;\n\n // Update crosshairs and scope\n if (this.crosshairH) {\n this.crosshairH.style.top = `${e.clientY}px`;\n }\n if (this.crosshairV) {\n this.crosshairV.style.left = `${e.clientX}px`;\n }\n if (this.scope) {\n this.scope.style.left = `${e.clientX}px`;\n this.scope.style.top = `${e.clientY}px`;\n }\n\n // Find element underneath\n if (this.captureLayer) {\n const elementUnder = getElementAtPointUnderOverlay(\n e.clientX,\n e.clientY,\n this.captureLayer\n );\n\n if (elementUnder && !isEmbedElement(elementUnder)) {\n if (this.lastHighlighted && this.lastHighlighted !== elementUnder) {\n this.lastHighlighted.classList.remove(\"qaid-highlight\");\n }\n elementUnder.classList.add(\"qaid-highlight\");\n this.lastHighlighted = elementUnder;\n } else if (this.lastHighlighted) {\n this.lastHighlighted.classList.remove(\"qaid-highlight\");\n this.lastHighlighted = null;\n }\n }\n }\n\n private handleClick(e: MouseEvent): void {\n if (!this.captureLayer) return;\n\n // Find element underneath\n const target = getElementAtPointUnderOverlay(\n e.clientX,\n e.clientY,\n this.captureLayer\n );\n\n if (!target || isEmbedElement(target)) {\n return;\n }\n\n // Remove highlight class before generating selector\n target.classList.remove(\"qaid-highlight\");\n\n // Get element bounds\n const bounds = getElementBounds(target, 8);\n this.selectedBounds = {\n ...bounds,\n clickX: e.clientX,\n clickY: e.clientY,\n visible: true,\n };\n\n const { selector, text } = generateElementInfo(target);\n this.feedbackData.elementSelector = selector;\n this.feedbackData.elementText = text;\n\n // Clean up highlights\n removeAllByClass(\"qaid-highlight\");\n\n // Remove targeting overlay\n this.removeTargetingOverlay();\n document.body.classList.remove(\"qaid-targeting\", \"qaid-type-up\");\n\n this.state = \"SELECTED\";\n\n // Show marker and submit feedback\n this.showSelectedMarker();\n this.submitFeedback();\n }\n\n private cancelTargeting(): void {\n // Clean up highlights\n removeAllByClass(\"qaid-highlight\");\n\n this.removeTargetingOverlay();\n document.body.classList.remove(\"qaid-targeting\", \"qaid-type-up\");\n document.removeEventListener(\"keydown\", this.boundKeyDown);\n\n this.state = \"IDLE\";\n this.feedbackData.feedbackType = null;\n this.feedbackData.elementSelector = null;\n this.feedbackData.elementText = null;\n this.selectedBounds.visible = false;\n this.lastHighlighted = null;\n }\n\n private removeTargetingOverlay(): void {\n if (this.overlayContainer) {\n this.overlayContainer.remove();\n this.overlayContainer = null;\n }\n this.captureLayer = null;\n this.crosshairH = null;\n this.crosshairV = null;\n this.scope = null;\n }\n\n private showSelectedMarker(): void {\n if (!this.selectedBounds.visible) return;\n\n this.marker = document.createElement(\"div\");\n this.marker.className = \"qaid-selected-marker\";\n this.marker.style.left = `${this.selectedBounds.x}px`;\n this.marker.style.top = `${this.selectedBounds.y}px`;\n this.marker.style.width = `${this.selectedBounds.width}px`;\n this.marker.style.height = `${this.selectedBounds.height}px`;\n this.marker.style.zIndex = String(this.config.zIndex + 1);\n\n document.body.appendChild(this.marker);\n }\n\n private hideSelectedMarker(): void {\n if (this.marker) {\n this.marker.remove();\n this.marker = null;\n }\n }\n\n private async submitFeedback(): Promise<void> {\n if (!this.feedbackData.feedbackType) return;\n\n // Capture screenshot if enabled and allowed by server\n let screenshot: string | null = null;\n const screenshotAllowed = this.allowedFeatures?.screenshots !== false;\n if (this.config.captureScreenshot && screenshotAllowed) {\n screenshot = await captureScreenshot(this.config.screenshotOptions);\n }\n\n // Include element bounds for server-side screenshot fallback\n // Only include if we have an element selector (element was targeted)\n const elementBounds = this.feedbackData.elementSelector\n ? {\n x: this.selectedBounds.x,\n y: this.selectedBounds.y,\n width: this.selectedBounds.width,\n height: this.selectedBounds.height,\n }\n : null;\n\n const payload: FeedbackPayload = {\n feedbackType: this.feedbackData.feedbackType,\n pageUrl: window.location.href,\n apiKey: this.config.apiKey || undefined,\n elementSelector: this.feedbackData.elementSelector,\n elementText: this.feedbackData.elementText,\n consoleErrors: [...this.feedbackData.consoleErrors],\n screenWidth: window.innerWidth,\n screenHeight: window.innerHeight,\n clickX: this.selectedBounds.clickX,\n clickY: this.selectedBounds.clickY,\n scrollX: window.scrollX,\n scrollY: window.scrollY,\n screenshot,\n visitorId: this.visitorId,\n // Include element bounds and user agent for server-side screenshot fallback\n elementBounds,\n userAgent: navigator.userAgent,\n };\n\n try {\n const response = await fetch(this.config.endpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(payload),\n });\n\n if (response.ok) {\n const data: FeedbackResponse = await response.json();\n this.feedbackId = data.id;\n }\n } catch (error) {\n console.error(\"Failed to submit feedback:\", error);\n }\n\n // Always show modal (even if API failed - useful for demos)\n this.state = \"MODAL_OPEN\";\n this.showModal();\n }\n\n private showModal(): void {\n // Create backdrop\n this.backdrop = document.createElement(\"div\");\n this.backdrop.className = \"qaid-backdrop\";\n this.backdrop.style.zIndex = String(this.config.zIndex + 2);\n this.backdrop.style.background = `rgba(0, 0, 0, ${this.config.backdropOpacity})`;\n this.backdrop.addEventListener(\"click\", () => this.closeModal());\n\n if (this.isMobile) {\n this.showBottomSheet();\n } else {\n this.showPositionedModal();\n }\n\n document.body.appendChild(this.backdrop);\n document.addEventListener(\"keydown\", this.boundKeyDown);\n }\n\n private showBottomSheet(): void {\n const sheet = document.createElement(\"div\");\n sheet.className = \"qaid-bottom-sheet\";\n sheet.style.zIndex = String(this.config.zIndex + 3);\n // Apply custom font if configured\n const customFont = this.config.fontFamily;\n if (customFont && customFont !== \"system-ui, -apple-system, sans-serif\") {\n sheet.style.fontFamily = customFont;\n }\n\n sheet.innerHTML = `\n <div class=\"qaid-bottom-sheet-content\">\n <div class=\"qaid-bottom-sheet-handle\"></div>\n ${this.getModalContent()}\n </div>\n `;\n\n document.body.appendChild(sheet);\n this.modalContainer = sheet;\n\n this.setupModalInteractions();\n }\n\n private showPositionedModal(): void {\n const { modal, arrow } = calculateModalAndArrowPosition(\n this.selectedBounds,\n window.innerWidth,\n window.innerHeight,\n {\n width: this.config.modalWidth,\n height: 280,\n arrowHeight: 12,\n gap: 8,\n viewportPadding: 16,\n }\n );\n\n this.modalContainer = document.createElement(\"div\");\n this.modalContainer.className = `qaid-modal-container qaid-${modal.position}`;\n this.modalContainer.style.top = `${modal.top}px`;\n this.modalContainer.style.left = `${modal.left}px`;\n this.modalContainer.style.zIndex = String(this.config.zIndex + 3);\n // Apply custom font if configured\n const customFont = this.config.fontFamily;\n if (customFont && customFont !== \"system-ui, -apple-system, sans-serif\") {\n this.modalContainer.style.fontFamily = customFont;\n }\n\n const arrowEl = document.createElement(\"div\");\n arrowEl.className = \"qaid-modal-arrow\";\n arrowEl.style.left = `${arrow.left}px`;\n\n const box = document.createElement(\"div\");\n box.className = \"qaid-modal-box\";\n box.innerHTML = this.getModalContent();\n\n this.modalContainer.appendChild(arrowEl);\n this.modalContainer.appendChild(box);\n document.body.appendChild(this.modalContainer);\n\n this.setupModalInteractions();\n }\n\n private getModalContent(): string {\n const isUp = this.feedbackData.feedbackType === \"up\";\n const positiveIcon = this.config.positiveIcon || THUMBS_UP_ICON;\n const negativeIcon = this.config.negativeIcon || THUMBS_DOWN_ICON;\n const toggleClass = this.config.buttonClass\n ? `qaid-type-toggle qaid-type-toggle-custom ${this.config.buttonClass} ${isUp ? \"qaid-btn-up\" : \"qaid-btn-down\"}`\n : `qaid-type-toggle ${isUp ? \"qaid-type-up\" : \"qaid-type-down\"}`;\n return `\n <div class=\"qaid-modal-header\">\n <button type=\"button\" class=\"${toggleClass}\" title=\"Click to switch\">\n ${isUp ? positiveIcon : negativeIcon}\n </button>\n <div class=\"qaid-modal-header-text\">\n <h3 class=\"qaid-modal-title\">${this.config.text.modalTitle}</h3>\n <p class=\"qaid-modal-subtitle\">${this.config.text.modalSubtitle}</p>\n </div>\n </div>\n <textarea class=\"qaid-textarea\" placeholder=\"${this.config.text.placeholder}\"></textarea>\n <div class=\"qaid-btn-row\">\n <button type=\"button\" class=\"qaid-btn-submit\">${this.config.text.skipButton}</button>\n </div>\n `;\n }\n\n private setupModalInteractions(): void {\n if (!this.modalContainer) return;\n\n const textarea =\n this.modalContainer.querySelector<HTMLTextAreaElement>(\".qaid-textarea\");\n const submitBtn =\n this.modalContainer.querySelector<HTMLButtonElement>(\".qaid-btn-submit\");\n if (textarea) {\n // Auto-focus\n setTimeout(() => textarea.focus(), 100);\n\n // Update button text based on content\n textarea.addEventListener(\"input\", () => {\n if (submitBtn) {\n submitBtn.textContent = textarea.value.trim() ? this.config.text.submitButton : this.config.text.skipButton;\n }\n });\n }\n\n if (submitBtn) {\n submitBtn.addEventListener(\"click\", () => {\n const message = textarea?.value.trim() || null;\n this.submitMessage(message);\n });\n }\n\n // Handle feedback type toggle\n const typeToggle = this.modalContainer.querySelector<HTMLButtonElement>(\".qaid-type-toggle\");\n if (typeToggle) {\n typeToggle.addEventListener(\"click\", () => {\n const newType = this.feedbackData.feedbackType === \"up\" ? \"down\" : \"up\";\n this.feedbackData.feedbackType = newType;\n\n // Update UI - use different classes depending on custom buttonClass\n if (this.config.buttonClass) {\n typeToggle.classList.toggle(\"qaid-btn-up\", newType === \"up\");\n typeToggle.classList.toggle(\"qaid-btn-down\", newType === \"down\");\n } else {\n typeToggle.classList.toggle(\"qaid-type-up\", newType === \"up\");\n typeToggle.classList.toggle(\"qaid-type-down\", newType === \"down\");\n }\n const positiveIcon = this.config.positiveIcon || THUMBS_UP_ICON;\n const negativeIcon = this.config.negativeIcon || THUMBS_DOWN_ICON;\n typeToggle.innerHTML = newType === \"up\" ? positiveIcon : negativeIcon;\n\n // Update feedback on server\n if (this.feedbackId) {\n fetch(`${this.config.endpoint}/${this.feedbackId}`, {\n method: \"PATCH\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ feedbackType: newType }),\n }).catch((err) => console.error(\"Failed to update feedback type:\", err));\n }\n });\n }\n }\n\n private async submitMessage(message: string | null): Promise<void> {\n if (this.feedbackId) {\n try {\n await fetch(`${this.config.endpoint}/${this.feedbackId}`, {\n method: \"PATCH\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ message }),\n });\n } catch (error) {\n console.error(\"Failed to submit feedback message:\", error);\n }\n // Clear feedbackId so closeModal doesn't send another PATCH\n this.feedbackId = null;\n }\n\n this.closeModal();\n }\n\n private closeModal(): void {\n // If modal is open and we're closing without submitting, still finalize\n if (this.state === \"MODAL_OPEN\" && this.feedbackId) {\n fetch(`${this.config.endpoint}/${this.feedbackId}`, {\n method: \"PATCH\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ message: null }),\n }).catch((err) => console.error(\"Failed to finalize feedback:\", err));\n }\n\n if (this.modalContainer) {\n this.modalContainer.remove();\n this.modalContainer = null;\n }\n if (this.backdrop) {\n this.backdrop.remove();\n this.backdrop = null;\n }\n\n document.removeEventListener(\"keydown\", this.boundKeyDown);\n this.hideSelectedMarker();\n\n this.state = \"IDLE\";\n this.feedbackId = null;\n this.feedbackData.feedbackType = null;\n this.feedbackData.elementSelector = null;\n this.feedbackData.elementText = null;\n this.selectedBounds.visible = false;\n }\n\n /**\n * Destroy the embed and clean up all resources\n */\n public destroy(): void {\n // Restore console.error\n if (this.consoleCapture) {\n this.consoleCapture.restore();\n this.consoleCapture = null;\n }\n\n // Remove event listeners\n window.removeEventListener(\"resize\", this.boundResize);\n document.removeEventListener(\"keydown\", this.boundKeyDown);\n\n // Clean up highlights\n removeAllByClass(\"qaid-highlight\");\n document.body.classList.remove(\"qaid-targeting\", \"qaid-type-up\");\n\n // Remove DOM elements\n if (this.container) {\n if (this.isUserProvidedContainer) {\n // Just clear the contents and remove our classes\n this.container.innerHTML = \"\";\n this.container.classList.remove(\"qaid-widget\", `qaid-${this.config.position}`);\n } else {\n this.container.remove();\n }\n this.container = null;\n }\n this.removeTargetingOverlay();\n this.hideSelectedMarker();\n if (this.modalContainer) {\n this.modalContainer.remove();\n this.modalContainer = null;\n }\n if (this.backdrop) {\n this.backdrop.remove();\n this.backdrop = null;\n }\n if (this.tooltipElement) {\n this.tooltipElement.remove();\n this.tooltipElement = null;\n }\n\n // Remove styles\n removeStyles();\n }\n}\n","/**\n * @babylonjsmarket/qaid - Standalone Feedback Embed\n *\n * A zero-dependency feedback embed with element targeting.\n *\n * Usage via ES module:\n * ```typescript\n * import { FeedbackEmbed } from '@babylonjsmarket/qaid';\n *\n * const embed = new FeedbackEmbed({\n * endpoint: '/api/feedback'\n * });\n * ```\n *\n * Usage via script tag:\n * ```html\n * <script src=\"feedback.umd.cjs\" data-endpoint=\"/api/feedback\"></script>\n * ```\n *\n * Or manually initialize:\n * ```html\n * <script src=\"feedback.umd.cjs\"></script>\n * <script>\n * const embed = new FeedbackEmbed.FeedbackEmbed({\n * endpoint: '/api/feedback'\n * });\n * </script>\n * ```\n */\n\nimport { FeedbackEmbed } from \"./embed\";\n\nexport { FeedbackEmbed };\nexport type {\n FeedbackConfig,\n ResolvedFeedbackConfig,\n ConsoleError,\n FeedbackData,\n SelectedBounds,\n FeedbackPayload,\n FeedbackResponse,\n FeedbackMessagePayload,\n} from \"./types\";\n\nimport type { FeedbackConfig } from \"./types\";\n\n/**\n * Parse JSON config from a separate script tag with type=\"application/json\"\n * Looks for: <script type=\"application/json\" data-feedback-config>{ ... }</script>\n */\nfunction parseJsonConfig(): Partial<FeedbackConfig> | null {\n const configScript = document.querySelector(\n 'script[type=\"application/json\"][data-feedback-config]'\n );\n if (!configScript) return null;\n\n const textContent = configScript.textContent?.trim();\n if (!textContent) return null;\n\n try {\n return JSON.parse(textContent);\n } catch {\n return null;\n }\n}\n\n/**\n * Parse config from data-* attributes (backward compatibility)\n */\nfunction parseDataAttributes(script: HTMLScriptElement): Partial<FeedbackConfig> | null {\n const endpoint = script.getAttribute(\"data-endpoint\");\n if (!endpoint) return null;\n\n const position = script.getAttribute(\"data-position\") as\n | \"bottom-right\"\n | \"bottom-left\"\n | \"top-right\"\n | \"top-left\"\n | null;\n const zIndex = script.getAttribute(\"data-zindex\");\n const positiveColor = script.getAttribute(\"data-positive-color\");\n const negativeColor = script.getAttribute(\"data-negative-color\");\n const markerColor = script.getAttribute(\"data-marker-color\");\n const container = script.getAttribute(\"data-container\");\n const buttonClass = script.getAttribute(\"data-button-class\");\n const skipTargeting = script.getAttribute(\"data-skip-targeting\");\n const incognito = script.getAttribute(\"data-incognito\");\n const buttonSize = script.getAttribute(\"data-button-size\") as\n | \"small\"\n | \"medium\"\n | \"large\"\n | null;\n const offsetX = script.getAttribute(\"data-offset-x\");\n const offsetY = script.getAttribute(\"data-offset-y\");\n const modalWidth = script.getAttribute(\"data-modal-width\");\n const backdropOpacity = script.getAttribute(\"data-backdrop-opacity\");\n const fontFamily = script.getAttribute(\"data-font-family\");\n const fontSize = script.getAttribute(\"data-font-size\");\n const tooltip = script.getAttribute(\"data-tooltip\");\n const bannerText = script.getAttribute(\"data-banner-text\");\n const bannerHint = script.getAttribute(\"data-banner-hint\");\n const modalTitle = script.getAttribute(\"data-modal-title\");\n const modalSubtitle = script.getAttribute(\"data-modal-subtitle\");\n const placeholder = script.getAttribute(\"data-placeholder\");\n const submitButton = script.getAttribute(\"data-submit-button\");\n const skipButton = script.getAttribute(\"data-skip-button\");\n const positiveIcon = script.getAttribute(\"data-positive-icon\");\n const negativeIcon = script.getAttribute(\"data-negative-icon\");\n\n return {\n endpoint,\n container: container ?? undefined,\n buttonClass: buttonClass ?? undefined,\n position: position ?? undefined,\n zIndex: zIndex ? parseInt(zIndex, 10) : undefined,\n skipTargeting: skipTargeting === \"true\" ? true : undefined,\n incognito: incognito === \"true\" ? true : undefined,\n buttonSize: buttonSize ?? undefined,\n offset: (offsetX || offsetY) ? {\n x: offsetX ? parseInt(offsetX, 10) : undefined,\n y: offsetY ? parseInt(offsetY, 10) : undefined,\n } : undefined,\n modalWidth: modalWidth ? parseInt(modalWidth, 10) : undefined,\n backdropOpacity: backdropOpacity ? parseFloat(backdropOpacity) : undefined,\n fontFamily: fontFamily ?? undefined,\n fontSize: fontSize ? parseInt(fontSize, 10) : undefined,\n colors: {\n positive: positiveColor ?? undefined,\n negative: negativeColor ?? undefined,\n marker: markerColor ?? undefined,\n },\n text: (tooltip || bannerText || bannerHint || modalTitle || modalSubtitle || placeholder || submitButton || skipButton) ? {\n tooltip: tooltip ?? undefined,\n bannerText: bannerText ?? undefined,\n bannerHint: bannerHint ?? undefined,\n modalTitle: modalTitle ?? undefined,\n modalSubtitle: modalSubtitle ?? undefined,\n placeholder: placeholder ?? undefined,\n submitButton: submitButton ?? undefined,\n skipButton: skipButton ?? undefined,\n } : undefined,\n positiveIcon: positiveIcon ?? undefined,\n negativeIcon: negativeIcon ?? undefined,\n };\n}\n\n// Auto-initialize from script tag\nif (typeof document !== \"undefined\") {\n const initFromScript = (): void => {\n const script = document.currentScript as HTMLScriptElement | null;\n\n // Try JSON config from separate script tag first, fall back to data-* attributes\n const jsonConfig = parseJsonConfig();\n const dataConfig = script ? parseDataAttributes(script) : null;\n const config = jsonConfig ?? dataConfig;\n\n if (config?.endpoint) {\n new FeedbackEmbed(config as FeedbackConfig);\n }\n };\n\n // Run immediately if DOM is ready, otherwise wait\n if (document.readyState === \"loading\") {\n document.addEventListener(\"DOMContentLoaded\", initFromScript);\n } else {\n initFromScript();\n }\n}\n"],"names":["THUMBS_UP_ICON","THUMBS_DOWN_ICON","stylesInjected","BUTTON_SIZES","ICON_SIZES","getLuminance","r","g","b","rs","gs","bs","c","parseColor","color","hex","fullHex","num","match","getContrastTextColor","bgColor","rgb","injectStyles","options","positiveColor","negativeColor","markerColor","buttonSize","modalWidth","backdropOpacity","fontFamily","fontSize","btnSize","iconSize","markerTextColor","cssVars","buttonStyles","style","baseStyles","removeStyles","DATA_ATTRS","findDataAttribute","element","stopAt","current","attr","value","getTruncatedText","maxLength","textContent","_a","getNthChildIndex","index","sibling","generateNthChildPath","path","tag","escapeCSSIdentifier","str","generateSelector","dataAttr","generateElementInfo","text","DEFAULT_DIMENSIONS","calculateVerticalPosition","bounds","viewportHeight","totalHeight","gap","viewportPadding","spaceAbove","spaceBelow","position","top","calculateHorizontalPosition","viewportWidth","left","calculateArrowPosition","modalLeft","minOffset","arrowWidth","arrowLeft","calculateModalPosition","dimensions","vertical","calculateModalAndArrowPosition","modal","MAX_MESSAGES","captureConsoleErrors","onError","errors","originalError","originalWarn","originalLog","capture","level","args","error","arg","isMobileViewport","breakpoint","getElementAtPointUnderOverlay","x","y","overlay","originalPointerEvents","isEmbedElement","getElementBounds","padding","rect","removeAllByClass","className","el","captureScreenshot","quality","maxWidth","maxHeight","stream","track","settings","video","resolve","checkFrame","srcWidth","srcHeight","scale","width","height","canvas","ctx","VISITOR_ID_KEY","getOrCreateVisitorId","visitorId","FeedbackEmbed","config","__publicField","_b","_c","_d","_e","_f","_g","_h","_i","_j","_k","_l","_m","_n","_o","_p","featuresUrl","response","userContainer","offsetX","offsetY","customFont","isCustomFont","useCustomClass","btnBaseClass","defaultTooltip","tooltipText","tooltip","upWrapper","upBtn","e","downWrapper","downBtn","anchor","anchorRect","tooltipRect","type","buttonEl","banner","vignette","elementUnder","target","selector","screenshot","screenshotAllowed","elementBounds","payload","data","sheet","arrow","arrowEl","box","isUp","positiveIcon","negativeIcon","textarea","submitBtn","message","typeToggle","newType","err","parseJsonConfig","configScript","parseDataAttributes","script","endpoint","zIndex","container","buttonClass","skipTargeting","incognito","bannerText","bannerHint","modalTitle","modalSubtitle","placeholder","submitButton","skipButton","initFromScript","jsonConfig","dataConfig"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"qaid.umd.cjs","sources":["../src/icons.ts","../src/styles.ts","../src/element-selector.ts","../src/modal-positioning.ts","../src/console-capture.ts","../src/dom-utils.ts","../src/screenshot.ts","../src/embed.ts","../src/index.ts"],"sourcesContent":["/**\n * SVG icon strings for thumbs up/down buttons\n */\n\nexport const THUMBS_UP_ICON = `<svg xmlns=\"http://www.w3.org/2000/svg\" class=\"qaid-icon\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M14 10h4.764a2 2 0 011.789 2.894l-3.5 7A2 2 0 0115.263 21h-4.017c-.163 0-.326-.02-.485-.06L7 20m7-10V5a2 2 0 00-2-2h-.095c-.5 0-.905.405-.905.905 0 .714-.211 1.412-.608 2.006L7 11v9m7-10h-2M7 20H5a2 2 0 01-2-2v-6a2 2 0 012-2h2.5\"/>\n</svg>`;\n\nexport const THUMBS_DOWN_ICON = `<svg xmlns=\"http://www.w3.org/2000/svg\" class=\"qaid-icon\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M10 14H5.236a2 2 0 01-1.789-2.894l3.5-7A2 2 0 018.737 3h4.018a2 2 0 01.485.06l3.76.94m-7 10v5a2 2 0 002 2h.095c.5 0 .905-.405.905-.905 0-.714.211-1.412.608-2.006L17 13V4m-7 10h2m5-10h2a2 2 0 012 2v6a2 2 0 01-2 2h-2.5\"/>\n</svg>`;\n","/**\n * CSS styles for the feedback embed\n * Uses .qaid- prefix to avoid conflicts\n */\n\n// Import CSS as minified string (vite handles minification in production)\nimport baseStyles from \"./styles.css?inline\";\n\nlet stylesInjected = false;\n\nexport interface InjectStylesOptions {\n positiveColor?: string;\n negativeColor?: string;\n /** Color for selected element marker */\n markerColor?: string;\n /** When true, skip default button visual styles (user provides via buttonClass) */\n skipButtonStyles?: boolean;\n /** Button size: small (36px), medium (48px), large (64px) */\n buttonSize?: \"small\" | \"medium\" | \"large\";\n /** Modal width in pixels */\n modalWidth?: number;\n /** Backdrop opacity (0-1) */\n backdropOpacity?: number;\n /** Font family */\n fontFamily?: string;\n /** Base font size in pixels */\n fontSize?: number;\n}\n\nconst BUTTON_SIZES = {\n small: 36,\n medium: 48,\n large: 64,\n} as const;\n\nconst ICON_SIZES = {\n small: 18,\n medium: 24,\n large: 32,\n} as const;\n\n/**\n * Calculate relative luminance of a color\n * Returns value between 0 (black) and 1 (white)\n */\nfunction getLuminance(r: number, g: number, b: number): number {\n const [rs, gs, bs] = [r, g, b].map((c) => {\n c = c / 255;\n return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);\n });\n return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;\n}\n\n/**\n * Parse a color string (hex or rgb) and return RGB values\n */\nfunction parseColor(color: string): { r: number; g: number; b: number } | null {\n // Hex format\n if (color.startsWith(\"#\")) {\n const hex = color.slice(1);\n const fullHex = hex.length === 3\n ? hex.split(\"\").map((c) => c + c).join(\"\")\n : hex;\n const num = parseInt(fullHex, 16);\n return {\n r: (num >> 16) & 255,\n g: (num >> 8) & 255,\n b: num & 255,\n };\n }\n // RGB format\n const match = color.match(/rgb\\((\\d+),\\s*(\\d+),\\s*(\\d+)\\)/);\n if (match) {\n return {\n r: parseInt(match[1]),\n g: parseInt(match[2]),\n b: parseInt(match[3]),\n };\n }\n return null;\n}\n\n/**\n * Get the best contrasting text color (black or white) for a background\n */\nfunction getContrastTextColor(bgColor: string): string {\n const rgb = parseColor(bgColor);\n if (!rgb) return \"white\";\n const luminance = getLuminance(rgb.r, rgb.g, rgb.b);\n // Use white text on dark backgrounds, black on light\n return luminance > 0.4 ? \"black\" : \"white\";\n}\n\nexport function injectStyles(options: InjectStylesOptions = {}): void {\n if (stylesInjected) return;\n stylesInjected = true;\n\n const {\n positiveColor = \"rgb(0, 200, 83)\",\n negativeColor = \"rgb(255, 0, 0)\",\n markerColor = \"#6366f1\",\n // skipButtonStyles kept for backwards compatibility but no longer used\n buttonSize = \"medium\",\n modalWidth = 400,\n backdropOpacity = 0.3,\n fontFamily = \"system-ui, -apple-system, sans-serif\",\n fontSize = 16,\n } = options;\n\n const btnSize = BUTTON_SIZES[buttonSize];\n const iconSize = ICON_SIZES[buttonSize];\n\n // Calculate contrast text color for marker (used on submit button)\n const markerTextColor = getContrastTextColor(markerColor);\n\n // CSS variables for colors and sizes\n const cssVars = `:root{--qaid-positive:${positiveColor};--qaid-negative:${negativeColor};--qaid-marker:${markerColor};--qaid-marker-text:${markerTextColor};--qaid-btn-size:${btnSize}px;--qaid-icon-size:${iconSize}px;--qaid-modal-width:${modalWidth}px;--qaid-backdrop-opacity:${backdropOpacity};--qaid-font-family:${fontFamily};--qaid-font-size:${fontSize}px}`;\n\n // Default button styles (always included - themed embeds use custom classes so these won't apply)\n const buttonStyles = `button.qaid-btn{width:var(--qaid-btn-size);height:var(--qaid-btn-size);border-radius:50%;border:none;background:#f3f4f6;color:#374151;display:flex;align-items:center;justify-content:center;cursor:pointer;box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06);transition:background-color .2s,color .2s,transform .2s;-webkit-appearance:none;appearance:none}button.qaid-btn:hover{transform:scale(1.05)}button.qaid-btn-up:hover{background:var(--qaid-positive);color:#fff}button.qaid-btn-down:hover{background:var(--qaid-negative);color:#fff}button.qaid-btn svg{width:var(--qaid-icon-size);height:var(--qaid-icon-size)}`;\n\n const style = document.createElement(\"style\");\n style.id = \"qaid-styles\";\n style.textContent = cssVars + baseStyles + buttonStyles;\n document.head.appendChild(style);\n}\n\nexport function removeStyles(): void {\n const style = document.getElementById(\"qaid-styles\");\n if (style) {\n style.remove();\n stylesInjected = false;\n }\n}\n","/**\n * Element selector generation utilities\n */\n\n/** Data attributes to look for (in priority order) */\nexport const DATA_ATTRS = [\"data-comp\", \"data-qa\", \"data-testid\", \"data-id\"];\n\nexport interface ElementInfo {\n selector: string;\n text: string;\n dataAttr: string | null;\n}\n\n/**\n * Find the first matching data attribute on an element or its ancestors\n */\nexport function findDataAttribute(\n element: Element,\n stopAt: Element = document.body\n): string | null {\n let current: Element | null = element;\n\n while (current && current !== stopAt) {\n for (const attr of DATA_ATTRS) {\n const value = current.getAttribute(attr);\n if (value) {\n return `${attr}=\"${value}\"`;\n }\n }\n current = current.parentElement;\n }\n\n return null;\n}\n\n/**\n * Get truncated text content from an element\n */\nexport function getTruncatedText(element: Element, maxLength = 100): string {\n const textContent = element.textContent?.trim().slice(0, maxLength) || \"\";\n return textContent.length === maxLength ? textContent + \"...\" : textContent;\n}\n\n/**\n * Get the nth-child index of an element among its siblings\n */\nfunction getNthChildIndex(element: Element): number {\n let index = 1;\n let sibling = element.previousElementSibling;\n while (sibling) {\n index++;\n sibling = sibling.previousElementSibling;\n }\n return index;\n}\n\n/**\n * Generate an nth-child path from body to the element\n * This is always a valid CSS selector and uniquely identifies the element\n * Exported for testing\n */\nexport function generateNthChildPath(element: Element): string {\n const path: string[] = [];\n let current: Element | null = element;\n\n while (current && current !== document.body && current !== document.documentElement) {\n const tag = current.tagName.toLowerCase();\n const index = getNthChildIndex(current);\n path.unshift(`${tag}:nth-child(${index})`);\n current = current.parentElement;\n }\n\n return path.length > 0 ? `body > ${path.join(\" > \")}` : \"body\";\n}\n\n/**\n * Escape special CSS characters in a string (for IDs with special chars)\n */\nfunction escapeCSSIdentifier(str: string): string {\n return str.replace(/([!\"#$%&'()*+,./:;<=>?@[\\\\\\]^`{|}~])/g, \"\\\\$1\");\n}\n\n/**\n * Generate a selector string for an element\n * Priority: data attribute > ID > nth-child path\n */\nexport function generateSelector(element: Element): string {\n // Try data attribute first (on element or ancestors) - most stable\n const dataAttr = findDataAttribute(element);\n if (dataAttr) {\n return `[${dataAttr}]`;\n }\n\n // Try ID (escaped for special characters)\n if (element.id) {\n return `#${escapeCSSIdentifier(element.id)}`;\n }\n\n // Fall back to nth-child path - always valid and unique\n return generateNthChildPath(element);\n}\n\n/**\n * Generate complete element info for feedback\n */\nexport function generateElementInfo(element: Element): ElementInfo {\n const text = getTruncatedText(element);\n const dataAttr = findDataAttribute(element);\n const selector = generateSelector(element);\n\n return { selector, text, dataAttr };\n}\n","/**\n * Modal positioning utilities\n */\n\nimport type { SelectedBounds } from \"./types\";\n\nexport interface ModalPosition {\n top: number;\n left: number;\n position: \"above\" | \"below\";\n}\n\nexport interface ArrowPosition {\n left: number;\n}\n\nexport interface ModalDimensions {\n width: number;\n height: number;\n arrowHeight: number;\n gap: number;\n viewportPadding: number;\n}\n\nconst DEFAULT_DIMENSIONS: ModalDimensions = {\n width: 400,\n height: 280,\n arrowHeight: 12,\n gap: 8,\n viewportPadding: 16,\n};\n\n/**\n * Calculate whether modal should go above or below the target element\n */\nexport function calculateVerticalPosition(\n bounds: SelectedBounds,\n viewportHeight: number,\n totalHeight: number,\n gap: number,\n viewportPadding: number\n): { top: number; position: \"above\" | \"below\" } {\n const spaceAbove = bounds.y;\n const spaceBelow = viewportHeight - (bounds.y + bounds.height);\n\n // Prefer below if there's enough space\n if (spaceBelow >= totalHeight + gap) {\n return {\n top: bounds.y + bounds.height + gap,\n position: \"below\",\n };\n }\n\n // Try above if there's enough space\n if (spaceAbove >= totalHeight + gap) {\n return {\n top: bounds.y - totalHeight - gap,\n position: \"above\",\n };\n }\n\n // Neither fits perfectly - clamp to viewport\n const position = spaceBelow > spaceAbove ? \"below\" : \"above\";\n let top: number;\n\n if (position === \"below\") {\n top = Math.min(\n bounds.y + bounds.height + gap,\n viewportHeight - totalHeight - viewportPadding\n );\n } else {\n top = Math.max(viewportPadding, bounds.y - totalHeight - gap);\n }\n\n return { top, position };\n}\n\n/**\n * Calculate horizontal position of modal, clamped to viewport\n */\nexport function calculateHorizontalPosition(\n bounds: SelectedBounds,\n viewportWidth: number,\n modalWidth: number,\n viewportPadding: number\n): number {\n const centerX = bounds.x + bounds.width / 2;\n let left = centerX - modalWidth / 2;\n\n // Clamp to viewport\n left = Math.max(viewportPadding, Math.min(left, viewportWidth - modalWidth - viewportPadding));\n\n return left;\n}\n\n/**\n * Calculate arrow position relative to modal\n * Points at the click position, not the element center\n */\nexport function calculateArrowPosition(\n bounds: SelectedBounds,\n modalLeft: number,\n modalWidth: number,\n minOffset = 24,\n arrowWidth = 24\n): number {\n // Use click position for arrow, not element center\n const targetX = bounds.clickX;\n // Subtract half the arrow width to center it on the target\n const arrowLeft = targetX - modalLeft - arrowWidth / 2;\n\n // Clamp arrow to stay within modal bounds\n return Math.max(minOffset, Math.min(arrowLeft, modalWidth - minOffset - arrowWidth / 2));\n}\n\n/**\n * Calculate complete modal position based on selected element bounds\n */\nexport function calculateModalPosition(\n bounds: SelectedBounds,\n viewportWidth: number,\n viewportHeight: number,\n dimensions: ModalDimensions = DEFAULT_DIMENSIONS\n): ModalPosition {\n const totalHeight = dimensions.height + dimensions.arrowHeight;\n\n const vertical = calculateVerticalPosition(\n bounds,\n viewportHeight,\n totalHeight,\n dimensions.gap,\n dimensions.viewportPadding\n );\n\n const left = calculateHorizontalPosition(\n bounds,\n viewportWidth,\n dimensions.width,\n dimensions.viewportPadding\n );\n\n return {\n top: vertical.top,\n left,\n position: vertical.position,\n };\n}\n\n/**\n * Calculate both modal and arrow positions\n */\nexport function calculateModalAndArrowPosition(\n bounds: SelectedBounds,\n viewportWidth: number,\n viewportHeight: number,\n dimensions: ModalDimensions = DEFAULT_DIMENSIONS\n): { modal: ModalPosition; arrow: ArrowPosition } {\n const modal = calculateModalPosition(bounds, viewportWidth, viewportHeight, dimensions);\n\n const arrowLeft = calculateArrowPosition(\n bounds,\n modal.left,\n dimensions.width\n );\n\n return {\n modal,\n arrow: { left: arrowLeft },\n };\n}\n","/**\n * Console capture utilities\n */\n\nimport type { ConsoleError } from \"./types\";\n\nconst MAX_MESSAGES = 20;\n\nexport interface ConsoleCapture {\n errors: ConsoleError[];\n restore: () => void;\n}\n\ntype ConsoleLevel = \"error\" | \"warn\" | \"log\";\n\n/**\n * Wrap console methods to capture messages\n */\nexport function captureConsoleErrors(\n onError?: (error: ConsoleError) => void\n): ConsoleCapture {\n const errors: ConsoleError[] = [];\n const originalError = console.error;\n const originalWarn = console.warn;\n const originalLog = console.log;\n\n const capture = (level: ConsoleLevel, args: unknown[]) => {\n const error: ConsoleError = {\n message: args.map((arg) => String(arg)).join(\" \"),\n timestamp: Date.now(),\n level,\n };\n\n // Keep max messages\n if (errors.length >= MAX_MESSAGES) {\n errors.shift();\n }\n errors.push(error);\n\n // Call callback if provided\n if (onError) {\n onError(error);\n }\n };\n\n console.error = function (...args: unknown[]) {\n capture(\"error\", args);\n originalError.apply(console, args);\n };\n\n console.warn = function (...args: unknown[]) {\n capture(\"warn\", args);\n originalWarn.apply(console, args);\n };\n\n console.log = function (...args: unknown[]) {\n capture(\"log\", args);\n originalLog.apply(console, args);\n };\n\n return {\n errors,\n restore: () => {\n console.error = originalError;\n console.warn = originalWarn;\n console.log = originalLog;\n },\n };\n}\n\n/**\n * Add an error to the captured errors array\n */\nexport function addError(errors: ConsoleError[], error: ConsoleError): void {\n if (errors.length >= MAX_MESSAGES) {\n errors.shift();\n }\n errors.push(error);\n}\n\n/**\n * Format console error for display\n */\nexport function formatError(error: ConsoleError): string {\n const date = new Date(error.timestamp);\n const time = date.toLocaleTimeString();\n const levelPrefix = error.level === \"error\" ? \"ERR\" : error.level === \"warn\" ? \"WRN\" : \"LOG\";\n return `[${time}] [${levelPrefix}] ${error.message}`;\n}\n","/**\n * DOM utility functions\n */\n\n/**\n * Create an element with attributes and optional children\n */\nexport function createElement<K extends keyof HTMLElementTagNameMap>(\n tag: K,\n attrs?: Record<string, string>,\n children?: (Node | string)[]\n): HTMLElementTagNameMap[K] {\n const el = document.createElement(tag);\n\n if (attrs) {\n for (const [key, value] of Object.entries(attrs)) {\n if (key === \"className\") {\n el.className = value;\n } else {\n el.setAttribute(key, value);\n }\n }\n }\n\n if (children) {\n for (const child of children) {\n if (typeof child === \"string\") {\n el.appendChild(document.createTextNode(child));\n } else {\n el.appendChild(child);\n }\n }\n }\n\n return el;\n}\n\n/**\n * Check if viewport width indicates mobile device\n */\nexport function isMobileViewport(breakpoint = 640): boolean {\n return typeof window !== \"undefined\" && window.innerWidth < breakpoint;\n}\n\n/**\n * Get element at point, temporarily hiding an overlay element\n */\nexport function getElementAtPointUnderOverlay(\n x: number,\n y: number,\n overlay: HTMLElement\n): Element | null {\n const originalPointerEvents = overlay.style.pointerEvents;\n overlay.style.pointerEvents = \"none\";\n const element = document.elementFromPoint(x, y);\n overlay.style.pointerEvents = originalPointerEvents;\n return element;\n}\n\n/**\n * Check if an element is part of the feedback embed\n */\nexport function isEmbedElement(element: Element | null): boolean {\n if (!element) return false;\n return !!(\n element.closest(\".qaid-widget\") ||\n element.closest(\".qaid-targeting-overlay\") ||\n element.closest(\".qaid-modal-container\") ||\n element.closest(\".qaid-bottom-sheet\")\n );\n}\n\n/**\n * Get element bounds with optional padding\n */\nexport function getElementBounds(\n element: Element,\n padding = 0\n): { x: number; y: number; width: number; height: number } {\n const rect = element.getBoundingClientRect();\n return {\n x: rect.left - padding,\n y: rect.top - padding,\n width: rect.width + padding * 2,\n height: rect.height + padding * 2,\n };\n}\n\n/**\n * Remove all elements with a specific class\n */\nexport function removeAllByClass(className: string): void {\n document.querySelectorAll(`.${className}`).forEach((el) => {\n el.classList.remove(className);\n });\n}\n","/**\n * Screenshot capture utility using native Screen Capture API\n */\n\nexport interface ScreenshotOptions {\n /** Quality of WebP compression (0-1). Default: 1.0 */\n quality?: number;\n /** Max width of the screenshot. Default: 1280 */\n maxWidth?: number;\n /** Max height of the screenshot. Default: 800 */\n maxHeight?: number;\n}\n\n/**\n * Captures a screenshot using the native Screen Capture API\n * Requires user permission but produces accurate results\n * Returns a base64-encoded WebP data URL\n */\nexport async function captureScreenshot(\n options: ScreenshotOptions = {}\n): Promise<string | null> {\n const { quality = 1.0, maxWidth = 1280, maxHeight = 800 } = options;\n\n try {\n // Check if API is available\n if (!navigator.mediaDevices?.getDisplayMedia) {\n console.warn('Screen Capture API not available');\n return null;\n }\n\n // Request screen capture - preferCurrentTab auto-selects current tab in supported browsers\n const stream = await navigator.mediaDevices.getDisplayMedia({\n preferCurrentTab: true,\n video: {\n displaySurface: 'browser',\n },\n } as DisplayMediaStreamOptions);\n\n // Get video track\n const track = stream.getVideoTracks()[0];\n const settings = track.getSettings();\n\n // Create video element\n const video = document.createElement('video');\n video.srcObject = stream;\n video.muted = true;\n\n await new Promise<void>((resolve) => {\n video.onloadedmetadata = () => {\n video.play();\n resolve();\n };\n });\n\n // Wait for video to have actual frame data\n await new Promise<void>((resolve) => {\n const checkFrame = () => {\n if (video.readyState >= 2) {\n resolve();\n } else {\n requestAnimationFrame(checkFrame);\n }\n };\n checkFrame();\n });\n\n // Extra delay to ensure frame is rendered\n await new Promise((resolve) => setTimeout(resolve, 100));\n\n // Calculate dimensions\n const srcWidth = settings.width || video.videoWidth;\n const srcHeight = settings.height || video.videoHeight;\n const scale = Math.min(maxWidth / srcWidth, maxHeight / srcHeight, 1);\n const width = Math.round(srcWidth * scale);\n const height = Math.round(srcHeight * scale);\n\n // Draw to canvas\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) {\n track.stop();\n return null;\n }\n\n ctx.drawImage(video, 0, 0, width, height);\n\n // Stop the stream\n track.stop();\n\n return canvas.toDataURL('image/webp', quality);\n } catch (error) {\n console.warn('Screenshot capture failed:', error);\n return null;\n }\n}\n","import type {\n FeedbackConfig,\n ResolvedFeedbackConfig,\n FeedbackData,\n SelectedBounds,\n EmbedState,\n FeedbackPayload,\n FeedbackResponse,\n} from \"./types\";\nimport { THUMBS_UP_ICON, THUMBS_DOWN_ICON } from \"./icons\";\nimport { injectStyles, removeStyles } from \"./styles\";\nimport { generateElementInfo } from \"./element-selector\";\nimport { calculateModalAndArrowPosition } from \"./modal-positioning\";\nimport { captureConsoleErrors, type ConsoleCapture } from \"./console-capture\";\nimport {\n getElementAtPointUnderOverlay,\n isEmbedElement,\n getElementBounds,\n removeAllByClass,\n isMobileViewport,\n} from \"./dom-utils\";\nimport { captureScreenshot } from \"./screenshot\";\n\nconst VISITOR_ID_KEY = \"qaid_visitor_id\";\n\n/**\n * Get or create a visitor ID stored in localStorage\n */\nfunction getOrCreateVisitorId(): string {\n try {\n let visitorId = localStorage.getItem(VISITOR_ID_KEY);\n if (!visitorId) {\n visitorId = crypto.randomUUID();\n localStorage.setItem(VISITOR_ID_KEY, visitorId);\n }\n return visitorId;\n } catch {\n // localStorage not available, generate a session-only ID\n return crypto.randomUUID();\n }\n}\n\n/**\n * FeedbackEmbed - Standalone feedback collection embed\n */\nexport class FeedbackEmbed {\n private config: ResolvedFeedbackConfig;\n private state: EmbedState = \"IDLE\";\n private feedbackData: FeedbackData = {\n feedbackType: null,\n elementSelector: null,\n elementText: null,\n consoleErrors: [],\n };\n private selectedBounds: SelectedBounds = {\n x: 0,\n y: 0,\n width: 0,\n height: 0,\n clickX: 0,\n clickY: 0,\n visible: false,\n };\n private feedbackId: number | null = null;\n private mousePos = { x: 0, y: 0 };\n private lastHighlighted: Element | null = null;\n private isMobile = false;\n private visitorId: string;\n\n // Console capture\n private consoleCapture: ConsoleCapture | null = null;\n\n // Server-side feature flags\n private allowedFeatures: { screenshots: boolean; consoleCapture: boolean } | null = null;\n\n // DOM elements\n private container: HTMLDivElement | null = null;\n private isUserProvidedContainer = false;\n private overlayContainer: HTMLDivElement | null = null;\n private captureLayer: HTMLDivElement | null = null;\n private crosshairH: HTMLDivElement | null = null;\n private crosshairV: HTMLDivElement | null = null;\n private scope: HTMLDivElement | null = null;\n private marker: HTMLDivElement | null = null;\n private modalContainer: HTMLDivElement | null = null;\n private backdrop: HTMLDivElement | null = null;\n\n // Bound event handlers\n private boundKeyDown: (e: KeyboardEvent) => void;\n private boundMouseMove: (e: MouseEvent) => void;\n private boundClick: (e: MouseEvent) => void;\n private boundResize: () => void;\n\n constructor(config: FeedbackConfig) {\n this.config = {\n endpoint: config.endpoint,\n apiKey: config.apiKey ?? \"\",\n container: config.container ?? \"\",\n buttonClass: config.buttonClass ?? \"\",\n position: config.position ?? \"bottom-right\",\n offset: {\n x: config.offset?.x ?? 16,\n y: config.offset?.y ?? 16,\n },\n zIndex: config.zIndex ?? 50,\n skipTargeting: config.skipTargeting ?? false,\n colors: {\n positive: config.colors?.positive ?? \"rgb(0, 200, 83)\",\n negative: config.colors?.negative ?? \"rgb(255, 0, 0)\",\n marker: config.colors?.marker ?? \"#6366f1\",\n },\n buttonSize: config.buttonSize ?? \"medium\",\n text: {\n tooltip: config.text?.tooltip ?? \"\",\n bannerText: config.text?.bannerText ?? \"Click on any element to target it with your feedback\",\n bannerHint: config.text?.bannerHint ?? \"(Press Escape to cancel)\",\n modalTitle: config.text?.modalTitle ?? \"Thank you for your feedback!\",\n modalSubtitle: config.text?.modalSubtitle ?? \"Would you like to add a message to help us understand your feedback better?\",\n placeholder: config.text?.placeholder ?? \"Optional: Tell us more about your experience...\",\n submitButton: config.text?.submitButton ?? \"Submit\",\n skipButton: config.text?.skipButton ?? \"Skip\",\n },\n modalWidth: config.modalWidth ?? 400,\n backdropOpacity: config.backdropOpacity ?? 0.3,\n fontFamily: config.fontFamily ?? \"system-ui, -apple-system, sans-serif\",\n fontSize: config.fontSize ?? 16,\n captureScreenshot: config.captureScreenshot ?? false,\n screenshotOptions: {\n quality: config.screenshotOptions?.quality ?? 0.8,\n maxWidth: config.screenshotOptions?.maxWidth ?? 1280,\n maxHeight: config.screenshotOptions?.maxHeight ?? 800,\n },\n incognito: config.incognito ?? false,\n positiveIcon: config.positiveIcon ?? \"\",\n negativeIcon: config.negativeIcon ?? \"\",\n };\n\n // Bind event handlers\n this.boundKeyDown = this.handleKeyDown.bind(this);\n this.boundMouseMove = this.handleMouseMove.bind(this);\n this.boundClick = this.handleClick.bind(this);\n this.boundResize = this.handleResize.bind(this);\n\n // Get or create visitor ID for anonymous feedback tracking\n this.visitorId = getOrCreateVisitorId();\n\n this.init();\n }\n\n private init(): void {\n // Inject styles\n injectStyles({\n positiveColor: this.config.colors.positive,\n negativeColor: this.config.colors.negative,\n markerColor: this.config.colors.marker,\n skipButtonStyles: !!this.config.buttonClass,\n buttonSize: this.config.buttonSize,\n modalWidth: this.config.modalWidth,\n backdropOpacity: this.config.backdropOpacity,\n fontFamily: this.config.fontFamily,\n fontSize: this.config.fontSize,\n });\n\n // Check mobile\n this.checkMobile();\n window.addEventListener(\"resize\", this.boundResize);\n\n // Create main embed container\n this.createEmbed();\n\n // Fetch allowed features from server\n this.fetchAllowedFeatures();\n\n // Capture console errors\n this.consoleCapture = captureConsoleErrors((error) => {\n this.feedbackData.consoleErrors = this.consoleCapture?.errors ?? [];\n });\n this.feedbackData.consoleErrors = this.consoleCapture.errors;\n }\n\n private async fetchAllowedFeatures(): Promise<void> {\n if (!this.config.apiKey) return;\n\n try {\n // Derive features endpoint from feedback endpoint\n const baseUrl = this.config.endpoint.replace(/\\/feedback\\/?$/, '');\n const featuresUrl = `${baseUrl}/features?apiKey=${encodeURIComponent(this.config.apiKey)}`;\n\n const response = await fetch(featuresUrl);\n if (response.ok) {\n this.allowedFeatures = await response.json();\n }\n } catch {\n // Silently fail - features will be determined server-side anyway\n }\n }\n\n private checkMobile(): void {\n this.isMobile = isMobileViewport();\n }\n\n private handleResize(): void {\n this.checkMobile();\n }\n\n private createEmbed(): void {\n // Check if user provided a container selector\n let userContainer: HTMLElement | null = null;\n if (this.config.container) {\n userContainer = document.querySelector(this.config.container);\n }\n\n if (userContainer) {\n // Use user-provided container\n this.container = userContainer as HTMLDivElement;\n this.container.classList.add(\"qaid-widget\", `qaid-${this.config.position}`);\n if (this.config.incognito) {\n this.container.classList.add(\"qaid-incognito\");\n }\n this.isUserProvidedContainer = true;\n } else {\n // Create our own fixed-position container\n this.container = document.createElement(\"div\");\n this.container.className = `qaid-widget qaid-auto-container qaid-${this.config.position}${this.config.incognito ? \" qaid-incognito\" : \"\"}`;\n this.container.style.zIndex = String(this.config.zIndex);\n // Apply custom offset\n const { x: offsetX, y: offsetY } = this.config.offset;\n if (this.config.position.includes(\"right\")) {\n this.container.style.right = `${offsetX}px`;\n } else {\n this.container.style.left = `${offsetX}px`;\n }\n if (this.config.position.includes(\"bottom\")) {\n this.container.style.bottom = `${offsetY}px`;\n } else {\n this.container.style.top = `${offsetY}px`;\n }\n document.body.appendChild(this.container);\n }\n\n // Apply custom fontFamily as inline style if not using default\n const customFont = this.config.fontFamily;\n const isCustomFont = customFont && customFont !== \"system-ui, -apple-system, sans-serif\";\n if (isCustomFont) {\n this.container.style.fontFamily = customFont;\n }\n\n // Determine button classes\n const useCustomClass = !!this.config.buttonClass;\n const btnBaseClass = useCustomClass\n ? `qaid-btn-structural ${this.config.buttonClass}`\n : \"qaid-btn\";\n\n // Determine tooltip text\n const defaultTooltip = this.config.skipTargeting\n ? \"Send feedback\"\n : \"Any feedback? Click to start, Esc to cancel\";\n const tooltipText = this.config.text.tooltip || defaultTooltip;\n\n // Create shared tooltip element\n const tooltip = document.createElement(\"div\");\n tooltip.className = \"qaid-tooltip-text\";\n tooltip.textContent = tooltipText;\n if (isCustomFont) {\n tooltip.style.fontFamily = customFont;\n }\n document.body.appendChild(tooltip);\n this.tooltipElement = tooltip;\n\n // Thumbs up button\n const upWrapper = document.createElement(\"div\");\n upWrapper.className = \"qaid-tooltip-wrapper\";\n\n const upBtn = document.createElement(\"button\");\n upBtn.type = \"button\";\n upBtn.className = useCustomClass ? `${btnBaseClass} qaid-btn-up` : \"qaid-btn qaid-btn-up\";\n upBtn.innerHTML = this.config.positiveIcon || THUMBS_UP_ICON;\n upBtn.addEventListener(\"click\", (e) => this.handleThumbClick(\"up\", e.currentTarget as HTMLElement));\n upBtn.addEventListener(\"mouseenter\", () => this.showTooltip(upBtn));\n upBtn.addEventListener(\"mouseleave\", () => this.hideTooltip());\n upWrapper.appendChild(upBtn);\n\n // Thumbs down button\n const downWrapper = document.createElement(\"div\");\n downWrapper.className = \"qaid-tooltip-wrapper\";\n\n const downBtn = document.createElement(\"button\");\n downBtn.type = \"button\";\n downBtn.className = useCustomClass ? `${btnBaseClass} qaid-btn-down` : \"qaid-btn qaid-btn-down\";\n downBtn.innerHTML = this.config.negativeIcon || THUMBS_DOWN_ICON;\n downBtn.addEventListener(\"click\", (e) => this.handleThumbClick(\"down\", e.currentTarget as HTMLElement));\n downBtn.addEventListener(\"mouseenter\", () => this.showTooltip(downBtn));\n downBtn.addEventListener(\"mouseleave\", () => this.hideTooltip());\n downWrapper.appendChild(downBtn);\n\n this.container.appendChild(upWrapper);\n this.container.appendChild(downWrapper);\n }\n\n private tooltipElement: HTMLElement | null = null;\n\n private showTooltip(anchor: HTMLElement): void {\n if (!this.tooltipElement) return;\n\n const tooltip = this.tooltipElement;\n const gap = 8;\n\n // Make visible to measure\n tooltip.style.visibility = \"hidden\";\n tooltip.classList.add(\"qaid-tooltip-visible\");\n\n const anchorRect = anchor.getBoundingClientRect();\n const tooltipRect = tooltip.getBoundingClientRect();\n const viewportWidth = window.innerWidth;\n const viewportHeight = window.innerHeight;\n\n let top: number;\n let left: number;\n\n // Always position below the button\n top = anchorRect.bottom + gap;\n\n // If below would overflow viewport, try above\n if (top + tooltipRect.height > viewportHeight - gap) {\n top = anchorRect.top - tooltipRect.height - gap;\n }\n\n // Align to left edge of anchor\n left = anchorRect.left;\n\n // Clamp to viewport horizontally\n if (left < gap) {\n left = gap;\n } else if (left + tooltipRect.width > viewportWidth - gap) {\n left = viewportWidth - tooltipRect.width - gap;\n }\n\n // Clamp to viewport vertically\n if (top < gap) {\n top = gap;\n } else if (top + tooltipRect.height > viewportHeight - gap) {\n top = viewportHeight - tooltipRect.height - gap;\n }\n\n tooltip.style.top = `${top}px`;\n tooltip.style.left = `${left}px`;\n tooltip.style.visibility = \"visible\";\n }\n\n private hideTooltip(): void {\n if (this.tooltipElement) {\n this.tooltipElement.classList.remove(\"qaid-tooltip-visible\");\n }\n }\n\n private handleThumbClick(type: \"up\" | \"down\", buttonEl: HTMLElement): void {\n if (this.config.skipTargeting) {\n this.submitDirectFeedback(type, buttonEl);\n } else {\n this.startTargeting(type);\n }\n }\n\n private submitDirectFeedback(type: \"up\" | \"down\", buttonEl: HTMLElement): void {\n this.feedbackData.feedbackType = type;\n this.feedbackData.elementSelector = null;\n this.feedbackData.elementText = null;\n\n // Use the button's position for modal placement\n const rect = buttonEl.getBoundingClientRect();\n this.selectedBounds = {\n x: rect.left,\n y: rect.top,\n width: rect.width,\n height: rect.height,\n clickX: rect.left + rect.width / 2,\n clickY: rect.top + rect.height / 2,\n visible: false,\n };\n this.submitFeedback();\n }\n\n private startTargeting(type: \"up\" | \"down\"): void {\n this.state = \"TARGETING\";\n this.feedbackData.feedbackType = type;\n this.feedbackData.elementSelector = null;\n this.feedbackData.elementText = null;\n this.selectedBounds.visible = false;\n\n // Add class to body\n document.body.classList.add(\"qaid-targeting\");\n if (type === \"up\") {\n document.body.classList.add(\"qaid-type-up\");\n } else {\n document.body.classList.remove(\"qaid-type-up\");\n }\n\n // Create targeting overlay\n this.createTargetingOverlay();\n\n // Add event listeners\n document.addEventListener(\"keydown\", this.boundKeyDown);\n }\n\n private createTargetingOverlay(): void {\n this.overlayContainer = document.createElement(\"div\");\n this.overlayContainer.className = `qaid-targeting-overlay qaid-type-${this.feedbackData.feedbackType}`;\n\n // Capture layer\n this.captureLayer = document.createElement(\"div\");\n this.captureLayer.className = \"qaid-capture-layer\";\n this.captureLayer.addEventListener(\"mousemove\", this.boundMouseMove);\n this.captureLayer.addEventListener(\"click\", this.boundClick);\n\n // Banner\n const banner = document.createElement(\"div\");\n banner.className = `qaid-banner qaid-banner-${this.feedbackData.feedbackType}`;\n // Apply custom font if configured\n const customFont = this.config.fontFamily;\n if (customFont && customFont !== \"system-ui, -apple-system, sans-serif\") {\n banner.style.fontFamily = customFont;\n }\n banner.innerHTML = `\n <span class=\"qaid-banner-text\">${this.config.text.bannerText}</span>\n <span class=\"qaid-banner-hint\">${this.config.text.bannerHint}</span>\n `;\n\n // Vignette\n const vignette = document.createElement(\"div\");\n vignette.className = \"qaid-vignette\";\n\n // Crosshairs\n this.crosshairH = document.createElement(\"div\");\n this.crosshairH.className = \"qaid-crosshair-h\";\n\n this.crosshairV = document.createElement(\"div\");\n this.crosshairV.className = \"qaid-crosshair-v\";\n\n // Scope\n this.scope = document.createElement(\"div\");\n this.scope.className = \"qaid-scope\";\n this.scope.innerHTML = `\n <div class=\"qaid-scope-ring\"></div>\n <div class=\"qaid-scope-ring-inner\"></div>\n <div class=\"qaid-scope-dot\"></div>\n `;\n\n this.overlayContainer.appendChild(this.captureLayer);\n this.overlayContainer.appendChild(banner);\n this.overlayContainer.appendChild(vignette);\n this.overlayContainer.appendChild(this.crosshairH);\n this.overlayContainer.appendChild(this.crosshairV);\n this.overlayContainer.appendChild(this.scope);\n\n document.body.appendChild(this.overlayContainer);\n }\n\n private handleKeyDown(e: KeyboardEvent): void {\n if (e.key === \"Escape\") {\n if (this.state === \"TARGETING\") {\n this.cancelTargeting();\n } else if (this.state === \"MODAL_OPEN\") {\n this.closeModal();\n }\n }\n }\n\n private handleMouseMove(e: MouseEvent): void {\n this.mousePos.x = e.clientX;\n this.mousePos.y = e.clientY;\n\n // Update crosshairs and scope\n if (this.crosshairH) {\n this.crosshairH.style.top = `${e.clientY}px`;\n }\n if (this.crosshairV) {\n this.crosshairV.style.left = `${e.clientX}px`;\n }\n if (this.scope) {\n this.scope.style.left = `${e.clientX}px`;\n this.scope.style.top = `${e.clientY}px`;\n }\n\n // Find element underneath\n if (this.captureLayer) {\n const elementUnder = getElementAtPointUnderOverlay(\n e.clientX,\n e.clientY,\n this.captureLayer\n );\n\n if (elementUnder && !isEmbedElement(elementUnder)) {\n if (this.lastHighlighted && this.lastHighlighted !== elementUnder) {\n this.lastHighlighted.classList.remove(\"qaid-highlight\");\n }\n elementUnder.classList.add(\"qaid-highlight\");\n this.lastHighlighted = elementUnder;\n } else if (this.lastHighlighted) {\n this.lastHighlighted.classList.remove(\"qaid-highlight\");\n this.lastHighlighted = null;\n }\n }\n }\n\n private handleClick(e: MouseEvent): void {\n if (!this.captureLayer) return;\n\n // Find element underneath\n const target = getElementAtPointUnderOverlay(\n e.clientX,\n e.clientY,\n this.captureLayer\n );\n\n if (!target || isEmbedElement(target)) {\n return;\n }\n\n // Remove highlight class before generating selector\n target.classList.remove(\"qaid-highlight\");\n\n // Get element bounds\n const bounds = getElementBounds(target, 8);\n this.selectedBounds = {\n ...bounds,\n clickX: e.clientX,\n clickY: e.clientY,\n visible: true,\n };\n\n const { selector, text } = generateElementInfo(target);\n this.feedbackData.elementSelector = selector;\n this.feedbackData.elementText = text;\n\n // Clean up highlights\n removeAllByClass(\"qaid-highlight\");\n\n // Remove targeting overlay\n this.removeTargetingOverlay();\n document.body.classList.remove(\"qaid-targeting\", \"qaid-type-up\");\n\n this.state = \"SELECTED\";\n\n // Show marker and submit feedback\n this.showSelectedMarker();\n this.submitFeedback();\n }\n\n private cancelTargeting(): void {\n // Clean up highlights\n removeAllByClass(\"qaid-highlight\");\n\n this.removeTargetingOverlay();\n document.body.classList.remove(\"qaid-targeting\", \"qaid-type-up\");\n document.removeEventListener(\"keydown\", this.boundKeyDown);\n\n this.state = \"IDLE\";\n this.feedbackData.feedbackType = null;\n this.feedbackData.elementSelector = null;\n this.feedbackData.elementText = null;\n this.selectedBounds.visible = false;\n this.lastHighlighted = null;\n }\n\n private removeTargetingOverlay(): void {\n if (this.overlayContainer) {\n this.overlayContainer.remove();\n this.overlayContainer = null;\n }\n this.captureLayer = null;\n this.crosshairH = null;\n this.crosshairV = null;\n this.scope = null;\n }\n\n private showSelectedMarker(): void {\n if (!this.selectedBounds.visible) return;\n\n this.marker = document.createElement(\"div\");\n this.marker.className = \"qaid-selected-marker\";\n this.marker.style.left = `${this.selectedBounds.x}px`;\n this.marker.style.top = `${this.selectedBounds.y}px`;\n this.marker.style.width = `${this.selectedBounds.width}px`;\n this.marker.style.height = `${this.selectedBounds.height}px`;\n this.marker.style.zIndex = String(this.config.zIndex + 1);\n\n document.body.appendChild(this.marker);\n }\n\n private hideSelectedMarker(): void {\n if (this.marker) {\n this.marker.remove();\n this.marker = null;\n }\n }\n\n private async submitFeedback(): Promise<void> {\n if (!this.feedbackData.feedbackType) return;\n\n // Capture screenshot if enabled and allowed by server\n let screenshot: string | null = null;\n const screenshotAllowed = this.allowedFeatures?.screenshots !== false;\n if (this.config.captureScreenshot && screenshotAllowed) {\n screenshot = await captureScreenshot(this.config.screenshotOptions);\n }\n\n // Include element bounds for server-side screenshot fallback\n // Only include if we have an element selector (element was targeted)\n const elementBounds = this.feedbackData.elementSelector\n ? {\n x: this.selectedBounds.x,\n y: this.selectedBounds.y,\n width: this.selectedBounds.width,\n height: this.selectedBounds.height,\n }\n : null;\n\n const payload: FeedbackPayload = {\n feedbackType: this.feedbackData.feedbackType,\n pageUrl: window.location.href,\n apiKey: this.config.apiKey || undefined,\n elementSelector: this.feedbackData.elementSelector,\n elementText: this.feedbackData.elementText,\n consoleErrors: [...this.feedbackData.consoleErrors],\n screenWidth: window.innerWidth,\n screenHeight: window.innerHeight,\n clickX: this.selectedBounds.clickX,\n clickY: this.selectedBounds.clickY,\n scrollX: window.scrollX,\n scrollY: window.scrollY,\n screenshot,\n visitorId: this.visitorId,\n // Include element bounds and user agent for server-side screenshot fallback\n elementBounds,\n userAgent: navigator.userAgent,\n };\n\n try {\n const response = await fetch(this.config.endpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(payload),\n });\n\n if (response.ok) {\n const data: FeedbackResponse = await response.json();\n this.feedbackId = data.id;\n }\n } catch (error) {\n console.error(\"Failed to submit feedback:\", error);\n }\n\n // Always show modal (even if API failed - useful for demos)\n this.state = \"MODAL_OPEN\";\n this.showModal();\n }\n\n private showModal(): void {\n // Create backdrop\n this.backdrop = document.createElement(\"div\");\n this.backdrop.className = \"qaid-backdrop\";\n this.backdrop.style.zIndex = String(this.config.zIndex + 2);\n this.backdrop.style.background = `rgba(0, 0, 0, ${this.config.backdropOpacity})`;\n this.backdrop.addEventListener(\"click\", () => this.closeModal());\n\n if (this.isMobile) {\n this.showBottomSheet();\n } else {\n this.showPositionedModal();\n }\n\n document.body.appendChild(this.backdrop);\n document.addEventListener(\"keydown\", this.boundKeyDown);\n }\n\n private showBottomSheet(): void {\n const sheet = document.createElement(\"div\");\n sheet.className = \"qaid-bottom-sheet\";\n sheet.style.zIndex = String(this.config.zIndex + 3);\n // Apply custom font if configured\n const customFont = this.config.fontFamily;\n if (customFont && customFont !== \"system-ui, -apple-system, sans-serif\") {\n sheet.style.fontFamily = customFont;\n }\n\n sheet.innerHTML = `\n <div class=\"qaid-bottom-sheet-content\">\n <div class=\"qaid-bottom-sheet-handle\"></div>\n ${this.getModalContent()}\n </div>\n `;\n\n document.body.appendChild(sheet);\n this.modalContainer = sheet;\n\n this.setupModalInteractions();\n }\n\n private showPositionedModal(): void {\n const { modal, arrow } = calculateModalAndArrowPosition(\n this.selectedBounds,\n window.innerWidth,\n window.innerHeight,\n {\n width: this.config.modalWidth,\n height: 280,\n arrowHeight: 12,\n gap: 8,\n viewportPadding: 16,\n }\n );\n\n this.modalContainer = document.createElement(\"div\");\n this.modalContainer.className = `qaid-modal-container qaid-${modal.position}`;\n this.modalContainer.style.top = `${modal.top}px`;\n this.modalContainer.style.left = `${modal.left}px`;\n this.modalContainer.style.zIndex = String(this.config.zIndex + 3);\n // Apply custom font if configured\n const customFont = this.config.fontFamily;\n if (customFont && customFont !== \"system-ui, -apple-system, sans-serif\") {\n this.modalContainer.style.fontFamily = customFont;\n }\n\n const arrowEl = document.createElement(\"div\");\n arrowEl.className = \"qaid-modal-arrow\";\n arrowEl.style.left = `${arrow.left}px`;\n\n const box = document.createElement(\"div\");\n box.className = \"qaid-modal-box\";\n box.innerHTML = this.getModalContent();\n\n this.modalContainer.appendChild(arrowEl);\n this.modalContainer.appendChild(box);\n document.body.appendChild(this.modalContainer);\n\n this.setupModalInteractions();\n }\n\n private getModalContent(): string {\n const isUp = this.feedbackData.feedbackType === \"up\";\n const positiveIcon = this.config.positiveIcon || THUMBS_UP_ICON;\n const negativeIcon = this.config.negativeIcon || THUMBS_DOWN_ICON;\n const toggleClass = this.config.buttonClass\n ? `qaid-type-toggle qaid-type-toggle-custom ${this.config.buttonClass} ${isUp ? \"qaid-btn-up\" : \"qaid-btn-down\"}`\n : `qaid-type-toggle ${isUp ? \"qaid-type-up\" : \"qaid-type-down\"}`;\n return `\n <div class=\"qaid-modal-header\">\n <button type=\"button\" class=\"${toggleClass}\" title=\"Click to switch\">\n ${isUp ? positiveIcon : negativeIcon}\n </button>\n <div class=\"qaid-modal-header-text\">\n <h3 class=\"qaid-modal-title\">${this.config.text.modalTitle}</h3>\n <p class=\"qaid-modal-subtitle\">${this.config.text.modalSubtitle}</p>\n </div>\n </div>\n <textarea class=\"qaid-textarea\" placeholder=\"${this.config.text.placeholder}\"></textarea>\n <div class=\"qaid-btn-row\">\n <button type=\"button\" class=\"qaid-btn-submit\">${this.config.text.skipButton}</button>\n </div>\n `;\n }\n\n private setupModalInteractions(): void {\n if (!this.modalContainer) return;\n\n const textarea =\n this.modalContainer.querySelector<HTMLTextAreaElement>(\".qaid-textarea\");\n const submitBtn =\n this.modalContainer.querySelector<HTMLButtonElement>(\".qaid-btn-submit\");\n if (textarea) {\n // Auto-focus\n setTimeout(() => textarea.focus(), 100);\n\n // Update button text based on content\n textarea.addEventListener(\"input\", () => {\n if (submitBtn) {\n submitBtn.textContent = textarea.value.trim() ? this.config.text.submitButton : this.config.text.skipButton;\n }\n });\n }\n\n if (submitBtn) {\n submitBtn.addEventListener(\"click\", () => {\n const message = textarea?.value.trim() || null;\n this.submitMessage(message);\n });\n }\n\n // Handle feedback type toggle\n const typeToggle = this.modalContainer.querySelector<HTMLButtonElement>(\".qaid-type-toggle\");\n if (typeToggle) {\n typeToggle.addEventListener(\"click\", () => {\n const newType = this.feedbackData.feedbackType === \"up\" ? \"down\" : \"up\";\n this.feedbackData.feedbackType = newType;\n\n // Update UI - use different classes depending on custom buttonClass\n if (this.config.buttonClass) {\n typeToggle.classList.toggle(\"qaid-btn-up\", newType === \"up\");\n typeToggle.classList.toggle(\"qaid-btn-down\", newType === \"down\");\n } else {\n typeToggle.classList.toggle(\"qaid-type-up\", newType === \"up\");\n typeToggle.classList.toggle(\"qaid-type-down\", newType === \"down\");\n }\n const positiveIcon = this.config.positiveIcon || THUMBS_UP_ICON;\n const negativeIcon = this.config.negativeIcon || THUMBS_DOWN_ICON;\n typeToggle.innerHTML = newType === \"up\" ? positiveIcon : negativeIcon;\n\n // Update feedback on server\n if (this.feedbackId) {\n fetch(`${this.config.endpoint}/${this.feedbackId}`, {\n method: \"PATCH\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ feedbackType: newType }),\n }).catch((err) => console.error(\"Failed to update feedback type:\", err));\n }\n });\n }\n }\n\n private async submitMessage(message: string | null): Promise<void> {\n if (this.feedbackId) {\n try {\n await fetch(`${this.config.endpoint}/${this.feedbackId}`, {\n method: \"PATCH\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ message }),\n });\n } catch (error) {\n console.error(\"Failed to submit feedback message:\", error);\n }\n // Clear feedbackId so closeModal doesn't send another PATCH\n this.feedbackId = null;\n }\n\n this.closeModal();\n }\n\n private closeModal(): void {\n // If modal is open and we're closing without submitting, still finalize\n if (this.state === \"MODAL_OPEN\" && this.feedbackId) {\n fetch(`${this.config.endpoint}/${this.feedbackId}`, {\n method: \"PATCH\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ message: null }),\n }).catch((err) => console.error(\"Failed to finalize feedback:\", err));\n }\n\n if (this.modalContainer) {\n this.modalContainer.remove();\n this.modalContainer = null;\n }\n if (this.backdrop) {\n this.backdrop.remove();\n this.backdrop = null;\n }\n\n document.removeEventListener(\"keydown\", this.boundKeyDown);\n this.hideSelectedMarker();\n\n this.state = \"IDLE\";\n this.feedbackId = null;\n this.feedbackData.feedbackType = null;\n this.feedbackData.elementSelector = null;\n this.feedbackData.elementText = null;\n this.selectedBounds.visible = false;\n }\n\n /**\n * Destroy the embed and clean up all resources\n */\n public destroy(): void {\n // Restore console.error\n if (this.consoleCapture) {\n this.consoleCapture.restore();\n this.consoleCapture = null;\n }\n\n // Remove event listeners\n window.removeEventListener(\"resize\", this.boundResize);\n document.removeEventListener(\"keydown\", this.boundKeyDown);\n\n // Clean up highlights\n removeAllByClass(\"qaid-highlight\");\n document.body.classList.remove(\"qaid-targeting\", \"qaid-type-up\");\n\n // Remove DOM elements\n if (this.container) {\n if (this.isUserProvidedContainer) {\n // Just clear the contents and remove our classes\n this.container.innerHTML = \"\";\n this.container.classList.remove(\"qaid-widget\", `qaid-${this.config.position}`);\n } else {\n this.container.remove();\n }\n this.container = null;\n }\n this.removeTargetingOverlay();\n this.hideSelectedMarker();\n if (this.modalContainer) {\n this.modalContainer.remove();\n this.modalContainer = null;\n }\n if (this.backdrop) {\n this.backdrop.remove();\n this.backdrop = null;\n }\n if (this.tooltipElement) {\n this.tooltipElement.remove();\n this.tooltipElement = null;\n }\n\n // Remove styles\n removeStyles();\n }\n}\n","/**\n * @qaiddev/thumbs-embed - Standalone Feedback Embed\n *\n * A zero-dependency feedback embed with element targeting.\n *\n * Usage via ES module:\n * ```typescript\n * import { FeedbackEmbed } from '@qaiddev/thumbs-embed';\n *\n * const embed = new FeedbackEmbed({\n * endpoint: '/api/feedback'\n * });\n * ```\n *\n * Usage via script tag:\n * ```html\n * <script src=\"feedback.umd.cjs\" data-endpoint=\"/api/feedback\"></script>\n * ```\n *\n * Or manually initialize:\n * ```html\n * <script src=\"feedback.umd.cjs\"></script>\n * <script>\n * const embed = new FeedbackEmbed.FeedbackEmbed({\n * endpoint: '/api/feedback'\n * });\n * </script>\n * ```\n */\n\nimport { FeedbackEmbed } from \"./embed\";\n\nexport { FeedbackEmbed };\nexport type {\n FeedbackConfig,\n ResolvedFeedbackConfig,\n ConsoleError,\n FeedbackData,\n SelectedBounds,\n FeedbackPayload,\n FeedbackResponse,\n FeedbackMessagePayload,\n} from \"./types\";\n\nimport type { FeedbackConfig } from \"./types\";\n\n/**\n * Parse JSON config from a separate script tag with type=\"application/json\"\n * Looks for: <script type=\"application/json\" data-feedback-config>{ ... }</script>\n */\nfunction parseJsonConfig(): Partial<FeedbackConfig> | null {\n const configScript = document.querySelector(\n 'script[type=\"application/json\"][data-feedback-config]'\n );\n if (!configScript) return null;\n\n const textContent = configScript.textContent?.trim();\n if (!textContent) return null;\n\n try {\n return JSON.parse(textContent);\n } catch {\n return null;\n }\n}\n\n/**\n * Parse config from data-* attributes (backward compatibility)\n */\nfunction parseDataAttributes(script: HTMLScriptElement): Partial<FeedbackConfig> | null {\n const endpoint = script.getAttribute(\"data-endpoint\");\n if (!endpoint) return null;\n\n const position = script.getAttribute(\"data-position\") as\n | \"bottom-right\"\n | \"bottom-left\"\n | \"top-right\"\n | \"top-left\"\n | null;\n const zIndex = script.getAttribute(\"data-zindex\");\n const positiveColor = script.getAttribute(\"data-positive-color\");\n const negativeColor = script.getAttribute(\"data-negative-color\");\n const markerColor = script.getAttribute(\"data-marker-color\");\n const container = script.getAttribute(\"data-container\");\n const buttonClass = script.getAttribute(\"data-button-class\");\n const skipTargeting = script.getAttribute(\"data-skip-targeting\");\n const incognito = script.getAttribute(\"data-incognito\");\n const buttonSize = script.getAttribute(\"data-button-size\") as\n | \"small\"\n | \"medium\"\n | \"large\"\n | null;\n const offsetX = script.getAttribute(\"data-offset-x\");\n const offsetY = script.getAttribute(\"data-offset-y\");\n const modalWidth = script.getAttribute(\"data-modal-width\");\n const backdropOpacity = script.getAttribute(\"data-backdrop-opacity\");\n const fontFamily = script.getAttribute(\"data-font-family\");\n const fontSize = script.getAttribute(\"data-font-size\");\n const tooltip = script.getAttribute(\"data-tooltip\");\n const bannerText = script.getAttribute(\"data-banner-text\");\n const bannerHint = script.getAttribute(\"data-banner-hint\");\n const modalTitle = script.getAttribute(\"data-modal-title\");\n const modalSubtitle = script.getAttribute(\"data-modal-subtitle\");\n const placeholder = script.getAttribute(\"data-placeholder\");\n const submitButton = script.getAttribute(\"data-submit-button\");\n const skipButton = script.getAttribute(\"data-skip-button\");\n const positiveIcon = script.getAttribute(\"data-positive-icon\");\n const negativeIcon = script.getAttribute(\"data-negative-icon\");\n\n return {\n endpoint,\n container: container ?? undefined,\n buttonClass: buttonClass ?? undefined,\n position: position ?? undefined,\n zIndex: zIndex ? parseInt(zIndex, 10) : undefined,\n skipTargeting: skipTargeting === \"true\" ? true : undefined,\n incognito: incognito === \"true\" ? true : undefined,\n buttonSize: buttonSize ?? undefined,\n offset: (offsetX || offsetY) ? {\n x: offsetX ? parseInt(offsetX, 10) : undefined,\n y: offsetY ? parseInt(offsetY, 10) : undefined,\n } : undefined,\n modalWidth: modalWidth ? parseInt(modalWidth, 10) : undefined,\n backdropOpacity: backdropOpacity ? parseFloat(backdropOpacity) : undefined,\n fontFamily: fontFamily ?? undefined,\n fontSize: fontSize ? parseInt(fontSize, 10) : undefined,\n colors: {\n positive: positiveColor ?? undefined,\n negative: negativeColor ?? undefined,\n marker: markerColor ?? undefined,\n },\n text: (tooltip || bannerText || bannerHint || modalTitle || modalSubtitle || placeholder || submitButton || skipButton) ? {\n tooltip: tooltip ?? undefined,\n bannerText: bannerText ?? undefined,\n bannerHint: bannerHint ?? undefined,\n modalTitle: modalTitle ?? undefined,\n modalSubtitle: modalSubtitle ?? undefined,\n placeholder: placeholder ?? undefined,\n submitButton: submitButton ?? undefined,\n skipButton: skipButton ?? undefined,\n } : undefined,\n positiveIcon: positiveIcon ?? undefined,\n negativeIcon: negativeIcon ?? undefined,\n };\n}\n\n// Auto-initialize from script tag\nif (typeof document !== \"undefined\") {\n const initFromScript = (): void => {\n const script = document.currentScript as HTMLScriptElement | null;\n\n // Try JSON config from separate script tag first, fall back to data-* attributes\n const jsonConfig = parseJsonConfig();\n const dataConfig = script ? parseDataAttributes(script) : null;\n const config = jsonConfig ?? dataConfig;\n\n if (config?.endpoint) {\n new FeedbackEmbed(config as FeedbackConfig);\n }\n };\n\n // Run immediately if DOM is ready, otherwise wait\n if (document.readyState === \"loading\") {\n document.addEventListener(\"DOMContentLoaded\", initFromScript);\n } else {\n initFromScript();\n }\n}\n"],"names":["THUMBS_UP_ICON","THUMBS_DOWN_ICON","stylesInjected","BUTTON_SIZES","ICON_SIZES","getLuminance","r","g","b","rs","gs","bs","c","parseColor","color","hex","fullHex","num","match","getContrastTextColor","bgColor","rgb","injectStyles","options","positiveColor","negativeColor","markerColor","buttonSize","modalWidth","backdropOpacity","fontFamily","fontSize","btnSize","iconSize","markerTextColor","cssVars","buttonStyles","style","baseStyles","removeStyles","DATA_ATTRS","findDataAttribute","element","stopAt","current","attr","value","getTruncatedText","maxLength","textContent","_a","getNthChildIndex","index","sibling","generateNthChildPath","path","tag","escapeCSSIdentifier","str","generateSelector","dataAttr","generateElementInfo","text","DEFAULT_DIMENSIONS","calculateVerticalPosition","bounds","viewportHeight","totalHeight","gap","viewportPadding","spaceAbove","spaceBelow","position","top","calculateHorizontalPosition","viewportWidth","left","calculateArrowPosition","modalLeft","minOffset","arrowWidth","arrowLeft","calculateModalPosition","dimensions","vertical","calculateModalAndArrowPosition","modal","MAX_MESSAGES","captureConsoleErrors","onError","errors","originalError","originalWarn","originalLog","capture","level","args","error","arg","isMobileViewport","breakpoint","getElementAtPointUnderOverlay","x","y","overlay","originalPointerEvents","isEmbedElement","getElementBounds","padding","rect","removeAllByClass","className","el","captureScreenshot","quality","maxWidth","maxHeight","stream","track","settings","video","resolve","checkFrame","srcWidth","srcHeight","scale","width","height","canvas","ctx","VISITOR_ID_KEY","getOrCreateVisitorId","visitorId","FeedbackEmbed","config","__publicField","_b","_c","_d","_e","_f","_g","_h","_i","_j","_k","_l","_m","_n","_o","_p","featuresUrl","response","userContainer","offsetX","offsetY","customFont","isCustomFont","useCustomClass","btnBaseClass","defaultTooltip","tooltipText","tooltip","upWrapper","upBtn","e","downWrapper","downBtn","anchor","anchorRect","tooltipRect","type","buttonEl","banner","vignette","elementUnder","target","selector","screenshot","screenshotAllowed","elementBounds","payload","data","sheet","arrow","arrowEl","box","isUp","positiveIcon","negativeIcon","textarea","submitBtn","message","typeToggle","newType","err","parseJsonConfig","configScript","parseDataAttributes","script","endpoint","zIndex","container","buttonClass","skipTargeting","incognito","bannerText","bannerHint","modalTitle","modalSubtitle","placeholder","submitButton","skipButton","initFromScript","jsonConfig","dataConfig"],"mappings":"oYAIO,MAAMA,EAAiB;AAAA;AAAA,QAIjBC,EAAmB;AAAA;AAAA,4xPCAhC,IAAIC,EAAiB,GAqBrB,MAAMC,EAAe,CACnB,MAAO,GACP,OAAQ,GACR,MAAO,EACT,EAEMC,EAAa,CACjB,MAAO,GACP,OAAQ,GACR,MAAO,EACT,EAMA,SAASC,EAAaC,EAAWC,EAAWC,EAAmB,CAC7D,KAAM,CAACC,EAAIC,EAAIC,CAAE,EAAI,CAACL,EAAGC,EAAGC,CAAC,EAAE,IAAKI,IAClCA,EAAIA,EAAI,IACDA,GAAK,OAAUA,EAAI,MAAQ,KAAK,KAAKA,EAAI,MAAS,MAAO,GAAG,EACpE,EACD,MAAO,OAASH,EAAK,MAASC,EAAK,MAASC,CAC9C,CAKA,SAASE,EAAWC,EAA2D,CAE7E,GAAIA,EAAM,WAAW,GAAG,EAAG,CACzB,MAAMC,EAAMD,EAAM,MAAM,CAAC,EACnBE,EAAUD,EAAI,SAAW,EAC3BA,EAAI,MAAM,EAAE,EAAE,IAAKH,GAAMA,EAAIA,CAAC,EAAE,KAAK,EAAE,EACvCG,EACEE,EAAM,SAASD,EAAS,EAAE,EAChC,MAAO,CACL,EAAIC,GAAO,GAAM,IACjB,EAAIA,GAAO,EAAK,IAChB,EAAGA,EAAM,GAAA,CAEb,CAEA,MAAMC,EAAQJ,EAAM,MAAM,gCAAgC,EAC1D,OAAII,EACK,CACL,EAAG,SAASA,EAAM,CAAC,CAAC,EACpB,EAAG,SAASA,EAAM,CAAC,CAAC,EACpB,EAAG,SAASA,EAAM,CAAC,CAAC,CAAA,EAGjB,IACT,CAKA,SAASC,EAAqBC,EAAyB,CACrD,MAAMC,EAAMR,EAAWO,CAAO,EAC9B,OAAKC,GACahB,EAAagB,EAAI,EAAGA,EAAI,EAAGA,EAAI,CAAC,EAE/B,GAAM,QAHR,OAInB,CAEO,SAASC,EAAaC,EAA+B,GAAU,CACpE,GAAIrB,EAAgB,OACpBA,EAAiB,GAEjB,KAAM,CACJ,cAAAsB,EAAgB,kBAChB,cAAAC,EAAgB,iBAChB,YAAAC,EAAc,UAEd,WAAAC,EAAa,SACb,WAAAC,EAAa,IACb,gBAAAC,EAAkB,GAClB,WAAAC,EAAa,uCACb,SAAAC,EAAW,EAAA,EACTR,EAEES,EAAU7B,EAAawB,CAAU,EACjCM,EAAW7B,EAAWuB,CAAU,EAGhCO,EAAkBf,EAAqBO,CAAW,EAGlDS,EAAU,yBAAyBX,CAAa,oBAAoBC,CAAa,kBAAkBC,CAAW,uBAAuBQ,CAAe,oBAAoBF,CAAO,uBAAuBC,CAAQ,yBAAyBL,CAAU,8BAA8BC,CAAe,uBAAuBC,CAAU,qBAAqBC,CAAQ,MAG5VK,EAAe,0nBAEfC,EAAQ,SAAS,cAAc,OAAO,EAC5CA,EAAM,GAAK,cACXA,EAAM,YAAcF,EAAUG,EAAaF,EAC3C,SAAS,KAAK,YAAYC,CAAK,CACjC,CAEO,SAASE,GAAqB,CACnC,MAAMF,EAAQ,SAAS,eAAe,aAAa,EAC/CA,IACFA,EAAM,OAAA,EACNnC,EAAiB,GAErB,CChIO,MAAMsC,EAAa,CAAC,YAAa,UAAW,cAAe,SAAS,EAWpE,SAASC,EACdC,EACAC,EAAkB,SAAS,KACZ,CACf,IAAIC,EAA0BF,EAE9B,KAAOE,GAAWA,IAAYD,GAAQ,CACpC,UAAWE,KAAQL,EAAY,CAC7B,MAAMM,EAAQF,EAAQ,aAAaC,CAAI,EACvC,GAAIC,EACF,MAAO,GAAGD,CAAI,KAAKC,CAAK,GAE5B,CACAF,EAAUA,EAAQ,aACpB,CAEA,OAAO,IACT,CAKO,SAASG,EAAiBL,EAAkBM,EAAY,IAAa,OAC1E,MAAMC,IAAcC,EAAAR,EAAQ,cAAR,YAAAQ,EAAqB,OAAO,MAAM,EAAGF,KAAc,GACvE,OAAOC,EAAY,SAAWD,EAAYC,EAAc,MAAQA,CAClE,CAKA,SAASE,EAAiBT,EAA0B,CAClD,IAAIU,EAAQ,EACRC,EAAUX,EAAQ,uBACtB,KAAOW,GACLD,IACAC,EAAUA,EAAQ,uBAEpB,OAAOD,CACT,CAOO,SAASE,EAAqBZ,EAA0B,CAC7D,MAAMa,EAAiB,CAAA,EACvB,IAAIX,EAA0BF,EAE9B,KAAOE,GAAWA,IAAY,SAAS,MAAQA,IAAY,SAAS,iBAAiB,CACnF,MAAMY,EAAMZ,EAAQ,QAAQ,YAAA,EACtBQ,EAAQD,EAAiBP,CAAO,EACtCW,EAAK,QAAQ,GAAGC,CAAG,cAAcJ,CAAK,GAAG,EACzCR,EAAUA,EAAQ,aACpB,CAEA,OAAOW,EAAK,OAAS,EAAI,UAAUA,EAAK,KAAK,KAAK,CAAC,GAAK,MAC1D,CAKA,SAASE,EAAoBC,EAAqB,CAChD,OAAOA,EAAI,QAAQ,wCAAyC,MAAM,CACpE,CAMO,SAASC,EAAiBjB,EAA0B,CAEzD,MAAMkB,EAAWnB,EAAkBC,CAAO,EAC1C,OAAIkB,EACK,IAAIA,CAAQ,IAIjBlB,EAAQ,GACH,IAAIe,EAAoBf,EAAQ,EAAE,CAAC,GAIrCY,EAAqBZ,CAAO,CACrC,CAKO,SAASmB,EAAoBnB,EAA+B,CACjE,MAAMoB,EAAOf,EAAiBL,CAAO,EAC/BkB,EAAWnB,EAAkBC,CAAO,EAG1C,MAAO,CAAE,SAFQiB,EAAiBjB,CAAO,EAEtB,KAAAoB,EAAM,SAAAF,CAAA,CAC3B,CCvFA,MAAMG,EAAsC,CAC1C,MAAO,IACP,OAAQ,IACR,YAAa,GACb,IAAK,EACL,gBAAiB,EACnB,EAKO,SAASC,EACdC,EACAC,EACAC,EACAC,EACAC,EAC8C,CAC9C,MAAMC,EAAaL,EAAO,EACpBM,EAAaL,GAAkBD,EAAO,EAAIA,EAAO,QAGvD,GAAIM,GAAcJ,EAAcC,EAC9B,MAAO,CACL,IAAKH,EAAO,EAAIA,EAAO,OAASG,EAChC,SAAU,OAAA,EAKd,GAAIE,GAAcH,EAAcC,EAC9B,MAAO,CACL,IAAKH,EAAO,EAAIE,EAAcC,EAC9B,SAAU,OAAA,EAKd,MAAMI,EAAWD,EAAaD,EAAa,QAAU,QACrD,IAAIG,EAEJ,OAAID,IAAa,QACfC,EAAM,KAAK,IACTR,EAAO,EAAIA,EAAO,OAASG,EAC3BF,EAAiBC,EAAcE,CAAA,EAGjCI,EAAM,KAAK,IAAIJ,EAAiBJ,EAAO,EAAIE,EAAcC,CAAG,EAGvD,CAAE,IAAAK,EAAK,SAAAD,CAAA,CAChB,CAKO,SAASE,GACdT,EACAU,EACA/C,EACAyC,EACQ,CAER,IAAIO,EADYX,EAAO,EAAIA,EAAO,MAAQ,EACrBrC,EAAa,EAGlC,OAAAgD,EAAO,KAAK,IAAIP,EAAiB,KAAK,IAAIO,EAAMD,EAAgB/C,EAAayC,CAAe,CAAC,EAEtFO,CACT,CAMO,SAASC,GACdZ,EACAa,EACAlD,EACAmD,EAAY,GACZC,EAAa,GACL,CAIR,MAAMC,EAFUhB,EAAO,OAEKa,EAAYE,EAAa,EAGrD,OAAO,KAAK,IAAID,EAAW,KAAK,IAAIE,EAAWrD,EAAamD,EAAYC,EAAa,CAAC,CAAC,CACzF,CAKO,SAASE,GACdjB,EACAU,EACAT,EACAiB,EAA8BpB,EACf,CACf,MAAMI,EAAcgB,EAAW,OAASA,EAAW,YAE7CC,EAAWpB,EACfC,EACAC,EACAC,EACAgB,EAAW,IACXA,EAAW,eAAA,EAGPP,EAAOF,GACXT,EACAU,EACAQ,EAAW,MACXA,EAAW,eAAA,EAGb,MAAO,CACL,IAAKC,EAAS,IACd,KAAAR,EACA,SAAUQ,EAAS,QAAA,CAEvB,CAKO,SAASC,GACdpB,EACAU,EACAT,EACAiB,EAA8BpB,EACkB,CAChD,MAAMuB,EAAQJ,GAAuBjB,EAAQU,EAAeT,EAAgBiB,CAAU,EAEhFF,EAAYJ,GAChBZ,EACAqB,EAAM,KACNH,EAAW,KAAA,EAGb,MAAO,CACL,MAAAG,EACA,MAAO,CAAE,KAAML,CAAA,CAAU,CAE7B,CCnKA,MAAMM,GAAe,GAYd,SAASC,GACdC,EACgB,CAChB,MAAMC,EAAyB,CAAA,EACzBC,EAAgB,QAAQ,MACxBC,EAAe,QAAQ,KACvBC,EAAc,QAAQ,IAEtBC,EAAU,CAACC,EAAqBC,IAAoB,CACxD,MAAMC,EAAsB,CAC1B,QAASD,EAAK,IAAKE,GAAQ,OAAOA,CAAG,CAAC,EAAE,KAAK,GAAG,EAChD,UAAW,KAAK,IAAA,EAChB,MAAAH,CAAA,EAIEL,EAAO,QAAUH,IACnBG,EAAO,MAAA,EAETA,EAAO,KAAKO,CAAK,EAGbR,GACFA,EAAQQ,CAAK,CAEjB,EAEA,eAAQ,MAAQ,YAAaD,EAAiB,CAC5CF,EAAQ,QAASE,CAAI,EACrBL,EAAc,MAAM,QAASK,CAAI,CACnC,EAEA,QAAQ,KAAO,YAAaA,EAAiB,CAC3CF,EAAQ,OAAQE,CAAI,EACpBJ,EAAa,MAAM,QAASI,CAAI,CAClC,EAEA,QAAQ,IAAM,YAAaA,EAAiB,CAC1CF,EAAQ,MAAOE,CAAI,EACnBH,EAAY,MAAM,QAASG,CAAI,CACjC,EAEO,CACL,OAAAN,EACA,QAAS,IAAM,CACb,QAAQ,MAAQC,EAChB,QAAQ,KAAOC,EACf,QAAQ,IAAMC,CAChB,CAAA,CAEJ,CC5BO,SAASM,GAAiBC,EAAa,IAAc,CAC1D,OAAO,OAAO,OAAW,KAAe,OAAO,WAAaA,CAC9D,CAKO,SAASC,EACdC,EACAC,EACAC,EACgB,CAChB,MAAMC,EAAwBD,EAAQ,MAAM,cAC5CA,EAAQ,MAAM,cAAgB,OAC9B,MAAM9D,EAAU,SAAS,iBAAiB4D,EAAGC,CAAC,EAC9C,OAAAC,EAAQ,MAAM,cAAgBC,EACvB/D,CACT,CAKO,SAASgE,EAAehE,EAAkC,CAC/D,OAAKA,EACE,CAAC,EACNA,EAAQ,QAAQ,cAAc,GAC9BA,EAAQ,QAAQ,yBAAyB,GACzCA,EAAQ,QAAQ,uBAAuB,GACvCA,EAAQ,QAAQ,oBAAoB,GALjB,EAOvB,CAKO,SAASiE,GACdjE,EACAkE,EAAU,EAC+C,CACzD,MAAMC,EAAOnE,EAAQ,sBAAA,EACrB,MAAO,CACL,EAAGmE,EAAK,KAAOD,EACf,EAAGC,EAAK,IAAMD,EACd,MAAOC,EAAK,MAAQD,EAAU,EAC9B,OAAQC,EAAK,OAASD,EAAU,CAAA,CAEpC,CAKO,SAASE,EAAiBC,EAAyB,CACxD,SAAS,iBAAiB,IAAIA,CAAS,EAAE,EAAE,QAASC,GAAO,CACzDA,EAAG,UAAU,OAAOD,CAAS,CAC/B,CAAC,CACH,CC7EA,eAAsBE,GACpB1F,EAA6B,GACL,OACxB,KAAM,CAAE,QAAA2F,EAAU,EAAK,SAAAC,EAAW,KAAM,UAAAC,EAAY,KAAQ7F,EAE5D,GAAI,CAEF,GAAI,GAAC2B,EAAA,UAAU,eAAV,MAAAA,EAAwB,iBAC3B,eAAQ,KAAK,kCAAkC,EACxC,KAIT,MAAMmE,EAAS,MAAM,UAAU,aAAa,gBAAgB,CAC1D,iBAAkB,GAClB,MAAO,CACL,eAAgB,SAAA,CAClB,CAC4B,EAGxBC,EAAQD,EAAO,eAAA,EAAiB,CAAC,EACjCE,EAAWD,EAAM,YAAA,EAGjBE,EAAQ,SAAS,cAAc,OAAO,EAC5CA,EAAM,UAAYH,EAClBG,EAAM,MAAQ,GAEd,MAAM,IAAI,QAAeC,GAAY,CACnCD,EAAM,iBAAmB,IAAM,CAC7BA,EAAM,KAAA,EACNC,EAAA,CACF,CACF,CAAC,EAGD,MAAM,IAAI,QAAeA,GAAY,CACnC,MAAMC,EAAa,IAAM,CACnBF,EAAM,YAAc,EACtBC,EAAA,EAEA,sBAAsBC,CAAU,CAEpC,EACAA,EAAA,CACF,CAAC,EAGD,MAAM,IAAI,QAASD,GAAY,WAAWA,EAAS,GAAG,CAAC,EAGvD,MAAME,EAAWJ,EAAS,OAASC,EAAM,WACnCI,EAAYL,EAAS,QAAUC,EAAM,YACrCK,EAAQ,KAAK,IAAIV,EAAWQ,EAAUP,EAAYQ,EAAW,CAAC,EAC9DE,EAAQ,KAAK,MAAMH,EAAWE,CAAK,EACnCE,EAAS,KAAK,MAAMH,EAAYC,CAAK,EAGrCG,EAAS,SAAS,cAAc,QAAQ,EAC9CA,EAAO,MAAQF,EACfE,EAAO,OAASD,EAEhB,MAAME,EAAMD,EAAO,WAAW,IAAI,EAClC,OAAKC,GAKLA,EAAI,UAAUT,EAAO,EAAG,EAAGM,EAAOC,CAAM,EAGxCT,EAAM,KAAA,EAECU,EAAO,UAAU,aAAcd,CAAO,IAT3CI,EAAM,KAAA,EACC,KASX,OAASrB,EAAO,CACd,eAAQ,KAAK,6BAA8BA,CAAK,EACzC,IACT,CACF,CC1EA,MAAMiC,EAAiB,kBAKvB,SAASC,IAA+B,CACtC,GAAI,CACF,IAAIC,EAAY,aAAa,QAAQF,CAAc,EACnD,OAAKE,IACHA,EAAY,OAAO,WAAA,EACnB,aAAa,QAAQF,EAAgBE,CAAS,GAEzCA,CACT,MAAQ,CAEN,OAAO,OAAO,WAAA,CAChB,CACF,CAKO,MAAMC,CAAc,CAgDzB,YAAYC,EAAwB,CA/C5BC,EAAA,eACAA,EAAA,aAAoB,QACpBA,EAAA,oBAA6B,CACnC,aAAc,KACd,gBAAiB,KACjB,YAAa,KACb,cAAe,CAAA,CAAC,GAEVA,EAAA,sBAAiC,CACvC,EAAG,EACH,EAAG,EACH,MAAO,EACP,OAAQ,EACR,OAAQ,EACR,OAAQ,EACR,QAAS,EAAA,GAEHA,EAAA,kBAA4B,MAC5BA,EAAA,gBAAW,CAAE,EAAG,EAAG,EAAG,CAAA,GACtBA,EAAA,uBAAkC,MAClCA,EAAA,gBAAW,IACXA,EAAA,kBAGAA,EAAA,sBAAwC,MAGxCA,EAAA,uBAA4E,MAG5EA,EAAA,iBAAmC,MACnCA,EAAA,+BAA0B,IAC1BA,EAAA,wBAA0C,MAC1CA,EAAA,oBAAsC,MACtCA,EAAA,kBAAoC,MACpCA,EAAA,kBAAoC,MACpCA,EAAA,aAA+B,MAC/BA,EAAA,cAAgC,MAChCA,EAAA,sBAAwC,MACxCA,EAAA,gBAAkC,MAGlCA,EAAA,qBACAA,EAAA,uBACAA,EAAA,mBACAA,EAAA,oBAgNAA,EAAA,sBAAqC,0CA7M3C,KAAK,OAAS,CACZ,SAAUD,EAAO,SACjB,OAAQA,EAAO,QAAU,GACzB,UAAWA,EAAO,WAAa,GAC/B,YAAaA,EAAO,aAAe,GACnC,SAAUA,EAAO,UAAY,eAC7B,OAAQ,CACN,IAAGpF,EAAAoF,EAAO,SAAP,YAAApF,EAAe,IAAK,GACvB,IAAGsF,EAAAF,EAAO,SAAP,YAAAE,EAAe,IAAK,EAAA,EAEzB,OAAQF,EAAO,QAAU,GACzB,cAAeA,EAAO,eAAiB,GACvC,OAAQ,CACN,WAAUG,EAAAH,EAAO,SAAP,YAAAG,EAAe,WAAY,kBACrC,WAAUC,EAAAJ,EAAO,SAAP,YAAAI,EAAe,WAAY,iBACrC,SAAQC,EAAAL,EAAO,SAAP,YAAAK,EAAe,SAAU,SAAA,EAEnC,WAAYL,EAAO,YAAc,SACjC,KAAM,CACJ,UAASM,EAAAN,EAAO,OAAP,YAAAM,EAAa,UAAW,GACjC,aAAYC,EAAAP,EAAO,OAAP,YAAAO,EAAa,aAAc,uDACvC,aAAYC,EAAAR,EAAO,OAAP,YAAAQ,EAAa,aAAc,2BACvC,aAAYC,EAAAT,EAAO,OAAP,YAAAS,EAAa,aAAc,+BACvC,gBAAeC,EAAAV,EAAO,OAAP,YAAAU,EAAa,gBAAiB,8EAC7C,cAAaC,EAAAX,EAAO,OAAP,YAAAW,EAAa,cAAe,kDACzC,eAAcC,EAAAZ,EAAO,OAAP,YAAAY,EAAa,eAAgB,SAC3C,aAAYC,EAAAb,EAAO,OAAP,YAAAa,EAAa,aAAc,MAAA,EAEzC,WAAYb,EAAO,YAAc,IACjC,gBAAiBA,EAAO,iBAAmB,GAC3C,WAAYA,EAAO,YAAc,uCACjC,SAAUA,EAAO,UAAY,GAC7B,kBAAmBA,EAAO,mBAAqB,GAC/C,kBAAmB,CACjB,UAASc,EAAAd,EAAO,oBAAP,YAAAc,EAA0B,UAAW,GAC9C,WAAUC,EAAAf,EAAO,oBAAP,YAAAe,EAA0B,WAAY,KAChD,YAAWC,EAAAhB,EAAO,oBAAP,YAAAgB,EAA0B,YAAa,GAAA,EAEpD,UAAWhB,EAAO,WAAa,GAC/B,aAAcA,EAAO,cAAgB,GACrC,aAAcA,EAAO,cAAgB,EAAA,EAIvC,KAAK,aAAe,KAAK,cAAc,KAAK,IAAI,EAChD,KAAK,eAAiB,KAAK,gBAAgB,KAAK,IAAI,EACpD,KAAK,WAAa,KAAK,YAAY,KAAK,IAAI,EAC5C,KAAK,YAAc,KAAK,aAAa,KAAK,IAAI,EAG9C,KAAK,UAAYH,GAAA,EAEjB,KAAK,KAAA,CACP,CAEQ,MAAa,CAEnB7G,EAAa,CACX,cAAe,KAAK,OAAO,OAAO,SAClC,cAAe,KAAK,OAAO,OAAO,SAClC,YAAa,KAAK,OAAO,OAAO,OAChC,iBAAkB,CAAC,CAAC,KAAK,OAAO,YAChC,WAAY,KAAK,OAAO,WACxB,WAAY,KAAK,OAAO,WACxB,gBAAiB,KAAK,OAAO,gBAC7B,WAAY,KAAK,OAAO,WACxB,SAAU,KAAK,OAAO,QAAA,CACvB,EAGD,KAAK,YAAA,EACL,OAAO,iBAAiB,SAAU,KAAK,WAAW,EAGlD,KAAK,YAAA,EAGL,KAAK,qBAAA,EAGL,KAAK,eAAiBkE,GAAsBS,GAAU,OACpD,KAAK,aAAa,gBAAgB/C,EAAA,KAAK,iBAAL,YAAAA,EAAqB,SAAU,CAAA,CACnE,CAAC,EACD,KAAK,aAAa,cAAgB,KAAK,eAAe,MACxD,CAEA,MAAc,sBAAsC,CAClD,GAAK,KAAK,OAAO,OAEjB,GAAI,CAGF,MAAMqG,EAAc,GADJ,KAAK,OAAO,SAAS,QAAQ,iBAAkB,EAAE,CACnC,oBAAoB,mBAAmB,KAAK,OAAO,MAAM,CAAC,GAElFC,EAAW,MAAM,MAAMD,CAAW,EACpCC,EAAS,KACX,KAAK,gBAAkB,MAAMA,EAAS,KAAA,EAE1C,MAAQ,CAER,CACF,CAEQ,aAAoB,CAC1B,KAAK,SAAWrD,GAAA,CAClB,CAEQ,cAAqB,CAC3B,KAAK,YAAA,CACP,CAEQ,aAAoB,CAE1B,IAAIsD,EAAoC,KAKxC,GAJI,KAAK,OAAO,YACdA,EAAgB,SAAS,cAAc,KAAK,OAAO,SAAS,GAG1DA,EAEF,KAAK,UAAYA,EACjB,KAAK,UAAU,UAAU,IAAI,cAAe,QAAQ,KAAK,OAAO,QAAQ,EAAE,EACtE,KAAK,OAAO,WACd,KAAK,UAAU,UAAU,IAAI,gBAAgB,EAE/C,KAAK,wBAA0B,OAC1B,CAEL,KAAK,UAAY,SAAS,cAAc,KAAK,EAC7C,KAAK,UAAU,UAAY,wCAAwC,KAAK,OAAO,QAAQ,GAAG,KAAK,OAAO,UAAY,kBAAoB,EAAE,GACxI,KAAK,UAAU,MAAM,OAAS,OAAO,KAAK,OAAO,MAAM,EAEvD,KAAM,CAAE,EAAGC,EAAS,EAAGC,GAAY,KAAK,OAAO,OAC3C,KAAK,OAAO,SAAS,SAAS,OAAO,EACvC,KAAK,UAAU,MAAM,MAAQ,GAAGD,CAAO,KAEvC,KAAK,UAAU,MAAM,KAAO,GAAGA,CAAO,KAEpC,KAAK,OAAO,SAAS,SAAS,QAAQ,EACxC,KAAK,UAAU,MAAM,OAAS,GAAGC,CAAO,KAExC,KAAK,UAAU,MAAM,IAAM,GAAGA,CAAO,KAEvC,SAAS,KAAK,YAAY,KAAK,SAAS,CAC1C,CAGA,MAAMC,EAAa,KAAK,OAAO,WACzBC,EAAeD,GAAcA,IAAe,uCAC9CC,IACF,KAAK,UAAU,MAAM,WAAaD,GAIpC,MAAME,EAAiB,CAAC,CAAC,KAAK,OAAO,YAC/BC,EAAeD,EACjB,uBAAuB,KAAK,OAAO,WAAW,GAC9C,WAGEE,EAAiB,KAAK,OAAO,cAC/B,gBACA,8CACEC,EAAc,KAAK,OAAO,KAAK,SAAWD,EAG1CE,EAAU,SAAS,cAAc,KAAK,EAC5CA,EAAQ,UAAY,oBACpBA,EAAQ,YAAcD,EAClBJ,IACFK,EAAQ,MAAM,WAAaN,GAE7B,SAAS,KAAK,YAAYM,CAAO,EACjC,KAAK,eAAiBA,EAGtB,MAAMC,EAAY,SAAS,cAAc,KAAK,EAC9CA,EAAU,UAAY,uBAEtB,MAAMC,EAAQ,SAAS,cAAc,QAAQ,EAC7CA,EAAM,KAAO,SACbA,EAAM,UAAYN,EAAiB,GAAGC,CAAY,eAAiB,uBACnEK,EAAM,UAAY,KAAK,OAAO,cAAgBpK,EAC9CoK,EAAM,iBAAiB,QAAUC,GAAM,KAAK,iBAAiB,KAAMA,EAAE,aAA4B,CAAC,EAClGD,EAAM,iBAAiB,aAAc,IAAM,KAAK,YAAYA,CAAK,CAAC,EAClEA,EAAM,iBAAiB,aAAc,IAAM,KAAK,aAAa,EAC7DD,EAAU,YAAYC,CAAK,EAG3B,MAAME,EAAc,SAAS,cAAc,KAAK,EAChDA,EAAY,UAAY,uBAExB,MAAMC,EAAU,SAAS,cAAc,QAAQ,EAC/CA,EAAQ,KAAO,SACfA,EAAQ,UAAYT,EAAiB,GAAGC,CAAY,iBAAmB,yBACvEQ,EAAQ,UAAY,KAAK,OAAO,cAAgBtK,EAChDsK,EAAQ,iBAAiB,QAAUF,GAAM,KAAK,iBAAiB,OAAQA,EAAE,aAA4B,CAAC,EACtGE,EAAQ,iBAAiB,aAAc,IAAM,KAAK,YAAYA,CAAO,CAAC,EACtEA,EAAQ,iBAAiB,aAAc,IAAM,KAAK,aAAa,EAC/DD,EAAY,YAAYC,CAAO,EAE/B,KAAK,UAAU,YAAYJ,CAAS,EACpC,KAAK,UAAU,YAAYG,CAAW,CACxC,CAIQ,YAAYE,EAA2B,CAC7C,GAAI,CAAC,KAAK,eAAgB,OAE1B,MAAMN,EAAU,KAAK,eACf9F,EAAM,EAGZ8F,EAAQ,MAAM,WAAa,SAC3BA,EAAQ,UAAU,IAAI,sBAAsB,EAE5C,MAAMO,EAAaD,EAAO,sBAAA,EACpBE,EAAcR,EAAQ,sBAAA,EACtBvF,EAAgB,OAAO,WACvBT,EAAiB,OAAO,YAE9B,IAAIO,EACAG,EAGJH,EAAMgG,EAAW,OAASrG,EAGtBK,EAAMiG,EAAY,OAASxG,EAAiBE,IAC9CK,EAAMgG,EAAW,IAAMC,EAAY,OAAStG,GAI9CQ,EAAO6F,EAAW,KAGd7F,EAAOR,EACTQ,EAAOR,EACEQ,EAAO8F,EAAY,MAAQ/F,EAAgBP,IACpDQ,EAAOD,EAAgB+F,EAAY,MAAQtG,GAIzCK,EAAML,EACRK,EAAML,EACGK,EAAMiG,EAAY,OAASxG,EAAiBE,IACrDK,EAAMP,EAAiBwG,EAAY,OAAStG,GAG9C8F,EAAQ,MAAM,IAAM,GAAGzF,CAAG,KAC1ByF,EAAQ,MAAM,KAAO,GAAGtF,CAAI,KAC5BsF,EAAQ,MAAM,WAAa,SAC7B,CAEQ,aAAoB,CACtB,KAAK,gBACP,KAAK,eAAe,UAAU,OAAO,sBAAsB,CAE/D,CAEQ,iBAAiBS,EAAqBC,EAA6B,CACrE,KAAK,OAAO,cACd,KAAK,qBAAqBD,EAAMC,CAAQ,EAExC,KAAK,eAAeD,CAAI,CAE5B,CAEQ,qBAAqBA,EAAqBC,EAA6B,CAC7E,KAAK,aAAa,aAAeD,EACjC,KAAK,aAAa,gBAAkB,KACpC,KAAK,aAAa,YAAc,KAGhC,MAAM9D,EAAO+D,EAAS,sBAAA,EACtB,KAAK,eAAiB,CACpB,EAAG/D,EAAK,KACR,EAAGA,EAAK,IACR,MAAOA,EAAK,MACZ,OAAQA,EAAK,OACb,OAAQA,EAAK,KAAOA,EAAK,MAAQ,EACjC,OAAQA,EAAK,IAAMA,EAAK,OAAS,EACjC,QAAS,EAAA,EAEX,KAAK,eAAA,CACP,CAEQ,eAAe8D,EAA2B,CAChD,KAAK,MAAQ,YACb,KAAK,aAAa,aAAeA,EACjC,KAAK,aAAa,gBAAkB,KACpC,KAAK,aAAa,YAAc,KAChC,KAAK,eAAe,QAAU,GAG9B,SAAS,KAAK,UAAU,IAAI,gBAAgB,EACxCA,IAAS,KACX,SAAS,KAAK,UAAU,IAAI,cAAc,EAE1C,SAAS,KAAK,UAAU,OAAO,cAAc,EAI/C,KAAK,uBAAA,EAGL,SAAS,iBAAiB,UAAW,KAAK,YAAY,CACxD,CAEQ,wBAA+B,CACrC,KAAK,iBAAmB,SAAS,cAAc,KAAK,EACpD,KAAK,iBAAiB,UAAY,oCAAoC,KAAK,aAAa,YAAY,GAGpG,KAAK,aAAe,SAAS,cAAc,KAAK,EAChD,KAAK,aAAa,UAAY,qBAC9B,KAAK,aAAa,iBAAiB,YAAa,KAAK,cAAc,EACnE,KAAK,aAAa,iBAAiB,QAAS,KAAK,UAAU,EAG3D,MAAME,EAAS,SAAS,cAAc,KAAK,EAC3CA,EAAO,UAAY,2BAA2B,KAAK,aAAa,YAAY,GAE5E,MAAMjB,EAAa,KAAK,OAAO,WAC3BA,GAAcA,IAAe,yCAC/BiB,EAAO,MAAM,WAAajB,GAE5BiB,EAAO,UAAY;AAAA,uCACgB,KAAK,OAAO,KAAK,UAAU;AAAA,uCAC3B,KAAK,OAAO,KAAK,UAAU;AAAA,MAI9D,MAAMC,EAAW,SAAS,cAAc,KAAK,EAC7CA,EAAS,UAAY,gBAGrB,KAAK,WAAa,SAAS,cAAc,KAAK,EAC9C,KAAK,WAAW,UAAY,mBAE5B,KAAK,WAAa,SAAS,cAAc,KAAK,EAC9C,KAAK,WAAW,UAAY,mBAG5B,KAAK,MAAQ,SAAS,cAAc,KAAK,EACzC,KAAK,MAAM,UAAY,aACvB,KAAK,MAAM,UAAY;AAAA;AAAA;AAAA;AAAA,MAMvB,KAAK,iBAAiB,YAAY,KAAK,YAAY,EACnD,KAAK,iBAAiB,YAAYD,CAAM,EACxC,KAAK,iBAAiB,YAAYC,CAAQ,EAC1C,KAAK,iBAAiB,YAAY,KAAK,UAAU,EACjD,KAAK,iBAAiB,YAAY,KAAK,UAAU,EACjD,KAAK,iBAAiB,YAAY,KAAK,KAAK,EAE5C,SAAS,KAAK,YAAY,KAAK,gBAAgB,CACjD,CAEQ,cAAcT,EAAwB,CACxCA,EAAE,MAAQ,WACR,KAAK,QAAU,YACjB,KAAK,gBAAA,EACI,KAAK,QAAU,cACxB,KAAK,WAAA,EAGX,CAEQ,gBAAgBA,EAAqB,CAiB3C,GAhBA,KAAK,SAAS,EAAIA,EAAE,QACpB,KAAK,SAAS,EAAIA,EAAE,QAGhB,KAAK,aACP,KAAK,WAAW,MAAM,IAAM,GAAGA,EAAE,OAAO,MAEtC,KAAK,aACP,KAAK,WAAW,MAAM,KAAO,GAAGA,EAAE,OAAO,MAEvC,KAAK,QACP,KAAK,MAAM,MAAM,KAAO,GAAGA,EAAE,OAAO,KACpC,KAAK,MAAM,MAAM,IAAM,GAAGA,EAAE,OAAO,MAIjC,KAAK,aAAc,CACrB,MAAMU,EAAe1E,EACnBgE,EAAE,QACFA,EAAE,QACF,KAAK,YAAA,EAGHU,GAAgB,CAACrE,EAAeqE,CAAY,GAC1C,KAAK,iBAAmB,KAAK,kBAAoBA,GACnD,KAAK,gBAAgB,UAAU,OAAO,gBAAgB,EAExDA,EAAa,UAAU,IAAI,gBAAgB,EAC3C,KAAK,gBAAkBA,GACd,KAAK,kBACd,KAAK,gBAAgB,UAAU,OAAO,gBAAgB,EACtD,KAAK,gBAAkB,KAE3B,CACF,CAEQ,YAAYV,EAAqB,CACvC,GAAI,CAAC,KAAK,aAAc,OAGxB,MAAMW,EAAS3E,EACbgE,EAAE,QACFA,EAAE,QACF,KAAK,YAAA,EAGP,GAAI,CAACW,GAAUtE,EAAesE,CAAM,EAClC,OAIFA,EAAO,UAAU,OAAO,gBAAgB,EAGxC,MAAM/G,EAAS0C,GAAiBqE,EAAQ,CAAC,EACzC,KAAK,eAAiB,CACpB,GAAG/G,EACH,OAAQoG,EAAE,QACV,OAAQA,EAAE,QACV,QAAS,EAAA,EAGX,KAAM,CAAE,SAAAY,EAAU,KAAAnH,GAASD,EAAoBmH,CAAM,EACrD,KAAK,aAAa,gBAAkBC,EACpC,KAAK,aAAa,YAAcnH,EAGhCgD,EAAiB,gBAAgB,EAGjC,KAAK,uBAAA,EACL,SAAS,KAAK,UAAU,OAAO,iBAAkB,cAAc,EAE/D,KAAK,MAAQ,WAGb,KAAK,mBAAA,EACL,KAAK,eAAA,CACP,CAEQ,iBAAwB,CAE9BA,EAAiB,gBAAgB,EAEjC,KAAK,uBAAA,EACL,SAAS,KAAK,UAAU,OAAO,iBAAkB,cAAc,EAC/D,SAAS,oBAAoB,UAAW,KAAK,YAAY,EAEzD,KAAK,MAAQ,OACb,KAAK,aAAa,aAAe,KACjC,KAAK,aAAa,gBAAkB,KACpC,KAAK,aAAa,YAAc,KAChC,KAAK,eAAe,QAAU,GAC9B,KAAK,gBAAkB,IACzB,CAEQ,wBAA+B,CACjC,KAAK,mBACP,KAAK,iBAAiB,OAAA,EACtB,KAAK,iBAAmB,MAE1B,KAAK,aAAe,KACpB,KAAK,WAAa,KAClB,KAAK,WAAa,KAClB,KAAK,MAAQ,IACf,CAEQ,oBAA2B,CAC5B,KAAK,eAAe,UAEzB,KAAK,OAAS,SAAS,cAAc,KAAK,EAC1C,KAAK,OAAO,UAAY,uBACxB,KAAK,OAAO,MAAM,KAAO,GAAG,KAAK,eAAe,CAAC,KACjD,KAAK,OAAO,MAAM,IAAM,GAAG,KAAK,eAAe,CAAC,KAChD,KAAK,OAAO,MAAM,MAAQ,GAAG,KAAK,eAAe,KAAK,KACtD,KAAK,OAAO,MAAM,OAAS,GAAG,KAAK,eAAe,MAAM,KACxD,KAAK,OAAO,MAAM,OAAS,OAAO,KAAK,OAAO,OAAS,CAAC,EAExD,SAAS,KAAK,YAAY,KAAK,MAAM,EACvC,CAEQ,oBAA2B,CAC7B,KAAK,SACP,KAAK,OAAO,OAAA,EACZ,KAAK,OAAS,KAElB,CAEA,MAAc,gBAAgC,OAC5C,GAAI,CAAC,KAAK,aAAa,aAAc,OAGrC,IAAIoE,EAA4B,KAChC,MAAMC,IAAoBjI,EAAA,KAAK,kBAAL,YAAAA,EAAsB,eAAgB,GAC5D,KAAK,OAAO,mBAAqBiI,IACnCD,EAAa,MAAMjE,GAAkB,KAAK,OAAO,iBAAiB,GAKpE,MAAMmE,EAAgB,KAAK,aAAa,gBACpC,CACE,EAAG,KAAK,eAAe,EACvB,EAAG,KAAK,eAAe,EACvB,MAAO,KAAK,eAAe,MAC3B,OAAQ,KAAK,eAAe,MAAA,EAE9B,KAEEC,EAA2B,CAC/B,aAAc,KAAK,aAAa,aAChC,QAAS,OAAO,SAAS,KACzB,OAAQ,KAAK,OAAO,QAAU,OAC9B,gBAAiB,KAAK,aAAa,gBACnC,YAAa,KAAK,aAAa,YAC/B,cAAe,CAAC,GAAG,KAAK,aAAa,aAAa,EAClD,YAAa,OAAO,WACpB,aAAc,OAAO,YACrB,OAAQ,KAAK,eAAe,OAC5B,OAAQ,KAAK,eAAe,OAC5B,QAAS,OAAO,QAChB,QAAS,OAAO,QAChB,WAAAH,EACA,UAAW,KAAK,UAEhB,cAAAE,EACA,UAAW,UAAU,SAAA,EAGvB,GAAI,CACF,MAAM5B,EAAW,MAAM,MAAM,KAAK,OAAO,SAAU,CACjD,OAAQ,OACR,QAAS,CAAE,eAAgB,kBAAA,EAC3B,KAAM,KAAK,UAAU6B,CAAO,CAAA,CAC7B,EAED,GAAI7B,EAAS,GAAI,CACf,MAAM8B,EAAyB,MAAM9B,EAAS,KAAA,EAC9C,KAAK,WAAa8B,EAAK,EACzB,CACF,OAASrF,EAAO,CACd,QAAQ,MAAM,6BAA8BA,CAAK,CACnD,CAGA,KAAK,MAAQ,aACb,KAAK,UAAA,CACP,CAEQ,WAAkB,CAExB,KAAK,SAAW,SAAS,cAAc,KAAK,EAC5C,KAAK,SAAS,UAAY,gBAC1B,KAAK,SAAS,MAAM,OAAS,OAAO,KAAK,OAAO,OAAS,CAAC,EAC1D,KAAK,SAAS,MAAM,WAAa,iBAAiB,KAAK,OAAO,eAAe,IAC7E,KAAK,SAAS,iBAAiB,QAAS,IAAM,KAAK,YAAY,EAE3D,KAAK,SACP,KAAK,gBAAA,EAEL,KAAK,oBAAA,EAGP,SAAS,KAAK,YAAY,KAAK,QAAQ,EACvC,SAAS,iBAAiB,UAAW,KAAK,YAAY,CACxD,CAEQ,iBAAwB,CAC9B,MAAMsF,EAAQ,SAAS,cAAc,KAAK,EAC1CA,EAAM,UAAY,oBAClBA,EAAM,MAAM,OAAS,OAAO,KAAK,OAAO,OAAS,CAAC,EAElD,MAAM3B,EAAa,KAAK,OAAO,WAC3BA,GAAcA,IAAe,yCAC/B2B,EAAM,MAAM,WAAa3B,GAG3B2B,EAAM,UAAY;AAAA;AAAA;AAAA,UAGZ,KAAK,iBAAiB;AAAA;AAAA,MAI5B,SAAS,KAAK,YAAYA,CAAK,EAC/B,KAAK,eAAiBA,EAEtB,KAAK,uBAAA,CACP,CAEQ,qBAA4B,CAClC,KAAM,CAAE,MAAAjG,EAAO,MAAAkG,CAAA,EAAUnG,GACvB,KAAK,eACL,OAAO,WACP,OAAO,YACP,CACE,MAAO,KAAK,OAAO,WACnB,OAAQ,IACR,YAAa,GACb,IAAK,EACL,gBAAiB,EAAA,CACnB,EAGF,KAAK,eAAiB,SAAS,cAAc,KAAK,EAClD,KAAK,eAAe,UAAY,6BAA6BC,EAAM,QAAQ,GAC3E,KAAK,eAAe,MAAM,IAAM,GAAGA,EAAM,GAAG,KAC5C,KAAK,eAAe,MAAM,KAAO,GAAGA,EAAM,IAAI,KAC9C,KAAK,eAAe,MAAM,OAAS,OAAO,KAAK,OAAO,OAAS,CAAC,EAEhE,MAAMsE,EAAa,KAAK,OAAO,WAC3BA,GAAcA,IAAe,yCAC/B,KAAK,eAAe,MAAM,WAAaA,GAGzC,MAAM6B,EAAU,SAAS,cAAc,KAAK,EAC5CA,EAAQ,UAAY,mBACpBA,EAAQ,MAAM,KAAO,GAAGD,EAAM,IAAI,KAElC,MAAME,EAAM,SAAS,cAAc,KAAK,EACxCA,EAAI,UAAY,iBAChBA,EAAI,UAAY,KAAK,gBAAA,EAErB,KAAK,eAAe,YAAYD,CAAO,EACvC,KAAK,eAAe,YAAYC,CAAG,EACnC,SAAS,KAAK,YAAY,KAAK,cAAc,EAE7C,KAAK,uBAAA,CACP,CAEQ,iBAA0B,CAChC,MAAMC,EAAO,KAAK,aAAa,eAAiB,KAC1CC,EAAe,KAAK,OAAO,cAAgB5L,EAC3C6L,EAAe,KAAK,OAAO,cAAgB5L,EAIjD,MAAO;AAAA;AAAA,uCAHa,KAAK,OAAO,YAC5B,4CAA4C,KAAK,OAAO,WAAW,IAAI0L,EAAO,cAAgB,eAAe,GAC7G,oBAAoBA,EAAO,eAAiB,gBAAgB,EAGlB;AAAA,YACtCA,EAAOC,EAAeC,CAAY;AAAA;AAAA;AAAA,yCAGL,KAAK,OAAO,KAAK,UAAU;AAAA,2CACzB,KAAK,OAAO,KAAK,aAAa;AAAA;AAAA;AAAA,qDAGpB,KAAK,OAAO,KAAK,WAAW;AAAA;AAAA,wDAEzB,KAAK,OAAO,KAAK,UAAU;AAAA;AAAA,KAGjF,CAEQ,wBAA+B,CACrC,GAAI,CAAC,KAAK,eAAgB,OAE1B,MAAMC,EACJ,KAAK,eAAe,cAAmC,gBAAgB,EACnEC,EACJ,KAAK,eAAe,cAAiC,kBAAkB,EACrED,IAEF,WAAW,IAAMA,EAAS,MAAA,EAAS,GAAG,EAGtCA,EAAS,iBAAiB,QAAS,IAAM,CACnCC,IACFA,EAAU,YAAcD,EAAS,MAAM,KAAA,EAAS,KAAK,OAAO,KAAK,aAAe,KAAK,OAAO,KAAK,WAErG,CAAC,GAGCC,GACFA,EAAU,iBAAiB,QAAS,IAAM,CACxC,MAAMC,GAAUF,GAAA,YAAAA,EAAU,MAAM,SAAU,KAC1C,KAAK,cAAcE,CAAO,CAC5B,CAAC,EAIH,MAAMC,EAAa,KAAK,eAAe,cAAiC,mBAAmB,EACvFA,GACFA,EAAW,iBAAiB,QAAS,IAAM,CACzC,MAAMC,EAAU,KAAK,aAAa,eAAiB,KAAO,OAAS,KACnE,KAAK,aAAa,aAAeA,EAG7B,KAAK,OAAO,aACdD,EAAW,UAAU,OAAO,cAAeC,IAAY,IAAI,EAC3DD,EAAW,UAAU,OAAO,gBAAiBC,IAAY,MAAM,IAE/DD,EAAW,UAAU,OAAO,eAAgBC,IAAY,IAAI,EAC5DD,EAAW,UAAU,OAAO,iBAAkBC,IAAY,MAAM,GAElE,MAAMN,EAAe,KAAK,OAAO,cAAgB5L,EAC3C6L,EAAe,KAAK,OAAO,cAAgB5L,EACjDgM,EAAW,UAAYC,IAAY,KAAON,EAAeC,EAGrD,KAAK,YACP,MAAM,GAAG,KAAK,OAAO,QAAQ,IAAI,KAAK,UAAU,GAAI,CAClD,OAAQ,QACR,QAAS,CAAE,eAAgB,kBAAA,EAC3B,KAAM,KAAK,UAAU,CAAE,aAAcK,EAAS,CAAA,CAC/C,EAAE,MAAOC,GAAQ,QAAQ,MAAM,kCAAmCA,CAAG,CAAC,CAE3E,CAAC,CAEL,CAEA,MAAc,cAAcH,EAAuC,CACjE,GAAI,KAAK,WAAY,CACnB,GAAI,CACF,MAAM,MAAM,GAAG,KAAK,OAAO,QAAQ,IAAI,KAAK,UAAU,GAAI,CACxD,OAAQ,QACR,QAAS,CAAE,eAAgB,kBAAA,EAC3B,KAAM,KAAK,UAAU,CAAE,QAAAA,EAAS,CAAA,CACjC,CACH,OAAS/F,EAAO,CACd,QAAQ,MAAM,qCAAsCA,CAAK,CAC3D,CAEA,KAAK,WAAa,IACpB,CAEA,KAAK,WAAA,CACP,CAEQ,YAAmB,CAErB,KAAK,QAAU,cAAgB,KAAK,YACtC,MAAM,GAAG,KAAK,OAAO,QAAQ,IAAI,KAAK,UAAU,GAAI,CAClD,OAAQ,QACR,QAAS,CAAE,eAAgB,kBAAA,EAC3B,KAAM,KAAK,UAAU,CAAE,QAAS,KAAM,CAAA,CACvC,EAAE,MAAOkG,GAAQ,QAAQ,MAAM,+BAAgCA,CAAG,CAAC,EAGlE,KAAK,iBACP,KAAK,eAAe,OAAA,EACpB,KAAK,eAAiB,MAEpB,KAAK,WACP,KAAK,SAAS,OAAA,EACd,KAAK,SAAW,MAGlB,SAAS,oBAAoB,UAAW,KAAK,YAAY,EACzD,KAAK,mBAAA,EAEL,KAAK,MAAQ,OACb,KAAK,WAAa,KAClB,KAAK,aAAa,aAAe,KACjC,KAAK,aAAa,gBAAkB,KACpC,KAAK,aAAa,YAAc,KAChC,KAAK,eAAe,QAAU,EAChC,CAKO,SAAgB,CAEjB,KAAK,iBACP,KAAK,eAAe,QAAA,EACpB,KAAK,eAAiB,MAIxB,OAAO,oBAAoB,SAAU,KAAK,WAAW,EACrD,SAAS,oBAAoB,UAAW,KAAK,YAAY,EAGzDrF,EAAiB,gBAAgB,EACjC,SAAS,KAAK,UAAU,OAAO,iBAAkB,cAAc,EAG3D,KAAK,YACH,KAAK,yBAEP,KAAK,UAAU,UAAY,GAC3B,KAAK,UAAU,UAAU,OAAO,cAAe,QAAQ,KAAK,OAAO,QAAQ,EAAE,GAE7E,KAAK,UAAU,OAAA,EAEjB,KAAK,UAAY,MAEnB,KAAK,uBAAA,EACL,KAAK,mBAAA,EACD,KAAK,iBACP,KAAK,eAAe,OAAA,EACpB,KAAK,eAAiB,MAEpB,KAAK,WACP,KAAK,SAAS,OAAA,EACd,KAAK,SAAW,MAEd,KAAK,iBACP,KAAK,eAAe,OAAA,EACpB,KAAK,eAAiB,MAIxBvE,EAAA,CACF,CACF,CCh2BA,SAAS6J,IAAkD,OACzD,MAAMC,EAAe,SAAS,cAC5B,uDAAA,EAEF,GAAI,CAACA,EAAc,OAAO,KAE1B,MAAMpJ,GAAcC,EAAAmJ,EAAa,cAAb,YAAAnJ,EAA0B,OAC9C,GAAI,CAACD,EAAa,OAAO,KAEzB,GAAI,CACF,OAAO,KAAK,MAAMA,CAAW,CAC/B,MAAQ,CACN,OAAO,IACT,CACF,CAKA,SAASqJ,GAAoBC,EAA2D,CACtF,MAAMC,EAAWD,EAAO,aAAa,eAAe,EACpD,GAAI,CAACC,EAAU,OAAO,KAEtB,MAAMhI,EAAW+H,EAAO,aAAa,eAAe,EAM9CE,EAASF,EAAO,aAAa,aAAa,EAC1C/K,EAAgB+K,EAAO,aAAa,qBAAqB,EACzD9K,EAAgB8K,EAAO,aAAa,qBAAqB,EACzD7K,EAAc6K,EAAO,aAAa,mBAAmB,EACrDG,EAAYH,EAAO,aAAa,gBAAgB,EAChDI,EAAcJ,EAAO,aAAa,mBAAmB,EACrDK,EAAgBL,EAAO,aAAa,qBAAqB,EACzDM,EAAYN,EAAO,aAAa,gBAAgB,EAChD5K,EAAa4K,EAAO,aAAa,kBAAkB,EAKnD7C,EAAU6C,EAAO,aAAa,eAAe,EAC7C5C,EAAU4C,EAAO,aAAa,eAAe,EAC7C3K,EAAa2K,EAAO,aAAa,kBAAkB,EACnD1K,EAAkB0K,EAAO,aAAa,uBAAuB,EAC7DzK,EAAayK,EAAO,aAAa,kBAAkB,EACnDxK,EAAWwK,EAAO,aAAa,gBAAgB,EAC/CrC,EAAUqC,EAAO,aAAa,cAAc,EAC5CO,EAAaP,EAAO,aAAa,kBAAkB,EACnDQ,EAAaR,EAAO,aAAa,kBAAkB,EACnDS,EAAaT,EAAO,aAAa,kBAAkB,EACnDU,EAAgBV,EAAO,aAAa,qBAAqB,EACzDW,EAAcX,EAAO,aAAa,kBAAkB,EACpDY,EAAeZ,EAAO,aAAa,oBAAoB,EACvDa,EAAab,EAAO,aAAa,kBAAkB,EACnDX,GAAeW,EAAO,aAAa,oBAAoB,EACvDV,GAAeU,EAAO,aAAa,oBAAoB,EAE7D,MAAO,CACL,SAAAC,EACA,UAAWE,GAAa,OACxB,YAAaC,GAAe,OAC5B,SAAUnI,GAAY,OACtB,OAAQiI,EAAS,SAASA,EAAQ,EAAE,EAAI,OACxC,cAAeG,IAAkB,OAAS,GAAO,OACjD,UAAWC,IAAc,OAAS,GAAO,OACzC,WAAYlL,GAAc,OAC1B,OAAS+H,GAAWC,EAAW,CAC7B,EAAGD,EAAU,SAASA,EAAS,EAAE,EAAI,OACrC,EAAGC,EAAU,SAASA,EAAS,EAAE,EAAI,MAAA,EACnC,OACJ,WAAY/H,EAAa,SAASA,EAAY,EAAE,EAAI,OACpD,gBAAiBC,EAAkB,WAAWA,CAAe,EAAI,OACjE,WAAYC,GAAc,OAC1B,SAAUC,EAAW,SAASA,EAAU,EAAE,EAAI,OAC9C,OAAQ,CACN,SAAUP,GAAiB,OAC3B,SAAUC,GAAiB,OAC3B,OAAQC,GAAe,MAAA,EAEzB,KAAOwI,GAAW4C,GAAcC,GAAcC,GAAcC,GAAiBC,GAAeC,GAAgBC,EAAc,CACxH,QAASlD,GAAW,OACpB,WAAY4C,GAAc,OAC1B,WAAYC,GAAc,OAC1B,WAAYC,GAAc,OAC1B,cAAeC,GAAiB,OAChC,YAAaC,GAAe,OAC5B,aAAcC,GAAgB,OAC9B,WAAYC,GAAc,MAAA,EACxB,OACJ,aAAcxB,IAAgB,OAC9B,aAAcC,IAAgB,MAAA,CAElC,CAGA,GAAI,OAAO,SAAa,IAAa,CACnC,MAAMwB,EAAiB,IAAY,CACjC,MAAMd,EAAS,SAAS,cAGlBe,EAAalB,GAAA,EACbmB,EAAahB,EAASD,GAAoBC,CAAM,EAAI,KACpDjE,EAASgF,GAAcC,EAEzBjF,GAAA,MAAAA,EAAQ,UACV,IAAID,EAAcC,CAAwB,CAE9C,EAGI,SAAS,aAAe,UAC1B,SAAS,iBAAiB,mBAAoB+E,CAAc,EAE5DA,EAAA,CAEJ"}
|