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

View File

@@ -0,0 +1,40 @@
const AboutSummary = () => {
return (
<div
id="who-am-i"
className="flex flex-col items-center justify-center gap-4 p-4 rounded w-full"
>
<h3 className="mt-4 mb-2 text-5xl font-bold tracking-tight text-white">
Who am I? 🤔
</h3>
<section className="prose text-white md:prose-lg lg:prose-xl max-w-3xl mx-auto">
<p>
Hi, my name is Gabriel Kaszewski - I&apos;m a Bioinformatics graduate
🧬 and a self-taught full-stack developer 💻. I love creating software
that resolves complex problems, and my most ambitious project yet is
my first game. Take a look at the Steam preview below and consider
adding it to your wishlist!
</p>
<div className="w-full max-w-full aspect-[646/190] mx-auto not-prose">
<iframe
className="w-full h-full"
src="https://store.steampowered.com/widget/3575090/"
></iframe>
</div>
<p>
My journey with programming started when I was 11 🚀. I love solving
problems and creating software that resolves them 👨💻.
</p>
<p>
Currently, I am working as a Python Developer at digimonkeys.com 🐒.
In my free time I like to read about new technologies and work on my
projects 📚.
</p>
</section>
</div>
);
};
export default AboutSummary;

13
components/chip.tsx Normal file
View File

@@ -0,0 +1,13 @@
interface ChipProps {
text: string;
}
const Chip = ({ text }: ChipProps) => {
return (
<p className="text-base text-center rounded-2xl font-semibold tracking-tight text-black bg-yellow-400 p-2 w-32 h-12 flex items-center justify-center glass-effect glossy-effect bottom gloss-highlight">
{text}
</p>
);
};
export default Chip;

View File

@@ -0,0 +1,30 @@
import { Job } from "@/lib/types";
import { CircleUserRound, Building, Clock, Microchip } from "lucide-react";
import Chip from "./chip";
import formatDate from "@/utils/format-date";
const ExperienceCard = ({ job }: { job: Job }) => (
<div className="bg-transparent backdrop-blur-sm glass-effect flex flex-col gap-2 p-4 text-white w-[20rem] max-w-[20rem] rounded-lg shadow-lg">
<h4 className="flex items-center gap-1 text-2xl">
<CircleUserRound /> {job.position}
</h4>
<h5 className="flex items-center gap-1 text-xl font-light">
<Building /> {job.company}
</h5>
<h6 className="flex items-center gap-1">
<Clock />
{formatDate(job.start_date)} -{" "}
{job.still_working ? "Present" : formatDate(job.end_date!)}
</h6>
<p className="flex items-center gap-1 font-bold">
<Microchip /> Technologies
</p>
<div className="flex flex-wrap items-center w-full gap-2">
{job.technologies.map((tech) => (
<Chip key={tech} text={tech} />
))}
</div>
</div>
);
export default ExperienceCard;

22
components/experience.tsx Normal file
View File

@@ -0,0 +1,22 @@
import { Job } from "@/lib/types";
import ExperienceCard from "@/components/experience-card";
const Experience = ({ jobs }: { jobs: Job[] }) => {
return (
<div
id="experience"
className="flex flex-col items-center justify-center gap-4 p-4 rounded w-full"
>
<h3 className="mt-4 mb-2 text-5xl font-bold tracking-tight text-white">
Experience 📈
</h3>
<div className="flex flex-wrap justify-center gap-4">
{jobs.map((job) => (
<ExperienceCard key={job.id} job={job} />
))}
</div>
</div>
);
};
export default Experience;

88
components/footer.tsx Normal file
View File

@@ -0,0 +1,88 @@
import Link from "next/link";
import { Github, Linkedin, Gamepad2 } from "lucide-react";
const Footer = () => {
const currentYear = new Date().getFullYear();
const socialLinks = [
{
title: "github",
href: "https://github.com/GKaszewski",
icon: <Github />,
},
{
title: "linkedin",
href: "https://www.linkedin.com/in/gabriel-kaszewski-5344b3183",
icon: <Linkedin />,
},
{
title: "itch.io",
href: "https://gabrielkaszewski.itch.io/",
icon: <Gamepad2 />,
},
];
return (
<footer className="flex w-full flex-col gap-4 bg-gray-900 p-4 text-white">
<div className="flex items-center gap-2">
<h1 className="text-xl">Gabriel Kaszewski</h1>
<span className="flex-1"></span>
<div className="flex gap-2">
{socialLinks.map((link) => (
<a
key={link.title}
title={link.title}
href={link.href}
target="_blank"
rel="noopener noreferrer"
className="hover:text-yellow-400 transition-colors"
>
{link.icon}
</a>
))}
</div>
</div>
<div className="text-sm">
<p>
Background photo by{" "}
<a className="underline" href="https://duckwithsunglasses.com">
Liam
</a>
</p>
</div>
<div className="flex flex-wrap gap-x-4 gap-y-2 text-sm">
<p className="font-semibold">
&copy; Gabriel Kaszewski, {currentYear}. All rights reserved.
</p>
<p>Made with 💗 in Poland</p>
<span className="flex-1"></span>
<div className="flex gap-4">
<Link
href="/projects"
className="hover:text-yellow-400 transition-colors"
>
Projects
</Link>
<a
href="https://blog.gabrielkaszewski.dev/"
target="_blank"
rel="noopener noreferrer"
className="hover:text-yellow-400 transition-colors"
>
Blog
</a>
<Link
href="/about"
className="hover:text-yellow-400 transition-colors"
>
About
</Link>
</div>
</div>
</footer>
);
};
export default Footer;

66
components/hero.tsx Normal file
View File

@@ -0,0 +1,66 @@
import Image from "next/image";
import { FileText, Github, Mail, Linkedin } from "lucide-react";
const Hero = () => {
const socialLinks = [
{ title: "My CV", href: "/cv.pdf", icon: <FileText /> },
{
title: "GitHub",
href: "https://github.com/GKaszewski",
icon: <Github />,
},
{
title: "My email",
href: "mailto:gabrielkaszewski@gmail.com",
icon: <Mail />,
},
{
title: "LinkedIn",
href: "https://www.linkedin.com/in/gabriel-kaszewski-5344b3183",
icon: <Linkedin />,
},
];
return (
<div className="w-full">
<div className="relative w-full h-screen">
<Image
src="/images/hero.avif"
alt="Background"
fill
priority
className="hidden object-cover pointer-events-none md:block"
/>
<div className="flex flex-col items-center justify-center md:justify-start w-full h-full md:inset-0 md:absolute md:items-start md:p-16 lg:p-20">
<div className="text-center md:text-left">
<h1 className="mb-4 text-4xl font-bold tracking-tight text-white md:text-6xl">
Gabriel Kaszewski
</h1>
<h2 className="text-xl font-light tracking-tight text-white md:text-2xl">
Full-Stack Developer
</h2>
</div>
<div className="flex items-center gap-4 mt-4">
{socialLinks.map((link) => (
<a
key={link.title}
href={link.href}
title={link.title}
target="_blank"
rel="noopener noreferrer"
className="text-white hover:text-yellow-400"
>
{link.icon}
</a>
))}
</div>
<div className="absolute bottom-0 hidden py-2 text-sm md:block">
<p>Photo by me Łazy, 2018 (compressed)</p>
</div>
</div>
</div>
</div>
);
};
export default Hero;

View File

@@ -0,0 +1,60 @@
"use client";
import { useState } from "react";
import Image from "next/image";
interface ImageCarouselProps {
id: string;
thumbnails: string[];
}
const ImageCarousel = ({ id, thumbnails }: ImageCarouselProps) => {
const [currentIndex, setCurrentIndex] = useState(0);
const goToSlide = (slideIndex: number) => {
setCurrentIndex(slideIndex);
};
return (
<div
id={id}
className="carousel relative shadow-lg w-full max-w-full md:max-w-[50hw] h-[40rem]"
>
<div className="relative w-full h-full overflow-hidden carousel-inner">
{thumbnails.map((thumbnail, index) => (
<div
key={index}
className={`absolute inset-0 w-full h-full transition-opacity duration-500 ease-in-out delay-250 ${
currentIndex === index ? "opacity-100" : "opacity-0"
}`}
>
<Image
alt={`Slide ${index + 1}`}
src={thumbnail}
fill
style={{ objectFit: "scale-down" }}
priority={index === 0}
/>
</div>
))}
</div>
<div className="absolute bottom-0 z-10 flex justify-center w-full gap-2 py-2">
{thumbnails.map((_, index) => (
<button
key={index}
onClick={() => goToSlide(index)}
className={`text-2xl leading-none ${
currentIndex === index ? "text-white" : "text-white/50"
}`}
aria-label={`Go to slide ${index + 1}`}
>
</button>
))}
</div>
</div>
);
};
export default ImageCarousel;

78
components/navbar.tsx Normal file
View File

@@ -0,0 +1,78 @@
"use client";
import { useState, useEffect } from "react";
import Link from "next/link";
import { usePathname } from "next/navigation";
const Navbar = () => {
const [isScrolled, setIsScrolled] = useState(false);
const pathname = usePathname();
useEffect(() => {
const handleScroll = () => {
setIsScrolled(window.scrollY > 10);
};
window.addEventListener("scroll", handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll);
};
}, []);
const navLinks = [
{ href: "/", label: "Home" },
{ href: "/projects", label: "Projects" },
{
href: "https://blog.gabrielkaszewski.dev/",
label: "Blog",
external: true,
},
{ href: "/about", label: "About" },
];
const baseClasses =
"fixed z-20 flex w-full items-center justify-center p-4 transition-all duration-300";
const scrolledClasses = "bg-gray-900/80 backdrop-blur-md";
const transparentClasses = "bg-transparent";
return (
<nav
className={`${baseClasses} ${
isScrolled ? scrolledClasses : transparentClasses
}`}
>
<div className="flex flex-1"></div>
<div className="flex gap-4">
{navLinks.map((link) => {
const isActive = pathname === link.href;
const linkClasses = `text-lg hover:text-yellow-400 transition-colors ${
isActive ? "text-yellow-400 font-semibold" : "text-white"
}`;
if (link.external) {
return (
<a
key={link.href}
href={link.href}
className={linkClasses}
target="_blank"
rel="noopener noreferrer"
>
{link.label}
</a>
);
}
return (
<Link key={link.href} href={link.href} className={linkClasses}>
{link.label}
</Link>
);
})}
</div>
</nav>
);
};
export default Navbar;

114
components/project-item.tsx Normal file
View File

@@ -0,0 +1,114 @@
import Link from "next/link";
import { Project } from "@/lib/types";
import Chip from "@/components/chip";
import ImageCarousel from "@/components/image-carousel";
import {
Eye,
CloudDownload,
AppWindow,
Smartphone,
Globe,
MonitorCog,
Gamepad2,
Github,
} from "lucide-react";
const PlaceholderIcon = ({ category }: { category: Project["category"] }) => {
const iconProps = { size: 128, className: "text-white/80" };
switch (category) {
case "Desktop":
return <AppWindow {...iconProps} />;
case "Mobile":
return <Smartphone {...iconProps} />;
case "Web":
return <Globe {...iconProps} />;
case "Api":
return <MonitorCog {...iconProps} />;
case "Game":
return <Gamepad2 {...iconProps} />;
default:
return null;
}
};
interface ProjectItemProps {
project: Project;
}
const ProjectItem = ({ project }: ProjectItemProps) => {
const ImageDisplay = (
<div className="bg-gradient-to-r from-violet-600 to-indigo-600 shadow-lg w-full h-[40rem] flex items-center justify-center rounded-lg">
{project.thumbnails && project.thumbnails.length > 0 ? (
<ImageCarousel
id={`carousel-${project.id}`}
thumbnails={project.thumbnails}
/>
) : (
<PlaceholderIcon category={project.category} />
)}
</div>
);
return (
<div className="flex flex-col md:flex-row items-center justify-between w-full h-full gap-8 text-white p-4">
<div className="flex flex-col w-full gap-4 md:w-1/3 backdrop-blur-2xl glass-effect glossy-effect bottom p-4 rounded-md h-full">
<div className="prose prose-invert">
<h1>{project.name}</h1>
<p className="whitespace-pre-wrap">{project.short_description}</p>
</div>
<div className="flex flex-wrap gap-2">
{project.technologies.map((tech) => (
<Chip key={tech} text={tech} />
))}
</div>
<Link
href={`/projects/${project.name}`}
className="text-center glass-effect glossy-effect bottom gloss-highlight p-2 rounded-2xl bg-yellow-400 hover:bg-yellow-500 text-black font-bold"
>
Read more
</Link>
<div className="flex flex-col sm:flex-row gap-2 w-full">
{project.github_url && (
<a
href={project.github_url}
target="_blank"
rel="noopener noreferrer"
className="flex-1 flex items-center justify-center gap-2 text-center glass-effect glossy-effect bottom gloss-highlight p-2 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-1 flex items-center justify-center gap-2 text-center glass-effect glossy-effect bottom gloss-highlight p-2 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-1 flex items-center justify-center gap-2 text-center glass-effect glossy-effect bottom gloss-highlight p-2 rounded-2xl bg-yellow-400 hover:bg-yellow-500 text-black font-bold"
>
<CloudDownload size={20} /> DOWNLOAD
</a>
)}
</div>
<div className="w-full mt-4 md:hidden">{ImageDisplay}</div>
</div>
<div className="hidden md:flex md:w-1/2">{ImageDisplay}</div>
</div>
);
};
export default ProjectItem;

31
components/skills.tsx Normal file
View File

@@ -0,0 +1,31 @@
import Chip from "@/components/chip";
import { Skill } from "@/lib/types";
interface SkillsProps {
skills: Skill[];
}
const Skills = ({ skills }: SkillsProps) => {
return (
<div
id="skills"
className="flex flex-col items-center justify-center gap-4 p-4 rounded w-full"
>
<h3 className="mt-4 mb-2 text-5xl font-bold tracking-tight text-white">
Skills 🛠
</h3>
<div className="flex flex-wrap justify-center w-full max-w-lg gap-4">
{skills.map((skill, index) => (
<div
key={index}
className="odd:motion-preset-slide-left even:motion-preset-slide-right odd:motion-delay-100"
>
<Chip text={skill.name} />
</div>
))}
</div>
</div>
);
};
export default Skills;