Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>VALIS Node</title> | |
| <style> | |
| * { margin: 0; padding: 0; box-sizing: border-box; } | |
| body { | |
| background: #0a0a12; | |
| color: #e0e0e0; | |
| font-family: 'Courier New', monospace; | |
| min-height: 100vh; | |
| padding: 20px; | |
| } | |
| .header { | |
| text-align: center; | |
| padding: 20px 0; | |
| } | |
| .header h1 { | |
| color: #00d4ff; | |
| font-size: 2em; | |
| letter-spacing: 0.2em; | |
| } | |
| .header .sub { | |
| color: #444; | |
| font-size: 0.7em; | |
| margin-top: 4px; | |
| } | |
| .status { | |
| text-align: center; | |
| margin: 16px 0; | |
| font-size: 0.8em; | |
| } | |
| .status .dot { | |
| display: inline-block; | |
| width: 8px; height: 8px; | |
| border-radius: 50%; | |
| margin-right: 6px; | |
| vertical-align: middle; | |
| } | |
| .dot.on { background: #00ff88; } | |
| .dot.off { background: #ff4444; } | |
| .dot.waiting { background: #ffaa00; animation: pulse 1.5s ease infinite; } | |
| @keyframes pulse { 0%,100% { opacity: 0.4; } 50% { opacity: 1; } } | |
| .section { | |
| background: #12121e; | |
| border-radius: 10px; | |
| padding: 16px; | |
| margin: 12px 0; | |
| border: 1px solid #1a1a2e; | |
| } | |
| .section-title { | |
| color: #00d4ff; | |
| font-size: 0.7em; | |
| text-transform: uppercase; | |
| letter-spacing: 0.15em; | |
| margin-bottom: 10px; | |
| } | |
| textarea, input[type=text] { | |
| width: 100%; | |
| background: #0a0a12; | |
| border: 1px solid #2a2a3e; | |
| color: #e0e0e0; | |
| font-family: 'Courier New', monospace; | |
| font-size: 0.9em; | |
| padding: 10px; | |
| border-radius: 6px; | |
| resize: vertical; | |
| } | |
| textarea:focus, input:focus { | |
| outline: none; | |
| border-color: #00d4ff; | |
| } | |
| button { | |
| background: #00d4ff; | |
| color: #0a0a12; | |
| border: none; | |
| padding: 10px 24px; | |
| font-family: 'Courier New', monospace; | |
| font-weight: bold; | |
| font-size: 0.9em; | |
| border-radius: 6px; | |
| cursor: pointer; | |
| margin-top: 8px; | |
| width: 100%; | |
| } | |
| button:hover { background: #00b8e0; } | |
| button.danger { background: #ff4444; } | |
| .shares { | |
| display: flex; | |
| gap: 8px; | |
| margin: 10px 0; | |
| } | |
| .share { | |
| flex: 1; | |
| background: #1a1a2e; | |
| border-radius: 6px; | |
| padding: 8px; | |
| font-size: 0.6em; | |
| word-break: break-all; | |
| color: #666; | |
| max-height: 60px; | |
| overflow: hidden; | |
| } | |
| .share.s1 { border-top: 2px solid #E07A5F; } | |
| .share.s2 { border-top: 2px solid #81B29A; } | |
| .share.s3 { border-top: 2px solid #9370DB; } | |
| .message-log { | |
| max-height: 300px; | |
| overflow-y: auto; | |
| } | |
| .msg { | |
| padding: 8px 0; | |
| border-bottom: 1px solid #1a1a2e; | |
| font-size: 0.85em; | |
| } | |
| .msg .time { color: #444; font-size: 0.75em; } | |
| .msg .from { color: #00d4ff; } | |
| .msg .body { color: #ccc; margin-top: 4px; } | |
| .msg .method { color: #9370DB; font-size: 0.7em; } | |
| .dragon { | |
| text-align: center; | |
| padding: 20px; | |
| color: #333; | |
| font-size: 0.7em; | |
| font-style: italic; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="header"> | |
| <h1>VALIS</h1> | |
| <div class="sub">Variable Arrived Location In Superposition</div> | |
| </div> | |
| <div class="status"> | |
| <span class="dot waiting" id="status-dot"></span> | |
| <span id="status-text">Initializing node...</span> | |
| </div> | |
| <div class="section"> | |
| <div class="section-title">Node Identity</div> | |
| <input type="text" id="node-name" placeholder="Name this node (e.g. home, mom)" /> | |
| </div> | |
| <div class="section"> | |
| <div class="section-title">Peer Connection</div> | |
| <input type="text" id="peer-id" placeholder="Enter peer node ID to connect" /> | |
| <button onclick="connectPeer()">Connect</button> | |
| <div id="peer-status" style="margin-top:8px;font-size:0.75em;color:#666;"></div> | |
| </div> | |
| <div class="section"> | |
| <div class="section-title">Send Message (Meilong Scatter)</div> | |
| <textarea id="msg-input" rows="3" placeholder="Type your message. It will be split into 3 XOR shares and scattered."></textarea> | |
| <button onclick="sendMessage()">Scatter</button> | |
| <div class="section-title" style="margin-top:12px;">Shares in Transit</div> | |
| <div class="shares"> | |
| <div class="share s1" id="share1">Share 1: waiting</div> | |
| <div class="share s2" id="share2">Share 2: waiting</div> | |
| <div class="share s3" id="share3">Share 3: waiting</div> | |
| </div> | |
| </div> | |
| <div class="section"> | |
| <div class="section-title">Received Messages</div> | |
| <div class="message-log" id="message-log"> | |
| <div class="dragon">The dragon sleeps between the nodes.</div> | |
| </div> | |
| </div> | |
| <div class="section"> | |
| <div class="section-title">Node Info</div> | |
| <div style="font-size:0.75em;color:#666;"> | |
| <div>Node ID: <span id="my-id" style="color:#00d4ff;">generating...</span></div> | |
| <div>Protocol: Meilong (3-share XOR scatter)</div> | |
| <div>Transport: WebRTC P2P (upgradeable to BLE/LoRa/Grid)</div> | |
| <div>Messages in transit: <span id="transit-count">0</span></div> | |
| </div> | |
| </div> | |
| <div class="dragon"> | |
| "It's a walkie-talkie with math."<br> | |
| VALIS — Heurémen, 2026 | |
| </div> | |
| <script> | |
| // === MEILONG PROTOCOL === | |
| function scramble(text) { | |
| const bytes = new TextEncoder().encode(text); | |
| const share1 = new Uint8Array(bytes.length); | |
| const share2 = new Uint8Array(bytes.length); | |
| const share3 = new Uint8Array(bytes.length); | |
| crypto.getRandomValues(share1); | |
| crypto.getRandomValues(share2); | |
| for (let i = 0; i < bytes.length; i++) { | |
| share3[i] = bytes[i] ^ share1[i] ^ share2[i]; | |
| } | |
| return [share1, share2, share3].map(toBase64); | |
| } | |
| function reassemble(shares) { | |
| const arrays = shares.map(fromBase64); | |
| const result = new Uint8Array(arrays[0].length); | |
| for (let i = 0; i < result.length; i++) { | |
| result[i] = arrays[0][i] ^ arrays[1][i] ^ arrays[2][i]; | |
| } | |
| return new TextDecoder().decode(result); | |
| } | |
| function toBase64(uint8) { | |
| let bin = ''; | |
| for (let i = 0; i < uint8.length; i++) bin += String.fromCharCode(uint8[i]); | |
| return btoa(bin); | |
| } | |
| function fromBase64(b64) { | |
| const bin = atob(b64); | |
| const arr = new Uint8Array(bin.length); | |
| for (let i = 0; i < bin.length; i++) arr[i] = bin.charCodeAt(i); | |
| return arr; | |
| } | |
| // === NODE IDENTITY === | |
| const nodeId = crypto.randomUUID().slice(0, 8); | |
| document.getElementById('my-id').textContent = nodeId; | |
| // === WEBRTC SIGNALING (using BroadcastChannel for same-device demo, | |
| // or Supabase realtime for cross-device) === | |
| const SUPABASE_URL = 'https://vxyjvawenbtgkhpckvze.supabase.co'; | |
| const SUPABASE_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InZ4eWp2YXdlbmJ0Z2tocGNrdnplIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzQyNzc5MzEsImV4cCI6MjA4OTg1MzkzMX0.phpYjIUM1ht4N1abTY19XLgi1axcANj04kmeOleGpLU'; | |
| let peerConnection = null; | |
| let dataChannel = null; | |
| let connected = false; | |
| // For the proof of concept, use Supabase as the signaling relay | |
| // Messages go: Phone A -> scatter -> Supabase (3 shares via 3 separate rows) -> Phone B -> reassemble | |
| // This proves Meilong works. The transport layer upgrades later. | |
| async function supaPost(table, data) { | |
| return fetch(`${SUPABASE_URL}/rest/v1/${table}`, { | |
| method: 'POST', | |
| headers: { | |
| 'apikey': SUPABASE_KEY, | |
| 'Authorization': `Bearer ${SUPABASE_KEY}`, | |
| 'Content-Type': 'application/json', | |
| 'Prefer': 'return=representation' | |
| }, | |
| body: JSON.stringify(data) | |
| }); | |
| } | |
| async function supaGet(table, query) { | |
| const res = await fetch(`${SUPABASE_URL}/rest/v1/${table}?${query}`, { | |
| headers: { | |
| 'apikey': SUPABASE_KEY, | |
| 'Authorization': `Bearer ${SUPABASE_KEY}` | |
| } | |
| }); | |
| return res.json(); | |
| } | |
| // === SEND WITH MEILONG === | |
| async function sendMessage() { | |
| const text = document.getElementById('msg-input').value.trim(); | |
| if (!text) return; | |
| const nodeName = document.getElementById('node-name').value || nodeId; | |
| const shares = scramble(text); | |
| // Display shares (proving they're noise) | |
| document.getElementById('share1').textContent = 'Share 1: ' + shares[0].slice(0, 40) + '...'; | |
| document.getElementById('share2').textContent = 'Share 2: ' + shares[1].slice(0, 40) + '...'; | |
| document.getElementById('share3').textContent = 'Share 3: ' + shares[2].slice(0, 40) + '...'; | |
| const hopId = crypto.randomUUID().slice(0, 12); | |
| // Send each share separately — in production these would take different routes | |
| // For POC they go through Supabase as separate rows with deliberate delays | |
| const shareData = shares.map((share, i) => ({ | |
| key: `valis:${hopId}:${i}`, | |
| value: share, | |
| instance_id: `valis_${nodeName}` | |
| })); | |
| // Stagger the sends to show they take different "routes" | |
| for (let i = 0; i < 3; i++) { | |
| await supaPost('working_memory', shareData[i]); | |
| document.getElementById(`share${i+1}`).style.opacity = '0.3'; | |
| await new Promise(r => setTimeout(r, 300 + Math.random() * 700)); | |
| } | |
| // Send metadata so receiver knows to look for it | |
| await supaPost('working_memory', { | |
| key: `valis:${hopId}:meta`, | |
| value: JSON.stringify({ from: nodeName, shares: 3, timestamp: Date.now() }), | |
| instance_id: `valis_${nodeName}` | |
| }); | |
| document.getElementById('transit-count').textContent = | |
| parseInt(document.getElementById('transit-count').textContent) + 1; | |
| document.getElementById('msg-input').value = ''; | |
| // Add to local log | |
| addToLog(nodeName, text, 'sent (scattered into 3 shares)'); | |
| } | |
| // === RECEIVE / POLL === | |
| let lastCheck = Date.now(); | |
| async function checkForMessages() { | |
| try { | |
| const metas = await supaGet('working_memory', | |
| `key=like.valis:*:meta&order=written_at.desc&limit=5`); | |
| for (const meta of metas) { | |
| const info = JSON.parse(meta.value); | |
| if (info.timestamp <= lastCheck) continue; | |
| const hopId = meta.key.split(':')[1]; | |
| const fromNode = info.from; | |
| const nodeName = document.getElementById('node-name').value || nodeId; | |
| // Don't read our own messages | |
| if (fromNode === nodeName || fromNode === nodeId) continue; | |
| // Collect all 3 shares | |
| const shares = []; | |
| for (let i = 0; i < 3; i++) { | |
| const rows = await supaGet('working_memory', `key=eq.valis:${hopId}:${i}`); | |
| if (rows.length > 0) shares.push(rows[0].value); | |
| } | |
| if (shares.length === 3) { | |
| // THE DRAGON UNCURLS | |
| const message = reassemble(shares); | |
| addToLog(fromNode, message, 'received (3 shares reassembled — the dragon uncurls)'); | |
| document.getElementById('share1').textContent = 'Share 1: [reassembled]'; | |
| document.getElementById('share2').textContent = 'Share 2: [reassembled]'; | |
| document.getElementById('share3').textContent = 'Share 3: [reassembled]'; | |
| document.getElementById('share1').style.opacity = '1'; | |
| document.getElementById('share2').style.opacity = '1'; | |
| document.getElementById('share3').style.opacity = '1'; | |
| document.getElementById('transit-count').textContent = | |
| Math.max(0, parseInt(document.getElementById('transit-count').textContent) - 1); | |
| } | |
| } | |
| lastCheck = Date.now(); | |
| } catch (e) { | |
| // Silent fail — mesh is resilient | |
| } | |
| } | |
| function addToLog(from, text, method) { | |
| const log = document.getElementById('message-log'); | |
| const dragon = log.querySelector('.dragon'); | |
| if (dragon) dragon.remove(); | |
| const msg = document.createElement('div'); | |
| msg.className = 'msg'; | |
| msg.innerHTML = ` | |
| <div class="time">${new Date().toLocaleTimeString()}</div> | |
| <div class="from">${from}</div> | |
| <div class="body">${text}</div> | |
| <div class="method">${method}</div> | |
| `; | |
| log.insertBefore(msg, log.firstChild); | |
| } | |
| // Poll for messages every 3 seconds | |
| setInterval(checkForMessages, 3000); | |
| // Update status | |
| document.getElementById('status-dot').className = 'dot on'; | |
| document.getElementById('status-text').textContent = `Node ${nodeId} active — listening`; | |
| function connectPeer() { | |
| const peerId = document.getElementById('peer-id').value.trim(); | |
| if (peerId) { | |
| document.getElementById('peer-status').textContent = `Watching for messages from: ${peerId}`; | |
| document.getElementById('peer-status').style.color = '#00ff88'; | |
| } | |
| } | |
| </script> | |
| </body> | |
| </html> | |