"use client"; import { useState, useEffect, useRef } from "react"; import { useRouter } from "next/navigation"; import { QUOTES } from "@/lib/quotes"; const cdRoutes: Record = { about: "/About", learning: "/Learning", selfhosted: "/SelfHosted", home: "/", "~": "/", }; export default function TerminalBox({ children }: { children: React.ReactNode }) { const router = useRouter(); // ---- BOOT SEQUENCE STATES ---- const [showFirstPrompt, setShowFirstPrompt] = useState(false); const [showEnterEcho, setShowEnterEcho] = useState(false); const [typedCommand1, setTypedCommand1] = useState(""); const [showQuote, setShowQuote] = useState(false); const [quote, setQuote] = useState(""); const [typedCommand2, setTypedCommand2] = useState(""); const [showLoader, setShowLoader] = useState(false); const [loaderProgress, setLoaderProgress] = useState(0); const [showTree, setShowTree] = useState(false); const [showIntroDone, setShowIntroDone] = useState(false); // ---- INTERACTIVE STATES ---- const [history, setHistory] = useState<{ type: "input" | "output"; text: any }[]>([]); const [currentInput, setCurrentInput] = useState(""); const inputRef = useRef(null); // --------------------------------------------------------- // BOOT SEQUENCE // --------------------------------------------------------- useEffect(() => { setQuote(QUOTES[Math.floor(Math.random() * QUOTES.length)]); setTimeout(() => setShowFirstPrompt(true), 400); setTimeout(() => setShowEnterEcho(true), 1500); setTimeout(() => typeCommand1(), 1800); }, []); // TYPE ONLY COMMAND, NOT PROMPT const typeCommand1 = () => { const cmd = "quoteOfTheDay"; let i = 0; const interval = setInterval(() => { setTypedCommand1(cmd.slice(0, i + 1)); i++; if (i >= cmd.length) { clearInterval(interval); setTimeout(() => setShowQuote(true), 300); setTimeout(() => typeCommand2(), 1200); } }, 50); }; const typeCommand2 = () => { const cmd = "tree ."; let i = 0; const interval = setInterval(() => { setTypedCommand2(cmd.slice(0, i + 1)); i++; if (i >= cmd.length) { clearInterval(interval); setTimeout(() => startLoader(), 400); } }, 50); }; const startLoader = () => { setShowLoader(true); let progress = 0; const interval = setInterval(() => { progress += Math.random() * 20; if (progress >= 100) { progress = 100; clearInterval(interval); setTimeout(() => { setShowTree(true); setShowLoader(false); setShowIntroDone(true); setTimeout(() => inputRef.current?.focus(), 200); }, 500); } setLoaderProgress(Math.floor(progress)); }, 200); }; // --------------------------------------------------------- // COMMAND REGISTRY // --------------------------------------------------------- const commands: Record any> = { quoteOfTheDay: () => QUOTES[Math.floor(Math.random() * QUOTES.length)], help: () => ` Available commands: quoteOfTheDay Show a random quote tree . Show the file structure ls, ls -la Minimal directory listing cd Navigate to a page (about, learning, selfhosted, home) pwd Print working directory whoami Identify yourself motivation Generate motivation vibecheck Check the vibe clear Clears the screen history Look into the past exit You can't exit, it's a website `, pwd: () => "/home/juntekim/site", whoami: () => "I suspect you know who you are.", history: () => "If we keep looking back, we won't see the glory of now", clear: () => "no takebacks, face your past commands.", motivation: () => [ "Compiling motivation…", setTimeout(() => { setHistory(h => [...h, { type: "output", text: "Segmentation fault (core dumped)" }]); }, 500), ], vibecheck: () => { const vibes = [ "vibes immaculate ✨", "vibes questionable 🧐", "vibes under maintenance 🚧", "vibes loading… 0%", "vibes not found", ]; return vibes[Math.floor(Math.random() * vibes.length)]; }, exit: () => "bro it's a website. chill.", ls: () => "nothing to see here 👀", "ls -la": () => "nothing to see here 👀", cd: (args: string[]) => { const dest = cdRoutes[args[0]?.toLowerCase()]; if (dest) { setTimeout(() => router.push(dest), 300); return `navigating to ${args[0]}…`; } return `cd: ${args[0] ?? ""}: no such directory`; }, tree: () => [ "Compiling…", setTimeout(() => { setHistory(h => [...h, { type: "output", text: "/" }]); setHistory(h => [...h, { type: "output", text: children }]); }, 400), ], }; // --------------------------------------------------------- // COMMAND EXECUTION // --------------------------------------------------------- const handleCommand = (cmd: string) => { const parts = cmd.trim().split(" "); const base = parts[0]; const args = parts.slice(1); setHistory(h => [...h, { type: "input", text: cmd }]); if (cmd === "tree .") return commands["tree"](args); if (commands[base]) { const output = commands[base](args); if (typeof output === "string") { setHistory(h => [...h, { type: "output", text: output }]); } return; } setHistory(h => [...h, { type: "output", text: `command not found: ${cmd}` }]); }; const onKeyDown = (e: any) => { if (e.key === "Enter") { handleCommand(currentInput); setCurrentInput(""); } }; // --------------------------------------------------------- // RENDER // --------------------------------------------------------- return (
{/* BOOT SEQUENCE */} {showFirstPrompt &&
juntekim@site:~$
} {showEnterEcho &&
juntekim@site:~$
} {typedCommand1 && (
juntekim@site:~$  {typedCommand1}
)} {showQuote &&
{quote}
} {typedCommand2 && (
juntekim@site:~$  {typedCommand2}
)} {showLoader && (
compiling… {loaderProgress}%
)} {showTree && (
/ {children}
)} {/* INTERACTIVE MODE */} {showIntroDone && ( <> {history.map((item, idx) => (
{item.type === "input" ? ( <> juntekim@site:~$  {item.text} ) : ( {item.text} )}
))}
juntekim@site:~$  setCurrentInput(e.target.value)} onKeyDown={onKeyDown} className="bg-black text-zinc-300 outline-none w-full" autoFocus />
)}
); }