1// Mobile Menu Toggle 2const initMobileMenu = () => { 3 const mobileMenuBtn = document.getElementById('mobileMenuBtn'); 4 const mobileMenu = document.getElementById('mobileMenu'); 5 6 if (mobileMenuBtn && mobileMenu) { 7 mobileMenuBtn.addEventListener('click', (e) => { 8 e.preventDefault(); 9 e.stopPropagation(); 10 console.log('Mobile menu button clicked'); // Debug log 11 mobileMenu.classList.toggle('hidden'); 12 13 // Change hamburger to X when menu is open 14 const isOpen = !mobileMenu.classList.contains('hidden'); 15 mobileMenuBtn.innerHTML = isOpen 16 ? '<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>' 17 : '<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/></svg>'; 18 19 // Toggle body scroll 20 document.body.style.overflow = isOpen ? 'hidden' : ''; 21 }); 22 23 // Close menu when clicking outside 24 document.addEventListener('click', (e) => { 25 if (!mobileMenu.contains(e.target) && !mobileMenuBtn.contains(e.target) && !mobileMenu.classList.contains('hidden')) { 26 mobileMenu.classList.add('hidden'); 27 document.body.style.overflow = ''; 28 } 29 }); 30 } 31}; 32 33// Category navigation scroll function 34function scrollCategories(direction) { 35 const container = document.getElementById('categoryContainer'); 36 if (!container) return; 37 38 const scrollAmount = 300; 39 if (direction === 'left') { 40 container.scrollBy({ 41 left: -scrollAmount, 42 behavior: 'smooth' 43 }); 44 } else { 45 container.scrollBy({ 46 left: scrollAmount, 47 behavior: 'smooth' 48 }); 49 } 50} 51 52// AJAX Search functionality 53const initSearch = () => { 54 const searchInput = document.getElementById('searchInput'); 55 const searchResults = document.getElementById('searchResults'); 56 let searchTimeout; 57 58 if (searchInput && searchResults) { 59 searchInput.addEventListener('input', function() { 60 clearTimeout(searchTimeout); 61 const query = this.value.trim(); 62 63 if (query.length < 2) { 64 searchResults.innerHTML = ''; 65 searchResults.classList.add('hidden'); 66 return; 67 } 68 69 searchTimeout = setTimeout(() => { 70 fetch(`ajax_search.php?q=${encodeURIComponent(query)}`) 71 .then(response => response.json()) 72 .then(data => { 73 if (data.success && data.results.length > 0) { 74 searchResults.innerHTML = data.results.map(deal => ` 75 <a href="${deal.url}" target="_blank" rel="noopener noreferrer" 76 class="block px-4 py-2 hover:bg-gray-50 border-b last:border-b-0"> 77 <div class="flex items-center"> 78 <img src="${deal.image}" 79 alt="${deal.title}" 80 class="w-12 h-12 object-contain mr-3"> 81 <div class="flex-1 min-w-0"> 82 <div class="text-sm font-medium text-gray-900 truncate">${deal.title}</div> 83 <div class="text-sm text-gray-500">${deal.brand}</div> 84 <div class="flex items-center mt-1"> 85 <span class="text-red-600 font-bold">$${deal.price}</span> 86 <span class="ml-2 text-green-600">-${deal.discount}%</span> 87 </div> 88 </div> 89 </div> 90 </a> 91 `).join(''); 92 searchResults.classList.remove('hidden'); 93 } else { 94 searchResults.innerHTML = ` 95 <div class="px-4 py-3 text-sm text-gray-500"> 96 No deals found for "${query}" 97 </div> 98 `; 99 searchResults.classList.remove('hidden'); 100 } 101 }) 102 .catch(error => { 103 console.error('Search error:', error); 104 searchResults.innerHTML = ` 105 <div class="px-4 py-3 text-sm text-red-500"> 106 Error performing search. Please try again. 107 </div> 108 `; 109 searchResults.classList.remove('hidden'); 110 }); 111 }, 300); 112 }); 113 114 // Hide search results when clicking outside 115 document.addEventListener('click', function(e) { 116 if (!searchInput.contains(e.target) && !searchResults.contains(e.target)) { 117 searchResults.classList.add('hidden'); 118 } 119 }); 120 } 121}; 122 123// Table sorting functionality 124const tableSortDirections = {}; 125 126function sortTable(tableId, columnIndex, type = 'text') { 127 const table = document.getElementById(tableId); 128 const tbody = table.querySelector('tbody'); 129 const rows = Array.from(tbody.querySelectorAll('tr')); 130 131 // Toggle sort direction for this column 132 if (!tableSortDirections[tableId]) { 133 tableSortDirections[tableId] = {}; 134 } 135 tableSortDirections[tableId][columnIndex] = tableSortDirections[tableId][columnIndex] === 'asc' ? 'desc' : 'asc'; 136 137 // Sort the rows 138 rows.sort((a, b) => { 139 let aValue, bValue; 140 141 if (type === 'number') { 142 // Get numeric values from data-value attributes 143 aValue = parseFloat(a.children[columnIndex].getAttribute('data-value')) || 0; 144 bValue = parseFloat(b.children[columnIndex].getAttribute('data-value')) || 0; 145 } else { 146 // Get text content for text comparison 147 aValue = a.children[columnIndex].textContent.trim().toLowerCase(); 148 bValue = b.children[columnIndex].textContent.trim().toLowerCase(); 149 } 150 151 if (type === 'number') { 152 return tableSortDirections[tableId][columnIndex] === 'asc' ? aValue - bValue : bValue - aValue; 153 } else { 154 return tableSortDirections[tableId][columnIndex] === 'asc' 155 ? aValue.localeCompare(bValue) 156 : bValue.localeCompare(aValue); 157 } 158 }); 159 160 // Remove existing rows 161 rows.forEach(row => tbody.removeChild(row)); 162 163 // Add sorted rows 164 rows.forEach(row => tbody.appendChild(row)); 165 166 // Update sort indicators 167 const headers = table.querySelectorAll('th');
168 headers.forEach((header, index) => { 169 header.classList.remove('sorted-asc', 'sorted-desc'); 170 if (index === columnIndex) { 171 header.classList.add(tableSortDirections[tableId][columnIndex] === 'asc' ? 'sorted-asc' : 'sorted-desc'); 172 } 173 }); 174} 175 176// Initialize table sorting 177const initTableSorting = () => { 178 // Find all tables and initialize their sorting 179 document.querySelectorAll('table[id^="dealsTable_"]').forEach(table => { 180 const tableId = table.id; 181 if (!tableSortDirections[tableId]) { 182 tableSortDirections[tableId] = {}; 183 } 184 // Set initial sort direction for savings column 185 tableSortDirections[tableId][3] = 'asc'; // This will be toggled to 'desc' by sortTable 186 sortTable(tableId, 3, 'number'); 187 }); 188}; 189 190// View toggle functionality 191function toggleView() { 192 const cardViews = document.querySelectorAll('.card-view'); 193 const tableViews = document.querySelectorAll('.table-view'); 194 const viewIcon = document.getElementById('viewIcon'); 195 196 const preferredView = localStorage.getItem('preferredView') || 'card'; 197 const newView = preferredView === 'card' ? 'table' : 'card'; 198 199 cardViews.forEach(cardView => { 200 if (newView === 'table') { 201 cardView.classList.add('hidden'); 202 } else { 203 cardView.classList.remove('hidden'); 204 } 205 }); 206 207 tableViews.forEach(tableView => { 208 if (newView === 'table') { 209 tableView.classList.remove('hidden'); 210 } else { 211 tableView.classList.add('hidden'); 212 } 213 }); 214 215 // Update icon 216 if (newView === 'table') { 217 viewIcon.classList.remove('fa-list'); 218 viewIcon.classList.add('fa-th-large'); 219 } else { 220 viewIcon.classList.remove('fa-th-large'); 221 viewIcon.classList.add('fa-list'); 222 } 223 224 localStorage.setItem('preferredView', newView); 225} 226 227// Dark mode toggle 228function toggleDarkMode() { 229 const body = document.body; 230 const darkModeIcon = document.getElementById('darkModeIcon'); 231 232 body.classList.toggle('dark'); 233 if (body.classList.contains('dark')) { 234 darkModeIcon.classList.remove('fa-moon'); 235 darkModeIcon.classList.add('fa-sun'); 236 localStorage.setItem('darkMode', 'enabled'); 237 } else { 238 darkModeIcon.classList.remove('fa-sun'); 239 darkModeIcon.classList.add('fa-moon'); 240 localStorage.setItem('darkMode', 'disabled'); 241 } 242} 243 244// Initialize everything when DOM is ready 245document.addEventListener('DOMContentLoaded', () => { 246 console.log('Initializing...'); // Debug log 247 initMobileMenu(); 248 initSearch(); 249 initTableSorting(); 250 251 if (localStorage.getItem('darkMode') === 'enabled') { 252 toggleDarkMode(); 253 } 254});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.