<!-- SAGA2 · part 105 -->
lyScalar(endDist));
      // SPARKLING BEAM: core + glow shell
      var beamGrp = new THREE.Group();
      var coreGeo = new THREE.CylinderGeometry(0.8, 0.8, endDist, 6);
      coreGeo.translate(0, endDist/2, 0);
      var core = new THREE.Mesh(coreGeo, new THREE.MeshBasicMaterial({
        color: 0xffffff, transparent:true, opacity:0.95 }));
      var shellGeo = new THREE.CylinderGeometry(2.5, 2.5, endDist, 8);
      shellGeo.translate(0, endDist/2, 0);
      var shell = new THREE.Mesh(shellGeo, new THREE.MeshBasicMaterial({
        color: i?0xffd766:0x44ccff, transparent:true, opacity:0.45,
        blending:THREE.AdditiveBlending, depthWrite:false }));
      beamGrp.add(core); beamGrp.add(shell);
      beamGrp.position.copy(start);
      beamGrp.lookAt(end);
      beamGrp.rotateX(Math.PI/2);
      scene.add(beamGrp);
      lasers.push({mesh:beamGrp, life:0.18});
      // SPARKLES along beam
      for(var sp=0; sp<8; sp++){
        var t = Math.random();
        var pos = start.clone().lerp(end, t);
        var spark = new THREE.Mesh(new THREE.OctahedronGeometry(1.5+Math.random()*2),
          new THREE.MeshBasicMaterial({color: i?0xffeebb:0xbbffff, transparent:true, opacity:1}));
        spark.position.copy(pos);
        spark.userData = {vx:(Math.random()-0.5)*30, vy:(Math.random()-0.5)*30, vz:(Math.random()-0.5)*30, life:0.5+Math.random()*0.4};
        scene.add(spark);
        sparkles.push(spark);
      }
      // BLACK SCORCH MARK at impact
      if(hitPoint && hitObj){
        var scorch = new THREE.Mesh(new THREE.CircleGeometry(6+Math.random()*4, 12),
          new THREE.MeshBasicMaterial({color:0x000000, transparent:true, opacity:0.55, depthWrite:false}));
        scorch.position.copy(hitPoint);
        // orient to surface
        var n = hits[0].face ? hits[0].face.normal.clone() : dir.clone().negate();
        if(hitObj.matrixWorld) n.transformDirection(hitObj.matrixWorld);
        scorch.lookAt(hitPoint.clone().add(n));
        scorch.position.add(n.clone().multiplyScalar(0.5));
        // irregular edge: second smaller mark
        var scorch2 = new THREE.Mesh(new THREE.CircleGeometry(3+Math.random()*2, 10),
          new THREE.MeshBasicMaterial({color:0x111111, transparent:true, opacity:0.7, depthWrite:false}));
        scorch2.position.copy(scorch.position).add(new THREE.Vector3((Math.random()-0.5)*8, (Math.random()-0.5)*8, 0.2));
        scorch2.lookAt(hitPoint.clone().add(n));
        scene.add(scorch); scene.add(scorch2);
        scorchMarks.push({m:scorch, life:4});
        scorchMarks.push({m:scorch2, life:4});
        // impact burst
        for(var b=0; b<6; b++){
          var burst = new THREE.Mesh(new THREE.OctahedronGeometry(1+Math.random()*1.5),
            new THREE.MeshBasicMaterial({color:0xffaa44, transparent:true, opacity:1}));
          burst.position.copy(hitPoint);
          burst.userData = {vx:(Math.random()-0.5)*60, vy:Math.random()*40, vz:(Math.random()-0.5)*60, life:0.4};
          scene.add(burst);
          sparkles.push(burst);
        }
      }

    }, i*60);
  });
}
// weapon toggle button
(function(){
  var b = document.createElement('button');
  b.id = 'wbtn';
  b.textContent = 'CANNONS';
  b.style.cssText = 'position:fixed;top:70px;right:12px;z-index:100;background:#8a1a1a;color:#fff;'+
    'border:none;font-weight:bold;font-size:14px;padding:12px 16px;border-radius:10px;min-height:48px;';
  b.addEventListener('click', function(){ setWeaponMode(!weaponMode); });
  document.body.appendChild(b);
  // fire button
  var f = document.createElement('button');
  f.id = 'fbtn';
  f.textContent = 'FIRE';
  f.style.cssText = 'position:fixed;bottom:200px;right:24px;z-index:100;background:#c9a227;color:#000;'+
    'border:none;font-weight:bold;font-size:16px;padding:16px 20px;border-radius:50%;width:80px;height:80px;display:none;';
  f.addEventListener('touchstart', function(e){ e.preventDefault(); e.stopPropagation(); fireLaser(); }, {passive:false});
  f.addEventListener('click', function(e){ e.stopPropagation(); fireLaser(); });
  document.body.appendChild(f);
  // show fire button only in weapon mode
  var origSet = setWeaponMode;
  setWeaponMode = function(on){
    origSet(on);
    // FIRE button retired — CANNONS toggle does it all
  };
})();

// ===== CREATOR TABLE (v204) — toggle button + 3D creator on a table =====
var creatorTable = new THREE.Group(); // (3D table retired v205 — button goes to /playa)
var creatorVisible = false;
(function buildCreatorTable(){
  var woodMat = new THREE.MeshStandardMaterial({color:0x6b4a2f, roughness:0.8});
  var metalMat = new THREE.MeshStandardMaterial({color:0x888888, roughness:0.4, metalness:0.6});
  var goldMat = new THREE.MeshStandardMaterial({color:0xc9a54f, roughness:0.3, metalness:0.5});
  // Table top
  var top = new THREE.Mesh(new THREE.BoxGeometry(12, 0.5, 8), woodMat);
  top.position.y = 3; top.castShadow = true; creatorTable.add(top);
  // Legs
  for(var lx=-1; lx<=1; lx+=2) for(var lz=