diff --git a/index.html b/index.html index 67eec3c..9881467 100644 --- a/index.html +++ b/index.html @@ -26,9 +26,6 @@ - - diff --git a/package.json b/package.json index 08a8d37..b60356d 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8f80fce..b5fe9ad 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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 diff --git a/src/App.tsx b/src/App.tsx index 4bfb9bc..5687228 100644 --- a/src/App.tsx +++ b/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๏ฟฝ (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 ( -
- {/* Background Grid */} -
-
-
+ + +
+ {/* Modern Background */} +
+ {/* Main Gradient Glows */} +
+
+ + {/* Subtle Grid Pattern */} +
+ + {/* Noise Overlay */} +
+
-
- + - +
+ + + } + /> + } /> + } /> + +
- - - {/* Projects Section */} -
-
-

- Favourite Projects -

-

- Personal favourites, projects that I'm proud of, or just things I - find fun to show off. -

-
- -
- {projects.map((project, index) => ( - - ))} -
-
- - {/* Mini Projects Section */} -
-
-

- Mini Projects -

-

- Small experiments and fun projects I've built along the way -

-
- -
- {miniProjects.map((project, index) => ( - setSelectedMiniProject(project)} - /> - ))} -
-
+
{selectedMiniProject && ( setSelectedExperience(null)} /> )} - - {/* Experience Section */} -
-
-

- Experience -

-

- Technologies, platforms, and roles I've worked with -

-
- -
- {Object.entries( - experiences.reduce( - (acc, exp) => { - if (!acc[exp.type]) acc[exp.type] = []; - acc[exp.type].push(exp); - return acc; - }, - {} as Record, - ), - ).map(([type, items]) => ( -
-

- {getTypeIcon(type as Experience["type"])}{" "} - {type.charAt(0).toUpperCase() + type.slice(1)} -

-
- {items.map((exp, index) => ( - setSelectedExperience(exp)} - /> - ))} -
-
- ))} -
- -

- And a bunch of stuff i probably also forgot... -

-
- - - - - -
-
- - {/* Floating Island: Bottom Left (only on localhost) */} - {typeof window !== "undefined" && - (window.location.hostname === "localhost" || - window.location.hostname === "127.0.0.1") && ( -
-
- - ๐Ÿ๏ธ - - Loaded -
-
- - Projects: {projects.length} - - - Mini Projects: {miniProjects.length} - - - Experiences: {experiences.length} - -
-
- )} -
+
+ ); } diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx new file mode 100644 index 0000000..9d88c5e --- /dev/null +++ b/src/components/Navbar.tsx @@ -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 ( + + ); +} diff --git a/src/pages/AboutPage.tsx b/src/pages/AboutPage.tsx new file mode 100644 index 0000000..da43e2a --- /dev/null +++ b/src/pages/AboutPage.tsx @@ -0,0 +1,39 @@ +import { About as AboutSection } from "../sections/About"; +import { Goals } from "../sections/Goals"; + +export function About() { + return ( +
+
+

+ About Me +

+

+ A little bit about me... +

+
+ + + + + +
+

Interests Outside of Coding

+
    +
  • + โœฆ 3D Printing & Design +
  • +
  • + โœฆ Graphical Design +
  • +
  • + โœฆ Gaming & Hardware +
  • +
  • + โœฆ Open Source Contribution +
  • +
+
+
+ ); +} diff --git a/src/pages/ContactPage.tsx b/src/pages/ContactPage.tsx new file mode 100644 index 0000000..48a6d75 --- /dev/null +++ b/src/pages/ContactPage.tsx @@ -0,0 +1,38 @@ +import { Contact as ContactSection } from "../sections/Contact"; + +export function Contact() { + return ( +
+
+

+ Let's Connect +

+

+ Whatever you've got in mind, I'm just a few clicks away. +

+
+ +
+ +
+ +
+
+
๐Ÿ“ง
+

Email

+

space@reversed.dev

+
+
+
๐Ÿ‘พ
+

Discord

+

@getspaced

+
+
+
๐Ÿ’ป
+

GitHub

+

github.com/Space-Banane

+
+
+
+ ); +} diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx new file mode 100644 index 0000000..c6bd559 --- /dev/null +++ b/src/pages/Home.tsx @@ -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, + ); + + 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 ( +
+ + +
+
+

+ Featured Projects +

+

+ A selection of my personal favorites. Many more on my GitHub. +

+
+
+ {projects.map((project, index) => ( + + ))} +
+
+ +
+
+

+ Mini Projects +

+

+ Small tools, experiments, and fun little things I've built. +

+
+
+ {miniProjects.map((mini, index) => ( + setSelectedMiniProject(mini)} + /> + ))} +
+
+ +
+
+

+ Experience +

+

+ My journey through the tech world so far. +

+
+ +
+ {Object.entries(groupedExperiences) + .sort(([typeA], [typeB]) => typeA.localeCompare(typeB)) + .map(([type, items]) => ( +
+
+

+ {rewriteType(type)} +

+
+
+
+ {items.map((exp, index) => ( + setSelectedExperience(exp)} + /> + ))} +
+
+ ))} +
+
+
+ ); +} diff --git a/src/sections/Contact.tsx b/src/sections/Contact.tsx index 57e9f2c..66421b8 100644 --- a/src/sections/Contact.tsx +++ b/src/sections/Contact.tsx @@ -1,6 +1,6 @@ export function Contact() { return ( -
+

Want to talk?

diff --git a/src/sections/Footer.tsx b/src/sections/Footer.tsx index 5972c47..5a11722 100644 --- a/src/sections/Footer.tsx +++ b/src/sections/Footer.tsx @@ -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 ( -
+ +
+
+
+ ๐Ÿ  +
+
+

+ Embrace Self-Hosting +

+

+ I want to learn more about self-hosting my own services and reduce reliance on cloud providers for personal projects. +

+
+
+
); diff --git a/src/sections/InternshipSection.tsx b/src/sections/InternshipSection.tsx deleted file mode 100644 index 1002024..0000000 --- a/src/sections/InternshipSection.tsx +++ /dev/null @@ -1,44 +0,0 @@ -export function InternshipSection() { - return ( -
-
-

- Real Life Experience (ABB Internship) -

-

- My internship experience at ABB and what i learned from it -

-
- -
-
-
-

- I did an internship at{" "} - - ABB{" "} - - 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. -

-

- 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. -

-

- 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. -

-
-
-
-
- ); -}