<!-- SAGA2 · part 82 -->
urn;
  if(!dragging)return;
  yaw-=(e.clientX-px)*0.0008; pitch-=(e.clientY-py)*0.0008; // 5x slower
  pitch=Math.max(-1.2,Math.min(1.2,pitch)); px=e.clientX; py=e.clientY;
});
/* ===== VISIBLE COLOR STICKS: anchors you can see, floating behavior, color = live ===== */
var stickL={active:false,id:null,ox:0,oy:0,dx:0,dy:0,base:null,knob:null};
var stickR={active:false,id:null,ox:0,oy:0,dx:0,dy:0,base:null,knob:null};
function buildStick(right, idleColor, liveColor){
  var base=document.createElement('div');
  // rest position: bottom-center, overlapping like infinity (∞)
  var cx = window.innerWidth/2 + (right?52:-52);
  base.style.cssText='position:fixed;left:'+(cx-48)+'px;bottom:70px;'+
    'width:96px;height:96px;border-radius:0%;transform:rotate(45deg);z-index:60;'+
    'border:3px solid '+idleColor+';background:rgba(0,0,0,0.25);'+
    'transition:border-color 0.15s, box-shadow 0.15s;touch-action:none;';
  var knob=document.createElement('div');
  knob.style.cssText='position:absolute;left:50%;top:50%;width:40px;height:40px;'+
    'margin:-20px 0 0 -20px;border-radius:0%;'+
    'background:'+idleColor+';opacity:0.55;transition:background 0.15s, opacity 0.15s;';
  base.appendChild(knob); document.body.appendChild(base);
  return {base:base,knob:knob,idle:idleColor,live:liveColor,
    setLive:function(on){
      this.base.style.borderColor = on ? this.live : this.idle;
      this.base.style.boxShadow = on ? ('0 0 18px '+this.live) : 'none';
      this.knob.style.background = on ? this.live : this.idle;
      this.knob.style.opacity = on ? '1' : '0.55';
    },
    setKnob:function(dx,dy){
      this.knob.style.transform='translate('+dx+'px,'+dy+'px)';
    }};
}
var visL=buildStick(false,'rgba(201,162,39,0.5)','#ffd766');   // gold = move
var visR=buildStick(true,'rgba(68,170,255,0.5)','#44ccff');    // cyan = look
visL.base.style.display='block'; visR.base.style.display='block';
visL.base.style.borderRadius='50%'; visR.base.style.borderRadius='50%';
visL.base.style.transform='none'; visR.base.style.transform='none';
visL.knob.style.borderRadius='50%'; visR.knob.style.borderRadius='50%'; // circles, not diamonds
function stickTouchStart(st, vis, t){
  st.active=true; st.id=t.identifier;
  st.ox=t.clientX; st.oy=t.clientY; st.dx=0; st.dy=0;
  // stick glides to thumb (floating) but stays visible
  vis.base.style.left=(t.clientX-48)+'px';
  vis.base.style.top=(t.clientY-48)+'px';
  vis.base.style.right='auto'; vis.base.style.bottom='auto';
  vis.setLive(true); vis.setKnob(0,0);
}
function stickTouchMove(st, vis, t, isLook){
  var dx=t.clientX-st.ox, dy=t.clientY-st.oy;
  var max=34, len=Math.sqrt(dx*dx+dy*dy);
  if(len>max){dx=dx/len*max; dy=dy/len*max;}
  st.dx=dx/max; st.dy=dy/max;
  vis.setKnob(dx,dy);
  if(isLook){
    yaw-=(t.clientX-st.px)*0.0014; pitch-=(t.clientY-st.py)*0.0014; // 5x slower
    pitch=Math.max(-1.2,Math.min(1.2,pitch));
    st.px=t.clientX; st.py=t.clientY;
  }
}
function stickTouchEnd(st, vis, t){
  if(t.identifier!==st.id) return false;
  st.active=false; st.dx=0; st.dy=0;
  vis.setLive(false); vis.setKnob(0,0);
  // glide back to infinity rest position
  var cx2 = window.innerWidth/2 + (vis===visR?52:-52);
  vis.base.style.left=(cx2-48)+'px';
  vis.base.style.top='';
  vis.base.style.bottom='70px';
  return true;
}
document.addEventListener('touchstart',function(e){
  // ignore touches on buttons (clear cache etc)
  if(e.target && (e.target.tagName==='BUTTON'||e.target.closest('button'))) return;
  for(var i=0;i<e.changedTouches.length;i++){
    var t=e.changedTouches[i];
    if(t.clientX < window.innerWidth*0.5 && !stickL.active){
      stickTouchStart(stickL, visL, t);
      e.preventDefault();
    } else if(t.clientX >= window.innerWidth*0.5 && !stickR.active){
      stickR.px=t.clientX; stickR.py=t.clientY;
      stickTouchStart(stickR, visR, t);
      e.preventDefault();
    }
  }
},{passive:false});
var pinchDist = 0;
document.addEventListener('touchmove',function(e){
  // PINCH TO ZOOM
  if(e.touches.length===2){
    var dx = e.touches[0].clientX - e.touches[1].clientX;
    var dy = e.touches[0].clientY - e.touches[1].clientY;
    var d = Math.sqrt(dx*dx+dy*dy);
    if(pinchDist>0){
      var zoom = (d - pinchDist)*0.1;
      // move camera forward/back along view direction
      var dir = new THREE.Vector3();
      camera.getWorldDirection(dir);
      camera.position.addScaledVector(dir, zoom);
    }
    pinchDist = d;
    e.preventDefault();
    return;
  }
  pinchDist = 0;
  for(var i=0;i<e.changedTouches.length;i++){
    var t=e.changedTouches[i];
    if(stickL.active&&t.identifier===stickL.id){ stickTouchMove(stickL,visL,t,false); e.preventDefault(); }
    if(stickR.active&&t.identifier===stickR.id){ stickTouchMove(stickR,visR,t,true); e.preventDefault(); }
  }
},{passive:false});
function endTouch2(e){
  for(var i=0;i<e.changedTouches.length;i++){
    stickTouchEnd(stickL,visL,e.changedTouches[i]);
    stickTouchEnd(stickR,visR,e.changedTouche