ui tweak fling

This commit is contained in:
legop3
2026-09-12 14:31:17 -04:00
parent 9ca039229a
commit b0389b5ddc
13 changed files with 63 additions and 51 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -12,8 +12,8 @@
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<!-- site-metadata:inject -->
<!-- analytics:inject -->
<script type="module" crossorigin src="/assets/index-DsdqxXyd.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-0J6fxLEg.css">
<script type="module" crossorigin src="/assets/index-UV1Q4Mdm.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-B5TEaoXl.css">
</head>
<body>
<div id="root"></div>
@@ -16,22 +16,23 @@ export default function AccessoriesExpansion({ roverId }) {
return (
<div className="pointer-events-none absolute inset-y-0 left-0 z-20 flex items-center">
<AccessoriesToggle
label="Accessories"
ariaLabel={open ? 'Hide accessory controls' : 'Show accessory controls'}
onClick={() => setOpen(!open)}
hud
className="pointer-events-auto !h-28"
/>
{open ? (
<div className="pointer-events-auto w-64 overflow-hidden rounded-r-xl bg-black/60 p-0.5">
{/* This is exactly the renderer mounted by AuxColumn. The desktop
wrapper changes available dimensions, never control behavior.
Content determines the normal panel height; max-height becomes a
scrolling boundary only for genuinely long accessory lists. */}
<RoverAccessoryControls roverId={roverId} className="max-h-[70vh]" />
</div>
) : null}
{/* The tab remains attached to the video's left wall. When open, this
single shell grows around both the unchanged tab position and the
controls to its right, so the controls are not rendered as a second
disconnected panel. Its height follows content until the shared
renderer reaches the scrolling boundary. */}
<div className="pointer-events-auto flex max-h-[70vh] items-center overflow-hidden rounded-r-xl bg-black/60">
<AccessoriesToggle
label="Accessories"
ariaLabel={open ? 'Hide accessory controls' : 'Show accessory controls'}
onClick={() => setOpen(!open)}
hud
className="!h-28"
/>
{open ? (
<RoverAccessoryControls roverId={roverId} className="w-64 max-h-[70vh]" />
) : null}
</div>
</div>
);
}
@@ -205,7 +205,7 @@ function RoverAuxColumn({ roverId, layout, className }) {
className="h-full"
headerAction={(
<AccessoriesToggle
label="Aux"
label="Back"
ariaLabel="Return to auxiliary controls"
compact
onClick={() => setShowAccessories(false)}
@@ -12,10 +12,17 @@ export default function AccessoriesToggle({
hud = false,
className = '',
}) {
const sizeClass = compact ? 'h-6 w-10' : 'h-full w-8';
// The Back action is still shorter than the vertical launcher, but it needs
// a normal touch target and a readable word instead of the previous tiny
// abbreviated control. The full launcher continues to fill the height its
// desktop or mobile parent assigns to it.
const sizeClass = compact ? 'h-8 w-14' : 'h-full w-8';
const toneClass = hud
? 'rounded-none border-0 bg-black/60 text-white/75 shadow-none hover:bg-black hover:text-white'
: 'rounded-xl border-2 border-cyan-300/70 bg-cyan-900 text-cyan-50 shadow-md hover:brightness-110 active:brightness-125';
// On desktop the tab sits inside the shared popout shell. Rounding only
// its exposed right edge preserves its left-wall attachment without
// introducing a separate accessory-specific panel treatment.
? 'rounded-r-xl border-0 bg-black/60 text-white shadow-none'
: 'rounded-xl border-2 border-cyan-300/70 bg-cyan-900 text-cyan-50 shadow-md';
return (
<button
@@ -25,10 +32,10 @@ export default function AccessoriesToggle({
triggerTouchHaptic('button');
onClick();
}}
className={`mobile-touch-control flex shrink-0 items-center justify-center text-sm font-semibold transition active:scale-[0.98] ${sizeClass} ${toneClass} ${className}`.trim()}
className={`mobile-touch-control flex shrink-0 items-center justify-center text-sm font-semibold ${sizeClass} ${toneClass} ${className}`.trim()}
>
{/* Full launchers use vertical writing in the narrow wall space. The
compact Aux return stays horizontal so it consumes only one heading. */}
compact Back action stays horizontal so it fits in the heading. */}
<span className={compact ? 'flex items-center' : 'flex items-center gap-1 [writing-mode:vertical-rl] rotate-180'}>
{!compact ? <FaPuzzlePiece className="shrink-0 text-sm" aria-hidden="true" /> : null}
<span>{label}</span>
@@ -21,6 +21,10 @@ function trimUnicode(value, maximumLength) {
}
const CARD_CLASS = 'mobile-touch-control rounded-xl border-2 px-2 py-1 text-slate-50';
// Sliders already contain a label row and a separate track. Giving that
// two-row control the same vertical padding as a one-row button made it much
// taller than its neighbors without improving its touch target.
const SLIDER_CARD_CLASS = 'mobile-touch-control rounded-xl border-2 px-1.5 py-0.5 text-slate-50';
const DISABLED_CLASS = 'disabled:cursor-not-allowed disabled:opacity-40';
function SliderControl({ peripheralId, control, disabled, send, value: storedValue }) {
@@ -97,7 +101,7 @@ function SliderControl({ peripheralId, control, disabled, send, value: storedVal
};
return (
<div className={`${CARD_CLASS} border-emerald-300/70 bg-emerald-900 ${disabled ? 'cursor-not-allowed opacity-40' : ''}`}>
<div className={`${SLIDER_CARD_CLASS} border-emerald-300/70 bg-emerald-900 ${disabled ? 'cursor-not-allowed opacity-40' : ''}`}>
<div className="flex items-center justify-between gap-1 text-sm font-semibold">
<span className="min-w-0 truncate">{control.name}</span>
<span className="shrink-0 font-mono text-emerald-100">{value}</span>
@@ -121,7 +125,7 @@ function SliderControl({ peripheralId, control, disabled, send, value: storedVal
onKeyDown={handleKeyDown}
onContextMenu={(event) => event.preventDefault()}
style={{ touchAction: 'none' }}
className="mobile-touch-control mobile-drag-control relative mt-1 h-7 w-full rounded-full border border-emerald-100/80 bg-emerald-950 shadow-inner focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-emerald-200"
className="mobile-touch-control mobile-drag-control relative mt-0.5 h-6 w-full rounded-full border border-emerald-100/80 bg-emerald-950 shadow-inner focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-emerald-200"
>
{/* An inset track gives the thumb room to remain entirely inside the
card at both endpoints without browser-specific range styling. */}
@@ -155,7 +159,7 @@ function ToggleControl({ peripheralId, control, disabled, send, value }) {
aria-pressed={enabled}
disabled={disabled}
onClick={toggle}
className={`${CARD_CLASS} ${DISABLED_CLASS} flex min-h-12 w-full items-center justify-between gap-1 font-semibold transition active:scale-[0.99] ${enabled ? 'border-emerald-300/70 bg-emerald-800 text-emerald-50' : 'border-amber-300/70 bg-amber-900 text-amber-50'}`}
className={`${CARD_CLASS} ${DISABLED_CLASS} flex min-h-12 w-full items-center justify-between gap-1 font-semibold ${enabled ? 'border-emerald-300/70 bg-emerald-800 text-emerald-50' : 'border-amber-300/70 bg-amber-900 text-amber-50'}`}
>
<span className="min-w-0 truncate">{control.name}</span>
<span className="shrink-0 text-xs">{enabled ? 'On' : 'Off'}</span>
@@ -221,7 +225,7 @@ function MomentaryControl({ peripheralId, control, disabled, send, value }) {
}
}}
onContextMenu={(event) => event.preventDefault()}
className={`${CARD_CLASS} ${DISABLED_CLASS} flex min-h-12 w-full items-center justify-center text-center font-semibold transition active:scale-[0.99] ${pressed ? 'border-fuchsia-200 bg-fuchsia-600 text-white' : 'border-fuchsia-300/70 bg-fuchsia-900 text-fuchsia-50'}`}
className={`${CARD_CLASS} ${DISABLED_CLASS} flex min-h-12 w-full items-center justify-center text-center font-semibold ${pressed ? 'border-fuchsia-200 bg-fuchsia-600 text-white' : 'border-fuchsia-300/70 bg-fuchsia-900 text-fuchsia-50'}`}
>
{control.name}
</button>
@@ -29,17 +29,17 @@ export default function RoverAccessoryControls({ roverId, headerAction = null, c
aria-label="Rover accessories"
>
{peripherals.map((peripheral, peripheralIndex) => {
const showHeading = peripherals.length > 1 || (peripheralIndex === 0 && headerAction);
return (
<section key={peripheral.id} className="mb-0.5 last:mb-0">
<section key={peripheral.id} className="mb-0.5 flex flex-col gap-0.5 last:mb-0">
{/* The firmware's array order is authoritative. Mapping directly over
it keeps physical authoring order intact across every UI host. */}
{showHeading ? (
<div className="mb-0.5 flex min-h-7 items-center gap-1 bg-black/60 px-1 text-xs font-semibold text-cyan-100">
<h3 className="min-w-0 flex-1 truncate">{peripheral.name}</h3>
{peripheralIndex === 0 ? headerAction : null}
</div>
) : null}
it keeps physical authoring order intact across every UI host.
Every peripheral keeps its heading even when it is the only
device, because its firmware-provided name identifies which
physical accessory owns the controls below it. */}
<div className="flex min-h-8 items-center gap-1 bg-black/60 px-1 text-xs font-semibold text-white">
<h3 className="min-w-0 flex-1 truncate">{peripheral.name}</h3>
{peripheralIndex === 0 ? headerAction : null}
</div>
<div className="flex flex-col gap-0.5">
{peripheral.controls.map((control) => (
<AccessoryControlField