Add empty-state designs for empty playlists and history

Replaces bare status text with visual empty-state components:
- History: clock icon, 'Nothing watched yet' title, description, 'Search videos' CTA
- Playlist: music icon, 'This playlist is empty' title, description with tip, 'Browse videos' CTA
- Search: unchanged (hero landing handles this case)
- CTA buttons navigate to search view for quick action
This commit is contained in:
Jonathan Sykes
2026-06-14 13:55:48 +08:00
parent 4d5d105e70
commit 460aaae234
6 changed files with 137 additions and 5 deletions

View File

@@ -596,11 +596,44 @@ function renderList() {
}
if (!list.length) {
els.status.classList.remove('hidden');
els.status.textContent =
view.type === 'search' ? 'Search for something to begin.'
: view.type === 'history' ? 'Nothing watched yet.'
: 'This playlist is empty. Add videos from search.';
els.status.classList.add('hidden');
els.cards.innerHTML = '';
const empty = document.createElement('div');
empty.className = 'empty-state';
if (view.type === 'search') {
// Hero landing is already in the player pane — list pane stays bare.
els.status.classList.remove('hidden');
els.status.textContent = 'Search for something to begin.';
return;
}
if (view.type === 'history') {
empty.innerHTML = `
<div class="empty-icon">🕘</div>
<h3 class="empty-title">Nothing watched yet</h3>
<p class="empty-desc">Your viewing history shows up here once you start playing videos.</p>
`;
const cta = document.createElement('button');
cta.className = 'empty-cta';
cta.textContent = '🔍 Search videos';
cta.addEventListener('click', () => { view = { type: 'search' }; render(); });
empty.appendChild(cta);
} else {
// Playlist view — empty
empty.innerHTML = `
<div class="empty-icon">🎵</div>
<h3 class="empty-title">This playlist is empty</h3>
<p class="empty-desc">Add videos from search results or use the <strong>+ Playlist</strong> button while playing.</p>
`;
const cta = document.createElement('button');
cta.className = 'empty-cta';
cta.textContent = '🔍 Browse videos';
cta.addEventListener('click', () => { view = { type: 'search' }; render(); });
empty.appendChild(cta);
}
els.cards.appendChild(empty);
return;
}
els.status.classList.add('hidden');

View File

@@ -557,6 +557,66 @@ input[type="range"]::-webkit-slider-thumb:hover { transform: scale(1.25); }
.list-actions button:hover { color: var(--text); border-color: var(--accent); background: var(--bg-3); }
.status { padding: 16px 20px; color: var(--text-dim); font-size: 13px; }
/* ===================== Empty state ===================== */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
padding: 40px 32px 60px;
gap: 8px;
animation: heroIn 0.5s var(--ease) both;
}
.empty-icon {
font-size: 42px;
width: 80px; height: 80px;
display: grid; place-items: center;
border-radius: 24px;
background: var(--bg-2);
border: 1px solid var(--line);
margin-bottom: 10px;
transition: transform 0.3s var(--ease), box-shadow 0.3s;
}
.empty-state:hover .empty-icon {
transform: translateY(-4px);
box-shadow: 0 12px 32px -12px var(--accent-glow);
}
.empty-title {
font-family: var(--display);
font-weight: 700;
font-size: 18px;
letter-spacing: -0.01em;
margin: 0;
color: var(--text);
}
.empty-desc {
margin: 4px 0 0;
color: var(--text-dim);
font-size: 13.5px;
line-height: 1.5;
max-width: 300px;
}
.empty-desc strong { color: var(--text-2); font-weight: 600; }
.empty-cta {
margin-top: 16px;
background: linear-gradient(145deg, var(--accent-bright), var(--accent-deep));
color: #fff;
border: none;
padding: 10px 22px;
border-radius: 11px;
font-family: var(--ui);
font-weight: 700;
font-size: 13px;
letter-spacing: 0.01em;
cursor: pointer;
box-shadow: 0 8px 22px -8px var(--accent-glow);
transition: transform 0.16s var(--ease), box-shadow 0.16s;
}
.empty-cta:hover { transform: translateY(-2px); box-shadow: 0 12px 28px -8px var(--accent-glow); }
.empty-cta:active { transform: translateY(0); }
.cards { flex: 1; overflow-y: auto; padding: 6px 14px 28px; display: flex; flex-direction: column; gap: 5px; }
.card {