This commit is contained in:
legop3
2025-12-27 04:27:14 -05:00
parent 8da2196ee0
commit 87db66ac1f
4 changed files with 64 additions and 55 deletions
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -11,7 +11,7 @@
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Multi Roomba Rover" /> <meta name="apple-mobile-web-app-title" content="Multi Roomba Rover" />
<title>Multi Roomba Rover</title> <title>Multi Roomba Rover</title>
<script type="module" crossorigin src="/assets/index-CoUbgLnI.js"></script> <script type="module" crossorigin src="/assets/index-Bap-IGi3.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-COtFQYoW.css"> <link rel="stylesheet" crossorigin href="/assets/index-COtFQYoW.css">
</head> </head>
<body> <body>
+19 -35
View File
@@ -2,16 +2,13 @@ import { useMemo } from 'react';
import { useDockIr } from '../hooks/useDockIr.js'; import { useDockIr } from '../hooks/useDockIr.js';
const SVG_W = 120; const SVG_W = 120;
const SVG_H = 110; const SVG_H = 96;
const CX = SVG_W / 2; const CX = SVG_W / 2;
const DOCK_Y = 16; const DOCK_Y = 14;
const ROVER_Y = 80; const ROVER_Y = 74;
function lobePath(side = 'left') { function alignmentBarPath() {
if (side === 'left') { return `M ${CX - 3} ${DOCK_Y + 4} L ${CX - 10} ${ROVER_Y - 6} L ${CX + 10} ${ROVER_Y - 6} Z`;
return `M ${CX} ${DOCK_Y} C ${CX - 10} ${DOCK_Y + 8} ${CX - 12} ${ROVER_Y - 18} ${CX - 4} ${ROVER_Y - 6} L ${CX} ${ROVER_Y - 10} Z`;
}
return `M ${CX} ${DOCK_Y} C ${CX + 10} ${DOCK_Y + 8} ${CX + 12} ${ROVER_Y - 18} ${CX + 4} ${ROVER_Y - 6} L ${CX} ${ROVER_Y - 10} Z`;
} }
export default function DockAssistHUD({ sensors }) { export default function DockAssistHUD({ sensors }) {
@@ -29,38 +26,29 @@ export default function DockAssistHUD({ sensors }) {
if (!state.visible) return null; if (!state.visible) return null;
const nudge = state.balance * 6; const nudge = state.balance * 5;
const leftActive = state.left.red || state.left.green || state.left.force; const leftHit = state.left.active;
const rightActive = state.right.red || state.right.green || state.right.force; const rightHit = state.right.active;
const forceHit = state.omni.force || state.forceDetected;
const allAligned = Boolean(state.left.green && state.right.red && state.omni.force); const allAligned = Boolean(state.left.green && state.right.red && state.omni.force);
const hasAny = leftHit || rightHit || forceHit;
return ( return (
<div className="pointer-events-none absolute inset-x-0 top-0 flex justify-center p-0.25"> <div className="pointer-events-none absolute inset-x-0 top-0 flex justify-center p-0.25">
<svg width={SVG_W} height={SVG_H} viewBox={`0 0 ${SVG_W} ${SVG_H}`} role="img" aria-label="Docking assist"> <svg width={SVG_W} height={SVG_H} viewBox={`0 0 ${SVG_W} ${SVG_H}`} role="img" aria-label="Docking assist">
<defs> <defs>
<linearGradient id="gradGreen" x1="0%" y1="0%" x2="0%" y2="100%"> <linearGradient id="alignGrad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stopColor={palette.green[0]} /> <stop offset="0%" stopColor="rgba(148,163,184,0.9)" />
<stop offset="60%" stopColor={palette.green[1]} /> <stop offset="100%" stopColor="rgba(148,163,184,0)" />
<stop offset="100%" stopColor={palette.green[2]} />
</linearGradient> </linearGradient>
<linearGradient id="gradRed" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stopColor={palette.red[0]} />
<stop offset="60%" stopColor={palette.red[1]} />
<stop offset="100%" stopColor={palette.red[2]} />
</linearGradient>
<radialGradient id="halo" cx="50%" cy="20%" r="60%">
<stop offset="0%" stopColor="rgba(59,130,246,0.45)" />
<stop offset="100%" stopColor="rgba(59,130,246,0)" />
</radialGradient>
</defs> </defs>
{/* Lobes */} {/* Alignment wedge */}
<path d={lobePath('left')} fill={leftActive ? 'url(#gradGreen)' : 'rgba(16,185,129,0.12)'} /> {hasAny ? (
<path d={lobePath('right')} fill={rightActive ? 'url(#gradRed)' : 'rgba(239,68,68,0.12)'} />
{leftActive && rightActive ? (
<path <path
d={`M ${CX - 6} ${ROVER_Y - 12} Q ${CX} ${ROVER_Y - 4} ${CX + 6} ${ROVER_Y - 12}`} d={alignmentBarPath()}
fill="rgba(255,255,255,0.08)" fill="url(#alignGrad)"
transform={`translate(${nudge * 3}, 0) rotate(${state.balance * 8}, ${CX}, ${ROVER_Y - 6})`}
/> />
) : null} ) : null}
@@ -119,11 +107,7 @@ export default function DockAssistHUD({ sensors }) {
)} )}
{/* Thumbs up when fully aligned */} {/* Thumbs up when fully aligned */}
{allAligned ? ( {allAligned ? <text x={CX + 30} y={DOCK_Y + 8} fontSize="12" fill="#fbbf24" fontFamily="sans-serif">👍</text> : null}
<text x={CX + 30} y={DOCK_Y + 8} fontSize="12" fill="#fbbf24" fontFamily="sans-serif">
👍
</text>
) : null}
</svg> </svg>
</div> </div>
); );
+29 -4
View File
@@ -113,10 +113,8 @@ function SensorDetails({ sensors }) {
return ( return (
<div className="grid gap-0.5 md:grid-cols-2"> <div className="grid gap-0.5 md:grid-cols-2">
<DetailCard title="Dock IR (raw)"> <DetailCard title="Dock IR">
<ValueRow label="Left" value={formatNumber(sensors?.infraredCharacterLeft)} /> <DockMiniStatus sensors={sensors} />
<ValueRow label="Omni" value={formatNumber(sensors?.infraredCharacterOmni)} />
<ValueRow label="Right" value={formatNumber(sensors?.infraredCharacterRight)} />
</DetailCard> </DetailCard>
<DetailCard title="Bumps & drops"> <DetailCard title="Bumps & drops">
@@ -200,3 +198,30 @@ function formatNumber(value, unit) {
if (value == null || Number.isNaN(value)) return '--'; if (value == null || Number.isNaN(value)) return '--';
return unit ? `${value} ${unit}` : value; return unit ? `${value} ${unit}` : value;
} }
function DockMiniStatus({ sensors }) {
const left = sensors?.infraredCharacterLeft;
const right = sensors?.infraredCharacterRight;
const omni = sensors?.infraredCharacterOmni;
const badge = (code) => (
<span className={`rounded px-1 py-0.25 text-[0.7rem] font-semibold ${code ? 'bg-emerald-600 text-white' : 'bg-slate-700 text-slate-300'}`}>
{code || '--'}
</span>
);
return (
<div className="flex items-center justify-between gap-1 text-xs text-slate-200">
<div className="flex items-center gap-1">
<span className="text-slate-400">L</span>
{badge(left)}
</div>
<div className="flex items-center gap-1">
<span className="text-slate-400">O</span>
{badge(omni)}
</div>
<div className="flex items-center gap-1">
<span className="text-slate-400">R</span>
{badge(right)}
</div>
</div>
);
}