<!-- RUBY-BACKUP · part 287 -->
Listener('click', function(e){
  e.stopPropagation();
  codeOpen = !codeOpen;
  cursorDot.visible = codeOpen;
  if(codeOpen){ kbUse('code'); chatOpen = false; codeTextShow(); }
  else codeTextHide();
  syncDock();
});
if(codeInput) codeInput.addEventListener('keydown', function(e){
  e.stopPropagation();
  if(e.key==='Enter'){ var v = codeInput.value; codeInput.value=''; if(v.trim()) Mainframe.run(v); }
  if(e.key==='Escape'){ codeOpen = false; codeTextHide(); cursorDot.visible=false; syncDock(); }
});
/* hide floating buttons when the keyboard is up */
function hideFloatButtons(){
  /* keyboard up: move the menu button and its panel above the keyboard */
  /* measure the actual keyboard height */
  var kb = document.getElementById('kb');
  var kbH = kb ? kb.offsetHeight : 300;
  var lift = kbH + 16;
  var mb = document.getElementById('mastermenubtn');
  if(mb) mb.style.bottom = lift + 'px';
  var mp = document.getElementById('buttonmenupanel');
  if(mp) mp.style.bottom = (lift + 58) + 'px';
  var cb = document.getElementById('camerabtn');
  if(cb && cb.style.opacity !== '0.5') cb.style.bottom = lift + 'px';
  var cp = document.getElementById('camerapanel');
  if(cp) cp.style.bottom = (lift + 58) + 'px';
}
function showFloatButtons(){
  /* keyboard down: move them back */
  var mb = document.getElementById('mastermenubtn');
  if(mb) mb.style.bottom = '14px';
  var mp = document.getElementById('buttonmenupanel');
  if(mp) mp.style.bottom = '72px';
  var cb = document.getElementById('camerabtn');
  if(cb) cb.style.bottom = '14px';
  var cp = document.getElementById('camerapanel');
  if(cp) cp.style.bottom = '72px';
}
if(codeInput){ codeInput.addEventListener('focus', hideFloatButtons);
codeInput.addEventListener('blur', showFloatButtons); }

/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   MARK: MAINFRAME — the code is the mainframe; the world ties in.
   Every mutation of the world flows through here: taps, the command
   line, procedures. The world renders and forwards touch; the
   Mainframe decides what stands.
   EDIT: the surface the world may call.
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
var Mainframe = {
  run:    runLine,                                            /* a line of code */
  parse:  function(line){ var ec={x:cursor.x, z:cursor.z}; return parseLine(line, ec); },
  place:  function(x, z){ return placeStone(x, z, 1, curShapeKey, false); },  /* seed */
  grow:   function(g){ growStone(g); },
  shrink: function(g){ shrinkStone(g); }
};

/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   MARK: HIS SKY — rain and clouds, by the Rain Law:
   rain falls ONLY when the whole sky holds clouds, no emptiness,
   density in totality. `rain` toggles the Law; `clouds` toggles clouds alone.
   EDIT: cluster count, drift, drop count. iPhone-light by design.
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
var skyGrp = null, cloudPuffs = [], rainPts = null, rainVel = null, starPts = null, dawnGrp = null, emberPts = null, emberVel = null, auroras = [];
var boltLine = null, boltBranch = null, boltGlow = null, boltLight = null, boltT = 0, nextBolt = 5, flashT = 0, skyBgHex = 0x0b0906, WHITEOUT_HELD = false, twinMarks = [], twinGrp = null;
var FLOOR_Y = 0, floorMesh = null;
var FLOOR_KEY = 'ruby-floor-v1';
function floorTop(){ return floorMesh ? 0.26 : 0; }
/* PERMANENT FLOOR — loads at start, never removed, like the buttons */
function ensurePermanentFloor(){
  try{
    if(typeof buildFloorMesh==='function'){
      buildFloorMesh();
      setTimeout(function(){
        try{
          if(typeof floorMesh==='undefined' || !floorMesh){
            buildFloorMesh();
          } else {
            var _in = false;
            if(typeof scene!=='undefined'){
              scene.traverse(function(o){ if(o===floorMesh) _in = true; });
              if(!_in) scene.add(floorMesh);
            }
          }
        }catch(e){}
      }, 3000);
    }
  }catch(e){}
}
if(typeof window!=='undefined'){
  window.addEventListener('load', function(){ setTimeout(ensurePermanentFloor, 500); });
  setTimeout(function(){ try{ ensurePermanentFloor(); }catch(e){} }, 1000);
}
function buildFloorMesh(){
  /* the stone layer — ONE seamless whole, no grid, grass merges at every edge */
  if(floorMesh) return;
  var _size = (typeof MAP_UNITS!=='undefined' ? MAP_UNITS : 1200);
  // single plane with REAL topography — rolling hills, valleys, not flat
  var geo = new T.PlaneGeometry(_size, _size, 128, 128);
  geo.rotateX(-Math.PI/2);
  var _pos = geo.attributes.position;
  for(var vi=0; vi<_pos.count; vi++){
    var vx = _pos.getX(vi), vz = _pos.getZ(vi);
    // layered noise for natural terrain
    var h = Math.sin(vx*0.01)*Math.cos(vz*0.01)*8
          + Math.sin(vx*0.03+1.7)*Math.cos(vz*0.025+0.6)*4
          + Math.sin(vx*0.08+4.2)*Math.cos(vz*0.07+2.1)*1.5;
    // keep the center (spawn/plaza) relatively flat
    var dc = Math.sqrt(