1212 lines
51 KiB
HTML
1212 lines
51 KiB
HTML
<!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);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/* ── 段落練習 ── */
|
|
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;
|
|
}
|
|
}
|
|
|
|
@media (min-width: 768px) and (max-width: 991.98px) {
|
|
body.practice-room-page .practice-room__song-grid {
|
|
grid-template-columns: repeat(2, 1fr);
|
|
}
|
|
}
|
|
</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">
|
|
水晶練習室延續我們當年以<span class="mark_b mark_y">水晶熱舞社、水晶歌詞社與水晶嘻哈社</span>等名稱交流的玩心,將歌曲、舞蹈、歌詞、Rap 與應援整理成可以反覆練習的粉絲向互動工具。第一版先以舞蹈影片練習與歌詞段落整理為主,未來可擴充唱歌、Rap、應援詞、錄音回放與練習紀錄。
|
|
</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>
|
|
|
|
<!-- ④ 練舞清單 -->
|
|
<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>
|
|
|
|
<!-- ⑤ 歌詞段落示意 -->
|
|
<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 © 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);
|
|
}
|
|
});
|
|
|
|
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);
|
|
}
|
|
|
|
/* 靜態資料,不依賴 PHP:本站部署在 GitHub Pages(不支援 PHP 執行),
|
|
本機 Live Server 同樣不會執行 .php,所以清單資料直接寫在這裡。
|
|
完整版(含後台管理、MySQL)在獨立專案 sechskies_fans。 */
|
|
var danceItems = [
|
|
{
|
|
title: 'SECHSKIES 回歸編舞練習影片',
|
|
tags: ['回歸曲', '編舞練習'],
|
|
practiceFocus: '跟著示範影片抓基本站位與轉場,作為之後細部拆解動作的參考版本。',
|
|
videoUrl: 'https://youtu.be/Idvu0XwUrgk?si=v7bD50zRkx90gA42',
|
|
difficulty: '中等',
|
|
progress: '整理中',
|
|
featured: true,
|
|
comingSoon: false
|
|
}
|
|
];
|
|
|
|
renderItems(danceItems);
|
|
})();
|
|
</script>
|
|
</body>
|
|
|
|
</html>
|