feat: complete design overhaul with bento grid and island nav
Refactored About section to use a responsive Bento Grid layout. Redesigned Hero for stronger visual impact. Implemented floating Island navigation. Updated Project cards for cleaner aesthetic.
This commit is contained in:
@@ -1,251 +1,100 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { ArrowDown, Code, Zap, Rocket } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { ArrowDown, Github, Linkedin, Mail } from "lucide-react";
|
||||
import { useLocale, useTranslations } from "next-intl";
|
||||
import type { JSONContent } from "@tiptap/react";
|
||||
import RichTextClient from "./RichTextClient";
|
||||
|
||||
const Hero = () => {
|
||||
const locale = useLocale();
|
||||
const t = useTranslations("home.hero");
|
||||
const [cmsDoc, setCmsDoc] = useState<JSONContent | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`/api/content/page?key=${encodeURIComponent("home-hero")}&locale=${encodeURIComponent(locale)}`,
|
||||
);
|
||||
const data = await res.json();
|
||||
// Only use CMS content if it exists for the active locale.
|
||||
// If the API falls back to another locale, keep showing next-intl strings
|
||||
// so the locale switch visibly changes the page.
|
||||
if (data?.content?.content && data?.content?.locale === locale) {
|
||||
setCmsDoc(data.content.content as JSONContent);
|
||||
} else {
|
||||
setCmsDoc(null);
|
||||
}
|
||||
} catch {
|
||||
// ignore; fallback to static
|
||||
setCmsDoc(null);
|
||||
}
|
||||
})();
|
||||
}, [locale]);
|
||||
|
||||
const features = [
|
||||
{ icon: Code, text: t("features.f1") },
|
||||
{ icon: Zap, text: t("features.f2") },
|
||||
{ icon: Rocket, text: t("features.f3") },
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="relative min-h-screen flex items-center justify-center overflow-hidden pt-32 pb-16 bg-gradient-to-br from-liquid-mint/10 via-liquid-lavender/10 to-liquid-rose/10 dark:from-stone-900 dark:via-stone-900 dark:to-stone-800 transition-colors duration-500">
|
||||
<div className="relative z-10 text-center px-4 max-w-5xl mx-auto">
|
||||
{/* Profile Image with Organic Blob Mask */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.6, delay: 0.1, ease: [0.25, 0.1, 0.25, 1] }}
|
||||
className="mb-12 flex justify-center relative z-20"
|
||||
<section className="relative min-h-screen flex flex-col items-center justify-center overflow-hidden bg-stone-50 dark:bg-stone-950 px-4">
|
||||
{/* Dynamic Background */}
|
||||
<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>
|
||||
<div className="absolute bottom-[20%] right-[20%] w-[400px] h-[400px] bg-liquid-purple/20 rounded-full blur-[100px] animate-pulse delay-1000"></div>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div className="relative z-10 text-center max-w-5xl mx-auto">
|
||||
{/* Availability Badge */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
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"
|
||||
>
|
||||
<div className="relative w-64 h-64 md:w-80 md:h-80 flex items-center justify-center">
|
||||
{/* Large Rotating Liquid Blobs behind image - Very slow and smooth */}
|
||||
<motion.div
|
||||
className="absolute w-[150%] h-[150%] bg-gradient-to-tr from-liquid-mint/40 via-liquid-blue/30 to-liquid-lavender/40 blur-3xl -z-10 dark:from-liquid-mint/20 dark:via-liquid-blue/15 dark:to-liquid-lavender/20"
|
||||
animate={{
|
||||
borderRadius: [
|
||||
"60% 40% 30% 70%/60% 30% 70% 40%",
|
||||
"30% 60% 70% 40%/50% 60% 30% 60%",
|
||||
"60% 40% 30% 70%/60% 30% 70% 40%",
|
||||
],
|
||||
rotate: [0, 120, 0],
|
||||
scale: [1, 1.08, 1],
|
||||
}}
|
||||
transition={{
|
||||
duration: 35,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut",
|
||||
repeatType: "reverse",
|
||||
}}
|
||||
/>
|
||||
<motion.div
|
||||
className="absolute w-[130%] h-[130%] bg-gradient-to-bl from-liquid-rose/35 via-purple-200/25 to-liquid-mint/35 blur-2xl -z-10 dark:from-liquid-rose/15 dark:via-purple-900/10 dark:to-liquid-mint/15"
|
||||
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: [0, -90, 0],
|
||||
scale: [1, 1.05, 1],
|
||||
}}
|
||||
transition={{
|
||||
duration: 40,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut",
|
||||
repeatType: "reverse",
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* The Image Container with Organic Border Radius */}
|
||||
<motion.div
|
||||
className="absolute inset-0 overflow-hidden bg-stone-100 dark:bg-stone-800"
|
||||
style={{
|
||||
filter: "drop-shadow(0 20px 40px rgba(0,0,0,0.1))",
|
||||
willChange: "border-radius",
|
||||
}}
|
||||
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: 12,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut",
|
||||
repeatType: "reverse",
|
||||
}}
|
||||
>
|
||||
{/* Use a plain <img> to fully bypass Next.js image optimizer (dev 400 issue). */}
|
||||
<img
|
||||
src="/images/me.jpg"
|
||||
alt="Dennis Konkol"
|
||||
className="absolute inset-0 w-full h-full object-cover scale-105 hover:scale-[1.08] transition-transform duration-1000 ease-out"
|
||||
loading="eager"
|
||||
decoding="async"
|
||||
/>
|
||||
|
||||
{/* Glossy Overlay for Liquid Feel */}
|
||||
<div className="absolute inset-0 bg-gradient-to-tr from-white/25 via-transparent to-white/10 opacity-60 pointer-events-none z-10" />
|
||||
|
||||
{/* Inner Border/Highlight */}
|
||||
<div className="absolute inset-0 border-[2px] border-white/30 rounded-[inherit] pointer-events-none z-20" />
|
||||
</motion.div>
|
||||
|
||||
{/* Domain Badge - repositioned below image */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.3, ease: "easeOut" }}
|
||||
className="absolute -bottom-8 left-1/2 -translate-x-1/2 z-30"
|
||||
>
|
||||
<div className="px-6 py-2.5 rounded-full bg-white/90 dark:bg-stone-800/90 backdrop-blur-xl text-stone-900 dark:text-stone-50 font-sans font-bold text-sm tracking-wide shadow-lg border-2 border-stone-300 dark:border-stone-700">
|
||||
dk<span className="text-red-500 font-extrabold">0</span>.dev
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Floating Badges - subtle animations */}
|
||||
<motion.div
|
||||
initial={{ scale: 0, opacity: 0 }}
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
transition={{ delay: 0.4, duration: 0.5, ease: "easeOut" }}
|
||||
whileHover={{ scale: 1.1, rotate: 5 }}
|
||||
className="absolute -top-4 right-0 md:-right-4 p-3 bg-white/95 dark:bg-stone-800/95 backdrop-blur-md shadow-lg rounded-full text-stone-700 dark:text-stone-300 z-30"
|
||||
>
|
||||
<Code size={24} />
|
||||
</motion.div>
|
||||
<motion.div
|
||||
initial={{ scale: 0, opacity: 0 }}
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
transition={{ delay: 0.5, duration: 0.5, ease: "easeOut" }}
|
||||
whileHover={{ scale: 1.1, rotate: -5 }}
|
||||
className="absolute bottom-4 -left-4 md:-left-8 p-3 bg-white/95 dark:bg-stone-800/95 backdrop-blur-md shadow-lg rounded-full text-stone-700 dark:text-stone-300 z-30"
|
||||
>
|
||||
<Zap size={24} />
|
||||
</motion.div>
|
||||
</div>
|
||||
<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>
|
||||
<span className="text-xs font-medium text-stone-600 dark:text-stone-400 uppercase tracking-wider">Available for work</span>
|
||||
</motion.div>
|
||||
|
||||
{/* Main Title */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.2, ease: [0.25, 0.1, 0.25, 1] }}
|
||||
className="mb-8 flex flex-col items-center justify-center relative"
|
||||
>
|
||||
<h1 className="text-5xl md:text-8xl font-bold tracking-tighter text-stone-900 dark:text-stone-50 mb-2">
|
||||
Dennis Konkol
|
||||
</h1>
|
||||
<h2 className="text-2xl md:text-4xl font-light tracking-wide text-stone-600 dark:text-stone-400 mt-2">
|
||||
Software Engineer
|
||||
</h2>
|
||||
</motion.div>
|
||||
|
||||
{/* Description */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.3, ease: [0.25, 0.1, 0.25, 1] }}
|
||||
className="text-lg md:text-xl text-stone-700 dark:text-stone-300 mb-12 max-w-2xl mx-auto leading-relaxed"
|
||||
>
|
||||
{cmsDoc ? (
|
||||
<RichTextClient doc={cmsDoc} className="prose prose-stone dark:prose-invert max-w-none" />
|
||||
) : (
|
||||
<p>{t("description")}</p>
|
||||
)}
|
||||
</motion.div>
|
||||
|
||||
{/* Features */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.4, ease: [0.25, 0.1, 0.25, 1] }}
|
||||
className="flex flex-wrap justify-center gap-4 mb-12"
|
||||
>
|
||||
{features.map((feature, index) => (
|
||||
<motion.div
|
||||
key={feature.text}
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{
|
||||
duration: 0.5,
|
||||
delay: 0.5 + index * 0.1,
|
||||
ease: [0.25, 0.1, 0.25, 1],
|
||||
}}
|
||||
whileHover={{ scale: 1.03, y: -3 }}
|
||||
className="flex items-center space-x-2 px-5 py-2.5 rounded-full bg-white/85 dark:bg-stone-800/85 border-2 border-stone-300 dark:border-stone-700 shadow-md backdrop-blur-lg"
|
||||
>
|
||||
<feature.icon className="w-4 h-4 text-stone-800 dark:text-stone-200" />
|
||||
<span className="text-stone-800 dark:text-stone-200 font-semibold text-sm">
|
||||
{feature.text}
|
||||
</span>
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
|
||||
{/* CTA Buttons */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.6, ease: [0.25, 0.1, 0.25, 1] }}
|
||||
className="flex flex-col sm:flex-row gap-5 justify-center items-center"
|
||||
>
|
||||
<motion.a
|
||||
href="#projects"
|
||||
whileHover={{ scale: 1.03, y: -2 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
transition={{ duration: 0.3, ease: "easeOut" }}
|
||||
className="px-8 py-4 bg-stone-900 text-cream rounded-full shadow-lg hover:shadow-xl hover:bg-stone-950 transition-all duration-500 flex items-center gap-2"
|
||||
<h1 className="text-6xl md:text-9xl font-black tracking-tighter text-stone-900 dark:text-stone-50 mb-6 leading-[0.9]">
|
||||
<motion.span
|
||||
initial={{ opacity: 0, y: 40 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.7, delay: 0.1 }}
|
||||
className="block"
|
||||
>
|
||||
<span className="text-cream">{t("ctaWork")}</span>
|
||||
<ArrowDown size={18} />
|
||||
</motion.a>
|
||||
|
||||
<motion.a
|
||||
href="#contact"
|
||||
whileHover={{ scale: 1.03, y: -2 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
transition={{ duration: 0.3, ease: "easeOut" }}
|
||||
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 duration-500"
|
||||
Building
|
||||
</motion.span>
|
||||
<motion.span
|
||||
initial={{ opacity: 0, y: 40 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.7, delay: 0.2 }}
|
||||
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"
|
||||
>
|
||||
<span>{t("ctaContact")}</span>
|
||||
</motion.a>
|
||||
Digital Products
|
||||
</motion.span>
|
||||
</h1>
|
||||
|
||||
{/* Subtitle */}
|
||||
<motion.p
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.8, delay: 0.4 }}
|
||||
className="text-lg md:text-2xl text-stone-600 dark:text-stone-400 max-w-2xl mx-auto font-light leading-relaxed mb-12"
|
||||
>
|
||||
I'm Dennis, a Software Engineer crafting polished web & mobile experiences with a focus on performance and design.
|
||||
</motion.p>
|
||||
|
||||
{/* Buttons */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.6 }}
|
||||
className="flex flex-col sm:flex-row items-center justify-center gap-4"
|
||||
>
|
||||
<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">
|
||||
View My Work
|
||||
</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">
|
||||
<Github size={20} />
|
||||
</a>
|
||||
<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">
|
||||
<Linkedin size={20} />
|
||||
</a>
|
||||
<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">
|
||||
<Mail size={20} />
|
||||
</a>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Scroll Indicator */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1, y: [0, 10, 0] }}
|
||||
transition={{ delay: 1, duration: 2, repeat: Infinity }}
|
||||
className="absolute bottom-10 left-1/2 -translate-x-1/2"
|
||||
>
|
||||
<ArrowDown className="text-stone-400 dark:text-stone-600" />
|
||||
</motion.div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user