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:
40
components/about-summary.tsx
Normal file
40
components/about-summary.tsx
Normal 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'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
13
components/chip.tsx
Normal 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;
|
30
components/experience-card.tsx
Normal file
30
components/experience-card.tsx
Normal 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
22
components/experience.tsx
Normal 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
88
components/footer.tsx
Normal 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">
|
||||
© 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
66
components/hero.tsx
Normal 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;
|
60
components/image-carousel.tsx
Normal file
60
components/image-carousel.tsx
Normal 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
78
components/navbar.tsx
Normal 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
114
components/project-item.tsx
Normal 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
31
components/skills.tsx
Normal 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;
|
Reference in New Issue
Block a user