/* =============================================================================
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 = '<span class="loading"></span> 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 = '<span class="loading"></span> 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');
}