SuperShipped // Item 12

SNIPPET VIEW

Live Render

HTML CODE

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>VibeStream - Music Player</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
    <script src="https://unpkg.com/@phosphor-icons/web"></script>
    <style>
        @import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;800&display=swap');
        
        body { 
            background-color: #f8fafc; 
            color: #0f172a; 
            font-family: 'Plus Jakarta Sans', sans-serif; 
            height: 100vh;
            margin: 0;
            overflow: hidden; /* Lock the body */
        }

        /* Layout Grid: Header (auto) | Main (1fr) | Player (auto) */
        .app-container {
            display: grid;
            grid-template-rows: auto 1fr auto;
            height: 100vh;
            padding: 20px; /* Internal Padding */
            gap: 20px;
        }

        .main-layout {
            display: grid;
            grid-template-columns: 260px 1fr;
            gap: 20px;
            min-height: 0; /* Critical for inner scrolling */
        }
        
        .custom-scroll::-webkit-scrollbar { width: 6px; }
        .custom-scroll::-webkit-scrollbar-thumb { background: #e2e8f0; border-radius: 10px; }
        
        .hero-tall { min-height: 400px; }
        
        #avatar-dropdown { 
            display: none; position: absolute; right: 0; top: 110%; width: 220px; 
            background: white; border: 1px solid #e2e8f0; border-radius: 20px; 
            box-shadow: 0 10px 30px rgba(0,0,0,0.1); padding: 8px; z-index: 100; 
        }
        .avatar-active #avatar-dropdown { display: block; animation: slideDown 0.2s ease-out; }
        @keyframes slideDown { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } }

        #modal-overlay { display: none; position: fixed; inset: 0; background: rgba(15, 23, 42, 0.6); backdrop-filter: blur(4px); z-index: 200; align-items: center; justify-content: center; }
        .modal-active #modal-overlay { display: flex; }

        .shimmer {
            background: #f1f5f9;
            background-image: linear-gradient(to right, #f1f5f9 0%, #e2e8f0 20%, #f1f5f9 40%, #f1f5f9 100%);
            background-repeat: no-repeat;
            background-size: 800px 100%;
            animation: shimmerInfinite 1.5s linear infinite forwards;
        }
        @keyframes shimmerInfinite { 0% { background-position: -468px 0; } 100% { background-position: 468px 0; } }
    </style>
</head>
<body onclick="closeDropdowns()">

    <div id="modal-overlay">
        <div class="bg-white rounded-[32px] p-5 w-96 shadow-2xl border border-slate-100" onclick="event.stopPropagation()">
            <h3 id="modal-title" class="text-xl font-extrabold text-slate-900 mb-1">Playlist</h3>
            <p id="modal-desc" class="text-xs text-slate-400 mb-5 font-medium">Configure your vibes.</p>
            <input type="text" id="modal-input" class="w-full px-4 py-3 bg-slate-100 rounded-xl border-none focus:ring-2 focus:ring-indigo-500 mb-5 font-bold text-sm" placeholder="Name...">
            <div id="modal-list" class="max-h-48 overflow-y-auto mb-5 hidden space-y-2 custom-scroll pr-2"></div>
            <div class="flex gap-3">
                <button onclick="closeModal()" class="flex-1 py-3 bg-slate-100 text-slate-600 rounded-xl font-bold text-sm">Cancel</button>
                <button id="modal-action" class="flex-1 py-3 bg-indigo-600 text-white rounded-xl font-bold text-sm">Confirm</button>
            </div>
        </div>
    </div>

    <div class="app-container">
        <header class="flex justify-between items-center bg-white rounded-[24px] border border-slate-200 p-4 shadow-sm">
            <div class="flex items-center gap-3 text-indigo-600 font-extrabold tracking-tight px-2">
                <i class="ph-bold ph-waveform text-3xl"></i> VibeStream
            </div>
            <div class="relative w-96">
                <i class="ph ph-magnifying-glass absolute left-4 top-1/2 -translate-y-1/2 text-slate-400"></i>
                <input type="text" placeholder="Search your 100 tracks..." class="w-full pl-11 pr-4 py-2 bg-slate-100 border-none rounded-2xl focus:ring-2 focus:ring-indigo-500 transition">
            </div>
            <div class="relative" id="avatar-container">
                <button onclick="toggleAvatar(event)" class="flex items-center gap-2 bg-slate-50 p-1 pr-4 rounded-full border border-slate-100 hover:bg-white transition">
                    <img src="https://api.dicebear.com/7.x/avataaars/svg?seed=Felix" class="w-8 h-8 rounded-full">
                    <span class="text-sm font-bold text-slate-700">Felix</span>
                </button>
                <div id="avatar-dropdown">
                    <div onclick="resetToMockData()" class="p-2 hover:bg-indigo-50 rounded-xl cursor-pointer text-sm font-semibold text-indigo-600"><i class="ph ph-arrows-clockwise mr-2"></i>Reset 100 Tracks</div>
                    <div onclick="location.reload()" class="p-2 hover:bg-red-50 rounded-xl cursor-pointer text-sm font-semibold text-red-500"><i class="ph ph-sign-out mr-2"></i>Logout</div>
                </div>
            </div>
        </header>

        <div class="main-layout">
            <aside class="flex flex-col gap-4 min-h-0">
                <div class="bg-white rounded-[32px] p-5 border border-slate-200 shadow-sm">
                    <nav class="space-y-1">
                        <div onclick="loadTrending()" class="flex items-center gap-3 px-4 py-3 bg-indigo-50 text-indigo-600 rounded-xl font-bold cursor-pointer">
                            <i class="ph-bold ph-fire"></i> Trending
                        </div>
                        <div onclick="showFavorites()" class="flex items-center gap-3 px-4 py-3 text-slate-500 hover:bg-slate-50 rounded-xl font-bold cursor-pointer">
                            <i class="ph-bold ph-heart text-red-400"></i> Favorites
                        </div>
                    </nav>
                </div>
                <div class="flex-1 bg-white rounded-[32px] border border-slate-200 p-5 flex flex-col overflow-hidden shadow-sm">
                    <div class="flex justify-between items-center mb-4 flex-none">
                        <p class="text-[10px] font-black text-slate-400 uppercase tracking-widest">Library</p>
                        <button onclick="openCreatePlaylist()" class="text-indigo-600 hover:scale-110 transition"><i class="ph-bold ph-plus-circle text-xl"></i></button>
                    </div>
                    <div id="playlist-sidebar-list" class="space-y-1 overflow-y-auto custom-scroll pr-2"></div>
                </div>
            </aside>

            <main class="bg-white rounded-[32px] border border-slate-200 shadow-sm overflow-y-auto custom-scroll min-h-0">
                <div class="p-6">
                    <div id="hero-banner" class="hero-tall mb-10 p-10 rounded-[48px] bg-slate-900 text-white relative overflow-hidden flex flex-col justify-end">
                        <img id="hero-bg" src="https://images.unsplash.com/photo-1470225620780-dba8ba36b745?q=80&w=1200" class="absolute inset-0 w-full h-full object-cover opacity-40">
                        <div class="relative z-10">
                            <h2 id="view-title" class="text-7xl font-black mb-2 leading-tight">Trending</h2>
                            <p id="view-desc" class="text-slate-300 text-lg font-medium">Scroll down to see all 100 tracks.</p>
                        </div>
                    </div>
                    <div id="track-container" class="space-y-1"></div>
                    <div id="missing-section" class="mt-12 hidden">
                        <div class="flex items-center gap-3 mb-6"><div class="h-[1px] flex-1 bg-slate-100"></div><span class="text-[10px] font-black text-slate-400 uppercase tracking-widest">Library Matches</span><div class="h-[1px] flex-1 bg-slate-100"></div></div>
                        <div id="missing-container" class="space-y-1"></div>
                    </div>
                </div>
            </main>
        </div>

        <footer class="h-24 bg-white border border-slate-200 rounded-[24px] flex items-center justify-between px-10 shadow-lg">
            <div class="flex items-center gap-4 w-1/3">
                <div id="footer-art" class="w-14 h-14 bg-slate-100 rounded-xl overflow-hidden shadow-inner"></div>
                <div class="truncate">
                    <div id="footer-title" class="font-bold text-slate-900 text-sm truncate w-48">Silence</div>
                    <div id="footer-artist" class="text-[10px] text-slate-400 font-bold uppercase tracking-widest">Select a song</div>
                </div>
            </div>
            <div class="flex flex-col items-center gap-2 w-1/3">
                <div class="flex items-center gap-8 text-slate-400">
                    <i class="ph-bold ph-skip-back text-2xl cursor-pointer hover:text-slate-900"></i>
                    <button onclick="togglePlayback()" class="w-12 h-12 bg-slate-900 text-white rounded-full flex items-center justify-center shadow-lg"><i id="play-icon" class="ph-fill ph-play text-xl ml-1"></i></button>
                    <i class="ph-bold ph-skip-forward text-2xl cursor-pointer hover:text-slate-900"></i>
                </div>
                <div class="w-full h-1.5 bg-slate-100 rounded-full overflow-hidden"><div id="progress-bar" class="h-full bg-indigo-600 w-0 transition-all duration-300"></div></div>
            </div>
            <div class="w-1/3 text-right text-slate-300"><i class="ph-bold ph-speaker-high text-xl"></i></div>
        </footer>
    </div>

    <audio id="audio-engine" ontimeupdate="handleRealTimeUpdate()"></audio>

    <script>
        const audio = document.getElementById('audio-engine');
        let favorites = JSON.parse(localStorage.getItem('vibe_favs') || '[]');
        let playlists = JSON.parse(localStorage.getItem('vibe_playlists') || '[]');
        let localTracks = JSON.parse(localStorage.getItem('vibe_local_tracks') || '[]');
        let isFakePlaying = false, fakeProgress = 0, fakeInterval = null;

        function resetToMockData() {
            const genres = ["Jazz", "Soul", "Hip-Hop", "Electronic", "Lo-Fi", "Funk"];
            const artists = ["The Collective", "Urban Drift", "Neon Nights", "Silk Waves", "Midnight Crew", "Velvet Echo"];
            const images = [
                "https://images.unsplash.com/photo-1511192336575-5a79af67a629?w=300",
                "https://images.unsplash.com/photo-1493225255756-d9584f8606e9?w=300",
                "https://images.unsplash.com/photo-1508700115892-45ecd05ae2ad?w=300",
                "https://images.unsplash.com/photo-1520527053377-4710dbf6c0df?w=300",
                "https://images.unsplash.com/photo-1453733190371-0a9bedd82893?w=300",
                "https://images.unsplash.com/photo-1470225620780-dba8ba36b745?w=300"
            ];
            let tracks = [];
            for (let i = 1; i <= 100; i++) {
                tracks.push({
                    id: `track-${i}`,
                    name: `${genres[i % genres.length]} Anthem #${i}`,
                    artist_name: artists[i % artists.length],
                    album_image: images[i % images.length],
                    audio: "" 
                });
            }
            localTracks = tracks;
            localStorage.setItem('vibe_local_tracks', JSON.stringify(localTracks));
            loadTrending();
        }
        if(localTracks.length === 0) resetToMockData();

        function openCreatePlaylist() {
            showModal("New Playlist", "Name your collection.", true);
            document.getElementById('modal-action').onclick = () => {
                const name = document.getElementById('modal-input').value;
                if(name) { playlists.push({id: Date.now(), name, tracks: []}); savePlaylists(); renderSidebar(); closeModal(); }
            };
        }

        function openEditPlaylist(id) {
            const pl = playlists.find(p => p.id === id);
            showModal("Edit", "Update name.", true);
            document.getElementById('modal-input').value = pl.name;
            document.getElementById('modal-action').onclick = () => {
                const name = document.getElementById('modal-input').value;
                if(name) { pl.name = name; savePlaylists(); renderSidebar(); closeModal(); }
            };
        }

        function openAddSong(track) {
            if(playlists.length === 0) return alert("Create a playlist first!");
            showModal("Add to Playlist", "Select destination.", false);
            const list = document.getElementById('modal-list');
            list.classList.remove('hidden');
            list.innerHTML = playlists.map(p => `<div onclick="confirmAddSong(${p.id}, ${JSON.stringify(track).replace(/"/g, '&quot;')})" class="p-3 bg-slate-50 hover:bg-indigo-50 rounded-xl cursor-pointer font-bold text-sm transition">${p.name}</div>`).join('');
            document.getElementById('modal-action').classList.add('hidden');
        }

        function confirmAddSong(plId, track) {
            const pl = playlists.find(p => p.id === plId);
            if(!pl.tracks.some(t => t.id === track.id)) { pl.tracks.push(track); savePlaylists(); }
            closeModal();
        }

        function showModal(title, desc, showInput) {
            document.body.classList.add('modal-active');
            document.getElementById('modal-title').innerText = title;
            document.getElementById('modal-desc').innerText = desc;
            document.getElementById('modal-input').classList.toggle('hidden', !showInput);
            document.getElementById('modal-list').classList.add('hidden');
            document.getElementById('modal-action').classList.remove('hidden');
        }

        function closeModal() { document.body.classList.remove('modal-active'); }
        function savePlaylists() { localStorage.setItem('vibe_playlists', JSON.stringify(playlists)); }

        function renderSidebar() {
            document.getElementById('playlist-sidebar-list').innerHTML = playlists.map(p => `
                <div class="flex items-center justify-between group p-2 hover:bg-slate-50 rounded-lg cursor-pointer">
                    <div onclick="showPlaylist(${p.id})" class="text-sm font-bold text-slate-600 flex items-center gap-2 truncate">
                        <i class="ph ph-music-notes text-slate-300"></i> ${p.name}
                    </div>
                    <div class="hidden group-hover:flex gap-2">
                        <i onclick="openEditPlaylist(${p.id})" class="ph ph-pencil-simple text-slate-400 hover:text-indigo-600"></i>
                        <i onclick="deletePlaylist(${p.id})" class="ph ph-trash text-slate-400 hover:text-red-500"></i>
                    </div>
                </div>`).join('');
        }

        function deletePlaylist(id) { if(confirm("Delete?")) { playlists = playlists.filter(p => p.id !== id); savePlaylists(); renderSidebar(); loadTrending(); } }
        function showPlaylist(id) { const pl = playlists.find(p => p.id === id); updateHeader(pl.name, `${pl.tracks.length} tracks.`); processTracks(pl.tracks, id); }

        function loadTrending() {
            updateHeader('Trending', 'Top 100 local tracks.');
            showShimmer(10);
            setTimeout(() => { processTracks(localTracks); }, 500);
        }

        function processTracks(tracks, activePlId = null) {
            const playable = tracks.filter(t => t.audio);
            const missing = tracks.filter(t => !t.audio);
            renderList('track-container', playable, false, activePlId);
            if(missing.length > 0) { 
                document.getElementById('missing-section').classList.remove('hidden'); 
                renderList('missing-container', missing, true, activePlId); 
            } else document.getElementById('missing-section').classList.add('hidden');
        }

        function renderList(targetId, tracks, isMissing, plId) {
            document.getElementById(targetId).innerHTML = tracks.map((t, i) => `
                <div class="flex items-center justify-between p-3 hover:bg-slate-50 rounded-2xl cursor-pointer group" onclick="initiatePlayback('${t.audio}', '${t.name}', '${t.artist_name}', '${t.album_image}')">
                    <div class="flex items-center gap-4">
                        <span class="text-slate-300 font-black w-8 text-xs">${i+1}</span>
                        <img src="${t.album_image}" class="w-11 h-11 rounded-xl object-cover">
                        <div class="truncate w-40 sm:w-64">
                            <div class="font-bold text-slate-800 text-sm truncate">${t.name}</div>
                            <div class="text-[9px] text-slate-400 font-bold uppercase tracking-widest">${t.artist_name}</div>
                        </div>
                    </div>
                    <div class="flex items-center gap-4">
                        <i onclick="event.stopPropagation(); openAddSong(${JSON.stringify(t).replace(/"/g, '&quot;')})" class="ph-bold ph-plus-circle text-slate-300 hover:text-indigo-500 text-xl"></i>
                        ${plId ? `<i onclick="event.stopPropagation(); removeFromPlaylist(${plId}, '${t.id}')" class="ph-bold ph-minus-circle text-slate-300 hover:text-red-500 text-xl"></i>` : ''}
                        <i onclick="toggleFav(event, ${JSON.stringify(t).replace(/"/g, '&quot;')})" class="ph-fill ph-heart ${favorites.some(f => f.id === t.id) ? 'text-red-500' : 'text-slate-200'} text-xl"></i>
                    </div>
                </div>`).join('');
        }

        function removeFromPlaylist(plId, trackId) { const pl = playlists.find(p => p.id === plId); pl.tracks = pl.tracks.filter(t => t.id !== trackId); savePlaylists(); showPlaylist(plId); }
        function initiatePlayback(url, title, artist, img) { stopFakePlay(); document.getElementById('footer-title').innerText = title; document.getElementById('footer-artist').innerText = artist; document.getElementById('footer-art').innerHTML = `<img src="${img}" class="w-full h-full object-cover">`; if (url) { isFakePlaying = false; audio.src = url; audio.play(); document.getElementById('play-icon').className = 'ph-fill ph-pause text-xl'; } else { startFakePlay(); } }
        function startFakePlay() { isFakePlaying = true; fakeProgress = 0; audio.pause(); audio.src = ""; document.getElementById('play-icon').className = 'ph-fill ph-pause text-xl'; fakeInterval = setInterval(() => { fakeProgress += 0.5; if (fakeProgress >= 100) fakeProgress = 0; document.getElementById('progress-bar').style.width = fakeProgress + '%'; }, 100); }
        function stopFakePlay() { isFakePlaying = false; clearInterval(fakeInterval); document.getElementById('progress-bar').style.width = '0%'; }
        function togglePlayback() { if (isFakePlaying) { if (fakeInterval) { clearInterval(fakeInterval); fakeInterval = null; document.getElementById('play-icon').className = 'ph-fill ph-play text-xl ml-1'; } else { startFakePlay(); } } else { audio.paused ? (audio.play(), document.getElementById('play-icon').className = 'ph-fill ph-pause text-xl') : (audio.pause(), document.getElementById('play-icon').className = 'ph-fill ph-play text-xl ml-1'); } }
        function handleRealTimeUpdate() { if (!isFakePlaying) document.getElementById('progress-bar').style.width = (audio.currentTime / (audio.duration || 1)) * 100 + '%'; }
        function showFavorites() { updateHeader('Favorites', `${favorites.length} saved tracks.`); processTracks(favorites); }
        function toggleFav(e, track) { e.stopPropagation(); const idx = favorites.findIndex(f => f.id === track.id); idx > -1 ? favorites.splice(idx, 1) : favorites.push(track); localStorage.setItem('vibe_favs', JSON.stringify(favorites)); e.target.className = `ph-fill ph-heart ${favorites.some(f => f.id === track.id) ? 'text-red-500' : 'text-slate-200'} text-xl`; }
        function showShimmer(count) { document.getElementById('track-container').innerHTML = Array.from({length:count}).map(() => `<div class="p-3 flex items-center gap-4"><div class="w-6 h-4 shimmer rounded"></div><div class="w-11 h-11 shimmer rounded-xl"></div><div class="space-y-2"><div class="w-32 h-4 shimmer rounded"></div><div class="w-20 h-2 shimmer rounded"></div></div></div>`).join(''); }
        function toggleAvatar(e) { e.stopPropagation(); document.getElementById('avatar-container').classList.toggle('avatar-active'); }
        function closeDropdowns() { document.getElementById('avatar-container').classList.remove('avatar-active'); }
        function updateHeader(title, desc) { document.getElementById('view-title').innerText = title; document.getElementById('view-desc').innerText = desc; }

        renderSidebar(); loadTrending();
    </script>
</body>
</html>

AI PROMPT

Create a "VibeStream - Music Player" component. Use GSAP for advanced animations, Phosphor Icons. Apply keyframe animations, glass-morphism / backdrop blur, gradients, layered shadows, hover transitions. Layout: CSS Grid, Flexbox, full-viewport sizing. Interactivity: click interactions, scroll-based effects. Includes form input fields. Includes media playback elements.