Pretty Ui Update
This commit is contained in:
@@ -26,9 +26,6 @@
|
||||
|
||||
<meta name="robots" content="index, follow" />
|
||||
<link rel="canonical" href="https://space.reversed.dev" />
|
||||
|
||||
<script defer src="https://not-a-tracker.reversed.dev/script.js"
|
||||
data-website-id="7d28af45-d984-428e-af79-fb4dc7e91492"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
@@ -10,9 +10,12 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@danielgtmn/umami-react": "^1.1.6",
|
||||
"@tailwindcss/vite": "^4.1.17",
|
||||
"lucide-react": "^0.577.0",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"react-router-dom": "^7.13.1",
|
||||
"tailwindcss": "^4.1.17"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
72
pnpm-lock.yaml
generated
72
pnpm-lock.yaml
generated
@@ -8,15 +8,24 @@ importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@danielgtmn/umami-react':
|
||||
specifier: ^1.1.6
|
||||
version: 1.1.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
'@tailwindcss/vite':
|
||||
specifier: ^4.1.17
|
||||
version: 4.1.17(rolldown-vite@7.2.5(@types/node@24.10.1)(jiti@2.6.1))
|
||||
lucide-react:
|
||||
specifier: ^0.577.0
|
||||
version: 0.577.0(react@19.2.0)
|
||||
react:
|
||||
specifier: ^19.2.0
|
||||
version: 19.2.0
|
||||
react-dom:
|
||||
specifier: ^19.2.0
|
||||
version: 19.2.0(react@19.2.0)
|
||||
react-router-dom:
|
||||
specifier: ^7.13.1
|
||||
version: 7.13.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
tailwindcss:
|
||||
specifier: ^4.1.17
|
||||
version: 4.1.17
|
||||
@@ -143,6 +152,13 @@ packages:
|
||||
resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@danielgtmn/umami-react@1.1.6':
|
||||
resolution: {integrity: sha512-vNk1Q8JOjbE1wx1vM5u2E6jPIDSrZJM/mg++ButiVtD56LNENb1cHvWKcQqC9/camx90PrmdKkWaYhVwU8fCEg==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
peerDependencies:
|
||||
react: ^18.2.0 || ^19.0.0
|
||||
react-dom: ^18.2.0 || ^19.0.0
|
||||
|
||||
'@emnapi/core@1.7.1':
|
||||
resolution: {integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==}
|
||||
|
||||
@@ -586,6 +602,10 @@ packages:
|
||||
convert-source-map@2.0.0:
|
||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
||||
|
||||
cookie@1.1.1:
|
||||
resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||
engines: {node: '>= 8'}
|
||||
@@ -910,6 +930,11 @@ packages:
|
||||
lru-cache@5.1.1:
|
||||
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
||||
|
||||
lucide-react@0.577.0:
|
||||
resolution: {integrity: sha512-4LjoFv2eEPwYDPg/CUdBJQSDfPyzXCRrVW1X7jrx/trgxnxkHFjnVZINbzvzxjN70dxychOfg+FTYwBiS3pQ5A==}
|
||||
peerDependencies:
|
||||
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
magic-string@0.30.21:
|
||||
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||
|
||||
@@ -1001,6 +1026,23 @@ packages:
|
||||
resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
react-router-dom@7.13.1:
|
||||
resolution: {integrity: sha512-UJnV3Rxc5TgUPJt2KJpo1Jpy0OKQr0AjgbZzBFjaPJcFOb2Y8jA5H3LT8HUJAiRLlWrEXWHbF1Z4SCZaQjWDHw==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
peerDependencies:
|
||||
react: '>=18'
|
||||
react-dom: '>=18'
|
||||
|
||||
react-router@7.13.1:
|
||||
resolution: {integrity: sha512-td+xP4X2/6BJvZoX6xw++A2DdEi++YypA69bJUV5oVvqf6/9/9nNlD70YO1e9d3MyamJEBQFEzk6mbfDYbqrSA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
peerDependencies:
|
||||
react: '>=18'
|
||||
react-dom: '>=18'
|
||||
peerDependenciesMeta:
|
||||
react-dom:
|
||||
optional: true
|
||||
|
||||
react@19.2.0:
|
||||
resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -1073,6 +1115,9 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
set-cookie-parser@2.7.2:
|
||||
resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==}
|
||||
|
||||
shebang-command@2.0.0:
|
||||
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -1284,6 +1329,11 @@ snapshots:
|
||||
'@babel/helper-string-parser': 7.27.1
|
||||
'@babel/helper-validator-identifier': 7.28.5
|
||||
|
||||
'@danielgtmn/umami-react@1.1.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
|
||||
dependencies:
|
||||
react: 19.2.0
|
||||
react-dom: 19.2.0(react@19.2.0)
|
||||
|
||||
'@emnapi/core@1.7.1':
|
||||
dependencies:
|
||||
'@emnapi/wasi-threads': 1.1.0
|
||||
@@ -1725,6 +1775,8 @@ snapshots:
|
||||
|
||||
convert-source-map@2.0.0: {}
|
||||
|
||||
cookie@1.1.1: {}
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
@@ -2013,6 +2065,10 @@ snapshots:
|
||||
dependencies:
|
||||
yallist: 3.1.1
|
||||
|
||||
lucide-react@0.577.0(react@19.2.0):
|
||||
dependencies:
|
||||
react: 19.2.0
|
||||
|
||||
magic-string@0.30.21:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
@@ -2090,6 +2146,20 @@ snapshots:
|
||||
|
||||
react-refresh@0.18.0: {}
|
||||
|
||||
react-router-dom@7.13.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
|
||||
dependencies:
|
||||
react: 19.2.0
|
||||
react-dom: 19.2.0(react@19.2.0)
|
||||
react-router: 7.13.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
|
||||
react-router@7.13.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
|
||||
dependencies:
|
||||
cookie: 1.1.1
|
||||
react: 19.2.0
|
||||
set-cookie-parser: 2.7.2
|
||||
optionalDependencies:
|
||||
react-dom: 19.2.0(react@19.2.0)
|
||||
|
||||
react@19.2.0: {}
|
||||
|
||||
resolve-from@4.0.0: {}
|
||||
@@ -2140,6 +2210,8 @@ snapshots:
|
||||
|
||||
semver@7.7.3: {}
|
||||
|
||||
set-cookie-parser@2.7.2: {}
|
||||
|
||||
shebang-command@2.0.0:
|
||||
dependencies:
|
||||
shebang-regex: 3.0.0
|
||||
|
||||
272
src/App.tsx
272
src/App.tsx
@@ -1,23 +1,22 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
|
||||
import UmamiAnalytics from "@danielgtmn/umami-react";
|
||||
import type { Experience, MiniProject, Project } from "./types";
|
||||
import { MiniProjectModal } from "./components/MiniProjectModal";
|
||||
import { ExperienceModal } from "./components/ExperienceModal";
|
||||
import { ProjectCard } from "./components/ProjectCard";
|
||||
import { MiniProjectCard } from "./components/MiniProjectCard";
|
||||
import { ExperienceCard } from "./components/ExperienceCard";
|
||||
import { getTypeIcon } from "./components/utils";
|
||||
import { Hero } from "./sections/Hero";
|
||||
import { About } from "./sections/About";
|
||||
import { Goals } from "./sections/Goals";
|
||||
import { InternshipSection } from "./sections/InternshipSection";
|
||||
import { Contact } from "./sections/Contact";
|
||||
import { Navbar } from "./components/Navbar";
|
||||
import { Footer } from "./sections/Footer";
|
||||
|
||||
// Pages
|
||||
import { Home } from "./pages/Home";
|
||||
import { About } from "./pages/AboutPage";
|
||||
import { Contact } from "./pages/ContactPage";
|
||||
|
||||
function App() {
|
||||
const [status, setStatus] = useState("");
|
||||
const [borderStatus, setBorderStatus] = useState("border-gray-700");
|
||||
const [glowColor, setGlowColor] = useState("rgba(55, 65, 81, 0.5)");
|
||||
const [statusMessage, setStatusMessage] = useState("[🔃🔃🔃]");
|
||||
const [statusMessage, setStatusMessage] = useState("[??????]");
|
||||
const [messageIndex, setMessageIndex] = useState(0);
|
||||
const [displayMessage, setDisplayMessage] = useState("");
|
||||
const [isScrambling, setIsScrambling] = useState(false);
|
||||
@@ -33,30 +32,28 @@ function App() {
|
||||
|
||||
const oldUsernames = [
|
||||
"getspaced (ingame)",
|
||||
"Space² (alternative)",
|
||||
"Space<EFBFBD> (alternative)",
|
||||
"Space-Banane (2022-2024)",
|
||||
];
|
||||
|
||||
const rotatingMessages = [
|
||||
"Yelling at the compiler ⁉️",
|
||||
"Shipping bug fixes 📦",
|
||||
"Waiting for CI ✅",
|
||||
"No debugger attached 🐞",
|
||||
"Yelling at Gemini 🤖",
|
||||
"Writing Readme files 📝",
|
||||
"Collecting Spotify hours 🎵",
|
||||
"Downloading 10 bajillion dependencies 📥",
|
||||
"Fighting with package managers 📦",
|
||||
"Considering nuking python from orbit 🐍",
|
||||
"Why is uv so cool? 🧊",
|
||||
"I'm hungry 🍔",
|
||||
"I swear this worked on my machine 🖥️",
|
||||
"Pulling 10GB of docker images 🐳",
|
||||
"Yelling at Claude",
|
||||
"Shipping bugs",
|
||||
"Compiling typescript files... Please wait.",
|
||||
"Waiting for CI, this might take a while...",
|
||||
"No debugger attached, but I'm sure it works ?",
|
||||
"Seems fine, must be a problem with your machine lol",
|
||||
"Waiting for pip, this might take a lifetime...",
|
||||
"Collecting Spotify hours, this might take a while...",
|
||||
"Fighting with go modules, i lost",
|
||||
"Nuking Python for the 50th time, it's winning",
|
||||
"Why is uv so cool?",
|
||||
"Pulling 20-ish GB of node modules, see you next year"
|
||||
];
|
||||
|
||||
const characters =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-_=+[]{}|;:',.<>?/`~" +
|
||||
"🧱⁉️🧪📦🦀✅🚧🐞🤖🔄🥹🧹🎵";
|
||||
"?????????????????????????";
|
||||
|
||||
useEffect(() => {
|
||||
if (status === "online") {
|
||||
@@ -78,7 +75,7 @@ function App() {
|
||||
} else {
|
||||
setBorderStatus("border-gray-700");
|
||||
setGlowColor("rgba(55, 65, 81, 0.5)");
|
||||
setStatusMessage("[🔃🔃🔃]");
|
||||
setStatusMessage("[??????]");
|
||||
}
|
||||
}, [status]);
|
||||
|
||||
@@ -123,8 +120,8 @@ function App() {
|
||||
setIsScrambling(false);
|
||||
}
|
||||
|
||||
iteration += 1 / 3;
|
||||
}, 25);
|
||||
iteration += 1; // Faster: increment by 1 instead of 1/3
|
||||
}, 15); // Faster: reduce interval to 15ms
|
||||
|
||||
return () => clearInterval(scrambleInterval);
|
||||
} else {
|
||||
@@ -169,77 +166,61 @@ function App() {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="relative min-h-screen bg-black text-white selection:bg-purple-500/30">
|
||||
{/* Background Grid */}
|
||||
<div className="fixed inset-0 z-0 pointer-events-none">
|
||||
<div
|
||||
className="absolute inset-0 blur-sm"
|
||||
style={{
|
||||
backgroundImage:
|
||||
"linear-gradient(#ffffff 1px, transparent 1px), linear-gradient(90deg, #ffffff 1px, transparent 1px)",
|
||||
backgroundSize: "30px 30px",
|
||||
maskImage:
|
||||
"radial-gradient(circle at center, black 40%, transparent 80%)",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Router>
|
||||
<UmamiAnalytics
|
||||
websiteId="7d28af45-d984-428e-af79-fb4dc7e91492"
|
||||
url="https://not-a-tracker.reversed.dev/script.js"
|
||||
/>
|
||||
<div className="relative min-h-screen bg-[#020205] text-white selection:bg-blue-500/30 font-sans overflow-x-hidden">
|
||||
{/* Modern Background */}
|
||||
<div className="fixed inset-0 z-0 pointer-events-none">
|
||||
{/* Main Gradient Glows */}
|
||||
<div className="absolute top-[-10%] left-[-10%] w-[40%] h-[40%] rounded-full bg-blue-600/10 blur-[120px] animate-pulse"></div>
|
||||
<div className="absolute bottom-[-10%] right-[-10%] w-[40%] h-[40%] rounded-full bg-purple-600/10 blur-[120px] animate-pulse" style={{ animationDelay: "1s" }}></div>
|
||||
|
||||
{/* Subtle Grid Pattern */}
|
||||
<div
|
||||
className="absolute inset-0 opacity-[0.03]"
|
||||
style={{
|
||||
backgroundImage: `linear-gradient(#fff 1px, transparent 1px), linear-gradient(90deg, #fff 1px, transparent 1px)`,
|
||||
backgroundSize: "40px 40px"
|
||||
}}
|
||||
></div>
|
||||
|
||||
{/* Noise Overlay */}
|
||||
<div className="absolute inset-0 opacity-[0.02] mix-blend-overlay pointer-events-none bg-[url('https://grainy-gradients.vercel.app/noise.svg')]"></div>
|
||||
</div>
|
||||
|
||||
<main className="relative z-10 flex flex-col items-center px-6 py-20 mx-auto max-w-5xl gap-24">
|
||||
<Hero
|
||||
glowColor={glowColor}
|
||||
borderStatus={borderStatus}
|
||||
displayMessage={displayMessage}
|
||||
rotatingMessages={rotatingMessages}
|
||||
statusMessage={statusMessage}
|
||||
showOldNames={showOldNames}
|
||||
setShowOldNames={setShowOldNames}
|
||||
oldUsernames={oldUsernames}
|
||||
/>
|
||||
<Navbar />
|
||||
|
||||
<About />
|
||||
<main className="relative z-10 pt-32 pb-20 px-6 mx-auto max-w-6xl">
|
||||
<Routes>
|
||||
<Route
|
||||
path="/"
|
||||
element={
|
||||
<Home
|
||||
glowColor={glowColor}
|
||||
borderStatus={borderStatus}
|
||||
displayMessage={displayMessage}
|
||||
rotatingMessages={rotatingMessages}
|
||||
statusMessage={statusMessage}
|
||||
showOldNames={showOldNames}
|
||||
setShowOldNames={setShowOldNames}
|
||||
oldUsernames={oldUsernames}
|
||||
projects={projects}
|
||||
miniProjects={miniProjects}
|
||||
setSelectedMiniProject={setSelectedMiniProject}
|
||||
experiences={experiences}
|
||||
setSelectedExperience={setSelectedExperience}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route path="/about" element={<About />} />
|
||||
<Route path="/contact" element={<Contact />} />
|
||||
</Routes>
|
||||
</main>
|
||||
|
||||
<Goals />
|
||||
|
||||
{/* Projects Section */}
|
||||
<section className="w-full space-y-12">
|
||||
<div className="text-center space-y-2">
|
||||
<h2 className="text-4xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-purple-400 via-pink-500 to-orange-400">
|
||||
Favourite Projects
|
||||
</h2>
|
||||
<p className="text-gray-400 max-w-2xl mx-auto">
|
||||
Personal favourites, projects that I'm proud of, or just things I
|
||||
find fun to show off.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{projects.map((project, index) => (
|
||||
<ProjectCard key={index} project={project} />
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Mini Projects Section */}
|
||||
<section className="w-full space-y-12">
|
||||
<div className="text-center space-y-2">
|
||||
<h2 className="text-4xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-green-400 via-blue-500 to-purple-500">
|
||||
Mini Projects
|
||||
</h2>
|
||||
<p className="text-gray-400 max-w-2xl mx-auto">
|
||||
Small experiments and fun projects I've built along the way
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{miniProjects.map((project, index) => (
|
||||
<MiniProjectCard
|
||||
key={index}
|
||||
project={project}
|
||||
onClick={() => setSelectedMiniProject(project)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
<Footer />
|
||||
|
||||
{selectedMiniProject && (
|
||||
<MiniProjectModal
|
||||
@@ -254,101 +235,8 @@ function App() {
|
||||
onClose={() => setSelectedExperience(null)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Experience Section */}
|
||||
<section className="w-full space-y-8">
|
||||
<div className="text-center space-y-2">
|
||||
<h2 className="text-4xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-orange-400 via-red-500 to-pink-500">
|
||||
Experience
|
||||
</h2>
|
||||
<p className="text-gray-400 max-w-2xl mx-auto">
|
||||
Technologies, platforms, and roles I've worked with
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="max-w-4xl mx-auto space-y-6">
|
||||
{Object.entries(
|
||||
experiences.reduce(
|
||||
(acc, exp) => {
|
||||
if (!acc[exp.type]) acc[exp.type] = [];
|
||||
acc[exp.type].push(exp);
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, Experience[]>,
|
||||
),
|
||||
).map(([type, items]) => (
|
||||
<div key={type} className="space-y-3">
|
||||
<h3
|
||||
className={`text-lg font-semibold flex items-center gap-2 ${
|
||||
type === "language"
|
||||
? "text-blue-400"
|
||||
: type === "service"
|
||||
? "text-purple-400"
|
||||
: type === "platform"
|
||||
? "text-green-400"
|
||||
: type === "real life experience"
|
||||
? "text-orange-400"
|
||||
: type === "roles"
|
||||
? "text-pink-400"
|
||||
: "text-gray-400"
|
||||
}`}
|
||||
>
|
||||
{getTypeIcon(type as Experience["type"])}{" "}
|
||||
{type.charAt(0).toUpperCase() + type.slice(1)}
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
{items.map((exp, index) => (
|
||||
<ExperienceCard
|
||||
key={index}
|
||||
experience={exp}
|
||||
onClick={() => setSelectedExperience(exp)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<p className="text-center text-gray-400 mt-4">
|
||||
And a bunch of stuff i probably also forgot...
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<InternshipSection />
|
||||
|
||||
<Contact />
|
||||
|
||||
<Footer />
|
||||
</main>
|
||||
|
||||
{/* Floating Island: Bottom Left (only on localhost) */}
|
||||
{typeof window !== "undefined" &&
|
||||
(window.location.hostname === "localhost" ||
|
||||
window.location.hostname === "127.0.0.1") && (
|
||||
<div
|
||||
className="fixed bottom-4 left-4 z-50 flex flex-col items-start gap-2 bg-gradient-to-br from-purple-900/80 to-black/80 border border-purple-500/30 rounded-2xl shadow-lg px-5 py-3 text-sm animate-fade-in"
|
||||
style={{ minWidth: 180 }}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<span role="img" aria-label="island">
|
||||
🏝️
|
||||
</span>
|
||||
<span className="font-semibold text-purple-300">Loaded</span>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1 mt-1">
|
||||
<span className="text-white/90">
|
||||
Projects: {projects.length}
|
||||
</span>
|
||||
<span className="text-white/90">
|
||||
Mini Projects: {miniProjects.length}
|
||||
</span>
|
||||
<span className="text-white/90">
|
||||
Experiences: {experiences.length}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
38
src/components/Navbar.tsx
Normal file
38
src/components/Navbar.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import { useUmami } from "@danielgtmn/umami-react";
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
|
||||
export function Navbar() {
|
||||
const location = useLocation();
|
||||
|
||||
const navItems = [
|
||||
{ label: "Home", path: "/" },
|
||||
{ label: "About", path: "/about" },
|
||||
{ label: "Connect", path: "/contact" },
|
||||
];
|
||||
|
||||
const handleSwitch = (path: string) => {
|
||||
useUmami().track("nav_to_" + path.replaceAll("/", "_"));
|
||||
};
|
||||
|
||||
return (
|
||||
<nav className="fixed top-8 left-1/2 -translate-x-1/2 z-[100] w-[min(90%,400px)]">
|
||||
<div className="bg-black/20 backdrop-blur-xl border border-white/10 rounded-full px-6 py-3 flex items-center justify-between gap-4 shadow-2xl">
|
||||
{navItems.map((item) => (
|
||||
<Link
|
||||
key={item.path}
|
||||
to={item.path}
|
||||
onClick={() => handleSwitch(item.path)}
|
||||
className={`text-sm font-semibold transition-all duration-300 px-4 py-2 rounded-full
|
||||
${
|
||||
location.pathname === item.path
|
||||
? "bg-white text-black scale-105 shadow-xl shadow-white/10"
|
||||
: "text-gray-400 hover:text-white"
|
||||
}`}
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
39
src/pages/AboutPage.tsx
Normal file
39
src/pages/AboutPage.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import { About as AboutSection } from "../sections/About";
|
||||
import { Goals } from "../sections/Goals";
|
||||
|
||||
export function About() {
|
||||
return (
|
||||
<div className="space-y-24 py-12 max-w-4xl mx-auto px-4">
|
||||
<div className="text-center space-y-4">
|
||||
<h1 className="text-4xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-blue-400 to-purple-500">
|
||||
About Me
|
||||
</h1>
|
||||
<p className="text-gray-400 max-w-2xl mx-auto">
|
||||
A little bit about me...
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<AboutSection />
|
||||
|
||||
<Goals />
|
||||
|
||||
<section className="bg-white/5 border border-white/10 rounded-3xl p-8 backdrop-blur-sm">
|
||||
<h3 className="text-2xl font-bold mb-4">Interests Outside of Coding</h3>
|
||||
<ul className="grid grid-cols-1 md:grid-cols-2 gap-4 text-gray-300">
|
||||
<li className="flex items-center gap-2">
|
||||
<span className="text-purple-400">✦</span> 3D Printing & Design
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<span className="text-purple-400">✦</span> Graphical Design
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<span className="text-purple-400">✦</span> Gaming & Hardware
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<span className="text-purple-400">✦</span> Open Source Contribution
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
38
src/pages/ContactPage.tsx
Normal file
38
src/pages/ContactPage.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import { Contact as ContactSection } from "../sections/Contact";
|
||||
|
||||
export function Contact() {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-[70vh] px-4 space-y-12 w-full max-w-4xl mx-auto">
|
||||
<div className="text-center space-y-4 w-full">
|
||||
<h1 className="text-5xl md:text-6xl font-extrabold text-white">
|
||||
Let's <span className="text-transparent bg-clip-text bg-gradient-to-r from-blue-400 to-purple-500">Connect</span>
|
||||
</h1>
|
||||
<p className="text-gray-400 text-xl max-w-xl mx-auto">
|
||||
Whatever you've got in mind, I'm just a few clicks away.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="w-full max-w-3xl">
|
||||
<ContactSection />
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6 w-full max-w-4xl text-center">
|
||||
<div className="bg-white/5 border border-white/10 p-6 rounded-3xl backdrop-blur-sm group hover:border-blue-500/30 transition-all duration-300">
|
||||
<div className="text-3xl mb-4 group-hover:scale-110 transition-transform">📧</div>
|
||||
<h3 className="font-bold mb-1">Email</h3>
|
||||
<p className="text-sm text-gray-500">space@reversed.dev</p>
|
||||
</div>
|
||||
<div className="bg-white/5 border border-white/10 p-6 rounded-3xl backdrop-blur-sm group hover:border-purple-500/30 transition-all duration-300">
|
||||
<div className="text-3xl mb-4 group-hover:scale-110 transition-transform">👾</div>
|
||||
<h3 className="font-bold mb-1">Discord</h3>
|
||||
<p className="text-sm text-gray-500">@getspaced</p>
|
||||
</div>
|
||||
<div className="bg-white/5 border border-white/10 p-6 rounded-3xl backdrop-blur-sm group hover:border-pink-500/30 transition-all duration-300">
|
||||
<div className="text-3xl mb-4 group-hover:scale-110 transition-transform">💻</div>
|
||||
<h3 className="font-bold mb-1">GitHub</h3>
|
||||
<p className="text-sm text-gray-500">github.com/Space-Banane</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
151
src/pages/Home.tsx
Normal file
151
src/pages/Home.tsx
Normal file
@@ -0,0 +1,151 @@
|
||||
import { Hero } from "../sections/Hero";
|
||||
import type { MiniProject, Experience, Project } from "../types";
|
||||
import { ProjectCard } from "../components/ProjectCard";
|
||||
import { MiniProjectCard } from "../components/MiniProjectCard";
|
||||
import { ExperienceCard } from "../components/ExperienceCard";
|
||||
|
||||
interface HomeProps {
|
||||
glowColor: string;
|
||||
borderStatus: string;
|
||||
displayMessage: string;
|
||||
rotatingMessages: string[];
|
||||
statusMessage: string;
|
||||
showOldNames: boolean;
|
||||
setShowOldNames: (show: boolean) => void;
|
||||
oldUsernames: string[];
|
||||
projects: Project[];
|
||||
miniProjects: MiniProject[];
|
||||
setSelectedMiniProject: (p: MiniProject) => void;
|
||||
experiences: Experience[];
|
||||
setSelectedExperience: (e: Experience) => void;
|
||||
}
|
||||
|
||||
export function Home({
|
||||
glowColor,
|
||||
borderStatus,
|
||||
displayMessage,
|
||||
rotatingMessages,
|
||||
statusMessage,
|
||||
showOldNames,
|
||||
setShowOldNames,
|
||||
oldUsernames,
|
||||
projects,
|
||||
miniProjects,
|
||||
setSelectedMiniProject,
|
||||
experiences,
|
||||
setSelectedExperience,
|
||||
}: HomeProps) {
|
||||
const groupedExperiences = experiences.reduce(
|
||||
(acc, exp) => {
|
||||
if (!acc[exp.type]) {
|
||||
acc[exp.type] = [];
|
||||
}
|
||||
acc[exp.type].push(exp);
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, Experience[]>,
|
||||
);
|
||||
|
||||
const rewriteType = (type: string) => {
|
||||
switch (type) {
|
||||
case "language":
|
||||
return "Languages";
|
||||
case "service":
|
||||
return "Services";
|
||||
case "platform":
|
||||
return "Platforms";
|
||||
case "real life experience":
|
||||
return "Real Life Experience";
|
||||
case "roles":
|
||||
return "Roles";
|
||||
default:
|
||||
return type.charAt(0).toUpperCase() + type.slice(1);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-24 pb-20">
|
||||
<Hero
|
||||
glowColor={glowColor}
|
||||
borderStatus={borderStatus}
|
||||
displayMessage={displayMessage}
|
||||
rotatingMessages={rotatingMessages}
|
||||
statusMessage={statusMessage}
|
||||
showOldNames={showOldNames}
|
||||
setShowOldNames={setShowOldNames}
|
||||
oldUsernames={oldUsernames}
|
||||
/>
|
||||
|
||||
<section className="w-full max-w-6xl space-y-12">
|
||||
<div className="text-center space-y-4">
|
||||
<h2 className="text-4xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-blue-400 to-purple-500">
|
||||
Featured Projects
|
||||
</h2>
|
||||
<p className="text-gray-400 max-w-2xl mx-auto">
|
||||
A selection of my personal favorites. Many more on my GitHub.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 px-4">
|
||||
{projects.map((project, index) => (
|
||||
<ProjectCard key={index} project={project} />
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="w-full max-w-6xl space-y-12">
|
||||
<div className="text-center space-y-4">
|
||||
<h2 className="text-4xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-purple-400 to-pink-500">
|
||||
Mini Projects
|
||||
</h2>
|
||||
<p className="text-gray-400 max-w-2xl mx-auto">
|
||||
Small tools, experiments, and fun little things I've built.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4 px-4">
|
||||
{miniProjects.map((mini, index) => (
|
||||
<MiniProjectCard
|
||||
key={index}
|
||||
project={mini}
|
||||
onClick={() => setSelectedMiniProject(mini)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="w-full max-w-6xl space-y-16">
|
||||
<div className="text-center space-y-4">
|
||||
<h2 className="text-4xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-green-400 to-blue-500">
|
||||
Experience
|
||||
</h2>
|
||||
<p className="text-gray-400 max-w-2xl mx-auto">
|
||||
My journey through the tech world so far.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-12 px-4">
|
||||
{Object.entries(groupedExperiences)
|
||||
.sort(([typeA], [typeB]) => typeA.localeCompare(typeB))
|
||||
.map(([type, items]) => (
|
||||
<div key={type} className="space-y-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<h3 className="text-xl font-semibold text-gray-200 capitalize whitespace-nowrap">
|
||||
{rewriteType(type)}
|
||||
</h3>
|
||||
<div className="h-px w-full bg-gradient-to-r from-gray-500/20 via-gray-500/10 to-transparent" />
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{items.map((exp, index) => (
|
||||
<ExperienceCard
|
||||
key={index}
|
||||
experience={exp}
|
||||
onClick={() => setSelectedExperience(exp)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
export function Contact() {
|
||||
return (
|
||||
<section className="w-full max-w-2xl text-center space-y-4 pb-8">
|
||||
<section className="w-full max-w-2xl text-center space-y-4 pb-8 mx-auto">
|
||||
<h2 className="text-3xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-green-400 via-blue-500 to-purple-500">
|
||||
Want to talk?
|
||||
</h2>
|
||||
|
||||
@@ -1,38 +1,90 @@
|
||||
import { useUmami } from "@danielgtmn/umami-react";
|
||||
import { Github, Mail, MessageSquare } from "lucide-react";
|
||||
|
||||
export function Footer() {
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
const handleLinkClick = (name: string, url: string) => {
|
||||
useUmami().track(`footer_click_${name.toLowerCase()}`, { url });
|
||||
};
|
||||
|
||||
return (
|
||||
<footer className="w-full border-t border-white/10 pt-4">
|
||||
<div className="text-center space-y-3">
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<img
|
||||
src="http://shsf.reversed.dev/SHSF%20SMALL%20TRANSPARENT.png"
|
||||
alt="SHSF Logo"
|
||||
className="w-6 h-6"
|
||||
/>
|
||||
<p className="text-gray-400 text-sm">
|
||||
Partly powered by{" "}
|
||||
<footer className="w-full border-t border-white/5 py-8 mt-20">
|
||||
<div className="max-w-7xl mx-auto px-6 flex flex-col items-center gap-6">
|
||||
{/* Social Icons Row */}
|
||||
<div className="flex items-center gap-6 text-gray-400">
|
||||
<a
|
||||
href="https://github.com/Space-Banane"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="hover:text-purple-400 transition-colors"
|
||||
aria-label="GitHub"
|
||||
onClick={() =>
|
||||
handleLinkClick("GitHub", "https://github.com/Space-Banane")
|
||||
}
|
||||
>
|
||||
<Github className="w-5 h-5" />
|
||||
</a>
|
||||
<a
|
||||
href="mailto:space@reversed.dev"
|
||||
className="hover:text-purple-400 transition-colors"
|
||||
aria-label="Email"
|
||||
onClick={() =>
|
||||
handleLinkClick("Email", "mailto:space@reversed.dev")
|
||||
}
|
||||
>
|
||||
<Mail className="w-5 h-5" />
|
||||
</a>
|
||||
<a
|
||||
href="https://discord.com/users/456443941169004545"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="hover:text-purple-400 transition-colors"
|
||||
aria-label="Discord"
|
||||
onClick={() =>
|
||||
handleLinkClick(
|
||||
"Discord",
|
||||
"https://discord.com/users/456443941169004545",
|
||||
)
|
||||
}
|
||||
>
|
||||
<MessageSquare className="w-5 h-5" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{/* Branding & Attribution */}
|
||||
<div className="flex flex-col items-center gap-2 text-center">
|
||||
<p className="text-gray-500 text-xs tracking-wide">
|
||||
© {currentYear} • Space-Banane • Love from Space
|
||||
</p>
|
||||
<div className="flex items-center gap-3 text-[10px] text-gray-600 uppercase tracking-[0.2em]">
|
||||
<a
|
||||
href="https://github.com/Space-Banane/shsf"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-purple-400 hover:text-purple-300 transition-colors"
|
||||
className="hover:text-gray-400 transition-colors"
|
||||
onClick={() =>
|
||||
handleLinkClick("SHSF", "https://github.com/Space-Banane/shsf")
|
||||
}
|
||||
>
|
||||
SHSF
|
||||
</a>
|
||||
</p>
|
||||
<span>•</span>
|
||||
<a
|
||||
href="https://gradienty.codes/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="hover:text-gray-400 transition-colors"
|
||||
onClick={() =>
|
||||
handleLinkClick("Gradienty", "https://gradienty.codes/")
|
||||
}
|
||||
>
|
||||
Gradienty
|
||||
</a>
|
||||
<span>•</span>
|
||||
<span>React</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-gray-400 text-sm">Love from Space ❤️</p>
|
||||
<p className="text-gray-500 text-xs">
|
||||
Designed with Copilot &{" "}
|
||||
<a
|
||||
href="https://gradienty.codes/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-purple-400 hover:text-purple-300 transition-colors underline"
|
||||
>
|
||||
gradienty.codes
|
||||
</a>
|
||||
, Tailwind and React
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
|
||||
@@ -69,6 +69,22 @@ export function Goals() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="md:col-span-2 p-6 rounded-2xl bg-gradient-to-br from-yellow-500/10 to-orange-500/5 backdrop-blur-sm border border-yellow-500/20">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="p-3 rounded-lg bg-yellow-500/20 text-3xl">
|
||||
🏠
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-xl font-bold text-white mb-2">
|
||||
Embrace Self-Hosting
|
||||
</h3>
|
||||
<p className="text-gray-400 leading-relaxed">
|
||||
I want to learn more about self-hosting my own services and reduce reliance on cloud providers for personal projects.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
export function InternshipSection() {
|
||||
return (
|
||||
<section className="w-full space-y-8">
|
||||
<div className="text-center space-y-2">
|
||||
<h2 className="text-3xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-yellow-400 to-orange-500">
|
||||
Real Life Experience (ABB Internship)
|
||||
</h2>
|
||||
<p className="text-gray-400 max-w-2xl mx-auto">
|
||||
My internship experience at ABB and what i learned from it
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<div className="p-6 rounded-2xl bg-gradient-to-br from-yellow-500/10 to-orange-500/5 backdrop-blur-sm border border-yellow-500/20">
|
||||
<div className="space-y-4 text-gray-300">
|
||||
<p className="leading-relaxed">
|
||||
I did an internship at{" "}
|
||||
<a
|
||||
href="https://www.abb.com/global/en"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-red-500 hover:text-red-600 transition-colors font-semibold"
|
||||
>
|
||||
ABB{" "}
|
||||
</a>
|
||||
where i got to work on a project that involved internal management
|
||||
software. It was a great experience that taught me a lot about
|
||||
working in a professional environment and collaborating with a
|
||||
team of developers.
|
||||
</p>
|
||||
<p className="leading-relaxed">
|
||||
This was the first propper time i worked with a Framework (Angular) and it was a great learning experience. Which helped me understand why
|
||||
frameworks are a thing and how they can help structure and organize codebases, especially as they grow larger.
|
||||
</p>
|
||||
<p className="leading-relaxed">
|
||||
Also, this was the first time submitting a PR, which was scary but super rewarding when they merged it. From that point on
|
||||
i was hooked on Frameworks and OSS contribution. I want to do more of both in the future.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user