317 lines
11 KiB
JavaScript
317 lines
11 KiB
JavaScript
export default {
|
|
id: 'terminal',
|
|
title: 'Terminal',
|
|
render: function (container, api) {
|
|
var commandHistory = [];
|
|
var historyIndex = -1;
|
|
var outputLines = [];
|
|
var bus = api.bus;
|
|
|
|
// ── DOM Structure ──────────────────────────────────────
|
|
container.style.cssText = 'height:100%;width:100%;background:#0a0b0d;color:#cfd3d8;display:flex;flex-direction:column;font-family:ui-monospace,"JetBrains Mono",Menlo,monospace;overflow:hidden;cursor:text;';
|
|
|
|
var outputEl = document.createElement('div');
|
|
outputEl.style.cssText = 'flex:1;overflow-y:auto;padding:12px;font-size:14px;line-height:1.5;white-space:pre-wrap;word-break:break-word;';
|
|
|
|
var inputRow = document.createElement('div');
|
|
inputRow.style.cssText = 'display:flex;align-items:center;padding:4px 12px 12px;';
|
|
|
|
var promptEl = document.createElement('span');
|
|
promptEl.style.cssText = 'color:#d4a24c;font-size:14px;line-height:1.5;white-space:pre;user-select:none;';
|
|
promptEl.textContent = 'tarnished@meridian:~$ ';
|
|
|
|
var inputEl = document.createElement('input');
|
|
inputEl.style.cssText = 'flex:1;background:transparent;border:none;color:inherit;outline:none;font-family:inherit;font-size:14px;line-height:1.5;cursor:text;';
|
|
inputEl.setAttribute('type', 'text');
|
|
inputEl.setAttribute('autocomplete', 'off');
|
|
inputEl.setAttribute('autocorrect', 'off');
|
|
inputEl.setAttribute('autocapitalize', 'off');
|
|
inputEl.setAttribute('spellcheck', 'false');
|
|
|
|
inputRow.appendChild(promptEl);
|
|
inputRow.appendChild(inputEl);
|
|
container.appendChild(outputEl);
|
|
container.appendChild(inputRow);
|
|
|
|
// ── Helpers ────────────────────────────────────────────
|
|
function appendLine(text, color) {
|
|
var line = document.createElement('div');
|
|
line.style.cssText = 'font-size:14px;line-height:1.5;';
|
|
line.style.color = color || '#cfd3d8';
|
|
line.textContent = text;
|
|
outputEl.appendChild(line);
|
|
outputLines.push(line);
|
|
outputEl.scrollTop = outputEl.scrollHeight;
|
|
}
|
|
|
|
function printBanner() {
|
|
appendLine('Meridian Shell 1.0 (kernel 4.19-browser)');
|
|
appendLine("Type 'help' to begin. Try 'praise_the_sun' if you know what you're doing.");
|
|
appendLine('');
|
|
}
|
|
|
|
// ── Real Commands ──────────────────────────────────────
|
|
function cmdHelp() {
|
|
appendLine('Available commands:');
|
|
appendLine(' help Show this help message');
|
|
appendLine(' ls List directory contents');
|
|
appendLine(' whoami Show current user');
|
|
appendLine(' date Show current date');
|
|
appendLine(' uname Show system info');
|
|
appendLine(' echo <text> Print text');
|
|
appendLine(' clear Clear terminal output');
|
|
appendLine(' cat <file> Read a file');
|
|
appendLine(' history Show command history');
|
|
appendLine(' achievements Show unlocked achievements');
|
|
appendLine(' exit Close terminal');
|
|
appendLine('');
|
|
appendLine("Try 'praise_the_sun' if you know what you're doing.");
|
|
}
|
|
|
|
function cmdLs() {
|
|
appendLine('Documents Downloads Music Pictures save_files.dat');
|
|
}
|
|
|
|
function cmdWhoami() {
|
|
var user = api.state.get('user');
|
|
appendLine(user ? user.name : 'tarnished');
|
|
}
|
|
|
|
function cmdDate() {
|
|
appendLine(new Date().toString());
|
|
}
|
|
|
|
function cmdUname() {
|
|
appendLine('Meridian 1.0 (browser-kernel)');
|
|
}
|
|
|
|
function cmdEcho(args) {
|
|
if (args.length === 0) return;
|
|
appendLine(args.join(' '));
|
|
}
|
|
|
|
function cmdClear() {
|
|
outputEl.innerHTML = '';
|
|
outputLines = [];
|
|
}
|
|
|
|
function cmdCat(file) {
|
|
if (!file) {
|
|
appendLine('cat: missing operand', '#c85d5d');
|
|
return;
|
|
}
|
|
if (file === 'save_files.dat') {
|
|
appendLine("Ah, you want to read your save file? Not today. Some mysteries are better left unsolved.", '#d4a24c');
|
|
return;
|
|
}
|
|
appendLine('cat: ' + file + ': No such file or directory', '#c85d5d');
|
|
}
|
|
|
|
function cmdHistory() {
|
|
if (commandHistory.length === 0) {
|
|
appendLine('No commands in history.');
|
|
return;
|
|
}
|
|
commandHistory.forEach(function (cmd, i) {
|
|
appendLine(' ' + String(i + 1).padStart(4) + ' ' + cmd);
|
|
});
|
|
}
|
|
|
|
function cmdAchievements() {
|
|
var achievements = api.state.get('achievements');
|
|
if (!achievements || achievements.length === 0) {
|
|
appendLine('No achievements unlocked yet.');
|
|
return;
|
|
}
|
|
appendLine('Unlocked achievements (' + achievements.length + '):');
|
|
achievements.forEach(function (a) {
|
|
appendLine(' [x] ' + a);
|
|
});
|
|
}
|
|
|
|
function cmdExit() {
|
|
api.close();
|
|
}
|
|
|
|
// ── Easter Eggs ────────────────────────────────────────
|
|
var easterEggs = {
|
|
'praise_the_sun': {
|
|
msg: 'A grace of Gold is bestowed upon you.\nMay it guide you in the dark times ahead.',
|
|
slug: 'er_grace'
|
|
},
|
|
'tarnished': {
|
|
msg: 'Well then, tarnished. Seek strength. For that is your duty.',
|
|
slug: 'er_tarnished'
|
|
},
|
|
'try finger, but hole': {
|
|
msg: 'Message left by a fellow traveler. Rating: brilliant. Rating: inappropriate.',
|
|
slug: 'er_bong_hits'
|
|
},
|
|
'dropshot': {
|
|
msg: 'Hitmarker. Clean drop.',
|
|
slug: 'er_dropshot'
|
|
},
|
|
'360_noscope': {
|
|
msg: ' |\n /|\\\n-------\n |/\nmontage material acquired.',
|
|
slug: 'er_360noscope'
|
|
},
|
|
'mw2': {
|
|
msg: 'A true classic. Soap would be proud.',
|
|
slug: 'er_mw2'
|
|
},
|
|
'siuuu': {
|
|
msg: 'SIUUUUU! The keeper had no chance.',
|
|
slug: 'er_siuuu'
|
|
},
|
|
'golazo': {
|
|
msg: 'Top bins. What a strike. What a moment.',
|
|
slug: 'er_golazo'
|
|
},
|
|
'wonderkid': {
|
|
msg: 'Regen detected. Potential: 5 stars.',
|
|
slug: 'er_wonderkid'
|
|
},
|
|
'konami': {
|
|
msg: 'Cheat mode enabled. (it does nothing. you are already a legend.)',
|
|
slug: 'er_konami'
|
|
},
|
|
'matrix': {
|
|
msg: null,
|
|
slug: 'er_matrix'
|
|
},
|
|
'sudo rm -rf /': {
|
|
msg: 'nice try, tarnished. root is a state of mind.',
|
|
slug: 'er_sudo_rm_rf'
|
|
}
|
|
};
|
|
|
|
function triggerEasterEgg(cmdName, egg) {
|
|
bus.emit('term:cmd', { cmd: cmdName });
|
|
bus.emit('easter:found', { id: egg.slug });
|
|
|
|
if (cmdName === 'matrix') {
|
|
appendLine('Entering the matrix...', '#d4a24c');
|
|
var mlines = [];
|
|
for (var mi = 0; mi < 8; mi++) {
|
|
var row = '';
|
|
for (var mj = 0; mj < 40; mj++) {
|
|
row += Math.random() < 0.5 ? '0' : '1';
|
|
}
|
|
mlines.push(row);
|
|
}
|
|
var mlIdx = 0;
|
|
function typeNext() {
|
|
if (mlIdx < mlines.length) {
|
|
appendLine(mlines[mlIdx], '#41c35a');
|
|
mlIdx++;
|
|
setTimeout(typeNext, 30);
|
|
}
|
|
}
|
|
setTimeout(typeNext, 30);
|
|
return;
|
|
}
|
|
|
|
if (cmdName === 'sudo rm -rf /') {
|
|
appendLine('...', '#d4a24c');
|
|
setTimeout(function () {
|
|
outputEl.removeChild(outputEl.lastChild);
|
|
appendLine('nice try, tarnished. root is a state of mind.', '#d4a24c');
|
|
}, 900);
|
|
return;
|
|
}
|
|
|
|
if (egg.msg && egg.msg.indexOf('\n') !== -1) {
|
|
egg.msg.split('\n').forEach(function (ln) {
|
|
appendLine(ln, '#d4a24c');
|
|
});
|
|
} else if (egg.msg) {
|
|
appendLine(egg.msg, '#d4a24c');
|
|
}
|
|
}
|
|
|
|
// ── Command Processing ─────────────────────────────────
|
|
function emitRealCmd(cmd) {
|
|
bus.emit('term:cmd', { cmd: cmd });
|
|
}
|
|
|
|
function processInput(raw) {
|
|
var trimmed = raw.trim();
|
|
if (!trimmed) return;
|
|
|
|
commandHistory.push(trimmed);
|
|
if (commandHistory.length > 50) {
|
|
commandHistory.shift();
|
|
}
|
|
historyIndex = commandHistory.length;
|
|
|
|
appendLine(promptEl.textContent + trimmed, '#d4a24c');
|
|
|
|
if (easterEggs[trimmed]) {
|
|
triggerEasterEgg(trimmed, easterEggs[trimmed]);
|
|
return;
|
|
}
|
|
|
|
var parts = trimmed.split(/\s+/);
|
|
var cmd = parts[0].toLowerCase();
|
|
|
|
if (easterEggs[cmd]) {
|
|
triggerEasterEgg(cmd, easterEggs[cmd]);
|
|
return;
|
|
}
|
|
|
|
emitRealCmd(cmd);
|
|
switch (cmd) {
|
|
case 'help': cmdHelp(); break;
|
|
case 'ls': cmdLs(); break;
|
|
case 'whoami': cmdWhoami(); break;
|
|
case 'date': cmdDate(); break;
|
|
case 'uname': cmdUname(); break;
|
|
case 'echo': cmdEcho(parts.slice(1)); break;
|
|
case 'clear': cmdClear(); break;
|
|
case 'cat': cmdCat(parts[1]); break;
|
|
case 'history': cmdHistory(); break;
|
|
case 'achievements': cmdAchievements(); break;
|
|
case 'exit': cmdExit(); break;
|
|
default:
|
|
appendLine('Command not found: ' + cmd, '#c85d5d');
|
|
}
|
|
}
|
|
|
|
// ── Keyboard Handling ──────────────────────────────────
|
|
inputEl.addEventListener('keydown', function (e) {
|
|
if (e.key === 'Enter') {
|
|
processInput(inputEl.value);
|
|
inputEl.value = '';
|
|
historyIndex = commandHistory.length;
|
|
} else if (e.key === 'ArrowUp') {
|
|
e.preventDefault();
|
|
if (historyIndex > 0) {
|
|
historyIndex--;
|
|
inputEl.value = commandHistory[historyIndex];
|
|
}
|
|
} else if (e.key === 'ArrowDown') {
|
|
e.preventDefault();
|
|
if (historyIndex < commandHistory.length - 1) {
|
|
historyIndex++;
|
|
inputEl.value = commandHistory[historyIndex];
|
|
} else {
|
|
historyIndex = commandHistory.length;
|
|
inputEl.value = '';
|
|
}
|
|
} else if (e.ctrlKey && e.key.toLowerCase() === 'l') {
|
|
e.preventDefault();
|
|
cmdClear();
|
|
}
|
|
});
|
|
|
|
// ── Click to Focus ─────────────────────────────────────
|
|
container.addEventListener('click', function () {
|
|
inputEl.focus();
|
|
});
|
|
|
|
// ── Initial Banner ─────────────────────────────────────
|
|
printBanner();
|
|
inputEl.focus();
|
|
}
|
|
};
|