feat: 本機新增頁面與導覽調整
This commit is contained in:
Vendored
+3
-1
@@ -1,3 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
// 本地預覽:雙擊專案根目錄 preview.bat
|
||||||
|
// 或在 Cursor:Ctrl+Shift+P →「執行工作」→「預覽網站」
|
||||||
"liveServer.settings.port": 5501
|
"liveServer.settings.port": 5501
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+23
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "預覽網站",
|
||||||
|
"detail": "本機 http://127.0.0.1:5501(等同雙擊 preview.bat)",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "${workspaceFolder}\\preview.bat",
|
||||||
|
"options": {
|
||||||
|
"cwd": "${workspaceFolder}"
|
||||||
|
},
|
||||||
|
"problemMatcher": [],
|
||||||
|
"presentation": {
|
||||||
|
"reveal": "always",
|
||||||
|
"panel": "dedicated",
|
||||||
|
"focus": false
|
||||||
|
},
|
||||||
|
"runOptions": {
|
||||||
|
"instanceLimit": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
# 水晶男孩推廣部 — AI 工作上下文
|
||||||
|
|
||||||
|
> 這個檔案讓 AI 在任何裝置(家/學校)都能快速恢復上下文。
|
||||||
|
> **每次對話做了重要決定,請叫我更新這裡,然後 git commit。**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 專案簡介
|
||||||
|
|
||||||
|
水晶男孩(Sechs Kies)推廣粉絲網站。靜態 HTML + PHP + 原生 CSS/JS,無框架。
|
||||||
|
站主(Noopy)2016 年入坑,藝廊式創作風格。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 規範速查(詳細見 `developer-rules/rules/`)
|
||||||
|
|
||||||
|
| 規範 | 檔案 |
|
||||||
|
|------|------|
|
||||||
|
| 改檔行為守則(必問再改) | `agent-must-ask.md` |
|
||||||
|
| 變更邊界(只改被點名的) | `ai-change-boundaries.md` |
|
||||||
|
| CSS 顏色 Token | `color-tokens.md` |
|
||||||
|
| 版面視覺預設 | `design-defaults.md` |
|
||||||
|
| Git commit 規範 | `git-commit-required.md` |
|
||||||
|
| 導覽邏輯(portal-nav) | `portal-nav-rules.md` |
|
||||||
|
| 文案語調 | `site-voice.md` |
|
||||||
|
|
||||||
|
**最重要的三條:**
|
||||||
|
1. 沒說「執行」→ 不改檔
|
||||||
|
2. 只改被點名的那一頁/區塊
|
||||||
|
3. 改完必附 commit 指令(禁止在 sandbox 跑 git)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 架構重點
|
||||||
|
|
||||||
|
- **全站導覽**:`assets/js/portal-nav.js`(`COMING_SOON_PAGES` 控制開放)
|
||||||
|
- **全站 CSS**:`assets/css/style.css`(定義所有 color token)
|
||||||
|
- **首頁**:`index.html`
|
||||||
|
- **站主索引**(自用):`extra/site-guide.html`(`data-portal-nav-all-open`,不對外)
|
||||||
|
- **內頁 header class**:`inner-page-header`(禁改回舊名)
|
||||||
|
|
||||||
|
### variety/ 頁面
|
||||||
|
|
||||||
|
| 檔案 | 頁面標題 | 說明 |
|
||||||
|
|------|----------|------|
|
||||||
|
| `variety/bangxiao.html` | 世紀級寶藏 / 爆笑養老院 | 甜蜜負擔計量器;六人卡片選取 + jQuery 計算;CTA → `totoga2/ebook.html` |
|
||||||
|
| `variety/variety-shows.html` | 全員發瘋 | 綜藝節目存檔表;tabs + 搜尋;已對齊 albums.html 扁平結構 |
|
||||||
|
| `variety/NJTW/NJTW5.html` | 新西遊記5 | 各自暴走專頁 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## portal-nav 操作守則(血淚版)
|
||||||
|
|
||||||
|
> 每次新增或修改導覽,必須先讀這裡再動手。
|
||||||
|
|
||||||
|
1. **先查 `extra/site-guide.html` 確認層級**,再動 `portal-nav.js`。
|
||||||
|
site-guide 的 depth 數字 = nav 的層級:depth-1 是第一層 `<li>`,depth-2 是子選單項目,depth-3 是 subgroup 裡的巢狀項目。
|
||||||
|
|
||||||
|
2. **`COMING_SOON_PAGES` 只放檔名**(不含路徑)。
|
||||||
|
`normalizePageKey` 只取 href 最後一段,所以 `variety/bangxiao.html` → 比對 `bangxiao.html`。
|
||||||
|
|
||||||
|
3. **直接連結不需要 subgroup**。
|
||||||
|
子選單內只要 `<li><a>` 就夠;`portal-nav__subgroup` 只在該項目還有巢狀子項目時才用(例如「傳奇的轉折」有時間軸、電子書等四項)。
|
||||||
|
|
||||||
|
4. **site-guide.html 要同步更新**,否則站主索引頁會顯示舊名稱。
|
||||||
|
|
||||||
|
5. **commit 前網站不會更新**。本地端強制重整用 `Ctrl+Shift+R`;GitHub Pages 需 `git push` 後等約 1 分鐘。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 目前工作狀態
|
||||||
|
|
||||||
|
<!-- 每次對話結束後更新這個區塊 -->
|
||||||
|
|
||||||
|
**最後更新:** 2026-06-17
|
||||||
|
|
||||||
|
**進行中:**
|
||||||
|
- 無
|
||||||
|
|
||||||
|
**待確認:**
|
||||||
|
- developer-rules 健檢尚未執行
|
||||||
|
|
||||||
|
**已完成:**
|
||||||
|
- 建立 AGENTS.md 跨裝置上下文檔案
|
||||||
|
- 新增 `variety/bangxiao.html`(世紀級寶藏 / 爆笑養老院,甜蜜負擔計量器)
|
||||||
|
- `variety/variety-shows.html` 重構:移除多層包裝,對齊 albums.html 扁平結構
|
||||||
|
- portal-nav 新增世紀級寶藏,正確放入淪陷瞬間子選單
|
||||||
|
- `extra/site-guide.html` 同步更新:全員發瘋 → 世紀級寶藏
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 如何使用這個檔案
|
||||||
|
|
||||||
|
換裝置後,告訴 AI:
|
||||||
|
> 「請先讀 AGENTS.md,再讀 developer-rules/rules/ 裡的所有規範,然後繼續 [任務]。」
|
||||||
|
|
||||||
|
## Imported Claude Cowork project instructions
|
||||||
|
|
||||||
|
「在這個專案資料夾中,有一個名為 developer-rules 的資料夾。
|
||||||
|
|
||||||
|
這是我們第一次合作,我不確定裡面目前記錄的規範是否完全正確、有沒有被之前亂寫的扣給弄髒。
|
||||||
|
|
||||||
|
請你在初始化專案後,立刻去徹底閱讀 developer-rules 裡的所有內容,並且不要盲目死背。請幫我仔細檢查、挑出裡面是否有前後矛盾、不夠乾淨、或是不符合現代高標準前端美感與 PHP 結構的規範。讀完後,請在我們的第一次對話中,條理分明地向我回報你的健檢結果,並給出優化建議,等我確認後我們再正式鎖定。」
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
/* 隊長的氣場 — 殷志源台北見面會存檔頁(2018-07-21) */
|
||||||
|
|
||||||
|
body.captain-aura-page {
|
||||||
|
background-color: var(--color-bg);
|
||||||
|
background-image: url('../images/concert/captain-aura/bg-digital-placeholder.svg');
|
||||||
|
background-repeat: repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.captain-aura-page .captain-aura-hero {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.captain-aura-page .captain-aura-hero img {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.captain-aura-page .captain-aura-main {
|
||||||
|
max-width: 893px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 50px 20px 72px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.captain-aura-page .captain-aura-act {
|
||||||
|
margin-bottom: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.captain-aura-page .captain-aura-act:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.captain-aura-page .captain-aura-act__title {
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--color-brand);
|
||||||
|
font-size: 1.65rem;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.35;
|
||||||
|
text-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.captain-aura-page .captain-aura-act__title--plain {
|
||||||
|
color: var(--color-text-inverse);
|
||||||
|
text-shadow: none;
|
||||||
|
font-size: 1.35rem;
|
||||||
|
margin-bottom: 0.65rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.captain-aura-page .captain-aura-act__title--plain + .captain-aura-act__title--plain {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.captain-aura-page .captain-aura-divider {
|
||||||
|
margin: 2.75rem 0;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--color-brand);
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.captain-aura-page .captain-aura-videos {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.captain-aura-page .captain-aura-video {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
padding-bottom: 56.25%;
|
||||||
|
background: #000;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.captain-aura-page .captain-aura-video iframe {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
body.captain-aura-page .captain-aura-main {
|
||||||
|
padding-top: 32px;
|
||||||
|
padding-bottom: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.captain-aura-page .captain-aura-act__title {
|
||||||
|
font-size: 1.35rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64" role="img" aria-hidden="true">
|
||||||
|
<rect width="64" height="64" fill="#0d0d0d"/>
|
||||||
|
<path d="M0 32h64M32 0v64" stroke="#1f1f1f" stroke-width="1"/>
|
||||||
|
<circle cx="32" cy="32" r="1.5" fill="#2a2a2a"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 285 B |
@@ -0,0 +1,13 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1920" height="1003" viewBox="0 0 1920 1003" role="img" aria-label="kEG…ÿÛ sli01.png ">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" stop-color="#1a1a1a"/>
|
||||||
|
<stop offset="50%" stop-color="#2a2410"/>
|
||||||
|
<stop offset="100%" stop-color="#0a0a0a"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect width="1920" height="1003" fill="url(#g)"/>
|
||||||
|
<rect x="80" y="80" width="1760" height="843" fill="none" stroke="#ffcc00" stroke-width="4" stroke-dasharray="16 12" opacity="0.45"/>
|
||||||
|
<text x="960" y="470" text-anchor="middle" fill="#ffcc00" font-family="sans-serif" font-size="42" font-weight="700">G1 private · 1theland in Taipei</text>
|
||||||
|
<text x="960" y="540" text-anchor="middle" fill="#f4f4f4" font-family="sans-serif" font-size="28" opacity="0.85">kEG · ËÿÛ hero.jpgŸ sli01.png </text>
|
||||||
|
</svg>
|
||||||
@@ -264,7 +264,7 @@
|
|||||||
<li><a target="_blank" rel="noopener noreferrer" href="${rp}concert/concert.html"><span class="portal-nav__label">現場的震撼</span></a>
|
<li><a target="_blank" rel="noopener noreferrer" href="${rp}concert/concert.html"><span class="portal-nav__label">現場的震撼</span></a>
|
||||||
<ul class="portal-submenu">
|
<ul class="portal-submenu">
|
||||||
<li><a target="_blank" rel="noopener noreferrer" href="${rp}concert/concert.html"><span class="portal-nav__label">集結水晶</span></a></li>
|
<li><a target="_blank" rel="noopener noreferrer" href="${rp}concert/concert.html"><span class="portal-nav__label">集結水晶</span></a></li>
|
||||||
<li><a href="#" data-coming-soon="true" aria-disabled="true" aria-label="隊長的氣場,尚無開放"><span class="portal-nav__label">隊長的氣場</span></a></li>
|
<li><a target="_blank" rel="noopener noreferrer" href="${rp}concert/captain-aura.html"><span class="portal-nav__label">隊長的氣場</span></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li><a target="_blank" rel="noopener noreferrer" href="${rp}albums/albums.html"><span class="portal-nav__label">黃色留聲機</span></a>
|
<li><a target="_blank" rel="noopener noreferrer" href="${rp}albums/albums.html"><span class="portal-nav__label">黃色留聲機</span></a>
|
||||||
|
|||||||
@@ -0,0 +1,300 @@
|
|||||||
|
<!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/captain-aura.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="captain-aura-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">G1 private · 1theland in Taipei · 2018.07.21</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<nav class="portal-nav" aria-label="水晶男孩推廣部導覽" data-portal-nav></nav>
|
||||||
|
<canvas id="particles"></canvas>
|
||||||
|
|
||||||
|
<div class="captain-aura-hero" aria-hidden="true">
|
||||||
|
<img src="../assets/images/concert/captain-aura/hero-placeholder.svg"
|
||||||
|
alt="見面會橫幅假圖(待替換 hero.jpg)" width="1920" height="1003" loading="eager">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<main class="captain-aura-main content-container" id="captain-aura-archive">
|
||||||
|
|
||||||
|
<p class="captain-aura-divider" role="presentation">上半場</p>
|
||||||
|
|
||||||
|
<section class="captain-aura-act" aria-labelledby="act-truck">
|
||||||
|
<h2 class="captain-aura-act__title" id="act-truck">8t. Truck</h2>
|
||||||
|
<div class="captain-aura-videos">
|
||||||
|
<div class="captain-aura-video">
|
||||||
|
<iframe title="8t. Truck ①" src="https://www.youtube.com/embed/phsOnzzHrZs"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen loading="lazy"></iframe>
|
||||||
|
</div>
|
||||||
|
<div class="captain-aura-video">
|
||||||
|
<iframe title="8t. Truck ②" src="https://www.youtube.com/embed/FccoV2LboWI"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen loading="lazy"></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="captain-aura-act" aria-labelledby="act-entry">
|
||||||
|
<h2 class="captain-aura-act__title" id="act-entry">入境表格</h2>
|
||||||
|
<div class="captain-aura-videos">
|
||||||
|
<div class="captain-aura-video">
|
||||||
|
<iframe title="入境表格 ①" src="https://www.youtube.com/embed/R9k6Cs4Rwqc"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen loading="lazy"></iframe>
|
||||||
|
</div>
|
||||||
|
<div class="captain-aura-video">
|
||||||
|
<iframe title="入境表格 ②" src="https://www.youtube.com/embed/tRPHtT0-JkE"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen loading="lazy"></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="captain-aura-act" aria-labelledby="act-fan-q">
|
||||||
|
<h2 class="captain-aura-act__title captain-aura-act__title--plain" id="act-fan-q">粉絲提問</h2>
|
||||||
|
<h2 class="captain-aura-act__title captain-aura-act__title--plain">Taipei in Melody</h2>
|
||||||
|
<h2 class="captain-aura-act__title captain-aura-act__title--plain">G1's Great Moment</h2>
|
||||||
|
<h2 class="captain-aura-act__title" id="act-dance">Dance</h2>
|
||||||
|
<div class="captain-aura-videos">
|
||||||
|
<div class="captain-aura-video">
|
||||||
|
<iframe title="Dance ①" src="https://www.youtube.com/embed/9uOETcuFjbE"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen loading="lazy"></iframe>
|
||||||
|
</div>
|
||||||
|
<div class="captain-aura-video">
|
||||||
|
<iframe title="Dance ②" src="https://www.youtube.com/embed/lOdKgsr9ttU"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen loading="lazy"></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="captain-aura-act" aria-labelledby="act-aegyo">
|
||||||
|
<h2 class="captain-aura-act__title" id="act-aegyo">撒嬌</h2>
|
||||||
|
<div class="captain-aura-videos">
|
||||||
|
<div class="captain-aura-video">
|
||||||
|
<iframe title="撒嬌" src="https://www.youtube.com/embed/S7HJAZ4Kgso"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen loading="lazy"></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="captain-aura-act" aria-labelledby="act-1theland">
|
||||||
|
<h2 class="captain-aura-act__title captain-aura-act__title--plain" id="act-1theland">1 the land</h2>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="captain-aura-act" aria-labelledby="act-ukulele">
|
||||||
|
<h2 class="captain-aura-act__title" id="act-ukulele">烏克麗麗</h2>
|
||||||
|
<div class="captain-aura-videos">
|
||||||
|
<div class="captain-aura-video">
|
||||||
|
<iframe title="烏克麗麗 ①" src="https://www.youtube.com/embed/p5_PiYMsoZc"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen loading="lazy"></iframe>
|
||||||
|
</div>
|
||||||
|
<div class="captain-aura-video">
|
||||||
|
<iframe title="烏克麗麗 ②" src="https://www.youtube.com/embed/7A_0l9wVoNE"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen loading="lazy"></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="captain-aura-act" aria-labelledby="act-couple">
|
||||||
|
<h2 class="captain-aura-act__title" id="act-couple">Couple大合唱</h2>
|
||||||
|
<div class="captain-aura-videos">
|
||||||
|
<div class="captain-aura-video">
|
||||||
|
<iframe title="Couple大合唱 ①" src="https://www.youtube.com/embed/sQ5P3BWL15E"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen loading="lazy"></iframe>
|
||||||
|
</div>
|
||||||
|
<div class="captain-aura-video">
|
||||||
|
<iframe title="Couple大合唱 ②" src="https://www.youtube.com/embed/XZvyS4yDulk"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen loading="lazy"></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="captain-aura-act" aria-labelledby="act-never">
|
||||||
|
<h2 class="captain-aura-act__title" id="act-never">Never, Ever…</h2>
|
||||||
|
<div class="captain-aura-videos">
|
||||||
|
<div class="captain-aura-video">
|
||||||
|
<iframe title="Never, Ever… ①" src="https://www.youtube.com/embed/v8XBssrcM0U"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen loading="lazy"></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<p class="captain-aura-divider" role="presentation">下半場</p>
|
||||||
|
|
||||||
|
<section class="captain-aura-act" aria-labelledby="act-never-2">
|
||||||
|
<div class="captain-aura-videos">
|
||||||
|
<div class="captain-aura-video">
|
||||||
|
<iframe title="Never, Ever… ②" src="https://www.youtube.com/embed/0uQgXToOyiA"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen loading="lazy"></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="captain-aura-act" aria-labelledby="act-drunk">
|
||||||
|
<h2 class="captain-aura-act__title" id="act-drunk">酒醉</h2>
|
||||||
|
<div class="captain-aura-videos">
|
||||||
|
<div class="captain-aura-video">
|
||||||
|
<iframe title="酒醉" src="https://www.youtube.com/embed/6mUl85XOyQw"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen loading="lazy"></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="captain-aura-act" aria-labelledby="act-melody">
|
||||||
|
<h2 class="captain-aura-act__title" id="act-melody">滿醉 in Melody</h2>
|
||||||
|
<div class="captain-aura-videos">
|
||||||
|
<div class="captain-aura-video">
|
||||||
|
<iframe title="滿醉 in Melody ①" src="https://www.youtube.com/embed/EJcpA7bubqM"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen loading="lazy"></iframe>
|
||||||
|
</div>
|
||||||
|
<div class="captain-aura-video">
|
||||||
|
<iframe title="滿醉 in Melody ②" src="https://www.youtube.com/embed/Ovzu07lYVVU"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen loading="lazy"></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="captain-aura-act" aria-labelledby="act-allfamy">
|
||||||
|
<h2 class="captain-aura-act__title" id="act-allfamy">All Famy</h2>
|
||||||
|
<div class="captain-aura-videos">
|
||||||
|
<div class="captain-aura-video">
|
||||||
|
<iframe title="All Famy ①" src="https://www.youtube.com/embed/b6S-CAk-RAA"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen loading="lazy"></iframe>
|
||||||
|
</div>
|
||||||
|
<div class="captain-aura-video">
|
||||||
|
<iframe title="All Famy ②" src="https://www.youtube.com/embed/G8u7D3Xleeo"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen loading="lazy"></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="captain-aura-act" aria-labelledby="act-suddenly">
|
||||||
|
<h2 class="captain-aura-act__title" id="act-suddenly">Suddenly</h2>
|
||||||
|
<div class="captain-aura-videos">
|
||||||
|
<div class="captain-aura-video">
|
||||||
|
<iframe title="Suddenly" src="https://www.youtube.com/embed/a70ojjXoB4M"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen loading="lazy"></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="captain-aura-act" aria-labelledby="act-one">
|
||||||
|
<h2 class="captain-aura-act__title" id="act-one">ONE</h2>
|
||||||
|
<div class="captain-aura-videos">
|
||||||
|
<div class="captain-aura-video">
|
||||||
|
<iframe title="ONE ①" src="https://www.youtube.com/embed/fhgboeZ-J0s"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen loading="lazy"></iframe>
|
||||||
|
</div>
|
||||||
|
<div class="captain-aura-video">
|
||||||
|
<iframe title="ONE ②" src="https://www.youtube.com/embed/CIhAi_gw0fI"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen loading="lazy"></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="captain-aura-act" aria-labelledby="act-encore">
|
||||||
|
<h2 class="captain-aura-act__title" id="act-encore">安可 / Adios</h2>
|
||||||
|
<div class="captain-aura-videos">
|
||||||
|
<div class="captain-aura-video">
|
||||||
|
<iframe title="安可 / Adios ①" src="https://www.youtube.com/embed/Jz3zUZolzjM"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen loading="lazy"></iframe>
|
||||||
|
</div>
|
||||||
|
<div class="captain-aura-video">
|
||||||
|
<iframe title="安可 / Adios ②" src="https://www.youtube.com/embed/ycydd8Bfkb8"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen loading="lazy"></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const canvas = document.getElementById('particles');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
function resizeCanvas() {
|
||||||
|
canvas.width = window.innerWidth;
|
||||||
|
canvas.height = window.innerHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
resizeCanvas();
|
||||||
|
window.addEventListener('resize', resizeCanvas);
|
||||||
|
|
||||||
|
let particles = [];
|
||||||
|
class Particle {
|
||||||
|
constructor() {
|
||||||
|
this.x = Math.random() * canvas.width;
|
||||||
|
this.y = Math.random() * canvas.height;
|
||||||
|
this.size = Math.random() * 2 + 0.5;
|
||||||
|
this.speedX = (Math.random() - 0.5) * 0.3;
|
||||||
|
this.speedY = (Math.random() - 0.5) * 0.3;
|
||||||
|
}
|
||||||
|
update() {
|
||||||
|
this.x += this.speedX;
|
||||||
|
this.y += this.speedY;
|
||||||
|
if (this.x < 0 || this.x > canvas.width) this.speedX *= -1;
|
||||||
|
if (this.y < 0 || this.y > canvas.height) this.speedY *= -1;
|
||||||
|
}
|
||||||
|
draw() {
|
||||||
|
ctx.fillStyle = 'rgba(255, 204, 0, 0.35)';
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initParticles() {
|
||||||
|
particles = [];
|
||||||
|
for (let i = 0; i < 80; i++) particles.push(new Particle());
|
||||||
|
}
|
||||||
|
|
||||||
|
function animate() {
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
for (const p of particles) {
|
||||||
|
p.update();
|
||||||
|
p.draw();
|
||||||
|
}
|
||||||
|
requestAnimationFrame(animate);
|
||||||
|
}
|
||||||
|
|
||||||
|
initParticles();
|
||||||
|
animate();
|
||||||
|
</script>
|
||||||
|
<script src="../assets/js/portal-nav.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -238,10 +238,11 @@
|
|||||||
href="../concert/concert.html"><code>concert/concert.html</code></a></td>
|
href="../concert/concert.html"><code>concert/concert.html</code></a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr data-site-guide-depth="2">
|
<tr data-site-guide-depth="2">
|
||||||
<td class="site-guide-nav-menu-cell"><span class="site-guide-nav-name"><span
|
<td class="site-guide-nav-menu-cell"><span class="site-guide-nav-name"><a
|
||||||
class="site-guide-nav-nolink">隊長的氣場</span></span></td>
|
href="../concert/captain-aura.html">隊長的氣場</a></span></td>
|
||||||
<td class="site-guide-nav-desc">—</td>
|
<td class="site-guide-nav-desc">殷志源 2018 台北見面會存檔;環節標題+ YouTube 嵌入。</td>
|
||||||
<td class="site-guide-nav-link-cell"><span class="site-guide-nav-nolink">—</span></td>
|
<td class="site-guide-nav-link-cell"><a
|
||||||
|
href="../concert/captain-aura.html"><code>concert/captain-aura.html</code></a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr data-site-guide-depth="1">
|
<tr data-site-guide-depth="1">
|
||||||
<td class="site-guide-nav-menu-cell"><span class="site-guide-nav-name"><a
|
<td class="site-guide-nav-menu-cell"><span class="site-guide-nav-name"><a
|
||||||
@@ -305,8 +306,8 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">固定</th>
|
<th scope="row">已開放</th>
|
||||||
<td>「隊長的氣場」固定籌備中(與 Set 無關)</td>
|
<td><code>captain-aura.html</code>(隊長的氣場)</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -415,7 +416,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">隊長的氣場</th>
|
<th scope="row">隊長的氣場</th>
|
||||||
<td>—</td>
|
<td><a href="../concert/captain-aura.html">captain-aura.html</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
@echo off
|
||||||
|
chcp 65001 >nul
|
||||||
|
cd /d "%~dp0"
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ═══════════════════════════════════════
|
||||||
|
echo 水晶男孩推廣部 — 本地預覽
|
||||||
|
echo 網址:http://127.0.0.1:5501
|
||||||
|
echo 關閉此視窗 = 停止預覽
|
||||||
|
echo ═══════════════════════════════════════
|
||||||
|
echo.
|
||||||
|
|
||||||
|
where py >nul 2>&1
|
||||||
|
if %errorlevel%==0 (
|
||||||
|
start "" "http://127.0.0.1:5501/index.html"
|
||||||
|
py -m http.server 5501
|
||||||
|
goto :eof
|
||||||
|
)
|
||||||
|
|
||||||
|
where python >nul 2>&1
|
||||||
|
if %errorlevel%==0 (
|
||||||
|
start "" "http://127.0.0.1:5501/index.html"
|
||||||
|
python -m http.server 5501
|
||||||
|
goto :eof
|
||||||
|
)
|
||||||
|
|
||||||
|
echo [錯誤] 找不到 Python。請安裝 Python 後再試,或使用 Cursor:Ctrl+Shift+P →「預覽網站」
|
||||||
|
pause
|
||||||
@@ -344,6 +344,11 @@
|
|||||||
一部是把 1997 年的青春寫得又甜又疼的<span class="mark_b">韓劇</span>。
|
一部是把 1997 年的青春寫得又甜又疼的<span class="mark_b">韓劇</span>。
|
||||||
點進去,進度與入口都在;先別急著一次追完,慢慢養也很好看。
|
點進去,進度與入口都在;先別急著一次追完,慢慢養也很好看。
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
此頁為線上靜態預覽。完整資料庫與後台 CRUD(乙級第三題骨架)請本機 XAMPP 匯入
|
||||||
|
<code>variety/vedio-list/dramas.sql</code> 後,開啟
|
||||||
|
<a href="vedio-list/"><code>variety/vedio-list/</code></a>。
|
||||||
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="list-section" aria-labelledby="vedio-list-heading">
|
<section class="list-section" aria-labelledby="vedio-list-heading">
|
||||||
|
|||||||
@@ -0,0 +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>
|
||||||
@@ -0,0 +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');
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?php
|
||||||
|
include_once "db.php";
|
||||||
|
|
||||||
|
$table = ${$_POST['table']};
|
||||||
|
$table->del($_POST['id']);
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?php
|
||||||
|
include_once "db.php";
|
||||||
|
|
||||||
|
$row = $Drama->find($_POST['id']);
|
||||||
|
$row['sh'] = ((int)$row['sh'] + 1) % 2;
|
||||||
|
$Drama->save($row);
|
||||||
@@ -0,0 +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);
|
||||||
@@ -0,0 +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>
|
||||||
@@ -0,0 +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&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>
|
||||||
@@ -0,0 +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&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>
|
||||||
@@ -0,0 +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>
|
||||||
@@ -0,0 +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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
-- 深夜清單 / 追劇資料庫
|
||||||
|
-- 本機 XAMPP:phpMyAdmin 匯入此檔即可
|
||||||
|
|
||||||
|
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
|
||||||
|
);
|
||||||
@@ -0,0 +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&id=<?= (int)$row['id'] ?>">更新進度</a>
|
||||||
|
<a href="?do=main">回清單</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
@@ -0,0 +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&type=all">全部</a>
|
||||||
|
<a class="type-tab<?= $filter === 'variety' ? ' is-active' : '' ?>" href="?do=main&type=variety">綜藝</a>
|
||||||
|
<a class="type-tab<?= $filter === 'drama' ? ' is-active' : '' ?>" href="?do=main&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&id=<?= (int)$row['id'] ?>">查看詳情</a>
|
||||||
|
<a href="?do=progress&id=<?= (int)$row['id'] ?>">更新進度</a>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</section>
|
||||||
@@ -0,0 +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&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&id=<?= (int)$row['id'] ?>">取消</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
@@ -0,0 +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 & 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 © 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>
|
||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user