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:
19
app/api/n8n/status/route.ts
Normal file
19
app/api/n8n/status/route.ts
Normal 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
|
||||
});
|
||||
}
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
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 [mounted, setMounted] = useState(false);
|
||||
@@ -11,180 +12,83 @@ const About = () => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
const skills = [
|
||||
const techStack = [
|
||||
{
|
||||
category: 'Frontend',
|
||||
icon: Code,
|
||||
technologies: ['React', 'Next.js', 'TypeScript', 'Tailwind CSS', 'Framer Motion'],
|
||||
color: 'from-blue-500 to-cyan-500'
|
||||
icon: Globe,
|
||||
items: ['React', 'TypeScript', 'Tailwind', 'Next.js']
|
||||
},
|
||||
{
|
||||
category: 'Backend',
|
||||
icon: Database,
|
||||
technologies: ['Node.js', 'PostgreSQL', 'Prisma', 'REST APIs', 'GraphQL'],
|
||||
color: 'from-purple-500 to-pink-500'
|
||||
icon: Terminal,
|
||||
items: ['Node.js', 'PostgreSQL', 'Prisma', 'API Design']
|
||||
},
|
||||
{
|
||||
category: 'DevOps',
|
||||
icon: Cloud,
|
||||
technologies: ['Docker', 'CI/CD', 'Nginx', 'Redis', 'AWS'],
|
||||
color: 'from-green-500 to-emerald-500'
|
||||
},
|
||||
{
|
||||
category: 'Mobile',
|
||||
icon: Smartphone,
|
||||
technologies: ['React Native', 'Expo', 'iOS', 'Android'],
|
||||
color: 'from-orange-500 to-red-500'
|
||||
},
|
||||
category: 'Tools',
|
||||
icon: Cpu,
|
||||
items: ['Git', 'Docker', 'VS Code', 'Figma']
|
||||
}
|
||||
];
|
||||
|
||||
const values = [
|
||||
{
|
||||
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;
|
||||
}
|
||||
if (!mounted) return null;
|
||||
|
||||
return (
|
||||
<section id="about" className="py-20 px-4 relative overflow-hidden">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
{/* Section Header */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.8 }}
|
||||
className="text-center mb-16"
|
||||
>
|
||||
<h2 className="text-4xl md:text-5xl font-bold mb-6 gradient-text">
|
||||
About Me
|
||||
</h2>
|
||||
<p className="text-xl text-gray-400 max-w-3xl mx-auto leading-relaxed">
|
||||
I'm a passionate software engineer with a love for creating beautiful,
|
||||
functional applications. I enjoy working with modern technologies and
|
||||
turning ideas into reality.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* About Content */}
|
||||
<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'm a student and software engineer based in Osnabrück, Germany.
|
||||
My passion for technology started early, and I'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'm always learning new
|
||||
technologies and improving my skills.
|
||||
</p>
|
||||
<p className="text-gray-300 leading-relaxed text-lg">
|
||||
When I'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>
|
||||
))}
|
||||
<section id="about" className="py-24 px-4 bg-white relative overflow-hidden">
|
||||
<div className="max-w-6xl mx-auto relative z-10">
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16 items-center">
|
||||
{/* Text Content */}
|
||||
<div className="space-y-8">
|
||||
<LiquidHeading
|
||||
text="About Me"
|
||||
level={2}
|
||||
className="text-4xl md:text-5xl font-bold text-stone-900"
|
||||
/>
|
||||
<div className="prose prose-stone prose-lg text-stone-600">
|
||||
<p>
|
||||
Hi, I'm Dennis. I'm a software engineer who likes building things that work well and look good.
|
||||
</p>
|
||||
<p>
|
||||
I'm currently based in Osnabrück, Germany. My journey in tech is driven by curiosity—I love figuring out how things work and how to make them better.
|
||||
</p>
|
||||
<p>
|
||||
When I'm not in front of a screen, you can find me listening to music, exploring new ideas, or just relaxing.
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Skills Section */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
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) => (
|
||||
{/* Simplified Skills / Tech Stack */}
|
||||
<div className="grid grid-cols-1 gap-6">
|
||||
<h3 className="text-xl font-bold text-stone-900 mb-2">My Toolbox</h3>
|
||||
{techStack.map((stack, idx) => (
|
||||
<motion.div
|
||||
key={skill.category}
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
key={stack.category}
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: idx * 0.1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6, delay: index * 0.1 }}
|
||||
whileHover={{ y: -8, scale: 1.02 }}
|
||||
className="glass-card p-6 rounded-2xl"
|
||||
className="p-6 rounded-xl bg-stone-50 border border-stone-100 hover:border-stone-200 transition-colors"
|
||||
>
|
||||
<div className={`w-14 h-14 bg-gradient-to-br ${skill.color} rounded-xl flex items-center justify-center mb-4`}>
|
||||
<skill.icon className="w-7 h-7 text-white" />
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<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>
|
||||
<h4 className="text-xl font-bold text-white mb-4">{skill.category}</h4>
|
||||
<div className="space-y-2">
|
||||
{skill.technologies.map((tech) => (
|
||||
<div
|
||||
key={tech}
|
||||
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 className="flex flex-wrap gap-2">
|
||||
{stack.items.map(item => (
|
||||
<span key={item} className="px-3 py-1 bg-white rounded-md border border-stone-200 text-sm text-stone-600">
|
||||
{item}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default About;
|
||||
|
||||
|
||||
export default About;
|
||||
160
app/components/ActivityFeed.tsx
Normal file
160
app/components/ActivityFeed.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
@@ -4,6 +4,7 @@ import { useState, useEffect } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Mail, MapPin, Send } from 'lucide-react';
|
||||
import { useToast } from '@/components/Toast';
|
||||
import { LiquidHeading } from '@/components/LiquidHeading';
|
||||
|
||||
const Contact = () => {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
@@ -139,23 +140,19 @@ const Contact = () => {
|
||||
}
|
||||
|
||||
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">
|
||||
{/* Section Header */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.8 }}
|
||||
className="text-center mb-16"
|
||||
>
|
||||
<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">
|
||||
<div className="text-center mb-16">
|
||||
<LiquidHeading
|
||||
text="Contact Me"
|
||||
level={2}
|
||||
className="text-4xl md:text-5xl font-bold mb-6 text-stone-800"
|
||||
/>
|
||||
<p className="text-xl text-stone-500 max-w-2xl mx-auto mt-4">
|
||||
Interested in working together or have questions about my projects? Feel free to reach out!
|
||||
</p>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12">
|
||||
{/* Contact Information */}
|
||||
@@ -167,10 +164,10 @@ const Contact = () => {
|
||||
className="space-y-8"
|
||||
>
|
||||
<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
|
||||
</h3>
|
||||
<p className="text-gray-400 leading-relaxed">
|
||||
<p className="text-stone-600 leading-relaxed">
|
||||
I'm always available to discuss new opportunities, interesting projects,
|
||||
or simply chat about technology and innovation.
|
||||
</p>
|
||||
@@ -187,14 +184,14 @@ const Contact = () => {
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6, delay: index * 0.1 }}
|
||||
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">
|
||||
<info.icon className="w-6 h-6 text-blue-400" />
|
||||
<div className="p-3 bg-white rounded-xl shadow-sm group-hover:shadow-md transition-all">
|
||||
<info.icon className="w-6 h-6 text-stone-700" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-semibold text-white">{info.title}</h4>
|
||||
<p className="text-gray-400">{info.value}</p>
|
||||
<h4 className="font-semibold text-stone-800">{info.title}</h4>
|
||||
<p className="text-stone-500">{info.value}</p>
|
||||
</div>
|
||||
</motion.a>
|
||||
))}
|
||||
@@ -208,15 +205,15 @@ const Contact = () => {
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true }}
|
||||
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">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label htmlFor="name" className="block text-sm font-medium text-gray-300 mb-2">
|
||||
Name <span className="text-red-400">*</span>
|
||||
<label htmlFor="name" className="block text-sm font-medium text-stone-600 mb-2">
|
||||
Name <span className="text-liquid-rose">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
@@ -226,23 +223,23 @@ const Contact = () => {
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
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
|
||||
? 'border-red-500 focus:ring-red-500'
|
||||
: 'border-gray-700 focus:ring-blue-500 focus:border-transparent'
|
||||
? 'border-red-400 focus:ring-red-400'
|
||||
: 'border-white/60 focus:ring-liquid-blue focus:border-transparent'
|
||||
}`}
|
||||
placeholder="Your name"
|
||||
aria-invalid={errors.name && touched.name ? 'true' : 'false'}
|
||||
aria-describedby={errors.name && touched.name ? 'name-error' : undefined}
|
||||
/>
|
||||
{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>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-gray-300 mb-2">
|
||||
Email <span className="text-red-400">*</span>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-stone-600 mb-2">
|
||||
Email <span className="text-liquid-rose">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
@@ -252,24 +249,24 @@ const Contact = () => {
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
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
|
||||
? 'border-red-500 focus:ring-red-500'
|
||||
: 'border-gray-700 focus:ring-blue-500 focus:border-transparent'
|
||||
? 'border-red-400 focus:ring-red-400'
|
||||
: 'border-white/60 focus:ring-liquid-blue focus:border-transparent'
|
||||
}`}
|
||||
placeholder="your@email.com"
|
||||
aria-invalid={errors.email && touched.email ? 'true' : 'false'}
|
||||
aria-describedby={errors.email && touched.email ? 'email-error' : undefined}
|
||||
/>
|
||||
{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>
|
||||
<label htmlFor="subject" className="block text-sm font-medium text-gray-300 mb-2">
|
||||
Subject <span className="text-red-400">*</span>
|
||||
<label htmlFor="subject" className="block text-sm font-medium text-stone-600 mb-2">
|
||||
Subject <span className="text-liquid-rose">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
@@ -279,23 +276,23 @@ const Contact = () => {
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
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
|
||||
? 'border-red-500 focus:ring-red-500'
|
||||
: 'border-gray-700 focus:ring-blue-500 focus:border-transparent'
|
||||
? 'border-red-400 focus:ring-red-400'
|
||||
: 'border-white/60 focus:ring-liquid-blue focus:border-transparent'
|
||||
}`}
|
||||
placeholder="What's this about?"
|
||||
aria-invalid={errors.subject && touched.subject ? 'true' : 'false'}
|
||||
aria-describedby={errors.subject && touched.subject ? 'subject-error' : undefined}
|
||||
/>
|
||||
{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>
|
||||
<label htmlFor="message" className="block text-sm font-medium text-gray-300 mb-2">
|
||||
Message <span className="text-red-400">*</span>
|
||||
<label htmlFor="message" className="block text-sm font-medium text-stone-600 mb-2">
|
||||
Message <span className="text-liquid-rose">*</span>
|
||||
</label>
|
||||
<textarea
|
||||
id="message"
|
||||
@@ -305,10 +302,10 @@ const Contact = () => {
|
||||
onBlur={handleBlur}
|
||||
required
|
||||
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
|
||||
? 'border-red-500 focus:ring-red-500'
|
||||
: 'border-gray-700 focus:ring-blue-500 focus:border-transparent'
|
||||
? 'border-red-400 focus:ring-red-400'
|
||||
: 'border-white/60 focus:ring-liquid-blue focus:border-transparent'
|
||||
}`}
|
||||
placeholder="Tell me more about your project or question..."
|
||||
aria-invalid={errors.message && touched.message ? 'true' : 'false'}
|
||||
@@ -316,11 +313,11 @@ const Contact = () => {
|
||||
/>
|
||||
<div className="flex justify-between items-center mt-1">
|
||||
{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 className="text-xs text-gray-500">
|
||||
<span className="text-xs text-stone-400">
|
||||
{formData.message.length} characters
|
||||
</span>
|
||||
</div>
|
||||
@@ -331,7 +328,7 @@ const Contact = () => {
|
||||
disabled={isSubmitting}
|
||||
whileHover={!isSubmitting ? { scale: 1.02, y: -2 } : {}}
|
||||
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 ? (
|
||||
<>
|
||||
|
||||
@@ -25,7 +25,7 @@ const Footer = () => {
|
||||
}
|
||||
|
||||
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="flex flex-col md:flex-row justify-between items-center space-y-6 md:space-y-0">
|
||||
{/* Brand */}
|
||||
@@ -39,15 +39,15 @@ const Footer = () => {
|
||||
<motion.div
|
||||
whileHover={{ rotate: 360, scale: 1.1 }}
|
||||
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>
|
||||
<div>
|
||||
<Link href="/" className="text-xl font-bold font-mono text-white hover:text-blue-400 transition-colors">
|
||||
dk<span className="text-red-500">0</span>
|
||||
<Link href="/" className="text-xl font-bold font-mono text-stone-800 hover:text-liquid-blue transition-colors">
|
||||
dk<span className="text-liquid-rose">0</span>
|
||||
</Link>
|
||||
<p className="text-xs text-gray-500">Software Engineer</p>
|
||||
<p className="text-xs text-stone-500">Software Engineer</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
@@ -67,7 +67,7 @@ const Footer = () => {
|
||||
rel="noopener noreferrer"
|
||||
whileHover={{ scale: 1.15, y: -3 }}
|
||||
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}
|
||||
>
|
||||
<social.icon size={18} />
|
||||
@@ -81,14 +81,14 @@ const Footer = () => {
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
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>
|
||||
<motion.div
|
||||
animate={{ scale: [1, 1.2, 1] }}
|
||||
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>
|
||||
<span>Made in Germany</span>
|
||||
</motion.div>
|
||||
@@ -100,30 +100,30 @@ const Footer = () => {
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
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">
|
||||
<Link
|
||||
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
|
||||
</Link>
|
||||
<Link
|
||||
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
|
||||
</Link>
|
||||
</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 className="text-blue-400 font-semibold">Next.js</span>
|
||||
<span className="text-gray-600">•</span>
|
||||
<span className="text-blue-400 font-semibold">TypeScript</span>
|
||||
<span className="text-gray-600">•</span>
|
||||
<span className="text-blue-400 font-semibold">Tailwind CSS</span>
|
||||
<span className="text-stone-600 font-semibold">Next.js</span>
|
||||
<span className="text-stone-300">•</span>
|
||||
<span className="text-stone-600 font-semibold">TypeScript</span>
|
||||
<span className="text-stone-300">•</span>
|
||||
<span className="text-stone-600 font-semibold">Tailwind CSS</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
@@ -43,36 +43,29 @@ const Header = () => {
|
||||
|
||||
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
|
||||
initial={{ y: -100 }}
|
||||
animate={{ y: 0 }}
|
||||
initial={{ y: -100, opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
transition={{ duration: 0.8, ease: "easeOut" }}
|
||||
className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${
|
||||
scrolled ? 'glass' : 'bg-transparent'
|
||||
}`}
|
||||
className="fixed top-6 left-0 right-0 z-50 flex justify-center px-4 pointer-events-none"
|
||||
>
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex justify-between items-center h-16">
|
||||
<div className={`pointer-events-auto transition-all duration-500 ease-out ${
|
||||
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
|
||||
whileHover={{ scale: 1.05 }}
|
||||
className="flex items-center space-x-2"
|
||||
>
|
||||
<Link href="/" className="text-2xl font-bold font-mono text-white">
|
||||
dk<span className="text-red-500">0</span>
|
||||
<Link href="/" className="text-2xl font-bold font-mono text-stone-800 tracking-tighter liquid-hover">
|
||||
dk<span className="text-liquid-rose">0</span>
|
||||
</Link>
|
||||
</motion.div>
|
||||
|
||||
@@ -85,7 +78,7 @@ const Header = () => {
|
||||
>
|
||||
<Link
|
||||
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) => {
|
||||
if (item.href.startsWith('#')) {
|
||||
e.preventDefault();
|
||||
@@ -97,24 +90,24 @@ const Header = () => {
|
||||
}}
|
||||
>
|
||||
{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>
|
||||
</motion.div>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
<div className="hidden md:flex items-center space-x-4">
|
||||
<div className="hidden md:flex items-center space-x-3">
|
||||
{socialLinks.map((social) => (
|
||||
<motion.a
|
||||
key={social.label}
|
||||
href={social.href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
whileHover={{ scale: 1.1, y: -2 }}
|
||||
whileHover={{ scale: 1.1, rotate: 5 }}
|
||||
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>
|
||||
))}
|
||||
</div>
|
||||
@@ -122,7 +115,7 @@ const Header = () => {
|
||||
<motion.button
|
||||
whileTap={{ scale: 0.95 }}
|
||||
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} />}
|
||||
</motion.button>
|
||||
@@ -137,17 +130,17 @@ const Header = () => {
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
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)}
|
||||
/>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -20 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className="md:hidden glass border-t border-gray-800/50 z-50 relative"
|
||||
initial={{ opacity: 0, scale: 0.95, y: -20 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
exit={{ opacity: 0, scale: 0.95, y: -20 }}
|
||||
transition={{ duration: 0.3, type: "spring" }}
|
||||
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) => (
|
||||
<motion.div
|
||||
key={item.name}
|
||||
@@ -170,16 +163,15 @@ const Header = () => {
|
||||
}, 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}
|
||||
</Link>
|
||||
</motion.div>
|
||||
))}
|
||||
|
||||
<div className="pt-4 mt-4 border-t border-gray-700/50">
|
||||
<p className="text-xs text-gray-500 mb-3 px-4">Connect with me</p>
|
||||
<div className="flex space-x-3 px-4">
|
||||
<div className="pt-6 mt-4 border-t border-stone-200">
|
||||
<div className="flex justify-center space-x-4">
|
||||
{socialLinks.map((social, index) => (
|
||||
<motion.a
|
||||
key={social.label}
|
||||
@@ -189,9 +181,8 @@ const Header = () => {
|
||||
initial={{ scale: 0, opacity: 0 }}
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
transition={{ delay: (navItems.length + index) * 0.05 }}
|
||||
whileHover={{ scale: 1.1, y: -2 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="p-3 rounded-xl bg-gray-800/50 hover:bg-gray-700/50 transition-all duration-200 text-gray-300 hover:text-white"
|
||||
whileHover={{ scale: 1.1 }}
|
||||
className="p-3 rounded-full bg-white/60 text-stone-600 shadow-sm"
|
||||
aria-label={social.label}
|
||||
>
|
||||
<social.icon size={20} />
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useState, useEffect } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { ArrowDown, Code, Zap, Rocket } from 'lucide-react';
|
||||
import Image from 'next/image';
|
||||
import { LiquidHeading } from '@/components/LiquidHeading';
|
||||
|
||||
const Hero = () => {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
@@ -23,153 +24,116 @@ const Hero = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="relative min-h-screen flex items-center justify-center overflow-hidden pt-20 pb-8">
|
||||
{/* Animated Background */}
|
||||
<div className="absolute inset-0 animated-bg"></div>
|
||||
<section className="relative min-h-screen flex items-center justify-center overflow-hidden pt-20 pb-8 bg-transparent">
|
||||
|
||||
{/* Floating Elements */}
|
||||
<div className="absolute inset-0 overflow-hidden">
|
||||
<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 */}
|
||||
<div className="relative z-10 text-center px-4 max-w-5xl mx-auto">
|
||||
{/* Domain Badge */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.5 }}
|
||||
className="mb-8"
|
||||
className="mb-8 inline-block"
|
||||
>
|
||||
<div className="domain-text text-white/95 text-center">
|
||||
dk<span className="text-red-500">0</span>.dev
|
||||
<div className="px-6 py-2 rounded-full glass-panel text-stone-600 font-mono text-sm tracking-wider uppercase">
|
||||
dk<span className="text-liquid-rose font-bold">0</span>.dev
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Profile Image */}
|
||||
{/* Profile Image with Organic Blob Mask */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.8, rotateY: -15 }}
|
||||
animate={{ opacity: 1, scale: 1, rotateY: 0 }}
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
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">
|
||||
{/* Profile image container */}
|
||||
<div className="relative bg-gray-900 rounded-full p-1">
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.05, rotateY: 5 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
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"
|
||||
>
|
||||
<div className="relative w-64 h-64 md:w-80 md:h-80 flex items-center justify-center">
|
||||
{/* Large Rotating Liquid Blobs behind image */}
|
||||
<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"
|
||||
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%"],
|
||||
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
|
||||
src="/images/me.jpg"
|
||||
alt="Dennis Konkol - Software Engineer"
|
||||
alt="Dennis Konkol"
|
||||
fill
|
||||
className="object-cover"
|
||||
className="object-cover scale-105 hover:scale-110 transition-transform duration-700"
|
||||
priority
|
||||
/>
|
||||
|
||||
{/* Hover overlay effect */}
|
||||
<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>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* 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" />
|
||||
{/* Glossy Overlay for Liquid Feel */}
|
||||
<div className="absolute inset-0 bg-gradient-to-tr from-white/30 via-transparent to-transparent opacity-50 pointer-events-none z-10" />
|
||||
|
||||
{/* Inner Border/Highlight */}
|
||||
<div className="absolute inset-0 border-[3px] border-white/20 rounded-[inherit] pointer-events-none z-20" />
|
||||
</motion.div>
|
||||
|
||||
{/* Floating Badges */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.5, delay: 1.7 }}
|
||||
className="absolute -bottom-3 -left-3 w-10 h-10 bg-purple-500 rounded-full flex items-center justify-center shadow-lg"
|
||||
initial={{ scale: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ delay: 1.5, type: "spring" }}
|
||||
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
|
||||
initial={{ opacity: 0, scale: 0 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.5, delay: 1.9 }}
|
||||
className="absolute -top-3 -left-3 w-10 h-10 bg-cyan-500 rounded-full flex items-center justify-center shadow-lg"
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ delay: 1.7, type: "spring" }}
|
||||
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"
|
||||
>
|
||||
<Rocket className="w-5 h-5 text-white" />
|
||||
<Zap size={24} />
|
||||
</motion.div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Main Title */}
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.8 }}
|
||||
className="text-5xl md:text-7xl font-bold mb-4"
|
||||
>
|
||||
<span className="gradient-text">Dennis Konkol</span>
|
||||
</motion.h1>
|
||||
<div className="mb-6 flex flex-col items-center justify-center relative">
|
||||
<LiquidHeading
|
||||
text="Dennis Konkol"
|
||||
level={1}
|
||||
className="text-5xl md:text-8xl font-bold tracking-tighter text-stone-800"
|
||||
/>
|
||||
<LiquidHeading
|
||||
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 */}
|
||||
<motion.p
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
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 create innovative solutions that make a difference.
|
||||
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>.
|
||||
</motion.p>
|
||||
|
||||
{/* Features */}
|
||||
@@ -177,7 +141,7 @@ const Hero = () => {
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
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) => (
|
||||
<motion.div
|
||||
@@ -185,11 +149,11 @@ const Hero = () => {
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.5, delay: 1.6 + index * 0.1 }}
|
||||
whileHover={{ scale: 1.05, y: -5 }}
|
||||
className="flex items-center space-x-2 px-4 py-2 rounded-full glass-card"
|
||||
whileHover={{ scale: 1.05, y: -2 }}
|
||||
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" />
|
||||
<span className="text-gray-300 font-medium">{feature.text}</span>
|
||||
<feature.icon className="w-4 h-4 text-stone-700" />
|
||||
<span className="text-stone-700 font-medium text-sm">{feature.text}</span>
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
@@ -199,45 +163,27 @@ const Hero = () => {
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
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
|
||||
href="#projects"
|
||||
whileHover={{ scale: 1.05, y: -2 }}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
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>
|
||||
<ArrowDown className="w-5 h-5" />
|
||||
<ArrowDown size={18} />
|
||||
</motion.a>
|
||||
|
||||
<motion.a
|
||||
href="#contact"
|
||||
whileHover={{ scale: 1.05, y: -2 }}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
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>
|
||||
</motion.a>
|
||||
</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>
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
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 { LiquidHeading } from '@/components/LiquidHeading';
|
||||
import Image from 'next/image';
|
||||
|
||||
interface Project {
|
||||
id: number;
|
||||
@@ -16,188 +18,139 @@ interface Project {
|
||||
date: string;
|
||||
github?: string;
|
||||
live?: string;
|
||||
imageUrl?: string;
|
||||
}
|
||||
|
||||
const Projects = () => {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [projects, setProjects] = useState<Project[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
const [projects, setProjects] = useState<Project[]>([]);
|
||||
|
||||
// Load projects from API
|
||||
useEffect(() => {
|
||||
const loadProjects = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/projects?featured=true&published=true&limit=6');
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setProjects(data.projects || []);
|
||||
} else {
|
||||
console.error('Failed to fetch projects from API');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading projects:', error);
|
||||
}
|
||||
};
|
||||
|
||||
loadProjects();
|
||||
}, []);
|
||||
|
||||
if (!mounted) {
|
||||
return null;
|
||||
}
|
||||
if (!mounted) return null;
|
||||
|
||||
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">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.8 }}
|
||||
className="text-center mb-16"
|
||||
>
|
||||
<h2 className="text-4xl md:text-5xl font-bold mb-6 gradient-text">
|
||||
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.
|
||||
<div className="text-center mb-20">
|
||||
<LiquidHeading
|
||||
text="Selected Works"
|
||||
level={2}
|
||||
className="text-4xl md:text-6xl font-bold mb-6 text-stone-900"
|
||||
/>
|
||||
<p className="text-lg text-stone-500 max-w-2xl mx-auto mt-4 font-light">
|
||||
A collection of projects I've worked on, ranging from web applications to experiments.
|
||||
</p>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{projects.map((project, index) => (
|
||||
<motion.div
|
||||
key={project.id}
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6, delay: index * 0.1 }}
|
||||
whileHover={{ y: -12, scale: 1.02 }}
|
||||
className={`group relative overflow-hidden rounded-2xl glass-card card-hover border border-gray-800/50 hover:border-gray-700/50 transition-all ${
|
||||
project.featured ? 'ring-2 ring-blue-500/30 shadow-lg shadow-blue-500/10' : ''
|
||||
}`}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
whileHover={{ y: -8 }}
|
||||
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"
|
||||
>
|
||||
<div className="relative h-48 overflow-hidden bg-gradient-to-br from-gray-900 to-gray-800">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-blue-500/10 via-purple-500/10 to-pink-500/10" />
|
||||
<div className="absolute inset-0 flex flex-col items-center justify-center p-4">
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.1, rotate: 5 }}
|
||||
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"
|
||||
>
|
||||
<span className="text-2xl font-bold text-white">
|
||||
{project.title.split(' ').map(word => word[0]).join('').toUpperCase().slice(0, 2)}
|
||||
</span>
|
||||
</motion.div>
|
||||
<span className="text-sm font-semibold text-gray-300 text-center leading-tight px-2">
|
||||
{project.title}
|
||||
</span>
|
||||
</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>
|
||||
{/* Project Cover / Header */}
|
||||
<div className="relative aspect-[4/3] overflow-hidden bg-stone-100">
|
||||
{project.imageUrl ? (
|
||||
<Image
|
||||
src={project.imageUrl}
|
||||
alt={project.title}
|
||||
fill
|
||||
className="object-cover transition-transform duration-500 group-hover:scale-105"
|
||||
/>
|
||||
) : (
|
||||
<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">
|
||||
<div className="w-full h-full border-2 border-dashed border-stone-300 rounded-xl flex items-center justify-center">
|
||||
<Layers className="text-stone-300 w-12 h-12" />
|
||||
</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>
|
||||
)}
|
||||
|
||||
<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">
|
||||
{project.github && project.github.trim() !== '' && project.github !== '#' && (
|
||||
<motion.a
|
||||
href={project.github}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
whileHover={{ scale: 1.15, y: -2 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
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"
|
||||
aria-label="View on GitHub"
|
||||
>
|
||||
<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>
|
||||
)}
|
||||
|
||||
{/* Overlay Links */}
|
||||
<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]">
|
||||
{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">
|
||||
<Github size={20} />
|
||||
</a>
|
||||
)}
|
||||
{project.live && (
|
||||
<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">
|
||||
<ExternalLink size={20} />
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-6">
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<h3 className="text-xl font-bold text-white group-hover:text-blue-400 transition-colors flex-1 pr-2">
|
||||
{/* Content */}
|
||||
<div className="flex flex-col flex-1 p-6">
|
||||
<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}
|
||||
</h3>
|
||||
<div className="flex items-center space-x-1.5 text-gray-400 flex-shrink-0">
|
||||
<Calendar size={14} />
|
||||
<span className="text-xs">{new Date(project.date).getFullYear()}</span>
|
||||
</div>
|
||||
<span className="text-xs font-mono text-stone-400 bg-stone-100 px-2 py-1 rounded">
|
||||
{new Date(project.date).getFullYear()}
|
||||
</span>
|
||||
</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}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap gap-2 mb-5">
|
||||
{project.tags.slice(0, 4).map((tag) => (
|
||||
<span
|
||||
key={tag}
|
||||
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>
|
||||
))}
|
||||
{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">
|
||||
+{project.tags.length - 4}
|
||||
</span>
|
||||
)}
|
||||
<div className="space-y-4 mt-auto">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{project.tags.slice(0, 3).map(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">
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
{project.tags.length > 3 && (
|
||||
<span className="text-xs px-2 py-1 text-stone-400">+ {project.tags.length - 3}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
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"
|
||||
>
|
||||
<div className="mt-16 text-center">
|
||||
<Link
|
||||
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>
|
||||
<ExternalLink size={20} />
|
||||
Archive <ArrowRight size={16} />
|
||||
</Link>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Projects;
|
||||
export default Projects;
|
||||
734
app/globals.css
734
app/globals.css
@@ -4,692 +4,150 @@
|
||||
|
||||
@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 {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #fafafa;
|
||||
--card: #0f0f0f;
|
||||
--card-foreground: #fafafa;
|
||||
--popover: #0f0f0f;
|
||||
--popover-foreground: #fafafa;
|
||||
--primary: #3b82f6;
|
||||
--primary-foreground: #f8fafc;
|
||||
--secondary: #1e293b;
|
||||
--secondary-foreground: #f1f5f9;
|
||||
--muted: #1e293b;
|
||||
--muted-foreground: #64748b;
|
||||
--accent: #1e293b;
|
||||
--accent-foreground: #f1f5f9;
|
||||
--destructive: #ef4444;
|
||||
--destructive-foreground: #f8fafc;
|
||||
--border: #1e293b;
|
||||
--input: #1e293b;
|
||||
--ring: #3b82f6;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
* {
|
||||
border-color: hsl(var(--border));
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
scroll-padding-top: 80px;
|
||||
/* Organic Modern Palette */
|
||||
--background: #FDFCF8; /* Cream */
|
||||
--foreground: #292524; /* Warm Grey */
|
||||
--card: rgba(255, 255, 255, 0.6);
|
||||
--card-foreground: #292524;
|
||||
--popover: #FFFFFF;
|
||||
--popover-foreground: #292524;
|
||||
--primary: #292524;
|
||||
--primary-foreground: #FDFCF8;
|
||||
--secondary: #E7E5E4;
|
||||
--secondary-foreground: #292524;
|
||||
--muted: #F5F5F4;
|
||||
--muted-foreground: #78716C;
|
||||
--accent: #F3F1E7; /* Sand */
|
||||
--accent-foreground: #292524;
|
||||
--destructive: #EF4444;
|
||||
--destructive-foreground: #FDFCF8;
|
||||
--border: #E7E5E4;
|
||||
--input: #E7E5E4;
|
||||
--ring: #A7F3D0; /* Mint ring */
|
||||
--radius: 1rem;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: hsl(var(--background));
|
||||
color: hsl(var(--foreground));
|
||||
background-color: var(--background);
|
||||
color: var(--foreground);
|
||||
font-family: 'Inter', sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* Custom Scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
/* Custom Selection */
|
||||
::selection {
|
||||
background: #A7F3D0; /* Mint */
|
||||
color: #292524;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: hsl(var(--background));
|
||||
/* Smooth Scrolling */
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: hsl(var(--muted));
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
/* 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);
|
||||
/* Liquid Glass Effects */
|
||||
.glass-panel {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
backdrop-filter: blur(12px) saturate(120%);
|
||||
-webkit-backdrop-filter: blur(12px) saturate(120%);
|
||||
border: 1px solid rgba(255, 255, 255, 0.6);
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.glass-card {
|
||||
background: rgba(15, 15, 15, 0.7);
|
||||
backdrop-filter: blur(16px) saturate(180%);
|
||||
-webkit-backdrop-filter: blur(16px) saturate(180%);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
background: rgba(255, 255, 255, 0.65);
|
||||
backdrop-filter: blur(24px) saturate(180%);
|
||||
-webkit-backdrop-filter: blur(24px) saturate(180%);
|
||||
border: 1px solid rgba(255, 255, 255, 0.8);
|
||||
box-shadow:
|
||||
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 {
|
||||
background: rgba(15, 15, 15, 0.8);
|
||||
border-color: rgba(255, 255, 255, 0.15);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
|
||||
background: rgba(255, 255, 255, 0.75);
|
||||
box-shadow:
|
||||
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 */
|
||||
.admin-glass {
|
||||
background: rgba(255, 255, 255, 0.05) !important;
|
||||
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;
|
||||
/* Typography & Headings */
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.admin-glass-card {
|
||||
background: rgba(255, 255, 255, 0.08) !important;
|
||||
backdrop-filter: blur(16px) !important;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2) !important;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3) !important;
|
||||
/* Utility for the liquid melt effect container */
|
||||
.liquid-container {
|
||||
filter: url('#goo');
|
||||
}
|
||||
|
||||
.admin-glass-light {
|
||||
background: rgba(255, 255, 255, 0.12) !important;
|
||||
backdrop-filter: blur(12px) !important;
|
||||
border: 1px solid rgba(255, 255, 255, 0.25) !important;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2) !important;
|
||||
/* Hide scrollbar but keep functionality */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #D6D3D1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #A8A29E;
|
||||
}
|
||||
|
||||
/* Admin Hover States */
|
||||
.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 */
|
||||
/* Animations */
|
||||
@keyframes float {
|
||||
0%, 100% { transform: translateY(0px); }
|
||||
50% { transform: translateY(-20px); }
|
||||
50% { transform: translateY(-10px); }
|
||||
}
|
||||
|
||||
.animate-float {
|
||||
animation: float 6s ease-in-out infinite;
|
||||
animation: float 5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* Glow Effects */
|
||||
.glow {
|
||||
box-shadow: 0 0 20px rgba(59, 130, 246, 0.3);
|
||||
@keyframes liquid-pulse {
|
||||
0% { transform: scale(1); }
|
||||
50% { transform: scale(1.05); }
|
||||
100% { transform: scale(1); }
|
||||
}
|
||||
|
||||
.glow-hover:hover {
|
||||
box-shadow: 0 0 30px rgba(59, 130, 246, 0.5);
|
||||
transition: box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
/* Particle Background */
|
||||
.particles {
|
||||
/* Liquid Blobs Background */
|
||||
.liquid-bg-blob {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
filter: blur(80px);
|
||||
opacity: 0.6;
|
||||
z-index: -1;
|
||||
animation: float 10s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.particle {
|
||||
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 Specifics for Blog/Projects */
|
||||
.markdown h1 {
|
||||
font-size: 2.5rem;
|
||||
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;
|
||||
@apply text-4xl font-bold mb-6 text-stone-800 tracking-tight;
|
||||
}
|
||||
|
||||
.markdown h2 {
|
||||
font-size: 2rem;
|
||||
font-weight: 600;
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
color: #ffffff !important;
|
||||
@apply text-2xl font-semibold mt-8 mb-4 text-stone-800 tracking-tight;
|
||||
}
|
||||
|
||||
.markdown h3 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
margin-top: 1.25rem;
|
||||
margin-bottom: 0.75rem;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.markdown p {
|
||||
margin-top: 0.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
line-height: 1.7;
|
||||
color: #e5e7eb !important;
|
||||
@apply mb-4 leading-relaxed text-stone-600;
|
||||
}
|
||||
|
||||
.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 {
|
||||
color: #3b82f6 !important;
|
||||
text-decoration: underline;
|
||||
transition: color 0.2s ease;
|
||||
@apply text-stone-800 underline decoration-liquid-mint decoration-2 underline-offset-2 hover:text-black transition-colors;
|
||||
}
|
||||
|
||||
.markdown a:hover {
|
||||
color: #1d4ed8 !important;
|
||||
.markdown ul {
|
||||
@apply list-disc list-inside mb-4 space-y-2 text-stone-600;
|
||||
}
|
||||
|
||||
.markdown strong {
|
||||
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 code {
|
||||
@apply bg-stone-100 px-1.5 py-0.5 rounded text-sm text-stone-800 font-mono;
|
||||
}
|
||||
.markdown pre {
|
||||
@apply bg-stone-900 text-stone-50 p-4 rounded-xl overflow-x-auto mb-6;
|
||||
}
|
||||
@@ -5,6 +5,8 @@ import React from "react";
|
||||
import { ToastProvider } from "@/components/Toast";
|
||||
import { AnalyticsProvider } from "@/components/AnalyticsProvider";
|
||||
import { PerformanceDashboard } from "@/components/PerformanceDashboard";
|
||||
import { GooFilter } from "@/components/GooFilter";
|
||||
import { BackgroundBlobs } from "@/components/BackgroundBlobs";
|
||||
|
||||
const inter = Inter({
|
||||
variable: "--font-inter",
|
||||
@@ -26,7 +28,11 @@ export default function RootLayout({
|
||||
<body className={inter.variable}>
|
||||
<AnalyticsProvider>
|
||||
<ToastProvider>
|
||||
{children}
|
||||
<GooFilter />
|
||||
<BackgroundBlobs />
|
||||
<div className="relative z-10">
|
||||
{children}
|
||||
</div>
|
||||
<PerformanceDashboard />
|
||||
</ToastProvider>
|
||||
</AnalyticsProvider>
|
||||
|
||||
10
app/page.tsx
10
app/page.tsx
@@ -7,6 +7,7 @@ import Projects from "./components/Projects";
|
||||
import Contact from "./components/Contact";
|
||||
import Footer from "./components/Footer";
|
||||
import Script from "next/script";
|
||||
import { ActivityFeed } from "./components/ActivityFeed";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
@@ -33,14 +34,13 @@ export default function Home() {
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
<ActivityFeed />
|
||||
<Header />
|
||||
<main className="relative">
|
||||
<Hero />
|
||||
<div className="bg-gradient-to-b from-gray-900 via-black to-black">
|
||||
<About />
|
||||
<Projects />
|
||||
<Contact />
|
||||
</div>
|
||||
<About />
|
||||
<Projects />
|
||||
<Contact />
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user