chore: variety資料夾改名vedio並同步連結

This commit is contained in:
jekkinoopy committed 2026-08-13 16:05:26 +08:00
1 parent 22e0e89f8e
commit ce6f946c94
73 files changed
+436 -109

No files matched your search

+10 -8
View File
@@ -39,13 +39,14 @@
- **站主索引**(自用):`extra/site-guide.html`(`data-portal-nav-all-open`,不對外) - **站主索引**(自用):`extra/site-guide.html`(`data-portal-nav-all-open`,不對外)
- **內頁 header class**:`inner-page-header`(禁改回舊名) - **內頁 header class**:`inner-page-header`(禁改回舊名)
### variety/ 頁面 ### vedio/ 頁面(原 variety/,已改名)
| 檔案 | 頁面標題 | 說明 | | 檔案 | 頁面標題 | 說明 |
|------|----------|------| |------|----------|------|
| `variety/bangxiao.html` | 世紀級寶藏 / 爆笑養老院 | 甜蜜負擔計量器;六人卡片選取 + jQuery 計算;CTA → `totoga2/ebook.html` | | `vedio/bangxiao.html` | 世紀級寶藏 / 爆笑養老院 | 甜蜜負擔計量器;六人卡片選取 + jQuery 計算;CTA → `totoga2/ebook.html` |
| `variety/variety-shows.html` | 全員發瘋 | 綜藝節目存檔表;tabs + 搜尋;已對齊 albums.html 扁平結構 | | `vedio/variety-shows.html` | 全員發瘋 | 綜藝節目存檔表;tabs + 搜尋;已對齊 albums.html 扁平結構 |
| `variety/NJTW/NJTW5.html` | 新西遊記5 | 各自暴走專頁 | | `vedio/NJTW/NJTW5.html` | 新西遊記5 | 各自暴走專頁 |
| `vedio/vedio-list.html` | 影視清單 | 追劇進度預覽頁(乙級第三題骨架,PHP 後台在 `vedio/vedio-list/`) |
--- ---
@@ -57,7 +58,7 @@
site-guide 的 depth 數字 = nav 的層級:depth-1 是第一層 `<li>`,depth-2 是子選單項目,depth-3 是 subgroup 裡的巢狀項目。 site-guide 的 depth 數字 = nav 的層級:depth-1 是第一層 `<li>`,depth-2 是子選單項目,depth-3 是 subgroup 裡的巢狀項目。
2. **`COMING_SOON_PAGES` 只放檔名**(不含路徑)。 2. **`COMING_SOON_PAGES` 只放檔名**(不含路徑)。
`normalizePageKey` 只取 href 最後一段,所以 `variety/bangxiao.html` → 比對 `bangxiao.html`。 `normalizePageKey` 只取 href 最後一段,所以 `vedio/bangxiao.html` → 比對 `bangxiao.html`。
3. **直接連結不需要 subgroup**。 3. **直接連結不需要 subgroup**。
子選單內只要 `<li><a>` 就夠;`portal-nav__subgroup` 只在該項目還有巢狀子項目時才用(例如「傳奇的轉折」有時間軸、電子書等四項)。 子選單內只要 `<li><a>` 就夠;`portal-nav__subgroup` 只在該項目還有巢狀子項目時才用(例如「傳奇的轉折」有時間軸、電子書等四項)。
@@ -72,7 +73,7 @@
<!-- 每次對話結束後更新這個區塊 --> <!-- 每次對話結束後更新這個區塊 -->
**最後更新:** 2026-08-04 **最後更新:** 2026-08-13
**進行中:** **進行中:**
- 無 - 無
@@ -89,13 +90,14 @@
**已完成:** **已完成:**
- 建立 AGENTS.md 跨裝置上下文檔案 - 建立 AGENTS.md 跨裝置上下文檔案
- 新增 `variety/bangxiao.html`(世紀級寶藏 / 爆笑養老院,甜蜜負擔計量器) - 新增 `vedio/bangxiao.html`(世紀級寶藏 / 爆笑養老院,甜蜜負擔計量器)
- `variety/variety-shows.html` 重構:移除多層包裝,對齊 albums.html 扁平結構 - `vedio/variety-shows.html` 重構:移除多層包裝,對齊 albums.html 扁平結構
- portal-nav 新增世紀級寶藏,正確放入淪陷瞬間子選單 - portal-nav 新增世紀級寶藏,正確放入淪陷瞬間子選單
- `extra/site-guide.html` 同步更新:全員發瘋 → 世紀級寶藏 - `extra/site-guide.html` 同步更新:全員發瘋 → 世紀級寶藏
- 新增 `admin/` 推廣部後臺第一階段:登入、控制台、管理者帳號、通用 CRUD、媒體上傳及公開狀態總覽 - 新增 `admin/` 推廣部後臺第一階段:登入、控制台、管理者帳號、通用 CRUD、媒體上傳及公開狀態總覽
- 水晶熱舞社/水晶練習室/偶像練習生已於 2026-08-04 拆成獨立專案 `sechskies_fans`(另一個 repo,路徑 `d:\Developer\projects\sechskies_fans`,不在本站範圍):30TH 應援報名表單、練舞清單、後台 CRUD 與管理者登入都已搬走並改用自己的資料庫;`extra/site-guide.html` 與本站 `admin/bootstrap.php`/`admin/database/schema.sql` 已同步移除相關項目 - 水晶熱舞社/水晶練習室/偶像練習生已於 2026-08-04 拆成獨立專案 `sechskies_fans`(另一個 repo,路徑 `d:\Developer\projects\sechskies_fans`,不在本站範圍):30TH 應援報名表單、練舞清單、後台 CRUD 與管理者登入都已搬走並改用自己的資料庫;`extra/site-guide.html` 與本站 `admin/bootstrap.php`/`admin/database/schema.sql` 已同步移除相關項目
- `portal-nav.js` 頂欄新增第 7 項「水晶練習室」(Yellow Note 之後),連到獨立專案 sechskies_fans;`extra/site-guide.html` 同步新增對應項目;目前是 coming-soon 佔位,尚未接上真實網址 - `portal-nav.js` 頂欄新增第 7 項「水晶練習室」(Yellow Note 之後),連到獨立專案 sechskies_fans;`extra/site-guide.html` 同步新增對應項目;目前是 coming-soon 佔位,尚未接上真實網址
- `variety/` 資料夾整個改名為 `vedio/`,同步修正 portal-nav.js、site-guide.html、style.css 註解、docs/site-structure.md、portal-nav-rules.md 等連結
--- ---
+10 -8
View File
@@ -39,13 +39,14 @@
- **站主索引**(自用):`extra/site-guide.html`(`data-portal-nav-all-open`,不對外) - **站主索引**(自用):`extra/site-guide.html`(`data-portal-nav-all-open`,不對外)
- **內頁 header class**:`inner-page-header`(禁改回舊名) - **內頁 header class**:`inner-page-header`(禁改回舊名)
### variety/ 頁面 ### vedio/ 頁面(原 variety/,已改名)
| 檔案 | 頁面標題 | 說明 | | 檔案 | 頁面標題 | 說明 |
|------|----------|------| |------|----------|------|
| `variety/bangxiao.html` | 世紀級寶藏 / 爆笑養老院 | 甜蜜負擔計量器;六人卡片選取 + jQuery 計算;CTA → `totoga2/ebook.html` | | `vedio/bangxiao.html` | 世紀級寶藏 / 爆笑養老院 | 甜蜜負擔計量器;六人卡片選取 + jQuery 計算;CTA → `totoga2/ebook.html` |
| `variety/variety-shows.html` | 全員發瘋 | 綜藝節目存檔表;tabs + 搜尋;已對齊 albums.html 扁平結構 | | `vedio/variety-shows.html` | 全員發瘋 | 綜藝節目存檔表;tabs + 搜尋;已對齊 albums.html 扁平結構 |
| `variety/NJTW/NJTW5.html` | 新西遊記5 | 各自暴走專頁 | | `vedio/NJTW/NJTW5.html` | 新西遊記5 | 各自暴走專頁 |
| `vedio/vedio-list.html` | 影視清單 | 追劇進度預覽頁(乙級第三題骨架,PHP 後台在 `vedio/vedio-list/`) |
--- ---
@@ -57,7 +58,7 @@
site-guide 的 depth 數字 = nav 的層級:depth-1 是第一層 `<li>`,depth-2 是子選單項目,depth-3 是 subgroup 裡的巢狀項目。 site-guide 的 depth 數字 = nav 的層級:depth-1 是第一層 `<li>`,depth-2 是子選單項目,depth-3 是 subgroup 裡的巢狀項目。
2. **`COMING_SOON_PAGES` 只放檔名**(不含路徑)。 2. **`COMING_SOON_PAGES` 只放檔名**(不含路徑)。
`normalizePageKey` 只取 href 最後一段,所以 `variety/bangxiao.html` → 比對 `bangxiao.html`。 `normalizePageKey` 只取 href 最後一段,所以 `vedio/bangxiao.html` → 比對 `bangxiao.html`。
3. **直接連結不需要 subgroup**。 3. **直接連結不需要 subgroup**。
子選單內只要 `<li><a>` 就夠;`portal-nav__subgroup` 只在該項目還有巢狀子項目時才用(例如「傳奇的轉折」有時間軸、電子書等四項)。 子選單內只要 `<li><a>` 就夠;`portal-nav__subgroup` 只在該項目還有巢狀子項目時才用(例如「傳奇的轉折」有時間軸、電子書等四項)。
@@ -72,7 +73,7 @@
<!-- 每次對話結束後更新這個區塊 --> <!-- 每次對話結束後更新這個區塊 -->
**最後更新:** 2026-06-17 **最後更新:** 2026-08-13
**進行中:** **進行中:**
- 無 - 無
@@ -82,10 +83,11 @@
**已完成:** **已完成:**
- 建立 CLAUDE.md 跨裝置上下文檔案 - 建立 CLAUDE.md 跨裝置上下文檔案
- 新增 `variety/bangxiao.html`(世紀級寶藏 / 爆笑養老院,甜蜜負擔計量器) - 新增 `vedio/bangxiao.html`(世紀級寶藏 / 爆笑養老院,甜蜜負擔計量器)
- `variety/variety-shows.html` 重構:移除多層包裝,對齊 albums.html 扁平結構 - `vedio/variety-shows.html` 重構:移除多層包裝,對齊 albums.html 扁平結構
- portal-nav 新增世紀級寶藏,正確放入淪陷瞬間子選單 - portal-nav 新增世紀級寶藏,正確放入淪陷瞬間子選單
- `extra/site-guide.html` 同步更新:全員發瘋 → 世紀級寶藏 - `extra/site-guide.html` 同步更新:全員發瘋 → 世紀級寶藏
- `variety/` 資料夾整個改名為 `vedio/`,同步修正 portal-nav.js、site-guide.html、style.css 註解、docs/site-structure.md、portal-nav-rules.md 等連結
--- ---
+1 -1
View File
@@ -2695,7 +2695,7 @@ header.inner-page-header .sub-title {
padding-bottom: 48px; padding-bottom: 48px;
} }
/* variety/NJTW/NJTW5.html — 樣式改為同資料夾 njtw5.css,避免與全站 style 雙重定義 */ /* vedio/NJTW/NJTW5.html — 樣式改為同資料夾 njtw5.css,避免與全站 style 雙重定義 */
/* Chronicle (webcomic-like reader) */ /* Chronicle (webcomic-like reader) */
.chronicle-controls { .chronicle-controls {
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

+1 -1
View File
@@ -259,7 +259,7 @@
</li> </li>
</ul> </ul>
</li> </li>
<li><a target="_blank" rel="noopener noreferrer" href="${rp}variety/bangxiao.html"><span class="portal-nav__label">世紀級寶藏</span></a></li> <li><a target="_blank" rel="noopener noreferrer" href="${rp}vedio/bangxiao.html"><span class="portal-nav__label">世紀級寶藏</span></a></li>
<li><a target="_blank" rel="noopener noreferrer" href="${rp}member/member.html"><span class="portal-nav__label">完整的六顆</span></a></li> <li><a target="_blank" rel="noopener noreferrer" href="${rp}member/member.html"><span class="portal-nav__label">完整的六顆</span></a></li>
<li><a target="_blank" rel="noopener noreferrer" href="${rp}concert/concert.html"><span class="portal-nav__label">現場的震撼</span></a> <li><a target="_blank" rel="noopener noreferrer" href="${rp}concert/concert.html"><span class="portal-nav__label">現場的震撼</span></a>
<ul class="portal-submenu"> <ul class="portal-submenu">
+3 -3
View File
@@ -20,7 +20,7 @@ alwaysApply: true
1. **未經站主逐句同意**:不得從 `COMING_SOON_PAGES` **刪**檔名、不得擴充 `INDEX_NAV_OPEN_HASHES`、不得在 `buildPortalNavInnerHTML` **新增**可點項、不得把籌備中改成真 `href`、不得刪站主已同意列入頂欄的項。 1. **未經站主逐句同意**:不得從 `COMING_SOON_PAGES` **刪**檔名、不得擴充 `INDEX_NAV_OPEN_HASHES`、不得在 `buildPortalNavInnerHTML` **新增**可點項、不得把籌備中改成真 `href`、不得刪站主已同意列入頂欄的項。
2. **「檔案在 repo」≠「可開放導覽」**;不得自行推論。 2. **「檔案在 repo」≠「可開放導覽」**;不得自行推論。
3. **`totoga2/readable.html`(圖文好讀版,檔名 `readable.html`)**、**`variety/variety.html`**:站主**沒有明講對外開放**時,檔名**必須**留在 `COMING_SOON_PAGES`(訪客頂欄不可點進)。要開放時**只**能等站主說了再從 Set 移除。 3. **`totoga2/readable.html`(圖文好讀版,檔名 `readable.html`)**、**`vedio/variety.html`**:站主**沒有明講對外開放**時,檔名**必須**留在 `COMING_SOON_PAGES`(訪客頂欄不可點進)。要開放時**只**能等站主說了再從 Set 移除。
4. **「刪除/拿掉/不要了」**:若未寫明是**單一檔案**、**頂欄某一列**、**整段子選單**還是**整個資料夾**,**一律先問站主**,禁止自行擴大刪除範圍。 4. **「刪除/拿掉/不要了」**:若未寫明是**單一檔案**、**頂欄某一列**、**整段子選單**還是**整個資料夾**,**一律先問站主**,禁止自行擴大刪除範圍。
5. **不確定**是否開放、連哪、是否例外:**先問站主再改**,不要猜。 5. **不確定**是否開放、連哪、是否例外:**先問站主再改**,不要猜。
6. 站主說刪 **`某頁#錨點`**:見下方 **§8 頁面錨點刪除**(先改 DOM,再修死鏈;禁止只動導覽)。 6. 站主說刪 **`某頁#錨點`**:見下方 **§8 頁面錨點刪除**(先改 DOM,再修死鏈;禁止只動導覽)。
@@ -32,7 +32,7 @@ alwaysApply: true
在 **`buildPortalNavInnerHTML`**(`assets/js/portal-nav.js`)裡維護連結: 在 **`buildPortalNavInnerHTML`**(`assets/js/portal-nav.js`)裡維護連結:
- 依資訊架構放在對應的 **`<li>`** 底下;需要子選單時用 **`portal-submenu`**。 - 依資訊架構放在對應的 **`<li>`** 底下;需要子選單時用 **`portal-submenu`**。
- `href` 使用**站根相對路徑**(腳本會依當前頁深度加上前綴 `rp`):例如 `` `${rp}variety/variety.html` ``、`` `${rp}index.html#section` ``。 - `href` 使用**站根相對路徑**(腳本會依當前頁深度加上前綴 `rp`):例如 `` `${rp}vedio/variety.html` ``、`` `${rp}index.html#section` ``。
- Logo 列:`` `${rp}index.html` `` 與 `` `${rp}assets/images/logov.svg` ``。 - Logo 列:`` `${rp}index.html` `` 與 `` `${rp}assets/images/logov.svg` ``。
新增或調整完後,所有帶 **`data-portal-nav`** 的 `<nav class="portal-nav">` 都會在載入腳本時被注入同一份 HTML。 新增或調整完後,所有帶 **`data-portal-nav`** 的 `<nav class="portal-nav">` 都會在載入腳本時被注入同一份 HTML。
@@ -93,7 +93,7 @@ alwaysApply: true
<script src="assets/js/portal-nav.js"></script> <script src="assets/js/portal-nav.js"></script>
``` ```
內頁(例如 `extra/`、`fallen/`、`member/`、`yellow-note/`、`variety/` 下)請用相對站根深度,例如: 內頁(例如 `extra/`、`fallen/`、`member/`、`yellow-note/`、`vedio/` 下)請用相對站根深度,例如:
```html ```html
<script src="../assets/js/portal-nav.js"></script> <script src="../assets/js/portal-nav.js"></script>
+3 -3
View File
@@ -6,8 +6,8 @@
|----------------|------------------| |----------------|------------------|
| 淪陷瞬間 · 初心與軌跡 | `index.html` | | 淪陷瞬間 · 初心與軌跡 | `index.html` |
| 傳奇的轉折 | `fallen/totoga2.html` | | 傳奇的轉折 | `fallen/totoga2.html` |
| 全員發瘋 · 隊長帶弟弟 | `variety/variety.html`(錨點 `#variety`) | | 全員發瘋 · 隊長帶弟弟 | `vedio/variety.html`(錨點 `#variety`) |
| 全員發瘋 · 各自暴走 · 新西遊記 | `variety/NJTW/NJTW5.html` | | 全員發瘋 · 各自暴走 · 新西遊記 | `vedio/NJTW/NJTW5.html` |
| 完整的六顆 | `member/member.html` | | 完整的六顆 | `member/member.html` |
| 現場的震撼 | `concert/concert.html` | | 現場的震撼 | `concert/concert.html` |
| 黃色留聲機 | `albums/albums.html` | | 黃色留聲機 | `albums/albums.html` |
@@ -21,7 +21,7 @@
- `member/` — 成員介紹(六顆水晶) - `member/` — 成員介紹(六顆水晶)
- `concert/`、`albums/` — 與選單同名主題 - `concert/`、`albums/` — 與選單同名主題
- `yellow-note/` — Yellow Note(minister + map) - `yellow-note/` — Yellow Note(minister + map)
- `variety/` — 綜藝與 NJTW 子頁 - `vedio/` — 綜藝與 NJTW 子頁
- `extra/` — 未掛主欄之草稿、複本、全站索引 `site-guide.html` 等 - `extra/` — 未掛主欄之草稿、複本、全站索引 `site-guide.html` 等
- `assets/` — 共用靜態資源 - `assets/` — 共用靜態資源
- `docs/sql/` — SQL 練習、計畫(`sql_plan.md`)與 `.sql` 腳本/匯出 - `docs/sql/` — SQL 練習、計畫(`sql_plan.md`)與 `.sql` 腳本/匯出
+7 -7
View File
@@ -80,18 +80,18 @@
<span class="sg-node sg-node--extra">首頁區塊錨點:<code>#start</code>·<code>#mission</code>·<code>#timeline</code>(<code>#story</code> 未開放)</span> <span class="sg-node sg-node--extra">首頁區塊錨點:<code>#start</code>·<code>#mission</code>·<code>#timeline</code>(<code>#story</code> 未開放)</span>
</li> </li>
<li data-site-guide-depth="2"> <li data-site-guide-depth="2">
<span class="sg-node"><a href="../variety/bangxiao.html">世紀級寶藏</a></span> <span class="sg-node"><a href="../vedio/bangxiao.html">世紀級寶藏</a></span>
<ul> <ul>
<li data-site-guide-depth="3"><span class="sg-node sg-node--extra">頁內「爆笑養老院」甜蜜負擔計量器區塊(同頁,非另一個檔案)</span></li> <li data-site-guide-depth="3"><span class="sg-node sg-node--extra">頁內「爆笑養老院」甜蜜負擔計量器區塊(同頁,非另一個檔案)</span></li>
<li data-site-guide-depth="3"> <li data-site-guide-depth="3">
<span class="sg-node sg-node--extra">variety/ 其他相關頁(不在頂欄)</span> <span class="sg-node sg-node--extra">vedio/ 其他相關頁(不在頂欄)</span>
<ul> <ul>
<li data-site-guide-depth="4"><span class="sg-node sg-node--extra"><a href="../variety/variety.html#variety">綜藝主頁・隊長帶弟弟</a></span></li> <li data-site-guide-depth="4"><span class="sg-node sg-node--extra"><a href="../vedio/variety.html#variety">綜藝主頁・隊長帶弟弟</a></span></li>
<li data-site-guide-depth="4"><span class="sg-node sg-node--extra"><a href="../variety/variety-shows.html">節目存檔表</a></span></li> <li data-site-guide-depth="4"><span class="sg-node sg-node--extra"><a href="../vedio/variety-shows.html">節目存檔表</a></span></li>
<li data-site-guide-depth="4"> <li data-site-guide-depth="4">
<span class="sg-node sg-node--extra sg-node--nolink">各自暴走</span> <span class="sg-node sg-node--extra sg-node--nolink">各自暴走</span>
<ul> <ul>
<li data-site-guide-depth="4"><span class="sg-node sg-node--extra"><a href="../variety/NJTW/NJTW5.html">新西遊記</a></span></li> <li data-site-guide-depth="4"><span class="sg-node sg-node--extra"><a href="../vedio/NJTW/NJTW5.html">新西遊記</a></span></li>
</ul> </ul>
</li> </li>
</ul> </ul>
@@ -168,8 +168,8 @@
<li data-site-guide-depth="3"><span class="sg-node sg-node--extra"><a href="../s20260612_04_jq_calculator.html">s20260612_04_jq_calculator.html(學校作業)</a></span></li> <li data-site-guide-depth="3"><span class="sg-node sg-node--extra"><a href="../s20260612_04_jq_calculator.html">s20260612_04_jq_calculator.html(學校作業)</a></span></li>
<li data-site-guide-depth="3"><span class="sg-node sg-node--extra"><a href="../sechskies_brand_color_spec.html">sechskies_brand_color_spec.html(品牌色規範)</a></span></li> <li data-site-guide-depth="3"><span class="sg-node sg-node--extra"><a href="../sechskies_brand_color_spec.html">sechskies_brand_color_spec.html(品牌色規範)</a></span></li>
<li data-site-guide-depth="3"><span class="sg-node sg-node--extra"><a href="../totoga2/readable_ebookcontent.html">totoga2/readable_ebookcontent.html(電子書內容草稿)</a></span></li> <li data-site-guide-depth="3"><span class="sg-node sg-node--extra"><a href="../totoga2/readable_ebookcontent.html">totoga2/readable_ebookcontent.html(電子書內容草稿)</a></span></li>
<li data-site-guide-depth="3"><span class="sg-node sg-node--extra"><a href="../variety/vedio-list.html">variety/vedio-list.html(深夜清單,追劇進度預覽頁)</a></span></li> <li data-site-guide-depth="3"><span class="sg-node sg-node--extra"><a href="../vedio/vedio-list.html">vedio/vedio-list.html(深夜清單,追劇進度預覽頁)</a></span></li>
<li data-site-guide-depth="3"><span class="sg-node sg-node--extra" title="admin/api/back 等 PHP 後台,非靜態站主線">variety/vedio-list/(追劇清單後台練習,非本站主線)</span></li> <li data-site-guide-depth="3"><span class="sg-node sg-node--extra" title="admin/api/back 等 PHP 後台,非靜態站主線">vedio/vedio-list/(追劇清單後台練習,非本站主線)</span></li>
</ul> </ul>
</li> </li>
<li data-site-guide-depth="2"> <li data-site-guide-depth="2">
-47
View File
@@ -1,47 +0,0 @@
<!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="../assets/css/style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="../assets/css/tablet.css">
</head>
<body class="chronicle-page">
<header class="inner-page-header">
<div class="header-inner site-header-inner">
<h1 class="main-title">瘋子與天才</h1>
<div class="subtitle-wrap">
<span class="sub-title">EUN JI-WON × VARIETY GRAVITY</span>
</div>
</div>
</header>
<nav class="portal-nav" aria-label="水晶男孩推廣部導覽" data-portal-nav></nav>
<canvas id="particles"></canvas>
<main class="content-container variety-page-main chronicle-reader">
<section id="variety" class="variety-block">
<h2 class="section-title">瘋狂出演中</h2>
<p class="intro-text">鏡頭一開就跑偏、台詞唸不完,卻總能把段子的節奏剪成「非他不可」——這是最容易被路人一眼看見的瘋勁,也是綜藝剪輯願意一路卡特寫給他的理由。</p>
<p class="intro-text">卸妝後的聊天、運動場上的暴走、對成員毫不修飾的吐槽;那些看起來像日常,其實比劇本更誇張。</p>
<p class="intro-text"><a href="variety-shows.html#variety-shows">全員發瘋</a>:團綜「隊長帶弟弟」與個綜「各自暴走」分表整理;名場面與入坑路線仍會陸續補在本區。</p>
</section>
</main>
<footer>
<p>SECHSKIES Fanpage &copy; 2026 - 黃色氣球永遠飄揚</p>
<a href="https://jekkinoopy.github.io/Portfolio/" class="studio-link" target="_blank">
Design by <span class="mark_b">Jekkinoopy Studio</span>
</a>
</footer>
<script src="../assets/js/portal-nav.js"></script>
<script src="../assets/js/particles.js"></script>
</body>
</html>
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -62,7 +62,7 @@ body.njtw5-page header.main-header {
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
/* 圖檔與此 CSS 同資料夾(variety/NJTW/) */ /* 圖檔與此 CSS 同資料夾(vedio/NJTW/) */
background-color: #1a1812; background-color: #1a1812;
background-image: url("1538075001_cu.jpg"); background-image: url("1538075001_cu.jpg");
background-size: cover; background-size: cover;
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -4,13 +4,13 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>水晶男孩|追劇清單</title> <title>水晶男孩|影視清單</title>
<link rel="stylesheet" href="../assets/css/style.css"> <link rel="stylesheet" href="../assets/css/style.css">
<link rel="stylesheet" href="../assets/css/index.css"> <link rel="stylesheet" href="../assets/css/index.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="../assets/css/tablet.css"> <link rel="stylesheet" href="../assets/css/tablet.css">
<style> <style>
/* ── 追劇清單頁面專用樣式 ── */ /* ── 影視清單頁面專用樣式 ── */
.vedio-list-page .vedio-intro { .vedio-list-page .vedio-intro {
text-align: left; text-align: left;
@@ -114,6 +114,19 @@
text-align: center; text-align: center;
padding: 0.75rem; padding: 0.75rem;
box-sizing: border-box; box-sizing: border-box;
overflow: hidden;
}
.vedio-list-page .drama-card__poster--image {
padding: 0;
background: var(--color-surface-soft);
}
.vedio-list-page .drama-card__poster--image img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
} }
.vedio-list-page .drama-card__meta { .vedio-list-page .drama-card__meta {
@@ -320,7 +333,7 @@
<body class="vedio-list-page"> <body class="vedio-list-page">
<header class="inner-page-header"> <header class="inner-page-header">
<div class="header-inner site-header-inner"> <div class="header-inner site-header-inner">
<h1 class="main-title">深夜清單</h1> <h1 class="main-title">影視清單</h1>
<div class="subtitle-wrap"> <div class="subtitle-wrap">
<span class="sub-title">DRAMA &amp; VARIETY WATCH LIST</span> <span class="sub-title">DRAMA &amp; VARIETY WATCH LIST</span>
</div> </div>
@@ -335,38 +348,33 @@
<section class="vedio-intro"> <section class="vedio-intro">
<h2 class="section-title">正在追的那些夜晚</h2> <h2 class="section-title">正在追的那些夜晚</h2>
<p> <p>綜藝裡的失控笑聲、戲劇裡的心跳停拍,都值得被好好記下來:看到哪一季、哪一集、哪一句台詞剛把你釘在沙發上。
有些夜晚適合考古黃色奇蹟,有些夜晚則是——螢幕一亮,人就栽進另一個世界。
綜藝裡的失控笑聲、戲劇裡的心跳停拍,都值得被好好記下來:看到哪一季、哪一集、哪一句台詞剛把你釘在沙發上。
</p> </p>
<p> <p>
這裡先收兩部最近在追的:一部是把人丟進肯亞還能笑到缺氧的<span class="mark_y">韓綜</span>, 這裡先收兩部最近在追的:一部是把人丟進肯亞還能笑到缺氧的<span class="mark_y">韓綜</span>,
一部是把 1997 年的青春寫得又甜又疼的<span class="mark_b">韓劇</span>。 一部是把 1997 年的青春寫得又甜又疼的<span class="mark_b">韓劇</span>。
點進去,進度與入口都在;先別急著一次追完,慢慢養也很好看。 點進去,進度與入口都在;先別急著一次追完,慢慢養也很好看。
</p> </p>
<p> <!-- <p>
此頁為線上靜態預覽。完整資料庫與後台 CRUD(乙級第三題骨架)請本機 XAMPP 匯入 此頁為線上靜態預覽。完整資料庫與後台 CRUD(乙級第三題骨架)請本機 XAMPP 匯入
<code>variety/vedio-list/dramas.sql</code> 後,開啟 <code>vedio/vedio-list/dramas.sql</code> 後,開啟
<a href="vedio-list/"><code>variety/vedio-list/</code></a>。 <a href="vedio-list/"><code>vedio/vedio-list/</code></a>。
</p> </p> -->
</section> </section>
<section class="list-section" aria-labelledby="vedio-list-heading"> <section class="list-section" aria-labelledby="vedio-list-heading">
<h2 class="section-title" id="vedio-list-heading">追劇清單</h2> <h2 class="section-title" id="vedio-list-heading">影視清單</h2>
<p class="list-lead">先用類型找——綜藝還是戲劇,跟 OTT 首頁那排分類一樣直覺。</p> <!-- <p class="list-lead">先用類型找——綜藝還是戲劇,跟 OTT 首頁那排分類一樣直覺。</p> -->
<div class="type-tabs" role="tablist" aria-label="作品類型"> <div class="type-tabs" role="tablist" aria-label="作品類型">
<button type="button" class="type-tab is-active" role="tab" aria-selected="true" <button type="button" class="type-tab is-active" role="tab" aria-selected="true"
data-type="all">全部</button> data-type="all">全部</button>
<button type="button" class="type-tab" role="tab" aria-selected="false" <button type="button" class="type-tab" role="tab" aria-selected="false" data-type="variety">綜藝</button>
data-type="variety">綜藝</button> <button type="button" class="type-tab" role="tab" aria-selected="false" data-type="drama">戲劇</button>
<button type="button" class="type-tab" role="tab" aria-selected="false"
data-type="drama">戲劇</button>
</div> </div>
<div class="drama-grid" id="dramaGrid"> <div class="drama-grid" id="dramaGrid">
<article class="drama-card" data-type="variety" data-id="kenya" tabindex="0" <article class="drama-card" data-type="variety" data-id="kenya" tabindex="0" aria-label="三傻遊肯亞,綜藝">
aria-label="三傻遊肯亞,綜藝">
<div class="drama-card__poster" aria-hidden="true">NETFLIX · 綜藝</div> <div class="drama-card__poster" aria-hidden="true">NETFLIX · 綜藝</div>
<div class="drama-card__meta"> <div class="drama-card__meta">
<span class="drama-tag">綜藝</span> <span class="drama-tag">綜藝</span>
@@ -376,16 +384,17 @@
<p class="drama-card__progress">觀看進度:第 1 季・第 2 集・看到 28:40</p> <p class="drama-card__progress">觀看進度:第 1 季・第 2 集・看到 28:40</p>
<p class="drama-card__note">三人剛被扔進野外,表情比獅子還慌。</p> <p class="drama-card__note">三人剛被扔進野外,表情比獅子還慌。</p>
<div class="drama-card__actions"> <div class="drama-card__actions">
<a class="is-primary" href="https://www.netflix.com/title/82031207" <a class="is-primary" href="https://www.netflix.com/tw/title/82031207" target="_blank"
target="_blank" rel="noopener noreferrer" rel="noopener noreferrer" onclick="event.stopPropagation()">繼續觀看</a>
onclick="event.stopPropagation()">繼續觀看</a>
<button type="button" data-open-detail="kenya">查看詳情</button> <button type="button" data-open-detail="kenya">查看詳情</button>
</div> </div>
</article> </article>
<article class="drama-card" data-type="drama" data-id="reply1997" tabindex="0" <article class="drama-card" data-type="drama" data-id="reply1997" tabindex="0" aria-label="請回答1997,戲劇">
aria-label="請回答1997,戲劇"> <!-- 海報來源:Wikipedia,版權歸原著作權人所有 -->
<div class="drama-card__poster" aria-hidden="true">DISNEY+ · 戲劇</div> <div class="drama-card__poster drama-card__poster--image" aria-hidden="true">
<img src="https://upload.wikimedia.org/wikipedia/zh/a/a9/Reply_1997.jpg" alt="" loading="lazy">
</div>
<div class="drama-card__meta"> <div class="drama-card__meta">
<span class="drama-tag">戲劇</span> <span class="drama-tag">戲劇</span>
<span class="drama-tag drama-tag--platform">Disney+</span> <span class="drama-tag drama-tag--platform">Disney+</span>
@@ -394,9 +403,9 @@
<p class="drama-card__progress">觀看進度:第 1 季・第 5 集・看到 41:12</p> <p class="drama-card__progress">觀看進度:第 1 季・第 5 集・看到 41:12</p>
<p class="drama-card__note">宿舍夜談剛結束,收音機還在響,心卻已經開始亂。</p> <p class="drama-card__note">宿舍夜談剛結束,收音機還在響,心卻已經開始亂。</p>
<div class="drama-card__actions"> <div class="drama-card__actions">
<a class="is-primary" href="https://www.disneyplus.com/en-tw/browse/entity-3979fb82-8681-44e8-b7be-e313091f0d73" <a class="is-primary"
target="_blank" rel="noopener noreferrer" href="https://www.disneyplus.com/en-tw/browse/entity-3979fb82-8681-44e8-b7be-e313091f0d73"
onclick="event.stopPropagation()">繼續觀看</a> target="_blank" rel="noopener noreferrer" onclick="event.stopPropagation()">繼續觀看</a>
<button type="button" data-open-detail="reply1997">查看詳情</button> <button type="button" data-open-detail="reply1997">查看詳情</button>
</div> </div>
</article> </article>
@@ -438,8 +447,8 @@
intro: '三個人、一座肯亞,以及無數個「這也能出事?」的現場。笑點來得又急又野,像把旅行節目的安全感整袋拆掉,只留下腎上腺素與默契。', intro: '三個人、一座肯亞,以及無數個「這也能出事?」的現場。笑點來得又急又野,像把旅行節目的安全感整袋拆掉,只留下腎上腺素與默契。',
progress: '第 1 季|第 2 集|28:40', progress: '第 1 季|第 2 集|28:40',
note: '三人剛被扔進野外,表情比獅子還慌。', note: '三人剛被扔進野外,表情比獅子還慌。',
extra: '狀態:追劇中 · 開始看的那天:最近才入坑,進度還很乖。', extra: '狀態:影視中 · 開始看的那天:最近才入坑,進度還很乖。',
watchUrl: 'https://www.netflix.com/title/82031207' watchUrl: 'https://www.netflix.com/tw/title/82031207'
}, },
reply1997: { reply1997: {
typeLabel: '戲劇|Disney+', typeLabel: '戲劇|Disney+',
@@ -447,7 +456,7 @@
intro: '把 1997 寫進宿舍、收音機與那一聲來不及說出口的喜歡。甜的時候甜到發亮,疼的時候又老實得讓人不敢快轉——那種青春,看完會想立刻打電話給誰。', intro: '把 1997 寫進宿舍、收音機與那一聲來不及說出口的喜歡。甜的時候甜到發亮,疼的時候又老實得讓人不敢快轉——那種青春,看完會想立刻打電話給誰。',
progress: '第 1 季|第 5 集|41:12', progress: '第 1 季|第 5 集|41:12',
note: '宿舍夜談剛結束,收音機還在響,心卻已經開始亂。', note: '宿舍夜談剛結束,收音機還在響,心卻已經開始亂。',
extra: '狀態:追劇中 · 提醒自己:下一集容易哭,衛生紙先備好。', extra: '狀態:影視中 · 提醒自己:下一集容易哭,衛生紙先備好。',
watchUrl: 'https://www.disneyplus.com/en-tw/browse/entity-3979fb82-8681-44e8-b7be-e313091f0d73' watchUrl: 'https://www.disneyplus.com/en-tw/browse/entity-3979fb82-8681-44e8-b7be-e313091f0d73'
} }
}; };
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,10 @@
# 打歌舞台後台|乙級練習版
1. 開啟 XAMPP Control Panel,啟動 Apache 與 MySQL。
2. 進入 phpMyAdmin,匯入 `setup.sql`。
3. 將本資料夾放入 `C:\xampp\htdocs\`,或在此資料夾執行:
`C:\xampp\php\php.exe -S 127.0.0.1:8080`
4. 開啟 `http://127.0.0.1:8080/login.php`。
5. 練習帳號:`admin`;密碼:`1234`。
練習內容:Session 登入、PDO、CRUD、預備敘述、防 XSS、CSRF、列表排序、顯示/隱藏與 JSON API。
@@ -0,0 +1,5 @@
<?php
require __DIR__ . '/config.php';
header('Content-Type: application/json; charset=utf-8');
$rows = $pdo->query('SELECT stage_date,youtube_id,station,program,title,note,sort_order FROM stages WHERE is_visible=1 ORDER BY sort_order,stage_date,id')->fetchAll();
echo json_encode(['stages'=>$rows], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
session_start();
const DB_HOST = '127.0.0.1';
const DB_NAME = 'three_words_10th';
const DB_USER = 'root';
const DB_PASS = '';
try {
$pdo = new PDO(
'mysql:host=' . DB_HOST . ';dbname=' . DB_NAME . ';charset=utf8mb4',
DB_USER,
DB_PASS,
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]
);
} catch (PDOException $e) {
http_response_code(500);
exit('資料庫連線失敗,請先啟動 XAMPP MySQL 並匯入 setup.sql。');
}
function e(?string $value): string { return htmlspecialchars($value ?? '', ENT_QUOTES, 'UTF-8'); }
function loggedIn(): bool { return isset($_SESSION['admin_id']); }
function requireLogin(): void { if (!loggedIn()) { header('Location: login.php'); exit; } }
function csrf(): string {
if (empty($_SESSION['csrf'])) $_SESSION['csrf'] = bin2hex(random_bytes(24));
return $_SESSION['csrf'];
}
function checkCsrf(): void {
if (!hash_equals($_SESSION['csrf'] ?? '', $_POST['csrf'] ?? '')) exit('表單驗證失敗');
}
function youtubeId(string $url): string {
if (preg_match('~(?:youtu\.be/|youtube\.com/(?:watch\?v=|embed/|shorts/))([\w-]{11})~', $url, $m)) return $m[1];
return preg_match('/^[\w-]{11}$/', $url) ? $url : '';
}
@@ -0,0 +1,5 @@
<?php
require __DIR__ . '/config.php'; requireLogin(); checkCsrf();
$stmt = $pdo->prepare('DELETE FROM stages WHERE id = ?');
$stmt->execute([(int)($_POST['id'] ?? 0)]);
header('Location: index.php');
@@ -0,0 +1,24 @@
<?php
require __DIR__ . '/config.php';
requireLogin();
$edit = ['id'=>'','stage_date'=>'','youtube_id'=>'','station'=>'','program'=>'','title'=>'','note'=>'','sort_order'=>0,'is_visible'=>1];
if (isset($_GET['edit'])) {
$stmt = $pdo->prepare('SELECT * FROM stages WHERE id = ?'); $stmt->execute([(int)$_GET['edit']]);
$edit = $stmt->fetch() ?: $edit;
}
$stages = $pdo->query('SELECT * FROM stages ORDER BY sort_order ASC, stage_date ASC, id ASC')->fetchAll();
?>
<!doctype html><html lang="zh-Hant"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>打歌舞台後台</title><link rel="stylesheet" href="style.css"></head><body>
<header><div><p class="eyebrow">THREE WORDS · 10TH</p><h1>打歌舞台後台</h1></div><nav><a href="stages.php" target="_blank">查看前台 ↗</a><a href="api.php" target="_blank">JSON API ↗</a><a href="logout.php">登出</a></nav></header>
<main class="adminGrid">
<section class="panel formPanel"><div class="panelTitle"><span>01</span><h2><?=$edit['id']?'修改舞台':'新增舞台'?></h2></div>
<form method="post" action="save.php"><input type="hidden" name="csrf" value="<?=csrf()?>"><input type="hidden" name="id" value="<?=e((string)$edit['id'])?>">
<div class="two"><label>播出日期<input type="date" name="stage_date" required value="<?=e($edit['stage_date'])?>"></label><label>顯示順序<input type="number" name="sort_order" value="<?=e((string)$edit['sort_order'])?>"></label></div>
<label>YouTube 網址或影片 ID<input name="youtube_url" required placeholder="https://www.youtube.com/watch?v=..." value="<?=e($edit['youtube_id'])?>"></label>
<div class="two"><label>電視台<input name="station" required placeholder="SBS" value="<?=e($edit['station'])?>"></label><label>節目名稱<input name="program" required placeholder="人氣歌謠" value="<?=e($edit['program'])?>"></label></div>
<label>舞台標題<input name="title" required value="<?=e($edit['title'])?>"></label><label>備註<textarea name="note" rows="3"><?=e($edit['note'])?></textarea></label>
<label class="check"><input type="checkbox" name="is_visible" value="1" <?=$edit['is_visible']?'checked':''?>> 前台顯示</label><div class="formActions"><button><?=$edit['id']?'儲存修改':'新增資料'?></button><?php if($edit['id']):?><a href="index.php">取消修改</a><?php endif;?></div></form></section>
<section class="panel listPanel"><div class="panelTitle"><span>02</span><h2>資料列表</h2><b><?=count($stages)?> 筆</b></div>
<div class="tableWrap"><table><thead><tr><th>順序</th><th>日期/縮圖</th><th>電視台/舞台</th><th>狀態</th><th>操作</th></tr></thead><tbody>
<?php foreach($stages as $row):?><tr><td><?=e((string)$row['sort_order'])?></td><td><time><?=e($row['stage_date'])?></time><img src="https://i.ytimg.com/vi/<?=e($row['youtube_id'])?>/mqdefault.jpg" alt="影片縮圖"></td><td><strong><?=e($row['station'])?> · <?=e($row['program'])?></strong><span><?=e($row['title'])?></span></td><td><i class="status <?=$row['is_visible']?'on':''?>"><?=$row['is_visible']?'顯示':'隱藏'?></i></td><td class="ops"><a href="?edit=<?=$row['id']?>">修改</a><form method="post" action="delete.php" onsubmit="return confirm('確定刪除這筆資料?')"><input type="hidden" name="csrf" value="<?=csrf()?>"><input type="hidden" name="id" value="<?=$row['id']?>"><button>刪除</button></form></td></tr><?php endforeach; ?>
</tbody></table></div></section></main></body></html>
@@ -0,0 +1,20 @@
<?php
require __DIR__ . '/config.php';
if (loggedIn()) { header('Location: index.php'); exit; }
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
checkCsrf();
$stmt = $pdo->prepare('SELECT * FROM admins WHERE account = ? AND password = SHA2(?, 256)');
$stmt->execute([trim($_POST['account'] ?? ''), $_POST['password'] ?? '']);
if ($admin = $stmt->fetch()) {
session_regenerate_id(true);
$_SESSION['admin_id'] = $admin['id'];
$_SESSION['admin_account'] = $admin['account'];
header('Location: index.php'); exit;
}
$error = '帳號或密碼錯誤';
}
?>
<!doctype html><html lang="zh-Hant"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>後台登入</title><link rel="stylesheet" href="style.css"></head>
<body class="loginPage"><main class="loginCard"><p class="eyebrow">THREE WORDS · ADMIN</p><h1>舞台資料管理</h1><p>乙級練習:Session 登入與資料庫驗證</p><?php if ($error): ?><div class="alert"><?=e($error)?></div><?php endif; ?>
<form method="post"><input type="hidden" name="csrf" value="<?=csrf()?>"><label>帳號<input name="account" required autofocus value="admin"></label><label>密碼<input type="password" name="password" required value="1234"></label><button>登入後台</button></form></main></body></html>
@@ -0,0 +1,5 @@
<?php
require __DIR__ . '/config.php';
$_SESSION = [];
session_destroy();
header('Location: login.php');
@@ -0,0 +1,14 @@
<?php
require __DIR__ . '/config.php'; requireLogin(); checkCsrf();
$id = (int)($_POST['id'] ?? 0);
$youtube = youtubeId(trim($_POST['youtube_url'] ?? ''));
if (!$youtube) exit('YouTube 網址或 ID 格式錯誤');
$data = [$_POST['stage_date'] ?? '', $youtube, trim($_POST['station'] ?? ''), trim($_POST['program'] ?? ''), trim($_POST['title'] ?? ''), trim($_POST['note'] ?? ''), (int)($_POST['sort_order'] ?? 0), isset($_POST['is_visible']) ? 1 : 0];
if ($id) {
$stmt = $pdo->prepare('UPDATE stages SET stage_date=?,youtube_id=?,station=?,program=?,title=?,note=?,sort_order=?,is_visible=? WHERE id=?');
$data[] = $id; $stmt->execute($data);
} else {
$stmt = $pdo->prepare('INSERT INTO stages(stage_date,youtube_id,station,program,title,note,sort_order,is_visible) VALUES(?,?,?,?,?,?,?,?)');
$stmt->execute($data);
}
header('Location: index.php');
@@ -0,0 +1,27 @@
CREATE DATABASE IF NOT EXISTS three_words_10th CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE three_words_10th;
CREATE TABLE IF NOT EXISTS admins (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
account VARCHAR(50) NOT NULL UNIQUE,
password CHAR(64) NOT NULL
);
CREATE TABLE IF NOT EXISTS stages (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
stage_date DATE NOT NULL,
youtube_id VARCHAR(11) NOT NULL,
station VARCHAR(30) NOT NULL,
program VARCHAR(100) NOT NULL,
title VARCHAR(150) NOT NULL,
note VARCHAR(255) NOT NULL DEFAULT '',
sort_order INT NOT NULL DEFAULT 0,
is_visible TINYINT(1) NOT NULL DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
INSERT IGNORE INTO admins (account, password) VALUES ('admin', SHA2('1234', 256));
INSERT INTO stages (stage_date, youtube_id, station, program, title, note, sort_order)
SELECT '2016-12-04', '8HTqr3Wp-R0', 'SBS', '인기가요 · Inkigayo', 'THREE WORDS · Goodbye Stage', '十週年收藏舞台', 10
WHERE NOT EXISTS (SELECT 1 FROM stages WHERE youtube_id = '8HTqr3Wp-R0');
@@ -0,0 +1,2 @@
<?php require __DIR__ . '/config.php'; $stages=$pdo->query('SELECT * FROM stages WHERE is_visible=1 ORDER BY sort_order,stage_date,id')->fetchAll(); ?>
<!doctype html><html lang="zh-Hant"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>THREE WORDS 打歌舞台</title><link rel="stylesheet" href="style.css"></head><body class="front"><main><p class="eyebrow">MUSIC SHOW ARCHIVE</p><h1>打歌舞台</h1><div class="frontTable"><div class="frontHead"><span>DATE</span><span>VIDEO</span><span>STATION</span></div><?php foreach($stages as $s):?><article><time><?=e($s['stage_date'])?></time><a href="https://youtu.be/<?=e($s['youtube_id'])?>" target="_blank"><img src="https://i.ytimg.com/vi/<?=e($s['youtube_id'])?>/hqdefault.jpg" alt="<?=e($s['title'])?>"><div><strong><?=e($s['title'])?></strong><small>WATCH ↗</small></div></a><div class="tv"><strong><?=e($s['station'])?></strong><span><?=e($s['program'])?></span></div></article><?php endforeach;?></div><a class="back" href="index.php">回後台</a></main></body></html>
@@ -0,0 +1,2 @@
:root{--ink:#07101d;--cream:#f2ede2;--gold:#efb53f;--blue:#16334d;--muted:#69727d}*{box-sizing:border-box}body{margin:0;background:#e8e2d6;color:var(--ink);font-family:Arial,"Microsoft JhengHei",sans-serif}a{color:inherit}.eyebrow{font:700 10px Arial;letter-spacing:.3em;color:#8a6c19}header{display:flex;justify-content:space-between;align-items:end;padding:32px 4vw;background:var(--ink);color:var(--cream)}h1{margin:8px 0 0;font:900 clamp(35px,5vw,68px)/1 serif}header nav{display:flex;gap:18px;flex-wrap:wrap}header nav a{text-decoration:none;border-bottom:1px solid var(--gold);padding-bottom:4px}.adminGrid{display:grid;grid-template-columns:minmax(310px,.7fr) minmax(600px,1.6fr);gap:24px;padding:28px 4vw}.panel{background:var(--cream);padding:25px;box-shadow:0 15px 45px rgba(7,16,29,.08)}.panelTitle{display:flex;align-items:center;gap:13px;margin-bottom:24px;border-bottom:1px solid #aaa;padding-bottom:15px}.panelTitle span{display:grid;place-content:center;width:35px;height:35px;border-radius:50%;background:var(--gold);font-weight:900}.panelTitle h2{margin:0;font:800 28px serif}.panelTitle b{margin-left:auto;color:var(--muted)}label{display:grid;gap:8px;margin:0 0 17px;font-size:13px;font-weight:700}input,textarea{width:100%;border:1px solid #aaa;background:#fffdf7;padding:11px;font:inherit}.two{display:grid;grid-template-columns:1fr 1fr;gap:14px}.check{display:flex;align-items:center}.check input{width:auto}.formActions{display:flex;align-items:center;gap:15px}.formActions button,.loginCard button{border:0;background:var(--gold);padding:13px 20px;font-weight:900;cursor:pointer}.tableWrap{overflow:auto}table{width:100%;border-collapse:collapse;min-width:760px}th,td{text-align:left;border-bottom:1px solid #aaa;padding:12px 10px;vertical-align:middle}th{font:700 9px Arial;letter-spacing:.15em;color:#76682f}td img{display:block;width:130px;aspect-ratio:16/9;object-fit:cover;margin-top:8px}td strong,td span{display:block}td span{margin-top:7px;color:var(--muted);font-size:13px}.status{display:inline-block;padding:5px 8px;background:#bbb;font-style:normal;font-size:12px}.status.on{background:var(--gold)}.ops{display:flex;gap:9px;align-items:center}.ops form{margin:0}.ops button{border:0;background:transparent;color:#9b2f22;text-decoration:underline;cursor:pointer}.loginPage{min-height:100vh;display:grid;place-content:center;background:radial-gradient(circle at 80% 10%,#3f5c72,var(--ink) 55%)}.loginCard{width:min(440px,90vw);padding:38px;background:var(--cream);box-shadow:0 30px 90px #0006}.loginCard h1{font-size:48px}.alert{padding:10px;background:#ffd9d2;color:#922;margin:15px 0}.front{background:var(--cream)}.front main{padding:80px 8vw}.front h1{font-size:90px}.frontTable{border-top:2px solid var(--ink);margin-top:50px}.frontHead,.frontTable article{display:grid;grid-template-columns:.55fr 2fr .8fr;gap:25px;align-items:center}.frontHead{padding:12px 0;border-bottom:1px solid #aaa;font:700 9px Arial;letter-spacing:.2em}.frontTable article{padding:22px 0;border-bottom:1px solid #aaa}.frontTable time{font:900 30px Arial}.frontTable article>a{display:grid;grid-template-columns:220px 1fr;gap:20px;align-items:center;text-decoration:none}.frontTable img{width:100%;aspect-ratio:16/9;object-fit:cover}.frontTable small{display:block;margin-top:10px;color:#8a6c19}.tv strong{display:block;font:900 48px Arial;color:#c48f17}.tv span{font-size:12px}.back{display:inline-block;margin-top:30px}.frontTable article>a:hover img{filter:brightness(.75)}
@media(max-width:900px){.adminGrid{grid-template-columns:1fr}.frontHead{display:none}.frontTable article{grid-template-columns:1fr}.frontTable article>a{grid-template-columns:1fr}.front h1{font-size:60px}}@media(max-width:560px){header{align-items:start;flex-direction:column;gap:22px}.adminGrid{padding:18px}.two{grid-template-columns:1fr}.front main{padding:55px 6vw}}
@@ -0,0 +1,20 @@
@import "tailwindcss";
:root{--ink:#07101d;--cream:#f2ede2;--gold:#efb53f;--blue:#16334d;--line:rgba(255,255,255,.22)}
*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;background:var(--ink);color:var(--cream);font-family:Arial,"Noto Sans TC","Microsoft JhengHei",sans-serif}button,a{font:inherit}
.hero{position:relative;min-height:100svh;overflow:hidden;background:radial-gradient(circle at 76% 18%,#80583c 0,#243f57 18%,#0a1726 49%,#050b13 100%);transition:1s}.hero.isLit{background:radial-gradient(circle at 73% 23%,#ffc676 0,#355e7c 25%,#0b1d31 54%,#050b13 100%)}
nav{position:absolute;z-index:6;top:0;left:0;right:0;display:flex;justify-content:space-between;align-items:center;padding:26px 4vw;border-bottom:1px solid var(--line);font:700 10px Arial;letter-spacing:.28em}.logo{font-size:24px;letter-spacing:-.08em}.logo span{margin-left:7px;color:var(--gold);font-size:12px;vertical-align:top}
.light{position:absolute;width:46vw;height:46vw;right:-4vw;top:-12vw;border-radius:50%;background:rgba(255,194,111,.16);filter:blur(30px);transition:1s}.isLit .light{background:rgba(255,202,115,.38);transform:scale(1.15)}
.frames{position:absolute;inset:12vh 5vw 8vh 42vw;perspective:900px}.frames i{position:absolute;display:block;width:36%;height:72%;border:1px solid rgba(255,255,255,.42);background:linear-gradient(135deg,rgba(255,255,255,.06),transparent);backdrop-filter:blur(2px);transform:rotateY(-14deg) rotateZ(4deg);box-shadow:0 30px 80px rgba(0,0,0,.24)}.frames i:nth-child(1){left:0;top:12%}.frames i:nth-child(2){left:29%;top:2%}.frames i:nth-child(3){left:58%;top:15%}
.noise{position:absolute;inset:0;opacity:.14;pointer-events:none;background-image:url("data:image/svg+xml,%3Csvg viewBox='0 0 160 160' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.55'/%3E%3C/svg%3E")}
.heroText{position:relative;z-index:4;padding:16vh 0 11vh 7vw;width:min(760px,90vw)}.kicker,.sectionTag{font:700 10px/1.5 Arial;letter-spacing:.34em;color:var(--gold)}h1{margin:28px 0 12px;font:900 clamp(76px,12vw,178px)/.72 Arial;letter-spacing:-.09em}h1 span{display:block}.outline{color:transparent;-webkit-text-stroke:1.5px rgba(242,237,226,.75)}
.anniversary{display:flex;align-items:center;gap:16px;margin:24px 0}.anniversary strong{font:900 clamp(72px,9vw,132px)/.7 Arial;color:var(--gold);letter-spacing:-.08em}.anniversary p{font:700 16px/1.15 Arial;letter-spacing:.12em}.dates{font:700 12px Arial;letter-spacing:.2em}.dates b{color:var(--gold);margin:0 10px}.actions{display:flex;gap:12px;flex-wrap:wrap;margin-top:36px}.actions button{padding:15px 20px;border:1px solid transparent;cursor:pointer;transition:.25s}.primary{background:var(--gold);color:var(--ink);font-weight:800}.secondary{background:transparent;color:var(--cream);border-color:rgba(255,255,255,.5)!important}.actions button:hover{transform:translateY(-3px)}.threeDots{position:absolute;z-index:4;right:4vw;bottom:36px;display:flex;gap:10px}.threeDots b{width:8px;height:8px;border:1px solid var(--cream);border-radius:50%}.threeDots b:first-child{background:var(--gold);border-color:var(--gold)}
.statement,.words,.memory{padding:100px 8vw}.statement{display:grid;grid-template-columns:1fr 3fr;gap:6vw;background:var(--gold);color:var(--ink)}.statement .sectionTag{color:var(--ink)}.hangul{font:700 13px Arial;letter-spacing:.24em}.statement h2{font:900 clamp(48px,8vw,112px)/1 serif;margin:20px 0 42px;letter-spacing:-.06em}.bigCopy{font:700 clamp(25px,3.4vw,48px)/1.55 serif;margin:0}
.words{background:var(--cream);color:var(--ink)}.words .sectionTag{color:#7c682f}.wordGrid{display:grid;grid-template-columns:repeat(3,1fr);margin-top:65px;border-top:1px solid #999}.wordGrid article{padding:28px 32px 45px 0;margin-right:32px;border-right:1px solid #aaa}.wordGrid span{font-size:11px;color:#786d51}.wordGrid h3{font:900 clamp(44px,7vw,100px)/1 Arial;letter-spacing:-.07em;margin:28px 0 16px}.wordGrid p{color:#59606a;line-height:1.7}
.lyricsPreview{position:relative;overflow:hidden;display:grid;grid-template-columns:1fr 2fr;gap:7vw;padding:100px 8vw;background:radial-gradient(circle at 8% 15%,#28455f 0,#102337 32%,#07101d 74%);border-top:1px solid rgba(255,255,255,.12)}.lyricsPreview:after{content:"3";position:absolute;right:-.04em;bottom:-.32em;font:900 min(48vw,680px)/1 Arial;color:rgba(255,255,255,.025);pointer-events:none}.lyricsMeta{position:relative;z-index:1}.lyricsMeta>span{display:block;margin-top:38px;font:900 clamp(38px,5vw,72px)/.95 Arial;letter-spacing:-.06em;color:transparent;-webkit-text-stroke:1px rgba(242,237,226,.5)}.lyricsLines{position:relative;z-index:1;border-left:1px solid rgba(239,181,63,.6);padding-left:clamp(25px,4vw,64px)}.lyricsLines p{margin:0 0 18px;font:700 clamp(22px,3vw,44px)/1.25 serif;letter-spacing:-.02em}.lyricsLines p:nth-child(2n){color:#aebdca}.lyricsLines a{display:inline-flex;align-items:center;gap:22px;margin-top:28px;padding:15px 20px;background:var(--gold);color:var(--ink);font-weight:800;text-decoration:none;transition:.25s}.lyricsLines a:hover{transform:translateY(-3px);box-shadow:0 15px 35px rgba(0,0,0,.28)}.lyricsLines a span{font-size:18px}
.memory{background:#0c1a2a}.memory>.sectionTag{display:block;margin-bottom:55px}.memory article{display:grid;grid-template-columns:1fr 2fr;gap:6vw;border-top:1px solid var(--line);padding:40px 0}.memory strong{font:900 clamp(42px,7vw,96px)/1 Arial;color:var(--gold);letter-spacing:-.06em}.memory h3{font:700 clamp(25px,3vw,42px)/1.2 serif;margin:0 0 16px}.memory p{max-width:650px;color:#b9c0c8;line-height:1.8;margin:0}
.video{padding:90px 8vw;background:#d9d0bf;color:var(--ink);display:grid;grid-template-columns:minmax(250px,.75fr) minmax(0,1.5fr);align-items:center;gap:7vw}.video .sectionTag{color:#796727}.video h2{font:800 clamp(42px,5vw,76px)/1.12 serif;margin:25px 0 32px}.videoHeading>a{display:inline-block;color:var(--ink);font-weight:800;text-decoration:none;border-bottom:1px solid var(--ink);padding-bottom:5px}.videoHeading>a:hover{color:#796727}.videoFrame{position:relative;width:100%;aspect-ratio:16/9;background:#050b13;box-shadow:0 35px 80px rgba(7,16,29,.25);overflow:hidden}.videoFrame:before{content:"";position:absolute;inset:-1px;border:1px solid rgba(255,255,255,.22);pointer-events:none;z-index:1}.videoFrame iframe{position:absolute;inset:0;width:100%;height:100%;border:0}
.stageArchive{padding:100px 8vw;background:var(--cream);color:var(--ink)}.stageIntro{display:grid;grid-template-columns:1fr 1.5fr;gap:20px 6vw;align-items:end;margin-bottom:55px}.stageIntro .sectionTag{color:#796727;grid-column:1/-1}.stageIntro h2{margin:0;font:900 clamp(54px,8vw,108px)/.9 serif;letter-spacing:-.06em}.stageIntro>p:last-child{max-width:470px;margin:0;color:#59606a;line-height:1.8}.stageTable{border-top:2px solid var(--ink)}.stageHead,.stageRow{display:grid;grid-template-columns:.55fr 2fr .8fr;gap:28px;align-items:center}.stageHead{padding:15px 0;border-bottom:1px solid #9e998e;font:700 9px Arial;letter-spacing:.2em;color:#76682f}.stageRow{padding:24px 0;border-bottom:1px solid #aaa59b}.stageRow time{font:900 clamp(22px,2.5vw,38px)/1 Arial;letter-spacing:-.04em}.stageVideo{display:grid;grid-template-columns:minmax(150px,240px) 1fr;gap:24px;align-items:center;color:var(--ink);text-decoration:none}.stageThumb{position:relative;display:block;aspect-ratio:16/9;overflow:hidden;background:#0a1726}.stageThumb img{width:100%;height:100%;object-fit:cover;transition:.35s}.stageThumb b{position:absolute;inset:50% auto auto 50%;translate:-50% -50%;width:46px;height:46px;border-radius:50%;display:grid;place-content:center;padding-left:3px;background:var(--gold);box-shadow:0 8px 25px rgba(0,0,0,.3)}.stageVideo:hover img{transform:scale(1.05)}.stageVideo>span:last-child{display:grid;gap:12px}.stageVideo strong{font:800 clamp(18px,2vw,28px)/1.25 serif}.stageVideo small{font:700 9px Arial;letter-spacing:.15em;color:#796727}.station{display:grid;gap:8px}.station strong{font:900 clamp(30px,4vw,58px)/1 Arial;color:#c48f17}.station span{font-size:12px;color:#59606a}
footer{display:flex;justify-content:space-between;gap:20px;padding:26px 4vw;background:#040910;color:#8d97a4;font:10px Arial;letter-spacing:.16em}
@media(max-width:720px){.hero{min-height:820px}.frames{inset:12vh -18vw 29vh 36vw;opacity:.8}.heroText{padding:17vh 7vw 8vh}.heroText h1{font-size:72px}.statement{grid-template-columns:1fr;padding:70px 7vw}.words,.memory{padding:75px 7vw}.wordGrid{grid-template-columns:1fr}.wordGrid article{border-right:0;border-bottom:1px solid #aaa;margin:0}.lyricsPreview{grid-template-columns:1fr;padding:75px 7vw}.lyricsMeta>span{margin:22px 0 36px}.lyricsLines{padding-left:24px}.lyricsLines p{font-size:24px}.memory article{grid-template-columns:1fr}.video{grid-template-columns:1fr;padding:65px 7vw;gap:42px}.video h2{font-size:48px}.stageArchive{padding:75px 7vw}.stageIntro{grid-template-columns:1fr}.stageIntro .sectionTag{grid-column:auto}.stageHead{display:none}.stageRow{grid-template-columns:1fr;gap:18px;padding:28px 0}.stageVideo{grid-template-columns:1fr}.stageRow time{font-size:30px}.station{grid-template-columns:auto 1fr;align-items:baseline}.station strong{font-size:38px}footer{flex-direction:column}}
@media(prefers-reduced-motion:reduce){*{scroll-behavior:auto!important;transition:none!important}}
@@ -0,0 +1,17 @@
import type { Metadata } from "next";
import "./globals.css";
export const metadata: Metadata = {
title: "THREE WORDS · 10th Anniversary",
description: "SECHSKIES〈세 단어 (THREE WORDS)〉發行十週年紀念頁:2016.10.07—2026.10.07。",
icons: { icon: "/favicon.svg" },
openGraph: {
title: "THREE WORDS · 10th Anniversary",
description: "現在、這裡、我們。十年後,再次相遇。",
images: [{ url: "/og.png", width: 1536, height: 1024 }],
},
};
export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
return <html lang="zh-Hant"><body>{children}</body></html>;
}
@@ -0,0 +1,169 @@
"use client";
import { useState } from "react";
const moments = [
{ year: "2016.10.07", title: "重新相遇", text: "睽違十六年後,SECHSKIES 以〈세 단어 (THREE WORDS)〉帶回重逢的聲音。" },
{ year: "2026.10.07", title: "十年以後", text: "旋律走過十年,當時的感動仍停在現在、這裡、我們之間。" },
];
const stages = [
{
date: "2016.12.04",
videoId: "8HTqr3Wp-R0",
station: "SBS",
program: "인기가요 · Inkigayo",
note: "THREE WORDS · Goodbye Stage",
},
];
export default function Home() {
const [lit, setLit] = useState(false);
function downloadPoster() {
const canvas = document.createElement("canvas");
canvas.width = 1080;
canvas.height = 1350;
const ctx = canvas.getContext("2d");
if (!ctx) return;
const glow = ctx.createRadialGradient(780, 300, 10, 780, 300, 900);
glow.addColorStop(0, "#e0a16b");
glow.addColorStop(.34, "#264765");
glow.addColorStop(1, "#07101d");
ctx.fillStyle = glow;
ctx.fillRect(0, 0, 1080, 1350);
ctx.strokeStyle = "rgba(255,255,255,.45)";
ctx.lineWidth = 2;
[150, 390, 630].forEach((x, index) => {
ctx.strokeRect(x, 180 + index * 70, 420, 660);
});
ctx.fillStyle = "#f5efe2";
ctx.font = "700 106px Arial";
ctx.fillText("THREE", 72, 980);
ctx.fillText("WORDS", 72, 1085);
ctx.fillStyle = "#efb53f";
ctx.font = "700 36px Arial";
ctx.fillText("10TH ANNIVERSARY", 76, 1155);
ctx.fillStyle = "#f5efe2";
ctx.font = "30px Arial";
ctx.fillText("2016.10.07 — 2026.10.07", 76, 1225);
ctx.font = "26px Arial";
ctx.fillText("NOW · HERE · US", 76, 1285);
const link = document.createElement("a");
link.download = "three-words-10th-anniversary.png";
link.href = canvas.toDataURL("image/png");
link.click();
}
return (
<main>
<section className={`hero ${lit ? "isLit" : ""}`}>
<nav>
<span className="logo">3W<span>10</span></span>
<span>2016 — 2026</span>
</nav>
<div className="light" aria-hidden="true" />
<div className="frames" aria-hidden="true"><i /><i /><i /></div>
<div className="noise" aria-hidden="true" />
<div className="heroText">
<p className="kicker">SECHSKIES · 세 단어</p>
<h1><span>THREE</span><span className="outline">WORDS</span></h1>
<div className="anniversary"><strong>10</strong><p>TH<br />ANNIVERSARY</p></div>
<p className="dates">2016.10.07 <b>—</b> 2026.10.07</p>
<div className="actions">
<button className="primary" onClick={() => setLit(!lit)}>{lit ? "讓記憶停在這裡" : "點亮第十年的光"} <span>✦</span></button>
<button className="secondary" onClick={downloadPoster}>下載 IG 紀念圖 ↓</button>
</div>
</div>
<div className="threeDots"><b /><b /><b /></div>
</section>
<section className="statement">
<p className="sectionTag">01 / TEN YEARS LATER</p>
<div>
<p className="hangul">지금 · 여기 · 우리</p>
<h2>現在。這裡。我們。</h2>
<p className="bigCopy">有些歌不只是被聽見,<br />而是替一段等待留下名字。</p>
</div>
</section>
<section className="words">
<p className="sectionTag">02 / THREE WORDS</p>
<div className="wordGrid">
<article><span>01</span><h3>NOW</h3><p>十年後的此刻,重新按下播放。</p></article>
<article><span>02</span><h3>HERE</h3><p>回到那個讓我們再次相遇的地方。</p></article>
<article><span>03</span><h3>US</h3><p>歌曲仍在,我們也仍然在這裡。</p></article>
</div>
</section>
<section className="lyricsPreview">
<div className="lyricsMeta">
<p className="sectionTag">03 / LYRICS PREVIEW</p>
<span>세 단어<br />THREE WORDS</span>
</div>
<div className="lyricsLines">
<p>I just wanna be with you</p>
<p>내가 사는 이유</p>
<p>다시는 멀리 가지 않을게요</p>
<p>I&apos;ll always be here for you</p>
<p>세월이 지난 후</p>
<a href="https://jekkinoopy.github.io/sechskies/extra/lyrics.html" target="_blank" rel="noreferrer">完整歌詞與歌曲資料 <span>↗</span></a>
</div>
</section>
<section className="memory">
<p className="sectionTag">04 / FROM THEN TO NOW</p>
{moments.map((moment) => <article key={moment.year}><strong>{moment.year}</strong><div><h3>{moment.title}</h3><p>{moment.text}</p></div></article>)}
</section>
<section className="video">
<div className="videoHeading">
<p className="sectionTag">OFFICIAL MUSIC VIDEO</p>
<h2>再聽一次,<br />十年前的三個詞。</h2>
<a href="https://www.youtube.com/watch?v=m7Zt_p9S-yg" target="_blank" rel="noreferrer">在 YouTube 開啟 ↗</a>
</div>
<div className="videoFrame">
<iframe
src="https://www.youtube-nocookie.com/embed/m7Zt_p9S-yg?rel=0"
title="SECHSKIES - 세 단어 (THREE WORDS) Official Music Video"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerPolicy="strict-origin-when-cross-origin"
allowFullScreen
/>
</div>
</section>
<section className="stageArchive">
<div className="stageIntro">
<p className="sectionTag">05 / MUSIC SHOW ARCHIVE</p>
<h2>打歌舞台</h2>
<p>把當年的舞台依日期收好,從十年前重新播放。</p>
</div>
<div className="stageTable" role="table" aria-label="THREE WORDS 打歌舞台列表">
<div className="stageHead" role="row">
<span role="columnheader">DATE</span>
<span role="columnheader">VIDEO</span>
<span role="columnheader">STATION</span>
</div>
{stages.map((stage) => (
<div className="stageRow" role="row" key={stage.videoId}>
<time role="cell" dateTime={stage.date}>{stage.date}</time>
<a className="stageVideo" role="cell" href={`https://www.youtube.com/watch?v=${stage.videoId}`} target="_blank" rel="noreferrer">
<span className="stageThumb">
<img src={`https://i.ytimg.com/vi/${stage.videoId}/hqdefault.jpg`} alt={`${stage.program} ${stage.note} 影片縮圖`} />
<b>▶</b>
</span>
<span><strong>{stage.note}</strong><small>WATCH ON YOUTUBE ↗</small></span>
</a>
<div className="station" role="cell"><strong>{stage.station}</strong><span>{stage.program}</span></div>
</div>
))}
</div>
</section>
<footer><span>FAN-MADE 10TH ANNIVERSARY TRIBUTE</span><span>SECHSKIES · THREE WORDS · 2016—2026</span></footer>
</main>
);
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

@@ -0,0 +1,6 @@
壓縮包包含:
十週年網站原始碼
CSS 與分享圖
最新的打歌舞台表格
PHP/MySQL 乙級練習後台
沒有包含 node_modules、建置檔或 Git 資料。D 槽原專案目前未被修改。