From 161adb250e4273d0bcaa160516bc950f5f46310f Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Mon, 3 Aug 2026 07:42:24 -0700 Subject: Self-host the punks; skipping avatar still publishes your punk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit coinos.io serves 502 for a third of the punk set (both the numbered and hash endpoints), which is why a default avatar could come up blank. The 64 webps now ship with the app (53 salvaged, 11 gaps filled with donors) and every reference is same-origin. Skipping the avatar step publishes the deterministic punk as the kind-0 picture — your face is the same on every nostr client. Success screen: checkmark now sits below the title, bigger. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi --- src/app.js | 13 ++++++++----- src/recipient-search.js | 4 +++- src/style.css | 1 + 3 files changed, 12 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/app.js b/src/app.js index 562ca99..36fb0d2 100644 --- a/src/app.js +++ b/src/app.js @@ -6,7 +6,7 @@ import { Wallet, newMnemonic, isValidMnemonic, accountXpubFor, cacheKeyFor, utxoId, parseExtendedKey, xpubToZpub, encryptVault, decryptVault } from './wallet.js'; import { qrSvg } from './qr.js'; -import { makeSearcher, resultRows, searchable } from './recipient-search.js'; +import { makeSearcher, resultRows, searchable, punkUrl } from './recipient-search.js'; import { npubOf } from './nostr.js'; import { scanQr } from './scan.js'; import { dataSources, getSource, setSource, getNetwork, setNetwork, NETWORKS } from './api.js'; @@ -2007,7 +2007,7 @@ function shouldOnboard() { } const PUNK_PICKS = [7, 14, 21, 3, 33, 40, 47, 36, 61, 26, 12, 50]; -const punkImg = (n) => `https://coinos.io/api/public/punks/${n}.webp`; +const punkImg = (n) => `punks/${n}.webp`; async function onbUpload(file) { const fd = new FormData(); @@ -2110,7 +2110,10 @@ function onboardScreen() { ui.onbBusy = true; ui.onbError = ''; render(); try { const addr = featureHook('namesAddress'); - const fields = { name: addr ? addr.split('@')[0] : undefined, picture: o.avatar || undefined }; + // keeping the default still publishes it — their punk should be + // their face on every nostr client, not just here + const picture = o.avatar || (me ? 'https://v3.coinos.io/' + punkUrl(me) : undefined); + const fields = { name: addr ? addr.split('@')[0] : undefined, picture }; if (fields.name || fields.picture) await featureHook('publishProfile', fields); o.step = 'success'; } catch (e) { ui.onbError = e.message; } @@ -2121,8 +2124,8 @@ function onboardScreen() { // success return page([ h('div', { class: 'onb-confetti' }, ...Array.from({ length: 18 }, () => h('i'))), - h('div', { class: 'check-badge', style: 'align-self:center' }, '✓'), - title(t('onbDoneTitle')), + h('h2', { class: 'onb-title', style: 'text-align:center' }, t('onbDoneTitle')), + h('div', { class: 'check-badge onb-check' }, '✓'), h('p', { class: 'muted', style: 'margin:0;text-align:center' }, t('onbDoneBody')), h('button', { class: 'btn-primary btn-block', style: 'padding:14px', onClick: () => { try { localStorage.setItem(ONBOARDED_KEY, '1'); } catch {} diff --git a/src/recipient-search.js b/src/recipient-search.js index 2594488..553673e 100644 --- a/src/recipient-search.js +++ b/src/recipient-search.js @@ -13,8 +13,10 @@ import { queryOn, parseNostrPubkey, npubOf, PROFILE_RELAYS } from './nostr.js'; // coinos punks: the deterministic default avatar — same formula the coinos // app uses (last byte of the pubkey picks one of 64). Initials show through // 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) => - `https://coinos.io/api/public/punks/${Math.floor((parseInt(pk.slice(-2), 16) / 256) * 64) + 1}.webp`; + `punks/${Math.floor((parseInt(pk.slice(-2), 16) / 256) * 64) + 1}.webp`; export const fallbackAvatar = (h, pk, name, cls) => h('div', { class: cls + ' fallback' }, diff --git a/src/style.css b/src/style.css index 3dbeaaa..562db92 100644 --- a/src/style.css +++ b/src/style.css @@ -506,3 +506,4 @@ body::after { .tour-overlay { background: transparent; } .tour-card { position: fixed; left: 50%; transform: translateX(-50%); width: min(90vw, 360px); background: var(--paper); color: var(--ink); border: 1px solid var(--line-strong); border-radius: 14px; padding: 14px 16px; box-shadow: var(--shadow); z-index: 81; } @media (prefers-reduced-motion: reduce) { .onb-confetti i { animation: none; display: none; } } +.onb-check { width: 96px; height: 96px; font-size: 52px; align-self: center; margin: 6px 0; } -- cgit v1.2.3