<!-- SS · part 89 -->
ow'); });
    if (step >= 0) { var it = locate(Math.floor(step / 16)); if (it && it.i === S.sel) $$('.es-cell[data-s="' + (step % 16) + '"]').forEach(function (c) { c.classList.add('now'); }); $$('.es-secbtn').forEach(function (b) { b.classList.toggle('is-play', !!it && +b.getAttribute('data-i') === it.i); }); }
    else $$('.es-secbtn.is-play').forEach(function (b) { b.classList.remove('is-play'); });
  }
  function updatePlayhead() {
    var pos = curPos(), tot = totalSec() || 1;
    $('#es-playhead').style.left = (Math.min(1, pos / tot) * 100) + '%';
    $('#es-time').textContent = mmss(pos) + ' / ' + mmss(tot);
  }
  (function raf() { if (playing) { updatePlayhead(); drawSpectrum(); } requestAnimationFrame(raf); })();

  // ------------------------------------------------------------------ timeline
  function drawTimeline() {
    PLAN = plan();
    var wrap = $('#es-tl-wrap'), cv = $('#es-tl'); var w = wrap.clientWidth, h = 250, dpr = window.devicePixelRatio || 1;
    renderSecButtons(); updatePlayhead();
    $('#es-tl-sub').textContent = 'AI-ARRANGED · ' + PLAN.items.length + ' SECTIONS · ' + PLAN.bars + ' BARS · ' + mmss(totalSec());
    if (!w) return;
    cv.width = w * dpr; cv.height = h * dpr; var g = cv.getContext('2d'); g.setTransform(dpr, 0, 0, dpr, 0, 0);
    g.fillStyle = '#050505'; g.fillRect(0, 0, w, h);
    var bars = PLAN.bars || 1, bx = w / bars, RU = 20, LH = 46;
    PLAN.items.forEach(function (it, i) {
      g.fillStyle = it.i === S.sel ? 'rgba(227,18,44,.10)' : (i % 2 ? 'rgba(255,255,255,.025)' : 'rgba(0,0,0,0)'); g.fillRect(it.start * bx, 0, it.bars * bx, h);
      g.strokeStyle = 'rgba(232,195,106,.45)'; g.beginPath(); g.moveTo(it.start * bx + .5, 0); g.lineTo(it.start * bx + .5, h); g.stroke();
    });
    g.font = '9px ui-monospace,Menlo,monospace';
    var tick = Math.max(1, Math.ceil(bars / Math.max(4, Math.floor(w / 46))));
    for (var b = 0; b <= bars; b += tick) { g.fillStyle = '#888'; g.fillText(mmss(b * barSec()), b * bx + 2, 13); g.fillStyle = '#333'; g.fillRect(b * bx, 16, 1, 4); }
    g.fillStyle = '#1b1b1b'; g.fillRect(0, RU - 1, w, 1);
    for (var li = 1; li < 5; li++) { g.fillStyle = '#141414'; g.fillRect(0, RU + li * LH, w, 1); }
    var spx = bx / 16, tot = totalSec() || 1;
    vocalPlan().forEach(function (v) {
      var c = VOX.clips[v.text], x0 = v.t / tot * w;
      if (!c) { g.fillStyle = 'rgba(255,48,72,.22)'; g.fillRect(x0, RU + 16, Math.max(2, Math.min(v.slot, 3) / tot * w - 1), LH - 32); return; }
      var cw = Math.max(2, c.dur / tot * w), pk = c.peaks; g.fillStyle = LANE_C.vocals;
      for (var x = 0; x < cw; x++) { var a = pk[Math.floor(x / cw * pk.length)] || 0, hh = Math.max(1, a * (LH - 8)); g.fillRect(x0 + x, RU + LH / 2 - hh / 2, 1, hh); }
    });
    hook('draw', g, w, RU, LH, tot);
    PLAN.items.forEach(function (it) {
      var p = S.sections[it.name].patterns;
      for (var rb = 0; rb < it.bars; rb++) {
        var bar0 = (it.start + rb) * bx;
        for (var s = 0; s < 16; s++) {
          var x = bar0 + s * spx;
          var amp = (p.drums.kick[s] ? .9 : 0) + (p.drums.snare[s] ? .7 : 0) + (p.drums.hat[s] ? .3 : 0) + (p.drums.perc[s] ? .4 : 0);
          if (amp) { var hh = Math.min(1, amp) * (LH - 10); g.fillStyle = LANE_C.drums; g.fillRect(x, RU + LH + LH / 2 - hh / 2, Math.max(1, spx * .6), hh); }
          ['bass', 'chords', 'lead'].forEach(function (t, ti) {
            var v = p[t][s]; if (!v) return; var y0 = RU + (2 + ti) * LH;
            var len = 1; for (var q = s + 1; q < 16 && !p[t][q]; q++) len++; if (t !== 'chords') len = Math.min(len, t === 'bass' ? 2 : 1);
            var mids = t === 'chords' ? chordMidis(v) : [noteMidi(v)]; if (!mids) return;
            g.fillStyle = LANE_C[t];
            mids.forEach(function (m) { if (m == null) return; var lo = t === 'bass' ? 24 : t === 'chords' ? 55 : 58, hi = t === 'bass' ? 52 : t === 'chords' ? 80 : 86; var yy = y0 + LH - 6 - (Math.max(lo, Math.min(hi, m)) - lo) / (hi - lo) * (LH - 12); g.fillRect(x, yy - 1.5, Math.max(1.5, len * spx - 1), 3); });
          });
        }
      }
    });
  }
  $('#es-tl-wrap').addEventListener('click', function (e) {
    var r = this.getBoundingClientRect(), t = (e.clientX - r.left) / r.width * totalSec();
    var it = locate(Math.floor(t / barSec())); if (it && it.i !== S.sel) { S.sel = it.i; refreshSection(); }
    seekTo(t);
  });
  function renderSecButtons() {
    $('#es-secs').innerHTML = PLAN.items.map(function (it) {
      return '<button type="button" class="es-secbtn' + (it.i === S.sel ? ' is-sel' : '') + '" data-i="' + it.i + '" style="flex-grow:' + it.bars + '">' + it.name.toUpperCase() + '<small>' + mmss(it.start * barSec()) + '–' + mmss((it.start + it.bars) * barSec()) + '</small></button>';
    }).join('');
  }
  $('#es-secs').addEventListener('click', function (e) { var b = e.target.closest('.es-secbtn'); if (!b) return; S.sel = +b.getAttribute('data-i'); refreshSection(); if (!playing) seekTo(PLAN.item