137 lines
3.6 KiB
TypeScript
137 lines
3.6 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
import { QUOTES } from "@/lib/quotes";
|
|
|
|
export default function TerminalBox({ children }: { children: React.ReactNode }) {
|
|
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);
|
|
|
|
// INIT
|
|
useEffect(() => {
|
|
setQuote(QUOTES[Math.floor(Math.random() * QUOTES.length)]);
|
|
|
|
setTimeout(() => setShowFirstPrompt(true), 400);
|
|
setTimeout(() => setShowEnterEcho(true), 1500);
|
|
setTimeout(() => typeCommand1(), 1800);
|
|
}, []);
|
|
|
|
// TYPE COMMAND 1
|
|
const typeCommand1 = () => {
|
|
const cmd = "juntekim@site:~$ 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 .";
|
|
let i = 0;
|
|
|
|
const interval = setInterval(() => {
|
|
setTypedCommand2(cmd.slice(0, i + 1));
|
|
i++;
|
|
if (i >= cmd.length) {
|
|
clearInterval(interval);
|
|
|
|
setTimeout(() => startLoader(), 400);
|
|
}
|
|
}, 60);
|
|
};
|
|
|
|
// LOADER
|
|
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);
|
|
}, 500);
|
|
}
|
|
|
|
setLoaderProgress(Math.floor(progress));
|
|
}, 300);
|
|
};
|
|
|
|
// 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>
|
|
);
|
|
|
|
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 */}
|
|
{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>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|