Files
ytplayer/docs/mockups/07-service-lyrics-spacing.html
2026-09-26 02:11:07 +08:00

1526 lines
44 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>YT Player — Service Mode Lyrics Redesign Mockup</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,600;12..96,700;12..96,800&family=Hanken+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap"
rel="stylesheet"
/>
<style>
:root {
--bg: #0a0a0c;
--bg-1: #101013;
--bg-2: #16161b;
--bg-3: #1d1d24;
--line: #26262f;
--line-soft: #1c1c23;
--text: #f5f3f0;
--text-2: #b6b5bf;
--text-dim: #76757f;
--accent: #ff4b32;
--accent-bright: #ff6a52;
--accent-deep: #d4321d;
--accent-glow: rgba(255, 75, 50, 0.45);
--display: "Bricolage Grotesque", Georgia, serif;
--ui: "Hanken Grotesk", system-ui, sans-serif;
--mono: "JetBrains Mono", ui-monospace, monospace;
--radius: 14px;
--radius-sm: 9px;
--shadow: 0 18px 50px -12px rgba(0, 0, 0, 0.7);
--ease: cubic-bezier(0.22, 1, 0.36, 1);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body {
background: var(--bg);
color: var(--text);
font-family: var(--ui);
font-size: 14px;
line-height: 1.45;
min-height: 100vh;
-webkit-font-smoothing: antialiased;
overflow-x: hidden;
}
body::before {
content: "";
position: fixed;
inset: 0;
pointer-events: none;
z-index: 0;
background:
radial-gradient(1000px 700px at 15% -5%, rgba(255, 75, 50, 0.12), transparent 60%),
radial-gradient(800px 600px at 90% 110%, rgba(90, 120, 255, 0.06), transparent 55%);
}
.showcase {
position: relative;
z-index: 1;
max-width: 1400px;
margin: 0 auto;
padding: 24px 28px 60px;
display: flex;
flex-direction: column;
gap: 32px;
}
/* Top Showcase Bar */
.topbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding-bottom: 18px;
border-bottom: 1px solid var(--line-soft);
}
.brand {
font-family: var(--display);
font-weight: 800;
font-size: 21px;
letter-spacing: -0.02em;
display: flex;
align-items: center;
gap: 11px;
}
.brand-mark {
display: grid;
place-items: center;
width: 32px;
height: 32px;
border-radius: 9px;
font-size: 13px;
color: #fff;
background: linear-gradient(145deg, var(--accent-bright), var(--accent-deep));
box-shadow: 0 6px 18px -4px var(--accent-glow), inset 0 1px 0 rgba(255,255,255,0.25);
}
.mock-badge {
font-family: var(--mono);
font-size: 11px;
font-weight: 600;
color: var(--accent-bright);
background: rgba(255, 75, 50, 0.12);
border: 1px solid rgba(255, 75, 50, 0.28);
padding: 5px 12px;
border-radius: 20px;
letter-spacing: 0.04em;
}
/* Feature Hero */
.feature-hero {
display: flex;
flex-direction: column;
gap: 8px;
max-width: 960px;
}
.feature-tag {
font-family: var(--mono);
font-size: 11.5px;
text-transform: uppercase;
letter-spacing: 0.14em;
color: var(--accent-bright);
font-weight: 700;
}
.feature-title {
font-family: var(--display);
font-size: 34px;
font-weight: 800;
letter-spacing: -0.025em;
line-height: 1.18;
color: #fff;
}
.feature-desc {
font-size: 15px;
color: var(--text-2);
line-height: 1.6;
}
/* Before / After Strip */
.comparison-section {
display: flex;
flex-direction: column;
gap: 12px;
}
.section-label-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.section-heading {
font-family: var(--mono);
font-size: 11px;
font-weight: 700;
color: var(--text-dim);
text-transform: uppercase;
letter-spacing: 0.14em;
}
.comparison-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
@media (max-width: 900px) {
.comparison-grid { grid-template-columns: 1fr; }
}
.cmp-card {
background: var(--bg-1);
border: 1px solid var(--line);
border-radius: var(--radius);
padding: 18px 20px;
display: flex;
flex-direction: column;
gap: 14px;
position: relative;
overflow: hidden;
}
.cmp-card.before {
border-color: rgba(239, 68, 68, 0.35);
background: linear-gradient(180deg, rgba(239, 68, 68, 0.04), var(--bg-1) 80%);
}
.cmp-card.after {
border-color: rgba(74, 222, 128, 0.35);
background: linear-gradient(180deg, rgba(74, 222, 128, 0.04), var(--bg-1) 80%);
}
.cmp-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
}
.cmp-badge {
font-family: var(--mono);
font-size: 10.5px;
font-weight: 700;
padding: 4px 9px;
border-radius: 6px;
letter-spacing: 0.05em;
}
.cmp-badge.bad {
color: #f87171;
background: rgba(239, 68, 68, 0.15);
border: 1px solid rgba(239, 68, 68, 0.3);
}
.cmp-badge.good {
color: #4ade80;
background: rgba(74, 222, 128, 0.15);
border: 1px solid rgba(74, 222, 128, 0.3);
}
.cmp-sub {
font-size: 12px;
color: var(--text-dim);
}
/* Before Visual Box */
.cmp-preview-box {
background: var(--bg-2);
border: 1px solid var(--line-soft);
border-radius: var(--radius-sm);
padding: 16px 14px;
position: relative;
min-height: 180px;
display: flex;
flex-direction: column;
justify-content: center;
}
.before-lyrics-sample {
display: flex;
flex-direction: column;
gap: 4px; /* cramped before gap */
text-align: center;
font-family: var(--display);
}
.before-line {
font-weight: 700;
color: var(--text-2);
opacity: 0.5;
font-size: 15px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.before-line.active-cramped {
font-size: 11.2px; /* Uncapped single row fit shrunk to micro-text */
color: #fff;
opacity: 0.95;
background: rgba(255, 75, 50, 0.2);
border-radius: 6px;
padding: 2px 6px;
white-space: nowrap;
letter-spacing: -0.02em;
}
/* After Visual Box */
.after-lyrics-sample {
display: flex;
flex-direction: column;
gap: 22px; /* clamp gap demonstration */
text-align: center;
font-family: var(--display);
}
.after-line {
font-weight: 700;
color: var(--text-2);
opacity: 0.55;
font-size: 22px; /* Floored min */
}
.after-line.active-spaced {
font-size: 28px;
line-height: 1.25;
font-weight: 800;
color: #fff;
background: color-mix(in srgb, var(--accent) 18%, transparent);
border: 1px solid rgba(255, 75, 50, 0.4);
box-shadow: 0 0 28px var(--accent-glow);
border-radius: 14px;
padding: 10px 16px;
text-wrap: balance;
}
.cmp-metrics {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
margin-top: 4px;
}
.metric-item {
background: var(--bg);
border: 1px solid var(--line-soft);
border-radius: 8px;
padding: 8px 10px;
display: flex;
flex-direction: column;
gap: 2px;
}
.metric-k {
font-family: var(--mono);
font-size: 10px;
text-transform: uppercase;
color: var(--text-dim);
}
.metric-v {
font-family: var(--mono);
font-size: 12px;
font-weight: 700;
color: var(--text);
}
.metric-v.bad { color: #f87171; }
.metric-v.good { color: #4ade80; }
/* Dual Stage Section: Phone & 16:9 Projector */
.stage-section {
display: flex;
flex-direction: column;
gap: 16px;
}
.stage-canvas {
display: flex;
align-items: flex-start;
justify-content: center;
gap: 40px;
flex-wrap: wrap;
background: radial-gradient(circle at 50% 30%, #15151e, #0c0c10 85%);
border: 1px solid var(--line);
border-radius: 24px;
padding: 40px 24px;
box-shadow: inset 0 2px 8px rgba(0,0,0,0.6);
}
/* Phone Device (iPhone 390x844) */
.device-column {
display: flex;
flex-direction: column;
align-items: center;
gap: 14px;
}
.device-tag {
font-family: var(--mono);
font-size: 11px;
font-weight: 600;
color: var(--accent-bright);
background: rgba(255, 75, 50, 0.1);
border: 1px solid rgba(255, 75, 50, 0.25);
padding: 4px 12px;
border-radius: 14px;
display: flex;
align-items: center;
gap: 6px;
}
.phone-outer {
width: 414px;
height: 868px;
background: #191922;
border-radius: 52px;
padding: 12px;
box-shadow:
0 30px 80px -15px rgba(0, 0, 0, 0.95),
0 0 0 1px rgba(255, 255, 255, 0.14),
inset 0 0 0 2px #0a0a0e;
position: relative;
flex-shrink: 0;
}
.phone-screen {
width: 390px;
height: 844px;
background: var(--bg);
border-radius: 40px;
overflow: hidden;
position: relative;
display: flex;
flex-direction: column;
border: 1px solid rgba(0, 0, 0, 0.7);
}
/* Dynamic Island */
.phone-island {
position: absolute;
top: 10px;
left: 50%;
transform: translateX(-50%);
width: 116px;
height: 28px;
background: #000;
border-radius: 20px;
z-index: 100;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 10px;
box-shadow: 0 2px 6px rgba(0,0,0,0.5);
}
.island-lens {
width: 9px;
height: 9px;
border-radius: 50%;
background: #0d1224;
border: 1px solid #1a223a;
}
.island-sensor {
width: 5px;
height: 5px;
border-radius: 50%;
background: #111;
}
/* iOS Status Bar */
.phone-status-bar {
height: 44px;
padding: 12px 24px 0;
display: flex;
justify-content: space-between;
align-items: center;
font-family: var(--ui);
font-size: 13px;
font-weight: 600;
color: #fff;
z-index: 90;
}
.phone-status-icons {
display: flex;
align-items: center;
gap: 6px;
font-size: 11px;
}
/* Service Mode Container */
.phone-service-mode {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 10px 18px 18px;
position: relative;
user-select: none;
}
/* Service Top Bar */
.service-topbar {
display: flex;
flex-direction: column;
gap: 10px;
width: 100%;
z-index: 20;
}
.service-top-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.service-song-meta {
display: flex;
flex-direction: column;
min-width: 0;
}
.service-song-title {
font-family: var(--display);
font-size: 16px;
font-weight: 800;
letter-spacing: -0.01em;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: #fff;
}
.service-song-key {
font-family: var(--mono);
font-size: 10.5px;
color: var(--text-dim);
display: flex;
align-items: center;
gap: 6px;
}
.wake-dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: #34d399;
box-shadow: 0 0 6px #34d399;
display: inline-block;
}
.service-actions-strip {
display: flex;
align-items: center;
gap: 6px;
}
.srv-btn {
font-family: var(--ui);
font-size: 11px;
font-weight: 600;
background: var(--bg-2);
border: 1px solid var(--line);
color: var(--text-2);
padding: 5px 9px;
border-radius: 8px;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 4px;
white-space: nowrap;
transition: all 0.18s var(--ease);
}
.srv-btn.active {
border-color: var(--accent);
color: #fff;
background: rgba(255, 75, 50, 0.16);
box-shadow: 0 0 10px -2px var(--accent-glow);
}
.srv-btn-exit {
background: rgba(255, 255, 255, 0.06);
border-color: rgba(255, 255, 255, 0.12);
color: var(--text);
}
.srv-btn-exit:hover {
background: rgba(255, 255, 255, 0.12);
}
/* Lyrics Center Stage */
.service-lyrics-center {
position: relative;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
overflow: hidden;
margin: 10px 0;
/* Soft fade at top and bottom edges */
-webkit-mask-image: linear-gradient(transparent 0%, #000 14%, #000 86%, transparent 100%);
mask-image: linear-gradient(transparent 0%, #000 14%, #000 86%, transparent 100%);
}
/* The .sl-list with generous gap */
.sl-list {
width: 100%;
display: flex;
flex-direction: column;
/* Generous breathing room requirement: clamp(14px, 3.2vh, 40px) */
gap: clamp(14px, 3.2vh, 40px);
align-items: center;
justify-content: center;
text-align: center;
font-family: var(--display);
}
/* Individual lyric lines */
.sl-line {
width: 100%;
box-sizing: border-box;
padding: 0.18em 4%;
text-align: center;
font-weight: 800;
line-height: 1.25;
letter-spacing: -0.015em;
border-radius: 18px;
cursor: pointer;
position: relative;
transition: font-size 0.35s var(--ease), opacity 0.35s var(--ease),
color 0.35s var(--ease), background-color 0.35s var(--ease),
transform 0.35s var(--ease);
overflow-wrap: break-word;
word-break: break-word;
text-wrap: balance;
}
/* Hard minimum 22px floor on phone */
.sl-line.past {
font-size: 22px;
color: var(--text-2);
opacity: 0.28;
}
.sl-line.near {
font-size: 25px;
color: var(--text-2);
opacity: 0.65;
}
/* Long wrapping line demonstration */
.sl-line.long-wrap {
max-width: 340px;
margin: 0 auto;
}
.wrap-tag {
display: inline-flex;
align-items: center;
gap: 4px;
font-family: var(--mono);
font-size: 9px;
font-weight: 700;
color: #38bdf8;
background: rgba(56, 189, 248, 0.12);
border: 1px solid rgba(56, 189, 248, 0.3);
padding: 1px 6px;
border-radius: 10px;
margin-top: 4px;
letter-spacing: 0.03em;
}
/* Active Sung Line: centered, largest, highlighted pill + glow */
.sl-line.active {
font-size: 32px;
opacity: 1;
color: #ffffff;
background-color: color-mix(in srgb, var(--accent) 18%, transparent);
border: 1px solid rgba(255, 75, 50, 0.45);
box-shadow: 0 0 34px var(--accent-glow);
padding: 8px 16px;
transform: scale(1.02);
}
.active-pill-cue {
position: absolute;
left: 10px;
top: 50%;
transform: translateY(-50%);
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--accent-bright);
box-shadow: 0 0 8px var(--accent-bright);
}
/* Vertical measurement guides (Caliper overlay) */
.caliper-guide {
font-family: var(--mono);
font-size: 9px;
color: rgba(255, 75, 50, 0.7);
background: rgba(255, 75, 50, 0.08);
border: 1px dashed rgba(255, 75, 50, 0.3);
border-radius: 4px;
padding: 1px 8px;
margin: -6px 0;
align-self: center;
display: inline-flex;
align-items: center;
gap: 4px;
}
/* Phone Bottom Area */
.phone-bottom-area {
display: flex;
flex-direction: column;
gap: 12px;
width: 100%;
z-index: 20;
}
/* Slim Progress Bar */
.slim-progress-wrap {
display: flex;
flex-direction: column;
gap: 6px;
width: 100%;
}
.slim-bar-track {
position: relative;
height: 4px;
background: rgba(255, 255, 255, 0.12);
border-radius: 4px;
cursor: pointer;
}
.slim-bar-fill {
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 52%;
background: linear-gradient(90deg, var(--accent), var(--accent-bright));
border-radius: 4px;
box-shadow: 0 0 8px var(--accent-glow);
}
.slim-bar-knob {
position: absolute;
left: 52%;
top: 50%;
transform: translate(-50%, -50%);
width: 10px;
height: 10px;
border-radius: 50%;
background: #fff;
box-shadow: 0 0 8px var(--accent);
}
.slim-time-row {
display: flex;
justify-content: space-between;
align-items: center;
font-family: var(--mono);
font-size: 11px;
color: var(--text-dim);
}
.phone-transport {
display: flex;
align-items: center;
justify-content: center;
gap: 22px;
padding: 4px 0;
}
.ph-btn {
background: var(--bg-2);
border: 1px solid var(--line);
color: var(--text);
border-radius: 50%;
width: 48px;
height: 48px;
font-size: 18px;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.16s var(--ease);
}
.ph-btn:hover {
background: var(--bg-3);
border-color: var(--text-dim);
transform: scale(1.05);
}
.ph-btn-play {
width: 64px;
height: 64px;
font-size: 24px;
background: linear-gradient(145deg, var(--accent-bright), var(--accent-deep));
border: none;
color: #fff;
box-shadow: 0 8px 24px -4px var(--accent-glow);
}
.ph-btn-play:hover {
transform: scale(1.06);
box-shadow: 0 12px 28px -4px var(--accent-glow);
}
/* iOS Home Bar */
.home-bar {
width: 130px;
height: 4.5px;
background: rgba(255, 255, 255, 0.28);
border-radius: 10px;
margin: 4px auto 0;
}
/* 16:9 Projector Display Column */
.projector-column {
display: flex;
flex-direction: column;
align-items: center;
gap: 14px;
flex: 1;
min-width: 580px;
max-width: 820px;
}
@media (max-width: 1100px) {
.projector-column {
min-width: 100%;
max-width: 100%;
}
}
.projector-outer {
width: 100%;
aspect-ratio: 16 / 9;
background: #000000;
border: 2px solid #282834;
border-radius: 20px;
box-shadow:
0 30px 80px -15px rgba(0, 0, 0, 0.95),
0 0 0 1px rgba(255, 255, 255, 0.08),
inset 0 0 24px rgba(0, 0, 0, 0.9);
position: relative;
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 16px 28px;
}
.projector-outer::before {
content: "";
position: absolute;
inset: 0;
pointer-events: none;
background: radial-gradient(circle at 50% 50%, rgba(255, 75, 50, 0.04), transparent 75%);
}
/* Projector Header Bar */
.proj-topbar {
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
z-index: 10;
padding-bottom: 8px;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
.proj-meta-left {
display: flex;
align-items: center;
gap: 12px;
}
.proj-title {
font-family: var(--display);
font-weight: 800;
font-size: 16px;
color: #fff;
}
.proj-verse-chip {
font-family: var(--mono);
font-size: 10px;
color: var(--accent-bright);
background: rgba(255, 75, 50, 0.15);
border: 1px solid rgba(255, 75, 50, 0.3);
padding: 2px 7px;
border-radius: 4px;
font-weight: 700;
}
.proj-meta-right {
display: flex;
align-items: center;
gap: 10px;
font-family: var(--mono);
font-size: 11px;
color: var(--text-dim);
}
.proj-live-indicator {
display: flex;
align-items: center;
gap: 6px;
color: #4ade80;
font-weight: 700;
background: rgba(74, 222, 128, 0.1);
border: 1px solid rgba(74, 222, 128, 0.25);
padding: 3px 8px;
border-radius: 6px;
}
.proj-live-dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: #4ade80;
box-shadow: 0 0 6px #4ade80;
}
/* Projector Lyrics Stage */
.proj-lyrics-stage {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
font-family: var(--display);
/* Generous projector gap */
gap: clamp(18px, 4.2vh, 46px);
padding: 10px 0;
-webkit-mask-image: linear-gradient(transparent 0%, #000 12%, #000 88%, transparent 100%);
mask-image: linear-gradient(transparent 0%, #000 12%, #000 88%, transparent 100%);
}
.proj-line {
font-weight: 800;
line-height: 1.18;
letter-spacing: -0.02em;
transition: all 0.3s var(--ease);
width: 100%;
}
.proj-line.past {
font-size: clamp(20px, 2.2vw, 26px);
color: rgba(245, 243, 240, 0.28);
}
.proj-line.near {
font-size: clamp(26px, 3vw, 34px);
color: rgba(245, 243, 240, 0.65);
}
.proj-line.active {
font-size: clamp(38px, 4.8vw, 56px);
color: #ffffff;
background: color-mix(in srgb, var(--accent) 18%, transparent);
border: 1px solid rgba(255, 75, 50, 0.45);
box-shadow: 0 0 45px var(--accent-glow);
border-radius: 20px;
padding: 8px 30px;
display: inline-block;
max-width: 90%;
}
.proj-line.long-wrap {
max-width: 82%;
margin: 0 auto;
text-wrap: balance;
}
/* Projector Bottom Strip */
.proj-footer {
display: flex;
flex-direction: column;
gap: 8px;
position: relative;
z-index: 10;
padding-top: 8px;
border-top: 1px solid rgba(255, 255, 255, 0.08);
}
.proj-progress-track {
height: 3px;
background: rgba(255, 255, 255, 0.12);
border-radius: 3px;
position: relative;
}
.proj-progress-fill {
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 52%;
background: linear-gradient(90deg, var(--accent), var(--accent-bright));
border-radius: 3px;
box-shadow: 0 0 8px var(--accent-glow);
}
.proj-footer-info {
display: flex;
justify-content: space-between;
align-items: center;
font-family: var(--mono);
font-size: 11px;
color: var(--text-dim);
}
/* Architecture Callout Cards */
.callout-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(290px, 1fr));
gap: 18px;
}
.callout-card {
background: linear-gradient(180deg, var(--bg-2), var(--bg-1));
border: 1px solid var(--line);
border-radius: var(--radius);
padding: 20px;
display: flex;
flex-direction: column;
gap: 10px;
position: relative;
overflow: hidden;
}
.callout-card::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2px;
background: linear-gradient(90deg, transparent, var(--accent), transparent);
opacity: 0.6;
}
.callout-head {
display: flex;
align-items: center;
gap: 10px;
}
.callout-num {
font-family: var(--mono);
font-size: 11px;
font-weight: 700;
color: var(--accent-bright);
background: rgba(255, 75, 50, 0.14);
border: 1px solid rgba(255, 75, 50, 0.3);
padding: 2px 7px;
border-radius: 6px;
}
.callout-title {
font-family: var(--display);
font-size: 16px;
font-weight: 700;
color: #fff;
}
.callout-body {
font-size: 13px;
color: var(--text-2);
line-height: 1.55;
}
.callout-code {
font-family: var(--mono);
font-size: 11px;
color: var(--accent-bright);
background: var(--bg);
border: 1px solid var(--line-soft);
padding: 6px 10px;
border-radius: 6px;
margin-top: 4px;
word-break: break-all;
}
/* Interactive hint pill */
.interactive-toast {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
font-family: var(--mono);
font-size: 11.5px;
color: var(--text-2);
background: var(--bg-2);
border: 1px solid var(--line);
border-radius: 30px;
padding: 8px 18px;
align-self: center;
}
.interactive-toast b {
color: var(--accent-bright);
}
</style>
</head>
<body>
<div class="showcase">
<!-- Topbar -->
<header class="topbar">
<div class="brand">
<span class="brand-mark">▶</span> YT Player
</div>
<div class="mock-badge">MOCKUP 07 · SERVICE MODE LYRICS REDESIGN</div>
</header>
<!-- Feature Hero -->
<section class="feature-hero">
<div class="feature-tag">Service Mode · Full-Screen Lyrics Spacing &amp; Type Floor</div>
<h1 class="feature-title">Legible Stage &amp; Sanctuary Lyrics: Hard Floor Font Size &amp; Generous Spacing</h1>
<p class="feature-desc">
Real implementation: <code style="font-family:var(--mono); color:var(--accent-bright);">frontend/styles.css</code> (<code style="font-family:var(--mono);">.sl-line</code>, <code style="font-family:var(--mono);">.sl-list</code>) and <code style="font-family:var(--mono);">frontend/index.html</code> (<code style="font-family:var(--mono);">#serviceMode</code>).
Eliminates unreadable 10–12px micro-text when songs have long lines. Rather than shrinking the font until the whole sentence fits on one row, this redesign enforces a hard minimum floor (<b style="color:#fff;">min 22px</b> on mobile) and gracefully wraps to 2 lines with balanced typography. Combined with generous breathing room (<code style="font-family:var(--mono);">clamp(14px, 3.2vh, 40px)</code>) and an accent-tinted glowing pill on the active sung line, musicians and vocalists can lead with effortless visual confidence.
</p>
</section>
<!-- Before / After Comparison Strip (Requirement 4) -->
<section class="comparison-section">
<div class="section-label-row">
<span class="section-heading">01 · Before vs After Comparison Strip</span>
<span style="font-family:var(--mono); font-size:11px; color:var(--text-dim);">Annotated with exact CSS pixel &amp; gap values</span>
</div>
<div class="comparison-grid">
<!-- Before Card -->
<div class="cmp-card before">
<div class="cmp-header">
<span class="cmp-badge bad">✕ BEFORE · UNFLOORED FIT CALCULATION</span>
<span class="cmp-sub">Cramped single-row force</span>
</div>
<div class="cmp-preview-box">
<div class="before-lyrics-sample">
<div class="before-line" style="font-size:15px;">Amazing grace, how sweet the sound</div>
<div class="before-line" style="font-size:14px;">That saved a wretch like me</div>
<div class="before-line active-cramped" title="Shrunk to 11.2px to force single row">
The Lord has promised good to me, His word my hope secures, He will my shield and portion be
</div>
<div class="before-line" style="font-size:13px;">As long as life endures</div>
</div>
</div>
<div class="cmp-metrics">
<div class="metric-item">
<span class="metric-k">Long Line Font</span>
<span class="metric-v bad">~11.2px</span>
</div>
<div class="metric-item">
<span class="metric-k">Line Spacing Gap</span>
<span class="metric-v bad">4px (cramped)</span>
</div>
<div class="metric-item">
<span class="metric-k">Stage Stand Read</span>
<span class="metric-v bad">Unreadable (&gt;1m)</span>
</div>
</div>
</div>
<!-- After Card -->
<div class="cmp-card after">
<div class="cmp-header">
<span class="cmp-badge good">✓ AFTER · HARD ≥ 22PX FLOOR + 2-LINE WRAP + SPACED</span>
<span class="cmp-sub">Legible stage typography</span>
</div>
<div class="cmp-preview-box">
<div class="after-lyrics-sample">
<div class="after-line" style="font-size:22px; opacity:0.4;">Amazing grace, how sweet the sound</div>
<div class="after-line active-spaced">
The Lord has promised good to me,<br>His word my hope secures
</div>
<div class="after-line" style="font-size:22px; opacity:0.4;">As long as life endures</div>
</div>
</div>
<div class="cmp-metrics">
<div class="metric-item">
<span class="metric-k">Long Line Font</span>
<span class="metric-v good">28px (wrapped)</span>
</div>
<div class="metric-item">
<span class="metric-k">Line Spacing Gap</span>
<span class="metric-v good">clamp(14px, 3.2vh, 40px)</span>
</div>
<div class="metric-item">
<span class="metric-k">Stage Stand Read</span>
<span class="metric-v good">Clear at 2.5m</span>
</div>
</div>
</div>
</div>
</section>
<!-- Interactive Hint -->
<div class="interactive-toast">
<span>💡 <b>Interactive Mockup:</b> Tap any lyric line on the phone or projector to change the active sung line in real-time.</span>
</div>
<!-- Dual Frame Showcase (Requirements: Phone 390x844 next to wide 16:9 projector) -->
<section class="stage-section">
<div class="section-label-row">
<span class="section-heading">02 · Side-by-Side Dual Viewports</span>
<span style="font-family:var(--mono); font-size:11px; color:var(--text-dim);">Phone PWA (390×844) &amp; Sanctuary 16:9 Projector Frame</span>
</div>
<div class="stage-canvas">
<!-- FRAME 1: PHONE (390 x 844) -->
<div class="device-column">
<div class="device-tag">
<span>📱</span> Mobile Viewport · 390 × 844 px (iPhone Stand)
</div>
<div class="phone-outer">
<div class="phone-screen">
<!-- Dynamic Island -->
<div class="phone-island">
<div class="island-lens"></div>
<div class="island-sensor"></div>
</div>
<!-- Status Bar -->
<div class="phone-status-bar">
<span>9:41</span>
<div class="phone-status-icons">
<span>5G</span>
<span>􀙇</span>
<span>100%</span>
</div>
</div>
<!-- Service Mode Container -->
<div class="phone-service-mode" id="phoneServiceMode">
<!-- Topbar -->
<div class="service-topbar">
<div class="service-top-row">
<div class="service-song-meta">
<h2 class="service-song-title">Amazing Grace</h2>
<div class="service-song-key">
<span class="wake-dot"></span>
<span>Key of G · 3/4 · John Newton</span>
</div>
</div>
<button class="srv-btn srv-btn-exit" type="button" aria-label="Exit Service Mode">Exit</button>
</div>
<div class="service-actions-strip">
<button class="srv-btn active" id="btnToggleLyrics" type="button" aria-pressed="true">
<span>🎵</span> Lyrics on
</button>
<button class="srv-btn active" id="btnToggleScroll" type="button" aria-pressed="true">
<span>⇅</span> Auto-scroll on
</button>
<span style="margin-left:auto; font-family:var(--mono); font-size:10px; color:var(--text-dim);">Set 2 / 5</span>
</div>
</div>
<!-- Lyrics Center View -->
<div class="service-lyrics-center">
<div class="sl-list" id="phoneLyricsList">
<!-- Line 1: Past -->
<div class="sl-line past" data-index="0">
Amazing grace, how sweet the sound
</div>
<!-- Visual gap caliper -->
<div class="caliper-guide">↕ clamp(14px, 3.2vh, 40px) [~26px]</div>
<!-- Line 2: Near (Previous) -->
<div class="sl-line near" data-index="1">
That saved a wretch like me
</div>
<!-- Visual gap caliper -->
<div class="caliper-guide">↕ clamp(14px, 3.2vh, 40px) [~26px]</div>
<!-- Line 3: SUNG LINE (ACTIVE, Centred, Largest, Highlighted Accent Pill + Glow) -->
<div class="sl-line active" data-index="2">
<span class="active-pill-cue"></span>
I once was lost, but now I'm found
</div>
<!-- Visual gap caliper -->
<div class="caliper-guide">↕ clamp(14px, 3.2vh, 40px) [~26px]</div>
<!-- Line 4: DELIBERATELY LONG WRAPPING LINE (HARD 22px FLOOR, WRAPS TO 2 LINES) -->
<div class="sl-line near long-wrap" data-index="3">
The Lord has promised good to me, His word my hope secures
<div>
<span class="wrap-tag">↳ Wraps to 2 lines · 25px (Never shrinks to 11px)</span>
</div>
</div>
<!-- Visual gap caliper -->
<div class="caliper-guide">↕ clamp(14px, 3.2vh, 40px) [~26px]</div>
<!-- Line 5: Future Line -->
<div class="sl-line past" data-index="4">
'Twas grace that taught my heart to fear
</div>
</div>
</div>
<!-- Bottom Controls & Slim Progress Bar -->
<div class="phone-bottom-area">
<div class="slim-progress-wrap">
<div class="slim-bar-track">
<div class="slim-bar-fill"></div>
<div class="slim-bar-knob"></div>
</div>
<div class="slim-time-row">
<span>02:14</span>
<span style="letter-spacing:0.04em;">AMAZING GRACE</span>
<span>04:30</span>
</div>
</div>
<div class="phone-transport">
<button class="ph-btn" type="button" title="Previous Track">⏮</button>
<button class="ph-btn ph-btn-play" id="phonePlayBtn" type="button" title="Play or Pause">⏸</button>
<button class="ph-btn" type="button" title="Next Track">⏭</button>
</div>
<div class="home-bar"></div>
</div>
</div>
</div>
</div>
</div>
<!-- FRAME 2: 16:9 PROJECTOR DISPLAY -->
<div class="projector-column">
<div class="device-tag">
<span>📽</span> Wide 16:9 Projector Frame · Sanctuary Broadcast Output
</div>
<div class="projector-outer">
<!-- Top Broadcast Header -->
<div class="proj-topbar">
<div class="proj-meta-left">
<span class="proj-title">Amazing Grace</span>
<span class="proj-verse-chip">VERSE 1 / 4</span>
</div>
<div class="proj-meta-right">
<span>1080p 60Hz</span>
<div class="proj-live-indicator">
<span class="proj-live-dot"></span>
<span>LIVE ON PROJECTOR</span>
</div>
</div>
</div>
<!-- Projector Lyrics Center Stage -->
<div class="proj-lyrics-stage" id="projLyricsStage">
<!-- Line 1 -->
<div class="proj-line past" data-index="0">
Amazing grace, how sweet the sound
</div>
<!-- Line 2 -->
<div class="proj-line near" data-index="1">
That saved a wretch like me
</div>
<!-- Active Sung Line: Huge display, glowing accent pill, high contrast -->
<div class="proj-line active" data-index="2">
I once was lost, but now I'm found
</div>
<!-- Long Wrapping Line on Projector -->
<div class="proj-line near long-wrap" data-index="3">
The Lord has promised good to me, His word my hope secures
</div>
<!-- Line 5 -->
<div class="proj-line past" data-index="4">
'Twas grace that taught my heart to fear
</div>
</div>
<!-- Projector Bottom Strip -->
<div class="proj-footer">
<div class="proj-progress-track">
<div class="proj-progress-fill"></div>
</div>
<div class="proj-footer-info">
<span>02:14 / 04:30</span>
<span>TEMPO: 72 BPM · KEY: G</span>
<span>UP NEXT: 10,000 Reasons (Bless The Lord)</span>
</div>
</div>
</div>
<!-- Projector Spec Badge -->
<div style="width:100%; display:flex; justify-content:space-between; align-items:center; font-family:var(--mono); font-size:11px; color:var(--text-dim); padding:0 8px;">
<span>Projector Target: 1920 × 1080 (16:9 Aspect)</span>
<span>Spacing: <code style="color:var(--accent-bright);">clamp(18px, 4.2vh, 46px)</code></span>
</div>
</div>
</div>
</section>
<!-- Architecture & Implementation Details -->
<section style="display:flex; flex-direction:column; gap:14px; margin-top:10px;">
<span class="section-heading">03 · Architectural Design Specifications</span>
<div class="callout-grid">
<div class="callout-card">
<div class="callout-head">
<span class="callout-num">01</span>
<h3 class="callout-title">Hard Type Floor (<code style="font-family:var(--mono); color:var(--accent-bright);">max(22px, ...)</code>)</h3>
</div>
<p class="callout-body">
Replaces pure dynamic downscaling with an enforced floor. On mobile screens, lyric lines never scale below 22px (or 26px for near, 30px for active sung lines), guaranteeing stage visibility from 1.5–3 meters.
</p>
<div class="callout-code">.service-lyrics .sl-line { font-size: max(22px, calc(var(--fit, 40px) * 0.48)); }</div>
</div>
<div class="callout-card">
<div class="callout-head">
<span class="callout-num">02</span>
<h3 class="callout-title">Two-Line Wrap Instead of Shrinking</h3>
</div>
<p class="callout-body">
When a hymn line contains long theological poetry, <code style="font-family:var(--mono);">text-wrap: balance</code> and <code style="font-family:var(--mono);">overflow-wrap: break-word</code> wrap it gracefully across 2 lines with tight <code style="font-family:var(--mono);">line-height: 1.25</code> rather than crushing the font into micro-text.
</p>
<div class="callout-code">overflow-wrap: break-word; text-wrap: balance; line-height: 1.25;</div>
</div>
<div class="callout-card">
<div class="callout-head">
<span class="callout-num">03</span>
<h3 class="callout-title">Generous Breathing Gap (<code style="font-family:var(--mono); color:var(--accent-bright);">clamp(14px, 3.2vh, 40px)</code>)</h3>
</div>
<p class="callout-body">
Replaces tight margins with a responsive CSS clamp. On smaller iPhone screens it ensures at least 14–26px between verse lines; on tall iPads and sanctuary projectors it expands cleanly up to 40px without crowding.
</p>
<div class="callout-code">.service-lyrics .sl-list { gap: clamp(14px, 3.2vh, 40px); }</div>
</div>
<div class="callout-card">
<div class="callout-head">
<span class="callout-num">04</span>
<h3 class="callout-title">Accent Pill &amp; Radiant Glow Elevation</h3>
</div>
<p class="callout-body">
The active sung line commands immediate focal attention through an 18% accent tint (<code style="font-family:var(--mono);">color-mix</code>) and a 34px radial glow box-shadow, distinguishing it unmistakably from adjacent lines.
</p>
<div class="callout-code">background-color: color-mix(in srgb, var(--accent) 18%, transparent);</div>
</div>
</div>
</section>
</div>
<script>
// Synchronized Interactive Selection across Phone and Projector
const phoneLines = document.querySelectorAll('#phoneLyricsList .sl-line');
const projLines = document.querySelectorAll('#projLyricsStage .proj-line');
function setActiveLine(index) {
phoneLines.forEach((line, i) => {
line.classList.remove('active', 'near', 'past');
if (i === index) {
line.classList.add('active');
} else if (Math.abs(i - index) === 1) {
line.classList.add('near');
} else {
line.classList.add('past');
}
});
projLines.forEach((line, i) => {
line.classList.remove('active', 'near', 'past');
if (i === index) {
line.classList.add('active');
} else if (Math.abs(i - index) === 1) {
line.classList.add('near');
} else {
line.classList.add('past');
}
});
}
// Attach click listeners to all lines
phoneLines.forEach(line => {
line.addEventListener('click', () => {
const idx = parseInt(line.getAttribute('data-index'), 10);
setActiveLine(idx);
});
});
projLines.forEach(line => {
line.addEventListener('click', () => {
const idx = parseInt(line.getAttribute('data-index'), 10);
setActiveLine(idx);
});
});
// Play/Pause button toggle
const phonePlayBtn = document.getElementById('phonePlayBtn');
let isPlaying = true;
if (phonePlayBtn) {
phonePlayBtn.addEventListener('click', () => {
isPlaying = !isPlaying;
phonePlayBtn.textContent = isPlaying ? '⏸' : '▶';
phonePlayBtn.title = isPlaying ? 'Pause playback' : 'Resume playback';
});
}
// Toggle button interactive feedback
const btnToggleLyrics = document.getElementById('btnToggleLyrics');
if (btnToggleLyrics) {
btnToggleLyrics.addEventListener('click', () => {
const isOn = btnToggleLyrics.classList.toggle('active');
btnToggleLyrics.setAttribute('aria-pressed', isOn);
btnToggleLyrics.innerHTML = isOn ? '<span>🎵</span> Lyrics on' : '<span>🎵</span> Lyrics off';
});
}
const btnToggleScroll = document.getElementById('btnToggleScroll');
if (btnToggleScroll) {
btnToggleScroll.addEventListener('click', () => {
const isOn = btnToggleScroll.classList.toggle('active');
btnToggleScroll.setAttribute('aria-pressed', isOn);
btnToggleScroll.innerHTML = isOn ? '<span>⇅</span> Auto-scroll on' : '<span>⇅</span> Auto-scroll off';
});
}
</script>
</body>
</html>