A fixed bottom HUD bar overlays live values (position, velocity, speed) from a moving dot to prove readability and layout


<div style="max-width:1500px;margin:0 auto;">
<iframe
  title="POC — Bottom HUD Bar"
  scrolling="no"
  style="width:1500px;height:1500px;border:0;display:block;background:#111;border-radius:14px;box-shadow:0 20px 60px rgba(0,0,0,.5);"
  sandbox="allow-scripts allow-same-origin"
  srcdoc='
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>POC — Bottom HUD Bar</title>
<style>
  html, body { margin:0; width:100%; height:100%; overflow:hidden; background:#111; }
  canvas { display:block; }
  .hint { position:absolute; left:12px; bottom:12px; color:#aaa; font:14px system-ui; }
</style>
</head>
<body>
<canvas id="c" width="1500" height="1500"></canvas>
<div class="hint">Arrows: move • HUD stays pinned to bottom</div>

<script>
const c = document.getElementById("c");
const x = c.getContext("2d");
const W = 1500, H = 1500;

const HUD_H = 130;

const p = { x: W/2, y: (H-HUD_H)/2, vx: 0, vy: 0 };

const ACCEL = 0.45;
const DRAG  = 0.92;
const MAX   = 12;

const k = {};
addEventListener("keydown", e => k[e.key] = true);
addEventListener("keyup",   e => k[e.key] = false);

function clamp(v,a,b){ return Math.max(a, Math.min(b, v)); }

function update(){
  if (k["ArrowUp"])    p.vy -= ACCEL;
  if (k["ArrowDown"])  p.vy += ACCEL;
  if (k["ArrowLeft"])  p.vx -= ACCEL;
  if (k["ArrowRight"]) p.vx += ACCEL;

  p.vx *= DRAG; p.vy *= DRAG;

  const sp = Math.hypot(p.vx, p.vy);
  if (sp > MAX) { p.vx = (p.vx/sp)*MAX; p.vy = (p.vy/sp)*MAX; }

  p.x = clamp(p.x + p.vx, 0, W);
  p.y = clamp(p.y + p.vy, 0, H - HUD_H);
}

function drawHUD(){
  // HUD background
  x.fillStyle = "rgba(0,0,0,0.65)";
  x.fillRect(0, H - HUD_H, W, HUD_H);

  // HUD top divider
  x.strokeStyle = "rgba(255,255,255,0.18)";
  x.lineWidth = 2;
  x.beginPath();
  x.moveTo(0, H - HUD_H);
  x.lineTo(W, H - HUD_H);
  x.stroke();

  // readouts
  const speed = Math.hypot(p.vx, p.vy);

  x.fillStyle = "rgba(255,255,255,0.85)";
  x.font = "700 18px system-ui";
  x.textAlign = "left";
  x.textBaseline = "top";
  x.fillText("HUD", 18, H - HUD_H + 14);

  x.font = "14px system-ui";
  x.fillStyle = "rgba(255,255,255,0.7)";
  x.fillText(`POS: ${p.x.toFixed(0)}, ${p.y.toFixed(0)}`, 18, H - HUD_H + 44);
  x.fillText(`VEL: ${p.vx.toFixed(2)}, ${p.vy.toFixed(2)}`, 18, H - HUD_H + 68);
  x.fillText(`SPD: ${speed.toFixed(2)}`, 18, H - HUD_H + 92);

  // simple bar meter for speed
  const barX = 240, barY = H - HUD_H + 50, barW = 360, barH = 16;
  x.strokeStyle = "rgba(255,255,255,0.25)";
  x.strokeRect(barX, barY, barW, barH);
  x.fillStyle = "rgba(255,255,255,0.8)";
  x.fillRect(barX, barY, barW * Math.min(1, speed / MAX), barH);

  x.fillStyle = "rgba(255,255,255,0.5)";
  x.font = "12px system-ui";
  x.fillText("SPEED METER", barX, barY - 18);
}

function draw(){
  x.clearRect(0,0,W,H);

  // play area background reference
  x.strokeStyle = "rgba(255,255,255,0.06)";
  for (let i=0;i<=W;i+=150){
    x.beginPath(); x.moveTo(i,0); x.lineTo(i,H-HUD_H); x.stroke();
  }
  for (let j=0;j<=H-HUD_H;j+=150){
    x.beginPath(); x.moveTo(0,j); x.lineTo(W,j); x.stroke();
  }

  // moving dot
  x.fillStyle = "#fff";
  x.beginPath();
  x.arc(p.x, p.y, 10, 0, Math.PI*2);
  x.fill();

  // HUD overlay last
  drawHUD();
}

(function loop(){
  update();
  draw();
  requestAnimationFrame(loop);
})();
</script>
</body>
</html>
'></iframe>
</div>