Removed floating overlays. Integrated ActivityFeed and Chat directly into Bento grid cells. Refined layout for maximum clarity and 'Dennis' feel.
204 lines
9.5 KiB
TypeScript
204 lines
9.5 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
import { Globe, Server, Wrench, Shield, Gamepad2, Code, Activity, Lightbulb, BookOpen, MessageSquare, ArrowRight } 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";
|
|
import { TechStackCategory, Hobby } from "@/lib/directus";
|
|
import Link from "next/link";
|
|
import ActivityFeed from "./ActivityFeed";
|
|
import BentoChat from "./BentoChat";
|
|
|
|
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);
|
|
const [techStack, setTechStack] = useState<TechStackCategory[]>([]);
|
|
const [hobbies, setHobbies] = useState<Hobby[]>([]);
|
|
const [isActivityActive, setIsActivityActive] = useState(false);
|
|
const [cmsMessages, setCmsMessages] = useState<Record<string, string>>({});
|
|
|
|
useEffect(() => {
|
|
const fetchData = async () => {
|
|
try {
|
|
const [cmsRes, techRes, hobbiesRes, msgRes] = await Promise.all([
|
|
fetch(`/api/content/page?key=home-about&locale=${locale}`),
|
|
fetch(`/api/tech-stack?locale=${locale}`),
|
|
fetch(`/api/hobbies?locale=${locale}`),
|
|
fetch(`/api/messages?locale=${locale}`)
|
|
]);
|
|
|
|
const cmsData = await cmsRes.json();
|
|
if (cmsData?.content?.content) setCmsDoc(cmsData.content.content as JSONContent);
|
|
|
|
const techData = await techRes.json();
|
|
if (techData?.techStack) setTechStack(techData.techStack);
|
|
|
|
const hobbiesData = await hobbiesRes.json();
|
|
if (hobbiesData?.hobbies) setHobbies(hobbiesData.hobbies);
|
|
|
|
const msgData = await msgRes.json();
|
|
if (msgData?.messages) setCmsMessages(msgData.messages);
|
|
} catch (error) {}
|
|
};
|
|
fetchData();
|
|
}, [locale]);
|
|
|
|
return (
|
|
<section id="about" className="py-32 px-6 bg-[#fdfcf8] dark:bg-stone-950 transition-colors duration-500">
|
|
<div className="max-w-7xl mx-auto">
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-12 gap-6 md:gap-8">
|
|
|
|
{/* 1. Large Bio Text */}
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 30 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true }}
|
|
className="md:col-span-8 bg-white dark:bg-stone-900 rounded-[3rem] p-10 md:p-16 border border-stone-200/60 dark:border-stone-800/60 shadow-sm"
|
|
>
|
|
<div className="space-y-8">
|
|
<h2 className="text-4xl md:text-7xl font-black text-stone-900 dark:text-stone-50 tracking-tighter uppercase">
|
|
{t("title")}<span className="text-liquid-mint">.</span>
|
|
</h2>
|
|
<div className="prose prose-stone dark:prose-invert max-w-none text-xl md:text-2xl font-light leading-relaxed text-stone-600 dark:text-stone-400">
|
|
{cmsDoc ? <RichTextClient doc={cmsDoc} /> : <p>{t("p1")} {t("p2")}</p>}
|
|
</div>
|
|
<div className="pt-8">
|
|
<div className="inline-block bg-liquid-mint/5 px-8 py-4 rounded-3xl border border-liquid-mint/20">
|
|
<p className="text-[10px] font-black uppercase tracking-[0.2em] text-liquid-mint mb-2">{t("funFactTitle")}</p>
|
|
<p className="text-base font-bold opacity-90">{t("funFactBody")}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
|
|
{/* 2. Activity / Status Box */}
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 30 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true }}
|
|
transition={{ delay: 0.1 }}
|
|
className="md:col-span-4 bg-stone-900 rounded-[3rem] p-10 border border-stone-800 shadow-2xl text-white overflow-hidden relative flex flex-col"
|
|
>
|
|
<div className="relative z-10 flex-1">
|
|
<h3 className="text-xl font-black mb-10 flex items-center gap-2 uppercase tracking-widest text-liquid-mint">
|
|
<Activity size={20} /> Currently
|
|
</h3>
|
|
|
|
<div className="space-y-6">
|
|
<ActivityFeed onActivityChange={(active) => setIsActivityActive(active)} />
|
|
|
|
{!isActivityActive && (
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
className="pt-6 border-t border-white/10"
|
|
>
|
|
<p className="text-stone-400 italic font-light text-lg">
|
|
“{cmsMessages["about.quote.idle"] || "Gerade am Planen des nächsten großen Projekts."}”
|
|
</p>
|
|
</motion.div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div className="absolute -bottom-10 -right-10 w-40 h-40 bg-liquid-mint/10 blur-[80px] rounded-full" />
|
|
</motion.div>
|
|
|
|
{/* 3. AI Chat Box (Integrated) */}
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 30 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true }}
|
|
transition={{ delay: 0.2 }}
|
|
className="md:col-span-12 lg:col-span-4 bg-white dark:bg-stone-900 rounded-[3rem] p-10 border border-stone-200/60 dark:border-stone-800/60 flex flex-col shadow-sm"
|
|
>
|
|
<div className="flex items-center gap-2 mb-8">
|
|
<MessageSquare className="text-liquid-purple" size={24} />
|
|
<h3 className="text-2xl font-black text-stone-900 dark:text-stone-50 uppercase tracking-tighter text-liquid-purple">AI Twin</h3>
|
|
</div>
|
|
<div className="flex-1">
|
|
<BentoChat />
|
|
</div>
|
|
</motion.div>
|
|
|
|
{/* 4. Tech Stack */}
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 30 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true }}
|
|
transition={{ delay: 0.3 }}
|
|
className="md:col-span-12 lg:col-span-8 bg-white dark:bg-stone-900 rounded-[3rem] p-10 md:p-16 border border-stone-200/60 dark:border-stone-800/60 shadow-sm"
|
|
>
|
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-12">
|
|
{techStack.map((cat) => (
|
|
<div key={cat.id} className="space-y-6">
|
|
<h4 className="text-[10px] font-black uppercase tracking-[0.2em] text-stone-400">{cat.name}</h4>
|
|
<div className="flex flex-wrap gap-2">
|
|
{cat.items?.map((item: any) => (
|
|
<span key={item.id} className="px-4 py-2 bg-stone-50 dark:bg-stone-800/50 rounded-xl text-xs font-bold border border-stone-100 dark:border-stone-700/50 hover:border-liquid-mint transition-colors">
|
|
{item.name}
|
|
</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</motion.div>
|
|
|
|
{/* 5. Library & Hobbies */}
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 30 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true }}
|
|
transition={{ delay: 0.4 }}
|
|
className="md:col-span-12 grid grid-cols-1 md:grid-cols-2 gap-8"
|
|
>
|
|
<div className="bg-white dark:bg-stone-900 rounded-[3rem] p-10 border border-stone-200/60 dark:border-stone-800/60 shadow-sm flex flex-col justify-between group overflow-hidden relative">
|
|
<div className="relative z-10">
|
|
<div className="flex justify-between items-center mb-10">
|
|
<h3 className="text-2xl font-black text-stone-900 dark:text-stone-50 flex items-center gap-3 uppercase tracking-tighter">
|
|
<BookOpen className="text-liquid-purple" size={24} /> Library
|
|
</h3>
|
|
<Link href={`/${locale}/books`} className="group flex items-center gap-2 text-stone-900 dark:text-stone-100 font-black border-b-2 border-stone-900 dark:border-stone-100 pb-1 hover:opacity-70 transition-all">
|
|
View All <ArrowRight size={14} className="group-hover:translate-x-1 transition-transform" />
|
|
</Link>
|
|
</div>
|
|
<CurrentlyReading />
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-white dark:bg-stone-900 rounded-[3rem] p-10 border border-stone-200/60 dark:border-stone-800/60 shadow-sm flex flex-col justify-between">
|
|
<div className="flex flex-wrap gap-4 mb-10">
|
|
{hobbies.map((hobby) => {
|
|
const Icon = iconMap[hobby.icon] || Lightbulb;
|
|
return (
|
|
<div key={hobby.id} className="w-12 h-12 rounded-2xl bg-stone-50 dark:bg-stone-800 flex items-center justify-center shadow-sm border border-stone-100 dark:border-stone-700">
|
|
<Icon size={20} className="text-liquid-mint" />
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
<div className="space-y-2">
|
|
<h3 className="text-2xl font-black text-stone-900 dark:text-stone-50">{t("hobbiesTitle")}</h3>
|
|
<p className="text-stone-500 font-light text-lg">Curiosity beyond software engineering.</p>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
};
|
|
|
|
export default About;
|