diff --git a/client/receive.html b/client/receive.html
index 120afa8..b45e2a1 100644
--- a/client/receive.html
+++ b/client/receive.html
@@ -26,27 +26,64 @@
     <!-- Main Content -->
     <div class="content">
         <div class="info-box">
-            <p>This tool allows you to receive and view HTTP events (kind 21120) from Nostr relays. Connect to a relay, subscribe to events, and decrypt messages.</p>
+            <p>This tool allows you to receive and view HTTP events (kind 21120) from Nostr relays, QR codes, or raw text input. Choose your preferred method using the tabs below.</p>
         </div>
         
-        <h2>Relay Connection</h2>
-        <div class="relay-connection">
-            <div class="relay-input-container">
-                <label for="relayUrl">Relay URL:</label>
-                <input type="text" id="relayUrl" value="wss://relay.degmods.com" placeholder="wss://relay.example.com">
-                <button id="connectRelayBtn" class="relay-connect-button">Connect</button>
+        <!-- Tab Navigation - Updated data-tab attributes to match section IDs -->
+        <div class="tabs">
+            <button class="tab-button active" data-tab="relay-connection-section">From Relay</button>
+            <button class="tab-button" data-tab="qr-code-scanner-section">From QR Scanner</button>
+            <button class="tab-button" data-tab="raw-event-input-section">From Raw Text</button>
+        </div>
+        
+        <!-- Tab Content - Each section gets a distinct ID -->
+        <!-- Relay Tab Content -->
+        <div id="relay-connection-section" class="active">
+            <h2>Relay Connection</h2>
+            <div class="relay-connection">
+                <div class="relay-input-container">
+                    <label for="relayUrl">Relay URL:</label>
+                    <input type="text" id="relayUrl" value="wss://relay.degmods.com" placeholder="wss://relay.example.com">
+                    <button id="connectRelayBtn" class="relay-connect-button">Connect</button>
+                </div>
+                <div id="relayStatus" class="relay-status">Not connected</div>
+            </div>
+        </div>
+        
+        <!-- QR Scanner Tab Content -->
+        <div id="qr-code-scanner-section">
+            <h2>QR Code Scanner</h2>
+            <div class="qr-scanner-container">
+                <div class="qr-viewport">
+                    <video id="qrVideo"></video>
+                    <canvas id="qrCanvas" style="display: none;"></canvas>
+                </div>
+                <div class="qr-controls">
+                    <button id="startScanBtn" class="action-button">Start Camera</button>
+                    <button id="stopScanBtn" class="action-button" disabled>Stop Camera</button>
+                </div>
+                <div id="qrStatus" class="status-message">Camera inactive. Click Start Camera to begin scanning.</div>
+            </div>
+        </div>
+        
+        <!-- Raw Text Tab Content -->
+        <div id="raw-event-input-section">
+            <h2>Raw Event Input</h2>
+            <div class="raw-input-container">
+                <p>Paste a raw Nostr event JSON below:</p>
+                <textarea id="rawEventInput" placeholder='{"id": "...", "pubkey": "...", "created_at": 1234567890, "kind": 21120, "tags": [], "content": "..."}' rows="10"></textarea>
+                <button id="parseRawEventBtn" class="action-button">Parse Event</button>
+                <div id="rawInputStatus" class="status-message"></div>
             </div>
-            <div id="relayStatus" class="relay-status">Not connected</div>
         </div>
         
         <h2>Received Events</h2>
         <div class="received-events">
-            
             <div class="events-container">
                 <div class="events-sidebar">
                     <div id="eventsList" class="events-list">
                         <div class="empty-state">
-                            No events received yet. Connect to a relay to start receiving events.
+                            No events received yet. Use one of the methods above to receive events.
                         </div>
                         <!-- Events will be displayed here -->
                     </div>
diff --git a/client/src/receiver.ts b/client/src/receiver.ts
index 327ee96..388eac1 100644
--- a/client/src/receiver.ts
+++ b/client/src/receiver.ts
@@ -1,4 +1,5 @@
 // External dependencies
+import jsQR from 'jsqr';
 import * as nostrTools from 'nostr-tools';
 
 // Internal imports
@@ -706,6 +707,271 @@ Decrypted key: ${decryptedKey}`;
     }
 }
 
+// Setup tab navigation
+function setupTabNavigation(): void {
+    const tabButtons = document.querySelectorAll('.tab-button');
+    const contentSections = document.querySelectorAll('#relay-connection-section, #qr-code-scanner-section, #raw-event-input-section');
+    
+    // Add click event listeners to tab buttons
+    tabButtons.forEach(button => {
+        button.addEventListener('click', () => {
+            // Get the target tab
+            const targetTabId = (button as HTMLElement).dataset.tab;
+            if (!targetTabId) {
+                return;
+            }
+            
+            // Remove active class from all buttons and content sections
+            tabButtons.forEach(btn => btn.classList.remove('active'));
+            contentSections.forEach(section => section.classList.remove('active'));
+            
+            // Add active class to clicked button and corresponding content section
+            button.classList.add('active');
+            const targetSection = document.getElementById(targetTabId);
+            if (targetSection) {
+                targetSection.classList.add('active');
+            }
+
+            // Log for debugging
+            console.log(`Tab switched to: ${targetTabId}, active sections: ${document.querySelectorAll('.active').length}`);
+        });
+    });
+}
+
+// Variables for QR scanner
+let qrScanning = false;
+let videoStream: MediaStream | null = null;
+
+// Setup QR code scanner
+function setupQRScanner(): void {
+    const startScanBtn = document.getElementById('startScanBtn');
+    const stopScanBtn = document.getElementById('stopScanBtn');
+    const qrVideo = document.getElementById('qrVideo') as HTMLVideoElement;
+    const qrCanvas = document.getElementById('qrCanvas') as HTMLCanvasElement;
+    const qrStatus = document.getElementById('qrStatus');
+    
+    // Function to start the QR scanner
+    async function startQRScanner() {
+        // Already scanning
+        if (qrScanning) {
+            return;
+        }
+        
+        try {
+            // Get access to the camera
+            const stream = await navigator.mediaDevices.getUserMedia({
+                video: { facingMode: 'environment' }
+            });
+            
+            // Update status
+            if (qrStatus) {
+                qrStatus.textContent = 'Camera active. Point at a QR code.';
+                qrStatus.className = 'status-message';
+            }
+            
+            // Set video source to camera stream
+            if (qrVideo) {
+                qrVideo.srcObject = stream;
+                qrVideo.play();
+                videoStream = stream;
+            }
+            
+            // Enable/disable buttons
+            if (startScanBtn) {
+                startScanBtn.setAttribute('disabled', 'true');
+            }
+            if (stopScanBtn) {
+                stopScanBtn.removeAttribute('disabled');
+            }
+            
+            // Start scanning
+            qrScanning = true;
+            scanQRCode();
+        } catch (error) {
+            // Handle errors
+            console.error('Error accessing camera:', error);
+            if (qrStatus) {
+                qrStatus.textContent = `Error: ${error instanceof Error ? error.message : String(error)}`;
+                qrStatus.className = 'status-message error';
+            }
+        }
+    }
+    
+    // Function to stop the QR scanner
+    function stopQRScanner() {
+        if (!qrScanning) {
+            return;
+        }
+        
+        // Stop all tracks
+        if (videoStream) {
+            videoStream.getTracks().forEach(track => track.stop());
+            videoStream = null;
+        }
+        
+        // Update status
+        if (qrStatus) {
+            qrStatus.textContent = 'Camera inactive. Click Start Camera to begin scanning.';
+            qrStatus.className = 'status-message';
+        }
+        
+        // Enable/disable buttons
+        if (startScanBtn) {
+            startScanBtn.removeAttribute('disabled');
+        }
+        if (stopScanBtn) {
+            stopScanBtn.setAttribute('disabled', 'true');
+        }
+        
+        qrScanning = false;
+    }
+    
+    // Add event listeners
+    if (startScanBtn) {
+        startScanBtn.addEventListener('click', startQRScanner);
+    }
+    
+    if (stopScanBtn) {
+        stopScanBtn.addEventListener('click', stopQRScanner);
+    }
+    
+    // Function to scan for QR codes
+    function scanQRCode() {
+        if (!qrScanning) {
+            return;
+        }
+        
+        // Request animation frame for next scan
+        window.requestAnimationFrame(scanQRCode);
+        
+        if (!qrVideo || !qrCanvas) {
+            return;
+        }
+        
+        // Skip if video isn't playing
+        if (qrVideo.readyState !== qrVideo.HAVE_ENOUGH_DATA) {
+            return;
+        }
+        
+        // Get canvas context
+        const context = qrCanvas.getContext('2d');
+        if (!context) {
+            return;
+        }
+        
+        // Set canvas dimensions to match video
+        qrCanvas.width = qrVideo.videoWidth;
+        qrCanvas.height = qrVideo.videoHeight;
+        
+        // Draw current video frame to canvas
+        context.drawImage(qrVideo, 0, 0, qrCanvas.width, qrCanvas.height);
+        
+        // Get image data for QR detection
+        const imageData = context.getImageData(0, 0, qrCanvas.width, qrCanvas.height);
+        
+        // Process with jsQR
+        const code = jsQR(imageData.data, imageData.width, imageData.height, {
+            inversionAttempts: 'dontInvert'
+        });
+        
+        // Process QR code if found
+        if (code) {
+            console.log('QR code detected:', code.data);
+            if (qrStatus) {
+                qrStatus.textContent = 'QR code found! Processing...';
+            }
+            
+            // Try to parse as a Nostr event
+            try {
+                const eventData = JSON.parse(code.data);
+                
+                // Check if this is a valid Nostr event
+                if (eventData && eventData.id && eventData.pubkey &&
+                    eventData.created_at && eventData.kind === 21120) {
+                    
+                    // Stop scanning after successful detection
+                    stopQRScanner();
+                    
+                    if (qrStatus) {
+                        qrStatus.textContent = 'Valid Nostr event found! Event processed.';
+                    }
+                    
+                    // Process the event
+                    processEvent(eventData);
+                } else {
+                    if (qrStatus) {
+                        qrStatus.textContent = 'QR code found, but not a valid kind 21120 Nostr event.';
+                    }
+                }
+            } catch (error) {
+                console.error('Error parsing QR code data:', error);
+                if (qrStatus) {
+                    qrStatus.textContent = `Error parsing QR data: ${error instanceof Error ? error.message : String(error)}`;
+                }
+            }
+        }
+    }
+    
+    // Add event listeners
+    if (startScanBtn) {
+        startScanBtn.addEventListener('click', startQRScanner);
+    }
+    
+    if (stopScanBtn) {
+        stopScanBtn.addEventListener('click', stopQRScanner);
+    }
+}
+
+// Setup raw text input
+function setupRawTextInput(): void {
+    const parseRawEventBtn = document.getElementById('parseRawEventBtn');
+    const rawEventInput = document.getElementById('rawEventInput') as HTMLTextAreaElement;
+    const rawInputStatus = document.getElementById('rawInputStatus');
+    
+    if (parseRawEventBtn && rawEventInput) {
+        parseRawEventBtn.addEventListener('click', () => {
+            const rawText = rawEventInput.value.trim();
+            
+            if (!rawText) {
+                if (rawInputStatus) {
+                    rawInputStatus.textContent = 'Please enter a raw Nostr event.';
+                }
+                return;
+            }
+            
+            try {
+                // Parse the JSON data
+                const eventData = JSON.parse(rawText);
+                
+                // Validate as Nostr event
+                if (eventData && eventData.id && eventData.pubkey &&
+                    eventData.created_at && eventData.kind === 21120) {
+                    
+                    // Process the event
+                    processEvent(eventData);
+                    
+                    if (rawInputStatus) {
+                        rawInputStatus.textContent = 'Event successfully processed!';
+                    }
+                    
+                    // Clear the input for convenience
+                    rawEventInput.value = '';
+                } else {
+                    if (rawInputStatus) {
+                        rawInputStatus.textContent = 'Not a valid kind 21120 Nostr event.';
+                    }
+                }
+            } catch (error) {
+                console.error('Error parsing raw event:', error);
+                if (rawInputStatus) {
+                    rawInputStatus.textContent = `Error parsing: ${error instanceof Error ? error.message : String(error)}`;
+                }
+            }
+        });
+    }
+}
+
+
 // Initialize
 document.addEventListener('DOMContentLoaded', () => {
     // Get DOM elements
@@ -716,6 +982,15 @@ document.addEventListener('DOMContentLoaded', () => {
     
     const connectRelayBtn = document.getElementById('connectRelayBtn');
     
+    // Tab functionality
+    setupTabNavigation();
+    
+    // QR code scanner setup
+    setupQRScanner();
+    
+    // Raw text input setup
+    setupRawTextInput();
+    
     // Connect to relay and automatically start subscription for logged-in user
     if (connectRelayBtn) {
         connectRelayBtn.addEventListener('click', async () => {
diff --git a/client/src/styles.css b/client/src/styles.css
index 81e3b12..0f2fd84 100644
--- a/client/src/styles.css
+++ b/client/src/styles.css
@@ -471,4 +471,131 @@ pre {
     .content {
         padding: 10px 0;
     }
+}
+
+/* Tab Interface Styles */
+.tabs {
+    display: flex;
+    border-bottom: 2px solid var(--border-color);
+    margin-bottom: 20px;
+}
+
+.tab-button {
+    padding: 10px 20px;
+    background: none;
+    border: none;
+    border-bottom: 2px solid transparent;
+    cursor: pointer;
+    font-weight: 500;
+    color: var(--text-secondary);
+    margin-right: 10px;
+    margin-bottom: -2px;
+    transition: all 0.3s ease;
+}
+
+.tab-button:hover {
+    color: var(--accent-color);
+}
+
+.tab-button.active {
+    color: var(--accent-color);
+    border-bottom: 2px solid var(--accent-color);
+}
+
+/* Tab content sections */
+#relay-connection-section,
+#qr-code-scanner-section,
+#raw-event-input-section {
+    display: none;
+    margin-top: 20px;
+    margin-bottom: 20px;
+}
+
+#relay-connection-section.active,
+#qr-code-scanner-section.active,
+#raw-event-input-section.active {
+    display: block;
+}
+
+/* QR Scanner Styles */
+.qr-scanner-container {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    gap: 15px;
+}
+
+.qr-viewport {
+    width: 100%;
+    max-width: 500px;
+    height: 300px;
+    position: relative;
+    overflow: hidden;
+    border: 3px solid var(--accent-color);
+    border-radius: 8px;
+    background-color: var(--bg-tertiary);
+}
+
+.qr-viewport video {
+    width: 100%;
+    height: 100%;
+    object-fit: cover;
+}
+
+.qr-controls {
+    display: flex;
+    gap: 10px;
+    margin-top: 10px;
+}
+
+.action-button {
+    padding: 8px 15px;
+    background-color: var(--button-primary);
+    color: white;
+    border: none;
+    border-radius: 4px;
+    cursor: pointer;
+}
+
+.action-button:hover {
+    background-color: var(--button-hover);
+}
+
+.action-button:disabled {
+    background-color: var(--bg-tertiary);
+    color: var(--text-tertiary);
+    cursor: not-allowed;
+}
+
+.status-message {
+    margin-top: 10px;
+    padding: 8px 12px;
+    border-radius: 4px;
+    background-color: var(--bg-tertiary);
+    color: var(--text-secondary);
+    font-size: 14px;
+}
+
+/* Raw Input Styles */
+.raw-input-container {
+    display: flex;
+    flex-direction: column;
+    gap: 10px;
+}
+
+.raw-input-container textarea {
+    width: 100%;
+    min-height: 200px;
+    padding: 12px;
+    border: 1px solid var(--border-color);
+    border-radius: 4px;
+    background-color: var(--bg-tertiary);
+    color: var(--text-primary);
+    font-family: 'Courier New', monospace;
+    font-size: 14px;
+    resize: vertical;
+}
+
+.raw-input-container button {
+    align-self: flex-start;
 }
\ No newline at end of file
diff --git a/client/styles.css b/client/styles.css
index 8bafbe6..c6cd1a5 100644
--- a/client/styles.css
+++ b/client/styles.css
@@ -1158,4 +1158,131 @@ footer {
 
 .clear-events-button:hover {
     background-color: #c82333;
+}
+
+/* Tab Interface Styles */
+.tabs {
+    display: flex;
+    border-bottom: 2px solid var(--border-color);
+    margin-bottom: 20px;
+}
+
+.tab-button {
+    padding: 10px 20px;
+    background: none;
+    border: none;
+    border-bottom: 2px solid transparent;
+    cursor: pointer;
+    font-weight: 500;
+    color: var(--text-secondary);
+    margin-right: 10px;
+    margin-bottom: -2px;
+    transition: all 0.3s ease;
+}
+
+.tab-button:hover {
+    color: var(--accent-color);
+}
+
+.tab-button.active {
+    color: var(--accent-color);
+    border-bottom: 2px solid var(--accent-color);
+}
+
+/* Tab content sections */
+#relay-connection-section,
+#qr-code-scanner-section,
+#raw-event-input-section {
+    display: none;
+    margin-top: 20px;
+    margin-bottom: 20px;
+}
+
+#relay-connection-section.active,
+#qr-code-scanner-section.active,
+#raw-event-input-section.active {
+    display: block;
+}
+
+/* QR Scanner Styles */
+.qr-scanner-container {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    gap: 15px;
+}
+
+.qr-viewport {
+    width: 100%;
+    max-width: 500px;
+    height: 300px;
+    position: relative;
+    overflow: hidden;
+    border: 3px solid var(--accent-color);
+    border-radius: 8px;
+    background-color: var(--bg-tertiary);
+}
+
+.qr-viewport video {
+    width: 100%;
+    height: 100%;
+    object-fit: cover;
+}
+
+.qr-controls {
+    display: flex;
+    gap: 10px;
+    margin-top: 10px;
+}
+
+.action-button {
+    padding: 8px 15px;
+    background-color: var(--button-primary);
+    color: white;
+    border: none;
+    border-radius: 4px;
+    cursor: pointer;
+}
+
+.action-button:hover {
+    background-color: var(--button-hover);
+}
+
+.action-button:disabled {
+    background-color: var(--bg-tertiary);
+    color: var(--text-tertiary);
+    cursor: not-allowed;
+}
+
+.status-message {
+    margin-top: 10px;
+    padding: 8px 12px;
+    border-radius: 4px;
+    background-color: var(--bg-tertiary);
+    color: var(--text-secondary);
+    font-size: 14px;
+}
+
+/* Raw Input Styles */
+.raw-input-container {
+    display: flex;
+    flex-direction: column;
+    gap: 10px;
+}
+
+.raw-input-container textarea {
+    width: 100%;
+    min-height: 200px;
+    padding: 12px;
+    border: 1px solid var(--border-color);
+    border-radius: 4px;
+    background-color: var(--bg-tertiary);
+    color: var(--text-primary);
+    font-family: 'Courier New', monospace;
+    font-size: 14px;
+    resize: vertical;
+}
+
+.raw-input-container button {
+    align-self: flex-start;
 }
\ No newline at end of file
diff --git a/client/webpack.config.js b/client/webpack.config.js
index 67e1084..25bafa5 100644
--- a/client/webpack.config.js
+++ b/client/webpack.config.js
@@ -25,7 +25,7 @@ module.exports = {
     new NodePolyfillPlugin(),
     new CopyPlugin({
       patterns: [
-        { from: 'src/styles.css', to: 'styles.css' },
+        { from: 'styles.css', to: 'styles.css' },
         { from: 'http.png', to: 'http.png' },
         { from: 'index.html', to: 'index.html' },
         { from: 'help.html', to: 'help.html' },
diff --git a/package-lock.json b/package-lock.json
index b1258ca..1941a34 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,7 @@
       "version": "1.0.0",
       "license": "MIT",
       "dependencies": {
+        "jsqr": "^1.4.0",
         "qrcode": "^1.5.4"
       }
     },
@@ -126,6 +127,12 @@
         "node": ">=8"
       }
     },
+    "node_modules/jsqr": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/jsqr/-/jsqr-1.4.0.tgz",
+      "integrity": "sha512-dxLob7q65Xg2DvstYkRpkYtmKm2sPJ9oFhrhmudT1dZvNFFTlroai3AWSpLey/w5vMcLBXRgOJsbXpdN9HzU/A==",
+      "license": "Apache-2.0"
+    },
     "node_modules/locate-path": {
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
diff --git a/package.json b/package.json
index 672f71b..cba74fe 100644
--- a/package.json
+++ b/package.json
@@ -24,6 +24,7 @@
   "author": "",
   "license": "MIT",
   "dependencies": {
+    "jsqr": "^1.4.0",
     "qrcode": "^1.5.4"
   }
 }