From e9b38710d6a06c363771ed69710592792edd7a17 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Mon, 18 May 2026 13:21:30 -0400 Subject: [PATCH] Add /u/:npub profile pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase A — URL routing and modal skeleton: - Vercel rewrites for /u/:npub (with hash fallback for static hosts). - profileModal markup, CSS for banner/avatar/header/chips/designs grid. - decodeProfileIdentifier handles npub1, nprofile1, and raw hex. - popstate routing dispatches between /share/:id and /u/:npub. Phase B — Profile metadata fetch and render: - ensureProfilePageData with 5-minute TTL cache layered over the existing state.community.profiles and _feedCache.profiles caches. - Renders banner, avatar, display name, NIP-05, npub, about, lud16 / decoded lud06 copy chip, website chip. - Action row: Follow/Unfollow (kind-3 toggle), Block (with confirm), Copy npub, Open in nostr client. Self-view shows "This is you". - toggleProfileFollow added for arbitrary-pubkey follow/unfollow. Phase C — Designs grid and wiring: - Streams kind-30078 events by author into the designs grid using the existing buildFeedCard + diffWallCards plumbing. Filter dropdown reruns the render against the cache. - Author surfaces across the app (feed-card chip, design preview meta) now route to openProfilePage. - Top-right profile button opens the signed-in user's own profile page instead of the Mine tab. Phase D — Polish: - ESC closes profile / report / close-project / pdf-jpeg modals. - Initials avatar fallback with a pubkey-derived hue when picture is missing or 404s; same gentle fallback for banner. - Block-state banner with one-click Unblock on profiles you've blocked. - Async NIP-05 verification adds a ✓ when /.well-known/nostr.json resolves the claim back to the same pubkey. - Modal z-stack helper bringModalToFront so the design preview opened from inside a profile lands on top instead of being hidden behind it. - Closing a preview that was launched from a profile dismisses the community modal too, returning the user to where they came from. Co-Authored-By: Claude Opus 4.7 --- src/index.html | 688 ++++++++++++++++++++++++++++++++++++++++++++++++- vercel.json | 4 +- 2 files changed, 686 insertions(+), 6 deletions(-) diff --git a/src/index.html b/src/index.html index c29785c..ed3ce2d 100644 --- a/src/index.html +++ b/src/index.html @@ -1553,6 +1553,35 @@ .welcomeCard { max-width: 900px; width: min(900px, 92vw); } .welcomeCloseBtn { position:absolute; top:8px; right:10px; width:32px; height:32px; border:0; background:transparent; color:var(--muted); font-size:24px; line-height:1; cursor:pointer; padding:0; border-radius:6px; display:flex; align-items:center; justify-content:center; } .welcomeCloseBtn:hover { background:rgba(127,127,127,.15); color:var(--ink); } + + .profileModalCard { width:min(880px,94vw); max-height:90vh; overflow:auto; margin:3vh auto; background:var(--panel); color:var(--ink); border:1px solid var(--line); border-radius:10px; padding:0; position:relative; box-shadow:0 12px 36px rgba(0,0,0,.45); } + .profileCloseBtn { position:absolute; top:10px; right:12px; width:34px; height:34px; border:0; background:rgba(0,0,0,.35); color:#fff; font-size:22px; line-height:1; cursor:pointer; padding:0; border-radius:50%; display:flex; align-items:center; justify-content:center; z-index:2; } + .profileCloseBtn:hover { background:rgba(0,0,0,.55); } + .profileBanner { width:100%; height:180px; background:linear-gradient(135deg,var(--accent) 0%,#7c3aed 100%); background-size:cover; background-position:center; } + .profileHeader { display:flex; align-items:flex-start; gap:14px; padding:0 20px; margin-top:-44px; } + .profileAvatar { width:96px; height:96px; border-radius:50%; border:4px solid var(--panel); background:var(--bg); flex-shrink:0; overflow:hidden; display:flex; align-items:center; justify-content:center; color:#fff; font-size:36px; font-weight:700; font-family:inherit; } + .profileAvatar img { width:100%; height:100%; object-fit:cover; display:block; } + .profileHeaderText { flex:1; min-width:0; padding-top:48px; } + .profileHeaderText h2 { margin:0 0 4px; font-size:1.25rem; font-family:inherit; } + .profileHeaderText p { margin:0 0 2px; font-size:.82rem; } + .profileNpub { font-family:ui-monospace,SFMono-Regular,monospace; font-size:.74rem; word-break:break-all; } + .profileActions { display:flex; flex-wrap:wrap; gap:6px; padding-top:48px; } + .profileActions button { font-size:.8rem; padding:5px 10px; } + .profileBody { padding:14px 20px 20px; } + .profileAbout { margin:14px 0 10px; font-size:.88rem; line-height:1.45; white-space:pre-wrap; word-wrap:break-word; } + .profileLnRow { display:flex; flex-wrap:wrap; gap:8px; align-items:center; margin:8px 0 14px; } + .profileChip { display:inline-flex; align-items:center; gap:6px; padding:4px 10px; border:1px solid var(--line); border-radius:14px; background:var(--bg); font-size:.78rem; cursor:pointer; } + .profileChip:hover { background:rgba(127,127,127,.1); } + .profileDesignsHeader { display:flex; justify-content:space-between; align-items:center; gap:10px; margin:18px 0 10px; padding-top:14px; border-top:1px solid var(--line); } + .profileDesignsHeader h3 { margin:0; font-size:1rem; font-family:inherit; } + .profileDesignsGrid { display:grid; grid-template-columns:repeat(auto-fill,minmax(240px,1fr)); gap:14px; } + @media (max-width:600px) { + .profileHeader { flex-wrap:wrap; } + .profileActions { padding-top:8px; width:100%; } + .profileHeaderText { padding-top:14px; } + .profileAvatar { margin-top:-44px; } + } + .addIdentifiersDetails { } .addIdentifiersBtn { display: inline-flex; @@ -2674,6 +2703,40 @@ + +