/* ============================================================================= AFFILIATE PRODUCT AGGREGATOR - MAIN JAVASCRIPT Author: MiniMax Agent Description: Vanilla JavaScript for affiliate product aggregator website ============================================================================= */ // DOM Content Loaded Event document.addEventListener('DOMContentLoaded', function() { initializeApp(); }); // Main Application Initialization function initializeApp() { setupMobileMenu(); setupSearchFunctionality(); setupProductFilters(); setupImageLazyLoading(); setupSmoothScrolling(); setupFormValidation(); setupProductInteractions(); } // ============================================================================= // MOBILE MENU FUNCTIONALITY // ============================================================================= function setupMobileMenu() { const mobileMenuToggle = document.querySelector('.mobile-menu-toggle'); const nav = document.querySelector('.nav'); if (mobileMenuToggle && nav) { mobileMenuToggle.addEventListener('click', function() { nav.classList.toggle('active'); mobileMenuToggle.classList.toggle('active'); // Animate hamburger menu const spans = mobileMenuToggle.querySelectorAll('span'); spans.forEach((span, index) => { if (mobileMenuToggle.classList.contains('active')) { if (index === 0) span.style.transform = 'rotate(45deg) translate(5px, 5px)'; if (index === 1) span.style.opacity = '0'; if (index === 2) span.style.transform = 'rotate(-45deg) translate(7px, -6px)'; } else { span.style.transform = 'none'; span.style.opacity = '1'; } }); }); // Close menu when clicking on nav links const navLinks = nav.querySelectorAll('a'); navLinks.forEach(link => { link.addEventListener('click', () => { nav.classList.remove('active'); mobileMenuToggle.classList.remove('active'); // Reset hamburger menu const spans = mobileMenuToggle.querySelectorAll('span'); spans.forEach(span => { span.style.transform = 'none'; span.style.opacity = '1'; }); }); }); } } // ============================================================================= // SEARCH FUNCTIONALITY // ============================================================================= function setupSearchFunctionality() { const searchForm = document.querySelector('.search-form'); const searchInput = document.querySelector('.search-input'); if (searchForm && searchInput) { searchForm.addEventListener('submit', function(e) { e.preventDefault(); const query = searchInput.value.trim(); if (query) { performSearch(query); } }); // Live search suggestions (basic implementation) let searchTimeout; searchInput.addEventListener('input', function() { clearTimeout(searchTimeout); searchTimeout = setTimeout(() => { const query = this.value.trim(); if (query.length > 2) { // In a real implementation, this would fetch suggestions from an API showSearchSuggestions(query); } }, 300); }); } } function performSearch(query) { // In a real implementation, this would redirect to search results page console.log('Searching for:', query); // Show loading state const searchBtn = document.querySelector('.search-btn'); if (searchBtn) { const originalText = searchBtn.textContent; searchBtn.innerHTML = ' Searching...'; searchBtn.disabled = true; // Simulate search delay setTimeout(() => { searchBtn.textContent = originalText; searchBtn.disabled = false; // Redirect to search results (in real implementation) // window.location.href = `/search?q=${encodeURIComponent(query)}`; }, 1500); } } function showSearchSuggestions(query) { // Basic implementation - in real app this would show actual suggestions console.log('Showing suggestions for:', query); } // ============================================================================= // PRODUCT FILTERS // ============================================================================= function setupProductFilters() { const filterButtons = document.querySelectorAll('.filter-btn'); const productGrid = document.querySelector('.product-grid'); if (filterButtons.length && productGrid) { filterButtons.forEach(button => { button.addEventListener('click', function() { const filter = this.dataset.filter; // Update active button filterButtons.forEach(btn => btn.classList.remove('active')); this.classList.add('active'); // Filter products filterProducts(filter); }); }); } } function filterProducts(filter) { const productCards = document.querySelectorAll('.product-card'); productCards.forEach(card => { const category = card.dataset.category || ''; const price = parseFloat(card.dataset.price) || 0; let shouldShow = true; // Apply filter logic switch(filter) { case 'price-low': shouldShow = price < 50; break; case 'price-high': shouldShow = price >= 50; break; case 'electronics': shouldShow = category === 'electronics'; break; case 'fashion': shouldShow = category === 'fashion'; break; case 'home': shouldShow = category === 'home'; break; default: shouldShow = true; // Show all } // Animate card visibility if (shouldShow) { card.style.display = 'block'; card.classList.add('fade-in'); } else { card.style.display = 'none'; card.classList.remove('fade-in'); } }); } // ============================================================================= // LAZY LOADING IMAGES // ============================================================================= function setupImageLazyLoading() { const images = document.querySelectorAll('img[data-src]'); if ('IntersectionObserver' in window) { const imageObserver = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; img.src = img.dataset.src; img.classList.remove('lazy'); imageObserver.unobserve(img); } }); }); images.forEach(img => imageObserver.observe(img)); } else { // Fallback for older browsers images.forEach(img => { img.src = img.dataset.src; }); } } // ============================================================================= // SMOOTH SCROLLING // ============================================================================= function setupSmoothScrolling() { const links = document.querySelectorAll('a[href^="#"]'); links.forEach(link => { link.addEventListener('click', function(e) { const href = this.getAttribute('href'); if (href === '#') return; e.preventDefault(); const target = document.querySelector(href); if (target) { const headerHeight = document.querySelector('.header').offsetHeight; const targetPosition = target.offsetTop - headerHeight - 20; window.scrollTo({ top: targetPosition, behavior: 'smooth' }); } }); }); } // ============================================================================= // FORM VALIDATION // ============================================================================= function setupFormValidation() { const forms = document.querySelectorAll('form'); forms.forEach(form => { form.addEventListener('submit', function(e) { if (!validateForm(this)) { e.preventDefault(); } }); // Real-time validation const inputs = form.querySelectorAll('input, textarea, select'); inputs.forEach(input => { input.addEventListener('blur', () => validateField(input)); input.addEventListener('input', () => clearFieldError(input)); }); }); } function validateForm(form) { let isValid = true; const inputs = form.querySelectorAll('input, textarea, select'); inputs.forEach(input => { if (!validateField(input)) { isValid = false; } }); return isValid; } function validateField(field) { const value = field.value.trim(); const type = field.type; const required = field.hasAttribute('required'); // Clear previous errors clearFieldError(field); // Required field validation if (required && !value) { showFieldError(field, 'This field is required'); return false; } // Email validation if (type === 'email' && value) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(value)) { showFieldError(field, 'Please enter a valid email address'); return false; } } // Phone validation if (type === 'tel' && value) { const phoneRegex = /^[\+]?[1-9][\d]{0,15}$/; if (!phoneRegex.test(value.replace(/[\s\-\(\)]/g, ''))) { showFieldError(field, 'Please enter a valid phone number'); return false; } } return true; } function showFieldError(field, message) { field.classList.add('error'); // Remove existing error message const existingError = field.parentNode.querySelector('.error-message'); if (existingError) { existingError.remove(); } // Add new error message const errorElement = document.createElement('span'); errorElement.className = 'error-message'; errorElement.textContent = message; errorElement.style.color = '#dc2626'; errorElement.style.fontSize = '0.875rem'; errorElement.style.marginTop = '0.25rem'; errorElement.style.display = 'block'; field.parentNode.appendChild(errorElement); } function clearFieldError(field) { field.classList.remove('error'); const errorMessage = field.parentNode.querySelector('.error-message'); if (errorMessage) { errorMessage.remove(); } } // ============================================================================= // PRODUCT INTERACTIONS // ============================================================================= function setupProductInteractions() { // Product card hover effects const productCards = document.querySelectorAll('.product-card'); productCards.forEach(card => { card.addEventListener('mouseenter', function() { this.style.transform = 'translateY(-5px)'; }); card.addEventListener('mouseleave', function() { this.style.transform = 'translateY(0)'; }); }); // Affiliate button tracking const affiliateButtons = document.querySelectorAll('.affiliate-btn, .btn-primary'); affiliateButtons.forEach(button => { button.addEventListener('click', function(e) { // Track affiliate click (in real implementation) const productTitle = this.closest('.product-card')?.querySelector('.product-title')?.textContent || 'Unknown Product'; console.log('Affiliate click tracked for:', productTitle); // Add loading state const originalText = this.textContent; this.innerHTML = ' Redirecting...'; this.disabled = true; // In real implementation, this would redirect to affiliate link setTimeout(() => { this.textContent = originalText; this.disabled = false; }, 1000); }); }); // Product quantity selector const quantityInputs = document.querySelectorAll('.quantity-input'); quantityInputs.forEach(input => { input.addEventListener('change', function() { updateProductPrice(this); }); }); } function updateProductPrice(quantityInput) { const quantity = parseInt(quantityInput.value) || 1; const productCard = quantityInput.closest('.product-card'); const basePrice = parseFloat(productCard.dataset.basePrice) || 0; const newPrice = (basePrice * quantity).toFixed(2); const priceElement = productCard.querySelector('.price-current'); if (priceElement) { priceElement.textContent = `$${newPrice}`; } } // ============================================================================= // UTILITY FUNCTIONS // ============================================================================= function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } function throttle(func, limit) { let inThrottle; return function() { const args = arguments; const context = this; if (!inThrottle) { func.apply(context, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } } } // Format currency function formatCurrency(amount, currency = 'USD') { return new Intl.NumberFormat('en-US', { style: 'currency', currency: currency }).format(amount); } // Local storage helpers function setLocalStorage(key, value) { try { localStorage.setItem(key, JSON.stringify(value)); } catch (e) { console.warn('LocalStorage not available:', e); } } function getLocalStorage(key) { try { const item = localStorage.getItem(key); return item ? JSON.parse(item) : null; } catch (e) { console.warn('LocalStorage not available:', e); return null; } } // ============================================================================= // SEO & ANALYTICS HELPERS // ============================================================================= function trackPageView(page) { // In real implementation, this would send data to analytics service console.log('Page view tracked:', page); } function trackEvent(eventName, eventData) { // In real implementation, this would send event data to analytics console.log('Event tracked:', eventName, eventData); } // Track user interactions for SEO analysis document.addEventListener('click', function(e) { if (e.target.matches('.product-card, .product-card *')) { trackEvent('product_click', { element: e.target.tagName, text: e.target.textContent?.trim()?.substring(0, 50) }); } }); // ============================================================================= // ERROR HANDLING // ============================================================================= window.addEventListener('error', function(e) { console.error('JavaScript error:', e.error); // In production, this could send error reports to monitoring service }); window.addEventListener('unhandledrejection', function(e) { console.error('Unhandled promise rejection:', e.reason); // In production, this could send error reports to monitoring service }); // ============================================================================= // PERFORMANCE MONITORING // ============================================================================= window.addEventListener('load', function() { // Track page load time const loadTime = performance.now(); console.log('Page loaded in:', Math.round(loadTime), 'ms'); // In real implementation, this could send performance metrics }); // ============================================================================= // ACCESSIBILITY ENHANCEMENTS // ============================================================================= // Keyboard navigation for product grid document.addEventListener('keydown', function(e) { if (e.key === 'ArrowRight' || e.key === 'ArrowLeft') { const focusedElement = document.activeElement; if (focusedElement.closest('.product-card')) { e.preventDefault(); navigateProductGrid(focusedElement, e.key); } } }); function navigateProductGrid(currentElement, direction) { const productCards = Array.from(document.querySelectorAll('.product-card')); const currentIndex = productCards.indexOf(currentElement.closest('.product-card')); let nextIndex; if (direction === 'ArrowRight') { nextIndex = (currentIndex + 1) % productCards.length; } else { nextIndex = currentIndex === 0 ? productCards.length - 1 : currentIndex - 1; } const nextCard = productCards[nextIndex]; const focusableElement = nextCard.querySelector('a, button, [tabindex="0"]'); if (focusableElement) { focusableElement.focus(); } } // Reduce motion for users who prefer it if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) { document.documentElement.style.setProperty('--animation-duration', '0.01ms'); }