const SPOTIFY_CLIENT_ID = "68d206681bfe4ceb96cbe5dcb32fbdc8"; const REDIRECT_URI = "https://dvnscoutenjoyer.gitlab.io/sillyguy/auth.html"; const SCOPES = [ "streaming", "user-read-email", "user-read-private", "user-modify-playback-state", "user-read-playback-state", "user-read-currently-playing" ]; let accessToken = null; let player = null; let deviceId = null; let isPlaying = false; // UI elements const loginButton = document.getElementById("loginButton"); const loginStatus = document.getElementById("loginStatus"); const trackTitleEl = document.getElementById("trackTitle"); const trackArtistEl = document.getElementById("trackArtist"); const trackContextEl = document.getElementById("trackContext"); const playlistLabelEl = document.getElementById("playlistLabel"); const playlistTitleEl = document.getElementById("playlistTitle"); const albumArtEl = document.getElementById("albumArt"); const elapsedTimeEl = document.getElementById("elapsedTime"); const totalTimeEl = document.getElementById("totalTime"); const progressFill = document.getElementById("progressFill"); const deviceLabelEl = document.getElementById("deviceLabel"); const playIcon = document.getElementById("playIcon"); const pauseIcon = document.getElementById("pauseIcon"); const heartBtn = document.getElementById("heartBtn"); const heartOutline = document.getElementById("heartOutline"); const heartFilled = document.getElementById("heartFilled"); const shuffleBtn = document.getElementById("shuffleBtn"); const prevBtn = document.getElementById("prevBtn"); const nextBtn = document.getElementById("nextBtn"); const playPauseBtn = document.getElementById("playPauseBtn"); const dialWrapper = document.getElementById("dial"); const dialKnob = document.getElementById("dialKnob"); const dialIndicator = document.getElementById("dialIndicator"); function formatTime(ms) { const totalSeconds = Math.floor(ms / 1000); const minutes = Math.floor(totalSeconds / 60); const seconds = String(totalSeconds % 60).padStart(2, "0"); return `${minutes}:${seconds}`; } // Simple color extractor function getDominantColor(imgEl) { try { const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); const w = (canvas.width = imgEl.naturalWidth || 100); const h = (canvas.height = imgEl.naturalHeight || 100); ctx.drawImage(imgEl, 0, 0, w, h); const data = ctx.getImageData(0, 0, w, h).data; let r = 0, g = 0, b = 0, count = 0; for (let i = 0; i < data.length; i += 20) { r += data[i]; g += data[i + 1]; b += data[i + 2]; count++; } r = Math.floor(r / count); g = Math.floor(g / count); b = Math.floor(b / count); return { r, g, b }; } catch (e) { return { r: 29, g: 185, b: 84 }; } } function applyAccentColor({ r, g, b }) { const accent = `rgb(${r}, ${g}, ${b})`; const accentSoft = `rgba(${r}, ${g}, ${b}, 0.22)`; document.documentElement.style.setProperty("--accent", accent); document.documentElement.style.setProperty("--accent-soft", accentSoft); document.body.style.background = `radial-gradient(circle at top, rgba(${r}, ${g}, ${b}, 0.35) 0, #050608 55%)`; } // Build Spotify auth URL (implicit grant) function buildAuthUrl() { const params = new URLSearchParams({ client_id: SPOTIFY_CLIENT_ID, response_type: "token", redirect_uri: REDIRECT_URI, scope: SCOPES.join(" "), show_dialog: "true" }); return `https://accounts.spotify.com/authorize?${params.toString()}`; } function getStoredToken() { const token = localStorage.getItem("spotify_access_token"); const expires = localStorage.getItem("spotify_access_token_expires"); if (!token || !expires) return null; if (Date.now() > Number(expires)) { localStorage.removeItem("spotify_access_token"); localStorage.removeItem("spotify_access_token_expires"); return null; } return token; } function initLoginFlow() { loginButton.addEventListener("click", () => { if (!accessToken) { window.location.href = buildAuthUrl(); } }); } window.onSpotifyWebPlaybackSDKReady = () => { accessToken = getStoredToken(); if (!accessToken) { loginStatus.textContent = "Connect"; deviceLabelEl.textContent = "Not connected"; return; } loginStatus.textContent = "Connected"; player = new Spotify.Player({ name: "Sillyguy Car Thing", getOAuthToken: cb => cb(accessToken), volume: 0.7 }); player.addListener("ready", ({ device_id }) => { deviceId = device_id; deviceLabelEl.textContent = "Device: Sillyguy"; }); player.addListener("not_ready", ({ device_id }) => { if (deviceId === device_id) { deviceLabelEl.textContent = "Device offline"; } }); player.addListener("player_state_changed", state => { if (!state) return; isPlaying = !state.paused; if (isPlaying) { playIcon.style.display = "none"; pauseIcon.style.display = "inline-block"; } else { playIcon.style.display = "inline-block"; pauseIcon.style.display = "none"; } const track = state.track_window.current_track; trackTitleEl.textContent = track.name || "Unknown track"; trackArtistEl.textContent = track.artists.map(a => a.name).join(", ") || "Unknown artist"; playlistTitleEl.textContent = track.album.name || "Album"; playlistLabelEl.textContent = "Now playing"; if (track.album.images && track.album.images.length > 0) { albumArtEl.crossOrigin = "anonymous"; albumArtEl.src = track.album.images[0].url; } const position = state.position; const duration = state.duration || 1; elapsedTimeEl.textContent = formatTime(position); totalTimeEl.textContent = formatTime(duration); const progress = Math.max(0, Math.min(1, position / duration)); progressFill.style.width = (progress * 100).toFixed(1) + "%"; }); player.connect(); // Controls playPauseBtn.addEventListener("click", () => { if (!player) return; player.togglePlay(); }); nextBtn.addEventListener("click", () => { if (!player) return; player.nextTrack(); }); prevBtn.addEventListener("click", () => { if (!player) return; player.previousTrack(); }); shuffleBtn.addEventListener("click", () => { if (!accessToken) return; fetch("https://api.spotify.com/v1/me/player/shuffle?state=true", { method: "PUT", headers: { Authorization: `Bearer ${accessToken}` } }).catch(() => {}); }); heartBtn.addEventListener("click", () => { heartBtn.classList.toggle("active"); const isActive = heartBtn.classList.contains("active"); heartOutline.style.display = isActive ? "none" : "inline-block"; heartFilled.style.display = isActive ? "inline-block" : "none"; }); albumArtEl.addEventListener("load", () => { const color = getDominantColor(albumArtEl); applyAccentColor(color); }); }; (function init() { initLoginFlow(); accessToken = getStoredToken(); if (!accessToken) { loginStatus.textContent = "Connect"; } else { loginStatus.textContent = "Connecting…"; } // simple dial highlight dialKnob.addEventListener("pointerdown", () => { dialWrapper.classList.add("active"); const up = () => { dialWrapper.classList.remove("active"); window.removeEventListener("pointerup", up); }; window.addEventListener("pointerup", up); }); })();