(function() { 'use strict'; // Get the organization ID from the script tag const scriptTag = document.currentScript || document.querySelector('script[data-organization-id]'); const organizationId = scriptTag ? scriptTag.getAttribute('data-organization-id') : null; if (!organizationId) { console.error('Anablock Widget: data-organization-id attribute is required'); return; } // Development-only logging const isDevelopment = window.location.hostname === 'localhost' || window.location.hostname.includes('vercel.app'); if (isDevelopment) { console.log('Anablock Widget: Initializing with org ID:', organizationId); } // Widget configuration const config = { organizationId: organizationId, widgetUrl: 'https://widget.anablock.com', position: 'bottom-right', zIndex: 9999 }; // Advanced Analytics System const analytics = { sessionId: 'ws_' + Date.now() + '_' + Math.random().toString(36).slice(2, 11), sessionStartTime: Date.now(), parentDomain: window.location.hostname, track: function(eventName, properties = {}) { const eventData = { type: 'widget:analytics', event: eventName, properties: { ...properties, organizationId: config.organizationId, sessionId: this.sessionId, parentDomain: this.parentDomain, timestamp: Date.now(), sessionDuration: Date.now() - this.sessionStartTime, viewport: { width: window.innerWidth, height: window.innerHeight }, userAgent: navigator.userAgent, referrer: document.referrer }, source: 'anablock-widget', version: '1.0' }; // Send analytics to multiple destinations this.sendToParent(eventData); this.sendToWidget(eventData); // Console logging for debugging (development only) if (window.location.hostname === 'localhost' || window.location.hostname.includes('vercel.app')) { console.log('Anablock Widget Analytics:', eventData); } }, sendToParent: function(data) { try { // Send to parent window for their analytics if (window.parent && window.parent !== window) { window.parent.postMessage(data, '*'); } } catch (error) { console.warn('Analytics parent communication failed:', error); } }, sendToWidget: function(data) { try { // Send to widget iframe for Vercel Analytics const iframe = document.getElementById('anablock-widget-iframe'); if (iframe && iframe.contentWindow) { iframe.contentWindow.postMessage(data, config.widgetUrl); } } catch (error) { console.warn('Analytics widget communication failed:', error); } } }; // Track widget script loaded analytics.track('Widget Script Loaded', { loadTime: Date.now() - analytics.sessionStartTime }); // Create widget container (modal overlay) function createWidgetContainer() { const container = document.createElement('div'); container.id = 'anablock-widget-container'; container.setAttribute('role', 'dialog'); container.setAttribute('aria-modal', 'true'); container.setAttribute('aria-label', 'AI Assistant Chat'); container.style.cssText = ` position: fixed; top: 0; left: 0; right: 0; bottom: 0; width: 100vw; height: 100vh; z-index: ${config.zIndex - 1}; background: rgba(0, 0, 0, 0.5); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); display: none; opacity: 0; transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94); padding: 20px; box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; `; return container; } // Create modal content wrapper (holds iframe + header) function createModalContent() { const modalContent = document.createElement('div'); const isMobile = window.innerWidth <= 768; if (isMobile) { modalContent.style.cssText = ` position: absolute; top: 12px; left: 12px; right: 12px; bottom: 12px; width: calc(100% - 24px); height: calc(100% - 24px); border-radius: 12px; background-color: #FFFFFF; transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94); transform: ${isOpen ? 'scale(1)' : 'scale(0.96)'}; `; } else { const width = isExpanded ? '1300px' : '1040px'; const height = isExpanded ? '975px' : '780px'; modalContent.style.cssText = ` position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%) scale(${isOpen ? '1' : '0.9'}); width: min(${width}, 90vw); height: min(${height}, 80vh); max-width: ${width}; max-height: ${height}; border-radius: 16px; background: white; box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25), 0 0 0 1px rgba(0, 0, 0, 0.05); transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94); `; } return modalContent; } // Create modal header with controls function createModalHeader() { const header = document.createElement('div'); header.style.cssText = ` position: absolute; top: 8px; right: 8px; display: flex; gap: 8px; z-index: 2; `; // Create "Open in new tab" button const newTabButton = document.createElement('button'); newTabButton.setAttribute('aria-label', 'Open chat in new tab'); newTabButton.setAttribute('title', 'Open in new tab'); newTabButton.style.cssText = ` width: 36px; height: 36px; border-radius: 8px; background: rgba(255, 255, 255, 0.9); color: #374151; border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 16px; transition: all 0.2s ease; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); `; // External link icon newTabButton.innerHTML = ` `; // Add hover effects newTabButton.addEventListener('mouseenter', () => { newTabButton.style.background = 'rgba(255, 255, 255, 1)'; newTabButton.style.transform = 'scale(1.05)'; }); newTabButton.addEventListener('mouseleave', () => { newTabButton.style.background = 'rgba(255, 255, 255, 0.9)'; newTabButton.style.transform = 'scale(1)'; }); // Add accessible focus styles for keyboard users newTabButton.addEventListener('focus', () => { newTabButton.style.outline = '2px solid #2563eb'; newTabButton.style.outlineOffset = '2px'; newTabButton.style.boxShadow = '0 2px 8px rgba(0, 0, 0, 0.1), 0 0 0 3px rgba(37, 99, 235, 0.2)'; newTabButton.style.background = 'rgba(255, 255, 255, 1)'; }); newTabButton.addEventListener('blur', () => { newTabButton.style.outline = 'none'; newTabButton.style.outlineOffset = ''; newTabButton.style.boxShadow = '0 2px 8px rgba(0, 0, 0, 0.1)'; newTabButton.style.background = 'rgba(255, 255, 255, 0.9)'; }); // Handle keyboard activation (Enter/Space) newTabButton.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); newTabButton.style.transform = 'scale(0.95)'; newTabButton.style.background = 'rgba(255, 255, 255, 0.8)'; } }); newTabButton.addEventListener('keyup', (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); newTabButton.style.transform = 'scale(1.05)'; newTabButton.style.background = 'rgba(255, 255, 255, 1)'; // Trigger click programmatically newTabButton.click(); } }); // Add click handler to open new tab newTabButton.addEventListener('click', (e) => { e.stopPropagation(); // Prevent modal close analytics.track('Widget Opened in New Tab', { trigger: 'new_tab_button' }); const fullScreenUrl = `https://widget.anablock.com/?orgId=${config.organizationId}`; window.open(fullScreenUrl, '_blank', 'noopener,noreferrer'); }); // Create size toggle button sizeToggleButton = document.createElement('button'); sizeToggleButton.setAttribute('aria-label', 'Expand view'); sizeToggleButton.setAttribute('title', 'Expand view'); const isMobile = window.innerWidth <= 768; sizeToggleButton.style.cssText = ` width: 36px; height: 36px; border-radius: 8px; background: rgba(255, 255, 255, 0.9); color: #374151; border: none; cursor: pointer; display: ${isMobile ? 'none' : 'flex'}; align-items: center; justify-content: center; font-size: 16px; transition: all 0.2s ease; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); `; // Expand icon (default state) sizeToggleButton.innerHTML = ` `; // Add hover effects sizeToggleButton.addEventListener('mouseenter', () => { sizeToggleButton.style.background = 'rgba(255, 255, 255, 1)'; sizeToggleButton.style.transform = 'scale(1.05)'; }); sizeToggleButton.addEventListener('mouseleave', () => { sizeToggleButton.style.background = 'rgba(255, 255, 255, 0.9)'; sizeToggleButton.style.transform = 'scale(1)'; }); // Add accessible focus styles for keyboard users sizeToggleButton.addEventListener('focus', () => { sizeToggleButton.style.outline = '2px solid #2563eb'; sizeToggleButton.style.outlineOffset = '2px'; sizeToggleButton.style.boxShadow = '0 2px 8px rgba(0, 0, 0, 0.1), 0 0 0 3px rgba(37, 99, 235, 0.2)'; sizeToggleButton.style.background = 'rgba(255, 255, 255, 1)'; }); sizeToggleButton.addEventListener('blur', () => { sizeToggleButton.style.outline = 'none'; sizeToggleButton.style.outlineOffset = ''; sizeToggleButton.style.boxShadow = '0 2px 8px rgba(0, 0, 0, 0.1)'; sizeToggleButton.style.background = 'rgba(255, 255, 255, 0.9)'; }); // Handle keyboard activation (Enter/Space) sizeToggleButton.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); sizeToggleButton.style.transform = 'scale(0.95)'; sizeToggleButton.style.background = 'rgba(255, 255, 255, 0.8)'; } }); sizeToggleButton.addEventListener('keyup', (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); sizeToggleButton.style.transform = 'scale(1.05)'; sizeToggleButton.style.background = 'rgba(255, 255, 255, 1)'; // Trigger click programmatically sizeToggleButton.click(); } }); // Add click handler to toggle size sizeToggleButton.addEventListener('click', (e) => { e.stopPropagation(); // Prevent modal close toggleSize(); }); // Create close button const closeButton = document.createElement('button'); closeButton.setAttribute('aria-label', 'Close chat'); closeButton.setAttribute('title', 'Close chat'); closeButton.style.cssText = ` width: 36px; height: 36px; border-radius: 8px; background: rgba(255, 255, 255, 0.9); color: #374151; border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 18px; transition: all 0.2s ease; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); `; // Close icon closeButton.innerHTML = ` `; // Add hover effects closeButton.addEventListener('mouseenter', () => { closeButton.style.background = 'rgba(255, 255, 255, 1)'; closeButton.style.transform = 'scale(1.05)'; }); closeButton.addEventListener('mouseleave', () => { closeButton.style.background = 'rgba(255, 255, 255, 0.9)'; closeButton.style.transform = 'scale(1)'; }); // Add accessible focus styles for keyboard users closeButton.addEventListener('focus', () => { closeButton.style.outline = '2px solid #2563eb'; closeButton.style.outlineOffset = '2px'; closeButton.style.boxShadow = '0 2px 8px rgba(0, 0, 0, 0.1), 0 0 0 3px rgba(37, 99, 235, 0.2)'; closeButton.style.background = 'rgba(255, 255, 255, 1)'; }); closeButton.addEventListener('blur', () => { closeButton.style.outline = 'none'; closeButton.style.outlineOffset = ''; closeButton.style.boxShadow = '0 2px 8px rgba(0, 0, 0, 0.1)'; closeButton.style.background = 'rgba(255, 255, 255, 0.9)'; }); // Handle keyboard activation (Enter/Space) closeButton.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); closeButton.style.transform = 'scale(0.95)'; closeButton.style.background = 'rgba(255, 255, 255, 0.8)'; } }); closeButton.addEventListener('keyup', (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); closeButton.style.transform = 'scale(1.05)'; closeButton.style.background = 'rgba(255, 255, 255, 1)'; // Trigger click programmatically closeButton.click(); } }); // Add click handler to close modal closeButton.addEventListener('click', (e) => { e.stopPropagation(); // Prevent event bubbling toggleWidget(); }); header.appendChild(newTabButton); header.appendChild(sizeToggleButton); header.appendChild(closeButton); return header; } // Create widget iframe (now positioned within modal content wrapper) function createWidgetIframe() { const iframe = document.createElement('iframe'); iframe.id = 'anablock-widget-iframe'; iframe.src = `${config.widgetUrl}?orgId=${config.organizationId}`; iframe.setAttribute('title', 'AI Assistant Chat Interface'); iframe.setAttribute('tabindex', '0'); iframe.style.cssText = ` width: 100%; height: 100%; border: none; border-radius: inherit; background: transparent; `; return iframe; } // Update modal content wrapper styles based on viewport size function updateModalContentStyles() { if (!modalContent) return; const isMobile = window.innerWidth <= 768; if (isMobile) { modalContent.style.cssText = ` position: absolute; top: 12px; left: 12px; right: 12px; bottom: 12px; width: calc(100% - 24px); height: calc(100% - 24px); border-radius: 12px; background-color: #FFFFFF; transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94); transform: ${isOpen ? 'scale(1)' : 'scale(0.96)'}; `; } else { const width = isExpanded ? '1300px' : '1040px'; const height = isExpanded ? '975px' : '780px'; modalContent.style.cssText = ` position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%) scale(${isOpen ? '1' : '0.9'}); width: min(${width}, 90vw); height: min(${height}, 80vh); max-width: ${width}; max-height: ${height}; border-radius: 16px; background: white; box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25), 0 0 0 1px rgba(0, 0, 0, 0.05); transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94); `; } } // Create widget button (floating action button) function createWidgetButton() { const button = document.createElement('button'); button.id = 'anablock-widget-button'; button.setAttribute('aria-label', 'Open AI Assistant'); button.style.cssText = ` position: fixed; bottom: 24px; right: 24px; width: 60px; height: 60px; border-radius: 50%; background: linear-gradient(135deg, #6366F1 0%, #4338CA 100%); color: white; display: flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: 0 10px 25px rgba(99, 102, 241, 0.3); border: none; font-size: 24px; transition: all 0.2s ease; z-index: ${config.zIndex}; touch-action: manipulation; -webkit-tap-highlight-color: transparent; `; // Add chat icon (static logo) button.innerHTML = ` AI Assistant `; // Add fallback if image fails to load setTimeout(() => { const img = button.querySelector('img'); if (img && img.style.display === 'none') { button.innerHTML = '💬'; button.setAttribute('aria-label', 'AI Assistant Chat'); } }, 1000); // Add hover effects button.addEventListener('mouseenter', () => { button.style.transform = 'scale(1.05)'; button.style.boxShadow = '0 6px 16px rgba(99, 102, 241, 0.4)'; }); button.addEventListener('mouseleave', () => { button.style.transform = 'scale(1)'; button.style.boxShadow = '0 4px 12px rgba(99, 102, 241, 0.3)'; }); return button; } // Widget state let isOpen = false; let isExpanded = false; let container, iframe, button, modalHeader, modalContent, sizeToggleButton; let previousActiveElement = null; let resizeTimeout = null; let scrollY = 0; let onKeyDown = null; let onMessage = null; // Debounced resize handler for responsive modal content styling function handleResize() { if (resizeTimeout) { clearTimeout(resizeTimeout); } resizeTimeout = setTimeout(() => { updateModalContentStyles(); // Hide size toggle button on mobile, show on desktop if (sizeToggleButton) { const isMobile = window.innerWidth <= 768; sizeToggleButton.style.display = isMobile ? 'none' : 'flex'; } resizeTimeout = null; }, 150); // 150ms debounce } // Enhanced scroll locking for iOS Safari compatibility function lockScroll() { // Store current scroll position scrollY = window.scrollY; // Apply iOS-compatible scroll lock document.body.style.position = 'fixed'; document.body.style.top = `-${scrollY}px`; document.body.style.width = '100%'; document.body.style.overflow = 'hidden'; // Prevent touch scrolling on mobile document.addEventListener('touchmove', preventTouchScroll, { passive: false }); } function unlockScroll() { // Remove touch scroll prevention document.removeEventListener('touchmove', preventTouchScroll); // Restore body styles document.body.style.position = ''; document.body.style.top = ''; document.body.style.width = ''; document.body.style.overflow = ''; // Restore scroll position window.scrollTo(0, scrollY); } // Prevent touch scrolling when modal is open function preventTouchScroll(e) { if (!container || !container.contains(e.target)) { e.preventDefault(); } } // Toggle modal size between compact and expanded function toggleSize() { isExpanded = !isExpanded; // Track size toggle analytics.track('Widget Modal Resized', { newSize: isExpanded ? 'expanded' : 'compact' }); // Update modal content styles with new size updateModalContentStyles(); // Update size toggle button icon and aria-label if (sizeToggleButton) { sizeToggleButton.innerHTML = isExpanded ? ` ` : ` `; sizeToggleButton.setAttribute('aria-label', isExpanded ? 'Compact view' : 'Expand view'); sizeToggleButton.setAttribute('title', isExpanded ? 'Compact view' : 'Expand view'); } } // Basic focus trap for modal accessibility function handleFocusTrap(e) { if (!isOpen || !container) return; const focusableElements = container.querySelectorAll( 'button, iframe, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' ); const firstElement = focusableElements[0]; const lastElement = focusableElements[focusableElements.length - 1]; if (e.key === 'Tab') { if (e.shiftKey) { // Shift + Tab if (document.activeElement === firstElement) { e.preventDefault(); lastElement.focus(); } } else { // Tab if (document.activeElement === lastElement) { e.preventDefault(); firstElement.focus(); } } } } // Toggle widget visibility (modal) function toggleWidget() { isOpen = !isOpen; if (isOpen) { // Track widget opened analytics.track('Widget Modal Opened', { trigger: 'button_click' }); // Store the currently focused element previousActiveElement = document.activeElement; // Open modal container.style.display = 'block'; button.style.setProperty('display', 'none', 'important'); // Hide widget button when modal is open lockScroll(); // Add focus trap document.addEventListener('keydown', handleFocusTrap); // Announce to screen readers that modal opened container.setAttribute('aria-live', 'polite'); container.setAttribute('aria-atomic', 'true'); setTimeout(() => { container.style.opacity = '1'; // Update modal content styles for open state updateModalContentStyles(); // Focus management - focus the iframe when modal opens setTimeout(() => { iframe.focus(); }, 50); }, 10); // Change button icon to close button.innerHTML = ` `; // Update button aria-label button.setAttribute('aria-label', 'Close AI Assistant'); } else { // Track widget closed analytics.track('Widget Modal Closed', { trigger: 'button_click', sessionDuration: Date.now() - analytics.sessionStartTime }); // Close modal container.style.opacity = '0'; button.style.setProperty('display', 'flex', 'important'); // Show widget button when modal is closed unlockScroll(); // Remove focus trap document.removeEventListener('keydown', handleFocusTrap); // Return focus to the previously focused element or button if (previousActiveElement && previousActiveElement.focus) { previousActiveElement.focus(); } else { button.focus(); } // Update modal content styles for closed state updateModalContentStyles(); setTimeout(() => { container.style.display = 'none'; // Remove aria-live to prevent unnecessary announcements container.removeAttribute('aria-live'); container.removeAttribute('aria-atomic'); }, 300); // Change button icon back to static logo button.innerHTML = ` AI Assistant `; // Add fallback if image fails to load setTimeout(() => { const img = button.querySelector('img'); if (img && img.style.display === 'none') { button.innerHTML = '💬'; button.setAttribute('aria-label', 'AI Assistant Chat'); } }, 1000); // Update button aria-label button.setAttribute('aria-label', 'Open AI Assistant'); } } // Initialize widget function initWidget() { // Create elements container = createWidgetContainer(); modalContent = createModalContent(); iframe = createWidgetIframe(); modalHeader = createModalHeader(); button = createWidgetButton(); // Apply initial modal content styles based on current viewport updateModalContentStyles(); // Assemble widget - modalContent contains iframe and header modalContent.appendChild(iframe); modalContent.appendChild(modalHeader); container.appendChild(modalContent); document.body.appendChild(container); document.body.appendChild(button); // Add click handler button.addEventListener('click', toggleWidget); // Handle escape key - capture handler for proper cleanup onKeyDown = (e) => { if (e.key === 'Escape' && isOpen) { toggleWidget(); } }; document.addEventListener('keydown', onKeyDown); // Handle clicks outside iframe (on backdrop) container.addEventListener('click', (e) => { if (e.target === container && isOpen) { toggleWidget(); } }); // Add responsive resize listener window.addEventListener('resize', handleResize); // Track successful initialization analytics.track('Widget Successfully Initialized', { initializationTime: Date.now() - analytics.sessionStartTime }); if (isDevelopment) { console.log('Anablock Widget: Successfully initialized'); } } // Wait for DOM to be ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initWidget); } else { initWidget(); } // Listen for messages from the widget iframe - capture handler for cleanup onMessage = (event) => { // Guard against accessing destroyed elements if (!container || !iframe) { return; } // Filter out unwanted messages (React DevTools, browser extensions, etc.) if (event.data?.source === 'react-devtools-content-script' || event.data?.source === 'react-devtools-bridge' || event.data?.source === 'react-devtools-detector' || event.data?.hello === true || typeof event.data === 'string' && event.data.includes('webpack') || typeof event.data === 'string' && event.data.includes('webpackOk')) { return; } // Verify origin for security if (!event.origin.includes('anablock') && !event.origin.includes('vercel.app') && event.origin !== window.location.origin) { return; } try { const data = typeof event.data === 'string' ? JSON.parse(event.data) : event.data; // Handle different message types switch (data.type) { case 'widget:close': if (isOpen) toggleWidget(); break; case 'widget:minimize': if (isOpen) toggleWidget(); break; case 'widget:resize': if (data.height && iframe) { iframe.style.height = `${data.height}px`; } break; default: // Only log relevant widget messages, not spam if (data.type && data.type.startsWith('widget:')) { console.log('Anablock Widget: Received message', data); } } } catch (error) { // Silently ignore parsing errors from unwanted messages if (event.data?.source !== 'react-devtools-content-script') { // Silently ignore parsing errors } } }; window.addEventListener('message', onMessage); // Cleanup function to remove event listeners (called when widget is torn down) function cleanup() { // Remove resize listener window.removeEventListener('resize', handleResize); // Clear any pending resize timeout if (resizeTimeout) { clearTimeout(resizeTimeout); resizeTimeout = null; } // Remove message listener if (onMessage) { window.removeEventListener('message', onMessage); onMessage = null; } // Remove keydown listeners if (onKeyDown) { document.removeEventListener('keydown', onKeyDown); onKeyDown = null; } // Remove focus trap if active if (isOpen) { document.removeEventListener('keydown', handleFocusTrap); } // Remove elements from DOM if (container) { container.remove(); container = null; } if (button) { button.remove(); button = null; } iframe = null; modalHeader = null; modalContent = null; // Restore body scroll if modal was open if (isOpen) { unlockScroll(); } if (isDevelopment) { console.log('Anablock Widget: Cleanup completed'); } } // Expose cleanup function globally (useful for single-page apps) window.AnablockWidget = { cleanup: cleanup }; })();