ORACLE TYPE KINDLY
ACTIONS {["LOOK AT","WALK TO","TALK TO","USE"].map((v,i)=>setSelected(i)} key={v}>{v} )}
SCENE TARGETS / INVENTORY {n.choices.map(c=>choose(c.to)}>{c.label}› )}{n.ending&&choose("dock")}>Play again ↻ }
}
-function Mines(){const[cells,setCells]=useState(()=>Array.from({length:81},(_,i)=>({mine:[7,14,25,42,66,73,78].includes(i),open:false})));const[dead,setDead]=useState(false);return
007 {setDead(false);setCells(cells.map(x=>({...x,open:false})))}}>🙂 999
{cells.map((c,i)=>{if(c.mine)setDead(true);setCells(x=>x.map((v,j)=>j===i?{...v,open:true}:v))}}>{c.open?(c.mine?"💣":((i*7)%4||"")):""} )}
{dead&&
Mine encountered. Your paperwork survives. }
}
-function Snake(){return
◆ ◆ ◆ ◆ ●
SNAKE.EXE
NEW GAME Use arrow keys. Imagine it is moving very carefully.
}
-function Ants({count,generation,clear}:{count:number;generation:number;clear:()=>void}){const ref=useRef
(null);const[pointer,setPointer]=useState({x:50,y:50});return setPointer({x:e.clientX/innerWidth*100,y:e.clientY/innerHeight*100})}>{Array.from({length:count},(_,i)=>{const p=antPosition(i,count);const flee=Math.hypot(p.x-pointer.x,p.y-pointer.y)<12;return
})}
⚠ Ant infestation detected Activity: {count<45?"local cluster":count<70?"spreading across chrome":"system-wide"}Source: final_final_v7_REAL.txt
}
+export type AppMeta = { id: AppId; title: string; icon: string; size?: { width: number; height: number } };
+export const apps: AppMeta[] = [
+ { id: "files", title: "My Computer", icon: "PC", size: { width: 760, height: 520 } },
+ { id: "about", title: "About Maxwell", icon: "MY", size: { width: 560, height: 480 } },
+ { id: "browser", title: "Internet", icon: "WWW", size: { width: 720, height: 560 } },
+ { id: "notes", title: "Notepad", icon: "TXT", size: { width: 640, height: 520 } },
+ { id: "terminal", title: "MS-DOS Prompt", icon: "C:", size: { width: 680, height: 440 } },
+ { id: "mines", title: "Minesweeper", icon: "*", size: { width: 420, height: 500 } },
+ { id: "snake", title: "Snake", icon: "S", size: { width: 460, height: 560 } },
+ { id: "adventure", title: "Elsewhere", icon: "MOON", size: { width: 900, height: 620 } },
+ { id: "office", title: "The Office", icon: "DOOR", size: { width: 820, height: 600 } },
+ { id: "settings", title: "Display Properties", icon: "CFG", size: { width: 520, height: 540 } },
+ { id: "help", title: "Help", icon: "?", size: { width: 560, height: 480 } },
+ { id: "trash", title: "Recycle Bin", icon: "BIN", size: { width: 420, height: 380 } },
+];
+const DESKTOP_ICONS: AppId[] = ["files", "about", "browser", "terminal", "adventure", "office", "trash"];
+
+export type OSApi = {
+ open: (id: AppId, payload?: WindowPayload, title?: string) => void;
+ close: (id: string) => void;
+ release: () => void;
+ exit: () => void;
+ prefs: OSPrefs;
+ setPrefs: (prefs: OSPrefs) => void;
+};
+
+export function PixelIcon({ name, small = false }: { name: string; small?: boolean }) {
+ return ;
+}
+
+const STORAGE = "maxwell-os:v2";
+type Session = { os: OSState; prefs: OSPrefs };
+function loadSession(): Session {
+ try {
+ const raw = localStorage.getItem(STORAGE);
+ if (raw) {
+ const saved = JSON.parse(raw) as Partial;
+ const os = saved.os ? windowReducer(initialOSState, { type: "hydrate", state: saved.os, viewport: { width: innerWidth, height: innerHeight } }) : initialOSState;
+ return { os, prefs: { ...defaultPrefs, ...saved.prefs } };
+ }
+ } catch {}
+ return { os: initialOSState, prefs: defaultPrefs };
+}
+
+export default function MaxwellOS() {
+ const session = useRef(null);
+ const boot = () => (session.current ??= loadSession());
+ const [os, dispatch] = useReducer(windowReducer, undefined, () => boot().os);
+ const [prefs, setPrefs] = useState(() => boot().prefs);
+ const [start, setStart] = useState(false);
+ const [menu, setMenu] = useState<{ x: number; y: number } | null>(null);
+ const [ants, setAnts] = useState({ active: false, count: 0, generation: 0 });
+ const [clock, setClock] = useState("");
+ const top = topWindow(os);
+
+ const open = useCallback((id: AppId, payload?: WindowPayload, title?: string) => {
+ const meta = apps.find((a) => a.id === id)!;
+ dispatch({ type: "open", app: id, title: title ?? meta.title, payload, size: meta.size });
+ setStart(false);
+ setMenu(null);
+ }, []);
+ const api: OSApi = {
+ open,
+ close: (id) => dispatch({ type: "close", id }),
+ release: () => setAnts((s) => ({ active: true, count: Math.max(s.count, 18), generation: s.generation })),
+ exit: () => location.assign("/"),
+ prefs,
+ setPrefs,
+ };
+
+ useEffect(() => { try { localStorage.setItem(STORAGE, JSON.stringify({ os, prefs })); } catch {} }, [os, prefs]);
+ useEffect(() => { const tick = () => setClock(new Date().toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })); tick(); const id = setInterval(tick, 1000); return () => clearInterval(id); }, []);
+ useEffect(() => { if (!ants.active) return; const id = setInterval(() => setAnts((s) => ({ ...s, count: Math.min(80, s.count + 12) })), 3500); return () => clearInterval(id); }, [ants.active]);
+ useEffect(() => {
+ const key = (event: KeyboardEvent) => {
+ const editing = /INPUT|TEXTAREA/.test((event.target as HTMLElement)?.tagName ?? "");
+ if (event.key === "Tab" && (event.altKey || event.ctrlKey)) { event.preventDefault(); dispatch({ type: "cycle", direction: event.shiftKey ? -1 : 1 }); return; }
+ if (event.key !== "Escape") return;
+ if (start || menu) { setStart(false); setMenu(null); return; }
+ if (editing) { (event.target as HTMLElement).blur(); return; }
+ const current = topWindow(os);
+ if (current) dispatch({ type: "close", id: current.id }); else location.assign("/");
+ };
+ addEventListener("keydown", key);
+ return () => removeEventListener("keydown", key);
+ }, [os, start, menu]);
+
+ const scheme = schemeById(prefs.scheme);
+ const wallpaper = wallpaperById(prefs.wallpaper);
+ const rootStyle = { "--os-desktop": scheme.desktop, "--os-face": scheme.face, "--os-title-a": scheme.titleA, "--os-title-b": scheme.titleB, "--os-text": scheme.text, "--os-link": scheme.link, backgroundImage: wallpaper.css } as React.CSSProperties;
+ const openIcon = (id: AppId) => (event: React.MouseEvent) => { if (matchMedia("(hover: none)").matches || event.detail >= 2) open(id); };
+
+ return (
+ { if (menu) setMenu(null); if (start) setStart(false); }} onContextMenu={(e) => { if ((e.target as HTMLElement).closest(`.${styles.window}`)) return; e.preventDefault(); setMenu({ x: e.clientX, y: e.clientY }); }}>
+ MAXWELLPROFESSIONAL
+
+ {DESKTOP_ICONS.map((id) => { const a = apps.find((x) => x.id === id)!; return
e.key === "Enter" && open(id)}>{a.title} ; })}
+
+ {os.windows.map((w) => !w.minimized && (
+ a.id === w.app)?.icon ?? "?"}>
+
+
+ ))}
+ {ants.active && setAnts((s) => ({ active: false, count: 0, generation: s.generation + 1 }))} />}
+ {menu && open("files")}>Open open("terminal")}>Command Prompt
open("settings")}>Properties setMenu(null)}>Refresh }
+
+
{ e.stopPropagation(); setStart(!start); }}>▦ Start
+ {os.windows.map((w) =>
dispatch(top?.id === w.id && !w.minimized ? { type: "minimize", id: w.id } : { type: "focus", id: w.id })}> a.id === w.app)?.icon ?? "?"} small />{w.title} )}
+
{ants.active ? `⚠ ANTS ${ants.count}` : "VOL"} {clock}
+
+ {start && (
+ e.stopPropagation()}>
+
+
+ {apps.filter((a) => !["settings", "help"].includes(a.id)).map((a) =>
open(a.id)}> {a.title}› )}
+
+
open("settings")}> Settings›
+
open("help")}> Help›
+
+
Shut Down...
+
+
+ )}
+
+ );
+}
+
+type Drag = { mode: "move" | "resize"; startX: number; startY: number; x: number; y: number; width: number; height: number };
+
+function WindowFrame({ w, active, dispatch, icon, children }: { w: OSWindow; active: boolean; dispatch: React.Dispatch[1]>; icon: string; children: React.ReactNode }) {
+ const drag = useRef(null);
+ const begin = (mode: Drag["mode"], e: ReactPointerEvent) => {
+ if (w.maximized || (e.target as HTMLElement).closest("button")) return;
+ if (e.button !== 0) return;
+ e.currentTarget.setPointerCapture(e.pointerId);
+ drag.current = { mode, startX: e.clientX, startY: e.clientY, x: w.x, y: w.y, width: w.width, height: w.height };
+ };
+ const track = (e: ReactPointerEvent) => {
+ const d = drag.current;
+ if (!d) return;
+ const dx = e.clientX - d.startX, dy = e.clientY - d.startY;
+ if (d.mode === "move") dispatch({ type: "move", id: w.id, x: Math.min(innerWidth - 80, d.x + dx), y: Math.min(innerHeight - 90, d.y + dy) });
+ else dispatch({ type: "resize", id: w.id, width: d.width + dx, height: d.height + dy });
+ };
+ const end = () => { drag.current = null; };
+ const frame = w.maximized ? { zIndex: w.z } : { left: w.x, top: w.y, width: w.width, height: w.height, zIndex: w.z };
+ return (
+ !active && dispatch({ type: "focus", id: w.id })} aria-label={w.title}>
+ begin("move", e)} onPointerMove={track} onPointerUp={end} onPointerCancel={end} onDoubleClick={() => dispatch({ type: "maximize", id: w.id })}>
+ {w.title}
+ dispatch({ type: "minimize", id: w.id })}>_ dispatch({ type: "maximize", id: w.id })}>{w.maximized ? "❐" : "□"} dispatch({ type: "close", id: w.id })}>×
+
+ {children}
+ {!w.maximized && begin("resize", e)} onPointerMove={track} onPointerUp={end} onPointerCancel={end} />}
+
+ );
+}
+
+function App({ w, api }: { w: OSWindow; api: OSApi }) {
+ const path = w.payload?.path;
+ switch (w.app) {
+ case "files": return ;
+ case "notes": return ;
+ case "terminal": return ;
+ case "settings": return ;
+ case "browser": return ;
+ case "about": return ;
+ case "help": return ;
+ case "trash": return ;
+ case "mines": return ;
+ case "snake": return ;
+ case "adventure": return ;
+ case "office": return ;
+ }
+}
diff --git a/src/components/maxwell-os/MaxwellOSLoader.tsx b/src/components/maxwell-os/MaxwellOSLoader.tsx
new file mode 100644
index 00000000..8a1499e3
--- /dev/null
+++ b/src/components/maxwell-os/MaxwellOSLoader.tsx
@@ -0,0 +1,8 @@
+"use client";
+import dynamic from "next/dynamic";
+import styles from "./MaxwellOS.module.css";
+
+// Client-only so the saved desktop session is available on first render.
+const MaxwellOS = dynamic(() => import("./MaxwellOS"), { ssr: false, loading: () => Starting Maxwell OS…
});
+
+export default function MaxwellOSLoader() { return ; }
diff --git a/src/components/maxwell-os/apps.tsx b/src/components/maxwell-os/apps.tsx
new file mode 100644
index 00000000..6d70a13d
--- /dev/null
+++ b/src/components/maxwell-os/apps.tsx
@@ -0,0 +1,193 @@
+"use client";
+// Productivity apps: My Computer, Notepad, MS-DOS Prompt, Display Properties,
+// Internet, About, Help, Recycle Bin.
+import { useEffect, useRef, useState, type FormEvent, type KeyboardEvent } from "react";
+import Link from "next/link";
+import { FILESYSTEM, SCHEMES, WALLPAPERS, displayPath, runCommand, traverse, type FileNode, type OSPrefs } from "@/lib/maxwellOS";
+import { resumeData } from "@/lib/resumeData";
+import { flagshipProjects } from "@/lib/projects";
+import { essays } from "@/lib/essays";
+import styles from "./MaxwellOS.module.css";
+import type { OSApi } from "./MaxwellOS";
+
+const isTouch = () => matchMedia("(hover: none)").matches;
+
+function openNode(api: OSApi, path: string[], node: FileNode, setCwd?: (p: string[]) => void) {
+ if (node.kind === "folder") return setCwd ? setCwd(path) : api.open("files", { path });
+ if (node.app) return api.open(node.app);
+ api.open("notes", { path }, `${node.name} - Notepad`);
+}
+
+function FolderTree({ node, path, cwd, onPick, depth = 0 }: { node: FileNode; path: string[]; cwd: string[]; onPick: (p: string[]) => void; depth?: number }) {
+ const folders = (node.children ?? []).filter((c) => c.kind === "folder");
+ const here = cwd.join("/") === path.join("/");
+ return (
+ <>
+ onPick(path)} aria-current={here ? "location" : undefined}> {node.name}
+ {depth < 2 && folders.map((f) => )}
+ >
+ );
+}
+
+export function Files({ api, path }: { api: OSApi; path?: string[] }) {
+ const [cwd, setCwd] = useState(() => (path && traverse(path)?.kind === "folder" ? path : []));
+ const [selected, setSelected] = useState(null);
+ const node = traverse(cwd) ?? FILESYSTEM;
+ const items = node.children ?? [];
+ const up = () => { setCwd(cwd.slice(0, -1)); setSelected(null); };
+ const go = (p: string[]) => { setCwd(p); setSelected(null); };
+ const keys = (e: KeyboardEvent) => { if (e.key === "Backspace" && cwd.length) { e.preventDefault(); up(); } };
+ return (
+
+
⬆ Up Address
+
+
+
+ {items.length === 0 &&
This folder is empty.
}
+ {items.map((item) => {
+ const p = [...cwd, item.name];
+ const act = () => openNode(api, p, item, go);
+ return
{ setSelected(item.name); if (isTouch() || e.detail >= 2) act(); }} onKeyDown={(e) => e.key === "Enter" && act()}>{item.name} ;
+ })}
+
+
+
{items.length} object{items.length === 1 ? "" : "s"} {selected ? (traverse([...cwd, selected])?.content ?? "").split("\n")[0].slice(0, 60) : <>Double-click to open. Backspace goes up. Tap to open. >}
+
+ );
+}
+
+export function Notepad({ api, path }: { api: OSApi; path?: string[] }) {
+ const node = path ? traverse(path) : null;
+ const text = node?.content ?? "NOTES.TXT\n\nMake useful things.\nLeave one strange door unlocked.\n\nOpen any .txt from My Computer to read it here.";
+ const [dirty, setDirty] = useState(false);
+ const href = node?.href;
+ return (
+
+
+
api.open("files", { path: path?.slice(0, -1) ?? [] })}>File
+ {href && (href.startsWith("/") ?
Open in portfolio ↗ :
Open link ↗ )}
+
{dirty ? "Edited (not saved: pretend disk)" : node ? "Read-only copy" : "Untitled"}
+
+
+ );
+}
+
+export function Terminal({ api }: { api: OSApi }) {
+ const [lines, setLines] = useState(["Maxwell OS [Version 2.0]", "(C) Maxwell Systems. Type help to begin.", ""]);
+ const [cwd, setCwd] = useState([]);
+ const [q, setQ] = useState("");
+ const [history, setHistory] = useState([]);
+ const [cursor, setCursor] = useState(-1);
+ const scroller = useRef(null);
+ const input = useRef(null);
+ useEffect(() => { scroller.current?.scrollTo({ top: scroller.current.scrollHeight }); }, [lines]);
+ const prompt = `${displayPath(cwd)}>`;
+ function go(e: FormEvent) {
+ e.preventDefault();
+ const r = runCommand(q, { cwd, now: new Date().toString() });
+ setLines((x) => (r.clear ? [] : [...x, `${prompt}${q}`, ...(r.output ? r.output.split("\n") : [])]));
+ if (q.trim()) setHistory((h) => [q, ...h].slice(0, 50));
+ setCursor(-1);
+ if (r.cwd) setCwd(r.cwd);
+ if (r.exit) api.exit();
+ if (r.vacuum) api.release();
+ if (r.open) api.open(r.open, r.openFile ? { path: r.openFile } : undefined, r.open === "notes" && r.openFile ? `${r.openFile.at(-1)} - Notepad` : undefined);
+ setQ("");
+ }
+ const keys = (e: KeyboardEvent) => {
+ if (e.key === "ArrowUp" || e.key === "ArrowDown") {
+ e.preventDefault();
+ const next = Math.max(-1, Math.min(history.length - 1, cursor + (e.key === "ArrowUp" ? 1 : -1)));
+ setCursor(next);
+ setQ(next === -1 ? "" : history[next]);
+ }
+ };
+ return (
+ input.current?.focus()}>
+ {lines.map((l, i) =>
{l || " "}
)}
+
+
+ );
+}
+
+export function Settings({ api, windowId }: { api: OSApi; windowId: string }) {
+ const original = useRef(api.prefs);
+ const [tab, setTab] = useState<"background" | "appearance">("background");
+ const update = (patch: Partial) => api.setPrefs({ ...api.prefs, ...patch });
+ const done = () => api.close(windowId);
+ const cancel = () => { api.setPrefs(original.current); done(); };
+ return (
+
+
+ setTab("background")}>Background
+ setTab("appearance")}>Appearance
+
+
+
OK Cancel (original.current = api.prefs)}>Apply
+
+ );
+}
+
+export function Browser({ api }: { api: OSApi }) {
+ return (
+
+
◀ ▶ Address Go
+
+ Welcome to the Internet
+ This copy is cached locally. The modem is resting. These links leave the computer:
+
+ dev.maxwellyoung.info — the portfolio this computer lives inside
+ Resume · Craft · Contact
+ {flagshipProjects.map((p) => {p.caseStudySlug ? {p.name} case study : {p.name} } — {p.description} )}
+ {essays.map((e) => {e.title} )}
+ Run personnel verification quiz
+
+ api.open("about")}>About the author
+
+
+ );
+}
+
+export function About() {
+ const r = resumeData;
+ return (
+
+ );
+}
+
+export function Help() {
+ return (
+
+
Maxwell OS Help
+
Double-click desktop icons (tap on touch screens). Drag title bars to move windows, drag the bottom-right corner to resize, double-click a title bar to maximize.
+
+ Alt+Tab / Ctrl+Tab Switch windows
+ Escape Close the front window, or return to the portfolio when none are open
+ Backspace Up one folder in My Computer
+ ↑ / ↓ in MS-DOS Prompt Command history
+ Right-click Desktop menu; flags in Minesweeper
+
+
Your windows and Display Properties are remembered on this device only. If ants appear, use the vacuum.
+
+ );
+}
+
+export function Trash({ api }: { api: OSApi }) {
+ return 🗑️ 1 object
DO NOT CLICK final_final_v7_REAL.txt ;
+}
diff --git a/src/components/maxwell-os/games.tsx b/src/components/maxwell-os/games.tsx
new file mode 100644
index 00000000..64eecd5f
--- /dev/null
+++ b/src/components/maxwell-os/games.tsx
@@ -0,0 +1,90 @@
+"use client";
+// Games and hazards: Minesweeper (shares the Lab's board engine), Snake,
+// the two stories, and the ant infestation.
+import { useEffect, useRef, useState, type KeyboardEvent } from "react";
+import { boardState, makeBoard, reveal, toggleFlag, type Board } from "@/lib/lab";
+import { ADVENTURE_GRAPH, OFFICE_GRAPH, SNAKE_SIZE, antPosition, newSnake, snakeTick, turnSnake, type SnakeState } from "@/lib/maxwellOS";
+import styles from "./MaxwellOS.module.css";
+
+const MINES = 10;
+export function Mines() {
+ const [board, setBoard] = useState(() => makeBoard(9, 9, MINES));
+ const [cursor, setCursor] = useState([0, 0]);
+ const [seconds, setSeconds] = useState(0);
+ const state = boardState(board);
+ const started = board.some((row) => row.some((c) => c.open || c.flagged));
+ const flags = board.flat().filter((c) => c.flagged).length;
+ useEffect(() => { if (!started || state !== "playing") return; const id = setInterval(() => setSeconds((s) => Math.min(999, s + 1)), 1000); return () => clearInterval(id); }, [started, state]);
+ const act = (x: number, y: number, flag = false) => { if (state !== "playing") return; setBoard((b) => (flag ? toggleFlag(b, x, y) : reveal(b, x, y))); };
+ const reset = () => { setBoard(makeBoard(9, 9, MINES)); setSeconds(0); setCursor([0, 0]); };
+ const keys = (e: KeyboardEvent) => {
+ let [x, y] = cursor;
+ if (e.key === "ArrowRight") x++; if (e.key === "ArrowLeft") x--; if (e.key === "ArrowDown") y++; if (e.key === "ArrowUp") y--;
+ x = Math.max(0, Math.min(8, x)); y = Math.max(0, Math.min(8, y));
+ if (e.key.startsWith("Arrow")) { e.preventDefault(); setCursor([x, y]); (e.currentTarget.querySelector(`[data-cell="${x}-${y}"]`) as HTMLElement | null)?.focus(); }
+ if (e.key === " " || e.key === "Enter") { e.preventDefault(); act(x, y, e.shiftKey); }
+ if (e.key.toLowerCase() === "f") { e.preventDefault(); act(x, y, true); }
+ };
+ const pad = (n: number) => String(Math.max(0, n)).padStart(3, "0");
+ return (
+
+
{pad(MINES - flags)} {state === "playing" ? "🙂" : state === "won" ? "😎" : "😵"} {pad(seconds)}
+
+ {board.map((row, y) => row.map((c, x) => setCursor([x, y])} onClick={() => act(x, y)} onContextMenu={(e) => { e.preventDefault(); act(x, y, true); }} className={`${c.open ? styles.open : ""} ${c.open && c.mine ? styles.boom : ""}`} data-n={c.open && !c.mine && c.adjacent ? c.adjacent : undefined}>{c.open ? (c.mine ? "💣" : c.adjacent || "") : c.flagged ? "🚩" : ""} ))}
+
+
{state === "won" ? "Cleared. Your paperwork is immaculate." : state === "lost" ? "Mine encountered. Your paperwork survives." : "Left click reveals. Right click, Shift+Enter or F flags."}
+
+ );
+}
+
+const HIGH = "maxwell-os:snake-high";
+export function Snake() {
+ const [s, setS] = useState(() => newSnake());
+ const [running, setRunning] = useState(false);
+ const [started, setStarted] = useState(false);
+ const [high, setHigh] = useState(() => { try { return Number(localStorage.getItem(HIGH) ?? 0); } catch { return 0; } });
+ // Best score is derived while dead so the effect only persists; setHigh happens on restart.
+ const best = s.dead ? Math.max(high, s.score) : high;
+ const board = useRef(null);
+ useEffect(() => { if (!running || s.dead) return; const id = setInterval(() => setS((cur) => snakeTick(cur)), Math.max(70, 140 - s.score)); return () => clearInterval(id); }, [running, s.dead, s.score]);
+ useEffect(() => { if (s.dead && best > high) { try { localStorage.setItem(HIGH, String(best)); } catch {} } }, [s.dead, best, high]);
+ const dirs: Record = { ArrowUp: { x: 0, y: -1 }, ArrowDown: { x: 0, y: 1 }, ArrowLeft: { x: -1, y: 0 }, ArrowRight: { x: 1, y: 0 }, w: { x: 0, y: -1 }, s: { x: 0, y: 1 }, a: { x: -1, y: 0 }, d: { x: 1, y: 0 } };
+ const keys = (e: KeyboardEvent) => {
+ const d = dirs[e.key] ?? dirs[e.key.toLowerCase()];
+ if (d) { e.preventDefault(); setS((cur) => turnSnake(cur, d)); if (!running && !s.dead) { setRunning(true); setStarted(true); } }
+ if (e.key === " ") { e.preventDefault(); if (s.dead) restart(); else setRunning((r) => !r); }
+ };
+ const restart = () => { setHigh(best); setS(newSnake()); setRunning(true); setStarted(true); board.current?.focus(); };
+ const cells = Array.from({ length: SNAKE_SIZE * SNAKE_SIZE }, (_, i) => ({ x: i % SNAKE_SIZE, y: Math.floor(i / SNAKE_SIZE) }));
+ const head = s.body[0];
+ return (
+
+
{String(s.score).padStart(4, "0")} SNAKE.EXE {String(best).padStart(4, "0")}
+
setRunning(false)} onClick={() => { board.current?.focus(); if (!s.dead) { setRunning(true); setStarted(true); } }}>
+ {cells.map((c) => { const isHead = head.x === c.x && head.y === c.y; const isBody = !isHead && s.body.some((p) => p.x === c.x && p.y === c.y); const isFood = s.food.x === c.x && s.food.y === c.y; return
; })}
+ {(s.dead || !running) &&
{s.dead ? <>GAME OVER {s.score} points{s.score >= high && s.score > 0 ? " · new high score" : ""} New game > : <>{started ? "PAUSED" : "SNAKE"} Arrow keys or WASD. Space pauses. The walls wrap. { setRunning(true); setStarted(true); board.current?.focus(); }}>{started ? "Resume" : "Start"} >}
}
+
+
{(["ArrowUp", "ArrowLeft", "ArrowDown", "ArrowRight"] as const).map((k) => { setS((cur) => turnSnake(cur, dirs[k])); if (!s.dead) setRunning(true); }}>{k === "ArrowUp" ? "▲" : k === "ArrowDown" ? "▼" : k === "ArrowLeft" ? "◀" : "▶"} )}
+
+ );
+}
+
+export function Story({ kind }: { kind: "adventure" | "office" }) {
+ const graph = kind === "adventure" ? ADVENTURE_GRAPH : OFFICE_GRAPH;
+ const [node, setNode] = useState(kind === "adventure" ? "dock" : "lobby");
+ const [selected, setSelected] = useState(0);
+ const n = graph[node];
+ const choose = (to: string) => { setNode(to); setSelected(0); };
+ if (kind === "office") return ROOM {Object.keys(graph).indexOf(node) + 301}
{n.ending || "This is a story about a person named You."} {n.text}
{n.choices.map((c, i) => choose(c.to)}>{i + 1} {c.label}CONTINUE › )}{n.ending && choose("lobby")}>↻ Clock in againRESTART }
NARRATOR STATUS: OBSERVING ;
+ return
☾
ORACLE TYPE KINDLY
{n.text}
ACTIONS {["LOOK AT", "WALK TO", "TALK TO", "USE"].map((v, i) => setSelected(i)} key={v}>{v} )}
SCENE TARGETS / INVENTORY {n.choices.map((c) => choose(c.to)}>{c.label}› )}{n.ending && choose("dock")}>Play again ↻ }
;
+}
+
+export function Ants({ count, generation, clear }: { count: number; generation: number; clear: () => void }) {
+ const [pointer, setPointer] = useState({ x: 50, y: 50 });
+ return (
+ setPointer({ x: (e.clientX / innerWidth) * 100, y: (e.clientY / innerHeight) * 100 })}>
+ {Array.from({ length: count }, (_, i) => { const p = antPosition(i, count); const flee = Math.hypot(p.x - pointer.x, p.y - pointer.y) < 12; return
; })}
+
⚠ Ant infestation detected Activity: {count < 45 ? "local cluster" : count < 70 ? "spreading across chrome" : "system-wide"}Source: final_final_v7_REAL.txt
+
+ );
+}
diff --git a/src/lib/maxwellOS.test.ts b/src/lib/maxwellOS.test.ts
index c61614b2..44f25d69 100644
--- a/src/lib/maxwellOS.test.ts
+++ b/src/lib/maxwellOS.test.ts
@@ -1,14 +1,33 @@
import test from "node:test";
import assert from "node:assert/strict";
-import { initialOSState,windowReducer,parseCommand,traverse,followStory,OFFICE_GRAPH,ADVENTURE_GRAPH,antReducer,antEscalation,antPosition,snakeStep,safeRestore } from "./maxwellOS";
-
-test("window manager opens, focuses, moves, minimizes, maximizes and closes",()=>{let s=windowReducer(initialOSState,{type:"open",app:"files",title:"Explorer"});assert.equal(s.windows.length,1);s=windowReducer(s,{type:"move",id:s.windows[0].id,x:-4,y:22});assert.equal(s.windows[0].x,0);s=windowReducer(s,{type:"minimize",id:s.windows[0].id});assert.equal(s.windows[0].minimized,true);s=windowReducer(s,{type:"maximize",id:s.windows[0].id});assert.equal(s.windows[0].maximized,true);s=windowReducer(s,{type:"close",id:s.windows[0].id});assert.equal(s.windows.length,0)});
-test("opening an existing app focuses rather than duplicates",()=>{let s=windowReducer(initialOSState,{type:"open",app:"notes",title:"Notes"});s=windowReducer(s,{type:"open",app:"notes",title:"Notes"});assert.equal(s.windows.length,1);assert.equal(s.windows[0].z,3)});
-test("terminal parser allowlists apps and handles utilities",()=>{assert.equal(parseCommand("date","NOW").output,"NOW");assert.equal(parseCommand("clear").clear,true);assert.equal(parseCommand("open mines").open,"mines");assert.match(parseCommand("rm -rf" ).output,/not found/);assert.equal(parseCommand("vacuum").vacuum,true)});
-test("filesystem traverses without escaping tree",()=>{assert.equal(traverse(["Documents"])?.kind,"folder");assert.equal(traverse(["nope"]),null)});
-test("story graphs follow valid branches and reject invalid choices",()=>{assert.equal(followStory(OFFICE_GRAPH,"lobby",0),"meeting");assert.equal(followStory(ADVENTURE_GRAPH,"dock",99),null)});
-test("ant reducer caps swarm and cleanup invalidates generation",()=>{let s={active:false,count:0,generation:0};s=antReducer(s,{type:"release",amount:999});assert.equal(s.count,80);s=antReducer(s,{type:"clear"});assert.deepEqual(s,{active:false,count:0,generation:1})});
-test("ant infestation escalates in bounded waves",()=>{assert.equal(antEscalation(28,1),40);assert.equal(antEscalation(76,1),80);assert.equal(antEscalation(80,9),80)});
-test("early ants cluster around the deleted file and recycle bin",()=>{const first=Array.from({length:24},(_,i)=>antPosition(i,80));assert.ok(first.filter(p=>p.origin!=="roaming").length>=18);assert.ok(first.some(p=>p.origin==="file"));assert.ok(first.some(p=>p.origin==="bin"));assert.ok(antPosition(70,80).origin==="roaming")});
-test("snake wraps and detects itself",()=>{assert.deepEqual(snakeStep([{x:0,y:0}],{x:-1,y:0},12).body[0],{x:11,y:0});assert.equal(snakeStep([{x:1,y:0},{x:0,y:0}],{x:-1,y:0}).hit,true)});
-test("safe restore clears lifecycle residue",()=>{assert.deepEqual(safeRestore({title:"x",scrollX:2,scrollY:3}),{title:"x",scrollX:2,scrollY:3,active:false,ants:0,timers:0})});
+import { initialOSState, windowReducer, runCommand, parseCommand, traverse, resolvePath, formatTree, FILESYSTEM, followStory, OFFICE_GRAPH, ADVENTURE_GRAPH, antReducer, antEscalation, antPosition, snakeStep, newSnake, snakeTick, turnSnake, placeFood, safeRestore, topWindow, SCHEMES, WALLPAPERS, schemeById } from "./maxwellOS";
+import { rankedProjects } from "./projects";
+import { essays } from "./essays";
+
+test("window manager opens, focuses, moves, minimizes, maximizes and closes", () => { let s = windowReducer(initialOSState, { type: "open", app: "files", title: "Explorer" }); assert.equal(s.windows.length, 1); s = windowReducer(s, { type: "move", id: s.windows[0].id, x: -4, y: 22 }); assert.equal(s.windows[0].x, 0); s = windowReducer(s, { type: "resize", id: s.windows[0].id, width: 10, height: 10 }); assert.equal(s.windows[0].width, 320); s = windowReducer(s, { type: "minimize", id: s.windows[0].id }); assert.equal(s.windows[0].minimized, true); s = windowReducer(s, { type: "maximize", id: s.windows[0].id }); assert.equal(s.windows[0].maximized, true); s = windowReducer(s, { type: "close", id: s.windows[0].id }); assert.equal(s.windows.length, 0); });
+test("opening an existing app focuses rather than duplicates", () => { let s = windowReducer(initialOSState, { type: "open", app: "notes", title: "Notes" }); s = windowReducer(s, { type: "open", app: "notes", title: "Notes" }); assert.equal(s.windows.length, 1); assert.equal(s.windows[0].z, 3); });
+test("files opened in Notepad get their own window per path", () => { let s = windowReducer(initialOSState, { type: "open", app: "notes", title: "A", payload: { path: ["Documents", "RESUME.txt"] } }); s = windowReducer(s, { type: "open", app: "notes", title: "B", payload: { path: ["Projects", "README.txt"] } }); s = windowReducer(s, { type: "open", app: "notes", title: "A", payload: { path: ["Documents", "RESUME.txt"] } }); assert.equal(s.windows.length, 2); assert.equal(topWindow(s)?.title, "A"); });
+test("cycle brings the next window forward, shift-cycle the last", () => { let s = initialOSState; for (const app of ["files", "notes", "terminal"] as const) s = windowReducer(s, { type: "open", app, title: app }); assert.equal(topWindow(s)?.app, "terminal"); s = windowReducer(s, { type: "cycle" }); assert.equal(topWindow(s)?.app, "notes"); s = windowReducer(s, { type: "cycle", direction: -1 }); assert.equal(topWindow(s)?.app, "files"); });
+test("hydrate clamps saved windows into the viewport and keeps z order sane", () => { const saved = { windows: [{ id: "files", app: "files" as const, title: "x", x: 5000, y: 5000, width: 100, height: 100, z: 9, minimized: false, maximized: false }], nextZ: 3 }; const s = windowReducer(initialOSState, { type: "hydrate", state: saved, viewport: { width: 1000, height: 700 } }); assert.equal(s.windows[0].x, 920); assert.equal(s.windows[0].y, 580); assert.equal(s.windows[0].width, 320); assert.equal(s.nextZ, 10); });
+
+test("filesystem is built from the real portfolio", () => { const projects = traverse(["Projects"]); assert.ok(projects?.children); for (const p of rankedProjects) { const folder = projects!.children!.find((c) => c.name === p.name.replace(/['`’]/g, "").replace(/[^a-z0-9-]/gi, "-").toLowerCase()); assert.ok(folder?.children?.some((f) => f.name === "README.txt"), `missing folder for ${p.name}`); } assert.ok(traverse(["Documents", "RESUME.txt"])?.content?.includes("EXPERIENCE")); assert.equal(traverse(["Documents", "Essays"])?.children?.length, essays.length); assert.equal(traverse(["Games", "mines.exe"])?.app, "mines"); assert.ok((traverse(["Now"])?.children?.length ?? 0) >= 4); });
+test("filesystem traverses case-insensitively without escaping tree", () => { assert.equal(traverse(["documents"])?.kind, "folder"); assert.equal(traverse(["nope"]), null); assert.equal(traverse(["Documents", "RESUME.txt", "deeper"]), null); });
+test("resolvePath handles relative, absolute and parent segments", () => { assert.deepEqual(resolvePath(["Documents"], ".."), []); assert.deepEqual(resolvePath(["Documents"], "..\\Games"), ["Games"]); assert.deepEqual(resolvePath(["Documents"], "/Projects"), ["Projects"]); assert.deepEqual(resolvePath(["Documents", "Essays"], "../../.."), []); assert.deepEqual(resolvePath([], "documents/essays"), ["Documents", "Essays"]); });
+test("tree renders nested folders", () => { const out = formatTree(FILESYSTEM); assert.match(out, /Projects\//); assert.match(out, /RESUME\.txt/); });
+
+test("terminal navigates and reads the file system", () => { assert.deepEqual(runCommand("cd Documents", { cwd: [] }).cwd, ["Documents"]); assert.match(runCommand("ls", { cwd: ["Documents"] }).output, /RESUME\.txt/); assert.match(runCommand("cat RESUME.txt", { cwd: ["Documents"] }).output, /EXPERIENCE/); assert.match(runCommand("cd nowhere", { cwd: [] }).output, /Not a folder/); assert.match(runCommand("pwd", { cwd: ["Games"] }).output, /C:\\MAXWELL\\GAMES/); });
+test("terminal opens apps and files", () => { assert.equal(runCommand("open mines", { cwd: [] }).open, "mines"); const r = runCommand("open Games/snake.exe", { cwd: [] }); assert.equal(r.open, "snake"); const f = runCommand("open RESUME.txt", { cwd: ["Documents"] }); assert.equal(f.open, "notes"); assert.deepEqual(f.openFile, ["Documents", "RESUME.txt"]); const d = runCommand("open Projects", { cwd: [] }); assert.equal(d.open, "files"); });
+test("terminal parser allowlists apps and handles utilities", () => { assert.equal(parseCommand("date", "NOW").output, "NOW"); assert.equal(parseCommand("clear").clear, true); assert.match(parseCommand("rm -rf").output, /not found/); assert.equal(parseCommand("vacuum").vacuum, true); assert.equal(parseCommand("echo hi there").output, "hi there"); assert.match(parseCommand("knock knock knock").output, /door/); });
+
+test("story graphs follow valid branches and reject invalid choices", () => { assert.equal(followStory(OFFICE_GRAPH, "lobby", 0), "meeting"); assert.equal(followStory(ADVENTURE_GRAPH, "dock", 99), null); });
+test("ant reducer caps swarm and cleanup invalidates generation", () => { let s = { active: false, count: 0, generation: 0 }; s = antReducer(s, { type: "release", amount: 999 }); assert.equal(s.count, 80); s = antReducer(s, { type: "clear" }); assert.deepEqual(s, { active: false, count: 0, generation: 1 }); });
+test("ant infestation escalates in bounded waves", () => { assert.equal(antEscalation(28, 1), 40); assert.equal(antEscalation(76, 1), 80); assert.equal(antEscalation(80, 9), 80); });
+test("early ants cluster around the deleted file and recycle bin", () => { const first = Array.from({ length: 24 }, (_, i) => antPosition(i, 80)); assert.ok(first.filter((p) => p.origin !== "roaming").length >= 18); assert.ok(first.some((p) => p.origin === "file")); assert.ok(first.some((p) => p.origin === "bin")); assert.ok(antPosition(70, 80).origin === "roaming"); });
+
+test("snake wraps and detects itself", () => { assert.deepEqual(snakeStep([{ x: 0, y: 0 }], { x: -1, y: 0 }, 12).body[0], { x: 11, y: 0 }); assert.equal(snakeStep([{ x: 1, y: 0 }, { x: 0, y: 0 }], { x: -1, y: 0 }).hit, true); });
+test("snake eats, grows, scores, and never reverses", () => { const fixed = () => 0; let s = newSnake(fixed); s = { ...s, food: { x: 9, y: 8 } }; s = snakeTick(s, fixed); assert.equal(s.score, 10); assert.equal(s.body.length, 4); assert.ok(!s.body.some((p) => p.x === s.food.x && p.y === s.food.y)); assert.deepEqual(turnSnake(s, { x: -1, y: 0 }).dir, { x: 1, y: 0 }); assert.deepEqual(turnSnake(s, { x: 0, y: 1 }).dir, { x: 0, y: 1 }); const food = placeFood([{ x: 0, y: 0 }], 2, fixed); assert.notDeepEqual(food, { x: 0, y: 0 }); });
+test("snake dies on itself and stays dead", () => { let s = newSnake(() => 0.99); s = { ...s, body: [{ x: 5, y: 5 }, { x: 6, y: 5 }, { x: 6, y: 6 }, { x: 5, y: 6 }, { x: 4, y: 6 }], dir: { x: 0, y: 1 } }; s = snakeTick(s); assert.equal(s.dead, true); assert.equal(snakeTick(s).dead, true); });
+
+test("display schemes and wallpapers resolve with fallbacks", () => { assert.ok(SCHEMES.length >= 4); assert.ok(WALLPAPERS.some((w) => w.id === "none")); assert.equal(schemeById("nope").id, "standard"); });
+test("safe restore clears lifecycle residue", () => { assert.deepEqual(safeRestore({ title: "x", scrollX: 2, scrollY: 3 }), { title: "x", scrollX: 2, scrollY: 3, active: false, ants: 0, timers: 0 }); });
+test("re-opening My Computer with a new path retargets the existing window", () => { let s = windowReducer(initialOSState, { type: "open", app: "files", title: "My Computer", payload: { path: ["Games"] } }); s = windowReducer(s, { type: "open", app: "files", title: "My Computer", payload: { path: ["Now"] } }); assert.equal(s.windows.length, 1); assert.deepEqual(s.windows[0].payload?.path, ["Now"]); assert.equal(runCommand("exit", { cwd: [] }).exit, true); });
diff --git a/src/lib/maxwellOS.ts b/src/lib/maxwellOS.ts
index a0651e3b..b921f3c9 100644
--- a/src/lib/maxwellOS.ts
+++ b/src/lib/maxwellOS.ts
@@ -1,21 +1,232 @@
-export type AppId="files"|"about"|"browser"|"terminal"|"notes"|"settings"|"help"|"trash"|"mines"|"snake"|"adventure"|"office";
-export type OSWindow={id:string;app:AppId;title:string;x:number;y:number;width:number;height:number;z:number;minimized:boolean;maximized:boolean};
-export type OSState={windows:OSWindow[];nextZ:number};
-export type OSAction={type:"open";app:AppId;title:string}|{type:"focus";id:string}|{type:"move";id:string;x:number;y:number}|{type:"resize";id:string;width:number;height:number}|{type:"minimize"|"maximize"|"close";id:string};
-export const initialOSState:OSState={windows:[],nextZ:2};
-export function windowReducer(state:OSState,action:OSAction):OSState{if(action.type==="open"){const old=state.windows.find(w=>w.app===action.app);if(old)return windowReducer(state,{type:"focus",id:old.id});const n=state.windows.length;return{windows:[...state.windows,{id:`${action.app}-${n}`,app:action.app,title:action.title,x:104+n*28,y:62+n*22,width:Math.min(900,innerSafeWidth()),height:Math.min(650,innerSafeHeight()),z:state.nextZ,minimized:false,maximized:false}],nextZ:state.nextZ+1}}if(action.type==="close")return{...state,windows:state.windows.filter(w=>w.id!==action.id)};return{windows:state.windows.map(w=>w.id!==action.id?w:action.type==="focus"?{...w,z:state.nextZ,minimized:false}:action.type==="move"?{...w,x:Math.max(0,action.x),y:Math.max(0,action.y)}:action.type==="resize"?{...w,width:Math.max(360,action.width),height:Math.max(260,action.height)}:action.type==="minimize"?{...w,minimized:true}:action.type==="maximize"?{...w,maximized:!w.maximized,minimized:false}:w),nextZ:action.type==="focus"?state.nextZ+1:state.nextZ}}function innerSafeWidth(){return typeof innerWidth==="number"?innerWidth-150:820}function innerSafeHeight(){return typeof innerHeight==="number"?innerHeight-145:600}
-export type FileNode={name:string;kind:"folder"|"file";content?:string;children?:FileNode[]};
-export const FILESYSTEM:FileNode={name:"Desktop",kind:"folder",children:[{name:"Projects",kind:"folder",children:[{name:"README.txt",kind:"file",content:"Small products, careful systems, and proof over promises."},{name:"the-door",kind:"folder",children:[{name:"knock-three-times.txt",kind:"file",content:"Try: knock knock knock in Terminal."}]}]},{name:"Documents",kind:"folder",children:[{name:"meeting-that-could-be-a-note.txt",kind:"file",content:"Agenda: cancel the meeting."},{name:"Adventure-pass.txt",kind:"file",content:"The lighthouse keeper trusts people who carry a paper moon."}]},{name:"Games",kind:"folder",children:[{name:"mines.launch",kind:"file",content:"Open Mines from the Start menu."}]},{name:"Recycle Bin",kind:"folder",children:[{name:"final_final_v7_REAL.txt",kind:"file",content:"This was never final."}]}]};
-export function traverse(path:string[],root=FILESYSTEM):FileNode|null{let node:FileNode|undefined=root;for(const part of path){node=node.children?.find(x=>x.name===part);if(!node)return null}return node}
-const commands=["help","about","ls","date","clear","whoami","fortune","open","knock","vacuum"] as const;
-export type TerminalResult={output:string;clear?:boolean;open?:AppId;vacuum?:boolean};
-export function parseCommand(raw:string,now="LOCAL TIME"):TerminalResult{const [cmd,...args]=raw.trim().toLowerCase().split(/\s+/);if(!cmd)return{output:""};if(!commands.includes(cmd as never))return{output:`Command not found: ${cmd}. This is a tiny pretend computer, not your shell.`};if(cmd==="help")return{output:"help, about, ls, date, clear, whoami, fortune, open , knock, vacuum"};if(cmd==="about")return{output:"Maxwell OS 1.0. Local, fictional, and mildly overqualified."};if(cmd==="ls")return{output:"Projects Documents Games Recycle Bin"};if(cmd==="date")return{output:now};if(cmd==="clear")return{output:"",clear:true};if(cmd==="whoami")return{output:"guest@portfolio. Excellent disguise."};if(cmd==="fortune")return{output:"A clean interface is often just a well-hidden argument."};if(cmd==="knock")return{output:args.join(" ")==="knock knock"?"A tiny door opens in File Explorer.":"The terminal knocks back."};if(cmd==="vacuum")return{output:"Ant containment protocol requested.",vacuum:true};if(cmd==="open"){const app=args[0] as AppId;return (["files","about","browser","notes","help","mines","snake","adventure","office"] as string[]).includes(app)?{output:`Opening ${app}.`,open:app}:{output:"That app is not on the allowlist."}}return{output:""}}
-export type StoryNode={text:string;choices:{label:string;to:string}[];ending?:string};
-export const OFFICE_GRAPH:Record={lobby:{text:"At 9:03, the office assigns you one task: choose a door. Facilities insists the stairs are legally a door. Nobody challenges Facilities.",choices:[{label:"Enter the blue door",to:"meeting"},{label:"Accept the stairs as a door",to:"roof"}]},meeting:{text:"The meeting room contains one chair and a slide titled Alignment.",choices:[{label:"Sit in the only chair",to:"aligned"},{label:"Turn off the projector",to:"dark"}]},roof:{text:"The stairs, still classified as a door, lead to a rooftop vegetable garden maintained by Accounting.",choices:[{label:"Water the basil",to:"basil"},{label:"Return to the lobby",to:"lobby"}]},aligned:{text:"You align perfectly with the chair. Nobody can find you. Promotion achieved.",choices:[],ending:"The Alignment Ending"},dark:{text:"With the projector off, everyone remembers the meeting was fictional.",choices:[],ending:"The Power Saving Ending"},basil:{text:"The basil approves your quarterly instincts. You resign to become weather.",choices:[],ending:"The Basil Ending"}};
-export const ADVENTURE_GRAPH:Record={dock:{text:"Low tide at the town of Elsewhere. A lighthouse blinks in an anxious rhythm.",choices:[{label:"Walk to lighthouse",to:"light"},{label:"Inspect vending oracle",to:"oracle"}]},oracle:{text:"The machine accepts compliments instead of coins and dispenses a paper moon.",choices:[{label:"Compliment its typography",to:"moon"},{label:"Return to dock",to:"dock"}]},moon:{text:"You pocket the paper moon. It smells faintly of toner.",choices:[{label:"Take moon to lighthouse",to:"keeper"}]},light:{text:"The keeper asks for proof that night has been properly filed.",choices:[{label:"Return to oracle",to:"oracle"}]},keeper:{text:"The paper moon completes the beam. Ships now navigate by excellent paperwork.",choices:[],ending:"Harbour Saved, Mostly"}};
-export function followStory(graph:Record,node:string,choice:number){const next=graph[node]?.choices[choice]?.to;return next&&graph[next]?next:null}
-export type AntState={active:boolean;count:number;generation:number};export function antReducer(s:AntState,a:{type:"release"|"clear";amount?:number}):AntState{return a.type==="clear"?{active:false,count:0,generation:s.generation+1}:{active:true,count:Math.min(80,s.count+(a.amount??18)),generation:s.generation}}
-export function antEscalation(count:number,waves=1){return Math.min(80,count+Math.max(0,waves)*12)}
-export function antPosition(index:number,total:number){const clustered=indexp.x===next.x&&p.y===next.y)}}
-export function safeRestore(s:{title:string;scrollX:number;scrollY:number}){return{...s,active:false,ants:0,timers:0}}
+// Maxwell OS model: window manager, terminal, stories, ants, snake, display
+// settings. Pure functions so the whole thing is unit-testable without a DOM.
+import { FILESYSTEM, traverse, resolvePath, formatTree, displayPath, type FileNode } from "./maxwellOSFiles";
+export { FILESYSTEM, traverse, resolvePath, formatTree, displayPath };
+export type { FileNode };
+
+export type AppId = "files" | "about" | "browser" | "terminal" | "notes" | "settings" | "help" | "trash" | "mines" | "snake" | "adventure" | "office";
+export type WindowPayload = { path?: string[] };
+export type OSWindow = { id: string; app: AppId; title: string; payload?: WindowPayload; x: number; y: number; width: number; height: number; z: number; minimized: boolean; maximized: boolean };
+export type OSState = { windows: OSWindow[]; nextZ: number };
+export type OSAction =
+ | { type: "open"; app: AppId; title: string; payload?: WindowPayload; size?: { width: number; height: number } }
+ | { type: "focus"; id: string }
+ | { type: "move"; id: string; x: number; y: number }
+ | { type: "resize"; id: string; width: number; height: number }
+ | { type: "minimize" | "maximize" | "close"; id: string }
+ | { type: "cycle"; direction?: 1 | -1 }
+ | { type: "hydrate"; state: OSState; viewport?: { width: number; height: number } };
+export const initialOSState: OSState = { windows: [], nextZ: 2 };
+export const MIN_WINDOW = { width: 320, height: 220 };
+
+/** Windows are keyed by app, except files opened in Notepad which are keyed by path. */
+export const windowKey = (app: AppId, payload?: WindowPayload) => (app === "notes" && payload?.path ? `notes:${payload.path.join("/")}` : app);
+
+const cascade = (n: number) => ({ x: 104 + (n % 8) * 28, y: 62 + (n % 8) * 22 });
+
+export function windowReducer(state: OSState, action: OSAction): OSState {
+ switch (action.type) {
+ case "open": {
+ const key = windowKey(action.app, action.payload);
+ const old = state.windows.find((w) => w.id === key);
+ if (old) {
+ const focused = windowReducer(state, { type: "focus", id: old.id });
+ if (!action.payload) return focused;
+ return { ...focused, windows: focused.windows.map((w) => (w.id === old.id ? { ...w, payload: action.payload, title: action.title } : w)) };
+ }
+ const n = state.windows.length;
+ const width = Math.min(action.size?.width ?? 900, innerSafeWidth());
+ const height = Math.min(action.size?.height ?? 650, innerSafeHeight());
+ return {
+ windows: [...state.windows, { id: key, app: action.app, title: action.title, payload: action.payload, ...cascade(n), width, height, z: state.nextZ, minimized: false, maximized: false }],
+ nextZ: state.nextZ + 1,
+ };
+ }
+ case "close":
+ return { ...state, windows: state.windows.filter((w) => w.id !== action.id) };
+ case "cycle": {
+ const ordered = [...state.windows].sort((a, b) => b.z - a.z);
+ if (ordered.length < 1) return state;
+ const dir = action.direction ?? 1;
+ // Alt+Tab: next window behind the top one; Shift+Alt+Tab: bring the bottom one up.
+ const next = dir === 1 ? ordered[1] ?? ordered[0] : ordered[ordered.length - 1];
+ return windowReducer(state, { type: "focus", id: next.id });
+ }
+ case "hydrate": {
+ const vw = action.viewport?.width ?? Infinity;
+ const vh = action.viewport?.height ?? Infinity;
+ const windows = action.state.windows.map((w) => ({
+ ...w,
+ x: Math.max(0, Math.min(w.x, vw - 80)),
+ y: Math.max(0, Math.min(w.y, vh - 120)),
+ width: Math.max(MIN_WINDOW.width, w.width),
+ height: Math.max(MIN_WINDOW.height, w.height),
+ }));
+ return { windows, nextZ: Math.max(action.state.nextZ, ...windows.map((w) => w.z + 1), 2) };
+ }
+ default:
+ return {
+ windows: state.windows.map((w) => {
+ if (w.id !== action.id) return w;
+ if (action.type === "focus") return { ...w, z: state.nextZ, minimized: false };
+ if (action.type === "move") return { ...w, x: Math.max(0, action.x), y: Math.max(0, action.y) };
+ if (action.type === "resize") return { ...w, width: Math.max(MIN_WINDOW.width, action.width), height: Math.max(MIN_WINDOW.height, action.height) };
+ if (action.type === "minimize") return { ...w, minimized: true };
+ if (action.type === "maximize") return { ...w, maximized: !w.maximized, minimized: false };
+ return w;
+ }),
+ nextZ: action.type === "focus" ? state.nextZ + 1 : state.nextZ,
+ };
+ }
+}
+export const topWindow = (state: OSState) => [...state.windows].filter((w) => !w.minimized).sort((a, b) => b.z - a.z)[0] ?? null;
+function innerSafeWidth() { return typeof innerWidth === "number" ? innerWidth - 150 : 820; }
+function innerSafeHeight() { return typeof innerHeight === "number" ? innerHeight - 145 : 600; }
+
+// ---------------------------------------------------------------- Terminal
+export type TerminalContext = { cwd: string[]; now?: string };
+export type TerminalResult = { output: string; clear?: boolean; open?: AppId; openFile?: string[]; vacuum?: boolean; cwd?: string[]; exit?: boolean };
+const COMMANDS = ["help", "about", "ls", "dir", "cd", "cat", "type", "pwd", "tree", "open", "start", "date", "clear", "cls", "whoami", "fortune", "knock", "vacuum", "echo", "ver", "exit"] as const;
+const OPENABLE: AppId[] = ["files", "about", "browser", "notes", "help", "mines", "snake", "adventure", "office", "settings", "trash", "terminal"];
+const FORTUNES = [
+ "A clean interface is often just a well-hidden argument.",
+ "Ship the small thing. The big thing is made of small things.",
+ "Every animation is a promise about what just happened.",
+ "If the release notes are boring, the release probably worked.",
+ "The best abstraction is the one you can delete on Friday.",
+];
+
+export function runCommand(raw: string, ctx: TerminalContext): TerminalResult {
+ const [cmdRaw, ...args] = raw.trim().split(/\s+/);
+ const cmd = (cmdRaw ?? "").toLowerCase();
+ if (!cmd) return { output: "" };
+ if (!COMMANDS.includes(cmd as never)) return { output: `Command not found: ${cmd}. This is a tiny pretend computer, not your shell. Try help.` };
+ const arg = args.join(" ");
+ switch (cmd) {
+ case "help":
+ return { output: ["ls / dir list the current folder", "cd change folder (.. goes up)", "cat / type print a file", "tree show everything", "open start a program or open a file", "date, whoami, ver, fortune, echo, clear", "knock knock knock worth a try", "vacuum ant containment protocol", "exit back to the portfolio"].join("\n") };
+ case "about":
+ case "ver":
+ return { output: "Maxwell OS 2.0. Local, fictional, and mildly overqualified. Window manager: one reducer, fully unit-tested." };
+ case "ls":
+ case "dir": {
+ const node = traverse(resolvePath(ctx.cwd, arg || undefined));
+ if (!node) return { output: `Folder not found: ${arg}` };
+ if (node.kind === "file") return { output: node.name };
+ const kids = node.children ?? [];
+ return { output: kids.length ? kids.map((k) => (k.kind === "folder" ? `${k.name}/` : k.name)).join("\n") : "(empty)" };
+ }
+ case "cd": {
+ if (!arg) return { output: displayPath(ctx.cwd) };
+ const path = resolvePath(ctx.cwd, arg);
+ const node = traverse(path);
+ if (!node || node.kind !== "folder") return { output: `Not a folder: ${arg}` };
+ return { output: "", cwd: path };
+ }
+ case "cat":
+ case "type": {
+ if (!arg) return { output: `Usage: ${cmd} ` };
+ const path = resolvePath(ctx.cwd, arg);
+ const node = traverse(path);
+ if (!node) return { output: `File not found: ${arg}` };
+ if (node.kind === "folder") return { output: `${node.name} is a folder. Try ls.` };
+ return { output: node.content ?? "(binary, and shy)" };
+ }
+ case "pwd":
+ return { output: displayPath(ctx.cwd) };
+ case "tree":
+ return { output: `${displayPath(ctx.cwd)}\n${formatTree(traverse(ctx.cwd) ?? FILESYSTEM)}` };
+ case "date":
+ return { output: ctx.now ?? "LOCAL TIME" };
+ case "clear":
+ case "cls":
+ return { output: "", clear: true };
+ case "whoami":
+ return { output: "guest@portfolio. Excellent disguise." };
+ case "fortune":
+ return { output: FORTUNES[raw.length % FORTUNES.length] };
+ case "echo":
+ return { output: arg };
+ case "knock":
+ return { output: arg.toLowerCase() === "knock knock" ? "A tiny door opens in My Computer: Projects\\the-door." : "The terminal knocks back." };
+ case "vacuum":
+ return { output: "Ant containment protocol requested.", vacuum: true };
+ case "exit":
+ return { output: "Shutting down. Returning you to the portfolio.", exit: true };
+ case "open":
+ case "start": {
+ if (!arg) return { output: "Usage: open . Apps: " + OPENABLE.join(", ") };
+ const app = arg.toLowerCase() as AppId;
+ if (OPENABLE.includes(app)) return { output: `Opening ${app}.`, open: app };
+ const path = resolvePath(ctx.cwd, arg);
+ const node = traverse(path);
+ if (!node) return { output: `Nothing called ${arg} here. Apps: ${OPENABLE.join(", ")}` };
+ if (node.app) return { output: `Starting ${node.name}.`, open: node.app };
+ if (node.kind === "folder") return { output: `Opening ${node.name} in My Computer.`, open: "files", openFile: path };
+ return { output: `Opening ${node.name} in Notepad.`, open: "notes", openFile: path };
+ }
+ }
+ return { output: "" };
+}
+/** Legacy single-arg parser kept for callers that only need the old behaviour. */
+export const parseCommand = (raw: string, now = "LOCAL TIME") => runCommand(raw, { cwd: [], now });
+
+// ---------------------------------------------------------------- Stories
+export type StoryNode = { text: string; choices: { label: string; to: string }[]; ending?: string };
+export const OFFICE_GRAPH: Record = { lobby: { text: "At 9:03, the office assigns you one task: choose a door. Facilities insists the stairs are legally a door. Nobody challenges Facilities.", choices: [{ label: "Enter the blue door", to: "meeting" }, { label: "Accept the stairs as a door", to: "roof" }] }, meeting: { text: "The meeting room contains one chair and a slide titled Alignment.", choices: [{ label: "Sit in the only chair", to: "aligned" }, { label: "Turn off the projector", to: "dark" }] }, roof: { text: "The stairs, still classified as a door, lead to a rooftop vegetable garden maintained by Accounting.", choices: [{ label: "Water the basil", to: "basil" }, { label: "Return to the lobby", to: "lobby" }] }, aligned: { text: "You align perfectly with the chair. Nobody can find you. Promotion achieved.", choices: [], ending: "The Alignment Ending" }, dark: { text: "With the projector off, everyone remembers the meeting was fictional.", choices: [], ending: "The Power Saving Ending" }, basil: { text: "The basil approves your quarterly instincts. You resign to become weather.", choices: [], ending: "The Basil Ending" } };
+export const ADVENTURE_GRAPH: Record = { dock: { text: "Low tide at the town of Elsewhere. A lighthouse blinks in an anxious rhythm.", choices: [{ label: "Walk to lighthouse", to: "light" }, { label: "Inspect vending oracle", to: "oracle" }] }, oracle: { text: "The machine accepts compliments instead of coins and dispenses a paper moon.", choices: [{ label: "Compliment its typography", to: "moon" }, { label: "Return to dock", to: "dock" }] }, moon: { text: "You pocket the paper moon. It smells faintly of toner.", choices: [{ label: "Take moon to lighthouse", to: "keeper" }] }, light: { text: "The keeper asks for proof that night has been properly filed.", choices: [{ label: "Return to oracle", to: "oracle" }] }, keeper: { text: "The paper moon completes the beam. Ships now navigate by excellent paperwork.", choices: [], ending: "Harbour Saved, Mostly" } };
+export function followStory(graph: Record, node: string, choice: number) { const next = graph[node]?.choices[choice]?.to; return next && graph[next] ? next : null; }
+
+// ---------------------------------------------------------------- Ants
+export type AntState = { active: boolean; count: number; generation: number };
+export function antReducer(s: AntState, a: { type: "release" | "clear"; amount?: number }): AntState { return a.type === "clear" ? { active: false, count: 0, generation: s.generation + 1 } : { active: true, count: Math.min(80, s.count + (a.amount ?? 18)), generation: s.generation }; }
+export function antEscalation(count: number, waves = 1) { return Math.min(80, count + Math.max(0, waves) * 12); }
+export function antPosition(index: number, total: number) { const clustered = index < Math.ceil(total * 0.7); if (clustered) { const origin = index % 2 ? "file" : "bin"; const base = origin === "file" ? { x: 62, y: 48 } : { x: 8, y: 72 }; return { x: base.x + ((index * 7) % 18) - 9, y: base.y + ((index * 11) % 16) - 8, origin } as const; } return { x: (index * 47) % 96, y: (index * 83) % 88, origin: "roaming" as const }; }
+
+// ---------------------------------------------------------------- Snake
+export type SnakePoint = { x: number; y: number };
+export const SNAKE_SIZE = 16;
+export type SnakeState = { body: SnakePoint[]; dir: SnakePoint; food: SnakePoint; score: number; dead: boolean };
+export function snakeStep(body: SnakePoint[], dir: SnakePoint, size = 12) { const h = body[0], next = { x: (h.x + dir.x + size) % size, y: (h.y + dir.y + size) % size }; return { body: [next, ...body.slice(0, -1)], hit: body.some((p) => p.x === next.x && p.y === next.y) }; }
+export function placeFood(body: SnakePoint[], size = SNAKE_SIZE, random = Math.random): SnakePoint {
+ const free: SnakePoint[] = [];
+ for (let y = 0; y < size; y++) for (let x = 0; x < size; x++) if (!body.some((p) => p.x === x && p.y === y)) free.push({ x, y });
+ return free[Math.floor(random() * free.length)] ?? { x: 0, y: 0 };
+}
+export function newSnake(random = Math.random): SnakeState { const body = [{ x: 8, y: 8 }, { x: 7, y: 8 }, { x: 6, y: 8 }]; return { body, dir: { x: 1, y: 0 }, food: placeFood(body, SNAKE_SIZE, random), score: 0, dead: false }; }
+export function snakeTick(s: SnakeState, random = Math.random): SnakeState {
+ if (s.dead) return s;
+ const h = s.body[0];
+ const next = { x: (h.x + s.dir.x + SNAKE_SIZE) % SNAKE_SIZE, y: (h.y + s.dir.y + SNAKE_SIZE) % SNAKE_SIZE };
+ const ate = next.x === s.food.x && next.y === s.food.y;
+ const trail = ate ? s.body : s.body.slice(0, -1);
+ if (trail.some((p) => p.x === next.x && p.y === next.y)) return { ...s, dead: true };
+ const body = [next, ...trail];
+ return { ...s, body, score: ate ? s.score + 10 : s.score, food: ate ? placeFood(body, SNAKE_SIZE, random) : s.food };
+}
+/** Reject reversing into yourself; everything else is allowed. */
+export function turnSnake(s: SnakeState, dir: SnakePoint): SnakeState { return dir.x === -s.dir.x && dir.y === -s.dir.y ? s : { ...s, dir }; }
+
+// ---------------------------------------------------------------- Display
+export type Scheme = { id: string; name: string; desktop: string; face: string; titleA: string; titleB: string; text: string; link: string };
+export const SCHEMES: Scheme[] = [
+ { id: "standard", name: "Maxwell Standard", desktop: "#087f80", face: "#c0c0c0", titleA: "#000080", titleB: "#1084d0", text: "#000000", link: "#000080" },
+ { id: "rose", name: "Rose Quartz", desktop: "#7c3f58", face: "#e8d5d8", titleA: "#5b1f3a", titleB: "#c06c84", text: "#2a1520", link: "#7a2246" },
+ { id: "slate", name: "Slate", desktop: "#3b4252", face: "#d8dee9", titleA: "#2e3440", titleB: "#5e81ac", text: "#1b1f27", link: "#2e5c8a" },
+ { id: "contrast", name: "High Contrast", desktop: "#000000", face: "#ffffff", titleA: "#000000", titleB: "#000000", text: "#000000", link: "#0000ee" },
+ { id: "hotdog", name: "Hot Dog Stand", desktop: "#ff0000", face: "#ffff00", titleA: "#ff0000", titleB: "#ff0000", text: "#000000", link: "#000000" },
+];
+export type Wallpaper = { id: string; name: string; css: string };
+export const WALLPAPERS: Wallpaper[] = [
+ { id: "none", name: "(None)", css: "none" },
+ { id: "clouds", name: "Clouds", css: "radial-gradient(ellipse 40% 30% at 20% 30%, #ffffff55, transparent), radial-gradient(ellipse 50% 35% at 70% 60%, #ffffff44, transparent), radial-gradient(ellipse 35% 25% at 45% 80%, #ffffff33, transparent), linear-gradient(#1b6fb8, #6fb3e0)" },
+ { id: "hills", name: "Hills", css: "linear-gradient(#2f7fd4 0 52%, #3f9a3a 52% 70%, #2f7a2c 70%)" },
+ { id: "midnight", name: "Midnight", css: "radial-gradient(circle at 20% 20%, #ffffff 0 1px, transparent 2px), radial-gradient(circle at 70% 60%, #ffffff 0 1px, transparent 2px), radial-gradient(circle at 40% 85%, #ffffff 0 1px, transparent 2px), #05071a" },
+ { id: "weave", name: "Weave", css: "repeating-linear-gradient(45deg, #0000 0 6px, #0002 6px 8px), repeating-linear-gradient(-45deg, #0000 0 6px, #0002 6px 8px)" },
+];
+export type OSPrefs = { scheme: string; wallpaper: string };
+export const defaultPrefs: OSPrefs = { scheme: "standard", wallpaper: "none" };
+export const schemeById = (id: string) => SCHEMES.find((s) => s.id === id) ?? SCHEMES[0];
+export const wallpaperById = (id: string) => WALLPAPERS.find((w) => w.id === id) ?? WALLPAPERS[0];
+
+export function safeRestore(s: { title: string; scrollX: number; scrollY: number }) { return { ...s, active: false, ants: 0, timers: 0 }; }
diff --git a/src/lib/maxwellOSFiles.ts b/src/lib/maxwellOSFiles.ts
new file mode 100644
index 00000000..423b71ff
--- /dev/null
+++ b/src/lib/maxwellOSFiles.ts
@@ -0,0 +1,228 @@
+// The Maxwell OS file system. Built once from the same data the rest of the
+// portfolio renders, so the pretend computer is actually full of the real work.
+import { rankedProjects, type Project } from "./projects";
+import { caseStudies } from "./caseStudies";
+import { essays } from "./essays";
+import { resumeData } from "./resumeData";
+import { canonFeed } from "./canonFeed";
+import { openSourceContributions } from "./openSource";
+import type { AppId } from "./maxwellOS";
+
+export type FileNode = {
+ name: string;
+ kind: "folder" | "file";
+ content?: string;
+ /** External or internal link the file points at (shown in Notepad and the Browser). */
+ href?: string;
+ /** Files that launch a program instead of opening in Notepad. */
+ app?: AppId;
+ children?: FileNode[];
+};
+
+// Notepad and the terminal both soft-wrap, so text is stored unwrapped.
+const wrap = (text: string) => text;
+
+const folderName = (s: string) => s.replace(/['`’]/g, "").replace(/[^a-z0-9-]/gi, "-").toLowerCase();
+
+function projectFolder(p: Project): FileNode {
+ const lines = [
+ p.name.toUpperCase(),
+ "=".repeat(p.name.length),
+ "",
+ `Status: ${p.status}${p.launchStage ? ` (${p.launchStage})` : ""}`,
+ p.role ? `Role: ${p.role}` : null,
+ p.startDate ? `Since: ${p.startDate.slice(0, 7)}` : null,
+ p.stack?.length ? `Stack: ${p.stack.join(", ")}` : null,
+ "",
+ wrap(p.longDescription ?? p.description),
+ "",
+ ...(p.impact?.length ? ["Impact:", ...p.impact.map((i) => ` * ${i}`), ""] : []),
+ p.links?.live ? `Live: ${p.links.live}` : null,
+ p.links?.repo ? `Source: ${p.links.repo}` : null,
+ p.caseStudySlug ? `Case study: /case-study/${p.caseStudySlug}` : null,
+ ].filter((l): l is string => l !== null);
+ const children: FileNode[] = [
+ { name: "README.txt", kind: "file", content: lines.join("\n"), href: p.links?.live ?? p.link },
+ ];
+ if (p.caseStudySlug && caseStudies[p.caseStudySlug]) {
+ const cs = caseStudies[p.caseStudySlug];
+ children.push({
+ name: "case-study.txt",
+ kind: "file",
+ href: `/case-study/${cs.slug}`,
+ content: [
+ cs.title.toUpperCase(),
+ cs.subtitle,
+ "",
+ `Timeline: ${cs.timeline}`,
+ `Role: ${cs.role}`,
+ `Tools: ${cs.tools.join(", ")}`,
+ "",
+ "OVERVIEW",
+ wrap(cs.overview),
+ "",
+ "CHALLENGE",
+ wrap(cs.challenge),
+ "",
+ "OUTCOME",
+ wrap(cs.outcome),
+ "",
+ `Full write-up: /case-study/${cs.slug}`,
+ ].join("\n"),
+ });
+ }
+ return { name: folderName(p.name), kind: "folder", children };
+}
+
+function essayFile(e: (typeof essays)[number]): FileNode {
+ return {
+ name: `${e.slug}.txt`,
+ kind: "file",
+ href: `/craft/essay/${e.slug}`,
+ content: `${e.title.toUpperCase()}\n${e.date} · ${e.readTime}\n\n${wrap(e.content)}\n\nRead it properly: /craft/essay/${e.slug}`,
+ };
+}
+
+function resumeFile(): FileNode {
+ const r = resumeData;
+ const lines = [
+ r.name.toUpperCase(),
+ r.title,
+ `${r.contact.location} · ${r.contact.email}`,
+ "",
+ wrap(r.profile),
+ "",
+ "EXPERIENCE",
+ ...r.experience.flatMap((x) => [`${x.date} ${x.title}, ${x.company}`, ...(x.summary ? [` ${x.summary}`] : []), ""]),
+ "EDUCATION",
+ ...r.education.map((e) => `${e.date} ${e.degree}, ${e.institution}`),
+ "",
+ "SKILLS",
+ ...r.skills.map((s) => `${s.category}: ${s.items.join(", ")}`),
+ "",
+ "Printable version: /resume",
+ ];
+ return { name: "RESUME.txt", kind: "file", content: lines.join("\n"), href: "/resume" };
+}
+
+function nowFolder(): FileNode {
+ const files: FileNode[] = canonFeed.now.map((item) => ({
+ name: `${item.verb.replace(/\s+/g, "-")}.txt`,
+ kind: "file",
+ href: item.href,
+ content: `${item.verb.toUpperCase()}\n\n${item.title}\n${item.creator}\n\n${item.note}\n\nLink: ${item.href}`,
+ }));
+ files.push({
+ name: "about-this-folder.txt",
+ kind: "file",
+ content: `Generated from Canon, a catalog of ${canonFeed.totalWorks} works I have read, watched, played and listened to.\nLast synced ${canonFeed.generatedAt}. Regions this month: ${canonFeed.regions.join(", ")}.`,
+ });
+ return { name: "Now", kind: "folder", children: files };
+}
+
+function openSourceFolder(): FileNode {
+ return {
+ name: "Open Source",
+ kind: "folder",
+ children: openSourceContributions.map((c) => ({
+ name: `${folderName(c.project)}.txt`,
+ kind: "file",
+ href: c.href,
+ content: `${c.project.toUpperCase()} — ${c.repository}\n${c.eyebrow} · ${c.date}\n\n${c.title}\n\n${wrap(c.summary)}\n\nProof:\n${c.proof.map((p) => ` * ${p}`).join("\n")}\n\nMerged change: ${c.href}`,
+ })),
+ };
+}
+
+export const FILESYSTEM: FileNode = {
+ name: "Desktop",
+ kind: "folder",
+ children: [
+ {
+ name: "Projects",
+ kind: "folder",
+ children: [
+ {
+ name: "README.txt",
+ kind: "file",
+ content:
+ "Small products, careful systems, and proof over promises.\n\nEach folder is a real project from the portfolio. Open README.txt for the summary, case-study.txt where one exists.",
+ },
+ ...rankedProjects.map(projectFolder),
+ {
+ name: "the-door",
+ kind: "folder",
+ children: [{ name: "knock-three-times.txt", kind: "file", content: "Try: knock knock knock in the terminal." }],
+ },
+ ],
+ },
+ {
+ name: "Documents",
+ kind: "folder",
+ children: [
+ resumeFile(),
+ { name: "Essays", kind: "folder", children: essays.map(essayFile) },
+ openSourceFolder(),
+ { name: "meeting-that-could-be-a-note.txt", kind: "file", content: "Agenda: cancel the meeting." },
+ { name: "Adventure-pass.txt", kind: "file", content: "The lighthouse keeper trusts people who carry a paper moon." },
+ ],
+ },
+ nowFolder(),
+ {
+ name: "Games",
+ kind: "folder",
+ children: [
+ { name: "mines.exe", kind: "file", app: "mines", content: "Minesweeper. 9 by 9, ten mines, no mercy." },
+ { name: "snake.exe", kind: "file", app: "snake", content: "Snake. Arrow keys. The walls wrap." },
+ { name: "elsewhere.exe", kind: "file", app: "adventure", content: "A short adventure about excellent paperwork." },
+ { name: "office.exe", kind: "file", app: "office", content: "A story about a person named You." },
+ ],
+ },
+ {
+ name: "Recycle Bin",
+ kind: "folder",
+ children: [{ name: "final_final_v7_REAL.txt", kind: "file", content: "This was never final." }],
+ },
+ ],
+};
+
+export function traverse(path: string[], root: FileNode = FILESYSTEM): FileNode | null {
+ let node: FileNode | undefined = root;
+ for (const part of path) {
+ if (node.kind !== "folder") return null;
+ node = node.children?.find((x) => x.name.toLowerCase() === part.toLowerCase());
+ if (!node) return null;
+ }
+ return node;
+}
+
+/** Resolve a DOS/Unix-ish path argument against a cwd, without escaping the tree. */
+export function resolvePath(cwd: string[], arg: string | undefined): string[] {
+ if (!arg || arg === ".") return cwd;
+ const absolute = /^[\\/]/.test(arg) || /^c:/i.test(arg) || arg === "~";
+ const parts = arg
+ .replace(/^c:/i, "")
+ .replace(/^~/, "")
+ .split(/[\\/]+/)
+ .filter((p) => p && p !== ".");
+ const out = absolute ? [] : [...cwd];
+ for (const p of parts) {
+ if (p === "..") out.pop();
+ else out.push(p);
+ }
+ const node = traverse(out);
+ return node ? out.map((p, i) => traverse(out.slice(0, i + 1))?.name ?? p) : out;
+}
+
+export function formatTree(node: FileNode, prefix = ""): string {
+ const kids = node.children ?? [];
+ return kids
+ .map((k, i) => {
+ const last = i === kids.length - 1;
+ const line = `${prefix}${last ? "└── " : "├── "}${k.name}${k.kind === "folder" ? "/" : ""}`;
+ return k.kind === "folder" ? `${line}\n${formatTree(k, prefix + (last ? " " : "│ "))}` : line;
+ })
+ .filter(Boolean)
+ .join("\n");
+}
+
+export const displayPath = (path: string[]) => `C:\\MAXWELL${path.length ? "\\" + path.map((p) => p.toUpperCase().replace(/\s+/g, "_")).join("\\") : ""}`;