copilot. Split into components
This commit is contained in:
881
src/App.tsx
881
src/App.tsx
@@ -1,506 +1,17 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import type { Experience, MiniProject, Project } from "./types";
|
||||||
interface MiniProject {
|
import { MiniProjectModal } from "./components/MiniProjectModal";
|
||||||
title: string;
|
import { ExperienceModal } from "./components/ExperienceModal";
|
||||||
description: string;
|
import { ProjectCard } from "./components/ProjectCard";
|
||||||
image?: string;
|
import { MiniProjectCard } from "./components/MiniProjectCard";
|
||||||
github?: string;
|
import { ExperienceCard } from "./components/ExperienceCard";
|
||||||
reproduction?: string;
|
import { getTypeIcon } from "./components/utils";
|
||||||
why: string;
|
import { Hero } from "./sections/Hero";
|
||||||
note?: {
|
import { About } from "./sections/About";
|
||||||
color: string;
|
import { Goals } from "./sections/Goals";
|
||||||
content: string;
|
import { InternshipSection } from "./sections/InternshipSection";
|
||||||
};
|
import { Contact } from "./sections/Contact";
|
||||||
last_commit?: Date;
|
import { Footer } from "./sections/Footer";
|
||||||
}
|
|
||||||
|
|
||||||
interface Experience {
|
|
||||||
name: string;
|
|
||||||
type: "language" | "service" | "platform" | "real life experience" | "roles";
|
|
||||||
description: string;
|
|
||||||
image?: string;
|
|
||||||
learned_at?: string;
|
|
||||||
learned_from?: string;
|
|
||||||
learned_because?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
function MiniProjectModal({
|
|
||||||
project,
|
|
||||||
onClose,
|
|
||||||
}: {
|
|
||||||
project: MiniProject;
|
|
||||||
onClose: () => void;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/80 backdrop-blur-sm animate-fade-in"
|
|
||||||
onClick={onClose}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="relative max-w-3xl w-full max-h-[90vh] overflow-y-auto bg-gradient-to-br from-gray-900 to-black border border-purple-500/30 rounded-2xl shadow-2xl animate-scale-in"
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
>
|
|
||||||
{/* Close Button */}
|
|
||||||
<button
|
|
||||||
onClick={onClose}
|
|
||||||
className="absolute top-4 right-4 p-2 rounded-full bg-white/10 hover:bg-white/20 text-white transition-colors z-10"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
className="w-6 h-6"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeWidth="2"
|
|
||||||
d="M6 18L18 6M6 6l12 12"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{/* Image */}
|
|
||||||
{project.image && (
|
|
||||||
<div className="w-full h-64 overflow-hidden bg-black/40 rounded-t-2xl">
|
|
||||||
<img
|
|
||||||
src={project.image}
|
|
||||||
alt={project.title}
|
|
||||||
className="w-full h-full object-cover"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Content */}
|
|
||||||
<div className="p-8 space-y-6">
|
|
||||||
<h2 className="text-3xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-purple-400 to-pink-500">
|
|
||||||
{project.title}
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
{project.note && (
|
|
||||||
<div
|
|
||||||
className="p-4 rounded-lg border-l-4 bg-black/20 backdrop-blur-sm"
|
|
||||||
style={{
|
|
||||||
borderColor: project.note.color,
|
|
||||||
backgroundColor: `${project.note.color}15`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<p
|
|
||||||
className="text-sm font-medium"
|
|
||||||
style={{ color: project.note.color }}
|
|
||||||
>
|
|
||||||
{project.note.content}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div>
|
|
||||||
<h3 className="text-lg font-semibold text-purple-400 mb-2">
|
|
||||||
Description
|
|
||||||
</h3>
|
|
||||||
<p className="text-gray-300 leading-relaxed">
|
|
||||||
{project.description}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h3 className="text-lg font-semibold text-purple-400 mb-2">
|
|
||||||
Why I Made This
|
|
||||||
</h3>
|
|
||||||
<p className="text-gray-300 leading-relaxed">{project.why}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{project.reproduction && (
|
|
||||||
<div>
|
|
||||||
<h3 className="text-lg font-semibold text-purple-400 mb-2">
|
|
||||||
How to Reproduce
|
|
||||||
</h3>
|
|
||||||
<pre className="text-sm text-gray-300 bg-black/40 p-4 rounded-lg overflow-x-auto border border-white/10">
|
|
||||||
<code>{project.reproduction}</code>
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{project.github && (
|
|
||||||
<a
|
|
||||||
href={project.github}
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
className="inline-flex items-center gap-2 px-6 py-3 rounded-lg bg-gradient-to-r from-purple-500 to-pink-500 text-white font-semibold hover:from-purple-600 hover:to-pink-600 transition-all shadow-lg shadow-purple-500/30 hover:shadow-purple-500/50"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
className="w-5 h-5"
|
|
||||||
fill="currentColor"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
|
|
||||||
</svg>
|
|
||||||
View on GitHub
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function ExperienceModal({
|
|
||||||
experience,
|
|
||||||
onClose,
|
|
||||||
}: {
|
|
||||||
experience: Experience;
|
|
||||||
onClose: () => void;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/80 backdrop-blur-sm animate-fade-in"
|
|
||||||
onClick={onClose}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="relative max-w-2xl w-full max-h-[90vh] overflow-y-auto bg-gradient-to-br from-gray-900 to-black border border-purple-500/30 rounded-2xl shadow-2xl animate-scale-in"
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
>
|
|
||||||
{/* Close Button */}
|
|
||||||
<button
|
|
||||||
onClick={onClose}
|
|
||||||
className="absolute top-4 right-4 p-2 rounded-full bg-white/10 hover:bg-white/20 text-white transition-colors z-10"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
className="w-6 h-6"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeWidth="2"
|
|
||||||
d="M6 18L18 6M6 6l12 12"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{/* Content */}
|
|
||||||
<div className="p-8 space-y-6">
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<div
|
|
||||||
className={`text-4xl p-3 rounded-lg ${getTypeColor(experience.type)} flex items-center justify-center shrink-0 ${experience.image ? "w-16 h-16" : ""}`}
|
|
||||||
>
|
|
||||||
{experience.image ? (
|
|
||||||
<img
|
|
||||||
src={experience.image}
|
|
||||||
alt={experience.name}
|
|
||||||
className="w-full h-full object-contain"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
getTypeIcon(experience.type)
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h2 className="text-3xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-purple-400 to-pink-500">
|
|
||||||
{experience.name}
|
|
||||||
</h2>
|
|
||||||
<p className="text-sm text-gray-400 mt-1">
|
|
||||||
{getTypeIcon(experience.type)}{" "}
|
|
||||||
{experience.type.charAt(0).toUpperCase() +
|
|
||||||
experience.type.slice(1)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-4">
|
|
||||||
{experience.description && (
|
|
||||||
<div>
|
|
||||||
<h3 className="text-lg font-semibold text-purple-400 mb-2">
|
|
||||||
Description
|
|
||||||
</h3>
|
|
||||||
<p className="text-gray-300 leading-relaxed">
|
|
||||||
{experience.description}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{experience.learned_at && (
|
|
||||||
<div>
|
|
||||||
<h3 className="text-lg font-semibold text-purple-400 mb-2">
|
|
||||||
📅 When I Learned It
|
|
||||||
</h3>
|
|
||||||
<p className="text-gray-300 leading-relaxed">
|
|
||||||
{experience.learned_at}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{experience.learned_from && (
|
|
||||||
<div>
|
|
||||||
<h3 className="text-lg font-semibold text-purple-400 mb-2">
|
|
||||||
👨🏫 How I Learned It
|
|
||||||
</h3>
|
|
||||||
<p className="text-gray-300 leading-relaxed">
|
|
||||||
{experience.learned_from}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{experience.learned_because && (
|
|
||||||
<div>
|
|
||||||
<h3 className="text-lg font-semibold text-purple-400 mb-2">
|
|
||||||
💡 Why I Learned It
|
|
||||||
</h3>
|
|
||||||
<p className="text-gray-300 leading-relaxed">
|
|
||||||
{experience.learned_because}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTypeIcon(type: Experience["type"]) {
|
|
||||||
switch (type) {
|
|
||||||
case "language":
|
|
||||||
return "💻";
|
|
||||||
case "service":
|
|
||||||
return "☁️";
|
|
||||||
case "platform":
|
|
||||||
return "🚀";
|
|
||||||
case "real life experience":
|
|
||||||
return "🌍";
|
|
||||||
case "roles":
|
|
||||||
return "👔";
|
|
||||||
default:
|
|
||||||
return "📦";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTypeColor(type: Experience["type"]) {
|
|
||||||
switch (type) {
|
|
||||||
case "language":
|
|
||||||
return "bg-blue-500/20";
|
|
||||||
case "service":
|
|
||||||
return "bg-purple-500/20";
|
|
||||||
case "platform":
|
|
||||||
return "bg-green-500/20";
|
|
||||||
case "real life experience":
|
|
||||||
return "bg-orange-500/20";
|
|
||||||
case "roles":
|
|
||||||
return "bg-pink-500/20";
|
|
||||||
default:
|
|
||||||
return "bg-gray-500/20";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function MiniProjectCard({
|
|
||||||
project,
|
|
||||||
onClick,
|
|
||||||
}: {
|
|
||||||
project: MiniProject;
|
|
||||||
onClick: () => void;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
onClick={onClick}
|
|
||||||
className="group cursor-pointer relative flex flex-col p-6 rounded-2xl bg-gradient-to-br from-white/5 to-white/2 backdrop-blur-sm border border-white/10 hover:border-purple-500/50 transition-all duration-300 hover:-translate-y-2 hover:shadow-2xl hover:shadow-purple-500/20"
|
|
||||||
>
|
|
||||||
{project.image && (
|
|
||||||
<div className="mb-4 overflow-hidden rounded-xl bg-black/20 aspect-video flex items-center justify-center">
|
|
||||||
<img
|
|
||||||
src={project.image}
|
|
||||||
alt={project.title}
|
|
||||||
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<h3 className="text-xl font-bold text-white mb-2 group-hover:text-purple-400 transition-colors">
|
|
||||||
{project.title}
|
|
||||||
</h3>
|
|
||||||
<p className="text-gray-400 text-sm line-clamp-2 mb-4">
|
|
||||||
{project.description}
|
|
||||||
</p>
|
|
||||||
{project.last_commit && (
|
|
||||||
<p className="text-gray-400 text-xs mb-2">
|
|
||||||
Last commit: {project.last_commit.toLocaleDateString()}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="flex items-center text-purple-400 text-sm font-medium mt-auto">
|
|
||||||
Click to learn more
|
|
||||||
<svg
|
|
||||||
className="w-4 h-4 ml-1 group-hover:translate-x-1 transition-transform"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeWidth="2"
|
|
||||||
d="M9 5l7 7-7 7"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function ExperienceCard({
|
|
||||||
experience,
|
|
||||||
onClick,
|
|
||||||
}: {
|
|
||||||
experience: Experience;
|
|
||||||
onClick?: () => void;
|
|
||||||
}) {
|
|
||||||
const isClickable = !!(
|
|
||||||
experience.learned_at ||
|
|
||||||
experience.learned_from ||
|
|
||||||
experience.learned_because
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
onClick={isClickable ? onClick : undefined}
|
|
||||||
className={`group flex items-center gap-3 p-3 rounded-lg bg-gradient-to-br from-white/5 to-white/2 backdrop-blur-sm border border-white/10 transition-all duration-200 ${
|
|
||||||
isClickable
|
|
||||||
? "cursor-pointer hover:border-purple-500/50 hover:bg-white/10 hover:-translate-y-0.5"
|
|
||||||
: ""
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={`text-2xl p-2 rounded-lg ${getTypeColor(experience.type)} flex items-center justify-center shrink-0 relative ${
|
|
||||||
experience.image ? "w-10 h-10" : ""
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{experience.image ? (
|
|
||||||
<img
|
|
||||||
src={experience.image}
|
|
||||||
alt={experience.name}
|
|
||||||
className="w-full h-full object-contain"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
getTypeIcon(experience.type)
|
|
||||||
)}
|
|
||||||
{isClickable && (
|
|
||||||
<div className="absolute -top-1 -right-1 w-3 h-3 bg-purple-500 rounded-full border-2 border-black animate-pulse" />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
<h4
|
|
||||||
className={`text-base font-semibold text-white transition-colors ${
|
|
||||||
isClickable ? "group-hover:text-purple-400" : ""
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{experience.name}
|
|
||||||
</h4>
|
|
||||||
{experience.description && (
|
|
||||||
<p className="text-xs text-gray-400 mt-0.5 line-clamp-1">
|
|
||||||
{experience.description}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{isClickable && (
|
|
||||||
<div className="text-purple-400 opacity-0 group-hover:opacity-100 transition-opacity shrink-0">
|
|
||||||
<svg
|
|
||||||
className="w-4 h-4"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeWidth="2"
|
|
||||||
d="M9 5l7 7-7 7"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function ProjectCard({ project }: { project: Project }) {
|
|
||||||
return (
|
|
||||||
<div className="group relative flex flex-col p-6 rounded-2xl bg-gradient-to-br from-white/10 to-white/5 backdrop-blur-sm border border-white/10 hover:border-purple-500/30 transition-all duration-300 hover:-translate-y-1 hover:shadow-2xl hover:shadow-purple-500/10">
|
|
||||||
{project.image && (
|
|
||||||
<div className="mb-6 overflow-hidden rounded-xl bg-black/20 aspect-video flex items-center justify-center relative">
|
|
||||||
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
|
|
||||||
<img
|
|
||||||
src={project.image}
|
|
||||||
alt={project.name}
|
|
||||||
className={`object-contain ${project.rounded === false ? "w-full h-auto max-h-24 rounded-lg" : "h-24 w-24 rounded-full"} shadow-lg group-hover:scale-110 transition-transform duration-500`}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<h3 className="text-xl font-bold text-white mb-2 group-hover:text-purple-400 transition-colors">
|
|
||||||
{project.name}
|
|
||||||
</h3>
|
|
||||||
<p className="text-gray-400 text-sm flex-grow mb-6 leading-relaxed">
|
|
||||||
{project.description}
|
|
||||||
</p>
|
|
||||||
{project.last_commit && (
|
|
||||||
<p className="text-gray-400 text-xs mb-2">
|
|
||||||
Last commit: {project.last_commit.toLocaleDateString()}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="flex gap-3 mt-auto">
|
|
||||||
<a
|
|
||||||
href={project.link + "?utm_source=portfolio&ref=space"}
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
className="flex-1 py-2 px-4 rounded-lg bg-white/10 hover:bg-white/20 text-white text-sm font-medium text-center transition-colors flex items-center justify-center gap-2"
|
|
||||||
>
|
|
||||||
Visit
|
|
||||||
<svg
|
|
||||||
className="w-3 h-3"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeWidth="2"
|
|
||||||
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
{project.open_source && (
|
|
||||||
<a
|
|
||||||
href={project.open_source.link}
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
className="p-2 rounded-lg bg-white/5 hover:bg-white/10 text-gray-400 hover:text-white transition-colors border border-white/5"
|
|
||||||
title="View Source"
|
|
||||||
>
|
|
||||||
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// async function getLastCommitDate(url: string) {
|
|
||||||
// const response = await fetch("https://shsf-api.reversed.dev/api/exec/6/c04c873c-6b75-4733-8636-bdaa962701c5", {
|
|
||||||
// method: "POST",
|
|
||||||
// headers: {
|
|
||||||
// "Content-Type": "application/json",
|
|
||||||
// },
|
|
||||||
// body: JSON.stringify({ url: url }),
|
|
||||||
// });
|
|
||||||
// const data = await response.json();
|
|
||||||
// return new Date(data.date);
|
|
||||||
// }
|
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [status, setStatus] = useState("");
|
const [status, setStatus] = useState("");
|
||||||
@@ -516,7 +27,6 @@ function App() {
|
|||||||
const [selectedExperience, setSelectedExperience] =
|
const [selectedExperience, setSelectedExperience] =
|
||||||
useState<Experience | null>(null);
|
useState<Experience | null>(null);
|
||||||
|
|
||||||
// Replace hardcoded arrays with state
|
|
||||||
const [projects, setProjects] = useState<Project[]>([]);
|
const [projects, setProjects] = useState<Project[]>([]);
|
||||||
const [miniProjects, setMiniProjects] = useState<MiniProject[]>([]);
|
const [miniProjects, setMiniProjects] = useState<MiniProject[]>([]);
|
||||||
const [experiences, setExperiences] = useState<Experience[]>([]);
|
const [experiences, setExperiences] = useState<Experience[]>([]);
|
||||||
@@ -541,7 +51,7 @@ function App() {
|
|||||||
"Why is uv so cool? 🧊",
|
"Why is uv so cool? 🧊",
|
||||||
"I'm hungry 🍔",
|
"I'm hungry 🍔",
|
||||||
"I swear this worked on my machine 🖥️",
|
"I swear this worked on my machine 🖥️",
|
||||||
"Pulling 10GB of docker images 🐳"
|
"Pulling 10GB of docker images 🐳",
|
||||||
];
|
];
|
||||||
|
|
||||||
const characters =
|
const characters =
|
||||||
@@ -636,18 +146,24 @@ function App() {
|
|||||||
|
|
||||||
// Fetch data from API on mount
|
// Fetch data from API on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch("https://shsf-api.reversed.dev/api/exec/4/e942538c-caa1-49d1-8953-dfab1e62f8cb/projects")
|
fetch(
|
||||||
.then(res => res.json())
|
"https://shsf-api.reversed.dev/api/exec/4/e942538c-caa1-49d1-8953-dfab1e62f8cb/projects",
|
||||||
|
)
|
||||||
|
.then((res) => res.json())
|
||||||
.then(setProjects)
|
.then(setProjects)
|
||||||
.catch(() => setProjects([]));
|
.catch(() => setProjects([]));
|
||||||
|
|
||||||
fetch("https://shsf-api.reversed.dev/api/exec/4/e942538c-caa1-49d1-8953-dfab1e62f8cb/mini_projects")
|
fetch(
|
||||||
.then(res => res.json())
|
"https://shsf-api.reversed.dev/api/exec/4/e942538c-caa1-49d1-8953-dfab1e62f8cb/mini_projects",
|
||||||
|
)
|
||||||
|
.then((res) => res.json())
|
||||||
.then(setMiniProjects)
|
.then(setMiniProjects)
|
||||||
.catch(() => setMiniProjects([]));
|
.catch(() => setMiniProjects([]));
|
||||||
|
|
||||||
fetch("https://shsf-api.reversed.dev/api/exec/4/e942538c-caa1-49d1-8953-dfab1e62f8cb/experience")
|
fetch(
|
||||||
.then(res => res.json())
|
"https://shsf-api.reversed.dev/api/exec/4/e942538c-caa1-49d1-8953-dfab1e62f8cb/experience",
|
||||||
|
)
|
||||||
|
.then((res) => res.json())
|
||||||
.then(setExperiences)
|
.then(setExperiences)
|
||||||
.catch(() => setExperiences([]));
|
.catch(() => setExperiences([]));
|
||||||
}, []);
|
}, []);
|
||||||
@@ -669,201 +185,20 @@ function App() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<main className="relative z-10 flex flex-col items-center px-6 py-20 mx-auto max-w-5xl gap-24">
|
<main className="relative z-10 flex flex-col items-center px-6 py-20 mx-auto max-w-5xl gap-24">
|
||||||
{/* Hero Section */}
|
<Hero
|
||||||
<section className="flex flex-col items-center text-center space-y-8 animate-fade-in">
|
glowColor={glowColor}
|
||||||
<div className="relative group">
|
borderStatus={borderStatus}
|
||||||
<div
|
displayMessage={displayMessage}
|
||||||
className={`absolute -inset-1 rounded-full blur opacity-75 transition duration-500`}
|
rotatingMessages={rotatingMessages}
|
||||||
style={{ backgroundColor: glowColor }}
|
statusMessage={statusMessage}
|
||||||
></div>
|
showOldNames={showOldNames}
|
||||||
<div
|
setShowOldNames={setShowOldNames}
|
||||||
className={`relative w-48 h-48 rounded-full border-4 transition-colors duration-500 overflow-hidden ${borderStatus} bg-black`}
|
oldUsernames={oldUsernames}
|
||||||
>
|
|
||||||
<img
|
|
||||||
src="https://cdn.reversed.dev/pictures/20250405_120402.png"
|
|
||||||
alt="Space"
|
|
||||||
className="w-full h-full object-cover scale-110 transition-transform duration-700 group-hover:scale-125"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Rotating Message Bubble (Left Side) */}
|
<About />
|
||||||
<div className="absolute -left-4 top-4 -translate-x-full">
|
|
||||||
<div className="relative bg-gradient-to-br from-purple-500/20 to-pink-500/10 backdrop-blur-sm border border-purple-500/30 rounded-2xl px-4 py-3 shadow-lg">
|
|
||||||
{/* Arrow pointing to profile */}
|
|
||||||
<div className="absolute right-0 top-1/2 translate-x-2 -translate-y-1/2 w-0 h-0 border-t-8 border-t-transparent border-b-8 border-b-transparent border-l-8 border-l-purple-500/30"></div>
|
|
||||||
<div className="absolute right-0 top-1/2 translate-x-1.5 -translate-y-1/2 w-0 h-0 border-t-8 border-t-transparent border-b-8 border-b-transparent border-l-8 border-l-purple-500/20"></div>
|
|
||||||
|
|
||||||
<p className="text-sm text-gray-200 whitespace-nowrap font-medium font-mono">
|
|
||||||
{displayMessage || rotatingMessages[0]}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Status Bubble (Right Side) */}
|
|
||||||
<div className="absolute -right-4 top-8 translate-x-full">
|
|
||||||
<div className="relative bg-gradient-to-br from-white/10 to-white/5 backdrop-blur-sm border border-white/20 rounded-2xl px-4 py-3 shadow-lg">
|
|
||||||
{/* Arrow pointing to profile */}
|
|
||||||
<div className="absolute left-0 top-1/2 -translate-x-2 -translate-y-1/2 w-0 h-0 border-t-8 border-t-transparent border-b-8 border-b-transparent border-r-8 border-r-white/20"></div>
|
|
||||||
<div className="absolute left-0 top-1/2 -translate-x-1.5 -translate-y-1/2 w-0 h-0 border-t-8 border-t-transparent border-b-8 border-b-transparent border-r-8 border-r-white/10"></div>
|
|
||||||
|
|
||||||
<p className="text-sm text-gray-300 whitespace-nowrap">
|
|
||||||
Currently{" "}
|
|
||||||
<span className="text-white font-semibold">
|
|
||||||
{statusMessage}
|
|
||||||
</span>
|
|
||||||
<br />
|
|
||||||
on Discord
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Name & Description */}
|
|
||||||
<div className="space-y-4 max-w-2xl">
|
|
||||||
<h1 className="text-6xl md:text-7xl font-extrabold">
|
|
||||||
Hey, I'm{" "}
|
|
||||||
<span
|
|
||||||
className="relative inline-block text-transparent bg-clip-text bg-gradient-to-r from-blue-400 via-purple-500 to-pink-500 cursor-help px-2"
|
|
||||||
style={{ marginLeft: "0.15em", marginRight: "0.15em" }}
|
|
||||||
onMouseEnter={() => setShowOldNames(true)}
|
|
||||||
onMouseLeave={() => setShowOldNames(false)}
|
|
||||||
>
|
|
||||||
Space²
|
|
||||||
{showOldNames && (
|
|
||||||
<div className="absolute left-1/2 -translate-x-1/2 top-full mt-4 z-50 animate-fade-in flex justify-center w-full">
|
|
||||||
<div className="relative bg-gradient-to-br from-purple-500/20 to-pink-500/10 backdrop-blur-md border border-purple-500/30 rounded-xl px-6 py-4 shadow-2xl min-w-[220px] max-w-xs">
|
|
||||||
<div className="absolute left-1/2 -translate-x-1/2 -top-2 w-0 h-0 border-l-8 border-l-transparent border-r-8 border-r-transparent border-b-8 border-b-purple-500/30"></div>
|
|
||||||
<p className="text-xs text-gray-400 mb-2 font-medium whitespace-nowrap text-center">
|
|
||||||
Also known as:
|
|
||||||
</p>
|
|
||||||
<ul className="flex flex-col items-center gap-1.5">
|
|
||||||
{oldUsernames.map((name, index) => (
|
|
||||||
<li
|
|
||||||
key={index}
|
|
||||||
className="text-sm text-gray-200 font-mono hover:text-purple-400 transition-colors whitespace-nowrap text-center"
|
|
||||||
>
|
|
||||||
{name}
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</h1>
|
|
||||||
<p className="text-2xl text-gray-400 font-light">
|
|
||||||
A{" "}
|
|
||||||
<span className="line-through decoration-purple-500/50 decoration-2">
|
|
||||||
Self-proclaimed
|
|
||||||
</span>{" "}
|
|
||||||
Developer breaking things to see how they work.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* About Section */}
|
|
||||||
<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-blue-400 to-purple-500">
|
|
||||||
Who?
|
|
||||||
</h2>
|
|
||||||
<p className="text-gray-400 max-w-2xl mx-auto">
|
|
||||||
A bit about me, my background, and how I got into programming.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="max-w-3xl mx-auto">
|
|
||||||
<div className="p-6 rounded-2xl bg-gradient-to-br from-blue-500/10 to-purple-500/5 backdrop-blur-sm border border-blue-500/20">
|
|
||||||
<div className="space-y-4 text-gray-300">
|
|
||||||
<p className="leading-relaxed">
|
|
||||||
I'm a Developer from Germany. I love doing full-stack web
|
|
||||||
development, tinkering with hardware, and 3D printing stuff i find quite fancy.
|
|
||||||
</p>
|
|
||||||
<p className="leading-relaxed">
|
|
||||||
I focus on building projects that i need, and that don't
|
|
||||||
already exist. Or break the ones that do exist, to see how they work and learn from them.
|
|
||||||
</p>
|
|
||||||
<p className="leading-relaxed">
|
|
||||||
I started programming around 2017, started of with C# in Unity, then moved to Python for a while, before settling on JavaScript/TypeScript as my main language around 2023.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Goals Section */}
|
|
||||||
<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-blue-400 to-purple-500">
|
|
||||||
My Goals
|
|
||||||
</h2>
|
|
||||||
<p className="text-gray-400 max-w-2xl mx-auto">Next 4 Years are going to look fun</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
||||||
<div className="p-6 rounded-2xl bg-gradient-to-br from-blue-500/10 to-purple-500/5 backdrop-blur-sm border border-blue-500/20">
|
|
||||||
<div className="flex items-start gap-4">
|
|
||||||
<div className="p-3 rounded-lg bg-blue-500/20 text-3xl">🚀</div>
|
|
||||||
<div>
|
|
||||||
<h3 className="text-xl font-bold text-white mb-2">
|
|
||||||
Not Just Semi-Fullstack
|
|
||||||
</h3>
|
|
||||||
<p className="text-gray-400 leading-relaxed">
|
|
||||||
I want to work more with Serverless Architectures and Cloud
|
|
||||||
Services to build scalable applications.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="p-6 rounded-2xl bg-gradient-to-br from-purple-500/10 to-pink-500/5 backdrop-blur-sm border border-purple-500/20">
|
|
||||||
<div className="flex items-start gap-4">
|
|
||||||
<div className="p-3 rounded-lg bg-purple-500/20 text-3xl">
|
|
||||||
💻
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3 className="text-xl font-bold text-white mb-2">
|
|
||||||
Contribute More to Open Source
|
|
||||||
</h3>
|
|
||||||
<p className="text-gray-400 leading-relaxed">
|
|
||||||
I want to commit more to Open-Source Projects. That's it...
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="p-6 rounded-2xl bg-gradient-to-br from-green-500/10 to-blue-500/5 backdrop-blur-sm border border-green-500/20">
|
|
||||||
<div className="flex items-start gap-4">
|
|
||||||
<div className="p-3 rounded-lg bg-green-500/20 text-3xl">
|
|
||||||
⚡
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3 className="text-xl font-bold text-white mb-2">
|
|
||||||
Expand on Existing Projects
|
|
||||||
</h3>
|
|
||||||
<p className="text-gray-400 leading-relaxed">
|
|
||||||
I want to make SHSF more stable and usable.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<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="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">
|
|
||||||
Testing before Breaking
|
|
||||||
</h3>
|
|
||||||
<p className="text-gray-400 leading-relaxed">In the future i want to write more tests and improve my code quality.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
|
<Goals />
|
||||||
|
|
||||||
{/* Projects Section */}
|
{/* Projects Section */}
|
||||||
<section className="w-full space-y-12">
|
<section className="w-full space-y-12">
|
||||||
@@ -872,7 +207,8 @@ function App() {
|
|||||||
Favourite Projects
|
Favourite Projects
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-gray-400 max-w-2xl mx-auto">
|
<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.
|
Personal favourites, projects that I'm proud of, or just things I
|
||||||
|
find fun to show off.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -978,130 +314,37 @@ function App() {
|
|||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Real Life Experience (ABB Internship) */}
|
<InternshipSection />
|
||||||
<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">
|
<Contact />
|
||||||
<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>
|
|
||||||
|
|
||||||
{/* Contact Section */}
|
<Footer />
|
||||||
<section className="w-full max-w-2xl text-center space-y-4 pb-8">
|
|
||||||
<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>
|
|
||||||
<div className="p-8 rounded-3xl bg-gradient-to-br from-blue-500/10 via-purple-500/10 to-pink-500/10 border border-blue-500/20 backdrop-blur-md">
|
|
||||||
<p className="text-gray-300 mb-8 text-lg">
|
|
||||||
Im almost always down to talk about anything. Tech, life and whatever. Feel free to reach out to me on Discord or via Email, i usually respond pretty quickly.
|
|
||||||
</p>
|
|
||||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
|
||||||
<a
|
|
||||||
href="mailto:space@reversed.dev"
|
|
||||||
className="px-8 py-3 rounded-full bg-gradient-to-r from-white to-gray-100 text-black font-bold hover:from-gray-100 hover:to-white transition-all shadow-lg shadow-white/20 hover:shadow-white/30"
|
|
||||||
>
|
|
||||||
Shoot me an Email
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="https://discord.com/users/456443941169004545"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
className="px-8 py-3 rounded-full bg-gradient-to-r from-[#5865F2] to-[#4752C4] text-white font-bold hover:from-[#4752C4] hover:to-[#5865F2] transition-all shadow-lg shadow-[#5865F2]/30 hover:shadow-[#5865F2]/50"
|
|
||||||
>
|
|
||||||
Chat on Discord
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Footer */}
|
|
||||||
<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{" "}
|
|
||||||
<a
|
|
||||||
href="https://github.com/Space-Banane/shsf"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
className="text-purple-400 hover:text-purple-300 transition-colors"
|
|
||||||
>
|
|
||||||
SHSF
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</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>
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
{/* Floating Island: Bottom Left (only on localhost) */}
|
{/* Floating Island: Bottom Left (only on localhost) */}
|
||||||
{typeof window !== "undefined" &&
|
{typeof window !== "undefined" &&
|
||||||
(window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1") && (
|
(window.location.hostname === "localhost" ||
|
||||||
|
window.location.hostname === "127.0.0.1") && (
|
||||||
<div
|
<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"
|
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 }}
|
style={{ minWidth: 180 }}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span role="img" aria-label="island">🏝️</span>
|
<span role="img" aria-label="island">
|
||||||
|
🏝️
|
||||||
|
</span>
|
||||||
<span className="font-semibold text-purple-300">Loaded</span>
|
<span className="font-semibold text-purple-300">Loaded</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-1 mt-1">
|
<div className="flex flex-col gap-1 mt-1">
|
||||||
<span className="text-white/90">Projects: <span className="font-bold text-purple-400">{projects.length}</span></span>
|
<span className="text-white/90">
|
||||||
<span className="text-white/90">Mini Projects: <span className="font-bold text-purple-400">{miniProjects.length}</span></span>
|
Projects: {projects.length}
|
||||||
<span className="text-white/90">Profile Data: <span className="font-bold text-purple-400">1/1</span></span>
|
</span>
|
||||||
<span className="text-white/90">Experiences: <span className="font-bold text-purple-400">{experiences.length}</span></span>
|
<span className="text-white/90">
|
||||||
|
Mini Projects: {miniProjects.length}
|
||||||
|
</span>
|
||||||
|
<span className="text-white/90">
|
||||||
|
Experiences: {experiences.length}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -1109,16 +352,4 @@ function App() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Project {
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
image?: string;
|
|
||||||
link: string;
|
|
||||||
open_source: false | { link: string };
|
|
||||||
rounded?: boolean;
|
|
||||||
last_commit?: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|||||||
77
src/components/ExperienceCard.tsx
Normal file
77
src/components/ExperienceCard.tsx
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import type { Experience } from "../types";
|
||||||
|
import { getTypeColor, getTypeIcon } from "./utils";
|
||||||
|
|
||||||
|
export function ExperienceCard({
|
||||||
|
experience,
|
||||||
|
onClick,
|
||||||
|
}: {
|
||||||
|
experience: Experience;
|
||||||
|
onClick?: () => void;
|
||||||
|
}) {
|
||||||
|
const isClickable = !!(
|
||||||
|
experience.learned_at ||
|
||||||
|
experience.learned_from ||
|
||||||
|
experience.learned_because
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
onClick={isClickable ? onClick : undefined}
|
||||||
|
className={`group flex items-center gap-3 p-3 rounded-lg bg-gradient-to-br from-white/5 to-white/2 backdrop-blur-sm border border-white/10 transition-all duration-200 ${
|
||||||
|
isClickable
|
||||||
|
? "cursor-pointer hover:border-purple-500/50 hover:bg-white/10 hover:-translate-y-0.5"
|
||||||
|
: ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`text-2xl p-2 rounded-lg ${getTypeColor(experience.type)} flex items-center justify-center shrink-0 relative ${
|
||||||
|
experience.image ? "w-10 h-10" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{experience.image ? (
|
||||||
|
<img
|
||||||
|
src={experience.image}
|
||||||
|
alt={experience.name}
|
||||||
|
className="w-full h-full object-contain"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
getTypeIcon(experience.type)
|
||||||
|
)}
|
||||||
|
{isClickable && (
|
||||||
|
<div className="absolute -top-1 -right-1 w-3 h-3 bg-purple-500 rounded-full border-2 border-black animate-pulse" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<h4
|
||||||
|
className={`text-base font-semibold text-white transition-colors ${
|
||||||
|
isClickable ? "group-hover:text-purple-400" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{experience.name}
|
||||||
|
</h4>
|
||||||
|
{experience.description && (
|
||||||
|
<p className="text-xs text-gray-400 mt-0.5 line-clamp-1">
|
||||||
|
{experience.description}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{isClickable && (
|
||||||
|
<div className="text-purple-400 opacity-0 group-hover:opacity-100 transition-opacity shrink-0">
|
||||||
|
<svg
|
||||||
|
className="w-4 h-4"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M9 5l7 7-7 7"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
117
src/components/ExperienceModal.tsx
Normal file
117
src/components/ExperienceModal.tsx
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import type { Experience } from "../types";
|
||||||
|
import { getTypeColor, getTypeIcon } from "./utils";
|
||||||
|
|
||||||
|
export function ExperienceModal({
|
||||||
|
experience,
|
||||||
|
onClose,
|
||||||
|
}: {
|
||||||
|
experience: Experience;
|
||||||
|
onClose: () => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/80 backdrop-blur-sm animate-fade-in"
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="relative max-w-2xl w-full max-h-[90vh] overflow-y-auto bg-gradient-to-br from-gray-900 to-black border border-purple-500/30 rounded-2xl shadow-2xl animate-scale-in"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
{/* Close Button */}
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
className="absolute top-4 right-4 p-2 rounded-full bg-white/10 hover:bg-white/20 text-white transition-colors z-10"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
className="w-6 h-6"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M6 18L18 6M6 6l12 12"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<div className="p-8 space-y-6">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div
|
||||||
|
className={`text-4xl p-3 rounded-lg ${getTypeColor(experience.type)} flex items-center justify-center shrink-0 ${experience.image ? "w-16 h-16" : ""}`}
|
||||||
|
>
|
||||||
|
{experience.image ? (
|
||||||
|
<img
|
||||||
|
src={experience.image}
|
||||||
|
alt={experience.name}
|
||||||
|
className="w-full h-full object-contain"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
getTypeIcon(experience.type)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 className="text-3xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-purple-400 to-pink-500">
|
||||||
|
{experience.name}
|
||||||
|
</h2>
|
||||||
|
<p className="text-sm text-gray-400 mt-1">
|
||||||
|
{getTypeIcon(experience.type)}{" "}
|
||||||
|
{experience.type.charAt(0).toUpperCase() +
|
||||||
|
experience.type.slice(1)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
{experience.description && (
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold text-purple-400 mb-2">
|
||||||
|
Description
|
||||||
|
</h3>
|
||||||
|
<p className="text-gray-300 leading-relaxed">
|
||||||
|
{experience.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{experience.learned_at && (
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold text-purple-400 mb-2">
|
||||||
|
📅 When I Learned It
|
||||||
|
</h3>
|
||||||
|
<p className="text-gray-300 leading-relaxed">
|
||||||
|
{experience.learned_at}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{experience.learned_from && (
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold text-purple-400 mb-2">
|
||||||
|
👨🏫 How I Learned It
|
||||||
|
</h3>
|
||||||
|
<p className="text-gray-300 leading-relaxed">
|
||||||
|
{experience.learned_from}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{experience.learned_because && (
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold text-purple-400 mb-2">
|
||||||
|
💡 Why I Learned It
|
||||||
|
</h3>
|
||||||
|
<p className="text-gray-300 leading-relaxed">
|
||||||
|
{experience.learned_because}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
55
src/components/MiniProjectCard.tsx
Normal file
55
src/components/MiniProjectCard.tsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import type { MiniProject } from "../types";
|
||||||
|
|
||||||
|
export function MiniProjectCard({
|
||||||
|
project,
|
||||||
|
onClick,
|
||||||
|
}: {
|
||||||
|
project: MiniProject;
|
||||||
|
onClick: () => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
onClick={onClick}
|
||||||
|
className="group cursor-pointer relative flex flex-col p-6 rounded-2xl bg-gradient-to-br from-white/5 to-white/2 backdrop-blur-sm border border-white/10 hover:border-purple-500/50 transition-all duration-300 hover:-translate-y-2 hover:shadow-2xl hover:shadow-purple-500/20"
|
||||||
|
>
|
||||||
|
{project.image && (
|
||||||
|
<div className="mb-4 overflow-hidden rounded-xl bg-black/20 aspect-video flex items-center justify-center">
|
||||||
|
<img
|
||||||
|
src={project.image}
|
||||||
|
alt={project.title}
|
||||||
|
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<h3 className="text-xl font-bold text-white mb-2 group-hover:text-purple-400 transition-colors">
|
||||||
|
{project.title}
|
||||||
|
</h3>
|
||||||
|
<p className="text-gray-400 text-sm line-clamp-2 mb-4">
|
||||||
|
{project.description}
|
||||||
|
</p>
|
||||||
|
{project.last_commit && (
|
||||||
|
<p className="text-gray-400 text-xs mb-2">
|
||||||
|
Last commit: {new Date(project.last_commit).toLocaleDateString()}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex items-center text-purple-400 text-sm font-medium mt-auto">
|
||||||
|
Click to learn more
|
||||||
|
<svg
|
||||||
|
className="w-4 h-4 ml-1 group-hover:translate-x-1 transition-transform"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M9 5l7 7-7 7"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
123
src/components/MiniProjectModal.tsx
Normal file
123
src/components/MiniProjectModal.tsx
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import type { MiniProject } from "../types";
|
||||||
|
|
||||||
|
export function MiniProjectModal({
|
||||||
|
project,
|
||||||
|
onClose,
|
||||||
|
}: {
|
||||||
|
project: MiniProject;
|
||||||
|
onClose: () => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/80 backdrop-blur-sm animate-fade-in"
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="relative max-w-3xl w-full max-h-[90vh] overflow-y-auto bg-gradient-to-br from-gray-900 to-black border border-purple-500/30 rounded-2xl shadow-2xl animate-scale-in"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
{/* Close Button */}
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
className="absolute top-4 right-4 p-2 rounded-full bg-white/10 hover:bg-white/20 text-white transition-colors z-10"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
className="w-6 h-6"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M6 18L18 6M6 6l12 12"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Image */}
|
||||||
|
{project.image && (
|
||||||
|
<div className="w-full h-64 overflow-hidden bg-black/40 rounded-t-2xl">
|
||||||
|
<img
|
||||||
|
src={project.image}
|
||||||
|
alt={project.title}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<div className="p-8 space-y-6">
|
||||||
|
<h2 className="text-3xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-purple-400 to-pink-500">
|
||||||
|
{project.title}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
{project.note && (
|
||||||
|
<div
|
||||||
|
className="p-4 rounded-lg border-l-4 bg-black/20 backdrop-blur-sm"
|
||||||
|
style={{
|
||||||
|
borderColor: project.note.color,
|
||||||
|
backgroundColor: `${project.note.color}15`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
className="text-sm font-medium"
|
||||||
|
style={{ color: project.note.color }}
|
||||||
|
>
|
||||||
|
{project.note.content}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold text-purple-400 mb-2">
|
||||||
|
Description
|
||||||
|
</h3>
|
||||||
|
<p className="text-gray-300 leading-relaxed">
|
||||||
|
{project.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold text-purple-400 mb-2">
|
||||||
|
Why I Made This
|
||||||
|
</h3>
|
||||||
|
<p className="text-gray-300 leading-relaxed">{project.why}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{project.reproduction && (
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold text-purple-400 mb-2">
|
||||||
|
How to Reproduce
|
||||||
|
</h3>
|
||||||
|
<pre className="text-sm text-gray-300 bg-black/40 p-4 rounded-lg overflow-x-auto border border-white/10">
|
||||||
|
<code>{project.reproduction}</code>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{project.github && (
|
||||||
|
<a
|
||||||
|
href={project.github}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="inline-flex items-center gap-2 px-6 py-3 rounded-lg bg-gradient-to-r from-purple-500 to-pink-500 text-white font-semibold hover:from-purple-600 hover:to-pink-600 transition-all shadow-lg shadow-purple-500/30 hover:shadow-purple-500/50"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
className="w-5 h-5"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
|
||||||
|
</svg>
|
||||||
|
View on GitHub
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
67
src/components/ProjectCard.tsx
Normal file
67
src/components/ProjectCard.tsx
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import type { Project } from "../types";
|
||||||
|
|
||||||
|
export function ProjectCard({ project }: { project: Project }) {
|
||||||
|
return (
|
||||||
|
<div className="group relative flex flex-col p-6 rounded-2xl bg-gradient-to-br from-white/10 to-white/5 backdrop-blur-sm border border-white/10 hover:border-purple-500/30 transition-all duration-300 hover:-translate-y-1 hover:shadow-2xl hover:shadow-purple-500/10">
|
||||||
|
{project.image && (
|
||||||
|
<div className="mb-6 overflow-hidden rounded-xl bg-black/20 aspect-video flex items-center justify-center relative">
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
|
||||||
|
<img
|
||||||
|
src={project.image}
|
||||||
|
alt={project.name}
|
||||||
|
className={`object-contain ${project.rounded === false ? "w-full h-auto max-h-24 rounded-lg" : "h-24 w-24 rounded-full"} shadow-lg group-hover:scale-110 transition-transform duration-500`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<h3 className="text-xl font-bold text-white mb-2 group-hover:text-purple-400 transition-colors">
|
||||||
|
{project.name}
|
||||||
|
</h3>
|
||||||
|
<p className="text-gray-400 text-sm flex-grow mb-6 leading-relaxed">
|
||||||
|
{project.description}
|
||||||
|
</p>
|
||||||
|
{project.last_commit && (
|
||||||
|
<p className="text-gray-400 text-xs mb-2">
|
||||||
|
Last commit: {new Date(project.last_commit).toLocaleDateString()}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex gap-3 mt-auto">
|
||||||
|
<a
|
||||||
|
href={project.link + "?utm_source=portfolio&ref=space"}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="flex-1 py-2 px-4 rounded-lg bg-white/10 hover:bg-white/20 text-white text-sm font-medium text-center transition-colors flex items-center justify-center gap-2"
|
||||||
|
>
|
||||||
|
Visit
|
||||||
|
<svg
|
||||||
|
className="w-3 h-3"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
{project.open_source && (
|
||||||
|
<a
|
||||||
|
href={project.open_source.link}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="p-2 rounded-lg bg-white/5 hover:bg-white/10 text-gray-400 hover:text-white transition-colors border border-white/5"
|
||||||
|
title="View Source"
|
||||||
|
>
|
||||||
|
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
35
src/components/utils.ts
Normal file
35
src/components/utils.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import type { Experience } from "../types";
|
||||||
|
|
||||||
|
export function getTypeIcon(type: Experience["type"]) {
|
||||||
|
switch (type) {
|
||||||
|
case "language":
|
||||||
|
return "💻";
|
||||||
|
case "service":
|
||||||
|
return "☁️";
|
||||||
|
case "platform":
|
||||||
|
return "🚀";
|
||||||
|
case "real life experience":
|
||||||
|
return "🌍";
|
||||||
|
case "roles":
|
||||||
|
return "👔";
|
||||||
|
default:
|
||||||
|
return "📦";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTypeColor(type: Experience["type"]) {
|
||||||
|
switch (type) {
|
||||||
|
case "language":
|
||||||
|
return "bg-blue-500/20";
|
||||||
|
case "service":
|
||||||
|
return "bg-purple-500/20";
|
||||||
|
case "platform":
|
||||||
|
return "bg-green-500/20";
|
||||||
|
case "real life experience":
|
||||||
|
return "bg-orange-500/20";
|
||||||
|
case "roles":
|
||||||
|
return "bg-pink-500/20";
|
||||||
|
default:
|
||||||
|
return "bg-gray-500/20";
|
||||||
|
}
|
||||||
|
}
|
||||||
32
src/sections/About.tsx
Normal file
32
src/sections/About.tsx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
export function About() {
|
||||||
|
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-blue-400 to-purple-500">
|
||||||
|
Who?
|
||||||
|
</h2>
|
||||||
|
<p className="text-gray-400 max-w-2xl mx-auto">
|
||||||
|
A bit about me, my background, and how I got into programming.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="max-w-3xl mx-auto">
|
||||||
|
<div className="p-6 rounded-2xl bg-gradient-to-br from-blue-500/10 to-purple-500/5 backdrop-blur-sm border border-blue-500/20">
|
||||||
|
<div className="space-y-4 text-gray-300">
|
||||||
|
<p className="leading-relaxed">
|
||||||
|
I'm a Developer from Germany. I love doing full-stack web
|
||||||
|
development, tinkering with hardware, and 3D printing stuff i find quite fancy.
|
||||||
|
</p>
|
||||||
|
<p className="leading-relaxed">
|
||||||
|
I focus on building projects that i need, and that don't
|
||||||
|
already exist. Or break the ones that do exist, to see how they work and learn from them.
|
||||||
|
</p>
|
||||||
|
<p className="leading-relaxed">
|
||||||
|
I started programming around 2017, started of with C# in Unity, then moved to Python for a while, before settling on JavaScript/TypeScript as my main language around 2023.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
30
src/sections/Contact.tsx
Normal file
30
src/sections/Contact.tsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
export function Contact() {
|
||||||
|
return (
|
||||||
|
<section className="w-full max-w-2xl text-center space-y-4 pb-8">
|
||||||
|
<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>
|
||||||
|
<div className="p-8 rounded-3xl bg-gradient-to-br from-blue-500/10 via-purple-500/10 to-pink-500/10 border border-blue-500/20 backdrop-blur-md">
|
||||||
|
<p className="text-gray-300 mb-8 text-lg">
|
||||||
|
Im almost always down to talk about anything. Tech, life and whatever. Feel free to reach out to me on Discord or via Email, i usually respond pretty quickly.
|
||||||
|
</p>
|
||||||
|
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||||
|
<a
|
||||||
|
href="mailto:space@reversed.dev"
|
||||||
|
className="px-8 py-3 rounded-full bg-gradient-to-r from-white to-gray-100 text-black font-bold hover:from-gray-100 hover:to-white transition-all shadow-lg shadow-white/20 hover:shadow-white/30"
|
||||||
|
>
|
||||||
|
Shoot me an Email
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://discord.com/users/456443941169004545"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="px-8 py-3 rounded-full bg-gradient-to-r from-[#5865F2] to-[#4752C4] text-white font-bold hover:from-[#4752C4] hover:to-[#5865F2] transition-all shadow-lg shadow-[#5865F2]/30 hover:shadow-[#5865F2]/50"
|
||||||
|
>
|
||||||
|
Chat on Discord
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
39
src/sections/Footer.tsx
Normal file
39
src/sections/Footer.tsx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
export function Footer() {
|
||||||
|
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{" "}
|
||||||
|
<a
|
||||||
|
href="https://github.com/Space-Banane/shsf"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="text-purple-400 hover:text-purple-300 transition-colors"
|
||||||
|
>
|
||||||
|
SHSF
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</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>
|
||||||
|
);
|
||||||
|
}
|
||||||
75
src/sections/Goals.tsx
Normal file
75
src/sections/Goals.tsx
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
export function Goals() {
|
||||||
|
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-blue-400 to-purple-500">
|
||||||
|
My Goals
|
||||||
|
</h2>
|
||||||
|
<p className="text-gray-400 max-w-2xl mx-auto">Next 4 Years are going to look fun</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<div className="p-6 rounded-2xl bg-gradient-to-br from-blue-500/10 to-purple-500/5 backdrop-blur-sm border border-blue-500/20">
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="p-3 rounded-lg bg-blue-500/20 text-3xl">🚀</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-xl font-bold text-white mb-2">
|
||||||
|
Not Just Semi-Fullstack
|
||||||
|
</h3>
|
||||||
|
<p className="text-gray-400 leading-relaxed">
|
||||||
|
I want to work more with Serverless Architectures and Cloud
|
||||||
|
Services to build scalable applications.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-6 rounded-2xl bg-gradient-to-br from-purple-500/10 to-pink-500/5 backdrop-blur-sm border border-purple-500/20">
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="p-3 rounded-lg bg-purple-500/20 text-3xl">
|
||||||
|
💻
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-xl font-bold text-white mb-2">
|
||||||
|
Contribute More to Open Source
|
||||||
|
</h3>
|
||||||
|
<p className="text-gray-400 leading-relaxed">
|
||||||
|
I want to commit more to Open-Source Projects. That's it...
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-6 rounded-2xl bg-gradient-to-br from-green-500/10 to-blue-500/5 backdrop-blur-sm border border-green-500/20">
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="p-3 rounded-lg bg-green-500/20 text-3xl">
|
||||||
|
⚡
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-xl font-bold text-white mb-2">
|
||||||
|
Expand on Existing Projects
|
||||||
|
</h3>
|
||||||
|
<p className="text-gray-400 leading-relaxed">
|
||||||
|
I want to make SHSF more stable and usable.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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="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">
|
||||||
|
Testing before Breaking
|
||||||
|
</h3>
|
||||||
|
<p className="text-gray-400 leading-relaxed">In the future i want to write more tests and improve my code quality.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
112
src/sections/Hero.tsx
Normal file
112
src/sections/Hero.tsx
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
export function Hero({
|
||||||
|
glowColor,
|
||||||
|
borderStatus,
|
||||||
|
displayMessage,
|
||||||
|
rotatingMessages,
|
||||||
|
statusMessage,
|
||||||
|
showOldNames,
|
||||||
|
setShowOldNames,
|
||||||
|
oldUsernames,
|
||||||
|
}: {
|
||||||
|
glowColor: string;
|
||||||
|
borderStatus: string;
|
||||||
|
displayMessage: string;
|
||||||
|
rotatingMessages: string[];
|
||||||
|
statusMessage: string;
|
||||||
|
showOldNames: boolean;
|
||||||
|
setShowOldNames: (show: boolean) => void;
|
||||||
|
oldUsernames: string[];
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<section className="flex flex-col items-center text-center space-y-8 animate-fade-in">
|
||||||
|
<div className="relative group">
|
||||||
|
<div
|
||||||
|
className={`absolute -inset-1 rounded-full blur opacity-75 transition duration-500`}
|
||||||
|
style={{ backgroundColor: glowColor }}
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
className={`relative w-48 h-48 rounded-full border-4 transition-colors duration-500 overflow-hidden ${borderStatus} bg-black`}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="https://cdn.reversed.dev/pictures/20250405_120402.png"
|
||||||
|
alt="Space"
|
||||||
|
className="w-full h-full object-cover scale-110 transition-transform duration-700 group-hover:scale-125"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Rotating Message Bubble (Left Side) */}
|
||||||
|
<div className="absolute -left-4 top-4 -translate-x-full">
|
||||||
|
<div className="relative bg-gradient-to-br from-purple-500/20 to-pink-500/10 backdrop-blur-sm border border-purple-500/30 rounded-2xl px-4 py-3 shadow-lg">
|
||||||
|
{/* Arrow pointing to profile */}
|
||||||
|
<div className="absolute right-0 top-1/2 translate-x-2 -translate-y-1/2 w-0 h-0 border-t-8 border-t-transparent border-b-8 border-b-transparent border-l-8 border-l-purple-500/30"></div>
|
||||||
|
<div className="absolute right-0 top-1/2 translate-x-1.5 -translate-y-1/2 w-0 h-0 border-t-8 border-t-transparent border-b-8 border-b-transparent border-l-8 border-l-purple-500/20"></div>
|
||||||
|
|
||||||
|
<p className="text-sm text-gray-200 whitespace-nowrap font-medium font-mono">
|
||||||
|
{displayMessage || rotatingMessages[0]}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Status Bubble (Right Side) */}
|
||||||
|
<div className="absolute -right-4 top-8 translate-x-full">
|
||||||
|
<div className="relative bg-gradient-to-br from-white/10 to-white/5 backdrop-blur-sm border border-white/20 rounded-2xl px-4 py-3 shadow-lg">
|
||||||
|
{/* Arrow pointing to profile */}
|
||||||
|
<div className="absolute left-0 top-1/2 -translate-x-2 -translate-y-1/2 w-0 h-0 border-t-8 border-t-transparent border-b-8 border-b-transparent border-r-8 border-r-white/20"></div>
|
||||||
|
<div className="absolute left-0 top-1/2 -translate-x-1.5 -translate-y-1/2 w-0 h-0 border-t-8 border-t-transparent border-b-8 border-b-transparent border-r-8 border-r-white/10"></div>
|
||||||
|
|
||||||
|
<p className="text-sm text-gray-300 whitespace-nowrap">
|
||||||
|
Currently{" "}
|
||||||
|
<span className="text-white font-semibold">
|
||||||
|
{statusMessage}
|
||||||
|
</span>
|
||||||
|
<br />
|
||||||
|
on Discord
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Name & Description */}
|
||||||
|
<div className="space-y-4 max-w-2xl">
|
||||||
|
<h1 className="text-6xl md:text-7xl font-extrabold">
|
||||||
|
Hey, I'm{" "}
|
||||||
|
<span
|
||||||
|
className="relative inline-block text-transparent bg-clip-text bg-gradient-to-r from-blue-400 via-purple-500 to-pink-500 cursor-help px-2"
|
||||||
|
style={{ marginLeft: "0.15em", marginRight: "0.15em" }}
|
||||||
|
onMouseEnter={() => setShowOldNames(true)}
|
||||||
|
onMouseLeave={() => setShowOldNames(false)}
|
||||||
|
>
|
||||||
|
Space²
|
||||||
|
{showOldNames && (
|
||||||
|
<div className="absolute left-1/2 -translate-x-1/2 top-full mt-4 z-50 animate-fade-in flex justify-center w-full">
|
||||||
|
<div className="relative bg-gradient-to-br from-purple-500/20 to-pink-500/10 backdrop-blur-md border border-purple-500/30 rounded-xl px-6 py-4 shadow-2xl min-w-[220px] max-w-xs">
|
||||||
|
<div className="absolute left-1/2 -translate-x-1/2 -top-2 w-0 h-0 border-l-8 border-l-transparent border-r-8 border-r-transparent border-b-8 border-b-purple-500/30"></div>
|
||||||
|
<p className="text-xs text-gray-400 mb-2 font-medium whitespace-nowrap text-center">
|
||||||
|
Also known as:
|
||||||
|
</p>
|
||||||
|
<ul className="flex flex-col items-center gap-1.5">
|
||||||
|
{oldUsernames.map((name, index) => (
|
||||||
|
<li
|
||||||
|
key={index}
|
||||||
|
className="text-sm text-gray-200 font-mono hover:text-purple-400 transition-colors whitespace-nowrap text-center"
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
<p className="text-2xl text-gray-400 font-light">
|
||||||
|
A{" "}
|
||||||
|
<span className="line-through decoration-purple-500/50 decoration-2">
|
||||||
|
Self-proclaimed
|
||||||
|
</span>{" "}
|
||||||
|
Developer breaking things to see how they work.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
44
src/sections/InternshipSection.tsx
Normal file
44
src/sections/InternshipSection.tsx
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
33
src/types.ts
Normal file
33
src/types.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
export interface MiniProject {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
image?: string;
|
||||||
|
github?: string;
|
||||||
|
reproduction?: string;
|
||||||
|
why: string;
|
||||||
|
note?: {
|
||||||
|
color: string;
|
||||||
|
content: string;
|
||||||
|
};
|
||||||
|
last_commit?: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Experience {
|
||||||
|
name: string;
|
||||||
|
type: "language" | "service" | "platform" | "real life experience" | "roles";
|
||||||
|
description: string;
|
||||||
|
image?: string;
|
||||||
|
learned_at?: string;
|
||||||
|
learned_from?: string;
|
||||||
|
learned_because?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Project {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
image?: string;
|
||||||
|
link: string;
|
||||||
|
open_source: false | { link: string };
|
||||||
|
rounded?: boolean;
|
||||||
|
last_commit?: Date;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user