<!-- SS · part 19 -->
 { toast(x.message); });
    } else if (act === 'delete') {
      if (!confirm('Delete this song for good?')) return;
      api('POST', '/api/ss/songs/' + id + '/delete', {}).then(function () { li.remove(); toast('Deleted.'); }).catch(function (x) { toast(x.message); });
    } else if (act === 'radio') {
      if (!confirm('Add this song to the radio library?')) return;
      busy(b, true);
      api('POST', '/api/ss/songs/' + id + '/radio', {}).then(function () { b.textContent = 'On the radio ✓'; toast('Added to the radio library.'); }).catch(function (x) { busy(b, false); toast(x.message); });
    } else if (act === 'load') {
      api('GET', '/api/ss/songs/' + id).then(function (j) {
        var s = j.song, st = s.studio || {};
        stop();
        S.title = s.title; S.lyrics = s.lyrics || ''; S.bpm = s.bpm; S.key = parseKey(s.key).name; S.patterns = s.patterns;
        if (st.sounds) S.sounds = st.sounds; if (st.vols) S.vols = st.vols; if (st.mutes) S.mutes = st.mutes;
        if (typeof st.swing === 'number') S.swing = st.swing; S.chords = st.chords || []; S.suno = st.suno || ''; S.genre = st.genre || ''; S.mood = st.mood || '';
        S.voxClips = null; save(); fillWrite();
        if (E) { ['drums', 'bass', 'chords', 'lead'].forEach(function (t) { setSound(t, S.sounds[t]); E.ch[t].volume.value = S.vols[t]; E.ch[t].mute = !!S.mutes[t]; }); }
        syncStudio(); showTab('studio'); toast('Opened “' + s.title + '”.');
      }).catch(function (x) { toast(x.message); });
    }
  });

  // ---------------------------------------------------------------- boot
  fillWrite(); syncStudio();
  var h = (location.hash || '').slice(1);
  if (['write', 'studio', 'voice', 'songs'].indexOf(h) >= 0) showTab(h);
  window.addEventListener('hashchange', function () { var t = (location.hash || '').slice(1); if (['write', 'studio', 'voice', 'songs'].indexOf(t) >= 0 && !$('.ss-panel[data-panel="' + t + '"]').classList.contains('is-on')) showTab(t); });
  document.addEventListener('visibilitychange', function () { if (document.hidden && playing) stop(); });
})();


===== FILE: public/ss/ss.js.bak-20261009-studio =====
/* /ss — SS Music Studio front end (Tone.js, self-hosted). */
(function () {
  'use strict';
  var $ = function (s, r) { return (r || document).querySelector(s); };
  var $$ = function (s, r) { return Array.prototype.slice.call((r || document).querySelectorAll(s)); };
  var app = $('#ss-app'); if (!app) return;
  var IS_ADMIN = app.getAttribute('data-admin') === '1';
  var CSRF = (document.querySelector('meta[name="csrf-token"]') || {}).content || '';
  var STEPS = 16;
  var LS_KEY = 'ss_state_v1';

  // ---------------------------------------------------------------- utils
  function toast(msg, ms) {
    var t = $('#ss-toast'); t.textContent = msg; t.hidden = false;
    clearTimeout(toast._t); toast._t = setTimeout(function () { t.hidden = true; }, ms || 3200);
  }
  function esc(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) { return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]; }); }
  function busy(btn, on) { if (!btn) return; btn.disabled = !!on; btn.classList.toggle('ss-busy', !!on); }
  function api(method, url, body) {
    var opt = { method: method, credentials: 'same-origin', headers: { 'x-csrf-token': CSRF, 'Accept': 'application/json' } };
    if (body instanceof FormData) opt.body = body;
    else if (body !== undefined) { opt.headers['Content-Type'] = 'application/json'; opt.body = JSON.stringify(body); }
    return fetch(url, opt).then(function (r) {
      return r.json().catch(function () { return {}; }).then(function (j) {
        if (!r.ok) { var e = new Error(j.error || (r.status === 429 ? 'Slow down — try again shortly.' : r.status === 401 ? 'Please log in again.' : 'Request failed (' + r.status + ')')); e.status = r.status; throw e; }
        return j;
      });
    });
  }
  function fmtDate(t) { try { return new Date(t * 1000).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' }); } catch (e) { return ''; } }

  // ---------------------------------------------------------------- music theory
  var NOTES = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
  var FLAT = { Db: 'C#', Eb: 'D#', Gb: 'F#', Ab: 'G#', Bb: 'A#', Cb: 'B', Fb: 'E' };
  var NICE = { 'C#': 'Db', 'D#': 'Eb', 'F#': 'F#', 'G#': 'Ab', 'A#': 'Bb' };
  function pc(name) { name = FLAT[name] || name; return NOTES.indexOf(name); }
  function nm(i) { var n = NOTES[((i % 12) + 12) % 12]; return NICE[n] || n; }
  var KEYS = []; ['C', 'Db', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'Ab', 'A', 'Bb', 'B'].forEach(function (k) { KEYS.push(k + ' minor'); KEYS.push(k + ' major'); });
  function parseKey(k) {
    var m = /^\s*([A-G])([#b]?)\s*(maj(?:or)?|m(?:in(?:or)?)?)?/i.exec(String(k || ''));
    if (!m) return { root: 0, minor: true, name: 'C minor' };
    var root = pc(m[1].toUpperCase() + m[2]); if (root < 0) roo