docs: 加入三個原版對照資料夾

This commit is contained in:
2026-08-04 16:27:02 +08:00
parent 52e59d90cf
commit 5094eae2fe
32 changed files with 117 additions and 5408 deletions
@@ -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>
-722
View File
@@ -1,722 +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 Reboot 還原 */
body.crystal-dance-page {
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;
}
/* 標題與內文:拉開垂直節奏,避免擠在一起 */
body.crystal-dance-page .section-title {
margin-bottom: 2rem;
}
body.crystal-dance-page .section-title::after {
margin-top: 1rem;
}
body.crystal-dance-page .section-body {
max-width: 760px;
margin: 0 auto 2.5rem;
}
body.crystal-dance-page .section-body p,
body.crystal-dance-page .form-lead {
color: var(--color-text-muted);
line-height: 1.9;
text-align: left;
margin-bottom: 1.15rem;
}
body.crystal-dance-page .section-body p:last-child {
margin-bottom: 0;
}
body.crystal-dance-page .form-lead {
max-width: 760px;
margin: 0 auto 2.5rem;
}
/* ① 認識區 */
body.crystal-dance-page #about {
text-align: center;
padding-bottom: 0;
overflow: hidden;
}
body.crystal-dance-page .category-tag {
display: inline-block;
background: var(--color-brand-light);
color: var(--color-brand-dark);
padding: 0.35rem 1.15rem;
border-radius: 999px;
font-size: 0.78rem;
font-weight: 700;
letter-spacing: 1px;
margin-bottom: 1.5rem;
}
body.crystal-dance-page .hero-banner {
display: block;
width: 100%;
max-width: 100%;
aspect-ratio: 21 / 9;
object-fit: cover;
border-radius: 12px;
border: 1px solid rgba(255, 204, 0, 0.22);
margin: 0 auto 2.25rem;
}
body.crystal-dance-page #about .section-title {
margin-top: 0.25rem;
}
body.crystal-dance-page .keyword-row {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 0.6rem;
margin: 2.25rem auto 2.5rem;
max-width: 640px;
}
body.crystal-dance-page .keyword-chip {
display: inline-flex;
align-items: center;
gap: 0.25rem;
background: var(--color-brand-light);
color: var(--color-brand-dark);
font-size: 0.82rem;
font-weight: 700;
padding: 0.4rem 0.9rem;
border-radius: 999px;
border: 1px solid rgba(255, 204, 0, 0.35);
}
/* 頁內導覽:深色底條(對照參考圖) */
body.crystal-dance-page .page-nav-bar {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
gap: 0.25rem 0;
margin: 0 -36px;
padding: 0.95rem 1.5rem;
background: var(--color-bg-raised);
border-top: 1px solid rgba(255, 204, 0, 0.2);
}
body.crystal-dance-page .page-nav-bar a {
color: var(--color-brand);
font-size: 0.82rem;
font-weight: 700;
letter-spacing: 0.5px;
padding: 0.2rem 0.85rem;
border-right: 1px solid rgba(255, 204, 0, 0.25);
transition: color 0.2s ease;
}
body.crystal-dance-page .page-nav-bar a:last-child {
border-right: none;
}
body.crystal-dance-page .page-nav-bar a:hover {
color: var(--color-text-inverse);
}
/* ② 影片區:主影片與下方三欄同寬對齊 */
body.crystal-dance-page .video-grid {
width: 100%;
}
body.crystal-dance-page .sub-section-title {
margin-top: 3rem;
margin-bottom: 1.25rem;
font-weight: 800;
color: var(--color-brand-dark);
font-size: 1.05rem;
text-align: center;
letter-spacing: 0.08em;
}
body.crystal-dance-page .video-label {
display: block;
color: var(--color-brand-dark);
font-weight: 800;
font-size: 0.9rem;
margin-bottom: 0.75rem;
}
body.crystal-dance-page .video-feature {
width: 100%;
margin-bottom: 0;
}
body.crystal-dance-page .video-feature .video-label {
text-align: left;
}
body.crystal-dance-page .video-feature .ratio,
body.crystal-dance-page .video-thumb .ratio {
border-radius: 10px;
overflow: hidden;
border: 1px solid rgba(255, 204, 0, 0.18);
}
body.crystal-dance-page .video-thumb {
height: 100%;
}
body.crystal-dance-page .video-thumb .video-label {
font-size: 0.85rem;
margin-bottom: 0.6rem;
}
body.crystal-dance-page .countdown-note {
display: block;
margin-top: 2.5rem;
text-align: center;
font-weight: 700;
color: var(--color-brand-dark);
font-size: 0.95rem;
letter-spacing: 0.5px;
padding: 0.75rem 1rem;
background: var(--color-surface-soft);
border-radius: 999px;
max-width: 420px;
margin-left: auto;
margin-right: auto;
}
/* ③ summer-form:分階段邊框排版(風格參考見面會應援調查表,顏色用全站 token) */
body.crystal-dance-page .summer-form {
max-width: 720px;
margin: 0 auto;
}
body.crystal-dance-page .summer-form__stage + .summer-form__stage {
margin-top: 2rem;
}
body.crystal-dance-page .summer-form__stage-title {
font-weight: 800;
color: var(--color-brand-dark);
font-size: 0.95rem;
letter-spacing: 0.04em;
margin-bottom: 0.75rem;
text-align: left;
}
body.crystal-dance-page .summer-form__stage-body {
border: 1px solid rgba(255, 204, 0, 0.35);
border-radius: 12px;
padding: 1.5rem 1.75rem;
background: #fff;
}
body.crystal-dance-page .summer-form__stage-body--notice {
background: var(--color-surface-soft);
}
body.crystal-dance-page .summer-form__stage-body .row {
--bs-gutter-y: 1.35rem;
}
body.crystal-dance-page .summer-form .form-label {
font-weight: 700;
color: var(--color-brand-dark);
font-size: 0.9rem;
margin-bottom: 0.5rem;
}
body.crystal-dance-page .summer-form .form-control,
body.crystal-dance-page .summer-form .form-select {
border: 1px solid rgba(255, 204, 0, 0.35);
border-radius: 10px;
padding: 0.65rem 0.9rem;
background: #fff;
}
body.crystal-dance-page .summer-form .form-control:focus,
body.crystal-dance-page .summer-form .form-select:focus {
border-color: var(--color-brand);
box-shadow: 0 0 0 0.2rem rgba(255, 204, 0, 0.2);
}
body.crystal-dance-page .summer-form .form-control[type="file"] {
border-style: dashed;
cursor: pointer;
background: var(--color-surface-soft);
}
body.crystal-dance-page .summer-form .form-text {
color: var(--color-text-muted);
font-size: 0.82rem;
margin-top: 0.35rem;
}
body.crystal-dance-page .summer-form .btn-outline-dance {
border: 1.5px solid var(--color-brand-dark);
color: var(--color-brand-dark);
border-radius: 999px;
font-weight: 600;
font-size: 0.88rem;
padding: 0.38rem 1rem;
background: #fff;
}
body.crystal-dance-page .summer-form .btn-check:checked + .btn-outline-dance,
body.crystal-dance-page .summer-form .btn-outline-dance:hover {
background: var(--color-brand);
color: var(--color-brand-dark);
border-color: var(--color-brand-dark);
}
body.crystal-dance-page .summer-form__option-row {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-top: 0.15rem;
}
body.crystal-dance-page .summer-form .info-list {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 0.85rem;
}
body.crystal-dance-page .summer-form .info-list li {
color: var(--color-text-muted);
font-size: 0.9rem;
line-height: 1.75;
}
body.crystal-dance-page .summer-form .info-footer-text {
color: var(--color-text-muted);
font-size: 0.8rem;
opacity: 0.85;
margin-top: 1rem;
}
body.crystal-dance-page .summer-form__actions {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.75rem;
margin-top: 2rem;
}
body.crystal-dance-page .summer-form__feedback {
max-width: 860px;
margin: 0 auto 20px;
padding: 14px 18px;
border: 1px solid rgba(78, 73, 43, 0.3);
border-radius: 10px;
color: var(--color-text-dark);
background: var(--color-surface-soft);
text-align: left;
}
body.crystal-dance-page .summer-form__feedback--success {
border-color: var(--color-brand);
background: var(--color-brand-light);
}
body.crystal-dance-page .summer-form__feedback--error {
border-color: var(--color-brand-dark);
}
body.crystal-dance-page .summer-form__trap {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
body.crystal-dance-page .summer-form .btn-dance {
background: var(--color-brand);
color: var(--color-brand-dark);
border: none;
border-radius: 999px;
font-weight: 800;
padding: 0.8rem 1.5rem;
letter-spacing: 0.5px;
}
body.crystal-dance-page .summer-form .btn-dance:hover {
background: var(--color-brand-dark);
color: var(--color-text-inverse);
}
body.crystal-dance-page .summer-form .btn-reset-dance {
background: #fff;
color: var(--color-brand-dark);
border: 1.5px solid var(--color-brand-dark);
border-radius: 999px;
font-weight: 700;
padding: 0.8rem 1.5rem;
}
body.crystal-dance-page .summer-form .btn-reset-dance:hover {
background: var(--color-surface-soft);
}
@media (max-width: 575.98px) {
body.crystal-dance-page .summer-form__stage-body {
padding: 1.25rem 1.15rem;
}
body.crystal-dance-page .summer-form__actions {
grid-template-columns: 1fr;
}
}
@media (max-width: 575.98px) {
body.crystal-dance-page .page-nav-bar {
margin: 0 -20px;
padding: 0.85rem 1rem;
}
body.crystal-dance-page .page-nav-bar a {
font-size: 0.76rem;
padding: 0.2rem 0.55rem;
}
}
</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>
<div class="container">
<!-- ① 認識水晶熱舞社 -->
<section id="about">
<img class="hero-banner" src="../assets/images/crystal-dance-hero.jpg" alt="水晶熱舞社應援視覺">
<span class="category-tag">水晶男孩推廣部・舞蹈應援企劃</span>
<!-- <h2 class="section-title">水晶熱舞社不是一般社團</h2> -->
<div class="section-body">
<p>水晶熱舞社是<span class="mark_b mark_y">水晶男孩推廣部</span>中的舞蹈應援企劃,從 20TH台灣小黃串燒聯舞的集體記憶出發,整理經典舞台、翻跳紀錄與應援行動,並<span class="fan-journey-wave">預備在 2027/04/15 30TH 前</span>募集新的參與者。</p>
<p>水晶熱舞社不是獨立成立的社團,而是「水晶男孩推廣部」底下的舞蹈、翻跳、應援與影片紀錄分類。凡是跟 SECHSKIE舞台有關的練習、重現與紀錄,都會被收進這個企劃裡,由歷屆台灣小黃接力整理與延續。</p>
</div>
<!-- <div class="keyword-row">
<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>
<nav class="page-nav-bar" aria-label="頁面導覽">
<a href="#about">認識熱舞社</a>
<a href="#from-20th-to-30th">20TH 到 30TH</a>
<a href="#form-30th">應援表單</a>
</nav> -->
</section>
<!-- ② 從 20TH 到 30TH -->
<section id="from-20th-to-30th">
<div class="video-grid">
<h2 class="section-title">從 20TH 到 30TH</h2>
<div class="video-feature">
<!-- <span class="video-label">20TH 正式影片</span> -->
<div class="ratio ratio-16x9">
<iframe src="https://www.youtube.com/embed/fkJmOMPirBc" title="20TH 正式影片:SECHSKIES 20週年紀念串燒舞蹈"
allowfullscreen></iframe>
</div>
<div class="section-body">
<p>水晶熱舞社是<span class="mark_b mark_y">水晶男孩推廣部</span>底下的舞蹈應援企劃,從 20TH 台灣小黃串燒聯舞的集體記憶出發,整理 SECHSKIES 經典舞台、翻跳紀錄與應援行動,並<span class="fan-journey-wave">預備在 2027/04/15 30TH 前</span>募集新的參與者,由歷屆台灣小黃接力延續。</p>
</div>
</div>
<h3 class="sub-section-title">延伸閱讀</h3>
<div class="row g-4">
<div class="col-12 col-sm-6 col-lg-4">
<div class="video-thumb">
<div class="ratio ratio-16x9">
<iframe src="https://www.youtube.com/embed/ARi0cfpv6mE" title="20TH 預告影片"
allowfullscreen></iframe>
</div>
<span class="video-label">預告</span>
</div>
</div>
<div class="col-12 col-sm-6 col-lg-4">
<div class="video-thumb">
<div class="ratio ratio-16x9">
<iframe src="https://www.youtube.com/embed/nygNm5mtsJM" title="幕後花絮 #1"
allowfullscreen></iframe>
</div>
<span class="video-label">幕後花絮 #1</span>
</div>
</div>
<div class="col-12 col-sm-6 col-lg-4">
<div class="video-thumb">
<div class="ratio ratio-16x9">
<iframe src="https://www.youtube.com/embed/f04neSErUEY" title="幕後花絮 #2・雨中共舞"
allowfullscreen></iframe>
</div>
<span class="video-label">幕後花絮 #2・雨中共舞</span>
</div>
</div>
</div>
</div>
<!-- <h3 class="sub-section-title">30TH 應援募集</h3> -->
<div class="section-body">
<p>2027/04/15 是 SECHSKIES 出道 30 周年的日子。水晶熱舞社預計在 30TH
前重新整理舞蹈應援內容,募集想一起參與的台灣小黃,從經典舞曲、翻跳練習、影片剪輯到祝福文字,讓新的應援行動從這份表單開始累積。</p>
<p>你不需要一開始就很會跳舞,只要還記得水晶男孩的舞台、歌曲,或某個讓你入坑的瞬間,都可以在這裡留下你的參與方式。</p>
</div>
<span class="countdown-note" id="countdown">💛 距離 30TH · 2027/04/15 倒數計算中...</span>
</section>
<!-- ③ 30TH 應援募集表單 -->
<section id="form-30th">
<h2 class="section-title">30TH 應援募集表單</h2>
<p class="form-lead">這份表單是水晶熱舞社為 2027/04/15 30TH 預備的應援募集模組,用來整理參與者想練的歌曲、可支援的內容與想留下的祝福。</p>
<div class="summer-form__feedback" data-form-feedback hidden tabindex="-1" aria-live="polite"></div>
<form class="summer-form" action="dance-application-submit.php" method="post" enctype="multipart/form-data" data-dance-application-form>
<label class="summer-form__trap" aria-hidden="true">網站<input type="text" name="website" tabindex="-1" autocomplete="off"></label>
<div class="summer-form__stage">
<h3 class="summer-form__stage-title">填寫須知</h3>
<div class="summer-form__stage-body summer-form__stage-body--notice">
<ul class="info-list">
<li>📎 檔案格式:影片或圖片皆可,單檔上限 8 MB</li>
<li>🔒 這份資料僅供水晶熱舞社內部整理 30TH 應援企劃使用</li>
<li>📅 送出後若確定參與,之後會另行公告練習與錄製時間</li>
</ul>
<p class="info-footer-text">水晶男孩推廣部・30TH 應援企劃</p>
</div>
</div>
<div class="summer-form__stage">
<h3 class="summer-form__stage-title">第一階段|參與者基本資料</h3>
<div class="summer-form__stage-body">
<div class="row g-4">
<div class="col-12 col-sm-6">
<label for="nickname" class="form-label">暱稱</label>
<input type="text" class="form-control" id="nickname" name="nickname"
maxlength="80" 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"
maxlength="190" 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>
</div>
</div>
<div class="summer-form__stage">
<h3 class="summer-form__stage-title">第二階段|30TH 參與意向</h3>
<div class="summer-form__stage-body">
<div class="row g-4">
<div class="col-12">
<label class="form-label d-block">是否參加過 20TH</label>
<div class="summer-form__option-row">
<input type="radio" class="btn-check" name="attended_20th"
id="attended_20th_yes" value="attended" autocomplete="off" required>
<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="summer-form__option-row">
<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>
</div>
</div>
<div class="summer-form__stage">
<h3 class="summer-form__stage-title">第三階段|應援內容與祝福</h3>
<div class="summer-form__stage-body">
<div class="row g-4">
<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" maxlength="5000"
placeholder="想對水晶熱舞社說的話、想貢獻的部分、想見到誰、想留下的祝福……"></textarea>
</div>
</div>
</div>
</div>
<div class="summer-form__actions">
<button type="reset" class="btn btn-reset-dance">重新填寫</button>
<button type="submit" class="btn btn-dance" data-submit-button>送出應援表單 💛</button>
</div>
</form>
</section>
</div>
<footer>
<p>SECHSKIES Fanpage &copy; 2026 - 黃色氣球永遠飄揚</p>
<a href="https://jekkinoopy.github.io/Portfolio/" class="studio-link" target="_blank" rel="noopener noreferrer">
Design by <span class="mark_b">Jekkinoopy Studio</span>
</a>
</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 已經到來,一起再集合!';
}
})();
(function () {
var form = document.querySelector('[data-dance-application-form]');
var feedback = document.querySelector('[data-form-feedback]');
var submitButton = document.querySelector('[data-submit-button]');
var result = new URLSearchParams(window.location.search).get('form');
var messages = {
success: ['success', '已收到你的應援報名!水晶熱舞社整理後會依你留下的 Email 聯絡。'],
invalid: ['error', '有欄位尚未完成或格式不正確,請檢查後再送出。'],
file_too_large: ['error', '上傳檔案超過 8 MB,請縮小檔案後再送出。'],
file_type: ['error', '檔案格式不支援,請改用圖片、MP4、WebM 或 MOV。'],
too_many: ['error', '短時間內送出次數較多,請稍候兩分鐘再試。'],
unavailable: ['error', '目前暫時無法儲存報名,請稍後再試。']
};
function showFeedback(type, message) {
if (!feedback) return;
feedback.hidden = false;
feedback.className = 'summer-form__feedback summer-form__feedback--' + type;
feedback.textContent = message;
feedback.focus();
}
if (result && messages[result]) {
showFeedback(messages[result][0], messages[result][1]);
}
if (form) {
form.addEventListener('submit', function (event) {
var selectedContent = form.querySelectorAll('input[name="participate_content[]"]:checked');
if (!selectedContent.length) {
event.preventDefault();
showFeedback('error', '請至少選擇一項想參與的 30TH 內容。');
var firstContent = form.querySelector('input[name="participate_content[]"]');
if (firstContent) firstContent.focus();
return;
}
if (submitButton) {
submitButton.disabled = true;
submitButton.textContent = '送出中…';
}
});
}
})();
</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);
File diff suppressed because it is too large Load Diff