<!-- PLAYA · part 2 -->
ocument.getElementById('bgpick').value = '#' + new THREE.Color(e.bg).getHexString();
  syncLightSliders();
}

/* ---------- ORBIT CAMERA (this page's own camera) ---------- */
var orbit = {target: new THREE.Vector3(0, 3, 0), yaw: 0.0, pitch: 0.42, dist: 40};
function updateCamera(){
  orbit.pitch = Math.max(0.05, Math.min(1.45, orbit.pitch));
  orbit.dist = Math.max(6, Math.min(300, orbit.dist));
  var cp = Math.cos(orbit.pitch);
  camera.position.set(orbit.target.x + Math.sin(orbit.yaw) * cp * orbit.dist,
    orbit.target.y + Math.sin(orbit.pitch) * orbit.dist,
    orbit.target.z + Math.cos(orbit.yaw) * cp * orbit.dist);
  camera.lookAt(orbit.target);
}

/* ---------- BLOCKS (shapes from saga-solid makeBlock + desktop primitives) ---------- */
var goldMat = new THREE.MeshStandardMaterial({color:0xd4a017, roughness:0.4, metalness:0.6});
var marbleMat = new THREE.MeshStandardMaterial({color:0xf5f5f5, roughness:0.3});
var cyanMat = new THREE.MeshStandardMaterial({color:0x00ccff, roughness:0.3, metalness:0.5});
var SHAPES = [
  ['cube','Standard Cube','■'], ['hourglass','Hourglass V-Core','⌛'], ['pylon','Refined Pylon','▮'],
  ['tetra','Hollow Tetra','▲'], ['pyramid','Layered Pyramid','◭'], ['sphere','Sphere','●'],
  ['cylinder','Cylinder','▯'], ['cone','Cone','▴'], ['torus','Torus','◯'], ['plane','Plane','▬'], ['icosphere','Icosphere','⬢']
];
function shapeName(t){ for(var i=0;i<SHAPES.length;i++) if(SHAPES[i][0]===t) return SHAPES[i][1]; return t; }
/* parts: [geometry, material template, y offset, flipX] — res multiplies segments */
function shapeParts(type, res){
  var r = res || 1;
  if(type === 'cube') return [[new THREE.BoxGeometry(4,4,4,2*r,4*r,2*r), goldMat, 2]];
  if(type === 'hourglass') return [[new THREE.ConeGeometry(2,3,4*r,4*r), goldMat, 4.5, true], [new THREE.ConeGeometry(2,3,4*r,4*r), goldMat, 1.5]];
  if(type === 'pylon') return [[new THREE.CylinderGeometry(1,1.5,8,8*r,6*r), marbleMat, 4]];
  if(type === 'tetra') return [[new THREE.TetrahedronGeometry(3, r-1), cyanMat, 1]];
  if(type === 'pyramid') return [[new THREE.ConeGeometry(3,2,4*r,3*r), goldMat, 1], [new THREE.ConeGeometry(2,1.5,4*r,3*r), marbleMat, 2.75]];
  if(type === 'sphere') return [[new THREE.SphereGeometry(2.5, 16*r, 12*r), marbleMat, 2.5]];
  if(type === 'cylinder') return [[new THREE.CylinderGeometry(2,2,5,16*r,4*r), marbleMat, 2.5]];
  if(type === 'cone') return [[new THREE.ConeGeometry(2.5,5,16*r,4*r), goldMat, 2.5]];
  if(type === 'torus') return [[new THREE.TorusGeometry(2.5,0.8,10*r,24*r), goldMat, 3.3]];
  if(type === 'plane') return [[new THREE.BoxGeometry(6,0.2,6,4*r,1,4*r), marbleMat, 0.1]];
  if(type === 'icosphere') return [[new THREE.IcosahedronGeometry(2.5, r), cyanMat, 2.5]];
  throw new Error('unknown shape ' + type);
}
var blocks = [];          /* every creator block in the scene */
var nextId = 1;
function makeBlock(type, res){
  var g = new THREE.Group();
  shapeParts(type, res).forEach(function(p){
    var m = new THREE.Mesh(p[0], p[1].clone());
    m.position.y = p[2]; if(p[3]) m.rotation.x = Math.PI;
    m.castShadow = true; m.receiveShadow = true;
    g.add(m);
  });
  g.userData = {creatorBlock: true, blockType: type, res: res || 1, bend: 0, section: false, spin: 0, bob: 0, id: nextId++};
  return g;
}
function meshesOf(obj){ var a = []; obj.traverse(function(o){ if(o.isMesh) a.push(o); }); return a; }
function boxOf(obj){ obj.updateMatrixWorld(true); return new THREE.Box3().setFromObject(obj); }
/* NO OVERLAP (saga-solid wouldOverlap, excluding itself; touching faces are allowed) */
function wouldOverlap(obj){
  var a = boxOf(obj).expandByScalar(-0.02);
  for(var i=0;i<blocks.length;i++){
    if(blocks[i] === obj || !blocks[i].visible) continue;
    if(a.intersectsBox(boxOf(blocks[i]).expandByScalar(-0.02))) return true;
  }
  return false;
}
function dropToGround(obj){ var b = boxOf(obj); obj.position.y -= b.min.y; }

/* ---------- HISTORY: undo / redo (snapshot based) ---------- */
var undoStack = [], redoStack = [];
function snap(obj){
  return {p: obj.position.clone(), q: obj.quaternion.clone(), s: obj.scale.clone(), vis: obj.visible,
    ud: {res: obj.userData.res, bend: obj.userData.bend, section: obj.userData.section, spin: obj.userData.spin, bob: obj.userData.bob},
    meshes: meshesOf(obj).map(function(m){ return {geo: m.geometry, mat: m.material.clone()}; })};
}
function restore(obj, sn){
  obj.position.copy(sn.p); obj.quaternion.copy(sn.q); obj.scale.copy(sn.s); obj.visible = sn.vis;
  for(var k in sn.ud) obj.userData[k] = sn.ud[k];
  meshesOf(obj).forEach(function(m, i){ m.geometry = sn.meshes[i].geo; m.material = sn.meshes[i].mat.clone(); m.material.needsUpdate = true; });
}
function pushHist(entry){ undoStack.push(entry); if(undoStack.length > 200) undoStack.shift(); redoStack.length = 0; }
function edit(label, fn){
  var obj = need(); if(!obj) return false;
  var before = snap(obj);
  var ok = fn(obj);
  if(ok === false){ restore(obj, before); refresh(); retur