<body class="min-h-screen flex items-center justify-center text-gray-800 select-none bg-gray-100">
<!-- Main Card -->
<div class="relative w-full max-w-md bg-white rounded-[30px] shadow-2xl overflow-hidden p-8 transition-all duration-300">
<!-- Top Header -->
<div class="flex justify-between items-center mb-8">
<div class="flex items-center gap-2 text-[#177245]">
<ion-icon name="musical-notes" class="text-xl"></ion-icon>
<span class="font-semibold tracking-wide text-sm uppercase">Music Player</span>
</div>
<label for="file-upload" class="cursor-pointer group relative">
<div class="w-10 h-10 rounded-full bg-gray-100 flex items-center justify-center text-gray-600 transition-all group-hover:bg-[#177245] group-hover:text-white shadow-sm">
<ion-icon name="cloud-upload-outline" class="text-xl"></ion-icon>
</div>
<input type="file" id="file-upload" class="hidden" accept="audio/*" multiple>
</label>
</div>
<!-- Visualizer / Cover Art -->
<div class="flex justify-center mb-8">
<div class="relative w-64 h-64">
<!-- Outer glow ring -->
<div class="absolute inset-0 rounded-full bg-[#177245] opacity-10 blur-xl transform scale-90"></div>
<!-- Rotating Disk -->
<div id="cover-art" class="w-full h-full rounded-full bg-gray-100 shadow-inner border-8 border-gray-50 flex items-center justify-center rotate-animation paused-animation overflow-hidden relative">
<!-- Fallback Icon if no art -->
<div id="default-art" class="absolute inset-0 bg-gradient-to-br from-gray-100 to-gray-300 flex items-center justify-center text-gray-400">
<ion-icon name="disc" class="text-9xl opacity-20"></ion-icon>
</div>
<!-- Center hole of record -->
<div class="absolute w-16 h-16 bg-white rounded-full shadow-md z-10 flex items-center justify-center">
<div class="w-4 h-4 bg-[#177245] rounded-full opacity-50"></div>
</div>
</div>
</div>
</div>
<!-- Track Info -->
<div class="text-center mb-8">
<h2 id="track-title" class="text-2xl font-bold text-gray-800 mb-1 truncate px-4">No Song Selected</h2>
<p id="track-artist" class="text-sm text-gray-500 font-medium truncate px-8">Upload music to start listening</p>
</div>
<!-- Progress Bar -->
<div class="mb-8">
<input type="range" id="progress-slider" value="0" max="100" class="slider-progress mb-2">
<div class="flex justify-between text-xs text-gray-400 font-medium font-mono">
<span id="current-time">0:00</span>
<span id="duration">0:00</span>
</div>
</div>
<!-- Controls -->
<div class="flex justify-center items-center gap-8 mb-8">
<button id="prev-btn" class="text-gray-400 hover:text-[#177245] transition-colors">
<ion-icon name="play-skip-back" class="text-3xl"></ion-icon>
</button>
<button id="play-btn" class="w-16 h-16 bg-[#177245] rounded-full text-white shadow-lg shadow-[#177245]/40 flex items-center justify-center hover:scale-105 active:scale-95 transition-transform">
<ion-icon id="play-icon" name="play" class="text-3xl ml-1"></ion-icon>
</button>
<button id="next-btn" class="text-gray-400 hover:text-[#177245] transition-colors">
<ion-icon name="play-skip-forward" class="text-3xl"></ion-icon>
</button>
</div>
<!-- Volume Control -->
<div class="flex items-center justify-center gap-3 px-8 group">
<button id="mute-btn" class="text-gray-400 hover:text-[#177245] transition-colors">
<ion-icon id="volume-icon" name="volume-high" class="text-xl"></ion-icon>
</button>
<div class="flex-1 max-w-[100px]">
<input type="range" id="volume-slider" value="80" max="100" class="slider-progress h-1.5">
</div>
</div>
<!-- Playlist Drawer (Collapsible logic handled by simple overlay or scroll area) -->
<div class="mt-8 border-t border-gray-100 pt-4">
<div class="flex justify-between items-center mb-3 px-1">
<span class="text-xs font-bold text-gray-400 uppercase tracking-wider">Playlist</span>
<span id="track-count" class="text-xs text-[#177245] font-medium bg-[#177245]/10 px-2 py-1 rounded-md">0 Songs</span>
</div>
<ul id="playlist-container" class="h-32 overflow-y-auto playlist-scroll space-y-2 pr-1">
<!-- Playlist items injected here -->
<li class="text-center text-xs text-gray-300 py-10 italic">Playlist is empty</li>
</ul>
</div>
</div>
body {
margin: 0;
padding: 20px;
font-family: system-ui, -apple-system, sans-serif;
background: #f5f5f5;
}
.container {
background: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
max-width: 600px;
margin: 0 auto;
}
h1 {
color: #333;
margin-top: 0;
}
button {
background: #000;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
}
</style>
</head>
<body>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Minimal Audio Player</title>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Google Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
<!-- Ionicons -->
<script type="module" src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.esm.js"></script>
<script nomodule src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.js"></script>
<style>
body {
font-family: 'Inter', sans-serif;
background-color: #f3f4f6;
}
/* Custom Range Slider Styling */
input[type=range] {
-webkit-appearance: none;
width: 100%;
background: transparent;
cursor: pointer;
height: 6px;
border-radius: 5px;
outline: none;
}
/* Webkit (Chrome, Safari, Edge) */
input[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 6px;
cursor: pointer;
background: #e5e7eb; /* Gray-200 default */
border-radius: 5px;
}
input[type=range]::-webkit-slider-thumb {
height: 16px;
width: 16px;
border-radius: 50%;
background: #177245;
cursor: pointer;
-webkit-appearance: none;
margin-top: -5px; /* Centers thumb */
box-shadow: 0 2px 6px rgba(23, 114, 69, 0.4);
transition: transform 0.1s;
}
input[type=range]::-webkit-slider-thumb:hover {
transform: scale(1.2);
}
/* Firefox */
input[type=range]::-moz-range-track {
width: 100%;
height: 6px;
cursor: pointer;
background: #e5e7eb;
border-radius: 5px;
}
input[type=range]::-moz-range-thumb {
height: 16px;
width: 16px;
border: none;
border-radius: 50%;
background: #177245;
cursor: pointer;
box-shadow: 0 2px 6px rgba(23, 114, 69, 0.4);
}
/* Animation for the Record/Image */
.rotate-animation {
animation: spin 10s linear infinite;
}
.paused-animation {
animation-play-state: paused;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* Scrollbar for playlist */
.playlist-scroll::-webkit-scrollbar {
width: 6px;
}
.playlist-scroll::-webkit-scrollbar-track {
background: transparent;
}
.playlist-scroll::-webkit-scrollbar-thumb {
background-color: #d1d5db;
border-radius: 20px;
}
/* Dynamic background gradient for sliders to show progress */
.slider-progress {
background: linear-gradient(to right, #177245 0%, #177245 0%, #e5e7eb 0%, #e5e7eb 100%);
}
// DOM Elements
const fileUpload = document.getElementById('file-upload');
const playBtn = document.getElementById('play-btn');
const playIcon = document.getElementById('play-icon');
const prevBtn = document.getElementById('prev-btn');
const nextBtn = document.getElementById('next-btn');
const audio = new Audio();
const progressSlider = document.getElementById('progress-slider');
const volumeSlider = document.getElementById('volume-slider');
const currentTimeEl = document.getElementById('current-time');
const durationEl = document.getElementById('duration');
const trackTitle = document.getElementById('track-title');
const trackArtist = document.getElementById('track-artist');
const coverArt = document.getElementById('cover-art');
const playlistContainer = document.getElementById('playlist-container');
const trackCount = document.getElementById('track-count');
const muteBtn = document.getElementById('mute-btn');
const volumeIcon = document.getElementById('volume-icon');
// State
let playlist = [];
let currentSongIndex = 0;
let isPlaying = false;
let previousVolume = 0.8;
// Constants
const PRIMARY_COLOR = '#177245';
const BG_COLOR = '#e5e7eb';
// Utility: Format Time
function formatTime(seconds) {
if (isNaN(seconds)) return "0:00";
const min = Math.floor(seconds / 60);
const sec = Math.floor(seconds % 60);
return `${min}:${sec < 10 ? '0' : ''}${sec}`;
}
// Utility: Update Slider Background Gradient
function updateSliderBackground(slider) {
const val = (slider.value - slider.min) / (slider.max - slider.min) * 100;
slider.style.background = `linear-gradient(to right, ${PRIMARY_COLOR} 0%, ${PRIMARY_COLOR} ${val}%, ${BG_COLOR} ${val}%, ${BG_COLOR} 100%)`;
}
// Initialize Sliders
updateSliderBackground(progressSlider);
updateSliderBackground(volumeSlider);
// --- Event Listeners ---
// 1. File Upload
fileUpload.addEventListener('change', (e) => {
const files = Array.from(e.target.files);
if (files.length === 0) return;
// Process files
files.forEach(file => {
const url = URL.createObjectURL(file);
// Basic parsing of filename "Artist - Title.mp3" or just "Title.mp3"
let name = file.name.replace(/\.[^/.]+$/, ""); // remove extension
let artist = "Unknown Artist";
let title = name;
if (name.includes('-')) {
const parts = name.split('-');
artist = parts[0].trim();
title = parts.slice(1).join('-').trim();
}
playlist.push({
title: title,
artist: artist,
src: url,
originalName: file.name
});
});
renderPlaylist();
// If this was the first upload, load the first song
if (playlist.length === files.length) {
loadSong(0);
}
});
// 2. Render Playlist
function renderPlaylist() {
trackCount.textContent = `${playlist.length} Song${playlist.length !== 1 ? 's' : ''}`;
playlistContainer.innerHTML = '';
if (playlist.length === 0) {
playlistContainer.innerHTML = '<li class="text-center text-xs text-gray-300 py-10 italic">Playlist is empty</li>';
return;
}
playlist.forEach((song, index) => {
const li = document.createElement('li');
const isActive = index === currentSongIndex;
li.className = `flex items-center justify-between p-3 rounded-xl cursor-pointer transition-all group ${isActive ? 'bg-[#177245]/10 border-l-4 border-[#177245]' : 'hover:bg-gray-50'}`;
li.innerHTML = `
<div class="flex items-center gap-3 overflow-hidden">
<div class="w-8 h-8 rounded-lg flex items-center justify-center ${isActive ? 'bg-[#177245] text-white' : 'bg-gray-200 text-gray-500'} shrink-0">
<ion-icon name="${isActive && isPlaying ? 'musical-notes' : 'musical-note'}" class="text-sm ${isActive && isPlaying ? 'animate-pulse' : ''}"></ion-icon>
</div>
<div class="flex flex-col overflow-hidden">
<span class="text-sm font-semibold truncate ${isActive ? 'text-[#177245]' : 'text-gray-700'}">${song.title}</span>
<span class="text-xs truncate ${isActive ? 'text-[#177245]/70' : 'text-gray-400'}">${song.artist}</span>
</div>
</div>
${isActive ? '<ion-icon name="stats-chart" class="text-[#177245]"></ion-icon>' : ''}
`;
li.addEventListener('click', () => {
loadSong(index);
playSong();
});
playlistContainer.appendChild(li);
});
}
// 3. Player Logic
function loadSong(index) {
if (index < 0 || index >= playlist.length) return;
currentSongIndex = index;
const song = playlist[currentSongIndex];
audio.src = song.src;
trackTitle.textContent = song.title;
trackArtist.textContent = song.artist;
// Reset progress
progressSlider.value = 0;
updateSliderBackground(progressSlider);
currentTimeEl.textContent = "0:00";
// Update Active Playlist UI
renderPlaylist();
// Scroll playlist to active item
// Simple delay to allow DOM update
setTimeout(() => {
const activeItem = playlistContainer.children[index];
if(activeItem) {
activeItem.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
}, 100);
}
function playSong() {
if (playlist.length === 0) return;
isPlaying = true;
audio.play();
// UI Updates
playIcon.setAttribute('name', 'pause');
coverArt.classList.remove('paused-animation');
renderPlaylist(); // Update icon animation
}
function pauseSong() {
isPlaying = false;
audio.pause();
// UI Updates
playIcon.setAttribute('name', 'play');
coverArt.classList.add('paused-animation');
renderPlaylist();
}
function togglePlay() {
if (playlist.length === 0) return;
if (isPlaying) {
pauseSong();
} else {
playSong();
}
}
function prevSong() {
if (playlist.length === 0) return;
let newIndex = currentSongIndex - 1;
if (newIndex < 0) newIndex = playlist.length - 1;
loadSong(newIndex);
playSong();
}
function nextSong() {
if (playlist.length === 0) return;
let newIndex = currentSongIndex + 1;
if (newIndex >= playlist.length) newIndex = 0;
loadSong(newIndex);
playSong();
}
// 4. Audio Events
playBtn.addEventListener('click', togglePlay);
prevBtn.addEventListener('click', prevSong);
nextBtn.addEventListener('click', nextSong);
audio.addEventListener('timeupdate', (e) => {
const { duration, currentTime } = e.srcElement;
if (isNaN(duration)) return;
const progressPercent = (currentTime / duration) * 100;
progressSlider.value = progressPercent;
updateSliderBackground(progressSlider);
currentTimeEl.textContent = formatTime(currentTime);
durationEl.textContent = formatTime(duration);
});
audio.addEventListener('ended', nextSong);
// 5. Sliders
progressSlider.addEventListener('input', (e) => {
const width = e.target.value;
const duration = audio.duration;
if (!isNaN(duration)) {
audio.currentTime = (width / 100) * duration;
}
updateSliderBackground(e.target);
});
volumeSlider.addEventListener('input', (e) => {
const val = e.target.value;
audio.volume = val / 100;
updateSliderBackground(e.target);
updateVolumeIcon(val);
});
function updateVolumeIcon(val) {
if (val == 0) volumeIcon.setAttribute('name', 'volume-mute');
else if (val < 50) volumeIcon.setAttribute('name', 'volume-low');
else volumeIcon.setAttribute('name', 'volume-high');
}
muteBtn.addEventListener('click', () => {
if (audio.volume > 0) {
previousVolume = audio.volume;
audio.volume = 0;
volumeSlider.value = 0;
updateSliderBackground(volumeSlider);
updateVolumeIcon(0);
} else {
audio.volume = previousVolume;
volumeSlider.value = previousVolume * 100;
updateSliderBackground(volumeSlider);
updateVolumeIcon(previousVolume * 100);
}
});
</script>
</body>
</html>
<script>
console.log('Editor loaded!');
let clickCount = 0;
function handleClick() {
clickCount++;
const output = document.getElementById('output');
output.innerHTML = `
<h3>Clicked ${clickCount} time(s)!</h3>
<p>Time: ${new Date().toLocaleTimeString()}</p>
`;
console.log(`Button clicked ${clickCount} times`);
}
No comments yet. Be the first!