<!-- RUBY-BACKUP · part 510 -->
tality, and the rain falls' : name==='cosmic' ? 'cosmic — deep violet, stars burning' : name==='dawn' ? 'dawn — the island wakes' : name==='ember' ? 'ember — the world breathes fire' : name==='aurora' ? 'aurora — ribbons of light' : 'cave — the dark closes in');
}
function skySet(cop){
  /* the Law: rain only under totality */
  skyBuild();
  var show = CLOUDS_ON || RAIN_ON || DAWN_ON || EMBER_ON || AURORA_ON || starPts.visible;
  skyGrp.visible = show;
  rainPts.visible = RAIN_ON;
  var i;
  for(i=0;i<cloudPuffs.length;i++){
    var m = cloudPuffs[i];
    m.visible = CLOUDS_ON;
    m.material.opacity = (typeof cop === 'number') ? cop : (RAIN_ON ? 0.62 : 0.42);
  }
}
function skyTick(dt){
  if(!skyGrp || !skyGrp.visible) return;
  var i;
  for(i=0;i<cloudPuffs.length;i++){
    var m = cloudPuffs[i];
    m.position.x += dt*(1.1 + (i%3)*0.35);
    if(m.position.x > 62) m.position.x = -62;
  }
  if(AURORA_ON){
    var at = clock.elapsedTime;
    for(var axi=0; axi<auroras.length; axi++){
      var am2 = auroras[axi], ap = am2.geometry.attributes.position, ab = am2.userData.base, aph = am2.userData.ph;
      for(var axj=0; axj<ap.count; axj++){
        var bx0 = ab[axj*3];
        ap.setY(axj, ab[axj*3+1] + Math.sin(bx0*0.09 + at*1.8 + aph)*2.2);
        ap.setZ(axj, ab[axj*3+2] + Math.cos(bx0*0.07 + at*1.3 + aph)*1.4);
      }
      ap.needsUpdate = true;
    }
  }
  if(EMBER_ON && emberPts.visible){
    var epos2 = emberPts.geometry.attributes.position, en = epos2.count;
    for(i=0;i<en;i++){
      var ey = epos2.getY(i) + emberVel[i]*dt;
      if(ey > 32){ ey = 0; epos2.setX(i, Math.random()*110-55); epos2.setZ(i, Math.random()*110-55); }
      epos2.setY(i, ey);
      epos2.setX(i, epos2.getX(i) + Math.sin(ey*0.5 + i)*dt*0.8);
    }
    epos2.needsUpdate = true;
  }
  if(RAIN_ON && rainPts.visible){
    var pos = rainPts.geometry.attributes.position, n = pos.count;
    for(i=0;i<n;i++){
      rainVel[i] += 32*dt;  /* gravity — the drops accelerate as they fall */
      var y = pos.getY(i) - rainVel[i]*dt;
      if(y < 0){ y = 40; rainVel[i] = 6+Math.random()*6; pos.setX(i, Math.random()*110-55); pos.setZ(i, Math.random()*110-55); }
      pos.setY(i, y);
    }
    pos.needsUpdate = true;
  }
  /* lightning strikes while the Rain Law holds */
  if(RAIN_ON){
    boltT += dt;
    if(boltT > nextBolt){
      boltT = 0; nextBolt = 6 + Math.random()*10;
      fireBolt(false);
      var br = Math.random();
      if(br < 0.25) setTimeout(function(){ fireBolt(true); }, 350 + Math.random()*450);
      else if(br < 0.35){ setTimeout(function(){ fireBolt(true); }, 350 + Math.random()*450); setTimeout(function(){ fireBolt(true); }, 800 + Math.random()*700); }
    }
    if(boltLight.intensity > 0) boltLight.intensity = Math.max(0, boltLight.intensity - dt*10);
  }
  /* the whiteout: every frame decides — white while flashing or held, true sky after */
  if(WHITEOUT_HELD){ scene.background.set(0xffffff); }
  else if(flashT > 0){ flashT -= dt; scene.background.set(0xffffff); if(flashT <= 0) scene.background.set(skyBgHex); }
  if(!RAIN_ON){
    boltLine.visible = false; boltBranch.visible = false; boltGlow.visible = false; boltLight.intensity = 0;
  }
}
/* his sky on arrival — dawn by default, ?sky=day|night|storm|cosmic|cave|dawn|ember to choose.
   placed here, after the sky's own variables are born. */
(function(){
  var sm = location.search.match(/[?&]sky=([a-z]+)/);
  var skn = sm ? sm[1] : 'day'; /* dawn removed — King's order */
  if(['day','night','storm','cosmic','cave','dawn','ember','aurora'].indexOf(skn) >= 0) skyState(skn);
})();


/* COLLAPSED BUTTON MENU — one button holds them all */
var _menuOpen = false;
var _menuBtn = null;
var _menuPanel = null;
function buildButtonMenu(){
  if(_menuBtn) return;
  /* the one button */
  _menuBtn = document.createElement('button');


/* === CAMERA BUTTON — above keyboard, opposite side from menu === */
var _camBtn = null;
var _camPanel = null;
var _camOpen = false;
var _camViews = [
  {code:'vfar', label:'Far'},
  {code:'crown', label:'Crown'},
  {code:'vleft', label:'Left'},
  {code:'vright', label:'Right'},
  {code:'vtop', label:'Above'},
  {code:'vbelow', label:'Below'},
  {code:'mirror', label:'Mirror'},
  {code:'vfront', label:'Front'},
  {code:'vback', label:'Back'},
  {code:'vclose', label:'Close-Up'},
  {code:'vzoomout', label:'Zoom Out'},
  {code:'vreset', label:'Reset'},
  {code:'vmefar', label:'Me Far'},
  {code:'vmehalf', label:'Me Half'},
  {code:'vmequarter', label:'Me Quarter'},
  {code:'vmeclose', label:'Me Close'},
  {code:'vmeyes', label:'My Eyes'},
  {code:'vtoggle', label:'Toggle View'}
];
function createCameraButton(){
  if(_camBtn) return;
  /* export button — separate fixed, by the camera */
  var _expBtn = document.createElement('button');
  _expBtn.id = 'exportbtn';
  _expBtn.textContent = '\u2B06';
  _expBtn.title = 'Export OBJ';
  _expBtn.style.cssText = 'position:fixed;bottom:320px;left:70px;z-index:10000;width