<!-- CODERS100 · part 23 -->
(){ camYaw=0; camPitch=0.5; camDist=20; }},
  'look':   {cat:'Camera', desc:'Look at x z — look 600 600', fn:function(a){
    if(a.length>=2){ kingPos.x=parseFloat(a[0])||0; kingPos.z=parseFloat(a[1])||0; }
  }},
  'skyview':{cat:'Camera', desc:'Sky view', fn:function(){
    camera.position.set(0,900,900); camera.lookAt(0,0,0);
  }},
  'vtoggle':{cat:'Camera', desc:'Toggle view mode', fn:function(){
    camDist = camDist > 30 ? 12 : 80;
  }},
  'angle':  {cat:'Camera', desc:'Set angle — angle 45', fn:function(a){
    if(a[0]) camYaw = (parseFloat(a[0])||0) * Math.PI/180;
  }},
  'glide':  {cat:'Camera', desc:'Smooth glide', fn:function(){ say('gliding'); }},
};

/* Code runner */
var codeInput = document.getElementById('codeinput');
function runCode(text){
  text = (text||'').trim().toLowerCase();
  if(!text) return;
  var parts = text.split(/\s+/);
  var cmd = parts[0], args = parts.slice(1);
  var c = CODES[cmd];
  if(c){ try{ c.fn(args); }catch(e){ say('stumbled'); } }
  else say('unknown — '+cmd);
}
document.getElementById('codesend').onclick = function(){
  runCode(codeInput.value); codeInput.value=''; codeInput.blur();
};
codeInput.addEventListener('keydown', function(e){
  if(e.key==='Enter'){ runCode(codeInput.value); codeInput.value=''; codeInput.blur(); }
});
/* Os never blocks keyboard — close Os when typing */
codeInput.addEventListener('focus', function(){
  osOpen = false;
  osEl.classList.remove('open');;
});

/* ===== CAMERA TOGGLE — cycle through modes ===== */
var camModes = [
  {n:'Orbit', yaw:0, pitch:0.5, dist:20},
  {n:'Top', yaw:0, pitch:1.45, dist:30},
  {n:'Front', yaw:0, pitch:0.35, dist:12},
  {n:'Back', yaw:Math.PI, pitch:0.35, dist:12},
  {n:'Left', yaw:Math.PI/2, pitch:0.35, dist:12},
  {n:'Right', yaw:-Math.PI/2, pitch:0.35, dist:12},
  {n:'Far', yaw:0, pitch:0.7, dist:80},
  {n:'Close', yaw:0, pitch:0.4, dist:6}
];
var camModeIdx = 0;
function toggleCamera(){
  camModeIdx = (camModeIdx + 1) % camModes.length;
  var m = camModes[camModeIdx];
  camYaw = m.yaw; camPitch = m.pitch; camDist = m.dist;
  say('camera: ' + m.n);
}

/* ===== BODY MOVEMENT TOGGLE — walk / fly / still ===== */
var moveModes = ['still', 'walk', 'fly', 'swim'];
var moveModeIdx = 0;
var moveMode = 'still';
function toggleMove(){
  moveModeIdx = (moveModeIdx + 1) % moveModes.length;
  moveMode = moveModes[moveModeIdx];
  var joy = document.getElementById('joy');
  if(moveMode === 'still'){
    if(joy) joy.style.display = 'none';
    say('still');
  } else if(moveMode === 'walk'){
    if(joy) joy.style.display = 'block';
    say('walk — joystick to move');
  } else if(moveMode === 'fly'){
    if(joy) joy.style.display = 'block';
    say('fly — joystick to soar');
  }
}

/* Joystick movement */
var joyActive = false, joyDX = 0, joyDY = 0;
var joyEl = document.getElementById('joy');
var knobEl = document.getElementById('joyknob');
if(joyEl){
  joyEl.addEventListener('touchstart', function(e){
    joyActive = true;
    e.preventDefault();
  }, {passive:false});
  joyEl.addEventListener('touchmove', function(e){
    if(!joyActive) return;
    var r = joyEl.getBoundingClientRect();
    var cx = r.left + r.width/2, cy = r.top + r.height/2;
    var dx = e.touches[0].clientX - cx, dy = e.touches[0].clientY - cy;
    var max = r.width/2 - 18;
    var len = Math.sqrt(dx*dx + dy*dy);
    if(len > max){ dx = dx/len*max; dy = dy/len*max; }
    joyDX = dx/max; joyDY = dy/max;
    knobEl.style.transform = 'translate('+dx+'px,'+dy+'px)';
    e.preventDefault();
  }, {passive:false});
  joyEl.addEventListener('touchend', function(){
    joyActive = false; joyDX = 0; joyDY = 0;
    knobEl.style.transform = 'translate(0,0)';
  }, {passive:true});
}

/* Movement applied in animate loop below */

/* ===== PRECISE MAP — canvas, exact positions ===== */
var mapCanvas = null, mapOpen = false;
function toggleMap(){
  if(!mapCanvas){
    mapCanvas = document.createElement('canvas');
    mapCanvas.width = 300; mapCanvas.height = 300;
    mapCanvas.style.cssText = 'position:fixed;top:70px;right:12px;z-index:90;border:2px solid #8a6a1c;border-radius:24px;background:rgba(10,8,4,0.95);display:none;';
    document.body.appendChild(mapCanvas);
  }
  mapOpen = !mapOpen;
  mapCanvas.style.display = mapOpen ? 'block' : 'none';
  if(mapOpen) drawMap();
}
var cityMapImg = new Image();
cityMapImg.src = '/coders100/buttons/city-map.webp';
function drawMap(){
  if(!mapCanvas || !mapOpen) return;
  var ctx = mapCanvas.getContext('2d');
  var W = 300, H = 300;
  /* city map background */
  if(cityMapImg.complete && cityMapImg.naturalWidth > 0){
    ctx.drawImage(cityMapImg, 0, 0, W, H);
  } else {
    ctx.clearRect(0,0,W,H);
  }
  /* range: 400 units across */
  var range = 400, scale = W/range;
  var cx = W/2, cy = H/2;
  /* grid */
  ctx.strokeStyle = '#333'; ctx.lineWidth = 1;
  for(var g=-200; g<=200; g+=50){
    ctx.beginPath(); ctx.moveTo(cx+g*scale, 0); ctx.lineTo(cx+g*scale, H); ctx.stroke();
    ctx.beginPath()