@mindbowser_inc/ui-feedback-plugin 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui-feedback-plugin.es.js","sources":["../src/core/state.ts","../src/ui/styles.ts","../src/ui/FloatingTrigger.ts","../src/ui/NameModal.ts","../src/drawing/renderer.ts","../src/ui/AnnotationOverlay.ts","../src/ui/utils.ts","../src/ui/ReplyThread.ts","../src/ui/CommentDetailView.ts","../src/ui/CommentsPanel.ts","../src/screenshot/capture.ts","../src/index.ts"],"sourcesContent":["import type { FeedbackComment, Reply, PluginConfig } from './types'\n\nexport interface PluginState {\n active: boolean\n userName: string | null\n comments: FeedbackComment[]\n replies: Map<string, Reply[]>\n selectedCommentId: string | null\n panelOpen: boolean\n filter: 'open' | 'resolved'\n config: PluginConfig\n}\n\nconst listeners = new Set<(state: PluginState) => void>()\n\nlet state: PluginState = {\n active: false,\n userName: null,\n comments: [],\n replies: new Map(),\n selectedCommentId: null,\n panelOpen: false,\n filter: 'open',\n config: {} as PluginConfig,\n}\n\nexport function getState(): PluginState {\n return state\n}\n\nexport function setState(partial: Partial<PluginState>): void {\n state = { ...state, ...partial }\n listeners.forEach((fn) => fn(state))\n}\n\nexport function subscribe(fn: (state: PluginState) => void): () => void {\n listeners.add(fn)\n return () => listeners.delete(fn)\n}\n\nexport function getUserName(): string | null {\n if (state.userName) return state.userName\n const stored = localStorage.getItem('ufp_user_name')\n if (stored) {\n setState({ userName: stored })\n return stored\n }\n return null\n}\n\nexport function setUserName(name: string): void {\n localStorage.setItem('ufp_user_name', name)\n setState({ userName: name })\n}\n\nexport function normalizeUrl(url: string): string {\n try {\n const u = new URL(url)\n return u.origin + u.pathname\n } catch {\n return url\n }\n}\n","export function getStyles(primaryColor: string): string {\n return `\n :host {\n --ufp-primary: ${primaryColor};\n --ufp-primary-dark: color-mix(in srgb, ${primaryColor} 80%, black);\n --ufp-white: #ffffff;\n --ufp-gray-50: #f9fafb;\n --ufp-gray-100: #f3f4f6;\n --ufp-gray-200: #e5e7eb;\n --ufp-gray-300: #d1d5db;\n --ufp-gray-400: #9ca3af;\n --ufp-gray-500: #6b7280;\n --ufp-gray-600: #4b5563;\n --ufp-gray-700: #374151;\n --ufp-gray-800: #1f2937;\n --ufp-gray-900: #111827;\n --ufp-green: #10b981;\n --ufp-red: #ef4444;\n --ufp-shadow: 0 4px 24px rgba(0,0,0,0.18);\n --ufp-radius: 12px;\n --ufp-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-family: var(--ufp-font);\n all: initial;\n }\n\n * { box-sizing: border-box; }\n\n /* Trigger */\n .ufp-trigger {\n position: fixed;\n z-index: 2147483646;\n display: flex;\n align-items: center;\n gap: 8px;\n background: var(--ufp-primary);\n color: var(--ufp-white);\n border: none;\n border-radius: 28px;\n padding: 10px 18px 10px 14px;\n cursor: pointer;\n box-shadow: var(--ufp-shadow);\n font-family: var(--ufp-font);\n font-size: 14px;\n font-weight: 600;\n user-select: none;\n transition: background 0.15s, transform 0.1s;\n touch-action: none;\n }\n .ufp-trigger:hover { background: var(--ufp-primary-dark); }\n .ufp-trigger:active { transform: scale(0.97); }\n .ufp-trigger.active { background: var(--ufp-gray-800); }\n .ufp-trigger-icon { width: 20px; height: 20px; flex-shrink: 0; }\n\n /* Modal overlay */\n .ufp-modal-backdrop {\n position: fixed;\n inset: 0;\n background: rgba(0,0,0,0.45);\n z-index: 2147483646;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n .ufp-modal {\n background: var(--ufp-white);\n border-radius: var(--ufp-radius);\n padding: 28px;\n width: 360px;\n box-shadow: var(--ufp-shadow);\n display: flex;\n flex-direction: column;\n gap: 16px;\n }\n .ufp-modal h2 {\n margin: 0;\n font-size: 18px;\n font-weight: 700;\n color: var(--ufp-gray-900);\n font-family: var(--ufp-font);\n }\n .ufp-modal p {\n margin: 0;\n font-size: 14px;\n color: var(--ufp-gray-500);\n font-family: var(--ufp-font);\n }\n\n /* Form elements */\n .ufp-input {\n width: 100%;\n padding: 10px 14px;\n border: 1.5px solid var(--ufp-gray-200);\n border-radius: 8px;\n font-size: 14px;\n font-family: var(--ufp-font);\n color: var(--ufp-gray-900);\n outline: none;\n transition: border-color 0.15s;\n }\n .ufp-input:focus { border-color: var(--ufp-primary); }\n .ufp-textarea {\n width: 100%;\n padding: 10px 14px;\n border: 1.5px solid var(--ufp-gray-200);\n border-radius: 8px;\n font-size: 14px;\n font-family: var(--ufp-font);\n color: var(--ufp-gray-900);\n outline: none;\n resize: vertical;\n min-height: 80px;\n transition: border-color 0.15s;\n }\n .ufp-textarea:focus { border-color: var(--ufp-primary); }\n\n /* Buttons */\n .ufp-btn {\n padding: 9px 18px;\n border-radius: 8px;\n font-size: 14px;\n font-weight: 600;\n font-family: var(--ufp-font);\n cursor: pointer;\n border: none;\n transition: background 0.15s, opacity 0.15s;\n }\n .ufp-btn-primary {\n background: var(--ufp-primary);\n color: var(--ufp-white);\n }\n .ufp-btn-primary:hover { background: var(--ufp-primary-dark); }\n .ufp-btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }\n .ufp-btn-ghost {\n background: transparent;\n color: var(--ufp-gray-600);\n border: 1.5px solid var(--ufp-gray-200);\n }\n .ufp-btn-ghost:hover { background: var(--ufp-gray-50); }\n .ufp-btn-danger {\n background: var(--ufp-red);\n color: var(--ufp-white);\n }\n .ufp-btn-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n padding: 0;\n border-radius: 6px;\n background: transparent;\n border: none;\n cursor: pointer;\n color: var(--ufp-gray-600);\n font-size: 16px;\n transition: background 0.12s, color 0.12s;\n }\n .ufp-btn-icon:hover { background: var(--ufp-gray-100); color: var(--ufp-gray-900); }\n .ufp-btn-icon.active { background: var(--ufp-primary); color: var(--ufp-white); }\n\n .ufp-btn-row {\n display: flex;\n gap: 8px;\n justify-content: flex-end;\n }\n\n /* Annotation overlay */\n .ufp-overlay {\n position: fixed;\n inset: 0;\n z-index: 2147483645;\n cursor: crosshair;\n background: rgba(99,102,241,0.04);\n }\n .ufp-overlay-svg {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n pointer-events: none;\n }\n\n /* Toolbar */\n .ufp-toolbar {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n z-index: 2147483647;\n background: var(--ufp-white);\n border-radius: 12px;\n box-shadow: var(--ufp-shadow);\n padding: 8px;\n display: flex;\n align-items: center;\n gap: 4px;\n top: 24px;\n left: 50%;\n transform: translateX(-50%);\n }\n .ufp-toolbar-sep {\n width: 1px;\n height: 24px;\n background: var(--ufp-gray-200);\n margin: 0 4px;\n }\n .ufp-toolbar-label {\n font-size: 12px;\n font-family: var(--ufp-font);\n color: var(--ufp-gray-500);\n padding: 0 6px;\n }\n\n /* Comment input popup */\n .ufp-comment-popup {\n position: fixed;\n z-index: 2147483647;\n background: var(--ufp-white);\n border-radius: var(--ufp-radius);\n box-shadow: var(--ufp-shadow);\n padding: 16px;\n width: 320px;\n display: flex;\n flex-direction: column;\n gap: 12px;\n }\n .ufp-comment-popup h3 {\n margin: 0;\n font-size: 14px;\n font-weight: 600;\n color: var(--ufp-gray-800);\n font-family: var(--ufp-font);\n }\n\n /* Comments panel */\n .ufp-panel {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n z-index: 2147483646;\n background: var(--ufp-white);\n box-shadow: -4px 0 24px rgba(0,0,0,0.12);\n display: flex;\n flex-direction: column;\n transform: translateX(100%);\n transition: transform 0.25s cubic-bezier(0.4,0,0.2,1);\n }\n .ufp-panel.open { transform: translateX(0); }\n .ufp-panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 20px;\n border-bottom: 1px solid var(--ufp-gray-100);\n flex-shrink: 0;\n }\n .ufp-panel-title {\n font-size: 16px;\n font-weight: 700;\n color: var(--ufp-gray-900);\n font-family: var(--ufp-font);\n }\n .ufp-filter-tabs {\n display: flex;\n gap: 4px;\n padding: 8px 12px;\n border-bottom: 1px solid var(--ufp-gray-100);\n flex-shrink: 0;\n }\n .ufp-filter-tab {\n padding: 6px 14px;\n border-radius: 20px;\n font-size: 13px;\n font-family: var(--ufp-font);\n font-weight: 500;\n cursor: pointer;\n border: none;\n background: transparent;\n color: var(--ufp-gray-500);\n transition: background 0.12s, color 0.12s;\n }\n .ufp-filter-tab.active {\n background: var(--ufp-primary);\n color: var(--ufp-white);\n }\n .ufp-panel-list {\n flex: 1;\n overflow-y: auto;\n padding: 8px 0;\n }\n .ufp-panel-empty {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 200px;\n color: var(--ufp-gray-400);\n font-size: 14px;\n font-family: var(--ufp-font);\n gap: 8px;\n }\n\n /* Comment card */\n .ufp-comment-card {\n padding: 12px 16px;\n border-bottom: 1px solid var(--ufp-gray-100);\n cursor: pointer;\n transition: background 0.12s;\n display: flex;\n flex-direction: column;\n gap: 8px;\n }\n .ufp-comment-card:hover { background: var(--ufp-gray-50); }\n .ufp-comment-card.selected { background: #eef2ff; }\n .ufp-comment-card-header {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 8px;\n }\n .ufp-comment-meta {\n display: flex;\n align-items: center;\n gap: 6px;\n }\n .ufp-avatar {\n width: 26px;\n height: 26px;\n border-radius: 50%;\n background: var(--ufp-primary);\n color: var(--ufp-white);\n font-size: 11px;\n font-weight: 700;\n font-family: var(--ufp-font);\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n }\n .ufp-comment-author {\n font-size: 13px;\n font-weight: 600;\n color: var(--ufp-gray-800);\n font-family: var(--ufp-font);\n }\n .ufp-comment-time {\n font-size: 11px;\n color: var(--ufp-gray-400);\n font-family: var(--ufp-font);\n }\n .ufp-comment-text {\n font-size: 13px;\n color: var(--ufp-gray-700);\n font-family: var(--ufp-font);\n line-height: 1.5;\n overflow: hidden;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n }\n .ufp-comment-thumbnail {\n width: 100%;\n height: 80px;\n object-fit: cover;\n object-position: top left;\n border-radius: 6px;\n border: 1px solid var(--ufp-gray-200);\n }\n .ufp-comment-footer {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n .ufp-reply-count {\n font-size: 12px;\n color: var(--ufp-gray-400);\n font-family: var(--ufp-font);\n }\n .ufp-resolved-badge {\n font-size: 11px;\n font-weight: 600;\n font-family: var(--ufp-font);\n padding: 2px 8px;\n border-radius: 12px;\n background: #d1fae5;\n color: #065f46;\n }\n .ufp-card-actions {\n display: flex;\n gap: 4px;\n }\n\n /* Detail view */\n .ufp-detail {\n position: fixed;\n inset: 0;\n z-index: 2147483647;\n background: rgba(0,0,0,0.75);\n display: flex;\n align-items: stretch;\n }\n .ufp-detail-image-area {\n flex: 1;\n overflow: auto;\n display: flex;\n align-items: flex-start;\n justify-content: center;\n padding: 24px;\n position: relative;\n }\n .ufp-detail-image-wrap {\n position: relative;\n display: inline-block;\n }\n .ufp-detail-screenshot {\n max-width: 100%;\n display: block;\n border-radius: 8px;\n }\n .ufp-detail-annotation-svg {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n pointer-events: none;\n }\n .ufp-detail-sidebar {\n width: 340px;\n background: var(--ufp-white);\n display: flex;\n flex-direction: column;\n flex-shrink: 0;\n }\n .ufp-detail-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 20px;\n border-bottom: 1px solid var(--ufp-gray-100);\n flex-shrink: 0;\n }\n .ufp-detail-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px 20px;\n display: flex;\n flex-direction: column;\n gap: 16px;\n }\n .ufp-detail-comment {\n display: flex;\n flex-direction: column;\n gap: 8px;\n }\n .ufp-detail-comment-text {\n font-size: 14px;\n color: var(--ufp-gray-800);\n font-family: var(--ufp-font);\n line-height: 1.6;\n }\n .ufp-detail-actions {\n display: flex;\n gap: 8px;\n flex-wrap: wrap;\n }\n\n /* Reply thread */\n .ufp-replies {\n display: flex;\n flex-direction: column;\n gap: 12px;\n border-top: 1px solid var(--ufp-gray-100);\n padding-top: 12px;\n }\n .ufp-replies-title {\n font-size: 12px;\n font-weight: 600;\n color: var(--ufp-gray-500);\n font-family: var(--ufp-font);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n }\n .ufp-reply {\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n .ufp-reply-meta {\n display: flex;\n align-items: center;\n gap: 6px;\n }\n .ufp-reply-author {\n font-size: 12px;\n font-weight: 600;\n color: var(--ufp-gray-700);\n font-family: var(--ufp-font);\n }\n .ufp-reply-time {\n font-size: 11px;\n color: var(--ufp-gray-400);\n font-family: var(--ufp-font);\n }\n .ufp-reply-text {\n font-size: 13px;\n color: var(--ufp-gray-700);\n font-family: var(--ufp-font);\n line-height: 1.5;\n padding-left: 32px;\n }\n .ufp-reply-input-area {\n display: flex;\n flex-direction: column;\n gap: 8px;\n border-top: 1px solid var(--ufp-gray-100);\n padding-top: 12px;\n }\n .ufp-reply-name-row {\n display: flex;\n gap: 8px;\n align-items: center;\n }\n .ufp-reply-name-label {\n font-size: 12px;\n color: var(--ufp-gray-500);\n font-family: var(--ufp-font);\n white-space: nowrap;\n }\n\n /* Loading spinner */\n .ufp-spinner {\n width: 20px;\n height: 20px;\n border: 2px solid var(--ufp-gray-200);\n border-top-color: var(--ufp-primary);\n border-radius: 50%;\n animation: ufp-spin 0.7s linear infinite;\n }\n @keyframes ufp-spin { to { transform: rotate(360deg); } }\n\n /* Scrollbar */\n .ufp-panel-list::-webkit-scrollbar,\n .ufp-detail-body::-webkit-scrollbar,\n .ufp-detail-image-area::-webkit-scrollbar { width: 4px; }\n .ufp-panel-list::-webkit-scrollbar-thumb,\n .ufp-detail-body::-webkit-scrollbar-thumb,\n .ufp-detail-image-area::-webkit-scrollbar-thumb {\n background: var(--ufp-gray-300);\n border-radius: 2px;\n }\n `\n}\n","import { getState, subscribe } from '../core/state'\n\ntype Position = 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'\n\nconst POSITION_STYLES: Record<Position, Partial<CSSStyleDeclaration>> = {\n 'bottom-right': { bottom: '24px', right: '24px' },\n 'bottom-left': { bottom: '24px', left: '24px' },\n 'top-right': { top: '24px', right: '24px' },\n 'top-left': { top: '24px', left: '24px' },\n}\n\nconst BG = '#6366f1'\nconst BG_HOVER = '#4f46e5'\nconst BG_PANEL_ACTIVE = '#4338ca'\n\nexport function createFloatingTrigger(\n shadow: ShadowRoot,\n position: Position,\n onStartAnnotation: () => void,\n onTogglePanel: () => void\n): HTMLElement {\n const wrapper = document.createElement('div')\n Object.assign(wrapper.style, {\n position: 'fixed',\n zIndex: '2147483647',\n display: 'flex',\n alignItems: 'stretch',\n borderRadius: '28px',\n boxShadow: '0 4px 20px rgba(0,0,0,0.28), 0 1px 4px rgba(0,0,0,0.16)',\n overflow: 'hidden',\n userSelect: 'none',\n ...POSITION_STYLES[position],\n })\n\n // ── Annotate button ──────────────────────────────────────────────────────\n const annotateBtn = document.createElement('button')\n annotateBtn.id = 'ufp-btn-annotate'\n annotateBtn.title = 'Add feedback annotation'\n applyBtnBase(annotateBtn)\n annotateBtn.style.padding = '10px 16px 10px 14px'\n annotateBtn.innerHTML = `\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" style=\"flex-shrink:0\">\n <path d=\"M12 20h9\"/>\n <path d=\"M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z\"/>\n </svg>\n <span id=\"ufp-annotate-label\" style=\"font-size:14px;font-weight:600;\">Feedback</span>\n `\n\n // ── Separator ────────────────────────────────────────────────────────────\n const sep = document.createElement('div')\n sep.style.cssText = 'width:1px;background:rgba(255,255,255,0.3);flex-shrink:0;'\n\n // ── Panel button ─────────────────────────────────────────────────────────\n const panelBtn = document.createElement('button')\n panelBtn.id = 'ufp-btn-panel'\n panelBtn.title = 'View all comments'\n applyBtnBase(panelBtn)\n panelBtn.style.padding = '10px 14px'\n panelBtn.innerHTML = `\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" style=\"flex-shrink:0\">\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/>\n </svg>\n <span id=\"ufp-panel-count\" style=\"font-size:13px;font-weight:600;min-width:8px;text-align:center;\"></span>\n `\n\n wrapper.appendChild(annotateBtn)\n wrapper.appendChild(sep)\n wrapper.appendChild(panelBtn)\n shadow.appendChild(wrapper)\n\n const annotateLabel = wrapper.querySelector('#ufp-annotate-label') as HTMLSpanElement\n const panelCount = wrapper.querySelector('#ufp-panel-count') as HTMLSpanElement\n\n // ── Click handlers ───────────────────────────────────────────────────────\n annotateBtn.addEventListener('click', (e) => {\n e.stopPropagation()\n if ((wrapper as any)._dragged) return\n const state = getState()\n if (state.active) return\n if (state.panelOpen) {\n onTogglePanel()\n setTimeout(onStartAnnotation, 220)\n } else {\n onStartAnnotation()\n }\n })\n\n panelBtn.addEventListener('click', (e) => {\n e.stopPropagation()\n if ((wrapper as any)._dragged) return\n if (getState().active) return\n onTogglePanel()\n })\n\n // ── Hover effects ─────────────────────────────────────────────────────────\n addHover(annotateBtn, () => getState().panelOpen ? BG : BG, BG_HOVER)\n addHover(panelBtn, () => getState().panelOpen ? BG_PANEL_ACTIVE : BG, BG_HOVER)\n\n // ── State subscription ───────────────────────────────────────────────────\n subscribe((state) => {\n const openCount = state.comments.filter((c) => !c.resolved).length\n panelCount.textContent = openCount > 0 ? String(openCount) : ''\n\n if (state.active) {\n annotateLabel.textContent = 'Drawing…'\n annotateBtn.style.opacity = '0.65'\n panelBtn.style.opacity = '0.65'\n annotateBtn.style.pointerEvents = 'none'\n panelBtn.style.pointerEvents = 'none'\n } else {\n annotateLabel.textContent = 'Feedback'\n annotateBtn.style.opacity = '1'\n panelBtn.style.opacity = '1'\n annotateBtn.style.pointerEvents = 'auto'\n panelBtn.style.pointerEvents = 'auto'\n }\n\n // Tint panel button when panel is open to signal active state\n panelBtn.style.background = state.panelOpen ? BG_PANEL_ACTIVE : BG\n })\n\n // ── Drag ─────────────────────────────────────────────────────────────────\n makeDraggable(wrapper)\n\n return wrapper\n}\n\nfunction applyBtnBase(btn: HTMLButtonElement) {\n Object.assign(btn.style, {\n display: 'flex',\n alignItems: 'center',\n gap: '7px',\n background: BG,\n color: '#ffffff',\n border: 'none',\n cursor: 'pointer',\n fontFamily: '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\n lineHeight: '1',\n transition: 'background 0.12s',\n })\n}\n\nfunction addHover(btn: HTMLButtonElement, getNormal: () => string, hoverColor: string) {\n btn.addEventListener('mouseenter', () => { btn.style.background = hoverColor })\n btn.addEventListener('mouseleave', () => { btn.style.background = getNormal() })\n}\n\nfunction makeDraggable(el: HTMLElement) {\n let startX = 0, startY = 0, initLeft = 0, initTop = 0\n\n el.addEventListener('pointerdown', (e: PointerEvent) => {\n if (e.button !== 0) return\n startX = e.clientX; startY = e.clientY\n const rect = el.getBoundingClientRect()\n initLeft = rect.left; initTop = rect.top\n ;(el as any)._dragged = false\n\n const onMove = (e: PointerEvent) => {\n const dx = e.clientX - startX\n const dy = e.clientY - startY\n if (!((el as any)._dragged) && Math.sqrt(dx * dx + dy * dy) < 5) return\n ;(el as any)._dragged = true\n el.style.left = `${Math.max(0, Math.min(window.innerWidth - el.offsetWidth, initLeft + dx))}px`\n el.style.top = `${Math.max(0, Math.min(window.innerHeight - el.offsetHeight, initTop + dy))}px`\n el.style.right = 'auto'\n el.style.bottom = 'auto'\n }\n\n const onUp = () => {\n document.removeEventListener('pointermove', onMove)\n document.removeEventListener('pointerup', onUp)\n // Reset after click event has fired\n requestAnimationFrame(() => { (el as any)._dragged = false })\n }\n\n document.addEventListener('pointermove', onMove)\n document.addEventListener('pointerup', onUp)\n })\n}\n","import { setUserName } from '../core/state'\n\nexport function showNameModal(shadow: ShadowRoot): Promise<string> {\n return new Promise((resolve) => {\n const backdrop = document.createElement('div')\n backdrop.className = 'ufp-modal-backdrop'\n\n backdrop.innerHTML = `\n <div class=\"ufp-modal\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"ufp-modal-title\">\n <h2 id=\"ufp-modal-title\">Welcome to Feedback Mode</h2>\n <p>Enter your name so others can see who left the comment.</p>\n <input\n class=\"ufp-input\"\n id=\"ufp-name-input\"\n type=\"text\"\n placeholder=\"Your name\"\n maxlength=\"60\"\n autocomplete=\"off\"\n />\n <div class=\"ufp-btn-row\">\n <button class=\"ufp-btn ufp-btn-primary\" id=\"ufp-name-submit\" disabled>Continue</button>\n </div>\n </div>\n `\n\n shadow.appendChild(backdrop)\n\n const input = backdrop.querySelector('#ufp-name-input') as HTMLInputElement\n const submitBtn = backdrop.querySelector('#ufp-name-submit') as HTMLButtonElement\n\n input.addEventListener('input', () => {\n submitBtn.disabled = input.value.trim().length === 0\n })\n\n const submit = () => {\n const name = input.value.trim()\n if (!name) return\n setUserName(name)\n backdrop.remove()\n resolve(name)\n }\n\n submitBtn.addEventListener('click', submit)\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') submit()\n })\n\n requestAnimationFrame(() => input.focus())\n })\n}\n","import type { AnnotationShape, AnnotationData, Point } from '../core/types'\n\nconst ANNOTATION_COLOR = '#6366f1'\nconst STROKE_WIDTH = 3\n\nexport function renderShapeToSVG(shape: AnnotationShape, scaleX = 1, scaleY = 1): SVGElement {\n switch (shape.type) {\n case 'pen': {\n const path = document.createElementNS('http://www.w3.org/2000/svg', 'path')\n path.setAttribute('d', pointsToPath(shape.points, scaleX, scaleY))\n path.setAttribute('stroke', shape.color)\n path.setAttribute('stroke-width', String(shape.width * Math.min(scaleX, scaleY)))\n path.setAttribute('fill', 'none')\n path.setAttribute('stroke-linecap', 'round')\n path.setAttribute('stroke-linejoin', 'round')\n return path\n }\n case 'rect': {\n const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect')\n rect.setAttribute('x', String(shape.x * scaleX))\n rect.setAttribute('y', String(shape.y * scaleY))\n rect.setAttribute('width', String(shape.width * scaleX))\n rect.setAttribute('height', String(shape.height * scaleY))\n rect.setAttribute('stroke', shape.color)\n rect.setAttribute('stroke-width', String(shape.strokeWidth * Math.min(scaleX, scaleY)))\n rect.setAttribute('fill', 'none')\n rect.setAttribute('rx', '3')\n return rect\n }\n case 'circle': {\n const ellipse = document.createElementNS('http://www.w3.org/2000/svg', 'ellipse')\n ellipse.setAttribute('cx', String(shape.cx * scaleX))\n ellipse.setAttribute('cy', String(shape.cy * scaleY))\n ellipse.setAttribute('rx', String(shape.rx * scaleX))\n ellipse.setAttribute('ry', String(shape.ry * scaleY))\n ellipse.setAttribute('stroke', shape.color)\n ellipse.setAttribute('stroke-width', String(shape.strokeWidth * Math.min(scaleX, scaleY)))\n ellipse.setAttribute('fill', 'none')\n return ellipse\n }\n case 'arrow': {\n const g = document.createElementNS('http://www.w3.org/2000/svg', 'g')\n const line = document.createElementNS('http://www.w3.org/2000/svg', 'line')\n line.setAttribute('x1', String(shape.x1 * scaleX))\n line.setAttribute('y1', String(shape.y1 * scaleY))\n line.setAttribute('x2', String(shape.x2 * scaleX))\n line.setAttribute('y2', String(shape.y2 * scaleY))\n line.setAttribute('stroke', shape.color)\n line.setAttribute('stroke-width', String(shape.width * Math.min(scaleX, scaleY)))\n line.setAttribute('marker-end', 'url(#ufp-arrowhead)')\n g.appendChild(line)\n return g\n }\n }\n}\n\nexport function renderAnnotationToSVG(\n svgEl: SVGSVGElement,\n annotation: AnnotationData,\n displayWidth: number,\n displayHeight: number\n): void {\n const scaleX = displayWidth / annotation.pageWidth\n const scaleY = displayHeight / annotation.pageHeight\n\n svgEl.innerHTML = ''\n\n const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs')\n defs.innerHTML = `\n <marker id=\"ufp-arrowhead\" markerWidth=\"10\" markerHeight=\"7\" refX=\"9\" refY=\"3.5\" orient=\"auto\">\n <polygon points=\"0 0, 10 3.5, 0 7\" fill=\"${ANNOTATION_COLOR}\" />\n </marker>\n `\n svgEl.appendChild(defs)\n\n annotation.shapes.forEach((shape) => {\n svgEl.appendChild(renderShapeToSVG(shape, scaleX, scaleY))\n })\n}\n\nfunction pointsToPath(points: Point[], scaleX: number, scaleY: number): string {\n if (points.length === 0) return ''\n if (points.length === 1) {\n const p = points[0]\n return `M ${p.x * scaleX} ${p.y * scaleY}`\n }\n let d = `M ${points[0].x * scaleX} ${points[0].y * scaleY}`\n for (let i = 1; i < points.length; i++) {\n d += ` L ${points[i].x * scaleX} ${points[i].y * scaleY}`\n }\n return d\n}\n\nexport function getBoundingBox(shapes: AnnotationShape[]): {\n x: number; y: number; width: number; height: number\n} {\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity\n\n shapes.forEach((shape) => {\n switch (shape.type) {\n case 'pen':\n shape.points.forEach((p) => {\n minX = Math.min(minX, p.x); minY = Math.min(minY, p.y)\n maxX = Math.max(maxX, p.x); maxY = Math.max(maxY, p.y)\n })\n break\n case 'rect':\n minX = Math.min(minX, shape.x); minY = Math.min(minY, shape.y)\n maxX = Math.max(maxX, shape.x + shape.width); maxY = Math.max(maxY, shape.y + shape.height)\n break\n case 'circle':\n minX = Math.min(minX, shape.cx - shape.rx); minY = Math.min(minY, shape.cy - shape.ry)\n maxX = Math.max(maxX, shape.cx + shape.rx); maxY = Math.max(maxY, shape.cy + shape.ry)\n break\n case 'arrow':\n minX = Math.min(minX, shape.x1, shape.x2); minY = Math.min(minY, shape.y1, shape.y2)\n maxX = Math.max(maxX, shape.x1, shape.x2); maxY = Math.max(maxY, shape.y1, shape.y2)\n break\n }\n })\n\n const pad = 16\n return {\n x: Math.max(0, minX - pad),\n y: Math.max(0, minY - pad),\n width: maxX - minX + pad * 2,\n height: maxY - minY + pad * 2,\n }\n}\n\nexport { ANNOTATION_COLOR, STROKE_WIDTH }\n","import type { DrawingTool, AnnotationShape, SVGRect, SVGCircle, SVGArrow, Point } from '../core/types'\nimport { renderShapeToSVG, ANNOTATION_COLOR, STROKE_WIDTH } from '../drawing/renderer'\n\nexport interface AnnotationResult {\n shapes: AnnotationShape[]\n text: string\n anchorX: number\n anchorY: number\n}\n\n// Where a stroke is pinned: normal page content, or a fixed element near the top/bottom of the viewport.\ntype Anchor = 'top' | 'bottom' | null\n\n// The screenshot is a full-page render taken at scroll (0,0) with the window sized to the whole page.\n// So a fixed element near the top appears at its viewport position, and one pinned to the bottom\n// appears at the bottom of the page. Strokes made on such elements are drawn in viewport\n// coordinates and converted to screenshot coordinates on submit.\nfunction detectFixedAnchor(clientX: number, clientY: number, host: Element): Anchor {\n if (window.scrollY === 0) return null\n const vw = window.innerWidth, vh = window.innerHeight\n const hit = document.elementsFromPoint(clientX, clientY).find((el) => el !== host)\n for (let el: Element | null = hit ?? null; el && el !== document.documentElement; el = el.parentElement) {\n const cs = getComputedStyle(el)\n if (cs.position !== 'fixed' && cs.position !== 'sticky') continue\n const r = el.getBoundingClientRect()\n // Full-screen fixed wrappers (backgrounds, app shells) are not \"a fixed element\" for this purpose.\n if (r.width >= vw * 0.9 && r.height >= vh * 0.9) continue\n if (cs.position === 'sticky') {\n // Only a sticky element that is currently stuck near the top behaves like a fixed header.\n if (r.top < vh * 0.25) return 'top'\n continue\n }\n // Computed top/bottom are resolved to pixels, so decide by which viewport edge the element sits closer to.\n return vh - r.bottom < r.top ? 'bottom' : 'top'\n }\n return null\n}\n\nfunction translateShapeY(shape: AnnotationShape, dy: number): AnnotationShape {\n if (!dy) return shape\n switch (shape.type) {\n case 'pen': return { ...shape, points: shape.points.map((p) => ({ x: p.x, y: p.y + dy })) }\n case 'rect': return { ...shape, y: shape.y + dy }\n case 'circle': return { ...shape, cy: shape.cy + dy }\n case 'arrow': return { ...shape, y1: shape.y1 + dy, y2: shape.y2 + dy }\n }\n}\n\nexport function showAnnotationOverlay(shadow: ShadowRoot): Promise<AnnotationResult | null> {\n return new Promise((resolve) => {\n let currentTool: DrawingTool = 'pen'\n const shapes: AnnotationShape[] = []\n let drawing = false\n let startX = 0\n let startY = 0\n let currentStroke: Point[] = []\n let previewEl: SVGElement | null = null\n let commentPopup: HTMLElement | null = null\n let frozen = false\n let suppressClicksUntil = 0\n const anchors: Anchor[] = []\n let currentAnchor: Anchor = null\n // Offset from viewport to stroke coordinates: fixed-element strokes are kept in viewport coordinates.\n const scrollOffset = () => (currentAnchor ? { x: 0, y: 0 } : { x: window.scrollX, y: window.scrollY })\n\n // Overlay — captures pointer events for drawing\n const overlay = document.createElement('div')\n overlay.style.cssText = 'position:fixed;inset:0;z-index:2147483645;cursor:crosshair;background:rgba(99,102,241,0.04);touch-action:none;user-select:none;-webkit-user-select:none;'\n\n // SVG lives inside the overlay so shapes stay visible as long as overlay is alive\n const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')\n svg.style.cssText = 'position:absolute;inset:0;width:100%;height:100%;pointer-events:none;overflow:visible;'\n // Use same marker ID as renderer.ts expects: \"ufp-arrowhead\"\n svg.innerHTML = `\n <defs>\n <marker id=\"ufp-arrowhead\" markerWidth=\"10\" markerHeight=\"7\" refX=\"9\" refY=\"3.5\" orient=\"auto\">\n <polygon points=\"0 0, 10 3.5, 0 7\" fill=\"${ANNOTATION_COLOR}\" />\n </marker>\n </defs>\n `\n overlay.appendChild(svg)\n\n // Shapes are stored in page coordinates, but the overlay is position:fixed. Every shape lives\n // in this layer, which is shifted by the scroll offset so strokes appear under the cursor\n // wherever the page is scrolled (and stay anchored to the page if the user scrolls mid-draw).\n const layer = document.createElementNS('http://www.w3.org/2000/svg', 'g')\n svg.appendChild(layer)\n // Strokes on fixed elements live here, not shifted by scroll\n const fixedLayer = document.createElementNS('http://www.w3.org/2000/svg', 'g')\n svg.appendChild(fixedLayer)\n const syncLayer = () => layer.setAttribute('transform', `translate(${-window.scrollX} ${-window.scrollY})`)\n syncLayer()\n window.addEventListener('scroll', syncLayer, { passive: true })\n\n function cleanup() {\n window.removeEventListener('scroll', syncLayer)\n for (const t of POINTER_EVENTS) window.removeEventListener(t, onWindowEvent, true)\n for (const t of BLOCKED_EVENTS) window.removeEventListener(t, onBlockedEvent, true)\n overlay.remove()\n toolbar.remove()\n commentPopup?.remove()\n }\n\n function redraw() {\n layer.innerHTML = ''\n fixedLayer.innerHTML = ''\n shapes.forEach((s, i) => (anchors[i] ? fixedLayer : layer).appendChild(renderShapeToSVG(s)))\n }\n\n function getAnchor() {\n let sumX = 0, sumY = 0, count = 0\n // Sum in viewport coordinates: fixed-element strokes are already there, others are page coordinates.\n shapes.forEach((s, i) => {\n const ox = anchors[i] ? 0 : window.scrollX, oy = anchors[i] ? 0 : window.scrollY\n if (s.type === 'pen' && s.points.length) {\n const p = s.points[s.points.length - 1]; sumX += p.x - ox; sumY += p.y - oy; count++\n } else if (s.type === 'rect') { sumX += s.x + s.width - ox; sumY += s.y + s.height - oy; count++ }\n else if (s.type === 'circle') { sumX += s.cx - ox; sumY += s.cy - oy; count++ }\n else if (s.type === 'arrow') { sumX += s.x2 - ox; sumY += s.y2 - oy; count++ }\n })\n return {\n anchorX: count > 0 ? sumX / count : window.innerWidth / 2,\n anchorY: count > 0 ? sumY / count : window.innerHeight / 2,\n }\n }\n\n // After a shape is committed: freeze drawing, auto-show comment popup\n function onShapeCommitted() {\n frozen = true\n drawing = false\n overlay.style.pointerEvents = 'none'\n toolbar.style.pointerEvents = 'none'\n showCommentPopup()\n }\n\n function showCommentPopup() {\n if (commentPopup) commentPopup.remove()\n\n const { anchorX, anchorY } = getAnchor()\n const W = 300, MARGIN = 12\n let left = anchorX + MARGIN\n let top = anchorY + MARGIN\n if (left + W > window.innerWidth - MARGIN) left = anchorX - W - MARGIN\n if (left < MARGIN) left = MARGIN\n if (top + 210 > window.innerHeight - MARGIN) top = window.innerHeight - 210 - MARGIN\n if (top < MARGIN) top = MARGIN\n\n commentPopup = document.createElement('div')\n commentPopup.style.cssText = `\n position:fixed;left:${left}px;top:${top}px;z-index:2147483647;\n background:#fff;border-radius:12px;padding:16px;width:${W}px;\n box-shadow:0 4px 24px rgba(0,0,0,0.18);\n display:flex;flex-direction:column;gap:10px;\n font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;\n `\n commentPopup.innerHTML = `\n <div style=\"font-size:13px;font-weight:600;color:#374151;\">Add your comment</div>\n <textarea id=\"ufp-inline-text\" placeholder=\"Describe your feedback…\" rows=\"3\"\n style=\"width:100%;padding:9px 12px;border:1.5px solid #e5e7eb;border-radius:8px;\n font-size:13px;font-family:inherit;color:#111827;resize:none;\n min-height:72px;outline:none;box-sizing:border-box;\"></textarea>\n <div style=\"display:flex;gap:6px;justify-content:flex-end;align-items:center;\">\n <button id=\"ufp-popup-cancel\"\n style=\"padding:7px 12px;border-radius:8px;font-size:13px;font-weight:500;\n font-family:inherit;cursor:pointer;background:transparent;\n border:1.5px solid #e5e7eb;color:#6b7280;\">Cancel</button>\n <button id=\"ufp-popup-submit\" disabled\n style=\"padding:7px 14px;border-radius:8px;font-size:13px;font-weight:600;\n font-family:inherit;cursor:pointer;background:#6366f1;border:none;\n color:#fff;opacity:0.45;transition:opacity 0.15s;\">Submit</button>\n </div>\n `\n shadow.appendChild(commentPopup)\n\n const textarea = commentPopup.querySelector('#ufp-inline-text') as HTMLTextAreaElement\n const submitBtn = commentPopup.querySelector('#ufp-popup-submit') as HTMLButtonElement\n const cancelBtn = commentPopup.querySelector('#ufp-popup-cancel') as HTMLButtonElement\n\n textarea.addEventListener('input', () => {\n const ok = textarea.value.trim().length > 0\n submitBtn.disabled = !ok\n submitBtn.style.opacity = ok ? '1' : '0.45'\n })\n\n submitBtn.addEventListener('click', () => {\n const text = textarea.value.trim()\n if (!text) return\n const anchor = getAnchor()\n cleanup()\n // Convert fixed-element strokes (viewport coordinates) to full-page screenshot coordinates.\n const bottomShift = Math.max(0, document.documentElement.scrollHeight - window.innerHeight)\n const pageShapes = shapes.map((sh, i) => translateShapeY(sh, anchors[i] === 'bottom' ? bottomShift : 0))\n resolve({ shapes: pageShapes, text, ...anchor })\n })\n\n cancelBtn.addEventListener('click', () => {\n cleanup()\n resolve(null)\n })\n\n requestAnimationFrame(() => textarea.focus())\n }\n\n const toolbar = createToolbar(\n shadow,\n (tool) => { currentTool = tool },\n () => { cleanup(); resolve(null) },\n () => {\n if (shapes.length === 0) return\n if (commentPopup) {\n commentPopup.remove()\n commentPopup = null\n overlay.style.pointerEvents = 'auto'\n toolbar.style.pointerEvents = 'auto'\n frozen = false\n }\n shapes.pop()\n anchors.pop()\n redraw()\n }\n )\n\n shadow.appendChild(overlay)\n\n // ── Drawing event listeners ────────────────────────────────────────────\n\n // Drawing is driven by capture-phase listeners on window rather than by the overlay's own\n // hit-testing. Host pages can have elements that sit above the overlay (top-layer dialogs,\n // animated cards, elements with their own pointer handling); capturing on window gets the\n // event first no matter what is under the cursor, and we stop it reaching the page.\n const POINTER_EVENTS = ['pointerdown', 'pointermove', 'pointerup', 'pointercancel'] as const\n const BLOCKED_EVENTS = ['mousedown', 'mouseup', 'click', 'dblclick', 'auxclick', 'touchstart', 'touchmove', 'contextmenu', 'dragstart', 'selectstart'] as const\n\n // True when the event belongs to the plugin's own UI (toolbar, comment popup, trigger, panel).\n function isOwnUi(e: Event): boolean {\n const path = e.composedPath()\n return path.includes(shadow.host) && !path.includes(overlay)\n }\n\n function onBlockedEvent(e: Event) {\n if (isOwnUi(e)) return\n // The click that follows a finished gesture would otherwise activate whatever is under the cursor.\n if (frozen && performance.now() > suppressClicksUntil) return\n e.preventDefault()\n e.stopPropagation()\n }\n\n function onWindowEvent(e: Event) {\n if (frozen || isOwnUi(e)) return\n e.stopPropagation()\n const pe = e as PointerEvent\n if (e.type === 'pointerdown') onPointerDown(pe)\n else if (e.type === 'pointermove') onPointerMove(pe)\n else if (e.type === 'pointerup') onPointerUp(pe)\n else if (e.type === 'pointercancel') { drawing = false; if (previewEl) { previewEl.remove(); previewEl = null } }\n }\n\n function onPointerDown(e: PointerEvent) {\n if (e.button !== 0) return\n e.preventDefault()\n // Capture to the overlay (not e.target, which is the shadow host when seen from window)\n try { overlay.setPointerCapture(e.pointerId) } catch { /* ignore */ }\n drawing = true\n currentAnchor = detectFixedAnchor(e.clientX, e.clientY, shadow.host)\n startX = e.clientX + scrollOffset().x\n startY = e.clientY + scrollOffset().y\n if (currentTool === 'pen') currentStroke = [{ x: startX, y: startY }]\n }\n\n function onPointerMove(e: PointerEvent) {\n if (!drawing) return\n const cx = e.clientX + scrollOffset().x\n const cy = e.clientY + scrollOffset().y\n if (previewEl) previewEl.remove()\n\n if (currentTool === 'pen') {\n currentStroke.push({ x: cx, y: cy })\n previewEl = renderShapeToSVG({ type: 'pen', points: [...currentStroke], color: ANNOTATION_COLOR, width: STROKE_WIDTH })\n } else if (currentTool === 'rect') {\n const r: SVGRect = { type: 'rect', x: Math.min(startX, cx), y: Math.min(startY, cy), width: Math.abs(cx - startX), height: Math.abs(cy - startY), color: ANNOTATION_COLOR, strokeWidth: STROKE_WIDTH }\n previewEl = renderShapeToSVG(r)\n } else if (currentTool === 'circle') {\n const rx = Math.abs(cx - startX) / 2, ry = Math.abs(cy - startY) / 2\n const c: SVGCircle = { type: 'circle', cx: Math.min(startX, cx) + rx, cy: Math.min(startY, cy) + ry, rx, ry, color: ANNOTATION_COLOR, strokeWidth: STROKE_WIDTH }\n previewEl = renderShapeToSVG(c)\n } else if (currentTool === 'arrow') {\n const a: SVGArrow = { type: 'arrow', x1: startX, y1: startY, x2: cx, y2: cy, color: ANNOTATION_COLOR, width: STROKE_WIDTH }\n previewEl = renderShapeToSVG(a)\n }\n\n if (previewEl) (currentAnchor ? fixedLayer : layer).appendChild(previewEl)\n }\n\n function onPointerUp(e: PointerEvent) {\n if (!drawing) return\n suppressClicksUntil = performance.now() + 400\n drawing = false\n if (previewEl) { previewEl.remove(); previewEl = null }\n\n const cx = e.clientX + scrollOffset().x\n const cy = e.clientY + scrollOffset().y\n let committed = false\n\n if (currentTool === 'pen' && currentStroke.length > 1) {\n shapes.push({ type: 'pen', points: [...currentStroke], color: ANNOTATION_COLOR, width: STROKE_WIDTH })\n currentStroke = []\n committed = true\n } else if (currentTool === 'rect') {\n const w = Math.abs(cx - startX), h = Math.abs(cy - startY)\n if (w > 4 && h > 4) {\n shapes.push({ type: 'rect', x: Math.min(startX, cx), y: Math.min(startY, cy), width: w, height: h, color: ANNOTATION_COLOR, strokeWidth: STROKE_WIDTH })\n committed = true\n }\n } else if (currentTool === 'circle') {\n const rx = Math.abs(cx - startX) / 2, ry = Math.abs(cy - startY) / 2\n if (rx > 4 && ry > 4) {\n shapes.push({ type: 'circle', cx: Math.min(startX, cx) + rx, cy: Math.min(startY, cy) + ry, rx, ry, color: ANNOTATION_COLOR, strokeWidth: STROKE_WIDTH })\n committed = true\n }\n } else if (currentTool === 'arrow') {\n const dist = Math.sqrt((cx - startX) ** 2 + (cy - startY) ** 2)\n if (dist > 10) {\n shapes.push({ type: 'arrow', x1: startX, y1: startY, x2: cx, y2: cy, color: ANNOTATION_COLOR, width: STROKE_WIDTH })\n committed = true\n }\n }\n\n if (committed) {\n anchors.push(currentAnchor)\n redraw()\n onShapeCommitted()\n }\n }\n\n for (const t of POINTER_EVENTS) window.addEventListener(t, onWindowEvent, { capture: true, passive: false })\n for (const t of BLOCKED_EVENTS) window.addEventListener(t, onBlockedEvent, { capture: true, passive: false })\n })\n}\n\nfunction createToolbar(\n shadow: ShadowRoot,\n onToolChange: (t: DrawingTool) => void,\n onCancel: () => void,\n onUndo: () => void\n): HTMLElement {\n const toolbar = document.createElement('div')\n toolbar.className = 'ufp-toolbar'\n toolbar.innerHTML = `\n <span class=\"ufp-toolbar-label\">Draw to annotate</span>\n <div class=\"ufp-toolbar-sep\"></div>\n <button class=\"ufp-btn-icon active\" data-tool=\"pen\" title=\"Pen\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M12 20h9\"/><path d=\"M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z\"/>\n </svg>\n </button>\n <button class=\"ufp-btn-icon\" data-tool=\"rect\" title=\"Rectangle\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/></svg>\n </button>\n <button class=\"ufp-btn-icon\" data-tool=\"circle\" title=\"Circle\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/></svg>\n </button>\n <button class=\"ufp-btn-icon\" data-tool=\"arrow\" title=\"Arrow\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\"/><polyline points=\"12 5 19 12 12 19\"/>\n </svg>\n </button>\n <div class=\"ufp-toolbar-sep\"></div>\n <button class=\"ufp-btn-icon\" id=\"ufp-undo-btn\" title=\"Undo last stroke\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <polyline points=\"9 14 4 9 9 4\"/><path d=\"M20 20v-7a4 4 0 0 0-4-4H4\"/>\n </svg>\n </button>\n <div class=\"ufp-toolbar-sep\"></div>\n <button class=\"ufp-btn ufp-btn-ghost\" id=\"ufp-cancel-btn\">Cancel</button>\n `\n\n toolbar.querySelectorAll('[data-tool]').forEach((btn) => {\n btn.addEventListener('click', () => {\n toolbar.querySelectorAll('[data-tool]').forEach((b) => b.classList.remove('active'))\n btn.classList.add('active')\n onToolChange((btn as HTMLElement).dataset.tool as DrawingTool)\n })\n })\n\n toolbar.querySelector('#ufp-undo-btn')!.addEventListener('click', onUndo)\n toolbar.querySelector('#ufp-cancel-btn')!.addEventListener('click', onCancel)\n\n shadow.appendChild(toolbar)\n return toolbar\n}\n","export function formatRelativeTime(timestamp: number): string {\n const diff = Date.now() - timestamp\n const seconds = Math.floor(diff / 1000)\n if (seconds < 60) return 'just now'\n const minutes = Math.floor(seconds / 60)\n if (minutes < 60) return `${minutes}m ago`\n const hours = Math.floor(minutes / 60)\n if (hours < 24) return `${hours}h ago`\n const days = Math.floor(hours / 24)\n if (days < 7) return `${days}d ago`\n return new Date(timestamp).toLocaleDateString()\n}\n\nexport function getInitials(name: string): string {\n return name\n .split(' ')\n .filter(Boolean)\n .slice(0, 2)\n .map((w) => w[0].toUpperCase())\n .join('')\n}\n\nexport function escHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n}\n","import type { Reply, DatabaseAdapter } from '../core/types'\nimport { getUserName, setUserName } from '../core/state'\nimport { formatRelativeTime, getInitials, escHtml } from './utils'\n\nexport function createReplyThread(\n commentId: string,\n initialReplies: Reply[],\n adapter: DatabaseAdapter,\n onReplyAdded: (reply: Reply) => void\n): HTMLElement {\n let replies = [...initialReplies]\n\n const container = document.createElement('div')\n container.className = 'ufp-replies'\n\n const renderReplies = (list: Reply[]) => {\n const existing = container.querySelector('.ufp-replies-list')\n if (existing) existing.remove()\n\n const listEl = document.createElement('div')\n listEl.className = 'ufp-replies-list'\n listEl.style.cssText = 'display:flex;flex-direction:column;gap:12px;'\n\n if (list.length > 0) {\n const title = document.createElement('div')\n title.className = 'ufp-replies-title'\n title.textContent = `${list.length} ${list.length === 1 ? 'Reply' : 'Replies'}`\n listEl.appendChild(title)\n\n list.forEach((reply) => {\n const replyEl = document.createElement('div')\n replyEl.className = 'ufp-reply'\n replyEl.innerHTML = `\n <div class=\"ufp-reply-meta\">\n <div class=\"ufp-avatar\" style=\"width:22px;height:22px;font-size:10px;\">${getInitials(reply.authorName)}</div>\n <span class=\"ufp-reply-author\">${escHtml(reply.authorName)}</span>\n <span class=\"ufp-reply-time\">${formatRelativeTime(reply.createdAt)}</span>\n </div>\n <div class=\"ufp-reply-text\">${escHtml(reply.text)}</div>\n `\n listEl.appendChild(replyEl)\n })\n }\n\n container.insertBefore(listEl, container.querySelector('.ufp-reply-input-area'))\n }\n\n // Reply input\n const inputArea = document.createElement('div')\n inputArea.className = 'ufp-reply-input-area'\n\n const savedName = getUserName()\n inputArea.innerHTML = `\n <div class=\"ufp-reply-name-row\">\n <span class=\"ufp-reply-name-label\">Replying as:</span>\n <input\n class=\"ufp-input\"\n id=\"ufp-reply-name\"\n type=\"text\"\n placeholder=\"Your name\"\n value=\"${escHtml(savedName ?? '')}\"\n style=\"flex:1;\"\n />\n </div>\n <textarea\n class=\"ufp-textarea\"\n id=\"ufp-reply-text\"\n placeholder=\"Write a reply…\"\n rows=\"2\"\n style=\"min-height:60px;\"\n ></textarea>\n <div class=\"ufp-btn-row\">\n <button class=\"ufp-btn ufp-btn-primary\" id=\"ufp-reply-submit\" disabled style=\"font-size:13px;padding:7px 14px;\">Reply</button>\n </div>\n `\n\n const nameInput = inputArea.querySelector('#ufp-reply-name') as HTMLInputElement\n const textInput = inputArea.querySelector('#ufp-reply-text') as HTMLTextAreaElement\n const submitBtn = inputArea.querySelector('#ufp-reply-submit') as HTMLButtonElement\n\n const validate = () => {\n submitBtn.disabled =\n nameInput.value.trim().length === 0 || textInput.value.trim().length === 0\n }\n nameInput.addEventListener('input', validate)\n textInput.addEventListener('input', validate)\n\n submitBtn.addEventListener('click', async () => {\n const name = nameInput.value.trim()\n const text = textInput.value.trim()\n if (!name || !text) return\n\n submitBtn.disabled = true\n submitBtn.textContent = 'Sending…'\n setUserName(name)\n\n const reply: Omit<Reply, 'id'> = {\n commentId,\n authorName: name,\n text,\n createdAt: Date.now(),\n }\n\n try {\n const id = await adapter.addReply(reply)\n const fullReply: Reply = { id, ...reply }\n\n textInput.value = ''\n submitBtn.textContent = 'Reply'\n // Keep button disabled until user types again (validate fires on input)\n submitBtn.disabled = true\n\n // Update local list immediately (don't wait for next getReplies)\n replies = [...replies, fullReply]\n renderReplies(replies)\n\n onReplyAdded(fullReply)\n } catch (err) {\n console.error('[ui-feedback-plugin] Failed to save reply:', err)\n submitBtn.disabled = false\n submitBtn.textContent = 'Reply'\n }\n })\n\n container.appendChild(inputArea)\n renderReplies(replies)\n\n // Exposed so CommentDetailView can inject a reply from outside\n ;(container as any).addReply = (reply: Reply) => {\n replies = [...replies, reply]\n renderReplies(replies)\n }\n\n return container\n}\n","import type { FeedbackComment, Reply, DatabaseAdapter } from '../core/types'\nimport { setState } from '../core/state'\nimport { renderAnnotationToSVG } from '../drawing/renderer'\nimport { createReplyThread } from './ReplyThread'\nimport { formatRelativeTime, getInitials, escHtml } from './utils'\n\nexport function showCommentDetail(\n shadow: ShadowRoot,\n comment: FeedbackComment,\n replies: Reply[],\n adapter: DatabaseAdapter,\n onResolve: (id: string) => void,\n onReopen: (id: string) => void,\n onDelete: (id: string) => void,\n onReplyAdded?: (reply: Reply) => void\n): void {\n const detail = document.createElement('div')\n detail.className = 'ufp-detail'\n\n detail.innerHTML = `\n <div class=\"ufp-detail-image-area\">\n <div class=\"ufp-detail-image-wrap\">\n <img class=\"ufp-detail-screenshot\" src=\"${comment.screenshotUrl}\" alt=\"Page screenshot\" />\n <svg class=\"ufp-detail-annotation-svg\" xmlns=\"http://www.w3.org/2000/svg\">\n <defs>\n <marker id=\"ufp-detail-arrowhead\" markerWidth=\"10\" markerHeight=\"7\" refX=\"9\" refY=\"3.5\" orient=\"auto\">\n <polygon points=\"0 0, 10 3.5, 0 7\" fill=\"#6366f1\" />\n </marker>\n </defs>\n </svg>\n </div>\n </div>\n <div class=\"ufp-detail-sidebar\">\n <div class=\"ufp-detail-header\">\n <button id=\"ufp-detail-back\" style=\"\n display:flex;align-items:center;gap:6px;\n background:none;border:none;cursor:pointer;\n font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;\n font-size:13px;font-weight:600;color:#6b7280;padding:4px 0;\n \">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\">\n <polyline points=\"15 18 9 12 15 6\"/>\n </svg>\n Back to comments\n </button>\n ${comment.resolved ? '<span class=\"ufp-resolved-badge\">Resolved</span>' : ''}\n </div>\n <div class=\"ufp-detail-body\">\n <div class=\"ufp-detail-comment\">\n <div class=\"ufp-comment-meta\">\n <div class=\"ufp-avatar\">${getInitials(comment.authorName)}</div>\n <div>\n <div class=\"ufp-comment-author\">${escHtml(comment.authorName)}</div>\n <div class=\"ufp-comment-time\">${formatRelativeTime(comment.createdAt)}</div>\n </div>\n </div>\n <div class=\"ufp-detail-comment-text\">${escHtml(comment.text)}</div>\n <div class=\"ufp-detail-actions\" id=\"ufp-detail-actions\"></div>\n </div>\n <div id=\"ufp-detail-replies\"></div>\n </div>\n </div>\n `\n\n shadow.appendChild(detail)\n\n detail.querySelector('#ufp-detail-back')!.addEventListener('click', () => {\n detail.remove()\n setState({ panelOpen: true })\n })\n\n // Action buttons\n const actionsEl = detail.querySelector('#ufp-detail-actions') as HTMLElement\n\n if (!comment.resolved) {\n const resolveBtn = document.createElement('button')\n resolveBtn.className = 'ufp-btn ufp-btn-primary'\n resolveBtn.style.fontSize = '13px'\n resolveBtn.innerHTML = `\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"\n style=\"vertical-align:middle;margin-right:4px\"><polyline points=\"20 6 9 17 4 12\"/></svg>\n Mark Resolved\n `\n resolveBtn.addEventListener('click', async () => {\n resolveBtn.disabled = true\n resolveBtn.textContent = 'Resolving…'\n await adapter.updateComment(comment.id, { resolved: true })\n onResolve(comment.id)\n detail.remove()\n setState({ panelOpen: true })\n })\n actionsEl.appendChild(resolveBtn)\n } else {\n const reopenBtn = document.createElement('button')\n reopenBtn.className = 'ufp-btn ufp-btn-ghost'\n reopenBtn.style.fontSize = '13px'\n reopenBtn.innerHTML = `\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"\n style=\"vertical-align:middle;margin-right:4px\"><polyline points=\"9 14 4 9 9 4\"/><path d=\"M20 20v-7a4 4 0 0 0-4-4H4\"/></svg>\n Reopen\n `\n reopenBtn.addEventListener('click', async () => {\n reopenBtn.disabled = true\n reopenBtn.textContent = 'Reopening…'\n await adapter.updateComment(comment.id, { resolved: false })\n onReopen(comment.id)\n detail.remove()\n setState({ panelOpen: true })\n })\n actionsEl.appendChild(reopenBtn)\n }\n\n const deleteBtn = document.createElement('button')\n deleteBtn.className = 'ufp-btn ufp-btn-ghost'\n deleteBtn.style.fontSize = '13px'\n deleteBtn.textContent = 'Delete'\n deleteBtn.addEventListener('click', async () => {\n if (!confirm('Delete this comment and all its replies?')) return\n deleteBtn.disabled = true\n await adapter.deleteComment(comment.id)\n onDelete(comment.id)\n detail.remove()\n setState({ panelOpen: true })\n })\n actionsEl.appendChild(deleteBtn)\n\n // Reply thread\n const repliesContainer = detail.querySelector('#ufp-detail-replies') as HTMLElement\n const replyThread = createReplyThread(comment.id, replies, adapter, (newReply) => {\n // Update the thread UI in place\n ;(replyThread as any).addReply?.(newReply)\n // Notify the panel so it can update the badge count\n onReplyAdded?.(newReply)\n })\n repliesContainer.appendChild(replyThread)\n\n // Render annotation over screenshot after image loads\n const img = detail.querySelector('.ufp-detail-screenshot') as HTMLImageElement\n const annotationSvg = detail.querySelector('.ufp-detail-annotation-svg') as SVGSVGElement\n\n const renderAnnotation = () => {\n const w = img.clientWidth\n const h = img.clientHeight\n annotationSvg.setAttribute('viewBox', `0 0 ${w} ${h}`)\n renderAnnotationToSVG(annotationSvg, comment.annotation, w, h)\n annotationSvg.querySelectorAll('[marker-end]').forEach((el) => {\n el.setAttribute('marker-end', 'url(#ufp-detail-arrowhead)')\n })\n }\n\n if (img.complete && img.naturalWidth > 0) {\n renderAnnotation()\n } else {\n img.addEventListener('load', renderAnnotation)\n }\n}\n","import type { FeedbackComment, Reply, DatabaseAdapter } from '../core/types'\nimport { getState, setState, subscribe } from '../core/state'\nimport { showCommentDetail } from './CommentDetailView'\nimport { formatRelativeTime, getInitials, escHtml } from './utils'\n\nexport function createCommentsPanel(\n shadow: ShadowRoot,\n adapter: DatabaseAdapter,\n onNewFeedback: () => void\n): HTMLElement {\n const panel = document.createElement('div')\n panel.className = 'ufp-panel'\n\n panel.innerHTML = `\n <div class=\"ufp-panel-header\">\n <span class=\"ufp-panel-title\">Feedback</span>\n <div style=\"display:flex;gap:8px;align-items:center;\">\n <button id=\"ufp-new-feedback\" class=\"ufp-btn ufp-btn-primary\" style=\"font-size:13px;padding:7px 14px;display:flex;align-items:center;gap:5px;\">\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><line x1=\"12\" y1=\"5\" x2=\"12\" y2=\"19\"/><line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\"/></svg>\n New\n </button>\n <button class=\"ufp-btn-icon\" id=\"ufp-panel-close\" title=\"Close panel\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n </div>\n </div>\n <div class=\"ufp-filter-tabs\">\n <button class=\"ufp-filter-tab active\" data-filter=\"open\">Open</button>\n <button class=\"ufp-filter-tab\" data-filter=\"resolved\">Resolved</button>\n </div>\n <div class=\"ufp-panel-list\" id=\"ufp-comment-list\"></div>\n `\n\n shadow.appendChild(panel)\n\n panel.querySelector('#ufp-panel-close')!.addEventListener('click', () => {\n setState({ panelOpen: false })\n })\n\n panel.querySelector('#ufp-new-feedback')!.addEventListener('click', () => {\n setState({ panelOpen: false })\n onNewFeedback()\n })\n\n panel.querySelectorAll('.ufp-filter-tab').forEach((tab) => {\n tab.addEventListener('click', () => {\n panel.querySelectorAll('.ufp-filter-tab').forEach((t) => t.classList.remove('active'))\n tab.classList.add('active')\n setState({ filter: (tab as HTMLElement).dataset.filter as 'open' | 'resolved' })\n })\n })\n\n const listEl = panel.querySelector('#ufp-comment-list') as HTMLElement\n\n // Cache is used ONLY for the reply-count badge shown in the panel list.\n // The detail view always fetches fresh replies.\n const repliesCache = new Map<string, Reply[]>()\n\n const render = async (comments: FeedbackComment[], filter: 'open' | 'resolved') => {\n const filtered = comments.filter((c) => (filter === 'open' ? !c.resolved : c.resolved))\n listEl.innerHTML = ''\n\n if (filtered.length === 0) {\n listEl.innerHTML = `\n <div class=\"ufp-panel-empty\">\n <svg width=\"32\" height=\"32\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" opacity=\"0.4\">\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/>\n </svg>\n <span>No ${filter} comments yet</span>\n ${filter === 'open' ? '<span style=\"font-size:12px;color:#9ca3af;\">Click <strong>New</strong> to add feedback</span>' : ''}\n </div>\n `\n return\n }\n\n for (const comment of filtered) {\n if (!repliesCache.has(comment.id)) {\n try {\n repliesCache.set(comment.id, await adapter.getReplies(comment.id))\n } catch {\n repliesCache.set(comment.id, [])\n }\n }\n listEl.appendChild(\n createCommentCard(comment, repliesCache, shadow, adapter, onNewFeedback)\n )\n }\n }\n\n subscribe(async (state) => {\n panel.classList.toggle('open', state.panelOpen)\n if (state.panelOpen) {\n await render(state.comments, state.filter)\n }\n })\n\n return panel\n}\n\nfunction createCommentCard(\n comment: FeedbackComment,\n repliesCache: Map<string, Reply[]>,\n shadow: ShadowRoot,\n adapter: DatabaseAdapter,\n onNewFeedback: () => void\n): HTMLElement {\n const cachedReplies = repliesCache.get(comment.id) ?? []\n\n const card = document.createElement('div')\n card.className = 'ufp-comment-card'\n card.dataset.id = comment.id\n\n card.innerHTML = `\n <div class=\"ufp-comment-card-header\">\n <div class=\"ufp-comment-meta\">\n <div class=\"ufp-avatar\">${getInitials(comment.authorName)}</div>\n <div>\n <div class=\"ufp-comment-author\">${escHtml(comment.authorName)}</div>\n <div class=\"ufp-comment-time\">${formatRelativeTime(comment.createdAt)}</div>\n </div>\n </div>\n ${comment.resolved ? '<span class=\"ufp-resolved-badge\">Resolved</span>' : ''}\n </div>\n <img class=\"ufp-comment-thumbnail\" src=\"${comment.screenshotUrl}\" alt=\"Screenshot\" loading=\"lazy\" />\n <div class=\"ufp-comment-text\">${escHtml(comment.text)}</div>\n <div class=\"ufp-comment-footer\">\n <span class=\"ufp-reply-count\">\n ${cachedReplies.length > 0\n ? `${cachedReplies.length} ${cachedReplies.length === 1 ? 'reply' : 'replies'}`\n : 'No replies yet — click to reply'}\n </span>\n </div>\n `\n\n card.addEventListener('click', async () => {\n // Always fetch fresh replies when opening the detail so nothing is stale\n let freshReplies: Reply[] = repliesCache.get(comment.id) ?? []\n try {\n freshReplies = await adapter.getReplies(comment.id)\n repliesCache.set(comment.id, freshReplies)\n } catch (err) {\n console.error('[ui-feedback-plugin] Failed to fetch replies:', err)\n }\n\n showCommentDetail(\n shadow,\n comment,\n freshReplies,\n adapter,\n (id) => {\n const state = getState()\n setState({ comments: state.comments.map((c) => c.id === id ? { ...c, resolved: true } : c) })\n },\n (id) => {\n const state = getState()\n setState({ comments: state.comments.map((c) => c.id === id ? { ...c, resolved: false } : c) })\n },\n (id) => {\n const state = getState()\n setState({ comments: state.comments.filter((c) => c.id !== id) })\n repliesCache.delete(id)\n },\n // When a reply is added in the detail view, update badge count immediately\n (reply) => {\n const existing = repliesCache.get(comment.id) ?? []\n repliesCache.set(comment.id, [...existing, reply])\n // Nudge state so the panel re-renders with the new reply count\n const state = getState()\n setState({ comments: [...state.comments] })\n }\n )\n })\n\n return card\n}\n","import type { AnnotationData } from '../core/types'\n\nexport async function capturePageScreenshot(): Promise<string> {\n try {\n const html2canvas = await loadHtml2Canvas()\n return await captureWithHtml2Canvas(html2canvas)\n } catch (err) {\n console.error('[ui-feedback-plugin] capturePageScreenshot failed:', err)\n return captureViewportFallback()\n }\n}\n\nasync function loadHtml2Canvas(): Promise<typeof import('html2canvas-pro').default> {\n const mod = await import('html2canvas-pro')\n return mod.default\n}\n\nasync function captureWithHtml2Canvas(\n html2canvas: typeof import('html2canvas-pro').default\n): Promise<string> {\n const scrollX = window.scrollX\n const scrollY = window.scrollY\n\n // Scroll to top so full page renders from the start\n window.scrollTo(0, 0)\n\n try {\n const canvas = await html2canvas(document.body, {\n allowTaint: false,\n useCORS: true,\n logging: false,\n scale: Math.min(window.devicePixelRatio, 2),\n width: document.documentElement.scrollWidth,\n height: document.documentElement.scrollHeight,\n windowWidth: document.documentElement.scrollWidth,\n windowHeight: document.documentElement.scrollHeight,\n x: 0,\n y: 0,\n ignoreElements: (el) => {\n // Skip the feedback widget itself\n return (el as HTMLElement).tagName === 'UI-FEEDBACK-PLUGIN'\n },\n })\n return canvas.toDataURL('image/png')\n } finally {\n window.scrollTo(scrollX, scrollY)\n }\n}\n\nfunction captureViewportFallback(): string {\n // Fallback: return a placeholder data URL indicating screenshot unavailable\n const canvas = document.createElement('canvas')\n canvas.width = window.innerWidth\n canvas.height = window.innerHeight\n const ctx = canvas.getContext('2d')!\n ctx.fillStyle = '#f3f4f6'\n ctx.fillRect(0, 0, canvas.width, canvas.height)\n ctx.fillStyle = '#9ca3af'\n ctx.font = '16px -apple-system, sans-serif'\n ctx.textAlign = 'center'\n ctx.fillText('Screenshot unavailable', canvas.width / 2, canvas.height / 2)\n return canvas.toDataURL('image/png')\n}\n\n/**\n * Draws the annotation shapes onto the screenshot so the stored PNG is self-contained.\n * Shapes are in full-page CSS pixels; the screenshot is scaled by (image width / pageWidth).\n */\nexport async function drawAnnotationsOnScreenshot(\n dataUrl: string,\n annotation: AnnotationData\n): Promise<string> {\n try {\n const img = await new Promise<HTMLImageElement>((resolve, reject) => {\n const el = new Image()\n el.onload = () => resolve(el)\n el.onerror = () => reject(new Error('screenshot failed to load'))\n el.src = dataUrl\n })\n const canvas = document.createElement('canvas')\n canvas.width = img.naturalWidth\n canvas.height = img.naturalHeight\n const ctx = canvas.getContext('2d')!\n ctx.drawImage(img, 0, 0)\n\n const sx = canvas.width / annotation.pageWidth\n const sy = canvas.height / annotation.pageHeight\n const sw = Math.min(sx, sy)\n ctx.lineCap = 'round'\n ctx.lineJoin = 'round'\n\n for (const shape of annotation.shapes) {\n ctx.strokeStyle = shape.color\n ctx.fillStyle = shape.color\n ctx.beginPath()\n switch (shape.type) {\n case 'pen': {\n ctx.lineWidth = shape.width * sw\n shape.points.forEach((p, i) => (i === 0 ? ctx.moveTo(p.x * sx, p.y * sy) : ctx.lineTo(p.x * sx, p.y * sy)))\n ctx.stroke()\n break\n }\n case 'rect': {\n ctx.lineWidth = shape.strokeWidth * sw\n ctx.roundRect(shape.x * sx, shape.y * sy, shape.width * sx, shape.height * sy, 3 * sw)\n ctx.stroke()\n break\n }\n case 'circle': {\n ctx.lineWidth = shape.strokeWidth * sw\n ctx.ellipse(shape.cx * sx, shape.cy * sy, shape.rx * sx, shape.ry * sy, 0, 0, Math.PI * 2)\n ctx.stroke()\n break\n }\n case 'arrow': {\n const w = shape.width * sw\n const x1 = shape.x1 * sx, y1 = shape.y1 * sy, x2 = shape.x2 * sx, y2 = shape.y2 * sy\n ctx.lineWidth = w\n ctx.lineCap = 'butt'\n ctx.moveTo(x1, y1)\n ctx.lineTo(x2, y2)\n ctx.stroke()\n ctx.lineCap = 'round'\n // Same arrowhead geometry as the SVG marker (10x7 in stroke-width units, tip 1 unit past the end)\n const len = Math.hypot(x2 - x1, y2 - y1) || 1\n const dx = (x2 - x1) / len, dy = (y2 - y1) / len\n const tipX = x2 + dx * w, tipY = y2 + dy * w\n const bx = tipX - dx * 10 * w, by = tipY - dy * 10 * w\n ctx.beginPath()\n ctx.moveTo(tipX, tipY)\n ctx.lineTo(bx - dy * 3.5 * w, by + dx * 3.5 * w)\n ctx.lineTo(bx + dy * 3.5 * w, by - dx * 3.5 * w)\n ctx.closePath()\n ctx.fill()\n break\n }\n }\n }\n return canvas.toDataURL('image/png')\n } catch (err) {\n console.error('[ui-feedback-plugin] drawAnnotationsOnScreenshot failed:', err)\n return dataUrl\n }\n}\n","import type {\n PluginConfig,\n DatabaseAdapter,\n ScreenshotAdapter,\n FeedbackComment,\n AnnotationData,\n} from './core/types'\nimport { getState, setState, getUserName, normalizeUrl } from './core/state'\nimport { getStyles } from './ui/styles'\nimport { createFloatingTrigger } from './ui/FloatingTrigger'\nimport { showNameModal } from './ui/NameModal'\nimport { showAnnotationOverlay } from './ui/AnnotationOverlay'\nimport { createCommentsPanel } from './ui/CommentsPanel'\nimport { capturePageScreenshot, drawAnnotationsOnScreenshot } from './screenshot/capture'\n\nconst TAG = 'ui-feedback-plugin'\n\nasync function createAdapters(config: PluginConfig): Promise<{\n db: DatabaseAdapter\n screenshot: ScreenshotAdapter\n}> {\n const { backend } = config\n\n if (backend.provider === 'firebase') {\n const { FirebaseAdapter } = await import('./adapters/firebase')\n const adapter = new FirebaseAdapter(backend)\n return { db: adapter, screenshot: adapter }\n }\n\n if (backend.provider === 'supabase') {\n const { SupabaseAdapter } = await import('./adapters/supabase')\n const adapter = new SupabaseAdapter(backend.url, backend.anonKey)\n return { db: adapter, screenshot: adapter }\n }\n\n if (backend.provider === 'aws') {\n const { AWSAdapter } = await import('./adapters/aws')\n const adapter = new AWSAdapter(backend)\n return { db: adapter, screenshot: adapter }\n }\n\n if (backend.provider === 'local') {\n const { LocalAdapter } = await import('./adapters/local')\n const adapter = new LocalAdapter()\n return { db: adapter, screenshot: adapter }\n }\n\n throw new Error('[ui-feedback-plugin] Unknown backend provider')\n}\n\nexport function initFeedbackPlugin(config: PluginConfig): () => void {\n if (customElements.get(TAG)) {\n console.warn('[ui-feedback-plugin] Already initialized.')\n return () => {}\n }\n\n const primaryColor = config.theme?.primaryColor ?? '#6366f1'\n const projectKey = config.projectKey ?? 'default'\n const position = config.position ?? 'bottom-right'\n\n setState({ config })\n\n class FeedbackPlugin extends HTMLElement {\n private shadow!: ShadowRoot\n private db!: DatabaseAdapter\n private screenshot!: ScreenshotAdapter\n private unsubscribe: (() => void) | null = null\n\n constructor() {\n super()\n this.shadow = this.attachShadow({ mode: 'open' })\n }\n\n async connectedCallback() {\n const style = document.createElement('style')\n style.textContent = getStyles(primaryColor)\n this.shadow.appendChild(style)\n\n try {\n const adapters = await createAdapters(config)\n this.db = adapters.db\n this.screenshot = adapters.screenshot\n } catch (err) {\n console.error('[ui-feedback-plugin] Failed to initialise backend:', err)\n return\n }\n\n this.mount()\n }\n\n disconnectedCallback() {\n this.unsubscribe?.()\n }\n\n private mount() {\n const pageUrl = normalizeUrl(window.location.href)\n\n createCommentsPanel(this.shadow, this.db, () => this.startAnnotation())\n\n createFloatingTrigger(\n this.shadow,\n position,\n () => this.startAnnotation(),\n () => setState({ panelOpen: !getState().panelOpen })\n )\n\n this.unsubscribe = this.db.subscribeToComments(pageUrl, projectKey, (comments) => {\n setState({ comments })\n })\n }\n\n private async startAnnotation() {\n let userName = getUserName()\n if (!userName) {\n userName = await showNameModal(this.shadow)\n if (!userName) return\n }\n\n setState({ active: true, panelOpen: false })\n\n const result = await showAnnotationOverlay(this.shadow)\n\n if (!result) {\n setState({ active: false })\n return\n }\n\n setState({ active: false })\n\n this.showToast('Saving comment…', 'info')\n try {\n await this.saveComment(userName, result.text, result.shapes)\n this.showToast('Comment saved!', 'success')\n } catch (err) {\n console.error('[ui-feedback-plugin] Failed to save comment:', err)\n this.showToast('Failed to save — check the browser console for details.', 'error')\n return\n }\n\n setState({ panelOpen: true })\n }\n\n private async saveComment(\n authorName: string,\n text: string,\n shapes: AnnotationData['shapes']\n ) {\n const pageUrl = normalizeUrl(window.location.href)\n const annotation: AnnotationData = {\n shapes,\n pageWidth: document.documentElement.scrollWidth,\n pageHeight: document.documentElement.scrollHeight,\n }\n\n this.style.visibility = 'hidden'\n let screenshotDataUrl: string\n try {\n screenshotDataUrl = await capturePageScreenshot()\n } finally {\n this.style.visibility = ''\n }\n screenshotDataUrl = await drawAnnotationsOnScreenshot(screenshotDataUrl, annotation)\n\n const placeholder: Omit<FeedbackComment, 'id'> = {\n pageUrl,\n projectKey,\n authorName,\n text,\n screenshotUrl: '',\n annotation,\n resolved: false,\n createdAt: Date.now(),\n }\n\n const commentId = await this.db.addComment(placeholder)\n const screenshotUrl = await this.screenshot.uploadScreenshot(commentId, screenshotDataUrl)\n await this.db.updateComment(commentId, { screenshotUrl })\n }\n\n private showToast(message: string, type: 'info' | 'success' | 'error') {\n const existing = this.shadow.querySelector('.ufp-toast')\n existing?.remove()\n\n const colors = { info: '#6366f1', success: '#10b981', error: '#ef4444' }\n\n const toast = document.createElement('div')\n toast.className = 'ufp-toast'\n toast.style.cssText = `\n position:fixed;bottom:80px;left:50%;transform:translateX(-50%);\n background:${colors[type]};color:white;padding:10px 20px;border-radius:8px;\n font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;\n font-size:13px;font-weight:500;z-index:2147483647;\n box-shadow:0 4px 12px rgba(0,0,0,0.2);pointer-events:none;\n max-width:360px;text-align:center;\n `\n toast.textContent = message\n this.shadow.appendChild(toast)\n setTimeout(() => toast.remove(), type === 'error' ? 6000 : 2500)\n }\n }\n\n customElements.define(TAG, FeedbackPlugin)\n\n const el = document.createElement(TAG)\n document.body.appendChild(el)\n\n return () => el.remove()\n}\n\nexport type { PluginConfig }\n"],"names":["listeners","state","getState","setState","partial","fn","subscribe","getUserName","stored","setUserName","name","normalizeUrl","url","u","getStyles","primaryColor","POSITION_STYLES","BG","BG_HOVER","BG_PANEL_ACTIVE","createFloatingTrigger","shadow","position","onStartAnnotation","onTogglePanel","wrapper","annotateBtn","applyBtnBase","sep","panelBtn","annotateLabel","panelCount","e","addHover","openCount","c","makeDraggable","btn","getNormal","hoverColor","el","startX","startY","initLeft","initTop","rect","onMove","dx","dy","onUp","showNameModal","resolve","backdrop","input","submitBtn","submit","ANNOTATION_COLOR","STROKE_WIDTH","renderShapeToSVG","shape","scaleX","scaleY","path","pointsToPath","ellipse","g","line","renderAnnotationToSVG","svgEl","annotation","displayWidth","displayHeight","defs","points","p","d","i","detectFixedAnchor","clientX","clientY","host","vw","vh","hit","cs","r","translateShapeY","showAnnotationOverlay","currentTool","shapes","drawing","currentStroke","previewEl","commentPopup","frozen","suppressClicksUntil","anchors","currentAnchor","scrollOffset","overlay","svg","layer","fixedLayer","syncLayer","cleanup","t","POINTER_EVENTS","onWindowEvent","BLOCKED_EVENTS","onBlockedEvent","toolbar","redraw","s","getAnchor","sumX","sumY","count","ox","oy","onShapeCommitted","showCommentPopup","anchorX","anchorY","W","MARGIN","left","top","textarea","cancelBtn","ok","text","anchor","bottomShift","pageShapes","sh","createToolbar","tool","isOwnUi","pe","onPointerDown","onPointerMove","onPointerUp","cx","cy","rx","ry","committed","w","h","onToolChange","onCancel","onUndo","b","formatRelativeTime","timestamp","diff","seconds","minutes","hours","days","getInitials","escHtml","str","createReplyThread","commentId","initialReplies","adapter","onReplyAdded","replies","container","renderReplies","list","existing","listEl","title","reply","replyEl","inputArea","savedName","nameInput","textInput","validate","fullReply","err","showCommentDetail","comment","onResolve","onReopen","onDelete","detail","actionsEl","reopenBtn","resolveBtn","deleteBtn","repliesContainer","replyThread","newReply","_a","img","annotationSvg","renderAnnotation","createCommentsPanel","onNewFeedback","panel","tab","repliesCache","render","comments","filter","filtered","createCommentCard","cachedReplies","card","freshReplies","id","capturePageScreenshot","html2canvas","loadHtml2Canvas","captureWithHtml2Canvas","captureViewportFallback","scrollX","scrollY","canvas","ctx","drawAnnotationsOnScreenshot","dataUrl","reject","sx","sy","sw","x1","y1","x2","y2","len","tipX","tipY","bx","by","TAG","createAdapters","config","backend","FirebaseAdapter","SupabaseAdapter","AWSAdapter","LocalAdapter","initFeedbackPlugin","projectKey","FeedbackPlugin","__publicField","style","adapters","pageUrl","userName","result","authorName","screenshotDataUrl","placeholder","screenshotUrl","message","type","colors","toast"],"mappings":";;;AAaA,MAAMA,wBAAgB,IAAA;AAEtB,IAAIC,IAAqB;AAAA,EACvB,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,UAAU,CAAA;AAAA,EACV,6BAAa,IAAA;AAAA,EACb,mBAAmB;AAAA,EACnB,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,QAAQ,CAAA;AACV;AAEO,SAASC,IAAwB;AACtC,SAAOD;AACT;AAEO,SAASE,EAASC,GAAqC;AAC5D,EAAAH,IAAQ,EAAE,GAAGA,GAAO,GAAGG,EAAA,GACvBJ,EAAU,QAAQ,CAACK,MAAOA,EAAGJ,CAAK,CAAC;AACrC;AAEO,SAASK,GAAUD,GAA8C;AACtE,SAAAL,EAAU,IAAIK,CAAE,GACT,MAAML,EAAU,OAAOK,CAAE;AAClC;AAEO,SAASE,KAA6B;AAC3C,MAAIN,EAAM,SAAU,QAAOA,EAAM;AACjC,QAAMO,IAAS,aAAa,QAAQ,eAAe;AACnD,SAAIA,KACFL,EAAS,EAAE,UAAUK,GAAQ,GACtBA,KAEF;AACT;AAEO,SAASC,GAAYC,GAAoB;AAC9C,eAAa,QAAQ,iBAAiBA,CAAI,GAC1CP,EAAS,EAAE,UAAUO,GAAM;AAC7B;AAEO,SAASC,EAAaC,GAAqB;AAChD,MAAI;AACF,UAAMC,IAAI,IAAI,IAAID,CAAG;AACrB,WAAOC,EAAE,SAASA,EAAE;AAAA,EACtB,QAAQ;AACN,WAAOD;AAAA,EACT;AACF;AC9DO,SAASE,GAAUC,GAA8B;AACtD,SAAO;AAAA;AAAA,uBAEcA,CAAY;AAAA,+CACYA,CAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;A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"}
|
|
1
|
+
{"version":3,"file":"ui-feedback-plugin.es.js","sources":["../src/core/state.ts","../src/ui/styles.ts","../src/ui/FloatingTrigger.ts","../src/ui/NameModal.ts","../src/drawing/renderer.ts","../src/ui/AnnotationOverlay.ts","../src/ui/utils.ts","../src/ui/ReplyThread.ts","../src/ui/CommentDetailView.ts","../src/ui/CommentsPanel.ts","../src/screenshot/capture.ts","../src/index.ts"],"sourcesContent":["import type { FeedbackComment, Reply, PluginConfig } from './types'\n\nexport interface PluginState {\n active: boolean\n userName: string | null\n comments: FeedbackComment[]\n replies: Map<string, Reply[]>\n selectedCommentId: string | null\n panelOpen: boolean\n filter: 'open' | 'resolved'\n config: PluginConfig\n}\n\nconst listeners = new Set<(state: PluginState) => void>()\n\nlet state: PluginState = {\n active: false,\n userName: null,\n comments: [],\n replies: new Map(),\n selectedCommentId: null,\n panelOpen: false,\n filter: 'open',\n config: {} as PluginConfig,\n}\n\nexport function getState(): PluginState {\n return state\n}\n\nexport function setState(partial: Partial<PluginState>): void {\n state = { ...state, ...partial }\n listeners.forEach((fn) => fn(state))\n}\n\nexport function subscribe(fn: (state: PluginState) => void): () => void {\n listeners.add(fn)\n return () => listeners.delete(fn)\n}\n\nexport function getUserName(): string | null {\n if (state.userName) return state.userName\n const stored = localStorage.getItem('ufp_user_name')\n if (stored) {\n setState({ userName: stored })\n return stored\n }\n return null\n}\n\nexport function setUserName(name: string): void {\n localStorage.setItem('ufp_user_name', name)\n setState({ userName: name })\n}\n\nexport function normalizeUrl(url: string): string {\n try {\n const u = new URL(url)\n return u.origin + u.pathname\n } catch {\n return url\n }\n}\n","export function getStyles(primaryColor: string): string {\n return `\n :host {\n --ufp-primary: ${primaryColor};\n --ufp-primary-dark: color-mix(in srgb, ${primaryColor} 80%, black);\n --ufp-white: #ffffff;\n --ufp-gray-50: #f9fafb;\n --ufp-gray-100: #f3f4f6;\n --ufp-gray-200: #e5e7eb;\n --ufp-gray-300: #d1d5db;\n --ufp-gray-400: #9ca3af;\n --ufp-gray-500: #6b7280;\n --ufp-gray-600: #4b5563;\n --ufp-gray-700: #374151;\n --ufp-gray-800: #1f2937;\n --ufp-gray-900: #111827;\n --ufp-green: #10b981;\n --ufp-red: #ef4444;\n --ufp-shadow: 0 4px 24px rgba(0,0,0,0.18);\n --ufp-radius: 12px;\n --ufp-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-family: var(--ufp-font);\n all: initial;\n }\n\n * { box-sizing: border-box; }\n\n /* Trigger */\n .ufp-trigger {\n position: fixed;\n z-index: 2147483646;\n display: flex;\n align-items: center;\n gap: 8px;\n background: var(--ufp-primary);\n color: var(--ufp-white);\n border: none;\n border-radius: 28px;\n padding: 10px 18px 10px 14px;\n cursor: pointer;\n box-shadow: var(--ufp-shadow);\n font-family: var(--ufp-font);\n font-size: 14px;\n font-weight: 600;\n user-select: none;\n transition: background 0.15s, transform 0.1s;\n touch-action: none;\n }\n .ufp-trigger:hover { background: var(--ufp-primary-dark); }\n .ufp-trigger:active { transform: scale(0.97); }\n .ufp-trigger.active { background: var(--ufp-gray-800); }\n .ufp-trigger-icon { width: 20px; height: 20px; flex-shrink: 0; }\n\n /* Modal overlay */\n .ufp-modal-backdrop {\n position: fixed;\n inset: 0;\n background: rgba(0,0,0,0.45);\n z-index: 2147483646;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n .ufp-modal {\n background: var(--ufp-white);\n border-radius: var(--ufp-radius);\n padding: 28px;\n width: 360px;\n box-shadow: var(--ufp-shadow);\n display: flex;\n flex-direction: column;\n gap: 16px;\n }\n .ufp-modal h2 {\n margin: 0;\n font-size: 18px;\n font-weight: 700;\n color: var(--ufp-gray-900);\n font-family: var(--ufp-font);\n }\n .ufp-modal p {\n margin: 0;\n font-size: 14px;\n color: var(--ufp-gray-500);\n font-family: var(--ufp-font);\n }\n\n /* Form elements */\n .ufp-input {\n width: 100%;\n padding: 10px 14px;\n border: 1.5px solid var(--ufp-gray-200);\n border-radius: 8px;\n font-size: 14px;\n font-family: var(--ufp-font);\n color: var(--ufp-gray-900);\n outline: none;\n transition: border-color 0.15s;\n }\n .ufp-input:focus { border-color: var(--ufp-primary); }\n .ufp-textarea {\n width: 100%;\n padding: 10px 14px;\n border: 1.5px solid var(--ufp-gray-200);\n border-radius: 8px;\n font-size: 14px;\n font-family: var(--ufp-font);\n color: var(--ufp-gray-900);\n outline: none;\n resize: vertical;\n min-height: 80px;\n transition: border-color 0.15s;\n }\n .ufp-textarea:focus { border-color: var(--ufp-primary); }\n\n /* Buttons */\n .ufp-btn {\n padding: 9px 18px;\n border-radius: 8px;\n font-size: 14px;\n font-weight: 600;\n font-family: var(--ufp-font);\n cursor: pointer;\n border: none;\n transition: background 0.15s, opacity 0.15s;\n }\n .ufp-btn-primary {\n background: var(--ufp-primary);\n color: var(--ufp-white);\n }\n .ufp-btn-primary:hover { background: var(--ufp-primary-dark); }\n .ufp-btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }\n .ufp-btn-ghost {\n background: transparent;\n color: var(--ufp-gray-600);\n border: 1.5px solid var(--ufp-gray-200);\n }\n .ufp-btn-ghost:hover { background: var(--ufp-gray-50); }\n .ufp-btn-danger {\n background: var(--ufp-red);\n color: var(--ufp-white);\n }\n .ufp-btn-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n padding: 0;\n border-radius: 6px;\n background: transparent;\n border: none;\n cursor: pointer;\n color: var(--ufp-gray-600);\n font-size: 16px;\n transition: background 0.12s, color 0.12s;\n }\n .ufp-btn-icon:hover { background: var(--ufp-gray-100); color: var(--ufp-gray-900); }\n .ufp-btn-icon.active { background: var(--ufp-primary); color: var(--ufp-white); }\n\n .ufp-btn-row {\n display: flex;\n gap: 8px;\n justify-content: flex-end;\n }\n\n /* Annotation overlay */\n .ufp-overlay {\n position: fixed;\n inset: 0;\n z-index: 2147483645;\n cursor: crosshair;\n background: rgba(99,102,241,0.04);\n }\n .ufp-overlay-svg {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n pointer-events: none;\n }\n\n /* Toolbar */\n .ufp-toolbar {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n z-index: 2147483647;\n background: var(--ufp-white);\n border-radius: 12px;\n box-shadow: var(--ufp-shadow);\n padding: 8px;\n display: flex;\n align-items: center;\n gap: 4px;\n top: 24px;\n left: 50%;\n transform: translateX(-50%);\n }\n .ufp-toolbar-sep {\n width: 1px;\n height: 24px;\n background: var(--ufp-gray-200);\n margin: 0 4px;\n }\n .ufp-toolbar-label {\n font-size: 12px;\n font-family: var(--ufp-font);\n color: var(--ufp-gray-500);\n padding: 0 6px;\n }\n\n /* Comment input popup */\n .ufp-comment-popup {\n position: fixed;\n z-index: 2147483647;\n background: var(--ufp-white);\n border-radius: var(--ufp-radius);\n box-shadow: var(--ufp-shadow);\n padding: 16px;\n width: 320px;\n display: flex;\n flex-direction: column;\n gap: 12px;\n }\n .ufp-comment-popup h3 {\n margin: 0;\n font-size: 14px;\n font-weight: 600;\n color: var(--ufp-gray-800);\n font-family: var(--ufp-font);\n }\n\n /* Comments panel */\n .ufp-panel {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n z-index: 2147483646;\n background: var(--ufp-white);\n box-shadow: -4px 0 24px rgba(0,0,0,0.12);\n display: flex;\n flex-direction: column;\n transform: translateX(100%);\n transition: transform 0.25s cubic-bezier(0.4,0,0.2,1);\n }\n .ufp-panel.open { transform: translateX(0); }\n .ufp-panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 20px;\n border-bottom: 1px solid var(--ufp-gray-100);\n flex-shrink: 0;\n }\n .ufp-panel-title {\n font-size: 16px;\n font-weight: 700;\n color: var(--ufp-gray-900);\n font-family: var(--ufp-font);\n }\n .ufp-filter-tabs {\n display: flex;\n gap: 4px;\n padding: 8px 12px;\n border-bottom: 1px solid var(--ufp-gray-100);\n flex-shrink: 0;\n }\n .ufp-filter-tab {\n padding: 6px 14px;\n border-radius: 20px;\n font-size: 13px;\n font-family: var(--ufp-font);\n font-weight: 500;\n cursor: pointer;\n border: none;\n background: transparent;\n color: var(--ufp-gray-500);\n transition: background 0.12s, color 0.12s;\n }\n .ufp-filter-tab.active {\n background: var(--ufp-primary);\n color: var(--ufp-white);\n }\n .ufp-panel-list {\n flex: 1;\n overflow-y: auto;\n padding: 8px 0;\n }\n .ufp-panel-empty {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 200px;\n color: var(--ufp-gray-400);\n font-size: 14px;\n font-family: var(--ufp-font);\n gap: 8px;\n }\n\n /* Comment card */\n .ufp-comment-card {\n padding: 12px 16px;\n border-bottom: 1px solid var(--ufp-gray-100);\n cursor: pointer;\n transition: background 0.12s;\n display: flex;\n flex-direction: column;\n gap: 8px;\n }\n .ufp-comment-card:hover { background: var(--ufp-gray-50); }\n .ufp-comment-card.selected { background: #eef2ff; }\n .ufp-comment-card-header {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 8px;\n }\n .ufp-comment-meta {\n display: flex;\n align-items: center;\n gap: 6px;\n }\n .ufp-avatar {\n width: 26px;\n height: 26px;\n border-radius: 50%;\n background: var(--ufp-primary);\n color: var(--ufp-white);\n font-size: 11px;\n font-weight: 700;\n font-family: var(--ufp-font);\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n }\n .ufp-comment-author {\n font-size: 13px;\n font-weight: 600;\n color: var(--ufp-gray-800);\n font-family: var(--ufp-font);\n }\n .ufp-comment-time {\n font-size: 11px;\n color: var(--ufp-gray-400);\n font-family: var(--ufp-font);\n }\n .ufp-comment-text {\n font-size: 13px;\n color: var(--ufp-gray-700);\n font-family: var(--ufp-font);\n line-height: 1.5;\n overflow: hidden;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n }\n .ufp-comment-thumbnail {\n width: 100%;\n height: 80px;\n object-fit: cover;\n object-position: top left;\n border-radius: 6px;\n border: 1px solid var(--ufp-gray-200);\n }\n .ufp-comment-footer {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n .ufp-reply-count {\n font-size: 12px;\n color: var(--ufp-gray-400);\n font-family: var(--ufp-font);\n }\n .ufp-resolved-badge {\n font-size: 11px;\n font-weight: 600;\n font-family: var(--ufp-font);\n padding: 2px 8px;\n border-radius: 12px;\n background: #d1fae5;\n color: #065f46;\n }\n .ufp-card-actions {\n display: flex;\n gap: 4px;\n }\n\n /* Detail view */\n .ufp-detail {\n position: fixed;\n inset: 0;\n z-index: 2147483647;\n background: rgba(0,0,0,0.75);\n display: flex;\n align-items: stretch;\n }\n .ufp-detail-image-area {\n flex: 1;\n overflow: auto;\n display: flex;\n align-items: flex-start;\n justify-content: center;\n padding: 24px;\n position: relative;\n }\n .ufp-detail-image-wrap {\n position: relative;\n display: inline-block;\n }\n .ufp-detail-screenshot {\n max-width: 100%;\n display: block;\n border-radius: 8px;\n }\n .ufp-detail-annotation-svg {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n pointer-events: none;\n }\n .ufp-detail-sidebar {\n width: 340px;\n background: var(--ufp-white);\n display: flex;\n flex-direction: column;\n flex-shrink: 0;\n }\n .ufp-detail-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 20px;\n border-bottom: 1px solid var(--ufp-gray-100);\n flex-shrink: 0;\n }\n .ufp-detail-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px 20px;\n display: flex;\n flex-direction: column;\n gap: 16px;\n }\n .ufp-detail-comment {\n display: flex;\n flex-direction: column;\n gap: 8px;\n }\n .ufp-detail-comment-text {\n font-size: 14px;\n color: var(--ufp-gray-800);\n font-family: var(--ufp-font);\n line-height: 1.6;\n }\n .ufp-detail-actions {\n display: flex;\n gap: 8px;\n flex-wrap: wrap;\n }\n\n /* Reply thread */\n .ufp-replies {\n display: flex;\n flex-direction: column;\n gap: 12px;\n border-top: 1px solid var(--ufp-gray-100);\n padding-top: 12px;\n }\n .ufp-replies-title {\n font-size: 12px;\n font-weight: 600;\n color: var(--ufp-gray-500);\n font-family: var(--ufp-font);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n }\n .ufp-reply {\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n .ufp-reply-meta {\n display: flex;\n align-items: center;\n gap: 6px;\n }\n .ufp-reply-author {\n font-size: 12px;\n font-weight: 600;\n color: var(--ufp-gray-700);\n font-family: var(--ufp-font);\n }\n .ufp-reply-time {\n font-size: 11px;\n color: var(--ufp-gray-400);\n font-family: var(--ufp-font);\n }\n .ufp-reply-text {\n font-size: 13px;\n color: var(--ufp-gray-700);\n font-family: var(--ufp-font);\n line-height: 1.5;\n padding-left: 32px;\n }\n .ufp-reply-input-area {\n display: flex;\n flex-direction: column;\n gap: 8px;\n border-top: 1px solid var(--ufp-gray-100);\n padding-top: 12px;\n }\n .ufp-reply-name-row {\n display: flex;\n gap: 8px;\n align-items: center;\n }\n .ufp-reply-name-label {\n font-size: 12px;\n color: var(--ufp-gray-500);\n font-family: var(--ufp-font);\n white-space: nowrap;\n }\n\n /* Loading spinner */\n .ufp-spinner {\n width: 20px;\n height: 20px;\n border: 2px solid var(--ufp-gray-200);\n border-top-color: var(--ufp-primary);\n border-radius: 50%;\n animation: ufp-spin 0.7s linear infinite;\n }\n @keyframes ufp-spin { to { transform: rotate(360deg); } }\n\n /* Scrollbar */\n .ufp-panel-list::-webkit-scrollbar,\n .ufp-detail-body::-webkit-scrollbar,\n .ufp-detail-image-area::-webkit-scrollbar { width: 4px; }\n .ufp-panel-list::-webkit-scrollbar-thumb,\n .ufp-detail-body::-webkit-scrollbar-thumb,\n .ufp-detail-image-area::-webkit-scrollbar-thumb {\n background: var(--ufp-gray-300);\n border-radius: 2px;\n }\n `\n}\n","import { getState, subscribe } from '../core/state'\n\ntype Position = 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'\n\nconst POSITION_STYLES: Record<Position, Partial<CSSStyleDeclaration>> = {\n 'bottom-right': { bottom: '24px', right: '24px' },\n 'bottom-left': { bottom: '24px', left: '24px' },\n 'top-right': { top: '24px', right: '24px' },\n 'top-left': { top: '24px', left: '24px' },\n}\n\nconst BG = '#6366f1'\nconst BG_HOVER = '#4f46e5'\nconst BG_PANEL_ACTIVE = '#4338ca'\n\nexport function createFloatingTrigger(\n shadow: ShadowRoot,\n position: Position,\n onStartAnnotation: () => void,\n onTogglePanel: () => void\n): HTMLElement {\n const wrapper = document.createElement('div')\n Object.assign(wrapper.style, {\n position: 'fixed',\n zIndex: '2147483647',\n display: 'flex',\n alignItems: 'stretch',\n borderRadius: '28px',\n boxShadow: '0 4px 20px rgba(0,0,0,0.28), 0 1px 4px rgba(0,0,0,0.16)',\n overflow: 'hidden',\n userSelect: 'none',\n ...POSITION_STYLES[position],\n })\n\n // ── Annotate button ──────────────────────────────────────────────────────\n const annotateBtn = document.createElement('button')\n annotateBtn.id = 'ufp-btn-annotate'\n annotateBtn.title = 'Add feedback annotation'\n applyBtnBase(annotateBtn)\n annotateBtn.style.padding = '10px 16px 10px 14px'\n annotateBtn.innerHTML = `\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" style=\"flex-shrink:0\">\n <path d=\"M12 20h9\"/>\n <path d=\"M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z\"/>\n </svg>\n <span id=\"ufp-annotate-label\" style=\"font-size:14px;font-weight:600;\">Feedback</span>\n `\n\n // ── Separator ────────────────────────────────────────────────────────────\n const sep = document.createElement('div')\n sep.style.cssText = 'width:1px;background:rgba(255,255,255,0.3);flex-shrink:0;'\n\n // ── Panel button ─────────────────────────────────────────────────────────\n const panelBtn = document.createElement('button')\n panelBtn.id = 'ufp-btn-panel'\n panelBtn.title = 'View all comments'\n applyBtnBase(panelBtn)\n panelBtn.style.padding = '10px 14px'\n panelBtn.innerHTML = `\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" style=\"flex-shrink:0\">\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/>\n </svg>\n <span id=\"ufp-panel-count\" style=\"font-size:13px;font-weight:600;min-width:8px;text-align:center;\"></span>\n `\n\n wrapper.appendChild(annotateBtn)\n wrapper.appendChild(sep)\n wrapper.appendChild(panelBtn)\n shadow.appendChild(wrapper)\n\n const annotateLabel = wrapper.querySelector('#ufp-annotate-label') as HTMLSpanElement\n const panelCount = wrapper.querySelector('#ufp-panel-count') as HTMLSpanElement\n\n // ── Click handlers ───────────────────────────────────────────────────────\n annotateBtn.addEventListener('click', (e) => {\n e.stopPropagation()\n if ((wrapper as any)._dragged) return\n const state = getState()\n if (state.active) return\n if (state.panelOpen) {\n onTogglePanel()\n setTimeout(onStartAnnotation, 220)\n } else {\n onStartAnnotation()\n }\n })\n\n panelBtn.addEventListener('click', (e) => {\n e.stopPropagation()\n if ((wrapper as any)._dragged) return\n if (getState().active) return\n onTogglePanel()\n })\n\n // ── Hover effects ─────────────────────────────────────────────────────────\n addHover(annotateBtn, () => getState().panelOpen ? BG : BG, BG_HOVER)\n addHover(panelBtn, () => getState().panelOpen ? BG_PANEL_ACTIVE : BG, BG_HOVER)\n\n // ── State subscription ───────────────────────────────────────────────────\n subscribe((state) => {\n const openCount = state.comments.filter((c) => !c.resolved).length\n panelCount.textContent = openCount > 0 ? String(openCount) : ''\n\n if (state.active) {\n annotateLabel.textContent = 'Drawing…'\n annotateBtn.style.opacity = '0.65'\n panelBtn.style.opacity = '0.65'\n annotateBtn.style.pointerEvents = 'none'\n panelBtn.style.pointerEvents = 'none'\n } else {\n annotateLabel.textContent = 'Feedback'\n annotateBtn.style.opacity = '1'\n panelBtn.style.opacity = '1'\n annotateBtn.style.pointerEvents = 'auto'\n panelBtn.style.pointerEvents = 'auto'\n }\n\n // Tint panel button when panel is open to signal active state\n panelBtn.style.background = state.panelOpen ? BG_PANEL_ACTIVE : BG\n })\n\n // ── Drag ─────────────────────────────────────────────────────────────────\n makeDraggable(wrapper)\n\n return wrapper\n}\n\nfunction applyBtnBase(btn: HTMLButtonElement) {\n Object.assign(btn.style, {\n display: 'flex',\n alignItems: 'center',\n gap: '7px',\n background: BG,\n color: '#ffffff',\n border: 'none',\n cursor: 'pointer',\n fontFamily: '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\n lineHeight: '1',\n transition: 'background 0.12s',\n })\n}\n\nfunction addHover(btn: HTMLButtonElement, getNormal: () => string, hoverColor: string) {\n btn.addEventListener('mouseenter', () => { btn.style.background = hoverColor })\n btn.addEventListener('mouseleave', () => { btn.style.background = getNormal() })\n}\n\nfunction makeDraggable(el: HTMLElement) {\n let startX = 0, startY = 0, initLeft = 0, initTop = 0\n\n el.addEventListener('pointerdown', (e: PointerEvent) => {\n if (e.button !== 0) return\n startX = e.clientX; startY = e.clientY\n const rect = el.getBoundingClientRect()\n initLeft = rect.left; initTop = rect.top\n ;(el as any)._dragged = false\n\n const onMove = (e: PointerEvent) => {\n const dx = e.clientX - startX\n const dy = e.clientY - startY\n if (!((el as any)._dragged) && Math.sqrt(dx * dx + dy * dy) < 5) return\n ;(el as any)._dragged = true\n el.style.left = `${Math.max(0, Math.min(window.innerWidth - el.offsetWidth, initLeft + dx))}px`\n el.style.top = `${Math.max(0, Math.min(window.innerHeight - el.offsetHeight, initTop + dy))}px`\n el.style.right = 'auto'\n el.style.bottom = 'auto'\n }\n\n const onUp = () => {\n document.removeEventListener('pointermove', onMove)\n document.removeEventListener('pointerup', onUp)\n // Reset after click event has fired\n requestAnimationFrame(() => { (el as any)._dragged = false })\n }\n\n document.addEventListener('pointermove', onMove)\n document.addEventListener('pointerup', onUp)\n })\n}\n","import { setUserName } from '../core/state'\n\nexport function showNameModal(shadow: ShadowRoot): Promise<string> {\n return new Promise((resolve) => {\n const backdrop = document.createElement('div')\n backdrop.className = 'ufp-modal-backdrop'\n\n backdrop.innerHTML = `\n <div class=\"ufp-modal\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"ufp-modal-title\">\n <h2 id=\"ufp-modal-title\">Welcome to Feedback Mode</h2>\n <p>Enter your name so others can see who left the comment.</p>\n <input\n class=\"ufp-input\"\n id=\"ufp-name-input\"\n type=\"text\"\n placeholder=\"Your name\"\n maxlength=\"60\"\n autocomplete=\"off\"\n />\n <div class=\"ufp-btn-row\">\n <button class=\"ufp-btn ufp-btn-primary\" id=\"ufp-name-submit\" disabled>Continue</button>\n </div>\n </div>\n `\n\n shadow.appendChild(backdrop)\n\n const input = backdrop.querySelector('#ufp-name-input') as HTMLInputElement\n const submitBtn = backdrop.querySelector('#ufp-name-submit') as HTMLButtonElement\n\n input.addEventListener('input', () => {\n submitBtn.disabled = input.value.trim().length === 0\n })\n\n const submit = () => {\n const name = input.value.trim()\n if (!name) return\n setUserName(name)\n backdrop.remove()\n resolve(name)\n }\n\n submitBtn.addEventListener('click', submit)\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') submit()\n })\n\n requestAnimationFrame(() => input.focus())\n })\n}\n","import type { AnnotationShape, AnnotationData, Point } from '../core/types'\n\nconst ANNOTATION_COLOR = '#6366f1'\nconst STROKE_WIDTH = 3\n\nexport function renderShapeToSVG(shape: AnnotationShape, scaleX = 1, scaleY = 1): SVGElement {\n switch (shape.type) {\n case 'pen': {\n const path = document.createElementNS('http://www.w3.org/2000/svg', 'path')\n path.setAttribute('d', pointsToPath(shape.points, scaleX, scaleY))\n path.setAttribute('stroke', shape.color)\n path.setAttribute('stroke-width', String(shape.width * Math.min(scaleX, scaleY)))\n path.setAttribute('fill', 'none')\n path.setAttribute('stroke-linecap', 'round')\n path.setAttribute('stroke-linejoin', 'round')\n return path\n }\n case 'rect': {\n const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect')\n rect.setAttribute('x', String(shape.x * scaleX))\n rect.setAttribute('y', String(shape.y * scaleY))\n rect.setAttribute('width', String(shape.width * scaleX))\n rect.setAttribute('height', String(shape.height * scaleY))\n rect.setAttribute('stroke', shape.color)\n rect.setAttribute('stroke-width', String(shape.strokeWidth * Math.min(scaleX, scaleY)))\n rect.setAttribute('fill', 'none')\n rect.setAttribute('rx', '3')\n return rect\n }\n case 'circle': {\n const ellipse = document.createElementNS('http://www.w3.org/2000/svg', 'ellipse')\n ellipse.setAttribute('cx', String(shape.cx * scaleX))\n ellipse.setAttribute('cy', String(shape.cy * scaleY))\n ellipse.setAttribute('rx', String(shape.rx * scaleX))\n ellipse.setAttribute('ry', String(shape.ry * scaleY))\n ellipse.setAttribute('stroke', shape.color)\n ellipse.setAttribute('stroke-width', String(shape.strokeWidth * Math.min(scaleX, scaleY)))\n ellipse.setAttribute('fill', 'none')\n return ellipse\n }\n case 'arrow': {\n const g = document.createElementNS('http://www.w3.org/2000/svg', 'g')\n const line = document.createElementNS('http://www.w3.org/2000/svg', 'line')\n line.setAttribute('x1', String(shape.x1 * scaleX))\n line.setAttribute('y1', String(shape.y1 * scaleY))\n line.setAttribute('x2', String(shape.x2 * scaleX))\n line.setAttribute('y2', String(shape.y2 * scaleY))\n line.setAttribute('stroke', shape.color)\n line.setAttribute('stroke-width', String(shape.width * Math.min(scaleX, scaleY)))\n line.setAttribute('marker-end', 'url(#ufp-arrowhead)')\n g.appendChild(line)\n return g\n }\n }\n}\n\nexport function renderAnnotationToSVG(\n svgEl: SVGSVGElement,\n annotation: AnnotationData,\n displayWidth: number,\n displayHeight: number\n): void {\n const scaleX = displayWidth / annotation.pageWidth\n const scaleY = displayHeight / annotation.pageHeight\n\n svgEl.innerHTML = ''\n\n const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs')\n defs.innerHTML = `\n <marker id=\"ufp-arrowhead\" markerWidth=\"10\" markerHeight=\"7\" refX=\"9\" refY=\"3.5\" orient=\"auto\">\n <polygon points=\"0 0, 10 3.5, 0 7\" fill=\"${ANNOTATION_COLOR}\" />\n </marker>\n `\n svgEl.appendChild(defs)\n\n annotation.shapes.forEach((shape) => {\n svgEl.appendChild(renderShapeToSVG(shape, scaleX, scaleY))\n })\n}\n\nfunction pointsToPath(points: Point[], scaleX: number, scaleY: number): string {\n if (points.length === 0) return ''\n if (points.length === 1) {\n const p = points[0]\n return `M ${p.x * scaleX} ${p.y * scaleY}`\n }\n let d = `M ${points[0].x * scaleX} ${points[0].y * scaleY}`\n for (let i = 1; i < points.length; i++) {\n d += ` L ${points[i].x * scaleX} ${points[i].y * scaleY}`\n }\n return d\n}\n\nexport function getBoundingBox(shapes: AnnotationShape[]): {\n x: number; y: number; width: number; height: number\n} {\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity\n\n shapes.forEach((shape) => {\n switch (shape.type) {\n case 'pen':\n shape.points.forEach((p) => {\n minX = Math.min(minX, p.x); minY = Math.min(minY, p.y)\n maxX = Math.max(maxX, p.x); maxY = Math.max(maxY, p.y)\n })\n break\n case 'rect':\n minX = Math.min(minX, shape.x); minY = Math.min(minY, shape.y)\n maxX = Math.max(maxX, shape.x + shape.width); maxY = Math.max(maxY, shape.y + shape.height)\n break\n case 'circle':\n minX = Math.min(minX, shape.cx - shape.rx); minY = Math.min(minY, shape.cy - shape.ry)\n maxX = Math.max(maxX, shape.cx + shape.rx); maxY = Math.max(maxY, shape.cy + shape.ry)\n break\n case 'arrow':\n minX = Math.min(minX, shape.x1, shape.x2); minY = Math.min(minY, shape.y1, shape.y2)\n maxX = Math.max(maxX, shape.x1, shape.x2); maxY = Math.max(maxY, shape.y1, shape.y2)\n break\n }\n })\n\n const pad = 16\n return {\n x: Math.max(0, minX - pad),\n y: Math.max(0, minY - pad),\n width: maxX - minX + pad * 2,\n height: maxY - minY + pad * 2,\n }\n}\n\nexport { ANNOTATION_COLOR, STROKE_WIDTH }\n","import type { DrawingTool, AnnotationShape, SVGRect, SVGCircle, SVGArrow, Point } from '../core/types'\nimport { renderShapeToSVG, ANNOTATION_COLOR, STROKE_WIDTH } from '../drawing/renderer'\n\nexport interface AnnotationResult {\n shapes: AnnotationShape[]\n text: string\n anchorX: number\n anchorY: number\n}\n\n// Where a stroke is pinned: normal page content, or a fixed element near the top/bottom of the viewport.\ntype Anchor = 'top' | 'bottom' | null\n\n// The screenshot covers only the visible viewport, so a stroke on a fixed/sticky element is already\n// in screenshot coordinates (viewport coordinates), while strokes on page content are stored in page\n// coordinates and shifted by the scroll offset on submit.\nfunction detectFixedAnchor(clientX: number, clientY: number, host: Element): Anchor {\n if (window.scrollX === 0 && window.scrollY === 0) return null\n const vw = window.innerWidth, vh = window.innerHeight\n const hit = document.elementsFromPoint(clientX, clientY).find((el) => el !== host)\n for (let el: Element | null = hit ?? null; el && el !== document.documentElement; el = el.parentElement) {\n const cs = getComputedStyle(el)\n if (cs.position !== 'fixed' && cs.position !== 'sticky') continue\n const r = el.getBoundingClientRect()\n // Full-screen fixed wrappers (backgrounds, app shells) are not \"a fixed element\" for this purpose.\n if (r.width >= vw * 0.9 && r.height >= vh * 0.9) continue\n if (cs.position === 'sticky') {\n // Only a sticky element that is currently stuck near the top behaves like a fixed header.\n if (r.top < vh * 0.25) return 'top'\n continue\n }\n // Computed top/bottom are resolved to pixels, so decide by which viewport edge the element sits closer to.\n return vh - r.bottom < r.top ? 'bottom' : 'top'\n }\n return null\n}\n\nfunction translateShape(shape: AnnotationShape, dx: number, dy: number): AnnotationShape {\n if (!dx && !dy) return shape\n switch (shape.type) {\n case 'pen': return { ...shape, points: shape.points.map((p) => ({ x: p.x + dx, y: p.y + dy })) }\n case 'rect': return { ...shape, x: shape.x + dx, y: shape.y + dy }\n case 'circle': return { ...shape, cx: shape.cx + dx, cy: shape.cy + dy }\n case 'arrow': return { ...shape, x1: shape.x1 + dx, y1: shape.y1 + dy, x2: shape.x2 + dx, y2: shape.y2 + dy }\n }\n}\n\nexport function showAnnotationOverlay(shadow: ShadowRoot): Promise<AnnotationResult | null> {\n return new Promise((resolve) => {\n let currentTool: DrawingTool = 'pen'\n const shapes: AnnotationShape[] = []\n let drawing = false\n let startX = 0\n let startY = 0\n let currentStroke: Point[] = []\n let previewEl: SVGElement | null = null\n let commentPopup: HTMLElement | null = null\n let frozen = false\n let suppressClicksUntil = 0\n const anchors: Anchor[] = []\n let currentAnchor: Anchor = null\n // Offset from viewport to stroke coordinates: fixed-element strokes are kept in viewport coordinates.\n const scrollOffset = () => (currentAnchor ? { x: 0, y: 0 } : { x: window.scrollX, y: window.scrollY })\n\n // Overlay — captures pointer events for drawing\n const overlay = document.createElement('div')\n overlay.style.cssText = 'position:fixed;inset:0;z-index:2147483645;cursor:crosshair;background:rgba(99,102,241,0.04);touch-action:none;user-select:none;-webkit-user-select:none;'\n\n // SVG lives inside the overlay so shapes stay visible as long as overlay is alive\n const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')\n svg.style.cssText = 'position:absolute;inset:0;width:100%;height:100%;pointer-events:none;overflow:visible;'\n // Use same marker ID as renderer.ts expects: \"ufp-arrowhead\"\n svg.innerHTML = `\n <defs>\n <marker id=\"ufp-arrowhead\" markerWidth=\"10\" markerHeight=\"7\" refX=\"9\" refY=\"3.5\" orient=\"auto\">\n <polygon points=\"0 0, 10 3.5, 0 7\" fill=\"${ANNOTATION_COLOR}\" />\n </marker>\n </defs>\n `\n overlay.appendChild(svg)\n\n // Shapes are stored in page coordinates, but the overlay is position:fixed. Every shape lives\n // in this layer, which is shifted by the scroll offset so strokes appear under the cursor\n // wherever the page is scrolled (and stay anchored to the page if the user scrolls mid-draw).\n const layer = document.createElementNS('http://www.w3.org/2000/svg', 'g')\n svg.appendChild(layer)\n // Strokes on fixed elements live here, not shifted by scroll\n const fixedLayer = document.createElementNS('http://www.w3.org/2000/svg', 'g')\n svg.appendChild(fixedLayer)\n const syncLayer = () => layer.setAttribute('transform', `translate(${-window.scrollX} ${-window.scrollY})`)\n syncLayer()\n window.addEventListener('scroll', syncLayer, { passive: true })\n\n function cleanup() {\n window.removeEventListener('scroll', syncLayer)\n for (const t of POINTER_EVENTS) window.removeEventListener(t, onWindowEvent, true)\n for (const t of BLOCKED_EVENTS) window.removeEventListener(t, onBlockedEvent, true)\n overlay.remove()\n toolbar.remove()\n commentPopup?.remove()\n }\n\n function redraw() {\n layer.innerHTML = ''\n fixedLayer.innerHTML = ''\n shapes.forEach((s, i) => (anchors[i] ? fixedLayer : layer).appendChild(renderShapeToSVG(s)))\n }\n\n function getAnchor() {\n let sumX = 0, sumY = 0, count = 0\n // Sum in viewport coordinates: fixed-element strokes are already there, others are page coordinates.\n shapes.forEach((s, i) => {\n const ox = anchors[i] ? 0 : window.scrollX, oy = anchors[i] ? 0 : window.scrollY\n if (s.type === 'pen' && s.points.length) {\n const p = s.points[s.points.length - 1]; sumX += p.x - ox; sumY += p.y - oy; count++\n } else if (s.type === 'rect') { sumX += s.x + s.width - ox; sumY += s.y + s.height - oy; count++ }\n else if (s.type === 'circle') { sumX += s.cx - ox; sumY += s.cy - oy; count++ }\n else if (s.type === 'arrow') { sumX += s.x2 - ox; sumY += s.y2 - oy; count++ }\n })\n return {\n anchorX: count > 0 ? sumX / count : window.innerWidth / 2,\n anchorY: count > 0 ? sumY / count : window.innerHeight / 2,\n }\n }\n\n // After a shape is committed: freeze drawing, auto-show comment popup\n function onShapeCommitted() {\n frozen = true\n drawing = false\n overlay.style.pointerEvents = 'none'\n toolbar.style.pointerEvents = 'none'\n showCommentPopup()\n }\n\n function showCommentPopup() {\n if (commentPopup) commentPopup.remove()\n\n const { anchorX, anchorY } = getAnchor()\n const W = 300, MARGIN = 12\n let left = anchorX + MARGIN\n let top = anchorY + MARGIN\n if (left + W > window.innerWidth - MARGIN) left = anchorX - W - MARGIN\n if (left < MARGIN) left = MARGIN\n if (top + 210 > window.innerHeight - MARGIN) top = window.innerHeight - 210 - MARGIN\n if (top < MARGIN) top = MARGIN\n\n commentPopup = document.createElement('div')\n commentPopup.style.cssText = `\n position:fixed;left:${left}px;top:${top}px;z-index:2147483647;\n background:#fff;border-radius:12px;padding:16px;width:${W}px;\n box-shadow:0 4px 24px rgba(0,0,0,0.18);\n display:flex;flex-direction:column;gap:10px;\n font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;\n `\n commentPopup.innerHTML = `\n <div style=\"font-size:13px;font-weight:600;color:#374151;\">Add your comment</div>\n <textarea id=\"ufp-inline-text\" placeholder=\"Describe your feedback…\" rows=\"3\"\n style=\"width:100%;padding:9px 12px;border:1.5px solid #e5e7eb;border-radius:8px;\n font-size:13px;font-family:inherit;color:#111827;resize:none;\n min-height:72px;outline:none;box-sizing:border-box;\"></textarea>\n <div style=\"display:flex;gap:6px;justify-content:flex-end;align-items:center;\">\n <button id=\"ufp-popup-cancel\"\n style=\"padding:7px 12px;border-radius:8px;font-size:13px;font-weight:500;\n font-family:inherit;cursor:pointer;background:transparent;\n border:1.5px solid #e5e7eb;color:#6b7280;\">Cancel</button>\n <button id=\"ufp-popup-submit\" disabled\n style=\"padding:7px 14px;border-radius:8px;font-size:13px;font-weight:600;\n font-family:inherit;cursor:pointer;background:#6366f1;border:none;\n color:#fff;opacity:0.45;transition:opacity 0.15s;\">Submit</button>\n </div>\n `\n shadow.appendChild(commentPopup)\n\n const textarea = commentPopup.querySelector('#ufp-inline-text') as HTMLTextAreaElement\n const submitBtn = commentPopup.querySelector('#ufp-popup-submit') as HTMLButtonElement\n const cancelBtn = commentPopup.querySelector('#ufp-popup-cancel') as HTMLButtonElement\n\n textarea.addEventListener('input', () => {\n const ok = textarea.value.trim().length > 0\n submitBtn.disabled = !ok\n submitBtn.style.opacity = ok ? '1' : '0.45'\n })\n\n submitBtn.addEventListener('click', () => {\n const text = textarea.value.trim()\n if (!text) return\n const anchor = getAnchor()\n cleanup()\n // The screenshot is the visible viewport: convert page-coordinate strokes to viewport coordinates\n // (fixed-element strokes already are).\n const viewportShapes = shapes.map((sh, i) =>\n anchors[i] ? sh : translateShape(sh, -window.scrollX, -window.scrollY))\n resolve({ shapes: viewportShapes, text, ...anchor })\n })\n\n cancelBtn.addEventListener('click', () => {\n cleanup()\n resolve(null)\n })\n\n requestAnimationFrame(() => textarea.focus())\n }\n\n const toolbar = createToolbar(\n shadow,\n (tool) => { currentTool = tool },\n () => { cleanup(); resolve(null) },\n () => {\n if (shapes.length === 0) return\n if (commentPopup) {\n commentPopup.remove()\n commentPopup = null\n overlay.style.pointerEvents = 'auto'\n toolbar.style.pointerEvents = 'auto'\n frozen = false\n }\n shapes.pop()\n anchors.pop()\n redraw()\n }\n )\n\n shadow.appendChild(overlay)\n\n // ── Drawing event listeners ────────────────────────────────────────────\n\n // Drawing is driven by capture-phase listeners on window rather than by the overlay's own\n // hit-testing. Host pages can have elements that sit above the overlay (top-layer dialogs,\n // animated cards, elements with their own pointer handling); capturing on window gets the\n // event first no matter what is under the cursor, and we stop it reaching the page.\n const POINTER_EVENTS = ['pointerdown', 'pointermove', 'pointerup', 'pointercancel'] as const\n const BLOCKED_EVENTS = ['mousedown', 'mouseup', 'click', 'dblclick', 'auxclick', 'touchstart', 'touchmove', 'contextmenu', 'dragstart', 'selectstart'] as const\n\n // True when the event belongs to the plugin's own UI (toolbar, comment popup, trigger, panel).\n function isOwnUi(e: Event): boolean {\n const path = e.composedPath()\n return path.includes(shadow.host) && !path.includes(overlay)\n }\n\n function onBlockedEvent(e: Event) {\n if (isOwnUi(e)) return\n // The click that follows a finished gesture would otherwise activate whatever is under the cursor.\n if (frozen && performance.now() > suppressClicksUntil) return\n e.preventDefault()\n e.stopPropagation()\n }\n\n function onWindowEvent(e: Event) {\n if (frozen || isOwnUi(e)) return\n e.stopPropagation()\n const pe = e as PointerEvent\n if (e.type === 'pointerdown') onPointerDown(pe)\n else if (e.type === 'pointermove') onPointerMove(pe)\n else if (e.type === 'pointerup') onPointerUp(pe)\n else if (e.type === 'pointercancel') { drawing = false; if (previewEl) { previewEl.remove(); previewEl = null } }\n }\n\n function onPointerDown(e: PointerEvent) {\n if (e.button !== 0) return\n e.preventDefault()\n // Capture to the overlay (not e.target, which is the shadow host when seen from window)\n try { overlay.setPointerCapture(e.pointerId) } catch { /* ignore */ }\n drawing = true\n currentAnchor = detectFixedAnchor(e.clientX, e.clientY, shadow.host)\n startX = e.clientX + scrollOffset().x\n startY = e.clientY + scrollOffset().y\n if (currentTool === 'pen') currentStroke = [{ x: startX, y: startY }]\n }\n\n function onPointerMove(e: PointerEvent) {\n if (!drawing) return\n const cx = e.clientX + scrollOffset().x\n const cy = e.clientY + scrollOffset().y\n if (previewEl) previewEl.remove()\n\n if (currentTool === 'pen') {\n currentStroke.push({ x: cx, y: cy })\n previewEl = renderShapeToSVG({ type: 'pen', points: [...currentStroke], color: ANNOTATION_COLOR, width: STROKE_WIDTH })\n } else if (currentTool === 'rect') {\n const r: SVGRect = { type: 'rect', x: Math.min(startX, cx), y: Math.min(startY, cy), width: Math.abs(cx - startX), height: Math.abs(cy - startY), color: ANNOTATION_COLOR, strokeWidth: STROKE_WIDTH }\n previewEl = renderShapeToSVG(r)\n } else if (currentTool === 'circle') {\n const rx = Math.abs(cx - startX) / 2, ry = Math.abs(cy - startY) / 2\n const c: SVGCircle = { type: 'circle', cx: Math.min(startX, cx) + rx, cy: Math.min(startY, cy) + ry, rx, ry, color: ANNOTATION_COLOR, strokeWidth: STROKE_WIDTH }\n previewEl = renderShapeToSVG(c)\n } else if (currentTool === 'arrow') {\n const a: SVGArrow = { type: 'arrow', x1: startX, y1: startY, x2: cx, y2: cy, color: ANNOTATION_COLOR, width: STROKE_WIDTH }\n previewEl = renderShapeToSVG(a)\n }\n\n if (previewEl) (currentAnchor ? fixedLayer : layer).appendChild(previewEl)\n }\n\n function onPointerUp(e: PointerEvent) {\n if (!drawing) return\n suppressClicksUntil = performance.now() + 400\n drawing = false\n if (previewEl) { previewEl.remove(); previewEl = null }\n\n const cx = e.clientX + scrollOffset().x\n const cy = e.clientY + scrollOffset().y\n let committed = false\n\n if (currentTool === 'pen' && currentStroke.length > 1) {\n shapes.push({ type: 'pen', points: [...currentStroke], color: ANNOTATION_COLOR, width: STROKE_WIDTH })\n currentStroke = []\n committed = true\n } else if (currentTool === 'rect') {\n const w = Math.abs(cx - startX), h = Math.abs(cy - startY)\n if (w > 4 && h > 4) {\n shapes.push({ type: 'rect', x: Math.min(startX, cx), y: Math.min(startY, cy), width: w, height: h, color: ANNOTATION_COLOR, strokeWidth: STROKE_WIDTH })\n committed = true\n }\n } else if (currentTool === 'circle') {\n const rx = Math.abs(cx - startX) / 2, ry = Math.abs(cy - startY) / 2\n if (rx > 4 && ry > 4) {\n shapes.push({ type: 'circle', cx: Math.min(startX, cx) + rx, cy: Math.min(startY, cy) + ry, rx, ry, color: ANNOTATION_COLOR, strokeWidth: STROKE_WIDTH })\n committed = true\n }\n } else if (currentTool === 'arrow') {\n const dist = Math.sqrt((cx - startX) ** 2 + (cy - startY) ** 2)\n if (dist > 10) {\n shapes.push({ type: 'arrow', x1: startX, y1: startY, x2: cx, y2: cy, color: ANNOTATION_COLOR, width: STROKE_WIDTH })\n committed = true\n }\n }\n\n if (committed) {\n anchors.push(currentAnchor)\n redraw()\n onShapeCommitted()\n }\n }\n\n for (const t of POINTER_EVENTS) window.addEventListener(t, onWindowEvent, { capture: true, passive: false })\n for (const t of BLOCKED_EVENTS) window.addEventListener(t, onBlockedEvent, { capture: true, passive: false })\n })\n}\n\nfunction createToolbar(\n shadow: ShadowRoot,\n onToolChange: (t: DrawingTool) => void,\n onCancel: () => void,\n onUndo: () => void\n): HTMLElement {\n const toolbar = document.createElement('div')\n toolbar.className = 'ufp-toolbar'\n toolbar.innerHTML = `\n <span class=\"ufp-toolbar-label\">Draw to annotate</span>\n <div class=\"ufp-toolbar-sep\"></div>\n <button class=\"ufp-btn-icon active\" data-tool=\"pen\" title=\"Pen\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M12 20h9\"/><path d=\"M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z\"/>\n </svg>\n </button>\n <button class=\"ufp-btn-icon\" data-tool=\"rect\" title=\"Rectangle\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/></svg>\n </button>\n <button class=\"ufp-btn-icon\" data-tool=\"circle\" title=\"Circle\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/></svg>\n </button>\n <button class=\"ufp-btn-icon\" data-tool=\"arrow\" title=\"Arrow\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\"/><polyline points=\"12 5 19 12 12 19\"/>\n </svg>\n </button>\n <div class=\"ufp-toolbar-sep\"></div>\n <button class=\"ufp-btn-icon\" id=\"ufp-undo-btn\" title=\"Undo last stroke\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <polyline points=\"9 14 4 9 9 4\"/><path d=\"M20 20v-7a4 4 0 0 0-4-4H4\"/>\n </svg>\n </button>\n <div class=\"ufp-toolbar-sep\"></div>\n <button class=\"ufp-btn ufp-btn-ghost\" id=\"ufp-cancel-btn\">Cancel</button>\n `\n\n toolbar.querySelectorAll('[data-tool]').forEach((btn) => {\n btn.addEventListener('click', () => {\n toolbar.querySelectorAll('[data-tool]').forEach((b) => b.classList.remove('active'))\n btn.classList.add('active')\n onToolChange((btn as HTMLElement).dataset.tool as DrawingTool)\n })\n })\n\n toolbar.querySelector('#ufp-undo-btn')!.addEventListener('click', onUndo)\n toolbar.querySelector('#ufp-cancel-btn')!.addEventListener('click', onCancel)\n\n shadow.appendChild(toolbar)\n return toolbar\n}\n","export function formatRelativeTime(timestamp: number): string {\n const diff = Date.now() - timestamp\n const seconds = Math.floor(diff / 1000)\n if (seconds < 60) return 'just now'\n const minutes = Math.floor(seconds / 60)\n if (minutes < 60) return `${minutes}m ago`\n const hours = Math.floor(minutes / 60)\n if (hours < 24) return `${hours}h ago`\n const days = Math.floor(hours / 24)\n if (days < 7) return `${days}d ago`\n return new Date(timestamp).toLocaleDateString()\n}\n\nexport function getInitials(name: string): string {\n return name\n .split(' ')\n .filter(Boolean)\n .slice(0, 2)\n .map((w) => w[0].toUpperCase())\n .join('')\n}\n\nexport function escHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n}\n","import type { Reply, DatabaseAdapter } from '../core/types'\nimport { getUserName, setUserName } from '../core/state'\nimport { formatRelativeTime, getInitials, escHtml } from './utils'\n\nexport function createReplyThread(\n commentId: string,\n initialReplies: Reply[],\n adapter: DatabaseAdapter,\n onReplyAdded: (reply: Reply) => void\n): HTMLElement {\n let replies = [...initialReplies]\n\n const container = document.createElement('div')\n container.className = 'ufp-replies'\n\n const renderReplies = (list: Reply[]) => {\n const existing = container.querySelector('.ufp-replies-list')\n if (existing) existing.remove()\n\n const listEl = document.createElement('div')\n listEl.className = 'ufp-replies-list'\n listEl.style.cssText = 'display:flex;flex-direction:column;gap:12px;'\n\n if (list.length > 0) {\n const title = document.createElement('div')\n title.className = 'ufp-replies-title'\n title.textContent = `${list.length} ${list.length === 1 ? 'Reply' : 'Replies'}`\n listEl.appendChild(title)\n\n list.forEach((reply) => {\n const replyEl = document.createElement('div')\n replyEl.className = 'ufp-reply'\n replyEl.innerHTML = `\n <div class=\"ufp-reply-meta\">\n <div class=\"ufp-avatar\" style=\"width:22px;height:22px;font-size:10px;\">${getInitials(reply.authorName)}</div>\n <span class=\"ufp-reply-author\">${escHtml(reply.authorName)}</span>\n <span class=\"ufp-reply-time\">${formatRelativeTime(reply.createdAt)}</span>\n </div>\n <div class=\"ufp-reply-text\">${escHtml(reply.text)}</div>\n `\n listEl.appendChild(replyEl)\n })\n }\n\n container.insertBefore(listEl, container.querySelector('.ufp-reply-input-area'))\n }\n\n // Reply input\n const inputArea = document.createElement('div')\n inputArea.className = 'ufp-reply-input-area'\n\n const savedName = getUserName()\n inputArea.innerHTML = `\n <div class=\"ufp-reply-name-row\">\n <span class=\"ufp-reply-name-label\">Replying as:</span>\n <input\n class=\"ufp-input\"\n id=\"ufp-reply-name\"\n type=\"text\"\n placeholder=\"Your name\"\n value=\"${escHtml(savedName ?? '')}\"\n style=\"flex:1;\"\n />\n </div>\n <textarea\n class=\"ufp-textarea\"\n id=\"ufp-reply-text\"\n placeholder=\"Write a reply…\"\n rows=\"2\"\n style=\"min-height:60px;\"\n ></textarea>\n <div class=\"ufp-btn-row\">\n <button class=\"ufp-btn ufp-btn-primary\" id=\"ufp-reply-submit\" disabled style=\"font-size:13px;padding:7px 14px;\">Reply</button>\n </div>\n `\n\n const nameInput = inputArea.querySelector('#ufp-reply-name') as HTMLInputElement\n const textInput = inputArea.querySelector('#ufp-reply-text') as HTMLTextAreaElement\n const submitBtn = inputArea.querySelector('#ufp-reply-submit') as HTMLButtonElement\n\n const validate = () => {\n submitBtn.disabled =\n nameInput.value.trim().length === 0 || textInput.value.trim().length === 0\n }\n nameInput.addEventListener('input', validate)\n textInput.addEventListener('input', validate)\n\n submitBtn.addEventListener('click', async () => {\n const name = nameInput.value.trim()\n const text = textInput.value.trim()\n if (!name || !text) return\n\n submitBtn.disabled = true\n submitBtn.textContent = 'Sending…'\n setUserName(name)\n\n const reply: Omit<Reply, 'id'> = {\n commentId,\n authorName: name,\n text,\n createdAt: Date.now(),\n }\n\n try {\n const id = await adapter.addReply(reply)\n const fullReply: Reply = { id, ...reply }\n\n textInput.value = ''\n submitBtn.textContent = 'Reply'\n // Keep button disabled until user types again (validate fires on input)\n submitBtn.disabled = true\n\n // Update local list immediately (don't wait for next getReplies)\n replies = [...replies, fullReply]\n renderReplies(replies)\n\n onReplyAdded(fullReply)\n } catch (err) {\n console.error('[ui-feedback-plugin] Failed to save reply:', err)\n submitBtn.disabled = false\n submitBtn.textContent = 'Reply'\n }\n })\n\n container.appendChild(inputArea)\n renderReplies(replies)\n\n // Exposed so CommentDetailView can inject a reply from outside\n ;(container as any).addReply = (reply: Reply) => {\n replies = [...replies, reply]\n renderReplies(replies)\n }\n\n return container\n}\n","import type { FeedbackComment, Reply, DatabaseAdapter } from '../core/types'\nimport { setState } from '../core/state'\nimport { renderAnnotationToSVG } from '../drawing/renderer'\nimport { createReplyThread } from './ReplyThread'\nimport { formatRelativeTime, getInitials, escHtml } from './utils'\n\nexport function showCommentDetail(\n shadow: ShadowRoot,\n comment: FeedbackComment,\n replies: Reply[],\n adapter: DatabaseAdapter,\n onResolve: (id: string) => void,\n onReopen: (id: string) => void,\n onDelete: (id: string) => void,\n onReplyAdded?: (reply: Reply) => void\n): void {\n const detail = document.createElement('div')\n detail.className = 'ufp-detail'\n\n detail.innerHTML = `\n <div class=\"ufp-detail-image-area\">\n <div class=\"ufp-detail-image-wrap\">\n <img class=\"ufp-detail-screenshot\" src=\"${comment.screenshotUrl}\" alt=\"Page screenshot\" />\n <svg class=\"ufp-detail-annotation-svg\" xmlns=\"http://www.w3.org/2000/svg\">\n <defs>\n <marker id=\"ufp-detail-arrowhead\" markerWidth=\"10\" markerHeight=\"7\" refX=\"9\" refY=\"3.5\" orient=\"auto\">\n <polygon points=\"0 0, 10 3.5, 0 7\" fill=\"#6366f1\" />\n </marker>\n </defs>\n </svg>\n </div>\n </div>\n <div class=\"ufp-detail-sidebar\">\n <div class=\"ufp-detail-header\">\n <button id=\"ufp-detail-back\" style=\"\n display:flex;align-items:center;gap:6px;\n background:none;border:none;cursor:pointer;\n font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;\n font-size:13px;font-weight:600;color:#6b7280;padding:4px 0;\n \">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\">\n <polyline points=\"15 18 9 12 15 6\"/>\n </svg>\n Back to comments\n </button>\n ${comment.resolved ? '<span class=\"ufp-resolved-badge\">Resolved</span>' : ''}\n </div>\n <div class=\"ufp-detail-body\">\n <div class=\"ufp-detail-comment\">\n <div class=\"ufp-comment-meta\">\n <div class=\"ufp-avatar\">${getInitials(comment.authorName)}</div>\n <div>\n <div class=\"ufp-comment-author\">${escHtml(comment.authorName)}</div>\n <div class=\"ufp-comment-time\">${formatRelativeTime(comment.createdAt)}</div>\n </div>\n </div>\n <div class=\"ufp-detail-comment-text\">${escHtml(comment.text)}</div>\n <div class=\"ufp-detail-actions\" id=\"ufp-detail-actions\"></div>\n </div>\n <div id=\"ufp-detail-replies\"></div>\n </div>\n </div>\n `\n\n shadow.appendChild(detail)\n\n detail.querySelector('#ufp-detail-back')!.addEventListener('click', () => {\n detail.remove()\n setState({ panelOpen: true })\n })\n\n // Action buttons\n const actionsEl = detail.querySelector('#ufp-detail-actions') as HTMLElement\n\n if (!comment.resolved) {\n const resolveBtn = document.createElement('button')\n resolveBtn.className = 'ufp-btn ufp-btn-primary'\n resolveBtn.style.fontSize = '13px'\n resolveBtn.innerHTML = `\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"\n style=\"vertical-align:middle;margin-right:4px\"><polyline points=\"20 6 9 17 4 12\"/></svg>\n Mark Resolved\n `\n resolveBtn.addEventListener('click', async () => {\n resolveBtn.disabled = true\n resolveBtn.textContent = 'Resolving…'\n await adapter.updateComment(comment.id, { resolved: true })\n onResolve(comment.id)\n detail.remove()\n setState({ panelOpen: true })\n })\n actionsEl.appendChild(resolveBtn)\n } else {\n const reopenBtn = document.createElement('button')\n reopenBtn.className = 'ufp-btn ufp-btn-ghost'\n reopenBtn.style.fontSize = '13px'\n reopenBtn.innerHTML = `\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"\n style=\"vertical-align:middle;margin-right:4px\"><polyline points=\"9 14 4 9 9 4\"/><path d=\"M20 20v-7a4 4 0 0 0-4-4H4\"/></svg>\n Reopen\n `\n reopenBtn.addEventListener('click', async () => {\n reopenBtn.disabled = true\n reopenBtn.textContent = 'Reopening…'\n await adapter.updateComment(comment.id, { resolved: false })\n onReopen(comment.id)\n detail.remove()\n setState({ panelOpen: true })\n })\n actionsEl.appendChild(reopenBtn)\n }\n\n const deleteBtn = document.createElement('button')\n deleteBtn.className = 'ufp-btn ufp-btn-ghost'\n deleteBtn.style.fontSize = '13px'\n deleteBtn.textContent = 'Delete'\n deleteBtn.addEventListener('click', async () => {\n if (!confirm('Delete this comment and all its replies?')) return\n deleteBtn.disabled = true\n await adapter.deleteComment(comment.id)\n onDelete(comment.id)\n detail.remove()\n setState({ panelOpen: true })\n })\n actionsEl.appendChild(deleteBtn)\n\n // Reply thread\n const repliesContainer = detail.querySelector('#ufp-detail-replies') as HTMLElement\n const replyThread = createReplyThread(comment.id, replies, adapter, (newReply) => {\n // Update the thread UI in place\n ;(replyThread as any).addReply?.(newReply)\n // Notify the panel so it can update the badge count\n onReplyAdded?.(newReply)\n })\n repliesContainer.appendChild(replyThread)\n\n // Render annotation over screenshot after image loads\n const img = detail.querySelector('.ufp-detail-screenshot') as HTMLImageElement\n const annotationSvg = detail.querySelector('.ufp-detail-annotation-svg') as SVGSVGElement\n\n const renderAnnotation = () => {\n const w = img.clientWidth\n const h = img.clientHeight\n annotationSvg.setAttribute('viewBox', `0 0 ${w} ${h}`)\n renderAnnotationToSVG(annotationSvg, comment.annotation, w, h)\n annotationSvg.querySelectorAll('[marker-end]').forEach((el) => {\n el.setAttribute('marker-end', 'url(#ufp-detail-arrowhead)')\n })\n }\n\n if (img.complete && img.naturalWidth > 0) {\n renderAnnotation()\n } else {\n img.addEventListener('load', renderAnnotation)\n }\n}\n","import type { FeedbackComment, Reply, DatabaseAdapter } from '../core/types'\nimport { getState, setState, subscribe } from '../core/state'\nimport { showCommentDetail } from './CommentDetailView'\nimport { formatRelativeTime, getInitials, escHtml } from './utils'\n\nexport function createCommentsPanel(\n shadow: ShadowRoot,\n adapter: DatabaseAdapter,\n onNewFeedback: () => void\n): HTMLElement {\n const panel = document.createElement('div')\n panel.className = 'ufp-panel'\n\n panel.innerHTML = `\n <div class=\"ufp-panel-header\">\n <span class=\"ufp-panel-title\">Feedback</span>\n <div style=\"display:flex;gap:8px;align-items:center;\">\n <button id=\"ufp-new-feedback\" class=\"ufp-btn ufp-btn-primary\" style=\"font-size:13px;padding:7px 14px;display:flex;align-items:center;gap:5px;\">\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><line x1=\"12\" y1=\"5\" x2=\"12\" y2=\"19\"/><line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\"/></svg>\n New\n </button>\n <button class=\"ufp-btn-icon\" id=\"ufp-panel-close\" title=\"Close panel\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n </div>\n </div>\n <div class=\"ufp-filter-tabs\">\n <button class=\"ufp-filter-tab active\" data-filter=\"open\">Open</button>\n <button class=\"ufp-filter-tab\" data-filter=\"resolved\">Resolved</button>\n </div>\n <div class=\"ufp-panel-list\" id=\"ufp-comment-list\"></div>\n `\n\n shadow.appendChild(panel)\n\n panel.querySelector('#ufp-panel-close')!.addEventListener('click', () => {\n setState({ panelOpen: false })\n })\n\n panel.querySelector('#ufp-new-feedback')!.addEventListener('click', () => {\n setState({ panelOpen: false })\n onNewFeedback()\n })\n\n panel.querySelectorAll('.ufp-filter-tab').forEach((tab) => {\n tab.addEventListener('click', () => {\n panel.querySelectorAll('.ufp-filter-tab').forEach((t) => t.classList.remove('active'))\n tab.classList.add('active')\n setState({ filter: (tab as HTMLElement).dataset.filter as 'open' | 'resolved' })\n })\n })\n\n const listEl = panel.querySelector('#ufp-comment-list') as HTMLElement\n\n // Cache is used ONLY for the reply-count badge shown in the panel list.\n // The detail view always fetches fresh replies.\n const repliesCache = new Map<string, Reply[]>()\n\n const render = async (comments: FeedbackComment[], filter: 'open' | 'resolved') => {\n const filtered = comments.filter((c) => (filter === 'open' ? !c.resolved : c.resolved))\n listEl.innerHTML = ''\n\n if (filtered.length === 0) {\n listEl.innerHTML = `\n <div class=\"ufp-panel-empty\">\n <svg width=\"32\" height=\"32\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" opacity=\"0.4\">\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/>\n </svg>\n <span>No ${filter} comments yet</span>\n ${filter === 'open' ? '<span style=\"font-size:12px;color:#9ca3af;\">Click <strong>New</strong> to add feedback</span>' : ''}\n </div>\n `\n return\n }\n\n for (const comment of filtered) {\n if (!repliesCache.has(comment.id)) {\n try {\n repliesCache.set(comment.id, await adapter.getReplies(comment.id))\n } catch {\n repliesCache.set(comment.id, [])\n }\n }\n listEl.appendChild(\n createCommentCard(comment, repliesCache, shadow, adapter, onNewFeedback)\n )\n }\n }\n\n subscribe(async (state) => {\n panel.classList.toggle('open', state.panelOpen)\n if (state.panelOpen) {\n await render(state.comments, state.filter)\n }\n })\n\n return panel\n}\n\nfunction createCommentCard(\n comment: FeedbackComment,\n repliesCache: Map<string, Reply[]>,\n shadow: ShadowRoot,\n adapter: DatabaseAdapter,\n onNewFeedback: () => void\n): HTMLElement {\n const cachedReplies = repliesCache.get(comment.id) ?? []\n\n const card = document.createElement('div')\n card.className = 'ufp-comment-card'\n card.dataset.id = comment.id\n\n card.innerHTML = `\n <div class=\"ufp-comment-card-header\">\n <div class=\"ufp-comment-meta\">\n <div class=\"ufp-avatar\">${getInitials(comment.authorName)}</div>\n <div>\n <div class=\"ufp-comment-author\">${escHtml(comment.authorName)}</div>\n <div class=\"ufp-comment-time\">${formatRelativeTime(comment.createdAt)}</div>\n </div>\n </div>\n ${comment.resolved ? '<span class=\"ufp-resolved-badge\">Resolved</span>' : ''}\n </div>\n <img class=\"ufp-comment-thumbnail\" src=\"${comment.screenshotUrl}\" alt=\"Screenshot\" loading=\"lazy\" />\n <div class=\"ufp-comment-text\">${escHtml(comment.text)}</div>\n <div class=\"ufp-comment-footer\">\n <span class=\"ufp-reply-count\">\n ${cachedReplies.length > 0\n ? `${cachedReplies.length} ${cachedReplies.length === 1 ? 'reply' : 'replies'}`\n : 'No replies yet — click to reply'}\n </span>\n </div>\n `\n\n card.addEventListener('click', async () => {\n // Always fetch fresh replies when opening the detail so nothing is stale\n let freshReplies: Reply[] = repliesCache.get(comment.id) ?? []\n try {\n freshReplies = await adapter.getReplies(comment.id)\n repliesCache.set(comment.id, freshReplies)\n } catch (err) {\n console.error('[ui-feedback-plugin] Failed to fetch replies:', err)\n }\n\n showCommentDetail(\n shadow,\n comment,\n freshReplies,\n adapter,\n (id) => {\n const state = getState()\n setState({ comments: state.comments.map((c) => c.id === id ? { ...c, resolved: true } : c) })\n },\n (id) => {\n const state = getState()\n setState({ comments: state.comments.map((c) => c.id === id ? { ...c, resolved: false } : c) })\n },\n (id) => {\n const state = getState()\n setState({ comments: state.comments.filter((c) => c.id !== id) })\n repliesCache.delete(id)\n },\n // When a reply is added in the detail view, update badge count immediately\n (reply) => {\n const existing = repliesCache.get(comment.id) ?? []\n repliesCache.set(comment.id, [...existing, reply])\n // Nudge state so the panel re-renders with the new reply count\n const state = getState()\n setState({ comments: [...state.comments] })\n }\n )\n })\n\n return card\n}\n","import type { AnnotationData } from '../core/types'\n\nexport async function capturePageScreenshot(): Promise<string> {\n try {\n const html2canvas = await loadHtml2Canvas()\n return await captureWithHtml2Canvas(html2canvas)\n } catch (err) {\n console.error('[ui-feedback-plugin] capturePageScreenshot failed:', err)\n return captureViewportFallback()\n }\n}\n\nasync function loadHtml2Canvas(): Promise<typeof import('html2canvas-pro').default> {\n const mod = await import('html2canvas-pro')\n return mod.default\n}\n\nasync function captureWithHtml2Canvas(\n html2canvas: typeof import('html2canvas-pro').default\n): Promise<string> {\n // Capture only what is currently visible: the viewport at the current scroll position.\n const width = document.documentElement.clientWidth\n const height = window.innerHeight\n\n const canvas = await html2canvas(document.body, {\n allowTaint: false,\n useCORS: true,\n logging: false,\n scale: Math.min(window.devicePixelRatio, 2),\n x: window.scrollX,\n y: window.scrollY,\n width,\n height,\n windowWidth: width,\n windowHeight: height,\n ignoreElements: (el) => {\n // Skip the feedback widget itself\n return (el as HTMLElement).tagName === 'UI-FEEDBACK-PLUGIN'\n },\n })\n return canvas.toDataURL('image/png')\n}\n\nfunction captureViewportFallback(): string {\n // Fallback: return a placeholder data URL indicating screenshot unavailable\n const canvas = document.createElement('canvas')\n canvas.width = window.innerWidth\n canvas.height = window.innerHeight\n const ctx = canvas.getContext('2d')!\n ctx.fillStyle = '#f3f4f6'\n ctx.fillRect(0, 0, canvas.width, canvas.height)\n ctx.fillStyle = '#9ca3af'\n ctx.font = '16px -apple-system, sans-serif'\n ctx.textAlign = 'center'\n ctx.fillText('Screenshot unavailable', canvas.width / 2, canvas.height / 2)\n return canvas.toDataURL('image/png')\n}\n\n/**\n * Draws the annotation shapes onto the screenshot so the stored PNG is self-contained.\n * Shape coordinates are in the screenshot's CSS-pixel space (pageWidth x pageHeight); the image is scaled by (image width / pageWidth).\n */\nexport async function drawAnnotationsOnScreenshot(\n dataUrl: string,\n annotation: AnnotationData\n): Promise<string> {\n try {\n const img = await new Promise<HTMLImageElement>((resolve, reject) => {\n const el = new Image()\n el.onload = () => resolve(el)\n el.onerror = () => reject(new Error('screenshot failed to load'))\n el.src = dataUrl\n })\n const canvas = document.createElement('canvas')\n canvas.width = img.naturalWidth\n canvas.height = img.naturalHeight\n const ctx = canvas.getContext('2d')!\n ctx.drawImage(img, 0, 0)\n\n const sx = canvas.width / annotation.pageWidth\n const sy = canvas.height / annotation.pageHeight\n const sw = Math.min(sx, sy)\n ctx.lineCap = 'round'\n ctx.lineJoin = 'round'\n\n for (const shape of annotation.shapes) {\n ctx.strokeStyle = shape.color\n ctx.fillStyle = shape.color\n ctx.beginPath()\n switch (shape.type) {\n case 'pen': {\n ctx.lineWidth = shape.width * sw\n shape.points.forEach((p, i) => (i === 0 ? ctx.moveTo(p.x * sx, p.y * sy) : ctx.lineTo(p.x * sx, p.y * sy)))\n ctx.stroke()\n break\n }\n case 'rect': {\n ctx.lineWidth = shape.strokeWidth * sw\n ctx.roundRect(shape.x * sx, shape.y * sy, shape.width * sx, shape.height * sy, 3 * sw)\n ctx.stroke()\n break\n }\n case 'circle': {\n ctx.lineWidth = shape.strokeWidth * sw\n ctx.ellipse(shape.cx * sx, shape.cy * sy, shape.rx * sx, shape.ry * sy, 0, 0, Math.PI * 2)\n ctx.stroke()\n break\n }\n case 'arrow': {\n const w = shape.width * sw\n const x1 = shape.x1 * sx, y1 = shape.y1 * sy, x2 = shape.x2 * sx, y2 = shape.y2 * sy\n ctx.lineWidth = w\n ctx.lineCap = 'butt'\n ctx.moveTo(x1, y1)\n ctx.lineTo(x2, y2)\n ctx.stroke()\n ctx.lineCap = 'round'\n // Same arrowhead geometry as the SVG marker (10x7 in stroke-width units, tip 1 unit past the end)\n const len = Math.hypot(x2 - x1, y2 - y1) || 1\n const dx = (x2 - x1) / len, dy = (y2 - y1) / len\n const tipX = x2 + dx * w, tipY = y2 + dy * w\n const bx = tipX - dx * 10 * w, by = tipY - dy * 10 * w\n ctx.beginPath()\n ctx.moveTo(tipX, tipY)\n ctx.lineTo(bx - dy * 3.5 * w, by + dx * 3.5 * w)\n ctx.lineTo(bx + dy * 3.5 * w, by - dx * 3.5 * w)\n ctx.closePath()\n ctx.fill()\n break\n }\n }\n }\n return canvas.toDataURL('image/png')\n } catch (err) {\n console.error('[ui-feedback-plugin] drawAnnotationsOnScreenshot failed:', err)\n return dataUrl\n }\n}\n","import type {\n PluginConfig,\n DatabaseAdapter,\n ScreenshotAdapter,\n FeedbackComment,\n AnnotationData,\n} from './core/types'\nimport { getState, setState, getUserName, normalizeUrl } from './core/state'\nimport { getStyles } from './ui/styles'\nimport { createFloatingTrigger } from './ui/FloatingTrigger'\nimport { showNameModal } from './ui/NameModal'\nimport { showAnnotationOverlay } from './ui/AnnotationOverlay'\nimport { createCommentsPanel } from './ui/CommentsPanel'\nimport { capturePageScreenshot, drawAnnotationsOnScreenshot } from './screenshot/capture'\n\nconst TAG = 'ui-feedback-plugin'\n\nasync function createAdapters(config: PluginConfig): Promise<{\n db: DatabaseAdapter\n screenshot: ScreenshotAdapter\n}> {\n const { backend } = config\n\n if (backend.provider === 'firebase') {\n const { FirebaseAdapter } = await import('./adapters/firebase')\n const adapter = new FirebaseAdapter(backend)\n return { db: adapter, screenshot: adapter }\n }\n\n if (backend.provider === 'supabase') {\n const { SupabaseAdapter } = await import('./adapters/supabase')\n const adapter = new SupabaseAdapter(backend.url, backend.anonKey)\n return { db: adapter, screenshot: adapter }\n }\n\n if (backend.provider === 'aws') {\n const { AWSAdapter } = await import('./adapters/aws')\n const adapter = new AWSAdapter(backend)\n return { db: adapter, screenshot: adapter }\n }\n\n if (backend.provider === 'local') {\n const { LocalAdapter } = await import('./adapters/local')\n const adapter = new LocalAdapter()\n return { db: adapter, screenshot: adapter }\n }\n\n throw new Error('[ui-feedback-plugin] Unknown backend provider')\n}\n\nexport function initFeedbackPlugin(config: PluginConfig): () => void {\n if (customElements.get(TAG)) {\n console.warn('[ui-feedback-plugin] Already initialized.')\n return () => {}\n }\n\n const primaryColor = config.theme?.primaryColor ?? '#6366f1'\n const projectKey = config.projectKey ?? 'default'\n const position = config.position ?? 'bottom-right'\n\n setState({ config })\n\n class FeedbackPlugin extends HTMLElement {\n private shadow!: ShadowRoot\n private db!: DatabaseAdapter\n private screenshot!: ScreenshotAdapter\n private unsubscribe: (() => void) | null = null\n\n constructor() {\n super()\n this.shadow = this.attachShadow({ mode: 'open' })\n }\n\n async connectedCallback() {\n const style = document.createElement('style')\n style.textContent = getStyles(primaryColor)\n this.shadow.appendChild(style)\n\n try {\n const adapters = await createAdapters(config)\n this.db = adapters.db\n this.screenshot = adapters.screenshot\n } catch (err) {\n console.error('[ui-feedback-plugin] Failed to initialise backend:', err)\n return\n }\n\n this.mount()\n }\n\n disconnectedCallback() {\n this.unsubscribe?.()\n }\n\n private mount() {\n const pageUrl = normalizeUrl(window.location.href)\n\n createCommentsPanel(this.shadow, this.db, () => this.startAnnotation())\n\n createFloatingTrigger(\n this.shadow,\n position,\n () => this.startAnnotation(),\n () => setState({ panelOpen: !getState().panelOpen })\n )\n\n this.unsubscribe = this.db.subscribeToComments(pageUrl, projectKey, (comments) => {\n setState({ comments })\n })\n }\n\n private async startAnnotation() {\n let userName = getUserName()\n if (!userName) {\n userName = await showNameModal(this.shadow)\n if (!userName) return\n }\n\n setState({ active: true, panelOpen: false })\n\n const result = await showAnnotationOverlay(this.shadow)\n\n if (!result) {\n setState({ active: false })\n return\n }\n\n setState({ active: false })\n\n this.showToast('Saving comment…', 'info')\n try {\n await this.saveComment(userName, result.text, result.shapes)\n this.showToast('Comment saved!', 'success')\n } catch (err) {\n console.error('[ui-feedback-plugin] Failed to save comment:', err)\n this.showToast('Failed to save — check the browser console for details.', 'error')\n return\n }\n\n setState({ panelOpen: true })\n }\n\n private async saveComment(\n authorName: string,\n text: string,\n shapes: AnnotationData['shapes']\n ) {\n const pageUrl = normalizeUrl(window.location.href)\n const annotation: AnnotationData = {\n shapes,\n // Shapes are in viewport coordinates; the screenshot covers just the visible viewport.\n pageWidth: document.documentElement.clientWidth,\n pageHeight: window.innerHeight,\n }\n\n this.style.visibility = 'hidden'\n let screenshotDataUrl: string\n try {\n screenshotDataUrl = await capturePageScreenshot()\n } finally {\n this.style.visibility = ''\n }\n screenshotDataUrl = await drawAnnotationsOnScreenshot(screenshotDataUrl, annotation)\n\n const placeholder: Omit<FeedbackComment, 'id'> = {\n pageUrl,\n projectKey,\n authorName,\n text,\n screenshotUrl: '',\n annotation,\n resolved: false,\n createdAt: Date.now(),\n }\n\n const commentId = await this.db.addComment(placeholder)\n const screenshotUrl = await this.screenshot.uploadScreenshot(commentId, screenshotDataUrl)\n await this.db.updateComment(commentId, { screenshotUrl })\n }\n\n private showToast(message: string, type: 'info' | 'success' | 'error') {\n const existing = this.shadow.querySelector('.ufp-toast')\n existing?.remove()\n\n const colors = { info: '#6366f1', success: '#10b981', error: '#ef4444' }\n\n const toast = document.createElement('div')\n toast.className = 'ufp-toast'\n toast.style.cssText = `\n position:fixed;bottom:80px;left:50%;transform:translateX(-50%);\n background:${colors[type]};color:white;padding:10px 20px;border-radius:8px;\n font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;\n font-size:13px;font-weight:500;z-index:2147483647;\n box-shadow:0 4px 12px rgba(0,0,0,0.2);pointer-events:none;\n max-width:360px;text-align:center;\n `\n toast.textContent = message\n this.shadow.appendChild(toast)\n setTimeout(() => toast.remove(), type === 'error' ? 6000 : 2500)\n }\n }\n\n customElements.define(TAG, FeedbackPlugin)\n\n const el = document.createElement(TAG)\n document.body.appendChild(el)\n\n return () => el.remove()\n}\n\nexport type { PluginConfig }\n"],"names":["listeners","state","getState","setState","partial","fn","subscribe","getUserName","stored","setUserName","name","normalizeUrl","url","u","getStyles","primaryColor","POSITION_STYLES","BG","BG_HOVER","BG_PANEL_ACTIVE","createFloatingTrigger","shadow","position","onStartAnnotation","onTogglePanel","wrapper","annotateBtn","applyBtnBase","sep","panelBtn","annotateLabel","panelCount","e","addHover","openCount","c","makeDraggable","btn","getNormal","hoverColor","el","startX","startY","initLeft","initTop","rect","onMove","dx","dy","onUp","showNameModal","resolve","backdrop","input","submitBtn","submit","ANNOTATION_COLOR","STROKE_WIDTH","renderShapeToSVG","shape","scaleX","scaleY","path","pointsToPath","ellipse","g","line","renderAnnotationToSVG","svgEl","annotation","displayWidth","displayHeight","defs","points","p","d","i","detectFixedAnchor","clientX","clientY","host","vw","vh","hit","cs","r","translateShape","showAnnotationOverlay","currentTool","shapes","drawing","currentStroke","previewEl","commentPopup","frozen","suppressClicksUntil","anchors","currentAnchor","scrollOffset","overlay","svg","layer","fixedLayer","syncLayer","cleanup","t","POINTER_EVENTS","onWindowEvent","BLOCKED_EVENTS","onBlockedEvent","toolbar","redraw","s","getAnchor","sumX","sumY","count","ox","oy","onShapeCommitted","showCommentPopup","anchorX","anchorY","W","MARGIN","left","top","textarea","cancelBtn","ok","text","anchor","viewportShapes","sh","createToolbar","tool","isOwnUi","pe","onPointerDown","onPointerMove","onPointerUp","cx","cy","rx","ry","committed","w","h","onToolChange","onCancel","onUndo","b","formatRelativeTime","timestamp","diff","seconds","minutes","hours","days","getInitials","escHtml","str","createReplyThread","commentId","initialReplies","adapter","onReplyAdded","replies","container","renderReplies","list","existing","listEl","title","reply","replyEl","inputArea","savedName","nameInput","textInput","validate","fullReply","err","showCommentDetail","comment","onResolve","onReopen","onDelete","detail","actionsEl","reopenBtn","resolveBtn","deleteBtn","repliesContainer","replyThread","newReply","_a","img","annotationSvg","renderAnnotation","createCommentsPanel","onNewFeedback","panel","tab","repliesCache","render","comments","filter","filtered","createCommentCard","cachedReplies","card","freshReplies","id","capturePageScreenshot","html2canvas","loadHtml2Canvas","captureWithHtml2Canvas","captureViewportFallback","width","height","canvas","ctx","drawAnnotationsOnScreenshot","dataUrl","reject","sx","sy","sw","x1","y1","x2","y2","len","tipX","tipY","bx","by","TAG","createAdapters","config","backend","FirebaseAdapter","SupabaseAdapter","AWSAdapter","LocalAdapter","initFeedbackPlugin","projectKey","FeedbackPlugin","__publicField","style","adapters","pageUrl","userName","result","authorName","screenshotDataUrl","placeholder","screenshotUrl","message","type","colors","toast"],"mappings":";;;AAaA,MAAMA,wBAAgB,IAAA;AAEtB,IAAIC,IAAqB;AAAA,EACvB,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,UAAU,CAAA;AAAA,EACV,6BAAa,IAAA;AAAA,EACb,mBAAmB;AAAA,EACnB,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,QAAQ,CAAA;AACV;AAEO,SAASC,IAAwB;AACtC,SAAOD;AACT;AAEO,SAASE,EAASC,GAAqC;AAC5D,EAAAH,IAAQ,EAAE,GAAGA,GAAO,GAAGG,EAAA,GACvBJ,EAAU,QAAQ,CAACK,MAAOA,EAAGJ,CAAK,CAAC;AACrC;AAEO,SAASK,GAAUD,GAA8C;AACtE,SAAAL,EAAU,IAAIK,CAAE,GACT,MAAML,EAAU,OAAOK,CAAE;AAClC;AAEO,SAASE,KAA6B;AAC3C,MAAIN,EAAM,SAAU,QAAOA,EAAM;AACjC,QAAMO,IAAS,aAAa,QAAQ,eAAe;AACnD,SAAIA,KACFL,EAAS,EAAE,UAAUK,GAAQ,GACtBA,KAEF;AACT;AAEO,SAASC,GAAYC,GAAoB;AAC9C,eAAa,QAAQ,iBAAiBA,CAAI,GAC1CP,EAAS,EAAE,UAAUO,GAAM;AAC7B;AAEO,SAASC,EAAaC,GAAqB;AAChD,MAAI;AACF,UAAMC,IAAI,IAAI,IAAID,CAAG;AACrB,WAAOC,EAAE,SAASA,EAAE;AAAA,EACtB,QAAQ;AACN,WAAOD;AAAA,EACT;AACF;AC9DO,SAASE,GAAUC,GAA8B;AACtD,SAAO;AAAA;AAAA,uBAEcA,CAAY;AAAA,+CACYA,CAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;A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"}
|