Files
ayristech_new/components/WorkDetailClient.tsx
T

550 lines
24 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { motion, useScroll, useTransform, AnimatePresence } from "framer-motion";
import { useRef, useState, useEffect } 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: [number, number, number, number] = [0.16, 1, 0.3, 1];
const IconArrowLeft = () => (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<line x1="19" y1="12" x2="5" y2="12" />
<polyline points="12 19 5 12 12 5" />
</svg>
);
const IconArrowRight = () => (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<line x1="5" y1="12" x2="19" y2="12" />
<polyline points="12 5 19 12 12 19" />
</svg>
);
const IconCheck = () => (
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#0A0A0A" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
<polyline points="20 6 9 17 4 12" />
</svg>
);
// Tag colors per category
const accentFor = (tag: string): string => {
if (/ai|yz|ml/i.test(tag)) return "#FFE600";
if (/web3|blockchain|chain/i.test(tag)) return "#FF4500";
if (/mobile|mobil/i.test(tag)) return "#0A0A0A";
return "#00CC77";
};
interface Props {
lang: Locale;
dict: any;
project: any;
prev: any | null;
next: any | null;
}
const screenshotsFor = (slug: string): string[] => {
const mapping: Record<string, string[]> = {
"financeai-dashboard": [
"https://images.unsplash.com/photo-1551288049-bebda4e38f71?auto=format&fit=crop&w=1200&q=80",
"https://images.unsplash.com/photo-1460925895917-afdab827c52f?auto=format&fit=crop&w=1200&q=80",
"https://images.unsplash.com/photo-1551836022-d5d88e9218df?auto=format&fit=crop&w=1200&q=80",
],
"chainsupply-network": [
"https://images.unsplash.com/photo-1508873535684-277a3cbcc4e8?auto=format&fit=crop&w=1200&q=80",
"https://images.unsplash.com/photo-1639762681485-074b7f938ba0?auto=format&fit=crop&w=1200&q=80",
"https://images.unsplash.com/photo-1504384308090-c894fdcc538d?auto=format&fit=crop&w=1200&q=80",
],
"meditrack-mobile": [
"https://images.unsplash.com/photo-1576091160399-112ba8d25d1d?auto=format&fit=crop&w=1200&q=80",
"https://images.unsplash.com/photo-1584982751601-97dcc096659c?auto=format&fit=crop&w=1200&q=80",
"https://images.unsplash.com/photo-1530026405186-ed1ea0ac7a63?auto=format&fit=crop&w=1200&q=80",
],
"novamart-platform": [
"https://images.unsplash.com/photo-1460925895917-afdab827c52f?auto=format&fit=crop&w=1200&q=80",
"https://images.unsplash.com/photo-1556742049-0cfed4f6a45d?auto=format&fit=crop&w=1200&q=80",
"https://images.unsplash.com/photo-1523474253046-8cd2748b5fd2?auto=format&fit=crop&w=1200&q=80",
],
};
return mapping[slug] || [];
};
export default function WorkDetailClient({ lang, dict, project, prev, next }: Props) {
const heroRef = useRef<HTMLDivElement>(null);
const { scrollYProgress } = useScroll({ target: heroRef, offset: ["start start", "end start"] });
const heroY = useTransform(scrollYProgress, [0, 1], ["0%", "30%"]);
const [activeImage, setActiveImage] = useState<string | null>(null);
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") setActiveImage(null);
};
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, []);
const accent = accentFor(project.tag);
let screenshots = screenshotsFor(project.slug);
if (project.gallery && project.gallery.length > 0) {
screenshots = project.gallery;
}
return (
<div className="bg-[#F4F0E8] text-[#0A0A0A] font-body min-h-screen">
<Header lang={lang} dict={dict.nav} />
{/* ── HERO ── */}
<section
ref={heroRef}
className="relative min-h-[70vh] flex flex-col justify-end overflow-hidden border-b border-[#C8C2B8]"
>
{/* Dot grid */}
<motion.div
className="absolute inset-0"
style={{ y: heroY }}
>
{project.image ? (
<>
<div
className="absolute inset-0 bg-cover bg-center"
style={{ backgroundImage: `url(${project.image})` }}
/>
<div className="absolute inset-0 bg-[#F4F0E8]/80" />
</>
) : (
<div className="absolute inset-0 dot-grid opacity-50" />
)}
</motion.div>
{/* Accent left vertical bar */}
<div className="absolute top-0 left-0 w-1 h-full" style={{ backgroundColor: accent }} />
{/* Big number watermark */}
<motion.div
className="absolute right-0 bottom-0 font-display font-black select-none pointer-events-none text-[#E8E3DC] leading-none"
style={{ fontSize: "clamp(8rem, 25vw, 20rem)" }}
initial={{ opacity: 0, x: 40 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 1, delay: 0.3 }}
>
{project.num}
</motion.div>
<div className="relative z-10 max-w-7xl mx-auto px-6 w-full pb-16 pt-36">
{/* Breadcrumb */}
<motion.div
className="flex items-center gap-3 mb-10"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.1 }}
>
<Link
href={`/${lang}/work`}
className="flex items-center gap-2 font-mono text-[10px] tracking-[0.25em] uppercase text-[#A0998E] hover:text-[#0A0A0A] transition-colors"
>
<IconArrowLeft /> {dict.nav.work}
</Link>
<span className="text-[#C8C2B8]">/</span>
<span className="font-mono text-[10px] tracking-[0.25em] uppercase text-[#0A0A0A]">
{project.num}
</span>
</motion.div>
{/* Tag badge */}
<motion.div
className="mb-5"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2, duration: 0.5 }}
>
<span
className="inline-block font-mono text-[10px] tracking-[0.25em] uppercase px-3 py-1.5 border border-[#C8C2B8]"
style={{ borderLeftColor: accent, borderLeftWidth: 3 }}
>
{project.tag}
</span>
</motion.div>
{/* Title */}
<div className="overflow-hidden mb-8">
<motion.h1
className="font-display font-black uppercase leading-[0.85] tracking-tight text-[#0A0A0A]"
style={{ fontSize: "clamp(3rem, 8vw, 7rem)" }}
initial={{ y: "110%" }}
animate={{ y: 0 }}
transition={{ duration: 0.85, ease: expo, delay: 0.25 }}
>
{project.title}
</motion.h1>
</div>
{/* Meta row */}
<motion.div
className={`grid grid-cols-2 ${project.website ? 'md:grid-cols-5' : 'md:grid-cols-4'} gap-px bg-[#C8C2B8] border-t border-[#C8C2B8]`}
initial={{ opacity: 0, y: 15 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.5, duration: 0.6 }}
>
{[
{ label: lang === "tr" ? "Yıl" : "Year", value: project.year },
{ label: lang === "tr" ? "Süre" : "Duration", value: project.duration },
{ label: lang === "tr" ? "Sektör" : "Sector", value: project.tag },
{ label: lang === "tr" ? "Müşteri" : "Client", value: project.client },
].map((m, i) => (
<div key={i} className="bg-[#F4F0E8] px-5 py-4">
<div className="font-mono text-[9px] tracking-[0.3em] uppercase text-[#A0998E] mb-1">{m.label}</div>
<div className="font-display font-black text-[13px] uppercase text-[#0A0A0A] truncate">{m.value}</div>
</div>
))}
{project.website && (
<a
href={project.website.startsWith('http') ? project.website : `https://${project.website}`}
target="_blank"
rel="noopener noreferrer"
className="bg-[#0A0A0A] px-5 py-4 group hover:bg-[#FFE600] transition-colors col-span-2 md:col-span-1 flex flex-col justify-center"
>
<div className="font-mono text-[9px] tracking-[0.3em] uppercase text-[#A0998E] group-hover:text-[#0A0A0A] mb-1 transition-colors">
{lang === "tr" ? "Canlı Proje" : "Live Project"}
</div>
<div className="font-display font-black text-[13px] uppercase text-white group-hover:text-[#0A0A0A] truncate flex items-center gap-2 transition-colors">
{lang === "tr" ? "SİTEYE GİT" : "VISIT SITE"}
<svg className="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="square" strokeLinejoin="miter" strokeWidth="2.5" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
</div>
</a>
)}
</motion.div>
</div>
</section>
{/* ── CONTENT ── */}
<main className="max-w-7xl mx-auto px-6 py-20">
<div className="grid grid-cols-1 lg:grid-cols-12 gap-16">
{/* Left column: main content */}
<div className="lg:col-span-8 space-y-20">
{/* Overview */}
<motion.section
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.7 }}
>
<div className="flex items-center gap-3 mb-6">
<div className="w-4 h-4 flex-shrink-0" style={{ backgroundColor: accent }} />
<span className="font-mono text-[10px] tracking-[0.3em] uppercase text-[#A0998E]">
{lang === "tr" ? "Genel Bakış" : "Overview"}
</span>
<div className="flex-1 h-px bg-[#C8C2B8]" />
</div>
<p className="text-[#4A4440] text-lg leading-relaxed border-l-4 pl-6" style={{ borderColor: accent }}>
{project.desc}
</p>
</motion.section>
{/* Challenge */}
<motion.section
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.7 }}
>
<div className="flex items-center gap-3 mb-6">
<div className="w-4 h-4 border-2 border-[#0A0A0A] flex-shrink-0" />
<span className="font-mono text-[10px] tracking-[0.3em] uppercase text-[#A0998E]">
{lang === "tr" ? "Problem" : "The Challenge"}
</span>
<div className="flex-1 h-px bg-[#C8C2B8]" />
</div>
<h2 className="font-display font-black text-2xl uppercase text-[#0A0A0A] mb-4">
{lang === "tr" ? "Ne Sorununu Çözdük?" : "What Problem Did We Solve?"}
</h2>
<p className="text-[#6A6460] leading-relaxed">
{project.challenge}
</p>
</motion.section>
{/* Solution */}
<motion.section
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.7 }}
>
<div className="flex items-center gap-3 mb-6">
<div className="w-4 h-4 border-2 flex-shrink-0" style={{ borderColor: accent }} />
<span className="font-mono text-[10px] tracking-[0.3em] uppercase text-[#A0998E]">
{lang === "tr" ? "Çözüm" : "Our Solution"}
</span>
<div className="flex-1 h-px bg-[#C8C2B8]" />
</div>
<h2 className="font-display font-black text-2xl uppercase text-[#0A0A0A] mb-4">
{lang === "tr" ? "Nasıl İnşa Ettik?" : "How Did We Build It?"}
</h2>
<p className="text-[#6A6460] leading-relaxed">
{project.solution}
</p>
</motion.section>
{/* Results */}
<motion.section
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.7 }}
>
<div className="flex items-center gap-3 mb-8">
<div className="w-4 h-4 flex-shrink-0" style={{ backgroundColor: accent }} />
<span className="font-mono text-[10px] tracking-[0.3em] uppercase text-[#A0998E]">
{lang === "tr" ? "Sonuçlar" : "Results"}
</span>
<div className="flex-1 h-px bg-[#C8C2B8]" />
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-px bg-[#C8C2B8]">
{project.results.map((r: string, idx: number) => (
<motion.div
key={idx}
className="bg-[#F4F0E8] p-6 flex items-start gap-4 group"
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ delay: idx * 0.1 }}
whileHover={{ backgroundColor: "#EDE8E0" }}
>
<div
className="w-6 h-6 flex-shrink-0 flex items-center justify-center mt-0.5"
style={{ backgroundColor: accent }}
>
<IconCheck />
</div>
<p className="font-display font-bold text-[14px] uppercase text-[#0A0A0A] leading-snug">
{r}
</p>
</motion.div>
))}
</div>
</motion.section>
</div>
{/* Right column: sidebar */}
<aside className="lg:col-span-4 space-y-0">
<div className="sticky top-24">
{/* Perf spec */}
<motion.div
className="border border-[#C8C2B8] mb-px"
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.4 }}
>
<div className="border-b border-[#C8C2B8] px-6 py-3 flex items-center gap-2">
<div className="w-2 h-2" style={{ backgroundColor: accent }} />
<span className="font-mono text-[9px] tracking-[0.3em] uppercase text-[#A0998E]">
{dict.work.specsLabel}
</span>
</div>
<div className="px-6 py-5">
<p className="font-display font-black text-sm uppercase text-[#0A0A0A] leading-snug">
{project.spec}
</p>
</div>
</motion.div>
{/* Tech stack */}
<motion.div
className="border border-[#C8C2B8] mb-px"
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.5 }}
>
<div className="border-b border-[#C8C2B8] px-6 py-3">
<span className="font-mono text-[9px] tracking-[0.3em] uppercase text-[#A0998E]">
{lang === "tr" ? "Teknoloji Yığını" : "Tech Stack"}
</span>
</div>
<div className="px-6 py-5 flex flex-wrap gap-2">
{project.tech.map((t: string) => (
<span
key={t}
className="font-mono text-[10px] tracking-wider uppercase px-2.5 py-1.5 border border-[#C8C2B8] text-[#6A6460] hover:border-[#0A0A0A] hover:text-[#0A0A0A] transition-colors cursor-default"
>
{t}
</span>
))}
</div>
</motion.div>
{/* Removed old Website block */}
{/* CTA */}
<motion.div
className="border-2 border-[#0A0A0A] p-6"
style={{ backgroundColor: accent }}
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.6 }}
>
<p className="font-display font-black text-sm uppercase text-[#0A0A0A] mb-4 leading-snug">
{lang === "tr" ? "Benzer bir proje mi istiyorsunuz?" : "Want a similar project?"}
</p>
<Link
href={`/${lang}/contact`}
className="btn-brutal inline-flex items-center gap-2 px-5 py-3 font-display font-black text-[11px] tracking-widest uppercase cursor-pointer bg-[#0A0A0A] text-[#F4F0E8] border-[#0A0A0A]"
>
{dict.nav.quote} <IconArrowRight />
</Link>
</motion.div>
</div>
</aside>
</div>
</main>
{/* ── PROJECT SCREENSHOTS GALLERY ── */}
{screenshots.length > 0 && (
<section className="py-24 border-t border-[#C8C2B8] bg-[#EDE8E0]">
<div className="max-w-7xl mx-auto px-6">
<div className="flex items-center gap-3 mb-12">
<div className="w-3 h-3" style={{ backgroundColor: accent }} />
<span className="font-mono text-[10px] tracking-[0.3em] uppercase text-[#A0998E]">
{dict.work.galleryTitle || "EKRAN GÖRÜNTÜLERİ"}
</span>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
{screenshots.map((src, idx) => (
<motion.div
key={idx}
className="group bg-[#F4F0E8] border border-[#C8C2B8] hover:border-[#0A0A0A] p-3 relative overflow-hidden transition-all duration-300 cursor-zoom-in"
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: idx * 0.1, duration: 0.6 }}
onClick={() => setActiveImage(src)}
whileHover={{ y: -6, boxShadow: `4px 4px 0px 0px ${accent}` }}
>
<div className="aspect-[16/10] w-full overflow-hidden border border-[#C8C2B8] group-hover:border-[#0A0A0A] bg-[#EDE8E0] transition-colors relative">
<img
src={src}
alt={`${project.title} screenshot ${idx + 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 className="mt-3 flex justify-between items-center font-mono text-[9px] text-[#A0998E] group-hover:text-[#0A0A0A] transition-colors">
<span>SCREEN // 0{idx + 1}</span>
<span className="uppercase tracking-widest">[ ZOOM ]</span>
</div>
</motion.div>
))}
</div>
</div>
</section>
)}
{/* Lightbox Modal */}
<AnimatePresence>
{activeImage && (
<motion.div
className="fixed inset-0 z-[100] flex items-center justify-center bg-black/85 backdrop-blur-md px-6 cursor-zoom-out"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={() => setActiveImage(null)}
>
<button
onClick={() => setActiveImage(null)}
className="absolute top-6 right-6 w-12 h-12 border border-white/20 hover:border-white/80 hover:bg-white/10 text-white flex items-center justify-center font-mono text-[10px] uppercase tracking-widest cursor-pointer transition-colors duration-200"
>
[ CLOSE ]
</button>
<motion.div
className="max-w-5xl max-h-[85vh] border-2 border-white/10 bg-[#0A0A0A] p-2 relative"
initial={{ scale: 0.9, y: 20 }}
animate={{ scale: 1, y: 0 }}
exit={{ scale: 0.9, y: 20 }}
transition={{ duration: 0.4, ease: expo }}
onClick={(e) => e.stopPropagation()}
>
<img
src={activeImage}
alt="Enlarged project screenshot"
className="max-w-full max-h-[80vh] object-contain block"
/>
</motion.div>
</motion.div>
)}
</AnimatePresence>
{/* ── PREV / NEXT NAVIGATION ── */}
<div className="border-t border-[#C8C2B8]">
<div className="max-w-7xl mx-auto grid grid-cols-1 md:grid-cols-2 gap-px bg-[#C8C2B8]">
{/* Prev */}
<div>
{prev ? (
<Link
href={`/${lang}/work/${prev.slug}`}
className="group flex flex-col p-10 bg-[#F4F0E8] hover:bg-[#EDE8E0] transition-colors h-full"
>
<div className="flex items-center gap-2 font-mono text-[9px] tracking-[0.3em] uppercase text-[#A0998E] group-hover:text-[#0A0A0A] transition-colors mb-4">
<IconArrowLeft />
{lang === "tr" ? "Önceki Proje" : "Previous Project"}
</div>
<div className="font-display font-black text-xl uppercase text-[#0A0A0A] mt-auto">
{prev.title}
</div>
<div className="font-mono text-[10px] tracking-wider uppercase text-[#A0998E] mt-1">
{prev.tag}
</div>
</Link>
) : (
<div className="p-10 bg-[#EDE8E0]" />
)}
</div>
{/* Next */}
<div>
{next ? (
<Link
href={`/${lang}/work/${next.slug}`}
className="group flex flex-col items-end p-10 bg-[#F4F0E8] hover:bg-[#EDE8E0] transition-colors h-full text-right"
>
<div className="flex items-center gap-2 font-mono text-[9px] tracking-[0.3em] uppercase text-[#A0998E] group-hover:text-[#0A0A0A] transition-colors mb-4">
{lang === "tr" ? "Sonraki Proje" : "Next Project"}
<IconArrowRight />
</div>
<div className="font-display font-black text-xl uppercase text-[#0A0A0A] mt-auto">
{next.title}
</div>
<div className="font-mono text-[10px] tracking-wider uppercase text-[#A0998E] mt-1">
{next.tag}
</div>
</Link>
) : (
<Link
href={`/${lang}/work`}
className="group flex flex-col items-end p-10 bg-[#F4F0E8] hover:bg-[#EDE8E0] transition-colors h-full text-right"
>
<div className="flex items-center gap-2 font-mono text-[9px] tracking-[0.3em] uppercase text-[#A0998E] group-hover:text-[#0A0A0A] transition-colors mb-4">
{lang === "tr" ? "Tüm Projeler" : "All Projects"}
<IconArrowRight />
</div>
<div className="font-display font-black text-xl uppercase text-[#C8C2B8] mt-auto">
{lang === "tr" ? "Portfolyo" : "Portfolio"}
</div>
</Link>
)}
</div>
</div>
</div>
<Footer lang={lang} dict={dict} />
</div>
);
}