@mindbowser_inc/ui-feedback-plugin 0.4.1 → 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\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\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);'\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 function cleanup() {\n overlay.remove()\n toolbar.remove()\n commentPopup?.remove()\n }\n\n function redraw() {\n const defs = svg.querySelector('defs')!\n svg.innerHTML = ''\n svg.appendChild(defs)\n shapes.forEach((s) => svg.appendChild(renderShapeToSVG(s)))\n }\n\n function getAnchor() {\n let sumX = 0, sumY = 0, count = 0\n shapes.forEach((s) => {\n if (s.type === 'pen' && s.points.length) {\n const p = s.points[s.points.length - 1]; sumX += p.x; sumY += p.y; count++\n } else if (s.type === 'rect') { sumX += s.x + s.width; sumY += s.y + s.height; count++ }\n else if (s.type === 'circle') { sumX += s.cx; sumY += s.cy; count++ }\n else if (s.type === 'arrow') { sumX += s.x2; sumY += s.y2; count++ }\n })\n return {\n anchorX: count > 0 ? sumX / count - window.scrollX : window.innerWidth / 2,\n anchorY: count > 0 ? sumY / count - window.scrollY : window.innerHeight / 2,\n }\n }\n\n // After a shape is committed: freeze drawing, auto-show comment popup\n function onShapeCommitted() {\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 resolve({ shapes: [...shapes], 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 }\n shapes.pop()\n redraw()\n }\n )\n\n shadow.appendChild(overlay)\n\n // ── Drawing event listeners ────────────────────────────────────────────\n\n overlay.addEventListener('pointerdown', (e: PointerEvent) => {\n if (e.button !== 0) return\n overlay.setPointerCapture(e.pointerId)\n drawing = true\n startX = e.clientX + window.scrollX\n startY = e.clientY + window.scrollY\n if (currentTool === 'pen') currentStroke = [{ x: startX, y: startY }]\n })\n\n overlay.addEventListener('pointermove', (e: PointerEvent) => {\n if (!drawing) return\n const cx = e.clientX + window.scrollX\n const cy = e.clientY + window.scrollY\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) svg.appendChild(previewEl)\n })\n\n overlay.addEventListener('pointerup', (e: PointerEvent) => {\n if (!drawing) return\n drawing = false\n if (previewEl) { previewEl.remove(); previewEl = null }\n\n const cx = e.clientX + window.scrollX\n const cy = e.clientY + window.scrollY\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 redraw()\n onShapeCommitted()\n }\n })\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","export 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","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 } 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\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","showAnnotationOverlay","currentTool","shapes","drawing","currentStroke","previewEl","commentPopup","overlay","svg","cleanup","toolbar","redraw","s","getAnchor","sumX","sumY","count","onShapeCommitted","showCommentPopup","anchorX","anchorY","W","MARGIN","left","top","textarea","cancelBtn","ok","text","anchor","createToolbar","tool","cx","cy","r","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","t","repliesCache","render","comments","filter","filtered","createCommentCard","cachedReplies","card","freshReplies","id","capturePageScreenshot","html2canvas","loadHtml2Canvas","captureWithHtml2Canvas","captureViewportFallback","scrollX","scrollY","canvas","ctx","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,EAAUD,GAA8C;AACtE,SAAAL,EAAU,IAAIK,CAAE,GACT,MAAML,EAAU,OAAOK,CAAE;AAClC;AAEO,SAASE,IAA6B;AAC3C,MAAIN,EAAM,SAAU,QAAOA,EAAM;AACjC,QAAMO,IAAS,aAAa,QAAQ,eAAe;AACnD,SAAIA,KACFL,EAAS,EAAE,UAAUK,GAAQ,GACtBA,KAEF;AACT;AAEO,SAASC,EAAYC,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,EAAUC,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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"}
|