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,
|
||||||
|
},
|
||||||
|
});
|
||||||