From 82cd8da7f5ab285b3a91f7284fb8d845400012ca Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Thu, 10 Sep 2026 22:51:56 -0700 Subject: Ship the punks at the size we actually draw them. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The art is 240x240 (20KB each, 1.5MB for the set) because that's what a punk published as someone's nostr picture should be — but the circles the app draws are 18 to 64px, so a new wallet with nothing cached was fetching eight times the pixels it could show, one request per face. punks-sm carries a 128px copy of each: 4KB instead of 20KB, indistinguishable at 64px. Small circles and the wizard's picker tiles load those; only the 64px profile avatar and the value the wizard publishes stay full-size. The bigger win is that a punk picture is our own art wherever it turns up. Everyone who keeps the default publishes v3.coinos.io/punks/N.webp as their nostr picture, so those faces now paint straight off disk at the size being drawn — no request, no thumbnail to make, and no broken circle when the legacy coinos.io copies 502 (which they do for a third of the set). Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_019mrt228Upn9nNCwGixzSLx --- build.js | 8 ++++++-- src/app.js | 6 +++++- src/features/messages.js | 34 ++++++++++++++++++++++++++++------ src/recipient-search.js | 17 ++++++++++++++--- static/punks-sm/1.webp | Bin 0 -> 3568 bytes static/punks-sm/10.webp | Bin 0 -> 2800 bytes static/punks-sm/11.webp | Bin 0 -> 3250 bytes static/punks-sm/12.webp | Bin 0 -> 2900 bytes static/punks-sm/13.webp | Bin 0 -> 3684 bytes static/punks-sm/14.webp | Bin 0 -> 2800 bytes static/punks-sm/15.webp | Bin 0 -> 5960 bytes static/punks-sm/16.webp | Bin 0 -> 2862 bytes static/punks-sm/17.webp | Bin 0 -> 2614 bytes static/punks-sm/18.webp | Bin 0 -> 5028 bytes static/punks-sm/19.webp | Bin 0 -> 5316 bytes static/punks-sm/2.webp | Bin 0 -> 1686 bytes static/punks-sm/20.webp | Bin 0 -> 4022 bytes static/punks-sm/21.webp | Bin 0 -> 5538 bytes static/punks-sm/22.webp | Bin 0 -> 4280 bytes static/punks-sm/23.webp | Bin 0 -> 5538 bytes static/punks-sm/24.webp | Bin 0 -> 3068 bytes static/punks-sm/25.webp | Bin 0 -> 3544 bytes static/punks-sm/26.webp | Bin 0 -> 3418 bytes static/punks-sm/27.webp | Bin 0 -> 3682 bytes static/punks-sm/28.webp | Bin 0 -> 3918 bytes static/punks-sm/29.webp | Bin 0 -> 3572 bytes static/punks-sm/3.webp | Bin 0 -> 3068 bytes static/punks-sm/30.webp | Bin 0 -> 4552 bytes static/punks-sm/31.webp | Bin 0 -> 4154 bytes static/punks-sm/32.webp | Bin 0 -> 5900 bytes static/punks-sm/33.webp | Bin 0 -> 3682 bytes static/punks-sm/34.webp | Bin 0 -> 2806 bytes static/punks-sm/35.webp | Bin 0 -> 4398 bytes static/punks-sm/36.webp | Bin 0 -> 3554 bytes static/punks-sm/37.webp | Bin 0 -> 6518 bytes static/punks-sm/38.webp | Bin 0 -> 2680 bytes static/punks-sm/39.webp | Bin 0 -> 3360 bytes static/punks-sm/4.webp | Bin 0 -> 2882 bytes static/punks-sm/40.webp | Bin 0 -> 5900 bytes static/punks-sm/41.webp | Bin 0 -> 4038 bytes static/punks-sm/42.webp | Bin 0 -> 3932 bytes static/punks-sm/43.webp | Bin 0 -> 3610 bytes static/punks-sm/44.webp | Bin 0 -> 5516 bytes static/punks-sm/45.webp | Bin 0 -> 3554 bytes static/punks-sm/46.webp | Bin 0 -> 5090 bytes static/punks-sm/47.webp | Bin 0 -> 5516 bytes static/punks-sm/48.webp | Bin 0 -> 5706 bytes static/punks-sm/49.webp | Bin 0 -> 3774 bytes static/punks-sm/5.webp | Bin 0 -> 3816 bytes static/punks-sm/50.webp | Bin 0 -> 3140 bytes static/punks-sm/51.webp | Bin 0 -> 5092 bytes static/punks-sm/52.webp | Bin 0 -> 3418 bytes static/punks-sm/53.webp | Bin 0 -> 4512 bytes static/punks-sm/54.webp | Bin 0 -> 2900 bytes static/punks-sm/55.webp | Bin 0 -> 3202 bytes static/punks-sm/56.webp | Bin 0 -> 4112 bytes static/punks-sm/57.webp | Bin 0 -> 4198 bytes static/punks-sm/58.webp | Bin 0 -> 5064 bytes static/punks-sm/59.webp | Bin 0 -> 4322 bytes static/punks-sm/6.webp | Bin 0 -> 2900 bytes static/punks-sm/60.webp | Bin 0 -> 2978 bytes static/punks-sm/61.webp | Bin 0 -> 3774 bytes static/punks-sm/62.webp | Bin 0 -> 4122 bytes static/punks-sm/63.webp | Bin 0 -> 3860 bytes static/punks-sm/64.webp | Bin 0 -> 5216 bytes static/punks-sm/7.webp | Bin 0 -> 2900 bytes static/punks-sm/8.webp | Bin 0 -> 2962 bytes static/punks-sm/9.webp | Bin 0 -> 2770 bytes tools/avatar-thumb-test.js | 37 +++++++++++++++++++++++++++++++++++++ 69 files changed, 90 insertions(+), 12 deletions(-) create mode 100644 static/punks-sm/1.webp create mode 100644 static/punks-sm/10.webp create mode 100644 static/punks-sm/11.webp create mode 100644 static/punks-sm/12.webp create mode 100644 static/punks-sm/13.webp create mode 100644 static/punks-sm/14.webp create mode 100644 static/punks-sm/15.webp create mode 100644 static/punks-sm/16.webp create mode 100644 static/punks-sm/17.webp create mode 100644 static/punks-sm/18.webp create mode 100644 static/punks-sm/19.webp create mode 100644 static/punks-sm/2.webp create mode 100644 static/punks-sm/20.webp create mode 100644 static/punks-sm/21.webp create mode 100644 static/punks-sm/22.webp create mode 100644 static/punks-sm/23.webp create mode 100644 static/punks-sm/24.webp create mode 100644 static/punks-sm/25.webp create mode 100644 static/punks-sm/26.webp create mode 100644 static/punks-sm/27.webp create mode 100644 static/punks-sm/28.webp create mode 100644 static/punks-sm/29.webp create mode 100644 static/punks-sm/3.webp create mode 100644 static/punks-sm/30.webp create mode 100644 static/punks-sm/31.webp create mode 100644 static/punks-sm/32.webp create mode 100644 static/punks-sm/33.webp create mode 100644 static/punks-sm/34.webp create mode 100644 static/punks-sm/35.webp create mode 100644 static/punks-sm/36.webp create mode 100644 static/punks-sm/37.webp create mode 100644 static/punks-sm/38.webp create mode 100644 static/punks-sm/39.webp create mode 100644 static/punks-sm/4.webp create mode 100644 static/punks-sm/40.webp create mode 100644 static/punks-sm/41.webp create mode 100644 static/punks-sm/42.webp create mode 100644 static/punks-sm/43.webp create mode 100644 static/punks-sm/44.webp create mode 100644 static/punks-sm/45.webp create mode 100644 static/punks-sm/46.webp create mode 100644 static/punks-sm/47.webp create mode 100644 static/punks-sm/48.webp create mode 100644 static/punks-sm/49.webp create mode 100644 static/punks-sm/5.webp create mode 100644 static/punks-sm/50.webp create mode 100644 static/punks-sm/51.webp create mode 100644 static/punks-sm/52.webp create mode 100644 static/punks-sm/53.webp create mode 100644 static/punks-sm/54.webp create mode 100644 static/punks-sm/55.webp create mode 100644 static/punks-sm/56.webp create mode 100644 static/punks-sm/57.webp create mode 100644 static/punks-sm/58.webp create mode 100644 static/punks-sm/59.webp create mode 100644 static/punks-sm/6.webp create mode 100644 static/punks-sm/60.webp create mode 100644 static/punks-sm/61.webp create mode 100644 static/punks-sm/62.webp create mode 100644 static/punks-sm/63.webp create mode 100644 static/punks-sm/64.webp create mode 100644 static/punks-sm/7.webp create mode 100644 static/punks-sm/8.webp create mode 100644 static/punks-sm/9.webp diff --git a/build.js b/build.js index 0620874..cdc54f5 100644 --- a/build.js +++ b/build.js @@ -472,9 +472,13 @@ if (import.meta.main) { if (await Bun.file('static/coinos.apk').exists()) { await Bun.write('dist/coinos.apk', Bun.file('static/coinos.apk')); } - // default avatars, self-hosted since coinos.io's copies are half-broken + // default avatars, self-hosted since coinos.io's copies are half-broken. + // punks-sm carries a 128px copy of each: the art is 240px because that's + // what a punk published as someone's nostr picture should be, but the + // circles the app draws are 18-64px and shouldn't pay for the rest. const { readdirSync } = await import('node:fs'); - for (const f of readdirSync('static/punks')) await Bun.write('dist/punks/' + f, Bun.file('static/punks/' + f)); + for (const d of ['punks', 'punks-sm']) + for (const f of readdirSync('static/' + d)) await Bun.write(`dist/${d}/${f}`, Bun.file(`static/${d}/${f}`)); const kb = (Buffer.byteLength(html) / 1024).toFixed(0); const jkb = (Buffer.byteLength(rawJs) / 1024).toFixed(0); diff --git a/src/app.js b/src/app.js index f36c9bc..19258c3 100644 --- a/src/app.js +++ b/src/app.js @@ -2904,7 +2904,11 @@ function shouldOnboard() { } const PUNK_PICKS = [7, 14, 21, 3, 33, 40, 47, 36, 61, 26, 12, 50]; +// The value the wizard publishes is always the full-size art — that URL +// becomes this person's picture on every nostr client. The tiles in the +// picker are 56px, so they load the small copies. const punkImg = (n) => `punks/${n}.webp`; +const punkTile = (n) => `punks-sm/${n}.webp`; async function onbUpload(file) { const fd = new FormData(); @@ -3106,7 +3110,7 @@ function onboardScreen() { ...PUNK_PICKS.map((n) => h('img', { class: 'onb-punk' + (sel === punkImg(n) ? ' sel' : ''), - src: punkImg(n), alt: '', + src: punkTile(n), alt: '', onClick: () => { o.avatar = punkImg(n); render(); }, onError: (e) => { e.target.remove(); }, // a 502'd punk removes itself }))), diff --git a/src/features/messages.js b/src/features/messages.js index 18703ab..b23d7ad 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -332,6 +332,17 @@ export function messagesFeature(ctx) { } wallet.saveFeatureState('profiles', s); } + // A punk picture is OUR OWN art. Every coinos user who keeps the default + // publishes https://v3.coinos.io/punks/N.webp as their nostr picture, and + // the legacy coinos.io copies 502 for a third of the set — so painting + // those from the local files means no network, no broken faces, and the + // right size for the circle being drawn. + const PUNK_PIC_RE = /^https?:\/\/(?:[a-z0-9-]+\.)*coinos\.io\/punks\/(\d{1,2})\.webp$/i; + const localPunk = (url, big) => { + const m = PUNK_PIC_RE.exec(url || ''); + return m ? (big ? `punks/${m[1]}.webp` : `punks-sm/${m[1]}.webp`) : null; + }; + // A refreshed profile whose picture hasn't changed keeps the thumbnail we // already made of it — otherwise every relay refresh throws the local copy // away and the original gets downloaded all over again. @@ -367,6 +378,7 @@ export function messagesFeature(ctx) { function makeThumb(pk, p) { if (!p || !p.picture || typeof document === 'undefined') return; if (p.thumbFor === p.picture || p.thumbFail === p.picture) return; + if (localPunk(p.picture)) return; // our own art, already the right size on disk if (thumbing.has(pk) || thumbing.size >= 3) return; // a few at a time // Making the thumbnail costs one more fetch of the original today to // save every fetch after it — but not on a connection someone is @@ -1732,6 +1744,21 @@ export function messagesFeature(ctx) { ' ' + d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' }); }; + // Which bytes a face paints from, in order of preference: + // our own punk art, straight off disk at the size being drawn; + // the thumbnail we keep locally, which needs no network at all; + // the original, when we have nothing else yet. + // The 64px profile avatar layers the original OVER the local copy: CSS + // paints the first layer that has arrived, so the face is there instantly + // and sharpens when the full-size one lands. + function avatarBg(p, big) { + const punk = localPunk(p.picture, big); + if (punk) return `url(${JSON.stringify(punk)})`; + const local = p.thumb && p.thumbFor === p.picture ? p.thumb : null; + if (!local) return `url(${JSON.stringify(p.picture)})`; + return (big ? `url(${JSON.stringify(p.picture)}),` : '') + `url(${JSON.stringify(local)})`; + } + const avatar = (pk, cls = 'chat-avatar', clickable = true) => { const p = profileOf(pk); // While the profile is in flight, a quiet empty circle — the punk is a @@ -1748,12 +1775,7 @@ export function messagesFeature(ctx) { // 64px profile avatar layers them: CSS paints the first layer that // has arrived, so the thumbnail shows instantly and the full-size // original takes over on top when it lands. - ? h('div', { - class: cls + ' ava-img', - style: 'background-image:' + (p.thumb && p.thumbFor === p.picture - ? (cls.includes('profile-avatar') ? `url(${JSON.stringify(p.picture)}),` : '') + `url(${JSON.stringify(p.thumb)})` - : `url(${JSON.stringify(p.picture)})`), - }) + ? h('div', { class: cls + ' ava-img', style: 'background-image:' + avatarBg(p, cls.includes('profile-avatar')) }) : fallbackAvatar(h, pk, p.name, cls); // A face we keep painting is one worth keeping a thumbnail of. if (p && p.picture) makeThumb(pk, p); diff --git a/src/recipient-search.js b/src/recipient-search.js index a5c5f46..be3c70e 100644 --- a/src/recipient-search.js +++ b/src/recipient-search.js @@ -15,12 +15,23 @@ import { queryOn, parseNostrPubkey, npubOf, PROFILE_RELAYS } from './nostr.js'; // if the image can't load (offline). // Self-hosted (dist/punks) — coinos.io's own copies 502 for a third of the // set. Same deterministic formula the coinos app uses. -export const punkUrl = (pk) => - `punks/${Math.floor((parseInt(pk.slice(-2), 16) / 256) * 64) + 1}.webp`; +const punkN = (pk) => Math.floor((parseInt(pk.slice(-2), 16) / 256) * 64) + 1; +export const punkUrl = (pk) => `punks/${punkN(pk)}.webp`; +// The art is 240px, which is what a punk published as someone's nostr +// picture should be — and eight times what a 30px circle needs. We ship a +// 128px copy of each (a fifth of the bytes, 20KB → 4KB) and paint the circles +// from that, so a brand-new wallet with nothing cached fills its faces in +// straight away. Only the 64px profile avatar takes the full-size one. +export const punkSmallUrl = (pk) => `punks-sm/${punkN(pk)}.webp`; +export const punkSmallN = (n) => `punks-sm/${n}.webp`; export const fallbackAvatar = (h, pk, name, cls) => h('div', { class: cls + ' fallback' }, - h('img', { class: 'punk', src: punkUrl(pk), alt: '', onError: (e) => { e.target.style.display = 'none'; } }), + h('img', { + class: 'punk', alt: '', + src: cls.includes('profile-avatar') ? punkUrl(pk) : punkSmallUrl(pk), + onError: (e) => { e.target.style.display = 'none'; }, + }), (name || npubOf(pk) || '??').slice(0, 2)); const REGISTRAR = 'https://names.coinos.io'; diff --git a/static/punks-sm/1.webp b/static/punks-sm/1.webp new file mode 100644 index 0000000..32ac268 Binary files /dev/null and b/static/punks-sm/1.webp differ diff --git a/static/punks-sm/10.webp b/static/punks-sm/10.webp new file mode 100644 index 0000000..55a8d1d Binary files /dev/null and b/static/punks-sm/10.webp differ diff --git a/static/punks-sm/11.webp b/static/punks-sm/11.webp new file mode 100644 index 0000000..9b8c583 Binary files /dev/null and b/static/punks-sm/11.webp differ diff --git a/static/punks-sm/12.webp b/static/punks-sm/12.webp new file mode 100644 index 0000000..f94eb28 Binary files /dev/null and b/static/punks-sm/12.webp differ diff --git a/static/punks-sm/13.webp b/static/punks-sm/13.webp new file mode 100644 index 0000000..a7325f8 Binary files /dev/null and b/static/punks-sm/13.webp differ diff --git a/static/punks-sm/14.webp b/static/punks-sm/14.webp new file mode 100644 index 0000000..55a8d1d Binary files /dev/null and b/static/punks-sm/14.webp differ diff --git a/static/punks-sm/15.webp b/static/punks-sm/15.webp new file mode 100644 index 0000000..2347925 Binary files /dev/null and b/static/punks-sm/15.webp differ diff --git a/static/punks-sm/16.webp b/static/punks-sm/16.webp new file mode 100644 index 0000000..2a56d71 Binary files /dev/null and b/static/punks-sm/16.webp differ diff --git a/static/punks-sm/17.webp b/static/punks-sm/17.webp new file mode 100644 index 0000000..97ffb72 Binary files /dev/null and b/static/punks-sm/17.webp differ diff --git a/static/punks-sm/18.webp b/static/punks-sm/18.webp new file mode 100644 index 0000000..af156fa Binary files /dev/null and b/static/punks-sm/18.webp differ diff --git a/static/punks-sm/19.webp b/static/punks-sm/19.webp new file mode 100644 index 0000000..b739d24 Binary files /dev/null and b/static/punks-sm/19.webp differ diff --git a/static/punks-sm/2.webp b/static/punks-sm/2.webp new file mode 100644 index 0000000..90d2a89 Binary files /dev/null and b/static/punks-sm/2.webp differ diff --git a/static/punks-sm/20.webp b/static/punks-sm/20.webp new file mode 100644 index 0000000..20dd513 Binary files /dev/null and b/static/punks-sm/20.webp differ diff --git a/static/punks-sm/21.webp b/static/punks-sm/21.webp new file mode 100644 index 0000000..5a92716 Binary files /dev/null and b/static/punks-sm/21.webp differ diff --git a/static/punks-sm/22.webp b/static/punks-sm/22.webp new file mode 100644 index 0000000..c1db319 Binary files /dev/null and b/static/punks-sm/22.webp differ diff --git a/static/punks-sm/23.webp b/static/punks-sm/23.webp new file mode 100644 index 0000000..5a92716 Binary files /dev/null and b/static/punks-sm/23.webp differ diff --git a/static/punks-sm/24.webp b/static/punks-sm/24.webp new file mode 100644 index 0000000..58271cf Binary files /dev/null and b/static/punks-sm/24.webp differ diff --git a/static/punks-sm/25.webp b/static/punks-sm/25.webp new file mode 100644 index 0000000..886e5a8 Binary files /dev/null and b/static/punks-sm/25.webp differ diff --git a/static/punks-sm/26.webp b/static/punks-sm/26.webp new file mode 100644 index 0000000..538949c Binary files /dev/null and b/static/punks-sm/26.webp differ diff --git a/static/punks-sm/27.webp b/static/punks-sm/27.webp new file mode 100644 index 0000000..4dc8f40 Binary files /dev/null and b/static/punks-sm/27.webp differ diff --git a/static/punks-sm/28.webp b/static/punks-sm/28.webp new file mode 100644 index 0000000..9cb00a3 Binary files /dev/null and b/static/punks-sm/28.webp differ diff --git a/static/punks-sm/29.webp b/static/punks-sm/29.webp new file mode 100644 index 0000000..cd1e263 Binary files /dev/null and b/static/punks-sm/29.webp differ diff --git a/static/punks-sm/3.webp b/static/punks-sm/3.webp new file mode 100644 index 0000000..58271cf Binary files /dev/null and b/static/punks-sm/3.webp differ diff --git a/static/punks-sm/30.webp b/static/punks-sm/30.webp new file mode 100644 index 0000000..80c9d0a Binary files /dev/null and b/static/punks-sm/30.webp differ diff --git a/static/punks-sm/31.webp b/static/punks-sm/31.webp new file mode 100644 index 0000000..4393d80 Binary files /dev/null and b/static/punks-sm/31.webp differ diff --git a/static/punks-sm/32.webp b/static/punks-sm/32.webp new file mode 100644 index 0000000..1d08adc Binary files /dev/null and b/static/punks-sm/32.webp differ diff --git a/static/punks-sm/33.webp b/static/punks-sm/33.webp new file mode 100644 index 0000000..4dc8f40 Binary files /dev/null and b/static/punks-sm/33.webp differ diff --git a/static/punks-sm/34.webp b/static/punks-sm/34.webp new file mode 100644 index 0000000..be85204 Binary files /dev/null and b/static/punks-sm/34.webp differ diff --git a/static/punks-sm/35.webp b/static/punks-sm/35.webp new file mode 100644 index 0000000..aa5ca93 Binary files /dev/null and b/static/punks-sm/35.webp differ diff --git a/static/punks-sm/36.webp b/static/punks-sm/36.webp new file mode 100644 index 0000000..917aedd Binary files /dev/null and b/static/punks-sm/36.webp differ diff --git a/static/punks-sm/37.webp b/static/punks-sm/37.webp new file mode 100644 index 0000000..30cf0d6 Binary files /dev/null and b/static/punks-sm/37.webp differ diff --git a/static/punks-sm/38.webp b/static/punks-sm/38.webp new file mode 100644 index 0000000..0b7138b Binary files /dev/null and b/static/punks-sm/38.webp differ diff --git a/static/punks-sm/39.webp b/static/punks-sm/39.webp new file mode 100644 index 0000000..9213fe6 Binary files /dev/null and b/static/punks-sm/39.webp differ diff --git a/static/punks-sm/4.webp b/static/punks-sm/4.webp new file mode 100644 index 0000000..10e88e9 Binary files /dev/null and b/static/punks-sm/4.webp differ diff --git a/static/punks-sm/40.webp b/static/punks-sm/40.webp new file mode 100644 index 0000000..1d08adc Binary files /dev/null and b/static/punks-sm/40.webp differ diff --git a/static/punks-sm/41.webp b/static/punks-sm/41.webp new file mode 100644 index 0000000..302fb73 Binary files /dev/null and b/static/punks-sm/41.webp differ diff --git a/static/punks-sm/42.webp b/static/punks-sm/42.webp new file mode 100644 index 0000000..17b3097 Binary files /dev/null and b/static/punks-sm/42.webp differ diff --git a/static/punks-sm/43.webp b/static/punks-sm/43.webp new file mode 100644 index 0000000..891ed4b Binary files /dev/null and b/static/punks-sm/43.webp differ diff --git a/static/punks-sm/44.webp b/static/punks-sm/44.webp new file mode 100644 index 0000000..e3f02fd Binary files /dev/null and b/static/punks-sm/44.webp differ diff --git a/static/punks-sm/45.webp b/static/punks-sm/45.webp new file mode 100644 index 0000000..917aedd Binary files /dev/null and b/static/punks-sm/45.webp differ diff --git a/static/punks-sm/46.webp b/static/punks-sm/46.webp new file mode 100644 index 0000000..c654f3a Binary files /dev/null and b/static/punks-sm/46.webp differ diff --git a/static/punks-sm/47.webp b/static/punks-sm/47.webp new file mode 100644 index 0000000..e3f02fd Binary files /dev/null and b/static/punks-sm/47.webp differ diff --git a/static/punks-sm/48.webp b/static/punks-sm/48.webp new file mode 100644 index 0000000..563b1a5 Binary files /dev/null and b/static/punks-sm/48.webp differ diff --git a/static/punks-sm/49.webp b/static/punks-sm/49.webp new file mode 100644 index 0000000..0b19b65 Binary files /dev/null and b/static/punks-sm/49.webp differ diff --git a/static/punks-sm/5.webp b/static/punks-sm/5.webp new file mode 100644 index 0000000..4bde169 Binary files /dev/null and b/static/punks-sm/5.webp differ diff --git a/static/punks-sm/50.webp b/static/punks-sm/50.webp new file mode 100644 index 0000000..dc045bb Binary files /dev/null and b/static/punks-sm/50.webp differ diff --git a/static/punks-sm/51.webp b/static/punks-sm/51.webp new file mode 100644 index 0000000..fcb70eb Binary files /dev/null and b/static/punks-sm/51.webp differ diff --git a/static/punks-sm/52.webp b/static/punks-sm/52.webp new file mode 100644 index 0000000..538949c Binary files /dev/null and b/static/punks-sm/52.webp differ diff --git a/static/punks-sm/53.webp b/static/punks-sm/53.webp new file mode 100644 index 0000000..d343463 Binary files /dev/null and b/static/punks-sm/53.webp differ diff --git a/static/punks-sm/54.webp b/static/punks-sm/54.webp new file mode 100644 index 0000000..f94eb28 Binary files /dev/null and b/static/punks-sm/54.webp differ diff --git a/static/punks-sm/55.webp b/static/punks-sm/55.webp new file mode 100644 index 0000000..1b6b160 Binary files /dev/null and b/static/punks-sm/55.webp differ diff --git a/static/punks-sm/56.webp b/static/punks-sm/56.webp new file mode 100644 index 0000000..7cab617 Binary files /dev/null and b/static/punks-sm/56.webp differ diff --git a/static/punks-sm/57.webp b/static/punks-sm/57.webp new file mode 100644 index 0000000..39135b9 Binary files /dev/null and b/static/punks-sm/57.webp differ diff --git a/static/punks-sm/58.webp b/static/punks-sm/58.webp new file mode 100644 index 0000000..51a42b4 Binary files /dev/null and b/static/punks-sm/58.webp differ diff --git a/static/punks-sm/59.webp b/static/punks-sm/59.webp new file mode 100644 index 0000000..2c6e1ca Binary files /dev/null and b/static/punks-sm/59.webp differ diff --git a/static/punks-sm/6.webp b/static/punks-sm/6.webp new file mode 100644 index 0000000..88126fc Binary files /dev/null and b/static/punks-sm/6.webp differ diff --git a/static/punks-sm/60.webp b/static/punks-sm/60.webp new file mode 100644 index 0000000..848e0aa Binary files /dev/null and b/static/punks-sm/60.webp differ diff --git a/static/punks-sm/61.webp b/static/punks-sm/61.webp new file mode 100644 index 0000000..0b19b65 Binary files /dev/null and b/static/punks-sm/61.webp differ diff --git a/static/punks-sm/62.webp b/static/punks-sm/62.webp new file mode 100644 index 0000000..81bd766 Binary files /dev/null and b/static/punks-sm/62.webp differ diff --git a/static/punks-sm/63.webp b/static/punks-sm/63.webp new file mode 100644 index 0000000..1e48384 Binary files /dev/null and b/static/punks-sm/63.webp differ diff --git a/static/punks-sm/64.webp b/static/punks-sm/64.webp new file mode 100644 index 0000000..58f70ff Binary files /dev/null and b/static/punks-sm/64.webp differ diff --git a/static/punks-sm/7.webp b/static/punks-sm/7.webp new file mode 100644 index 0000000..88126fc Binary files /dev/null and b/static/punks-sm/7.webp differ diff --git a/static/punks-sm/8.webp b/static/punks-sm/8.webp new file mode 100644 index 0000000..da9319b Binary files /dev/null and b/static/punks-sm/8.webp differ diff --git a/static/punks-sm/9.webp b/static/punks-sm/9.webp new file mode 100644 index 0000000..86ecb2a Binary files /dev/null and b/static/punks-sm/9.webp differ diff --git a/tools/avatar-thumb-test.js b/tools/avatar-thumb-test.js index 48830be..50ceadc 100644 --- a/tools/avatar-thumb-test.js +++ b/tools/avatar-thumb-test.js @@ -31,6 +31,11 @@ const server = Bun.serve({ port: 5235, async fetch(req) { const u = new URL(req.url); + if (u.pathname.startsWith('/punks')) { + const f = Bun.file('dist' + u.pathname); + if (await f.exists()) return new Response(await f.arrayBuffer(), { headers: { 'content-type': 'image/webp' } }); + return new Response('no', { status: 404 }); + } if (u.pathname === '/big.png') { originalHits++; const bytes = await Bun.file('/tmp/claude-1000/-home-adam-coinosv3/61dea255-d26d-411d-be8b-92dfb0a75887/scratchpad/big.png').arrayBuffer(); @@ -102,6 +107,38 @@ try { await waitText('receive', 20000); await sleep(2000); check('a later boot fetches no picture at all', originalHits === 0, originalHits + ' request(s)'); + + // A punk picture is our own art: painted from disk at the size being drawn, + // never fetched from coinos.io (whose copies 502 for a third of the set). + await page.evaluate((pkHex) => { + for (const k of Object.keys(localStorage).filter((x) => /^btc-wallet-cache:[0-9a-f]+$/.test(x))) + localStorage.setItem(k + ':profiles', JSON.stringify({ + [pkHex]: { name: 'Punk Test', picture: 'https://v3.coinos.io/punks/7.webp', t: Date.now() }, + })); + }, pk); + await page.reload({ waitUntil: 'domcontentloaded' }); + await waitText('receive', 20000); + await sleep(1200); + const punkStyle = await page.evaluate(() => { + const a = document.querySelector('.header-avatar .ava-img') || document.querySelector('.ava-img'); + return a ? a.getAttribute('style') : ''; + }); + check('a punk picture paints from the local small copy', /punks-sm\/7\.webp/.test(punkStyle), punkStyle.slice(0, 60)); + check('...and never from coinos.io', !/coinos\.io/.test(punkStyle)); + + // And someone with no picture at all still gets a punk — the small one. + await page.evaluate((pkHex) => { + for (const k of Object.keys(localStorage).filter((x) => /^btc-wallet-cache:[0-9a-f]+$/.test(x))) + localStorage.setItem(k + ':profiles', JSON.stringify({ [pkHex]: { name: 'Bare', picture: null, t: Date.now() } })); + }, pk); + await page.reload({ waitUntil: 'domcontentloaded' }); + await waitText('receive', 20000); + await sleep(1200); + const punkSrc = await page.evaluate(() => { + const i = document.querySelector('.header-avatar img.punk') || document.querySelector('img.punk'); + return i ? i.getAttribute('src') : ''; + }); + check('the default face is the small punk', /^punks-sm\//.test(punkSrc), punkSrc); } finally { await browser.close(); server.stop(true); } console.log(ok ? '\n✅ faces come back instantly' : '\n❌ failed'); process.exit(ok ? 0 : 1); -- cgit v1.2.3