Файловый менеджер - Редактировать - /home/gqdcvggs/vertchasseur.com/shop.php
Назад
<?php include 'header.php'; include 'db.php'; function parseShopHours($hours, $days) { list($openHour, $closeHour) = explode(' à ', $hours); $daysArray = explode(',', $days); $daysArray = array_map('trim', $daysArray); $isMorningOpen = strtotime($openHour) <= strtotime('10:00'); $isLateOpen = strtotime($closeHour) >= strtotime('20:00'); $isWeekendOpen = in_array('Saturday', $daysArray) || in_array('Sunday', $daysArray); $schedule = []; $allDays = ['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday']; $dayTranslation = [ 'Monday' => 'Lundi', 'Tuesday' => 'Mardi', 'Wednesday' => 'Mercredi', 'Thursday' => 'Jeudi', 'Friday' => 'Vendredi', 'Saturday' => 'Samedi', 'Sunday' => 'Dimanche' ]; foreach ($allDays as $day) { if (in_array($day, $daysArray)) { $schedule[$dayTranslation[$day]] = [$openHour . ' - ' . $closeHour]; } else { $schedule[$dayTranslation[$day]] = ['Fermé']; } } return [ 'open_hour' => $openHour, 'close_hour' => $closeHour, 'open_days' => $daysArray, 'morning_open' => $isMorningOpen, 'late_open' => $isLateOpen, 'weekend_open' => $isWeekendOpen, 'formatted_schedule' => $schedule ]; } $shops = mysqli_query($conn, "SELECT * FROM shop WHERE active = 1 ORDER BY price_smilemore DESC, title ASC"); $shopsList = []; while ($shop = mysqli_fetch_assoc($shops)) { $shopsList[] = $shop; } mysqli_data_seek($shops, 0); $shopsJson = json_encode($shopsList); $zoneData = [ 'center' => ['lat' => 50.800740898699665, 'lng' => 4.370884895324708, 'zoom' => 16], 'zone' => [ ['lat' => 50.80547367922133, 'lng' => 4.376249313354493], ['lat' => 50.801961432385966, 'lng' => 4.377751350402833], ['lat' => 50.79931690243816, 'lng' => 4.378888607025147], ['lat' => 50.796712911337075, 'lng' => 4.379446506500245], ['lat' => 50.79622464685215, 'lng' => 4.374210834503175], ['lat' => 50.79600763877701, 'lng' => 4.367022514343263], ['lat' => 50.797282546788885, 'lng' => 4.366786479949952], ['lat' => 50.79747242372991, 'lng' => 4.365756511688233], ['lat' => 50.79846248385208, 'lng' => 4.364705085754395], ['lat' => 50.79934402658185, 'lng' => 4.363932609558106], ['lat' => 50.801134185257695, 'lng' => 4.362559318542481], ['lat' => 50.80212416780201, 'lng' => 4.362301826477052], ['lat' => 50.80304632445409, 'lng' => 4.362366199493409], ['lat' => 50.80373792999973, 'lng' => 4.3625807762146005], ['lat' => 50.80455157047645, 'lng' => 4.367430210113526], ['lat' => 50.80506686878319, 'lng' => 4.370605945587159], ['lat' => 50.80532451580529, 'lng' => 4.372129440307618], ['lat' => 50.80546011893046, 'lng' => 4.373846054077149] ] ]; ?> <!DOCTYPE html> <html lang="fr"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Commerces - Vert Chasseur</title> <link rel="icon" type="image/png" href="logo_new.png"> <meta name="description" content="Découvrez tous les commerces et restaurants partenaires dans le quartier de Vert Chasseur à Uccle, Bruxelles."> <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" /> <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script> <script src="https://cdn.tailwindcss.com"></script> <script> tailwind.config = { darkMode: 'class', theme: { extend: {} } } </script> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Princess+Sofia&display=swap" rel="stylesheet"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"> <style> body { font-family: 'Poppins', sans-serif; letter-spacing: -0.01em; } .card { animation: fadeIn 0.5s ease-out forwards; opacity: 0; transition: all 0.4s cubic-bezier(0.165, 0.84, 0.44, 1); } .card:hover { transform: translateY(-8px); } @keyframes fadeIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } .status-dot { animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; } @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } } .txt-chasseur { font-family: "Princess Sofia", serif; font-weight: 400; font-style: normal; } .shop-image { transition: transform 0.7s ease; } .card:hover .shop-image { transform: scale(1.05); } .map-container { position: relative; width: 100%; height: 450px; border-radius: 20px; overflow: hidden; opacity: 0; transform: scale(0.95); transition: all 0.6s cubic-bezier(0.165, 0.84, 0.44, 1); } .map-container.visible { opacity: 1; transform: scale(1); } .map-container.hidden { display: none; } #map { width: 100%; height: 100%; } .location-dot { width: 22px; height: 22px; background: linear-gradient(135deg, #0ea5e9 0%, #06b6d4 100%); border-radius: 50%; border: 4px solid white; box-shadow: 0 0 0 3px rgba(14, 165, 233, 0.4); animation: locationPulse 2.5s infinite; } @keyframes locationPulse { 0% { box-shadow: 0 0 0 0 rgba(14, 165, 233, 0.8); } 70% { box-shadow: 0 0 0 20px rgba(14, 165, 233, 0); } 100% { box-shadow: 0 0 0 0 rgba(14, 165, 233, 0); } } .blip-icon { width: 48px; height: 48px; background: white; border: 3px solid #0ea5e9; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 20px; color: #0ea5e9; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); cursor: pointer; transition: all 0.3s cubic-bezier(0.165, 0.84, 0.44, 1); } .blip-icon:hover { transform: scale(1.2); box-shadow: 0 8px 24px rgba(14, 165, 233, 0.4); } .dark .blip-icon { background: #1a1a1a; border-color: #0ea5e9; } .leaflet-popup-content-wrapper { background: white; border-radius: 16px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15); border: none; color: black; } .dark .leaflet-popup-content-wrapper { background: #1a1a1a; color: white; } .dark .leaflet-tile { filter: invert(0.93) hue-rotate(180deg) brightness(1.05) contrast(0.9); } .leaflet-control-container { display: none; } .closing-soon-badge { background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); animation: urgentPulse 1.5s ease-in-out infinite; } @keyframes urgentPulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.05); } } </style> </head> <body class="bg-white dark:bg-black text-black dark:text-white transition-colors duration-300"> <script> if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { document.documentElement.classList.add('dark'); } window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => { if (e.matches) { document.documentElement.classList.add('dark'); } else { document.documentElement.classList.remove('dark'); } }); </script> <main class="container mx-auto px-4 py-16 max-w-6xl"> <header class="text-center mb-16 mt-20"> <p class="text-gray-500 dark:text-gray-400 text-sm tracking-wider uppercase mb-3">Uccle · Bruxelles</p> <h1 class="text-4xl md:text-6xl font-light mb-5 text-black dark:text-white"> <span class="txt-chasseur">Commerces</span> à Vert Chasseur </h1> <p class="text-lg text-gray-600 dark:text-gray-300 leading-relaxed max-w-2xl mx-auto"> Découvrez les enseignes qui font la richesse du quartier </p> <div class="text-center mt-4 mb-8"> <p class="text-gray-600 dark:text-gray-300 text-sm"> Votre commerce n'est pas dans la liste ? <a href="https://imators.systems/comm-access" class="text-black dark:text-white font-medium hover:underline transition-all duration-300"> Ajouter le via Comm Access ! </a> </p> </div> </header> <div id="mapSection" class="mb-16 map-container hidden bg-gradient-to-br from-gray-100 to-gray-200 dark:from-gray-900 dark:to-gray-800 shadow-2xl dark:shadow-gray-900/50"> <div id="map"></div> </div> <div class="space-y-8 mb-16"> <div class="relative max-w-xl mx-auto"> <input type="text" id="shop-search" placeholder="Rechercher un commerce..." class="w-full px-6 py-4 rounded-2xl bg-white dark:bg-black shadow-sm dark:shadow-none border border-gray-200 dark:border-gray-800 focus:border-gray-400 dark:focus:border-gray-600 focus:outline-none text-black dark:text-white text-lg"> <i class="fas fa-search absolute right-6 top-1/2 transform -translate-y-1/2 text-gray-400 dark:text-gray-500"></i> </div> <div class="flex flex-wrap justify-center gap-4 mt-8"> <button id="filter-open-now" class="px-5 py-3 rounded-xl transition-all duration-300 hover:shadow-lg text-sm font-medium bg-white text-black dark:bg-black dark:text-white border border-gray-200 dark:border-gray-800"> <i class="fas fa-door-open mr-2"></i>Ouvert maintenant </button> <button id="filter-last-minute" class="px-5 py-3 rounded-xl transition-all duration-300 hover:shadow-lg text-sm font-medium bg-white text-black dark:bg-black dark:text-white border border-gray-200 dark:border-gray-800"> <i class="fas fa-clock mr-2"></i>Dernières minutes </button> <button id="filter-no-reservation" class="px-5 py-3 rounded-xl transition-all duration-300 hover:shadow-lg text-sm font-medium bg-white text-black dark:bg-black dark:text-white border border-gray-200 dark:border-gray-800"> <i class="fas fa-walking mr-2"></i>Sans réservation </button> <button id="filter-morning" class="px-5 py-3 rounded-xl transition-all duration-300 hover:shadow-lg text-sm font-medium bg-white text-black dark:bg-black dark:text-white border border-gray-200 dark:border-gray-800"> <i class="fas fa-sun mr-2"></i>Ouvert le matin </button> <button id="filter-late" class="px-5 py-3 rounded-xl transition-all duration-300 hover:shadow-lg text-sm font-medium bg-white text-black dark:bg-black dark:text-white border border-gray-200 dark:border-gray-800"> <i class="fas fa-moon mr-2"></i>Ouvert après 20h </button> <button id="filter-weekend" class="px-5 py-3 rounded-xl transition-all duration-300 hover:shadow-lg text-sm font-medium bg-white text-black dark:bg-black dark:text-white border border-gray-200 dark:border-gray-800"> <i class="fas fa-calendar mr-2"></i>Ouvert le week-end </button> <button id="filter-delivery" class="px-5 py-3 rounded-xl transition-all duration-300 hover:shadow-lg text-sm font-medium bg-white text-black dark:bg-black dark:text-white border border-gray-200 dark:border-gray-800"> <i class="fas fa-truck mr-2"></i>Livraison privée </button> </div> </div> <div class="grid gap-8 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3"> <?php $delay = 0; $shopCount = 0; while($shop = mysqli_fetch_assoc($shops)): $shopCount++; $openingInfo = parseShopHours($shop['hour_open'], $shop['day_open']); $mainImage = trim(explode(',', trim($shop['url_image'], '()'))[0]); ?> <article class="card bg-white dark:bg-black rounded-3xl overflow-hidden border border-gray-100 dark:border-gray-800 shadow-lg dark:shadow-gray-900/30" data-shop data-shop-id="<?= $shop['id'] ?>" data-open-hour="<?= htmlspecialchars($openingInfo['open_hour']) ?>" data-close-hour="<?= htmlspecialchars($openingInfo['close_hour']) ?>" data-open-days='<?= json_encode($openingInfo['open_days']) ?>' data-title="<?= htmlspecialchars($shop['title']) ?>" data-description="<?= htmlspecialchars($shop['description']) ?>" data-morning="<?= $openingInfo['morning_open'] ? 'true' : 'false' ?>" data-late="<?= $openingInfo['late_open'] ? 'true' : 'false' ?>" data-weekend="<?= $openingInfo['weekend_open'] ? 'true' : 'false' ?>" data-delivery="<?= $shop['delivery_possible'] == 1 ? 'true' : 'false' ?>" data-no-reservation="<?= $shop['reservation_taked'] == 0 ? 'true' : 'false' ?>" data-category="<?= htmlspecialchars($shop['category']) ?>" data-latitude="<?= htmlspecialchars($shop['latitude']) ?>" data-longitude="<?= htmlspecialchars($shop['longitude']) ?>" style="animation-delay: <?= $delay ?>s"> <div class="aspect-[4/3] relative overflow-hidden"> <img src="<?= htmlspecialchars($mainImage) ?>" alt="<?= htmlspecialchars($shop['title']) ?>" class="w-full h-full object-cover shop-image"> <div class="absolute inset-0 bg-gradient-to-t from-black/60 via-black/20 to-transparent"></div> <?php if($shop['price_smilemore'] == 1): ?> <div class="absolute top-4 right-4 bg-white/10 backdrop-blur-md px-3 py-1.5 rounded-full text-sm font-medium text-white border border-white/20"> <i class="fas fa-award mr-1.5"></i>Le plus fréquenté </div> <?php endif; ?> <div class="absolute top-4 left-4"> <span class="status-badge inline-flex items-center px-3 py-1.5 rounded-full text-xs font-medium bg-red-500 text-white"> <span class="status-dot w-1.5 h-1.5 rounded-full bg-white mr-1.5"></span> <span class="status-text">Fermé</span> </span> </div> <div class="closing-soon-indicator absolute top-4 right-4 hidden"> <span class="closing-soon-badge inline-flex items-center px-4 py-2 rounded-full text-xs font-bold text-white shadow-lg"> <i class="fas fa-exclamation-triangle mr-2"></i>Ferme bientôt </span> </div> <div class="absolute bottom-4 left-4 flex gap-2"> <?php if($openingInfo['morning_open']): ?> <span class="px-2 py-1 bg-blue-500/70 text-white backdrop-blur-md rounded-full text-xs border border-blue-400/20"> <i class="fas fa-sun"></i> </span> <?php endif; ?> <?php if($openingInfo['late_open']): ?> <span class="px-2 py-1 bg-indigo-500/70 text-white backdrop-blur-md rounded-full text-xs border border-indigo-400/20"> <i class="fas fa-moon"></i> </span> <?php endif; ?> <?php if($openingInfo['weekend_open']): ?> <span class="px-2 py-1 bg-purple-500/70 text-white backdrop-blur-md rounded-full text-xs border border-purple-400/20"> <i class="fas fa-calendar"></i> </span> <?php endif; ?> </div> </div> <div class="p-6"> <h3 class="text-xl font-medium text-black dark:text-white mb-3"><?= htmlspecialchars($shop['title']) ?></h3> <p class="text-gray-600 dark:text-gray-300 text-sm line-clamp-3 mb-4"><?= htmlspecialchars($shop['description']) ?></p> <div class="text-sm text-gray-600 dark:text-gray-300 space-y-1 mb-5"> <div class="flex items-center gap-2"> <i class="fas fa-clock w-5 text-gray-400 dark:text-gray-500"></i> <button class="schedule-button flex-1 text-left hover:underline" data-shop-id="<?= $shop['id'] ?>"> Voir les horaires </button> </div> <div id="schedule-<?= $shop['id'] ?>" class="mt-3 p-3 bg-gray-50 dark:bg-gray-900 rounded-lg text-xs space-y-1" style="display: none;"> <?php foreach($openingInfo['formatted_schedule'] as $day => $hours): ?> <div class="flex justify-between"> <span class="font-medium"><?= $day ?>:</span> <span> <?php if (count($hours) === 1 && $hours[0] === 'Fermé'): ?> <span class="text-red-500">Fermé</span> <?php else: ?> <?= implode(' & ', $hours) ?> <?php endif; ?> </span> </div> <?php endforeach; ?> </div> <div class="next-opening text-xs bg-gray-50 dark:bg-gray-900 p-2 rounded-lg mt-2" style="display: none;"> <span class="font-medium">Prochaine ouverture: </span> <span class="next-opening-text"></span> </div> </div> <a href="shop-details.php?id=<?= $shop['id'] ?>" class="group block w-full text-center py-3 rounded-xl transition-all duration-300 text-sm font-medium relative overflow-hidden bg-black dark:bg-white text-white dark:text-black hover:shadow-lg"> <span class="flex items-center justify-center"> Plus d'informations <i class="fas fa-arrow-right ml-2 transform group-hover:translate-x-1 transition-transform"></i> </span> </a> </div> </article> <?php $delay += 0.1; endwhile; ?> </div> </main> <footer class="w-full py-8 mt-16 border-t border-gray-100 dark:border-gray-900"> <p class="text-xs text-gray-500 dark:text-gray-400 text-center max-w-3xl mx-auto px-4">© 2025 Vert Chasseur · A <a href="https://aktascorp.com" class="underline hover:text-gray-600 dark:hover:text-gray-300">aktascorp</a> member · <a href="https://aktascorp.com/privacy" class="hover:text-gray-600 dark:hover:text-gray-300">Confidentialité</a></p> </footer> <div id="deli-modal" class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm hidden"> <div class="relative w-full max-w-md mx-4 bg-white dark:bg-black rounded-3xl overflow-hidden shadow-2xl transform transition-all duration-300 scale-95 opacity-0" id="deli-modal-content"> <button onclick="closeDeliModal()" class="absolute top-4 right-4 z-10 w-8 h-8 bg-white/20 backdrop-blur-sm rounded-full flex items-center justify-center text-white hover:bg-white/30 transition-colors"> <i class="fas fa-times text-sm"></i> </button> <div class="h-48 relative deli-gradient"> <div class="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent"></div> </div> <div class="absolute bottom-0 left-0 right-0 bg-black text-white p-6"> <h3 class="text-2xl font-medium mb-2">Delitraiteur</h3> <p class="text-white/90 text-sm leading-relaxed">Le coin de douceur de Vert Chasseur. Smile and quality, with simplicity.</p> <a href="https://vertchasseur.com/shop-details?id=3" class="inline-block mt-4 bg-white/15 text-white px-6 py-2.5 rounded-full backdrop-blur-sm hover:bg-white/25 transition-colors text-sm font-medium"> Découvrir </a> </div> </div> </div> <style> .deli-gradient { background: linear-gradient(90deg, #a9d7d0 0%, #a9d7d0 25%, #ef6f56 25%, #ef6f56 50%, #bbbae3 50%, #bbbae3 75%, #ffe076 75%, #ffe076 100%); } </style> <script> const shopsData = <?= $shopsJson ?>; const zoneData = <?= json_encode($zoneData) ?>; let map = null; let userMarker = null; let zonePolygon = null; let shopMarkers = {}; let isInZone = false; let isRecenteringMap = false; let mapInitialized = false; const targetPos = { lat: null, lng: null }; const currentPos = { lat: null, lng: null }; let lastUpdateTime = Date.now(); const userIcon = L.divIcon({ className: 'location-dot', iconSize: [22, 22], iconAnchor: [11, 11] }); function createShopIcon(shop) { const icon = shop.category === 'Restaurant' ? 'fa-utensils' : 'fa-store'; return L.divIcon({ className: 'blip-icon', html: `<i class="fas ${icon}"></i>`, iconSize: [48, 48], iconAnchor: [24, 24] }); } function pointInPolygon(point, polygon) { const x = point.lat, y = point.lng; let inside = false; for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) { const xi = polygon[i].lat, yi = polygon[i].lng; const xj = polygon[j].lat, yj = polygon[j].lng; const intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); if (intersect) inside = !inside; } return inside; } function calculateZoneBounds() { let minLat = Infinity, maxLat = -Infinity; let minLng = Infinity, maxLng = -Infinity; zoneData.zone.forEach(point => { if (point.lat < minLat) minLat = point.lat; if (point.lat > maxLat) maxLat = point.lat; if (point.lng < minLng) minLng = point.lng; if (point.lng > maxLng) maxLng = point.lng; }); return { minLat, maxLat, minLng, maxLng }; } const zoneBounds = calculateZoneBounds(); function updateMapVisibility() { const mapSection = document.getElementById('mapSection'); if (isInZone && !mapInitialized) { mapSection.classList.remove('hidden'); setTimeout(() => { mapSection.classList.add('visible'); initMap(); mapInitialized = true; }, 50); } else if (!isInZone && mapInitialized) { mapSection.classList.remove('visible'); setTimeout(() => { mapSection.classList.add('hidden'); }, 600); } } function initMap() { map = L.map('map', { zoomControl: false, attributionControl: false, minZoom: 15, maxZoom: 18, maxBounds: L.latLngBounds( [zoneBounds.minLat - 0.002, zoneBounds.minLng - 0.002], [zoneBounds.maxLat + 0.002, zoneBounds.maxLng + 0.002] ), maxBoundsViscosity: 1.0 }).setView([zoneData.center.lat, zoneData.center.lng], zoneData.center.zoom); const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches; const tileLayer = isDark ? 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png' : 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png'; L.tileLayer(tileLayer, { maxZoom: 19 }).addTo(map); shopsData.forEach(shop => { if (shop.latitude && shop.longitude) { const icon = createShopIcon(shop); const marker = L.marker([parseFloat(shop.latitude), parseFloat(shop.longitude)], { icon: icon }).addTo(map); marker.bindPopup(`<strong>${shop.title}</strong><br><small>${shop.category}</small>`); marker.on('click', () => { window.location.href = 'shop-details.php?id=' + shop.id; }); shopMarkers[shop.id] = marker; } }); map.on('movestart', function() { isRecenteringMap = false; }); map.on('moveend', function() { if (!isRecenteringMap && isInZone && userMarker) { const mapCenter = map.getCenter(); const userPos = userMarker.getLatLng(); const distance = mapCenter.distanceTo(userPos); if (distance > 100) { isRecenteringMap = true; map.panTo(userPos, { animate: true, duration: 0.5 }); setTimeout(() => { isRecenteringMap = false; }, 600); } } }); window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => { if (!map) return; const newTileLayer = e.matches ? 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png' : 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png'; map.eachLayer(layer => { if (layer instanceof L.TileLayer) { map.removeLayer(layer); } }); L.tileLayer(newTileLayer, { maxZoom: 19 }).addTo(map); }); } function updateMarker() { if (targetPos.lat === null) { requestAnimationFrame(updateMarker); return; } if (currentPos.lat === null) { currentPos.lat = targetPos.lat; currentPos.lng = targetPos.lng; } const now = Date.now(); const deltaTime = (now - lastUpdateTime) / 16.67; lastUpdateTime = now; const smoothFactor = Math.min(0.12 * deltaTime, 1); currentPos.lat += (targetPos.lat - currentPos.lat) * smoothFactor; currentPos.lng += (targetPos.lng - currentPos.lng) * smoothFactor; const wasInZone = isInZone; isInZone = pointInPolygon({ lat: currentPos.lat, lng: currentPos.lng }, zoneData.zone); if (wasInZone !== isInZone) { updateMapVisibility(); } if (isInZone && mapInitialized) { if (!userMarker && map) { userMarker = L.marker([currentPos.lat, currentPos.lng], { icon: userIcon }).addTo(map); isRecenteringMap = true; map.setView([currentPos.lat, currentPos.lng], zoneData.center.zoom); setTimeout(() => { isRecenteringMap = false; }, 600); } else if (userMarker) { userMarker.setLatLng([currentPos.lat, currentPos.lng]); if (!isRecenteringMap) { const mapCenter = map.getCenter(); const userPos = L.latLng(currentPos.lat, currentPos.lng); const distance = mapCenter.distanceTo(userPos); if (distance > 50) { isRecenteringMap = true; map.panTo(userPos, { animate: true, duration: 0.3 }); setTimeout(() => { isRecenteringMap = false; }, 400); } } } } requestAnimationFrame(updateMarker); } navigator.geolocation.watchPosition( function(position) { targetPos.lat = position.coords.latitude; targetPos.lng = position.coords.longitude; }, function(error) { console.error('GPS error:', error); }, { enableHighAccuracy: true, maximumAge: 0, timeout: 5000 } ); document.addEventListener('DOMContentLoaded', function() { updateMarker(); checkDeliModal(); let searchInput = document.getElementById('shop-search'); let filterOpenNow = document.getElementById('filter-open-now'); let filterLastMinute = document.getElementById('filter-last-minute'); let filterNoReservation = document.getElementById('filter-no-reservation'); let filterMorning = document.getElementById('filter-morning'); let filterLate = document.getElementById('filter-late'); let filterWeekend = document.getElementById('filter-weekend'); let filterDelivery = document.getElementById('filter-delivery'); let shopCards = document.querySelectorAll('[data-shop]'); let currentDay = ''; let currentTime = ''; let shopStatus = {}; let shopClosingSoon = {}; let nextOpenings = {}; function updateCurrentTime() { const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; const now = new Date(); currentDay = days[now.getDay()]; let hours = now.getHours(); let minutes = now.getMinutes(); hours = hours < 10 ? '0' + hours : hours; minutes = minutes < 10 ? '0' + minutes : minutes; currentTime = `${hours}:${minutes}`; checkShopOpenStatus(); } function timeToMinutes(timeString) { const [hours, minutes] = timeString.split(':').map(Number); return hours * 60 + minutes; } function isShopOpen(openDays, openHour, closeHour) { if (!openDays.includes(currentDay)) { return false; } const currentTimeMinutes = timeToMinutes(currentTime); const openTimeMinutes = timeToMinutes(openHour); const closeTimeMinutes = timeToMinutes(closeHour); return currentTimeMinutes >= openTimeMinutes && currentTimeMinutes <= closeTimeMinutes; } function isClosingSoon(openDays, closeHour, category) { if (!openDays.includes(currentDay) || category !== 'Restaurant') { return false; } const currentTimeMinutes = timeToMinutes(currentTime); const closeTimeMinutes = timeToMinutes(closeHour); const timeUntilClose = closeTimeMinutes - currentTimeMinutes; return timeUntilClose > 0 && timeUntilClose <= 120; } function calculateNextOpening(openDays, openHour) { if (openDays.length === 0) { return null; } const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; const currentDayIndex = days.indexOf(currentDay); const currentTimeMinutes = timeToMinutes(currentTime); const openTimeMinutes = timeToMinutes(openHour); if (openDays.includes(currentDay) && currentTimeMinutes < openTimeMinutes) { return { day: currentDay, time: openHour }; } for (let i = 1; i <= 7; i++) { const nextDayIndex = (currentDayIndex + i) % 7; const nextDay = days[nextDayIndex]; if (openDays.includes(nextDay)) { return { day: nextDay, time: openHour }; } } return null; } function formatNextOpeningDay(day) { const dayTranslations = { 'Monday': 'Lundi', 'Tuesday': 'Mardi', 'Wednesday': 'Mercredi', 'Thursday': 'Jeudi', 'Friday': 'Vendredi', 'Saturday': 'Samedi', 'Sunday': 'Dimanche' }; if (day === currentDay) { return "Aujourd'hui"; } return dayTranslations[day]; } function checkShopOpenStatus() { shopCards.forEach(shop => { const shopId = shop.getAttribute('data-shop-id'); const openHour = shop.getAttribute('data-open-hour'); const closeHour = shop.getAttribute('data-close-hour'); const openDays = JSON.parse(shop.getAttribute('data-open-days')); const category = shop.getAttribute('data-category'); const isOpen = isShopOpen(openDays, openHour, closeHour); const closingSoon = isClosingSoon(openDays, closeHour, category); shopStatus[shopId] = isOpen; shopClosingSoon[shopId] = closingSoon; const statusBadge = shop.querySelector('.status-badge'); const statusText = shop.querySelector('.status-text'); const closingIndicator = shop.querySelector('.closing-soon-indicator'); if (statusBadge && statusText) { if (isOpen) { statusBadge.classList.remove('bg-red-500'); statusBadge.classList.add('bg-green-500'); statusText.textContent = 'Ouvert'; } else { statusBadge.classList.remove('bg-green-500'); statusBadge.classList.add('bg-red-500'); statusText.textContent = 'Fermé'; const nextOpen = calculateNextOpening(openDays, openHour); if (nextOpen) { nextOpenings[shopId] = nextOpen; const nextOpeningElement = shop.querySelector('.next-opening'); if (nextOpeningElement) { const nextOpeningText = nextOpeningElement.querySelector('.next-opening-text'); if (nextOpeningText) { nextOpeningText.textContent = formatNextOpeningDay(nextOpen.day) + ' à ' + nextOpen.time; nextOpeningElement.style.display = 'block'; } } } } } if (closingIndicator) { if (closingSoon) { closingIndicator.classList.remove('hidden'); } else { closingIndicator.classList.add('hidden'); } } }); } function filterShops() { const searchTerm = searchInput.value.toLowerCase(); const filterOpenNowActive = filterOpenNow.classList.contains('filter-active'); const filterLastMinuteActive = filterLastMinute.classList.contains('filter-active'); const filterNoReservationActive = filterNoReservation.classList.contains('filter-active'); const filterMorningActive = filterMorning.classList.contains('filter-active'); const filterLateActive = filterLate.classList.contains('filter-active'); const filterWeekendActive = filterWeekend.classList.contains('filter-active'); const filterDeliveryActive = filterDelivery.classList.contains('filter-active'); shopCards.forEach(shop => { const shopId = shop.getAttribute('data-shop-id'); const title = shop.getAttribute('data-title').toLowerCase(); const description = shop.getAttribute('data-description').toLowerCase(); const morning = shop.getAttribute('data-morning') === 'true'; const late = shop.getAttribute('data-late') === 'true'; const weekend = shop.getAttribute('data-weekend') === 'true'; const delivery = shop.getAttribute('data-delivery') === 'true'; const noReservation = shop.getAttribute('data-no-reservation') === 'true'; const isOpen = shopStatus[shopId] || false; const isClosingSoon = shopClosingSoon[shopId] || false; const matchesSearch = !searchTerm || title.includes(searchTerm) || description.includes(searchTerm); const matchesFilters = (!filterOpenNowActive || isOpen) && (!filterLastMinuteActive || isClosingSoon) && (!filterNoReservationActive || noReservation) && (!filterMorningActive || morning) && (!filterLateActive || late) && (!filterWeekendActive || weekend) && (!filterDeliveryActive || delivery); shop.style.display = matchesSearch && matchesFilters ? 'block' : 'none'; }); } searchInput.addEventListener('input', filterShops); [filterOpenNow, filterLastMinute, filterNoReservation, filterMorning, filterLate, filterWeekend, filterDelivery].forEach(filterBtn => { filterBtn.addEventListener('click', function() { this.classList.toggle('filter-active'); if (this.classList.contains('filter-active')) { this.classList.remove('bg-white', 'text-black', 'dark:bg-black', 'dark:text-white', 'border', 'border-gray-200', 'dark:border-gray-800'); this.classList.add('bg-black', 'text-white', 'dark:bg-white', 'dark:text-black'); } else { this.classList.add('bg-white', 'text-black', 'dark:bg-black', 'dark:text-white', 'border', 'border-gray-200', 'dark:border-gray-800'); this.classList.remove('bg-black', 'text-white', 'dark:bg-white', 'dark:text-black'); } filterShops(); }); }); document.querySelectorAll('.schedule-button').forEach(button => { button.addEventListener('click', function() { const shopId = this.getAttribute('data-shop-id'); const scheduleDetails = document.getElementById('schedule-' + shopId); if (scheduleDetails.style.display === 'none' || scheduleDetails.style.display === '') { scheduleDetails.style.display = 'block'; } else { scheduleDetails.style.display = 'none'; } }); }); updateCurrentTime(); setInterval(updateCurrentTime, 60000); }); function checkDeliModal() { const lastShown = localStorage.getItem('deliModalLastShown'); const now = Date.now(); const oneWeek = 7 * 24 * 60 * 60 * 1000; if (!lastShown || (now - parseInt(lastShown)) >= oneWeek) { setTimeout(() => { showDeliModal(); }, 2000); } } function showDeliModal() { const modal = document.getElementById('deli-modal'); const content = document.getElementById('deli-modal-content'); modal.classList.remove('hidden'); setTimeout(() => { content.classList.remove('scale-95', 'opacity-0'); content.classList.add('scale-100', 'opacity-100'); }, 50); localStorage.setItem('deliModalLastShown', Date.now().toString()); } function closeDeliModal() { const modal = document.getElementById('deli-modal'); const content = document.getElementById('deli-modal-content'); content.classList.remove('scale-100', 'opacity-100'); content.classList.add('scale-95', 'opacity-0'); setTimeout(() => { modal.classList.add('hidden'); }, 300); } document.getElementById('deli-modal').addEventListener('click', function(e) { if (e.target === this) { closeDeliModal(); } }); </script> </body> </html>
| ver. 1.6 |
Github
|
.
| PHP 8.1.33 | Генерация страницы: 0 |
proxy
|
phpinfo
|
Настройка