/** * Vista Residences - Behavioral Tracking Module * Stores all interaction data in localStorage for dashboard consumption * Compatible with existing dashboard-onni.html implementation */ const VistaTracker = (function() { const STORAGE_KEY = 'vista_analytics'; const USER_KEY = 'vista_user'; const SESSION_KEY = 'vista_session'; // Initialize or get existing data function getData() { try { const data = localStorage.getItem(STORAGE_KEY); return data ? JSON.parse(data) : { events: [], users: {}, sessions: [], comparisons: [], favorites: [] }; } catch (e) { console.error('VistaTracker: Error reading data', e); return { events: [], users: {}, sessions: [], comparisons: [], favorites: [] }; } } function saveData(data) { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); } catch (e) { console.error('VistaTracker: Error saving data', e); } } // Get or create user function getUser() { let user = localStorage.getItem(USER_KEY); if (user) { return JSON.parse(user); } // Create anonymous user const anonId = 'anon_' + Math.random().toString(36).substr(2, 9); user = { id: anonId, type: 'anonymous', created_at: new Date().toISOString(), is_vip: false, vip_id: null, persona: null }; localStorage.setItem(USER_KEY, JSON.stringify(user)); return user; } function setUser(userData) { const current = getUser(); const updated = { ...current, ...userData }; localStorage.setItem(USER_KEY, JSON.stringify(updated)); return updated; } // Session management function getSession() { let session = sessionStorage.getItem(SESSION_KEY); if (session) { return JSON.parse(session); } const user = getUser(); session = { session_id: 'sess_' + Math.random().toString(36).substr(2, 9), user_id: user.id, user_type: user.type, started_at: new Date().toISOString(), page_views: 0, events: [] }; sessionStorage.setItem(SESSION_KEY, JSON.stringify(session)); // Record session start const data = getData(); data.sessions.push(session); saveData(data); return session; } function updateSession(updates) { const session = getSession(); const updated = { ...session, ...updates }; sessionStorage.setItem(SESSION_KEY, JSON.stringify(updated)); return updated; } // Core tracking function function track(eventType, payload = {}) { const user = getUser(); const session = getSession(); const event = { event_id: 'evt_' + Math.random().toString(36).substr(2, 9), event_type: eventType, timestamp: new Date().toISOString(), user_id: user.id, user_type: user.type, user_name: user.name || null, session_id: session.session_id, is_vip: user.is_vip, vip_id: user.vip_id, persona: user.persona, url: window.location.href, page: window.location.pathname, ...payload }; const data = getData(); data.events.push(event); saveData(data); // Update session session.events.push(event); session.page_views = session.events.filter(e => e.event_type === 'page_view').length; updateSession(session); console.log('VistaTracker:', eventType, payload); return event; } // Page visit tracking function trackPageView(pageName, metadata = {}) { return track('page_view', { page_name: pageName, referrer: document.referrer, ...metadata }); } // Unit view tracking function trackUnitView(unitId, unitName, floorPlanType, tower, metadata = {}) { return track('unit_view', { unit_id: unitId, unit_name: unitName, floor_plan_type: floorPlanType, tower: tower, ...metadata }); } // Modal tracking let modalOpenTime = null; function trackModalOpen(modalType, unitId = null) { modalOpenTime = Date.now(); return track('modal_open', { modal_type: modalType, unit_id: unitId }); } function trackModalClose(modalType, unitId = null) { const dwellTime = modalOpenTime ? Math.round((Date.now() - modalOpenTime) / 1000) : 0; modalOpenTime = null; return track('modal_close', { modal_type: modalType, unit_id: unitId, dwell_time_sec: dwellTime }); } // Tab click tracking function trackTabClick(tabName, context = {}) { return track('tab_click', { tab_name: tabName, ...context }); } // Pricing section dwell time let pricingStartTime = null; let pricingCheckInterval = null; function startPricingDwell() { pricingStartTime = Date.now(); // Check for pricing hesitation (>30 seconds without inquiry) pricingCheckInterval = setTimeout(() => { if (pricingStartTime) { track('pricing_hesitation', { dwell_time_sec: 30, flagged: true }); } }, 30000); return track('pricing_section_enter', {}); } function endPricingDwell(clickedInquire = false) { if (pricingCheckInterval) { clearTimeout(pricingCheckInterval); pricingCheckInterval = null; } const dwellTime = pricingStartTime ? Math.round((Date.now() - pricingStartTime) / 1000) : 0; pricingStartTime = null; return track('pricing_section_exit', { dwell_time_sec: dwellTime, clicked_inquire: clickedInquire, hesitation_flagged: dwellTime > 30 && !clickedInquire }); } // Favorites tracking function trackFavorite(unitId, action = 'add') { const data = getData(); if (action === 'add') { if (!data.favorites.includes(unitId)) { data.favorites.push(unitId); } } else { data.favorites = data.favorites.filter(id => id !== unitId); } saveData(data); return track('favorite', { unit_id: unitId, action: action, total_favorites: data.favorites.length }); } // Comparison tracking function trackComparison(unitIds) { const data = getData(); const comparisonId = 'cmp_' + Math.random().toString(36).substr(2, 9); data.comparisons.push({ comparison_id: comparisonId, unit_ids: unitIds, timestamp: new Date().toISOString() }); saveData(data); return track('comparison', { comparison_id: comparisonId, unit_ids: unitIds, unit_count: unitIds.length }); } // Inquiry button tracking function trackInquiry(unitId, inquiryType = 'general') { return track('inquiry_click', { unit_id: unitId, inquiry_type: inquiryType }); } // CTA clicks (compatible with existing dashboard) function trackCTA(ctaName, metadata = {}) { return track('cta_click', { cta_name: ctaName, ...metadata }); } // Lead tagging (rule-based) function computeLeadTags(userId) { const data = getData(); const userEvents = data.events.filter(e => e.user_id === userId); const tags = []; // Hot Lead: High dwell time + multiple unit comparisons const comparisons = userEvents.filter(e => e.event_type === 'comparison'); const totalDwellTime = userEvents .filter(e => e.event_type === 'modal_close' && e.dwell_time_sec) .reduce((sum, e) => sum + e.dwell_time_sec, 0); if (totalDwellTime > 300 && comparisons.length >= 2) { tags.push('hot_lead'); } // Pricing Sensitive: High pricing dwell time + no inquiry const pricingEvents = userEvents.filter(e => e.event_type === 'pricing_section_exit'); const pricingDwellTotal = pricingEvents.reduce((sum, e) => sum + (e.dwell_time_sec || 0), 0); const hasInquiry = userEvents.some(e => e.event_type === 'inquiry_click'); if (pricingDwellTotal > 60 && !hasInquiry) { tags.push('pricing_sensitive'); } // Investor: Visits multiple different unit types const unitTypes = [...new Set(userEvents .filter(e => e.event_type === 'unit_view' && e.floor_plan_type) .map(e => e.floor_plan_type))]; if (unitTypes.length >= 3) { tags.push('investor'); } return tags; } // VIP setup function setVIP(vipId, persona, vipName = null) { const updated = setUser({ id: vipId, // Use VIP ID as user ID for consistency type: 'vip', is_vip: true, vip_id: vipId, persona: persona, name: vipName }); // Update existing session with VIP info const session = getSession(); session.user_id = vipId; session.user_type = 'vip'; updateSession(session); return updated; } // Dummy login (for registered users) function dummyLogin(email, name) { // Use deterministic user ID for Teo (demo user) so activities are grouped const userId = (name.toLowerCase() === 'teo' || email.toLowerCase() === 'teo@demo.com') ? 'user_teo_demo' : 'user_' + Math.random().toString(36).substr(2, 9); const updated = setUser({ id: userId, type: 'registered', email: email, name: name, logged_in_at: new Date().toISOString() }); // Update existing session with registered user info const session = getSession(); session.user_id = userId; session.user_type = 'registered'; session.user_name = name; updateSession(session); // Track login event track('user_login', { user_name: name, email: email }); return updated; } function logout() { localStorage.removeItem(USER_KEY); sessionStorage.removeItem(SESSION_KEY); } // Data export function exportData() { return getData(); } function exportJSON() { const data = getData(); return JSON.stringify(data, null, 2); } // Clear data (for testing) function clearData() { localStorage.removeItem(STORAGE_KEY); localStorage.removeItem(USER_KEY); sessionStorage.removeItem(SESSION_KEY); } // Get analytics summary for dashboard function getSummary() { const data = getData(); const events = data.events; return { total_events: events.length, total_sessions: data.sessions.length, total_users: [...new Set(events.map(e => e.user_id))].length, vip_sessions: events.filter(e => e.is_vip).length, anonymous_sessions: events.filter(e => e.user_type === 'anonymous').length, registered_sessions: events.filter(e => e.user_type === 'registered').length, page_views: events.filter(e => e.event_type === 'page_view').length, unit_views: events.filter(e => e.event_type === 'unit_view').length, favorites: data.favorites.length, comparisons: data.comparisons.length, inquiries: events.filter(e => e.event_type === 'inquiry_click').length, cta_clicks: { book_viewing: events.filter(e => e.cta_name === 'book_viewing').length, request_pricing: events.filter(e => e.cta_name === 'request_pricing').length, request_payment_plan: events.filter(e => e.cta_name === 'request_payment_plan').length, download_brochure: events.filter(e => e.cta_name === 'download_brochure').length } }; } // Get events by type for dashboard function getEventsByType(eventType) { const data = getData(); return data.events.filter(e => e.event_type === eventType); } // Get user timeline function getUserTimeline(userId) { const data = getData(); return data.events .filter(e => e.user_id === userId) .sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp)); } // Public API return { // User management getUser, setUser, setVIP, dummyLogin, logout, // Session getSession, // Tracking track, trackPageView, trackUnitView, trackModalOpen, trackModalClose, trackTabClick, trackFavorite, trackComparison, trackInquiry, trackCTA, startPricingDwell, endPricingDwell, // Lead tagging computeLeadTags, // Data access getData, exportData, exportJSON, getSummary, getEventsByType, getUserTimeline, clearData }; })(); // Auto-track page view on load document.addEventListener('DOMContentLoaded', function() { const pageName = document.title || window.location.pathname; VistaTracker.trackPageView(pageName); });