<!-- SOLID CITY · part 2861 -->
 True shuffle: every not-yet-played song (in random order) comes before any
  // repeat; st.played survives reloads, and resets once the whole set has played.
  function setQueue(list) {
    if (!st.shuffle) queue = list.slice();
    else {
      var seen = {}; (st.played || []).forEach(function (n) { seen[n] = 1; });
      var fresh = list.filter(function (s) { return !seen[s.n]; });
      if (!fresh.length) { st.played = cur ? [cur.n] : []; fresh = list.filter(function (s) { return s !== cur; }); }
      queue = shuffled(fresh).concat(shuffled(list.filter(function (s) { return fresh.indexOf(s) < 0 && s !== cur; })));
      if (cur && list.indexOf(cur) >= 0) { queue = queue.filter(function (s) { return s !== cur; }); queue.unshift(cur); }
    }
    qi = cur ? queue.indexOf(cur) : -1;
  }

  /* ---------- playback ---------- */
  function playSong(s, at) {
    if (!s) return; cur = s; qi = queue.indexOf(s); st.n = s.n;
    if (!st.played) st.played = []; if (st.played.indexOf(s.n) < 0) st.played.push(s.n); st.t = at || 0; st.paused = false; preloadedFor = null;
    audio.src = MP3 + s.file;
    if (at) { var seekOnce = function () { audio.removeEventListener('loadedmetadata', seekOnce); try { audio.currentTime = at; } catch (e) {} }; audio.addEventListener('loadedmetadata', seekOnce); }
    doPlay(); renderNow(); emit(); save(); if (tab === 'lyrics') loadLyrics();
  }
  function doPlay() {
    wantPlay = true;
    var p = audio.play(); if (p && p.catch) p.catch(function () { wantPlay = false; el.mini.classList.add('mp-needtap'); el.mTitle.textContent = 'Tap ▶ to start the music'; renderBtns(); });
  }
  function toggle() {
    if (!data) { load().then(function () { if (cur) playSong(cur, st.t); }); return; }
    if (!audio.src && cur) return playSong(cur, st.t);
    if (audio.paused) { st.paused = false; doPlay(); } else { audio.pause(); st.paused = true; wantPlay = false; }
    save(); renderBtns(); emit();
  }
  function playNow() { if (audio.paused) toggle(); }
  function pauseNow() { if (!audio.paused) toggle(); }
  function step(d, auto) {
    if (!queue.length) return;
    if (auto && st.repeat === 'one') { audio.currentTime = 0; doPlay(); return; }
    var n = qi + d;
    if (n >= queue.length) {
      if (auto && st.repeat === 'off') { st.paused = true; wantPlay = false; renderBtns(); emit(); return; }
      if (st.shuffle) { st.played = [cur.n]; setQueue(visible()); n = Math.min(1, queue.length - 1); } else n = 0;   // new shuffle cycle, never the same song twice in a row
    }
    if (n < 0) n = queue.length - 1;
    playSong(queue[n], 0);
  }
  function prev() { if (audio.currentTime > 4) { audio.currentTime = 0; return; } step(-1); }
  audio.addEventListener('ended', function () { step(1, true); });
  audio.addEventListener('play', function () { el.mini.classList.remove('mp-needtap'); renderNow(); renderBtns(); emit(); msState(); });
  audio.addEventListener('pause', function () { renderBtns(); emit(); msState(); save(); });
  audio.addEventListener('loadedmetadata', msPos);
  audio.addEventListener('seeked', msPos);
  audio.addEventListener('error', function () { if (cur && wantPlay) setTimeout(function () { step(1, true); }, 1500); });
  var lastSave = 0;
  function syncSeek() {
    if (!el.seek) return; var t = audio.currentTime, d = audio.duration || (cur && cur.dur) || 0;
    if (!seeking && d) { el.seek.value = Math.round(t / d * 1000); el.tCur.textContent = fmt(t); el.tDur.textContent = fmt(d); el.mBar.style.width = (t / d * 100).toFixed(1) + '%'; }
  }
  audio.addEventListener('timeupdate', function () {
    var t = audio.currentTime, d = audio.duration || (cur && cur.dur) || 0;
    if (!document.hidden) syncSeek();
    if (Date.now() - lastSave > 5000) { lastSave = Date.now(); save(); msPos(); }
    // preload ONLY the next track, once this one is under way (earlier while hidden, so a locked phone has it cached)
    if (d && cur && preloadedFor !== cur.n && (t / d > (document.hidden ? 0.3 : 0.6) || d - t < 45)) {
      preloadedFor = cur.n; var nx = queue[(qi + 1) % queue.length];
      if (nx && nx !== cur && st.repeat !== 'one') { pre.src = MP3 + nx.file; try { pre.load(); } catch (e) {} }
    }
  });

  /* ---------- lyrics ---------- */
  var lyrCache = {};
  function loadLyrics() {
    if (!cur) { el.lyr.textContent = ''; return; }
    var s = cur;
    el.lyrTitle.textContent = s.title;
    el.lyrLink.hidden = !s.url; if (s.url) el.lyrLink.href = s.url;
    if (!s.lyr) { el.lyr.textContent = s.kind === 'instrumental' ? 'Instrumental — no lyrics.' : 'No lyrics on file for this song.'; return; }
    if (lyrCache[s.n]) { el.lyr.textContent = lyrCache[s.n]; return; }
    el.lyr.textContent = 'Loading lyrics…';
    fetch('/api/city/songs/' + s.n + '/lyrics', { credentials: 'same-origin' }).then(function (r) { return r.json(); }).then(function (j) {
      var txt = String(j.lyrics || '').replace(/\s+\/\s+/g, '\n'); lyrCache[s.n] = txt || 'No lyrics on fil