From c355fadfdac577c1e7516f1c4d8c17dd90feee69 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Sun, 7 Dec 2025 13:50:40 +0000 Subject: [PATCH] save latest --- .../app/components/TerminalBox.tsx | 224 +++++++++++++----- juntekim_frontend/app/globals.css | 9 + juntekim_frontend/app/page.tsx | 2 +- juntekim_frontend/lib/quotes.ts | 2 +- 4 files changed, 181 insertions(+), 56 deletions(-) diff --git a/juntekim_frontend/app/components/TerminalBox.tsx b/juntekim_frontend/app/components/TerminalBox.tsx index e8c27d5..083a548 100644 --- a/juntekim_frontend/app/components/TerminalBox.tsx +++ b/juntekim_frontend/app/components/TerminalBox.tsx @@ -1,9 +1,10 @@ "use client"; -import { useState, useEffect } from "react"; +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(""); @@ -13,8 +14,16 @@ export default function TerminalBox({ children }: { children: React.ReactNode }) const [showLoader, setShowLoader] = useState(false); const [loaderProgress, setLoaderProgress] = useState(0); const [showTree, setShowTree] = useState(false); + const [showIntroDone, setShowIntroDone] = useState(false); - // INIT + // ---- 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)]); @@ -23,44 +32,42 @@ export default function TerminalBox({ children }: { children: React.ReactNode }) setTimeout(() => typeCommand1(), 1800); }, []); - // TYPE COMMAND 1 + // TYPE ONLY COMMAND, NOT PROMPT const typeCommand1 = () => { - const cmd = "juntekim@site:~$ quoteOfTheDay"; + 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); }; - // TYPE COMMAND 2 const typeCommand2 = () => { - const cmd = "juntekim@site:~$ tree ."; + 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); } - }, 60); + }, 50); }; - // LOADER const startLoader = () => { setShowLoader(true); - let progress = 0; + const interval = setInterval(() => { progress += Math.random() * 20; @@ -71,67 +78,176 @@ export default function TerminalBox({ children }: { children: React.ReactNode }) setTimeout(() => { setShowTree(true); setShowLoader(false); + setShowIntroDone(true); + setTimeout(() => inputRef.current?.focus(), 200); }, 500); } setLoaderProgress(Math.floor(progress)); - }, 300); + }, 200); }; - // Loader UI - const LoaderBar = () => ( -
- compiling… {loaderProgress}% -
-
-
-
- ); + // --------------------------------------------------------- + // 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 (may crash) + vibecheck Check the vibe + clear I will not clear your history :) + exit You can't exit, it's a website +`, + + pwd: () => "/home/juntekim/site", + whoami: () => "juntekim", + + clear: () => "no ❤️ you need to 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 (
- {/* Prompt 1 */} + {/* BOOT SEQUENCE */} {showFirstPrompt &&
juntekim@site:~$
} - - {/* Enter echo */} {showEnterEcho &&
juntekim@site:~$
} - {/* Command 1 (NO cursor here) */} {typedCommand1 && ( -
{typedCommand1}
- )} - - {/* Quote */} - {showQuote && ( -
{quote}
- )} - - {/* Command 2 (NO cursor here) */} - {typedCommand2 && ( -
{typedCommand2}
- )} - - {/* Loader */} - {showLoader && } - - {/* Final tree */} - {showTree && ( -
- / -
{children}
- - {/* Final Prompt WITH cursor */} -
- juntekim@site:~$ -
+
+ 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 + /> +
+ + )}
); } diff --git a/juntekim_frontend/app/globals.css b/juntekim_frontend/app/globals.css index 54cf200..5908171 100644 --- a/juntekim_frontend/app/globals.css +++ b/juntekim_frontend/app/globals.css @@ -33,3 +33,12 @@ body { .animate-fadeIn { animation: fadeIn 0.4s ease forwards; } + +@keyframes blink { + 0%, 50% { opacity: 1; } + 50.01%, 100% { opacity: 0; } +} + +.animate-blink { + animation: blink 1s steps(1) infinite; +} diff --git a/juntekim_frontend/app/page.tsx b/juntekim_frontend/app/page.tsx index 8a79bc3..0403854 100644 --- a/juntekim_frontend/app/page.tsx +++ b/juntekim_frontend/app/page.tsx @@ -8,7 +8,7 @@ export default function Home() { return nodes.map((node, i) => (
- + {depth === 0 ? "├── " : "│ ".repeat(depth) + "└── "} {node.name} diff --git a/juntekim_frontend/lib/quotes.ts b/juntekim_frontend/lib/quotes.ts index 6696b13..2fe851d 100644 --- a/juntekim_frontend/lib/quotes.ts +++ b/juntekim_frontend/lib/quotes.ts @@ -2,6 +2,6 @@ export const QUOTES = [ '"Impatient with actions, patient with results." - Naval Ravikant', '"What good shall I do today? - Benjamin Franklin"', '"Nothing like a health problem to turn up the contrast dial on the rest of life." - Naval Ravikant', - '"If you want to go fast go alone; if you want to go far go together" - Unknown', + '"If you want to go fast, go alone; if you want to go far, go together" - Unknown', '“I don’t know if it happened for the best — but I know I’ll make the best out of whatever happens.” - Unknown', ];