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/favicon-32x32-now.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('<br>');
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 = `
<div class="popup-content">
<h3>${data.name.replace(/"$/, '')}</h3>
<p>${description}</p>
<img src="${image1}" width="106" height="73" style="cursor: pointer;" onclick="openModal('${image1}')" />
<img src="${image2}" width="106" height="73" style="cursor: pointer;" onclick="openModal('${image2}')" />
<button onclick="addToFavorites('${data.name}')">行きたいお店リストに追加</button>
</div>`;
const marker = L.marker([lat, lng], { icon: customIcon }).addTo(map).bindPopup(popupContent);
marker.isOpen = isOpen;
markers.push(marker);
}
})
.catch(error => {
console.error('CSV読み込みエラー:', error);
});
const toggleBtn = document.createElement('button');
toggleBtn.textContent = '営業中の店を表示';
toggleBtn.style.position = 'absolute';
toggleBtn.style.top = '10px';
toggleBtn.style.right = '10px';
toggleBtn.style.zIndex = 1000;
toggleBtn.style.padding = '10px 10px';
toggleBtn.style.background = '#f3e8ff';
toggleBtn.style.color = '#3e004f';
toggleBtn.style.border = '1px solid #3e004f';
toggleBtn.style.cursor = 'pointer';
document.body.appendChild(toggleBtn);
let showingOnlyOpen = false;
toggleBtn.addEventListener('click', () => {
showingOnlyOpen = !showingOnlyOpen;
toggleBtn.textContent = showingOnlyOpen ? '全ての店を表示' : '営業中の店を表示';
markers.forEach(marker => {
if (showingOnlyOpen) {
if (marker.isOpen) marker.addTo(map);
else map.removeLayer(marker);
} else {
marker.addTo(map);
}
});
});
});
window.addToFavorites = function(name) {
const favorites = JSON.parse(localStorage.getItem('favorites') || '[]');
favorites.push({ name, addedAt: new Date().toISOString() });
localStorage.setItem('favorites', JSON.stringify(favorites));
alert(`${name}を行きたいお店リストに追加しました!`);
};
window.openModal = function(src) {
const modal = document.getElementById("modal");
const modalImg = document.getElementById("modal-img");
modal.style.display = "block";
modalImg.src = src;
};
window.closeModal = function() {
document.getElementById("modal").style.display = "none";
};