|
| 1 | +<!DOCTYPE html> |
| 2 | +<html lang="en"> |
| 3 | +<head> |
| 4 | + <script> |
| 5 | + // Dual-Layer Hit Counter |
| 6 | + (async () => { |
| 7 | + const key = 'collab'; |
| 8 | + const ns = 'rmkr-dev-suite'; |
| 9 | + const current = parseInt(localStorage.getItem(`stat-${key}`) || "0"); |
| 10 | + localStorage.setItem(`stat-${key}`, current + 1); |
| 11 | + try { await fetch(`https://countapi.it/hit/${ns}/${key}`, { mode: 'no-cors' }); } catch (e) {} |
| 12 | + })(); |
| 13 | + </script> |
| 14 | + <meta charset="UTF-8"> |
| 15 | + <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| 16 | + <title>Collab Pad | RMKR Dev</title> |
| 17 | + |
| 18 | + <script src="https://cdn.tailwindcss.com"></script> |
| 19 | + <script src="https://unpkg.com/peerjs@1.5.2/dist/peerjs.min.js"></script> |
| 20 | + <link href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&family=Fira+Code:wght@300..700&display=swap" rel="stylesheet"> |
| 21 | + <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css" /> |
| 22 | + |
| 23 | + <style> |
| 24 | + :root { |
| 25 | + --color-bg: #0C1524; --color-card: #18233C; |
| 26 | + --color-accent: #FFC300; --color-text: #F0F4F8; |
| 27 | + --color-line: #2D3748; |
| 28 | + } |
| 29 | + body { font-family: 'Inter', sans-serif; background-color: var(--color-bg); color: var(--color-text); height: 100vh; overflow: hidden; } |
| 30 | + .wrap { max-width: 1200px; margin: 0 auto; padding: 2rem; height: 100%; display: flex; flex-direction: column; } |
| 31 | + |
| 32 | + textarea { |
| 33 | + font-family: 'Fira Code', monospace; |
| 34 | + background: #080E1A !important; |
| 35 | + color: #4ade80 !important; |
| 36 | + border: 1px solid var(--color-line); |
| 37 | + flex-grow: 1; resize: none; outline: none; padding: 2rem; |
| 38 | + border-radius: 12px; font-size: 0.9rem; line-height: 1.6; |
| 39 | + } |
| 40 | + .status-pill { padding: 4px 12px; border-radius: 20px; font-size: 0.7rem; font-weight: 800; text-transform: uppercase; } |
| 41 | + .status-off { background: rgba(239, 68, 68, 0.1); color: #ef4444; } |
| 42 | + .status-on { background: rgba(34, 197, 94, 0.1); color: #22c55e; } |
| 43 | + </style> |
| 44 | +</head> |
| 45 | +<body> |
| 46 | + |
| 47 | +<div class="wrap"> |
| 48 | + <header class="flex justify-between items-center mb-6"> |
| 49 | + <div> |
| 50 | + <h1 class="text-2xl font-bold italic tracking-tighter">COLLAB<span class="text-[--color-accent] not-italic">PAD</span></h1> |
| 51 | + <div class="flex items-center gap-2 mt-1"> |
| 52 | + <span id="connection-status" class="status-pill status-off">Disconnected</span> |
| 53 | + <span class="text-[10px] text-gray-500 font-mono">ID: <span id="my-id" class="text-white">loading...</span></span> |
| 54 | + </div> |
| 55 | + </div> |
| 56 | + <div class="flex gap-3"> |
| 57 | + <input type="text" id="peer-id-input" placeholder="Paste Peer ID to Connect" class="bg-[#080E1A] border border-[--color-line] px-3 py-2 rounded-lg text-xs text-white outline-none focus:border-[--color-accent]"> |
| 58 | + <button onclick="connectToPeer()" class="bg-[--color-accent] text-[--color-bg] px-4 py-2 rounded-lg font-bold text-xs uppercase">Connect</button> |
| 59 | + <a href="../" class="bg-[--color-card] p-2 rounded-lg border border-[--color-line] text-[--color-accent]"><i class="fa-solid fa-house"></i></a> |
| 60 | + </div> |
| 61 | + </header> |
| 62 | + |
| 63 | + <textarea id="editor" placeholder="Start typing... Everything here is synced with your peer."></textarea> |
| 64 | + |
| 65 | + <footer class="mt-4 flex justify-between items-center text-[10px] text-gray-500 font-bold uppercase tracking-widest"> |
| 66 | + <span>P2P Encrypted Session</span> |
| 67 | + <span id="peer-info">No Peer Connected</span> |
| 68 | + </footer> |
| 69 | +</div> |
| 70 | + |
| 71 | +<script> |
| 72 | + const editor = document.getElementById('editor'); |
| 73 | + const myIdDisplay = document.getElementById('my-id'); |
| 74 | + const statusPill = document.getElementById('connection-status'); |
| 75 | + const peerInfo = document.getElementById('peer-info'); |
| 76 | + |
| 77 | + let peer = new Peer(); // Create a new PeerJS instance |
| 78 | + let conn = null; |
| 79 | + |
| 80 | + // 1. Initialize My ID |
| 81 | + peer.on('open', (id) => { |
| 82 | + myIdDisplay.innerText = id; |
| 83 | + }); |
| 84 | + |
| 85 | + // 2. Handle Incoming Connections |
| 86 | + peer.on('connection', (connection) => { |
| 87 | + setupConnection(connection); |
| 88 | + }); |
| 89 | + |
| 90 | + // 3. Connect to a Peer |
| 91 | + function connectToPeer() { |
| 92 | + const remoteId = document.getElementById('peer-id-input').value; |
| 93 | + if (!remoteId) return; |
| 94 | + const connection = peer.connect(remoteId); |
| 95 | + setupConnection(connection); |
| 96 | + } |
| 97 | + |
| 98 | + function setupConnection(connection) { |
| 99 | + conn = connection; |
| 100 | + |
| 101 | + conn.on('open', () => { |
| 102 | + statusPill.innerText = "Connected"; |
| 103 | + statusPill.className = "status-pill status-on"; |
| 104 | + peerInfo.innerText = "Connected to: " + conn.peer; |
| 105 | + |
| 106 | + // Sync current editor state immediately |
| 107 | + conn.send(editor.value); |
| 108 | + }); |
| 109 | + |
| 110 | + conn.on('data', (data) => { |
| 111 | + // Update editor when data is received from peer |
| 112 | + if (editor.value !== data) { |
| 113 | + const selectionStart = editor.selectionStart; |
| 114 | + const selectionEnd = editor.selectionEnd; |
| 115 | + editor.value = data; |
| 116 | + editor.setSelectionRange(selectionStart, selectionEnd); |
| 117 | + } |
| 118 | + }); |
| 119 | + } |
| 120 | + |
| 121 | + // 4. Send Data on Input |
| 122 | + editor.addEventListener('input', () => { |
| 123 | + if (conn && conn.open) { |
| 124 | + conn.send(editor.value); |
| 125 | + } |
| 126 | + }); |
| 127 | + |
| 128 | + // Error handling |
| 129 | + peer.on('error', (err) => { |
| 130 | + console.error(err); |
| 131 | + alert("Peer Error: " + err.type); |
| 132 | + }); |
| 133 | +</script> |
| 134 | +</body> |
| 135 | +</html> |
0 commit comments