Save pre-V2 motor controls and UI checkpoint

This commit is contained in:
2026-10-22 19:31:28 +02:00
parent a4d8f1b7af
commit 5e91c28f1e
15 changed files with 1041 additions and 236 deletions
+76 -29
View File
@@ -713,23 +713,27 @@ body[data-app-state="link_degraded"] .status-line .status-dot {
pointer-events: none;
}
.engage-button.charging { transition: background-color .24s ease, border-color .24s ease, color .2s ease; }
.engage-face { position: relative; display: grid; place-items: center; min-width: 74px; }
.engage-face > span { grid-area: 1/1; font-size: 11px; font-weight: 650; letter-spacing: .12em; }
.engage-face { position:relative; display:grid; place-items:center; width:7.5em; height:1em; min-width:0; text-align:center; font-size:clamp(52px,11cqw,90px); }
.engage-face > span { grid-area:1/1; justify-self:center; align-self:start; font-size:11px; font-weight:650; letter-spacing:.12em; white-space:nowrap; }
.engage-label {
position:relative;
color: transparent;
background: linear-gradient(105deg,#f4f6f3 20%,#ffffff 43%,#9da6a2 50%,#ffffff 57%,#f4f6f3 80%);
background-size: 250% 100%;
background-clip: text;
-webkit-background-clip: text;
text-align:center;
transition:transform 180ms cubic-bezier(.77,0,.175,1);
}
#engage .engage-label.is-appending { transform:translateX(var(--engage-append-shift,0px)); }
.arm:not(:disabled):not(.charging):not(.canceling):not(.state-transition) .engage-label { animation: engage-shimmer 4.8s linear infinite; }
.engage-suffix { display:inline-block; opacity:0; clip-path:inset(0 100% 0 0); transform:translateX(-.08em); transition:clip-path 180ms cubic-bezier(.77,0,.175,1),opacity 120ms ease-out,transform 180ms cubic-bezier(.77,0,.175,1); }
.state-transition .engage-suffix,.engage-suffix.is-visible { opacity:1; clip-path:inset(0); transform:translateX(0); }
.engage-progress-label {
color: #a9ed72;
clip-path: inset(0 calc(100% - var(--engage-progress)) 0 0);
transition: opacity .18s ease;
}
.state-transition .engage-face { animation: state-label .24s ease-out; }
@keyframes state-label { from {opacity:.35} to {opacity:1} }
@keyframes engage-shimmer { 0%,35%{background-position:160% 0} 80%,100%{background-position:-90% 0} }
.engage-button:hover { transform:none; }
.engage-button.pending { background:#405346; border-color:#516e56; }
@@ -1015,21 +1019,14 @@ body[data-telemetry-stale="true"] #battery-fill { opacity:.35; }
color: #7a8280;
margin-top: 8px;
}
.motor-modal { width: min(600px, calc(100vw - 40px)); }
.motor-head, .motor-row {
display: grid;
grid-template-columns: 70px 1fr 80px 75px;
align-items: center;
gap: 8px;
padding: 6px 0;
border-bottom: 1px solid #e0e4df;
font-size: 10.5px;
}
.motor-head {
font-weight: 500;
color: #727a7c;
border-bottom: 1.5px solid #c8cec6;
}
.motor-modal { width: min(860px, calc(100vw - 40px)); }
.battery-live { margin:14px 0 20px; padding:14px; border-radius:16px; background:#f0f3ef; }
.battery-live-grid { display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:10px; }
.battery-live-grid > div { min-width:0; padding:10px 11px; border-radius:12px; background:#fff; }
.battery-live-grid span,.battery-live-grid small { display:block; color:#727b77; font-size:11px; }
.battery-live-grid strong { display:block; margin-top:7px; color:#303936; font-size:18px; font-weight:500; font-variant-numeric:tabular-nums; }
.battery-live-grid small { margin-top:4px; }
.battery-live .note { margin:10px 0 0; line-height:1.5; font-size:12px; }
/* ── Toast ── */
.toast {
@@ -1158,7 +1155,7 @@ body #engage { width:calc(100% + 40px); margin-left:-20px; height:104px; padding
body #engage:hover, body #engage.pending, body #engage.danger { background:transparent; border:0; }
#engage::before, #engage::after { display:none; }
body #engage { align-items:flex-end; padding-bottom:12px; }
.engage-face > span { font-family:inherit; font-size:clamp(64px,7.3vw,90px); font-weight:200; letter-spacing:.015em; line-height:1; padding-bottom:.22em; margin-bottom:-.22em; }
.engage-face > span { font-family:inherit; font-size:clamp(52px,11cqw,90px); font-weight:200; letter-spacing:.015em; line-height:1; padding-bottom:.22em; margin-bottom:-.22em; }
#engage { --engage-green:#4cce73; --engage-glow:drop-shadow(0 0 2px rgba(76,206,115,.3)); }
#engage .engage-label { color:#777; background:none; animation:none; }
.engage-sheen-label { color:#fff; pointer-events:none; opacity:0; mask-image:linear-gradient(105deg,transparent 40%,#000 49%,#000 51%,transparent 60%); mask-size:300% 100%; mask-repeat:no-repeat; -webkit-mask-image:linear-gradient(105deg,transparent 40%,#000 49%,#000 51%,transparent 60%); -webkit-mask-size:300% 100%; -webkit-mask-repeat:no-repeat; }
@@ -1173,6 +1170,9 @@ body #engage { align-items:flex-end; padding-bottom:12px; }
.control-status[hidden] { display:none; }
@media (prefers-reduced-motion: reduce) {
.engage-sheen-label { display:none; }
#engage .engage-label.is-appending { transition:none; transform:none; }
.engage-suffix { transition:opacity var(--ui-duration,180ms) ease-out; }
.state-transition .engage-suffix { clip-path:inset(0); transform:none; }
}
.controls-grid {
--joy-size:min(54vh,calc(100vw - 480px),460px);
@@ -1265,12 +1265,8 @@ body:not([data-controls-enabled="true"]) .controls-grid { opacity:.65; }
.modal .form-feedback { margin:16px 0 0; padding:10px 12px; border-radius:12px; font-size:13px; line-height:1.5; color:#9b392e; background:#f5e6e3; }
.modal [hidden] { display:none !important; }
.modal [data-busy="true"] { cursor:progress; }
.motor-modal { width:min(650px,calc(100vw - 32px)); }
.modal .motor-head,.modal .motor-row { grid-template-columns:78px minmax(100px,1fr) 94px 86px; font-size:13px; gap:10px; padding:10px 0; }
.modal .motor-head { margin-top:18px; color:#727b80; font-size:12px; font-weight:400; }
.motor-row strong { font-size:12px; font-weight:500; }
.motor-row > span { font-size:12px; color:#727b80; }
.modal .motor-row .button { min-height:36px; font-size:12px; padding:8px 12px; }
.motor-modal { width:min(860px,calc(100vw - 32px)); }
@media (max-width:650px) { .battery-live-grid { grid-template-columns:repeat(2,minmax(0,1fr)); } }
.motor-other { margin-top:16px; }
.motor-other summary { cursor:pointer; padding:12px 0; color:#697278; font-size:13px; }
.quality-summary { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:10px; margin-top:18px; }
@@ -1289,8 +1285,6 @@ body:not([data-controls-enabled="true"]) .controls-grid { opacity:.65; }
@media (max-width:520px) {
.modal { padding:22px 18px; }
.modal .form-grid.two { grid-template-columns:1fr; }
.modal .motor-head,.modal .motor-row { grid-template-columns:56px minmax(80px,1fr) 74px 66px; gap:5px; font-size:12px; }
.modal .motor-row .button { padding:8px 6px; }
.quality-summary { gap:6px; }
.quality-summary div { padding:12px 8px; }
.quality-summary strong { font-size:17px; }
@@ -1385,7 +1379,6 @@ body #connect { height:var(--pill-lg); 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. */
@@ -1472,3 +1465,57 @@ body #connect { height:var(--pill-lg); border-radius:var(--pill-radius); }
@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; }
/* Motor scaling and control-plane fit: keep interaction areas inside their rows. */
.primary-control-zone { grid-template-rows:clamp(76px,14vh,108px) minmax(0,1fr) auto; }
.engage-header-zone { height:auto; min-height:0; align-self:stretch; }
body #engage { height:100%; min-height:0; }
.controls-grid { --joy-size:min(40vh,calc(100cqw - 168px),380px); min-height:0; align-content:center; }
.joystick { min-width:0; min-height:0; max-width:100%; max-height:100%; }
.joystick-pad,.heave-pad { min-width:0; min-height:0; flex:1 1 auto; overflow:hidden; }
.control-card { min-width:0; min-height:0; overflow:hidden; }
.control-toolbar { flex-wrap:nowrap; min-height:48px; padding:5px 0 0; gap:clamp(8px,1.5vw,18px); }
.control-toolbar .output-limit { min-width:0; }
.control-toolbar .output-limit > span { flex:0 1 120px; width:120px; }
.control-toolbar .range-pill { width:clamp(112px,20cqw,190px); flex:0 1 190px; }
body[data-motor-scaling="independent"] .control-toolbar .output-limit { opacity:.42; }
body[data-motor-scaling="independent"] .control-toolbar .range-pill { background:#dedfdf; }
.control-toolbar .range-pill :disabled { cursor:not-allowed; }
@media (max-height:610px) {
.primary-control-zone { grid-template-rows:clamp(64px,12vh,82px) minmax(0,1fr) auto; }
.controls-grid { --joy-size:min(34vh,calc(100cqw - 168px),300px); }
}
.motor-modal { width:min(720px,calc(100vw - 36px)); max-height:calc(100dvh - 28px); padding:clamp(18px,3vw,28px); overflow:auto; }
.motor-modal > h2 { margin:0 0 2px; }
.motor-scale-heading { display:flex; align-items:center; justify-content:space-between; padding:12px 0 7px; border-bottom:1px solid #e7eae7; color:#56635b; font-size:12px; }
.switch-control { display:flex; align-items:center; gap:9px; cursor:pointer; color:#34423a; font-size:12px; }
.switch-control input { position:absolute; opacity:0; width:1px; height:1px; }
.switch-track { position:relative; display:block; width:34px; height:20px; border-radius:999px; background:#d8ddda; transition:background-color 160ms ease-out; }
.switch-track::after { content:""; position:absolute; width:14px; height:14px; left:3px; top:3px; border-radius:50%; background:#fff; box-shadow:0 1px 2px #0002; transition:transform 160ms ease-out; }
.switch-control input:checked + .switch-track { background:#4d8767; }
.switch-control input:checked + .switch-track::after { transform:translateX(14px); }
.switch-control input:focus-visible + .switch-track { outline:2px solid #6598b5; outline-offset:3px; }
.motor-list { display:grid; }
.motor-row { display:grid; grid-template-columns:minmax(0,1fr) minmax(100px,1.5fr) 42px 76px; align-items:center; gap:12px; padding:12px 0; border-bottom:1px solid #e7eae7; }
.motor-name { min-width:0; height:34px; padding:0 8px; border:1px solid transparent; border-radius:6px; background:transparent; font-size:13px; }
.motor-name:hover,.motor-name:focus { border-color:#d8dfda; background:#fff; }
.motor-row .motor-scale { width:100%; min-width:0; height:22px; padding:0; border:0; background:transparent; accent-color:#4d8767; }
.motor-scale-value { color:#344b3c; font-size:12px; font-weight:600; font-variant-numeric:tabular-nums; text-align:right; }
.motor-reverse { min-height:30px; padding:5px 9px; font-size:11px; }
.motor-reverse:disabled { opacity:.42; cursor:not-allowed; }
.motor-channel { grid-column:1/-1; margin-top:-8px; color:#87918a; font-size:10px; }
.motor-mapping { margin-top:8px; }
.motor-mapping summary { padding:9px 0; color:#657168; font-size:12px; cursor:pointer; }
.motor-map-row { display:grid; grid-template-columns:65px minmax(0,1fr) 72px; align-items:center; gap:9px; padding:6px 0; border-bottom:1px solid #edf0ed; }
.motor-map-port { color:#4b5a50; font:11px ui-monospace,Consolas,monospace; }
.motor-map-row select { height:32px; font-size:12px; }
.motor-map-direction { color:#77827b; font-size:11px; text-align:right; }
body[data-motor-scaling="independent"] .control-toolbar .output-limit { opacity:.42; }
body[data-motor-scaling="independent"] .control-toolbar .range-pill { background:#dedfdf; }
.control-toolbar .range-pill :disabled { cursor:not-allowed; }
.controls-grid { align-content:start; }
.joystick-pad,.heave-pad { padding-top:0; }
.joystick { transform:translateY(-7px); }
@media (max-width:640px) { .motor-row { grid-template-columns:minmax(0,1fr) minmax(75px,1.3fr) 38px 70px; gap:7px; } .motor-map-row { grid-template-columns:54px minmax(0,1fr) 64px; gap:6px; } }
@media (max-width:520px) { .motor-modal { padding:18px 14px; } .motor-row { grid-template-columns:minmax(0,1fr) minmax(64px,1fr) 36px 66px; gap:5px; } .motor-name { font-size:12px; } .motor-reverse { padding:5px 6px; } }
@media (prefers-reduced-motion:reduce) { .switch-track,.switch-track::after { transition:none; } }