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.
196 lines
7.7 KiB
TypeScript
196 lines
7.7 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
import { BentoGrid, BentoGridItem } from "./ui/BentoGrid";
|
|
import {
|
|
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 type { JSONContent } from "@tiptap/react";
|
|
import RichTextClient from "./RichTextClient";
|
|
import CurrentlyReading from "./CurrentlyReading";
|
|
import ReadBooks from "./ReadBooks";
|
|
import { motion } from "framer-motion";
|
|
|
|
// Helper for Tech Stack Icons
|
|
const iconMap: Record<string, any> = {
|
|
Globe, Server, Code, Wrench, Shield, Activity, Lightbulb, Gamepad2
|
|
};
|
|
|
|
const About = () => {
|
|
const locale = useLocale();
|
|
const t = useTranslations("home.about");
|
|
const [cmsDoc, setCmsDoc] = useState<JSONContent | null>(null);
|
|
|
|
// Data State
|
|
const [techStack, setTechStack] = useState<any[]>([]);
|
|
const [hobbies, setHobbies] = useState<any[]>([]);
|
|
|
|
useEffect(() => {
|
|
// Load Content Page
|
|
(async () => {
|
|
try {
|
|
const res = await fetch(`/api/content/page?key=home-about&locale=${locale}`);
|
|
const data = await res.json();
|
|
if (data?.content?.content) setCmsDoc(data.content.content as JSONContent);
|
|
} catch {}
|
|
})();
|
|
|
|
// Load Tech Stack
|
|
(async () => {
|
|
try {
|
|
const res = await fetch(`/api/tech-stack?locale=${locale}`);
|
|
const data = await res.json();
|
|
if (data?.techStack) setTechStack(data.techStack);
|
|
} catch {}
|
|
})();
|
|
|
|
// Load Hobbies
|
|
(async () => {
|
|
try {
|
|
const res = await fetch(`/api/hobbies?locale=${locale}`);
|
|
const data = await res.json();
|
|
if (data?.hobbies) setHobbies(data.hobbies);
|
|
} catch {}
|
|
})();
|
|
}, [locale]);
|
|
|
|
return (
|
|
<section id="about" className="py-32 px-4 relative bg-stone-50 dark:bg-stone-950">
|
|
{/* Background Noise/Gradient */}
|
|
<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="max-w-7xl mx-auto mb-16 text-center relative z-10">
|
|
<motion.h2
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true }}
|
|
className="text-4xl md:text-6xl font-black text-stone-900 dark:text-stone-50 tracking-tight"
|
|
>
|
|
{t("title")}
|
|
</motion.h2>
|
|
</div>
|
|
|
|
<BentoGrid className="max-w-6xl mx-auto relative z-10">
|
|
|
|
{/* 1. The Bio (Large Item) */}
|
|
<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"
|
|
title={
|
|
<div className="flex items-center gap-2">
|
|
<User size={20} className="text-liquid-mint" />
|
|
<span>Dennis Konkol</span>
|
|
</div>
|
|
}
|
|
description={
|
|
<div className="mt-4 prose prose-stone dark:prose-invert max-w-none text-base leading-relaxed">
|
|
{cmsDoc ? (
|
|
<RichTextClient doc={cmsDoc} />
|
|
) : (
|
|
<p className="text-stone-600 dark:text-stone-400">
|
|
{t("p1")} {t("p2")}
|
|
</p>
|
|
)}
|
|
</div>
|
|
}
|
|
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="absolute inset-0 bg-[url('/images/me.jpg')] bg-cover bg-center opacity-40 group-hover:scale-105 transition-transform duration-700"></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">
|
|
<span className="font-mono text-sm font-bold text-stone-800 dark:text-white">Software Engineer</span>
|
|
</div>
|
|
</div>
|
|
}
|
|
/>
|
|
|
|
{/* 2. Location & Status */}
|
|
<BentoGridItem
|
|
className="md:col-span-1"
|
|
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")}
|
|
description="Tools I work with daily"
|
|
header={
|
|
<div className="flex flex-wrap gap-2 min-h-[6rem] p-2">
|
|
{techStack.length > 0 ? (
|
|
techStack.slice(0, 8).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">
|
|
{item.name}
|
|
</span>
|
|
))).slice(0, 12)
|
|
) : (
|
|
<div className="text-xs text-stone-400">Loading Stack...</div>
|
|
)}
|
|
</div>
|
|
}
|
|
icon={<Code className="h-4 w-4 text-neutral-500" />}
|
|
/>
|
|
|
|
{/* 4. Currently Reading */}
|
|
<BentoGridItem
|
|
className="md:col-span-1 md:row-span-2"
|
|
title={null}
|
|
description={null}
|
|
header={
|
|
<div className="h-full flex flex-col">
|
|
<div className="font-bold text-stone-900 dark:text-white mb-4 flex items-center gap-2">
|
|
<Activity size={16} /> Reading
|
|
</div>
|
|
<div className="flex-1 overflow-hidden">
|
|
<CurrentlyReading />
|
|
</div>
|
|
<div className="mt-4 pt-4 border-t border-stone-100 dark:border-stone-800">
|
|
<ReadBooks />
|
|
</div>
|
|
</div>
|
|
}
|
|
/>
|
|
|
|
{/* 5. Hobbies */}
|
|
<BentoGridItem
|
|
className="md:col-span-2"
|
|
title={t("hobbiesTitle")}
|
|
description="What keeps me busy"
|
|
header={
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mt-2">
|
|
{hobbies.map((hobby, i) => {
|
|
const Icon = iconMap[hobby.icon] || Lightbulb;
|
|
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">
|
|
<Icon size={24} className="mb-2 text-liquid-purple" />
|
|
<span className="text-xs font-medium text-center">{hobby.title}</span>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
}
|
|
icon={<Gamepad2 className="h-4 w-4 text-neutral-500" />}
|
|
/>
|
|
|
|
</BentoGrid>
|
|
</section>
|
|
);
|
|
};
|
|
|
|
export default About;
|