From bb231e82b4724407640c8e73c4a9a512b1ab70e1 Mon Sep 17 00:00:00 2001 From: Dorian Date: Fri, 17 Jul 2026 02:12:54 +0100 Subject: [PATCH 1/2] fix(ui): web5 cards mobile polish + demo identity avatars - Identities render as profile cards (banner, overlapping avatar, bio) in narrow containers via container query; wide/desktop row untouched - Demo identities get purpose-tinted SVG avatars (public/demo-avatars) in both mock identity.list/get branches - Node Visibility: description stacks under icon+title on mobile, Refresh is a full-width bottom action on all sizes, discoverable warning line removed, and the demo no longer exposes an onion address Co-Authored-By: Claude Fable 5 --- neode-ui/mock-backend.js | 16 ++- neode-ui/public/demo-avatars/anon.svg | 18 +++ neode-ui/public/demo-avatars/business.svg | 20 ++++ neode-ui/public/demo-avatars/satoshi.svg | 17 +++ neode-ui/src/views/web5/Web5Identities.vue | 110 +++++++++++++++++- .../src/views/web5/Web5NodeVisibility.vue | 73 ++++++------ 6 files changed, 212 insertions(+), 42 deletions(-) create mode 100644 neode-ui/public/demo-avatars/anon.svg create mode 100644 neode-ui/public/demo-avatars/business.svg create mode 100644 neode-ui/public/demo-avatars/satoshi.svg diff --git a/neode-ui/mock-backend.js b/neode-ui/mock-backend.js index 7a1fbd50..8977a89a 100755 --- a/neode-ui/mock-backend.js +++ b/neode-ui/mock-backend.js @@ -2032,6 +2032,7 @@ app.post('/rpc/v1', (req, res) => { about: 'Self-sovereign Bitcoin node', nip05: 'satoshi@archipelago.local', lud16: 'satoshi@getalby.com', + picture: '/demo-avatars/satoshi.svg', }, }, { @@ -2044,7 +2045,7 @@ app.post('/rpc/v1', (req, res) => { is_default: false, nostr_pubkey: 'f6e5d4c3b2a19876543210fedcba9876543210fedcba9876543210fedcba98', nostr_npub: 'npub1mockanonidentitypubkeyvalue000000000000000000000000000xyz', - profile: { display_name: 'Anon' }, + profile: { display_name: 'Anon', picture: '/demo-avatars/anon.svg' }, }, { id: 'id-merchant', @@ -2056,7 +2057,7 @@ app.post('/rpc/v1', (req, res) => { is_default: false, nostr_pubkey: '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef', nostr_npub: 'npub1mockmerchantidentitypubkeyvalue000000000000000000000000def', - profile: { display_name: 'My Shop', website: 'https://myshop.onion' }, + profile: { display_name: 'My Shop', website: 'https://myshop.onion', picture: '/demo-avatars/business.svg' }, }, ], }, @@ -2079,6 +2080,7 @@ app.post('/rpc/v1', (req, res) => { profile: { display_name: 'Archipelago Node', about: 'Self-sovereign Bitcoin node', + picture: '/demo-avatars/satoshi.svg', }, }, }) @@ -2121,6 +2123,7 @@ app.post('/rpc/v1', (req, res) => { about: 'Running a sovereign Bitcoin node', nip05: 'satoshi@archipelago.local', lud16: 'satoshi@getalby.com', + picture: '/demo-avatars/satoshi.svg', }, }, { @@ -2136,6 +2139,7 @@ app.post('/rpc/v1', (req, res) => { profile: { display_name: 'Archy Consulting', about: 'Bitcoin infrastructure services', + picture: '/demo-avatars/business.svg', }, }, { @@ -2146,7 +2150,7 @@ app.post('/rpc/v1', (req, res) => { did: 'did:key:z6MknGc3ocHs3zdPiJbnaaqDi5hjrZo4HzmQnwzaxWhAbWAs', created_at: '2026-03-01T18:00:00Z', is_default: false, - profile: {}, + profile: { picture: '/demo-avatars/anon.svg' }, }, ], }, @@ -2163,7 +2167,7 @@ app.post('/rpc/v1', (req, res) => { nostr_pubkey: 'a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456', nostr_npub: 'npub1598eg0y7m08htfzjfmzv6zjvf5u7p00dr9w0yfamaxqhkwlryckq5dh9ee', is_default: true, created_at: '2026-01-10T08:00:00Z', - profile: { display_name: 'Satoshi', about: 'Running a sovereign Bitcoin node' }, + profile: { display_name: 'Satoshi', about: 'Running a sovereign Bitcoin node', picture: '/demo-avatars/satoshi.svg' }, }, } return res.json({ result: identities[id] || identities['id-primary'] }) @@ -2238,9 +2242,10 @@ app.post('/rpc/v1', (req, res) => { // Node visibility — interactive (persisted per demo session) case 'network.get-visibility': { + // No onion_address in the demo: the public showcase shouldn't display + // a Tor address (even a fake one) on the Node Visibility card. return res.json({ result: { visibility: mockState.nodeVisibility, - onion_address: mockData['server-info']['tor-address'], } }) } case 'network.set-visibility': { @@ -2248,7 +2253,6 @@ app.post('/rpc/v1', (req, res) => { if (['hidden', 'discoverable', 'public'].includes(v)) mockState.nodeVisibility = v return res.json({ result: { visibility: mockState.nodeVisibility, - onion_address: mockData['server-info']['tor-address'], } }) } diff --git a/neode-ui/public/demo-avatars/anon.svg b/neode-ui/public/demo-avatars/anon.svg new file mode 100644 index 00000000..aaf1a694 --- /dev/null +++ b/neode-ui/public/demo-avatars/anon.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/neode-ui/public/demo-avatars/business.svg b/neode-ui/public/demo-avatars/business.svg new file mode 100644 index 00000000..c2b90038 --- /dev/null +++ b/neode-ui/public/demo-avatars/business.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/neode-ui/public/demo-avatars/satoshi.svg b/neode-ui/public/demo-avatars/satoshi.svg new file mode 100644 index 00000000..9e3dcbe2 --- /dev/null +++ b/neode-ui/public/demo-avatars/satoshi.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/neode-ui/src/views/web5/Web5Identities.vue b/neode-ui/src/views/web5/Web5Identities.vue index 1d232c31..79f60ec9 100644 --- a/neode-ui/src/views/web5/Web5Identities.vue +++ b/neode-ui/src/views/web5/Web5Identities.vue @@ -61,9 +61,91 @@
+ +
+
+ +
+
+
+ +
+ + + +
+
+
+ {{ identity.profile?.display_name || identity.name }} + + {{ identity.purpose }} +
+

{{ identity.profile.about }}

+
+
+

{{ identity.did }}

+ +
+
+

{{ identity.nostr_npub }}

+ +
+
+
+
+ + +
+
@@ -465,6 +548,16 @@ watch(() => profileForm.value.picture, () => { editorPictureFailed.value = false // Rewrite onion-rooted `/blob/` URLs (with or without capability // query) to same-origin `/blob/` so they render in this UI. Data // URLs and plain external URLs pass through untouched. +// Gradient banner backdrop for the narrow profile card when the identity has +// no banner image, tinted by purpose to match the avatar/chip colors. +function bannerFallback(identity: ManagedIdentity): string { + switch (identity.purpose) { + case 'business': return 'bg-gradient-to-br from-orange-500/40 to-amber-500/15' + case 'anonymous': return 'bg-gradient-to-br from-purple-500/40 to-fuchsia-500/15' + default: return 'bg-gradient-to-br from-blue-500/40 to-indigo-500/15' + } +} + function displayableUrl(url: string | null | undefined): string { if (!url) return '' if (url.startsWith('data:') || url.startsWith('/')) return url @@ -710,8 +803,21 @@ defineExpose({ loadIdentities, managedIdentities }) container-name: identities-card; } +/* Narrow containers (default): the profile card renders and the classic row + is hidden. Wide containers: the original desktop row — untouched — and the + profile card hides. Doubled selectors out-rank the Tailwind utilities on + the same elements regardless of stylesheet order. */ +.identities-card .identity-row { + display: none; +} + @container identities-card (min-width: 560px) { - .identity-row { + .identities-card .identity-card-m { + display: none; + } + + .identities-card .identity-row { + display: flex; flex-direction: row; align-items: center; gap: 1rem; diff --git a/neode-ui/src/views/web5/Web5NodeVisibility.vue b/neode-ui/src/views/web5/Web5NodeVisibility.vue index 0574e5e9..afde2f50 100644 --- a/neode-ui/src/views/web5/Web5NodeVisibility.vue +++ b/neode-ui/src/views/web5/Web5NodeVisibility.vue @@ -1,27 +1,36 @@