Throwback player
body {
margin: 0;
padding: 40px 16px;
background: #050505;
display: flex;
justify-content: center;
}
/* ———- shell ———- */
.tbp {
–red: #d31017;
–red-lit: #ff2b2b;
–red-deep: #6b0206;
–ink: #050505;
–steel-hi: #4a4a4a;
–steel-lo: #171717;
–text: #d8d8d8;
–text-dim: #8b8b8b;
width: 440px;
max-width: 100%;
font: 11px/1.4 Verdana, Tahoma, Geneva, sans-serif;
color: var(–text);
background: linear-gradient(#232323, #101010);
border: 1px solid #000;
border-radius: 3px;
box-shadow:
inset 0 1px 0 rgba(255,255,255,.14),
0 0 0 1px rgba(255,255,255,.05),
0 14px 34px rgba(0,0,0,.75);
padding: 6px;
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
}
.tbp *, .tbp *::before, .tbp *::after { box-sizing: border-box; }
/* ———- title bar ———- */
.tbp-titlebar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 2px 6px;
}
.tbp-wordmark {
display: flex;
align-items: baseline;
gap: 5px;
letter-spacing: .02em;
}
.tbp-wordmark b {
font-size: 15px;
font-weight: 700;
color: #fff;
text-shadow: 0 0 12px rgba(211,16,23,.6);
}
.tbp-wordmark span { font-size: 9px; color: var(–red-lit); }
.tbp-winbtns { display: flex; gap: 3px; }
.tbp-winbtns i {
width: 13px; height: 12px;
background: linear-gradient(#3d3d3d, #1a1a1a);
border: 1px solid #000;
border-radius: 2px;
box-shadow: inset 0 1px 0 rgba(255,255,255,.15);
}
/* ———- top deck ———- */
.tbp-deck { display: flex; gap: 6px; }
.tbp-art {
width: 96px; height: 96px;
flex: none;
border: 1px solid #000;
background: repeating-linear-gradient(135deg, #1b0203 0 8px, #2a0406 8px 16px);
box-shadow: inset 0 0 0 1px rgba(255,255,255,.07);
object-fit: cover;
display: block;
}
.tbp-console { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 5px; }
/* LCD */
.tbp-lcd {
position: relative;
background:
repeating-linear-gradient(rgba(0,0,0,.35) 0 1px, transparent 1px 3px),
linear-gradient(#1a0304, #0a0101);
border: 1px solid #000;
box-shadow: inset 0 0 14px rgba(211,16,23,.22), inset 0 1px 0 rgba(255,255,255,.06);
padding: 5px 7px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
overflow: hidden;
}
.tbp-lcd-text { min-width: 0; }
.tbp-track {
font-weight: 700;
font-size: 12px;
color: #ff5a55;
text-shadow: 0 0 8px rgba(255,60,55,.55);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.tbp-artist {
font-size: 10px;
color: #a8514f;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.tbp-state {
font-size: 9px;
color: var(–red-lit);
letter-spacing: .08em;
flex: none;
align-self: flex-start;
}
/* EQ */
.tbp-eq { display: flex; align-items: flex-end; gap: 2px; height: 22px; flex: none; }
.tbp-eq i {
width: 3px;
background: linear-gradient(#ff6a3d, #d31017 60%, #6b0206);
height: 4px;
transform-origin: bottom;
}
.tbp.is-playing .tbp-eq i { animation: tbp-bounce .9s ease-in-out infinite alternate; }
.tbp-eq i:nth-child(1) { animation-delay: -.10s; }
.tbp-eq i:nth-child(2) { animation-delay: -.45s; }
.tbp-eq i:nth-child(3) { animation-delay: -.72s; }
.tbp-eq i:nth-child(4) { animation-delay: -.28s; }
.tbp-eq i:nth-child(5) { animation-delay: -.60s; }
.tbp-eq i:nth-child(6) { animation-delay: -.05s; }
@keyframes tbp-bounce { from { height: 3px; } to { height: 20px; } }
/* scrubber */
.tbp-scrub { display: flex; align-items: center; gap: 7px; }
.tbp-time {
font: 10px/1 “Lucida Console”, Monaco, monospace;
color: #ff5a55;
flex: none;
letter-spacing: .04em;
}
.tbp-rail {
position: relative;
flex: 1;
height: 12px;
display: flex;
align-items: center;
border: 0;
padding: 0;
background: none;
cursor: pointer;
}
.tbp-rail::before {
content: “”;
position: absolute;
left: 0; right: 0;
height: 6px;
background: linear-gradient(#000, #1c1c1c);
border: 1px solid #000;
box-shadow: inset 0 1px 2px rgba(0,0,0,.9);
}
.tbp-fill {
position: absolute;
left: 1px;
height: 4px;
width: 0;
background: linear-gradient(var(–red-lit), var(–red-deep));
box-shadow: 0 0 7px rgba(255,43,43,.7);
}
.tbp-knob {
position: absolute;
left: 0;
width: 11px; height: 11px;
margin-left: -5px;
border-radius: 50%;
background: radial-gradient(circle at 34% 30%, #fff, #bdbdbd 45%, #4c4c4c);
border: 1px solid #000;
}
/* ———- transport ———- */
.tbp-transport {
display: flex;
align-items: center;
gap: 4px;
padding: 6px 2px 7px;
}
.tbp-btn {
width: 46px; height: 26px;
display: inline-flex;
align-items: center;
justify-content: center;
background: linear-gradient(var(–steel-hi), var(–steel-lo));
border: 1px solid #000;
border-radius: 3px;
box-shadow: inset 0 1px 0 rgba(255,255,255,.22), inset 0 -1px 0 rgba(0,0,0,.7);
cursor: pointer;
padding: 0;
}
.tbp-btn svg { fill: #cfcfcf; }
.tbp-btn:hover svg { fill: #fff; }
.tbp-btn:active {
background: linear-gradient(#0d0d0d, #262626);
box-shadow: inset 0 2px 4px rgba(0,0,0,.9);
}
.tbp-btn–play { width: 68px; }
.tbp-btn–play svg { fill: var(–red-lit); filter: drop-shadow(0 0 5px rgba(255,43,43,.75)); }
.tbp-spacer { flex: 1; }
.tbp-link {
font-size: 9px;
letter-spacing: .06em;
color: var(–text-dim);
text-decoration: none;
border: 1px solid #000;
border-radius: 3px;
background: linear-gradient(#333, #151515);
box-shadow: inset 0 1px 0 rgba(255,255,255,.16);
padding: 6px 9px;
}
.tbp-link:hover { color: #fff; }
/* ———- stats ———- */
.tbp-stats {
display: flex;
justify-content: space-between;
gap: 6px;
padding: 5px 9px;
background: linear-gradient(#e8262c, #a10a10 52%, #6b0206);
border: 1px solid #000;
box-shadow: inset 0 1px 0 rgba(255,255,255,.35), inset 0 -1px 0 rgba(0,0,0,.5);
color: #fff;
font-size: 10px;
text-shadow: 0 1px 0 rgba(0,0,0,.45);
}
.tbp-stats b { font-weight: 700; }
/* ———- barbed wire ———- */
.tbp-wire {
display: block;
width: 100%;
height: auto;
margin: 7px 0 3px;
filter: drop-shadow(0 0 6px rgba(255,43,43,.45));
pointer-events: none;
}
/* ———- list window ———- */
.tbp-window { margin-top: 3px; }
.tbp-tab {
display: flex;
align-items: center;
justify-content: space-between;
background: linear-gradient(#3a3a3a, #191919);
border: 1px solid #000;
border-bottom: 0;
box-shadow: inset 0 1px 0 rgba(255,255,255,.18);
padding: 5px 8px;
font-size: 10px;
color: #e2e2e2;
}
.tbp-tab i {
width: 0; height: 0;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 5px solid var(–red-lit);
}
.tbp-pane {
border: 1px solid #000;
background: linear-gradient(#141414, #0a0a0a);
box-shadow: inset 0 1px 0 rgba(255,255,255,.05);
padding: 4px;
}
.tbp-list { list-style: none; margin: 0; padding: 0; }
.tbp-row {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
text-align: left;
background: none;
border: 0;
border-bottom: 1px solid #1d1d1d;
padding: 8px 6px;
color: inherit;
font: inherit;
cursor: pointer;
}
.tbp-list li:last-child .tbp-row { border-bottom: 0; }
.tbp-row:hover { background: rgba(211,16,23,.13); }
.tbp-row[aria-current=”true”] { background: rgba(211,16,23,.22); }
.tbp-row[aria-current=”true”] .tbp-name { color: #ff5a55; }
.tbp-num { color: var(–text-dim); flex: none; width: 14px; }
.tbp-name {
font-weight: 700;
font-size: 12px;
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.tbp-plays { font-size: 9px; color: var(–text-dim); flex: none; }
.tbp-embed { display: block; width: 100%; border: 0; }
.tbp-foot {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 6px 2px 1px;
font-size: 9px;
color: #5f5f5f;
letter-spacing: .04em;
}
.tbp-foot a { color: #8b8b8b; text-decoration: none; }
.tbp-foot a:hover { color: var(–red-lit); }
.tbp :focus-visible { outline: 2px solid var(–red-lit); outline-offset: 1px; }
@media (max-width: 420px) {
.tbp-art { width: 74px; height: 74px; }
.tbp-btn { width: 38px; }
.tbp-btn–play { width: 54px; }
.tbp-link { display: none; }
}
@media (prefers-reduced-motion: reduce) {
.tbp.is-playing .tbp-eq i { animation: none; height: 12px; }
}
0 total plays
0 adds
0 plays today
/* ================= CONFIG ================= */
const CONFIG = {
// Your playlist. Swap in your own ID.
playlistId: ’19Ilkfzn4le7rZ2kjrTmOO’,
profileName: “XauraKat’s Mix”,
artistLine: “this mix was made just for you document.getElementById(id);
const shell = el(‘tbp’);
const playIcon = el(‘tbp-play-icon’);
const listEl = el(‘tbp-list’);
const playlistUri = ‘spotify:playlist:’ + CONFIG.playlistId;
const playlistUrl = ‘https://open.spotify.com/playlist/’ + CONFIG.playlistId;
el(‘tbp-profile’).textContent = CONFIG.profileName;
el(‘tbp-caption’).textContent = CONFIG.caption;
el(‘tbp-tab-label’).textContent = CONFIG.tabLabel;
el(‘tbp-total-plays’).textContent = CONFIG.stats.totalPlays;
el(‘tbp-adds’).textContent = CONFIG.stats.adds;
el(‘tbp-today’).textContent = CONFIG.stats.playsToday;
el(‘tbp-open’).href = playlistUrl;
el(‘tbp-foot-link’).href = playlistUrl;
if (CONFIG.artworkUrl) el(‘tbp-art’).src = CONFIG.artworkUrl;
const tracks = CONFIG.tracks || [];
const useList = tracks.length > 0;
let index = 0;
let controller = null;
let duration = 0;
let paused = true;
const clock = ms => {
const s = Math.max(0, Math.floor(ms / 1000));
return Math.floor(s / 60) + ‘:’ + String(s % 60).padStart(2, ‘0’);
};
function screen(title, sub, state) {
el(‘tbp-track’).textContent = title;
el(‘tbp-artist’).textContent = sub;
el(‘tbp-state’).textContent = state;
}
function progress(position) {
const pct = duration ? Math.min(100, (position / duration) * 100) : 0;
el(‘tbp-fill’).style.width = pct + ‘%’;
el(‘tbp-knob’).style.left = pct + ‘%’;
el(‘tbp-elapsed’).textContent = clock(position);
el(‘tbp-total’).textContent = clock(duration);
}
function setPlaying(isPlaying) {
paused = !isPlaying;
shell.classList.toggle(‘is-playing’, isPlaying);
el(‘tbp-state’).textContent = isPlaying ? ‘PLAYING’ : ‘PAUSED’;
el(‘tbp-play’).setAttribute(‘aria-label’, isPlaying ? ‘Pause’ : ‘Play’);
playIcon.innerHTML = isPlaying
? ”
: ”;
}
// Build the numbered list
if (useList) {
listEl.hidden = false;
el(‘tbp-embed-slot’).style.height = ’80px’;
tracks.forEach((t, i) => {
const li = document.createElement(‘li’);
const row = document.createElement(‘button’);
row.className = ‘tbp-row’;
row.type = ‘button’;
row.innerHTML =
‘
‘ + (i + 1) + ‘.‘ +
‘
‘ +
‘
‘;
row.querySelector(‘.tbp-name’).textContent = t.title;
row.querySelector(‘.tbp-plays’).textContent = (t.plays || ”) && t.plays + ‘ plays’;
row.addEventListener(‘click’, () => select(i, true));
li.appendChild(row);
listEl.appendChild(li);
});
}
function select(i, autoplay) {
if (!useList || !controller) return;
index = (i + tracks.length) % tracks.length;
const t = tracks[index];
listEl.querySelectorAll(‘.tbp-row’).forEach((r, n) =>
r.setAttribute(‘aria-current’, String(n === index)));
screen(t.title, CONFIG.artistLine, ‘LOADING’);
duration = 0;
progress(0);
if (t.uri) controller.loadUri(t.uri, false, 0);
if (autoplay) setTimeout(() => controller.play(), 400);
}
screen(useList ? tracks[0].title : CONFIG.profileName,
CONFIG.artistLine, ‘STOPPED’);
window.onSpotifyIframeApiReady = API => {
const first = useList && tracks[0].uri ? tracks[0].uri : playlistUri;
API.createController(
el(‘tbp-embed’),
{ uri: first, width: ‘100%’, height: useList ? 80 : CONFIG.embedHeight },
ctrl => {
controller = ctrl;
if (useList) select(0, false);
ctrl.addListener(‘playback_update’, e => {
duration = e.data.duration || duration;
progress(e.data.position || 0);
setPlaying(!e.data.isPaused);
});
el(‘tbp-play’).addEventListener(‘click’, () => ctrl.togglePlay());
el(‘tbp-prev’).addEventListener(‘click’, () => {
if (useList) select(index – 1, !paused);
else ctrl.seek(0);
});
el(‘tbp-next’).addEventListener(‘click’, () => {
if (useList) select(index + 1, !paused);
});
el(‘tbp-rail’).addEventListener(‘click’, ev => {
if (!duration) return;
const box = ev.currentTarget.getBoundingClientRect();
ctrl.seek(((ev.clientX – box.left) / box.width) * (duration / 1000));
});
if (CONFIG.autoplayOnFirstTouch) armAutoplay(ctrl);
}
);
};
// Start on the first interaction anywhere on the page. If that interaction
// is a click on the player itself, stand down and let the button do its job.
function armAutoplay(ctrl) {
const events = [‘pointerdown’, ‘keydown’, ‘touchstart’];
let done = false;
const go = ev => {
if (done) return;
done = true;
events.forEach(t => document.removeEventListener(t, go, true));
if (ev.target && ev.target.closest && ev.target.closest(‘#tbp’)) return;
ctrl.play();
};
events.forEach(t =>
document.addEventListener(t, go, { capture: true, passive: true }));
}
// If Spotify’s control script is blocked (ad blockers, sandboxed previews,
// opening the file straight off the desktop), fall back to a plain embed so
// the music still plays. Spotify’s own play button drives it in that case.
setTimeout(() => {
if (controller) return;
const frame = document.createElement(‘iframe’);
frame.className = ‘tbp-embed’;
frame.src = ‘https://open.spotify.com/embed/playlist/’ + CONFIG.playlistId + ‘?theme=0’;
frame.height = CONFIG.embedHeight;
frame.allow = ‘autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture’;
frame.loading = ‘lazy’;
frame.title = ‘Spotify playlist’;
el(‘tbp-embed-slot’).replaceChildren(frame);
el(‘tbp-embed-slot’).style.height = ”;
listEl.hidden = true;
screen(CONFIG.profileName, CONFIG.artistLine, ‘READY’);
}, 4000);
})();
https://open.spotify.com/embed/iframe-api/v1