interactive intro to open social at-me.zzstoatzz.io

feat: improve guestbook UX and add POV indicator

- Update POV indicator: change "point of view:" to "point of view of" and make handle a clickable Bluesky profile link
- Fix guestbook button avatar display: always show page owner's avatar, not authenticated user's avatar
- Add dynamic guestbook sign text: shows "you already signed" when page owner has signed, "sign the guest list" otherwise
- Update authentication success toast to mention both sign and unsign actions
- Add CSS styling for clickable POV handle with hover effects

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

Changed files
+43 -34
src
templates
static
+10 -1
src/templates/app.html
··· 1685 1685 margin-left: 0.3rem; 1686 1686 font-size: inherit; 1687 1687 opacity: 0.9; 1688 + pointer-events: auto; 1689 + text-decoration: none; 1690 + color: inherit; 1691 + transition: opacity 0.2s ease; 1692 + } 1693 + 1694 + .pov-handle:hover { 1695 + opacity: 1; 1696 + text-decoration: underline; 1688 1697 } 1689 1698 1690 1699 @media (prefers-color-scheme: dark) { ··· 1780 1789 <span class="watch-indicator"></span> 1781 1790 <span class="watch-label">watch live</span> 1782 1791 </button> 1783 - <div class="pov-indicator">point of view:<span class="pov-handle" id="povHandle"></span></div> 1792 + <div class="pov-indicator">point of view of <a class="pov-handle" id="povHandle" href="#" target="_blank" rel="noopener noreferrer"></a></div> 1784 1793 <div class="guestbook-sign">sign the guest list</div> 1785 1794 <div class="guestbook-buttons-container"> 1786 1795 <button class="view-guestbook-btn" id="viewGuestbookBtn" title="view all signatures">
+33 -33
static/app.js
··· 95 95 const povHandleEl = document.getElementById('povHandle'); 96 96 if (povHandleEl) { 97 97 povHandleEl.textContent = `@${viewedHandle}`; 98 + povHandleEl.href = `https://bsky.app/profile/${viewedHandle}`; 98 99 } 99 100 100 101 // Display user's avatar if available ··· 1745 1746 linkEl.style.display = 'none'; 1746 1747 1747 1748 actionEl.textContent = 'signed in successfully'; 1748 - collectionEl.innerHTML = 'you may now sign the guestbook with your identity'; 1749 + collectionEl.innerHTML = 'you may now sign or unsign the guestbook with your identity'; 1749 1750 1750 1751 toast.classList.add('visible'); 1751 1752 setTimeout(() => { ··· 1762 1763 const signatures = await response.json(); 1763 1764 pageOwnerHasSigned = signatures.some(sig => sig.did === did || sig.did === `at://${did}`); 1764 1765 console.log('[Guestbook] Page owner signed?', pageOwnerHasSigned); 1766 + updateGuestbookSign(); 1765 1767 return pageOwnerHasSigned; 1766 1768 } catch (error) { 1767 1769 console.error('[Guestbook] Error checking page owner signature:', error); 1768 1770 return false; 1771 + } 1772 + } 1773 + 1774 + function updateGuestbookSign() { 1775 + const sign = document.querySelector('.guestbook-sign'); 1776 + if (sign) { 1777 + sign.textContent = pageOwnerHasSigned ? 'you already signed' : 'sign the guest list'; 1769 1778 } 1770 1779 } 1771 1780 ··· 1808 1817 signGuestbookBtn.classList.add('signed'); 1809 1818 signGuestbookBtn.setAttribute('title', viewingOwnPage ? 'you\'ve signed the guestbook' : 'this user has signed the guestbook'); 1810 1819 signGuestbookBtn.disabled = false; // Allow clicking to view/unsign if own page 1811 - } else if (isAuthenticated) { 1812 - // Authenticated user - show THEIR avatar (not the page owner's) 1813 - if (authenticatedAvatar && avatarImg) { 1814 - avatarImg.src = authenticatedAvatar; 1820 + } else { 1821 + // NOT signed - ALWAYS show the page owner's avatar (viewedAvatar), regardless of auth state 1822 + if (viewedAvatar && avatarImg) { 1823 + avatarImg.src = viewedAvatar; 1815 1824 avatarImg.style.display = 'block'; 1816 1825 iconSpan.style.display = 'none'; 1817 1826 } else { ··· 1822 1831 1823 1832 textSpan.textContent = 'sign as'; 1824 1833 1825 - if (viewingOwnPage) { 1826 - // Viewing own page, authenticated, ready to sign 1827 - signGuestbookBtn.style.background = 'var(--surface)'; 1828 - signGuestbookBtn.style.color = 'var(--text)'; 1829 - signGuestbookBtn.classList.add('pulse'); 1830 - signGuestbookBtn.setAttribute('title', 'click to sign the guestbook'); 1831 - signGuestbookBtn.disabled = false; 1834 + if (isAuthenticated) { 1835 + if (viewingOwnPage) { 1836 + // Viewing own page, authenticated, ready to sign 1837 + signGuestbookBtn.style.background = 'var(--surface)'; 1838 + signGuestbookBtn.style.color = 'var(--text)'; 1839 + signGuestbookBtn.classList.add('pulse'); 1840 + signGuestbookBtn.setAttribute('title', 'click to sign the guestbook'); 1841 + signGuestbookBtn.disabled = false; 1842 + } else { 1843 + // Authenticated but viewing someone else's page - disabled 1844 + signGuestbookBtn.setAttribute('title', 'visit your own page to sign'); 1845 + signGuestbookBtn.disabled = true; 1846 + signGuestbookBtn.style.opacity = '0.5'; 1847 + signGuestbookBtn.style.cursor = 'not-allowed'; 1848 + } 1832 1849 } else { 1833 - // Authenticated but viewing someone else's page - disabled 1834 - signGuestbookBtn.setAttribute('title', 'visit your own page to sign'); 1835 - signGuestbookBtn.disabled = true; 1836 - signGuestbookBtn.style.opacity = '0.5'; 1837 - signGuestbookBtn.style.cursor = 'not-allowed'; 1838 - } 1839 - } else { 1840 - // Not authenticated - show page owner's avatar 1841 - if (viewedAvatar && avatarImg) { 1842 - avatarImg.src = viewedAvatar; 1843 - avatarImg.style.display = 'block'; 1844 - iconSpan.style.display = 'none'; 1845 - } else { 1846 - // No avatar - hide both avatar and icon, just show text 1847 - if (avatarImg) avatarImg.style.display = 'none'; 1848 - iconSpan.style.display = 'none'; 1850 + // Not authenticated - allow them to try to sign as this user 1851 + signGuestbookBtn.setAttribute('title', `sign in as @${viewedHandle || 'user'}`); 1852 + signGuestbookBtn.disabled = false; 1849 1853 } 1850 - 1851 - textSpan.textContent = 'sign as'; 1852 - signGuestbookBtn.setAttribute('title', `sign in as @${viewedHandle || 'user'}`); 1853 - signGuestbookBtn.disabled = false; 1854 1854 } 1855 1855 } 1856 1856 ··· 1869 1869 modal.innerHTML = ` 1870 1870 <h2>confirm identity</h2> 1871 1871 <p style="margin-bottom: 1rem;">are you <strong>@${suggestedHandle}</strong>?</p> 1872 - <p style="margin-bottom: 1rem; color: var(--text-light); font-size: 0.7rem;">only you can authenticate as this person. you'll be redirected to sign in.</p> 1872 + <p style="margin-bottom: 1rem; color: var(--text-light); font-size: 0.7rem;">only the owner of this identity can authenticate as @${suggestedHandle}. you'll be redirected to sign in.</p> 1873 1873 <div style="display: flex; gap: 0.5rem; justify-content: flex-end;"> 1874 1874 <button id="cancelBtn" style="background: var(--bg);">no, cancel</button> 1875 1875 <button id="confirmBtn" style="background: var(--surface-hover);">yes, that's me</button>