diff --git a/portal-nav.js b/portal-nav.js index df9e36f..0c27104 100644 --- a/portal-nav.js +++ b/portal-nav.js @@ -1,4 +1,100 @@ (() => { + /** + * ── 頁面籌備中:開關方式 ───────────────────────────────────────── + * ① 整個檔案先關:COMING_SOON_PAGES 列出主檔名(目前已含長卷/電子書/志源頁/演唱會/典藏/地圖等)。 + * ② index.html 細到錨點:改 INDEX_NAV_OPEN_HASHES;列在其中的 #才可點,其餘同檔連結為籌備中。 + * ③ 視覺:「(頁面籌備中)」由 style.css 的 ::after 在 hover/鍵盤 focus 時才顯示, + * title 與 aria-label 供觸控長按/螢幕閱讀器補充說明。 + * 要開放某連結:從對應 Set 移除檔名或補上錨點後重新整理。 + */ + const COMING_SOON_PAGES = new Set([ + "chronicle.html", + "ssg2-ebook.html", + "jiwon.html", + "concert.html", + "archive.html", + "map.html", + ]); + + /** 目前先開:淪陷瞬間 #origin + 子選單三項 #start / #mission / #timeline */ + const INDEX_NAV_OPEN_HASHES = new Set(["#origin", "#start", "#mission", "#timeline"]); + + function normalizePageKey(href) { + if (!href || href === "#") return ""; + const core = href.split("#")[0].split("?")[0]; + const parts = core.split("/").filter(Boolean); + const last = parts.length ? parts[parts.length - 1] : ""; + return last.toLowerCase(); + } + + /** 比照原邏輯:取 href 前半與網址列檔名比對 */ + function hrefMatchesCurrentPage(href, currentPage) { + if (!href || href === "#") return false; + const [filePart] = href.split("#"); + const base = filePart.split("/").pop(); + return base === currentPage; + } + + /** 取得 #fragment(含 #);若無錨點則回傳空字串 */ + function anchorFromHref(href) { + const i = href.indexOf("#"); + if (i < 0) return ""; + return href.slice(i).split("?")[0]; + } + + function markLinkComingSoon(link, originalHref) { + const visibleTitle = link.textContent.trim(); + link.setAttribute("data-coming-soon", "true"); + link.setAttribute("aria-disabled", "true"); + link.setAttribute("title", "頁面籌備中"); + link.setAttribute("aria-label", `${visibleTitle}(頁面籌備中)`); + if (!link.dataset.originalHref) { + link.dataset.originalHref = originalHref; + } + + link.setAttribute("href", "#"); + } + + function linkShouldBeComingSoon(href, pageKey, pageKeysSoon) { + if (pageKeysSoon.has(pageKey)) { + return true; + } + if (pageKey === "index.html") { + const frag = anchorFromHref(href); + return !INDEX_NAV_OPEN_HASHES.has(frag); + } + return false; + } + + /** 將籌備中規則套用至單一導覽列 */ + function applyComingSoonToNav(nav) { + const pageKeysSoon = new Set( + [...COMING_SOON_PAGES].map((p) => normalizePageKey(String(p))).filter(Boolean), + ); + + nav.querySelectorAll('a[href]').forEach((link) => { + if (link.classList.contains("logo")) return; + + const href = link.getAttribute("href"); + if (!href || href === "#") return; + + const pageKey = normalizePageKey(href); + if (!pageKey) return; + + if (!linkShouldBeComingSoon(href, pageKey, pageKeysSoon)) return; + + markLinkComingSoon(link, href); + }); + + nav.querySelectorAll(":scope > ul > li").forEach((li) => { + const trigger = li.querySelector(":scope > a[href]"); + if (!trigger || trigger.classList.contains("logo")) return; + if (trigger.getAttribute("data-coming-soon") === "true") { + li.classList.add("coming-soon"); + } + }); + } + /** 全站導覽 HTML — 僅修改此字串即可同步所有頁面 */ const PORTAL_NAV_INNER_HTML = ` @@ -42,6 +138,7 @@ document.querySelectorAll("nav.portal-nav[data-portal-nav]").forEach((nav) => { nav.innerHTML = PORTAL_NAV_INNER_HTML; + applyComingSoonToNav(nav); }); const navs = Array.from(document.querySelectorAll(".portal-nav")); @@ -53,11 +150,13 @@ const items = Array.from(nav.querySelectorAll("li")); const soonLinks = Array.from(nav.querySelectorAll('a[data-coming-soon="true"]')); + const blockNavigation = (event) => { + event.preventDefault(); + event.stopPropagation(); + }; soonLinks.forEach((link) => { - link.addEventListener("click", (event) => { - event.preventDefault(); - event.stopPropagation(); - }); + link.addEventListener("click", blockNavigation); + link.addEventListener("auxclick", blockNavigation); }); items.forEach((item) => { @@ -84,9 +183,18 @@ trigger.addEventListener("focus", openItem); trigger.addEventListener("click", (event) => { + const isSoon = trigger.getAttribute("data-coming-soon") === "true"; + if (window.innerWidth <= 1024) { event.preventDefault(); - item.classList.toggle("open"); + if (!isSoon && !item.classList.contains("coming-soon")) { + item.classList.toggle("open"); + } + return; + } + + if (isSoon) { + event.preventDefault(); } }); }); @@ -99,10 +207,10 @@ const topLevelLinks = Array.from(nav.querySelectorAll(":scope > ul > li > a[href]")); topLevelLinks.forEach((link) => { - const href = link.getAttribute("href"); - if (!href) return; - const [file] = href.split("#"); - if (file === currentPage) { + const fallback = link.getAttribute("href"); + const source = link.dataset.originalHref || fallback; + if (!source || source === "#") return; + if (hrefMatchesCurrentPage(source, currentPage)) { link.classList.add("nav-current"); } }); diff --git a/style.css b/style.css index 03db663..4497834 100644 --- a/style.css +++ b/style.css @@ -150,63 +150,77 @@ nav a:hover { } .portal-nav>ul>li.coming-soon>a { - position: relative; - padding-bottom: 20px; - color: #c8a66a !important; cursor: not-allowed; -} - -.portal-nav>ul>li.coming-soon>a::after { - content: "建置中"; - position: absolute; - left: 50%; - bottom: -8px; - transform: translateX(-50%); - white-space: nowrap; - line-height: 1; - font-size: 0.72rem; - letter-spacing: 0.5px; - color: #fff; - background: #a66a2d; - border-radius: 999px; - padding: 4px 10px; + opacity: 0.82; + color: rgba(212, 196, 150, 0.95); } .portal-nav a[data-coming-soon="true"] { cursor: not-allowed; - opacity: 0.72; + opacity: 0.82; + position: relative; +} + +.portal-nav a[data-coming-soon="true"]::after { + content: "(頁面籌備中)"; + position: absolute; + z-index: 120; + white-space: nowrap; + font-size: 0.72rem; + font-weight: 600; + letter-spacing: 0.02em; + color: #6a4818; + background: #fff9e6; + padding: 5px 11px; + border-radius: 8px; + box-shadow: 0 6px 18px rgba(0, 0, 0, 0.14); + opacity: 0; + pointer-events: none; + transition: opacity 0.16s ease; +} + +.portal-nav>ul>li>a[data-coming-soon="true"]::after { + left: 50%; + top: calc(100% + 4px); + transform: translateX(-50%); +} + +.portal-nav .portal-submenu a[data-coming-soon="true"]::after { + left: 50%; + top: calc(100% + 2px); + transform: translateX(-50%); +} + +.portal-nav>ul>li>a[data-coming-soon="true"]:hover, +.portal-nav>ul>li>a[data-coming-soon="true"]:focus-visible { + color: rgba(212, 196, 150, 0.95); +} + +.portal-nav .portal-submenu a[data-coming-soon="true"]:hover, +.portal-nav .portal-submenu a[data-coming-soon="true"]:focus-visible { + background-color: transparent; + color: #5f5f5f; +} + +@media (hover: hover) { + + .portal-nav a[data-coming-soon="true"]:hover::after, + .portal-nav a[data-coming-soon="true"]:focus-visible::after { + opacity: 1; + } +} + +@media (hover: none) { + .portal-nav a[data-coming-soon="true"]:active::after { + opacity: 1; + transition: opacity 0.08s ease; + } } .portal-nav li.coming-soon>.portal-submenu { display: none !important; } -.portal-nav .portal-submenu a[data-coming-soon="true"] { - position: relative; -} - -.portal-nav .portal-submenu a[data-coming-soon="true"]::after { - content: "建置中"; - position: absolute; - top: 50%; - right: -78px; - transform: translateY(-50%); - font-size: 0.72rem; - letter-spacing: 0.5px; - color: #fff; - background: #a66a2d; - border-radius: 999px; - padding: 4px 10px; - opacity: 0; - pointer-events: none; - transition: opacity 0.18s ease; -} - -.portal-nav .portal-submenu a[data-coming-soon="true"]:hover::after, -.portal-nav .portal-submenu a[data-coming-soon="true"]:focus-visible::after { - opacity: 1; -} - .portal-nav li:hover>.portal-submenu, .portal-nav li:focus-within>.portal-submenu, .portal-nav li.open>.portal-submenu { @@ -938,6 +952,17 @@ footer { color: #3a300a; } +.map-page .portal-nav>ul>li>a[data-coming-soon="true"]:hover, +.map-page .portal-nav>ul>li>a[data-coming-soon="true"]:focus-visible { + color: rgba(212, 196, 150, 0.95); +} + +.map-page .portal-nav .portal-submenu a[data-coming-soon="true"]:hover, +.map-page .portal-nav .portal-submenu a[data-coming-soon="true"]:focus-visible { + background-color: transparent; + color: #5f5f5f; +} + .map-archive-header { width: 100%; height: 100vh;