<!-- RUBY-BACKUP · part 314 -->
{
    _menuPanel.style.display = _menuOpen ? 'flex' : 'none';
  }
  if(_menuBtn){
    _menuBtn.textContent = _menuOpen ? '\u2715' : '\u2630';
  }
  /* Os: collapse buttons — keyboard stays if he's typing */
  try{
    var kb = document.getElementById('kb');
    if(kb) kb.style.display = 'none';
    if(typeof showFloatButtons==='function') showFloatButtons();
  }catch(e){}
}
/* build the menu after buttons exist */
/* buildButtonMenu banished — the old buttons are banned */
/* DYNAMIC PANEL — auto-collapse after 5s no-touch, spring back when thumb nears */
var _panelIdleTimer = null;
var _panelLastTouch = Date.now();
function resetPanelIdle(){
  _panelLastTouch = Date.now();
  if(_panelIdleTimer) clearTimeout(_panelIdleTimer);
  _panelIdleTimer = setTimeout(function(){
    try{
      if(_menuOpen && typeof toggleButtonMenu==='function'){
        toggleButtonMenu();
        try{ codeStatus('Panel resting.'); }catch(e){}
      }
    }catch(e){}
  }, 5000);
}
setTimeout(function(){
  try{
    var mp = document.getElementById('buttonmenupanel');
    if(mp){
      mp.addEventListener('touchstart', resetPanelIdle, {passive:true});
      mp.addEventListener('touchmove', resetPanelIdle, {passive:true});
    }
    var mb = document.getElementById('mastermenubtn');
    if(mb) mb.addEventListener('touchstart', resetPanelIdle, {passive:true});
    /* thumb nears right edge — spring the panel back */
    document.addEventListener('touchstart', function(e){
      try{
        var t = e.touches[0];
        if(t && t.clientX > window.innerWidth - 80 && !_menuOpen){
          if(typeof toggleButtonMenu==='function') toggleButtonMenu();
          resetPanelIdle();
        }
      }catch(e){}
    }, {passive:true});
    resetPanelIdle();
  }catch(e){}
}, 7000);
/* AUTO-BUILD — the city stands when you arrive */
var CITY_AUTOBUILT = false;
function autoBuildCity(){
  if(CITY_AUTOBUILT) return;
  if(typeof scene==='undefined') return;
  CITY_AUTOBUILT = true;
  try{ localStorage.setItem('ruby-city-autobuilt', '1'); }catch(e){}
  codeStatus('building the golden city...');
  var steps = [
    {fn: 'goldenCity', delay: 1000},
    {fn: 'goldenPlaza', delay: 2500},
    {fn: 'placeRegalia', delay: 4000},
    {fn: 'placeGraffiti', delay: 5500},
    {fn: 'heavyBuild', delay: 7000},
    {fn: 'placeGuardian', delay: 8500},
    {fn: 'placePhoneCallers', delay: 10000}
  ];
  steps.forEach(function(s){
    setTimeout(function(){
      try{
        if(s.fn==='goldenCity' && typeof goldenCity==='function') goldenCity();
        else if(s.fn==='goldenPlaza' && typeof goldenPlaza==='function') goldenPlaza();
        else if(s.fn==='placeRegalia' && typeof placeRegalia==='function') placeRegalia();
        else if(s.fn==='placeGraffiti' && typeof placeGraffiti==='function') placeGraffiti();
        else if(s.fn==='heavyBuild' && typeof heavyBuild==='function') heavyBuild();
        else if(s.fn==='placeGuardian' && typeof placeGuardian==='function') placeGuardian();
        else if(s.fn==='placePhoneCallers' && typeof placePhoneCallers==='function') placePhoneCallers();
        else codeStatus('sweep: '+s.fn+' not found');
      }catch(e){ codeStatus('sweep: '+s.fn+' stumbled: '+e.message); }
    }, s.delay);
  });
  setTimeout(function(){ codeStatus('the golden city stands'); }, 11500);
}
/* trigger when scene is ready — retry until it is */
var _autoTries = 0;
var _autoTimer = setInterval(function(){
  _autoTries++;
  try{
    if(typeof scene!=='undefined' && scene){
      clearInterval(_autoTimer);
      autoBuildCity();
    }
  }catch(e){}
  if(_autoTries > 60) clearInterval(_autoTimer);
}, 1000);
(function frame(){
  requestAnimationFrame(frame);
  var dt = Math.min(clock.getDelta(), 0.05), t = clock.elapsedTime;
  /* the ruby breathes */
  var pulse = 1 + Math.sin(t*2.4)*0.12;
  ruby.scale.set(pulse, pulse, pulse);
  ruby.position.y = 1.1 + Math.sin(t*1.7)*0.12;
  rubyLight.intensity = 2.6 + Math.sin(t*2.4)*0.6;
  ring.rotation.z = t*0.25;
  /* anchor nodes breathe */
  for(var ni=0; ni<anchorGroup.children.length; ni++){
    var np = 1 + Math.sin(t*3 + ni*1.7)*0.25;
    anchorGroup.children[ni].scale.set(np, np, np);
  }
  if(cursorDot.visible){ var cp = 1 + Math.sin(t*4)*0.2; cursorDot.scale.set(cp, cp, cp); }
  /* stones rise — the whole group grows from the ground */
  for(var i=anims.length-1;i>=0;i--){
    var a = anims[i]; a.t += dt/0.55;
    var e = a.t>=1 ? 1 : Math.max(0.01, easeOutBack(a.t));
    a.m.scale.set(e,e,e);
    if(a.t>=1){ anims.splice(i,1); a.m.scale.set(1,1,1); }
  }
  /* self-destruct: the whole frame crumbles at once */
  var hadDying = dying.length > 0;
  for(var dxi=dying.length-1; dxi>=0; dxi--){
    var dx = dying[dxi]; dx.t += dt;
    var dk = Math.max(0.001, 1 - dx.t/0.4);
    dx.m.scale.set(dx.sx*dk, dx.sy*dk, dx.sz*dk);
    if(dx.t >= 0.4){ stones.remove(dx.m); dying.splice(dxi,1); }
  }
  if(hadDying && !dying.length) save();
  for(var nki=0; nki<npcKings.length; nki++){ n