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 Print text'); appendLine(' clear Clear terminal output'); appendLine(' cat 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(); } };