<!-- OG Terminal --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1" /> <title>OG Linux Terminal</title> <style> :root{ /* classic console */ --bg: #000; /* page */ --term-bg: #000; /* terminal */ --fg: #c0c0c0; /* text gray */ --muted: #8a8a8a; --mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace; /* 80x24 vibe */ --cols: 80; --rows: 24; --font-size: 16px; --line-height: 1.35; --pad-v: 8px; --pad-h: 10px; } * { box-sizing: border-box; } html, body { height: 100%; } body { margin: 0; background: var(--bg); color: var(--fg); font-family: var(--mono); font-size: var(--font-size); line-height: var(--line-height); user-select: none; cursor: none; /* hide mouse */ overflow: hidden; /* no page scroll */ } /* center the terminal */ .wrap { height: 100%; width: 100%; display: grid; place-items: center; } .terminal { background: var(--term-bg); width: calc(var(--cols) * 1ch + var(--pad-h) * 2); height: calc(var(--rows) * 1lh + var(--pad-v) * 2); /* 1lh = line-height unit */ border: 1px solid #222; box-shadow: 0 0 0 1px #111; padding: var(--pad-v) var(--pad-h); display: flex; flex-direction: column; outline: none; /* we’ll focus via JS */ } .screen { flex: 1; overflow: auto; scrollbar-color: #333 transparent; padding-right: 2px; } .line { white-space: pre-wrap; word-break: break-word; } .sys { color: var(--muted); } .out { color: var(--fg); } /* prompt pieces to mimic bash default */ .prompt .user { color: var(--fg); } .prompt .at { color: var(--muted); } .prompt .host { color: var(--fg); } .prompt .path { color: #b6b6b6; } .prompt .dollar { color: var(--fg); } /* active input line */ .typed { white-space: pre; } .caret { display: inline-block; width: 0.65ch; height: 1em; background: #d0d0d0; margin-left: -0.05ch; vertical-align: -0.15em; } @keyframes blink { 0%,49% { opacity: 1; } 50%,100% { opacity: 0; } } .blink { animation: blink 1s step-start infinite; } /* disable pointer interactions to keep it keyboard-only */ .wrap, .terminal, .screen { pointer-events: none; } </style> </head> <body> <div class="wrap"> <div class="terminal" id="term" tabindex="0" aria-label="terminal"> <div id="screen" class="screen" aria-live="polite"></div> </div> </div> <script> (() => { const term = document.getElementById('term'); const screen = document.getElementById('screen'); const state = { user: 'user', host: 'host', path: '~', input: '', active: null, // current input line element }; /* ---------- helpers ---------- */ const esc = s => String(s).replace(/[&<>"']/g, m => ({'&':'&','<':'<','>':'>','"':'"'}[m])); function mkPromptHTML() { return ( `<span class="prompt">` + `<span class="user">${esc(state.user)}</span>` + `<span class="at">@</span>` + `<span class="host">${esc(state.host)}</span>:` + ` <span class="path">${esc(state.path)}</span>` + `<span class="dollar">$</span>` + `</span> ` ); } function appendLine(innerHTML, cls='line') { const div = document.createElement('div'); div.className = `line ${cls}`; div.innerHTML = innerHTML; screen.appendChild(div); screen.scrollTop = screen.scrollHeight; return div; } function newPromptLine() { // create a new input line as part of the scrollback (inline typing) const html = mkPromptHTML() + `<span class="typed"></span><span class="caret blink" aria-hidden="true"> </span>`; state.active = appendLine(html); state.input = ''; } function renderInput() { if (!state.active) return; const typed = state.active.querySelector('.typed'); typed.textContent = state.input; screen.scrollTop = screen.scrollHeight; } function printOut(text) { appendLine(esc(text), 'out'); } /* ---------- input behavior ---------- */ function onKeyDown(e) { // keep keyboard-only and allow terminal-like scrolling if (e.key === 'ArrowUp') { e.preventDefault(); screen.scrollBy({ top: -20 }); return; } if (e.key === 'ArrowDown') { e.preventDefault(); screen.scrollBy({ top: 20 }); return; } if (e.key === 'PageUp') { e.preventDefault(); screen.scrollBy({ top: -screen.clientHeight }); return; } if (e.key === 'PageDown') { e.preventDefault(); screen.scrollBy({ top: screen.clientHeight }); return; } // clear screen like Ctrl+L if (e.ctrlKey && e.key.toLowerCase() === 'l') { e.preventDefault(); screen.innerHTML = ''; newPromptLine(); return; } // simple editing (no left/right movement by design) if (e.key === 'Backspace') { e.preventDefault(); if (state.input.length) { state.input = state.input.slice(0, -1); renderInput(); } return; } if (e.key === 'Enter') { e.preventDefault(); // finalize current input line (leave it in the buffer) const cmd = state.input.trimEnd(); // remove caret from the old line (make it static) const caret = state.active?.querySelector('.caret'); if (caret) caret.remove(); // "run": print the command as output, then add fresh prompt if (cmd.length) printOut(cmd); newPromptLine(); renderInput(); return; } // regular printable chars if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) { e.preventDefault(); state.input += e.key; renderInput(); return; } } /* ---------- disable mouse completely ---------- */ function killMouse(ev){ ev.preventDefault(); ev.stopPropagation(); return false; } ['click','mousedown','mouseup','mousemove','wheel','contextmenu'].forEach(type => { window.addEventListener(type, killMouse, { capture: true }); }); /* ---------- init ---------- */ term.addEventListener('keydown', onKeyDown); term.focus(); // optional boot-ish header lines (comment out if you want a cold blank) appendLine('Linux host 6.2.0-xx-generic tty1', 'sys'); appendLine('', 'sys'); appendLine('host login: user', 'sys'); appendLine(`Last login: ${new Date().toLocaleString()} on tty1`, 'sys'); newPromptLine(); renderInput(); })(); </script> </body> </html>