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

This commit is contained in:
2026-08-13 16:05:26 +08:00
parent 22e0e89f8e
commit ce6f946c94
73 changed files with 4339 additions and 4012 deletions
+10 -8
View File
@@ -39,13 +39,14 @@
- **站主索引**(自用):`extra/site-guide.html``data-portal-nav-all-open`,不對外)
- **內頁 header class**`inner-page-header`(禁改回舊名)
### variety/ 頁面
### vedio/ 頁面(原 variety/,已改名)
| 檔案 | 頁面標題 | 說明 |
|------|----------|------|
| `variety/bangxiao.html` | 世紀級寶藏 / 爆笑養老院 | 甜蜜負擔計量器;六人卡片選取 + jQuery 計算;CTA → `totoga2/ebook.html` |
| `variety/variety-shows.html` | 全員發瘋 | 綜藝節目存檔表;tabs + 搜尋;已對齊 albums.html 扁平結構 |
| `variety/NJTW/NJTW5.html` | 新西遊記5 | 各自暴走專頁 |
| `vedio/bangxiao.html` | 世紀級寶藏 / 爆笑養老院 | 甜蜜負擔計量器;六人卡片選取 + jQuery 計算;CTA → `totoga2/ebook.html` |
| `vedio/variety-shows.html` | 全員發瘋 | 綜藝節目存檔表;tabs + 搜尋;已對齊 albums.html 扁平結構 |
| `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 裡的巢狀項目。
2. **`COMING_SOON_PAGES` 只放檔名**(不含路徑)。
`normalizePageKey` 只取 href 最後一段,所以 `variety/bangxiao.html` → 比對 `bangxiao.html`
`normalizePageKey` 只取 href 最後一段,所以 `vedio/bangxiao.html` → 比對 `bangxiao.html`
3. **直接連結不需要 subgroup**
子選單內只要 `<li><a>` 就夠;`portal-nav__subgroup` 只在該項目還有巢狀子項目時才用(例如「傳奇的轉折」有時間軸、電子書等四項)。
@@ -72,7 +73,7 @@
<!-- 每次對話結束後更新這個區塊 -->
**最後更新:** 2026-08-04
**最後更新:** 2026-08-13
**進行中:**
-
@@ -89,13 +90,14 @@
**已完成:**
- 建立 AGENTS.md 跨裝置上下文檔案
- 新增 `variety/bangxiao.html`(世紀級寶藏 / 爆笑養老院,甜蜜負擔計量器)
- `variety/variety-shows.html` 重構:移除多層包裝,對齊 albums.html 扁平結構
- 新增 `vedio/bangxiao.html`(世紀級寶藏 / 爆笑養老院,甜蜜負擔計量器)
- `vedio/variety-shows.html` 重構:移除多層包裝,對齊 albums.html 扁平結構
- portal-nav 新增世紀級寶藏,正確放入淪陷瞬間子選單
- `extra/site-guide.html` 同步更新:全員發瘋 → 世紀級寶藏
- 新增 `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` 已同步移除相關項目
- `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`,不對外)
- **內頁 header class**`inner-page-header`(禁改回舊名)
### variety/ 頁面
### vedio/ 頁面(原 variety/,已改名)
| 檔案 | 頁面標題 | 說明 |
|------|----------|------|
| `variety/bangxiao.html` | 世紀級寶藏 / 爆笑養老院 | 甜蜜負擔計量器;六人卡片選取 + jQuery 計算;CTA → `totoga2/ebook.html` |
| `variety/variety-shows.html` | 全員發瘋 | 綜藝節目存檔表;tabs + 搜尋;已對齊 albums.html 扁平結構 |
| `variety/NJTW/NJTW5.html` | 新西遊記5 | 各自暴走專頁 |
| `vedio/bangxiao.html` | 世紀級寶藏 / 爆笑養老院 | 甜蜜負擔計量器;六人卡片選取 + jQuery 計算;CTA → `totoga2/ebook.html` |
| `vedio/variety-shows.html` | 全員發瘋 | 綜藝節目存檔表;tabs + 搜尋;已對齊 albums.html 扁平結構 |
| `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 裡的巢狀項目。
2. **`COMING_SOON_PAGES` 只放檔名**(不含路徑)。
`normalizePageKey` 只取 href 最後一段,所以 `variety/bangxiao.html` → 比對 `bangxiao.html`
`normalizePageKey` 只取 href 最後一段,所以 `vedio/bangxiao.html` → 比對 `bangxiao.html`
3. **直接連結不需要 subgroup**
子選單內只要 `<li><a>` 就夠;`portal-nav__subgroup` 只在該項目還有巢狀子項目時才用(例如「傳奇的轉折」有時間軸、電子書等四項)。
@@ -72,7 +73,7 @@
<!-- 每次對話結束後更新這個區塊 -->
**最後更新:** 2026-06-17
**最後更新:** 2026-08-13
**進行中:**
-
@@ -82,10 +83,11 @@
**已完成:**
- 建立 CLAUDE.md 跨裝置上下文檔案
- 新增 `variety/bangxiao.html`(世紀級寶藏 / 爆笑養老院,甜蜜負擔計量器)
- `variety/variety-shows.html` 重構:移除多層包裝,對齊 albums.html 扁平結構
- 新增 `vedio/bangxiao.html`(世紀級寶藏 / 爆笑養老院,甜蜜負擔計量器)
- `vedio/variety-shows.html` 重構:移除多層包裝,對齊 albums.html 扁平結構
- portal-nav 新增世紀級寶藏,正確放入淪陷瞬間子選單
- `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;
}
/* variety/NJTW/NJTW5.html — 樣式改為同資料夾 njtw5.css,避免與全站 style 雙重定義 */
/* vedio/NJTW/NJTW5.html — 樣式改為同資料夾 njtw5.css,避免與全站 style 雙重定義 */
/* Chronicle (webcomic-like reader) */
.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: 126 KiB

+1 -1
View File
@@ -259,7 +259,7 @@
</li>
</ul>
</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}concert/concert.html"><span class="portal-nav__label">現場的震撼</span></a>
<ul class="portal-submenu">
+3 -3
View File
@@ -20,7 +20,7 @@ alwaysApply: true
1. **未經站主逐句同意**:不得從 `COMING_SOON_PAGES` **刪**檔名、不得擴充 `INDEX_NAV_OPEN_HASHES`、不得在 `buildPortalNavInnerHTML` **新增**可點項、不得把籌備中改成真 `href`、不得刪站主已同意列入頂欄的項。
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. **「刪除/拿掉/不要了」**:若未寫明是**單一檔案**、**頂欄某一列**、**整段子選單**還是**整個資料夾**,**一律先問站主**,禁止自行擴大刪除範圍。
5. **不確定**是否開放、連哪、是否例外:**先問站主再改**,不要猜。
6. 站主說刪 **`某頁#錨點`**:見下方 **§8 頁面錨點刪除**(先改 DOM,再修死鏈;禁止只動導覽)。
@@ -32,7 +32,7 @@ alwaysApply: true
**`buildPortalNavInnerHTML`**`assets/js/portal-nav.js`)裡維護連結:
- 依資訊架構放在對應的 **`<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` ``。
新增或調整完後,所有帶 **`data-portal-nav`** 的 `<nav class="portal-nav">` 都會在載入腳本時被注入同一份 HTML。
@@ -93,7 +93,7 @@ alwaysApply: true
<script src="assets/js/portal-nav.js"></script>
```
內頁(例如 `extra/``fallen/``member/``yellow-note/``variety/` 下)請用相對站根深度,例如:
內頁(例如 `extra/``fallen/``member/``yellow-note/``vedio/` 下)請用相對站根深度,例如:
```html
<script src="../assets/js/portal-nav.js"></script>
+3 -3
View File
@@ -6,8 +6,8 @@
|----------------|------------------|
| 淪陷瞬間 · 初心與軌跡 | `index.html` |
| 傳奇的轉折 | `fallen/totoga2.html` |
| 全員發瘋 · 隊長帶弟弟 | `variety/variety.html`(錨點 `#variety` |
| 全員發瘋 · 各自暴走 · 新西遊記 | `variety/NJTW/NJTW5.html` |
| 全員發瘋 · 隊長帶弟弟 | `vedio/variety.html`(錨點 `#variety` |
| 全員發瘋 · 各自暴走 · 新西遊記 | `vedio/NJTW/NJTW5.html` |
| 完整的六顆 | `member/member.html` |
| 現場的震撼 | `concert/concert.html` |
| 黃色留聲機 | `albums/albums.html` |
@@ -21,7 +21,7 @@
- `member/` — 成員介紹(六顆水晶)
- `concert/``albums/` — 與選單同名主題
- `yellow-note/` — Yellow Noteminister + map
- `variety/` — 綜藝與 NJTW 子頁
- `vedio/` — 綜藝與 NJTW 子頁
- `extra/` — 未掛主欄之草稿、複本、全站索引 `site-guide.html`
- `assets/` — 共用靜態資源
- `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>
</li>
<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>
<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">variety/ 其他相關頁(不在頂欄)</span>
<span class="sg-node sg-node--extra">vedio/ 其他相關頁(不在頂欄)</span>
<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="../variety/variety-shows.html">節目存檔表</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="../vedio/variety-shows.html">節目存檔表</a></span></li>
<li data-site-guide-depth="4">
<span class="sg-node sg-node--extra sg-node--nolink">各自暴走</span>
<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>
</li>
</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="../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="../variety/vedio-list.html">variety/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"><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 後台,非靜態站主線">vedio/vedio-list/(追劇清單後台練習,非本站主線)</span></li>
</ul>
</li>
<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>

Before

Width:  |  Height:  |  Size: 460 KiB

After

Width:  |  Height:  |  Size: 460 KiB

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 134 KiB

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

Before

Width:  |  Height:  |  Size: 264 KiB

After

Width:  |  Height:  |  Size: 264 KiB

Before

Width:  |  Height:  |  Size: 142 KiB

After

Width:  |  Height:  |  Size: 142 KiB

Before

Width:  |  Height:  |  Size: 276 KiB

After

Width:  |  Height:  |  Size: 276 KiB

Before

Width:  |  Height:  |  Size: 187 KiB

After

Width:  |  Height:  |  Size: 187 KiB

Before

Width:  |  Height:  |  Size: 143 KiB

After

Width:  |  Height:  |  Size: 143 KiB

Before

Width:  |  Height:  |  Size: 954 KiB

After

Width:  |  Height:  |  Size: 954 KiB

Before

Width:  |  Height:  |  Size: 173 KiB

After

Width:  |  Height:  |  Size: 173 KiB

Before

Width:  |  Height:  |  Size: 220 KiB

After

Width:  |  Height:  |  Size: 220 KiB

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 154 KiB

Before

Width:  |  Height:  |  Size: 147 KiB

After

Width:  |  Height:  |  Size: 147 KiB

+444 -444
View File
@@ -1,445 +1,445 @@
<!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="../../assets/css/tablet.css">
<link rel="stylesheet" href="njtw5.css">
<style>
/* 本頁專用:.nickname 只寫在 HTML(依你要求,未再放到 njtw5.css) */
body.njtw5-page .member-card .nickname {
display: block;
margin-top: 0.65rem;
padding: 0.15rem 0;
font-size: max(14px, 0.92rem);
font-weight: 700;
color: #3d240c;
line-height: 1.5;
word-break: break-word;
overflow-wrap: anywhere;
opacity: 1;
visibility: visible;
position: relative;
z-index: 1;
}
.main-header {
text-align: center;
margin-bottom: 50px;
width: 100%;
}
</style>
</head>
<body class="njtw5-page">
<header class="main-header">
<h1>新西遊記 5:與鬼神同行</h1>
<p class="intro">
這是一場驚心動魄(搞笑)的鬼怪盛宴!<br>
六位成員化身世界各地的經典鬼怪,展開一場前往香港與北海道的爆笑冒險。
</p>
</header>
<div class="container">
<h2 class="section-title">鬼神介紹</h2>
<!-- tr/td 必須放在 <table> 裡;外層用 div 接在 .container 的 grid 下,佔滿整列 -->
<div class="njtw-member-table-wrap">
<table class="njtw-member-table">
<tbody>
<tr>
<td>
<div class="njtw-td-fill">
<div class="member-card m1 parchment-card">
<div class="parchment-inner">
<!-- <div class="parchment-logo" aria-hidden="true">新西遊記<span class="parchment-logo-num">5</span></div> -->
<div class="parchment-headline">
<p class="parchment-name"></p>
<p class="parchment-role">姜鎬臉男 姜鎬童</p>
<p class="parchment-byline">吃得津津有味的鬼怪</p>
</div>
<div class="parchment-figure">
<div class="member-image"></div>
</div>
<div class="parchment-stats">
<div class="parchment-stat-row">
<span class="parchment-stat-label">視覺</span>
<div class="parchment-stat-balls">
<!-- 將每顆 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="" />
</div>
</div>
<div class="parchment-stat-row">
<span class="parchment-stat-label">注目度</span>
<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="" />
</div>
</div>
<div class="parchment-stat-row">
<span class="parchment-stat-label">可愛</span>
<div class="parchment-stat-balls">
<img src="dragon-ball.png" width="28" height="28" alt="" />
</div>
</div>
</div>
<p class="parchment-tags">#吃貨精靈 #低聲細語</p>
</div>
</div>
</div>
</td>
<td>
<div class="njtw-td-fill">
<div class="member-card m2 parchment-card">
<div class="parchment-inner">
<div class="parchment-headline">
<p class="parchment-name"></p>
<p class="parchment-role">鬼娃恰吉 李壽根</p>
<p class="parchment-byline">不搞笑會死的鬼怪</p>
</div>
<div class="parchment-figure">
<div class="member-image"></div>
</div>
<div class="parchment-stats">
<div class="parchment-stat-row">
<span class="parchment-stat-label">娃娃美</span>
<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="" />
</div>
</div>
<div class="parchment-stat-row">
<span class="parchment-stat-label">語言能力</span>
<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="" />
</div>
</div>
<div class="parchment-stat-row">
<span class="parchment-stat-label">體能</span>
<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="" />
</div>
</div>
</div>
<p class="parchment-tags">#最強短腿 #瘋狂跑動</p>
</div>
</div>
</div>
</td>
<td>
<div class="njtw-td-fill">
<div class="member-card m3 parchment-card">
<div class="parchment-inner">
<div class="parchment-headline">
<p class="parchment-name"></p>
<p class="parchment-role">陰間使者 殷志源</p>
<p class="parchment-byline">戴上黑帽子 神志源回來了!</p>
</div>
<div class="parchment-figure">
<div class="member-image"></div>
</div>
<div class="parchment-stats">
<div class="parchment-stat-row">
<span class="parchment-stat-label">化妝消化力</span>
<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="" />
</div>
</div>
<div class="parchment-stat-row">
<span class="parchment-stat-label">黑眼圈</span>
<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 class="parchment-stat-row">
<span class="parchment-stat-label">暴走</span>
<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>
<p class="parchment-tags">#瘋子使者 #最帥鬼怪</p>
</div>
</div>
</div>
</td>
<td class="njtw-layout-slot shinmyohan OB">
<div class="njtw-td-fill">
<div class="njtw-slot-stack njtw-team-panel ob-panel">
<div class="njtw-slot-visual">
<div class="njtw-team-title-band">
<h2 class="njtw-team-hero-title" id="njtw-ob-team-title">OB 隊:老練的瘋子們</h2>
</div>
<div class="njtw-slot-faces">
<img src="1.png" alt="" />
<img src="2.png" alt="" />
<img src="3.png" alt="" />
</div>
</div>
<div class="team-info">
<p>看他們玩遊戲不是在看技術,是在看如何「鑽漏洞」和「互相背叛」。他們不用說話,光是一個眼神就能知道對方在想什麼(通常是壞主意)。</p>
<p><strong>圈粉亮點:</strong>鎬童的食量、壽根的即興才藝、志源那種「初丁」卻天才的直覺。</p>
<div class="parchment-stats">
<div class="parchment-stat-row">
<span class="parchment-stat-label">老練度</span>
<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="" />
</div>
</div>
<div class="parchment-stat-row">
<span class="parchment-stat-label">體力值</span>
<div class="parchment-stat-balls" aria-label="1">
<img src="dragon-ball.png" width="28" height="28" alt="" />
</div>
</div>
</div>
<p class="njtw-team-catchphrase">口頭禪:喔嗚!吃吧!</p>
</div>
</div>
</div>
</td>
</tr>
<tr>
<td class="njtw-layout-slot shinmyohan YB">
<div class="njtw-td-fill">
<div class="njtw-slot-stack njtw-team-panel yb-panel">
<div class="njtw-slot-visual">
<div class="njtw-team-title-band">
<h2 class="njtw-team-hero-title" id="njtw-yb-team-title">YB 隊:新瘋子隊</h2>
</div>
<div class="njtw-slot-faces">
<img src="4.png" alt="" />
<img src="5.png" alt="" />
<img src="6.png" alt="" class="Shinmyohan" />
</div>
</div>
<div class="team-info">
<p>一開始以為是來幫哥哥們打雜的,結果卻是一個比一個還瘋的「瘋子新秀」。他們擁有體力優勢,卻常常在簡單的常識題中全軍覆沒。</p>
<p><strong>圈粉亮點:</strong>安宰賢的紙片人運動神經、宋旻浩的藝術靈魂與神手指、P.O 的超大聲反應。</p>
<div class="parchment-stats">
<div class="parchment-stat-row">
<span class="parchment-stat-label">老練度</span>
<div class="parchment-stat-balls" aria-label="1">
<img src="dragon-ball.png" width="28" height="28" alt="" />
</div>
</div>
<div class="parchment-stat-row">
<span class="parchment-stat-label">體力值</span>
<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="" />
</div>
</div>
</div>
<p class="njtw-team-catchphrase">口頭禪:哥!對不起!</p>
</div>
</div>
</div>
</td>
<td>
<div class="njtw-td-fill">
<div class="member-card m4 parchment-card">
<div class="parchment-inner">
<div class="parchment-headline">
<p class="parchment-name"></p>
<p class="parchment-role">帥氣殭屍 安宰賢</p>
<p class="parchment-byline">美貌&amp;藝能兼具 八面玲瓏的帥氣殭屍</p>
</div>
<div class="parchment-figure">
<div class="member-image"></div>
</div>
<div class="parchment-stats">
<div class="parchment-stat-row">
<span class="parchment-stat-label">帥氣度</span>
<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="" />
</div>
</div>
<div class="parchment-stat-row">
<span class="parchment-stat-label">瘋癲</span>
<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 class="parchment-stat-row">
<span class="parchment-stat-label">臉紅紅</span>
<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>
<p class="parchment-tags">#美男殭屍 #運動白痴</p>
</div>
</div>
</div>
</td>
<td>
<div class="njtw-td-fill">
<div class="member-card m5 parchment-card">
<div class="parchment-inner">
<div class="parchment-headline">
<p class="parchment-name"></p>
<p class="parchment-role">現代鬼 宋旻浩</p>
<p class="parchment-byline">獨特的文藝青年 現代鬼怪</p>
</div>
<div class="parchment-figure">
<div class="member-image"></div>
</div>
<div class="parchment-stats">
<div class="parchment-stat-row">
<span class="parchment-stat-label">髮型</span>
<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="" />
</div>
</div>
<div class="parchment-stat-row">
<span class="parchment-stat-label">清純美</span>
<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="" />
</div>
</div>
<div class="parchment-stat-row">
<span class="parchment-stat-label">時髦度</span>
<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="" />
</div>
</div>
</div>
<p class="parchment-tags">#長髮宋 #綜藝神降臨</p>
</div>
</div>
</div>
</td>
<td>
<div class="njtw-td-fill">
<div class="member-card m6 parchment-card">
<div class="parchment-inner">
<div class="parchment-headline">
<p class="parchment-name"></p>
<p class="parchment-role">德古拉 P.O</p>
<p class="parchment-byline">新血降臨!華麗誤答轟炸機!</p>
</div>
<div class="parchment-figure">
<div class="member-image"></div>
</div>
<div class="parchment-stats">
<div class="parchment-stat-row">
<span class="parchment-stat-label">腦力</span>
<div class="parchment-stat-balls">
<img src="dragon-ball.png" width="28" height="28" alt="" />
</div>
</div>
<div class="parchment-stat-row">
<span class="parchment-stat-label">披風+領子</span>
<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="" />
</div>
</div>
<div class="parchment-stat-row">
<span class="parchment-stat-label">藝能感</span>
<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>
<p class="parchment-tags">#新弟子 #德古拉</p>
</div>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- 影片區塊:將 iframe 的 src 換成 https://www.youtube.com/embed/你的影片ID -->
<div class="video-container">
<h2 class="video-title">經典片段回顧</h2>
<div class="video-wrapper">
<iframe class="video-embed" src="https://www.youtube.com/embed/3zSeN9Ss3BI" title="新西遊記 5 精華片段"
loading="lazy"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div>
</div>
<div class="video-container">
<h2 class="video-title">S5 EP01・180930(神叨字幕組)</h2>
<div class="video-wrapper">
<iframe class="video-embed" title="新西遊記5 EP01 神叨字幕組"
src="//player.bilibili.com/player.html?isOutside=true&bvid=BV12t411U7Ni&p=1" scrolling="no"
border="0" frameborder="no" framespacing="0" allowfullscreen="true"></iframe>
</div>
</div>
</body>
<!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="../../assets/css/tablet.css">
<link rel="stylesheet" href="njtw5.css">
<style>
/* 本頁專用:.nickname 只寫在 HTML(依你要求,未再放到 njtw5.css) */
body.njtw5-page .member-card .nickname {
display: block;
margin-top: 0.65rem;
padding: 0.15rem 0;
font-size: max(14px, 0.92rem);
font-weight: 700;
color: #3d240c;
line-height: 1.5;
word-break: break-word;
overflow-wrap: anywhere;
opacity: 1;
visibility: visible;
position: relative;
z-index: 1;
}
.main-header {
text-align: center;
margin-bottom: 50px;
width: 100%;
}
</style>
</head>
<body class="njtw5-page">
<header class="main-header">
<h1>新西遊記 5:與鬼神同行</h1>
<p class="intro">
這是一場驚心動魄(搞笑)的鬼怪盛宴!<br>
六位成員化身世界各地的經典鬼怪,展開一場前往香港與北海道的爆笑冒險。
</p>
</header>
<div class="container">
<h2 class="section-title">鬼神介紹</h2>
<!-- tr/td 必須放在 <table> 裡;外層用 div 接在 .container 的 grid 下,佔滿整列 -->
<div class="njtw-member-table-wrap">
<table class="njtw-member-table">
<tbody>
<tr>
<td>
<div class="njtw-td-fill">
<div class="member-card m1 parchment-card">
<div class="parchment-inner">
<!-- <div class="parchment-logo" aria-hidden="true">新西遊記<span class="parchment-logo-num">5</span></div> -->
<div class="parchment-headline">
<p class="parchment-name"></p>
<p class="parchment-role">姜鎬臉男 姜鎬童</p>
<p class="parchment-byline">吃得津津有味的鬼怪</p>
</div>
<div class="parchment-figure">
<div class="member-image"></div>
</div>
<div class="parchment-stats">
<div class="parchment-stat-row">
<span class="parchment-stat-label">視覺</span>
<div class="parchment-stat-balls">
<!-- 將每顆 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="" />
</div>
</div>
<div class="parchment-stat-row">
<span class="parchment-stat-label">注目度</span>
<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="" />
</div>
</div>
<div class="parchment-stat-row">
<span class="parchment-stat-label">可愛</span>
<div class="parchment-stat-balls">
<img src="dragon-ball.png" width="28" height="28" alt="" />
</div>
</div>
</div>
<p class="parchment-tags">#吃貨精靈 #低聲細語</p>
</div>
</div>
</div>
</td>
<td>
<div class="njtw-td-fill">
<div class="member-card m2 parchment-card">
<div class="parchment-inner">
<div class="parchment-headline">
<p class="parchment-name"></p>
<p class="parchment-role">鬼娃恰吉 李壽根</p>
<p class="parchment-byline">不搞笑會死的鬼怪</p>
</div>
<div class="parchment-figure">
<div class="member-image"></div>
</div>
<div class="parchment-stats">
<div class="parchment-stat-row">
<span class="parchment-stat-label">娃娃美</span>
<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="" />
</div>
</div>
<div class="parchment-stat-row">
<span class="parchment-stat-label">語言能力</span>
<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="" />
</div>
</div>
<div class="parchment-stat-row">
<span class="parchment-stat-label">體能</span>
<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="" />
</div>
</div>
</div>
<p class="parchment-tags">#最強短腿 #瘋狂跑動</p>
</div>
</div>
</div>
</td>
<td>
<div class="njtw-td-fill">
<div class="member-card m3 parchment-card">
<div class="parchment-inner">
<div class="parchment-headline">
<p class="parchment-name"></p>
<p class="parchment-role">陰間使者 殷志源</p>
<p class="parchment-byline">戴上黑帽子 神志源回來了!</p>
</div>
<div class="parchment-figure">
<div class="member-image"></div>
</div>
<div class="parchment-stats">
<div class="parchment-stat-row">
<span class="parchment-stat-label">化妝消化力</span>
<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="" />
</div>
</div>
<div class="parchment-stat-row">
<span class="parchment-stat-label">黑眼圈</span>
<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 class="parchment-stat-row">
<span class="parchment-stat-label">暴走</span>
<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>
<p class="parchment-tags">#瘋子使者 #最帥鬼怪</p>
</div>
</div>
</div>
</td>
<td class="njtw-layout-slot shinmyohan OB">
<div class="njtw-td-fill">
<div class="njtw-slot-stack njtw-team-panel ob-panel">
<div class="njtw-slot-visual">
<div class="njtw-team-title-band">
<h2 class="njtw-team-hero-title" id="njtw-ob-team-title">OB 隊:老練的瘋子們</h2>
</div>
<div class="njtw-slot-faces">
<img src="1.png" alt="" />
<img src="2.png" alt="" />
<img src="3.png" alt="" />
</div>
</div>
<div class="team-info">
<p>看他們玩遊戲不是在看技術,是在看如何「鑽漏洞」和「互相背叛」。他們不用說話,光是一個眼神就能知道對方在想什麼(通常是壞主意)。</p>
<p><strong>圈粉亮點:</strong>鎬童的食量、壽根的即興才藝、志源那種「初丁」卻天才的直覺。</p>
<div class="parchment-stats">
<div class="parchment-stat-row">
<span class="parchment-stat-label">老練度</span>
<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="" />
</div>
</div>
<div class="parchment-stat-row">
<span class="parchment-stat-label">體力值</span>
<div class="parchment-stat-balls" aria-label="1">
<img src="dragon-ball.png" width="28" height="28" alt="" />
</div>
</div>
</div>
<p class="njtw-team-catchphrase">口頭禪:喔嗚!吃吧!</p>
</div>
</div>
</div>
</td>
</tr>
<tr>
<td class="njtw-layout-slot shinmyohan YB">
<div class="njtw-td-fill">
<div class="njtw-slot-stack njtw-team-panel yb-panel">
<div class="njtw-slot-visual">
<div class="njtw-team-title-band">
<h2 class="njtw-team-hero-title" id="njtw-yb-team-title">YB 隊:新瘋子隊</h2>
</div>
<div class="njtw-slot-faces">
<img src="4.png" alt="" />
<img src="5.png" alt="" />
<img src="6.png" alt="" class="Shinmyohan" />
</div>
</div>
<div class="team-info">
<p>一開始以為是來幫哥哥們打雜的,結果卻是一個比一個還瘋的「瘋子新秀」。他們擁有體力優勢,卻常常在簡單的常識題中全軍覆沒。</p>
<p><strong>圈粉亮點:</strong>安宰賢的紙片人運動神經、宋旻浩的藝術靈魂與神手指、P.O 的超大聲反應。</p>
<div class="parchment-stats">
<div class="parchment-stat-row">
<span class="parchment-stat-label">老練度</span>
<div class="parchment-stat-balls" aria-label="1">
<img src="dragon-ball.png" width="28" height="28" alt="" />
</div>
</div>
<div class="parchment-stat-row">
<span class="parchment-stat-label">體力值</span>
<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="" />
</div>
</div>
</div>
<p class="njtw-team-catchphrase">口頭禪:哥!對不起!</p>
</div>
</div>
</div>
</td>
<td>
<div class="njtw-td-fill">
<div class="member-card m4 parchment-card">
<div class="parchment-inner">
<div class="parchment-headline">
<p class="parchment-name"></p>
<p class="parchment-role">帥氣殭屍 安宰賢</p>
<p class="parchment-byline">美貌&amp;藝能兼具 八面玲瓏的帥氣殭屍</p>
</div>
<div class="parchment-figure">
<div class="member-image"></div>
</div>
<div class="parchment-stats">
<div class="parchment-stat-row">
<span class="parchment-stat-label">帥氣度</span>
<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="" />
</div>
</div>
<div class="parchment-stat-row">
<span class="parchment-stat-label">瘋癲</span>
<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 class="parchment-stat-row">
<span class="parchment-stat-label">臉紅紅</span>
<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>
<p class="parchment-tags">#美男殭屍 #運動白痴</p>
</div>
</div>
</div>
</td>
<td>
<div class="njtw-td-fill">
<div class="member-card m5 parchment-card">
<div class="parchment-inner">
<div class="parchment-headline">
<p class="parchment-name"></p>
<p class="parchment-role">現代鬼 宋旻浩</p>
<p class="parchment-byline">獨特的文藝青年 現代鬼怪</p>
</div>
<div class="parchment-figure">
<div class="member-image"></div>
</div>
<div class="parchment-stats">
<div class="parchment-stat-row">
<span class="parchment-stat-label">髮型</span>
<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="" />
</div>
</div>
<div class="parchment-stat-row">
<span class="parchment-stat-label">清純美</span>
<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="" />
</div>
</div>
<div class="parchment-stat-row">
<span class="parchment-stat-label">時髦度</span>
<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="" />
</div>
</div>
</div>
<p class="parchment-tags">#長髮宋 #綜藝神降臨</p>
</div>
</div>
</div>
</td>
<td>
<div class="njtw-td-fill">
<div class="member-card m6 parchment-card">
<div class="parchment-inner">
<div class="parchment-headline">
<p class="parchment-name"></p>
<p class="parchment-role">德古拉 P.O</p>
<p class="parchment-byline">新血降臨!華麗誤答轟炸機!</p>
</div>
<div class="parchment-figure">
<div class="member-image"></div>
</div>
<div class="parchment-stats">
<div class="parchment-stat-row">
<span class="parchment-stat-label">腦力</span>
<div class="parchment-stat-balls">
<img src="dragon-ball.png" width="28" height="28" alt="" />
</div>
</div>
<div class="parchment-stat-row">
<span class="parchment-stat-label">披風+領子</span>
<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="" />
</div>
</div>
<div class="parchment-stat-row">
<span class="parchment-stat-label">藝能感</span>
<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>
<p class="parchment-tags">#新弟子 #德古拉</p>
</div>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- 影片區塊:將 iframe 的 src 換成 https://www.youtube.com/embed/你的影片ID -->
<div class="video-container">
<h2 class="video-title">經典片段回顧</h2>
<div class="video-wrapper">
<iframe class="video-embed" src="https://www.youtube.com/embed/3zSeN9Ss3BI" title="新西遊記 5 精華片段"
loading="lazy"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div>
</div>
<div class="video-container">
<h2 class="video-title">S5 EP01・180930(神叨字幕組)</h2>
<div class="video-wrapper">
<iframe class="video-embed" title="新西遊記5 EP01 神叨字幕組"
src="//player.bilibili.com/player.html?isOutside=true&bvid=BV12t411U7Ni&p=1" scrolling="no"
border="0" frameborder="no" framespacing="0" allowfullscreen="true"></iframe>
</div>
</div>
</body>
</html>

Before

Width:  |  Height:  |  Size: 550 KiB

After

Width:  |  Height:  |  Size: 550 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 968 KiB

After

Width:  |  Height:  |  Size: 968 KiB

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"; ?>
<!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="css/vedio-list.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
</head>
<body class="vedio-admin-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">ADMIN · CRUD</span>
</div>
</div>
</header>
<div class="container admin-wrap">
<nav class="admin-nav">
<a href="index.php">回前台</a>
<a href="admin.php">後台首頁</a>
<a href="?do=drama">追劇管理</a>
<a href="?do=add">新增作品</a>
</nav>
<?php
$do = $_GET['do'] ?? 'main';
$file = "back/$do.php";
if (file_exists($file)) {
include $file;
} else {
include "back/main.php";
}
?>
</div>
</body>
</html>
<?php include_once "./api/db.php"; ?>
<!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="css/vedio-list.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
</head>
<body class="vedio-admin-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">ADMIN · CRUD</span>
</div>
</div>
</header>
<div class="container admin-wrap">
<nav class="admin-nav">
<a href="index.php">回前台</a>
<a href="admin.php">後台首頁</a>
<a href="?do=drama">追劇管理</a>
<a href="?do=add">新增作品</a>
</nav>
<?php
$do = $_GET['do'] ?? 'main';
$file = "back/$do.php";
if (file_exists($file)) {
include $file;
} else {
include "back/main.php";
}
?>
</div>
</body>
</html>
@@ -1,125 +1,125 @@
<?php
session_start();
date_default_timezone_set("Asia/Taipei");
class DB
{
protected $dsn = "mysql:host=localhost;charset=utf8;dbname=vedio_list";
protected $pdo;
protected $table;
function __construct($table)
{
$this->table = $table;
$this->pdo = new PDO($this->dsn, 'root', '');
}
function all(...$arg)
{
$sql = "SELECT * FROM `$this->table` ";
if (isset($arg[0])) {
if (is_array($arg[0])) {
$tmp = $this->a2s($arg[0]);
$sql .= " WHERE " . join(" AND ", $tmp);
} else {
$sql .= $arg[0];
}
}
if (isset($arg[1])) {
$sql .= $arg[1];
}
return $this->pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
}
function count(...$arg)
{
$sql = "SELECT count(*) FROM `$this->table`";
if (isset($arg[0])) {
if (is_array($arg[0])) {
$tmp = $this->a2s($arg[0]);
$sql .= " WHERE " . join(" AND ", $tmp);
} else {
$sql .= $arg[0];
}
}
if (isset($arg[1])) {
$sql .= $arg[1];
}
return $this->pdo->query($sql)->fetchColumn();
}
function find($arg)
{
$sql = "SELECT * FROM `$this->table` ";
if (is_array($arg)) {
$tmp = $this->a2s($arg);
$sql .= " WHERE " . join(" AND ", $tmp);
} else {
$sql .= " WHERE `id`='$arg'";
}
return $this->pdo->query($sql)->fetch(PDO::FETCH_ASSOC);
}
function save($arg)
{
if (isset($arg['id'])) {
$tmp = $this->a2s($arg);
$sql = "UPDATE `$this->table` SET " . join(" , ", $tmp);
$sql .= " WHERE `id`='{$arg['id']}'";
} else {
$keys = array_keys($arg);
$sql = "INSERT INTO `$this->table` (`" . join("`,`", $keys) . "`) VALUES('" . join("','", $arg) . "');";
}
return $this->pdo->exec($sql);
}
function del($arg)
{
$sql = "DELETE FROM `$this->table` ";
if (is_array($arg)) {
$tmp = $this->a2s($arg);
$sql .= " WHERE " . join(" AND ", $tmp);
} else {
$sql .= " WHERE `id`='$arg'";
}
return $this->pdo->exec($sql);
}
protected function a2s($array)
{
$tmp = [];
foreach ($array as $key => $val) {
$tmp[] = "`$key`='$val'";
}
return $tmp;
}
function q($sql)
{
return $this->pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
}
}
function dd($array)
{
echo "<pre>";
print_r($array);
echo "</pre>";
}
function to($url)
{
header("location:$url");
}
$Drama = new DB('dramas');
<?php
session_start();
date_default_timezone_set("Asia/Taipei");
class DB
{
protected $dsn = "mysql:host=localhost;charset=utf8;dbname=vedio_list";
protected $pdo;
protected $table;
function __construct($table)
{
$this->table = $table;
$this->pdo = new PDO($this->dsn, 'root', '');
}
function all(...$arg)
{
$sql = "SELECT * FROM `$this->table` ";
if (isset($arg[0])) {
if (is_array($arg[0])) {
$tmp = $this->a2s($arg[0]);
$sql .= " WHERE " . join(" AND ", $tmp);
} else {
$sql .= $arg[0];
}
}
if (isset($arg[1])) {
$sql .= $arg[1];
}
return $this->pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
}
function count(...$arg)
{
$sql = "SELECT count(*) FROM `$this->table`";
if (isset($arg[0])) {
if (is_array($arg[0])) {
$tmp = $this->a2s($arg[0]);
$sql .= " WHERE " . join(" AND ", $tmp);
} else {
$sql .= $arg[0];
}
}
if (isset($arg[1])) {
$sql .= $arg[1];
}
return $this->pdo->query($sql)->fetchColumn();
}
function find($arg)
{
$sql = "SELECT * FROM `$this->table` ";
if (is_array($arg)) {
$tmp = $this->a2s($arg);
$sql .= " WHERE " . join(" AND ", $tmp);
} else {
$sql .= " WHERE `id`='$arg'";
}
return $this->pdo->query($sql)->fetch(PDO::FETCH_ASSOC);
}
function save($arg)
{
if (isset($arg['id'])) {
$tmp = $this->a2s($arg);
$sql = "UPDATE `$this->table` SET " . join(" , ", $tmp);
$sql .= " WHERE `id`='{$arg['id']}'";
} else {
$keys = array_keys($arg);
$sql = "INSERT INTO `$this->table` (`" . join("`,`", $keys) . "`) VALUES('" . join("','", $arg) . "');";
}
return $this->pdo->exec($sql);
}
function del($arg)
{
$sql = "DELETE FROM `$this->table` ";
if (is_array($arg)) {
$tmp = $this->a2s($arg);
$sql .= " WHERE " . join(" AND ", $tmp);
} else {
$sql .= " WHERE `id`='$arg'";
}
return $this->pdo->exec($sql);
}
protected function a2s($array)
{
$tmp = [];
foreach ($array as $key => $val) {
$tmp[] = "`$key`='$val'";
}
return $tmp;
}
function q($sql)
{
return $this->pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
}
}
function dd($array)
{
echo "<pre>";
print_r($array);
echo "</pre>";
}
function to($url)
{
header("location:$url");
}
$Drama = new DB('dramas');
@@ -1,5 +1,5 @@
<?php
include_once "db.php";
$table = ${$_POST['table']};
$table->del($_POST['id']);
<?php
include_once "db.php";
$table = ${$_POST['table']};
$table->del($_POST['id']);
@@ -1,6 +1,6 @@
<?php
include_once "db.php";
$row = $Drama->find($_POST['id']);
$row['sh'] = ((int)$row['sh'] + 1) % 2;
$Drama->save($row);
<?php
include_once "db.php";
$row = $Drama->find($_POST['id']);
$row['sh'] = ((int)$row['sh'] + 1) % 2;
$Drama->save($row);
@@ -1,13 +1,13 @@
<?php
include_once "db.php";
$table = ${$_POST['table']};
$row1 = $table->find($_POST['ids'][0]);
$row2 = $table->find($_POST['ids'][1]);
$tmp = $row1['rank'];
$row1['rank'] = $row2['rank'];
$row2['rank'] = $tmp;
$table->save($row1);
$table->save($row2);
<?php
include_once "db.php";
$table = ${$_POST['table']};
$row1 = $table->find($_POST['ids'][0]);
$row2 = $table->find($_POST['ids'][1]);
$tmp = $row1['rank'];
$row1['rank'] = $row2['rank'];
$row2['rank'] = $tmp;
$table->save($row1);
$table->save($row2);
@@ -1,96 +1,96 @@
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$maxRank = $Drama->q("SELECT MAX(`rank`) AS m FROM `dramas`");
$nextRank = ((int)($maxRank[0]['m'] ?? 0)) + 1;
$data = [
'title' => trim($_POST['title'] ?? ''),
'type' => $_POST['type'] ?? 'drama',
'platform' => $_POST['platform'] ?? '其他',
'watch_url' => trim($_POST['watch_url'] ?? ''),
'current_season' => max(1, (int)($_POST['current_season'] ?? 1)),
'current_episode' => max(0, (int)($_POST['current_episode'] ?? 0)),
'current_position' => trim($_POST['current_position'] ?? ''),
'progress_note' => trim($_POST['progress_note'] ?? ''),
'status' => $_POST['status'] ?? 'want',
'rating' => $_POST['rating'] !== '' ? $_POST['rating'] : null,
'intro' => trim($_POST['intro'] ?? ''),
'note' => trim($_POST['note'] ?? ''),
'sh' => 1,
'rank' => $nextRank,
];
// DB::save 用字串拼接;null 改空字串避免 SQL 破掉
if ($data['rating'] === null) {
$data['rating'] = '';
}
$Drama->save($data);
to("?do=drama");
exit;
}
?>
<section>
<h2 class="section-title">新增作品</h2>
<form class="admin-form" method="post" action="?do=add">
<label for="title">劇名/節目名</label>
<input type="text" id="title" name="title" required>
<label for="type">類型</label>
<select id="type" name="type">
<option value="variety">綜藝</option>
<option value="drama" selected>戲劇</option>
</select>
<label for="platform">上架平台</label>
<select id="platform" name="platform">
<option value="Netflix">Netflix</option>
<option value="Disney+">Disney+</option>
<option value="friDay影音">friDay影音</option>
<option value="愛奇藝">愛奇藝</option>
<option value="LINE TV">LINE TV</option>
<option value="YouTube">YouTube</option>
<option value="其他">其他</option>
</select>
<label for="watch_url">直接觀看網址</label>
<input type="url" id="watch_url" name="watch_url" placeholder="https://...">
<label for="current_season">目前看到第幾季</label>
<input type="number" id="current_season" name="current_season" min="1" value="1">
<label for="current_episode">目前看到第幾集</label>
<input type="number" id="current_episode" name="current_episode" min="0" value="0">
<label for="current_position">這一集看到哪裡</label>
<input type="text" id="current_position" name="current_position" placeholder="例如 35:20">
<label for="progress_note">進度情節備註</label>
<input type="text" id="progress_note" name="progress_note" placeholder="例如:女主角剛看到手機裡的影片">
<label for="status">狀態</label>
<select id="status" name="status">
<option value="want">想看</option>
<option value="watching" selected>追劇中</option>
<option value="done">已追完</option>
<option value="paused">暫停</option>
<option value="dropped">棄劇</option>
</select>
<label for="rating">評分</label>
<input type="number" id="rating" name="rating" min="0" max="10" step="0.1" placeholder="例如 8.5">
<label for="intro">簡介</label>
<textarea id="intro" name="intro" rows="4"></textarea>
<label for="note">心得備註</label>
<textarea id="note" name="note" rows="3"></textarea>
<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>
<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>
</form>
</section>
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$maxRank = $Drama->q("SELECT MAX(`rank`) AS m FROM `dramas`");
$nextRank = ((int)($maxRank[0]['m'] ?? 0)) + 1;
$data = [
'title' => trim($_POST['title'] ?? ''),
'type' => $_POST['type'] ?? 'drama',
'platform' => $_POST['platform'] ?? '其他',
'watch_url' => trim($_POST['watch_url'] ?? ''),
'current_season' => max(1, (int)($_POST['current_season'] ?? 1)),
'current_episode' => max(0, (int)($_POST['current_episode'] ?? 0)),
'current_position' => trim($_POST['current_position'] ?? ''),
'progress_note' => trim($_POST['progress_note'] ?? ''),
'status' => $_POST['status'] ?? 'want',
'rating' => $_POST['rating'] !== '' ? $_POST['rating'] : null,
'intro' => trim($_POST['intro'] ?? ''),
'note' => trim($_POST['note'] ?? ''),
'sh' => 1,
'rank' => $nextRank,
];
// DB::save 用字串拼接;null 改空字串避免 SQL 破掉
if ($data['rating'] === null) {
$data['rating'] = '';
}
$Drama->save($data);
to("?do=drama");
exit;
}
?>
<section>
<h2 class="section-title">新增作品</h2>
<form class="admin-form" method="post" action="?do=add">
<label for="title">劇名/節目名</label>
<input type="text" id="title" name="title" required>
<label for="type">類型</label>
<select id="type" name="type">
<option value="variety">綜藝</option>
<option value="drama" selected>戲劇</option>
</select>
<label for="platform">上架平台</label>
<select id="platform" name="platform">
<option value="Netflix">Netflix</option>
<option value="Disney+">Disney+</option>
<option value="friDay影音">friDay影音</option>
<option value="愛奇藝">愛奇藝</option>
<option value="LINE TV">LINE TV</option>
<option value="YouTube">YouTube</option>
<option value="其他">其他</option>
</select>
<label for="watch_url">直接觀看網址</label>
<input type="url" id="watch_url" name="watch_url" placeholder="https://...">
<label for="current_season">目前看到第幾季</label>
<input type="number" id="current_season" name="current_season" min="1" value="1">
<label for="current_episode">目前看到第幾集</label>
<input type="number" id="current_episode" name="current_episode" min="0" value="0">
<label for="current_position">這一集看到哪裡</label>
<input type="text" id="current_position" name="current_position" placeholder="例如 35:20">
<label for="progress_note">進度情節備註</label>
<input type="text" id="progress_note" name="progress_note" placeholder="例如:女主角剛看到手機裡的影片">
<label for="status">狀態</label>
<select id="status" name="status">
<option value="want">想看</option>
<option value="watching" selected>追劇中</option>
<option value="done">已追完</option>
<option value="paused">暫停</option>
<option value="dropped">棄劇</option>
</select>
<label for="rating">評分</label>
<input type="number" id="rating" name="rating" min="0" max="10" step="0.1" placeholder="例如 8.5">
<label for="intro">簡介</label>
<textarea id="intro" name="intro" rows="4"></textarea>
<label for="note">心得備註</label>
<textarea id="note" name="note" rows="3"></textarea>
<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>
<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>
</form>
</section>
@@ -1,71 +1,71 @@
<?php
$typeLabels = [
'variety' => '綜藝',
'drama' => '戲劇',
];
$rows = $Drama->all(" ORDER BY `rank` ASC, `id` ASC");
?>
<section>
<h2 class="section-title">追劇管理</h2>
<div class="admin-toolbar">
<a href="?do=add">新增作品</a>
</div>
<div class="drama-list">
<?php foreach ($rows as $idx => $row): ?>
<?php
$prev = ($idx === 0) ? $row['id'] : $rows[$idx - 1]['id'];
$next = ($idx === count($rows) - 1) ? $row['id'] : $rows[$idx + 1]['id'];
$typeText = $typeLabels[$row['type']] ?? $row['type'];
?>
<div class="drama-row">
<div class="drama-row__info">
<strong><?= htmlspecialchars($row['title'], ENT_QUOTES, 'UTF-8') ?></strong><br>
<?= htmlspecialchars($typeText . '' . $row['platform'], ENT_QUOTES, 'UTF-8') ?><br>
進度:第 <?= (int)$row['current_season'] ?> 季・第 <?= (int)$row['current_episode'] ?> 集
<?php if (!empty($row['current_position'])): ?>
<?= htmlspecialchars($row['current_position'], ENT_QUOTES, 'UTF-8') ?>
<?php endif; ?>
</div>
<div class="drama-row__actions">
<button type="button" class="show" data-id="<?= (int)$row['id'] ?>">
<?= ((int)$row['sh'] === 1) ? '顯示' : '隱藏' ?>
</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>
<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>
</div>
</div>
<?php endforeach; ?>
<?php if (count($rows) === 0): ?>
<p class="admin-lead">尚無資料,先新增一部作品吧。</p>
<?php endif; ?>
</div>
</section>
<script>
$(".switch-rank").on("click", function () {
let ids = $(this).data("ids").toString().split("-");
$.post("./api/sw.php", { ids, table: "Drama" }, function () {
location.reload();
});
});
$(".show").on("click", function () {
let id = $(this).data("id");
$.post("./api/show.php", { id }, function () {
location.reload();
});
});
$(".del-drama").on("click", function () {
if (!confirm("確定刪除這部作品?")) return;
let id = $(this).data("id");
$.post("./api/del.php", { id, table: "Drama" }, function () {
location.reload();
});
});
</script>
<?php
$typeLabels = [
'variety' => '綜藝',
'drama' => '戲劇',
];
$rows = $Drama->all(" ORDER BY `rank` ASC, `id` ASC");
?>
<section>
<h2 class="section-title">追劇管理</h2>
<div class="admin-toolbar">
<a href="?do=add">新增作品</a>
</div>
<div class="drama-list">
<?php foreach ($rows as $idx => $row): ?>
<?php
$prev = ($idx === 0) ? $row['id'] : $rows[$idx - 1]['id'];
$next = ($idx === count($rows) - 1) ? $row['id'] : $rows[$idx + 1]['id'];
$typeText = $typeLabels[$row['type']] ?? $row['type'];
?>
<div class="drama-row">
<div class="drama-row__info">
<strong><?= htmlspecialchars($row['title'], ENT_QUOTES, 'UTF-8') ?></strong><br>
<?= htmlspecialchars($typeText . '' . $row['platform'], ENT_QUOTES, 'UTF-8') ?><br>
進度:第 <?= (int)$row['current_season'] ?> 季・第 <?= (int)$row['current_episode'] ?> 集
<?php if (!empty($row['current_position'])): ?>
<?= htmlspecialchars($row['current_position'], ENT_QUOTES, 'UTF-8') ?>
<?php endif; ?>
</div>
<div class="drama-row__actions">
<button type="button" class="show" data-id="<?= (int)$row['id'] ?>">
<?= ((int)$row['sh'] === 1) ? '顯示' : '隱藏' ?>
</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>
<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>
</div>
</div>
<?php endforeach; ?>
<?php if (count($rows) === 0): ?>
<p class="admin-lead">尚無資料,先新增一部作品吧。</p>
<?php endif; ?>
</div>
</section>
<script>
$(".switch-rank").on("click", function () {
let ids = $(this).data("ids").toString().split("-");
$.post("./api/sw.php", { ids, table: "Drama" }, function () {
location.reload();
});
});
$(".show").on("click", function () {
let id = $(this).data("id");
$.post("./api/show.php", { id }, function () {
location.reload();
});
});
$(".del-drama").on("click", function () {
if (!confirm("確定刪除這部作品?")) return;
let id = $(this).data("id");
$.post("./api/del.php", { id, table: "Drama" }, function () {
location.reload();
});
});
</script>
@@ -1,107 +1,107 @@
<?php
$id = $_GET['id'] ?? 0;
$row = $Drama->find($id);
if (!$row) {
echo '<p class="admin-lead">找不到這筆資料。</p>';
return;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$row['title'] = trim($_POST['title'] ?? $row['title']);
$row['type'] = $_POST['type'] ?? $row['type'];
$row['platform'] = $_POST['platform'] ?? $row['platform'];
$row['watch_url'] = trim($_POST['watch_url'] ?? '');
$row['current_season'] = max(1, (int)($_POST['current_season'] ?? 1));
$row['current_episode'] = max(0, (int)($_POST['current_episode'] ?? 0));
$row['current_position'] = trim($_POST['current_position'] ?? '');
$row['progress_note'] = trim($_POST['progress_note'] ?? '');
$row['status'] = $_POST['status'] ?? $row['status'];
$row['rating'] = ($_POST['rating'] !== '') ? $_POST['rating'] : '';
$row['intro'] = trim($_POST['intro'] ?? '');
$row['note'] = trim($_POST['note'] ?? '');
$Drama->save($row);
to("?do=drama");
exit;
}
?>
<section>
<h2 class="section-title">編輯作品</h2>
<form class="admin-form" method="post" action="?do=edit&amp;id=<?= (int)$row['id'] ?>">
<label for="title">劇名/節目名</label>
<input type="text" id="title" name="title" required
value="<?= htmlspecialchars($row['title'], ENT_QUOTES, 'UTF-8') ?>">
<label for="type">類型</label>
<select id="type" name="type">
<option value="variety"<?= $row['type'] === 'variety' ? ' selected' : '' ?>>綜藝</option>
<option value="drama"<?= $row['type'] === 'drama' ? ' selected' : '' ?>>戲劇</option>
</select>
<label for="platform">上架平台</label>
<select id="platform" name="platform">
<?php
$platforms = ['Netflix', 'Disney+', 'friDay影音', '愛奇藝', 'LINE TV', 'YouTube', '其他'];
foreach ($platforms as $p):
?>
<option value="<?= htmlspecialchars($p, ENT_QUOTES, 'UTF-8') ?>"<?= $row['platform'] === $p ? ' selected' : '' ?>>
<?= htmlspecialchars($p, ENT_QUOTES, 'UTF-8') ?>
</option>
<?php endforeach; ?>
</select>
<label for="watch_url">直接觀看網址</label>
<input type="url" id="watch_url" name="watch_url"
value="<?= htmlspecialchars($row['watch_url'] ?? '', ENT_QUOTES, 'UTF-8') ?>"
placeholder="https://...">
<label for="current_season">目前看到第幾季</label>
<input type="number" id="current_season" name="current_season" min="1"
value="<?= (int)$row['current_season'] ?>">
<label for="current_episode">目前看到第幾集</label>
<input type="number" id="current_episode" name="current_episode" min="0"
value="<?= (int)$row['current_episode'] ?>">
<label for="current_position">這一集看到哪裡</label>
<input type="text" id="current_position" name="current_position"
value="<?= htmlspecialchars($row['current_position'] ?? '', ENT_QUOTES, 'UTF-8') ?>">
<label for="progress_note">進度情節備註</label>
<input type="text" id="progress_note" name="progress_note"
value="<?= htmlspecialchars($row['progress_note'] ?? '', ENT_QUOTES, 'UTF-8') ?>">
<label for="status">狀態</label>
<select id="status" name="status">
<?php
$statuses = [
'want' => '想看',
'watching' => '追劇中',
'done' => '已追完',
'paused' => '暫停',
'dropped' => '棄劇',
];
foreach ($statuses as $key => $label):
?>
<option value="<?= $key ?>"<?= $row['status'] === $key ? ' selected' : '' ?>><?= $label ?></option>
<?php endforeach; ?>
</select>
<label for="rating">評分</label>
<input type="number" id="rating" name="rating" min="0" max="10" step="0.1"
value="<?= htmlspecialchars($row['rating'] ?? '', ENT_QUOTES, 'UTF-8') ?>">
<label for="intro">簡介</label>
<textarea id="intro" name="intro" rows="4"><?= htmlspecialchars($row['intro'] ?? '', ENT_QUOTES, 'UTF-8') ?></textarea>
<label for="note">心得備註</label>
<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;">
<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>
</div>
</form>
</section>
<?php
$id = $_GET['id'] ?? 0;
$row = $Drama->find($id);
if (!$row) {
echo '<p class="admin-lead">找不到這筆資料。</p>';
return;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$row['title'] = trim($_POST['title'] ?? $row['title']);
$row['type'] = $_POST['type'] ?? $row['type'];
$row['platform'] = $_POST['platform'] ?? $row['platform'];
$row['watch_url'] = trim($_POST['watch_url'] ?? '');
$row['current_season'] = max(1, (int)($_POST['current_season'] ?? 1));
$row['current_episode'] = max(0, (int)($_POST['current_episode'] ?? 0));
$row['current_position'] = trim($_POST['current_position'] ?? '');
$row['progress_note'] = trim($_POST['progress_note'] ?? '');
$row['status'] = $_POST['status'] ?? $row['status'];
$row['rating'] = ($_POST['rating'] !== '') ? $_POST['rating'] : '';
$row['intro'] = trim($_POST['intro'] ?? '');
$row['note'] = trim($_POST['note'] ?? '');
$Drama->save($row);
to("?do=drama");
exit;
}
?>
<section>
<h2 class="section-title">編輯作品</h2>
<form class="admin-form" method="post" action="?do=edit&amp;id=<?= (int)$row['id'] ?>">
<label for="title">劇名/節目名</label>
<input type="text" id="title" name="title" required
value="<?= htmlspecialchars($row['title'], ENT_QUOTES, 'UTF-8') ?>">
<label for="type">類型</label>
<select id="type" name="type">
<option value="variety"<?= $row['type'] === 'variety' ? ' selected' : '' ?>>綜藝</option>
<option value="drama"<?= $row['type'] === 'drama' ? ' selected' : '' ?>>戲劇</option>
</select>
<label for="platform">上架平台</label>
<select id="platform" name="platform">
<?php
$platforms = ['Netflix', 'Disney+', 'friDay影音', '愛奇藝', 'LINE TV', 'YouTube', '其他'];
foreach ($platforms as $p):
?>
<option value="<?= htmlspecialchars($p, ENT_QUOTES, 'UTF-8') ?>"<?= $row['platform'] === $p ? ' selected' : '' ?>>
<?= htmlspecialchars($p, ENT_QUOTES, 'UTF-8') ?>
</option>
<?php endforeach; ?>
</select>
<label for="watch_url">直接觀看網址</label>
<input type="url" id="watch_url" name="watch_url"
value="<?= htmlspecialchars($row['watch_url'] ?? '', ENT_QUOTES, 'UTF-8') ?>"
placeholder="https://...">
<label for="current_season">目前看到第幾季</label>
<input type="number" id="current_season" name="current_season" min="1"
value="<?= (int)$row['current_season'] ?>">
<label for="current_episode">目前看到第幾集</label>
<input type="number" id="current_episode" name="current_episode" min="0"
value="<?= (int)$row['current_episode'] ?>">
<label for="current_position">這一集看到哪裡</label>
<input type="text" id="current_position" name="current_position"
value="<?= htmlspecialchars($row['current_position'] ?? '', ENT_QUOTES, 'UTF-8') ?>">
<label for="progress_note">進度情節備註</label>
<input type="text" id="progress_note" name="progress_note"
value="<?= htmlspecialchars($row['progress_note'] ?? '', ENT_QUOTES, 'UTF-8') ?>">
<label for="status">狀態</label>
<select id="status" name="status">
<?php
$statuses = [
'want' => '想看',
'watching' => '追劇中',
'done' => '已追完',
'paused' => '暫停',
'dropped' => '棄劇',
];
foreach ($statuses as $key => $label):
?>
<option value="<?= $key ?>"<?= $row['status'] === $key ? ' selected' : '' ?>><?= $label ?></option>
<?php endforeach; ?>
</select>
<label for="rating">評分</label>
<input type="number" id="rating" name="rating" min="0" max="10" step="0.1"
value="<?= htmlspecialchars($row['rating'] ?? '', ENT_QUOTES, 'UTF-8') ?>">
<label for="intro">簡介</label>
<textarea id="intro" name="intro" rows="4"><?= htmlspecialchars($row['intro'] ?? '', ENT_QUOTES, 'UTF-8') ?></textarea>
<label for="note">心得備註</label>
<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;">
<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>
</div>
</form>
</section>
@@ -1,7 +1,7 @@
<section>
<h2 class="section-title">後台首頁</h2>
<p class="admin-lead">乙級第三題骨架:清單、詳情、進度、後台 CRUD。訂票不做。</p>
<div class="admin-toolbar">
<a href="?do=drama">進入追劇管理</a>
</div>
</section>
<section>
<h2 class="section-title">後台首頁</h2>
<p class="admin-lead">乙級第三題骨架:清單、詳情、進度、後台 CRUD。訂票不做。</p>
<div class="admin-toolbar">
<a href="?do=drama">進入追劇管理</a>
</div>
</section>
@@ -1,429 +1,429 @@
/* ── 追劇清單/後台專用樣式 ── */
.vedio-list-page .vedio-intro {
text-align: left;
}
.vedio-list-page .vedio-intro p {
color: var(--color-text-muted);
font-size: 1rem;
line-height: 1.8;
margin: 0 0 0.85rem;
text-align: left;
}
.vedio-list-page .vedio-intro p:last-child {
margin-bottom: 0;
}
.vedio-list-page .list-lead {
color: var(--color-text-muted);
font-size: 1rem;
margin: 0 0 1.25rem;
text-align: left;
}
.vedio-list-page .type-tabs {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 10px;
margin: 0 0 1.75rem;
}
.vedio-list-page .type-tab {
display: inline-flex;
align-items: center;
justify-content: center;
border: 2px solid rgba(255, 204, 0, 0.22);
background: var(--color-surface-soft);
color: var(--color-text-dark);
font-weight: 700;
font-size: 0.92rem;
padding: 0.45rem 1.1rem;
border-radius: 999px;
text-decoration: none;
white-space: nowrap;
transition: border-color 0.18s ease, background 0.18s ease, color 0.18s ease;
}
.vedio-list-page .type-tab:hover {
border-color: var(--color-brand);
}
.vedio-list-page .type-tab.is-active {
border-color: var(--color-brand);
background: #fffce8;
color: var(--color-brand-dark);
}
.vedio-list-page .drama-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16px;
margin: 0 0 2rem;
}
.vedio-list-page .drama-card {
display: flex;
flex-direction: column;
align-items: stretch;
text-align: left;
padding: 1.15rem 1.2rem 1.25rem;
background: var(--color-surface-soft);
border: 2px solid rgba(255, 204, 0, 0.18);
border-radius: 12px;
box-sizing: border-box;
min-height: 100%;
}
.vedio-list-page .drama-card:hover {
border-color: var(--color-brand);
background: #fffce8;
}
.vedio-list-page .drama-card__poster {
display: flex;
align-items: center;
justify-content: center;
aspect-ratio: 16 / 9;
margin: 0 0 0.95rem;
border-radius: 8px;
background:
linear-gradient(145deg, rgba(255, 204, 0, 0.22), rgba(0, 0, 0, 0.08)),
var(--color-surface-soft);
color: var(--color-brand-dark);
font-weight: 800;
font-size: 0.95rem;
letter-spacing: 0.04em;
text-align: center;
padding: 0.75rem;
box-sizing: border-box;
}
.vedio-list-page .drama-card__meta {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin: 0 0 0.55rem;
}
.vedio-list-page .drama-tag {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.18rem 0.55rem;
border-radius: 999px;
font-size: 0.72rem;
font-weight: 700;
letter-spacing: 0.03em;
white-space: nowrap;
background: rgba(255, 204, 0, 0.18);
color: var(--color-brand-dark);
}
.vedio-list-page .drama-tag--platform {
background: rgba(0, 0, 0, 0.06);
color: var(--color-text-muted);
}
.vedio-list-page .drama-card__title {
margin: 0 0 0.45rem;
font-size: 1.15rem;
font-weight: 800;
color: var(--color-text-dark);
line-height: 1.35;
text-align: center;
}
.vedio-list-page .drama-card__progress,
.vedio-list-page .drama-card__note {
margin: 0 0 0.35rem;
color: var(--color-text-muted);
font-size: 0.9rem;
line-height: 1.65;
text-align: left;
}
.vedio-list-page .drama-card__note {
font-size: 0.84rem;
margin-bottom: 0.85rem;
}
.vedio-list-page .drama-card__actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
justify-content: center;
margin-top: auto;
padding-top: 0.35rem;
}
.vedio-list-page .drama-card__actions a {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
min-height: 38px;
padding: 0.35rem 0.9rem;
border-radius: 999px;
font-size: 0.86rem;
font-weight: 700;
text-decoration: none;
white-space: nowrap;
border: 2px solid rgba(255, 204, 0, 0.35);
background: #fff;
color: var(--color-brand-dark);
}
.vedio-list-page .drama-card__actions a:hover {
border-color: var(--color-brand);
background: #fffce8;
}
.vedio-list-page .drama-card__actions a.is-primary {
background: var(--color-brand);
border-color: var(--color-brand);
color: #1a1a1a;
}
.vedio-list-page .detail-block {
padding: 1.35rem 1.4rem 1.5rem;
border-radius: 12px;
background: var(--color-surface-soft);
border: 1px solid rgba(255, 204, 0, 0.25);
margin-bottom: 1.5rem;
}
.vedio-list-page .detail-block__eyebrow {
display: block;
text-align: center;
font-size: 0.78rem;
font-weight: 700;
letter-spacing: 0.08em;
color: var(--color-text-muted);
margin: 0 0 0.45rem;
}
.vedio-list-page .detail-block__title {
margin: 0 0 0.75rem;
text-align: center;
font-size: 1.35rem;
font-weight: 800;
color: var(--color-brand-dark);
line-height: 1.35;
}
.vedio-list-page .detail-block p {
margin: 0 0 0.75rem;
color: var(--color-text-muted);
font-size: 0.98rem;
line-height: 1.8;
text-align: left;
}
.vedio-list-page .detail-block__progress-box {
margin: 1rem 0;
padding: 0.9rem 1rem;
border-radius: 10px;
background: #fffce8;
border: 1px solid rgba(255, 204, 0, 0.35);
}
.vedio-list-page .detail-block__progress-box p {
margin: 0 0 0.35rem;
}
.vedio-list-page .detail-block__progress-box p:last-child {
margin-bottom: 0;
}
.vedio-list-page .detail-block__actions,
.vedio-list-page .form-actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
justify-content: center;
margin-top: 1.1rem;
}
.vedio-list-page .detail-block__actions a,
.vedio-list-page .form-actions a,
.vedio-list-page .form-actions button,
.vedio-list-page .progress-form button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
min-height: 40px;
padding: 0.4rem 1.05rem;
border-radius: 999px;
font-size: 0.9rem;
font-weight: 700;
text-decoration: none;
white-space: nowrap;
cursor: pointer;
border: 2px solid rgba(255, 204, 0, 0.35);
background: #fff;
color: var(--color-brand-dark);
}
.vedio-list-page .detail-block__actions a.is-primary,
.vedio-list-page .form-actions button.is-primary,
.vedio-list-page .progress-form button.is-primary {
background: var(--color-brand);
border-color: var(--color-brand);
color: #1a1a1a;
}
.vedio-list-page .empty-hint {
text-align: center;
color: var(--color-text-muted);
padding: 1.5rem 1rem;
margin: 0 0 1.5rem;
}
.vedio-list-page .progress-form,
.vedio-admin-page .admin-form {
max-width: 560px;
margin: 0 auto 2rem;
padding: 1.25rem 1.35rem;
background: var(--color-surface-soft);
border: 1px solid rgba(255, 204, 0, 0.25);
border-radius: 12px;
}
.vedio-list-page .progress-form label,
.vedio-admin-page .admin-form label {
display: block;
margin: 0.85rem 0 0.35rem;
font-weight: 700;
color: var(--color-text-dark);
text-align: left;
}
.vedio-list-page .progress-form input,
.vedio-list-page .progress-form select,
.vedio-list-page .progress-form textarea,
.vedio-admin-page .admin-form input,
.vedio-admin-page .admin-form select,
.vedio-admin-page .admin-form textarea {
width: 100%;
box-sizing: border-box;
padding: 0.55rem 0.7rem;
border: 1px solid rgba(0, 0, 0, 0.15);
border-radius: 8px;
font-size: 0.95rem;
}
.vedio-list-page .vedio-admin-link,
.vedio-admin-page .admin-nav {
text-align: center;
margin: 1rem 0;
}
.vedio-list-page .vedio-admin-link a {
color: var(--color-brand-dark);
font-weight: 700;
}
.vedio-admin-page .admin-wrap {
padding-top: 1.5rem;
padding-bottom: 2.5rem;
}
.vedio-admin-page .admin-nav {
display: flex;
flex-wrap: wrap;
gap: 10px;
justify-content: center;
margin-bottom: 1.5rem;
}
.vedio-admin-page .admin-nav a {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.4rem 1rem;
border-radius: 999px;
border: 2px solid rgba(255, 204, 0, 0.35);
background: #fffce8;
color: var(--color-brand-dark);
font-weight: 700;
text-decoration: none;
white-space: nowrap;
}
.vedio-admin-page .admin-lead {
text-align: center;
color: var(--color-text-muted);
margin: 0 0 1.25rem;
}
.vedio-admin-page .drama-row {
display: flex;
flex-wrap: wrap;
gap: 12px;
justify-content: space-between;
align-items: center;
padding: 12px 14px;
margin: 0 0 10px;
background: #fff;
border: 1px solid rgba(255, 204, 0, 0.25);
border-radius: 10px;
color: #222;
}
.vedio-admin-page .drama-row__info {
flex: 1 1 220px;
text-align: left;
}
.vedio-admin-page .drama-row__actions {
display: flex;
flex-wrap: wrap;
gap: 6px;
justify-content: flex-end;
}
.vedio-admin-page .drama-row__actions button,
.vedio-admin-page .drama-row__actions a.btn-like {
appearance: none;
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 34px;
padding: 0.25rem 0.7rem;
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 6px;
background: #fffce8;
color: #222;
font-size: 0.85rem;
font-weight: 700;
cursor: pointer;
text-decoration: none;
white-space: nowrap;
}
.vedio-admin-page .admin-toolbar {
text-align: center;
margin: 0 0 1rem;
}
.vedio-admin-page .admin-toolbar a {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.45rem 1.1rem;
border-radius: 999px;
background: var(--color-brand);
color: #1a1a1a;
font-weight: 800;
text-decoration: none;
}
@media (max-width: 640px) {
.vedio-list-page .drama-grid {
grid-template-columns: 1fr;
}
}
/* ── 追劇清單/後台專用樣式 ── */
.vedio-list-page .vedio-intro {
text-align: left;
}
.vedio-list-page .vedio-intro p {
color: var(--color-text-muted);
font-size: 1rem;
line-height: 1.8;
margin: 0 0 0.85rem;
text-align: left;
}
.vedio-list-page .vedio-intro p:last-child {
margin-bottom: 0;
}
.vedio-list-page .list-lead {
color: var(--color-text-muted);
font-size: 1rem;
margin: 0 0 1.25rem;
text-align: left;
}
.vedio-list-page .type-tabs {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 10px;
margin: 0 0 1.75rem;
}
.vedio-list-page .type-tab {
display: inline-flex;
align-items: center;
justify-content: center;
border: 2px solid rgba(255, 204, 0, 0.22);
background: var(--color-surface-soft);
color: var(--color-text-dark);
font-weight: 700;
font-size: 0.92rem;
padding: 0.45rem 1.1rem;
border-radius: 999px;
text-decoration: none;
white-space: nowrap;
transition: border-color 0.18s ease, background 0.18s ease, color 0.18s ease;
}
.vedio-list-page .type-tab:hover {
border-color: var(--color-brand);
}
.vedio-list-page .type-tab.is-active {
border-color: var(--color-brand);
background: #fffce8;
color: var(--color-brand-dark);
}
.vedio-list-page .drama-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16px;
margin: 0 0 2rem;
}
.vedio-list-page .drama-card {
display: flex;
flex-direction: column;
align-items: stretch;
text-align: left;
padding: 1.15rem 1.2rem 1.25rem;
background: var(--color-surface-soft);
border: 2px solid rgba(255, 204, 0, 0.18);
border-radius: 12px;
box-sizing: border-box;
min-height: 100%;
}
.vedio-list-page .drama-card:hover {
border-color: var(--color-brand);
background: #fffce8;
}
.vedio-list-page .drama-card__poster {
display: flex;
align-items: center;
justify-content: center;
aspect-ratio: 16 / 9;
margin: 0 0 0.95rem;
border-radius: 8px;
background:
linear-gradient(145deg, rgba(255, 204, 0, 0.22), rgba(0, 0, 0, 0.08)),
var(--color-surface-soft);
color: var(--color-brand-dark);
font-weight: 800;
font-size: 0.95rem;
letter-spacing: 0.04em;
text-align: center;
padding: 0.75rem;
box-sizing: border-box;
}
.vedio-list-page .drama-card__meta {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin: 0 0 0.55rem;
}
.vedio-list-page .drama-tag {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.18rem 0.55rem;
border-radius: 999px;
font-size: 0.72rem;
font-weight: 700;
letter-spacing: 0.03em;
white-space: nowrap;
background: rgba(255, 204, 0, 0.18);
color: var(--color-brand-dark);
}
.vedio-list-page .drama-tag--platform {
background: rgba(0, 0, 0, 0.06);
color: var(--color-text-muted);
}
.vedio-list-page .drama-card__title {
margin: 0 0 0.45rem;
font-size: 1.15rem;
font-weight: 800;
color: var(--color-text-dark);
line-height: 1.35;
text-align: center;
}
.vedio-list-page .drama-card__progress,
.vedio-list-page .drama-card__note {
margin: 0 0 0.35rem;
color: var(--color-text-muted);
font-size: 0.9rem;
line-height: 1.65;
text-align: left;
}
.vedio-list-page .drama-card__note {
font-size: 0.84rem;
margin-bottom: 0.85rem;
}
.vedio-list-page .drama-card__actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
justify-content: center;
margin-top: auto;
padding-top: 0.35rem;
}
.vedio-list-page .drama-card__actions a {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
min-height: 38px;
padding: 0.35rem 0.9rem;
border-radius: 999px;
font-size: 0.86rem;
font-weight: 700;
text-decoration: none;
white-space: nowrap;
border: 2px solid rgba(255, 204, 0, 0.35);
background: #fff;
color: var(--color-brand-dark);
}
.vedio-list-page .drama-card__actions a:hover {
border-color: var(--color-brand);
background: #fffce8;
}
.vedio-list-page .drama-card__actions a.is-primary {
background: var(--color-brand);
border-color: var(--color-brand);
color: #1a1a1a;
}
.vedio-list-page .detail-block {
padding: 1.35rem 1.4rem 1.5rem;
border-radius: 12px;
background: var(--color-surface-soft);
border: 1px solid rgba(255, 204, 0, 0.25);
margin-bottom: 1.5rem;
}
.vedio-list-page .detail-block__eyebrow {
display: block;
text-align: center;
font-size: 0.78rem;
font-weight: 700;
letter-spacing: 0.08em;
color: var(--color-text-muted);
margin: 0 0 0.45rem;
}
.vedio-list-page .detail-block__title {
margin: 0 0 0.75rem;
text-align: center;
font-size: 1.35rem;
font-weight: 800;
color: var(--color-brand-dark);
line-height: 1.35;
}
.vedio-list-page .detail-block p {
margin: 0 0 0.75rem;
color: var(--color-text-muted);
font-size: 0.98rem;
line-height: 1.8;
text-align: left;
}
.vedio-list-page .detail-block__progress-box {
margin: 1rem 0;
padding: 0.9rem 1rem;
border-radius: 10px;
background: #fffce8;
border: 1px solid rgba(255, 204, 0, 0.35);
}
.vedio-list-page .detail-block__progress-box p {
margin: 0 0 0.35rem;
}
.vedio-list-page .detail-block__progress-box p:last-child {
margin-bottom: 0;
}
.vedio-list-page .detail-block__actions,
.vedio-list-page .form-actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
justify-content: center;
margin-top: 1.1rem;
}
.vedio-list-page .detail-block__actions a,
.vedio-list-page .form-actions a,
.vedio-list-page .form-actions button,
.vedio-list-page .progress-form button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
min-height: 40px;
padding: 0.4rem 1.05rem;
border-radius: 999px;
font-size: 0.9rem;
font-weight: 700;
text-decoration: none;
white-space: nowrap;
cursor: pointer;
border: 2px solid rgba(255, 204, 0, 0.35);
background: #fff;
color: var(--color-brand-dark);
}
.vedio-list-page .detail-block__actions a.is-primary,
.vedio-list-page .form-actions button.is-primary,
.vedio-list-page .progress-form button.is-primary {
background: var(--color-brand);
border-color: var(--color-brand);
color: #1a1a1a;
}
.vedio-list-page .empty-hint {
text-align: center;
color: var(--color-text-muted);
padding: 1.5rem 1rem;
margin: 0 0 1.5rem;
}
.vedio-list-page .progress-form,
.vedio-admin-page .admin-form {
max-width: 560px;
margin: 0 auto 2rem;
padding: 1.25rem 1.35rem;
background: var(--color-surface-soft);
border: 1px solid rgba(255, 204, 0, 0.25);
border-radius: 12px;
}
.vedio-list-page .progress-form label,
.vedio-admin-page .admin-form label {
display: block;
margin: 0.85rem 0 0.35rem;
font-weight: 700;
color: var(--color-text-dark);
text-align: left;
}
.vedio-list-page .progress-form input,
.vedio-list-page .progress-form select,
.vedio-list-page .progress-form textarea,
.vedio-admin-page .admin-form input,
.vedio-admin-page .admin-form select,
.vedio-admin-page .admin-form textarea {
width: 100%;
box-sizing: border-box;
padding: 0.55rem 0.7rem;
border: 1px solid rgba(0, 0, 0, 0.15);
border-radius: 8px;
font-size: 0.95rem;
}
.vedio-list-page .vedio-admin-link,
.vedio-admin-page .admin-nav {
text-align: center;
margin: 1rem 0;
}
.vedio-list-page .vedio-admin-link a {
color: var(--color-brand-dark);
font-weight: 700;
}
.vedio-admin-page .admin-wrap {
padding-top: 1.5rem;
padding-bottom: 2.5rem;
}
.vedio-admin-page .admin-nav {
display: flex;
flex-wrap: wrap;
gap: 10px;
justify-content: center;
margin-bottom: 1.5rem;
}
.vedio-admin-page .admin-nav a {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.4rem 1rem;
border-radius: 999px;
border: 2px solid rgba(255, 204, 0, 0.35);
background: #fffce8;
color: var(--color-brand-dark);
font-weight: 700;
text-decoration: none;
white-space: nowrap;
}
.vedio-admin-page .admin-lead {
text-align: center;
color: var(--color-text-muted);
margin: 0 0 1.25rem;
}
.vedio-admin-page .drama-row {
display: flex;
flex-wrap: wrap;
gap: 12px;
justify-content: space-between;
align-items: center;
padding: 12px 14px;
margin: 0 0 10px;
background: #fff;
border: 1px solid rgba(255, 204, 0, 0.25);
border-radius: 10px;
color: #222;
}
.vedio-admin-page .drama-row__info {
flex: 1 1 220px;
text-align: left;
}
.vedio-admin-page .drama-row__actions {
display: flex;
flex-wrap: wrap;
gap: 6px;
justify-content: flex-end;
}
.vedio-admin-page .drama-row__actions button,
.vedio-admin-page .drama-row__actions a.btn-like {
appearance: none;
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 34px;
padding: 0.25rem 0.7rem;
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 6px;
background: #fffce8;
color: #222;
font-size: 0.85rem;
font-weight: 700;
cursor: pointer;
text-decoration: none;
white-space: nowrap;
}
.vedio-admin-page .admin-toolbar {
text-align: center;
margin: 0 0 1rem;
}
.vedio-admin-page .admin-toolbar a {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.45rem 1.1rem;
border-radius: 999px;
background: var(--color-brand);
color: #1a1a1a;
font-weight: 800;
text-decoration: none;
}
@media (max-width: 640px) {
.vedio-list-page .drama-grid {
grid-template-columns: 1fr;
}
}
@@ -1,62 +1,62 @@
-- 深夜清單 / 追劇資料庫
-- 本機 XAMPPphpMyAdmin 匯入此檔即可
CREATE DATABASE IF NOT EXISTS `vedio_list` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE `vedio_list`;
DROP TABLE IF EXISTS `dramas`;
CREATE TABLE `dramas` (
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`title` varchar(100) NOT NULL COMMENT '劇名/節目名',
`type` varchar(20) NOT NULL DEFAULT 'drama' COMMENT 'variety=綜藝, drama=戲劇',
`platform` varchar(50) NOT NULL COMMENT '上架平台',
`watch_url` varchar(500) DEFAULT NULL COMMENT '作品直接觀看網址',
`current_season` int(10) UNSIGNED NOT NULL DEFAULT 1 COMMENT '目前第幾季',
`current_episode` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '目前第幾集',
`current_position` varchar(50) DEFAULT NULL COMMENT '該集看到的位置',
`progress_note` varchar(255) DEFAULT NULL COMMENT '進度情節備註',
`status` varchar(20) NOT NULL DEFAULT 'watching' COMMENT 'want/watching/done/paused/dropped',
`rating` decimal(3,1) DEFAULT NULL COMMENT '評分',
`intro` text COMMENT '簡介',
`note` text COMMENT '心得備註',
`sh` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '是否顯示',
`rank` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '排列順序',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO `dramas`
(`title`, `type`, `platform`, `watch_url`, `current_season`, `current_episode`, `current_position`, `progress_note`, `status`, `rating`, `intro`, `note`, `sh`, `rank`)
VALUES
(
'三傻遊肯亞',
'variety',
'Netflix',
'https://www.netflix.com/title/82031207',
1,
2,
'28:40',
'三人剛被扔進野外,表情比獅子還慌。',
'watching',
8.5,
'三個人、一座肯亞,以及無數個「這也能出事?」的現場。笑點來得又急又野,像把旅行節目的安全感整袋拆掉,只留下腎上腺素與默契。',
'羅PD 首登 Netflix;志源在裡面,小黃更沒有藉口不補。',
1,
1
),
(
'請回答1997',
'drama',
'Disney+',
'https://www.disneyplus.com/en-tw/browse/entity-3979fb82-8681-44e8-b7be-e313091f0d73',
1,
5,
'41:12',
'宿舍夜談剛結束,收音機還在響,心卻已經開始亂。',
'watching',
9.0,
'把 1997 寫進宿舍、收音機與那一聲來不及說出口的喜歡。甜的時候甜到發亮,疼的時候又老實得讓人不敢快轉。',
'下一集容易哭,衛生紙先備好。',
1,
2
);
-- 深夜清單 / 追劇資料庫
-- 本機 XAMPPphpMyAdmin 匯入此檔即可
CREATE DATABASE IF NOT EXISTS `vedio_list` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE `vedio_list`;
DROP TABLE IF EXISTS `dramas`;
CREATE TABLE `dramas` (
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`title` varchar(100) NOT NULL COMMENT '劇名/節目名',
`type` varchar(20) NOT NULL DEFAULT 'drama' COMMENT 'variety=綜藝, drama=戲劇',
`platform` varchar(50) NOT NULL COMMENT '上架平台',
`watch_url` varchar(500) DEFAULT NULL COMMENT '作品直接觀看網址',
`current_season` int(10) UNSIGNED NOT NULL DEFAULT 1 COMMENT '目前第幾季',
`current_episode` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '目前第幾集',
`current_position` varchar(50) DEFAULT NULL COMMENT '該集看到的位置',
`progress_note` varchar(255) DEFAULT NULL COMMENT '進度情節備註',
`status` varchar(20) NOT NULL DEFAULT 'watching' COMMENT 'want/watching/done/paused/dropped',
`rating` decimal(3,1) DEFAULT NULL COMMENT '評分',
`intro` text COMMENT '簡介',
`note` text COMMENT '心得備註',
`sh` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '是否顯示',
`rank` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '排列順序',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO `dramas`
(`title`, `type`, `platform`, `watch_url`, `current_season`, `current_episode`, `current_position`, `progress_note`, `status`, `rating`, `intro`, `note`, `sh`, `rank`)
VALUES
(
'三傻遊肯亞',
'variety',
'Netflix',
'https://www.netflix.com/title/82031207',
1,
2,
'28:40',
'三人剛被扔進野外,表情比獅子還慌。',
'watching',
8.5,
'三個人、一座肯亞,以及無數個「這也能出事?」的現場。笑點來得又急又野,像把旅行節目的安全感整袋拆掉,只留下腎上腺素與默契。',
'羅PD 首登 Netflix;志源在裡面,小黃更沒有藉口不補。',
1,
1
),
(
'請回答1997',
'drama',
'Disney+',
'https://www.disneyplus.com/en-tw/browse/entity-3979fb82-8681-44e8-b7be-e313091f0d73',
1,
5,
'41:12',
'宿舍夜談剛結束,收音機還在響,心卻已經開始亂。',
'watching',
9.0,
'把 1997 寫進宿舍、收音機與那一聲來不及說出口的喜歡。甜的時候甜到發亮,疼的時候又老實得讓人不敢快轉。',
'下一集容易哭,衛生紙先備好。',
1,
2
);
@@ -1,65 +1,65 @@
<?php
$id = $_GET['id'] ?? 0;
$row = $Drama->find($id);
if (!$row || (int)$row['sh'] !== 1) {
echo '<p class="empty-hint">找不到這部作品,或它目前未公開顯示。</p>';
echo '<p class="empty-hint"><a href="?do=main">回清單</a></p>';
return;
}
$typeLabels = [
'variety' => '綜藝',
'drama' => '戲劇',
];
$statusLabels = [
'want' => '想看',
'watching' => '追劇中',
'done' => '已追完',
'paused' => '暫停',
'dropped' => '棄劇',
];
$typeText = $typeLabels[$row['type']] ?? $row['type'];
$statusText = $statusLabels[$row['status']] ?? $row['status'];
?>
<section class="detail-block">
<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>
<?php if (!empty($row['intro'])): ?>
<p><?= nl2br(htmlspecialchars($row['intro'], ENT_QUOTES, 'UTF-8')) ?></p>
<?php endif; ?>
<div class="detail-block__progress-box">
<p>
觀看進度:第 <?= (int)$row['current_season'] ?> 季|第 <?= (int)$row['current_episode'] ?> 集
<?php if (!empty($row['current_position'])): ?>
<?= htmlspecialchars($row['current_position'], ENT_QUOTES, 'UTF-8') ?>
<?php endif; ?>
</p>
<?php if (!empty($row['progress_note'])): ?>
<p><?= htmlspecialchars($row['progress_note'], ENT_QUOTES, 'UTF-8') ?></p>
<?php endif; ?>
</div>
<p>狀態:<?= htmlspecialchars($statusText, ENT_QUOTES, 'UTF-8') ?>
<?php if ($row['rating'] !== null && $row['rating'] !== ''): ?>
 · 評分:<?= htmlspecialchars($row['rating'], ENT_QUOTES, 'UTF-8') ?>
<?php endif; ?>
</p>
<?php if (!empty($row['note'])): ?>
<p><?= nl2br(htmlspecialchars($row['note'], ENT_QUOTES, 'UTF-8')) ?></p>
<?php endif; ?>
<div class="detail-block__actions">
<?php if (!empty($row['watch_url'])): ?>
<a class="is-primary" href="<?= htmlspecialchars($row['watch_url'], ENT_QUOTES, 'UTF-8') ?>"
target="_blank" rel="noopener noreferrer">繼續觀看</a>
<?php endif; ?>
<a href="?do=progress&amp;id=<?= (int)$row['id'] ?>">更新進度</a>
<a href="?do=main">回清單</a>
</div>
</section>
<?php
$id = $_GET['id'] ?? 0;
$row = $Drama->find($id);
if (!$row || (int)$row['sh'] !== 1) {
echo '<p class="empty-hint">找不到這部作品,或它目前未公開顯示。</p>';
echo '<p class="empty-hint"><a href="?do=main">回清單</a></p>';
return;
}
$typeLabels = [
'variety' => '綜藝',
'drama' => '戲劇',
];
$statusLabels = [
'want' => '想看',
'watching' => '追劇中',
'done' => '已追完',
'paused' => '暫停',
'dropped' => '棄劇',
];
$typeText = $typeLabels[$row['type']] ?? $row['type'];
$statusText = $statusLabels[$row['status']] ?? $row['status'];
?>
<section class="detail-block">
<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>
<?php if (!empty($row['intro'])): ?>
<p><?= nl2br(htmlspecialchars($row['intro'], ENT_QUOTES, 'UTF-8')) ?></p>
<?php endif; ?>
<div class="detail-block__progress-box">
<p>
觀看進度:第 <?= (int)$row['current_season'] ?> 季|第 <?= (int)$row['current_episode'] ?> 集
<?php if (!empty($row['current_position'])): ?>
<?= htmlspecialchars($row['current_position'], ENT_QUOTES, 'UTF-8') ?>
<?php endif; ?>
</p>
<?php if (!empty($row['progress_note'])): ?>
<p><?= htmlspecialchars($row['progress_note'], ENT_QUOTES, 'UTF-8') ?></p>
<?php endif; ?>
</div>
<p>狀態:<?= htmlspecialchars($statusText, ENT_QUOTES, 'UTF-8') ?>
<?php if ($row['rating'] !== null && $row['rating'] !== ''): ?>
 · 評分:<?= htmlspecialchars($row['rating'], ENT_QUOTES, 'UTF-8') ?>
<?php endif; ?>
</p>
<?php if (!empty($row['note'])): ?>
<p><?= nl2br(htmlspecialchars($row['note'], ENT_QUOTES, 'UTF-8')) ?></p>
<?php endif; ?>
<div class="detail-block__actions">
<?php if (!empty($row['watch_url'])): ?>
<a class="is-primary" href="<?= htmlspecialchars($row['watch_url'], ENT_QUOTES, 'UTF-8') ?>"
target="_blank" rel="noopener noreferrer">繼續觀看</a>
<?php endif; ?>
<a href="?do=progress&amp;id=<?= (int)$row['id'] ?>">更新進度</a>
<a href="?do=main">回清單</a>
</div>
</section>
@@ -1,84 +1,84 @@
<?php
$typeLabels = [
'variety' => '綜藝',
'drama' => '戲劇',
];
$statusLabels = [
'want' => '想看',
'watching' => '追劇中',
'done' => '已追完',
'paused' => '暫停',
'dropped' => '棄劇',
];
$filter = $_GET['type'] ?? 'all';
$where = ['sh' => 1];
if ($filter === 'variety' || $filter === 'drama') {
$where['type'] = $filter;
}
$rows = $Drama->all($where, " ORDER BY `rank` ASC, `id` ASC");
?>
<section class="vedio-intro">
<h2 class="section-title">正在追的那些夜晚</h2>
<p>
有些夜晚適合考古黃色奇蹟,有些夜晚則是——螢幕一亮,人就栽進另一個世界。
綜藝裡的失控笑聲、戲劇裡的心跳停拍,都值得被好好記下來:看到哪一季、哪一集、哪一句台詞剛把你釘在沙發上。
</p>
<p>
這裡的清單來自資料庫:類型先分<span class="mark_y">綜藝</span><span class="mark_b">戲劇</span>
進度與「繼續觀看」入口都能改。慢慢養也很好看。
</p>
</section>
<section class="list-section" aria-labelledby="vedio-list-heading">
<h2 class="section-title" id="vedio-list-heading">追劇清單</h2>
<p class="list-lead">先用類型找——綜藝還是戲劇,跟 OTT 首頁那排分類一樣直覺。</p>
<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 === '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>
</div>
<?php if (count($rows) === 0): ?>
<p class="empty-hint">這個類型目前還沒有作品——換「全部」看看,或到後台新增一筆。</p>
<?php else: ?>
<div class="drama-grid">
<?php foreach ($rows as $row): ?>
<?php
$typeText = $typeLabels[$row['type']] ?? $row['type'];
$statusText = $statusLabels[$row['status']] ?? $row['status'];
$posterLabel = htmlspecialchars(strtoupper($row['platform']) . ' · ' . $typeText, 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__meta">
<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>
</div>
<h3 class="drama-card__title"><?= htmlspecialchars($row['title'], ENT_QUOTES, 'UTF-8') ?></h3>
<p class="drama-card__progress">
觀看進度:第 <?= (int)$row['current_season'] ?> 季・第 <?= (int)$row['current_episode'] ?> 集
<?php if (!empty($row['current_position'])): ?>
・看到 <?= htmlspecialchars($row['current_position'], ENT_QUOTES, 'UTF-8') ?>
<?php endif; ?>
</p>
<?php if (!empty($row['progress_note'])): ?>
<p class="drama-card__note"><?= htmlspecialchars($row['progress_note'], ENT_QUOTES, 'UTF-8') ?></p>
<?php else: ?>
<p class="drama-card__note">狀態:<?= htmlspecialchars($statusText, ENT_QUOTES, 'UTF-8') ?></p>
<?php endif; ?>
<div class="drama-card__actions">
<?php if (!empty($row['watch_url'])): ?>
<a class="is-primary" href="<?= htmlspecialchars($row['watch_url'], ENT_QUOTES, 'UTF-8') ?>"
target="_blank" rel="noopener noreferrer">繼續觀看</a>
<?php endif; ?>
<a href="?do=detail&amp;id=<?= (int)$row['id'] ?>">查看詳情</a>
<a href="?do=progress&amp;id=<?= (int)$row['id'] ?>">更新進度</a>
</div>
</article>
<?php endforeach; ?>
</div>
<?php endif; ?>
</section>
<?php
$typeLabels = [
'variety' => '綜藝',
'drama' => '戲劇',
];
$statusLabels = [
'want' => '想看',
'watching' => '追劇中',
'done' => '已追完',
'paused' => '暫停',
'dropped' => '棄劇',
];
$filter = $_GET['type'] ?? 'all';
$where = ['sh' => 1];
if ($filter === 'variety' || $filter === 'drama') {
$where['type'] = $filter;
}
$rows = $Drama->all($where, " ORDER BY `rank` ASC, `id` ASC");
?>
<section class="vedio-intro">
<h2 class="section-title">正在追的那些夜晚</h2>
<p>
有些夜晚適合考古黃色奇蹟,有些夜晚則是——螢幕一亮,人就栽進另一個世界。
綜藝裡的失控笑聲、戲劇裡的心跳停拍,都值得被好好記下來:看到哪一季、哪一集、哪一句台詞剛把你釘在沙發上。
</p>
<p>
這裡的清單來自資料庫:類型先分<span class="mark_y">綜藝</span><span class="mark_b">戲劇</span>
進度與「繼續觀看」入口都能改。慢慢養也很好看。
</p>
</section>
<section class="list-section" aria-labelledby="vedio-list-heading">
<h2 class="section-title" id="vedio-list-heading">追劇清單</h2>
<p class="list-lead">先用類型找——綜藝還是戲劇,跟 OTT 首頁那排分類一樣直覺。</p>
<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 === '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>
</div>
<?php if (count($rows) === 0): ?>
<p class="empty-hint">這個類型目前還沒有作品——換「全部」看看,或到後台新增一筆。</p>
<?php else: ?>
<div class="drama-grid">
<?php foreach ($rows as $row): ?>
<?php
$typeText = $typeLabels[$row['type']] ?? $row['type'];
$statusText = $statusLabels[$row['status']] ?? $row['status'];
$posterLabel = htmlspecialchars(strtoupper($row['platform']) . ' · ' . $typeText, 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__meta">
<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>
</div>
<h3 class="drama-card__title"><?= htmlspecialchars($row['title'], ENT_QUOTES, 'UTF-8') ?></h3>
<p class="drama-card__progress">
觀看進度:第 <?= (int)$row['current_season'] ?> 季・第 <?= (int)$row['current_episode'] ?> 集
<?php if (!empty($row['current_position'])): ?>
・看到 <?= htmlspecialchars($row['current_position'], ENT_QUOTES, 'UTF-8') ?>
<?php endif; ?>
</p>
<?php if (!empty($row['progress_note'])): ?>
<p class="drama-card__note"><?= htmlspecialchars($row['progress_note'], ENT_QUOTES, 'UTF-8') ?></p>
<?php else: ?>
<p class="drama-card__note">狀態:<?= htmlspecialchars($statusText, ENT_QUOTES, 'UTF-8') ?></p>
<?php endif; ?>
<div class="drama-card__actions">
<?php if (!empty($row['watch_url'])): ?>
<a class="is-primary" href="<?= htmlspecialchars($row['watch_url'], ENT_QUOTES, 'UTF-8') ?>"
target="_blank" rel="noopener noreferrer">繼續觀看</a>
<?php endif; ?>
<a href="?do=detail&amp;id=<?= (int)$row['id'] ?>">查看詳情</a>
<a href="?do=progress&amp;id=<?= (int)$row['id'] ?>">更新進度</a>
</div>
</article>
<?php endforeach; ?>
</div>
<?php endif; ?>
</section>
@@ -1,69 +1,69 @@
<?php
$id = $_GET['id'] ?? 0;
$row = $Drama->find($id);
if (!$row) {
echo '<p class="empty-hint">找不到這部作品。</p>';
echo '<p class="empty-hint"><a href="?do=main">回清單</a></p>';
return;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$row['current_season'] = max(1, (int)($_POST['current_season'] ?? 1));
$row['current_episode'] = max(0, (int)($_POST['current_episode'] ?? 0));
$row['current_position'] = trim($_POST['current_position'] ?? '');
$row['progress_note'] = trim($_POST['progress_note'] ?? '');
$row['status'] = $_POST['status'] ?? $row['status'];
$Drama->save($row);
to("?do=detail&id=" . (int)$row['id']);
exit;
}
?>
<section class="vedio-intro">
<h2 class="section-title">更新進度</h2>
<p>記下你停在哪一季、哪一集、哪一秒——以及當時螢幕上正在發生什麼,之後才找得回那個心跳瞬間。</p>
</section>
<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>
<label for="current_season">目前看到第幾季</label>
<input type="number" id="current_season" name="current_season" min="1"
value="<?= (int)$row['current_season'] ?>">
<label for="current_episode">目前看到第幾集</label>
<input type="number" id="current_episode" name="current_episode" min="0"
value="<?= (int)$row['current_episode'] ?>">
<label for="current_position">這一集看到哪裡</label>
<input type="text" id="current_position" name="current_position"
value="<?= htmlspecialchars($row['current_position'] ?? '', ENT_QUOTES, 'UTF-8') ?>"
placeholder="例如 35:20、剩下 10 分鐘、片尾前">
<label for="progress_note">進度情節備註</label>
<input type="text" id="progress_note" name="progress_note"
value="<?= htmlspecialchars($row['progress_note'] ?? '', ENT_QUOTES, 'UTF-8') ?>"
placeholder="例如:女主角剛看到手機裡的影片">
<label for="status">狀態</label>
<select id="status" name="status">
<?php
$statuses = [
'want' => '想看',
'watching' => '追劇中',
'done' => '已追完',
'paused' => '暫停',
'dropped' => '棄劇',
];
foreach ($statuses as $key => $label):
?>
<option value="<?= $key ?>"<?= $row['status'] === $key ? ' selected' : '' ?>><?= $label ?></option>
<?php endforeach; ?>
</select>
<div class="form-actions">
<button type="submit" class="is-primary">儲存進度</button>
<a href="?do=detail&amp;id=<?= (int)$row['id'] ?>">取消</a>
</div>
</form>
<?php
$id = $_GET['id'] ?? 0;
$row = $Drama->find($id);
if (!$row) {
echo '<p class="empty-hint">找不到這部作品。</p>';
echo '<p class="empty-hint"><a href="?do=main">回清單</a></p>';
return;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$row['current_season'] = max(1, (int)($_POST['current_season'] ?? 1));
$row['current_episode'] = max(0, (int)($_POST['current_episode'] ?? 0));
$row['current_position'] = trim($_POST['current_position'] ?? '');
$row['progress_note'] = trim($_POST['progress_note'] ?? '');
$row['status'] = $_POST['status'] ?? $row['status'];
$Drama->save($row);
to("?do=detail&id=" . (int)$row['id']);
exit;
}
?>
<section class="vedio-intro">
<h2 class="section-title">更新進度</h2>
<p>記下你停在哪一季、哪一集、哪一秒——以及當時螢幕上正在發生什麼,之後才找得回那個心跳瞬間。</p>
</section>
<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>
<label for="current_season">目前看到第幾季</label>
<input type="number" id="current_season" name="current_season" min="1"
value="<?= (int)$row['current_season'] ?>">
<label for="current_episode">目前看到第幾集</label>
<input type="number" id="current_episode" name="current_episode" min="0"
value="<?= (int)$row['current_episode'] ?>">
<label for="current_position">這一集看到哪裡</label>
<input type="text" id="current_position" name="current_position"
value="<?= htmlspecialchars($row['current_position'] ?? '', ENT_QUOTES, 'UTF-8') ?>"
placeholder="例如 35:20、剩下 10 分鐘、片尾前">
<label for="progress_note">進度情節備註</label>
<input type="text" id="progress_note" name="progress_note"
value="<?= htmlspecialchars($row['progress_note'] ?? '', ENT_QUOTES, 'UTF-8') ?>"
placeholder="例如:女主角剛看到手機裡的影片">
<label for="status">狀態</label>
<select id="status" name="status">
<?php
$statuses = [
'want' => '想看',
'watching' => '追劇中',
'done' => '已追完',
'paused' => '暫停',
'dropped' => '棄劇',
];
foreach ($statuses as $key => $label):
?>
<option value="<?= $key ?>"<?= $row['status'] === $key ? ' selected' : '' ?>><?= $label ?></option>
<?php endforeach; ?>
</select>
<div class="form-actions">
<button type="submit" class="is-primary">儲存進度</button>
<a href="?do=detail&amp;id=<?= (int)$row['id'] ?>">取消</a>
</div>
</form>
@@ -1,54 +1,54 @@
<?php include_once "./api/db.php"; ?>
<!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="../../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="../../assets/css/tablet.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>
</head>
<body class="vedio-list-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">DRAMA &amp; VARIETY WATCH LIST</span>
</div>
</div>
</header>
<nav class="portal-nav" aria-label="水晶男孩推廣部導覽" data-portal-nav></nav>
<canvas id="particles"></canvas>
<div class="container">
<?php
$do = $_GET['do'] ?? 'main';
$file = "front/$do.php";
if (file_exists($file)) {
include $file;
} else {
include "front/main.php";
}
?>
</div>
<footer>
<p>SECHSKIES Fanpage &copy; 2026 - 黃色氣球永遠飄揚</p>
<a href="https://jekkinoopy.github.io/Portfolio/" class="studio-link" target="_blank" rel="noopener noreferrer">
Design by <span class="mark_b">Jekkinoopy Studio</span>
</a>
<p class="vedio-admin-link"><a href="admin.php">管理後台</a></p>
</footer>
<script src="../../assets/js/particles.js"></script>
<script src="../../assets/js/portal-nav.js"></script>
</body>
</html>
<?php include_once "./api/db.php"; ?>
<!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="../../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="../../assets/css/tablet.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>
</head>
<body class="vedio-list-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">DRAMA &amp; VARIETY WATCH LIST</span>
</div>
</div>
</header>
<nav class="portal-nav" aria-label="水晶男孩推廣部導覽" data-portal-nav></nav>
<canvas id="particles"></canvas>
<div class="container">
<?php
$do = $_GET['do'] ?? 'main';
$file = "front/$do.php";
if (file_exists($file)) {
include $file;
} else {
include "front/main.php";
}
?>
</div>
<footer>
<p>SECHSKIES Fanpage &copy; 2026 - 黃色氣球永遠飄揚</p>
<a href="https://jekkinoopy.github.io/Portfolio/" class="studio-link" target="_blank" rel="noopener noreferrer">
Design by <span class="mark_b">Jekkinoopy Studio</span>
</a>
<p class="vedio-admin-link"><a href="admin.php">管理後台</a></p>
</footer>
<script src="../../assets/js/particles.js"></script>
<script src="../../assets/js/portal-nav.js"></script>
</body>
</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 · 20162026</span></footer>
</main>
);
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

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