






















(function () {
  'use strict';

  
  function iconOf(a) {
    return ActIcon.of(a.type, a.state !== 'off');
  }
  
  
  var NUDGE = 1;
  var NUDGE_FAST = 10;
  


  var MIN_BAND_PX = 14;
  var MIN_MOVE_PX = 2;      
  

  var GRIP_PX = 6;
  var GRIP_MIN_CELL_PX = 12;
  var MIN_DRAG_WAIT_S = 0.1;   

  function el(tag, css, text) {
    var e = document.createElement(tag);
    if (css) e.style.cssText = css;
    if (text !== undefined) e.textContent = text;
    return e;
  }

  function fmtS(v) { return (Math.round(v * 10) / 10) + 's'; }

  function TeachTrack(hostEl, ui, opts) {
    opts = opts || {};
    this.ui = ui;                 
    this.teach = null;
    this.sel = null;              
    this._drag = null;
    
    this.onVisibility = opts.onVisibility || function () {};
    this._build(hostEl);
  }

  TeachTrack.prototype._build = function (hostEl) {
    var self = this;
    
    var root = el('div',
      'position:absolute; left:320px; right:10px; bottom:10px; z-index:6; ' +
      'background:rgba(20,22,26,.92); border:1px solid #3a4148; border-radius:6px; ' +
      'padding:8px 10px 10px; display:none; font-size:12px; color:#c8cdd2;');
    this.root = root;

    var head = el('div', 'display:flex; align-items:center; gap:8px; margin-bottom:6px;');
    this.titleEl = el('b', 'flex:0 0 auto;');
    head.appendChild(this.titleEl);
    this.hintEl = el('span', 'flex:1 1 auto; color:#8f979e; font-size:11px;');
    head.appendChild(this.hintEl);
    var close = el('button', 'flex:0 0 auto; padding:1px 8px;', '×');
    close.title = VizI18n.tr('viz_teach_track_close', '收起（Esc）');
    close.addEventListener('click', function (e) { e.stopPropagation(); self.hide(); });
    head.appendChild(close);
    root.appendChild(head);

    







    var addRow = el('div', 'position:relative; height:20px;');
    this.addBtn = el('button',
      'position:absolute; bottom:0; transform:translateX(-50%); padding:0 6px; ' +
      'line-height:1.3; font-size:12px;', '+');
    this.addBtn.title = VizI18n.tr('viz_teach_track_add', '在播放头处加动作');
    this.addBtn.addEventListener('click', function (e) {
      e.stopPropagation();
      self._openAddMenu(e.clientX, e.clientY);
    });
    addRow.appendChild(this.addBtn);
    root.appendChild(addRow);

    this.rulerEl = el('div', 'position:relative; height:14px; margin:0 0 1px;');
    root.appendChild(this.rulerEl);

    






    this.rowsEl = el('div', 'position:relative; touch-action:none;');
    root.appendChild(this.rowsEl);

    this.trackEl = el('div',
      'position:relative; height:34px; background:#22262b; border:1px solid #3a4148; ' +
      'border-radius:3px; cursor:pointer;');
    this.rowsEl.appendChild(this.trackEl);

    this.actEl = el('div',
      'position:relative; height:22px; margin-top:3px; background:#1c2025; ' +
      'border:1px solid #3a4148; border-radius:3px; cursor:pointer; display:none;');
    this.rowsEl.appendChild(this.actEl);

    this.headEl = el('div',
      'position:absolute; top:-1px; bottom:-1px; width:2px; background:#4da3ff; ' +
      'pointer-events:none; z-index:2;');
    this.rowsEl.appendChild(this.headEl);

    this.footEl = el('div', 'margin-top:2px; color:#8f979e; font-size:11px;');
    root.appendChild(this.footEl);

    this.rowsEl.addEventListener('pointerdown', function (e) {
      if (self.locked()) return;      
      


      var gripEl = e.target.closest ? e.target.closest('[data-grip]') : null;
      if (gripEl) {
        var gp = gripEl.getAttribute('data-grip').split(':');
        var gb = self._bandOf(parseInt(gp[0], 10));
        var gc = gb && gb.acts[parseInt(gp[1], 10)];
        if (gc && gc.a.type === 'wait') {
          var L0 = self._lay();
          self.sel = gc.a;
          


          self._drag = { grip: gc.a, x0: e.clientX, sec0: Number(gc.a.seconds) || 0,
                         pxPerS: L0.totalS > 0 ? L0.w / L0.totalS : 1, moved: false };
          try { self.rowsEl.setPointerCapture(e.pointerId); } catch (_) {}
          self.root.focus();
          self.addBtn.style.display = 'none';
          self._render();
          return;
        }
      }
      



      var pinEl = e.target.closest ? e.target.closest('[data-pin]') : null;
      if (pinEl) {
        var pp = pinEl.getAttribute('data-pin').split(':');
        var pb = self._bandOf(parseInt(pp[0], 10));
        var pc = pb && pb.acts[parseInt(pp[1], 10)];
        if (pc) {
          self.sel = pc.a;
          if (pb.t1 > pb.t0) {
            self._drag = { pin: pc.a, bandAt: pb.at, moved: false };
            try { self.rowsEl.setPointerCapture(e.pointerId); } catch (_) {}
            self.root.focus();
            self.addBtn.style.display = 'none';
            self._render();
            return;
          }
        }
      }
      


      var mk = e.target.closest ? e.target.closest('[data-mk]') : null;
      var handle = mk || pinEl;
      if (handle) {
        var si = mk ? parseInt(mk.getAttribute('data-mk'), 10)
                    : parseInt(pinEl.getAttribute('data-pin').split(':')[0], 10);
        var st = self._stops[si];
        
        
        var cell = e.target.closest ? e.target.closest('[data-act]') : null;
        var ai = cell ? parseInt(cell.getAttribute('data-act'), 10)
                      : (pinEl ? parseInt(pinEl.getAttribute('data-pin').split(':')[1], 10) : 0);
        self.sel = st.actions[ai] || st.actions[0];
        
        
        self._drag = { mk: handle, si: si, moved: false, lay: self._layout(si) };
      } else {
        
        
        self.sel = null;
        self._drag = { scrub: true };
        self._seekToTime(self._timeFromX(e.clientX));
      }
      
      
      
      
      try { self.rowsEl.setPointerCapture(e.pointerId); } catch (_) {}
      self.root.focus();
      self.addBtn.style.display = 'none';     
      self._render();
    });
    this.rowsEl.addEventListener('pointermove', function (e) {
      if (!self._drag) return;
      self._drag.moved = true;
      if (self._drag.scrub) {          
        self._seekToTime(self._timeFromX(e.clientX));
        self._paintHead();
        return;
      }
      if (self._drag.grip) {
        

        var g = self._drag.grip;
        var sec = self._drag.sec0 + (e.clientX - self._drag.x0) / self._drag.pxPerS;
        self.ui.setActionSeconds(self.teach, g, Math.max(MIN_DRAG_WAIT_S, sec));
        self._render();
        
        
        var gb2 = self._bandAtPoint(g.at);
        if (gb2) {
          self._seekToTime(gb2.t0 + self.ui.actionOffsetS(self.teach, g) +
                           (Number(g.seconds) || 0));
        }
        self._paintHead();
        return;
      }
      if (self._drag.pin) {
        




        var pin = self._drag.pin;
        var pb = self._bandAtPoint(self._drag.bandAt);
        var s = self._timeFromX(e.clientX);
        if (pb && s >= pb.t0 - 1e-9 && s <= pb.t1 + 1e-9) {
          if (pin.at !== pb.at) self.ui.moveAction(self.teach, pin, pb.at);  
          self.ui.setActionOffset(self.teach, pin, s - pb.t0);
          self._render();
          
          var b2 = self._bandAtPoint(self._drag.bandAt);
          if (b2) self._seekToTime(b2.t0 + self.ui.actionOffsetS(self.teach, pin));
        } else {
          var at = self._dropTool(pin, s, null);   
          self._render();
          self._seekTo(at);
        }
        self._paintHead();
        return;
      }
      var lay = self._drag.lay;
      var st = self._timeFromX(e.clientX, lay);
      var group = self._groupOf(self.sel);
      var at;
      if (group.length === 1 && self.sel && self.sel.type !== 'wait') {
        
        at = self._dropTool(self.sel, st, lay);
      } else {
        
        at = self._atFromTime(st, lay);
        group.forEach(function (a) { self.ui.moveAction(self.teach, a, at); });
      }
      
      self._render();
      self._seekTo(at);
      self._paintHead();
    });
    
    function endDrag(e) {
      if (!self._drag) return;
      self._drag = null;
      try { self.rowsEl.releasePointerCapture(e.pointerId); } catch (_) {}
      self.addBtn.style.display = '';
      self._paintHead();
    }
    this.rowsEl.addEventListener('pointerup', endDrag);
    this.rowsEl.addEventListener('pointercancel', endDrag);

    


    root.addEventListener('contextmenu', function (e) {
      e.preventDefault();
      e.stopPropagation();
      if (self.locked()) return;      
      
      var pin = e.target.closest ? e.target.closest('[data-pin]') : null;
      var mk = e.target.closest ? e.target.closest('[data-mk]') : null;
      if (pin) self._openStopMenu(parseInt(pin.getAttribute('data-pin').split(':')[0], 10),
                                  e.clientX, e.clientY,
                                  parseInt(pin.getAttribute('data-pin').split(':')[1], 10));
      else if (mk) self._openStopMenu(parseInt(mk.getAttribute('data-mk'), 10),
                                      e.clientX, e.clientY);
    });

    
    
    root.tabIndex = -1;
    root.style.outline = 'none';
    root.addEventListener('keydown', function (e) {
      if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') return;
      if (self.locked()) return;      
      var step = (e.shiftKey ? NUDGE_FAST : NUDGE) * (e.key === 'ArrowLeft' ? -1 : 1);
      e.preventDefault();
      e.stopPropagation();
      if (self.sel) {
        var group = self._groupOf(self.sel);
        var at = self.sel.at + step;
        group.forEach(function (a) { self.ui.moveAction(self.teach, a, at); });
        self._render();                 
        self._seekTo(self.sel.at);
      } else {
        self._render();
        self._seekTo(self._cur + step);
      }
      self._paintHead();
    });

    hostEl.appendChild(root);
  };

  

  TeachTrack.prototype._n = function () {
    return this.teach ? this.teach.points.length : 0;
  };

  











  TeachTrack.prototype._layout = function (skipSi) {
    var self = this, t = this.teach;
    var out = { w: 1, totalS: 0, blocks: [] };
    if (!t) return out;
    var n = this._n();
    var w = this.rowsEl.getBoundingClientRect().width || 600;
    out.w = w;
    var stops = this.ui.stops(t);
    var perPoint = 1 / t.hz / t.speed;          

    
    var seq = [], prevAt = 0;
    stops.forEach(function (st, si) {
      if (st.at > prevAt) {
        seq.push({ kind: 'move', at0: prevAt, at1: st.at,
                   sec: (st.at - prevAt) * perPoint });
      }
      var acts = st.actions.map(function (a) {
        return { a: a, sec: self.ui.actionDwellS(a) };
      });
      var sec = (si === skipSi) ? 0 : acts.reduce(function (s, x) { return s + x.sec; }, 0);
      seq.push({ kind: 'dwell', si: si, at: st.at, sec: sec, acts: acts });
      prevAt = st.at;
    });
    if (prevAt < n - 1) {
      seq.push({ kind: 'move', at0: prevAt, at1: n - 1,
                 sec: (n - 1 - prevAt) * perPoint });
    }
    if (!seq.length) {          
      seq.push({ kind: 'move', at0: 0, at1: 0, sec: 0 });
    }

    
    var totalS = seq.reduce(function (s, b) { return s + b.sec; }, 0);
    out.totalS = totalS;
    var scale = totalS > 0 ? w / totalS : 0;
    var deficit = 0, movePx = 0;
    seq.forEach(function (b) {
      b.px = b.sec * scale;
      if (b.kind === 'dwell') {
        if (b.sec > 0 && b.px < MIN_BAND_PX) {
          deficit += MIN_BAND_PX - b.px;
          b.px = MIN_BAND_PX;
        }
      } else {
        movePx += b.px;
      }
    });
    if (deficit > 0 && movePx > 0) {
      var k = Math.max(0, movePx - deficit) / movePx;
      seq.forEach(function (b) {
        if (b.kind === 'move') b.px = Math.max(MIN_MOVE_PX, b.px * k);
      });
    }
    
    var sumPx = seq.reduce(function (s, b) { return s + b.px; }, 0);
    var norm = sumPx > 0 ? w / sumPx : 0;

    
    var accX = 0, accT = 0;
    seq.forEach(function (b) {
      var x0 = accX / w, x1 = (accX + b.px * norm) / w;
      b.x0 = x0; b.x1 = x1;
      b.t0 = accT; b.t1 = accT + b.sec;
      if (b.kind === 'dwell') {
        var offS = 0, offX = x0;
        b.acts.forEach(function (c) {
          c.off = offS;
          c.x0 = offX;
          
          c.x1 = b.sec > 0 ? offX + (c.sec / b.sec) * (x1 - x0) : offX;
          offS += c.sec;
          offX = c.x1;
        });
      }
      accX += b.px * norm;
      accT += b.sec;
      out.blocks.push(b);
    });
    
    if (out.blocks.length) out.blocks[out.blocks.length - 1].x1 = 1;
    return out;
  };

  

  TeachTrack.prototype._lay = function () {
    if (!this._layCache) this._layCache = this._layout();
    return this._layCache;
  };

  

  TeachTrack.prototype._blockAtTime = function (L, s) {
    var bs = L.blocks;
    for (var i = 0; i < bs.length; i++) {
      if (s <= bs[i].t1 + 1e-9) return bs[i];
    }
    return bs[bs.length - 1] || null;
  };

  TeachTrack.prototype._blockAtX = function (L, f) {
    var bs = L.blocks;
    for (var i = 0; i < bs.length; i++) {
      if (f <= bs[i].x1 + 1e-9) return bs[i];
    }
    return bs[bs.length - 1] || null;
  };

  TeachTrack.prototype._xOfTime = function (s, L) {
    L = L || this._lay();
    var b = this._blockAtTime(L, Math.max(0, Math.min(L.totalS, s)));
    if (!b) return 0;
    var k = b.t1 > b.t0 ? (Math.max(b.t0, Math.min(b.t1, s)) - b.t0) / (b.t1 - b.t0) : 0;
    return b.x0 + k * (b.x1 - b.x0);
  };

  TeachTrack.prototype._timeFromX = function (clientX, L) {
    L = L || this._lay();
    var r = this.rowsEl.getBoundingClientRect();
    var f = Math.max(0, Math.min(1, (clientX - r.left) / Math.max(1, r.width)));
    var b = this._blockAtX(L, f);
    if (!b) return 0;
    var k = b.x1 > b.x0 ? (f - b.x0) / (b.x1 - b.x0) : 0;
    return b.t0 + Math.max(0, Math.min(1, k)) * (b.t1 - b.t0);
  };

  
  TeachTrack.prototype._atFromTime = function (s, L) {
    L = L || this._lay();
    var b = this._blockAtTime(L, s);
    if (!b) return 0;
    if (b.kind === 'dwell') return b.at;
    var k = b.t1 > b.t0 ? (s - b.t0) / (b.t1 - b.t0) : 0;
    return Math.max(0, Math.min(this._n() - 1,
      Math.round(b.at0 + Math.max(0, Math.min(1, k)) * (b.at1 - b.at0))));
  };

  
  TeachTrack.prototype._timeOfAt = function (at, L) {
    L = L || this._lay();
    var bs = L.blocks, i;
    for (i = 0; i < bs.length; i++) {
      if (bs[i].kind === 'dwell' && bs[i].at === at) return bs[i].t0;
    }
    for (i = 0; i < bs.length; i++) {
      if (bs[i].kind === 'move' && at >= bs[i].at0 && at <= bs[i].at1) {
        var k = bs[i].at1 > bs[i].at0 ? (at - bs[i].at0) / (bs[i].at1 - bs[i].at0) : 0;
        return bs[i].t0 + k * (bs[i].t1 - bs[i].t0);
      }
    }
    return 0;
  };

  
  TeachTrack.prototype._bandOf = function (si) {
    var bs = this._lay().blocks;
    for (var i = 0; i < bs.length; i++) {
      if (bs[i].kind === 'dwell' && bs[i].si === si) return bs[i];
    }
    return null;
  };

  


  TeachTrack.prototype._bandAtPoint = function (at) {
    var bs = this._lay().blocks;
    for (var i = 0; i < bs.length; i++) {
      if (bs[i].kind === 'dwell' && bs[i].at === at && bs[i].t1 > bs[i].t0) return bs[i];
    }
    return null;
  };

  


  TeachTrack.prototype._dropTool = function (a, s, lay) {
    var at = this._atFromTime(s, lay);
    this.ui.moveAction(this.teach, a, at);
    var nb = this._bandAtPoint(at);
    if (nb) this.ui.setActionOffset(this.teach, a, s - nb.t0);
    return at;
  };

  

  TeachTrack.prototype._bandPos = function () {
    var L = this._lay();
    var f = this._xOfTime(this._t || 0, L);
    var b = this._blockAtX(L, f);
    if (!b || b.kind !== 'dwell' || !(b.x1 > b.x0)) return null;
    var k = 0;
    b.acts.forEach(function (c) {
      if (f > (c.x0 + c.x1) / 2) k++;            
    });
    return { si: b.si, k: k, block: b };
  };

  
  TeachTrack.prototype._groupOf = function (a) {
    if (!a || !this.teach) return [];
    return (this.teach.actions || []).filter(function (x) { return x.at === a.at; });
  };

  













  TeachTrack.prototype.locked = function () {
    return this.ui.editLocked();
  };

  

  TeachTrack.prototype.syncLock = function () {
    if (!this.visible() || !this.teach) return;
    var lk = this.locked();
    if (lk === this._lockedNow) return;
    if (lk) this._drag = null;        
    this._render();
  };

  

  
  TeachTrack.prototype._livePlaying = function () {
    return !!(this.teach && this.ui.running === this.teach && this.ui.player.playing);
  };

  TeachTrack.prototype.show = function (t, focusAction) {
    this.teach = t;
    this.sel = focusAction || null;
    this._layCache = null;
    this.root.style.display = 'block';       
    this.root.focus();
    









    if (this._livePlaying()) {
      this.followPlayback(this.ui.player.elapsedS());
    } else {
      this._cur = focusAction ? focusAction.at : 0;
      this._t = 0;
      this._seekTo(this._cur);
    }
    this._render();
    this.onVisibility(true, t && t.name);
  };

  TeachTrack.prototype.hide = function () {
    this.root.style.display = 'none';
    this.teach = null;
    this.sel = null;
    this._drag = null;
    this._layCache = null;
    this.onVisibility(false, '');
  };

  TeachTrack.prototype.visible = function () {
    return this.root.style.display !== 'none';
  };

  
  TeachTrack.prototype.render = function () {
    if (this.visible() && this.teach) this._render();
  };

  





  
  TeachTrack.prototype._seekTo = function (at) {
    var t = this.teach;
    if (!t) return;
    at = Math.max(0, Math.min(this._n() - 1, Math.round(at) || 0));
    this._cur = at;
    this._t = this._timeOfAt(at);
    this._applyPose();
  };

  

  TeachTrack.prototype._seekToTime = function (s) {
    var t = this.teach;
    if (!t) return;
    var L = this._lay();
    this._t = Math.max(0, Math.min(L.totalS, s));
    this._cur = this._atFromTime(this._t, L);
    this._applyPose();
  };

  TeachTrack.prototype._applyPose = function () {
    var t = this.teach;
    
    if (this.ui.running === t && this.ui.player.playing) this.ui.seek(this._cur);
    else this.ui.onJoints(t.points[this._cur].slice());
  };

  

  TeachTrack.prototype.followPlayback = function (elapsedS) {
    if (!this.visible() || this._drag) return;
    var L = this._lay();
    this._t = Math.max(0, Math.min(L.totalS, elapsedS || 0));
    this._cur = this._atFromTime(this._t, L);
    this._paintHead();
  };

  

  TeachTrack.prototype._openAddMenu = function (x, y) {
    var self = this, t = this.teach;
    if (!t || this.locked()) return;
    var items = [{
      label: VizI18n.tr('viz_act_wait', '延时'),
      onClick: function () { self._add('wait'); }
    }];
    var tool = this.ui.getToolKind();
    if (tool === 'gripper') {
      items.push({
        label: VizI18n.tr('viz_act_gripper', '夹爪开合'),
        onClick: function () { self._add('gripper'); }
      });
    } else if (tool === 'suction') {
      


      items.push({
        label: VizI18n.tr('viz_act_vacuum', '吸盘吸/放'),
        children: [
          { label: VizI18n.tr('viz_suction_vacuum', '吸附'),
            onClick: function () { self._add('vacuum', { state: 'on' }); } },
          { label: VizI18n.tr('viz_act_release', '放开'),
            onClick: function () { self._add('vacuum', { state: 'off' }); } }
        ]
      });
    }
    this.ui.showMenu(items, x, y);
  };

  

  TeachTrack.prototype._actLabel = function (a) {
    if (a.type === 'wait') {
      return iconOf(a) + ' ' + VizI18n.tr('viz_act_wait', '延时') + ' ' +
             fmtS(a.seconds || 0);
    }
    if (a.type === 'gripper') {
      return iconOf(a) + ' ' + VizI18n.tr('viz_act_gripper', '夹爪开合') + ' ' +
             (a.angle_deg || 0) + '°';
    }
    return iconOf(a) + ' ' + (a.state === 'off'
      ? VizI18n.tr('viz_act_release', '放开')
      : VizI18n.tr('viz_suction_vacuum', '吸附'));
  };

  

  TeachTrack.prototype._openStopMenu = function (i, x, y, focusIdx) {
    var self = this, st = (this._stops || [])[i];
    if (!st || !this.teach) return;
    
    this.sel = st.actions[focusIdx >= 0 ? focusIdx : 0] || st.actions[0];
    this._seekTo(st.at);
    this._render();

    var acts = st.actions.slice();
    var items = acts.map(function (a) {
      return {
        label: VizI18n.tr('viz_teach_track_del_act', '删除 {a}')
                 .replace('{a}', self._actLabel(a)),
        onClick: function () { self._del([a]); }
      };
    });
    if (acts.length > 1) {
      items.push({ sep: true });
      items.push({
        label: VizI18n.tr('viz_teach_track_del_stop', '删除这一站的 {n} 个动作')
                 .replace('{n}', String(acts.length)),
        onClick: function () { self._del(acts); }
      });
    }
    this.ui.showMenu(items, x, y);
  };

  TeachTrack.prototype._del = function (list) {
    var self = this;
    list.forEach(function (a) { self.ui.removeAction(self.teach, a); });
    this.sel = null;
    this._render();
  };

  


  TeachTrack.prototype._add = function (kind, init) {
    var t = this.teach;
    if (!t || this.locked()) return;
    var pos = this._bandPos();
    var order;
    if (pos) {
      var first = (t.actions || []).indexOf(this._stops[pos.si].actions[0]);
      if (first >= 0) order = first + pos.k;
    }
    var a = this.ui.addAction(t, this._cur, kind, order, init);
    if (a) this.sel = a;
    


    if (a && pos && kind !== 'wait') {
      this.ui.setActionOffset(t, a, this._t - pos.block.t0);
    }
    this._render();
  };

  

  TeachTrack.prototype._paintHead = function () {
    var t = this.teach;
    if (!t) return;
    var L = this._lay();
    var pct = this._xOfTime(this._t || 0, L) * 100;
    this.headEl.style.left = pct + '%';
    this.addBtn.style.left = pct + '%';
    
    var b = this._blockAtTime(L, this._t || 0);
    if (b && b.kind === 'dwell' && b.t1 > b.t0) {
      this.hintEl.textContent =
        VizI18n.tr('viz_teach_track_pos_dwell', '第 {i}/{n} 点 · 停顿 {d}/{t}')
          .replace('{i}', this._cur).replace('{n}', this._n() - 1)
          .replace('{d}', fmtS(this._t - b.t0)).replace('{t}', fmtS(b.t1 - b.t0));
      return;
    }
    this.hintEl.textContent =
      VizI18n.tr('viz_teach_track_pos', '第 {i}/{n} 点 · {s}')
        .replace('{i}', this._cur).replace('{n}', this._n() - 1)
        .replace('{s}', fmtS(this._t || 0));
  };

  

  TeachTrack.prototype._cellText = function (c, px) {
    var icon = iconOf(c.a);
    if (px < 26) return icon;
    if (c.a.type === 'wait') return icon + ' ' + fmtS(c.sec);
    if (c.a.type === 'gripper') return icon + ' ' + (c.a.angle_deg || 0) + '°';
    return icon;
  };

  TeachTrack.prototype._render = function () {
    var self = this, t = this.teach;
    if (!t) return;
    
    
    var lk = this.locked();
    this._lockedNow = lk;
    this.titleEl.textContent = t.name + (lk ? ' 🔒' : '');
    this.addBtn.style.display = (lk || this._drag) ? 'none' : '';
    this.trackEl.style.cursor = lk ? 'not-allowed' : 'pointer';
    this.actEl.style.cursor = lk ? 'not-allowed' : 'pointer';
    this._layCache = null;                 
    var L = this._lay();
    this._stops = this.ui.stops(t);

    


    var totalS = L.totalS;
    var w = L.w;
    var stepS = Math.max(1, Math.ceil(totalS / Math.max(1, Math.floor(w / 60))));
    this.rulerEl.innerHTML = '';
    for (var s = 0; s <= totalS + 1e-6; s += stepS) {
      var k = this._xOfTime(s, L) * 100;
      var tick = el('div',
        'position:absolute; bottom:0; left:' + k + '%; width:1px; height:5px; ' +
        'background:#4a5158;');
      this.rulerEl.appendChild(tick);
      var lb = el('div',
        'position:absolute; bottom:5px; left:' + k + '%; transform:translateX(-50%); ' +
        'color:#6b7883; font-size:10px; white-space:nowrap;', s + 's');
      this.rulerEl.appendChild(lb);
    }

    







    this.trackEl.innerHTML = '';
    this.actEl.innerHTML = '';
    
    
    
    this.actEl.style.display = L.blocks.some(function (b) {
      return b.kind === 'dwell' && b.t1 > b.t0;
    }) ? '' : 'none';
    var mkTitle = VizI18n.tr('viz_teach_track_mk', '拖动改位置；← → 逐点微调；右键删除');

    var pinTitle = VizI18n.tr('viz_teach_track_pin', '拖动改触发时刻；右键删除');
    L.blocks.forEach(function (b) {
      if (b.kind !== 'dwell') return;
      var wide = b.x1 > b.x0;

      if (wide) {
        

        var gapEl = el('div',
          'position:absolute; top:0; bottom:0; pointer-events:none; box-sizing:border-box; ' +
          'left:' + (b.x0 * 100) + '%; width:' + ((b.x1 - b.x0) * 100) + '%; ' +
          'background:repeating-linear-gradient(-45deg, ' +
          'rgba(255,204,68,.16) 0 6px, rgba(255,204,68,.05) 6px 12px);');
        gapEl.title = VizI18n.tr('viz_teach_track_pause', '停顿 {s}')
          .replace('{s}', fmtS(b.t1 - b.t0));
        self.trackEl.appendChild(gapEl);

        



        var band = el('div',
          'position:absolute; top:2px; bottom:2px; overflow:hidden; ' +
          'cursor:' + (lk ? 'not-allowed' : 'grab') + '; ' +
          'box-sizing:border-box; display:flex; border-radius:3px; font-size:11px; ' +
          'white-space:nowrap; ' +
          'left:' + (b.x0 * 100) + '%; width:' + ((b.x1 - b.x0) * 100) + '%; ' +
          'background:rgba(255,204,68,.18); border:1px solid #ffcc44;');
        band.setAttribute('data-mk', String(b.si));
        band.title = mkTitle;
        b.acts.forEach(function (c, j) {
          if (!(c.sec > 0)) return;               
          var picked = self.sel === c.a;
          var cellPx = (c.x1 - c.x0) * w;
          var cell = el('div',
            
            'position:relative; flex:0 0 auto; ' +
            'width:' + ((c.x1 - c.x0) / (b.x1 - b.x0) * 100) + '%; ' +
            'display:flex; align-items:center; justify-content:center; overflow:hidden; ' +
            'border-left:' + (j ? '1px solid rgba(255,204,68,.5)' : '0') + '; ' +
            'background:' + (picked ? '#ffcc44' : 'transparent') + '; ' +
            'color:' + (picked ? '#221a00' : '#ffd977') + ';',
            self._cellText(c, cellPx));
          cell.setAttribute('data-act', String(j));
          



          if (!lk && c.a.type === 'wait' && cellPx >= GRIP_MIN_CELL_PX) {
            var grip = el('div',
              'position:absolute; top:0; bottom:0; right:0; width:' + GRIP_PX + 'px; ' +
              'cursor:col-resize; z-index:1; ' +
              'background:linear-gradient(90deg, rgba(255,204,68,0), rgba(255,204,68,.55));');
            grip.setAttribute('data-grip', b.si + ':' + j);
            grip.title = VizI18n.tr('viz_teach_track_grip', '拖这条边改延时长短');
            cell.appendChild(grip);
          }
          band.appendChild(cell);
        });
        self.actEl.appendChild(band);
      }

      


      b.acts.forEach(function (c, j) {
        if (c.a.type === 'wait') return;
        var picked = self.sel === c.a;
        var pin = el('div',
          'position:absolute; top:0; bottom:0; width:0; z-index:1; ' +
          'cursor:' + (lk ? 'not-allowed' : 'grab') + '; ' +
          'left:' + (self._xOfTime(b.t0 + c.off, L) * 100) + '%;');
        pin.setAttribute('data-pin', b.si + ':' + j);
        pin.title = wide ? pinTitle : mkTitle;
        pin.appendChild(el('div',
          'position:absolute; top:0; bottom:0; left:-4px; width:9px;'));   
        pin.appendChild(el('div',
          'position:absolute; top:0; bottom:0; width:2px; transform:translateX(-1px); ' +
          'background:' + (picked ? '#4da3ff' : '#ffcc44') + ';'));
        pin.appendChild(el('div',
          'position:absolute; top:2px; transform:translateX(-50%); padding:0 3px; ' +
          'border-radius:3px; font-size:11px; line-height:1.4; white-space:nowrap; ' +
          'background:' + (picked ? '#4da3ff' : '#2b2f36') + '; ' +
          'color:' + (picked ? '#10131a' : '#ffcc44') + '; ' +
          'border:1px solid ' + (picked ? '#8ac4ff' : '#ffcc44') + ';',
          iconOf(c.a)));
        self.trackEl.appendChild(pin);
      });
    });

    this._paintHead();
    var dwellTotal = this._stops.reduce(function (acc, st) { return acc + st.dwell; }, 0);
    
    this.footEl.style.color = lk ? '#ffcc44' : '#8f979e';
    if (lk) {
      this.footEl.textContent = VizI18n.tr('viz_edit_locked',
        '正在运行：这一段只能看，不能改。真机跑的是按下播放那一刻的副本。要改先停下来。');
      return;
    }
    this.footEl.textContent = this._stops.length
      ? VizI18n.tr('viz_teach_track_foot',
          '{n} 个动作点，停顿共 {s}；下面一条是停顿轨，一条带一次停顿、一格一条动作；'
          + '拖带改位置、拖格子右边缘改延时长短，← → 逐点微调（Shift 跳 10 点）')
          .replace('{n}', String(this._stops.length))
          .replace('{s}', fmtS(dwellTotal))
      : VizI18n.tr('viz_teach_track_empty',
          '拖动轨道定位到要停的那一帧（← → 逐点微调），再点播放头上面的「+」');
  };

  window.TeachTrack = TeachTrack;
})();
