feat: add activity feed and background effects

- Implemented ActivityFeed component to display real-time user activity including coding, music, and chat interactions.
- Added GooFilter and BackgroundBlobs components for enhanced visual effects.
- Updated layout to include new components and ensure proper stacking context.
- Enhanced Tailwind CSS configuration with new color and font settings.
- Created API route to mock activity data from n8n.
- Refactored main page structure to streamline component rendering.
This commit is contained in:
2026-01-06 20:10:00 +01:00
parent e74f85da41
commit 4dc727fcd6
16 changed files with 801 additions and 1172 deletions

View File

@@ -0,0 +1,19 @@
import { NextResponse } from 'next/server';
export async function GET() {
// Mock data - in real integration this would fetch from n8n webhook or database
return NextResponse.json({
activity: {
type: 'coding', // coding, listening, watching
details: 'Portfolio Website',
timestamp: new Date().toISOString(),
},
music: {
isPlaying: true,
track: 'Midnight City',
artist: 'M83',
platform: 'spotify'
},
watching: null
});
}

View File

@@ -2,7 +2,8 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { Code, Database, Cloud, Smartphone, Globe, Zap, Brain, Rocket } from 'lucide-react'; import { Code, Terminal, Cpu, Globe } from 'lucide-react';
import { LiquidHeading } from '@/components/LiquidHeading';
const About = () => { const About = () => {
const [mounted, setMounted] = useState(false); const [mounted, setMounted] = useState(false);
@@ -11,180 +12,83 @@ const About = () => {
setMounted(true); setMounted(true);
}, []); }, []);
const skills = [ const techStack = [
{ {
category: 'Frontend', category: 'Frontend',
icon: Code, icon: Globe,
technologies: ['React', 'Next.js', 'TypeScript', 'Tailwind CSS', 'Framer Motion'], items: ['React', 'TypeScript', 'Tailwind', 'Next.js']
color: 'from-blue-500 to-cyan-500'
}, },
{ {
category: 'Backend', category: 'Backend',
icon: Database, icon: Terminal,
technologies: ['Node.js', 'PostgreSQL', 'Prisma', 'REST APIs', 'GraphQL'], items: ['Node.js', 'PostgreSQL', 'Prisma', 'API Design']
color: 'from-purple-500 to-pink-500'
}, },
{ {
category: 'DevOps', category: 'Tools',
icon: Cloud, icon: Cpu,
technologies: ['Docker', 'CI/CD', 'Nginx', 'Redis', 'AWS'], items: ['Git', 'Docker', 'VS Code', 'Figma']
color: 'from-green-500 to-emerald-500' }
},
{
category: 'Mobile',
icon: Smartphone,
technologies: ['React Native', 'Expo', 'iOS', 'Android'],
color: 'from-orange-500 to-red-500'
},
]; ];
const values = [ if (!mounted) return null;
{
icon: Brain,
title: 'Problem Solving',
description: 'I love tackling complex challenges and finding elegant solutions.'
},
{
icon: Zap,
title: 'Performance',
description: 'Building fast, efficient applications that scale with your needs.'
},
{
icon: Rocket,
title: 'Innovation',
description: 'Always exploring new technologies and best practices.'
},
{
icon: Globe,
title: 'User Experience',
description: 'Creating intuitive interfaces that users love to interact with.'
},
];
if (!mounted) {
return null;
}
return ( return (
<section id="about" className="py-20 px-4 relative overflow-hidden"> <section id="about" className="py-24 px-4 bg-white relative overflow-hidden">
<div className="max-w-7xl mx-auto"> <div className="max-w-6xl mx-auto relative z-10">
{/* Section Header */}
<motion.div <div className="grid grid-cols-1 lg:grid-cols-2 gap-16 items-center">
initial={{ opacity: 0, y: 30 }} {/* Text Content */}
whileInView={{ opacity: 1, y: 0 }} <div className="space-y-8">
viewport={{ once: true }} <LiquidHeading
transition={{ duration: 0.8 }} text="About Me"
className="text-center mb-16" level={2}
> className="text-4xl md:text-5xl font-bold text-stone-900"
<h2 className="text-4xl md:text-5xl font-bold mb-6 gradient-text"> />
About Me <div className="prose prose-stone prose-lg text-stone-600">
</h2> <p>
<p className="text-xl text-gray-400 max-w-3xl mx-auto leading-relaxed"> Hi, I&apos;m Dennis. I&apos;m a software engineer who likes building things that work well and look good.
I&apos;m a passionate software engineer with a love for creating beautiful, </p>
functional applications. I enjoy working with modern technologies and <p>
turning ideas into reality. I&apos;m currently based in Osnabrück, Germany. My journey in tech is driven by curiosityI love figuring out how things work and how to make them better.
</p> </p>
</motion.div> <p>
When I&apos;m not in front of a screen, you can find me listening to music, exploring new ideas, or just relaxing.
{/* About Content */} </p>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 mb-20">
<motion.div
initial={{ opacity: 0, x: -30 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.8 }}
className="space-y-6"
>
<h3 className="text-3xl font-bold text-white mb-4">My Journey</h3>
<p className="text-gray-300 leading-relaxed text-lg">
I&apos;m a student and software engineer based in Osnabrück, Germany.
My passion for technology started early, and I&apos;ve been building
applications ever since.
</p>
<p className="text-gray-300 leading-relaxed text-lg">
I specialize in full-stack development, with a focus on creating
modern, performant web applications. I&apos;m always learning new
technologies and improving my skills.
</p>
<p className="text-gray-300 leading-relaxed text-lg">
When I&apos;m not coding, I enjoy exploring new technologies, contributing
to open-source projects, and sharing knowledge with the developer community.
</p>
</motion.div>
<motion.div
initial={{ opacity: 0, x: 30 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.8 }}
className="space-y-6"
>
<h3 className="text-3xl font-bold text-white mb-4">What I Do</h3>
<div className="grid grid-cols-2 gap-4">
{values.map((value, index) => (
<motion.div
key={value.title}
initial={{ opacity: 0, scale: 0.9 }}
whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: index * 0.1 }}
whileHover={{ y: -5, scale: 1.02 }}
className="p-6 rounded-xl glass-card"
>
<div className="w-12 h-12 bg-gradient-to-br from-blue-500 to-purple-500 rounded-lg flex items-center justify-center mb-4">
<value.icon className="w-6 h-6 text-white" />
</div>
<h4 className="text-lg font-semibold text-white mb-2">{value.title}</h4>
<p className="text-sm text-gray-400 leading-relaxed">{value.description}</p>
</motion.div>
))}
</div> </div>
</motion.div> </div>
</div>
{/* Skills Section */} {/* Simplified Skills / Tech Stack */}
<motion.div <div className="grid grid-cols-1 gap-6">
initial={{ opacity: 0, y: 30 }} <h3 className="text-xl font-bold text-stone-900 mb-2">My Toolbox</h3>
whileInView={{ opacity: 1, y: 0 }} {techStack.map((stack, idx) => (
viewport={{ once: true }}
transition={{ duration: 0.8 }}
className="mb-16"
>
<h3 className="text-3xl font-bold text-white mb-8 text-center">Skills & Technologies</h3>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{skills.map((skill, index) => (
<motion.div <motion.div
key={skill.category} key={stack.category}
initial={{ opacity: 0, y: 30 }} initial={{ opacity: 0, x: 20 }}
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, x: 0 }}
transition={{ delay: idx * 0.1 }}
viewport={{ once: true }} viewport={{ once: true }}
transition={{ duration: 0.6, delay: index * 0.1 }} className="p-6 rounded-xl bg-stone-50 border border-stone-100 hover:border-stone-200 transition-colors"
whileHover={{ y: -8, scale: 1.02 }}
className="glass-card p-6 rounded-2xl"
> >
<div className={`w-14 h-14 bg-gradient-to-br ${skill.color} rounded-xl flex items-center justify-center mb-4`}> <div className="flex items-center gap-3 mb-4">
<skill.icon className="w-7 h-7 text-white" /> <div className="p-2 bg-white rounded-lg shadow-sm text-stone-700">
<stack.icon size={20} />
</div>
<h4 className="font-semibold text-stone-800">{stack.category}</h4>
</div> </div>
<h4 className="text-xl font-bold text-white mb-4">{skill.category}</h4> <div className="flex flex-wrap gap-2">
<div className="space-y-2"> {stack.items.map(item => (
{skill.technologies.map((tech) => ( <span key={item} className="px-3 py-1 bg-white rounded-md border border-stone-200 text-sm text-stone-600">
<div {item}
key={tech} </span>
className="px-3 py-1.5 bg-gray-800/50 rounded-lg text-sm text-gray-300 border border-gray-700/50"
>
{tech}
</div>
))} ))}
</div> </div>
</motion.div> </motion.div>
))} ))}
</div> </div>
</motion.div> </div>
</div> </div>
</section> </section>
); );
}; };
export default About; export default About;

View File

@@ -0,0 +1,160 @@
'use client';
import { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { Music, Code, Monitor, MessageSquare, Send, X } from 'lucide-react';
interface ActivityData {
activity: {
type: 'coding' | 'listening' | 'watching';
details: string;
timestamp: string;
} | null;
music: {
isPlaying: boolean;
track: string;
artist: string;
platform: 'spotify' | 'apple';
} | null;
watching: {
title: string;
platform: 'youtube' | 'netflix';
} | null;
}
export const ActivityFeed = () => {
const [data, setData] = useState<ActivityData | null>(null);
const [showChat, setShowChat] = useState(false);
const [chatMessage, setChatMessage] = useState('');
const [chatHistory, setChatHistory] = useState<{
role: 'user' | 'ai';
text: string;
}[]>([
{ role: 'ai', text: 'Hi! I am Dennis\'s AI assistant. Ask me anything about him!' }
]);
useEffect(() => {
const fetchData = async () => {
try {
const res = await fetch('/api/n8n/status');
if (res.ok) {
const json = await res.json();
setData(json);
}
} catch (e) {
console.error('Failed to fetch activity', e);
}
};
fetchData();
const interval = setInterval(fetchData, 30000); // Poll every 30s
return () => clearInterval(interval);
}, []);
const handleSendMessage = (e: React.FormEvent) => {
e.preventDefault();
if (!chatMessage.trim()) return;
const userMsg = chatMessage;
setChatHistory(prev => [...prev, { role: 'user', text: userMsg }]);
setChatMessage('');
// Mock AI response - would connect to n8n webhook
setTimeout(() => {
setChatHistory(prev => [...prev, { role: 'ai', text: `That's a great question about "${userMsg}"! I'll ask Dennis to add more info about that.` }]);
}, 1000);
};
if (!data) return null;
return (
<div className="fixed bottom-6 right-6 z-40 flex flex-col items-end gap-4 pointer-events-none">
{/* Chat Window */}
<AnimatePresence>
{showChat && (
<motion.div
initial={{ opacity: 0, scale: 0.9, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.9, y: 20 }}
className="pointer-events-auto bg-white/80 backdrop-blur-xl border border-white/60 shadow-2xl rounded-2xl w-80 overflow-hidden"
>
<div className="p-4 border-b border-white/50 flex justify-between items-center bg-white/40">
<span className="font-semibold text-stone-800 flex items-center gap-2">
<MessageSquare size={16} />
Ask me anything
</span>
<button onClick={() => setShowChat(false)} className="text-stone-500 hover:text-stone-800">
<X size={16} />
</button>
</div>
<div className="h-64 overflow-y-auto p-4 space-y-3 bg-white/20">
{chatHistory.map((msg, i) => (
<div key={i} className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`}>
<div className={`max-w-[85%] p-3 rounded-xl text-sm ${
msg.role === 'user'
? 'bg-stone-800 text-white rounded-tr-none'
: 'bg-white text-stone-800 shadow-sm rounded-tl-none'
}`}>
{msg.text}
</div>
</div>
))}
</div>
<form onSubmit={handleSendMessage} className="p-3 border-t border-white/50 bg-white/40 flex gap-2">
<input
type="text"
value={chatMessage}
onChange={(e) => setChatMessage(e.target.value)}
placeholder="Type a message..."
className="flex-1 bg-white/60 border border-white/60 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-stone-400"
/>
<button type="submit" className="p-2 bg-stone-800 text-white rounded-lg hover:bg-black transition-colors">
<Send size={16} />
</button>
</form>
</motion.div>
)}
</AnimatePresence>
{/* Activity Bubbles */}
<div className="flex flex-col items-end gap-2 pointer-events-auto">
{data.activity?.type === 'coding' && (
<motion.div
initial={{ x: 50, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
className="bg-white/80 backdrop-blur-md border border-white/60 shadow-lg rounded-full px-4 py-2 flex items-center gap-2 text-sm text-stone-700"
>
<span className="relative flex h-2 w-2">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span>
<span className="relative inline-flex rounded-full h-2 w-2 bg-green-500"></span>
</span>
<Code size={14} />
<span>Working on <strong>{data.activity.details}</strong></span>
</motion.div>
)}
{data.music?.isPlaying && (
<motion.div
initial={{ x: 50, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
transition={{ delay: 0.1 }}
className="bg-white/80 backdrop-blur-md border border-white/60 shadow-lg rounded-full px-4 py-2 flex items-center gap-2 text-sm text-stone-700"
>
<Music size={14} className="animate-pulse text-liquid-rose" />
<span>Listening to <strong>{data.music.track}</strong></span>
</motion.div>
)}
{/* Chat Toggle Button */}
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={() => setShowChat(!showChat)}
className="bg-stone-900 text-white rounded-full p-4 shadow-xl hover:bg-black transition-all"
>
<MessageSquare size={20} />
</motion.button>
</div>
</div>
);
};

View File

@@ -4,6 +4,7 @@ import { useState, useEffect } from 'react';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { Mail, MapPin, Send } from 'lucide-react'; import { Mail, MapPin, Send } from 'lucide-react';
import { useToast } from '@/components/Toast'; import { useToast } from '@/components/Toast';
import { LiquidHeading } from '@/components/LiquidHeading';
const Contact = () => { const Contact = () => {
const [mounted, setMounted] = useState(false); const [mounted, setMounted] = useState(false);
@@ -139,23 +140,19 @@ const Contact = () => {
} }
return ( return (
<section id="contact" className="py-20 px-4 relative"> <section id="contact" className="py-24 px-4 relative bg-cream">
<div className="max-w-7xl mx-auto"> <div className="max-w-7xl mx-auto">
{/* Section Header */} {/* Section Header */}
<motion.div <div className="text-center mb-16">
initial={{ opacity: 0, y: 30 }} <LiquidHeading
whileInView={{ opacity: 1, y: 0 }} text="Contact Me"
viewport={{ once: true }} level={2}
transition={{ duration: 0.8 }} className="text-4xl md:text-5xl font-bold mb-6 text-stone-800"
className="text-center mb-16" />
> <p className="text-xl text-stone-500 max-w-2xl mx-auto mt-4">
<h2 className="text-4xl md:text-5xl font-bold mb-6 gradient-text">
Contact Me
</h2>
<p className="text-xl text-gray-400 max-w-2xl mx-auto">
Interested in working together or have questions about my projects? Feel free to reach out! Interested in working together or have questions about my projects? Feel free to reach out!
</p> </p>
</motion.div> </div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-12">
{/* Contact Information */} {/* Contact Information */}
@@ -167,10 +164,10 @@ const Contact = () => {
className="space-y-8" className="space-y-8"
> >
<div> <div>
<h3 className="text-2xl font-bold text-white mb-6"> <h3 className="text-2xl font-bold text-stone-800 mb-6">
Get In Touch Get In Touch
</h3> </h3>
<p className="text-gray-400 leading-relaxed"> <p className="text-stone-600 leading-relaxed">
I&apos;m always available to discuss new opportunities, interesting projects, I&apos;m always available to discuss new opportunities, interesting projects,
or simply chat about technology and innovation. or simply chat about technology and innovation.
</p> </p>
@@ -187,14 +184,14 @@ const Contact = () => {
viewport={{ once: true }} viewport={{ once: true }}
transition={{ duration: 0.6, delay: index * 0.1 }} transition={{ duration: 0.6, delay: index * 0.1 }}
whileHover={{ x: 5 }} whileHover={{ x: 5 }}
className="flex items-center space-x-4 p-4 rounded-lg glass-card hover:bg-gray-800/30 transition-colors group" className="flex items-center space-x-4 p-4 rounded-2xl glass-card hover:bg-white/60 transition-colors group border-transparent hover:border-white/60"
> >
<div className="p-3 bg-blue-500/20 rounded-lg group-hover:bg-blue-500/30 transition-colors"> <div className="p-3 bg-white rounded-xl shadow-sm group-hover:shadow-md transition-all">
<info.icon className="w-6 h-6 text-blue-400" /> <info.icon className="w-6 h-6 text-stone-700" />
</div> </div>
<div> <div>
<h4 className="font-semibold text-white">{info.title}</h4> <h4 className="font-semibold text-stone-800">{info.title}</h4>
<p className="text-gray-400">{info.value}</p> <p className="text-stone-500">{info.value}</p>
</div> </div>
</motion.a> </motion.a>
))} ))}
@@ -208,15 +205,15 @@ const Contact = () => {
whileInView={{ opacity: 1, x: 0 }} whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
transition={{ duration: 0.8 }} transition={{ duration: 0.8 }}
className="glass-card p-8 rounded-2xl" className="glass-card p-8 rounded-3xl bg-white/40 border border-white/60"
> >
<h3 className="text-2xl font-bold text-white mb-6">Send Message</h3> <h3 className="text-2xl font-bold text-stone-800 mb-6">Send Message</h3>
<form onSubmit={handleSubmit} className="space-y-6"> <form onSubmit={handleSubmit} className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div> <div>
<label htmlFor="name" className="block text-sm font-medium text-gray-300 mb-2"> <label htmlFor="name" className="block text-sm font-medium text-stone-600 mb-2">
Name <span className="text-red-400">*</span> Name <span className="text-liquid-rose">*</span>
</label> </label>
<input <input
type="text" type="text"
@@ -226,23 +223,23 @@ const Contact = () => {
onChange={handleChange} onChange={handleChange}
onBlur={handleBlur} onBlur={handleBlur}
required required
className={`w-full px-4 py-3 bg-gray-800/50 backdrop-blur-sm border rounded-lg text-white placeholder-gray-500 focus:outline-none focus:ring-2 transition-all ${ className={`w-full px-4 py-3 bg-white/50 backdrop-blur-sm border rounded-xl text-stone-800 placeholder-stone-400 focus:outline-none focus:ring-2 transition-all ${
errors.name && touched.name errors.name && touched.name
? 'border-red-500 focus:ring-red-500' ? 'border-red-400 focus:ring-red-400'
: 'border-gray-700 focus:ring-blue-500 focus:border-transparent' : 'border-white/60 focus:ring-liquid-blue focus:border-transparent'
}`} }`}
placeholder="Your name" placeholder="Your name"
aria-invalid={errors.name && touched.name ? 'true' : 'false'} aria-invalid={errors.name && touched.name ? 'true' : 'false'}
aria-describedby={errors.name && touched.name ? 'name-error' : undefined} aria-describedby={errors.name && touched.name ? 'name-error' : undefined}
/> />
{errors.name && touched.name && ( {errors.name && touched.name && (
<p id="name-error" className="mt-1 text-sm text-red-400">{errors.name}</p> <p id="name-error" className="mt-1 text-sm text-red-500">{errors.name}</p>
)} )}
</div> </div>
<div> <div>
<label htmlFor="email" className="block text-sm font-medium text-gray-300 mb-2"> <label htmlFor="email" className="block text-sm font-medium text-stone-600 mb-2">
Email <span className="text-red-400">*</span> Email <span className="text-liquid-rose">*</span>
</label> </label>
<input <input
type="email" type="email"
@@ -252,24 +249,24 @@ const Contact = () => {
onChange={handleChange} onChange={handleChange}
onBlur={handleBlur} onBlur={handleBlur}
required required
className={`w-full px-4 py-3 bg-gray-800/50 backdrop-blur-sm border rounded-lg text-white placeholder-gray-500 focus:outline-none focus:ring-2 transition-all ${ className={`w-full px-4 py-3 bg-white/50 backdrop-blur-sm border rounded-xl text-stone-800 placeholder-stone-400 focus:outline-none focus:ring-2 transition-all ${
errors.email && touched.email errors.email && touched.email
? 'border-red-500 focus:ring-red-500' ? 'border-red-400 focus:ring-red-400'
: 'border-gray-700 focus:ring-blue-500 focus:border-transparent' : 'border-white/60 focus:ring-liquid-blue focus:border-transparent'
}`} }`}
placeholder="your@email.com" placeholder="your@email.com"
aria-invalid={errors.email && touched.email ? 'true' : 'false'} aria-invalid={errors.email && touched.email ? 'true' : 'false'}
aria-describedby={errors.email && touched.email ? 'email-error' : undefined} aria-describedby={errors.email && touched.email ? 'email-error' : undefined}
/> />
{errors.email && touched.email && ( {errors.email && touched.email && (
<p id="email-error" className="mt-1 text-sm text-red-400">{errors.email}</p> <p id="email-error" className="mt-1 text-sm text-red-500">{errors.email}</p>
)} )}
</div> </div>
</div> </div>
<div> <div>
<label htmlFor="subject" className="block text-sm font-medium text-gray-300 mb-2"> <label htmlFor="subject" className="block text-sm font-medium text-stone-600 mb-2">
Subject <span className="text-red-400">*</span> Subject <span className="text-liquid-rose">*</span>
</label> </label>
<input <input
type="text" type="text"
@@ -279,23 +276,23 @@ const Contact = () => {
onChange={handleChange} onChange={handleChange}
onBlur={handleBlur} onBlur={handleBlur}
required required
className={`w-full px-4 py-3 bg-gray-800/50 backdrop-blur-sm border rounded-lg text-white placeholder-gray-500 focus:outline-none focus:ring-2 transition-all ${ className={`w-full px-4 py-3 bg-white/50 backdrop-blur-sm border rounded-xl text-stone-800 placeholder-stone-400 focus:outline-none focus:ring-2 transition-all ${
errors.subject && touched.subject errors.subject && touched.subject
? 'border-red-500 focus:ring-red-500' ? 'border-red-400 focus:ring-red-400'
: 'border-gray-700 focus:ring-blue-500 focus:border-transparent' : 'border-white/60 focus:ring-liquid-blue focus:border-transparent'
}`} }`}
placeholder="What's this about?" placeholder="What's this about?"
aria-invalid={errors.subject && touched.subject ? 'true' : 'false'} aria-invalid={errors.subject && touched.subject ? 'true' : 'false'}
aria-describedby={errors.subject && touched.subject ? 'subject-error' : undefined} aria-describedby={errors.subject && touched.subject ? 'subject-error' : undefined}
/> />
{errors.subject && touched.subject && ( {errors.subject && touched.subject && (
<p id="subject-error" className="mt-1 text-sm text-red-400">{errors.subject}</p> <p id="subject-error" className="mt-1 text-sm text-red-500">{errors.subject}</p>
)} )}
</div> </div>
<div> <div>
<label htmlFor="message" className="block text-sm font-medium text-gray-300 mb-2"> <label htmlFor="message" className="block text-sm font-medium text-stone-600 mb-2">
Message <span className="text-red-400">*</span> Message <span className="text-liquid-rose">*</span>
</label> </label>
<textarea <textarea
id="message" id="message"
@@ -305,10 +302,10 @@ const Contact = () => {
onBlur={handleBlur} onBlur={handleBlur}
required required
rows={6} rows={6}
className={`w-full px-4 py-3 bg-gray-800/50 backdrop-blur-sm border rounded-lg text-white placeholder-gray-500 focus:outline-none focus:ring-2 transition-all resize-none ${ className={`w-full px-4 py-3 bg-white/50 backdrop-blur-sm border rounded-xl text-stone-800 placeholder-stone-400 focus:outline-none focus:ring-2 transition-all resize-none ${
errors.message && touched.message errors.message && touched.message
? 'border-red-500 focus:ring-red-500' ? 'border-red-400 focus:ring-red-400'
: 'border-gray-700 focus:ring-blue-500 focus:border-transparent' : 'border-white/60 focus:ring-liquid-blue focus:border-transparent'
}`} }`}
placeholder="Tell me more about your project or question..." placeholder="Tell me more about your project or question..."
aria-invalid={errors.message && touched.message ? 'true' : 'false'} aria-invalid={errors.message && touched.message ? 'true' : 'false'}
@@ -316,11 +313,11 @@ const Contact = () => {
/> />
<div className="flex justify-between items-center mt-1"> <div className="flex justify-between items-center mt-1">
{errors.message && touched.message ? ( {errors.message && touched.message ? (
<p id="message-error" className="text-sm text-red-400">{errors.message}</p> <p id="message-error" className="text-sm text-red-500">{errors.message}</p>
) : ( ) : (
<span></span> <span></span>
)} )}
<span className="text-xs text-gray-500"> <span className="text-xs text-stone-400">
{formData.message.length} characters {formData.message.length} characters
</span> </span>
</div> </div>
@@ -331,7 +328,7 @@ const Contact = () => {
disabled={isSubmitting} disabled={isSubmitting}
whileHover={!isSubmitting ? { scale: 1.02, y: -2 } : {}} whileHover={!isSubmitting ? { scale: 1.02, y: -2 } : {}}
whileTap={!isSubmitting ? { scale: 0.98 } : {}} whileTap={!isSubmitting ? { scale: 0.98 } : {}}
className="w-full btn-primary py-4 text-lg font-semibold disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center space-x-2 disabled:hover:scale-100 disabled:hover:translate-y-0" className="w-full py-4 text-lg font-semibold disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center space-x-2 disabled:hover:scale-100 disabled:hover:translate-y-0 bg-stone-900 text-white rounded-xl hover:bg-black transition-all shadow-lg"
> >
{isSubmitting ? ( {isSubmitting ? (
<> <>

View File

@@ -25,7 +25,7 @@ const Footer = () => {
} }
return ( return (
<footer className="relative py-12 px-4 bg-black/95 backdrop-blur-sm border-t border-gray-800/50"> <footer className="relative py-12 px-4 bg-white border-t border-stone-200">
<div className="max-w-7xl mx-auto"> <div className="max-w-7xl mx-auto">
<div className="flex flex-col md:flex-row justify-between items-center space-y-6 md:space-y-0"> <div className="flex flex-col md:flex-row justify-between items-center space-y-6 md:space-y-0">
{/* Brand */} {/* Brand */}
@@ -39,15 +39,15 @@ const Footer = () => {
<motion.div <motion.div
whileHover={{ rotate: 360, scale: 1.1 }} whileHover={{ rotate: 360, scale: 1.1 }}
transition={{ duration: 0.5 }} transition={{ duration: 0.5 }}
className="w-12 h-12 bg-gradient-to-r from-blue-500 to-purple-500 rounded-xl flex items-center justify-center shadow-lg" className="w-12 h-12 bg-gradient-to-br from-liquid-mint to-liquid-lavender rounded-xl flex items-center justify-center shadow-md"
> >
<Code className="w-6 h-6 text-white" /> <Code className="w-6 h-6 text-stone-800" />
</motion.div> </motion.div>
<div> <div>
<Link href="/" className="text-xl font-bold font-mono text-white hover:text-blue-400 transition-colors"> <Link href="/" className="text-xl font-bold font-mono text-stone-800 hover:text-liquid-blue transition-colors">
dk<span className="text-red-500">0</span> dk<span className="text-liquid-rose">0</span>
</Link> </Link>
<p className="text-xs text-gray-500">Software Engineer</p> <p className="text-xs text-stone-500">Software Engineer</p>
</div> </div>
</motion.div> </motion.div>
@@ -67,7 +67,7 @@ const Footer = () => {
rel="noopener noreferrer" rel="noopener noreferrer"
whileHover={{ scale: 1.15, y: -3 }} whileHover={{ scale: 1.15, y: -3 }}
whileTap={{ scale: 0.95 }} whileTap={{ scale: 0.95 }}
className="p-3 bg-gray-800/60 backdrop-blur-sm hover:bg-gray-700/60 rounded-xl text-gray-300 hover:text-white transition-all duration-200 border border-gray-700/50 hover:border-gray-600 shadow-lg" className="p-3 bg-stone-50 hover:bg-white rounded-xl text-stone-600 hover:text-stone-900 transition-all duration-200 border border-stone-200 hover:border-stone-300 shadow-sm"
aria-label={social.label} aria-label={social.label}
> >
<social.icon size={18} /> <social.icon size={18} />
@@ -81,14 +81,14 @@ const Footer = () => {
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.2 }} transition={{ duration: 0.6, delay: 0.2 }}
className="flex items-center space-x-2 text-gray-400 text-sm" className="flex items-center space-x-2 text-stone-400 text-sm"
> >
<span>© {currentYear}</span> <span>© {currentYear}</span>
<motion.div <motion.div
animate={{ scale: [1, 1.2, 1] }} animate={{ scale: [1, 1.2, 1] }}
transition={{ duration: 1.5, repeat: Infinity }} transition={{ duration: 1.5, repeat: Infinity }}
> >
<Heart size={14} className="text-red-500" /> <Heart size={14} className="text-liquid-rose fill-liquid-rose" />
</motion.div> </motion.div>
<span>Made in Germany</span> <span>Made in Germany</span>
</motion.div> </motion.div>
@@ -100,30 +100,30 @@ const Footer = () => {
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.3 }} transition={{ duration: 0.6, delay: 0.3 }}
className="mt-8 pt-6 border-t border-gray-800/50 flex flex-col md:flex-row justify-between items-center space-y-4 md:space-y-0" className="mt-8 pt-6 border-t border-stone-100 flex flex-col md:flex-row justify-between items-center space-y-4 md:space-y-0"
> >
<div className="flex space-x-6 text-sm"> <div className="flex space-x-6 text-sm">
<Link <Link
href="/legal-notice" href="/legal-notice"
className="text-gray-500 hover:text-gray-300 transition-colors duration-200" className="text-stone-500 hover:text-stone-800 transition-colors duration-200"
> >
Impressum Impressum
</Link> </Link>
<Link <Link
href="/privacy-policy" href="/privacy-policy"
className="text-gray-500 hover:text-gray-300 transition-colors duration-200" className="text-stone-500 hover:text-stone-800 transition-colors duration-200"
> >
Privacy Policy Privacy Policy
</Link> </Link>
</div> </div>
<div className="text-xs text-gray-500 flex items-center space-x-1"> <div className="text-xs text-stone-400 flex items-center space-x-1">
<span>Built with</span> <span>Built with</span>
<span className="text-blue-400 font-semibold">Next.js</span> <span className="text-stone-600 font-semibold">Next.js</span>
<span className="text-gray-600"></span> <span className="text-stone-300"></span>
<span className="text-blue-400 font-semibold">TypeScript</span> <span className="text-stone-600 font-semibold">TypeScript</span>
<span className="text-gray-600"></span> <span className="text-stone-300"></span>
<span className="text-blue-400 font-semibold">Tailwind CSS</span> <span className="text-stone-600 font-semibold">Tailwind CSS</span>
</div> </div>
</motion.div> </motion.div>
</div> </div>

View File

@@ -43,36 +43,29 @@ const Header = () => {
return ( return (
<> <>
<div className="particles">
{[...Array(20)].map((_, i) => (
<div
key={i}
className="particle"
style={{
left: `${(i * 5.5) % 100}%`,
animationDelay: `${(i * 0.8) % 20}s`,
animationDuration: `${20 + (i * 0.4) % 10}s`,
}}
/>
))}
</div>
<motion.header <motion.header
initial={{ y: -100 }} initial={{ y: -100, opacity: 0 }}
animate={{ y: 0 }} animate={{ y: 0, opacity: 1 }}
transition={{ duration: 0.8, ease: "easeOut" }} transition={{ duration: 0.8, ease: "easeOut" }}
className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${ className="fixed top-6 left-0 right-0 z-50 flex justify-center px-4 pointer-events-none"
scrolled ? 'glass' : 'bg-transparent'
}`}
> >
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className={`pointer-events-auto transition-all duration-500 ease-out ${
<div className="flex justify-between items-center h-16"> scrolled ? 'w-full max-w-5xl' : 'w-full max-w-7xl'
}`}>
<div className={`
backdrop-blur-xl transition-all duration-500
${scrolled
? 'bg-white/95 border border-stone-300 shadow-[0_8px_30px_rgba(0,0,0,0.12)] rounded-full px-6 py-3'
: 'bg-white/85 border border-stone-200 shadow-[0_4px_24px_rgba(0,0,0,0.08)] px-4 py-4 rounded-full'
}
flex justify-between items-center
`}>
<motion.div <motion.div
whileHover={{ scale: 1.05 }} whileHover={{ scale: 1.05 }}
className="flex items-center space-x-2" className="flex items-center space-x-2"
> >
<Link href="/" className="text-2xl font-bold font-mono text-white"> <Link href="/" className="text-2xl font-bold font-mono text-stone-800 tracking-tighter liquid-hover">
dk<span className="text-red-500">0</span> dk<span className="text-liquid-rose">0</span>
</Link> </Link>
</motion.div> </motion.div>
@@ -85,7 +78,7 @@ const Header = () => {
> >
<Link <Link
href={item.href} href={item.href}
className="text-gray-300 hover:text-white transition-colors duration-200 font-medium relative group px-2 py-1" className="text-stone-600 hover:text-stone-900 transition-colors duration-200 font-medium relative group px-2 py-1 liquid-hover"
onClick={(e) => { onClick={(e) => {
if (item.href.startsWith('#')) { if (item.href.startsWith('#')) {
e.preventDefault(); e.preventDefault();
@@ -97,24 +90,24 @@ const Header = () => {
}} }}
> >
{item.name} {item.name}
<span className="absolute -bottom-1 left-2 right-2 h-0.5 bg-gradient-to-r from-blue-500 to-purple-500 transform scale-x-0 group-hover:scale-x-100 transition-transform duration-300 origin-left"></span> <span className="absolute -bottom-1 left-0 right-0 h-0.5 bg-gradient-to-r from-liquid-mint to-liquid-lavender transform scale-x-0 group-hover:scale-x-100 transition-transform duration-300 origin-center rounded-full"></span>
</Link> </Link>
</motion.div> </motion.div>
))} ))}
</nav> </nav>
<div className="hidden md:flex items-center space-x-4"> <div className="hidden md:flex items-center space-x-3">
{socialLinks.map((social) => ( {socialLinks.map((social) => (
<motion.a <motion.a
key={social.label} key={social.label}
href={social.href} href={social.href}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
whileHover={{ scale: 1.1, y: -2 }} whileHover={{ scale: 1.1, rotate: 5 }}
whileTap={{ scale: 0.95 }} whileTap={{ scale: 0.95 }}
className="p-2 rounded-lg bg-gray-800/50 hover:bg-gray-700/50 transition-colors duration-200 text-gray-300 hover:text-white" className="p-2 rounded-full bg-white/40 hover:bg-white/80 border border-white/50 text-stone-600 hover:text-stone-900 transition-all shadow-sm liquid-hover"
> >
<social.icon size={20} /> <social.icon size={18} />
</motion.a> </motion.a>
))} ))}
</div> </div>
@@ -122,7 +115,7 @@ const Header = () => {
<motion.button <motion.button
whileTap={{ scale: 0.95 }} whileTap={{ scale: 0.95 }}
onClick={() => setIsOpen(!isOpen)} onClick={() => setIsOpen(!isOpen)}
className="md:hidden p-2 rounded-lg bg-gray-800/50 hover:bg-gray-700/50 transition-colors duration-200 text-gray-300 hover:text-white" className="md:hidden p-2 rounded-full bg-white/40 hover:bg-white/60 text-stone-800 transition-colors liquid-hover"
> >
{isOpen ? <X size={24} /> : <Menu size={24} />} {isOpen ? <X size={24} /> : <Menu size={24} />}
</motion.button> </motion.button>
@@ -137,17 +130,17 @@ const Header = () => {
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
exit={{ opacity: 0 }} exit={{ opacity: 0 }}
transition={{ duration: 0.2 }} transition={{ duration: 0.2 }}
className="fixed inset-0 bg-black/50 backdrop-blur-sm z-40 md:hidden" className="fixed inset-0 bg-stone-900/20 backdrop-blur-sm z-40 md:hidden pointer-events-auto"
onClick={() => setIsOpen(false)} onClick={() => setIsOpen(false)}
/> />
<motion.div <motion.div
initial={{ opacity: 0, y: -20 }} initial={{ opacity: 0, scale: 0.95, y: -20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }} exit={{ opacity: 0, scale: 0.95, y: -20 }}
transition={{ duration: 0.3 }} transition={{ duration: 0.3, type: "spring" }}
className="md:hidden glass border-t border-gray-800/50 z-50 relative" className="absolute top-24 left-4 right-4 bg-cream/95 backdrop-blur-xl border border-stone-200 shadow-xl rounded-3xl z-50 p-6 pointer-events-auto"
> >
<div className="px-4 py-6 space-y-2"> <div className="space-y-2">
{navItems.map((item, index) => ( {navItems.map((item, index) => (
<motion.div <motion.div
key={item.name} key={item.name}
@@ -170,16 +163,15 @@ const Header = () => {
}, 100); }, 100);
} }
}} }}
className="block text-gray-300 hover:text-white transition-all duration-200 font-medium py-3 px-4 rounded-lg hover:bg-gray-800/50 border-l-2 border-transparent hover:border-blue-500" className="block text-stone-600 hover:text-stone-900 hover:bg-white/50 transition-all font-medium py-3 px-4 rounded-xl"
> >
{item.name} {item.name}
</Link> </Link>
</motion.div> </motion.div>
))} ))}
<div className="pt-4 mt-4 border-t border-gray-700/50"> <div className="pt-6 mt-4 border-t border-stone-200">
<p className="text-xs text-gray-500 mb-3 px-4">Connect with me</p> <div className="flex justify-center space-x-4">
<div className="flex space-x-3 px-4">
{socialLinks.map((social, index) => ( {socialLinks.map((social, index) => (
<motion.a <motion.a
key={social.label} key={social.label}
@@ -189,9 +181,8 @@ const Header = () => {
initial={{ scale: 0, opacity: 0 }} initial={{ scale: 0, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }} animate={{ scale: 1, opacity: 1 }}
transition={{ delay: (navItems.length + index) * 0.05 }} transition={{ delay: (navItems.length + index) * 0.05 }}
whileHover={{ scale: 1.1, y: -2 }} whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.95 }} className="p-3 rounded-full bg-white/60 text-stone-600 shadow-sm"
className="p-3 rounded-xl bg-gray-800/50 hover:bg-gray-700/50 transition-all duration-200 text-gray-300 hover:text-white"
aria-label={social.label} aria-label={social.label}
> >
<social.icon size={20} /> <social.icon size={20} />

View File

@@ -4,6 +4,7 @@ import { useState, useEffect } from 'react';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { ArrowDown, Code, Zap, Rocket } from 'lucide-react'; import { ArrowDown, Code, Zap, Rocket } from 'lucide-react';
import Image from 'next/image'; import Image from 'next/image';
import { LiquidHeading } from '@/components/LiquidHeading';
const Hero = () => { const Hero = () => {
const [mounted, setMounted] = useState(false); const [mounted, setMounted] = useState(false);
@@ -23,153 +24,116 @@ const Hero = () => {
} }
return ( return (
<section className="relative min-h-screen flex items-center justify-center overflow-hidden pt-20 pb-8"> <section className="relative min-h-screen flex items-center justify-center overflow-hidden pt-20 pb-8 bg-transparent">
{/* Animated Background */}
<div className="absolute inset-0 animated-bg"></div>
{/* Floating Elements */} <div className="relative z-10 text-center px-4 max-w-5xl mx-auto">
<div className="absolute inset-0 overflow-hidden"> {/* Domain Badge */}
<motion.div
className="absolute top-20 left-20 w-32 h-32 bg-blue-500/10 rounded-full blur-xl"
initial={{ scale: 1, opacity: 0.3 }}
animate={{
scale: [1, 1.2, 1],
opacity: [0.3, 0.6, 0.3],
}}
transition={{
duration: 4,
repeat: Infinity,
ease: "easeInOut",
}}
/>
<motion.div
className="absolute top-40 right-32 w-24 h-24 bg-purple-500/10 rounded-full blur-xl"
initial={{ scale: 1.2, opacity: 0.6 }}
animate={{
scale: [1.2, 1, 1.2],
opacity: [0.6, 0.3, 0.6],
}}
transition={{
duration: 5,
repeat: Infinity,
ease: "easeInOut",
}}
/>
<motion.div
className="absolute bottom-32 left-1/3 w-40 h-40 bg-cyan-500/10 rounded-full blur-xl"
initial={{ scale: 1, opacity: 0.4 }}
animate={{
scale: [1, 1.3, 1],
opacity: [0.4, 0.7, 0.4],
}}
transition={{
duration: 6,
repeat: Infinity,
ease: "easeInOut",
}}
/>
</div>
<div className="relative z-10 text-center px-4 max-w-4xl mx-auto">
{/* Domain - über dem Profilbild */}
<motion.div <motion.div
initial={{ opacity: 0, y: 30 }} initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.5 }} transition={{ duration: 0.8, delay: 0.5 }}
className="mb-8" className="mb-8 inline-block"
> >
<div className="domain-text text-white/95 text-center"> <div className="px-6 py-2 rounded-full glass-panel text-stone-600 font-mono text-sm tracking-wider uppercase">
dk<span className="text-red-500">0</span>.dev dk<span className="text-liquid-rose font-bold">0</span>.dev
</div> </div>
</motion.div> </motion.div>
{/* Profile Image */} {/* Profile Image with Organic Blob Mask */}
<motion.div <motion.div
initial={{ opacity: 0, scale: 0.8, rotateY: -15 }} initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1, rotateY: 0 }} animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 1, delay: 0.7, ease: "easeOut" }} transition={{ duration: 1, delay: 0.7, ease: "easeOut" }}
className="mb-8 flex justify-center" className="mb-10 flex justify-center relative z-20"
> >
<div className="relative group"> <div className="relative w-64 h-64 md:w-80 md:h-80 flex items-center justify-center">
{/* Profile image container */} {/* Large Rotating Liquid Blobs behind image */}
<div className="relative bg-gray-900 rounded-full p-1"> <motion.div
<motion.div className="absolute w-[140%] h-[140%] bg-gradient-to-tr from-liquid-mint via-liquid-blue to-liquid-lavender opacity-60 blur-3xl -z-10"
whileHover={{ scale: 1.05, rotateY: 5 }} animate={{
transition={{ duration: 0.3 }} borderRadius: ["60% 40% 30% 70%/60% 30% 70% 40%", "30% 60% 70% 40%/50% 60% 30% 60%", "60% 40% 30% 70%/60% 30% 70% 40%"],
className="relative w-40 h-40 md:w-48 md:h-48 lg:w-56 lg:h-56 rounded-full overflow-hidden border-4 border-gray-800" rotate: [0, 180, 360],
> scale: [1, 1.1, 1]
}}
transition={{ duration: 20, repeat: Infinity, ease: "linear" }}
/>
<motion.div
className="absolute w-[120%] h-[120%] bg-gradient-to-bl from-liquid-rose via-purple-200 to-liquid-mint opacity-40 blur-2xl -z-10"
animate={{
borderRadius: ["40% 60% 70% 30%/40% 50% 60% 50%", "60% 30% 40% 70%/60% 40% 70% 30%", "40% 60% 70% 30%/40% 50% 60% 50%"],
rotate: [360, 180, 0],
scale: [1, 0.9, 1]
}}
transition={{ duration: 15, repeat: Infinity, ease: "linear" }}
/>
{/* The Image Container with Organic Border Radius - No hard border, just shadow and glass */}
<motion.div
className="absolute inset-0 overflow-hidden shadow-2xl bg-stone-100"
style={{ filter: "drop-shadow(0 20px 40px rgba(0,0,0,0.15))" }}
animate={{
borderRadius: ["60% 40% 30% 70%/60% 30% 70% 40%", "30% 60% 70% 40%/50% 60% 30% 60%", "60% 40% 30% 70%/60% 30% 70% 40%"]
}}
transition={{ duration: 8, repeat: Infinity, ease: "easeInOut" }}
>
<Image <Image
src="/images/me.jpg" src="/images/me.jpg"
alt="Dennis Konkol - Software Engineer" alt="Dennis Konkol"
fill fill
className="object-cover" className="object-cover scale-105 hover:scale-110 transition-transform duration-700"
priority priority
/> />
{/* Hover overlay effect */} {/* Glossy Overlay for Liquid Feel */}
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div> <div className="absolute inset-0 bg-gradient-to-tr from-white/30 via-transparent to-transparent opacity-50 pointer-events-none z-10" />
</motion.div>
</div> {/* Inner Border/Highlight */}
<div className="absolute inset-0 border-[3px] border-white/20 rounded-[inherit] pointer-events-none z-20" />
{/* Floating tech badges around the image */}
<motion.div
initial={{ opacity: 0, scale: 0 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.5, delay: 1.5 }}
className="absolute -top-3 -right-3 w-10 h-10 bg-blue-500 rounded-full flex items-center justify-center shadow-lg"
>
<Code className="w-5 h-5 text-white" />
</motion.div> </motion.div>
{/* Floating Badges */}
<motion.div <motion.div
initial={{ opacity: 0, scale: 0 }} initial={{ scale: 0 }}
animate={{ opacity: 1, scale: 1 }} animate={{ scale: 1 }}
transition={{ duration: 0.5, delay: 1.7 }} transition={{ delay: 1.5, type: "spring" }}
className="absolute -bottom-3 -left-3 w-10 h-10 bg-purple-500 rounded-full flex items-center justify-center shadow-lg" className="absolute -top-4 right-0 md:-right-4 p-3 bg-white/90 backdrop-blur-md shadow-lg rounded-full text-stone-700 z-30"
> >
<Zap className="w-5 h-5 text-white" /> <Code size={24} />
</motion.div> </motion.div>
<motion.div
<motion.div initial={{ scale: 0 }}
initial={{ opacity: 0, scale: 0 }} animate={{ scale: 1 }}
animate={{ opacity: 1, scale: 1 }} transition={{ delay: 1.7, type: "spring" }}
transition={{ duration: 0.5, delay: 1.9 }} className="absolute bottom-4 -left-4 md:-left-8 p-3 bg-white/90 backdrop-blur-md shadow-lg rounded-full text-stone-700 z-30"
className="absolute -top-3 -left-3 w-10 h-10 bg-cyan-500 rounded-full flex items-center justify-center shadow-lg"
> >
<Rocket className="w-5 h-5 text-white" /> <Zap size={24} />
</motion.div> </motion.div>
</div> </div>
</motion.div> </motion.div>
{/* Main Title */} {/* Main Title */}
<motion.h1 <div className="mb-6 flex flex-col items-center justify-center relative">
initial={{ opacity: 0, y: 30 }} <LiquidHeading
animate={{ opacity: 1, y: 0 }} text="Dennis Konkol"
transition={{ duration: 0.8, delay: 0.8 }} level={1}
className="text-5xl md:text-7xl font-bold mb-4" className="text-5xl md:text-8xl font-bold tracking-tighter text-stone-800"
> />
<span className="gradient-text">Dennis Konkol</span> <LiquidHeading
</motion.h1> text="Software Engineer"
level={2}
className="text-2xl md:text-4xl font-light tracking-wide text-stone-500 mt-2"
/>
</div>
{/* Subtitle */}
<motion.p
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 1.1 }}
className="text-xl md:text-2xl text-gray-300 mb-8 max-w-2xl mx-auto"
>
Student & Software Engineer based in Osnabrück, Germany
</motion.p>
{/* Description */} {/* Description */}
<motion.p <motion.p
initial={{ opacity: 0, y: 30 }} initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 1.2 }} transition={{ duration: 0.8, delay: 1.2 }}
className="text-lg text-gray-400 mb-12 max-w-3xl mx-auto leading-relaxed" className="text-lg md:text-xl text-stone-600 mb-12 max-w-2xl mx-auto leading-relaxed"
> >
Passionate about technology, coding, and solving real-world problems. I craft digital experiences with a focus on <span className="text-stone-900 font-semibold decoration-liquid-mint decoration-2 underline underline-offset-2">design</span>, <span className="text-stone-900 font-semibold decoration-liquid-lavender decoration-2 underline underline-offset-2">performance</span>, and <span className="text-stone-900 font-semibold decoration-liquid-rose decoration-2 underline underline-offset-2">user experience</span>.
I create innovative solutions that make a difference.
</motion.p> </motion.p>
{/* Features */} {/* Features */}
@@ -177,7 +141,7 @@ const Hero = () => {
initial={{ opacity: 0, y: 30 }} initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 1.4 }} transition={{ duration: 0.8, delay: 1.4 }}
className="flex flex-wrap justify-center gap-6 mb-12" className="flex flex-wrap justify-center gap-4 mb-12"
> >
{features.map((feature, index) => ( {features.map((feature, index) => (
<motion.div <motion.div
@@ -185,11 +149,11 @@ const Hero = () => {
initial={{ opacity: 0, scale: 0.8 }} initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }} animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.5, delay: 1.6 + index * 0.1 }} transition={{ duration: 0.5, delay: 1.6 + index * 0.1 }}
whileHover={{ scale: 1.05, y: -5 }} whileHover={{ scale: 1.05, y: -2 }}
className="flex items-center space-x-2 px-4 py-2 rounded-full glass-card" className="flex items-center space-x-2 px-5 py-2.5 rounded-full bg-white/60 border border-white/80 shadow-sm backdrop-blur-sm liquid-hover"
> >
<feature.icon className="w-5 h-5 text-blue-400" /> <feature.icon className="w-4 h-4 text-stone-700" />
<span className="text-gray-300 font-medium">{feature.text}</span> <span className="text-stone-700 font-medium text-sm">{feature.text}</span>
</motion.div> </motion.div>
))} ))}
</motion.div> </motion.div>
@@ -199,45 +163,27 @@ const Hero = () => {
initial={{ opacity: 0, y: 30 }} initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 1.8 }} transition={{ duration: 0.8, delay: 1.8 }}
className="flex flex-col sm:flex-row gap-4 justify-center items-center" className="flex flex-col sm:flex-row gap-5 justify-center items-center"
> >
<motion.a <motion.a
href="#projects" href="#projects"
whileHover={{ scale: 1.05, y: -2 }} whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }} whileTap={{ scale: 0.95 }}
className="btn-primary px-8 py-4 text-lg font-semibold inline-flex items-center space-x-2" className="px-8 py-4 bg-stone-900 text-cream rounded-full font-medium shadow-lg hover:shadow-xl hover:bg-black transition-all flex items-center gap-2 liquid-hover"
> >
<span>View My Work</span> <span>View My Work</span>
<ArrowDown className="w-5 h-5" /> <ArrowDown size={18} />
</motion.a> </motion.a>
<motion.a <motion.a
href="#contact" href="#contact"
whileHover={{ scale: 1.05, y: -2 }} whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }} whileTap={{ scale: 0.95 }}
className="btn-secondary px-8 py-4 text-lg font-semibold inline-flex items-center space-x-2" className="px-8 py-4 bg-white text-stone-900 border border-stone-200 rounded-full font-medium shadow-sm hover:shadow-md transition-all liquid-hover"
> >
<span>Contact Me</span> <span>Contact Me</span>
</motion.a> </motion.a>
</motion.div> </motion.div>
{/* Scroll Indicator */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 1, delay: 2 }}
className="mt-12 md:mt-16 text-center relative z-20"
>
<motion.a
href="#about"
animate={{ y: [0, 10, 0] }}
transition={{ duration: 2, repeat: Infinity, ease: "easeInOut" }}
className="inline-flex flex-col items-center text-white/90 bg-black/30 backdrop-blur-md px-6 py-3 rounded-full border border-white/20 shadow-lg hover:bg-black/50 hover:border-white/30 transition-all cursor-pointer group"
>
<span className="text-sm md:text-base mb-2 font-medium group-hover:text-white transition-colors">Scroll Down</span>
<ArrowDown className="w-5 h-5 md:w-6 md:h-6 group-hover:translate-y-1 transition-transform" />
</motion.a>
</motion.div>
</div> </div>
</section> </section>
); );

View File

@@ -2,8 +2,10 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { ExternalLink, Github, Calendar } from 'lucide-react'; import { ExternalLink, Github, Calendar, Layers, ArrowRight } from 'lucide-react';
import Link from 'next/link'; import Link from 'next/link';
import { LiquidHeading } from '@/components/LiquidHeading';
import Image from 'next/image';
interface Project { interface Project {
id: number; id: number;
@@ -16,188 +18,139 @@ interface Project {
date: string; date: string;
github?: string; github?: string;
live?: string; live?: string;
imageUrl?: string;
} }
const Projects = () => { const Projects = () => {
const [mounted, setMounted] = useState(false); const [mounted, setMounted] = useState(false);
const [projects, setProjects] = useState<Project[]>([]);
useEffect(() => { useEffect(() => {
setMounted(true); setMounted(true);
}, []);
const [projects, setProjects] = useState<Project[]>([]);
// Load projects from API
useEffect(() => {
const loadProjects = async () => { const loadProjects = async () => {
try { try {
const response = await fetch('/api/projects?featured=true&published=true&limit=6'); const response = await fetch('/api/projects?featured=true&published=true&limit=6');
if (response.ok) { if (response.ok) {
const data = await response.json(); const data = await response.json();
setProjects(data.projects || []); setProjects(data.projects || []);
} else {
console.error('Failed to fetch projects from API');
} }
} catch (error) { } catch (error) {
console.error('Error loading projects:', error); console.error('Error loading projects:', error);
} }
}; };
loadProjects(); loadProjects();
}, []); }, []);
if (!mounted) { if (!mounted) return null;
return null;
}
return ( return (
<section id="projects" className="py-20 px-4 relative"> <section id="projects" className="py-24 px-4 relative bg-stone-50/50">
<div className="max-w-7xl mx-auto"> <div className="max-w-7xl mx-auto">
<motion.div <div className="text-center mb-20">
initial={{ opacity: 0, y: 30 }} <LiquidHeading
whileInView={{ opacity: 1, y: 0 }} text="Selected Works"
viewport={{ once: true }} level={2}
transition={{ duration: 0.8 }} className="text-4xl md:text-6xl font-bold mb-6 text-stone-900"
className="text-center mb-16" />
> <p className="text-lg text-stone-500 max-w-2xl mx-auto mt-4 font-light">
<h2 className="text-4xl md:text-5xl font-bold mb-6 gradient-text"> A collection of projects I&apos;ve worked on, ranging from web applications to experiments.
Featured Projects
</h2>
<p className="text-xl text-gray-400 max-w-2xl mx-auto">
Here are some of my recent projects that showcase my skills and passion for creating innovative solutions.
</p> </p>
</motion.div> </div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{projects.map((project, index) => ( {projects.map((project, index) => (
<motion.div <motion.div
key={project.id} key={project.id}
initial={{ opacity: 0, y: 30 }} initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
transition={{ duration: 0.6, delay: index * 0.1 }} transition={{ duration: 0.5, delay: index * 0.1 }}
whileHover={{ y: -12, scale: 1.02 }} whileHover={{ y: -8 }}
className={`group relative overflow-hidden rounded-2xl glass-card card-hover border border-gray-800/50 hover:border-gray-700/50 transition-all ${ className="group relative flex flex-col bg-white rounded-2xl overflow-hidden shadow-sm hover:shadow-xl transition-all duration-300 border border-stone-100"
project.featured ? 'ring-2 ring-blue-500/30 shadow-lg shadow-blue-500/10' : ''
}`}
> >
<div className="relative h-48 overflow-hidden bg-gradient-to-br from-gray-900 to-gray-800"> {/* Project Cover / Header */}
<div className="absolute inset-0 bg-gradient-to-br from-blue-500/10 via-purple-500/10 to-pink-500/10" /> <div className="relative aspect-[4/3] overflow-hidden bg-stone-100">
<div className="absolute inset-0 flex flex-col items-center justify-center p-4"> {project.imageUrl ? (
<motion.div <Image
whileHover={{ scale: 1.1, rotate: 5 }} src={project.imageUrl}
className="w-20 h-20 bg-gradient-to-br from-blue-500 to-purple-500 rounded-2xl flex items-center justify-center mb-3 shadow-lg" alt={project.title}
> fill
<span className="text-2xl font-bold text-white"> className="object-cover transition-transform duration-500 group-hover:scale-105"
{project.title.split(' ').map(word => word[0]).join('').toUpperCase().slice(0, 2)} />
</span> ) : (
</motion.div> <div className="absolute inset-0 bg-gradient-to-br from-stone-100 to-stone-200 flex items-center justify-center p-8 group-hover:from-stone-50 group-hover:to-stone-100 transition-colors">
<span className="text-sm font-semibold text-gray-300 text-center leading-tight px-2"> <div className="w-full h-full border-2 border-dashed border-stone-300 rounded-xl flex items-center justify-center">
{project.title} <Layers className="text-stone-300 w-12 h-12" />
</span> </div>
</div> <div className="absolute inset-0 bg-gradient-to-tr from-liquid-mint/10 via-transparent to-liquid-rose/10 opacity-0 group-hover:opacity-100 transition-opacity duration-500" />
</div>
{project.featured && (
<motion.div
initial={{ scale: 0 }}
animate={{ scale: 1 }}
className="absolute top-4 right-4 px-3 py-1 bg-gradient-to-r from-blue-500 to-purple-500 text-white text-xs font-semibold rounded-full shadow-lg"
>
Featured
</motion.div>
)} )}
<div className="absolute inset-0 bg-gradient-to-t from-black/90 via-black/50 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-end justify-center pb-4 space-x-3"> {/* Overlay Links */}
{project.github && project.github.trim() !== '' && project.github !== '#' && ( <div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-center justify-center gap-4 backdrop-blur-[2px]">
<motion.a {project.github && (
href={project.github} <a href={project.github} target="_blank" rel="noopener noreferrer" className="p-3 bg-white rounded-full text-stone-900 hover:scale-110 transition-transform" aria-label="GitHub">
target="_blank" <Github size={20} />
rel="noopener noreferrer" </a>
whileHover={{ scale: 1.15, y: -2 }} )}
whileTap={{ scale: 0.95 }} {project.live && (
className="p-3 bg-gray-800/90 backdrop-blur-sm rounded-xl text-white hover:bg-gray-700/90 transition-all shadow-lg border border-gray-700/50" <a href={project.live} target="_blank" rel="noopener noreferrer" className="p-3 bg-white rounded-full text-stone-900 hover:scale-110 transition-transform" aria-label="Live Demo">
aria-label="View on GitHub" <ExternalLink size={20} />
> </a>
<Github size={20} /> )}
</motion.a>
)}
{project.live && project.live.trim() !== '' && project.live !== '#' && (
<motion.a
href={project.live}
target="_blank"
rel="noopener noreferrer"
whileHover={{ scale: 1.15, y: -2 }}
whileTap={{ scale: 0.95 }}
className="p-3 bg-gradient-to-r from-blue-600 to-purple-600 rounded-xl text-white hover:from-blue-500 hover:to-purple-500 transition-all shadow-lg"
aria-label="View live site"
>
<ExternalLink size={20} />
</motion.a>
)}
</div> </div>
</div> </div>
<div className="p-6"> {/* Content */}
<div className="flex items-start justify-between mb-3"> <div className="flex flex-col flex-1 p-6">
<h3 className="text-xl font-bold text-white group-hover:text-blue-400 transition-colors flex-1 pr-2"> <div className="flex justify-between items-start mb-3">
<h3 className="text-xl font-bold text-stone-900 group-hover:text-stone-600 transition-colors">
{project.title} {project.title}
</h3> </h3>
<div className="flex items-center space-x-1.5 text-gray-400 flex-shrink-0"> <span className="text-xs font-mono text-stone-400 bg-stone-100 px-2 py-1 rounded">
<Calendar size={14} /> {new Date(project.date).getFullYear()}
<span className="text-xs">{new Date(project.date).getFullYear()}</span> </span>
</div>
</div> </div>
<p className="text-gray-300 mb-4 leading-relaxed line-clamp-3"> <p className="text-stone-600 text-sm leading-relaxed mb-6 line-clamp-3 flex-1">
{project.description} {project.description}
</p> </p>
<div className="flex flex-wrap gap-2 mb-5"> <div className="space-y-4 mt-auto">
{project.tags.slice(0, 4).map((tag) => ( <div className="flex flex-wrap gap-2">
<span {project.tags.slice(0, 3).map(tag => (
key={tag} <span key={tag} className="text-xs px-2.5 py-1 bg-stone-50 border border-stone-100 rounded-md text-stone-500 font-medium">
className="px-3 py-1 bg-gray-800/60 backdrop-blur-sm text-gray-300 text-xs rounded-lg border border-gray-700/50 hover:border-gray-600 transition-colors" {tag}
> </span>
{tag} ))}
</span> {project.tags.length > 3 && (
))} <span className="text-xs px-2 py-1 text-stone-400">+ {project.tags.length - 3}</span>
{project.tags.length > 4 && ( )}
<span className="px-3 py-1 bg-gray-800/60 backdrop-blur-sm text-gray-400 text-xs rounded-lg border border-gray-700/50"> </div>
+{project.tags.length - 4}
</span> <Link
)} href={`/projects/${project.title.toLowerCase().replace(/\s+/g, '-')}`}
className="inline-flex items-center text-sm font-semibold text-stone-900 hover:gap-2 transition-all group/link"
>
Read more <ArrowRight size={16} className="ml-1 transition-transform group-hover/link:translate-x-1" />
</Link>
</div> </div>
<Link
href={`/projects/${project.title.toLowerCase().replace(/[^a-z0-9]+/g, '-')}`}
className="inline-flex items-center space-x-2 text-blue-400 hover:text-blue-300 transition-all font-semibold group/link"
>
<span>View Details</span>
<ExternalLink size={16} className="group-hover/link:translate-x-1 transition-transform" />
</Link>
</div> </div>
</motion.div> </motion.div>
))} ))}
</div> </div>
<motion.div <div className="mt-16 text-center">
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.8, delay: 0.4 }}
className="text-center mt-12"
>
<Link <Link
href="/projects" href="/projects"
className="btn-primary px-8 py-4 text-lg font-semibold inline-flex items-center space-x-2" className="inline-flex items-center gap-2 px-6 py-3 bg-white border border-stone-200 rounded-full text-stone-600 font-medium hover:bg-stone-50 hover:border-stone-300 transition-all shadow-sm"
> >
<span>View All Projects</span> Archive <ArrowRight size={16} />
<ExternalLink size={20} />
</Link> </Link>
</motion.div> </div>
</div> </div>
</section> </section>
); );
}; };
export default Projects; export default Projects;

View File

@@ -4,692 +4,150 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
/* Monaco Font for Domain */
@font-face {
font-family: 'Monaco';
src: url('https://fonts.gstatic.com/s/monaco/v1/Monaco-Regular.woff2') format('woff2');
font-weight: normal;
font-style: normal;
font-display: swap;
}
:root { :root {
--background: #0a0a0a; /* Organic Modern Palette */
--foreground: #fafafa; --background: #FDFCF8; /* Cream */
--card: #0f0f0f; --foreground: #292524; /* Warm Grey */
--card-foreground: #fafafa; --card: rgba(255, 255, 255, 0.6);
--popover: #0f0f0f; --card-foreground: #292524;
--popover-foreground: #fafafa; --popover: #FFFFFF;
--primary: #3b82f6; --popover-foreground: #292524;
--primary-foreground: #f8fafc; --primary: #292524;
--secondary: #1e293b; --primary-foreground: #FDFCF8;
--secondary-foreground: #f1f5f9; --secondary: #E7E5E4;
--muted: #1e293b; --secondary-foreground: #292524;
--muted-foreground: #64748b; --muted: #F5F5F4;
--accent: #1e293b; --muted-foreground: #78716C;
--accent-foreground: #f1f5f9; --accent: #F3F1E7; /* Sand */
--destructive: #ef4444; --accent-foreground: #292524;
--destructive-foreground: #f8fafc; --destructive: #EF4444;
--border: #1e293b; --destructive-foreground: #FDFCF8;
--input: #1e293b; --border: #E7E5E4;
--ring: #3b82f6; --input: #E7E5E4;
--radius: 0.5rem; --ring: #A7F3D0; /* Mint ring */
} --radius: 1rem;
* {
border-color: hsl(var(--border));
}
html {
scroll-behavior: smooth;
scroll-padding-top: 80px;
} }
body { body {
background-color: hsl(var(--background)); background-color: var(--background);
color: hsl(var(--foreground)); color: var(--foreground);
font-family: 'Inter', sans-serif; font-family: 'Inter', sans-serif;
margin: 0; margin: 0;
padding: 0; padding: 0;
position: relative;
min-height: 100vh;
overflow-x: hidden; overflow-x: hidden;
} }
/* Custom Scrollbar */ /* Custom Selection */
::-webkit-scrollbar { ::selection {
width: 8px; background: #A7F3D0; /* Mint */
color: #292524;
} }
::-webkit-scrollbar-track { /* Smooth Scrolling */
background: hsl(var(--background)); html {
scroll-behavior: smooth;
} }
::-webkit-scrollbar-thumb { /* Liquid Glass Effects */
background: hsl(var(--muted)); .glass-panel {
border-radius: 4px; background: rgba(255, 255, 255, 0.3);
} backdrop-filter: blur(12px) saturate(120%);
-webkit-backdrop-filter: blur(12px) saturate(120%);
::-webkit-scrollbar-thumb:hover { border: 1px solid rgba(255, 255, 255, 0.6);
background: hsl(var(--muted-foreground)); box-shadow: 0 8px 32px rgba(0, 0, 0, 0.05);
}
/* Glassmorphism Effects */
.glass {
background: rgba(15, 15, 15, 0.85);
backdrop-filter: blur(20px) saturate(180%);
-webkit-backdrop-filter: blur(20px) saturate(180%);
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
} }
.glass-card { .glass-card {
background: rgba(15, 15, 15, 0.7); background: rgba(255, 255, 255, 0.65);
backdrop-filter: blur(16px) saturate(180%); backdrop-filter: blur(24px) saturate(180%);
-webkit-backdrop-filter: blur(16px) saturate(180%); -webkit-backdrop-filter: blur(24px) saturate(180%);
border: 1px solid rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.8);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2); box-shadow:
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); 0 4px 6px -1px rgba(0, 0, 0, 0.02),
0 2px 4px -1px rgba(0, 0, 0, 0.02),
inset 0 0 20px rgba(255, 255, 255, 0.5);
transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
} }
.glass-card:hover { .glass-card:hover {
background: rgba(15, 15, 15, 0.8); background: rgba(255, 255, 255, 0.75);
border-color: rgba(255, 255, 255, 0.15); box-shadow:
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3); 0 20px 25px -5px rgba(0, 0, 0, 0.05),
0 10px 10px -5px rgba(0, 0, 0, 0.01),
inset 0 0 20px rgba(255, 255, 255, 0.8);
transform: translateY(-4px) scale(1.005);
border-color: #ffffff;
} }
/* Admin Panel Specific Glassmorphism */ /* Typography & Headings */
.admin-glass { h1, h2, h3, h4, h5, h6 {
background: rgba(255, 255, 255, 0.05) !important; letter-spacing: -0.02em;
backdrop-filter: blur(20px) !important;
border: 1px solid rgba(255, 255, 255, 0.15) !important;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4) !important;
} }
.admin-glass-card { /* Utility for the liquid melt effect container */
background: rgba(255, 255, 255, 0.08) !important; .liquid-container {
backdrop-filter: blur(16px) !important; filter: url('#goo');
border: 1px solid rgba(255, 255, 255, 0.2) !important;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3) !important;
} }
.admin-glass-light { /* Hide scrollbar but keep functionality */
background: rgba(255, 255, 255, 0.12) !important; ::-webkit-scrollbar {
backdrop-filter: blur(12px) !important; width: 8px;
border: 1px solid rgba(255, 255, 255, 0.25) !important; }
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2) !important; ::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: #D6D3D1;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #A8A29E;
} }
/* Admin Hover States */ /* Animations */
.admin-hover:hover {
background: rgba(255, 255, 255, 0.15) !important;
border: 1px solid rgba(255, 255, 255, 0.3) !important;
transform: scale(1.02) !important;
transition: all 0.2s ease !important;
}
/* Admin Gradient Background */
.admin-gradient {
background:
radial-gradient(circle at 20% 80%, rgba(59, 130, 246, 0.15) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(139, 92, 246, 0.15) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(236, 72, 153, 0.08) 0%, transparent 50%),
linear-gradient(-45deg, #0a0a0a, #111111, #0d0d0d, #151515);
background-size: 400% 400%, 400% 400%, 400% 400%, 400% 400%;
animation: gradientShift 25s ease infinite;
min-height: 100vh;
}
/* Admin Glass Header */
.admin-glass-header {
background: rgba(255, 255, 255, 0.08) !important;
backdrop-filter: blur(20px) !important;
border-bottom: 1px solid rgba(255, 255, 255, 0.15) !important;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3) !important;
}
/* Editor-specific styles */
.editor-content-editable:empty:before {
content: attr(data-placeholder);
color: #9ca3af;
pointer-events: none;
font-style: italic;
opacity: 0.7;
}
.editor-content-editable:focus:before {
content: none;
}
.editor-content-editable:empty {
min-height: 400px;
display: flex;
align-items: flex-start;
padding-top: 1.5rem;
}
.editor-content-editable:not(:empty) {
min-height: 400px;
}
/* Enhanced form styling */
.form-input-enhanced {
background: rgba(17, 24, 39, 0.8) !important;
border: 1px solid rgba(75, 85, 99, 0.5) !important;
color: #ffffff !important;
transition: all 0.3s ease !important;
backdrop-filter: blur(10px) !important;
}
.form-input-enhanced:focus {
border-color: #3b82f6 !important;
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2), 0 0 20px rgba(59, 130, 246, 0.1) !important;
background: rgba(17, 24, 39, 0.9) !important;
transform: translateY(-1px) !important;
}
.form-input-enhanced::placeholder {
color: #9ca3af !important;
}
/* Select styling */
select.form-input-enhanced {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e");
background-position: right 0.5rem center;
background-repeat: no-repeat;
background-size: 1.5em 1.5em;
padding-right: 2.5rem;
appearance: none;
cursor: pointer;
}
select.form-input-enhanced:focus {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%233b82f6' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e");
}
/* Custom dropdown styling */
.custom-select {
position: relative;
display: inline-block;
width: 100%;
}
.custom-select select {
width: 100%;
padding: 0.75rem 2.5rem 0.75rem 0.75rem;
background: rgba(17, 24, 39, 0.8);
border: 1px solid rgba(75, 85, 99, 0.5);
border-radius: 0.5rem;
color: #ffffff;
font-size: 0.875rem;
cursor: pointer;
transition: all 0.3s ease;
backdrop-filter: blur(10px);
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e");
background-position: right 0.75rem center;
background-repeat: no-repeat;
background-size: 1.25em 1.25em;
}
.custom-select select:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2), 0 0 20px rgba(59, 130, 246, 0.1);
background: rgba(17, 24, 39, 0.9);
transform: translateY(-1px);
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%233b82f6' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e");
}
/* Ensure no default browser arrows show */
.custom-select select::-ms-expand {
display: none;
}
.custom-select select::-webkit-appearance {
-webkit-appearance: none;
}
/* Gradient Text */
.gradient-text {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* Domain Text with Monaco Font */
.domain-text {
font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
font-size: 2.5rem;
font-weight: bold;
letter-spacing: 0.1em;
position: relative;
}
@media (min-width: 768px) {
.domain-text {
font-size: 3.5rem;
}
}
@media (min-width: 1024px) {
.domain-text {
font-size: 4rem;
}
}
.gradient-text-blue {
background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* Animated Background */
.animated-bg {
background:
radial-gradient(circle at 20% 80%, rgba(59, 130, 246, 0.08) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(139, 92, 246, 0.08) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(236, 72, 153, 0.04) 0%, transparent 50%),
linear-gradient(-45deg, #0a0a0a, #111111, #0d0d0d, #151515);
background-size: 400% 400%, 400% 400%, 400% 400%, 400% 400%;
animation: gradientShift 25s ease infinite;
}
/* Film Grain / TV Noise Effect */
.animated-bg::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image:
radial-gradient(circle at 2px 2px, rgba(255,255,255,0.08) 2px, transparent 0),
radial-gradient(circle at 4px 4px, rgba(0,0,0,0.04) 2px, transparent 0),
radial-gradient(circle at 6px 6px, rgba(255,255,255,0.06) 2px, transparent 0),
radial-gradient(circle at 8px 8px, rgba(0,0,0,0.03) 2px, transparent 0);
background-size: 4px 4px, 6px 6px, 8px 8px, 10px 10px;
pointer-events: none;
opacity: 0.7;
}
@keyframes filmGrain {
0%, 100% {
background-position: 0px 0px, 0px 0px, 0px 0px;
}
10% {
background-position: -1px -1px, 1px 1px, -1px 1px;
}
20% {
background-position: 1px -1px, -1px 1px, 1px -1px;
}
30% {
background-position: -1px 1px, 1px -1px, -1px -1px;
}
40% {
background-position: 1px 1px, -1px -1px, 1px 1px;
}
50% {
background-position: -1px -1px, 1px 1px, -1px 1px;
}
60% {
background-position: 1px -1px, -1px 1px, 1px -1px;
}
70% {
background-position: -1px 1px, 1px -1px, -1px -1px;
}
80% {
background-position: 1px 1px, -1px -1px, 1px 1px;
}
90% {
background-position: -1px -1px, 1px 1px, -1px 1px;
}
}
@keyframes gradientShift {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
/* Floating Animation */
@keyframes float { @keyframes float {
0%, 100% { transform: translateY(0px); } 0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-20px); } 50% { transform: translateY(-10px); }
} }
.animate-float { .animate-float {
animation: float 6s ease-in-out infinite; animation: float 5s ease-in-out infinite;
} }
/* Glow Effects */ @keyframes liquid-pulse {
.glow { 0% { transform: scale(1); }
box-shadow: 0 0 20px rgba(59, 130, 246, 0.3); 50% { transform: scale(1.05); }
100% { transform: scale(1); }
} }
.glow-hover:hover { /* Liquid Blobs Background */
box-shadow: 0 0 30px rgba(59, 130, 246, 0.5); .liquid-bg-blob {
transition: box-shadow 0.3s ease;
}
/* Particle Background */
.particles {
position: absolute; position: absolute;
top: 0; filter: blur(80px);
left: 0; opacity: 0.6;
width: 100%;
height: 100%;
overflow: hidden;
z-index: -1; z-index: -1;
animation: float 10s ease-in-out infinite;
} }
.particle { /* Markdown Specifics for Blog/Projects */
position: absolute;
width: 2px;
height: 2px;
background: rgba(59, 130, 246, 0.5);
border-radius: 50%;
animation: particleFloat 20s infinite linear;
}
@keyframes particleFloat {
0% {
transform: translateY(100vh) rotate(0deg);
opacity: 0;
}
10% {
opacity: 1;
}
90% {
opacity: 1;
}
100% {
transform: translateY(-100vh) rotate(360deg);
opacity: 0;
}
}
/* Markdown Styles */
.markdown {
color: #ffffff !important;
line-height: 1.7;
}
.markdown h1 { .markdown h1 {
font-size: 2.5rem; @apply text-4xl font-bold mb-6 text-stone-800 tracking-tight;
font-weight: 700;
margin-top: 2rem;
margin-bottom: 1rem;
color: #ffffff !important;
background: linear-gradient(135deg, #3b82f6, #1d4ed8);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
} }
.markdown h2 { .markdown h2 {
font-size: 2rem; @apply text-2xl font-semibold mt-8 mb-4 text-stone-800 tracking-tight;
font-weight: 600;
margin-top: 1.5rem;
margin-bottom: 1rem;
color: #ffffff !important;
} }
.markdown h3 {
font-size: 1.5rem;
font-weight: 600;
margin-top: 1.25rem;
margin-bottom: 0.75rem;
color: #ffffff !important;
}
.markdown p { .markdown p {
margin-top: 0.75rem; @apply mb-4 leading-relaxed text-stone-600;
margin-bottom: 0.75rem;
line-height: 1.7;
color: #e5e7eb !important;
} }
.markdown img {
max-width: 100%;
height: auto;
margin: 1.5rem 0;
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
transition: transform 0.3s ease;
}
.markdown img:hover {
transform: scale(1.02);
}
.markdown ul, .markdown ol {
margin: 1rem 0;
padding-left: 2rem;
}
.markdown li {
margin: 0.5rem 0;
color: #e5e7eb !important;
}
.markdown blockquote {
border-left: 4px solid #3b82f6;
background: rgba(59, 130, 246, 0.1);
padding: 1rem 1.5rem;
margin: 1.5rem 0;
border-radius: 8px;
font-style: italic;
color: #e5e7eb !important;
}
.markdown code {
background: rgba(59, 130, 246, 0.1);
color: #3b82f6 !important;
padding: 0.2rem 0.4rem;
border-radius: 4px;
font-size: 0.9em;
}
.markdown pre {
background: #0f0f0f;
border: 1px solid #1e293b;
border-radius: 8px;
padding: 1rem;
overflow-x: auto;
margin: 1.5rem 0;
}
.markdown pre code {
background: none;
color: #ffffff !important;
padding: 0;
}
.markdown a { .markdown a {
color: #3b82f6 !important; @apply text-stone-800 underline decoration-liquid-mint decoration-2 underline-offset-2 hover:text-black transition-colors;
text-decoration: underline;
transition: color 0.2s ease;
} }
.markdown ul {
.markdown a:hover { @apply list-disc list-inside mb-4 space-y-2 text-stone-600;
color: #1d4ed8 !important;
} }
.markdown code {
.markdown strong { @apply bg-stone-100 px-1.5 py-0.5 rounded text-sm text-stone-800 font-mono;
color: #ffffff !important;
font-weight: 600;
}
.markdown em {
color: #e5e7eb !important;
font-style: italic;
}
/* Button Styles */
.btn-primary {
background: linear-gradient(135deg, #3b82f6, #1d4ed8);
color: white;
padding: 0.75rem 1.5rem;
border-radius: 12px;
font-weight: 600;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
border: none;
cursor: pointer;
position: relative;
overflow: hidden;
box-shadow: 0 4px 14px rgba(59, 130, 246, 0.3);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 8px 30px rgba(59, 130, 246, 0.5);
background: linear-gradient(135deg, #2563eb, #1e40af);
}
.btn-primary:active {
transform: translateY(0);
}
.btn-primary::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s;
}
.btn-primary:hover::before {
left: 100%;
}
.btn-secondary {
background: transparent;
color: #e5e7eb;
padding: 0.75rem 1.5rem;
border-radius: 12px;
font-weight: 600;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
border: 2px solid rgba(75, 85, 99, 0.5);
cursor: pointer;
position: relative;
overflow: hidden;
}
.btn-secondary:hover {
border-color: rgba(75, 85, 99, 0.8);
background: rgba(31, 41, 55, 0.5);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
/* Card Hover Effects */
.card-hover {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
cursor: pointer;
}
.card-hover:hover {
transform: translateY(-8px) scale(1.02);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4);
}
/* Line clamp utility */
.line-clamp-3 {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* Loading Animation */
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.animate-pulse {
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
/* Fade In Animation */
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.fade-in-up {
animation: fadeInUp 0.6s ease-out;
}
/* Focus visible improvements */
*:focus-visible {
outline: 2px solid #3b82f6;
outline-offset: 2px;
border-radius: 4px;
}
/* Selection styling */
::selection {
background-color: rgba(59, 130, 246, 0.3);
color: #ffffff;
}
::-moz-selection {
background-color: rgba(59, 130, 246, 0.3);
color: #ffffff;
}
/* Improved scrollbar for webkit */
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track {
background: hsl(var(--background));
}
::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, #3b82f6, #1d4ed8);
border-radius: 5px;
border: 2px solid hsl(var(--background));
}
::-webkit-scrollbar-thumb:hover {
background: linear-gradient(180deg, #2563eb, #1e40af);
}
/* Responsive Design */
@media (max-width: 768px) {
.markdown h1 {
font-size: 2rem;
}
.markdown h2 {
font-size: 1.75rem;
}
.markdown h3 {
font-size: 1.25rem;
}
.domain-text {
font-size: 2rem;
}
} }
.markdown pre {
@apply bg-stone-900 text-stone-50 p-4 rounded-xl overflow-x-auto mb-6;
}

View File

@@ -5,6 +5,8 @@ import React from "react";
import { ToastProvider } from "@/components/Toast"; import { ToastProvider } from "@/components/Toast";
import { AnalyticsProvider } from "@/components/AnalyticsProvider"; import { AnalyticsProvider } from "@/components/AnalyticsProvider";
import { PerformanceDashboard } from "@/components/PerformanceDashboard"; import { PerformanceDashboard } from "@/components/PerformanceDashboard";
import { GooFilter } from "@/components/GooFilter";
import { BackgroundBlobs } from "@/components/BackgroundBlobs";
const inter = Inter({ const inter = Inter({
variable: "--font-inter", variable: "--font-inter",
@@ -26,7 +28,11 @@ export default function RootLayout({
<body className={inter.variable}> <body className={inter.variable}>
<AnalyticsProvider> <AnalyticsProvider>
<ToastProvider> <ToastProvider>
{children} <GooFilter />
<BackgroundBlobs />
<div className="relative z-10">
{children}
</div>
<PerformanceDashboard /> <PerformanceDashboard />
</ToastProvider> </ToastProvider>
</AnalyticsProvider> </AnalyticsProvider>

View File

@@ -7,6 +7,7 @@ import Projects from "./components/Projects";
import Contact from "./components/Contact"; import Contact from "./components/Contact";
import Footer from "./components/Footer"; import Footer from "./components/Footer";
import Script from "next/script"; import Script from "next/script";
import { ActivityFeed } from "./components/ActivityFeed";
export default function Home() { export default function Home() {
return ( return (
@@ -33,14 +34,13 @@ export default function Home() {
}), }),
}} }}
/> />
<ActivityFeed />
<Header /> <Header />
<main className="relative"> <main className="relative">
<Hero /> <Hero />
<div className="bg-gradient-to-b from-gray-900 via-black to-black"> <About />
<About /> <Projects />
<Projects /> <Contact />
<Contact />
</div>
</main> </main>
<Footer /> <Footer />
</div> </div>

View File

@@ -0,0 +1,77 @@
'use client';
import { motion, useMotionValue, useTransform, useSpring } from 'framer-motion';
import { useEffect, useState } from 'react';
export const BackgroundBlobs = () => {
const mouseX = useMotionValue(0);
const mouseY = useMotionValue(0);
const springConfig = { damping: 50, stiffness: 200 };
const springX = useSpring(mouseX, springConfig);
const springY = useSpring(mouseY, springConfig);
// Parallax offsets
const x1 = useTransform(springX, (value) => value / 20);
const y1 = useTransform(springY, (value) => value / 20);
const x2 = useTransform(springX, (value) => value / -15);
const y2 = useTransform(springY, (value) => value / -15);
const x3 = useTransform(springX, (value) => value / 10);
const y3 = useTransform(springY, (value) => value / 10);
useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
// Center the coordinate system
const { innerWidth, innerHeight } = window;
const x = e.clientX - innerWidth / 2;
const y = e.clientY - innerHeight / 2;
mouseX.set(x);
mouseY.set(y);
};
window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
}, [mouseX, mouseY]);
// Prevent hydration mismatch
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) return null;
return (
<div className="fixed inset-0 overflow-hidden pointer-events-none z-0 liquid-container">
<motion.div
className="absolute top-[-10%] left-[-10%] w-[40vw] h-[40vw] bg-liquid-mint/40 rounded-full blur-[80px] mix-blend-multiply opacity-70"
style={{ x: x1, y: y1 }}
animate={{
scale: [1, 1.2, 1],
rotate: [0, 90, 0],
}}
transition={{ duration: 25, repeat: Infinity, ease: "linear" }}
/>
<motion.div
className="absolute top-[20%] right-[-10%] w-[35vw] h-[35vw] bg-liquid-lavender/40 rounded-full blur-[80px] mix-blend-multiply opacity-70"
style={{ x: x2, y: y2 }}
animate={{
scale: [1, 1.1, 1],
rotate: [0, -60, 0],
}}
transition={{ duration: 30, repeat: Infinity, ease: "linear" }}
/>
<motion.div
className="absolute bottom-[-10%] left-[20%] w-[45vw] h-[45vw] bg-liquid-rose/30 rounded-full blur-[80px] mix-blend-multiply opacity-70"
style={{ x: x3, y: y3 }}
animate={{
scale: [1, 1.3, 1],
rotate: [0, 45, 0]
}}
transition={{ duration: 35, repeat: Infinity, ease: "linear" }}
/>
</div>
);
};

33
components/GooFilter.tsx Normal file
View File

@@ -0,0 +1,33 @@
export const GooFilter = () => (
<svg
style={{ position: 'fixed', top: 0, left: 0, width: 0, height: 0, pointerEvents: 'none', zIndex: -1 }}
xmlns="http://www.w3.org/2000/svg"
version="1.1"
>
<defs>
{/* Global subtle filter */}
<filter id="goo">
<feGaussianBlur in="SourceGraphic" stdDeviation="2" result="blur" />
<feColorMatrix
in="blur"
mode="matrix"
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 19 -9"
result="goo"
/>
<feComposite in="SourceGraphic" in2="goo" operator="atop" />
</filter>
{/* Stronger filter specifically for LiquidHeading interaction */}
<filter id="goo-text">
<feGaussianBlur in="SourceGraphic" stdDeviation="2" result="blur" />
<feColorMatrix
in="blur"
mode="matrix"
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 25 -10"
result="goo"
/>
<feComposite in="SourceGraphic" in2="goo" operator="atop" />
</filter>
</defs>
</svg>
);

View File

@@ -0,0 +1,5 @@
'use client';
export const LiquidCursor = () => {
return null;
};

View File

@@ -0,0 +1,19 @@
'use client';
import clsx from 'clsx';
interface LiquidHeadingProps {
text: string;
className?: string;
level?: 1 | 2 | 3 | 4;
}
export const LiquidHeading = ({ text, className, level = 1 }: LiquidHeadingProps) => {
const Tag = `h${level}` as keyof JSX.IntrinsicElements;
return (
<Tag className={clsx("font-bold tracking-tight text-stone-800", className)}>
{text}
</Tag>
);
};

View File

@@ -10,7 +10,68 @@ export default {
// Add other paths if necessary // Add other paths if necessary
], ],
theme: { theme: {
extend: {}, extend: {
colors: {
background: "var(--background)",
foreground: "var(--foreground)",
card: {
DEFAULT: "var(--card)",
foreground: "var(--card-foreground)",
},
popover: {
DEFAULT: "var(--popover)",
foreground: "var(--popover-foreground)",
},
primary: {
DEFAULT: "var(--primary)",
foreground: "var(--primary-foreground)",
},
secondary: {
DEFAULT: "var(--secondary)",
foreground: "var(--secondary-foreground)",
},
muted: {
DEFAULT: "var(--muted)",
foreground: "var(--muted-foreground)",
},
accent: {
DEFAULT: "var(--accent)",
foreground: "var(--accent-foreground)",
},
destructive: {
DEFAULT: "var(--destructive)",
foreground: "var(--destructive-foreground)",
},
border: "var(--border)",
input: "var(--input)",
ring: "var(--ring)",
cream: "#FDFCF8",
sand: "#F3F1E7",
stone: {
50: "#FAFAF9",
100: "#F5F5F4",
200: "#E7E5E4",
300: "#D6D3D1",
400: "#A8A29E",
500: "#78716C",
600: "#57534E",
700: "#44403C",
800: "#292524",
900: "#1C1917",
},
liquid: {
mint: "#A7F3D0",
lavender: "#DDD6FE",
blue: "#BFDBFE",
rose: "#FECACA",
yellow: "#FDE68A",
}
},
fontFamily: {
sans: ['var(--font-inter)', 'sans-serif'],
mono: ['var(--font-roboto-mono)', 'monospace'],
},
},
}, },
plugins: [], plugins: [],
} satisfies Config; } satisfies Config;