<!-- RUBY-BACKUP · part 88 -->
 var dx = t.clientX - lastX, dy = t.clientY - lastY;
      lastX = t.clientX; lastY = t.clientY;
      try{
        if(typeof window.nudgeCamera === 'function'){
          window.nudgeCamera(dx * 5, dy * 5);
        }
      }catch(err){}
      var r = camJoy.getBoundingClientRect();
      var kx = Math.max(-28, Math.min(28, t.clientX - r.left - 50));
      var ky = Math.max(-28, Math.min(28, t.clientY - r.top - 50));
      knob.style.transform = 'translate(' + kx + 'px,' + ky + 'px)';
    }, {passive:false});
    camJoy.addEventListener('touchend', function(e){
      e.stopPropagation();
      dragging = false;
      knob.style.transform = 'translate(0,0)';
      try{ if(typeof window.snapCamera === 'function') window.snapCamera(); }catch(err){}
    }, {passive:true});
    document.body.appendChild(camJoy);
    /* Label: View */
    var viewLbl = document.createElement('div');
    viewLbl.textContent = 'View';
    viewLbl.style.cssText = 'position:fixed;right:120px;bottom:234px;width:100px;'
      + 'text-align:center;font-size:11px;font-weight:bold;color:#d8a920;'
      + 'font-family:Georgia,serif;z-index:999;pointer-events:none;letter-spacing:1px;';
    document.body.appendChild(viewLbl);

    /* Body buttons — Land, Fly, Crouch, Lay. Above the Body joystick. */
    (function addBodyButtons(){
      /* Around the Body joystick: N=Land, E=Fly, S=Crouch, W=Lay */
      var defs = [
        ['Fly', 'fly', 36, 232],     /* up — above joystick */
        ['Sonic', 'sonicboom', 36, 276], /* above Fly — King's order */
        ['Land', 'land', 36, 60],    /* bottom — below joystick */
        ['Lay', 'lay', 36, 10],      /* below Land */
        ['Crouch', 'crouch', 130, 138], /* right of joystick */
        ['Stand', 'stand', 130, 182], /* on top of Crouch */
        ['Crawl', 'crawl', 130, 94], /* below Crouch — tactical */
      ];
      defs.forEach(function(d){
        var b = document.createElement('div');
        b.textContent = d[0];
        b.style.cssText = 'position:fixed;left:' + d[2] + 'px;bottom:' + d[3] + 'px;'
          + 'min-width:64px;height:36px;line-height:36px;text-align:center;'
          + 'background:rgba(10,8,4,0.95);border:2px solid #d8a920;border-radius:18px;'
          + 'color:#ffe27a;font-size:12px;font-weight:bold;font-family:Georgia,serif;'
          + 'z-index:999;touch-action:manipulation;letter-spacing:0.5px;';
        b.addEventListener('touchend', function(e){
          e.stopPropagation(); e.preventDefault();
          try{ if(typeof window.runLine === 'function') window.runLine(d[1]); }catch(err){}
        }, {passive:false});
        b.addEventListener('click', function(e){
          e.stopPropagation();
          try{ if(typeof window.runLine === 'function') window.runLine(d[1]); }catch(err){}
        });
        document.body.appendChild(b);
      });
    })();

    /* MARQUEE EDITOR — create glorious announcements */
    window.openMarqueeEditor = function(){
      var ed = document.getElementById('marquee-editor');
      if(ed){ ed.style.display = ed.style.display === 'none' ? 'flex' : 'none'; return; }
      ed = document.createElement('div');
      ed.id = 'marquee-editor';
      ed.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);'
        + 'z-index:99997;background:rgba(10,8,4,0.95);border:2px solid #d8a920;border-radius:12px;'
        + 'padding:20px;display:flex;flex-direction:column;gap:12px;min-width:300px;';
      ed.innerHTML = '<div style="color:#ffd700;font-family:Georgia,serif;font-size:16px;letter-spacing:2px;text-align:center;">MARQUEE EDITOR</div>'
        + '<input id="mq-text" placeholder="Your words..." style="padding:10px;background:#1a1a1a;border:1px solid #d8a920;color:#fff;font-size:14px;">'
        + '<div style="display:flex;gap:8px;justify-content:center;">'
        + '<button data-style="gold" style="padding:8px 16px;background:#d8a920;color:#000;border:none;font-weight:bold;">GOLD</button>'
        + '<button data-style="power" style="padding:8px 16px;background:#ff6b35;color:#fff;border:none;font-weight:bold;">POWER</button>'
        + '<button data-style="news" style="padding:8px 16px;background:#4a90d9;color:#fff;border:none;font-weight:bold;">NEWS</button>'
        + '<button data-style="glory" style="padding:8px 16px;background:#fff;color:#000;border:none;font-weight:bold;">GLORY</button>'
        + '</div>'
        + '<button id="mq-close" style="padding:8px;background:transparent;border:1px solid #666;color:#888;">CLOSE</button>';
      document.body.appendChild(ed);
      ed.querySelectorAll('[data-style]').forEach(function(btn){
        btn.addEventListener('click', function(){
          var txt = document.getElementById('mq-text').value || 'GLORY';
          var style = btn.getAttribute('data-style');
          if(style === 'power') window.announcePower(txt);
          else if(style === 'news') window.announceNews(txt);
          else if(style === 'glory') window.a