Terminal — Creating Commands, Games & Interactive Programs
How to add your own commands, games, and interactive canvas programs to the jekyll-infops-theme terminal.
Table of Contents
The terminal embedded in the hero section is fully extensible. You can add text commands, canvas games, or any interactive program in a few minutes.
Architecture overview
assets/js/
├── modules/
│ └── hero-terminal.js ← Terminal engine (ctx API)
└── terminal-commands/
├── help.js ← Auto-built from registry
├── system.js ← clear / cls
├── color.js ← color command
├── fun.js ← matrix
├── pong.js ← Canvas game
├── pacman.js ← Canvas game
├── snake.js ← Canvas game
└── tetris.js ← Canvas game
Every command file calls window.Terminal.register() and is loaded via a <script> tag in _layouts/default.html.
Step 1 — Create the command file
Create assets/js/terminal-commands/my-command.js:
window.Terminal.register({
name: 'hello',
aliases: ['hi'],
help: ['hello [name]', 'Say hello'],
run: function(args, ctx) {
var name = args[0] || 'world';
ctx.printLine('Hello, ' + name + '!', 'term-out-bold');
}
});
Step 2 — Register it in default.html
Open _layouts/default.html and add a script tag after hero-terminal.js:
<script src="/assets/js/terminal-commands/my-command.js" defer></script>
That’s it. Rebuild Jekyll and type hello in the terminal.
The ctx API
The ctx object is passed to every command’s run function.
| Method / Property | Description |
|---|---|
ctx.printLine(text, cssClass, delayMs) |
Print a line of text |
ctx.printLines(array, cssClass, baseDelay) |
Print multiple lines |
ctx.clearOutput() |
Clear the terminal |
ctx.hideOutput() |
Hide output + input (for games) |
ctx.showOutput() |
Restore output + input |
ctx.stopGame() |
Stop canvas loop, restore terminal |
ctx.canvas |
The <canvas> element |
ctx.getGameDimensions() |
Returns {W, H} — usable canvas size |
ctx.resizeForGame(W, H) |
Resize terminal to fit game, returns Promise |
ctx.getAccentColor() |
Returns {r, g, b} — current user/theme accent |
ctx.isFullscreen() |
true if terminal is in fullscreen mode |
ctx.gameLoop.set(handle) |
Set rAF handle |
CSS classes for printLine
| Class | Appearance |
|---|---|
term-out |
Default accent color |
term-out-bold |
Bright accent, bold |
term-out-dim |
Dimmed accent |
term-out-error |
Red |
term-out-warn |
Yellow |
term-out-info |
Light blue |
term-out-ascii |
Small, dimmed — for ASCII art |
Text commands
window.Terminal.register({
name: 'uptime',
help: ['uptime', 'Show page uptime'],
run: function(args, ctx) {
var s = Math.floor(performance.now() / 1000);
ctx.printLine('up ' + Math.floor(s/60) + 'm ' + (s%60) + 's', 'term-out');
}
});
Canvas games
Pattern: resizeForGame + getAccentColor
(function() {
var keyH = null;
function cleanup(ctx) {
if (keyH) document.removeEventListener('keydown', keyH);
keyH = null;
ctx.stopGame();
}
window.Terminal.register({
name: 'mygame',
help: ['mygame', 'My canvas game', 'games'],
run: function(args, ctx) {
ctx.stopGame(); ctx.hideOutput();
// Compute desired size from viewport
var W = Math.min(window.innerWidth - 40, 800);
var H = Math.min(window.innerHeight - 140, 500);
ctx.resizeForGame(W, H).then(function() {
var canvas = ctx.canvas; if (!canvas) return;
var dim = ctx.getGameDimensions();
W = dim.W; H = dim.H;
canvas.style.display = 'block'; canvas.style.height = H + 'px';
canvas.width = W; canvas.height = H;
var c = canvas.getContext('2d');
var x = W/2, y = H/2;
keyH = function(e) {
if (e.key === 'Escape') { cleanup(ctx); return; }
if (e.key === 'ArrowLeft') x -= 8;
if (e.key === 'ArrowRight') x += 8;
if (e.key === 'ArrowUp') y -= 8;
if (e.key === 'ArrowDown') y += 8;
};
document.addEventListener('keydown', keyH);
ctx.printLine('Arrow keys · ESC to quit', 'term-out-bold');
function loop() {
// Read accent color every frame — changes instantly with color command
var ac = ctx.getAccentColor();
var light = document.documentElement.getAttribute('data-theme') === 'light';
c.fillStyle = light ? '#f0f4fc' : '#070d18';
c.fillRect(0, 0, W, H);
var color = 'rgba('+ac.r+','+ac.g+','+ac.b+',.9)';
c.beginPath(); c.arc(x, y, 20, 0, Math.PI * 2);
c.fillStyle = color; c.shadowColor = color; c.shadowBlur = 12;
c.fill(); c.shadowBlur = 0;
ctx.gameLoop.set(requestAnimationFrame(loop));
}
loop();
});
}
});
})();
Adding to /help
The help array:
help: ['command syntax', 'Description'] // regular command
help: ['command syntax', 'Description', 'games'] // listed under Games section
Customizing the neofetch boot screen
Edit _config.yml:
theme_config:
terminal_user: "user@infops" # header label
terminal_boot:
user: "loup"
host: "infops"
os: "AlmaLinux 10"
shell: "bash 5.2"
role: "SysOps"
line1: "Org: My Company"
line2: "Stack: Docker · Proxmox"
motd: "Type /help for commands."
ascii: |
██╗███╗ ██╗███████╗
██║████╗ ██║██╔════╝
██║██╔██╗ ██║█████╗
██║██║╚██╗██║██╔══╝
██║██║ ╚████║██║
╚═╝╚═╝ ╚═══╝╚═╝
Leave any field as "" to hide that line. Leave ascii empty to use the default Ubuntu-style logo.
Comments