<!-- PLAYA · part 6 -->
n.set(Math.round(o.rotation.x/st)*st, Math.round(o.rotation.y/st)*st, Math.round(o.rotation.z/st)*st);
      dropToGround(o);
    })) say('Snapped to grid 1 and 15°.'); },
  calibrate: function(){ if(edit('Calibrate', function(o){ o.rotation.x = 0; o.rotation.z = 0; applyMinimum(o); dropToGround(o); })) say('Calibrated — upright, 0.13 minimum, set on the ground.'); },
  alignrow: function(){
    /* saga-solid autoAlign: every block in one straight line, 10 apart */
    if(!blocks.length){ say('No blocks to align.', true); return; }
    var list = [], x = -((blocks.length - 1) * 10) / 2;
    blocks.forEach(function(b, i){ var before = snap(b); b.position.x = Math.round(x + i*10); b.position.z = Math.round(orbit.target.z); dropToGround(b); list.push({kind:'state', obj:b, before:before, after:snap(b)}); });
    pushHist({label: 'Align row', kind: 'multi', list: list}); refresh();
    say('Aligned ' + blocks.length + ' block(s) in one straight row, 10 apart.');
  },
  section: function(){ if(edit('Section', function(o){ o.userData.section = !o.userData.section; })) say(selected.userData.section ? 'Section cut on — the top half is cut away.' : 'Section cut off.'); },
  smooth: function(){ if(edit('Smooth', function(o){ eachMat(o, function(mt){ mt.flatShading = false; }); })) say('Smooth shading on ' + label(selected) + '.'); },
  sharpen: function(){ if(edit('Sharpen', function(o){ eachMat(o, function(mt){ mt.flatShading = true; }); })) say('Sharp edges on ' + label(selected) + '.'); },
  hollow: function(){ if(edit('Hollow', function(o){ var w = !meshesOf(o)[0].material.wireframe; eachMat(o, function(mt){ mt.wireframe = w; }); })) say(meshesOf(selected)[0].material.wireframe ? 'Hollow — wire shell.' : 'Solid again.'); },
  nudge: function(a){ var p = a.split(','), ax = p[0], d = parseFloat(p[1]);
    if(edit('Move ' + ax.toUpperCase() + (d > 0 ? '+' : '−'), function(o){ o.position[ax] += d; if(o.position.y < 0 && ax === 'y'){ o.position.y -= d; say('Cannot go below the ground.', true); return false; } if(wouldOverlap(o)){ say('Blocked — would overlap.', true); return false; } })) say(label(selected) + ' at ' + fmtPos(selected) + '.'); },
  turn: function(d){ d = parseFloat(d); if(edit('Rotate ' + d + '°', function(o){ o.rotation.y += d * Math.PI / 180; })) say('Rotated ' + label(selected) + ' ' + d + '°.'); },
  isolate: function(){
    if(isolated){ isolated.forEach(function(b){ b.visible = true; }); isolated = null; say('Isolate off — all blocks back.'); refresh(); return; }
    var o = need(); if(!o) return;
    isolated = blocks.filter(function(b){ return b !== o && b.visible; });
    isolated.forEach(function(b){ b.visible = false; });
    say('Isolated ' + label(o) + '. Tap Isolate again to bring the rest back.'); refresh();
  },
  hide: function(){ if(edit('Hide', function(o){ o.visible = false; })) say('Hid ' + label(selected) + ' — show it again in Manage Layers.'); },
  showall: function(){
    var list = [];
    blocks.forEach(function(b){ if(!b.visible){ var bf = snap(b); b.visible = true; list.push({kind:'state', obj:b, before:bf, after:snap(b)}); } });
    isolated = null;
    if(list.length) pushHist({label: 'Show all', kind: 'multi', list: list});
    say(list.length ? 'Showing all ' + blocks.length + ' block(s).' : 'Everything is already visible.'); refresh();
  },
  layersel: function(id){ var b = byId(id); if(b){ select(b); say('Selected ' + label(b) + '.'); } },
  layervis: function(id){ var b = byId(id); if(!b) return; var bf = snap(b); b.visible = !b.visible; pushHist({label: (b.visible ? 'Show ' : 'Hide ') + label(b), kind:'state', obj:b, before:bf, after:snap(b)}); say((b.visible ? 'Showing ' : 'Hid ') + label(b) + '.'); refresh(); },
  brush: function(i){ applyBrush(parseInt(i, 10)); },
  swatch: function(hex){ setColor(hex); },
  applyhex: function(){
    var v = document.getElementById('chex').value.trim();
    if(v[0] !== '#') v = '#' + v;
    if(!/^#[0-9a-fA-F]{6}$/.test(v)){ say('Hex must look like #RRGGBB.', true); return; }
    document.getElementById('cprev').style.background = v; setColor(v.toUpperCase());
  },
  opencolor: function(){ if(layout === 'phone') ACT.sheet('color'); else { var el = document.getElementById('sec-color'); el.scrollIntoView({block:'nearest'}); say('Color is in the bottom panel — tap the wheel or a swatch.'); } },
  grid: function(d){ gridDiv = parseInt(d, 10); buildGrid(); say(gridDiv ? 'Grid density ' + gridDiv + ' lines.' : 'Grid off.'); refresh(); },
  lw: function(i){ var p = LW[parseInt(i, 10)]; scene.background = new THREE.Color(p[1]); scene.fog.color.setHex(p[1]); gridColor = p[2]; ground.material.color.setHex(p[3]); buildGrid();
    document.getElementById('bgpick').value = '#' + new THREE.Color(p[1]).getHexString(); document.getElementById('lnpick').value = '#' + new THREE.Color(p[2]).getHexString();
    say('Linework preset: ' + p[0] + '.'); },
  env: function(k){ setEnv(k); say('Lighting: ' + k + '.'); },
  shadows