<!-- SS · part 32 -->
tton><button type="button" class="es-btn es-btn-sm es-btn-red" id="es-kt-assign">Assign to Chop pad</button></div>' +
    '<canvas id="es-kt-cv" class="es-kt-cv"></canvas>' +
    '<h3 class="es-kt-h">MY BEATS (TEMPLATES)</h3><div class="es-sg-row"><button type="button" class="es-btn es-btn-sm" id="es-kt-tsave">Save this section\'s beat</button><select id="es-kt-tpl" aria-label="Templates"></select><button type="button" class="es-btn es-btn-sm" id="es-kt-tload">Load into section</button><button type="button" class="es-btn es-btn-sm" id="es-kt-tdel">Delete</button></div>' +
    '<h3 class="es-kt-h">PROVENANCE</h3><div class="es-kt-prov" id="es-kt-prov"></div>' +
    '<div class="es-status" id="es-kt-status">Upload or record one-shots, build a kit, then switch on “Play my kit”. Everything is stored on the server under your account.</div>';
  var NOTE = ['C', 'C#', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'Ab', 'A', 'Bb', 'B'];
  function nn(m) { return NOTE[m % 12] + (Math.floor(m / 12) - 1); }
  function sampleOpts(sel) { return '<option value="">— none —</option>' + D.samples.map(function (s) { return '<option value="' + s.id + '"' + (s.id === sel ? ' selected' : '') + '>' + esc(s.name) + '</option>'; }).join(''); }
  function rng(p, k, lab, min, max, step, fmt) { return '<label>' + lab + ' <i>' + fmt(p[k]) + '</i><input type="range" data-pk="' + k + '" min="' + min + '" max="' + max + '" step="' + step + '" value="' + p[k] + '"></label>'; }
  function f2(v) { return (+v).toFixed(2); }
  function renderPads() {
    var k = kit(), box = $('#es-kt-pads'); if (!k) { box.innerHTML = '<div class="es-hint">Make a kit first (New kit).</div>'; return; }
    box.innerHTML = PADS.map(function (P) {
      var p = k.pads[P[0]] || { sample: '', root: 60, start: 0, end: 1, a: 0.002, d: 0.2, s: 1, r: 0.08, gain: 0, choke: P[0] === 'hat' ? 1 : 0 };
      return '<div class="es-kt-pad" data-pad="' + P[0] + '"><div class="es-kt-ph"><b>' + P[1] + '</b><button type="button" class="es-btn es-btn-sm" data-pa="aud">▶</button></div><select data-pk="sample">' + sampleOpts(p.sample) + '</select>' +
        (p.sample ? '<div class="es-kt-pg">' + (P[2] ? rng(p, 'root', 'Root', 24, 96, 1, nn) : '') + rng(p, 'start', 'Start', 0, 0.99, 0.01, f2) + rng(p, 'end', 'End', 0.01, 1, 0.01, f2) + rng(p, 'a', 'A', 0, 1, 0.001, f2) + rng(p, 'd', 'D', 0, 2, 0.01, f2) + rng(p, 's', 'S', 0, 1, 0.01, f2) + rng(p, 'r', 'R', 0, 2, 0.01, f2) + rng(p, 'gain', 'Gain dB', -24, 12, 0.5, function (v) { return v; }) + rng(p, 'choke', 'Choke grp', 0, 4, 1, function (v) { return v ? v : 'off'; }) + '</div>' + (P[0] === 'chop' && p.slices ? '<div class="es-hint">' + p.slices.length + ' slices — lead notes from C4 pick slices</div>' : '') : '') + '</div>';
    }).join('');
  }
  $('#es-kt-pads').addEventListener('input', function (e) { var k = kit(), el = e.target, pk = el.getAttribute('data-pk'), padEl = el.closest('[data-pad]'); if (!k || !pk || !padEl || pk === 'sample') return; var pn = padEl.getAttribute('data-pad'), p = k.pads[pn]; if (!p) return; p[pk] = +el.value; var i = el.parentNode.querySelector('i'); if (i) i.textContent = pk === 'root' ? nn(p.root) : pk === 'gain' ? p.gain : pk === 'choke' ? (p.choke || 'off') : f2(p[pk]); dirty(); });
  $('#es-kt-pads').addEventListener('change', function (e) { var k = kit(), el = e.target; if (!k || el.getAttribute('data-pk') !== 'sample') return; var pn = el.closest('[data-pad]').getAttribute('data-pad'); if (!el.value) delete k.pads[pn]; else { k.pads[pn] = Object.assign({ root: 60, start: 0, end: 1, a: 0.002, d: 0.2, s: 1, r: 0.08, gain: 0, choke: pn === 'hat' ? 1 : 0 }, k.pads[pn] || {}, { sample: el.value }); delete k.pads[pn].slices; loadBuf(el.value).catch(function () {}); } renderPads(); dirty(); });
  $('#es-kt-pads').addEventListener('click', function (e) { if (e.target.getAttribute('data-pa') !== 'aud') return; var k = kit(), pn = e.target.closest('[data-pad]').getAttribute('data-pad'), p = k && k.pads[pn]; if (!p) return; ES.unlock().then(function () { return loadBuf(p.sample); }).then(function () { var g = new Tone.Gain(1).toDestination(); hit({}, p, p.root, Tone.now() + 0.02, 1.2, 0.9, g, pn === 'chop' && p.slices ? p.slices[0] : null); setTimeout(function () { g.dispose(); }, 4000); }).catch(function () { say('Could not play that sample.'); }); });
  var saveT = null; function dirty() { $('#es-kt-save').textContent = 'Save kit •'; renderProv(); }
  function renderLib() {
    $('#es-kt-lib').innerHTML = D.samples.length ? D.samples.map(function (s) { return '<li data-s="' + s.id + '"><input type="text" value="' + esc(s.name) + '" maxlength="60" data-sn aria-label="Sample name"><small>' + (s.source === 'recording' ? 'rec' : 'upload') + ' · ' + (s.size / 1024 | 0) + ' KB</small><button type="button" class="es-btn es-btn-sm" data-sa="play">▶</button><button type="button" class="es-btn es-btn-sm" data-sa="del">✕</button></li>'; }).join('') : '<li class="es-hint">No samples yet.</li>';
    $