// Advanced analytics and heatmap tracking class AdvancedAnalytics { constructor() { this.sessionId = this.generateSessionId(); this.startTime = Date.now(); this.init(); } init() { this.trackSession(); this.setupHeatmapTracking(); this.setupScrollTracking(); this.setupClickTracking(); this.setupTimeOnPage(); this.setupVisibilityTracking(); } generateSessionId() { return 'session_' + Math.random().toString(36).substr(2, 9) + '_' + Date.now(); } // Track user session trackSession() { const sessionData = { sessionId: this.sessionId, userAgent: navigator.userAgent, screenResolution: `${screen.width}x${screen.height}`, windowSize: `${window.innerWidth}x${window.innerHeight}`, language: navigator.language, timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, referrer: document.referrer, landingPage: window.location.pathname, timestamp: new Date().toISOString() }; this.sendAnalytics('session_start', sessionData); } // Setup heatmap tracking setupHeatmapTracking() { let mousePositions = []; let lastSampleTime = 0; const sampleRate = 100; // Sample every 100ms document.addEventListener('mousemove', (e) => { const now = Date.now(); if (now - lastSampleTime > sampleRate) { mousePositions.push({ x: e.pageX, y: e.pageY, timestamp: now }); lastSampleTime = now; // Send data in batches if (mousePositions.length >= 50) { this.sendAnalytics('heatmap_data', { sessionId: this.sessionId, positions: mousePositions, page: window.location.pathname }); mousePositions = []; } } }); // Send remaining data on page unload window.addEventListener('beforeunload', () => { if (mousePositions.length > 0) { navigator.sendBeacon('/api/analytics/heatmap', JSON.stringify({ sessionId: this.sessionId, positions: mousePositions, page: window.location.pathname })); } }); } // Setup scroll depth tracking setupScrollTracking() { let maxScroll = 0; const scrollMilestones = [25, 50, 75, 90, 100]; const triggered = new Set(); window.addEventListener('scroll', () => { const scrollPercent = Math.round( (window.pageYOffset / (document.documentElement.scrollHeight - window.innerHeight)) * 100 ); if (scrollPercent > maxScroll) { maxScroll = scrollPercent; } // Track scroll milestones scrollMilestones.forEach(milestone => { if (scrollPercent >= milestone && !triggered.has(milestone)) { triggered.add(milestone); this.sendAnalytics('scroll_depth', { sessionId: this.sessionId, depth: milestone, page: window.location.pathname, timestamp: new Date().toISOString() }); } }); }); } // Setup click tracking setupClickTracking() { document.addEventListener('click', (e) => { const element = e.target; const tagName = element.tagName.toLowerCase(); // Track important elements if (['a', 'button', 'input'].includes(tagName) || element.classList.contains('trackable')) { const clickData = { sessionId: this.sessionId, element: { tagName, id: element.id, className: element.className, text: element.textContent?.substring(0, 100), href: element.href, type: element.type }, position: { x: e.pageX, y: e.pageY }, page: window.location.pathname, timestamp: new Date().toISOString() }; this.sendAnalytics('click_tracking', clickData); } // Track form field clicks if (tagName === 'input' || tagName === 'textarea' || tagName === 'select') { this.sendAnalytics('form_interaction', { sessionId: this.sessionId, fieldName: element.name, fieldType: element.type, formId: element.closest('form')?.id, action: 'focus', page: window.location.pathname, timestamp: new Date().toISOString() }); } }); } // Setup time on page tracking setupTimeOnPage() { let pageStartTime = Date.now(); let isActive = true; let totalActiveTime = 0; let lastActiveTime = pageStartTime; // Track when page becomes active/inactive document.addEventListener('visibilitychange', () => { const now = Date.now(); if (document.hidden) { if (isActive) { totalActiveTime += now - lastActiveTime; isActive = false; } } else { isActive = true; lastActiveTime = now; } }); // Send time data periodically setInterval(() => { const now = Date.now(); const currentActiveTime = isActive ? totalActiveTime + (now - lastActiveTime) : totalActiveTime; this.sendAnalytics('time_on_page', { sessionId: this.sessionId, activeTime: Math.round(currentActiveTime / 1000), // in seconds totalTime: Math.round((now - pageStartTime) / 1000), page: window.location.pathname, timestamp: new Date().toISOString() }); }, 30000); // Every 30 seconds // Send final time on page unload window.addEventListener('beforeunload', () => { const now = Date.now(); const finalActiveTime = isActive ? totalActiveTime + (now - lastActiveTime) : totalActiveTime; navigator.sendBeacon('/api/analytics/time', JSON.stringify({ sessionId: this.sessionId, activeTime: Math.round(finalActiveTime / 1000), totalTime: Math.round((now - pageStartTime) / 1000), page: window.location.pathname, timestamp: new Date().toISOString() })); }); } // Setup visibility tracking for elements setupVisibilityTracking() { const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const element = entry.target; this.sendAnalytics('element_view', { sessionId: this.sessionId, element: { id: element.id, className: element.className, tagName: element.tagName.toLowerCase() }, page: window.location.pathname, timestamp: new Date().toISOString() }); } }); }, { threshold: 0.5 }); // Track important sections document.querySelectorAll('section, .hero-section, .features-section, .conversion-section').forEach(el => { observer.observe(el); }); } // A/B Test tracking trackABTest(testName, variant) { this.sendAnalytics('ab_test', { sessionId: this.sessionId, testName, variant, page: window.location.pathname, timestamp: new Date().toISOString() }); } // Error tracking trackError(error, context = {}) { this.sendAnalytics('javascript_error', { sessionId: this.sessionId, error: { message: error.message, stack: error.stack, filename: error.filename, lineno: error.lineno, colno: error.colno }, context, page: window.location.pathname, userAgent: navigator.userAgent, timestamp: new Date().toISOString() }); } // Send analytics data async sendAnalytics(event, data) { try { const payload = { event, data, url: window.location.href, timestamp: new Date().toISOString() }; // Use sendBeacon for important events if available if (navigator.sendBeacon && ['session_start', 'conversion', 'form_submit'].includes(event)) { navigator.sendBeacon('/api/analytics/track', JSON.stringify(payload)); } else { // Fallback to fetch fetch('/api/analytics/track', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }).catch(err => console.error('Analytics error:', err)); } } catch (error) { console.error('Analytics sending error:', error); } } // Get session statistics getSessionStats() { return { sessionId: this.sessionId, duration: Math.round((Date.now() - this.startTime) / 1000), page: window.location.pathname }; } } // Performance monitoring class PerformanceMonitor { constructor() { this.init(); } init() { // Monitor page load performance window.addEventListener('load', () => { setTimeout(() => { this.trackPagePerformance(); }, 0); }); // Monitor Core Web Vitals this.trackWebVitals(); } trackPagePerformance() { if ('performance' in window) { const navigation = performance.getEntriesByType('navigation')[0]; const paint = performance.getEntriesByType('paint'); const performanceData = { loadTime: Math.round(navigation.loadEventEnd - navigation.loadEventStart), domContentLoaded: Math.round(navigation.domContentLoadedEventEnd - navigation.domContentLoadedEventStart), firstPaint: paint.find(p => p.name === 'first-paint')?.startTime || 0, firstContentfulPaint: paint.find(p => p.name === 'first-contentful-paint')?.startTime || 0, pageSize: navigation.transferSize, resources: performance.getEntriesByType('resource').length }; window.analytics?.sendAnalytics('page_performance', performanceData); } } trackWebVitals() { // Track Largest Contentful Paint (LCP) if ('PerformanceObserver' in window) { new PerformanceObserver((list) => { const entries = list.getEntries(); const lastEntry = entries[entries.length - 1]; window.analytics?.sendAnalytics('web_vital_lcp', { value: lastEntry.startTime, rating: lastEntry.startTime > 2500 ? 'poor' : lastEntry.startTime > 1200 ? 'needs-improvement' : 'good' }); }).observe({ entryTypes: ['largest-contentful-paint'] }); // Track First Input Delay (FID) new PerformanceObserver((list) => { const entries = list.getEntries(); entries.forEach(entry => { window.analytics?.sendAnalytics('web_vital_fid', { value: entry.processingStart - entry.startTime, rating: entry.processingStart - entry.startTime > 100 ? 'poor' : entry.processingStart - entry.startTime > 25 ? 'needs-improvement' : 'good' }); }); }).observe({ entryTypes: ['first-input'] }); // Track Cumulative Layout Shift (CLS) let clsScore = 0; new PerformanceObserver((list) => { const entries = list.getEntries(); entries.forEach(entry => { if (!entry.hadRecentInput) { clsScore += entry.value; } }); window.analytics?.sendAnalytics('web_vital_cls', { value: clsScore, rating: clsScore > 0.25 ? 'poor' : clsScore > 0.1 ? 'needs-improvement' : 'good' }); }).observe({ entryTypes: ['layout-shift'] }); } } } // Initialize analytics window.addEventListener('DOMContentLoaded', () => { window.analytics = new AdvancedAnalytics(); window.performanceMonitor = new PerformanceMonitor(); // Global error handling window.addEventListener('error', (event) => { window.analytics?.trackError(event.error, { type: 'javascript_error', source: event.filename, line: event.lineno, column: event.colno }); }); // Promise rejection handling window.addEventListener('unhandledrejection', (event) => { window.analytics?.trackError(new Error(event.reason), { type: 'unhandled_promise_rejection' }); }); });