Spaces:
Running
Running
-
4.86 kB
Upload 23 files
-
6.13 MB
xetUpload 23 files
-
8.99 MB
xetUpload 23 files
-
7.59 MB
xetUpload 23 files
-
7.38 MB
xetUpload 23 files
-
8.37 MB
xetUpload 23 files
-
10.5 MB
xetUpload 23 files
-
8.75 MB
xetUpload 23 files
-
7.92 MB
xetUpload 23 files
-
10.2 MB
xetUpload 23 files
-
7.95 MB
xetUpload 23 files
-
6.53 MB
xetUpload 23 files
-
10 MB
xetUpload 23 files
-
7.88 MB
xetUpload 23 files
-
8.89 MB
xetUpload 23 files
-
9.26 MB
xetUpload 23 files
-
9.12 MB
xetUpload 23 files
-
9.24 MB
xetUpload 23 files
-
129 MB
xetUpload 23 files
-
5.74 MB
xetUpload 23 files
-
12.5 MB
xetUpload 23 files
-
6.59 MB
xetUpload 23 files
-
9.34 MB
xetUpload 23 files
-
12 MB
xetUpload 23 files
-
329 Bytes
Update README.md
-
40.8 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>G.Andrei - Heavenly Music Platform</title> <script src="https://cdn.tailwindcss.com"></script> <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;700&family=Playfair+Display:wght@700&display=swap" rel="stylesheet"> <script src="https://kit.fontawesome.com/a076d05399.js" crossorigin="anonymous"></script> <style> @keyframes glow { 0% { text-shadow: 0 0 5px rgba(255, 215, 0, 0.5); } 50% { text-shadow: 0 0 20px rgba(255, 215, 0, 0.9); } 100% { text-shadow: 0 0 5px rgba(255, 215, 0, 0.5); } } @keyframes pulse { 0% { box-shadow: 0 0 5px rgba(255, 215, 0, 0.5); } 50% { box-shadow: 0 0 20px rgba(255, 215, 0, 0.9); } 100% { box-shadow: 0 0 5px rgba(255, 215, 0, 0.5); } } .glow-text { animation: glow 2s infinite; } .glow-button { animation: pulse 2s infinite; } .glow-button:hover { animation: pulse 1s infinite; } .visualizer { display: flex; justify-content: center; align-items: flex-end; height: 60px; gap: 3px; } .bar { background: linear-gradient(to top, #ffd700, #ffffff); width: 4px; border-radius: 3px; animation: equalize 1.5s infinite alternate; } @keyframes equalize { 0% { height: 10%; } 10% { height: 30%; } 20% { height: 50%; } 30% { height: 15%; } 40% { height: 60%; } 50% { height: 25%; } 60% { height: 40%; } 70% { height: 75%; } 80% { height: 30%; } 90% { height: 50%; } 100% { height: 20%; } } .bar:nth-child(1) { animation-delay: 0.1s; } .bar:nth-child(2) { animation-delay: 0.3s; } .bar:nth-child(3) { animation-delay: 0.5s; } .bar:nth-child(4) { animation-delay: 0.2s; } .bar:nth-child(5) { animation-delay: 0.4s; } .bar:nth-child(6) { animation-delay: 0.6s; } .bar:nth-child(7) { animation-delay: 0.1s; } .bar:nth-child(8) { animation-delay: 0.3s; } .bar:nth-child(9) { animation-delay: 0.5s; } .bar:nth-child(10) { animation-delay: 0.2s; } .dropzone { border: 2px dashed rgba(255, 215, 0, 0.5); transition: all 0.3s; } .dropzone.active { border: 2px dashed rgba(255, 215, 0, 1); background-color: rgba(255, 215, 0, 0.1); } .track-card { transition: all 0.3s; } .track-card:hover { transform: translateY(-5px); box-shadow: 0 10px 25px rgba(255, 215, 0, 0.3); } .neon-circle { width: 80px; height: 80px; border-radius: 50%; display: flex; justify-content: center; align-items: center; background: linear-gradient(135deg, rgba(255,215,0,0.2), rgba(255,255,255,0.3)); box-shadow: 0 0 10px rgba(255,215,0,0.5); color: white; font-weight: bold; font-size: 24px; } .modal-overlay { background-color: rgba(0, 0, 0, 0.8); backdrop-filter: blur(5px); } .modal-content { animation: modalFadeIn 0.3s ease-out; } @keyframes modalFadeIn { from { opacity: 0; transform: translateY(-20px); } to { opacity: 1; transform: translateY(0); } } .delete-confirm { animation: shake 0.5s; } @keyframes shake { 0%, 100% { transform: translateX(0); } 20%, 60% { transform: translateX(-5px); } 40%, 80% { transform: translateX(5px); } } </style> </head> <body class="bg-gradient-to-b from-white to-gray-100 min-h-screen font-sans"> <!-- Header --> <header class="bg-gradient-to-r from-white to-yellow-50 shadow-md"> <div class="container mx-auto px-4 py-6"> <div class="flex justify-between items-center"> <div class="flex items-center"> <div class="bg-gradient-to-br from-yellow-200 to-yellow-500 rounded-full p-2 mr-4 shadow-lg"> <i class="fas fa-music text-white text-2xl"></i> </div> <h1 class="text-3xl font-bold text-yellow-600 font-serif">G.Andrei</h1> </div> <div class="text-center"> <p class="text-yellow-700 font-semibold glow-text">Люби Иисуса ❤️</p> </div> <button id="add-track-btn" class="bg-yellow-500 hover:bg-yellow-600 text-white font-bold py-2 px-6 rounded-full glow-button transition-all flex items-center"> <i class="fas fa-plus mr-2"></i> Добавить трек </button> </div> </div> </header> <!-- Main Content --> <main class="container mx-auto px-4 py-8"> <!-- Player Controls --> <div class="bg-white rounded-xl shadow-lg p-6 mb-8 relative overflow-hidden"> <div class="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-yellow-200 to-yellow-500"></div> <div class="flex flex-col md:flex-row justify-between items-center mb-6"> <div class="visualizer mb-4 md:mb-0 w-full md:w-1/3"> <div class="bar"></div> <div class="bar"></div> <div class="bar"></div> <div class="bar"></div> <div class="bar"></div> <div class="bar"></div> <div class="bar"></div> <div class="bar"></div> <div class="bar"></div> <div class="bar"></div> </div> <div class="flex items-center space-x-4"> <button id="play-all" class="bg-yellow-500 hover:bg-yellow-600 text-white font-bold py-3 px-8 rounded-full glow-button transition-all flex items-center"> <i class="fas fa-play mr-2"></i> Воспроизвести всё </button> <div class="flex items-center"> <span class="text-gray-600 mr-2">RANDOM</span> <label class="relative inline-flex items-center cursor-pointer"> <input type="checkbox" id="random-mode" class="sr-only peer"> <div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-yellow-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-yellow-500"></div> </label> </div> </div> </div> <div class="text-right"> <p class="text-yellow-600 font-semibold"> Всего треков: <span id="total-tracks" class="text-2xl font-bold">0</span> </p> </div> </div> <!-- Drop Zone --> <div id="dropzone" class="dropzone bg-white rounded-xl shadow-lg p-8 mb-8 text-center cursor-pointer transition-all"> <i class="fas fa-cloud-upload-alt text-yellow-400 text-4xl mb-4"></i> <h3 class="text-xl font-bold text-gray-700 mb-2">Добавь список треков</h3> <p class="text-gray-500">Перетащи сюда аудиофайлы или кликни для выбора</p> <input type="file" id="file-input" class="hidden" multiple accept="audio/*"> </div> <!-- Tracks List --> <div id="tracks-container" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> <!-- Track cards will be added here dynamically --> </div> </main> <!-- Add Track Modal --> <div id="add-track-modal" class="fixed inset-0 z-50 hidden modal-overlay"> <div class="flex items-center justify-center min-h-screen"> <div class="modal-content bg-white rounded-xl shadow-2xl w-full max-w-md mx-4"> <div class="p-6"> <div class="flex justify-between items-center mb-4"> <h2 class="text-2xl font-bold text-yellow-600">Добавить новый трек</h2> <button id="close-modal" class="text-gray-400 hover:text-gray-600"> <i class="fas fa-times text-xl"></i> </button> </div> <form id="track-form" class="space-y-4"> <div> <label for="track-name" class="block text-gray-700 mb-2">Название трека</label> <input type="text" id="track-name" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-yellow-500 focus:border-yellow-500" placeholder="Трек #XXX"> </div> <div> <label class="block text-gray-700 mb-2">Обложка</label> <div class="flex items-center space-x-4"> <div id="cover-preview" class="neon-circle flex-shrink-0"> NP </div> <div class="flex-1"> <input type="file" id="cover-input" class="hidden" accept="image/*"> <button type="button" id="upload-cover" class="bg-yellow-100 hover:bg-yellow-200 text-yellow-700 font-medium py-2 px-4 rounded-lg transition-all"> Загрузить обложку </button> <p class="text-xs text-gray-500 mt-1">PNG/JPG, не более 7 Мб</p> </div> </div> </div> <div> <label class="block text-gray-700 mb-2">Аудиофайл</label> <input type="file" id="audio-input" class="hidden" accept="audio/*"> <button type="button" id="upload-audio" class="w-full bg-yellow-100 hover:bg-yellow-200 text-yellow-700 font-medium py-2 px-4 rounded-lg transition-all"> Загрузить аудиофайл </button> <p class="text-xs text-gray-500 mt-1">MP3/WAV, не более 50 Мб</p> </div> <div class="pt-4"> <button type="submit" class="w-full bg-yellow-500 hover:bg-yellow-600 text-white font-bold py-3 px-4 rounded-full glow-button transition-all"> Добавить трек </button> </div> </form> </div> </div> </div> </div> <!-- Audio Element --> <audio id="audio-player" class="hidden"></audio> <script> // Sample data for tracks let tracks = [ { id: 1, title: "Небесная мелодия", cover: "https://source.unsplash.com/random/300x300/?angel", audio: "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3", duration: "3:45" }, { id: 2, title: "Ангельский хор", cover: "https://source.unsplash.com/random/300x300/?heaven", audio: "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3", duration: "4:12" }, { id: 3, title: "Свет любви", cover: "https://source.unsplash.com/random/300x300/?light", audio: "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-3.mp3", duration: "2:58" } ]; // DOM Elements const tracksContainer = document.getElementById('tracks-container'); const totalTracksElement = document.getElementById('total-tracks'); const addTrackBtn = document.getElementById('add-track-btn'); const addTrackModal = document.getElementById('add-track-modal'); const closeModalBtn = document.getElementById('close-modal'); const trackForm = document.getElementById('track-form'); const dropzone = document.getElementById('dropzone'); const fileInput = document.getElementById('file-input'); const audioPlayer = document.getElementById('audio-player'); const playAllBtn = document.getElementById('play-all'); const randomMode = document.getElementById('random-mode'); const uploadCoverBtn = document.getElementById('upload-cover'); const coverInput = document.getElementById('cover-input'); const coverPreview = document.getElementById('cover-preview'); const uploadAudioBtn = document.getElementById('upload-audio'); const audioInput = document.getElementById('audio-input'); // Variables let currentTrackIndex = 0; let isPlaying = false; let deleteClickCount = 0; let lastDeletedTrackId = null; // Initialize the app function init() { renderTracks(); updateTotalTracks(); setupEventListeners(); } // Render all tracks function renderTracks() { tracksContainer.innerHTML = ''; tracks.forEach((track, index) => { const trackCard = document.createElement('div'); trackCard.className = 'track-card bg-white rounded-xl shadow-md overflow-hidden hover:shadow-lg'; trackCard.dataset.id = track.id; trackCard.draggable = true; trackCard.innerHTML = ` <div class="relative"> ${track.cover ? `<img src="${track.cover}" alt="${track.title}" class="w-full h-48 object-cover">` : `<div class="w-full h-48 flex items-center justify-center bg-gradient-to-br from-yellow-100 to-yellow-200"> <div class="neon-circle">${getInitials(track.title)}</div> </div>` } <div class="absolute top-2 right-2"> <button class="delete-track bg-red-500 hover:bg-red-600 text-white rounded-full w-8 h-8 flex items-center justify-center shadow-md" data-id="${track.id}"> <i class="fas fa-trash text-xs"></i> </button> </div> </div> <div class="p-4"> <h3 class="font-bold text-lg text-gray-800 mb-2 truncate">${track.title}</h3> <div class="flex items-center justify-between"> <div class="flex items-center space-x-3"> <button class="play-pause bg-yellow-500 hover:bg-yellow-600 text-white rounded-full w-10 h-10 flex items-center justify-center shadow-md" data-index="${index}"> <i class="fas fa-play"></i> </button> <span class="time-display text-gray-600 cursor-pointer" data-index="${index}">${track.duration}</span> </div> <span class="text-xs text-gray-500">ID: ${track.id}</span> </div> </div> `; tracksContainer.appendChild(trackCard); }); } // Get initials for cover function getInitials(title) { const words = title.split(' '); let initials = ''; if (words.length >= 2) { initials = words[0].charAt(0) + words[1].charAt(0); } else if (title.length >= 2) { initials = title.substring(0, 2); } else { initials = title.charAt(0) + 'X'; } return initials.toUpperCase(); } // Update total tracks count function updateTotalTracks() { totalTracksElement.textContent = tracks.length; } // Play track function playTrack(index) { const track = tracks[index]; currentTrackIndex = index; audioPlayer.src = track.audio; audioPlayer.play() .then(() => { isPlaying = true; updatePlayPauseButtons(); // Highlight current playing track document.querySelectorAll('.track-card').forEach(card => { card.classList.remove('ring-2', 'ring-yellow-500'); }); const currentCard = document.querySelector(`.play-pause[data-index="${index}"]`).closest('.track-card'); currentCard.classList.add('ring-2', 'ring-yellow-500'); }) .catch(error => { console.error('Error playing track:', error); }); } // Pause track function pauseTrack() { audioPlayer.pause(); isPlaying = false; updatePlayPauseButtons(); } // Update play/pause buttons function updatePlayPauseButtons() { document.querySelectorAll('.play-pause').forEach(button => { const index = parseInt(button.dataset.index); if (index === currentTrackIndex && isPlaying) { button.innerHTML = '<i class="fas fa-pause"></i>'; button.classList.add('bg-yellow-600'); } else { button.innerHTML = '<i class="fas fa-play"></i>'; button.classList.remove('bg-yellow-600'); } }); } // Play all tracks function playAllTracks() { if (tracks.length === 0) return; if (randomMode.checked) { // Play in random order const randomIndex = Math.floor(Math.random() * tracks.length); playTrack(randomIndex); } else { // Play in sequence playTrack(0); } } // Add new track function addTrack(title, cover, audio) { const newTrack = { id: Date.now(), title: title || `Трек #${tracks.length + 1}`, cover: cover || null, audio: audio, duration: "0:00" // Would be calculated from actual audio file in real implementation }; tracks.push(newTrack); renderTracks(); updateTotalTracks(); // Play sound effect const sound = new Audio('https://assets.mixkit.co/sfx/preview/mixkit-arcade-game-jump-coin-216.mp3'); sound.volume = 0.3; sound.play(); } // Delete track function deleteTrack(id) { if (lastDeletedTrackId === id && deleteClickCount >= 2) { // Third consecutive click on same track - delete it tracks = tracks.filter(track => track.id !== id); renderTracks(); updateTotalTracks(); deleteClickCount = 0; lastDeletedTrackId = null; // Show confirmation message alert('Трек успешно удалён!'); } else if (lastDeletedTrackId === id) { // Second click on same track deleteClickCount++; // Show warning const deleteBtn = document.querySelector(`.delete-track[data-id="${id}"]`); deleteBtn.classList.add('delete-confirm', 'bg-red-600'); setTimeout(() => { deleteBtn.classList.remove('delete-confirm', 'bg-red-600'); }, 500); } else { // First click on this track lastDeletedTrackId = id; deleteClickCount = 1; } } // Setup event listeners function setupEventListeners() { // Play/Pause buttons document.addEventListener('click', (e) => { if (e.target.closest('.play-pause')) { const button = e.target.closest('.play-pause'); const index = parseInt(button.dataset.index); if (index === currentTrackIndex && isPlaying) { pauseTrack(); } else { playTrack(index); } } // Time display click if (e.target.classList.contains('time-display')) { const index = parseInt(e.target.dataset.index); // In a real app, this would seek to that time in the track alert(`В реальном приложении здесь будет перемотка трека #${index + 1} на выбранное время`); } // Delete track if (e.target.closest('.delete-track')) { const button = e.target.closest('.delete-track'); const id = parseInt(button.dataset.id); deleteTrack(id); } }); // Play all button playAllBtn.addEventListener('click', playAllTracks); // Random mode toggle randomMode.addEventListener('change', () => { if (randomMode.checked) { playAllBtn.innerHTML = '<i class="fas fa-random mr-2"></i> Случайный порядок'; } else { playAllBtn.innerHTML = '<i class="fas fa-play mr-2"></i> Воспроизвести всё'; } }); // Add track modal addTrackBtn.addEventListener('click', () => { addTrackModal.classList.remove('hidden'); }); closeModalBtn.addEventListener('click', () => { addTrackModal.classList.add('hidden'); }); // Track form submission trackForm.addEventListener('submit', (e) => { e.preventDefault(); const title = document.getElementById('track-name').value; const cover = coverInput.files[0] ? URL.createObjectURL(coverInput.files[0]) : null; const audio = audioInput.files[0] ? URL.createObjectURL(audioInput.files[0]) : null; if (!audio) { alert('Пожалуйста, загрузите аудиофайл'); return; } addTrack(title, cover, audio); addTrackModal.classList.add('hidden'); trackForm.reset(); coverPreview.textContent = 'NP'; }); // Upload cover button uploadCoverBtn.addEventListener('click', () => { coverInput.click(); }); coverInput.addEventListener('change', () => { if (coverInput.files && coverInput.files[0]) { const reader = new FileReader(); reader.onload = (e) => { coverPreview.innerHTML = `<img src="${e.target.result}" class="w-full h-full object-cover rounded-full">`; }; reader.readAsDataURL(coverInput.files[0]); } }); // Upload audio button uploadAudioBtn.addEventListener('click', () => { audioInput.click(); }); // Audio player events audioPlayer.addEventListener('ended', () => { if (randomMode.checked) { const nextIndex = Math.floor(Math.random() * tracks.length); playTrack(nextIndex); } else { const nextIndex = (currentTrackIndex + 1) % tracks.length; playTrack(nextIndex); } }); // Dropzone functionality dropzone.addEventListener('click', () => { fileInput.click(); }); fileInput.addEventListener('change', (e) => { if (fileInput.files.length > 0) { handleFiles(fileInput.files); } }); ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { dropzone.addEventListener(eventName, preventDefaults, false); }); function preventDefaults(e) { e.preventDefault(); e.stopPropagation(); } ['dragenter', 'dragover'].forEach(eventName => { dropzone.addEventListener(eventName, highlight, false); }); ['dragleave', 'drop'].forEach(eventName => { dropzone.addEventListener(eventName, unhighlight, false); }); function highlight() { dropzone.classList.add('active'); } function unhighlight() { dropzone.classList.remove('active'); } dropzone.addEventListener('drop', (e) => { const dt = e.dataTransfer; const files = dt.files; if (files.length > 0) { handleFiles(files); } }); // Drag and drop for tracks tracksContainer.addEventListener('dragstart', (e) => { if (e.target.classList.contains('track-card')) { e.target.classList.add('opacity-50'); e.dataTransfer.setData('text/plain', e.target.dataset.id); } }); tracksContainer.addEventListener('dragend', (e) => { if (e.target.classList.contains('track-card')) { e.target.classList.remove('opacity-50'); } }); tracksContainer.addEventListener('dragover', (e) => { e.preventDefault(); const afterElement = getDragAfterElement(e.clientY); const draggable = document.querySelector('.track-card.opacity-50'); if (afterElement == null) { tracksContainer.appendChild(draggable); } else { tracksContainer.insertBefore(draggable, afterElement); } }); function getDragAfterElement(y) { const draggableElements = [...document.querySelectorAll('.track-card:not(.opacity-50)')]; return draggableElements.reduce((closest, child) => { const box = child.getBoundingClientRect(); const offset = y - box.top - box.height / 2; if (offset < 0 && offset > closest.offset) { return { offset: offset, element: child }; } else { return closest; } }, { offset: Number.NEGATIVE_INFINITY }).element; } } // Handle dropped files function handleFiles(files) { for (let i = 0; i < files.length; i++) { const file = files[i]; if (file.type.startsWith('audio/')) { const title = file.name.replace(/\.[^/.]+$/, ""); // Remove extension const audioUrl = URL.createObjectURL(file); // Generate random pixel art cover const randomNum = Math.floor(Math.random() * 1000); const coverUrl = `https://source.unsplash.com/random/300x300/?pixel,abstract,${randomNum}`; addTrack(title, coverUrl, audioUrl); } } // Play sound effect const sound = new Audio('https://assets.mixkit.co/sfx/preview/mixkit-unlock-game-notification-253.mp3'); sound.volume = 0.3; sound.play(); } // Initialize the app init(); </script> </body> </html> - Follow Up Deployment
-
388 Bytes
initial commit