Improve controller input, console, and UI consistency

This commit is contained in:
2026-10-02 08:02:41 +02:00
parent 0093f3f4aa
commit 31f720a316
18 changed files with 1427 additions and 175 deletions
+175 -2
View File
@@ -756,7 +756,7 @@ body[data-telemetry-stale="true"] #battery-fill { opacity:.35; }
background: #4e7140;
border-color: #78ad5a;
}
.danger {
.danger {
--engage-progress: 0%;
background: #ad3f38;
color: #ffffff;
@@ -947,7 +947,7 @@ body[data-telemetry-stale="true"] #battery-fill { opacity:.35; }
border-radius: 24px;
background: rgba(247, 248, 245, 0.96);
color: #1a1e1f;
box-shadow:
box-shadow:
0 30px 80px -12px rgba(0, 0, 0, 0.42),
0 0 0 1px rgba(0, 0, 0, 0.06),
0 10px 24px -6px rgba(0, 0, 0, 0.16);
@@ -1298,3 +1298,176 @@ body:not([data-controls-enabled="true"]) .controls-grid { opacity:.65; }
@media (prefers-reduced-motion:reduce) {
.modal[open],.modal.closing { animation:none; }
}
/* Input selection stays separate from vehicle arming and command ownership. */
#engage:focus,#engage:focus-visible { outline:none!important; box-shadow:none!important; }
.controls-workspace { container-type:inline-size; }
.input-mode-controls { display:flex; align-items:center; gap:6px; flex-shrink:0; -webkit-app-region:no-drag; }
.input-mode-switch { display:flex; padding:3px; gap:2px; background:#dedede; border-radius:999px; }
.input-mode-switch button,.controller-settings { border:0; border-radius:999px; background:transparent; color:#686868; padding:7px 11px; font-size:12px; cursor:pointer; }
.input-mode-switch button[aria-pressed="true"] { background:#fafafa; color:#252525; box-shadow:0 1px 3px #0000000c; }
.controller-settings { background:#dedede; }
.controller-status { margin:0; height:24px; flex-shrink:0; font-size:12px; line-height:20px; color:#6d7478; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
body[data-input-mode="joystick"] .key-hint { visibility:hidden; }
body[data-input-mode="joystick"] :is(#joystick-canvas,#heave) { pointer-events:none; cursor:default; }
.controls-grid { --joy-size:min(52vh,calc(100cqw - 136px),460px); }
.control-toolbar { flex-wrap:wrap; gap:8px 14px; }
.primary-control-zone { grid-template-rows:120px minmax(0,1fr) auto; }
.control-toolbar { min-height:52px; }
.engage-face > span { font-size:clamp(52px,11cqw,90px); }
.controller-mapping-head,.controller-mapping-row { display:grid; grid-template-columns:1fr 1.25fr 50px; gap:14px; align-items:center; }
.controller-mapping-head { color:#777; font-size:12px; margin-bottom:8px; }
.controller-mapping-row { margin:8px 0; }
.modal .controller-mapping-row input[type="checkbox"] { width:22px; height:22px; min-height:0; margin:auto; padding:0; accent-color:#527f9b; }
.controller-mapping-row label { display:flex; align-items:center; }
#controller-axes { min-height:22px; margin:12px 0; font-variant-numeric:tabular-nums; overflow-wrap:anywhere; color:#65737d; font-size:12px; }
#controller-form > label { margin-top:18px; }
.visually-hidden { position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; border:0; }
/* Read-only local console, not a modal: driving stays available while it is open. */
.app-shell.console-open { grid-template-columns:260px minmax(0,1fr) 340px; }
.console-dock[hidden] { display:none!important; }
.console-dock { display:flex; flex-direction:column; min-width:0; min-height:0; padding:18px 16px; background:#f5f5f5; border-left:1px solid #d8dcde; color:#29343b; color-scheme:light; }
.console-dock-header { display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:14px; }
.console-dock-header small { font-size:11px; color:#81888d; }
.console-dock-header h2 { font-size:22px; font-weight:400; margin:4px 0 0; letter-spacing:-.02em; }
.console-close { width:38px; height:38px; flex-shrink:0; border:0; border-radius:999px; background:#e7e8e9; color:#48525a; font-size:25px; cursor:pointer; }
.console-toolbar { display:flex; align-items:center; justify-content:space-between; gap:8px; }
.console-toolbar label { flex:1; min-width:0; }
.console-toolbar select,.console-toolbar button { height:34px; border:0; border-radius:999px; background:#e7e9ea; color:#42525e; font-size:12px; padding:0 12px; }
.console-toolbar select { width:100%; }
.console-toolbar button { cursor:pointer; }
.console-state { min-height:28px; margin:10px 0 8px; font-size:11px; color:#7e868b; line-height:1.4; }
.console-streams { display:grid; grid-template-rows:1fr 1fr; gap:14px; min-height:0; flex:1; }
.console-side { display:flex; flex-direction:column; min-height:0; }
.console-side > header { display:flex; align-items:center; gap:6px; padding:0 4px 8px; font-size:12px; font-weight:500; color:#435966; }
.console-side > header small { margin-left:auto; font-size:11px; font-weight:300; color:#899198; }
.console-side > header i { width:6px; height:6px; border-radius:50%; background:#5888a3; }
.console-side > header .tx-dot { background:#659287; }
.console-output { min-height:0; flex:1; overflow:auto; overscroll-behavior:contain; padding:0 8px; border:1px solid #e0e3e5; border-radius:12px; background:#fafbfc; color:#343d43; font:12px/1.5 "Helvetica Neue",Helvetica,Arial,sans-serif; }
.console-row { border-bottom:1px solid #e9edef; }
.console-row:last-child { border-bottom:0; }
.console-row summary { display:flex; gap:8px; cursor:pointer; padding:10px 0; list-style:none; }
.console-row summary::-webkit-details-marker { display:none; }
.console-row time { flex-shrink:0; color:#929ba1; font:10px/1.8 ui-monospace,Consolas,monospace; font-variant-numeric:tabular-nums; }
.console-message { min-width:0; display:block; overflow-wrap:anywhere; }
.console-message strong { display:block; font-size:11px; font-weight:500; color:#517c97; letter-spacing:.025em; }
.console-row[data-tone="command"] strong { color:#467a69; }
.console-row[data-tone="warning"] strong { color:#aa664b; }
.console-fields { display:flex; flex-wrap:wrap; gap:2px 8px; font-size:11px; }
.console-fields i { color:#929a9f; font-style:normal; }
.console-fields b { font-weight:400; color:#45535d; }
.console-row pre { margin:0 0 10px; padding:10px; border-radius:8px; background:#eef1f3; color:#52616c; font:11px/1.5 ui-monospace,Consolas,monospace; white-space:pre-wrap; overflow-wrap:anywhere; }
.console-empty { color:#959da3; font-size:12px; line-height:1.6; margin:14px 0; }
@media (max-height:610px) { .primary-control-zone { grid-template-rows:100px minmax(0,1fr) auto; } .controls-grid { --joy-size:min(46vh,calc(100cqw - 136px),420px); } }
@media (max-width:1000px) {
.app-shell.console-open { grid-template-columns:260px minmax(0,1fr); }
.console-dock { position:absolute; top:0; right:0; bottom:0; width:min(340px,calc(100vw - 32px)); z-index:12; box-shadow:-8px 0 24px #00000012; border-radius:12px 0 0 12px; }
}
/* Three shared sizes: compact 32px, standard 40px, primary 62px. */
:root {
--pill-sm:32px; --pill-md:40px; --pill-lg:62px; --pill-radius:999px;
--battery-charge:#63cb85; --battery-low:#e98a7b;
}
.port-row select,.icon-button { height:var(--pill-sm); }
.icon-button { width:var(--pill-sm); }
body #connect { height:var(--pill-lg); border-radius:var(--pill-radius); }
.input-mode-switch { height:var(--pill-md); padding:4px; }
.input-mode-switch button { height:var(--pill-sm); padding:0 12px; }
.controller-settings { height:var(--pill-md); padding:0 14px; }
.input-mode-switch button:disabled { opacity:.65!important; }
.input-mode-switch button[aria-pressed="true"]:disabled { opacity:1!important; }
.control-toolbar .range-pill,.control-toolbar .config-btn { height:var(--pill-md); border-radius:var(--pill-radius); }
.control-toolbar .range-pill button { height:var(--pill-md); }
.window-actions button { width:var(--pill-md); height:var(--pill-md); }
.console-toolbar select,.console-toolbar button { height:var(--pill-sm); border-radius:var(--pill-radius); }
.modal input,.modal select,.modal .number-pill { height:var(--pill-md); border-radius:var(--pill-radius); }
.modal .number-pill input,.modal .number-pill button { height:calc(var(--pill-md) - 2px); }
.modal .button { min-height:var(--pill-md); padding:8px 18px; border-radius:var(--pill-radius); }
.modal .motor-row .button { min-height:var(--pill-sm); padding:6px 12px; }
.modal .controller-mapping-row input[type="checkbox"] { height:22px; border-radius:4px; }
/* Fixed SVG geometry avoids font baselines shifting the close icon. */
.modal-close,.modal-close:hover,.console-close,.console-close:hover,.window-actions .window-close,.window-actions .window-close:hover {
display:grid; place-items:center; width:var(--pill-md); height:var(--pill-md);
padding:0; border:0; border-radius:0; background:transparent; box-shadow:none;
}
.modal-close svg,.console-close svg { display:block; width:20px; height:20px; fill:none; stroke:currentColor; stroke-width:1.7; stroke-linecap:round; }
.modal-close:hover,.console-close:hover { color:#222; }
.heave-knob { height:var(--pill-md); background:linear-gradient(145deg,#737373,#454545); }
/* Activity has a heading, compact event history, then explicit actions. */
.telemetry-zone { gap:16px; }
.deck-status-bar { flex-shrink:0; margin-top:0; padding-top:12px; }
.activity-heading { display:block; margin:0 0 8px; color:#eee; font-size:14px; font-weight:650; }
.activity-history { gap:4px; margin:0 0 10px; }
.activity-actions { display:flex; gap:8px; }
.activity-actions .logs-link {
display:inline-flex; align-items:center; justify-content:center; box-sizing:border-box;
height:var(--pill-sm); min-width:76px; padding:0 14px; margin:0;
border:0; border-radius:var(--pill-radius); background:#414141; color:#eee;
font-size:12px; font-weight:400; letter-spacing:0; line-height:1; cursor:pointer;
}
.activity-actions .logs-link:hover { background:#4b4b4b; }
.activity-actions .logs-link:focus-visible { outline:2px solid #a5c0d0; outline-offset:2px; }
.activity-actions .logs-link[aria-expanded="true"] { background:#555; }
/* Battery is the final deck item: text floats above the charge fill. */
.battery-card {
position:relative; display:block; flex-shrink:0; width:100%; height:var(--pill-lg);
min-height:var(--pill-lg); margin:0; padding:0; overflow:hidden;
border:1px solid #565956; border-radius:var(--pill-radius); background:#383c39;
color:#f2f5f2; isolation:isolate;
}
.battery-meter { position:absolute; inset:0; z-index:0; overflow:hidden; border-radius:inherit; pointer-events:none; }
.battery-meter #battery-fill { display:block; height:100%; width:0; border-radius:inherit; background:var(--battery-charge); opacity:.25; transition:width .35s ease,background .2s ease; }
.battery-card .battery-copy { position:absolute; inset:0; z-index:1; display:flex; flex-direction:row; align-items:center; justify-content:space-between; gap:8px; padding:0 18px; pointer-events:none; }
.battery-summary { display:flex; flex-direction:column; gap:3px; flex-shrink:0; }
.battery-card small { color:#c3cec5; font-size:11px; line-height:1; }
.battery-card strong { font-size:20px; font-weight:400; line-height:1.15; font-variant-numeric:tabular-nums; }
.battery-card i { align-self:auto; color:#d9e0da; text-align:right; font-size:12px; line-height:1.35; font-style:normal; }
.battery-card[data-low="true"] { border-color:#a87468; }
.battery-card:focus-visible { outline:2px solid #a5c0d0; outline-offset:2px; }
/* The same slender, rounded scrollbar is used by every scrollable panel. */
*::-webkit-scrollbar { width:8px; height:8px; }
*::-webkit-scrollbar-track,*::-webkit-scrollbar-corner { background:transparent; }
*::-webkit-scrollbar-thumb { min-height:28px; border:2px solid transparent; border-radius:var(--pill-radius); background:#aeb6ba; background-clip:padding-box; }
*::-webkit-scrollbar-thumb:hover { background:#87959e; background-clip:padding-box; }
*::-webkit-scrollbar-button { display:none; width:0; height:0; }
.sidebar-deck::-webkit-scrollbar-thumb { background:#666; background-clip:padding-box; }
.modal { scrollbar-gutter:stable; }
@supports not selector(::-webkit-scrollbar) { * { scrollbar-width:thin; scrollbar-color:#aeb6ba transparent; } }
@media (max-height:610px) { .telemetry-zone { gap:12px; } }
/* Shared panel geometry and quiet, presentational motion. Never ease command data. */
:root { --panel-inset:20px; --ui-gap:12px; --ui-duration:180ms; --ui-ease:cubic-bezier(.2,0,.2,1); }
.sidebar-deck,.controls-workspace,.console-dock { padding:var(--panel-inset); }
.sidebar-deck,.telemetry-zone,.deck-connection,.port-row,.activity-actions,.input-mode-controls,.console-toolbar,.console-streams { gap:var(--ui-gap); }
.deck-connection { padding-bottom:var(--ui-gap); }
.workspace-topbar,.console-dock-header { min-height:var(--pill-md); height:var(--pill-md); margin-bottom:var(--ui-gap); }
.port-row select,.port-row .icon-button { height:var(--pill-md); }
.port-row .icon-button { width:var(--pill-md); }
.activity-actions { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); }
.activity-actions .logs-link { min-width:0; width:100%; }
.console-dock-header h2 { margin:0; }
.console-toolbar { margin-bottom:var(--ui-gap); }
.console-state { min-height:0; margin:0 0 var(--ui-gap); font-size:12px; }
.console-side > header { padding-left:0; padding-right:0; }
.console-side > header i { background:#b4bdc2; transition:background-color var(--ui-duration) ease-out,box-shadow var(--ui-duration) ease-out; }
.console-side > header .rx-dot.lit { background:#4fa4cf; box-shadow:0 0 5px #4fa4cf66; }
.console-side > header .tx-dot.lit { background:#56ae8a; box-shadow:0 0 5px #56ae8a66; }
:is(button,select,input,.input-mode-switch,.console-row summary,.heave-knob,.heave-fill,.control-status,.battery-card)::before,
:is(button,select,input,.input-mode-switch,.console-row summary,.heave-knob,.heave-fill,.control-status,.battery-card)::after,
:is(button,select,input,.input-mode-switch,.console-row summary,.heave-knob,.heave-fill,.control-status,.battery-card) {
transition-property:background-color,border-color,color,opacity,box-shadow,filter;
transition-duration:var(--ui-duration); transition-timing-function:var(--ui-ease);
}
/* Keep pointer positions immediate; the existing conditioning owns motor easing. */
.modal[open],.console-dock:not([hidden]),.form-feedback:not([hidden]),.console-state:not([hidden]),.console-row pre { animation:ui-fade-in var(--ui-duration) var(--ui-ease); }
.modal.closing,.console-dock.closing { animation:ui-fade-out var(--ui-duration) ease-out forwards; pointer-events:none; }
@keyframes ui-fade-in { from { opacity:0; } to { opacity:1; } }
@keyframes ui-fade-out { from { opacity:1; } to { opacity:0; } }
.controller-status { transition:opacity var(--ui-duration) ease-out; }
.controller-status[data-quiet="true"] { opacity:0; pointer-events:none; }