Started my dev site, terminal theme
This commit is contained in:
parent
57d4c25f1f
commit
90ed7aede2
5 changed files with 333 additions and 104 deletions
|
@ -9,18 +9,19 @@
|
||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"next": "15.0.3",
|
||||||
"react": "19.0.0-rc-66855b96-20241106",
|
"react": "19.0.0-rc-66855b96-20241106",
|
||||||
"react-dom": "19.0.0-rc-66855b96-20241106",
|
"react-dom": "19.0.0-rc-66855b96-20241106",
|
||||||
"next": "15.0.3"
|
"react-icons": "^5.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5",
|
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^18",
|
"@types/react": "^18",
|
||||||
"@types/react-dom": "^18",
|
"@types/react-dom": "^18",
|
||||||
|
"eslint": "^8",
|
||||||
|
"eslint-config-next": "15.0.3",
|
||||||
"postcss": "^8",
|
"postcss": "^8",
|
||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.4.1",
|
||||||
"eslint": "^8",
|
"typescript": "^5"
|
||||||
"eslint-config-next": "15.0.3"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,5 +17,5 @@
|
||||||
body {
|
body {
|
||||||
color: var(--foreground);
|
color: var(--foreground);
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
font-family: 'Courier New', Courier, monospace;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,8 +14,8 @@ const geistMono = localFont({
|
||||||
});
|
});
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Create Next App",
|
title: "Ronniie.dev",
|
||||||
description: "Generated by create next app",
|
description: "Welcome to Ronnie's Development Terminal",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
|
|
102
src/app/page.tsx
102
src/app/page.tsx
|
@ -1,101 +1,9 @@
|
||||||
import Image from "next/image";
|
import Terminal from "../components/Terminal";
|
||||||
|
|
||||||
export default function Home() {
|
export default function TerminalPage() {
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
|
<div className="bg-black text-white w-full h-screen" suppressHydrationWarning >
|
||||||
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
|
<Terminal />
|
||||||
<Image
|
</div>
|
||||||
className="dark:invert"
|
|
||||||
src="/next.svg"
|
|
||||||
alt="Next.js logo"
|
|
||||||
width={180}
|
|
||||||
height={38}
|
|
||||||
priority
|
|
||||||
/>
|
|
||||||
<ol className="list-inside list-decimal text-sm text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
|
|
||||||
<li className="mb-2">
|
|
||||||
Get started by editing{" "}
|
|
||||||
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold">
|
|
||||||
src/app/page.tsx
|
|
||||||
</code>
|
|
||||||
.
|
|
||||||
</li>
|
|
||||||
<li>Save and see your changes instantly.</li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
<div className="flex gap-4 items-center flex-col sm:flex-row">
|
|
||||||
<a
|
|
||||||
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5"
|
|
||||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
className="dark:invert"
|
|
||||||
src="/vercel.svg"
|
|
||||||
alt="Vercel logomark"
|
|
||||||
width={20}
|
|
||||||
height={20}
|
|
||||||
/>
|
|
||||||
Deploy now
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44"
|
|
||||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
Read our docs
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
<footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
|
|
||||||
<a
|
|
||||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
|
||||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
aria-hidden
|
|
||||||
src="/file.svg"
|
|
||||||
alt="File icon"
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
/>
|
|
||||||
Learn
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
|
||||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
aria-hidden
|
|
||||||
src="/window.svg"
|
|
||||||
alt="Window icon"
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
/>
|
|
||||||
Examples
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
|
||||||
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
aria-hidden
|
|
||||||
src="/globe.svg"
|
|
||||||
alt="Globe icon"
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
/>
|
|
||||||
Go to nextjs.org →
|
|
||||||
</a>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
320
src/components/Terminal.tsx
Normal file
320
src/components/Terminal.tsx
Normal file
|
@ -0,0 +1,320 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState, useRef, useEffect } from "react";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
|
||||||
|
// Dynamically import React Icons with client-side rendering
|
||||||
|
const FaYoutube = dynamic(() => import("react-icons/fa6").then((mod) => mod.FaYoutube), { ssr: false });
|
||||||
|
const FaBluesky = dynamic(() => import("react-icons/fa6").then((mod) => mod.FaBluesky), { ssr: false });
|
||||||
|
const FaDiscord = dynamic(() => import("react-icons/fa6").then((mod) => mod.FaDiscord), { ssr: false });
|
||||||
|
const FaGithub = dynamic(() => import("react-icons/fa6").then((mod) => mod.FaGithub), { ssr: false });
|
||||||
|
const FaReddit = dynamic(() => import("react-icons/fa6").then((mod) => mod.FaReddit), { ssr: false });
|
||||||
|
|
||||||
|
// Self-hosted Services
|
||||||
|
const SiDocker = dynamic(() => import("react-icons/si").then((mod) => mod.SiDocker), { ssr: false });
|
||||||
|
const SiPlex = dynamic(() => import("react-icons/si").then((mod) => mod.SiPlex), { ssr: false });
|
||||||
|
const SiProxmox = dynamic(() => import("react-icons/si").then((mod) => mod.SiProxmox), { ssr: false });
|
||||||
|
const SiHomeassistant = dynamic(() => import("react-icons/si").then((mod) => mod.SiHomeassistant), { ssr: false });
|
||||||
|
const SiPaperlessngx = dynamic(() => import("react-icons/si").then((mod) => mod.SiPaperlessngx), { ssr: false });
|
||||||
|
|
||||||
|
|
||||||
|
const Terminal: React.FC = () => {
|
||||||
|
const [input, setInput] = useState("");
|
||||||
|
const [output, setOutput] = useState<JSX.Element[]>([<MOTD key="motd" />]);
|
||||||
|
const [history, setHistory] = useState<string[]>([]);
|
||||||
|
const [historyIndex, setHistoryIndex] = useState<number | null>(null);
|
||||||
|
|
||||||
|
const terminalEndRef = useRef<HTMLDivElement>(null);
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
const handleInput = (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (input.trim() === "") return;
|
||||||
|
|
||||||
|
const commands: { [key: string]: JSX.Element[] } = {
|
||||||
|
help: [
|
||||||
|
<div key="help">
|
||||||
|
<strong className="text-yellow-400">📖 Help Menu:</strong>
|
||||||
|
<ul className="mt-2 space-y-1">
|
||||||
|
<li>🆘 <strong className="text-green-400">help</strong>: Display this help menu.</li>
|
||||||
|
<li>📡 <strong className="text-green-400">socials</strong>: View my social links.</li>
|
||||||
|
<li>❌ <strong className="text-green-400">exit</strong>: Exit this terminal.</li>
|
||||||
|
</ul>
|
||||||
|
<p className="mt-4 text-gray-400">
|
||||||
|
💡 Pro Tip: Use <span className="text-green-400">↑</span> and <span className="text-green-400">↓</span> to navigate through your command history.
|
||||||
|
</p>
|
||||||
|
</div>,
|
||||||
|
],
|
||||||
|
socials: [
|
||||||
|
<div key="socials">
|
||||||
|
<strong className="text-yellow-400">📡 Social Links:</strong>
|
||||||
|
<div className="text-white flex items-center mt-2">
|
||||||
|
<FaGithub className="text-gray-400 mr-2"/>
|
||||||
|
GitHub:{" "}
|
||||||
|
<a
|
||||||
|
href="https://github.com/Ronniie"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="underline"
|
||||||
|
>
|
||||||
|
Ronniie
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-white flex items-center mt-2">
|
||||||
|
<FaBluesky className="text-blue-400 mr-2"/>
|
||||||
|
BlueSky:{" "}
|
||||||
|
<a
|
||||||
|
href="https://bsky.app/profile/ronniie.dev"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="underline"
|
||||||
|
>
|
||||||
|
@ronniie.dev
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="text-white flex items-center mt-2">
|
||||||
|
<FaDiscord className="text-blue-500 mr-2"/>
|
||||||
|
Discord:{" "}
|
||||||
|
ronniie.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-white flex items-center mt-2">
|
||||||
|
<FaYoutube className="text-red-500 mr-2"/>
|
||||||
|
YouTube:{" "}
|
||||||
|
<a
|
||||||
|
href="https://www.youtube.com/@Zotechz"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="underline"
|
||||||
|
>
|
||||||
|
@Zotechz
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="text-white flex items-center mt-2">
|
||||||
|
<FaReddit className="text-orange-500 mr-2"/>
|
||||||
|
Reddit:{" "}
|
||||||
|
<a
|
||||||
|
href="https://www.reddit.com/user/Zotechz/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="underline"
|
||||||
|
>
|
||||||
|
u/Zotechz
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const commandOutput = commands[input] || [
|
||||||
|
<div key="unknown" className="text-red-400">
|
||||||
|
Unknown command: {input}
|
||||||
|
</div>,
|
||||||
|
];
|
||||||
|
|
||||||
|
setOutput((prev) => [...prev, <div key={`cmd-${input}`}>$ {input}</div>, ...commandOutput]);
|
||||||
|
setHistory((prev) => [...prev, input]);
|
||||||
|
setHistoryIndex(null); // Reset the history index
|
||||||
|
setInput("");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (history.length === 0) return;
|
||||||
|
|
||||||
|
if (e.key === "ArrowUp") {
|
||||||
|
e.preventDefault();
|
||||||
|
if (historyIndex === null) {
|
||||||
|
setHistoryIndex(history.length - 1);
|
||||||
|
setInput(history[history.length - 1]);
|
||||||
|
} else if (historyIndex > 0) {
|
||||||
|
setHistoryIndex((prev) => {
|
||||||
|
const newIndex = prev! - 1;
|
||||||
|
setInput(history[newIndex]);
|
||||||
|
return newIndex;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.key === "ArrowDown") {
|
||||||
|
e.preventDefault();
|
||||||
|
if (historyIndex !== null && historyIndex < history.length - 1) {
|
||||||
|
setHistoryIndex((prev) => {
|
||||||
|
const newIndex = prev! + 1;
|
||||||
|
setInput(history[newIndex]);
|
||||||
|
return newIndex;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setHistoryIndex(null);
|
||||||
|
setInput("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClick = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
|
const selection = window.getSelection();
|
||||||
|
if (selection && selection.toString()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
inputRef.current?.focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
terminalEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
||||||
|
}, [output]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="bg-black text-white font-mono w-full h-screen overflow-y-auto p-4"
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
{output.map((line, index) => (
|
||||||
|
<div key={index} className="whitespace-pre-wrap">
|
||||||
|
{line}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form onSubmit={handleInput} className="flex items-center mt-2">
|
||||||
|
<span className="text-green-500">$</span>
|
||||||
|
<input
|
||||||
|
ref={inputRef}
|
||||||
|
type="text"
|
||||||
|
className="bg-black text-white outline-none flex-1 ml-2"
|
||||||
|
value={input}
|
||||||
|
onChange={(e) => setInput(e.target.value)}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
<div ref={terminalEndRef}></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const MOTD = () => (
|
||||||
|
<>
|
||||||
|
<div className="text-green-400">
|
||||||
|
{` _____ _ _ _ `}
|
||||||
|
{`\n | __ \\ (_|_) | | `}
|
||||||
|
{`\n | |__) |___ _ __ _ __ _ _ ___ __| | _____ __`}
|
||||||
|
{`\n | _ // _ \\| '_ \\| '_ \\| | |/ _ \\ / _\` |/ _ \\ \\ / /`}
|
||||||
|
{`\n | | \\ \\ (_) | | | | | | | | | __/| (_| | __/\\ V / `}
|
||||||
|
{`\n |_| \\_\\___/|_| |_|_| |_|_|_|\\___(_)__,_|\\___| \\_/ `}
|
||||||
|
</div>
|
||||||
|
<div className="text-green-300 mt-4">
|
||||||
|
<strong>Welcome to Ronnie's Development Terminal!</strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="border-t border-gray-700 my-4"/>
|
||||||
|
<strong className="text-yellow-400">🎩 ABOUT THE DEV</strong>
|
||||||
|
<p className="text-white">
|
||||||
|
Hi! I'm Ronnie—a developer passionate about blending code and design.
|
||||||
|
</p>
|
||||||
|
<p className="text-white">
|
||||||
|
This site is my playground for testing and crafting innovative projects.
|
||||||
|
</p>
|
||||||
|
<p className="text-white">
|
||||||
|
When I'm not tinkering here, I'm programming for NullDaily LLC, building
|
||||||
|
open-source tools to empower creators and developers.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="border-t border-gray-700 my-4"/>
|
||||||
|
<strong className="text-yellow-400">📡 SOCIALS</strong>
|
||||||
|
<div className="text-white flex items-center mt-2">
|
||||||
|
<FaGithub className="text-gray-400 mr-2"/>
|
||||||
|
GitHub:{" "}
|
||||||
|
<a
|
||||||
|
href="https://github.com/Ronniie"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="underline"
|
||||||
|
>
|
||||||
|
Ronniie
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-white flex items-center mt-2">
|
||||||
|
<FaBluesky className="text-blue-400 mr-2"/>
|
||||||
|
BlueSky:{" "}
|
||||||
|
<a
|
||||||
|
href="https://bsky.app/profile/ronniie.dev"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="underline"
|
||||||
|
>
|
||||||
|
@ronniie.dev
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="text-white flex items-center mt-2">
|
||||||
|
<FaDiscord className="text-blue-500 mr-2"/>
|
||||||
|
Discord:{" "}
|
||||||
|
ronniie.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-white flex items-center mt-2">
|
||||||
|
<FaYoutube className="text-red-500 mr-2"/>
|
||||||
|
YouTube:{" "}
|
||||||
|
<a
|
||||||
|
href="https://www.youtube.com/@Zotechz"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="underline"
|
||||||
|
>
|
||||||
|
@Zotechz
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="text-white flex items-center mt-2">
|
||||||
|
<FaReddit className="text-orange-500 mr-2"/>
|
||||||
|
Reddit:{" "}
|
||||||
|
<a
|
||||||
|
href="https://www.reddit.com/user/Zotechz/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="underline"
|
||||||
|
>
|
||||||
|
u/Zotechz
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="border-t border-gray-700 my-4"/>
|
||||||
|
<strong className="text-yellow-400">⚙️ SELF-HOSTED TOOLS</strong>
|
||||||
|
<div className="text-white flex items-center mt-2">
|
||||||
|
<SiProxmox className="text-orange-500 mr-2"/>
|
||||||
|
Proxmox: Virtual machines and containers, all in one place.
|
||||||
|
</div>
|
||||||
|
<div className="text-white flex items-center mt-2">
|
||||||
|
<SiDocker className="text-blue-500 mr-2"/>
|
||||||
|
Portainer: Orchestrating Docker containers effortlessly.
|
||||||
|
</div>
|
||||||
|
<div className="text-white flex items-center mt-2">
|
||||||
|
<SiPlex className="text-blue-400 mr-2"/>
|
||||||
|
Plex: Streaming my curated media library.
|
||||||
|
</div>
|
||||||
|
<div className="text-white flex items-center mt-2">
|
||||||
|
<SiHomeassistant className="text-teal-400 mr-2"/>
|
||||||
|
Home Assistant: Automating everything smart at home.
|
||||||
|
</div>
|
||||||
|
<div className="text-white flex items-center mt-2">
|
||||||
|
<SiPaperlessngx className="text-green-500 mr-2"/>
|
||||||
|
Paperless-ngx: Turning paper piles into searchable archives.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="border-t border-gray-700 my-4"/>
|
||||||
|
<p className="text-green-300">
|
||||||
|
🌟 "Code, automate, and create with purpose. This isn't just development;
|
||||||
|
it's an adventure." 🌟
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<div className="border-t border-gray-700 my-4"/>
|
||||||
|
<div className="text-gray-400 mt-2">
|
||||||
|
Type <span className="text-green-400">'help'</span> to see what you can do. Let’s explore together!
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Terminal;
|
Loading…
Add table
Add a link
Reference in a new issue