From fa9ef000b93e4280217130c763f45388b91e02ba Mon Sep 17 00:00:00 2001 From: Jonathan Sykes Date: Sat, 15 Aug 2026 07:19:46 +0000 Subject: [PATCH] Show playlists instead of the branding hero on the empty home --- frontend/app.js | 49 +++++++++++++++++++++++++++++++++++ frontend/index.html | 8 ++++++ frontend/styles.css | 62 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+) diff --git a/frontend/app.js b/frontend/app.js index eef866f..b83a922 100755 --- a/frontend/app.js +++ b/frontend/app.js @@ -1936,6 +1936,52 @@ function renderSidebar() { }); updateQueueBadge(); updateDownloadBadge(); + renderHomePlaylists(); +} + +// The empty player pane (nothing playing yet) shows the branding hero. Once +// this device has playlists, they take that space instead — the hero stays as +// the fallback so a first-time visitor still gets the landing. +// Driven from renderSidebar() so every playlist mutation refreshes both. +function renderHomePlaylists() { + const wrap = document.getElementById('homePlaylists'); + const hero = document.querySelector('#playerPlaceholder .hero'); + const grid = document.getElementById('homePlGrid'); + if (!wrap || !hero || !grid) return; + + const lists = data.playlists || []; + wrap.classList.toggle('hidden', !lists.length); + hero.classList.toggle('hidden', !!lists.length); + // The hero is centered art; the grid wants the full box, so the placeholder + // switches from centering to stretching (and tightens its padding). + if (els.placeholder) els.placeholder.classList.toggle('showing-playlists', !!lists.length); + if (!lists.length) { grid.innerHTML = ''; return; } + + grid.innerHTML = ''; + for (const pl of lists) { + const count = pl.videos.length; + const thumb = (pl.videos.find((v) => v && v.thumbnail) || {}).thumbnail || ''; + const card = document.createElement('button'); + card.className = 'home-pl-card'; + card.type = 'button'; + card.innerHTML = ` + + ${thumb ? `` + : '🎵'} + ▶ + + + ${count} video${count === 1 ? '' : 's'}`; + card.querySelector('.home-pl-name').textContent = pl.name; + card.title = pl.name; + card.addEventListener('click', () => { view = { type: 'playlist', id: pl.id }; render(); }); + card.querySelector('.home-pl-play').addEventListener('click', (e) => { + e.stopPropagation(); // the ▶ badge plays; the rest of the card opens the list + if (count) playFromList(pl.videos, 0, 'playlist:' + pl.id); + else toast('That playlist is empty'); + }); + grid.appendChild(card); + } } function currentList() { @@ -3263,6 +3309,9 @@ function wireUI() { $('sidebarProfileCopy').addEventListener('click', copyProfileLink); $('sidebarProfileShare').addEventListener('click', shareProfileLink); + // ---- Empty-home playlist grid ---- + $('homePlNewBtn').addEventListener('click', () => newPlaylist(null)); + els.searchForm.addEventListener('submit', async (e) => { e.preventDefault(); const q = els.searchInput.value.trim(); diff --git a/frontend/index.html b/frontend/index.html index 3e39a74..9a59c21 100755 --- a/frontend/index.html +++ b/frontend/index.html @@ -121,6 +121,14 @@ + + diff --git a/frontend/styles.css b/frontend/styles.css index 45ea28d..30356db 100755 --- a/frontend/styles.css +++ b/frontend/styles.css @@ -363,6 +363,61 @@ body::after { animation: heroIn 0.6s var(--ease) both; } @keyframes heroIn { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } } + +/* Empty home: the playlist grid takes the hero's place once playlists exist */ +.player-placeholder.showing-playlists { padding: 16px; align-items: stretch; } +.home-pl { + flex: 1 1 auto; align-self: stretch; min-width: 0; max-height: 100%; + display: flex; flex-direction: column; gap: 10px; + text-align: left; overflow: hidden; + animation: heroIn 0.5s var(--ease) both; +} +.home-pl-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-shrink: 0; } +.home-pl-title { font-family: var(--display); font-size: 17px; font-weight: 700; color: var(--text); } +.home-pl-new { + padding: 5px 10px; font-size: 12px; cursor: pointer; + color: var(--text-2); background: var(--bg-2); + border: 1px solid var(--line); border-radius: var(--radius-sm); + transition: background 0.16s, color 0.16s, border-color 0.16s; +} +.home-pl-new:hover { background: var(--accent); border-color: var(--accent); color: #fff; } +.home-pl-grid { + display: grid; grid-template-columns: repeat(auto-fill, minmax(122px, 1fr)); + gap: 12px; align-content: start; + overflow-y: auto; scrollbar-gutter: stable; padding-right: 4px; +} +.home-pl-card { + display: flex; flex-direction: column; gap: 6px; + padding: 0; border: 0; background: none; color: inherit; + text-align: left; cursor: pointer; min-width: 0; +} +.home-pl-thumb { + position: relative; display: block; aspect-ratio: 16 / 9; + border-radius: var(--radius-sm); overflow: hidden; + background: var(--bg-3); border: 1px solid var(--line-soft); + transition: border-color 0.16s, transform 0.16s var(--ease); +} +.home-pl-card:hover .home-pl-thumb { border-color: var(--accent); transform: translateY(-2px); } +.home-pl-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; } +.home-pl-thumb-fallback { position: absolute; inset: 0; display: grid; place-items: center; font-size: 22px; } +.home-pl-play { + position: absolute; right: 6px; bottom: 6px; + width: 26px; height: 26px; display: grid; place-items: center; + font-size: 11px; color: #fff; border-radius: 50%; + background: rgba(0, 0, 0, 0.62); backdrop-filter: blur(4px); + opacity: 0; transition: opacity 0.16s, background 0.16s; +} +.home-pl-card:hover .home-pl-play, .home-pl-play:focus-visible { opacity: 1; } +.home-pl-play:hover { background: var(--accent); } +.home-pl-name { + font-size: 13px; font-weight: 600; color: var(--text); + overflow: hidden; text-overflow: ellipsis; white-space: nowrap; +} +.home-pl-count { font-size: 11px; color: var(--text-dim); } +/* Touch devices have no hover — keep the play badge permanently visible */ +@media (hover: none) { + .home-pl-play { opacity: 1; } +} .ph-logo { font-size: 32px; width: 88px; height: 88px; @@ -1366,6 +1421,13 @@ input[type="range"]::-webkit-slider-thumb:hover { transform: scale(1.25); } .app.sidebar-open .sidebar { transform: translateX(0); } .app.sidebar-open .sidebar-backdrop { display: block; opacity: 1; } + + /* Empty-home playlist grid: the player stage is short on phones, so pack in + more, smaller cards rather than one oversized full-width column. */ + .player-placeholder.showing-playlists { padding: 12px; } + .home-pl-grid { grid-template-columns: repeat(auto-fill, minmax(92px, 1fr)); gap: 10px; } + .home-pl-title { font-size: 15px; } + .home-pl-name { font-size: 12px; } } /* Very narrow screens (e.g. 320px) — keep the topbar usable and the drawer