# 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
@@ -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);