2026-07-30 14:32:51 +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 searchInput = document.getElementById("itinerary-search");
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 每個 .region-panel(例如 #taipei、#global)都是獨立的地區區塊,各自的
|
|
|
|
|
|
* member-tabs/地圖/地點卡片互不影響。新增行程時只要在對應 .region-panel
|
|
|
|
|
|
* 裡加一顆 .tab-btn(帶 data-trip-name / data-map-embed / data-map-open,
|
|
|
|
|
|
* 沒有地圖就不要寫 data-map-embed),以及對應的 .polaroid-card,不需要動這支
|
|
|
|
|
|
* 檔案。「全部行程」的地圖會依當時有設定地圖的 tab 自動組出來;完全沒有
|
|
|
|
|
|
* 設定地圖的行程,地圖區塊會自動收起來,只留卡片列表。
|
|
|
|
|
|
*/
|
|
|
|
|
|
function initRegionController(panel) {
|
|
|
|
|
|
const tabs = Array.from(panel.querySelectorAll(":scope > .member-tabs .tab-btn"));
|
|
|
|
|
|
const cards = Array.from(panel.querySelectorAll(".polaroid-card"));
|
|
|
|
|
|
const itineraryList = panel.querySelector(".itinerary-list");
|
|
|
|
|
|
const detailPanel = panel.querySelector(".detail-panel");
|
|
|
|
|
|
const detailImage = panel.querySelector(".detail-photo img");
|
|
|
|
|
|
const detailDate = panel.querySelector(".detail-content .date");
|
|
|
|
|
|
const detailTitle = panel.querySelector(".detail-content h3");
|
|
|
|
|
|
const detailDesc = panel.querySelector(".detail-content p");
|
|
|
|
|
|
const detailQuote = panel.querySelector(".detail-content blockquote");
|
|
|
|
|
|
const detailTag = panel.querySelector(".detail-content .tag");
|
|
|
|
|
|
const detailLink = panel.querySelector(".detail-content .detail-link");
|
|
|
|
|
|
|
|
|
|
|
|
const mapSticky = panel.querySelector(".map-sticky");
|
|
|
|
|
|
const mapEmbedSingle = mapSticky ? mapSticky.querySelector(".map-embed-single") : null;
|
|
|
|
|
|
const memberMapFrame = mapEmbedSingle ? mapEmbedSingle.querySelector(".map-my-maps-frame") : null;
|
|
|
|
|
|
const mapEmbedEmpty = mapSticky ? mapSticky.querySelector(".map-embed-empty") : null;
|
|
|
|
|
|
const mapEmbedAll = mapSticky ? mapSticky.querySelector(".map-embed-all") : null;
|
|
|
|
|
|
const openMapLink = mapSticky ? mapSticky.querySelector(".map-link") : null;
|
|
|
|
|
|
const openMapLinksDual = mapSticky ? mapSticky.querySelector(".map-links-dual") : null;
|
|
|
|
|
|
|
|
|
|
|
|
const dayGroups = new Map();
|
|
|
|
|
|
|
|
|
|
|
|
/* 舊資料裡卡片用 data-member="kogo",tab 用 data-member="ko";新地區請直接讓兩邊字串一致,不需要這個對照 */
|
|
|
|
|
|
function normalizeMember(member) {
|
|
|
|
|
|
if (member === "kogo") return "ko";
|
|
|
|
|
|
return member;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const tripConfig = {};
|
|
|
|
|
|
tabs.forEach((tab) => {
|
|
|
|
|
|
const member = normalizeMember(tab.dataset.member || "");
|
|
|
|
|
|
if (!member || member === "all") return;
|
|
|
|
|
|
tripConfig[member] = {
|
|
|
|
|
|
tripName: tab.dataset.tripName || tab.textContent.trim(),
|
|
|
|
|
|
memberLabel: tab.dataset.memberLabel || tab.textContent.trim(),
|
|
|
|
|
|
sortOrder: Number.isFinite(Number(tab.dataset.sortOrder)) ? Number(tab.dataset.sortOrder) : 99,
|
|
|
|
|
|
mapEmbed: tab.dataset.mapEmbed || "",
|
|
|
|
|
|
mapOpen: tab.dataset.mapOpen || "",
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const initialTab = tabs.find((tab) => tab.classList.contains("active")) || tabs[0];
|
|
|
|
|
|
let currentMember = initialTab ? normalizeMember(initialTab.dataset.member || "all") : "all";
|
|
|
|
|
|
let currentKeyword = "";
|
|
|
|
|
|
|
|
|
|
|
|
function getMemberLabel(member) {
|
|
|
|
|
|
return (tripConfig[member] && tripConfig[member].memberLabel) || "未分類";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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 member = normalizeMember(card.dataset.member || "");
|
|
|
|
|
|
const prefix = (tripConfig[member] && tripConfig[member].tripName) || getMemberLabel(member);
|
|
|
|
|
|
return prefix + "_" + date;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function tripsWithMaps() {
|
|
|
|
|
|
return Object.keys(tripConfig)
|
|
|
|
|
|
.filter((member) => tripConfig[member].mapEmbed)
|
|
|
|
|
|
.sort((a, b) => tripConfig[a].sortOrder - tripConfig[b].sortOrder);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function buildMapPairs(members) {
|
|
|
|
|
|
if (!mapEmbedAll) return;
|
|
|
|
|
|
mapEmbedAll.innerHTML = "";
|
|
|
|
|
|
members.forEach((member) => {
|
|
|
|
|
|
const trip = tripConfig[member];
|
|
|
|
|
|
const pair = document.createElement("div");
|
|
|
|
|
|
pair.className = "map-embed-pair";
|
|
|
|
|
|
|
|
|
|
|
|
const label = document.createElement("p");
|
|
|
|
|
|
label.className = "map-embed-label";
|
|
|
|
|
|
label.textContent = trip.tripName;
|
|
|
|
|
|
|
|
|
|
|
|
const iframe = document.createElement("iframe");
|
|
|
|
|
|
iframe.className = "map-my-maps-frame";
|
|
|
|
|
|
iframe.loading = "lazy";
|
|
|
|
|
|
iframe.referrerPolicy = "no-referrer-when-downgrade";
|
|
|
|
|
|
iframe.title = trip.tripName + " 踩點地圖";
|
|
|
|
|
|
iframe.src = trip.mapEmbed;
|
|
|
|
|
|
|
|
|
|
|
|
pair.appendChild(label);
|
|
|
|
|
|
pair.appendChild(iframe);
|
|
|
|
|
|
mapEmbedAll.appendChild(pair);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function buildOpenLinksDual(members) {
|
|
|
|
|
|
if (!openMapLinksDual) return;
|
|
|
|
|
|
openMapLinksDual.innerHTML = "";
|
|
|
|
|
|
members.forEach((member) => {
|
|
|
|
|
|
const trip = tripConfig[member];
|
|
|
|
|
|
if (!trip.mapOpen) return;
|
|
|
|
|
|
const a = document.createElement("a");
|
|
|
|
|
|
a.className = "map-link";
|
|
|
|
|
|
a.target = "_blank";
|
|
|
|
|
|
a.href = trip.mapOpen;
|
|
|
|
|
|
a.textContent = "開啟「" + trip.tripName + "」My Maps";
|
|
|
|
|
|
openMapLinksDual.appendChild(a);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function updateMapByMember(member) {
|
|
|
|
|
|
if (!mapSticky) return;
|
|
|
|
|
|
if (!Object.keys(tripConfig).length) {
|
|
|
|
|
|
mapSticky.hidden = true;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
mapSticky.hidden = false;
|
|
|
|
|
|
|
|
|
|
|
|
if (member === "all") {
|
|
|
|
|
|
const withMaps = tripsWithMaps();
|
|
|
|
|
|
if (mapEmbedSingle) mapEmbedSingle.hidden = true;
|
|
|
|
|
|
if (openMapLink) openMapLink.hidden = true;
|
|
|
|
|
|
if (mapEmbedAll) {
|
|
|
|
|
|
buildMapPairs(withMaps);
|
|
|
|
|
|
mapEmbedAll.hidden = withMaps.length === 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (openMapLinksDual) {
|
|
|
|
|
|
buildOpenLinksDual(withMaps);
|
|
|
|
|
|
openMapLinksDual.hidden = withMaps.length === 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (mapEmbedEmpty) mapEmbedEmpty.hidden = withMaps.length > 0;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const trip = tripConfig[member];
|
|
|
|
|
|
const hasMap = !!(trip && trip.mapEmbed);
|
|
|
|
|
|
if (mapEmbedAll) mapEmbedAll.hidden = true;
|
|
|
|
|
|
if (openMapLinksDual) openMapLinksDual.hidden = true;
|
|
|
|
|
|
if (mapEmbedSingle) mapEmbedSingle.hidden = !hasMap;
|
|
|
|
|
|
if (memberMapFrame && hasMap) memberMapFrame.src = trip.mapEmbed;
|
|
|
|
|
|
if (openMapLink) {
|
|
|
|
|
|
openMapLink.hidden = !hasMap;
|
|
|
|
|
|
if (hasMap) openMapLink.href = trip.mapOpen || "#";
|
|
|
|
|
|
}
|
|
|
|
|
|
if (mapEmbedEmpty) mapEmbedEmpty.hidden = hasMap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
itineraryList.appendChild(group);
|
|
|
|
|
|
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() {
|
|
|
|
|
|
if (!itineraryList || !cards.length) return;
|
|
|
|
|
|
const sortedCards = [...cards].sort((a, b) => {
|
|
|
|
|
|
const memberA = normalizeMember(a.dataset.member || "");
|
|
|
|
|
|
const memberB = normalizeMember(b.dataset.member || "");
|
|
|
|
|
|
const sortA = (tripConfig[memberA] && tripConfig[memberA].sortOrder) ?? 99;
|
|
|
|
|
|
const sortB = (tripConfig[memberB] && tripConfig[memberB].sortOrder) ?? 99;
|
|
|
|
|
|
if (sortA !== sortB) return sortA - sortB;
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
updateMapByMember(currentMember === "all" ? "all" : currentMember);
|
|
|
|
|
|
|
|
|
|
|
|
if (!detailPanel) return;
|
|
|
|
|
|
if (detailDate) detailDate.textContent = card.dataset.date || "";
|
|
|
|
|
|
if (detailTitle) detailTitle.textContent = card.dataset.title || "";
|
|
|
|
|
|
if (detailDesc) detailDesc.textContent = card.dataset.desc || "";
|
|
|
|
|
|
if (detailQuote) detailQuote.textContent = "「" + (card.dataset.quote || "") + "」";
|
|
|
|
|
|
if (detailTag) detailTag.textContent = card.dataset.tag || "";
|
|
|
|
|
|
if (detailLink) detailLink.href = card.dataset.link || "#";
|
|
|
|
|
|
const cardImg = card.querySelector("img");
|
|
|
|
|
|
if (detailImage && cardImg) {
|
|
|
|
|
|
detailImage.src = cardImg.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 (itineraryList) {
|
|
|
|
|
|
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) {
|
|
|
|
|
|
detailPanel.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
initializeDayGroups();
|
|
|
|
|
|
applyFilter();
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
setKeyword(keyword) {
|
|
|
|
|
|
currentKeyword = keyword;
|
|
|
|
|
|
applyFilter();
|
|
|
|
|
|
},
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const regionPanels = Array.from(document.querySelectorAll(".region-panel"));
|
|
|
|
|
|
const controllers = regionPanels.map((panel) => initRegionController(panel));
|
|
|
|
|
|
|
|
|
|
|
|
if (searchInput) {
|
|
|
|
|
|
searchInput.addEventListener("input", (event) => {
|
|
|
|
|
|
const keyword = event.target.value.trim().toLowerCase();
|
|
|
|
|
|
controllers.forEach((controller) => controller.setKeyword(keyword));
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const regionTabs = Array.from(document.querySelectorAll("[data-region-tabs] .region-tab-btn"));
|
|
|
|
|
|
if (regionTabs.length && regionPanels.length) {
|
|
|
|
|
|
regionTabs.forEach((tab) => {
|
|
|
|
|
|
tab.addEventListener("click", () => {
|
|
|
|
|
|
const region = tab.dataset.region;
|
|
|
|
|
|
regionPanels.forEach((panel) => {
|
|
|
|
|
|
panel.hidden = panel.dataset.regionPanel !== region;
|
|
|
|
|
|
});
|
|
|
|
|
|
regionTabs.forEach((item) => {
|
|
|
|
|
|
const isActive = item === tab;
|
|
|
|
|
|
item.classList.toggle("active", isActive);
|
|
|
|
|
|
item.setAttribute("aria-pressed", isActive ? "true" : "false");
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
})();
|