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.
116 lines
4.4 KiB
TypeScript
116 lines
4.4 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
import { motion } from "framer-motion";
|
|
import { ArrowUpRight } from "lucide-react";
|
|
import Link from "next/link";
|
|
import Image from "next/image";
|
|
import { useLocale, useTranslations } from "next-intl";
|
|
|
|
interface Project {
|
|
id: number;
|
|
slug: string;
|
|
title: string;
|
|
description: string;
|
|
content: string;
|
|
tags: string[];
|
|
featured: boolean;
|
|
category: string;
|
|
date: string;
|
|
github?: string;
|
|
live?: string;
|
|
imageUrl?: string;
|
|
}
|
|
|
|
const Projects = () => {
|
|
const [projects, setProjects] = useState<Project[]>([]);
|
|
const locale = useLocale();
|
|
const t = useTranslations("home.projects");
|
|
|
|
useEffect(() => {
|
|
const loadProjects = async () => {
|
|
try {
|
|
const response = await fetch("/api/projects?featured=true&published=true&limit=6");
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
setProjects(data.projects || []);
|
|
}
|
|
} catch (error) {}
|
|
};
|
|
loadProjects();
|
|
}, []);
|
|
|
|
return (
|
|
<section id="projects" className="py-32 px-4 bg-stone-50 dark:bg-stone-950">
|
|
<div className="max-w-7xl mx-auto">
|
|
<div className="flex flex-col md:flex-row justify-between items-end mb-16 gap-6">
|
|
<div>
|
|
<h2 className="text-4xl md:text-6xl font-black text-stone-900 dark:text-stone-50 tracking-tight mb-4">
|
|
Selected Work
|
|
</h2>
|
|
<p className="text-xl text-stone-500 max-w-xl">
|
|
Projects that pushed my boundaries.
|
|
</p>
|
|
</div>
|
|
<Link href={`/${locale}/projects`} className="group flex items-center gap-2 text-stone-900 dark:text-stone-100 font-bold border-b-2 border-stone-900 dark:border-stone-100 pb-1 hover:opacity-70 transition-opacity">
|
|
View Archive <ArrowUpRight className="group-hover:-translate-y-1 group-hover:translate-x-1 transition-transform" />
|
|
</Link>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
|
{projects.map((project) => (
|
|
<motion.div
|
|
key={project.id}
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true }}
|
|
className="group relative"
|
|
>
|
|
<Link href={`/${locale}/projects/${project.slug}`} className="block">
|
|
{/* Image Card */}
|
|
<div className="relative aspect-[4/3] rounded-3xl overflow-hidden bg-stone-200 dark:bg-stone-900 mb-6">
|
|
{project.imageUrl ? (
|
|
<Image
|
|
src={project.imageUrl}
|
|
alt={project.title}
|
|
fill
|
|
className="object-cover transition-transform duration-700 group-hover:scale-105"
|
|
/>
|
|
) : (
|
|
<div className="absolute inset-0 flex items-center justify-center bg-gradient-to-br from-stone-100 to-stone-200 dark:from-stone-800 dark:to-stone-900">
|
|
<span className="text-4xl font-bold text-stone-300 dark:text-stone-700">{project.title.charAt(0)}</span>
|
|
</div>
|
|
)}
|
|
{/* Overlay on Hover */}
|
|
<div className="absolute inset-0 bg-black/0 group-hover:bg-black/10 transition-colors duration-500" />
|
|
</div>
|
|
|
|
{/* Text Content */}
|
|
<div className="flex justify-between items-start">
|
|
<div>
|
|
<h3 className="text-2xl font-bold text-stone-900 dark:text-stone-100 mb-2 group-hover:underline decoration-2 underline-offset-4">
|
|
{project.title}
|
|
</h3>
|
|
<p className="text-stone-500 dark:text-stone-400 line-clamp-2 max-w-md">
|
|
{project.description}
|
|
</p>
|
|
</div>
|
|
<div className="hidden md:flex gap-2">
|
|
{project.tags.slice(0, 2).map(tag => (
|
|
<span key={tag} className="px-3 py-1 rounded-full border border-stone-200 dark:border-stone-800 text-xs font-medium text-stone-600 dark:text-stone-400">
|
|
{tag}
|
|
</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</Link>
|
|
</motion.div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
};
|
|
|
|
export default Projects;
|