diff --git a/web/js/sections/board-control.js b/web/js/sections/board-control.js
index d997664..ea0f990 100644
--- a/web/js/sections/board-control.js
+++ b/web/js/sections/board-control.js
@@ -61,6 +61,19 @@ function buildHTML() {
color: var(--accent-red, #ff5252);
border: 1px solid rgba(255, 82, 82, 0.3);
}
+ .pos-marker {
+ position: absolute;
+ width: 14px;
+ height: 14px;
+ border-radius: 50%;
+ background: var(--accent-blue);
+ border: 2px solid white;
+ transform: translate(-50%, -50%);
+ transition: left 0.1s linear, top 0.1s linear;
+ box-shadow: 0 0 10px rgba(0,0,0,0.5);
+ z-index: 10;
+ pointer-events: none;
+ }
@@ -78,6 +91,7 @@ function buildHTML() {
+
@@ -167,14 +181,37 @@ function buildHTML() {
`;
}
-function updatePositionReadout() {
+function updatePosMarker() {
+ const marker = document.getElementById('pos-marker');
+ if (!marker) return;
+
const t1 = IK.stepsToRad(steps1);
const t2 = IK.stepsToRad(steps2);
- const { endX, endY } = IK.forward(t1, t2);
- const px = document.getElementById('pos-x');
- const py = document.getElementById('pos-y');
- if (px) px.textContent = isFinite(endX) ? endX.toFixed(1) : '---';
- if (py) py.textContent = isFinite(endY) ? endY.toFixed(1) : '---';
+ const { endX, endY, valid } = IK.forward(t1, t2);
+
+ if (valid && isFinite(endX) && isFinite(endY)) {
+ const px = document.getElementById('pos-x');
+ const py = document.getElementById('pos-y');
+ if (px) px.textContent = endX.toFixed(1);
+ if (py) py.textContent = endY.toFixed(1);
+
+ const w = bounds.xMax - bounds.xMin;
+ const h = bounds.yMax - bounds.yMin;
+
+ // Bounds check to avoid marker flying off screen entirely
+ if (endX >= bounds.xMin && endX <= bounds.xMax && endY >= bounds.yMin && endY <= bounds.yMax) {
+ const xPct = ((endX - bounds.xMin) / w) * 100;
+ const yPct = ((bounds.yMax - endY) / h) * 100;
+
+ marker.style.left = `${xPct}%`;
+ marker.style.top = `${yPct}%`;
+ marker.style.display = 'block';
+ } else {
+ marker.style.display = 'none'; // Out of bounds visually
+ }
+ } else {
+ marker.style.display = 'none';
+ }
}
function updateHomingUI() {
@@ -214,6 +251,7 @@ async function loadSpots() {
spots = parsedSpots;
document.getElementById('bg-image').src = `web/assets/backgrounds/${currentBg}/bg.svg?v=${Date.now()}`;
renderSpots();
+ updatePosMarker();
} catch(e) {
UI.log('Failed to load spots.json', 'error');
}
@@ -301,7 +339,7 @@ export default {
() => ({ steps1, steps2 }),
(s1, s2) => {
steps1 = s1; steps2 = s2;
- updatePositionReadout();
+ updatePosMarker();
}
);
});
@@ -345,7 +383,7 @@ export default {
steps2 = IK.ARM.HOME_STEPS.m2;
Motion.setSteps(steps1, steps2);
saveSteps();
- updatePositionReadout();
+ updatePosMarker();
UI.log('[sim] HOMEALL', 'info');
}
});
@@ -378,7 +416,7 @@ export default {
const [s1, s2] = msg.slice(2).split(',').map(Number);
steps1 = s1; steps2 = s2;
saveSteps();
- updatePositionReadout();
+ updatePosMarker();
}
};
document.addEventListener('ble:status', onBLEStatus);
@@ -388,7 +426,7 @@ export default {
BLE.write('POS').catch(() => {});
sendSettings();
} else {
- updatePositionReadout();
+ updatePosMarker();
}
initQuickSequences(
@@ -397,7 +435,7 @@ export default {
() => ({ steps1, steps2 }),
(s1, s2) => {
steps1 = s1; steps2 = s2;
- updatePositionReadout();
+ updatePosMarker();
}
);