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
+4339 -4012

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.
+444 -444
View File
@@ -1,445 +1,445 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="zh-TW"> <html lang="zh-TW">
<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/tablet.css"> <link rel="stylesheet" href="../../assets/css/tablet.css">
<link rel="stylesheet" href="njtw5.css"> <link rel="stylesheet" href="njtw5.css">
<style> <style>
/* 本頁專用:.nickname 只寫在 HTML(依你要求,未再放到 njtw5.css) */ /* 本頁專用:.nickname 只寫在 HTML(依你要求,未再放到 njtw5.css) */
body.njtw5-page .member-card .nickname { body.njtw5-page .member-card .nickname {
display: block; display: block;
margin-top: 0.65rem; margin-top: 0.65rem;
padding: 0.15rem 0; padding: 0.15rem 0;
font-size: max(14px, 0.92rem); font-size: max(14px, 0.92rem);
font-weight: 700; font-weight: 700;
color: #3d240c; color: #3d240c;
line-height: 1.5; line-height: 1.5;
word-break: break-word; word-break: break-word;
overflow-wrap: anywhere; overflow-wrap: anywhere;
opacity: 1; opacity: 1;
visibility: visible; visibility: visible;
position: relative; position: relative;
z-index: 1; z-index: 1;
} }
.main-header { .main-header {
text-align: center; text-align: center;
margin-bottom: 50px; margin-bottom: 50px;
width: 100%; width: 100%;
} }
</style> </style>
</head> </head>
<body class="njtw5-page"> <body class="njtw5-page">
<header class="main-header"> <header class="main-header">
<h1>新西遊記 5:與鬼神同行</h1> <h1>新西遊記 5:與鬼神同行</h1>
<p class="intro"> <p class="intro">
這是一場驚心動魄(搞笑)的鬼怪盛宴!<br> 這是一場驚心動魄(搞笑)的鬼怪盛宴!<br>
六位成員化身世界各地的經典鬼怪,展開一場前往香港與北海道的爆笑冒險。 六位成員化身世界各地的經典鬼怪,展開一場前往香港與北海道的爆笑冒險。
</p> </p>
</header> </header>
<div class="container"> <div class="container">
<h2 class="section-title">鬼神介紹</h2> <h2 class="section-title">鬼神介紹</h2>
<!-- tr/td 必須放在 <table> 裡;外層用 div 接在 .container 的 grid 下,佔滿整列 --> <!-- tr/td 必須放在 <table> 裡;外層用 div 接在 .container 的 grid 下,佔滿整列 -->
<div class="njtw-member-table-wrap"> <div class="njtw-member-table-wrap">
<table class="njtw-member-table"> <table class="njtw-member-table">
<tbody> <tbody>
<tr> <tr>
<td> <td>
<div class="njtw-td-fill"> <div class="njtw-td-fill">
<div class="member-card m1 parchment-card"> <div class="member-card m1 parchment-card">
<div class="parchment-inner"> <div class="parchment-inner">
<!-- <div class="parchment-logo" aria-hidden="true">新西遊記<span class="parchment-logo-num">5</span></div> --> <!-- <div class="parchment-logo" aria-hidden="true">新西遊記<span class="parchment-logo-num">5</span></div> -->
<div class="parchment-headline"> <div class="parchment-headline">
<p class="parchment-name"></p> <p class="parchment-name"></p>
<p class="parchment-role">姜鎬臉男 姜鎬童</p> <p class="parchment-role">姜鎬臉男 姜鎬童</p>
<p class="parchment-byline">吃得津津有味的鬼怪</p> <p class="parchment-byline">吃得津津有味的鬼怪</p>
</div> </div>
<div class="parchment-figure"> <div class="parchment-figure">
<div class="member-image"></div> <div class="member-image"></div>
</div> </div>
<div class="parchment-stats"> <div class="parchment-stats">
<div class="parchment-stat-row"> <div class="parchment-stat-row">
<span class="parchment-stat-label">視覺</span> <span class="parchment-stat-label">視覺</span>
<div class="parchment-stat-balls"> <div class="parchment-stat-balls">
<!-- 將每顆 img 的 src 改成你的龍珠圖片網址(或同資料夾檔名) --> <!-- 將每顆 img 的 src 改成你的龍珠圖片網址(或同資料夾檔名) -->
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
</div> </div>
</div> </div>
<div class="parchment-stat-row"> <div class="parchment-stat-row">
<span class="parchment-stat-label">注目度</span> <span class="parchment-stat-label">注目度</span>
<div class="parchment-stat-balls"> <div class="parchment-stat-balls">
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
</div> </div>
</div> </div>
<div class="parchment-stat-row"> <div class="parchment-stat-row">
<span class="parchment-stat-label">可愛</span> <span class="parchment-stat-label">可愛</span>
<div class="parchment-stat-balls"> <div class="parchment-stat-balls">
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
</div> </div>
</div> </div>
</div> </div>
<p class="parchment-tags">#吃貨精靈 #低聲細語</p> <p class="parchment-tags">#吃貨精靈 #低聲細語</p>
</div> </div>
</div> </div>
</div> </div>
</td> </td>
<td> <td>
<div class="njtw-td-fill"> <div class="njtw-td-fill">
<div class="member-card m2 parchment-card"> <div class="member-card m2 parchment-card">
<div class="parchment-inner"> <div class="parchment-inner">
<div class="parchment-headline"> <div class="parchment-headline">
<p class="parchment-name"></p> <p class="parchment-name"></p>
<p class="parchment-role">鬼娃恰吉 李壽根</p> <p class="parchment-role">鬼娃恰吉 李壽根</p>
<p class="parchment-byline">不搞笑會死的鬼怪</p> <p class="parchment-byline">不搞笑會死的鬼怪</p>
</div> </div>
<div class="parchment-figure"> <div class="parchment-figure">
<div class="member-image"></div> <div class="member-image"></div>
</div> </div>
<div class="parchment-stats"> <div class="parchment-stats">
<div class="parchment-stat-row"> <div class="parchment-stat-row">
<span class="parchment-stat-label">娃娃美</span> <span class="parchment-stat-label">娃娃美</span>
<div class="parchment-stat-balls"> <div class="parchment-stat-balls">
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
</div> </div>
</div> </div>
<div class="parchment-stat-row"> <div class="parchment-stat-row">
<span class="parchment-stat-label">語言能力</span> <span class="parchment-stat-label">語言能力</span>
<div class="parchment-stat-balls"> <div class="parchment-stat-balls">
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
</div> </div>
</div> </div>
<div class="parchment-stat-row"> <div class="parchment-stat-row">
<span class="parchment-stat-label">體能</span> <span class="parchment-stat-label">體能</span>
<div class="parchment-stat-balls"> <div class="parchment-stat-balls">
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
</div> </div>
</div> </div>
</div> </div>
<p class="parchment-tags">#最強短腿 #瘋狂跑動</p> <p class="parchment-tags">#最強短腿 #瘋狂跑動</p>
</div> </div>
</div> </div>
</div> </div>
</td> </td>
<td> <td>
<div class="njtw-td-fill"> <div class="njtw-td-fill">
<div class="member-card m3 parchment-card"> <div class="member-card m3 parchment-card">
<div class="parchment-inner"> <div class="parchment-inner">
<div class="parchment-headline"> <div class="parchment-headline">
<p class="parchment-name"></p> <p class="parchment-name"></p>
<p class="parchment-role">陰間使者 殷志源</p> <p class="parchment-role">陰間使者 殷志源</p>
<p class="parchment-byline">戴上黑帽子 神志源回來了!</p> <p class="parchment-byline">戴上黑帽子 神志源回來了!</p>
</div> </div>
<div class="parchment-figure"> <div class="parchment-figure">
<div class="member-image"></div> <div class="member-image"></div>
</div> </div>
<div class="parchment-stats"> <div class="parchment-stats">
<div class="parchment-stat-row"> <div class="parchment-stat-row">
<span class="parchment-stat-label">化妝消化力</span> <span class="parchment-stat-label">化妝消化力</span>
<div class="parchment-stat-balls"> <div class="parchment-stat-balls">
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
</div> </div>
</div> </div>
<div class="parchment-stat-row"> <div class="parchment-stat-row">
<span class="parchment-stat-label">黑眼圈</span> <span class="parchment-stat-label">黑眼圈</span>
<div class="parchment-stat-balls"> <div class="parchment-stat-balls">
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
</div> </div>
</div> </div>
<div class="parchment-stat-row"> <div class="parchment-stat-row">
<span class="parchment-stat-label">暴走</span> <span class="parchment-stat-label">暴走</span>
<div class="parchment-stat-balls"> <div class="parchment-stat-balls">
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
</div> </div>
</div> </div>
</div> </div>
<p class="parchment-tags">#瘋子使者 #最帥鬼怪</p> <p class="parchment-tags">#瘋子使者 #最帥鬼怪</p>
</div> </div>
</div> </div>
</div> </div>
</td> </td>
<td class="njtw-layout-slot shinmyohan OB"> <td class="njtw-layout-slot shinmyohan OB">
<div class="njtw-td-fill"> <div class="njtw-td-fill">
<div class="njtw-slot-stack njtw-team-panel ob-panel"> <div class="njtw-slot-stack njtw-team-panel ob-panel">
<div class="njtw-slot-visual"> <div class="njtw-slot-visual">
<div class="njtw-team-title-band"> <div class="njtw-team-title-band">
<h2 class="njtw-team-hero-title" id="njtw-ob-team-title">OB 隊:老練的瘋子們</h2> <h2 class="njtw-team-hero-title" id="njtw-ob-team-title">OB 隊:老練的瘋子們</h2>
</div> </div>
<div class="njtw-slot-faces"> <div class="njtw-slot-faces">
<img src="1.png" alt="" /> <img src="1.png" alt="" />
<img src="2.png" alt="" /> <img src="2.png" alt="" />
<img src="3.png" alt="" /> <img src="3.png" alt="" />
</div> </div>
</div> </div>
<div class="team-info"> <div class="team-info">
<p>看他們玩遊戲不是在看技術,是在看如何「鑽漏洞」和「互相背叛」。他們不用說話,光是一個眼神就能知道對方在想什麼(通常是壞主意)。</p> <p>看他們玩遊戲不是在看技術,是在看如何「鑽漏洞」和「互相背叛」。他們不用說話,光是一個眼神就能知道對方在想什麼(通常是壞主意)。</p>
<p><strong>圈粉亮點:</strong>鎬童的食量、壽根的即興才藝、志源那種「初丁」卻天才的直覺。</p> <p><strong>圈粉亮點:</strong>鎬童的食量、壽根的即興才藝、志源那種「初丁」卻天才的直覺。</p>
<div class="parchment-stats"> <div class="parchment-stats">
<div class="parchment-stat-row"> <div class="parchment-stat-row">
<span class="parchment-stat-label">老練度</span> <span class="parchment-stat-label">老練度</span>
<div class="parchment-stat-balls" aria-label="5"> <div class="parchment-stat-balls" aria-label="5">
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
</div> </div>
</div> </div>
<div class="parchment-stat-row"> <div class="parchment-stat-row">
<span class="parchment-stat-label">體力值</span> <span class="parchment-stat-label">體力值</span>
<div class="parchment-stat-balls" aria-label="1"> <div class="parchment-stat-balls" aria-label="1">
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
</div> </div>
</div> </div>
</div> </div>
<p class="njtw-team-catchphrase">口頭禪:喔嗚!吃吧!</p> <p class="njtw-team-catchphrase">口頭禪:喔嗚!吃吧!</p>
</div> </div>
</div> </div>
</div> </div>
</td> </td>
</tr> </tr>
<tr> <tr>
<td class="njtw-layout-slot shinmyohan YB"> <td class="njtw-layout-slot shinmyohan YB">
<div class="njtw-td-fill"> <div class="njtw-td-fill">
<div class="njtw-slot-stack njtw-team-panel yb-panel"> <div class="njtw-slot-stack njtw-team-panel yb-panel">
<div class="njtw-slot-visual"> <div class="njtw-slot-visual">
<div class="njtw-team-title-band"> <div class="njtw-team-title-band">
<h2 class="njtw-team-hero-title" id="njtw-yb-team-title">YB 隊:新瘋子隊</h2> <h2 class="njtw-team-hero-title" id="njtw-yb-team-title">YB 隊:新瘋子隊</h2>
</div> </div>
<div class="njtw-slot-faces"> <div class="njtw-slot-faces">
<img src="4.png" alt="" /> <img src="4.png" alt="" />
<img src="5.png" alt="" /> <img src="5.png" alt="" />
<img src="6.png" alt="" class="Shinmyohan" /> <img src="6.png" alt="" class="Shinmyohan" />
</div> </div>
</div> </div>
<div class="team-info"> <div class="team-info">
<p>一開始以為是來幫哥哥們打雜的,結果卻是一個比一個還瘋的「瘋子新秀」。他們擁有體力優勢,卻常常在簡單的常識題中全軍覆沒。</p> <p>一開始以為是來幫哥哥們打雜的,結果卻是一個比一個還瘋的「瘋子新秀」。他們擁有體力優勢,卻常常在簡單的常識題中全軍覆沒。</p>
<p><strong>圈粉亮點:</strong>安宰賢的紙片人運動神經、宋旻浩的藝術靈魂與神手指、P.O 的超大聲反應。</p> <p><strong>圈粉亮點:</strong>安宰賢的紙片人運動神經、宋旻浩的藝術靈魂與神手指、P.O 的超大聲反應。</p>
<div class="parchment-stats"> <div class="parchment-stats">
<div class="parchment-stat-row"> <div class="parchment-stat-row">
<span class="parchment-stat-label">老練度</span> <span class="parchment-stat-label">老練度</span>
<div class="parchment-stat-balls" aria-label="1"> <div class="parchment-stat-balls" aria-label="1">
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
</div> </div>
</div> </div>
<div class="parchment-stat-row"> <div class="parchment-stat-row">
<span class="parchment-stat-label">體力值</span> <span class="parchment-stat-label">體力值</span>
<div class="parchment-stat-balls" aria-label="5"> <div class="parchment-stat-balls" aria-label="5">
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
</div> </div>
</div> </div>
</div> </div>
<p class="njtw-team-catchphrase">口頭禪:哥!對不起!</p> <p class="njtw-team-catchphrase">口頭禪:哥!對不起!</p>
</div> </div>
</div> </div>
</div> </div>
</td> </td>
<td> <td>
<div class="njtw-td-fill"> <div class="njtw-td-fill">
<div class="member-card m4 parchment-card"> <div class="member-card m4 parchment-card">
<div class="parchment-inner"> <div class="parchment-inner">
<div class="parchment-headline"> <div class="parchment-headline">
<p class="parchment-name"></p> <p class="parchment-name"></p>
<p class="parchment-role">帥氣殭屍 安宰賢</p> <p class="parchment-role">帥氣殭屍 安宰賢</p>
<p class="parchment-byline">美貌&amp;藝能兼具 八面玲瓏的帥氣殭屍</p> <p class="parchment-byline">美貌&amp;藝能兼具 八面玲瓏的帥氣殭屍</p>
</div> </div>
<div class="parchment-figure"> <div class="parchment-figure">
<div class="member-image"></div> <div class="member-image"></div>
</div> </div>
<div class="parchment-stats"> <div class="parchment-stats">
<div class="parchment-stat-row"> <div class="parchment-stat-row">
<span class="parchment-stat-label">帥氣度</span> <span class="parchment-stat-label">帥氣度</span>
<div class="parchment-stat-balls"> <div class="parchment-stat-balls">
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
</div> </div>
</div> </div>
<div class="parchment-stat-row"> <div class="parchment-stat-row">
<span class="parchment-stat-label">瘋癲</span> <span class="parchment-stat-label">瘋癲</span>
<div class="parchment-stat-balls"> <div class="parchment-stat-balls">
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
</div> </div>
</div> </div>
<div class="parchment-stat-row"> <div class="parchment-stat-row">
<span class="parchment-stat-label">臉紅紅</span> <span class="parchment-stat-label">臉紅紅</span>
<div class="parchment-stat-balls"> <div class="parchment-stat-balls">
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
</div> </div>
</div> </div>
</div> </div>
<p class="parchment-tags">#美男殭屍 #運動白痴</p> <p class="parchment-tags">#美男殭屍 #運動白痴</p>
</div> </div>
</div> </div>
</div> </div>
</td> </td>
<td> <td>
<div class="njtw-td-fill"> <div class="njtw-td-fill">
<div class="member-card m5 parchment-card"> <div class="member-card m5 parchment-card">
<div class="parchment-inner"> <div class="parchment-inner">
<div class="parchment-headline"> <div class="parchment-headline">
<p class="parchment-name"></p> <p class="parchment-name"></p>
<p class="parchment-role">現代鬼 宋旻浩</p> <p class="parchment-role">現代鬼 宋旻浩</p>
<p class="parchment-byline">獨特的文藝青年 現代鬼怪</p> <p class="parchment-byline">獨特的文藝青年 現代鬼怪</p>
</div> </div>
<div class="parchment-figure"> <div class="parchment-figure">
<div class="member-image"></div> <div class="member-image"></div>
</div> </div>
<div class="parchment-stats"> <div class="parchment-stats">
<div class="parchment-stat-row"> <div class="parchment-stat-row">
<span class="parchment-stat-label">髮型</span> <span class="parchment-stat-label">髮型</span>
<div class="parchment-stat-balls"> <div class="parchment-stat-balls">
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
</div> </div>
</div> </div>
<div class="parchment-stat-row"> <div class="parchment-stat-row">
<span class="parchment-stat-label">清純美</span> <span class="parchment-stat-label">清純美</span>
<div class="parchment-stat-balls"> <div class="parchment-stat-balls">
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
</div> </div>
</div> </div>
<div class="parchment-stat-row"> <div class="parchment-stat-row">
<span class="parchment-stat-label">時髦度</span> <span class="parchment-stat-label">時髦度</span>
<div class="parchment-stat-balls"> <div class="parchment-stat-balls">
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
</div> </div>
</div> </div>
</div> </div>
<p class="parchment-tags">#長髮宋 #綜藝神降臨</p> <p class="parchment-tags">#長髮宋 #綜藝神降臨</p>
</div> </div>
</div> </div>
</div> </div>
</td> </td>
<td> <td>
<div class="njtw-td-fill"> <div class="njtw-td-fill">
<div class="member-card m6 parchment-card"> <div class="member-card m6 parchment-card">
<div class="parchment-inner"> <div class="parchment-inner">
<div class="parchment-headline"> <div class="parchment-headline">
<p class="parchment-name"></p> <p class="parchment-name"></p>
<p class="parchment-role">德古拉 P.O</p> <p class="parchment-role">德古拉 P.O</p>
<p class="parchment-byline">新血降臨!華麗誤答轟炸機!</p> <p class="parchment-byline">新血降臨!華麗誤答轟炸機!</p>
</div> </div>
<div class="parchment-figure"> <div class="parchment-figure">
<div class="member-image"></div> <div class="member-image"></div>
</div> </div>
<div class="parchment-stats"> <div class="parchment-stats">
<div class="parchment-stat-row"> <div class="parchment-stat-row">
<span class="parchment-stat-label">腦力</span> <span class="parchment-stat-label">腦力</span>
<div class="parchment-stat-balls"> <div class="parchment-stat-balls">
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
</div> </div>
</div> </div>
<div class="parchment-stat-row"> <div class="parchment-stat-row">
<span class="parchment-stat-label">披風+領子</span> <span class="parchment-stat-label">披風+領子</span>
<div class="parchment-stat-balls"> <div class="parchment-stat-balls">
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
</div> </div>
</div> </div>
<div class="parchment-stat-row"> <div class="parchment-stat-row">
<span class="parchment-stat-label">藝能感</span> <span class="parchment-stat-label">藝能感</span>
<div class="parchment-stat-balls"> <div class="parchment-stat-balls">
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
<img src="dragon-ball.png" width="28" height="28" alt="" /> <img src="dragon-ball.png" width="28" height="28" alt="" />
</div> </div>
</div> </div>
</div> </div>
<p class="parchment-tags">#新弟子 #德古拉</p> <p class="parchment-tags">#新弟子 #德古拉</p>
</div> </div>
</div> </div>
</div> </div>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
<!-- 影片區塊:將 iframe 的 src 換成 https://www.youtube.com/embed/你的影片ID --> <!-- 影片區塊:將 iframe 的 src 換成 https://www.youtube.com/embed/你的影片ID -->
<div class="video-container"> <div class="video-container">
<h2 class="video-title">經典片段回顧</h2> <h2 class="video-title">經典片段回顧</h2>
<div class="video-wrapper"> <div class="video-wrapper">
<iframe class="video-embed" src="https://www.youtube.com/embed/3zSeN9Ss3BI" title="新西遊記 5 精華片段" <iframe class="video-embed" src="https://www.youtube.com/embed/3zSeN9Ss3BI" title="新西遊記 5 精華片段"
loading="lazy" loading="lazy"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe> referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div> </div>
</div> </div>
<div class="video-container"> <div class="video-container">
<h2 class="video-title">S5 EP01・180930(神叨字幕組)</h2> <h2 class="video-title">S5 EP01・180930(神叨字幕組)</h2>
<div class="video-wrapper"> <div class="video-wrapper">
<iframe class="video-embed" title="新西遊記5 EP01 神叨字幕組" <iframe class="video-embed" title="新西遊記5 EP01 神叨字幕組"
src="//player.bilibili.com/player.html?isOutside=true&bvid=BV12t411U7Ni&p=1" scrolling="no" src="//player.bilibili.com/player.html?isOutside=true&bvid=BV12t411U7Ni&p=1" scrolling="no"
border="0" frameborder="no" framespacing="0" allowfullscreen="true"></iframe> border="0" frameborder="no" framespacing="0" allowfullscreen="true"></iframe>
</div> </div>
</div> </div>
</body> </body>
</html> </html>
File renamed without changes.
File diff suppressed because it is too large. Load diff
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -1,44 +1,44 @@
<?php include_once "./api/db.php"; ?> <?php include_once "./api/db.php"; ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="zh-TW"> <html lang="zh-TW">
<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="css/vedio-list.css"> <link rel="stylesheet" href="css/vedio-list.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
</head> </head>
<body class="vedio-admin-page"> <body class="vedio-admin-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">ADMIN · CRUD</span> <span class="sub-title">ADMIN · CRUD</span>
</div> </div>
</div> </div>
</header> </header>
<div class="container admin-wrap"> <div class="container admin-wrap">
<nav class="admin-nav"> <nav class="admin-nav">
<a href="index.php">回前台</a> <a href="index.php">回前台</a>
<a href="admin.php">後台首頁</a> <a href="admin.php">後台首頁</a>
<a href="?do=drama">追劇管理</a> <a href="?do=drama">追劇管理</a>
<a href="?do=add">新增作品</a> <a href="?do=add">新增作品</a>
</nav> </nav>
<?php <?php
$do = $_GET['do'] ?? 'main'; $do = $_GET['do'] ?? 'main';
$file = "back/$do.php"; $file = "back/$do.php";
if (file_exists($file)) { if (file_exists($file)) {
include $file; include $file;
} else { } else {
include "back/main.php"; include "back/main.php";
} }
?> ?>
</div> </div>
</body> </body>
</html> </html>
@@ -1,125 +1,125 @@
<?php <?php
session_start(); session_start();
date_default_timezone_set("Asia/Taipei"); date_default_timezone_set("Asia/Taipei");
class DB class DB
{ {
protected $dsn = "mysql:host=localhost;charset=utf8;dbname=vedio_list"; protected $dsn = "mysql:host=localhost;charset=utf8;dbname=vedio_list";
protected $pdo; protected $pdo;
protected $table; protected $table;
function __construct($table) function __construct($table)
{ {
$this->table = $table; $this->table = $table;
$this->pdo = new PDO($this->dsn, 'root', ''); $this->pdo = new PDO($this->dsn, 'root', '');
} }
function all(...$arg) function all(...$arg)
{ {
$sql = "SELECT * FROM `$this->table` "; $sql = "SELECT * FROM `$this->table` ";
if (isset($arg[0])) { if (isset($arg[0])) {
if (is_array($arg[0])) { if (is_array($arg[0])) {
$tmp = $this->a2s($arg[0]); $tmp = $this->a2s($arg[0]);
$sql .= " WHERE " . join(" AND ", $tmp); $sql .= " WHERE " . join(" AND ", $tmp);
} else { } else {
$sql .= $arg[0]; $sql .= $arg[0];
} }
} }
if (isset($arg[1])) { if (isset($arg[1])) {
$sql .= $arg[1]; $sql .= $arg[1];
} }
return $this->pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC); return $this->pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
} }
function count(...$arg) function count(...$arg)
{ {
$sql = "SELECT count(*) FROM `$this->table`"; $sql = "SELECT count(*) FROM `$this->table`";
if (isset($arg[0])) { if (isset($arg[0])) {
if (is_array($arg[0])) { if (is_array($arg[0])) {
$tmp = $this->a2s($arg[0]); $tmp = $this->a2s($arg[0]);
$sql .= " WHERE " . join(" AND ", $tmp); $sql .= " WHERE " . join(" AND ", $tmp);
} else { } else {
$sql .= $arg[0]; $sql .= $arg[0];
} }
} }
if (isset($arg[1])) { if (isset($arg[1])) {
$sql .= $arg[1]; $sql .= $arg[1];
} }
return $this->pdo->query($sql)->fetchColumn(); return $this->pdo->query($sql)->fetchColumn();
} }
function find($arg) function find($arg)
{ {
$sql = "SELECT * FROM `$this->table` "; $sql = "SELECT * FROM `$this->table` ";
if (is_array($arg)) { if (is_array($arg)) {
$tmp = $this->a2s($arg); $tmp = $this->a2s($arg);
$sql .= " WHERE " . join(" AND ", $tmp); $sql .= " WHERE " . join(" AND ", $tmp);
} else { } else {
$sql .= " WHERE `id`='$arg'"; $sql .= " WHERE `id`='$arg'";
} }
return $this->pdo->query($sql)->fetch(PDO::FETCH_ASSOC); return $this->pdo->query($sql)->fetch(PDO::FETCH_ASSOC);
} }
function save($arg) function save($arg)
{ {
if (isset($arg['id'])) { if (isset($arg['id'])) {
$tmp = $this->a2s($arg); $tmp = $this->a2s($arg);
$sql = "UPDATE `$this->table` SET " . join(" , ", $tmp); $sql = "UPDATE `$this->table` SET " . join(" , ", $tmp);
$sql .= " WHERE `id`='{$arg['id']}'"; $sql .= " WHERE `id`='{$arg['id']}'";
} else { } else {
$keys = array_keys($arg); $keys = array_keys($arg);
$sql = "INSERT INTO `$this->table` (`" . join("`,`", $keys) . "`) VALUES('" . join("','", $arg) . "');"; $sql = "INSERT INTO `$this->table` (`" . join("`,`", $keys) . "`) VALUES('" . join("','", $arg) . "');";
} }
return $this->pdo->exec($sql); return $this->pdo->exec($sql);
} }
function del($arg) function del($arg)
{ {
$sql = "DELETE FROM `$this->table` "; $sql = "DELETE FROM `$this->table` ";
if (is_array($arg)) { if (is_array($arg)) {
$tmp = $this->a2s($arg); $tmp = $this->a2s($arg);
$sql .= " WHERE " . join(" AND ", $tmp); $sql .= " WHERE " . join(" AND ", $tmp);
} else { } else {
$sql .= " WHERE `id`='$arg'"; $sql .= " WHERE `id`='$arg'";
} }
return $this->pdo->exec($sql); return $this->pdo->exec($sql);
} }
protected function a2s($array) protected function a2s($array)
{ {
$tmp = []; $tmp = [];
foreach ($array as $key => $val) { foreach ($array as $key => $val) {
$tmp[] = "`$key`='$val'"; $tmp[] = "`$key`='$val'";
} }
return $tmp; return $tmp;
} }
function q($sql) function q($sql)
{ {
return $this->pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC); return $this->pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
} }
} }
function dd($array) function dd($array)
{ {
echo "<pre>"; echo "<pre>";
print_r($array); print_r($array);
echo "</pre>"; echo "</pre>";
} }
function to($url) function to($url)
{ {
header("location:$url"); header("location:$url");
} }
$Drama = new DB('dramas'); $Drama = new DB('dramas');
@@ -1,5 +1,5 @@
<?php <?php
include_once "db.php"; include_once "db.php";
$table = ${$_POST['table']}; $table = ${$_POST['table']};
$table->del($_POST['id']); $table->del($_POST['id']);
@@ -1,6 +1,6 @@
<?php <?php
include_once "db.php"; include_once "db.php";
$row = $Drama->find($_POST['id']); $row = $Drama->find($_POST['id']);
$row['sh'] = ((int)$row['sh'] + 1) % 2; $row['sh'] = ((int)$row['sh'] + 1) % 2;
$Drama->save($row); $Drama->save($row);
@@ -1,13 +1,13 @@
<?php <?php
include_once "db.php"; include_once "db.php";
$table = ${$_POST['table']}; $table = ${$_POST['table']};
$row1 = $table->find($_POST['ids'][0]); $row1 = $table->find($_POST['ids'][0]);
$row2 = $table->find($_POST['ids'][1]); $row2 = $table->find($_POST['ids'][1]);
$tmp = $row1['rank']; $tmp = $row1['rank'];
$row1['rank'] = $row2['rank']; $row1['rank'] = $row2['rank'];
$row2['rank'] = $tmp; $row2['rank'] = $tmp;
$table->save($row1); $table->save($row1);
$table->save($row2); $table->save($row2);
@@ -1,96 +1,96 @@
<?php <?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$maxRank = $Drama->q("SELECT MAX(`rank`) AS m FROM `dramas`"); $maxRank = $Drama->q("SELECT MAX(`rank`) AS m FROM `dramas`");
$nextRank = ((int)($maxRank[0]['m'] ?? 0)) + 1; $nextRank = ((int)($maxRank[0]['m'] ?? 0)) + 1;
$data = [ $data = [
'title' => trim($_POST['title'] ?? ''), 'title' => trim($_POST['title'] ?? ''),
'type' => $_POST['type'] ?? 'drama', 'type' => $_POST['type'] ?? 'drama',
'platform' => $_POST['platform'] ?? '其他', 'platform' => $_POST['platform'] ?? '其他',
'watch_url' => trim($_POST['watch_url'] ?? ''), 'watch_url' => trim($_POST['watch_url'] ?? ''),
'current_season' => max(1, (int)($_POST['current_season'] ?? 1)), 'current_season' => max(1, (int)($_POST['current_season'] ?? 1)),
'current_episode' => max(0, (int)($_POST['current_episode'] ?? 0)), 'current_episode' => max(0, (int)($_POST['current_episode'] ?? 0)),
'current_position' => trim($_POST['current_position'] ?? ''), 'current_position' => trim($_POST['current_position'] ?? ''),
'progress_note' => trim($_POST['progress_note'] ?? ''), 'progress_note' => trim($_POST['progress_note'] ?? ''),
'status' => $_POST['status'] ?? 'want', 'status' => $_POST['status'] ?? 'want',
'rating' => $_POST['rating'] !== '' ? $_POST['rating'] : null, 'rating' => $_POST['rating'] !== '' ? $_POST['rating'] : null,
'intro' => trim($_POST['intro'] ?? ''), 'intro' => trim($_POST['intro'] ?? ''),
'note' => trim($_POST['note'] ?? ''), 'note' => trim($_POST['note'] ?? ''),
'sh' => 1, 'sh' => 1,
'rank' => $nextRank, 'rank' => $nextRank,
]; ];
// DB::save 用字串拼接;null 改空字串避免 SQL 破掉 // DB::save 用字串拼接;null 改空字串避免 SQL 破掉
if ($data['rating'] === null) { if ($data['rating'] === null) {
$data['rating'] = ''; $data['rating'] = '';
} }
$Drama->save($data); $Drama->save($data);
to("?do=drama"); to("?do=drama");
exit; exit;
} }
?> ?>
<section> <section>
<h2 class="section-title">新增作品</h2> <h2 class="section-title">新增作品</h2>
<form class="admin-form" method="post" action="?do=add"> <form class="admin-form" method="post" action="?do=add">
<label for="title">劇名/節目名</label> <label for="title">劇名/節目名</label>
<input type="text" id="title" name="title" required> <input type="text" id="title" name="title" required>
<label for="type">類型</label> <label for="type">類型</label>
<select id="type" name="type"> <select id="type" name="type">
<option value="variety">綜藝</option> <option value="variety">綜藝</option>
<option value="drama" selected>戲劇</option> <option value="drama" selected>戲劇</option>
</select> </select>
<label for="platform">上架平台</label> <label for="platform">上架平台</label>
<select id="platform" name="platform"> <select id="platform" name="platform">
<option value="Netflix">Netflix</option> <option value="Netflix">Netflix</option>
<option value="Disney+">Disney+</option> <option value="Disney+">Disney+</option>
<option value="friDay影音">friDay影音</option> <option value="friDay影音">friDay影音</option>
<option value="愛奇藝">愛奇藝</option> <option value="愛奇藝">愛奇藝</option>
<option value="LINE TV">LINE TV</option> <option value="LINE TV">LINE TV</option>
<option value="YouTube">YouTube</option> <option value="YouTube">YouTube</option>
<option value="其他">其他</option> <option value="其他">其他</option>
</select> </select>
<label for="watch_url">直接觀看網址</label> <label for="watch_url">直接觀看網址</label>
<input type="url" id="watch_url" name="watch_url" placeholder="https://..."> <input type="url" id="watch_url" name="watch_url" placeholder="https://...">
<label for="current_season">目前看到第幾季</label> <label for="current_season">目前看到第幾季</label>
<input type="number" id="current_season" name="current_season" min="1" value="1"> <input type="number" id="current_season" name="current_season" min="1" value="1">
<label for="current_episode">目前看到第幾集</label> <label for="current_episode">目前看到第幾集</label>
<input type="number" id="current_episode" name="current_episode" min="0" value="0"> <input type="number" id="current_episode" name="current_episode" min="0" value="0">
<label for="current_position">這一集看到哪裡</label> <label for="current_position">這一集看到哪裡</label>
<input type="text" id="current_position" name="current_position" placeholder="例如 35:20"> <input type="text" id="current_position" name="current_position" placeholder="例如 35:20">
<label for="progress_note">進度情節備註</label> <label for="progress_note">進度情節備註</label>
<input type="text" id="progress_note" name="progress_note" placeholder="例如:女主角剛看到手機裡的影片"> <input type="text" id="progress_note" name="progress_note" placeholder="例如:女主角剛看到手機裡的影片">
<label for="status">狀態</label> <label for="status">狀態</label>
<select id="status" name="status"> <select id="status" name="status">
<option value="want">想看</option> <option value="want">想看</option>
<option value="watching" selected>追劇中</option> <option value="watching" selected>追劇中</option>
<option value="done">已追完</option> <option value="done">已追完</option>
<option value="paused">暫停</option> <option value="paused">暫停</option>
<option value="dropped">棄劇</option> <option value="dropped">棄劇</option>
</select> </select>
<label for="rating">評分</label> <label for="rating">評分</label>
<input type="number" id="rating" name="rating" min="0" max="10" step="0.1" placeholder="例如 8.5"> <input type="number" id="rating" name="rating" min="0" max="10" step="0.1" placeholder="例如 8.5">
<label for="intro">簡介</label> <label for="intro">簡介</label>
<textarea id="intro" name="intro" rows="4"></textarea> <textarea id="intro" name="intro" rows="4"></textarea>
<label for="note">心得備註</label> <label for="note">心得備註</label>
<textarea id="note" name="note" rows="3"></textarea> <textarea id="note" name="note" rows="3"></textarea>
<div class="form-actions" style="margin-top:1.25rem;display:flex;gap:10px;justify-content:center;"> <div class="form-actions" style="margin-top:1.25rem;display:flex;gap:10px;justify-content:center;">
<button type="submit" class="is-primary" style="min-height:40px;padding:0.4rem 1.05rem;border-radius:999px;border:2px solid var(--color-brand);background:var(--color-brand);font-weight:700;cursor:pointer;">儲存</button> <button type="submit" class="is-primary" style="min-height:40px;padding:0.4rem 1.05rem;border-radius:999px;border:2px solid var(--color-brand);background:var(--color-brand);font-weight:700;cursor:pointer;">儲存</button>
<a href="?do=drama" style="min-height:40px;padding:0.4rem 1.05rem;border-radius:999px;border:2px solid rgba(255,204,0,0.35);background:#fff;font-weight:700;text-decoration:none;display:inline-flex;align-items:center;">取消</a> <a href="?do=drama" style="min-height:40px;padding:0.4rem 1.05rem;border-radius:999px;border:2px solid rgba(255,204,0,0.35);background:#fff;font-weight:700;text-decoration:none;display:inline-flex;align-items:center;">取消</a>
</div> </div>
</form> </form>
</section> </section>
@@ -1,71 +1,71 @@
<?php <?php
$typeLabels = [ $typeLabels = [
'variety' => '綜藝', 'variety' => '綜藝',
'drama' => '戲劇', 'drama' => '戲劇',
]; ];
$rows = $Drama->all(" ORDER BY `rank` ASC, `id` ASC"); $rows = $Drama->all(" ORDER BY `rank` ASC, `id` ASC");
?> ?>
<section> <section>
<h2 class="section-title">追劇管理</h2> <h2 class="section-title">追劇管理</h2>
<div class="admin-toolbar"> <div class="admin-toolbar">
<a href="?do=add">新增作品</a> <a href="?do=add">新增作品</a>
</div> </div>
<div class="drama-list"> <div class="drama-list">
<?php foreach ($rows as $idx => $row): ?> <?php foreach ($rows as $idx => $row): ?>
<?php <?php
$prev = ($idx === 0) ? $row['id'] : $rows[$idx - 1]['id']; $prev = ($idx === 0) ? $row['id'] : $rows[$idx - 1]['id'];
$next = ($idx === count($rows) - 1) ? $row['id'] : $rows[$idx + 1]['id']; $next = ($idx === count($rows) - 1) ? $row['id'] : $rows[$idx + 1]['id'];
$typeText = $typeLabels[$row['type']] ?? $row['type']; $typeText = $typeLabels[$row['type']] ?? $row['type'];
?> ?>
<div class="drama-row"> <div class="drama-row">
<div class="drama-row__info"> <div class="drama-row__info">
<strong><?= htmlspecialchars($row['title'], ENT_QUOTES, 'UTF-8') ?></strong><br> <strong><?= htmlspecialchars($row['title'], ENT_QUOTES, 'UTF-8') ?></strong><br>
<?= htmlspecialchars($typeText . '|' . $row['platform'], ENT_QUOTES, 'UTF-8') ?><br> <?= htmlspecialchars($typeText . '|' . $row['platform'], ENT_QUOTES, 'UTF-8') ?><br>
進度:第 <?= (int)$row['current_season'] ?> 季・第 <?= (int)$row['current_episode'] ?> 集 進度:第 <?= (int)$row['current_season'] ?> 季・第 <?= (int)$row['current_episode'] ?> 集
<?php if (!empty($row['current_position'])): ?> <?php if (!empty($row['current_position'])): ?>
・<?= htmlspecialchars($row['current_position'], ENT_QUOTES, 'UTF-8') ?> ・<?= htmlspecialchars($row['current_position'], ENT_QUOTES, 'UTF-8') ?>
<?php endif; ?> <?php endif; ?>
</div> </div>
<div class="drama-row__actions"> <div class="drama-row__actions">
<button type="button" class="show" data-id="<?= (int)$row['id'] ?>"> <button type="button" class="show" data-id="<?= (int)$row['id'] ?>">
<?= ((int)$row['sh'] === 1) ? '顯示' : '隱藏' ?> <?= ((int)$row['sh'] === 1) ? '顯示' : '隱藏' ?>
</button> </button>
<button type="button" class="switch-rank" data-ids="<?= (int)$row['id'] . '-' . (int)$prev ?>">往上</button> <button type="button" class="switch-rank" data-ids="<?= (int)$row['id'] . '-' . (int)$prev ?>">往上</button>
<button type="button" class="switch-rank" data-ids="<?= (int)$row['id'] . '-' . (int)$next ?>">往下</button> <button type="button" class="switch-rank" data-ids="<?= (int)$row['id'] . '-' . (int)$next ?>">往下</button>
<a class="btn-like" href="?do=edit&amp;id=<?= (int)$row['id'] ?>">編輯</a> <a class="btn-like" href="?do=edit&amp;id=<?= (int)$row['id'] ?>">編輯</a>
<button type="button" class="del-drama" data-id="<?= (int)$row['id'] ?>">刪除</button> <button type="button" class="del-drama" data-id="<?= (int)$row['id'] ?>">刪除</button>
</div> </div>
</div> </div>
<?php endforeach; ?> <?php endforeach; ?>
<?php if (count($rows) === 0): ?> <?php if (count($rows) === 0): ?>
<p class="admin-lead">尚無資料,先新增一部作品吧。</p> <p class="admin-lead">尚無資料,先新增一部作品吧。</p>
<?php endif; ?> <?php endif; ?>
</div> </div>
</section> </section>
<script> <script>
$(".switch-rank").on("click", function () { $(".switch-rank").on("click", function () {
let ids = $(this).data("ids").toString().split("-"); let ids = $(this).data("ids").toString().split("-");
$.post("./api/sw.php", { ids, table: "Drama" }, function () { $.post("./api/sw.php", { ids, table: "Drama" }, function () {
location.reload(); location.reload();
}); });
}); });
$(".show").on("click", function () { $(".show").on("click", function () {
let id = $(this).data("id"); let id = $(this).data("id");
$.post("./api/show.php", { id }, function () { $.post("./api/show.php", { id }, function () {
location.reload(); location.reload();
}); });
}); });
$(".del-drama").on("click", function () { $(".del-drama").on("click", function () {
if (!confirm("確定刪除這部作品?")) return; if (!confirm("確定刪除這部作品?")) return;
let id = $(this).data("id"); let id = $(this).data("id");
$.post("./api/del.php", { id, table: "Drama" }, function () { $.post("./api/del.php", { id, table: "Drama" }, function () {
location.reload(); location.reload();
}); });
}); });
</script> </script>
@@ -1,107 +1,107 @@
<?php <?php
$id = $_GET['id'] ?? 0; $id = $_GET['id'] ?? 0;
$row = $Drama->find($id); $row = $Drama->find($id);
if (!$row) { if (!$row) {
echo '<p class="admin-lead">找不到這筆資料。</p>'; echo '<p class="admin-lead">找不到這筆資料。</p>';
return; return;
} }
if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$row['title'] = trim($_POST['title'] ?? $row['title']); $row['title'] = trim($_POST['title'] ?? $row['title']);
$row['type'] = $_POST['type'] ?? $row['type']; $row['type'] = $_POST['type'] ?? $row['type'];
$row['platform'] = $_POST['platform'] ?? $row['platform']; $row['platform'] = $_POST['platform'] ?? $row['platform'];
$row['watch_url'] = trim($_POST['watch_url'] ?? ''); $row['watch_url'] = trim($_POST['watch_url'] ?? '');
$row['current_season'] = max(1, (int)($_POST['current_season'] ?? 1)); $row['current_season'] = max(1, (int)($_POST['current_season'] ?? 1));
$row['current_episode'] = max(0, (int)($_POST['current_episode'] ?? 0)); $row['current_episode'] = max(0, (int)($_POST['current_episode'] ?? 0));
$row['current_position'] = trim($_POST['current_position'] ?? ''); $row['current_position'] = trim($_POST['current_position'] ?? '');
$row['progress_note'] = trim($_POST['progress_note'] ?? ''); $row['progress_note'] = trim($_POST['progress_note'] ?? '');
$row['status'] = $_POST['status'] ?? $row['status']; $row['status'] = $_POST['status'] ?? $row['status'];
$row['rating'] = ($_POST['rating'] !== '') ? $_POST['rating'] : ''; $row['rating'] = ($_POST['rating'] !== '') ? $_POST['rating'] : '';
$row['intro'] = trim($_POST['intro'] ?? ''); $row['intro'] = trim($_POST['intro'] ?? '');
$row['note'] = trim($_POST['note'] ?? ''); $row['note'] = trim($_POST['note'] ?? '');
$Drama->save($row); $Drama->save($row);
to("?do=drama"); to("?do=drama");
exit; exit;
} }
?> ?>
<section> <section>
<h2 class="section-title">編輯作品</h2> <h2 class="section-title">編輯作品</h2>
<form class="admin-form" method="post" action="?do=edit&amp;id=<?= (int)$row['id'] ?>"> <form class="admin-form" method="post" action="?do=edit&amp;id=<?= (int)$row['id'] ?>">
<label for="title">劇名/節目名</label> <label for="title">劇名/節目名</label>
<input type="text" id="title" name="title" required <input type="text" id="title" name="title" required
value="<?= htmlspecialchars($row['title'], ENT_QUOTES, 'UTF-8') ?>"> value="<?= htmlspecialchars($row['title'], ENT_QUOTES, 'UTF-8') ?>">
<label for="type">類型</label> <label for="type">類型</label>
<select id="type" name="type"> <select id="type" name="type">
<option value="variety"<?= $row['type'] === 'variety' ? ' selected' : '' ?>>綜藝</option> <option value="variety"<?= $row['type'] === 'variety' ? ' selected' : '' ?>>綜藝</option>
<option value="drama"<?= $row['type'] === 'drama' ? ' selected' : '' ?>>戲劇</option> <option value="drama"<?= $row['type'] === 'drama' ? ' selected' : '' ?>>戲劇</option>
</select> </select>
<label for="platform">上架平台</label> <label for="platform">上架平台</label>
<select id="platform" name="platform"> <select id="platform" name="platform">
<?php <?php
$platforms = ['Netflix', 'Disney+', 'friDay影音', '愛奇藝', 'LINE TV', 'YouTube', '其他']; $platforms = ['Netflix', 'Disney+', 'friDay影音', '愛奇藝', 'LINE TV', 'YouTube', '其他'];
foreach ($platforms as $p): foreach ($platforms as $p):
?> ?>
<option value="<?= htmlspecialchars($p, ENT_QUOTES, 'UTF-8') ?>"<?= $row['platform'] === $p ? ' selected' : '' ?>> <option value="<?= htmlspecialchars($p, ENT_QUOTES, 'UTF-8') ?>"<?= $row['platform'] === $p ? ' selected' : '' ?>>
<?= htmlspecialchars($p, ENT_QUOTES, 'UTF-8') ?> <?= htmlspecialchars($p, ENT_QUOTES, 'UTF-8') ?>
</option> </option>
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
<label for="watch_url">直接觀看網址</label> <label for="watch_url">直接觀看網址</label>
<input type="url" id="watch_url" name="watch_url" <input type="url" id="watch_url" name="watch_url"
value="<?= htmlspecialchars($row['watch_url'] ?? '', ENT_QUOTES, 'UTF-8') ?>" value="<?= htmlspecialchars($row['watch_url'] ?? '', ENT_QUOTES, 'UTF-8') ?>"
placeholder="https://..."> placeholder="https://...">
<label for="current_season">目前看到第幾季</label> <label for="current_season">目前看到第幾季</label>
<input type="number" id="current_season" name="current_season" min="1" <input type="number" id="current_season" name="current_season" min="1"
value="<?= (int)$row['current_season'] ?>"> value="<?= (int)$row['current_season'] ?>">
<label for="current_episode">目前看到第幾集</label> <label for="current_episode">目前看到第幾集</label>
<input type="number" id="current_episode" name="current_episode" min="0" <input type="number" id="current_episode" name="current_episode" min="0"
value="<?= (int)$row['current_episode'] ?>"> value="<?= (int)$row['current_episode'] ?>">
<label for="current_position">這一集看到哪裡</label> <label for="current_position">這一集看到哪裡</label>
<input type="text" id="current_position" name="current_position" <input type="text" id="current_position" name="current_position"
value="<?= htmlspecialchars($row['current_position'] ?? '', ENT_QUOTES, 'UTF-8') ?>"> value="<?= htmlspecialchars($row['current_position'] ?? '', ENT_QUOTES, 'UTF-8') ?>">
<label for="progress_note">進度情節備註</label> <label for="progress_note">進度情節備註</label>
<input type="text" id="progress_note" name="progress_note" <input type="text" id="progress_note" name="progress_note"
value="<?= htmlspecialchars($row['progress_note'] ?? '', ENT_QUOTES, 'UTF-8') ?>"> value="<?= htmlspecialchars($row['progress_note'] ?? '', ENT_QUOTES, 'UTF-8') ?>">
<label for="status">狀態</label> <label for="status">狀態</label>
<select id="status" name="status"> <select id="status" name="status">
<?php <?php
$statuses = [ $statuses = [
'want' => '想看', 'want' => '想看',
'watching' => '追劇中', 'watching' => '追劇中',
'done' => '已追完', 'done' => '已追完',
'paused' => '暫停', 'paused' => '暫停',
'dropped' => '棄劇', 'dropped' => '棄劇',
]; ];
foreach ($statuses as $key => $label): foreach ($statuses as $key => $label):
?> ?>
<option value="<?= $key ?>"<?= $row['status'] === $key ? ' selected' : '' ?>><?= $label ?></option> <option value="<?= $key ?>"<?= $row['status'] === $key ? ' selected' : '' ?>><?= $label ?></option>
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
<label for="rating">評分</label> <label for="rating">評分</label>
<input type="number" id="rating" name="rating" min="0" max="10" step="0.1" <input type="number" id="rating" name="rating" min="0" max="10" step="0.1"
value="<?= htmlspecialchars($row['rating'] ?? '', ENT_QUOTES, 'UTF-8') ?>"> value="<?= htmlspecialchars($row['rating'] ?? '', ENT_QUOTES, 'UTF-8') ?>">
<label for="intro">簡介</label> <label for="intro">簡介</label>
<textarea id="intro" name="intro" rows="4"><?= htmlspecialchars($row['intro'] ?? '', ENT_QUOTES, 'UTF-8') ?></textarea> <textarea id="intro" name="intro" rows="4"><?= htmlspecialchars($row['intro'] ?? '', ENT_QUOTES, 'UTF-8') ?></textarea>
<label for="note">心得備註</label> <label for="note">心得備註</label>
<textarea id="note" name="note" rows="3"><?= htmlspecialchars($row['note'] ?? '', ENT_QUOTES, 'UTF-8') ?></textarea> <textarea id="note" name="note" rows="3"><?= htmlspecialchars($row['note'] ?? '', ENT_QUOTES, 'UTF-8') ?></textarea>
<div class="form-actions" style="margin-top:1.25rem;display:flex;gap:10px;justify-content:center;"> <div class="form-actions" style="margin-top:1.25rem;display:flex;gap:10px;justify-content:center;">
<button type="submit" class="is-primary" style="min-height:40px;padding:0.4rem 1.05rem;border-radius:999px;border:2px solid var(--color-brand);background:var(--color-brand);font-weight:700;cursor:pointer;">儲存</button> <button type="submit" class="is-primary" style="min-height:40px;padding:0.4rem 1.05rem;border-radius:999px;border:2px solid var(--color-brand);background:var(--color-brand);font-weight:700;cursor:pointer;">儲存</button>
<a href="?do=drama" style="min-height:40px;padding:0.4rem 1.05rem;border-radius:999px;border:2px solid rgba(255,204,0,0.35);background:#fff;font-weight:700;text-decoration:none;display:inline-flex;align-items:center;">取消</a> <a href="?do=drama" style="min-height:40px;padding:0.4rem 1.05rem;border-radius:999px;border:2px solid rgba(255,204,0,0.35);background:#fff;font-weight:700;text-decoration:none;display:inline-flex;align-items:center;">取消</a>
</div> </div>
</form> </form>
</section> </section>
@@ -1,7 +1,7 @@
<section> <section>
<h2 class="section-title">後台首頁</h2> <h2 class="section-title">後台首頁</h2>
<p class="admin-lead">乙級第三題骨架:清單、詳情、進度、後台 CRUD。訂票不做。</p> <p class="admin-lead">乙級第三題骨架:清單、詳情、進度、後台 CRUD。訂票不做。</p>
<div class="admin-toolbar"> <div class="admin-toolbar">
<a href="?do=drama">進入追劇管理</a> <a href="?do=drama">進入追劇管理</a>
</div> </div>
</section> </section>
@@ -1,429 +1,429 @@
/* ── 追劇清單/後台專用樣式 ── */ /* ── 追劇清單/後台專用樣式 ── */
.vedio-list-page .vedio-intro { .vedio-list-page .vedio-intro {
text-align: left; text-align: left;
} }
.vedio-list-page .vedio-intro p { .vedio-list-page .vedio-intro p {
color: var(--color-text-muted); color: var(--color-text-muted);
font-size: 1rem; font-size: 1rem;
line-height: 1.8; line-height: 1.8;
margin: 0 0 0.85rem; margin: 0 0 0.85rem;
text-align: left; text-align: left;
} }
.vedio-list-page .vedio-intro p:last-child { .vedio-list-page .vedio-intro p:last-child {
margin-bottom: 0; margin-bottom: 0;
} }
.vedio-list-page .list-lead { .vedio-list-page .list-lead {
color: var(--color-text-muted); color: var(--color-text-muted);
font-size: 1rem; font-size: 1rem;
margin: 0 0 1.25rem; margin: 0 0 1.25rem;
text-align: left; text-align: left;
} }
.vedio-list-page .type-tabs { .vedio-list-page .type-tabs {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: center; justify-content: center;
gap: 10px; gap: 10px;
margin: 0 0 1.75rem; margin: 0 0 1.75rem;
} }
.vedio-list-page .type-tab { .vedio-list-page .type-tab {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
border: 2px solid rgba(255, 204, 0, 0.22); border: 2px solid rgba(255, 204, 0, 0.22);
background: var(--color-surface-soft); background: var(--color-surface-soft);
color: var(--color-text-dark); color: var(--color-text-dark);
font-weight: 700; font-weight: 700;
font-size: 0.92rem; font-size: 0.92rem;
padding: 0.45rem 1.1rem; padding: 0.45rem 1.1rem;
border-radius: 999px; border-radius: 999px;
text-decoration: none; text-decoration: none;
white-space: nowrap; white-space: nowrap;
transition: border-color 0.18s ease, background 0.18s ease, color 0.18s ease; transition: border-color 0.18s ease, background 0.18s ease, color 0.18s ease;
} }
.vedio-list-page .type-tab:hover { .vedio-list-page .type-tab:hover {
border-color: var(--color-brand); border-color: var(--color-brand);
} }
.vedio-list-page .type-tab.is-active { .vedio-list-page .type-tab.is-active {
border-color: var(--color-brand); border-color: var(--color-brand);
background: #fffce8; background: #fffce8;
color: var(--color-brand-dark); color: var(--color-brand-dark);
} }
.vedio-list-page .drama-grid { .vedio-list-page .drama-grid {
display: grid; display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16px; gap: 16px;
margin: 0 0 2rem; margin: 0 0 2rem;
} }
.vedio-list-page .drama-card { .vedio-list-page .drama-card {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: stretch; align-items: stretch;
text-align: left; text-align: left;
padding: 1.15rem 1.2rem 1.25rem; padding: 1.15rem 1.2rem 1.25rem;
background: var(--color-surface-soft); background: var(--color-surface-soft);
border: 2px solid rgba(255, 204, 0, 0.18); border: 2px solid rgba(255, 204, 0, 0.18);
border-radius: 12px; border-radius: 12px;
box-sizing: border-box; box-sizing: border-box;
min-height: 100%; min-height: 100%;
} }
.vedio-list-page .drama-card:hover { .vedio-list-page .drama-card:hover {
border-color: var(--color-brand); border-color: var(--color-brand);
background: #fffce8; background: #fffce8;
} }
.vedio-list-page .drama-card__poster { .vedio-list-page .drama-card__poster {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
aspect-ratio: 16 / 9; aspect-ratio: 16 / 9;
margin: 0 0 0.95rem; margin: 0 0 0.95rem;
border-radius: 8px; border-radius: 8px;
background: background:
linear-gradient(145deg, rgba(255, 204, 0, 0.22), rgba(0, 0, 0, 0.08)), linear-gradient(145deg, rgba(255, 204, 0, 0.22), rgba(0, 0, 0, 0.08)),
var(--color-surface-soft); var(--color-surface-soft);
color: var(--color-brand-dark); color: var(--color-brand-dark);
font-weight: 800; font-weight: 800;
font-size: 0.95rem; font-size: 0.95rem;
letter-spacing: 0.04em; letter-spacing: 0.04em;
text-align: center; text-align: center;
padding: 0.75rem; padding: 0.75rem;
box-sizing: border-box; box-sizing: border-box;
} }
.vedio-list-page .drama-card__meta { .vedio-list-page .drama-card__meta {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 6px; gap: 6px;
margin: 0 0 0.55rem; margin: 0 0 0.55rem;
} }
.vedio-list-page .drama-tag { .vedio-list-page .drama-tag {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 0.18rem 0.55rem; padding: 0.18rem 0.55rem;
border-radius: 999px; border-radius: 999px;
font-size: 0.72rem; font-size: 0.72rem;
font-weight: 700; font-weight: 700;
letter-spacing: 0.03em; letter-spacing: 0.03em;
white-space: nowrap; white-space: nowrap;
background: rgba(255, 204, 0, 0.18); background: rgba(255, 204, 0, 0.18);
color: var(--color-brand-dark); color: var(--color-brand-dark);
} }
.vedio-list-page .drama-tag--platform { .vedio-list-page .drama-tag--platform {
background: rgba(0, 0, 0, 0.06); background: rgba(0, 0, 0, 0.06);
color: var(--color-text-muted); color: var(--color-text-muted);
} }
.vedio-list-page .drama-card__title { .vedio-list-page .drama-card__title {
margin: 0 0 0.45rem; margin: 0 0 0.45rem;
font-size: 1.15rem; font-size: 1.15rem;
font-weight: 800; font-weight: 800;
color: var(--color-text-dark); color: var(--color-text-dark);
line-height: 1.35; line-height: 1.35;
text-align: center; text-align: center;
} }
.vedio-list-page .drama-card__progress, .vedio-list-page .drama-card__progress,
.vedio-list-page .drama-card__note { .vedio-list-page .drama-card__note {
margin: 0 0 0.35rem; margin: 0 0 0.35rem;
color: var(--color-text-muted); color: var(--color-text-muted);
font-size: 0.9rem; font-size: 0.9rem;
line-height: 1.65; line-height: 1.65;
text-align: left; text-align: left;
} }
.vedio-list-page .drama-card__note { .vedio-list-page .drama-card__note {
font-size: 0.84rem; font-size: 0.84rem;
margin-bottom: 0.85rem; margin-bottom: 0.85rem;
} }
.vedio-list-page .drama-card__actions { .vedio-list-page .drama-card__actions {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 10px; gap: 10px;
justify-content: center; justify-content: center;
margin-top: auto; margin-top: auto;
padding-top: 0.35rem; padding-top: 0.35rem;
} }
.vedio-list-page .drama-card__actions a { .vedio-list-page .drama-card__actions a {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: 6px; gap: 6px;
min-height: 38px; min-height: 38px;
padding: 0.35rem 0.9rem; padding: 0.35rem 0.9rem;
border-radius: 999px; border-radius: 999px;
font-size: 0.86rem; font-size: 0.86rem;
font-weight: 700; font-weight: 700;
text-decoration: none; text-decoration: none;
white-space: nowrap; white-space: nowrap;
border: 2px solid rgba(255, 204, 0, 0.35); border: 2px solid rgba(255, 204, 0, 0.35);
background: #fff; background: #fff;
color: var(--color-brand-dark); color: var(--color-brand-dark);
} }
.vedio-list-page .drama-card__actions a:hover { .vedio-list-page .drama-card__actions a:hover {
border-color: var(--color-brand); border-color: var(--color-brand);
background: #fffce8; background: #fffce8;
} }
.vedio-list-page .drama-card__actions a.is-primary { .vedio-list-page .drama-card__actions a.is-primary {
background: var(--color-brand); background: var(--color-brand);
border-color: var(--color-brand); border-color: var(--color-brand);
color: #1a1a1a; color: #1a1a1a;
} }
.vedio-list-page .detail-block { .vedio-list-page .detail-block {
padding: 1.35rem 1.4rem 1.5rem; padding: 1.35rem 1.4rem 1.5rem;
border-radius: 12px; border-radius: 12px;
background: var(--color-surface-soft); background: var(--color-surface-soft);
border: 1px solid rgba(255, 204, 0, 0.25); border: 1px solid rgba(255, 204, 0, 0.25);
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
} }
.vedio-list-page .detail-block__eyebrow { .vedio-list-page .detail-block__eyebrow {
display: block; display: block;
text-align: center; text-align: center;
font-size: 0.78rem; font-size: 0.78rem;
font-weight: 700; font-weight: 700;
letter-spacing: 0.08em; letter-spacing: 0.08em;
color: var(--color-text-muted); color: var(--color-text-muted);
margin: 0 0 0.45rem; margin: 0 0 0.45rem;
} }
.vedio-list-page .detail-block__title { .vedio-list-page .detail-block__title {
margin: 0 0 0.75rem; margin: 0 0 0.75rem;
text-align: center; text-align: center;
font-size: 1.35rem; font-size: 1.35rem;
font-weight: 800; font-weight: 800;
color: var(--color-brand-dark); color: var(--color-brand-dark);
line-height: 1.35; line-height: 1.35;
} }
.vedio-list-page .detail-block p { .vedio-list-page .detail-block p {
margin: 0 0 0.75rem; margin: 0 0 0.75rem;
color: var(--color-text-muted); color: var(--color-text-muted);
font-size: 0.98rem; font-size: 0.98rem;
line-height: 1.8; line-height: 1.8;
text-align: left; text-align: left;
} }
.vedio-list-page .detail-block__progress-box { .vedio-list-page .detail-block__progress-box {
margin: 1rem 0; margin: 1rem 0;
padding: 0.9rem 1rem; padding: 0.9rem 1rem;
border-radius: 10px; border-radius: 10px;
background: #fffce8; background: #fffce8;
border: 1px solid rgba(255, 204, 0, 0.35); border: 1px solid rgba(255, 204, 0, 0.35);
} }
.vedio-list-page .detail-block__progress-box p { .vedio-list-page .detail-block__progress-box p {
margin: 0 0 0.35rem; margin: 0 0 0.35rem;
} }
.vedio-list-page .detail-block__progress-box p:last-child { .vedio-list-page .detail-block__progress-box p:last-child {
margin-bottom: 0; margin-bottom: 0;
} }
.vedio-list-page .detail-block__actions, .vedio-list-page .detail-block__actions,
.vedio-list-page .form-actions { .vedio-list-page .form-actions {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 10px; gap: 10px;
justify-content: center; justify-content: center;
margin-top: 1.1rem; margin-top: 1.1rem;
} }
.vedio-list-page .detail-block__actions a, .vedio-list-page .detail-block__actions a,
.vedio-list-page .form-actions a, .vedio-list-page .form-actions a,
.vedio-list-page .form-actions button, .vedio-list-page .form-actions button,
.vedio-list-page .progress-form button { .vedio-list-page .progress-form button {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: 6px; gap: 6px;
min-height: 40px; min-height: 40px;
padding: 0.4rem 1.05rem; padding: 0.4rem 1.05rem;
border-radius: 999px; border-radius: 999px;
font-size: 0.9rem; font-size: 0.9rem;
font-weight: 700; font-weight: 700;
text-decoration: none; text-decoration: none;
white-space: nowrap; white-space: nowrap;
cursor: pointer; cursor: pointer;
border: 2px solid rgba(255, 204, 0, 0.35); border: 2px solid rgba(255, 204, 0, 0.35);
background: #fff; background: #fff;
color: var(--color-brand-dark); color: var(--color-brand-dark);
} }
.vedio-list-page .detail-block__actions a.is-primary, .vedio-list-page .detail-block__actions a.is-primary,
.vedio-list-page .form-actions button.is-primary, .vedio-list-page .form-actions button.is-primary,
.vedio-list-page .progress-form button.is-primary { .vedio-list-page .progress-form button.is-primary {
background: var(--color-brand); background: var(--color-brand);
border-color: var(--color-brand); border-color: var(--color-brand);
color: #1a1a1a; color: #1a1a1a;
} }
.vedio-list-page .empty-hint { .vedio-list-page .empty-hint {
text-align: center; text-align: center;
color: var(--color-text-muted); color: var(--color-text-muted);
padding: 1.5rem 1rem; padding: 1.5rem 1rem;
margin: 0 0 1.5rem; margin: 0 0 1.5rem;
} }
.vedio-list-page .progress-form, .vedio-list-page .progress-form,
.vedio-admin-page .admin-form { .vedio-admin-page .admin-form {
max-width: 560px; max-width: 560px;
margin: 0 auto 2rem; margin: 0 auto 2rem;
padding: 1.25rem 1.35rem; padding: 1.25rem 1.35rem;
background: var(--color-surface-soft); background: var(--color-surface-soft);
border: 1px solid rgba(255, 204, 0, 0.25); border: 1px solid rgba(255, 204, 0, 0.25);
border-radius: 12px; border-radius: 12px;
} }
.vedio-list-page .progress-form label, .vedio-list-page .progress-form label,
.vedio-admin-page .admin-form label { .vedio-admin-page .admin-form label {
display: block; display: block;
margin: 0.85rem 0 0.35rem; margin: 0.85rem 0 0.35rem;
font-weight: 700; font-weight: 700;
color: var(--color-text-dark); color: var(--color-text-dark);
text-align: left; text-align: left;
} }
.vedio-list-page .progress-form input, .vedio-list-page .progress-form input,
.vedio-list-page .progress-form select, .vedio-list-page .progress-form select,
.vedio-list-page .progress-form textarea, .vedio-list-page .progress-form textarea,
.vedio-admin-page .admin-form input, .vedio-admin-page .admin-form input,
.vedio-admin-page .admin-form select, .vedio-admin-page .admin-form select,
.vedio-admin-page .admin-form textarea { .vedio-admin-page .admin-form textarea {
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
padding: 0.55rem 0.7rem; padding: 0.55rem 0.7rem;
border: 1px solid rgba(0, 0, 0, 0.15); border: 1px solid rgba(0, 0, 0, 0.15);
border-radius: 8px; border-radius: 8px;
font-size: 0.95rem; font-size: 0.95rem;
} }
.vedio-list-page .vedio-admin-link, .vedio-list-page .vedio-admin-link,
.vedio-admin-page .admin-nav { .vedio-admin-page .admin-nav {
text-align: center; text-align: center;
margin: 1rem 0; margin: 1rem 0;
} }
.vedio-list-page .vedio-admin-link a { .vedio-list-page .vedio-admin-link a {
color: var(--color-brand-dark); color: var(--color-brand-dark);
font-weight: 700; font-weight: 700;
} }
.vedio-admin-page .admin-wrap { .vedio-admin-page .admin-wrap {
padding-top: 1.5rem; padding-top: 1.5rem;
padding-bottom: 2.5rem; padding-bottom: 2.5rem;
} }
.vedio-admin-page .admin-nav { .vedio-admin-page .admin-nav {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 10px; gap: 10px;
justify-content: center; justify-content: center;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
} }
.vedio-admin-page .admin-nav a { .vedio-admin-page .admin-nav a {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 0.4rem 1rem; padding: 0.4rem 1rem;
border-radius: 999px; border-radius: 999px;
border: 2px solid rgba(255, 204, 0, 0.35); border: 2px solid rgba(255, 204, 0, 0.35);
background: #fffce8; background: #fffce8;
color: var(--color-brand-dark); color: var(--color-brand-dark);
font-weight: 700; font-weight: 700;
text-decoration: none; text-decoration: none;
white-space: nowrap; white-space: nowrap;
} }
.vedio-admin-page .admin-lead { .vedio-admin-page .admin-lead {
text-align: center; text-align: center;
color: var(--color-text-muted); color: var(--color-text-muted);
margin: 0 0 1.25rem; margin: 0 0 1.25rem;
} }
.vedio-admin-page .drama-row { .vedio-admin-page .drama-row {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 12px; gap: 12px;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 12px 14px; padding: 12px 14px;
margin: 0 0 10px; margin: 0 0 10px;
background: #fff; background: #fff;
border: 1px solid rgba(255, 204, 0, 0.25); border: 1px solid rgba(255, 204, 0, 0.25);
border-radius: 10px; border-radius: 10px;
color: #222; color: #222;
} }
.vedio-admin-page .drama-row__info { .vedio-admin-page .drama-row__info {
flex: 1 1 220px; flex: 1 1 220px;
text-align: left; text-align: left;
} }
.vedio-admin-page .drama-row__actions { .vedio-admin-page .drama-row__actions {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 6px; gap: 6px;
justify-content: flex-end; justify-content: flex-end;
} }
.vedio-admin-page .drama-row__actions button, .vedio-admin-page .drama-row__actions button,
.vedio-admin-page .drama-row__actions a.btn-like { .vedio-admin-page .drama-row__actions a.btn-like {
appearance: none; appearance: none;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
min-height: 34px; min-height: 34px;
padding: 0.25rem 0.7rem; padding: 0.25rem 0.7rem;
border: 1px solid rgba(0, 0, 0, 0.2); border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 6px; border-radius: 6px;
background: #fffce8; background: #fffce8;
color: #222; color: #222;
font-size: 0.85rem; font-size: 0.85rem;
font-weight: 700; font-weight: 700;
cursor: pointer; cursor: pointer;
text-decoration: none; text-decoration: none;
white-space: nowrap; white-space: nowrap;
} }
.vedio-admin-page .admin-toolbar { .vedio-admin-page .admin-toolbar {
text-align: center; text-align: center;
margin: 0 0 1rem; margin: 0 0 1rem;
} }
.vedio-admin-page .admin-toolbar a { .vedio-admin-page .admin-toolbar a {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 0.45rem 1.1rem; padding: 0.45rem 1.1rem;
border-radius: 999px; border-radius: 999px;
background: var(--color-brand); background: var(--color-brand);
color: #1a1a1a; color: #1a1a1a;
font-weight: 800; font-weight: 800;
text-decoration: none; text-decoration: none;
} }
@media (max-width: 640px) { @media (max-width: 640px) {
.vedio-list-page .drama-grid { .vedio-list-page .drama-grid {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
} }
@@ -1,62 +1,62 @@
-- 深夜清單 / 追劇資料庫 -- 深夜清單 / 追劇資料庫
-- 本機 XAMPP:phpMyAdmin 匯入此檔即可 -- 本機 XAMPP:phpMyAdmin 匯入此檔即可
CREATE DATABASE IF NOT EXISTS `vedio_list` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE DATABASE IF NOT EXISTS `vedio_list` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE `vedio_list`; USE `vedio_list`;
DROP TABLE IF EXISTS `dramas`; DROP TABLE IF EXISTS `dramas`;
CREATE TABLE `dramas` ( CREATE TABLE `dramas` (
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`title` varchar(100) NOT NULL COMMENT '劇名/節目名', `title` varchar(100) NOT NULL COMMENT '劇名/節目名',
`type` varchar(20) NOT NULL DEFAULT 'drama' COMMENT 'variety=綜藝, drama=戲劇', `type` varchar(20) NOT NULL DEFAULT 'drama' COMMENT 'variety=綜藝, drama=戲劇',
`platform` varchar(50) NOT NULL COMMENT '上架平台', `platform` varchar(50) NOT NULL COMMENT '上架平台',
`watch_url` varchar(500) DEFAULT NULL COMMENT '作品直接觀看網址', `watch_url` varchar(500) DEFAULT NULL COMMENT '作品直接觀看網址',
`current_season` int(10) UNSIGNED NOT NULL DEFAULT 1 COMMENT '目前第幾季', `current_season` int(10) UNSIGNED NOT NULL DEFAULT 1 COMMENT '目前第幾季',
`current_episode` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '目前第幾集', `current_episode` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '目前第幾集',
`current_position` varchar(50) DEFAULT NULL COMMENT '該集看到的位置', `current_position` varchar(50) DEFAULT NULL COMMENT '該集看到的位置',
`progress_note` varchar(255) DEFAULT NULL COMMENT '進度情節備註', `progress_note` varchar(255) DEFAULT NULL COMMENT '進度情節備註',
`status` varchar(20) NOT NULL DEFAULT 'watching' COMMENT 'want/watching/done/paused/dropped', `status` varchar(20) NOT NULL DEFAULT 'watching' COMMENT 'want/watching/done/paused/dropped',
`rating` decimal(3,1) DEFAULT NULL COMMENT '評分', `rating` decimal(3,1) DEFAULT NULL COMMENT '評分',
`intro` text COMMENT '簡介', `intro` text COMMENT '簡介',
`note` text COMMENT '心得備註', `note` text COMMENT '心得備註',
`sh` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '是否顯示', `sh` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '是否顯示',
`rank` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '排列順序', `rank` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '排列順序',
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO `dramas` INSERT INTO `dramas`
(`title`, `type`, `platform`, `watch_url`, `current_season`, `current_episode`, `current_position`, `progress_note`, `status`, `rating`, `intro`, `note`, `sh`, `rank`) (`title`, `type`, `platform`, `watch_url`, `current_season`, `current_episode`, `current_position`, `progress_note`, `status`, `rating`, `intro`, `note`, `sh`, `rank`)
VALUES VALUES
( (
'三傻遊肯亞', '三傻遊肯亞',
'variety', 'variety',
'Netflix', 'Netflix',
'https://www.netflix.com/title/82031207', 'https://www.netflix.com/title/82031207',
1, 1,
2, 2,
'28:40', '28:40',
'三人剛被扔進野外,表情比獅子還慌。', '三人剛被扔進野外,表情比獅子還慌。',
'watching', 'watching',
8.5, 8.5,
'三個人、一座肯亞,以及無數個「這也能出事?」的現場。笑點來得又急又野,像把旅行節目的安全感整袋拆掉,只留下腎上腺素與默契。', '三個人、一座肯亞,以及無數個「這也能出事?」的現場。笑點來得又急又野,像把旅行節目的安全感整袋拆掉,只留下腎上腺素與默契。',
'羅PD 首登 Netflix;志源在裡面,小黃更沒有藉口不補。', '羅PD 首登 Netflix;志源在裡面,小黃更沒有藉口不補。',
1, 1,
1 1
), ),
( (
'請回答1997', '請回答1997',
'drama', 'drama',
'Disney+', 'Disney+',
'https://www.disneyplus.com/en-tw/browse/entity-3979fb82-8681-44e8-b7be-e313091f0d73', 'https://www.disneyplus.com/en-tw/browse/entity-3979fb82-8681-44e8-b7be-e313091f0d73',
1, 1,
5, 5,
'41:12', '41:12',
'宿舍夜談剛結束,收音機還在響,心卻已經開始亂。', '宿舍夜談剛結束,收音機還在響,心卻已經開始亂。',
'watching', 'watching',
9.0, 9.0,
'把 1997 寫進宿舍、收音機與那一聲來不及說出口的喜歡。甜的時候甜到發亮,疼的時候又老實得讓人不敢快轉。', '把 1997 寫進宿舍、收音機與那一聲來不及說出口的喜歡。甜的時候甜到發亮,疼的時候又老實得讓人不敢快轉。',
'下一集容易哭,衛生紙先備好。', '下一集容易哭,衛生紙先備好。',
1, 1,
2 2
); );
@@ -1,65 +1,65 @@
<?php <?php
$id = $_GET['id'] ?? 0; $id = $_GET['id'] ?? 0;
$row = $Drama->find($id); $row = $Drama->find($id);
if (!$row || (int)$row['sh'] !== 1) { if (!$row || (int)$row['sh'] !== 1) {
echo '<p class="empty-hint">找不到這部作品,或它目前未公開顯示。</p>'; echo '<p class="empty-hint">找不到這部作品,或它目前未公開顯示。</p>';
echo '<p class="empty-hint"><a href="?do=main">回清單</a></p>'; echo '<p class="empty-hint"><a href="?do=main">回清單</a></p>';
return; return;
} }
$typeLabels = [ $typeLabels = [
'variety' => '綜藝', 'variety' => '綜藝',
'drama' => '戲劇', 'drama' => '戲劇',
]; ];
$statusLabels = [ $statusLabels = [
'want' => '想看', 'want' => '想看',
'watching' => '追劇中', 'watching' => '追劇中',
'done' => '已追完', 'done' => '已追完',
'paused' => '暫停', 'paused' => '暫停',
'dropped' => '棄劇', 'dropped' => '棄劇',
]; ];
$typeText = $typeLabels[$row['type']] ?? $row['type']; $typeText = $typeLabels[$row['type']] ?? $row['type'];
$statusText = $statusLabels[$row['status']] ?? $row['status']; $statusText = $statusLabels[$row['status']] ?? $row['status'];
?> ?>
<section class="detail-block"> <section class="detail-block">
<span class="detail-block__eyebrow"><?= htmlspecialchars($typeText . '|' . $row['platform'], ENT_QUOTES, 'UTF-8') ?></span> <span class="detail-block__eyebrow"><?= htmlspecialchars($typeText . '|' . $row['platform'], ENT_QUOTES, 'UTF-8') ?></span>
<h2 class="detail-block__title"><?= htmlspecialchars($row['title'], ENT_QUOTES, 'UTF-8') ?></h2> <h2 class="detail-block__title"><?= htmlspecialchars($row['title'], ENT_QUOTES, 'UTF-8') ?></h2>
<?php if (!empty($row['intro'])): ?> <?php if (!empty($row['intro'])): ?>
<p><?= nl2br(htmlspecialchars($row['intro'], ENT_QUOTES, 'UTF-8')) ?></p> <p><?= nl2br(htmlspecialchars($row['intro'], ENT_QUOTES, 'UTF-8')) ?></p>
<?php endif; ?> <?php endif; ?>
<div class="detail-block__progress-box"> <div class="detail-block__progress-box">
<p> <p>
觀看進度:第 <?= (int)$row['current_season'] ?> 季|第 <?= (int)$row['current_episode'] ?> 集 觀看進度:第 <?= (int)$row['current_season'] ?> 季|第 <?= (int)$row['current_episode'] ?> 集
<?php if (!empty($row['current_position'])): ?> <?php if (!empty($row['current_position'])): ?>
|<?= htmlspecialchars($row['current_position'], ENT_QUOTES, 'UTF-8') ?> |<?= htmlspecialchars($row['current_position'], ENT_QUOTES, 'UTF-8') ?>
<?php endif; ?> <?php endif; ?>
</p> </p>
<?php if (!empty($row['progress_note'])): ?> <?php if (!empty($row['progress_note'])): ?>
<p><?= htmlspecialchars($row['progress_note'], ENT_QUOTES, 'UTF-8') ?></p> <p><?= htmlspecialchars($row['progress_note'], ENT_QUOTES, 'UTF-8') ?></p>
<?php endif; ?> <?php endif; ?>
</div> </div>
<p>狀態:<?= htmlspecialchars($statusText, ENT_QUOTES, 'UTF-8') ?> <p>狀態:<?= htmlspecialchars($statusText, ENT_QUOTES, 'UTF-8') ?>
<?php if ($row['rating'] !== null && $row['rating'] !== ''): ?> <?php if ($row['rating'] !== null && $row['rating'] !== ''): ?>
 · 評分:<?= htmlspecialchars($row['rating'], ENT_QUOTES, 'UTF-8') ?>  · 評分:<?= htmlspecialchars($row['rating'], ENT_QUOTES, 'UTF-8') ?>
<?php endif; ?> <?php endif; ?>
</p> </p>
<?php if (!empty($row['note'])): ?> <?php if (!empty($row['note'])): ?>
<p><?= nl2br(htmlspecialchars($row['note'], ENT_QUOTES, 'UTF-8')) ?></p> <p><?= nl2br(htmlspecialchars($row['note'], ENT_QUOTES, 'UTF-8')) ?></p>
<?php endif; ?> <?php endif; ?>
<div class="detail-block__actions"> <div class="detail-block__actions">
<?php if (!empty($row['watch_url'])): ?> <?php if (!empty($row['watch_url'])): ?>
<a class="is-primary" href="<?= htmlspecialchars($row['watch_url'], ENT_QUOTES, 'UTF-8') ?>" <a class="is-primary" href="<?= htmlspecialchars($row['watch_url'], ENT_QUOTES, 'UTF-8') ?>"
target="_blank" rel="noopener noreferrer">繼續觀看</a> target="_blank" rel="noopener noreferrer">繼續觀看</a>
<?php endif; ?> <?php endif; ?>
<a href="?do=progress&amp;id=<?= (int)$row['id'] ?>">更新進度</a> <a href="?do=progress&amp;id=<?= (int)$row['id'] ?>">更新進度</a>
<a href="?do=main">回清單</a> <a href="?do=main">回清單</a>
</div> </div>
</section> </section>
@@ -1,84 +1,84 @@
<?php <?php
$typeLabels = [ $typeLabels = [
'variety' => '綜藝', 'variety' => '綜藝',
'drama' => '戲劇', 'drama' => '戲劇',
]; ];
$statusLabels = [ $statusLabels = [
'want' => '想看', 'want' => '想看',
'watching' => '追劇中', 'watching' => '追劇中',
'done' => '已追完', 'done' => '已追完',
'paused' => '暫停', 'paused' => '暫停',
'dropped' => '棄劇', 'dropped' => '棄劇',
]; ];
$filter = $_GET['type'] ?? 'all'; $filter = $_GET['type'] ?? 'all';
$where = ['sh' => 1]; $where = ['sh' => 1];
if ($filter === 'variety' || $filter === 'drama') { if ($filter === 'variety' || $filter === 'drama') {
$where['type'] = $filter; $where['type'] = $filter;
} }
$rows = $Drama->all($where, " ORDER BY `rank` ASC, `id` ASC"); $rows = $Drama->all($where, " ORDER BY `rank` ASC, `id` ASC");
?> ?>
<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_b">戲劇</span>, 這裡的清單來自資料庫:類型先分<span class="mark_y">綜藝</span>與<span class="mark_b">戲劇</span>,
進度與「繼續觀看」入口都能改。慢慢養也很好看。 進度與「繼續觀看」入口都能改。慢慢養也很好看。
</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="作品類型">
<a class="type-tab<?= $filter === 'all' ? ' is-active' : '' ?>" href="?do=main&amp;type=all">全部</a> <a class="type-tab<?= $filter === 'all' ? ' is-active' : '' ?>" href="?do=main&amp;type=all">全部</a>
<a class="type-tab<?= $filter === 'variety' ? ' is-active' : '' ?>" href="?do=main&amp;type=variety">綜藝</a> <a class="type-tab<?= $filter === 'variety' ? ' is-active' : '' ?>" href="?do=main&amp;type=variety">綜藝</a>
<a class="type-tab<?= $filter === 'drama' ? ' is-active' : '' ?>" href="?do=main&amp;type=drama">戲劇</a> <a class="type-tab<?= $filter === 'drama' ? ' is-active' : '' ?>" href="?do=main&amp;type=drama">戲劇</a>
</div> </div>
<?php if (count($rows) === 0): ?> <?php if (count($rows) === 0): ?>
<p class="empty-hint">這個類型目前還沒有作品——換「全部」看看,或到後台新增一筆。</p> <p class="empty-hint">這個類型目前還沒有作品——換「全部」看看,或到後台新增一筆。</p>
<?php else: ?> <?php else: ?>
<div class="drama-grid"> <div class="drama-grid">
<?php foreach ($rows as $row): ?> <?php foreach ($rows as $row): ?>
<?php <?php
$typeText = $typeLabels[$row['type']] ?? $row['type']; $typeText = $typeLabels[$row['type']] ?? $row['type'];
$statusText = $statusLabels[$row['status']] ?? $row['status']; $statusText = $statusLabels[$row['status']] ?? $row['status'];
$posterLabel = htmlspecialchars(strtoupper($row['platform']) . ' · ' . $typeText, ENT_QUOTES, 'UTF-8'); $posterLabel = htmlspecialchars(strtoupper($row['platform']) . ' · ' . $typeText, ENT_QUOTES, 'UTF-8');
?> ?>
<article class="drama-card" data-type="<?= htmlspecialchars($row['type'], ENT_QUOTES, 'UTF-8') ?>"> <article class="drama-card" data-type="<?= htmlspecialchars($row['type'], ENT_QUOTES, 'UTF-8') ?>">
<div class="drama-card__poster" aria-hidden="true"><?= $posterLabel ?></div> <div class="drama-card__poster" aria-hidden="true"><?= $posterLabel ?></div>
<div class="drama-card__meta"> <div class="drama-card__meta">
<span class="drama-tag"><?= htmlspecialchars($typeText, ENT_QUOTES, 'UTF-8') ?></span> <span class="drama-tag"><?= htmlspecialchars($typeText, ENT_QUOTES, 'UTF-8') ?></span>
<span class="drama-tag drama-tag--platform"><?= htmlspecialchars($row['platform'], ENT_QUOTES, 'UTF-8') ?></span> <span class="drama-tag drama-tag--platform"><?= htmlspecialchars($row['platform'], ENT_QUOTES, 'UTF-8') ?></span>
</div> </div>
<h3 class="drama-card__title"><?= htmlspecialchars($row['title'], ENT_QUOTES, 'UTF-8') ?></h3> <h3 class="drama-card__title"><?= htmlspecialchars($row['title'], ENT_QUOTES, 'UTF-8') ?></h3>
<p class="drama-card__progress"> <p class="drama-card__progress">
觀看進度:第 <?= (int)$row['current_season'] ?> 季・第 <?= (int)$row['current_episode'] ?> 集 觀看進度:第 <?= (int)$row['current_season'] ?> 季・第 <?= (int)$row['current_episode'] ?> 集
<?php if (!empty($row['current_position'])): ?> <?php if (!empty($row['current_position'])): ?>
・看到 <?= htmlspecialchars($row['current_position'], ENT_QUOTES, 'UTF-8') ?> ・看到 <?= htmlspecialchars($row['current_position'], ENT_QUOTES, 'UTF-8') ?>
<?php endif; ?> <?php endif; ?>
</p> </p>
<?php if (!empty($row['progress_note'])): ?> <?php if (!empty($row['progress_note'])): ?>
<p class="drama-card__note"><?= htmlspecialchars($row['progress_note'], ENT_QUOTES, 'UTF-8') ?></p> <p class="drama-card__note"><?= htmlspecialchars($row['progress_note'], ENT_QUOTES, 'UTF-8') ?></p>
<?php else: ?> <?php else: ?>
<p class="drama-card__note">狀態:<?= htmlspecialchars($statusText, ENT_QUOTES, 'UTF-8') ?></p> <p class="drama-card__note">狀態:<?= htmlspecialchars($statusText, ENT_QUOTES, 'UTF-8') ?></p>
<?php endif; ?> <?php endif; ?>
<div class="drama-card__actions"> <div class="drama-card__actions">
<?php if (!empty($row['watch_url'])): ?> <?php if (!empty($row['watch_url'])): ?>
<a class="is-primary" href="<?= htmlspecialchars($row['watch_url'], ENT_QUOTES, 'UTF-8') ?>" <a class="is-primary" href="<?= htmlspecialchars($row['watch_url'], ENT_QUOTES, 'UTF-8') ?>"
target="_blank" rel="noopener noreferrer">繼續觀看</a> target="_blank" rel="noopener noreferrer">繼續觀看</a>
<?php endif; ?> <?php endif; ?>
<a href="?do=detail&amp;id=<?= (int)$row['id'] ?>">查看詳情</a> <a href="?do=detail&amp;id=<?= (int)$row['id'] ?>">查看詳情</a>
<a href="?do=progress&amp;id=<?= (int)$row['id'] ?>">更新進度</a> <a href="?do=progress&amp;id=<?= (int)$row['id'] ?>">更新進度</a>
</div> </div>
</article> </article>
<?php endforeach; ?> <?php endforeach; ?>
</div> </div>
<?php endif; ?> <?php endif; ?>
</section> </section>
@@ -1,69 +1,69 @@
<?php <?php
$id = $_GET['id'] ?? 0; $id = $_GET['id'] ?? 0;
$row = $Drama->find($id); $row = $Drama->find($id);
if (!$row) { if (!$row) {
echo '<p class="empty-hint">找不到這部作品。</p>'; echo '<p class="empty-hint">找不到這部作品。</p>';
echo '<p class="empty-hint"><a href="?do=main">回清單</a></p>'; echo '<p class="empty-hint"><a href="?do=main">回清單</a></p>';
return; return;
} }
if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$row['current_season'] = max(1, (int)($_POST['current_season'] ?? 1)); $row['current_season'] = max(1, (int)($_POST['current_season'] ?? 1));
$row['current_episode'] = max(0, (int)($_POST['current_episode'] ?? 0)); $row['current_episode'] = max(0, (int)($_POST['current_episode'] ?? 0));
$row['current_position'] = trim($_POST['current_position'] ?? ''); $row['current_position'] = trim($_POST['current_position'] ?? '');
$row['progress_note'] = trim($_POST['progress_note'] ?? ''); $row['progress_note'] = trim($_POST['progress_note'] ?? '');
$row['status'] = $_POST['status'] ?? $row['status']; $row['status'] = $_POST['status'] ?? $row['status'];
$Drama->save($row); $Drama->save($row);
to("?do=detail&id=" . (int)$row['id']); to("?do=detail&id=" . (int)$row['id']);
exit; exit;
} }
?> ?>
<section class="vedio-intro"> <section class="vedio-intro">
<h2 class="section-title">更新進度</h2> <h2 class="section-title">更新進度</h2>
<p>記下你停在哪一季、哪一集、哪一秒——以及當時螢幕上正在發生什麼,之後才找得回那個心跳瞬間。</p> <p>記下你停在哪一季、哪一集、哪一秒——以及當時螢幕上正在發生什麼,之後才找得回那個心跳瞬間。</p>
</section> </section>
<form class="progress-form" method="post" action="?do=progress&amp;id=<?= (int)$row['id'] ?>"> <form class="progress-form" method="post" action="?do=progress&amp;id=<?= (int)$row['id'] ?>">
<h3 class="drama-card__title"><?= htmlspecialchars($row['title'], ENT_QUOTES, 'UTF-8') ?></h3> <h3 class="drama-card__title"><?= htmlspecialchars($row['title'], ENT_QUOTES, 'UTF-8') ?></h3>
<label for="current_season">目前看到第幾季</label> <label for="current_season">目前看到第幾季</label>
<input type="number" id="current_season" name="current_season" min="1" <input type="number" id="current_season" name="current_season" min="1"
value="<?= (int)$row['current_season'] ?>"> value="<?= (int)$row['current_season'] ?>">
<label for="current_episode">目前看到第幾集</label> <label for="current_episode">目前看到第幾集</label>
<input type="number" id="current_episode" name="current_episode" min="0" <input type="number" id="current_episode" name="current_episode" min="0"
value="<?= (int)$row['current_episode'] ?>"> value="<?= (int)$row['current_episode'] ?>">
<label for="current_position">這一集看到哪裡</label> <label for="current_position">這一集看到哪裡</label>
<input type="text" id="current_position" name="current_position" <input type="text" id="current_position" name="current_position"
value="<?= htmlspecialchars($row['current_position'] ?? '', ENT_QUOTES, 'UTF-8') ?>" value="<?= htmlspecialchars($row['current_position'] ?? '', ENT_QUOTES, 'UTF-8') ?>"
placeholder="例如 35:20、剩下 10 分鐘、片尾前"> placeholder="例如 35:20、剩下 10 分鐘、片尾前">
<label for="progress_note">進度情節備註</label> <label for="progress_note">進度情節備註</label>
<input type="text" id="progress_note" name="progress_note" <input type="text" id="progress_note" name="progress_note"
value="<?= htmlspecialchars($row['progress_note'] ?? '', ENT_QUOTES, 'UTF-8') ?>" value="<?= htmlspecialchars($row['progress_note'] ?? '', ENT_QUOTES, 'UTF-8') ?>"
placeholder="例如:女主角剛看到手機裡的影片"> placeholder="例如:女主角剛看到手機裡的影片">
<label for="status">狀態</label> <label for="status">狀態</label>
<select id="status" name="status"> <select id="status" name="status">
<?php <?php
$statuses = [ $statuses = [
'want' => '想看', 'want' => '想看',
'watching' => '追劇中', 'watching' => '追劇中',
'done' => '已追完', 'done' => '已追完',
'paused' => '暫停', 'paused' => '暫停',
'dropped' => '棄劇', 'dropped' => '棄劇',
]; ];
foreach ($statuses as $key => $label): foreach ($statuses as $key => $label):
?> ?>
<option value="<?= $key ?>"<?= $row['status'] === $key ? ' selected' : '' ?>><?= $label ?></option> <option value="<?= $key ?>"<?= $row['status'] === $key ? ' selected' : '' ?>><?= $label ?></option>
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
<div class="form-actions"> <div class="form-actions">
<button type="submit" class="is-primary">儲存進度</button> <button type="submit" class="is-primary">儲存進度</button>
<a href="?do=detail&amp;id=<?= (int)$row['id'] ?>">取消</a> <a href="?do=detail&amp;id=<?= (int)$row['id'] ?>">取消</a>
</div> </div>
</form> </form>
@@ -1,54 +1,54 @@
<?php include_once "./api/db.php"; ?> <?php include_once "./api/db.php"; ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="zh-TW"> <html lang="zh-TW">
<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">
<link rel="stylesheet" href="css/vedio-list.css"> <link rel="stylesheet" href="css/vedio-list.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
</head> </head>
<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>
</div> </div>
</header> </header>
<nav class="portal-nav" aria-label="水晶男孩推廣部導覽" data-portal-nav></nav> <nav class="portal-nav" aria-label="水晶男孩推廣部導覽" data-portal-nav></nav>
<canvas id="particles"></canvas> <canvas id="particles"></canvas>
<div class="container"> <div class="container">
<?php <?php
$do = $_GET['do'] ?? 'main'; $do = $_GET['do'] ?? 'main';
$file = "front/$do.php"; $file = "front/$do.php";
if (file_exists($file)) { if (file_exists($file)) {
include $file; include $file;
} else { } else {
include "front/main.php"; include "front/main.php";
} }
?> ?>
</div> </div>
<footer> <footer>
<p>SECHSKIES Fanpage &copy; 2026 - 黃色氣球永遠飄揚</p> <p>SECHSKIES Fanpage &copy; 2026 - 黃色氣球永遠飄揚</p>
<a href="https://jekkinoopy.github.io/Portfolio/" class="studio-link" target="_blank" rel="noopener noreferrer"> <a href="https://jekkinoopy.github.io/Portfolio/" class="studio-link" target="_blank" rel="noopener noreferrer">
Design by <span class="mark_b">Jekkinoopy Studio</span> Design by <span class="mark_b">Jekkinoopy Studio</span>
</a> </a>
<p class="vedio-admin-link"><a href="admin.php">管理後台</a></p> <p class="vedio-admin-link"><a href="admin.php">管理後台</a></p>
</footer> </footer>
<script src="../../assets/js/particles.js"></script> <script src="../../assets/js/particles.js"></script>
<script src="../../assets/js/portal-nav.js"></script> <script src="../../assets/js/portal-nav.js"></script>
</body> </body>
</html> </html>
@@ -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 槽原專案目前未被修改。