<!-- RUBY-BACKUP · part 104 -->
 0xe8e4da, metalness: 0.3, roughness: 0.3});
  /* tabletop: 3 x 0.15 x 2 */
  var top = new THREE.Mesh(new THREE.BoxGeometry(3, 0.15, 2), pearlWhite);
  top.position.y = 1.1;
  top.userData.permanent = true;
  g.add(top);
  /* beveled edge — a thinner slab under the top for craft */
  var edge = new THREE.Mesh(new THREE.BoxGeometry(2.7, 0.08, 1.7), shadowWhite);
  edge.position.y = 1.0;
  edge.userData.permanent = true;
  g.add(edge);
  /* four legs — tapered boxes */
  var legGeo = new THREE.BoxGeometry(0.18, 1.0, 0.18);
  var legPos = [[-1.3, -0.85], [1.3, -0.85], [-1.3, 0.85], [1.3, 0.85]];
  legPos.forEach(function(p){
    var leg = new THREE.Mesh(legGeo, pearlWhite);
    leg.position.set(p[0], 0.5, p[1]);
    leg.userData.permanent = true;
    g.add(leg);
    /* little foot */
    var foot = new THREE.Mesh(new THREE.BoxGeometry(0.26, 0.06, 0.26), shadowWhite);
    foot.position.set(p[0], 0.03, p[1]);
    foot.userData.permanent = true;
    g.add(foot);
  });
  /* crossbar for strength — like real joinery */
  var bar1 = new THREE.Mesh(new THREE.BoxGeometry(2.6, 0.1, 0.12), shadowWhite);
  bar1.position.y = 0.35; bar1.userData.permanent = true; g.add(bar1);
  var bar2 = new THREE.Mesh(new THREE.BoxGeometry(0.12, 0.1, 1.7), shadowWhite);
  bar2.position.y = 0.35; bar2.userData.permanent = true; g.add(bar2);
  g.position.set(x, 0, z);
  scene.add(g);
  codeStatus('white table built — pearl finish, like her cape');
  return g;
}

/* PULL HER APART — PROVOLTA piece by piece on the white table */
var DISSECT_GROUP = null;




/* SHE BLINKS — eyes blink while she stands, blink-out when dismissed */
var HER_BLINK_TIMER = null;
var HER_EYE_MESHES = [];
function findHerEyes(group){
  HER_EYE_MESHES = [];
  try{
    group.traverse(function(o){
      if(o.isMesh){
        var n = (o.name || '').toLowerCase();
        var mn = (o.material && o.material.name ? o.material.name : '').toLowerCase();
        if(n.indexOf('eye') >= 0 || mn.indexOf('eye') >= 0 || n.indexOf('hud') >= 0){
          HER_EYE_MESHES.push(o);
          if(!o.userData.origScaleY) o.userData.origScaleY = o.scale.y;
        }
      }
    });
  }catch(e){}
}
function herBlink(){
  /* close eyes */
  HER_EYE_MESHES.forEach(function(e){
    try{ e.scale.y = e.userData.origScaleY * 0.1; }catch(err){}
  });
  setTimeout(function(){
    /* open eyes */
    HER_EYE_MESHES.forEach(function(e){
      try{ e.scale.y = e.userData.origScaleY; }catch(err){}
    });
  }, 150);
}
function startHerBlinking(){
  stopHerBlinking();
  findHerEyes(BROUGHT_HER);
  /* blink every 3-6 seconds, randomly */
  function schedule(){
    HER_BLINK_TIMER = setTimeout(function(){
      herBlink();
      schedule();
    }, 3000 + Math.random()*3000);
  }
  schedule();
  /* first blink soon */
  setTimeout(herBlink, 1200);
}
function stopHerBlinking(){
  if(HER_BLINK_TIMER){
    clearTimeout(HER_BLINK_TIMER);
    HER_BLINK_TIMER = null;
  }
  HER_EYE_MESHES = [];
}
function dismissHer(){
  /* blink-out: eyes close, she fades */
  if(!BROUGHT_HER) return;
  stopHerBlinking();
  findHerEyes(BROUGHT_HER);
  herBlink();
  var g = BROUGHT_HER;
  var op = 1;
  var fade = setInterval(function(){
    op -= 0.1;
    try{
      g.traverse(function(o){
        if(o.isMesh && o.material){
          if(!o.material.transparent) o.material.transparent = true;
          o.material.opacity = Math.max(0, op);
        }
      });
    }catch(e){}
    if(op <= 0){
      clearInterval(fade);
      try{ scene.remove(g); }catch(e){}
      if(BROUGHT_HER === g) BROUGHT_HER = null;
      var db2 = document.getElementById('dismissherbtn');
      if(db2) db2.style.display = 'none';
      var bb2 = document.getElementById('bringherbtn');
      if(bb2) bb2.style.display = 'none';
    }
  }, 50);
  codeStatus('she blinks out');
}


/* TAP-TO-CROWN — tap an object, your eyes lock on, the pad appears */


/* ruby corner handles — tap a block, rubies mark the corners */
var RUBY_HANDLES = null;
var RUBY_MIN_SIZE = (typeof window.MINIMUM!=="undefined"?window.MINIMUM:0.13)*4;

/* ruby anchor — click a ruby, the body stays in one spot, intaglio linework flows */
var ANCHORED_OBJECTS = {};
var INTAGLIO_OVERLAYS = {};
function toggleRubyAnchor(rubyMesh){
  try{
    var obj = rubyMesh.userData.anchoredObj;
    if(!obj) return;
    var objId = obj.uuid;
    if(ANCHORED_OBJECTS[objId]){
      /* unanchor */
      delete ANCHORED_OBJECTS[objId];
      if(INTAGLIO_OVERLAYS[objId]){
        try{
          obj.remove(INTAGLIO_OVERLAYS[objId]);
          delete INTAGLIO_OVERLAYS[objId];
        }catch(e){}
      }
      codeStatus('released — free to move');
    } else {
      /* anchor — body stays in one spot */
      ANCHORED_OBJECTS[objId] = {
        x: obj.position.x,
        y: obj.position.y,
        z: obj.position.z
      };
      /* each layer gets its own ultra-dense intaglio linework */
      try{
        var layers = [];
        obj.traverse(function(m){
          if(m.isMesh &&