@tacktext/widget 0.1.11 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../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/drag.ts","../src/ui/panel.ts","../src/anchoring.ts","../src/ui/pins.ts","../src/ui/card.ts","../src/realtime.ts","../src/presence.ts","../src/ui/fab.ts","../src/widget.ts"],"sourcesContent":["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","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 addReaction(commentId: string, emoji: string): Promise<void> {\n const res = await fetch(`${this.baseUrl}/comments/${commentId}/reactions`, {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify({ emoji }),\n })\n await this.handleResponse(res)\n if (!res.ok) throw new Error('Failed to add reaction')\n }\n\n async removeReaction(commentId: string, emoji: string): Promise<void> {\n const res = await fetch(`${this.baseUrl}/comments/${commentId}/reactions`, {\n method: 'DELETE',\n headers: this.getHeaders(),\n body: JSON.stringify({ emoji }),\n })\n await this.handleResponse(res)\n if (!res.ok) throw new Error('Failed to remove reaction')\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 /* ── FAB Toolbar ── */\n\n /* Collapsed: 44px dark circle, positioned via top + left/right (set by JS) */\n .tack-fab {\n position: fixed;\n top: calc(100vh - 64px);\n right: 20px;\n height: 44px;\n width: 44px;\n border-radius: 22px;\n background: #18181B;\n display: flex;\n align-items: center;\n justify-content: flex-end;\n padding: 5px;\n gap: 2px;\n box-shadow: 0 2px 8px rgba(0,0,0,0.22), 0 3px 12px rgba(0,0,0,0.08);\n z-index: 10000;\n transition: width 180ms cubic-bezier(0.55, 0, 1, 0.45);\n will-change: width;\n cursor: grab;\n touch-action: none;\n }\n\n .tack-fab:hover {\n box-shadow: 0 3px 10px rgba(0,0,0,0.25), 0 5px 18px rgba(0,0,0,0.10);\n }\n\n /* Expanded: unfurls LEFT, right edge stays anchored */\n .tack-fab--expanded {\n width: 92px;\n transition: width 220ms cubic-bezier(0.25, 1, 0.5, 1);\n }\n\n /* Entrance */\n @keyframes tack-fab-entrance {\n from { transform: scale(0.5) rotate(90deg); opacity: 0; }\n to { transform: scale(1) rotate(0); opacity: 1; }\n }\n .tack-fab--entering {\n animation: tack-fab-entrance 500ms cubic-bezier(0.16,1,0.3,1) both;\n }\n\n /* Button base */\n .tack-fab button {\n width: 34px;\n height: 34px;\n border-radius: 50%;\n border: none;\n background: transparent;\n color: #FFFFFF;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n padding: 0;\n transition: background 120ms ease;\n }\n .tack-fab button:hover { background: rgba(255,255,255,0.12); }\n .tack-fab button:active { background: rgba(255,255,255,0.18); }\n\n /* Main button — holds stacked comment/close icons */\n .tack-fab-main { position: relative; }\n\n /* Icon swap — two icons stacked absolutely */\n .tack-fab-icon {\n position: absolute;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: opacity 150ms ease, transform 220ms cubic-bezier(0.34, 1.56, 0.64, 1);\n }\n\n /* Comment icon: visible when collapsed */\n .tack-fab-icon--comment {\n opacity: 1;\n transform: rotate(0deg) scale(1);\n }\n .tack-fab--expanded .tack-fab-icon--comment {\n opacity: 0;\n transform: rotate(90deg) scale(0.6);\n }\n\n /* Close icon: visible when expanded */\n .tack-fab-icon--close {\n opacity: 0;\n transform: rotate(-90deg) scale(0.6);\n }\n .tack-fab--expanded .tack-fab-icon--close {\n opacity: 1;\n transform: rotate(0deg) scale(1);\n transition-delay: 60ms;\n }\n\n /* Panel toggle — hidden when collapsed, slides in on expand */\n .tack-fab-panel-toggle {\n opacity: 0;\n width: 0;\n overflow: hidden;\n transform: translateX(8px);\n pointer-events: none;\n transition: opacity 80ms ease, transform 80ms ease, width 180ms cubic-bezier(0.55, 0, 1, 0.45);\n }\n .tack-fab--expanded .tack-fab-panel-toggle {\n opacity: 1;\n width: 34px;\n transform: translateX(0);\n pointer-events: auto;\n transition: opacity 150ms ease 80ms, transform 150ms cubic-bezier(0.25, 1, 0.5, 1) 80ms, width 220ms cubic-bezier(0.25, 1, 0.5, 1);\n }\n\n /* Active state for panel toggle */\n .tack-fab-btn--active { background: rgba(255,255,255,0.15) !important; }\n .tack-fab-btn--active:hover { background: rgba(255,255,255,0.20) !important; }\n\n /* Badge — top-right of FAB */\n .tack-fab-badge {\n position: absolute;\n top: -3px;\n right: -3px;\n min-width: 20px;\n height: 20px;\n border-radius: 10px;\n background: #4F6BED;\n color: white;\n font-size: 11px;\n font-weight: 600;\n display: none;\n align-items: center;\n justify-content: center;\n padding: 0 5px;\n pointer-events: none;\n box-shadow: 0 1px 4px rgba(0,0,0,0.25);\n }\n .tack-fab-badge.visible { display: flex; }\n .tack-fab--expanded .tack-fab-badge { display: none !important; }\n\n /* Left-edge: unfurl to the RIGHT instead of LEFT */\n .tack-fab--left {\n justify-content: flex-start;\n }\n\n .tack-fab--left .tack-fab-panel-toggle {\n transform: translateX(-8px);\n }\n\n .tack-fab--left.tack-fab--expanded .tack-fab-panel-toggle {\n transform: translateX(0);\n }\n\n /* Dragging state — elevated, slightly scaled */\n .tack-fab-dragging {\n transform: scale(1.06) !important;\n box-shadow: 0 12px 32px rgba(0, 0, 0, 0.18) !important;\n opacity: 0.92 !important;\n cursor: grabbing !important;\n transition: none !important;\n z-index: 10005 !important;\n }\n\n /* Snapping to edge */\n .tack-fab-snapping {\n transition: left 400ms cubic-bezier(0.175, 0.885, 0.32, 1.1),\n right 400ms cubic-bezier(0.175, 0.885, 0.32, 1.1),\n top 400ms cubic-bezier(0.175, 0.885, 0.32, 1.1) !important;\n }\n\n /* Settling after snap */\n .tack-fab-settling {\n transition: transform 350ms cubic-bezier(0.2, 0, 0, 1),\n box-shadow 350ms cubic-bezier(0.2, 0, 0, 1),\n opacity 350ms cubic-bezier(0.2, 0, 0, 1) !important;\n }\n\n /* Reduced motion — drag */\n @media (prefers-reduced-motion: reduce) {\n .tack-fab-snapping { transition-duration: 0ms !important; }\n .tack-fab-settling { transition-duration: 0ms !important; }\n .tack-fab-dragging { transform: none !important; opacity: 1 !important; }\n }\n\n /* Reduced motion — FAB */\n @media (prefers-reduced-motion: reduce) {\n .tack-fab, .tack-fab--expanded { transition-duration: 0ms !important; }\n .tack-fab--entering { animation: none; }\n .tack-fab-icon { transition-duration: 0ms !important; transition-delay: 0ms !important; }\n .tack-fab-panel-toggle { transition-duration: 0ms !important; transition-delay: 0ms !important; }\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 — collapsed width set via --pin-w custom property */\n .tack-pin.multi-author .tack-pin-shell {\n border-radius: 17px;\n padding: 3px;\n width: var(--pin-w, 74px);\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 display: block;\n }\n\n .tack-pin-avatar-stacked .tack-pin-avatar-fallback {\n width: 100% !important;\n height: 100% !important;\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 --pin-w: 256px;\n border-radius: 14px;\n max-height: 160px;\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 /* Multi-author preview: avatar stack row at top */\n .tack-pin-preview-avatars {\n display: flex;\n align-items: center;\n margin-bottom: 6px;\n }\n\n .tack-pin-preview-avatars .tack-pin-avatar-stacked {\n width: 24px;\n height: 24px;\n border-radius: 50%;\n margin-left: -6px;\n position: relative;\n flex-shrink: 0;\n overflow: hidden;\n border: 2px solid #FFFFFF;\n }\n\n .tack-pin-preview-avatars .tack-pin-avatar-stacked:first-child {\n margin-left: 0;\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 — right side (default), floating with 32px gap */\n .tack-panel-strip {\n position: fixed;\n top: 32px;\n right: 32px;\n width: 360px;\n height: calc(100vh - 64px);\n background: transparent;\n padding: 0;\n transform: translateX(calc(100% + 100px));\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 /* When FAB is on the same side, push panel inward (only when open,\n so the slide-out transform still pushes fully off-screen).\n Expanded FAB = 92px wide + 20px edge margin + 32px gap = 144px */\n .tack-panel-strip.open.tack-panel-fab-offset {\n right: 144px;\n }\n\n /* Panel Strip — left side */\n .tack-panel-strip.tack-panel-left {\n right: auto;\n left: 32px;\n transform: translateX(calc(-100% - 100px));\n }\n\n .tack-panel-strip.tack-panel-left.open {\n transform: translateX(0);\n }\n\n .tack-panel-strip.tack-panel-left.open.tack-panel-fab-offset {\n left: 144px;\n }\n\n /* Snap transition when switching sides */\n .tack-panel-strip.tack-panel-snapping {\n transition: transform 350ms cubic-bezier(0.16, 1, 0.3, 1);\n }\n\n .tack-panel-header.tack-dragging {\n cursor: grabbing;\n }\n\n /* Edge snap hint during panel drag */\n .tack-panel-snap-hint {\n position: fixed;\n top: 0;\n bottom: 0;\n width: 4px;\n background: rgba(37, 99, 235, 0.35);\n z-index: 10005;\n pointer-events: none;\n opacity: 0;\n transition: opacity 150ms ease;\n }\n\n .tack-panel-snap-hint.visible {\n opacity: 1;\n }\n\n .tack-panel-snap-hint.left {\n left: 0;\n }\n\n .tack-panel-snap-hint.right {\n right: 0;\n }\n\n /* Panel dragging state — elevated ghost */\n .tack-panel-strip.tack-panel-dragging {\n box-shadow: 0 16px 48px rgba(0, 0, 0, 0.2) !important;\n transition: none !important;\n z-index: 10005 !important;\n }\n\n .tack-panel-strip.tack-panel-dragging .tack-panel {\n box-shadow: 0 16px 48px rgba(0, 0, 0, 0.2);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .tack-panel-strip.tack-panel-snapping {\n transition-duration: 0ms !important;\n }\n .tack-panel-strip.tack-panel-dragging {\n opacity: 1 !important;\n }\n }\n\n /* Panel Card — floating with rounded corners */\n .tack-panel {\n flex: 1;\n background: #FFFFFF;\n border-radius: 16px;\n border: none;\n box-shadow:\n 0 8px 32px rgba(0, 0, 0, 0.12),\n 0 0 0 1px rgba(0, 0, 0, 0.06);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .tack-panel-header {\n padding: 16px 16px 12px 16px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n cursor: grab;\n user-select: none;\n -webkit-user-select: none;\n touch-action: none;\n }\n\n .tack-panel-title {\n font-weight: 600;\n font-size: 15px;\n color: #18181b;\n }\n\n .tack-panel-close {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n border-radius: 8px;\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 .tack-panel-toolbar {\n padding: 0 16px 12px 16px;\n display: flex;\n align-items: center;\n gap: 8px;\n border-bottom: 1px solid rgba(0, 0, 0, 0.06);\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 scrollbar-width: none;\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 .tack-panel-content::-webkit-scrollbar {\n display: none;\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 100ms cubic-bezier(0.4, 0, 0.2, 1);\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 opacity: 1;\n pointer-events: auto;\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(37, 99, 235, 0.05);\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: #2563EB;\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: none;\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\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 /* Jitter — gentle \"you have unsaved content\" nudge.\n Horizontal shake with decaying amplitude + subtle border warmth.\n Distinct from tack-pin-shake (error), this is cautionary, not alarming. */\n @keyframes tack-form-jitter {\n 0% { transform: translateX(0); }\n 12% { transform: translateX(-6px) scale(1.01); }\n 28% { transform: translateX(4px); }\n 44% { transform: translateX(-3px); }\n 60% { transform: translateX(2px); }\n 76% { transform: translateX(-1px); }\n 88% { transform: translateX(0.5px);}\n 100% { transform: translateX(0); }\n }\n\n @keyframes tack-form-jitter-border {\n 0% { border-color: #D1D5DB; box-shadow: 0 4px 12px rgba(0,0,0,0.08), 0 1px 3px rgba(0,0,0,0.06); }\n 25% { border-color: #F59E0B; box-shadow: 0 4px 16px rgba(245,158,11,0.12), 0 1px 3px rgba(0,0,0,0.06); }\n 100% { border-color: #D1D5DB; box-shadow: 0 4px 12px rgba(0,0,0,0.08), 0 1px 3px rgba(0,0,0,0.06); }\n }\n\n .tack-form.jitter {\n animation: tack-form-jitter 350ms ease-out;\n }\n\n .tack-form.jitter .tack-form-pill {\n animation: tack-form-jitter-border 400ms cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .tack-form.jitter {\n animation: none;\n }\n .tack-form.jitter .tack-form-pill {\n animation: tack-form-jitter-border 300ms ease;\n }\n }\n\n /* Pill container */\n .tack-form-pill {\n display: flex;\n flex-direction: column;\n background: #FFFFFF;\n border: 1px solid #E5E7EB;\n border-radius: 20px;\n padding: 6px;\n width: 300px;\n box-shadow: 0 8px 24px rgba(0,0,0,0.08), 0 1px 3px rgba(0,0,0,0.04);\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n overflow: hidden;\n }\n\n .tack-form-input-row {\n display: flex;\n align-items: flex-end;\n gap: 8px;\n padding: 8px 8px 8px 14px;\n background: #F4F4F5;\n border-radius: 16px;\n transition: background 150ms ease;\n }\n\n .tack-form-input-row:focus-within {\n background: #EBEBED;\n }\n\n /* Element inspect accordion */\n .tack-form-inspect {\n padding: 0 4px;\n margin-bottom: 4px;\n }\n\n .tack-form-inspect-toggle {\n display: flex;\n align-items: center;\n gap: 4px;\n width: 100%;\n padding: 6px 8px 4px 8px;\n background: none;\n border: none;\n cursor: pointer;\n font-size: 12px;\n font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;\n color: #71717a;\n text-align: left;\n border-radius: 8px;\n transition: background 0.1s ease;\n }\n\n .tack-form-inspect-toggle:hover {\n background: rgba(0, 0, 0, 0.04);\n }\n\n .tack-form-inspect-chevron {\n flex-shrink: 0;\n transition: transform 150ms ease;\n }\n\n .tack-form-inspect.expanded .tack-form-inspect-chevron {\n transform: rotate(90deg);\n }\n\n .tack-form-inspect-label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .tack-form-inspect-props {\n max-height: 0;\n overflow: hidden;\n transition: max-height 200ms cubic-bezier(0.16, 1, 0.3, 1), padding 200ms ease;\n background: #1e1e1e;\n border-radius: 8px;\n margin: 0 4px;\n padding: 0 10px;\n font-size: 11.5px;\n font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;\n line-height: 1.6;\n white-space: pre;\n }\n\n .tack-form-inspect.expanded .tack-form-inspect-props {\n max-height: 160px;\n padding: 8px 10px;\n margin-bottom: 4px;\n overflow-y: auto;\n scrollbar-width: none;\n }\n\n .tack-form-inspect.expanded .tack-form-inspect-props::-webkit-scrollbar {\n display: none;\n }\n\n .tack-inspect-prop {\n display: block;\n color: #d4d4d4;\n }\n\n .tack-inspect-key {\n color: #9cdcfe;\n }\n\n .tack-inspect-val {\n color: #ce9178;\n }\n\n .tack-form-pill:focus-within {\n border-color: #D1D5DB;\n box-shadow: 0 8px 24px rgba(0,0,0,0.10), 0 2px 6px rgba(0,0,0,0.04);\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: 2px 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: #E0E0E3;\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 /* Reactions */\n .tack-reaction-bar {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n padding: 6px 0 2px 36px;\n align-items: center;\n }\n\n .tack-reaction-pill {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 2px 8px;\n border-radius: 12px;\n background: #F4F4F5;\n border: 1px solid transparent;\n font-size: 13px;\n cursor: pointer;\n line-height: 1.4;\n transition: background 0.15s, border-color 0.15s;\n }\n\n .tack-reaction-pill:hover {\n background: #E4E4E7;\n }\n\n .tack-reaction-pill.mine {\n background: #EEF2FF;\n border-color: #C7D2FE;\n }\n\n .tack-reaction-pill.mine:hover {\n background: #E0E7FF;\n }\n\n .tack-reaction-add {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 26px;\n height: 26px;\n border-radius: 50%;\n background: #F4F4F5;\n border: 1px solid transparent;\n font-size: 14px;\n color: #A1A1AA;\n cursor: pointer;\n transition: background 0.15s, color 0.15s;\n }\n\n .tack-reaction-add:hover {\n background: #E4E4E7;\n color: #71717A;\n }\n\n .tack-reaction-picker {\n display: flex;\n gap: 2px;\n background: white;\n border: 1px solid #E4E4E7;\n border-radius: 8px;\n padding: 4px;\n box-shadow: 0 2px 8px rgba(0,0,0,0.1);\n position: absolute;\n z-index: 10;\n }\n\n .tack-reaction-picker-item {\n width: 32px;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n border: none;\n background: none;\n border-radius: 6px;\n font-size: 18px;\n cursor: pointer;\n transition: background 0.1s;\n }\n\n .tack-reaction-picker-item:hover {\n background: #F4F4F5;\n }\n\n .tack-reaction-picker-item.mine {\n background: #EEF2FF;\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 /* ── Loading Pin ── */\n\n @keyframes tack-pin-enter {\n from { opacity: 0; transform: scale(0.6); }\n to { opacity: 1; transform: scale(1); }\n }\n\n @keyframes tack-loading-ring {\n from { transform: rotate(0deg); }\n to { transform: rotate(360deg); }\n }\n\n @keyframes tack-loading-pulse {\n 0%, 100% { opacity: 0.3; }\n 50% { opacity: 0.6; }\n }\n\n @keyframes tack-pin-shake {\n 0%, 100% { transform: translateX(0); }\n 20% { transform: translateX(-5px); }\n 40% { transform: translateX(4px); }\n 60% { transform: translateX(-3px); }\n 80% { transform: translateX(2px); }\n }\n\n @keyframes tack-pin-settle {\n 0% { transform: scale(1); }\n 50% { transform: scale(1.05); }\n 100% { transform: scale(1); }\n }\n\n .tack-pin-loading {\n animation: tack-pin-enter 200ms cubic-bezier(0.16, 1, 0.3, 1) both;\n pointer-events: none;\n }\n\n .tack-pin-loading .tack-pin-avatar-img,\n .tack-pin-loading .tack-pin-avatar-fallback {\n opacity: 0.35;\n filter: blur(3px);\n }\n\n .tack-pin-loading-ring {\n position: absolute;\n top: 1px;\n left: 1px;\n width: 36px;\n height: 36px;\n animation: tack-loading-ring 3s linear infinite;\n }\n\n .tack-pin-loading-ring circle {\n fill: none;\n stroke: rgba(59, 130, 246, 0.5);\n stroke-width: 2;\n stroke-dasharray: 25 75;\n stroke-linecap: round;\n animation: tack-loading-pulse 1.8s ease-in-out infinite;\n }\n\n .tack-pin-settle {\n animation: tack-pin-settle 100ms cubic-bezier(0.34, 1.56, 0.64, 1);\n }\n\n .tack-pin-failed {\n cursor: pointer;\n pointer-events: auto;\n animation: tack-pin-shake 400ms ease-out;\n }\n\n .tack-pin-failed .tack-pin-loading-ring circle {\n stroke: rgba(239, 68, 68, 0.5);\n animation: none;\n stroke-dasharray: 100 0;\n }\n\n .tack-pin-failed-tooltip {\n position: absolute;\n top: -32px;\n left: 50%;\n transform: translateX(-50%);\n background: #1a1a1a;\n color: white;\n font-size: 11px;\n padding: 4px 8px;\n border-radius: 4px;\n white-space: nowrap;\n opacity: 0;\n pointer-events: none;\n transition: opacity 150ms ease;\n }\n\n .tack-pin-failed:hover .tack-pin-failed-tooltip {\n opacity: 1;\n }\n\n @media (prefers-reduced-motion: reduce) {\n .tack-pin-loading {\n animation: none;\n opacity: 0.6;\n }\n .tack-pin-loading-ring {\n animation: none;\n }\n .tack-pin-loading-ring circle {\n animation: none;\n opacity: 0.5;\n }\n .tack-pin-settle {\n animation: none;\n }\n .tack-pin-failed {\n animation: none;\n }\n }\n\n /* ── Pin ↔ Panel Cross-linking ── */\n\n /* Panel row highlight when hovering corresponding pin */\n .tack-comment-row.pin-hover {\n background-color: rgba(0, 0, 0, 0.04);\n transition: background-color 120ms cubic-bezier(0.16, 1, 0.3, 1);\n }\n\n /* Pin highlight when hovering corresponding panel row —\n scale up + blue ring on shell */\n .tack-pin.hover-linked {\n transform: scale(1.06);\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 transition: transform 220ms cubic-bezier(0.16, 1, 0.3, 1), filter 200ms cubic-bezier(0.16, 1, 0.3, 1);\n }\n\n .tack-pin.hover-linked .tack-pin-shell {\n box-shadow: 0 0 0 2px rgba(6, 182, 212, 0.65);\n transition:\n box-shadow 200ms cubic-bezier(0.16, 1, 0.3, 1),\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 /* Beacon pulse when navigating from panel click —\n pseudo-element so it doesn't conflict with .active ring */\n .tack-pin.beacon .tack-pin-shell::before {\n content: '';\n position: absolute;\n inset: -4px;\n border-radius: 23px;\n pointer-events: none;\n animation: tack-pin-beacon 700ms cubic-bezier(0.16, 1, 0.3, 1);\n }\n\n @keyframes tack-pin-beacon {\n 0% { box-shadow: 0 0 0 0 rgba(37, 99, 235, 0.4); }\n 40% { box-shadow: 0 0 0 8px rgba(37, 99, 235, 0); }\n 50% { box-shadow: 0 0 0 0 rgba(37, 99, 235, 0.25); }\n 90% { box-shadow: 0 0 0 12px rgba(37, 99, 235, 0); }\n 100% { box-shadow: 0 0 0 12px rgba(37, 99, 235, 0); }\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 onEscape: (() => void) | undefined\n private enabled = false\n private highlightedElement: Element | null = null\n private highlight: HTMLDivElement | null = null\n private scrim: HTMLDivElement | null = null\n private styleElement: HTMLStyleElement | null = null\n private settleTimer: ReturnType<typeof setTimeout> | null = null\n private pendingTarget: Element | null = null\n private enabledAt: number = 0\n\n constructor(callback: SelectionCallback, onEscape?: () => void) {\n this.callback = callback\n this.onEscape = onEscape\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 }\n\n enable(): void {\n if (this.enabled) return\n this.enabled = true\n this.enabledAt = Date.now()\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 // Set crosshair cursor on the page (except on the tack widget itself)\n this.styleElement = document.createElement('style')\n this.styleElement.textContent = `\n body, body * { cursor: crosshair !important; }\n #tack-widget, #tack-widget * { cursor: default !important; }\n `\n document.head.appendChild(this.styleElement)\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.styleElement) { this.styleElement.remove(); this.styleElement = null }\n this.highlightedElement = null\n this.pendingTarget = null\n }\n\n private inCooldown(): boolean {\n return Date.now() - this.enabledAt < 150\n }\n\n private onMouseDown(e: MouseEvent): void {\n if ((e.target as Element).closest('#tack-widget')) {\n return\n }\n if (this.inCooldown()) return\n e.preventDefault()\n e.stopPropagation()\n }\n\n private onMouseMove(e: MouseEvent): void {\n if (this.inCooldown()) return\n const target = e.target as Element\n\n if (target.closest('#tack-widget')) {\n this.clearHighlight()\n return\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 if (this.inCooldown()) return\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 // Viewport-relative position for fallback when element can't be found\n const viewportRelativeX = e.clientX / window.innerWidth\n const viewportRelativeY = e.clientY / window.innerHeight\n\n const anchor = this.generateAnchor(target, relativeX, relativeY, viewportRelativeX, viewportRelativeY)\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 this.onEscape?.()\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(\n element: Element,\n relativeX: number,\n relativeY: number,\n viewportRelativeX: number,\n viewportRelativeY: number\n ): 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 viewportRelativeX,\n viewportRelativeY,\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 private boundOnOutsideClick: ((e: MouseEvent) => void) | 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 <div class=\"tack-form-inspect\" style=\"display:none;\">\n <button type=\"button\" class=\"tack-form-inspect-toggle\">\n <svg class=\"tack-form-inspect-chevron\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\">\n <path d=\"M3.5 2L6.5 5L3.5 8\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n <span class=\"tack-form-inspect-label\"></span>\n </button>\n <div class=\"tack-form-inspect-props\"></div>\n </div>\n <div class=\"tack-form-input-row\">\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 </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 // Inspect accordion toggle\n form.querySelector('.tack-form-inspect-toggle')?.addEventListener('click', (e) => {\n e.stopPropagation()\n const inspect = form.querySelector('.tack-form-inspect') as HTMLElement\n if (inspect) inspect.classList.toggle('expanded')\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 — jitter first if there's unsaved content\n form.addEventListener('keydown', (e: Event) => {\n const ke = e as KeyboardEvent\n if (ke.key === 'Escape') {\n ke.preventDefault()\n if (this.hasContent()) {\n this.jitter()\n } else {\n this.hide()\n }\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 at the actual click position\n let viewportX: number\n let viewportY: number\n\n if (target.element) {\n const rect = target.element.getBoundingClientRect()\n // Reconstruct click position within the element\n viewportX = rect.left + (target.anchor.relativeX * rect.width) + 12\n viewportY = rect.top + (target.anchor.relativeY * rect.height)\n } else if (target.anchor.viewportRelativeX != null && target.anchor.viewportRelativeY != null) {\n viewportX = target.anchor.viewportRelativeX * window.innerWidth + 12\n viewportY = target.anchor.viewportRelativeY * window.innerHeight\n } else {\n viewportX = target.anchor.relativeX * window.innerWidth + 20\n viewportY = target.anchor.relativeY * window.innerHeight\n }\n\n // Prevent form from going off-screen right\n const fw = 300\n if (viewportX + fw > window.innerWidth - 20) {\n viewportX = Math.max(20, viewportX - fw - 24)\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 // Populate element inspect accordion\n this.populateInspect(target.element)\n\n // Listen for outside clicks to jitter (if content) or dismiss (if empty)\n this.boundOnOutsideClick = (e: MouseEvent) => {\n // Clicks inside the form's shadow host are stopped by stopPropagation\n // in attachFormListeners, so anything that reaches here is \"outside.\"\n if (this.hasContent()) {\n this.jitter()\n } else {\n this.hide()\n }\n }\n // Use setTimeout so the click that opened the form doesn't immediately trigger\n setTimeout(() => {\n if (this.boundOnOutsideClick) {\n document.addEventListener('mousedown', this.boundOnOutsideClick)\n }\n }, 0)\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.form.classList.remove('jitter')\n if (this.boundOnOutsideClick) {\n document.removeEventListener('mousedown', this.boundOnOutsideClick)\n this.boundOnOutsideClick = null\n }\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 // Reset inspect accordion\n const inspect = this.form.querySelector('.tack-form-inspect') as HTMLElement\n if (inspect) {\n inspect.classList.remove('expanded')\n inspect.style.display = 'none'\n }\n\n this.onHide?.()\n }\n\n isFormVisible(): boolean {\n return this.isVisible\n }\n\n hasContent(): boolean {\n const textarea = this.form.querySelector('#tack-comment-content') as HTMLTextAreaElement\n return !!textarea && textarea.value.trim().length > 0\n }\n\n jitter(): void {\n // Remove then re-add class so animation replays if already in progress\n this.form.classList.remove('jitter')\n // Force reflow so the browser sees the class removal before re-add\n void this.form.offsetWidth\n this.form.classList.add('jitter')\n\n // Refocus the textarea so the user can keep typing\n const textarea = this.form.querySelector('#tack-comment-content') as HTMLTextAreaElement\n textarea?.focus()\n\n // Clean up class after animation completes\n const onEnd = () => {\n this.form.classList.remove('jitter')\n this.form.removeEventListener('animationend', onEnd)\n }\n this.form.addEventListener('animationend', onEnd)\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 // ── Element Inspector ──\n\n private static CATEGORY_PROPS: Record<string, string[]> = {\n text: ['font-size', 'font-weight', 'line-height', 'color', 'font-family'],\n button: ['font-size', 'color', 'background-color', 'border-radius', '@padding'],\n link: ['font-size', 'color', 'text-decoration', 'font-weight'],\n image: ['@dimensions', 'object-fit', 'border-radius'],\n input: ['@dimensions', 'font-size', '@padding', 'border-radius'],\n toggle: ['@state', '@dimensions', 'background-color', 'border-radius'],\n 'table-cell': ['text-align', 'vertical-align', '@padding', 'font-size', 'background-color'],\n layout: ['display', '@layout-direction', 'gap', '@padding', 'align-items'],\n generic: ['color', 'font-size', 'font-weight', 'background-color'],\n }\n\n private classifyElement(el: Element): string {\n // Order matters — more specific categories first\n if (el.matches('input[type=\"checkbox\"], input[type=\"radio\"], [role=\"switch\"], [role=\"checkbox\"], [role=\"radio\"]')) return 'toggle'\n if (el.matches('button, [role=\"button\"], input[type=\"submit\"], input[type=\"reset\"], input[type=\"button\"]')) return 'button'\n if (el.matches('a[href]')) return 'link'\n if (el.matches('img, picture, video, canvas') || el.closest('svg')) return 'image'\n if (el.matches('input, textarea, select, [contenteditable=\"true\"]')) return 'input'\n if (el.matches('td, th, [role=\"cell\"], [role=\"columnheader\"], [role=\"rowheader\"]')) return 'table-cell'\n if (el.matches('h1, h2, h3, h4, h5, h6, p, span, label, blockquote, li, code, pre, em, strong, small')) return 'text'\n if (el.matches('div, section, main, aside, article, nav, header, footer, ul, ol') && el.children.length > 0) {\n const d = window.getComputedStyle(el).display\n if (d.includes('flex') || d.includes('grid')) return 'layout'\n }\n return 'generic'\n }\n\n private populateInspect(element: Element | null): void {\n const inspect = this.form.querySelector('.tack-form-inspect') as HTMLElement\n if (!inspect) return\n\n if (!element) {\n inspect.style.display = 'none'\n return\n }\n\n const category = this.classifyElement(element)\n const label = this.getElementLabel(element, category)\n const props = this.getRelevantStyles(element, category)\n\n const labelEl = inspect.querySelector('.tack-form-inspect-label') as HTMLElement\n if (labelEl) labelEl.textContent = label\n\n const propsEl = inspect.querySelector('.tack-form-inspect-props') as HTMLElement\n if (propsEl) {\n propsEl.innerHTML = props\n .map(([k, v]) => `<span class=\"tack-inspect-prop\"><span class=\"tack-inspect-key\">${this.escapeHtml(k)}</span>: <span class=\"tack-inspect-val\">${this.escapeHtml(v)}</span>;</span>`)\n .join('\\n')\n }\n\n inspect.classList.remove('expanded')\n inspect.style.display = props.length > 0 ? '' : 'none'\n }\n\n private getElementLabel(el: Element, category: string): string {\n // ID takes priority across all categories\n if (el.id) return `#${el.id}`\n // Meaningful class name\n const meaningful = Array.from(el.classList).find(\n (c) => !c.startsWith('tack-') && c.length > 1 && c.length < 40\n )\n if (meaningful) return `.${meaningful}`\n\n // Category-specific labels\n const truncate = (s: string, n: number) => s.length > n ? s.slice(0, n) + '...' : s\n\n switch (category) {\n case 'button': {\n const text = (el.textContent || '').trim()\n if (text) return `Button \"${truncate(text, 24)}\"`\n return '<button>'\n }\n case 'link': {\n const href = el.getAttribute('href') || ''\n if (href) {\n try {\n const u = new URL(href, window.location.origin)\n return `Link \"${truncate(u.pathname, 28)}\"`\n } catch { return `Link \"${truncate(href, 28)}\"` }\n }\n const text = (el.textContent || '').trim()\n return text ? `Link \"${truncate(text, 24)}\"` : '<a>'\n }\n case 'image': {\n const alt = el.getAttribute('alt')\n if (alt) return `Image \"${truncate(alt, 28)}\"`\n const src = el.getAttribute('src')\n if (src) {\n const filename = src.split('/').pop()?.split('?')[0] || ''\n if (filename) return `Image \"${truncate(filename, 28)}\"`\n }\n const ariaLabel = el.getAttribute('aria-label')\n if (ariaLabel) return `Image \"${truncate(ariaLabel, 28)}\"`\n return el.matches('svg') || el.closest('svg') ? '<svg>' : '<img>'\n }\n case 'input': {\n const tag = el.tagName.toLowerCase()\n if (tag === 'textarea') {\n const name = el.getAttribute('name') || el.getAttribute('placeholder') || ''\n return name ? `Textarea \"${truncate(name, 24)}\"` : '<textarea>'\n }\n if (tag === 'select') {\n const name = el.getAttribute('name') || ''\n return name ? `Select \"${truncate(name, 24)}\"` : '<select>'\n }\n const type = el.getAttribute('type') || 'text'\n const name = el.getAttribute('name') || el.getAttribute('placeholder') || ''\n return name ? `Input ${type} \"${truncate(name, 20)}\"` : `<input ${type}>`\n }\n case 'toggle': {\n const checked = (el as HTMLInputElement).checked ?? el.getAttribute('aria-checked') === 'true'\n const type = el.matches('[role=\"switch\"]') ? 'Switch' :\n el.matches('input[type=\"radio\"], [role=\"radio\"]') ? 'Radio' : 'Checkbox'\n return `${type} (${checked ? 'on' : 'off'})`\n }\n case 'layout': {\n const d = window.getComputedStyle(el).display\n const tag = el.tagName.toLowerCase()\n return `<${tag}> ${d}`\n }\n default: {\n const tag = el.tagName.toLowerCase()\n const text = (el.textContent || '').trim()\n if (text.length > 0) return `<${tag}> \"${truncate(text, 24)}\"`\n return `<${tag}>`\n }\n }\n }\n\n private getRelevantStyles(el: Element, category: string): [string, string][] {\n const cs = window.getComputedStyle(el)\n const propNames = CommentForm.CATEGORY_PROPS[category] || CommentForm.CATEGORY_PROPS.generic\n const props: [string, string][] = []\n\n for (const name of propNames) {\n if (name.startsWith('@')) {\n const resolved = this.resolveCustomProp(name, el, cs)\n if (resolved) props.push(resolved)\n } else {\n const val = cs.getPropertyValue(name)\n if (val) props.push([name, val])\n }\n }\n\n return props.filter(([k, v]) => {\n if (k === 'background-color' && (v === 'rgba(0, 0, 0, 0)' || v === 'transparent')) return false\n if (k === 'text-decoration' && v.startsWith('none')) return false\n if (k === 'object-fit' && v === 'fill') return false\n if (k === 'text-align' && v === 'start') return false\n if (k === 'vertical-align' && v === 'middle') return false\n if (k === 'align-items' && v === 'normal') return false\n return true\n }).map(([k, v]) => {\n // Clean up font-family to first name only\n if (k === 'font-family') {\n const first = v.split(',')[0].trim().replace(/^[\"']|[\"']$/g, '')\n return [k, first]\n }\n return [k, v]\n })\n }\n\n private resolveCustomProp(name: string, el: Element, cs: CSSStyleDeclaration): [string, string] | null {\n switch (name) {\n case '@dimensions': {\n const rect = el.getBoundingClientRect()\n const w = Math.round(rect.width)\n const h = Math.round(rect.height)\n if (w === 0 && h === 0) return null\n return ['size', `${w} × ${h}px`]\n }\n case '@state': {\n const checked = (el as HTMLInputElement).checked ?? el.getAttribute('aria-checked') === 'true'\n return ['state', checked ? 'checked' : 'unchecked']\n }\n case '@padding': {\n const t = cs.getPropertyValue('padding-top')\n const r = cs.getPropertyValue('padding-right')\n const b = cs.getPropertyValue('padding-bottom')\n const l = cs.getPropertyValue('padding-left')\n if (!t) return null\n if (t === r && r === b && b === l) return ['padding', t]\n if (t === b && r === l) return ['padding', `${t} ${r}`]\n return ['padding', `${t} ${r} ${b} ${l}`]\n }\n case '@layout-direction': {\n const d = cs.display\n if (d.includes('flex')) {\n const dir = cs.getPropertyValue('flex-direction')\n return dir ? ['flex-direction', dir] : null\n }\n if (d.includes('grid')) {\n let cols = cs.getPropertyValue('grid-template-columns')\n if (cols && cols.length > 40) cols = cols.slice(0, 37) + '...'\n return cols ? ['grid-columns', cols] : null\n }\n return null\n }\n default:\n return null\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 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 return `<img\n src=\"${avatarUrl}\"\n alt=\"${getInitials(name)}\"\n referrerpolicy=\"no-referrer\"\n crossorigin=\"anonymous\"\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","export interface FabPosition {\n edge: 'left' | 'right'\n y: number // px from top of viewport\n}\n\nconst STORAGE_KEY_PREFIX = 'tack-fab-pos-'\n\nfunction storageKey(): string {\n return `${STORAGE_KEY_PREFIX}${location.origin}`\n}\n\nexport function loadFabPosition(): FabPosition | null {\n try {\n const raw = localStorage.getItem(storageKey())\n if (!raw) return null\n const parsed = JSON.parse(raw)\n if (\n parsed &&\n (parsed.edge === 'left' || parsed.edge === 'right') &&\n typeof parsed.y === 'number'\n ) {\n return parsed as FabPosition\n }\n return null\n } catch {\n return null\n }\n}\n\nexport function saveFabPosition(pos: FabPosition): void {\n try {\n localStorage.setItem(storageKey(), JSON.stringify(pos))\n } catch {\n // Storage full or unavailable — silently ignore\n }\n}\n\nexport function createDragOverlay(options: { zIndex: number; cursor: string }): HTMLDivElement {\n const overlay = document.createElement('div')\n overlay.style.cssText = `\n position: fixed;\n inset: 0;\n z-index: ${options.zIndex};\n cursor: ${options.cursor};\n background: transparent;\n touch-action: none;\n `\n document.body.appendChild(overlay)\n return overlay\n}\n\nexport function removeDragOverlay(overlay: HTMLDivElement | null): void {\n if (overlay && overlay.parentNode) {\n overlay.parentNode.removeChild(overlay)\n }\n}\n\nexport function clampFabY(y: number, fabHeight: number): number {\n const minY = 20\n const maxY = window.innerHeight - fabHeight - 20\n return Math.max(minY, Math.min(y, maxY))\n}\n\nexport function exceedsDeadZone(\n startX: number,\n startY: number,\n currentX: number,\n currentY: number,\n threshold: number = 5\n): boolean {\n const dx = currentX - startX\n const dy = currentY - startY\n return Math.sqrt(dx * dx + dy * dy) > threshold\n}\n\n// ── Panel Side Persistence ──\n\nexport type PanelSide = 'left' | 'right'\n\nconst PANEL_SIDE_KEY_PREFIX = 'tack-panel-side-'\n\nfunction panelSideKey(): string {\n return `${PANEL_SIDE_KEY_PREFIX}${location.origin}`\n}\n\nexport function loadPanelSide(): PanelSide {\n try {\n const raw = localStorage.getItem(panelSideKey())\n if (raw === 'left' || raw === 'right') return raw\n } catch {}\n return 'right'\n}\n\nexport function savePanelSide(side: PanelSide): void {\n try {\n localStorage.setItem(panelSideKey(), side)\n } catch {}\n}\n","import type { Comment } from '../types'\nimport type { WidgetUser } from '../auth'\nimport { getAvatarColor, getInitials, renderAvatar } from './avatars'\nimport {\n type PanelSide,\n loadPanelSide,\n savePanelSide,\n exceedsDeadZone,\n createDragOverlay,\n removeDragOverlay,\n} from './drag'\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 onRowHover?: (commentId: string) => void\n onRowHoverEnd?: (commentId: string) => void\n onSideChange?: (side: PanelSide) => 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: true, 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 currentUser: WidgetUser | null = null\n private readCommentIds: Set<string>\n private projectId: string = ''\n private side: PanelSide\n private dragState: 'idle' | 'pending' | 'dragging' = 'idle'\n private dragStartPointer: { x: number; y: number } = { x: 0, y: 0 }\n private dragStartRect: DOMRect | null = null\n private dragOverlay: HTMLDivElement | null = null\n private snapHint: HTMLDivElement | null = null\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.side = loadPanelSide()\n this.strip = this.createPanelStrip()\n this.panel = this.strip.querySelector('.tack-panel')!\n this.applySide()\n this.setupHeaderDrag()\n this.container.appendChild(this.strip)\n }\n\n getSide(): PanelSide {\n return this.side\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 <span class=\"tack-panel-title\">Comments</span>\n <button class=\"tack-panel-close\" aria-label=\"Close comments panel\">\n <svg width=\"20\" height=\"20\" 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-toolbar\">\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>\n <div class=\"tack-panel-content\"></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.filterDropdownOpen = !this.filterDropdownOpen\n this.renderFilterDropdown()\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 })\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 // More dropdown removed from panel UI\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 private applySide(): void {\n if (this.side === 'left') {\n this.strip.classList.add('tack-panel-left')\n } else {\n this.strip.classList.remove('tack-panel-left')\n }\n }\n\n private switchSide(newSide: PanelSide): void {\n if (newSide === this.side) return\n const wasOpen = this.isOpen()\n\n // Hide on current side instantly (no transition)\n this.strip.style.transition = 'none'\n this.strip.classList.remove('open')\n\n // Force reflow so the hide takes effect immediately\n void this.strip.offsetHeight\n\n // Switch side\n this.side = newSide\n savePanelSide(newSide)\n this.applySide()\n\n // Force reflow after side change\n void this.strip.offsetHeight\n\n // Restore transition and re-open on new side\n this.strip.style.transition = ''\n if (wasOpen) {\n // Small delay so the browser applies the new position first\n requestAnimationFrame(() => {\n this.strip.classList.add('tack-panel-snapping')\n this.strip.classList.add('open')\n setTimeout(() => {\n this.strip.classList.remove('tack-panel-snapping')\n }, 350)\n })\n }\n\n this.callbacks.onSideChange?.(newSide)\n }\n\n private setupHeaderDrag(): void {\n const header = this.strip.querySelector('.tack-panel-header') as HTMLElement\n if (!header) return\n\n header.addEventListener('pointerdown', (e: PointerEvent) => {\n // Ignore clicks on interactive children\n const target = e.target as HTMLElement\n if (target.closest('button') || target.closest('input')) return\n if (e.button !== 0) return\n\n e.preventDefault()\n this.dragState = 'pending'\n this.dragStartPointer = { x: e.clientX, y: e.clientY }\n this.dragStartRect = this.strip.getBoundingClientRect()\n\n // Register move/up on window so events are never lost when the\n // pointer leaves the Shadow DOM boundary or an overlay intercepts.\n const onMove = (me: PointerEvent) => {\n if (this.dragState === 'pending') {\n if (!exceedsDeadZone(\n this.dragStartPointer.x,\n this.dragStartPointer.y,\n me.clientX,\n me.clientY,\n 5\n )) {\n return\n }\n this.enterPanelDrag()\n }\n\n if (this.dragState === 'dragging') {\n this.updatePanelDrag(me)\n }\n }\n\n const onUp = () => {\n window.removeEventListener('pointermove', onMove)\n window.removeEventListener('pointerup', onUp)\n window.removeEventListener('pointercancel', onUp)\n\n if (this.dragState === 'dragging') {\n this.finalizePanelDrop()\n }\n\n this.dragState = 'idle'\n header.classList.remove('tack-dragging')\n }\n\n window.addEventListener('pointermove', onMove)\n window.addEventListener('pointerup', onUp)\n window.addEventListener('pointercancel', onUp)\n })\n }\n\n private enterPanelDrag(): void {\n this.dragState = 'dragging'\n\n const header = this.strip.querySelector('.tack-panel-header') as HTMLElement\n if (header) header.classList.add('tack-dragging')\n\n // Create drag overlay on document.body to capture events across the page\n this.dragOverlay = createDragOverlay({ zIndex: 10005, cursor: 'grabbing' })\n\n // Apply dragging visual state\n this.strip.classList.add('tack-panel-dragging')\n\n // Position the strip absolutely using left/top instead of right/transform\n if (this.dragStartRect) {\n this.strip.style.left = `${this.dragStartRect.left}px`\n this.strip.style.top = `${this.dragStartRect.top}px`\n this.strip.style.right = 'auto'\n this.strip.style.transform = 'none'\n this.strip.classList.remove('tack-panel-left')\n }\n\n // Create snap hint\n this.createSnapHint()\n }\n\n private updatePanelDrag(e: PointerEvent): void {\n if (!this.dragStartRect) return\n\n const dx = e.clientX - this.dragStartPointer.x\n const newX = this.dragStartRect.left + dx\n\n this.strip.style.left = `${newX}px`\n\n // Determine which edge the panel center is closer to\n const panelCenter = newX + this.dragStartRect.width / 2\n const vpMidpoint = window.innerWidth / 2\n const targetSide: PanelSide = panelCenter < vpMidpoint ? 'left' : 'right'\n\n this.updateSnapHint(targetSide)\n }\n\n private finalizePanelDrop(): void {\n // Remove drag overlay\n removeDragOverlay(this.dragOverlay)\n this.dragOverlay = null\n\n // Remove snap hint\n this.removeSnapHint()\n\n // Remove dragging visual state\n this.strip.classList.remove('tack-panel-dragging')\n\n // Determine target side based on current position\n const rect = this.strip.getBoundingClientRect()\n const panelCenter = rect.left + rect.width / 2\n const vpMidpoint = window.innerWidth / 2\n const targetSide: PanelSide = panelCenter < vpMidpoint ? 'left' : 'right'\n\n // Reset inline styles from drag\n this.strip.style.left = ''\n this.strip.style.top = ''\n this.strip.style.right = ''\n this.strip.style.transform = ''\n\n // Apply the new side (this handles the CSS class and persistence)\n if (targetSide !== this.side) {\n this.switchSide(targetSide)\n } else {\n // Same side — just reapply normal positioning\n this.applySide()\n // Re-add open class since we cleared transform\n if (this.isOpen()) {\n this.strip.classList.add('open')\n }\n }\n }\n\n private createSnapHint(): void {\n if (this.snapHint) return\n this.snapHint = document.createElement('div')\n // Inline styles since this element lives on document.body, outside shadow DOM\n this.snapHint.style.cssText = `\n position: fixed;\n top: 0;\n bottom: 0;\n width: 4px;\n background: rgba(37, 99, 235, 0.35);\n z-index: 10005;\n pointer-events: none;\n opacity: 0;\n transition: opacity 150ms ease;\n `\n document.body.appendChild(this.snapHint)\n }\n\n private updateSnapHint(side: PanelSide): void {\n if (!this.snapHint) return\n if (side === 'left') {\n this.snapHint.style.left = '0'\n this.snapHint.style.right = 'auto'\n } else {\n this.snapHint.style.right = '0'\n this.snapHint.style.left = 'auto'\n }\n this.snapHint.style.opacity = '1'\n }\n\n private removeSnapHint(): void {\n if (this.snapHint) {\n this.snapHint.remove()\n this.snapHint = null\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.renderFilterDropdown()\n }\n\n setFabOffset(fabEdge: 'left' | 'right'): void {\n const sameSide = fabEdge === this.side\n this.strip.classList.toggle('tack-panel-fab-offset', sameSide)\n this.strip.style.left = ''\n this.strip.style.right = ''\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 // Presence section removed from panel UI\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 // Row hover → cross-link to pin\n row.addEventListener('mouseenter', () => {\n this.callbacks.onRowHover?.(id)\n })\n row.addEventListener('mouseleave', () => {\n this.callbacks.onRowHoverEnd?.(id)\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 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 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M22 11.08V12a10 10 0 1 1-5.93-9.14\"/><polyline points=\"22 4 12 14.01 9 11.01\"/></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 <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 highlightRow(commentId: string): void {\n const row = this.panel.querySelector(`.tack-comment-row[data-id=\"${commentId}\"]`) as HTMLElement\n if (row) row.classList.add('pin-hover')\n }\n\n clearRowHighlight(commentId: string): void {\n const row = this.panel.querySelector(`.tack-comment-row[data-id=\"${commentId}\"]`) as HTMLElement\n if (row) row.classList.remove('pin-hover')\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, CommentAnchor } 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\ntype PinHoverCallback = (commentId: string) => 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 private loadingPin: HTMLElement | null = null\n private onHover: PinHoverCallback | null = null\n private onHoverEnd: PinHoverCallback | null = null\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 // Remove loading pin if present\n this.removeLoadingPin()\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 if (author.avatar_url) {\n return `<img src=\"${author.avatar_url}\" alt=\"${initials}\" referrerpolicy=\"no-referrer\" crossorigin=\"anonymous\" 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 return `<img src=\"${author.avatar_url}\" alt=\"${initials}\" referrerpolicy=\"no-referrer\" crossorigin=\"anonymous\" 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 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 const totalSlots = visibleAuthors.length + (overflow > 0 ? 1 : 0)\n // Each avatar is 28px, overlaps 8px. First is full, rest are 20px visible. Plus 6px padding (3px each side).\n const shellWidth = 28 + (totalSlots - 1) * 20 + 6\n\n // Collapsed: stacked avatars\n let avatarsHTML = `<div class=\"tack-pin-shell\" style=\"--pin-w:${shellWidth}px\"><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>'\n\n // Multi-author preview: avatar stack at top, then author + time, then text\n let previewAvatarsHTML = '<div class=\"tack-pin-preview-avatars\">'\n uniqueAuthors.slice(0, 4).forEach((author, i) => {\n previewAvatarsHTML += `<div class=\"tack-pin-avatar-stacked\" style=\"z-index:${i + 1}\">${this.renderStackedAvatar(author, 20)}</div>`\n })\n if (uniqueAuthors.length > 4) {\n previewAvatarsHTML += `<div class=\"tack-pin-avatar-stacked tack-pin-avatar-overflow\" style=\"z-index:5;width:24px;height:24px;font-size:8px\">+${uniqueAuthors.length - 4}</div>`\n }\n previewAvatarsHTML += '</div>'\n\n const multiPreviewHTML = `<div class=\"tack-pin-preview\">\n ${previewAvatarsHTML}\n <div class=\"tack-pin-preview-meta\" style=\"margin-bottom:4px\">\n <span class=\"tack-pin-preview-name\">${this.escapeHtml(previewAuthor.name)}</span>\n <span class=\"tack-pin-preview-time\">${timeAgo}</span>\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 avatarsHTML += `${multiPreviewHTML}</div>`\n pinHTML = avatarsHTML\n } else {\n // Single author: white circle shell with inset avatar\n const author = uniqueAuthors[0]\n const singlePreviewHTML = `<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 pinHTML = `<div class=\"tack-pin-shell\">${this.renderInsetAvatar(author, 32)}${singlePreviewHTML}</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 // Viewport-relative fallback: position based on where user clicked in viewport\n // This scales correctly with responsive layouts\n if (comment.anchor?.viewportRelativeX != null && comment.anchor?.viewportRelativeY != null) {\n const x = comment.anchor.viewportRelativeX * window.innerWidth + window.scrollX\n const y = comment.anchor.viewportRelativeY * window.innerHeight + window.scrollY\n pin.style.left = `${x - pinOffsetX}px`\n pin.style.top = `${y - 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) — fire cross-link at same moment\n const timer = window.setTimeout(() => {\n this.expandPin(comment.id, pin)\n this.onHover?.(comment.id)\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) — clear cross-link\n const timer = window.setTimeout(() => {\n this.collapsePin(comment.id, pin)\n this.onHoverEnd?.(comment.id)\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 setHoverCallbacks(onHover: PinHoverCallback, onHoverEnd: PinHoverCallback): void {\n this.onHover = onHover\n this.onHoverEnd = onHoverEnd\n }\n\n setHoverLinked(commentId: string | null): void {\n this.pins.forEach((pinState, id) => {\n pinState.element.classList.toggle('hover-linked', id === commentId)\n })\n }\n\n beacon(commentId: string): void {\n const pinState = this.pins.get(commentId)\n if (!pinState) return\n pinState.element.classList.add('beacon')\n setTimeout(() => {\n pinState.element.classList.remove('beacon')\n }, 700)\n }\n\n getPinElement(commentId: string): HTMLElement | null {\n return this.pins.get(commentId)?.element || null\n }\n\n showLoadingPin(anchor: CommentAnchor, user: { name: string; avatar_url: string | null }): void {\n this.removeLoadingPin()\n\n const pin = document.createElement('div')\n pin.className = 'tack-pin tack-pin-loading'\n\n const initials = getInitials(user.name)\n const gradient = getAvatarGradient(user.name)\n let avatarHTML: string\n if (user.avatar_url) {\n avatarHTML = `<img src=\"${user.avatar_url}\" alt=\"${initials}\" referrerpolicy=\"no-referrer\" crossorigin=\"anonymous\" class=\"tack-pin-avatar-img\" style=\"width:32px;height:32px;\" />`\n } else {\n avatarHTML = `<div class=\"tack-pin-avatar-fallback\" style=\"width:32px;height:32px;background:${gradient};font-size:12px;\">${initials}</div>`\n }\n\n pin.innerHTML = `<div class=\"tack-pin-shell\">${avatarHTML}<svg class=\"tack-pin-loading-ring\" viewBox=\"0 0 36 36\"><circle cx=\"18\" cy=\"18\" r=\"16\" /></svg></div>`\n\n // Position using anchor data\n const anchorResult = this.anchoring.resolve(anchor)\n const pinOffsetX = 7\n const pinOffsetY = 45\n\n if (anchorResult.element) {\n const position = this.anchoring.calculatePinPosition(anchorResult.element, anchor)\n pin.style.left = `${position.x - pinOffsetX}px`\n pin.style.top = `${position.y - pinOffsetY}px`\n } else if (anchor.relativeX !== undefined && anchor.relativeY !== undefined) {\n const x = anchor.relativeX * document.documentElement.scrollWidth\n const y = anchor.relativeY * document.documentElement.scrollHeight\n pin.style.left = `${x - pinOffsetX}px`\n pin.style.top = `${y - pinOffsetY}px`\n }\n\n this.loadingPin = pin\n this.pinsContainer.appendChild(pin)\n }\n\n resolveLoadingPin(): void {\n if (!this.loadingPin) return\n const pin = this.loadingPin\n pin.classList.remove('tack-pin-loading')\n pin.classList.add('tack-pin-settle')\n // Remove the SVG ring\n const ring = pin.querySelector('.tack-pin-loading-ring')\n ring?.remove()\n // Restore avatar opacity/blur\n const avatar = pin.querySelector('.tack-pin-avatar-img, .tack-pin-avatar-fallback') as HTMLElement\n if (avatar) {\n avatar.style.opacity = '1'\n avatar.style.filter = 'none'\n }\n setTimeout(() => {\n pin.classList.remove('tack-pin-settle')\n }, 350)\n }\n\n failLoadingPin(onRetry: () => void): void {\n if (!this.loadingPin) return\n const pin = this.loadingPin\n pin.classList.remove('tack-pin-loading')\n pin.classList.add('tack-pin-failed')\n\n // Add retry tooltip\n const tooltip = document.createElement('div')\n tooltip.className = 'tack-pin-failed-tooltip'\n tooltip.textContent = 'Failed to save. Click to retry.'\n pin.appendChild(tooltip)\n\n pin.addEventListener('click', (e) => {\n e.stopPropagation()\n this.removeLoadingPin()\n onRetry()\n })\n }\n\n removeLoadingPin(): void {\n if (this.loadingPin) {\n this.loadingPin.remove()\n this.loadingPin = null\n }\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 onReaction: (commentId: string, emoji: string, add: boolean) => 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 pickerOpen = 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 this.pickerOpen = 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\n let left = pinRect.right + gap\n if (left + cardWidth > window.innerWidth - 16) {\n left = pinRect.left - cardWidth - gap\n }\n left = Math.max(16, left)\n\n let top = pinRect.top\n const estimatedHeight = 300\n if (top + estimatedHeight > window.innerHeight - 16) {\n top = window.innerHeight - 16 - estimatedHeight\n }\n top = Math.max(16, 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 // Reactions (main comment only)\n if (isMain) {\n html += this.renderReactions(comment)\n }\n\n html += `</div>`\n return html\n }\n\n private renderReactions(comment: Comment): string {\n const reactions = comment.reactions || []\n const userId = this.currentUser?.id\n\n // Group by emoji: { emoji: { count, userReacted, users[] } }\n const grouped = new Map<string, { count: number; userReacted: boolean; users: string[] }>()\n for (const r of reactions) {\n const existing = grouped.get(r.emoji) || { count: 0, userReacted: false, users: [] }\n existing.count++\n existing.users.push(r.user_name)\n if (r.user_id === userId) existing.userReacted = true\n grouped.set(r.emoji, existing)\n }\n\n let html = '<div class=\"tack-reaction-bar\">'\n\n // Existing reaction pills\n for (const [emoji, data] of grouped) {\n const mine = data.userReacted ? ' mine' : ''\n const title = data.users.join(', ')\n html += `<button class=\"tack-reaction-pill${mine}\" data-emoji=\"${emoji}\" title=\"${this.escapeHtml(title)}\">${emoji} ${data.count}</button>`\n }\n\n // Add reaction button\n html += `<button class=\"tack-reaction-add\" title=\"Add reaction\">+</button>`\n\n // Picker (if open)\n if (this.pickerOpen) {\n const emojis = ['👍', '👎', '❤️', '🎉', '👀', '🚀']\n html += '<div class=\"tack-reaction-picker\">'\n for (const e of emojis) {\n const alreadyReacted = grouped.get(e)?.userReacted ? ' mine' : ''\n html += `<button class=\"tack-reaction-picker-item${alreadyReacted}\" data-picker-emoji=\"${e}\">${e}</button>`\n }\n html += '</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 // Reaction pills — toggle existing reaction\n this.card.querySelectorAll('.tack-reaction-pill').forEach((btn) => {\n btn.addEventListener('click', (e) => {\n e.stopPropagation()\n if (!this.currentComment || !this.currentUser) return\n const emoji = (btn as HTMLElement).dataset.emoji!\n const isMine = btn.classList.contains('mine')\n this.callbacks.onReaction(this.currentComment.id, emoji, !isMine)\n })\n })\n\n // Add reaction button — toggle picker\n this.card.querySelector('.tack-reaction-add')?.addEventListener('click', (e) => {\n e.stopPropagation()\n this.pickerOpen = !this.pickerOpen\n this.renderCard(this.currentComment!)\n this.attachListeners()\n })\n\n // Picker items\n this.card.querySelectorAll('.tack-reaction-picker-item').forEach((btn) => {\n btn.addEventListener('click', (e) => {\n e.stopPropagation()\n if (!this.currentComment || !this.currentUser) return\n const emoji = (btn as HTMLElement).dataset.pickerEmoji!\n const isMine = btn.classList.contains('mine')\n this.pickerOpen = false\n this.callbacks.onReaction(this.currentComment.id, emoji, !isMine)\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 {\n FabPosition,\n loadFabPosition,\n saveFabPosition,\n createDragOverlay,\n removeDragOverlay,\n clampFabY,\n exceedsDeadZone,\n} from './drag'\n\nexport interface FabCallbacks {\n onToggle: () => void\n onPanelToggle: () => void\n onExit: () => void\n}\n\ntype DragState = 'idle' | 'pending' | 'dragging'\n\nconst FAB_SIZE = 44\nconst EDGE_MARGIN = 20\n\nexport class WidgetFAB {\n private el: HTMLDivElement\n private mainBtn: HTMLButtonElement\n private commentIcon: HTMLSpanElement\n private closeIcon: HTMLSpanElement\n private panelToggleBtn: HTMLButtonElement\n private badge: HTMLSpanElement\n private expanded = false\n private callbacks: FabCallbacks\n\n // Drag state\n private dragState: DragState = 'idle'\n private position: FabPosition\n private startPointerX = 0\n private startPointerY = 0\n private startFabX = 0\n private startFabY = 0\n private dragOverlay: HTMLDivElement | null = null\n private snapHint: HTMLDivElement | null = null\n\n // Prevents native click from double-firing after handleFabClick\n private handledByPointer = false\n\n // Bound handlers for cleanup\n private boundOnPointerMove: ((e: PointerEvent) => void) | null = null\n private boundOnPointerUp: ((e: PointerEvent) => void) | null = null\n private boundOnResize: (() => void) | null = null\n\n constructor(container: HTMLElement, callbacks: FabCallbacks) {\n this.callbacks = callbacks\n\n // Load persisted position or use default (bottom-right)\n const saved = loadFabPosition()\n this.position = saved || {\n edge: 'right',\n y: window.innerHeight - FAB_SIZE - EDGE_MARGIN,\n }\n\n this.el = document.createElement('div')\n this.el.className = 'tack-fab tack-fab--entering'\n\n // Apply edge class if left\n if (this.position.edge === 'left') {\n this.el.classList.add('tack-fab--left')\n }\n\n // Panel toggle (left button, hidden when collapsed)\n this.panelToggleBtn = document.createElement('button')\n this.panelToggleBtn.className = 'tack-fab-btn tack-fab-panel-toggle'\n this.panelToggleBtn.setAttribute('aria-label', 'Toggle panel')\n this.panelToggleBtn.innerHTML = `<svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\"></rect><line x1=\"15\" y1=\"3\" x2=\"15\" y2=\"21\"></line></svg>`\n this.panelToggleBtn.addEventListener('click', (e) => {\n e.stopPropagation()\n // Skip if already handled by the pointerup → handleFabClick path\n if (this.handledByPointer) return\n // Block during active drag\n if (this.dragState === 'dragging') return\n this.callbacks.onPanelToggle()\n })\n\n // Main button (rightmost -- comment icon / X icon)\n this.mainBtn = document.createElement('button')\n this.mainBtn.className = 'tack-fab-main'\n this.mainBtn.setAttribute('aria-label', 'Toggle comments')\n\n // Comment icon (visible when collapsed)\n this.commentIcon = document.createElement('span')\n this.commentIcon.className = 'tack-fab-icon tack-fab-icon--comment'\n this.commentIcon.innerHTML = `<svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><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></svg>`\n\n // Close icon (visible when expanded)\n this.closeIcon = document.createElement('span')\n this.closeIcon.className = 'tack-fab-icon tack-fab-icon--close'\n this.closeIcon.innerHTML = `<svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line></svg>`\n\n this.mainBtn.appendChild(this.commentIcon)\n this.mainBtn.appendChild(this.closeIcon)\n\n // Badge\n this.badge = document.createElement('span')\n this.badge.className = 'tack-fab-badge'\n\n // Assemble: [panel toggle] [main button] [badge] -- left to right\n this.el.appendChild(this.panelToggleBtn)\n this.el.appendChild(this.mainBtn)\n this.el.appendChild(this.badge)\n\n container.appendChild(this.el)\n\n // Apply initial position\n this.applyPosition()\n\n // Remove entrance animation class after it completes\n setTimeout(() => {\n this.el.classList.remove('tack-fab--entering')\n }, 500)\n\n // Set up drag handling on the entire FAB container\n this.el.addEventListener('pointerdown', this.onPointerDown.bind(this))\n\n // Listen for viewport resize to re-clamp Y\n this.boundOnResize = this.onResize.bind(this)\n window.addEventListener('resize', this.boundOnResize)\n }\n\n // ── Position management ──\n\n private applyPosition(): void {\n const y = clampFabY(this.position.y, FAB_SIZE)\n this.position.y = y\n\n // Clear both left/right, then set the active edge\n this.el.style.left = ''\n this.el.style.right = ''\n this.el.style.bottom = ''\n\n if (this.position.edge === 'left') {\n this.el.style.left = `${EDGE_MARGIN}px`\n } else {\n this.el.style.right = `${EDGE_MARGIN}px`\n }\n this.el.style.top = `${y}px`\n\n // Toggle edge class\n this.el.classList.toggle('tack-fab--left', this.position.edge === 'left')\n }\n\n private onResize(): void {\n if (this.dragState !== 'idle') return\n this.position.y = clampFabY(this.position.y, FAB_SIZE)\n this.applyPosition()\n saveFabPosition(this.position)\n }\n\n // ── Drag handling ──\n\n private onPointerDown(e: PointerEvent): void {\n // Only primary button\n if (e.button !== 0) return\n // Don't start drag if the panel toggle button was the direct target\n // (let its click handler work normally)\n\n this.dragState = 'pending'\n this.startPointerX = e.clientX\n this.startPointerY = e.clientY\n\n // Record the current FAB position on screen\n const rect = this.el.getBoundingClientRect()\n this.startFabX = rect.left\n this.startFabY = rect.top\n\n this.boundOnPointerMove = this.onPointerMove.bind(this)\n this.boundOnPointerUp = this.onPointerUp.bind(this)\n window.addEventListener('pointermove', this.boundOnPointerMove)\n window.addEventListener('pointerup', this.boundOnPointerUp)\n }\n\n private onPointerMove(e: PointerEvent): void {\n if (this.dragState === 'pending') {\n if (!exceedsDeadZone(this.startPointerX, this.startPointerY, e.clientX, e.clientY)) {\n return\n }\n // Exceeded dead zone -- enter dragging state\n this.enterDragState()\n }\n\n if (this.dragState !== 'dragging') return\n\n // Move FAB freely following the pointer\n const dx = e.clientX - this.startPointerX\n const dy = e.clientY - this.startPointerY\n const newX = this.startFabX + dx\n const newY = this.startFabY + dy\n\n // Position using left/top for free movement\n this.el.style.left = `${newX}px`\n this.el.style.right = 'auto'\n this.el.style.top = `${newY}px`\n\n // Update snap hint\n this.updateSnapHint(e.clientX)\n }\n\n private onPointerUp(e: PointerEvent): void {\n const wasDragging = this.dragState === 'dragging'\n\n if (this.dragState === 'pending') {\n // Was within dead zone -- treat as click\n this.cleanupDragListeners()\n this.dragState = 'idle'\n // Let the original click event bubble naturally.\n // But since we used pointerdown, we need to manually fire\n // the click logic for the main button.\n this.handleFabClick(e)\n return\n }\n\n if (wasDragging) {\n this.exitDragState(e)\n }\n\n this.cleanupDragListeners()\n }\n\n private enterDragState(): void {\n this.dragState = 'dragging'\n\n // If expanded, collapse first\n if (this.expanded) {\n this.collapse()\n }\n\n // Create full-viewport overlay on document.body\n this.dragOverlay = createDragOverlay({ zIndex: 10004, cursor: 'grabbing' })\n\n // Add dragging class\n this.el.classList.add('tack-fab-dragging')\n\n // Switch to left/top positioning for free movement\n const rect = this.el.getBoundingClientRect()\n this.el.style.left = `${rect.left}px`\n this.el.style.right = 'auto'\n this.el.style.top = `${rect.top}px`\n\n // Prevent text selection\n document.body.style.userSelect = 'none'\n }\n\n private exitDragState(e: PointerEvent): void {\n this.dragState = 'idle'\n\n // Remove drag overlay\n removeDragOverlay(this.dragOverlay)\n this.dragOverlay = null\n\n // Remove snap hint\n this.removeSnapHint()\n\n // Remove dragging class\n this.el.classList.remove('tack-fab-dragging')\n\n // Determine which edge to snap to\n const rect = this.el.getBoundingClientRect()\n const fabCenterX = rect.left + rect.width / 2\n const edge: 'left' | 'right' = fabCenterX < window.innerWidth / 2 ? 'left' : 'right'\n\n // Clamp Y position\n const clampedY = clampFabY(rect.top, FAB_SIZE)\n\n // Update position\n this.position = { edge, y: clampedY }\n saveFabPosition(this.position)\n\n // Animate snap\n this.el.classList.add('tack-fab-snapping')\n this.applyPosition()\n\n // After snap animation, do the settle\n const onSnapEnd = () => {\n this.el.classList.remove('tack-fab-snapping')\n this.el.removeEventListener('transitionend', onSnapEnd)\n\n // Settle animation\n this.el.classList.add('tack-fab-settling')\n setTimeout(() => {\n this.el.classList.remove('tack-fab-settling')\n }, 350)\n }\n this.el.addEventListener('transitionend', onSnapEnd)\n\n // Fallback in case transitionend doesn't fire\n setTimeout(() => {\n this.el.classList.remove('tack-fab-snapping')\n this.el.classList.remove('tack-fab-settling')\n }, 800)\n\n // Restore text selection\n document.body.style.userSelect = ''\n }\n\n private handleFabClick(e: PointerEvent): void {\n // Use composedPath() to see through Shadow DOM boundaries,\n // since window-level pointerup retargets e.target to the shadow host.\n const path = e.composedPath()\n const clickedPanelToggle = path.includes(this.panelToggleBtn)\n\n // Prevent the subsequent native click event from double-firing.\n // Use setTimeout instead of requestAnimationFrame for more reliable timing.\n this.handledByPointer = true\n setTimeout(() => { this.handledByPointer = false }, 100)\n\n if (clickedPanelToggle) {\n this.callbacks.onPanelToggle()\n return\n }\n\n // Otherwise treat as main button click\n if (this.expanded) {\n this.callbacks.onExit()\n } else {\n this.callbacks.onToggle()\n }\n }\n\n private cleanupDragListeners(): void {\n if (this.boundOnPointerMove) {\n window.removeEventListener('pointermove', this.boundOnPointerMove)\n this.boundOnPointerMove = null\n }\n if (this.boundOnPointerUp) {\n window.removeEventListener('pointerup', this.boundOnPointerUp)\n this.boundOnPointerUp = null\n }\n }\n\n // ── Snap hint ──\n\n private updateSnapHint(pointerX: number): void {\n const targetEdge: 'left' | 'right' = pointerX < window.innerWidth / 2 ? 'left' : 'right'\n\n if (!this.snapHint) {\n this.snapHint = document.createElement('div')\n this.snapHint.style.cssText = `\n position: fixed;\n top: 0;\n width: 3px;\n height: 100vh;\n background: rgba(24, 24, 27, 0.15);\n z-index: 10003;\n pointer-events: none;\n transition: left 150ms ease, right 150ms ease, opacity 150ms ease;\n `\n document.body.appendChild(this.snapHint)\n }\n\n if (targetEdge === 'left') {\n this.snapHint.style.left = '0px'\n this.snapHint.style.right = 'auto'\n } else {\n this.snapHint.style.left = 'auto'\n this.snapHint.style.right = '0px'\n }\n this.snapHint.style.opacity = '1'\n }\n\n private removeSnapHint(): void {\n if (this.snapHint) {\n this.snapHint.style.opacity = '0'\n const hint = this.snapHint\n this.snapHint = null\n setTimeout(() => {\n if (hint.parentNode) {\n hint.parentNode.removeChild(hint)\n }\n }, 150)\n }\n }\n\n // ── Public API (unchanged) ──\n\n expand(): void {\n if (this.expanded) return\n this.expanded = true\n\n this.el.classList.add('tack-fab--expanded')\n this.badge.classList.remove('visible')\n }\n\n collapse(): void {\n if (!this.expanded) return\n this.expanded = false\n\n this.el.classList.remove('tack-fab--expanded')\n // Show badge again after collapse if there's a count\n setTimeout(() => {\n if (!this.expanded && parseInt(this.badge.textContent || '0', 10) > 0) {\n this.badge.classList.add('visible')\n }\n }, 200)\n }\n\n isExpanded(): boolean {\n return this.expanded\n }\n\n getEdge(): 'left' | 'right' {\n return this.position.edge\n }\n\n setPanelToggleActive(active: boolean): void {\n this.panelToggleBtn.classList.toggle('tack-fab-btn--active', active)\n }\n\n setBadgeCount(n: number): void {\n this.badge.textContent = n > 99 ? '99+' : String(n)\n if (!this.expanded && n > 0) {\n this.badge.classList.add('visible')\n } else {\n this.badge.classList.remove('visible')\n }\n }\n\n destroy(): void {\n // Clean up drag state\n this.cleanupDragListeners()\n removeDragOverlay(this.dragOverlay)\n this.removeSnapHint()\n document.body.style.userSelect = ''\n\n if (this.boundOnResize) {\n window.removeEventListener('resize', this.boundOnResize)\n this.boundOnResize = null\n }\n\n this.el.remove()\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 { WidgetFAB } from './ui/fab'\n\ntype WidgetState = 'idle' | 'active' | 'active-panel'\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 fab: WidgetFAB | null = null\n private state: WidgetState = 'idle'\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(\n this.onElementSelected.bind(this),\n () => this.transitionTo('idle')\n )\n this.form = new CommentForm(wrapper, this.onCommentSubmit.bind(this))\n this.form.setUser(this.auth.getUser())\n this.form.setOnHide(() => {\n if (this.state === 'active') {\n // Delay re-enable so the click that dismissed the form\n // fully propagates before the selector starts listening.\n // The selector also has a 150ms cooldown after enable() to\n // guard against late-arriving click events from the same gesture.\n setTimeout(() => {\n if (this.state === 'active') {\n this.selector?.enable()\n }\n }, 100)\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.transitionTo('active'),\n onAddComment: () => this.transitionTo('active'),\n onShare: this.onShare.bind(this),\n onRowHover: (commentId: string) => {\n this.pins?.setHoverLinked(commentId)\n },\n onRowHoverEnd: () => {\n this.pins?.setHoverLinked(null)\n },\n onSideChange: () => {\n // Re-apply FAB offset when the panel switches sides\n if (this.state === 'active-panel') {\n if (this.fab) {\n this.panel?.setFabOffset(this.fab.getEdge())\n }\n this.setPanelOpen(true)\n }\n },\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 onReaction: this.onReaction.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 this.pins.setHoverCallbacks(\n (commentId: string) => {\n this.panel?.highlightRow(commentId)\n },\n (commentId: string) => {\n this.panel?.clearRowHighlight(commentId)\n }\n )\n\n // Add FAB toolbar\n this.fab = new WidgetFAB(wrapper, {\n onToggle: () => {\n if (this.state === 'idle') {\n this.transitionTo('active')\n }\n },\n onPanelToggle: () => {\n if (this.state === 'active') {\n this.transitionTo('active-panel')\n } else if (this.state === 'active-panel') {\n this.transitionTo('active')\n }\n },\n onExit: () => {\n this.transitionTo('idle')\n },\n })\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 transitionTo(newState: WidgetState): void {\n const prev = this.state\n\n if (newState === 'idle') {\n // Guard unsaved form content\n if (this.form?.isFormVisible() && this.form.hasContent()) {\n this.form.jitter()\n return\n }\n this.selector?.disable()\n this.form?.hide()\n this.pins?.hide()\n if (prev === 'active-panel') {\n this.panel?.hide()\n this.setPanelOpen(false)\n }\n this.fab?.collapse()\n this.container?.classList.remove('tack-active')\n this.fab?.setPanelToggleActive(false)\n\n this.updateFabBadge()\n } else if (newState === 'active') {\n this.selector?.enable()\n this.pins?.show()\n if (prev === 'active-panel') {\n this.panel?.hide()\n this.setPanelOpen(false)\n }\n if (prev === 'idle') {\n this.fab?.expand()\n }\n this.container?.classList.add('tack-active')\n\n this.fab?.setPanelToggleActive(false)\n } else if (newState === 'active-panel') {\n this.selector?.disable()\n this.pins?.show()\n // Apply FAB-aware offset before showing panel\n if (this.fab) {\n this.panel?.setFabOffset(this.fab.getEdge())\n }\n this.panel?.show()\n this.setPanelOpen(true)\n if (prev === 'idle') {\n this.fab?.expand()\n }\n this.container?.classList.add('tack-active')\n\n this.fab?.setPanelToggleActive(true)\n }\n\n this.state = newState\n }\n\n private updateFabBadge(): void {\n const unresolvedCount = this.comments.filter(c => !c.resolved && !c.parent_id).length\n this.fab?.setBadgeCount(unresolvedCount)\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 // Panel overlays the page — no page-push styles needed\n }\n\n private setPanelOpen(_open: boolean): void {\n // Panel overlays the page — no body class manipulation needed\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 const key = e.key.toLowerCase()\n // Only handle our shortcuts\n if (key !== 'c' && key !== 'n' && key !== 'p') 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 if (key === 'c') {\n e.preventDefault()\n if (this.state === 'idle') {\n this.transitionTo('active')\n } else if (this.state === 'active-panel') {\n this.transitionTo('active')\n } else {\n this.transitionTo('idle')\n }\n return\n }\n\n // N/P only work when panel is open\n if (this.state !== 'active-panel') 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 this.pins?.render(this.comments)\n this.panel?.render(this.comments)\n this.updateFabBadge()\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 pendingRetryData: {\n content: string\n authorName: string\n authorEmail?: string\n anchor: CommentAnchor\n screenshot?: string\n } | null = null\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 // Show loading pin after 150ms delay (skip if API responds fast)\n let showedLoading = false\n const loadingTimer = window.setTimeout(() => {\n const user = this.auth.getUser()\n this.pins?.showLoadingPin(data.anchor, {\n name: user?.name || data.authorName,\n avatar_url: user?.avatar_url || null,\n })\n showedLoading = true\n }, 150)\n\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 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\n clearTimeout(loadingTimer)\n if (showedLoading) {\n this.pins?.resolveLoadingPin()\n }\n\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 this.pendingRetryData = null\n this.updateFabBadge()\n if (this.state === 'active') {\n this.selector?.enable()\n }\n } catch (error) {\n clearTimeout(loadingTimer)\n console.error('[Tack] Failed to submit comment:', error)\n\n if (showedLoading) {\n this.pendingRetryData = data\n this.pins?.failLoadingPin(() => this.retryFailedComment())\n } else {\n this.showToast('Failed to add comment')\n }\n }\n }\n\n private retryFailedComment(): void {\n if (!this.pendingRetryData) return\n const data = this.pendingRetryData\n this.pendingRetryData = null\n this.onCommentSubmit(data)\n }\n\n private onCommentClick(comment: Comment): void {\n // Scroll page to the pin location\n this.scrollToComment(comment)\n\n // After scroll settles, open the card at the pin and beacon\n setTimeout(() => {\n const pinEl = this.pins?.getPinElement(comment.id)\n if (pinEl) {\n this.card?.show(comment, pinEl)\n this.pins?.setActive(comment.id)\n this.pins?.beacon(comment.id)\n }\n }, 300)\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 this.updateFabBadge()\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.updateFabBadge()\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 async onReaction(commentId: string, emoji: string, add: boolean): Promise<void> {\n if (!this.auth.isAuthenticated()) return\n\n const comment = this.comments.find(c => c.id === commentId)\n if (!comment) return\n\n const user = this.auth.getUser()!\n comment.reactions = comment.reactions || []\n\n // Optimistic update\n if (add) {\n comment.reactions.push({ emoji, user_id: user.id, user_name: user.name })\n } else {\n comment.reactions = comment.reactions.filter(\n r => !(r.emoji === emoji && r.user_id === user.id)\n )\n }\n this.card?.updateComment(comment)\n\n try {\n if (add) {\n await this.api.addReaction(commentId, emoji)\n } else {\n await this.api.removeReaction(commentId, emoji)\n }\n } catch (error) {\n console.error('[Tack] Failed to update reaction:', error)\n // Reload to restore correct state\n await this.loadComments()\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.transitionTo('active-panel')\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.transitionTo('active-panel')\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 this.updateFabBadge()\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 this.fab?.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 // No page-push styles or body classes to clean up (panel overlays)\n\n this.container?.remove()\n }\n}\n"],"mappings":"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AAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;A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","names":["index_exports","__export","Tack","TackWidget","__toCommonJS","TackAPI","baseUrl","projectId","token","cb","headers","res","data","id","commentId","emoji","url","WidgetAuth","apiUrl","token","res","user","resolve","reject","baseUrl","popup","expectedOrigin","handleMessage","event","pollTimer","cb","createStyles","ElementSelector","callback","onEscape","target","rect","pad","radius","relativeX","relativeY","viewportRelativeX","viewportRelativeY","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","inspect","textarea","sendBtn","ke","target","captureElementWithContext","rect","viewportX","viewportY","fw","formWidth","formHeight","left","top","onEnd","maxHeight","content","authorName","submitData","error","el","d","element","category","label","props","labelEl","propsEl","k","v","meaningful","c","truncate","s","n","text","href","u","alt","src","filename","ariaLabel","tag","name","type","checked","cs","propNames","resolved","val","first","w","h","t","r","b","l","dir","cols","div","CommentForm","AVATAR_GRADIENTS","AVATAR_COLORS","to","hashName","name","hash","i","getAvatarColor","getAvatarGradient","from","getInitials","parts","renderAvatar","avatarUrl","size","extraClass","fontSize","cls","STORAGE_KEY_PREFIX","storageKey","loadFabPosition","raw","parsed","saveFabPosition","pos","createDragOverlay","options","overlay","removeDragOverlay","clampFabY","y","fabHeight","maxY","exceedsDeadZone","startX","startY","currentX","currentY","threshold","dx","dy","PANEL_SIDE_KEY_PREFIX","panelSideKey","loadPanelSide","savePanelSide","side","STORAGE_KEY","loadFilters","stored","parsed","saveFilters","filters","CommentPanel","container","callbacks","loadPanelSide","user","projectId","key","commentId","filtered","changed","c","strip","searchInput","searchClear","val","e","dropdown","f","check","empty","item","action","badge","btn","count","newSide","wasOpen","savePanelSide","header","target","onMove","me","exceedsDeadZone","onUp","createDragOverlay","dx","newX","panelCenter","vpMidpoint","targetSide","removeDragOverlay","rect","side","fabEdge","sameSide","comments","_users","content","activeFilterCount","comment","row","id","dot","resolveBtn","moreBtn","parts","s","pagePath","path","isHighlighted","timeAgo","currentPath","isCurrentPage","pageName","unread","renderAvatar","result","userId","userName","r","q","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","anchorResult","targetElement","pin","isActive","isHighlighted","uniqueAuthors","isMultiAuthor","previewAuthor","timeAgo","previewText","replyCount","pinHTML","visibleAuthors","overflow","avatarsHTML","i","previewAvatarsHTML","multiPreviewHTML","singlePreviewHTML","e","pinOffsetX","pinOffsetY","position","rect","x","y","date","seconds","text","div","leaveTimer","timer","hoverTimer","prev","pinRect","id","onHover","onHoverEnd","anchor","user","avatarHTML","avatar","onRetry","tooltip","CommentCard","container","callbacks","e","user","comment","pinElement","pinRect","cardWidth","gap","left","top","estimatedHeight","replies","currentAuthor","html","reply","index","renderAvatar","isMain","timeAgo","canEdit","reactions","userId","grouped","existing","emoji","data","mine","title","emojis","alreadyReacted","btn","action","textarea","ke","isMine","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","FAB_SIZE","EDGE_MARGIN","WidgetFAB","container","callbacks","saved","loadFabPosition","e","y","clampFabY","saveFabPosition","rect","exceedsDeadZone","dx","dy","newX","newY","wasDragging","createDragOverlay","removeDragOverlay","edge","clampedY","onSnapEnd","clickedPanelToggle","pointerX","targetEdge","hint","active","n","TackWidget","config","TackAPI","WidgetAuth","styles","createStyles","wrapper","user","ElementSelector","CommentForm","CommentPanel","commentId","CommentCard","CommentPins","WidgetFAB","PresenceManager","users","error","args","newPath","newState","prev","unresolvedCount","c","url","_open","key","active","shadowActive","direction","ids","comment","data","target","showedLoading","loadingTimer","createData","commentWithScreenshot","pinEl","pinElement","resolved","parentId","content","reply","parent","emoji","add","x","y","params","RealtimeSubscription","event","existing","message","toast","Tack","config","widget","TackWidget"]}
1
+ {"version":3,"sources":["../src/index.ts","../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/drag.ts","../src/ui/panel.ts","../src/anchoring.ts","../src/ui/pins.ts","../src/ui/card.ts","../src/realtime.ts","../src/presence.ts","../src/ui/fab.ts","../src/widget.ts"],"sourcesContent":["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","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 addReaction(commentId: string, emoji: string): Promise<void> {\n const res = await fetch(`${this.baseUrl}/comments/${commentId}/reactions`, {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify({ emoji }),\n })\n await this.handleResponse(res)\n if (!res.ok) throw new Error('Failed to add reaction')\n }\n\n async removeReaction(commentId: string, emoji: string): Promise<void> {\n const res = await fetch(`${this.baseUrl}/comments/${commentId}/reactions`, {\n method: 'DELETE',\n headers: this.getHeaders(),\n body: JSON.stringify({ emoji }),\n })\n await this.handleResponse(res)\n if (!res.ok) throw new Error('Failed to remove reaction')\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 /* ── FAB Toolbar ── */\n\n /* Collapsed: 44px dark circle, positioned via top + left/right (set by JS) */\n .tack-fab {\n position: fixed;\n top: calc(100vh - 64px);\n right: 20px;\n height: 44px;\n width: 44px;\n border-radius: 22px;\n background: #18181B;\n display: flex;\n align-items: center;\n justify-content: flex-end;\n padding: 5px;\n gap: 2px;\n box-shadow: 0 2px 8px rgba(0,0,0,0.22), 0 3px 12px rgba(0,0,0,0.08);\n z-index: 10000;\n transition: width 180ms cubic-bezier(0.55, 0, 1, 0.45);\n will-change: width;\n cursor: grab;\n touch-action: none;\n }\n\n .tack-fab:hover {\n box-shadow: 0 3px 10px rgba(0,0,0,0.25), 0 5px 18px rgba(0,0,0,0.10);\n }\n\n /* Expanded: unfurls LEFT, right edge stays anchored */\n .tack-fab--expanded {\n width: 92px;\n transition: width 220ms cubic-bezier(0.25, 1, 0.5, 1);\n }\n\n /* Entrance */\n @keyframes tack-fab-entrance {\n from { transform: scale(0.5) rotate(90deg); opacity: 0; }\n to { transform: scale(1) rotate(0); opacity: 1; }\n }\n .tack-fab--entering {\n animation: tack-fab-entrance 500ms cubic-bezier(0.16,1,0.3,1) both;\n }\n\n /* Button base */\n .tack-fab button {\n width: 34px;\n height: 34px;\n border-radius: 50%;\n border: none;\n background: transparent;\n color: #FFFFFF;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n padding: 0;\n transition: background 120ms ease;\n }\n .tack-fab button:hover { background: rgba(255,255,255,0.12); }\n .tack-fab button:active { background: rgba(255,255,255,0.18); }\n\n /* Main button — holds stacked comment/close icons */\n .tack-fab-main { position: relative; }\n\n /* Icon swap — two icons stacked absolutely */\n .tack-fab-icon {\n position: absolute;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: opacity 150ms ease, transform 220ms cubic-bezier(0.34, 1.56, 0.64, 1);\n }\n\n /* Comment icon: visible when collapsed */\n .tack-fab-icon--comment {\n opacity: 1;\n transform: rotate(0deg) scale(1);\n }\n .tack-fab--expanded .tack-fab-icon--comment {\n opacity: 0;\n transform: rotate(90deg) scale(0.6);\n }\n\n /* Close icon: visible when expanded */\n .tack-fab-icon--close {\n opacity: 0;\n transform: rotate(-90deg) scale(0.6);\n }\n .tack-fab--expanded .tack-fab-icon--close {\n opacity: 1;\n transform: rotate(0deg) scale(1);\n transition-delay: 60ms;\n }\n\n /* Panel toggle — hidden when collapsed, slides in on expand */\n .tack-fab-panel-toggle {\n opacity: 0;\n width: 0;\n overflow: hidden;\n transform: translateX(8px);\n pointer-events: none;\n transition: opacity 80ms ease, transform 80ms ease, width 180ms cubic-bezier(0.55, 0, 1, 0.45);\n }\n .tack-fab--expanded .tack-fab-panel-toggle {\n opacity: 1;\n width: 34px;\n transform: translateX(0);\n pointer-events: auto;\n transition: opacity 150ms ease 80ms, transform 150ms cubic-bezier(0.25, 1, 0.5, 1) 80ms, width 220ms cubic-bezier(0.25, 1, 0.5, 1);\n }\n\n /* Active state for panel toggle */\n .tack-fab-btn--active { background: rgba(255,255,255,0.15) !important; }\n .tack-fab-btn--active:hover { background: rgba(255,255,255,0.20) !important; }\n\n /* Badge — top-right of FAB */\n .tack-fab-badge {\n position: absolute;\n top: -3px;\n right: -3px;\n min-width: 20px;\n height: 20px;\n border-radius: 10px;\n background: #4F6BED;\n color: white;\n font-size: 11px;\n font-weight: 600;\n display: none;\n align-items: center;\n justify-content: center;\n padding: 0 5px;\n pointer-events: none;\n box-shadow: 0 1px 4px rgba(0,0,0,0.25);\n }\n .tack-fab-badge.visible { display: flex; }\n .tack-fab--expanded .tack-fab-badge { display: none !important; }\n\n /* Left-edge: unfurl to the RIGHT instead of LEFT */\n .tack-fab--left {\n justify-content: flex-start;\n }\n\n .tack-fab--left .tack-fab-panel-toggle {\n transform: translateX(-8px);\n }\n\n .tack-fab--left.tack-fab--expanded .tack-fab-panel-toggle {\n transform: translateX(0);\n }\n\n /* Dragging state — elevated, slightly scaled */\n .tack-fab-dragging {\n transform: scale(1.06) !important;\n box-shadow: 0 12px 32px rgba(0, 0, 0, 0.18) !important;\n opacity: 0.92 !important;\n cursor: grabbing !important;\n transition: none !important;\n z-index: 10005 !important;\n }\n\n /* Snapping to edge */\n .tack-fab-snapping {\n transition: left 400ms cubic-bezier(0.175, 0.885, 0.32, 1.1),\n right 400ms cubic-bezier(0.175, 0.885, 0.32, 1.1),\n top 400ms cubic-bezier(0.175, 0.885, 0.32, 1.1) !important;\n }\n\n /* Settling after snap */\n .tack-fab-settling {\n transition: transform 350ms cubic-bezier(0.2, 0, 0, 1),\n box-shadow 350ms cubic-bezier(0.2, 0, 0, 1),\n opacity 350ms cubic-bezier(0.2, 0, 0, 1) !important;\n }\n\n /* Reduced motion — drag */\n @media (prefers-reduced-motion: reduce) {\n .tack-fab-snapping { transition-duration: 0ms !important; }\n .tack-fab-settling { transition-duration: 0ms !important; }\n .tack-fab-dragging { transform: none !important; opacity: 1 !important; }\n }\n\n /* Reduced motion — FAB */\n @media (prefers-reduced-motion: reduce) {\n .tack-fab, .tack-fab--expanded { transition-duration: 0ms !important; }\n .tack-fab--entering { animation: none; }\n .tack-fab-icon { transition-duration: 0ms !important; transition-delay: 0ms !important; }\n .tack-fab-panel-toggle { transition-duration: 0ms !important; transition-delay: 0ms !important; }\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 — collapsed width set via --pin-w custom property */\n .tack-pin.multi-author .tack-pin-shell {\n border-radius: 17px;\n padding: 3px;\n width: var(--pin-w, 74px);\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 display: block;\n }\n\n .tack-pin-avatar-stacked .tack-pin-avatar-fallback {\n width: 100% !important;\n height: 100% !important;\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 --pin-w: 256px;\n border-radius: 14px;\n max-height: 160px;\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 /* Multi-author preview: avatar stack row at top */\n .tack-pin-preview-avatars {\n display: flex;\n align-items: center;\n margin-bottom: 6px;\n }\n\n .tack-pin-preview-avatars .tack-pin-avatar-stacked {\n width: 24px;\n height: 24px;\n border-radius: 50%;\n margin-left: -6px;\n position: relative;\n flex-shrink: 0;\n overflow: hidden;\n border: 2px solid #FFFFFF;\n }\n\n .tack-pin-preview-avatars .tack-pin-avatar-stacked:first-child {\n margin-left: 0;\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 — right side (default), floating with 32px gap */\n .tack-panel-strip {\n position: fixed;\n top: 32px;\n right: 32px;\n width: 360px;\n height: calc(100vh - 64px);\n background: transparent;\n padding: 0;\n transform: translateX(calc(100% + 100px));\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 /* When FAB is on the same side, push panel inward (only when open,\n so the slide-out transform still pushes fully off-screen).\n Expanded FAB = 92px wide + 20px edge margin + 32px gap = 144px */\n .tack-panel-strip.open.tack-panel-fab-offset {\n right: 144px;\n }\n\n /* Panel Strip — left side */\n .tack-panel-strip.tack-panel-left {\n right: auto;\n left: 32px;\n transform: translateX(calc(-100% - 100px));\n }\n\n .tack-panel-strip.tack-panel-left.open {\n transform: translateX(0);\n }\n\n .tack-panel-strip.tack-panel-left.open.tack-panel-fab-offset {\n left: 144px;\n }\n\n /* Snap transition when switching sides */\n .tack-panel-strip.tack-panel-snapping {\n transition: transform 350ms cubic-bezier(0.16, 1, 0.3, 1);\n }\n\n .tack-panel-header.tack-dragging {\n cursor: grabbing;\n }\n\n /* Edge snap hint during panel drag */\n .tack-panel-snap-hint {\n position: fixed;\n top: 0;\n bottom: 0;\n width: 4px;\n background: rgba(37, 99, 235, 0.35);\n z-index: 10005;\n pointer-events: none;\n opacity: 0;\n transition: opacity 150ms ease;\n }\n\n .tack-panel-snap-hint.visible {\n opacity: 1;\n }\n\n .tack-panel-snap-hint.left {\n left: 0;\n }\n\n .tack-panel-snap-hint.right {\n right: 0;\n }\n\n /* Panel dragging state — elevated ghost */\n .tack-panel-strip.tack-panel-dragging {\n box-shadow: 0 16px 48px rgba(0, 0, 0, 0.2) !important;\n transition: none !important;\n z-index: 10005 !important;\n }\n\n .tack-panel-strip.tack-panel-dragging .tack-panel {\n box-shadow: 0 16px 48px rgba(0, 0, 0, 0.2);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .tack-panel-strip.tack-panel-snapping {\n transition-duration: 0ms !important;\n }\n .tack-panel-strip.tack-panel-dragging {\n opacity: 1 !important;\n }\n }\n\n /* Panel Card — floating with rounded corners */\n .tack-panel {\n flex: 1;\n background: #FFFFFF;\n border-radius: 16px;\n border: none;\n box-shadow:\n 0 8px 32px rgba(0, 0, 0, 0.12),\n 0 0 0 1px rgba(0, 0, 0, 0.06);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .tack-panel-header {\n padding: 16px 16px 12px 16px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n cursor: grab;\n user-select: none;\n -webkit-user-select: none;\n touch-action: none;\n }\n\n .tack-panel-title {\n font-weight: 600;\n font-size: 15px;\n color: #18181b;\n }\n\n .tack-panel-close {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n border-radius: 8px;\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 .tack-panel-toolbar {\n padding: 0 16px 12px 16px;\n display: flex;\n align-items: center;\n gap: 8px;\n border-bottom: 1px solid rgba(0, 0, 0, 0.06);\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 scrollbar-width: none;\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 .tack-panel-content::-webkit-scrollbar {\n display: none;\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 100ms cubic-bezier(0.4, 0, 0.2, 1);\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 opacity: 1;\n pointer-events: auto;\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(37, 99, 235, 0.05);\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: #2563EB;\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: none;\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\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 /* Jitter — gentle \"you have unsaved content\" nudge.\n Horizontal shake with decaying amplitude + subtle border warmth.\n Distinct from tack-pin-shake (error), this is cautionary, not alarming. */\n @keyframes tack-form-jitter {\n 0% { transform: translateX(0); }\n 12% { transform: translateX(-6px) scale(1.01); }\n 28% { transform: translateX(4px); }\n 44% { transform: translateX(-3px); }\n 60% { transform: translateX(2px); }\n 76% { transform: translateX(-1px); }\n 88% { transform: translateX(0.5px);}\n 100% { transform: translateX(0); }\n }\n\n @keyframes tack-form-jitter-border {\n 0% { border-color: #D1D5DB; box-shadow: 0 4px 12px rgba(0,0,0,0.08), 0 1px 3px rgba(0,0,0,0.06); }\n 25% { border-color: #F59E0B; box-shadow: 0 4px 16px rgba(245,158,11,0.12), 0 1px 3px rgba(0,0,0,0.06); }\n 100% { border-color: #D1D5DB; box-shadow: 0 4px 12px rgba(0,0,0,0.08), 0 1px 3px rgba(0,0,0,0.06); }\n }\n\n .tack-form.jitter {\n animation: tack-form-jitter 350ms ease-out;\n }\n\n .tack-form.jitter .tack-form-pill {\n animation: tack-form-jitter-border 400ms cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .tack-form.jitter {\n animation: none;\n }\n .tack-form.jitter .tack-form-pill {\n animation: tack-form-jitter-border 300ms ease;\n }\n }\n\n /* Pill container */\n .tack-form-pill {\n display: flex;\n flex-direction: column;\n background: #FFFFFF;\n border: 1px solid #E5E7EB;\n border-radius: 20px;\n padding: 6px;\n width: 300px;\n box-shadow: 0 8px 24px rgba(0,0,0,0.08), 0 1px 3px rgba(0,0,0,0.04);\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n overflow: hidden;\n }\n\n .tack-form-input-row {\n display: flex;\n align-items: flex-end;\n gap: 8px;\n padding: 8px 8px 8px 14px;\n background: #F4F4F5;\n border-radius: 16px;\n transition: background 150ms ease;\n }\n\n .tack-form-input-row:focus-within {\n background: #EBEBED;\n }\n\n /* Element inspect accordion */\n .tack-form-inspect {\n padding: 0 4px;\n margin-bottom: 4px;\n }\n\n .tack-form-inspect-toggle {\n display: flex;\n align-items: center;\n gap: 4px;\n width: 100%;\n padding: 6px 8px 4px 8px;\n background: none;\n border: none;\n cursor: pointer;\n font-size: 12px;\n font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;\n color: #71717a;\n text-align: left;\n border-radius: 8px;\n transition: background 0.1s ease;\n }\n\n .tack-form-inspect-toggle:hover {\n background: rgba(0, 0, 0, 0.04);\n }\n\n .tack-form-inspect-chevron {\n flex-shrink: 0;\n transition: transform 150ms ease;\n }\n\n .tack-form-inspect.expanded .tack-form-inspect-chevron {\n transform: rotate(90deg);\n }\n\n .tack-form-inspect-label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .tack-form-inspect-props {\n max-height: 0;\n overflow: hidden;\n transition: max-height 200ms cubic-bezier(0.16, 1, 0.3, 1), padding 200ms ease;\n background: #1e1e1e;\n border-radius: 8px;\n margin: 0 4px;\n padding: 0 10px;\n font-size: 11.5px;\n font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;\n line-height: 1.6;\n white-space: pre;\n }\n\n .tack-form-inspect.expanded .tack-form-inspect-props {\n max-height: 160px;\n padding: 8px 10px;\n margin-bottom: 4px;\n overflow-y: auto;\n scrollbar-width: none;\n }\n\n .tack-form-inspect.expanded .tack-form-inspect-props::-webkit-scrollbar {\n display: none;\n }\n\n .tack-inspect-prop {\n display: block;\n color: #d4d4d4;\n }\n\n .tack-inspect-key {\n color: #9cdcfe;\n }\n\n .tack-inspect-val {\n color: #ce9178;\n }\n\n .tack-form-pill:focus-within {\n border-color: #D1D5DB;\n box-shadow: 0 8px 24px rgba(0,0,0,0.10), 0 2px 6px rgba(0,0,0,0.04);\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: 2px 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: #E0E0E3;\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 /* Reactions */\n .tack-reaction-bar {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n padding: 6px 0 2px 36px;\n align-items: center;\n }\n\n .tack-reaction-pill {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 2px 8px;\n border-radius: 12px;\n background: #F4F4F5;\n border: 1px solid transparent;\n font-size: 13px;\n cursor: pointer;\n line-height: 1.4;\n transition: background 0.15s, border-color 0.15s;\n }\n\n .tack-reaction-pill:hover {\n background: #E4E4E7;\n }\n\n .tack-reaction-pill.mine {\n background: #EEF2FF;\n border-color: #C7D2FE;\n }\n\n .tack-reaction-pill.mine:hover {\n background: #E0E7FF;\n }\n\n .tack-reaction-add {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 26px;\n height: 26px;\n border-radius: 50%;\n background: #F4F4F5;\n border: 1px solid transparent;\n font-size: 14px;\n color: #A1A1AA;\n cursor: pointer;\n transition: background 0.15s, color 0.15s;\n }\n\n .tack-reaction-add:hover {\n background: #E4E4E7;\n color: #71717A;\n }\n\n .tack-reaction-picker {\n display: flex;\n gap: 2px;\n background: white;\n border: 1px solid #E4E4E7;\n border-radius: 8px;\n padding: 4px;\n box-shadow: 0 2px 8px rgba(0,0,0,0.1);\n position: absolute;\n z-index: 10;\n }\n\n .tack-reaction-picker-item {\n width: 32px;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n border: none;\n background: none;\n border-radius: 6px;\n font-size: 18px;\n cursor: pointer;\n transition: background 0.1s;\n }\n\n .tack-reaction-picker-item:hover {\n background: #F4F4F5;\n }\n\n .tack-reaction-picker-item.mine {\n background: #EEF2FF;\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 flex-direction: row;\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 /* ── Loading Pin ── */\n\n @keyframes tack-pin-enter {\n from { opacity: 0; transform: scale(0.6); }\n to { opacity: 1; transform: scale(1); }\n }\n\n @keyframes tack-loading-ring {\n from { transform: rotate(0deg); }\n to { transform: rotate(360deg); }\n }\n\n @keyframes tack-loading-pulse {\n 0%, 100% { opacity: 0.3; }\n 50% { opacity: 0.6; }\n }\n\n @keyframes tack-pin-shake {\n 0%, 100% { transform: translateX(0); }\n 20% { transform: translateX(-5px); }\n 40% { transform: translateX(4px); }\n 60% { transform: translateX(-3px); }\n 80% { transform: translateX(2px); }\n }\n\n @keyframes tack-pin-settle {\n 0% { transform: scale(1); }\n 50% { transform: scale(1.05); }\n 100% { transform: scale(1); }\n }\n\n .tack-pin-loading {\n animation: tack-pin-enter 200ms cubic-bezier(0.16, 1, 0.3, 1) both;\n pointer-events: none;\n }\n\n .tack-pin-loading .tack-pin-avatar-img,\n .tack-pin-loading .tack-pin-avatar-fallback {\n opacity: 0.35;\n filter: blur(3px);\n }\n\n .tack-pin-loading-ring {\n position: absolute;\n top: 1px;\n left: 1px;\n width: 36px;\n height: 36px;\n animation: tack-loading-ring 3s linear infinite;\n }\n\n .tack-pin-loading-ring circle {\n fill: none;\n stroke: rgba(59, 130, 246, 0.5);\n stroke-width: 2;\n stroke-dasharray: 25 75;\n stroke-linecap: round;\n animation: tack-loading-pulse 1.8s ease-in-out infinite;\n }\n\n .tack-pin-settle {\n animation: tack-pin-settle 100ms cubic-bezier(0.34, 1.56, 0.64, 1);\n }\n\n .tack-pin-failed {\n cursor: pointer;\n pointer-events: auto;\n animation: tack-pin-shake 400ms ease-out;\n }\n\n .tack-pin-failed .tack-pin-loading-ring circle {\n stroke: rgba(239, 68, 68, 0.5);\n animation: none;\n stroke-dasharray: 100 0;\n }\n\n .tack-pin-failed-tooltip {\n position: absolute;\n top: -32px;\n left: 50%;\n transform: translateX(-50%);\n background: #1a1a1a;\n color: white;\n font-size: 11px;\n padding: 4px 8px;\n border-radius: 4px;\n white-space: nowrap;\n opacity: 0;\n pointer-events: none;\n transition: opacity 150ms ease;\n }\n\n .tack-pin-failed:hover .tack-pin-failed-tooltip {\n opacity: 1;\n }\n\n @media (prefers-reduced-motion: reduce) {\n .tack-pin-loading {\n animation: none;\n opacity: 0.6;\n }\n .tack-pin-loading-ring {\n animation: none;\n }\n .tack-pin-loading-ring circle {\n animation: none;\n opacity: 0.5;\n }\n .tack-pin-settle {\n animation: none;\n }\n .tack-pin-failed {\n animation: none;\n }\n }\n\n /* ── Pin ↔ Panel Cross-linking ── */\n\n /* Panel row highlight when hovering corresponding pin */\n .tack-comment-row.pin-hover {\n background-color: rgba(0, 0, 0, 0.04);\n transition: background-color 120ms cubic-bezier(0.16, 1, 0.3, 1);\n }\n\n /* Pin highlight when hovering corresponding panel row —\n scale up + blue ring on shell */\n .tack-pin.hover-linked {\n transform: scale(1.06);\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 transition: transform 220ms cubic-bezier(0.16, 1, 0.3, 1), filter 200ms cubic-bezier(0.16, 1, 0.3, 1);\n }\n\n .tack-pin.hover-linked .tack-pin-shell {\n box-shadow: 0 0 0 2px rgba(6, 182, 212, 0.65);\n transition:\n box-shadow 200ms cubic-bezier(0.16, 1, 0.3, 1),\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 /* Beacon pulse when navigating from panel click —\n pseudo-element so it doesn't conflict with .active ring */\n .tack-pin.beacon .tack-pin-shell::before {\n content: '';\n position: absolute;\n inset: -4px;\n border-radius: 23px;\n pointer-events: none;\n animation: tack-pin-beacon 700ms cubic-bezier(0.16, 1, 0.3, 1);\n }\n\n @keyframes tack-pin-beacon {\n 0% { box-shadow: 0 0 0 0 rgba(37, 99, 235, 0.4); }\n 40% { box-shadow: 0 0 0 8px rgba(37, 99, 235, 0); }\n 50% { box-shadow: 0 0 0 0 rgba(37, 99, 235, 0.25); }\n 90% { box-shadow: 0 0 0 12px rgba(37, 99, 235, 0); }\n 100% { box-shadow: 0 0 0 12px rgba(37, 99, 235, 0); }\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 onEscape: (() => void) | undefined\n private enabled = false\n private highlightedElement: Element | null = null\n private highlight: HTMLDivElement | null = null\n private scrim: HTMLDivElement | null = null\n private styleElement: HTMLStyleElement | null = null\n private settleTimer: ReturnType<typeof setTimeout> | null = null\n private pendingTarget: Element | null = null\n private enabledAt: number = 0\n\n constructor(callback: SelectionCallback, onEscape?: () => void) {\n this.callback = callback\n this.onEscape = onEscape\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 }\n\n enable(): void {\n if (this.enabled) return\n this.enabled = true\n this.enabledAt = Date.now()\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 // Set crosshair cursor on the page (except on the tack widget itself)\n this.styleElement = document.createElement('style')\n this.styleElement.textContent = `\n body, body * { cursor: crosshair !important; }\n #tack-widget, #tack-widget * { cursor: default !important; }\n `\n document.head.appendChild(this.styleElement)\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.styleElement) { this.styleElement.remove(); this.styleElement = null }\n this.highlightedElement = null\n this.pendingTarget = null\n }\n\n private inCooldown(): boolean {\n return Date.now() - this.enabledAt < 150\n }\n\n private onMouseDown(e: MouseEvent): void {\n if ((e.target as Element).closest('#tack-widget')) {\n return\n }\n if (this.inCooldown()) return\n e.preventDefault()\n e.stopPropagation()\n }\n\n private onMouseMove(e: MouseEvent): void {\n if (this.inCooldown()) return\n const target = e.target as Element\n\n if (target.closest('#tack-widget')) {\n this.clearHighlight()\n return\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 if (this.inCooldown()) return\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 // Viewport-relative position for fallback when element can't be found\n const viewportRelativeX = e.clientX / window.innerWidth\n const viewportRelativeY = e.clientY / window.innerHeight\n\n const anchor = this.generateAnchor(target, relativeX, relativeY, viewportRelativeX, viewportRelativeY)\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 this.onEscape?.()\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(\n element: Element,\n relativeX: number,\n relativeY: number,\n viewportRelativeX: number,\n viewportRelativeY: number\n ): 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 viewportRelativeX,\n viewportRelativeY,\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 private boundOnOutsideClick: ((e: MouseEvent) => void) | 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 <div class=\"tack-form-inspect\" style=\"display:none;\">\n <button type=\"button\" class=\"tack-form-inspect-toggle\">\n <svg class=\"tack-form-inspect-chevron\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\">\n <path d=\"M3.5 2L6.5 5L3.5 8\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n <span class=\"tack-form-inspect-label\"></span>\n </button>\n <div class=\"tack-form-inspect-props\"></div>\n </div>\n <div class=\"tack-form-input-row\">\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 </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 // Inspect accordion toggle\n form.querySelector('.tack-form-inspect-toggle')?.addEventListener('click', (e) => {\n e.stopPropagation()\n const inspect = form.querySelector('.tack-form-inspect') as HTMLElement\n if (inspect) inspect.classList.toggle('expanded')\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 — jitter first if there's unsaved content\n form.addEventListener('keydown', (e: Event) => {\n const ke = e as KeyboardEvent\n if (ke.key === 'Escape') {\n ke.preventDefault()\n if (this.hasContent()) {\n this.jitter()\n } else {\n this.hide()\n }\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 at the actual click position\n let viewportX: number\n let viewportY: number\n\n if (target.element) {\n const rect = target.element.getBoundingClientRect()\n // Reconstruct click position within the element\n viewportX = rect.left + (target.anchor.relativeX * rect.width) + 12\n viewportY = rect.top + (target.anchor.relativeY * rect.height)\n } else if (target.anchor.viewportRelativeX != null && target.anchor.viewportRelativeY != null) {\n viewportX = target.anchor.viewportRelativeX * window.innerWidth + 12\n viewportY = target.anchor.viewportRelativeY * window.innerHeight\n } else {\n viewportX = target.anchor.relativeX * window.innerWidth + 20\n viewportY = target.anchor.relativeY * window.innerHeight\n }\n\n // Prevent form from going off-screen right\n const fw = 300\n if (viewportX + fw > window.innerWidth - 20) {\n viewportX = Math.max(20, viewportX - fw - 24)\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 // Populate element inspect accordion\n this.populateInspect(target.element)\n\n // Listen for outside clicks to jitter (if content) or dismiss (if empty)\n this.boundOnOutsideClick = (e: MouseEvent) => {\n // Clicks inside the form's shadow host are stopped by stopPropagation\n // in attachFormListeners, so anything that reaches here is \"outside.\"\n if (this.hasContent()) {\n this.jitter()\n } else {\n this.hide()\n }\n }\n // Use setTimeout so the click that opened the form doesn't immediately trigger\n setTimeout(() => {\n if (this.boundOnOutsideClick) {\n document.addEventListener('mousedown', this.boundOnOutsideClick)\n }\n }, 0)\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.form.classList.remove('jitter')\n if (this.boundOnOutsideClick) {\n document.removeEventListener('mousedown', this.boundOnOutsideClick)\n this.boundOnOutsideClick = null\n }\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 // Reset inspect accordion\n const inspect = this.form.querySelector('.tack-form-inspect') as HTMLElement\n if (inspect) {\n inspect.classList.remove('expanded')\n inspect.style.display = 'none'\n }\n\n this.onHide?.()\n }\n\n isFormVisible(): boolean {\n return this.isVisible\n }\n\n hasContent(): boolean {\n const textarea = this.form.querySelector('#tack-comment-content') as HTMLTextAreaElement\n return !!textarea && textarea.value.trim().length > 0\n }\n\n jitter(): void {\n // Remove then re-add class so animation replays if already in progress\n this.form.classList.remove('jitter')\n // Force reflow so the browser sees the class removal before re-add\n void this.form.offsetWidth\n this.form.classList.add('jitter')\n\n // Refocus the textarea so the user can keep typing\n const textarea = this.form.querySelector('#tack-comment-content') as HTMLTextAreaElement\n textarea?.focus()\n\n // Clean up class after animation completes\n const onEnd = () => {\n this.form.classList.remove('jitter')\n this.form.removeEventListener('animationend', onEnd)\n }\n this.form.addEventListener('animationend', onEnd)\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 // ── Element Inspector ──\n\n private static CATEGORY_PROPS: Record<string, string[]> = {\n text: ['font-size', 'font-weight', 'line-height', 'color', 'font-family'],\n button: ['font-size', 'color', 'background-color', 'border-radius', '@padding'],\n link: ['font-size', 'color', 'text-decoration', 'font-weight'],\n image: ['@dimensions', 'object-fit', 'border-radius'],\n input: ['@dimensions', 'font-size', '@padding', 'border-radius'],\n toggle: ['@state', '@dimensions', 'background-color', 'border-radius'],\n 'table-cell': ['text-align', 'vertical-align', '@padding', 'font-size', 'background-color'],\n layout: ['display', '@layout-direction', 'gap', '@padding', 'align-items'],\n generic: ['color', 'font-size', 'font-weight', 'background-color'],\n }\n\n private classifyElement(el: Element): string {\n // Order matters — more specific categories first\n if (el.matches('input[type=\"checkbox\"], input[type=\"radio\"], [role=\"switch\"], [role=\"checkbox\"], [role=\"radio\"]')) return 'toggle'\n if (el.matches('button, [role=\"button\"], input[type=\"submit\"], input[type=\"reset\"], input[type=\"button\"]')) return 'button'\n if (el.matches('a[href]')) return 'link'\n if (el.matches('img, picture, video, canvas') || el.closest('svg')) return 'image'\n if (el.matches('input, textarea, select, [contenteditable=\"true\"]')) return 'input'\n if (el.matches('td, th, [role=\"cell\"], [role=\"columnheader\"], [role=\"rowheader\"]')) return 'table-cell'\n if (el.matches('h1, h2, h3, h4, h5, h6, p, span, label, blockquote, li, code, pre, em, strong, small')) return 'text'\n if (el.matches('div, section, main, aside, article, nav, header, footer, ul, ol') && el.children.length > 0) {\n const d = window.getComputedStyle(el).display\n if (d.includes('flex') || d.includes('grid')) return 'layout'\n }\n return 'generic'\n }\n\n private populateInspect(element: Element | null): void {\n const inspect = this.form.querySelector('.tack-form-inspect') as HTMLElement\n if (!inspect) return\n\n if (!element) {\n inspect.style.display = 'none'\n return\n }\n\n const category = this.classifyElement(element)\n const label = this.getElementLabel(element, category)\n const props = this.getRelevantStyles(element, category)\n\n const labelEl = inspect.querySelector('.tack-form-inspect-label') as HTMLElement\n if (labelEl) labelEl.textContent = label\n\n const propsEl = inspect.querySelector('.tack-form-inspect-props') as HTMLElement\n if (propsEl) {\n propsEl.innerHTML = props\n .map(([k, v]) => `<span class=\"tack-inspect-prop\"><span class=\"tack-inspect-key\">${this.escapeHtml(k)}</span>: <span class=\"tack-inspect-val\">${this.escapeHtml(v)}</span>;</span>`)\n .join('\\n')\n }\n\n inspect.classList.remove('expanded')\n inspect.style.display = props.length > 0 ? '' : 'none'\n }\n\n private getElementLabel(el: Element, category: string): string {\n // ID takes priority across all categories\n if (el.id) return `#${el.id}`\n // Meaningful class name\n const meaningful = Array.from(el.classList).find(\n (c) => !c.startsWith('tack-') && c.length > 1 && c.length < 40\n )\n if (meaningful) return `.${meaningful}`\n\n // Category-specific labels\n const truncate = (s: string, n: number) => s.length > n ? s.slice(0, n) + '...' : s\n\n switch (category) {\n case 'button': {\n const text = (el.textContent || '').trim()\n if (text) return `Button \"${truncate(text, 24)}\"`\n return '<button>'\n }\n case 'link': {\n const href = el.getAttribute('href') || ''\n if (href) {\n try {\n const u = new URL(href, window.location.origin)\n return `Link \"${truncate(u.pathname, 28)}\"`\n } catch { return `Link \"${truncate(href, 28)}\"` }\n }\n const text = (el.textContent || '').trim()\n return text ? `Link \"${truncate(text, 24)}\"` : '<a>'\n }\n case 'image': {\n const alt = el.getAttribute('alt')\n if (alt) return `Image \"${truncate(alt, 28)}\"`\n const src = el.getAttribute('src')\n if (src) {\n const filename = src.split('/').pop()?.split('?')[0] || ''\n if (filename) return `Image \"${truncate(filename, 28)}\"`\n }\n const ariaLabel = el.getAttribute('aria-label')\n if (ariaLabel) return `Image \"${truncate(ariaLabel, 28)}\"`\n return el.matches('svg') || el.closest('svg') ? '<svg>' : '<img>'\n }\n case 'input': {\n const tag = el.tagName.toLowerCase()\n if (tag === 'textarea') {\n const name = el.getAttribute('name') || el.getAttribute('placeholder') || ''\n return name ? `Textarea \"${truncate(name, 24)}\"` : '<textarea>'\n }\n if (tag === 'select') {\n const name = el.getAttribute('name') || ''\n return name ? `Select \"${truncate(name, 24)}\"` : '<select>'\n }\n const type = el.getAttribute('type') || 'text'\n const name = el.getAttribute('name') || el.getAttribute('placeholder') || ''\n return name ? `Input ${type} \"${truncate(name, 20)}\"` : `<input ${type}>`\n }\n case 'toggle': {\n const checked = (el as HTMLInputElement).checked ?? el.getAttribute('aria-checked') === 'true'\n const type = el.matches('[role=\"switch\"]') ? 'Switch' :\n el.matches('input[type=\"radio\"], [role=\"radio\"]') ? 'Radio' : 'Checkbox'\n return `${type} (${checked ? 'on' : 'off'})`\n }\n case 'layout': {\n const d = window.getComputedStyle(el).display\n const tag = el.tagName.toLowerCase()\n return `<${tag}> ${d}`\n }\n default: {\n const tag = el.tagName.toLowerCase()\n const text = (el.textContent || '').trim()\n if (text.length > 0) return `<${tag}> \"${truncate(text, 24)}\"`\n return `<${tag}>`\n }\n }\n }\n\n private getRelevantStyles(el: Element, category: string): [string, string][] {\n const cs = window.getComputedStyle(el)\n const propNames = CommentForm.CATEGORY_PROPS[category] || CommentForm.CATEGORY_PROPS.generic\n const props: [string, string][] = []\n\n for (const name of propNames) {\n if (name.startsWith('@')) {\n const resolved = this.resolveCustomProp(name, el, cs)\n if (resolved) props.push(resolved)\n } else {\n const val = cs.getPropertyValue(name)\n if (val) props.push([name, val])\n }\n }\n\n return props.filter(([k, v]) => {\n if (k === 'background-color' && (v === 'rgba(0, 0, 0, 0)' || v === 'transparent')) return false\n if (k === 'text-decoration' && v.startsWith('none')) return false\n if (k === 'object-fit' && v === 'fill') return false\n if (k === 'text-align' && v === 'start') return false\n if (k === 'vertical-align' && v === 'middle') return false\n if (k === 'align-items' && v === 'normal') return false\n return true\n }).map(([k, v]) => {\n // Clean up font-family to first name only\n if (k === 'font-family') {\n const first = v.split(',')[0].trim().replace(/^[\"']|[\"']$/g, '')\n return [k, first]\n }\n return [k, v]\n })\n }\n\n private resolveCustomProp(name: string, el: Element, cs: CSSStyleDeclaration): [string, string] | null {\n switch (name) {\n case '@dimensions': {\n const rect = el.getBoundingClientRect()\n const w = Math.round(rect.width)\n const h = Math.round(rect.height)\n if (w === 0 && h === 0) return null\n return ['size', `${w} × ${h}px`]\n }\n case '@state': {\n const checked = (el as HTMLInputElement).checked ?? el.getAttribute('aria-checked') === 'true'\n return ['state', checked ? 'checked' : 'unchecked']\n }\n case '@padding': {\n const t = cs.getPropertyValue('padding-top')\n const r = cs.getPropertyValue('padding-right')\n const b = cs.getPropertyValue('padding-bottom')\n const l = cs.getPropertyValue('padding-left')\n if (!t) return null\n if (t === r && r === b && b === l) return ['padding', t]\n if (t === b && r === l) return ['padding', `${t} ${r}`]\n return ['padding', `${t} ${r} ${b} ${l}`]\n }\n case '@layout-direction': {\n const d = cs.display\n if (d.includes('flex')) {\n const dir = cs.getPropertyValue('flex-direction')\n return dir ? ['flex-direction', dir] : null\n }\n if (d.includes('grid')) {\n let cols = cs.getPropertyValue('grid-template-columns')\n if (cols && cols.length > 40) cols = cols.slice(0, 37) + '...'\n return cols ? ['grid-columns', cols] : null\n }\n return null\n }\n default:\n return null\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 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 return `<img\n src=\"${avatarUrl}\"\n alt=\"${getInitials(name)}\"\n referrerpolicy=\"no-referrer\"\n crossorigin=\"anonymous\"\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","export interface FabPosition {\n edge: 'left' | 'right'\n y: number // px from top of viewport\n}\n\nconst STORAGE_KEY_PREFIX = 'tack-fab-pos-'\n\nfunction storageKey(): string {\n return `${STORAGE_KEY_PREFIX}${location.origin}`\n}\n\nexport function loadFabPosition(): FabPosition | null {\n try {\n const raw = localStorage.getItem(storageKey())\n if (!raw) return null\n const parsed = JSON.parse(raw)\n if (\n parsed &&\n (parsed.edge === 'left' || parsed.edge === 'right') &&\n typeof parsed.y === 'number'\n ) {\n return parsed as FabPosition\n }\n return null\n } catch {\n return null\n }\n}\n\nexport function saveFabPosition(pos: FabPosition): void {\n try {\n localStorage.setItem(storageKey(), JSON.stringify(pos))\n } catch {\n // Storage full or unavailable — silently ignore\n }\n}\n\nexport function createDragOverlay(options: { zIndex: number; cursor: string }): HTMLDivElement {\n const overlay = document.createElement('div')\n overlay.style.cssText = `\n position: fixed;\n inset: 0;\n z-index: ${options.zIndex};\n cursor: ${options.cursor};\n background: transparent;\n touch-action: none;\n `\n document.body.appendChild(overlay)\n return overlay\n}\n\nexport function removeDragOverlay(overlay: HTMLDivElement | null): void {\n if (overlay && overlay.parentNode) {\n overlay.parentNode.removeChild(overlay)\n }\n}\n\nexport function clampFabY(y: number, fabHeight: number): number {\n const minY = 20\n const maxY = window.innerHeight - fabHeight - 20\n return Math.max(minY, Math.min(y, maxY))\n}\n\nexport function exceedsDeadZone(\n startX: number,\n startY: number,\n currentX: number,\n currentY: number,\n threshold: number = 5\n): boolean {\n const dx = currentX - startX\n const dy = currentY - startY\n return Math.sqrt(dx * dx + dy * dy) > threshold\n}\n\n// ── Panel Side Persistence ──\n\nexport type PanelSide = 'left' | 'right'\n\nconst PANEL_SIDE_KEY_PREFIX = 'tack-panel-side-'\n\nfunction panelSideKey(): string {\n return `${PANEL_SIDE_KEY_PREFIX}${location.origin}`\n}\n\nexport function loadPanelSide(): PanelSide {\n try {\n const raw = localStorage.getItem(panelSideKey())\n if (raw === 'left' || raw === 'right') return raw\n } catch {}\n return 'right'\n}\n\nexport function savePanelSide(side: PanelSide): void {\n try {\n localStorage.setItem(panelSideKey(), side)\n } catch {}\n}\n","import type { Comment } from '../types'\nimport type { WidgetUser } from '../auth'\nimport { getAvatarColor, getInitials, renderAvatar } from './avatars'\nimport {\n type PanelSide,\n loadPanelSide,\n savePanelSide,\n exceedsDeadZone,\n createDragOverlay,\n removeDragOverlay,\n} from './drag'\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 onRowHover?: (commentId: string) => void\n onRowHoverEnd?: (commentId: string) => void\n onSideChange?: (side: PanelSide) => 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: true, 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 currentUser: WidgetUser | null = null\n private readCommentIds: Set<string>\n private projectId: string = ''\n private side: PanelSide\n private dragState: 'idle' | 'pending' | 'dragging' = 'idle'\n private dragStartPointer: { x: number; y: number } = { x: 0, y: 0 }\n private dragStartRect: DOMRect | null = null\n private dragOverlay: HTMLDivElement | null = null\n private snapHint: HTMLDivElement | null = null\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.side = loadPanelSide()\n this.strip = this.createPanelStrip()\n this.panel = this.strip.querySelector('.tack-panel')!\n this.applySide()\n this.setupHeaderDrag()\n this.container.appendChild(this.strip)\n }\n\n getSide(): PanelSide {\n return this.side\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 <span class=\"tack-panel-title\">Comments</span>\n <button class=\"tack-panel-close\" aria-label=\"Close comments panel\">\n <svg width=\"20\" height=\"20\" 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-toolbar\">\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>\n <div class=\"tack-panel-content\"></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.filterDropdownOpen = !this.filterDropdownOpen\n this.renderFilterDropdown()\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 })\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 // More dropdown removed from panel UI\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 private applySide(): void {\n if (this.side === 'left') {\n this.strip.classList.add('tack-panel-left')\n } else {\n this.strip.classList.remove('tack-panel-left')\n }\n }\n\n private switchSide(newSide: PanelSide): void {\n if (newSide === this.side) return\n const wasOpen = this.isOpen()\n\n // Hide on current side instantly (no transition)\n this.strip.style.transition = 'none'\n this.strip.classList.remove('open')\n\n // Force reflow so the hide takes effect immediately\n void this.strip.offsetHeight\n\n // Switch side\n this.side = newSide\n savePanelSide(newSide)\n this.applySide()\n\n // Force reflow after side change\n void this.strip.offsetHeight\n\n // Restore transition and re-open on new side\n this.strip.style.transition = ''\n if (wasOpen) {\n // Small delay so the browser applies the new position first\n requestAnimationFrame(() => {\n this.strip.classList.add('tack-panel-snapping')\n this.strip.classList.add('open')\n setTimeout(() => {\n this.strip.classList.remove('tack-panel-snapping')\n }, 350)\n })\n }\n\n this.callbacks.onSideChange?.(newSide)\n }\n\n private setupHeaderDrag(): void {\n const header = this.strip.querySelector('.tack-panel-header') as HTMLElement\n if (!header) return\n\n header.addEventListener('pointerdown', (e: PointerEvent) => {\n // Ignore clicks on interactive children\n const target = e.target as HTMLElement\n if (target.closest('button') || target.closest('input')) return\n if (e.button !== 0) return\n\n e.preventDefault()\n this.dragState = 'pending'\n this.dragStartPointer = { x: e.clientX, y: e.clientY }\n this.dragStartRect = this.strip.getBoundingClientRect()\n\n // Register move/up on window so events are never lost when the\n // pointer leaves the Shadow DOM boundary or an overlay intercepts.\n const onMove = (me: PointerEvent) => {\n if (this.dragState === 'pending') {\n if (!exceedsDeadZone(\n this.dragStartPointer.x,\n this.dragStartPointer.y,\n me.clientX,\n me.clientY,\n 5\n )) {\n return\n }\n this.enterPanelDrag()\n }\n\n if (this.dragState === 'dragging') {\n this.updatePanelDrag(me)\n }\n }\n\n const onUp = () => {\n window.removeEventListener('pointermove', onMove)\n window.removeEventListener('pointerup', onUp)\n window.removeEventListener('pointercancel', onUp)\n\n if (this.dragState === 'dragging') {\n this.finalizePanelDrop()\n }\n\n this.dragState = 'idle'\n header.classList.remove('tack-dragging')\n }\n\n window.addEventListener('pointermove', onMove)\n window.addEventListener('pointerup', onUp)\n window.addEventListener('pointercancel', onUp)\n })\n }\n\n private enterPanelDrag(): void {\n this.dragState = 'dragging'\n\n const header = this.strip.querySelector('.tack-panel-header') as HTMLElement\n if (header) header.classList.add('tack-dragging')\n\n // Create drag overlay on document.body to capture events across the page\n this.dragOverlay = createDragOverlay({ zIndex: 10005, cursor: 'grabbing' })\n\n // Apply dragging visual state\n this.strip.classList.add('tack-panel-dragging')\n\n // Position the strip absolutely using left/top instead of right/transform\n if (this.dragStartRect) {\n this.strip.style.left = `${this.dragStartRect.left}px`\n this.strip.style.top = `${this.dragStartRect.top}px`\n this.strip.style.right = 'auto'\n this.strip.style.transform = 'none'\n this.strip.classList.remove('tack-panel-left')\n }\n\n // Create snap hint\n this.createSnapHint()\n }\n\n private updatePanelDrag(e: PointerEvent): void {\n if (!this.dragStartRect) return\n\n const dx = e.clientX - this.dragStartPointer.x\n const newX = this.dragStartRect.left + dx\n\n this.strip.style.left = `${newX}px`\n\n // Determine which edge the panel center is closer to\n const panelCenter = newX + this.dragStartRect.width / 2\n const vpMidpoint = window.innerWidth / 2\n const targetSide: PanelSide = panelCenter < vpMidpoint ? 'left' : 'right'\n\n this.updateSnapHint(targetSide)\n }\n\n private finalizePanelDrop(): void {\n // Remove drag overlay\n removeDragOverlay(this.dragOverlay)\n this.dragOverlay = null\n\n // Remove snap hint\n this.removeSnapHint()\n\n // Remove dragging visual state\n this.strip.classList.remove('tack-panel-dragging')\n\n // Determine target side based on current position\n const rect = this.strip.getBoundingClientRect()\n const panelCenter = rect.left + rect.width / 2\n const vpMidpoint = window.innerWidth / 2\n const targetSide: PanelSide = panelCenter < vpMidpoint ? 'left' : 'right'\n\n // Reset inline styles from drag\n this.strip.style.left = ''\n this.strip.style.top = ''\n this.strip.style.right = ''\n this.strip.style.transform = ''\n\n // Apply the new side (this handles the CSS class and persistence)\n if (targetSide !== this.side) {\n this.switchSide(targetSide)\n } else {\n // Same side — just reapply normal positioning\n this.applySide()\n // Re-add open class since we cleared transform\n if (this.isOpen()) {\n this.strip.classList.add('open')\n }\n }\n }\n\n private createSnapHint(): void {\n if (this.snapHint) return\n this.snapHint = document.createElement('div')\n // Inline styles since this element lives on document.body, outside shadow DOM\n this.snapHint.style.cssText = `\n position: fixed;\n top: 0;\n bottom: 0;\n width: 4px;\n background: rgba(37, 99, 235, 0.35);\n z-index: 10005;\n pointer-events: none;\n opacity: 0;\n transition: opacity 150ms ease;\n `\n document.body.appendChild(this.snapHint)\n }\n\n private updateSnapHint(side: PanelSide): void {\n if (!this.snapHint) return\n if (side === 'left') {\n this.snapHint.style.left = '0'\n this.snapHint.style.right = 'auto'\n } else {\n this.snapHint.style.right = '0'\n this.snapHint.style.left = 'auto'\n }\n this.snapHint.style.opacity = '1'\n }\n\n private removeSnapHint(): void {\n if (this.snapHint) {\n this.snapHint.remove()\n this.snapHint = null\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.renderFilterDropdown()\n }\n\n setFabOffset(fabEdge: 'left' | 'right'): void {\n const sameSide = fabEdge === this.side\n this.strip.classList.toggle('tack-panel-fab-offset', sameSide)\n this.strip.style.left = ''\n this.strip.style.right = ''\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 // Presence section removed from panel UI\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 // Row hover → cross-link to pin\n row.addEventListener('mouseenter', () => {\n this.callbacks.onRowHover?.(id)\n })\n row.addEventListener('mouseleave', () => {\n this.callbacks.onRowHoverEnd?.(id)\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 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 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M22 11.08V12a10 10 0 1 1-5.93-9.14\"/><polyline points=\"22 4 12 14.01 9 11.01\"/></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 <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 highlightRow(commentId: string): void {\n const row = this.panel.querySelector(`.tack-comment-row[data-id=\"${commentId}\"]`) as HTMLElement\n if (row) row.classList.add('pin-hover')\n }\n\n clearRowHighlight(commentId: string): void {\n const row = this.panel.querySelector(`.tack-comment-row[data-id=\"${commentId}\"]`) as HTMLElement\n if (row) row.classList.remove('pin-hover')\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, CommentAnchor } 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\ntype PinHoverCallback = (commentId: string) => 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 private loadingPin: HTMLElement | null = null\n private onHover: PinHoverCallback | null = null\n private onHoverEnd: PinHoverCallback | null = null\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 // Remove loading pin if present\n this.removeLoadingPin()\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 if (author.avatar_url) {\n return `<img src=\"${author.avatar_url}\" alt=\"${initials}\" referrerpolicy=\"no-referrer\" crossorigin=\"anonymous\" 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 return `<img src=\"${author.avatar_url}\" alt=\"${initials}\" referrerpolicy=\"no-referrer\" crossorigin=\"anonymous\" 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 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 const totalSlots = visibleAuthors.length + (overflow > 0 ? 1 : 0)\n // Each avatar is 28px, overlaps 8px. First is full, rest are 20px visible. Plus 6px padding (3px each side).\n const shellWidth = 28 + (totalSlots - 1) * 20 + 6\n\n // Collapsed: stacked avatars\n let avatarsHTML = `<div class=\"tack-pin-shell\" style=\"--pin-w:${shellWidth}px\"><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>'\n\n // Multi-author preview: avatar stack at top, then author + time, then text\n let previewAvatarsHTML = '<div class=\"tack-pin-preview-avatars\">'\n uniqueAuthors.slice(0, 4).forEach((author, i) => {\n previewAvatarsHTML += `<div class=\"tack-pin-avatar-stacked\" style=\"z-index:${i + 1}\">${this.renderStackedAvatar(author, 20)}</div>`\n })\n if (uniqueAuthors.length > 4) {\n previewAvatarsHTML += `<div class=\"tack-pin-avatar-stacked tack-pin-avatar-overflow\" style=\"z-index:5;width:24px;height:24px;font-size:8px\">+${uniqueAuthors.length - 4}</div>`\n }\n previewAvatarsHTML += '</div>'\n\n const multiPreviewHTML = `<div class=\"tack-pin-preview\">\n ${previewAvatarsHTML}\n <div class=\"tack-pin-preview-meta\" style=\"margin-bottom:4px\">\n <span class=\"tack-pin-preview-name\">${this.escapeHtml(previewAuthor.name)}</span>\n <span class=\"tack-pin-preview-time\">${timeAgo}</span>\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 avatarsHTML += `${multiPreviewHTML}</div>`\n pinHTML = avatarsHTML\n } else {\n // Single author: white circle shell with inset avatar\n const author = uniqueAuthors[0]\n const singlePreviewHTML = `<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 pinHTML = `<div class=\"tack-pin-shell\">${this.renderInsetAvatar(author, 32)}${singlePreviewHTML}</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 // Viewport-relative fallback: position based on where user clicked in viewport\n // This scales correctly with responsive layouts\n if (comment.anchor?.viewportRelativeX != null && comment.anchor?.viewportRelativeY != null) {\n const x = comment.anchor.viewportRelativeX * window.innerWidth + window.scrollX\n const y = comment.anchor.viewportRelativeY * window.innerHeight + window.scrollY\n pin.style.left = `${x - pinOffsetX}px`\n pin.style.top = `${y - 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) — fire cross-link at same moment\n const timer = window.setTimeout(() => {\n this.expandPin(comment.id, pin)\n this.onHover?.(comment.id)\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) — clear cross-link\n const timer = window.setTimeout(() => {\n this.collapsePin(comment.id, pin)\n this.onHoverEnd?.(comment.id)\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 setHoverCallbacks(onHover: PinHoverCallback, onHoverEnd: PinHoverCallback): void {\n this.onHover = onHover\n this.onHoverEnd = onHoverEnd\n }\n\n setHoverLinked(commentId: string | null): void {\n this.pins.forEach((pinState, id) => {\n pinState.element.classList.toggle('hover-linked', id === commentId)\n })\n }\n\n beacon(commentId: string): void {\n const pinState = this.pins.get(commentId)\n if (!pinState) return\n pinState.element.classList.add('beacon')\n setTimeout(() => {\n pinState.element.classList.remove('beacon')\n }, 700)\n }\n\n getPinElement(commentId: string): HTMLElement | null {\n return this.pins.get(commentId)?.element || null\n }\n\n showLoadingPin(anchor: CommentAnchor, user: { name: string; avatar_url: string | null }): void {\n this.removeLoadingPin()\n\n const pin = document.createElement('div')\n pin.className = 'tack-pin tack-pin-loading'\n\n const initials = getInitials(user.name)\n const gradient = getAvatarGradient(user.name)\n let avatarHTML: string\n if (user.avatar_url) {\n avatarHTML = `<img src=\"${user.avatar_url}\" alt=\"${initials}\" referrerpolicy=\"no-referrer\" crossorigin=\"anonymous\" class=\"tack-pin-avatar-img\" style=\"width:32px;height:32px;\" />`\n } else {\n avatarHTML = `<div class=\"tack-pin-avatar-fallback\" style=\"width:32px;height:32px;background:${gradient};font-size:12px;\">${initials}</div>`\n }\n\n pin.innerHTML = `<div class=\"tack-pin-shell\">${avatarHTML}<svg class=\"tack-pin-loading-ring\" viewBox=\"0 0 36 36\"><circle cx=\"18\" cy=\"18\" r=\"16\" /></svg></div>`\n\n // Position using anchor data\n const anchorResult = this.anchoring.resolve(anchor)\n const pinOffsetX = 7\n const pinOffsetY = 45\n\n if (anchorResult.element) {\n const position = this.anchoring.calculatePinPosition(anchorResult.element, anchor)\n pin.style.left = `${position.x - pinOffsetX}px`\n pin.style.top = `${position.y - pinOffsetY}px`\n } else if (anchor.relativeX !== undefined && anchor.relativeY !== undefined) {\n const x = anchor.relativeX * document.documentElement.scrollWidth\n const y = anchor.relativeY * document.documentElement.scrollHeight\n pin.style.left = `${x - pinOffsetX}px`\n pin.style.top = `${y - pinOffsetY}px`\n }\n\n this.loadingPin = pin\n this.pinsContainer.appendChild(pin)\n }\n\n resolveLoadingPin(): void {\n if (!this.loadingPin) return\n const pin = this.loadingPin\n pin.classList.remove('tack-pin-loading')\n pin.classList.add('tack-pin-settle')\n // Remove the SVG ring\n const ring = pin.querySelector('.tack-pin-loading-ring')\n ring?.remove()\n // Restore avatar opacity/blur\n const avatar = pin.querySelector('.tack-pin-avatar-img, .tack-pin-avatar-fallback') as HTMLElement\n if (avatar) {\n avatar.style.opacity = '1'\n avatar.style.filter = 'none'\n }\n setTimeout(() => {\n pin.classList.remove('tack-pin-settle')\n }, 350)\n }\n\n failLoadingPin(onRetry: () => void): void {\n if (!this.loadingPin) return\n const pin = this.loadingPin\n pin.classList.remove('tack-pin-loading')\n pin.classList.add('tack-pin-failed')\n\n // Add retry tooltip\n const tooltip = document.createElement('div')\n tooltip.className = 'tack-pin-failed-tooltip'\n tooltip.textContent = 'Failed to save. Click to retry.'\n pin.appendChild(tooltip)\n\n pin.addEventListener('click', (e) => {\n e.stopPropagation()\n this.removeLoadingPin()\n onRetry()\n })\n }\n\n removeLoadingPin(): void {\n if (this.loadingPin) {\n this.loadingPin.remove()\n this.loadingPin = null\n }\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 onReaction: (commentId: string, emoji: string, add: boolean) => 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 pickerOpen = 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 this.pickerOpen = 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 // Use the pin's stable absolute position (document coords) rather than\n // getBoundingClientRect() which fluctuates during shell expand/collapse animations.\n const pinDocX = parseFloat(pinElement.style.left) || 0\n const pinDocY = parseFloat(pinElement.style.top) || 0\n // Pin shell collapsed size: ~38px for single, ~74px for multi-author\n const pinWidth = 44\n const pinHeight = 44\n\n // Convert document coords to viewport coords for position: fixed card\n const pinViewX = pinDocX - window.scrollX\n const pinViewY = pinDocY - window.scrollY\n\n const cardWidth = 320\n const gap = 8\n\n let left = pinViewX + pinWidth + gap\n if (left + cardWidth > window.innerWidth - 16) {\n left = pinViewX - cardWidth - gap\n }\n left = Math.max(16, left)\n\n let top = pinViewY\n const estimatedHeight = 300\n if (top + estimatedHeight > window.innerHeight - 16) {\n top = window.innerHeight - 16 - estimatedHeight\n }\n top = Math.max(16, 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 // Reactions (main comment only)\n if (isMain) {\n html += this.renderReactions(comment)\n }\n\n html += `</div>`\n return html\n }\n\n private renderReactions(comment: Comment): string {\n const reactions = comment.reactions || []\n const userId = this.currentUser?.id\n\n // Group by emoji: { emoji: { count, userReacted, users[] } }\n const grouped = new Map<string, { count: number; userReacted: boolean; users: string[] }>()\n for (const r of reactions) {\n const existing = grouped.get(r.emoji) || { count: 0, userReacted: false, users: [] }\n existing.count++\n existing.users.push(r.user_name)\n if (r.user_id === userId) existing.userReacted = true\n grouped.set(r.emoji, existing)\n }\n\n let html = '<div class=\"tack-reaction-bar\">'\n\n // Existing reaction pills\n for (const [emoji, data] of grouped) {\n const mine = data.userReacted ? ' mine' : ''\n const title = data.users.join(', ')\n html += `<button class=\"tack-reaction-pill${mine}\" data-emoji=\"${emoji}\" title=\"${this.escapeHtml(title)}\">${emoji} ${data.count}</button>`\n }\n\n // Add reaction button\n html += `<button class=\"tack-reaction-add\" title=\"Add reaction\">+</button>`\n\n // Picker (if open)\n if (this.pickerOpen) {\n const emojis = ['👍', '👎', '❤️', '🎉', '👀', '🚀']\n html += '<div class=\"tack-reaction-picker\">'\n for (const e of emojis) {\n const alreadyReacted = grouped.get(e)?.userReacted ? ' mine' : ''\n html += `<button class=\"tack-reaction-picker-item${alreadyReacted}\" data-picker-emoji=\"${e}\">${e}</button>`\n }\n html += '</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 // Reaction pills — toggle existing reaction\n this.card.querySelectorAll('.tack-reaction-pill').forEach((btn) => {\n btn.addEventListener('click', (e) => {\n e.stopPropagation()\n if (!this.currentComment || !this.currentUser) return\n const emoji = (btn as HTMLElement).dataset.emoji!\n const isMine = btn.classList.contains('mine')\n this.callbacks.onReaction(this.currentComment.id, emoji, !isMine)\n })\n })\n\n // Add reaction button — toggle picker\n this.card.querySelector('.tack-reaction-add')?.addEventListener('click', (e) => {\n e.stopPropagation()\n this.pickerOpen = !this.pickerOpen\n this.renderCard(this.currentComment!)\n this.attachListeners()\n })\n\n // Picker items\n this.card.querySelectorAll('.tack-reaction-picker-item').forEach((btn) => {\n btn.addEventListener('click', (e) => {\n e.stopPropagation()\n if (!this.currentComment || !this.currentUser) return\n const emoji = (btn as HTMLElement).dataset.pickerEmoji!\n const isMine = btn.classList.contains('mine')\n this.pickerOpen = false\n this.callbacks.onReaction(this.currentComment.id, emoji, !isMine)\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 {\n FabPosition,\n loadFabPosition,\n saveFabPosition,\n createDragOverlay,\n removeDragOverlay,\n clampFabY,\n exceedsDeadZone,\n} from './drag'\n\nexport interface FabCallbacks {\n onToggle: () => void\n onPanelToggle: () => void\n onExit: () => void\n}\n\ntype DragState = 'idle' | 'pending' | 'dragging'\n\nconst FAB_SIZE = 44\nconst EDGE_MARGIN = 20\n\nexport class WidgetFAB {\n private el: HTMLDivElement\n private mainBtn: HTMLButtonElement\n private commentIcon: HTMLSpanElement\n private closeIcon: HTMLSpanElement\n private panelToggleBtn: HTMLButtonElement\n private badge: HTMLSpanElement\n private expanded = false\n private callbacks: FabCallbacks\n\n // Drag state\n private dragState: DragState = 'idle'\n private position: FabPosition\n private startPointerX = 0\n private startPointerY = 0\n private startFabX = 0\n private startFabY = 0\n private dragOverlay: HTMLDivElement | null = null\n private snapHint: HTMLDivElement | null = null\n\n // Prevents native click from double-firing after handleFabClick\n private handledByPointer = false\n\n // Bound handlers for cleanup\n private boundOnPointerMove: ((e: PointerEvent) => void) | null = null\n private boundOnPointerUp: ((e: PointerEvent) => void) | null = null\n private boundOnResize: (() => void) | null = null\n\n constructor(container: HTMLElement, callbacks: FabCallbacks) {\n this.callbacks = callbacks\n\n // Load persisted position or use default (bottom-right)\n const saved = loadFabPosition()\n this.position = saved || {\n edge: 'right',\n y: window.innerHeight - FAB_SIZE - EDGE_MARGIN,\n }\n\n this.el = document.createElement('div')\n this.el.className = 'tack-fab tack-fab--entering'\n\n // Apply edge class if left\n if (this.position.edge === 'left') {\n this.el.classList.add('tack-fab--left')\n }\n\n // Panel toggle (left button, hidden when collapsed)\n this.panelToggleBtn = document.createElement('button')\n this.panelToggleBtn.className = 'tack-fab-btn tack-fab-panel-toggle'\n this.panelToggleBtn.setAttribute('aria-label', 'Toggle panel')\n this.panelToggleBtn.innerHTML = `<svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\"></rect><line x1=\"15\" y1=\"3\" x2=\"15\" y2=\"21\"></line></svg>`\n this.panelToggleBtn.addEventListener('click', (e) => {\n e.stopPropagation()\n // Skip if already handled by the pointerup → handleFabClick path\n if (this.handledByPointer) return\n // Block during active drag\n if (this.dragState === 'dragging') return\n this.callbacks.onPanelToggle()\n })\n\n // Main button (rightmost -- comment icon / X icon)\n this.mainBtn = document.createElement('button')\n this.mainBtn.className = 'tack-fab-main'\n this.mainBtn.setAttribute('aria-label', 'Toggle comments')\n\n // Comment icon (visible when collapsed)\n this.commentIcon = document.createElement('span')\n this.commentIcon.className = 'tack-fab-icon tack-fab-icon--comment'\n this.commentIcon.innerHTML = `<svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><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></svg>`\n\n // Close icon (visible when expanded)\n this.closeIcon = document.createElement('span')\n this.closeIcon.className = 'tack-fab-icon tack-fab-icon--close'\n this.closeIcon.innerHTML = `<svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line></svg>`\n\n this.mainBtn.appendChild(this.commentIcon)\n this.mainBtn.appendChild(this.closeIcon)\n\n // Badge\n this.badge = document.createElement('span')\n this.badge.className = 'tack-fab-badge'\n\n // Assemble: [panel toggle] [main button] [badge] -- left to right\n this.el.appendChild(this.panelToggleBtn)\n this.el.appendChild(this.mainBtn)\n this.el.appendChild(this.badge)\n\n container.appendChild(this.el)\n\n // Apply initial position\n this.applyPosition()\n\n // Remove entrance animation class after it completes\n setTimeout(() => {\n this.el.classList.remove('tack-fab--entering')\n }, 500)\n\n // Set up drag handling on the entire FAB container\n this.el.addEventListener('pointerdown', this.onPointerDown.bind(this))\n\n // Listen for viewport resize to re-clamp Y\n this.boundOnResize = this.onResize.bind(this)\n window.addEventListener('resize', this.boundOnResize)\n }\n\n // ── Position management ──\n\n private applyPosition(): void {\n const y = clampFabY(this.position.y, FAB_SIZE)\n this.position.y = y\n\n // Clear both left/right, then set the active edge\n this.el.style.left = ''\n this.el.style.right = ''\n this.el.style.bottom = ''\n\n if (this.position.edge === 'left') {\n this.el.style.left = `${EDGE_MARGIN}px`\n } else {\n this.el.style.right = `${EDGE_MARGIN}px`\n }\n this.el.style.top = `${y}px`\n\n // Toggle edge class\n this.el.classList.toggle('tack-fab--left', this.position.edge === 'left')\n }\n\n private onResize(): void {\n if (this.dragState !== 'idle') return\n this.position.y = clampFabY(this.position.y, FAB_SIZE)\n this.applyPosition()\n saveFabPosition(this.position)\n }\n\n // ── Drag handling ──\n\n private onPointerDown(e: PointerEvent): void {\n // Only primary button\n if (e.button !== 0) return\n // Don't start drag if the panel toggle button was the direct target\n // (let its click handler work normally)\n\n this.dragState = 'pending'\n this.startPointerX = e.clientX\n this.startPointerY = e.clientY\n\n // Record the current FAB position on screen\n const rect = this.el.getBoundingClientRect()\n this.startFabX = rect.left\n this.startFabY = rect.top\n\n this.boundOnPointerMove = this.onPointerMove.bind(this)\n this.boundOnPointerUp = this.onPointerUp.bind(this)\n window.addEventListener('pointermove', this.boundOnPointerMove)\n window.addEventListener('pointerup', this.boundOnPointerUp)\n }\n\n private onPointerMove(e: PointerEvent): void {\n if (this.dragState === 'pending') {\n if (!exceedsDeadZone(this.startPointerX, this.startPointerY, e.clientX, e.clientY)) {\n return\n }\n // Exceeded dead zone -- enter dragging state\n this.enterDragState()\n }\n\n if (this.dragState !== 'dragging') return\n\n // Move FAB freely following the pointer\n const dx = e.clientX - this.startPointerX\n const dy = e.clientY - this.startPointerY\n const newX = this.startFabX + dx\n const newY = this.startFabY + dy\n\n // Position using left/top for free movement\n this.el.style.left = `${newX}px`\n this.el.style.right = 'auto'\n this.el.style.top = `${newY}px`\n\n // Update snap hint\n this.updateSnapHint(e.clientX)\n }\n\n private onPointerUp(e: PointerEvent): void {\n const wasDragging = this.dragState === 'dragging'\n\n if (this.dragState === 'pending') {\n // Was within dead zone -- treat as click\n this.cleanupDragListeners()\n this.dragState = 'idle'\n // Let the original click event bubble naturally.\n // But since we used pointerdown, we need to manually fire\n // the click logic for the main button.\n this.handleFabClick(e)\n return\n }\n\n if (wasDragging) {\n this.exitDragState(e)\n }\n\n this.cleanupDragListeners()\n }\n\n private enterDragState(): void {\n this.dragState = 'dragging'\n\n // If expanded, collapse first\n if (this.expanded) {\n this.collapse()\n }\n\n // Create full-viewport overlay on document.body\n this.dragOverlay = createDragOverlay({ zIndex: 10004, cursor: 'grabbing' })\n\n // Add dragging class\n this.el.classList.add('tack-fab-dragging')\n\n // Switch to left/top positioning for free movement\n const rect = this.el.getBoundingClientRect()\n this.el.style.left = `${rect.left}px`\n this.el.style.right = 'auto'\n this.el.style.top = `${rect.top}px`\n\n // Prevent text selection\n document.body.style.userSelect = 'none'\n }\n\n private exitDragState(e: PointerEvent): void {\n this.dragState = 'idle'\n\n // Remove drag overlay\n removeDragOverlay(this.dragOverlay)\n this.dragOverlay = null\n\n // Remove snap hint\n this.removeSnapHint()\n\n // Remove dragging class\n this.el.classList.remove('tack-fab-dragging')\n\n // Determine which edge to snap to\n const rect = this.el.getBoundingClientRect()\n const fabCenterX = rect.left + rect.width / 2\n const edge: 'left' | 'right' = fabCenterX < window.innerWidth / 2 ? 'left' : 'right'\n\n // Clamp Y position\n const clampedY = clampFabY(rect.top, FAB_SIZE)\n\n // Update position\n this.position = { edge, y: clampedY }\n saveFabPosition(this.position)\n\n // Animate snap\n this.el.classList.add('tack-fab-snapping')\n this.applyPosition()\n\n // After snap animation, do the settle\n const onSnapEnd = () => {\n this.el.classList.remove('tack-fab-snapping')\n this.el.removeEventListener('transitionend', onSnapEnd)\n\n // Settle animation\n this.el.classList.add('tack-fab-settling')\n setTimeout(() => {\n this.el.classList.remove('tack-fab-settling')\n }, 350)\n }\n this.el.addEventListener('transitionend', onSnapEnd)\n\n // Fallback in case transitionend doesn't fire\n setTimeout(() => {\n this.el.classList.remove('tack-fab-snapping')\n this.el.classList.remove('tack-fab-settling')\n }, 800)\n\n // Restore text selection\n document.body.style.userSelect = ''\n }\n\n private handleFabClick(e: PointerEvent): void {\n // Use composedPath() to see through Shadow DOM boundaries,\n // since window-level pointerup retargets e.target to the shadow host.\n const path = e.composedPath()\n const clickedPanelToggle = path.includes(this.panelToggleBtn)\n\n // Prevent the subsequent native click event from double-firing.\n // Use setTimeout instead of requestAnimationFrame for more reliable timing.\n this.handledByPointer = true\n setTimeout(() => { this.handledByPointer = false }, 100)\n\n if (clickedPanelToggle) {\n this.callbacks.onPanelToggle()\n return\n }\n\n // Otherwise treat as main button click\n if (this.expanded) {\n this.callbacks.onExit()\n } else {\n this.callbacks.onToggle()\n }\n }\n\n private cleanupDragListeners(): void {\n if (this.boundOnPointerMove) {\n window.removeEventListener('pointermove', this.boundOnPointerMove)\n this.boundOnPointerMove = null\n }\n if (this.boundOnPointerUp) {\n window.removeEventListener('pointerup', this.boundOnPointerUp)\n this.boundOnPointerUp = null\n }\n }\n\n // ── Snap hint ──\n\n private updateSnapHint(pointerX: number): void {\n const targetEdge: 'left' | 'right' = pointerX < window.innerWidth / 2 ? 'left' : 'right'\n\n if (!this.snapHint) {\n this.snapHint = document.createElement('div')\n this.snapHint.style.cssText = `\n position: fixed;\n top: 0;\n width: 3px;\n height: 100vh;\n background: rgba(24, 24, 27, 0.15);\n z-index: 10003;\n pointer-events: none;\n transition: left 150ms ease, right 150ms ease, opacity 150ms ease;\n `\n document.body.appendChild(this.snapHint)\n }\n\n if (targetEdge === 'left') {\n this.snapHint.style.left = '0px'\n this.snapHint.style.right = 'auto'\n } else {\n this.snapHint.style.left = 'auto'\n this.snapHint.style.right = '0px'\n }\n this.snapHint.style.opacity = '1'\n }\n\n private removeSnapHint(): void {\n if (this.snapHint) {\n this.snapHint.style.opacity = '0'\n const hint = this.snapHint\n this.snapHint = null\n setTimeout(() => {\n if (hint.parentNode) {\n hint.parentNode.removeChild(hint)\n }\n }, 150)\n }\n }\n\n // ── Public API (unchanged) ──\n\n expand(): void {\n if (this.expanded) return\n this.expanded = true\n\n this.el.classList.add('tack-fab--expanded')\n this.badge.classList.remove('visible')\n }\n\n collapse(): void {\n if (!this.expanded) return\n this.expanded = false\n\n this.el.classList.remove('tack-fab--expanded')\n // Show badge again after collapse if there's a count\n setTimeout(() => {\n if (!this.expanded && parseInt(this.badge.textContent || '0', 10) > 0) {\n this.badge.classList.add('visible')\n }\n }, 200)\n }\n\n isExpanded(): boolean {\n return this.expanded\n }\n\n getEdge(): 'left' | 'right' {\n return this.position.edge\n }\n\n setPanelToggleActive(active: boolean): void {\n this.panelToggleBtn.classList.toggle('tack-fab-btn--active', active)\n }\n\n setBadgeCount(n: number): void {\n this.badge.textContent = n > 99 ? '99+' : String(n)\n if (!this.expanded && n > 0) {\n this.badge.classList.add('visible')\n } else {\n this.badge.classList.remove('visible')\n }\n }\n\n destroy(): void {\n // Clean up drag state\n this.cleanupDragListeners()\n removeDragOverlay(this.dragOverlay)\n this.removeSnapHint()\n document.body.style.userSelect = ''\n\n if (this.boundOnResize) {\n window.removeEventListener('resize', this.boundOnResize)\n this.boundOnResize = null\n }\n\n this.el.remove()\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 { WidgetFAB } from './ui/fab'\n\ntype WidgetState = 'idle' | 'active' | 'active-panel'\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 fab: WidgetFAB | null = null\n private state: WidgetState = 'idle'\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(\n this.onElementSelected.bind(this),\n () => this.transitionTo('idle')\n )\n this.form = new CommentForm(wrapper, this.onCommentSubmit.bind(this))\n this.form.setUser(this.auth.getUser())\n this.form.setOnHide(() => {\n if (this.state === 'active') {\n // Delay re-enable so the click that dismissed the form\n // fully propagates before the selector starts listening.\n // The selector also has a 150ms cooldown after enable() to\n // guard against late-arriving click events from the same gesture.\n setTimeout(() => {\n if (this.state === 'active') {\n this.selector?.enable()\n }\n }, 100)\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.transitionTo('active'),\n onAddComment: () => this.transitionTo('active'),\n onShare: this.onShare.bind(this),\n onRowHover: (commentId: string) => {\n this.pins?.setHoverLinked(commentId)\n },\n onRowHoverEnd: () => {\n this.pins?.setHoverLinked(null)\n },\n onSideChange: () => {\n // Re-apply FAB offset when the panel switches sides\n if (this.state === 'active-panel') {\n if (this.fab) {\n this.panel?.setFabOffset(this.fab.getEdge())\n }\n this.setPanelOpen(true)\n }\n },\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 onReaction: this.onReaction.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 this.pins.setHoverCallbacks(\n (commentId: string) => {\n this.panel?.highlightRow(commentId)\n },\n (commentId: string) => {\n this.panel?.clearRowHighlight(commentId)\n }\n )\n\n // Add FAB toolbar\n this.fab = new WidgetFAB(wrapper, {\n onToggle: () => {\n if (this.state === 'idle') {\n this.transitionTo('active')\n }\n },\n onPanelToggle: () => {\n if (this.state === 'active') {\n this.transitionTo('active-panel')\n } else if (this.state === 'active-panel') {\n this.transitionTo('active')\n }\n },\n onExit: () => {\n this.transitionTo('idle')\n },\n })\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 transitionTo(newState: WidgetState): void {\n const prev = this.state\n\n if (newState === 'idle') {\n // Guard unsaved form content\n if (this.form?.isFormVisible() && this.form.hasContent()) {\n this.form.jitter()\n return\n }\n this.selector?.disable()\n this.form?.hide()\n this.pins?.hide()\n if (prev === 'active-panel') {\n this.panel?.hide()\n this.setPanelOpen(false)\n }\n this.fab?.collapse()\n this.container?.classList.remove('tack-active')\n this.fab?.setPanelToggleActive(false)\n\n this.updateFabBadge()\n } else if (newState === 'active') {\n this.selector?.enable()\n this.pins?.show()\n if (prev === 'active-panel') {\n this.panel?.hide()\n this.setPanelOpen(false)\n }\n if (prev === 'idle') {\n this.fab?.expand()\n }\n this.container?.classList.add('tack-active')\n\n this.fab?.setPanelToggleActive(false)\n } else if (newState === 'active-panel') {\n this.selector?.disable()\n this.pins?.show()\n // Apply FAB-aware offset before showing panel\n if (this.fab) {\n this.panel?.setFabOffset(this.fab.getEdge())\n }\n this.panel?.show()\n this.setPanelOpen(true)\n if (prev === 'idle') {\n this.fab?.expand()\n }\n this.container?.classList.add('tack-active')\n\n this.fab?.setPanelToggleActive(true)\n }\n\n this.state = newState\n }\n\n private updateFabBadge(): void {\n const unresolvedCount = this.comments.filter(c => !c.resolved && !c.parent_id).length\n this.fab?.setBadgeCount(unresolvedCount)\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 // Panel overlays the page — no page-push styles needed\n }\n\n private setPanelOpen(_open: boolean): void {\n // Panel overlays the page — no body class manipulation needed\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 const key = e.key.toLowerCase()\n // Only handle our shortcuts\n if (key !== 'c' && key !== 'n' && key !== 'p') 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 if (key === 'c') {\n e.preventDefault()\n if (this.state === 'idle') {\n this.transitionTo('active')\n } else if (this.state === 'active-panel') {\n this.transitionTo('active')\n } else {\n this.transitionTo('idle')\n }\n return\n }\n\n // N/P only work when panel is open\n if (this.state !== 'active-panel') 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 this.pins?.render(this.comments)\n this.panel?.render(this.comments)\n this.updateFabBadge()\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 pendingRetryData: {\n content: string\n authorName: string\n authorEmail?: string\n anchor: CommentAnchor\n screenshot?: string\n } | null = null\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 // Show loading pin after 150ms delay (skip if API responds fast)\n let showedLoading = false\n const loadingTimer = window.setTimeout(() => {\n const user = this.auth.getUser()\n this.pins?.showLoadingPin(data.anchor, {\n name: user?.name || data.authorName,\n avatar_url: user?.avatar_url || null,\n })\n showedLoading = true\n }, 150)\n\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 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\n clearTimeout(loadingTimer)\n if (showedLoading) {\n this.pins?.resolveLoadingPin()\n }\n\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 this.pendingRetryData = null\n this.updateFabBadge()\n if (this.state === 'active') {\n this.selector?.enable()\n }\n } catch (error) {\n clearTimeout(loadingTimer)\n console.error('[Tack] Failed to submit comment:', error)\n\n if (showedLoading) {\n this.pendingRetryData = data\n this.pins?.failLoadingPin(() => this.retryFailedComment())\n } else {\n this.showToast('Failed to add comment')\n }\n }\n }\n\n private retryFailedComment(): void {\n if (!this.pendingRetryData) return\n const data = this.pendingRetryData\n this.pendingRetryData = null\n this.onCommentSubmit(data)\n }\n\n private onCommentClick(comment: Comment): void {\n // Scroll page to the pin location\n this.scrollToComment(comment)\n\n // After scroll settles, open the card at the pin and beacon\n setTimeout(() => {\n const pinEl = this.pins?.getPinElement(comment.id)\n if (pinEl) {\n this.card?.show(comment, pinEl)\n this.pins?.setActive(comment.id)\n this.pins?.beacon(comment.id)\n }\n }, 300)\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 this.updateFabBadge()\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.updateFabBadge()\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 async onReaction(commentId: string, emoji: string, add: boolean): Promise<void> {\n if (!this.auth.isAuthenticated()) return\n\n const comment = this.comments.find(c => c.id === commentId)\n if (!comment) return\n\n const user = this.auth.getUser()!\n comment.reactions = comment.reactions || []\n\n // Optimistic update\n if (add) {\n comment.reactions.push({ emoji, user_id: user.id, user_name: user.name })\n } else {\n comment.reactions = comment.reactions.filter(\n r => !(r.emoji === emoji && r.user_id === user.id)\n )\n }\n this.card?.updateComment(comment)\n\n try {\n if (add) {\n await this.api.addReaction(commentId, emoji)\n } else {\n await this.api.removeReaction(commentId, emoji)\n }\n } catch (error) {\n console.error('[Tack] Failed to update reaction:', error)\n // Reload to restore correct state\n await this.loadComments()\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.transitionTo('active-panel')\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.transitionTo('active-panel')\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 this.updateFabBadge()\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 this.fab?.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 // No page-push styles or body classes to clean up (panel overlays)\n\n this.container?.remove()\n }\n}\n"],"mappings":"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AAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;A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","names":["index_exports","__export","Tack","TackWidget","__toCommonJS","TackAPI","baseUrl","projectId","token","cb","headers","res","data","id","commentId","emoji","url","WidgetAuth","apiUrl","token","res","user","resolve","reject","baseUrl","popup","expectedOrigin","handleMessage","event","pollTimer","cb","createStyles","ElementSelector","callback","onEscape","target","rect","pad","radius","relativeX","relativeY","viewportRelativeX","viewportRelativeY","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","inspect","textarea","sendBtn","ke","target","captureElementWithContext","rect","viewportX","viewportY","fw","formWidth","formHeight","left","top","onEnd","maxHeight","content","authorName","submitData","error","el","d","element","category","label","props","labelEl","propsEl","k","v","meaningful","c","truncate","s","n","text","href","u","alt","src","filename","ariaLabel","tag","name","type","checked","cs","propNames","resolved","val","first","w","h","t","r","b","l","dir","cols","div","CommentForm","AVATAR_GRADIENTS","AVATAR_COLORS","to","hashName","name","hash","i","getAvatarColor","getAvatarGradient","from","getInitials","parts","renderAvatar","avatarUrl","size","extraClass","fontSize","cls","STORAGE_KEY_PREFIX","storageKey","loadFabPosition","raw","parsed","saveFabPosition","pos","createDragOverlay","options","overlay","removeDragOverlay","clampFabY","y","fabHeight","maxY","exceedsDeadZone","startX","startY","currentX","currentY","threshold","dx","dy","PANEL_SIDE_KEY_PREFIX","panelSideKey","loadPanelSide","savePanelSide","side","STORAGE_KEY","loadFilters","stored","parsed","saveFilters","filters","CommentPanel","container","callbacks","loadPanelSide","user","projectId","key","commentId","filtered","changed","c","strip","searchInput","searchClear","val","e","dropdown","f","check","empty","item","action","badge","btn","count","newSide","wasOpen","savePanelSide","header","target","onMove","me","exceedsDeadZone","onUp","createDragOverlay","dx","newX","panelCenter","vpMidpoint","targetSide","removeDragOverlay","rect","side","fabEdge","sameSide","comments","_users","content","activeFilterCount","comment","row","id","dot","resolveBtn","moreBtn","parts","s","pagePath","path","isHighlighted","timeAgo","currentPath","isCurrentPage","pageName","unread","renderAvatar","result","userId","userName","r","q","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","anchorResult","targetElement","pin","isActive","isHighlighted","uniqueAuthors","isMultiAuthor","previewAuthor","timeAgo","previewText","replyCount","pinHTML","visibleAuthors","overflow","avatarsHTML","i","previewAvatarsHTML","multiPreviewHTML","singlePreviewHTML","e","pinOffsetX","pinOffsetY","position","rect","x","y","date","seconds","text","div","leaveTimer","timer","hoverTimer","prev","pinRect","id","onHover","onHoverEnd","anchor","user","avatarHTML","avatar","onRetry","tooltip","CommentCard","container","callbacks","e","user","comment","pinElement","pinDocX","pinDocY","pinWidth","pinHeight","pinViewX","pinViewY","cardWidth","gap","left","top","estimatedHeight","replies","currentAuthor","html","reply","index","renderAvatar","isMain","timeAgo","canEdit","reactions","userId","grouped","existing","emoji","data","mine","title","emojis","alreadyReacted","btn","action","textarea","ke","isMine","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","FAB_SIZE","EDGE_MARGIN","WidgetFAB","container","callbacks","saved","loadFabPosition","e","y","clampFabY","saveFabPosition","rect","exceedsDeadZone","dx","dy","newX","newY","wasDragging","createDragOverlay","removeDragOverlay","edge","clampedY","onSnapEnd","clickedPanelToggle","pointerX","targetEdge","hint","active","n","TackWidget","config","TackAPI","WidgetAuth","styles","createStyles","wrapper","user","ElementSelector","CommentForm","CommentPanel","commentId","CommentCard","CommentPins","WidgetFAB","PresenceManager","users","error","args","newPath","newState","prev","unresolvedCount","c","url","_open","key","active","shadowActive","direction","ids","comment","data","target","showedLoading","loadingTimer","createData","commentWithScreenshot","pinEl","pinElement","resolved","parentId","content","reply","parent","emoji","add","x","y","params","RealtimeSubscription","event","existing","message","toast","Tack","config","widget","TackWidget"]}