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

@@ -108,7 +108,14 @@
<p>Week6 wraps up the series with a practical, faithfirst guide to the next steps in a Bitcoin journey. Logan reviews the final podcast episodes, outlines how to move from “just learning” to owning, securing, and sharing Bitcoin, and reminds listeners that the true goal is to place Christ first, practice generosity, and build a financial legacy that glorifies God. The session ends with a heartfelt prayer and an invitation for anyone to step into the gospel—one small step at a time.</p>
</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>
</body>
</html>

View File

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

View File

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

View File

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

View File

@@ -105,7 +105,7 @@
</div>
</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-brand-name="Chat with BTCforPlebs"
data-color="#fdad01"

View File

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

View File

@@ -10,7 +10,6 @@
* </script>
*/
(function() {
'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';
// Inject custom styles with glassmorphism
const style = document.createElement('style');
style.textContent = `
.safe-area-bottom {
padding-bottom: env(safe-area-inset-bottom);
padding-bottom: max(env(safe-area-inset-bottom), 1rem);
}
#nostr-chat-widget-root > div {
pointer-events: auto !important;
@@ -127,6 +125,7 @@
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;
}
}
`;
@@ -200,14 +199,26 @@
}
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.myPubKey = getPublicKey(state.myPrivKey);
state.sessionId = state.myPubKey.substring(0, 8);
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) => {
try {
console.log(\`Attempting: \${url}\`);
const relay = relayInit(url);
relay.on('connect', () => {
@@ -217,6 +228,11 @@
relay.on('disconnect', () => {
console.log(\`✗ Disconnected from \${url}\`);
checkConnection();
});
relay.on('error', (err) => {
console.error(\`❌ Relay error \${url}:\`, err);
});
await relay.connect();
@@ -229,13 +245,13 @@
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) {
addMessage('system', '⚠️ Failed to connect to any relays');
addMessage('system', '⚠️ Failed to connect to any relays. Check console for details.');
return;
}
console.log(\`✓ Connected to \${state.relays.length}/\${CONFIG.relays.length} relays\`);
subscribeToReplies();
loadPreviousMessages();
@@ -324,6 +340,10 @@
async function sendMessage() {
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;
state.inputMessage = '';
@@ -360,17 +380,26 @@
event.sig = signEvent(event, state.myPrivKey);
let published = 0;
for (const relay of state.relays) {
const publishPromises = state.relays.map(async (relay) => {
try {
await relay.publish(event);
published++;
console.log(\`✓ Published to \${relay.url}\`);
return true;
} catch (err) {
console.error(\`✗ Failed: \${relay.url}:\`, err);
return false;
}
}
});
await Promise.all(publishPromises);
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');
return;
}
@@ -386,7 +415,13 @@
} catch (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="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">
\${state.connected ? \`P2P E2EE\${state.relays.length} relays\` : 'Connecting...'}
\${state.connected ? \`Encrypted\${state.relays.length} relays\` : 'Connecting...'}
</span>
</div>
</div>
@@ -526,7 +561,7 @@
}).join('')}
</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">
<input
id="nostr-message-input"
@@ -563,7 +598,6 @@
sendButton.disabled = !state.connected || !e.target.value.trim();
}
});
messageInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();

View File

@@ -33,15 +33,15 @@
if (!viewportMeta) {
viewportMeta = document.createElement('meta');
viewportMeta.name = 'viewport';
viewportMeta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no';
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
const style = document.createElement('style');
style.textContent = `
.safe-area-bottom {
padding-bottom: env(safe-area-inset-bottom);
padding-bottom: max(env(safe-area-inset-bottom), 1rem);
}
#nostr-chat-widget-root > div {
pointer-events: auto !important;
@@ -71,7 +71,26 @@
.glass-input::placeholder {
color: rgba(255, 255, 255, 0.6);
}
.mobile-input-container {
position: sticky;
bottom: 0;
left: 0;
right: 0;
background: transparent;
}
@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 {
position: fixed !important;
top: 0 !important;
@@ -79,9 +98,34 @@
right: 0 !important;
bottom: 0 !important;
width: 100% !important;
height: 100% !important;
height: 100vh !important;
height: 100dvh !important;
max-height: 100vh !important;
max-height: 100dvh !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() {
// 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.myPubKey = getPublicKey(state.myPrivKey);
state.sessionId = state.myPubKey.substring(0, 8);
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) => {
try {
console.log(\`Attempting: \${url}\`);
const relay = relayInit(url);
relay.on('connect', () => {
@@ -172,6 +228,11 @@
relay.on('disconnect', () => {
console.log(\`✗ Disconnected from \${url}\`);
checkConnection();
});
relay.on('error', (err) => {
console.error(\`❌ Relay error \${url}:\`, err);
});
await relay.connect();
@@ -184,13 +245,13 @@
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) {
addMessage('system', '⚠️ Failed to connect to any relays');
addMessage('system', '⚠️ Failed to connect to any relays. Check console for details.');
return;
}
console.log(\`✓ Connected to \${state.relays.length}/\${CONFIG.relays.length} relays\`);
subscribeToReplies();
loadPreviousMessages();
@@ -279,6 +340,10 @@
async function sendMessage() {
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;
state.inputMessage = '';
@@ -315,17 +380,26 @@
event.sig = signEvent(event, state.myPrivKey);
let published = 0;
for (const relay of state.relays) {
const publishPromises = state.relays.map(async (relay) => {
try {
await relay.publish(event);
published++;
console.log(\`✓ Published to \${relay.url}\`);
return true;
} catch (err) {
console.error(\`✗ Failed: \${relay.url}:\`, err);
return false;
}
}
});
await Promise.all(publishPromises);
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');
return;
}
@@ -341,7 +415,13 @@
} catch (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,7 +443,12 @@
setTimeout(() => {
const container = document.getElementById('nostr-messages');
if (container) {
container.scrollTop = container.scrollHeight;
// 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;
}
}
}, 100);
}
@@ -408,14 +493,14 @@
container.innerHTML = \`
<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 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-1 min-w-0">
<h3 class="font-bold text-base sm:text-lg">\${CONFIG.brandName}</h3>
<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>
<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>
</div>
</div>
@@ -432,7 +517,7 @@
</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 ? \`
<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">
@@ -476,7 +561,7 @@
}).join('')}
</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">
<input
id="nostr-message-input"
@@ -527,7 +612,10 @@
}, 100);
}
messageInput.focus();
// Only auto-focus on desktop
if (window.innerWidth >= 640) {
messageInput.focus();
}
}
}
@@ -535,6 +623,11 @@
window.NostrChat = {
open: async () => {
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();
if (state.relays.length === 0) {
await init();
@@ -542,6 +635,9 @@
},
close: () => {
state.isOpen = false;
// Restore body scroll on mobile
document.documentElement.classList.remove('chat-open');
document.body.classList.remove('chat-open');
render();
},
send: sendMessage
@@ -554,7 +650,6 @@
document.body.appendChild(widgetScript);
})();
/*!
* 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>
</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-brand-name="Chat with BTCforPlebs"
data-color="#8e30eb"