<!-- SS · part 108 -->
 topic = $('#es-topic').value.trim();
    if (!topic && !remix) { toast('Describe the song first.'); if (window.innerWidth < 1100) setMTab('write'); $('#es-topic').focus(); return; }
    chatAdd('me', topic || ('Remix "' + remix.title + '"'));
    busy(btn, true); busy($('#es-send'), true); busy($('#es-gen'), true);
    chatAdd('ai', 'Writing and arranging… (about 10–30 s)');
    api('POST', '/api/ss/arrange', { topic: topic, mood: $('#es-mood').value.trim(), genre: $('#es-genre').value.trim(), remix_id: remix ? remix.id : undefined })
      .then(function (j) {
        var s = j.song; S.chat.pop(); applySong(s);
        chatAdd('ai', 'Here\'s "' + s.title + '" — ' + s.bpm + ' BPM, ' + S.key + ', ' + s.arrangement.length + ' sections (' + mmss(totalSec()) + '). ' + (withVoice ? 'Voicing it now…' : 'Want a different vibe? Send another prompt.'));
        if (withVoice) return renderVocals($('#es-render')).then(function (ok) { if (ok) chatAdd('ai', 'Vocals placed. Press ▶ to hear the full song, then Export.'); });
      })
      .catch(function (e) { S.chat.pop(); chatAdd('ai', 'That didn\'t work: ' + e.message); toast(e.message, 5000); })
      .then(function () { busy(btn, false); busy($('#es-send'), false); busy($('#es-gen'), false); });
  }
  $('#es-send').addEventListener('click', function () { generate(this, false); });
  $('#es-gen').addEventListener('click', function () { generate(this, true); });

  var remixList = null;
  function drawRemix() { var q = $('#es-remix-q').value.trim().toLowerCase(); var items = (remixList || []).filter(function (s) { return !q || s.title.toLowerCase().indexOf(q) >= 0; }).slice(0, 200); $('#es-remix-list').innerHTML = items.length ? items.map(function (s) { return '<li><button type="button" data-id="' + esc(s.id) + '">' + esc(s.title) + (s.date ? '<small>' + esc(s.date) + '</small>' : '') + '</button></li>'; }).join('') : '<li class="es-hint">No songs found.</li>'; }
  function openRemix() {
    var d = $('#es-remix-dlg'); if (d.showModal) d.showModal(); else d.setAttribute('open', '');
    if (remixList) return drawRemix(); $('#es-remix-list').innerHTML = '<li class="es-hint">Loading…</li>';
    api('GET', '/api/ss/remix').then(function (j) { remixList = j.songs || []; drawRemix(); }).catch(function (e) { $('#es-remix-list').innerHTML = '<li class="es-hint">' + esc(e.message) + '</li>'; });
  }
  $('#es-remix-btn').addEventListener('click', openRemix); $('#es-remix2').addEventListener('click', openRemix);
  $('#es-remix-q').addEventListener('input', drawRemix);
  $('#es-remix-list').addEventListener('click', function (e) {
    var b = e.target.closest('button[data-id]'); if (!b) return; var s = (remixList || []).filter(function (x) { return x.id === b.getAttribute('data-id'); })[0]; if (!s) return;
    remix = { id: s.id, title: s.title }; $('#es-remix-title').textContent = s.title; $('#es-remix-box').hidden = false;
    var d = $('#es-remix-dlg'); if (d.close) d.close(); else d.removeAttribute('open');
    if (!$('#es-topic').value.trim()) { $('#es-topic').value = 'A fresh remix of "' + s.title + '"'; S.topic = $('#es-topic').value; }
    if (window.innerWidth < 1100) setMTab('write'); toast('Press Send prompt or Generate Song to remix it.');
  });
  $('#es-remix-clear').addEventListener('click', function () { remix = null; $('#es-remix-box').hidden = true; });

  // ------------------------------------------------------------------ export (offline render) + live record + save
  function wavEncode(ab, rate) {
    var ch = Math.min(2, ab.numberOfChannels), src = ab.sampleRate, len = Math.floor(ab.length * rate / src), data = [];
    for (var c = 0; c < ch; c++) data.push(ab.getChannelData(c));
    var buf = new ArrayBuffer(44 + len * ch * 2), v = new DataView(buf);
    function w(o, s) { for (var i = 0; i < s.length; i++) v.setUint8(o + i, s.charCodeAt(i)); }
    w(0, 'RIFF'); v.setUint32(4, 36 + len * ch * 2, true); w(8, 'WAVE'); w(12, 'fmt '); v.setUint32(16, 16, true); v.setUint16(20, 1, true); v.setUint16(22, ch, true);
    v.setUint32(24, rate, true); v.setUint32(28, rate * ch * 2, true); v.setUint16(32, ch * 2, true); v.setUint16(34, 16, true); w(36, 'data'); v.setUint32(40, len * ch * 2, true);
    var o = 44, ratio = src / rate;
    for (var i = 0; i < len; i++) { var si = Math.min(ab.length - 1, Math.floor(i * ratio)); for (var c2 = 0; c2 < ch; c2++) { var s = Math.max(-1, Math.min(1, data[c2][si])); v.setInt16(o, s < 0 ? s * 0x8000 : s * 0x7fff, true); o += 2; } }
    return new Blob([buf], { type: 'audio/wav' });
  }
  function projectForm(audio, name) {
    var fd = new FormData();
    fd.append('title', S.title || 'Untitled'); fd.append('lyrics', S.lyrics || ''); fd.append('bpm', S.bpm); fd.append('key', S.key);
    fd.append('patterns', JSON.stringify(S.sections.verse.patterns));
    fd.append('arrangement', JSON.stringify({ sections: S.sections, arrangement: S.arrangement }));
    fd.append('studio', JSON.stringify({ v: 2, sounds