Files
sechskies/yellow-note/practice-room.html
T

1207 lines
51 KiB
HTML
Raw Normal View History

2026-07-27 17:45:20 +08:00
<!doctype html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>水晶男孩推廣部|水晶練習室</title>
<link rel="stylesheet" href="../assets/css/style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="../assets/css/tablet.css">
<style>
/* ── 水晶練習室 scope token ── */
body.practice-room-page {
--practice-accent: var(--color-brand);
--practice-accent-soft: var(--color-brand-light);
--practice-surface: var(--color-surface);
--practice-surface-soft: var(--color-surface-soft);
--practice-text: var(--color-text);
--practice-text-muted: var(--color-text-muted);
--practice-text-dark: var(--color-brand-dark);
--practice-border: rgba(255, 204, 0, 0.28);
--practice-shadow: 0 12px 40px rgba(0, 0, 0, 0.12);
background-color: var(--color-bg);
color: var(--practice-text);
font-family: 'Noto Sans TC', 'Microsoft JhengHei', system-ui, sans-serif;
line-height: 1.6;
}
body.practice-room-page h1,
body.practice-room-page h2,
body.practice-room-page h3,
body.practice-room-page h4,
body.practice-room-page p,
body.practice-room-page ul,
body.practice-room-page ol {
margin: 0;
padding: 0;
}
body.practice-room-page a {
color: inherit;
text-decoration: none;
}
body.practice-room-page .practice-room {
position: relative;
z-index: 1;
max-width: 1080px;
margin: 0 auto;
padding: 2.5rem 1.25rem 3.5rem;
}
body.practice-room-page .practice-room__block {
margin-bottom: 4rem;
}
body.practice-room-page .practice-room__block-title {
font-size: clamp(1.35rem, 3vw, 1.75rem);
font-weight: 900;
color: var(--practice-text-dark);
text-align: center;
letter-spacing: 0.06em;
margin-bottom: 0.5rem;
}
body.practice-room-page .practice-room__block-sub {
display: block;
text-align: center;
font-size: 0.78rem;
font-weight: 700;
letter-spacing: 0.22em;
color: var(--practice-text-muted);
margin-bottom: 2rem;
}
body.practice-room-page .practice-room__block-title::after {
content: "";
display: block;
width: 3rem;
height: 3px;
margin: 0.85rem auto 0;
background: linear-gradient(90deg, transparent, var(--practice-accent), transparent);
border-radius: 2px;
}
/* ── Hero ── */
body.practice-room-page .practice-room__hero {
text-align: center;
padding: 2rem 1.25rem 2.5rem;
background: linear-gradient(165deg,
var(--practice-surface-soft) 0%,
var(--practice-surface) 55%,
rgba(255, 249, 232, 0.6) 100%);
border: 1px solid var(--practice-border);
border-radius: 20px;
box-shadow: var(--practice-shadow);
margin-bottom: 3.5rem;
}
body.practice-room-page .practice-room__hero-badge {
display: inline-block;
background: var(--color-bg-raised);
color: var(--practice-accent);
font-size: 0.72rem;
font-weight: 700;
letter-spacing: 0.12em;
padding: 0.35rem 1rem;
border-radius: 999px;
border: 1px solid var(--practice-border);
margin-bottom: 1.25rem;
}
body.practice-room-page .practice-room__hero-title {
font-size: clamp(2rem, 5.5vw, 3rem);
font-weight: 900;
color: var(--practice-text-dark);
letter-spacing: 0.1em;
margin-bottom: 0.35rem;
}
body.practice-room-page .practice-room__hero-en {
font-size: clamp(0.85rem, 2vw, 1rem);
font-weight: 700;
letter-spacing: 0.28em;
color: var(--practice-text-muted);
margin-bottom: 1.5rem;
}
body.practice-room-page .practice-room__hero-lead {
max-width: 680px;
margin: 0 auto 1.75rem;
color: var(--practice-text-muted);
font-size: 0.95rem;
line-height: 1.9;
text-align: left;
}
body.practice-room-page .practice-room__tag-row {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 0.55rem;
max-width: 640px;
margin: 0 auto;
}
body.practice-room-page .practice-room__tag {
display: inline-flex;
align-items: center;
gap: 0.3rem;
background: var(--practice-accent-soft);
color: var(--practice-text-dark);
font-size: 0.8rem;
font-weight: 700;
padding: 0.42rem 0.9rem;
border-radius: 999px;
border: 1px solid var(--practice-border);
white-space: nowrap;
}
/* ── 分館卡片 ── */
body.practice-room-page .practice-room__hall-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1.15rem;
align-items: stretch;
}
body.practice-room-page .practice-room__hall-card {
display: flex;
flex-direction: column;
background: var(--practice-surface);
border: 1px solid var(--practice-border);
border-radius: 16px;
padding: 1.5rem 1.35rem;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.06);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
body.practice-room-page .practice-room__hall-card:hover {
transform: translateY(-3px);
box-shadow: var(--practice-shadow);
}
body.practice-room-page .practice-room__hall-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 2.5rem;
height: 2.5rem;
background: var(--practice-accent-soft);
color: var(--practice-text-dark);
border-radius: 12px;
font-size: 1.1rem;
margin-bottom: 0.85rem;
}
body.practice-room-page .practice-room__hall-name {
font-size: 1.05rem;
font-weight: 800;
color: var(--practice-text-dark);
text-align: center;
margin-bottom: 0.65rem;
letter-spacing: 0.04em;
}
body.practice-room-page .practice-room__hall-desc {
color: var(--practice-text-muted);
font-size: 0.88rem;
line-height: 1.8;
text-align: left;
flex: 1;
}
/* ── 練習播放器 ── */
body.practice-room-page .practice-room__player-wrap {
background: var(--color-bg-raised);
border: 1px solid var(--practice-border);
border-radius: 18px;
padding: 1.25rem;
box-shadow: var(--practice-shadow);
}
body.practice-room-page .practice-room__video-stage {
position: relative;
width: 100%;
aspect-ratio: 16 / 9;
background: var(--color-bg);
border-radius: 12px;
overflow: hidden;
border: 1px solid rgba(255, 204, 0, 0.15);
}
body.practice-room-page .practice-room__video-inner {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
body.practice-room-page .practice-room__video-inner--mirror {
transform: scaleX(-1);
}
body.practice-room-page .practice-room__video {
width: 100%;
height: 100%;
object-fit: contain;
display: block;
}
body.practice-room-page .practice-room__video-placeholder {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 0.75rem;
color: var(--color-text-inverse);
text-align: center;
padding: 1.5rem;
background: radial-gradient(ellipse at center, rgba(78, 73, 43, 0.35) 0%, rgba(10, 10, 10, 0.92) 70%);
pointer-events: none;
}
body.practice-room-page .practice-room__video-placeholder i {
font-size: 2.5rem;
color: var(--practice-accent);
opacity: 0.85;
}
body.practice-room-page .practice-room__video-placeholder p {
font-size: 0.95rem;
font-weight: 700;
letter-spacing: 0.06em;
color: var(--color-text-inverse);
}
body.practice-room-page .practice-room__video-placeholder span {
font-size: 0.78rem;
color: rgba(244, 244, 244, 0.65);
}
body.practice-room-page .practice-room__video-placeholder.is-hidden {
display: none;
}
body.practice-room-page .practice-room__controls {
margin-top: 1.15rem;
display: flex;
flex-direction: column;
gap: 0.85rem;
}
body.practice-room-page .practice-room__progress-row {
display: flex;
align-items: center;
gap: 0.65rem;
}
body.practice-room-page .practice-room__time {
font-size: 0.78rem;
font-weight: 700;
color: var(--color-text-inverse);
font-variant-numeric: tabular-nums;
min-width: 2.8rem;
text-align: center;
flex-shrink: 0;
}
body.practice-room-page .practice-room__progress {
flex: 1;
height: 8px;
background: rgba(255, 255, 255, 0.12);
border-radius: 999px;
cursor: pointer;
position: relative;
touch-action: none;
}
body.practice-room-page .practice-room__progress-fill {
height: 100%;
width: 0%;
background: linear-gradient(90deg, var(--practice-accent), var(--practice-accent-soft));
border-radius: 999px;
pointer-events: none;
}
body.practice-room-page .practice-room__progress-ab {
position: absolute;
top: -3px;
height: 14px;
background: rgba(255, 204, 0, 0.35);
border-radius: 4px;
pointer-events: none;
display: none;
}
body.practice-room-page .practice-room__progress-ab.is-visible {
display: block;
}
body.practice-room-page .practice-room__btn-row {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
justify-content: center;
}
body.practice-room-page .practice-room__btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.35rem;
min-height: 44px;
min-width: 44px;
padding: 0.5rem 0.85rem;
border: 1px solid var(--practice-border);
border-radius: 10px;
background: rgba(255, 255, 255, 0.06);
color: var(--color-text-inverse);
font-size: 0.82rem;
font-weight: 700;
font-family: inherit;
cursor: pointer;
transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
white-space: nowrap;
}
body.practice-room-page .practice-room__btn:hover {
background: rgba(255, 204, 0, 0.15);
border-color: var(--practice-accent);
}
body.practice-room-page .practice-room__btn.is-active {
background: var(--practice-accent);
color: var(--practice-text-dark);
border-color: var(--practice-accent);
}
body.practice-room-page .practice-room__btn--primary {
background: var(--practice-accent);
color: var(--practice-text-dark);
border-color: var(--practice-accent);
font-weight: 800;
}
body.practice-room-page .practice-room__btn--primary:hover {
background: var(--practice-accent-soft);
}
body.practice-room-page .practice-room__control-group {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.5rem;
padding: 0.65rem 0;
border-top: 1px solid rgba(255, 255, 255, 0.08);
}
body.practice-room-page .practice-room__control-label {
font-size: 0.75rem;
font-weight: 800;
letter-spacing: 0.08em;
color: var(--practice-accent);
min-width: 3.5rem;
text-align: center;
flex-shrink: 0;
}
body.practice-room-page .practice-room__ab-status {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
justify-content: center;
font-size: 0.78rem;
font-weight: 700;
color: rgba(244, 244, 244, 0.75);
font-variant-numeric: tabular-nums;
}
body.practice-room-page .practice-room__ab-status strong {
color: var(--practice-accent);
}
/* ── 歌曲卡片 ── */
body.practice-room-page .practice-room__song-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1.15rem;
align-items: stretch;
}
body.practice-room-page .practice-room__song-card {
display: flex;
flex-direction: column;
background: var(--practice-surface);
border: 1px solid var(--practice-border);
border-radius: 16px;
padding: 1.35rem 1.25rem;
box-shadow: 0 4px 18px rgba(0, 0, 0, 0.05);
}
body.practice-room-page .practice-room__song-card--featured {
border-color: var(--practice-accent);
box-shadow: 0 0 0 1px rgba(255, 204, 0, 0.2), var(--practice-shadow);
}
body.practice-room-page .practice-room__song-title {
font-size: 1.15rem;
font-weight: 900;
color: var(--practice-text-dark);
text-align: center;
margin-bottom: 0.5rem;
}
body.practice-room-page .practice-room__song-meta {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 0.4rem;
margin-bottom: 0.85rem;
}
body.practice-room-page .practice-room__song-tag {
font-size: 0.72rem;
font-weight: 700;
padding: 0.2rem 0.55rem;
border-radius: 6px;
background: var(--practice-accent-soft);
color: var(--practice-text-dark);
}
body.practice-room-page .practice-room__song-focus {
font-size: 0.86rem;
color: var(--practice-text-muted);
line-height: 1.75;
text-align: left;
flex: 1;
margin-bottom: 0.85rem;
}
body.practice-room-page .practice-room__song-focus strong {
color: var(--practice-text-dark);
font-weight: 800;
}
body.practice-room-page .practice-room__song-status {
display: block;
text-align: center;
font-size: 0.75rem;
font-weight: 700;
padding: 0.35rem 0.65rem;
border-radius: 999px;
background: rgba(255, 204, 0, 0.12);
color: var(--practice-text-dark);
}
2026-08-03 00:09:35 +08:00
body.practice-room-page .practice-room__song-status--soon {
background: rgba(255, 255, 255, 0.5);
color: var(--practice-text-muted);
}
body.practice-room-page .practice-room__song-link {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.45rem;
min-height: 42px;
margin-bottom: 0.75rem;
padding: 0.55rem 0.9rem;
border: 1px solid var(--practice-accent);
border-radius: 999px;
color: var(--practice-text-dark);
text-decoration: none;
font-size: 0.78rem;
font-weight: 800;
}
body.practice-room-page .practice-room__song-link:hover {
background: var(--practice-accent-soft);
}
body.practice-room-page .practice-room__song-empty {
grid-column: 1 / -1;
margin: 0;
padding: 1.5rem;
border: 1px dashed var(--practice-border);
border-radius: 14px;
color: var(--practice-text-muted);
background: var(--practice-surface);
text-align: center;
}
2026-07-27 17:45:20 +08:00
/* ── 段落練習 ── */
body.practice-room-page .practice-room__section-panel {
background: var(--practice-surface);
border: 1px solid var(--practice-border);
border-radius: 18px;
padding: 1.75rem 1.5rem;
box-shadow: 0 4px 22px rgba(0, 0, 0, 0.05);
}
body.practice-room-page .practice-room__section-btns {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 0.55rem;
margin-bottom: 1.5rem;
}
body.practice-room-page .practice-room__section-btn {
min-height: 44px;
padding: 0.5rem 1.1rem;
border: 1px solid var(--practice-border);
border-radius: 999px;
background: var(--practice-surface);
color: var(--practice-text-dark);
font-size: 0.85rem;
font-weight: 800;
font-family: inherit;
cursor: pointer;
transition: background 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
letter-spacing: 0.04em;
}
body.practice-room-page .practice-room__section-btn:hover {
background: var(--practice-accent-soft);
}
body.practice-room-page .practice-room__section-btn.is-active {
background: var(--practice-accent);
border-color: var(--practice-accent);
color: var(--practice-text-dark);
}
body.practice-room-page .practice-room__section-hint {
max-width: 560px;
margin: 0 auto;
text-align: center;
padding: 1.25rem 1.5rem;
background: var(--practice-surface-soft);
border-radius: 14px;
border: 1px dashed var(--practice-border);
}
body.practice-room-page .practice-room__section-hint-label {
display: block;
font-size: 0.78rem;
font-weight: 800;
letter-spacing: 0.1em;
color: var(--practice-text-dark);
margin-bottom: 0.5rem;
}
body.practice-room-page .practice-room__section-hint-text {
color: var(--practice-text-muted);
font-size: 0.92rem;
line-height: 1.85;
text-align: left;
}
/* ── 未來擴充 ── */
body.practice-room-page .practice-room__future-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 0.65rem 1.25rem;
max-width: 720px;
margin: 0 auto;
}
body.practice-room-page .practice-room__future-item {
display: flex;
align-items: flex-start;
gap: 0.55rem;
color: var(--practice-text-muted);
font-size: 0.88rem;
line-height: 1.6;
text-align: left;
}
body.practice-room-page .practice-room__future-item i {
color: var(--practice-accent);
margin-top: 0.15rem;
flex-shrink: 0;
}
/* ── 版權聲明 ── */
body.practice-room-page .practice-room__notice {
max-width: 720px;
margin: 0 auto;
padding: 1.25rem 1.5rem;
background: rgba(26, 26, 26, 0.04);
border: 1px solid rgba(255, 204, 0, 0.18);
border-radius: 12px;
font-size: 0.78rem;
color: var(--practice-text-muted);
line-height: 1.8;
text-align: left;
}
/* ── RWD ── */
@media (max-width: 767.98px) {
body.practice-room-page .practice-room {
padding: 1.75rem 1rem 2.5rem;
}
body.practice-room-page .practice-room__hall-grid {
grid-template-columns: 1fr;
}
body.practice-room-page .practice-room__song-grid {
grid-template-columns: 1fr;
}
body.practice-room-page .practice-room__future-grid {
grid-template-columns: 1fr;
}
body.practice-room-page .practice-room__player-wrap {
padding: 0.85rem;
}
body.practice-room-page .practice-room__btn {
font-size: 0.78rem;
padding: 0.45rem 0.7rem;
}
}
2026-08-03 00:09:35 +08:00
@media (min-width: 768px) and (max-width: 991.98px) {
body.practice-room-page .practice-room__song-grid {
grid-template-columns: repeat(2, 1fr);
}
}
2026-07-27 17:45:20 +08:00
</style>
</head>
<body class="practice-room-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">CRYSTAL PRACTICE ROOM</span>
</div>
</div>
</header>
<nav class="portal-nav" aria-label="水晶男孩推廣部導覽" data-portal-nav></nav>
<canvas id="particles"></canvas>
<main class="practice-room">
<!-- ① Hero -->
<section class="practice-room__hero" aria-labelledby="practice-hero-title">
<span class="practice-room__hero-badge">SECHSKIES PRACTICE DEMO</span>
<h2 class="practice-room__hero-title" id="practice-hero-title">水晶練習室</h2>
<p class="practice-room__hero-en">Crystal Practice Room</p>
<p class="practice-room__hero-lead">
2026-08-07 23:50:26 +08:00
水晶練習室延續我們當年以<span class="mark_b mark_y">水晶熱舞社、水晶歌詞社與水晶嘻哈社</span>等名稱交流的玩心,將歌曲、舞蹈、歌詞、Rap 與應援整理成可以反覆練習的粉絲向互動工具。第一版先以舞蹈影片練習與歌詞段落整理為主,未來可擴充唱歌、Rap、應援詞、錄音回放與練習紀錄。
2026-07-27 17:45:20 +08:00
</p>
<div class="practice-room__tag-row">
<span class="practice-room__tag"><i class="fa-solid fa-fire" aria-hidden="true"></i> 水晶熱舞社</span>
<span class="practice-room__tag"><i class="fa-solid fa-music" aria-hidden="true"></i> 水晶歌詞社</span>
<span class="practice-room__tag"><i class="fa-solid fa-microphone-lines" aria-hidden="true"></i> 水晶嘻哈社</span>
<span class="practice-room__tag"><i class="fa-solid fa-lightbulb" aria-hidden="true"></i> 應援練習</span>
<span class="practice-room__tag"><i class="fa-solid fa-star" aria-hidden="true"></i> SECHSKIES Practice</span>
</div>
</section>
<!-- ② 分館功能區 -->
<section class="practice-room__block" aria-labelledby="halls-title">
<h2 class="practice-room__block-title" id="halls-title">分館功能</h2>
<span class="practice-room__block-sub">FOUR PRACTICE HALLS</span>
<div class="practice-room__hall-grid">
<article class="practice-room__hall-card">
<span class="practice-room__hall-icon" aria-hidden="true"><i class="fa-solid fa-person-running"></i></span>
<h3 class="practice-room__hall-name">水晶熱舞社</h3>
<p class="practice-room__hall-desc">以舞蹈影片為核心,支援鏡像、慢速播放、逐段練習與 A-B 循環,適合整理經典舞台、翻跳練習與應援舞蹈。</p>
</article>
<article class="practice-room__hall-card">
<span class="practice-room__hall-icon" aria-hidden="true"><i class="fa-solid fa-book-open"></i></span>
<h3 class="practice-room__hall-name">水晶歌詞社</h3>
<p class="practice-room__hall-desc">整理歌曲段落、中文理解、韓文歌詞位置與跟唱提示,未來可加入羅馬拼音、逐句練唱與原唱開關。</p>
</article>
<article class="practice-room__hall-card">
<span class="practice-room__hall-icon" aria-hidden="true"><i class="fa-solid fa-headphones"></i></span>
<h3 class="practice-room__hall-name">水晶嘻哈社</h3>
<p class="practice-room__hall-desc">針對 Rap 段落設計節奏、flow、咬字與慢速練習,未來可加入節拍器與錄音比較。</p>
</article>
<article class="practice-room__hall-card">
<span class="practice-room__hall-icon" aria-hidden="true"><i class="fa-solid fa-hands-clapping"></i></span>
<h3 class="practice-room__hall-name">水晶應援社</h3>
<p class="practice-room__hall-desc">整理演唱會應援詞、口號、手燈節奏與粉絲活動資訊,讓新加入的 Yellowkies 可以更快跟上。</p>
</article>
</div>
</section>
<!-- ③ 練習播放器 -->
<section class="practice-room__block" aria-labelledby="player-title">
<h2 class="practice-room__block-title" id="player-title">舞蹈練習播放器</h2>
<span class="practice-room__block-sub">DANCE PRACTICE PLAYER</span>
<div class="practice-room__player-wrap" id="practice-player">
<div class="practice-room__video-stage">
<div class="practice-room__video-inner" id="video-inner">
<video class="practice-room__video" id="practice-video" playsinline preload="metadata"
aria-label="水晶熱舞社練習影片">
<!-- 請將合法來源的練習影片 src 放在此處 -->
</video>
</div>
<div class="practice-room__video-placeholder" id="video-placeholder">
<i class="fa-solid fa-video" aria-hidden="true"></i>
<p>請放入水晶熱舞社練習影片</p>
<span>支援本機或合法來源影片替換</span>
</div>
</div>
<div class="practice-room__controls">
<div class="practice-room__progress-row">
<span class="practice-room__time" id="time-current">0:00</span>
<div class="practice-room__progress" id="progress-bar" role="slider"
aria-label="播放進度" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0"
tabindex="0">
<div class="practice-room__progress-ab" id="progress-ab"></div>
<div class="practice-room__progress-fill" id="progress-fill"></div>
</div>
<span class="practice-room__time" id="time-total">0:00</span>
</div>
<div class="practice-room__btn-row">
<button type="button" class="practice-room__btn practice-room__btn--primary" id="btn-play"
aria-label="播放或暫停">
<i class="fa-solid fa-play" id="icon-play" aria-hidden="true"></i>
<span id="label-play">播放</span>
</button>
<button type="button" class="practice-room__btn" id="btn-rewind" aria-label="倒退 5 秒">
<i class="fa-solid fa-backward-step" aria-hidden="true"></i> -5s
</button>
<button type="button" class="practice-room__btn" id="btn-forward" aria-label="前進 5 秒">
<i class="fa-solid fa-forward-step" aria-hidden="true"></i> +5s
</button>
</div>
<div class="practice-room__control-group">
<span class="practice-room__control-label">鏡像</span>
<button type="button" class="practice-room__btn" id="btn-mirror" aria-pressed="false">
<i class="fa-solid fa-right-left" aria-hidden="true"></i> OFF
</button>
</div>
<div class="practice-room__control-group">
<span class="practice-room__control-label">速度</span>
<button type="button" class="practice-room__btn" data-speed="0.5">0.5x</button>
<button type="button" class="practice-room__btn" data-speed="0.75">0.75x</button>
<button type="button" class="practice-room__btn is-active" data-speed="1">1x</button>
<button type="button" class="practice-room__btn" data-speed="1.25">1.25x</button>
</div>
<div class="practice-room__control-group">
<span class="practice-room__control-label">A-B</span>
<button type="button" class="practice-room__btn" id="btn-set-a">設 A</button>
<button type="button" class="practice-room__btn" id="btn-set-b">設 B</button>
<button type="button" class="practice-room__btn" id="btn-loop" aria-pressed="false">循環 OFF</button>
<button type="button" class="practice-room__btn" id="btn-clear-ab">清除</button>
</div>
<div class="practice-room__ab-status" id="ab-status">
<span>A 點:<strong id="ab-a">未設定</strong></span>
<span>B 點:<strong id="ab-b">未設定</strong></span>
</div>
</div>
</div>
</section>
2026-08-03 00:09:35 +08:00
<!-- ④ 練舞清單 -->
<section class="practice-room__block" aria-labelledby="songs-title">
<h2 class="practice-room__block-title" id="songs-title">練舞清單</h2>
<span class="practice-room__block-sub">DANCE PRACTICE LIST</span>
<div class="practice-room__song-grid" data-dance-practice-list aria-live="polite">
<p class="practice-room__song-empty">正在載入練舞清單…</p>
</div>
</section>
2026-07-27 17:45:20 +08:00
<!-- ⑤ 歌詞段落示意 -->
<section class="practice-room__block" aria-labelledby="sections-title">
<h2 class="practice-room__block-title" id="sections-title">段落練習</h2>
<span class="practice-room__block-sub">SECTION PRACTICE</span>
<div class="practice-room__section-panel">
<div class="practice-room__section-btns" role="tablist" aria-label="歌曲段落">
<button type="button" class="practice-room__section-btn is-active" role="tab"
aria-selected="true" data-section="intro">Intro</button>
<button type="button" class="practice-room__section-btn" role="tab"
aria-selected="false" data-section="verse">Verse</button>
<button type="button" class="practice-room__section-btn" role="tab"
aria-selected="false" data-section="chorus">Chorus</button>
<button type="button" class="practice-room__section-btn" role="tab"
aria-selected="false" data-section="rap">Rap</button>
<button type="button" class="practice-room__section-btn" role="tab"
aria-selected="false" data-section="danceBreak">Dance Break</button>
<button type="button" class="practice-room__section-btn" role="tab"
aria-selected="false" data-section="ending">Ending</button>
</div>
<div class="practice-room__section-hint" role="tabpanel" id="section-hint-panel">
<span class="practice-room__section-hint-label" id="section-hint-label">Intro</span>
<p class="practice-room__section-hint-text" id="section-hint-text">先熟悉站位與進場節奏。</p>
</div>
</div>
</section>
<!-- ⑥ 未來擴充 -->
<section class="practice-room__block" aria-labelledby="future-title">
<h2 class="practice-room__block-title" id="future-title">未來擴充</h2>
<span class="practice-room__block-sub">COMING SOON</span>
<ul class="practice-room__future-grid">
<li class="practice-room__future-item"><i class="fa-regular fa-circle-check" aria-hidden="true"></i>韓文歌詞 / 羅馬拼音 / 中文理解</li>
<li class="practice-room__future-item"><i class="fa-regular fa-circle-check" aria-hidden="true"></i>Rap 節拍器</li>
<li class="practice-room__future-item"><i class="fa-regular fa-circle-check" aria-hidden="true"></i>應援詞練習</li>
<li class="practice-room__future-item"><i class="fa-regular fa-circle-check" aria-hidden="true"></i>錄音回放</li>
<li class="practice-room__future-item"><i class="fa-regular fa-circle-check" aria-hidden="true"></i>錄影回放</li>
<li class="practice-room__future-item"><i class="fa-regular fa-circle-check" aria-hidden="true"></i>練習紀錄</li>
<li class="practice-room__future-item"><i class="fa-regular fa-circle-check" aria-hidden="true"></i>歌曲資料庫</li>
<li class="practice-room__future-item"><i class="fa-regular fa-circle-check" aria-hidden="true"></i>舞台版本比較</li>
<li class="practice-room__future-item"><i class="fa-regular fa-circle-check" aria-hidden="true"></i>30TH 應援企劃串接</li>
</ul>
</section>
<!-- 版權聲明 -->
<aside class="practice-room__notice" aria-label="版權與素材提醒">
本頁為粉絲向非商業作品集 Demo,第一版使用示意資料與可替換素材結構,不直接內建完整受版權保護的音源、MV 或歌詞。實際素材請以合法來源或自行製作內容替換。
</aside>
</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="../assets/js/particles.js"></script>
<script src="../assets/js/portal-nav.js"></script>
<script>
(function () {
/* ── 段落練習提示 ── */
var sectionHints = {
intro: { label: 'Intro', text: '先熟悉站位與進場節奏。' },
verse: { label: 'Verse', text: '注意主唱與和聲輪替,把握段落情緒轉折。' },
chorus: { label: 'Chorus', text: '適合搭配鏡像與 0.75x 速度反覆練習。' },
rap: { label: 'Rap', text: '未來可加入水晶嘻哈社的節拍與咬字練習。' },
danceBreak: { label: 'Dance Break', text: '適合使用 A-B 循環拆動作。' },
ending: { label: 'Ending', text: '確認退場走位與收尾 pose,銜接全曲記憶。' }
};
var sectionBtns = document.querySelectorAll('.practice-room__section-btn');
var hintLabel = document.getElementById('section-hint-label');
var hintText = document.getElementById('section-hint-text');
sectionBtns.forEach(function (btn) {
btn.addEventListener('click', function () {
var key = btn.getAttribute('data-section');
var hint = sectionHints[key];
if (!hint) return;
sectionBtns.forEach(function (b) {
b.classList.remove('is-active');
b.setAttribute('aria-selected', 'false');
});
btn.classList.add('is-active');
btn.setAttribute('aria-selected', 'true');
hintLabel.textContent = hint.label;
hintText.textContent = hint.text;
});
});
/* ── 練習播放器 ── */
var video = document.getElementById('practice-video');
var videoInner = document.getElementById('video-inner');
var placeholder = document.getElementById('video-placeholder');
var btnPlay = document.getElementById('btn-play');
var iconPlay = document.getElementById('icon-play');
var labelPlay = document.getElementById('label-play');
var btnRewind = document.getElementById('btn-rewind');
var btnForward = document.getElementById('btn-forward');
var btnMirror = document.getElementById('btn-mirror');
var progressBar = document.getElementById('progress-bar');
var progressFill = document.getElementById('progress-fill');
var progressAb = document.getElementById('progress-ab');
var timeCurrent = document.getElementById('time-current');
var timeTotal = document.getElementById('time-total');
var speedBtns = document.querySelectorAll('[data-speed]');
var btnSetA = document.getElementById('btn-set-a');
var btnSetB = document.getElementById('btn-set-b');
var btnLoop = document.getElementById('btn-loop');
var btnClearAb = document.getElementById('btn-clear-ab');
var abA = document.getElementById('ab-a');
var abB = document.getElementById('ab-b');
var pointA = null;
var pointB = null;
var loopEnabled = false;
var mirrorOn = false;
function formatTime(sec) {
if (!isFinite(sec) || sec < 0) return '0:00';
var m = Math.floor(sec / 60);
var s = Math.floor(sec % 60);
return m + ':' + String(s).padStart(2, '0');
}
function hasVideoSource() {
return video.currentSrc || video.src || (video.querySelector('source') && video.querySelector('source').src);
}
function updatePlaceholder() {
if (hasVideoSource() && video.readyState >= 1) {
placeholder.classList.add('is-hidden');
} else if (!hasVideoSource()) {
placeholder.classList.remove('is-hidden');
}
}
function updateProgress() {
var duration = video.duration || 0;
var current = video.currentTime || 0;
var pct = duration ? (current / duration) * 100 : 0;
progressFill.style.width = pct + '%';
progressBar.setAttribute('aria-valuenow', String(Math.round(pct)));
timeCurrent.textContent = formatTime(current);
timeTotal.textContent = formatTime(duration);
if (loopEnabled && pointA !== null && pointB !== null && duration) {
if (current >= pointB - 0.05) {
video.currentTime = pointA;
}
}
updateAbMarker();
}
function updateAbMarker() {
var duration = video.duration || 0;
if (pointA !== null && pointB !== null && duration && pointB > pointA) {
var left = (pointA / duration) * 100;
var width = ((pointB - pointA) / duration) * 100;
progressAb.style.left = left + '%';
progressAb.style.width = width + '%';
progressAb.classList.add('is-visible');
} else {
progressAb.classList.remove('is-visible');
}
}
function seekByRatio(clientX) {
var rect = progressBar.getBoundingClientRect();
var ratio = Math.min(1, Math.max(0, (clientX - rect.left) / rect.width));
var duration = video.duration || 0;
if (duration) {
video.currentTime = ratio * duration;
}
}
function togglePlay() {
if (!hasVideoSource()) return;
if (video.paused) {
video.play();
} else {
video.pause();
}
}
function setPlayState(playing) {
if (playing) {
iconPlay.className = 'fa-solid fa-pause';
labelPlay.textContent = '暫停';
} else {
iconPlay.className = 'fa-solid fa-play';
labelPlay.textContent = '播放';
}
}
btnPlay.addEventListener('click', togglePlay);
video.addEventListener('play', function () { setPlayState(true); });
video.addEventListener('pause', function () { setPlayState(false); });
video.addEventListener('timeupdate', updateProgress);
video.addEventListener('loadedmetadata', function () {
updatePlaceholder();
updateProgress();
});
video.addEventListener('emptied', updatePlaceholder);
btnRewind.addEventListener('click', function () {
if (!hasVideoSource()) return;
video.currentTime = Math.max(0, video.currentTime - 5);
});
btnForward.addEventListener('click', function () {
if (!hasVideoSource()) return;
var duration = video.duration || 0;
video.currentTime = Math.min(duration, video.currentTime + 5);
});
btnMirror.addEventListener('click', function () {
mirrorOn = !mirrorOn;
videoInner.classList.toggle('practice-room__video-inner--mirror', mirrorOn);
btnMirror.classList.toggle('is-active', mirrorOn);
btnMirror.setAttribute('aria-pressed', String(mirrorOn));
btnMirror.innerHTML = mirrorOn
? '<i class="fa-solid fa-right-left" aria-hidden="true"></i> ON'
: '<i class="fa-solid fa-right-left" aria-hidden="true"></i> OFF';
});
speedBtns.forEach(function (btn) {
btn.addEventListener('click', function () {
var speed = parseFloat(btn.getAttribute('data-speed'));
video.playbackRate = speed;
speedBtns.forEach(function (b) { b.classList.remove('is-active'); });
btn.classList.add('is-active');
});
});
btnSetA.addEventListener('click', function () {
pointA = video.currentTime || 0;
abA.textContent = formatTime(pointA);
btnSetA.classList.add('is-active');
updateAbMarker();
});
btnSetB.addEventListener('click', function () {
pointB = video.currentTime || 0;
abB.textContent = formatTime(pointB);
btnSetB.classList.add('is-active');
updateAbMarker();
});
btnLoop.addEventListener('click', function () {
if (pointA === null || pointB === null || pointB <= pointA) return;
loopEnabled = !loopEnabled;
btnLoop.classList.toggle('is-active', loopEnabled);
btnLoop.setAttribute('aria-pressed', String(loopEnabled));
btnLoop.textContent = loopEnabled ? '循環 ON' : '循環 OFF';
});
btnClearAb.addEventListener('click', function () {
pointA = null;
pointB = null;
loopEnabled = false;
abA.textContent = '未設定';
abB.textContent = '未設定';
btnSetA.classList.remove('is-active');
btnSetB.classList.remove('is-active');
btnLoop.classList.remove('is-active');
btnLoop.setAttribute('aria-pressed', 'false');
btnLoop.textContent = '循環 OFF';
updateAbMarker();
});
progressBar.addEventListener('click', function (e) {
seekByRatio(e.clientX);
});
var dragging = false;
progressBar.addEventListener('pointerdown', function (e) {
dragging = true;
progressBar.setPointerCapture(e.pointerId);
seekByRatio(e.clientX);
});
progressBar.addEventListener('pointermove', function (e) {
if (dragging) seekByRatio(e.clientX);
});
progressBar.addEventListener('pointerup', function (e) {
dragging = false;
progressBar.releasePointerCapture(e.pointerId);
});
progressBar.addEventListener('keydown', function (e) {
if (!hasVideoSource()) return;
if (e.key === 'ArrowLeft') {
video.currentTime = Math.max(0, video.currentTime - 5);
} else if (e.key === 'ArrowRight') {
var duration = video.duration || 0;
video.currentTime = Math.min(duration, video.currentTime + 5);
}
});
2026-08-03 00:09:35 +08:00
updatePlaceholder();
updateProgress();
})();
(function () {
var list = document.querySelector('[data-dance-practice-list]');
if (!list) return;
function element(tagName, className, text) {
var node = document.createElement(tagName);
if (className) node.className = className;
if (typeof text === 'string') node.textContent = text;
return node;
}
function renderEmpty(message) {
list.replaceChildren(element('p', 'practice-room__song-empty', message));
}
function renderItems(items) {
if (!items.length) {
renderEmpty('練舞清單目前整理中。');
return;
}
var fragment = document.createDocumentFragment();
items.forEach(function (item) {
var cardClass = 'practice-room__song-card' + (item.featured ? ' practice-room__song-card--featured' : '');
var card = element('article', cardClass);
card.appendChild(element('h3', 'practice-room__song-title', item.title));
var meta = element('div', 'practice-room__song-meta');
(item.tags || []).forEach(function (tag) {
meta.appendChild(element('span', 'practice-room__song-tag', tag));
});
meta.appendChild(element('span', 'practice-room__song-tag', '難度:' + item.difficulty));
meta.appendChild(element('span', 'practice-room__song-tag', item.progress));
card.appendChild(meta);
var focus = element('p', 'practice-room__song-focus');
focus.appendChild(element('strong', '', '練習重點:'));
focus.appendChild(document.createTextNode(item.practiceFocus));
card.appendChild(focus);
if (item.videoUrl) {
var link = element('a', 'practice-room__song-link', '打開參考影片');
link.href = item.videoUrl;
link.target = '_blank';
link.rel = 'noopener noreferrer';
card.appendChild(link);
}
var statusClass = 'practice-room__song-status' + (item.comingSoon ? ' practice-room__song-status--soon' : '');
card.appendChild(element('span', statusClass, item.note || item.progress));
fragment.appendChild(card);
});
list.replaceChildren(fragment);
}
fetch('dance-practices-data.php', { headers: { Accept: 'application/json' } })
.then(function (response) {
if (!response.ok) throw new Error('request failed');
return response.json();
})
.then(function (data) {
if (!data.ok || !Array.isArray(data.items)) throw new Error('invalid data');
renderItems(data.items);
})
.catch(function () {
renderEmpty('練舞清單暫時無法載入,請稍後再試。');
});
})();
</script>
2026-07-27 17:45:20 +08:00
</body>
</html>