feat 整合多頁面 導覽列

This commit is contained in:
2026-05-09 01:00:07 +08:00
parent 16e89049de
commit 91d2f82a26
11 changed files with 1533 additions and 196 deletions
+146
View File
@@ -0,0 +1,146 @@
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>水晶男孩|十六年約定</title>
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
</head>
<body class="chronicle-page">
<header class="map-archive-header">
<div class="header-inner">
<h1 class="main-title">十六年約定</h1>
<div class="subtitle-wrap">
<span class="sub-title">SECHSKIES COM' BACK · APRIL 14 · 2016</span>
</div>
</div>
</header>
<nav class="portal-nav" aria-label="水晶男孩推廣部導覽">
<a class="logo" href="index.html"><img src="./img/logov.svg" alt="SECHSKIES Logo"></a>
<ul>
<li><a href="index.html#intro">淪陷瞬間</a>
<ul class="portal-submenu">
<li><a href="index.html#mission">成立的初衷</a></li>
<li><a href="index.html#current">跨時空軌跡</a></li>
<li><a href="index.html#story">好奇的代價</a></li>
</ul>
</li>
<li><a href="chronicle.html">十六年約定</a>
<ul class="portal-submenu">
<li><a href="#challenge">無限挑戰</a></li>
<li><a href="#ssg2">六六歌第二季</a></li>
<li><a href="#sechskies-profile">水晶男孩</a></li>
<li><a href="#gather">召集</a></li>
<li><a href="#negotiate">正式交涉</a></li>
<li><a href="#variables">變數</a></li>
<li><a href="#surprise">驚喜</a></li>
<li><a href="#final-stage">登場</a></li>
</ul>
</li>
<li><a href="index.html#archive">黃色留聲機</a>
<ul class="portal-submenu">
<li><a href="classic.html">輝煌全盛期</a></li>
<li><a href="index.html#new">啟動新篇章</a></li>
</ul>
</li>
<li><a href="concert.html">現場的悸動</a>
<ul class="portal-submenu">
<li><a href="concert.html#classic-stage" data-coming-soon="true" aria-disabled="true">燦爛的最初</a></li>
<li><a href="concert.html#reunion-stage">永恆的重逢</a></li>
</ul>
</li>
<li class="coming-soon"><a href="variety.html" data-coming-soon="true" aria-disabled="true">綜藝熱播中</a>
<ul class="portal-submenu">
<li><a href="variety.html#jiwon" data-coming-soon="true" aria-disabled="true">瘋子與天才</a></li>
<li><a href="variety.html#group" data-coming-soon="true" aria-disabled="true">舞台外真實</a></li>
</ul>
</li>
<li><a href="map.html">聖地巡禮</a>
<ul class="portal-submenu">
<li><a href="map.html#taiwan">台灣聖地</a></li>
<li><a href="map.html#global">海外遠征</a></li>
</ul>
</li>
</ul>
</nav>
<canvas id="particles"></canvas>
<div class="chronicle-controls content-container">
<button class="mode-btn active" data-mode="scroll">網漫模式(一路滑下去)</button>
<button class="mode-btn" data-mode="paged">分頁模式(上一頁 / 下一頁)</button>
</div>
<main class="content-container chronicle-reader" id="chronicle-reader" data-mode="scroll">
<article class="chronicle-chapter active" id="challenge" data-title="無限挑戰">
<h2>04 無限挑戰</h2>
<p>《無限挑戰》是韓國綜藝史上指標性的真人秀節目。「星期六星期六是歌手(六六歌)」企劃,成為水晶男孩 2016 回歸舞台的關鍵起點。</p>
<p>第一季掀起 90 年代懷舊熱潮,雖然水晶男孩未能完整出演,卻留下「總有一天要六人完整回到舞台」的遺憾伏筆。</p>
</article>
<article class="chronicle-chapter" id="ssg2" data-title="六六歌第二季">
<h2>06 六六歌第二季</h2>
<p>第二季改成「為單一團體打造完整回歸敘事」,保密層級更高,連主持群都縮小到劉在錫與 HAHA,避免消息提前走漏。</p>
<p>從開場的復古裝扮、試探式線索到真正揭曉團體身份,節目節奏在搞笑與懸念間拉開這場 16 年重逢序章。</p>
</article>
<article class="chronicle-chapter" id="sechskies-profile" data-title="水晶男孩">
<h2>08 水晶男孩</h2>
<p>1997 出道後迅速登頂,與同世代頂流並列,短短三年創下極高銷量與舞台紀錄,卻在 2000 無預警解散,留下集體創傷。</p>
<p>16 年間六人從未以水晶男孩名義完整合體,這次企劃不只是回憶,而是回應當年未說完的離別。</p>
</article>
<article class="chronicle-chapter" id="gather" data-title="召集">
<h2>10 召集</h2>
<p>製作組先與隊長殷志源交涉,再透過隱藏攝影機確認成員意願。大家都希望重組,卻同時知道最困難的拼圖是離開演藝圈多年的高志溶。</p>
<p>這個階段的重點不是舞台,而是「願不願意再次站在一起」的心理門檻。</p>
</article>
<article class="chronicle-chapter" id="negotiate" data-title="正式交涉">
<h2>11 正式交涉</h2>
<p>主持人與成員深度對話,回顧解散緣由、舞台記憶、KTV 挑戰與游擊演唱會規則。笑鬧之間,團魂逐漸浮現。</p>
<p>同時,節目也把高志溶議題正式擺上檯面:如果不是完整體,這次回歸是否還成立?</p>
</article>
<article class="chronicle-chapter" id="variables" data-title="變數">
<h2>16 變數</h2>
<p>排練期間接連出現傷病、手術需求與消息外流,原本理想的游擊演唱會計畫屢次被迫改寫,團隊陷入 B 計畫陰影。</p>
<p>在體力與心理都被拉到極限的情況下,成員仍選擇撐住,只為把久違舞台交回給等待 16 年的小黃。</p>
</article>
<article class="chronicle-chapter" id="surprise" data-title="驚喜">
<h2>19 驚喜</h2>
<p>當所有人幾乎接受「不完整」時,節目仍悄悄推進最後安排。高志溶與成員的情感張力在此段達到最高點。</p>
<p>這份驚喜不是綜藝反轉,而是把六顆水晶的空位真正填回舞台。</p>
</article>
<article class="chronicle-chapter" id="final-stage" data-title="登場">
<h2>21 登場</h2>
<p>眼罩揭開後,滿場黃海與哭聲同時湧現。從《請記得我》到安可,《離別曲》在那一晚變成了「重逢證詞」。</p>
<p>2000 的缺憾,在 2016 終於補上。這一頁不是結束,而是團體與粉絲共同完成的回歸章節。</p>
</article>
</main>
<div class="chronicle-pager content-container" id="chronicle-pager" hidden>
<button id="prev-chapter" class="btn">上一頁</button>
<span id="chapter-indicator">1 / 8</span>
<button id="next-chapter" class="btn">下一頁</button>
</div>
<footer>
<p>SECHSKIES Fanpage &copy; 2026 - 黃色氣球永遠飄揚</p>
<a href="https://jekkinoopy.github.io/Portfolio/" class="studio-link" target="_blank">
Design by <strong>Jekkinoopy Studio</strong>
</a>
</footer>
<script src="portal-nav.js"></script>
<script src="particles.js"></script>
<script src="chronicle.js"></script>
</body>
</html>
+57
View File
@@ -0,0 +1,57 @@
(() => {
const reader = document.getElementById("chronicle-reader");
if (!reader) return;
const chapters = Array.from(reader.querySelectorAll(".chronicle-chapter"));
const modeButtons = Array.from(document.querySelectorAll(".mode-btn"));
const pager = document.getElementById("chronicle-pager");
const prevBtn = document.getElementById("prev-chapter");
const nextBtn = document.getElementById("next-chapter");
const indicator = document.getElementById("chapter-indicator");
let currentIndex = 0;
function updatePagedView() {
chapters.forEach((chapter, index) => {
chapter.style.display = index === currentIndex ? "" : "none";
chapter.classList.toggle("active", index === currentIndex);
});
indicator.textContent = `${currentIndex + 1} / ${chapters.length}`;
prevBtn.disabled = currentIndex === 0;
nextBtn.disabled = currentIndex === chapters.length - 1;
}
function setMode(mode) {
reader.dataset.mode = mode;
modeButtons.forEach((btn) => btn.classList.toggle("active", btn.dataset.mode === mode));
if (mode === "paged") {
pager.hidden = false;
updatePagedView();
} else {
pager.hidden = true;
chapters.forEach((chapter) => {
chapter.style.display = "";
chapter.classList.remove("active");
});
}
}
modeButtons.forEach((button) => {
button.addEventListener("click", () => setMode(button.dataset.mode));
});
prevBtn.addEventListener("click", () => {
if (currentIndex > 0) {
currentIndex -= 1;
updatePagedView();
}
});
nextBtn.addEventListener("click", () => {
if (currentIndex < chapters.length - 1) {
currentIndex += 1;
updatePagedView();
}
});
setMode("scroll");
})();
+167
View File
@@ -0,0 +1,167 @@
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>水晶男孩|輝煌全盛期</title>
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
</head>
<body>
<header>
<div class="hero">
<h1>輝煌全盛期</h1>
<p>26 年前的經典作品整理</p>
<a href="#classic-albums" class="btn">查看專輯清單</a>
</div>
</header>
<nav class="portal-nav" aria-label="水晶男孩推廣部導覽">
<a class="logo" href="index.html"><img src="./img/logov.svg" alt="SECHSKIES Logo"></a>
<ul>
<li><a href="index.html#intro">淪陷瞬間</a>
<ul class="portal-submenu">
<li><a href="index.html#mission">成立的初衷</a></li>
<li><a href="index.html#current">跨時空軌跡</a></li>
<li><a href="index.html#story">好奇的代價</a></li>
</ul>
</li>
<li><a href="chronicle.html">十六年約定</a>
<ul class="portal-submenu">
<li><a href="chronicle.html#challenge">無限挑戰</a></li>
<li><a href="chronicle.html#ssg2">六六歌第二季</a></li>
<li><a href="chronicle.html#sechskies-profile">水晶男孩</a></li>
<li><a href="chronicle.html#gather">召集</a></li>
<li><a href="chronicle.html#negotiate">正式交涉</a></li>
<li><a href="chronicle.html#variables">變數</a></li>
<li><a href="chronicle.html#surprise">驚喜</a></li>
<li><a href="chronicle.html#final-stage">登場</a></li>
</ul>
</li>
<li><a href="index.html#archive">黃色留聲機</a>
<ul class="portal-submenu">
<li><a href="classic.html">輝煌全盛期</a></li>
<li><a href="index.html#new">啟動新篇章</a></li>
</ul>
</li>
<li><a href="concert.html">現場的悸動</a>
<ul class="portal-submenu">
<li><a href="concert.html#classic-stage" data-coming-soon="true" aria-disabled="true">燦爛的最初</a></li>
<li><a href="concert.html#reunion-stage">永恆的重逢</a></li>
</ul>
</li>
<li class="coming-soon"><a href="variety.html" data-coming-soon="true" aria-disabled="true">綜藝熱播中</a>
<ul class="portal-submenu">
<li><a href="variety.html#jiwon" data-coming-soon="true" aria-disabled="true">瘋子與天才</a></li>
<li><a href="variety.html#group" data-coming-soon="true" aria-disabled="true">舞台外真實</a></li>
</ul>
</li>
<li><a href="map.html">聖地巡禮</a>
<ul class="portal-submenu">
<li><a href="map.html#taiwan">台灣聖地</a></li>
<li><a href="map.html#global">海外遠征</a></li>
</ul>
</li>
</ul>
</nav>
<canvas id="particles"></canvas>
<div class="container">
<section id="classic-albums">
<h2 class="section-title">輝煌全盛期 (精選)</h2>
<table>
<thead>
<tr>
<th>年份</th>
<th>專輯/作品</th>
<th>主打歌</th>
</tr>
</thead>
<tbody>
<tr>
<td>1997</td>
<td>學園別曲</td>
<td>學園別曲 / 男兒之路 (Pom Saeng Pom Sa)</td>
</tr>
<tr>
<td>1998</td>
<td>Road Fighter</td>
<td>Road Fighter / Couple</td>
</tr>
<tr>
<td>1999</td>
<td>Com'Back</td>
<td>Com'Back</td>
</tr>
</tbody>
</table>
</section>
</div>
<footer>
<p>SECHSKIES Fanpage &copy; 2026 - 黃色氣球永遠飄揚</p>
</footer>
<script>
const canvas = document.getElementById('particles');
const ctx = canvas.getContext('2d');
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
resizeCanvas();
let particles = [];
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 init() {
particles = [];
for (let i = 0; i < 80; i++) particles.push(new Particle());
}
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (const p of particles) {
p.update();
p.draw();
}
requestAnimationFrame(animate);
}
window.addEventListener('resize', () => {
resizeCanvas();
init();
});
init();
animate();
</script>
<script src="portal-nav.js"></script>
</body>
</html>
+27 -8
View File
@@ -2,20 +2,38 @@ body {
background-color: var(--);
}
.concert-page {
background-color: var(--deep-black);
margin: 0;
padding: 0;
}
.concert-archive-header {
width: 100%;
height: 100vh;
min-height: 100dvh;
background-image: url('img/header.jpg');
background-position: center center;
background-repeat: no-repeat;
background-size: 108%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
padding: 100px 20px 80px;
background: #fcfaf2;
/* 延用你的米白背景 */
padding: 0 20px;
overflow: hidden;
position: relative;
}
.main-title {
font-size: 3.5rem;
font-weight: 900;
color: #333;
color: var(--yellow);
margin: 0;
letter-spacing: 10px;
letter-spacing: 8px;
text-transform: uppercase;
text-shadow: 0 0 30px rgba(255, 221, 0, 0.75);
}
.subtitle-wrap {
@@ -36,8 +54,8 @@ body {
}
.sub-title {
font-size: 1.2rem;
color: #888;
font-size: 1rem;
color: #d4d4d4;
letter-spacing: 6px;
margin: 0 25px;
}
@@ -126,8 +144,9 @@ body {
}
.concert-container {
max-width: 800px;
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
/* 第一層:演唱會卡片外殼 */
+118 -4
View File
@@ -1,13 +1,16 @@
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SECHSKIES Concert Archive</title>
<link rel="stylesheet" href="concert.css">
<!-- <link rel="stylesheet" href="style.css"> -->
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
</head>
<body>
<body class="concert-page">
<!-- HTML 結構 -->
<header class="concert-archive-header">
<div class="header-inner">
@@ -17,13 +20,62 @@
</div>
</div>
</header>
<nav class="portal-nav" aria-label="水晶男孩推廣部導覽">
<a class="logo" href="index.html"><img src="./img/logov.svg" alt="SECHSKIES Logo"></a>
<ul>
<li><a href="index.html#intro">淪陷瞬間</a>
<ul class="portal-submenu">
<li><a href="index.html#mission">成立的初衷</a></li>
<li><a href="index.html#current">跨時空軌跡</a></li>
<li><a href="index.html#story">好奇的代價</a></li>
</ul>
</li>
<li><a href="chronicle.html">十六年約定</a>
<ul class="portal-submenu">
<li><a href="chronicle.html#challenge">無限挑戰</a></li>
<li><a href="chronicle.html#ssg2">六六歌第二季</a></li>
<li><a href="chronicle.html#sechskies-profile">水晶男孩</a></li>
<li><a href="chronicle.html#gather">召集</a></li>
<li><a href="chronicle.html#negotiate">正式交涉</a></li>
<li><a href="chronicle.html#variables">變數</a></li>
<li><a href="chronicle.html#surprise">驚喜</a></li>
<li><a href="chronicle.html#final-stage">登場</a></li>
</ul>
</li>
<li><a href="index.html#archive">黃色留聲機</a>
<ul class="portal-submenu">
<li><a href="classic.html">輝煌全盛期</a></li>
<li><a href="index.html#new">啟動新篇章</a></li>
</ul>
</li>
<li><a href="concert.html">現場的悸動</a>
<ul class="portal-submenu">
<li><a href="concert.html#classic-stage" data-coming-soon="true" aria-disabled="true">燦爛的最初</a></li>
<li><a href="concert.html#reunion-stage">永恆的重逢</a></li>
</ul>
</li>
<li class="coming-soon"><a href="variety.html" data-coming-soon="true" aria-disabled="true">綜藝熱播中</a>
<ul class="portal-submenu">
<li><a href="variety.html#jiwon" data-coming-soon="true" aria-disabled="true">瘋子與天才</a></li>
<li><a href="variety.html#group" data-coming-soon="true" aria-disabled="true">舞台外真實</a></li>
</ul>
</li>
<li><a href="map.html">聖地巡禮</a>
<ul class="portal-submenu">
<li><a href="map.html#taiwan">台灣聖地</a></li>
<li><a href="map.html#global">海外遠征</a></li>
</ul>
</li>
</ul>
</nav>
<canvas id="particles"></canvas>
<div class="search-section">
<input type="text" class="search-input" placeholder="搜尋曲目或場次...">
</div>
<div class="concert-container">
<!-- 演唱會 con-$ -->
<div class="con-1" data-tour="YELLOW NOTE">
<div id="classic-stage" class="con-1" data-tour="YELLOW NOTE">
<span class="title">2016&nbsp;&nbsp;|&nbsp;&nbsp; YELLOW NOTE</span>
<img src="img/concert/2016.jpg" alt="">
<!-- 地點 con-$-$ -->
@@ -151,7 +203,7 @@
</div>
</div>
<!-- 演唱會 02 -->
<div class="con-2" data-tour="20TH ANNIV.">
<div id="reunion-stage" class="con-2" data-tour="20TH ANNIV.">
<span class="title">2017-2018&nbsp;&nbsp;|&nbsp;&nbsp;20TH ANNIVERSARY</span>
<img src="img/concert/2017.jpg" alt="">
@@ -399,7 +451,69 @@
</div>
</div>
</div>
<footer>
<p>SECHSKIES Fanpage &copy; 2026 - 黃色氣球永遠飄揚</p>
<a href="https://jekkinoopy.github.io/Portfolio/" class="studio-link" target="_blank">
Design by <strong>Jekkinoopy Studio</strong>
</a>
</footer>
<script>
const canvas = document.getElementById('particles');
const ctx = canvas.getContext('2d');
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
resizeCanvas();
let particles = [];
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 init() {
particles = [];
for (let i = 0; i < 80; i++) particles.push(new Particle());
}
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (const p of particles) {
p.update();
p.draw();
}
requestAnimationFrame(animate);
}
window.addEventListener('resize', () => {
resizeCanvas();
init();
});
init();
animate();
</script>
<script src="portal-nav.js"></script>
</body>
</html>
+54 -6
View File
@@ -20,19 +20,65 @@
</div>
</header>
<nav>
<div class="logo"><img src="./img/logov.svg" alt=""></div>
<nav class="portal-nav" aria-label="水晶男孩推廣部導覽">
<a class="logo" href="index.html"><img src="./img/logov.svg" alt=""></a>
<ul>
<li><a href="#background">重拾黃色奇蹟</a></li>
<li><a href="#albums">歷年作品</a></li>
<li><a href="#current">現在故事線</a></li>
<li><a href="concert.html">演唱會紀錄</a></li>
<li><a href="index.html#intro">淪陷瞬間</a>
<ul class="portal-submenu">
<li><a href="index.html#mission">成立的初衷</a></li>
<li><a href="index.html#current">跨時空軌跡</a></li>
<li><a href="index.html#story">好奇的代價</a></li>
</ul>
</li>
<li><a href="chronicle.html">十六年約定</a>
<ul class="portal-submenu">
<li><a href="chronicle.html#challenge">無限挑戰</a></li>
<li><a href="chronicle.html#ssg2">六六歌第二季</a></li>
<li><a href="chronicle.html#sechskies-profile">水晶男孩</a></li>
<li><a href="chronicle.html#gather">召集</a></li>
<li><a href="chronicle.html#negotiate">正式交涉</a></li>
<li><a href="chronicle.html#variables">變數</a></li>
<li><a href="chronicle.html#surprise">驚喜</a></li>
<li><a href="chronicle.html#final-stage">登場</a></li>
</ul>
</li>
<li><a href="index.html#archive">黃色留聲機</a>
<ul class="portal-submenu">
<li><a href="classic.html">輝煌全盛期</a></li>
<li><a href="index.html#new">啟動新篇章</a></li>
</ul>
</li>
<li><a href="concert.html">現場的悸動</a>
<ul class="portal-submenu">
<li><a href="concert.html#classic-stage" data-coming-soon="true" aria-disabled="true">燦爛的最初</a></li>
<li><a href="concert.html#reunion-stage">永恆的重逢</a></li>
</ul>
</li>
<li class="coming-soon"><a href="variety.html" data-coming-soon="true" aria-disabled="true">綜藝熱播中</a>
<ul class="portal-submenu">
<li><a href="variety.html#jiwon" data-coming-soon="true" aria-disabled="true">瘋子與天才</a></li>
<li><a href="variety.html#group" data-coming-soon="true" aria-disabled="true">舞台外真實</a></li>
</ul>
</li>
<li><a href="map.html">聖地巡禮</a>
<ul class="portal-submenu">
<li><a href="map.html#taiwan">台灣聖地</a></li>
<li><a href="map.html#global">海外遠征</a></li>
</ul>
</li>
</ul>
</nav>
<canvas id="particles"></canvas>
<div class="container">
<div id="intro"></div>
<div id="mission"></div>
<div id="timeline"></div>
<div id="story"></div>
<div id="archive"></div>
<div id="classic"></div>
<div id="new"></div>
<section id="background">
<h2 class="section-title">故事背景</h2>
<div class="intro-text">
@@ -135,6 +181,7 @@
</article>
</div>
</section>
</div>
<footer>
<p>SECHSKIES Fanpage &copy; 2026 - 黃色氣球永遠飄揚</p>
@@ -201,6 +248,7 @@
init();
animate();
</script>
<script src="portal-nav.js"></script>
</body>
</html>
+204 -169
View File
@@ -9,7 +9,7 @@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
</head>
<body>
<body class="map-page">
<!-- 備註:本頁一定要是完整 HTML 文件(含 head 與 style.css),
否則會變成瀏覽器的「片段模式」,樣式常常吃不到或跑版。 -->
<header class="map-archive-header">
@@ -21,24 +21,76 @@
</div>
</header>
<nav class="member-tabs" aria-label="成員篩選">
<button class="tab-btn active" data-member="all" aria-pressed="true">全部行程</button>
<button class="tab-btn" data-member="kogo" aria-pressed="false">高高父子遊台灣</button>
<nav class="portal-nav" aria-label="水晶男孩推廣部導覽">
<a class="logo" href="index.html"><img src="./img/logov.svg" alt="SECHSKIES Logo"></a>
<ul>
<li><a href="index.html#intro">淪陷瞬間</a>
<ul class="portal-submenu">
<li><a href="index.html#mission">成立的初衷</a></li>
<li><a href="index.html#current">跨時空軌跡</a></li>
<li><a href="index.html#story">好奇的代價</a></li>
</ul>
</li>
<li><a href="chronicle.html">十六年約定</a>
<ul class="portal-submenu">
<li><a href="chronicle.html#challenge">無限挑戰</a></li>
<li><a href="chronicle.html#ssg2">六六歌第二季</a></li>
<li><a href="chronicle.html#sechskies-profile">水晶男孩</a></li>
<li><a href="chronicle.html#gather">召集</a></li>
<li><a href="chronicle.html#negotiate">正式交涉</a></li>
<li><a href="chronicle.html#variables">變數</a></li>
<li><a href="chronicle.html#surprise">驚喜</a></li>
<li><a href="chronicle.html#final-stage">登場</a></li>
</ul>
</li>
<li><a href="index.html#archive">黃色留聲機</a>
<ul class="portal-submenu">
<li><a href="classic.html">輝煌全盛期</a></li>
<li><a href="index.html#new">啟動新篇章</a></li>
</ul>
</li>
<li><a href="concert.html">現場的悸動</a>
<ul class="portal-submenu">
<li><a href="concert.html#classic-stage" data-coming-soon="true" aria-disabled="true">燦爛的最初</a></li>
<li><a href="concert.html#reunion-stage">永恆的重逢</a></li>
</ul>
</li>
<li class="coming-soon"><a href="variety.html" data-coming-soon="true" aria-disabled="true">綜藝熱播中</a>
<ul class="portal-submenu">
<li><a href="variety.html#jiwon" data-coming-soon="true" aria-disabled="true">瘋子與天才</a></li>
<li><a href="variety.html#group" data-coming-soon="true" aria-disabled="true">舞台外真實</a></li>
</ul>
</li>
<li><a href="map.html">聖地巡禮</a>
<ul class="portal-submenu">
<li><a href="map.html#taiwan">台灣聖地</a></li>
<li><a href="map.html#global">海外遠征</a></li>
</ul>
</li>
</ul>
</nav>
<div class="member-tabs" aria-label="成員篩選">
<button class="tab-btn active" data-member="all" aria-pressed="true">全部行程</button>
<button class="tab-btn" data-member="ko" aria-pressed="false">高高父子遊台灣</button>
<button class="tab-btn" data-member="eun" aria-pressed="false">殷空少搭飛機去台灣高雄</button>
</div>
<canvas id="particles"></canvas>
<input id="itinerary-search" type="search" aria-label="行程搜尋"
placeholder="搜尋地點、描述、標籤(例如:九份、夜市、Day 2)">
<main class="content-container map-layout">
<main id="taiwan" class="content-container map-layout">
<!-- 備註:地圖區固定使用你的 Google My Maps iframe。
這裡不改地圖資料來源,右側列表只負責敘事與導覽。 -->
<section class="map-sticky">
<h2 class="map-title">水晶男孩台灣行程地圖</h2>
<iframe id="member-map" title="SECHSKIES 踩點地圖" loading="lazy" referrerpolicy="no-referrer-when-downgrade"
src="https://www.google.com/maps/d/embed?mid=1xpAsSoO8gNmMiW4x2sfkndISFTtjMQDw&hl=zh-TW&ehbc=2E312F">
src="https://www.google.com/maps/d/embed?mid=1VGZ76SPPjm3NWCBQe-hkm08oe5WPRJI&ehbc=2E312F">
</iframe>
<a id="open-map-link" class="map-link" target="_blank"
href="https://www.google.com/maps/d/u/0/viewer?mid=1xpAsSoO8gNmMiW4x2sfkndISFTtjMQDw">
href="https://www.google.com/maps/d/u/0/viewer?mid=1VGZ76SPPjm3NWCBQe-hkm08oe5WPRJI">
在 Google My Maps 開啟完整地圖
</a>
</section>
@@ -46,7 +98,141 @@
<section class="itinerary-list">
<!-- 備註:每張卡的 data-* 是「細節面板的資料來源」。
你只要改 data-date/title/desc/quote/tag/link 和圖片,就會同步更新下方介紹。 -->
<article class="polaroid-card active" data-member="kogo" data-date="Day 1" data-title="臺灣桃園國際機場"
<article class="polaroid-card active" data-member="eun" data-date="殷志源 Day 1" data-title="仁川國際機場"
data-desc="殷空少搭飛機去台灣高雄 Day 1 起點,從仁川出發。"
data-quote="高雄,我們來了。" data-tag="#殷志源 #Day1 #出發"
data-link="https://maps.google.com/?q=仁川國際機場">
<div class="photo-frame">
<img src="https://picsum.photos/seed/eun-khh-day1-1/640/420" alt="殷志源高雄行程節目截圖-仁川國際機場">
</div>
<div class="card-content">
<span class="date">殷志源 Day 1</span>
<h3>仁川國際機場</h3>
<p class="description">殷志源高雄行程第一站。</p>
<div class="tag">#殷志源 #Day1</div>
</div>
</article>
<article class="polaroid-card" data-member="eun" data-date="殷志源 Day 1" data-title="高雄國際機場"
data-desc="抵達高雄,正式開始在地行程。"
data-quote="落地就是旅行模式開啟。" data-tag="#殷志源 #Day1 #高雄"
data-link="https://maps.google.com/?q=高雄國際機場">
<div class="photo-frame"><img src="https://picsum.photos/seed/eun-khh-day1-2/640/420" alt="殷志源高雄行程節目截圖-高雄國際機場"></div>
<div class="card-content"><span class="date">殷志源 Day 1</span><h3>高雄國際機場</h3><p class="description">殷志源高雄行程第二站。</p><div class="tag">#殷志源 #Day1</div></div>
</article>
<article class="polaroid-card" data-member="eun" data-date="殷志源 Day 1" data-title="星巴克小港國際站門市"
data-desc="機場周邊補給點,為 Day 1 暖身。"
data-quote="先來一杯,狀態就到位。" data-tag="#殷志源 #Day1 #咖啡"
data-link="https://maps.google.com/?q=星巴克小港國際站門市">
<div class="photo-frame"><img src="https://picsum.photos/seed/eun-khh-day1-3/640/420" alt="殷志源高雄行程節目截圖-星巴克小港國際站門市"></div>
<div class="card-content"><span class="date">殷志源 Day 1</span><h3>星巴克小港國際站門市</h3><p class="description">殷志源高雄行程第三站。</p><div class="tag">#殷志源 #Day1</div></div>
</article>
<article class="polaroid-card" data-member="eun" data-date="殷志源 Day 1" data-title="元帝廟北極亭"
data-desc="在地信仰文化路線,感受高雄生活節奏。"
data-quote="走進廟埕,城市的日常就有了溫度。" data-tag="#殷志源 #Day1 #文化"
data-link="https://maps.google.com/?q=元帝廟北極亭">
<div class="photo-frame"><img src="https://picsum.photos/seed/eun-khh-day1-4/640/420" alt="殷志源高雄行程節目截圖-元帝廟北極亭"></div>
<div class="card-content"><span class="date">殷志源 Day 1</span><h3>元帝廟北極亭</h3><p class="description">殷志源高雄行程第四站。</p><div class="tag">#殷志源 #Day1</div></div>
</article>
<article class="polaroid-card" data-member="eun" data-date="殷志源 Day 1" data-title="桃子園餐廳"
data-desc="Day 1 用餐點,銜接下一天行程。"
data-quote="每一站都要有好吃的收尾。" data-tag="#殷志源 #Day1 #美食"
data-link="https://maps.google.com/?q=桃子園餐廳">
<div class="photo-frame"><img src="https://picsum.photos/seed/eun-khh-day1-5/640/420" alt="殷志源高雄行程節目截圖-桃子園餐廳"></div>
<div class="card-content"><span class="date">殷志源 Day 1</span><h3>桃子園餐廳</h3><p class="description">殷志源高雄行程第五站。</p><div class="tag">#殷志源 #Day1</div></div>
</article>
<article class="polaroid-card" data-member="eun" data-date="殷志源 Day 2" data-title="哈囉市場"
data-desc="Day 2 從市場開始,走進高雄在地日常。"
data-quote="市場最能看見一座城市的生活感。" data-tag="#殷志源 #Day2 #市場"
data-link="https://maps.google.com/?q=哈囉市場">
<div class="photo-frame"><img src="https://picsum.photos/seed/eun-khh-day2-1/640/420" alt="殷志源高雄行程節目截圖-哈囉市場"></div>
<div class="card-content"><span class="date">殷志源 Day 2</span><h3>哈囉市場</h3><p class="description">殷志源高雄行程 Day 2 第一站。</p><div class="tag">#殷志源 #Day2</div></div>
</article>
<article class="polaroid-card" data-member="eun" data-date="殷志源 Day 2" data-title="蓮池潭"
data-desc="高雄經典景點,白天景色與步行路線都很適合取景。"
data-quote="每一步都有風景可以拍。" data-tag="#殷志源 #Day2 #景點"
data-link="https://maps.google.com/?q=蓮池潭">
<div class="photo-frame"><img src="https://picsum.photos/seed/eun-khh-day2-2/640/420" alt="殷志源高雄行程節目截圖-蓮池潭"></div>
<div class="card-content"><span class="date">殷志源 Day 2</span><h3>蓮池潭</h3><p class="description">殷志源高雄行程 Day 2 第二站。</p><div class="tag">#殷志源 #Day2</div></div>
</article>
<article class="polaroid-card" data-member="eun" data-date="殷志源 Day 2" data-title="龍虎塔"
data-desc="蓮池潭代表地標,適合搭配導覽與拍照段落。"
data-quote="高雄的經典場景之一。" data-tag="#殷志源 #Day2 #地標"
data-link="https://maps.google.com/?q=龍虎塔">
<div class="photo-frame"><img src="https://picsum.photos/seed/eun-khh-day2-3/640/420" alt="殷志源高雄行程節目截圖-龍虎塔"></div>
<div class="card-content"><span class="date">殷志源 Day 2</span><h3>龍虎塔</h3><p class="description">殷志源高雄行程 Day 2 第三站。</p><div class="tag">#殷志源 #Day2</div></div>
</article>
<article class="polaroid-card" data-member="eun" data-date="殷志源 Day 2" data-title="輕軌高雄展覽館站(新光碼頭)"
data-desc="港區路線轉乘點,串接後續海線行程。"
data-quote="搭上輕軌,城市風景一路展開。" data-tag="#殷志源 #Day2 #輕軌"
data-link="https://maps.google.com/?q=輕軌高雄展覽館站(新光碼頭)">
<div class="photo-frame"><img src="https://picsum.photos/seed/eun-khh-day2-4/640/420" alt="殷志源高雄行程節目截圖-輕軌高雄展覽館站"></div>
<div class="card-content"><span class="date">殷志源 Day 2</span><h3>輕軌高雄展覽館站(新光碼頭)</h3><p class="description">殷志源高雄行程 Day 2 第四站。</p><div class="tag">#殷志源 #Day2</div></div>
</article>
<article class="polaroid-card" data-member="eun" data-date="殷志源 Day 2" data-title="捷運哈瑪星站(輕軌哈瑪星站)"
data-desc="山海交界的人氣轉運點,步行動線完整。"
data-quote="到了哈瑪星,海港味就很濃。" data-tag="#殷志源 #Day2 #捷運"
data-link="https://maps.google.com/?q=捷運哈瑪星站">
<div class="photo-frame"><img src="https://picsum.photos/seed/eun-khh-day2-5/640/420" alt="殷志源高雄行程節目截圖-捷運哈瑪星站"></div>
<div class="card-content"><span class="date">殷志源 Day 2</span><h3>捷運哈瑪星站(輕軌哈瑪星站)</h3><p class="description">殷志源高雄行程 Day 2 第五站。</p><div class="tag">#殷志源 #Day2</div></div>
</article>
<article class="polaroid-card" data-member="eun" data-date="殷志源 Day 2" data-title="仁武烤鴨"
data-desc="高雄人氣美食站,安排在中段補給。"
data-quote="好吃到會想直接再外帶一份。" data-tag="#殷志源 #Day2 #美食"
data-link="https://maps.google.com/?q=仁武烤鴨">
<div class="photo-frame"><img src="https://picsum.photos/seed/eun-khh-day2-6/640/420" alt="殷志源高雄行程節目截圖-仁武烤鴨"></div>
<div class="card-content"><span class="date">殷志源 Day 2</span><h3>仁武烤鴨</h3><p class="description">殷志源高雄行程 Day 2 第六站。</p><div class="tag">#殷志源 #Day2</div></div>
</article>
<article class="polaroid-card" data-member="eun" data-date="殷志源 Day 2" data-title="握咖啡西子灣店(總店)"
data-desc="海邊咖啡節奏,適合休息和訪談。"
data-quote="在西子灣,時間會慢下來。" data-tag="#殷志源 #Day2 #咖啡"
data-link="https://maps.google.com/?q=握咖啡西子灣店(總店)">
<div class="photo-frame"><img src="https://picsum.photos/seed/eun-khh-day2-7/640/420" alt="殷志源高雄行程節目截圖-握咖啡西子灣店"></div>
<div class="card-content"><span class="date">殷志源 Day 2</span><h3>握咖啡西子灣店(總店)</h3><p class="description">殷志源高雄行程 Day 2 第七站。</p><div class="tag">#殷志源 #Day2</div></div>
</article>
<article class="polaroid-card" data-member="eun" data-date="殷志源 Day 2" data-title="鼓山輪渡站"
data-desc="前往旗津的重要交通節點。"
data-quote="坐上渡輪,旅程感直接拉滿。" data-tag="#殷志源 #Day2 #輪渡"
data-link="https://maps.google.com/?q=鼓山輪渡站">
<div class="photo-frame"><img src="https://picsum.photos/seed/eun-khh-day2-8/640/420" alt="殷志源高雄行程節目截圖-鼓山輪渡站"></div>
<div class="card-content"><span class="date">殷志源 Day 2</span><h3>鼓山輪渡站</h3><p class="description">殷志源高雄行程 Day 2 第八站。</p><div class="tag">#殷志源 #Day2</div></div>
</article>
<article class="polaroid-card" data-member="eun" data-date="殷志源 Day 2" data-title="旗津輪渡站"
data-desc="抵達旗津,展開海岸線路段。"
data-quote="到了旗津,風都不一樣。" data-tag="#殷志源 #Day2 #旗津"
data-link="https://maps.google.com/?q=旗津輪渡站">
<div class="photo-frame"><img src="https://picsum.photos/seed/eun-khh-day2-9/640/420" alt="殷志源高雄行程節目截圖-旗津輪渡站"></div>
<div class="card-content"><span class="date">殷志源 Day 2</span><h3>旗津輪渡站</h3><p class="description">殷志源高雄行程 Day 2 第九站。</p><div class="tag">#殷志源 #Day2</div></div>
</article>
<article class="polaroid-card" data-member="eun" data-date="殷志源 Day 2" data-title="樂騎腳踏車、電動車、遊園車(旗津)"
data-desc="旗津移動工具租借點(和隔壁不是同一間)。"
data-quote="騎上車,海邊路線更完整。" data-tag="#殷志源 #Day2 #旗津"
data-link="https://maps.google.com/?q=樂騎腳踏車 旗津">
<div class="photo-frame"><img src="https://picsum.photos/seed/eun-khh-day2-10/640/420" alt="殷志源高雄行程節目截圖-樂騎腳踏車"></div>
<div class="card-content"><span class="date">殷志源 Day 2</span><h3>樂騎腳踏車、電動車、遊園車(旗津)</h3><p class="description">殷志源高雄行程 Day 2 第十站。</p><div class="tag">#殷志源 #Day2</div></div>
</article>
<article class="polaroid-card" data-member="eun" data-date="殷志源 Day 2" data-title="旗津彩虹教堂"
data-desc="旗津拍照地標之一。"
data-quote="彩虹教堂是旗津必拍清單。" data-tag="#殷志源 #Day2 #旗津"
data-link="https://maps.google.com/?q=旗津彩虹教堂">
<div class="photo-frame"><img src="https://picsum.photos/seed/eun-khh-day2-11/640/420" alt="殷志源高雄行程節目截圖-旗津彩虹教堂"></div>
<div class="card-content"><span class="date">殷志源 Day 2</span><h3>旗津彩虹教堂</h3><p class="description">殷志源高雄行程 Day 2 第十一站。</p><div class="tag">#殷志源 #Day2</div></div>
</article>
<article class="polaroid-card" data-member="eun" data-date="殷志源 Day 2" data-title="旗津貝殼館"
data-desc="旗津主題展點,補足海線內容。"
data-quote="一站一站把海線故事拼起來。" data-tag="#殷志源 #Day2 #旗津"
data-link="https://maps.google.com/?q=旗津貝殼館">
<div class="photo-frame"><img src="https://picsum.photos/seed/eun-khh-day2-12/640/420" alt="殷志源高雄行程節目截圖-旗津貝殼館"></div>
<div class="card-content"><span class="date">殷志源 Day 2</span><h3>旗津貝殼館</h3><p class="description">殷志源高雄行程 Day 2 第十二站。</p><div class="tag">#殷志源 #Day2</div></div>
</article>
<article class="polaroid-card" data-member="eun" data-date="殷志源 Day 2" data-title="海忠寶活海產店"
data-desc="高雄海線行程收尾美食站。"
data-quote="最後用海鮮把今天完美收尾。" data-tag="#殷志源 #Day2 #美食"
data-link="https://maps.google.com/?q=海忠寶活海產店">
<div class="photo-frame"><img src="https://picsum.photos/seed/eun-khh-day2-13/640/420" alt="殷志源高雄行程節目截圖-海忠寶活海產店"></div>
<div class="card-content"><span class="date">殷志源 Day 2</span><h3>海忠寶活海產店</h3><p class="description">殷志源高雄行程 Day 2 第十三站。</p><div class="tag">#殷志源 #Day2</div></div>
</article>
<article class="polaroid-card" data-member="kogo" data-date="高志溶 Day 1" data-title="臺灣桃園國際機場"
data-desc="高高父子遊台灣 Day 1 首站,從桃園機場正式開啟三天行程。"
data-quote="旅程從落地那一刻就開始了。" data-tag="#高高父子遊台灣 #Day1 #機場"
data-link="https://maps.google.com/?q=臺灣桃園國際機場">
@@ -54,7 +240,7 @@
<img src="https://picsum.photos/seed/day1-airport/640/420" alt="高高父子遊台灣節目截圖-臺灣桃園國際機場">
</div>
<div class="card-content">
<span class="date">Day 1</span>
<span class="date">高志溶 Day 1</span>
<h3>臺灣桃園國際機場</h3>
<p class="description">高高父子遊台灣 Day 1 第一站。</p>
<div class="tag">#高高父子遊台灣 #Day1</div>
@@ -332,167 +518,16 @@
</article>
</section>
</main>
<footer id="contact">
<div id="global"></div>
<p>SECHSKIES Fanpage &copy; 2026 - 黃色氣球永遠飄揚</p>
<a href="https://jekkinoopy.github.io/Portfolio/" class="studio-link" target="_blank">
Design by <strong>Jekkinoopy Studio</strong>
</a>
</footer>
<script>
(function () {
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 dayGroups = new Map();
let currentMember = "all";
let currentKeyword = "";
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.insertBefore(group, detailPanel);
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() {
cards.forEach((card) => {
const dayLabel = card.dataset.date || "未分類";
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(card.dataset.date || "未分類");
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);
}
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 matchedMember = currentMember === "all" || card.dataset.member === 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) {
// 保持預設收合:篩選/初始化時只更新細節,不主動展開 Day 群組。
setActiveCard(fallbackCard, { expandDay: false });
}
}
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 = tab.dataset.member;
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();
})();
</script>
<script src="map.js"></script>
<script src="portal-nav.js"></script>
</body>
+279
View File
@@ -0,0 +1,279 @@
(() => {
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 openMapLink = document.getElementById("open-map-link");
const dayGroups = new Map();
let currentMember = "all";
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: "殷空少搭飛機去台灣高雄"
};
const memberSortOrder = { ko: 0, eun: 1 };
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 updateMapByMember(member) {
const normalized = normalizeMember(member);
const mapConfig = memberMaps[normalized];
if (!mapConfig) return;
memberMapFrame.src = mapConfig.embed;
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);
itineraryList.insertBefore(group, detailPanel);
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);
}
updateMapByMember(card.dataset.member || "all");
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 if (currentMember !== "all") {
updateMapByMember(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();
})();
+58
View File
@@ -0,0 +1,58 @@
(() => {
const canvas = document.getElementById("particles");
if (!canvas) return;
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();
});
})();
+65
View File
@@ -0,0 +1,65 @@
(() => {
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"]'));
soonLinks.forEach((link) => {
link.addEventListener("click", (event) => {
event.preventDefault();
event.stopPropagation();
});
});
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) => {
if (window.innerWidth <= 1024) {
event.preventDefault();
item.classList.toggle("open");
}
});
});
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 href = link.getAttribute("href");
if (!href) return;
const [file] = href.split("#");
if (file === currentPage) {
link.classList.add("nav-current");
}
});
});
})();
+358 -9
View File
@@ -103,6 +103,117 @@ nav a:hover {
color: white;
}
.portal-nav li {
position: relative;
padding-bottom: 8px;
}
.portal-nav {
position: relative;
z-index: 60;
}
.portal-nav .portal-submenu {
display: none;
position: absolute;
top: calc(100% - 2px);
left: 0;
min-width: 220px;
padding: 10px;
border-radius: 12px;
background: #ffffff;
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.12);
z-index: 80;
}
.portal-nav .portal-submenu li {
padding-bottom: 0;
}
.portal-nav .portal-submenu a {
display: block;
padding: 6px 8px;
border-radius: 8px;
color: #5f5f5f;
font-size: 0.84rem;
font-weight: 500;
letter-spacing: 0;
}
.portal-nav .portal-submenu a:hover {
background: #fff8df;
color: #3a300a;
}
.portal-nav a.nav-current {
color: #ffffff;
}
.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;
}
.portal-nav a[data-coming-soon="true"] {
cursor: not-allowed;
opacity: 0.72;
}
.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 {
display: grid;
gap: 6px;
}
#particles {
position: fixed;
inset: 0;
@@ -169,11 +280,18 @@ footer {
/* 區塊容器 */
.container {
max-width: 1000px;
max-width: 1200px;
margin: 0 auto;
padding: 40px 20px;
}
.content-container {
max-width: 1200px;
margin: 0 auto;
padding-left: 20px;
padding-right: 20px;
}
.section-title {
color: var(--primary);
text-align: center;
@@ -457,19 +575,136 @@ footer {
}
/* map.html */
.map-page .map-archive-header,
.map-page .portal-nav,
.map-page .member-tabs,
.map-page #itinerary-search,
.map-page .map-layout,
.map-page footer {
position: relative;
z-index: 1;
}
.concert-page .concert-archive-header,
.concert-page .portal-nav,
.concert-page .search-section,
.concert-page .concert-container,
.concert-page footer {
position: relative;
z-index: 1;
}
.map-page .portal-nav {
width: 100%;
margin: 0 0 14px;
padding: 0.8rem 100px;
background-color: var(--black);
border-radius: 0;
box-shadow: none;
overflow: visible;
position: sticky;
top: 0;
z-index: 30;
}
.map-page .portal-nav ul {
list-style: none;
margin: 0;
padding: 0;
}
.map-page .portal-nav>ul {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 24px;
flex-wrap: wrap;
min-width: 0;
padding: 0;
}
.map-page .portal-nav>ul>li {
position: relative;
line-height: 1;
}
.map-page .portal-nav>ul>li>a {
display: inline-block;
color: var(--primary);
text-decoration: none;
font-size: 0.95rem;
letter-spacing: 1px;
white-space: nowrap;
padding: 4px 2px;
border-bottom: 0;
transition: color 0.2s ease;
}
.map-page .portal-nav>ul>li>a:hover,
.map-page .portal-nav>ul>li:focus-within>a {
color: white;
}
.map-page .portal-nav .portal-submenu {
position: absolute;
left: auto;
right: 0;
top: calc(100% + 8px);
min-width: 240px;
padding: 10px;
border-radius: 12px;
background: #ffffff;
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.12);
display: none;
gap: 6px;
z-index: 20;
}
.map-page .portal-nav>ul>li:hover .portal-submenu,
.map-page .portal-nav>ul>li:focus-within .portal-submenu {
display: grid;
}
.map-page .portal-nav .portal-submenu a {
display: block;
padding: 6px 8px;
border-radius: 8px;
font-size: 0.84rem;
color: #5f5f5f;
font-weight: 500;
}
.map-page .portal-nav .portal-submenu a:hover {
background: #fff8df;
color: #3a300a;
}
.map-archive-header {
width: 100%;
height: 100vh;
min-height: 100dvh;
background-image: url('img/header.jpg');
background-position: center center;
background-repeat: no-repeat;
background-size: 108%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
padding: 90px 20px 38px;
background: #fcfaf2;
padding: 0 20px;
overflow: hidden;
position: relative;
}
.map-archive-header .main-title {
font-size: clamp(2rem, 4.5vw, 3.2rem);
font-weight: 900;
color: #333;
letter-spacing: 8px;
color: var(--yellow);
letter-spacing: 6px;
text-transform: uppercase;
margin: 0;
text-shadow: 0 0 30px rgba(255, 221, 0, 0.75);
}
.map-archive-header .subtitle-wrap {
@@ -484,9 +719,9 @@ footer {
}
.map-archive-header .sub-title {
font-size: 0.96rem;
font-size: 0.92rem;
letter-spacing: 4px;
color: #8a8a8a;
color: #d4d4d4;
}
.map-layout {
@@ -498,12 +733,13 @@ footer {
}
.member-tabs {
background: transparent;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
gap: 12px;
margin: 14px auto 14px;
margin: 18px auto 14px;
max-width: 1100px;
padding: 4px 20px;
}
@@ -763,7 +999,8 @@ footer {
@media (max-width: 920px) {
.map-archive-header {
padding: 76px 16px 30px;
background-size: cover;
padding: 0 16px;
}
.map-archive-header .main-title {
@@ -774,6 +1011,25 @@ footer {
grid-template-columns: 1fr;
}
.map-page .portal-nav>ul {
justify-content: center;
gap: 16px;
}
.map-page .portal-nav {
padding: 0.8rem 16px;
}
.map-page .portal-nav .portal-submenu {
position: static;
min-width: 0;
margin-top: 6px;
display: grid;
box-shadow: none;
background: #fffaf0;
padding: 8px;
}
#itinerary-search {
width: calc(100% - 28px);
}
@@ -805,4 +1061,97 @@ footer {
.detail-content {
padding: 13px;
}
}
/* Chronicle (webcomic-like reader) */
.chronicle-controls {
max-width: 1200px;
margin: 24px auto 10px;
padding: 0 14px;
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.mode-btn {
border: 0;
background: #2a2a2a;
color: #ffe9a8;
padding: 10px 16px;
border-radius: 999px;
cursor: pointer;
font-weight: 700;
letter-spacing: 0.3px;
}
.mode-btn.active {
background: var(--primary);
color: #161616;
}
.chronicle-reader {
max-width: 960px;
margin: 0 auto 24px;
padding: 0 14px;
}
.chronicle-chapter {
background: #fffdf6;
border-radius: 16px;
padding: 24px 20px;
margin-bottom: 16px;
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.08);
}
.chronicle-chapter h2 {
margin: 0 0 12px;
color: #252525;
letter-spacing: 1px;
}
.chronicle-chapter p {
margin: 0 0 10px;
line-height: 1.9;
color: #333;
}
.chronicle-pager {
max-width: 960px;
margin: 0 auto 30px;
padding: 0 14px;
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
}
#chapter-indicator {
min-width: 64px;
text-align: center;
font-weight: 700;
color: #f6d66f;
}
.chronicle-pager .btn {
border: 0;
background: #171717;
color: #fff2bf;
padding: 9px 14px;
border-radius: 8px;
cursor: pointer;
}
.chronicle-pager .btn:disabled {
opacity: 0.4;
cursor: not-allowed;
}
@media (max-width: 640px) {
.chronicle-chapter {
padding: 18px 14px;
}
.mode-btn {
width: 100%;
}
}