feat: add AboutSummary component with personal introduction and game preview

feat: create Chip component for displaying technology tags

feat: implement ExperienceCard component to showcase job experiences

feat: add Experience component to list multiple job experiences

feat: create Footer component with social links and copyright information

feat: implement Hero component for the landing section with social links

feat: add ImageCarousel component for displaying project images

feat: create Navbar component with scroll effect and navigation links

feat: implement ProjectItem component to display individual project details

feat: add Skills component to showcase technical skills

feat: create data module with skills, jobs, and projects information

feat: define types for Skill, Job, and Project in types module

chore: update package.json with new dependencies for Tailwind CSS and Lucide icons

chore: add CV PDF file to public directory

chore: remove unused SVG files from public directory

chore: add new images for background and hero sections

feat: implement formatDate utility function for date formatting
This commit is contained in:
2025-09-08 19:12:30 +02:00
parent fab7310436
commit a99b6353be
32 changed files with 1522 additions and 107 deletions

206
app/about/page.tsx Normal file
View File

@@ -0,0 +1,206 @@
import Image from "next/image";
import Chip from "@/components/chip";
import { Metadata } from "next";
import { Check } from "lucide-react";
export const metadata: Metadata = {
title: "About Me | Gabriel Kaszewski",
description:
"Learn more about Gabriel Kaszewski, his skills, and his journey as a developer.",
};
const hobbies = [
"Programming 💻",
"Filmmaking 🎥",
"Gaming 🕹️",
"Playing guitar 🎸",
];
const interests = [
"Computer Science 💾",
"Sci-Fi Books📚",
"Astronomy 🔭",
"Sports 🏅",
"History 🏰",
];
const futureGoals = [
"Deepen my expertise in Rust for high-performance applications.",
"Contribute to impactful open-source projects.",
"Develop and release my first full-fledged indie game.",
];
const faqItems = [
{
q: "How old were you when you started programming?",
a: "I was 11 years old 🧑‍💻.",
},
{
q: "How did you learn programming?",
a: "I read books 📖 and practiced a lot.",
},
{
q: "Are you studying Computer Science?",
a: "No, I have a degree in Bioinformatics, which is a closely related field.",
},
{
q: "Which programming language do you recommend for a beginner?",
a: "The language doesn't really matter, just choose one you like and dive in 🌊.",
},
{
q: "What was your first programming language?",
a: "My journey began with C++ 🖥️.",
},
{
q: "What is your favorite programming language?",
a: "I enjoy writing in C, Rust, and Python 🐍. But Rust is the best 💖🦀",
},
];
const calculateAge = (birthDate: string): number => {
const today = new Date();
const birth = new Date(birthDate);
let age = today.getFullYear() - birth.getFullYear();
const monthDifference = today.getMonth() - birth.getMonth();
if (
monthDifference < 0 ||
(monthDifference === 0 && today.getDate() < birth.getDate())
) {
age--;
}
return age;
};
const AboutPage = () => {
const age = calculateAge("2002-02-27");
return (
<div className="flex w-full flex-col items-center gap-8 p-4 pt-24 text-white">
<div className="flex flex-col items-center justify-center gap-2 p-4 backdrop-blur-sm glass-effect glossy-effect bottom gloss-highlight rounded-lg shadow-lg">
<Image
src="/images/ja.avif"
alt="A photo of Gabriel Kaszewski"
width={300}
height={300}
className="object-cover rounded-md shadow-lg"
priority
/>
<h2 className="mt-4 text-2xl font-bold">Hello, I&apos;m Gabriel! 👋</h2>
</div>
<div className="prose prose-invert lg:prose-lg xl:prose-xl max-w-3xl mx-auto w-full">
<section id="more-info">
<h1 className="text-center">More info about me! 💡</h1>
<p>
Hi! I am Gabriel and I am {age} years old. I graduated in
Bioinformatics from the University of Gdańsk 🏫. I&apos;m fluent in
Polish and English and currently work as a Python Developer at
digimonkeys.com 🐒💻.
</p>
<p>
I have co-authored one scientific article, which you can read{" "}
<a
target="_blank"
href="http://dx.doi.org/10.1038/s41598-023-44488-7"
className="text-yellow-400 underline"
>
here
</a>
.
</p>
</section>
<section
id="hobbies&interests"
className="not-prose flex flex-col sm:flex-row gap-8 mt-12"
>
<div className="flex-1">
<h2 className="text-center">Hobbies 🎮🎸</h2>
<div className="flex flex-wrap items-center justify-center gap-4 mt-4">
{hobbies.map((hobby) => (
<Chip key={hobby} text={hobby} />
))}
</div>
</div>
<div className="flex-1">
<h2 className="text-center">Interests 🌌📚</h2>
<div className="flex flex-wrap items-center justify-center gap-4 mt-4">
{interests.map((interest) => (
<Chip key={interest} text={interest} />
))}
</div>
</div>
</section>
<section id="philosophy" className="mt-12">
<h1>My Philosophy 🧠</h1>
<p>
I believe much of today&apos;s software is bloated, inefficient, and
disrespectful of the user&apos;s resources. My passion, which
started with a simple curiosity at age 11, is to build a better
alternative. I focus on creating software that is{" "}
<strong className="text-yellow-400">
fast, reliable, and genuinely intuitive
</strong>
, guided by the principles of clean and efficient code.
</p>
</section>
<section id="toolkit">
<h1>My Toolkit 🛠</h1>
<div className="not-prose bg-black/20 backdrop-blur-sm glass-effect rounded-2xl p-4 text-white text-shadow-sm">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<h3 className="text-xl font-bold">OS & Hardware</h3>
<p>
Arch Linux, Custom-built PC (Ryzen 7 5800X3D, RTX 4070 Ti,
48GB RAM)
</p>
</div>
<div>
<h3 className="text-xl font-bold">Editor</h3>
<p>Jetbrains IDEs (Pycharm, Rider) & VS Code</p>
</div>
<div>
<h3 className="text-xl font-bold">Primary Languages</h3>
<p>Rust, Python, C#, TypeScript</p>
</div>
<div>
<h3 className="text-xl font-bold">Favorite Tech</h3>
<p>Axum, Godot, React, TailwindCSS</p>
</div>
</div>
</div>
</section>
<section id="future-goals" className="mt-12">
<h1>Future Goals 🚀</h1>
<p>
I&apos;m always eager to learn and grow. My goal is to continue
honing my skills in backend development and system architecture.
Here&apos;s what&apos;s on my radar:
</p>
<ul className="list-none p-0">
{futureGoals.map((goal) => (
<li key={goal} className="flex items-center gap-2 not-prose mb-2">
<Check className="text-yellow-400 flex-shrink-0" size={20} />
<span>{goal}</span>
</li>
))}
</ul>
</section>
<section id="faq" className="mt-12">
<h1>FAQ </h1>
<div className="not-prose flex flex-col gap-4">
{faqItems.map((item) => (
<div key={item.q}>
<h3 className="text-xl font-bold">{item.q}</h3>
<p>{item.a}</p>
</div>
))}
</div>
</section>
</div>
</div>
);
};
export default AboutPage;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -1,4 +1,6 @@
@import "tailwindcss";
@plugin "@tailwindcss/typography";
@plugin "tailwindcss-motion";
:root {
--background: #ffffff;
@@ -6,10 +8,30 @@
}
@theme inline {
--card: hsl(0 0% 100%);
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
--gradient-fa-blue: 135deg, hsl(217 91% 60%) 0%, hsl(200 90% 70%) 100%;
--gradient-fa-green: 135deg, hsl(155 70% 55%) 0%, hsl(170 80% 65%) 100%;
--gradient-fa-card: 180deg, hsl(var(--card)) 0%, hsl(var(--card)) 90%,
hsl(var(--card)) 100%;
--gradient-fa-gloss: 135deg, rgba(255, 255, 255, 0.2) 0%,
rgba(255, 255, 255, 0) 100%;
--shadow-fa-sm: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06);
--shadow-fa-md: 0 4px 6px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.06);
--shadow-fa-lg: 0 10px 15px rgba(0, 0, 0, 0.1), 0 4px 6px rgba(0, 0, 0, 0.05);
--fa-inner: inset 0 1px 2px rgba(0, 0, 0, 0.1);
--text-shadow-default: 0 1px 1px rgba(0, 0, 0, 0.2);
--text-shadow-sm: 0 1px 0px rgba(255, 255, 255, 0.4);
--text-shadow-md: 0 2px 2px rgba(0, 0, 0, 0.2);
--text-shadow-lg: 0 4px 4px rgba(0, 0, 0, 0.2);
}
@media (prefers-color-scheme: dark) {
@@ -20,7 +42,149 @@
}
body {
background: var(--background);
/* background: var(--background); */
background-image: url("/images/background.avif");
background-size: cover;
background-attachment: fixed;
background-position: center;
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
}
@layer base {
glossy-effect::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
height: 50%;
border-radius: inherit;
background: linear-gradient(
180deg,
rgba(255, 255, 255, 0.4) 0%,
rgba(255, 255, 255, 0.1) 100%
);
opacity: 0.8;
pointer-events: none;
z-index: 1;
}
.glossy-effect.bottom::after {
content: "";
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 30%;
border-radius: inherit;
background: linear-gradient(
0deg,
rgba(0, 0, 0, 0.1) 0%,
rgba(0, 0, 0, 0) 100%
);
pointer-events: none;
z-index: 1;
}
.fa-gradient-blue {
background: linear-gradient(var(--gradient-fa-blue));
}
.fa-gradient-green {
background: linear-gradient(var(--gradient-fa-green));
}
.fa-gradient-card {
background: linear-gradient(var(--gradient-fa-card));
}
.fa-gloss {
position: relative;
}
.fa-gloss::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
height: 50%;
border-radius: inherit;
background: linear-gradient(var(--gradient-fa-gloss));
opacity: 0.8;
pointer-events: none;
z-index: 1;
}
.fa-gloss.bottom::after {
content: "";
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 30%;
border-radius: inherit;
background: linear-gradient(
0deg,
rgba(0, 0, 0, 0.1) 0%,
rgba(0, 0, 0, 0) 100%
);
pointer-events: none;
z-index: 1;
}
}
@layer components {
.shadow-fa-sm {
box-shadow: var(--shadow-fa-sm), var(--fa-inner);
}
.shadow-fa-md {
box-shadow: var(--shadow-fa-md), var(--fa-inner);
}
.shadow-fa-lg {
box-shadow: var(--shadow-fa-lg), var(--fa-inner);
}
.text-shadow-default {
text-shadow: var(--text-shadow-default);
}
.text-shadow-sm {
text-shadow: var(--text-shadow-sm);
}
.text-shadow-md {
text-shadow: var(--text-shadow-md);
}
.text-shadow-lg {
text-shadow: var(--text-shadow-lg);
}
.glass-effect {
@apply backdrop-blur-lg border border-white/20 shadow-fa-lg;
}
.gloss-highlight::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
height: 60%;
border-radius: inherit;
background: linear-gradient(
180deg,
rgba(255, 255, 255, 0.5) 0%,
rgba(255, 255, 255, 0) 100%
);
pointer-events: none;
z-index: 1;
}
}
@layer utilities {
.my-animate {
opacity: 1 !important;
transform: translateY(0) !important;
transition: opacity 0.3s ease, transform 0.3s ease;
}
.hidden-for-animation {
opacity: 0;
transform: translateY(20px);
transition: opacity 0.3s ease, transform 0.3s ease;
}
}

View File

@@ -1,6 +1,9 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import Navbar from "@/components/navbar";
import Footer from "@/components/footer";
import Image from "next/image";
const geistSans = Geist({
variable: "--font-geist-sans",
@@ -13,8 +16,8 @@ const geistMono = Geist_Mono({
});
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
title: "Gabriel Kaszewski",
description: "Welcome to my portfolio",
};
export default function RootLayout({
@@ -25,9 +28,11 @@ export default function RootLayout({
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
className={`${geistSans.variable} ${geistMono.variable} antialiased relative`}
>
<Navbar />
{children}
<Footer />
</body>
</html>
);

View File

@@ -1,103 +1,18 @@
import Image from "next/image";
import AboutSummary from "@/components/about-summary";
import Experience from "@/components/experience";
import Hero from "@/components/hero";
import Skills from "@/components/skills";
import { skills, jobs } from "@/lib/data";
export default function Home() {
return (
<div className="font-sans grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20">
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
<Image
className="dark:invert"
src="/next.svg"
alt="Next.js logo"
width={180}
height={38}
priority
/>
<ol className="font-mono list-inside list-decimal text-sm/6 text-center sm:text-left">
<li className="mb-2 tracking-[-.01em]">
Get started by editing{" "}
<code className="bg-black/[.05] dark:bg-white/[.06] font-mono font-semibold px-1 py-0.5 rounded">
app/page.tsx
</code>
.
</li>
<li className="tracking-[-.01em]">
Save and see your changes instantly.
</li>
</ol>
<div className="flex gap-4 items-center flex-col sm:flex-row">
<a
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:w-auto"
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
className="dark:invert"
src="/vercel.svg"
alt="Vercel logomark"
width={20}
height={20}
/>
Deploy now
</a>
<a
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-full sm:w-auto md:w-[158px]"
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Read our docs
</a>
</div>
</main>
<footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center">
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/file.svg"
alt="File icon"
width={16}
height={16}
/>
Learn
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/window.svg"
alt="Window icon"
width={16}
height={16}
/>
Examples
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/globe.svg"
alt="Globe icon"
width={16}
height={16}
/>
Go to nextjs.org
</a>
</footer>
<div className="flex flex-col items-center w-full">
<Hero />
<div className="container mx-auto px-4">
<AboutSummary />
<Skills skills={skills} />
<Experience jobs={jobs} />
</div>
</div>
);
}

View File

@@ -0,0 +1,139 @@
import { projects } from "@/lib/data";
import { Project } from "@/lib/types";
import Chip from "@/components/chip";
import { Metadata } from "next";
import Image from "next/image";
import { Github, Eye, CloudDownload } from "lucide-react";
import { notFound } from "next/navigation";
import { remark } from "remark";
import html from "remark-html";
function getProjectByName(name: string): Project | undefined {
const decodedName = decodeURIComponent(name.replace(/\+/g, " "));
return projects.find(
(p) => p.name.toLowerCase() === decodedName.toLowerCase()
);
}
export async function generateStaticParams() {
return projects.map((project) => ({
projectName: encodeURIComponent(project.name.replace(/\s/g, "+")),
}));
}
export async function generateMetadata({
params,
}: {
params: { projectName: string };
}): Promise<Metadata> {
const { projectName } = await params;
const project = getProjectByName(projectName);
if (!project) {
return { title: "Project Not Found" };
}
return {
title: `${project.name} | Gabriel Kaszewski`,
description: project.short_description,
};
}
async function getProjectData(project: Project) {
const processedContent = await remark()
.use(html)
.process(project.description);
return processedContent.toString();
}
export default async function ProjectDetailPage({
params,
}: {
params: { projectName: string };
}) {
const { projectName } = await params;
const project = getProjectByName(projectName);
const descriptionHtml = project ? await getProjectData(project) : "";
if (!project) {
notFound();
}
const hasLinks =
project.github_url || project.visit_url || project.download_url;
return (
<div className="flex flex-col w-full h-full min-h-screen gap-4 p-4 pt-24">
<div className="prose prose-invert lg:prose-lg xl:prose-xl max-w-4xl mx-auto">
<h1>{project.name}</h1>
<section dangerouslySetInnerHTML={{ __html: descriptionHtml }} />
<section className="not-prose mt-12 flex flex-col items-center">
<h2>Technologies</h2>
<div className="flex flex-wrap justify-center gap-2 mt-4">
{project.technologies.map((tech) => (
<Chip key={tech} text={tech} />
))}
</div>
</section>
{project.thumbnails && project.thumbnails.length > 0 && (
<section className="not-prose mt-12 flex flex-col items-center">
<h2>Gallery</h2>
<div className="flex flex-col gap-4 mt-4">
{project.thumbnails.map((thumb, index) => (
<Image
key={index}
src={thumb}
alt={`${project.name} thumbnail ${index + 1}`}
width={1024}
height={768}
className="rounded-lg shadow-lg"
/>
))}
</div>
</section>
)}
{hasLinks && (
<section className="not-prose mt-12 flex flex-col items-center">
<h2>Links</h2>
<div className="flex flex-col sm:flex-row gap-4 mt-4">
{project.github_url && (
<a
href={project.github_url}
target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-center gap-2 p-2 px-4 text-center glass-effect glossy-effect bottom gloss-highlight rounded-2xl bg-yellow-400 hover:bg-yellow-500 text-black font-bold"
>
<Github size={20} /> CODE
</a>
)}
{project.visit_url && (
<a
href={project.visit_url}
target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-center gap-2 p-2 px-4 text-center glass-effect glossy-effect bottom gloss-highlight rounded-2xl bg-yellow-400 hover:bg-yellow-500 text-black font-bold"
>
<Eye size={20} /> LIVE
</a>
)}
{project.download_url && (
<a
href={project.download_url}
target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-center gap-2 p-2 px-4 text-center glass-effect glossy-effect bottom gloss-highlight rounded-2xl bg-yellow-400 hover:bg-yellow-500 text-black font-bold"
>
<CloudDownload size={20} /> DOWNLOAD
</a>
)}
</div>
</section>
)}
</div>
</div>
);
}

29
app/projects/page.tsx Normal file
View File

@@ -0,0 +1,29 @@
import ProjectItem from "@/components/project-item";
import { projects } from "@/lib/data";
import { Metadata } from "next";
export const metadata: Metadata = {
title: "My Projects | Gabriel Kaszewski",
description: "A showcase of projects by Gabriel Kaszewski.",
};
const ProjectsPage = () => {
return (
<div className="flex w-full h-full min-h-screen flex-col items-center gap-4 pt-24">
<h1 className="text-5xl font-bold text-center text-white">My Projects</h1>
<div className="w-full flex flex-col items-center gap-16 mt-8">
{projects.map((project) => (
<ProjectItem key={project.id} project={project} />
))}
{projects.length === 0 && (
<p className="text-white text-center">
No projects available. Working on it!
</p>
)}
</div>
</div>
);
};
export default ProjectsPage;