| <!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> |
|
|