diff --git a/map.js b/map.js
new file mode 100644
index 0000000..c00b830
--- /dev/null
+++ b/map.js
@@ -0,0 +1,249 @@
+document.addEventListener('DOMContentLoaded', () => {
+ const map = L.map('map').setView([38.9175, 139.8353], 16);
+ const markers = [];
+
+ L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
+ attribution: '© OpenStreetMap contributors'
+ }).addTo(map);
+
+ // 現在地表示
+ if (navigator.geolocation) {
+ navigator.geolocation.getCurrentPosition(position => {
+ const lat = position.coords.latitude;
+ const lng = position.coords.longitude;
+ L.marker([lat, lng], {
+ icon: L.icon({
+ iconUrl: 'images/current-location-icon.png',
+ iconSize: [24, 24],
+ iconAnchor: [12, 12]
+ })
+ }).addTo(map).bindPopup('現在地');
+ });
+ }
+
+ const dayMap = ['日', '月', '火', '水', '木', '金', '土'];
+ const today = new Date();
+ const todayStr = dayMap[today.getDay()];
+
+ function isClosedToday(desc1) {
+ if (!desc1) return false;
+ const closed = desc1.replace('定休日:', '').replace(/:/g, '').split(/[、,/・\/\s]/).filter(Boolean);
+ for (let entry of closed) {
+ entry = entry.trim();
+ if (entry.includes('不定休')) return true;
+ if (entry.includes(todayStr) && !entry.match(/第\d/)) return true;
+ const nthMatch = entry.match(/第(\d)(.)曜日/);
+ if (nthMatch) {
+ const nth = parseInt(nthMatch[1], 10);
+ const day = nthMatch[2];
+ if (day !== todayStr) continue;
+ let count = 0;
+ for (let d = 1; d <= 31; d++) {
+ const date = new Date(today.getFullYear(), today.getMonth(), d);
+ if (date.getMonth() !== today.getMonth()) break;
+ if (date.getDay() === dayMap.indexOf(day)) count++;
+ if (date.getDate() === today.getDate() && count === nth) return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ function isOpenNow(desc2) {
+ if (!desc2) return false;
+ const now = new Date();
+ const day = now.getDay();
+ const time = now.getHours() * 60 + now.getMinutes();
+ const segments = desc2.split(/\/|\\n|\\r\\n/);
+ for (let segment of segments) {
+ const match = segment.match(/(月|火|水|木|金|土|日)(?:~(月|火|水|木|金|土|日))?[::]?\s*(\d{1,2})[::](\d{2})\s*〜\s*(\d{1,2})[::](\d{2})/);
+ if (match) {
+ const [_, startDay, endDay, sh, sm, eh, em] = match;
+ const startMin = parseInt(sh) * 60 + parseInt(sm);
+ let endMin = parseInt(eh) * 60 + parseInt(em);
+ const dayIndex = { '日': 0, '月': 1, '火': 2, '水': 3, '木': 4, '金': 5, '土': 6 };
+ let validDay = false;
+ if (!endDay) {
+ validDay = day === dayIndex[startDay];
+ } else {
+ const startIdx = dayIndex[startDay];
+ const endIdx = dayIndex[endDay];
+ validDay = startIdx <= endIdx ? day >= startIdx && day <= endIdx : day >= startIdx || day <= endIdx;
+ }
+ if (!validDay) continue;
+ if (endMin <= startMin) endMin += 1440;
+ const currentTime = time < startMin ? time + 1440 : time;
+ if (currentTime >= startMin && currentTime <= endMin) return true;
+ }
+ const simple = segment.match(/(\d{1,2})[::](\d{2})\s*〜\s*(\d{1,2})[::](\d{2})/);
+ if (simple) {
+ const startMin = parseInt(simple[1]) * 60 + parseInt(simple[2]);
+ let endMin = parseInt(simple[3]) * 60 + parseInt(simple[4]);
+ if (endMin <= startMin) endMin += 1440;
+ const currentTime = time < startMin ? time + 1440 : time;
+ if (currentTime >= startMin && currentTime <= endMin) return true;
+ }
+ }
+ return false;
+ }
+
+ fetch('snack.csv')
+ .then(response => response.text())
+ .then(text => {
+ const rows = text.split('\n').filter(row => row.trim());
+ const headers = rows[0].split(',').map(h => h.trim());
+
+ for (let i = 1; i < rows.length; i++) {
+ const values = rows[i].split(',').map(v => v.replace(/^"|"$/g, '').trim());
+ const data = {};
+ headers.forEach((key, index) => data[key] = values[index] || '');
+
+ const lat = parseFloat(data.latitude);
+ const lng = parseFloat(data.longitude);
+ if (isNaN(lat) || isNaN(lng)) continue;
+
+ const isOpen = !isClosedToday(data.description1) && isOpenNow(data.description2);
+ const iconPath = isOpen ? (data.icon || 'images/snack-icon.png') : 'images/favicon-door-32x32.png';
+
+ const customIcon = L.icon({
+ iconUrl: iconPath,
+ iconSize: [32, 32],
+ iconAnchor: [16, 32],
+ popupAnchor: [0, -32]
+ });
+
+ const description = [data.description1, data.description2, data.description3, data.mama]
+ .filter(Boolean)
+ .map(d => d.trim())
+ .join('
');
+
+ const image1 = data.img1 && data.img1.trim() !== '' ? data.img1 : 'images/snacktitle.png';
+ const image2 = data.img2 && data.img2.trim() !== '' ? data.img2 : 'images/snacktitle.png';
+
+ const popupContent =
+
${description}
+