<!-- SS · part 43 -->
se { ES.stop(); stopUI(); } }
  function finish() {
    ES.stop(); stopUI(); var type = (mr && mr.mimeType) || (chunks[0] && chunks[0].type) || 'audio/webm', blob = new Blob(chunks, { type: type });
    if (blob.size < 2000) { $('#es-r-status').textContent = 'Nothing was recorded.'; return; }
    decode(blob).then(function (buf) {
      var n = STEMS.filter(function (s) { return s.kind === 'mic'; }).length + 1, lat = +$('#es-r-lat').value / 1000;
      addStem(blob, buf, { name: 'Take ' + n, kind: 'mic', start: recAt, trimIn: Math.min(lat, buf.duration / 2), fx: { preset: 'polish', verb: 18, echo: 0, comp: 50, level: true } });
      $('#es-r-status').textContent = 'Take ' + n + ' saved on this device (' + buf.duration.toFixed(1) + 's). Mute or delete takes you don\'t want.';
    }).catch(function (e) { $('#es-r-status').textContent = e.message; });
  }
  $('#es-r-rec').addEventListener('click', function () { if (recOn) stopRec(); else startRec(); });
  $('#es-r-lat').addEventListener('input', function () { this._set = 1; $('#es-r-lat-v').textContent = this.value + ' ms'; });
  // ---------- boot: load saved stems, register with EQ + Wave Studio
  function boot() {
    renderStems();
    if (window.ESEQ) ESEQ.setStemApi({
      list: function () { return STEMS; },
      getEq: function (id) { var s = byId(id); if (s && !s.eq) s.eq = ESEQ.flat(); return s && s.eq; },
      setEq: function (id, e) { var s = byId(id); if (s) { s.eq = e; persist(s); } },
      rewire: function (id) { if (ES.isPlaying()) ES.play(ES.curPos()); }
    });
    idb('readonly', function (st) { return st.getAll(); }).then(function (rows) {
      if (!rows || !rows.length) return;
      return Tone.start().catch(function () {}).then(function () {
        return Promise.all(rows.map(function (r) { return decode(r.blob).then(function (buf) { var s = Object.assign({}, r.meta, { blob: r.blob, buf: buf, peaks: peaksOf(buf, 400).p }); if (!s.fx) s.fx = { preset: 'dry', verb: 0, echo: 0, comp: 0, level: true }; return s; }, function () { return null; }); }));
      }).then(function (list) { STEMS = (list || []).filter(Boolean).sort(function (a, b) { return (a.created || 0) - (b.created || 0); }); renderStems(); changed(); });
    }).catch(function () {});
  }
  if (window.ES && !ES) { ES = window.ES; boot(); }
  window.ESREC = { add: function (blob, buf, o) { return addStem(blob, buf, o); }, list: function () { return STEMS; } };
})();


===== FILE: public/ss/studio-sales.js =====
/* Epistles Studio — "Sell…" dialog for My Songs (selling settings, cover, preview). Server: lib/ss-sales.js */
(function () {
  'use strict';
  var root = document.getElementById('es'); if (!root) return;
  var IS_ADMIN = root.getAttribute('data-admin') === '1';
  function ES() { return window.ES; }
  function esc(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) { return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]; }); }
  var dlg = document.createElement('dialog'); dlg.className = 'es-dialog es-sell-dlg'; dlg.id = 'es-sell-dlg';
  root.appendChild(dlg);
  var cur = null;

  // Admin links in the My Songs drawer
  var panel = document.getElementById('es-songs-panel');
  if (panel) {
    var links = document.createElement('div'); links.className = 'es-sell-links';
    links.innerHTML = '<a class="es-link" href="/ss/store" target="_blank" rel="noopener">Song Store ↗</a>' + (IS_ADMIN ? ' · <a class="es-link" href="/ss/sales" target="_blank" rel="noopener">Sales dashboard ↗</a>' : '');
    var h = panel.querySelector('.es-h'); if (h && h.nextSibling) panel.insertBefore(links, h.nextSibling); else panel.appendChild(links);
  }

  function render(j) {
    var s = j.sale; cur = s;
    dlg.innerHTML = '<form method="dialog" class="es-dlg-in es-sell" id="es-sell-form">' +
      '<h2>Sell “' + esc(s.title) + '” <span class="es-sell-mode">' + esc(j.mode) + ' MODE</span></h2>' +
      (s.has_audio ? '' : '<div class="es-hint">This project has no audio yet — export it first to sell it.</div>') +
      '<div class="es-sell-grid">' +
      '<label class="es-sell-sw"><input type="checkbox" name="for_sale"' + (s.for_sale ? ' checked' : '') + (j.can_sell && s.has_audio ? '' : ' disabled') + '> For sale' + (j.can_sell ? '' : ' <small>(admins only)</small>') + '</label>' +
      '<label>Price (USD, min $' + esc(s.min_price) + ')<input name="price" type="number" step="0.01" min="' + esc(s.min_price) + '" max="999.99" value="' + esc(s.price) + '"></label>' +
      '<label>Visibility<select name="visibility"><option value="private"' + (s.visibility !== 'public' ? ' selected' : '') + '>Private</option><option value="public"' + (s.visibility === 'public' ? ' selected' : '') + '>Public</option></select></label>' +
      '<label>Artist<input name="artist" maxlength="80" placeholder="' + esc(s.artist_default || 'The Epistolary') + '" value="' + esc(s.artist) + '"></label>' +
      '<label>Preview starts at (sec)<inp