This commit is contained in:
2025-10-10 08:23:56 -04:00
parent 3fc9ed9c37
commit 640be15ac0
9 changed files with 173 additions and 36 deletions

View File

@@ -109,6 +109,13 @@
</section> </section>
<footer id="footer"></footer> <footer id="footer"></footer>
<script src="/nostr-chat-widget.js"
data-nostr-pubkey="75462f4dece4fbde54a535cfa09eb0d329bda090a9c2f9ed6b5f9d1d2fb6c15b"
data-brand-name="Chat with BTCforPlebs"
data-color="#fdad01"
data-color-secondary="#222222">
</script>
<div id="back-to-top"><a href="#top" title="Back to Top">🔝</a></div> <div id="back-to-top"><a href="#top" title="Back to Top">🔝</a></div>
</body> </body>
</html> </html>

View File

@@ -188,7 +188,7 @@
</section> </section>
</div> </div>
<script src="https://btcforplebs.com/nostr-chat-widget.js" <script src="/nostr-chat-widget.js"
data-nostr-pubkey="75462f4dece4fbde54a535cfa09eb0d329bda090a9c2f9ed6b5f9d1d2fb6c15b" data-nostr-pubkey="75462f4dece4fbde54a535cfa09eb0d329bda090a9c2f9ed6b5f9d1d2fb6c15b"
data-brand-name="Chat with BTCforPlebs" data-brand-name="Chat with BTCforPlebs"
data-color="#fdad01" data-color="#fdad01"
@@ -213,7 +213,8 @@
if (sectionId) { if (sectionId) {
document.getElementById(sectionId).scrollIntoView({ behavior: 'smooth' }); document.getElementById(sectionId).scrollIntoView({ behavior: 'smooth' });
} }
}</script> }
</script>
</html> </html>

View File

@@ -62,7 +62,7 @@
} }
} }
};</script> };</script>
<script src="https://btcforplebs.com/nostr-chat-widget.js" <script src="/nostr-chat-widget.js"
data-nostr-pubkey="75462f4dece4fbde54a535cfa09eb0d329bda090a9c2f9ed6b5f9d1d2fb6c15b" data-nostr-pubkey="75462f4dece4fbde54a535cfa09eb0d329bda090a9c2f9ed6b5f9d1d2fb6c15b"
data-brand-name="Chat with BTCforPlebs" data-brand-name="Chat with BTCforPlebs"
data-color="#fdad01" data-color="#fdad01"

View File

@@ -44,7 +44,7 @@
<div id="back-to-top"> <div id="back-to-top">
<a href="#top" title="Back to Top">🔝</a> <a href="#top" title="Back to Top">🔝</a>
</div> </div>
<script src="https://btcforplebs.com/nostr-chat-widget.js" <script src="/nostr-chat-widget.js"
data-nostr-pubkey="75462f4dece4fbde54a535cfa09eb0d329bda090a9c2f9ed6b5f9d1d2fb6c15b" data-nostr-pubkey="75462f4dece4fbde54a535cfa09eb0d329bda090a9c2f9ed6b5f9d1d2fb6c15b"
data-brand-name="Chat with BTCforPlebs" data-brand-name="Chat with BTCforPlebs"
data-color="#fdad01" data-color="#fdad01"

View File

@@ -105,7 +105,7 @@
</div> </div>
</div> </div>
<div id="footer"></div> <div id="footer"></div>
<script src="https://btcforplebs.com/nostr-chat-widget.js" <script src="/nostr-chat-widget.js"
data-nostr-pubkey="75462f4dece4fbde54a535cfa09eb0d329bda090a9c2f9ed6b5f9d1d2fb6c15b" data-nostr-pubkey="75462f4dece4fbde54a535cfa09eb0d329bda090a9c2f9ed6b5f9d1d2fb6c15b"
data-brand-name="Chat with BTCforPlebs" data-brand-name="Chat with BTCforPlebs"
data-color="#fdad01" data-color="#fdad01"

View File

@@ -44,7 +44,7 @@
<a href="/index.html" class="button">Home</a> <a href="/index.html" class="button">Home</a>
</div> </div>
<script src="https://btcforplebs.com/nostr-chat-widget.js" <script src="/nostr-chat-widget.js"
data-nostr-pubkey="75462f4dece4fbde54a535cfa09eb0d329bda090a9c2f9ed6b5f9d1d2fb6c15b" data-nostr-pubkey="75462f4dece4fbde54a535cfa09eb0d329bda090a9c2f9ed6b5f9d1d2fb6c15b"
data-brand-name="Chat with BTCforPlebs" data-brand-name="Chat with BTCforPlebs"
data-color="#fdad01" data-color="#fdad01"

View File

@@ -10,7 +10,6 @@
* </script> * </script>
*/ */
(function() { (function() {
'use strict'; 'use strict';
@@ -38,12 +37,11 @@
} }
viewportMeta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover'; viewportMeta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover';
// Inject custom styles with glassmorphism // Inject custom styles with glassmorphism
const style = document.createElement('style'); const style = document.createElement('style');
style.textContent = ` style.textContent = `
.safe-area-bottom { .safe-area-bottom {
padding-bottom: env(safe-area-inset-bottom); padding-bottom: max(env(safe-area-inset-bottom), 1rem);
} }
#nostr-chat-widget-root > div { #nostr-chat-widget-root > div {
pointer-events: auto !important; pointer-events: auto !important;
@@ -127,6 +125,7 @@
z-index: 10 !important; z-index: 10 !important;
backdrop-filter: blur(20px) !important; backdrop-filter: blur(20px) !important;
-webkit-backdrop-filter: blur(20px) !important; -webkit-backdrop-filter: blur(20px) !important;
padding-bottom: max(env(safe-area-inset-bottom), 1.5rem) !important;
} }
} }
`; `;
@@ -200,14 +199,26 @@
} }
async function init() { async function init() {
// Check for crypto.subtle availability (requires HTTPS)
if (!window.crypto || !window.crypto.subtle) {
state.connected = false;
addMessage('system', '⚠️ Secure connection required. Please use HTTPS.');
console.error('crypto.subtle not available. Page must be served over HTTPS.');
render();
return;
}
state.myPrivKey = getSessionKey(); state.myPrivKey = getSessionKey();
state.myPubKey = getPublicKey(state.myPrivKey); state.myPubKey = getPublicKey(state.myPrivKey);
state.sessionId = state.myPubKey.substring(0, 8); state.sessionId = state.myPubKey.substring(0, 8);
console.log('🔑 Session Identity:', nip19.npubEncode(state.myPubKey)); console.log('🔑 Session Identity:', nip19.npubEncode(state.myPubKey));
console.log('📱 User Agent:', navigator.userAgent);
console.log('🌐 Connecting to relays...');
const relayPromises = CONFIG.relays.map(async (url) => { const relayPromises = CONFIG.relays.map(async (url) => {
try { try {
console.log(\`Attempting: \${url}\`);
const relay = relayInit(url); const relay = relayInit(url);
relay.on('connect', () => { relay.on('connect', () => {
@@ -217,6 +228,11 @@
relay.on('disconnect', () => { relay.on('disconnect', () => {
console.log(\`✗ Disconnected from \${url}\`); console.log(\`✗ Disconnected from \${url}\`);
checkConnection();
});
relay.on('error', (err) => {
console.error(\`❌ Relay error \${url}:\`, err);
}); });
await relay.connect(); await relay.connect();
@@ -229,13 +245,13 @@
state.relays = (await Promise.all(relayPromises)).filter(r => r !== null); state.relays = (await Promise.all(relayPromises)).filter(r => r !== null);
console.log(\`✓ Connected to \${state.relays.length}/\${CONFIG.relays.length} relays\`);
if (state.relays.length === 0) { if (state.relays.length === 0) {
addMessage('system', '⚠️ Failed to connect to any relays'); addMessage('system', '⚠️ Failed to connect to any relays. Check console for details.');
return; return;
} }
console.log(\`✓ Connected to \${state.relays.length}/\${CONFIG.relays.length} relays\`);
subscribeToReplies(); subscribeToReplies();
loadPreviousMessages(); loadPreviousMessages();
@@ -324,6 +340,10 @@
async function sendMessage() { async function sendMessage() {
if (!state.inputMessage.trim()) return; if (!state.inputMessage.trim()) return;
if (!state.connected || state.relays.length === 0) {
addMessage('system', '⚠️ Not connected to relays. Please wait...');
return;
}
const messageText = state.inputMessage; const messageText = state.inputMessage;
state.inputMessage = ''; state.inputMessage = '';
@@ -360,17 +380,26 @@
event.sig = signEvent(event, state.myPrivKey); event.sig = signEvent(event, state.myPrivKey);
let published = 0; let published = 0;
for (const relay of state.relays) { const publishPromises = state.relays.map(async (relay) => {
try { try {
await relay.publish(event); await relay.publish(event);
published++; published++;
console.log(\`✓ Published to \${relay.url}\`); console.log(\`✓ Published to \${relay.url}\`);
return true;
} catch (err) { } catch (err) {
console.error(\`✗ Failed: \${relay.url}:\`, err); console.error(\`✗ Failed: \${relay.url}:\`, err);
return false;
} }
} });
await Promise.all(publishPromises);
if (published === 0) { if (published === 0) {
// Remove the temp message
const msgIndex = state.messages.findIndex(m => m.id === tempMessage.id);
if (msgIndex !== -1) {
state.messages.splice(msgIndex, 1);
}
addMessage('system', '⚠️ Failed to send - no relay connections'); addMessage('system', '⚠️ Failed to send - no relay connections');
return; return;
} }
@@ -386,7 +415,13 @@
} catch (error) { } catch (error) {
console.error('Error sending:', error); console.error('Error sending:', error);
addMessage('system', '⚠️ Failed to send message'); // Remove the temp message on error
const msgIndex = state.messages.findIndex(m => m.id === tempMessage.id);
if (msgIndex !== -1) {
state.messages.splice(msgIndex, 1);
}
addMessage('system', '⚠️ Failed to send: ' + error.message);
render();
} }
} }
@@ -465,7 +500,7 @@
<div class="flex items-center gap-2 mt-0.5"> <div class="flex items-center gap-2 mt-0.5">
<div class="w-2 h-2 rounded-full flex-shrink-0 \${state.connected ? 'bg-green-400 animate-pulse' : 'bg-red-400'}"></div> <div class="w-2 h-2 rounded-full flex-shrink-0 \${state.connected ? 'bg-green-400 animate-pulse' : 'bg-red-400'}"></div>
<span class="text-xs text-white/80 truncate"> <span class="text-xs text-white/80 truncate">
\${state.connected ? \`P2P E2EE\${state.relays.length} relays\` : 'Connecting...'} \${state.connected ? \`Encrypted\${state.relays.length} relays\` : 'Connecting...'}
</span> </span>
</div> </div>
</div> </div>
@@ -526,7 +561,7 @@
}).join('')} }).join('')}
</div> </div>
<div class="p-3 sm:p-3.5 sm:mb-2 flex-shrink-0 safe-area-bottom mobile-input-container"> <div class="p-3 sm:p-3.5 sm:mb-2 pb-6 flex-shrink-0 safe-area-bottom mobile-input-container">
<div class="glass-input rounded-xl p-2 flex gap-2 items-center"> <div class="glass-input rounded-xl p-2 flex gap-2 items-center">
<input <input
id="nostr-message-input" id="nostr-message-input"
@@ -563,7 +598,6 @@
sendButton.disabled = !state.connected || !e.target.value.trim(); sendButton.disabled = !state.connected || !e.target.value.trim();
} }
}); });
messageInput.addEventListener('keypress', (e) => { messageInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter' && !e.shiftKey) { if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault(); e.preventDefault();

View File

@@ -33,15 +33,15 @@
if (!viewportMeta) { if (!viewportMeta) {
viewportMeta = document.createElement('meta'); viewportMeta = document.createElement('meta');
viewportMeta.name = 'viewport'; viewportMeta.name = 'viewport';
viewportMeta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no';
document.head.appendChild(viewportMeta); document.head.appendChild(viewportMeta);
} }
viewportMeta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover';
// Inject custom styles with glassmorphism // Inject custom styles with glassmorphism
const style = document.createElement('style'); const style = document.createElement('style');
style.textContent = ` style.textContent = `
.safe-area-bottom { .safe-area-bottom {
padding-bottom: env(safe-area-inset-bottom); padding-bottom: max(env(safe-area-inset-bottom), 1rem);
} }
#nostr-chat-widget-root > div { #nostr-chat-widget-root > div {
pointer-events: auto !important; pointer-events: auto !important;
@@ -71,7 +71,26 @@
.glass-input::placeholder { .glass-input::placeholder {
color: rgba(255, 255, 255, 0.6); color: rgba(255, 255, 255, 0.6);
} }
.mobile-input-container {
position: sticky;
bottom: 0;
left: 0;
right: 0;
background: transparent;
}
@media (max-width: 640px) { @media (max-width: 640px) {
html.chat-open {
overflow: hidden;
position: fixed;
width: 100%;
height: 100%;
}
body.chat-open {
overflow: hidden;
position: fixed;
width: 100%;
height: 100%;
}
#nostr-chat-widget-root .chat-window-mobile { #nostr-chat-widget-root .chat-window-mobile {
position: fixed !important; position: fixed !important;
top: 0 !important; top: 0 !important;
@@ -79,9 +98,34 @@
right: 0 !important; right: 0 !important;
bottom: 0 !important; bottom: 0 !important;
width: 100% !important; width: 100% !important;
height: 100% !important; height: 100vh !important;
height: 100dvh !important;
max-height: 100vh !important; max-height: 100vh !important;
max-height: 100dvh !important;
border-radius: 0 !important; border-radius: 0 !important;
display: flex !important;
flex-direction: column !important;
}
#nostr-chat-widget-root .chat-header-mobile {
flex-shrink: 0 !important;
}
#nostr-chat-widget-root .chat-messages-mobile {
flex: 1 1 0% !important;
overflow-y: auto !important;
overflow-x: hidden !important;
-webkit-overflow-scrolling: touch !important;
min-height: 0 !important;
overscroll-behavior: contain !important;
padding-bottom: 1rem !important;
}
#nostr-chat-widget-root .mobile-input-container {
position: sticky !important;
bottom: 0 !important;
flex-shrink: 0 !important;
z-index: 10 !important;
backdrop-filter: blur(20px) !important;
-webkit-backdrop-filter: blur(20px) !important;
padding-bottom: max(env(safe-area-inset-bottom), 1.5rem) !important;
} }
} }
`; `;
@@ -155,14 +199,26 @@
} }
async function init() { async function init() {
// Check for crypto.subtle availability (requires HTTPS)
if (!window.crypto || !window.crypto.subtle) {
state.connected = false;
addMessage('system', '⚠️ Secure connection required. Please use HTTPS.');
console.error('crypto.subtle not available. Page must be served over HTTPS.');
render();
return;
}
state.myPrivKey = getSessionKey(); state.myPrivKey = getSessionKey();
state.myPubKey = getPublicKey(state.myPrivKey); state.myPubKey = getPublicKey(state.myPrivKey);
state.sessionId = state.myPubKey.substring(0, 8); state.sessionId = state.myPubKey.substring(0, 8);
console.log('🔑 Session Identity:', nip19.npubEncode(state.myPubKey)); console.log('🔑 Session Identity:', nip19.npubEncode(state.myPubKey));
console.log('📱 User Agent:', navigator.userAgent);
console.log('🌐 Connecting to relays...');
const relayPromises = CONFIG.relays.map(async (url) => { const relayPromises = CONFIG.relays.map(async (url) => {
try { try {
console.log(\`Attempting: \${url}\`);
const relay = relayInit(url); const relay = relayInit(url);
relay.on('connect', () => { relay.on('connect', () => {
@@ -172,6 +228,11 @@
relay.on('disconnect', () => { relay.on('disconnect', () => {
console.log(\`✗ Disconnected from \${url}\`); console.log(\`✗ Disconnected from \${url}\`);
checkConnection();
});
relay.on('error', (err) => {
console.error(\`❌ Relay error \${url}:\`, err);
}); });
await relay.connect(); await relay.connect();
@@ -184,13 +245,13 @@
state.relays = (await Promise.all(relayPromises)).filter(r => r !== null); state.relays = (await Promise.all(relayPromises)).filter(r => r !== null);
console.log(\`✓ Connected to \${state.relays.length}/\${CONFIG.relays.length} relays\`);
if (state.relays.length === 0) { if (state.relays.length === 0) {
addMessage('system', '⚠️ Failed to connect to any relays'); addMessage('system', '⚠️ Failed to connect to any relays. Check console for details.');
return; return;
} }
console.log(\`✓ Connected to \${state.relays.length}/\${CONFIG.relays.length} relays\`);
subscribeToReplies(); subscribeToReplies();
loadPreviousMessages(); loadPreviousMessages();
@@ -279,6 +340,10 @@
async function sendMessage() { async function sendMessage() {
if (!state.inputMessage.trim()) return; if (!state.inputMessage.trim()) return;
if (!state.connected || state.relays.length === 0) {
addMessage('system', '⚠️ Not connected to relays. Please wait...');
return;
}
const messageText = state.inputMessage; const messageText = state.inputMessage;
state.inputMessage = ''; state.inputMessage = '';
@@ -315,17 +380,26 @@
event.sig = signEvent(event, state.myPrivKey); event.sig = signEvent(event, state.myPrivKey);
let published = 0; let published = 0;
for (const relay of state.relays) { const publishPromises = state.relays.map(async (relay) => {
try { try {
await relay.publish(event); await relay.publish(event);
published++; published++;
console.log(\`✓ Published to \${relay.url}\`); console.log(\`✓ Published to \${relay.url}\`);
return true;
} catch (err) { } catch (err) {
console.error(\`✗ Failed: \${relay.url}:\`, err); console.error(\`✗ Failed: \${relay.url}:\`, err);
return false;
} }
} });
await Promise.all(publishPromises);
if (published === 0) { if (published === 0) {
// Remove the temp message
const msgIndex = state.messages.findIndex(m => m.id === tempMessage.id);
if (msgIndex !== -1) {
state.messages.splice(msgIndex, 1);
}
addMessage('system', '⚠️ Failed to send - no relay connections'); addMessage('system', '⚠️ Failed to send - no relay connections');
return; return;
} }
@@ -341,7 +415,13 @@
} catch (error) { } catch (error) {
console.error('Error sending:', error); console.error('Error sending:', error);
addMessage('system', '⚠️ Failed to send message'); // Remove the temp message on error
const msgIndex = state.messages.findIndex(m => m.id === tempMessage.id);
if (msgIndex !== -1) {
state.messages.splice(msgIndex, 1);
}
addMessage('system', '⚠️ Failed to send: ' + error.message);
render();
} }
} }
@@ -363,8 +443,13 @@
setTimeout(() => { setTimeout(() => {
const container = document.getElementById('nostr-messages'); const container = document.getElementById('nostr-messages');
if (container) { if (container) {
// Smooth scroll on desktop, instant on mobile for better keyboard handling
if (window.innerWidth >= 640) {
container.scrollTo({ top: container.scrollHeight, behavior: 'smooth' });
} else {
container.scrollTop = container.scrollHeight; container.scrollTop = container.scrollHeight;
} }
}
}, 100); }, 100);
} }
@@ -408,14 +493,14 @@
container.innerHTML = \` container.innerHTML = \`
<div class="fixed inset-0 sm:inset-auto sm:bottom-6 sm:right-6 z-[99999]"> <div class="fixed inset-0 sm:inset-auto sm:bottom-6 sm:right-6 z-[99999]">
<div class="glass-morphism chat-window-mobile rounded-none sm:rounded-2xl shadow-2xl w-full h-full sm:w-96 sm:h-[600px] max-w-full flex flex-col overflow-hidden"> <div class="glass-morphism chat-window-mobile rounded-none sm:rounded-2xl shadow-2xl w-full h-full sm:w-96 sm:h-[600px] max-w-full flex flex-col overflow-hidden">
<div style="background: linear-gradient(to bottom right, \${CONFIG.primaryColor}, \${CONFIG.secondaryColor});" class="text-white p-3.5 sm:p-4"> <div style="background: linear-gradient(to bottom right, \${CONFIG.primaryColor}, \${CONFIG.secondaryColor});" class="text-white p-3.5 sm:p-4 chat-header-mobile">
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<div class="flex-1 min-w-0"> <div class="flex-1 min-w-0">
<h3 class="font-bold text-base sm:text-lg">\${CONFIG.brandName}</h3> <h3 class="font-bold text-base sm:text-lg">\${CONFIG.brandName}</h3>
<div class="flex items-center gap-2 mt-0.5"> <div class="flex items-center gap-2 mt-0.5">
<div class="w-2 h-2 rounded-full flex-shrink-0 \${state.connected ? 'bg-green-400 animate-pulse' : 'bg-red-400'}"></div> <div class="w-2 h-2 rounded-full flex-shrink-0 \${state.connected ? 'bg-green-400 animate-pulse' : 'bg-red-400'}"></div>
<span class="text-xs text-white/80 truncate"> <span class="text-xs text-white/80 truncate">
\${state.connected ? \`P2P E2EE\${state.relays.length} relays\` : 'Connecting...'} \${state.connected ? \`Encrypted\${state.relays.length} relays\` : 'Connecting...'}
</span> </span>
</div> </div>
</div> </div>
@@ -432,7 +517,7 @@
</div> </div>
</div> </div>
<div id="nostr-messages" class="flex-1 overflow-y-auto p-3 sm:p-3.5 space-y-3 glass-morphism-light"> <div id="nostr-messages" class="flex-1 overflow-y-auto p-3 sm:p-3.5 space-y-3 glass-morphism-light chat-messages-mobile">
\${state.messages.length === 0 ? \` \${state.messages.length === 0 ? \`
<div class="text-center text-white/60 mt-8"> <div class="text-center text-white/60 mt-8">
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" class="mx-auto mb-3 opacity-50"> <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" class="mx-auto mb-3 opacity-50">
@@ -476,7 +561,7 @@
}).join('')} }).join('')}
</div> </div>
<div class="p-3 sm:p-3.5 mb-2 flex-shrink-0 safe-area-bottom"> <div class="p-3 sm:p-3.5 sm:mb-2 pb-6 flex-shrink-0 safe-area-bottom mobile-input-container">
<div class="glass-input rounded-xl p-2 flex gap-2 items-center"> <div class="glass-input rounded-xl p-2 flex gap-2 items-center">
<input <input
id="nostr-message-input" id="nostr-message-input"
@@ -527,14 +612,22 @@
}, 100); }, 100);
} }
// Only auto-focus on desktop
if (window.innerWidth >= 640) {
messageInput.focus(); messageInput.focus();
} }
} }
}
// Expose global API // Expose global API
window.NostrChat = { window.NostrChat = {
open: async () => { open: async () => {
state.isOpen = true; state.isOpen = true;
// Prevent body scroll on mobile
if (window.innerWidth < 640) {
document.documentElement.classList.add('chat-open');
document.body.classList.add('chat-open');
}
render(); render();
if (state.relays.length === 0) { if (state.relays.length === 0) {
await init(); await init();
@@ -542,6 +635,9 @@
}, },
close: () => { close: () => {
state.isOpen = false; state.isOpen = false;
// Restore body scroll on mobile
document.documentElement.classList.remove('chat-open');
document.body.classList.remove('chat-open');
render(); render();
}, },
send: sendMessage send: sendMessage
@@ -554,7 +650,6 @@
document.body.appendChild(widgetScript); document.body.appendChild(widgetScript);
})(); })();
/*! /*!
* fill-range <https://github.com/jonschlinkert/fill-range> * fill-range <https://github.com/jonschlinkert/fill-range>
* *

View File

@@ -45,7 +45,7 @@
<p>Follow BTCforPlebs on <a href="https://nostrudel.btcforplebs.com/u/npub1w4rz7n0vunaau499xh86p84s6v5mmgys48p0nmttt7w36takc9dsf4382j">Nostr</a> or <a href="https://youtube.com/@btcforplebs">YouTube</a> for the latest</p> <p>Follow BTCforPlebs on <a href="https://nostrudel.btcforplebs.com/u/npub1w4rz7n0vunaau499xh86p84s6v5mmgys48p0nmttt7w36takc9dsf4382j">Nostr</a> or <a href="https://youtube.com/@btcforplebs">YouTube</a> for the latest</p>
</div> </div>
<script src="https://btcforplebs.com/nostr-chat-widget.js" <script src="https://btcforplebs.com/nostr-chat-widget-WIP.js"
data-nostr-pubkey="75462f4dece4fbde54a535cfa09eb0d329bda090a9c2f9ed6b5f9d1d2fb6c15b" data-nostr-pubkey="75462f4dece4fbde54a535cfa09eb0d329bda090a9c2f9ed6b5f9d1d2fb6c15b"
data-brand-name="Chat with BTCforPlebs" data-brand-name="Chat with BTCforPlebs"
data-color="#8e30eb" data-color="#8e30eb"