Refactor App and Terminal components for improved styling and functionality; enhance BackgroundImages with random animations; update filesystem data with new personal information and remove unused files.
This commit is contained in:
parent
e3b91e1094
commit
aadb19be3e
@ -149,8 +149,8 @@ function App() {
|
||||
|
||||
{/* Your gothic header */}
|
||||
<div className="fixed top-0 left-0 w-full text-center pt-8 z-10 gothic-text">
|
||||
<h1 className="text-6xl text-purple-300 mb-2">Npc-About-Me</h1>
|
||||
<h2 className="text-xl text-purple-400">Console View</h2>
|
||||
<h1 className="text-6xl text-purple-300 mb-2">Npc About Me</h1>
|
||||
<h2 className="text-3xl text-purple-400">Console View</h2>
|
||||
</div>
|
||||
|
||||
{/* Your terminal component */}
|
||||
@ -179,7 +179,7 @@ function App() {
|
||||
<div className="h-3 w-3 bg-yellow-500 rounded-full hover:bg-yellow-600 transition-colors"></div>
|
||||
<div className="h-3 w-3 bg-green-500 rounded-full hover:bg-green-600 transition-colors"></div>
|
||||
</div>
|
||||
<span className="mx-auto text-sm text-gray-300 font-medium">
|
||||
<span className="mx-auto text-sm text-gray-300 gothic-text text-2xl">
|
||||
terminal@npc-about-me
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -17,22 +17,67 @@ const BackgroundImages = () => {
|
||||
"/bg-ims/blahaj.png",
|
||||
];
|
||||
|
||||
// Function to generate random values within a range
|
||||
const randomRange = (min, max) => Math.random() * (max - min) + min;
|
||||
|
||||
// Function to generate new position and animation values
|
||||
const generateImageData = () => {
|
||||
// Generate random movement offsets
|
||||
const moveX1 = randomRange(-20, 20);
|
||||
const moveY1 = randomRange(-20, 20);
|
||||
const moveX2 = randomRange(-20, 20);
|
||||
const moveY2 = randomRange(-20, 20);
|
||||
const moveX3 = randomRange(-20, 20);
|
||||
const moveY3 = randomRange(-20, 20);
|
||||
const rotateDeg = randomRange(-20, 20);
|
||||
|
||||
return {
|
||||
src: bgImages[Math.floor(Math.random() * bgImages.length)],
|
||||
size: randomRange(50, 150),
|
||||
opacity: randomRange(0.1, 0.3),
|
||||
// Animation durations (in seconds)
|
||||
moveDuration: randomRange(60, 120),
|
||||
rotateDuration: randomRange(30, 60),
|
||||
// Starting position
|
||||
startTop: randomRange(5, 90),
|
||||
startLeft: randomRange(5, 90),
|
||||
// Pre-calculated movement values
|
||||
moveX1,
|
||||
moveY1,
|
||||
moveX2,
|
||||
moveY2,
|
||||
moveX3,
|
||||
moveY3,
|
||||
rotateDeg,
|
||||
};
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Generate 30 random positioned images
|
||||
const randomImages = [];
|
||||
for (let i = 0; i < 30; i++) {
|
||||
randomImages.push({
|
||||
src: bgImages[Math.floor(Math.random() * bgImages.length)],
|
||||
top: Math.random() * 90 + 5, // 5-95% from top
|
||||
left: Math.random() * 90 + 5, // 5-95% from left
|
||||
size: Math.random() * 100 + 50, // 50-150px
|
||||
rotation: Math.random() * 40 - 20, // -20 to 20 degrees
|
||||
opacity: Math.random() * 0.2 + 0.1, // 0.1-0.3 opacity
|
||||
});
|
||||
}
|
||||
setImages(randomImages);
|
||||
// Generate initial images
|
||||
const initialImages = Array(30).fill(null).map(generateImageData);
|
||||
setImages(initialImages);
|
||||
}, []);
|
||||
|
||||
// Helper to create the keyframes CSS
|
||||
const generateKeyframesStyles = () => {
|
||||
return images
|
||||
.map(
|
||||
(img, index) => `
|
||||
@keyframes float-${index} {
|
||||
0%, 100% { transform: translate(-50%, -50%) translate(0, 0); }
|
||||
25% { transform: translate(-50%, -50%) translate(${img.moveX1}px, ${img.moveY1}px); }
|
||||
50% { transform: translate(-50%, -50%) translate(${img.moveX2}px, ${img.moveY2}px); }
|
||||
75% { transform: translate(-50%, -50%) translate(${img.moveX3}px, ${img.moveY3}px); }
|
||||
}
|
||||
@keyframes rotate-${index} {
|
||||
0%, 100% { transform: translate(-50%, -50%) rotate(0deg); }
|
||||
50% { transform: translate(-50%, -50%) rotate(${img.rotateDeg}deg); }
|
||||
}
|
||||
`
|
||||
)
|
||||
.join("\n");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 overflow-hidden pointer-events-none z-0">
|
||||
{images.map((img, index) => (
|
||||
@ -40,9 +85,13 @@ const BackgroundImages = () => {
|
||||
key={index}
|
||||
className="absolute"
|
||||
style={{
|
||||
top: `${img.top}%`,
|
||||
left: `${img.left}%`,
|
||||
transform: `translate(-50%, -50%) rotate(${img.rotation}deg)`,
|
||||
top: `${img.startTop}%`,
|
||||
left: `${img.startLeft}%`,
|
||||
transform: "translate(-50%, -50%)",
|
||||
animation: `
|
||||
float-${index} ${img.moveDuration}s infinite ease-in-out,
|
||||
rotate-${index} ${img.rotateDuration}s infinite ease-in-out
|
||||
`,
|
||||
}}
|
||||
>
|
||||
<img
|
||||
@ -57,6 +106,8 @@ const BackgroundImages = () => {
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<style dangerouslySetInnerHTML={{ __html: generateKeyframesStyles() }} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -8,6 +8,7 @@ export default function Terminal() {
|
||||
const inputRef = useRef(null);
|
||||
const terminalRef = useRef(null);
|
||||
const [hasBeenCleared, setHasBeenCleared] = useState(false);
|
||||
const [historyIndex, setHistoryIndex] = useState(-1);
|
||||
|
||||
// Focus the input when the component mounts and on click
|
||||
useEffect(() => {
|
||||
@ -42,6 +43,12 @@ export default function Terminal() {
|
||||
handleClear();
|
||||
setInputValue("");
|
||||
return;
|
||||
} else if (e.key === "ArrowDown") {
|
||||
e.preventDefault();
|
||||
navigateHistory(-1);
|
||||
} else if (e.key === "ArrowUp") {
|
||||
e.preventDefault();
|
||||
navigateHistory(1);
|
||||
}
|
||||
};
|
||||
|
||||
@ -188,6 +195,7 @@ export default function Terminal() {
|
||||
const executeCommand = () => {
|
||||
if (!inputValue.trim()) return;
|
||||
|
||||
setHistoryIndex(-1);
|
||||
// Create a new history entry with the CURRENT PATH at execution time
|
||||
const newHistoryEntry = {
|
||||
command: inputValue,
|
||||
@ -260,23 +268,48 @@ export default function Terminal() {
|
||||
// Handle ls command
|
||||
const handleLs = (args) => {
|
||||
let targetPath = currentPath;
|
||||
let showHidden = false;
|
||||
let pathArgs = [];
|
||||
|
||||
if (args.length > 0) {
|
||||
// Handle path argument
|
||||
targetPath = resolvePath(args[0]);
|
||||
// Parse arguments
|
||||
args.forEach((arg) => {
|
||||
if (arg.startsWith("-")) {
|
||||
// Handle flags
|
||||
if (arg === "-a" || arg === "--all") {
|
||||
showHidden = true;
|
||||
} else if (arg === "--help") {
|
||||
return `Usage: ls [OPTION]... [FILE]...
|
||||
List information about the FILEs (the current directory by default).
|
||||
Sort entries alphabetically.
|
||||
|
||||
-a, --all do not ignore entries starting with .
|
||||
--help display this help and exit`;
|
||||
}
|
||||
} else {
|
||||
// It's a path argument
|
||||
pathArgs.push(arg);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle path argument if provided
|
||||
if (pathArgs.length > 0) {
|
||||
targetPath = resolvePath(pathArgs[0]);
|
||||
}
|
||||
|
||||
const item = getItemAtPath(targetPath);
|
||||
|
||||
if (!item) {
|
||||
return `ls: ${args[0]}: No such file or directory`;
|
||||
return `ls: ${pathArgs[0]}: No such file or directory`;
|
||||
}
|
||||
|
||||
if (item.type !== "directory") {
|
||||
return `ls: ${args[0]}: Not a directory`;
|
||||
return `ls: ${pathArgs[0]}: Not a directory`;
|
||||
}
|
||||
|
||||
const children = Object.keys(item.children).sort();
|
||||
// Get and filter children based on showHidden flag
|
||||
const children = Object.keys(item.children)
|
||||
.filter((name) => showHidden || !name.startsWith("."))
|
||||
.sort();
|
||||
|
||||
if (children.length === 0) {
|
||||
return "<span class='text-gray-400'>Empty directory</span>";
|
||||
@ -345,20 +378,36 @@ export default function Terminal() {
|
||||
}
|
||||
|
||||
const fileName = args[0];
|
||||
const currentDir = getItemAtPath(currentPath);
|
||||
let targetPath = fileName;
|
||||
|
||||
if (!currentDir || currentDir.type !== "directory") {
|
||||
return "Current location is not a directory";
|
||||
// If it's not an absolute path, resolve it relative to current directory
|
||||
if (!fileName.startsWith("/")) {
|
||||
targetPath = resolvePath(fileName);
|
||||
}
|
||||
|
||||
if (
|
||||
fileName in currentDir.children &&
|
||||
currentDir.children[fileName].type === "file"
|
||||
) {
|
||||
return currentDir.children[fileName].content;
|
||||
// Get the directory and file name from the path
|
||||
const lastSlashIndex = targetPath.lastIndexOf("/");
|
||||
const dirPath =
|
||||
lastSlashIndex === -1
|
||||
? currentPath
|
||||
: targetPath.substring(0, lastSlashIndex);
|
||||
const targetFileName =
|
||||
lastSlashIndex === -1
|
||||
? targetPath
|
||||
: targetPath.substring(lastSlashIndex + 1);
|
||||
|
||||
const dir = getItemAtPath(dirPath);
|
||||
|
||||
if (!dir || dir.type !== "directory") {
|
||||
return `cat: ${fileName}: No such file or directory`;
|
||||
}
|
||||
|
||||
return `cat: ${fileName}: No such file`;
|
||||
const file = dir.children[targetFileName];
|
||||
if (!file || file.type !== "file") {
|
||||
return `cat: ${fileName}: No such file`;
|
||||
}
|
||||
|
||||
return file.content;
|
||||
};
|
||||
|
||||
// Handle view command for images
|
||||
@ -407,6 +456,23 @@ export default function Terminal() {
|
||||
</div>`;
|
||||
};
|
||||
|
||||
// Function to navigate command history
|
||||
const navigateHistory = (direction) => {
|
||||
if (commandHistory.length === 0) return;
|
||||
|
||||
const newIndex = historyIndex + direction;
|
||||
if (newIndex >= -1 && newIndex < commandHistory.length) {
|
||||
setHistoryIndex(newIndex);
|
||||
if (newIndex === -1) {
|
||||
setInputValue("");
|
||||
} else {
|
||||
setInputValue(
|
||||
commandHistory[commandHistory.length - 1 - newIndex].command
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="h-full text-terminal-text font-mono p-4 overflow-hidden flex flex-col text-left bg-gradient-to-b from-gray-900 to-black rounded-b-lg"
|
||||
@ -425,20 +491,13 @@ export default function Terminal() {
|
||||
>
|
||||
{!hasBeenCleared && (
|
||||
<div className="text-lg mb-4 text-terminal-accent text-left">
|
||||
<pre className="text-purple-300 font-bold">
|
||||
{`
|
||||
_ _ ____ ____ _ ____ ___ _ _ _____ __ __ _____
|
||||
| \\ | | _ \\ / ___| / \\ | __ ) / _ \\| | | |_ _| | \\/ | ____|
|
||||
| \\| | |_) | | / _ \\ | _ \\| | | | | | | | | | |\\/| | _|
|
||||
| |\\ | __/| |___ / ___ \\| |_) | |_| | |_| | | | | | | | |___
|
||||
|_| \\_|_| \\____| /_/ \\_\\____/ \\___/ \\___/ |_| |_| |_|_____|
|
||||
|
||||
`}
|
||||
</pre>
|
||||
<div className="text-green-400 font-semibold mb-2">
|
||||
<div className="text-purple-300 gothic-text text-4xl tracking-wider">
|
||||
NPC ABOUT ME
|
||||
</div>
|
||||
<div className="text-green-400 gothic-text font-semibold mb-2 text-2xl">
|
||||
Welcome to my interactive terminal!
|
||||
</div>
|
||||
<div className="text-amber-300 mb-3">
|
||||
<div className="text-amber-300 gothic-text mb-3 text-xl">
|
||||
Type{" "}
|
||||
<span className="bg-gray-800 px-1 rounded text-white">help</span>{" "}
|
||||
to see available commands.
|
||||
@ -449,7 +508,7 @@ export default function Terminal() {
|
||||
{commandHistory.map((entry, index) => (
|
||||
<div key={index} className="mb-2 text-left">
|
||||
<div className="flex">
|
||||
<span className="text-terminal-prompt mr-2">
|
||||
<span className="text-terminal-prompt mr-2 gothic-text text-xl">
|
||||
guest@npc-shell:
|
||||
{entry.path === "/home/guest"
|
||||
? "~"
|
||||
@ -479,7 +538,7 @@ export default function Terminal() {
|
||||
className="w-full"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<span className="text-terminal-prompt mr-2 flex items-center">
|
||||
<span className="text-terminal-prompt mr-2 flex items-center gothic-text text-xl">
|
||||
<span className="text-green-400">guest</span>
|
||||
<span className="text-gray-500">@</span>
|
||||
<span className="text-purple-400">npc-shell</span>
|
||||
|
||||
@ -17,7 +17,12 @@ export const fileSystem = {
|
||||
"about.txt": {
|
||||
type: "file",
|
||||
content:
|
||||
"Hi there! I'm a developer passionate about creating interactive web experiences.\n\nI enjoy working with modern JavaScript frameworks, building intuitive UIs, and exploring new technologies.\n\nWhen I'm not coding, you can find me gaming, reading sci-fi, or hanging out with my Blahaj.",
|
||||
"I'm the average Internet person. I currently code and play video games :) I especially play Euro Truck Sim 2 and Roblox, Phantom Forces to be exact. Sometimes I play Minecraft with my friends :D.\n\nI realy love Ikea's stuffed animals\n\nI can Program in html, css, python, c++, c#, c and maybe i can do minecraft mods too :]",
|
||||
},
|
||||
"status.txt": {
|
||||
type: "file",
|
||||
content:
|
||||
"Status :)\nHi there! Here are some details about me:\n\nAge: 14\nPronouns: He/Him ^_^\nRelationship Status: Single :D\nFun Fact 1: I'm an NPC! :O Just doing my thing, not needing to be in the center of attention or talk much. :|\nFun Fact 2: I really like the Ikea Plushies. (BLÅHAJ on top)",
|
||||
},
|
||||
".secret": {
|
||||
type: "file",
|
||||
@ -32,30 +37,9 @@ export const fileSystem = {
|
||||
content:
|
||||
"Terminal Portfolio\n------------------\n\nAn interactive terminal-style portfolio website built with React and Tailwind CSS.\n\nFeatures:\n- Fully functional command line interface\n- Virtual filesystem navigation\n- Custom commands\n- Responsive design",
|
||||
},
|
||||
"project1.txt": {
|
||||
type: "file",
|
||||
content:
|
||||
"Project 1\n---------\n\nA full-stack web application for task management.\n\nTechnologies:\n- React\n- Node.js\n- MongoDB\n- Express",
|
||||
},
|
||||
"project2.txt": {
|
||||
type: "file",
|
||||
content:
|
||||
"Project 2\n---------\n\nAn e-commerce platform with real-time inventory tracking.\n\nTechnologies:\n- Vue.js\n- Firebase\n- Stripe API\n- Tailwind CSS",
|
||||
},
|
||||
screenshots: {
|
||||
type: "directory",
|
||||
children: {
|
||||
"project1.png": {
|
||||
type: "image",
|
||||
src: "/images/project1.png",
|
||||
description: "Screenshot of Project 1",
|
||||
},
|
||||
"project2.png": {
|
||||
type: "image",
|
||||
src: "/images/project2.png",
|
||||
description: "Screenshot of Project 2",
|
||||
},
|
||||
},
|
||||
children: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -70,12 +54,12 @@ export const fileSystem = {
|
||||
"backend.txt": {
|
||||
type: "file",
|
||||
content:
|
||||
"Backend Skills\n-------------\n\n- Node.js, Express\n- Python, Django\n- RESTful APIs\n- GraphQL\n- MongoDB, PostgreSQL\n- Firebase, AWS",
|
||||
"Backend Skills\n-------------\n\n- Node.js\n- Python\n- Flask APIs\n- ChartJS\n- MariaDB, PostgreSQL",
|
||||
},
|
||||
"tools.txt": {
|
||||
type: "file",
|
||||
content:
|
||||
"Development Tools\n----------------\n\n- Git, GitHub\n- Docker\n- VS Code\n- Figma\n- Jest, Cypress\n- CI/CD Pipelines",
|
||||
"Development Tools\n----------------\n\n- Gitea, GitHub\n- Docker\n- VS Code/Cursor\n- Proxmox, Linux",
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -84,15 +68,15 @@ export const fileSystem = {
|
||||
children: {
|
||||
"email.txt": {
|
||||
type: "file",
|
||||
content: "Email: example@domain.com",
|
||||
content: "Email: npc.lele@gmail.com",
|
||||
},
|
||||
"github.txt": {
|
||||
type: "file",
|
||||
content: "GitHub: https://github.com/yourusername",
|
||||
content: "GitHub: https://github.com/Lele7663",
|
||||
},
|
||||
"linkedin.txt": {
|
||||
"discord.txt": {
|
||||
type: "file",
|
||||
content: "LinkedIn: https://linkedin.com/in/yourprofile",
|
||||
content: "jxstnpc",
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -147,7 +131,7 @@ export const fileSystem = {
|
||||
"coding-music.txt": {
|
||||
type: "file",
|
||||
content:
|
||||
"Best Music for Coding\n------------------\n\n- Lo-fi beats\n- Ambient electronic\n- Video game soundtracks\n- Classical music\n- Synthwave\n\nPersonally, I find that music without lyrics helps me focus best when tackling complex problems.",
|
||||
"Best Music for Coding\n------------------\n- Anything that sounds good!\n(based answer, i know)",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -132,13 +132,6 @@ button:focus-visible {
|
||||
}
|
||||
|
||||
/* Add these styles to your index.css */
|
||||
@font-face {
|
||||
font-family: "CloisterBlack";
|
||||
src: url("/CloisterBlack.ttf") format("truetype");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.terminal-window {
|
||||
transition: box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user