Skip to content

Commit 1efae4a

Browse files
committed
- playing around with spotify resulted in great success: introducing the Spotify Music Quiz Cards
- add redirect to the spotify music quiz, as I already printed some of the qr codes
1 parent 03ec85b commit 1efae4a

File tree

2 files changed

+375
-365
lines changed

2 files changed

+375
-365
lines changed
Lines changed: 371 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,371 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Spotify Music Quiz Cards</title>
5+
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script>
6+
<script src="https://cdn.tailwindcss.com"></script>
7+
<style>
8+
@media print {
9+
body * {
10+
visibility: hidden;
11+
}
12+
#printArea, #printArea * {
13+
visibility: visible;
14+
}
15+
#printArea {
16+
position: absolute;
17+
left: 0;
18+
top: 0;
19+
}
20+
}
21+
22+
.card {
23+
width: 6cm;
24+
height: 6cm;
25+
border: 1px solid #e5e7eb;
26+
border-radius: 8px;
27+
background: white;
28+
padding: 10px;
29+
margin: 10px;
30+
page-break-inside: avoid !important;
31+
break-inside: avoid !important;
32+
}
33+
34+
.page {
35+
page-break-after: always !important;
36+
page-break-before: always !important;
37+
margin-bottom: 2cm;
38+
}
39+
40+
.card-container {
41+
display: grid;
42+
grid-template-columns: repeat(3, 1fr);
43+
gap: 20px;
44+
padding: 20px;
45+
page-break-inside: avoid !important;
46+
break-inside: avoid !important;
47+
}
48+
49+
.card-page {
50+
display: grid;
51+
grid-template-columns: repeat(3, 1fr);
52+
gap: 20px;
53+
padding: 20px;
54+
page-break-after: always;
55+
}
56+
57+
.qr-container {
58+
display: flex;
59+
justify-content: center;
60+
height: 100%;
61+
align-items: center;
62+
}
63+
64+
.metadata {
65+
text-align: center;
66+
height: 100%;
67+
display: flex;
68+
flex-direction: column;
69+
justify-content: center;
70+
}
71+
72+
.metadata .year {
73+
font-size: 24px;
74+
margin-top: 8px;
75+
}
76+
77+
#printArea {
78+
display: block;
79+
padding: 1cm;
80+
}
81+
82+
.hidden {
83+
display: none;
84+
}
85+
86+
#pauseResumeButton {
87+
width: 60vmin;
88+
height: 60vmin;
89+
font-size: 30vmin;
90+
border: none;
91+
border-radius: 50%;
92+
background: linear-gradient(145deg, #1DB954, #169c45);
93+
color: white;
94+
cursor: pointer;
95+
transition: all 0.3s ease;
96+
box-shadow: 0 10px 20px rgba(29, 185, 84, 0.3);
97+
display: flex;
98+
justify-content: center;
99+
align-items: center;
100+
}
101+
102+
#pauseResumeButton:hover {
103+
transform: scale(1.02);
104+
box-shadow: 0 15px 30px rgba(29, 185, 84, 0.4);
105+
}
106+
107+
#pauseResumeButton:active {
108+
transform: scale(0.98);
109+
box-shadow: 0 5px 15px rgba(29, 185, 84, 0.2);
110+
}
111+
112+
#player {
113+
display: flex;
114+
justify-content: center;
115+
align-items: center;
116+
height: 100vh;
117+
margin: 0;
118+
flex-direction: column;
119+
}
120+
121+
.button-container {
122+
display: flex;
123+
flex-direction: column;
124+
align-items: center;
125+
gap: 2rem;
126+
}
127+
128+
.scan-button {
129+
padding: 1.5rem 3rem;
130+
font-size: 1.5rem;
131+
font-weight: 600;
132+
color: white;
133+
background: linear-gradient(145deg, #1e1e1e, #2d2d2d);
134+
border: none;
135+
border-radius: 15px;
136+
cursor: pointer;
137+
text-decoration: none;
138+
transition: all 0.3s ease;
139+
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
140+
text-transform: uppercase;
141+
letter-spacing: 1px;
142+
}
143+
144+
.scan-button:hover {
145+
transform: translateY(-2px);
146+
box-shadow: 0 15px 30px rgba(0, 0, 0, 0.3);
147+
}
148+
149+
.scan-button:active {
150+
transform: translateY(1px);
151+
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
152+
}
153+
</style>
154+
</head>
155+
<body class="bg-gray-100 min-h-screen">
156+
<div id="setup" class="max-w-md mx-auto p-6">
157+
<div class="bg-white rounded-lg shadow-xl p-8">
158+
<input type="text" id="clientId" placeholder="Enter Spotify Client ID" class="w-full px-4 py-2 mb-4 border rounded">
159+
<button onclick="startAuth()" class="w-full bg-green-500 text-white py-2 px-4 rounded">Initialize</button>
160+
</div>
161+
</div>
162+
163+
<div id="player" class="hidden">
164+
<div class="button-container">
165+
<button id="pauseResumeButton">⏸️</button>
166+
<a href="https://www.gptgames.dev/tools/spotify_qr_scanner" class="scan-button">
167+
Scan for next song
168+
</a>
169+
</div>
170+
<button id="createQRButton" class="fixed top-4 right-4 bg-green-500 text-white py-2 px-4 rounded">
171+
Create QR codes from Playlist
172+
</button>
173+
</div>
174+
175+
<div id="printArea"></div>
176+
177+
<script>
178+
let playerInstance = null;
179+
let isPlaying = false;
180+
const redirectUri = 'http://gptgames.dev/tools/spotify_music_quiz_cards.html';
181+
182+
function togglePauseResume() {
183+
if (isPlaying) {
184+
playerInstance.pause();
185+
} else {
186+
playerInstance.resume();
187+
}
188+
isPlaying = !isPlaying;
189+
pauseResumeButton.textContent = isPlaying ? '⏸️' : '▶️';
190+
}
191+
192+
function getUrlParameter(name) {
193+
return new URLSearchParams(window.location.search).get(name);
194+
}
195+
196+
window.onSpotifyWebPlaybackSDKReady = () => {
197+
const token = localStorage.getItem('spotify_access_token') || getHashParams().access_token;
198+
if (token) initializePlayer(token);
199+
};
200+
201+
function startAuth() {
202+
const clientId = document.getElementById('clientId').value;
203+
localStorage.setItem('spotify_client_id', clientId);
204+
const scope = 'streaming user-read-email user-read-private user-modify-playback-state playlist-read-private';
205+
window.location.href = `https://accounts.spotify.com/authorize?client_id=${clientId}&response_type=token&redirect_uri=${encodeURIComponent(redirectUri)}&scope=${encodeURIComponent(scope)}`;
206+
}
207+
208+
function getHashParams() {
209+
const hashParams = {};
210+
window.location.hash.substring(1).split('&').forEach(param => {
211+
const [key, value] = param.split('=');
212+
hashParams[key] = decodeURIComponent(value);
213+
});
214+
return hashParams;
215+
}
216+
217+
async function getPlaylistTracks(input) {
218+
let playlistId = input.match(/playlist\/([a-zA-Z0-9]+)/)?.[1];
219+
if (!playlistId) {
220+
throw new Error('Could not extract playlist ID from URL');
221+
}
222+
const response = await fetch(`https://api.spotify.com/v1/playlists/${playlistId}/tracks`, {
223+
headers: {
224+
'Authorization': `Bearer ${localStorage.getItem('spotify_access_token')}`
225+
}
226+
});
227+
if (!response.ok) {
228+
throw new Error(`API request failed with status ${response.status}`);
229+
}
230+
const data = await response.json();
231+
return data.items
232+
.filter(item => item?.track && item.track.album?.release_date)
233+
.map(item => ({
234+
id: item.track.id || '',
235+
name: item.track.name || 'Unknown Title',
236+
artist: item.track.artists?.[0]?.name || 'Unknown Artist',
237+
year: item.track.album.release_date.split('-')[0] || 'Unknown Year'
238+
}));
239+
}
240+
241+
function createQRCard(track, isQRSide) {
242+
const card = document.createElement('div');
243+
card.className = 'card';
244+
245+
if (isQRSide) {
246+
const qrContainer = document.createElement('div');
247+
qrContainer.className = 'qr-container';
248+
qrContainer.id = `qr-${track.id}`;
249+
card.appendChild(qrContainer);
250+
251+
new QRCode(qrContainer, {
252+
text: `https://www.gptgames.dev/tools/spotify_test.html?track=${track.id}`,
253+
width: 220,
254+
height: 220,
255+
colorDark: "#000000",
256+
colorLight: "#ffffff",
257+
correctLevel: QRCode.CorrectLevel.H
258+
});
259+
} else {
260+
const metadata = document.createElement('div');
261+
metadata.className = 'metadata';
262+
metadata.innerHTML = `
263+
<h3 class="title">${track.name}</h3>
264+
<p class="artist">${track.artist}</p>
265+
<p class="year">${track.year}</p>
266+
`;
267+
card.appendChild(metadata);
268+
}
269+
270+
return card;
271+
}
272+
273+
async function createQRCodesFromPlaylist() {
274+
const input = prompt("Enter Spotify playlist URL:");
275+
if (!input) return;
276+
277+
try {
278+
const tracks = await getPlaylistTracks(input);
279+
if (tracks.length === 0) {
280+
throw new Error('No valid tracks found in playlist');
281+
}
282+
283+
const printArea = document.getElementById('printArea');
284+
printArea.innerHTML = '';
285+
286+
// Create wrapper divs
287+
const qrPage = document.createElement('div');
288+
qrPage.className = 'page';
289+
const qrContainer = document.createElement('div');
290+
qrContainer.className = 'card-container';
291+
292+
// Create QR codes page
293+
const selectedTracks = tracks.slice(0, 21);
294+
selectedTracks.forEach(track => {
295+
qrContainer.appendChild(createQRCard(track, true));
296+
});
297+
298+
qrPage.appendChild(qrContainer);
299+
printArea.appendChild(qrPage);
300+
301+
// Create metadata page
302+
const metadataPage = document.createElement('div');
303+
metadataPage.className = 'page';
304+
const metadataContainer = document.createElement('div');
305+
metadataContainer.className = 'card-container';
306+
307+
selectedTracks.forEach(track => {
308+
metadataContainer.appendChild(createQRCard(track, false));
309+
});
310+
311+
metadataPage.appendChild(metadataContainer);
312+
printArea.appendChild(metadataPage);
313+
314+
setTimeout(() => {
315+
window.print();
316+
}, 1000);
317+
318+
} catch (error) {
319+
alert('Error processing tracks: ' + error.message);
320+
console.error('Detailed error:', error);
321+
}
322+
}
323+
324+
function initializePlayer(token) {
325+
document.getElementById('setup').classList.add('hidden');
326+
document.getElementById('player').classList.remove('hidden');
327+
localStorage.setItem('spotify_access_token', token);
328+
329+
playerInstance = new Spotify.Player({
330+
name: 'Development Player',
331+
getOAuthToken: cb => {
332+
cb(token);
333+
}
334+
});
335+
336+
playerInstance.addListener('ready', async ({ device_id }) => {
337+
await fetch('https://api.spotify.com/v1/me/player', {
338+
method: 'PUT',
339+
headers: {
340+
'Authorization': `Bearer ${token}`,
341+
'Content-Type': 'application/json'
342+
},
343+
body: JSON.stringify({
344+
device_ids: [device_id],
345+
play: true
346+
})
347+
});
348+
349+
const trackId = getUrlParameter('track');
350+
if (trackId) {
351+
await fetch(`https://api.spotify.com/v1/me/player/play?device_id=${device_id}`, {
352+
method: 'PUT',
353+
headers: {
354+
'Authorization': `Bearer ${token}`,
355+
'Content-Type': 'application/json'
356+
},
357+
body: JSON.stringify({
358+
uris: [`spotify:track:${trackId}`]
359+
})
360+
});
361+
}
362+
});
363+
364+
document.getElementById('pauseResumeButton').onclick = togglePauseResume;
365+
document.getElementById('createQRButton').onclick = createQRCodesFromPlaylist;
366+
playerInstance.connect();
367+
}
368+
</script>
369+
<script src="https://sdk.scdn.co/spotify-player.js"></script>
370+
</body>
371+
</html>

0 commit comments

Comments
 (0)