|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1"> |
|
|
<title>Spotify Downloader</title> |
|
|
<style> |
|
|
:root { |
|
|
--spotify-green: #1DB954; |
|
|
--bg: #f9f9f9; |
|
|
--card: #fff; |
|
|
--text: #333; |
|
|
--muted: #666; |
|
|
} |
|
|
* { box-sizing: border-box; margin: 0; padding: 0; } |
|
|
body { |
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; |
|
|
background: var(--bg); |
|
|
color: var(--text); |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
min-height: 100vh; |
|
|
padding: 1rem; |
|
|
} |
|
|
.container { |
|
|
background: var(--card); |
|
|
border-radius: 12px; |
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.05); |
|
|
max-width: 360px; |
|
|
width: 100%; |
|
|
padding: 2rem; |
|
|
text-align: center; |
|
|
} |
|
|
.logo { |
|
|
width: 64px; |
|
|
margin-bottom: 1rem; |
|
|
} |
|
|
h1 { |
|
|
font-size: 1.25rem; |
|
|
margin-bottom: 1.5rem; |
|
|
color: var(--spotify-green); |
|
|
} |
|
|
form input { |
|
|
width: 100%; |
|
|
padding: 0.75rem; |
|
|
border: 1px solid #ddd; |
|
|
border-radius: 8px; |
|
|
font-size: 1rem; |
|
|
margin-bottom: 1rem; |
|
|
} |
|
|
form button { |
|
|
width: 100%; |
|
|
padding: 0.75rem; |
|
|
background: var(--spotify-green); |
|
|
color: #fff; |
|
|
border: none; |
|
|
border-radius: 8px; |
|
|
font-size: 1rem; |
|
|
cursor: pointer; |
|
|
transition: background 0.2s; |
|
|
} |
|
|
form button:hover { |
|
|
background: #17a44c; |
|
|
} |
|
|
.result { |
|
|
margin-top: 1.5rem; |
|
|
text-align: left; |
|
|
} |
|
|
.media-item { |
|
|
display: block; |
|
|
margin: 0.5rem 0; |
|
|
padding: 0.75rem; |
|
|
background: var(--bg); |
|
|
border-radius: 6px; |
|
|
text-decoration: none; |
|
|
color: var(--spotify-green); |
|
|
font-weight: 500; |
|
|
font-size: 0.95rem; |
|
|
transition: background 0.2s; |
|
|
} |
|
|
.media-item:hover { |
|
|
background: #e6f7ee; |
|
|
} |
|
|
.info { |
|
|
display: flex; |
|
|
align-items: center; |
|
|
margin-bottom: 1rem; |
|
|
} |
|
|
.info img { |
|
|
width: 64px; |
|
|
height: 64px; |
|
|
border-radius: 8px; |
|
|
margin-right: 1rem; |
|
|
} |
|
|
.info div { |
|
|
flex: 1; |
|
|
} |
|
|
.info .title { |
|
|
font-size: 1rem; |
|
|
font-weight: 600; |
|
|
margin-bottom: 0.25rem; |
|
|
} |
|
|
.info .author { |
|
|
font-size: 0.85rem; |
|
|
color: var(--muted); |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body> |
|
|
<div class="container"> |
|
|
<img src="https://cdn.jsdelivr.net/gh/edent/SuperTinyIcons/images/svg/spotify.svg" alt="Spotify" class="logo"> |
|
|
<h1>Spotify Downloader</h1> |
|
|
<form id="download-form"> |
|
|
<input type="url" id="track-url" placeholder="Enter Spotify track URL..." required> |
|
|
<button type="submit">Fetch Links</button> |
|
|
</form> |
|
|
<div id="output" class="result" hidden> |
|
|
<div class="info"> |
|
|
<img id="thumb" src="" alt="Thumbnail"> |
|
|
<div> |
|
|
<div id="track-title" class="title"></div> |
|
|
<div id="track-author" class="author"></div> |
|
|
</div> |
|
|
</div> |
|
|
<div id="links"></div> |
|
|
</div> |
|
|
</div> |
|
|
<script> |
|
|
const form = document.getElementById('download-form'); |
|
|
const output = document.getElementById('output'); |
|
|
const thumb = document.getElementById('thumb'); |
|
|
const trackTitle = document.getElementById('track-title'); |
|
|
const trackAuthor = document.getElementById('track-author'); |
|
|
const linksDiv = document.getElementById('links'); |
|
|
|
|
|
form.addEventListener('submit', async e => { |
|
|
e.preventDefault(); |
|
|
const url = document.getElementById('track-url').value.trim(); |
|
|
output.hidden = true; |
|
|
linksDiv.innerHTML = ''; |
|
|
|
|
|
try { |
|
|
const res = await fetch(`/proxy-url?url=${encodeURIComponent(url)}`); |
|
|
if (!res.ok) throw new Error('Failed to fetch'); |
|
|
const data = await res.json(); |
|
|
const info = data.data; |
|
|
thumb.src = info.thumbnail; |
|
|
trackTitle.textContent = info.title; |
|
|
trackAuthor.textContent = info.author; |
|
|
|
|
|
info.medias.forEach(item => { |
|
|
const a = document.createElement('a'); |
|
|
a.href = item.url; |
|
|
a.textContent = `Download ${item.quality}`; |
|
|
a.className = 'media-item'; |
|
|
a.setAttribute('download', ''); |
|
|
linksDiv.appendChild(a); |
|
|
}); |
|
|
|
|
|
output.hidden = false; |
|
|
} catch (err) { |
|
|
alert(err.message); |
|
|
} |
|
|
}); |
|
|
</script> |
|
|
</body> |
|
|
</html> |
|
|
|