<!-- SAGA3 · part 53 -->
g.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(); ctx.moveTo(0, cy+g*scale); ctx.lineTo(W, cy+g*scale); ctx.stroke();
  }
  /* north arrow */
  ctx.fillStyle = '#ffd700'; ctx.font = '12px Georgia';
  ctx.fillText('N', cx-4, 16);
  ctx.beginPath(); ctx.moveTo(cx, 20); ctx.lineTo(cx-5, 32); ctx.lineTo(cx+5, 32); ctx.fill();
  /* districts */
  ctx.fillStyle = '#d8a920';
  var dx = (600-kingPos.x)*scale, dz = (600-kingPos.z)*scale;
  if(Math.abs(dx)<W/2 && Math.abs(dz)<H/2){
    ctx.beginPath(); ctx.arc(cx+dx, cy+dz, 6, 0, Math.PI*2); ctx.fill();
    ctx.fillText('Practice', cx+dx+8, cy+dz);
  }
  /* player — precise */
  var px = cx, py = cy;
  ctx.fillStyle = '#00ffff';
  ctx.beginPath(); ctx.arc(px, py, 5, 0, Math.PI*2); ctx.fill();
  /* facing direction */
  var fx = Math.cos(camYaw), fz = Math.sin(camYaw);
  ctx.strokeStyle = '#00ffff'; ctx.lineWidth = 2;
  ctx.beginPath(); ctx.moveTo(px, py); ctx.lineTo(px+fx*20, py+fz*20); ctx.stroke();
  /* coordinates */
  ctx.fillStyle = '#ffd700'; ctx.font = '11px monospace';
  ctx.fillText(Math.round(kingPos.x)+','+Math.round(kingPos.z), 8, H-8);
  /* redraw while open */
  if(mapOpen) requestAnimationFrame(drawMap);
}

/* ===== PRECISE COMPASS — true degrees ===== */
var compassEl = null;
function updateCompass(){
  if(!compassEl){
    compassEl = document.createElement('div');
    compassEl.style.cssText = 'position:fixed;top:70px;left:12px;z-index:90;width:60px;height:60px;border-radius:50%;background:#141006;border:2px solid #d8a920;display:flex;align-items:center;justify-content:center;color:#ffd700;font:11px monospace;';
    document.body.appendChild(compassEl);
  }
  var deg = Math.round(((camYaw*180/Math.PI)%360+360)%360);
  var dirs = ['E','SE','S','SW','W','NW','N','NE'];
  var dir = dirs[Math.round(deg/45)%8];
  compassEl.textContent = deg+'° '+dir;
}
/* update compass in animate loop */
var _origUpdateCam = updateCamera;
updateCamera = function(){
  _origUpdateCam();
  if(compassEl) updateCompass();
};

/* ===== 56 BUTTONS — uniform, intricate ===== */
var BTNS = [
  ['p1-temple','Build district',function(){ nextDistrict(); }],
  ['p1-map','Map',function(){ toggleMap(); }],
  ['p1-camera','Camera',function(){ toggleCamera(); }],
  ['p1-sun','Day',function(){ runCode('daycity'); }],
  ['p1-moon','Night',function(){ runCode('nightcity'); }],
  ['p1-pin','Where am I',function(){ runCode('whereami'); }],
  ['p1-hamburger','Menu',function(){ osOpen=false; osEl.classList.remove('open');; }],
  ['p1-plus','Create',function(){ say('create mode'); }],
  ['p1-minus','Delete',function(){ say('delete mode'); }],
  ['p1-cube','Shapes',function(){ say('shapes'); }],
  ['p1-palette','Render',function(){ say('render options'); }],
  ['p1-walker','Walk',function(){ moveMode='walk'; document.getElementById('joy').style.display='block'; say('walk'); }],
  ['p1-bird','Fly',function(){ moveMode='fly'; document.getElementById('joy').style.display='block'; say('fly'); }],
  ['p1-uparrow','Jump',function(){ say('jump!'); }],
  ['p1-hand','Grab',function(){ say('grab mode'); }],
  ['p1-speech','Chat',function(){ say('chat'); }],
  ['p1-gt','Codes',function(){ codeInput.focus(); }],
  ['p1-link','Go',function(){ say('go to...'); }],
  ['p1-house','Home',function(){ kingPos.set(0,0,0); say('home'); }],
  ['p1-music','Music',function(){ say('music'); }],
  ['p2-continuum','Continuum',function(){ say('continuum'); }],
  ['p2-place','Place',function(){ say('place mode'); }],
  ['p2-help','Help',function(){ say('help'); }],
  ['p2-export','Export',function(){ runCode('exportfbx'); }],
  ['p2-fixstation','Fix station',function(){ say('Playa 2.0 fix station'); }],
  ['p2-sky','Sky',function(){ say('sky options'); }],
  ['p2-treasure','Treasure',function(){ say('treasure hunt'); }],
  ['p2-clearcache','Clear cache',function(){ if(confirm('Clear?')) location.reload(); }],
  ['p2-waypoints','Waypoints',function(){ say('waypoints'); }],
  ['p2-kidmode','Kid mode',function(){ say('kid mode'); }],
  ['p2-crown','Crown',function(){ say('crown view'); }],
  ['p2-dimension','Dimension',function(){ say('dimension'); }],
  ['p3-soliditronium','Soliditronium',function(){ say('soliditronium'); }],
  ['p3-capsule','Capsule',function(){ say('capsule'); }],
  ['p3-fillgaps','Fill gaps',function(){ say('filling gaps'); }],
  ['p3-recolor','Recolor',function(){ say('recolor'); }],
  ['p3-saveview','Sav