diff --git a/SUB Control/tests/ui_windows.cjs b/SUB Control/tests/ui_windows.cjs index 1051102..b2bfbda 100644 --- a/SUB Control/tests/ui_windows.cjs +++ b/SUB Control/tests/ui_windows.cjs @@ -248,6 +248,13 @@ async function main(){ assert.equal(deckLayout.actionHeight,32);assert.equal(deckLayout.batteryHeight,62);assert.equal(deckLayout.connectHeight,62); assert(deckLayout.bottomGap>=10&&deckLayout.bottomGap<=24);assert.equal(deckLayout.overlay,'1'); assert.equal(deckLayout.actionRightGap,0);assert.equal(deckLayout.sidebarPadding,deckLayout.workspacePadding); + const batteryReadout=await page.locator('.battery-card').evaluate(el=>{ + const percent=el.querySelector('#battery-percent'),voltage=el.querySelector('#battery-title'); + return {percent:percent.textContent,voltage:voltage.textContent,border:getComputedStyle(el).borderTopWidth, + mainSize:parseFloat(getComputedStyle(percent).fontSize),voltageSize:parseFloat(getComputedStyle(voltage).fontSize)}; + }); + assert.match(batteryReadout.percent,/^\d+%$/);assert.match(batteryReadout.voltage,/^\d+\.\d{2} V$/); + assert.equal(batteryReadout.border,'0px');assert(batteryReadout.mainSize>batteryReadout.voltageSize); await page.screenshot({path:path.join(output,'ui-cleanup.png')}); assert.deepEqual(errors,[]); console.log('PASS: settings cancel/save/failure, saved baud, custom port rescan, motor confirmation/failure, heavy test, console pause/resume, keyboard gating, five dialogs at two window sizes; controller conditioning/read-only graphics, actual bridge values, mapping, focus/unplug/identity safety and dock filtering; mode locking during hold/armed/pending/unknown states, centered SVG close icons and bottom battery layout.'); diff --git a/SUB Control/web/app.js b/SUB Control/web/app.js index 5e704b1..a47c9bb 100644 --- a/SUB Control/web/app.js +++ b/SUB Control/web/app.js @@ -763,13 +763,17 @@ function render(s,confirmed=false){ const b=s.battery; if(b){ $('#battery-title').textContent=`${b.voltage.toFixed(2)} V`; - $('#battery-percent').textContent=`${Math.round(b.percent)}% • ${b.cells}s${!view.known||s.battery_stale?' · stale':''}`; + $('#battery-percent').textContent=`${Math.round(b.percent)}%`; + $('.battery-card').dataset.stale=String(!view.known||!!s.battery_stale); + $('.battery-card').title=`Estimated charge from voltage · ${b.cells}S${!view.known||s.battery_stale?' · stale reading':''} · Battery settings`; $('#battery-fill').style.width=`${b.percent}%`; $('#battery-fill').style.background=b.low?'var(--battery-low)':'var(--battery-charge)'; $('.battery-card').dataset.low=String(!!b.low); }else{ - $('#battery-title').textContent='—'; - $('#battery-percent').textContent='waiting'; + $('#battery-title').textContent='No voltage'; + $('#battery-percent').textContent='—'; + $('.battery-card').dataset.stale='false'; + $('.battery-card').title='Waiting for battery voltage · Battery settings'; $('#battery-fill').style.width='0'; $('.battery-card').dataset.low='false'; } diff --git a/SUB Control/web/index.html b/SUB Control/web/index.html index 1c0f964..1b80a0d 100644 --- a/SUB Control/web/index.html +++ b/SUB Control/web/index.html @@ -61,7 +61,7 @@ diff --git a/SUB Control/web/styles.css b/SUB Control/web/styles.css index 0fe96a9..2ececce 100644 --- a/SUB Control/web/styles.css +++ b/SUB Control/web/styles.css @@ -1417,17 +1417,18 @@ body #connect { height:var(--pill-lg); border-radius:var(--pill-radius); } .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; + border:0; border-radius:var(--pill-radius); background:#314b3a; 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-meter #battery-fill { display:block; height:100%; width:0; border-radius:inherit; background:var(--battery-charge); opacity:.55; transition:width .35s ease,background .2s ease,opacity .18s ease-out; } .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 small { color:#d7e8dc; font-size:12px; line-height:1; font-variant-numeric:tabular-nums; } +.battery-card strong { font-size:26px; 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[data-low="true"] { background:#4a3734; } +.battery-card[data-stale="true"] #battery-fill { opacity:.25; } .battery-card:focus-visible { outline:2px solid #a5c0d0; outline-offset:2px; } /* The same slender, rounded scrollbar is used by every scrollable panel. */ diff --git a/tests/ui_windows.cjs b/tests/ui_windows.cjs index 1051102..b2bfbda 100644 --- a/tests/ui_windows.cjs +++ b/tests/ui_windows.cjs @@ -248,6 +248,13 @@ async function main(){ assert.equal(deckLayout.actionHeight,32);assert.equal(deckLayout.batteryHeight,62);assert.equal(deckLayout.connectHeight,62); assert(deckLayout.bottomGap>=10&&deckLayout.bottomGap<=24);assert.equal(deckLayout.overlay,'1'); assert.equal(deckLayout.actionRightGap,0);assert.equal(deckLayout.sidebarPadding,deckLayout.workspacePadding); + const batteryReadout=await page.locator('.battery-card').evaluate(el=>{ + const percent=el.querySelector('#battery-percent'),voltage=el.querySelector('#battery-title'); + return {percent:percent.textContent,voltage:voltage.textContent,border:getComputedStyle(el).borderTopWidth, + mainSize:parseFloat(getComputedStyle(percent).fontSize),voltageSize:parseFloat(getComputedStyle(voltage).fontSize)}; + }); + assert.match(batteryReadout.percent,/^\d+%$/);assert.match(batteryReadout.voltage,/^\d+\.\d{2} V$/); + assert.equal(batteryReadout.border,'0px');assert(batteryReadout.mainSize>batteryReadout.voltageSize); await page.screenshot({path:path.join(output,'ui-cleanup.png')}); assert.deepEqual(errors,[]); console.log('PASS: settings cancel/save/failure, saved baud, custom port rescan, motor confirmation/failure, heavy test, console pause/resume, keyboard gating, five dialogs at two window sizes; controller conditioning/read-only graphics, actual bridge values, mapping, focus/unplug/identity safety and dock filtering; mode locking during hold/armed/pending/unknown states, centered SVG close icons and bottom battery layout.'); diff --git a/web/app.js b/web/app.js index 5e704b1..a47c9bb 100644 --- a/web/app.js +++ b/web/app.js @@ -763,13 +763,17 @@ function render(s,confirmed=false){ const b=s.battery; if(b){ $('#battery-title').textContent=`${b.voltage.toFixed(2)} V`; - $('#battery-percent').textContent=`${Math.round(b.percent)}% • ${b.cells}s${!view.known||s.battery_stale?' · stale':''}`; + $('#battery-percent').textContent=`${Math.round(b.percent)}%`; + $('.battery-card').dataset.stale=String(!view.known||!!s.battery_stale); + $('.battery-card').title=`Estimated charge from voltage · ${b.cells}S${!view.known||s.battery_stale?' · stale reading':''} · Battery settings`; $('#battery-fill').style.width=`${b.percent}%`; $('#battery-fill').style.background=b.low?'var(--battery-low)':'var(--battery-charge)'; $('.battery-card').dataset.low=String(!!b.low); }else{ - $('#battery-title').textContent='—'; - $('#battery-percent').textContent='waiting'; + $('#battery-title').textContent='No voltage'; + $('#battery-percent').textContent='—'; + $('.battery-card').dataset.stale='false'; + $('.battery-card').title='Waiting for battery voltage · Battery settings'; $('#battery-fill').style.width='0'; $('.battery-card').dataset.low='false'; } diff --git a/web/index.html b/web/index.html index 1c0f964..1b80a0d 100644 --- a/web/index.html +++ b/web/index.html @@ -61,7 +61,7 @@ diff --git a/web/styles.css b/web/styles.css index 0fe96a9..2ececce 100644 --- a/web/styles.css +++ b/web/styles.css @@ -1417,17 +1417,18 @@ body #connect { height:var(--pill-lg); border-radius:var(--pill-radius); } .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; + border:0; border-radius:var(--pill-radius); background:#314b3a; 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-meter #battery-fill { display:block; height:100%; width:0; border-radius:inherit; background:var(--battery-charge); opacity:.55; transition:width .35s ease,background .2s ease,opacity .18s ease-out; } .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 small { color:#d7e8dc; font-size:12px; line-height:1; font-variant-numeric:tabular-nums; } +.battery-card strong { font-size:26px; 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[data-low="true"] { background:#4a3734; } +.battery-card[data-stale="true"] #battery-fill { opacity:.25; } .battery-card:focus-visible { outline:2px solid #a5c0d0; outline-offset:2px; } /* The same slender, rounded scrollbar is used by every scrollable panel. */