"use client"; import { useState, useEffect, useRef } from "react"; import { QUOTES } from "@/lib/quotes"; export default function TerminalBox({ children }: { children: React.ReactNode }) { // ---- 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 Playful message about not leaving 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: () => "where you trying to go? everything you need is in tree .", 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; } if (base === "cd") { setHistory(h => [...h, { type: "output", text: commands["cd"](args) }]); 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 />
)}
); }