Merge pull request #9 from MealCraft/feature/moredeployments
save latest
This commit is contained in:
commit
3cb5e5d6c7
4 changed files with 181 additions and 56 deletions
|
|
@ -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<HTMLInputElement>(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 = () => (
|
||||
<div className="text-green-400 font-mono mt-2">
|
||||
compiling… {loaderProgress}%
|
||||
<div className="w-full bg-zinc-800 h-2 rounded mt-1">
|
||||
<div
|
||||
className="bg-green-500 h-2 rounded"
|
||||
style={{ width: `${loaderProgress}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
// ---------------------------------------------------------
|
||||
// COMMAND REGISTRY
|
||||
// ---------------------------------------------------------
|
||||
const commands: Record<string, (args: string[]) => 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 <path> 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 (
|
||||
<div
|
||||
className="rounded-lg border border-zinc-700 bg-black text-zinc-100 p-4 font-mono text-sm shadow-xl w-full max-w-2xl"
|
||||
style={{ whiteSpace: "pre-wrap" }}
|
||||
>
|
||||
{/* Prompt 1 */}
|
||||
{/* BOOT SEQUENCE */}
|
||||
{showFirstPrompt && <div className="text-green-400">juntekim@site:~$</div>}
|
||||
|
||||
{/* Enter echo */}
|
||||
{showEnterEcho && <div className="text-green-400">juntekim@site:~$</div>}
|
||||
|
||||
{/* Command 1 (NO cursor here) */}
|
||||
{typedCommand1 && (
|
||||
<div className="text-green-400">{typedCommand1}</div>
|
||||
)}
|
||||
|
||||
{/* Quote */}
|
||||
{showQuote && (
|
||||
<div className="text-zinc-300 italic my-2 animate-fadeIn">{quote}</div>
|
||||
)}
|
||||
|
||||
{/* Command 2 (NO cursor here) */}
|
||||
{typedCommand2 && (
|
||||
<div className="text-green-400">{typedCommand2}</div>
|
||||
)}
|
||||
|
||||
{/* Loader */}
|
||||
{showLoader && <LoaderBar />}
|
||||
|
||||
{/* Final tree */}
|
||||
{showTree && (
|
||||
<div className="animate-fadeIn mt-2 text-green-400">
|
||||
/
|
||||
<div className="ml-2">{children}</div>
|
||||
|
||||
{/* Final Prompt WITH cursor */}
|
||||
<div className="text-green-400 mt-2">
|
||||
juntekim@site:~$ <span className="animate-pulse">▮</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-green-400">juntekim@site:~$ </span>
|
||||
<span className="text-zinc-300">{typedCommand1}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showQuote && <div className="text-zinc-300 italic my-2">{quote}</div>}
|
||||
|
||||
{typedCommand2 && (
|
||||
<div className="flex">
|
||||
<span className="text-green-400">juntekim@site:~$ </span>
|
||||
<span className="text-zinc-300">{typedCommand2}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showLoader && (
|
||||
<div className="text-green-400 mt-2">compiling… {loaderProgress}%</div>
|
||||
)}
|
||||
|
||||
{showTree && (
|
||||
<div className="text-zinc-300 mt-2">
|
||||
/
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* INTERACTIVE MODE */}
|
||||
{showIntroDone && (
|
||||
<>
|
||||
{history.map((item, idx) => (
|
||||
<div key={idx} className="flex">
|
||||
{item.type === "input" ? (
|
||||
<>
|
||||
<span className="text-green-400">juntekim@site:~$ </span>
|
||||
<span className="text-zinc-300">{item.text}</span>
|
||||
</>
|
||||
) : (
|
||||
<span className="text-zinc-300">{item.text}</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="flex items-center mt-1">
|
||||
<span className="text-green-400">juntekim@site:~$ </span>
|
||||
<input
|
||||
ref={inputRef}
|
||||
value={currentInput}
|
||||
onChange={(e) => setCurrentInput(e.target.value)}
|
||||
onKeyDown={onKeyDown}
|
||||
className="bg-black text-zinc-300 outline-none w-full"
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export default function Home() {
|
|||
return nodes.map((node, i) => (
|
||||
<div key={node.path}>
|
||||
<div style={{ marginLeft: depth * 16 }}>
|
||||
<a href={node.path} className="text-green-400 hover:underline">
|
||||
<a href={node.path} className="text-zinc-300 hover:underline">
|
||||
{depth === 0 ? "├── " : "│ ".repeat(depth) + "└── "}
|
||||
{node.name}
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
];
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue