## 🎨 UI/UX Fixes ### Fixed React Hydration Errors - ActivityFeed: Standardized button styling (gradient → solid) - ActivityFeed: Unified icon sizes and spacing for SSR/CSR consistency - ActivityFeed: Added timestamps to chat messages for stable React keys - About: Fixed duplicate keys in tech stack items (added unique key combinations) - Projects: Fixed duplicate keys in project tags (combined projectId + tag + index) ### Fixed Layout Issues - Added spacer after Header component (h-24 md:h-32) to prevent navbar overlap - Hero section now properly visible below fixed navbar ## 🔧 Backend Improvements ### Database Schema - Added ActivityStatus model for real-time activity tracking - Supports: coding activity, music playing, watching, gaming, status/mood - Single-row design (id=1) with auto-updating timestamps ### API Enhancements - Fixed n8n status endpoint to handle missing table gracefully - Added TypeScript interfaces (removed ESLint `any` warnings) - New API: POST /api/n8n/generate-image for AI image generation - New API: GET /api/n8n/generate-image?projectId=X for status check ## 🔐 Security & Auth ### Middleware Updates - Removed premature auth redirect for /manage and /editor routes - Pages now handle their own authentication (show login forms) - Security headers still applied to all routes ## 🤖 New Feature: AI Image Generation System ### Complete automated project cover image generation using local Stable Diffusion **Core Components:** - Admin UI component (AIImageGenerator.tsx) with preview, generate, and regenerate - n8n workflow integration for automation - Context-aware prompt generation based on project metadata - Support for 10+ project categories with optimized prompts **Documentation (6 new files):** - README.md - System overview and features - SETUP.md - Detailed installation guide (486 lines) - QUICKSTART.md - 15-minute quick start - PROMPT_TEMPLATES.md - Category-specific templates (612 lines) - ENVIRONMENT.md - Environment variables reference - n8n-workflow-ai-image-generator.json - Ready-to-import workflow **Database Migration:** - SQL script: create_activity_status.sql - Auto-setup script: quick-fix.sh - Migration guide: prisma/migrations/README.md **Key Features:** ✅ Automatic generation on project creation ✅ Manual regeneration via admin UI ✅ Category-specific prompts (web, mobile, devops, ai, game, etc.) ✅ Local Stable Diffusion (no API costs, privacy-first) ✅ n8n workflow orchestration ✅ Optimized for web (1024x768) ## 📝 Documentation - CHANGELOG_DEV.md - Complete changelog with migration guide - PRE_PUSH_CHECKLIST.md - Pre-push verification checklist - Comprehensive AI image generation docs ## 🐛 Bug Fixes 1. Fixed "Hydration failed" errors in ActivityFeed 2. Fixed "two children with same key" warnings 3. Fixed navbar overlapping hero section 4. Fixed "relation activity_status does not exist" errors 5. Fixed /manage redirect loop (was going to home page) 6. Fixed TypeScript ESLint errors and warnings 7. Fixed duplicate transition prop in Hero component ## ⚠️ Breaking Changes None - All changes are backward compatible ## 🔄 Migration Required Database migration needed for new ActivityStatus table: ```bash ./prisma/migrations/quick-fix.sh # OR psql -d portfolio -f prisma/migrations/create_activity_status.sql ``` ## 📦 Files Changed **Modified (7):** - app/page.tsx - app/components/About.tsx - app/components/Projects.tsx - app/components/ActivityFeed.tsx - app/components/Hero.tsx - app/api/n8n/status/route.ts - middleware.ts - prisma/schema.prisma **Created (14):** - app/api/n8n/generate-image/route.ts - app/components/admin/AIImageGenerator.tsx - docs/ai-image-generation/* (6 files) - prisma/migrations/* (3 files) - CHANGELOG_DEV.md - PRE_PUSH_CHECKLIST.md - COMMIT_MESSAGE.txt ## ✅ Testing - [x] Build successful: npm run build - [x] Linting passed: npm run lint (0 errors, 8 warnings) - [x] No hydration errors in console - [x] No duplicate key warnings - [x] /manage accessible (shows login form) - [x] API endpoints responding correctly - [x] Navbar no longer overlaps content ## 🚀 Next Steps 1. Test AI image generation with Stable Diffusion setup 2. Test n8n workflow integration 3. Create demo screenshots for new features 4. Update main README.md after merge --- Co-authored-by: AI Assistant (Claude Sonnet 4.5)
251 lines
9.7 KiB
TypeScript
251 lines
9.7 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
import { motion } from "framer-motion";
|
|
import { ArrowDown, Code, Zap, Rocket } from "lucide-react";
|
|
import Image from "next/image";
|
|
|
|
const Hero = () => {
|
|
const [mounted, setMounted] = useState(false);
|
|
|
|
useEffect(() => {
|
|
setMounted(true);
|
|
}, []);
|
|
|
|
const features = [
|
|
{ icon: Code, text: "Next.js & Flutter" },
|
|
{ icon: Zap, text: "Docker Swarm & CI/CD" },
|
|
{ icon: Rocket, text: "Self-Hosted Infrastructure" },
|
|
];
|
|
|
|
// Smooth scroll configuration
|
|
const smoothTransition = {
|
|
type: "spring",
|
|
damping: 30,
|
|
stiffness: 50,
|
|
mass: 1,
|
|
};
|
|
|
|
if (!mounted) {
|
|
return null;
|
|
}
|
|
|
|
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">
|
|
<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: 1.2, delay: 0.3, ease: [0.25, 0.1, 0.25, 1] }}
|
|
className="mb-12 flex justify-center relative z-20"
|
|
>
|
|
<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"
|
|
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"
|
|
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"
|
|
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",
|
|
}}
|
|
>
|
|
<Image
|
|
src="/images/me.jpg"
|
|
alt="Dennis Konkol"
|
|
fill
|
|
className="object-cover scale-105 hover:scale-[1.08] transition-transform duration-1000 ease-out"
|
|
priority
|
|
/>
|
|
|
|
{/* 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: 1, delay: 0.8, ease: "easeOut" }}
|
|
className="absolute -bottom-8 left-1/2 -translate-x-1/2 z-30"
|
|
>
|
|
<div className="px-6 py-2.5 rounded-full glass-panel text-stone-700 font-mono text-sm tracking-wider shadow-lg backdrop-blur-xl border border-white/50">
|
|
dk<span className="text-liquid-rose font-bold">0</span>.dev
|
|
</div>
|
|
</motion.div>
|
|
|
|
{/* Floating Badges - subtle animations */}
|
|
<motion.div
|
|
initial={{ scale: 0, opacity: 0 }}
|
|
animate={{ scale: 1, opacity: 1 }}
|
|
transition={{ delay: 1.2, duration: 0.8, ease: "easeOut" }}
|
|
whileHover={{ scale: 1.1, rotate: 5 }}
|
|
className="absolute -top-4 right-0 md:-right-4 p-3 bg-white/95 backdrop-blur-md shadow-lg rounded-full text-stone-700 z-30"
|
|
>
|
|
<Code size={24} />
|
|
</motion.div>
|
|
<motion.div
|
|
initial={{ scale: 0, opacity: 0 }}
|
|
animate={{ scale: 1, opacity: 1 }}
|
|
transition={{ delay: 1.4, duration: 0.8, ease: "easeOut" }}
|
|
whileHover={{ scale: 1.1, rotate: -5 }}
|
|
className="absolute bottom-4 -left-4 md:-left-8 p-3 bg-white/95 backdrop-blur-md shadow-lg rounded-full text-stone-700 z-30"
|
|
>
|
|
<Zap size={24} />
|
|
</motion.div>
|
|
</div>
|
|
</motion.div>
|
|
|
|
{/* Main Title */}
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 1, delay: 0.6, 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 mb-2">
|
|
Dennis Konkol
|
|
</h1>
|
|
<h2 className="text-2xl md:text-4xl font-light tracking-wide text-stone-600 mt-2">
|
|
Software Engineer
|
|
</h2>
|
|
</motion.div>
|
|
|
|
{/* Description */}
|
|
<motion.p
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 1, delay: 0.9, ease: [0.25, 0.1, 0.25, 1] }}
|
|
className="text-lg md:text-xl text-stone-700 mb-12 max-w-2xl mx-auto leading-relaxed"
|
|
>
|
|
Student and passionate{" "}
|
|
<span className="text-stone-900 font-semibold decoration-liquid-mint decoration-2 underline underline-offset-4">
|
|
self-hoster
|
|
</span>{" "}
|
|
building full-stack web apps and mobile solutions. I run my own{" "}
|
|
<span className="text-stone-900 font-semibold decoration-liquid-lavender decoration-2 underline underline-offset-4">
|
|
infrastructure
|
|
</span>{" "}
|
|
and love exploring{" "}
|
|
<span className="text-stone-900 font-semibold decoration-liquid-rose decoration-2 underline underline-offset-4">
|
|
DevOps
|
|
</span>
|
|
.
|
|
</motion.p>
|
|
|
|
{/* Features */}
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 1, delay: 1.1, 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.8,
|
|
delay: 1.3 + index * 0.15,
|
|
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/70 border border-white/90 shadow-sm backdrop-blur-sm"
|
|
>
|
|
<feature.icon className="w-4 h-4 text-stone-700" />
|
|
<span className="text-stone-700 font-medium text-sm">
|
|
{feature.text}
|
|
</span>
|
|
</motion.div>
|
|
))}
|
|
</motion.div>
|
|
|
|
{/* CTA Buttons */}
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 1, delay: 1.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 font-medium shadow-lg hover:shadow-xl hover:bg-stone-950 transition-all duration-500 flex items-center gap-2"
|
|
>
|
|
<span>View My Work</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"
|
|
>
|
|
<span>Contact Me</span>
|
|
</motion.a>
|
|
</motion.div>
|
|
</div>
|
|
</section>
|
|
);
|
|
};
|
|
|
|
export default Hero;
|