<!-- RUBY-BACKUP · part 514 -->
rabPanel();
    hideGrabHint();
  }
}
function showGrabHint(){
  var h = document.createElement('div');
  h.id = 'grabhint';
  h.textContent = 'Tap a block to pick it up';
  h.style.cssText = 'position:fixed;top:80px;left:50%;transform:translateX(-50%);z-index:10001;background:#d8a920;color:#111;padding:10px 20px;border-radius:20px;font-size:14px;font-weight:bold;';
  document.body.appendChild(h);
}
function hideGrabHint(){
  var h = document.getElementById('grabhint');
  if(h) h.remove();
}
function pickUp(obj){
  _grabbed = obj;
  hideGrabHint();
  // gold glow on grabbed
  if(obj.material && obj.material.emissive){
    obj.userData._oldEmissive = obj.material.emissive.getHex();
    obj.material.emissive.setHex(0x664400);
  }
  showGrabPanel();
  codeStatus('picked up — twist and turn');
}
function dropGrabbed(){
  if(_grabbed){
    if(_grabbed.material && _grabbed.material.emissive && _grabbed.userData._oldEmissive!==undefined){
      _grabbed.material.emissive.setHex(_grabbed.userData._oldEmissive);
    }
    _grabbed = null;
  }
}
var INSTALL_MODE = false;
function startInstall(){
  if(!_grabbed){ codeStatus('pick something up first'); return; }
  INSTALL_MODE = true;
  hideGrabPanel();
  var h = document.createElement('div');
  h.id = 'installhint';
  h.textContent = 'Tap the thing to install into';
  h.style.cssText = 'position:fixed;top:80px;left:50%;transform:translateX(-50%);z-index:10001;background:#d8a920;color:#111;padding:10px 20px;border-radius:20px;font-size:14px;font-weight:bold;';
  document.body.appendChild(h);
  codeStatus('tap the target');
}
function doInstall(target){
  if(!_grabbed || !target) return;
  try{
    // attach grabbed as child of target, keeping world position
    var worldPos = new THREE.Vector3();
    _grabbed.getWorldPosition(worldPos);
    target.updateWorldMatrix(true, false);
    var localPos = target.worldToLocal(worldPos.clone());
    // remove from current parent
    if(_grabbed.parent) _grabbed.parent.remove(_grabbed);
    // add as child
    target.add(_grabbed);
    _grabbed.position.copy(localPos);
    // clear the gold glow
    if(_grabbed.material && _grabbed.material.emissive && _grabbed.userData._oldEmissive!==undefined){
      _grabbed.material.emissive.setHex(_grabbed.userData._oldEmissive);
    }
    var name = _grabbed.name || 'thing';
    _grabbed = null;
    INSTALL_MODE = false;
    var h = document.getElementById('installhint');
    if(h) h.remove();
    showGrabHint();
    codeStatus('installed');
    // undo support
    var installed = target.children[target.children.length-1];
    var targetRef = target;
    pushCommand({
      name: 'install',
      execute: function(){ targetRef.add(installed); },
      undo: function(){ targetRef.remove(installed); if(typeof scene!=='undefined') scene.add(installed); }
    });
  }catch(e){ codeStatus('install failed'); }
}
// tap to install when in install mode
if(typeof window!=='undefined'){
  window.addEventListener('click', function(e){
    if(!INSTALL_MODE || !_grabbed) return;
    try{
      if(typeof raycaster!=='undefined' && typeof camera!=='undefined' && typeof scene!=='undefined'){
        var mouse = new THREE.Vector2();
        mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
        mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
        raycaster.setFromCamera(mouse, camera);
        var hits = raycaster.intersectObjects(scene.children, true);
        for(var i=0; i<hits.length; i++){
          var o = hits[i].object;
          if(o===_grabbed) continue;
          if(o.name && (o.name.indexOf('street')===0 || o.name==='plaza' || o.name.indexOf('ground')===0)) continue;
          var target = o;
          while(target.parent && target.parent!==scene && !target.userData.isPrimitive){
            target = target.parent;
          }
          if(target!==_grabbed){
            doInstall(target);
            break;
          }
        }
      }
    }catch(err){}
  });
}
function showGrabPanel(){
  hideGrabPanel();
  var p = document.createElement('div');
  p.id = 'grabpanel';
  p.style.cssText = 'position:fixed;bottom:120px;right:14px;z-index:10001;display:flex;flex-direction:column;gap:8px;';
  var btns = [
    ['⟲', 'twist left', function(){ if(_grabbed) _grabbed.rotation.y += 0.26; }],
    ['⟳', 'twist right', function(){ if(_grabbed) _grabbed.rotation.y -= 0.26; }],
    ['⤴', 'tilt up', function(){ if(_grabbed) _grabbed.rotation.x += 0.26; }],
    ['⤵', 'tilt down', function(){ if(_grabbed) _grabbed.rotation.x -= 0.26; }],
    ['⬆', 'lift', function(){ if(_grabbed) _grabbed.position.y += 0.5; }],
    ['⬇', 'lower', function(){ if(_grabbed) _grabbed.position.y -= 0.5; }],
    ['＋', 'bigger', function(){ if(_grabbed) _grabbed.scale.multiplyScalar(1.1); }],
    ['－', 'smaller', function(){ if(_grabbed) _grabbed.scale.multiplyScalar(0.9); }],
    ['✓', 'drop', function(){ dropGrabbed(); hideGrabPanel(); showGrabHint(); codeStatus('dropped'); }],
    ['⬒', 'i