aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-08-04 01:50:24 -0700
committerAdam Soltys <[email protected]>2026-08-04 01:50:24 -0700
commitc19fd63f1687c8fdfd38a8446e7cf936342ba4ef (patch)
tree0238a55a48d2a6f387a4547df9320428b3e8c7ad /src
parent7b7f2b5e8a6f2abc05fef94e0a8f6cb9dd0b9928 (diff)
downloadcoinosv3-c19fd63f1687c8fdfd38a8446e7cf936342ba4ef.tar.gz
coinosv3-c19fd63f1687c8fdfd38a8446e7cf936342ba4ef.zip
Avatars stop flashing everywhere: backgrounds, not <img>
A recreated <img> decodes async — one blank frame per render, and boot renders in bursts. A CSS background paints synchronously from cache, so the profile page, chat rows, and send candidates all hold steady now. Co-Authored-By: Claude Fable 5 <[email protected]>
Diffstat (limited to 'src')
-rw-r--r--src/features/messages.js4
-rw-r--r--src/recipient-search.js4
-rw-r--r--src/style.css1
3 files changed, 7 insertions, 2 deletions
diff --git a/src/features/messages.js b/src/features/messages.js
index f9a2176..293ebd9 100644
--- a/src/features/messages.js
+++ b/src/features/messages.js
@@ -962,7 +962,9 @@ export function messagesFeature(ctx) {
const node = p === null
? h('div', { class: cls + ' fallback loading' })
: p.picture
- ? h('img', { class: cls, src: p.picture, alt: '' })
+ // A background paints synchronously from cache; a fresh <img> decodes
+ // async, so recreating one per render made avatars visibly flash.
+ ? h('div', { class: cls + ' ava-img', style: `background-image:url(${JSON.stringify(p.picture)})` })
: fallbackAvatar(h, pk, p.name, cls);
if (clickable) {
node.classList.add('clickable');
diff --git a/src/recipient-search.js b/src/recipient-search.js
index b8a17ea..2cf52b6 100644
--- a/src/recipient-search.js
+++ b/src/recipient-search.js
@@ -161,7 +161,9 @@ export function resultRows(h, rows, onPick) {
return rows.map((r) =>
h('div', { class: 'item chat-thread-row', onClick: () => onPick(r) },
r.picture
- ? h('img', { class: 'chat-avatar', src: r.picture, alt: '' })
+ // background, not <img>: paints synchronously from cache, so rows
+ // re-rendered per keystroke don't flash their avatars
+ ? h('div', { class: 'chat-avatar ava-img', style: `background-image:url(${JSON.stringify(r.picture)})` })
: fallbackAvatar(h, r.pk, r.name, 'chat-avatar'),
h('div', { class: 'col grow', style: 'min-width:0;gap:1px' },
h('div', { class: 'chat-name' }, r.name || (npubOf(r.pk) || '').slice(0, 12)),
diff --git a/src/style.css b/src/style.css
index 264b85e..ff652a5 100644
--- a/src/style.css
+++ b/src/style.css
@@ -365,6 +365,7 @@ hr.divider { margin: 16px 0; }
width: 30px; height: 30px; border-radius: 50%; flex-shrink: 0; object-fit: cover;
background: var(--accent-soft);
}
+.ava-img { background-size: cover; background-position: center; }
.chat-avatar.fallback {
display: flex; align-items: center; justify-content: center;
font-size: 11px; font-weight: 650; color: var(--muted); text-transform: uppercase;