Open demo

CSS code

HTML
<div><span class="lbl">Now playing screen</span>
<section class="np np-screen" data-np aria-label="Now playing, Late Night Drive">
  <audio src="../audio/track-3.mp3" preload="metadata"></audio>
  <p class="np-from">Playing from playlist <b>Late Night Drive</b></p>
  <div class="np-cover np-art" data-cover style="--art:radial-gradient(circle at 68% 30%,#fef3c7 0 9%,rgba(254,243,199,.16) 9.5% 15%,transparent 15.5%),radial-gradient(circle at 20% 25%,#fff 0 1.2%,transparent 2%),radial-gradient(circle at 38% 55%,#fff 0 .9%,transparent 1.6%),radial-gradient(circle at 86% 64%,#fff 0 1%,transparent 1.7%),radial-gradient(circle at 12% 70%,#c4b5fd 0 1%,transparent 1.7%),linear-gradient(180deg,#0f172a,#312e81 70%,#7c3aed)" aria-hidden="true"></div>
  <div class="np-head">
    <div><p class="np-title" data-show="name">Night Bus to Nowhere</p><p class="np-artist" data-show="artist">Orla Finch</p></div>
    <button type="button" class="np-icon" data-like aria-label="Like Night Bus to Nowhere" aria-pressed="false"><svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20s-7.5-4.6-7.5-10.2A4.2 4.2 0 0 1 12 7.3a4.2 4.2 0 0 1 7.5 2.5C19.5 15.4 12 20 12 20z"/></svg></button>
  </div>
  <div>
    <input type="range" data-seek min="0" max="30" step="any" value="0" aria-label="Seek" aria-valuetext="0:00 of 0:30">
    <div class="np-times"><span class="np-time" data-cur>0:00</span><span class="np-time" data-dur>0:30</span></div>
  </div>
  <div class="np-ctrls">
    <button type="button" class="np-icon" data-shuffle aria-label="Shuffle" aria-pressed="false"><svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 4h4v4M4 20 20 4M20 16v4h-4M15 15l5 5M4 4l5 5"/></svg></button>
    <button type="button" class="np-skip" data-prev aria-label="Previous track"><svg viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M5 5h2.2v14H5zM19.5 6.2v11.6a.8.8 0 0 1-1.24.67L9.6 12.67a.8.8 0 0 1 0-1.34l8.66-5.8a.8.8 0 0 1 1.24.67z"/></svg></button>
    <button type="button" class="np-play" data-play aria-label="Play Night Bus to Nowhere"><svg class="i-play" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M8 5.5v13a1 1 0 0 0 1.5.86l10.5-6.5a1 1 0 0 0 0-1.72L9.5 4.64A1 1 0 0 0 8 5.5z"/></svg><svg class="i-pause" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><rect x="6.5" y="5" width="4" height="14" rx="1.2"/><rect x="13.5" y="5" width="4" height="14" rx="1.2"/></svg></button>
    <button type="button" class="np-skip" data-next aria-label="Next track"><svg viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M16.8 5H19v14h-2.2zM4.5 6.2v11.6a.8.8 0 0 0 1.24.67l8.66-5.8a.8.8 0 0 0 0-1.34L5.74 5.53a.8.8 0 0 0-1.24.67z"/></svg></button>
    <button type="button" class="np-icon" data-repeat aria-label="Repeat this track" aria-pressed="false"><svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m17 2 4 4-4 4"/><path d="M3 11V9a3 3 0 0 1 3-3h15"/><path d="m7 22-4-4 4-4"/><path d="M21 13v2a3 3 0 0 1-3 3H3"/></svg></button>
  </div>
  <div class="np-volume">
    <button type="button" class="np-icon np-mute" data-mute aria-label="Mute" aria-pressed="false"><svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 5 6 9H3v6h3l5 4z"/><path class="i-wave" d="M15.5 8.5a5 5 0 0 1 0 7M18.5 5.5a9 9 0 0 1 0 13"/><path class="i-x" d="m16 9 6 6m0-6-6 6"/></svg></button>
    <input type="range" class="np-vol" data-vol min="0" max="1" step="0.05" value="0.7" aria-label="Volume" aria-valuetext="70%">
  </div>
  <p class="np-upnext">Queue</p>
  <ol class="np-queue">
    <li><button type="button" class="np-item" data-src="../audio/track-3.mp3" data-name="Night Bus to Nowhere" data-artist="Orla Finch" aria-current="true">
      <span class="np-art" data-art style="--art:radial-gradient(circle at 68% 30%,#fef3c7 0 9%,rgba(254,243,199,.16) 9.5% 15%,transparent 15.5%),radial-gradient(circle at 20% 25%,#fff 0 1.2%,transparent 2%),radial-gradient(circle at 38% 55%,#fff 0 .9%,transparent 1.6%),radial-gradient(circle at 86% 64%,#fff 0 1%,transparent 1.7%),radial-gradient(circle at 12% 70%,#c4b5fd 0 1%,transparent 1.7%),linear-gradient(180deg,#0f172a,#312e81 70%,#7c3aed)"></span><span><span class="np-name">Night Bus to Nowhere</span><span class="np-by">Orla Finch</span></span><span class="np-time">0:30</span></button></li>
    <li><button type="button" class="np-item" data-src="../audio/track-1.mp3" data-name="Harbour Lights" data-artist="Juniper Vale">
      <span class="np-art" data-art style="--art:radial-gradient(circle at 68% 40%,#fde68a 0 11%,transparent 11.5%),repeating-linear-gradient(180deg,transparent 0 6%,rgba(253,230,138,.3) 6% 7.5%) bottom/100% 42% no-repeat,linear-gradient(180deg,#fb923c,#e11d48 58%,#312e81 58%,#1e1b4b)"></span><span><span class="np-name">Harbour Lights</span><span class="np-by">Juniper Vale</span></span><span class="np-time">0:24</span></button></li>
    <li><button type="button" class="np-item" data-src="../audio/track-4.mp3" data-name="Copper Sky" data-artist="Milo & the Tidelines">
      <span class="np-art" data-art style="--art:radial-gradient(circle at 50% 100%,#fff7ed 0 24%,#fdba74 24% 27%,transparent 27.5%),linear-gradient(180deg,#7c2d12 0 22%,#9a3412 22% 40%,#c2410c 40% 56%,#ea580c 56% 70%,#f59e0b 70% 84%,#fcd34d 84%)"></span><span><span class="np-name">Copper Sky</span><span class="np-by">Milo & the Tidelines</span></span><span class="np-time">0:29</span></button></li>
  </ol>
</section></div>

<div><span class="lbl">Desktop player bar</span>
<section class="np np-desk" data-np aria-label="Player">
  <audio src="../audio/track-2.mp3" preload="metadata"></audio>
  <div class="np-row">
    <div class="np-now">
      <div class="np-art" data-cover style="--art:linear-gradient(to bottom right,transparent 50%,#fde047 50%) 37.5% 26.67%/20% 25% no-repeat,linear-gradient(to bottom left,transparent 50%,#f97316 50%) 62.5% 26.67%/20% 25% no-repeat,linear-gradient(to top right,transparent 50%,#fb923c 50%) 37.5% 69.23%/20% 35% no-repeat,linear-gradient(to top left,transparent 50%,#facc15 50%) 62.5% 69.23%/20% 35% no-repeat,linear-gradient(to bottom right,transparent calc(50% - 1px),rgba(255,255,255,.85) 0 calc(50% + 1px),transparent 0) 41.86% 100%/14% 20% no-repeat,linear-gradient(160deg,#0ea5e9,#14b8a6 55%,#84cc16)" aria-hidden="true"></div>
      <div><p class="np-title" data-show="name">Paper Kites</p><p class="np-artist" data-show="artist">The Lumen Choir</p></div>
      <button type="button" class="np-icon" data-like aria-label="Like Paper Kites" aria-pressed="false"><svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20s-7.5-4.6-7.5-10.2A4.2 4.2 0 0 1 12 7.3a4.2 4.2 0 0 1 7.5 2.5C19.5 15.4 12 20 12 20z"/></svg></button>
    </div>
    <div class="np-mid">
      <div class="np-ctrls">
        <button type="button" class="np-skip" data-prev aria-label="Previous track"><svg viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M5 5h2.2v14H5zM19.5 6.2v11.6a.8.8 0 0 1-1.24.67L9.6 12.67a.8.8 0 0 1 0-1.34l8.66-5.8a.8.8 0 0 1 1.24.67z"/></svg></button>
        <button type="button" class="np-play" data-play aria-label="Play Paper Kites"><svg class="i-play" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M8 5.5v13a1 1 0 0 0 1.5.86l10.5-6.5a1 1 0 0 0 0-1.72L9.5 4.64A1 1 0 0 0 8 5.5z"/></svg><svg class="i-pause" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><rect x="6.5" y="5" width="4" height="14" rx="1.2"/><rect x="13.5" y="5" width="4" height="14" rx="1.2"/></svg></button>
        <button type="button" class="np-skip" data-next aria-label="Next track"><svg viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M16.8 5H19v14h-2.2zM4.5 6.2v11.6a.8.8 0 0 0 1.24.67l8.66-5.8a.8.8 0 0 0 0-1.34L5.74 5.53a.8.8 0 0 0-1.24.67z"/></svg></button>
      </div>
      <div class="np-bar">
        <span class="np-time" data-cur>0:00</span>
        <input type="range" data-seek min="0" max="24" step="any" value="0" aria-label="Seek" aria-valuetext="0:00 of 0:24">
        <span class="np-time" data-dur>0:24</span>
      </div>
    </div>
    <div class="np-side">
      <button type="button" class="np-icon" data-queue aria-label="Queue" aria-expanded="false" aria-controls="np-queue-2"><svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6h11M4 12h11M4 18h7M19 6v10"/><circle cx="17" cy="16.5" r="2"/></svg></button>
      <button type="button" class="np-icon np-mute" data-mute aria-label="Mute" aria-pressed="false"><svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 5 6 9H3v6h3l5 4z"/><path class="i-wave" d="M15.5 8.5a5 5 0 0 1 0 7M18.5 5.5a9 9 0 0 1 0 13"/><path class="i-x" d="m16 9 6 6m0-6-6 6"/></svg></button>
      <input type="range" class="np-vol" data-vol min="0" max="1" step="0.05" value="0.7" aria-label="Volume" aria-valuetext="70%">
    </div>
  </div>
  <ol class="np-queue" id="np-queue-2" hidden>
    <li><button type="button" class="np-item" data-src="../audio/track-2.mp3" data-name="Paper Kites" data-artist="The Lumen Choir" aria-current="true">
      <span class="np-art" data-art style="--art:linear-gradient(to bottom right,transparent 50%,#fde047 50%) 37.5% 26.67%/20% 25% no-repeat,linear-gradient(to bottom left,transparent 50%,#f97316 50%) 62.5% 26.67%/20% 25% no-repeat,linear-gradient(to top right,transparent 50%,#fb923c 50%) 37.5% 69.23%/20% 35% no-repeat,linear-gradient(to top left,transparent 50%,#facc15 50%) 62.5% 69.23%/20% 35% no-repeat,linear-gradient(to bottom right,transparent calc(50% - 1px),rgba(255,255,255,.85) 0 calc(50% + 1px),transparent 0) 41.86% 100%/14% 20% no-repeat,linear-gradient(160deg,#0ea5e9,#14b8a6 55%,#84cc16)"></span><span><span class="np-name">Paper Kites</span><span class="np-by">The Lumen Choir</span></span><span class="np-time">0:24</span></button></li>
    <li><button type="button" class="np-item" data-src="../audio/track-4.mp3" data-name="Copper Sky" data-artist="Milo & the Tidelines">
      <span class="np-art" data-art style="--art:radial-gradient(circle at 50% 100%,#fff7ed 0 24%,#fdba74 24% 27%,transparent 27.5%),linear-gradient(180deg,#7c2d12 0 22%,#9a3412 22% 40%,#c2410c 40% 56%,#ea580c 56% 70%,#f59e0b 70% 84%,#fcd34d 84%)"></span><span><span class="np-name">Copper Sky</span><span class="np-by">Milo & the Tidelines</span></span><span class="np-time">0:29</span></button></li>
    <li><button type="button" class="np-item" data-src="../audio/track-1.mp3" data-name="Harbour Lights" data-artist="Juniper Vale">
      <span class="np-art" data-art style="--art:radial-gradient(circle at 68% 40%,#fde68a 0 11%,transparent 11.5%),repeating-linear-gradient(180deg,transparent 0 6%,rgba(253,230,138,.3) 6% 7.5%) bottom/100% 42% no-repeat,linear-gradient(180deg,#fb923c,#e11d48 58%,#312e81 58%,#1e1b4b)"></span><span><span class="np-name">Harbour Lights</span><span class="np-by">Juniper Vale</span></span><span class="np-time">0:24</span></button></li>
  </ol>
</section></div>
CSS
/* Dark streaming players: a phone-style now playing screen and a desktop player bar.
   The script sets --p and --v (0 to 1) on .np, toggles [data-playing] and moves aria-current along the queue. */
.np{--np-accent:#34d399;--np-bg:#16161f;--np-raise:#22222d;--np-ink:#f4f4f5;--np-muted:#a1a1aa;--np-track:#6b6b7b;--p:0;--v:1;
  color:var(--np-ink);background:var(--np-bg);border:1px solid #2a2a36;border-radius:24px}
.np button{flex:none;display:grid;place-items:center;border:0;background:none;color:inherit;cursor:pointer;font:inherit}
.np button:focus-visible{outline:2px solid var(--np-accent);outline-offset:2px}
.np svg{display:block;width:20px;height:20px}
.np .np-play{width:60px;height:60px;border-radius:50%;background:var(--np-ink);color:#0c0c12}
.np .np-play svg{width:26px;height:26px}
.np .np-play:hover{background:#fff}
.np .i-pause,.np[data-playing] .i-play{display:none}
.np[data-playing] .i-pause{display:block}
.np .np-icon{width:40px;height:40px;border-radius:50%;color:var(--np-muted)}
.np .np-icon:hover{color:var(--np-ink);background:var(--np-raise)}
.np .np-icon[aria-pressed="true"],.np .np-icon[aria-expanded="true"]{color:var(--np-accent)}
.np [data-like][aria-pressed="true"] svg{fill:currentColor}
.np .np-skip{width:44px;height:44px;border-radius:50%;color:var(--np-ink)}
.np .np-skip svg{width:24px;height:24px}
.np .np-skip:hover{background:var(--np-raise)}
.np-mute .i-x,.np-mute[aria-pressed="true"] .i-wave{display:none}
.np-mute[aria-pressed="true"] .i-x{display:block}
.np-title{font-weight:800;font-size:1.3rem;letter-spacing:-.015em}
.np-artist{font-size:.92rem;color:var(--np-muted);margin-top:3px}
.np-time{font-size:.75rem;color:var(--np-muted);font-variant-numeric:tabular-nums;min-width:2.3em}
/* range inputs: --f is the filled share of the track */
.np input[type="range"]{-webkit-appearance:none;appearance:none;display:block;flex:1;width:100%;min-width:0;height:14px;margin:0;background:none;cursor:pointer;--f:var(--p)}
.np input[type="range"].np-vol{--f:var(--v)}
.np input[type="range"]:focus-visible{outline:none}
.np input[type="range"]::-webkit-slider-runnable-track{height:4px;border-radius:999px;
  background:linear-gradient(to right,var(--np-ink) 0 calc(7px + (100% - 14px) * var(--f)),var(--np-track) 0)}
.np input[type="range"]::-moz-range-track{height:4px;border-radius:999px;
  background:linear-gradient(to right,var(--np-ink) 0 calc(7px + (100% - 14px) * var(--f)),var(--np-track) 0)}
.np input[type="range"]::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:14px;height:14px;margin-top:-5px;border:0;border-radius:50%;background:var(--np-ink)}
.np input[type="range"]::-moz-range-thumb{width:14px;height:14px;border:0;border-radius:50%;background:var(--np-ink)}
.np input[type="range"]:hover::-webkit-slider-runnable-track{background:linear-gradient(to right,var(--np-accent) 0 calc(7px + (100% - 14px) * var(--f)),var(--np-track) 0)}
.np input[type="range"]:hover::-moz-range-track{background:linear-gradient(to right,var(--np-accent) 0 calc(7px + (100% - 14px) * var(--f)),var(--np-track) 0)}
.np input[type="range"]:focus-visible::-webkit-slider-thumb{box-shadow:0 0 0 3px var(--np-bg),0 0 0 5px var(--np-accent)}
.np input[type="range"]:focus-visible::-moz-range-thumb{box-shadow:0 0 0 3px var(--np-bg),0 0 0 5px var(--np-accent)}
.np-bar{display:flex;align-items:center;gap:8px}
.np-bar .np-time:last-child{text-align:right}
.np-times{display:flex;justify-content:space-between;margin-top:4px}
/* queue */
.np-queue{list-style:none}
.np .np-item{width:100%;display:grid;grid-template-columns:40px 1fr auto;place-items:center stretch;gap:12px;padding:8px 10px;border-radius:12px;text-align:left}
.np .np-item:hover{background:var(--np-raise)}
.np .np-item:focus-visible{outline-offset:-2px}
.np-item .np-art{width:40px;height:40px;border-radius:8px}
.np-name{display:block;font-weight:600;font-size:.92rem}
.np-by{display:block;font-size:.78rem;color:var(--np-muted);margin-top:2px}
.np-item[aria-current="true"] .np-name{color:var(--np-accent)}
.np-art{background:var(--art)}
/* 1. now playing screen */
.np-screen{max-width:380px;padding:20px 22px 22px;display:flex;flex-direction:column;gap:18px;
  background:linear-gradient(180deg,#26203a 0,var(--np-bg) 320px)}
.np-from{text-align:center;font-size:.72rem;letter-spacing:.1em;text-transform:uppercase;color:var(--np-muted)}
.np-from b{display:block;margin-top:3px;font-size:.88rem;letter-spacing:0;text-transform:none;color:var(--np-ink)}
.np-cover{aspect-ratio:1;border-radius:18px;box-shadow:0 20px 40px rgba(0,0,0,.5)}
.np-head{display:flex;align-items:center;gap:16px}
.np-head > div{flex:1;min-width:0}
.np-ctrls{display:flex;align-items:center;justify-content:space-between}
.np-volume{display:flex;align-items:center;gap:8px}
.np-upnext{font-size:.72rem;letter-spacing:.1em;text-transform:uppercase;color:var(--np-muted);margin:4px 0 -8px}
/* 2. desktop player bar */
.np-desk{padding:12px 18px}
.np-row{display:grid;grid-template-columns:minmax(0,1.3fr) minmax(0,1.5fr) minmax(0,1fr);align-items:center;gap:20px}
.np-now{display:flex;align-items:center;gap:16px;min-width:0}
.np-now .np-art{flex:none;width:52px;height:52px;border-radius:10px}
.np-now > div{min-width:0}
.np-now .np-title{font-size:.95rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.np-now .np-artist{font-size:.8rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.np-mid{display:flex;flex-direction:column;gap:4px}
.np-mid .np-ctrls{justify-content:center;gap:8px}
.np-desk .np-play{width:40px;height:40px}
.np-desk .np-play svg{width:20px;height:20px}
.np-desk .np-skip{width:36px;height:36px}
.np-desk .np-skip svg{width:20px;height:20px}
.np-side{display:flex;align-items:center;justify-content:flex-end;gap:4px}
.np-side .np-vol{flex:none;width:96px}
.np-desk .np-queue{border-top:1px solid #2a2a36;margin-top:12px;padding-top:8px;display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:4px}
.np-desk .np-queue[hidden]{display:none}
@media (max-width:720px){
  .np-row{grid-template-columns:1fr auto}
  .np-mid{grid-column:1 / -1;grid-row:2}
  .np-side .np-vol,.np-side .np-mute{display:none}
}
JavaScript
// Dark streaming players. Each [data-np] holds one <audio> and a queue of track buttons (data-src, data-name,
// data-artist). Also wires like, shuffle, repeat-one, mute, volume and the queue toggle when they are present.
(() => {
  const fmt = s => { s = Math.floor(s || 0); return Math.floor(s / 60) + ':' + String(s % 60).padStart(2, '0'); };
  const toggle = b => { const on = b.getAttribute('aria-pressed') !== 'true'; b.setAttribute('aria-pressed', String(on)); return on; };
  document.querySelectorAll('[data-np]').forEach(root => {
    const $ = s => root.querySelector(s);
    const audio = $('audio'), play = $('[data-play]'), seek = $('[data-seek]'), cur = $('[data-cur]'), dur = $('[data-dur]');
    const vol = $('[data-vol]'), mute = $('[data-mute]'), shuffle = $('[data-shuffle]'), repeat = $('[data-repeat]');
    const cover = $('[data-cover]'), items = [...root.querySelectorAll('[data-src]')];
    let i = Math.max(0, items.findIndex(b => b.getAttribute('aria-current') === 'true')), raf = 0;
    const label = () => play.setAttribute('aria-label', (audio.paused ? 'Play ' : 'Pause ') + items[i].dataset.name);
    const length = () => audio.duration || +seek.max;
    const sync = () => {
      const t = audio.currentTime, d = length(), text = fmt(t) + ' of ' + fmt(d);
      seek.value = t;
      root.style.setProperty('--p', d ? t / d : 0);
      cur.textContent = fmt(t);
      if (seek.getAttribute('aria-valuetext') !== text) seek.setAttribute('aria-valuetext', text);
    };
    const tick = () => { sync(); raf = requestAnimationFrame(tick); };
    const meta = () => { seek.max = audio.duration; dur.textContent = fmt(audio.duration); sync(); };
    const sound = () => {
      const v = audio.muted ? 0 : audio.volume;
      root.style.setProperty('--v', v);
      if (vol) { vol.value = v; vol.setAttribute('aria-valuetext', Math.round(v * 100) + '%'); }
      if (mute) mute.setAttribute('aria-pressed', String(v === 0));
    };
    const start = () => {
      document.querySelectorAll('audio').forEach(a => { if (a !== audio) a.pause(); });
      audio.play().catch(() => {});
    };
    // Switch to track n: mark it current, swap the source, title, artist and cover art.
    const load = n => {
      i = (n + items.length) % items.length;
      const item = items[i], art = item.querySelector('[data-art]');
      items.forEach(b => b.removeAttribute('aria-current'));
      item.setAttribute('aria-current', 'true');
      audio.src = item.dataset.src;
      root.querySelectorAll('[data-show]').forEach(el => { el.textContent = item.dataset[el.dataset.show]; });
      root.querySelectorAll('[data-like]').forEach(b => { b.setAttribute('aria-pressed', 'false'); b.setAttribute('aria-label', 'Like ' + item.dataset.name); });
      if (cover && art) {
        const cs = getComputedStyle(art);
        ['backgroundImage', 'backgroundPosition', 'backgroundSize', 'backgroundRepeat'].forEach(p => { cover.style[p] = cs[p]; });
      }
      label();
    };
    const next = () => {
      const on = shuffle && shuffle.getAttribute('aria-pressed') === 'true';
      load(on ? (i + 1 + Math.floor(Math.random() * (items.length - 1))) % items.length : i + 1);
      start();
    };
    play.addEventListener('click', () => (audio.paused ? start() : audio.pause()));
    $('[data-prev]').addEventListener('click', () => {
      if (audio.currentTime > 3) audio.currentTime = 0; else load(i - 1);
      start();
    });
    $('[data-next]').addEventListener('click', next);
    items.forEach((item, n) => item.addEventListener('click', () => {
      if (n === i) return play.click();
      load(n); start();
    }));
    root.querySelectorAll('[data-like]').forEach(b => b.addEventListener('click', () => toggle(b)));
    if (shuffle) shuffle.addEventListener('click', () => toggle(shuffle));
    if (repeat) repeat.addEventListener('click', () => { audio.loop = toggle(repeat); });
    root.querySelectorAll('[data-queue]').forEach(b => b.addEventListener('click', () => {
      const open = b.getAttribute('aria-expanded') !== 'true';
      b.setAttribute('aria-expanded', String(open));
      document.getElementById(b.getAttribute('aria-controls')).hidden = !open;
    }));
    audio.addEventListener('play', () => { root.setAttribute('data-playing', ''); label(); tick(); });
    audio.addEventListener('pause', () => { root.removeAttribute('data-playing'); label(); cancelAnimationFrame(raf); sync(); });
    audio.addEventListener('ended', () => { if (i < items.length - 1 || (shuffle && shuffle.getAttribute('aria-pressed') === 'true')) next(); });
    audio.addEventListener('loadedmetadata', meta);
    audio.addEventListener('volumechange', sound);
    if (audio.readyState > 0) meta();
    seek.addEventListener('input', () => { audio.currentTime = seek.value; sync(); });
    // Arrow keys jump 5 seconds; Home and End work natively.
    seek.addEventListener('keydown', e => {
      const step = { ArrowLeft: -5, ArrowDown: -5, ArrowRight: 5, ArrowUp: 5 }[e.key];
      if (!step) return;
      e.preventDefault();
      audio.currentTime = Math.min(Math.max(audio.currentTime + step, 0), length());
      sync();
    });
    if (vol) {
      audio.volume = vol.value;
      vol.addEventListener('input', () => { audio.volume = vol.value; audio.muted = false; });
    }
    if (mute) mute.addEventListener('click', () => {
      if (audio.muted || audio.volume === 0) { audio.muted = false; if (audio.volume === 0) audio.volume = 0.6; }
      else audio.muted = true;
    });
    sound();
  });
})();

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<div><span class="lbl">Now playing screen</span>
<section class="card np np-screen" data-bs-theme="dark" data-np aria-label="Now playing, Late Night Drive">
  <div class="card-body">
  <audio src="../audio/track-3.mp3" preload="metadata"></audio>
  <p class="np-from mb-0">Playing from playlist <b>Late Night Drive</b></p>
  <div class="np-cover np-art" data-cover style="--art:radial-gradient(circle at 68% 30%,#fef3c7 0 9%,rgba(254,243,199,.16) 9.5% 15%,transparent 15.5%),radial-gradient(circle at 20% 25%,#fff 0 1.2%,transparent 2%),radial-gradient(circle at 38% 55%,#fff 0 .9%,transparent 1.6%),radial-gradient(circle at 86% 64%,#fff 0 1%,transparent 1.7%),radial-gradient(circle at 12% 70%,#c4b5fd 0 1%,transparent 1.7%),linear-gradient(180deg,#0f172a,#312e81 70%,#7c3aed)" aria-hidden="true"></div>
  <div class="d-flex align-items-center gap-3">
    <div class="flex-grow-1" style="min-width:0"><p class="np-title mb-0" data-show="name">Night Bus to Nowhere</p><p class="np-artist mb-0" data-show="artist">Orla Finch</p></div>
    <button type="button" class="btn np-icon rounded-circle" data-like aria-label="Like Night Bus to Nowhere" aria-pressed="false"><svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20s-7.5-4.6-7.5-10.2A4.2 4.2 0 0 1 12 7.3a4.2 4.2 0 0 1 7.5 2.5C19.5 15.4 12 20 12 20z"/></svg></button>
  </div>
  <div>
    <input type="range" class="form-range" data-seek min="0" max="30" step="any" value="0" aria-label="Seek" aria-valuetext="0:00 of 0:30">
    <div class="d-flex justify-content-between mt-1"><span class="np-time" data-cur>0:00</span><span class="np-time text-end" data-dur>0:30</span></div>
  </div>
  <div class="d-flex align-items-center justify-content-between">
    <button type="button" class="btn np-icon rounded-circle" data-shuffle aria-label="Shuffle" aria-pressed="false"><svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 4h4v4M4 20 20 4M20 16v4h-4M15 15l5 5M4 4l5 5"/></svg></button>
    <button type="button" class="btn np-skip rounded-circle" data-prev aria-label="Previous track"><svg viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M5 5h2.2v14H5zM19.5 6.2v11.6a.8.8 0 0 1-1.24.67L9.6 12.67a.8.8 0 0 1 0-1.34l8.66-5.8a.8.8 0 0 1 1.24.67z"/></svg></button>
    <button type="button" class="btn np-play rounded-circle" data-play aria-label="Play Night Bus to Nowhere"><svg class="i-play" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M8 5.5v13a1 1 0 0 0 1.5.86l10.5-6.5a1 1 0 0 0 0-1.72L9.5 4.64A1 1 0 0 0 8 5.5z"/></svg><svg class="i-pause" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><rect x="6.5" y="5" width="4" height="14" rx="1.2"/><rect x="13.5" y="5" width="4" height="14" rx="1.2"/></svg></button>
    <button type="button" class="btn np-skip rounded-circle" data-next aria-label="Next track"><svg viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M16.8 5H19v14h-2.2zM4.5 6.2v11.6a.8.8 0 0 0 1.24.67l8.66-5.8a.8.8 0 0 0 0-1.34L5.74 5.53a.8.8 0 0 0-1.24.67z"/></svg></button>
    <button type="button" class="btn np-icon rounded-circle" data-repeat aria-label="Repeat this track" aria-pressed="false"><svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m17 2 4 4-4 4"/><path d="M3 11V9a3 3 0 0 1 3-3h15"/><path d="m7 22-4-4 4-4"/><path d="M21 13v2a3 3 0 0 1-3 3H3"/></svg></button>
  </div>
  <div class="d-flex align-items-center gap-2">
    <button type="button" class="btn np-icon np-mute rounded-circle" data-mute aria-label="Mute" aria-pressed="false"><svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 5 6 9H3v6h3l5 4z"/><path class="i-wave" d="M15.5 8.5a5 5 0 0 1 0 7M18.5 5.5a9 9 0 0 1 0 13"/><path class="i-x" d="m16 9 6 6m0-6-6 6"/></svg></button>
    <input type="range" class="form-range np-vol" data-vol min="0" max="1" step="0.05" value="0.7" aria-label="Volume" aria-valuetext="70%">
  </div>
  <p class="np-upnext">Queue</p>
  <ol class="list-group">
    <li class="list-group-item"><button type="button" class="list-group-item-action np-item" data-src="../audio/track-3.mp3" data-name="Night Bus to Nowhere" data-artist="Orla Finch" aria-current="true">
      <span class="np-art" data-art style="--art:radial-gradient(circle at 68% 30%,#fef3c7 0 9%,rgba(254,243,199,.16) 9.5% 15%,transparent 15.5%),radial-gradient(circle at 20% 25%,#fff 0 1.2%,transparent 2%),radial-gradient(circle at 38% 55%,#fff 0 .9%,transparent 1.6%),radial-gradient(circle at 86% 64%,#fff 0 1%,transparent 1.7%),radial-gradient(circle at 12% 70%,#c4b5fd 0 1%,transparent 1.7%),linear-gradient(180deg,#0f172a,#312e81 70%,#7c3aed)"></span><span><span class="np-name">Night Bus to Nowhere</span><span class="np-by">Orla Finch</span></span><span class="np-time">0:30</span></button></li>
    <li class="list-group-item"><button type="button" class="list-group-item-action np-item" data-src="../audio/track-1.mp3" data-name="Harbour Lights" data-artist="Juniper Vale">
      <span class="np-art" data-art style="--art:radial-gradient(circle at 68% 40%,#fde68a 0 11%,transparent 11.5%),repeating-linear-gradient(180deg,transparent 0 6%,rgba(253,230,138,.3) 6% 7.5%) bottom/100% 42% no-repeat,linear-gradient(180deg,#fb923c,#e11d48 58%,#312e81 58%,#1e1b4b)"></span><span><span class="np-name">Harbour Lights</span><span class="np-by">Juniper Vale</span></span><span class="np-time">0:24</span></button></li>
    <li class="list-group-item"><button type="button" class="list-group-item-action np-item" data-src="../audio/track-4.mp3" data-name="Copper Sky" data-artist="Milo & the Tidelines">
      <span class="np-art" data-art style="--art:radial-gradient(circle at 50% 100%,#fff7ed 0 24%,#fdba74 24% 27%,transparent 27.5%),linear-gradient(180deg,#7c2d12 0 22%,#9a3412 22% 40%,#c2410c 40% 56%,#ea580c 56% 70%,#f59e0b 70% 84%,#fcd34d 84%)"></span><span><span class="np-name">Copper Sky</span><span class="np-by">Milo & the Tidelines</span></span><span class="np-time">0:29</span></button></li>
  </ol>
  </div>
</section></div>

<div><span class="lbl">Desktop player bar</span>
<section class="card np np-desk" data-bs-theme="dark" data-np aria-label="Player">
  <div class="card-body">
  <audio src="../audio/track-2.mp3" preload="metadata"></audio>
  <div class="np-row">
    <div class="np-now d-flex align-items-center gap-3" style="min-width:0">
      <div class="np-art" data-cover style="--art:linear-gradient(to bottom right,transparent 50%,#fde047 50%) 37.5% 26.67%/20% 25% no-repeat,linear-gradient(to bottom left,transparent 50%,#f97316 50%) 62.5% 26.67%/20% 25% no-repeat,linear-gradient(to top right,transparent 50%,#fb923c 50%) 37.5% 69.23%/20% 35% no-repeat,linear-gradient(to top left,transparent 50%,#facc15 50%) 62.5% 69.23%/20% 35% no-repeat,linear-gradient(to bottom right,transparent calc(50% - 1px),rgba(255,255,255,.85) 0 calc(50% + 1px),transparent 0) 41.86% 100%/14% 20% no-repeat,linear-gradient(160deg,#0ea5e9,#14b8a6 55%,#84cc16)" aria-hidden="true"></div>
      <div class="text-truncate"><p class="np-title mb-0 text-truncate" data-show="name">Paper Kites</p><p class="np-artist mb-0 text-truncate" data-show="artist">The Lumen Choir</p></div>
      <button type="button" class="btn np-icon rounded-circle" data-like aria-label="Like Paper Kites" aria-pressed="false"><svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20s-7.5-4.6-7.5-10.2A4.2 4.2 0 0 1 12 7.3a4.2 4.2 0 0 1 7.5 2.5C19.5 15.4 12 20 12 20z"/></svg></button>
    </div>
    <div class="np-mid d-flex flex-column gap-1">
      <div class="d-flex align-items-center justify-content-center gap-2">
        <button type="button" class="btn np-skip rounded-circle" data-prev aria-label="Previous track"><svg viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M5 5h2.2v14H5zM19.5 6.2v11.6a.8.8 0 0 1-1.24.67L9.6 12.67a.8.8 0 0 1 0-1.34l8.66-5.8a.8.8 0 0 1 1.24.67z"/></svg></button>
        <button type="button" class="btn np-play rounded-circle" data-play aria-label="Play Paper Kites"><svg class="i-play" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M8 5.5v13a1 1 0 0 0 1.5.86l10.5-6.5a1 1 0 0 0 0-1.72L9.5 4.64A1 1 0 0 0 8 5.5z"/></svg><svg class="i-pause" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><rect x="6.5" y="5" width="4" height="14" rx="1.2"/><rect x="13.5" y="5" width="4" height="14" rx="1.2"/></svg></button>
        <button type="button" class="btn np-skip rounded-circle" data-next aria-label="Next track"><svg viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M16.8 5H19v14h-2.2zM4.5 6.2v11.6a.8.8 0 0 0 1.24.67l8.66-5.8a.8.8 0 0 0 0-1.34L5.74 5.53a.8.8 0 0 0-1.24.67z"/></svg></button>
      </div>
      <div class="d-flex align-items-center gap-2">
        <span class="np-time" data-cur>0:00</span>
        <input type="range" class="form-range" data-seek min="0" max="24" step="any" value="0" aria-label="Seek" aria-valuetext="0:00 of 0:24">
        <span class="np-time text-end" data-dur>0:24</span>
      </div>
    </div>
    <div class="np-side d-flex align-items-center justify-content-end gap-1">
      <button type="button" class="btn np-icon rounded-circle" data-queue aria-label="Queue" aria-expanded="false" aria-controls="np-queue-2"><svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6h11M4 12h11M4 18h7M19 6v10"/><circle cx="17" cy="16.5" r="2"/></svg></button>
      <button type="button" class="btn np-icon np-mute rounded-circle" data-mute aria-label="Mute" aria-pressed="false"><svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 5 6 9H3v6h3l5 4z"/><path class="i-wave" d="M15.5 8.5a5 5 0 0 1 0 7M18.5 5.5a9 9 0 0 1 0 13"/><path class="i-x" d="m16 9 6 6m0-6-6 6"/></svg></button>
      <input type="range" class="form-range np-vol" data-vol min="0" max="1" step="0.05" value="0.7" aria-label="Volume" aria-valuetext="70%">
    </div>
  </div>
  <ol class="list-group" id="np-queue-2" hidden>
    <li class="list-group-item"><button type="button" class="list-group-item-action np-item" data-src="../audio/track-2.mp3" data-name="Paper Kites" data-artist="The Lumen Choir" aria-current="true">
      <span class="np-art" data-art style="--art:linear-gradient(to bottom right,transparent 50%,#fde047 50%) 37.5% 26.67%/20% 25% no-repeat,linear-gradient(to bottom left,transparent 50%,#f97316 50%) 62.5% 26.67%/20% 25% no-repeat,linear-gradient(to top right,transparent 50%,#fb923c 50%) 37.5% 69.23%/20% 35% no-repeat,linear-gradient(to top left,transparent 50%,#facc15 50%) 62.5% 69.23%/20% 35% no-repeat,linear-gradient(to bottom right,transparent calc(50% - 1px),rgba(255,255,255,.85) 0 calc(50% + 1px),transparent 0) 41.86% 100%/14% 20% no-repeat,linear-gradient(160deg,#0ea5e9,#14b8a6 55%,#84cc16)"></span><span><span class="np-name">Paper Kites</span><span class="np-by">The Lumen Choir</span></span><span class="np-time">0:24</span></button></li>
    <li class="list-group-item"><button type="button" class="list-group-item-action np-item" data-src="../audio/track-4.mp3" data-name="Copper Sky" data-artist="Milo & the Tidelines">
      <span class="np-art" data-art style="--art:radial-gradient(circle at 50% 100%,#fff7ed 0 24%,#fdba74 24% 27%,transparent 27.5%),linear-gradient(180deg,#7c2d12 0 22%,#9a3412 22% 40%,#c2410c 40% 56%,#ea580c 56% 70%,#f59e0b 70% 84%,#fcd34d 84%)"></span><span><span class="np-name">Copper Sky</span><span class="np-by">Milo & the Tidelines</span></span><span class="np-time">0:29</span></button></li>
    <li class="list-group-item"><button type="button" class="list-group-item-action np-item" data-src="../audio/track-1.mp3" data-name="Harbour Lights" data-artist="Juniper Vale">
      <span class="np-art" data-art style="--art:radial-gradient(circle at 68% 40%,#fde68a 0 11%,transparent 11.5%),repeating-linear-gradient(180deg,transparent 0 6%,rgba(253,230,138,.3) 6% 7.5%) bottom/100% 42% no-repeat,linear-gradient(180deg,#fb923c,#e11d48 58%,#312e81 58%,#1e1b4b)"></span><span><span class="np-name">Harbour Lights</span><span class="np-by">Juniper Vale</span></span><span class="np-time">0:24</span></button></li>
  </ol>
  </div>
</section></div>
CSS
/* Dark streaming players on Bootstrap (data-bs-theme="dark"): .card shells, .btn controls, .form-range and a .list-group queue.
   The script sets --p and --v (0 to 1) on .np, toggles [data-playing] and moves aria-current along the queue. */
.np{--np-accent:#34d399;--np-bg:#16161f;--np-raise:#22222d;--np-ink:#f4f4f5;--np-muted:#a1a1aa;--np-track:#6b6b7b;--p:0;--v:1;
  --bs-card-bg:var(--np-bg);--bs-card-border-color:#2a2a36;--bs-card-border-radius:24px;--bs-card-color:var(--np-ink);color:var(--np-ink)}
.np .btn{--bs-btn-padding-x:0;--bs-btn-padding-y:0;--bs-btn-border-width:0;flex:none;display:grid;place-items:center}
.np .btn:focus-visible{outline:2px solid var(--np-accent);outline-offset:2px;box-shadow:none}
.np svg{display:block;width:20px;height:20px}
.np .np-play{--bs-btn-bg:var(--np-ink);--bs-btn-color:#0c0c12;--bs-btn-hover-bg:#fff;--bs-btn-hover-color:#0c0c12;
  --bs-btn-active-bg:#d4d4d8;--bs-btn-active-color:#0c0c12;width:60px;height:60px}
.np .np-play svg{width:26px;height:26px}
.np .i-pause,.np[data-playing] .i-play{display:none}
.np[data-playing] .i-pause{display:block}
.np .np-icon{--bs-btn-color:var(--np-muted);--bs-btn-hover-color:var(--np-ink);--bs-btn-hover-bg:var(--np-raise);
  --bs-btn-active-color:var(--np-accent);--bs-btn-active-bg:var(--np-raise);width:40px;height:40px}
.np .np-icon[aria-pressed="true"],.np .np-icon[aria-expanded="true"]{--bs-btn-color:var(--np-accent);--bs-btn-hover-color:var(--np-accent)}
.np [data-like][aria-pressed="true"] svg{fill:currentColor}
.np .np-skip{--bs-btn-color:var(--np-ink);--bs-btn-hover-color:var(--np-ink);--bs-btn-hover-bg:var(--np-raise);
  --bs-btn-active-color:var(--np-ink);--bs-btn-active-bg:var(--np-raise);width:44px;height:44px}
.np .np-skip svg{width:24px;height:24px}
.np-mute .i-x,.np-mute[aria-pressed="true"] .i-wave{display:none}
.np-mute[aria-pressed="true"] .i-x{display:block}
.np-title{font-weight:800;font-size:1.3rem;letter-spacing:-.015em}
.np-artist{font-size:.92rem;color:var(--np-muted);margin-top:3px}
.np-time{font-size:.75rem;color:var(--np-muted);font-variant-numeric:tabular-nums;min-width:2.3em}
/* .form-range: --f is the filled share of the track */
.np .form-range{flex:1;min-width:0;height:14px;--f:var(--p)}
.np .form-range.np-vol{--f:var(--v)}
.np .form-range::-webkit-slider-runnable-track{height:4px;border-radius:999px;
  background:linear-gradient(to right,var(--np-ink) 0 calc(7px + (100% - 14px) * var(--f)),var(--np-track) 0)}
.np .form-range::-moz-range-track{height:4px;border-radius:999px;
  background:linear-gradient(to right,var(--np-ink) 0 calc(7px + (100% - 14px) * var(--f)),var(--np-track) 0)}
.np .form-range::-webkit-slider-thumb{width:14px;height:14px;margin-top:-5px;background:var(--np-ink)}
.np .form-range::-moz-range-thumb{width:14px;height:14px;background:var(--np-ink)}
.np .form-range:hover::-webkit-slider-runnable-track{background:linear-gradient(to right,var(--np-accent) 0 calc(7px + (100% - 14px) * var(--f)),var(--np-track) 0)}
.np .form-range:hover::-moz-range-track{background:linear-gradient(to right,var(--np-accent) 0 calc(7px + (100% - 14px) * var(--f)),var(--np-track) 0)}
.np .form-range:focus::-webkit-slider-thumb{box-shadow:none}
.np .form-range:focus::-moz-range-thumb{box-shadow:none}
.np .form-range:focus-visible::-webkit-slider-thumb{box-shadow:0 0 0 3px var(--np-bg),0 0 0 5px var(--np-accent)}
.np .form-range:focus-visible::-moz-range-thumb{box-shadow:0 0 0 3px var(--np-bg),0 0 0 5px var(--np-accent)}
/* queue: .list-group of .list-group-item-action buttons */
.np .list-group{--bs-list-group-bg:transparent;--bs-list-group-border-width:0;--bs-list-group-color:var(--np-ink);--bs-list-group-action-color:var(--np-ink);
  --bs-list-group-action-hover-color:var(--np-ink);--bs-list-group-action-hover-bg:var(--np-raise);--bs-list-group-action-active-bg:var(--np-raise);
  --bs-list-group-action-active-color:var(--np-ink);--bs-list-group-item-padding-x:0;--bs-list-group-item-padding-y:0}
.np .np-item{display:grid;grid-template-columns:40px 1fr auto;align-items:center;gap:12px;padding:8px 10px;border:0;border-radius:12px;background:none}
.np .np-item:focus-visible{outline:2px solid var(--np-accent);outline-offset:-2px}
.np-item .np-art{width:40px;height:40px;border-radius:8px}
.np-name{display:block;font-weight:600;font-size:.92rem}
.np-by{display:block;font-size:.78rem;color:var(--np-muted);margin-top:2px}
.np-item[aria-current="true"] .np-name{color:var(--np-accent)}
.np-art{background:var(--art)}
/* 1. now playing screen */
.np-screen{max-width:380px;background-image:linear-gradient(180deg,#26203a 0,var(--np-bg) 320px)}
.np-screen .card-body{padding:20px 22px 22px;display:flex;flex-direction:column;gap:18px}
.np-from{text-align:center;font-size:.72rem;letter-spacing:.1em;text-transform:uppercase;color:var(--np-muted)}
.np-from b{display:block;margin-top:3px;font-size:.88rem;letter-spacing:0;text-transform:none;color:var(--np-ink)}
.np-cover{aspect-ratio:1;border-radius:18px;box-shadow:0 20px 40px rgba(0,0,0,.5)}
.np-upnext{font-size:.72rem;letter-spacing:.1em;text-transform:uppercase;color:var(--np-muted);margin:4px 0 -8px}
/* 2. desktop player bar */
.np-desk .card-body{padding:12px 18px}
.np-row{display:grid;grid-template-columns:minmax(0,1.3fr) minmax(0,1.5fr) minmax(0,1fr);align-items:center;gap:20px}
.np-now .np-art{flex:none;width:52px;height:52px;border-radius:10px}
.np-now .np-title{font-size:.95rem}
.np-now .np-artist{font-size:.8rem}
.np-desk .np-play{width:40px;height:40px}
.np-desk .np-play svg{width:20px;height:20px}
.np-desk .np-skip{width:36px;height:36px}
.np-desk .np-skip svg{width:20px;height:20px}
.np-side .np-vol{flex:none;width:96px}
.np-desk .list-group{border-top:1px solid #2a2a36;margin-top:12px;padding-top:8px;display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:4px}
.np-desk .list-group[hidden]{display:none}
@media (max-width:720px){
  .np-row{grid-template-columns:1fr auto}
  .np-mid{grid-column:1 / -1;grid-row:2}
  .np-side .np-vol,.np-side .np-mute{display:none}
}
JavaScript
// Dark streaming players. Each [data-np] holds one <audio> and a queue of track buttons (data-src, data-name,
// data-artist). Also wires like, shuffle, repeat-one, mute, volume and the queue toggle when they are present.
(() => {
  const fmt = s => { s = Math.floor(s || 0); return Math.floor(s / 60) + ':' + String(s % 60).padStart(2, '0'); };
  const toggle = b => { const on = b.getAttribute('aria-pressed') !== 'true'; b.setAttribute('aria-pressed', String(on)); return on; };
  document.querySelectorAll('[data-np]').forEach(root => {
    const $ = s => root.querySelector(s);
    const audio = $('audio'), play = $('[data-play]'), seek = $('[data-seek]'), cur = $('[data-cur]'), dur = $('[data-dur]');
    const vol = $('[data-vol]'), mute = $('[data-mute]'), shuffle = $('[data-shuffle]'), repeat = $('[data-repeat]');
    const cover = $('[data-cover]'), items = [...root.querySelectorAll('[data-src]')];
    let i = Math.max(0, items.findIndex(b => b.getAttribute('aria-current') === 'true')), raf = 0;
    const label = () => play.setAttribute('aria-label', (audio.paused ? 'Play ' : 'Pause ') + items[i].dataset.name);
    const length = () => audio.duration || +seek.max;
    const sync = () => {
      const t = audio.currentTime, d = length(), text = fmt(t) + ' of ' + fmt(d);
      seek.value = t;
      root.style.setProperty('--p', d ? t / d : 0);
      cur.textContent = fmt(t);
      if (seek.getAttribute('aria-valuetext') !== text) seek.setAttribute('aria-valuetext', text);
    };
    const tick = () => { sync(); raf = requestAnimationFrame(tick); };
    const meta = () => { seek.max = audio.duration; dur.textContent = fmt(audio.duration); sync(); };
    const sound = () => {
      const v = audio.muted ? 0 : audio.volume;
      root.style.setProperty('--v', v);
      if (vol) { vol.value = v; vol.setAttribute('aria-valuetext', Math.round(v * 100) + '%'); }
      if (mute) mute.setAttribute('aria-pressed', String(v === 0));
    };
    const start = () => {
      document.querySelectorAll('audio').forEach(a => { if (a !== audio) a.pause(); });
      audio.play().catch(() => {});
    };
    // Switch to track n: mark it current, swap the source, title, artist and cover art.
    const load = n => {
      i = (n + items.length) % items.length;
      const item = items[i], art = item.querySelector('[data-art]');
      items.forEach(b => b.removeAttribute('aria-current'));
      item.setAttribute('aria-current', 'true');
      audio.src = item.dataset.src;
      root.querySelectorAll('[data-show]').forEach(el => { el.textContent = item.dataset[el.dataset.show]; });
      root.querySelectorAll('[data-like]').forEach(b => { b.setAttribute('aria-pressed', 'false'); b.setAttribute('aria-label', 'Like ' + item.dataset.name); });
      if (cover && art) {
        const cs = getComputedStyle(art);
        ['backgroundImage', 'backgroundPosition', 'backgroundSize', 'backgroundRepeat'].forEach(p => { cover.style[p] = cs[p]; });
      }
      label();
    };
    const next = () => {
      const on = shuffle && shuffle.getAttribute('aria-pressed') === 'true';
      load(on ? (i + 1 + Math.floor(Math.random() * (items.length - 1))) % items.length : i + 1);
      start();
    };
    play.addEventListener('click', () => (audio.paused ? start() : audio.pause()));
    $('[data-prev]').addEventListener('click', () => {
      if (audio.currentTime > 3) audio.currentTime = 0; else load(i - 1);
      start();
    });
    $('[data-next]').addEventListener('click', next);
    items.forEach((item, n) => item.addEventListener('click', () => {
      if (n === i) return play.click();
      load(n); start();
    }));
    root.querySelectorAll('[data-like]').forEach(b => b.addEventListener('click', () => toggle(b)));
    if (shuffle) shuffle.addEventListener('click', () => toggle(shuffle));
    if (repeat) repeat.addEventListener('click', () => { audio.loop = toggle(repeat); });
    root.querySelectorAll('[data-queue]').forEach(b => b.addEventListener('click', () => {
      const open = b.getAttribute('aria-expanded') !== 'true';
      b.setAttribute('aria-expanded', String(open));
      document.getElementById(b.getAttribute('aria-controls')).hidden = !open;
    }));
    audio.addEventListener('play', () => { root.setAttribute('data-playing', ''); label(); tick(); });
    audio.addEventListener('pause', () => { root.removeAttribute('data-playing'); label(); cancelAnimationFrame(raf); sync(); });
    audio.addEventListener('ended', () => { if (i < items.length - 1 || (shuffle && shuffle.getAttribute('aria-pressed') === 'true')) next(); });
    audio.addEventListener('loadedmetadata', meta);
    audio.addEventListener('volumechange', sound);
    if (audio.readyState > 0) meta();
    seek.addEventListener('input', () => { audio.currentTime = seek.value; sync(); });
    // Arrow keys jump 5 seconds; Home and End work natively.
    seek.addEventListener('keydown', e => {
      const step = { ArrowLeft: -5, ArrowDown: -5, ArrowRight: 5, ArrowUp: 5 }[e.key];
      if (!step) return;
      e.preventDefault();
      audio.currentTime = Math.min(Math.max(audio.currentTime + step, 0), length());
      sync();
    });
    if (vol) {
      audio.volume = vol.value;
      vol.addEventListener('input', () => { audio.volume = vol.value; audio.muted = false; });
    }
    if (mute) mute.addEventListener('click', () => {
      if (audio.muted || audio.volume === 0) { audio.muted = false; if (audio.volume === 0) audio.volume = 0.6; }
      else audio.muted = true;
    });
    sound();
  });
})();

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<div><span class="mb-3.5 block text-[.72rem] tracking-[.12em] text-[#9ca3af] uppercase">Now playing screen</span>
<section class="group rounded-3xl border border-[#2a2a36] bg-np-bg text-np-ink [--p:0] [--v:1] flex max-w-[380px] flex-col gap-[18px] bg-[linear-gradient(180deg,#26203a_0,var(--color-np-bg)_320px)] px-[22px] pt-5 pb-[22px]" data-np aria-label="Now playing, Late Night Drive">
  <audio src="../audio/track-3.mp3" preload="metadata"></audio>
  <p class="text-center text-[.72rem] tracking-[.1em] text-np-muted uppercase">Playing from playlist <b class="mt-[3px] block text-[.88rem] tracking-normal text-np-ink normal-case">Late Night Drive</b></p>
  <div class="aspect-square rounded-[18px] shadow-[0_20px_40px_rgba(0,0,0,.5)] [background:radial-gradient(circle_at_68%_30%,#fef3c7_0_9%,rgba(254,243,199,.16)_9.5%_15%,transparent_15.5%),radial-gradient(circle_at_20%_25%,#fff_0_1.2%,transparent_2%),radial-gradient(circle_at_38%_55%,#fff_0_.9%,transparent_1.6%),radial-gradient(circle_at_86%_64%,#fff_0_1%,transparent_1.7%),radial-gradient(circle_at_12%_70%,#c4b5fd_0_1%,transparent_1.7%),linear-gradient(180deg,#0f172a,#312e81_70%,#7c3aed)]" data-cover aria-hidden="true"></div>
  <div class="flex items-center gap-4">
    <div class="min-w-0 flex-1"><p class="text-[1.3rem] font-extrabold tracking-[-.015em]" data-show="name">Night Bus to Nowhere</p><p class="mt-[3px] text-[.92rem] text-np-muted" data-show="artist">Orla Finch</p></div>
    <button type="button" class="grid size-10 flex-none cursor-pointer place-items-center rounded-full text-np-muted hover:bg-np-raise hover:text-np-ink aria-pressed:text-np-accent focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-np-accent aria-pressed:[&_svg]:fill-current" data-like aria-label="Like Night Bus to Nowhere" aria-pressed="false"><svg class="block size-5" viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20s-7.5-4.6-7.5-10.2A4.2 4.2 0 0 1 12 7.3a4.2 4.2 0 0 1 7.5 2.5C19.5 15.4 12 20 12 20z"/></svg></button>
  </div>
  <div>
    <input type="range" class="block h-[14px] min-w-0 cursor-pointer appearance-none bg-transparent [--f:var(--p)] focus-visible:outline-none [&::-webkit-slider-runnable-track]:h-[4px] [&::-webkit-slider-runnable-track]:rounded-full [&::-webkit-slider-runnable-track]:bg-[linear-gradient(to_right,var(--color-np-ink)_0_calc(7px_+_(100%_-_14px)_*_var(--f)),var(--color-np-track)_0)] [&::-moz-range-track]:h-[4px] [&::-moz-range-track]:rounded-full [&::-moz-range-track]:bg-[linear-gradient(to_right,var(--color-np-ink)_0_calc(7px_+_(100%_-_14px)_*_var(--f)),var(--color-np-track)_0)] [&::-webkit-slider-thumb]:-mt-[5px] [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:size-[14px] [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-[color:var(--color-np-ink)] [&::-webkit-slider-thumb]:border-0 focus-visible:[&::-webkit-slider-thumb]:shadow-[0_0_0_3px_var(--color-np-bg),0_0_0_5px_var(--color-np-accent)] [&::-moz-range-thumb]:size-[14px] [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:bg-[color:var(--color-np-ink)] [&::-moz-range-thumb]:border-0 focus-visible:[&::-moz-range-thumb]:shadow-[0_0_0_3px_var(--color-np-bg),0_0_0_5px_var(--color-np-accent)] w-full flex-1 hover:[&::-webkit-slider-runnable-track]:bg-[linear-gradient(to_right,var(--color-np-accent)_0_calc(7px_+_(100%_-_14px)_*_var(--f)),var(--color-np-track)_0)] hover:[&::-moz-range-track]:bg-[linear-gradient(to_right,var(--color-np-accent)_0_calc(7px_+_(100%_-_14px)_*_var(--f)),var(--color-np-track)_0)]" data-seek min="0" max="30" step="any" value="0" aria-label="Seek" aria-valuetext="0:00 of 0:30">
    <div class="mt-1 flex justify-between"><span class="min-w-[2.3em] text-[.75rem] text-np-muted tabular-nums" data-cur>0:00</span><span class="min-w-[2.3em] text-[.75rem] text-np-muted tabular-nums text-right" data-dur>0:30</span></div>
  </div>
  <div class="flex items-center justify-between">
    <button type="button" class="grid size-10 flex-none cursor-pointer place-items-center rounded-full text-np-muted hover:bg-np-raise hover:text-np-ink aria-pressed:text-np-accent focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-np-accent" data-shuffle aria-label="Shuffle" aria-pressed="false"><svg class="block size-5" viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 4h4v4M4 20 20 4M20 16v4h-4M15 15l5 5M4 4l5 5"/></svg></button>
    <button type="button" class="grid flex-none cursor-pointer place-items-center rounded-full text-np-ink hover:bg-np-raise focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-np-accent size-11" data-prev aria-label="Previous track"><svg class="block size-6" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M5 5h2.2v14H5zM19.5 6.2v11.6a.8.8 0 0 1-1.24.67L9.6 12.67a.8.8 0 0 1 0-1.34l8.66-5.8a.8.8 0 0 1 1.24.67z"/></svg></button>
    <button type="button" class="grid flex-none cursor-pointer place-items-center rounded-full bg-np-ink text-[#0c0c12] hover:bg-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-np-accent size-[60px]" data-play aria-label="Play Night Bus to Nowhere"><svg class="block size-[26px] group-data-playing:hidden" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M8 5.5v13a1 1 0 0 0 1.5.86l10.5-6.5a1 1 0 0 0 0-1.72L9.5 4.64A1 1 0 0 0 8 5.5z"/></svg><svg class="hidden size-[26px] group-data-playing:block" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><rect x="6.5" y="5" width="4" height="14" rx="1.2"/><rect x="13.5" y="5" width="4" height="14" rx="1.2"/></svg></button>
    <button type="button" class="grid flex-none cursor-pointer place-items-center rounded-full text-np-ink hover:bg-np-raise focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-np-accent size-11" data-next aria-label="Next track"><svg class="block size-6" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M16.8 5H19v14h-2.2zM4.5 6.2v11.6a.8.8 0 0 0 1.24.67l8.66-5.8a.8.8 0 0 0 0-1.34L5.74 5.53a.8.8 0 0 0-1.24.67z"/></svg></button>
    <button type="button" class="grid size-10 flex-none cursor-pointer place-items-center rounded-full text-np-muted hover:bg-np-raise hover:text-np-ink aria-pressed:text-np-accent focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-np-accent" data-repeat aria-label="Repeat this track" aria-pressed="false"><svg class="block size-5" viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m17 2 4 4-4 4"/><path d="M3 11V9a3 3 0 0 1 3-3h15"/><path d="m7 22-4-4 4-4"/><path d="M21 13v2a3 3 0 0 1-3 3H3"/></svg></button>
  </div>
  <div class="flex items-center gap-2">
    <button type="button" class="group/m grid size-10 flex-none cursor-pointer place-items-center rounded-full text-np-muted hover:bg-np-raise hover:text-np-ink aria-pressed:text-np-accent focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-np-accent" data-mute aria-label="Mute" aria-pressed="false"><svg class="block size-5" viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 5 6 9H3v6h3l5 4z"/><path class="group-aria-pressed/m:hidden" d="M15.5 8.5a5 5 0 0 1 0 7M18.5 5.5a9 9 0 0 1 0 13"/><path class="hidden group-aria-pressed/m:inline" d="m16 9 6 6m0-6-6 6"/></svg></button>
    <input type="range" class="block h-[14px] min-w-0 cursor-pointer appearance-none bg-transparent [--f:var(--v)] focus-visible:outline-none [&::-webkit-slider-runnable-track]:h-[4px] [&::-webkit-slider-runnable-track]:rounded-full [&::-webkit-slider-runnable-track]:bg-[linear-gradient(to_right,var(--color-np-ink)_0_calc(7px_+_(100%_-_14px)_*_var(--f)),var(--color-np-track)_0)] [&::-moz-range-track]:h-[4px] [&::-moz-range-track]:rounded-full [&::-moz-range-track]:bg-[linear-gradient(to_right,var(--color-np-ink)_0_calc(7px_+_(100%_-_14px)_*_var(--f)),var(--color-np-track)_0)] [&::-webkit-slider-thumb]:-mt-[5px] [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:size-[14px] [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-[color:var(--color-np-ink)] [&::-webkit-slider-thumb]:border-0 focus-visible:[&::-webkit-slider-thumb]:shadow-[0_0_0_3px_var(--color-np-bg),0_0_0_5px_var(--color-np-accent)] [&::-moz-range-thumb]:size-[14px] [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:bg-[color:var(--color-np-ink)] [&::-moz-range-thumb]:border-0 focus-visible:[&::-moz-range-thumb]:shadow-[0_0_0_3px_var(--color-np-bg),0_0_0_5px_var(--color-np-accent)] w-full flex-1 hover:[&::-webkit-slider-runnable-track]:bg-[linear-gradient(to_right,var(--color-np-accent)_0_calc(7px_+_(100%_-_14px)_*_var(--f)),var(--color-np-track)_0)] hover:[&::-moz-range-track]:bg-[linear-gradient(to_right,var(--color-np-accent)_0_calc(7px_+_(100%_-_14px)_*_var(--f)),var(--color-np-track)_0)]" data-vol min="0" max="1" step="0.05" value="0.7" aria-label="Volume" aria-valuetext="70%">
  </div>
  <p class="mt-1 -mb-2 text-[.72rem] tracking-[.1em] text-np-muted uppercase">Queue</p>
  <ol>
    <li><button type="button" class="group/item grid w-full cursor-pointer grid-cols-[40px_1fr_auto] items-center gap-3 rounded-xl px-2.5 py-2 text-left hover:bg-np-raise focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-np-accent" data-src="../audio/track-3.mp3" data-name="Night Bus to Nowhere" data-artist="Orla Finch" aria-current="true">
      <span class="size-10 rounded-lg [background:radial-gradient(circle_at_68%_30%,#fef3c7_0_9%,rgba(254,243,199,.16)_9.5%_15%,transparent_15.5%),radial-gradient(circle_at_20%_25%,#fff_0_1.2%,transparent_2%),radial-gradient(circle_at_38%_55%,#fff_0_.9%,transparent_1.6%),radial-gradient(circle_at_86%_64%,#fff_0_1%,transparent_1.7%),radial-gradient(circle_at_12%_70%,#c4b5fd_0_1%,transparent_1.7%),linear-gradient(180deg,#0f172a,#312e81_70%,#7c3aed)]" data-art></span><span><span class="block text-[.92rem] font-semibold group-aria-[current=true]/item:text-np-accent">Night Bus to Nowhere</span><span class="mt-0.5 block text-[.78rem] text-np-muted">Orla Finch</span></span><span class="min-w-[2.3em] text-[.75rem] text-np-muted tabular-nums">0:30</span></button></li>
    <li><button type="button" class="group/item grid w-full cursor-pointer grid-cols-[40px_1fr_auto] items-center gap-3 rounded-xl px-2.5 py-2 text-left hover:bg-np-raise focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-np-accent" data-src="../audio/track-1.mp3" data-name="Harbour Lights" data-artist="Juniper Vale">
      <span class="size-10 rounded-lg [background:radial-gradient(circle_at_68%_40%,#fde68a_0_11%,transparent_11.5%),repeating-linear-gradient(180deg,transparent_0_6%,rgba(253,230,138,.3)_6%_7.5%)_bottom/100%_42%_no-repeat,linear-gradient(180deg,#fb923c,#e11d48_58%,#312e81_58%,#1e1b4b)]" data-art></span><span><span class="block text-[.92rem] font-semibold group-aria-[current=true]/item:text-np-accent">Harbour Lights</span><span class="mt-0.5 block text-[.78rem] text-np-muted">Juniper Vale</span></span><span class="min-w-[2.3em] text-[.75rem] text-np-muted tabular-nums">0:24</span></button></li>
    <li><button type="button" class="group/item grid w-full cursor-pointer grid-cols-[40px_1fr_auto] items-center gap-3 rounded-xl px-2.5 py-2 text-left hover:bg-np-raise focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-np-accent" data-src="../audio/track-4.mp3" data-name="Copper Sky" data-artist="Milo & the Tidelines">
      <span class="size-10 rounded-lg [background:radial-gradient(circle_at_50%_100%,#fff7ed_0_24%,#fdba74_24%_27%,transparent_27.5%),linear-gradient(180deg,#7c2d12_0_22%,#9a3412_22%_40%,#c2410c_40%_56%,#ea580c_56%_70%,#f59e0b_70%_84%,#fcd34d_84%)]" data-art></span><span><span class="block text-[.92rem] font-semibold group-aria-[current=true]/item:text-np-accent">Copper Sky</span><span class="mt-0.5 block text-[.78rem] text-np-muted">Milo & the Tidelines</span></span><span class="min-w-[2.3em] text-[.75rem] text-np-muted tabular-nums">0:29</span></button></li>
  </ol>
</section></div>

<div><span class="mb-3.5 block text-[.72rem] tracking-[.12em] text-[#9ca3af] uppercase">Desktop player bar</span>
<section class="group rounded-3xl border border-[#2a2a36] bg-np-bg text-np-ink [--p:0] [--v:1] px-[18px] py-3" data-np aria-label="Player">
  <audio src="../audio/track-2.mp3" preload="metadata"></audio>
  <div class="grid grid-cols-[minmax(0,1.3fr)_minmax(0,1.5fr)_minmax(0,1fr)] items-center gap-5 max-[720px]:grid-cols-[1fr_auto]">
    <div class="flex min-w-0 items-center gap-4">
      <div class="size-[52px] flex-none rounded-[10px] [background:linear-gradient(to_bottom_right,transparent_50%,#fde047_50%)_37.5%_26.67%/20%_25%_no-repeat,linear-gradient(to_bottom_left,transparent_50%,#f97316_50%)_62.5%_26.67%/20%_25%_no-repeat,linear-gradient(to_top_right,transparent_50%,#fb923c_50%)_37.5%_69.23%/20%_35%_no-repeat,linear-gradient(to_top_left,transparent_50%,#facc15_50%)_62.5%_69.23%/20%_35%_no-repeat,linear-gradient(to_bottom_right,transparent_calc(50%_-_1px),rgba(255,255,255,.85)_0_calc(50%_+_1px),transparent_0)_41.86%_100%/14%_20%_no-repeat,linear-gradient(160deg,#0ea5e9,#14b8a6_55%,#84cc16)]" data-cover aria-hidden="true"></div>
      <div class="min-w-0"><p class="truncate text-[.95rem] font-extrabold tracking-[-.015em]" data-show="name">Paper Kites</p><p class="mt-[3px] truncate text-[.8rem] text-np-muted" data-show="artist">The Lumen Choir</p></div>
      <button type="button" class="grid size-10 flex-none cursor-pointer place-items-center rounded-full text-np-muted hover:bg-np-raise hover:text-np-ink aria-pressed:text-np-accent focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-np-accent aria-pressed:[&_svg]:fill-current" data-like aria-label="Like Paper Kites" aria-pressed="false"><svg class="block size-5" viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20s-7.5-4.6-7.5-10.2A4.2 4.2 0 0 1 12 7.3a4.2 4.2 0 0 1 7.5 2.5C19.5 15.4 12 20 12 20z"/></svg></button>
    </div>
    <div class="flex flex-col gap-1 max-[720px]:col-span-full max-[720px]:row-start-2">
      <div class="flex items-center justify-center gap-2">
        <button type="button" class="grid flex-none cursor-pointer place-items-center rounded-full text-np-ink hover:bg-np-raise focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-np-accent size-9" data-prev aria-label="Previous track"><svg class="block size-5" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M5 5h2.2v14H5zM19.5 6.2v11.6a.8.8 0 0 1-1.24.67L9.6 12.67a.8.8 0 0 1 0-1.34l8.66-5.8a.8.8 0 0 1 1.24.67z"/></svg></button>
        <button type="button" class="grid flex-none cursor-pointer place-items-center rounded-full bg-np-ink text-[#0c0c12] hover:bg-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-np-accent size-10" data-play aria-label="Play Paper Kites"><svg class="block size-5 group-data-playing:hidden" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M8 5.5v13a1 1 0 0 0 1.5.86l10.5-6.5a1 1 0 0 0 0-1.72L9.5 4.64A1 1 0 0 0 8 5.5z"/></svg><svg class="hidden size-5 group-data-playing:block" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><rect x="6.5" y="5" width="4" height="14" rx="1.2"/><rect x="13.5" y="5" width="4" height="14" rx="1.2"/></svg></button>
        <button type="button" class="grid flex-none cursor-pointer place-items-center rounded-full text-np-ink hover:bg-np-raise focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-np-accent size-9" data-next aria-label="Next track"><svg class="block size-5" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M16.8 5H19v14h-2.2zM4.5 6.2v11.6a.8.8 0 0 0 1.24.67l8.66-5.8a.8.8 0 0 0 0-1.34L5.74 5.53a.8.8 0 0 0-1.24.67z"/></svg></button>
      </div>
      <div class="flex items-center gap-2">
        <span class="min-w-[2.3em] text-[.75rem] text-np-muted tabular-nums" data-cur>0:00</span>
        <input type="range" class="block h-[14px] min-w-0 cursor-pointer appearance-none bg-transparent [--f:var(--p)] focus-visible:outline-none [&::-webkit-slider-runnable-track]:h-[4px] [&::-webkit-slider-runnable-track]:rounded-full [&::-webkit-slider-runnable-track]:bg-[linear-gradient(to_right,var(--color-np-ink)_0_calc(7px_+_(100%_-_14px)_*_var(--f)),var(--color-np-track)_0)] [&::-moz-range-track]:h-[4px] [&::-moz-range-track]:rounded-full [&::-moz-range-track]:bg-[linear-gradient(to_right,var(--color-np-ink)_0_calc(7px_+_(100%_-_14px)_*_var(--f)),var(--color-np-track)_0)] [&::-webkit-slider-thumb]:-mt-[5px] [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:size-[14px] [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-[color:var(--color-np-ink)] [&::-webkit-slider-thumb]:border-0 focus-visible:[&::-webkit-slider-thumb]:shadow-[0_0_0_3px_var(--color-np-bg),0_0_0_5px_var(--color-np-accent)] [&::-moz-range-thumb]:size-[14px] [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:bg-[color:var(--color-np-ink)] [&::-moz-range-thumb]:border-0 focus-visible:[&::-moz-range-thumb]:shadow-[0_0_0_3px_var(--color-np-bg),0_0_0_5px_var(--color-np-accent)] w-full flex-1 hover:[&::-webkit-slider-runnable-track]:bg-[linear-gradient(to_right,var(--color-np-accent)_0_calc(7px_+_(100%_-_14px)_*_var(--f)),var(--color-np-track)_0)] hover:[&::-moz-range-track]:bg-[linear-gradient(to_right,var(--color-np-accent)_0_calc(7px_+_(100%_-_14px)_*_var(--f)),var(--color-np-track)_0)]" data-seek min="0" max="24" step="any" value="0" aria-label="Seek" aria-valuetext="0:00 of 0:24">
        <span class="min-w-[2.3em] text-[.75rem] text-np-muted tabular-nums text-right" data-dur>0:24</span>
      </div>
    </div>
    <div class="flex items-center justify-end gap-1">
      <button type="button" class="grid size-10 flex-none cursor-pointer place-items-center rounded-full text-np-muted hover:bg-np-raise hover:text-np-ink aria-pressed:text-np-accent focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-np-accent aria-expanded:text-np-accent" data-queue aria-label="Queue" aria-expanded="false" aria-controls="np-queue-2"><svg class="block size-5" viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6h11M4 12h11M4 18h7M19 6v10"/><circle cx="17" cy="16.5" r="2"/></svg></button>
      <button type="button" class="group/m grid size-10 flex-none cursor-pointer place-items-center rounded-full text-np-muted hover:bg-np-raise hover:text-np-ink aria-pressed:text-np-accent focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-np-accent max-[720px]:hidden" data-mute aria-label="Mute" aria-pressed="false"><svg class="block size-5" viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 5 6 9H3v6h3l5 4z"/><path class="group-aria-pressed/m:hidden" d="M15.5 8.5a5 5 0 0 1 0 7M18.5 5.5a9 9 0 0 1 0 13"/><path class="hidden group-aria-pressed/m:inline" d="m16 9 6 6m0-6-6 6"/></svg></button>
      <input type="range" class="block h-[14px] min-w-0 cursor-pointer appearance-none bg-transparent [--f:var(--v)] focus-visible:outline-none [&::-webkit-slider-runnable-track]:h-[4px] [&::-webkit-slider-runnable-track]:rounded-full [&::-webkit-slider-runnable-track]:bg-[linear-gradient(to_right,var(--color-np-ink)_0_calc(7px_+_(100%_-_14px)_*_var(--f)),var(--color-np-track)_0)] [&::-moz-range-track]:h-[4px] [&::-moz-range-track]:rounded-full [&::-moz-range-track]:bg-[linear-gradient(to_right,var(--color-np-ink)_0_calc(7px_+_(100%_-_14px)_*_var(--f)),var(--color-np-track)_0)] [&::-webkit-slider-thumb]:-mt-[5px] [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:size-[14px] [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-[color:var(--color-np-ink)] [&::-webkit-slider-thumb]:border-0 focus-visible:[&::-webkit-slider-thumb]:shadow-[0_0_0_3px_var(--color-np-bg),0_0_0_5px_var(--color-np-accent)] [&::-moz-range-thumb]:size-[14px] [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:bg-[color:var(--color-np-ink)] [&::-moz-range-thumb]:border-0 focus-visible:[&::-moz-range-thumb]:shadow-[0_0_0_3px_var(--color-np-bg),0_0_0_5px_var(--color-np-accent)] w-full flex-1 hover:[&::-webkit-slider-runnable-track]:bg-[linear-gradient(to_right,var(--color-np-accent)_0_calc(7px_+_(100%_-_14px)_*_var(--f)),var(--color-np-track)_0)] hover:[&::-moz-range-track]:bg-[linear-gradient(to_right,var(--color-np-accent)_0_calc(7px_+_(100%_-_14px)_*_var(--f)),var(--color-np-track)_0)] w-24! flex-none! max-[720px]:hidden" data-vol min="0" max="1" step="0.05" value="0.7" aria-label="Volume" aria-valuetext="70%">
    </div>
  </div>
  <ol class="mt-3 grid grid-cols-[repeat(auto-fill,minmax(220px,1fr))] gap-1 border-t border-[#2a2a36] pt-2 [&[hidden]]:hidden" id="np-queue-2" hidden>
    <li><button type="button" class="group/item grid w-full cursor-pointer grid-cols-[40px_1fr_auto] items-center gap-3 rounded-xl px-2.5 py-2 text-left hover:bg-np-raise focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-np-accent" data-src="../audio/track-2.mp3" data-name="Paper Kites" data-artist="The Lumen Choir" aria-current="true">
      <span class="size-10 rounded-lg [background:linear-gradient(to_bottom_right,transparent_50%,#fde047_50%)_37.5%_26.67%/20%_25%_no-repeat,linear-gradient(to_bottom_left,transparent_50%,#f97316_50%)_62.5%_26.67%/20%_25%_no-repeat,linear-gradient(to_top_right,transparent_50%,#fb923c_50%)_37.5%_69.23%/20%_35%_no-repeat,linear-gradient(to_top_left,transparent_50%,#facc15_50%)_62.5%_69.23%/20%_35%_no-repeat,linear-gradient(to_bottom_right,transparent_calc(50%_-_1px),rgba(255,255,255,.85)_0_calc(50%_+_1px),transparent_0)_41.86%_100%/14%_20%_no-repeat,linear-gradient(160deg,#0ea5e9,#14b8a6_55%,#84cc16)]" data-art></span><span><span class="block text-[.92rem] font-semibold group-aria-[current=true]/item:text-np-accent">Paper Kites</span><span class="mt-0.5 block text-[.78rem] text-np-muted">The Lumen Choir</span></span><span class="min-w-[2.3em] text-[.75rem] text-np-muted tabular-nums">0:24</span></button></li>
    <li><button type="button" class="group/item grid w-full cursor-pointer grid-cols-[40px_1fr_auto] items-center gap-3 rounded-xl px-2.5 py-2 text-left hover:bg-np-raise focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-np-accent" data-src="../audio/track-4.mp3" data-name="Copper Sky" data-artist="Milo & the Tidelines">
      <span class="size-10 rounded-lg [background:radial-gradient(circle_at_50%_100%,#fff7ed_0_24%,#fdba74_24%_27%,transparent_27.5%),linear-gradient(180deg,#7c2d12_0_22%,#9a3412_22%_40%,#c2410c_40%_56%,#ea580c_56%_70%,#f59e0b_70%_84%,#fcd34d_84%)]" data-art></span><span><span class="block text-[.92rem] font-semibold group-aria-[current=true]/item:text-np-accent">Copper Sky</span><span class="mt-0.5 block text-[.78rem] text-np-muted">Milo & the Tidelines</span></span><span class="min-w-[2.3em] text-[.75rem] text-np-muted tabular-nums">0:29</span></button></li>
    <li><button type="button" class="group/item grid w-full cursor-pointer grid-cols-[40px_1fr_auto] items-center gap-3 rounded-xl px-2.5 py-2 text-left hover:bg-np-raise focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-np-accent" data-src="../audio/track-1.mp3" data-name="Harbour Lights" data-artist="Juniper Vale">
      <span class="size-10 rounded-lg [background:radial-gradient(circle_at_68%_40%,#fde68a_0_11%,transparent_11.5%),repeating-linear-gradient(180deg,transparent_0_6%,rgba(253,230,138,.3)_6%_7.5%)_bottom/100%_42%_no-repeat,linear-gradient(180deg,#fb923c,#e11d48_58%,#312e81_58%,#1e1b4b)]" data-art></span><span><span class="block text-[.92rem] font-semibold group-aria-[current=true]/item:text-np-accent">Harbour Lights</span><span class="mt-0.5 block text-[.78rem] text-np-muted">Juniper Vale</span></span><span class="min-w-[2.3em] text-[.75rem] text-np-muted tabular-nums">0:24</span></button></li>
  </ol>
</section></div>
CSS
@theme {
  --color-np-accent: #34d399;
  --color-np-bg: #16161f;
  --color-np-raise: #22222d;
  --color-np-ink: #f4f4f5;
  --color-np-muted: #a1a1aa;
  --color-np-track: #6b6b7b;
}
JavaScript
// Dark streaming players. Each [data-np] holds one <audio> and a queue of track buttons (data-src, data-name,
// data-artist). Also wires like, shuffle, repeat-one, mute, volume and the queue toggle when they are present.
(() => {
  const fmt = s => { s = Math.floor(s || 0); return Math.floor(s / 60) + ':' + String(s % 60).padStart(2, '0'); };
  const toggle = b => { const on = b.getAttribute('aria-pressed') !== 'true'; b.setAttribute('aria-pressed', String(on)); return on; };
  document.querySelectorAll('[data-np]').forEach(root => {
    const $ = s => root.querySelector(s);
    const audio = $('audio'), play = $('[data-play]'), seek = $('[data-seek]'), cur = $('[data-cur]'), dur = $('[data-dur]');
    const vol = $('[data-vol]'), mute = $('[data-mute]'), shuffle = $('[data-shuffle]'), repeat = $('[data-repeat]');
    const cover = $('[data-cover]'), items = [...root.querySelectorAll('[data-src]')];
    let i = Math.max(0, items.findIndex(b => b.getAttribute('aria-current') === 'true')), raf = 0;
    const label = () => play.setAttribute('aria-label', (audio.paused ? 'Play ' : 'Pause ') + items[i].dataset.name);
    const length = () => audio.duration || +seek.max;
    const sync = () => {
      const t = audio.currentTime, d = length(), text = fmt(t) + ' of ' + fmt(d);
      seek.value = t;
      root.style.setProperty('--p', d ? t / d : 0);
      cur.textContent = fmt(t);
      if (seek.getAttribute('aria-valuetext') !== text) seek.setAttribute('aria-valuetext', text);
    };
    const tick = () => { sync(); raf = requestAnimationFrame(tick); };
    const meta = () => { seek.max = audio.duration; dur.textContent = fmt(audio.duration); sync(); };
    const sound = () => {
      const v = audio.muted ? 0 : audio.volume;
      root.style.setProperty('--v', v);
      if (vol) { vol.value = v; vol.setAttribute('aria-valuetext', Math.round(v * 100) + '%'); }
      if (mute) mute.setAttribute('aria-pressed', String(v === 0));
    };
    const start = () => {
      document.querySelectorAll('audio').forEach(a => { if (a !== audio) a.pause(); });
      audio.play().catch(() => {});
    };
    // Switch to track n: mark it current, swap the source, title, artist and cover art.
    const load = n => {
      i = (n + items.length) % items.length;
      const item = items[i], art = item.querySelector('[data-art]');
      items.forEach(b => b.removeAttribute('aria-current'));
      item.setAttribute('aria-current', 'true');
      audio.src = item.dataset.src;
      root.querySelectorAll('[data-show]').forEach(el => { el.textContent = item.dataset[el.dataset.show]; });
      root.querySelectorAll('[data-like]').forEach(b => { b.setAttribute('aria-pressed', 'false'); b.setAttribute('aria-label', 'Like ' + item.dataset.name); });
      if (cover && art) {
        const cs = getComputedStyle(art);
        ['backgroundImage', 'backgroundPosition', 'backgroundSize', 'backgroundRepeat'].forEach(p => { cover.style[p] = cs[p]; });
      }
      label();
    };
    const next = () => {
      const on = shuffle && shuffle.getAttribute('aria-pressed') === 'true';
      load(on ? (i + 1 + Math.floor(Math.random() * (items.length - 1))) % items.length : i + 1);
      start();
    };
    play.addEventListener('click', () => (audio.paused ? start() : audio.pause()));
    $('[data-prev]').addEventListener('click', () => {
      if (audio.currentTime > 3) audio.currentTime = 0; else load(i - 1);
      start();
    });
    $('[data-next]').addEventListener('click', next);
    items.forEach((item, n) => item.addEventListener('click', () => {
      if (n === i) return play.click();
      load(n); start();
    }));
    root.querySelectorAll('[data-like]').forEach(b => b.addEventListener('click', () => toggle(b)));
    if (shuffle) shuffle.addEventListener('click', () => toggle(shuffle));
    if (repeat) repeat.addEventListener('click', () => { audio.loop = toggle(repeat); });
    root.querySelectorAll('[data-queue]').forEach(b => b.addEventListener('click', () => {
      const open = b.getAttribute('aria-expanded') !== 'true';
      b.setAttribute('aria-expanded', String(open));
      document.getElementById(b.getAttribute('aria-controls')).hidden = !open;
    }));
    audio.addEventListener('play', () => { root.setAttribute('data-playing', ''); label(); tick(); });
    audio.addEventListener('pause', () => { root.removeAttribute('data-playing'); label(); cancelAnimationFrame(raf); sync(); });
    audio.addEventListener('ended', () => { if (i < items.length - 1 || (shuffle && shuffle.getAttribute('aria-pressed') === 'true')) next(); });
    audio.addEventListener('loadedmetadata', meta);
    audio.addEventListener('volumechange', sound);
    if (audio.readyState > 0) meta();
    seek.addEventListener('input', () => { audio.currentTime = seek.value; sync(); });
    // Arrow keys jump 5 seconds; Home and End work natively.
    seek.addEventListener('keydown', e => {
      const step = { ArrowLeft: -5, ArrowDown: -5, ArrowRight: 5, ArrowUp: 5 }[e.key];
      if (!step) return;
      e.preventDefault();
      audio.currentTime = Math.min(Math.max(audio.currentTime + step, 0), length());
      sync();
    });
    if (vol) {
      audio.volume = vol.value;
      vol.addEventListener('input', () => { audio.volume = vol.value; audio.muted = false; });
    }
    if (mute) mute.addEventListener('click', () => {
      if (audio.muted || audio.volume === 0) { audio.muted = false; if (audio.volume === 0) audio.volume = 0.6; }
      else audio.muted = true;
    });
    sound();
  });
})();

About this set

Two players for dark streaming-style interfaces. The now playing screen is a phone-width card with the playlist it is playing from, a large cover, the title and artist with a like button, a seek slider with times, shuffle, previous, play, next and repeat buttons, a mute button with a volume slider and a three-track queue. The desktop player bar is a three-column strip with the cover, title and like button on the left, the transport controls and seek slider in the middle and a queue button, mute and volume on the right; the queue button uses aria-expanded to open the track list underneath, and under 720 pixels the controls move to a second row and the volume slider hides. Shuffle makes next pick a random other track, and repeat sets the audio element’s loop property so the current track starts again. Sliders fill in white and turn mint on hover. The muted text reaches about 7:1 on the card, the empty slider track 3.4:1, and the Bootstrap version runs under data-bs-theme=”dark”.

What’s included

  • Phone-style now playing screen and a desktop player bar
  • Shuffle and repeat-one as aria-pressed toggle buttons
  • Queue as buttons with aria-current; the bar's queue opens with aria-expanded
  • Like, mute and volume on both layouts
  • Muted text about 7:1 and slider tracks 3.4:1 on the dark card
  • Bootstrap version runs with data-bs-theme="dark"

Accessibility

Each player is a labelled section. Shuffle, repeat, like and mute are toggle buttons with aria-pressed, the queue button has aria-expanded and aria-controls, and the current queue entry carries aria-current. The play button names the current track, the seek slider reads as 0:20 of 0:30 and volume as a percentage. Focus draws a mint 2px outline, with a gap in the card colour around slider thumbs. The players use no motion.

Customise it

Change --np-accent, --np-bg, --np-raise, --np-ink, --np-muted and --np-track on .np. In Bootstrap the card picks these up through --bs-card-bg and the --bs-btn-* variables; in Tailwind edit the --color-np-* theme colours.