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. */