<!-- SS · part 35 -->
sign').addEventListener('click', function () { var k = kit(); if (!k) return say('Make a kit first.'); if (!CH.slices.length) return say('Chop a sample first.'); k.pads.chop = { sample: CH.id, root: 60, start: 0, end: 1, a: 0.002, d: 0.3, s: 1, r: 0.05, gain: 0, choke: 2, slices: CH.slices }; renderPads(); persist({ kits: D.kits }).then(function () { say('Chop pad set (' + CH.slices.length + ' slices). Tick “Lead lane plays chops” and write lead notes from C4 up.'); }); });
  // templates
  function renderTpl() { $('#es-kt-tpl').innerHTML = D.templates.length ? D.templates.map(function (t) { return '<option value="' + esc(t.id) + '">' + esc(t.name) + ' · ' + t.bpm + ' BPM</option>'; }).join('') : '<option value="">No saved beats yet</option>'; }
  $('#es-kt-tsave').addEventListener('click', function () { var n = prompt('Name this beat', ES.selName() + ' beat'); if (!n) return; D.templates.push({ id: Date.now().toString(36), name: n.slice(0, 60), bpm: ES.S.bpm, patterns: JSON.parse(JSON.stringify(ES.S.sections[ES.selName()].patterns)), created: Math.floor(Date.now() / 1000) }); persist({ templates: D.templates }).then(function () { say('Beat saved to My Beats.'); }); });
  $('#es-kt-tload').addEventListener('click', function () { var t = D.templates.filter(function (x) { return x.id === $('#es-kt-tpl').value; })[0]; if (!t) return; var n = ES.selName(); ES.S.sections[n].patterns = JSON.parse(JSON.stringify(t.patterns)); if (ES.S.ai) delete ES.S.ai[n]; ES.refreshSection(); ES.save(); renderProv(); say('Loaded "' + t.name + '" into ' + n + '.'); });
  $('#es-kt-tdel').addEventListener('click', function () { var id = $('#es-kt-tpl').value; if (!id || !confirm('Delete this beat?')) return; D.templates = D.templates.filter(function (x) { return x.id !== id; }); persist({ templates: D.templates }); });
  function renderProv() { if (!ES) return; var p = provenance(); $('#es-kt-prov').innerHTML = '<p>' + esc(p.statement) + '</p><small>Saved with every export/save as part of the song file\'s project data.</small>'; }
  function boot() { $('#es-kt-on').checked = !!K.on; $('#es-kt-only').checked = !!K.only; $('#es-kt-lc').checked = !!K.leadChops; syncOnly(); refresh().catch(function (x) { say(x.message); }); }
  if (window.ES && !ES) { ES = window.ES; boot(); }
  window.addEventListener('resize', function () { setTimeout(drawChop, 150); });
  document.addEventListener('click', function (ev) { var b = ev.target.closest && ev.target.closest('.es-mtabs button[data-m="kit"]'); if (b) setTimeout(function () { renderLib(); renderProv(); drawChop(); }, 50); });
})();


===== FILE: public/ss/studio-rec.js =====
/* Epistles Studio — Record & Stems: mic takes (count-in, metronome, latency fix), uploads, trim/move/fade/volume/FX, stored in this browser (IndexedDB). Free. */
(function () {
  'use strict';
  var root = document.getElementById('es'); if (!root || !document.getElementById('es-stems')) return;
  function $(s) { return document.querySelector(s); }
  var ES = null, STEMS = [], MAXB = 25 * 1048576;
  function esc(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) { return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]; }); }
  function toast(m) { if (ES) ES.toast(m, 4000); }
  function uid() { return Date.now().toString(36) + Math.random().toString(36).slice(2, 7); }
  // ---------- IndexedDB (blobs survive reloads, stay on this device)
  var dbp = null;
  function db() { if (dbp) return dbp; dbp = new Promise(function (res, rej) { if (!window.indexedDB) return rej(new Error('no idb')); var r = indexedDB.open('es_stems', 1); r.onupgradeneeded = function () { r.result.createObjectStore('stems', { keyPath: 'id' }); }; r.onsuccess = function () { res(r.result); }; r.onerror = function () { rej(r.error); }; }); return dbp; }
  function idb(mode, fn) { return db().then(function (d) { return new Promise(function (res, rej) { var tx = d.transaction('stems', mode), st = tx.objectStore('stems'), out = fn(st); tx.oncomplete = function () { res(out && out.result); }; tx.onerror = function () { rej(tx.error); }; }); }); }
  function meta(s) { var m = {}; ['id', 'name', 'kind', 'start', 'trimIn', 'trimOut', 'fadeIn', 'fadeOut', 'vol', 'mute', 'fx', 'eq', 'norm', 'from', 'created'].forEach(function (k) { m[k] = s[k]; }); return m; }
  var putT = {};
  function persist(s, withBlob) { clearTimeout(putT[s.id]); putT[s.id] = setTimeout(function () { idb('readwrite', function (st) { return st.put({ id: s.id, meta: meta(s), blob: s.blob }); }).catch(function () {}); }, withBlob ? 0 : 300); }
  // ---------- stem model
  var FXP = { dry: { verb: 0, echo: 0, comp: 0 }, polish: { verb: 18, echo: 0, comp: 50 }, radio: { verb: 5, echo: 0, comp: 80 }, room: { verb: 35, echo: 0, comp: 30 }, echo: { verb: 15, echo: 40, comp: 30 } };
  function decode(blob) { return blob.arrayBuffer().then(function (ab) { return new Promise(function (res, rej) { Tone.getContext().rawContext.de