diff --git a/gui/admin.html b/gui/admin.html
index 9f21097..cb0fc05 100644
--- a/gui/admin.html
+++ b/gui/admin.html
@@ -172,6 +172,14 @@
+
+
+ HUD-Farbe
+
+
+
+
+
Hinweis
@@ -2820,6 +2828,27 @@
}
}
+ (function() {
+ try {
+ var h = localStorage.getItem('hudHexColor');
+ if (h) {
+ var el = document.getElementById('hud_color_picker');
+ if (el) el.value = '#' + h;
+ }
+ } catch(e) {}
+ })();
+
+ function applyHudColorFromAdmin(cssColor) {
+ var hex = cssColor.replace('#', '');
+ try { localStorage.setItem('hudHexColor', hex); } catch(e) {}
+ }
+
+ function resetHudColor() {
+ var el = document.getElementById('hud_color_picker');
+ if (el) el.value = '#00ff91';
+ applyHudColorFromAdmin('#00ff91');
+ }
+
async function setImuNorth() {
if (!window.confirm('Rover jetzt wirklich sauber nach Norden ausgerichtet?')) return;
setAdminStatus('Speichere Nordrichtung...');
diff --git a/gui/app.css b/gui/app.css
index 1a8b608..898f678 100644
--- a/gui/app.css
+++ b/gui/app.css
@@ -281,10 +281,11 @@ header {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
- width: calc(270px * var(--cam-scale));
- height: calc(270px * var(--cam-scale));
+ width: calc(405px * var(--cam-scale));
+ height: calc(405px * var(--cam-scale));
z-index: 10;
pointer-events: none;
+ filter: drop-shadow(0 0 1.5px rgba(0,0,0,.9)) drop-shadow(0 0 3px rgba(0,0,0,.6));
}
@keyframes xh-spin { to { transform: rotate(360deg); } }
@keyframes xh-pulse {
diff --git a/gui/index.html b/gui/index.html
index ecd2628..4c6ee82 100644
--- a/gui/index.html
+++ b/gui/index.html
@@ -94,7 +94,7 @@
S: 0,00 m
-
-
@@ -329,6 +328,26 @@ var imuOverlayState = {
lastUpdateAt: 0
};
+var hudRGB = [0, 255, 145];
+var xhairSvgOrigHTML = null;
+
+function hudRgba(alpha) {
+ return 'rgba(' + hudRGB[0] + ',' + hudRGB[1] + ',' + hudRGB[2] + ',' + alpha + ')';
+}
+
+function applyHudColor(hex) {
+ var r = parseInt(hex.slice(0, 2), 16);
+ var g = parseInt(hex.slice(2, 4), 16);
+ var b = parseInt(hex.slice(4, 6), 16);
+ if (isNaN(r) || isNaN(g) || isNaN(b)) return;
+ hudRGB = [r, g, b];
+ try { localStorage.setItem('hudHexColor', hex); } catch (e) {}
+ var svgEl = document.querySelector('.c-xhair svg');
+ if (!svgEl) return;
+ if (!xhairSvgOrigHTML) xhairSvgOrigHTML = svgEl.innerHTML;
+ svgEl.innerHTML = xhairSvgOrigHTML.replace(/rgba\(0,255,145,/g, 'rgba(' + r + ',' + g + ',' + b + ',');
+}
+
function setText(id, text) {
var el = document.getElementById(id);
if (el) {
@@ -357,7 +376,7 @@ function formatSignedNumber(value, digits) {
}
function headingToCompass(deg) {
- var dirs = ["N","NNE","NE","ENE","E","ESE","SE","SSE","S","SSW","SW","WSW","W","WNW","NW","NNW"];
+ var dirs = ["N","NNO","NO","ONO","O","OSO","SO","SSO","S","SSW","SW","WSW","W","WNW","NW","NNW"];
return dirs[Math.round(((deg % 360) + 360) % 360 / 22.5) % 16];
}
@@ -1141,12 +1160,14 @@ function drawImuHorizonOverlay(context, canvasWidth, canvasHeight, size) {
context.save();
context.translate(cx, cy);
context.rotate(-rollRad);
+ context.shadowColor = "rgba(0,0,0,.85)";
+ context.shadowBlur = 3;
context.beginPath();
context.moveTo(0, -90 * unit);
context.lineTo(-5.5 * unit, -79 * unit);
context.lineTo(5.5 * unit, -79 * unit);
context.closePath();
- context.fillStyle = "rgba(0,255,145,.88)";
+ context.fillStyle = hudRgba(.88);
context.fill();
context.restore();
@@ -1155,6 +1176,8 @@ function drawImuHorizonOverlay(context, canvasWidth, canvasHeight, size) {
context.translate(cx, cy);
context.rotate(-rollRad);
context.translate(0, pitchOffsetPx);
+ context.shadowColor = "rgba(0,0,0,.85)";
+ context.shadowBlur = 2.5;
[
{ y: -33, span: 11, op: .28 }, { y: 33, span: 11, op: .28 },
@@ -1164,12 +1187,12 @@ function drawImuHorizonOverlay(context, canvasWidth, canvasHeight, size) {
context.beginPath();
context.moveTo(-pl.span * unit, pl.y * unit);
context.lineTo(pl.span * unit, pl.y * unit);
- context.strokeStyle = "rgba(0,255,145," + pl.op + ")";
+ context.strokeStyle = hudRgba(pl.op);
context.lineWidth = 0.9 * unit;
context.stroke();
});
- context.strokeStyle = "rgba(0,255,145,.95)";
+ context.strokeStyle = hudRgba(.95);
context.lineWidth = 2.8 * unit;
context.beginPath(); context.moveTo(-60 * unit, 0); context.lineTo(60 * unit, 0); context.stroke();
context.beginPath(); context.moveTo(-60 * unit, 0); context.lineTo(-52 * unit, -5 * unit); context.stroke();
@@ -1181,13 +1204,15 @@ function drawImuHorizonOverlay(context, canvasWidth, canvasHeight, size) {
var bw = 84 * unit, bh = 15 * unit;
context.fillStyle = "rgba(0,15,8,.65)";
context.fillRect(cx - bw / 2, cy - 72 * unit, bw, bh);
+ context.shadowColor = "rgba(0,0,0,.9)";
+ context.shadowBlur = 3;
context.font = (9 * unit) + 'px "Consolas","Cascadia Mono",monospace';
- context.fillStyle = "rgba(0,255,145,.92)";
+ context.fillStyle = hudRgba(.92);
context.textAlign = "center";
context.textBaseline = "middle";
context.fillText("HDG " + String(Math.round(headingDeg)).padStart(3, "0") + "° " + headingToCompass(headingDeg), cx, cy - 62.5 * unit);
- context.font = (7.5 * unit) + 'px "Consolas","Cascadia Mono",monospace';
- context.fillStyle = "rgba(0,255,145,.72)";
+ context.font = (10.5 * unit) + 'px "Consolas","Cascadia Mono",monospace';
+ context.fillStyle = hudRgba(.82);
context.fillText("R " + formatSignedNumber(rollDeg, 1) + "°", cx - 73 * unit, cy + 76 * unit);
context.fillText("P " + formatSignedNumber(pitchDeg, 1) + "°", cx + 73 * unit, cy + 76 * unit);
context.restore();
@@ -1198,67 +1223,76 @@ function drawCrosshairOverlayToCanvas(context, canvasWidth, canvasHeight, size)
var cy = canvasHeight / 2;
var unit = size / 180;
- function circle(radius, stroke, lineWidth, dash) {
+ function circle(radius, alpha, lineWidth, dash) {
context.save();
context.beginPath();
context.arc(cx, cy, radius * unit, 0, Math.PI * 2);
if (dash) context.setLineDash(dash.map(function (value) { return value * unit; }));
- context.strokeStyle = stroke;
+ context.shadowColor = "rgba(0,0,0,.85)";
+ context.shadowBlur = 2.5;
+ context.strokeStyle = hudRgba(alpha);
context.lineWidth = lineWidth * unit;
context.stroke();
context.restore();
}
- function line(x1, y1, x2, y2, stroke, lineWidth) {
+ function line(x1, y1, x2, y2, alpha, lineWidth) {
context.save();
context.beginPath();
context.moveTo(cx + (x1 * unit), cy + (y1 * unit));
context.lineTo(cx + (x2 * unit), cy + (y2 * unit));
- context.strokeStyle = stroke;
+ context.shadowColor = "rgba(0,0,0,.85)";
+ context.shadowBlur = 2.5;
+ context.strokeStyle = hudRgba(alpha);
context.lineWidth = lineWidth * unit;
context.stroke();
context.restore();
}
- function path(points, stroke, lineWidth) {
+ function path(points, alpha, lineWidth) {
context.save();
context.beginPath();
context.moveTo(cx + (points[0][0] * unit), cy + (points[0][1] * unit));
for (var i = 1; i < points.length; i += 1) {
context.lineTo(cx + (points[i][0] * unit), cy + (points[i][1] * unit));
}
- context.strokeStyle = stroke;
+ context.shadowColor = "rgba(0,0,0,.85)";
+ context.shadowBlur = 2.5;
+ context.strokeStyle = hudRgba(alpha);
context.lineWidth = lineWidth * unit;
context.stroke();
context.restore();
}
- circle(82, "rgba(0,255,145,.2)", 1, [4, 10]);
- circle(60, "rgba(0,255,145,.15)", 0.75, [2, 16]);
- circle(16, "rgba(0,255,145,.3)", 0.75);
- line(-86, 0, -19, 0, "rgba(0,255,145,.6)", 1);
- line(19, 0, 86, 0, "rgba(0,255,145,.6)", 1);
- line(0, -86, 0, -19, "rgba(0,255,145,.6)", 1);
- line(0, 19, 0, 86, "rgba(0,255,145,.6)", 1);
- line(-60, -6, -60, 6, "rgba(0,255,145,.45)", 0.75);
- line(60, -6, 60, 6, "rgba(0,255,145,.45)", 0.75);
- line(-6, -60, 6, -60, "rgba(0,255,145,.45)", 0.75);
- line(-6, 60, 6, 60, "rgba(0,255,145,.45)", 0.75);
- line(-38, -3.5, -38, 3.5, "rgba(0,255,145,.3)", 0.5);
- line(38, -3.5, 38, 3.5, "rgba(0,255,145,.3)", 0.5);
- line(-3.5, -38, 3.5, -38, "rgba(0,255,145,.3)", 0.5);
- line(-3.5, 38, 3.5, 38, "rgba(0,255,145,.3)", 0.5);
- path([[-74, -58], [-74, -74], [-58, -74]], "rgba(0,255,145,.75)", 1.5);
- path([[58, -74], [74, -74], [74, -58]], "rgba(0,255,145,.75)", 1.5);
- path([[-74, 58], [-74, 74], [-58, 74]], "rgba(0,255,145,.75)", 1.5);
- path([[58, 74], [74, 74], [74, 58]], "rgba(0,255,145,.75)", 1.5);
- circle(6, "rgba(0,255,145,.7)", 1);
+ circle(82, .2, 1, [4, 10]);
+ circle(60, .15, 0.75, [2, 16]);
+ circle(16, .3, 0.75);
+ line(-86, 0, -19, 0, .6, 1);
+ line(19, 0, 86, 0, .6, 1);
+ line(0, -86, 0, -19, .6, 1);
+ line(0, 19, 0, 86, .6, 1);
+ line(-60, -6, -60, 6, .45, 0.75);
+ line(60, -6, 60, 6, .45, 0.75);
+ line(-6, -60, 6, -60, .45, 0.75);
+ line(-6, 60, 6, 60, .45, 0.75);
+ line(-38, -3.5, -38, 3.5, .3, 0.5);
+ line(38, -3.5, 38, 3.5, .3, 0.5);
+ line(-3.5, -38, 3.5, -38, .3, 0.5);
+ line(-3.5, 38, 3.5, 38, .3, 0.5);
+ path([[-74, -58], [-74, -74], [-58, -74]], .75, 1.5);
+ path([[58, -74], [74, -74], [74, -58]], .75, 1.5);
+ path([[-74, 58], [-74, 74], [-58, 74]], .75, 1.5);
+ path([[58, 74], [74, 74], [74, 58]], .75, 1.5);
+ circle(6, .7, 1);
context.save();
+ context.shadowColor = "rgba(0,0,0,.85)";
+ context.shadowBlur = 3;
context.beginPath();
context.arc(cx, cy, 2.5 * unit, 0, Math.PI * 2);
- context.fillStyle = "rgba(0,255,145,.9)";
+ context.fillStyle = hudRgba(.9);
context.fill();
+ context.shadowBlur = 0;
context.beginPath();
context.arc(cx, cy, 1 * unit, 0, Math.PI * 2);
context.fillStyle = "rgba(255,255,255,.7)";
@@ -1271,17 +1305,9 @@ function drawCrosshairOverlayToCanvas(context, canvasWidth, canvasHeight, size)
function drawVignetteAndScanlines(context, canvasWidth, canvasHeight) {
context.save();
for (var y = 0; y < canvasHeight; y += 4) {
- context.fillStyle = "rgba(0,255,145,.012)";
+ context.fillStyle = hudRgba(.012);
context.fillRect(0, y, canvasWidth, 1);
}
- var vignette = context.createRadialGradient(
- canvasWidth * 0.5, canvasHeight * 0.5, canvasWidth * 0.25,
- canvasWidth * 0.5, canvasHeight * 0.5, canvasWidth * 0.75
- );
- vignette.addColorStop(0, "rgba(0,0,0,0)");
- vignette.addColorStop(1, "rgba(0,0,0,.58)");
- context.fillStyle = vignette;
- context.fillRect(0, 0, canvasWidth, canvasHeight);
context.restore();
}
@@ -1339,7 +1365,7 @@ function drawHudOverlayToCanvas(context, canvasWidth, canvasHeight) {
context.fillText(document.getElementById("cam-live-text").textContent, canvasWidth / 2 + (6 * camScale * scaleToCanvas), padding);
context.restore();
- drawCrosshairOverlayToCanvas(context, canvasWidth, canvasHeight, 270 * camScale * scaleToCanvas);
+ drawCrosshairOverlayToCanvas(context, canvasWidth, canvasHeight, 405 * camScale * scaleToCanvas);
drawVignetteAndScanlines(context, canvasWidth, canvasHeight);
}
@@ -1544,7 +1570,12 @@ function toggleStatPanel() {
setStatPanelCollapsed(!panel.classList.contains("collapsed"));
}
+window.addEventListener("storage", function (e) {
+ if (e.key === "hudHexColor" && e.newValue) applyHudColor(e.newValue);
+});
+
window.addEventListener("load", function () {
+ try { var h = localStorage.getItem("hudHexColor"); if (h) applyHudColor(h); } catch (e) {}
setText("cam-host", hostUrl);
updateModeDisplay();
updateMotorDisplay();