<!-- SS · part 24 -->
;
        body += '<div class="ss-grid" data-track="' + id + '" style="margin-top:6px">';
        for (var j = 0; j < STEPS; j++) body += stepBtn(j, !!P[id][j], P[id][j] || '');
        body += '</div>';
      }
      return '<div class="ss-track' + (S.mutes[id] ? ' is-muted' : '') + '" data-t="' + id + '">' + head + body + '</div>';
    }).join('');
    $('#ss-tracks').innerHTML = html;
  }
  function syncStudio() {
    $('#ss-bpm').value = S.bpm; $('#ss-swing').value = S.swing;
    if (KEYS.indexOf(S.key) < 0) S.key = parseKey(S.key).name;
    keySel.value = S.key;
    if (E) { E.T.bpm.value = S.bpm; E.T.swing = S.swing; }
    renderTracks(); updateNow();
  }
  $('#ss-tracks').addEventListener('click', function (e) {
    var cell = e.target.closest('.ss-step'); 
    if (cell) {
      var g = cell.parentNode, track = g.getAttribute('data-track'), i = +cell.getAttribute('data-i');
      if (track === 'drums') { var r = g.getAttribute('data-row'); S.patterns.drums[r][i] = S.patterns.drums[r][i] ? 0 : 1; cell.classList.toggle('on'); }
      else {
        var cur = S.patterns[track][i];
        S.patterns[track][i] = cur ? null : S.pens[track];
        cell.classList.toggle('on', !cur); cell.textContent = cur ? '' : S.pens[track];
        if (!cur && E && Tone.getContext().state === 'running' && !playing) { try { var n = track === 'chords' ? chordNotes(S.pens[track]) : S.pens[track]; if (n) E.inst[track].triggerAttackRelease(n, '8n'); } catch (x) {} }
      }
      save(); return;
    }
    var m = e.target.closest('[data-mute]');
    if (m) { var t = m.getAttribute('data-mute'); S.mutes[t] = !S.mutes[t]; m.classList.toggle('is-on', S.mutes[t]); m.closest('.ss-track').classList.toggle('is-muted', S.mutes[t]); if (E) E.ch[t].mute = S.mutes[t]; save(); return; }
    var rg = e.target.closest('[data-regen]');
    if (rg) regen(rg.getAttribute('data-regen'), rg);
  });
  $('#ss-tracks').addEventListener('change', function (e) {
    var t = e.target;
    if (t.hasAttribute('data-snd')) setSound(t.getAttribute('data-snd'), t.value);
    else if (t.hasAttribute('data-pen')) S.pens[t.getAttribute('data-pen')] = t.value;
  });
  $('#ss-tracks').addEventListener('input', function (e) {
    var t = e.target; if (!t.hasAttribute('data-vol')) return;
    var id = t.getAttribute('data-vol'); S.vols[id] = +t.value; if (E) E.ch[id].volume.value = +t.value; save();
  });
  $('#ss-bpm').addEventListener('change', function () { S.bpm = Math.min(200, Math.max(60, Math.round(+this.value || 92))); this.value = S.bpm; if (E) E.T.bpm.value = S.bpm; save(); renderLines(); });
  $('#ss-swing').addEventListener('input', function () { S.swing = +this.value; if (E) E.T.swing = S.swing; save(); });
  keySel.addEventListener('change', function () { S.key = this.value; save(); renderTracks(); });
  $('#ss-load-ai').addEventListener('click', function () {
    if (!S.ai) { toast('Write a song with AI first (Write tab).'); return; }
    S.patterns = JSON.parse(JSON.stringify(S.ai)); save(); renderTracks(); toast('AI beat loaded.');
  });
  $('#ss-clear').addEventListener('click', function () { if (!confirm('Clear all steps?')) return; S.patterns = emptyPatterns(); save(); renderTracks(); });
  function regen(track, btn) {
    busy(btn, true);
    api('POST', '/api/ss/pattern', { track: track, key: S.key, bpm: S.bpm, chords: S.chords, genre: S.genre, mood: S.mood, current: S.patterns[track] })
      .then(function (j) { S.patterns[track] = j.pattern; save(); renderTracks(); toast(track.charAt(0).toUpperCase() + track.slice(1) + ' regenerated.'); })
      .catch(function (e) { toast(e.message, 5000); busy(btn, false); });
  }

  // ---------------------------------------------------------------- VOICE
  function lyricLines() {
    return String(S.lyrics || '').split(/\r?\n/).map(function (l) { return l.trim(); })
      .filter(function (l) { return l && !/^\[[^\]]*\]$/.test(l) && !/^\(.*\)$/.test(l); }).slice(0, 24)
      .map(function (l) { return l.slice(0, 160); });
  }
  function barSec() { return 4 * 60 / S.bpm; }
  function renderLines() {
    var lines = lyricLines(), bpl = +$('#ss-bpl').value, slot = bpl * barSec();
    var stale = S.voxClips && S.voxLines && S.voxLines.join('\n') !== lines.join('\n');
    $('#ss-lines').innerHTML = lines.length ? lines.map(function (l, i) {
      var c = S.voxClips && !stale ? S.voxClips[i] : null;
      var cls = c ? (c.dur > slot ? 'warn' : 'ok') : '';
      return '<li class="' + cls + '">' + esc(l) + (c ? '<span class="ss-ldur">' + c.dur.toFixed(1) + 's' + (c.dur > slot ? ' — longer than ' + bpl + ' bar' + (bpl > 1 ? 's' : '') + ', try speed up' : '') + '</span>' : '') + '</li>';
    }).join('') : '<li class="ss-hint">No lyrics yet — write a song on the Write tab (or type lyrics there).</li>';
    if (stale) $('#ss-vstatus').textContent = 'Lyrics changed since the last render — render again.';
  }
  $('#ss-vspeed').addEventListener('input', function () { $('#ss-vspeed-v').te