# Conflicts:
#	yellow-note/crystal-dance-survey.html
#	yellow-note/practice-room.html
This commit is contained in:
2026-08-07 23:53:06 +08:00
30 changed files with 118 additions and 3481 deletions
+103
View File
@@ -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 結構的規範。讀完後,請在我們的第一次對話中,條理分明地向我回報你的健檢結果,並給出優化建議,等我確認後我們再正式鎖定。
+6 -7
View File
@@ -72,13 +72,15 @@
<!-- 每次對話結束後更新這個區塊 --> <!-- 每次對話結束後更新這個區塊 -->
**最後更新:** 2026-08-02 **最後更新:** 2026-08-04
**進行中:** **進行中:**
- 水晶熱舞社重啟:30TH 報名與水晶練習室練舞清單已接上後臺;播放器本身仍待加入合法影片來源 -
**待確認:** **待確認:**
- developer-rules 健檢尚未執行 - developer-rules 健檢尚未執行
- `sechskies_fans`(新獨立專案)本機尚未建 MySQL 資料庫、尚未跑過 `admin/setup.php`;Render 部署中,資料庫主機未定
- `portal-nav.js` 新增的「水晶練習室」項目目前是 `data-coming-soon` 佔位(href=`#`),等 sechskies_fans 網址確定後要補上真實外部連結並移除 coming-soon 標記
**歷史定位(文案不可再寫錯):** **歷史定位(文案不可再寫錯):**
- 水晶熱舞社是水晶男孩粉絲自主成立的舞蹈社團,並非水晶男孩推廣部旗下組織 - 水晶熱舞社是水晶男孩粉絲自主成立的舞蹈社團,並非水晶男孩推廣部旗下組織
@@ -92,11 +94,8 @@
- portal-nav 新增世紀級寶藏,正確放入淪陷瞬間子選單 - portal-nav 新增世紀級寶藏,正確放入淪陷瞬間子選單
- `extra/site-guide.html` 同步更新:全員發瘋 → 世紀級寶藏 - `extra/site-guide.html` 同步更新:全員發瘋 → 世紀級寶藏
- 新增 `admin/` 推廣部後臺第一階段:登入、控制台、管理者帳號、通用 CRUD、媒體上傳及公開狀態總覽 - 新增 `admin/` 推廣部後臺第一階段:登入、控制台、管理者帳號、通用 CRUD、媒體上傳及公開狀態總覽
- 水晶熱舞社 30TH 募集表單已接上 PHP/PDO/MySQL,可儲存報名、參與內容與參考檔案 - 水晶熱舞社/水晶練習室/偶像練習生已於 2026-08-04 拆成獨立專案 `sechskies_fans`(另一個 repo,路徑 `d:\Developer\projects\sechskies_fans`,不在本站範圍):30TH 應援報名表單、練舞清單、後台 CRUD 與管理者登入都已搬走並改用自己的資料庫;`extra/site-guide.html` 與本站 `admin/bootstrap.php``admin/database/schema.sql` 已同步移除相關項目
- 後臺新增「30TH 應援報名」清單、搜尋、狀態處理、內部備註與受登入保護的檔案下載 - `portal-nav.js` 頂欄新增第 7 項「水晶練習室」(Yellow Note 之後),連到獨立專案 sechskies_fans`extra/site-guide.html` 同步新增對應項目;目前是 coming-soon 佔位,尚未接上真實網址
- 架構決定:水晶熱舞社暫時共用推廣部後臺;偶像培訓維持獨立資料邊界,之後另拆專案與登入系統
- 水晶練習室的「練舞清單」已改為讀取 `dance_practice_items`,後臺可管理歌曲、分類、練習重點、影片網址、難度、進度、優先順序與公開狀態
- 練舞清單初始資料沿用原頁面的 Couple、Com' Back、Road Fighter;偶像培訓的練習生/課程/紀錄未接入此資料表
--- ---
@@ -1,170 +0,0 @@
# 水晶熱舞社頁面改版對話紀錄
> 來源:`yellow-note/crystal-dance-survey.html` 改版討論
> 日期:2026-07-09
> 用途:可貼上 Notion 的整理稿
---
## 一、任務目標
**水晶熱舞社** 頁(`yellow-note/crystal-dance-survey.html`)從「自創黃框、多層卡片、閱讀擁擠」改成:
1. **對齊首頁/全站簡約風格**(黑底 + 白卡片 + 品牌黃)
2. **多套用全站 CSS**`style.css``section``.section-title``.card` 語彙)
3. **熱舞社相關內容不刪**(可改外觀與排版)
4. **Bootstrap class 必須保留**(作業要求)
5. **表單區參考「見面會應援調查表」的 summer-form 邊框排版**,顏色仍用全站黃色 token
---
## 二、核心原則(這次定下來的)
| 原則 | 說明 |
|------|------|
| 內容優先 | 跟熱舞社相關的資訊不要刪;外觀排版可以改 |
| 只改被點名的頁 | 主要動 `crystal-dance-survey.html` |
| Bootstrap 保留 | `row` / `col` / `form-control` / `form-select` / `btn-check` / `ratio` 等 class 留下 |
| 顏色不動 | 用全站 `--color-brand` 系列,不改成參考圖的藍 |
| 美感對齊 | 參考 index 簡約乾淨 + 參考圖的留白與分階段框線 |
---
## 三、舊版問題診斷
### 為什麼「怎麼看都不順眼」
1. **雙重/三重卡片**
全站 `section` 本身已是圓角白卡片,頁內又加 `video-card``song-card``form-panel` → 盒子套盒子。
2. **約 400 行 inline CSS 重做設計系統**
全站已有 `.card``.section-title`,卻自創多套黃框樣式。
3. **`info-card` 深色漸層突兀**
跟全站暖白、輕盈感相反。
4. **導覽太吵**
五顆黃色 pill 按鈕搶眼;首頁錨點是低調文字列。
5. **UX 問題(後續回饋)**
- 表單左右分欄難讀
- 標題跟內容距離太近
- 主影片寬度沒跟下方三支影片左右對齊
- 文字擠在一起、閱讀節奏差
---
## 四、結構重組(6 區 → 3 區)
### 備份
```
yellow-note/crystal-dance-survey.backup-2026-07-09.html
```
(本地備份;改動前版本)
### 新架構
| 新區塊 | ID | 合併來源 | 合併理由 |
|--------|-----|----------|----------|
| ① 認識水晶熱舞社 | `#about` | Hero + 熱舞社介紹 | 都在回答「這是什麼」 |
| ② 從 20TH 到 30TH | `#from-20th-to-30th` | 20TH 影片回顧 + 30TH 應援募集敘事 | 同一條時間軸:過去記憶 → 未來募集 |
| ③ 30TH 應援募集表單 | `#form-30th` | 表單區獨立 | Bootstrap 表單作業清楚;敘事講完才行動 |
### 移除
- 整個 **經典舞曲區**`#song-archive` 四張 song-card
- 歌曲資訊不丟:表單「想練的歌曲」`<select>` 仍保留 Couple / Com' Back / Road Fighter / Pom Saeng Pom Sa / 其他
---
## 五、視覺與版型決策
### 對齊全站
- 骨架:`inner-page-header``portal-nav``particles``container``section`
- 大區塊靠全站 `section` 白卡片浮在星空底上
- 避免 section 內再套一層 `.card`(影片/表單尤甚)
### 影片區
- 主影片與下方三支延伸影片包在同一 `video-grid`
- **主影片寬度與下方三欄左右切齊**(拿掉主影片單獨 `max-width: 720px` 置中)
### 間距與閱讀
- `.section-title` 與內文拉開(約 `2rem`
- 段落行高約 `1.9`、段間距加大
- 子標題(延伸閱讀、30TH)上方留白加大,避免貼住上一段
---
## 六、表單:summer-form 風格
參考「見面會應援調查表」的**排版結構**,**顏色維持全站黃**:
### 結構
1. **填寫須知**(淡黃底邊框盒)
2. **第一階段|參與者基本資料**(暱稱、Email、練舞年數、可參與日期)
3. **第二階段|30TH 參與意向**20TH 經驗、參與內容複選)
4. **第三階段|應援內容與祝福**(歌曲、上傳、留言)
5. **底部按鈕**(重新填寫 / 送出並排)
### 排版特徵
- 階段標題在邊框盒**上方**、左對齊
- 每階段獨立 `summer-form__stage-body`(圓角細邊框)
- label 在上、輸入在下(單欄垂直流)
- 不用表單左右分欄
- Bootstrap class 全保留
### Class 命名(頁面專用)
- `.summer-form`
- `.summer-form__stage`
- `.summer-form__stage-title`
- `.summer-form__stage-body`
- `.summer-form__stage-body--notice`
- `.summer-form__option-row`
- `.summer-form__actions`
---
## 七、必須保留的 Bootstrap class(作業)
```html
row / col-* / g-*
form-label / form-control / form-select / form-text
btn-check / btn
ratio / ratio-16x9
d-flex / flex-wrap / gap-* / mt-*
```
---
## 八、Git 狀態(對話當時)
- Commit`a977e78`
`refactor: 水晶熱舞社頁面重組為三區並對齊全站簡約風格`
- 已 push 至 `origin/main`(後續還有本地再改:間距、影片對齊、summer-form
- 後續的 summer-form/間距微調若尚未再 commit,需另開 commit
---
## 九、給下次改這頁的檢查清單
- [ ] 熱舞社敘事、20TH 影片、30TH 募集說明、表單欄位是否都在
- [ ] 是否又出現「section 外套 + 內層黃框」雙重卡片
- [ ] 主影片是否與下方三支左右對齊
- [ ] 表單是否單欄分階段(summer-form),沒有左右表單/須知分欄
- [ ] 標題與內文、區塊與區塊是否有足夠留白
- [ ] Bootstrap 必要 class 是否還在
- [ ] 顏色是否仍用 `--color-brand` 系列(沒偷換成藍色)
---
## 十、一句話總結
> **讓 Bootstrap 負責格線與表單元件,讓 `style.css` 負責全站美感;頁面專用 CSS 只補缺口。內容依敘事合三區,表單用 summer-form 分階段邊框,顏色不動。**
-68
View File
@@ -165,22 +165,6 @@ function modules(): array
], ],
'list' => ['display_name', 'media_type', 'category', 'file_path', 'status', 'updated_at'], 'list' => ['display_name', 'media_type', 'category', 'file_path', 'status', 'updated_at'],
], ],
'dance_practices' => [
'label' => '練舞清單', 'item_label' => '練舞項目', 'list_title' => '練舞清單', 'icon' => 'list-check', 'table' => 'dance_practice_items', 'order' => 'sort_order, id', 'group' => 'dance',
'fields' => [
'title' => ['label' => '歌曲名稱', 'type' => 'text', 'required' => true],
'tags' => ['label' => '練習分類', 'type' => 'text'],
'practice_focus' => ['label' => '練習重點', 'type' => 'textarea', 'required' => true],
'video_url' => ['label' => '參考影片網址', 'type' => 'url'],
'difficulty' => ['label' => '難度', 'type' => 'select', 'default' => 'medium', 'required' => true, 'options' => ['easy' => '入門', 'medium' => '中等', 'hard' => '挑戰']],
'progress_status' => ['label' => '練習進度', 'type' => 'select', 'default' => 'not_started', 'required' => true, 'options' => ['not_started' => '未開始', 'practicing' => '練習中', 'review' => '待複習', 'completed' => '已完成']],
'note_text' => ['label' => '補充說明', 'type' => 'text'],
'is_featured' => ['label' => '優先練習', 'type' => 'checkbox'],
'sort_order' => ['label' => '排序', 'type' => 'number', 'default' => '0'],
'status' => ['label' => '公開狀態', 'type' => 'status', 'required' => true],
],
'list' => ['title', 'difficulty', 'progress_status', 'is_featured', 'sort_order', 'status', 'updated_at'],
],
]; ];
} }
@@ -199,51 +183,6 @@ function status_label(string $status): string
return ['draft' => '草稿', 'coming_soon' => '籌備中', 'published' => '公開', 'archived' => '封存', 'available' => '可使用'][$status] ?? $status; return ['draft' => '草稿', 'coming_soon' => '籌備中', 'published' => '公開', 'archived' => '封存', 'available' => '可使用'][$status] ?? $status;
} }
function dance_application_statuses(): array
{
return [
'new' => '新報名',
'contacted' => '已聯絡',
'confirmed' => '確認參與',
'declined' => '暫不參與',
'archived' => '已封存',
];
}
function dance_attendance_label(?string $value): string
{
return [
'attended' => '參加過 20TH',
'watched_video' => '看過 20TH 影片',
'first_time' => '第一次知道',
][$value ?? ''] ?? '未填寫';
}
function dance_song_label(?string $value): string
{
return [
'couple' => 'Couple',
'comeback' => "Com' Back",
'road_fighter' => 'Road Fighter',
'pom_saeng_pom_sa' => 'Pom Saeng Pom Sa',
'other' => '其他',
][$value ?? ''] ?? '未選擇';
}
function dance_content_labels(?string $value): array
{
$labels = [
'cover' => '翻跳',
'medley' => '串燒聯舞',
'blessing' => '祝福影片',
'graphic' => '圖文應援',
'editing' => '影片剪輯',
'archive' => '資料整理',
];
$selected = array_filter(array_map('trim', explode(',', (string) $value)));
return array_map(static fn (string $item): string => $labels[$item] ?? $item, $selected);
}
function audit(string $action, string $entityType, ?int $entityId, array $details = []): void function audit(string $action, string $entityType, ?int $entityId, array $details = []): void
{ {
$stmt = db()->prepare('INSERT INTO audit_logs (admin_user_id, action_name, entity_type, entity_id, details_json, ip_address) VALUES (?, ?, ?, ?, ?, ?)'); $stmt = db()->prepare('INSERT INTO audit_logs (admin_user_id, action_name, entity_type, entity_id, details_json, ip_address) VALUES (?, ?, ?, ?, ?, ?)');
@@ -279,15 +218,8 @@ function render_header(string $title, string $active = ''): void
<a class="<?= $active === 'dashboard' ? 'active' : '' ?>" href="index.php"><i class="bi bi-speedometer2"></i>控制台</a> <a class="<?= $active === 'dashboard' ? 'active' : '' ?>" href="index.php"><i class="bi bi-speedometer2"></i>控制台</a>
<p>內容資料</p> <p>內容資料</p>
<?php foreach ($mods as $key => $mod): ?> <?php foreach ($mods as $key => $mod): ?>
<?php if (($mod['group'] ?? 'content') === 'dance') continue; ?>
<a class="<?= $active === $key ? 'active' : '' ?>" href="records.php?module=<?= h($key) ?>"><i class="bi bi-<?= h($mod['icon']) ?>"></i><?= h($mod['label']) ?></a> <a class="<?= $active === $key ? 'active' : '' ?>" href="records.php?module=<?= h($key) ?>"><i class="bi bi-<?= h($mod['icon']) ?>"></i><?= h($mod['label']) ?></a>
<?php endforeach; ?> <?php endforeach; ?>
<p>水晶熱舞社</p>
<?php foreach ($mods as $key => $mod): ?>
<?php if (($mod['group'] ?? 'content') !== 'dance') continue; ?>
<a class="<?= $active === $key ? 'active' : '' ?>" href="records.php?module=<?= h($key) ?>"><i class="bi bi-<?= h($mod['icon']) ?>"></i><?= h($mod['label']) ?></a>
<?php endforeach; ?>
<a class="<?= $active === 'dance-applications' ? 'active' : '' ?>" href="dance-applications.php"><i class="bi bi-person-hearts"></i>30TH 應援報名</a>
<p>系統</p> <p>系統</p>
<a class="<?= $active === 'publishing' ? 'active' : '' ?>" href="publishing.php"><i class="bi bi-eye"></i>公開狀態</a> <a class="<?= $active === 'publishing' ? 'active' : '' ?>" href="publishing.php"><i class="bi bi-eye"></i>公開狀態</a>
<a class="<?= $active === 'admins' ? 'active' : '' ?>" href="admins.php"><i class="bi bi-shield-lock"></i>管理者帳號</a> <a class="<?= $active === 'admins' ? 'active' : '' ?>" href="admins.php"><i class="bi bi-shield-lock"></i>管理者帳號</a>
-31
View File
@@ -1,31 +0,0 @@
<?php
declare(strict_types=1);
require __DIR__ . '/bootstrap.php';
require_login();
$id = (int) ($_GET['id'] ?? 0);
$stmt = db()->prepare('SELECT reference_file_name, reference_original_name, reference_mime_type FROM dance_applications WHERE id = ?');
$stmt->execute([$id]);
$file = $stmt->fetch();
if (!$file || !$file['reference_file_name']) {
http_response_code(404);
exit('找不到檔案。');
}
$storedName = basename((string) $file['reference_file_name']);
$path = (string) $config['dance_upload_dir'] . DIRECTORY_SEPARATOR . $storedName;
if (!is_file($path)) {
http_response_code(404);
exit('檔案不存在。');
}
$downloadName = basename((string) ($file['reference_original_name'] ?: $storedName));
$downloadName = str_replace(["\r", "\n", '"'], '', $downloadName);
audit('download', 'dance_applications', $id, ['file' => $downloadName]);
header('Content-Type: ' . ((string) $file['reference_mime_type'] ?: 'application/octet-stream'));
header('Content-Length: ' . filesize($path));
header("Content-Disposition: attachment; filename*=UTF-8''" . rawurlencode($downloadName));
header('X-Content-Type-Options: nosniff');
readfile($path);
exit;
-61
View File
@@ -1,61 +0,0 @@
<?php
declare(strict_types=1);
require __DIR__ . '/bootstrap.php';
require_login();
$id = (int) ($_GET['id'] ?? $_POST['id'] ?? 0);
$statusOptions = dance_application_statuses();
$stmt = db()->prepare('SELECT * FROM dance_applications WHERE id = ?');
$stmt->execute([$id]);
$application = $stmt->fetch();
if (!$application) {
http_response_code(404);
exit('找不到這筆報名資料。');
}
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
verify_csrf();
$status = trim((string) ($_POST['status'] ?? ''));
$adminNotes = mb_substr(trim((string) ($_POST['admin_notes'] ?? '')), 0, 5000, 'UTF-8');
if (!isset($statusOptions[$status])) {
$error = '請選擇有效的處理狀態。';
} else {
$update = db()->prepare('UPDATE dance_applications SET status = ?, admin_notes = ? WHERE id = ?');
$update->execute([$status, $adminNotes !== '' ? $adminNotes : null, $id]);
audit('update', 'dance_applications', $id, ['status' => $status, 'admin_notes' => $adminNotes]);
flash('success', '報名資料的處理狀態已更新。');
redirect('dance-application.php?id=' . $id);
}
}
render_header('處理熱舞社報名 #' . $id, 'dance-applications');
?>
<?php if ($error): ?><div class="notice notice--error"><?= h($error) ?></div><?php endif; ?>
<div class="toolbar"><a class="button" href="dance-applications.php"><i class="bi bi-arrow-left"></i>回報名清單</a></div>
<section class="panel application-detail">
<div class="panel__head"><div><h2><?= h($application['nickname']) ?></h2><p>送出於 <?= h($application['created_at']) ?></p></div><span class="status status--<?= h($application['status']) ?>"><?= h($statusOptions[$application['status']] ?? $application['status']) ?></span></div>
<div class="detail-grid">
<div class="detail-item"><span>暱稱</span><strong><?= h($application['nickname']) ?></strong></div>
<div class="detail-item"><span>Email</span><a href="mailto:<?= h($application['email']) ?>"><?= h($application['email']) ?></a></div>
<div class="detail-item"><span>練舞經驗</span><strong><?= $application['dance_years'] !== null ? (int) $application['dance_years'] . ' 年' : '未填寫' ?></strong></div>
<div class="detail-item"><span>可參與日期</span><strong><?= h($application['available_date'] ?: '未填寫') ?></strong></div>
<div class="detail-item"><span>20TH 經驗</span><strong><?= h(dance_attendance_label($application['attended_20th'])) ?></strong></div>
<div class="detail-item"><span>想練的歌曲</span><strong><?= h(dance_song_label($application['song'])) ?></strong></div>
<div class="detail-item detail-item--wide"><span>想參與的內容</span><strong><?= h(implode('、', dance_content_labels($application['participate_content']))) ?></strong></div>
<div class="detail-item detail-item--wide"><span>30TH 留言</span><div class="detail-value"><?= $application['message_30th'] ? nl2br(h($application['message_30th'])) : '未填寫' ?></div></div>
<div class="detail-item detail-item--wide"><span>參考檔案</span><?php if ($application['reference_file_name']): ?><a class="button detail-download" href="dance-application-file.php?id=<?= $id ?>"><i class="bi bi-download"></i><?= h($application['reference_original_name'] ?: '下載檔案') ?><?php if ($application['reference_file_size']): ?><?= h(number_format((int) $application['reference_file_size'] / 1024 / 1024, 2)) ?> MB<?php endif; ?></a><?php else: ?><strong>未上傳</strong><?php endif; ?></div>
</div>
</section>
<form class="panel editor" method="post">
<input type="hidden" name="csrf" value="<?= h(csrf_token()) ?>">
<input type="hidden" name="id" value="<?= $id ?>">
<div class="form-grid">
<label class="field"><span>處理狀態 *</span><select name="status" required><?php foreach ($statusOptions as $value => $label): ?><option value="<?= h($value) ?>" <?= $application['status'] === $value ? 'selected' : '' ?>><?= h($label) ?></option><?php endforeach; ?></select></label>
<label class="field field--wide"><span>內部備註</span><textarea name="admin_notes" rows="6" maxlength="5000" placeholder="例如:已寄信、等待確認練習日期……"><?= h((string) $application['admin_notes']) ?></textarea></label>
</div>
<div class="form-actions"><a class="button" href="dance-applications.php">取消</a><button class="button button--primary" type="submit">儲存處理狀態</button></div>
</form>
<?php render_footer();
-84
View File
@@ -1,84 +0,0 @@
<?php
declare(strict_types=1);
require __DIR__ . '/bootstrap.php';
require_login();
$page = max(1, (int) ($_GET['page'] ?? 1));
$perPage = 15;
$query = trim((string) ($_GET['q'] ?? ''));
$status = trim((string) ($_GET['status'] ?? ''));
$statusOptions = dance_application_statuses();
$where = [];
$params = [];
if ($query !== '') {
$where[] = '(nickname LIKE ? OR email LIKE ? OR message_30th LIKE ?)';
$searchValue = '%' . $query . '%';
$params = [$searchValue, $searchValue, $searchValue];
}
if ($status !== '' && isset($statusOptions[$status])) {
$where[] = 'status = ?';
$params[] = $status;
} else {
$status = '';
}
$whereSql = $where ? ' WHERE ' . implode(' AND ', $where) : '';
$countStmt = db()->prepare('SELECT COUNT(*) FROM dance_applications' . $whereSql);
$countStmt->execute($params);
$total = (int) $countStmt->fetchColumn();
$pages = max(1, (int) ceil($total / $perPage));
$page = min($page, $pages);
$offset = ($page - 1) * $perPage;
$stmt = db()->prepare('SELECT * FROM dance_applications' . $whereSql . " ORDER BY created_at DESC, id DESC LIMIT $perPage OFFSET $offset");
$stmt->execute($params);
$rows = $stmt->fetchAll();
render_header('30TH 應援報名', 'dance-applications');
?>
<div class="toolbar">
<form class="filters" method="get">
<label class="search"><i class="bi bi-search"></i><input name="q" value="<?= h($query) ?>" placeholder="搜尋暱稱、Email 或留言"></label>
<select name="status" aria-label="處理狀態">
<option value="">全部狀態</option>
<?php foreach ($statusOptions as $value => $label): ?>
<option value="<?= h($value) ?>" <?= $status === $value ? 'selected' : '' ?>><?= h($label) ?></option>
<?php endforeach; ?>
</select>
<button class="button" type="submit">篩選</button>
</form>
<a class="button" href="../yellow-note/crystal-dance-survey.html#form-30th" target="_blank" rel="noopener"><i class="bi bi-box-arrow-up-right"></i>查看募集表單</a>
</div>
<section class="panel">
<div class="panel__head"><div><h2>報名清單</h2><p>共 <?= $total ?> 筆;Email 與上傳檔案僅供企劃聯絡使用</p></div></div>
<div class="table-wrap">
<table>
<thead><tr><th>編號</th><th>暱稱</th><th>Email</th><th>想參與</th><th>歌曲</th><th>狀態</th><th>送出時間</th><th>操作</th></tr></thead>
<tbody>
<?php foreach ($rows as $row): ?>
<tr>
<td>#<?= (int) $row['id'] ?></td>
<td><?= h($row['nickname']) ?></td>
<td><?= h(mb_strimwidth((string) $row['email'], 0, 34, '…', 'UTF-8')) ?></td>
<td><?= h(implode('、', dance_content_labels($row['participate_content']))) ?></td>
<td><?= h(dance_song_label($row['song'])) ?></td>
<td><span class="status status--<?= h($row['status']) ?>"><?= h($statusOptions[$row['status']] ?? $row['status']) ?></span></td>
<td><?= h($row['created_at']) ?></td>
<td class="actions"><a href="dance-application.php?id=<?= (int) $row['id'] ?>">查看/處理</a></td>
</tr>
<?php endforeach; ?>
<?php if (!$rows): ?><tr><td colspan="8" class="empty">目前沒有符合條件的報名資料。</td></tr><?php endif; ?>
</tbody>
</table>
</div>
<?php if ($pages > 1): ?>
<nav class="pagination" aria-label="報名資料分頁">
<?php for ($i = 1; $i <= $pages; $i++): ?>
<a class="<?= $i === $page ? 'active' : '' ?>" href="?q=<?= urlencode($query) ?>&status=<?= urlencode($status) ?>&page=<?= $i ?>"><?= $i ?></a>
<?php endfor; ?>
</nav>
<?php endif; ?>
</section>
<?php render_footer();
-57
View File
@@ -115,63 +115,6 @@ CREATE TABLE IF NOT EXISTS locations (
CONSTRAINT fk_location_media FOREIGN KEY (media_id) REFERENCES media_assets(id) ON DELETE SET NULL CONSTRAINT fk_location_media FOREIGN KEY (media_id) REFERENCES media_assets(id) ON DELETE SET NULL
) ENGINE=InnoDB; ) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS dance_applications (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
nickname VARCHAR(80) NOT NULL,
email VARCHAR(190) NOT NULL,
dance_years TINYINT UNSIGNED NULL,
available_date DATE NULL,
attended_20th ENUM('attended','watched_video','first_time') NULL,
participate_content VARCHAR(255) NOT NULL,
song VARCHAR(80) NULL,
reference_file_name VARCHAR(100) NULL,
reference_original_name VARCHAR(255) NULL,
reference_mime_type VARCHAR(100) NULL,
reference_file_size INT UNSIGNED NULL,
message_30th TEXT NULL,
status ENUM('new','contacted','confirmed','declined','archived') NOT NULL DEFAULT 'new',
admin_notes TEXT NULL,
submitted_ip VARCHAR(45) NULL,
user_agent VARCHAR(255) NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_dance_applications_status_created (status, created_at),
INDEX idx_dance_applications_email (email)
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS dance_practice_items (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(160) NOT NULL,
tags VARCHAR(255) NULL,
practice_focus TEXT NOT NULL,
video_url VARCHAR(500) NULL,
difficulty ENUM('easy','medium','hard') NOT NULL DEFAULT 'medium',
progress_status ENUM('not_started','practicing','review','completed') NOT NULL DEFAULT 'not_started',
note_text VARCHAR(255) NULL,
is_featured TINYINT(1) NOT NULL DEFAULT 0,
sort_order INT NOT NULL DEFAULT 0,
status ENUM('draft','coming_soon','published','archived') NOT NULL DEFAULT 'draft',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_dance_practice_order (status, sort_order, id),
INDEX idx_dance_practice_title (title)
) ENGINE=InnoDB;
INSERT INTO dance_practice_items
(title, tags, practice_focus, difficulty, progress_status, note_text, is_featured, sort_order, status)
SELECT 'Couple', 'Dance,Vocal', '副歌動作、隊形感、甜感表情', 'easy', 'not_started', '可作為第一首示範歌', 1, 10, 'published'
WHERE NOT EXISTS (SELECT 1 FROM dance_practice_items WHERE title = 'Couple');
INSERT INTO dance_practice_items
(title, tags, practice_focus, difficulty, progress_status, note_text, is_featured, sort_order, status)
SELECT 'Com'' Back', 'Dance,Rap', '強拍、Rap 段落、舞台氣勢', 'medium', 'not_started', '未來擴充', 0, 20, 'coming_soon'
WHERE NOT EXISTS (SELECT 1 FROM dance_practice_items WHERE title = 'Com'' Back');
INSERT INTO dance_practice_items
(title, tags, practice_focus, difficulty, progress_status, note_text, is_featured, sort_order, status)
SELECT 'Road Fighter', 'Dance,Rap,Performance', '節奏、力量、舞台感', 'hard', 'not_started', '未來擴充', 0, 30, 'coming_soon'
WHERE NOT EXISTS (SELECT 1 FROM dance_practice_items WHERE title = 'Road Fighter');
CREATE TABLE IF NOT EXISTS audit_logs ( CREATE TABLE IF NOT EXISTS audit_logs (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
admin_user_id INT UNSIGNED NULL, admin_user_id INT UNSIGNED NULL,
+3 -2
View File
@@ -234,7 +234,7 @@
} }
/** 全站導覽 HTML — `rp` 為站根相對前綴;僅修改此模板即可同步所有頁面 /** 全站導覽 HTML — `rp` 為站根相對前綴;僅修改此模板即可同步所有頁面
* 主項:淪陷瞬間、完整的六顆、現場的震撼、黃色留聲機、世紀級寶藏、Yellow Note。 */ * 主項:淪陷瞬間、完整的六顆、現場的震撼、黃色留聲機、世紀級寶藏、Yellow Note、水晶練習室(外站 sechskies_fans,籌備中)。 */
function buildPortalNavInnerHTML(rp) { function buildPortalNavInnerHTML(rp) {
return ` return `
<a class="logo" target="_blank" rel="noopener noreferrer" href="${rp}index.html"><img src="${rp}assets/images/logov.svg" alt="SECHSKIES Logo"></a> <a class="logo" target="_blank" rel="noopener noreferrer" href="${rp}index.html"><img src="${rp}assets/images/logov.svg" alt="SECHSKIES Logo"></a>
@@ -257,9 +257,9 @@
<li><a target="_blank" rel="noopener noreferrer" href="${rp}totoga2/video.html"><span class="portal-nav__label">影片</span></a></li> <li><a target="_blank" rel="noopener noreferrer" href="${rp}totoga2/video.html"><span class="portal-nav__label">影片</span></a></li>
</ul> </ul>
</li> </li>
<li><a target="_blank" rel="noopener noreferrer" href="${rp}variety/bangxiao.html"><span class="portal-nav__label">世紀級寶藏</span></a></li>
</ul> </ul>
</li> </li>
<li><a target="_blank" rel="noopener noreferrer" href="${rp}variety/bangxiao.html"><span class="portal-nav__label">世紀級寶藏</span></a></li>
<li><a target="_blank" rel="noopener noreferrer" href="${rp}member/member.html"><span class="portal-nav__label">完整的六顆</span></a></li> <li><a target="_blank" rel="noopener noreferrer" href="${rp}member/member.html"><span class="portal-nav__label">完整的六顆</span></a></li>
<li><a target="_blank" rel="noopener noreferrer" href="${rp}concert/concert.html"><span class="portal-nav__label">現場的震撼</span></a> <li><a target="_blank" rel="noopener noreferrer" href="${rp}concert/concert.html"><span class="portal-nav__label">現場的震撼</span></a>
<ul class="portal-submenu"> <ul class="portal-submenu">
@@ -279,6 +279,7 @@
<li><a target="_blank" rel="noopener noreferrer" href="${rp}yellow-note/map.html"><span class="portal-nav__label">聖地巡禮</span></a></li> <li><a target="_blank" rel="noopener noreferrer" href="${rp}yellow-note/map.html"><span class="portal-nav__label">聖地巡禮</span></a></li>
</ul> </ul>
</li> </li>
<li><a href="#" data-coming-soon="true" aria-disabled="true" aria-label="水晶練習室,尚無開放"><span class="portal-nav__label">水晶練習室</span></a></li>
</ul> </ul>
`.trim(); `.trim();
} }
+6 -3
View File
@@ -143,6 +143,9 @@
</li> </li>
</ul> </ul>
</li> </li>
<li data-site-guide-depth="1">
<span class="sg-node sg-node--extra sg-node--nolink" title="外站 sechskies_fans,籌備中(href=# data-coming-soon),尚未接上真實網址">水晶練習室(籌備中,外部連結待 sechskies_fans 部署完成後補上)</span>
</li>
<li data-site-guide-depth="1"> <li data-site-guide-depth="1">
<span class="sg-node sg-node--extra sg-node--nolink">站內其他頁面(草稿/作業/備查,不在頂欄)</span> <span class="sg-node sg-node--extra sg-node--nolink">站內其他頁面(草稿/作業/備查,不在頂欄)</span>
<ul> <ul>
@@ -165,13 +168,13 @@
<li data-site-guide-depth="3"><span class="sg-node sg-node--extra"><a href="../s20260612_04_jq_calculator.html">s20260612_04_jq_calculator.html(學校作業)</a></span></li> <li data-site-guide-depth="3"><span class="sg-node sg-node--extra"><a href="../s20260612_04_jq_calculator.html">s20260612_04_jq_calculator.html(學校作業)</a></span></li>
<li data-site-guide-depth="3"><span class="sg-node sg-node--extra"><a href="../sechskies_brand_color_spec.html">sechskies_brand_color_spec.html(品牌色規範)</a></span></li> <li data-site-guide-depth="3"><span class="sg-node sg-node--extra"><a href="../sechskies_brand_color_spec.html">sechskies_brand_color_spec.html(品牌色規範)</a></span></li>
<li data-site-guide-depth="3"><span class="sg-node sg-node--extra"><a href="../totoga2/readable_ebookcontent.html">totoga2/readable_ebookcontent.html(電子書內容草稿)</a></span></li> <li data-site-guide-depth="3"><span class="sg-node sg-node--extra"><a href="../totoga2/readable_ebookcontent.html">totoga2/readable_ebookcontent.html(電子書內容草稿)</a></span></li>
<li data-site-guide-depth="3"><span class="sg-node sg-node--extra"><a href="../yellow-note/crystal-dance-survey.html">yellow-note/crystal-dance-survey.html(問卷頁)</a></span></li>
<li data-site-guide-depth="3"><span class="sg-node sg-node--extra"><a href="../variety/vedio-list.html">variety/vedio-list.html(深夜清單,追劇進度預覽頁)</a></span></li> <li data-site-guide-depth="3"><span class="sg-node sg-node--extra"><a href="../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" 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="../yellow-note/practice-room.html">yellow-note/practice-room.html(水晶練習室,舞蹈練習播放器)</a></span></li>
</ul> </ul>
</li> </li>
<li data-site-guide-depth="2"><span class="sg-node sg-node--extra"><a href="../sechskies-admin-hw/index.html">sechskies-admin-hw/(另一份學校作業,非本站,與上方水晶練習室頁面無關)</a></span></li> <li data-site-guide-depth="2">
<span class="sg-node sg-node--extra sg-node--nolink" title="2026-08-04 拆出,另一個 repo,不在本站範圍">水晶熱舞社/水晶練習室/偶像練習生 → 已拆成獨立專案 sechskies_fans</span>
</li>
<li data-site-guide-depth="2"> <li data-site-guide-depth="2">
<span class="sg-node sg-node--extra sg-node--nolink" title="2026-05-16 15:56 備份,基準 commit fa8b04d,內容為當時尚未提交的修改與未追蹤新檔">待確認/(舊備份快照,滑鼠移入看說明)</span> <span class="sg-node sg-node--extra sg-node--nolink" title="2026-05-16 15:56 備份,基準 commit fa8b04d,內容為當時尚未提交的修改與未追蹤新檔">待確認/(舊備份快照,滑鼠移入看說明)</span>
<ul> <ul>
-133
View File
@@ -1,133 +0,0 @@
<!doctype html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>後台首頁|水晶練習室 Admin</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet">
<link href="../css/style.css" rel="stylesheet">
</head>
<body>
<!-- 後台上方 Navbar -->
<nav class="navbar navbar-expand navbar-dark sk-navbar">
<div class="container-fluid">
<span class="navbar-brand mb-0"><i class="bi bi-gem me-1"></i>水晶練習室 <span class="fw-light">Admin</span></span>
<div class="d-flex gap-2">
<a class="btn btn-brand btn-sm" href="../index.html">回前台</a>
<a class="btn btn-outline-brand btn-sm" href="../index.html">登出</a>
</div>
</div>
</nav>
<div class="container-fluid">
<div class="row">
<!-- 左側管理選單 -->
<aside class="col-lg-2 border-end sk-admin-sidebar py-4 px-3 min-vh-100">
<nav class="nav flex-column">
<a class="nav-link active" href="index.html"><i class="bi bi-speedometer2 me-2"></i>後台首頁</a>
<a class="nav-link disabled" href="#" aria-disabled="true"><i class="bi bi-people me-2"></i>練習生管理</a>
<a class="nav-link disabled" href="#" aria-disabled="true"><i class="bi bi-mortarboard me-2"></i>課程管理</a>
<a class="nav-link" href="practices/index.html"><i class="bi bi-journal-text me-2"></i>練習紀錄管理</a>
<a class="nav-link" href="songs/index.html"><i class="bi bi-music-note-list me-2"></i>歌曲資料管理</a>
<a class="nav-link disabled" href="#" aria-disabled="true"><i class="bi bi-list-task me-2"></i>訓練任務管理</a>
</nav>
</aside>
<!-- 主內容 -->
<main class="col-lg-10 py-4 px-4">
<h1 class="h3 mb-4 text-white">後台首頁</h1>
<div class="row g-4 mb-5">
<div class="col-sm-6 col-xl-3">
<div class="card sk-card shadow-sm h-100 p-3">
<div class="card-body">
<i class="bi bi-people fs-3 text-white"></i>
<div class="text-body-secondary small mt-2 mb-1">練習生人數</div>
<div class="sk-stat-value">6</div>
</div>
</div>
</div>
<div class="col-sm-6 col-xl-3">
<div class="card sk-card shadow-sm h-100 p-3">
<div class="card-body">
<i class="bi bi-lightning-charge fs-3 text-white"></i>
<div class="text-body-secondary small mt-2 mb-1">今日練習次數</div>
<div class="sk-stat-value">9</div>
</div>
</div>
</div>
<div class="col-sm-6 col-xl-3">
<div class="card sk-card shadow-sm h-100 p-3">
<div class="card-body">
<i class="bi bi-mortarboard fs-3 text-white"></i>
<div class="text-body-secondary small mt-2 mb-1">課程數量</div>
<div class="sk-stat-value">8</div>
</div>
</div>
</div>
<div class="col-sm-6 col-xl-3">
<div class="card sk-card shadow-sm h-100 p-3">
<div class="card-body">
<i class="bi bi-check2-circle fs-3 text-white"></i>
<div class="text-body-secondary small mt-2 mb-1">已完成任務數</div>
<div class="sk-stat-value">23</div>
</div>
</div>
</div>
</div>
<h2 class="sk-section-title"><i class="bi bi-journal-text"></i>最近練習紀錄</h2>
<div class="card sk-card sk-surface">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead>
<tr>
<th>練習生</th>
<th>日期</th>
<th>訓練類型</th>
<th>歌曲</th>
<th>練習時間</th>
<th>完成度</th>
</tr>
</thead>
<tbody>
<tr>
<td>努比</td>
<td>2026-07-16</td>
<td><span class="badge badge-type badge-type--dance">舞蹈</span></td>
<td>Road Fighter</td>
<td>40 分鐘</td>
<td>85%</td>
</tr>
<tr>
<td>昇材</td>
<td>2026-07-16</td>
<td><span class="badge badge-type badge-type--vocal">vocal</span></td>
<td>Couple</td>
<td>35 分鐘</td>
<td>78%</td>
</tr>
<tr>
<td>水院</td>
<td>2026-07-15</td>
<td><span class="badge badge-type badge-type--rap">Rap</span></td>
<td>Chivalry</td>
<td>25 分鐘</td>
<td>60%</td>
</tr>
</tbody>
</table>
</div>
</div>
</main>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
@@ -1,119 +0,0 @@
<!doctype html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>新增練習紀錄|水晶練習室 Admin</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet">
<link href="../../css/style.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand navbar-dark sk-navbar">
<div class="container-fluid">
<span class="navbar-brand mb-0"><i class="bi bi-gem me-1"></i>水晶練習室 <span class="fw-light">Admin</span></span>
<div class="d-flex gap-2">
<a class="btn btn-brand btn-sm" href="../../index.html">回前台</a>
<a class="btn btn-outline-brand btn-sm" href="../../index.html">登出</a>
</div>
</div>
</nav>
<div class="container-fluid">
<div class="row">
<aside class="col-lg-2 border-end sk-admin-sidebar py-4 px-3 min-vh-100">
<nav class="nav flex-column">
<a class="nav-link" href="../index.html"><i class="bi bi-speedometer2 me-2"></i>後台首頁</a>
<a class="nav-link disabled" href="#" aria-disabled="true"><i class="bi bi-people me-2"></i>練習生管理</a>
<a class="nav-link disabled" href="#" aria-disabled="true"><i class="bi bi-mortarboard me-2"></i>課程管理</a>
<a class="nav-link active" href="index.html"><i class="bi bi-journal-text me-2"></i>練習紀錄管理</a>
<a class="nav-link" href="../songs/index.html"><i class="bi bi-music-note-list me-2"></i>歌曲資料管理</a>
<a class="nav-link disabled" href="#" aria-disabled="true"><i class="bi bi-list-task me-2"></i>訓練任務管理</a>
</nav>
</aside>
<main class="col-lg-10 py-4 px-4">
<h1 class="h3 mb-4 text-white">新增練習紀錄</h1>
<div class="card sk-card sk-surface p-4" style="max-width: 42rem;">
<form>
<div class="row g-3">
<div class="col-md-6">
<label for="traineeName" class="form-label">練習生</label>
<select class="form-select" id="traineeName">
<option>努比</option>
<option>昇材</option>
<option>宰鎮</option>
<option>水院</option>
</select>
</div>
<div class="col-md-6">
<label for="practiceDate" class="form-label">日期</label>
<input type="date" class="form-control" id="practiceDate">
</div>
<div class="col-md-6">
<label for="practiceType" class="form-label">訓練類型</label>
<select class="form-select" id="practiceType">
<option>vocal</option>
<option>舞蹈</option>
<option>Rap</option>
<option>舞台表現</option>
<option>體能</option>
</select>
</div>
<div class="col-md-6">
<label for="practiceSong" class="form-label">歌曲</label>
<select class="form-select" id="practiceSong">
<option>Couple</option>
<option>Road Fighter</option>
<option>Com' Back</option>
<option>Chivalry</option>
<option>The Way This Guy Lives</option>
</select>
</div>
<div class="col-md-6">
<label for="practiceMinutes" class="form-label">練習時間(分鐘)</label>
<input type="number" class="form-control" id="practiceMinutes" placeholder="例如:40">
</div>
<div class="col-md-6">
<label for="practiceStatus" class="form-label">完成狀態</label>
<select class="form-select" id="practiceStatus">
<option>未開始</option>
<option>進行中</option>
<option>已完成</option>
</select>
</div>
<div class="col-12">
<label for="practiceCompletion" class="form-label d-flex justify-content-between">
<span>完成度</span>
<span class="text-body-secondary" id="completionValue">50%</span>
</label>
<input type="range" class="form-range" id="practiceCompletion" min="0" max="100" value="50"
oninput="document.getElementById('completionValue').textContent = this.value + '%'">
</div>
</div>
<div class="d-flex gap-2 mt-4">
<button type="submit" class="btn btn-brand">儲存</button>
<a class="btn btn-outline-brand" href="index.html">取消</a>
</div>
</form>
</div>
</main>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
@@ -1,68 +0,0 @@
<!doctype html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>刪除練習紀錄|水晶練習室 Admin</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet">
<link href="../../css/style.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand navbar-dark sk-navbar">
<div class="container-fluid">
<span class="navbar-brand mb-0"><i class="bi bi-gem me-1"></i>水晶練習室 <span class="fw-light">Admin</span></span>
<div class="d-flex gap-2">
<a class="btn btn-brand btn-sm" href="../../index.html">回前台</a>
<a class="btn btn-outline-brand btn-sm" href="../../index.html">登出</a>
</div>
</div>
</nav>
<div class="container-fluid">
<div class="row">
<aside class="col-lg-2 border-end sk-admin-sidebar py-4 px-3 min-vh-100">
<nav class="nav flex-column">
<a class="nav-link" href="../index.html"><i class="bi bi-speedometer2 me-2"></i>後台首頁</a>
<a class="nav-link disabled" href="#" aria-disabled="true"><i class="bi bi-people me-2"></i>練習生管理</a>
<a class="nav-link disabled" href="#" aria-disabled="true"><i class="bi bi-mortarboard me-2"></i>課程管理</a>
<a class="nav-link active" href="index.html"><i class="bi bi-journal-text me-2"></i>練習紀錄管理</a>
<a class="nav-link" href="../songs/index.html"><i class="bi bi-music-note-list me-2"></i>歌曲資料管理</a>
<a class="nav-link disabled" href="#" aria-disabled="true"><i class="bi bi-list-task me-2"></i>訓練任務管理</a>
</nav>
</aside>
<main class="col-lg-10 py-4 px-4">
<h1 class="h3 mb-4 text-white">刪除練習紀錄</h1>
<div class="card sk-card sk-surface p-4" style="max-width: 32rem;">
<div class="alert alert-danger mb-4" role="alert">
<i class="bi bi-exclamation-triangle-fill me-1"></i>確定要刪除這筆練習紀錄嗎?此動作無法復原。
</div>
<dl class="row mb-4">
<dt class="col-4 text-body-secondary">練習生</dt>
<dd class="col-8">努比</dd>
<dt class="col-4 text-body-secondary">日期</dt>
<dd class="col-8">2026-07-16</dd>
<dt class="col-4 text-body-secondary">訓練類型</dt>
<dd class="col-8">舞蹈</dd>
<dt class="col-4 text-body-secondary">歌曲</dt>
<dd class="col-8">Road Fighter</dd>
</dl>
<div class="d-flex gap-2">
<button type="button" class="btn btn-danger">確認刪除</button>
<a class="btn btn-outline-brand" href="index.html">取消</a>
</div>
</div>
</main>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
@@ -1,119 +0,0 @@
<!doctype html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>編輯練習紀錄|水晶練習室 Admin</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet">
<link href="../../css/style.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand navbar-dark sk-navbar">
<div class="container-fluid">
<span class="navbar-brand mb-0"><i class="bi bi-gem me-1"></i>水晶練習室 <span class="fw-light">Admin</span></span>
<div class="d-flex gap-2">
<a class="btn btn-brand btn-sm" href="../../index.html">回前台</a>
<a class="btn btn-outline-brand btn-sm" href="../../index.html">登出</a>
</div>
</div>
</nav>
<div class="container-fluid">
<div class="row">
<aside class="col-lg-2 border-end sk-admin-sidebar py-4 px-3 min-vh-100">
<nav class="nav flex-column">
<a class="nav-link" href="../index.html"><i class="bi bi-speedometer2 me-2"></i>後台首頁</a>
<a class="nav-link disabled" href="#" aria-disabled="true"><i class="bi bi-people me-2"></i>練習生管理</a>
<a class="nav-link disabled" href="#" aria-disabled="true"><i class="bi bi-mortarboard me-2"></i>課程管理</a>
<a class="nav-link active" href="index.html"><i class="bi bi-journal-text me-2"></i>練習紀錄管理</a>
<a class="nav-link" href="../songs/index.html"><i class="bi bi-music-note-list me-2"></i>歌曲資料管理</a>
<a class="nav-link disabled" href="#" aria-disabled="true"><i class="bi bi-list-task me-2"></i>訓練任務管理</a>
</nav>
</aside>
<main class="col-lg-10 py-4 px-4">
<h1 class="h3 mb-4 text-white">編輯練習紀錄</h1>
<div class="card sk-card sk-surface p-4" style="max-width: 42rem;">
<form>
<div class="row g-3">
<div class="col-md-6">
<label for="traineeName" class="form-label">練習生</label>
<select class="form-select" id="traineeName">
<option selected>努比</option>
<option>昇材</option>
<option>宰鎮</option>
<option>水院</option>
</select>
</div>
<div class="col-md-6">
<label for="practiceDate" class="form-label">日期</label>
<input type="date" class="form-control" id="practiceDate" value="2026-07-16">
</div>
<div class="col-md-6">
<label for="practiceType" class="form-label">訓練類型</label>
<select class="form-select" id="practiceType">
<option>vocal</option>
<option selected>舞蹈</option>
<option>Rap</option>
<option>舞台表現</option>
<option>體能</option>
</select>
</div>
<div class="col-md-6">
<label for="practiceSong" class="form-label">歌曲</label>
<select class="form-select" id="practiceSong">
<option>Couple</option>
<option selected>Road Fighter</option>
<option>Com' Back</option>
<option>Chivalry</option>
<option>The Way This Guy Lives</option>
</select>
</div>
<div class="col-md-6">
<label for="practiceMinutes" class="form-label">練習時間(分鐘)</label>
<input type="number" class="form-control" id="practiceMinutes" value="40">
</div>
<div class="col-md-6">
<label for="practiceStatus" class="form-label">完成狀態</label>
<select class="form-select" id="practiceStatus">
<option>未開始</option>
<option>進行中</option>
<option selected>已完成</option>
</select>
</div>
<div class="col-12">
<label for="practiceCompletion" class="form-label d-flex justify-content-between">
<span>完成度</span>
<span class="text-body-secondary" id="completionValue">85%</span>
</label>
<input type="range" class="form-range" id="practiceCompletion" min="0" max="100" value="85"
oninput="document.getElementById('completionValue').textContent = this.value + '%'">
</div>
</div>
<div class="d-flex gap-2 mt-4">
<button type="submit" class="btn btn-brand">儲存變更</button>
<a class="btn btn-outline-brand" href="index.html">取消</a>
</div>
</form>
</div>
</main>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
@@ -1,125 +0,0 @@
<!doctype html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>練習紀錄管理|水晶練習室 Admin</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet">
<link href="../../css/style.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand navbar-dark sk-navbar">
<div class="container-fluid">
<span class="navbar-brand mb-0"><i class="bi bi-gem me-1"></i>水晶練習室 <span class="fw-light">Admin</span></span>
<div class="d-flex gap-2">
<a class="btn btn-brand btn-sm" href="../../index.html">回前台</a>
<a class="btn btn-outline-brand btn-sm" href="../../index.html">登出</a>
</div>
</div>
</nav>
<div class="container-fluid">
<div class="row">
<aside class="col-lg-2 border-end sk-admin-sidebar py-4 px-3 min-vh-100">
<nav class="nav flex-column">
<a class="nav-link" href="../index.html"><i class="bi bi-speedometer2 me-2"></i>後台首頁</a>
<a class="nav-link disabled" href="#" aria-disabled="true"><i class="bi bi-people me-2"></i>練習生管理</a>
<a class="nav-link disabled" href="#" aria-disabled="true"><i class="bi bi-mortarboard me-2"></i>課程管理</a>
<a class="nav-link active" href="index.html"><i class="bi bi-journal-text me-2"></i>練習紀錄管理</a>
<a class="nav-link" href="../songs/index.html"><i class="bi bi-music-note-list me-2"></i>歌曲資料管理</a>
<a class="nav-link disabled" href="#" aria-disabled="true"><i class="bi bi-list-task me-2"></i>訓練任務管理</a>
</nav>
</aside>
<main class="col-lg-10 py-4 px-4">
<div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-4">
<h1 class="h3 mb-0 text-white">練習紀錄管理</h1>
<div class="d-flex gap-2">
<a class="btn btn-brand" href="create.html"><i class="bi bi-plus-lg me-1"></i>新增練習紀錄</a>
<a class="btn btn-outline-brand" href="../index.html">回後台首頁</a>
</div>
</div>
<div class="card sk-card sk-surface">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead>
<tr>
<th>練習生</th>
<th>日期</th>
<th>訓練類型</th>
<th>歌曲</th>
<th>練習時間</th>
<th>完成度</th>
<th>完成狀態</th>
<th class="text-end">操作</th>
</tr>
</thead>
<tbody>
<tr>
<td>努比</td>
<td>2026-07-16</td>
<td><span class="badge badge-type badge-type--dance">舞蹈</span></td>
<td>Road Fighter</td>
<td>40 分鐘</td>
<td>85%</td>
<td><span class="badge badge-status badge-status--done">已完成</span></td>
<td class="text-end">
<a class="btn btn-sm btn-outline-brand" href="edit.html">編輯</a>
<a class="btn btn-sm btn-outline-danger" href="delete.html">刪除</a>
</td>
</tr>
<tr>
<td>昇材</td>
<td>2026-07-16</td>
<td><span class="badge badge-type badge-type--vocal">vocal</span></td>
<td>Couple</td>
<td>35 分鐘</td>
<td>78%</td>
<td><span class="badge badge-status badge-status--done">已完成</span></td>
<td class="text-end">
<a class="btn btn-sm btn-outline-brand" href="edit.html">編輯</a>
<a class="btn btn-sm btn-outline-danger" href="delete.html">刪除</a>
</td>
</tr>
<tr>
<td>宰鎮</td>
<td>2026-07-15</td>
<td><span class="badge badge-type badge-type--rap">Rap</span></td>
<td>Chivalry</td>
<td>25 分鐘</td>
<td>60%</td>
<td><span class="badge badge-status badge-status--progress">進行中</span></td>
<td class="text-end">
<a class="btn btn-sm btn-outline-brand" href="edit.html">編輯</a>
<a class="btn btn-sm btn-outline-danger" href="delete.html">刪除</a>
</td>
</tr>
<tr>
<td>水院</td>
<td>2026-07-14</td>
<td><span class="badge badge-type badge-type--stage">舞台表現</span></td>
<td>Com' Back</td>
<td>50 分鐘</td>
<td>0%</td>
<td><span class="badge badge-status badge-status--pending">未開始</span></td>
<td class="text-end">
<a class="btn btn-sm btn-outline-brand" href="edit.html">編輯</a>
<a class="btn btn-sm btn-outline-danger" href="delete.html">刪除</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</main>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
@@ -1,96 +0,0 @@
<!doctype html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>新增歌曲|水晶練習室 Admin</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet">
<link href="../../css/style.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand navbar-dark sk-navbar">
<div class="container-fluid">
<span class="navbar-brand mb-0"><i class="bi bi-gem me-1"></i>水晶練習室 <span class="fw-light">Admin</span></span>
<div class="d-flex gap-2">
<a class="btn btn-brand btn-sm" href="../../index.html">回前台</a>
<a class="btn btn-outline-brand btn-sm" href="../../index.html">登出</a>
</div>
</div>
</nav>
<div class="container-fluid">
<div class="row">
<aside class="col-lg-2 border-end sk-admin-sidebar py-4 px-3 min-vh-100">
<nav class="nav flex-column">
<a class="nav-link" href="../index.html"><i class="bi bi-speedometer2 me-2"></i>後台首頁</a>
<a class="nav-link disabled" href="#" aria-disabled="true"><i class="bi bi-people me-2"></i>練習生管理</a>
<a class="nav-link disabled" href="#" aria-disabled="true"><i class="bi bi-mortarboard me-2"></i>課程管理</a>
<a class="nav-link" href="../practices/index.html"><i class="bi bi-journal-text me-2"></i>練習紀錄管理</a>
<a class="nav-link active" href="index.html"><i class="bi bi-music-note-list me-2"></i>歌曲資料管理</a>
<a class="nav-link disabled" href="#" aria-disabled="true"><i class="bi bi-list-task me-2"></i>訓練任務管理</a>
</nav>
</aside>
<main class="col-lg-10 py-4 px-4">
<h1 class="h3 mb-4 text-white">新增歌曲</h1>
<div class="card sk-card sk-surface p-4">
<form>
<div class="row g-3">
<div class="col-md-6">
<label for="songName" class="form-label">歌名</label>
<input type="text" class="form-control" id="songName" placeholder="例如:Couple">
</div>
<div class="col-md-6">
<label for="songAlbum" class="form-label">專輯</label>
<input type="text" class="form-control" id="songAlbum" placeholder="例如:Come Back Again">
</div>
<div class="col-md-6">
<label for="songYear" class="form-label">發行年份</label>
<input type="number" class="form-control" id="songYear" placeholder="例如:1997">
</div>
<div class="col-md-6">
<label for="songYoutube" class="form-label">YouTube 連結</label>
<input type="url" class="form-control" id="songYoutube" placeholder="https://www.youtube.com/...">
</div>
<div class="col-md-6">
<label for="songDifficulty" class="form-label">舞蹈難度</label>
<select class="form-select" id="songDifficulty">
<option></option>
<option>中等</option>
<option></option>
</select>
</div>
<div class="col-md-6">
<label class="form-label d-block">是否顯示</label>
<div class="form-check form-switch mt-2">
<input class="form-check-input" type="checkbox" role="switch" id="songVisible" checked>
<label class="form-check-label" for="songVisible">於前台顯示</label>
</div>
</div>
</div>
<div class="d-flex gap-2 mt-4">
<button type="submit" class="btn btn-brand">儲存</button>
<a class="btn btn-outline-brand" href="index.html">取消</a>
</div>
</form>
</div>
</main>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
@@ -1,68 +0,0 @@
<!doctype html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>刪除歌曲|水晶練習室 Admin</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet">
<link href="../../css/style.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand navbar-dark sk-navbar">
<div class="container-fluid">
<span class="navbar-brand mb-0"><i class="bi bi-gem me-1"></i>水晶練習室 <span class="fw-light">Admin</span></span>
<div class="d-flex gap-2">
<a class="btn btn-brand btn-sm" href="../../index.html">回前台</a>
<a class="btn btn-outline-brand btn-sm" href="../../index.html">登出</a>
</div>
</div>
</nav>
<div class="container-fluid">
<div class="row">
<aside class="col-lg-2 border-end sk-admin-sidebar py-4 px-3 min-vh-100">
<nav class="nav flex-column">
<a class="nav-link" href="../index.html"><i class="bi bi-speedometer2 me-2"></i>後台首頁</a>
<a class="nav-link disabled" href="#" aria-disabled="true"><i class="bi bi-people me-2"></i>練習生管理</a>
<a class="nav-link disabled" href="#" aria-disabled="true"><i class="bi bi-mortarboard me-2"></i>課程管理</a>
<a class="nav-link" href="../practices/index.html"><i class="bi bi-journal-text me-2"></i>練習紀錄管理</a>
<a class="nav-link active" href="index.html"><i class="bi bi-music-note-list me-2"></i>歌曲資料管理</a>
<a class="nav-link disabled" href="#" aria-disabled="true"><i class="bi bi-list-task me-2"></i>訓練任務管理</a>
</nav>
</aside>
<main class="col-lg-10 py-4 px-4">
<h1 class="h3 mb-4 text-white">刪除歌曲</h1>
<div class="card sk-card sk-surface p-4" style="max-width: 32rem;">
<div class="alert alert-danger mb-4" role="alert">
<i class="bi bi-exclamation-triangle-fill me-1"></i>確定要刪除這首歌曲嗎?此動作無法復原。
</div>
<dl class="row mb-4">
<dt class="col-4 text-body-secondary">編號</dt>
<dd class="col-8">1</dd>
<dt class="col-4 text-body-secondary">歌名</dt>
<dd class="col-8">Couple</dd>
<dt class="col-4 text-body-secondary">專輯</dt>
<dd class="col-8">Come Back Again</dd>
<dt class="col-4 text-body-secondary">發行年份</dt>
<dd class="col-8">1997</dd>
</dl>
<div class="d-flex gap-2">
<button type="button" class="btn btn-danger">確認刪除</button>
<a class="btn btn-outline-brand" href="index.html">取消</a>
</div>
</div>
</main>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
-103
View File
@@ -1,103 +0,0 @@
<!doctype html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>編輯歌曲|水晶練習室 Admin</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet">
<link href="../../css/style.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand navbar-dark sk-navbar">
<div class="container-fluid">
<span class="navbar-brand mb-0"><i class="bi bi-gem me-1"></i>水晶練習室 <span class="fw-light">Admin</span></span>
<div class="d-flex gap-2">
<a class="btn btn-brand btn-sm" href="../../index.html">回前台</a>
<a class="btn btn-outline-brand btn-sm" href="../../index.html">登出</a>
</div>
</div>
</nav>
<div class="container-fluid">
<div class="row">
<aside class="col-lg-2 border-end sk-admin-sidebar py-4 px-3 min-vh-100">
<nav class="nav flex-column">
<a class="nav-link" href="../index.html"><i class="bi bi-speedometer2 me-2"></i>後台首頁</a>
<a class="nav-link disabled" href="#" aria-disabled="true"><i class="bi bi-people me-2"></i>練習生管理</a>
<a class="nav-link disabled" href="#" aria-disabled="true"><i class="bi bi-mortarboard me-2"></i>課程管理</a>
<a class="nav-link" href="../practices/index.html"><i class="bi bi-journal-text me-2"></i>練習紀錄管理</a>
<a class="nav-link active" href="index.html"><i class="bi bi-music-note-list me-2"></i>歌曲資料管理</a>
<a class="nav-link disabled" href="#" aria-disabled="true"><i class="bi bi-list-task me-2"></i>訓練任務管理</a>
</nav>
</aside>
<main class="col-lg-10 py-4 px-4">
<h1 class="h3 mb-4 text-white">編輯歌曲</h1>
<div class="card sk-card sk-surface p-4">
<form>
<div class="row g-3">
<div class="col-md-6">
<label for="songId" class="form-label">編號</label>
<input type="text" class="form-control" id="songId" value="1" disabled>
</div>
<div class="col-md-6"></div>
<div class="col-md-6">
<label for="songName" class="form-label">歌名</label>
<input type="text" class="form-control" id="songName" value="Couple">
</div>
<div class="col-md-6">
<label for="songAlbum" class="form-label">專輯</label>
<input type="text" class="form-control" id="songAlbum" value="Come Back Again">
</div>
<div class="col-md-6">
<label for="songYear" class="form-label">發行年份</label>
<input type="number" class="form-control" id="songYear" value="1997">
</div>
<div class="col-md-6">
<label for="songYoutube" class="form-label">YouTube 連結</label>
<input type="url" class="form-control" id="songYoutube" value="https://www.youtube.com/">
</div>
<div class="col-md-6">
<label for="songDifficulty" class="form-label">舞蹈難度</label>
<select class="form-select" id="songDifficulty">
<option></option>
<option selected>中等</option>
<option></option>
</select>
</div>
<div class="col-md-6">
<label class="form-label d-block">是否顯示</label>
<div class="form-check form-switch mt-2">
<input class="form-check-input" type="checkbox" role="switch" id="songVisible" checked>
<label class="form-check-label" for="songVisible">於前台顯示</label>
</div>
</div>
</div>
<div class="d-flex gap-2 mt-4">
<button type="submit" class="btn btn-brand">儲存變更</button>
<a class="btn btn-outline-brand" href="index.html">取消</a>
</div>
</form>
</div>
</main>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
-112
View File
@@ -1,112 +0,0 @@
<!doctype html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>歌曲資料管理|水晶練習室 Admin</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet">
<link href="../../css/style.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand navbar-dark sk-navbar">
<div class="container-fluid">
<span class="navbar-brand mb-0"><i class="bi bi-gem me-1"></i>水晶練習室 <span class="fw-light">Admin</span></span>
<div class="d-flex gap-2">
<a class="btn btn-brand btn-sm" href="../../index.html">回前台</a>
<a class="btn btn-outline-brand btn-sm" href="../../index.html">登出</a>
</div>
</div>
</nav>
<div class="container-fluid">
<div class="row">
<aside class="col-lg-2 border-end sk-admin-sidebar py-4 px-3 min-vh-100">
<nav class="nav flex-column">
<a class="nav-link" href="../index.html"><i class="bi bi-speedometer2 me-2"></i>後台首頁</a>
<a class="nav-link disabled" href="#" aria-disabled="true"><i class="bi bi-people me-2"></i>練習生管理</a>
<a class="nav-link disabled" href="#" aria-disabled="true"><i class="bi bi-mortarboard me-2"></i>課程管理</a>
<a class="nav-link" href="../practices/index.html"><i class="bi bi-journal-text me-2"></i>練習紀錄管理</a>
<a class="nav-link active" href="index.html"><i class="bi bi-music-note-list me-2"></i>歌曲資料管理</a>
<a class="nav-link disabled" href="#" aria-disabled="true"><i class="bi bi-list-task me-2"></i>訓練任務管理</a>
</nav>
</aside>
<main class="col-lg-10 py-4 px-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3 mb-0 text-white">歌曲資料管理</h1>
<div class="d-flex gap-2">
<a class="btn btn-brand" href="create.html"><i class="bi bi-plus-lg me-1"></i>新增歌曲</a>
<a class="btn btn-outline-brand" href="../index.html">回後台首頁</a>
</div>
</div>
<div class="card sk-card sk-surface">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead>
<tr>
<th>編號</th>
<th>歌名</th>
<th>專輯</th>
<th>發行年份</th>
<th>YouTube 連結</th>
<th>舞蹈難度</th>
<th>是否顯示</th>
<th class="text-end">操作</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Couple</td>
<td>Come Back Again</td>
<td>1997</td>
<td><a href="https://www.youtube.com/" target="_blank" rel="noopener">連結</a></td>
<td><span class="badge text-bg-warning">中等</span></td>
<td><span class="badge text-bg-success">顯示</span></td>
<td class="text-end">
<a class="btn btn-sm btn-outline-brand" href="edit.html">編輯</a>
<a class="btn btn-sm btn-outline-danger" href="delete.html">刪除</a>
</td>
</tr>
<tr>
<td>2</td>
<td>Road Fighter</td>
<td>Road Fighter</td>
<td>2000</td>
<td><a href="https://www.youtube.com/" target="_blank" rel="noopener">連結</a></td>
<td><span class="badge text-bg-danger"></span></td>
<td><span class="badge text-bg-success">顯示</span></td>
<td class="text-end">
<a class="btn btn-sm btn-outline-brand" href="edit.html">編輯</a>
<a class="btn btn-sm btn-outline-danger" href="delete.html">刪除</a>
</td>
</tr>
<tr>
<td>3</td>
<td>Couple 재수사</td>
<td>The Story of Kies Part 1</td>
<td>2016</td>
<td><a href="https://www.youtube.com/" target="_blank" rel="noopener">連結</a></td>
<td><span class="badge text-bg-secondary"></span></td>
<td><span class="badge text-bg-secondary">隱藏</span></td>
<td class="text-end">
<a class="btn btn-sm btn-outline-brand" href="edit.html">編輯</a>
<a class="btn btn-sm btn-outline-danger" href="delete.html">刪除</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</main>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
-391
View File
@@ -1,391 +0,0 @@
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@100..900&display=swap');
/* ============================================
水晶練習室偶像練習生訓練管理系統
主題專輯質感 黑底 + 銀白金屬漸層呼應 G1 專輯視覺
重點行動區保留少量烈焰紅點綴
============================================ */
:root {
/* ── 環境色(銀白金屬,用於底色/邊框/卡片) ── */
--sk-primary: #9aa0a8;
--sk-primary-dark: #4b4e54;
--sk-secondary: #e4e6ea;
--sk-accent: #ffffff;
--sk-gradient: linear-gradient(135deg, #3a3c40 0%, #8b8f96 45%, #d6d9dd 75%, #ffffff 100%);
/* ── 重點色(烈焰紅,僅用於關鍵行動/狀態) ── */
--sk-red: #e2102b;
--sk-red-dark: #9c0b1e;
--sk-red-gradient: linear-gradient(135deg, #ff2d3d 0%, #9c0b1e 100%);
/* ── 背景色(舞台黑) ── */
--sk-bg: #0a0a0a;
--sk-bg-raised: #161616;
/* ── 玻璃卡片面 ── */
--sk-surface: rgba(180, 184, 191, 0.06);
--sk-surface-soft: rgba(180, 184, 191, 0.12);
--sk-border: rgba(180, 184, 191, 0.35);
/* ── 文字色(銀白,呼應專輯封面) ── */
--sk-text: #f1f2f4;
--sk-text-muted: #a7abb2;
}
html,
body {
background-color: var(--sk-bg);
color: var(--sk-text);
}
body {
font-family: 'Noto Sans TC', system-ui, sans-serif;
line-height: 1.6;
position: relative;
}
a {
color: var(--sk-secondary);
}
.text-body-secondary {
color: silver !important;
}
/* 舞台感光暈:兩顆柔光暈,僅作背景點綴,不搶內容 */
body::before,
body::after {
content: "";
position: fixed;
width: 40vw;
height: 40vw;
border-radius: 50%;
filter: blur(80px);
opacity: 0.16;
z-index: -1;
pointer-events: none;
}
body::before {
top: -10vw;
left: -10vw;
background: var(--sk-primary);
}
body::after {
bottom: -10vw;
right: -10vw;
background: var(--sk-accent);
}
/* ============ Navbar(前台/後台共用) ============ */
.sk-navbar {
background-color: var(--sk-bg-raised) !important;
border-bottom: 1px solid var(--sk-border);
padding-top: 0.75rem;
padding-bottom: 0.75rem;
}
.sk-navbar .navbar-brand,
.sk-brand-text {
background: var(--sk-gradient);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
font-weight: 700;
letter-spacing: 1px;
}
.sk-navbar .nav-link {
color: var(--sk-text);
}
.sk-navbar .nav-link:hover,
.sk-navbar .nav-link.active {
color: var(--sk-secondary);
}
.sk-navbar .nav-link.disabled {
color: #5c5f64;
}
/* ============ 品牌按鈕 ============ */
.btn-brand {
background: var(--sk-red-gradient);
border: none;
color: #fff;
font-weight: 600;
}
.btn-brand:hover,
.btn-brand:focus {
filter: brightness(1.1);
color: #fff;
}
.btn-outline-brand {
color: var(--sk-secondary);
border-color: var(--sk-border);
background-color: transparent;
}
.btn-outline-brand:hover {
background: var(--sk-red-gradient);
border-color: transparent;
color: #fff;
}
/* ============ 主視覺(嵌入影片背景,尺寸依影片比例) ============ */
.sk-hero {
border-bottom: 1px solid var(--sk-border);
overflow: hidden;
}
.sk-hero h1 {
color: #fff;
letter-spacing: 2px;
}
.sk-hero .sk-title-bar {
width: 60px;
height: 3px;
background: var(--sk-gradient);
margin: 18px auto 0;
border-radius: 2px;
}
.sk-hero-video-frame {
position: relative;
width: 100%;
aspect-ratio: 16 / 9;
background: #000;
}
.sk-hero-video-frame iframe {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
border: 0;
pointer-events: none;
}
.sk-hero-overlay {
position: absolute;
inset: 0;
background: linear-gradient(180deg, rgba(10, 10, 10, 0.35) 0%, rgba(10, 10, 10, 0.8) 100%);
}
.sk-hero-content {
position: relative;
z-index: 2;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 1rem;
}
/* ============ 區塊標題 ============ */
.sk-section-title {
color: var(--sk-text);
font-size: 1.25rem;
font-weight: 700;
letter-spacing: 1px;
margin-bottom: 1.25rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.sk-section-title i {
color: var(--sk-accent);
}
/* ============ 玻璃卡片(練習/統計/表格/表單容器共用) ============ */
.sk-card,
.sk-surface {
background-color: var(--sk-surface);
border: 1px solid var(--sk-border);
backdrop-filter: blur(10px);
color: var(--sk-text);
border-radius: 0.75rem;
}
.sk-card {
transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
}
.sk-card:hover {
transform: translateY(-4px);
border-color: var(--sk-primary);
box-shadow: 0 0.75rem 1.75rem rgba(180, 184, 191, 0.3);
}
.sk-card .card-title {
color: #fff;
}
.sk-stat-value {
font-size: 2rem;
font-weight: 700;
background: var(--sk-gradient);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
/* ============ 練習生歡迎區 ============ */
.sk-welcome-icon {
width: 3.25rem;
height: 3.25rem;
border-radius: 50%;
background: var(--sk-gradient);
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
color: #fff;
flex-shrink: 0;
}
/* ============ 訓練類型徽章(紅/金/黑/米白家族) ============ */
.badge-type {
border-radius: 999px;
padding: 0.35em 0.75em;
font-weight: 600;
border: 1px solid transparent;
}
.badge-type--vocal { background: rgba(200, 205, 214, 0.18); color: #dde0e6; border-color: rgba(200, 205, 214, 0.45); }
.badge-type--dance { background: rgba(154, 160, 168, 0.18); color: #c9ccd2; border-color: rgba(154, 160, 168, 0.45); }
.badge-type--rap { background: rgba(110, 114, 121, 0.30); color: #b7bac0; border-color: rgba(110, 114, 121, 0.55); }
.badge-type--stage { background: var(--sk-text); color: #1a1a1a; border-color: var(--sk-text); }
.badge-type--fitness { background: rgba(150, 154, 161, 0.16); color: #c4c7cd; border-color: rgba(150, 154, 161, 0.4); }
/* ============ 完成狀態徽章 ============ */
.badge-status {
border-radius: 999px;
padding: 0.35em 0.75em;
font-weight: 600;
border: 1px solid transparent;
}
.badge-status--pending { background: rgba(150, 154, 161, 0.16); color: var(--sk-text-muted); border-color: rgba(150, 154, 161, 0.35); }
.badge-status--progress { background: rgba(226, 16, 43, 0.18); color: #ff8a94; border-color: rgba(226, 16, 43, 0.45); }
.badge-status--done { background: rgba(52, 211, 153, 0.18); color: #6ee7b7; border-color: rgba(52, 211, 153, 0.45); }
/* ============ Progress(能力進度) ============ */
.sk-progress {
height: 0.6rem;
background-color: var(--sk-bg-raised);
border: 1px solid var(--sk-border);
border-radius: 999px;
}
.sk-progress .progress-bar {
background: var(--sk-gradient);
}
/* ============ 後台側邊選單 ============ */
.sk-admin-sidebar {
background-color: var(--sk-bg-raised);
border-color: var(--sk-border) !important;
}
.sk-admin-sidebar .nav-link {
color: var(--sk-text);
border-radius: 0.5rem;
margin-bottom: 0.25rem;
}
.sk-admin-sidebar .nav-link.active {
background: var(--sk-gradient);
color: #fff;
font-weight: 600;
}
.sk-admin-sidebar .nav-link:not(.active):hover {
background-color: var(--sk-surface-soft);
}
.sk-admin-sidebar .nav-link.disabled {
color: #5c5f64;
}
/* ============ 表格 ============ */
.sk-surface .table {
--bs-table-color: var(--sk-text);
--bs-table-bg: transparent;
--bs-table-striped-bg: rgba(180, 184, 191, 0.06);
--bs-table-hover-bg: rgba(180, 184, 191, 0.1);
--bs-table-border-color: var(--sk-border);
vertical-align: middle;
}
.sk-surface .table thead {
background-color: var(--sk-surface-soft);
color: #fff;
}
.sk-surface a:not(.btn):not(.nav-link) {
color: var(--sk-secondary);
}
/* ============ 表單元件(暗色輸入框) ============ */
.form-control,
.form-select {
background-color: var(--sk-bg-raised);
border: 1px solid var(--sk-border);
color: var(--sk-text);
}
.form-control:focus,
.form-select:focus {
background-color: var(--sk-bg-raised);
border-color: var(--sk-primary);
color: var(--sk-text);
box-shadow: 0 0 0 0.25rem rgba(180, 184, 191, 0.25);
}
.form-control::placeholder {
color: #6c7076;
}
.form-control:disabled {
background-color: var(--sk-bg-raised);
color: var(--sk-text-muted);
opacity: 0.7;
}
.form-label {
color: var(--sk-text-muted);
font-weight: 500;
}
.form-check-input {
background-color: var(--sk-bg-raised);
border-color: var(--sk-border);
}
.form-check-input:checked {
background-color: var(--sk-primary);
border-color: var(--sk-primary);
}
.form-range::-webkit-slider-thumb {
background: var(--sk-primary);
}
.form-range::-moz-range-thumb {
background: var(--sk-primary);
}
/* ============ Footer ============ */
.sk-footer {
background-color: var(--sk-bg-raised);
color: var(--sk-text-muted);
border-top: 1px solid var(--sk-border);
}
-225
View File
@@ -1,225 +0,0 @@
<!doctype html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>全能Leader</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet">
<link href="css/style.css" rel="stylesheet">
</head>
<body>
<!-- 上方導覽列 -->
<nav class="navbar navbar-expand-lg navbar-dark sk-navbar sticky-top">
<div class="container">
<a class="navbar-brand" href="index.html"><i class="bi bi-gem me-1"></i>全能Leader練習室</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#mainNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="mainNav">
<ul class="navbar-nav ms-auto align-items-lg-center gap-lg-1">
<!-- <li class="nav-item"><a class="nav-link active" href="index.html">練習室首頁</a></li> -->
<li class="nav-item"><a class="nav-link" href="#today">今日訓練</a></li>
<li class="nav-item"><a class="nav-link" href="practice/index.html">我的練習紀錄</a></li>
<li class="nav-item"><a class="nav-link disabled" href="#" aria-disabled="true">課程庫</a></li>
<li class="nav-item"><a class="nav-link" href="#progress">個人進度</a></li>
<li class="nav-item ms-lg-2">
<a class="btn btn-brand btn-sm" href="admin/index.html">後台管理</a>
</li>
</ul>
</div>
</div>
</nav>
<!-- 主視覺(嵌入影片背景) -->
<header class="sk-hero">
<div class="sk-hero-video-frame">
<iframe
src="https://www.youtube.com/embed/ARi0cfpv6mE?autoplay=1&mute=1&loop=1&playlist=ARi0cfpv6mE&controls=0&modestbranding=1&rel=0&showinfo=0"
title="水晶練習室主視覺影片"
allow="autoplay; encrypted-media"
loading="lazy"></iframe>
<div class="sk-hero-overlay"></div>
<div class="sk-hero-content container text-center">
<h1 class="display-4 fw-bold mb-0">偶像練習生</h1>
<div class="sk-title-bar"></div>
<p class="lead mb-0 mt-4 text-body-secondary">全能Leader 殷志源打造全新綜藝</p>
</div>
</div>
</header>
<main class="container py-5">
<!-- 練習生歡迎區 -->
<section class="mb-5">
<div class="card sk-card p-4">
<div class="d-flex flex-wrap align-items-center gap-3">
<div class="sk-welcome-icon"><i class="bi bi-person-video3"></i></div>
<div class="me-auto">
<div class="fs-5 fw-bold text-white">練習生:努比</div>
<span class="badge badge-status badge-status--progress"><i class="bi bi-record-circle me-1"></i>今日狀態:訓練中</span>
</div>
<div class="d-flex gap-4 text-center">
<div>
<div class="sk-stat-value fs-3">4</div>
<div class="small text-body-secondary">本週完成練習(次)</div>
</div>
<div>
<div class="sk-stat-value fs-3">3</div>
<div class="small text-body-secondary">連續練習(天)</div>
</div>
</div>
</div>
</div>
</section>
<!-- 今日訓練任務 -->
<section class="mb-5" id="today">
<h2 class="sk-section-title"><i class="bi bi-list-task"></i>今日訓練任務</h2>
<div class="row g-4">
<div class="col-md-4">
<div class="card sk-card h-100 p-4">
<div class="d-flex align-items-center gap-2 mb-3">
<i class="bi bi-mic-fill fs-4 text-white"></i>
<span class="badge badge-type badge-type--vocal">vocal</span>
</div>
<h3 class="h5 text-white">完成一首歌曲錄唱</h3>
<p class="text-body-secondary small mb-3"><i class="bi bi-clock me-1"></i>預計時間:30 分鐘</p>
<span class="badge badge-status badge-status--pending mb-3 align-self-start">未開始</span>
<a class="btn btn-brand mt-auto" href="practice/create.html">開始練習</a>
</div>
</div>
<div class="col-md-4">
<div class="card sk-card h-100 p-4">
<div class="d-flex align-items-center gap-2 mb-3">
<i class="bi bi-person-arms-up fs-4 text-white"></i>
<span class="badge badge-type badge-type--dance">舞蹈</span>
</div>
<h3 class="h5 text-white">練習一段K-POP舞蹈</h3>
<p class="text-body-secondary small mb-3"><i class="bi bi-clock me-1"></i>預計時間:45 分鐘</p>
<span class="badge badge-status badge-status--progress mb-3 align-self-start">進行中</span>
<a class="btn btn-brand mt-auto" href="practice/create.html">開始練習</a>
</div>
</div>
<div class="col-md-4">
<div class="card sk-card h-100 p-4">
<div class="d-flex align-items-center gap-2 mb-3">
<i class="bi bi-soundwave fs-4 text-white"></i>
<span class="badge badge-type badge-type--rap">Rap</span>
</div>
<h3 class="h5 text-white">完成節奏與咬字訓練</h3>
<p class="text-body-secondary small mb-3"><i class="bi bi-clock me-1"></i>預計時間:20 分鐘</p>
<span class="badge badge-status badge-status--done mb-3 align-self-start">已完成</span>
<a class="btn btn-outline-brand mt-auto" href="practice/create.html">再次練習</a>
</div>
</div>
</div>
</section>
<!-- 能力進度 -->
<section class="mb-5" id="progress">
<h2 class="sk-section-title"><i class="bi bi-graph-up-arrow"></i>能力進度</h2>
<div class="card sk-card p-4">
<div class="row g-4">
<div class="col-md-6">
<div class="d-flex justify-content-between small mb-1">
<span><i class="bi bi-mic-fill me-1"></i>vocal</span>
<span class="text-body-secondary">99%</span>
</div>
<div class="progress sk-progress">
<div class="progress-bar" style="width: 99%" role="progressbar" aria-valuenow="99" aria-valuemin="0" aria-valuemax="100"></div>
</div>
</div>
<div class="col-md-6">
<div class="d-flex justify-content-between small mb-1">
<span><i class="bi bi-person-arms-up me-1"></i>舞蹈</span>
<span class="text-body-secondary">85%</span>
</div>
<div class="progress sk-progress">
<div class="progress-bar" style="width: 85%" role="progressbar" aria-valuenow="85" aria-valuemin="0" aria-valuemax="100"></div>
</div>
</div>
<div class="col-md-6">
<div class="d-flex justify-content-between small mb-1">
<span><i class="bi bi-soundwave me-1"></i>Rap</span>
<span class="text-body-secondary">66%</span>
</div>
<div class="progress sk-progress">
<div class="progress-bar" style="width: 66%" role="progressbar" aria-valuenow="66" aria-valuemin="0" aria-valuemax="100"></div>
</div>
</div>
<div class="col-md-6">
<div class="d-flex justify-content-between small mb-1">
<span><i class="bi bi-stars me-1"></i>舞台表現</span>
<span class="text-body-secondary">80%</span>
</div>
<div class="progress sk-progress">
<div class="progress-bar" style="width: 80%" role="progressbar" aria-valuenow="80" aria-valuemin="0" aria-valuemax="100"></div>
</div>
</div>
</div>
</div>
</section>
<!-- 最近練習紀錄 -->
<section>
<h2 class="sk-section-title"><i class="bi bi-journal-text"></i>最近練習紀錄</h2>
<div class="card sk-card sk-surface">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead>
<tr>
<th>日期</th>
<th>練習項目</th>
<th>歌曲</th>
<th>練習時間</th>
<th>完成度</th>
<th class="text-end">查看紀錄</th>
</tr>
</thead>
<tbody>
<tr>
<td>2026-07-16</td>
<td><span class="badge badge-type badge-type--dance">舞蹈</span></td>
<td>Road Fighter</td>
<td>40 分鐘</td>
<td>85%</td>
<td class="text-end"><a class="btn btn-sm btn-outline-brand" href="practice/show.html">查看</a></td>
</tr>
<tr>
<td>2026-07-15</td>
<td><span class="badge badge-type badge-type--vocal">vocal</span></td>
<td>Couple</td>
<td>30 分鐘</td>
<td>70%</td>
<td class="text-end"><a class="btn btn-sm btn-outline-brand" href="practice/show.html">查看</a></td>
</tr>
<tr>
<td>2026-07-14</td>
<td><span class="badge badge-type badge-type--rap">Rap</span></td>
<td>Chivalry</td>
<td>25 分鐘</td>
<td>60%</td>
<td class="text-end"><a class="btn btn-sm btn-outline-brand" href="practice/show.html">查看</a></td>
</tr>
</tbody>
</table>
</div>
</div>
</section>
</main>
<footer class="sk-footer text-center py-4">
<small>&copy; 2026 水晶練習室</small>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
-115
View File
@@ -1,115 +0,0 @@
<!doctype html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>新增練習紀錄|水晶練習室</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet">
<link href="../css/style.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark sk-navbar sticky-top">
<div class="container">
<a class="navbar-brand" href="../index.html"><i class="bi bi-gem me-1"></i>水晶練習室</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#mainNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="mainNav">
<ul class="navbar-nav ms-auto align-items-lg-center gap-lg-1">
<li class="nav-item"><a class="nav-link" href="../index.html">練習室首頁</a></li>
<li class="nav-item"><a class="nav-link" href="../index.html#today">今日訓練</a></li>
<li class="nav-item"><a class="nav-link active" href="index.html">我的練習紀錄</a></li>
<li class="nav-item"><a class="nav-link disabled" href="#" aria-disabled="true">課程庫</a></li>
<li class="nav-item"><a class="nav-link" href="../index.html#progress">個人進度</a></li>
<li class="nav-item ms-lg-2">
<a class="btn btn-brand btn-sm" href="../admin/index.html">後台管理</a>
</li>
</ul>
</div>
</div>
</nav>
<main class="container py-5">
<h1 class="h3 mb-4 text-white"><i class="bi bi-plus-circle me-2"></i>新增練習紀錄</h1>
<div class="card sk-card sk-surface p-4" style="max-width: 42rem;">
<form>
<div class="row g-3">
<div class="col-md-6">
<label for="practiceDate" class="form-label">練習日期</label>
<input type="date" class="form-control" id="practiceDate">
</div>
<div class="col-md-6">
<label for="practiceType" class="form-label">訓練類型</label>
<select class="form-select" id="practiceType">
<option>vocal</option>
<option>舞蹈</option>
<option>Rap</option>
<option>舞台表現</option>
<option>體能</option>
</select>
</div>
<div class="col-md-6">
<label for="practiceSong" class="form-label">練習歌曲</label>
<select class="form-select" id="practiceSong">
<option>Couple</option>
<option>Road Fighter</option>
<option>Com' Back</option>
<option>Chivalry</option>
<option>The Way This Guy Lives</option>
</select>
</div>
<div class="col-md-6">
<label for="practiceMinutes" class="form-label">練習分鐘數</label>
<input type="number" class="form-control" id="practiceMinutes" placeholder="例如:30">
</div>
<div class="col-12">
<label for="practiceContent" class="form-label">練習內容</label>
<textarea class="form-control" id="practiceContent" rows="2" placeholder="例如:副歌走位+隊形轉換"></textarea>
</div>
<div class="col-12">
<label for="practiceCompletion" class="form-label d-flex justify-content-between">
<span>今日完成度</span>
<span class="text-body-secondary" id="completionValue">50%</span>
</label>
<input type="range" class="form-range" id="practiceCompletion" min="0" max="100" value="50"
oninput="document.getElementById('completionValue').textContent = this.value + '%'">
</div>
<div class="col-12">
<label for="practiceNote" class="form-label">練習心得</label>
<textarea class="form-control" id="practiceNote" rows="3" placeholder="今天練習的感想與需要加強的地方"></textarea>
</div>
<div class="col-12">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="practiceDone">
<label class="form-check-label" for="practiceDone">是否完成</label>
</div>
</div>
</div>
<div class="d-flex gap-2 mt-4">
<button type="submit" class="btn btn-brand">儲存</button>
<a class="btn btn-outline-brand" href="index.html">取消</a>
</div>
</form>
</div>
</main>
<footer class="sk-footer text-center py-4">
<small>&copy; 2026 水晶練習室</small>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
-66
View File
@@ -1,66 +0,0 @@
<!doctype html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>刪除練習紀錄|水晶練習室</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet">
<link href="../css/style.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark sk-navbar sticky-top">
<div class="container">
<a class="navbar-brand" href="../index.html"><i class="bi bi-gem me-1"></i>水晶練習室</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#mainNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="mainNav">
<ul class="navbar-nav ms-auto align-items-lg-center gap-lg-1">
<li class="nav-item"><a class="nav-link" href="../index.html">練習室首頁</a></li>
<li class="nav-item"><a class="nav-link" href="../index.html#today">今日訓練</a></li>
<li class="nav-item"><a class="nav-link active" href="index.html">我的練習紀錄</a></li>
<li class="nav-item"><a class="nav-link disabled" href="#" aria-disabled="true">課程庫</a></li>
<li class="nav-item"><a class="nav-link" href="../index.html#progress">個人進度</a></li>
<li class="nav-item ms-lg-2">
<a class="btn btn-brand btn-sm" href="../admin/index.html">後台管理</a>
</li>
</ul>
</div>
</div>
</nav>
<main class="container py-5">
<h1 class="h3 mb-4 text-white"><i class="bi bi-trash3 me-2"></i>刪除練習紀錄</h1>
<div class="card sk-card sk-surface p-4" style="max-width: 32rem;">
<div class="alert alert-danger mb-4" role="alert">
<i class="bi bi-exclamation-triangle-fill me-1"></i>確定要刪除這筆練習紀錄嗎?此動作無法復原。
</div>
<dl class="row mb-4">
<dt class="col-4 text-body-secondary">練習日期</dt>
<dd class="col-8">2026-07-16</dd>
<dt class="col-4 text-body-secondary">訓練類型</dt>
<dd class="col-8">舞蹈</dd>
<dt class="col-4 text-body-secondary">練習歌曲</dt>
<dd class="col-8">Road Fighter</dd>
<dt class="col-4 text-body-secondary">練習時間</dt>
<dd class="col-8">40 分鐘</dd>
</dl>
<div class="d-flex gap-2">
<button type="button" class="btn btn-danger">確認刪除</button>
<a class="btn btn-outline-brand" href="index.html">取消</a>
</div>
</div>
</main>
<footer class="sk-footer text-center py-4">
<small>&copy; 2026 水晶練習室</small>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
-115
View File
@@ -1,115 +0,0 @@
<!doctype html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>編輯練習紀錄|水晶練習室</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet">
<link href="../css/style.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark sk-navbar sticky-top">
<div class="container">
<a class="navbar-brand" href="../index.html"><i class="bi bi-gem me-1"></i>水晶練習室</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#mainNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="mainNav">
<ul class="navbar-nav ms-auto align-items-lg-center gap-lg-1">
<li class="nav-item"><a class="nav-link" href="../index.html">練習室首頁</a></li>
<li class="nav-item"><a class="nav-link" href="../index.html#today">今日訓練</a></li>
<li class="nav-item"><a class="nav-link active" href="index.html">我的練習紀錄</a></li>
<li class="nav-item"><a class="nav-link disabled" href="#" aria-disabled="true">課程庫</a></li>
<li class="nav-item"><a class="nav-link" href="../index.html#progress">個人進度</a></li>
<li class="nav-item ms-lg-2">
<a class="btn btn-brand btn-sm" href="../admin/index.html">後台管理</a>
</li>
</ul>
</div>
</div>
</nav>
<main class="container py-5">
<h1 class="h3 mb-4 text-white"><i class="bi bi-pencil-square me-2"></i>編輯練習紀錄</h1>
<div class="card sk-card sk-surface p-4" style="max-width: 42rem;">
<form>
<div class="row g-3">
<div class="col-md-6">
<label for="practiceDate" class="form-label">練習日期</label>
<input type="date" class="form-control" id="practiceDate" value="2026-07-16">
</div>
<div class="col-md-6">
<label for="practiceType" class="form-label">訓練類型</label>
<select class="form-select" id="practiceType">
<option>vocal</option>
<option selected>舞蹈</option>
<option>Rap</option>
<option>舞台表現</option>
<option>體能</option>
</select>
</div>
<div class="col-md-6">
<label for="practiceSong" class="form-label">練習歌曲</label>
<select class="form-select" id="practiceSong">
<option>Couple</option>
<option selected>Road Fighter</option>
<option>Com' Back</option>
<option>Chivalry</option>
<option>The Way This Guy Lives</option>
</select>
</div>
<div class="col-md-6">
<label for="practiceMinutes" class="form-label">練習分鐘數</label>
<input type="number" class="form-control" id="practiceMinutes" value="40">
</div>
<div class="col-12">
<label for="practiceContent" class="form-label">練習內容</label>
<textarea class="form-control" id="practiceContent" rows="2">整段主歌+副歌走位</textarea>
</div>
<div class="col-12">
<label for="practiceCompletion" class="form-label d-flex justify-content-between">
<span>今日完成度</span>
<span class="text-body-secondary" id="completionValue">85%</span>
</label>
<input type="range" class="form-range" id="practiceCompletion" min="0" max="100" value="85"
oninput="document.getElementById('completionValue').textContent = this.value + '%'">
</div>
<div class="col-12">
<label for="practiceNote" class="form-label">練習心得</label>
<textarea class="form-control" id="practiceNote" rows="3">隊形轉換卡點抓得比昨天準,副歌動作幅度還要再放大。</textarea>
</div>
<div class="col-12">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="practiceDone" checked>
<label class="form-check-label" for="practiceDone">是否完成</label>
</div>
</div>
</div>
<div class="d-flex gap-2 mt-4">
<button type="submit" class="btn btn-brand">儲存變更</button>
<a class="btn btn-outline-brand" href="index.html">取消</a>
</div>
</form>
</div>
</main>
<footer class="sk-footer text-center py-4">
<small>&copy; 2026 水晶練習室</small>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
-115
View File
@@ -1,115 +0,0 @@
<!doctype html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>我的練習紀錄|水晶練習室</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet">
<link href="../css/style.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark sk-navbar sticky-top">
<div class="container">
<a class="navbar-brand" href="../index.html"><i class="bi bi-gem me-1"></i>水晶練習室</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#mainNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="mainNav">
<ul class="navbar-nav ms-auto align-items-lg-center gap-lg-1">
<li class="nav-item"><a class="nav-link" href="../index.html">練習室首頁</a></li>
<li class="nav-item"><a class="nav-link" href="../index.html#today">今日訓練</a></li>
<li class="nav-item"><a class="nav-link active" href="index.html">我的練習紀錄</a></li>
<li class="nav-item"><a class="nav-link disabled" href="#" aria-disabled="true">課程庫</a></li>
<li class="nav-item"><a class="nav-link" href="../index.html#progress">個人進度</a></li>
<li class="nav-item ms-lg-2">
<a class="btn btn-brand btn-sm" href="../admin/index.html">後台管理</a>
</li>
</ul>
</div>
</div>
</nav>
<main class="container py-5">
<div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-4">
<h1 class="h3 mb-0 text-white"><i class="bi bi-journal-text me-2"></i>我的練習紀錄</h1>
<div class="d-flex gap-2">
<a class="btn btn-brand" href="create.html"><i class="bi bi-plus-lg me-1"></i>新增練習紀錄</a>
<a class="btn btn-outline-brand" href="../index.html">回練習室首頁</a>
</div>
</div>
<div class="card sk-card sk-surface">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead>
<tr>
<th>編號</th>
<th>練習日期</th>
<th>訓練類型</th>
<th>練習歌曲</th>
<th>練習內容</th>
<th>練習時間</th>
<th>完成度</th>
<th class="text-end">操作</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>2026-07-16</td>
<td><span class="badge badge-type badge-type--dance">舞蹈</span></td>
<td>Road Fighter</td>
<td>整段主歌+副歌走位</td>
<td>40 分鐘</td>
<td>85%</td>
<td class="text-end">
<a class="btn btn-sm btn-outline-brand" href="show.html">查看</a>
<a class="btn btn-sm btn-outline-brand" href="edit.html">編輯</a>
<a class="btn btn-sm btn-outline-danger" href="delete.html">刪除</a>
</td>
</tr>
<tr>
<td>2</td>
<td>2026-07-15</td>
<td><span class="badge badge-type badge-type--vocal">vocal</span></td>
<td>Couple</td>
<td>副歌高音音準練習</td>
<td>30 分鐘</td>
<td>70%</td>
<td class="text-end">
<a class="btn btn-sm btn-outline-brand" href="show.html">查看</a>
<a class="btn btn-sm btn-outline-brand" href="edit.html">編輯</a>
<a class="btn btn-sm btn-outline-danger" href="delete.html">刪除</a>
</td>
</tr>
<tr>
<td>3</td>
<td>2026-07-14</td>
<td><span class="badge badge-type badge-type--rap">Rap</span></td>
<td>Chivalry</td>
<td>咬字與節奏卡點</td>
<td>25 分鐘</td>
<td>60%</td>
<td class="text-end">
<a class="btn btn-sm btn-outline-brand" href="show.html">查看</a>
<a class="btn btn-sm btn-outline-brand" href="edit.html">編輯</a>
<a class="btn btn-sm btn-outline-danger" href="delete.html">刪除</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</main>
<footer class="sk-footer text-center py-4">
<small>&copy; 2026 水晶練習室</small>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
-81
View File
@@ -1,81 +0,0 @@
<!doctype html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>練習紀錄詳情|水晶練習室</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet">
<link href="../css/style.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark sk-navbar sticky-top">
<div class="container">
<a class="navbar-brand" href="../index.html"><i class="bi bi-gem me-1"></i>水晶練習室</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#mainNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="mainNav">
<ul class="navbar-nav ms-auto align-items-lg-center gap-lg-1">
<li class="nav-item"><a class="nav-link" href="../index.html">練習室首頁</a></li>
<li class="nav-item"><a class="nav-link" href="../index.html#today">今日訓練</a></li>
<li class="nav-item"><a class="nav-link active" href="index.html">我的練習紀錄</a></li>
<li class="nav-item"><a class="nav-link disabled" href="#" aria-disabled="true">課程庫</a></li>
<li class="nav-item"><a class="nav-link" href="../index.html#progress">個人進度</a></li>
<li class="nav-item ms-lg-2">
<a class="btn btn-brand btn-sm" href="../admin/index.html">後台管理</a>
</li>
</ul>
</div>
</div>
</nav>
<main class="container py-5">
<h1 class="h3 mb-4 text-white"><i class="bi bi-journal-check me-2"></i>練習紀錄詳情</h1>
<div class="card sk-card sk-surface p-4" style="max-width: 42rem;">
<div class="d-flex align-items-center gap-2 mb-4">
<span class="badge badge-type badge-type--dance">舞蹈</span>
<span class="badge badge-status badge-status--done">已完成</span>
</div>
<dl class="row mb-0">
<dt class="col-4 text-body-secondary">練習日期</dt>
<dd class="col-8">2026-07-16</dd>
<dt class="col-4 text-body-secondary">訓練類型</dt>
<dd class="col-8">舞蹈</dd>
<dt class="col-4 text-body-secondary">練習歌曲</dt>
<dd class="col-8">Road Fighter</dd>
<dt class="col-4 text-body-secondary">練習時間</dt>
<dd class="col-8">40 分鐘</dd>
<dt class="col-4 text-body-secondary">完成度</dt>
<dd class="col-8">
<div class="progress sk-progress" style="max-width: 12rem;">
<div class="progress-bar" style="width: 85%" role="progressbar" aria-valuenow="85" aria-valuemin="0" aria-valuemax="100"></div>
</div>
</dd>
<dt class="col-4 text-body-secondary">練習心得</dt>
<dd class="col-8">隊形轉換卡點抓得比昨天準,副歌動作幅度還要再放大。</dd>
</dl>
<div class="d-flex gap-2 mt-4">
<a class="btn btn-brand" href="edit.html">編輯這筆紀錄</a>
<a class="btn btn-outline-brand" href="index.html">回練習紀錄列表</a>
</div>
</div>
</main>
<footer class="sk-footer text-center py-4">
<small>&copy; 2026 水晶練習室</small>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
-1
View File
@@ -1 +0,0 @@
@@ -1,724 +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 href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
<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">
<style>
/* ?€?€ Bootstrap ??敺???靽風嚗? index-bootstrap.html ??嚗?
Bootstrap Reboot ?函?璅惜?豢??券?閮?body嚗?憿?畾菔嚗€??嚗? ?ㄐ?典?????撖怠 Bootstrap 敺??璅惜?豢??函??蝡??祆?潘?
銝?韐? .section-title ?? class ?豢??剁?銝蔣?踵?隞見撘??€?€ */
body {
background-color: var(--color-bg);
color: var(--color-text);
font-family: 'Noto Sans TC', 'Microsoft JhengHei', system-ui, sans-serif;
line-height: 1.6;
}
h1,
h2,
h3,
h4,
h5,
h6,
p,
ul,
ol,
dl,
dd,
blockquote,
hr {
margin: 0;
padding: 0;
}
a {
color: var(--color-text);
text-decoration: none;
}
/* 瘞湔?梯?蝷橘?瘞湔?瑕酋?典誨?冽?銝?頩??港??€? 憿銝€敺?朣蝡?color-tokens嚗?-color-brand 蝟餃?嚗?銝?菔??*/
:root {
--dance-yellow: var(--color-brand);
--dance-yellow-light: var(--color-brand-light);
--dance-yellow-dark: var(--color-brand-dark);
--dance-ink: var(--color-brand-dark);
}
/* 憭扳?摨?嚗???函??蝷曉??閬死嚗??芾????銝??函??梁??header.jpg */
body.crystal-dance-page header.inner-page-header {
background-image: url('../assets/images/crystal-dance-hero.jpg');
background-position: center 68%;
}
/* ?€?€ Hero ?€嚗窒??inner-page-header ?嗡蜓璅??ㄐ?芣撘????批?閬踝? ?€?€ */
.crystal-dance-hero {
text-align: center;
}
.crystal-dance-hero .category-tag {
display: inline-block;
background: var(--dance-yellow-light);
color: var(--dance-ink);
padding: .35rem 1.1rem;
border-radius: 999px;
font-size: .8rem;
font-weight: 700;
letter-spacing: 1px;
margin-bottom: 1rem;
}
.crystal-dance-hero .hero-desc {
color: var(--color-text-muted);
line-height: 1.85;
max-width: 640px;
margin: 0 auto 1.5rem;
}
.crystal-dance-hero .quick-nav {
display: flex;
flex-wrap: nowrap;
overflow-x: auto;
justify-content: center;
gap: .6rem;
padding: .25rem 0 .5rem;
}
.crystal-dance-hero .quick-nav a {
white-space: nowrap;
text-decoration: none;
}
/* --- ?挾?賢?冽?憿見撘?--- */
.section-alt {
background: rgba(255, 204, 0, 0.08);
}
.section-kicker {
font-weight: 800;
color: var(--dance-yellow-dark);
letter-spacing: 2px;
font-size: .85rem;
text-transform: uppercase;
margin-bottom: .5rem;
}
.crystal-dance-page .section-title {
text-align: left;
margin-bottom: 1.1rem;
}
.section-lead {
color: var(--color-text-muted);
line-height: 1.85;
max-width: 760px;
}
.section-lead+.section-lead {
margin-top: .9rem;
}
/* --- 瘣餃?銝餉?閬?/ 銵典 Banner --- */
.event-banner {
background: linear-gradient(135deg, var(--dance-yellow) 0%, var(--dance-yellow-light) 100%);
border-radius: 24px;
padding: 2.25rem 2.5rem;
text-align: center;
color: var(--dance-ink);
box-shadow: 0 12px 30px rgba(255, 204, 0, .25);
}
.event-banner .event-badge {
display: inline-block;
background: var(--dance-ink);
color: var(--dance-yellow-light);
padding: .35rem 1.1rem;
border-radius: 999px;
font-size: .8rem;
font-weight: 700;
letter-spacing: 1px;
margin-bottom: .9rem;
}
.event-banner h2 {
font-weight: 900;
letter-spacing: 3px;
font-size: 1.9rem;
margin-bottom: .6rem;
}
.event-banner p {
margin: 0 auto;
font-weight: 600;
letter-spacing: .5px;
max-width: 640px;
}
/* --- ?梯?蝷曆?蝝對??摮?chip --- */
.keyword-row {
margin-top: 1.5rem;
}
.keyword-chip {
background: #fff;
border: 1.5px solid var(--dance-yellow);
border-radius: 999px;
padding: .5rem 1.1rem;
font-weight: 700;
color: var(--dance-ink);
display: inline-flex;
align-items: center;
gap: .4rem;
}
/* --- 20TH 敶梁??“ --- */
.video-card {
background: #fff;
border: 1.5px solid var(--dance-yellow);
border-radius: 16px;
padding: 1rem;
height: 100%;
box-shadow: 0 6px 18px rgba(255, 204, 0, .1);
}
.video-tag {
display: inline-block;
background: var(--dance-yellow-dark);
color: #fff;
font-weight: 700;
font-size: .75rem;
padding: .3rem .8rem;
border-radius: 999px;
margin-bottom: .75rem;
}
.video-card .ratio {
border-radius: 10px;
overflow: hidden;
}
/* --- 蝬??€ --- */
.song-card {
background: #fff;
border: 1.5px solid var(--dance-yellow);
border-radius: 16px;
padding: 1.5rem;
height: 100%;
box-shadow: 0 6px 18px rgba(255, 204, 0, .1);
}
.song-card h4 {
color: var(--dance-yellow-dark);
font-weight: 800;
margin-bottom: .8rem;
}
.song-card .song-meta {
font-size: .78rem;
font-weight: 800;
color: var(--dance-ink);
background: var(--dance-yellow-light);
display: inline-block;
padding: .15rem .6rem;
border-radius: 999px;
margin-bottom: .4rem;
}
.song-card p {
font-size: .9rem;
color: var(--color-text-muted);
line-height: 1.7;
margin-bottom: .9rem;
}
/* --- 30TH ?隡? --- */
.countdown-strip {
background: var(--dance-ink);
color: var(--dance-yellow-light);
border-radius: 16px;
padding: .9rem 1.5rem;
display: inline-flex;
align-items: center;
gap: .6rem;
font-weight: 800;
letter-spacing: 1px;
margin-top: 1.5rem;
}
/* --- 30TH ???銵典 --- */
.form-intro {
background: var(--color-surface-soft);
border-left: 4px solid var(--dance-yellow-dark);
padding: 1rem 1.25rem;
border-radius: 0 12px 12px 0;
color: var(--dance-ink);
font-weight: 600;
margin-bottom: 1.75rem;
}
.form-panel {
background: #fff;
border: 1.5px solid var(--dance-yellow);
border-radius: 20px;
padding: 2rem;
height: 100%;
box-shadow: 0 8px 24px rgba(255, 204, 0, .12);
}
.panel-title {
font-weight: 800;
color: var(--dance-yellow-dark);
margin-bottom: 1.5rem;
font-size: 1.3rem;
}
.form-label {
font-weight: 700;
color: var(--dance-ink);
}
.form-control,
.form-select {
border: 1.5px solid var(--color-brand-light);
border-radius: 12px;
padding: .65rem .9rem;
}
.form-control:focus,
.form-select:focus {
border-color: var(--dance-yellow-dark);
box-shadow: 0 0 0 .2rem rgba(255, 204, 0, .25);
}
.form-control[type="file"] {
border-style: dashed;
cursor: pointer;
}
.form-text {
color: var(--color-text-muted);
}
/* 頨思遢 / ?閎隞交??? radio?heckbox ?嚗??身憭??湔餈?撖虫??? */
.btn-outline-dance {
border: 1.5px solid var(--dance-yellow-dark);
color: var(--dance-yellow-dark);
border-radius: 999px;
font-weight: 600;
padding: .4rem 1.15rem;
}
.btn-check:checked+.btn-outline-dance,
.btn-outline-dance:hover {
background: var(--dance-yellow);
color: var(--dance-ink);
border-color: var(--dance-yellow-dark);
}
.btn-dance {
background: var(--dance-yellow);
color: var(--dance-ink);
border: 2px solid var(--dance-yellow-dark);
border-radius: 999px;
font-weight: 800;
padding: .75rem;
}
.btn-dance:hover {
background: var(--dance-yellow-dark);
color: #fff;
}
.btn-reset-dance {
background: #fff;
color: var(--dance-yellow-dark);
border: 2px solid var(--dance-yellow-dark);
border-radius: 999px;
font-weight: 700;
padding: .75rem;
}
.btn-reset-dance:hover {
background: #fdecec;
border-color: #dc3545;
color: #dc3545;
}
/* --- 憛怠神???--- */
.info-card {
background: linear-gradient(160deg, var(--dance-ink) 0%, var(--color-bg-raised) 100%);
color: var(--dance-yellow-light);
border-radius: 20px;
padding: 2rem;
height: 100%;
box-shadow: 0 8px 24px rgba(78, 73, 43, .3);
}
.info-tag {
display: inline-block;
background: var(--dance-yellow);
color: var(--dance-ink);
padding: .3rem 1rem;
border-radius: 999px;
font-size: .75rem;
font-weight: 700;
margin-bottom: 1rem;
}
.info-card h3 {
color: #fff;
font-weight: 800;
margin-bottom: 1rem;
}
.info-list {
list-style: none;
padding: 0;
margin: 0 0 1.5rem;
display: flex;
flex-direction: column;
gap: .6rem;
}
.info-list li {
background: rgba(255, 255, 255, .08);
padding: .6rem 1rem;
border-radius: 12px;
font-weight: 600;
font-size: .9rem;
line-height: 1.5;
}
.info-footer {
display: flex;
flex-wrap: wrap;
gap: .5rem;
}
.mini-badge {
border: 1px solid var(--dance-yellow);
color: var(--dance-yellow-light);
padding: .25rem .8rem;
border-radius: 999px;
font-size: .75rem;
}
@media (max-width: 575.98px) {
.event-banner {
padding: 1.75rem 1.25rem;
border-radius: 18px;
}
.event-banner h2 {
font-size: 1.5rem;
letter-spacing: 1px;
}
.form-panel,
.info-card {
padding: 1.5rem;
}
}
</style>
</head>
<body class="crystal-dance-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">SECHSKIES DANCE ARCHIVE &amp; 30TH FAN PROJECT</span>
</div>
</div>
</header>
<nav class="portal-nav" aria-label="瘞湔?瑕酋?典誨?典?閬? data-portal-nav></nav>
<canvas id="particles"></canvas>
<main class="content-container crystal-dance-main">
<section class="crystal-dance-hero">
<span class="category-tag">瘞湔?瑕酋?典誨?具???隡?</span>
<p class="hero-desc">瘞湔?梯?蝷暹瘞湔?瑕酋?典誨?其葉??頩??港???敺?20TH
?啁撠?銝脩??航???擃??嗅?潘??渡?蝬??蕃頝喟????銵?嚗蒂????2027/04/15 30TH ??????€€?/p>
<nav class="quick-nav" aria-label="?撠汗">
<a href="#about" class="btn btn-outline-dance btn-sm">02 ?梯?蝷曆?蝝?/a>
<a href="#throwback-20th" class="btn btn-outline-dance btn-sm">03 20TH 敶梁??“</a>
<a href="#song-archive" class="btn btn-outline-dance btn-sm">04 蝬??€</a>
<a href="#project-30th" class="btn btn-outline-dance btn-sm">05 30TH ?隡?</a>
<a href="#form-30th" class="btn btn-outline-dance btn-sm">06 ???銵典</a>
</nav>
</section>
<!-- 02 ?梯?蝷曆?蝝?-->
<section id="about" class="section-alt">
<div class="section-kicker">02 繚 ?梯?蝷曆?蝝?/div>
<h2 class="section-title">瘞湔?梯?蝷曆??臭??祉冗??/h2>
<p class="section-lead">瘞湔?梯?蝷曆??舐蝡?蝡?蝷曉?嚗€?偌?嗥摮拇撱???銝????蕃頝喋€??渲?敶梁?蝝€??憿€?航? SECHSKIES
????毀蝧€??曇?蝝€???賣?鋡急?脤€€??ㄐ嚗甇瑕??啁撠??亙??渡??辣蝥€?/p>
<div class="keyword-row d-flex flex-wrap gap-2">
<span class="keyword-chip">?? 蝬?</span>
<span class="keyword-chip">? ???</span>
<span class="keyword-chip">? 蝧餉歲蝝€??/span>
<span class="keyword-chip">?? 銝脩??航?</span>
<span class="keyword-chip">?? ?啁撠?</span>
</div>
</section>
<!-- 03 20TH 敶梁??“ -->
<section id="throwback-20th">
<div class="section-kicker">03 繚 20TH 敶梁??“</div>
<h2 class="section-title">20TH 敶梁??“</h2>
<p class="section-lead">2017 撟湛??啁撠?隞交偌?嗥摮拙??20 ?典僑?箏?璈?蝐齒鈭??豢??脖葡????氬€€??芣銝€甈∟歲?暑??????乓€?敺€?甇貉???賊?瞈葬?典?銝€?臬蔣?ㄐ??擃??嗚€?/p>
<p class="section-lead">瘞湔?梯?蝷曉??辣蝥€遢閮嚗? 20TH ?銵€銵??渡??隞亥◤?活??????銋 2027/04/15 ??30TH ?隡????啁?????/p>
<div class="row g-4 mt-1">
<div class="col-12 col-sm-6">
<div class="video-card">
<span class="video-tag">20TH ??敶梁?</span>
<div class="ratio ratio-16x9">
<iframe src="https://www.youtube.com/embed/ARi0cfpv6mE" title="20TH ??敶梁?"
allowfullscreen></iframe>
</div>
</div>
</div>
<div class="col-12 col-sm-6">
<div class="video-card">
<span class="video-tag">20TH 甇??敶梁?</span>
<div class="ratio ratio-16x9">
<iframe src="https://www.youtube.com/embed/fkJmOMPirBc" title="20TH 甇??敶梁?"
allowfullscreen></iframe>
</div>
</div>
</div>
</div>
</section>
<!-- 04 蝬??€ -->
<section id="song-archive" class="section-alt">
<div class="section-kicker">04 繚 蝬??€</div>
<h2 class="section-title">蝬??€</h2>
<p class="section-lead">隞乩??撟暸??冽??渲?蝧餉歲銝剜?撣詨?曄??嚗???30TH 蝺渡??葡??楊???€??柴€?/p>
<div class="row g-4 mt-1">
<div class="col-12 col-sm-6 col-lg-3">
<div class="song-card">
<span class="song-meta">??啗情</span>
<h4>Couple</h4>
<p>頛翰???望??嚗??貊??犖鈭?韏唬?嚗虜鋡怠?脫風撟渡????嗆捏?毽?芸蔣?ㄐ??/p>
<span class="song-meta">?拙???</span>
<p>??憟質???敶Z???銴?嚗??拙??嗡??????€?脩??/p>
</div>
</div>
<div class="col-12 col-sm-6 col-lg-3">
<div class="song-card">
<span class="song-meta">??啗情</span>
<h4>Com' Back</h4>
<p>?飛?隞?”?脖?銝€嚗?憟?敹怒€??撘瘀?敺捆?葆??湔除瘞€?/p>
<span class="song-meta">?拙???</span>
<p>?舀????折?嚗停蝞蝚砌?甈∪????渡?撠?銋敹恍€?銝€?/p>
</div>
</div>
<div class="col-12 col-sm-6 col-lg-3">
<div class="song-card">
<span class="song-meta">??啗情</span>
<h4>Road Fighter</h4>
<p>??撟漲憭扼€?憟?敹恬??舫???暺??€?嚗??唳除??頞喋€?/p>
<span class="song-meta">?拙???</span>
<p>?拙?撌脫??箇????€?蝺渲絲靘??擛交?嚗??拙??曉銝脩???頠詻€?/p>
</div>
</div>
<div class="col-12 col-sm-6 col-lg-3">
<div class="song-card">
<span class="song-meta">??啗情</span>
<h4>Pom Saeng Pom Sa</h4>
<p>??敺??朣?敶g??舫€?甇甇瑕僑蝧餉歲銝剛◤??€憭活????銝€??/p>
<span class="song-meta">?拙???</span>
<p>颲刻?摨阡?嚗?其葡???銝剜挾敺捆???勗?湔除瘞€?/p>
</div>
</div>
</div>
</section>
<!-- 05 30TH ?隡? -->
<section id="project-30th">
<div class="section-kicker">05 繚 30TH ?隡?</div>
<h2 class="section-title">30TH ???</h2>
<p class="section-lead">2027/04/15 ??SECHSKIES ?粹? 30 ?典僑?摮€偌?嗥?冗????30TH
???唳??頩??游摰對????喃?韏瑕????啁撠?嚗?蝬??蕃頝喟毀蝧€蔣?頛臬蟡???嚗??啁??銵?敺€遢銵典??蝝舐???/p>
<p class="section-lead">雿??€閬???撠勗??歲???芾???敺偌?嗥摮拍?????莎?????雿???祇?嚗?臭誑?券€ㄐ??雿????孵???/p>
<div class="countdown-strip" id="countdown">?? 頝 30TH 繚 2027/04/15 ?閮?銝?..</div>
</section>
<!-- 06 30TH ???銵典 -->
<section id="form-30th" class="section-alt">
<div class="section-kicker">06 繚 30TH ???銵典</div>
<div class="event-banner mb-4">
<span class="event-badge">?? SECHSKIES 30TH 繚 2027.04.15</span>
<h2>30TH ???銵典</h2>
<p>?遢銵典?舀偌?嗥?冗??2027/04/15 30TH ?????游??芋蝯??其??渡????蝺渡?甇??舀?摰寡??喟?銝?蟡???/p>
</div>
<div class="row g-4">
<div class="col-12 col-sm-7">
<div class="form-panel">
<h3 class="panel-title">?? ???鞈?</h3>
<form action="#">
<div class="row g-3">
<div class="col-12 col-sm-6">
<label for="nickname" class="form-label">?梁迂</label>
<input type="text" class="form-control" id="nickname" name="nickname"
placeholder="靘??梯?撠?" required>
</div>
<div class="col-12 col-sm-6">
<label for="email" class="form-label">Email</label>
<input type="email" class="form-control" id="email" name="email"
placeholder="dance@example.com" required>
</div>
<div class="col-12 col-sm-6">
<label for="dance_years" class="form-label">蝺渲?蝬?撟湔</label>
<input type="number" class="form-control" id="dance_years" name="dance_years"
min="0" max="30" placeholder="0">
</div>
<div class="col-12 col-sm-6">
<label for="available_date" class="form-label">?臬????/label>
<input type="date" class="form-control" id="available_date" name="available_date">
</div>
<div class="col-12">
<label class="form-label d-block">?臬????20TH</label>
<div class="d-flex flex-wrap gap-2">
<input type="radio" class="btn-check" name="attended_20th"
id="attended_20th_yes" value="attended" autocomplete="off">
<label class="btn btn-outline-dance" for="attended_20th_yes">????20TH</label>
<input type="radio" class="btn-check" name="attended_20th"
id="attended_20th_video" value="watched_video" autocomplete="off">
<label class="btn btn-outline-dance" for="attended_20th_video">?? 20TH
敶梁?</label>
<input type="radio" class="btn-check" name="attended_20th"
id="attended_20th_first" value="first_time" autocomplete="off">
<label class="btn btn-outline-dance" for="attended_20th_first">蝚砌?甈∠??/label>
</div>
</div>
<div class="col-12">
<label class="form-label d-block">?喳??? 30TH ?批捆嚗銴嚗?/label>
<div class="d-flex flex-wrap gap-2">
<input type="checkbox" class="btn-check" name="participate_content"
id="content_cover" value="cover" autocomplete="off">
<label class="btn btn-outline-dance" for="content_cover">蝧餉歲</label>
<input type="checkbox" class="btn-check" name="participate_content"
id="content_medley" value="medley" autocomplete="off">
<label class="btn btn-outline-dance" for="content_medley">銝脩??航?</label>
<input type="checkbox" class="btn-check" name="participate_content"
id="content_blessing" value="blessing" autocomplete="off">
<label class="btn btn-outline-dance" for="content_blessing">蟡?敶梁?</label>
<input type="checkbox" class="btn-check" name="participate_content"
id="content_graphic" value="graphic" autocomplete="off">
<label class="btn btn-outline-dance" for="content_graphic">???</label>
<input type="checkbox" class="btn-check" name="participate_content"
id="content_editing" value="editing" autocomplete="off">
<label class="btn btn-outline-dance" for="content_editing">敶梁??芾摩</label>
<input type="checkbox" class="btn-check" name="participate_content"
id="content_archive" value="archive" autocomplete="off">
<label class="btn btn-outline-dance" for="content_archive">鞈??渡?</label>
</div>
</div>
<div class="col-12 col-sm-6">
<label for="song" class="form-label">?喟毀????/label>
<select class="form-select" id="song" name="song">
<option value="" selected>隢????/option>
<option value="couple">Couple</option>
<option value="comeback">Com' Back</option>
<option value="road_fighter">Road Fighter</option>
<option value="pom_saeng_pom_sa">Pom Saeng Pom Sa</option>
<option value="other">?嗡?</option>
</select>
</div>
<div class="col-12 col-sm-6">
<label for="reference_file" class="form-label">銝?€蔣????</label>
<input class="form-control" type="file" id="reference_file" name="reference_file"
accept="image/*,video/*">
<div class="form-text">蝧餉歲敶梁????啁???芾摩蝝??</div>
</div>
<div class="col-12">
<label for="message_30th" class="form-label">?喳? 30TH 隤芰?閰?/label>
<textarea class="form-control" id="message_30th" name="message_30th" rows="4"
placeholder="?喳?瘞湔?梯?蝷曇牧?店?鞎Y??€閬隤啜€????蝳€色€?></textarea>
</div>
</div>
<div class="d-grid d-sm-flex gap-3 mt-4">
<button type="reset"
class="btn btn-reset-dance flex-fill order-2 order-sm-1">?憛怠神</button>
<button type="submit"
class="btn btn-dance flex-fill order-1 order-sm-2">??銵典 ??</button>
</div>
</form>
</div>
</div>
<div class="col-12 col-sm-5">
<div class="info-card">
<span class="info-tag">憛怠神?</span>
<h3>???蝣箄?銝€銝?/h3>
<ul class="info-list">
<li>?? 瑼??澆?嚗蔣?????嚗遣霅啣瑼?100MB 隞亙</li>
<li>?? ?遢鞈???瘞湔?梯?蝷曉?冽??30TH ?隡?雿輻</li>
<li>?? ?敺蝣箏???嚗?敺??西??砍?蝺渡???鋆賣???/li>
</ul>
<div class="info-footer">
<span class="mini-badge">瘞湔?瑕酋?典誨??/span>
<span class="mini-badge">30TH ?隡?</span>
</div>
</div>
</div>
</div>
</section>
</main>
<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>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"></script>
<script src="../assets/js/particles.js"></script>
<script src="../assets/js/portal-nav.js"></script>
<script>
(function () {
var target = new Date('2027-04-15T00:00:00+08:00');
var now = new Date();
var diffDays = Math.ceil((target - now) / (1000 * 60 * 60 * 24));
var el = document.getElementById('countdown');
if (el) {
el.textContent = diffDays > 0
? '?? 頝 30TH 繚 2027/04/15 ?? ' + diffDays + ' 憭?
: '?? 30TH 繚 2027/04/15 撌脩??唬?嚗?韏瑕???嚗?;
}
})();
</script>
</body>
</html>
-170
View File
@@ -1,170 +0,0 @@
<?php
declare(strict_types=1);
$config = require __DIR__ . '/../admin/config.php';
function return_to_dance_form(string $result): never
{
header('Location: crystal-dance-survey.html?form=' . rawurlencode($result) . '#form-30th', true, 303);
exit;
}
function post_value(string $key, int $maxLength): string
{
$value = trim((string) ($_POST[$key] ?? ''));
return mb_substr($value, 0, $maxLength, 'UTF-8');
}
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
header('Allow: POST');
exit('Method Not Allowed');
}
$maxUploadBytes = (int) $config['dance_max_upload_bytes'];
$contentLength = (int) ($_SERVER['CONTENT_LENGTH'] ?? 0);
if ($contentLength > $maxUploadBytes + 1024 * 1024) {
return_to_dance_form('file_too_large');
}
// 隱藏欄位有內容時視為機器送件;不透露攔截規則。
if (trim((string) ($_POST['website'] ?? '')) !== '') {
return_to_dance_form('success');
}
$nickname = post_value('nickname', 80);
$email = mb_strtolower(post_value('email', 190), 'UTF-8');
$danceYearsRaw = trim((string) ($_POST['dance_years'] ?? ''));
$availableDate = trim((string) ($_POST['available_date'] ?? ''));
$attended20th = trim((string) ($_POST['attended_20th'] ?? ''));
$song = trim((string) ($_POST['song'] ?? ''));
$message30th = post_value('message_30th', 5000);
$allowedAttendance = ['attended', 'watched_video', 'first_time'];
$allowedContent = ['cover', 'medley', 'blessing', 'graphic', 'editing', 'archive'];
$allowedSongs = ['', 'couple', 'comeback', 'road_fighter', 'pom_saeng_pom_sa', 'other'];
$selectedContent = $_POST['participate_content'] ?? [];
if (!is_array($selectedContent)) {
$selectedContent = [$selectedContent];
}
$selectedContent = array_values(array_unique(array_intersect(
$allowedContent,
array_map(static fn ($value): string => trim((string) $value), $selectedContent)
)));
if ($nickname === '' || !filter_var($email, FILTER_VALIDATE_EMAIL) || !$selectedContent) {
return_to_dance_form('invalid');
}
if ($attended20th !== '' && !in_array($attended20th, $allowedAttendance, true)) {
return_to_dance_form('invalid');
}
if (!in_array($song, $allowedSongs, true)) {
return_to_dance_form('invalid');
}
$danceYears = null;
if ($danceYearsRaw !== '') {
$danceYearsFilter = filter_var($danceYearsRaw, FILTER_VALIDATE_INT, ['options' => ['min_range' => 0, 'max_range' => 30]]);
if ($danceYearsFilter === false) {
return_to_dance_form('invalid');
}
$danceYears = (int) $danceYearsFilter;
}
if ($availableDate !== '') {
$date = DateTimeImmutable::createFromFormat('!Y-m-d', $availableDate);
if (!$date || $date->format('Y-m-d') !== $availableDate) {
return_to_dance_form('invalid');
}
}
$db = $config['db'];
$dsn = "mysql:host={$db['host']};port={$db['port']};dbname={$db['name']};charset=utf8mb4";
try {
$pdo = new PDO($dsn, $db['user'], $db['pass'], [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]);
} catch (PDOException) {
return_to_dance_form('unavailable');
}
$submittedIp = mb_substr((string) ($_SERVER['REMOTE_ADDR'] ?? ''), 0, 45, 'UTF-8');
if ($submittedIp !== '') {
$rate = $pdo->prepare('SELECT COUNT(*) FROM dance_applications WHERE submitted_ip = ? AND created_at >= DATE_SUB(NOW(), INTERVAL 2 MINUTE)');
$rate->execute([$submittedIp]);
if ((int) $rate->fetchColumn() >= 3) {
return_to_dance_form('too_many');
}
}
$storedFileName = null;
$originalFileName = null;
$mimeType = null;
$fileSize = null;
$file = $_FILES['reference_file'] ?? null;
if (is_array($file) && (int) ($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_NO_FILE) {
if ((int) $file['error'] !== UPLOAD_ERR_OK || (int) $file['size'] > $maxUploadBytes) {
return_to_dance_form('file_too_large');
}
$allowedMimeTypes = [
'image/jpeg' => 'jpg',
'image/png' => 'png',
'image/gif' => 'gif',
'image/webp' => 'webp',
'video/mp4' => 'mp4',
'video/webm' => 'webm',
'video/quicktime' => 'mov',
];
$mimeType = (new finfo(FILEINFO_MIME_TYPE))->file((string) $file['tmp_name']) ?: '';
if (!isset($allowedMimeTypes[$mimeType])) {
return_to_dance_form('file_type');
}
$uploadDir = (string) $config['dance_upload_dir'];
if (!is_dir($uploadDir) && !mkdir($uploadDir, 0750, true) && !is_dir($uploadDir)) {
return_to_dance_form('unavailable');
}
$storedFileName = bin2hex(random_bytes(20)) . '.' . $allowedMimeTypes[$mimeType];
if (!move_uploaded_file((string) $file['tmp_name'], $uploadDir . DIRECTORY_SEPARATOR . $storedFileName)) {
return_to_dance_form('unavailable');
}
$originalFileName = mb_substr(basename((string) $file['name']), 0, 255, 'UTF-8');
$fileSize = (int) $file['size'];
}
try {
$stmt = $pdo->prepare(
'INSERT INTO dance_applications
(nickname, email, dance_years, available_date, attended_20th, participate_content, song,
reference_file_name, reference_original_name, reference_mime_type, reference_file_size,
message_30th, submitted_ip, user_agent)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
);
$stmt->execute([
$nickname,
$email,
$danceYears,
$availableDate !== '' ? $availableDate : null,
$attended20th !== '' ? $attended20th : null,
implode(',', $selectedContent),
$song !== '' ? $song : null,
$storedFileName,
$originalFileName,
$mimeType,
$fileSize,
$message30th !== '' ? $message30th : null,
$submittedIp !== '' ? $submittedIp : null,
mb_substr((string) ($_SERVER['HTTP_USER_AGENT'] ?? ''), 0, 255, 'UTF-8'),
]);
} catch (PDOException) {
if ($storedFileName !== null) {
@unlink((string) $config['dance_upload_dir'] . DIRECTORY_SEPARATOR . $storedFileName);
}
return_to_dance_form('unavailable');
}
return_to_dance_form('success');
-52
View File
@@ -1,52 +0,0 @@
<?php
declare(strict_types=1);
header('Content-Type: application/json; charset=utf-8');
header('Cache-Control: no-store');
$config = require __DIR__ . '/../admin/config.php';
$db = $config['db'];
$dsn = "mysql:host={$db['host']};port={$db['port']};dbname={$db['name']};charset=utf8mb4";
try {
$pdo = new PDO($dsn, $db['user'], $db['pass'], [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]);
$rows = $pdo->query(
"SELECT id, title, tags, practice_focus, video_url, difficulty, progress_status,
note_text, is_featured, sort_order, status
FROM dance_practice_items
WHERE status IN ('published', 'coming_soon')
ORDER BY sort_order, id"
)->fetchAll();
} catch (PDOException) {
http_response_code(503);
echo json_encode(['ok' => false, 'items' => []], JSON_UNESCAPED_UNICODE);
exit;
}
$difficultyLabels = ['easy' => '入門', 'medium' => '中等', 'hard' => '挑戰'];
$progressLabels = ['not_started' => '未開始', 'practicing' => '練習中', 'review' => '待複習', 'completed' => '已完成'];
$items = array_map(static function (array $row) use ($difficultyLabels, $progressLabels): array {
$videoUrl = trim((string) ($row['video_url'] ?? ''));
if ($videoUrl !== '' && (!filter_var($videoUrl, FILTER_VALIDATE_URL) || !preg_match('/^https?:\/\//i', $videoUrl))) {
$videoUrl = '';
}
return [
'id' => (int) $row['id'],
'title' => (string) $row['title'],
'tags' => array_values(array_filter(array_map('trim', explode(',', (string) ($row['tags'] ?? ''))))),
'practiceFocus' => (string) $row['practice_focus'],
'videoUrl' => $videoUrl,
'difficulty' => $difficultyLabels[$row['difficulty']] ?? (string) $row['difficulty'],
'progress' => $progressLabels[$row['progress_status']] ?? (string) $row['progress_status'],
'note' => (string) ($row['note_text'] ?? ''),
'featured' => (bool) $row['is_featured'],
'comingSoon' => $row['status'] === 'coming_soon',
];
}, $rows);
echo json_encode(['ok' => true, 'items' => $items], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);