<!-- RUBY-BACKUP · part 384 -->
 rubiEl.textContent = shapeCodeOf(u.shape||'cube');
  rubiEl.style.opacity = '1';
  if(rubiT) clearTimeout(rubiT);
  rubiT = setTimeout(function(){ rubiEl.style.opacity = '0'; }, 1200);
}

var rayc = new T.Raycaster();
var ndc = new T.Vector2();
var planeY0 = new T.Plane(new T.Vector3(0,1,0), 0);
function groundCell(clientX, clientY){
  ndc.set((clientX/window.innerWidth)*2-1, -(clientY/window.innerHeight)*2+1);
  rayc.setFromCamera(ndc, camera);
  var hitP = new T.Vector3();
  if(!rayc.ray.intersectPlane(planeY0, hitP)) return null;
  var cx = Math.round(hitP.x/CELL)*CELL, cz = Math.round(hitP.z/CELL)*CELL;
  if(Math.abs(cx)>MAP_UNITS/2-2 || Math.abs(cz)>MAP_UNITS/2-2) return null;
  if(cx===0 && cz===0) return null;          /* the ruby's ground is sacred */
  return {x:cx, z:cz};
}

/* ---- touch: drag orbits, pinch zooms, tap places / lifts ---- */
var pointers = {};
var downInfo = null;
function ptrPos(e){ return {x:e.clientX, y:e.clientY}; }
/* LONG-PRESS BUILD — hold a block to see build options */
var _lpTimer = null;
var _lpFired = false;
var _spinArmed = false;
var _spinMoved = false;
var _spinLastX = 0;
var _spinLastY = 0;
function cancelLongPress(){ if(_lpTimer){ clearTimeout(_lpTimer); _lpTimer = null; } }
/* BLOCK PICKER — pop up a button to cycle through all blocks for spinning */
var _blockList = [];
var _blockIdx = 0;
function collectBlocks(){
  _blockList = [];
  try{
    if(typeof scene!=='undefined'){
      scene.traverse(function(o){
        if(o.isMesh && o.geometry){
          var s = new THREE.Box3().setFromObject(o).getSize(new THREE.Vector3());
          var maxS = Math.max(s.x, s.y, s.z);
          if(maxS>0.5 && maxS<10) _blockList.push(o);
        }
      });
    }
  }catch(e){}
}
function showBlockPicker(){
  try{
    var old = document.getElementById('blockpicker');
    if(old) document.body.removeChild(old);
    collectBlocks();
    if(!_blockList.length) return;
    /* find current block index */
    _blockIdx = 0;
    for(var i=0;i<_blockList.length;i++){
      if(_blockList[i]===window._selectedBlock){ _blockIdx=i; break; }
    }
    var b = document.createElement('button');
    b.id = 'blockpicker';
    b.textContent = '🧱';
    b.title = 'Next block';
    b.style.cssText = 'position:fixed;right:14px;top:40%;z-index:20004;width:56px;height:56px;'
      + 'font-size:28px;background:rgba(255,215,0,0.15);border:2px solid #ffd700;border-radius:50%;cursor:pointer;';
    b.onclick = function(){
      try{
        _blockIdx = (_blockIdx+1) % _blockList.length;
        window._selectedBlock = _blockList[_blockIdx];
        try{ var pn = _blockList[_blockIdx].name || ('Block '+(_blockIdx+1)); codeStatus(pn+' ('+(_blockIdx+1)+' of '+_blockList.length+') — swipe to spin.'); }catch(e){}
        /* pulse the selected block */
        var blk = window._selectedBlock;
        if(blk.material && blk.material.emissive){
          var orig = blk.material.emissive.getHex();
          blk.material.emissive.setHex(0xffd700);
          setTimeout(function(){ try{ blk.material.emissive.setHex(orig); }catch(e){} }, 300);
        }
      }catch(e){}
    };
    document.body.appendChild(b);
    /* Os to dismiss the picker */
    var x = document.createElement('button');
    x.id = 'blockpicker-x';
    x.textContent = '\u2715';
    x.style.cssText = 'position:fixed;right:20px;top:calc(40% + 62px);z-index:20004;width:36px;height:36px;'
      + 'font-size:14px;background:#d8a920;color:#111;border:none;border-radius:50%;cursor:pointer;';
    x.onclick = function(){
      var p = document.getElementById('blockpicker'); if(p) document.body.removeChild(p);
      document.body.removeChild(x);
      _spinArmed = false;
    };
    document.body.appendChild(x);
  }catch(e){}
}
/* OPACITY SHIFTER — slide to make the block transparent */
function showOpacityShifter(){
  try{
    var old = document.getElementById('opacityshifter');
    if(old) document.body.removeChild(old);
    var blk = window._selectedBlock;
    if(!blk){ try{ codeStatus('No block selected.'); }catch(e){} return; }
    var d = document.createElement('div');
    d.id = 'opacityshifter';
    d.style.cssText = 'position:fixed;left:50%;top:50%;transform:translate(-50%,-50%);z-index:20004;'
      + 'background:rgba(10,10,15,0.95);border:2px solid #ffd700;border-radius:12px;padding:20px;'
      + 'display:flex;flex-direction:column;gap:12px;align-items:center;min-width:280px;';
    d.innerHTML = '<div style="color:#ffd700;font-size:14px;">◐ OPACITY</div>';
    var slider = document.createElement('input');
    slider.type = 'range'; slider.min = '0'; slider.max = '100'; slider.value = '100';
    slider.style.cssText = 'width:100%;accent-color:#ffd700;';
    /* get current opacity */
    try{
      if(blk.material && typeof blk.material.opacity!=='undefined'){
        slider.value = Math.round(blk.material.opacity*100);
      }
    }catch(e){}
    var label = document.createElement('div');
    label.style.cssText = 