165 lines
6.8 KiB
TypeScript
165 lines
6.8 KiB
TypeScript
"use client";
|
|
|
|
import { motion } from "framer-motion";
|
|
import { useState } from "react";
|
|
import Link from "next/link";
|
|
import Header from "@/components/Header";
|
|
import Footer from "@/components/Footer";
|
|
import type { Locale } from "@/i18n-config";
|
|
|
|
const expo = [0.16, 1, 0.3, 1] as [number, number, number, number];
|
|
|
|
const IconArrowUpRight = () => (
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
<line x1="7" y1="17" x2="17" y2="7" />
|
|
<polyline points="7 7 17 7 17 17" />
|
|
</svg>
|
|
);
|
|
|
|
export default function WorkClient({
|
|
lang,
|
|
dict,
|
|
initialProjects,
|
|
}: {
|
|
lang: Locale;
|
|
dict: any;
|
|
initialProjects?: any[];
|
|
}) {
|
|
const [hoverCase, setHoverCase] = useState<number | null>(null);
|
|
|
|
const accentColors = ["#FFE600", "#FF4500", "#0A0A0A", "#00CC77"];
|
|
|
|
const projectsList = initialProjects && initialProjects.length > 0 ? initialProjects : (dict.work.items as any[]);
|
|
|
|
const caseStudies = projectsList.map((item: any, idx: number) => ({
|
|
...item,
|
|
accent: accentColors[idx % accentColors.length] ?? "#FFE600",
|
|
// Fallback tech in case older dict snapshot doesn't have it yet
|
|
tech: item.tech ?? ["React", "TypeScript", "Node.js"],
|
|
}));
|
|
|
|
return (
|
|
<div className="bg-[#F4F0E8] text-[#0A0A0A] font-body min-h-screen">
|
|
<Header lang={lang} dict={dict.nav} />
|
|
|
|
<main className="pt-32 pb-24 px-6 max-w-7xl mx-auto">
|
|
|
|
{/* Page header */}
|
|
<div className="border-b border-[#C8C2B8] pb-12 mb-16">
|
|
<div className="flex items-center gap-3 mb-6">
|
|
<div className="w-3 h-3 bg-[#FFE600]" />
|
|
<span className="font-mono text-[10px] tracking-[0.3em] uppercase text-[#A0998E]">
|
|
{dict.work.badge}
|
|
</span>
|
|
</div>
|
|
<motion.h1
|
|
className="font-display font-black text-5xl sm:text-6xl lg:text-8xl uppercase leading-[0.85] tracking-tight"
|
|
initial={{ opacity: 0, y: 30 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.8, ease: expo }}
|
|
>
|
|
{dict.work.title}
|
|
</motion.h1>
|
|
</div>
|
|
|
|
{/* Case study list */}
|
|
<div className="space-y-px bg-[#C8C2B8]">
|
|
{caseStudies.map((study: any, idx: number) => (
|
|
<motion.div
|
|
key={idx}
|
|
className="group bg-[#F4F0E8] grid grid-cols-1 lg:grid-cols-12 relative overflow-hidden cursor-pointer"
|
|
initial={{ opacity: 0 }}
|
|
whileInView={{ opacity: 1 }}
|
|
viewport={{ once: true }}
|
|
transition={{ delay: idx * 0.1 }}
|
|
whileHover={{ backgroundColor: "#EDE8E0" }}
|
|
onHoverStart={() => setHoverCase(idx)}
|
|
onHoverEnd={() => setHoverCase(null)}
|
|
>
|
|
{/* Full-card link */}
|
|
<Link
|
|
href={`/${lang}/work/${study.slug}`}
|
|
className="absolute inset-0 z-10"
|
|
aria-label={study.title}
|
|
/>
|
|
|
|
{/* Accent top bar */}
|
|
<motion.div
|
|
className="absolute top-0 left-0 right-0 h-1"
|
|
style={{ backgroundColor: study.accent }}
|
|
initial={{ scaleX: 0 }}
|
|
animate={{ scaleX: hoverCase === idx ? 1 : 0 }}
|
|
transition={{ duration: 0.3, ease: expo }}
|
|
/>
|
|
|
|
{/* Left column */}
|
|
<div className="lg:col-span-4 p-10 border-b lg:border-b-0 lg:border-r border-[#C8C2B8] flex flex-col justify-between">
|
|
<div>
|
|
<span className="font-mono text-[9px] tracking-[0.25em] uppercase text-[#A0998E] border border-[#C8C2B8] px-2.5 py-1">
|
|
{study.tag}
|
|
</span>
|
|
<h2 className="font-display font-black text-2xl uppercase mt-6 mb-2">
|
|
{study.title}
|
|
</h2>
|
|
</div>
|
|
<div>
|
|
<div className="font-mono text-[9px] tracking-[0.2em] uppercase text-[#A0998E] mb-1">
|
|
{dict.work.specsLabel}:
|
|
</div>
|
|
<div className="font-display font-bold text-[13px] text-[#0A0A0A]">
|
|
{study.spec}
|
|
</div>
|
|
<div className="font-display font-black text-[5rem] leading-none text-[#E8E3DC] select-none mt-4">
|
|
{study.num}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Right column */}
|
|
<div className="lg:col-span-8 p-10 flex flex-col justify-between">
|
|
<p className="text-[#7A7470] text-base leading-relaxed mb-8">
|
|
{study.desc}
|
|
</p>
|
|
|
|
{study.gallery && study.gallery.length > 0 && (
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8">
|
|
{study.gallery.slice(0, 3).map((src: string, i: number) => (
|
|
<div key={i} className="aspect-[16/10] w-full overflow-hidden border border-[#C8C2B8] group-hover:border-[#0A0A0A] bg-[#EDE8E0] transition-colors relative">
|
|
<img
|
|
src={src}
|
|
alt={`${study.title} preview ${i + 1}`}
|
|
className="w-full h-full object-contain grayscale group-hover:grayscale-0 contrast-[1.1] brightness-[0.98] group-hover:scale-105 transition-all duration-700"
|
|
/>
|
|
<div className="absolute inset-0 bg-[#0A0A0A]/5 opacity-100 group-hover:opacity-0 transition-opacity duration-300" />
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
<div className="flex flex-wrap items-center justify-between gap-4 pt-6 border-t border-[#D8D3CA]">
|
|
<div className="flex flex-wrap gap-2">
|
|
{(study.tech as string[]).map((t) => (
|
|
<span
|
|
key={t}
|
|
className="font-mono text-[10px] tracking-wider uppercase px-2.5 py-1 border border-[#C8C2B8] text-[#8A8480] group-hover:border-[#0A0A0A] group-hover:text-[#0A0A0A] transition-colors"
|
|
>
|
|
{t}
|
|
</span>
|
|
))}
|
|
</div>
|
|
{/* Button sits above the overlay link via z-20 */}
|
|
<span className="relative z-20 font-display font-black text-[12px] uppercase tracking-wider text-[#0A0A0A] flex items-center gap-2 group-hover:opacity-60 transition-opacity">
|
|
{dict.work.analyzeBtn} <IconArrowUpRight />
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
))}
|
|
</div>
|
|
</main>
|
|
|
|
<Footer lang={lang} dict={dict} />
|
|
</div>
|
|
);
|
|
}
|