@tacktext/widget 0.1.2 → 0.1.4

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,"sources":["../src/api.ts","../src/auth.ts","../src/styles.ts","../src/selector.ts","../src/capture.ts","../src/ui/form.ts","../src/ui/avatars.ts","../src/ui/panel.ts","../src/anchoring.ts","../src/ui/pins.ts","../src/ui/card.ts","../src/realtime.ts","../src/presence.ts","../src/widget.ts","../src/index.ts"],"sourcesContent":["import type { Comment, Version, CommentAnchor } from './types'\n\ninterface CreateCommentData {\n content: string\n author_name?: string\n author_email?: string\n parent_id?: string\n element_selector?: string | null\n x_percent?: number\n y_percent?: number\n anchor?: CommentAnchor\n page_path: string\n screenshot_data?: string\n}\n\nexport class TackAPI {\n private baseUrl: string\n private projectId: string\n private authToken: string | null = null\n private onUnauthorized: (() => void) | null = null\n\n constructor(baseUrl: string, projectId: string) {\n this.baseUrl = baseUrl\n this.projectId = projectId\n }\n\n setAuthToken(token: string | null): void {\n this.authToken = token\n }\n\n setOnUnauthorized(cb: () => void): void {\n this.onUnauthorized = cb\n }\n\n private getHeaders(): Record<string, string> {\n const headers: Record<string, string> = { 'Content-Type': 'application/json' }\n if (this.authToken) {\n headers['Authorization'] = `Bearer ${this.authToken}`\n }\n return headers\n }\n\n private async handleResponse(res: Response): Promise<Response> {\n if (res.status === 401) {\n this.onUnauthorized?.()\n }\n return res\n }\n\n async getComments(): Promise<{ comments: Comment[]; version: Version }> {\n const res = await fetch(\n `${this.baseUrl}/projects/${this.projectId}/comments?path=${encodeURIComponent(window.location.pathname)}`,\n { headers: this.getHeaders() }\n )\n await this.handleResponse(res)\n if (!res.ok) throw new Error('Failed to fetch comments')\n return res.json()\n }\n\n async createComment(data: CreateCommentData): Promise<Comment> {\n const res = await fetch(`${this.baseUrl}/comments`, {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify({\n project_id: this.projectId,\n ...data,\n }),\n })\n await this.handleResponse(res)\n if (!res.ok) throw new Error('Failed to create comment')\n return res.json()\n }\n\n async updateComment(id: string, data: { resolved?: boolean; content?: string }): Promise<Comment> {\n const res = await fetch(`${this.baseUrl}/comments/${id}`, {\n method: 'PATCH',\n headers: this.getHeaders(),\n body: JSON.stringify(data),\n })\n await this.handleResponse(res)\n if (!res.ok) throw new Error('Failed to update comment')\n return res.json()\n }\n\n async deleteComment(id: string): Promise<void> {\n const res = await fetch(`${this.baseUrl}/comments/${id}`, {\n method: 'DELETE',\n headers: this.getHeaders(),\n })\n await this.handleResponse(res)\n if (!res.ok) throw new Error('Failed to delete comment')\n }\n\n async uploadScreenshot(data: string): Promise<string> {\n const res = await fetch(`${this.baseUrl}/upload`, {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify({ image: data, project_id: this.projectId }),\n })\n await this.handleResponse(res)\n if (!res.ok) throw new Error('Failed to upload screenshot')\n const { url } = await res.json()\n return url\n }\n}\n","export interface WidgetUser {\n id: string\n name: string\n email: string\n avatar_url: string | null\n}\n\ntype AuthChangeCallback = (user: WidgetUser | null) => void\n\nexport class WidgetAuth {\n private apiUrl: string\n private sessionToken: string | null = null\n private user: WidgetUser | null = null\n private onAuthChange: AuthChangeCallback | null = null\n\n constructor(apiUrl: string) {\n this.apiUrl = apiUrl\n }\n\n async init(): Promise<void> {\n const token = localStorage.getItem('tack_session')\n if (!token) return\n\n try {\n const res = await fetch(`${this.apiUrl}/auth/widget-session`, {\n headers: { Authorization: `Bearer ${token}` },\n })\n if (!res.ok) {\n localStorage.removeItem('tack_session')\n return\n }\n\n const { user } = await res.json()\n this.sessionToken = token\n this.user = {\n id: user.user_id,\n name: user.user_name,\n email: user.user_email,\n avatar_url: user.user_avatar_url,\n }\n this.onAuthChange?.(this.user)\n } catch {\n localStorage.removeItem('tack_session')\n }\n }\n\n signIn(): Promise<WidgetUser> {\n return new Promise((resolve, reject) => {\n // Derive auth base URL from API URL (e.g., https://tackapp.io/api -> https://tackapp.io)\n const baseUrl = this.apiUrl.replace(/\\/api$/, '')\n const popup = window.open(\n `${baseUrl}/auth/widget`,\n 'tack-auth',\n 'width=500,height=600,popup=yes'\n )\n\n if (!popup) {\n reject(new Error('Popup blocked'))\n return\n }\n\n const expectedOrigin = new URL(this.apiUrl).origin\n\n const handleMessage = (event: MessageEvent) => {\n if (event.data?.type !== 'tack:auth') return\n if (event.origin !== expectedOrigin) return\n\n window.removeEventListener('message', handleMessage)\n clearInterval(pollTimer)\n\n const { token, user } = event.data\n this.sessionToken = token\n this.user = {\n id: user.user_id,\n name: user.user_name,\n email: user.user_email,\n avatar_url: user.user_avatar_url,\n }\n localStorage.setItem('tack_session', token)\n this.onAuthChange?.(this.user)\n resolve(this.user)\n }\n\n window.addEventListener('message', handleMessage)\n\n // Poll in case the popup closes without posting a message\n const pollTimer = setInterval(() => {\n if (popup.closed) {\n clearInterval(pollTimer)\n window.removeEventListener('message', handleMessage)\n if (!this.user) {\n reject(new Error('Auth popup closed'))\n }\n }\n }, 500)\n })\n }\n\n signOut(): void {\n if (this.sessionToken) {\n fetch(`${this.apiUrl}/auth/widget-session`, {\n method: 'DELETE',\n headers: { Authorization: `Bearer ${this.sessionToken}` },\n }).catch(() => {})\n }\n\n this.sessionToken = null\n this.user = null\n localStorage.removeItem('tack_session')\n this.onAuthChange?.(null)\n }\n\n getUser(): WidgetUser | null {\n return this.user\n }\n\n getSessionToken(): string | null {\n return this.sessionToken\n }\n\n isAuthenticated(): boolean {\n return this.user !== null\n }\n\n setOnAuthChange(cb: AuthChangeCallback): void {\n this.onAuthChange = cb\n }\n}\n","export function createStyles(): string {\n return `\n * {\n box-sizing: border-box;\n }\n\n .tack-wrapper {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-size: 14px;\n line-height: 1.5;\n color: #18181b;\n }\n\n /* Toggle Button - Teal gradient */\n .tack-toggle {\n position: fixed;\n bottom: 20px;\n right: 20px;\n width: 52px;\n height: 52px;\n border-radius: 16px;\n background: linear-gradient(135deg, #14B8A6 0%, #0D9488 100%);\n color: white;\n border: none;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow:\n 0 4px 14px rgba(20, 184, 166, 0.4),\n 0 2px 4px rgba(0, 0, 0, 0.1);\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n z-index: 10000;\n }\n\n .tack-toggle:hover {\n transform: translateY(-2px) scale(1.02);\n box-shadow:\n 0 8px 20px rgba(20, 184, 166, 0.5),\n 0 4px 8px rgba(0, 0, 0, 0.1);\n }\n\n .tack-toggle:active {\n transform: scale(0.98);\n }\n\n .tack-active .tack-toggle {\n opacity: 0;\n pointer-events: none;\n }\n\n /* Pins container - hidden by default */\n .tack-pins-container {\n display: none;\n }\n\n .tack-pins-container.visible {\n display: block;\n }\n\n /* Comment Pin — unified white shell */\n .tack-pin {\n position: absolute;\n cursor: pointer;\n z-index: 9999;\n transition: transform 150ms ease, filter 150ms ease;\n filter: drop-shadow(0 2px 6px rgba(0,0,0,0.15)) drop-shadow(0 1px 2px rgba(0,0,0,0.10));\n }\n\n .tack-pin:hover {\n filter: drop-shadow(0 4px 12px rgba(0,0,0,0.18)) drop-shadow(0 2px 4px rgba(0,0,0,0.12));\n z-index: 10000;\n }\n\n /* White shell — the unified container (circle + ::after tail)\n Uses CSS Grid so avatar and preview stack in the same cell.\n The shell's width/max-height control which content is visible\n via overflow:hidden, enabling smooth morph animations. */\n .tack-pin-shell {\n position: relative;\n background: #FFFFFF;\n border-radius: 19px;\n padding: 3px;\n display: grid;\n align-items: start;\n width: 38px;\n max-height: 38px;\n will-change: width, max-height, border-radius;\n }\n\n /* All direct children stack in the same grid cell */\n .tack-pin-shell > * {\n grid-area: 1 / 1;\n }\n\n /* Multi-author shell becomes pill-shaped */\n .tack-pin.multi-author .tack-pin-shell {\n border-radius: 17px;\n padding: 3px;\n width: auto;\n max-height: 34px;\n }\n\n /* White tail as ::after pseudo-element — seamless with shell.\n Height is 13px (not 6px) so the top of the triangle extends behind\n the circle where it curves away from the bounding box. The opaque\n circle paints over the hidden overlap, eliminating the gap. */\n .tack-pin-shell::after {\n content: '';\n position: absolute;\n bottom: -7px;\n left: 2px;\n width: 18px;\n height: 16px;\n background: #FFFFFF;\n clip-path: polygon(25% 100%, 100% 0%, 0% 0%);\n z-index: -1;\n }\n\n /* Multi-author: tail under first avatar */\n .tack-pin.multi-author .tack-pin-shell::after {\n left: 10px;\n }\n\n /* Avatar inset — sits inside the shell with white padding visible */\n .tack-pin-avatar-img {\n border-radius: 50%;\n object-fit: cover;\n display: block;\n }\n\n .tack-pin-avatar-fallback {\n border-radius: 50%;\n color: white;\n font-weight: 600;\n display: flex;\n align-items: center;\n justify-content: center;\n line-height: 1;\n }\n\n /* Multi-author avatar stack inside the shell */\n .tack-pin-avatars {\n display: flex;\n align-items: center;\n z-index: 1;\n transition: opacity 120ms ease;\n }\n\n .tack-pin-avatar-stacked {\n width: 28px;\n height: 28px;\n border-radius: 50%;\n margin-left: -8px;\n position: relative;\n flex-shrink: 0;\n overflow: hidden;\n line-height: 1;\n border: 2px solid #FFFFFF;\n }\n\n .tack-pin-avatar-stacked:first-child {\n margin-left: 0;\n }\n\n .tack-pin-avatar-stacked img,\n .tack-pin-avatar-stacked div {\n display: block;\n }\n\n /* Overflow \"+N\" chip */\n .tack-pin-avatar-overflow {\n background: #E4E4E7;\n color: #52525B;\n font-size: 9px;\n font-weight: 600;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n /* ── Hover Preview Card ── */\n\n /* Shell transitions — collapse timing by default (faster close) */\n .tack-pin-shell {\n overflow: hidden;\n transition:\n width 160ms cubic-bezier(0.4, 0, 0.2, 1),\n max-height 160ms cubic-bezier(0.4, 0, 0.2, 1),\n border-radius 160ms cubic-bezier(0.4, 0, 0.2, 1),\n padding 160ms cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n /* Avatar: fade out on expand instead of display:none */\n .tack-pin-shell > .tack-pin-avatar-img,\n .tack-pin-shell > .tack-pin-avatar-fallback {\n z-index: 1;\n transition: opacity 120ms ease;\n }\n\n /* Expanded shell — smooth open */\n .tack-pin.expanded .tack-pin-shell {\n width: 256px;\n max-height: 130px;\n border-radius: 14px;\n padding: 3px;\n transition:\n width 240ms cubic-bezier(0.16, 1, 0.3, 1),\n max-height 240ms cubic-bezier(0.16, 1, 0.3, 1),\n border-radius 240ms cubic-bezier(0.16, 1, 0.3, 1),\n padding 240ms cubic-bezier(0.16, 1, 0.3, 1);\n }\n\n .tack-pin.expanded.multi-author .tack-pin-shell {\n border-radius: 14px;\n width: 256px;\n max-height: 130px;\n }\n\n /* Fade avatar/stacked avatars when expanded (not display:none) */\n .tack-pin.expanded .tack-pin-shell > .tack-pin-avatar-img,\n .tack-pin.expanded .tack-pin-shell > .tack-pin-avatar-fallback {\n opacity: 0;\n pointer-events: none;\n }\n\n .tack-pin.expanded .tack-pin-avatars {\n opacity: 0;\n pointer-events: none;\n }\n\n /* Preview card: always rendered, clipped by shell overflow when collapsed */\n .tack-pin-preview {\n display: flex;\n width: 240px;\n flex-direction: column;\n padding: 6px 8px 8px 8px;\n opacity: 0;\n pointer-events: none;\n align-self: start;\n justify-self: start;\n }\n\n .tack-pin.expanded .tack-pin-preview {\n opacity: 1;\n pointer-events: auto;\n transition:\n opacity 120ms cubic-bezier(0.4, 0, 0.2, 1) 100ms;\n }\n\n /* Top row: avatar + meta (name + time) */\n .tack-pin-preview-header {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .tack-pin-preview-avatar {\n width: 28px;\n height: 28px;\n border-radius: 50%;\n flex-shrink: 0;\n overflow: hidden;\n }\n\n .tack-pin-preview-avatar img,\n .tack-pin-preview-avatar div {\n display: block;\n }\n\n .tack-pin-preview-meta {\n flex: 1;\n min-width: 0;\n display: flex;\n align-items: baseline;\n gap: 4px;\n }\n\n .tack-pin-preview-name {\n font-size: 12.5px;\n font-weight: 600;\n color: #18181b;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 120px;\n }\n\n .tack-pin-preview-time {\n font-size: 11px;\n color: #a1a1aa;\n white-space: nowrap;\n flex-shrink: 0;\n }\n\n /* Comment text preview — staggered fade */\n .tack-pin-preview-text {\n font-size: 12.5px;\n line-height: 1.4;\n color: #3f3f46;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n text-overflow: ellipsis;\n word-break: break-word;\n margin: 4px 0 0 0;\n opacity: 0;\n transform: translateY(4px);\n }\n\n .tack-pin.expanded .tack-pin-preview-text {\n opacity: 1;\n transform: translateY(0);\n transition:\n opacity 120ms cubic-bezier(0.4, 0, 0.2, 1) 140ms,\n transform 120ms cubic-bezier(0.4, 0, 0.2, 1) 140ms;\n }\n\n /* Reply count badge */\n .tack-pin-preview-replies {\n font-size: 11px;\n color: #a1a1aa;\n margin-top: 4px;\n opacity: 0;\n }\n\n .tack-pin.expanded .tack-pin-preview-replies {\n opacity: 1;\n transition: opacity 100ms ease 180ms;\n }\n\n /* Expanded pin gets higher z-index */\n .tack-pin.expanded {\n z-index: 10000;\n }\n\n /* Expand-left variant (near right viewport edge) —\n shift shell leftward so the card doesn't overflow viewport */\n .tack-pin.expand-left.expanded .tack-pin-shell {\n transform: translateX(-218px);\n }\n\n /* Active pins should not show the preview (card is already open) */\n .tack-pin.active .tack-pin-preview {\n opacity: 0 !important;\n pointer-events: none !important;\n }\n\n .tack-pin.active.expanded .tack-pin-shell > .tack-pin-avatar-img,\n .tack-pin.active.expanded .tack-pin-shell > .tack-pin-avatar-fallback {\n opacity: 1;\n pointer-events: auto;\n }\n\n .tack-pin.active.expanded .tack-pin-avatars {\n opacity: 1;\n pointer-events: auto;\n }\n\n .tack-pin.active.expanded .tack-pin-shell {\n border-radius: 19px;\n width: 38px;\n max-height: 38px;\n }\n\n .tack-pin.active.expanded.multi-author .tack-pin-shell {\n border-radius: 17px;\n width: auto;\n max-height: 34px;\n }\n\n /* On touch devices, suppress hover preview entirely */\n @media (hover: none) {\n .tack-pin.expanded .tack-pin-preview {\n opacity: 0;\n pointer-events: none;\n }\n\n .tack-pin.expanded .tack-pin-shell > .tack-pin-avatar-img,\n .tack-pin.expanded .tack-pin-shell > .tack-pin-avatar-fallback {\n opacity: 1;\n pointer-events: auto;\n }\n\n .tack-pin.expanded .tack-pin-avatars {\n opacity: 1;\n pointer-events: auto;\n }\n\n .tack-pin.expanded .tack-pin-shell {\n border-radius: 19px;\n width: 38px;\n max-height: 38px;\n }\n\n .tack-pin.expanded.multi-author .tack-pin-shell {\n border-radius: 17px;\n width: auto;\n max-height: 34px;\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .tack-pin-shell,\n .tack-pin.expanded .tack-pin-shell,\n .tack-pin-preview,\n .tack-pin.expanded .tack-pin-preview,\n .tack-pin-preview-text,\n .tack-pin.expanded .tack-pin-preview-text {\n transition-duration: 0ms !important;\n transition-delay: 0ms !important;\n }\n }\n\n /* Resolved pin: green check dot */\n .tack-pin-check {\n position: absolute;\n bottom: 8px;\n right: -2px;\n width: 14px;\n height: 14px;\n border-radius: 50%;\n background: #10B981;\n border: 2px solid white;\n z-index: 10;\n }\n\n /* Resolved: fade entire pin */\n .tack-pin.resolved {\n opacity: 0.5;\n }\n\n .tack-pin.resolved:hover {\n opacity: 0.85;\n }\n\n /* Active / selected pin — solid blue ring on shell */\n .tack-pin.active {\n transform: scale(1.05);\n }\n\n .tack-pin.active .tack-pin-shell {\n box-shadow: 0 0 0 2.5px #2563EB;\n }\n\n /* Highlighted — finite pulse, 2 cycles */\n .tack-pin.highlighted {\n animation: tack-pulse 1.5s ease-in-out 2;\n }\n\n @keyframes tack-pulse {\n 0%, 100% {\n filter: drop-shadow(0 2px 6px rgba(0,0,0,0.15)) drop-shadow(0 1px 2px rgba(0,0,0,0.10));\n }\n 50% {\n filter: drop-shadow(0 0 6px rgba(37,99,235,0.3)) drop-shadow(0 2px 8px rgba(0,0,0,0.10));\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .tack-pin.highlighted {\n animation: none;\n filter: drop-shadow(0 0 4px rgba(37,99,235,0.25)) drop-shadow(0 2px 8px rgba(0,0,0,0.10));\n }\n }\n\n /* Panel Strip */\n .tack-panel-strip {\n position: fixed;\n top: 0;\n right: 0;\n width: 360px;\n height: 100vh;\n background: transparent;\n padding: 0;\n transform: translateX(100%);\n transition: transform 250ms cubic-bezier(0.16, 1, 0.3, 1);\n z-index: 10001;\n display: flex;\n flex-direction: column;\n }\n\n .tack-panel-strip.open {\n transform: translateX(0);\n }\n\n /* Panel Card */\n .tack-panel {\n flex: 1;\n background: #FFFFFF;\n border-radius: 0;\n border: none;\n box-shadow:\n 0 0 0 1px rgba(0, 0, 0, 0.04),\n 0 2px 4px rgba(0, 0, 0, 0.04),\n 0 8px 24px rgba(0, 0, 0, 0.08);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .tack-panel-header {\n padding: 12px 16px;\n border-bottom: 1px solid rgba(0, 0, 0, 0.06);\n display: flex;\n align-items: center;\n gap: 10px;\n }\n\n .tack-panel-close {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border-radius: 6px;\n background: transparent;\n border: none;\n cursor: pointer;\n color: #71717a;\n flex-shrink: 0;\n transition: all 0.15s ease;\n }\n\n .tack-panel-close:hover {\n background: #f4f4f5;\n color: #18181b;\n }\n\n /* Search bar */\n .tack-panel-search-wrap {\n flex: 1;\n display: flex;\n align-items: center;\n gap: 6px;\n background: #f4f4f5;\n border-radius: 6px;\n padding: 0 8px;\n min-height: 32px;\n min-width: 0;\n }\n\n .tack-panel-search-icon {\n flex-shrink: 0;\n color: #a1a1aa;\n }\n\n .tack-panel-search {\n flex: 1;\n border: none;\n background: transparent;\n font-size: 13px;\n font-family: inherit;\n color: #18181b;\n outline: none;\n min-width: 0;\n padding: 0;\n line-height: 32px;\n }\n\n .tack-panel-search::placeholder {\n color: #a1a1aa;\n }\n\n .tack-panel-search-clear {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n border-radius: 4px;\n border: none;\n background: transparent;\n cursor: pointer;\n color: #a1a1aa;\n flex-shrink: 0;\n padding: 0;\n transition: all 0.1s ease;\n }\n\n .tack-panel-search-clear:hover {\n background: rgba(0, 0, 0, 0.08);\n color: #71717a;\n }\n\n /* Icon buttons (filter + more) */\n .tack-panel-filter-wrap,\n .tack-panel-more-wrap {\n position: relative;\n flex-shrink: 0;\n }\n\n .tack-panel-filter-btn,\n .tack-panel-more-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n border-radius: 6px;\n background: transparent;\n border: none;\n cursor: pointer;\n color: #71717a;\n position: relative;\n transition: all 0.15s ease;\n padding: 0;\n }\n\n .tack-panel-filter-btn:hover,\n .tack-panel-more-btn:hover {\n background: #f4f4f5;\n color: #18181b;\n }\n\n .tack-panel-filter-btn.filter-active {\n color: #2563EB;\n }\n\n .tack-panel-filter-btn.filter-active:hover {\n color: #1d4ed8;\n }\n\n /* Filter badge (count) */\n .tack-panel-filter-badge {\n position: absolute;\n top: 2px;\n right: 2px;\n min-width: 16px;\n height: 16px;\n border-radius: 8px;\n background: #2563EB;\n color: white;\n font-size: 10px;\n font-weight: 600;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0 4px;\n line-height: 1;\n }\n\n /* Dark dropdown menu */\n .tack-panel-filter-dropdown,\n .tack-panel-more-dropdown {\n position: absolute;\n top: calc(100% + 6px);\n right: 0;\n background: #1e1e1e;\n border-radius: 10px;\n box-shadow: 0 8px 30px rgba(0, 0, 0, 0.25), 0 0 0 1px rgba(255, 255, 255, 0.06);\n z-index: 100;\n min-width: 220px;\n padding: 4px;\n overflow: hidden;\n }\n\n .tack-panel-filter-dropdown[data-state=\"closed\"],\n .tack-panel-more-dropdown[data-state=\"closed\"] {\n display: none;\n }\n\n .tack-panel-filter-dropdown[data-state=\"open\"],\n .tack-panel-more-dropdown[data-state=\"open\"] {\n display: block;\n animation: tack-dropdown-in 0.12s ease;\n }\n\n @keyframes tack-dropdown-in {\n from {\n opacity: 0;\n transform: translateY(-4px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n }\n\n .tack-dropdown-item {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n padding: 8px 10px;\n font-size: 13px;\n font-family: inherit;\n background: transparent;\n border: none;\n cursor: pointer;\n text-align: left;\n color: #e4e4e7;\n border-radius: 6px;\n transition: background 0.1s ease;\n }\n\n .tack-dropdown-item:hover {\n background: rgba(255, 255, 255, 0.08);\n }\n\n .tack-dropdown-check {\n flex-shrink: 0;\n color: #a1a1aa;\n }\n\n .tack-dropdown-check-space {\n display: inline-block;\n width: 14px;\n flex-shrink: 0;\n }\n\n .tack-dropdown-sep {\n height: 1px;\n background: rgba(255, 255, 255, 0.08);\n margin: 4px 10px;\n }\n\n /* Toggle items */\n .tack-dropdown-toggle {\n justify-content: space-between;\n }\n\n .tack-dropdown-toggle-switch {\n width: 32px;\n height: 18px;\n border-radius: 9px;\n background: rgba(255, 255, 255, 0.15);\n position: relative;\n flex-shrink: 0;\n transition: background 0.15s ease;\n }\n\n .tack-dropdown-toggle-switch.on {\n background: #2563EB;\n }\n\n .tack-dropdown-toggle-knob {\n position: absolute;\n top: 2px;\n left: 2px;\n width: 14px;\n height: 14px;\n border-radius: 50%;\n background: white;\n transition: transform 0.15s ease;\n }\n\n .tack-dropdown-toggle-switch.on .tack-dropdown-toggle-knob {\n transform: translateX(14px);\n }\n\n /* Unread dot */\n .tack-unread-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: #2563EB;\n flex-shrink: 0;\n }\n\n .tack-comment-row-top {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 6px;\n }\n\n /* Empty state reset button */\n .tack-empty-reset {\n display: inline-block;\n margin-top: 12px;\n padding: 6px 14px;\n font-size: 12px;\n font-weight: 500;\n font-family: inherit;\n border: 1px solid #e4e4e7;\n border-radius: 6px;\n background: white;\n color: #71717a;\n cursor: pointer;\n transition: all 0.15s ease;\n }\n\n .tack-empty-reset:hover {\n background: #f4f4f5;\n color: #18181b;\n border-color: #d4d4d8;\n }\n\n .tack-panel-content {\n flex: 1;\n overflow-y: auto;\n padding: 6px 0;\n mask-image: linear-gradient(\n to bottom,\n transparent 0%,\n black 8px,\n black calc(100% - 8px),\n transparent 100%\n );\n }\n\n /* Comment Row — ghost card on hover */\n .tack-comment-row {\n padding: 14px 16px;\n margin: 0 6px;\n border-radius: 8px;\n cursor: pointer;\n transition: background-color 150ms ease-out;\n position: relative;\n }\n\n .tack-comment-row:hover {\n background-color: rgba(0, 0, 0, 0.04);\n }\n\n /* Row action buttons (overflow + resolve) */\n .tack-comment-row-actions {\n position: absolute;\n top: 10px;\n right: 10px;\n display: flex;\n align-items: center;\n gap: 2px;\n opacity: 0;\n transition: opacity 120ms ease;\n }\n\n .tack-comment-row:hover .tack-comment-row-actions {\n opacity: 1;\n }\n\n .tack-comment-row-more,\n .tack-comment-row-resolve {\n width: 28px;\n height: 28px;\n border-radius: 6px;\n border: none;\n background: transparent;\n cursor: pointer;\n color: #a1a1aa;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n transition: all 120ms ease;\n }\n\n .tack-comment-row-more:hover,\n .tack-comment-row-resolve:hover {\n background: rgba(0, 0, 0, 0.06);\n color: #18181b;\n }\n\n .tack-comment-row-resolve.resolved {\n color: #10B981;\n }\n\n .tack-comment-row-resolve.resolved:hover {\n background: rgba(16, 185, 129, 0.1);\n color: #059669;\n }\n\n .tack-comment-row:active {\n background-color: rgba(0, 0, 0, 0.06);\n transition-duration: 50ms;\n }\n\n .tack-comment-row.highlighted {\n background-color: rgba(20, 184, 166, 0.06);\n }\n\n .tack-comment-row.highlighted::before {\n content: '';\n position: absolute;\n left: 0;\n top: 6px;\n bottom: 6px;\n width: 3px;\n border-radius: 0 3px 3px 0;\n background: #14B8A6;\n }\n\n .tack-comment-avatar {\n width: 32px;\n height: 32px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n color: white;\n font-size: 12px;\n font-weight: 600;\n flex-shrink: 0;\n line-height: 1;\n margin-bottom: 8px;\n }\n\n .tack-comment-meta {\n display: flex;\n align-items: baseline;\n gap: 4px;\n flex-wrap: wrap;\n margin-bottom: 4px;\n }\n\n .tack-comment-author {\n font-weight: 600;\n font-size: 13px;\n color: #18181b;\n }\n\n .tack-comment-time {\n font-size: 12px;\n color: rgba(0, 0, 0, 0.35);\n font-weight: 400;\n }\n\n .tack-comment-resolved-badge {\n font-size: 11px;\n font-weight: 500;\n padding: 1px 6px;\n border-radius: 9999px;\n background: #ECFDF5;\n color: #059669;\n line-height: 1.4;\n }\n\n .tack-comment-page-badge {\n font-size: 11px;\n font-weight: 500;\n padding: 1px 6px;\n border-radius: 9999px;\n background: #EEF2FF;\n color: #4F46E5;\n line-height: 1.4;\n }\n\n .tack-comment-body {\n padding-left: 0;\n margin-top: 2px;\n }\n\n .tack-comment-body.dimmed {\n opacity: 0.6;\n }\n\n .tack-comment-screenshot {\n width: 120px;\n height: 64px;\n object-fit: cover;\n border-radius: 6px;\n margin-bottom: 6px;\n border: 1px solid #e4e4e7;\n display: block;\n }\n\n .tack-comment-content {\n font-size: 13.5px;\n color: #3f3f46;\n line-height: 1.5;\n }\n\n /* New Comment Input */\n .tack-new-comment {\n padding: 10px 12px;\n border-top: 1px solid rgba(0, 0, 0, 0.06);\n }\n\n .tack-new-comment-input {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 12px;\n background: #f4f4f5;\n border: 1px dashed #d4d4d8;\n border-radius: 8px;\n font-size: 13px;\n color: #71717a;\n cursor: pointer;\n transition: all 0.15s ease;\n font-weight: 500;\n }\n\n .tack-new-comment-input:hover {\n background: #e4e4e7;\n border-color: #a1a1aa;\n color: #18181b;\n }\n\n /* Comment Form - Floating pill input */\n .tack-form {\n position: fixed;\n z-index: 10003;\n display: none;\n }\n\n .tack-form.visible {\n display: block;\n animation: tack-form-in 0.15s ease;\n }\n\n @keyframes tack-form-in {\n from {\n opacity: 0;\n transform: translateY(4px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n }\n\n /* Pill container */\n .tack-form-pill {\n display: flex;\n align-items: flex-end;\n gap: 6px;\n background: #FFFFFF;\n border: 1px solid #D1D5DB;\n border-radius: 20px;\n padding: 4px 4px 4px 16px;\n width: 300px;\n box-shadow: 0 4px 12px rgba(0,0,0,0.08), 0 1px 3px rgba(0,0,0,0.06);\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n }\n\n .tack-form-pill:focus-within {\n border-color: #9CA3AF;\n box-shadow: 0 4px 12px rgba(0,0,0,0.10), 0 1px 3px rgba(0,0,0,0.06);\n }\n\n /* Textarea styled as single-line input */\n .tack-form-pill-input {\n flex: 1;\n border: none;\n background: transparent;\n font-size: 13px;\n font-family: inherit;\n color: #111827;\n outline: none;\n resize: none;\n min-width: 0;\n padding: 4px 0;\n line-height: 20px;\n height: 28px;\n overflow-y: hidden;\n min-height: 28px;\n }\n\n .tack-form-pill-input::placeholder {\n color: #9CA3AF;\n }\n\n /* Send button */\n .tack-form-pill-send {\n width: 28px;\n height: 28px;\n border-radius: 50%;\n border: none;\n background: #E5E7EB;\n color: #9CA3AF;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n transition: background-color 150ms ease, color 150ms ease;\n }\n\n .tack-form-pill-send.active {\n background: #18181B;\n color: #FFFFFF;\n }\n\n .tack-form-pill-send.active:hover {\n background: #27272A;\n }\n\n .tack-form-pill-send.sending {\n opacity: 0.5;\n pointer-events: none;\n }\n\n /* Empty State */\n .tack-empty {\n text-align: center;\n padding: 40px 20px;\n }\n\n .tack-empty-icon {\n margin-bottom: 12px;\n margin-left: auto;\n margin-right: auto;\n color: #a1a1aa;\n }\n\n .tack-empty-title {\n font-size: 14px;\n font-weight: 500;\n color: #18181b;\n margin: 0 0 4px 0;\n }\n\n .tack-empty-subtitle {\n font-size: 12px;\n color: #a1a1aa;\n margin: 0;\n }\n\n /* Comment Card Popover */\n .tack-card {\n position: fixed;\n width: 320px;\n background: white;\n border-radius: 12px;\n border: 1px solid #e4e4e7;\n box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12), 0 0 0 1px rgba(0, 0, 0, 0.04);\n z-index: 10003;\n max-height: 480px;\n overflow-y: auto;\n display: none;\n flex-direction: column;\n }\n\n .tack-card.visible {\n display: flex;\n animation: tack-form-in 0.15s ease;\n }\n\n /* Card title bar */\n .tack-card-title-bar {\n display: flex;\n align-items: center;\n padding: 10px 12px;\n border-bottom: 1px solid #F4F4F5;\n flex-shrink: 0;\n }\n\n .tack-card-title {\n flex: 1;\n font-weight: 600;\n font-size: 13px;\n color: #18181b;\n }\n\n .tack-card-title-actions {\n display: flex;\n align-items: center;\n gap: 2px;\n }\n\n .tack-card-resolve-icon,\n .tack-card-close-btn {\n width: 28px;\n height: 28px;\n border-radius: 6px;\n border: none;\n background: transparent;\n cursor: pointer;\n color: #A1A1AA;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.15s ease;\n }\n\n .tack-card-resolve-icon:hover,\n .tack-card-close-btn:hover {\n background: #F4F4F5;\n color: #18181b;\n }\n\n .tack-card-resolve-icon.resolved {\n color: #10B981;\n }\n\n /* Resolved banner */\n .tack-card-resolved-banner {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 12px;\n background: #ECFDF5;\n color: #059669;\n font-size: 12px;\n font-weight: 500;\n flex-shrink: 0;\n }\n\n /* Card content area (scrollable) */\n .tack-card-row {\n padding: 12px;\n position: relative;\n }\n\n .tack-card-row:hover .tack-card-more-btn {\n opacity: 1;\n }\n\n .tack-card-row-header {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .tack-card-row-avatar {\n width: 28px;\n height: 28px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n color: white;\n font-size: 10px;\n font-weight: 600;\n flex-shrink: 0;\n line-height: 1;\n }\n\n .tack-card-row-meta {\n flex: 1;\n min-width: 0;\n display: flex;\n align-items: center;\n gap: 4px;\n flex-wrap: wrap;\n }\n\n .tack-card-row-author {\n font-weight: 600;\n font-size: 13px;\n color: #18181b;\n }\n\n .tack-card-row-time {\n font-size: 12px;\n color: #A1A1AA;\n }\n\n .tack-card-editing-badge {\n font-size: 11px;\n font-weight: 600;\n color: #6366F1;\n }\n\n /* More (three-dot) button */\n .tack-card-more-btn {\n width: 24px;\n height: 24px;\n border-radius: 6px;\n border: none;\n background: #F4F4F5;\n cursor: pointer;\n color: #71717a;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n opacity: 0;\n transition: opacity 0.15s ease, background 0.15s ease;\n }\n\n .tack-card-more-btn:hover {\n background: #E4E4E7;\n color: #18181b;\n }\n\n /* Dropdown menu */\n .tack-card-dropdown {\n position: absolute;\n top: 42px;\n right: 12px;\n background: white;\n border: 1px solid #E4E4E7;\n border-radius: 8px;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);\n z-index: 10;\n overflow: hidden;\n min-width: 140px;\n }\n\n .tack-card-dropdown-item {\n display: block;\n width: 100%;\n padding: 8px 12px;\n font-size: 13px;\n font-family: inherit;\n background: transparent;\n border: none;\n cursor: pointer;\n text-align: left;\n color: #18181b;\n transition: background 0.1s ease;\n }\n\n .tack-card-dropdown-item:hover {\n background: #F4F4F5;\n }\n\n .tack-card-dropdown-item.delete {\n color: #EF4444;\n }\n\n .tack-card-dropdown-item.delete:hover {\n background: #FEF2F2;\n }\n\n /* Comment body text */\n .tack-card-row-body {\n padding: 6px 0 0 36px;\n font-size: 13px;\n color: #3F3F46;\n line-height: 1.5;\n }\n\n .tack-card-row-screenshot {\n width: 200px;\n border-radius: 6px;\n object-fit: cover;\n border: 1px solid #e4e4e7;\n display: block;\n margin: 8px 0 0 36px;\n }\n\n /* Divider */\n .tack-card-divider {\n height: 1px;\n background: #F4F4F5;\n margin: 0;\n }\n\n /* Edit mode */\n .tack-card-edit-wrap {\n padding: 8px 0 0 36px;\n }\n\n .tack-card-edit-textarea {\n width: 100%;\n padding: 8px 10px;\n border: 1px solid #18181B;\n border-radius: 6px;\n font-size: 13px;\n font-family: inherit;\n resize: vertical;\n min-height: 60px;\n line-height: 1.5;\n color: #18181b;\n background: white;\n }\n\n .tack-card-edit-textarea:focus {\n outline: none;\n }\n\n .tack-card-edit-actions {\n display: flex;\n justify-content: flex-end;\n gap: 6px;\n margin-top: 8px;\n }\n\n .tack-card-edit-cancel {\n padding: 5px 12px;\n font-size: 12px;\n font-weight: 500;\n border: 1px solid #E4E4E7;\n border-radius: 6px;\n background: white;\n cursor: pointer;\n color: #71717a;\n font-family: inherit;\n transition: all 0.15s ease;\n }\n\n .tack-card-edit-cancel:hover {\n background: #F4F4F5;\n color: #18181b;\n }\n\n .tack-card-edit-save {\n padding: 5px 12px;\n font-size: 12px;\n font-weight: 500;\n border: none;\n border-radius: 6px;\n background: #18181B;\n color: white;\n cursor: pointer;\n font-family: inherit;\n transition: all 0.15s ease;\n }\n\n .tack-card-edit-save:hover {\n background: #27272a;\n }\n\n /* Reply bar — always visible */\n .tack-card-reply-bar {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 12px;\n flex-shrink: 0;\n }\n\n .tack-card-reply-bar-avatar {\n width: 24px;\n height: 24px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n color: white;\n font-size: 9px;\n font-weight: 600;\n flex-shrink: 0;\n line-height: 1;\n }\n\n .tack-card-reply-bar-input-wrap {\n flex: 1;\n display: flex;\n align-items: center;\n background: #F4F4F5;\n border-radius: 9999px;\n padding: 0 4px 0 12px;\n min-height: 32px;\n }\n\n .tack-card-reply-bar-input {\n flex: 1;\n border: none;\n background: transparent;\n font-size: 13px;\n font-family: inherit;\n color: #18181b;\n outline: none;\n min-width: 0;\n }\n\n .tack-card-reply-bar-input::placeholder {\n color: #A1A1AA;\n }\n\n .tack-card-reply-bar-send {\n width: 24px;\n height: 24px;\n border-radius: 50%;\n border: none;\n background: #18181B;\n color: white;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n transition: background 0.15s ease;\n }\n\n .tack-card-reply-bar-send:hover {\n background: #27272a;\n }\n\n /* Sign-in pill variant */\n .tack-form-pill-signin {\n align-items: center;\n padding: 6px 6px 6px 16px;\n cursor: default;\n }\n\n .tack-sign-in-prompt-text {\n flex: 1;\n font-size: 13px;\n color: #9CA3AF;\n margin: 0;\n line-height: 20px;\n }\n\n .tack-sign-in-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 12px;\n border: none;\n border-radius: 14px;\n background: #F4F4F5;\n cursor: pointer;\n font-size: 12px;\n font-weight: 500;\n color: #3c4043;\n font-family: inherit;\n transition: all 0.15s ease;\n flex-shrink: 0;\n white-space: nowrap;\n }\n\n .tack-sign-in-btn:hover {\n background: #E5E7EB;\n color: #18181b;\n }\n\n .tack-sign-in-btn:active {\n background: #D1D5DB;\n }\n\n /* Avatar image support */\n .tack-avatar-img {\n flex-shrink: 0;\n }\n\n .tack-avatar-fallback {\n flex-shrink: 0;\n }\n\n .tack-card-row-avatar-wrap,\n .tack-card-reply-bar-avatar-wrap {\n display: flex;\n flex-shrink: 0;\n }\n\n /* Presence bar */\n .tack-presence-bar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 16px;\n border-bottom: 1px solid rgba(0, 0, 0, 0.06);\n }\n\n .tack-presence-avatars {\n display: flex;\n align-items: center;\n }\n\n .tack-presence-avatar {\n margin-left: -6px;\n border-radius: 50%;\n border: 2px solid white;\n display: flex;\n line-height: 1;\n }\n\n .tack-presence-avatar:first-child {\n margin-left: 0;\n }\n\n .tack-presence-overflow {\n margin-left: -6px;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n background: #e4e4e7;\n color: #52525b;\n font-size: 10px;\n font-weight: 600;\n display: flex;\n align-items: center;\n justify-content: center;\n border: 2px solid white;\n }\n\n .tack-presence-label {\n font-size: 12px;\n color: #a1a1aa;\n font-weight: 500;\n }\n\n /* Toast */\n .tack-toast {\n animation: tack-toast-in 0.2s ease;\n }\n\n @keyframes tack-toast-in {\n from {\n opacity: 0;\n transform: translateY(8px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n }\n `\n}\n","import type { CommentAnchor, TextQuoteSelector } from './types'\n\ntype SelectionCallback = (target: {\n element: Element | null\n anchor: CommentAnchor\n}) => void\n\nexport class ElementSelector {\n private callback: SelectionCallback\n private enabled = false\n private highlightedElement: Element | null = null\n private highlight: HTMLDivElement | null = null\n private scrim: HTMLDivElement | null = null\n private cursor: HTMLDivElement | null = null\n private styleElement: HTMLStyleElement | null = null\n private cursorRAF: number | null = null\n private cursorX = 0\n private cursorY = 0\n private targetX = 0\n private targetY = 0\n private settleTimer: ReturnType<typeof setTimeout> | null = null\n private pendingTarget: Element | null = null\n\n constructor(callback: SelectionCallback) {\n this.callback = callback\n this.onMouseMove = this.onMouseMove.bind(this)\n this.onMouseDown = this.onMouseDown.bind(this)\n this.onClick = this.onClick.bind(this)\n this.onKeyDown = this.onKeyDown.bind(this)\n this.animateCursor = this.animateCursor.bind(this)\n }\n\n enable(): void {\n if (this.enabled) return\n this.enabled = true\n document.addEventListener('mousemove', this.onMouseMove)\n document.addEventListener('mousedown', this.onMouseDown, true)\n document.addEventListener('click', this.onClick, true)\n document.addEventListener('keydown', this.onKeyDown)\n\n // Page scrim — very subtle dim\n this.scrim = document.createElement('div')\n this.scrim.style.cssText = `\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.03);\n z-index: 9997;\n pointer-events: none;\n transition: opacity 0.2s ease;\n `\n document.body.appendChild(this.scrim)\n\n // Highlight ring — soft shadow, no hard border\n this.highlight = document.createElement('div')\n this.highlight.style.cssText = `\n position: fixed;\n pointer-events: none;\n z-index: 9998;\n opacity: 0;\n transition: opacity 0.2s ease, left 0.15s ease-out, top 0.15s ease-out, width 0.15s ease-out, height 0.15s ease-out, border-radius 0.15s ease-out;\n border-radius: 6px;\n box-shadow: 0 0 0 1.5px rgba(0, 0, 0, 0.08), 0 2px 12px rgba(0, 0, 0, 0.06);\n background: rgba(255, 255, 255, 0.4);\n `\n document.body.appendChild(this.highlight)\n\n // Custom cursor — small circle with +\n this.cursor = document.createElement('div')\n this.cursor.style.cssText = `\n position: fixed;\n pointer-events: none;\n z-index: 10004;\n width: 28px;\n height: 28px;\n border-radius: 50%;\n background: #18181B;\n color: white;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 16px;\n font-weight: 300;\n font-family: -apple-system, BlinkMacSystemFont, sans-serif;\n line-height: 1;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);\n transform: translate(-50%, -50%);\n opacity: 0;\n transition: opacity 0.15s ease;\n `\n this.cursor.textContent = '+'\n document.body.appendChild(this.cursor)\n\n // Fade cursor in after a frame\n requestAnimationFrame(() => {\n if (this.cursor) this.cursor.style.opacity = '1'\n })\n\n // Hide the real cursor (except on the tack widget itself)\n this.styleElement = document.createElement('style')\n this.styleElement.textContent = `\n body, body *:not(#tack-widget) { cursor: none !important; }\n #tack-widget, #tack-widget * { cursor: default !important; }\n `\n document.head.appendChild(this.styleElement)\n\n this.cursorRAF = requestAnimationFrame(this.animateCursor)\n }\n\n disable(): void {\n if (!this.enabled) return\n this.enabled = false\n document.removeEventListener('mousemove', this.onMouseMove)\n document.removeEventListener('mousedown', this.onMouseDown, true)\n document.removeEventListener('click', this.onClick, true)\n document.removeEventListener('keydown', this.onKeyDown)\n\n if (this.settleTimer) { clearTimeout(this.settleTimer); this.settleTimer = null }\n if (this.highlight) { this.highlight.remove(); this.highlight = null }\n if (this.scrim) { this.scrim.remove(); this.scrim = null }\n if (this.cursor) { this.cursor.remove(); this.cursor = null }\n if (this.styleElement) { this.styleElement.remove(); this.styleElement = null }\n if (this.cursorRAF) { cancelAnimationFrame(this.cursorRAF); this.cursorRAF = null }\n this.highlightedElement = null\n this.pendingTarget = null\n }\n\n private animateCursor(): void {\n // Smooth follow with lerp — gentle so it feels unhurried\n this.cursorX += (this.targetX - this.cursorX) * 0.2\n this.cursorY += (this.targetY - this.cursorY) * 0.2\n if (this.cursor) {\n this.cursor.style.left = `${this.cursorX}px`\n this.cursor.style.top = `${this.cursorY}px`\n }\n this.cursorRAF = requestAnimationFrame(this.animateCursor)\n }\n\n private onMouseDown(e: MouseEvent): void {\n if ((e.target as Element).closest('#tack-widget')) {\n return\n }\n e.preventDefault()\n e.stopPropagation()\n }\n\n private onMouseMove(e: MouseEvent): void {\n this.targetX = e.clientX\n this.targetY = e.clientY\n\n const target = e.target as Element\n\n if (target.closest('#tack-widget')) {\n this.clearHighlight()\n if (this.cursor) this.cursor.style.opacity = '0'\n return\n }\n\n // Restore custom cursor when back on the page\n if (this.cursor && this.cursor.style.opacity === '0') {\n this.cursor.style.opacity = '1'\n }\n\n if (target !== this.pendingTarget && target !== this.highlightedElement) {\n // New element — start settle timer\n this.pendingTarget = target\n if (this.settleTimer) clearTimeout(this.settleTimer)\n\n this.settleTimer = setTimeout(() => {\n if (this.pendingTarget && this.pendingTarget !== this.highlightedElement) {\n this.showHighlight(this.pendingTarget)\n }\n this.pendingTarget = null\n }, 150)\n }\n }\n\n private showHighlight(target: Element): void {\n this.highlightedElement = target\n\n if (this.highlight) {\n const rect = target.getBoundingClientRect()\n const pad = 3\n this.highlight.style.left = `${rect.left - pad}px`\n this.highlight.style.top = `${rect.top - pad}px`\n this.highlight.style.width = `${rect.width + pad * 2}px`\n this.highlight.style.height = `${rect.height + pad * 2}px`\n this.highlight.style.opacity = '1'\n\n const computed = window.getComputedStyle(target)\n const radius = computed.borderRadius\n this.highlight.style.borderRadius = radius && radius !== '0px' ? radius : '6px'\n }\n }\n\n private onClick(e: MouseEvent): void {\n if ((e.target as Element).closest('#tack-widget')) {\n return\n }\n\n e.preventDefault()\n e.stopPropagation()\n\n const target = e.target as Element\n const rect = target.getBoundingClientRect()\n\n const relativeX = (e.clientX - rect.left) / rect.width\n const relativeY = (e.clientY - rect.top) / rect.height\n\n const anchor = this.generateAnchor(target, relativeX, relativeY)\n\n this.callback({ element: target, anchor })\n this.clearHighlight()\n }\n\n private onKeyDown(e: KeyboardEvent): void {\n if (e.key === 'Escape') {\n this.disable()\n }\n }\n\n private clearHighlight(): void {\n this.highlightedElement = null\n this.pendingTarget = null\n if (this.settleTimer) { clearTimeout(this.settleTimer); this.settleTimer = null }\n if (this.highlight) {\n this.highlight.style.opacity = '0'\n }\n }\n\n private generateAnchor(element: Element, relativeX: number, relativeY: number): CommentAnchor {\n return {\n cssSelector: this.generateCssSelector(element),\n xpath: this.generateXPath(element),\n textQuote: this.generateTextQuote(element),\n contentHash: this.generateContentHash(element),\n relativeX,\n relativeY,\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n }\n }\n\n private generateCssSelector(element: Element): string {\n // Try ID first\n if (element.id && !this.isDynamicId(element.id)) {\n return `#${CSS.escape(element.id)}`\n }\n\n // Build path from element to a unique ancestor\n const path: string[] = []\n let current: Element | null = element\n\n while (current && current !== document.body) {\n let selector = current.tagName.toLowerCase()\n\n // Add stable classes (filter out dynamic ones)\n if (current.className && typeof current.className === 'string') {\n const classes = current.className\n .trim()\n .split(/\\s+/)\n .filter(c => !this.isDynamicClass(c))\n if (classes.length > 0) {\n selector += '.' + classes.slice(0, 2).map(c => CSS.escape(c)).join('.')\n }\n }\n\n // Add data attributes for stability\n const dataTestId = current.getAttribute('data-testid') || current.getAttribute('data-test-id')\n if (dataTestId) {\n selector += `[data-testid=\"${CSS.escape(dataTestId)}\"]`\n }\n\n // Add nth-child if needed for uniqueness\n const parent = current.parentElement\n if (parent) {\n const siblings = Array.from(parent.children).filter(\n (child) => child.tagName === current!.tagName\n )\n if (siblings.length > 1) {\n const index = siblings.indexOf(current) + 1\n selector += `:nth-child(${index})`\n }\n }\n\n path.unshift(selector)\n\n // Check if current path is unique\n const fullSelector = path.join(' > ')\n try {\n if (document.querySelectorAll(fullSelector).length === 1) {\n return fullSelector\n }\n } catch {\n // Invalid selector, continue building path\n }\n\n current = parent\n }\n\n return path.join(' > ')\n }\n\n private generateXPath(element: Element): string {\n const parts: string[] = []\n let current: Element | null = element\n\n while (current && current !== document.body) {\n let part = current.tagName.toLowerCase()\n\n // Add index among same-tag siblings\n const parent = current.parentElement\n if (parent) {\n const siblings = Array.from(parent.children).filter(\n (child) => child.tagName === current!.tagName\n )\n if (siblings.length > 1) {\n const index = siblings.indexOf(current) + 1\n part += `[${index}]`\n }\n }\n\n parts.unshift(part)\n current = parent\n }\n\n return '//' + parts.join('/')\n }\n\n private generateTextQuote(element: Element): TextQuoteSelector | null {\n const text = element.textContent?.trim()\n if (!text || text.length === 0 || text.length > 500) {\n return null\n }\n\n // Get surrounding context\n const parent = element.parentElement\n let prefix = ''\n let suffix = ''\n\n if (parent) {\n const parentText = parent.textContent || ''\n const index = parentText.indexOf(text)\n\n if (index > 0) {\n prefix = parentText.slice(Math.max(0, index - 32), index).trim()\n }\n\n const endIndex = index + text.length\n if (endIndex < parentText.length) {\n suffix = parentText.slice(endIndex, endIndex + 32).trim()\n }\n }\n\n return {\n prefix,\n exact: text.slice(0, 200), // Limit exact match length\n suffix,\n }\n }\n\n private generateContentHash(element: Element): string {\n // Simple hash of text content + tag + key attributes\n const text = element.textContent?.trim().slice(0, 500) || ''\n const tag = element.tagName.toLowerCase()\n const className = element.className?.toString() || ''\n const content = `${tag}:${className}:${text}`\n\n // Simple hash function (djb2)\n let hash = 5381\n for (let i = 0; i < content.length; i++) {\n hash = ((hash << 5) + hash) + content.charCodeAt(i)\n }\n return (hash >>> 0).toString(16)\n }\n\n private isDynamicId(id: string): boolean {\n // Filter out IDs that look auto-generated\n return /^[:_]/.test(id) ||\n /[0-9]{5,}/.test(id) ||\n /^(ember|react|vue|ng-|_)[0-9]+/.test(id) ||\n /^[a-f0-9]{8,}$/i.test(id)\n }\n\n private isDynamicClass(className: string): boolean {\n // Filter out classes that look auto-generated (CSS-in-JS, etc.)\n return className.startsWith('tack-') ||\n /^css-[a-z0-9]+$/i.test(className) ||\n /^sc-[a-z]+$/i.test(className) ||\n /^emotion-[0-9]+$/i.test(className) ||\n /^_[a-zA-Z0-9]{5,}$/.test(className) ||\n /^[a-z]{1,3}[A-Z][a-zA-Z0-9]{10,}$/.test(className)\n }\n}\n","export async function captureScreenshot(element?: Element | null): Promise<string | undefined> {\n const html2canvas = (await import('html2canvas')).default\n try {\n const target = element || document.body\n\n const canvas = await html2canvas(target as HTMLElement, {\n logging: false,\n useCORS: true,\n allowTaint: true,\n scale: 1,\n width: Math.min(target.scrollWidth, 800),\n height: Math.min(target.scrollHeight, 600),\n })\n\n return canvas.toDataURL('image/png', 0.8)\n } catch (e) {\n console.warn('Tack: Screenshot capture failed', e)\n return undefined\n }\n}\n\nexport async function captureElementWithContext(\n selector: string | null,\n x: number,\n y: number\n): Promise<string | undefined> {\n const html2canvas = (await import('html2canvas')).default\n try {\n if (selector) {\n const element = document.querySelector(selector)\n if (element) {\n // Capture element with some padding\n const rect = element.getBoundingClientRect()\n const padding = 20\n\n const canvas = await html2canvas(document.body, {\n logging: false,\n useCORS: true,\n allowTaint: true,\n scale: 1,\n x: Math.max(0, rect.left + window.scrollX - padding),\n y: Math.max(0, rect.top + window.scrollY - padding),\n width: Math.min(rect.width + padding * 2, 600),\n height: Math.min(rect.height + padding * 2, 400),\n })\n\n return canvas.toDataURL('image/png', 0.8)\n }\n }\n\n // For coordinate pins or fallback, capture area around the point\n const viewportX = (x / 100) * document.documentElement.scrollWidth\n const viewportY = (y / 100) * document.documentElement.scrollHeight\n const captureWidth = 400\n const captureHeight = 300\n\n const canvas = await html2canvas(document.body, {\n logging: false,\n useCORS: true,\n allowTaint: true,\n scale: 1,\n x: Math.max(0, viewportX - captureWidth / 2),\n y: Math.max(0, viewportY - captureHeight / 2),\n width: captureWidth,\n height: captureHeight,\n })\n\n return canvas.toDataURL('image/png', 0.8)\n } catch (e) {\n console.warn('Tack: Screenshot capture failed', e)\n return undefined\n }\n}\n","import { captureElementWithContext } from '../capture'\nimport type { CommentAnchor } from '../types'\nimport type { WidgetUser } from '../auth'\nimport { renderAvatar } from './avatars'\n\ninterface FormSubmitData {\n content: string\n authorName: string\n authorEmail?: string\n anchor: CommentAnchor\n screenshot?: string\n}\n\ntype SubmitCallback = (data: FormSubmitData) => Promise<void>\ntype HideCallback = () => void\ntype SignInCallback = () => void\n\nexport class CommentForm {\n private container: HTMLElement\n private form: HTMLElement\n private onSubmit: SubmitCallback\n private onHide: HideCallback | null = null\n private onSignIn: SignInCallback | null = null\n private currentTarget: { element: Element | null; anchor: CommentAnchor } | null = null\n private isSubmitting = false\n private capturedScreenshot: string | undefined = undefined\n private highlightElement: HTMLElement | null = null\n private isVisible = false\n private currentUser: WidgetUser | null = null\n\n constructor(container: HTMLElement, onSubmit: SubmitCallback) {\n this.container = container\n this.onSubmit = onSubmit\n this.form = this.createForm()\n this.container.appendChild(this.form)\n\n // Create highlight element for showing what's being commented on\n this.highlightElement = document.createElement('div')\n this.highlightElement.className = 'tack-form-highlight'\n this.highlightElement.style.cssText = `\n position: fixed;\n pointer-events: none;\n border: 2px solid #14B8A6;\n border-radius: 4px;\n background: rgba(20, 184, 166, 0.1);\n z-index: 10002;\n display: none;\n transition: all 0.15s ease;\n `\n document.body.appendChild(this.highlightElement)\n }\n\n setOnHide(callback: HideCallback): void {\n this.onHide = callback\n }\n\n setOnSignIn(callback: SignInCallback): void {\n this.onSignIn = callback\n }\n\n setUser(user: WidgetUser | null): void {\n this.currentUser = user\n this.rebuildFormContent()\n }\n\n private createForm(): HTMLElement {\n const form = document.createElement('div')\n form.className = 'tack-form'\n this.buildFormInner(form)\n return form\n }\n\n private rebuildFormContent(): void {\n this.buildFormInner(this.form)\n }\n\n private buildFormInner(form: HTMLElement): void {\n if (this.currentUser) {\n // Authenticated: pill-shaped input with send button\n form.innerHTML = `\n <div class=\"tack-form-pill\">\n <textarea class=\"tack-form-pill-input\" id=\"tack-comment-content\" placeholder=\"Add a comment...\" rows=\"1\"></textarea>\n <button type=\"button\" class=\"tack-form-pill-send\" aria-label=\"Send\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" fill=\"none\">\n <path d=\"M7 11.5V2.5M7 2.5L3 6.5M7 2.5L11 6.5\" stroke=\"currentColor\" stroke-width=\"1.75\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </button>\n </div>\n `\n } else {\n // Not authenticated: show sign-in prompt in a pill\n form.innerHTML = `\n <div class=\"tack-form-pill tack-form-pill-signin\">\n <span class=\"tack-sign-in-prompt-text\">Sign in to leave feedback</span>\n <button type=\"button\" class=\"tack-sign-in-btn\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\">\n <path d=\"M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 0 1-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z\" fill=\"#4285F4\"/>\n <path d=\"M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z\" fill=\"#34A853\"/>\n <path d=\"M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z\" fill=\"#FBBC05\"/>\n <path d=\"M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z\" fill=\"#EA4335\"/>\n </svg>\n Sign in\n </button>\n </div>\n `\n }\n\n this.attachFormListeners(form)\n }\n\n private attachFormListeners(form: HTMLElement): void {\n // Stop clicks inside form from propagating\n form.addEventListener('mousedown', (e) => e.stopPropagation())\n form.addEventListener('click', (e) => e.stopPropagation())\n\n // Sign in button\n form.querySelector('.tack-sign-in-btn')?.addEventListener('click', (e) => {\n e.stopPropagation()\n this.onSignIn?.()\n })\n\n // Send button\n form.querySelector('.tack-form-pill-send')?.addEventListener('click', (e) => {\n e.stopPropagation()\n this.handleSubmit()\n })\n\n // Auto-expand textarea + Enter to send, Shift+Enter for newline\n const textarea = form.querySelector('#tack-comment-content') as HTMLTextAreaElement\n if (textarea) {\n textarea.addEventListener('input', () => {\n this.autoExpandTextarea(textarea)\n // Toggle send button active state\n const sendBtn = form.querySelector('.tack-form-pill-send')\n if (sendBtn) {\n sendBtn.classList.toggle('active', textarea.value.trim().length > 0)\n }\n })\n\n textarea.addEventListener('keydown', (e: Event) => {\n const ke = e as KeyboardEvent\n if (ke.key === 'Enter' && !ke.shiftKey) {\n ke.preventDefault()\n this.handleSubmit()\n }\n })\n }\n\n // Escape to close\n form.addEventListener('keydown', (e: Event) => {\n const ke = e as KeyboardEvent\n if (ke.key === 'Escape') {\n ke.preventDefault()\n this.hide()\n }\n })\n }\n\n async show(target: { element: Element | null; anchor: CommentAnchor }): Promise<void> {\n if (this.isVisible) return\n\n this.currentTarget = target\n this.isVisible = true\n\n // Capture screenshot BEFORE showing form\n this.capturedScreenshot = await captureElementWithContext(\n target.anchor.cssSelector,\n target.anchor.relativeX * 100,\n target.anchor.relativeY * 100\n )\n\n // Show highlight on the element being commented on\n if (target.element && this.highlightElement) {\n const rect = target.element.getBoundingClientRect()\n this.highlightElement.style.display = 'block'\n this.highlightElement.style.left = `${rect.left - 4}px`\n this.highlightElement.style.top = `${rect.top - 4}px`\n this.highlightElement.style.width = `${rect.width + 8}px`\n this.highlightElement.style.height = `${rect.height + 8}px`\n }\n\n // Position form near the clicked element\n let viewportX: number\n let viewportY: number\n\n if (target.element) {\n const rect = target.element.getBoundingClientRect()\n viewportX = rect.right + 12\n viewportY = rect.top\n const fw = 300\n if (viewportX + fw > window.innerWidth - 380) {\n viewportX = rect.left - fw - 12\n }\n } else {\n viewportX = target.anchor.relativeX * window.innerWidth + 20\n viewportY = target.anchor.relativeY * window.innerHeight\n }\n\n const formWidth = 300\n const formHeight = 52\n\n let left = Math.min(viewportX, window.innerWidth - formWidth - 380)\n let top = Math.min(viewportY, window.innerHeight - formHeight - 20)\n\n left = Math.max(20, left)\n top = Math.max(20, top)\n\n this.form.style.left = `${left}px`\n this.form.style.top = `${top}px`\n this.form.classList.add('visible')\n\n // Focus the comment textarea\n setTimeout(() => {\n ;(this.form.querySelector('#tack-comment-content') as HTMLTextAreaElement)?.focus()\n }, 50)\n }\n\n hide(): void {\n if (!this.isVisible) return\n\n this.isVisible = false\n this.form.classList.remove('visible')\n this.currentTarget = null\n this.capturedScreenshot = undefined\n const textarea = this.form.querySelector('#tack-comment-content') as HTMLTextAreaElement\n if (textarea) {\n textarea.value = ''\n textarea.style.height = '28px'\n textarea.style.overflowY = 'hidden'\n }\n const sendBtn = this.form.querySelector('.tack-form-pill-send')\n if (sendBtn) sendBtn.classList.remove('active')\n\n if (this.highlightElement) {\n this.highlightElement.style.display = 'none'\n }\n\n this.onHide?.()\n }\n\n isFormVisible(): boolean {\n return this.isVisible\n }\n\n private autoExpandTextarea(textarea: HTMLTextAreaElement): void {\n textarea.style.height = 'auto'\n const maxHeight = 120 // ~5 lines\n textarea.style.height = `${Math.min(textarea.scrollHeight, maxHeight)}px`\n textarea.style.overflowY = textarea.scrollHeight > maxHeight ? 'auto' : 'hidden'\n }\n\n private async handleSubmit(): Promise<void> {\n if (this.isSubmitting || !this.currentTarget) return\n\n const content = (this.form.querySelector('#tack-comment-content') as HTMLTextAreaElement)?.value.trim()\n if (!content) return\n\n // Determine author name\n let authorName: string\n if (this.currentUser) {\n authorName = this.currentUser.name\n } else {\n const nameInput = this.form.querySelector('#tack-author-name') as HTMLInputElement\n authorName = nameInput?.value.trim() || ''\n if (!authorName) return\n }\n\n this.isSubmitting = true\n const sendBtn = this.form.querySelector('.tack-form-pill-send') as HTMLButtonElement\n if (sendBtn) sendBtn.classList.add('sending')\n\n try {\n if (!this.currentUser) {\n localStorage.setItem('tack_author_name', authorName)\n }\n\n const submitData = {\n content,\n authorName,\n anchor: this.currentTarget.anchor,\n screenshot: this.capturedScreenshot,\n }\n\n this.hide()\n await this.onSubmit(submitData)\n } catch (error) {\n console.error('[Tack] Submit failed:', error)\n } finally {\n this.isSubmitting = false\n if (sendBtn) sendBtn.classList.remove('sending')\n }\n }\n\n private escapeHtml(text: string): string {\n const div = document.createElement('div')\n div.textContent = text\n return div.innerHTML\n }\n}\n","// Gradient pairs for avatar pins (from → to)\nconst AVATAR_GRADIENTS: [string, string][] = [\n ['#818CF8', '#6366F1'], // indigo\n ['#A78BFA', '#7C3AED'], // violet\n ['#F472B6', '#EC4899'], // pink\n ['#FBBF24', '#F59E0B'], // amber\n ['#34D399', '#10B981'], // emerald\n ['#60A5FA', '#3B82F6'], // blue\n ['#F87171', '#EF4444'], // red\n ['#2DD4BF', '#14B8A6'], // teal\n]\n\n// Flat colors (primary of each pair) for contexts that need a single color\nexport const AVATAR_COLORS = AVATAR_GRADIENTS.map(([, to]) => to)\n\nfunction hashName(name: string): number {\n let hash = 0\n for (let i = 0; i < name.length; i++) {\n hash = name.charCodeAt(i) + ((hash << 5) - hash)\n }\n return Math.abs(hash)\n}\n\nexport function getAvatarColor(name: string): string {\n return AVATAR_COLORS[hashName(name) % AVATAR_COLORS.length]\n}\n\nexport function getAvatarGradient(name: string): string {\n const [from, to] = AVATAR_GRADIENTS[hashName(name) % AVATAR_GRADIENTS.length]\n return `linear-gradient(135deg, ${from} 0%, ${to} 100%)`\n}\n\nexport function getInitials(name: string): string {\n const parts = name.trim().split(/\\s+/)\n if (parts.length >= 2) {\n return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase()\n }\n return name.slice(0, 2).toUpperCase()\n}\n\n/**\n * Renders an avatar as an HTML string. Uses Google profile photo when available,\n * falls back to initials with colored background.\n */\nexport function renderAvatar(\n name: string,\n avatarUrl: string | null,\n size: number,\n extraClass = ''\n): string {\n const fontSize = Math.round(size * 0.38)\n const cls = extraClass ? ` ${extraClass}` : ''\n if (avatarUrl) {\n return `<img\n src=\"${avatarUrl}\"\n alt=\"${getInitials(name)}\"\n referrerpolicy=\"no-referrer\"\n class=\"tack-avatar-img${cls}\"\n style=\"width:${size}px;height:${size}px;border-radius:50%;object-fit:cover;\"\n onerror=\"this.style.display='none';this.nextElementSibling.style.display='flex'\"\n /><div\n class=\"tack-avatar-fallback${cls}\"\n style=\"display:none;width:${size}px;height:${size}px;border-radius:50%;background:${getAvatarColor(name)};color:white;font-size:${fontSize}px;font-weight:600;align-items:center;justify-content:center;line-height:1;flex-shrink:0\"\n >${getInitials(name)}</div>`\n }\n return `<div\n class=\"tack-avatar-fallback${cls}\"\n style=\"display:flex;width:${size}px;height:${size}px;border-radius:50%;background:${getAvatarColor(name)};color:white;font-size:${fontSize}px;font-weight:600;align-items:center;justify-content:center;line-height:1;flex-shrink:0\"\n >${getInitials(name)}</div>`\n}\n","import type { Comment } from '../types'\nimport type { WidgetUser } from '../auth'\nimport { getAvatarColor, getInitials, renderAvatar } from './avatars'\n\ninterface PanelCallbacks {\n onCommentClick: (comment: Comment) => void\n onResolve: (commentId: string, resolved: boolean) => void\n onReply: (parentId: string, content: string) => void\n onClose?: () => void\n onAddComment?: () => void\n onShare?: () => void\n}\n\ntype SortMode = 'date' | 'unread' | 'replies'\n\ninterface FilterState {\n sort: SortMode\n showResolved: boolean\n onlyYourThreads: boolean\n onlyCurrentPage: boolean\n}\n\nconst STORAGE_KEY = 'tack_panel_filters'\n\nfunction loadFilters(): FilterState {\n try {\n const stored = localStorage.getItem(STORAGE_KEY)\n if (stored) {\n const parsed = JSON.parse(stored)\n return {\n sort: parsed.sort || 'date',\n showResolved: parsed.showResolved ?? false,\n onlyYourThreads: parsed.onlyYourThreads ?? false,\n onlyCurrentPage: parsed.onlyCurrentPage ?? false,\n }\n }\n } catch {}\n return { sort: 'date', showResolved: false, onlyYourThreads: false, onlyCurrentPage: false }\n}\n\nfunction saveFilters(filters: FilterState): void {\n try {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(filters))\n } catch {}\n}\n\nexport class CommentPanel {\n private container: HTMLElement\n private strip: HTMLElement\n private panel: HTMLElement\n private callbacks: PanelCallbacks\n private comments: Comment[] = []\n private filters: FilterState\n private searchQuery: string = ''\n private searchTimer: ReturnType<typeof setTimeout> | null = null\n private highlightedId: string | null = null\n private filterDropdownOpen = false\n private moreDropdownOpen = false\n private currentUser: WidgetUser | null = null\n private readCommentIds: Set<string>\n private projectId: string = ''\n\n constructor(container: HTMLElement, callbacks: PanelCallbacks) {\n this.container = container\n this.callbacks = callbacks\n this.filters = loadFilters()\n this.readCommentIds = this.loadReadIds()\n this.strip = this.createPanelStrip()\n this.panel = this.strip.querySelector('.tack-panel')!\n this.container.appendChild(this.strip)\n }\n\n setUser(user: WidgetUser | null): void {\n this.currentUser = user\n }\n\n setProjectId(projectId: string): void {\n this.projectId = projectId\n this.readCommentIds = this.loadReadIds()\n }\n\n private loadReadIds(): Set<string> {\n try {\n const key = `tack_read_${this.projectId}`\n const stored = localStorage.getItem(key)\n if (stored) return new Set(JSON.parse(stored))\n } catch {}\n return new Set()\n }\n\n private saveReadIds(): void {\n try {\n const key = `tack_read_${this.projectId}`\n localStorage.setItem(key, JSON.stringify([...this.readCommentIds]))\n } catch {}\n }\n\n markAsRead(commentId: string): void {\n if (!this.readCommentIds.has(commentId)) {\n this.readCommentIds.add(commentId)\n this.saveReadIds()\n }\n }\n\n markVisibleAsRead(): void {\n if (!this.isOpen()) return\n const filtered = this.getFilteredComments()\n let changed = false\n for (const c of filtered) {\n if (!this.readCommentIds.has(c.id)) {\n this.readCommentIds.add(c.id)\n changed = true\n }\n }\n if (changed) this.saveReadIds()\n }\n\n isRead(commentId: string): boolean {\n return this.readCommentIds.has(commentId)\n }\n\n private createPanelStrip(): HTMLElement {\n const strip = document.createElement('div')\n strip.className = 'tack-panel-strip'\n strip.innerHTML = `\n <div class=\"tack-panel\" role=\"complementary\" aria-label=\"Comments panel\">\n <div class=\"tack-panel-header\">\n <button class=\"tack-panel-close\" aria-label=\"Close comments panel\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\n </svg>\n </button>\n <div class=\"tack-panel-search-wrap\">\n <svg class=\"tack-panel-search-icon\" width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"></circle>\n <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"></line>\n </svg>\n <input type=\"text\" class=\"tack-panel-search\" placeholder=\"Search\" />\n <button class=\"tack-panel-search-clear\" style=\"display:none;\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\n </svg>\n </button>\n </div>\n <div class=\"tack-panel-filter-wrap\">\n <button class=\"tack-panel-filter-btn\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.75\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"4\" y1=\"6\" x2=\"20\" y2=\"6\"></line>\n <line x1=\"7\" y1=\"12\" x2=\"17\" y2=\"12\"></line>\n <line x1=\"10\" y1=\"18\" x2=\"14\" y2=\"18\"></line>\n </svg>\n <span class=\"tack-panel-filter-badge\" style=\"display:none;\"></span>\n </button>\n <div class=\"tack-panel-filter-dropdown\" data-state=\"closed\"></div>\n </div>\n <div class=\"tack-panel-more-wrap\">\n <button class=\"tack-panel-more-btn\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"5\" r=\"1\" fill=\"currentColor\"></circle>\n <circle cx=\"12\" cy=\"12\" r=\"1\" fill=\"currentColor\"></circle>\n <circle cx=\"12\" cy=\"19\" r=\"1\" fill=\"currentColor\"></circle>\n </svg>\n </button>\n <div class=\"tack-panel-more-dropdown\" data-state=\"closed\"></div>\n </div>\n </div>\n <div class=\"tack-panel-content\"></div>\n <div class=\"tack-new-comment\">\n <div class=\"tack-new-comment-input\" role=\"button\" aria-label=\"Add new comment\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M15 15l-2 5L9 9l11 4-5 2z\"></path>\n <path d=\"M22 2l-7 7\"></path>\n </svg>\n Click anywhere to add comment\n </div>\n </div>\n </div>\n `\n\n // Close button\n strip.querySelector('.tack-panel-close')!.addEventListener('click', () => {\n this.hide()\n this.callbacks.onClose?.()\n })\n\n // Search input\n const searchInput = strip.querySelector('.tack-panel-search') as HTMLInputElement\n const searchClear = strip.querySelector('.tack-panel-search-clear') as HTMLElement\n searchInput.addEventListener('input', () => {\n const val = searchInput.value\n searchClear.style.display = val ? 'flex' : 'none'\n if (this.searchTimer) clearTimeout(this.searchTimer)\n this.searchTimer = setTimeout(() => {\n this.searchQuery = val.trim().toLowerCase()\n this.renderComments()\n }, 200)\n })\n searchClear.addEventListener('click', (e) => {\n e.stopPropagation()\n searchInput.value = ''\n searchClear.style.display = 'none'\n this.searchQuery = ''\n this.renderComments()\n searchInput.focus()\n })\n\n // Filter button\n strip.querySelector('.tack-panel-filter-btn')!.addEventListener('click', (e) => {\n e.stopPropagation()\n this.moreDropdownOpen = false\n this.renderMoreDropdown()\n this.filterDropdownOpen = !this.filterDropdownOpen\n this.renderFilterDropdown()\n })\n\n // More button\n strip.querySelector('.tack-panel-more-btn')!.addEventListener('click', (e) => {\n e.stopPropagation()\n this.filterDropdownOpen = false\n this.renderFilterDropdown()\n this.moreDropdownOpen = !this.moreDropdownOpen\n this.renderMoreDropdown()\n })\n\n // New comment input\n strip.querySelector('.tack-new-comment-input')!.addEventListener('click', () => {\n this.callbacks.onAddComment?.()\n })\n\n // Close dropdowns on click outside\n strip.addEventListener('click', (e) => {\n const target = e.target as HTMLElement\n if (!target.closest('.tack-panel-filter-wrap') && this.filterDropdownOpen) {\n this.filterDropdownOpen = false\n this.renderFilterDropdown()\n }\n if (!target.closest('.tack-panel-more-wrap') && this.moreDropdownOpen) {\n this.moreDropdownOpen = false\n this.renderMoreDropdown()\n }\n })\n\n return strip\n }\n\n private renderFilterDropdown(): void {\n const dropdown = this.strip.querySelector('.tack-panel-filter-dropdown') as HTMLElement\n if (!this.filterDropdownOpen) {\n dropdown.setAttribute('data-state', 'closed')\n dropdown.innerHTML = ''\n return\n }\n\n const f = this.filters\n const check = `<svg class=\"tack-dropdown-check\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg>`\n const empty = '<span class=\"tack-dropdown-check-space\"></span>'\n\n dropdown.innerHTML = `\n <button class=\"tack-dropdown-item\" data-action=\"sort-date\">\n ${f.sort === 'date' ? check : empty}\n <span>Sort by date</span>\n </button>\n <button class=\"tack-dropdown-item\" data-action=\"sort-unread\">\n ${f.sort === 'unread' ? check : empty}\n <span>Sort by unread</span>\n </button>\n <button class=\"tack-dropdown-item\" data-action=\"sort-replies\">\n ${f.sort === 'replies' ? check : empty}\n <span>Sort by most replies</span>\n </button>\n <div class=\"tack-dropdown-sep\"></div>\n <button class=\"tack-dropdown-item tack-dropdown-toggle\" data-action=\"showResolved\">\n <span>Show resolved comments</span>\n <span class=\"tack-dropdown-toggle-switch ${f.showResolved ? 'on' : ''}\"><span class=\"tack-dropdown-toggle-knob\"></span></span>\n </button>\n <button class=\"tack-dropdown-item tack-dropdown-toggle\" data-action=\"onlyYourThreads\">\n <span>Only your threads</span>\n <span class=\"tack-dropdown-toggle-switch ${f.onlyYourThreads ? 'on' : ''}\"><span class=\"tack-dropdown-toggle-knob\"></span></span>\n </button>\n <button class=\"tack-dropdown-item tack-dropdown-toggle\" data-action=\"onlyCurrentPage\">\n <span>Only current page</span>\n <span class=\"tack-dropdown-toggle-switch ${f.onlyCurrentPage ? 'on' : ''}\"><span class=\"tack-dropdown-toggle-knob\"></span></span>\n </button>\n `\n dropdown.setAttribute('data-state', 'open')\n\n dropdown.querySelectorAll('.tack-dropdown-item').forEach((item) => {\n item.addEventListener('click', (e) => {\n e.stopPropagation()\n const action = (item as HTMLElement).dataset.action!\n if (action.startsWith('sort-')) {\n this.filters.sort = action.replace('sort-', '') as SortMode\n } else if (action === 'showResolved') {\n this.filters.showResolved = !this.filters.showResolved\n } else if (action === 'onlyYourThreads') {\n this.filters.onlyYourThreads = !this.filters.onlyYourThreads\n } else if (action === 'onlyCurrentPage') {\n this.filters.onlyCurrentPage = !this.filters.onlyCurrentPage\n }\n saveFilters(this.filters)\n this.updateFilterBadge()\n this.renderFilterDropdown()\n this.renderComments()\n })\n })\n }\n\n private renderMoreDropdown(): void {\n const dropdown = this.strip.querySelector('.tack-panel-more-dropdown') as HTMLElement\n if (!this.moreDropdownOpen) {\n dropdown.setAttribute('data-state', 'closed')\n dropdown.innerHTML = ''\n return\n }\n\n dropdown.innerHTML = `\n <button class=\"tack-dropdown-item\" data-action=\"share\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8\"></path>\n <polyline points=\"16 6 12 2 8 6\"></polyline>\n <line x1=\"12\" y1=\"2\" x2=\"12\" y2=\"15\"></line>\n </svg>\n <span>Copy share link</span>\n </button>\n `\n dropdown.setAttribute('data-state', 'open')\n\n dropdown.querySelector('[data-action=\"share\"]')!.addEventListener('click', (e) => {\n e.stopPropagation()\n this.moreDropdownOpen = false\n this.renderMoreDropdown()\n this.callbacks.onShare?.()\n })\n }\n\n private updateFilterBadge(): void {\n const badge = this.strip.querySelector('.tack-panel-filter-badge') as HTMLElement\n const btn = this.strip.querySelector('.tack-panel-filter-btn') as HTMLElement\n let count = 0\n if (this.filters.showResolved) count++\n if (this.filters.onlyYourThreads) count++\n if (this.filters.onlyCurrentPage) count++\n\n if (count > 0) {\n badge.textContent = String(count)\n badge.style.display = 'flex'\n btn.classList.add('filter-active')\n } else {\n badge.style.display = 'none'\n btn.classList.remove('filter-active')\n }\n }\n\n show(): void {\n this.strip.classList.add('open')\n this.updateFilterBadge()\n // Mark visible comments as read after a delay\n setTimeout(() => this.markVisibleAsRead(), 1500)\n }\n\n hide(): void {\n this.strip.classList.remove('open')\n this.filterDropdownOpen = false\n this.moreDropdownOpen = false\n this.renderFilterDropdown()\n this.renderMoreDropdown()\n }\n\n toggle(): void {\n if (this.strip.classList.contains('open')) {\n this.hide()\n } else {\n this.show()\n }\n }\n\n isOpen(): boolean {\n return this.strip.classList.contains('open')\n }\n\n render(comments: Comment[]): void {\n this.comments = comments\n this.renderComments()\n // Mark visible as read when panel is open\n if (this.isOpen()) {\n setTimeout(() => this.markVisibleAsRead(), 1500)\n }\n }\n\n renderPresence(users: { id: string; name: string; avatar_url: string | null }[]): void {\n let presenceBar = this.panel.querySelector('.tack-presence-bar') as HTMLElement\n if (!presenceBar) {\n presenceBar = document.createElement('div')\n presenceBar.className = 'tack-presence-bar'\n const header = this.panel.querySelector('.tack-panel-header')\n if (header) {\n header.parentNode?.insertBefore(presenceBar, header.nextSibling)\n }\n }\n\n if (users.length === 0) {\n presenceBar.style.display = 'none'\n return\n }\n\n presenceBar.style.display = 'flex'\n const maxShow = 5\n const visible = users.slice(0, maxShow)\n const overflow = users.length - maxShow\n\n let html = '<div class=\"tack-presence-avatars\">'\n visible.forEach((u) => {\n html += `<div class=\"tack-presence-avatar\" title=\"${this.escapeHtml(u.name)}\">${renderAvatar(u.name, u.avatar_url, 24)}</div>`\n })\n if (overflow > 0) {\n html += `<div class=\"tack-presence-overflow\">+${overflow}</div>`\n }\n html += '</div>'\n html += `<span class=\"tack-presence-label\">${users.length} viewing</span>`\n\n presenceBar.innerHTML = html\n }\n\n private renderComments(): void {\n const content = this.panel.querySelector('.tack-panel-content')!\n const filtered = this.getFilteredComments()\n\n if (filtered.length === 0) {\n if (this.searchQuery) {\n const activeFilterCount = (this.filters.showResolved ? 1 : 0) + (this.filters.onlyYourThreads ? 1 : 0) + (this.filters.onlyCurrentPage ? 1 : 0)\n content.innerHTML = `\n <div class=\"tack-empty\">\n <div class=\"tack-empty-icon\">\n <svg width=\"40\" height=\"40\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"></circle>\n <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"></line>\n </svg>\n </div>\n <p class=\"tack-empty-title\">No comments matching \"${this.escapeHtml(this.searchQuery)}\"</p>\n <p class=\"tack-empty-subtitle\">${activeFilterCount > 0 ? 'Try adjusting your search or filters' : 'Try a different search term'}</p>\n ${activeFilterCount > 0 ? '<button class=\"tack-empty-reset\">Reset filters</button>' : ''}\n </div>\n `\n } else if (this.hasActiveFilters()) {\n content.innerHTML = `\n <div class=\"tack-empty\">\n <div class=\"tack-empty-icon\">\n <svg width=\"40\" height=\"40\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <line x1=\"4\" y1=\"6\" x2=\"20\" y2=\"6\"></line>\n <line x1=\"7\" y1=\"12\" x2=\"17\" y2=\"12\"></line>\n <line x1=\"10\" y1=\"18\" x2=\"14\" y2=\"18\"></line>\n </svg>\n </div>\n <p class=\"tack-empty-title\">No comments to show</p>\n <p class=\"tack-empty-subtitle\">${this.describeActiveFilters()}</p>\n <button class=\"tack-empty-reset\">Reset filters</button>\n </div>\n `\n } else {\n content.innerHTML = `\n <div class=\"tack-empty\">\n <div class=\"tack-empty-icon\">\n <svg width=\"40\" height=\"40\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\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\"></path>\n </svg>\n </div>\n <p class=\"tack-empty-title\">No comments yet</p>\n <p class=\"tack-empty-subtitle\">Click anywhere on the page to add feedback</p>\n </div>\n `\n }\n\n content.querySelector('.tack-empty-reset')?.addEventListener('click', () => {\n this.resetFilters()\n })\n return\n }\n\n content.innerHTML = filtered\n .map((comment) => this.renderCommentRow(comment))\n .join('')\n\n content.querySelectorAll('.tack-comment-row').forEach((row) => {\n const id = (row as HTMLElement).dataset.id!\n row.addEventListener('click', () => {\n this.markAsRead(id)\n // Remove unread dot immediately\n const dot = row.querySelector('.tack-unread-dot')\n if (dot) dot.remove()\n const comment = this.comments.find((c) => c.id === id)\n if (comment) this.callbacks.onCommentClick(comment)\n })\n\n // Resolve button\n const resolveBtn = row.querySelector('.tack-comment-row-resolve')\n if (resolveBtn) {\n resolveBtn.addEventListener('click', (e) => {\n e.stopPropagation()\n const comment = this.comments.find((c) => c.id === id)\n if (comment) {\n this.callbacks.onResolve(id, !comment.resolved)\n }\n })\n }\n\n // More button\n const moreBtn = row.querySelector('.tack-comment-row-more')\n if (moreBtn) {\n moreBtn.addEventListener('click', (e) => {\n e.stopPropagation()\n // For now, open the comment (same as row click) to access card actions\n const comment = this.comments.find((c) => c.id === id)\n if (comment) this.callbacks.onCommentClick(comment)\n })\n }\n })\n }\n\n private hasActiveFilters(): boolean {\n return this.filters.showResolved || this.filters.onlyYourThreads || this.filters.onlyCurrentPage\n }\n\n private describeActiveFilters(): string {\n const parts: string[] = []\n if (this.filters.onlyYourThreads) parts.push('only your threads')\n if (this.filters.onlyCurrentPage) parts.push('only current page')\n if (!this.filters.showResolved) parts.push('resolved comments hidden')\n return parts.length > 0 ? parts.join(', ').replace(/^./, s => s.toUpperCase()) : ''\n }\n\n private resetFilters(): void {\n this.filters = { sort: 'date', showResolved: false, onlyYourThreads: false, onlyCurrentPage: false }\n this.searchQuery = ''\n const searchInput = this.strip.querySelector('.tack-panel-search') as HTMLInputElement\n if (searchInput) searchInput.value = ''\n const searchClear = this.strip.querySelector('.tack-panel-search-clear') as HTMLElement\n if (searchClear) searchClear.style.display = 'none'\n saveFilters(this.filters)\n this.updateFilterBadge()\n this.renderComments()\n }\n\n private getCurrentPagePath(): string {\n return window.location.pathname + window.location.search\n }\n\n private formatPagePath(pagePath: string): string {\n if (!pagePath || pagePath === '/') return 'Home'\n const path = pagePath.split('?')[0].slice(1)\n return path.charAt(0).toUpperCase() + path.slice(1)\n }\n\n private renderCommentRow(comment: Comment): string {\n const isHighlighted = comment.id === this.highlightedId\n const timeAgo = this.formatTimeAgo(new Date(comment.created_at))\n const currentPath = this.getCurrentPagePath()\n const isCurrentPage = comment.page_path === currentPath\n const pageName = this.formatPagePath(comment.page_path)\n const hasScreenshot = !!comment.screenshot_url\n const unread = !this.isRead(comment.id)\n\n return `\n <div class=\"tack-comment-row ${isHighlighted ? 'highlighted' : ''}\" data-id=\"${comment.id}\">\n <div class=\"tack-comment-row-actions\">\n <button class=\"tack-comment-row-more\" data-action=\"more\" data-id=\"${comment.id}\" title=\"More actions\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\"><circle cx=\"3\" cy=\"8\" r=\"1.25\" fill=\"currentColor\"/><circle cx=\"8\" cy=\"8\" r=\"1.25\" fill=\"currentColor\"/><circle cx=\"13\" cy=\"8\" r=\"1.25\" fill=\"currentColor\"/></svg>\n </button>\n <button class=\"tack-comment-row-resolve ${comment.resolved ? 'resolved' : ''}\" data-action=\"resolve\" data-id=\"${comment.id}\" title=\"${comment.resolved ? 'Unresolve' : 'Resolve'}\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\"><circle cx=\"8\" cy=\"8\" r=\"6.5\" stroke=\"currentColor\" stroke-width=\"1.5\"/>${comment.resolved ? '<path d=\"M5.5 8L7.2 9.7L10.5 6.3\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>' : ''}</svg>\n </button>\n </div>\n <div class=\"tack-comment-row-top\">\n ${renderAvatar(comment.author_name, comment.author_avatar_url, 32, 'tack-comment-avatar')}\n ${unread ? '<span class=\"tack-unread-dot\"></span>' : ''}\n </div>\n <div class=\"tack-comment-meta\">\n <span class=\"tack-comment-author\">${this.escapeHtml(comment.author_name)}</span>\n <span class=\"tack-comment-time\">${timeAgo}</span>\n ${comment.resolved ? '<span class=\"tack-comment-resolved-badge\">Resolved</span>' : ''}\n ${!isCurrentPage ? `<span class=\"tack-comment-page-badge\">${this.escapeHtml(pageName)}</span>` : ''}\n </div>\n <div class=\"tack-comment-body ${comment.resolved ? 'dimmed' : ''}\">\n ${hasScreenshot ? `<img src=\"${comment.screenshot_url}\" class=\"tack-comment-screenshot\" alt=\"Screenshot\" />` : ''}\n <div class=\"tack-comment-content\">${this.escapeHtml(comment.content)}</div>\n </div>\n </div>\n `\n }\n\n private getFilteredComments(): Comment[] {\n let result = this.comments.filter((c) => !c.parent_id)\n\n // Filter: resolved\n if (!this.filters.showResolved) {\n result = result.filter((c) => !c.resolved)\n }\n\n // Filter: only your threads\n if (this.filters.onlyYourThreads && this.currentUser) {\n const userId = this.currentUser.id\n const userName = this.currentUser.name\n result = result.filter((c) => {\n if (c.user_id === userId) return true\n if (c.author_name === userName) return true\n if (c.replies?.some((r) => r.user_id === userId || r.author_name === userName)) return true\n return false\n })\n }\n\n // Filter: only current page\n if (this.filters.onlyCurrentPage) {\n const currentPath = this.getCurrentPagePath()\n result = result.filter((c) => c.page_path === currentPath)\n }\n\n // Search\n if (this.searchQuery) {\n const q = this.searchQuery\n result = result.filter((c) => {\n if (c.author_name.toLowerCase().includes(q)) return true\n if (c.content.toLowerCase().includes(q)) return true\n if (c.replies?.some((r) => r.content.toLowerCase().includes(q) || r.author_name.toLowerCase().includes(q))) return true\n return false\n })\n }\n\n // Sort\n switch (this.filters.sort) {\n case 'unread':\n result.sort((a, b) => {\n const aUnread = !this.isRead(a.id) ? 0 : 1\n const bUnread = !this.isRead(b.id) ? 0 : 1\n if (aUnread !== bUnread) return aUnread - bUnread\n return new Date(b.created_at).getTime() - new Date(a.created_at).getTime()\n })\n break\n case 'replies':\n result.sort((a, b) => {\n const aReplies = a.replies?.length || 0\n const bReplies = b.replies?.length || 0\n if (bReplies !== aReplies) return bReplies - aReplies\n return new Date(b.created_at).getTime() - new Date(a.created_at).getTime()\n })\n break\n case 'date':\n default:\n result.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())\n break\n }\n\n return result\n }\n\n scrollToComment(commentId: string): void {\n this.highlightedId = commentId\n this.renderComments()\n\n const card = this.panel.querySelector(`[data-id=\"${commentId}\"]`)\n card?.scrollIntoView({ behavior: 'smooth', block: 'nearest' })\n\n setTimeout(() => {\n this.highlightedId = null\n this.renderComments()\n }, 2000)\n }\n\n private formatTimeAgo(date: Date): string {\n const seconds = Math.floor((Date.now() - date.getTime()) / 1000)\n\n if (seconds < 60) return 'just now'\n if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`\n if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`\n if (seconds < 604800) return `${Math.floor(seconds / 86400)}d ago`\n return date.toLocaleDateString()\n }\n\n private escapeHtml(text: string): string {\n const div = document.createElement('div')\n div.textContent = text\n return div.innerHTML\n }\n}\n","import type { CommentAnchor, TextQuoteSelector } from './types'\n\nexport interface AnchorResult {\n element: Element | null\n confidence: 'high' | 'medium' | 'low' | 'none'\n method: 'css' | 'xpath' | 'textQuote' | 'fuzzy' | 'none'\n}\n\nexport class ElementAnchoring {\n /**\n * Resolve an anchor to a DOM element using multiple fallback strategies\n */\n resolve(anchor: CommentAnchor | null): AnchorResult {\n if (!anchor) {\n return { element: null, confidence: 'none', method: 'none' }\n }\n\n // Strategy 1: Try CSS selector (fastest)\n if (anchor.cssSelector) {\n const result = this.tryCssSelector(anchor)\n if (result.element) {\n return result\n }\n }\n\n // Strategy 2: Try XPath\n if (anchor.xpath) {\n const result = this.tryXPath(anchor)\n if (result.element) {\n return result\n }\n }\n\n // Strategy 3: Try text quote exact match\n if (anchor.textQuote) {\n const result = this.tryTextQuote(anchor.textQuote)\n if (result.element) {\n return result\n }\n }\n\n // Strategy 4: Try fuzzy text search\n if (anchor.textQuote) {\n const result = this.tryFuzzySearch(anchor.textQuote)\n if (result.element) {\n return result\n }\n }\n\n return { element: null, confidence: 'none', method: 'none' }\n }\n\n /**\n * Calculate the position for a pin given an anchor and its target element\n */\n calculatePinPosition(element: Element, anchor: CommentAnchor): { x: number; y: number } {\n const rect = element.getBoundingClientRect()\n\n // Position at the relative point within the element\n const x = rect.left + (anchor.relativeX * rect.width) + window.scrollX\n const y = rect.top + (anchor.relativeY * rect.height) + window.scrollY\n\n return { x, y }\n }\n\n private tryCssSelector(anchor: CommentAnchor): AnchorResult {\n try {\n const element = document.querySelector(anchor.cssSelector!)\n if (!element) {\n return { element: null, confidence: 'none', method: 'css' }\n }\n\n // Validate with content hash if available\n if (anchor.contentHash) {\n const currentHash = this.generateContentHash(element)\n if (currentHash === anchor.contentHash) {\n return { element, confidence: 'high', method: 'css' }\n }\n // Hash mismatch - element found but content changed\n return { element, confidence: 'medium', method: 'css' }\n }\n\n return { element, confidence: 'high', method: 'css' }\n } catch {\n return { element: null, confidence: 'none', method: 'css' }\n }\n }\n\n private tryXPath(anchor: CommentAnchor): AnchorResult {\n try {\n const result = document.evaluate(\n anchor.xpath!,\n document.body,\n null,\n XPathResult.FIRST_ORDERED_NODE_TYPE,\n null\n )\n const element = result.singleNodeValue as Element | null\n\n if (!element) {\n return { element: null, confidence: 'none', method: 'xpath' }\n }\n\n // Validate with content hash\n if (anchor.contentHash) {\n const currentHash = this.generateContentHash(element)\n if (currentHash === anchor.contentHash) {\n return { element, confidence: 'high', method: 'xpath' }\n }\n return { element, confidence: 'medium', method: 'xpath' }\n }\n\n return { element, confidence: 'medium', method: 'xpath' }\n } catch {\n return { element: null, confidence: 'none', method: 'xpath' }\n }\n }\n\n private tryTextQuote(textQuote: TextQuoteSelector): AnchorResult {\n // Walk the DOM looking for exact text match\n const walker = document.createTreeWalker(\n document.body,\n NodeFilter.SHOW_ELEMENT,\n null\n )\n\n let node: Node | null = walker.nextNode()\n while (node) {\n const element = node as Element\n const text = element.textContent?.trim()\n\n if (text && text.includes(textQuote.exact)) {\n // Verify with prefix/suffix if available\n if (this.verifyContext(element, textQuote)) {\n return { element, confidence: 'high', method: 'textQuote' }\n }\n }\n\n node = walker.nextNode()\n }\n\n return { element: null, confidence: 'none', method: 'textQuote' }\n }\n\n private tryFuzzySearch(textQuote: TextQuoteSelector): AnchorResult {\n // Fuzzy search using Levenshtein distance\n const walker = document.createTreeWalker(\n document.body,\n NodeFilter.SHOW_ELEMENT,\n null\n )\n\n let bestMatch: { element: Element; score: number } | null = null\n const targetText = textQuote.exact.toLowerCase()\n\n let node: Node | null = walker.nextNode()\n while (node) {\n const element = node as Element\n const text = element.textContent?.trim().toLowerCase()\n\n if (text && text.length < 1000) {\n const score = this.similarityScore(text, targetText)\n if (score > 0.7 && (!bestMatch || score > bestMatch.score)) {\n bestMatch = { element, score }\n }\n }\n\n node = walker.nextNode()\n }\n\n if (bestMatch) {\n return {\n element: bestMatch.element,\n confidence: bestMatch.score > 0.9 ? 'medium' : 'low',\n method: 'fuzzy'\n }\n }\n\n return { element: null, confidence: 'none', method: 'fuzzy' }\n }\n\n private verifyContext(element: Element, textQuote: TextQuoteSelector): boolean {\n if (!textQuote.prefix && !textQuote.suffix) {\n return true // No context to verify\n }\n\n const parent = element.parentElement\n if (!parent) return true\n\n const parentText = parent.textContent || ''\n const elementText = element.textContent || ''\n const index = parentText.indexOf(elementText)\n\n if (index === -1) return true\n\n // Check prefix\n if (textQuote.prefix) {\n const actualPrefix = parentText.slice(Math.max(0, index - 50), index).trim()\n if (!actualPrefix.includes(textQuote.prefix.slice(-20))) {\n return false\n }\n }\n\n // Check suffix\n if (textQuote.suffix) {\n const endIndex = index + elementText.length\n const actualSuffix = parentText.slice(endIndex, endIndex + 50).trim()\n if (!actualSuffix.includes(textQuote.suffix.slice(0, 20))) {\n return false\n }\n }\n\n return true\n }\n\n private similarityScore(a: string, b: string): number {\n // Simple Dice coefficient for similarity\n if (a === b) return 1\n if (a.length < 2 || b.length < 2) return 0\n\n const bigramsA = new Set<string>()\n const bigramsB = new Set<string>()\n\n for (let i = 0; i < a.length - 1; i++) {\n bigramsA.add(a.slice(i, i + 2))\n }\n for (let i = 0; i < b.length - 1; i++) {\n bigramsB.add(b.slice(i, i + 2))\n }\n\n let intersection = 0\n bigramsA.forEach(bigram => {\n if (bigramsB.has(bigram)) intersection++\n })\n\n return (2 * intersection) / (bigramsA.size + bigramsB.size)\n }\n\n private generateContentHash(element: Element): string {\n const text = element.textContent?.trim().slice(0, 500) || ''\n const tag = element.tagName.toLowerCase()\n const className = element.className?.toString() || ''\n const content = `${tag}:${className}:${text}`\n\n let hash = 5381\n for (let i = 0; i < content.length; i++) {\n hash = ((hash << 5) + hash) + content.charCodeAt(i)\n }\n return (hash >>> 0).toString(16)\n }\n}\n","import type { Comment } from '../types'\nimport { ElementAnchoring, AnchorResult } from '../anchoring'\nimport { getAvatarColor, getAvatarGradient, getInitials, renderAvatar } from './avatars'\n\ninterface UniqueAuthor {\n name: string\n avatar_url: string | null\n}\n\ntype PinClickCallback = (commentId: string, pinElement: HTMLElement) => void\n\ninterface PinState {\n comment: Comment\n element: HTMLElement\n targetElement: Element | null\n anchorResult: AnchorResult\n}\n\nexport class CommentPins {\n private container: HTMLElement\n private pinsContainer: HTMLElement\n private onClick: PinClickCallback\n private highlightedId: string | null = null\n private activeId: string | null = null\n private anchoring: ElementAnchoring\n private pins: Map<string, PinState> = new Map()\n private resizeObserver: ResizeObserver | null = null\n private repositionRAF: number | null = null\n private hoverTimers: Map<string, number> = new Map()\n private leaveTimers: Map<string, number> = new Map()\n private currentlyExpanded: string | null = null\n private boundScheduleReposition: () => void\n\n constructor(container: HTMLElement, onClick: PinClickCallback) {\n this.container = container\n this.onClick = onClick\n this.anchoring = new ElementAnchoring()\n this.boundScheduleReposition = this.scheduleReposition.bind(this)\n\n this.pinsContainer = document.createElement('div')\n this.pinsContainer.className = 'tack-pins-container'\n this.container.appendChild(this.pinsContainer)\n\n // Set up resize observer for real-time repositioning\n this.setupResizeObserver()\n\n // Also listen to scroll and resize events\n window.addEventListener('scroll', this.boundScheduleReposition, { passive: true })\n window.addEventListener('resize', this.boundScheduleReposition, { passive: true })\n }\n\n private setupResizeObserver(): void {\n this.resizeObserver = new ResizeObserver(() => {\n this.scheduleReposition()\n })\n\n // Observe the document body for any layout changes\n this.resizeObserver.observe(document.body)\n }\n\n private scheduleReposition(): void {\n // Use requestAnimationFrame to batch repositioning\n if (this.repositionRAF) {\n cancelAnimationFrame(this.repositionRAF)\n }\n this.repositionRAF = requestAnimationFrame(() => {\n this.repositionAllPins()\n })\n }\n\n private repositionAllPins(): void {\n this.pins.forEach((pinState, commentId) => {\n // Re-resolve anchor if element is gone\n if (pinState.targetElement && !document.contains(pinState.targetElement)) {\n pinState.anchorResult = this.anchoring.resolve(pinState.comment.anchor)\n pinState.targetElement = pinState.anchorResult.element\n }\n\n this.positionPin(pinState)\n })\n }\n\n private getCurrentPagePath(): string {\n return window.location.pathname + window.location.search\n }\n\n render(comments: Comment[]): void {\n // Clear hover timers\n this.hoverTimers.forEach((t) => clearTimeout(t))\n this.hoverTimers.clear()\n this.leaveTimers.forEach((t) => clearTimeout(t))\n this.leaveTimers.clear()\n this.currentlyExpanded = null\n\n // Clear existing pins\n this.pinsContainer.innerHTML = ''\n this.pins.clear()\n\n const currentPath = this.getCurrentPagePath()\n\n // Only render top-level comments for the current page\n const topLevel = comments.filter((c) => !c.parent_id && c.page_path === currentPath)\n\n topLevel.forEach((comment) => {\n const pinState = this.createPin(comment)\n if (pinState) {\n this.pins.set(comment.id, pinState)\n this.pinsContainer.appendChild(pinState.element)\n }\n })\n }\n\n private getUniqueAuthors(comment: Comment): UniqueAuthor[] {\n const seen = new Set<string>()\n const authors: UniqueAuthor[] = []\n\n // Original author first\n const key = comment.user_id || comment.author_name\n seen.add(key)\n authors.push({ name: comment.author_name, avatar_url: comment.author_avatar_url })\n\n // Add unique reply authors in order of appearance\n if (comment.replies) {\n for (const reply of comment.replies) {\n const rKey = reply.user_id || reply.author_name\n if (!seen.has(rKey)) {\n seen.add(rKey)\n authors.push({ name: reply.author_name, avatar_url: reply.author_avatar_url })\n }\n }\n }\n\n return authors\n }\n\n private renderInsetAvatar(author: UniqueAuthor, size: number): string {\n const initials = getInitials(author.name)\n const gradient = getAvatarGradient(author.name)\n const fs = Math.round(size * 0.38)\n\n if (author.avatar_url) {\n return `<img src=\"${author.avatar_url}\" alt=\"${initials}\" class=\"tack-pin-avatar-img\" referrerpolicy=\"no-referrer\" crossorigin=\"anonymous\" style=\"width:${size}px;height:${size}px;\" onerror=\"this.outerHTML='<div class=\\\\'tack-pin-avatar-fallback\\\\' style=\\\\'width:${size}px;height:${size}px;background:${gradient};font-size:${fs}px;\\\\'>${initials}</div>'\" />`\n }\n return `<div class=\"tack-pin-avatar-fallback\" style=\"width:${size}px;height:${size}px;background:${gradient};font-size:${fs}px;\">${initials}</div>`\n }\n\n private renderStackedAvatar(author: UniqueAuthor, size: number): string {\n const initials = getInitials(author.name)\n const gradient = getAvatarGradient(author.name)\n const fs = Math.round(size * 0.38)\n\n if (author.avatar_url) {\n return `<img src=\"${author.avatar_url}\" alt=\"${initials}\" class=\"tack-pin-avatar-img\" referrerpolicy=\"no-referrer\" crossorigin=\"anonymous\" style=\"width:${size}px;height:${size}px;\" onerror=\"this.outerHTML='<div class=\\\\'tack-pin-avatar-fallback\\\\' style=\\\\'width:${size}px;height:${size}px;background:${gradient};font-size:${fs}px;\\\\'>${initials}</div>'\" />`\n }\n return `<div class=\"tack-pin-avatar-fallback\" style=\"width:${size}px;height:${size}px;background:${gradient};font-size:${fs}px;\">${initials}</div>`\n }\n\n private createPin(comment: Comment): PinState | null {\n let anchorResult: AnchorResult = { element: null, confidence: 'none', method: 'none' }\n let targetElement: Element | null = null\n\n if (comment.anchor) {\n anchorResult = this.anchoring.resolve(comment.anchor)\n targetElement = anchorResult.element\n } else if (comment.element_selector) {\n targetElement = document.querySelector(comment.element_selector)\n if (targetElement) {\n anchorResult = { element: targetElement, confidence: 'medium', method: 'css' }\n }\n }\n\n const pin = document.createElement('div')\n const isActive = comment.id === this.activeId\n const isHighlighted = comment.id === this.highlightedId\n const uniqueAuthors = this.getUniqueAuthors(comment)\n const isMultiAuthor = uniqueAuthors.length > 1\n\n pin.className = `tack-pin${comment.resolved ? ' resolved' : ''}${isActive ? ' active' : ''}${isHighlighted ? ' highlighted' : ''}${isMultiAuthor ? ' multi-author' : ''}`\n pin.dataset.id = comment.id\n\n if (anchorResult.confidence === 'low') {\n pin.classList.add('low-confidence')\n }\n\n // Build the hover preview card content\n const previewAuthor = uniqueAuthors[0]\n const timeAgo = this.formatTimeAgo(new Date(comment.created_at))\n const previewText = this.escapeHtml(comment.content)\n const replyCount = comment.replies?.length || 0\n\n const previewHTML = `<div class=\"tack-pin-preview\">\n <div class=\"tack-pin-preview-header\">\n <div class=\"tack-pin-preview-avatar\">${this.renderInsetAvatar(previewAuthor, 28)}</div>\n <div class=\"tack-pin-preview-meta\">\n <span class=\"tack-pin-preview-name\">${this.escapeHtml(previewAuthor.name)}</span>\n <span class=\"tack-pin-preview-time\">${timeAgo}</span>\n </div>\n </div>\n <p class=\"tack-pin-preview-text\">${previewText}</p>\n ${replyCount > 0 ? `<span class=\"tack-pin-preview-replies\">${replyCount} ${replyCount === 1 ? 'reply' : 'replies'}</span>` : ''}\n </div>`\n\n let pinHTML: string\n\n if (isMultiAuthor) {\n // Multi-author: white pill shell with stacked avatars inside\n const maxVisible = 3\n const visibleAuthors = uniqueAuthors.slice(0, maxVisible)\n const overflow = uniqueAuthors.length - maxVisible\n\n let avatarsHTML = '<div class=\"tack-pin-shell\"><div class=\"tack-pin-avatars\">'\n visibleAuthors.forEach((author, i) => {\n avatarsHTML += `<div class=\"tack-pin-avatar-stacked\" style=\"z-index:${i + 1}\">${this.renderStackedAvatar(author, 24)}</div>`\n })\n if (overflow > 0) {\n avatarsHTML += `<div class=\"tack-pin-avatar-stacked tack-pin-avatar-overflow\" style=\"z-index:${maxVisible + 1}\">+${overflow}</div>`\n }\n avatarsHTML += `</div>${previewHTML}</div>`\n pinHTML = avatarsHTML\n } else {\n // Single author: white circle shell with inset avatar\n const author = uniqueAuthors[0]\n pinHTML = `<div class=\"tack-pin-shell\">${this.renderInsetAvatar(author, 32)}${previewHTML}</div>`\n }\n\n // Resolved check dot\n if (comment.resolved) {\n pinHTML += `<div class=\"tack-pin-check\"></div>`\n }\n\n pin.innerHTML = pinHTML\n\n pin.addEventListener('click', (e) => {\n e.stopPropagation()\n this.collapsePin(comment.id, pin)\n this.onClick(comment.id, pin)\n })\n\n this.setupHoverListeners(pin, comment)\n\n const pinState: PinState = {\n comment,\n element: pin,\n targetElement,\n anchorResult,\n }\n\n this.positionPin(pinState)\n\n if (pin.style.left === '' && pin.style.top === '') {\n return null\n }\n\n return pinState\n }\n\n private positionPin(pinState: PinState): void {\n const { comment, element: pin, targetElement } = pinState\n // Offset so the tail tip sits at the anchor point\n // Tail tip: left 2px + 25% of 18px width = 6.5px from shell left\n const pinOffsetX = 7\n const pinOffsetY = 45\n\n // If we have anchor data and a target element, use relative positioning\n if (comment.anchor && targetElement) {\n const position = this.anchoring.calculatePinPosition(targetElement, comment.anchor)\n pin.style.left = `${position.x - pinOffsetX}px`\n pin.style.top = `${position.y - pinOffsetY}px`\n return\n }\n\n // Legacy: element selector without anchor\n if (targetElement) {\n const rect = targetElement.getBoundingClientRect()\n pin.style.left = `${rect.left + window.scrollX - pinOffsetX}px`\n pin.style.top = `${rect.top + window.scrollY - pinOffsetY}px`\n return\n }\n\n // Fall back to percentage coordinates (legacy or orphaned)\n if (comment.x_percent !== null && comment.y_percent !== null) {\n const x = (comment.x_percent / 100) * document.documentElement.scrollWidth\n const y = (comment.y_percent / 100) * document.documentElement.scrollHeight\n pin.style.left = `${x - pinOffsetX}px`\n pin.style.top = `${y - pinOffsetY}px`\n return\n }\n\n // Could not position - pin will be hidden\n pin.style.display = 'none'\n }\n\n private formatTimeAgo(date: Date): string {\n const seconds = Math.floor((Date.now() - date.getTime()) / 1000)\n if (seconds < 60) return 'just now'\n if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`\n if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`\n if (seconds < 604800) return `${Math.floor(seconds / 86400)}d ago`\n return date.toLocaleDateString()\n }\n\n private escapeHtml(text: string): string {\n const div = document.createElement('div')\n div.textContent = text\n return div.innerHTML\n }\n\n private setupHoverListeners(pin: HTMLElement, comment: Comment): void {\n pin.addEventListener('mouseenter', () => {\n // Cancel any pending collapse\n const leaveTimer = this.leaveTimers.get(comment.id)\n if (leaveTimer) {\n clearTimeout(leaveTimer)\n this.leaveTimers.delete(comment.id)\n }\n\n // Debounce the expand (150ms)\n const timer = window.setTimeout(() => {\n this.expandPin(comment.id, pin)\n this.hoverTimers.delete(comment.id)\n }, 150)\n this.hoverTimers.set(comment.id, timer)\n })\n\n pin.addEventListener('mouseleave', () => {\n // Cancel any pending expand\n const hoverTimer = this.hoverTimers.get(comment.id)\n if (hoverTimer) {\n clearTimeout(hoverTimer)\n this.hoverTimers.delete(comment.id)\n }\n\n // Grace period before collapse (120ms)\n const timer = window.setTimeout(() => {\n this.collapsePin(comment.id, pin)\n this.leaveTimers.delete(comment.id)\n }, 120)\n this.leaveTimers.set(comment.id, timer)\n })\n }\n\n private expandPin(commentId: string, pin: HTMLElement): void {\n // Skip if this pin is active (card already open)\n if (pin.classList.contains('active')) return\n\n // Collapse any other expanded pin first\n if (this.currentlyExpanded && this.currentlyExpanded !== commentId) {\n const prev = this.pins.get(this.currentlyExpanded)\n if (prev) {\n prev.element.classList.remove('expanded', 'expand-left')\n }\n }\n this.currentlyExpanded = commentId\n\n // Edge detection: expand left if too close to right viewport edge\n const pinRect = pin.getBoundingClientRect()\n if (window.innerWidth - pinRect.left < 300) {\n pin.classList.add('expand-left')\n } else {\n pin.classList.remove('expand-left')\n }\n\n pin.classList.add('expanded')\n }\n\n private collapsePin(commentId: string, pin: HTMLElement): void {\n pin.classList.remove('expanded', 'expand-left')\n if (this.currentlyExpanded === commentId) {\n this.currentlyExpanded = null\n }\n }\n\n setActive(commentId: string | null): void {\n this.activeId = commentId\n this.pins.forEach((pinState, id) => {\n pinState.element.classList.toggle('active', id === commentId)\n })\n }\n\n highlight(commentId: string): void {\n this.highlightedId = commentId\n\n // Update pin classes\n this.pins.forEach((pinState, id) => {\n pinState.element.classList.toggle('highlighted', id === commentId)\n })\n\n // Clear highlight after animation\n setTimeout(() => {\n this.highlightedId = null\n this.pins.forEach((pinState) => {\n pinState.element.classList.remove('highlighted')\n })\n }, 2000)\n }\n\n show(): void {\n this.pinsContainer.classList.add('visible')\n }\n\n hide(): void {\n this.pinsContainer.classList.remove('visible')\n }\n\n destroy(): void {\n this.hoverTimers.forEach((t) => clearTimeout(t))\n this.leaveTimers.forEach((t) => clearTimeout(t))\n if (this.resizeObserver) {\n this.resizeObserver.disconnect()\n }\n if (this.repositionRAF) {\n cancelAnimationFrame(this.repositionRAF)\n }\n window.removeEventListener('scroll', this.boundScheduleReposition)\n window.removeEventListener('resize', this.boundScheduleReposition)\n }\n}\n","import type { Comment } from '../types'\nimport type { WidgetUser } from '../auth'\nimport { getAvatarColor, getAvatarGradient, getInitials, renderAvatar } from './avatars'\n\ninterface CardCallbacks {\n onResolve: (commentId: string, resolved: boolean) => void\n onReply: (parentId: string, content: string) => void\n onEdit: (commentId: string, content: string) => void\n onDelete: (commentId: string) => void\n onClose: () => void\n}\n\nexport class CommentCard {\n private container: HTMLElement\n private card: HTMLElement\n private callbacks: CardCallbacks\n private currentComment: Comment | null = null\n private currentUser: WidgetUser | null = null\n private visible = false\n private editing = false\n private dropdownOpen = false\n private boundOnMouseDown: (e: MouseEvent) => void\n private boundOnKeyDown: (e: KeyboardEvent) => void\n\n constructor(container: HTMLElement, callbacks: CardCallbacks) {\n this.container = container\n this.callbacks = callbacks\n\n this.card = document.createElement('div')\n this.card.className = 'tack-card'\n this.card.setAttribute('role', 'dialog')\n this.card.setAttribute('aria-label', 'Comment details')\n this.container.appendChild(this.card)\n\n // Stop clicks inside card from propagating\n this.card.addEventListener('mousedown', (e) => e.stopPropagation())\n this.card.addEventListener('click', (e) => e.stopPropagation())\n\n this.boundOnMouseDown = this.onDocumentMouseDown.bind(this)\n this.boundOnKeyDown = this.onKeyDown.bind(this)\n }\n\n setUser(user: WidgetUser | null): void {\n this.currentUser = user\n }\n\n show(comment: Comment, pinElement: HTMLElement): void {\n // If already showing this comment, just hide\n if (this.visible && this.currentComment?.id === comment.id) {\n this.hide()\n return\n }\n\n this.currentComment = comment\n this.visible = true\n this.editing = false\n this.dropdownOpen = false\n\n this.renderCard(comment)\n this.positionNear(pinElement)\n this.card.classList.add('visible')\n this.attachListeners()\n\n document.addEventListener('mousedown', this.boundOnMouseDown, true)\n document.addEventListener('keydown', this.boundOnKeyDown)\n }\n\n hide(): void {\n if (!this.visible) return\n\n this.visible = false\n this.currentComment = null\n this.editing = false\n this.dropdownOpen = false\n this.card.classList.remove('visible')\n\n document.removeEventListener('mousedown', this.boundOnMouseDown, true)\n document.removeEventListener('keydown', this.boundOnKeyDown)\n\n this.callbacks.onClose()\n }\n\n isVisible(): boolean {\n return this.visible\n }\n\n updateComment(comment: Comment): void {\n if (!this.visible || this.currentComment?.id !== comment.id) return\n this.currentComment = comment\n this.renderCard(comment)\n this.attachListeners()\n }\n\n private onDocumentMouseDown(e: MouseEvent): void {\n if (this.card.contains(e.target as Node)) return\n const path = e.composedPath()\n if (path.includes(this.card)) return\n this.hide()\n }\n\n private onKeyDown(e: KeyboardEvent): void {\n if (e.key === 'Escape') {\n if (this.dropdownOpen) {\n this.dropdownOpen = false\n this.renderCard(this.currentComment!)\n this.attachListeners()\n return\n }\n if (this.editing) {\n this.editing = false\n this.renderCard(this.currentComment!)\n this.attachListeners()\n return\n }\n e.preventDefault()\n this.hide()\n }\n }\n\n private positionNear(pinElement: HTMLElement): void {\n const pinRect = pinElement.getBoundingClientRect()\n const cardWidth = 320\n const gap = 8\n const panelWidth = document.body.classList.contains('tack-panel-open') ? 360 : 0\n const maxRight = window.innerWidth - panelWidth - 16\n\n let left = pinRect.right + gap + window.scrollX\n if (left + cardWidth > maxRight + window.scrollX) {\n left = pinRect.left - cardWidth - gap + window.scrollX\n }\n left = Math.max(16, left)\n\n let top = pinRect.top + window.scrollY\n const maxBottom = window.innerHeight + window.scrollY - 16\n const estimatedHeight = 300\n if (top + estimatedHeight > maxBottom) {\n top = maxBottom - estimatedHeight\n }\n top = Math.max(16 + window.scrollY, top)\n\n this.card.style.left = `${left}px`\n this.card.style.top = `${top}px`\n }\n\n private isOwnComment(comment: Comment): boolean {\n // Prefer user_id match for authenticated users\n if (this.currentUser?.id && comment.user_id) {\n return this.currentUser.id === comment.user_id\n }\n // Fall back to name match for legacy comments\n const authorName = localStorage.getItem('tack_author_name') || ''\n return authorName === comment.author_name\n }\n\n private renderCard(comment: Comment): void {\n const replies = comment.replies || []\n const currentAuthor = localStorage.getItem('tack_author_name') || 'Anonymous'\n\n // Header\n let html = `\n <div class=\"tack-card-title-bar\">\n <span class=\"tack-card-title\">Comment</span>\n <div class=\"tack-card-title-actions\">\n <button class=\"tack-card-resolve-icon ${comment.resolved ? 'resolved' : ''}\" data-id=\"${comment.id}\" title=\"${comment.resolved ? 'Unresolve' : 'Resolve'}\" aria-label=\"${comment.resolved ? 'Unresolve comment' : 'Resolve comment'}\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M22 11.08V12a10 10 0 1 1-5.93-9.14\"/>\n <polyline points=\"22 4 12 14.01 9 11.01\"/>\n </svg>\n </button>\n <button class=\"tack-card-close-btn\" title=\"Close\" aria-label=\"Close comment\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n </div>\n </div>`\n\n // Resolved banner\n if (comment.resolved) {\n html += `\n <div class=\"tack-card-resolved-banner\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\"/>\n </svg>\n Resolved by ${this.escapeHtml(comment.author_name)}\n </div>`\n }\n\n // Main comment row\n html += this.renderCommentRow(comment, true)\n\n // Divider\n html += `<div class=\"tack-card-divider\"></div>`\n\n // Replies\n if (replies.length > 0) {\n replies.forEach((reply, index) => {\n html += this.renderCommentRow(reply, false)\n if (index < replies.length - 1) {\n html += `<div class=\"tack-card-divider\"></div>`\n }\n })\n html += `<div class=\"tack-card-divider\"></div>`\n }\n\n // Always-visible reply input\n html += `\n <div class=\"tack-card-reply-bar\">\n <div class=\"tack-card-reply-bar-avatar-wrap\">${renderAvatar(currentAuthor, this.currentUser?.avatar_url || null, 24)}</div>\n <div class=\"tack-card-reply-bar-input-wrap\">\n <input type=\"text\" class=\"tack-card-reply-bar-input\" placeholder=\"Reply…\" data-parent-id=\"${comment.id}\" />\n <button class=\"tack-card-reply-bar-send\" data-parent-id=\"${comment.id}\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"12\" y1=\"19\" x2=\"12\" y2=\"5\"/>\n <polyline points=\"5 12 12 5 19 12\"/>\n </svg>\n </button>\n </div>\n </div>`\n\n this.card.innerHTML = html\n }\n\n private renderCommentRow(comment: Comment, isMain: boolean): string {\n const timeAgo = this.formatTimeAgo(new Date(comment.created_at))\n const canEdit = isMain && this.isOwnComment(comment)\n\n let html = `<div class=\"tack-card-row${isMain ? ' main' : ''}\" data-comment-id=\"${comment.id}\">`\n\n // Row header: avatar + meta\n html += `\n <div class=\"tack-card-row-header\">\n <div class=\"tack-card-row-avatar-wrap\">${renderAvatar(comment.author_name, comment.author_avatar_url, 28)}</div>\n <div class=\"tack-card-row-meta\">\n <span class=\"tack-card-row-author\">${this.escapeHtml(comment.author_name)}</span>\n ${this.editing && isMain ? '<span class=\"tack-card-editing-badge\">Editing</span>' : ''}\n <span class=\"tack-card-row-time\">· ${timeAgo}</span>\n </div>\n ${canEdit ? `<button class=\"tack-card-more-btn\" data-comment-id=\"${comment.id}\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\">\n <circle cx=\"12\" cy=\"5\" r=\"1\"/><circle cx=\"12\" cy=\"12\" r=\"1\"/><circle cx=\"12\" cy=\"19\" r=\"1\"/>\n </svg>\n </button>` : ''}\n </div>`\n\n // Dropdown menu\n if (canEdit && this.dropdownOpen) {\n html += `\n <div class=\"tack-card-dropdown\">\n <button class=\"tack-card-dropdown-item\" data-action=\"edit\" aria-label=\"Edit comment\">Edit Comment</button>\n <button class=\"tack-card-dropdown-item delete\" data-action=\"delete\" aria-label=\"Delete comment\">Delete Comment</button>\n </div>`\n }\n\n // Screenshot\n if (isMain && comment.screenshot_url) {\n html += `<img src=\"${comment.screenshot_url}\" class=\"tack-card-row-screenshot\" alt=\"Screenshot\" />`\n }\n\n // Comment body / edit mode\n if (this.editing && isMain) {\n html += `\n <div class=\"tack-card-edit-wrap\">\n <textarea class=\"tack-card-edit-textarea\">${this.escapeHtml(comment.content)}</textarea>\n <div class=\"tack-card-edit-actions\">\n <button class=\"tack-card-edit-cancel\">Cancel</button>\n <button class=\"tack-card-edit-save\">Save</button>\n </div>\n </div>`\n } else {\n html += `<div class=\"tack-card-row-body\">${this.escapeHtml(comment.content)}</div>`\n }\n\n html += `</div>`\n return html\n }\n\n private attachListeners(): void {\n // Close button\n this.card.querySelector('.tack-card-close-btn')?.addEventListener('click', (e) => {\n e.stopPropagation()\n this.hide()\n })\n\n // Resolve icon\n this.card.querySelector('.tack-card-resolve-icon')?.addEventListener('click', (e) => {\n e.stopPropagation()\n if (!this.currentComment) return\n this.callbacks.onResolve(this.currentComment.id, !this.currentComment.resolved)\n })\n\n // More menu button\n this.card.querySelector('.tack-card-more-btn')?.addEventListener('click', (e) => {\n e.stopPropagation()\n this.dropdownOpen = !this.dropdownOpen\n this.renderCard(this.currentComment!)\n this.attachListeners()\n })\n\n // Dropdown items\n this.card.querySelectorAll('.tack-card-dropdown-item').forEach((btn) => {\n btn.addEventListener('click', (e) => {\n e.stopPropagation()\n const action = (btn as HTMLElement).dataset.action\n if (action === 'edit') {\n this.dropdownOpen = false\n this.editing = true\n this.renderCard(this.currentComment!)\n this.attachListeners()\n // Focus textarea\n const textarea = this.card.querySelector('.tack-card-edit-textarea') as HTMLTextAreaElement\n textarea?.focus()\n textarea?.setSelectionRange(textarea.value.length, textarea.value.length)\n } else if (action === 'delete') {\n this.dropdownOpen = false\n if (this.currentComment) {\n this.callbacks.onDelete(this.currentComment.id)\n this.hide()\n }\n }\n })\n })\n\n // Edit cancel\n this.card.querySelector('.tack-card-edit-cancel')?.addEventListener('click', (e) => {\n e.stopPropagation()\n this.editing = false\n this.renderCard(this.currentComment!)\n this.attachListeners()\n })\n\n // Edit save\n this.card.querySelector('.tack-card-edit-save')?.addEventListener('click', (e) => {\n e.stopPropagation()\n this.saveEdit()\n })\n\n // Edit textarea Cmd+Enter\n this.card.querySelector('.tack-card-edit-textarea')?.addEventListener('keydown', (e: Event) => {\n const ke = e as KeyboardEvent\n if (ke.key === 'Enter' && (ke.metaKey || ke.ctrlKey)) {\n ke.preventDefault()\n this.saveEdit()\n }\n })\n\n // Reply input — Enter to send\n const replyInput = this.card.querySelector('.tack-card-reply-bar-input') as HTMLInputElement\n replyInput?.addEventListener('keydown', (e: Event) => {\n const ke = e as KeyboardEvent\n if (ke.key === 'Enter' && !ke.shiftKey) {\n ke.preventDefault()\n this.submitReply()\n }\n })\n\n // Reply send button\n this.card.querySelector('.tack-card-reply-bar-send')?.addEventListener('click', (e) => {\n e.stopPropagation()\n this.submitReply()\n })\n }\n\n private saveEdit(): void {\n if (!this.currentComment) return\n const textarea = this.card.querySelector('.tack-card-edit-textarea') as HTMLTextAreaElement\n const content = textarea?.value.trim()\n if (!content) return\n this.editing = false\n this.callbacks.onEdit(this.currentComment.id, content)\n }\n\n private submitReply(): void {\n if (!this.currentComment) return\n const input = this.card.querySelector('.tack-card-reply-bar-input') as HTMLInputElement\n const content = input?.value.trim()\n if (!content) return\n this.callbacks.onReply(this.currentComment.id, content)\n input.value = ''\n }\n\n private formatTimeAgo(date: Date): string {\n const seconds = Math.floor((Date.now() - date.getTime()) / 1000)\n if (seconds < 60) return 'just now'\n if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`\n if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`\n if (seconds < 604800) return `${Math.floor(seconds / 86400)}d ago`\n return date.toLocaleDateString()\n }\n\n private escapeHtml(text: string): string {\n const div = document.createElement('div')\n div.textContent = text\n return div.innerHTML\n }\n}\n","import type { Comment } from './types'\n\ntype RealtimeCallback = (event: 'INSERT' | 'UPDATE' | 'DELETE', comment: Comment) => void\n\nexport class RealtimeSubscription {\n private ws: WebSocket | null = null\n private callback: RealtimeCallback\n private supabaseUrl: string\n private supabaseKey: string\n private versionId: string\n private reconnectAttempts = 0\n private maxReconnectAttempts = 5\n private heartbeatInterval: ReturnType<typeof setInterval> | null = null\n\n constructor(\n supabaseUrl: string,\n supabaseKey: string,\n versionId: string,\n callback: RealtimeCallback\n ) {\n this.supabaseUrl = supabaseUrl\n this.supabaseKey = supabaseKey\n this.versionId = versionId\n this.callback = callback\n }\n\n connect(): void {\n const wsUrl = this.supabaseUrl.replace('https://', 'wss://') + '/realtime/v1/websocket'\n const params = new URLSearchParams({\n apikey: this.supabaseKey,\n vsn: '1.0.0',\n })\n\n this.ws = new WebSocket(`${wsUrl}?${params}`)\n\n this.ws.onopen = () => {\n this.reconnectAttempts = 0\n this.subscribe()\n }\n\n this.ws.onmessage = (event) => {\n const data = JSON.parse(event.data)\n this.handleMessage(data)\n }\n\n this.ws.onclose = () => {\n this.attemptReconnect()\n }\n\n this.ws.onerror = (error) => {\n console.error('[Tack] Realtime error:', error)\n }\n }\n\n private subscribe(): void {\n if (!this.ws) return\n\n // Join the realtime channel for comments\n const joinMessage = {\n topic: `realtime:public:comments`,\n event: 'phx_join',\n payload: {\n config: {\n postgres_changes: [\n {\n event: '*',\n schema: 'public',\n table: 'comments',\n filter: `version_id=eq.${this.versionId}`,\n },\n ],\n },\n },\n ref: '1',\n }\n\n this.ws.send(JSON.stringify(joinMessage))\n\n // Start heartbeat\n this.startHeartbeat()\n }\n\n private handleMessage(data: { event: string; payload?: { data?: { type: string; record: Comment } } }): void {\n if (data.event === 'postgres_changes' && data.payload?.data) {\n const { type, record } = data.payload.data\n const eventType = type.toUpperCase() as 'INSERT' | 'UPDATE' | 'DELETE'\n this.callback(eventType, record)\n }\n }\n\n private startHeartbeat(): void {\n if (this.heartbeatInterval) {\n clearInterval(this.heartbeatInterval)\n }\n this.heartbeatInterval = setInterval(() => {\n if (this.ws?.readyState === WebSocket.OPEN) {\n this.ws.send(JSON.stringify({\n topic: 'phoenix',\n event: 'heartbeat',\n payload: {},\n ref: Date.now().toString(),\n }))\n }\n }, 30000)\n }\n\n private attemptReconnect(): void {\n if (this.reconnectAttempts >= this.maxReconnectAttempts) {\n console.error('[Tack] Max reconnect attempts reached')\n return\n }\n\n this.reconnectAttempts++\n const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000)\n\n setTimeout(() => {\n console.log(`[Tack] Reconnecting... (attempt ${this.reconnectAttempts})`)\n this.connect()\n }, delay)\n }\n\n disconnect(): void {\n if (this.heartbeatInterval) {\n clearInterval(this.heartbeatInterval)\n this.heartbeatInterval = null\n }\n if (this.ws) {\n this.ws.close()\n this.ws = null\n }\n }\n}\n","export interface PresenceUser {\n id: string\n name: string\n avatar_url: string | null\n}\n\ntype PresenceChangeCallback = (users: PresenceUser[]) => void\n\n/**\n * Manages real-time presence using Supabase Realtime Presence channels.\n * Uses raw WebSocket protocol (same as RealtimeSubscription) to avoid extra deps.\n */\nexport class PresenceManager {\n private ws: WebSocket | null = null\n private supabaseUrl: string\n private supabaseKey: string\n private projectId: string\n private currentUser: PresenceUser | null = null\n private pagePath: string = ''\n private users: Map<string, PresenceUser> = new Map()\n private onPresenceChange: PresenceChangeCallback | null = null\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null\n private reconnectAttempts = 0\n private joined = false\n private ref = 1\n\n constructor(supabaseUrl: string, supabaseKey: string, projectId: string) {\n this.supabaseUrl = supabaseUrl\n this.supabaseKey = supabaseKey\n this.projectId = projectId\n }\n\n join(user: PresenceUser): void {\n this.currentUser = user\n this.connect()\n }\n\n leave(): void {\n this.joined = false\n this.currentUser = null\n this.users.clear()\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer)\n this.heartbeatTimer = null\n }\n if (this.ws) {\n this.ws.close()\n this.ws = null\n }\n }\n\n updatePagePath(path: string): void {\n this.pagePath = path\n if (this.joined && this.ws?.readyState === WebSocket.OPEN) {\n this.trackPresence()\n }\n }\n\n setOnPresenceChange(cb: PresenceChangeCallback): void {\n this.onPresenceChange = cb\n }\n\n private connect(): void {\n const wsUrl = this.supabaseUrl.replace('https://', 'wss://') + '/realtime/v1/websocket'\n const params = new URLSearchParams({\n apikey: this.supabaseKey,\n vsn: '1.0.0',\n })\n\n this.ws = new WebSocket(`${wsUrl}?${params}`)\n\n this.ws.onopen = () => {\n this.reconnectAttempts = 0\n this.joinChannel()\n }\n\n this.ws.onmessage = (event) => {\n try {\n const data = JSON.parse(event.data)\n this.handleMessage(data)\n } catch {\n // Ignore parse errors\n }\n }\n\n this.ws.onclose = () => {\n if (this.currentUser) {\n this.attemptReconnect()\n }\n }\n\n this.ws.onerror = () => {\n // Error handler, reconnect will be triggered by onclose\n }\n }\n\n private nextRef(): string {\n return (this.ref++).toString()\n }\n\n private joinChannel(): void {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return\n\n const topic = `realtime:presence:${this.projectId}`\n this.ws.send(JSON.stringify({\n topic,\n event: 'phx_join',\n payload: {\n config: {\n presence: { key: this.currentUser!.id },\n },\n },\n ref: this.nextRef(),\n }))\n\n this.startHeartbeat()\n\n // Track presence after a short delay to ensure channel is joined\n setTimeout(() => {\n this.joined = true\n this.trackPresence()\n }, 200)\n }\n\n private trackPresence(): void {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN || !this.currentUser) return\n\n const topic = `realtime:presence:${this.projectId}`\n this.ws.send(JSON.stringify({\n topic,\n event: 'presence',\n payload: {\n type: 'presence',\n event: 'track',\n payload: {\n user_id: this.currentUser.id,\n name: this.currentUser.name,\n avatar_url: this.currentUser.avatar_url,\n page_path: this.pagePath,\n online_at: Date.now(),\n },\n },\n ref: this.nextRef(),\n }))\n }\n\n private handleMessage(data: { event: string; topic?: string; payload?: any }): void {\n if (data.event === 'presence_state') {\n // Full sync of all present users\n this.users.clear()\n const state = data.payload || {}\n for (const key of Object.keys(state)) {\n const metas = state[key]?.metas\n if (metas?.length > 0) {\n const meta = metas[0]\n this.users.set(key, {\n id: meta.user_id || key,\n name: meta.name || 'Unknown',\n avatar_url: meta.avatar_url || null,\n })\n }\n }\n this.notifyChange()\n } else if (data.event === 'presence_diff') {\n const { joins, leaves } = data.payload || {}\n\n // Handle joins\n if (joins) {\n for (const key of Object.keys(joins)) {\n const metas = joins[key]?.metas\n if (metas?.length > 0) {\n const meta = metas[0]\n this.users.set(key, {\n id: meta.user_id || key,\n name: meta.name || 'Unknown',\n avatar_url: meta.avatar_url || null,\n })\n }\n }\n }\n\n // Handle leaves\n if (leaves) {\n for (const key of Object.keys(leaves)) {\n this.users.delete(key)\n }\n }\n\n this.notifyChange()\n }\n }\n\n private notifyChange(): void {\n const userList = Array.from(this.users.values())\n this.onPresenceChange?.(userList)\n }\n\n private startHeartbeat(): void {\n if (this.heartbeatTimer) clearInterval(this.heartbeatTimer)\n this.heartbeatTimer = setInterval(() => {\n if (this.ws?.readyState === WebSocket.OPEN) {\n this.ws.send(JSON.stringify({\n topic: 'phoenix',\n event: 'heartbeat',\n payload: {},\n ref: this.nextRef(),\n }))\n }\n }, 30000)\n }\n\n private attemptReconnect(): void {\n if (this.reconnectAttempts >= 5) return\n this.reconnectAttempts++\n const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000)\n setTimeout(() => {\n if (this.currentUser) {\n this.connect()\n }\n }, delay)\n }\n}\n","import type { TackConfig, Comment, Version, CommentAnchor } from './types'\nimport { TackAPI } from './api'\nimport { WidgetAuth } from './auth'\nimport { createStyles } from './styles'\nimport { ElementSelector } from './selector'\nimport { CommentForm } from './ui/form'\nimport { CommentPanel } from './ui/panel'\nimport { CommentPins } from './ui/pins'\nimport { CommentCard } from './ui/card'\nimport { RealtimeSubscription } from './realtime'\nimport { PresenceManager } from './presence'\n\nexport class TackWidget {\n private config: TackConfig\n private api: TackAPI\n private auth: WidgetAuth\n private container: HTMLElement | null = null\n private shadowRoot: ShadowRoot | null = null\n private selector: ElementSelector | null = null\n private form: CommentForm | null = null\n private panel: CommentPanel | null = null\n private pins: CommentPins | null = null\n private card: CommentCard | null = null\n private realtime: RealtimeSubscription | null = null\n private presence: PresenceManager | null = null\n private commentMode = false\n private comments: Comment[] = []\n private currentVersion: Version | null = null\n private currentPagePath: string = ''\n private originalPushState: typeof history.pushState | null = null\n private originalReplaceState: typeof history.replaceState | null = null\n private pollingInterval: ReturnType<typeof setInterval> | null = null\n private boundOnUrlChange: (() => void) | null = null\n\n constructor(config: TackConfig) {\n this.config = {\n apiUrl: 'https://tacktext.vercel.app/api',\n supabaseUrl: 'https://etpavqvnpupqdxdptkhc.supabase.co',\n supabaseKey: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImV0cGF2cXZucHVwcWR4ZHB0a2hjIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzQ4MzU4MzgsImV4cCI6MjA5MDQxMTgzOH0.GMSIuPmMaEkNhss_laSm_NchihN_KF9VyyEh5YH4Ru0',\n ...config,\n }\n this.api = new TackAPI(this.config.apiUrl!, this.config.projectId)\n this.auth = new WidgetAuth(this.config.apiUrl!)\n }\n\n async mount(): Promise<void> {\n // Create container with Shadow DOM\n this.container = document.createElement('div')\n this.container.id = 'tack-widget'\n this.shadowRoot = this.container.attachShadow({ mode: 'open' })\n\n // Add styles to shadow DOM\n const styles = document.createElement('style')\n styles.textContent = createStyles()\n this.shadowRoot.appendChild(styles)\n\n // Add styles to main document for pushing page content\n this.injectPageStyles()\n\n // Create main wrapper\n const wrapper = document.createElement('div')\n wrapper.className = 'tack-wrapper'\n this.shadowRoot.appendChild(wrapper)\n\n // Initialize auth\n this.auth.setOnAuthChange((user) => {\n this.api.setAuthToken(user ? this.auth.getSessionToken() : null)\n this.form?.setUser(user)\n this.card?.setUser(user)\n this.panel?.setUser(user)\n\n // Manage presence based on auth state\n if (user) {\n this.joinPresence(user)\n } else {\n this.presence?.leave()\n this.presence = null\n }\n })\n\n await this.auth.init()\n if (this.auth.isAuthenticated()) {\n this.api.setAuthToken(this.auth.getSessionToken())\n }\n\n // Handle 401 from API\n this.api.setOnUnauthorized(() => {\n this.auth.signOut()\n this.showToast('Session expired. Please sign in again.')\n })\n\n // Initialize components\n this.selector = new ElementSelector(this.onElementSelected.bind(this))\n this.form = new CommentForm(wrapper, this.onCommentSubmit.bind(this))\n this.form.setUser(this.auth.getUser())\n this.form.setOnHide(() => {\n if (this.panel?.isOpen()) {\n this.enableCommentMode()\n }\n })\n this.form.setOnSignIn(() => this.signIn())\n this.panel = new CommentPanel(wrapper, {\n onCommentClick: this.onCommentClick.bind(this),\n onResolve: this.onResolve.bind(this),\n onReply: this.onReply.bind(this),\n onClose: this.onPanelClose.bind(this),\n onAddComment: this.enableCommentMode.bind(this),\n onShare: this.onShare.bind(this),\n })\n this.card = new CommentCard(wrapper, {\n onResolve: this.onResolve.bind(this),\n onReply: this.onReply.bind(this),\n onEdit: this.onEdit.bind(this),\n onDelete: this.onDelete.bind(this),\n onClose: this.onCardClose.bind(this),\n })\n this.card.setUser(this.auth.getUser())\n this.panel.setUser(this.auth.getUser())\n this.panel.setProjectId(this.config.projectId)\n this.pins = new CommentPins(wrapper, this.onPinClick.bind(this))\n\n // Add toggle button\n this.createToggleButton(wrapper)\n\n // Mount to document\n document.body.appendChild(this.container)\n\n // Load initial data\n await this.loadComments()\n\n // Check for deep link\n this.handleDeepLink()\n\n // Set up realtime subscription\n this.subscribeToRealtime()\n\n // Set up URL change detection for SPAs\n this.setupUrlChangeDetection()\n\n // Join presence if authenticated\n if (this.auth.isAuthenticated()) {\n const user = this.auth.getUser()!\n this.joinPresence(user)\n }\n }\n\n private joinPresence(user: { id: string; name: string; avatar_url: string | null }): void {\n if (!this.config.supabaseUrl || !this.config.supabaseKey) return\n\n this.presence?.leave()\n this.presence = new PresenceManager(\n this.config.supabaseUrl,\n this.config.supabaseKey,\n this.config.projectId\n )\n this.presence.setOnPresenceChange((users) => {\n this.panel?.renderPresence(users)\n })\n this.presence.join(user)\n this.presence.updatePagePath(this.getPagePath())\n }\n\n async signIn(): Promise<void> {\n try {\n await this.auth.signIn()\n } catch (error) {\n console.error('[Tack] Sign in failed:', error)\n }\n }\n\n private getPagePath(): string {\n return window.location.pathname + window.location.search\n }\n\n private setupUrlChangeDetection(): void {\n this.currentPagePath = this.getPagePath()\n\n this.boundOnUrlChange = this.onUrlChange.bind(this)\n window.addEventListener('popstate', this.boundOnUrlChange)\n\n this.originalPushState = history.pushState.bind(history)\n this.originalReplaceState = history.replaceState.bind(history)\n\n history.pushState = (...args) => {\n this.originalPushState!(...args)\n this.onUrlChange()\n }\n\n history.replaceState = (...args) => {\n this.originalReplaceState!(...args)\n this.onUrlChange()\n }\n }\n\n private onUrlChange(): void {\n const newPath = this.getPagePath()\n if (newPath !== this.currentPagePath) {\n this.currentPagePath = newPath\n this.presence?.updatePagePath(newPath)\n setTimeout(() => {\n console.log('[Tack] URL changed, re-rendering pins after DOM settle')\n this.pins?.render(this.comments)\n this.panel?.render(this.comments)\n }, 100)\n }\n }\n\n private createToggleButton(wrapper: HTMLElement): void {\n const button = document.createElement('button')\n button.className = 'tack-toggle'\n button.setAttribute('aria-label', 'Toggle comments')\n button.setAttribute('aria-expanded', 'false')\n button.innerHTML = `\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\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\"></path>\n </svg>\n `\n button.addEventListener('click', () => this.toggleCommentMode())\n wrapper.appendChild(button)\n }\n\n private toggleCommentMode(): void {\n if (this.panel?.isOpen()) {\n this.panel.hide()\n this.setPanelOpen(false)\n this.pins?.hide()\n this.disableCommentMode()\n this.updateToggleAria(false)\n } else {\n this.panel?.show()\n this.setPanelOpen(true)\n this.pins?.show()\n this.container?.classList.add('tack-active')\n this.enableCommentMode()\n this.updateToggleAria(true)\n }\n }\n\n private enableCommentMode(): void {\n this.commentMode = true\n this.selector?.enable()\n }\n\n private disableCommentMode(): void {\n this.commentMode = false\n this.selector?.disable()\n this.form?.hide()\n this.container?.classList.remove('tack-active')\n }\n\n private onPanelClose(): void {\n this.setPanelOpen(false)\n this.pins?.hide()\n this.disableCommentMode()\n this.updateToggleAria(false)\n }\n\n private onShare(): void {\n const url = new URL(window.location.href)\n url.searchParams.set('tack_open', 'true')\n\n navigator.clipboard.writeText(url.toString()).then(() => {\n this.showToast('Link copied to clipboard')\n }).catch(() => {\n this.showToast('Failed to copy link')\n })\n }\n\n private injectPageStyles(): void {\n const styleId = 'tack-page-styles'\n if (document.getElementById(styleId)) return\n\n const style = document.createElement('style')\n style.id = styleId\n style.textContent = `\n body.tack-panel-open {\n margin-right: 360px !important;\n transition: margin-right 250ms cubic-bezier(0.16, 1, 0.3, 1) !important;\n }\n body {\n transition: margin-right 250ms cubic-bezier(0.16, 1, 0.3, 1);\n }\n `\n document.head.appendChild(style)\n }\n\n private setPanelOpen(open: boolean): void {\n if (open) {\n document.body.classList.add('tack-panel-open')\n } else {\n document.body.classList.remove('tack-panel-open')\n }\n }\n\n private updateToggleAria(expanded: boolean): void {\n const toggle = this.shadowRoot?.querySelector('.tack-toggle')\n if (toggle) {\n toggle.setAttribute('aria-expanded', String(expanded))\n }\n }\n\n private async loadComments(): Promise<void> {\n try {\n const data = await this.api.getComments()\n this.comments = data.comments\n this.currentVersion = data.version\n this.pins?.render(this.comments)\n this.panel?.render(this.comments)\n } catch (error) {\n console.error('[Tack] Failed to load comments:', error)\n }\n }\n\n private onElementSelected(target: { element: Element | null; anchor: CommentAnchor }): void {\n if (this.form?.isFormVisible()) return\n this.selector?.disable()\n this.form?.show(target)\n }\n\n private async onCommentSubmit(data: {\n content: string\n authorName: string\n authorEmail?: string\n anchor: CommentAnchor\n screenshot?: string\n }): Promise<void> {\n try {\n const createData: Record<string, unknown> = {\n content: data.content,\n element_selector: data.anchor.cssSelector,\n x_percent: data.anchor.relativeX * 100,\n y_percent: data.anchor.relativeY * 100,\n anchor: data.anchor,\n page_path: window.location.pathname + window.location.search,\n screenshot_data: data.screenshot,\n }\n // Only send author_name if not authenticated (server sets it otherwise)\n if (!this.auth.isAuthenticated()) {\n createData.author_name = data.authorName\n createData.author_email = data.authorEmail\n }\n\n const comment = await this.api.createComment(createData as any)\n const commentWithScreenshot = {\n ...comment,\n screenshot_url: comment.screenshot_url || data.screenshot || null,\n }\n this.comments.push(commentWithScreenshot)\n this.pins?.render(this.comments)\n this.panel?.render(this.comments)\n this.showToast('Comment added')\n if (this.panel?.isOpen()) {\n this.enableCommentMode()\n }\n } catch (error) {\n console.error('[Tack] Failed to submit comment:', error)\n this.showToast('Failed to add comment')\n }\n }\n\n private onCommentClick(comment: Comment): void {\n this.pins?.highlight(comment.id)\n this.scrollToComment(comment)\n }\n\n private onPinClick(commentId: string, pinElement: HTMLElement): void {\n const comment = this.comments.find((c) => c.id === commentId)\n if (comment) {\n this.card?.show(comment, pinElement)\n this.pins?.setActive(commentId)\n this.pins?.highlight(commentId)\n if (this.panel?.isOpen()) {\n this.panel.scrollToComment(commentId)\n }\n }\n }\n\n private onCardClose(): void {\n this.pins?.setActive(null)\n }\n\n private async onResolve(commentId: string, resolved: boolean): Promise<void> {\n try {\n await this.api.updateComment(commentId, { resolved })\n const comment = this.comments.find((c) => c.id === commentId)\n if (comment) {\n comment.resolved = resolved\n this.pins?.render(this.comments)\n this.panel?.render(this.comments)\n this.card?.updateComment(comment)\n }\n } catch (error) {\n console.error('[Tack] Failed to update comment:', error)\n }\n }\n\n private async onReply(parentId: string, content: string): Promise<void> {\n try {\n const createData: Record<string, unknown> = {\n content,\n parent_id: parentId,\n page_path: window.location.pathname + window.location.search,\n }\n if (!this.auth.isAuthenticated()) {\n createData.author_name = localStorage.getItem('tack_author_name') || 'Anonymous'\n }\n\n const reply = await this.api.createComment(createData as any)\n const parent = this.comments.find((c) => c.id === parentId)\n if (parent) {\n parent.replies = parent.replies || []\n parent.replies.push(reply)\n this.panel?.render(this.comments)\n this.card?.updateComment(parent)\n }\n } catch (error) {\n console.error('[Tack] Failed to submit reply:', error)\n }\n }\n\n private async onEdit(commentId: string, content: string): Promise<void> {\n try {\n await this.api.updateComment(commentId, { content })\n const comment = this.comments.find((c) => c.id === commentId)\n if (comment) {\n comment.content = content\n this.pins?.render(this.comments)\n this.panel?.render(this.comments)\n this.card?.updateComment(comment)\n }\n } catch (error) {\n console.error('[Tack] Failed to edit comment:', error)\n this.showToast('Failed to edit comment')\n }\n }\n\n private async onDelete(commentId: string): Promise<void> {\n try {\n await this.api.deleteComment(commentId)\n this.comments = this.comments.filter((c) => c.id !== commentId)\n this.pins?.render(this.comments)\n this.panel?.render(this.comments)\n this.showToast('Comment deleted')\n } catch (error) {\n console.error('[Tack] Failed to delete comment:', error)\n this.showToast('Failed to delete comment')\n }\n }\n\n private scrollToComment(comment: Comment): void {\n if (comment.element_selector) {\n const element = document.querySelector(comment.element_selector)\n element?.scrollIntoView({ behavior: 'smooth', block: 'center' })\n } else if (comment.x_percent !== null && comment.y_percent !== null) {\n const x = (comment.x_percent / 100) * document.documentElement.scrollWidth\n const y = (comment.y_percent / 100) * document.documentElement.scrollHeight\n window.scrollTo({ left: x - window.innerWidth / 2, top: y - window.innerHeight / 2, behavior: 'smooth' })\n }\n }\n\n private handleDeepLink(): void {\n const params = new URLSearchParams(window.location.search)\n\n const shouldOpen = params.get('tack_open')\n if (shouldOpen === 'true') {\n this.panel?.show()\n this.setPanelOpen(true)\n this.pins?.show()\n this.container?.classList.add('tack-active')\n this.updateToggleAria(true)\n }\n\n const commentId = params.get('tack_comment')\n if (commentId) {\n const comment = this.comments.find((c) => c.id === commentId)\n if (comment) {\n this.panel?.show()\n this.setPanelOpen(true)\n this.pins?.show()\n this.container?.classList.add('tack-active')\n this.updateToggleAria(true)\n this.panel?.scrollToComment(commentId)\n this.pins?.highlight(commentId)\n this.scrollToComment(comment)\n }\n }\n }\n\n private subscribeToRealtime(): void {\n if (!this.currentVersion || !this.config.supabaseUrl || !this.config.supabaseKey) {\n this.pollingInterval = setInterval(() => this.loadComments(), 30000)\n return\n }\n\n this.realtime = new RealtimeSubscription(\n this.config.supabaseUrl,\n this.config.supabaseKey,\n this.currentVersion.id,\n (event, comment) => {\n this.handleRealtimeEvent(event, comment)\n }\n )\n this.realtime.connect()\n }\n\n private handleRealtimeEvent(event: 'INSERT' | 'UPDATE' | 'DELETE', comment: Comment): void {\n switch (event) {\n case 'INSERT':\n const alreadyExists = this.comments.some((c) => c.id === comment.id) ||\n this.comments.some((c) => c.replies?.some((r) => r.id === comment.id))\n if (alreadyExists) {\n return\n }\n\n if (comment.parent_id) {\n const parent = this.comments.find((c) => c.id === comment.parent_id)\n if (parent) {\n parent.replies = parent.replies || []\n parent.replies.push(comment)\n }\n } else {\n this.comments.push(comment)\n }\n this.showToast(`New comment from ${comment.author_name}`)\n break\n\n case 'UPDATE':\n const existing = this.comments.find((c) => c.id === comment.id)\n if (existing) {\n Object.assign(existing, comment)\n }\n break\n\n case 'DELETE':\n this.comments = this.comments.filter((c) => c.id !== comment.id)\n break\n }\n\n this.pins?.render(this.comments)\n this.panel?.render(this.comments)\n }\n\n private showToast(message: string): void {\n if (!this.shadowRoot) return\n\n const toast = document.createElement('div')\n toast.className = 'tack-toast'\n toast.textContent = message\n toast.style.cssText = `\n position: fixed;\n bottom: 80px;\n right: 20px;\n background: #1a1a1a;\n color: white;\n padding: 12px 20px;\n border-radius: 8px;\n font-size: 14px;\n z-index: 10002;\n animation: tack-toast-in 0.3s ease;\n `\n this.shadowRoot.appendChild(toast)\n\n setTimeout(() => {\n toast.remove()\n }, 3000)\n }\n\n destroy(): void {\n if (this.pollingInterval) {\n clearInterval(this.pollingInterval)\n this.pollingInterval = null\n }\n\n this.selector?.disable()\n this.realtime?.disconnect()\n this.presence?.leave()\n this.pins?.destroy()\n\n if (this.boundOnUrlChange) {\n window.removeEventListener('popstate', this.boundOnUrlChange)\n this.boundOnUrlChange = null\n }\n\n if (this.originalPushState) {\n history.pushState = this.originalPushState\n }\n if (this.originalReplaceState) {\n history.replaceState = this.originalReplaceState\n }\n\n // Remove page styles and body class\n document.getElementById('tack-page-styles')?.remove()\n document.body.classList.remove('tack-panel-open')\n\n this.container?.remove()\n }\n}\n","import { TackWidget } from './widget'\nimport type { TackConfig } from './types'\n\nexport type { TackConfig }\n\nexport const Tack = {\n init(config: TackConfig): TackWidget {\n const widget = new TackWidget(config)\n widget.mount()\n return widget\n },\n}\n\nexport { TackWidget }\n"],"mappings":"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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA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","names":["TackAPI","baseUrl","projectId","token","cb","headers","res","data","id","url","WidgetAuth","apiUrl","token","res","user","resolve","reject","baseUrl","popup","expectedOrigin","handleMessage","event","pollTimer","cb","createStyles","ElementSelector","callback","target","rect","pad","radius","relativeX","relativeY","anchor","element","path","current","selector","classes","dataTestId","parent","siblings","child","index","fullSelector","parts","part","text","prefix","suffix","parentText","endIndex","tag","className","content","hash","i","id","captureElementWithContext","selector","x","y","html2canvas","element","rect","padding","viewportX","viewportY","captureWidth","captureHeight","e","CommentForm","container","onSubmit","callback","user","form","e","textarea","sendBtn","ke","target","captureElementWithContext","rect","viewportX","viewportY","fw","formWidth","formHeight","left","top","maxHeight","content","authorName","submitData","error","text","div","AVATAR_GRADIENTS","AVATAR_COLORS","to","hashName","name","hash","i","getAvatarColor","getAvatarGradient","from","getInitials","parts","renderAvatar","avatarUrl","size","extraClass","fontSize","cls","STORAGE_KEY","loadFilters","stored","parsed","saveFilters","filters","CommentPanel","container","callbacks","user","projectId","key","commentId","filtered","changed","c","strip","searchInput","searchClear","val","e","target","dropdown","f","check","empty","item","action","badge","btn","count","comments","users","presenceBar","header","maxShow","visible","overflow","html","u","renderAvatar","content","activeFilterCount","comment","row","id","dot","resolveBtn","moreBtn","parts","s","pagePath","path","isHighlighted","timeAgo","currentPath","isCurrentPage","pageName","hasScreenshot","unread","result","userId","userName","q","r","a","b","aUnread","bUnread","aReplies","bReplies","date","seconds","text","div","ElementAnchoring","anchor","result","element","rect","x","y","textQuote","walker","node","text","bestMatch","targetText","score","parent","parentText","elementText","index","endIndex","a","b","bigramsA","bigramsB","i","intersection","bigram","tag","className","content","hash","CommentPins","container","onClick","ElementAnchoring","pinState","commentId","comments","t","currentPath","c","comment","seen","authors","key","reply","rKey","author","size","initials","getInitials","gradient","getAvatarGradient","fs","anchorResult","targetElement","pin","isActive","isHighlighted","uniqueAuthors","isMultiAuthor","previewAuthor","timeAgo","previewText","replyCount","previewHTML","pinHTML","visibleAuthors","overflow","avatarsHTML","i","e","pinOffsetX","pinOffsetY","position","rect","x","y","date","seconds","text","div","leaveTimer","timer","hoverTimer","prev","pinRect","id","CommentCard","container","callbacks","e","user","comment","pinElement","pinRect","cardWidth","gap","panelWidth","maxRight","left","top","maxBottom","estimatedHeight","replies","currentAuthor","html","reply","index","renderAvatar","isMain","timeAgo","canEdit","btn","action","textarea","ke","content","input","date","seconds","text","div","RealtimeSubscription","supabaseUrl","supabaseKey","versionId","callback","wsUrl","params","event","data","error","joinMessage","type","record","eventType","delay","PresenceManager","supabaseUrl","supabaseKey","projectId","user","path","cb","wsUrl","params","event","data","topic","state","key","metas","meta","joins","leaves","userList","delay","TackWidget","config","TackAPI","WidgetAuth","styles","createStyles","wrapper","user","ElementSelector","CommentForm","CommentPanel","CommentCard","CommentPins","PresenceManager","users","error","args","newPath","button","url","styleId","style","open","expanded","toggle","data","target","createData","comment","commentWithScreenshot","commentId","pinElement","c","resolved","parentId","content","reply","parent","x","y","params","RealtimeSubscription","event","r","existing","message","toast","Tack","config","widget","TackWidget"]}
1
+ {"version":3,"sources":["../src/api.ts","../src/auth.ts","../src/styles.ts","../src/selector.ts","../src/capture.ts","../src/ui/form.ts","../src/ui/avatarCache.ts","../src/ui/avatars.ts","../src/ui/panel.ts","../src/anchoring.ts","../src/ui/pins.ts","../src/ui/card.ts","../src/realtime.ts","../src/presence.ts","../src/widget.ts","../src/index.ts"],"sourcesContent":["import type { Comment, Version, CommentAnchor } from './types'\n\ninterface CreateCommentData {\n content: string\n author_name?: string\n author_email?: string\n parent_id?: string\n element_selector?: string | null\n x_percent?: number\n y_percent?: number\n anchor?: CommentAnchor\n page_path: string\n screenshot_data?: string\n}\n\nexport class TackAPI {\n private baseUrl: string\n private projectId: string\n private authToken: string | null = null\n private onUnauthorized: (() => void) | null = null\n\n constructor(baseUrl: string, projectId: string) {\n this.baseUrl = baseUrl\n this.projectId = projectId\n }\n\n setAuthToken(token: string | null): void {\n this.authToken = token\n }\n\n setOnUnauthorized(cb: () => void): void {\n this.onUnauthorized = cb\n }\n\n private getHeaders(): Record<string, string> {\n const headers: Record<string, string> = { 'Content-Type': 'application/json' }\n if (this.authToken) {\n headers['Authorization'] = `Bearer ${this.authToken}`\n }\n return headers\n }\n\n private async handleResponse(res: Response): Promise<Response> {\n if (res.status === 401) {\n this.onUnauthorized?.()\n }\n return res\n }\n\n async getComments(): Promise<{ comments: Comment[]; version: Version }> {\n const res = await fetch(\n `${this.baseUrl}/projects/${this.projectId}/comments?path=${encodeURIComponent(window.location.pathname)}`,\n { headers: this.getHeaders() }\n )\n await this.handleResponse(res)\n if (!res.ok) throw new Error('Failed to fetch comments')\n return res.json()\n }\n\n async createComment(data: CreateCommentData): Promise<Comment> {\n const res = await fetch(`${this.baseUrl}/comments`, {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify({\n project_id: this.projectId,\n ...data,\n }),\n })\n await this.handleResponse(res)\n if (!res.ok) throw new Error('Failed to create comment')\n return res.json()\n }\n\n async updateComment(id: string, data: { resolved?: boolean; content?: string }): Promise<Comment> {\n const res = await fetch(`${this.baseUrl}/comments/${id}`, {\n method: 'PATCH',\n headers: this.getHeaders(),\n body: JSON.stringify(data),\n })\n await this.handleResponse(res)\n if (!res.ok) throw new Error('Failed to update comment')\n return res.json()\n }\n\n async deleteComment(id: string): Promise<void> {\n const res = await fetch(`${this.baseUrl}/comments/${id}`, {\n method: 'DELETE',\n headers: this.getHeaders(),\n })\n await this.handleResponse(res)\n if (!res.ok) throw new Error('Failed to delete comment')\n }\n\n async uploadScreenshot(data: string): Promise<string> {\n const res = await fetch(`${this.baseUrl}/upload`, {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify({ image: data, project_id: this.projectId }),\n })\n await this.handleResponse(res)\n if (!res.ok) throw new Error('Failed to upload screenshot')\n const { url } = await res.json()\n return url\n }\n}\n","export interface WidgetUser {\n id: string\n name: string\n email: string\n avatar_url: string | null\n}\n\ntype AuthChangeCallback = (user: WidgetUser | null) => void\n\nexport class WidgetAuth {\n private apiUrl: string\n private sessionToken: string | null = null\n private user: WidgetUser | null = null\n private onAuthChange: AuthChangeCallback | null = null\n\n constructor(apiUrl: string) {\n this.apiUrl = apiUrl\n }\n\n async init(): Promise<void> {\n const token = localStorage.getItem('tack_session')\n if (!token) return\n\n try {\n const res = await fetch(`${this.apiUrl}/auth/widget-session`, {\n headers: { Authorization: `Bearer ${token}` },\n })\n if (!res.ok) {\n localStorage.removeItem('tack_session')\n return\n }\n\n const { user } = await res.json()\n this.sessionToken = token\n this.user = {\n id: user.user_id,\n name: user.user_name,\n email: user.user_email,\n avatar_url: user.user_avatar_url,\n }\n this.onAuthChange?.(this.user)\n } catch {\n localStorage.removeItem('tack_session')\n }\n }\n\n signIn(): Promise<WidgetUser> {\n return new Promise((resolve, reject) => {\n // Derive auth base URL from API URL (e.g., https://tackapp.io/api -> https://tackapp.io)\n const baseUrl = this.apiUrl.replace(/\\/api$/, '')\n const popup = window.open(\n `${baseUrl}/auth/widget`,\n 'tack-auth',\n 'width=500,height=600,popup=yes'\n )\n\n if (!popup) {\n reject(new Error('Popup blocked'))\n return\n }\n\n const expectedOrigin = new URL(this.apiUrl).origin\n\n const handleMessage = (event: MessageEvent) => {\n if (event.data?.type !== 'tack:auth') return\n if (event.origin !== expectedOrigin) return\n\n window.removeEventListener('message', handleMessage)\n clearInterval(pollTimer)\n\n const { token, user } = event.data\n this.sessionToken = token\n this.user = {\n id: user.user_id,\n name: user.user_name,\n email: user.user_email,\n avatar_url: user.user_avatar_url,\n }\n localStorage.setItem('tack_session', token)\n this.onAuthChange?.(this.user)\n resolve(this.user)\n }\n\n window.addEventListener('message', handleMessage)\n\n // Poll in case the popup closes without posting a message\n const pollTimer = setInterval(() => {\n if (popup.closed) {\n clearInterval(pollTimer)\n window.removeEventListener('message', handleMessage)\n if (!this.user) {\n reject(new Error('Auth popup closed'))\n }\n }\n }, 500)\n })\n }\n\n signOut(): void {\n if (this.sessionToken) {\n fetch(`${this.apiUrl}/auth/widget-session`, {\n method: 'DELETE',\n headers: { Authorization: `Bearer ${this.sessionToken}` },\n }).catch(() => {})\n }\n\n this.sessionToken = null\n this.user = null\n localStorage.removeItem('tack_session')\n this.onAuthChange?.(null)\n }\n\n getUser(): WidgetUser | null {\n return this.user\n }\n\n getSessionToken(): string | null {\n return this.sessionToken\n }\n\n isAuthenticated(): boolean {\n return this.user !== null\n }\n\n setOnAuthChange(cb: AuthChangeCallback): void {\n this.onAuthChange = cb\n }\n}\n","export function createStyles(): string {\n return `\n * {\n box-sizing: border-box;\n }\n\n .tack-wrapper {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-size: 14px;\n line-height: 1.5;\n color: #18181b;\n }\n\n /* Toggle Button - Teal gradient */\n .tack-toggle {\n position: fixed;\n bottom: 20px;\n right: 20px;\n width: 52px;\n height: 52px;\n border-radius: 16px;\n background: linear-gradient(135deg, #14B8A6 0%, #0D9488 100%);\n color: white;\n border: none;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow:\n 0 4px 14px rgba(20, 184, 166, 0.4),\n 0 2px 4px rgba(0, 0, 0, 0.1);\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n z-index: 10000;\n }\n\n .tack-toggle:hover {\n transform: translateY(-2px) scale(1.02);\n box-shadow:\n 0 8px 20px rgba(20, 184, 166, 0.5),\n 0 4px 8px rgba(0, 0, 0, 0.1);\n }\n\n .tack-toggle:active {\n transform: scale(0.98);\n }\n\n .tack-active .tack-toggle {\n opacity: 0;\n pointer-events: none;\n }\n\n /* Pins container - hidden by default */\n .tack-pins-container {\n display: none;\n }\n\n .tack-pins-container.visible {\n display: block;\n }\n\n /* Comment Pin — unified white shell */\n .tack-pin {\n position: absolute;\n cursor: pointer;\n z-index: 9999;\n transition: transform 150ms ease, filter 150ms ease;\n filter: drop-shadow(0 2px 6px rgba(0,0,0,0.15)) drop-shadow(0 1px 2px rgba(0,0,0,0.10));\n }\n\n .tack-pin:hover {\n filter: drop-shadow(0 4px 12px rgba(0,0,0,0.18)) drop-shadow(0 2px 4px rgba(0,0,0,0.12));\n z-index: 10000;\n }\n\n /* White shell — the unified container (circle + ::after tail)\n Uses CSS Grid so avatar and preview stack in the same cell.\n The shell's width/max-height control which content is visible\n via overflow:hidden, enabling smooth morph animations. */\n .tack-pin-shell {\n position: relative;\n background: #FFFFFF;\n border-radius: 19px;\n padding: 3px;\n display: grid;\n align-items: start;\n width: 38px;\n max-height: 38px;\n will-change: width, max-height, border-radius;\n }\n\n /* All direct children stack in the same grid cell */\n .tack-pin-shell > * {\n grid-area: 1 / 1;\n }\n\n /* Multi-author shell becomes pill-shaped */\n .tack-pin.multi-author .tack-pin-shell {\n border-radius: 17px;\n padding: 3px;\n width: auto;\n max-height: 34px;\n }\n\n /* White tail as ::after pseudo-element — seamless with shell.\n Height is 13px (not 6px) so the top of the triangle extends behind\n the circle where it curves away from the bounding box. The opaque\n circle paints over the hidden overlap, eliminating the gap. */\n .tack-pin-shell::after {\n content: '';\n position: absolute;\n bottom: -7px;\n left: 2px;\n width: 18px;\n height: 16px;\n background: #FFFFFF;\n clip-path: polygon(25% 100%, 100% 0%, 0% 0%);\n z-index: -1;\n }\n\n /* Multi-author: tail under first avatar */\n .tack-pin.multi-author .tack-pin-shell::after {\n left: 10px;\n }\n\n /* Avatar inset — sits inside the shell with white padding visible */\n .tack-pin-avatar-img {\n border-radius: 50%;\n object-fit: cover;\n display: block;\n }\n\n .tack-pin-avatar-fallback {\n border-radius: 50%;\n color: white;\n font-weight: 600;\n display: flex;\n align-items: center;\n justify-content: center;\n line-height: 1;\n }\n\n /* Multi-author avatar stack inside the shell */\n .tack-pin-avatars {\n display: flex;\n align-items: center;\n z-index: 1;\n transition: opacity 120ms ease;\n }\n\n .tack-pin-avatar-stacked {\n width: 28px;\n height: 28px;\n border-radius: 50%;\n margin-left: -8px;\n position: relative;\n flex-shrink: 0;\n overflow: hidden;\n line-height: 1;\n border: 2px solid #FFFFFF;\n }\n\n .tack-pin-avatar-stacked:first-child {\n margin-left: 0;\n }\n\n .tack-pin-avatar-stacked img,\n .tack-pin-avatar-stacked div {\n display: block;\n }\n\n /* Overflow \"+N\" chip */\n .tack-pin-avatar-overflow {\n background: #E4E4E7;\n color: #52525B;\n font-size: 9px;\n font-weight: 600;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n /* ── Hover Preview Card ── */\n\n /* Shell transitions — collapse timing by default (faster close) */\n .tack-pin-shell {\n overflow: hidden;\n transition:\n width 160ms cubic-bezier(0.4, 0, 0.2, 1),\n max-height 160ms cubic-bezier(0.4, 0, 0.2, 1),\n border-radius 160ms cubic-bezier(0.4, 0, 0.2, 1),\n padding 160ms cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n /* Avatar: fade out on expand instead of display:none */\n .tack-pin-shell > .tack-pin-avatar-img,\n .tack-pin-shell > .tack-pin-avatar-fallback {\n z-index: 1;\n transition: opacity 120ms ease;\n }\n\n /* Expanded shell — smooth open */\n .tack-pin.expanded .tack-pin-shell {\n width: 256px;\n max-height: 130px;\n border-radius: 14px;\n padding: 3px;\n transition:\n width 240ms cubic-bezier(0.16, 1, 0.3, 1),\n max-height 240ms cubic-bezier(0.16, 1, 0.3, 1),\n border-radius 240ms cubic-bezier(0.16, 1, 0.3, 1),\n padding 240ms cubic-bezier(0.16, 1, 0.3, 1);\n }\n\n .tack-pin.expanded.multi-author .tack-pin-shell {\n border-radius: 14px;\n width: 256px;\n max-height: 130px;\n }\n\n /* Fade avatar/stacked avatars when expanded (not display:none) */\n .tack-pin.expanded .tack-pin-shell > .tack-pin-avatar-img,\n .tack-pin.expanded .tack-pin-shell > .tack-pin-avatar-fallback {\n opacity: 0;\n pointer-events: none;\n }\n\n .tack-pin.expanded .tack-pin-avatars {\n opacity: 0;\n pointer-events: none;\n }\n\n /* Preview card: always rendered, clipped by shell overflow when collapsed */\n .tack-pin-preview {\n display: flex;\n width: 240px;\n flex-direction: column;\n padding: 6px 8px 8px 8px;\n opacity: 0;\n pointer-events: none;\n align-self: start;\n justify-self: start;\n }\n\n .tack-pin.expanded .tack-pin-preview {\n opacity: 1;\n pointer-events: auto;\n transition:\n opacity 120ms cubic-bezier(0.4, 0, 0.2, 1) 100ms;\n }\n\n /* Top row: avatar + meta (name + time) */\n .tack-pin-preview-header {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .tack-pin-preview-avatar {\n width: 28px;\n height: 28px;\n border-radius: 50%;\n flex-shrink: 0;\n overflow: hidden;\n }\n\n .tack-pin-preview-avatar img,\n .tack-pin-preview-avatar div {\n display: block;\n }\n\n .tack-pin-preview-meta {\n flex: 1;\n min-width: 0;\n display: flex;\n align-items: baseline;\n gap: 4px;\n }\n\n .tack-pin-preview-name {\n font-size: 12.5px;\n font-weight: 600;\n color: #18181b;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 120px;\n }\n\n .tack-pin-preview-time {\n font-size: 11px;\n color: #a1a1aa;\n white-space: nowrap;\n flex-shrink: 0;\n }\n\n /* Comment text preview — staggered fade */\n .tack-pin-preview-text {\n font-size: 12.5px;\n line-height: 1.4;\n color: #3f3f46;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n text-overflow: ellipsis;\n word-break: break-word;\n margin: 4px 0 0 0;\n opacity: 0;\n transform: translateY(4px);\n }\n\n .tack-pin.expanded .tack-pin-preview-text {\n opacity: 1;\n transform: translateY(0);\n transition:\n opacity 120ms cubic-bezier(0.4, 0, 0.2, 1) 140ms,\n transform 120ms cubic-bezier(0.4, 0, 0.2, 1) 140ms;\n }\n\n /* Reply count badge */\n .tack-pin-preview-replies {\n font-size: 11px;\n color: #a1a1aa;\n margin-top: 4px;\n opacity: 0;\n }\n\n .tack-pin.expanded .tack-pin-preview-replies {\n opacity: 1;\n transition: opacity 100ms ease 180ms;\n }\n\n /* Expanded pin gets higher z-index */\n .tack-pin.expanded {\n z-index: 10000;\n }\n\n /* Expand-left variant (near right viewport edge) —\n shift shell leftward so the card doesn't overflow viewport */\n .tack-pin.expand-left.expanded .tack-pin-shell {\n transform: translateX(-218px);\n }\n\n /* Active pins should not show the preview (card is already open) */\n .tack-pin.active .tack-pin-preview {\n opacity: 0 !important;\n pointer-events: none !important;\n }\n\n .tack-pin.active.expanded .tack-pin-shell > .tack-pin-avatar-img,\n .tack-pin.active.expanded .tack-pin-shell > .tack-pin-avatar-fallback {\n opacity: 1;\n pointer-events: auto;\n }\n\n .tack-pin.active.expanded .tack-pin-avatars {\n opacity: 1;\n pointer-events: auto;\n }\n\n .tack-pin.active.expanded .tack-pin-shell {\n border-radius: 19px;\n width: 38px;\n max-height: 38px;\n }\n\n .tack-pin.active.expanded.multi-author .tack-pin-shell {\n border-radius: 17px;\n width: auto;\n max-height: 34px;\n }\n\n /* On touch devices, suppress hover preview entirely */\n @media (hover: none) {\n .tack-pin.expanded .tack-pin-preview {\n opacity: 0;\n pointer-events: none;\n }\n\n .tack-pin.expanded .tack-pin-shell > .tack-pin-avatar-img,\n .tack-pin.expanded .tack-pin-shell > .tack-pin-avatar-fallback {\n opacity: 1;\n pointer-events: auto;\n }\n\n .tack-pin.expanded .tack-pin-avatars {\n opacity: 1;\n pointer-events: auto;\n }\n\n .tack-pin.expanded .tack-pin-shell {\n border-radius: 19px;\n width: 38px;\n max-height: 38px;\n }\n\n .tack-pin.expanded.multi-author .tack-pin-shell {\n border-radius: 17px;\n width: auto;\n max-height: 34px;\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .tack-pin-shell,\n .tack-pin.expanded .tack-pin-shell,\n .tack-pin-preview,\n .tack-pin.expanded .tack-pin-preview,\n .tack-pin-preview-text,\n .tack-pin.expanded .tack-pin-preview-text {\n transition-duration: 0ms !important;\n transition-delay: 0ms !important;\n }\n }\n\n /* Resolved pin: green check dot */\n .tack-pin-check {\n position: absolute;\n bottom: 8px;\n right: -2px;\n width: 14px;\n height: 14px;\n border-radius: 50%;\n background: #10B981;\n border: 2px solid white;\n z-index: 10;\n }\n\n /* Resolved: fade entire pin */\n .tack-pin.resolved {\n opacity: 0.5;\n }\n\n .tack-pin.resolved:hover {\n opacity: 0.85;\n }\n\n /* Active / selected pin — solid blue ring on shell */\n .tack-pin.active {\n transform: scale(1.05);\n }\n\n .tack-pin.active .tack-pin-shell {\n box-shadow: 0 0 0 2.5px #2563EB;\n }\n\n /* Highlighted — finite pulse, 2 cycles */\n .tack-pin.highlighted {\n animation: tack-pulse 1.5s ease-in-out 2;\n }\n\n @keyframes tack-pulse {\n 0%, 100% {\n filter: drop-shadow(0 2px 6px rgba(0,0,0,0.15)) drop-shadow(0 1px 2px rgba(0,0,0,0.10));\n }\n 50% {\n filter: drop-shadow(0 0 6px rgba(37,99,235,0.3)) drop-shadow(0 2px 8px rgba(0,0,0,0.10));\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .tack-pin.highlighted {\n animation: none;\n filter: drop-shadow(0 0 4px rgba(37,99,235,0.25)) drop-shadow(0 2px 8px rgba(0,0,0,0.10));\n }\n }\n\n /* Panel Strip */\n .tack-panel-strip {\n position: fixed;\n top: 0;\n right: 0;\n width: 360px;\n height: 100vh;\n background: transparent;\n padding: 0;\n transform: translateX(100%);\n transition: transform 250ms cubic-bezier(0.16, 1, 0.3, 1);\n z-index: 10001;\n display: flex;\n flex-direction: column;\n }\n\n .tack-panel-strip.open {\n transform: translateX(0);\n }\n\n /* Panel Card */\n .tack-panel {\n flex: 1;\n background: #FFFFFF;\n border-radius: 0;\n border: none;\n box-shadow:\n 0 0 0 1px rgba(0, 0, 0, 0.04),\n 0 2px 4px rgba(0, 0, 0, 0.04),\n 0 8px 24px rgba(0, 0, 0, 0.08);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .tack-panel-header {\n padding: 12px 16px;\n border-bottom: 1px solid rgba(0, 0, 0, 0.06);\n display: flex;\n align-items: center;\n gap: 10px;\n }\n\n .tack-panel-close {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border-radius: 6px;\n background: transparent;\n border: none;\n cursor: pointer;\n color: #71717a;\n flex-shrink: 0;\n transition: all 0.15s ease;\n }\n\n .tack-panel-close:hover {\n background: #f4f4f5;\n color: #18181b;\n }\n\n /* Search bar */\n .tack-panel-search-wrap {\n flex: 1;\n display: flex;\n align-items: center;\n gap: 6px;\n background: #f4f4f5;\n border-radius: 6px;\n padding: 0 8px;\n min-height: 32px;\n min-width: 0;\n }\n\n .tack-panel-search-icon {\n flex-shrink: 0;\n color: #a1a1aa;\n }\n\n .tack-panel-search {\n flex: 1;\n border: none;\n background: transparent;\n font-size: 13px;\n font-family: inherit;\n color: #18181b;\n outline: none;\n min-width: 0;\n padding: 0;\n line-height: 32px;\n }\n\n .tack-panel-search::placeholder {\n color: #a1a1aa;\n }\n\n .tack-panel-search-clear {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n border-radius: 4px;\n border: none;\n background: transparent;\n cursor: pointer;\n color: #a1a1aa;\n flex-shrink: 0;\n padding: 0;\n transition: all 0.1s ease;\n }\n\n .tack-panel-search-clear:hover {\n background: rgba(0, 0, 0, 0.08);\n color: #71717a;\n }\n\n /* Icon buttons (filter + more) */\n .tack-panel-filter-wrap,\n .tack-panel-more-wrap {\n position: relative;\n flex-shrink: 0;\n }\n\n .tack-panel-filter-btn,\n .tack-panel-more-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n border-radius: 6px;\n background: transparent;\n border: none;\n cursor: pointer;\n color: #71717a;\n position: relative;\n transition: all 0.15s ease;\n padding: 0;\n }\n\n .tack-panel-filter-btn:hover,\n .tack-panel-more-btn:hover {\n background: #f4f4f5;\n color: #18181b;\n }\n\n .tack-panel-filter-btn.filter-active {\n color: #2563EB;\n }\n\n .tack-panel-filter-btn.filter-active:hover {\n color: #1d4ed8;\n }\n\n /* Filter badge (count) */\n .tack-panel-filter-badge {\n position: absolute;\n top: 2px;\n right: 2px;\n min-width: 16px;\n height: 16px;\n border-radius: 8px;\n background: #2563EB;\n color: white;\n font-size: 10px;\n font-weight: 600;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0 4px;\n line-height: 1;\n }\n\n /* Dark dropdown menu */\n .tack-panel-filter-dropdown,\n .tack-panel-more-dropdown {\n position: absolute;\n top: calc(100% + 6px);\n right: 0;\n background: #1e1e1e;\n border-radius: 10px;\n box-shadow: 0 8px 30px rgba(0, 0, 0, 0.25), 0 0 0 1px rgba(255, 255, 255, 0.06);\n z-index: 100;\n min-width: 220px;\n padding: 4px;\n overflow: hidden;\n }\n\n .tack-panel-filter-dropdown[data-state=\"closed\"],\n .tack-panel-more-dropdown[data-state=\"closed\"] {\n display: none;\n }\n\n .tack-panel-filter-dropdown[data-state=\"open\"],\n .tack-panel-more-dropdown[data-state=\"open\"] {\n display: block;\n animation: tack-dropdown-in 0.12s ease;\n }\n\n @keyframes tack-dropdown-in {\n from {\n opacity: 0;\n transform: translateY(-4px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n }\n\n .tack-dropdown-item {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n padding: 8px 10px;\n font-size: 13px;\n font-family: inherit;\n background: transparent;\n border: none;\n cursor: pointer;\n text-align: left;\n color: #e4e4e7;\n border-radius: 6px;\n transition: background 0.1s ease;\n }\n\n .tack-dropdown-item:hover {\n background: rgba(255, 255, 255, 0.08);\n }\n\n .tack-dropdown-check {\n flex-shrink: 0;\n color: #a1a1aa;\n }\n\n .tack-dropdown-check-space {\n display: inline-block;\n width: 14px;\n flex-shrink: 0;\n }\n\n .tack-dropdown-sep {\n height: 1px;\n background: rgba(255, 255, 255, 0.08);\n margin: 4px 10px;\n }\n\n /* Toggle items */\n .tack-dropdown-toggle {\n justify-content: space-between;\n }\n\n .tack-dropdown-toggle-switch {\n width: 32px;\n height: 18px;\n border-radius: 9px;\n background: rgba(255, 255, 255, 0.15);\n position: relative;\n flex-shrink: 0;\n transition: background 0.15s ease;\n }\n\n .tack-dropdown-toggle-switch.on {\n background: #2563EB;\n }\n\n .tack-dropdown-toggle-knob {\n position: absolute;\n top: 2px;\n left: 2px;\n width: 14px;\n height: 14px;\n border-radius: 50%;\n background: white;\n transition: transform 0.15s ease;\n }\n\n .tack-dropdown-toggle-switch.on .tack-dropdown-toggle-knob {\n transform: translateX(14px);\n }\n\n /* Unread dot */\n .tack-unread-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: #2563EB;\n flex-shrink: 0;\n }\n\n .tack-comment-row-top {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 6px;\n }\n\n /* Empty state reset button */\n .tack-empty-reset {\n display: inline-block;\n margin-top: 12px;\n padding: 6px 14px;\n font-size: 12px;\n font-weight: 500;\n font-family: inherit;\n border: 1px solid #e4e4e7;\n border-radius: 6px;\n background: white;\n color: #71717a;\n cursor: pointer;\n transition: all 0.15s ease;\n }\n\n .tack-empty-reset:hover {\n background: #f4f4f5;\n color: #18181b;\n border-color: #d4d4d8;\n }\n\n .tack-panel-content {\n flex: 1;\n overflow-y: auto;\n padding: 6px 0;\n mask-image: linear-gradient(\n to bottom,\n transparent 0%,\n black 8px,\n black calc(100% - 8px),\n transparent 100%\n );\n }\n\n /* Comment Row — ghost card on hover */\n .tack-comment-row {\n padding: 14px 16px;\n margin: 0 6px;\n border-radius: 8px;\n cursor: pointer;\n transition: background-color 150ms ease-out;\n position: relative;\n }\n\n .tack-comment-row:hover {\n background-color: rgba(0, 0, 0, 0.04);\n }\n\n /* Row action buttons (overflow + resolve) */\n .tack-comment-row-actions {\n position: absolute;\n top: 10px;\n right: 10px;\n display: flex;\n align-items: center;\n gap: 2px;\n opacity: 0;\n transition: opacity 120ms ease;\n }\n\n .tack-comment-row:hover .tack-comment-row-actions {\n opacity: 1;\n }\n\n .tack-comment-row-more,\n .tack-comment-row-resolve {\n width: 28px;\n height: 28px;\n border-radius: 6px;\n border: none;\n background: transparent;\n cursor: pointer;\n color: #a1a1aa;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n transition: all 120ms ease;\n }\n\n .tack-comment-row-more:hover,\n .tack-comment-row-resolve:hover {\n background: rgba(0, 0, 0, 0.06);\n color: #18181b;\n }\n\n .tack-comment-row-resolve.resolved {\n color: #10B981;\n }\n\n .tack-comment-row-resolve.resolved:hover {\n background: rgba(16, 185, 129, 0.1);\n color: #059669;\n }\n\n .tack-comment-row:active {\n background-color: rgba(0, 0, 0, 0.06);\n transition-duration: 50ms;\n }\n\n .tack-comment-row.highlighted {\n background-color: rgba(20, 184, 166, 0.06);\n }\n\n .tack-comment-row.highlighted::before {\n content: '';\n position: absolute;\n left: 0;\n top: 6px;\n bottom: 6px;\n width: 3px;\n border-radius: 0 3px 3px 0;\n background: #14B8A6;\n }\n\n .tack-comment-avatar {\n width: 32px;\n height: 32px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n color: white;\n font-size: 12px;\n font-weight: 600;\n flex-shrink: 0;\n line-height: 1;\n margin-bottom: 8px;\n }\n\n .tack-comment-meta {\n display: flex;\n align-items: baseline;\n gap: 4px;\n flex-wrap: wrap;\n margin-bottom: 4px;\n }\n\n .tack-comment-author {\n font-weight: 600;\n font-size: 13px;\n color: #18181b;\n }\n\n .tack-comment-time {\n font-size: 12px;\n color: rgba(0, 0, 0, 0.35);\n font-weight: 400;\n }\n\n .tack-comment-resolved-badge {\n font-size: 11px;\n font-weight: 500;\n padding: 1px 6px;\n border-radius: 9999px;\n background: #ECFDF5;\n color: #059669;\n line-height: 1.4;\n }\n\n .tack-comment-page-badge {\n font-size: 11px;\n font-weight: 500;\n padding: 1px 6px;\n border-radius: 9999px;\n background: #EEF2FF;\n color: #4F46E5;\n line-height: 1.4;\n }\n\n .tack-comment-body {\n padding-left: 0;\n margin-top: 2px;\n }\n\n .tack-comment-body.dimmed {\n opacity: 0.6;\n }\n\n .tack-comment-screenshot {\n width: 120px;\n height: 64px;\n object-fit: cover;\n border-radius: 6px;\n margin-bottom: 6px;\n border: 1px solid #e4e4e7;\n display: block;\n }\n\n .tack-comment-content {\n font-size: 13.5px;\n color: #3f3f46;\n line-height: 1.5;\n }\n\n /* New Comment Input */\n .tack-new-comment {\n padding: 10px 12px;\n border-top: 1px solid rgba(0, 0, 0, 0.06);\n }\n\n .tack-new-comment-input {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 12px;\n background: #f4f4f5;\n border: 1px dashed #d4d4d8;\n border-radius: 8px;\n font-size: 13px;\n color: #71717a;\n cursor: pointer;\n transition: all 0.15s ease;\n font-weight: 500;\n }\n\n .tack-new-comment-input:hover {\n background: #e4e4e7;\n border-color: #a1a1aa;\n color: #18181b;\n }\n\n /* Comment Form - Floating pill input */\n .tack-form {\n position: fixed;\n z-index: 10003;\n display: none;\n }\n\n .tack-form.visible {\n display: block;\n animation: tack-form-in 0.15s ease;\n }\n\n @keyframes tack-form-in {\n from {\n opacity: 0;\n transform: translateY(4px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n }\n\n /* Pill container */\n .tack-form-pill {\n display: flex;\n align-items: flex-end;\n gap: 6px;\n background: #FFFFFF;\n border: 1px solid #D1D5DB;\n border-radius: 20px;\n padding: 4px 4px 4px 16px;\n width: 300px;\n box-shadow: 0 4px 12px rgba(0,0,0,0.08), 0 1px 3px rgba(0,0,0,0.06);\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n }\n\n .tack-form-pill:focus-within {\n border-color: #9CA3AF;\n box-shadow: 0 4px 12px rgba(0,0,0,0.10), 0 1px 3px rgba(0,0,0,0.06);\n }\n\n /* Textarea styled as single-line input */\n .tack-form-pill-input {\n flex: 1;\n border: none;\n background: transparent;\n font-size: 13px;\n font-family: inherit;\n color: #111827;\n outline: none;\n resize: none;\n min-width: 0;\n padding: 4px 0;\n line-height: 20px;\n height: 28px;\n overflow-y: hidden;\n min-height: 28px;\n }\n\n .tack-form-pill-input::placeholder {\n color: #9CA3AF;\n }\n\n /* Send button */\n .tack-form-pill-send {\n width: 28px;\n height: 28px;\n border-radius: 50%;\n border: none;\n background: #E5E7EB;\n color: #9CA3AF;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n transition: background-color 150ms ease, color 150ms ease;\n }\n\n .tack-form-pill-send.active {\n background: #18181B;\n color: #FFFFFF;\n }\n\n .tack-form-pill-send.active:hover {\n background: #27272A;\n }\n\n .tack-form-pill-send.sending {\n opacity: 0.5;\n pointer-events: none;\n }\n\n /* Empty State */\n .tack-empty {\n text-align: center;\n padding: 40px 20px;\n }\n\n .tack-empty-icon {\n margin-bottom: 12px;\n margin-left: auto;\n margin-right: auto;\n color: #a1a1aa;\n }\n\n .tack-empty-title {\n font-size: 14px;\n font-weight: 500;\n color: #18181b;\n margin: 0 0 4px 0;\n }\n\n .tack-empty-subtitle {\n font-size: 12px;\n color: #a1a1aa;\n margin: 0;\n }\n\n /* Comment Card Popover */\n .tack-card {\n position: fixed;\n width: 320px;\n background: white;\n border-radius: 12px;\n border: 1px solid #e4e4e7;\n box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12), 0 0 0 1px rgba(0, 0, 0, 0.04);\n z-index: 10003;\n max-height: 480px;\n overflow-y: auto;\n display: none;\n flex-direction: column;\n }\n\n .tack-card.visible {\n display: flex;\n animation: tack-form-in 0.15s ease;\n }\n\n /* Card title bar */\n .tack-card-title-bar {\n display: flex;\n align-items: center;\n padding: 10px 12px;\n border-bottom: 1px solid #F4F4F5;\n flex-shrink: 0;\n }\n\n .tack-card-title {\n flex: 1;\n font-weight: 600;\n font-size: 13px;\n color: #18181b;\n }\n\n .tack-card-title-actions {\n display: flex;\n align-items: center;\n gap: 2px;\n }\n\n .tack-card-resolve-icon,\n .tack-card-close-btn {\n width: 28px;\n height: 28px;\n border-radius: 6px;\n border: none;\n background: transparent;\n cursor: pointer;\n color: #A1A1AA;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.15s ease;\n }\n\n .tack-card-resolve-icon:hover,\n .tack-card-close-btn:hover {\n background: #F4F4F5;\n color: #18181b;\n }\n\n .tack-card-resolve-icon.resolved {\n color: #10B981;\n }\n\n /* Resolved banner */\n .tack-card-resolved-banner {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 12px;\n background: #ECFDF5;\n color: #059669;\n font-size: 12px;\n font-weight: 500;\n flex-shrink: 0;\n }\n\n /* Card content area (scrollable) */\n .tack-card-row {\n padding: 12px;\n position: relative;\n }\n\n .tack-card-row:hover .tack-card-more-btn {\n opacity: 1;\n }\n\n .tack-card-row-header {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .tack-card-row-avatar {\n width: 28px;\n height: 28px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n color: white;\n font-size: 10px;\n font-weight: 600;\n flex-shrink: 0;\n line-height: 1;\n }\n\n .tack-card-row-meta {\n flex: 1;\n min-width: 0;\n display: flex;\n align-items: center;\n gap: 4px;\n flex-wrap: wrap;\n }\n\n .tack-card-row-author {\n font-weight: 600;\n font-size: 13px;\n color: #18181b;\n }\n\n .tack-card-row-time {\n font-size: 12px;\n color: #A1A1AA;\n }\n\n .tack-card-editing-badge {\n font-size: 11px;\n font-weight: 600;\n color: #6366F1;\n }\n\n /* More (three-dot) button */\n .tack-card-more-btn {\n width: 24px;\n height: 24px;\n border-radius: 6px;\n border: none;\n background: #F4F4F5;\n cursor: pointer;\n color: #71717a;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n opacity: 0;\n transition: opacity 0.15s ease, background 0.15s ease;\n }\n\n .tack-card-more-btn:hover {\n background: #E4E4E7;\n color: #18181b;\n }\n\n /* Dropdown menu */\n .tack-card-dropdown {\n position: absolute;\n top: 42px;\n right: 12px;\n background: white;\n border: 1px solid #E4E4E7;\n border-radius: 8px;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);\n z-index: 10;\n overflow: hidden;\n min-width: 140px;\n }\n\n .tack-card-dropdown-item {\n display: block;\n width: 100%;\n padding: 8px 12px;\n font-size: 13px;\n font-family: inherit;\n background: transparent;\n border: none;\n cursor: pointer;\n text-align: left;\n color: #18181b;\n transition: background 0.1s ease;\n }\n\n .tack-card-dropdown-item:hover {\n background: #F4F4F5;\n }\n\n .tack-card-dropdown-item.delete {\n color: #EF4444;\n }\n\n .tack-card-dropdown-item.delete:hover {\n background: #FEF2F2;\n }\n\n /* Comment body text */\n .tack-card-row-body {\n padding: 6px 0 0 36px;\n font-size: 13px;\n color: #3F3F46;\n line-height: 1.5;\n }\n\n .tack-card-row-screenshot {\n width: 200px;\n border-radius: 6px;\n object-fit: cover;\n border: 1px solid #e4e4e7;\n display: block;\n margin: 8px 0 0 36px;\n }\n\n /* Divider */\n .tack-card-divider {\n height: 1px;\n background: #F4F4F5;\n margin: 0;\n }\n\n /* Edit mode */\n .tack-card-edit-wrap {\n padding: 8px 0 0 36px;\n }\n\n .tack-card-edit-textarea {\n width: 100%;\n padding: 8px 10px;\n border: 1px solid #18181B;\n border-radius: 6px;\n font-size: 13px;\n font-family: inherit;\n resize: vertical;\n min-height: 60px;\n line-height: 1.5;\n color: #18181b;\n background: white;\n }\n\n .tack-card-edit-textarea:focus {\n outline: none;\n }\n\n .tack-card-edit-actions {\n display: flex;\n justify-content: flex-end;\n gap: 6px;\n margin-top: 8px;\n }\n\n .tack-card-edit-cancel {\n padding: 5px 12px;\n font-size: 12px;\n font-weight: 500;\n border: 1px solid #E4E4E7;\n border-radius: 6px;\n background: white;\n cursor: pointer;\n color: #71717a;\n font-family: inherit;\n transition: all 0.15s ease;\n }\n\n .tack-card-edit-cancel:hover {\n background: #F4F4F5;\n color: #18181b;\n }\n\n .tack-card-edit-save {\n padding: 5px 12px;\n font-size: 12px;\n font-weight: 500;\n border: none;\n border-radius: 6px;\n background: #18181B;\n color: white;\n cursor: pointer;\n font-family: inherit;\n transition: all 0.15s ease;\n }\n\n .tack-card-edit-save:hover {\n background: #27272a;\n }\n\n /* Reply bar — always visible */\n .tack-card-reply-bar {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 12px;\n flex-shrink: 0;\n }\n\n .tack-card-reply-bar-avatar {\n width: 24px;\n height: 24px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n color: white;\n font-size: 9px;\n font-weight: 600;\n flex-shrink: 0;\n line-height: 1;\n }\n\n .tack-card-reply-bar-input-wrap {\n flex: 1;\n display: flex;\n align-items: center;\n background: #F4F4F5;\n border-radius: 9999px;\n padding: 0 4px 0 12px;\n min-height: 32px;\n }\n\n .tack-card-reply-bar-input {\n flex: 1;\n border: none;\n background: transparent;\n font-size: 13px;\n font-family: inherit;\n color: #18181b;\n outline: none;\n min-width: 0;\n }\n\n .tack-card-reply-bar-input::placeholder {\n color: #A1A1AA;\n }\n\n .tack-card-reply-bar-send {\n width: 24px;\n height: 24px;\n border-radius: 50%;\n border: none;\n background: #18181B;\n color: white;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n transition: background 0.15s ease;\n }\n\n .tack-card-reply-bar-send:hover {\n background: #27272a;\n }\n\n /* Sign-in pill variant */\n .tack-form-pill-signin {\n align-items: center;\n padding: 6px 6px 6px 16px;\n cursor: default;\n }\n\n .tack-sign-in-prompt-text {\n flex: 1;\n font-size: 13px;\n color: #9CA3AF;\n margin: 0;\n line-height: 20px;\n }\n\n .tack-sign-in-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 12px;\n border: none;\n border-radius: 14px;\n background: #F4F4F5;\n cursor: pointer;\n font-size: 12px;\n font-weight: 500;\n color: #3c4043;\n font-family: inherit;\n transition: all 0.15s ease;\n flex-shrink: 0;\n white-space: nowrap;\n }\n\n .tack-sign-in-btn:hover {\n background: #E5E7EB;\n color: #18181b;\n }\n\n .tack-sign-in-btn:active {\n background: #D1D5DB;\n }\n\n /* Avatar image support */\n .tack-avatar-img {\n flex-shrink: 0;\n }\n\n .tack-avatar-fallback {\n flex-shrink: 0;\n }\n\n .tack-card-row-avatar-wrap,\n .tack-card-reply-bar-avatar-wrap {\n display: flex;\n flex-shrink: 0;\n }\n\n /* Presence bar */\n .tack-presence-bar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 16px;\n border-bottom: 1px solid rgba(0, 0, 0, 0.06);\n }\n\n .tack-presence-avatars {\n display: flex;\n align-items: center;\n }\n\n .tack-presence-avatar {\n margin-left: -6px;\n border-radius: 50%;\n border: 2px solid white;\n display: flex;\n line-height: 1;\n }\n\n .tack-presence-avatar:first-child {\n margin-left: 0;\n }\n\n .tack-presence-overflow {\n margin-left: -6px;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n background: #e4e4e7;\n color: #52525b;\n font-size: 10px;\n font-weight: 600;\n display: flex;\n align-items: center;\n justify-content: center;\n border: 2px solid white;\n }\n\n .tack-presence-label {\n font-size: 12px;\n color: #a1a1aa;\n font-weight: 500;\n }\n\n /* Toast */\n .tack-toast {\n animation: tack-toast-in 0.2s ease;\n }\n\n @keyframes tack-toast-in {\n from {\n opacity: 0;\n transform: translateY(8px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n }\n `\n}\n","import type { CommentAnchor, TextQuoteSelector } from './types'\n\ntype SelectionCallback = (target: {\n element: Element | null\n anchor: CommentAnchor\n}) => void\n\nexport class ElementSelector {\n private callback: SelectionCallback\n private enabled = false\n private highlightedElement: Element | null = null\n private highlight: HTMLDivElement | null = null\n private scrim: HTMLDivElement | null = null\n private cursor: HTMLDivElement | null = null\n private styleElement: HTMLStyleElement | null = null\n private cursorRAF: number | null = null\n private cursorX = 0\n private cursorY = 0\n private targetX = 0\n private targetY = 0\n private settleTimer: ReturnType<typeof setTimeout> | null = null\n private pendingTarget: Element | null = null\n\n constructor(callback: SelectionCallback) {\n this.callback = callback\n this.onMouseMove = this.onMouseMove.bind(this)\n this.onMouseDown = this.onMouseDown.bind(this)\n this.onClick = this.onClick.bind(this)\n this.onKeyDown = this.onKeyDown.bind(this)\n this.animateCursor = this.animateCursor.bind(this)\n }\n\n enable(): void {\n if (this.enabled) return\n this.enabled = true\n document.addEventListener('mousemove', this.onMouseMove)\n document.addEventListener('mousedown', this.onMouseDown, true)\n document.addEventListener('click', this.onClick, true)\n document.addEventListener('keydown', this.onKeyDown)\n\n // Page scrim — very subtle dim\n this.scrim = document.createElement('div')\n this.scrim.style.cssText = `\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.03);\n z-index: 9997;\n pointer-events: none;\n transition: opacity 0.2s ease;\n `\n document.body.appendChild(this.scrim)\n\n // Highlight ring — soft shadow, no hard border\n this.highlight = document.createElement('div')\n this.highlight.style.cssText = `\n position: fixed;\n pointer-events: none;\n z-index: 9998;\n opacity: 0;\n transition: opacity 0.2s ease, left 0.15s ease-out, top 0.15s ease-out, width 0.15s ease-out, height 0.15s ease-out, border-radius 0.15s ease-out;\n border-radius: 6px;\n box-shadow: 0 0 0 1.5px rgba(0, 0, 0, 0.08), 0 2px 12px rgba(0, 0, 0, 0.06);\n background: rgba(255, 255, 255, 0.4);\n `\n document.body.appendChild(this.highlight)\n\n // Custom cursor — small circle with +\n this.cursor = document.createElement('div')\n this.cursor.style.cssText = `\n position: fixed;\n pointer-events: none;\n z-index: 10004;\n width: 28px;\n height: 28px;\n border-radius: 50%;\n background: #18181B;\n color: white;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 16px;\n font-weight: 300;\n font-family: -apple-system, BlinkMacSystemFont, sans-serif;\n line-height: 1;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);\n transform: translate(-50%, -50%);\n opacity: 0;\n transition: opacity 0.15s ease;\n `\n this.cursor.textContent = '+'\n document.body.appendChild(this.cursor)\n\n // Fade cursor in after a frame\n requestAnimationFrame(() => {\n if (this.cursor) this.cursor.style.opacity = '1'\n })\n\n // Hide the real cursor (except on the tack widget itself)\n this.styleElement = document.createElement('style')\n this.styleElement.textContent = `\n body, body *:not(#tack-widget) { cursor: none !important; }\n #tack-widget, #tack-widget * { cursor: default !important; }\n `\n document.head.appendChild(this.styleElement)\n\n this.cursorRAF = requestAnimationFrame(this.animateCursor)\n }\n\n disable(): void {\n if (!this.enabled) return\n this.enabled = false\n document.removeEventListener('mousemove', this.onMouseMove)\n document.removeEventListener('mousedown', this.onMouseDown, true)\n document.removeEventListener('click', this.onClick, true)\n document.removeEventListener('keydown', this.onKeyDown)\n\n if (this.settleTimer) { clearTimeout(this.settleTimer); this.settleTimer = null }\n if (this.highlight) { this.highlight.remove(); this.highlight = null }\n if (this.scrim) { this.scrim.remove(); this.scrim = null }\n if (this.cursor) { this.cursor.remove(); this.cursor = null }\n if (this.styleElement) { this.styleElement.remove(); this.styleElement = null }\n if (this.cursorRAF) { cancelAnimationFrame(this.cursorRAF); this.cursorRAF = null }\n this.highlightedElement = null\n this.pendingTarget = null\n }\n\n private animateCursor(): void {\n // Smooth follow with lerp — gentle so it feels unhurried\n this.cursorX += (this.targetX - this.cursorX) * 0.2\n this.cursorY += (this.targetY - this.cursorY) * 0.2\n if (this.cursor) {\n this.cursor.style.left = `${this.cursorX}px`\n this.cursor.style.top = `${this.cursorY}px`\n }\n this.cursorRAF = requestAnimationFrame(this.animateCursor)\n }\n\n private onMouseDown(e: MouseEvent): void {\n if ((e.target as Element).closest('#tack-widget')) {\n return\n }\n e.preventDefault()\n e.stopPropagation()\n }\n\n private onMouseMove(e: MouseEvent): void {\n this.targetX = e.clientX\n this.targetY = e.clientY\n\n const target = e.target as Element\n\n if (target.closest('#tack-widget')) {\n this.clearHighlight()\n if (this.cursor) this.cursor.style.opacity = '0'\n return\n }\n\n // Restore custom cursor when back on the page\n if (this.cursor && this.cursor.style.opacity === '0') {\n this.cursor.style.opacity = '1'\n }\n\n if (target !== this.pendingTarget && target !== this.highlightedElement) {\n // New element — start settle timer\n this.pendingTarget = target\n if (this.settleTimer) clearTimeout(this.settleTimer)\n\n this.settleTimer = setTimeout(() => {\n if (this.pendingTarget && this.pendingTarget !== this.highlightedElement) {\n this.showHighlight(this.pendingTarget)\n }\n this.pendingTarget = null\n }, 150)\n }\n }\n\n private showHighlight(target: Element): void {\n this.highlightedElement = target\n\n if (this.highlight) {\n const rect = target.getBoundingClientRect()\n const pad = 3\n this.highlight.style.left = `${rect.left - pad}px`\n this.highlight.style.top = `${rect.top - pad}px`\n this.highlight.style.width = `${rect.width + pad * 2}px`\n this.highlight.style.height = `${rect.height + pad * 2}px`\n this.highlight.style.opacity = '1'\n\n const computed = window.getComputedStyle(target)\n const radius = computed.borderRadius\n this.highlight.style.borderRadius = radius && radius !== '0px' ? radius : '6px'\n }\n }\n\n private onClick(e: MouseEvent): void {\n if ((e.target as Element).closest('#tack-widget')) {\n return\n }\n\n e.preventDefault()\n e.stopPropagation()\n\n const target = e.target as Element\n const rect = target.getBoundingClientRect()\n\n const relativeX = (e.clientX - rect.left) / rect.width\n const relativeY = (e.clientY - rect.top) / rect.height\n\n const anchor = this.generateAnchor(target, relativeX, relativeY)\n\n this.callback({ element: target, anchor })\n this.clearHighlight()\n }\n\n private onKeyDown(e: KeyboardEvent): void {\n if (e.key === 'Escape') {\n this.disable()\n }\n }\n\n private clearHighlight(): void {\n this.highlightedElement = null\n this.pendingTarget = null\n if (this.settleTimer) { clearTimeout(this.settleTimer); this.settleTimer = null }\n if (this.highlight) {\n this.highlight.style.opacity = '0'\n }\n }\n\n private generateAnchor(element: Element, relativeX: number, relativeY: number): CommentAnchor {\n return {\n cssSelector: this.generateCssSelector(element),\n xpath: this.generateXPath(element),\n textQuote: this.generateTextQuote(element),\n contentHash: this.generateContentHash(element),\n relativeX,\n relativeY,\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n }\n }\n\n private generateCssSelector(element: Element): string {\n // Try ID first\n if (element.id && !this.isDynamicId(element.id)) {\n return `#${CSS.escape(element.id)}`\n }\n\n // Build path from element to a unique ancestor\n const path: string[] = []\n let current: Element | null = element\n\n while (current && current !== document.body) {\n let selector = current.tagName.toLowerCase()\n\n // Add stable classes (filter out dynamic ones)\n if (current.className && typeof current.className === 'string') {\n const classes = current.className\n .trim()\n .split(/\\s+/)\n .filter(c => !this.isDynamicClass(c))\n if (classes.length > 0) {\n selector += '.' + classes.slice(0, 2).map(c => CSS.escape(c)).join('.')\n }\n }\n\n // Add data attributes for stability\n const dataTestId = current.getAttribute('data-testid') || current.getAttribute('data-test-id')\n if (dataTestId) {\n selector += `[data-testid=\"${CSS.escape(dataTestId)}\"]`\n }\n\n // Add nth-child if needed for uniqueness\n const parent = current.parentElement\n if (parent) {\n const siblings = Array.from(parent.children).filter(\n (child) => child.tagName === current!.tagName\n )\n if (siblings.length > 1) {\n const index = siblings.indexOf(current) + 1\n selector += `:nth-child(${index})`\n }\n }\n\n path.unshift(selector)\n\n // Check if current path is unique\n const fullSelector = path.join(' > ')\n try {\n if (document.querySelectorAll(fullSelector).length === 1) {\n return fullSelector\n }\n } catch {\n // Invalid selector, continue building path\n }\n\n current = parent\n }\n\n return path.join(' > ')\n }\n\n private generateXPath(element: Element): string {\n const parts: string[] = []\n let current: Element | null = element\n\n while (current && current !== document.body) {\n let part = current.tagName.toLowerCase()\n\n // Add index among same-tag siblings\n const parent = current.parentElement\n if (parent) {\n const siblings = Array.from(parent.children).filter(\n (child) => child.tagName === current!.tagName\n )\n if (siblings.length > 1) {\n const index = siblings.indexOf(current) + 1\n part += `[${index}]`\n }\n }\n\n parts.unshift(part)\n current = parent\n }\n\n return '//' + parts.join('/')\n }\n\n private generateTextQuote(element: Element): TextQuoteSelector | null {\n const text = element.textContent?.trim()\n if (!text || text.length === 0 || text.length > 500) {\n return null\n }\n\n // Get surrounding context\n const parent = element.parentElement\n let prefix = ''\n let suffix = ''\n\n if (parent) {\n const parentText = parent.textContent || ''\n const index = parentText.indexOf(text)\n\n if (index > 0) {\n prefix = parentText.slice(Math.max(0, index - 32), index).trim()\n }\n\n const endIndex = index + text.length\n if (endIndex < parentText.length) {\n suffix = parentText.slice(endIndex, endIndex + 32).trim()\n }\n }\n\n return {\n prefix,\n exact: text.slice(0, 200), // Limit exact match length\n suffix,\n }\n }\n\n private generateContentHash(element: Element): string {\n // Simple hash of text content + tag + key attributes\n const text = element.textContent?.trim().slice(0, 500) || ''\n const tag = element.tagName.toLowerCase()\n const className = element.className?.toString() || ''\n const content = `${tag}:${className}:${text}`\n\n // Simple hash function (djb2)\n let hash = 5381\n for (let i = 0; i < content.length; i++) {\n hash = ((hash << 5) + hash) + content.charCodeAt(i)\n }\n return (hash >>> 0).toString(16)\n }\n\n private isDynamicId(id: string): boolean {\n // Filter out IDs that look auto-generated\n return /^[:_]/.test(id) ||\n /[0-9]{5,}/.test(id) ||\n /^(ember|react|vue|ng-|_)[0-9]+/.test(id) ||\n /^[a-f0-9]{8,}$/i.test(id)\n }\n\n private isDynamicClass(className: string): boolean {\n // Filter out classes that look auto-generated (CSS-in-JS, etc.)\n return className.startsWith('tack-') ||\n /^css-[a-z0-9]+$/i.test(className) ||\n /^sc-[a-z]+$/i.test(className) ||\n /^emotion-[0-9]+$/i.test(className) ||\n /^_[a-zA-Z0-9]{5,}$/.test(className) ||\n /^[a-z]{1,3}[A-Z][a-zA-Z0-9]{10,}$/.test(className)\n }\n}\n","function ignoreTackElements(el: Element): boolean {\n return el.id === 'tack-widget' || el.classList?.contains('tack-form-highlight')\n}\n\nexport async function captureScreenshot(element?: Element | null): Promise<string | undefined> {\n const html2canvas = (await import('html2canvas')).default\n try {\n const target = element || document.body\n\n const canvas = await html2canvas(target as HTMLElement, {\n logging: false,\n useCORS: true,\n allowTaint: true,\n scale: 1,\n ignoreElements: ignoreTackElements,\n width: Math.min(target.scrollWidth, 800),\n height: Math.min(target.scrollHeight, 600),\n })\n\n return canvas.toDataURL('image/png', 0.8)\n } catch (e) {\n console.warn('Tack: Screenshot capture failed', e)\n return undefined\n }\n}\n\nexport async function captureElementWithContext(\n selector: string | null,\n x: number,\n y: number\n): Promise<string | undefined> {\n const html2canvas = (await import('html2canvas')).default\n try {\n if (selector) {\n const element = document.querySelector(selector)\n if (element) {\n // Capture element with some padding\n const rect = element.getBoundingClientRect()\n const padding = 20\n\n const canvas = await html2canvas(document.body, {\n logging: false,\n useCORS: true,\n allowTaint: true,\n scale: 1,\n x: Math.max(0, rect.left + window.scrollX - padding),\n y: Math.max(0, rect.top + window.scrollY - padding),\n width: Math.min(rect.width + padding * 2, 600),\n height: Math.min(rect.height + padding * 2, 400),\n })\n\n return canvas.toDataURL('image/png', 0.8)\n }\n }\n\n // For coordinate pins or fallback, capture area around the point\n const viewportX = (x / 100) * document.documentElement.scrollWidth\n const viewportY = (y / 100) * document.documentElement.scrollHeight\n const captureWidth = 400\n const captureHeight = 300\n\n const canvas = await html2canvas(document.body, {\n logging: false,\n useCORS: true,\n allowTaint: true,\n scale: 1,\n ignoreElements: ignoreTackElements,\n x: Math.max(0, viewportX - captureWidth / 2),\n y: Math.max(0, viewportY - captureHeight / 2),\n width: captureWidth,\n height: captureHeight,\n })\n\n return canvas.toDataURL('image/png', 0.8)\n } catch (e) {\n console.warn('Tack: Screenshot capture failed', e)\n return undefined\n }\n}\n","import { captureElementWithContext } from '../capture'\nimport type { CommentAnchor } from '../types'\nimport type { WidgetUser } from '../auth'\nimport { renderAvatar } from './avatars'\n\ninterface FormSubmitData {\n content: string\n authorName: string\n authorEmail?: string\n anchor: CommentAnchor\n screenshot?: string\n}\n\ntype SubmitCallback = (data: FormSubmitData) => Promise<void>\ntype HideCallback = () => void\ntype SignInCallback = () => void\n\nexport class CommentForm {\n private container: HTMLElement\n private form: HTMLElement\n private onSubmit: SubmitCallback\n private onHide: HideCallback | null = null\n private onSignIn: SignInCallback | null = null\n private currentTarget: { element: Element | null; anchor: CommentAnchor } | null = null\n private isSubmitting = false\n private capturedScreenshot: string | undefined = undefined\n private highlightElement: HTMLElement | null = null\n private isVisible = false\n private currentUser: WidgetUser | null = null\n\n constructor(container: HTMLElement, onSubmit: SubmitCallback) {\n this.container = container\n this.onSubmit = onSubmit\n this.form = this.createForm()\n this.container.appendChild(this.form)\n\n // Create highlight element for showing what's being commented on\n this.highlightElement = document.createElement('div')\n this.highlightElement.className = 'tack-form-highlight'\n this.highlightElement.style.cssText = `\n position: fixed;\n pointer-events: none;\n border: 2px solid #14B8A6;\n border-radius: 4px;\n background: rgba(20, 184, 166, 0.1);\n z-index: 10002;\n display: none;\n transition: all 0.15s ease;\n `\n document.body.appendChild(this.highlightElement)\n }\n\n setOnHide(callback: HideCallback): void {\n this.onHide = callback\n }\n\n setOnSignIn(callback: SignInCallback): void {\n this.onSignIn = callback\n }\n\n setUser(user: WidgetUser | null): void {\n this.currentUser = user\n this.rebuildFormContent()\n }\n\n private createForm(): HTMLElement {\n const form = document.createElement('div')\n form.className = 'tack-form'\n this.buildFormInner(form)\n return form\n }\n\n private rebuildFormContent(): void {\n this.buildFormInner(this.form)\n }\n\n private buildFormInner(form: HTMLElement): void {\n if (this.currentUser) {\n // Authenticated: pill-shaped input with send button\n form.innerHTML = `\n <div class=\"tack-form-pill\">\n <textarea class=\"tack-form-pill-input\" id=\"tack-comment-content\" placeholder=\"Add a comment...\" rows=\"1\"></textarea>\n <button type=\"button\" class=\"tack-form-pill-send\" aria-label=\"Send\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" fill=\"none\">\n <path d=\"M7 11.5V2.5M7 2.5L3 6.5M7 2.5L11 6.5\" stroke=\"currentColor\" stroke-width=\"1.75\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </button>\n </div>\n `\n } else {\n // Not authenticated: show sign-in prompt in a pill\n form.innerHTML = `\n <div class=\"tack-form-pill tack-form-pill-signin\">\n <span class=\"tack-sign-in-prompt-text\">Sign in to leave feedback</span>\n <button type=\"button\" class=\"tack-sign-in-btn\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\">\n <path d=\"M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 0 1-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z\" fill=\"#4285F4\"/>\n <path d=\"M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z\" fill=\"#34A853\"/>\n <path d=\"M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z\" fill=\"#FBBC05\"/>\n <path d=\"M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z\" fill=\"#EA4335\"/>\n </svg>\n Sign in\n </button>\n </div>\n `\n }\n\n this.attachFormListeners(form)\n }\n\n private attachFormListeners(form: HTMLElement): void {\n // Stop clicks inside form from propagating\n form.addEventListener('mousedown', (e) => e.stopPropagation())\n form.addEventListener('click', (e) => e.stopPropagation())\n\n // Sign in button\n form.querySelector('.tack-sign-in-btn')?.addEventListener('click', (e) => {\n e.stopPropagation()\n this.onSignIn?.()\n })\n\n // Send button\n form.querySelector('.tack-form-pill-send')?.addEventListener('click', (e) => {\n e.stopPropagation()\n this.handleSubmit()\n })\n\n // Auto-expand textarea + Enter to send, Shift+Enter for newline\n const textarea = form.querySelector('#tack-comment-content') as HTMLTextAreaElement\n if (textarea) {\n textarea.addEventListener('input', () => {\n this.autoExpandTextarea(textarea)\n // Toggle send button active state\n const sendBtn = form.querySelector('.tack-form-pill-send')\n if (sendBtn) {\n sendBtn.classList.toggle('active', textarea.value.trim().length > 0)\n }\n })\n\n textarea.addEventListener('keydown', (e: Event) => {\n const ke = e as KeyboardEvent\n if (ke.key === 'Enter' && !ke.shiftKey) {\n ke.preventDefault()\n this.handleSubmit()\n }\n })\n }\n\n // Escape to close\n form.addEventListener('keydown', (e: Event) => {\n const ke = e as KeyboardEvent\n if (ke.key === 'Escape') {\n ke.preventDefault()\n this.hide()\n }\n })\n }\n\n async show(target: { element: Element | null; anchor: CommentAnchor }): Promise<void> {\n if (this.isVisible) return\n\n this.currentTarget = target\n this.isVisible = true\n\n // Capture screenshot BEFORE showing form\n this.capturedScreenshot = await captureElementWithContext(\n target.anchor.cssSelector,\n target.anchor.relativeX * 100,\n target.anchor.relativeY * 100\n )\n\n // Show highlight on the element being commented on\n if (target.element && this.highlightElement) {\n const rect = target.element.getBoundingClientRect()\n this.highlightElement.style.display = 'block'\n this.highlightElement.style.left = `${rect.left - 4}px`\n this.highlightElement.style.top = `${rect.top - 4}px`\n this.highlightElement.style.width = `${rect.width + 8}px`\n this.highlightElement.style.height = `${rect.height + 8}px`\n }\n\n // Position form near the clicked element\n let viewportX: number\n let viewportY: number\n\n if (target.element) {\n const rect = target.element.getBoundingClientRect()\n viewportX = rect.right + 12\n viewportY = rect.top\n const fw = 300\n if (viewportX + fw > window.innerWidth - 380) {\n viewportX = rect.left - fw - 12\n }\n } else {\n viewportX = target.anchor.relativeX * window.innerWidth + 20\n viewportY = target.anchor.relativeY * window.innerHeight\n }\n\n const formWidth = 300\n const formHeight = 52\n\n let left = Math.min(viewportX, window.innerWidth - formWidth - 380)\n let top = Math.min(viewportY, window.innerHeight - formHeight - 20)\n\n left = Math.max(20, left)\n top = Math.max(20, top)\n\n this.form.style.left = `${left}px`\n this.form.style.top = `${top}px`\n this.form.classList.add('visible')\n\n // Focus the comment textarea\n setTimeout(() => {\n ;(this.form.querySelector('#tack-comment-content') as HTMLTextAreaElement)?.focus()\n }, 50)\n }\n\n hide(): void {\n if (!this.isVisible) return\n\n this.isVisible = false\n this.form.classList.remove('visible')\n this.currentTarget = null\n this.capturedScreenshot = undefined\n const textarea = this.form.querySelector('#tack-comment-content') as HTMLTextAreaElement\n if (textarea) {\n textarea.value = ''\n textarea.style.height = '28px'\n textarea.style.overflowY = 'hidden'\n }\n const sendBtn = this.form.querySelector('.tack-form-pill-send')\n if (sendBtn) sendBtn.classList.remove('active')\n\n if (this.highlightElement) {\n this.highlightElement.style.display = 'none'\n }\n\n this.onHide?.()\n }\n\n isFormVisible(): boolean {\n return this.isVisible\n }\n\n private autoExpandTextarea(textarea: HTMLTextAreaElement): void {\n textarea.style.height = 'auto'\n const maxHeight = 120 // ~5 lines\n textarea.style.height = `${Math.min(textarea.scrollHeight, maxHeight)}px`\n textarea.style.overflowY = textarea.scrollHeight > maxHeight ? 'auto' : 'hidden'\n }\n\n private async handleSubmit(): Promise<void> {\n if (this.isSubmitting || !this.currentTarget) return\n\n const content = (this.form.querySelector('#tack-comment-content') as HTMLTextAreaElement)?.value.trim()\n if (!content) return\n\n // Determine author name\n let authorName: string\n if (this.currentUser) {\n authorName = this.currentUser.name\n } else {\n const nameInput = this.form.querySelector('#tack-author-name') as HTMLInputElement\n authorName = nameInput?.value.trim() || ''\n if (!authorName) return\n }\n\n this.isSubmitting = true\n const sendBtn = this.form.querySelector('.tack-form-pill-send') as HTMLButtonElement\n if (sendBtn) sendBtn.classList.add('sending')\n\n try {\n if (!this.currentUser) {\n localStorage.setItem('tack_author_name', authorName)\n }\n\n const submitData = {\n content,\n authorName,\n anchor: this.currentTarget.anchor,\n screenshot: this.capturedScreenshot,\n }\n\n this.hide()\n await this.onSubmit(submitData)\n } catch (error) {\n console.error('[Tack] Submit failed:', error)\n } finally {\n this.isSubmitting = false\n if (sendBtn) sendBtn.classList.remove('sending')\n }\n }\n\n private escapeHtml(text: string): string {\n const div = document.createElement('div')\n div.textContent = text\n return div.innerHTML\n }\n}\n","/**\n * Singleton cache that fetches avatar images once and converts them to local blob URLs.\n * Prevents repeated network requests to Google's CDN which triggers 429 rate limiting.\n */\nclass AvatarCache {\n private cache = new Map<string, string>()\n private pending = new Map<string, Promise<string>>()\n\n /** Get cached blob URL synchronously. Returns original URL if not yet cached. */\n get(url: string): string {\n return this.cache.get(url) || url\n }\n\n /** Pre-fetch and cache a list of avatar URLs as blob URLs. */\n async preload(urls: (string | null | undefined)[]): Promise<void> {\n const unique = [...new Set(urls.filter((u): u is string => !!u && !this.cache.has(u)))]\n if (unique.length === 0) return\n await Promise.all(unique.map((url) => this.resolve(url)))\n }\n\n private resolve(url: string): Promise<string> {\n if (this.cache.has(url)) return Promise.resolve(this.cache.get(url)!)\n if (this.pending.has(url)) return this.pending.get(url)!\n\n const promise = fetch(url, { mode: 'cors' })\n .then((r) => {\n if (!r.ok) throw new Error(`${r.status}`)\n return r.blob()\n })\n .then((blob) => {\n const blobUrl = URL.createObjectURL(blob)\n this.cache.set(url, blobUrl)\n this.pending.delete(url)\n return blobUrl\n })\n .catch(() => {\n this.pending.delete(url)\n // Don't cache failures — allow retry on next preload\n return url\n })\n\n this.pending.set(url, promise)\n return promise\n }\n\n destroy(): void {\n this.cache.forEach((blobUrl) => URL.revokeObjectURL(blobUrl))\n this.cache.clear()\n this.pending.clear()\n }\n}\n\nexport const avatarCache = new AvatarCache()\n","import { avatarCache } from './avatarCache'\n\n// Gradient pairs for avatar pins (from → to)\nconst AVATAR_GRADIENTS: [string, string][] = [\n ['#818CF8', '#6366F1'], // indigo\n ['#A78BFA', '#7C3AED'], // violet\n ['#F472B6', '#EC4899'], // pink\n ['#FBBF24', '#F59E0B'], // amber\n ['#34D399', '#10B981'], // emerald\n ['#60A5FA', '#3B82F6'], // blue\n ['#F87171', '#EF4444'], // red\n ['#2DD4BF', '#14B8A6'], // teal\n]\n\n// Flat colors (primary of each pair) for contexts that need a single color\nexport const AVATAR_COLORS = AVATAR_GRADIENTS.map(([, to]) => to)\n\nfunction hashName(name: string): number {\n let hash = 0\n for (let i = 0; i < name.length; i++) {\n hash = name.charCodeAt(i) + ((hash << 5) - hash)\n }\n return Math.abs(hash)\n}\n\nexport function getAvatarColor(name: string): string {\n return AVATAR_COLORS[hashName(name) % AVATAR_COLORS.length]\n}\n\nexport function getAvatarGradient(name: string): string {\n const [from, to] = AVATAR_GRADIENTS[hashName(name) % AVATAR_GRADIENTS.length]\n return `linear-gradient(135deg, ${from} 0%, ${to} 100%)`\n}\n\nexport function getInitials(name: string): string {\n const parts = name.trim().split(/\\s+/)\n if (parts.length >= 2) {\n return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase()\n }\n return name.slice(0, 2).toUpperCase()\n}\n\n/**\n * Renders an avatar as an HTML string. Uses cached blob URL for profile photos\n * to avoid repeated network requests. Falls back to initials with colored background.\n */\nexport function renderAvatar(\n name: string,\n avatarUrl: string | null,\n size: number,\n extraClass = ''\n): string {\n const fontSize = Math.round(size * 0.38)\n const cls = extraClass ? ` ${extraClass}` : ''\n if (avatarUrl) {\n const src = avatarCache.get(avatarUrl)\n return `<img\n src=\"${src}\"\n alt=\"${getInitials(name)}\"\n class=\"tack-avatar-img${cls}\"\n style=\"width:${size}px;height:${size}px;border-radius:50%;object-fit:cover;\"\n onerror=\"this.style.display='none';this.nextElementSibling.style.display='flex'\"\n /><div\n class=\"tack-avatar-fallback${cls}\"\n style=\"display:none;width:${size}px;height:${size}px;border-radius:50%;background:${getAvatarColor(name)};color:white;font-size:${fontSize}px;font-weight:600;align-items:center;justify-content:center;line-height:1;flex-shrink:0\"\n >${getInitials(name)}</div>`\n }\n return `<div\n class=\"tack-avatar-fallback${cls}\"\n style=\"display:flex;width:${size}px;height:${size}px;border-radius:50%;background:${getAvatarColor(name)};color:white;font-size:${fontSize}px;font-weight:600;align-items:center;justify-content:center;line-height:1;flex-shrink:0\"\n >${getInitials(name)}</div>`\n}\n","import type { Comment } from '../types'\nimport type { WidgetUser } from '../auth'\nimport { getAvatarColor, getInitials, renderAvatar } from './avatars'\n\ninterface PanelCallbacks {\n onCommentClick: (comment: Comment) => void\n onResolve: (commentId: string, resolved: boolean) => void\n onReply: (parentId: string, content: string) => void\n onClose?: () => void\n onAddComment?: () => void\n onShare?: () => void\n}\n\ntype SortMode = 'date' | 'unread' | 'replies'\n\ninterface FilterState {\n sort: SortMode\n showResolved: boolean\n onlyYourThreads: boolean\n onlyCurrentPage: boolean\n}\n\nconst STORAGE_KEY = 'tack_panel_filters'\n\nfunction loadFilters(): FilterState {\n try {\n const stored = localStorage.getItem(STORAGE_KEY)\n if (stored) {\n const parsed = JSON.parse(stored)\n return {\n sort: parsed.sort || 'date',\n showResolved: parsed.showResolved ?? false,\n onlyYourThreads: parsed.onlyYourThreads ?? false,\n onlyCurrentPage: parsed.onlyCurrentPage ?? false,\n }\n }\n } catch {}\n return { sort: 'date', showResolved: false, onlyYourThreads: false, onlyCurrentPage: false }\n}\n\nfunction saveFilters(filters: FilterState): void {\n try {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(filters))\n } catch {}\n}\n\nexport class CommentPanel {\n private container: HTMLElement\n private strip: HTMLElement\n private panel: HTMLElement\n private callbacks: PanelCallbacks\n private comments: Comment[] = []\n private filters: FilterState\n private searchQuery: string = ''\n private searchTimer: ReturnType<typeof setTimeout> | null = null\n private highlightedId: string | null = null\n private filterDropdownOpen = false\n private moreDropdownOpen = false\n private currentUser: WidgetUser | null = null\n private readCommentIds: Set<string>\n private projectId: string = ''\n\n constructor(container: HTMLElement, callbacks: PanelCallbacks) {\n this.container = container\n this.callbacks = callbacks\n this.filters = loadFilters()\n this.readCommentIds = this.loadReadIds()\n this.strip = this.createPanelStrip()\n this.panel = this.strip.querySelector('.tack-panel')!\n this.container.appendChild(this.strip)\n }\n\n setUser(user: WidgetUser | null): void {\n this.currentUser = user\n }\n\n setProjectId(projectId: string): void {\n this.projectId = projectId\n this.readCommentIds = this.loadReadIds()\n }\n\n private loadReadIds(): Set<string> {\n try {\n const key = `tack_read_${this.projectId}`\n const stored = localStorage.getItem(key)\n if (stored) return new Set(JSON.parse(stored))\n } catch {}\n return new Set()\n }\n\n private saveReadIds(): void {\n try {\n const key = `tack_read_${this.projectId}`\n localStorage.setItem(key, JSON.stringify([...this.readCommentIds]))\n } catch {}\n }\n\n markAsRead(commentId: string): void {\n if (!this.readCommentIds.has(commentId)) {\n this.readCommentIds.add(commentId)\n this.saveReadIds()\n }\n }\n\n markVisibleAsRead(): void {\n if (!this.isOpen()) return\n const filtered = this.getFilteredComments()\n let changed = false\n for (const c of filtered) {\n if (!this.readCommentIds.has(c.id)) {\n this.readCommentIds.add(c.id)\n changed = true\n }\n }\n if (changed) this.saveReadIds()\n }\n\n isRead(commentId: string): boolean {\n return this.readCommentIds.has(commentId)\n }\n\n private createPanelStrip(): HTMLElement {\n const strip = document.createElement('div')\n strip.className = 'tack-panel-strip'\n strip.innerHTML = `\n <div class=\"tack-panel\" role=\"complementary\" aria-label=\"Comments panel\">\n <div class=\"tack-panel-header\">\n <button class=\"tack-panel-close\" aria-label=\"Close comments panel\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\n </svg>\n </button>\n <div class=\"tack-panel-search-wrap\">\n <svg class=\"tack-panel-search-icon\" width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"></circle>\n <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"></line>\n </svg>\n <input type=\"text\" class=\"tack-panel-search\" placeholder=\"Search\" />\n <button class=\"tack-panel-search-clear\" style=\"display:none;\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\n </svg>\n </button>\n </div>\n <div class=\"tack-panel-filter-wrap\">\n <button class=\"tack-panel-filter-btn\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.75\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"4\" y1=\"6\" x2=\"20\" y2=\"6\"></line>\n <line x1=\"7\" y1=\"12\" x2=\"17\" y2=\"12\"></line>\n <line x1=\"10\" y1=\"18\" x2=\"14\" y2=\"18\"></line>\n </svg>\n <span class=\"tack-panel-filter-badge\" style=\"display:none;\"></span>\n </button>\n <div class=\"tack-panel-filter-dropdown\" data-state=\"closed\"></div>\n </div>\n <div class=\"tack-panel-more-wrap\">\n <button class=\"tack-panel-more-btn\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"5\" r=\"1\" fill=\"currentColor\"></circle>\n <circle cx=\"12\" cy=\"12\" r=\"1\" fill=\"currentColor\"></circle>\n <circle cx=\"12\" cy=\"19\" r=\"1\" fill=\"currentColor\"></circle>\n </svg>\n </button>\n <div class=\"tack-panel-more-dropdown\" data-state=\"closed\"></div>\n </div>\n </div>\n <div class=\"tack-panel-content\"></div>\n <div class=\"tack-new-comment\">\n <div class=\"tack-new-comment-input\" role=\"button\" aria-label=\"Add new comment\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M15 15l-2 5L9 9l11 4-5 2z\"></path>\n <path d=\"M22 2l-7 7\"></path>\n </svg>\n Click anywhere to add comment\n </div>\n </div>\n </div>\n `\n\n // Close button\n strip.querySelector('.tack-panel-close')!.addEventListener('click', () => {\n this.hide()\n this.callbacks.onClose?.()\n })\n\n // Search input\n const searchInput = strip.querySelector('.tack-panel-search') as HTMLInputElement\n const searchClear = strip.querySelector('.tack-panel-search-clear') as HTMLElement\n searchInput.addEventListener('input', () => {\n const val = searchInput.value\n searchClear.style.display = val ? 'flex' : 'none'\n if (this.searchTimer) clearTimeout(this.searchTimer)\n this.searchTimer = setTimeout(() => {\n this.searchQuery = val.trim().toLowerCase()\n this.renderComments()\n }, 200)\n })\n searchClear.addEventListener('click', (e) => {\n e.stopPropagation()\n searchInput.value = ''\n searchClear.style.display = 'none'\n this.searchQuery = ''\n this.renderComments()\n searchInput.focus()\n })\n\n // Filter button\n strip.querySelector('.tack-panel-filter-btn')!.addEventListener('click', (e) => {\n e.stopPropagation()\n this.moreDropdownOpen = false\n this.renderMoreDropdown()\n this.filterDropdownOpen = !this.filterDropdownOpen\n this.renderFilterDropdown()\n })\n\n // More button\n strip.querySelector('.tack-panel-more-btn')!.addEventListener('click', (e) => {\n e.stopPropagation()\n this.filterDropdownOpen = false\n this.renderFilterDropdown()\n this.moreDropdownOpen = !this.moreDropdownOpen\n this.renderMoreDropdown()\n })\n\n // New comment input\n strip.querySelector('.tack-new-comment-input')!.addEventListener('click', () => {\n this.callbacks.onAddComment?.()\n })\n\n // Close dropdowns on click outside\n strip.addEventListener('click', (e) => {\n const target = e.target as HTMLElement\n if (!target.closest('.tack-panel-filter-wrap') && this.filterDropdownOpen) {\n this.filterDropdownOpen = false\n this.renderFilterDropdown()\n }\n if (!target.closest('.tack-panel-more-wrap') && this.moreDropdownOpen) {\n this.moreDropdownOpen = false\n this.renderMoreDropdown()\n }\n })\n\n return strip\n }\n\n private renderFilterDropdown(): void {\n const dropdown = this.strip.querySelector('.tack-panel-filter-dropdown') as HTMLElement\n if (!this.filterDropdownOpen) {\n dropdown.setAttribute('data-state', 'closed')\n dropdown.innerHTML = ''\n return\n }\n\n const f = this.filters\n const check = `<svg class=\"tack-dropdown-check\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg>`\n const empty = '<span class=\"tack-dropdown-check-space\"></span>'\n\n dropdown.innerHTML = `\n <button class=\"tack-dropdown-item\" data-action=\"sort-date\">\n ${f.sort === 'date' ? check : empty}\n <span>Sort by date</span>\n </button>\n <button class=\"tack-dropdown-item\" data-action=\"sort-unread\">\n ${f.sort === 'unread' ? check : empty}\n <span>Sort by unread</span>\n </button>\n <button class=\"tack-dropdown-item\" data-action=\"sort-replies\">\n ${f.sort === 'replies' ? check : empty}\n <span>Sort by most replies</span>\n </button>\n <div class=\"tack-dropdown-sep\"></div>\n <button class=\"tack-dropdown-item tack-dropdown-toggle\" data-action=\"showResolved\">\n <span>Show resolved comments</span>\n <span class=\"tack-dropdown-toggle-switch ${f.showResolved ? 'on' : ''}\"><span class=\"tack-dropdown-toggle-knob\"></span></span>\n </button>\n <button class=\"tack-dropdown-item tack-dropdown-toggle\" data-action=\"onlyYourThreads\">\n <span>Only your threads</span>\n <span class=\"tack-dropdown-toggle-switch ${f.onlyYourThreads ? 'on' : ''}\"><span class=\"tack-dropdown-toggle-knob\"></span></span>\n </button>\n <button class=\"tack-dropdown-item tack-dropdown-toggle\" data-action=\"onlyCurrentPage\">\n <span>Only current page</span>\n <span class=\"tack-dropdown-toggle-switch ${f.onlyCurrentPage ? 'on' : ''}\"><span class=\"tack-dropdown-toggle-knob\"></span></span>\n </button>\n `\n dropdown.setAttribute('data-state', 'open')\n\n dropdown.querySelectorAll('.tack-dropdown-item').forEach((item) => {\n item.addEventListener('click', (e) => {\n e.stopPropagation()\n const action = (item as HTMLElement).dataset.action!\n if (action.startsWith('sort-')) {\n this.filters.sort = action.replace('sort-', '') as SortMode\n } else if (action === 'showResolved') {\n this.filters.showResolved = !this.filters.showResolved\n } else if (action === 'onlyYourThreads') {\n this.filters.onlyYourThreads = !this.filters.onlyYourThreads\n } else if (action === 'onlyCurrentPage') {\n this.filters.onlyCurrentPage = !this.filters.onlyCurrentPage\n }\n saveFilters(this.filters)\n this.updateFilterBadge()\n this.renderFilterDropdown()\n this.renderComments()\n })\n })\n }\n\n private renderMoreDropdown(): void {\n const dropdown = this.strip.querySelector('.tack-panel-more-dropdown') as HTMLElement\n if (!this.moreDropdownOpen) {\n dropdown.setAttribute('data-state', 'closed')\n dropdown.innerHTML = ''\n return\n }\n\n dropdown.innerHTML = `\n <button class=\"tack-dropdown-item\" data-action=\"share\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8\"></path>\n <polyline points=\"16 6 12 2 8 6\"></polyline>\n <line x1=\"12\" y1=\"2\" x2=\"12\" y2=\"15\"></line>\n </svg>\n <span>Copy share link</span>\n </button>\n `\n dropdown.setAttribute('data-state', 'open')\n\n dropdown.querySelector('[data-action=\"share\"]')!.addEventListener('click', (e) => {\n e.stopPropagation()\n this.moreDropdownOpen = false\n this.renderMoreDropdown()\n this.callbacks.onShare?.()\n })\n }\n\n private updateFilterBadge(): void {\n const badge = this.strip.querySelector('.tack-panel-filter-badge') as HTMLElement\n const btn = this.strip.querySelector('.tack-panel-filter-btn') as HTMLElement\n let count = 0\n if (this.filters.showResolved) count++\n if (this.filters.onlyYourThreads) count++\n if (this.filters.onlyCurrentPage) count++\n\n if (count > 0) {\n badge.textContent = String(count)\n badge.style.display = 'flex'\n btn.classList.add('filter-active')\n } else {\n badge.style.display = 'none'\n btn.classList.remove('filter-active')\n }\n }\n\n show(): void {\n this.strip.classList.add('open')\n this.updateFilterBadge()\n // Mark visible comments as read after a delay\n setTimeout(() => this.markVisibleAsRead(), 1500)\n }\n\n hide(): void {\n this.strip.classList.remove('open')\n this.filterDropdownOpen = false\n this.moreDropdownOpen = false\n this.renderFilterDropdown()\n this.renderMoreDropdown()\n }\n\n toggle(): void {\n if (this.strip.classList.contains('open')) {\n this.hide()\n } else {\n this.show()\n }\n }\n\n isOpen(): boolean {\n return this.strip.classList.contains('open')\n }\n\n render(comments: Comment[]): void {\n this.comments = comments\n this.renderComments()\n // Mark visible as read when panel is open\n if (this.isOpen()) {\n setTimeout(() => this.markVisibleAsRead(), 1500)\n }\n }\n\n renderPresence(users: { id: string; name: string; avatar_url: string | null }[]): void {\n let presenceBar = this.panel.querySelector('.tack-presence-bar') as HTMLElement\n if (!presenceBar) {\n presenceBar = document.createElement('div')\n presenceBar.className = 'tack-presence-bar'\n const header = this.panel.querySelector('.tack-panel-header')\n if (header) {\n header.parentNode?.insertBefore(presenceBar, header.nextSibling)\n }\n }\n\n if (users.length === 0) {\n presenceBar.style.display = 'none'\n return\n }\n\n presenceBar.style.display = 'flex'\n const maxShow = 5\n const visible = users.slice(0, maxShow)\n const overflow = users.length - maxShow\n\n let html = '<div class=\"tack-presence-avatars\">'\n visible.forEach((u) => {\n html += `<div class=\"tack-presence-avatar\" title=\"${this.escapeHtml(u.name)}\">${renderAvatar(u.name, u.avatar_url, 24)}</div>`\n })\n if (overflow > 0) {\n html += `<div class=\"tack-presence-overflow\">+${overflow}</div>`\n }\n html += '</div>'\n html += `<span class=\"tack-presence-label\">${users.length} viewing</span>`\n\n presenceBar.innerHTML = html\n }\n\n private renderComments(): void {\n const content = this.panel.querySelector('.tack-panel-content')!\n const filtered = this.getFilteredComments()\n\n if (filtered.length === 0) {\n if (this.searchQuery) {\n const activeFilterCount = (this.filters.showResolved ? 1 : 0) + (this.filters.onlyYourThreads ? 1 : 0) + (this.filters.onlyCurrentPage ? 1 : 0)\n content.innerHTML = `\n <div class=\"tack-empty\">\n <div class=\"tack-empty-icon\">\n <svg width=\"40\" height=\"40\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"></circle>\n <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"></line>\n </svg>\n </div>\n <p class=\"tack-empty-title\">No comments matching \"${this.escapeHtml(this.searchQuery)}\"</p>\n <p class=\"tack-empty-subtitle\">${activeFilterCount > 0 ? 'Try adjusting your search or filters' : 'Try a different search term'}</p>\n ${activeFilterCount > 0 ? '<button class=\"tack-empty-reset\">Reset filters</button>' : ''}\n </div>\n `\n } else if (this.hasActiveFilters()) {\n content.innerHTML = `\n <div class=\"tack-empty\">\n <div class=\"tack-empty-icon\">\n <svg width=\"40\" height=\"40\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <line x1=\"4\" y1=\"6\" x2=\"20\" y2=\"6\"></line>\n <line x1=\"7\" y1=\"12\" x2=\"17\" y2=\"12\"></line>\n <line x1=\"10\" y1=\"18\" x2=\"14\" y2=\"18\"></line>\n </svg>\n </div>\n <p class=\"tack-empty-title\">No comments to show</p>\n <p class=\"tack-empty-subtitle\">${this.describeActiveFilters()}</p>\n <button class=\"tack-empty-reset\">Reset filters</button>\n </div>\n `\n } else {\n content.innerHTML = `\n <div class=\"tack-empty\">\n <div class=\"tack-empty-icon\">\n <svg width=\"40\" height=\"40\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\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\"></path>\n </svg>\n </div>\n <p class=\"tack-empty-title\">No comments yet</p>\n <p class=\"tack-empty-subtitle\">Click anywhere on the page to add feedback</p>\n </div>\n `\n }\n\n content.querySelector('.tack-empty-reset')?.addEventListener('click', () => {\n this.resetFilters()\n })\n return\n }\n\n content.innerHTML = filtered\n .map((comment) => this.renderCommentRow(comment))\n .join('')\n\n content.querySelectorAll('.tack-comment-row').forEach((row) => {\n const id = (row as HTMLElement).dataset.id!\n row.addEventListener('click', () => {\n this.markAsRead(id)\n // Remove unread dot immediately\n const dot = row.querySelector('.tack-unread-dot')\n if (dot) dot.remove()\n const comment = this.comments.find((c) => c.id === id)\n if (comment) this.callbacks.onCommentClick(comment)\n })\n\n // Resolve button\n const resolveBtn = row.querySelector('.tack-comment-row-resolve')\n if (resolveBtn) {\n resolveBtn.addEventListener('click', (e) => {\n e.stopPropagation()\n const comment = this.comments.find((c) => c.id === id)\n if (comment) {\n this.callbacks.onResolve(id, !comment.resolved)\n }\n })\n }\n\n // More button\n const moreBtn = row.querySelector('.tack-comment-row-more')\n if (moreBtn) {\n moreBtn.addEventListener('click', (e) => {\n e.stopPropagation()\n // For now, open the comment (same as row click) to access card actions\n const comment = this.comments.find((c) => c.id === id)\n if (comment) this.callbacks.onCommentClick(comment)\n })\n }\n })\n }\n\n private hasActiveFilters(): boolean {\n return this.filters.showResolved || this.filters.onlyYourThreads || this.filters.onlyCurrentPage\n }\n\n private describeActiveFilters(): string {\n const parts: string[] = []\n if (this.filters.onlyYourThreads) parts.push('only your threads')\n if (this.filters.onlyCurrentPage) parts.push('only current page')\n if (!this.filters.showResolved) parts.push('resolved comments hidden')\n return parts.length > 0 ? parts.join(', ').replace(/^./, s => s.toUpperCase()) : ''\n }\n\n private resetFilters(): void {\n this.filters = { sort: 'date', showResolved: false, onlyYourThreads: false, onlyCurrentPage: false }\n this.searchQuery = ''\n const searchInput = this.strip.querySelector('.tack-panel-search') as HTMLInputElement\n if (searchInput) searchInput.value = ''\n const searchClear = this.strip.querySelector('.tack-panel-search-clear') as HTMLElement\n if (searchClear) searchClear.style.display = 'none'\n saveFilters(this.filters)\n this.updateFilterBadge()\n this.renderComments()\n }\n\n private getCurrentPagePath(): string {\n return window.location.pathname\n }\n\n private formatPagePath(pagePath: string): string {\n if (!pagePath || pagePath === '/') return 'Home'\n const path = pagePath.split('?')[0].slice(1)\n return path.charAt(0).toUpperCase() + path.slice(1)\n }\n\n private renderCommentRow(comment: Comment): string {\n const isHighlighted = comment.id === this.highlightedId\n const timeAgo = this.formatTimeAgo(new Date(comment.created_at))\n const currentPath = this.getCurrentPagePath()\n const isCurrentPage = comment.page_path === currentPath\n const pageName = this.formatPagePath(comment.page_path)\n const hasScreenshot = !!comment.screenshot_url\n const unread = !this.isRead(comment.id)\n\n return `\n <div class=\"tack-comment-row ${isHighlighted ? 'highlighted' : ''}\" data-id=\"${comment.id}\">\n <div class=\"tack-comment-row-actions\">\n <button class=\"tack-comment-row-more\" data-action=\"more\" data-id=\"${comment.id}\" title=\"More actions\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\"><circle cx=\"3\" cy=\"8\" r=\"1.25\" fill=\"currentColor\"/><circle cx=\"8\" cy=\"8\" r=\"1.25\" fill=\"currentColor\"/><circle cx=\"13\" cy=\"8\" r=\"1.25\" fill=\"currentColor\"/></svg>\n </button>\n <button class=\"tack-comment-row-resolve ${comment.resolved ? 'resolved' : ''}\" data-action=\"resolve\" data-id=\"${comment.id}\" title=\"${comment.resolved ? 'Unresolve' : 'Resolve'}\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\"><circle cx=\"8\" cy=\"8\" r=\"6.5\" stroke=\"currentColor\" stroke-width=\"1.5\"/>${comment.resolved ? '<path d=\"M5.5 8L7.2 9.7L10.5 6.3\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>' : ''}</svg>\n </button>\n </div>\n <div class=\"tack-comment-row-top\">\n ${renderAvatar(comment.author_name, comment.author_avatar_url, 32, 'tack-comment-avatar')}\n ${unread ? '<span class=\"tack-unread-dot\"></span>' : ''}\n </div>\n <div class=\"tack-comment-meta\">\n <span class=\"tack-comment-author\">${this.escapeHtml(comment.author_name)}</span>\n <span class=\"tack-comment-time\">${timeAgo}</span>\n ${comment.resolved ? '<span class=\"tack-comment-resolved-badge\">Resolved</span>' : ''}\n ${!isCurrentPage ? `<span class=\"tack-comment-page-badge\">${this.escapeHtml(pageName)}</span>` : ''}\n </div>\n <div class=\"tack-comment-body ${comment.resolved ? 'dimmed' : ''}\">\n ${hasScreenshot ? `<img src=\"${comment.screenshot_url}\" class=\"tack-comment-screenshot\" alt=\"Screenshot\" />` : ''}\n <div class=\"tack-comment-content\">${this.escapeHtml(comment.content)}</div>\n </div>\n </div>\n `\n }\n\n private getFilteredComments(): Comment[] {\n let result = this.comments.filter((c) => !c.parent_id)\n\n // Filter: resolved\n if (!this.filters.showResolved) {\n result = result.filter((c) => !c.resolved)\n }\n\n // Filter: only your threads\n if (this.filters.onlyYourThreads && this.currentUser) {\n const userId = this.currentUser.id\n const userName = this.currentUser.name\n result = result.filter((c) => {\n if (c.user_id === userId) return true\n if (c.author_name === userName) return true\n if (c.replies?.some((r) => r.user_id === userId || r.author_name === userName)) return true\n return false\n })\n }\n\n // Filter: only current page\n if (this.filters.onlyCurrentPage) {\n const currentPath = this.getCurrentPagePath()\n result = result.filter((c) => c.page_path === currentPath)\n }\n\n // Search\n if (this.searchQuery) {\n const q = this.searchQuery\n result = result.filter((c) => {\n if (c.author_name.toLowerCase().includes(q)) return true\n if (c.content.toLowerCase().includes(q)) return true\n if (c.replies?.some((r) => r.content.toLowerCase().includes(q) || r.author_name.toLowerCase().includes(q))) return true\n return false\n })\n }\n\n // Sort\n switch (this.filters.sort) {\n case 'unread':\n result.sort((a, b) => {\n const aUnread = !this.isRead(a.id) ? 0 : 1\n const bUnread = !this.isRead(b.id) ? 0 : 1\n if (aUnread !== bUnread) return aUnread - bUnread\n return new Date(b.created_at).getTime() - new Date(a.created_at).getTime()\n })\n break\n case 'replies':\n result.sort((a, b) => {\n const aReplies = a.replies?.length || 0\n const bReplies = b.replies?.length || 0\n if (bReplies !== aReplies) return bReplies - aReplies\n return new Date(b.created_at).getTime() - new Date(a.created_at).getTime()\n })\n break\n case 'date':\n default:\n result.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())\n break\n }\n\n return result\n }\n\n getFilteredCommentIds(): string[] {\n return this.getFilteredComments().map(c => c.id)\n }\n\n scrollToComment(commentId: string): void {\n this.highlightedId = commentId\n this.renderComments()\n\n const card = this.panel.querySelector(`[data-id=\"${commentId}\"]`)\n card?.scrollIntoView({ behavior: 'smooth', block: 'nearest' })\n\n setTimeout(() => {\n this.highlightedId = null\n this.renderComments()\n }, 2000)\n }\n\n private formatTimeAgo(date: Date): string {\n const seconds = Math.floor((Date.now() - date.getTime()) / 1000)\n\n if (seconds < 60) return 'just now'\n if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`\n if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`\n if (seconds < 604800) return `${Math.floor(seconds / 86400)}d ago`\n return date.toLocaleDateString()\n }\n\n private escapeHtml(text: string): string {\n const div = document.createElement('div')\n div.textContent = text\n return div.innerHTML\n }\n}\n","import type { CommentAnchor, TextQuoteSelector } from './types'\n\nexport interface AnchorResult {\n element: Element | null\n confidence: 'high' | 'medium' | 'low' | 'none'\n method: 'css' | 'xpath' | 'textQuote' | 'fuzzy' | 'none'\n}\n\nexport class ElementAnchoring {\n /**\n * Resolve an anchor to a DOM element using multiple fallback strategies\n */\n resolve(anchor: CommentAnchor | null): AnchorResult {\n if (!anchor) {\n return { element: null, confidence: 'none', method: 'none' }\n }\n\n // Strategy 1: Try CSS selector (fastest)\n if (anchor.cssSelector) {\n const result = this.tryCssSelector(anchor)\n if (result.element) {\n return result\n }\n }\n\n // Strategy 2: Try XPath\n if (anchor.xpath) {\n const result = this.tryXPath(anchor)\n if (result.element) {\n return result\n }\n }\n\n // Strategy 3: Try text quote exact match\n if (anchor.textQuote) {\n const result = this.tryTextQuote(anchor.textQuote)\n if (result.element) {\n return result\n }\n }\n\n // Strategy 4: Try fuzzy text search\n if (anchor.textQuote) {\n const result = this.tryFuzzySearch(anchor.textQuote)\n if (result.element) {\n return result\n }\n }\n\n return { element: null, confidence: 'none', method: 'none' }\n }\n\n /**\n * Calculate the position for a pin given an anchor and its target element\n */\n calculatePinPosition(element: Element, anchor: CommentAnchor): { x: number; y: number } {\n const rect = element.getBoundingClientRect()\n\n // Position at the relative point within the element\n const x = rect.left + (anchor.relativeX * rect.width) + window.scrollX\n const y = rect.top + (anchor.relativeY * rect.height) + window.scrollY\n\n return { x, y }\n }\n\n private tryCssSelector(anchor: CommentAnchor): AnchorResult {\n try {\n const element = document.querySelector(anchor.cssSelector!)\n if (!element) {\n return { element: null, confidence: 'none', method: 'css' }\n }\n\n // Validate with content hash if available\n if (anchor.contentHash) {\n const currentHash = this.generateContentHash(element)\n if (currentHash === anchor.contentHash) {\n return { element, confidence: 'high', method: 'css' }\n }\n // Hash mismatch - element found but content changed\n return { element, confidence: 'medium', method: 'css' }\n }\n\n return { element, confidence: 'high', method: 'css' }\n } catch {\n return { element: null, confidence: 'none', method: 'css' }\n }\n }\n\n private tryXPath(anchor: CommentAnchor): AnchorResult {\n try {\n const result = document.evaluate(\n anchor.xpath!,\n document.body,\n null,\n XPathResult.FIRST_ORDERED_NODE_TYPE,\n null\n )\n const element = result.singleNodeValue as Element | null\n\n if (!element) {\n return { element: null, confidence: 'none', method: 'xpath' }\n }\n\n // Validate with content hash\n if (anchor.contentHash) {\n const currentHash = this.generateContentHash(element)\n if (currentHash === anchor.contentHash) {\n return { element, confidence: 'high', method: 'xpath' }\n }\n return { element, confidence: 'medium', method: 'xpath' }\n }\n\n return { element, confidence: 'medium', method: 'xpath' }\n } catch {\n return { element: null, confidence: 'none', method: 'xpath' }\n }\n }\n\n private tryTextQuote(textQuote: TextQuoteSelector): AnchorResult {\n // Walk the DOM looking for exact text match\n const walker = document.createTreeWalker(\n document.body,\n NodeFilter.SHOW_ELEMENT,\n null\n )\n\n let node: Node | null = walker.nextNode()\n while (node) {\n const element = node as Element\n const text = element.textContent?.trim()\n\n if (text && text.includes(textQuote.exact)) {\n // Verify with prefix/suffix if available\n if (this.verifyContext(element, textQuote)) {\n return { element, confidence: 'high', method: 'textQuote' }\n }\n }\n\n node = walker.nextNode()\n }\n\n return { element: null, confidence: 'none', method: 'textQuote' }\n }\n\n private tryFuzzySearch(textQuote: TextQuoteSelector): AnchorResult {\n // Fuzzy search using Levenshtein distance\n const walker = document.createTreeWalker(\n document.body,\n NodeFilter.SHOW_ELEMENT,\n null\n )\n\n let bestMatch: { element: Element; score: number } | null = null\n const targetText = textQuote.exact.toLowerCase()\n\n let node: Node | null = walker.nextNode()\n while (node) {\n const element = node as Element\n const text = element.textContent?.trim().toLowerCase()\n\n if (text && text.length < 1000) {\n const score = this.similarityScore(text, targetText)\n if (score > 0.7 && (!bestMatch || score > bestMatch.score)) {\n bestMatch = { element, score }\n }\n }\n\n node = walker.nextNode()\n }\n\n if (bestMatch) {\n return {\n element: bestMatch.element,\n confidence: bestMatch.score > 0.9 ? 'medium' : 'low',\n method: 'fuzzy'\n }\n }\n\n return { element: null, confidence: 'none', method: 'fuzzy' }\n }\n\n private verifyContext(element: Element, textQuote: TextQuoteSelector): boolean {\n if (!textQuote.prefix && !textQuote.suffix) {\n return true // No context to verify\n }\n\n const parent = element.parentElement\n if (!parent) return true\n\n const parentText = parent.textContent || ''\n const elementText = element.textContent || ''\n const index = parentText.indexOf(elementText)\n\n if (index === -1) return true\n\n // Check prefix\n if (textQuote.prefix) {\n const actualPrefix = parentText.slice(Math.max(0, index - 50), index).trim()\n if (!actualPrefix.includes(textQuote.prefix.slice(-20))) {\n return false\n }\n }\n\n // Check suffix\n if (textQuote.suffix) {\n const endIndex = index + elementText.length\n const actualSuffix = parentText.slice(endIndex, endIndex + 50).trim()\n if (!actualSuffix.includes(textQuote.suffix.slice(0, 20))) {\n return false\n }\n }\n\n return true\n }\n\n private similarityScore(a: string, b: string): number {\n // Simple Dice coefficient for similarity\n if (a === b) return 1\n if (a.length < 2 || b.length < 2) return 0\n\n const bigramsA = new Set<string>()\n const bigramsB = new Set<string>()\n\n for (let i = 0; i < a.length - 1; i++) {\n bigramsA.add(a.slice(i, i + 2))\n }\n for (let i = 0; i < b.length - 1; i++) {\n bigramsB.add(b.slice(i, i + 2))\n }\n\n let intersection = 0\n bigramsA.forEach(bigram => {\n if (bigramsB.has(bigram)) intersection++\n })\n\n return (2 * intersection) / (bigramsA.size + bigramsB.size)\n }\n\n private generateContentHash(element: Element): string {\n const text = element.textContent?.trim().slice(0, 500) || ''\n const tag = element.tagName.toLowerCase()\n const className = element.className?.toString() || ''\n const content = `${tag}:${className}:${text}`\n\n let hash = 5381\n for (let i = 0; i < content.length; i++) {\n hash = ((hash << 5) + hash) + content.charCodeAt(i)\n }\n return (hash >>> 0).toString(16)\n }\n}\n","import type { Comment } from '../types'\nimport { ElementAnchoring, AnchorResult } from '../anchoring'\nimport { getAvatarColor, getAvatarGradient, getInitials, renderAvatar } from './avatars'\nimport { avatarCache } from './avatarCache'\n\ninterface UniqueAuthor {\n name: string\n avatar_url: string | null\n}\n\ntype PinClickCallback = (commentId: string, pinElement: HTMLElement) => void\n\ninterface PinState {\n comment: Comment\n element: HTMLElement\n targetElement: Element | null\n anchorResult: AnchorResult\n}\n\nfunction commentsFingerprint(comments: Comment[]): string {\n return comments.map(c => `${c.id}:${c.resolved}:${c.content}:${c.replies?.length || 0}`).join('|')\n}\n\nexport class CommentPins {\n private container: HTMLElement\n private pinsContainer: HTMLElement\n private onClick: PinClickCallback\n private highlightedId: string | null = null\n private activeId: string | null = null\n private anchoring: ElementAnchoring\n private pins: Map<string, PinState> = new Map()\n private resizeObserver: ResizeObserver | null = null\n private repositionRAF: number | null = null\n private hoverTimers: Map<string, number> = new Map()\n private leaveTimers: Map<string, number> = new Map()\n private currentlyExpanded: string | null = null\n private boundScheduleReposition: () => void\n private lastFingerprint: string = ''\n\n constructor(container: HTMLElement, onClick: PinClickCallback) {\n this.container = container\n this.onClick = onClick\n this.anchoring = new ElementAnchoring()\n this.boundScheduleReposition = this.scheduleReposition.bind(this)\n\n this.pinsContainer = document.createElement('div')\n this.pinsContainer.className = 'tack-pins-container'\n this.container.appendChild(this.pinsContainer)\n\n // Set up resize observer for real-time repositioning\n this.setupResizeObserver()\n\n // Also listen to scroll and resize events\n window.addEventListener('scroll', this.boundScheduleReposition, { passive: true })\n window.addEventListener('resize', this.boundScheduleReposition, { passive: true })\n }\n\n private setupResizeObserver(): void {\n this.resizeObserver = new ResizeObserver(() => {\n this.scheduleReposition()\n })\n\n // Observe the document body for any layout changes\n this.resizeObserver.observe(document.body)\n }\n\n private scheduleReposition(): void {\n // Use requestAnimationFrame to batch repositioning\n if (this.repositionRAF) {\n cancelAnimationFrame(this.repositionRAF)\n }\n this.repositionRAF = requestAnimationFrame(() => {\n this.repositionAllPins()\n })\n }\n\n private repositionAllPins(): void {\n this.pins.forEach((pinState, commentId) => {\n // Re-resolve anchor if element is gone\n if (pinState.targetElement && !document.contains(pinState.targetElement)) {\n pinState.anchorResult = this.anchoring.resolve(pinState.comment.anchor)\n pinState.targetElement = pinState.anchorResult.element\n }\n\n this.positionPin(pinState)\n })\n }\n\n private getCurrentPagePath(): string {\n return window.location.pathname\n }\n\n render(comments: Comment[]): void {\n // Skip rebuild if comments haven't changed AND page path is the same\n const currentPath = this.getCurrentPagePath()\n const fp = currentPath + '::' + commentsFingerprint(comments)\n if (fp === this.lastFingerprint) return\n this.lastFingerprint = fp\n\n // Clear hover timers\n this.hoverTimers.forEach((t) => clearTimeout(t))\n this.hoverTimers.clear()\n this.leaveTimers.forEach((t) => clearTimeout(t))\n this.leaveTimers.clear()\n this.currentlyExpanded = null\n\n // Clear existing pins\n this.pinsContainer.innerHTML = ''\n this.pins.clear()\n\n // Only render top-level comments for the current page\n const topLevel = comments.filter((c) => !c.parent_id && c.page_path === currentPath)\n\n topLevel.forEach((comment) => {\n const pinState = this.createPin(comment)\n if (pinState) {\n this.pins.set(comment.id, pinState)\n this.pinsContainer.appendChild(pinState.element)\n }\n })\n }\n\n private getUniqueAuthors(comment: Comment): UniqueAuthor[] {\n const seen = new Set<string>()\n const authors: UniqueAuthor[] = []\n\n // Original author first\n const key = comment.user_id || comment.author_name\n seen.add(key)\n authors.push({ name: comment.author_name, avatar_url: comment.author_avatar_url })\n\n // Add unique reply authors in order of appearance\n if (comment.replies) {\n for (const reply of comment.replies) {\n const rKey = reply.user_id || reply.author_name\n if (!seen.has(rKey)) {\n seen.add(rKey)\n authors.push({ name: reply.author_name, avatar_url: reply.author_avatar_url })\n }\n }\n }\n\n return authors\n }\n\n private renderInsetAvatar(author: UniqueAuthor, size: number): string {\n const initials = getInitials(author.name)\n const gradient = getAvatarGradient(author.name)\n const fs = Math.round(size * 0.38)\n\n if (author.avatar_url) {\n const src = avatarCache.get(author.avatar_url)\n return `<img src=\"${src}\" alt=\"${initials}\" class=\"tack-pin-avatar-img\" style=\"width:${size}px;height:${size}px;\" onerror=\"this.outerHTML='<div class=\\\\'tack-pin-avatar-fallback\\\\' style=\\\\'width:${size}px;height:${size}px;background:${gradient};font-size:${fs}px;\\\\'>${initials}</div>'\" />`\n }\n return `<div class=\"tack-pin-avatar-fallback\" style=\"width:${size}px;height:${size}px;background:${gradient};font-size:${fs}px;\">${initials}</div>`\n }\n\n private renderStackedAvatar(author: UniqueAuthor, size: number): string {\n const initials = getInitials(author.name)\n const gradient = getAvatarGradient(author.name)\n const fs = Math.round(size * 0.38)\n\n if (author.avatar_url) {\n const src = avatarCache.get(author.avatar_url)\n return `<img src=\"${src}\" alt=\"${initials}\" class=\"tack-pin-avatar-img\" style=\"width:${size}px;height:${size}px;\" onerror=\"this.outerHTML='<div class=\\\\'tack-pin-avatar-fallback\\\\' style=\\\\'width:${size}px;height:${size}px;background:${gradient};font-size:${fs}px;\\\\'>${initials}</div>'\" />`\n }\n return `<div class=\"tack-pin-avatar-fallback\" style=\"width:${size}px;height:${size}px;background:${gradient};font-size:${fs}px;\">${initials}</div>`\n }\n\n private createPin(comment: Comment): PinState | null {\n let anchorResult: AnchorResult = { element: null, confidence: 'none', method: 'none' }\n let targetElement: Element | null = null\n\n if (comment.anchor) {\n anchorResult = this.anchoring.resolve(comment.anchor)\n targetElement = anchorResult.element\n } else if (comment.element_selector) {\n targetElement = document.querySelector(comment.element_selector)\n if (targetElement) {\n anchorResult = { element: targetElement, confidence: 'medium', method: 'css' }\n }\n }\n\n const pin = document.createElement('div')\n const isActive = comment.id === this.activeId\n const isHighlighted = comment.id === this.highlightedId\n const uniqueAuthors = this.getUniqueAuthors(comment)\n const isMultiAuthor = uniqueAuthors.length > 1\n\n pin.className = `tack-pin${comment.resolved ? ' resolved' : ''}${isActive ? ' active' : ''}${isHighlighted ? ' highlighted' : ''}${isMultiAuthor ? ' multi-author' : ''}`\n pin.dataset.id = comment.id\n\n if (anchorResult.confidence === 'low') {\n pin.classList.add('low-confidence')\n }\n\n // Build the hover preview card content\n const previewAuthor = uniqueAuthors[0]\n const timeAgo = this.formatTimeAgo(new Date(comment.created_at))\n const previewText = this.escapeHtml(comment.content)\n const replyCount = comment.replies?.length || 0\n\n const previewHTML = `<div class=\"tack-pin-preview\">\n <div class=\"tack-pin-preview-header\">\n <div class=\"tack-pin-preview-avatar\">${this.renderInsetAvatar(previewAuthor, 28)}</div>\n <div class=\"tack-pin-preview-meta\">\n <span class=\"tack-pin-preview-name\">${this.escapeHtml(previewAuthor.name)}</span>\n <span class=\"tack-pin-preview-time\">${timeAgo}</span>\n </div>\n </div>\n <p class=\"tack-pin-preview-text\">${previewText}</p>\n ${replyCount > 0 ? `<span class=\"tack-pin-preview-replies\">${replyCount} ${replyCount === 1 ? 'reply' : 'replies'}</span>` : ''}\n </div>`\n\n let pinHTML: string\n\n if (isMultiAuthor) {\n // Multi-author: white pill shell with stacked avatars inside\n const maxVisible = 3\n const visibleAuthors = uniqueAuthors.slice(0, maxVisible)\n const overflow = uniqueAuthors.length - maxVisible\n\n let avatarsHTML = '<div class=\"tack-pin-shell\"><div class=\"tack-pin-avatars\">'\n visibleAuthors.forEach((author, i) => {\n avatarsHTML += `<div class=\"tack-pin-avatar-stacked\" style=\"z-index:${i + 1}\">${this.renderStackedAvatar(author, 24)}</div>`\n })\n if (overflow > 0) {\n avatarsHTML += `<div class=\"tack-pin-avatar-stacked tack-pin-avatar-overflow\" style=\"z-index:${maxVisible + 1}\">+${overflow}</div>`\n }\n avatarsHTML += `</div>${previewHTML}</div>`\n pinHTML = avatarsHTML\n } else {\n // Single author: white circle shell with inset avatar\n const author = uniqueAuthors[0]\n pinHTML = `<div class=\"tack-pin-shell\">${this.renderInsetAvatar(author, 32)}${previewHTML}</div>`\n }\n\n // Resolved check dot\n if (comment.resolved) {\n pinHTML += `<div class=\"tack-pin-check\"></div>`\n }\n\n pin.innerHTML = pinHTML\n\n pin.addEventListener('click', (e) => {\n e.stopPropagation()\n this.collapsePin(comment.id, pin)\n this.onClick(comment.id, pin)\n })\n\n this.setupHoverListeners(pin, comment)\n\n const pinState: PinState = {\n comment,\n element: pin,\n targetElement,\n anchorResult,\n }\n\n this.positionPin(pinState)\n\n if (pin.style.left === '' && pin.style.top === '') {\n return null\n }\n\n return pinState\n }\n\n private positionPin(pinState: PinState): void {\n const { comment, element: pin, targetElement } = pinState\n // Offset so the tail tip sits at the anchor point\n // Tail tip: left 2px + 25% of 18px width = 6.5px from shell left\n const pinOffsetX = 7\n const pinOffsetY = 45\n\n // If we have anchor data and a target element, use relative positioning\n if (comment.anchor && targetElement) {\n const position = this.anchoring.calculatePinPosition(targetElement, comment.anchor)\n pin.style.left = `${position.x - pinOffsetX}px`\n pin.style.top = `${position.y - pinOffsetY}px`\n return\n }\n\n // Legacy: element selector without anchor\n if (targetElement) {\n const rect = targetElement.getBoundingClientRect()\n pin.style.left = `${rect.left + window.scrollX - pinOffsetX}px`\n pin.style.top = `${rect.top + window.scrollY - pinOffsetY}px`\n return\n }\n\n // Fall back to percentage coordinates (legacy or orphaned)\n if (comment.x_percent !== null && comment.y_percent !== null) {\n const x = (comment.x_percent / 100) * document.documentElement.scrollWidth\n const y = (comment.y_percent / 100) * document.documentElement.scrollHeight\n pin.style.left = `${x - pinOffsetX}px`\n pin.style.top = `${y - pinOffsetY}px`\n return\n }\n\n // Could not position - pin will be hidden\n pin.style.display = 'none'\n }\n\n private formatTimeAgo(date: Date): string {\n const seconds = Math.floor((Date.now() - date.getTime()) / 1000)\n if (seconds < 60) return 'just now'\n if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`\n if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`\n if (seconds < 604800) return `${Math.floor(seconds / 86400)}d ago`\n return date.toLocaleDateString()\n }\n\n private escapeHtml(text: string): string {\n const div = document.createElement('div')\n div.textContent = text\n return div.innerHTML\n }\n\n private setupHoverListeners(pin: HTMLElement, comment: Comment): void {\n pin.addEventListener('mouseenter', () => {\n // Cancel any pending collapse\n const leaveTimer = this.leaveTimers.get(comment.id)\n if (leaveTimer) {\n clearTimeout(leaveTimer)\n this.leaveTimers.delete(comment.id)\n }\n\n // Debounce the expand (150ms)\n const timer = window.setTimeout(() => {\n this.expandPin(comment.id, pin)\n this.hoverTimers.delete(comment.id)\n }, 150)\n this.hoverTimers.set(comment.id, timer)\n })\n\n pin.addEventListener('mouseleave', () => {\n // Cancel any pending expand\n const hoverTimer = this.hoverTimers.get(comment.id)\n if (hoverTimer) {\n clearTimeout(hoverTimer)\n this.hoverTimers.delete(comment.id)\n }\n\n // Grace period before collapse (120ms)\n const timer = window.setTimeout(() => {\n this.collapsePin(comment.id, pin)\n this.leaveTimers.delete(comment.id)\n }, 120)\n this.leaveTimers.set(comment.id, timer)\n })\n }\n\n private expandPin(commentId: string, pin: HTMLElement): void {\n // Skip if this pin is active (card already open)\n if (pin.classList.contains('active')) return\n\n // Collapse any other expanded pin first\n if (this.currentlyExpanded && this.currentlyExpanded !== commentId) {\n const prev = this.pins.get(this.currentlyExpanded)\n if (prev) {\n prev.element.classList.remove('expanded', 'expand-left')\n }\n }\n this.currentlyExpanded = commentId\n\n // Edge detection: expand left if too close to right viewport edge\n const pinRect = pin.getBoundingClientRect()\n if (window.innerWidth - pinRect.left < 300) {\n pin.classList.add('expand-left')\n } else {\n pin.classList.remove('expand-left')\n }\n\n pin.classList.add('expanded')\n }\n\n private collapsePin(commentId: string, pin: HTMLElement): void {\n pin.classList.remove('expanded', 'expand-left')\n if (this.currentlyExpanded === commentId) {\n this.currentlyExpanded = null\n }\n }\n\n setActive(commentId: string | null): void {\n this.activeId = commentId\n this.pins.forEach((pinState, id) => {\n pinState.element.classList.toggle('active', id === commentId)\n })\n }\n\n highlight(commentId: string): void {\n this.highlightedId = commentId\n\n // Update pin classes\n this.pins.forEach((pinState, id) => {\n pinState.element.classList.toggle('highlighted', id === commentId)\n })\n\n // Clear highlight after animation\n setTimeout(() => {\n this.highlightedId = null\n this.pins.forEach((pinState) => {\n pinState.element.classList.remove('highlighted')\n })\n }, 2000)\n }\n\n show(): void {\n this.pinsContainer.classList.add('visible')\n }\n\n hide(): void {\n this.pinsContainer.classList.remove('visible')\n }\n\n destroy(): void {\n this.hoverTimers.forEach((t) => clearTimeout(t))\n this.leaveTimers.forEach((t) => clearTimeout(t))\n if (this.resizeObserver) {\n this.resizeObserver.disconnect()\n }\n if (this.repositionRAF) {\n cancelAnimationFrame(this.repositionRAF)\n }\n window.removeEventListener('scroll', this.boundScheduleReposition)\n window.removeEventListener('resize', this.boundScheduleReposition)\n }\n}\n","import type { Comment } from '../types'\nimport type { WidgetUser } from '../auth'\nimport { getAvatarColor, getAvatarGradient, getInitials, renderAvatar } from './avatars'\n\ninterface CardCallbacks {\n onResolve: (commentId: string, resolved: boolean) => void\n onReply: (parentId: string, content: string) => void\n onEdit: (commentId: string, content: string) => void\n onDelete: (commentId: string) => void\n onClose: () => void\n}\n\nexport class CommentCard {\n private container: HTMLElement\n private card: HTMLElement\n private callbacks: CardCallbacks\n private currentComment: Comment | null = null\n private currentUser: WidgetUser | null = null\n private visible = false\n private editing = false\n private dropdownOpen = false\n private boundOnMouseDown: (e: MouseEvent) => void\n private boundOnKeyDown: (e: KeyboardEvent) => void\n\n constructor(container: HTMLElement, callbacks: CardCallbacks) {\n this.container = container\n this.callbacks = callbacks\n\n this.card = document.createElement('div')\n this.card.className = 'tack-card'\n this.card.setAttribute('role', 'dialog')\n this.card.setAttribute('aria-label', 'Comment details')\n this.container.appendChild(this.card)\n\n // Stop clicks inside card from propagating\n this.card.addEventListener('mousedown', (e) => e.stopPropagation())\n this.card.addEventListener('click', (e) => e.stopPropagation())\n\n this.boundOnMouseDown = this.onDocumentMouseDown.bind(this)\n this.boundOnKeyDown = this.onKeyDown.bind(this)\n }\n\n setUser(user: WidgetUser | null): void {\n this.currentUser = user\n }\n\n show(comment: Comment, pinElement: HTMLElement): void {\n // If already showing this comment, just hide\n if (this.visible && this.currentComment?.id === comment.id) {\n this.hide()\n return\n }\n\n this.currentComment = comment\n this.visible = true\n this.editing = false\n this.dropdownOpen = false\n\n this.renderCard(comment)\n this.positionNear(pinElement)\n this.card.classList.add('visible')\n this.attachListeners()\n\n document.addEventListener('mousedown', this.boundOnMouseDown, true)\n document.addEventListener('keydown', this.boundOnKeyDown)\n }\n\n hide(): void {\n if (!this.visible) return\n\n this.visible = false\n this.currentComment = null\n this.editing = false\n this.dropdownOpen = false\n this.card.classList.remove('visible')\n\n document.removeEventListener('mousedown', this.boundOnMouseDown, true)\n document.removeEventListener('keydown', this.boundOnKeyDown)\n\n this.callbacks.onClose()\n }\n\n isVisible(): boolean {\n return this.visible\n }\n\n updateComment(comment: Comment): void {\n if (!this.visible || this.currentComment?.id !== comment.id) return\n this.currentComment = comment\n this.renderCard(comment)\n this.attachListeners()\n }\n\n private onDocumentMouseDown(e: MouseEvent): void {\n if (this.card.contains(e.target as Node)) return\n const path = e.composedPath()\n if (path.includes(this.card)) return\n this.hide()\n }\n\n private onKeyDown(e: KeyboardEvent): void {\n if (e.key === 'Escape') {\n if (this.dropdownOpen) {\n this.dropdownOpen = false\n this.renderCard(this.currentComment!)\n this.attachListeners()\n return\n }\n if (this.editing) {\n this.editing = false\n this.renderCard(this.currentComment!)\n this.attachListeners()\n return\n }\n e.preventDefault()\n this.hide()\n }\n }\n\n private positionNear(pinElement: HTMLElement): void {\n const pinRect = pinElement.getBoundingClientRect()\n const cardWidth = 320\n const gap = 8\n const panelWidth = document.body.classList.contains('tack-panel-open') ? 360 : 0\n const maxRight = window.innerWidth - panelWidth - 16\n\n let left = pinRect.right + gap + window.scrollX\n if (left + cardWidth > maxRight + window.scrollX) {\n left = pinRect.left - cardWidth - gap + window.scrollX\n }\n left = Math.max(16, left)\n\n let top = pinRect.top + window.scrollY\n const maxBottom = window.innerHeight + window.scrollY - 16\n const estimatedHeight = 300\n if (top + estimatedHeight > maxBottom) {\n top = maxBottom - estimatedHeight\n }\n top = Math.max(16 + window.scrollY, top)\n\n this.card.style.left = `${left}px`\n this.card.style.top = `${top}px`\n }\n\n private isOwnComment(comment: Comment): boolean {\n // Prefer user_id match for authenticated users\n if (this.currentUser?.id && comment.user_id) {\n return this.currentUser.id === comment.user_id\n }\n // Fall back to name match for legacy comments\n const authorName = localStorage.getItem('tack_author_name') || ''\n return authorName === comment.author_name\n }\n\n private renderCard(comment: Comment): void {\n const replies = comment.replies || []\n const currentAuthor = localStorage.getItem('tack_author_name') || 'Anonymous'\n\n // Header\n let html = `\n <div class=\"tack-card-title-bar\">\n <span class=\"tack-card-title\">Comment</span>\n <div class=\"tack-card-title-actions\">\n <button class=\"tack-card-resolve-icon ${comment.resolved ? 'resolved' : ''}\" data-id=\"${comment.id}\" title=\"${comment.resolved ? 'Unresolve' : 'Resolve'}\" aria-label=\"${comment.resolved ? 'Unresolve comment' : 'Resolve comment'}\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M22 11.08V12a10 10 0 1 1-5.93-9.14\"/>\n <polyline points=\"22 4 12 14.01 9 11.01\"/>\n </svg>\n </button>\n <button class=\"tack-card-close-btn\" title=\"Close\" aria-label=\"Close comment\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n </div>\n </div>`\n\n // Resolved banner\n if (comment.resolved) {\n html += `\n <div class=\"tack-card-resolved-banner\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\"/>\n </svg>\n Resolved by ${this.escapeHtml(comment.author_name)}\n </div>`\n }\n\n // Main comment row\n html += this.renderCommentRow(comment, true)\n\n // Divider\n html += `<div class=\"tack-card-divider\"></div>`\n\n // Replies\n if (replies.length > 0) {\n replies.forEach((reply, index) => {\n html += this.renderCommentRow(reply, false)\n if (index < replies.length - 1) {\n html += `<div class=\"tack-card-divider\"></div>`\n }\n })\n html += `<div class=\"tack-card-divider\"></div>`\n }\n\n // Always-visible reply input\n html += `\n <div class=\"tack-card-reply-bar\">\n <div class=\"tack-card-reply-bar-avatar-wrap\">${renderAvatar(currentAuthor, this.currentUser?.avatar_url || null, 24)}</div>\n <div class=\"tack-card-reply-bar-input-wrap\">\n <input type=\"text\" class=\"tack-card-reply-bar-input\" placeholder=\"Reply…\" data-parent-id=\"${comment.id}\" />\n <button class=\"tack-card-reply-bar-send\" data-parent-id=\"${comment.id}\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"12\" y1=\"19\" x2=\"12\" y2=\"5\"/>\n <polyline points=\"5 12 12 5 19 12\"/>\n </svg>\n </button>\n </div>\n </div>`\n\n this.card.innerHTML = html\n }\n\n private renderCommentRow(comment: Comment, isMain: boolean): string {\n const timeAgo = this.formatTimeAgo(new Date(comment.created_at))\n const canEdit = isMain && this.isOwnComment(comment)\n\n let html = `<div class=\"tack-card-row${isMain ? ' main' : ''}\" data-comment-id=\"${comment.id}\">`\n\n // Row header: avatar + meta\n html += `\n <div class=\"tack-card-row-header\">\n <div class=\"tack-card-row-avatar-wrap\">${renderAvatar(comment.author_name, comment.author_avatar_url, 28)}</div>\n <div class=\"tack-card-row-meta\">\n <span class=\"tack-card-row-author\">${this.escapeHtml(comment.author_name)}</span>\n ${this.editing && isMain ? '<span class=\"tack-card-editing-badge\">Editing</span>' : ''}\n <span class=\"tack-card-row-time\">· ${timeAgo}</span>\n </div>\n ${canEdit ? `<button class=\"tack-card-more-btn\" data-comment-id=\"${comment.id}\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\">\n <circle cx=\"12\" cy=\"5\" r=\"1\"/><circle cx=\"12\" cy=\"12\" r=\"1\"/><circle cx=\"12\" cy=\"19\" r=\"1\"/>\n </svg>\n </button>` : ''}\n </div>`\n\n // Dropdown menu\n if (canEdit && this.dropdownOpen) {\n html += `\n <div class=\"tack-card-dropdown\">\n <button class=\"tack-card-dropdown-item\" data-action=\"edit\" aria-label=\"Edit comment\">Edit Comment</button>\n <button class=\"tack-card-dropdown-item delete\" data-action=\"delete\" aria-label=\"Delete comment\">Delete Comment</button>\n </div>`\n }\n\n // Screenshot\n if (isMain && comment.screenshot_url) {\n html += `<img src=\"${comment.screenshot_url}\" class=\"tack-card-row-screenshot\" alt=\"Screenshot\" />`\n }\n\n // Comment body / edit mode\n if (this.editing && isMain) {\n html += `\n <div class=\"tack-card-edit-wrap\">\n <textarea class=\"tack-card-edit-textarea\">${this.escapeHtml(comment.content)}</textarea>\n <div class=\"tack-card-edit-actions\">\n <button class=\"tack-card-edit-cancel\">Cancel</button>\n <button class=\"tack-card-edit-save\">Save</button>\n </div>\n </div>`\n } else {\n html += `<div class=\"tack-card-row-body\">${this.escapeHtml(comment.content)}</div>`\n }\n\n html += `</div>`\n return html\n }\n\n private attachListeners(): void {\n // Close button\n this.card.querySelector('.tack-card-close-btn')?.addEventListener('click', (e) => {\n e.stopPropagation()\n this.hide()\n })\n\n // Resolve icon\n this.card.querySelector('.tack-card-resolve-icon')?.addEventListener('click', (e) => {\n e.stopPropagation()\n if (!this.currentComment) return\n this.callbacks.onResolve(this.currentComment.id, !this.currentComment.resolved)\n })\n\n // More menu button\n this.card.querySelector('.tack-card-more-btn')?.addEventListener('click', (e) => {\n e.stopPropagation()\n this.dropdownOpen = !this.dropdownOpen\n this.renderCard(this.currentComment!)\n this.attachListeners()\n })\n\n // Dropdown items\n this.card.querySelectorAll('.tack-card-dropdown-item').forEach((btn) => {\n btn.addEventListener('click', (e) => {\n e.stopPropagation()\n const action = (btn as HTMLElement).dataset.action\n if (action === 'edit') {\n this.dropdownOpen = false\n this.editing = true\n this.renderCard(this.currentComment!)\n this.attachListeners()\n // Focus textarea\n const textarea = this.card.querySelector('.tack-card-edit-textarea') as HTMLTextAreaElement\n textarea?.focus()\n textarea?.setSelectionRange(textarea.value.length, textarea.value.length)\n } else if (action === 'delete') {\n this.dropdownOpen = false\n if (this.currentComment) {\n this.callbacks.onDelete(this.currentComment.id)\n this.hide()\n }\n }\n })\n })\n\n // Edit cancel\n this.card.querySelector('.tack-card-edit-cancel')?.addEventListener('click', (e) => {\n e.stopPropagation()\n this.editing = false\n this.renderCard(this.currentComment!)\n this.attachListeners()\n })\n\n // Edit save\n this.card.querySelector('.tack-card-edit-save')?.addEventListener('click', (e) => {\n e.stopPropagation()\n this.saveEdit()\n })\n\n // Edit textarea Cmd+Enter\n this.card.querySelector('.tack-card-edit-textarea')?.addEventListener('keydown', (e: Event) => {\n const ke = e as KeyboardEvent\n if (ke.key === 'Enter' && (ke.metaKey || ke.ctrlKey)) {\n ke.preventDefault()\n this.saveEdit()\n }\n })\n\n // Reply input — Enter to send\n const replyInput = this.card.querySelector('.tack-card-reply-bar-input') as HTMLInputElement\n replyInput?.addEventListener('keydown', (e: Event) => {\n const ke = e as KeyboardEvent\n if (ke.key === 'Enter' && !ke.shiftKey) {\n ke.preventDefault()\n this.submitReply()\n }\n })\n\n // Reply send button\n this.card.querySelector('.tack-card-reply-bar-send')?.addEventListener('click', (e) => {\n e.stopPropagation()\n this.submitReply()\n })\n }\n\n private saveEdit(): void {\n if (!this.currentComment) return\n const textarea = this.card.querySelector('.tack-card-edit-textarea') as HTMLTextAreaElement\n const content = textarea?.value.trim()\n if (!content) return\n this.editing = false\n this.callbacks.onEdit(this.currentComment.id, content)\n }\n\n private submitReply(): void {\n if (!this.currentComment) return\n const input = this.card.querySelector('.tack-card-reply-bar-input') as HTMLInputElement\n const content = input?.value.trim()\n if (!content) return\n this.callbacks.onReply(this.currentComment.id, content)\n input.value = ''\n }\n\n private formatTimeAgo(date: Date): string {\n const seconds = Math.floor((Date.now() - date.getTime()) / 1000)\n if (seconds < 60) return 'just now'\n if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`\n if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`\n if (seconds < 604800) return `${Math.floor(seconds / 86400)}d ago`\n return date.toLocaleDateString()\n }\n\n private escapeHtml(text: string): string {\n const div = document.createElement('div')\n div.textContent = text\n return div.innerHTML\n }\n}\n","import type { Comment } from './types'\n\ntype RealtimeCallback = (event: 'INSERT' | 'UPDATE' | 'DELETE', comment: Comment) => void\n\nexport class RealtimeSubscription {\n private ws: WebSocket | null = null\n private callback: RealtimeCallback\n private supabaseUrl: string\n private supabaseKey: string\n private versionId: string\n private reconnectAttempts = 0\n private maxReconnectAttempts = 5\n private heartbeatInterval: ReturnType<typeof setInterval> | null = null\n\n constructor(\n supabaseUrl: string,\n supabaseKey: string,\n versionId: string,\n callback: RealtimeCallback\n ) {\n this.supabaseUrl = supabaseUrl\n this.supabaseKey = supabaseKey\n this.versionId = versionId\n this.callback = callback\n }\n\n connect(): void {\n const wsUrl = this.supabaseUrl.replace('https://', 'wss://') + '/realtime/v1/websocket'\n const params = new URLSearchParams({\n apikey: this.supabaseKey,\n vsn: '1.0.0',\n })\n\n this.ws = new WebSocket(`${wsUrl}?${params}`)\n\n this.ws.onopen = () => {\n this.reconnectAttempts = 0\n this.subscribe()\n }\n\n this.ws.onmessage = (event) => {\n const data = JSON.parse(event.data)\n this.handleMessage(data)\n }\n\n this.ws.onclose = () => {\n this.attemptReconnect()\n }\n\n this.ws.onerror = (error) => {\n console.error('[Tack] Realtime error:', error)\n }\n }\n\n private subscribe(): void {\n if (!this.ws) return\n\n // Join the realtime channel for comments\n const joinMessage = {\n topic: `realtime:public:comments`,\n event: 'phx_join',\n payload: {\n config: {\n postgres_changes: [\n {\n event: '*',\n schema: 'public',\n table: 'comments',\n filter: `version_id=eq.${this.versionId}`,\n },\n ],\n },\n },\n ref: '1',\n }\n\n this.ws.send(JSON.stringify(joinMessage))\n\n // Start heartbeat\n this.startHeartbeat()\n }\n\n private handleMessage(data: { event: string; payload?: { data?: { type: string; record: Comment } } }): void {\n if (data.event === 'postgres_changes' && data.payload?.data) {\n const { type, record } = data.payload.data\n const eventType = type.toUpperCase() as 'INSERT' | 'UPDATE' | 'DELETE'\n this.callback(eventType, record)\n }\n }\n\n private startHeartbeat(): void {\n if (this.heartbeatInterval) {\n clearInterval(this.heartbeatInterval)\n }\n this.heartbeatInterval = setInterval(() => {\n if (this.ws?.readyState === WebSocket.OPEN) {\n this.ws.send(JSON.stringify({\n topic: 'phoenix',\n event: 'heartbeat',\n payload: {},\n ref: Date.now().toString(),\n }))\n }\n }, 30000)\n }\n\n private attemptReconnect(): void {\n if (this.reconnectAttempts >= this.maxReconnectAttempts) {\n console.error('[Tack] Max reconnect attempts reached')\n return\n }\n\n this.reconnectAttempts++\n const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000)\n\n setTimeout(() => {\n console.log(`[Tack] Reconnecting... (attempt ${this.reconnectAttempts})`)\n this.connect()\n }, delay)\n }\n\n disconnect(): void {\n if (this.heartbeatInterval) {\n clearInterval(this.heartbeatInterval)\n this.heartbeatInterval = null\n }\n if (this.ws) {\n this.ws.close()\n this.ws = null\n }\n }\n}\n","export interface PresenceUser {\n id: string\n name: string\n avatar_url: string | null\n}\n\ntype PresenceChangeCallback = (users: PresenceUser[]) => void\n\n/**\n * Manages real-time presence using Supabase Realtime Presence channels.\n * Uses raw WebSocket protocol (same as RealtimeSubscription) to avoid extra deps.\n */\nexport class PresenceManager {\n private ws: WebSocket | null = null\n private supabaseUrl: string\n private supabaseKey: string\n private projectId: string\n private currentUser: PresenceUser | null = null\n private pagePath: string = ''\n private users: Map<string, PresenceUser> = new Map()\n private onPresenceChange: PresenceChangeCallback | null = null\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null\n private reconnectAttempts = 0\n private joined = false\n private ref = 1\n\n constructor(supabaseUrl: string, supabaseKey: string, projectId: string) {\n this.supabaseUrl = supabaseUrl\n this.supabaseKey = supabaseKey\n this.projectId = projectId\n }\n\n join(user: PresenceUser): void {\n this.currentUser = user\n this.connect()\n }\n\n leave(): void {\n this.joined = false\n this.currentUser = null\n this.users.clear()\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer)\n this.heartbeatTimer = null\n }\n if (this.ws) {\n this.ws.close()\n this.ws = null\n }\n }\n\n updatePagePath(path: string): void {\n this.pagePath = path\n if (this.joined && this.ws?.readyState === WebSocket.OPEN) {\n this.trackPresence()\n }\n }\n\n setOnPresenceChange(cb: PresenceChangeCallback): void {\n this.onPresenceChange = cb\n }\n\n private connect(): void {\n const wsUrl = this.supabaseUrl.replace('https://', 'wss://') + '/realtime/v1/websocket'\n const params = new URLSearchParams({\n apikey: this.supabaseKey,\n vsn: '1.0.0',\n })\n\n this.ws = new WebSocket(`${wsUrl}?${params}`)\n\n this.ws.onopen = () => {\n this.reconnectAttempts = 0\n this.joinChannel()\n }\n\n this.ws.onmessage = (event) => {\n try {\n const data = JSON.parse(event.data)\n this.handleMessage(data)\n } catch {\n // Ignore parse errors\n }\n }\n\n this.ws.onclose = () => {\n if (this.currentUser) {\n this.attemptReconnect()\n }\n }\n\n this.ws.onerror = () => {\n // Error handler, reconnect will be triggered by onclose\n }\n }\n\n private nextRef(): string {\n return (this.ref++).toString()\n }\n\n private joinChannel(): void {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return\n\n const topic = `realtime:presence:${this.projectId}`\n this.ws.send(JSON.stringify({\n topic,\n event: 'phx_join',\n payload: {\n config: {\n presence: { key: this.currentUser!.id },\n },\n },\n ref: this.nextRef(),\n }))\n\n this.startHeartbeat()\n\n // Track presence after a short delay to ensure channel is joined\n setTimeout(() => {\n this.joined = true\n this.trackPresence()\n }, 200)\n }\n\n private trackPresence(): void {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN || !this.currentUser) return\n\n const topic = `realtime:presence:${this.projectId}`\n this.ws.send(JSON.stringify({\n topic,\n event: 'presence',\n payload: {\n type: 'presence',\n event: 'track',\n payload: {\n user_id: this.currentUser.id,\n name: this.currentUser.name,\n avatar_url: this.currentUser.avatar_url,\n page_path: this.pagePath,\n online_at: Date.now(),\n },\n },\n ref: this.nextRef(),\n }))\n }\n\n private handleMessage(data: { event: string; topic?: string; payload?: any }): void {\n if (data.event === 'presence_state') {\n // Full sync of all present users\n this.users.clear()\n const state = data.payload || {}\n for (const key of Object.keys(state)) {\n const metas = state[key]?.metas\n if (metas?.length > 0) {\n const meta = metas[0]\n this.users.set(key, {\n id: meta.user_id || key,\n name: meta.name || 'Unknown',\n avatar_url: meta.avatar_url || null,\n })\n }\n }\n this.notifyChange()\n } else if (data.event === 'presence_diff') {\n const { joins, leaves } = data.payload || {}\n\n // Handle joins\n if (joins) {\n for (const key of Object.keys(joins)) {\n const metas = joins[key]?.metas\n if (metas?.length > 0) {\n const meta = metas[0]\n this.users.set(key, {\n id: meta.user_id || key,\n name: meta.name || 'Unknown',\n avatar_url: meta.avatar_url || null,\n })\n }\n }\n }\n\n // Handle leaves\n if (leaves) {\n for (const key of Object.keys(leaves)) {\n this.users.delete(key)\n }\n }\n\n this.notifyChange()\n }\n }\n\n private notifyChange(): void {\n const userList = Array.from(this.users.values())\n this.onPresenceChange?.(userList)\n }\n\n private startHeartbeat(): void {\n if (this.heartbeatTimer) clearInterval(this.heartbeatTimer)\n this.heartbeatTimer = setInterval(() => {\n if (this.ws?.readyState === WebSocket.OPEN) {\n this.ws.send(JSON.stringify({\n topic: 'phoenix',\n event: 'heartbeat',\n payload: {},\n ref: this.nextRef(),\n }))\n }\n }, 30000)\n }\n\n private attemptReconnect(): void {\n if (this.reconnectAttempts >= 5) return\n this.reconnectAttempts++\n const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000)\n setTimeout(() => {\n if (this.currentUser) {\n this.connect()\n }\n }, delay)\n }\n}\n","import type { TackConfig, Comment, Version, CommentAnchor } from './types'\nimport { TackAPI } from './api'\nimport { WidgetAuth } from './auth'\nimport { createStyles } from './styles'\nimport { ElementSelector } from './selector'\nimport { CommentForm } from './ui/form'\nimport { CommentPanel } from './ui/panel'\nimport { CommentPins } from './ui/pins'\nimport { CommentCard } from './ui/card'\nimport { RealtimeSubscription } from './realtime'\nimport { PresenceManager } from './presence'\nimport { avatarCache } from './ui/avatarCache'\n\nexport class TackWidget {\n private config: TackConfig\n private api: TackAPI\n private auth: WidgetAuth\n private container: HTMLElement | null = null\n private shadowRoot: ShadowRoot | null = null\n private selector: ElementSelector | null = null\n private form: CommentForm | null = null\n private panel: CommentPanel | null = null\n private pins: CommentPins | null = null\n private card: CommentCard | null = null\n private realtime: RealtimeSubscription | null = null\n private presence: PresenceManager | null = null\n private commentMode = false\n private comments: Comment[] = []\n private currentVersion: Version | null = null\n private currentPagePath: string = ''\n private originalPushState: typeof history.pushState | null = null\n private originalReplaceState: typeof history.replaceState | null = null\n private pollingInterval: ReturnType<typeof setInterval> | null = null\n private boundOnUrlChange: (() => void) | null = null\n private boundOnKeyDown: ((e: KeyboardEvent) => void) | null = null\n private navigationIndex: number = -1\n\n constructor(config: TackConfig) {\n this.config = {\n apiUrl: 'https://tacktext.vercel.app/api',\n supabaseUrl: 'https://etpavqvnpupqdxdptkhc.supabase.co',\n supabaseKey: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImV0cGF2cXZucHVwcWR4ZHB0a2hjIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzQ4MzU4MzgsImV4cCI6MjA5MDQxMTgzOH0.GMSIuPmMaEkNhss_laSm_NchihN_KF9VyyEh5YH4Ru0',\n ...config,\n }\n this.api = new TackAPI(this.config.apiUrl!, this.config.projectId)\n this.auth = new WidgetAuth(this.config.apiUrl!)\n }\n\n async mount(): Promise<void> {\n // Create container with Shadow DOM\n this.container = document.createElement('div')\n this.container.id = 'tack-widget'\n this.shadowRoot = this.container.attachShadow({ mode: 'open' })\n\n // Add styles to shadow DOM\n const styles = document.createElement('style')\n styles.textContent = createStyles()\n this.shadowRoot.appendChild(styles)\n\n // Add styles to main document for pushing page content\n this.injectPageStyles()\n\n // Create main wrapper\n const wrapper = document.createElement('div')\n wrapper.className = 'tack-wrapper'\n this.shadowRoot.appendChild(wrapper)\n\n // Initialize auth\n this.auth.setOnAuthChange((user) => {\n this.api.setAuthToken(user ? this.auth.getSessionToken() : null)\n this.form?.setUser(user)\n this.card?.setUser(user)\n this.panel?.setUser(user)\n\n // Manage presence based on auth state\n if (user) {\n this.joinPresence(user)\n } else {\n this.presence?.leave()\n this.presence = null\n }\n })\n\n await this.auth.init()\n if (this.auth.isAuthenticated()) {\n this.api.setAuthToken(this.auth.getSessionToken())\n }\n\n // Handle 401 from API\n this.api.setOnUnauthorized(() => {\n this.auth.signOut()\n this.showToast('Session expired. Please sign in again.')\n })\n\n // Initialize components\n this.selector = new ElementSelector(this.onElementSelected.bind(this))\n this.form = new CommentForm(wrapper, this.onCommentSubmit.bind(this))\n this.form.setUser(this.auth.getUser())\n this.form.setOnHide(() => {\n if (this.panel?.isOpen()) {\n this.enableCommentMode()\n }\n })\n this.form.setOnSignIn(() => this.signIn())\n this.panel = new CommentPanel(wrapper, {\n onCommentClick: this.onCommentClick.bind(this),\n onResolve: this.onResolve.bind(this),\n onReply: this.onReply.bind(this),\n onClose: this.onPanelClose.bind(this),\n onAddComment: this.enableCommentMode.bind(this),\n onShare: this.onShare.bind(this),\n })\n this.card = new CommentCard(wrapper, {\n onResolve: this.onResolve.bind(this),\n onReply: this.onReply.bind(this),\n onEdit: this.onEdit.bind(this),\n onDelete: this.onDelete.bind(this),\n onClose: this.onCardClose.bind(this),\n })\n this.card.setUser(this.auth.getUser())\n this.panel.setUser(this.auth.getUser())\n this.panel.setProjectId(this.config.projectId)\n this.pins = new CommentPins(wrapper, this.onPinClick.bind(this))\n\n // Add toggle button\n this.createToggleButton(wrapper)\n\n // Mount to document\n document.body.appendChild(this.container)\n\n // Load initial data\n await this.loadComments()\n\n // Check for deep link\n this.handleDeepLink()\n\n // Set up realtime subscription\n this.subscribeToRealtime()\n\n // Set up keyboard shortcuts\n this.setupKeyboardShortcuts()\n\n // Set up URL change detection for SPAs\n this.setupUrlChangeDetection()\n\n // Join presence if authenticated\n if (this.auth.isAuthenticated()) {\n const user = this.auth.getUser()!\n this.joinPresence(user)\n }\n }\n\n private joinPresence(user: { id: string; name: string; avatar_url: string | null }): void {\n if (!this.config.supabaseUrl || !this.config.supabaseKey) return\n\n this.presence?.leave()\n this.presence = new PresenceManager(\n this.config.supabaseUrl,\n this.config.supabaseKey,\n this.config.projectId\n )\n this.presence.setOnPresenceChange((users) => {\n this.panel?.renderPresence(users)\n })\n this.presence.join(user)\n this.presence.updatePagePath(this.getPagePath())\n }\n\n async signIn(): Promise<void> {\n try {\n await this.auth.signIn()\n } catch (error) {\n console.error('[Tack] Sign in failed:', error)\n }\n }\n\n private getPagePath(): string {\n return window.location.pathname\n }\n\n private setupUrlChangeDetection(): void {\n this.currentPagePath = this.getPagePath()\n\n this.boundOnUrlChange = this.onUrlChange.bind(this)\n window.addEventListener('popstate', this.boundOnUrlChange)\n\n this.originalPushState = history.pushState.bind(history)\n this.originalReplaceState = history.replaceState.bind(history)\n\n history.pushState = (...args) => {\n this.originalPushState!(...args)\n this.onUrlChange()\n }\n\n history.replaceState = (...args) => {\n this.originalReplaceState!(...args)\n this.onUrlChange()\n }\n }\n\n private onUrlChange(): void {\n const newPath = this.getPagePath()\n if (newPath !== this.currentPagePath) {\n this.currentPagePath = newPath\n this.presence?.updatePagePath(newPath)\n setTimeout(() => {\n this.loadComments()\n }, 100)\n }\n }\n\n private createToggleButton(wrapper: HTMLElement): void {\n const button = document.createElement('button')\n button.className = 'tack-toggle'\n button.setAttribute('aria-label', 'Toggle comments')\n button.setAttribute('aria-expanded', 'false')\n button.innerHTML = `\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\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\"></path>\n </svg>\n `\n button.addEventListener('click', () => this.toggleCommentMode())\n wrapper.appendChild(button)\n }\n\n private toggleCommentMode(): void {\n if (this.panel?.isOpen()) {\n this.panel.hide()\n this.setPanelOpen(false)\n this.pins?.hide()\n this.disableCommentMode()\n this.updateToggleAria(false)\n } else {\n this.panel?.show()\n this.setPanelOpen(true)\n this.pins?.show()\n this.container?.classList.add('tack-active')\n this.enableCommentMode()\n this.updateToggleAria(true)\n }\n }\n\n private enableCommentMode(): void {\n this.commentMode = true\n this.selector?.enable()\n }\n\n private disableCommentMode(): void {\n this.commentMode = false\n this.selector?.disable()\n this.form?.hide()\n this.container?.classList.remove('tack-active')\n }\n\n private onPanelClose(): void {\n this.setPanelOpen(false)\n this.pins?.hide()\n this.disableCommentMode()\n this.updateToggleAria(false)\n }\n\n private onShare(): void {\n const url = new URL(window.location.href)\n url.searchParams.set('tack_open', 'true')\n\n navigator.clipboard.writeText(url.toString()).then(() => {\n this.showToast('Link copied to clipboard')\n }).catch(() => {\n this.showToast('Failed to copy link')\n })\n }\n\n private injectPageStyles(): void {\n const styleId = 'tack-page-styles'\n if (document.getElementById(styleId)) return\n\n const style = document.createElement('style')\n style.id = styleId\n style.textContent = `\n body.tack-panel-open {\n margin-right: 360px !important;\n transition: margin-right 250ms cubic-bezier(0.16, 1, 0.3, 1) !important;\n }\n body {\n transition: margin-right 250ms cubic-bezier(0.16, 1, 0.3, 1);\n }\n `\n document.head.appendChild(style)\n }\n\n private setPanelOpen(open: boolean): void {\n if (open) {\n document.body.classList.add('tack-panel-open')\n } else {\n document.body.classList.remove('tack-panel-open')\n }\n }\n\n private updateToggleAria(expanded: boolean): void {\n const toggle = this.shadowRoot?.querySelector('.tack-toggle')\n if (toggle) {\n toggle.setAttribute('aria-expanded', String(expanded))\n }\n }\n\n private setupKeyboardShortcuts(): void {\n this.boundOnKeyDown = this.handleKeyDown.bind(this)\n document.addEventListener('keydown', this.boundOnKeyDown)\n }\n\n private handleKeyDown(e: KeyboardEvent): void {\n // Ignore when modifier keys are held\n if (e.metaKey || e.ctrlKey || e.altKey) return\n\n // Ignore when typing in an input, textarea, or contenteditable\n const active = document.activeElement\n if (active && (\n active.tagName === 'INPUT' ||\n active.tagName === 'TEXTAREA' ||\n (active as HTMLElement).isContentEditable\n )) return\n\n // Also check shadow DOM active element\n const shadowActive = this.shadowRoot?.activeElement\n if (shadowActive && (\n shadowActive.tagName === 'INPUT' ||\n shadowActive.tagName === 'TEXTAREA' ||\n (shadowActive as HTMLElement).isContentEditable\n )) return\n\n // Ignore when form or card is open\n if (this.form?.isFormVisible() || this.card?.isVisible()) return\n\n const key = e.key.toLowerCase()\n\n if (key === 'c') {\n e.preventDefault()\n this.toggleCommentMode()\n return\n }\n\n // N/P only work when panel is open\n if (!this.panel?.isOpen()) return\n\n if (key === 'n') {\n e.preventDefault()\n this.navigateComments('next')\n } else if (key === 'p') {\n e.preventDefault()\n this.navigateComments('prev')\n }\n }\n\n private navigateComments(direction: 'next' | 'prev'): void {\n const ids = this.panel?.getFilteredCommentIds() || []\n if (ids.length === 0) return\n\n if (direction === 'next') {\n this.navigationIndex = this.navigationIndex < ids.length - 1\n ? this.navigationIndex + 1\n : 0\n } else {\n this.navigationIndex = this.navigationIndex > 0\n ? this.navigationIndex - 1\n : ids.length - 1\n }\n\n const commentId = ids[this.navigationIndex]\n const comment = this.comments.find(c => c.id === commentId)\n if (comment) {\n this.pins?.highlight(commentId)\n this.panel?.scrollToComment(commentId)\n this.scrollToComment(comment)\n }\n }\n\n private async loadComments(): Promise<void> {\n try {\n const data = await this.api.getComments()\n this.comments = data.comments\n this.currentVersion = data.version\n this.navigationIndex = -1\n\n // Pre-warm avatar blob cache before rendering to avoid repeated CDN hits\n const avatarUrls = this.comments.flatMap(c => [\n c.author_avatar_url,\n ...(c.replies?.map(r => r.author_avatar_url) || [])\n ])\n await avatarCache.preload(avatarUrls)\n\n this.pins?.render(this.comments)\n this.panel?.render(this.comments)\n } catch (error) {\n console.error('[Tack] Failed to load comments:', error)\n }\n }\n\n private onElementSelected(target: { element: Element | null; anchor: CommentAnchor }): void {\n if (this.form?.isFormVisible()) return\n this.selector?.disable()\n this.form?.show(target)\n }\n\n private async onCommentSubmit(data: {\n content: string\n authorName: string\n authorEmail?: string\n anchor: CommentAnchor\n screenshot?: string\n }): Promise<void> {\n try {\n const createData: Record<string, unknown> = {\n content: data.content,\n element_selector: data.anchor.cssSelector,\n x_percent: data.anchor.relativeX * 100,\n y_percent: data.anchor.relativeY * 100,\n anchor: data.anchor,\n page_path: this.getPagePath(),\n screenshot_data: data.screenshot,\n }\n // Only send author_name if not authenticated (server sets it otherwise)\n if (!this.auth.isAuthenticated()) {\n createData.author_name = data.authorName\n createData.author_email = data.authorEmail\n }\n\n const comment = await this.api.createComment(createData as any)\n const commentWithScreenshot = {\n ...comment,\n screenshot_url: comment.screenshot_url || data.screenshot || null,\n }\n this.comments.push(commentWithScreenshot)\n this.pins?.render(this.comments)\n this.panel?.render(this.comments)\n this.showToast('Comment added')\n if (this.panel?.isOpen()) {\n this.enableCommentMode()\n }\n } catch (error) {\n console.error('[Tack] Failed to submit comment:', error)\n this.showToast('Failed to add comment')\n }\n }\n\n private onCommentClick(comment: Comment): void {\n this.pins?.highlight(comment.id)\n this.scrollToComment(comment)\n }\n\n private onPinClick(commentId: string, pinElement: HTMLElement): void {\n const comment = this.comments.find((c) => c.id === commentId)\n if (comment) {\n this.card?.show(comment, pinElement)\n this.pins?.setActive(commentId)\n this.pins?.highlight(commentId)\n if (this.panel?.isOpen()) {\n this.panel.scrollToComment(commentId)\n }\n }\n }\n\n private onCardClose(): void {\n this.pins?.setActive(null)\n }\n\n private async onResolve(commentId: string, resolved: boolean): Promise<void> {\n try {\n await this.api.updateComment(commentId, { resolved })\n const comment = this.comments.find((c) => c.id === commentId)\n if (comment) {\n comment.resolved = resolved\n this.pins?.render(this.comments)\n this.panel?.render(this.comments)\n this.card?.updateComment(comment)\n }\n } catch (error) {\n console.error('[Tack] Failed to update comment:', error)\n }\n }\n\n private async onReply(parentId: string, content: string): Promise<void> {\n try {\n const createData: Record<string, unknown> = {\n content,\n parent_id: parentId,\n page_path: this.getPagePath(),\n }\n if (!this.auth.isAuthenticated()) {\n createData.author_name = localStorage.getItem('tack_author_name') || 'Anonymous'\n }\n\n const reply = await this.api.createComment(createData as any)\n const parent = this.comments.find((c) => c.id === parentId)\n if (parent) {\n parent.replies = parent.replies || []\n parent.replies.push(reply)\n this.panel?.render(this.comments)\n this.card?.updateComment(parent)\n }\n } catch (error) {\n console.error('[Tack] Failed to submit reply:', error)\n }\n }\n\n private async onEdit(commentId: string, content: string): Promise<void> {\n try {\n await this.api.updateComment(commentId, { content })\n const comment = this.comments.find((c) => c.id === commentId)\n if (comment) {\n comment.content = content\n this.pins?.render(this.comments)\n this.panel?.render(this.comments)\n this.card?.updateComment(comment)\n }\n } catch (error) {\n console.error('[Tack] Failed to edit comment:', error)\n this.showToast('Failed to edit comment')\n }\n }\n\n private async onDelete(commentId: string): Promise<void> {\n try {\n await this.api.deleteComment(commentId)\n this.comments = this.comments.filter((c) => c.id !== commentId)\n this.pins?.render(this.comments)\n this.panel?.render(this.comments)\n this.showToast('Comment deleted')\n } catch (error) {\n console.error('[Tack] Failed to delete comment:', error)\n this.showToast('Failed to delete comment')\n }\n }\n\n private scrollToComment(comment: Comment): void {\n if (comment.element_selector) {\n const element = document.querySelector(comment.element_selector)\n element?.scrollIntoView({ behavior: 'smooth', block: 'center' })\n } else if (comment.x_percent !== null && comment.y_percent !== null) {\n const x = (comment.x_percent / 100) * document.documentElement.scrollWidth\n const y = (comment.y_percent / 100) * document.documentElement.scrollHeight\n window.scrollTo({ left: x - window.innerWidth / 2, top: y - window.innerHeight / 2, behavior: 'smooth' })\n }\n }\n\n private handleDeepLink(): void {\n const params = new URLSearchParams(window.location.search)\n\n const shouldOpen = params.get('tack_open')\n if (shouldOpen === 'true') {\n this.panel?.show()\n this.setPanelOpen(true)\n this.pins?.show()\n this.container?.classList.add('tack-active')\n this.updateToggleAria(true)\n }\n\n const commentId = params.get('tack_comment')\n if (commentId) {\n const comment = this.comments.find((c) => c.id === commentId)\n if (comment) {\n this.panel?.show()\n this.setPanelOpen(true)\n this.pins?.show()\n this.container?.classList.add('tack-active')\n this.updateToggleAria(true)\n this.panel?.scrollToComment(commentId)\n this.pins?.highlight(commentId)\n this.scrollToComment(comment)\n }\n }\n }\n\n private subscribeToRealtime(): void {\n if (!this.currentVersion || !this.config.supabaseUrl || !this.config.supabaseKey) {\n this.pollingInterval = setInterval(() => this.loadComments(), 30000)\n return\n }\n\n this.realtime = new RealtimeSubscription(\n this.config.supabaseUrl,\n this.config.supabaseKey,\n this.currentVersion.id,\n (event, comment) => {\n this.handleRealtimeEvent(event, comment)\n }\n )\n this.realtime.connect()\n }\n\n private handleRealtimeEvent(event: 'INSERT' | 'UPDATE' | 'DELETE', comment: Comment): void {\n switch (event) {\n case 'INSERT':\n const alreadyExists = this.comments.some((c) => c.id === comment.id) ||\n this.comments.some((c) => c.replies?.some((r) => r.id === comment.id))\n if (alreadyExists) {\n return\n }\n\n if (comment.parent_id) {\n const parent = this.comments.find((c) => c.id === comment.parent_id)\n if (parent) {\n parent.replies = parent.replies || []\n parent.replies.push(comment)\n }\n } else {\n this.comments.push(comment)\n }\n this.showToast(`New comment from ${comment.author_name}`)\n break\n\n case 'UPDATE':\n const existing = this.comments.find((c) => c.id === comment.id)\n if (existing) {\n Object.assign(existing, comment)\n }\n break\n\n case 'DELETE':\n this.comments = this.comments.filter((c) => c.id !== comment.id)\n break\n }\n\n this.pins?.render(this.comments)\n this.panel?.render(this.comments)\n }\n\n private showToast(message: string): void {\n if (!this.shadowRoot) return\n\n const toast = document.createElement('div')\n toast.className = 'tack-toast'\n toast.textContent = message\n toast.style.cssText = `\n position: fixed;\n bottom: 80px;\n right: 20px;\n background: #1a1a1a;\n color: white;\n padding: 12px 20px;\n border-radius: 8px;\n font-size: 14px;\n z-index: 10002;\n animation: tack-toast-in 0.3s ease;\n `\n this.shadowRoot.appendChild(toast)\n\n setTimeout(() => {\n toast.remove()\n }, 3000)\n }\n\n destroy(): void {\n if (this.pollingInterval) {\n clearInterval(this.pollingInterval)\n this.pollingInterval = null\n }\n\n this.selector?.disable()\n this.realtime?.disconnect()\n this.presence?.leave()\n this.pins?.destroy()\n avatarCache.destroy()\n\n if (this.boundOnKeyDown) {\n document.removeEventListener('keydown', this.boundOnKeyDown)\n this.boundOnKeyDown = null\n }\n\n if (this.boundOnUrlChange) {\n window.removeEventListener('popstate', this.boundOnUrlChange)\n this.boundOnUrlChange = null\n }\n\n if (this.originalPushState) {\n history.pushState = this.originalPushState\n }\n if (this.originalReplaceState) {\n history.replaceState = this.originalReplaceState\n }\n\n // Remove page styles and body class\n document.getElementById('tack-page-styles')?.remove()\n document.body.classList.remove('tack-panel-open')\n\n this.container?.remove()\n }\n}\n","import { TackWidget } from './widget'\nimport type { TackConfig } from './types'\n\nexport type { TackConfig }\n\nexport const Tack = {\n init(config: TackConfig): TackWidget {\n const widget = new TackWidget(config)\n widget.mount()\n return widget\n },\n}\n\nexport { TackWidget }\n"],"mappings":"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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA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","names":["TackAPI","baseUrl","projectId","token","cb","headers","res","data","id","url","WidgetAuth","apiUrl","token","res","user","resolve","reject","baseUrl","popup","expectedOrigin","handleMessage","event","pollTimer","cb","createStyles","ElementSelector","callback","target","rect","pad","radius","relativeX","relativeY","anchor","element","path","current","selector","classes","dataTestId","parent","siblings","child","index","fullSelector","parts","part","text","prefix","suffix","parentText","endIndex","tag","className","content","hash","i","id","ignoreTackElements","el","captureElementWithContext","selector","x","y","html2canvas","element","rect","padding","viewportX","viewportY","captureWidth","captureHeight","ignoreTackElements","e","CommentForm","container","onSubmit","callback","user","form","e","textarea","sendBtn","ke","target","captureElementWithContext","rect","viewportX","viewportY","fw","formWidth","formHeight","left","top","maxHeight","content","authorName","submitData","error","text","div","AvatarCache","url","urls","unique","u","promise","r","blob","blobUrl","avatarCache","AVATAR_GRADIENTS","AVATAR_COLORS","to","hashName","name","hash","i","getAvatarColor","getAvatarGradient","from","getInitials","parts","renderAvatar","avatarUrl","size","extraClass","fontSize","cls","avatarCache","STORAGE_KEY","loadFilters","stored","parsed","saveFilters","filters","CommentPanel","container","callbacks","user","projectId","key","commentId","filtered","changed","c","strip","searchInput","searchClear","val","e","target","dropdown","f","check","empty","item","action","badge","btn","count","comments","users","presenceBar","header","maxShow","visible","overflow","html","u","renderAvatar","content","activeFilterCount","comment","row","id","dot","resolveBtn","moreBtn","parts","s","pagePath","path","isHighlighted","timeAgo","currentPath","isCurrentPage","pageName","hasScreenshot","unread","result","userId","userName","q","r","a","b","aUnread","bUnread","aReplies","bReplies","date","seconds","text","div","ElementAnchoring","anchor","result","element","rect","x","y","textQuote","walker","node","text","bestMatch","targetText","score","parent","parentText","elementText","index","endIndex","a","b","bigramsA","bigramsB","i","intersection","bigram","tag","className","content","hash","commentsFingerprint","comments","c","CommentPins","container","onClick","ElementAnchoring","pinState","commentId","currentPath","fp","t","comment","seen","authors","key","reply","rKey","author","size","initials","getInitials","gradient","getAvatarGradient","fs","avatarCache","anchorResult","targetElement","pin","isActive","isHighlighted","uniqueAuthors","isMultiAuthor","previewAuthor","timeAgo","previewText","replyCount","previewHTML","pinHTML","visibleAuthors","overflow","avatarsHTML","i","e","pinOffsetX","pinOffsetY","position","rect","x","y","date","seconds","text","div","leaveTimer","timer","hoverTimer","prev","pinRect","id","CommentCard","container","callbacks","e","user","comment","pinElement","pinRect","cardWidth","gap","panelWidth","maxRight","left","top","maxBottom","estimatedHeight","replies","currentAuthor","html","reply","index","renderAvatar","isMain","timeAgo","canEdit","btn","action","textarea","ke","content","input","date","seconds","text","div","RealtimeSubscription","supabaseUrl","supabaseKey","versionId","callback","wsUrl","params","event","data","error","joinMessage","type","record","eventType","delay","PresenceManager","supabaseUrl","supabaseKey","projectId","user","path","cb","wsUrl","params","event","data","topic","state","key","metas","meta","joins","leaves","userList","delay","TackWidget","config","TackAPI","WidgetAuth","styles","createStyles","wrapper","user","ElementSelector","CommentForm","CommentPanel","CommentCard","CommentPins","PresenceManager","users","error","args","newPath","button","url","styleId","style","open","expanded","toggle","active","shadowActive","key","direction","ids","commentId","comment","c","data","avatarUrls","r","avatarCache","target","createData","commentWithScreenshot","pinElement","resolved","parentId","content","reply","parent","x","y","params","RealtimeSubscription","event","existing","message","toast","Tack","config","widget","TackWidget"]}