// Initialize mobile menu functionality function initMobileMenu() { const menuButton = document.querySelector('[data-landingsite-mobile-menu-toggle]'); const mobileMenu = document.querySelector('[data-landingsite-mobile-menu]'); if (menuButton && mobileMenu) { // Remove any existing event listeners to prevent duplicates const newMenuButton = menuButton.cloneNode(true); menuButton.parentNode.replaceChild(newMenuButton, menuButton); // Add fresh event listener for menu button newMenuButton.addEventListener('click', function() { mobileMenu.classList.toggle('hidden'); // Update aria-expanded attribute for accessibility const isExpanded = !mobileMenu.classList.contains('hidden'); newMenuButton.setAttribute('aria-expanded', isExpanded ? 'true' : 'false'); }); // Close mobile menu when any navigation link is clicked const mobileNavLinks = mobileMenu.querySelectorAll('a'); mobileNavLinks.forEach(link => { link.addEventListener('click', function() { // Close the mobile menu mobileMenu.classList.add('hidden'); newMenuButton.setAttribute('aria-expanded', 'false'); }); }); } } // Shop status functionality using South African Standard Time (SAST) function initShopStatus() { const shopHours = { 0: { // Shop 1 - Bikini Brew monday: { open: '06:00', close: '18:00' }, tuesday: { open: '06:00', close: '18:00' }, wednesday: { open: '06:00', close: '18:00' }, thursday: { open: '06:00', close: '18:00' }, friday: { open: '06:00', close: '18:00' }, saturday: { open: '07:00', close: '15:00' }, sunday: { open: '07:00', close: '14:00' } }, 1: { // Shop 2 - Harties FM (24/7 Live Radio) monday: { open: '00:00', close: '23:59' }, tuesday: { open: '00:00', close: '23:59' }, wednesday: { open: '00:00', close: '23:59' }, thursday: { open: '00:00', close: '23:59' }, friday: { open: '00:00', close: '23:59' }, saturday: { open: '00:00', close: '23:59' }, sunday: { open: '00:00', close: '23:59' } }, 2: { // Shop 3 - Mibot (PTY) Ltd monday: { open: '08:00', close: '17:00' }, tuesday: { open: '08:00', close: '17:00' }, wednesday: { open: '08:00', close: '17:00' }, thursday: { open: '08:00', close: '17:00' }, friday: { open: '08:00', close: '17:00' }, saturday: null, // Weekends: Closed sunday: null }, 3: { // Shop 4 - JJR Motors monday: { open: '07:30', close: '17:30' }, tuesday: { open: '07:30', close: '17:30' }, wednesday: { open: '07:30', close: '17:30' }, thursday: { open: '07:30', close: '17:30' }, friday: { open: '07:30', close: '17:30' }, saturday: { open: '07:30', close: '13:30' }, sunday: null // Sun: Closed }, 4: { // Shop 5 - Property.CoZa monday: { open: '09:00', close: '17:00' }, tuesday: { open: '09:00', close: '17:00' }, wednesday: { open: '09:00', close: '17:00' }, thursday: { open: '09:00', close: '17:00' }, friday: { open: '09:00', close: '17:00' }, saturday: { open: '09:00', close: '14:00' }, sunday: { open: '09:00', close: '14:00' } }, 5: { // Shop 6 - Battcol Batteries (24/7 per HTML) monday: { open: '00:00', close: '23:59' }, tuesday: { open: '00:00', close: '23:59' }, wednesday: { open: '00:00', close: '23:59' }, thursday: { open: '00:00', close: '23:59' }, friday: { open: '00:00', close: '23:59' }, saturday: { open: '00:00', close: '23:59' }, sunday: { open: '00:00', close: '23:59' } }, 6: { // Shop 7 - Bodytec monday: { open: '06:00', close: '18:00' }, tuesday: { open: '06:00', close: '18:00' }, wednesday: { open: '06:00', close: '18:00' }, thursday: { open: '06:00', close: '18:00' }, friday: { open: '06:00', close: '18:00' }, saturday: { open: '07:00', close: '13:00' }, sunday: null // Closed }, 7: { // Shop 8 - Megga Water Investment monday: { open: '08:00', close: '18:00' }, tuesday: { open: '08:00', close: '18:00' }, wednesday: { open: '08:00', close: '18:00' }, thursday: { open: '08:00', close: '18:00' }, friday: { open: '08:00', close: '18:00' }, saturday: { open: '08:00', close: '13:00' }, sunday: null // Closed }, 8: { // Shop 9 - The Joint Wellness Shop monday: { open: '09:00', close: '18:00' }, tuesday: { open: '09:00', close: '18:00' }, wednesday: { open: '09:00', close: '18:00' }, thursday: { open: '09:00', close: '18:00' }, friday: { open: '09:00', close: '18:00' }, saturday: { open: '09:00', close: '18:00' }, sunday: { open: '09:00', close: '16:00' } }, 9: { // Shop 10 - Obaro Retail (PTY) Ltd monday: { open: '08:00', close: '17:00' }, tuesday: { open: '08:00', close: '17:00' }, wednesday: { open: '08:00', close: '17:00' }, thursday: { open: '08:00', close: '17:00' }, friday: { open: '08:00', close: '17:00' }, saturday: { open: '08:00', close: '13:00' }, sunday: { open: '09:00', close: '13:00' } }, 10: { // Shop 11 - The Barber monday: { open: '08:00', close: '17:00' }, tuesday: { open: '08:00', close: '17:00' }, wednesday: { open: '08:00', close: '17:00' }, thursday: { open: '08:00', close: '17:00' }, friday: { open: '08:00', close: '17:00' }, saturday: { open: '08:00', close: '14:00' }, sunday: null // Closed }, 11: { // Shop 12 - AK Pro Hair Studio (Coming Soon) monday: null, tuesday: null, wednesday: null, thursday: null, friday: null, saturday: null, sunday: null // Coming Soon - all days closed }, 12: { // Shop 13 - Hannon Beauty (no specific hours listed, assume standard business hours) monday: { open: '09:00', close: '17:00' }, tuesday: { open: '09:00', close: '17:00' }, wednesday: { open: '09:00', close: '17:00' }, thursday: { open: '09:00', close: '17:00' }, friday: { open: '09:00', close: '17:00' }, saturday: { open: '09:00', close: '15:00' }, sunday: null // Closed }, 13: { // Shop 14 - Regus (24/7) monday: { open: '00:00', close: '23:59' }, tuesday: { open: '00:00', close: '23:59' }, wednesday: { open: '00:00', close: '23:59' }, thursday: { open: '00:00', close: '23:59' }, friday: { open: '00:00', close: '23:59' }, saturday: { open: '00:00', close: '23:59' }, sunday: { open: '00:00', close: '23:59' } }, 14: { // Shop 15 - PG Glass monday: { open: '08:00', close: '17:00' }, tuesday: { open: '08:00', close: '17:00' }, wednesday: { open: '08:00', close: '17:00' }, thursday: { open: '08:00', close: '17:00' }, friday: { open: '08:00', close: '17:00' }, saturday: { open: '08:00', close: '12:00' }, sunday: null // Closed }, 15: { // Shop 16 - Base Jump CC monday: { open: '08:30', close: '16:00' }, tuesday: { open: '08:30', close: '16:00' }, wednesday: { open: '08:30', close: '16:00' }, thursday: { open: '08:30', close: '16:00' }, friday: { open: '08:30', close: '16:00' }, saturday: { open: '08:30', close: '13:00' }, sunday: null // Closed }, 16: { // Shop 17 - Enyi Chinese Restaurant monday: { open: '10:30', close: '21:00' }, tuesday: { open: '10:30', close: '21:00' }, wednesday: { open: '10:30', close: '21:00' }, thursday: { open: '10:30', close: '21:00' }, friday: { open: '10:30', close: '21:00' }, saturday: { open: '10:30', close: '21:00' }, sunday: { open: '10:30', close: '21:00' } }, 17: { // Shop 18 - Debt in Assistance monday: { open: '09:00', close: '17:00' }, tuesday: { open: '09:00', close: '17:00' }, wednesday: { open: '09:00', close: '17:00' }, thursday: { open: '09:00', close: '17:00' }, friday: { open: '09:00', close: '17:00' }, saturday: null, // By appointment sunday: null // By appointment } }; function getSouthAfricanTime() { // Get current time in South African Standard Time (SAST - UTC+2) const now = new Date(); const utc = now.getTime() + (now.getTimezoneOffset() * 60000); const sast = new Date(utc + (2 * 3600000)); // UTC+2 return sast; } function getDayName(dayIndex) { const days = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday']; return days[dayIndex]; } function timeToMinutes(timeStr) { const [hours, minutes] = timeStr.split(':').map(Number); return hours * 60 + minutes; } function formatCountdown(minutes) { if (minutes < 60) { return minutes + 'm'; } const hours = Math.floor(minutes / 60); const mins = minutes % 60; return mins > 0 ? hours + 'h ' + mins + 'm' : hours + 'h'; } function getNextOpenDay(shopIndex, currentDay, currentDate) { const shop = shopHours[shopIndex]; const days = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday']; for (let i = 1; i <= 7; i++) { const nextDate = new Date(currentDate); nextDate.setDate(nextDate.getDate() + i); const nextDayName = getDayName(nextDate.getDay()); if (shop[nextDayName]) { return { day: nextDayName, hours: shop[nextDayName], daysAhead: i }; } } return null; } function updateShopStatus(shopIndex) { const now = getSouthAfricanTime(); // Use SAST instead of local time const currentDay = getDayName(now.getDay()); const currentMinutes = now.getHours() * 60 + now.getMinutes(); // Find the shop card const shopCards = document.querySelectorAll('.grid .group'); const shopCard = shopCards[shopIndex]; if (!shopCard || !shopHours[shopIndex]) return; const statusBadge = shopCard.querySelector('.absolute.top-4.left-3, .absolute.top-3.left-3'); if (!statusBadge) return; const statusSpans = statusBadge.querySelectorAll('span'); const statusText = statusSpans[statusSpans.length - 1]; if (!statusText) return; const todayHours = shopHours[shopIndex][currentDay]; if (!todayHours) { // Shop is closed today - find next opening statusBadge.className = statusBadge.className.replace(/bg-green-500|bg-amber-500|bg-sky-500|bg-cyan-500/g, 'bg-red-500'); const nextOpen = getNextOpenDay(shopIndex, currentDay, now); if (nextOpen) { if (nextOpen.daysAhead === 1) { const openTime = nextOpen.hours.open; const [hours, minutes] = openTime.split(':'); const displayHour = parseInt(hours) > 12 ? parseInt(hours) - 12 : (parseInt(hours) === 0 ? 12 : parseInt(hours)); const ampm = parseInt(hours) >= 12 ? 'PM' : 'AM'; statusText.textContent = 'OPENS ' + displayHour + (minutes !== '00' ? ':' + minutes : '') + ampm + ' TMR'; } else { const dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; const nextDayIndex = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'].indexOf(nextOpen.day); statusText.textContent = 'OPENS ' + dayNames[nextDayIndex]; } } else { // Handle special cases if (shopIndex === 11) { // AK Pro Hair Studio statusText.textContent = 'COMING SOON'; } else { statusText.textContent = 'CLOSED'; } } return; } const openMinutes = timeToMinutes(todayHours.open); const closeMinutes = timeToMinutes(todayHours.close); // Handle 24/7 shops (like Harties FM, Battcol, and Regus) if (openMinutes === 0 && closeMinutes === 1439) { statusBadge.className = statusBadge.className.replace(/bg-red-500|bg-amber-500|bg-cyan-500/g, 'bg-green-500'); if (shopIndex === 1) { // Harties FM statusText.textContent = 'LIVE'; } else if (shopIndex === 13) { // Regus statusText.textContent = '24/7 OPEN'; } else { statusText.textContent = 'OPEN 24/7'; } return; } if (currentMinutes >= openMinutes && currentMinutes < closeMinutes) { // Shop is OPEN const minutesUntilClose = closeMinutes - currentMinutes; if (minutesUntilClose <= 30) { // Closing soon - show amber warning statusBadge.className = statusBadge.className.replace(/bg-green-500|bg-red-500|bg-cyan-500/g, 'bg-amber-500'); statusText.textContent = 'CLOSES ' + formatCountdown(minutesUntilClose); } else if (minutesUntilClose <= 120) { // Within 2 hours of closing statusBadge.className = statusBadge.className.replace(/bg-red-500|bg-amber-500|bg-cyan-500/g, 'bg-green-500'); statusText.textContent = 'CLOSES ' + formatCountdown(minutesUntilClose); } else { statusBadge.className = statusBadge.className.replace(/bg-red-500|bg-amber-500|bg-cyan-500/g, 'bg-green-500'); statusText.textContent = 'OPEN'; } } else { // Shop is CLOSED statusBadge.className = statusBadge.className.replace(/bg-green-500|bg-amber-500|bg-cyan-500/g, 'bg-red-500'); if (currentMinutes < openMinutes) { // Before opening time today const minutesUntilOpen = openMinutes - currentMinutes; if (minutesUntilOpen <= 60) { statusText.textContent = 'OPENS ' + formatCountdown(minutesUntilOpen); } else { const openTime = todayHours.open; const [hours, minutes] = openTime.split(':'); const displayHour = parseInt(hours) > 12 ? parseInt(hours) - 12 : (parseInt(hours) === 0 ? 12 : parseInt(hours)); const ampm = parseInt(hours) >= 12 ? 'PM' : 'AM'; statusText.textContent = 'OPENS ' + displayHour + (minutes !== '00' ? ':' + minutes : '') + ampm; } } else { // After closing time - show when opens next const nextOpen = getNextOpenDay(shopIndex, currentDay, now); if (nextOpen) { if (nextOpen.daysAhead === 1) { const openTime = nextOpen.hours.open; const [hours, minutes] = openTime.split(':'); const displayHour = parseInt(hours) > 12 ? parseInt(hours) - 12 : (parseInt(hours) === 0 ? 12 : parseInt(hours)); const ampm = parseInt(hours) >= 12 ? 'PM' : 'AM'; statusText.textContent = 'OPENS ' + displayHour + (minutes !== '00' ? ':' + minutes : '') + ampm + ' TMR'; } else { const dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; const nextDayIndex = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'].indexOf(nextOpen.day); statusText.textContent = 'OPENS ' + dayNames[nextDayIndex]; } } else { statusText.textContent = 'CLOSED'; } } } } function updateAllShopsStatus() { // Update all 18 shops (indices 0-17) for (let i = 0; i <= 17; i++) { updateShopStatus(i); } // Debug logging with SAST time const now = getSouthAfricanTime(); console.log('Shops Status Update (SAST):', { currentTime: now.getHours() + ':' + String(now.getMinutes()).padStart(2, '0'), currentDay: getDayName(now.getDay()), timestamp: now.toLocaleString('en-ZA', { timeZone: 'Africa/Johannesburg' }) }); } // Initialize and start updating updateAllShopsStatus(); // Update every minute const updateInterval = setInterval(updateAllShopsStatus, 60000); // Also update every 10 seconds for more real-time feel const frequentUpdateInterval = setInterval(updateAllShopsStatus, 10000); // Store intervals for cleanup window.shopStatusIntervals = { minute: updateInterval, frequent: frequentUpdateInterval }; } let mobileMenuClickHandler = null; // Export init function that will be called after page loads export function init() { // Small delay to ensure DOM is fully ready setTimeout(() => { initMobileMenu(); initShopStatus(); }, 100); } // Export teardown function for cleanup export function teardown() { // Clear shop status intervals if (window.shopStatusIntervals) { clearInterval(window.shopStatusIntervals.minute); clearInterval(window.shopStatusIntervals.frequent); delete window.shopStatusIntervals; } // Cleanup mobile menu handler if needed if (mobileMenuClickHandler) { const menuButton = document.querySelector('[data-landingsite-mobile-menu-toggle]'); if (menuButton) { menuButton.removeEventListener('click', mobileMenuClickHandler); } mobileMenuClickHandler = null; } }