Pastebin

ID: dc2c4af3 Never expires
<!-- 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 => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[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">&nbsp;</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>
Link: https://fencott.ca/p/dc2c4af3