<!-- SS · part 83 -->
ong 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;
        if (s.sections && s.arrangement && s.arrangement.length) { SECS.forEach(function (n) { if (s.sections[n]) S.sections[n] = { bars: s.sections[n].bars, patterns: normPat(s.sections[n].patterns) }; }); S.arrangement = s.arrangement.slice(); }
        else { SECS.forEach(function (n) { S.sections[n].patterns = normPat(s.patterns); }); }
        ['sounds', 'vols', 'mutes', 'solos', 'master', 'voice'].forEach(function (k) { if (st[k]) S[k] = Object.assign({}, S[k], st[k]); });
        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.sel = 0; S.ai = clone(S.sections); VOX.clips = {}; seekPos = 0;
        if (E) { ['drums', 'bass', 'chords', 'lead'].forEach(function (t) { setSound(E, t, S.sounds[t]); }); applyMix(E); applyMaster(E); applyVocalFx(E); }
        syncAll(); root.classList.remove('songs-open'); if (window.innerWidth < 1100) setMTab('arrange'); toast('Opened "' + s.title + '". Render vocals to hear the voice.');
      }).catch(function (x) { toast(x.message); });
    }
  });

  // ------------------------------------------------------------------ boot
  function syncAll() { syncTop(); renderChat(); renderBlocks(); renderFaders(); syncKnobs(); syncVoiceUI(); refreshSection(); drawCurve(); idleSpectrum(); }
  syncAll(); checkUnlock();
  window.ES = { S: S, engine: function () { return E; }, unlock: unlock, play: play, stop: stop, isPlaying: function () { return playing; }, curPos: curPos, seekTo: seekTo, totalSec: totalSec, songEnd: songEnd, barSec: barSec, drawTimeline: drawTimeline, toast: toast, api: api, busy: busy, esc: esc, mmss: mmss, wavEncode: wavEncode, renderOffline: renderOffline, setMTab: setMTab, VOX: VOX, refreshSection: refreshSection, selName: selName, save: save, setSound: setSound, syncAll: syncAll, vocalPlan: vocalPlan, b64ToBuf: b64ToBuf, analyse: analyse };
  hook('ready', window.ES);
  window.addEventListener('resize', function () { clearTimeout(window.__esR); window.__esR = setTimeout(function () { drawTimeline(); drawCurve(); if (!playing) idleSpectrum(); }, 150); });
  document.addEventListener('visibilitychange', function () { if (document.hidden && playing && !recording) stop(); });
})();


===== FILE: public/ss/studio.js.bak-20261009-sales =====
/* Epistles Studio (/ss) — arrangement-based AI music studio. Tone.js (self-hosted), Gemini via /api/ss/arrange,
   Kokoro via /api/ss/voice jobs, offline WAV export via Tone.Offline. The classic page lives at /ss?classic=1. */
(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 root = $('#es'); if (!root) return;
  var IS_ADMIN = root.getAttribute('data-admin') === '1';
  var CSRF = (document.querySelector('meta[name="csrf-token"]') || {}).content || '';
  var STEPS = 16, LS_KEY = 'es_state_v1';
  var SECS = ['intro', 'verse', 'chorus', 'bridge', 'outro'];
  var LANES = ['vocals', 'drums', 'bass', 'chords', 'lead'];
  var LANE_C = { vocals: '#ff3048', drums: '#ff5a3c', bass: '#f2b632', chords: '#b46cff', lead: '#2fd6e0' };
  var DRUM_ROWS = ['kick', 'snare', 'hat', 'perc'];
  var SOUNDS = { drums: ['808', 'Lo-fi', 'Chip'], bass: ['Sub', 'Saw', 'FM'], chords: ['Pad', 'Keys', 'Pluck'], lead: ['Saw', 'Square', 'Bell'] };

  // ------------------------------------------------------------------ utils
  function toast(msg, ms) { var t = $('#es-toast'); t.textContent = msg; t.hidden = false; clearTimeout(toast._t); toast._t = setTimeout(function () { t.hidden = true; }, ms || 3400); }
  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('es-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[