| <!DOCTYPE html> |
| <html> |
| <head> |
| <meta charset="UTF-8"> |
| <title>HF Space Terminal</title> |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.min.css" /> |
| <script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.min.js"></script> |
| <script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.min.js"></script> |
| <style> |
| body { margin: 0; background: #1e1e1e; overflow: hidden; } |
| #terminal { height: 100vh; width: 100vw; } |
| #status { position: absolute; top: 10px; right: 10px; color: #0f0; background: rgba(0,0,0,0.7); padding: 5px 10px; border-radius: 4px; font-family: sans-serif; font-size: 12px; z-index: 999; pointer-events: none; } |
| </style> |
| </head> |
| <body> |
| <div id="status">Connecting...</div> |
| <div id="terminal"></div> |
| <script> |
| const term = new Terminal({ |
| cursorBlink: true, |
| theme: { background: '#1e1e1e', foreground: '#ffffff' }, |
| fontSize: 14, |
| fontFamily: 'Menlo, Monaco, "Courier New", monospace' |
| }); |
| const fitAddon = new FitAddon.FitAddon(); |
| term.loadAddon(fitAddon); |
| term.open(document.getElementById('terminal')); |
| fitAddon.fit(); |
| |
| |
| const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; |
| const wsUrl = `${protocol}//${window.location.host}/ws`; |
| |
| let sock = null; |
| let reconnectTimer = null; |
| |
| function connect() { |
| if (reconnectTimer) clearTimeout(reconnectTimer); |
| document.getElementById('status').innerText = "Connecting..."; |
| |
| try { |
| sock = new WebSocket(wsUrl); |
| |
| sock.onopen = () => { |
| document.getElementById('status').innerText = "Connected (Ready)"; |
| document.getElementById('status').style.color = "#0f0"; |
| term.write('\\r\\n\\x1b[32m>>> HF Space Ubuntu Terminal Ready <<<\\x1b[0m\\r\\n'); |
| term.write('\\r\\nType commands (e.g., ls, g++, vim)...\\r\\n\\r\\n'); |
| |
| |
| fitAddon.fit(); |
| const dims = { cols: term.cols, rows: term.rows }; |
| sock.send(JSON.stringify({ resize: dims })); |
| }; |
| |
| term.onData(data => { |
| if (sock && sock.readyState === WebSocket.OPEN) { |
| sock.send(data); |
| } |
| }); |
| |
| sock.onmessage = event => { |
| if (event.data instanceof ArrayBuffer) { |
| term.write(new Uint8Array(event.data)); |
| } else { |
| term.write(event.data); |
| } |
| }; |
| |
| sock.onclose = () => { |
| document.getElementById('status').innerText = "Disconnected. Reconnecting..."; |
| document.getElementById('status').style.color = "#ffa500"; |
| reconnectTimer = setTimeout(connect, 2000); |
| }; |
| |
| sock.onerror = () => { |
| document.getElementById('status').innerText = "Error"; |
| document.getElementById('status').style.color = "#ff0000"; |
| }; |
| } catch (e) { |
| document.getElementById('status').innerText = "Error: " + e.message; |
| } |
| } |
| |
| connect(); |
| window.addEventListener('resize', () => { |
| fitAddon.fit(); |
| if (sock && sock.readyState === WebSocket.OPEN) { |
| sock.send(JSON.stringify({ resize: { cols: term.cols, rows: term.rows } })); |
| } |
| }); |
| </script> |
| </body> |
| </html> |