// ==UserScript==
// @name Soundgasm Enhanced Player and Downloadeder
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Transform Soundgasm player with a modern interface, reliable download, and copyable tags.
// @author You
// @match https://soundgasm.net/u/*/*
// @grant none
// @run-at document-ready
// @license mit
// ==/UserScript==
(function() {
'use strict';
let audioUrl = '';
let originalDescription = '';
let extractedTags = [];
let actualTitle = '';
function init() {
addCustomStyles();
const checkForAudio = setInterval(() => {
const audioElement = document.querySelector('audio');
if (audioElement && audioElement.src) {
clearInterval(checkForAudio);
extractContentInfo();
createEnhancedPlayer();
}
}, 500);
}
function extractContentInfo() {
// Extract audio URL
const audioElement = document.querySelector('audio');
if (audioElement && audioElement.src) {
audioUrl = audioElement.src;
}
// Extract actual title from the correct element
const titleElement = document.querySelector('#jp_container_1 .jp-title ul li');
if (titleElement) {
actualTitle = titleElement.textContent.trim();
} else {
actualTitle = document.title.replace(' : Soundgasm', '') || 'Audio Player';
}
// Extract description from the correct element
const descriptionElement = document.querySelector('#jp_container_1 .jp-description');
if (descriptionElement) {
originalDescription = descriptionElement.textContent || descriptionElement.innerText || '';
}
// Extract tags from both title and description
const titleTags = extractTags(actualTitle);
const descriptionTags = extractTags(originalDescription);
extractedTags = [...new Set([...titleTags, ...descriptionTags])];
}
function extractTags(text) {
const tagRegex = /\[([^\]]+)\]/g;
const tags = [];
let match;
while ((match = tagRegex.exec(text)) !== null) {
tags.push(match[1].trim());
}
return tags;
}
function addCustomStyles() {
const style = document.createElement('style');
style.textContent = `
* {
box-sizing: border-box;
}
body {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
font-family: 'Inter', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif !important;
margin: 0 !important;
padding: 20px !important;
min-height: 100vh !important;
color: #ffffff !important;
overflow-x: hidden !important;
}
audio {
display: none !important;
}
.enhanced-player-container {
max-width: 900px;
margin: 0 auto;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(25px);
border-radius: 24px;
padding: 40px;
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.25), 0 0 0 1px rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
animation: slideUp 0.8s cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes slideUp {
from { opacity: 0; transform: translateY(50px); }
to { opacity: 1; transform: translateY(0); }
}
.player-title {
text-align: center;
margin-bottom: 40px;
padding-bottom: 24px;
border-bottom: 2px solid rgba(255, 255, 255, 0.15);
}
.player-title h1 {
font-size: 2.8em !important;
font-weight: 700 !important;
margin: 0 0 12px 0 !important;
background: linear-gradient(135deg, #ffffff, #e0e0e0);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: none !important;
line-height: 1.2 !important;
}
.player-subtitle {
font-size: 1.3em;
opacity: 0.85;
margin: 0 !important;
font-weight: 400;
}
/* Interactive Waveform */
.waveform-container {
height: 100px;
background: rgba(0, 0, 0, 0.3);
border-radius: 16px;
margin: 30px 0 20px 0;
padding: 15px;
position: relative;
overflow: hidden;
cursor: pointer;
transition: all 0.3s ease;
}
.waveform-container:hover {
background: rgba(0, 0, 0, 0.4);
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3);
}
.waveform {
display: flex;
align-items: center;
justify-content: space-between;
height: 100%;
gap: 2px;
}
.wave-bar {
width: 4px;
background: linear-gradient(to top, #667eea, #764ba2);
border-radius: 2px;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
cursor: pointer;
opacity: 0.6;
}
.wave-bar:hover {
opacity: 1;
transform: scaleY(1.1);
}
.wave-bar.active {
background: linear-gradient(to top, #ff6b6b, #ffd93d);
opacity: 1;
box-shadow: 0 0 8px rgba(255, 107, 107, 0.5);
}
.wave-bar.playing {
animation: pulse 1.5s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { transform: scaleY(1); opacity: 0.8; }
50% { transform: scaleY(1.3); opacity: 1; }
}
/* Seek Bar */
.progress-section {
margin: 20px 0 30px 0;
}
.progress-bar-container {
position: relative;
height: 8px;
background: rgba(255, 255, 255, 0.2);
border-radius: 12px;
cursor: pointer;
margin: 20px 0;
overflow: hidden;
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.2);
}
.progress-bar-container:hover {
height: 10px;
margin: 19px 0;
}
.progress-bar {
height: 100%;
background: linear-gradient(90deg, #667eea 0%, #764ba2 50%, #ff6b6b 100%);
border-radius: 12px;
width: 0%;
transition: width 0.1s linear;
position: relative;
box-shadow: 0 0 12px rgba(102, 126, 234, 0.4);
}
.progress-bar::after {
content: '';
position: absolute;
right: -8px;
top: 50%;
transform: translateY(-50%);
width: 16px;
height: 16px;
background: white;
border-radius: 50%;
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.3);
opacity: 0;
transition: all 0.3s ease;
}
.progress-bar-container:hover .progress-bar::after {
opacity: 1;
transform: translateY(-50%) scale(1.2);
}
.time-display {
display: flex;
justify-content: space-between;
font-size: 15px;
font-weight: 500;
opacity: 0.9;
margin-top: 12px;
}
/* Control Buttons - Main Controls */
.main-controls {
display: flex;
align-items: center;
justify-content: center;
margin: 40px 0 30px 0;
gap: 24px;
}
.control-btn {
width: 64px;
height: 64px;
border-radius: 50%;
border: none;
background: rgba(255, 255, 255, 0.15);
color: white;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
font-weight: 600;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
backdrop-filter: blur(10px);
border: 2px solid rgba(255, 255, 255, 0.2);
position: relative;
overflow: hidden;
}
.control-btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s ease;
}
.control-btn:hover::before {
left: 100%;
}
.control-btn:hover {
background: rgba(255, 255, 255, 0.25);
transform: translateY(-4px) scale(1.05);
box-shadow: 0 12px 25px rgba(0, 0, 0, 0.3);
border-color: rgba(255, 255, 255, 0.4);
}
.control-btn:active {
transform: translateY(-2px) scale(0.98);
}
.play-btn {
width: 88px !important;
height: 88px !important;
font-size: 36px !important;
background: linear-gradient(135deg, #667eea, #764ba2) !important;
box-shadow: 0 15px 35px rgba(102, 126, 234, 0.4);
border: 2px solid rgba(255, 255, 255, 0.3) !important;
}
.play-btn:hover {
background: linear-gradient(135deg, #5a6fd8, #6b42a0) !important;
box-shadow: 0 20px 40px rgba(102, 126, 234, 0.6);
transform: translateY(-6px) scale(1.08) !important;
}
/* Secondary Controls */
.secondary-controls {
display: flex;
align-items: center;
justify-content: center;
margin: 20px 0 30px 0;
gap: 20px;
}
.secondary-btn {
width: 56px;
height: 56px;
border-radius: 50%;
border: none;
background: rgba(255, 255, 255, 0.1);
color: white;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
transition: all 0.3s ease;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.15);
opacity: 0.8;
}
.secondary-btn:hover {
background: rgba(255, 255, 255, 0.2);
transform: translateY(-3px);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.25);
opacity: 1;
}
.btn-active {
background: rgba(102, 126, 234, 0.7) !important;
box-shadow: 0 0 15px rgba(102, 126, 234, 0.5);
color: #ffffff !important;
}
/* Volume Section - More compact */
.volume-section {
display: flex;
align-items: center;
justify-content: center;
gap: 15px;
margin: 25px 0;
padding: 15px 20px;
background: rgba(0, 0, 0, 0.15);
border-radius: 12px;
}
.volume-slider {
width: 150px;
height: 4px;
border-radius: 2px;
background: rgba(255, 255, 255, 0.2);
outline: none;
cursor: pointer;
-webkit-appearance: none;
position: relative;
}
.volume-slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 16px;
height: 16px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea, #764ba2);
cursor: pointer;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
transition: all 0.3s ease;
}
.volume-slider::-webkit-slider-thumb:hover {
transform: scale(1.2);
box-shadow: 0 3px 10px rgba(102, 126, 234, 0.5);
}
.volume-icon {
font-size: 20px;
opacity: 0.9;
cursor: pointer;
transition: all 0.3s ease;
min-width: 24px;
}
.volume-icon:hover {
opacity: 1;
transform: scale(1.1);
}
.volume-value {
font-size: 14px;
font-weight: 600;
min-width: 40px;
text-align: center;
opacity: 0.8;
}
/* Tags Section - Below volume */
.tags-container {
margin: 25px 0;
background: rgba(0, 0, 0, 0.15);
border-radius: 16px;
padding: 20px;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.tags-title {
font-size: 1.2em;
font-weight: 600;
margin-bottom: 12px;
color: #ffffff;
opacity: 0.9;
}
.tags-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.tag {
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
padding: 6px 14px;
border-radius: 16px;
font-size: 13px;
font-weight: 500;
border: 1px solid rgba(255, 255, 255, 0.2);
transition: all 0.3s ease;
cursor: pointer; /* MODIFIED: Indicate clickable tag */
user-select: none; /* MODIFIED: Prevent text selection on click */
}
.tag:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
background: linear-gradient(135deg, #5a6fd8, #6b42a0);
}
/* Description Section - Below tags */
.description-container {
margin: 25px 0;
background: rgba(0, 0, 0, 0.15);
border-radius: 16px;
padding: 20px;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.description-title {
font-size: 1.2em;
font-weight: 600;
margin-bottom: 12px;
color: #ffffff;
opacity: 0.9;
}
.description-content {
background: rgba(255, 255, 255, 0.05);
padding: 16px;
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.1);
line-height: 1.6;
font-size: 14px;
max-height: 100px;
overflow: hidden;
transition: all 0.3s ease;
position: relative;
}
.description-content.expanded {
max-height: 500px;
overflow-y: auto;
}
.description-content::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 30px;
background: linear-gradient(transparent, rgba(0, 0, 0, 0.3));
pointer-events: none;
opacity: 1;
transition: opacity 0.3s ease;
}
.description-content.expanded::after {
opacity: 0;
}
.expand-btn {
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
border: none;
padding: 8px 16px;
border-radius: 20px;
font-size: 12px;
font-weight: 500;
cursor: pointer;
margin-top: 10px;
transition: all 0.3s ease;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.expand-btn:hover {
background: linear-gradient(135deg, #5a6fd8, #6b42a0);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}
/* Info Panel */
.info-panel {
background: rgba(0, 0, 0, 0.15);
border-radius: 12px;
padding: 20px;
margin-top: 25px;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.info-row {
display: flex;
justify-content: space-between;
align-items: center;
margin: 8px 0;
padding: 6px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.info-row:last-child {
border-bottom: none;
}
.info-label {
opacity: 0.8;
font-weight: 500;
font-size: 14px;
}
.info-value {
font-weight: 600;
font-size: 14px;
color: #ffffff;
}
/* Responsive Design */
@media (max-width: 768px) {
body {
padding: 10px !important;
}
.enhanced-player-container {
padding: 25px;
margin: 5px;
}
.player-title h1 {
font-size: 2.2em !important;
}
.main-controls {
gap: 18px;
}
.control-btn {
width: 56px;
height: 56px;
font-size: 20px;
}
.play-btn {
width: 76px !important;
height: 76px !important;
font-size: 30px !important;
}
.secondary-controls {
gap: 15px;
}
.secondary-btn {
width: 48px;
height: 48px;
font-size: 18px;
}
.volume-section {
flex-direction: column;
gap: 12px;
}
.volume-slider {
width: 180px;
}
.tags-list {
justify-content: center;
}
}
/* Loading States */
.loading {
opacity: 0.7;
pointer-events: none;
}
.loading .wave-bar {
animation: loadingWave 1.5s ease-in-out infinite;
}
@keyframes loadingWave {
0%, 100% { transform: scaleY(0.3); opacity: 0.5; }
50% { transform: scaleY(1); opacity: 1; }
}
/* Scrollbar */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
}
::-webkit-scrollbar-thumb {
background: linear-gradient(135deg, #667eea, #764ba2);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: linear-gradient(135deg, #5a6fd8, #6b42a0);
}
`;
document.head.appendChild(style);
}
function createEnhancedPlayer() {
if (!audioUrl) return;
const userName = window.location.pathname.split('/')[2] || 'User';
// Clean title and description from tags for display
const cleanTitle = actualTitle.replace(/\[[^\]]+\]/g, '').trim();
const cleanDescription = originalDescription.replace(/\[[^\]]+\]/g, '').trim();
const playerContainer = document.createElement('div');
playerContainer.className = 'enhanced-player-container';
playerContainer.innerHTML = `
<div class="player-title">
<h1>${cleanTitle}</h1>
<div class="player-subtitle">by ${userName}</div>
</div>
<div class="waveform-container" id="waveformContainer">
<div class="waveform" id="waveform">
${Array.from({length: 80}, (_, i) => `
<div class="wave-bar" data-index="${i}" style="height: ${Math.random() * 60 + 20}%;"></div>
`).join('')}
</div>
</div>
<div class="progress-section">
<div class="progress-bar-container" id="progressContainer">
<div class="progress-bar" id="progressBar"></div>
</div>
<div class="time-display">
<span id="currentTime">0:00</span>
<span id="duration">0:00</span>
</div>
</div>
<div class="main-controls">
<button class="control-btn" id="prevBtn" title="Previous (←)">⏮</button>
<button class="control-btn play-btn" id="playPauseBtn" title="Play/Pause (Space)">▶</button>
<button class="control-btn" id="nextBtn" title="Next (→)">⏭</button>
</div>
<div class="secondary-controls">
<button class="secondary-btn" id="shuffleBtn" title="Shuffle">🔀</button>
<button class="secondary-btn" id="repeatBtn" title="Repeat">🔁</button>
<button class="secondary-btn" id="downloadBtn" title="Download Audio">⬇</button>
</div>
<div class="volume-section">
<span class="volume-icon" id="volumeIcon">🔊</span>
<input type="range" class="volume-slider" id="volumeSlider" min="0" max="100" value="50">
<span class="volume-value" id="volumeValue">50%</span>
</div>
${extractedTags.length > 0 ? `
<div class="tags-container">
<div class="tags-title">Tags</div>
<div class="tags-list">
${extractedTags.map(tag => `<span class="tag">${tag}</span>`).join('')}
</div>
</div>
` : ''}
${cleanDescription ? `
<div class="description-container">
<div class="description-title">Description</div>
<div class="description-content" id="descriptionContent">
${cleanDescription.replace(/\n/g, '<br>')}
</div>
${cleanDescription.length > 150 ? `
<button class="expand-btn" id="expandBtn">Show More</button>
` : ''}
</div>
` : ''}
<div class="info-panel">
<div class="info-row">
<span class="info-label">Status:</span>
<span class="info-value" id="statusValue">Ready</span>
</div>
<div class="info-row">
<span class="info-label">Quality:</span>
<span class="info-value" id="qualityValue">High</span>
</div>
<div class="info-row">
<span class="info-label">Format:</span>
<span class="info-value" id="formatValue">MP3</span>
</div>
</div>
`;
document.body.innerHTML = '';
document.body.appendChild(playerContainer);
const enhancedAudio = document.createElement('audio');
enhancedAudio.src = audioUrl;
playerContainer.appendChild(enhancedAudio);
initializePlayer(enhancedAudio);
}
function initializePlayer(audio) {
// Get all controls
const playPauseBtn = document.getElementById('playPauseBtn');
const progressBar = document.getElementById('progressBar');
const progressContainer = document.getElementById('progressContainer');
const currentTimeSpan = document.getElementById('currentTime');
const durationSpan = document.getElementById('duration');
const volumeSlider = document.getElementById('volumeSlider');
const volumeValue = document.getElementById('volumeValue');
const volumeIcon = document.getElementById('volumeIcon');
const statusValue = document.getElementById('statusValue');
const downloadBtn = document.getElementById('downloadBtn');
const waveformContainer = document.getElementById('waveformContainer');
const waveformBars = document.querySelectorAll('.wave-bar');
const expandBtn = document.getElementById('expandBtn');
const descriptionContent = document.getElementById('descriptionContent');
const shuffleBtn = document.getElementById('shuffleBtn');
const repeatBtn = document.getElementById('repeatBtn');
let isPlaying = false;
let isDragging = false;
let shuffleEnabled = false;
let repeatEnabled = false;
audio.volume = 0.5;
// --- Player Logic ---
function togglePlayPause() {
if (isPlaying) {
audio.pause();
} else {
audio.play();
}
}
function updatePlayPauseUI(playing) {
isPlaying = playing;
playPauseBtn.innerHTML = isPlaying ? '⏸' : '▶';
statusValue.textContent = isPlaying ? 'Playing' : 'Paused';
if (isPlaying) {
updateWaveformAnimation();
} else {
waveformBars.forEach(bar => bar.classList.remove('playing'));
}
}
function updateWaveform(progress) {
const activeIndex = Math.floor((progress / 100) * waveformBars.length);
waveformBars.forEach((bar, index) => {
bar.classList.toggle('active', index <= activeIndex);
});
}
function updateWaveformAnimation() {
const currentProgress = (audio.currentTime / audio.duration) * 100;
const activeIndex = Math.floor((currentProgress / 100) * waveformBars.length);
waveformBars.forEach((bar, index) => {
bar.classList.toggle('playing', isPlaying && index === activeIndex);
});
}
function seek(e) {
const rect = progressContainer.getBoundingClientRect();
const ratio = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
if (isFinite(audio.duration)) {
audio.currentTime = ratio * audio.duration;
}
}
// --- Event Listeners ---
playPauseBtn.addEventListener('click', togglePlayPause);
audio.addEventListener('play', () => updatePlayPauseUI(true));
audio.addEventListener('pause', () => updatePlayPauseUI(false));
audio.addEventListener('timeupdate', () => {
if (!isDragging && isFinite(audio.duration)) {
const progress = (audio.currentTime / audio.duration) * 100;
progressBar.style.width = progress + '%';
currentTimeSpan.textContent = formatTime(audio.currentTime);
updateWaveform(progress);
updateWaveformAnimation();
}
});
audio.addEventListener('loadedmetadata', () => {
durationSpan.textContent = formatTime(audio.duration);
});
audio.addEventListener('ended', () => {
if (repeatEnabled) {
audio.currentTime = 0;
audio.play();
} else {
updatePlayPauseUI(false);
statusValue.textContent = 'Ended';
progressBar.style.width = '0%';
waveformBars.forEach(bar => bar.classList.remove('active', 'playing'));
}
});
['loadstart', 'waiting'].forEach(event => audio.addEventListener(event, () => {
statusValue.textContent = event === 'loadstart' ? 'Loading...' : 'Buffering...';
}));
audio.addEventListener('canplay', () => statusValue.textContent = 'Ready');
audio.addEventListener('error', () => statusValue.textContent = 'Error');
progressContainer.addEventListener('mousedown', e => {
isDragging = true;
seek(e);
});
document.addEventListener('mousemove', e => {
if (isDragging) seek(e);
});
document.addEventListener('mouseup', () => {
isDragging = false;
});
waveformContainer.addEventListener('click', (e) => {
if (isFinite(audio.duration)) {
const rect = waveformContainer.getBoundingClientRect();
const clickX = e.clientX - rect.left;
const width = rect.width;
const clickRatio = (clickX - 15) / (width - 30);
const clampedRatio = Math.max(0, Math.min(1, clickRatio));
audio.currentTime = clampedRatio * audio.duration;
}
});
volumeSlider.addEventListener('input', (e) => {
const volume = e.target.value / 100;
audio.volume = volume;
volumeValue.textContent = e.target.value + '%';
if (volume === 0) volumeIcon.textContent = '🔇';
else if (volume < 0.5) volumeIcon.textContent = '🔉';
else volumeIcon.textContent = '🔊';
});
volumeIcon.addEventListener('click', () => {
if (audio.volume > 0) {
audio.dataset.previousVolume = audio.volume;
audio.volume = 0;
volumeSlider.value = 0;
} else {
const previousVolume = parseFloat(audio.dataset.previousVolume) || 0.5;
audio.volume = previousVolume;
volumeSlider.value = previousVolume * 100;
}
volumeSlider.dispatchEvent(new Event('input'));
});
document.getElementById('prevBtn').addEventListener('click', () => {
audio.currentTime = Math.max(0, audio.currentTime - 10);
});
document.getElementById('nextBtn').addEventListener('click', () => {
audio.currentTime = Math.min(audio.duration, audio.currentTime + 10);
});
shuffleBtn.addEventListener('click', () => {
shuffleEnabled = !shuffleEnabled;
shuffleBtn.classList.toggle('btn-active', shuffleEnabled);
statusValue.textContent = `Shuffle ${shuffleEnabled ? 'ON' : 'OFF'}`;
});
repeatBtn.addEventListener('click', () => {
repeatEnabled = !repeatEnabled;
repeatBtn.classList.toggle('btn-active', repeatEnabled);
statusValue.textContent = `Repeat ${repeatEnabled ? 'ON' : 'OFF'}`;
});
// --- NEW: RELIABLE DOWNLOAD ---
downloadBtn.addEventListener('click', () => {
if (!audioUrl) return;
const originalText = downloadBtn.innerHTML;
statusValue.textContent = 'Preparing download...';
downloadBtn.innerHTML = '⏬';
try {
const link = document.createElement('a');
link.href = audioUrl;
// Sanitize filename
const fileName = (actualTitle.replace(/[\W_]+/g," ").trim() || 'audio') + '.mp3';
link.download = fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
statusValue.textContent = 'Download started!';
} catch (error) {
console.error('Download initiation failed:', error);
statusValue.textContent = 'Download failed';
} finally {
setTimeout(() => {
downloadBtn.innerHTML = originalText;
if (statusValue.textContent === 'Download started!') {
statusValue.textContent = isPlaying ? 'Playing' : 'Ready';
}
}, 2000);
}
});
if (expandBtn && descriptionContent) {
expandBtn.addEventListener('click', () => {
const isExpanded = descriptionContent.classList.toggle('expanded');
expandBtn.textContent = isExpanded ? 'Show Less' : 'Show More';
});
}
// --- NEW: COPY TAGS TO CLIPBOARD ---
const tagElements = document.querySelectorAll('.tag');
tagElements.forEach(tag => {
tag.addEventListener('click', () => {
const originalText = tag.textContent;
navigator.clipboard.writeText(originalText).then(() => {
const originalBg = tag.style.background;
tag.textContent = 'Copied!';
tag.style.background = 'linear-gradient(135deg, #28a745, #218838)';
setTimeout(() => {
tag.textContent = originalText;
tag.style.background = originalBg;
}, 1500);
}).catch(err => {
console.error('Failed to copy tag: ', err);
});
});
});
document.addEventListener('keydown', (e) => {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
switch(e.code) {
case 'Space': e.preventDefault(); togglePlayPause(); break;
case 'ArrowLeft': e.preventDefault(); audio.currentTime = Math.max(0, audio.currentTime - 5); break;
case 'ArrowRight': e.preventDefault(); audio.currentTime = Math.min(audio.duration, audio.currentTime + 5); break;
case 'ArrowUp': e.preventDefault(); volumeSlider.value = Math.min(100, parseInt(volumeSlider.value) + 5); volumeSlider.dispatchEvent(new Event('input')); break;
case 'ArrowDown': e.preventDefault(); volumeSlider.value = Math.max(0, parseInt(volumeSlider.value) - 5); volumeSlider.dispatchEvent(new Event('input')); break;
case 'KeyM': e.preventDefault(); volumeIcon.click(); break;
}
});
// Initial animation for waveform
setTimeout(() => {
waveformBars.forEach((bar, i) => {
bar.style.animationDelay = i * 0.02 + 's';
});
}, 500);
}
function formatTime(seconds) {
if (!isFinite(seconds)) return '0:00';
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins}:${secs.toString().padStart(2, '0')}`;
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();