Compare commits
35 Commits
a6353da49c
...
gravity
| Author | SHA1 | Date | |
|---|---|---|---|
| 7b115c2284 | |||
| a3ccba0663 | |||
| cc284c27a9 | |||
| 7cec30170a | |||
| abe307a6ca | |||
| 7cc57e966b | |||
| 2ba0b90fce | |||
| 415dcb8459 | |||
| abde9ff0e9 | |||
| 571cf35151 | |||
| abb7651e41 | |||
| c34d069d54 | |||
| 6bc9456b2d | |||
| 881b34842e | |||
| b5007f2e31 | |||
| 32faa559fa | |||
| ccde0af995 | |||
| 695ebf707f | |||
| f914d14f15 | |||
| 39cb194b5b | |||
| 51820e6507 | |||
| 26b798f2d5 | |||
| 7db4b71984 | |||
| d265599280 | |||
| d1dab290a4 | |||
| 292a5d2587 | |||
| 0bdef596b4 | |||
| 987b589f72 | |||
| 333c87e3dc | |||
| eb10977086 | |||
| 6ed0fe41be | |||
| e21671b4f0 | |||
| 9e7c900d85 | |||
| 8714949289 | |||
| a0463d1db9 |
@@ -9,18 +9,13 @@ export const metadata: Metadata = {
|
||||
"Learn more about Gabriel Kaszewski, his skills, and his journey as a developer.",
|
||||
};
|
||||
|
||||
const hobbies = [
|
||||
"Programming 💻",
|
||||
"Filmmaking 🎥",
|
||||
"Gaming 🕹️",
|
||||
"Playing guitar 🎸",
|
||||
];
|
||||
const hobbies = ["Programming", "Filmmaking", "Gaming", "Playing guitar"];
|
||||
const interests = [
|
||||
"Computer Science 💾",
|
||||
"Sci-Fi Books📚",
|
||||
"Astronomy 🔭",
|
||||
"Sports 🏅",
|
||||
"History 🏰",
|
||||
"Computer Science",
|
||||
"Sci-Fi Books",
|
||||
"Astronomy",
|
||||
"Sports",
|
||||
"History",
|
||||
];
|
||||
const futureGoals = [
|
||||
"Deepen my expertise in Rust for high-performance applications.",
|
||||
@@ -30,11 +25,11 @@ const futureGoals = [
|
||||
const faqItems = [
|
||||
{
|
||||
q: "How old were you when you started programming?",
|
||||
a: "I was 11 years old 🧑💻.",
|
||||
a: "I was 11 years old.",
|
||||
},
|
||||
{
|
||||
q: "How did you learn programming?",
|
||||
a: "I read books 📖 and practiced a lot.",
|
||||
a: "I read books and practiced a lot.",
|
||||
},
|
||||
{
|
||||
q: "Are you studying Computer Science?",
|
||||
@@ -42,15 +37,15 @@ const faqItems = [
|
||||
},
|
||||
{
|
||||
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 🌊.",
|
||||
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++ 🖥️.",
|
||||
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 💖🦀",
|
||||
a: "I enjoy writing in C, Rust, and Python. But Rust is the best",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -72,7 +67,7 @@ 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 w-full flex-col items-center gap-8 p-4 pt-24 text-white gravity-body">
|
||||
<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"
|
||||
@@ -82,17 +77,17 @@ const AboutPage = () => {
|
||||
className="object-cover rounded-md shadow-lg"
|
||||
priority
|
||||
/>
|
||||
<h2 className="mt-4 text-2xl font-bold">Hello, I'm Gabriel! 👋</h2>
|
||||
<h2 className="mt-4 text-2xl font-bold">Hello, I'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>
|
||||
<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'm fluent in
|
||||
Polish and English and currently work as a Python Developer at
|
||||
digimonkeys.com 🐒💻.
|
||||
Bioinformatics from the University of Gdańsk. I'm fluent in
|
||||
Polish and English and currently work as a Software Engineer at WPP
|
||||
Media.
|
||||
</p>
|
||||
<p>
|
||||
I have co-authored one scientific article, which you can read{" "}
|
||||
@@ -124,7 +119,7 @@ const AboutPage = () => {
|
||||
className="not-prose flex flex-col sm:flex-row gap-8 mt-12"
|
||||
>
|
||||
<div className="flex-1">
|
||||
<h2 className="text-center">Hobbies 🎮🎸</h2>
|
||||
<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} />
|
||||
@@ -132,7 +127,7 @@ const AboutPage = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h2 className="text-center">Interests 🌌📚</h2>
|
||||
<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} />
|
||||
@@ -142,7 +137,7 @@ const AboutPage = () => {
|
||||
</section>
|
||||
|
||||
<section id="philosophy" className="mt-12">
|
||||
<h1>My Philosophy 🧠</h1>
|
||||
<h1>My Philosophy</h1>
|
||||
<p>
|
||||
I believe much of today's software is bloated, inefficient, and
|
||||
disrespectful of the user's resources. My passion, which
|
||||
@@ -156,7 +151,7 @@ const AboutPage = () => {
|
||||
</section>
|
||||
|
||||
<section id="toolkit">
|
||||
<h1>My Toolkit 🛠️</h1>
|
||||
<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>
|
||||
@@ -183,7 +178,7 @@ const AboutPage = () => {
|
||||
</section>
|
||||
|
||||
<section id="future-goals" className="mt-12">
|
||||
<h1>Future Goals 🚀</h1>
|
||||
<h1>Future Goals</h1>
|
||||
<p>
|
||||
I'm always eager to learn and grow. My goal is to continue
|
||||
honing my skills in backend development and system architecture.
|
||||
@@ -200,7 +195,7 @@ const AboutPage = () => {
|
||||
</section>
|
||||
|
||||
<section id="faq" className="mt-12">
|
||||
<h1>FAQ ❓</h1>
|
||||
<h1>FAQ</h1>
|
||||
<div className="not-prose flex flex-col gap-4">
|
||||
{faqItems.map((item) => (
|
||||
<div key={item.q}>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import { ExternalLink, Github } from "lucide-react";
|
||||
|
||||
@@ -47,7 +45,7 @@ const kSuiteApps: KSuiteApp[] = [
|
||||
description:
|
||||
"Tune guitar, ukulele, and piano with this Frutiger Aero styled PWA.",
|
||||
url: "https://tuner.gabrielkaszewski.dev/",
|
||||
githubUrl: "https://github.com/GKaszewski/aero-tuner",
|
||||
githubUrl: "https://github.com/GKaszewski/k-tuner",
|
||||
icon: "/images/k-tuner.png",
|
||||
technologies: ["React", "PWA"],
|
||||
color: "from-emerald-400 to-teal-500",
|
||||
@@ -62,7 +60,19 @@ const kSuiteApps: KSuiteApp[] = [
|
||||
githubUrl: "https://github.com/GKaszewski/k-qr",
|
||||
icon: "/images/k-qr.png",
|
||||
technologies: ["Rust", "HTML"],
|
||||
color: "from-amber-400 to-orange-500"
|
||||
color: "from-amber-400 to-orange-500",
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "K-TV",
|
||||
shortDescription: "Classic broadcast TV from your media library",
|
||||
description:
|
||||
"Turn Jellyfin, Plex, or local files into a classic broadcast TV experience. Create channels, define programming blocks, and set up schedules.",
|
||||
url: "https://tv.gabrielkaszewski.dev/",
|
||||
githubUrl: "https://git.gabrielkaszewski.dev/GKaszewski/k-tv",
|
||||
icon: "/images/k-tv.png",
|
||||
technologies: ["Rust", "Next.js", "TailwindCSS"],
|
||||
color: "from-violet-400 to-purple-500",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -71,9 +81,11 @@ const connections = [
|
||||
{ from: 0, to: 1 }, // K-Notes -> Thoughts
|
||||
{ from: 1, to: 2 }, // Thoughts -> K-Tuner
|
||||
{ from: 2, to: 3 }, // K-Tuner -> K-QR
|
||||
{ from: 3, to: 0 }, // K-QR -> K-Notes
|
||||
{ from: 3, to: 4 }, // K-QR -> K-TV
|
||||
{ from: 4, to: 0 }, // K-TV -> K-Notes
|
||||
{ from: 0, to: 2 }, // K-Notes -> K-Tuner (cross)
|
||||
{ from: 1, to: 3 }, // Thoughts -> K-QR (cross)
|
||||
{ from: 2, to: 4 }, // K-Tuner -> K-TV (cross)
|
||||
];
|
||||
|
||||
const KSuiteOrganism = () => {
|
||||
@@ -88,7 +100,7 @@ const KSuiteOrganism = () => {
|
||||
|
||||
const radius = 35;
|
||||
const positions = kSuiteApps.map((_, i) =>
|
||||
getPosition(i, kSuiteApps.length, radius)
|
||||
getPosition(i, kSuiteApps.length, radius),
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -134,8 +146,6 @@ const KSuiteOrganism = () => {
|
||||
);
|
||||
})}
|
||||
|
||||
|
||||
|
||||
{/* Lines from center to each app */}
|
||||
{positions.map((pos, i) => (
|
||||
<line
|
||||
@@ -288,7 +298,7 @@ const KSuiteAppCard = ({ app }: { app: KSuiteApp }) => {
|
||||
|
||||
export default function KSuitePage() {
|
||||
return (
|
||||
<div className="min-h-screen pt-20">
|
||||
<div className="min-h-screen pt-20 gravity-body">
|
||||
{/* Hero Section */}
|
||||
<section className="relative py-16 md:py-24 overflow-hidden">
|
||||
<div className="container mx-auto px-4 text-center">
|
||||
@@ -300,8 +310,8 @@ export default function KSuitePage() {
|
||||
designed to restore digital sovereignty to the user.
|
||||
</p>
|
||||
<p className="text-base text-white/60 max-w-2xl mx-auto mb-12">
|
||||
A "Personal Universe" containing interconnected applications ranging
|
||||
from media playback to knowledge management.
|
||||
A "Personal Universe" containing interconnected
|
||||
applications ranging from media playback to knowledge management.
|
||||
</p>
|
||||
|
||||
{/* Organism Visualization */}
|
||||
@@ -319,14 +329,17 @@ export default function KSuitePage() {
|
||||
</h2>
|
||||
<div className="space-y-4 text-white/80 leading-relaxed">
|
||||
<p>
|
||||
The concept of K-Suite was born in <span className="text-yellow-400 font-semibold">2017</span>.
|
||||
As a middle school student, I envisioned a digital environment where every
|
||||
tool I used was built by my own hands—a space customized exactly to my needs.
|
||||
However, my vision at the time far outpaced my technical abilities.
|
||||
The concept of K-Suite was born in{" "}
|
||||
<span className="text-yellow-400 font-semibold">2017</span>.
|
||||
As a middle school student, I envisioned a digital environment
|
||||
where every tool I used was built by my own hands—a space
|
||||
customized exactly to my needs. However, my vision at the time
|
||||
far outpaced my technical abilities.
|
||||
</p>
|
||||
<p>
|
||||
K-Suite is the realization of that long-standing dream. It bridges the gap
|
||||
between the middle schooler who wanted to build, and the developer who now can.
|
||||
K-Suite is the realization of that long-standing dream. It
|
||||
bridges the gap between the middle schooler who wanted to
|
||||
build, and the developer who now can.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -344,10 +357,15 @@ export default function KSuitePage() {
|
||||
</h2>
|
||||
<div className="space-y-4 text-white/80 leading-relaxed">
|
||||
<p>
|
||||
This project represents my <span className="text-yellow-400 font-semibold">Magnum Opus</span>.
|
||||
It is the most ambitious software engineering undertaking I have attempted to date.
|
||||
Beyond simply providing privacy and utility, K-Suite serves as a rigorous testing
|
||||
ground for advanced <span className="text-cyan-400">System Design</span> and{" "}
|
||||
This project represents my{" "}
|
||||
<span className="text-yellow-400 font-semibold">
|
||||
Magnum Opus
|
||||
</span>
|
||||
. It is the most ambitious software engineering undertaking I
|
||||
have attempted to date. Beyond simply providing privacy and
|
||||
utility, K-Suite serves as a rigorous testing ground for
|
||||
advanced <span className="text-cyan-400">System Design</span>{" "}
|
||||
and{" "}
|
||||
<span className="text-cyan-400">Software Architecture</span>.
|
||||
</p>
|
||||
</div>
|
||||
@@ -361,8 +379,9 @@ export default function KSuitePage() {
|
||||
Architectural Mastery
|
||||
</h4>
|
||||
<p className="text-white/70">
|
||||
To design a distributed system where independent modules (like K-Notes)
|
||||
function flawlessly on their own but become exponentially more powerful when connected.
|
||||
To design a distributed system where independent modules
|
||||
(like K-Notes) function flawlessly on their own but become
|
||||
exponentially more powerful when connected.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -371,8 +390,9 @@ export default function KSuitePage() {
|
||||
Digital Sovereignty
|
||||
</h4>
|
||||
<p className="text-white/70">
|
||||
To create a viable, privacy-first alternative to commercial ecosystems
|
||||
(Google/Apple), focused entirely on open-source principles and self-hosting.
|
||||
To create a viable, privacy-first alternative to commercial
|
||||
ecosystems (Google/Apple), focused entirely on open-source
|
||||
principles and self-hosting.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -381,9 +401,10 @@ export default function KSuitePage() {
|
||||
Seamless Integration
|
||||
</h4>
|
||||
<p className="text-white/70">
|
||||
To solve the complex challenge of inter-app communication. In K-Suite,
|
||||
a photo stored in K-Photos isn't just a file; it's an asset that can be
|
||||
referenced in other apps or attached to an entry in the upcoming K-Mood.
|
||||
To solve the complex challenge of inter-app communication.
|
||||
In K-Suite, a photo stored in K-Photos isn't just a
|
||||
file; it's an asset that can be referenced in other
|
||||
apps or attached to an entry in the upcoming K-Mood.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -399,8 +420,8 @@ export default function KSuitePage() {
|
||||
Explore the Suite
|
||||
</h2>
|
||||
<p className="text-white/60 text-center mb-12 max-w-xl mx-auto">
|
||||
Each application is designed to work independently, but together they form
|
||||
something greater.
|
||||
Each application is designed to work independently, but together
|
||||
they form something greater.
|
||||
</p>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 max-w-4xl mx-auto">
|
||||
{kSuiteApps.map((app) => (
|
||||
@@ -418,8 +439,8 @@ export default function KSuitePage() {
|
||||
More Apps Coming Soon
|
||||
</h2>
|
||||
<p className="text-white/70 mb-4">
|
||||
K-Suite is still growing. The ecosystem will expand with more interconnected
|
||||
applications including K-Photos, K-Mood, and more.
|
||||
K-Suite is still growing. The ecosystem will expand with more
|
||||
interconnected applications including K-Photos, K-Mood, and more.
|
||||
</p>
|
||||
<p className="text-sm text-white/50">
|
||||
Built with Rust, React, and modern web technologies.
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Geist, Geist_Mono } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import Navbar from "@/components/navbar";
|
||||
import Footer from "@/components/footer";
|
||||
import GravityToggle from "@/components/gravity-toggle";
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
@@ -16,21 +17,21 @@ const geistMono = Geist_Mono({
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
default: "Gabriel Kaszewski | Full-Stack Developer",
|
||||
default: "Gabriel Kaszewski | Software Engineer",
|
||||
template: "%s | Gabriel Kaszewski",
|
||||
},
|
||||
description:
|
||||
"The portfolio of Gabriel Kaszewski, a self-taught full-stack developer specializing in Rust, Python, and modern web technologies.",
|
||||
"The portfolio of Gabriel Kaszewski, a software engineer specializing in Rust, Python, and modern web technologies.",
|
||||
keywords: [
|
||||
"Gabriel Kaszewski",
|
||||
"Full-Stack Developer",
|
||||
"Software Engineer",
|
||||
"Rust Developer",
|
||||
"Python Developer",
|
||||
"Next.js",
|
||||
"Portfolio",
|
||||
],
|
||||
openGraph: {
|
||||
title: "Gabriel Kaszewski | Full-Stack Developer",
|
||||
title: "Gabriel Kaszewski | Software Engineer",
|
||||
description:
|
||||
"Welcome to my portfolio. Discover my projects, skills, and journey.",
|
||||
url: "https://gabrielkaszewski.dev",
|
||||
@@ -41,9 +42,9 @@ export const metadata: Metadata = {
|
||||
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: "Gabriel Kaszewski | Full-Stack Developer",
|
||||
title: "Gabriel Kaszewski | Software Engineer",
|
||||
description:
|
||||
"Explore my work as a full-stack developer, from web apps to game development.",
|
||||
"Explore my work as a software engineer, from web apps to game development.",
|
||||
},
|
||||
|
||||
robots: {
|
||||
@@ -73,7 +74,7 @@ export default function RootLayout({
|
||||
"https://github.com/GKaszewski",
|
||||
"https://www.linkedin.com/in/gabriel-kaszewski-5344b3183",
|
||||
],
|
||||
jobTitle: "Full-Stack Developer",
|
||||
jobTitle: "Software Engineer",
|
||||
alumniOf: "University of Gdańsk",
|
||||
knowsAbout: [
|
||||
"Rust",
|
||||
@@ -98,6 +99,7 @@ export default function RootLayout({
|
||||
|
||||
<Navbar />
|
||||
{children}
|
||||
<GravityToggle />
|
||||
<Footer />
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { skills, jobs } from "@/lib/data";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="flex flex-col items-center w-full">
|
||||
<div className="flex flex-col items-center w-full gravity-body">
|
||||
<Hero />
|
||||
<div className="container mx-auto px-4">
|
||||
<AboutSummary />
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import { projects } from "@/lib/data";
|
||||
import { Project } from "@/lib/types";
|
||||
import Chip from "@/components/chip";
|
||||
import MarkdownContent from "@/components/markdown-content";
|
||||
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()
|
||||
(p) => p.name.toLowerCase() === decodedName.toLowerCase(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -54,13 +53,6 @@ export async function generateMetadata({
|
||||
};
|
||||
}
|
||||
|
||||
async function getProjectData(project: Project) {
|
||||
const processedContent = await remark()
|
||||
.use(html)
|
||||
.process(project.description);
|
||||
return processedContent.toString();
|
||||
}
|
||||
|
||||
export default async function ProjectDetailPage({
|
||||
params,
|
||||
}: {
|
||||
@@ -68,7 +60,6 @@ export default async function ProjectDetailPage({
|
||||
}) {
|
||||
const { projectName } = await params;
|
||||
const project = getProjectByName(projectName);
|
||||
const descriptionHtml = project ? await getProjectData(project) : "";
|
||||
|
||||
if (!project) {
|
||||
notFound();
|
||||
@@ -78,15 +69,17 @@ export default async function ProjectDetailPage({
|
||||
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>
|
||||
<div className="flex flex-col w-full h-full min-h-screen gap-4 p-4 pt-24 gravity-body">
|
||||
<div className="max-w-4xl mx-auto w-full">
|
||||
<h1 className="text-4xl font-extrabold mb-6 bg-gradient-to-r from-yellow-400 to-blue-400 bg-clip-text text-transparent tracking-tight">
|
||||
{project.name}
|
||||
</h1>
|
||||
|
||||
<section dangerouslySetInnerHTML={{ __html: descriptionHtml }} />
|
||||
<MarkdownContent content={project.description} />
|
||||
|
||||
<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">
|
||||
<section className="mt-12 flex flex-col items-center">
|
||||
<h2 className="text-xl font-bold text-white mb-4">Technologies</h2>
|
||||
<div className="flex flex-wrap justify-center gap-2">
|
||||
{project.technologies.map((tech) => (
|
||||
<Chip key={tech} text={tech} />
|
||||
))}
|
||||
@@ -94,9 +87,9 @@ export default async function ProjectDetailPage({
|
||||
</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">
|
||||
<section className="mt-12 flex flex-col items-center">
|
||||
<h2 className="text-xl font-bold text-white mb-4">Gallery</h2>
|
||||
<div className="flex flex-col gap-4">
|
||||
{project.thumbnails.map((thumb, index) => (
|
||||
<Image
|
||||
key={index}
|
||||
@@ -112,9 +105,9 @@ export default async function ProjectDetailPage({
|
||||
)}
|
||||
|
||||
{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">
|
||||
<section className="mt-12 flex flex-col items-center">
|
||||
<h2 className="text-xl font-bold text-white mb-4">Links</h2>
|
||||
<div className="flex flex-col sm:flex-row gap-4">
|
||||
{project.github_url && (
|
||||
<a
|
||||
href={project.github_url}
|
||||
|
||||
@@ -10,7 +10,7 @@ export const metadata: Metadata = {
|
||||
|
||||
const ProjectsPage = () => {
|
||||
return (
|
||||
<div className="flex w-full h-full min-h-screen flex-col items-center gap-4 pt-24">
|
||||
<div className="flex w-full h-full min-h-screen flex-col items-center gap-4 pt-24 gravity-body">
|
||||
<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">
|
||||
|
||||
132
bun.lock
132
bun.lock
@@ -9,6 +9,7 @@
|
||||
"next": "15.5.7",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"remark": "^15.0.1",
|
||||
"remark-html": "^16.0.1",
|
||||
"tailwindcss-motion": "^1.1.1",
|
||||
@@ -21,6 +22,7 @@
|
||||
"@types/react-dom": "^19",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.5.2",
|
||||
"hast": "^0.0.2",
|
||||
"tailwindcss": "^4",
|
||||
"typescript": "^5",
|
||||
},
|
||||
@@ -193,6 +195,8 @@
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||
|
||||
"@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="],
|
||||
|
||||
"@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="],
|
||||
|
||||
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
||||
@@ -303,13 +307,15 @@
|
||||
|
||||
"async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="],
|
||||
|
||||
"attach-ware": ["attach-ware@1.1.1", "", { "dependencies": { "unherit": "^1.0.0" } }, "sha512-OpavlXWZkyE7m28fpCWF/RmxCukC1edukJp9IKjEpZs/O11H3896DkLpK7lMiL8ZDx2yxo9FrZQaeHkyJGcIuQ=="],
|
||||
|
||||
"available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
|
||||
|
||||
"axe-core": ["axe-core@4.10.3", "", {}, "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg=="],
|
||||
|
||||
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
|
||||
|
||||
"bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="],
|
||||
"bail": ["bail@1.0.5", "", {}, "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ=="],
|
||||
|
||||
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
@@ -325,6 +331,8 @@
|
||||
|
||||
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
||||
|
||||
"camelcase": ["camelcase@1.2.1", "", {}, "sha512-wzLkDa4K/mzI1OSITC+DUyjgIl/ETNHE9QvYgy6J6Jvqyyz4C0Xfd+lQhb19sX2jMpZV4IssUn0VDVmglV+s4g=="],
|
||||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001745", "", {}, "sha512-ywt6i8FzvdgrrrGbr1jZVObnVv6adj+0if2/omv9cmR2oiZs30zL4DIyaptKcbOrBdOIc74QTMoJvSE2QHh5UQ=="],
|
||||
|
||||
"ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
|
||||
@@ -337,10 +345,14 @@
|
||||
|
||||
"character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="],
|
||||
|
||||
"character-reference-invalid": ["character-reference-invalid@2.0.1", "", {}, "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw=="],
|
||||
|
||||
"chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="],
|
||||
|
||||
"client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="],
|
||||
|
||||
"co": ["co@3.1.0", "", {}, "sha512-CQsjCRiNObI8AtTsNIBDRMQ4oMR83CzEswHYahClvul7gKk+lDQiOKv+5qh7LQWf5sh6jkZNispz/QlsZxyNgA=="],
|
||||
|
||||
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||
|
||||
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
||||
@@ -381,12 +393,24 @@
|
||||
|
||||
"doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="],
|
||||
|
||||
"dom-serializer": ["dom-serializer@0.2.2", "", { "dependencies": { "domelementtype": "^2.0.1", "entities": "^2.0.0" } }, "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g=="],
|
||||
|
||||
"domelementtype": ["domelementtype@1.3.1", "", {}, "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w=="],
|
||||
|
||||
"domhandler": ["domhandler@2.4.2", "", { "dependencies": { "domelementtype": "1" } }, "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA=="],
|
||||
|
||||
"domutils": ["domutils@1.7.0", "", { "dependencies": { "dom-serializer": "0", "domelementtype": "1" } }, "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg=="],
|
||||
|
||||
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
||||
|
||||
"emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
||||
|
||||
"enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="],
|
||||
|
||||
"ent": ["ent@2.2.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "punycode": "^1.4.1", "safe-regex-test": "^1.1.0" } }, "sha512-kKvD1tO6BM+oK9HzCPpUdRb4vKFQY/FPTFmurMvh6LlN68VMrdj77w8yp51/kDbpkFOS9J8w5W6zIzgM2H8/hw=="],
|
||||
|
||||
"entities": ["entities@1.1.2", "", {}, "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w=="],
|
||||
|
||||
"es-abstract": ["es-abstract@1.24.0", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg=="],
|
||||
|
||||
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
|
||||
@@ -403,6 +427,8 @@
|
||||
|
||||
"es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="],
|
||||
|
||||
"escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
|
||||
|
||||
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
||||
|
||||
"eslint": ["eslint@9.36.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", "@eslint/config-helpers": "^0.3.1", "@eslint/core": "^0.15.2", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.36.0", "@eslint/plugin-kit": "^0.3.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ=="],
|
||||
@@ -435,6 +461,8 @@
|
||||
|
||||
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
|
||||
|
||||
"estree-util-is-identifier-name": ["estree-util-is-identifier-name@3.0.0", "", {}, "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg=="],
|
||||
|
||||
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
|
||||
|
||||
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
|
||||
@@ -503,22 +531,38 @@
|
||||
|
||||
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
||||
|
||||
"hast": ["hast@0.0.2", "", { "dependencies": { "bail": "^1.0.0", "camelcase": "^1.2.1", "ent": "^2.2.0", "escape-html": "^1.0.3", "htmlparser2": "^3.8.3", "param-case": "^1.1.1", "property-information": "^2.0.0", "trim": "0.0.1", "unified": "^2.1.0" } }, "sha512-1MrzC9MtAYhzLix2w++pGEtRyCm6N1fcxCjx+1xJo/92fNDRFemFaum18XWd8No3f+FgT9lv6fKOC8LZRcxxuw=="],
|
||||
|
||||
"hast-util-sanitize": ["hast-util-sanitize@5.0.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "unist-util-position": "^5.0.0" } }, "sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg=="],
|
||||
|
||||
"hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="],
|
||||
|
||||
"hast-util-to-jsx-runtime": ["hast-util-to-jsx-runtime@2.3.6", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "style-to-js": "^1.0.0", "unist-util-position": "^5.0.0", "vfile-message": "^4.0.0" } }, "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg=="],
|
||||
|
||||
"hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="],
|
||||
|
||||
"html-url-attributes": ["html-url-attributes@3.0.1", "", {}, "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="],
|
||||
|
||||
"html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="],
|
||||
|
||||
"htmlparser2": ["htmlparser2@3.10.1", "", { "dependencies": { "domelementtype": "^1.3.1", "domhandler": "^2.3.0", "domutils": "^1.5.1", "entities": "^1.1.1", "inherits": "^2.0.1", "readable-stream": "^3.1.1" } }, "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ=="],
|
||||
|
||||
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
||||
|
||||
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
|
||||
|
||||
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
|
||||
|
||||
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
||||
|
||||
"inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="],
|
||||
|
||||
"internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="],
|
||||
|
||||
"is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="],
|
||||
|
||||
"is-alphanumerical": ["is-alphanumerical@2.0.1", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw=="],
|
||||
|
||||
"is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="],
|
||||
|
||||
"is-async-function": ["is-async-function@2.1.1", "", { "dependencies": { "async-function": "^1.0.0", "call-bound": "^1.0.3", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ=="],
|
||||
@@ -537,6 +581,8 @@
|
||||
|
||||
"is-date-object": ["is-date-object@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg=="],
|
||||
|
||||
"is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="],
|
||||
|
||||
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
||||
|
||||
"is-finalizationregistry": ["is-finalizationregistry@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg=="],
|
||||
@@ -545,6 +591,8 @@
|
||||
|
||||
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
|
||||
|
||||
"is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="],
|
||||
|
||||
"is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="],
|
||||
|
||||
"is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="],
|
||||
@@ -633,6 +681,8 @@
|
||||
|
||||
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
|
||||
|
||||
"lower-case": ["lower-case@1.1.4", "", {}, "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA=="],
|
||||
|
||||
"lucide-react": ["lucide-react@0.542.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-w3hD8/SQB7+lzU2r4VdFyzzOzKnUjTZIF/MQJGSSvni7Llewni4vuViRppfRAa2guOsY5k4jZyxw/i9DQHv+dw=="],
|
||||
|
||||
"magic-string": ["magic-string@0.30.19", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw=="],
|
||||
@@ -641,6 +691,12 @@
|
||||
|
||||
"mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="],
|
||||
|
||||
"mdast-util-mdx-expression": ["mdast-util-mdx-expression@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ=="],
|
||||
|
||||
"mdast-util-mdx-jsx": ["mdast-util-mdx-jsx@3.2.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "parse-entities": "^4.0.0", "stringify-entities": "^4.0.0", "unist-util-stringify-position": "^4.0.0", "vfile-message": "^4.0.0" } }, "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q=="],
|
||||
|
||||
"mdast-util-mdxjs-esm": ["mdast-util-mdxjs-esm@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg=="],
|
||||
|
||||
"mdast-util-phrasing": ["mdast-util-phrasing@4.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" } }, "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w=="],
|
||||
|
||||
"mdast-util-to-hast": ["mdast-util-to-hast@13.2.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA=="],
|
||||
@@ -737,8 +793,12 @@
|
||||
|
||||
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
|
||||
|
||||
"param-case": ["param-case@1.1.2", "", { "dependencies": { "sentence-case": "^1.1.2" } }, "sha512-gksk6zeZQxwBm1AHsKh+XDFsTGf1LvdZSkkpSIkfDtzW+EQj/P2PBgNb3Cs0Y9Xxqmbciv2JZe3fWU6Xbher+Q=="],
|
||||
|
||||
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
|
||||
|
||||
"parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="],
|
||||
|
||||
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
|
||||
|
||||
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||
@@ -759,9 +819,9 @@
|
||||
|
||||
"prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
|
||||
|
||||
"property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="],
|
||||
"property-information": ["property-information@2.0.0", "", {}, "sha512-8oVcjnCeqANq/exCzgse3D47GBmgOuI47vNya7xBIJhUXeh49AjZuXWw2gTh1UuN4rfwz5pEv2ZFzu45vBby5A=="],
|
||||
|
||||
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||
"punycode": ["punycode@1.4.1", "", {}, "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ=="],
|
||||
|
||||
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
||||
|
||||
@@ -771,6 +831,10 @@
|
||||
|
||||
"react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
|
||||
|
||||
"react-markdown": ["react-markdown@10.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "html-url-attributes": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "unified": "^11.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" }, "peerDependencies": { "@types/react": ">=18", "react": ">=18" } }, "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ=="],
|
||||
|
||||
"readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
|
||||
|
||||
"reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="],
|
||||
|
||||
"regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="],
|
||||
@@ -781,6 +845,8 @@
|
||||
|
||||
"remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="],
|
||||
|
||||
"remark-rehype": ["remark-rehype@11.1.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "mdast-util-to-hast": "^13.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw=="],
|
||||
|
||||
"remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="],
|
||||
|
||||
"resolve": ["resolve@1.22.10", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="],
|
||||
@@ -795,6 +861,8 @@
|
||||
|
||||
"safe-array-concat": ["safe-array-concat@1.1.3", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q=="],
|
||||
|
||||
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
||||
|
||||
"safe-push-apply": ["safe-push-apply@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "isarray": "^2.0.5" } }, "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA=="],
|
||||
|
||||
"safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="],
|
||||
@@ -803,6 +871,8 @@
|
||||
|
||||
"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"sentence-case": ["sentence-case@1.1.3", "", { "dependencies": { "lower-case": "^1.1.1" } }, "sha512-laa/UDTPXsrQnoN/Kc8ZO7gTeEjMsuPiDgUCk9N0iINRZvqAMCTXjGl8+tD27op1eF/JHbdUlEUmovDh6AX7sA=="],
|
||||
|
||||
"set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
|
||||
|
||||
"set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="],
|
||||
@@ -843,12 +913,18 @@
|
||||
|
||||
"string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="],
|
||||
|
||||
"string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
|
||||
|
||||
"stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="],
|
||||
|
||||
"strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="],
|
||||
|
||||
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
|
||||
|
||||
"style-to-js": ["style-to-js@1.1.21", "", { "dependencies": { "style-to-object": "1.0.14" } }, "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ=="],
|
||||
|
||||
"style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="],
|
||||
|
||||
"styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="],
|
||||
|
||||
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
@@ -867,6 +943,8 @@
|
||||
|
||||
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
|
||||
|
||||
"trim": ["trim@0.0.1", "", {}, "sha512-YzQV+TZg4AxpKxaTHK3c3D+kRDCGVEE7LemdlQZoQXn0iennk10RsIoY6ikzAqJTc9Xjl9C1/waHom/J86ziAQ=="],
|
||||
|
||||
"trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="],
|
||||
|
||||
"trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="],
|
||||
@@ -893,7 +971,9 @@
|
||||
|
||||
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
||||
|
||||
"unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="],
|
||||
"unherit": ["unherit@1.1.3", "", { "dependencies": { "inherits": "^2.0.0", "xtend": "^4.0.0" } }, "sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ=="],
|
||||
|
||||
"unified": ["unified@2.1.4", "", { "dependencies": { "attach-ware": "^1.0.0", "bail": "^1.0.0", "extend": "^3.0.0", "unherit": "^1.0.4", "vfile": "^1.0.0", "ware": "^1.3.0" } }, "sha512-qa4nA26ms49OczPueTt7G46r89TOlwAJ4pEk2U4mwkV1wNhjttItF03SE/YnfkgWg14tzmAHXGhJp2GhDYwn1A=="],
|
||||
|
||||
"unist-util-is": ["unist-util-is@6.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw=="],
|
||||
|
||||
@@ -915,6 +995,8 @@
|
||||
|
||||
"vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="],
|
||||
|
||||
"ware": ["ware@1.3.0", "", { "dependencies": { "wrap-fn": "^0.1.0" } }, "sha512-Y2HUDMktriUm+SR2gZWxlrszcgtXExlhQYZ8QJNYbl22jum00KIUcHJ/h/sdAXhWTJcbSkiMYN9Z2tWbWYSrrw=="],
|
||||
|
||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
"which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="],
|
||||
@@ -927,6 +1009,10 @@
|
||||
|
||||
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
|
||||
|
||||
"wrap-fn": ["wrap-fn@0.1.5", "", { "dependencies": { "co": "3.1.0" } }, "sha512-xDLdGx0M8JQw9QDAC9s5NUxtg9MI09F6Vbxa2LYoSoCvzJnx2n81YMIfykmXEGsUvuLaxnblJTzhSOjUOX37ag=="],
|
||||
|
||||
"xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="],
|
||||
|
||||
"yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
|
||||
|
||||
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||
@@ -955,6 +1041,10 @@
|
||||
|
||||
"@typescript-eslint/typescript-estree/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
|
||||
|
||||
"dom-serializer/domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
|
||||
|
||||
"dom-serializer/entities": ["entities@2.2.0", "", {}, "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A=="],
|
||||
|
||||
"eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
|
||||
|
||||
"eslint-module-utils/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
|
||||
@@ -965,16 +1055,50 @@
|
||||
|
||||
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||
|
||||
"hast-util-to-html/property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="],
|
||||
|
||||
"hast-util-to-jsx-runtime/property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="],
|
||||
|
||||
"is-bun-module/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
|
||||
|
||||
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
|
||||
"next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
|
||||
|
||||
"parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
|
||||
|
||||
"react-markdown/unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="],
|
||||
|
||||
"remark/unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="],
|
||||
|
||||
"remark-html/unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="],
|
||||
|
||||
"remark-parse/unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="],
|
||||
|
||||
"remark-rehype/unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="],
|
||||
|
||||
"remark-stringify/unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="],
|
||||
|
||||
"sharp/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
|
||||
|
||||
"unified/vfile": ["vfile@1.4.0", "", {}, "sha512-7Fz639rwERslMqQCuf1/0H4Tqe2q484Xl6X/jsKqrP7IjFcDODFURhv0GekMnImpbj9pTOojtqL7r39LJJkjGA=="],
|
||||
|
||||
"uri-js/punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
||||
|
||||
"react-markdown/unified/bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="],
|
||||
|
||||
"remark-html/unified/bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="],
|
||||
|
||||
"remark-parse/unified/bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="],
|
||||
|
||||
"remark-rehype/unified/bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="],
|
||||
|
||||
"remark-stringify/unified/bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="],
|
||||
|
||||
"remark/unified/bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,15 +5,20 @@ const AboutSummary = () => {
|
||||
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? 🤔
|
||||
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!
|
||||
Hi, I'm Gabriel Kaszewski — a Bioinformatics graduate and a
|
||||
software engineer by trade. I’ve been obsessed with programming since
|
||||
I was 11, and I thrive on building software that solves complex,
|
||||
real-world problems.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
My most ambitious project to date is my first game. You can check out
|
||||
the Steam preview below — if it catches your eye, I’d love for you to
|
||||
add it to your wishlist!
|
||||
</p>
|
||||
|
||||
<div className="w-full max-w-full aspect-[646/190] mx-auto not-prose">
|
||||
@@ -24,13 +29,10 @@ const AboutSummary = () => {
|
||||
</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 📚.
|
||||
Currently, I am a Software Engineer at{" "}
|
||||
<strong className="text-yellow-400">WPP Media</strong>. When I’m not
|
||||
at my day job, I’m usually deep-diving into new technologies or
|
||||
bringing my own side projects to life.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,6 @@ import { Eye } from "lucide-react";
|
||||
|
||||
const CommercialProjectCard = ({ project }: { project: Project }) => {
|
||||
return (
|
||||
// Removed h-full to allow flexbox stretch to control height
|
||||
<div className="flex flex-col justify-between p-6 text-white transition-all duration-300 bg-white/5 backdrop-blur-md border border-white/10 rounded-3xl hover:bg-white/10 hover:border-white/20 w-[22rem] md:w-[28rem] shrink-0 snap-center shadow-xl group">
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
|
||||
@@ -11,7 +11,7 @@ const CommercialProjects = () => {
|
||||
>
|
||||
<div className="text-center">
|
||||
<h3 className="mt-4 mb-2 text-5xl font-bold tracking-tight text-white">
|
||||
Commissioned Work 💼
|
||||
Commissioned Work
|
||||
</h3>
|
||||
<p className="text-xl text-white/80 max-w-2xl mx-auto">
|
||||
Selected commercial projects and freelance commissions.
|
||||
@@ -19,7 +19,7 @@ const CommercialProjects = () => {
|
||||
</div>
|
||||
|
||||
<div className="w-full max-w-[90vw] md:max-w-6xl">
|
||||
<div className="flex items-stretch gap-6 overflow-x-auto pb-8 pt-4 px-4 snap-x snap-mandatory md:justify-center scrollbar-thin scrollbar-thumb-yellow-400 scrollbar-track-white/10">
|
||||
<div className="flex flex-wrap items-stretch gap-6 overflow-x-auto pb-8 pt-4 px-4 snap-x snap-mandatory md:justify-center scrollbar-thin scrollbar-thumb-yellow-400 scrollbar-track-white/10">
|
||||
{commercialProjects.map((project) => (
|
||||
<CommercialProjectCard key={project.id} project={project} />
|
||||
))}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
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;
|
||||
83
components/experience-timeline.tsx
Normal file
83
components/experience-timeline.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import { Job } from "@/lib/types";
|
||||
import Chip from "./chip";
|
||||
import formatDate from "@/utils/format-date";
|
||||
|
||||
interface ExperienceTimelineProps {
|
||||
jobs: Job[];
|
||||
newestFirst?: boolean;
|
||||
}
|
||||
|
||||
const ExperienceTimeline = ({ jobs, newestFirst = true }: ExperienceTimelineProps) => {
|
||||
const sorted = [...jobs].sort((a, b) => {
|
||||
const diff = new Date(b.start_date).getTime() - new Date(a.start_date).getTime();
|
||||
return newestFirst ? diff : -diff;
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="relative w-full max-w-3xl mx-auto flex flex-col gap-0">
|
||||
<div className="absolute left-[9px] top-3 bottom-3 w-px bg-gradient-to-b from-white/30 to-white/0" />
|
||||
{sorted.map((job) => (
|
||||
<div key={job.id} className="flex gap-6 pb-10 last:pb-0">
|
||||
<div className="flex flex-col items-center shrink-0 w-5 pt-1.5 z-10">
|
||||
<div className="w-3 h-3 rounded-full bg-white/40 border border-white/20 shadow-[0_0_8px_rgba(255,255,255,0.2)]" />
|
||||
</div>
|
||||
<div className="flex-1 transition-all duration-300 bg-white/5 backdrop-blur-md border border-white/10 rounded-3xl hover:bg-white/10 hover:border-white/20 shadow-xl group p-5">
|
||||
<div className="flex justify-between items-start flex-wrap gap-1 mb-1">
|
||||
<h4 className="text-lg font-semibold text-white">{job.position}</h4>
|
||||
<span className="text-xs text-white/40">
|
||||
{formatDate(job.start_date)} –{" "}
|
||||
{job.still_working ? "Present" : job.end_date ? formatDate(job.end_date) : "—"}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm text-white/60 mb-4">{job.company}</p>
|
||||
|
||||
{job.summary && (
|
||||
<p className="text-sm text-white/50 italic mb-4">{job.summary}</p>
|
||||
)}
|
||||
|
||||
{job.sub_phases && job.sub_phases.length > 0 && (
|
||||
<div className="flex flex-col gap-3 mb-4">
|
||||
{job.sub_phases.map((phase) => (
|
||||
<div
|
||||
key={phase.label}
|
||||
className="bg-white/[0.03] border border-white/[0.07] rounded-xl px-4 py-3"
|
||||
>
|
||||
<div className="flex justify-between items-center flex-wrap gap-1 mb-2">
|
||||
<span className="text-[11px] font-semibold uppercase tracking-widest text-white/50">
|
||||
{phase.label}
|
||||
</span>
|
||||
<span className="text-[10px] text-white/30">
|
||||
{formatDate(phase.start_date)} –{" "}
|
||||
{phase.end_date ? formatDate(phase.end_date) : "Present"}
|
||||
</span>
|
||||
</div>
|
||||
<ul className="list-disc list-inside space-y-1">
|
||||
{phase.bullets.map((b, i) => (
|
||||
<li key={i} className="text-xs text-white/50 leading-relaxed">
|
||||
{b}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="border-t border-white/10 pt-4 mt-2">
|
||||
<p className="text-[10px] uppercase tracking-widest text-white/30 mb-2">
|
||||
Technologies
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{job.technologies.map((tech) => (
|
||||
<Chip key={tech} text={tech} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExperienceTimeline;
|
||||
@@ -1,22 +1,16 @@
|
||||
import { Job } from "@/lib/types";
|
||||
import ExperienceCard from "@/components/experience-card";
|
||||
import ExperienceTimeline from "@/components/experience-timeline";
|
||||
|
||||
const Experience = ({ jobs }: { jobs: Job[] }) => {
|
||||
return (
|
||||
const Experience = ({ jobs }: { jobs: Job[] }) => (
|
||||
<div
|
||||
id="experience"
|
||||
className="flex flex-col items-center justify-center gap-4 p-4 rounded w-full"
|
||||
className="flex flex-col items-center gap-8 p-4 w-full"
|
||||
>
|
||||
<h3 className="mt-4 mb-2 text-5xl font-bold tracking-tight text-white">
|
||||
Experience 📈
|
||||
<h3 className="mt-4 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>
|
||||
<ExperienceTimeline jobs={jobs} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Experience;
|
||||
|
||||
36
components/gravity-toggle.tsx
Normal file
36
components/gravity-toggle.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { GravityEngine } from "@/lib/gravity-engine";
|
||||
import { Magnet } from "lucide-react";
|
||||
|
||||
export default function GravityToggle() {
|
||||
const [isActive, setIsActive] = useState(false);
|
||||
const engineRef = useRef<GravityEngine | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
engineRef.current = new GravityEngine();
|
||||
return () => engineRef.current?.stop();
|
||||
}, []);
|
||||
|
||||
const toggleGravity = () => {
|
||||
if (!engineRef.current) return;
|
||||
|
||||
if (isActive) {
|
||||
engineRef.current.stop();
|
||||
} else {
|
||||
engineRef.current.start();
|
||||
}
|
||||
setIsActive(!isActive);
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={toggleGravity}
|
||||
className="fixed bottom-4 right-4 p-3 bg-yellow-400 text-black rounded-full shadow-lg z-50 hover:bg-yellow-500 transition-colors"
|
||||
title="Toggle Gravity"
|
||||
>
|
||||
<Magnet size={24} />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -37,7 +37,7 @@ const Hero = () => {
|
||||
Gabriel Kaszewski
|
||||
</h1>
|
||||
<h2 className="text-xl font-light tracking-tight text-white md:text-2xl">
|
||||
Full-Stack Developer
|
||||
Software Engineer
|
||||
</h2>
|
||||
</div>
|
||||
<div className="flex items-center gap-4 mt-4">
|
||||
|
||||
302
components/markdown-content.tsx
Normal file
302
components/markdown-content.tsx
Normal file
@@ -0,0 +1,302 @@
|
||||
import React from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import type { Components } from "react-markdown";
|
||||
import type { Element } from "hast";
|
||||
|
||||
function GoldDot() {
|
||||
return (
|
||||
<span
|
||||
style={{
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: "50%",
|
||||
background: "linear-gradient(135deg, #facc15, #f97316)",
|
||||
boxShadow: "0 0 8px rgba(250, 204, 21, 0.7)",
|
||||
flexShrink: 0,
|
||||
display: "inline-block",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function BlueDot() {
|
||||
return (
|
||||
<span
|
||||
style={{
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: "50%",
|
||||
background: "linear-gradient(135deg, #60a5fa, #34d399)",
|
||||
boxShadow: "0 0 8px rgba(96, 165, 250, 0.6)",
|
||||
flexShrink: 0,
|
||||
display: "inline-block",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function KeyBadge({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<span
|
||||
style={{
|
||||
background: "rgba(250, 204, 21, 0.12)",
|
||||
border: "1px solid rgba(250, 204, 21, 0.35)",
|
||||
borderRadius: 6,
|
||||
padding: "1px 10px",
|
||||
color: "#fde68a",
|
||||
fontWeight: 600,
|
||||
fontSize: "0.875rem",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
// Extracts plain text from a HAST element's text children,
|
||||
// stripping a trailing colon (e.g. "Backend:" → "Backend").
|
||||
function extractHastText(node: Element): string {
|
||||
return (node.children ?? [])
|
||||
.filter((c): c is { type: "text"; value: string } => c.type === "text")
|
||||
.map((c) => c.value)
|
||||
.join("")
|
||||
.replace(/:$/, "")
|
||||
.trim();
|
||||
}
|
||||
|
||||
|
||||
const components: Components = {
|
||||
h1: ({ children }) => (
|
||||
<h1
|
||||
style={{
|
||||
fontSize: "2.4rem",
|
||||
fontWeight: 800,
|
||||
margin: "0 0 1.5rem 0",
|
||||
background: "linear-gradient(90deg, #facc15, #60a5fa)",
|
||||
WebkitBackgroundClip: "text",
|
||||
WebkitTextFillColor: "transparent",
|
||||
backgroundClip: "text",
|
||||
letterSpacing: "-0.02em",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</h1>
|
||||
),
|
||||
|
||||
h2: ({ children }) => (
|
||||
<div style={{ margin: "1.75rem 0 0.75rem 0" }}>
|
||||
<h2
|
||||
style={{
|
||||
fontSize: "1.25rem",
|
||||
fontWeight: 700,
|
||||
margin: 0,
|
||||
background: "linear-gradient(90deg, #facc15, #60a5fa, #34d399)",
|
||||
WebkitBackgroundClip: "text",
|
||||
WebkitTextFillColor: "transparent",
|
||||
backgroundClip: "text",
|
||||
letterSpacing: "0.02em",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</h2>
|
||||
<span
|
||||
style={{
|
||||
display: "block",
|
||||
height: 2,
|
||||
marginTop: 6,
|
||||
background: "linear-gradient(90deg, #facc15, #60a5fa, transparent)",
|
||||
borderRadius: 2,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
|
||||
h3: ({ children }) => (
|
||||
<div style={{ margin: "1.25rem 0 0.5rem 0" }}>
|
||||
<h3
|
||||
style={{
|
||||
fontSize: "1.1rem",
|
||||
fontWeight: 600,
|
||||
margin: 0,
|
||||
background: "linear-gradient(90deg, #facc15, #60a5fa)",
|
||||
WebkitBackgroundClip: "text",
|
||||
WebkitTextFillColor: "transparent",
|
||||
backgroundClip: "text",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</h3>
|
||||
<span
|
||||
style={{
|
||||
display: "block",
|
||||
height: 1,
|
||||
marginTop: 4,
|
||||
background:
|
||||
"linear-gradient(90deg, rgba(250,204,21,0.5), rgba(96,165,250,0.5), transparent)",
|
||||
borderRadius: 1,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
|
||||
p: ({ children }) => (
|
||||
<p
|
||||
style={{
|
||||
color: "#f1f5f9",
|
||||
lineHeight: 1.75,
|
||||
margin: "0 0 1.25rem 0",
|
||||
fontSize: "1.05rem",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</p>
|
||||
),
|
||||
|
||||
ul: ({ children }) => (
|
||||
<ul
|
||||
style={{
|
||||
listStyle: "none",
|
||||
padding: 0,
|
||||
margin: "0 0 1.25rem 0",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 9,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ul>
|
||||
),
|
||||
|
||||
li: ({ node, children }) => {
|
||||
// Inspect HAST to detect the `- **Key:** value` pattern.
|
||||
// HAST tagName is always 'strong' regardless of custom component overrides.
|
||||
const firstHast = node?.children?.[0];
|
||||
const isKeyValue =
|
||||
firstHast?.type === "element" &&
|
||||
firstHast.tagName === "strong";
|
||||
|
||||
if (isKeyValue) {
|
||||
const keyText = extractHastText(firstHast);
|
||||
// Skip index 0 (the rendered <strong> element) — works because react-markdown
|
||||
// emits strong as child[0] for tight lists with the `- **Key:** value` pattern.
|
||||
// Tight lists only: loose lists wrap content in <p>, changing the HAST structure.
|
||||
const rest = React.Children.toArray(children).slice(1);
|
||||
return (
|
||||
<li
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 10,
|
||||
fontSize: "1rem",
|
||||
}}
|
||||
>
|
||||
<GoldDot />
|
||||
<KeyBadge>{keyText}</KeyBadge>
|
||||
<span style={{ color: "#e2e8f0" }}>{rest}</span>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<li
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 10,
|
||||
fontSize: "1rem",
|
||||
color: "#f1f5f9",
|
||||
}}
|
||||
>
|
||||
<BlueDot />
|
||||
{children}
|
||||
</li>
|
||||
);
|
||||
},
|
||||
|
||||
a: ({ href, children }) => {
|
||||
const isExternal = !!href && /^https?:\/\//.test(href);
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
target={isExternal ? "_blank" : undefined}
|
||||
rel={isExternal ? "noopener noreferrer" : undefined}
|
||||
style={{
|
||||
background: "linear-gradient(90deg, #facc15, #60a5fa)",
|
||||
WebkitBackgroundClip: "text",
|
||||
WebkitTextFillColor: "transparent",
|
||||
backgroundClip: "text",
|
||||
fontWeight: 600,
|
||||
textDecoration: "underline",
|
||||
textDecorationColor: "rgba(250, 204, 21, 0.5)",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
},
|
||||
|
||||
strong: ({ children }) => (
|
||||
<strong style={{ color: "#fde68a", fontWeight: 700 }}>{children}</strong>
|
||||
),
|
||||
|
||||
em: ({ children }) => (
|
||||
<em style={{ color: "#e2e8f0", fontStyle: "italic" }}>{children}</em>
|
||||
),
|
||||
|
||||
// `pre` reads code text directly from the HAST node so `code` renderer
|
||||
// is only ever called for inline code — no client-side context needed.
|
||||
pre: ({ node }) => {
|
||||
const codeEl = node?.children?.[0] as Element | undefined;
|
||||
const codeText = (codeEl?.children ?? [])
|
||||
.filter((c): c is { type: "text"; value: string } => c.type === "text")
|
||||
.map((c) => c.value)
|
||||
.join("");
|
||||
return (
|
||||
<pre
|
||||
style={{
|
||||
background: "rgba(255, 255, 255, 0.04)",
|
||||
border: "1px solid rgba(255, 255, 255, 0.12)",
|
||||
borderTop: "2px solid rgba(250, 204, 21, 0.6)",
|
||||
borderRadius: "0 0 10px 10px",
|
||||
padding: "18px 22px",
|
||||
overflowX: "auto",
|
||||
margin: "0 0 1.25rem 0",
|
||||
}}
|
||||
>
|
||||
<code
|
||||
style={{
|
||||
fontFamily: "var(--font-mono, 'Geist Mono', monospace)",
|
||||
fontSize: "0.9rem",
|
||||
color: "#e2e8f0",
|
||||
lineHeight: 1.65,
|
||||
}}
|
||||
>
|
||||
{codeText}
|
||||
</code>
|
||||
</pre>
|
||||
);
|
||||
},
|
||||
|
||||
// Always inline — block code is handled entirely by the `pre` renderer above.
|
||||
code: ({ children }) => (
|
||||
<code
|
||||
style={{
|
||||
background: "rgba(250, 204, 21, 0.1)",
|
||||
border: "1px solid rgba(250, 204, 21, 0.3)",
|
||||
borderRadius: 5,
|
||||
padding: "1px 7px",
|
||||
color: "#fde68a",
|
||||
fontFamily: "var(--font-mono, 'Geist Mono', monospace)",
|
||||
fontSize: "0.875em",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</code>
|
||||
),
|
||||
};
|
||||
|
||||
export default function MarkdownContent({ content }: { content: string }) {
|
||||
return <ReactMarkdown components={components}>{content}</ReactMarkdown>;
|
||||
}
|
||||
@@ -12,7 +12,7 @@ const Skills = ({ skills }: SkillsProps) => {
|
||||
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 🛠️
|
||||
Skills
|
||||
</h3>
|
||||
<div className="flex flex-wrap justify-center w-full max-w-lg gap-4">
|
||||
{skills.map((skill, index) => (
|
||||
|
||||
691
lib/data.ts
691
lib/data.ts
@@ -2,417 +2,472 @@ import { Skill, Job, Project } from "@/lib/types";
|
||||
|
||||
export const skills: Skill[] = [
|
||||
{
|
||||
"name": "Azure Pipelines"
|
||||
name: "Azure Pipelines",
|
||||
},
|
||||
{
|
||||
"name": "C#"
|
||||
name: "C#",
|
||||
},
|
||||
{
|
||||
"name": "C++"
|
||||
name: "C++",
|
||||
},
|
||||
{
|
||||
"name": "Django"
|
||||
name: "Django",
|
||||
},
|
||||
{
|
||||
"name": "Docker"
|
||||
name: "Docker",
|
||||
},
|
||||
{
|
||||
"name": "FastAPI"
|
||||
name: "FastAPI",
|
||||
},
|
||||
{
|
||||
"name": "Git"
|
||||
name: "Git",
|
||||
},
|
||||
{
|
||||
"name": "Java"
|
||||
name: "Java",
|
||||
},
|
||||
{
|
||||
"name": "Javascript"
|
||||
name: "Javascript",
|
||||
},
|
||||
{
|
||||
"name": "Linux"
|
||||
name: "Linux",
|
||||
},
|
||||
{
|
||||
"name": "PostGIS"
|
||||
name: "PostGIS",
|
||||
},
|
||||
{
|
||||
"name": "PostgreSQL"
|
||||
name: "PostgreSQL",
|
||||
},
|
||||
{
|
||||
"name": "Python"
|
||||
name: "Python",
|
||||
},
|
||||
{
|
||||
"name": "Qt"
|
||||
name: "Qt",
|
||||
},
|
||||
{
|
||||
"name": "React"
|
||||
name: "React",
|
||||
},
|
||||
{
|
||||
"name": "Rust"
|
||||
name: "Rust",
|
||||
},
|
||||
{
|
||||
"name": "TailwindCSS"
|
||||
name: "TailwindCSS",
|
||||
},
|
||||
{
|
||||
"name": "Typescript"
|
||||
name: "Typescript",
|
||||
},
|
||||
{
|
||||
"name": "Unity"
|
||||
}
|
||||
name: "Unity",
|
||||
},
|
||||
];
|
||||
|
||||
export const jobs: Job[] = [
|
||||
{
|
||||
"id": 2,
|
||||
"position": "Python Developer",
|
||||
"company": "GIAP",
|
||||
"still_working": false,
|
||||
"start_date": "2021-05-19",
|
||||
"end_date": "2023-02-03",
|
||||
"technologies": [
|
||||
id: 2,
|
||||
position: "Full Stack Developer",
|
||||
company: "GIAP",
|
||||
still_working: false,
|
||||
start_date: "2021-05-19",
|
||||
end_date: "2023-02-03",
|
||||
sub_phases: [
|
||||
{
|
||||
label: "Frontend",
|
||||
start_date: "2022-02-01",
|
||||
end_date: "2023-02-03",
|
||||
bullets: [
|
||||
"Engineered a comprehensive, public-facing web application for the City of Gdańsk (geogdansk.pl) leveraging React, TypeScript, Redux, and the ArcGIS JS API.",
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Desktop / Backend",
|
||||
start_date: "2021-05-19",
|
||||
end_date: "2022-02-01",
|
||||
bullets: [
|
||||
"Architected and optimized complex PostGIS/PostgreSQL cross-database comparison queries utilizing Common Table Expressions (CTEs), drastically reducing execution time from over 5 minutes to under 15 seconds.",
|
||||
"Developed a robust GIS data assertion module using Python and Qt to automatically validate spatial data against strict compliance standards.",
|
||||
],
|
||||
},
|
||||
],
|
||||
technologies: [
|
||||
"Python",
|
||||
"Qt",
|
||||
"React",
|
||||
"Typescript",
|
||||
"PostgreSQL",
|
||||
"PostGIS",
|
||||
"Git",
|
||||
"QGIS"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"position": "Frontend Developer",
|
||||
"company": "GIAP",
|
||||
"still_working": false,
|
||||
"start_date": "2022-02-01",
|
||||
"end_date": "2023-02-03",
|
||||
"technologies": [
|
||||
"React",
|
||||
"Typescript",
|
||||
"ArcGIS JS API",
|
||||
"Redux",
|
||||
"ArcGIS JS API"
|
||||
]
|
||||
"Qt",
|
||||
"QGIS",
|
||||
"Git",
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"position": "Frontend Developer",
|
||||
"company": "Wavemaker",
|
||||
"still_working": false,
|
||||
"start_date": "2023-09-13",
|
||||
"end_date": "2024-01-01",
|
||||
"technologies": [
|
||||
"React",
|
||||
"Typescript",
|
||||
"Angular",
|
||||
"TailwindCSS",
|
||||
"SCSS",
|
||||
"Azure Pipelines",
|
||||
" Gitlab CI",
|
||||
"Kubernetes"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"position": "Frontend Developer",
|
||||
"company": "Choreograph",
|
||||
"still_working": false,
|
||||
"start_date": "2024-01-01",
|
||||
"end_date": "2025-03-01",
|
||||
"technologies": [
|
||||
"React",
|
||||
"Typescript",
|
||||
"Angular",
|
||||
"TailwindCSS",
|
||||
"SCSS",
|
||||
"Azure Pipelines",
|
||||
" Gitlab CI",
|
||||
"Kubernetes"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"position": "Python Developer",
|
||||
"company": "Choreograph",
|
||||
"still_working": true,
|
||||
"start_date": "2025-03-01",
|
||||
"end_date": null,
|
||||
"technologies": [
|
||||
id: 7,
|
||||
position: "Software Engineer",
|
||||
company: "digimonkeys.com",
|
||||
still_working: true,
|
||||
start_date: "2021-05-19",
|
||||
end_date: null,
|
||||
technologies: [
|
||||
"Python",
|
||||
"FastAPI",
|
||||
"Django",
|
||||
"PostgreSQL",
|
||||
"GCP",
|
||||
"React",
|
||||
"TailwindCSS",
|
||||
"Nginx",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
position: "Software Engineer",
|
||||
company: "WPP Media | Choreograph | Wavemaker",
|
||||
still_working: true,
|
||||
start_date: "2023-09-13",
|
||||
end_date: null,
|
||||
summary:
|
||||
"Advanced from frontend UI development to backend systems engineering, leading infrastructure-agnostic design initiatives.",
|
||||
sub_phases: [
|
||||
{
|
||||
label: "Backend & Infrastructure",
|
||||
start_date: "2025-03-01",
|
||||
end_date: null,
|
||||
bullets: [
|
||||
"Engineered and optimized backend applications and internal tools using Python, FastAPI, and Django.",
|
||||
"Streamlined CI/CD workflows and containerized applications with GitLab Pipelines, Docker, and Kubernetes across GCP and Azure environments.",
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Frontend Architecture",
|
||||
start_date: "2023-09-13",
|
||||
end_date: "2025-03-01",
|
||||
bullets: [
|
||||
"Architected scalable microfrontends utilizing Angular and Module Federation, seamlessly integrating standalone internal tools into a unified enterprise shell application.",
|
||||
"Ensured seamless integration of UI components with Kubernetes-based deployments and Azure Pipelines.",
|
||||
],
|
||||
},
|
||||
],
|
||||
technologies: [
|
||||
"Angular",
|
||||
"Azure",
|
||||
"Docker",
|
||||
"Kubernetes",
|
||||
"Gitlab Pipelines"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"position": "Full Stack Developer",
|
||||
"company": "digimonkeys.com",
|
||||
"still_working": true,
|
||||
"start_date": "2021-05-19",
|
||||
"end_date": null,
|
||||
"technologies": [
|
||||
"Python",
|
||||
"FastAPI",
|
||||
"Azure Pipelines",
|
||||
"Django",
|
||||
"Docker",
|
||||
"FastAPI",
|
||||
"GCP",
|
||||
"Gitlab CI",
|
||||
"Gitlab Pipelines",
|
||||
"Kubernetes",
|
||||
"PostgreSQL",
|
||||
"Python",
|
||||
"React",
|
||||
"SCSS",
|
||||
"TailwindCSS",
|
||||
"Nginx"
|
||||
]
|
||||
}
|
||||
"Typescript",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const projects: Project[] = [
|
||||
{
|
||||
"id": 16,
|
||||
"name": "K-Notes",
|
||||
"short_description": "A Google Keep replica focusing on simplicity.",
|
||||
"description": "A full-featured Google Keep replica designed for speed and simplicity. It allows users to manage their notes efficiently and is a core part of the K-Suite.\n\n**Technical details:**\n- **Backend:** Rust\n- **Frontend:** React",
|
||||
"category": "Web",
|
||||
"github_url": "https://github.com/GKaszewski/k-notes",
|
||||
"visit_url": "https://knotes.gabrielkaszewski.dev/",
|
||||
"download_url": null,
|
||||
"technologies": [
|
||||
"Rust",
|
||||
"React",
|
||||
"TailwindCSS",
|
||||
"PWA"
|
||||
],
|
||||
"thumbnails": ["/images/k-notes.png"]
|
||||
id: 16,
|
||||
name: "K-Notes",
|
||||
short_description: "A Google Keep replica focusing on simplicity.",
|
||||
description:
|
||||
"A full-featured Google Keep replica designed for speed and simplicity. It allows users to manage their notes efficiently and is a core part of the K-Suite.\n\n**Technical details:**\n- **Backend:** Rust\n- **Frontend:** React",
|
||||
category: "Web",
|
||||
github_url: "https://github.com/GKaszewski/k-notes",
|
||||
visit_url: "https://knotes.gabrielkaszewski.dev/",
|
||||
download_url: null,
|
||||
technologies: ["Rust", "React", "TailwindCSS", "PWA"],
|
||||
thumbnails: ["/images/k-notes.png"],
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"name": "Thoughts",
|
||||
"short_description": "Nostalgic microblogging platform with a Frutiger Aero aesthetic.",
|
||||
"description": "Thoughts is a microblogging social website straight out of the 00s, featuring a distinctive Frutiger Aero style. Users can post short text-based thoughts (up to 128 characters), customize their entire profile page with their own CSS, and follow others in a purely chronological, algorithm-free environment. It's a return to the 'old times' of the web, focusing on genuine interaction and user expression.\n\n**Technical details:**\n- **Backend:** Rust\n- **Frontend:** Next.js",
|
||||
"category": "Web",
|
||||
"github_url": "https://git.gabrielkaszewski.dev/GKaszewski/thoughts",
|
||||
"visit_url": "https://thoughts.gabrielkaszewski.dev/",
|
||||
"download_url": null,
|
||||
"technologies": [
|
||||
"Rust",
|
||||
"Next.js",
|
||||
"TailwindCSS",
|
||||
"Axum"
|
||||
],
|
||||
"thumbnails": ["/images/thoughts.avif"]
|
||||
id: 6,
|
||||
name: "Thoughts",
|
||||
short_description:
|
||||
"Nostalgic microblogging platform with a Frutiger Aero aesthetic.",
|
||||
description:
|
||||
"Thoughts is a microblogging social website straight out of the 00s, featuring a distinctive Frutiger Aero style. Users can post short text-based thoughts (up to 128 characters), customize their entire profile page with their own CSS, and follow others in a purely chronological, algorithm-free environment. It's a return to the 'old times' of the web, focusing on genuine interaction and user expression.\n\n**Technical details:**\n- **Backend:** Rust\n- **Frontend:** Next.js",
|
||||
category: "Web",
|
||||
github_url: "https://git.gabrielkaszewski.dev/GKaszewski/thoughts",
|
||||
visit_url: "https://thoughts.gabrielkaszewski.dev/",
|
||||
download_url: null,
|
||||
technologies: ["Rust", "Next.js", "TailwindCSS", "Axum"],
|
||||
thumbnails: ["/images/thoughts.avif"],
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"name": "K-Tuner",
|
||||
"short_description": "Web app to tune guitar, ukulele and piano.",
|
||||
"description": "A web application for tuning musical instruments including guitar, ukulele, and piano. It features a nostalgic Frutiger Aero style and is built as a Progressive Web App (PWA) for use on any device.\n\n**Technical details:**\n- **Frontend:** React PWA",
|
||||
"category": "Web",
|
||||
"github_url": "https://github.com/GKaszewski/aero-tuner",
|
||||
"visit_url": "https://tuner.gabrielkaszewski.dev/",
|
||||
"download_url": null,
|
||||
"technologies": [
|
||||
"React",
|
||||
"PWA",
|
||||
"TailwindCSS"
|
||||
],
|
||||
"thumbnails": ["/images/k-tuner.png"]
|
||||
id: 17,
|
||||
name: "K-Tuner",
|
||||
short_description: "Web app to tune guitar, ukulele and piano.",
|
||||
description:
|
||||
"A web application for tuning musical instruments including guitar, ukulele, and piano. It features a nostalgic Frutiger Aero style and is built as a Progressive Web App (PWA) for use on any device.\n\n**Technical details:**\n- **Frontend:** React PWA",
|
||||
category: "Web",
|
||||
github_url: "https://github.com/GKaszewski/k-tuner",
|
||||
visit_url: "https://tuner.gabrielkaszewski.dev/",
|
||||
download_url: null,
|
||||
technologies: ["React", "PWA", "TailwindCSS"],
|
||||
thumbnails: ["/images/k-tuner.png"],
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"name": "K-QR",
|
||||
"short_description": "Fast and simple QR code generator.",
|
||||
"description": "A high-performance QR code generator built with Rust. It serves a clean HTML template and provides a fast, single-executable solution for generating QR codes. Part of the K-Suite.\n\n**Technical details:**\n- **Backend:** Rust\n- **Frontend:** HTML templates",
|
||||
"category": "Web",
|
||||
"github_url": "https://github.com/GKaszewski/k-qr",
|
||||
"visit_url": "https://qr.gabrielkaszewski.dev/",
|
||||
"download_url": null,
|
||||
"technologies": [
|
||||
"Rust",
|
||||
"HTML"
|
||||
],
|
||||
"thumbnails": ["/images/k-qr.png"]
|
||||
id: 15,
|
||||
name: "K-QR",
|
||||
short_description: "Fast and simple QR code generator.",
|
||||
description:
|
||||
"A high-performance QR code generator built with Rust. It serves a clean HTML template and provides a fast, single-executable solution for generating QR codes. Part of the K-Suite.\n\n**Technical details:**\n- **Backend:** Rust\n- **Frontend:** HTML templates",
|
||||
category: "Web",
|
||||
github_url: "https://github.com/GKaszewski/k-qr",
|
||||
visit_url: "https://qr.gabrielkaszewski.dev/",
|
||||
download_url: null,
|
||||
technologies: ["Rust", "HTML"],
|
||||
thumbnails: ["/images/k-qr.png"],
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Spanish Inquisition",
|
||||
"short_description": "Educational game made in 24 hours in Unity.",
|
||||
"description": "I made this game because I have Spanish as one of the subjects in school. I wanted to improve my vocabulary by having fun and a game is a perfect solution for this problem. I made this in 24 hours. It is not perfect gameplay-wise but I learned some Spanish from it. So it kind of works.",
|
||||
"category": "Game",
|
||||
"github_url": "https://github.com/GKaszewski/Spanish-Learning-Game",
|
||||
"visit_url": "https://gamejolt.com/games/spanish-inquisition/425125",
|
||||
"download_url": null,
|
||||
"technologies": [
|
||||
"C#",
|
||||
"Unity"
|
||||
],
|
||||
"thumbnails": []
|
||||
id: 2,
|
||||
name: "Spanish Inquisition",
|
||||
short_description: "Educational game made in 24 hours in Unity.",
|
||||
description:
|
||||
"I made this game because I have Spanish as one of the subjects in school. I wanted to improve my vocabulary by having fun and a game is a perfect solution for this problem. I made this in 24 hours. It is not perfect gameplay-wise but I learned some Spanish from it. So it kind of works.",
|
||||
category: "Game",
|
||||
github_url: "https://github.com/GKaszewski/Spanish-Learning-Game",
|
||||
visit_url: "https://gamejolt.com/games/spanish-inquisition/425125",
|
||||
download_url: null,
|
||||
technologies: ["C#", "Unity"],
|
||||
thumbnails: [],
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Everyday quotes",
|
||||
"short_description": "Simple app for reading various quotations",
|
||||
"description": "I made this app as my first Flutter app because I wanted to learn how to write mobile apps and how to use public APIs.",
|
||||
"category": "Mobile",
|
||||
"github_url": "https://github.com/GKaszewski/Everyday_quotes",
|
||||
"visit_url": "https://play.google.com/store/apps/details?id=com.GabrielKaszewski.everydayquotes",
|
||||
"download_url": null,
|
||||
"technologies": [
|
||||
"Dart",
|
||||
"Flutter"
|
||||
],
|
||||
"thumbnails": []
|
||||
id: 3,
|
||||
name: "Everyday quotes",
|
||||
short_description: "Simple app for reading various quotations",
|
||||
description:
|
||||
"I made this app as my first Flutter app because I wanted to learn how to write mobile apps and how to use public APIs.",
|
||||
category: "Mobile",
|
||||
github_url: "https://github.com/GKaszewski/Everyday_quotes",
|
||||
visit_url:
|
||||
"https://play.google.com/store/apps/details?id=com.GabrielKaszewski.everydayquotes",
|
||||
download_url: null,
|
||||
technologies: ["Dart", "Flutter"],
|
||||
thumbnails: [],
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "Raytracer",
|
||||
"short_description": "CPU based raytracer",
|
||||
"description": "My attempt at making CPU raytracer in Rust.\r\n\r\nFeatures:\r\n- rendering spheres, cubes, triangles and meshes (gltf only)\r\n- multithreading\r\nResources:\r\n[Raytracing in One weekend](https://raytracing.github.io/books/RayTracingInOneWeekend.html)",
|
||||
"category": "Desktop",
|
||||
"github_url": "https://github.com/GKaszewski/raytracer-rs",
|
||||
"visit_url": null,
|
||||
"download_url": null,
|
||||
"technologies": [
|
||||
"Rust"
|
||||
],
|
||||
"thumbnails": []
|
||||
id: 4,
|
||||
name: "Raytracer",
|
||||
short_description: "CPU based raytracer",
|
||||
description:
|
||||
"My attempt at making CPU raytracer in Rust.\r\n\r\nFeatures:\r\n- rendering spheres, cubes, triangles and meshes (gltf only)\r\n- multithreading\r\nResources:\r\n[Raytracing in One weekend](https://raytracing.github.io/books/RayTracingInOneWeekend.html)",
|
||||
category: "Desktop",
|
||||
github_url: "https://github.com/GKaszewski/raytracer-rs",
|
||||
visit_url: null,
|
||||
download_url: null,
|
||||
technologies: ["Rust"],
|
||||
thumbnails: [],
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"name": "Tiny packer",
|
||||
"short_description": "Small utility to combine images into one atlas.",
|
||||
"description": "**Overview:**\r\nTiny Packer is a command-line utility designed to combine multiple images into a single texture atlas. It provides options for manual and automatic sizing of the atlas, including adjustable padding between images.\r\n\r\n# Features\r\n- CLI \r\n- GUI \r\n- Padding support\r\n- Auto size\r\n\r\n# How to use? \r\n## GUI\r\n**Hotkeys**\r\n- `Ctrl+I` - Import images\r\n- `Ctrl+Shift+I` - Add images\r\n- `Ctrl+Shift+C` - Clear images\r\n- `Ctrl+S` - Save generated atlas\r\n## CLI\r\n**Usage:**\r\n```bash\r\ntiny_packer --input <input_files> --output <output_file> [OPTIONS]\r\n```\r\n\r\n**Required Arguments:**\r\n- `--input`, `-i`: Specify the input image files. Multiple files can be specified by repeating the argument for each file.\r\n- `--output`, `-o`: Specify the path where the output atlas image will be saved.\r\n\r\n**Options:**\r\n- `--width`: Specify the width of the atlas. Defaults to `512` pixels. This option is ignored if auto sizing is enabled.\r\n- `--height`: Specify the height of the atlas. Defaults to `512` pixels. This option is ignored if auto sizing is enabled.\r\n- `--padding`, `-p`: Set the padding between images in the atlas. Defaults to `0` pixels.\r\n- `--auto_size`, `-a`: Enable or disable automatic sizing of the atlas dimensions. Defaults to `false`. When enabled, the atlas dimensions are calculated based on the input images.\r\n- `--unified`: Each cell has the same size (based on largest dimensions of image.) Defaults to `false`. [CLI only for now] \r\n\r\n**Examples:**\r\n\r\n1. **Creating an Atlas with Specified Dimensions:**\r\n Generate an atlas with a specific width and height, ignoring automatic sizing.\r\n ```bash\r\n tiny_packer -i image1.png -i image2.png -o atlas.png --width 1024 --height 1024 -a false\r\n ```\r\n\r\n2. **Creating an Atlas with Automatic Sizing:**\r\n Generate an atlas where dimensions are automatically calculated.\r\n ```bash\r\n tiny_packer -i image1.png -i image2.png -i image3.png -o atlas.png\r\n ```\r\n\r\n3. **Creating an Atlas with Padding:**\r\n Generate an atlas with a specified padding between images.\r\n ```bash\r\n tiny_packer -i image1.png -i image2.png -o atlas.png -p 10\r\n ```\r\n\r\n**Additional Tips:**\r\n- Multiple input files should be specified by repeating the `-i` or `--input` option for each file.\r\n- Ensure that file paths are correctly specified and accessible from the command line.\r\n- For best results, images should be of compatible formats and dimensions when padding and auto sizing are considered.\r\n\r\n**Help:**\r\nTo view more information and help regarding the command options, you can use the `--help` flag:\r\n```bash\r\ntiny_packer --help\r\n```\r\n# License\r\nMIT",
|
||||
"category": "Desktop",
|
||||
"github_url": "https://github.com/GKaszewski/tiny_packer",
|
||||
"visit_url": null,
|
||||
"download_url": null,
|
||||
"technologies": [
|
||||
"Rust",
|
||||
"Tauri",
|
||||
"React",
|
||||
"TailwindCSS"
|
||||
],
|
||||
"thumbnails": []
|
||||
id: 5,
|
||||
name: "Tiny packer",
|
||||
short_description: "Small utility to combine images into one atlas.",
|
||||
description:
|
||||
"**Overview:**\r\nTiny Packer is a command-line utility designed to combine multiple images into a single texture atlas. It provides options for manual and automatic sizing of the atlas, including adjustable padding between images.\r\n\r\n# Features\r\n- CLI \r\n- GUI \r\n- Padding support\r\n- Auto size\r\n\r\n# How to use? \r\n## GUI\r\n**Hotkeys**\r\n- `Ctrl+I` - Import images\r\n- `Ctrl+Shift+I` - Add images\r\n- `Ctrl+Shift+C` - Clear images\r\n- `Ctrl+S` - Save generated atlas\r\n## CLI\r\n**Usage:**\r\n```bash\r\ntiny_packer --input <input_files> --output <output_file> [OPTIONS]\r\n```\r\n\r\n**Required Arguments:**\r\n- `--input`, `-i`: Specify the input image files. Multiple files can be specified by repeating the argument for each file.\r\n- `--output`, `-o`: Specify the path where the output atlas image will be saved.\r\n\r\n**Options:**\r\n- `--width`: Specify the width of the atlas. Defaults to `512` pixels. This option is ignored if auto sizing is enabled.\r\n- `--height`: Specify the height of the atlas. Defaults to `512` pixels. This option is ignored if auto sizing is enabled.\r\n- `--padding`, `-p`: Set the padding between images in the atlas. Defaults to `0` pixels.\r\n- `--auto_size`, `-a`: Enable or disable automatic sizing of the atlas dimensions. Defaults to `false`. When enabled, the atlas dimensions are calculated based on the input images.\r\n- `--unified`: Each cell has the same size (based on largest dimensions of image.) Defaults to `false`. [CLI only for now] \r\n\r\n**Examples:**\r\n\r\n1. **Creating an Atlas with Specified Dimensions:**\r\n Generate an atlas with a specific width and height, ignoring automatic sizing.\r\n ```bash\r\n tiny_packer -i image1.png -i image2.png -o atlas.png --width 1024 --height 1024 -a false\r\n ```\r\n\r\n2. **Creating an Atlas with Automatic Sizing:**\r\n Generate an atlas where dimensions are automatically calculated.\r\n ```bash\r\n tiny_packer -i image1.png -i image2.png -i image3.png -o atlas.png\r\n ```\r\n\r\n3. **Creating an Atlas with Padding:**\r\n Generate an atlas with a specified padding between images.\r\n ```bash\r\n tiny_packer -i image1.png -i image2.png -o atlas.png -p 10\r\n ```\r\n\r\n**Additional Tips:**\r\n- Multiple input files should be specified by repeating the `-i` or `--input` option for each file.\r\n- Ensure that file paths are correctly specified and accessible from the command line.\r\n- For best results, images should be of compatible formats and dimensions when padding and auto sizing are considered.\r\n\r\n**Help:**\r\nTo view more information and help regarding the command options, you can use the `--help` flag:\r\n```bash\r\ntiny_packer --help\r\n```\r\n# License\r\nMIT",
|
||||
category: "Desktop",
|
||||
github_url: "https://github.com/GKaszewski/tiny_packer",
|
||||
visit_url: null,
|
||||
download_url: null,
|
||||
technologies: ["Rust", "Tauri", "React", "TailwindCSS"],
|
||||
thumbnails: [],
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"name": "Fleet Compass",
|
||||
"short_description": "SaaS solution for modern fleet management.",
|
||||
"description": "Fleet Compass is a comprehensive SaaS platform designed to streamline fleet management operations. It provides real-time tracking, analytics, and management tools to help businesses optimize their logistics.",
|
||||
"category": "Web",
|
||||
"github_url": null,
|
||||
"visit_url": "https://fleetcompass.pl/",
|
||||
"download_url": null,
|
||||
"technologies": [
|
||||
id: 7,
|
||||
name: "Fleet Compass",
|
||||
short_description: "SaaS solution for modern fleet management.",
|
||||
description:
|
||||
"Fleet Compass is a comprehensive SaaS platform designed to streamline fleet management operations. It provides real-time tracking, analytics, and management tools to help businesses optimize their logistics.",
|
||||
category: "Web",
|
||||
github_url: null,
|
||||
visit_url: "https://fleetcompass.pl/",
|
||||
download_url: null,
|
||||
technologies: [
|
||||
"React",
|
||||
"Python",
|
||||
"Django",
|
||||
"PostgreSQL",
|
||||
"PostGIS",
|
||||
"TailwindCSS"
|
||||
"TailwindCSS",
|
||||
],
|
||||
"thumbnails": [],
|
||||
"commercial": true,
|
||||
thumbnails: [],
|
||||
commercial: true,
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"name": "Poczuj - Szkoła z Lasu",
|
||||
"short_description": "Interactive music composition app for children.",
|
||||
"description": "A commissioned project for \"Szkoła z Lasu\". This application is an interactive tool designed for children, allowing them to compose music that reflects their current emotions. It combines educational values with creative expression, helping young users understand and express their feelings through sound.",
|
||||
"category": "Web",
|
||||
"github_url": null,
|
||||
"visit_url": "https://poczuj.szkolazlasu.pl/",
|
||||
"download_url": null,
|
||||
"technologies": [
|
||||
"React",
|
||||
],
|
||||
"thumbnails": [],
|
||||
"commercial": true,
|
||||
id: 8,
|
||||
name: "Poczuj - Szkoła z Lasu",
|
||||
short_description: "Interactive music composition app for children.",
|
||||
description:
|
||||
'A commissioned project for "Szkoła z Lasu". This application is an interactive tool designed for children, allowing them to compose music that reflects their current emotions. It combines educational values with creative expression, helping young users understand and express their feelings through sound.',
|
||||
category: "Web",
|
||||
github_url: null,
|
||||
visit_url: "https://poczuj.szkolazlasu.pl/",
|
||||
download_url: null,
|
||||
technologies: ["React"],
|
||||
thumbnails: [],
|
||||
commercial: true,
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"name": "Codebase to Prompt",
|
||||
"short_description": "CLI tool to convert codebase context into LLM prompts.",
|
||||
"description": "A Rust-based Command Line Interface (CLI) tool designed to aggregate and format a codebase into a single prompt suitable for Large Language Models (LLMs). This tool streamlines the process of sharing code context with AI assistants.",
|
||||
"category": "Desktop",
|
||||
"github_url": "https://github.com/GKaszewski/codebase-to-prompt",
|
||||
"visit_url": "https://crates.io/crates/codebase-to-prompt",
|
||||
"download_url": null,
|
||||
"technologies": [
|
||||
"Rust",
|
||||
],
|
||||
"thumbnails": []
|
||||
id: 9,
|
||||
name: "Codebase to Prompt",
|
||||
short_description: "CLI tool to convert codebase context into LLM prompts.",
|
||||
description:
|
||||
"A Rust-based Command Line Interface (CLI) tool designed to aggregate and format a codebase into a single prompt suitable for Large Language Models (LLMs). This tool streamlines the process of sharing code context with AI assistants.",
|
||||
category: "Desktop",
|
||||
github_url: "https://github.com/GKaszewski/codebase-to-prompt",
|
||||
visit_url: "https://crates.io/crates/codebase-to-prompt",
|
||||
download_url: null,
|
||||
technologies: ["Rust"],
|
||||
thumbnails: [],
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"name": "Parasitic God",
|
||||
"short_description": "Game Jam entry created in Godot.",
|
||||
"description": "A game created during a Game Jam. It explores themes of cosmic horror and parasitic control. Developed using the Godot Engine.",
|
||||
"category": "Game",
|
||||
"github_url": null,
|
||||
"visit_url": "https://gabrielkaszewski.itch.io/parasitic-god",
|
||||
"download_url": null,
|
||||
"technologies": [
|
||||
"Godot",
|
||||
"C#"
|
||||
],
|
||||
"thumbnails": []
|
||||
id: 10,
|
||||
name: "Parasitic God",
|
||||
short_description: "Game Jam entry created in Godot.",
|
||||
description:
|
||||
"A game created during a Game Jam. It explores themes of cosmic horror and parasitic control. Developed using the Godot Engine.",
|
||||
category: "Game",
|
||||
github_url: null,
|
||||
visit_url: "https://gabrielkaszewski.itch.io/parasitic-god",
|
||||
download_url: null,
|
||||
technologies: ["Godot", "C#"],
|
||||
thumbnails: [],
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"name": "Broberry",
|
||||
"short_description": "Action-packed Game Jam entry.",
|
||||
"description": "A fun and fast-paced game developed for a Game Jam. Check it out on Itch.io!",
|
||||
"category": "Game",
|
||||
"github_url": null,
|
||||
"visit_url": "https://gabrielkaszewski.itch.io/broberry",
|
||||
"download_url": null,
|
||||
"technologies": [
|
||||
"Unity",
|
||||
"C#"
|
||||
],
|
||||
"thumbnails": []
|
||||
id: 11,
|
||||
name: "Broberry",
|
||||
short_description: "Action-packed Game Jam entry.",
|
||||
description:
|
||||
"A fun and fast-paced game developed for a Game Jam. Check it out on Itch.io!",
|
||||
category: "Game",
|
||||
github_url: null,
|
||||
visit_url: "https://gabrielkaszewski.itch.io/broberry",
|
||||
download_url: null,
|
||||
technologies: ["Unity", "C#"],
|
||||
thumbnails: [],
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"name": "Chip8 Emulator",
|
||||
"short_description": "CHIP-8 interpreter written in Rust.",
|
||||
"description": "A fully functional emulator for the CHIP-8 programming language, written in Rust. It demonstrates low-level programming concepts and system emulation.",
|
||||
"category": "Desktop",
|
||||
"github_url": "https://github.com/GKaszewski/chip8",
|
||||
"visit_url": null,
|
||||
"download_url": null,
|
||||
"technologies": [
|
||||
"Rust",
|
||||
"Raylib"
|
||||
],
|
||||
"thumbnails": []
|
||||
id: 12,
|
||||
name: "Chip8 Emulator",
|
||||
short_description: "CHIP-8 interpreter written in Rust.",
|
||||
description:
|
||||
"A fully functional emulator for the CHIP-8 programming language, written in Rust. It demonstrates low-level programming concepts and system emulation.",
|
||||
category: "Desktop",
|
||||
github_url: "https://github.com/GKaszewski/chip8",
|
||||
visit_url: null,
|
||||
download_url: null,
|
||||
technologies: ["Rust", "Raylib"],
|
||||
thumbnails: [],
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"name": "Godot LDtk Importer",
|
||||
"short_description": "Plugin to import LDtk maps into Godot.",
|
||||
"description": "A tool designed to bridge the gap between the LDtk level editor and the Godot Game Engine. It streamlines the workflow for developers using LDtk for level design.",
|
||||
"category": "Desktop",
|
||||
"github_url": "https://github.com/GKaszewski/godot-ldtk-importer",
|
||||
"visit_url": null,
|
||||
"download_url": null,
|
||||
"technologies": [
|
||||
"Godot",
|
||||
"C#"
|
||||
],
|
||||
"thumbnails": []
|
||||
id: 13,
|
||||
name: "Godot LDtk Importer",
|
||||
short_description: "Plugin to import LDtk maps into Godot.",
|
||||
description:
|
||||
"A tool designed to bridge the gap between the LDtk level editor and the Godot Game Engine. It streamlines the workflow for developers using LDtk for level design.",
|
||||
category: "Desktop",
|
||||
github_url: "https://github.com/GKaszewski/godot-ldtk-importer",
|
||||
visit_url: null,
|
||||
download_url: null,
|
||||
technologies: ["Godot", "C#"],
|
||||
thumbnails: [],
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"name": "Simple Cloudflare DDNS",
|
||||
"short_description": "Dynamic DNS updater for Cloudflare.",
|
||||
"description": "A lightweight utility to automatically update Cloudflare DNS records with your dynamic IP address. Useful for home labs and self-hosting setups.",
|
||||
"category": "Api",
|
||||
"github_url": "https://github.com/GKaszewski/simple_cloudflare_ddns",
|
||||
"visit_url": null,
|
||||
"download_url": null,
|
||||
"technologies": [
|
||||
"Rust"
|
||||
],
|
||||
"thumbnails": []
|
||||
}
|
||||
id: 14,
|
||||
name: "Simple Cloudflare DDNS",
|
||||
short_description: "Dynamic DNS updater for Cloudflare.",
|
||||
description:
|
||||
"A lightweight utility to automatically update Cloudflare DNS records with your dynamic IP address. Useful for home labs and self-hosting setups.",
|
||||
category: "Api",
|
||||
github_url: "https://github.com/GKaszewski/simple_cloudflare_ddns",
|
||||
visit_url: null,
|
||||
download_url: null,
|
||||
technologies: ["Rust"],
|
||||
thumbnails: [],
|
||||
},
|
||||
{
|
||||
id: 18,
|
||||
name: "K-Launcher",
|
||||
short_description: "A Rofi-like application launcher with a plugin system.",
|
||||
description:
|
||||
"A keyboard-driven application launcher inspired by Rofi. Ships with built-in plugins for launching apps, browsing files, and a calculator. The community can build and share additional plugins. Colors and theme are fully configurable via a config file.\n\n**Technical details:**\n- **Language:** Rust\n- **GUI:** iced",
|
||||
category: "Desktop",
|
||||
github_url: "https://git.gabrielkaszewski.dev/GKaszewski/k-launcher",
|
||||
visit_url: null,
|
||||
download_url: null,
|
||||
technologies: ["Rust", "iced"],
|
||||
thumbnails: ["/images/k-launcher.avif"],
|
||||
},
|
||||
{
|
||||
id: 19,
|
||||
name: "K-TV",
|
||||
short_description: "Turn your media library into classic broadcast TV.",
|
||||
description:
|
||||
"K-TV takes your existing media library (Jellyfin, Plex, or local files) and turns it into a classic broadcast TV experience. Users can create channels, define programming blocks, and set up schedules — simulating the feel of live TV from your own collection. Part of the K-Suite.\n\n**Technical details:**\n- **Backend:** Rust\n- **Frontend:** Next.js, TailwindCSS",
|
||||
category: "Web",
|
||||
github_url: "https://git.gabrielkaszewski.dev/GKaszewski/k-tv",
|
||||
visit_url: "https://tv.gabrielkaszewski.dev/",
|
||||
download_url: null,
|
||||
technologies: ["Rust", "Next.js", "TailwindCSS"],
|
||||
thumbnails: ["/images/k-tv.png"],
|
||||
},
|
||||
{
|
||||
id: 20,
|
||||
name: "K-Convert",
|
||||
short_description:
|
||||
"Client-side image and audio converter powered by FFmpeg & WASM.",
|
||||
description:
|
||||
"K-Convert is a 100% client-side web app for converting images and audio files. All processing happens locally in the browser — no files are ever uploaded to a server. Powered by FFmpeg compiled to WebAssembly.\n\n**Technical details:**\n- **Frontend:** React, TypeScript, TailwindCSS\n- **Processing:** FFmpeg.wasm (WASM)",
|
||||
category: "Web",
|
||||
github_url: "https://github.com/GKaszewski/k-convert",
|
||||
visit_url: "https://convert.gabrielkaszewski.dev/",
|
||||
download_url: null,
|
||||
technologies: ["React", "TypeScript", "TailwindCSS", "FFmpeg.wasm", "WASM"],
|
||||
thumbnails: ["/images/k-convert.avif"],
|
||||
},
|
||||
{
|
||||
id: 21,
|
||||
name: "K-Shrink",
|
||||
short_description:
|
||||
"Wayland clipboard daemon that auto-compresses images on copy.",
|
||||
description:
|
||||
"K-Shrink is a Wayland clipboard daemon that automatically compresses images the moment you copy them. Copy a 3 MB screenshot, paste a 300 KB WebP — no extra steps required.\n\n**Technical details:**\n- **Language:** Rust\n- **Protocol:** Wayland",
|
||||
category: "Desktop",
|
||||
github_url: "https://github.com/GKaszewski/k-shrink",
|
||||
visit_url: null,
|
||||
download_url: "https://aur.archlinux.org/packages/k-shrink",
|
||||
technologies: ["Rust", "Wayland"],
|
||||
thumbnails: [],
|
||||
},
|
||||
{
|
||||
id: 22,
|
||||
name: "Galeria Rumia",
|
||||
short_description: "Website for shopping mall in Rumia, Poland.",
|
||||
description:
|
||||
"A commercial project for a shopping mall in Rumia, Poland. The website provides information about the mall, its stores, events, and promotions. It features a modern design and is built with custom handmade wordpress theme and in-house map plugin.",
|
||||
category: "Web",
|
||||
github_url: null,
|
||||
visit_url: "https://galeriarumia.com/",
|
||||
download_url: null,
|
||||
technologies: ["WordPress"],
|
||||
thumbnails: ["/images/galeria_rumia.avif", "/images/galeria_rumia2.avif"],
|
||||
commercial: true,
|
||||
},
|
||||
{
|
||||
id: 23,
|
||||
name: "Pixel palette colorizer",
|
||||
short_description:
|
||||
"CLI tool that changes image colors to fit a given palette.",
|
||||
description:
|
||||
"This tool was created because I was lazy to change palettes of pixel art to match the style of my game. Usage is pretty straightforward, you give the tool a palette and images you want to colorize, and it will output the colorized images, you can also specify the color space to use for color matching.",
|
||||
category: "Desktop",
|
||||
github_url: "https://github.com/GKaszewski/pixel-palette-colorizer",
|
||||
visit_url: null,
|
||||
download_url:
|
||||
"https://github.com/GKaszewski/pixel-palette-colorizer/releases",
|
||||
technologies: ["Rust"],
|
||||
thumbnails: [],
|
||||
},
|
||||
];
|
||||
|
||||
233
lib/gravity-engine.ts
Normal file
233
lib/gravity-engine.ts
Normal file
@@ -0,0 +1,233 @@
|
||||
interface PhysicsBody {
|
||||
el: HTMLElement;
|
||||
x: number;
|
||||
y: number;
|
||||
vx: number;
|
||||
vy: number;
|
||||
width: number;
|
||||
height: number;
|
||||
originX: number;
|
||||
originY: number;
|
||||
isDragging: boolean;
|
||||
startX: number;
|
||||
startY: number;
|
||||
}
|
||||
|
||||
export class GravityEngine {
|
||||
private bodies: PhysicsBody[] = [];
|
||||
private dirtyElements: Array<{ el: HTMLElement; originalCssText: string }> = [];
|
||||
private animationFrameId: number | null = null;
|
||||
private isRunning = false;
|
||||
|
||||
INITIAL_FORCE = 10;
|
||||
|
||||
start() {
|
||||
if (this.isRunning) return;
|
||||
this.isRunning = true;
|
||||
this.dirtyElements = [];
|
||||
|
||||
const MAX_DEPTH = 3;
|
||||
const containers = document.querySelectorAll(".gravity-body");
|
||||
const leafElements: HTMLElement[] = [];
|
||||
const intermediates: { el: HTMLElement; w: number; h: number }[] = [];
|
||||
|
||||
// READ-ONLY traversal: classify all elements and snapshot sizes.
|
||||
// No DOM writes here — one layout flush for the entire traversal.
|
||||
const collectElements = (el: HTMLElement, depth: number) => {
|
||||
// Skip purely decorative elements (e.g. background images).
|
||||
if (el.classList.contains("pointer-events-none")) return;
|
||||
|
||||
const isSolid = ["SVG", "IMG", "BUTTON", "IFRAME", "A"].includes(
|
||||
el.tagName.toUpperCase(),
|
||||
);
|
||||
|
||||
// Treat as a leaf if solid, childless, or at the depth cap.
|
||||
if (isSolid || el.children.length === 0 || depth >= MAX_DEPTH) {
|
||||
if (el.offsetWidth > 0 && el.offsetHeight > 0) {
|
||||
leafElements.push(el);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
intermediates.push({ el, w: el.offsetWidth, h: el.offsetHeight });
|
||||
Array.from(el.children).forEach((child) =>
|
||||
collectElements(child as HTMLElement, depth + 1),
|
||||
);
|
||||
};
|
||||
|
||||
const rootSizes: { el: HTMLElement; w: number; h: number }[] = [];
|
||||
containers.forEach((container) => {
|
||||
const htmlContainer = container as HTMLElement;
|
||||
rootSizes.push({
|
||||
el: htmlContainer,
|
||||
w: htmlContainer.offsetWidth,
|
||||
h: htmlContainer.offsetHeight,
|
||||
});
|
||||
Array.from(htmlContainer.children).forEach((child) =>
|
||||
collectElements(child as HTMLElement, 0),
|
||||
);
|
||||
});
|
||||
|
||||
// WRITE PASS 1: size-lock roots and intermediates so they don't collapse
|
||||
// when their children become position:fixed.
|
||||
[...rootSizes, ...intermediates].forEach(({ el, w, h }) => {
|
||||
this.dirtyElements.push({ el, originalCssText: el.style.cssText });
|
||||
el.style.width = `${w}px`;
|
||||
el.style.height = `${h}px`;
|
||||
});
|
||||
|
||||
// READ PASS 2: batch all getBoundingClientRect calls before any leaf writes.
|
||||
const snapshots = leafElements.map((el) => ({
|
||||
el,
|
||||
rect: el.getBoundingClientRect(),
|
||||
}));
|
||||
|
||||
// WRITE PASS 2: apply fixed positioning to leaves using snapshotted rects.
|
||||
this.bodies = snapshots.map(({ el, rect }) => {
|
||||
this.dirtyElements.push({ el, originalCssText: el.style.cssText });
|
||||
el.style.width = `${rect.width}px`;
|
||||
el.style.height = `${rect.height}px`;
|
||||
el.style.margin = "0px";
|
||||
el.style.position = "fixed";
|
||||
el.style.left = "0px";
|
||||
el.style.top = "0px";
|
||||
el.style.transform = `translate(${rect.left}px, ${rect.top}px)`;
|
||||
|
||||
const body: PhysicsBody = {
|
||||
el,
|
||||
x: rect.left,
|
||||
y: rect.top,
|
||||
vx: (Math.random() - 0.5) * 8,
|
||||
vy: (Math.random() - 0.5) * 5,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
originX: rect.left,
|
||||
originY: rect.top,
|
||||
isDragging: false,
|
||||
startX: 0,
|
||||
startY: 0,
|
||||
};
|
||||
|
||||
this.attachMouseEvents(body);
|
||||
return body;
|
||||
});
|
||||
|
||||
this.tick();
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.isRunning = false;
|
||||
if (this.animationFrameId) {
|
||||
cancelAnimationFrame(this.animationFrameId);
|
||||
this.animationFrameId = null;
|
||||
}
|
||||
|
||||
const DURATION = 600;
|
||||
|
||||
// Glide each body back to its origin position.
|
||||
this.bodies.forEach((body) => {
|
||||
body.el.style.transition = `transform ${DURATION}ms cubic-bezier(0.22, 1, 0.36, 1)`;
|
||||
body.el.style.transform = `translate(${body.originX}px, ${body.originY}px)`;
|
||||
});
|
||||
|
||||
// After the transition completes, restore every element's original inline
|
||||
// styles so document flow is fully recovered — including elements that had
|
||||
// pre-existing inline styles we must not erase (e.g. Next.js fill images).
|
||||
const elementsToClear = [...this.dirtyElements];
|
||||
this.bodies = [];
|
||||
this.dirtyElements = [];
|
||||
|
||||
setTimeout(() => {
|
||||
elementsToClear.forEach(({ el, originalCssText }) => {
|
||||
el.style.cssText = originalCssText;
|
||||
});
|
||||
}, DURATION);
|
||||
}
|
||||
|
||||
private attachMouseEvents(body: PhysicsBody) {
|
||||
// Prevent default drag behaviors that interfere with physics
|
||||
body.el.ondragstart = () => false;
|
||||
|
||||
body.el.addEventListener("pointerdown", (e) => {
|
||||
body.isDragging = true;
|
||||
body.startX = e.clientX;
|
||||
body.startY = e.clientY;
|
||||
body.vx = 0;
|
||||
body.vy = 0;
|
||||
body.el.setPointerCapture(e.pointerId);
|
||||
});
|
||||
|
||||
body.el.addEventListener("pointermove", (e) => {
|
||||
if (!body.isDragging) return;
|
||||
|
||||
// Calculate velocity based on mouse movement for the "throw"
|
||||
body.vx = e.movementX * 0.5;
|
||||
body.vy = e.movementY * 0.5;
|
||||
|
||||
body.x += e.movementX;
|
||||
body.y += e.movementY;
|
||||
});
|
||||
|
||||
body.el.addEventListener("pointerup", (e) => {
|
||||
body.isDragging = false;
|
||||
body.el.releasePointerCapture(e.pointerId);
|
||||
|
||||
// The Drag vs. Click Resolver
|
||||
const deltaX = Math.abs(e.clientX - body.startX);
|
||||
const deltaY = Math.abs(e.clientY - body.startY);
|
||||
|
||||
// If the user moved the mouse less than 5px, treat it as a click
|
||||
if (deltaX > 5 || deltaY > 5) {
|
||||
// It was a drag. Prevent links from firing.
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
|
||||
// Catch clicks at the capture phase to stop them if we were dragging
|
||||
body.el.addEventListener(
|
||||
"click",
|
||||
(e) => {
|
||||
const deltaX = Math.abs(e.clientX - body.startX);
|
||||
const deltaY = Math.abs(e.clientY - body.startY);
|
||||
if (deltaX > 5 || deltaY > 5) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
},
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
private tick = () => {
|
||||
if (!this.isRunning) return;
|
||||
|
||||
const gravity = 0.5;
|
||||
const bounce = -0.7;
|
||||
const floorY = window.innerHeight;
|
||||
|
||||
this.bodies.forEach((body) => {
|
||||
if (body.isDragging) {
|
||||
body.el.style.transform = `translate(${body.x}px, ${body.y}px)`;
|
||||
return;
|
||||
}
|
||||
|
||||
body.vy += gravity;
|
||||
body.x += body.vx;
|
||||
body.y += body.vy;
|
||||
|
||||
body.vx *= 0.99;
|
||||
body.vy *= 0.99;
|
||||
|
||||
if (body.y + body.height > floorY) {
|
||||
body.y = floorY - body.height;
|
||||
body.vy *= bounce;
|
||||
body.vx *= 0.9;
|
||||
}
|
||||
|
||||
body.el.style.transform = `translate(${body.x}px, ${body.y}px)`;
|
||||
});
|
||||
|
||||
this.animationFrameId = requestAnimationFrame(this.tick);
|
||||
};
|
||||
}
|
||||
@@ -2,6 +2,13 @@ export interface Skill {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface JobSubPhase {
|
||||
label: string;
|
||||
start_date: string;
|
||||
end_date: string | null;
|
||||
bullets: string[];
|
||||
}
|
||||
|
||||
export interface Job {
|
||||
id: number;
|
||||
position: string;
|
||||
@@ -10,6 +17,8 @@ export interface Job {
|
||||
start_date: string;
|
||||
end_date: string | null;
|
||||
technologies: string[];
|
||||
summary?: string;
|
||||
sub_phases?: JobSubPhase[];
|
||||
}
|
||||
|
||||
export interface Project {
|
||||
|
||||
6968
package-lock.json
generated
Normal file
6968
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@@ -14,19 +14,21 @@
|
||||
"next": "15.5.7",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"remark": "^15.0.1",
|
||||
"remark-html": "^16.0.1",
|
||||
"tailwindcss-motion": "^1.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5",
|
||||
"@eslint/eslintrc": "^3",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"tailwindcss": "^4",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.5.2",
|
||||
"@eslint/eslintrc": "^3"
|
||||
"hast": "^0.0.2",
|
||||
"tailwindcss": "^4",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
|
||||
BIN
public/cv.pdf
BIN
public/cv.pdf
Binary file not shown.
BIN
public/images/galeria_rumia.avif
Normal file
BIN
public/images/galeria_rumia.avif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
BIN
public/images/galeria_rumia2.avif
Normal file
BIN
public/images/galeria_rumia2.avif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
BIN
public/images/k-convert.avif
Normal file
BIN
public/images/k-convert.avif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
public/images/k-launcher.avif
Normal file
BIN
public/images/k-launcher.avif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.3 KiB |
BIN
public/images/k-tv.png
Normal file
BIN
public/images/k-tv.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
Reference in New Issue
Block a user