feat: add simcity, pacman, tetris as inline retro-game easter eggs

Generalized the doom launcher into launchGame(name) keyed by archive.org identifier; each cds into a fake .<name> folder and runs <name>.sh, same as doom.
This commit is contained in:
kgva CMS
2026-08-10 23:03:53 +00:00
parent d436474719
commit a49603890b
2 changed files with 36 additions and 22 deletions
+5 -4
View File
@@ -276,14 +276,15 @@ body:not(.home) .menu { display: none; }
} }
.proj-path__seg--last { font-weight: 700; } .proj-path__seg--last { font-weight: 700; }
/* secret `doom` — runs inline as doom.sh inside a fake .doom folder, same /* secret retro games (doom, simcity, pacman, tetris) — each runs inline as
file-listing chrome as any other page. sized to the classic 4:3 game. */ <name>.sh inside a fake .<name> folder, same file-listing chrome as any
.doom-player { other page. sized to the classic 4:3 DOS/arcade aspect ratio. */
.game-player {
width: 100%; width: 100%;
aspect-ratio: 4 / 3; aspect-ratio: 4 / 3;
background: #000; background: #000;
} }
.doom-player iframe { .game-player iframe {
width: 100%; width: 100%;
height: 100%; height: 100%;
border: 0; border: 0;
+31 -18
View File
@@ -203,38 +203,50 @@
setTimeout(function () { pre.remove(); }, 4200); setTimeout(function () { pre.remove(); }, 4200);
} }
// secret: `doom` cds into a fake ".doom" folder and runs doom.sh — shareware // secret: retro games (doom, simcity, pacman, tetris) cd into a fake
// DOOM (id Software), embedded inline in the same file-listing chrome as any // ".<name>" folder and run "<name>.sh" — archive.org DOSBox emulators
// other page (not a floating window). ".." is a real link back to wherever // embedded inline in the same file-listing chrome as any other page (not a
// you typed the command from, so leaving is just normal navigation — nav.js's // floating window). ".." is a real link back to wherever you typed the
// pjax swap restores the real page (and real breadcrumb) on its own. // command from, so leaving is just normal navigation — nav.js's pjax swap
function doom() { // restores the real page (and real breadcrumb) on its own. archive.org
// still gates each one behind its own "click to begin" — that's the
// emulator requiring a real user gesture before it can play audio, a
// cross-origin iframe we can't click into or configure around.
var GAMES = {
doom: "DoomsharewareEpisode",
simcity: "msdos_SimCity_1989",
pacman: "msdos_Pac-Man_1983",
tetris: "msdos_Tetris_1987"
};
function launchGame(name) {
var archiveId = GAMES[name];
var main = document.getElementById("main-content"); var main = document.getElementById("main-content");
if (!main || document.querySelector(".doom-embed")) return; if (!archiveId || !main || document.querySelector(".game-embed")) return;
var back = window.location.pathname + window.location.search; var back = window.location.pathname + window.location.search;
var file = name + ".sh";
var pathEl = document.querySelector(".prompt__path"); var pathEl = document.querySelector(".prompt__path");
if (pathEl && /\$$/.test(pathEl.innerHTML)) { if (pathEl && /\$$/.test(pathEl.innerHTML)) {
pathEl.innerHTML = pathEl.innerHTML.replace(/\$$/, '/<span class="prompt__seg">.doom</span>$'); pathEl.innerHTML = pathEl.innerHTML.replace(/\$$/, '/<span class="prompt__seg">.' + name + '</span>$');
} }
main.innerHTML = main.innerHTML =
'<div class="tui tui--files filebrowser doom-embed">' + '<div class="tui tui--files filebrowser game-embed">' +
'<p class="tui__cmd"><span class="tui__sym">$</span> ls</p>' + '<p class="tui__cmd"><span class="tui__sym">$</span> ls</p>' +
'<p class="tui__cmd tui__total">total 1</p>' + '<p class="tui__cmd tui__total">total 1</p>' +
'<ul class="tui__ls">' + '<ul class="tui__ls">' +
'<li><a href="' + back + '" data-name="..">' + '<li><a href="' + back + '" data-name="..">' +
'<span class="tui__perm">drwxr-xr-x</span><span class="tui__name">..</span>' + '<span class="tui__perm">drwxr-xr-x</span><span class="tui__name">..</span>' +
'</a></li>' + '</a></li>' +
'<li><a data-name="doom.sh" class="is-selected">' + '<li><a data-name="' + file + '" class="is-selected">' +
'<span class="tui__perm">-rwxr-xr-x</span><span class="tui__name">doom.sh</span>' + '<span class="tui__perm">-rwxr-xr-x</span><span class="tui__name">' + file + '</span>' +
'</a></li>' + '</a></li>' +
'</ul>' + '</ul>' +
'<figure class="tui__preview">' + '<figure class="tui__preview">' +
'<div class="doom-player">' + '<div class="game-player">' +
'<iframe src="https://archive.org/embed/DoomsharewareEpisode" allowfullscreen allow="fullscreen; autoplay; gamepad"></iframe>' + '<iframe src="https://archive.org/embed/' + archiveId + '" allowfullscreen allow="fullscreen; autoplay; gamepad"></iframe>' +
'</div>' + '</div>' +
'<figcaption class="tui__status">doom.sh — running</figcaption>' + '<figcaption class="tui__status">' + file + ' — running</figcaption>' +
'</figure>' + '</figure>' +
'</div>'; '</div>';
@@ -243,9 +255,9 @@
document.dispatchEvent(new CustomEvent("pjax:load")); document.dispatchEvent(new CustomEvent("pjax:load"));
// the home page's always-open menu lives in the header, not #main-content // the home page's always-open menu lives in the header, not #main-content
// — hide it too, otherwise its own "$ ls" sits right above doom's and // — hide it too, otherwise its own "$ ls" sits right above the game's and
// the listing looks doubled. restored on the next real navigation // the listing looks doubled. restored on the next real navigation
// (leaving doom), which also fires pjax:load — registered after the // (leaving the game), which also fires pjax:load — registered after the
// dispatch above so it doesn't just catch its own event. // dispatch above so it doesn't just catch its own event.
if (menu && !menu.hidden) { if (menu && !menu.hidden) {
menu.hidden = true; menu.hidden = true;
@@ -275,7 +287,7 @@
// tab-completion over known commands + navigable routes // tab-completion over known commands + navigable routes
var COMMANDS = ["help", "man", "info", "contact", "date", "time", "tree", "fetch", "neofetch", var COMMANDS = ["help", "man", "info", "contact", "date", "time", "tree", "fetch", "neofetch",
"uname", "history", "clear", "echo", "pwd", "whoami", "ls", "cd", "cat", "open", "uname", "history", "clear", "echo", "pwd", "whoami", "ls", "cd", "cat", "open",
"dark", "light", "theme", "sl", "doom", "coffee"]; "dark", "light", "theme", "sl", "doom", "simcity", "pacman", "tetris", "coffee"];
function complete() { function complete() {
var val = input.textContent; var val = input.textContent;
var parts = val.split(" "); var parts = val.split(" ");
@@ -303,7 +315,8 @@
remember(cmd); remember(cmd);
if (lc === "clear") { say(""); input.textContent = ""; return; } if (lc === "clear") { say(""); input.textContent = ""; return; }
if (lc === "doom") { doom(); say(""); input.textContent = ""; return; } if (lc === "doom" || lc === "simcity" || lc === "tetris") { launchGame(lc); say(""); input.textContent = ""; return; }
if (lc === "pacman" || lc === "pac-man") { launchGame("pacman"); say(""); input.textContent = ""; return; }
if (lc === "sl") { sl(); say(""); input.textContent = ""; return; } if (lc === "sl") { sl(); say(""); input.textContent = ""; return; }
if (lc === "dark" || lc === "light") { setTheme(lc); say(""); input.textContent = ""; return; } if (lc === "dark" || lc === "light") { setTheme(lc); say(""); input.textContent = ""; return; }
if (lc === "theme") { var next = document.documentElement.classList.contains("dark") ? "light" : "dark"; setTheme(next); say("theme: " + next); input.textContent = ""; return; } if (lc === "theme") { var next = document.documentElement.classList.contains("dark") ? "light" : "dark"; setTheme(next); say("theme: " + next); input.textContent = ""; return; }