feat:added project details page with prod. api

This commit is contained in:
sanjidaRimi023
2026-05-01 22:19:05 +06:00
parent b51d6dbca6
commit 9c98052638
4 changed files with 448 additions and 375 deletions
+1 -1
View File
@@ -23,7 +23,7 @@ function AnimatedRoutes() {
<Routes location={location} key={location.pathname}>
<Route path="/" element={<Index />} />
<Route path="/projects" element={<Projects />} />
<Route path="/projects/:slug" element={<ProjectDetails />} />
<Route path="/projects/:id" element={<ProjectDetails />} />
{/* <Route path="/blog" element={<Blog />} />
<Route path="/blog/:id" element={<BlogArticle />} /> */}
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
+102
View File
@@ -0,0 +1,102 @@
import { motion } from "framer-motion";
import { ExternalLink } from "lucide-react";
import { useState } from "react";
import { Link } from "react-router-dom";
export function ProjectCard({ project, color, index, isInView }) {
const [mousePosition, setMousePosition] = useState({ x: 50, y: 50 });
const [isHovered, setIsHovered] = useState(false);
const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
const rect = e.currentTarget.getBoundingClientRect();
const x = ((e.clientX - rect.left) / rect.width) * 100;
const y = ((e.clientY - rect.top) / rect.height) * 100;
setMousePosition({ x, y });
};
return (
<Link to={`/projects/${project._id}`}>
<motion.div
initial={{ opacity: 0, y: 60, rotateX: 15 }}
animate={isInView ? { opacity: 1, y: 0, rotateX: 0 } : {}}
transition={{ duration: 0.6, delay: index * 0.08 }}
whileHover={{ scale: 1.02 }}
className="group relative rounded-3xl overflow-hidden cursor-pointer h-full"
onMouseMove={handleMouseMove}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => {
setIsHovered(false);
setMousePosition({ x: 50, y: 50 });
}}
>
{/* Main Card Container with 3D Tilt */}
<div
className="relative h-full rounded-3xl overflow-hidden shadow-2xl transition-transform duration-300"
style={{
transform: isHovered
? `perspective(1000px) rotateX(${(50 - mousePosition.y) * 0.12}deg) rotateY(${(mousePosition.x - 50) * 0.15}deg)`
: "perspective(1000px) rotateX(0deg) rotateY(0deg)",
transition: isHovered
? "transform 0.1s ease-out"
: "transform 0.4s ease-out",
}}
>
{/* Image Container */}
<div className="aspect-[16/13] overflow-hidden relative">
<img
src={project.image}
alt={project.title}
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110"
/>
{/* Dynamic Shine Overlay */}
<div
className="absolute inset-0 opacity-0 group-hover:opacity-30 transition-opacity duration-300 pointer-events-none"
style={{
background: `radial-gradient(circle at ${mousePosition.x}% ${mousePosition.y}%, rgba(255,255,255,0.8) 0%, transparent 60%)`,
}}
/>
</div>
{/* Gradient Overlay */}
<div
className={`absolute inset-0 bg-gradient-to-t ${color} via-black/40 to-transparent opacity-60 group-hover:opacity-90 transition-all duration-500`}
/>
{/* Content */}
<div className="absolute inset-0 flex flex-col justify-end p-8">
<div className="space-y-4 transform transition-all duration-500 group-hover:translate-y-0">
{/* Category */}
<motion.span
initial={{ opacity: 0, y: 20 }}
animate={
isHovered ? { opacity: 1, y: 0 } : { opacity: 0.7, y: 10 }
}
className="inline-block px-4 py-1.5 rounded-full bg-white/10 backdrop-blur-md text-white text-xs font-medium border border-white/20"
>
{project.category}
</motion.span>
{/* Title */}
<h3 className="text-2xl md:text-3xl font-bold text-white leading-tight tracking-tight">
{project.title}
</h3>
{/* View Project Link */}
<Link
to={project.liveUrl}
className="flex items-center gap-3 text-white/80 group-hover:text-white transition-colors"
>
<span className="font-medium text-sm tracking-wider">live</span>
<ExternalLink className="w-5 h-5 transition-transform group-hover:rotate-45" />
</Link>
</div>
</div>
{/* Neon Border Glow */}
<div className="absolute inset-0 rounded-3xl border-2 border-transparent group-hover:border-primary/60 transition-all duration-500 pointer-events-none neon-glow" />
</div>
</motion.div>
</Link>
);
}
+3 -98
View File
@@ -1,9 +1,10 @@
import { Button } from "@/components/ui/button";
import { useProjects } from "@/hooks/queires/useProjects";
import { motion, useInView } from "framer-motion";
import { ArrowRight, ExternalLink } from "lucide-react";
import { useRef, useState } from "react";
import { ArrowRight } from "lucide-react";
import { useRef } from "react";
import { Link } from "react-router-dom";
import { ProjectCard } from "./ProjectCard";
const gradientColors = [
"from-neon-blue/90",
@@ -98,99 +99,3 @@ export default function ProjectsSection() {
</section>
);
}
function ProjectCard({ project, color, index, isInView }) {
const [mousePosition, setMousePosition] = useState({ x: 50, y: 50 });
const [isHovered, setIsHovered] = useState(false);
const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
const rect = e.currentTarget.getBoundingClientRect();
const x = ((e.clientX - rect.left) / rect.width) * 100;
const y = ((e.clientY - rect.top) / rect.height) * 100;
setMousePosition({ x, y });
};
return (
<motion.div
initial={{ opacity: 0, y: 60, rotateX: 15 }}
animate={isInView ? { opacity: 1, y: 0, rotateX: 0 } : {}}
transition={{ duration: 0.6, delay: index * 0.08 }}
whileHover={{ scale: 1.02 }}
className="group relative rounded-3xl overflow-hidden cursor-pointer h-full"
onMouseMove={handleMouseMove}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => {
setIsHovered(false);
setMousePosition({ x: 50, y: 50 });
}}
>
{/* Main Card Container with 3D Tilt */}
<div
className="relative h-full rounded-3xl overflow-hidden shadow-2xl transition-transform duration-300"
style={{
transform: isHovered
? `perspective(1000px) rotateX(${(50 - mousePosition.y) * 0.12}deg) rotateY(${(mousePosition.x - 50) * 0.15}deg)`
: "perspective(1000px) rotateX(0deg) rotateY(0deg)",
transition: isHovered
? "transform 0.1s ease-out"
: "transform 0.4s ease-out",
}}
>
{/* Image Container */}
<div className="aspect-[16/13] overflow-hidden relative">
<img
src={project.image}
alt={project.title}
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110"
/>
{/* Dynamic Shine Overlay */}
<div
className="absolute inset-0 opacity-0 group-hover:opacity-30 transition-opacity duration-300 pointer-events-none"
style={{
background: `radial-gradient(circle at ${mousePosition.x}% ${mousePosition.y}%, rgba(255,255,255,0.8) 0%, transparent 60%)`,
}}
/>
</div>
{/* Gradient Overlay */}
<div
className={`absolute inset-0 bg-gradient-to-t ${color} via-black/40 to-transparent opacity-60 group-hover:opacity-90 transition-all duration-500`}
/>
{/* Content */}
<div className="absolute inset-0 flex flex-col justify-end p-8">
<div className="space-y-4 transform transition-all duration-500 group-hover:translate-y-0">
{/* Category */}
<motion.span
initial={{ opacity: 0, y: 20 }}
animate={
isHovered ? { opacity: 1, y: 0 } : { opacity: 0.7, y: 10 }
}
className="inline-block px-4 py-1.5 rounded-full bg-white/10 backdrop-blur-md text-white text-xs font-medium border border-white/20"
>
{project.category}
</motion.span>
{/* Title */}
<h3 className="text-2xl md:text-3xl font-bold text-white leading-tight tracking-tight">
{project.title}
</h3>
{/* View Project Link */}
<Link
to={project.liveUrl}
className="flex items-center gap-3 text-white/80 group-hover:text-white transition-colors"
>
<span className="font-medium text-sm tracking-wider">live</span>
<ExternalLink className="w-5 h-5 transition-transform group-hover:rotate-45" />
</Link>
</div>
</div>
{/* Neon Border Glow */}
<div className="absolute inset-0 rounded-3xl border-2 border-transparent group-hover:border-primary/60 transition-all duration-500 pointer-events-none neon-glow" />
</div>
</motion.div>
);
}
+342 -276
View File
@@ -1,366 +1,432 @@
import { useParams, Link, useNavigate } from 'react-router-dom';
import { motion } from 'framer-motion';
import { ArrowLeft, ExternalLink, Github, Calendar, Clock, Users, ChevronRight } from 'lucide-react';
import { Button } from '@/components/ui/button';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import PageTransition from '@/components/PageTransition';
import { getProjectBySlug, getRelatedProjects } from '@/data/projectData';
import Footer from "@/components/Footer";
import Navbar from "@/components/Navbar";
import PageTransition from "@/components/PageTransition";
import { Button } from "@/components/ui/button";
import { useProjectById } from "@/hooks/queires/useProjects";
import { motion } from "framer-motion";
import {
Calendar,
ChevronRight,
Clock,
ExternalLink,
Github,
Users,
} from "lucide-react";
import ReactMarkdown from "react-markdown";
import { Link, useNavigate, useParams } from "react-router-dom";
import remarkGfm from "remark-gfm";
interface Result {
label: string;
value: string | number;
}
interface ProjectData {
id: string;
category: string;
title: string;
description: string;
year: string | number;
duration: string;
team: string | number;
client: string;
liveUrl: string;
codeUrl?: string;
image: string;
results?: Result[];
fullDescription: string;
features?: string[];
technologies?: string[];
challenges?: string[];
gallery?: string[];
}
const CATEGORY_LABELS: Record<string, string> = {
web: "Web Development",
mobile: "Mobile App",
ai: "AI & Machine Learning",
cloud: "Cloud Solutions",
};
export default function ProjectDetails() {
const { slug } = useParams<{ slug: string }>();
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const project = getProjectBySlug(slug || '');
const { data, isLoading, isError } = useProjectById(id || "");
if (!project) {
const project: ProjectData | null =
data?.data?.data || data?.data || data || null;
if (isLoading) {
return (
<PageTransition>
<div className="min-h-screen bg-background flex items-center justify-center">
<div className="text-center">
<h1 className="text-4xl font-bold mb-4">Project Not Found</h1>
<p className="text-muted-foreground mb-8">The project you're looking for doesn't exist.</p>
<Button onClick={() => navigate('/projects')}>Back to Projects</Button>
<div className="flex flex-col items-center gap-4">
<div className="w-10 h-10 border-4 border-primary border-t-transparent rounded-full animate-spin" />
<span className="text-sm font-medium text-muted-foreground animate-pulse">
Loading project details...
</span>
</div>
</div>
</PageTransition>
);
}
const relatedProjects = getRelatedProjects(project);
const categoryLabels: Record<string, string> = {
web: 'Web Development',
mobile: 'Mobile App',
ai: 'AI & Machine Learning',
cloud: 'Cloud Solutions',
};
if (isError || !project) {
return (
<PageTransition>
<div className="min-h-screen bg-background flex items-center justify-center p-4">
<div className="text-center max-w-md border border-primary/20 bg-secondary/30 backdrop-blur-xl p-8 rounded-3xl shadow-2xl">
<h1 className="text-3xl font-bold tracking-tight mb-3">
Project Not Found
</h1>
<p className="text-muted-foreground mb-6">
The project you're looking for does not exist or could not be
loaded.
</p>
<Button
onClick={() => navigate("/projects")}
className="rounded-full w-full bg-primary text-primary-foreground hover:opacity-90 transition-all shadow-lg"
>
Back to Projects
</Button>
</div>
</div>
</PageTransition>
);
}
return (
<PageTransition>
<div className="min-h-screen bg-background overflow-x-hidden">
<div className="min-h-screen bg-background text-foreground overflow-x-hidden selection:bg-primary/30 selection:text-primary">
<Navbar />
{/* Hero */}
<section className="pt-32 pb-16 relative overflow-hidden">
<div className="absolute inset-0">
<div className="absolute top-1/4 left-1/4 w-96 h-96 bg-primary/20 rounded-full blur-3xl" />
<div className="absolute bottom-0 right-1/4 w-64 h-64 bg-neon-purple/20 rounded-full blur-3xl" />
{/* Hero Section */}
<section className="pt-36 pb-20 relative overflow-hidden">
{/* Ambient Background Glows */}
<div className="absolute inset-0 pointer-events-none z-0">
<div className="absolute top-1/4 left-[10%] w-[500px] h-[500px] bg-primary/15 rounded-full mix-blend-screen filter blur-[120px] animate-pulse duration-[6000ms]" />
<div className="absolute bottom-0 right-[10%] w-[400px] h-[400px] bg-purple-500/10 rounded-full mix-blend-screen filter blur-[100px] delay-1000" />
</div>
<div className="container mx-auto px-4 relative z-10">
{/* Breadcrumb */}
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.5 }}
className="flex items-center gap-2 text-sm text-muted-foreground mb-8"
<div className="container mx-auto px-4 md:px-6 relative z-10">
{/* Breadcrumb Navigation */}
<motion.nav
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4 }}
className="flex items-center gap-2 text-xs font-medium text-muted-foreground/80 mb-10"
aria-label="Breadcrumb"
>
<Link to="/" className="hover:text-primary transition-colors">Home</Link>
<ChevronRight className="w-4 h-4" />
<Link to="/projects" className="hover:text-primary transition-colors">Projects</Link>
<ChevronRight className="w-4 h-4" />
<span className="text-foreground">{project.title}</span>
</motion.div>
<Link to="/" className="hover:text-primary transition-colors">
Home
</Link>
<ChevronRight className="w-3.5 h-3.5 opacity-50" />
<Link
to="/projects"
className="hover:text-primary transition-colors"
>
Projects
</Link>
<ChevronRight className="w-3.5 h-3.5 opacity-50" />
<span className="text-foreground font-semibold truncate">
{project.title}
</span>
</motion.nav>
<div className="grid lg:grid-cols-2 gap-12 items-center">
{/* Content */}
<div>
{/* Content Block */}
<div className="flex flex-col">
<motion.span
initial={{ opacity: 0, y: 20 }}
initial={{ opacity: 0, y: 15 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
className="inline-block px-4 py-1.5 rounded-full bg-primary/20 text-primary text-sm font-medium mb-6"
transition={{ duration: 0.4 }}
className="self-start px-3.5 py-1 rounded-full bg-primary/15 text-primary border border-primary/20 text-xs font-semibold tracking-wider uppercase mb-6"
>
{categoryLabels[project.category]}
{CATEGORY_LABELS[project.category] || project.category}
</motion.span>
<motion.h1
initial={{ opacity: 0, y: 20 }}
initial={{ opacity: 0, y: 15 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.1 }}
className="text-4xl md:text-5xl font-bold mb-6"
transition={{ duration: 0.5, delay: 0.1 }}
className="text-4xl md:text-5xl lg:text-6xl font-black tracking-tight leading-none mb-6"
>
{project.title}
</motion.h1>
<motion.p
initial={{ opacity: 0, y: 20 }}
initial={{ opacity: 0, y: 15 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.2 }}
className="text-lg text-muted-foreground mb-8"
transition={{ duration: 0.5, delay: 0.2 }}
className="text-base md:text-lg text-muted-foreground leading-relaxed max-w-2xl mb-10"
>
{project.description}
</motion.p>
{/* Meta */}
{/* Project Metadata */}
<motion.div
initial={{ opacity: 0, y: 20 }}
initial={{ opacity: 0, y: 15 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.3 }}
className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8"
transition={{ duration: 0.5, delay: 0.3 }}
className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-10"
>
<div className="glass rounded-xl p-4 text-center">
<Calendar className="w-5 h-5 mx-auto mb-2 text-primary" />
<p className="text-sm text-muted-foreground">Year</p>
<p className="font-bold">{project.year}</p>
</div>
<div className="glass rounded-xl p-4 text-center">
<Clock className="w-5 h-5 mx-auto mb-2 text-primary" />
<p className="text-sm text-muted-foreground">Duration</p>
<p className="font-bold">{project.duration}</p>
</div>
<div className="glass rounded-xl p-4 text-center">
<Users className="w-5 h-5 mx-auto mb-2 text-primary" />
<p className="text-sm text-muted-foreground">Team</p>
<p className="font-bold">{project.team}</p>
</div>
<div className="glass rounded-xl p-4 text-center">
<p className="text-sm text-muted-foreground mb-1">Client</p>
<p className="font-bold text-sm">{project.client}</p>
</div>
{[
{ label: "Year", value: project.year, icon: Calendar },
{ label: "Duration", value: project.duration, icon: Clock },
{ label: "Team Size", value: project.team, icon: Users },
{ label: "Client", value: project.client },
].map((item, idx) => (
<div
key={item.label}
className="group flex flex-col items-center justify-center p-4 bg-secondary/40 backdrop-blur-md border border-border/50 rounded-2xl transition-all duration-300 hover:border-primary/30 hover:shadow-lg"
>
{item.icon ? (
<item.icon className="w-5 h-5 text-primary mb-2.5 transition-transform group-hover:scale-105" />
) : (
<div className="w-5 h-5 mb-2.5" />
)}
<span className="text-[10px] font-medium tracking-wider uppercase text-muted-foreground mb-1">
{item.label}
</span>
<span className="text-sm font-bold truncate max-w-full text-center">
{item.value}
</span>
</div>
))}
</motion.div>
{/* Actions */}
{/* Call to Actions */}
<motion.div
initial={{ opacity: 0, y: 20 }}
initial={{ opacity: 0, y: 15 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.4 }}
className="flex gap-4"
transition={{ duration: 0.5, delay: 0.4 }}
className="flex flex-wrap gap-4"
>
<Button className="rounded-full neon-glow">
<ExternalLink className="w-4 h-4 mr-2" />
View Live
</Button>
<Button variant="outline" className="rounded-full glass border-primary/30">
<Github className="w-4 h-4 mr-2" />
View Code
</Button>
<a
href={project.liveUrl}
target="_blank"
rel="noopener noreferrer"
className="w-full sm:w-auto"
>
<Button className="w-full sm:w-auto px-6 h-11 rounded-full bg-primary text-primary-foreground font-medium gap-2 shadow-xl shadow-primary/10 hover:opacity-90 transition-all duration-300">
<ExternalLink className="w-4 h-4" />
View Live
</Button>
</a>
{project.codeUrl && (
<a
href={project.codeUrl}
target="_blank"
rel="noopener noreferrer"
className="w-full sm:w-auto"
>
<Button
variant="outline"
className="w-full sm:w-auto px-6 h-11 rounded-full border-border/70 bg-secondary/30 backdrop-blur-sm font-medium gap-2 hover:bg-secondary transition-all duration-300"
>
<Github className="w-4 h-4 text-muted-foreground" />
View Code
</Button>
</a>
)}
</motion.div>
</div>
{/* Image */}
{/* Project Cover Visual */}
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.6, delay: 0.3 }}
className="rounded-3xl overflow-hidden neon-glow"
transition={{ duration: 0.6, delay: 0.2 }}
className="relative group rounded-3xl overflow-hidden border border-border/60 shadow-2xl bg-secondary/30 aspect-[4/3] lg:aspect-auto h-[450px]"
>
<img
src={project.image}
alt={project.title}
className="w-full h-[400px] object-cover"
className="w-full h-full object-cover object-center transition-transform duration-700 ease-out group-hover:scale-[1.02]"
loading="lazy"
/>
<div className="absolute inset-0 bg-gradient-to-t from-background/80 via-transparent to-transparent opacity-80" />
</motion.div>
</div>
</div>
</section>
{/* Results */}
<section className="py-16 bg-secondary/30">
<div className="container mx-auto px-4">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="text-center mb-12"
>
<h2 className="text-3xl font-bold">Key Results</h2>
</motion.div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-6 max-w-4xl mx-auto">
{project.results.map((result, index) => (
<motion.div
key={result.label}
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: index * 0.1 }}
className="glass-strong rounded-2xl p-6 text-center"
>
<p className="text-3xl md:text-4xl font-bold text-primary neon-text-glow mb-2">
{result.value}
</p>
<p className="text-muted-foreground text-sm">{result.label}</p>
</motion.div>
))}
</div>
</div>
</section>
{/* Details */}
<section className="py-24">
<div className="container mx-auto px-4">
<div className="grid lg:grid-cols-2 gap-16 max-w-6xl mx-auto">
{/* Full Description */}
{/* Key Results Section */}
{project.results && project.results.length > 0 && (
<section className="py-20 border-t border-b border-border/50 bg-secondary/20">
<div className="container mx-auto px-4 md:px-6">
<motion.div
initial={{ opacity: 0, x: -30 }}
whileInView={{ opacity: 1, x: 0 }}
initial={{ opacity: 0, y: 15 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="text-center max-w-2xl mx-auto mb-14"
>
<h3 className="text-2xl font-bold mb-6">About the Project</h3>
<div className="prose-content">
<h2 className="text-3xl font-bold tracking-tight mb-3">
Key Performance Outcomes
</h2>
<p className="text-sm text-muted-foreground">
Measurable impact and business value generated by this
project.
</p>
</motion.div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-6 max-w-5xl mx-auto">
{project.results.map((result, index) => (
<motion.div
key={result.label}
initial={{ opacity: 0, y: 25 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: index * 0.08, duration: 0.4 }}
className="flex flex-col items-center bg-background/60 backdrop-blur-md border border-border/40 rounded-3xl p-8 transition-all duration-300 hover:border-primary/30 hover:shadow-xl"
>
<span className="text-3xl md:text-4xl font-black text-primary tracking-tight mb-2">
{result.value}
</span>
<span className="text-xs font-medium text-center text-muted-foreground">
{result.label}
</span>
</motion.div>
))}
</div>
</div>
</section>
)}
{/* Main Details Section */}
<section className="py-28">
<div className="container mx-auto px-4 md:px-6">
<div className="grid lg:grid-cols-12 gap-16 max-w-7xl mx-auto">
{/* Main Text Content */}
<div className="lg:col-span-7 flex flex-col justify-start">
<h3 className="text-3xl font-bold tracking-tight mb-8">
About the Project
</h3>
<div className="prose prose-neutral dark:prose-invert max-w-none text-muted-foreground leading-relaxed">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={{
p: ({ children }) => <p className="text-muted-foreground leading-relaxed mb-4">{children}</p>,
strong: ({ children }) => <strong className="font-bold text-foreground">{children}</strong>,
ul: ({ children }) => <ul className="list-disc list-inside text-muted-foreground space-y-2 mb-4 ml-4">{children}</ul>,
li: ({ children }) => <li className="text-muted-foreground">{children}</li>,
p: ({ children }) => (
<p className="text-base md:text-lg text-muted-foreground leading-relaxed mb-6">
{children}
</p>
),
strong: ({ children }) => (
<strong className="font-semibold text-foreground">
{children}
</strong>
),
ul: ({ children }) => (
<ul className="list-disc list-inside space-y-2 mb-6 ml-2 text-muted-foreground">
{children}
</ul>
),
li: ({ children }) => (
<li className="text-muted-foreground">{children}</li>
),
}}
>
{project.fullDescription}
</ReactMarkdown>
</div>
</motion.div>
</div>
{/* Features & Tech */}
<div className="space-y-12">
<motion.div
initial={{ opacity: 0, x: 30 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
>
<h3 className="text-2xl font-bold mb-6">Key Features</h3>
<ul className="space-y-3">
{project.features.map((feature, index) => (
<li key={index} className="flex items-start gap-3">
<span className="w-2 h-2 rounded-full bg-primary mt-2 flex-shrink-0" />
<span className="text-muted-foreground">{feature}</span>
</li>
))}
</ul>
</motion.div>
<motion.div
initial={{ opacity: 0, x: 30 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.1 }}
>
<h3 className="text-2xl font-bold mb-6">Technologies Used</h3>
<div className="flex flex-wrap gap-3">
{project.technologies.map((tech) => (
<span
key={tech}
className="px-4 py-2 rounded-full glass text-sm font-medium border border-primary/30"
>
{tech}
</span>
))}
{/* Sidebar Capabilities */}
<div className="lg:col-span-5 flex flex-col gap-12">
{/* Features */}
{project.features && project.features.length > 0 && (
<div>
<h3 className="text-xl font-bold tracking-tight mb-6">
Key Capabilities
</h3>
<ul className="space-y-4">
{project.features.map((feature, index) => (
<li key={index} className="flex items-start gap-4">
<span className="flex-shrink-0 w-1.5 h-1.5 rounded-full bg-primary mt-2.5" />
<span className="text-sm md:text-base text-muted-foreground leading-snug">
{feature}
</span>
</li>
))}
</ul>
</div>
</motion.div>
)}
<motion.div
initial={{ opacity: 0, x: 30 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.2 }}
>
<h3 className="text-2xl font-bold mb-6">Challenges Solved</h3>
<ul className="space-y-3">
{project.challenges.map((challenge, index) => (
<li key={index} className="flex items-start gap-3">
<span className="w-2 h-2 rounded-full bg-neon-purple mt-2 flex-shrink-0" />
<span className="text-muted-foreground">{challenge}</span>
</li>
))}
</ul>
</motion.div>
{/* Technologies Used */}
{project.technologies && project.technologies.length > 0 && (
<div className="border-t border-border/40 pt-8">
<h3 className="text-xl font-bold tracking-tight mb-6">
Technologies Used
</h3>
<div className="flex flex-wrap gap-2.5">
{project.technologies.map((tech) => (
<span
key={tech}
className="px-3.5 py-1.5 bg-secondary text-xs font-medium rounded-full border border-border/60 text-muted-foreground"
>
{tech}
</span>
))}
</div>
</div>
)}
{/* Challenges Solved */}
{project.challenges && project.challenges.length > 0 && (
<div className="border-t border-border/40 pt-8">
<h3 className="text-xl font-bold tracking-tight mb-6">
Challenges Solved
</h3>
<ul className="space-y-4">
{project.challenges.map((challenge, index) => (
<li key={index} className="flex items-start gap-4">
<span className="flex-shrink-0 w-1.5 h-1.5 rounded-full bg-purple-500 mt-2.5" />
<span className="text-sm md:text-base text-muted-foreground leading-snug">
{challenge}
</span>
</li>
))}
</ul>
</div>
)}
</div>
</div>
</div>
</section>
{/* Gallery */}
<section className="py-16 bg-secondary/30">
<div className="container mx-auto px-4">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="text-center mb-12"
>
<h2 className="text-3xl font-bold">Project Gallery</h2>
</motion.div>
{/* Gallery Section */}
{project.gallery && project.gallery.length > 0 && (
<section className="py-20 border-t border-border/50 bg-secondary/30">
<div className="container mx-auto px-4 md:px-6">
<div className="text-center max-w-2xl mx-auto mb-14">
<h2 className="text-3xl font-bold tracking-tight mb-3">
Project Gallery
</h2>
<p className="text-sm text-muted-foreground">
A visual overview of the implementation and user interface.
</p>
</div>
<div className="grid md:grid-cols-3 gap-6 max-w-5xl mx-auto">
{project.gallery.map((image, index) => (
<motion.div
key={index}
initial={{ opacity: 0, scale: 0.9 }}
whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true }}
transition={{ delay: index * 0.1 }}
className="rounded-2xl overflow-hidden hover:neon-glow transition-all duration-500"
>
<img
src={image}
alt={`${project.title} screenshot ${index + 1}`}
className="w-full h-48 object-cover hover:scale-105 transition-transform duration-500"
/>
</motion.div>
))}
</div>
</div>
</section>
{/* Related Projects */}
{relatedProjects.length > 0 && (
<section className="py-24">
<div className="container mx-auto px-4">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="text-center mb-12"
>
<h2 className="text-3xl font-bold">Related Projects</h2>
</motion.div>
<div className="grid md:grid-cols-3 gap-8 max-w-5xl mx-auto">
{relatedProjects.map((relatedProject, index) => (
<motion.article
key={relatedProject.id}
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
<div className="grid md:grid-cols-3 gap-6 max-w-6xl mx-auto">
{project.gallery.map((image, index) => (
<motion.div
key={index}
initial={{ opacity: 0, scale: 0.97 }}
whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true }}
transition={{ delay: index * 0.1 }}
className="group glass rounded-2xl overflow-hidden hover:neon-glow transition-all duration-500"
transition={{ delay: index * 0.06, duration: 0.4 }}
className="group relative aspect-[4/3] rounded-3xl overflow-hidden border border-border/60 bg-background/40"
>
<Link to={`/projects/${relatedProject.slug}`}>
<div className="relative h-40 overflow-hidden">
<img
src={relatedProject.image}
alt={relatedProject.title}
className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-110"
/>
</div>
<div className="p-5">
<h3 className="font-bold mb-2 group-hover:text-primary transition-colors">
{relatedProject.title}
</h3>
<p className="text-sm text-muted-foreground line-clamp-2">
{relatedProject.description}
</p>
</div>
</Link>
</motion.article>
<img
src={image}
alt={`${project.title} visual display ${index + 1}`}
className="w-full h-full object-cover object-center transition-all duration-500 group-hover:scale-105"
loading="lazy"
/>
<div className="absolute inset-0 bg-gradient-to-t from-background/50 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
</motion.div>
))}
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="text-center mt-12"
>
<Link to="/projects">
<Button variant="outline" className="rounded-full px-8 glass border-primary/30 hover:neon-glow">
View All Projects
</Button>
</Link>
</motion.div>
</div>
</section>
)}