<!-- SS · part 99 -->
ect.keys(d).forEach(function (k) { S[k] = d[k]; }); }
      else {
        var c = JSON.parse(localStorage.getItem('ss_state_v1') || 'null'); // classic page work carries over
        if (c && c.patterns) { ['title', 'lyrics', 'suno', 'bpm', 'key', 'chords', 'genre', 'mood', 'swing'].forEach(function (k) { if (c[k] != null) S[k] = c[k]; }); SECS.forEach(function (n) { S.sections[n].patterns = normPat(c.patterns); }); }
      }
    } catch (e) {}
    SECS.forEach(function (n) { if (!S.sections[n]) S.sections[n] = { bars: 4, patterns: emptyPat() }; S.sections[n].patterns = normPat(S.sections[n].patterns); });
    S.arrangement = (S.arrangement || []).filter(function (n) { return SECS.indexOf(n) >= 0; });
    if (!S.arrangement.length) S.arrangement = ['verse', 'chorus'];
    if (S.sel >= S.arrangement.length) S.sel = 0;
    S.mutes = S.mutes || {}; S.solos = S.solos || {};
  })();
  function selName() { return S.arrangement[S.sel] || 'verse'; }
  function selPat() { return S.sections[selName()].patterns; }

  // ------------------------------------------------------------------ timing / arrangement
  function stepSec() { return 60 / S.bpm / 4; }
  function barSec() { return stepSec() * 16; }
  function plan() { var out = [], b = 0; S.arrangement.forEach(function (n, i) { var bars = S.sections[n].bars; out.push({ i: i, name: n, start: b, bars: bars }); b += bars; }); return { items: out, bars: b }; }
  var PLAN = plan();
  function locate(bar) { for (var i = 0; i < PLAN.items.length; i++) { var it = PLAN.items[i]; if (bar >= it.start && bar < it.start + it.bars) return it; } return null; }
  function totalSec() { return PLAN.bars * barSec(); }

  // lyric blocks -> per-section vocal placements
  function blocks() {
    var out = [], cur = null;
    String(S.lyrics || '').split(/\r?\n/).forEach(function (l) {
      var t = l.trim(), m = /^\[([^\]]*)\]$/.exec(t);
      if (m) { cur = { label: m[1], type: secType(m[1]), lines: [] }; out.push(cur); return; }
      if (!t) return;
      if (!cur) { cur = { label: 'Verse', type: 'verse', lines: [] }; out.push(cur); }
      cur.lines.push(t);
    });
    return out;
  }
  function secType(label) { var l = String(label).toLowerCase(); if (/chorus|hook|refrain/.test(l)) return 'chorus'; if (/bridge|break/.test(l)) return 'bridge'; if (/intro/.test(l)) return 'intro'; if (/outro|end/.test(l)) return 'outro'; return 'verse'; }
  function vocalPlan() {
    var bl = blocks(), byType = {}, used = {}, out = [];
    bl.forEach(function (b) { (byType[b.type] = byType[b.type] || []).push(b); });
    PLAN.items.forEach(function (it) {
      var list = byType[it.name]; if (!list || !list.length) return;
      var k = used[it.name] || 0; used[it.name] = k + 1;
      var blk = list[k] || (it.name === 'chorus' ? list[list.length - 1] : null); if (!blk) return;
      var n = blk.lines.length; if (!n) return;
      var per = it.bars / n, secEnd = (it.start + it.bars) * barSec(), prevEnd = 0;
      // grid-place each line on a half-bar; once voiced, pack lines so they never overlap (spill is reported, not clipped)
      var need = blk.lines.reduce(function (m, l) { var c = VOX.clips[l]; return m + (c ? c.dur + 0.12 : 0); }, 0), tight = need > it.bars * barSec() * 0.92;
      blk.lines.forEach(function (line, j) {
        var bar = it.start + Math.round(j * per * 2) / 2, t = tight ? it.start * barSec() + 0.05 : bar * barSec(), c = VOX.clips[line];
        if (j) t = Math.max(t, prevEnd + 0.12);
        if (c) prevEnd = t + c.dur; else prevEnd = t;
        out.push({ text: line, t: t, slot: Math.max(0.3, (j < n - 1 ? (it.start + Math.round((j + 1) * per * 2) / 2) * barSec() : secEnd) - t), sec: it.i, spill: c ? Math.max(0, t + c.dur - secEnd) : 0 });
      });
    });
    return out;
  }
  function uniqueLines() { var seen = {}, out = []; vocalPlan().forEach(function (p) { if (!seen[p.text]) { seen[p.text] = 1; out.push(p.text); } }); return out; }

  // ------------------------------------------------------------------ mobile tabs / songs drawer
  function setMTab(t) { root.setAttribute('data-mtab', t); $$('.es-mtabs button').forEach(function (b) { b.classList.toggle('is-on', b.getAttribute('data-m') === t); }); if (t === 'songs') loadSongs(); if (t === 'arrange') drawTimeline(); if (t === 'voice') drawCurve(); window.scrollTo(0, 0); }
  $$('.es-mtabs button').forEach(function (b) { b.addEventListener('click', function () { setMTab(b.getAttribute('data-m')); }); });
  $('#es-songs-open').addEventListener('click', function () { if (window.innerWidth >= 1100) { root.classList.toggle('songs-open'); if (root.classList.contains('songs-open')) loadSongs(); } else setMTab('songs'); });
  $('#es-songs-close').addEventListener('click', function () { root.classList.remove('songs-open'); });

  // ------------------------------------------------------------------ AUDIO GRAPH
  function makeDrums(kit, out) {
    var d = {}, nodes = []; function k(n) { nodes.push(n); return 