Compare commits

...

2 Commits

7 changed files with 184 additions and 155 deletions

View File

@ -37,10 +37,10 @@ import MobileMenu from "./MobileMenu.astro";
class="flex bg-black/5 backdrop-blur-md overflow-hidden theme-border border divide-x divide-white/5 shadow-sm rounded-xl"
>
{
["About", "Work", "Contact"].map((item, index) => (
["About", "Hobby", "Work", "Contact"].map((item, index) => (
<a
href={`#${item.toLowerCase()}`}
class={`nav-item px-5 py-2 theme-secondary hover:theme-primary theme-transition ${index === 0 ? "rounded-l-xl" : ""} ${index === 2 ? "rounded-r-xl" : ""}`}
class={`nav-item px-5 py-2 theme-secondary hover:theme-primary theme-transition ${index === 0 ? "rounded-l-xl" : ""} ${index === 3 ? "rounded-r-xl" : ""}`}
aria-label={`View my ${item.toLowerCase()}`}
>
{item}

View File

@ -3,6 +3,7 @@ import { FaGithub, FaLinkedin, FaEnvelope } from "react-icons/fa";
const navItems = [
{ label: "About", href: "#about" },
{ label: "Hobby", href: "#hobby" },
{ label: "Work", href: "#work" },
{ label: "Contact", href: "#contact" },
];

View File

@ -1,24 +1,24 @@
---
import FadeIn from "../ui/FadeIn.astro";
import { Icon } from "astro-icon/components";
import { SiPhp, SiJavascript, SiMysql, SiReact } from "react-icons/si";
import { FaGithub, FaLinkedin, FaGlobe, FaPalette } from "react-icons/fa";
import { SiPhp, SiJavascript, SiMysql, SiReact, SiTypescript, SiAstro } from "react-icons/si";
import { FaGithub, FaLinkedin, FaGlobe, FaPalette, FaCamera } from "react-icons/fa";
import { BsLightningChargeFill } from "react-icons/bs";
import { IoServerOutline } from "react-icons/io5";
import { Image } from "astro:assets";
const technologies = [
{ name: "PHP", icon: SiPhp },
{ name: "JavaScript", icon: SiJavascript },
{ name: "MySQL", icon: SiMysql },
{ name: "TypeScript", icon: SiTypescript },
{ name: "React", icon: SiReact },
{ name: "PHP", icon: SiPhp },
{ name: "Astro", icon: SiAstro },
];
const interests = [
{ name: "Web Development", icon: FaGlobe },
{ name: "System Architecture", icon: IoServerOutline },
{ name: "UI/UX Design", icon: FaPalette },
{ name: "Performance Optimization", icon: BsLightningChargeFill },
{ name: "Creative Vision", icon: FaCamera },
];
---
@ -66,7 +66,9 @@ const interests = [
Jan-Marlon Leibl
</h3>
<p class="font-['Instrument_Sans'] theme-text-70 text-base sm:text-lg">
Fullstack Developer
Fullstack Web Developer
<br>
Photographer Hobbyist
</p>
</div>
</FadeIn>
@ -76,13 +78,13 @@ const interests = [
<div class="space-y-10 sm:space-y-16 font-['Instrument_Sans']">
<div class="space-y-6 sm:space-y-8">
<p class="text-xl sm:text-2xl theme-text-90 leading-relaxed">
Hello! I'm Jan-Marlon, but please call me Jan. I started my journey in programming at
the age of 11 with C#, fascinated by a desktop application my friend created.
Hey there! I'm Jan-Marlon, but everyone calls me Jan. My coding journey kicked off at age 11 with C#, after being amazed by a simple app my friend built.
</p>
<p class="text-base sm:text-xl theme-text-70 leading-relaxed">
Today, I specialize in PHP and TypeScript development, constantly pushing the
boundaries of what's possible on the web. My journey has led me from creating simple
applications to developing complex systems used by thousands.
These days, I work with PHP and TypeScript to build cool stuff on the web. I've grown from tinkering with basic projects to crafting robust systems that thousands of people use daily.
</p>
<p class="text-base sm:text-xl theme-text-70 leading-relaxed">
I bring the same eye for detail to my code that I do to my photography. There's something magical about creating something both functional and beautiful, whether it's a web app or a perfect shot.
</p>
</div>

View File

@ -19,7 +19,7 @@ import GermanyFlag from "../ui/GermanyFlag.astro";
<span class="w-2 h-2 rounded-full bg-emerald-500 animate-pulse"></span>
<span
class="theme-secondary uppercase tracking-wider text-xs sm:text-sm font-medium font-['Instrument_Sans']"
>Available for Work</span
>Open to New Projects</span
>
</div>
<div class="space-y-3 sm:space-y-4">
@ -32,10 +32,10 @@ import GermanyFlag from "../ui/GermanyFlag.astro";
<h1
class="font-['DM_Sans'] text-4xl sm:text-6xl md:text-7xl lg:text-8xl font-bold tracking-tight leading-[1.05] sm:leading-[0.95] max-w-4xl theme-primary theme-transition"
>
Software Developer
Web Developer
<br />
<span class="theme-secondary inline-flex items-center gap-2"
>based in <GermanyFlag /></span
>from <GermanyFlag /></span
>
</h1>
</div>
@ -44,15 +44,14 @@ import GermanyFlag from "../ui/GermanyFlag.astro";
<p
class="font-['Instrument_Sans'] theme-secondary text-lg sm:text-2xl max-w-2xl leading-relaxed tracking-tight theme-transition"
>
Passionate about creating digital experiences, with a focus on PHP and modern web
technologies.
Crafting engaging digital experiences with PHP, TypeScript, and a splash of creative thinking.
</p>
<div class="flex flex-col sm:flex-row gap-4 sm:gap-5">
<a
href="#work"
class="animate-button w-full sm:w-auto text-center inline-flex items-center justify-center gap-2 px-6 sm:px-8 py-3 sm:py-4 theme-button-primary rounded-xl transition-all text-sm sm:text-base tracking-tight font-medium group shadow-sm"
>
View My Work
See My Work
<svg
class="w-4 h-4 sm:w-5 sm:h-5 transform group-hover:translate-x-1 transition-transform"
viewBox="0 0 24 24"
@ -67,7 +66,7 @@ import GermanyFlag from "../ui/GermanyFlag.astro";
href="mailto:jleibl@proton.me"
class="animate-button w-full sm:w-auto text-center inline-flex items-center justify-center gap-2 px-6 sm:px-8 py-3 sm:py-4 border theme-border rounded-xl theme-button-secondary hover:theme-bg-05 transition-all text-sm sm:text-base tracking-tight font-medium group shadow-sm"
>
Get in Touch
Say Hello
<svg
class="w-4 h-4 sm:w-5 sm:h-5 transform group-hover:translate-x-1 transition-transform"
viewBox="0 0 24 24"
@ -112,7 +111,7 @@ import GermanyFlag from "../ui/GermanyFlag.astro";
>
</div>
<span class="theme-text-90 text-base sm:text-lg block w-full">
Fullstack Developer
Fullstack Web Dev
</span>
</div>

View File

@ -0,0 +1,104 @@
---
import FadeIn from "../ui/FadeIn.astro";
import { FaCamera, FaPhotoVideo } from "react-icons/fa";
import { MdPhotoCamera, MdCameraAlt } from "react-icons/md";
const cameras = [
{ name: "Nikon D7200", icon: MdCameraAlt, description: "Dad's camera that got me hooked on photography" },
{ name: "Nikon D3300", icon: MdCameraAlt, description: "My first personal camera where it all began" },
{ name: "Sony A7 III", icon: MdPhotoCamera, description: "Current setup that's taken my shots to the next level" },
];
const genres = [
{ name: "Portrait", icon: FaCamera, description: "Love capturing people's authentic expressions" },
{ name: "Street", icon: FaCamera, description: "Finding beauty in everyday urban moments" },
{ name: "Studio", icon: FaPhotoVideo, description: "Creating controlled lighting magic" },
{ name: "Landscape", icon: FaPhotoVideo, description: "Chasing perfect light in nature" },
];
---
<section id="hobby" class="py-24 sm:py-40 px-4 sm:px-8 relative">
<div class="absolute inset-0 theme-bg-gradient opacity-50"></div>
<div
class="absolute top-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-white/10 to-transparent"
>
</div>
<div class="max-w-(--breakpoint-xl) mx-auto relative">
<FadeIn>
<div class="flex items-baseline gap-4 mb-12 sm:mb-24">
<h2
class="font-['DM_Sans'] text-3xl sm:text-5xl font-semibold tracking-tight theme-primary"
>
Hobby
</h2>
<div class="h-px grow theme-border opacity-60"></div>
</div>
</FadeIn>
<div class="grid gap-12 sm:gap-16">
<FadeIn delay={0.2}>
<div class="space-y-10 sm:space-y-16 font-['Instrument_Sans']">
<div class="space-y-6 sm:space-y-8">
<p class="text-xl sm:text-2xl theme-text-90 leading-relaxed">
When I'm not coding, you'll find me with a camera in hand, hunting for that perfect shot that tells a story without words.
</p>
<p class="text-base sm:text-xl theme-text-70 leading-relaxed">
It all started with borrowing my dad's Nikon D7200 I was instantly hooked. Eventually got my own Nikon D3300 to learn the basics. These days, I'm having a blast with my Sony A7 III, experimenting with different styles and techniques across various photography genres.
</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-10 sm:gap-16">
<div class="space-y-6 sm:space-y-8">
<div class="flex items-baseline gap-4">
<h3 class="text-sm sm:text-base theme-text-40 uppercase tracking-wider font-medium">
Camera Evolution
</h3>
<div class="h-px grow theme-border opacity-30"></div>
</div>
<ul class="space-y-4 sm:space-y-5">
{
cameras.map((camera) => (
<li class="text-base sm:text-lg group flex flex-col gap-1 theme-text-70 hover:theme-text-90 cursor-default theme-bg-05 px-4 py-3 rounded-xl shadow-sm hover:shadow-md border border-transparent hover:theme-border transition-all duration-300">
<div class="flex items-center gap-3">
<span class="theme-text-40 group-hover:theme-text-90 flex items-center justify-center w-6 h-6 transition-all">
<camera.icon className="w-5 h-5" />
</span>
{camera.name}
</div>
<p class="text-sm theme-text-50 pl-9">{camera.description}</p>
</li>
))
}
</ul>
</div>
<div class="space-y-6 sm:space-y-8">
<div class="flex items-baseline gap-4">
<h3 class="text-sm sm:text-base theme-text-40 uppercase tracking-wider font-medium">
Favorite Genres
</h3>
<div class="h-px grow theme-border opacity-30"></div>
</div>
<ul class="space-y-4 sm:space-y-5">
{
genres.map((genre) => (
<li class="text-base sm:text-lg group flex flex-col gap-1 theme-text-70 hover:theme-text-90 cursor-default theme-bg-05 px-4 py-3 rounded-xl shadow-sm hover:shadow-md border border-transparent hover:theme-border transition-all duration-300">
<div class="flex items-center gap-3">
<span class="theme-text-40 group-hover:theme-text-90 flex items-center justify-center w-6 h-6 transition-all">
<genre.icon className="w-5 h-5" />
</span>
{genre.name}
</div>
<p class="text-sm theme-text-50 pl-9">{genre.description}</p>
</li>
))
}
</ul>
</div>
</div>
</div>
</FadeIn>
</div>
</div>
</section>

View File

@ -9,68 +9,31 @@ import {
SiDiscord,
} from "react-icons/si";
import { BsLightningChargeFill } from "react-icons/bs";
import { FaCode } from "react-icons/fa";
import { HiOutlineCollection } from "react-icons/hi";
import { MdLaunch } from "react-icons/md";
import FadeIn from "../ui/FadeIn.astro";
interface Project {
title: string;
year: string;
description: string;
icon: any;
tags: Array<{
name: string;
icon: any;
}>;
link?: string;
}
const getTagIcon = (tag: string) => {
switch (tag) {
case "Next.js":
return SiNextdotjs;
case "Tailwind CSS":
return SiTailwindcss;
case "TypeScript":
return SiTypescript;
case "PHP":
return SiPhp;
case "JavaScript":
return SiJavascript;
case "MySQL":
return SiMysql;
case "Performance":
return BsLightningChargeFill;
case "Discord API":
return SiDiscord;
default:
return null;
}
const iconMap = {
"Next.js": SiNextdotjs,
"Tailwind CSS": SiTailwindcss,
"TypeScript": SiTypescript,
"PHP": SiPhp,
"JavaScript": SiJavascript,
"MySQL": SiMysql,
"Performance": BsLightningChargeFill,
"Discord API": SiDiscord,
};
const getProjectIcon = (title: string) => {
if (title.includes("ventry")) {
return MdLaunch;
} else if (title.includes("ShareUpload")) {
return HiOutlineCollection;
} else if (title.includes("RestoreM")) {
return SiDiscord;
} else {
return FaCode;
}
};
const projects: Project[] = [
const projects = [
{
title: "ventry.host v2",
year: "2025",
description: "Free file hosting revamped with a modern design and improved user experience.",
icon: getProjectIcon("ventry.host v2"),
icon: MdLaunch,
tags: [
{ name: "Next.js", icon: getTagIcon("Next.js") },
{ name: "Tailwind CSS", icon: getTagIcon("Tailwind CSS") },
{ name: "TypeScript", icon: getTagIcon("TypeScript") },
{ name: "Next.js", icon: iconMap["Next.js"] },
{ name: "Tailwind CSS", icon: iconMap["Tailwind CSS"] },
{ name: "TypeScript", icon: iconMap["TypeScript"] },
],
link: "https://ventry.host",
},
@ -78,40 +41,36 @@ const projects: Project[] = [
title: "ventry.host",
year: "2023",
description: "A free file hosting solution with thousands of daily visitors.",
icon: getProjectIcon("ventry.host"),
icon: MdLaunch,
tags: [
{ name: "PHP", icon: getTagIcon("PHP") },
{ name: "JavaScript", icon: getTagIcon("JavaScript") },
{ name: "MySQL", icon: getTagIcon("MySQL") },
{ name: "PHP", icon: iconMap["PHP"] },
{ name: "JavaScript", icon: iconMap["JavaScript"] },
{ name: "MySQL", icon: iconMap["MySQL"] },
],
},
{
title: "ShareUpload",
year: "2022",
description: "High-performance file sharing platform with unlimited storage.",
icon: getProjectIcon("ShareUpload"),
icon: HiOutlineCollection,
tags: [
{ name: "PHP", icon: getTagIcon("PHP") },
{ name: "MySQL", icon: getTagIcon("MySQL") },
{ name: "Performance", icon: getTagIcon("Performance") },
{ name: "PHP", icon: iconMap["PHP"] },
{ name: "MySQL", icon: iconMap["MySQL"] },
{ name: "Performance", icon: iconMap["Performance"] },
],
},
{
title: "RestoreM",
year: "2023",
description: "Discord server backup and restoration service.",
icon: getProjectIcon("RestoreM"),
icon: SiDiscord,
tags: [
{ name: "PHP", icon: getTagIcon("PHP") },
{ name: "MySQL", icon: getTagIcon("MySQL") },
{ name: "Discord API", icon: getTagIcon("Discord API") },
{ name: "PHP", icon: iconMap["PHP"] },
{ name: "MySQL", icon: iconMap["MySQL"] },
{ name: "Discord API", icon: iconMap["Discord API"] },
],
},
];
const sortedProjects = [...projects].sort((a, b) => {
return parseInt(b.year) - parseInt(a.year);
});
].sort((a, b) => parseInt(b.year) - parseInt(a.year));
---
<section id="work" class="py-24 sm:py-40 px-4 sm:px-8 relative">
@ -138,7 +97,7 @@ const sortedProjects = [...projects].sort((a, b) => {
<div class="grid gap-24 sm:gap-32">
{
sortedProjects.map((project, index) => (
projects.map((project, index) => (
<FadeIn delay={index * 0.1}>
<div class="group relative" data-project-card>
{project.link && (
@ -182,17 +141,14 @@ const sortedProjects = [...projects].sort((a, b) => {
Technologies
</h4>
<div class="flex flex-wrap items-center gap-3 sm:gap-4 relative z-20 pointer-events-none">
{project.tags.map((tag, tagIndex) => {
const Icon = tag.icon;
return (
<span class="flex items-center text-sm sm:text-base theme-text-70 font-['Instrument_Sans'] tracking-tight font-medium py-2 px-5 group-hover:theme-text-90 transition-all duration-300 theme-bg-05 rounded-full shadow-sm border border-transparent group-hover:theme-border">
<span class="mr-2 flex items-center">
<Icon className="text-lg" />
</span>
{tag.name}
{project.tags.map((tag) => (
<span class="flex items-center text-sm sm:text-base theme-text-70 font-['Instrument_Sans'] tracking-tight font-medium py-2 px-5 group-hover:theme-text-90 transition-all duration-300 theme-bg-05 rounded-full shadow-sm border border-transparent group-hover:theme-border">
<span class="mr-2 flex items-center">
<tag.icon className="text-lg" />
</span>
);
})}
{tag.name}
</span>
))}
</div>
</div>
</div>
@ -254,35 +210,21 @@ const sortedProjects = [...projects].sort((a, b) => {
const modal = document.getElementById('redirect-modal');
const modalOverlay = document.getElementById('modal-overlay');
const modalContent = modal?.querySelector('.max-w-md');
const cancelButton = document.getElementById('cancel-redirect');
const confirmButton = document.getElementById('confirm-redirect');
const redirectUrlElement = document.getElementById('redirect-url');
const redirectUrlEl = document.getElementById('redirect-url');
let currentUrl = '';
let currentTitle = '';
function showModal(title, url) {
function showModal(url) {
currentUrl = url;
currentTitle = title;
if (redirectUrlElement) {
redirectUrlElement.textContent = new URL(url).hostname;
}
if (redirectUrlEl) redirectUrlEl.textContent = new URL(url).hostname;
if (modal) {
modal.classList.remove('pointer-events-none');
setTimeout(() => {
modal.classList.add('opacity-100');
if (modalOverlay) {
modalOverlay.classList.add('bg-opacity-60', 'backdrop-blur-sm');
}
if (modalContent) {
modalContent.classList.remove('scale-95', 'opacity-0');
modalContent.classList.add('scale-100', 'opacity-100');
}
modalOverlay?.classList.add('bg-opacity-60', 'backdrop-blur-sm');
modalContent?.classList.remove('scale-95', 'opacity-0');
modalContent?.classList.add('scale-100', 'opacity-100');
}, 10);
document.body.style.overflow = 'hidden';
}
}
@ -290,13 +232,9 @@ const sortedProjects = [...projects].sort((a, b) => {
function hideModal() {
if (modal) {
modal.classList.remove('opacity-100');
if (modalOverlay) {
modalOverlay.classList.remove('bg-opacity-60', 'backdrop-blur-sm');
}
if (modalContent) {
modalContent.classList.remove('scale-100', 'opacity-100');
modalContent.classList.add('scale-95', 'opacity-0');
}
modalOverlay?.classList.remove('bg-opacity-60', 'backdrop-blur-sm');
modalContent?.classList.remove('scale-100', 'opacity-100');
modalContent?.classList.add('scale-95', 'opacity-0');
setTimeout(() => {
modal.classList.add('pointer-events-none');
@ -305,41 +243,24 @@ const sortedProjects = [...projects].sort((a, b) => {
}
}
const projectLinks = document.querySelectorAll('[data-project-link]');
projectLinks.forEach(link => {
document.querySelectorAll('[data-project-link]').forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const url = link.getAttribute('data-project-url');
const title = link.getAttribute('data-project-title');
if (url) {
showModal(title, url);
}
if (url) showModal(url);
});
});
if (cancelButton) {
cancelButton.addEventListener('click', hideModal);
}
if (modalOverlay) {
modalOverlay.addEventListener('click', hideModal);
}
if (confirmButton) {
confirmButton.addEventListener('click', () => {
if (currentUrl) {
window.open(currentUrl, '_blank');
}
hideModal();
});
}
document.getElementById('cancel-redirect')?.addEventListener('click', hideModal);
modalOverlay?.addEventListener('click', hideModal);
document.getElementById('confirm-redirect')?.addEventListener('click', () => {
if (currentUrl) window.open(currentUrl, '_blank');
hideModal();
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
hideModal();
}
if (e.key === 'Escape') hideModal();
});
});
</script>

View File

@ -5,6 +5,7 @@ import SkipToContent from "../components/ui/SkipToContent.astro";
import Hero from "../components/sections/Hero.astro";
import About from "../components/sections/About.astro";
import Work from "../components/sections/Work.astro";
import Hobby from "../components/sections/Hobby.astro";
import Contact from "../components/sections/Contact.astro";
import Footer from "../components/layout/Footer.astro";
@ -30,6 +31,7 @@ import "@fontsource/instrument-sans/500.css";
<main id="main-content">
<Hero />
<About />
<Hobby />
<Work />
<Contact />
</main>