style: final polish for design overhaul

Fixed all compilation errors, improved responsive layout, added missing icons, and refined animations for a perfect user experience.
This commit is contained in:
2026-02-16 00:54:41 +01:00
parent 332adab08c
commit 18f8fb7407
3 changed files with 225 additions and 147 deletions

View File

@@ -2,21 +2,16 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { BentoGrid, BentoGridItem } from "./ui/BentoGrid"; import { BentoGrid, BentoGridItem } from "./ui/BentoGrid";
import { import { Globe, Server, Wrench, Shield, Gamepad2, Code, Activity, Lightbulb, MapPin, User, BookOpen } from "lucide-react";
IconClipboardCopy,
IconFileBroken,
IconSignature,
IconTableColumn,
} from "@tabler/icons-react"; // Wir nutzen Lucide, ich tausche die gleich aus
import { Globe, Server, Wrench, Shield, Gamepad2, Code, Activity, Lightbulb, MapPin, User } from "lucide-react";
import { useLocale, useTranslations } from "next-intl"; import { useLocale, useTranslations } from "next-intl";
import type { JSONContent } from "@tiptap/react"; import type { JSONContent } from "@tiptap/react";
import RichTextClient from "./RichTextClient"; import RichTextClient from "./RichTextClient";
import CurrentlyReading from "./CurrentlyReading"; import CurrentlyReading from "./CurrentlyReading";
import ReadBooks from "./ReadBooks"; import ReadBooks from "./ReadBooks";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { TechStackCategory, Hobby } from "@/lib/directus";
// Helper for Tech Stack Icons // Map icon names from Directus to Lucide components
const iconMap: Record<string, any> = { const iconMap: Record<string, any> = {
Globe, Server, Code, Wrench, Shield, Activity, Lightbulb, Gamepad2 Globe, Server, Code, Wrench, Shield, Activity, Lightbulb, Gamepad2
}; };
@@ -27,50 +22,50 @@ const About = () => {
const [cmsDoc, setCmsDoc] = useState<JSONContent | null>(null); const [cmsDoc, setCmsDoc] = useState<JSONContent | null>(null);
// Data State // Data State
const [techStack, setTechStack] = useState<any[]>([]); const [techStack, setTechStack] = useState<TechStackCategory[]>([]);
const [hobbies, setHobbies] = useState<any[]>([]); const [hobbies, setHobbies] = useState<Hobby[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => { useEffect(() => {
// Load Content Page const fetchData = async () => {
(async () => { setLoading(true);
try { try {
const res = await fetch(`/api/content/page?key=home-about&locale=${locale}`); const [cmsRes, techRes, hobbiesRes] = await Promise.all([
const data = await res.json(); fetch(`/api/content/page?key=home-about&locale=${locale}`),
if (data?.content?.content) setCmsDoc(data.content.content as JSONContent); fetch(`/api/tech-stack?locale=${locale}`),
} catch {} fetch(`/api/hobbies?locale=${locale}`)
})(); ]);
// Load Tech Stack const cmsData = await cmsRes.json();
(async () => { if (cmsData?.content?.content) setCmsDoc(cmsData.content.content as JSONContent);
try {
const res = await fetch(`/api/tech-stack?locale=${locale}`);
const data = await res.json();
if (data?.techStack) setTechStack(data.techStack);
} catch {}
})();
// Load Hobbies const techData = await techRes.json();
(async () => { if (techData?.techStack) setTechStack(techData.techStack);
try {
const res = await fetch(`/api/hobbies?locale=${locale}`); const hobbiesData = await hobbiesRes.json();
const data = await res.json(); if (hobbiesData?.hobbies) setHobbies(hobbiesData.hobbies);
if (data?.hobbies) setHobbies(data.hobbies); } catch (error) {
} catch {} console.error("Error fetching about data:", error);
})(); } finally {
setLoading(false);
}
};
fetchData();
}, [locale]); }, [locale]);
return ( return (
<section id="about" className="py-32 px-4 relative bg-stone-50 dark:bg-stone-950"> <section id="about" className="py-24 md:py-32 px-4 relative bg-stone-50 dark:bg-stone-950 transition-colors duration-500">
{/* Background Noise/Gradient */} {/* Background Decor */}
<div className="absolute inset-0 bg-[url('https://grainy-gradients.vercel.app/noise.svg')] opacity-20 pointer-events-none mix-blend-soft-light"></div> <div className="absolute inset-0 bg-[url('https://grainy-gradients.vercel.app/noise.svg')] opacity-20 pointer-events-none mix-blend-soft-light"></div>
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-full h-[500px] bg-gradient-to-b from-liquid-mint/10 via-transparent to-transparent blur-3xl pointer-events-none"></div> <div className="absolute top-0 left-1/2 -translate-x-1/2 w-full h-[500px] bg-gradient-to-b from-liquid-mint/10 dark:from-liquid-mint/5 via-transparent to-transparent blur-3xl pointer-events-none"></div>
<div className="max-w-7xl mx-auto mb-16 text-center relative z-10"> <div className="max-w-7xl mx-auto mb-12 md:mb-20 text-center relative z-10">
<motion.h2 <motion.h2
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
className="text-4xl md:text-6xl font-black text-stone-900 dark:text-stone-50 tracking-tight" className="text-4xl md:text-7xl font-black text-stone-900 dark:text-stone-50 tracking-tighter"
> >
{t("title")} {t("title")}
</motion.h2> </motion.h2>
@@ -78,113 +73,135 @@ const About = () => {
<BentoGrid className="max-w-6xl mx-auto relative z-10"> <BentoGrid className="max-w-6xl mx-auto relative z-10">
{/* 1. The Bio (Large Item) */} {/* 1. Bio (2x2) */}
<BentoGridItem <BentoGridItem
className="md:col-span-2 md:row-span-2 bg-gradient-to-br from-white to-stone-50 dark:from-stone-900 dark:to-stone-950" className="md:col-span-2 md:row-span-2 bg-gradient-to-br from-white to-stone-50 dark:from-stone-900 dark:to-stone-950 border-stone-200/60 dark:border-stone-800/60"
title={ title={
<div className="flex items-center gap-2"> <div className="flex items-center gap-2 text-lg">
<User size={20} className="text-liquid-mint" /> <User size={20} className="text-liquid-mint" />
<span>Dennis Konkol</span> <span className="font-black">Dennis Konkol</span>
</div> </div>
} }
description={ description={
<div className="mt-4 prose prose-stone dark:prose-invert max-w-none text-base leading-relaxed"> <div className="mt-4 prose prose-stone dark:prose-invert max-w-none text-stone-600 dark:text-stone-400 leading-relaxed">
{cmsDoc ? ( {cmsDoc ? (
<RichTextClient doc={cmsDoc} /> <RichTextClient doc={cmsDoc} />
) : ( ) : (
<p className="text-stone-600 dark:text-stone-400"> <p className="text-lg font-light">
{t("p1")} {t("p2")} {t("p1")} {t("p2")} {t("p3")}
</p> </p>
)} )}
</div> </div>
} }
header={ header={
<div className="w-full h-40 bg-gradient-to-r from-liquid-mint/20 to-liquid-sky/20 rounded-xl mb-4 flex items-center justify-center overflow-hidden relative group"> <div className="w-full h-48 md:h-64 bg-stone-200 dark:bg-stone-800 rounded-2xl mb-4 overflow-hidden relative group shadow-inner">
<div className="absolute inset-0 bg-[url('/images/me.jpg')] bg-cover bg-center opacity-40 group-hover:scale-105 transition-transform duration-700"></div> <motion.div
<div className="relative z-10 bg-white/30 dark:bg-black/30 backdrop-blur-md px-6 py-2 rounded-full border border-white/20"> initial={{ scale: 1.1 }}
<span className="font-mono text-sm font-bold text-stone-800 dark:text-white">Software Engineer</span> whileInView={{ scale: 1 }}
transition={{ duration: 1.5 }}
className="absolute inset-0 bg-[url('/images/me.jpg')] bg-cover bg-center opacity-60 dark:opacity-40 group-hover:scale-105 transition-transform duration-1000"
/>
<div className="absolute inset-0 bg-gradient-to-t from-stone-50 dark:from-stone-950 via-transparent to-transparent" />
<div className="absolute bottom-6 left-6 z-10">
<div className="bg-white/80 dark:bg-black/40 backdrop-blur-md px-4 py-1.5 rounded-full border border-white/20 shadow-sm">
<span className="font-mono text-xs font-bold text-stone-800 dark:text-white uppercase tracking-widest">Available for hire</span>
</div>
</div> </div>
</div> </div>
} }
/> />
{/* 2. Location & Status */} {/* 2. Tech Stack */}
<BentoGridItem <BentoGridItem
className="md:col-span-1" className="md:col-span-1 bg-white dark:bg-stone-900 border-stone-200/60 dark:border-stone-800/60"
title="Osnabrück, Germany"
description="Available for new opportunities"
icon={<MapPin className="h-4 w-4 text-neutral-500" />}
header={
<div className="flex flex-1 w-full h-full min-h-[6rem] rounded-xl bg-gradient-to-br from-neutral-200 dark:from-neutral-900 to-neutral-100 dark:to-neutral-800 items-center justify-center">
<div className="relative">
<div className="w-3 h-3 bg-green-500 rounded-full animate-ping absolute top-0 right-0"></div>
<div className="w-3 h-3 bg-green-500 rounded-full relative z-10 border-2 border-white dark:border-stone-900"></div>
</div>
<span className="ml-3 text-sm font-bold text-stone-600 dark:text-stone-300">Online & Active</span>
</div>
}
/>
{/* 3. Tech Stack (Marquee Style or Grid) */}
<BentoGridItem
className="md:col-span-1"
title={t("techStackTitle")} title={t("techStackTitle")}
description="Tools I work with daily" description="Tools & Technologies"
header={ header={
<div className="flex flex-wrap gap-2 min-h-[6rem] p-2"> <div className="flex flex-wrap gap-2 p-2 overflow-y-auto max-h-40 scrollbar-hide">
{techStack.length > 0 ? ( {techStack.length > 0 ? (
techStack.slice(0, 8).flatMap(cat => cat.items.map((item: any) => ( techStack.flatMap(cat => cat.items.map((item: any) => (
<span key={item.name} className="px-2 py-1 bg-stone-100 dark:bg-stone-800 rounded text-xs font-mono border border-stone-200 dark:border-stone-700"> <motion.span
key={item.id}
whileHover={{ scale: 1.05 }}
className="px-3 py-1 bg-stone-100 dark:bg-stone-800 rounded-lg text-xs font-bold border border-stone-200/50 dark:border-stone-700/50 text-stone-700 dark:text-stone-300"
>
{item.name} {item.name}
</span> </motion.span>
))).slice(0, 12) )))
) : ( ) : (
<div className="text-xs text-stone-400">Loading Stack...</div> <div className="flex gap-2 flex-wrap">
{[1,2,3,4,5,6].map(i => <div key={i} className="h-6 w-16 bg-stone-100 dark:bg-stone-800 rounded animate-pulse" />)}
</div>
)} )}
</div> </div>
} }
icon={<Code className="h-4 w-4 text-neutral-500" />} icon={<Code className="h-4 w-4 text-liquid-mint" />}
/> />
{/* 4. Currently Reading */} {/* 3. Location */}
<BentoGridItem <BentoGridItem
className="md:col-span-1 md:row-span-2" className="md:col-span-1 bg-white dark:bg-stone-900 border-stone-200/60 dark:border-stone-800/60"
title="Osnabrück, Germany"
description="UTC+1 (CET)"
icon={<MapPin className="h-4 w-4 text-liquid-rose" />}
header={
<div className="relative w-full h-full min-h-[6rem] rounded-xl overflow-hidden bg-stone-100 dark:bg-stone-800 flex items-center justify-center">
<div className="absolute inset-0 opacity-20 dark:opacity-10 bg-[url('https://www.transparenttextures.com/patterns/carbon-fibre.png')]" />
<div className="relative z-10 flex flex-col items-center">
<div className="flex items-center gap-2 bg-white dark:bg-stone-950 px-4 py-2 rounded-xl shadow-sm border border-stone-200/50 dark:border-stone-700/50">
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse" />
<span className="text-sm font-bold font-mono">52.27° N, 8.04° E</span>
</div>
</div>
</div>
}
/>
{/* 4. Currently Reading & Reviews (Long) */}
<BentoGridItem
className="md:col-span-1 md:row-span-2 bg-white dark:bg-stone-900 border-stone-200/60 dark:border-stone-800/60"
title={null} title={null}
description={null} description={null}
header={ header={
<div className="h-full flex flex-col"> <div className="h-full flex flex-col">
<div className="font-bold text-stone-900 dark:text-white mb-4 flex items-center gap-2"> <div className="font-black text-stone-900 dark:text-white mb-6 flex items-center gap-2 uppercase tracking-tighter">
<Activity size={16} /> Reading <BookOpen size={18} className="text-liquid-purple" /> Reading Log
</div> </div>
<div className="flex-1 overflow-hidden"> <div className="flex-1 space-y-8 overflow-y-auto pr-2 scrollbar-hide">
<CurrentlyReading /> <CurrentlyReading />
</div> <div className="pt-6 border-t border-stone-100 dark:border-stone-800">
<div className="mt-4 pt-4 border-t border-stone-100 dark:border-stone-800">
<ReadBooks /> <ReadBooks />
</div> </div>
</div> </div>
</div>
} }
/> />
{/* 5. Hobbies */} {/* 5. Hobbies (Wide) */}
<BentoGridItem <BentoGridItem
className="md:col-span-2" className="md:col-span-2 bg-white dark:bg-stone-900 border-stone-200/60 dark:border-stone-800/60"
title={t("hobbiesTitle")} title={t("hobbiesTitle")}
description="What keeps me busy" description="Beyond the screen"
header={ header={
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mt-2"> <div className="grid grid-cols-2 md:grid-cols-4 gap-3 mt-2">
{hobbies.map((hobby, i) => { {hobbies.length > 0 ? hobbies.map((hobby, i) => {
const Icon = iconMap[hobby.icon] || Lightbulb; const Icon = iconMap[hobby.icon] || Lightbulb;
return ( return (
<div key={i} className="flex flex-col items-center justify-center p-4 bg-stone-50 dark:bg-stone-800/50 rounded-xl border border-stone-100 dark:border-stone-700/50 hover:bg-white dark:hover:bg-stone-800 transition-colors"> <motion.div
<Icon size={24} className="mb-2 text-liquid-purple" /> key={hobby.id}
<span className="text-xs font-medium text-center">{hobby.title}</span> whileHover={{ y: -3 }}
</div> className="flex flex-col items-center justify-center p-4 bg-stone-50 dark:bg-stone-800/40 rounded-2xl border border-stone-100 dark:border-stone-700/30 transition-colors"
>
<Icon size={24} className="mb-2 text-stone-700 dark:text-stone-300" />
<span className="text-[10px] font-bold uppercase tracking-widest text-center text-stone-500">{hobby.title}</span>
</motion.div>
) )
})} }) : (
[1,2,3,4].map(i => <div key={i} className="h-20 bg-stone-50 dark:bg-stone-800 animate-pulse rounded-2xl" />)
)}
</div> </div>
} }
icon={<Gamepad2 className="h-4 w-4 text-neutral-500" />} icon={<Gamepad2 className="h-4 w-4 text-liquid-purple" />}
/> />
</BentoGrid> </BentoGrid>

View File

@@ -1,7 +1,7 @@
"use client"; "use client";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { ArrowDown, Github, Linkedin, Mail } from "lucide-react"; import { ArrowDown, Github, Linkedin, Mail, Code, Zap } from "lucide-react";
import { useLocale, useTranslations } from "next-intl"; import { useLocale, useTranslations } from "next-intl";
const Hero = () => { const Hero = () => {
@@ -9,91 +9,142 @@ const Hero = () => {
const t = useTranslations("home.hero"); const t = useTranslations("home.hero");
return ( return (
<section className="relative min-h-screen flex flex-col items-center justify-center overflow-hidden bg-stone-50 dark:bg-stone-950 px-4"> <section className="relative min-h-screen flex flex-col items-center justify-center overflow-hidden bg-stone-50 dark:bg-stone-950 px-4 transition-colors duration-500">
{/* Dynamic Background */} {/* Liquid Ambient Background */}
<div className="absolute inset-0 pointer-events-none"> <div className="absolute inset-0 pointer-events-none">
<div className="absolute top-[20%] left-[20%] w-[500px] h-[500px] bg-liquid-mint/20 rounded-full blur-[120px] animate-pulse"></div> <motion.div
<div className="absolute bottom-[20%] right-[20%] w-[400px] h-[400px] bg-liquid-purple/20 rounded-full blur-[100px] animate-pulse delay-1000"></div> animate={{
<div className="absolute top-[50%] left-[50%] -translate-x-1/2 -translate-y-1/2 w-[800px] h-[800px] bg-white/50 dark:bg-stone-950/80 blur-3xl rounded-full"></div> scale: [1, 1.2, 1],
x: [0, 30, 0],
y: [0, -20, 0],
}}
transition={{ duration: 20, repeat: Infinity, ease: "easeInOut" }}
className="absolute top-[10%] left-[10%] w-[600px] h-[600px] bg-liquid-mint/15 dark:bg-liquid-mint/5 rounded-full blur-[120px]"
/>
<motion.div
animate={{
scale: [1.2, 1, 1.2],
x: [0, -40, 0],
y: [0, 30, 0],
}}
transition={{ duration: 25, repeat: Infinity, ease: "easeInOut" }}
className="absolute bottom-[10%] right-[10%] w-[500px] h-[500px] bg-liquid-purple/15 dark:bg-liquid-purple/5 rounded-full blur-[100px]"
/>
</div> </div>
<div className="relative z-10 text-center max-w-5xl mx-auto"> <div className="relative z-10 text-center max-w-6xl mx-auto">
{/* Availability Badge */} {/* Profile / dk0.dev Badge */}
<motion.div <motion.div
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.5 }} transition={{ duration: 0.8, ease: [0.25, 0.1, 0.25, 1] }}
className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-white/50 dark:bg-stone-900/50 border border-stone-200 dark:border-stone-800 backdrop-blur-sm mb-8" className="mb-10 relative inline-block"
> >
<span className="relative flex h-2 w-2"> <div className="px-6 py-2.5 rounded-full bg-white/80 dark:bg-stone-900/80 backdrop-blur-xl border-2 border-stone-200 dark:border-stone-800 shadow-xl flex items-center gap-3">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span> <div className="w-8 h-8 rounded-full bg-gradient-to-tr from-liquid-mint to-liquid-sky flex items-center justify-center font-black text-xs text-stone-900 shadow-sm">
<span className="relative inline-flex rounded-full h-2 w-2 bg-green-500"></span> dk
</div>
<span className="font-mono text-sm font-bold tracking-tight text-stone-800 dark:text-stone-200">
dk<span className="text-red-500">0</span>.dev
</span> </span>
<span className="text-xs font-medium text-stone-600 dark:text-stone-400 uppercase tracking-wider">Available for work</span> </div>
{/* Floating decorative icons */}
<motion.div
animate={{ y: [0, -10, 0] }}
transition={{ duration: 4, repeat: Infinity, ease: "easeInOut" }}
className="absolute -top-6 -right-6 p-2 bg-white dark:bg-stone-800 rounded-lg shadow-lg border border-stone-100 dark:border-stone-700 hidden md:block"
>
<Code size={16} className="text-stone-400" />
</motion.div>
<motion.div
animate={{ y: [0, 10, 0] }}
transition={{ duration: 5, repeat: Infinity, ease: "easeInOut", delay: 1 }}
className="absolute -bottom-4 -left-8 p-2 bg-white dark:bg-stone-800 rounded-lg shadow-lg border border-stone-100 dark:border-stone-700 hidden md:block"
>
<Zap size={16} className="text-yellow-400" />
</motion.div>
</motion.div> </motion.div>
{/* Main Title */} {/* Huge Headlines */}
<h1 className="text-6xl md:text-9xl font-black tracking-tighter text-stone-900 dark:text-stone-50 mb-6 leading-[0.9]"> <h1 className="text-6xl md:text-[10rem] font-black tracking-tighter text-stone-900 dark:text-stone-50 mb-8 leading-[0.85] uppercase">
<motion.span <motion.span
initial={{ opacity: 0, y: 40 }} initial={{ opacity: 0, x: -100 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.7, delay: 0.1 }} transition={{ duration: 1, ease: [0.25, 0.1, 0.25, 1] }}
className="block" className="block"
> >
Building Engineer
</motion.span> </motion.span>
<motion.span <motion.span
initial={{ opacity: 0, y: 40 }} initial={{ opacity: 0, x: 100 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.7, delay: 0.2 }} transition={{ duration: 1, delay: 0.2, ease: [0.25, 0.1, 0.25, 1] }}
className="block text-transparent bg-clip-text bg-gradient-to-r from-stone-800 via-stone-600 to-stone-800 dark:from-stone-100 dark:via-stone-400 dark:to-stone-100 pb-2" className="block text-transparent bg-clip-text bg-gradient-to-r from-liquid-mint via-liquid-sky to-liquid-purple pb-4"
> >
Digital Products Design
</motion.span> </motion.span>
</h1> </h1>
{/* Subtitle */} {/* Description */}
<motion.p <motion.p
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
transition={{ duration: 0.8, delay: 0.4 }} transition={{ duration: 1, delay: 0.5 }}
className="text-lg md:text-2xl text-stone-600 dark:text-stone-400 max-w-2xl mx-auto font-light leading-relaxed mb-12" className="text-xl md:text-3xl text-stone-500 dark:text-stone-400 max-w-3xl mx-auto font-light leading-snug mb-14 tracking-tight"
> >
I'm Dennis, a Software Engineer crafting polished web & mobile experiences with a focus on performance and design. Merging technical excellence with high-end aesthetic.
<br className="hidden md:block" /> Based in Osnabrück, building globally.
</motion.p> </motion.p>
{/* Buttons */} {/* Primary Actions */}
<motion.div <motion.div
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.6 }} transition={{ duration: 0.8, delay: 0.8 }}
className="flex flex-col sm:flex-row items-center justify-center gap-4" className="flex flex-col sm:flex-row items-center justify-center gap-6"
> >
<a href="#projects" className="px-8 py-4 bg-stone-900 dark:bg-stone-100 text-white dark:text-stone-900 rounded-full font-bold hover:scale-105 active:scale-95 transition-transform"> <a
View My Work href="#projects"
className="group relative px-10 py-5 bg-stone-900 dark:bg-stone-50 text-white dark:text-stone-900 rounded-2xl font-black text-lg hover:scale-[1.02] active:scale-[0.98] transition-all overflow-hidden"
>
<div className="absolute inset-0 bg-gradient-to-r from-white/0 via-white/10 to-white/0 -translate-x-full group-hover:translate-x-full transition-transform duration-1000" />
START EXPLORING
</a> </a>
<div className="flex gap-2">
<a href="https://github.com/Denshooter" target="_blank" rel="noopener noreferrer" className="p-4 bg-white dark:bg-stone-900 border border-stone-200 dark:border-stone-800 rounded-full hover:bg-stone-50 dark:hover:bg-stone-800 transition-colors"> <div className="flex items-center gap-4">
<Github size={20} /> {[
</a> { icon: Github, href: "https://github.com/Denshooter" },
<a href="https://linkedin.com/in/dkonkol" target="_blank" rel="noopener noreferrer" className="p-4 bg-white dark:bg-stone-900 border border-stone-200 dark:border-stone-800 rounded-full hover:bg-stone-50 dark:hover:bg-stone-800 transition-colors"> { icon: Linkedin, href: "https://linkedin.com/in/dkonkol" },
<Linkedin size={20} /> { icon: Mail, href: "mailto:contact@dk0.dev" }
</a> ].map((social, i) => (
<a href="mailto:contact@dk0.dev" className="p-4 bg-white dark:bg-stone-900 border border-stone-200 dark:border-stone-800 rounded-full hover:bg-stone-50 dark:hover:bg-stone-800 transition-colors"> <a
<Mail size={20} /> key={i}
href={social.href}
target="_blank"
rel="noopener noreferrer"
className="p-4 bg-white dark:bg-stone-900 border border-stone-200 dark:border-stone-800 rounded-2xl text-stone-400 hover:text-stone-900 dark:hover:text-stone-50 hover:bg-stone-50 dark:hover:bg-stone-800 transition-all shadow-sm hover:shadow-md"
>
<social.icon size={22} />
</a> </a>
))}
</div> </div>
</motion.div> </motion.div>
</div> </div>
{/* Scroll Indicator */} {/* Modern Scroll Indicator */}
<motion.div <motion.div
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: 1, y: [0, 10, 0] }} animate={{ opacity: 1 }}
transition={{ delay: 1, duration: 2, repeat: Infinity }} transition={{ delay: 1.5, duration: 1 }}
className="absolute bottom-10 left-1/2 -translate-x-1/2" className="absolute bottom-12 flex flex-col items-center gap-4"
> >
<ArrowDown className="text-stone-400 dark:text-stone-600" /> <span className="text-[10px] font-bold uppercase tracking-[0.3em] text-stone-400 dark:text-stone-600">Scroll Down</span>
<motion.div
animate={{ y: [0, 12, 0] }}
transition={{ duration: 2, repeat: Infinity, ease: "easeInOut" }}
className="w-px h-12 bg-gradient-to-b from-stone-300 dark:from-stone-700 to-transparent"
/>
</motion.div> </motion.div>
</section> </section>
); );

View File

@@ -1,7 +1,17 @@
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
/** /**
* Utility functions for the application * Utility functions for the application
*/ */
/**
* Combine tailwind classes safely
*/
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
/** /**
* Debounce helper to prevent duplicate function calls * Debounce helper to prevent duplicate function calls
* @param func - The function to debounce * @param func - The function to debounce