Make dashboard cards uniform and symmetric; stop clipping update text

The four welcome cards varied in shape and typography: the grid
produced a 3+1 orphan layout at wide widths, sub text was
single-line ellipsized (the updates card ended mid-sentence with
"…"), the Network card's values used a different size than the
other cards' sub text, and only the clickable cards carried a
chevron circle.

- Grid is now a symmetric 2×2 at wide widths (equal-height rows);
  the three-card Desktop-only role renders one balanced row
- Card sub text wraps instead of being cut off — "Sovran_SystemsOS
  keeps itself current" is fully visible at every width
- One type scale across all cards: 0.9rem titles, 0.8rem sub text
  (Network values aligned to it), 46px chips, uniform min-height
- Chevron circles removed from the cards — hover/focus lift is the
  click affordance, and every card has the same shape
- The updates card now reflects the real update state (failed,
  restart required, running) instead of only "updates available";
  it can no longer claim up-to-date while a restart is pending
This commit is contained in:
2026-09-08 20:08:52 -05:00
parent bbf53d089a
commit 4c22c2363b
2 changed files with 33 additions and 31 deletions
+15 -22
View File
@@ -358,11 +358,19 @@
.welcome-cards { .welcome-cards {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); grid-template-columns: repeat(2, 1fr);
grid-auto-rows: 1fr; /* both rows share the taller height → uniform cards */
gap: 16px; gap: 16px;
margin-top: 34px; margin-top: 34px;
} }
/* Three cards (Desktop-only role, no Bitcoin card) → one symmetric row */
@media (min-width: 681px) {
.welcome-cards:not(:has(#w-btc)) {
grid-template-columns: repeat(3, 1fr);
}
}
/* wrappers whose children become grid items */ /* wrappers whose children become grid items */
.welcome-dyn { display: contents; } .welcome-dyn { display: contents; }
@@ -384,7 +392,7 @@
} }
.w-network .ip-value { .w-network .ip-value {
font-size: 0.82rem; font-size: 0.8rem;
font-weight: 600; font-weight: 600;
color: var(--text); color: var(--text);
} }
@@ -408,9 +416,11 @@
position: relative; position: relative;
overflow: hidden; overflow: hidden;
min-width: 0; min-width: 0;
min-height: 88px; /* uniform card height everywhere */
} }
.widget:hover { border-color: var(--border-strong); } .widget:hover { border-color: var(--border-strong); }
.widget.clickable:hover { transform: translateY(-1px); }
.widget.clickable { cursor: pointer; } .widget.clickable { cursor: pointer; }
.widget.clickable:focus-visible { .widget.clickable:focus-visible {
@@ -465,11 +475,10 @@
.widget .sub { .widget .sub {
margin-top: 4px; margin-top: 4px;
font-size: 0.78rem; font-size: 0.8rem;
line-height: 1.45;
color: var(--text-2); color: var(--text-2);
white-space: nowrap; overflow-wrap: anywhere;
overflow: hidden;
text-overflow: ellipsis;
} }
.widget .sub b { color: var(--text); font-weight: 650; } .widget .sub b { color: var(--text); font-weight: 650; }
@@ -491,22 +500,6 @@
transition: width 0.6s cubic-bezier(0.3, 0.7, 0.3, 1); transition: width 0.6s cubic-bezier(0.3, 0.7, 0.3, 1);
} }
.w-chev {
margin-left: auto;
width: 30px;
height: 30px;
border-radius: 50%;
display: grid;
place-items: center;
color: var(--text-3);
flex-shrink: 0;
}
.w-chev svg { width: 15px; height: 15px; }
.widget.clickable:hover .w-chev {
color: var(--text-2);
background: rgba(255, 255, 255, 0.07);
}
/* ── Tiles area ─────────────────────────────────────────────────── */ /* ── Tiles area ─────────────────────────────────────────────────── */
@@ -232,7 +232,6 @@
'<div class="widget clickable" id="w-systems" role="button" tabindex="0" title="System status and router setup">' + '<div class="widget clickable" id="w-systems" role="button" tabindex="0" title="System status and router setup">' +
'<div class="widget-chip chip-green">' + icon("g-pulse") + '</div>' + '<div class="widget-chip chip-green">' + icon("g-pulse") + '</div>' +
'<div class="w-body"><h3>' + attentionTitle + '</h3><div class="sub">' + sub + '</div></div>' + '<div class="w-body"><h3>' + attentionTitle + '</h3><div class="sub">' + sub + '</div></div>' +
'<span class="w-chev">' + icon("g-chev") + '</span>' +
'</div>'; '</div>';
var wSys = document.getElementById("w-systems"); var wSys = document.getElementById("w-systems");
@@ -259,7 +258,6 @@
'<div class="w-body"><h3>' + escHtml(btc.name) + ' — syncing timechain</h3>' + '<div class="w-body"><h3>' + escHtml(btc.name) + ' — syncing timechain</h3>' +
'<div class="w-bar"><div class="w-bar-fill" style="width:' + pct + '%"></div></div>' + '<div class="w-bar"><div class="w-bar-fill" style="width:' + pct + '%"></div></div>' +
'<div class="sub">Block <b>' + blocks + '</b> · ' + pct + '% · <span class="warn">' + escHtml(eta) + '</span></div></div>' + '<div class="sub">Block <b>' + blocks + '</b> · ' + pct + '% · <span class="warn">' + escHtml(eta) + '</span></div></div>' +
'<span class="w-chev">' + icon("g-chev") + '</span>' +
'</div>'; '</div>';
} else { } else {
var btcDone = null; var btcDone = null;
@@ -270,20 +268,31 @@
'<div class="widget clickable" id="w-btc" role="button" tabindex="0" title="' + escHtml(btcDone.name) + ' details">' + '<div class="widget clickable" id="w-btc" role="button" tabindex="0" title="' + escHtml(btcDone.name) + ' details">' +
'<div class="widget-chip chip-btc"><img src="/static/icons/' + escHtml(btcDone.icon) + '.svg" alt="" style="width:46px;height:46px;display:block;object-fit:contain"/></div>' + '<div class="widget-chip chip-btc"><img src="/static/icons/' + escHtml(btcDone.icon) + '.svg" alt="" style="width:46px;height:46px;display:block;object-fit:contain"/></div>' +
'<div class="w-body"><h3>' + escHtml(btcDone.name) + '</h3><div class="sub"><span class="good">Fully synced</span>' + (blk ? ' · Block <b>' + blk + '</b>' : '') + '</div></div>' + '<div class="w-body"><h3>' + escHtml(btcDone.name) + '</h3><div class="sub"><span class="good">Fully synced</span>' + (blk ? ' · Block <b>' + blk + '</b>' : '') + '</div></div>' +
'<span class="w-chev">' + icon("g-chev") + '</span>' +
'</div>'; '</div>';
} }
} }
/* Updates */ /* Updates — mirror the sidebar's read of the update state, so the card
never claims "up to date" while an update failed or needs a restart */
var upd = (typeof window._lastUpdateCheck === "object" && window._lastUpdateCheck) ? window._lastUpdateCheck : null; var upd = (typeof window._lastUpdateCheck === "object" && window._lastUpdateCheck) ? window._lastUpdateCheck : null;
var hasUpdates = !!(upd && upd.available); var updStatus = (upd && upd.status) || "idle";
var updTitle, updSub, updChip;
if (updStatus === "failed") {
updTitle = "Update failed"; updSub = "Click to retry the update"; updChip = "chip-amber";
} else if (updStatus === "reboot_required") {
updTitle = "Restart required"; updSub = "Click to restart and finish the update"; updChip = "chip-amber";
} else if (updStatus === "running") {
updTitle = "Update in progress"; updSub = "Installing the new system generation"; updChip = "chip-amber";
} else if (upd && upd.available) {
updTitle = "Updates available"; updSub = "Click to review and update"; updChip = "chip-amber";
} else {
updTitle = "System is up to date"; updSub = "Sovran_SystemsOS keeps itself current"; updChip = "chip-green";
}
more += more +=
'<div class="widget clickable" id="w-updates" role="button" tabindex="0" title="Check for system updates">' + '<div class="widget clickable" id="w-updates" role="button" tabindex="0" title="Check for system updates">' +
'<div class="widget-chip ' + (hasUpdates ? "chip-amber" : "chip-green") + '">' + icon("g-update") + '</div>' + '<div class="widget-chip ' + updChip + '">' + icon("g-update") + '</div>' +
'<div class="w-body"><h3>' + (hasUpdates ? "Updates available" : "System is up to date") + '</h3>' + '<div class="w-body"><h3>' + updTitle + '</h3>' +
'<div class="sub">' + (hasUpdates ? 'Click to review and update' : 'Sovran_SystemsOS keeps itself current') + '</div></div>' + '<div class="sub">' + updSub + '</div></div>' +
'<span class="w-chev">' + icon("g-chev") + '</span>' +
'</div>'; '</div>';
$wcMore.innerHTML = more; $wcMore.innerHTML = more;