aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--build.js8
-rw-r--r--src/app.js6
-rw-r--r--src/features/messages.js34
-rw-r--r--src/recipient-search.js17
-rw-r--r--static/punks-sm/1.webpbin0 -> 3568 bytes
-rw-r--r--static/punks-sm/10.webpbin0 -> 2800 bytes
-rw-r--r--static/punks-sm/11.webpbin0 -> 3250 bytes
-rw-r--r--static/punks-sm/12.webpbin0 -> 2900 bytes
-rw-r--r--static/punks-sm/13.webpbin0 -> 3684 bytes
-rw-r--r--static/punks-sm/14.webpbin0 -> 2800 bytes
-rw-r--r--static/punks-sm/15.webpbin0 -> 5960 bytes
-rw-r--r--static/punks-sm/16.webpbin0 -> 2862 bytes
-rw-r--r--static/punks-sm/17.webpbin0 -> 2614 bytes
-rw-r--r--static/punks-sm/18.webpbin0 -> 5028 bytes
-rw-r--r--static/punks-sm/19.webpbin0 -> 5316 bytes
-rw-r--r--static/punks-sm/2.webpbin0 -> 1686 bytes
-rw-r--r--static/punks-sm/20.webpbin0 -> 4022 bytes
-rw-r--r--static/punks-sm/21.webpbin0 -> 5538 bytes
-rw-r--r--static/punks-sm/22.webpbin0 -> 4280 bytes
-rw-r--r--static/punks-sm/23.webpbin0 -> 5538 bytes
-rw-r--r--static/punks-sm/24.webpbin0 -> 3068 bytes
-rw-r--r--static/punks-sm/25.webpbin0 -> 3544 bytes
-rw-r--r--static/punks-sm/26.webpbin0 -> 3418 bytes
-rw-r--r--static/punks-sm/27.webpbin0 -> 3682 bytes
-rw-r--r--static/punks-sm/28.webpbin0 -> 3918 bytes
-rw-r--r--static/punks-sm/29.webpbin0 -> 3572 bytes
-rw-r--r--static/punks-sm/3.webpbin0 -> 3068 bytes
-rw-r--r--static/punks-sm/30.webpbin0 -> 4552 bytes
-rw-r--r--static/punks-sm/31.webpbin0 -> 4154 bytes
-rw-r--r--static/punks-sm/32.webpbin0 -> 5900 bytes
-rw-r--r--static/punks-sm/33.webpbin0 -> 3682 bytes
-rw-r--r--static/punks-sm/34.webpbin0 -> 2806 bytes
-rw-r--r--static/punks-sm/35.webpbin0 -> 4398 bytes
-rw-r--r--static/punks-sm/36.webpbin0 -> 3554 bytes
-rw-r--r--static/punks-sm/37.webpbin0 -> 6518 bytes
-rw-r--r--static/punks-sm/38.webpbin0 -> 2680 bytes
-rw-r--r--static/punks-sm/39.webpbin0 -> 3360 bytes
-rw-r--r--static/punks-sm/4.webpbin0 -> 2882 bytes
-rw-r--r--static/punks-sm/40.webpbin0 -> 5900 bytes
-rw-r--r--static/punks-sm/41.webpbin0 -> 4038 bytes
-rw-r--r--static/punks-sm/42.webpbin0 -> 3932 bytes
-rw-r--r--static/punks-sm/43.webpbin0 -> 3610 bytes
-rw-r--r--static/punks-sm/44.webpbin0 -> 5516 bytes
-rw-r--r--static/punks-sm/45.webpbin0 -> 3554 bytes
-rw-r--r--static/punks-sm/46.webpbin0 -> 5090 bytes
-rw-r--r--static/punks-sm/47.webpbin0 -> 5516 bytes
-rw-r--r--static/punks-sm/48.webpbin0 -> 5706 bytes
-rw-r--r--static/punks-sm/49.webpbin0 -> 3774 bytes
-rw-r--r--static/punks-sm/5.webpbin0 -> 3816 bytes
-rw-r--r--static/punks-sm/50.webpbin0 -> 3140 bytes
-rw-r--r--static/punks-sm/51.webpbin0 -> 5092 bytes
-rw-r--r--static/punks-sm/52.webpbin0 -> 3418 bytes
-rw-r--r--static/punks-sm/53.webpbin0 -> 4512 bytes
-rw-r--r--static/punks-sm/54.webpbin0 -> 2900 bytes
-rw-r--r--static/punks-sm/55.webpbin0 -> 3202 bytes
-rw-r--r--static/punks-sm/56.webpbin0 -> 4112 bytes
-rw-r--r--static/punks-sm/57.webpbin0 -> 4198 bytes
-rw-r--r--static/punks-sm/58.webpbin0 -> 5064 bytes
-rw-r--r--static/punks-sm/59.webpbin0 -> 4322 bytes
-rw-r--r--static/punks-sm/6.webpbin0 -> 2900 bytes
-rw-r--r--static/punks-sm/60.webpbin0 -> 2978 bytes
-rw-r--r--static/punks-sm/61.webpbin0 -> 3774 bytes
-rw-r--r--static/punks-sm/62.webpbin0 -> 4122 bytes
-rw-r--r--static/punks-sm/63.webpbin0 -> 3860 bytes
-rw-r--r--static/punks-sm/64.webpbin0 -> 5216 bytes
-rw-r--r--static/punks-sm/7.webpbin0 -> 2900 bytes
-rw-r--r--static/punks-sm/8.webpbin0 -> 2962 bytes
-rw-r--r--static/punks-sm/9.webpbin0 -> 2770 bytes
-rw-r--r--tools/avatar-thumb-test.js37
69 files changed, 90 insertions, 12 deletions
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
--- /dev/null
+++ b/static/punks-sm/1.webp
Binary files differ
diff --git a/static/punks-sm/10.webp b/static/punks-sm/10.webp
new file mode 100644
index 0000000..55a8d1d
--- /dev/null
+++ b/static/punks-sm/10.webp
Binary files differ
diff --git a/static/punks-sm/11.webp b/static/punks-sm/11.webp
new file mode 100644
index 0000000..9b8c583
--- /dev/null
+++ b/static/punks-sm/11.webp
Binary files differ
diff --git a/static/punks-sm/12.webp b/static/punks-sm/12.webp
new file mode 100644
index 0000000..f94eb28
--- /dev/null
+++ b/static/punks-sm/12.webp
Binary files differ
diff --git a/static/punks-sm/13.webp b/static/punks-sm/13.webp
new file mode 100644
index 0000000..a7325f8
--- /dev/null
+++ b/static/punks-sm/13.webp
Binary files differ
diff --git a/static/punks-sm/14.webp b/static/punks-sm/14.webp
new file mode 100644
index 0000000..55a8d1d
--- /dev/null
+++ b/static/punks-sm/14.webp
Binary files differ
diff --git a/static/punks-sm/15.webp b/static/punks-sm/15.webp
new file mode 100644
index 0000000..2347925
--- /dev/null
+++ b/static/punks-sm/15.webp
Binary files differ
diff --git a/static/punks-sm/16.webp b/static/punks-sm/16.webp
new file mode 100644
index 0000000..2a56d71
--- /dev/null
+++ b/static/punks-sm/16.webp
Binary files differ
diff --git a/static/punks-sm/17.webp b/static/punks-sm/17.webp
new file mode 100644
index 0000000..97ffb72
--- /dev/null
+++ b/static/punks-sm/17.webp
Binary files differ
diff --git a/static/punks-sm/18.webp b/static/punks-sm/18.webp
new file mode 100644
index 0000000..af156fa
--- /dev/null
+++ b/static/punks-sm/18.webp
Binary files differ
diff --git a/static/punks-sm/19.webp b/static/punks-sm/19.webp
new file mode 100644
index 0000000..b739d24
--- /dev/null
+++ b/static/punks-sm/19.webp
Binary files differ
diff --git a/static/punks-sm/2.webp b/static/punks-sm/2.webp
new file mode 100644
index 0000000..90d2a89
--- /dev/null
+++ b/static/punks-sm/2.webp
Binary files differ
diff --git a/static/punks-sm/20.webp b/static/punks-sm/20.webp
new file mode 100644
index 0000000..20dd513
--- /dev/null
+++ b/static/punks-sm/20.webp
Binary files differ
diff --git a/static/punks-sm/21.webp b/static/punks-sm/21.webp
new file mode 100644
index 0000000..5a92716
--- /dev/null
+++ b/static/punks-sm/21.webp
Binary files differ
diff --git a/static/punks-sm/22.webp b/static/punks-sm/22.webp
new file mode 100644
index 0000000..c1db319
--- /dev/null
+++ b/static/punks-sm/22.webp
Binary files differ
diff --git a/static/punks-sm/23.webp b/static/punks-sm/23.webp
new file mode 100644
index 0000000..5a92716
--- /dev/null
+++ b/static/punks-sm/23.webp
Binary files differ
diff --git a/static/punks-sm/24.webp b/static/punks-sm/24.webp
new file mode 100644
index 0000000..58271cf
--- /dev/null
+++ b/static/punks-sm/24.webp
Binary files differ
diff --git a/static/punks-sm/25.webp b/static/punks-sm/25.webp
new file mode 100644
index 0000000..886e5a8
--- /dev/null
+++ b/static/punks-sm/25.webp
Binary files differ
diff --git a/static/punks-sm/26.webp b/static/punks-sm/26.webp
new file mode 100644
index 0000000..538949c
--- /dev/null
+++ b/static/punks-sm/26.webp
Binary files differ
diff --git a/static/punks-sm/27.webp b/static/punks-sm/27.webp
new file mode 100644
index 0000000..4dc8f40
--- /dev/null
+++ b/static/punks-sm/27.webp
Binary files differ
diff --git a/static/punks-sm/28.webp b/static/punks-sm/28.webp
new file mode 100644
index 0000000..9cb00a3
--- /dev/null
+++ b/static/punks-sm/28.webp
Binary files differ
diff --git a/static/punks-sm/29.webp b/static/punks-sm/29.webp
new file mode 100644
index 0000000..cd1e263
--- /dev/null
+++ b/static/punks-sm/29.webp
Binary files differ
diff --git a/static/punks-sm/3.webp b/static/punks-sm/3.webp
new file mode 100644
index 0000000..58271cf
--- /dev/null
+++ b/static/punks-sm/3.webp
Binary files differ
diff --git a/static/punks-sm/30.webp b/static/punks-sm/30.webp
new file mode 100644
index 0000000..80c9d0a
--- /dev/null
+++ b/static/punks-sm/30.webp
Binary files differ
diff --git a/static/punks-sm/31.webp b/static/punks-sm/31.webp
new file mode 100644
index 0000000..4393d80
--- /dev/null
+++ b/static/punks-sm/31.webp
Binary files differ
diff --git a/static/punks-sm/32.webp b/static/punks-sm/32.webp
new file mode 100644
index 0000000..1d08adc
--- /dev/null
+++ b/static/punks-sm/32.webp
Binary files differ
diff --git a/static/punks-sm/33.webp b/static/punks-sm/33.webp
new file mode 100644
index 0000000..4dc8f40
--- /dev/null
+++ b/static/punks-sm/33.webp
Binary files differ
diff --git a/static/punks-sm/34.webp b/static/punks-sm/34.webp
new file mode 100644
index 0000000..be85204
--- /dev/null
+++ b/static/punks-sm/34.webp
Binary files differ
diff --git a/static/punks-sm/35.webp b/static/punks-sm/35.webp
new file mode 100644
index 0000000..aa5ca93
--- /dev/null
+++ b/static/punks-sm/35.webp
Binary files differ
diff --git a/static/punks-sm/36.webp b/static/punks-sm/36.webp
new file mode 100644
index 0000000..917aedd
--- /dev/null
+++ b/static/punks-sm/36.webp
Binary files differ
diff --git a/static/punks-sm/37.webp b/static/punks-sm/37.webp
new file mode 100644
index 0000000..30cf0d6
--- /dev/null
+++ b/static/punks-sm/37.webp
Binary files differ
diff --git a/static/punks-sm/38.webp b/static/punks-sm/38.webp
new file mode 100644
index 0000000..0b7138b
--- /dev/null
+++ b/static/punks-sm/38.webp
Binary files differ
diff --git a/static/punks-sm/39.webp b/static/punks-sm/39.webp
new file mode 100644
index 0000000..9213fe6
--- /dev/null
+++ b/static/punks-sm/39.webp
Binary files differ
diff --git a/static/punks-sm/4.webp b/static/punks-sm/4.webp
new file mode 100644
index 0000000..10e88e9
--- /dev/null
+++ b/static/punks-sm/4.webp
Binary files differ
diff --git a/static/punks-sm/40.webp b/static/punks-sm/40.webp
new file mode 100644
index 0000000..1d08adc
--- /dev/null
+++ b/static/punks-sm/40.webp
Binary files differ
diff --git a/static/punks-sm/41.webp b/static/punks-sm/41.webp
new file mode 100644
index 0000000..302fb73
--- /dev/null
+++ b/static/punks-sm/41.webp
Binary files differ
diff --git a/static/punks-sm/42.webp b/static/punks-sm/42.webp
new file mode 100644
index 0000000..17b3097
--- /dev/null
+++ b/static/punks-sm/42.webp
Binary files differ
diff --git a/static/punks-sm/43.webp b/static/punks-sm/43.webp
new file mode 100644
index 0000000..891ed4b
--- /dev/null
+++ b/static/punks-sm/43.webp
Binary files differ
diff --git a/static/punks-sm/44.webp b/static/punks-sm/44.webp
new file mode 100644
index 0000000..e3f02fd
--- /dev/null
+++ b/static/punks-sm/44.webp
Binary files differ
diff --git a/static/punks-sm/45.webp b/static/punks-sm/45.webp
new file mode 100644
index 0000000..917aedd
--- /dev/null
+++ b/static/punks-sm/45.webp
Binary files differ
diff --git a/static/punks-sm/46.webp b/static/punks-sm/46.webp
new file mode 100644
index 0000000..c654f3a
--- /dev/null
+++ b/static/punks-sm/46.webp
Binary files differ
diff --git a/static/punks-sm/47.webp b/static/punks-sm/47.webp
new file mode 100644
index 0000000..e3f02fd
--- /dev/null
+++ b/static/punks-sm/47.webp
Binary files differ
diff --git a/static/punks-sm/48.webp b/static/punks-sm/48.webp
new file mode 100644
index 0000000..563b1a5
--- /dev/null
+++ b/static/punks-sm/48.webp
Binary files differ
diff --git a/static/punks-sm/49.webp b/static/punks-sm/49.webp
new file mode 100644
index 0000000..0b19b65
--- /dev/null
+++ b/static/punks-sm/49.webp
Binary files differ
diff --git a/static/punks-sm/5.webp b/static/punks-sm/5.webp
new file mode 100644
index 0000000..4bde169
--- /dev/null
+++ b/static/punks-sm/5.webp
Binary files differ
diff --git a/static/punks-sm/50.webp b/static/punks-sm/50.webp
new file mode 100644
index 0000000..dc045bb
--- /dev/null
+++ b/static/punks-sm/50.webp
Binary files differ
diff --git a/static/punks-sm/51.webp b/static/punks-sm/51.webp
new file mode 100644
index 0000000..fcb70eb
--- /dev/null
+++ b/static/punks-sm/51.webp
Binary files differ
diff --git a/static/punks-sm/52.webp b/static/punks-sm/52.webp
new file mode 100644
index 0000000..538949c
--- /dev/null
+++ b/static/punks-sm/52.webp
Binary files differ
diff --git a/static/punks-sm/53.webp b/static/punks-sm/53.webp
new file mode 100644
index 0000000..d343463
--- /dev/null
+++ b/static/punks-sm/53.webp
Binary files differ
diff --git a/static/punks-sm/54.webp b/static/punks-sm/54.webp
new file mode 100644
index 0000000..f94eb28
--- /dev/null
+++ b/static/punks-sm/54.webp
Binary files differ
diff --git a/static/punks-sm/55.webp b/static/punks-sm/55.webp
new file mode 100644
index 0000000..1b6b160
--- /dev/null
+++ b/static/punks-sm/55.webp
Binary files differ
diff --git a/static/punks-sm/56.webp b/static/punks-sm/56.webp
new file mode 100644
index 0000000..7cab617
--- /dev/null
+++ b/static/punks-sm/56.webp
Binary files differ
diff --git a/static/punks-sm/57.webp b/static/punks-sm/57.webp
new file mode 100644
index 0000000..39135b9
--- /dev/null
+++ b/static/punks-sm/57.webp
Binary files differ
diff --git a/static/punks-sm/58.webp b/static/punks-sm/58.webp
new file mode 100644
index 0000000..51a42b4
--- /dev/null
+++ b/static/punks-sm/58.webp
Binary files differ
diff --git a/static/punks-sm/59.webp b/static/punks-sm/59.webp
new file mode 100644
index 0000000..2c6e1ca
--- /dev/null
+++ b/static/punks-sm/59.webp
Binary files differ
diff --git a/static/punks-sm/6.webp b/static/punks-sm/6.webp
new file mode 100644
index 0000000..88126fc
--- /dev/null
+++ b/static/punks-sm/6.webp
Binary files differ
diff --git a/static/punks-sm/60.webp b/static/punks-sm/60.webp
new file mode 100644
index 0000000..848e0aa
--- /dev/null
+++ b/static/punks-sm/60.webp
Binary files differ
diff --git a/static/punks-sm/61.webp b/static/punks-sm/61.webp
new file mode 100644
index 0000000..0b19b65
--- /dev/null
+++ b/static/punks-sm/61.webp
Binary files differ
diff --git a/static/punks-sm/62.webp b/static/punks-sm/62.webp
new file mode 100644
index 0000000..81bd766
--- /dev/null
+++ b/static/punks-sm/62.webp
Binary files differ
diff --git a/static/punks-sm/63.webp b/static/punks-sm/63.webp
new file mode 100644
index 0000000..1e48384
--- /dev/null
+++ b/static/punks-sm/63.webp
Binary files differ
diff --git a/static/punks-sm/64.webp b/static/punks-sm/64.webp
new file mode 100644
index 0000000..58f70ff
--- /dev/null
+++ b/static/punks-sm/64.webp
Binary files differ
diff --git a/static/punks-sm/7.webp b/static/punks-sm/7.webp
new file mode 100644
index 0000000..88126fc
--- /dev/null
+++ b/static/punks-sm/7.webp
Binary files differ
diff --git a/static/punks-sm/8.webp b/static/punks-sm/8.webp
new file mode 100644
index 0000000..da9319b
--- /dev/null
+++ b/static/punks-sm/8.webp
Binary files differ
diff --git a/static/punks-sm/9.webp b/static/punks-sm/9.webp
new file mode 100644
index 0000000..86ecb2a
--- /dev/null
+++ b/static/punks-sm/9.webp
Binary files 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);