<!-- SS · part 20 -->
t = 0;
    var q = (m[3] || '').toLowerCase(); var minor = q ? q.charAt(0) === 'm' && q.indexOf('maj') !== 0 : /minor/i.test(k);
    return { root: root, minor: minor, name: nm(root) + (minor ? ' minor' : ' major') };
  }
  function scale(k) {
    var K = parseKey(k), iv = K.minor ? [0, 2, 3, 5, 7, 8, 10] : [0, 2, 4, 5, 7, 9, 11];
    return iv.map(function (x) { return (K.root + x) % 12; });
  }
  function scaleNotes(k, octs) {
    var sc = scale(k), K = parseKey(k), out = [];
    octs.forEach(function (o) { sc.forEach(function (p) { var oct = o + (p < K.root ? 1 : 0); out.push(nm(p) + oct); }); });
    return out;
  }
  function diatonicChords(k) {
    var sc = scale(k), out = [];
    for (var i = 0; i < 7; i++) {
      var a = sc[i], b = sc[(i + 2) % 7], c = sc[(i + 4) % 7];
      var t3 = (b - a + 12) % 12, t5 = (c - a + 12) % 12;
      out.push(nm(a) + (t3 === 3 ? (t5 === 6 ? 'dim' : 'm') : ''));
    }
    return out;
  }
  var QUAL = { '': [0, 4, 7], m: [0, 3, 7], min: [0, 3, 7], maj: [0, 4, 7], maj7: [0, 4, 7, 11], m7: [0, 3, 7, 10], '7': [0, 4, 7, 10], dim: [0, 3, 6], aug: [0, 4, 8], sus2: [0, 2, 7], sus4: [0, 5, 7], add9: [0, 4, 7, 14], '6': [0, 4, 7, 9], m6: [0, 3, 7, 9], '9': [0, 4, 7, 10, 14], m9: [0, 3, 7, 10, 14] };
  function chordNotes(sym) {
    var m = /^([A-G])([#b]?)(.*)$/.exec(String(sym || '')); if (!m) return null;
    var r = pc(m[1] + m[2]); var iv = QUAL[m[3]]; if (r < 0 || !iv) return null;
    var base = 48 + r; if (base > 55) base -= 12; // around C3..G3
    return iv.map(function (x) { var n = base + x + 12; return Tone.Frequency(n, 'midi').toNote(); });
  }

  // ---------------------------------------------------------------- state
  function emptyPatterns() {
    var z = function (v) { var a = []; for (var i = 0; i < STEPS; i++) a.push(v); return a; };
    return { drums: { kick: z(0), snare: z(0), hat: z(0) }, bass: z(null), chords: z(null), lead: z(null) };
  }
  function starterPatterns() {
    var p = emptyPatterns();
    [0, 6, 10].forEach(function (i) { p.drums.kick[i] = 1; });
    [4, 12].forEach(function (i) { p.drums.snare[i] = 1; });
    for (var i = 0; i < STEPS; i += 2) p.drums.hat[i] = 1;
    p.bass[0] = 'C2'; p.bass[6] = 'C2'; p.bass[10] = 'Eb2'; p.bass[14] = 'G2';
    p.chords[0] = 'Cm'; p.chords[8] = 'Ab';
    return p;
  }
  var S = {
    title: '', lyrics: '', suno: '', bpm: 92, key: 'C minor', chords: [], genre: '', mood: '',
    patterns: starterPatterns(), ai: null, remix: null,
    sounds: { drums: '808', bass: 'Sub', chords: 'Pad', lead: 'Saw' },
    vols: { drums: -4, bass: -6, chords: -14, lead: -12 }, mutes: { drums: false, bass: false, chords: false, lead: false },
    pens: { bass: null, chords: null, lead: null }, swing: 0.1,
  };
  function save() {
    try { localStorage.setItem(LS_KEY, JSON.stringify({ title: S.title, lyrics: S.lyrics, suno: S.suno, bpm: S.bpm, key: S.key, chords: S.chords, genre: S.genre, mood: S.mood, patterns: S.patterns, ai: S.ai, sounds: S.sounds, vols: S.vols, mutes: S.mutes, swing: S.swing })); } catch (e) {}
  }
  try { var saved = JSON.parse(localStorage.getItem(LS_KEY) || 'null'); if (saved && saved.patterns) Object.keys(saved).forEach(function (k) { S[k] = saved[k]; }); } catch (e) {}

  // ---------------------------------------------------------------- tabs
  function showTab(name) {
    $$('.ss-tab').forEach(function (b) { var on = b.getAttribute('data-tab') === name; b.classList.toggle('is-on', on); b.setAttribute('aria-selected', on ? 'true' : 'false'); });
    $$('.ss-panel').forEach(function (p) { p.classList.toggle('is-on', p.getAttribute('data-panel') === name); });
    if (name === 'voice') renderLines();
    if (name === 'songs') loadSongs();
    if (name === 'studio' || name === 'voice') checkUnlock();
    try { history.replaceState(null, '', '#' + name); } catch (e) {}
    window.scrollTo(0, 0);
  }
  $$('.ss-tab').forEach(function (b) { b.addEventListener('click', function () { showTab(b.getAttribute('data-tab')); }); });
  function updateNow() { $('#ss-now').textContent = S.title ? '♪ ' + S.title : ''; }

  // ---------------------------------------------------------------- WRITE
  function fillWrite() {
    var out = $('#ss-write-out');
    if (!S.lyrics && !S.title) { out.hidden = true; return; }
    out.hidden = false;
    $('#ss-title').value = S.title || '';
    $('#ss-lyrics').value = S.lyrics || '';
    $('#ss-suno').value = S.suno || '';
    var chips = [S.bpm + ' BPM', S.key].concat(S.chords && S.chords.length ? [S.chords.join(' – ')] : []);
    $('#ss-meta').innerHTML = chips.map(function (c) { return '<span class="ss-chip">' + esc(c) + '</span>'; }).join('');
    updateNow();
  }
  $('#ss-topic').value = S.topic || '';
  $('#ss-mood').value = S.mood || ''; $('#ss-genre').value = S.genre || '';
  $('#ss-title').addEventListener('input', function () { S.title = this.value; updateNow(); save(); });
  $('#ss-lyrics').addEventListener('input', function () { S.lyrics = th