2025-09-12 09:39:18 +00:00
|
|
|
<template>
|
|
|
|
<div class="winamp-window main-window" @mousedown="startDrag">
|
|
|
|
<div class="title-bar">
|
|
|
|
<div class="title-bar-text">WINAMP</div>
|
|
|
|
<div class="title-bar-controls">
|
|
|
|
<button aria-label="Minimize"></button>
|
|
|
|
<button aria-label="Maximize"></button>
|
|
|
|
<button aria-label="Close"></button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="main-body">
|
|
|
|
<div class="top-panel">
|
|
|
|
<div class="visualizer"></div>
|
|
|
|
<div class="track-display">
|
|
|
|
<div class="track-title-marquee">
|
|
|
|
<span>{{ currentTrack ? `${currentTrackIndex + 1}. ${currentTrack.artist} - ${currentTrack.title}` : 'No Track Loaded' }}</span>
|
|
|
|
</div>
|
|
|
|
<div class="time-display">{{ formatTime(currentTime) }}</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="middle-panel">
|
|
|
|
<div class="kbps">
|
|
|
|
{{ Math.round(audioRate / 1000) || '0' }}
|
|
|
|
<span>kbps</span>
|
|
|
|
</div>
|
|
|
|
<div class="khz">
|
|
|
|
{{ Math.round(audioSampleRate / 1000) || '0' }}
|
|
|
|
<span>kHz</span>
|
|
|
|
</div>
|
|
|
|
<div class="mono-stereo">{{ stereoStatus }}</div>
|
|
|
|
</div>
|
|
|
|
<div class="bottom-panel">
|
|
|
|
<div class="control-buttons">
|
|
|
|
<button class="control-btn prev" @click="$emit('prev')"></button>
|
|
|
|
<button class="control-btn play" @click="$emit('play-pause')" :class="{ 'pause-active': isPlaying }"></button>
|
|
|
|
<button class="control-btn stop" @click="$emit('stop')"></button>
|
|
|
|
<button class="control-btn next" @click="$emit('next')"></button>
|
|
|
|
</div>
|
|
|
|
<div class="volume-slider-container">
|
|
|
|
<input
|
|
|
|
type="range"
|
|
|
|
min="0"
|
|
|
|
max="1"
|
|
|
|
step="0.01"
|
|
|
|
:value="volume"
|
|
|
|
class="slider volume-slider"
|
|
|
|
@input="$emit('update:volume', $event.target.value)"
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<div class="progress-bar-container">
|
|
|
|
<input
|
|
|
|
type="range"
|
|
|
|
min="0"
|
|
|
|
:max="duration"
|
|
|
|
:value="currentTime"
|
|
|
|
class="slider progress-slider"
|
|
|
|
@input="$emit('seek', $event.target.value)"
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
2025-09-11 16:50:28 +00:00
|
|
|
<script setup>
|
2025-09-12 09:39:18 +00:00
|
|
|
import { defineProps, defineEmits, computed } from 'vue';
|
2025-09-11 16:50:28 +00:00
|
|
|
|
2025-09-12 09:39:18 +00:00
|
|
|
const props = defineProps({
|
|
|
|
currentTrack: Object,
|
|
|
|
currentTrackIndex: Number,
|
|
|
|
isPlaying: Boolean,
|
|
|
|
currentTime: Number,
|
|
|
|
duration: Number,
|
|
|
|
volume: Number,
|
|
|
|
audioRate: Number, // Example for bitrate, you'd calculate this or get from metadata
|
|
|
|
audioSampleRate: Number, // Example for sample rate
|
|
|
|
stereoStatus: String, // 'mono' or 'stereo'
|
2025-09-12 07:24:29 +00:00
|
|
|
});
|
|
|
|
|
2025-09-12 09:39:18 +00:00
|
|
|
const emit = defineEmits(['prev', 'play-pause', 'stop', 'next', 'update:volume', 'seek', 'start-drag']);
|
2025-09-11 16:50:28 +00:00
|
|
|
|
2025-09-12 09:39:18 +00:00
|
|
|
const formatTime = (time) => {
|
|
|
|
if (isNaN(time) || time === 0) return '0:00';
|
|
|
|
const minutes = Math.floor(time / 60);
|
|
|
|
const seconds = Math.floor(time % 60).toString().padStart(2, '0');
|
|
|
|
return `${minutes}:${seconds}`;
|
|
|
|
};
|
|
|
|
|
|
|
|
const startDrag = (event) => {
|
|
|
|
if (event.target.classList.contains('title-bar')) {
|
|
|
|
emit('start-drag', event, 'main-window');
|
|
|
|
}
|
|
|
|
};
|
|
|
|
</script>
|
2025-09-11 16:50:28 +00:00
|
|
|
|
|
|
|
<style scoped>
|
2025-09-12 09:39:18 +00:00
|
|
|
@import url('https://fonts.googleapis.com/css2?family=VT323&display=swap');
|
|
|
|
|
|
|
|
.winamp-window {
|
|
|
|
font-family: 'VT323', monospace;
|
|
|
|
background-color: #3b3b3b; /* Dark grey base */
|
|
|
|
border: 1px solid #7a7a7a;
|
|
|
|
border-right-color: #000;
|
|
|
|
border-bottom-color: #000;
|
|
|
|
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.5);
|
|
|
|
position: absolute;
|
|
|
|
color: #c0c0c0; /* Light grey text */
|
|
|
|
box-sizing: border-box;
|
|
|
|
}
|
|
|
|
|
|
|
|
.main-window {
|
|
|
|
width: 275px;
|
|
|
|
height: 116px; /* Adjusted height to match the image */
|
|
|
|
left: 50px;
|
|
|
|
top: 50px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.title-bar {
|
|
|
|
background: #000;
|
|
|
|
color: #00ff00; /* Green text */
|
|
|
|
padding: 1px 3px;
|
|
|
|
display: flex;
|
|
|
|
justify-content: space-between;
|
|
|
|
align-items: center;
|
|
|
|
height: 15px;
|
|
|
|
cursor: grab;
|
|
|
|
font-size: 14px;
|
|
|
|
border-bottom: 1px solid #3b3b3b;
|
|
|
|
}
|
|
|
|
|
|
|
|
.title-bar:active { cursor: grabbing; }
|
|
|
|
|
|
|
|
.title-bar-text {
|
|
|
|
font-weight: bold;
|
|
|
|
}
|
|
|
|
|
|
|
|
.title-bar-controls button {
|
|
|
|
width: 13px;
|
|
|
|
height: 11px;
|
|
|
|
background: #c0c0c0;
|
|
|
|
border: 1px solid #fff;
|
|
|
|
border-right-color: #404040;
|
|
|
|
border-bottom-color: #404040;
|
|
|
|
margin-left: 1px;
|
|
|
|
font-size: 0; /* Hide default button text */
|
|
|
|
position: relative;
|
|
|
|
overflow: hidden;
|
|
|
|
}
|
|
|
|
|
|
|
|
.title-bar-controls button[aria-label="Close"] {
|
|
|
|
background-color: #800000; /* Red close button */
|
|
|
|
border: 1px solid #ff0000;
|
|
|
|
border-right-color: #400000;
|
|
|
|
border-bottom-color: #400000;
|
|
|
|
}
|
|
|
|
.title-bar-controls button[aria-label="Close"]::before,
|
|
|
|
.title-bar-controls button[aria-label="Close"]::after {
|
|
|
|
content: '';
|
|
|
|
position: absolute;
|
|
|
|
top: 5px; /* Center 'x' */
|
|
|
|
left: 2px;
|
|
|
|
width: 9px;
|
|
|
|
height: 1px;
|
|
|
|
background: white;
|
|
|
|
}
|
|
|
|
.title-bar-controls button[aria-label="Close"]::before {
|
|
|
|
transform: rotate(45deg);
|
|
|
|
}
|
|
|
|
.title-bar-controls button[aria-label="Close"]::after {
|
|
|
|
transform: rotate(-45deg);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Main Player Body */
|
|
|
|
.main-body {
|
|
|
|
padding: 3px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.top-panel {
|
|
|
|
display: flex;
|
|
|
|
background-color: #000;
|
|
|
|
border: 1px inset #808080;
|
|
|
|
padding: 2px;
|
|
|
|
height: 25px;
|
|
|
|
margin-bottom: 2px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.visualizer {
|
|
|
|
width: 40px; /* Placeholder for visualizer */
|
|
|
|
height: 100%;
|
|
|
|
background-color: #003300; /* Dark green for silent visualizer */
|
|
|
|
margin-right: 5px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.track-display {
|
2025-09-11 16:50:28 +00:00
|
|
|
flex-grow: 1;
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
2025-09-12 09:39:18 +00:00
|
|
|
justify-content: space-between;
|
|
|
|
color: #00ff00; /* Bright green text */
|
|
|
|
overflow: hidden;
|
|
|
|
}
|
|
|
|
|
|
|
|
.track-title-marquee {
|
|
|
|
overflow: hidden;
|
|
|
|
white-space: nowrap;
|
|
|
|
position: relative;
|
|
|
|
height: 14px; /* Adjust height for single line of text */
|
|
|
|
}
|
|
|
|
|
|
|
|
.track-title-marquee span {
|
|
|
|
position: absolute;
|
|
|
|
animation: marquee 10s linear infinite;
|
|
|
|
padding-left: 100%; /* Start off-screen */
|
|
|
|
}
|
|
|
|
|
|
|
|
@keyframes marquee {
|
|
|
|
0% { transform: translateX(0%); }
|
|
|
|
100% { transform: translateX(-100%); }
|
|
|
|
}
|
|
|
|
|
|
|
|
.time-display {
|
|
|
|
font-size: 16px;
|
|
|
|
text-align: right;
|
|
|
|
height: 14px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.middle-panel {
|
|
|
|
display: flex;
|
|
|
|
justify-content: space-between;
|
|
|
|
background-color: #000;
|
|
|
|
border: 1px inset #808080;
|
|
|
|
padding: 2px;
|
|
|
|
color: #c0c0c0;
|
|
|
|
font-size: 12px;
|
|
|
|
height: 16px;
|
|
|
|
margin-bottom: 2px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.kbps, .khz, .mono-stereo {
|
|
|
|
display: flex;
|
2025-09-11 16:50:28 +00:00
|
|
|
align-items: center;
|
|
|
|
}
|
2025-09-12 09:39:18 +00:00
|
|
|
.kbps span, .khz span {
|
|
|
|
font-size: 10px;
|
|
|
|
margin-left: 2px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.bottom-panel {
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
gap: 5px;
|
|
|
|
background-color: #3b3b3b;
|
|
|
|
padding: 2px;
|
|
|
|
border: 1px solid #7a7a7a;
|
|
|
|
border-top-color: #000;
|
|
|
|
border-left-color: #000;
|
|
|
|
}
|
|
|
|
|
|
|
|
.control-buttons {
|
|
|
|
display: flex;
|
|
|
|
gap: 1px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.control-btn {
|
|
|
|
width: 20px;
|
|
|
|
height: 18px;
|
|
|
|
border: 1px solid #7a7a7a;
|
|
|
|
border-right-color: #000;
|
|
|
|
border-bottom-color: #000;
|
|
|
|
background-color: #3b3b3b;
|
|
|
|
cursor: pointer;
|
|
|
|
background-repeat: no-repeat;
|
|
|
|
background-position: center;
|
|
|
|
background-size: 80%;
|
|
|
|
position: relative;
|
|
|
|
overflow: hidden;
|
|
|
|
}
|
|
|
|
|
|
|
|
.control-btn:active {
|
|
|
|
border: 1px solid #000;
|
|
|
|
border-right-color: #7a7a7a;
|
|
|
|
border-bottom-color: #7a7a7a;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Data URI for button icons (basic representation, replace with actual pixel art) */
|
|
|
|
.control-btn.prev { background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23c0c0c0"><path d="M11 18V6l-7 6zm3.5-6l7 6V6z"/></svg>'); }
|
|
|
|
.control-btn.play { background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23c0c0c0"><path d="M8 5v14l11-7z"/></svg>'); }
|
|
|
|
.control-btn.play.pause-active { background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23c0c0c0"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/></svg>'); }
|
|
|
|
.control-btn.stop { background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23c0c0c0"><path d="M6 6h12v12H6z"/></svg>'); }
|
|
|
|
.control-btn.next { background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23c0c0c0"><path d="M13 6v12l7-6zm-7-6v12l7-6z"/></svg>'); }
|
|
|
|
|
|
|
|
|
|
|
|
/* Sliders */
|
|
|
|
.slider {
|
|
|
|
-webkit-appearance: none;
|
|
|
|
appearance: none;
|
|
|
|
background: #000;
|
|
|
|
outline: none;
|
|
|
|
cursor: pointer;
|
|
|
|
border: 1px inset #808080; /* Sunken effect */
|
|
|
|
}
|
|
|
|
|
|
|
|
.slider::-webkit-slider-thumb {
|
|
|
|
-webkit-appearance: none;
|
|
|
|
appearance: none;
|
|
|
|
width: 8px;
|
|
|
|
height: 16px;
|
|
|
|
background: #c0c0c0;
|
|
|
|
border: 1px solid #fff;
|
|
|
|
border-right-color: #404040;
|
|
|
|
border-bottom-color: #404040;
|
|
|
|
cursor: grab;
|
|
|
|
}
|
|
|
|
|
|
|
|
.slider::-moz-range-thumb {
|
|
|
|
width: 8px;
|
|
|
|
height: 16px;
|
|
|
|
background: #c0c0c0;
|
|
|
|
border: 1px solid #fff;
|
|
|
|
border-right-color: #404040;
|
|
|
|
border-bottom-color: #404040;
|
|
|
|
cursor: grab;
|
|
|
|
}
|
|
|
|
|
|
|
|
.volume-slider-container {
|
|
|
|
width: 60px;
|
|
|
|
}
|
|
|
|
.volume-slider {
|
|
|
|
width: 100%;
|
|
|
|
height: 8px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.progress-bar-container {
|
|
|
|
flex-grow: 1;
|
|
|
|
}
|
|
|
|
.progress-slider {
|
|
|
|
width: 100%;
|
|
|
|
height: 8px;
|
|
|
|
}
|
|
|
|
</style>
|