Spaces:
Running
Running
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.
Browse files- design_brief.md +82 -0
- player.html +12 -9
- script.js +11 -3
- style.css +2 -3
design_brief.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
```markdown
|
| 2 |
+
# VoidScape Media Player Design Brief
|
| 3 |
+
|
| 4 |
+
## Core Features
|
| 5 |
+
1. **Dual Interface System**:
|
| 6 |
+
- Main Playlist View
|
| 7 |
+
- Search Modal View
|
| 8 |
+
|
| 9 |
+
2. **Player Components**:
|
| 10 |
+
- Draggable mini-player (fixed position)
|
| 11 |
+
- Play/Pause, Next/Previous, Shuffle controls
|
| 12 |
+
- Progress bar with time indicators
|
| 13 |
+
- Current track display (X of Y)
|
| 14 |
+
|
| 15 |
+
3. **Playlist View**:
|
| 16 |
+
- Title header with gradient text
|
| 17 |
+
- Search bar with search button
|
| 18 |
+
- Variable zoom flex-grid (2x2 to 8x8)
|
| 19 |
+
- Card components showing:
|
| 20 |
+
- Video thumbnail
|
| 21 |
+
- Duration badge
|
| 22 |
+
- Video title
|
| 23 |
+
- Channel/author name
|
| 24 |
+
- Green background (downloaded)
|
| 25 |
+
- Remove "X" button (lower right)
|
| 26 |
+
|
| 27 |
+
4. **Search Modal**:
|
| 28 |
+
- Full-screen overlay with blurred background
|
| 29 |
+
- Same card format as playlist but with:
|
| 30 |
+
- Red background (not downloaded)
|
| 31 |
+
- Remove "X" button
|
| 32 |
+
- Green glowing border on selection
|
| 33 |
+
- "Back to Playlist" button
|
| 34 |
+
|
| 35 |
+
5. **Interaction Flow**:
|
| 36 |
+
- User enters search term → clicks search → modal opens
|
| 37 |
+
- Clicking cards in search adds to playlist
|
| 38 |
+
- "Download New" button initiates downloads
|
| 39 |
+
- All functions accessible while playing media
|
| 40 |
+
|
| 41 |
+
## Technical Requirements
|
| 42 |
+
1. **Frontend**:
|
| 43 |
+
- HTML5, CSS3, JavaScript (ES6+)
|
| 44 |
+
- Web Components for reusable UI
|
| 45 |
+
- Responsive flex-grid layout
|
| 46 |
+
- Smooth animations/transitions
|
| 47 |
+
- Dark theme with accent colors
|
| 48 |
+
|
| 49 |
+
2. **Backend**:
|
| 50 |
+
- Python Flask server
|
| 51 |
+
- REST API endpoints for:
|
| 52 |
+
- Playlist management
|
| 53 |
+
- Search functionality
|
| 54 |
+
- Player controls
|
| 55 |
+
|
| 56 |
+
3. **State Management**:
|
| 57 |
+
- Current playlist
|
| 58 |
+
- Search results
|
| 59 |
+
- Player status (playing/paused)
|
| 60 |
+
- Current track position
|
| 61 |
+
|
| 62 |
+
## Visual Design
|
| 63 |
+
- Color Scheme:
|
| 64 |
+
- Primary: Dark background (#111827)
|
| 65 |
+
- Secondary: Gradient accents (blue-purple)
|
| 66 |
+
- Status Indicators:
|
| 67 |
+
- Green: Downloaded (#14532d)
|
| 68 |
+
- Red: Not downloaded (#7f1d1d)
|
| 69 |
+
|
| 70 |
+
- Typography:
|
| 71 |
+
- Main font: Inter (clean, modern)
|
| 72 |
+
- Headers: Bold with gradient text
|
| 73 |
+
- Body: Medium weight, high contrast
|
| 74 |
+
|
| 75 |
+
## User Flow
|
| 76 |
+
1. Landing on playlist view
|
| 77 |
+
2. Searching for content
|
| 78 |
+
3. Adding to playlist
|
| 79 |
+
4. Managing downloads
|
| 80 |
+
5. Controlling playback
|
| 81 |
+
6. Accessing all features during playback
|
| 82 |
+
```
|
player.html
CHANGED
|
@@ -59,20 +59,23 @@
|
|
| 59 |
Search
|
| 60 |
</button>
|
| 61 |
</div>
|
| 62 |
-
|
| 63 |
<div class="flex justify-between items-center mb-6">
|
| 64 |
<h2 class="text-2xl font-semibold">Your Playlist</h2>
|
| 65 |
-
<div class="flex gap-
|
| 66 |
-
<button id="
|
| 67 |
-
|
| 68 |
-
</button>
|
| 69 |
-
<button id="zoom-in" class="p-2 bg-gray-800 rounded-lg hover:bg-gray-700">
|
| 70 |
-
<i data-feather="plus"></i>
|
| 71 |
</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
</div>
|
| 73 |
</div>
|
| 74 |
-
|
| 75 |
-
<div id="playlist-grid" class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4">
|
| 76 |
<!-- Playlist items will be inserted here -->
|
| 77 |
</div>
|
| 78 |
</div>
|
|
|
|
| 59 |
Search
|
| 60 |
</button>
|
| 61 |
</div>
|
|
|
|
| 62 |
<div class="flex justify-between items-center mb-6">
|
| 63 |
<h2 class="text-2xl font-semibold">Your Playlist</h2>
|
| 64 |
+
<div class="flex gap-4">
|
| 65 |
+
<button id="download-all" class="px-4 py-2 bg-green-600 hover:bg-green-700 rounded-lg font-medium">
|
| 66 |
+
Download New
|
|
|
|
|
|
|
|
|
|
| 67 |
</button>
|
| 68 |
+
<div class="flex gap-2">
|
| 69 |
+
<button id="zoom-out" class="p-2 bg-gray-800 rounded-lg hover:bg-gray-700">
|
| 70 |
+
<i data-feather="minus"></i>
|
| 71 |
+
</button>
|
| 72 |
+
<button id="zoom-in" class="p-2 bg-gray-800 rounded-lg hover:bg-gray-700">
|
| 73 |
+
<i data-feather="plus"></i>
|
| 74 |
+
</button>
|
| 75 |
+
</div>
|
| 76 |
</div>
|
| 77 |
</div>
|
| 78 |
+
<div id="playlist-grid" class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4">
|
|
|
|
| 79 |
<!-- Playlist items will be inserted here -->
|
| 80 |
</div>
|
| 81 |
</div>
|
script.js
CHANGED
|
@@ -81,7 +81,6 @@ function renderSearchResults(songs) {
|
|
| 81 |
grid.appendChild(card);
|
| 82 |
});
|
| 83 |
}
|
| 84 |
-
|
| 85 |
function createMusicCard(song, isPlaylist) {
|
| 86 |
const card = document.createElement('div');
|
| 87 |
card.className = `music-card ${song.downloaded ? 'downloaded' : 'not-downloaded'}`;
|
|
@@ -90,16 +89,25 @@ function createMusicCard(song, isPlaylist) {
|
|
| 90 |
card.innerHTML = `
|
| 91 |
<img src="${song.thumbnail}" class="thumbnail">
|
| 92 |
<span class="duration">${song.duration}</span>
|
| 93 |
-
|
| 94 |
<div class="info">
|
| 95 |
<div class="title">${song.title}</div>
|
| 96 |
<div class="artist">${song.artist}</div>
|
| 97 |
</div>
|
| 98 |
`;
|
| 99 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
return card;
|
| 101 |
}
|
| 102 |
-
|
| 103 |
function addToPlaylist(id) {
|
| 104 |
fetch('/api/add_to_playlist', {
|
| 105 |
method: 'POST',
|
|
|
|
| 81 |
grid.appendChild(card);
|
| 82 |
});
|
| 83 |
}
|
|
|
|
| 84 |
function createMusicCard(song, isPlaylist) {
|
| 85 |
const card = document.createElement('div');
|
| 86 |
card.className = `music-card ${song.downloaded ? 'downloaded' : 'not-downloaded'}`;
|
|
|
|
| 89 |
card.innerHTML = `
|
| 90 |
<img src="${song.thumbnail}" class="thumbnail">
|
| 91 |
<span class="duration">${song.duration}</span>
|
| 92 |
+
<div class="remove-btn"><i data-feather="x"></i></div>
|
| 93 |
<div class="info">
|
| 94 |
<div class="title">${song.title}</div>
|
| 95 |
<div class="artist">${song.artist}</div>
|
| 96 |
</div>
|
| 97 |
`;
|
| 98 |
|
| 99 |
+
if (!isPlaylist) {
|
| 100 |
+
card.addEventListener('click', () => {
|
| 101 |
+
document.querySelectorAll('#search-grid .music-card').forEach(c => {
|
| 102 |
+
c.classList.remove('selected');
|
| 103 |
+
});
|
| 104 |
+
card.classList.add('selected');
|
| 105 |
+
addToPlaylist(song.id);
|
| 106 |
+
});
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
return card;
|
| 110 |
}
|
|
|
|
| 111 |
function addToPlaylist(id) {
|
| 112 |
fetch('/api/add_to_playlist', {
|
| 113 |
method: 'POST',
|
style.css
CHANGED
|
@@ -85,11 +85,10 @@ body {
|
|
| 85 |
.music-card.not-downloaded {
|
| 86 |
background: linear-gradient(to bottom right, #1e293b, #7f1d1d);
|
| 87 |
}
|
| 88 |
-
|
| 89 |
.music-card.selected {
|
| 90 |
-
box-shadow: 0 0 0
|
|
|
|
| 91 |
}
|
| 92 |
-
|
| 93 |
.music-card .thumbnail {
|
| 94 |
width: 100%;
|
| 95 |
aspect-ratio: 1;
|
|
|
|
| 85 |
.music-card.not-downloaded {
|
| 86 |
background: linear-gradient(to bottom right, #1e293b, #7f1d1d);
|
| 87 |
}
|
|
|
|
| 88 |
.music-card.selected {
|
| 89 |
+
box-shadow: 0 0 0 3px #4ade80, 0 0 20px rgba(74, 222, 128, 0.5);
|
| 90 |
+
transition: box-shadow 0.3s ease;
|
| 91 |
}
|
|
|
|
| 92 |
.music-card .thumbnail {
|
| 93 |
width: 100%;
|
| 94 |
aspect-ratio: 1;
|