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 { 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 = [
|
|
||||||
{
|
|
||||||
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 (
|
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
|
|
||||||
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-16 items-center">
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 mb-20">
|
{/* 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>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 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
|
<motion.div
|
||||||
initial={{ opacity: 0, x: -30 }}
|
key={stack.category}
|
||||||
|
initial={{ opacity: 0, x: 20 }}
|
||||||
whileInView={{ opacity: 1, x: 0 }}
|
whileInView={{ opacity: 1, x: 0 }}
|
||||||
|
transition={{ delay: idx * 0.1 }}
|
||||||
viewport={{ once: true }}
|
viewport={{ once: true }}
|
||||||
transition={{ duration: 0.8 }}
|
className="p-6 rounded-xl bg-stone-50 border border-stone-100 hover:border-stone-200 transition-colors"
|
||||||
className="space-y-6"
|
|
||||||
>
|
>
|
||||||
<h3 className="text-3xl font-bold text-white mb-4">My Journey</h3>
|
<div className="flex items-center gap-3 mb-4">
|
||||||
<p className="text-gray-300 leading-relaxed text-lg">
|
<div className="p-2 bg-white rounded-lg shadow-sm text-stone-700">
|
||||||
I'm a student and software engineer based in Osnabrück, Germany.
|
<stack.icon size={20} />
|
||||||
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>
|
</div>
|
||||||
<h4 className="text-lg font-semibold text-white mb-2">{value.title}</h4>
|
<h4 className="font-semibold text-stone-800">{stack.category}</h4>
|
||||||
<p className="text-sm text-gray-400 leading-relaxed">{value.description}</p>
|
|
||||||
</motion.div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</motion.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) => (
|
|
||||||
<motion.div
|
|
||||||
key={skill.category}
|
|
||||||
initial={{ opacity: 0, y: 30 }}
|
|
||||||
whileInView={{ opacity: 1, y: 0 }}
|
|
||||||
viewport={{ once: true }}
|
|
||||||
transition={{ duration: 0.6, delay: index * 0.1 }}
|
|
||||||
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`}>
|
|
||||||
<skill.icon className="w-7 h-7 text-white" />
|
|
||||||
</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>
|
||||||
|
<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>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</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 { 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'm always available to discuss new opportunities, interesting projects,
|
I'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 ? (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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} />
|
||||||
|
|||||||
@@ -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
|
||||||
whileHover={{ scale: 1.05, rotateY: 5 }}
|
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"
|
||||||
transition={{ duration: 0.3 }}
|
animate={{
|
||||||
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"
|
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
|
<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>
|
|
||||||
|
|
||||||
{/* Floating tech badges around the image */}
|
{/* Inner Border/Highlight */}
|
||||||
<motion.div
|
<div className="absolute inset-0 border-[3px] border-white/20 rounded-[inherit] pointer-events-none z-20" />
|
||||||
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={{ opacity: 0, scale: 0 }}
|
initial={{ scale: 0 }}
|
||||||
animate={{ opacity: 1, scale: 1 }}
|
animate={{ scale: 1 }}
|
||||||
transition={{ duration: 0.5, delay: 1.9 }}
|
transition={{ delay: 1.7, type: "spring" }}
|
||||||
className="absolute -top-3 -left-3 w-10 h-10 bg-cyan-500 rounded-full flex items-center justify-center shadow-lg"
|
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>
|
</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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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,185 +18,136 @@ 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'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 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>
|
||||||
|
|
||||||
{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"
|
|
||||||
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} />
|
<Github size={20} />
|
||||||
</motion.a>
|
</a>
|
||||||
)}
|
)}
|
||||||
{project.live && project.live.trim() !== '' && project.live !== '#' && (
|
{project.live && (
|
||||||
<motion.a
|
<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">
|
||||||
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} />
|
<ExternalLink size={20} />
|
||||||
</motion.a>
|
</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}
|
{tag}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
{project.tags.length > 4 && (
|
{project.tags.length > 3 && (
|
||||||
<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">
|
<span className="text-xs px-2 py-1 text-stone-400">+ {project.tags.length - 3}</span>
|
||||||
+{project.tags.length - 4}
|
|
||||||
</span>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
href={`/projects/${project.title.toLowerCase().replace(/[^a-z0-9]+/g, '-')}`}
|
href={`/projects/${project.title.toLowerCase().replace(/\s+/g, '-')}`}
|
||||||
className="inline-flex items-center space-x-2 text-blue-400 hover:text-blue-300 transition-all font-semibold group/link"
|
className="inline-flex items-center text-sm font-semibold text-stone-900 hover:gap-2 transition-all group/link"
|
||||||
>
|
>
|
||||||
<span>View Details</span>
|
Read more <ArrowRight size={16} className="ml-1 transition-transform group-hover/link:translate-x-1" />
|
||||||
<ExternalLink size={16} className="group-hover/link:translate-x-1 transition-transform" />
|
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</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>
|
||||||
);
|
);
|
||||||
|
|||||||
732
app/globals.css
732
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');
|
@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;
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
|
<GooFilter />
|
||||||
|
<BackgroundBlobs />
|
||||||
|
<div className="relative z-10">
|
||||||
{children}
|
{children}
|
||||||
|
</div>
|
||||||
<PerformanceDashboard />
|
<PerformanceDashboard />
|
||||||
</ToastProvider>
|
</ToastProvider>
|
||||||
</AnalyticsProvider>
|
</AnalyticsProvider>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
77
components/BackgroundBlobs.tsx
Normal file
77
components/BackgroundBlobs.tsx
Normal 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
33
components/GooFilter.tsx
Normal 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>
|
||||||
|
);
|
||||||
5
components/LiquidCursor.tsx
Normal file
5
components/LiquidCursor.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
export const LiquidCursor = () => {
|
||||||
|
return null;
|
||||||
|
};
|
||||||
19
components/LiquidHeading.tsx
Normal file
19
components/LiquidHeading.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user