Created
November 27, 2025 19:00
-
-
Save dwerbam/a5308d9d3043de1941fc89828d537e3c to your computer and use it in GitHub Desktop.
receiver
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> | |
| <title>Optical Receiver</title> | |
| <style> | |
| body { | |
| background-color: #000; | |
| color: #0f0; | |
| font-family: 'Courier New', monospace; | |
| margin: 0; | |
| height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| overflow: hidden; | |
| } | |
| /* --- Screens --- */ | |
| #start-screen, #success-screen { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| height: 100%; | |
| text-align: center; | |
| z-index: 10; | |
| background: #000; | |
| } | |
| #scan-screen { | |
| display: none; | |
| position: relative; | |
| height: 100%; | |
| width: 100%; | |
| } | |
| /* --- UI Elements --- */ | |
| h1 { margin: 0 0 20px 0; text-shadow: 0 0 10px #0f0; } | |
| button { | |
| background: #002200; | |
| border: 2px solid #0f0; | |
| color: #0f0; | |
| padding: 20px 40px; | |
| font-size: 1.2rem; | |
| font-weight: bold; | |
| border-radius: 5px; | |
| cursor: pointer; | |
| text-transform: uppercase; | |
| box-shadow: 0 0 15px #0f0; | |
| } | |
| button:active { background: #0f0; color: #000; } | |
| /* --- Video Feed --- */ | |
| video { | |
| width: 100%; | |
| height: 100%; | |
| object-fit: cover; /* Fills screen */ | |
| } | |
| /* --- Overlay Stats --- */ | |
| #overlay { | |
| position: absolute; | |
| bottom: 20px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| background: rgba(0, 0, 0, 0.8); | |
| padding: 15px; | |
| border: 1px solid #0f0; | |
| border-radius: 10px; | |
| text-align: center; | |
| width: 80%; | |
| } | |
| #progress-bar { | |
| width: 100%; | |
| height: 10px; | |
| background: #333; | |
| margin-top: 10px; | |
| border-radius: 5px; | |
| overflow: hidden; | |
| } | |
| #progress-fill { | |
| height: 100%; | |
| width: 0%; | |
| background: #0f0; | |
| transition: width 0.2s; | |
| } | |
| /* --- Success Message --- */ | |
| .success-text { font-size: 1.5rem; margin-bottom: 20px; color: #fff; } | |
| .data-preview { | |
| color: #888; | |
| font-size: 0.8rem; | |
| max-width: 80%; | |
| word-break: break-all; | |
| margin-bottom: 30px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- 1. START SCREEN --> | |
| <div id="start-screen"> | |
| <button onclick="startCamera()">read</button> | |
| <p style="color:#555; margin-top:20px; font-size: 0.8rem;">Ensure HTTPS or Localhost</p> | |
| </div> | |
| <!-- 2. SCANNING SCREEN --> | |
| <div id="scan-screen"> | |
| <video id="video-feed" playsinline autoplay muted></video> | |
| <div id="overlay"> | |
| <div id="status-text">WAITING FOR SIGNAL...</div> | |
| <div id="progress-bar"><div id="progress-fill"></div></div> | |
| </div> | |
| <!-- Back Button --> | |
| <button style="position:absolute; top:20px; right:20px; padding:10px; font-size:0.8rem; box-shadow:none;" onclick="stopCamera()">X</button> | |
| </div> | |
| <!-- 3. SUCCESS SCREEN --> | |
| <div id="success-screen" style="display:none;"> | |
| <h1 style="color: #fff;">COMPLETE</h1> | |
| <div class="success-text">Data Copied to Clipboard</div> | |
| <div class="data-preview" id="final-data">...</div> | |
| <button onclick="location.reload()">SCAN AGAIN</button> | |
| </div> | |
| <script> | |
| let detector = null; | |
| let scanInterval = null; | |
| let receivedChunks = {}; | |
| let totalChunks = 0; | |
| let videoStream = null; | |
| // Check compatibility immediately | |
| if (!('BarcodeDetector' in window)) { | |
| alert("Your browser does not support native BarcodeDetector.\nPlease use Chrome on Android/Desktop."); | |
| } else { | |
| detector = new BarcodeDetector({ formats: ['qr_code'] }); | |
| } | |
| async function startCamera() { | |
| try { | |
| // Request back camera | |
| videoStream = await navigator.mediaDevices.getUserMedia({ | |
| video: { facingMode: { exact: "environment" } } | |
| }).catch(e => { | |
| // Fallback if 'exact environment' fails (e.g. laptop) | |
| return navigator.mediaDevices.getUserMedia({ video: true }); | |
| }); | |
| const video = document.getElementById('video-feed'); | |
| video.srcObject = videoStream; | |
| // Switch UI | |
| document.getElementById('start-screen').style.display = 'none'; | |
| document.getElementById('scan-screen').style.display = 'block'; | |
| // Reset State | |
| receivedChunks = {}; | |
| totalChunks = 0; | |
| updateUI("Looking for Stream...", 0); | |
| // Start Detection Loop (Every 50ms for speed) | |
| scanInterval = setInterval(scanFrame, 50); | |
| } catch (err) { | |
| alert("Camera Error: " + err.message); | |
| } | |
| } | |
| function stopCamera() { | |
| clearInterval(scanInterval); | |
| if (videoStream) { | |
| videoStream.getTracks().forEach(track => track.stop()); | |
| } | |
| location.reload(); | |
| } | |
| async function scanFrame() { | |
| const video = document.getElementById('video-feed'); | |
| // Only scan if video is ready | |
| if (video.readyState < 2) return; | |
| try { | |
| const barcodes = await detector.detect(video); | |
| if (barcodes.length > 0) { | |
| for (const barcode of barcodes) { | |
| processQR(barcode.rawValue); | |
| } | |
| } | |
| } catch (e) { | |
| console.error(e); | |
| } | |
| } | |
| function processQR(data) { | |
| // Protocol: INDEX ||| TOTAL ||| DATA | |
| const parts = data.split("|||", 3); | |
| if (parts.length < 3) return; | |
| const index = parseInt(parts[0]); | |
| const total = parseInt(parts[1]); | |
| const content = data.substring(parts[0].length + parts[1].length + 6); // Robust substring | |
| // If new packet found | |
| if (!receivedChunks[index]) { | |
| receivedChunks[index] = content; | |
| totalChunks = total; | |
| // Update UI | |
| const count = Object.keys(receivedChunks).length; | |
| const percent = (count / total) * 100; | |
| updateUI(`Receiving: ${count} / ${total}`, percent); | |
| // Check Completion | |
| if (count === total) { | |
| finishTransfer(); | |
| } | |
| } | |
| } | |
| function updateUI(text, percent) { | |
| document.getElementById('status-text').innerText = text; | |
| document.getElementById('progress-fill').style.width = percent + "%"; | |
| } | |
| async function finishTransfer() { | |
| clearInterval(scanInterval); | |
| // Reassemble | |
| let fullText = ""; | |
| for (let i = 1; i <= totalChunks; i++) { | |
| fullText += receivedChunks[i]; | |
| } | |
| // Copy to Clipboard | |
| try { | |
| await navigator.clipboard.writeText(fullText); | |
| } catch (err) { | |
| alert("Auto-copy failed. Please manually copy the text."); | |
| } | |
| // Show Success Screen | |
| document.getElementById('scan-screen').style.display = 'none'; | |
| document.getElementById('success-screen').style.display = 'flex'; | |
| // Preview (Truncated) | |
| document.getElementById('final-data').innerText = fullText.length > 50 | |
| ? fullText.substring(0, 50) + "..." | |
| : fullText; | |
| // Stop Camera Hardware | |
| if (videoStream) { | |
| videoStream.getTracks().forEach(track => track.stop()); | |
| } | |
| } | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment