From d1584bb6c023b21068a09133c5c1dbaa126b0f0e Mon Sep 17 00:00:00 2001 From: jekkinoopy Date: Thu, 14 May 2026 09:01:08 +0800 Subject: [PATCH] =?UTF-8?q?fix=20=E8=AA=BF=E6=95=B4=E8=B3=87=E6=96=99?= =?UTF-8?q?=E5=A4=BE=E7=B5=90=E6=A7=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGESET-review.md | 14 +- albums.html => albums/albums.html | 30 +- concert.css => concert/concert.css | 2 +- concert.html => concert/concert.html | 14 +- docs/portal-nav-rules.md | 6 +- grok.html => extra/grok.html | 4 +- lyrics.css => extra/lyrics.css | 0 lyrics.html => extra/lyrics.html | 4 +- minister.html => extra/minister.html | 24 +- minister_ge.html => extra/minister_ge.html | 12 +- site-guide.html => extra/site-guide.html | 439 +++++++++--------- sunghoon.html => extra/sunghoon.html | 8 +- totoga2.html => extra/totoga2.html | 10 +- totoga2.js => extra/totoga2.js | 0 extra/variety.html | 51 ++ img/{ => map}/jiyong_taiwan01s.jpg | Bin img/{ => map}/jiyong_taiwan02.jpg | Bin img/{ => map}/jiyong_taiwan03 s.jpg | Bin img/{ => totoga2}/toto1.png | Bin img/{ => totoga2}/xyJDQ8m.png | Bin index.css | 4 +- index.html | 16 +- jiwon.html => jiwon/jiwon.html | 102 ++-- map.html => map/map.html | 6 +- map.js => map/map.js | 0 portal-nav.js | 511 +++++++++++---------- 26 files changed, 671 insertions(+), 586 deletions(-) rename albums.html => albums/albums.html (81%) rename concert.css => concert/concert.css (95%) rename concert.html => concert/concert.html (99%) rename grok.html => extra/grok.html (96%) rename lyrics.css => extra/lyrics.css (100%) rename lyrics.html => extra/lyrics.html (98%) rename minister.html => extra/minister.html (83%) rename minister_ge.html => extra/minister_ge.html (96%) rename site-guide.html => extra/site-guide.html (81%) rename sunghoon.html => extra/sunghoon.html (90%) rename totoga2.html => extra/totoga2.html (96%) rename totoga2.js => extra/totoga2.js (100%) create mode 100644 extra/variety.html rename img/{ => map}/jiyong_taiwan01s.jpg (100%) rename img/{ => map}/jiyong_taiwan02.jpg (100%) rename img/{ => map}/jiyong_taiwan03 s.jpg (100%) rename img/{ => totoga2}/toto1.png (100%) rename img/{ => totoga2}/xyJDQ8m.png (100%) rename jiwon.html => jiwon/jiwon.html (91%) rename map.html => map/map.html (98%) rename map.js => map/map.js (100%) diff --git a/CHANGESET-review.md b/CHANGESET-review.md index d6926ae..f363b2b 100644 --- a/CHANGESET-review.md +++ b/CHANGESET-review.md @@ -4,17 +4,17 @@ 本文依 **`git diff`** 對照 **`HEAD`** 整理,方便逐項核對;不含未在本 diff 出現之檔案或臆測內容。 -**涉及檔案(5)**:`albums.html`、`index.css`、`index.html`、`portal-nav.js`、`style.css` +**涉及檔案(5)**:`albums/albums.html`、`index.css`、`index.html`、`portal-nav.js`、`style.css` **統計**:以 `git diff --stat` 為準(以下內容對應該批變更初版筆記)。 --- -## 1. `albums.html` +## 1. `albums/albums.html` ### 目的 - 年表區塊(「啟動新篇章」「輝煌全盛期」)每一資料列:**點列上非連結區域**時,於新分頁開對應 **Spotify 專輯**。 -- 列內既有 ``(本站 `lyrics.html`、`concert.html`、`albums.html#classic`)維持原行為。 +- 列內既有 ``(本站 `extra/lyrics.html`、`concert/concert.html`、`albums/albums.html#classic`)維持原行為。 ### 標記與無障礙 @@ -68,14 +68,14 @@ 1. `
` 追加 modifier:`story-node--ebook-ssg2`。 2. 在該篇 `

` 之後新增: - - `
` + - `` - Font Awesome:`fa-solid fa-hand-pointer`(`aria-hidden="true"`) - 文案:「點這裡翻開《六六歌2》紀實電子書」 ### 相依性(請檢查) - 頁面須已載入 Font Awesome(與現有 `` 一致),否則圖示不顯示。 -- `totoga2.html` 須存在;`totoga2.js` 會辨識網址列 `#ebook` 並切換至電子書模式。 +- `extra/totoga2.html` 須存在;`extra/totoga2.js` 會辨識網址列 `#ebook` 並切換至電子書模式。 --- @@ -122,8 +122,8 @@ ## 建議檢查步驟(逐項勾選) -1. **`albums.html`**:每列空白處/封面/年份點擊 → 新分頁 Spotify 正確專輯;點列內本站連結 → 仍進本站、不開 Spotify。 -2. **`albums.html`**:鍵盤 Tab 聚焦列後 Enter/空白 → 開 Spotify;聚焦在 `` 上時 Enter → 遵循連結。 +1. **`albums/albums.html`**:每列空白處/封面/年份點擊 → 新分頁 Spotify 正確專輯;點列內本站連結 → 仍進本站、不開 Spotify。 +2. **`albums/albums.html`**:鍵盤 Tab 聚焦列後 Enter/空白 → 開 Spotify;聚焦在 `` 上時 Enter → 遵循連結。 3. **`index.html` + `style.css`**:2016 卡片外觀與 CTA 按鈕、`hover`、`focus-visible`、縮減動態偏好設定下手勢動畫關閉。 4. **`portal-nav.js`**:全站注入後選單無「消失的真相」;改 `true` 後該項回來。 5. **跨頁**:任意頁載入 `portal-nav.js` 後行為一致。 diff --git a/albums.html b/albums/albums.html similarity index 81% rename from albums.html rename to albums/albums.html index 6f31973..fcf3364 100644 --- a/albums.html +++ b/albums/albums.html @@ -5,10 +5,10 @@ 水晶男孩|黃色留聲機 - - + + - + @@ -35,7 +35,7 @@

啟動新篇章

回歸後的數位單曲與專輯,是現在進行式的聲紋紀錄;耳機調大聲一格,就能把「新篇章」接住。

-

聽見歌詞細節(2016 代表曲目)

+

聽見歌詞細節(2016 代表曲目)

@@ -50,31 +50,31 @@ data-spotify-url="https://open.spotify.com/album/1Dijv8G0wnCN8PzFZoC8bN" aria-label="在 Spotify 開啟:THREE WORDS(2016 數位單曲)"> - - + + - - + + - - + +
- 2016 數位單曲 封面 + 2016 數位單曲 封面 2016數位單曲Three Words (三個詞)數位單曲Three Words (三個詞)
- 2017 ANOTHER LIGHT 封面 + 2017 ANOTHER LIGHT 封面 2017ANOTHER LIGHTSomething Special / SmileANOTHER LIGHTSomething Special / Smile
- 2020 ALL FOR YOU 封面 + 2020 ALL FOR YOU 封面 2020ALL FOR YOUALL FOR YOUALL FOR YOUALL FOR YOU
@@ -126,8 +126,8 @@ - - + + - + \ No newline at end of file diff --git a/docs/portal-nav-rules.md b/docs/portal-nav-rules.md index 065ca39..e0ff2f8 100644 --- a/docs/portal-nav-rules.md +++ b/docs/portal-nav-rules.md @@ -6,11 +6,11 @@ ## 1. 必做:把頁面掛進導覽 HTML -在 **`PORTAL_NAV_INNER_HTML`**(約第 123 行起)裡加入連結: +在 **`buildPortalNavInnerHTML`**(`portal-nav.js`)裡維護連結: - 依資訊架構放在對應的 **`
  • `** 底下;需要子選單時用 **`portal-submenu`**。 -- `href` 使用與其他頁一致的**相對路徑**(例如 `newpage.html`、`index.html#section`)。 -- Logo 列不要動:``。 +- `href` 使用**站根相對路徑**(腳本會依當前頁深度加上前綴 `rp`):例如 `` `${rp}jiwon/jiwon.html` ``、`` `${rp}index.html#section` ``。 +- Logo 列:`` `${rp}index.html` `` 與 `` `${rp}img/logov.svg` ``。 新增或調整完後,所有帶 **`data-portal-nav`** 的 `
  • @@ -87,7 +87,7 @@

    01 月|職人精神的啟發

    參與資管組 D 大的新年紅包活動。其書法與刻印結合的質感,深深影響了我對美感的標準。

    - 新年紅包活動 + 新年紅包活動

    02 月|身體記憶的熱血

    @@ -96,8 +96,8 @@

    04 月|筆尖的沈澱

    跟著文具社走遍全台咖啡廳,在鋼筆墨水與玻璃杯的陪伴下,用手寫記錄初心。

    - 鋼筆墨水與玻璃杯 - 手寫記錄初心 + 鋼筆墨水與玻璃杯 + 手寫記錄初心
    @@ -113,7 +113,7 @@

    - 回到首頁 · 成立的初衷 + 回到首頁 · 成立的初衷

    @@ -124,8 +124,8 @@ - - + + diff --git a/minister_ge.html b/extra/minister_ge.html similarity index 96% rename from minister_ge.html rename to extra/minister_ge.html index 252f73e..138f4fc 100644 --- a/minister_ge.html +++ b/extra/minister_ge.html @@ -5,10 +5,10 @@ 水晶男孩推廣部|視覺進化論:認真魔人的進化之路 - - + + - + @@ -225,7 +225,7 @@
    - 回到首頁 · 成立的初衷 + 回到首頁 · 成立的初衷
    @@ -236,8 +236,8 @@ - - + + \ No newline at end of file diff --git a/site-guide.html b/extra/site-guide.html similarity index 81% rename from site-guide.html rename to extra/site-guide.html index d4f8548..883c24f 100644 --- a/site-guide.html +++ b/extra/site-guide.html @@ -1,218 +1,221 @@ - - - - - - - 水晶男孩|全站導覽 - - - - - - -
    -
    -

    全站導覽

    -
    - SITE MAP · 頁面索引 -
    -
    -
    - - - - - -
    -

    - 以下為本專案目前所有 HTML 頁面:每張卡片含連結、瀏覽器分頁上的標題<title>)、以及主要區塊/錨點,方便你開發或簡報時一鍵跳轉。 - 此頁未放入全站導覽列,僅供你自行書籤或輸入網址(例如 site-guide.html)開啟。 -

    - -
    -

    首頁與入口

    -
    -
    -

    index.html

    -

    標題列:SECHSKIES - 水晶男孩傳奇介紹

    -

    主視覺與導覽列;內文以區塊錨點分段。

    -

    區塊錨點

    - -
    - - -
    -
    - -
    -

    無限挑戰 · 六六歌 2

    -
    -
    -

    totoga2.html

    -

    標題列:水晶男孩|無限挑戰六六歌2 — 紀實電子書

    -

    單一入口:影片三集、FlipHTML5 電子書、傳奇的轉折時間軸、紀實長文八章(可切網漫/分頁)。

    - -
    -

    怎麼開

    -
      -
    • 影片模式 — 預設開啟(三集 Bilibili)。
    • -
    • 電子書模式 — 進頁即切到 FlipHTML5 全螢幕。
    • -
    • 紀實長文 — 自「無限挑戰」章進入;亦可從下方任一章捷徑直達。
    • -
    -
    - -
    -

    長文章節捷徑

    - -
    - -

    舊檔 ssg2-ebook.htmlchronicle.html 已併入 totoga2 整合頁

    -
    -
    -
    - -
    -

    成員與紀實

    -
    -
    -

    jiwon.html

    -

    標題列:水晶男孩|瘋子與天才

    -

    區塊錨點

    - -
    - - -
    -
    - -
    -

    推廣部 · 視覺進化論

    -
    -
    -

    minister.html

    -

    標題列:水晶男孩推廣部|視覺進化論

    -

    頂欄未開放;僅由此全站導覽索引或書籤開啟。圖檔請放 img/minister/(與頁面內檔名一致)。

    -

    區塊錨點

    - -
    - -
    -

    minister_ge.html

    -

    標題列:水晶男孩推廣部|視覺進化論:認真魔人的進化之路

    -

    供 Gemini 等工具改稿用的備份頁;頂欄未開放,請由此索引或書籤開啟。

    -

    區塊錨點

    - -
    -
    -
    - -
    -

    現場 · 典藏 · 地圖 · 歌詞

    -
    - - -
    -

    albums.html

    -

    標題列:水晶男孩|黃色留聲機

    -

    區塊錨點

    -
      -
    • #new — 啟動新篇章
    • -
    • #classic — 輝煌全盛期
    • -
    -
    - -
    -

    map.html

    -

    標題列:SECHSKIES 台灣行程地圖

    -

    區塊錨點

    -
      -
    • #taipei — 台北聖地(主版面)
    • -
    • #global — 海外遠征
    • -
    -
    - -
    -

    lyrics.html

    -

    標題列:세 단어 (THREE WORDS) — SECHSKIES

    -

    <html lang="ko">;歌詞+YouTube 嵌入單頁。

    -

    結構

    -
      -
    • .lyrics-layout — 主標+表格雙欄
    • -
    -
    -
    -
    -
    - - - - - - - - + + + + + + + 水晶男孩|全站導覽 + + + + + + +
    +
    +

    全站導覽

    +
    + SITE MAP · 頁面索引 +
    +
    +
    + + + + + +
    +

    + 以下為本專案目前所有 HTML 頁面:每張卡片含連結、瀏覽器分頁上的標題<title>)、以及主要區塊/錨點,方便你開發或簡報時一鍵跳轉。 + 此頁未放入全站導覽列,僅供你自行書籤或輸入網址(例如 extra/site-guide.html)開啟。 +

    + +
    +

    首頁與入口

    +
    +
    +

    index.html

    +

    標題列:SECHSKIES - 水晶男孩傳奇介紹

    +

    主視覺與導覽列;內文以區塊錨點分段。

    +

    區塊錨點

    + +
    + + +
    +
    + +
    +

    無限挑戰 · 六六歌 2

    +
    +
    +

    totoga2.html

    +

    標題列:水晶男孩|無限挑戰六六歌2 — 紀實電子書

    +

    單一入口:影片三集、FlipHTML5 電子書、傳奇的轉折時間軸、紀實長文八章(可切網漫/分頁)。

    + +
    +

    怎麼開

    +
      +
    • 影片模式 — 預設開啟(三集 Bilibili)。
    • +
    • 電子書模式 — 進頁即切到 FlipHTML5 全螢幕。
    • +
    • 紀實長文 — 自「無限挑戰」章進入;亦可從下方任一章捷徑直達。
    • +
    +
    + +
    +

    長文章節捷徑

    + +
    + +

    舊檔 ssg2-ebook.htmlchronicle.html 已併入 totoga2 整合頁

    +
    +
    +
    + +
    +

    成員與紀實

    +
    + + + +
    +
    + +
    +

    推廣部 · 視覺進化論

    +
    +
    +

    minister.html

    +

    標題列:水晶男孩推廣部|視覺進化論

    +

    頂欄未開放;僅由此全站導覽索引或書籤開啟。圖檔請放 img/minister/(與頁面內檔名一致)。

    +

    區塊錨點

    + +
    + +
    +

    minister_ge.html

    +

    標題列:水晶男孩推廣部|視覺進化論:認真魔人的進化之路

    +

    供 Gemini 等工具改稿用的備份頁;頂欄未開放,請由此索引或書籤開啟。

    +

    區塊錨點

    + +
    +
    +
    + +
    +

    現場 · 典藏 · 地圖 · 歌詞

    +
    + + + + +
    +

    map/map.html

    +

    標題列:SECHSKIES 台灣行程地圖

    +

    區塊錨點

    +
      +
    • #taipei — 台北聖地(主版面)
    • +
    • #global — 海外遠征
    • +
    +
    + +
    +

    lyrics.html

    +

    標題列:세 단어 (THREE WORDS) — SECHSKIES

    +

    <html lang="ko">;歌詞+YouTube 嵌入單頁。

    +

    結構

    +
      +
    • .lyrics-layout — 主標+表格雙欄
    • +
    +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/sunghoon.html b/extra/sunghoon.html similarity index 90% rename from sunghoon.html rename to extra/sunghoon.html index 1216a12..a7fe9dc 100644 --- a/sunghoon.html +++ b/extra/sunghoon.html @@ -5,9 +5,9 @@ 水晶男孩|消失的真相 - + - + @@ -44,8 +44,8 @@ - - + + diff --git a/totoga2.html b/extra/totoga2.html similarity index 96% rename from totoga2.html rename to extra/totoga2.html index 98f83bf..6e9fe5f 100644 --- a/totoga2.html +++ b/extra/totoga2.html @@ -5,9 +5,9 @@ 水晶男孩|無限挑戰六六歌2 — 紀實電子書 - + - + @@ -179,7 +179,7 @@ @@ -190,8 +190,8 @@ - - + + diff --git a/totoga2.js b/extra/totoga2.js similarity index 100% rename from totoga2.js rename to extra/totoga2.js diff --git a/extra/variety.html b/extra/variety.html new file mode 100644 index 0000000..2ad296e --- /dev/null +++ b/extra/variety.html @@ -0,0 +1,51 @@ + + + + + + + 水晶男孩|瘋子與天才 + + + + + + +
    +
    +

    瘋子與天才

    +
    + EUN JI-WON × VARIETY GRAVITY +
    +
    +
    + + + + + +
    +
    +

    瘋狂出演中

    +

    鏡頭一開就跑偏、台詞唸不完,卻總能把段子的節奏剪成「非他不可」——這是最容易被路人一眼看見的瘋勁,也是綜藝剪輯願意一路卡特寫給他的理由。

    +

    卸妝後的聊天、運動場上的暴走、對成員毫不修飾的吐槽;那些看起來像日常,其實比劇本更誇張。此區之後會補上節目清單、名場面與入坑路線,先把舞台留好。

    +
    +
    +

    隊長的氣場

    +

    同樣一句話,由他說出口就變成節奏點;同樣混亂的場面,他一站出來觀眾會先笑、再安靜聽。那不是威嚴口號,而是多年在台上台下磨出來的「控場直覺」。

    +

    此區預留給談話、音樂、與團體互動裡的隊長切片——讓新粉先用氣場認人,再慢慢讀懂他背後的堅持。

    +
    +
    + + + + + + + + diff --git a/img/jiyong_taiwan01s.jpg b/img/map/jiyong_taiwan01s.jpg similarity index 100% rename from img/jiyong_taiwan01s.jpg rename to img/map/jiyong_taiwan01s.jpg diff --git a/img/jiyong_taiwan02.jpg b/img/map/jiyong_taiwan02.jpg similarity index 100% rename from img/jiyong_taiwan02.jpg rename to img/map/jiyong_taiwan02.jpg diff --git a/img/jiyong_taiwan03 s.jpg b/img/map/jiyong_taiwan03 s.jpg similarity index 100% rename from img/jiyong_taiwan03 s.jpg rename to img/map/jiyong_taiwan03 s.jpg diff --git a/img/toto1.png b/img/totoga2/toto1.png similarity index 100% rename from img/toto1.png rename to img/totoga2/toto1.png diff --git a/img/xyJDQ8m.png b/img/totoga2/xyJDQ8m.png similarity index 100% rename from img/xyJDQ8m.png rename to img/totoga2/xyJDQ8m.png diff --git a/index.css b/index.css index 44dd2f8..680d97a 100644 --- a/index.css +++ b/index.css @@ -90,7 +90,7 @@ padding-bottom: 0; } -/* albums.html:.archive-block 已改為
    ,沿用 style.css 的 section 卡片版型 */ +/* albums/albums.html:.archive-block 已改為
    ,沿用 style.css 的 section 卡片版型 */ section.archive-block p { color: #4a4a4a; line-height: 1.7; @@ -101,7 +101,7 @@ section.archive-block p:last-child { margin-bottom: 0; } -/* albums.html:精選作品表(含封面欄) */ +/* albums/albums.html:精選作品表(含封面欄) */ .archive-discography { width: 100%; margin-top: 1.25rem; diff --git a/index.html b/index.html index 77d80f6..af653da 100644 --- a/index.html +++ b/index.html @@ -123,7 +123,7 @@

    傳奇回歸:重逢的感動合體

    透過《無限挑戰》「六六歌2」跨越16年再次重聚,引起全韓黃色氣球海再現。

    - + 點這裡翻開《六六歌2》紀實電子書 @@ -148,33 +148,33 @@

    像劇情的反轉一樣過癮:這幾個節點,剛好是許多人從「旁觀綜藝」滑進「正式入坑」的轉軌。若想讀對白級細節,請開 - 六六歌2 整合專區(影片/電子書/紀實長文八章,同一頁切換模式即可)。 + 六六歌2 整合專區(影片/電子書/紀實長文八章,同一頁切換模式即可)。

    - 開啟六六歌2 紀實電子書 - 六六歌2 整合專區 + 開啟六六歌2 紀實電子書 + 六六歌2 整合專區
    diff --git a/jiwon.html b/jiwon/jiwon.html similarity index 91% rename from jiwon.html rename to jiwon/jiwon.html index e127b80..a99623b 100644 --- a/jiwon.html +++ b/jiwon/jiwon.html @@ -1,51 +1,51 @@ - - - - - - - 水晶男孩|瘋子與天才 - - - - - - -
    -
    -

    瘋子與天才

    -
    - EUN JI-WON × VARIETY GRAVITY -
    -
    -
    - - - - - -
    -
    -

    瘋狂出演中

    -

    鏡頭一開就跑偏、台詞唸不完,卻總能把段子的節奏剪成「非他不可」——這是最容易被路人一眼看見的瘋勁,也是綜藝剪輯願意一路卡特寫給他的理由。

    -

    卸妝後的聊天、運動場上的暴走、對成員毫不修飾的吐槽;那些看起來像日常,其實比劇本更誇張。此區之後會補上節目清單、名場面與入坑路線,先把舞台留好。

    -
    -
    -

    隊長的氣場

    -

    同樣一句話,由他說出口就變成節奏點;同樣混亂的場面,他一站出來觀眾會先笑、再安靜聽。那不是威嚴口號,而是多年在台上台下磨出來的「控場直覺」。

    -

    此區預留給談話、音樂、與團體互動裡的隊長切片——讓新粉先用氣場認人,再慢慢讀懂他背後的堅持。

    -
    -
    - - - - - - - - + + + + + + + 水晶男孩|瘋子與天才 + + + + + + +
    +
    +

    瘋子與天才

    +
    + EUN JI-WON × VARIETY GRAVITY +
    +
    +
    + + + + + +
    +
    +

    瘋狂出演中

    +

    鏡頭一開就跑偏、台詞唸不完,卻總能把段子的節奏剪成「非他不可」——這是最容易被路人一眼看見的瘋勁,也是綜藝剪輯願意一路卡特寫給他的理由。

    +

    卸妝後的聊天、運動場上的暴走、對成員毫不修飾的吐槽;那些看起來像日常,其實比劇本更誇張。此區之後會補上節目清單、名場面與入坑路線,先把舞台留好。

    +
    +
    +

    隊長的氣場

    +

    同樣一句話,由他說出口就變成節奏點;同樣混亂的場面,他一站出來觀眾會先笑、再安靜聽。那不是威嚴口號,而是多年在台上台下磨出來的「控場直覺」。

    +

    此區預留給談話、音樂、與團體互動裡的隊長切片——讓新粉先用氣場認人,再慢慢讀懂他背後的堅持。

    +
    +
    + + + + + + + + diff --git a/map.html b/map/map.html similarity index 98% rename from map.html rename to map/map.html index a03c31f..57d6fe3 100644 --- a/map.html +++ b/map/map.html @@ -5,9 +5,9 @@ SECHSKIES 台灣行程地圖 - + - + @@ -508,7 +508,7 @@ - + diff --git a/map.js b/map/map.js similarity index 100% rename from map.js rename to map/map.js diff --git a/portal-nav.js b/portal-nav.js index 46e170f..b17be8f 100644 --- a/portal-nav.js +++ b/portal-nav.js @@ -1,240 +1,271 @@ -(() => { - /** - * ── 導覽「尚未開放」連結 ───────────────────────────────────────── - * ① 整個檔案先關:COMING_SOON_PAGES(目前已含志源頁/演唱會/典藏/地圖/視覺進化論等)。 - * ② index.html 細到錨點:INDEX_NAV_OPEN_HASHES。 - * ③ 文案 NAV_SOON_PHRASES:每次「滑過或鍵盤聚焦」連結時隨機擇一句(不交錯只靠重整)。 - * 浮層、title 與該次互動同步;無障礙用語固定為「尚無開放」(避免標籤隨機跳動)。 - * 要調整詞句:改 NAV_SOON_PHRASES 即可。 - */ - const NAV_SOON_PHRASES = ["奇蹟醞釀中", // 頁面準備中,呼應黃色奇蹟的誕生 - "感動載入中", // 資料讀取中,連結入坑時的悸動 - "初心校準中", // 結構調整中,呼應 LOGO 的六人設計核心 - "黃海漫延中", // 視覺填充中,發想自黃色海洋填滿漆黑的過程 - "傳奇編排中", // 內容整理中,記錄 1999 年榮譽與歷史軌跡 - "夢想航行中", // 未來規劃中,期待水晶男孩的下一個十年 - ]; - - function pickSoonPhrase() { - return NAV_SOON_PHRASES[Math.floor(Math.random() * NAV_SOON_PHRASES.length)]; - } - - /** 每次互動重抽,讀者沿導覽滑動時會交錯看到不同字樣 */ - function rollSoonLabel(link) { - const p = pickSoonPhrase(); - link.style.setProperty("--portal-nav-soon-msg", JSON.stringify(p)); - link.setAttribute("title", p); - } - - const COMING_SOON_PAGES = new Set([ - "jiwon.html", - "concert.html", - "albums.html", - "map.html", - "minister.html", - "minister_ge.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("aria-label", `${visibleTitle},尚無開放`); - - const onSoonInteract = () => rollSoonLabel(link); - link.addEventListener("pointerenter", onSoonInteract); - link.addEventListener("focus", onSoonInteract); - link.addEventListener("touchstart", onSoonInteract, { passive: true }); - - 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 = ` - - -`.trim(); - - 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")); - if (!navs.length) return; - - const currentPage = window.location.pathname.split("/").pop() || "index.html"; - - navs.forEach((nav) => { - 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", blockNavigation); - link.addEventListener("auxclick", blockNavigation); - }); - - items.forEach((item) => { - const trigger = item.querySelector(":scope > a"); - const submenu = item.querySelector(":scope > .portal-submenu"); - if (!trigger || !submenu) return; - - let closeTimer = null; - - const openItem = () => { - if (closeTimer) window.clearTimeout(closeTimer); - items.forEach((other) => { - if (other !== item) other.classList.remove("open"); - }); - item.classList.add("open"); - }; - - const scheduleClose = () => { - closeTimer = window.setTimeout(() => item.classList.remove("open"), 180); - }; - - item.addEventListener("mouseenter", openItem); - item.addEventListener("mouseleave", scheduleClose); - trigger.addEventListener("focus", openItem); - - trigger.addEventListener("click", (event) => { - const isSoon = trigger.getAttribute("data-coming-soon") === "true"; - - if (window.innerWidth <= 1024) { - event.preventDefault(); - if (!isSoon && !item.classList.contains("coming-soon")) { - item.classList.toggle("open"); - } - return; - } - - if (isSoon) { - event.preventDefault(); - } - }); - }); - - document.addEventListener("click", (event) => { - if (!nav.contains(event.target)) { - items.forEach((item) => item.classList.remove("open")); - } - }); - - const topLevelLinks = Array.from(nav.querySelectorAll(":scope > ul > li > a[href]")); - topLevelLinks.forEach((link) => { - const fallback = link.getAttribute("href"); - const source = link.dataset.originalHref || fallback; - if (!source || source === "#") return; - if (hrefMatchesCurrentPage(source, currentPage)) { - link.classList.add("nav-current"); - } - }); - }); -})(); +(() => { + /** + * ── 導覽「尚未開放」連結 ───────────────────────────────────────── + * ① 整個檔案先關:COMING_SOON_PAGES(目前已含志源頁/演唱會/典藏/地圖/視覺進化論等)。 + * ② index.html 細到錨點:INDEX_NAV_OPEN_HASHES。 + * ③ 文案 NAV_SOON_PHRASES:每次「滑過或鍵盤聚焦」連結時隨機擇一句(不交錯只靠重整)。 + * 浮層、title 與該次互動同步;無障礙用語固定為「尚無開放」(避免標籤隨機跳動)。 + * 要調整詞句:改 NAV_SOON_PHRASES 即可。 + */ + const NAV_SOON_PHRASES = ["奇蹟醞釀中", // 頁面準備中,呼應黃色奇蹟的誕生 + "感動載入中", // 資料讀取中,連結入坑時的悸動 + "初心校準中", // 結構調整中,呼應 LOGO 的六人設計核心 + "黃海漫延中", // 視覺填充中,發想自黃色海洋填滿漆黑的過程 + "傳奇編排中", // 內容整理中,記錄 1999 年榮譽與歷史軌跡 + "夢想航行中", // 未來規劃中,期待水晶男孩的下一個十年 + ]; + + function pickSoonPhrase() { + return NAV_SOON_PHRASES[Math.floor(Math.random() * NAV_SOON_PHRASES.length)]; + } + + /** 每次互動重抽,讀者沿導覽滑動時會交錯看到不同字樣 */ + function rollSoonLabel(link) { + const p = pickSoonPhrase(); + link.style.setProperty("--portal-nav-soon-msg", JSON.stringify(p)); + link.setAttribute("title", p); + } + + const COMING_SOON_PAGES = new Set([ + "jiwon.html", + "concert.html", + "albums.html", + "map.html", + "minister.html", + "minister_ge.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("aria-label", `${visibleTitle},尚無開放`); + + const onSoonInteract = () => rollSoonLabel(link); + link.addEventListener("pointerenter", onSoonInteract); + link.addEventListener("focus", onSoonInteract); + link.addEventListener("touchstart", onSoonInteract, { passive: true }); + + 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"); + } + }); + } + + /** + * 站根與 `portal-nav.js` 同層;依腳本路徑與當前頁路徑計算回到站根的相對前綴(如 `../`)。 + * 子資料夾內頁面之導覽連結必須帶此前綴,否則 `./img`、`index.html` 會錯層。 + */ + function portalNavRootPrefix() { + const el = + document.currentScript || + Array.from(document.querySelectorAll("script[src*='portal-nav.js']")).find((s) => s.src); + if (!el?.src) return "./"; + try { + const scriptPathname = new URL(el.src, window.location.href).pathname; + const scriptDirSegs = scriptPathname.split("/").filter(Boolean); + scriptDirSegs.pop(); + const pageDirSegs = window.location.pathname.split("/").filter(Boolean); + pageDirSegs.pop(); + let i = 0; + const a = scriptDirSegs; + const b = pageDirSegs; + while (i < a.length && i < b.length && a[i] === b[i]) { + i += 1; + } + const ups = b.length - i; + return ups > 0 ? "../".repeat(ups) : "./"; + } catch { + return "./"; + } + } + + /** 全站導覽 HTML — `rp` 為站根相對前綴;僅修改此模板即可同步所有頁面 */ + function buildPortalNavInnerHTML(rp) { + return ` + + +`.trim(); + } + + const rootPrefix = portalNavRootPrefix(); + document.querySelectorAll("nav.portal-nav[data-portal-nav]").forEach((nav) => { + nav.innerHTML = buildPortalNavInnerHTML(rootPrefix); + applyComingSoonToNav(nav); + }); + + const navs = Array.from(document.querySelectorAll(".portal-nav")); + if (!navs.length) return; + + const currentPage = window.location.pathname.split("/").pop() || "index.html"; + + navs.forEach((nav) => { + 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", blockNavigation); + link.addEventListener("auxclick", blockNavigation); + }); + + items.forEach((item) => { + const trigger = item.querySelector(":scope > a"); + const submenu = item.querySelector(":scope > .portal-submenu"); + if (!trigger || !submenu) return; + + let closeTimer = null; + + const openItem = () => { + if (closeTimer) window.clearTimeout(closeTimer); + items.forEach((other) => { + if (other !== item) other.classList.remove("open"); + }); + item.classList.add("open"); + }; + + const scheduleClose = () => { + closeTimer = window.setTimeout(() => item.classList.remove("open"), 180); + }; + + item.addEventListener("mouseenter", openItem); + item.addEventListener("mouseleave", scheduleClose); + trigger.addEventListener("focus", openItem); + + trigger.addEventListener("click", (event) => { + const isSoon = trigger.getAttribute("data-coming-soon") === "true"; + + if (window.innerWidth <= 1024) { + event.preventDefault(); + if (!isSoon && !item.classList.contains("coming-soon")) { + item.classList.toggle("open"); + } + return; + } + + if (isSoon) { + event.preventDefault(); + } + }); + }); + + document.addEventListener("click", (event) => { + if (!nav.contains(event.target)) { + items.forEach((item) => item.classList.remove("open")); + } + }); + + const topLevelLinks = Array.from(nav.querySelectorAll(":scope > ul > li > a[href]")); + topLevelLinks.forEach((link) => { + const fallback = link.getAttribute("href"); + const source = link.dataset.originalHref || fallback; + if (!source || source === "#") return; + if (hrefMatchesCurrentPage(source, currentPage)) { + link.classList.add("nav-current"); + } + }); + }); +})();