fix(lnd-ui,bitcoin-ui): canonical app icon, header stacking, square QR, button parity

Five review fixes across both node UIs.

1. LND now uses the app-store icon. It was shipping its own 182KB lnd.svg
   while the app store, My Apps and the signed catalog all render
   neode-ui/public/assets/img/app-icons/lnd.png (catalog.json points at
   /assets/img/app-icons/lnd.png). Same file is now vendored into the
   image, so the app header, the launcher and the store agree. That icon is
   a full-bleed square with an opaque white background rather than a
   transparent glyph, so it fills the frame and is clipped to the inner
   radius — exactly how bitcoin-ui frames its own icon — instead of being
   inset with padding on a dark plate.

2. Header no longer squishes at tablet widths. Both headers had a single
   768px breakpoint, so between 768 and 1024 the title and description got
   crushed against the controls on the right (four status cards on
   bitcoin-ui) and overlapped. Both now use three breakpoints: fully
   stacked and centred below 768, logo + title on one row with the controls
   wrapped underneath below 1024, single row above. The app name and
   description are centred on mobile.

3. QR codes stay square. .conn-layout is a flex row on desktop and flex
   items stretch by default, so the white QR plate was being pulled to the
   height of the fields column and the square QR sat letterboxed in it. The
   plate is now a fixed square inside a black glass panel that absorbs the
   extra height, so the panel matches the fields and the QR stays square.

4. Buttons read as one family. The Settings button and both modal dismiss
   buttons used the flat .glass-button while every other button on the page
   used .info-card-button; they now all use the latter, via new .compact
   (inline) and .icon-only (square) variants so the shared style works at
   button size rather than only as a full-width card.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-08-02 19:00:31 -04:00
co-authored by Claude Opus 5
parent ba493fb0fc
commit 2e68384234
4 changed files with 146 additions and 43 deletions
+76 -28
View File
@@ -80,6 +80,13 @@
-webkit-mask-composite: xor; mask-composite: exclude; pointer-events: none; transition: all 0.3s ease;
}
.info-card-button > * { position: relative; z-index: 2; }
/* Same treatment, sized for a normal inline button rather than a
full-width card, so every button on the page reads as one family. */
.info-card-button.compact {
width: auto; display: inline-flex; align-items: center; justify-content: center;
gap: 0.5rem; padding: 0.65rem 1rem; text-align: center;
font-size: 0.875rem; font-weight: 500;
}
.info-card-button:hover {
transform: translateY(-2px); background: rgba(0,0,0,0.35);
box-shadow: 0 12px 32px rgba(0,0,0,0.6), inset 0 1px 0 rgba(255,255,255,0.25);
@@ -108,17 +115,44 @@
}
.gradient-button:active { transform: translateY(1px); }
/* Logo — LND purple gradient ring */
/* Logo — LND purple gradient ring. The app-store icon is a full-bleed
square with an opaque white background (not a transparent glyph), so
it fills the frame and gets clipped to the inner radius, exactly the
way bitcoin-ui frames its icon. */
.logo-gradient-border {
position: relative; border-radius: 16px; padding: 3px;
background: linear-gradient(135deg, rgba(167,139,250,0.85) 0%, rgba(0,0,0,0.8) 100%);
box-shadow: 0 8px 24px rgba(0,0,0,0.5); display: inline-block;
}
.logo-gradient-border::after { content: ''; position: absolute; inset: 3px; border-radius: 13px; background: #0d0b16; z-index: 0; }
.logo-gradient-border img { border-radius: 13px; display: block; position: relative; z-index: 1; width: 64px; height: 64px; object-fit: contain; padding: 6px; }
.logo-gradient-border::after { content: ''; position: absolute; inset: 3px; border-radius: 13px; background: #fff; z-index: 0; }
.logo-gradient-border img { border-radius: 13px; display: block; position: relative; z-index: 1; width: 64px; height: 64px; object-fit: cover; }
.container { max-width: 1400px; margin: 0 auto; padding: 2rem; padding-bottom: 4rem; }
/* ── App header ──────────────────────────────────────────────────
Three deliberate breakpoints, because the identity block and the
controls both need room before they sit side by side:
< 768px fully stacked, everything centred
< 1024px logo + text on one row, controls stacked underneath
(this is the width where the title used to squish
against the controls)
>= 1024px single row */
.app-header { display: flex; flex-direction: column; align-items: center; gap: 1rem; }
.app-header-id { display: flex; flex-direction: column; align-items: center; gap: 1rem; width: 100%; min-width: 0; }
.app-header-text { min-width: 0; width: 100%; text-align: center; }
.app-header-title { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; justify-content: center; margin-bottom: 0.25rem; }
.app-header-actions { display: flex; align-items: center; gap: 0.75rem; justify-content: center; width: 100%; flex-wrap: wrap; }
@media (min-width: 768px) {
.app-header-id { flex-direction: row; align-items: center; }
.app-header-text { text-align: left; }
.app-header-title { justify-content: flex-start; }
}
@media (min-width: 1024px) {
.app-header { flex-direction: row; align-items: center; }
.app-header-actions { width: auto; justify-content: flex-end; flex-shrink: 0; }
}
/* Layout utilities */
.flex { display: flex; } .flex-col { flex-direction: column; }
.items-center { align-items: center; } .items-start { align-items: flex-start; } .items-end { align-items: flex-end; }
@@ -250,7 +284,15 @@
.conn-select:focus { border-color: rgba(255,255,255,0.25); }
.conn-select option { background: #1a1a2e; color: white; }
.conn-layout { display: flex; flex-direction: column; gap: 1.5rem; }
.qr-box { flex-shrink: 0; width: 100%; max-width: 216px; aspect-ratio: 1; margin: 0 auto; background: white; border-radius: 0.75rem; padding: 0.6rem; display: flex; align-items: center; justify-content: center; }
/* The QR plate stays square. It sits inside a glass panel that takes
the height of the fields column, so a tall fields list no longer
stretches the white plate into a letterboxed rectangle. */
.qr-panel {
flex-shrink: 0; display: flex; align-items: center; justify-content: center;
padding: 1rem; background: rgba(0, 0, 0, 0.45);
border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 0.9rem;
}
.qr-box { width: 100%; max-width: 200px; aspect-ratio: 1; background: #fff; border-radius: 0.6rem; padding: 0.55rem; display: flex; align-items: center; justify-content: center; }
.qr-box img { width: 100%; height: auto; display: block; image-rendering: pixelated; }
.conn-fields { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.75rem; }
@@ -301,7 +343,7 @@
.sm-grid-cols-3 { grid-template-columns: repeat(3, minmax(0,1fr)); }
.balance-hero { grid-template-columns: repeat(3, minmax(0,1fr)); }
.conn-layout { flex-direction: row; }
.qr-box { margin: 0; width: 216px; }
.qr-box { width: 200px; }
}
@media (min-width: 768px) {
.md-grid-cols-2 { grid-template-columns: repeat(2, minmax(0,1fr)); }
@@ -342,30 +384,32 @@
<div class="container">
<!-- ── Header ──────────────────────────────────────────────────── -->
<div class="glass-card p-6 mb-6">
<div class="flex flex-col md-flex-row items-center gap-4">
<div class="flex-shrink-0">
<div class="logo-gradient-border">
<img src="assets/img/app-icons/lnd.svg" alt="LND" onerror="this.style.display='none'" />
<div class="app-header">
<div class="app-header-id">
<div class="flex-shrink-0">
<div class="logo-gradient-border">
<img src="assets/img/app-icons/lnd.png" alt="LND" onerror="this.style.display='none'" />
</div>
</div>
<div class="app-header-text flex-1">
<div class="app-header-title">
<h1 class="text-2xl font-bold text-white">LND</h1>
<span class="pill" id="headerStatusPill">
<span class="status-dot-sm bg-grey" id="headerStatusDot"></span>
<span id="headerStatusText">Connecting…</span>
</span>
<span class="pill" id="headerNetworkPill" style="display:none"></span>
</div>
<p class="text-white-70 text-sm" id="headerAlias">Lightning Network Daemon for instant Bitcoin payments</p>
<p class="text-xs text-white-40 mt-1 font-mono truncate" id="headerPubkey">&mdash;</p>
</div>
</div>
<div class="flex-1 min-w-0 w-full">
<div class="flex items-center gap-3 flex-wrap mb-1">
<h1 class="text-2xl font-bold text-white">LND</h1>
<span class="pill" id="headerStatusPill">
<span class="status-dot-sm bg-grey" id="headerStatusDot"></span>
<span id="headerStatusText">Connecting…</span>
</span>
<span class="pill" id="headerNetworkPill" style="display:none"></span>
</div>
<p class="text-white-70 text-sm" id="headerAlias">Lightning Network Daemon for instant Bitcoin payments</p>
<p class="text-xs text-white-40 mt-1 font-mono truncate" id="headerPubkey">&mdash;</p>
</div>
<div class="flex items-center gap-3 flex-shrink-0">
<div class="app-header-actions">
<div class="unit-switch" role="group" aria-label="Display unit">
<button id="unitSats" class="active" onclick="setUnit('sats')">sats</button>
<button id="unitBtc" onclick="setUnit('btc')">BTC</button>
</div>
<button onclick="refreshAll()" class="glass-button flex items-center gap-2 px-4 py-2-5 rounded-lg text-sm font-medium" id="refreshBtn" title="Refresh">
<button onclick="refreshAll()" class="info-card-button compact" id="refreshBtn" title="Refresh">
<svg style="width:1.1rem;height:1.1rem" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" id="refreshIcon">
<path stroke-linecap="round" stroke-linejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
@@ -751,8 +795,10 @@
</div>
<div class="conn-layout" id="connDisplay">
<div class="qr-box" id="lndQrBox">
<div style="color:#999;font-size:12px;text-align:center;padding:2rem">Loading…</div>
<div class="qr-panel">
<div class="qr-box" id="lndQrBox">
<div style="color:#999;font-size:12px;text-align:center;padding:2rem">Loading…</div>
</div>
</div>
<div class="conn-fields">
<div>
@@ -795,8 +841,10 @@
<h2 class="text-xl font-semibold text-white mb-1">Node ID</h2>
<p class="text-white-70 text-sm mb-4">Share this so other nodes can open a channel with you.</p>
<div class="conn-layout">
<div class="qr-box" id="nodeIdQrBox">
<div style="color:#999;font-size:12px;text-align:center;padding:2rem">&mdash;</div>
<div class="qr-panel">
<div class="qr-box" id="nodeIdQrBox">
<div style="color:#999;font-size:12px;text-align:center;padding:2rem">&mdash;</div>
</div>
</div>
<div class="conn-fields">
<div>
@@ -896,7 +944,7 @@
<div class="glass-card p-6">
<div class="flex justify-between items-center gap-3 mb-3">
<h3 class="text-lg font-semibold text-white">Node Logs</h3>
<button onclick="loadLogs()" class="glass-button px-3 py-1-5 rounded-lg text-xs font-medium">Refresh</button>
<button onclick="loadLogs()" class="info-card-button compact" style="font-size:0.75rem;padding:0.45rem 0.8rem">Refresh</button>
</div>
<div class="logs-box" id="logsContent">Open this tab to load logs.</div>
</div>