// Product Management System class ProductManager { constructor() { this.products = []; this.categories = []; this.pagination = {}; this.currentPage = 1; this.itemsPerPage = 16; this.filteredProducts = []; this.selectedCategories = new Set(); } // Load products from JSON file async loadProducts() { try { const response = await fetch("/data/products.json"); const data = await response.json(); this.products = data.products; this.categories = data.categories; this.pagination = data.pagination; this.filteredProducts = [...this.products]; this.renderProducts(); this.updatePagination(); this.updateResultsCount(); this.setupEventListeners(); this.renderCategoryFilters(); // Check for URL parameters and pre-select category this.handleUrlParameters(); } catch (error) { console.error("Error loading products:", error); } } // Handle URL parameters for pre-selecting category filters handleUrlParameters() { const urlParams = new URLSearchParams(window.location.search); const category = urlParams.get("category"); if (category) { // Pre-select the category in the filter this.selectedCategories = new Set([category]); this.applyFilters(); this.currentPage = 1; this.renderProducts(); this.updatePagination(); this.updateResultsCount(); // Update the UI to show the filter is active this.updateFilterUI(category); } } // Update filter UI to show selected category updateFilterUI(categoryId) { // Find the category object to get the display name const category = this.categories.find((c) => c.id === categoryId); const displayName = category ? category.name : categoryId; // Update filter button text to show active filter const filterToggle = document.getElementById("filter-toggle"); if (filterToggle) { const filterText = filterToggle.querySelector("span:last-child"); if (filterText) { filterText.textContent = `Filter: ${displayName}`; } } // Check the corresponding checkbox in the dropdown setTimeout(() => { const checkboxes = document.querySelectorAll(".category-checkbox"); checkboxes.forEach((checkbox) => { if (checkbox.value === categoryId) { checkbox.checked = true; } }); }, 100); } // Update filter button text based on selected categories updateFilterButtonText() { const filterToggle = document.getElementById("filter-toggle"); if (!filterToggle) return; const filterText = filterToggle.querySelector("span:last-child"); if (!filterText) return; // If no categories selected or "all" is selected, show default text if ( this.selectedCategories.size === 0 || this.selectedCategories.has("all") ) { filterText.textContent = "Filter"; return; } // Get the first selected category and find its display name const firstSelectedCategory = Array.from(this.selectedCategories)[0]; const category = this.categories.find( (c) => c.id === firstSelectedCategory ); if (category) { filterText.textContent = `Filter: ${category.name}`; } else { filterText.textContent = "Filter"; } } // Render products in the grid renderProducts() { const productGrid = document.getElementById("product-grid"); if (!productGrid) return; const startIndex = (this.currentPage - 1) * this.itemsPerPage; const endIndex = startIndex + this.itemsPerPage; const productsToShow = this.filteredProducts.slice(startIndex, endIndex); productGrid.innerHTML = productsToShow .map((product) => this.createProductCard(product)) .join(""); this.updateResultsCount(); this.updatePagination(); // Re-add image enlargement listeners and lazy loading after products are rendered setTimeout(() => { addImageEnlargementListeners(); this.initLazyLoading(); }, 50); } // Create individual product card HTML createProductCard(product) { // Check if we're in comparison mode const urlParams = new URLSearchParams(window.location.search); const returnTo = urlParams.get("returnTo"); const isComparisonMode = returnTo === "comparison"; return `
${product.alt}

${product.name}

${product.description}

`; } // Filter products by category filterByCategory(category) { // Single category helper (not used directly by UI) this.selectedCategories = new Set([category]); this.applyFilters(); this.currentPage = 1; this.renderProducts(); this.updatePagination(); } // Search products searchProducts(query) { if (!query.trim()) { this.filteredProducts = [...this.products]; } else { this.filteredProducts = this.products.filter( (product) => product.name.toLowerCase().includes(query.toLowerCase()) || product.description.toLowerCase().includes(query.toLowerCase()) ); } this.currentPage = 1; this.renderProducts(); this.updatePagination(); } // Apply selected category filters applyFilters() { if ( this.selectedCategories.size === 0 || this.selectedCategories.has("all") ) { this.filteredProducts = [...this.products]; return; } this.filteredProducts = this.products.filter((product) => this.selectedCategories.has(product.category) ); } // Sort products sortProducts(sortBy) { switch (sortBy) { case "name-asc": this.filteredProducts.sort((a, b) => a.name.localeCompare(b.name)); break; case "name-desc": this.filteredProducts.sort((a, b) => b.name.localeCompare(a.name)); break; default: // Default sorting by ID this.filteredProducts.sort((a, b) => a.id - b.id); } this.renderProducts(); } // Change page changePage(page) { this.currentPage = page; this.renderProducts(); this.updatePagination(); } // Update pagination controls updatePagination() { const totalPages = Math.ceil( this.filteredProducts.length / this.itemsPerPage ); const paginationContainer = document.getElementById("pagination"); if (!paginationContainer) return; let paginationHTML = ""; for (let i = 1; i <= totalPages; i++) { const isActive = i === this.currentPage; paginationHTML += ` `; } if (totalPages > 1 && this.currentPage < totalPages) { paginationHTML += ` `; } paginationContainer.innerHTML = paginationHTML; } // Build category multi-select dropdown renderCategoryFilters() { const container = document.getElementById("filter-categories"); if (!container) return; const categoryOptions = this.categories .map( (c) => ` ` ) .join(""); const allOption = ` `; container.innerHTML = allOption + categoryOptions; // Add event listeners for "All" checkbox behavior const allCheckbox = container.querySelector(".category-all"); const specificCheckboxes = container.querySelectorAll(".category-specific"); if (allCheckbox) { allCheckbox.addEventListener("change", (e) => { const isChecked = e.target.checked; specificCheckboxes.forEach((checkbox) => { checkbox.checked = isChecked; }); }); } // Update "All" checkbox when specific categories change specificCheckboxes.forEach((checkbox) => { checkbox.addEventListener("change", () => { const allChecked = Array.from(specificCheckboxes).every( (c) => c.checked ); const anyChecked = Array.from(specificCheckboxes).some( (c) => c.checked ); if (allChecked) { allCheckbox.checked = true; } else if (!anyChecked) { allCheckbox.checked = false; } }); }); } // View product details viewProduct(productId) { const product = this.products.find((p) => p.id === productId); if (product) { // Check if we're in comparison mode const urlParams = new URLSearchParams(window.location.search); const returnTo = urlParams.get("returnTo"); const slot = urlParams.get("slot"); const product1Id = urlParams.get("product1"); const product2Id = urlParams.get("product2"); if (returnTo === "comparison" && slot) { // Navigate back to comparison page with the selected product let comparisonUrl = "product-comparison.html?"; if (slot === "1") { // Replace product 1 comparisonUrl += `product1=${productId}`; if (product2Id) { comparisonUrl += `&product2=${product2Id}`; } } else { // Replace product 2 if (product1Id) { comparisonUrl += `product1=${product1Id}&`; } comparisonUrl += `product2=${productId}`; } console.log("Navigating to comparison page:", comparisonUrl); window.location.href = comparisonUrl; } else { // Normal mode - navigate to product detail page window.location.href = `product-detail.html?id=${productId}`; } } } // Setup event listeners setupEventListeners() { // Filter dropdown toggle and outside click const filterToggle = document.getElementById("filter-toggle"); const filterDropdown = document.getElementById("filter-dropdown"); if (filterToggle && filterDropdown) { filterToggle.addEventListener("click", () => { filterDropdown.classList.toggle("hidden"); }); document.addEventListener("click", (e) => { if ( !filterDropdown.contains(e.target) && !filterToggle.contains(e.target) ) { filterDropdown.classList.add("hidden"); } }); } // Sort dropdown const sortSelect = document.querySelector("select"); if (sortSelect) { sortSelect.addEventListener("change", (e) => { this.sortProducts(e.target.value); }); } // Apply/clear category filters const applyBtn = document.getElementById("filter-apply"); const clearBtn = document.getElementById("filter-clear"); if (applyBtn) { applyBtn.addEventListener("click", () => { const checks = Array.from( document.querySelectorAll(".category-checkbox") ); this.selectedCategories = new Set( checks.filter((c) => c.checked).map((c) => c.value) ); this.currentPage = 1; this.applyFilters(); this.renderProducts(); this.updatePagination(); this.updateResultsCount(); // Update filter button text to show selected category this.updateFilterButtonText(); const dropdown = document.getElementById("filter-dropdown"); if (dropdown) dropdown.classList.add("hidden"); }); } if (clearBtn) { clearBtn.addEventListener("click", () => { const checks = Array.from( document.querySelectorAll(".category-checkbox") ); checks.forEach((c) => (c.checked = false)); this.selectedCategories.clear(); this.currentPage = 1; this.applyFilters(); this.renderProducts(); this.updatePagination(); this.updateResultsCount(); // Update filter button text to show default this.updateFilterButtonText(); }); } // Initialize lazy loading this.initLazyLoading(); } // Initialize lazy loading for images initLazyLoading() { // Use Intersection Observer for better performance if ("IntersectionObserver" in window) { const imageObserver = new IntersectionObserver( (entries, observer) => { entries.forEach((entry) => { if (entry.isIntersecting) { const img = entry.target; if (img.dataset.src) { img.src = img.dataset.src; img.classList.remove("lazy-load"); img.classList.add("loaded"); observer.unobserve(img); } } }); }, { rootMargin: "50px 0px", // Start loading 50px before image comes into view threshold: 0.01, } ); // Observe all lazy-loaded images const lazyImages = document.querySelectorAll(".lazy-load"); lazyImages.forEach((img) => imageObserver.observe(img)); } else { // Fallback for older browsers - load all images immediately const lazyImages = document.querySelectorAll(".lazy-load"); lazyImages.forEach((img) => { if (img.dataset.src) { img.src = img.dataset.src; img.classList.remove("lazy-load"); img.classList.add("loaded"); } }); } } // Update results count updateResultsCount() { const resultsElement = document.querySelector(".text-quick-silver"); if (resultsElement) { const startIndex = (this.currentPage - 1) * this.itemsPerPage + 1; const endIndex = Math.min( startIndex + this.itemsPerPage - 1, this.filteredProducts.length ); resultsElement.textContent = `Showing ${startIndex}–${endIndex} of ${this.filteredProducts.length} results`; } } } // Initialize product manager const productManager = new ProductManager(); // Image enlargement modal functionality function addImageEnlargementListeners() { const productImages = document.querySelectorAll( "#product-grid img[data-enlarge-src]" ); const modal = document.getElementById("image-modal"); const modalImage = document.getElementById("modal-image"); const modalCloseBtn = document.getElementById("modal-close-btn"); if (!modal || !modalImage || !modalCloseBtn) return; productImages.forEach((img) => { img.addEventListener("click", function (e) { e.preventDefault(); // Prevent any default behavior e.stopPropagation(); // Stop event bubbling to parent elements const imageSrc = this.getAttribute("data-enlarge-src"); const imageAlt = this.getAttribute("alt"); modalImage.src = imageSrc; modalImage.alt = imageAlt; // Show modal with animation modal.classList.remove("hidden"); document.body.style.overflow = "hidden"; // Prevent background scrolling // Trigger animation after a brief delay setTimeout(() => { modalImage.classList.remove("scale-95"); modalImage.classList.add("scale-100"); }, 10); }); }); // Close modal functionality function closeModal() { // Animate out modalImage.classList.remove("scale-100"); modalImage.classList.add("scale-95"); // Hide modal after animation setTimeout(() => { modal.classList.add("hidden"); document.body.style.overflow = ""; // Restore scrolling }, 300); } modalCloseBtn.addEventListener("click", closeModal); // Close modal when clicking outside the image modal.addEventListener("click", function (e) { if (e.target === modal) { closeModal(); } }); // Close modal with Escape key document.addEventListener("keydown", function (e) { if (e.key === "Escape" && !modal.classList.contains("hidden")) { closeModal(); } }); } // Load products when DOM is ready document.addEventListener("DOMContentLoaded", () => { productManager.loadProducts(); // Add image enlargement listeners after products are loaded setTimeout(() => { addImageEnlargementListeners(); }, 100); });