first commit
24
.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
12
README.md
Normal file
@ -0,0 +1,12 @@
|
||||
# React + Vite
|
||||
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||
|
||||
Currently, two official plugins are available:
|
||||
|
||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
|
||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||
|
||||
## Expanding the ESLint configuration
|
||||
|
||||
If you are developing a production application, we recommend using TypeScript and enable type-aware lint rules. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
|
||||
33
eslint.config.js
Normal file
@ -0,0 +1,33 @@
|
||||
import js from '@eslint/js'
|
||||
import globals from 'globals'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
|
||||
export default [
|
||||
{ ignores: ['dist'] },
|
||||
{
|
||||
files: ['**/*.{js,jsx}'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
ecmaFeatures: { jsx: true },
|
||||
sourceType: 'module',
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
'react-hooks': reactHooks,
|
||||
'react-refresh': reactRefresh,
|
||||
},
|
||||
rules: {
|
||||
...js.configs.recommended.rules,
|
||||
...reactHooks.configs.recommended.rules,
|
||||
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
17
index.html
Normal file
@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
href="/blahaj-stoffspielzeug-hai__08773-removebg-preview.png"
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>About Me Terminal</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
3996
package-lock.json
generated
Normal file
30
package.json
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "aboutmecmd",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.21.0",
|
||||
"@types/react": "^19.0.10",
|
||||
"@types/react-dom": "^19.0.4",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"eslint": "^9.21.0",
|
||||
"eslint-plugin-react-hooks": "^5.1.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.19",
|
||||
"globals": "^15.15.0",
|
||||
"postcss": "^8.4.35",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"vite": "^6.2.0"
|
||||
}
|
||||
}
|
||||
6
postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
BIN
public/CloisterBlack.ttf
Normal file
BIN
public/background.jpg
Normal file
|
After Width: | Height: | Size: 2.9 MiB |
BIN
public/bg-ims/Archlinux_600x600.png
Normal file
|
After Width: | Height: | Size: 103 KiB |
BIN
public/bg-ims/bash-logo.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
public/bg-ims/blahaj.png
Normal file
|
After Width: | Height: | Size: 123 KiB |
BIN
public/bg-ims/kitty.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
public/bg-ims/nixoslogo.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
public/bg-ims/nixostext.jpg
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
BIN
public/bg-ims/tux-large.webp
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
public/bg-ims/tux.jpg
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
public/blahaj-stoffspielzeug-hai__08773-removebg-preview.png
Normal file
|
After Width: | Height: | Size: 215 KiB |
BIN
public/blahaj-stoffspielzeug-hai__08773.jpg
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
public/favicon.ico
Normal file
|
After Width: | Height: | Size: 264 KiB |
BIN
public/profile.jpg
Normal file
|
After Width: | Height: | Size: 41 KiB |
1
public/vite.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
62
shell.nix
Normal file
@ -0,0 +1,62 @@
|
||||
{ pkgs ? import <nixpkgs> {} }:
|
||||
|
||||
pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
# Node.js and package managers
|
||||
nodejs_20
|
||||
nodePackages.npm
|
||||
nodePackages.yarn
|
||||
|
||||
# Utilities
|
||||
git
|
||||
curl
|
||||
wget
|
||||
|
||||
# Optional: Additional helpful tools
|
||||
direnv # Environment management
|
||||
nodePackages.prettier # Code formatting
|
||||
nodePackages.eslint # Linting
|
||||
];
|
||||
|
||||
shellHook = ''
|
||||
echo "React + Tailwind development environment"
|
||||
echo "Node version: $(node -v)"
|
||||
echo "npm version: $(npm -v)"
|
||||
|
||||
# Automatically set up a new project if package.json doesn't exist
|
||||
if [ ! -f package.json ]; then
|
||||
echo "No package.json found. Would you like to initialize a new React + Tailwind project? (y/n)"
|
||||
read response
|
||||
if [ "$response" = "y" ]; then
|
||||
# Create React project with Vite
|
||||
npm create vite@latest . -- --template react
|
||||
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Install Tailwind CSS and its dependencies
|
||||
echo "Installing Tailwind CSS..."
|
||||
npm install -D tailwindcss postcss autoprefixer
|
||||
|
||||
# Initialize Tailwind config
|
||||
echo "Initializing Tailwind configuration..."
|
||||
npx tailwindcss init -p
|
||||
|
||||
# Update src/index.css with Tailwind directives
|
||||
echo "Updating CSS with Tailwind directives..."
|
||||
if [ -f src/index.css ]; then
|
||||
cat > src/index.css << 'EOL'
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
EOL
|
||||
echo "Tailwind CSS directives added to src/index.css"
|
||||
else
|
||||
echo "Warning: src/index.css not found. You'll need to manually add Tailwind directives."
|
||||
fi
|
||||
|
||||
echo -e "\nSetup complete! Start your development server with: npm run dev"
|
||||
fi
|
||||
fi
|
||||
'';
|
||||
}
|
||||
47
src/App.css
Normal file
@ -0,0 +1,47 @@
|
||||
#root {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Override for terminal content */
|
||||
.bg-\[\#232323\] {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 6em;
|
||||
padding: 1.5em;
|
||||
will-change: filter;
|
||||
transition: filter 300ms;
|
||||
}
|
||||
.logo:hover {
|
||||
filter: drop-shadow(0 0 2em #646cffaa);
|
||||
}
|
||||
.logo.react:hover {
|
||||
filter: drop-shadow(0 0 2em #61dafbaa);
|
||||
}
|
||||
|
||||
@keyframes logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
a:nth-of-type(2) .logo {
|
||||
animation: logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
.read-the-docs {
|
||||
color: #888;
|
||||
}
|
||||
246
src/App.jsx
Normal file
@ -0,0 +1,246 @@
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import Terminal from "./components/Terminal";
|
||||
import "./App.css";
|
||||
import Background from "./components/Background";
|
||||
import BackgroundImages from "./components/BackgroundImages";
|
||||
|
||||
function App() {
|
||||
// State for terminal position and size
|
||||
const [position, setPosition] = useState({ x: 100, y: 100 });
|
||||
const [size, setSize] = useState({ width: 800, height: 500 });
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [isResizing, setIsResizing] = useState(false);
|
||||
const [resizeDirection, setResizeDirection] = useState("");
|
||||
const [startPos, setStartPos] = useState({ x: 0, y: 0 });
|
||||
const [startSize, setStartSize] = useState({ width: 0, height: 0 });
|
||||
const [startResizePos, setStartResizePos] = useState({ x: 0, y: 0 });
|
||||
const terminalRef = useRef(null);
|
||||
|
||||
console.log("Terminal state:", {
|
||||
position,
|
||||
size,
|
||||
isResizing,
|
||||
resizeDirection,
|
||||
});
|
||||
|
||||
// Start dragging the terminal window
|
||||
const handleMouseDown = (e) => {
|
||||
e.preventDefault();
|
||||
setIsDragging(true);
|
||||
setStartPos({ x: e.clientX - position.x, y: e.clientY - position.y });
|
||||
};
|
||||
|
||||
// Start resizing from a specific corner
|
||||
const handleResizeMouseDown = (direction) => (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsResizing(true);
|
||||
setResizeDirection(direction);
|
||||
setStartResizePos({ x: e.clientX, y: e.clientY });
|
||||
setStartSize({ ...size });
|
||||
setStartPos({ ...position });
|
||||
};
|
||||
|
||||
// Handle mouse movement for dragging and resizing
|
||||
const handleMouseMove = (e) => {
|
||||
if (isDragging) {
|
||||
const newX = e.clientX - startPos.x;
|
||||
const newY = e.clientY - startPos.y;
|
||||
setPosition({
|
||||
x: Math.max(0, Math.min(newX, window.innerWidth - size.width)),
|
||||
y: Math.max(0, Math.min(newY, window.innerHeight - size.height)),
|
||||
});
|
||||
} else if (isResizing) {
|
||||
const deltaX = e.clientX - startResizePos.x;
|
||||
const deltaY = e.clientY - startResizePos.y;
|
||||
const minWidth = 400;
|
||||
const minHeight = 300;
|
||||
|
||||
let newWidth = startSize.width;
|
||||
let newHeight = startSize.height;
|
||||
let newX = position.x;
|
||||
let newY = position.y;
|
||||
|
||||
switch (resizeDirection) {
|
||||
case "bottom-right":
|
||||
newWidth = Math.max(minWidth, startSize.width + deltaX);
|
||||
newHeight = Math.max(minHeight, startSize.height + deltaY);
|
||||
break;
|
||||
case "bottom-left":
|
||||
newWidth = Math.max(minWidth, startSize.width - deltaX);
|
||||
newHeight = Math.max(minHeight, startSize.height + deltaY);
|
||||
newX = startPos.x + startSize.width - newWidth;
|
||||
break;
|
||||
case "top-right":
|
||||
newWidth = Math.max(minWidth, startSize.width + deltaX);
|
||||
newHeight = Math.max(minHeight, startSize.height - deltaY);
|
||||
newY = startPos.y + startSize.height - newHeight;
|
||||
break;
|
||||
case "top-left":
|
||||
newWidth = Math.max(minWidth, startSize.width - deltaX);
|
||||
newHeight = Math.max(minHeight, startSize.height - deltaY);
|
||||
newX = startPos.x + startSize.width - newWidth;
|
||||
newY = startPos.y + startSize.height - newHeight;
|
||||
break;
|
||||
}
|
||||
|
||||
setSize({ width: newWidth, height: newHeight });
|
||||
setPosition({ x: newX, y: newY });
|
||||
}
|
||||
};
|
||||
|
||||
// End dragging or resizing
|
||||
const handleMouseUp = () => {
|
||||
setIsDragging(false);
|
||||
setIsResizing(false);
|
||||
};
|
||||
|
||||
// Set up event listeners
|
||||
useEffect(() => {
|
||||
// Change cursor style based on action
|
||||
if (isDragging) {
|
||||
document.body.style.cursor = "move";
|
||||
} else if (isResizing) {
|
||||
switch (resizeDirection) {
|
||||
case "bottom-right":
|
||||
document.body.style.cursor = "se-resize";
|
||||
break;
|
||||
case "bottom-left":
|
||||
document.body.style.cursor = "sw-resize";
|
||||
break;
|
||||
case "top-right":
|
||||
document.body.style.cursor = "ne-resize";
|
||||
break;
|
||||
case "top-left":
|
||||
document.body.style.cursor = "nw-resize";
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
document.body.style.cursor = "auto";
|
||||
}
|
||||
|
||||
// Add event listeners when dragging or resizing
|
||||
if (isDragging || isResizing) {
|
||||
window.addEventListener("mousemove", handleMouseMove);
|
||||
window.addEventListener("mouseup", handleMouseUp);
|
||||
}
|
||||
|
||||
// Cleanup function
|
||||
return () => {
|
||||
window.removeEventListener("mousemove", handleMouseMove);
|
||||
window.removeEventListener("mouseup", handleMouseUp);
|
||||
document.body.style.cursor = "auto";
|
||||
};
|
||||
}, [
|
||||
isDragging,
|
||||
isResizing,
|
||||
resizeDirection,
|
||||
position,
|
||||
size,
|
||||
startPos,
|
||||
startSize,
|
||||
startResizePos,
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className="App min-h-screen w-full fixed inset-0 bg-indigo-950">
|
||||
{/* Background images */}
|
||||
<BackgroundImages />
|
||||
|
||||
{/* 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>
|
||||
</div>
|
||||
|
||||
{/* Your terminal component */}
|
||||
<div
|
||||
ref={terminalRef}
|
||||
className="rounded-lg shadow-2xl overflow-hidden border-2 border-purple-600 select-none terminal-window transition-all duration-300"
|
||||
style={{
|
||||
position: "absolute",
|
||||
left: `${position.x}px`,
|
||||
top: `${position.y}px`,
|
||||
width: `${size.width}px`,
|
||||
height: `${size.height}px`,
|
||||
zIndex: 10,
|
||||
boxShadow:
|
||||
"0 0 15px rgba(128, 0, 255, 0.5), 0 0 30px rgba(128, 0, 255, 0.3)",
|
||||
backdropFilter: "blur(3px)",
|
||||
}}
|
||||
>
|
||||
{/* Terminal header */}
|
||||
<div
|
||||
className="bg-gradient-to-r from-gray-800 to-gray-900 px-4 py-2 flex items-center border-b border-purple-500 cursor-move"
|
||||
onMouseDown={handleMouseDown}
|
||||
>
|
||||
<div className="flex space-x-3">
|
||||
<div className="h-3 w-3 bg-red-500 rounded-full hover:bg-red-600 transition-colors"></div>
|
||||
<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">
|
||||
terminal@npc-about-me
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Terminal component with better background */}
|
||||
<div className="bg-terminal-bg w-full h-[calc(100%-2.5rem)] overflow-hidden bg-[#0d1117]">
|
||||
<Terminal />
|
||||
</div>
|
||||
|
||||
{/* Improved resize handles */}
|
||||
<div
|
||||
className="absolute bottom-0 right-0 w-8 h-8 cursor-se-resize"
|
||||
onMouseDown={handleResizeMouseDown("bottom-right")}
|
||||
style={{
|
||||
background:
|
||||
"linear-gradient(135deg, transparent 70%, rgba(168, 85, 247, 0.7) 100%)",
|
||||
opacity: 0.7,
|
||||
transition: "opacity 0.2s ease",
|
||||
}}
|
||||
onMouseEnter={(e) => (e.currentTarget.style.opacity = "1")}
|
||||
onMouseLeave={(e) => (e.currentTarget.style.opacity = "0.7")}
|
||||
/>
|
||||
<div
|
||||
className="absolute bottom-0 left-0 w-8 h-8 cursor-sw-resize"
|
||||
onMouseDown={handleResizeMouseDown("bottom-left")}
|
||||
style={{
|
||||
background:
|
||||
"linear-gradient(225deg, transparent 70%, rgba(168, 85, 247, 0.7) 100%)",
|
||||
opacity: 0.7,
|
||||
transition: "opacity 0.2s ease",
|
||||
}}
|
||||
onMouseEnter={(e) => (e.currentTarget.style.opacity = "1")}
|
||||
onMouseLeave={(e) => (e.currentTarget.style.opacity = "0.7")}
|
||||
/>
|
||||
<div
|
||||
className="absolute top-0 right-0 w-8 h-8 cursor-ne-resize"
|
||||
onMouseDown={handleResizeMouseDown("top-right")}
|
||||
style={{
|
||||
background:
|
||||
"linear-gradient(45deg, transparent 70%, rgba(168, 85, 247, 0.7) 100%)",
|
||||
opacity: 0.7,
|
||||
transition: "opacity 0.2s ease",
|
||||
}}
|
||||
onMouseEnter={(e) => (e.currentTarget.style.opacity = "1")}
|
||||
onMouseLeave={(e) => (e.currentTarget.style.opacity = "0.7")}
|
||||
/>
|
||||
<div
|
||||
className="absolute top-0 left-0 w-8 h-8 cursor-nw-resize"
|
||||
onMouseDown={handleResizeMouseDown("top-left")}
|
||||
style={{
|
||||
background:
|
||||
"linear-gradient(315deg, transparent 70%, rgba(168, 85, 247, 0.7) 100%)",
|
||||
opacity: 0.7,
|
||||
transition: "opacity 0.2s ease",
|
||||
}}
|
||||
onMouseEnter={(e) => (e.currentTarget.style.opacity = "1")}
|
||||
onMouseLeave={(e) => (e.currentTarget.style.opacity = "0.7")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
1
src/assets/react.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
47
src/components/Background.jsx
Normal file
@ -0,0 +1,47 @@
|
||||
import React from "react";
|
||||
|
||||
const Background = () => {
|
||||
return (
|
||||
<div className="fixed inset-0 z-0 bg-indigo-950 flex flex-col items-center justify-center overflow-hidden">
|
||||
{/* Background pattern */}
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
<div className="grid grid-cols-12 h-full">
|
||||
{Array(60)
|
||||
.fill()
|
||||
.map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="text-xs font-mono text-purple-300 p-2 overflow-hidden"
|
||||
>
|
||||
{`$ sudo apt upgrade\n> updating system...\n$ cd ~/projects\n$ vim main.cpp\n$ gcc -Wall main.cpp\n$ ./a.out\n$ ps aux | grep chrome\n$ kill -9 1337\n`}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Funny Linux/Windows tiles */}
|
||||
|
||||
{/* Footer quote */}
|
||||
<div className="absolute bottom-4 text-center text-purple-300 px-4 max-w-2xl">
|
||||
<p className="text-lg font-semibold italic">
|
||||
"Imagine using an OS that needs antivirus software to protect itself
|
||||
from itself."
|
||||
</p>
|
||||
<p className="text-sm mt-2">— Every Linux User Ever</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Tile component for each funny caption
|
||||
const FunnyTile = ({ icon, title, description }) => {
|
||||
return (
|
||||
<div className="bg-purple-900 bg-opacity-30 backdrop-blur-sm p-4 rounded-lg border border-purple-700 transition-all duration-300 hover:bg-opacity-50 hover:scale-105">
|
||||
<div className="text-4xl mb-2">{icon}</div>
|
||||
<h3 className="text-xl font-bold text-purple-300 mb-2">{title}</h3>
|
||||
<p className="text-gray-300 text-sm">{description}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Background;
|
||||
64
src/components/BackgroundImages.jsx
Normal file
@ -0,0 +1,64 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
const BackgroundImages = () => {
|
||||
const [images, setImages] = useState([]);
|
||||
|
||||
// List of background images to use
|
||||
const bgImages = [
|
||||
"/bg-ims/tux-large.webp",
|
||||
"/bg-ims/kitty.png",
|
||||
"/bg-ims/bash-logo.png",
|
||||
"/bg-ims/nixoslogo.png",
|
||||
"/bg-ims/Archlinux_600x600.png",
|
||||
"/bg-ims/blahaj.png",
|
||||
"/bg-ims/blahaj.png",
|
||||
"/bg-ims/blahaj.png",
|
||||
"/bg-ims/blahaj.png",
|
||||
"/bg-ims/blahaj.png",
|
||||
];
|
||||
|
||||
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);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 overflow-hidden pointer-events-none z-0">
|
||||
{images.map((img, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="absolute"
|
||||
style={{
|
||||
top: `${img.top}%`,
|
||||
left: `${img.left}%`,
|
||||
transform: `translate(-50%, -50%) rotate(${img.rotation}deg)`,
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={img.src}
|
||||
alt=""
|
||||
style={{
|
||||
width: `${img.size}px`,
|
||||
height: `${img.size}px`,
|
||||
opacity: img.opacity,
|
||||
objectFit: "contain",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BackgroundImages;
|
||||
509
src/components/Terminal.jsx
Normal file
@ -0,0 +1,509 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { fileSystem } from "../data/filesystem";
|
||||
|
||||
export default function Terminal() {
|
||||
const [currentPath, setCurrentPath] = useState("/home/guest");
|
||||
const [commandHistory, setCommandHistory] = useState([]);
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
const inputRef = useRef(null);
|
||||
const terminalRef = useRef(null);
|
||||
const [hasBeenCleared, setHasBeenCleared] = useState(false);
|
||||
|
||||
// Focus the input when the component mounts and on click
|
||||
useEffect(() => {
|
||||
inputRef.current.focus();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// Make sure input is focused after render
|
||||
if (inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
}
|
||||
}, [commandHistory]); // Re-focus after command history changes
|
||||
|
||||
const focusInput = () => {
|
||||
inputRef.current.focus();
|
||||
};
|
||||
|
||||
const handleKeyDown = (e) => {
|
||||
console.log("Key pressed:", e.key, "Input value:", inputValue); // Add debugging
|
||||
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
e.stopPropagation(); // Stop event propagation
|
||||
console.log("Enter pressed, executing command:", inputValue);
|
||||
executeCommand();
|
||||
return false; // Ensure the event is fully handled
|
||||
} else if (e.key === "Tab") {
|
||||
e.preventDefault();
|
||||
handleTabCompletion();
|
||||
} else if (e.key === "l" && e.ctrlKey) {
|
||||
e.preventDefault();
|
||||
handleClear();
|
||||
setInputValue("");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const handleContainerKeyDown = (e) => {
|
||||
if (e.key === "Enter" && document.activeElement === inputRef.current) {
|
||||
e.preventDefault();
|
||||
executeCommand();
|
||||
}
|
||||
};
|
||||
|
||||
const handleClear = () => {
|
||||
setCommandHistory([]);
|
||||
setHasBeenCleared(true);
|
||||
|
||||
// Return focus to input
|
||||
setTimeout(() => {
|
||||
if (inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
}
|
||||
}, 10);
|
||||
|
||||
return "";
|
||||
};
|
||||
|
||||
// Path resolution functions
|
||||
const resolvePath = (path) => {
|
||||
// If it's an absolute path, start from root
|
||||
if (path.startsWith("/")) {
|
||||
return path;
|
||||
}
|
||||
|
||||
// If it's ".", return current path
|
||||
if (path === ".") {
|
||||
return currentPath;
|
||||
}
|
||||
|
||||
// If it's "..", go up one level
|
||||
if (path === "..") {
|
||||
const pathParts = currentPath.split("/").filter(Boolean);
|
||||
if (pathParts.length === 0) {
|
||||
return "/";
|
||||
}
|
||||
pathParts.pop();
|
||||
return "/" + pathParts.join("/");
|
||||
}
|
||||
|
||||
// Handle relative paths - append to current path
|
||||
if (currentPath.endsWith("/")) {
|
||||
return currentPath + path;
|
||||
}
|
||||
return currentPath + "/" + path;
|
||||
};
|
||||
|
||||
const getItemAtPath = (path) => {
|
||||
const normalizedPath =
|
||||
path.endsWith("/") && path !== "/" ? path.slice(0, -1) : path;
|
||||
|
||||
if (normalizedPath === "/") {
|
||||
return fileSystem["/"];
|
||||
}
|
||||
|
||||
const pathParts = normalizedPath.split("/").filter(Boolean);
|
||||
let current = fileSystem["/"];
|
||||
|
||||
for (const part of pathParts) {
|
||||
if (!current || current.type !== "directory" || !current.children[part]) {
|
||||
return null;
|
||||
}
|
||||
current = current.children[part];
|
||||
}
|
||||
|
||||
return current;
|
||||
};
|
||||
|
||||
// Handle tab completion
|
||||
const handleTabCompletion = () => {
|
||||
if (!inputValue.trim()) return;
|
||||
|
||||
const parts = inputValue.trim().split(" ");
|
||||
const command = parts[0].toLowerCase();
|
||||
|
||||
// If completing a command
|
||||
if (parts.length === 1) {
|
||||
const commands = [
|
||||
"ls",
|
||||
"cd",
|
||||
"cat",
|
||||
"pwd",
|
||||
"date",
|
||||
"help",
|
||||
"clear",
|
||||
"view",
|
||||
];
|
||||
const matchingCommands = commands.filter((cmd) =>
|
||||
cmd.startsWith(command)
|
||||
);
|
||||
|
||||
if (matchingCommands.length === 1) {
|
||||
setInputValue(matchingCommands[0]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If completing a path/file/directory
|
||||
if (["cd", "cat", "ls", "view"].includes(command)) {
|
||||
const partialPath = parts[parts.length - 1];
|
||||
const suggestions = getPathSuggestions(partialPath);
|
||||
|
||||
if (suggestions.length === 1) {
|
||||
const completedParts = [...parts];
|
||||
completedParts[completedParts.length - 1] = suggestions[0];
|
||||
setInputValue(completedParts.join(" "));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Get path suggestions based on current directory
|
||||
const getPathSuggestions = (partialPath) => {
|
||||
const dirPath = partialPath.includes("/")
|
||||
? partialPath.substring(0, partialPath.lastIndexOf("/") + 1)
|
||||
: "";
|
||||
|
||||
const prefix = partialPath.includes("/")
|
||||
? partialPath.substring(partialPath.lastIndexOf("/") + 1)
|
||||
: partialPath;
|
||||
|
||||
const resolvedDirPath = resolvePath(dirPath || currentPath);
|
||||
const dir = getItemAtPath(resolvedDirPath);
|
||||
|
||||
if (!dir || dir.type !== "directory") return [];
|
||||
|
||||
return Object.keys(dir.children)
|
||||
.filter((name) => name.startsWith(prefix))
|
||||
.map((name) => {
|
||||
const fullPath = dirPath + name;
|
||||
if (dir.children[name].type === "directory") {
|
||||
return fullPath + "/";
|
||||
}
|
||||
return fullPath;
|
||||
});
|
||||
};
|
||||
|
||||
// Process and execute commands
|
||||
const executeCommand = () => {
|
||||
if (!inputValue.trim()) return;
|
||||
|
||||
// Create a new history entry with the CURRENT PATH at execution time
|
||||
const newHistoryEntry = {
|
||||
command: inputValue,
|
||||
output: "",
|
||||
path: currentPath, // Store the path at execution time
|
||||
};
|
||||
|
||||
const parts = inputValue.trim().split(/\s+/);
|
||||
const command = parts[0].toLowerCase();
|
||||
const args = parts.slice(1);
|
||||
|
||||
let output = "";
|
||||
|
||||
switch (command) {
|
||||
case "ls":
|
||||
output = handleLs(args);
|
||||
break;
|
||||
case "cd":
|
||||
output = handleCd(args);
|
||||
break;
|
||||
case "cat":
|
||||
output = handleCat(args);
|
||||
break;
|
||||
case "view":
|
||||
output = handleView(args);
|
||||
break;
|
||||
case "pwd":
|
||||
output = currentPath;
|
||||
break;
|
||||
case "date":
|
||||
output = new Date().toLocaleString();
|
||||
break;
|
||||
case "help":
|
||||
output =
|
||||
"Available commands:\n" +
|
||||
" ls - List directory contents\n" +
|
||||
" cd [directory] - Change directory\n" +
|
||||
" cat [file] - Display file contents\n" +
|
||||
" view [image] - Display image files\n" +
|
||||
" pwd - Print working directory\n" +
|
||||
" clear - Clear the terminal\n" +
|
||||
" help - Display this help message\n\n" +
|
||||
"Shortcuts:\n" +
|
||||
" Tab - Autocomplete commands and paths\n" +
|
||||
" Ctrl+L - Clear the terminal";
|
||||
break;
|
||||
case "clear":
|
||||
clearTerminal();
|
||||
setInputValue("");
|
||||
return;
|
||||
default:
|
||||
output = `Command not found: ${command}`;
|
||||
}
|
||||
|
||||
newHistoryEntry.output = output;
|
||||
setCommandHistory([...commandHistory, newHistoryEntry]);
|
||||
setInputValue("");
|
||||
|
||||
// Auto-scroll to bottom and re-focus input after rendering
|
||||
setTimeout(() => {
|
||||
if (terminalRef.current) {
|
||||
terminalRef.current.scrollTop = terminalRef.current.scrollHeight;
|
||||
}
|
||||
if (inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
}
|
||||
}, 50);
|
||||
};
|
||||
|
||||
// Handle ls command
|
||||
const handleLs = (args) => {
|
||||
let targetPath = currentPath;
|
||||
|
||||
if (args.length > 0) {
|
||||
// Handle path argument
|
||||
targetPath = resolvePath(args[0]);
|
||||
}
|
||||
|
||||
const item = getItemAtPath(targetPath);
|
||||
|
||||
if (!item) {
|
||||
return `ls: ${args[0]}: No such file or directory`;
|
||||
}
|
||||
|
||||
if (item.type !== "directory") {
|
||||
return `ls: ${args[0]}: Not a directory`;
|
||||
}
|
||||
|
||||
const children = Object.keys(item.children).sort();
|
||||
|
||||
if (children.length === 0) {
|
||||
return "<span class='text-gray-400'>Empty directory</span>";
|
||||
}
|
||||
|
||||
return children
|
||||
.map((name) => {
|
||||
const child = item.children[name];
|
||||
const itemClass =
|
||||
child.type === "directory"
|
||||
? "directory"
|
||||
: child.type === "image"
|
||||
? "image"
|
||||
: "file";
|
||||
|
||||
return `<div class="file-item ${itemClass}">${name}</div>`;
|
||||
})
|
||||
.join("");
|
||||
};
|
||||
|
||||
// Handle cd command
|
||||
const handleCd = (args) => {
|
||||
if (!args.length || args[0] === "~" || args[0] === "/home/guest") {
|
||||
setCurrentPath("/home/guest");
|
||||
return "";
|
||||
}
|
||||
|
||||
// Handle special case for tilde expansion
|
||||
let targetPath = args[0];
|
||||
if (targetPath.startsWith("~/")) {
|
||||
targetPath = "/home/guest/" + targetPath.substring(2);
|
||||
} else if (targetPath === "~") {
|
||||
targetPath = "/home/guest";
|
||||
}
|
||||
|
||||
let newPath = resolvePath(targetPath);
|
||||
|
||||
// Ensure path has leading slash
|
||||
if (!newPath.startsWith("/")) {
|
||||
newPath = "/" + newPath;
|
||||
}
|
||||
|
||||
// Normalize path (remove trailing slash except for root)
|
||||
if (newPath !== "/" && newPath.endsWith("/")) {
|
||||
newPath = newPath.slice(0, -1);
|
||||
}
|
||||
|
||||
const targetDir = getItemAtPath(newPath);
|
||||
|
||||
if (!targetDir) {
|
||||
return `cd: ${targetPath}: No such directory`;
|
||||
}
|
||||
|
||||
if (targetDir.type !== "directory") {
|
||||
return `cd: ${targetPath}: Not a directory`;
|
||||
}
|
||||
|
||||
setCurrentPath(newPath);
|
||||
return "";
|
||||
};
|
||||
|
||||
// Handle cat command
|
||||
const handleCat = (args) => {
|
||||
if (!args.length) {
|
||||
return "Usage: cat [filename]";
|
||||
}
|
||||
|
||||
const fileName = args[0];
|
||||
const currentDir = getItemAtPath(currentPath);
|
||||
|
||||
if (!currentDir || currentDir.type !== "directory") {
|
||||
return "Current location is not a directory";
|
||||
}
|
||||
|
||||
if (
|
||||
fileName in currentDir.children &&
|
||||
currentDir.children[fileName].type === "file"
|
||||
) {
|
||||
return currentDir.children[fileName].content;
|
||||
}
|
||||
|
||||
return `cat: ${fileName}: No such file`;
|
||||
};
|
||||
|
||||
// Handle view command for images
|
||||
const handleView = (args) => {
|
||||
if (!args.length) {
|
||||
return "Usage: view [image filename]";
|
||||
}
|
||||
|
||||
const fileName = args[0];
|
||||
let file;
|
||||
|
||||
// Handle paths with slashes
|
||||
if (fileName.includes("/")) {
|
||||
const path = resolvePath(fileName);
|
||||
file = getItemAtPath(path);
|
||||
} else {
|
||||
// Look in current directory
|
||||
const currentDir = getItemAtPath(currentPath);
|
||||
if (!currentDir || currentDir.type !== "directory") {
|
||||
return "Current location is not a directory";
|
||||
}
|
||||
|
||||
file = currentDir.children[fileName];
|
||||
}
|
||||
|
||||
if (!file) {
|
||||
return `view: ${fileName}: No such file`;
|
||||
}
|
||||
|
||||
if (file.type !== "image") {
|
||||
return `view: ${fileName}: Not an image file`;
|
||||
}
|
||||
|
||||
// Return HTML with the image
|
||||
setTimeout(() => {
|
||||
// Re-focus the input after image is displayed
|
||||
if (inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
}
|
||||
}, 50);
|
||||
|
||||
return `<div class="mt-2 flex flex-col items-center">
|
||||
<img src="${file.src}" alt="${file.description || "Image"}"
|
||||
class="max-w-full rounded shadow-md" style="max-height: 300px;" />
|
||||
<div class="text-sm text-gray-400 mt-1">${file.description || ""}</div>
|
||||
</div>`;
|
||||
};
|
||||
|
||||
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"
|
||||
onClick={focusInput}
|
||||
onKeyDown={handleContainerKeyDown}
|
||||
tabIndex="-1"
|
||||
>
|
||||
<div
|
||||
ref={terminalRef}
|
||||
className="flex-1 overflow-y-auto overflow-x-hidden pb-4 custom-scrollbar text-left"
|
||||
style={{
|
||||
maxHeight: "calc(100% - 30px)",
|
||||
scrollbarWidth: "thin",
|
||||
scrollbarColor: "rgba(128, 90, 213, 0.7) rgba(35, 35, 35, 0.5)",
|
||||
}}
|
||||
>
|
||||
{!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">
|
||||
Welcome to my interactive terminal!
|
||||
</div>
|
||||
<div className="text-amber-300 mb-3">
|
||||
Type{" "}
|
||||
<span className="bg-gray-800 px-1 rounded text-white">help</span>{" "}
|
||||
to see available commands.
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{commandHistory.map((entry, index) => (
|
||||
<div key={index} className="mb-2 text-left">
|
||||
<div className="flex">
|
||||
<span className="text-terminal-prompt mr-2">
|
||||
guest@npc-shell:
|
||||
{entry.path === "/home/guest"
|
||||
? "~"
|
||||
: entry.path.replace("/home/guest", "~")}
|
||||
$
|
||||
</span>
|
||||
<span className="text-terminal-command">{entry.command}</span>
|
||||
</div>
|
||||
{entry.output && (
|
||||
<div
|
||||
className={`mt-1 text-left terminal-content ${
|
||||
entry.command.startsWith("ls")
|
||||
? "command-output-ls"
|
||||
: "whitespace-pre-line"
|
||||
}`}
|
||||
dangerouslySetInnerHTML={{ __html: entry.output }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
executeCommand();
|
||||
}}
|
||||
className="w-full"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<span className="text-terminal-prompt mr-2 flex items-center">
|
||||
<span className="text-green-400">guest</span>
|
||||
<span className="text-gray-500">@</span>
|
||||
<span className="text-purple-400">npc-shell</span>
|
||||
<span className="text-gray-500">:</span>
|
||||
<span className="text-blue-400">
|
||||
{currentPath === "/home/guest"
|
||||
? "~"
|
||||
: currentPath.replace("/home/guest", "~")}
|
||||
</span>
|
||||
<span className="text-yellow-500">$</span>
|
||||
</span>
|
||||
<input
|
||||
ref={inputRef}
|
||||
className="bg-transparent text-terminal-command outline-none border-none flex-1 caret-purple-400"
|
||||
type="text"
|
||||
value={inputValue}
|
||||
onChange={(e) => setInputValue(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
autoComplete="off"
|
||||
spellCheck="false"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
189
src/data/filesystem-copy.js
Normal file
@ -0,0 +1,189 @@
|
||||
// Virtual file system structure
|
||||
export const fileSystem = {
|
||||
"/": {
|
||||
type: "directory",
|
||||
children: {
|
||||
home: {
|
||||
type: "directory",
|
||||
children: {
|
||||
guest: {
|
||||
type: "directory",
|
||||
children: {
|
||||
"welcome.txt": {
|
||||
type: "file",
|
||||
content:
|
||||
"Welcome to my interactive terminal portfolio!\n\nType 'help' to see available commands and explore the filesystem to learn more about me.\n\nTry commands like 'ls', 'cd projects', and 'cat about.txt'.",
|
||||
},
|
||||
"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.",
|
||||
},
|
||||
".secret": {
|
||||
type: "file",
|
||||
content:
|
||||
"You found a secret file! Nice terminal skills. 🔍\n\nHere's a fun fact: The first computer bug was an actual bug - a moth found trapped in a Harvard Mark II computer in 1947.",
|
||||
},
|
||||
projects: {
|
||||
type: "directory",
|
||||
children: {
|
||||
"terminal-portfolio.txt": {
|
||||
type: "file",
|
||||
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",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
skills: {
|
||||
type: "directory",
|
||||
children: {
|
||||
"frontend.txt": {
|
||||
type: "file",
|
||||
content:
|
||||
"Frontend Skills\n--------------\n\n- HTML5, CSS3, JavaScript\n- React, Vue.js\n- Tailwind CSS, SASS\n- TypeScript\n- Responsive Design\n- Webpack, Vite",
|
||||
},
|
||||
"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",
|
||||
},
|
||||
"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",
|
||||
},
|
||||
},
|
||||
},
|
||||
contact: {
|
||||
type: "directory",
|
||||
children: {
|
||||
"email.txt": {
|
||||
type: "file",
|
||||
content: "Email: example@domain.com",
|
||||
},
|
||||
"github.txt": {
|
||||
type: "file",
|
||||
content: "GitHub: https://github.com/yourusername",
|
||||
},
|
||||
"linkedin.txt": {
|
||||
type: "file",
|
||||
content: "LinkedIn: https://linkedin.com/in/yourprofile",
|
||||
},
|
||||
},
|
||||
},
|
||||
images: {
|
||||
type: "directory",
|
||||
children: {
|
||||
"profile.jpg": {
|
||||
type: "image",
|
||||
src: "/images/profile.jpg",
|
||||
description: "My profile picture",
|
||||
},
|
||||
"blahaj.png": {
|
||||
type: "image",
|
||||
src: "/images/blahaj.png",
|
||||
description: "My coding buddy Blahaj",
|
||||
},
|
||||
},
|
||||
},
|
||||
blog: {
|
||||
type: "directory",
|
||||
children: {
|
||||
"why-linux.txt": {
|
||||
type: "file",
|
||||
content:
|
||||
"Why I Love Linux\n---------------\n\nLinux offers freedom, customization, and powerful command-line tools that make development a joy.\n\nNo forced updates, better performance, and the satisfaction of understanding what your computer is actually doing.\n\nPlus, it's a great conversation starter at parties... with other developers.",
|
||||
},
|
||||
"terminal-tricks.txt": {
|
||||
type: "file",
|
||||
content:
|
||||
"Terminal Tricks\n--------------\n\n1. Use Ctrl+R to search command history\n2. Create aliases for common commands\n3. Master tab completion\n4. Learn to pipe commands together\n5. Use screen or tmux for multiple terminal sessions",
|
||||
},
|
||||
"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.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
bin: {
|
||||
type: "directory",
|
||||
children: {
|
||||
ls: {
|
||||
type: "file",
|
||||
content:
|
||||
"This is a simulated 'ls' binary. In a real Linux system, this would be an executable file.",
|
||||
},
|
||||
cat: {
|
||||
type: "file",
|
||||
content:
|
||||
"This is a simulated 'cat' binary. In a real Linux system, this would be an executable file.",
|
||||
},
|
||||
cd: {
|
||||
type: "file",
|
||||
content:
|
||||
"This is a simulated 'cd' binary. In a real Linux system, this would be a shell builtin.",
|
||||
},
|
||||
},
|
||||
},
|
||||
usr: {
|
||||
type: "directory",
|
||||
children: {
|
||||
share: {
|
||||
type: "directory",
|
||||
children: {
|
||||
doc: {
|
||||
type: "directory",
|
||||
children: {
|
||||
"terminal-help.txt": {
|
||||
type: "file",
|
||||
content:
|
||||
"Terminal Help\n------------\n\nCommands available:\n- ls: List directory contents\n- cd: Change directory\n- cat: Display file contents\n- pwd: Print working directory\n- help: Display help information\n- clear: Clear the terminal screen\n- view: Display an image file",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
npc: {
|
||||
type: "directory",
|
||||
children: {
|
||||
"passwords.txt": {
|
||||
type: "file",
|
||||
content: "Dont go through my fucking passwords you moron.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
239
src/data/filesystem.js
Normal file
@ -0,0 +1,239 @@
|
||||
// Virtual file system structure
|
||||
export const fileSystem = {
|
||||
"/": {
|
||||
type: "directory",
|
||||
children: {
|
||||
home: {
|
||||
type: "directory",
|
||||
children: {
|
||||
guest: {
|
||||
type: "directory",
|
||||
children: {
|
||||
"welcome.txt": {
|
||||
type: "file",
|
||||
content:
|
||||
"Welcome to my interactive terminal portfolio!\n\nType 'help' to see available commands and explore the filesystem to learn more about me.\n\nTry commands like 'ls', 'cd projects', and 'cat about.txt'.",
|
||||
},
|
||||
"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.",
|
||||
},
|
||||
".secret": {
|
||||
type: "file",
|
||||
content:
|
||||
"You found a secret file! Nice terminal skills. 🔍\n\nHere's a fun fact: The first computer bug was an actual bug - a moth found trapped in a Harvard Mark II computer in 1947.",
|
||||
},
|
||||
projects: {
|
||||
type: "directory",
|
||||
children: {
|
||||
"terminal-portfolio.txt": {
|
||||
type: "file",
|
||||
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",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
skills: {
|
||||
type: "directory",
|
||||
children: {
|
||||
"frontend.txt": {
|
||||
type: "file",
|
||||
content:
|
||||
"Frontend Skills\n--------------\n\n- HTML5, CSS3, JavaScript\n- React, Vue.js\n- Tailwind CSS, SASS\n- TypeScript\n- Responsive Design\n- Webpack, Vite",
|
||||
},
|
||||
"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",
|
||||
},
|
||||
"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",
|
||||
},
|
||||
},
|
||||
},
|
||||
contact: {
|
||||
type: "directory",
|
||||
children: {
|
||||
"email.txt": {
|
||||
type: "file",
|
||||
content: "Email: example@domain.com",
|
||||
},
|
||||
"github.txt": {
|
||||
type: "file",
|
||||
content: "GitHub: https://github.com/yourusername",
|
||||
},
|
||||
"linkedin.txt": {
|
||||
type: "file",
|
||||
content: "LinkedIn: https://linkedin.com/in/yourprofile",
|
||||
},
|
||||
},
|
||||
},
|
||||
images: {
|
||||
type: "directory",
|
||||
children: {
|
||||
"tux.webp": {
|
||||
type: "image",
|
||||
src: "/bg-ims/tux-large.webp",
|
||||
description: "OMG, TUX!",
|
||||
},
|
||||
"kitty.png": {
|
||||
type: "image",
|
||||
src: "/bg-ims/kitty.png",
|
||||
description: "Kitty cat",
|
||||
},
|
||||
"bash-logo.png": {
|
||||
type: "image",
|
||||
src: "/bg-ims/bash-logo.png",
|
||||
description: "OMG, BASH!",
|
||||
},
|
||||
"nixoslogo.png": {
|
||||
type: "image",
|
||||
src: "/bg-ims/nixoslogo.png",
|
||||
description: "OMG, NIXOS!",
|
||||
},
|
||||
"Archlinux_600x600.png": {
|
||||
type: "image",
|
||||
src: "/bg-ims/Archlinux_600x600.png",
|
||||
description: "Arch Linux logo",
|
||||
},
|
||||
"blahaj.png": {
|
||||
type: "image",
|
||||
src: "/profile.jpg",
|
||||
description: "OMG, BLAHAJ!",
|
||||
},
|
||||
},
|
||||
},
|
||||
blog: {
|
||||
type: "directory",
|
||||
children: {
|
||||
"why-linux.txt": {
|
||||
type: "file",
|
||||
content:
|
||||
"Why I Love Linux\n---------------\n\nLinux offers freedom, customization, and powerful command-line tools that make development a joy.\n\nNo forced updates, better performance, and the satisfaction of understanding what your computer is actually doing.\n\nPlus, it's a great conversation starter at parties... with other developers.",
|
||||
},
|
||||
"terminal-tricks.txt": {
|
||||
type: "file",
|
||||
content:
|
||||
"Terminal Tricks\n--------------\n\n1. Use Ctrl+R to search command history\n2. Create aliases for common commands\n3. Master tab completion\n4. Learn to pipe commands together\n5. Use screen or tmux for multiple terminal sessions",
|
||||
},
|
||||
"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.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
npc: {
|
||||
type: "directory",
|
||||
children: {
|
||||
"passwords.txt": {
|
||||
type: "file",
|
||||
content: "Dont go through my fucking passwords you moron.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
bin: {
|
||||
type: "directory",
|
||||
children: {
|
||||
ls: {
|
||||
type: "file",
|
||||
content:
|
||||
"This is a simulated 'ls' binary. In a real Linux system, this would be an executable file.",
|
||||
},
|
||||
cat: {
|
||||
type: "file",
|
||||
content:
|
||||
"This is a simulated 'cat' binary. In a real Linux system, this would be an executable file.",
|
||||
},
|
||||
cd: {
|
||||
type: "file",
|
||||
content:
|
||||
"This is a simulated 'cd' binary. In a real Linux system, this would be a shell builtin.",
|
||||
},
|
||||
},
|
||||
},
|
||||
usr: {
|
||||
type: "directory",
|
||||
children: {
|
||||
share: {
|
||||
type: "directory",
|
||||
children: {
|
||||
doc: {
|
||||
type: "directory",
|
||||
children: {
|
||||
"terminal-help.txt": {
|
||||
type: "file",
|
||||
content:
|
||||
"Terminal Help\n------------\n\nCommands available:\n- ls: List directory contents\n- cd: Change directory\n- cat: Display file contents\n- pwd: Print working directory\n- help: Display help information\n- clear: Clear the terminal screen\n- view: Display an image file",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"bg-ims": {
|
||||
type: "directory",
|
||||
children: {
|
||||
"tux-large.webp": {
|
||||
type: "image",
|
||||
src: "/bg-ims/tux-large.webp",
|
||||
description: "Tux - The Linux mascot",
|
||||
},
|
||||
"kitty.png": {
|
||||
type: "image",
|
||||
src: "/bg-ims/kitty.png",
|
||||
description: "Kitty terminal emulator logo",
|
||||
},
|
||||
"bash-logo.png": {
|
||||
type: "image",
|
||||
src: "/bg-ims/bash-logo.png",
|
||||
description: "Bash shell logo",
|
||||
},
|
||||
"nixoslogo.png": {
|
||||
type: "image",
|
||||
src: "/bg-ims/nixoslogo.png",
|
||||
description: "NixOS logo",
|
||||
},
|
||||
"Archlinux_600x600.png": {
|
||||
type: "image",
|
||||
src: "/bg-ims/Archlinux_600x600.png",
|
||||
description: "Arch Linux logo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
189
src/data/filesystem2.js
Normal file
@ -0,0 +1,189 @@
|
||||
// Virtual file system structure
|
||||
export const fileSystem = {
|
||||
"/": {
|
||||
type: "directory",
|
||||
children: {
|
||||
home: {
|
||||
type: "directory",
|
||||
children: {
|
||||
guest: {
|
||||
type: "directory",
|
||||
children: {
|
||||
"welcome.txt": {
|
||||
type: "file",
|
||||
content:
|
||||
"Welcome to my interactive terminal portfolio!\n\nType 'help' to see available commands and explore the filesystem to learn more about me.\n\nTry commands like 'ls', 'cd projects', and 'cat about.txt'.",
|
||||
},
|
||||
"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.",
|
||||
},
|
||||
".secret": {
|
||||
type: "file",
|
||||
content:
|
||||
"You found a secret file! Nice terminal skills. 🔍\n\nHere's a fun fact: The first computer bug was an actual bug - a moth found trapped in a Harvard Mark II computer in 1947.",
|
||||
},
|
||||
projects: {
|
||||
type: "directory",
|
||||
children: {
|
||||
"terminal-portfolio.txt": {
|
||||
type: "file",
|
||||
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",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
skills: {
|
||||
type: "directory",
|
||||
children: {
|
||||
"frontend.txt": {
|
||||
type: "file",
|
||||
content:
|
||||
"Frontend Skills\n--------------\n\n- HTML5, CSS3, JavaScript\n- React, Vue.js\n- Tailwind CSS, SASS\n- TypeScript\n- Responsive Design\n- Webpack, Vite",
|
||||
},
|
||||
"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",
|
||||
},
|
||||
"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",
|
||||
},
|
||||
},
|
||||
},
|
||||
contact: {
|
||||
type: "directory",
|
||||
children: {
|
||||
"email.txt": {
|
||||
type: "file",
|
||||
content: "Email: example@domain.com",
|
||||
},
|
||||
"github.txt": {
|
||||
type: "file",
|
||||
content: "GitHub: https://github.com/yourusername",
|
||||
},
|
||||
"linkedin.txt": {
|
||||
type: "file",
|
||||
content: "LinkedIn: https://linkedin.com/in/yourprofile",
|
||||
},
|
||||
},
|
||||
},
|
||||
images: {
|
||||
type: "directory",
|
||||
children: {
|
||||
"profile.jpg": {
|
||||
type: "image",
|
||||
src: "/images/profile.jpg",
|
||||
description: "My profile picture",
|
||||
},
|
||||
"blahaj.png": {
|
||||
type: "image",
|
||||
src: "/images/blahaj.png",
|
||||
description: "My coding buddy Blahaj",
|
||||
},
|
||||
},
|
||||
},
|
||||
blog: {
|
||||
type: "directory",
|
||||
children: {
|
||||
"why-linux.txt": {
|
||||
type: "file",
|
||||
content:
|
||||
"Why I Love Linux\n---------------\n\nLinux offers freedom, customization, and powerful command-line tools that make development a joy.\n\nNo forced updates, better performance, and the satisfaction of understanding what your computer is actually doing.\n\nPlus, it's a great conversation starter at parties... with other developers.",
|
||||
},
|
||||
"terminal-tricks.txt": {
|
||||
type: "file",
|
||||
content:
|
||||
"Terminal Tricks\n--------------\n\n1. Use Ctrl+R to search command history\n2. Create aliases for common commands\n3. Master tab completion\n4. Learn to pipe commands together\n5. Use screen or tmux for multiple terminal sessions",
|
||||
},
|
||||
"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.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
npc: {
|
||||
type: "directory",
|
||||
children: {
|
||||
"passwords.txt": {
|
||||
type: "file",
|
||||
content: "Dont go through my fucking passwords you moron.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
bin: {
|
||||
type: "directory",
|
||||
children: {
|
||||
ls: {
|
||||
type: "file",
|
||||
content:
|
||||
"This is a simulated 'ls' binary. In a real Linux system, this would be an executable file.",
|
||||
},
|
||||
cat: {
|
||||
type: "file",
|
||||
content:
|
||||
"This is a simulated 'cat' binary. In a real Linux system, this would be an executable file.",
|
||||
},
|
||||
cd: {
|
||||
type: "file",
|
||||
content:
|
||||
"This is a simulated 'cd' binary. In a real Linux system, this would be a shell builtin.",
|
||||
},
|
||||
},
|
||||
},
|
||||
usr: {
|
||||
type: "directory",
|
||||
children: {
|
||||
share: {
|
||||
type: "directory",
|
||||
children: {
|
||||
doc: {
|
||||
type: "directory",
|
||||
children: {
|
||||
"terminal-help.txt": {
|
||||
type: "file",
|
||||
content:
|
||||
"Terminal Help\n------------\n\nCommands available:\n- ls: List directory contents\n- cd: Change directory\n- cat: Display file contents\n- pwd: Print working directory\n- help: Display help information\n- clear: Clear the terminal screen\n- view: Display an image file",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
251
src/index.css
Normal file
@ -0,0 +1,251 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@font-face {
|
||||
font-family: "CloisterBlack";
|
||||
src: url("/CloisterBlack.ttf") format("truetype");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.gothic-text {
|
||||
font-family: "CloisterBlack", cursive;
|
||||
text-shadow: 0 0 10px rgba(128, 0, 128, 0.8), 0 0 20px rgba(128, 0, 128, 0.5);
|
||||
}
|
||||
|
||||
:root {
|
||||
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow-x: hidden;
|
||||
min-width: 100vw;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: #1a1a1a;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
}
|
||||
|
||||
/* Custom Scrollbar Styles */
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-track {
|
||||
background: rgba(35, 35, 35, 0.5);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
background: rgba(128, 90, 213, 0.7);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(168, 130, 253, 0.8);
|
||||
}
|
||||
|
||||
/* For Firefox */
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(128, 90, 213, 0.7) rgba(35, 35, 35, 0.5);
|
||||
}
|
||||
|
||||
#root {
|
||||
max-width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* Add this to your existing CSS */
|
||||
.command-output-ls {
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
/* Add to your index.css */
|
||||
.terminal-image {
|
||||
max-width: 100%;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
.terminal-window:hover {
|
||||
box-shadow: 0 0 25px rgba(128, 0, 255, 0.6), 0 0 40px rgba(128, 0, 255, 0.4);
|
||||
}
|
||||
|
||||
/* Custom scrollbar */
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-track {
|
||||
background: rgba(30, 30, 30, 0.5);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
background: rgba(138, 75, 215, 0.7);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(158, 95, 235, 0.9);
|
||||
}
|
||||
|
||||
/* Command output styling */
|
||||
.command-output-ls {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.file-item {
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.file-item:hover {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.file-item.directory {
|
||||
color: #7dd3fc;
|
||||
}
|
||||
|
||||
.file-item.file {
|
||||
color: #d1d5db;
|
||||
}
|
||||
|
||||
.file-item.image {
|
||||
color: #fca5a5;
|
||||
}
|
||||
|
||||
/* Terminal command highlighting */
|
||||
.text-terminal-command {
|
||||
color: #f9fafb;
|
||||
}
|
||||
|
||||
.text-terminal-prompt {
|
||||
color: #a5b4fc;
|
||||
}
|
||||
|
||||
/* Subtle typing animation for cursor */
|
||||
@keyframes blink {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
input.caret-purple-400 {
|
||||
caret-color: #c084fc;
|
||||
animation: blink 1.2s infinite;
|
||||
}
|
||||
|
||||
/* Ensure text wrapping for command outputs */
|
||||
.whitespace-pre-line {
|
||||
white-space: pre-line;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* Handle ASCII art properly */
|
||||
pre {
|
||||
max-width: 100%;
|
||||
overflow-x: hidden;
|
||||
font-size: 0.8em; /* Reduce font size for ASCII art */
|
||||
}
|
||||
|
||||
/* Ensure all terminal content wraps properly */
|
||||
.terminal-content * {
|
||||
max-width: 100%;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
/* Ensure command output wraps properly */
|
||||
.command-output-ls {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
gap: 8px;
|
||||
max-width: 100%;
|
||||
}
|
||||
10
src/main.jsx
Normal file
@ -0,0 +1,10 @@
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import './index.css'
|
||||
import App from './App.jsx'
|
||||
|
||||
createRoot(document.getElementById('root')).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
)
|
||||
39
tailwind.config.js
Normal file
@ -0,0 +1,39 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
terminal: {
|
||||
bg: "#232323",
|
||||
text: "#e6edf3",
|
||||
prompt: "#f97583",
|
||||
command: "#79c0ff",
|
||||
result: "#d2a8ff",
|
||||
error: "#f97583",
|
||||
accent: "#58a6ff",
|
||||
},
|
||||
},
|
||||
fontFamily: {
|
||||
mono: [
|
||||
"Fira Code",
|
||||
"JetBrains Mono",
|
||||
"Consolas",
|
||||
"Menlo",
|
||||
"Monaco",
|
||||
"monospace",
|
||||
],
|
||||
},
|
||||
animation: {
|
||||
"cursor-blink": "blink 1s step-end infinite",
|
||||
},
|
||||
keyframes: {
|
||||
blink: {
|
||||
"0%, 100%": { opacity: "1" },
|
||||
"50%": { opacity: "0" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
10
vite.config.js
Normal file
@ -0,0 +1,10 @@
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
port: 3000,
|
||||
},
|
||||
});
|
||||