voidscape-noir / player.html
PrometheusGroup's picture
ok, give me a straight forward design brief for this product, using separate html and python server, draggable mini-player, with play/pause next/previous random shuffle icon, video "x of y", main page has a title header, search bar, under the search bar is a flex-grid (variable zoom upto 8x8 cards - use "+" and "-" buttons) displaying search results using CSS cards, video thumbnail, duration, video title, video author or channel name, green background signifying it is downloaded, each card will have a remove "X" circle button on the lower right corner, when user enters text and clicks search a modal opens and displays the results in a search grid (this search grid is to be the same format as the playlist flex-grid - css cards will have a red background for 'not downloaded', a remove "x" circle button to remove it from the search results list, user can click a card to add it to the playlist when selected a card in the search results display gets a green glowing border. Search modal needs a 'back' button, returning the user to the playlist, where they can press 'download new' to start the downloads. Theuser must be able to acess the search AND playlist while a video is playing.
5f4cbfa verified
<!DOCTYPE html>
<html lang="en" class="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VoidScape Player</title>
<link rel="stylesheet" href="style.css">
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/feather-icons"></script>
<script>
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
colors: {
primary: {
50: '#f0f9ff',
100: '#e0f2fe',
200: '#bae6fd',
300: '#7dd3fc',
400: '#38bdf8',
500: '#0ea5e9',
600: '#0284c7',
700: '#0369a1',
800: '#075985',
900: '#0c4a6e',
},
secondary: {
50: '#f5f3ff',
100: '#ede9fe',
200: '#ddd6fe',
300: '#c4b5fd',
400: '#a78bfa',
500: '#8b5cf6',
600: '#7c3aed',
700: '#6d28d9',
800: '#5b21b6',
900: '#4c1d95',
}
}
}
}
}
</script>
</head>
<body class="bg-gray-900 text-gray-100 min-h-screen">
<custom-navbar></custom-navbar>
<main class="container mx-auto px-4 py-12">
<div class="max-w-6xl mx-auto">
<h1 class="text-4xl font-bold mb-8 text-transparent bg-clip-text bg-gradient-to-r from-primary-500 to-secondary-500">
VoidScape Player
</h1>
<div class="relative mb-8">
<input type="text" id="search-input" placeholder="Search for music..."
class="w-full bg-gray-800 border border-gray-700 rounded-lg px-6 py-4 text-lg focus:outline-none focus:ring-2 focus:ring-primary-500">
<button id="search-btn" class="absolute right-3 top-1/2 transform -translate-y-1/2 bg-primary-600 hover:bg-primary-700 rounded-lg px-4 py-2 transition-all">
Search
</button>
</div>
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-semibold">Your Playlist</h2>
<div class="flex gap-4">
<button id="download-all" class="px-4 py-2 bg-green-600 hover:bg-green-700 rounded-lg font-medium">
Download New
</button>
<div class="flex gap-2">
<button id="zoom-out" class="p-2 bg-gray-800 rounded-lg hover:bg-gray-700">
<i data-feather="minus"></i>
</button>
<button id="zoom-in" class="p-2 bg-gray-800 rounded-lg hover:bg-gray-700">
<i data-feather="plus"></i>
</button>
</div>
</div>
</div>
<div id="playlist-grid" class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4">
<!-- Playlist items will be inserted here -->
</div>
</div>
</main>
<!-- Search Modal -->
<div id="search-modal" class="fixed inset-0 bg-black bg-opacity-90 z-50 hidden overflow-y-auto">
<div class="container mx-auto px-4 py-12">
<div class="flex justify-between items-center mb-8">
<h2 class="text-2xl font-semibold">Search Results</h2>
<button id="back-btn" class="px-4 py-2 bg-gray-800 hover:bg-gray-700 rounded-lg">
Back to Playlist
</button>
</div>
<div id="search-grid" class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4">
<!-- Search results will be inserted here -->
</div>
</div>
</div>
<!-- Mini Player -->
<div id="mini-player" class="fixed bottom-4 right-4 bg-gray-800 border border-gray-700 rounded-lg shadow-xl w-80 overflow-hidden draggable">
<div class="p-4 cursor-move">
<div class="flex items-center gap-4">
<img id="mini-player-thumb" src="http://static.photos/music/200x200/1" class="w-12 h-12 rounded-lg object-cover">
<div class="flex-1 min-w-0">
<h3 id="mini-player-title" class="font-medium truncate">Song Title</h3>
<p id="mini-player-artist" class="text-sm text-gray-400 truncate">Artist Name</p>
</div>
<div class="flex items-center gap-2">
<span id="mini-player-count" class="text-sm text-gray-400">1/10</span>
</div>
</div>
<div class="flex justify-between items-center mt-4">
<button id="shuffle-btn" class="p-2 text-gray-400 hover:text-white">
<i data-feather="shuffle"></i>
</button>
<button id="prev-btn" class="p-2 text-gray-400 hover:text-white">
<i data-feather="skip-back"></i>
</button>
<button id="play-btn" class="p-2 bg-primary-600 hover:bg-primary-700 rounded-full">
<i data-feather="play"></i>
</button>
<button id="next-btn" class="p-2 text-gray-400 hover:text-white">
<i data-feather="skip-forward"></i>
</button>
<button id="close-player" class="p-2 text-gray-400 hover:text-white">
<i data-feather="x"></i>
</button>
</div>
<div class="mt-2 flex items-center gap-2">
<span class="text-xs text-gray-400">0:00</span>
<div class="flex-1 h-1 bg-gray-700 rounded-full">
<div id="progress-bar" class="h-full bg-primary-500 rounded-full w-0"></div>
</div>
<span class="text-xs text-gray-400">3:45</span>
</div>
</div>
</div>
<script src="components/navbar.js"></script>
<script src="script.js"></script>
<script>
feather.replace();
// Make mini-player draggable
const player = document.getElementById('mini-player');
let isDragging = false;
let offsetX, offsetY;
player.addEventListener('mousedown', (e) => {
if (e.target.closest('button')) return;
isDragging = true;
offsetX = e.clientX - player.getBoundingClientRect().left;
offsetY = e.clientY - player.getBoundingClientRect().top;
player.style.cursor = 'grabbing';
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
player.style.left = `${e.clientX - offsetX}px`;
player.style.top = `${e.clientY - offsetY}px`;
});
document.addEventListener('mouseup', () => {
isDragging = false;
player.style.cursor = 'grab';
});
// Modal functionality
const searchBtn = document.getElementById('search-btn');
const searchModal = document.getElementById('search-modal');
const backBtn = document.getElementById('back-btn');
searchBtn.addEventListener('click', () => {
searchModal.classList.remove('hidden');
// Here you would fetch search results and populate the search grid
});
backBtn.addEventListener('click', () => {
searchModal.classList.add('hidden');
});
// Zoom functionality
const zoomIn = document.getElementById('zoom-in');
const zoomOut = document.getElementById('zoom-out');
const playlistGrid = document.getElementById('playlist-grid');
let zoomLevel = 5; // Default to 5 columns
zoomIn.addEventListener('click', () => {
if (zoomLevel < 8) {
zoomLevel++;
updateGridColumns();
}
});
zoomOut.addEventListener('click', () => {
if (zoomLevel > 2) {
zoomLevel--;
updateGridColumns();
}
});
function updateGridColumns() {
playlistGrid.className = `grid gap-4 grid-cols-${zoomLevel}`;
}
</script>
</body>
</html>