voidscape-noir / script.js
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
document.addEventListener('DOMContentLoaded', () => {
// Initialize animations
const animatedElements = document.querySelectorAll('.fade-in, .slide-up');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('animate');
observer.unobserve(entry.target);
}
});
}, {
threshold: 0.1
});
animatedElements.forEach(el => observer.observe(el));
// Load playlist
fetch('/api/playlist')
.then(response => response.json())
.then(data => renderPlaylist(data));
// Search functionality
document.getElementById('search-btn').addEventListener('click', () => {
const query = document.getElementById('search-input').value.trim();
if (query) {
fetch('/api/search', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ query })
})
.then(response => response.json())
.then(data => renderSearchResults(data));
}
});
// Playlist item click handler
document.getElementById('playlist-grid').addEventListener('click', (e) => {
if (e.target.classList.contains('remove-btn') || e.target.closest('.remove-btn')) {
const card = e.target.closest('.music-card');
const id = parseInt(card.dataset.id);
removeFromPlaylist(id);
}
});
// Search result click handler
document.getElementById('search-grid').addEventListener('click', (e) => {
const card = e.target.closest('.music-card');
if (!card) return;
if (e.target.classList.contains('remove-btn') || e.target.closest('.remove-btn')) {
card.remove();
} else {
const id = parseInt(card.dataset.id);
addToPlaylist(id);
card.classList.add('selected');
setTimeout(() => card.classList.remove('selected'), 1000);
}
});
});
function renderPlaylist(songs) {
const grid = document.getElementById('playlist-grid');
grid.innerHTML = '';
songs.forEach(song => {
const card = createMusicCard(song, true);
grid.appendChild(card);
});
}
function renderSearchResults(songs) {
const grid = document.getElementById('search-grid');
grid.innerHTML = '';
songs.forEach(song => {
const card = createMusicCard(song, false);
grid.appendChild(card);
});
}
function createMusicCard(song, isPlaylist) {
const card = document.createElement('div');
card.className = `music-card ${song.downloaded ? 'downloaded' : 'not-downloaded'}`;
card.dataset.id = song.id;
card.innerHTML = `
<img src="${song.thumbnail}" class="thumbnail">
<span class="duration">${song.duration}</span>
<div class="remove-btn"><i data-feather="x"></i></div>
<div class="info">
<div class="title">${song.title}</div>
<div class="artist">${song.artist}</div>
</div>
`;
if (!isPlaylist) {
card.addEventListener('click', () => {
document.querySelectorAll('#search-grid .music-card').forEach(c => {
c.classList.remove('selected');
});
card.classList.add('selected');
addToPlaylist(song.id);
});
}
return card;
}
function addToPlaylist(id) {
fetch('/api/add_to_playlist', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ id })
})
.then(response => response.json())
.then(data => {
if (data.success) {
fetch('/api/playlist')
.then(response => response.json())
.then(data => renderPlaylist(data));
}
});
}
function removeFromPlaylist(id) {
fetch('/api/remove_from_playlist', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ id })
})
.then(response => response.json())
.then(data => {
if (data.success) {
fetch('/api/playlist')
.then(response => response.json())
.then(data => renderPlaylist(data));
}
});
}