feat: add portfolio, blog, and contact features, update theme styling, refine imports, and clean up unused code (#2)
Reviewed-on: #2
This commit is contained in:
@ -83,8 +83,8 @@ const interests = [
|
||||
</div>
|
||||
<ul class="space-y-4 sm:space-y-5">
|
||||
{technologies.map((tech) => (
|
||||
<li class="text-base sm:text-lg group flex items-center gap-3 theme-text-70 hover:theme-text-90 transition-colors cursor-default theme-bg-05 px-4 py-2.5 rounded-xl shadow-sm hover:shadow-md border border-transparent hover:theme-border transition-all duration-300">
|
||||
<span class="theme-text-40 group-hover:theme-text-90 flex items-center justify-center w-6 h-6 transition-colors">
|
||||
<li class="text-base sm:text-lg group flex items-center gap-3 theme-text-70 hover:theme-text-90 cursor-default theme-bg-05 px-4 py-2.5 rounded-xl shadow-sm hover:shadow-md border border-transparent hover:theme-border transition-all duration-300">
|
||||
<span class="theme-text-40 group-hover:theme-text-90 flex items-center justify-center w-6 h-6 transition-all">
|
||||
<tech.icon className="w-5 h-5" />
|
||||
</span>
|
||||
{tech.name}
|
||||
@ -102,8 +102,8 @@ const interests = [
|
||||
</div>
|
||||
<ul class="space-y-4 sm:space-y-5">
|
||||
{interests.map((interest) => (
|
||||
<li class="text-base sm:text-lg group flex items-center gap-3 theme-text-70 hover:theme-text-90 transition-colors cursor-default theme-bg-05 px-4 py-2.5 rounded-xl shadow-sm hover:shadow-md border border-transparent hover:theme-border transition-all duration-300">
|
||||
<span class="theme-text-40 group-hover:theme-text-90 flex items-center justify-center w-6 h-6 transition-colors">
|
||||
<li class="text-base sm:text-lg group flex items-center gap-3 theme-text-70 hover:theme-text-90 cursor-default theme-bg-05 px-4 py-2.5 rounded-xl shadow-sm hover:shadow-md border border-transparent hover:theme-border transition-all duration-300">
|
||||
<span class="theme-text-40 group-hover:theme-text-90 flex items-center justify-center w-6 h-6 transition-all">
|
||||
<interest.icon className="w-5 h-5" />
|
||||
</span>
|
||||
{interest.name}
|
||||
|
132
src/components/sections/BlogPreview.astro
Normal file
132
src/components/sections/BlogPreview.astro
Normal file
@ -0,0 +1,132 @@
|
||||
---
|
||||
import FadeIn from '../ui/FadeIn.astro';
|
||||
|
||||
const blogPosts = [
|
||||
{
|
||||
title: "Best Practices for Modern PHP Development",
|
||||
excerpt: "Explore how to leverage PHP 8.x features and modern tools to create maintainable and performant PHP applications.",
|
||||
date: "May 15, 2023",
|
||||
slug: "best-practices-modern-php-development",
|
||||
tags: ["PHP", "Development", "Best Practices"]
|
||||
},
|
||||
{
|
||||
title: "Building Type-Safe APIs with TypeScript",
|
||||
excerpt: "How to use TypeScript's powerful type system to create robust and maintainable API integrations in your frontend applications.",
|
||||
date: "April 3, 2023",
|
||||
slug: "building-type-safe-apis-typescript",
|
||||
tags: ["TypeScript", "API", "Frontend"]
|
||||
},
|
||||
{
|
||||
title: "Performance Optimization Techniques for Web Applications",
|
||||
excerpt: "A comprehensive guide to identifying and solving performance bottlenecks in modern web applications.",
|
||||
date: "March 12, 2023",
|
||||
slug: "performance-optimization-web-applications",
|
||||
tags: ["Performance", "Web", "Optimization"]
|
||||
}
|
||||
];
|
||||
---
|
||||
|
||||
<section id="blog" class="py-20 sm:py-32 px-4 sm:px-8 relative">
|
||||
<div class="absolute inset-0 theme-bg-gradient opacity-30"></div>
|
||||
|
||||
<div class="max-w-7xl mx-auto relative">
|
||||
<FadeIn className="space-y-16">
|
||||
<div class="space-y-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-3xl sm:text-4xl md:text-5xl font-bold font-['DM_Sans'] tracking-tight theme-primary">
|
||||
Blog
|
||||
</h2>
|
||||
<p class="font-['Instrument_Sans'] theme-secondary text-lg sm:text-xl max-w-3xl mt-4">
|
||||
Thoughts, insights, and tutorials on software development and tech.
|
||||
<span class="inline-flex items-center px-3 py-1 ml-2 rounded-full text-xs font-medium bg-black/20 text-white font-['Instrument_Sans']">Coming Soon</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="hidden sm:block">
|
||||
<button
|
||||
class="animate-button inline-flex items-center justify-center gap-2 px-6 py-3 bg-transparent border theme-border theme-text-50 rounded-xl transition-all text-sm tracking-tight font-medium shadow-sm font-['Instrument_Sans'] cursor-not-allowed opacity-75"
|
||||
title="Coming soon!"
|
||||
>
|
||||
View All Posts
|
||||
<svg
|
||||
class="w-4 h-4"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M5 12h14M12 5l7 7-7 7"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 sm:gap-8 relative p-12">
|
||||
<div class="absolute inset-0 backdrop-blur-sm z-10 flex items-center justify-center rounded-xl">
|
||||
<div class="bg-black/80 px-6 py-5 rounded-xl border border-white/20 text-center">
|
||||
<span class="text-xl font-semibold text-white font-['DM_Sans']">Blog Coming Soon!</span>
|
||||
<p class="text-white/80 mt-2 font-['Instrument_Sans']">The blog feature is currently in development.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{blogPosts.map((post) => (
|
||||
<div
|
||||
class="flex flex-col h-full theme-accent border theme-border rounded-xl overflow-hidden opacity-80 group"
|
||||
>
|
||||
<div class="p-6 sm:p-8 flex flex-col h-full">
|
||||
<div class="flex-grow space-y-4">
|
||||
<div class="space-y-1.5">
|
||||
<div class="flex gap-2 flex-wrap">
|
||||
{post.tags.map((tag) => (
|
||||
<span class="inline-flex px-2.5 py-1 rounded-lg text-xs font-medium theme-text-70 bg-black/10 font-['Instrument_Sans']">{tag}</span>
|
||||
))}
|
||||
</div>
|
||||
<h3 class="text-xl font-bold theme-primary font-['DM_Sans']">{post.title}</h3>
|
||||
</div>
|
||||
<p class="theme-secondary font-['Instrument_Sans'] text-sm sm:text-base leading-relaxed">{post.excerpt}</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 pt-6 border-t theme-border flex items-center justify-between">
|
||||
<span class="text-sm theme-text-70 font-['Instrument_Sans']">{post.date}</span>
|
||||
<div class="p-2 rounded-full theme-accent">
|
||||
<svg
|
||||
class="w-4 h-4 theme-primary"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path d="M5 12h14M12 5l7 7-7 7"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div class="sm:hidden flex justify-center mt-8">
|
||||
<button
|
||||
class="animate-button inline-flex items-center justify-center gap-2 px-8 py-4 bg-transparent border theme-border theme-text-50 rounded-xl transition-all text-base tracking-tight font-medium shadow-sm font-['Instrument_Sans'] cursor-not-allowed opacity-75"
|
||||
title="Coming soon!"
|
||||
>
|
||||
View All Posts
|
||||
<svg
|
||||
class="w-5 h-5"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M5 12h14M12 5l7 7-7 7"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</FadeIn>
|
||||
</div>
|
||||
</section>
|
179
src/components/sections/Contact.astro
Normal file
179
src/components/sections/Contact.astro
Normal file
@ -0,0 +1,179 @@
|
||||
---
|
||||
import FadeIn from '../ui/FadeIn.astro';
|
||||
import { FaGithub, FaLinkedin, FaEnvelope, FaPhone, FaMapMarkerAlt, FaGlobe } from "react-icons/fa";
|
||||
---
|
||||
|
||||
<section id="contact" class="py-20 sm:py-32 px-4 sm:px-8 relative">
|
||||
<div class="absolute inset-0 theme-bg-gradient opacity-30"></div>
|
||||
|
||||
<div class="max-w-7xl mx-auto relative">
|
||||
<FadeIn className="space-y-16">
|
||||
<div class="space-y-6">
|
||||
<h2 class="text-3xl sm:text-4xl md:text-5xl font-bold font-['DM_Sans'] tracking-tight theme-primary">
|
||||
Get In Touch
|
||||
</h2>
|
||||
<p class="font-['Instrument_Sans'] theme-secondary text-lg sm:text-xl max-w-3xl">
|
||||
Have a project in mind or want to chat? Feel free to reach out.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-10 lg:gap-16">
|
||||
<div class="space-y-8">
|
||||
<form id="contact-form" class="space-y-6" onsubmit="return openEmailClient(event)">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label for="name" class="block mb-2 text-sm font-medium theme-secondary font-['Instrument_Sans']">Your Name</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
name="name"
|
||||
class="w-full px-4 py-3 rounded-xl theme-accent border theme-border theme-secondary focus:ring-2 focus:ring-white/20 focus:border-white/30 transition-all bg-transparent font-['Instrument_Sans']"
|
||||
required
|
||||
aria-required="true"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="email" class="block mb-2 text-sm font-medium theme-secondary font-['Instrument_Sans']">Your Email</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
class="w-full px-4 py-3 rounded-xl theme-accent border theme-border theme-secondary focus:ring-2 focus:ring-white/20 focus:border-white/30 transition-all bg-transparent font-['Instrument_Sans']"
|
||||
required
|
||||
aria-required="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="subject" class="block mb-2 text-sm font-medium theme-secondary font-['Instrument_Sans']">Subject</label>
|
||||
<input
|
||||
type="text"
|
||||
id="subject"
|
||||
name="subject"
|
||||
class="w-full px-4 py-3 rounded-xl theme-accent border theme-border theme-secondary focus:ring-2 focus:ring-white/20 focus:border-white/30 transition-all bg-transparent font-['Instrument_Sans']"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="message" class="block mb-2 text-sm font-medium theme-secondary font-['Instrument_Sans']">Your Message</label>
|
||||
<textarea
|
||||
id="message"
|
||||
name="message"
|
||||
rows="5"
|
||||
class="w-full px-4 py-3 rounded-xl theme-accent border theme-border theme-secondary focus:ring-2 focus:ring-white/20 focus:border-white/30 bg-transparent font-['Instrument_Sans']"
|
||||
required
|
||||
aria-required="true"
|
||||
></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
type="submit"
|
||||
class="animate-button w-full sm:w-auto inline-flex items-center justify-center gap-2 px-8 py-4 theme-button-primary rounded-xl transition-all text-base tracking-tight font-medium group shadow-sm font-['Instrument_Sans']"
|
||||
>
|
||||
Send Message
|
||||
<svg
|
||||
class="w-5 h-5 transform group-hover:translate-x-1 transition-transform"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M5 12h14M12 5l7 7-7 7"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div id="form-status" class="mt-4 text-sm hidden font-['Instrument_Sans']"></div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="space-y-8">
|
||||
<div>
|
||||
<h3 class="text-2xl font-semibold theme-primary mb-4 font-['DM_Sans']">Contact Information</h3>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-start gap-4">
|
||||
<span class="p-2 rounded-xl theme-bg-05 border theme-border theme-text-70 mt-1">
|
||||
<FaEnvelope className="w-5 h-5" />
|
||||
</span>
|
||||
<div>
|
||||
<h4 class="theme-secondary font-medium font-['Instrument_Sans']">Email</h4>
|
||||
<a href="mailto:jleibl@proton.me" class="theme-primary hover:underline font-['Instrument_Sans']">jleibl@proton.me</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-start gap-4">
|
||||
<span class="p-2 rounded-xl theme-bg-05 border theme-border theme-text-70 mt-1">
|
||||
<FaMapMarkerAlt className="w-5 h-5" />
|
||||
</span>
|
||||
<div>
|
||||
<h4 class="theme-secondary font-medium font-['Instrument_Sans']">Location</h4>
|
||||
<p class="theme-primary font-['Instrument_Sans']">Bremen, Germany</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-2xl font-semibold theme-primary mb-4 font-['DM_Sans']">Connect</h3>
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<a
|
||||
href="https://github.com/AtomicWasTaken"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="GitHub profile"
|
||||
class="p-3 sm:p-4 rounded-xl theme-bg-05 border theme-border theme-text-70 hover:theme-text-90 hover:scale-105 transition-all duration-300 shadow-sm"
|
||||
>
|
||||
<FaGithub className="w-6 h-6" />
|
||||
</a>
|
||||
<a
|
||||
href="https://www.linkedin.com/in/janmarlonleibl/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="LinkedIn profile"
|
||||
class="p-3 sm:p-4 rounded-xl theme-bg-05 border theme-border theme-text-70 hover:theme-text-90 hover:scale-105 transition-all duration-300 shadow-sm"
|
||||
>
|
||||
<FaLinkedin className="w-6 h-6" />
|
||||
</a>
|
||||
<a
|
||||
href="https://jleibl.net"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="My website"
|
||||
class="p-3 sm:p-4 rounded-xl theme-bg-05 border theme-border theme-text-70 hover:theme-text-90 hover:scale-105 transition-all duration-300 shadow-sm"
|
||||
>
|
||||
<FaGlobe className="w-6 h-6" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</FadeIn>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
(window as any).openEmailClient = (e: Event) => {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = new FormData(e.target as HTMLFormElement);
|
||||
const name = formData.get('name') as string;
|
||||
const email = formData.get('email') as string;
|
||||
const subject = formData.get('subject') as string || 'Contact Form Message';
|
||||
const message = formData.get('message') as string;
|
||||
|
||||
const formStatus = document.getElementById('form-status');
|
||||
if (!name || !email || !message) {
|
||||
if (formStatus) {
|
||||
formStatus.textContent = 'Please fill in all required fields.';
|
||||
formStatus.classList.remove('hidden');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const body = `Name: ${name}\nEmail: ${email}\n\n${message}`;
|
||||
window.location.href = `mailto:jleibl@proton.me?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
|
||||
|
||||
return false;
|
||||
};
|
||||
});
|
||||
</script>
|
@ -6,7 +6,7 @@ import GermanyFlag from '../ui/GermanyFlag.astro';
|
||||
<section class="min-h-[100dvh] flex items-center justify-center px-4 sm:px-8 relative pt-24 pb-16" id="hero">
|
||||
<div class="absolute inset-0 theme-bg-gradient opacity-80"></div>
|
||||
|
||||
<div class="max-w-7xl w-full relative pt-8 sm:pt-24">
|
||||
<div class="max-w-7xl w-full relative pt-8 sm:pt-4">
|
||||
<FadeIn className="space-y-10 sm:space-y-16">
|
||||
<div class="space-y-8 sm:space-y-10">
|
||||
<div class="space-y-6 sm:space-y-8">
|
||||
|
Reference in New Issue
Block a user