diff --git a/public/bitcoin-and-the-bible/index.html b/public/bitcoin-and-the-bible/index.html index 63b8adf..3bfb5b3 100644 --- a/public/bitcoin-and-the-bible/index.html +++ b/public/bitcoin-and-the-bible/index.html @@ -108,7 +108,14 @@

Week 6 wraps up the series with a practical, faith‑first 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.

- + + +
🔝
diff --git a/public/learn-bitcoin/index.html b/public/learn-bitcoin/index.html index 479719e..1b6bdfc 100644 --- a/public/learn-bitcoin/index.html +++ b/public/learn-bitcoin/index.html @@ -188,7 +188,7 @@ - \ No newline at end of file diff --git a/public/learn-bitcoin/nodes.html b/public/learn-bitcoin/nodes.html index 95c74de..5b590c8 100644 --- a/public/learn-bitcoin/nodes.html +++ b/public/learn-bitcoin/nodes.html @@ -62,7 +62,7 @@ } } }; - */ - (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 @@
- \${state.connected ? \`P2P E2EE • \${state.relays.length} relays\` : 'Connecting...'} + \${state.connected ? \`Encrypted • \${state.relays.length} relays\` : 'Connecting...'}
@@ -526,7 +561,7 @@ }).join('')} -
+
{ if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); diff --git a/public/nostr-chat-widget.js b/public/nostr-chat-widget.js index 1a0a699..d85cfd1 100644 --- a/public/nostr-chat-widget.js +++ b/public/nostr-chat-widget.js @@ -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 = \`
-
+

\${CONFIG.brandName}

- \${state.connected ? \`P2P E2EE • \${state.relays.length} relays\` : 'Connecting...'} + \${state.connected ? \`Encrypted • \${state.relays.length} relays\` : 'Connecting...'}
@@ -432,7 +517,7 @@
-
+
\${state.messages.length === 0 ? \`
@@ -476,7 +561,7 @@ }).join('')}
-
+
= 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 * diff --git a/public/relay/index.html b/public/relay/index.html index 0018b7c..3f919ec 100644 --- a/public/relay/index.html +++ b/public/relay/index.html @@ -45,7 +45,7 @@

Follow BTCforPlebs on Nostr or YouTube for the latest

-