Files
sechskies/assets/js/map.js
T

305 lines
12 KiB
JavaScript
Raw Normal View History

2026-05-14 09:20:10 +08:00
(() => {
const canvas = document.getElementById("particles");
if (canvas) {
const ctx = canvas.getContext("2d");
let particles = [];
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
class Particle {
constructor() {
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
this.size = Math.random() * 4 + 1;
this.speedX = Math.random() * 0.5 - 0.25;
this.speedY = Math.random() * 0.5 + 0.2;
}
update() {
this.x += this.speedX;
this.y += this.speedY;
if (this.y > canvas.height) this.y = 0;
}
draw() {
ctx.fillStyle = "#FFDD00";
ctx.globalAlpha = 0.6;
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fill();
}
}
function initParticles() {
particles = [];
for (let i = 0; i < 80; i += 1) {
particles.push(new Particle());
}
}
function animateParticles() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (const particle of particles) {
particle.update();
particle.draw();
}
requestAnimationFrame(animateParticles);
}
resizeCanvas();
initParticles();
animateParticles();
window.addEventListener("resize", () => {
resizeCanvas();
initParticles();
});
}
const tabs = Array.from(document.querySelectorAll(".tab-btn"));
const cards = Array.from(document.querySelectorAll(".polaroid-card"));
const searchInput = document.getElementById("itinerary-search");
const itineraryList = document.querySelector(".itinerary-list");
const detailPanel = document.getElementById("detail-panel");
const detailDate = document.getElementById("detail-date");
const detailTitle = document.getElementById("detail-title");
const detailDesc = document.getElementById("detail-desc");
const detailQuote = document.getElementById("detail-quote");
const detailTag = document.getElementById("detail-tag");
const detailLink = document.getElementById("detail-link");
const detailImage = document.getElementById("detail-image");
const memberMapFrame = document.getElementById("member-map");
const mapEmbedSingle = document.getElementById("map-embed-single");
const mapEmbedAll = document.getElementById("map-embed-all");
const openMapLink = document.getElementById("open-map-link");
const openMapLinksDual = document.getElementById("open-map-links-dual");
const openMapLinkKo = document.getElementById("open-map-link-ko");
const openMapLinkEun = document.getElementById("open-map-link-eun");
const dayGroups = new Map();
2026-06-03 13:28:45 +08:00
let currentMember = "eun";
2026-05-14 09:20:10 +08:00
let currentKeyword = "";
const memberMaps = {
eun: {
embed: "https://www.google.com/maps/d/embed?mid=1VGZ76SPPjm3NWCBQe-hkm08oe5WPRJI&ehbc=2E312F",
open: "https://www.google.com/maps/d/u/0/viewer?mid=1VGZ76SPPjm3NWCBQe-hkm08oe5WPRJI"
},
ko: {
embed: "https://www.google.com/maps/d/embed?mid=1xpAsSoO8gNmMiW4x2sfkndISFTtjMQDw&hl=zh-TW&ehbc=2E312F",
open: "https://www.google.com/maps/d/u/0/viewer?mid=1xpAsSoO8gNmMiW4x2sfkndISFTtjMQDw"
}
};
const memberTripPrefix = {
ko: "高高父子遊台灣",
eun: "殷空少搭飛機去台灣高雄"
};
2026-06-03 13:28:45 +08:00
const memberSortOrder = { eun: 0, ko: 1 };
2026-05-14 09:20:10 +08:00
function normalizeMember(member) {
if (member === "kogo") return "ko";
return member;
}
function getMemberLabel(member) {
const normalized = normalizeMember(member);
if (normalized === "eun") return "殷志源";
if (normalized === "ko") return "高志溶";
return "未分類";
}
function getDayToken(rawDate) {
const match = String(rawDate || "").match(/Day\s*(\d+)/i);
if (!match) return String(rawDate || "未分類");
return "Day " + match[1];
}
function getGroupLabel(card) {
const date = getDayToken(card.dataset.date || "未分類");
const normalized = normalizeMember(card.dataset.member || "");
const prefix = memberTripPrefix[normalized] || getMemberLabel(normalized);
return prefix + "_" + date;
}
function setDualMapVisible(isAll) {
if (mapEmbedSingle && mapEmbedAll) {
mapEmbedSingle.hidden = isAll ? true : false;
mapEmbedAll.hidden = isAll ? false : true;
mapEmbedAll.setAttribute("aria-hidden", isAll ? "false" : "true");
}
if (openMapLink && openMapLinksDual) {
openMapLink.hidden = isAll ? true : false;
openMapLinksDual.hidden = isAll ? false : true;
}
}
function updateMapByMember(member) {
const normalized = normalizeMember(member);
if (normalized === "all") {
setDualMapVisible(true);
if (openMapLinkKo) openMapLinkKo.href = memberMaps.ko.open;
if (openMapLinkEun) openMapLinkEun.href = memberMaps.eun.open;
return;
}
setDualMapVisible(false);
const mapConfig = memberMaps[normalized];
if (!mapConfig || !memberMapFrame) return;
memberMapFrame.src = mapConfig.embed;
if (openMapLink) openMapLink.href = mapConfig.open;
}
function getCardSearchText(card) {
return [
card.dataset.date || "",
card.dataset.title || "",
card.dataset.desc || "",
card.dataset.tag || "",
].join(" ").toLowerCase();
}
function createDayGroup(dayLabel) {
const group = document.createElement("section");
group.className = "day-group";
group.dataset.day = dayLabel;
const toggle = document.createElement("button");
toggle.type = "button";
toggle.className = "day-toggle";
toggle.setAttribute("aria-expanded", "false");
toggle.innerHTML = "<span>" + dayLabel + "</span><i class='fa-solid fa-chevron-down'></i>";
const body = document.createElement("div");
body.className = "day-cards";
body.style.display = "none";
group.appendChild(toggle);
group.appendChild(body);
2026-06-03 15:24:31 +08:00
itineraryList.appendChild(group);
2026-05-14 09:20:10 +08:00
dayGroups.set(dayLabel, { group, toggle, body });
return dayGroups.get(dayLabel);
}
function setDayExpanded(dayGroup, shouldExpand) {
dayGroup.toggle.setAttribute("aria-expanded", shouldExpand ? "true" : "false");
dayGroup.body.style.display = shouldExpand ? "grid" : "none";
dayGroup.group.classList.toggle("expanded", shouldExpand);
}
function initializeDayGroups() {
const sortedCards = [...cards].sort((a, b) => {
const memberA = normalizeMember(a.dataset.member || "");
const memberB = normalizeMember(b.dataset.member || "");
const memberDiff = (memberSortOrder[memberA] ?? 99) - (memberSortOrder[memberB] ?? 99);
if (memberDiff !== 0) return memberDiff;
const dayA = Number((getDayToken(a.dataset.date).match(/\d+/) || [999])[0]);
const dayB = Number((getDayToken(b.dataset.date).match(/\d+/) || [999])[0]);
return dayA - dayB;
});
sortedCards.forEach((card) => {
const dayLabel = getGroupLabel(card);
const dayGroup = dayGroups.get(dayLabel) || createDayGroup(dayLabel);
dayGroup.body.appendChild(card);
});
}
function updateDayGroupVisibility() {
dayGroups.forEach(({ group, body, toggle }) => {
const visibleCards = Array.from(body.querySelectorAll(".polaroid-card")).filter((card) => card.style.display !== "none");
group.style.display = visibleCards.length ? "" : "none";
if (!visibleCards.length) {
setDayExpanded({ group, toggle, body }, false);
}
});
}
function ensureExpandedForCard(card) {
const dayGroup = dayGroups.get(getGroupLabel(card));
if (!dayGroup) return;
setDayExpanded(dayGroup, true);
}
function setActiveCard(card, options = {}) {
const { expandDay = true } = options;
cards.forEach((item) => item.classList.remove("active"));
card.classList.add("active");
if (expandDay) {
ensureExpandedForCard(card);
}
const mapKey = currentMember === "all" ? "all" : currentMember;
updateMapByMember(mapKey);
detailDate.textContent = card.dataset.date || "";
detailTitle.textContent = card.dataset.title || "";
detailDesc.textContent = card.dataset.desc || "";
detailQuote.textContent = "「" + (card.dataset.quote || "") + "」";
detailTag.textContent = card.dataset.tag || "";
detailLink.href = card.dataset.link || "#";
detailImage.src = card.querySelector("img").src;
detailImage.alt = "節目截圖 - " + (card.dataset.title || "行程地點");
}
function applyFilter() {
let firstVisible = null;
cards.forEach((card) => {
const cardMember = normalizeMember(card.dataset.member || "");
const matchedMember = currentMember === "all" || cardMember === currentMember;
const matchedKeyword = !currentKeyword || getCardSearchText(card).includes(currentKeyword);
const matched = matchedMember && matchedKeyword;
card.style.display = matched ? "" : "none";
if (matched && !firstVisible) firstVisible = card;
});
updateDayGroupVisibility();
const activeVisibleCard = cards.find((card) => card.classList.contains("active") && card.style.display !== "none");
const fallbackCard = activeVisibleCard || firstVisible;
if (fallbackCard) {
setActiveCard(fallbackCard, { expandDay: false });
} else {
updateMapByMember(currentMember === "all" ? "all" : currentMember);
}
}
tabs.forEach((tab) => {
tab.addEventListener("click", () => {
tabs.forEach((item) => {
item.classList.remove("active");
item.setAttribute("aria-pressed", "false");
});
tab.classList.add("active");
tab.setAttribute("aria-pressed", "true");
currentMember = normalizeMember(tab.dataset.member || "all");
applyFilter();
});
});
if (searchInput) {
searchInput.addEventListener("input", (event) => {
currentKeyword = event.target.value.trim().toLowerCase();
applyFilter();
});
}
itineraryList.addEventListener("click", (event) => {
const toggle = event.target.closest(".day-toggle");
if (!toggle) return;
const group = toggle.closest(".day-group");
if (!group) return;
const dayGroup = dayGroups.get(group.dataset.day);
if (!dayGroup) return;
const isExpanded = dayGroup.toggle.getAttribute("aria-expanded") === "true";
setDayExpanded(dayGroup, !isExpanded);
});
cards.forEach((card) => {
card.addEventListener("click", () => {
setActiveCard(card, { expandDay: true });
if (window.innerWidth < 920) {
detailPanel.scrollIntoView({ behavior: "smooth", block: "start" });
}
});
});
initializeDayGroups();
applyFilter();
})();