feat:added project details page with prod. api
This commit is contained in:
+1
-1
@@ -23,7 +23,7 @@ function AnimatedRoutes() {
|
|||||||
<Routes location={location} key={location.pathname}>
|
<Routes location={location} key={location.pathname}>
|
||||||
<Route path="/" element={<Index />} />
|
<Route path="/" element={<Index />} />
|
||||||
<Route path="/projects" element={<Projects />} />
|
<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" element={<Blog />} />
|
||||||
<Route path="/blog/:id" element={<BlogArticle />} /> */}
|
<Route path="/blog/:id" element={<BlogArticle />} /> */}
|
||||||
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
|
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useProjects } from "@/hooks/queires/useProjects";
|
import { useProjects } from "@/hooks/queires/useProjects";
|
||||||
import { motion, useInView } from "framer-motion";
|
import { motion, useInView } from "framer-motion";
|
||||||
import { ArrowRight, ExternalLink } from "lucide-react";
|
import { ArrowRight } from "lucide-react";
|
||||||
import { useRef, useState } from "react";
|
import { useRef } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import { ProjectCard } from "./ProjectCard";
|
||||||
|
|
||||||
const gradientColors = [
|
const gradientColors = [
|
||||||
"from-neon-blue/90",
|
"from-neon-blue/90",
|
||||||
@@ -98,99 +99,3 @@ export default function ProjectsSection() {
|
|||||||
</section>
|
</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
@@ -1,366 +1,432 @@
|
|||||||
import { useParams, Link, useNavigate } from 'react-router-dom';
|
import Footer from "@/components/Footer";
|
||||||
import { motion } from 'framer-motion';
|
import Navbar from "@/components/Navbar";
|
||||||
import { ArrowLeft, ExternalLink, Github, Calendar, Clock, Users, ChevronRight } from 'lucide-react';
|
import PageTransition from "@/components/PageTransition";
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from "@/components/ui/button";
|
||||||
import ReactMarkdown from 'react-markdown';
|
import { useProjectById } from "@/hooks/queires/useProjects";
|
||||||
import remarkGfm from 'remark-gfm';
|
import { motion } from "framer-motion";
|
||||||
import Navbar from '@/components/Navbar';
|
import {
|
||||||
import Footer from '@/components/Footer';
|
Calendar,
|
||||||
import PageTransition from '@/components/PageTransition';
|
ChevronRight,
|
||||||
import { getProjectBySlug, getRelatedProjects } from '@/data/projectData';
|
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() {
|
export default function ProjectDetails() {
|
||||||
const { slug } = useParams<{ slug: string }>();
|
const { id } = useParams<{ id: string }>();
|
||||||
const navigate = useNavigate();
|
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 (
|
return (
|
||||||
<PageTransition>
|
<PageTransition>
|
||||||
<div className="min-h-screen bg-background flex items-center justify-center">
|
<div className="min-h-screen bg-background flex items-center justify-center">
|
||||||
<div className="text-center">
|
<div className="flex flex-col items-center gap-4">
|
||||||
<h1 className="text-4xl font-bold mb-4">Project Not Found</h1>
|
<div className="w-10 h-10 border-4 border-primary border-t-transparent rounded-full animate-spin" />
|
||||||
<p className="text-muted-foreground mb-8">The project you're looking for doesn't exist.</p>
|
<span className="text-sm font-medium text-muted-foreground animate-pulse">
|
||||||
<Button onClick={() => navigate('/projects')}>Back to Projects</Button>
|
Loading project details...
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</PageTransition>
|
</PageTransition>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const relatedProjects = getRelatedProjects(project);
|
if (isError || !project) {
|
||||||
const categoryLabels: Record<string, string> = {
|
return (
|
||||||
web: 'Web Development',
|
<PageTransition>
|
||||||
mobile: 'Mobile App',
|
<div className="min-h-screen bg-background flex items-center justify-center p-4">
|
||||||
ai: 'AI & Machine Learning',
|
<div className="text-center max-w-md border border-primary/20 bg-secondary/30 backdrop-blur-xl p-8 rounded-3xl shadow-2xl">
|
||||||
cloud: 'Cloud Solutions',
|
<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 (
|
return (
|
||||||
<PageTransition>
|
<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 />
|
<Navbar />
|
||||||
|
|
||||||
{/* Hero */}
|
{/* Hero Section */}
|
||||||
<section className="pt-32 pb-16 relative overflow-hidden">
|
<section className="pt-36 pb-20 relative overflow-hidden">
|
||||||
<div className="absolute inset-0">
|
{/* Ambient Background Glows */}
|
||||||
<div className="absolute top-1/4 left-1/4 w-96 h-96 bg-primary/20 rounded-full blur-3xl" />
|
<div className="absolute inset-0 pointer-events-none z-0">
|
||||||
<div className="absolute bottom-0 right-1/4 w-64 h-64 bg-neon-purple/20 rounded-full blur-3xl" />
|
<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>
|
||||||
|
|
||||||
<div className="container mx-auto px-4 relative z-10">
|
<div className="container mx-auto px-4 md:px-6 relative z-10">
|
||||||
{/* Breadcrumb */}
|
{/* Breadcrumb Navigation */}
|
||||||
<motion.div
|
<motion.nav
|
||||||
initial={{ opacity: 0, x: -20 }}
|
initial={{ opacity: 0, y: -10 }}
|
||||||
animate={{ opacity: 1, x: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 0.5 }}
|
transition={{ duration: 0.4 }}
|
||||||
className="flex items-center gap-2 text-sm text-muted-foreground mb-8"
|
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>
|
<Link to="/" className="hover:text-primary transition-colors">
|
||||||
<ChevronRight className="w-4 h-4" />
|
Home
|
||||||
<Link to="/projects" className="hover:text-primary transition-colors">Projects</Link>
|
</Link>
|
||||||
<ChevronRight className="w-4 h-4" />
|
<ChevronRight className="w-3.5 h-3.5 opacity-50" />
|
||||||
<span className="text-foreground">{project.title}</span>
|
<Link
|
||||||
</motion.div>
|
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">
|
<div className="grid lg:grid-cols-2 gap-12 items-center">
|
||||||
{/* Content */}
|
{/* Content Block */}
|
||||||
<div>
|
<div className="flex flex-col">
|
||||||
<motion.span
|
<motion.span
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 15 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 0.5 }}
|
transition={{ duration: 0.4 }}
|
||||||
className="inline-block px-4 py-1.5 rounded-full bg-primary/20 text-primary text-sm font-medium mb-6"
|
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.span>
|
||||||
|
|
||||||
<motion.h1
|
<motion.h1
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 15 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 0.6, delay: 0.1 }}
|
transition={{ duration: 0.5, delay: 0.1 }}
|
||||||
className="text-4xl md:text-5xl font-bold mb-6"
|
className="text-4xl md:text-5xl lg:text-6xl font-black tracking-tight leading-none mb-6"
|
||||||
>
|
>
|
||||||
{project.title}
|
{project.title}
|
||||||
</motion.h1>
|
</motion.h1>
|
||||||
|
|
||||||
<motion.p
|
<motion.p
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 15 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 0.6, delay: 0.2 }}
|
transition={{ duration: 0.5, delay: 0.2 }}
|
||||||
className="text-lg text-muted-foreground mb-8"
|
className="text-base md:text-lg text-muted-foreground leading-relaxed max-w-2xl mb-10"
|
||||||
>
|
>
|
||||||
{project.description}
|
{project.description}
|
||||||
</motion.p>
|
</motion.p>
|
||||||
|
|
||||||
{/* Meta */}
|
{/* Project Metadata */}
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 15 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 0.6, delay: 0.3 }}
|
transition={{ duration: 0.5, delay: 0.3 }}
|
||||||
className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8"
|
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" />
|
{ label: "Year", value: project.year, icon: Calendar },
|
||||||
<p className="text-sm text-muted-foreground">Year</p>
|
{ label: "Duration", value: project.duration, icon: Clock },
|
||||||
<p className="font-bold">{project.year}</p>
|
{ label: "Team Size", value: project.team, icon: Users },
|
||||||
</div>
|
{ label: "Client", value: project.client },
|
||||||
<div className="glass rounded-xl p-4 text-center">
|
].map((item, idx) => (
|
||||||
<Clock className="w-5 h-5 mx-auto mb-2 text-primary" />
|
<div
|
||||||
<p className="text-sm text-muted-foreground">Duration</p>
|
key={item.label}
|
||||||
<p className="font-bold">{project.duration}</p>
|
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"
|
||||||
</div>
|
>
|
||||||
<div className="glass rounded-xl p-4 text-center">
|
{item.icon ? (
|
||||||
<Users className="w-5 h-5 mx-auto mb-2 text-primary" />
|
<item.icon className="w-5 h-5 text-primary mb-2.5 transition-transform group-hover:scale-105" />
|
||||||
<p className="text-sm text-muted-foreground">Team</p>
|
) : (
|
||||||
<p className="font-bold">{project.team}</p>
|
<div className="w-5 h-5 mb-2.5" />
|
||||||
</div>
|
)}
|
||||||
<div className="glass rounded-xl p-4 text-center">
|
<span className="text-[10px] font-medium tracking-wider uppercase text-muted-foreground mb-1">
|
||||||
<p className="text-sm text-muted-foreground mb-1">Client</p>
|
{item.label}
|
||||||
<p className="font-bold text-sm">{project.client}</p>
|
</span>
|
||||||
</div>
|
<span className="text-sm font-bold truncate max-w-full text-center">
|
||||||
|
{item.value}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
{/* Actions */}
|
{/* Call to Actions */}
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 15 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 0.6, delay: 0.4 }}
|
transition={{ duration: 0.5, delay: 0.4 }}
|
||||||
className="flex gap-4"
|
className="flex flex-wrap gap-4"
|
||||||
>
|
>
|
||||||
<Button className="rounded-full neon-glow">
|
<a
|
||||||
<ExternalLink className="w-4 h-4 mr-2" />
|
href={project.liveUrl}
|
||||||
View Live
|
target="_blank"
|
||||||
</Button>
|
rel="noopener noreferrer"
|
||||||
<Button variant="outline" className="rounded-full glass border-primary/30">
|
className="w-full sm:w-auto"
|
||||||
<Github className="w-4 h-4 mr-2" />
|
>
|
||||||
View Code
|
<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">
|
||||||
</Button>
|
<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>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Image */}
|
{/* Project Cover Visual */}
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, scale: 0.9 }}
|
initial={{ opacity: 0, scale: 0.95 }}
|
||||||
animate={{ opacity: 1, scale: 1 }}
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
transition={{ duration: 0.6, delay: 0.3 }}
|
transition={{ duration: 0.6, delay: 0.2 }}
|
||||||
className="rounded-3xl overflow-hidden neon-glow"
|
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
|
<img
|
||||||
src={project.image}
|
src={project.image}
|
||||||
alt={project.title}
|
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>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Results */}
|
{/* Key Results Section */}
|
||||||
<section className="py-16 bg-secondary/30">
|
{project.results && project.results.length > 0 && (
|
||||||
<div className="container mx-auto px-4">
|
<section className="py-20 border-t border-b border-border/50 bg-secondary/20">
|
||||||
<motion.div
|
<div className="container mx-auto px-4 md:px-6">
|
||||||
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 */}
|
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, x: -30 }}
|
initial={{ opacity: 0, y: 15 }}
|
||||||
whileInView={{ opacity: 1, x: 0 }}
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
viewport={{ once: true }}
|
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>
|
<h2 className="text-3xl font-bold tracking-tight mb-3">
|
||||||
<div className="prose-content">
|
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
|
<ReactMarkdown
|
||||||
remarkPlugins={[remarkGfm]}
|
remarkPlugins={[remarkGfm]}
|
||||||
components={{
|
components={{
|
||||||
p: ({ children }) => <p className="text-muted-foreground leading-relaxed mb-4">{children}</p>,
|
p: ({ children }) => (
|
||||||
strong: ({ children }) => <strong className="font-bold text-foreground">{children}</strong>,
|
<p className="text-base md:text-lg text-muted-foreground leading-relaxed mb-6">
|
||||||
ul: ({ children }) => <ul className="list-disc list-inside text-muted-foreground space-y-2 mb-4 ml-4">{children}</ul>,
|
{children}
|
||||||
li: ({ children }) => <li className="text-muted-foreground">{children}</li>,
|
</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}
|
{project.fullDescription}
|
||||||
</ReactMarkdown>
|
</ReactMarkdown>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</div>
|
||||||
|
|
||||||
{/* Features & Tech */}
|
{/* Sidebar Capabilities */}
|
||||||
<div className="space-y-12">
|
<div className="lg:col-span-5 flex flex-col gap-12">
|
||||||
<motion.div
|
{/* Features */}
|
||||||
initial={{ opacity: 0, x: 30 }}
|
{project.features && project.features.length > 0 && (
|
||||||
whileInView={{ opacity: 1, x: 0 }}
|
<div>
|
||||||
viewport={{ once: true }}
|
<h3 className="text-xl font-bold tracking-tight mb-6">
|
||||||
>
|
Key Capabilities
|
||||||
<h3 className="text-2xl font-bold mb-6">Key Features</h3>
|
</h3>
|
||||||
<ul className="space-y-3">
|
<ul className="space-y-4">
|
||||||
{project.features.map((feature, index) => (
|
{project.features.map((feature, index) => (
|
||||||
<li key={index} className="flex items-start gap-3">
|
<li key={index} className="flex items-start gap-4">
|
||||||
<span className="w-2 h-2 rounded-full bg-primary mt-2 flex-shrink-0" />
|
<span className="flex-shrink-0 w-1.5 h-1.5 rounded-full bg-primary mt-2.5" />
|
||||||
<span className="text-muted-foreground">{feature}</span>
|
<span className="text-sm md:text-base text-muted-foreground leading-snug">
|
||||||
</li>
|
{feature}
|
||||||
))}
|
</span>
|
||||||
</ul>
|
</li>
|
||||||
</motion.div>
|
))}
|
||||||
|
</ul>
|
||||||
<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>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
)}
|
||||||
|
|
||||||
<motion.div
|
{/* Technologies Used */}
|
||||||
initial={{ opacity: 0, x: 30 }}
|
{project.technologies && project.technologies.length > 0 && (
|
||||||
whileInView={{ opacity: 1, x: 0 }}
|
<div className="border-t border-border/40 pt-8">
|
||||||
viewport={{ once: true }}
|
<h3 className="text-xl font-bold tracking-tight mb-6">
|
||||||
transition={{ delay: 0.2 }}
|
Technologies Used
|
||||||
>
|
</h3>
|
||||||
<h3 className="text-2xl font-bold mb-6">Challenges Solved</h3>
|
<div className="flex flex-wrap gap-2.5">
|
||||||
<ul className="space-y-3">
|
{project.technologies.map((tech) => (
|
||||||
{project.challenges.map((challenge, index) => (
|
<span
|
||||||
<li key={index} className="flex items-start gap-3">
|
key={tech}
|
||||||
<span className="w-2 h-2 rounded-full bg-neon-purple mt-2 flex-shrink-0" />
|
className="px-3.5 py-1.5 bg-secondary text-xs font-medium rounded-full border border-border/60 text-muted-foreground"
|
||||||
<span className="text-muted-foreground">{challenge}</span>
|
>
|
||||||
</li>
|
{tech}
|
||||||
))}
|
</span>
|
||||||
</ul>
|
))}
|
||||||
</motion.div>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Gallery */}
|
{/* Gallery Section */}
|
||||||
<section className="py-16 bg-secondary/30">
|
{project.gallery && project.gallery.length > 0 && (
|
||||||
<div className="container mx-auto px-4">
|
<section className="py-20 border-t border-border/50 bg-secondary/30">
|
||||||
<motion.div
|
<div className="container mx-auto px-4 md:px-6">
|
||||||
initial={{ opacity: 0, y: 20 }}
|
<div className="text-center max-w-2xl mx-auto mb-14">
|
||||||
whileInView={{ opacity: 1, y: 0 }}
|
<h2 className="text-3xl font-bold tracking-tight mb-3">
|
||||||
viewport={{ once: true }}
|
Project Gallery
|
||||||
className="text-center mb-12"
|
</h2>
|
||||||
>
|
<p className="text-sm text-muted-foreground">
|
||||||
<h2 className="text-3xl font-bold">Project Gallery</h2>
|
A visual overview of the implementation and user interface.
|
||||||
</motion.div>
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="grid md:grid-cols-3 gap-6 max-w-5xl mx-auto">
|
<div className="grid md:grid-cols-3 gap-6 max-w-6xl mx-auto">
|
||||||
{project.gallery.map((image, index) => (
|
{project.gallery.map((image, index) => (
|
||||||
<motion.div
|
<motion.div
|
||||||
key={index}
|
key={index}
|
||||||
initial={{ opacity: 0, scale: 0.9 }}
|
initial={{ opacity: 0, scale: 0.97 }}
|
||||||
whileInView={{ opacity: 1, scale: 1 }}
|
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 }}
|
|
||||||
viewport={{ once: true }}
|
viewport={{ once: true }}
|
||||||
transition={{ delay: index * 0.1 }}
|
transition={{ delay: index * 0.06, duration: 0.4 }}
|
||||||
className="group glass rounded-2xl overflow-hidden hover:neon-glow transition-all duration-500"
|
className="group relative aspect-[4/3] rounded-3xl overflow-hidden border border-border/60 bg-background/40"
|
||||||
>
|
>
|
||||||
<Link to={`/projects/${relatedProject.slug}`}>
|
<img
|
||||||
<div className="relative h-40 overflow-hidden">
|
src={image}
|
||||||
<img
|
alt={`${project.title} visual display ${index + 1}`}
|
||||||
src={relatedProject.image}
|
className="w-full h-full object-cover object-center transition-all duration-500 group-hover:scale-105"
|
||||||
alt={relatedProject.title}
|
loading="lazy"
|
||||||
className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-110"
|
/>
|
||||||
/>
|
<div className="absolute inset-0 bg-gradient-to-t from-background/50 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
|
||||||
</div>
|
</motion.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>
|
|
||||||
))}
|
))}
|
||||||
</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>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user