refactor:redesign some components

This commit is contained in:
sanjidarimi
2026-06-17 20:15:02 +06:00
parent 8805bab731
commit 7b6531c41a
7 changed files with 624 additions and 712 deletions
+3 -73
View File
@@ -1,73 +1,3 @@
# Welcome to your Lovable project
## Project info
**URL**: https://lovable.dev/projects/REPLACE_WITH_PROJECT_ID
## How can I edit this code?
There are several ways of editing your application.
**Use Lovable**
Simply visit the [Lovable Project](https://lovable.dev/projects/REPLACE_WITH_PROJECT_ID) and start prompting.
Changes made via Lovable will be committed automatically to this repo.
**Use your preferred IDE**
If you want to work locally using your own IDE, you can clone this repo and push changes. Pushed changes will also be reflected in Lovable.
The only requirement is having Node.js & npm installed - [install with nvm](https://github.com/nvm-sh/nvm#installing-and-updating)
Follow these steps:
```sh
# Step 1: Clone the repository using the project's Git URL.
git clone <YOUR_GIT_URL>
# Step 2: Navigate to the project directory.
cd <YOUR_PROJECT_NAME>
# Step 3: Install the necessary dependencies.
npm i
# Step 4: Start the development server with auto-reloading and an instant preview.
npm run dev
```
**Edit a file directly in GitHub**
- Navigate to the desired file(s).
- Click the "Edit" button (pencil icon) at the top right of the file view.
- Make your changes and commit the changes.
**Use GitHub Codespaces**
- Navigate to the main page of your repository.
- Click on the "Code" button (green button) near the top right.
- Select the "Codespaces" tab.
- Click on "New codespace" to launch a new Codespace environment.
- Edit files directly within the Codespace and commit and push your changes once you're done.
## What technologies are used for this project?
This project is built with:
- Vite
- TypeScript
- React
- shadcn-ui
- Tailwind CSS
## How can I deploy this project?
Simply open [Lovable](https://lovable.dev/projects/REPLACE_WITH_PROJECT_ID) and click on Share -> Publish.
## Can I connect a custom domain to my Lovable project?
Yes, you can!
To connect a domain, navigate to Project > Settings > Domains and click Connect Domain.
Read more here: [Setting up a custom domain](https://docs.lovable.dev/features/custom-domain#custom-domain)
techzaa.alpha@gmail.com,
</br>
TechZaa@@##123
+122 -51
View File
@@ -1,102 +1,173 @@
import { motion } from "framer-motion";
import { ExternalLink } from "lucide-react";
import { useState } from "react";
import { motion, useReducedMotion } from "framer-motion";
import { ArrowUpRight, Layers } from "lucide-react";
import { useId, useState } from "react";
import { Link } from "react-router-dom";
export function ProjectCard({ project, color, index, isInView }) {
interface ProjectPayload {
_id: string;
title: string;
category: string[];
isFeatured: boolean;
technologies: string[];
publishedYear: string | number;
previewUrl: string;
image?: string;
}
interface ProjectCardProps {
project: ProjectPayload;
index: number;
isInView: boolean;
}
export function ProjectCard({ project, index, isInView }: ProjectCardProps) {
const [mousePosition, setMousePosition] = useState({ x: 50, y: 50 });
const [isHovered, setIsHovered] = useState(false);
const shouldReduceMotion = useReducedMotion();
const titleId = useId();
const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
if (shouldReduceMotion) return;
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 });
};
// Safe Fallback Asset Frame
const projectImage =
project.image ||
"https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?q=80&w=800&auto=format&fit=crop";
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"
initial={{ opacity: 0, y: shouldReduceMotion ? 0 : 40 }}
animate={isInView ? { opacity: 1, y: 0 } : {}}
transition={{
duration: 0.5,
delay: index * 0.05,
ease: [0.215, 0.61, 0.355, 1],
}}
className="group relative bg-card border border-border/60 hover:border-primary/30 rounded-2xl overflow-hidden shadow-md hover:shadow-xl transition-all duration-300 flex flex-col h-full transform-gpu"
onMouseMove={handleMouseMove}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => {
setIsHovered(false);
setMousePosition({ x: 50, y: 50 });
}}
aria-labelledby={titleId}
>
{/* Main Card Container with 3D Tilt */}
{/* Interactive Visual Window */}
<div
className="relative h-full rounded-3xl overflow-hidden shadow-2xl transition-transform duration-300"
className="aspect-[16/10] overflow-hidden relative bg-muted w-full border-b border-border/40"
style={{
transform: isHovered
? `perspective(1000px) rotateX(${(50 - mousePosition.y) * 0.12}deg) rotateY(${(mousePosition.x - 50) * 0.15}deg)`
transform:
isHovered && !shouldReduceMotion
? `perspective(1000px) rotateX(${(50 - mousePosition.y) * 0.06}deg) rotateY(${(mousePosition.x - 50) * 0.06}deg)`
: "perspective(1000px) rotateX(0deg) rotateY(0deg)",
transition: isHovered
? "transform 0.1s ease-out"
: "transform 0.4s ease-out",
? "transform 0.05s ease-out"
: "transform 0.3s 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"
src={projectImage}
alt={`Interface screenshot overview for ${project.title}`}
className="w-full h-full object-cover transition-transform duration-700 ease-out group-hover:scale-[1.04]"
loading="lazy"
/>
{/* Dynamic Shine Overlay */}
{/* Techzaa Radial Spotlight Overlay */}
{!shouldReduceMotion && (
<div
className="absolute inset-0 opacity-0 group-hover:opacity-30 transition-opacity duration-300 pointer-events-none"
className="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity duration-500 pointer-events-none mix-blend-screen"
style={{
background: `radial-gradient(circle at ${mousePosition.x}% ${mousePosition.y}%, rgba(255,255,255,0.8) 0%, transparent 60%)`,
background: `radial-gradient(circle 180px at ${mousePosition.x}% ${mousePosition.y}%, rgba(var(--primary-rgb, 99, 102, 241), 0.15), transparent 80%)`,
}}
/>
)}
{/* Absolute Year Floating Tag */}
<span className="absolute top-3 right-3 text-[10px] font-bold px-2 py-0.5 rounded bg-background/80 backdrop-blur-md border border-border/40 text-muted-foreground tracking-wider">
{project.publishedYear}
</span>
</div>
{/* Gradient Overlay */}
{/* Structured Informational Body Content */}
<div className="p-6 flex flex-col flex-grow justify-between space-y-6">
<div className="space-y-3">
{/* Category Chip List Mapping handles strings or arrays cleanly */}
<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"
className="flex flex-wrap gap-1"
aria-label="Project classifications"
>
{Array.isArray(project.category) ? (
project.category.slice(0, 3).map((cat) => (
<span
key={cat}
className="inline-flex items-center text-[10px] font-bold uppercase tracking-wider text-primary bg-primary/5 border border-primary/10 px-2 py-0.5 rounded"
>
{cat}
</span>
))
) : (
<span className="inline-flex items-center text-[10px] font-bold uppercase tracking-wider text-primary bg-primary/5 border border-primary/10 px-2 py-0.5 rounded">
{project.category}
</motion.span>
</span>
)}
</div>
{/* Title */}
<h3 className="text-2xl md:text-3xl font-bold text-white leading-tight tracking-tight">
{/* Title Identifier linked to parent card components via standard a11y specs */}
<h3
id={titleId}
className="text-xl font-bold tracking-tight text-foreground leading-snug group-hover:text-primary transition-colors duration-200"
>
{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"
{/* Previewing active Framework stacks cleanly underneath */}
{project.technologies && project.technologies.length > 0 && (
<div className="flex flex-wrap gap-1 pt-1">
{project.technologies.slice(0, 4).map((tech) => (
<span
key={tech}
className="text-[11px] font-medium text-muted-foreground bg-secondary/40 px-2 py-0.5 rounded border border-border/30"
>
<span className="font-medium text-sm tracking-wider">live</span>
<ExternalLink className="w-5 h-5 transition-transform group-hover:rotate-45" />
</Link>
{tech}
</span>
))}
{project.technologies.length > 4 && (
<span className="text-[10px] font-semibold text-muted-foreground/60 px-1.5 py-0.5">
+{project.technologies.length - 4} more
</span>
)}
</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" />
{/* Action Controls Matrix Bar */}
<div className="flex items-center justify-between pt-4 border-t border-border/40 w-full text-xs font-semibold tracking-wide">
<Link
to={`/projects/${project._id}`}
className="inline-flex items-center text-muted-foreground hover:text-foreground transition-colors py-1 focus-visible:outline-none focus-visible:underline"
aria-label={`Read technical case documentation for ${project.title}`}
>
<Layers className="w-3.5 h-3.5 mr-1.5 text-primary/70" />
Case Study
</Link>
<a
href={project.previewUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center text-primary hover:opacity-80 transition-opacity py-1 focus-visible:outline-none focus-visible:underline"
aria-label={`Launch deployment window for ${project.title}`}
>
Live Platform
<ArrowUpRight className="w-3.5 h-3.5 ml-1 transition-transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5" />
</a>
</div>
</div>
</motion.div>
</Link>
);
}
+9 -9
View File
@@ -4,8 +4,8 @@ import { motion, useInView } from "framer-motion";
import { ArrowRight } from "lucide-react";
import { useRef } from "react";
import { Link } from "react-router-dom";
import { ProjectCard } from "./ProjectCard";
import PremiumBadge from "../shared/PremiumBadge";
import { ProjectCard } from "./ProjectCard";
const gradientColors = [
"from-neon-blue/90",
@@ -19,9 +19,9 @@ export default function ProjectsSection() {
fields: "category, title, image, liveUrl",
limit: 6,
});
const projects = projectsData?.data.data.result || [];
console.log(projectsData?.data.data);
const projects = projectsData?.data.data || [];
console.log("project from homepage", projects);
const ref = useRef<HTMLElement>(null);
const isInView = useInView(ref, { once: true, margin: "-100px" });
@@ -50,9 +50,12 @@ export default function ProjectsSection() {
transition={{ duration: 0.7 }}
className="text-center mb-20"
>
<PremiumBadge text="our projects"/>
<PremiumBadge text="our projects" />
<h2 className="text-4xl md:text-5xl lg:text-6xl font-bold mb-6">
Featured <span className="text-transparent bg-clip-text bg-gradient-to-r from-primary to-accent-foreground">Projects</span>
Featured{" "}
<span className="text-transparent bg-clip-text bg-gradient-to-r from-primary to-accent-foreground">
Projects
</span>
</h2>
<p className="text-muted-foreground max-w-2xl mx-auto text-lg">
Crafted with passion. Delivered with excellence.
@@ -62,13 +65,10 @@ export default function ProjectsSection() {
{/* Projects Grid */}
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8 mb-16">
{projects.map((project, index) => {
const color = gradientColors[index % gradientColors.length];
return (
<ProjectCard
key={project._id}
project={project}
color={color}
index={index}
isInView={isInView}
/>
+2 -13
View File
@@ -59,7 +59,7 @@ export default function TeamSection() {
}
pagination={{ clickable: true }}
modules={[Autoplay, Pagination]}
className="pb-12" // Space for pagination dots
className="pb-12"
breakpoints={{
640: { slidesPerView: 2 },
1024: { slidesPerView: 3 },
@@ -84,18 +84,7 @@ export default function TeamSection() {
className="absolute inset-0 w-full h-full object-cover object-top transition-transform duration-500 group-hover:scale-110"
/>
</div>
<div className="absolute inset-0 rounded-full bg-background/80 backdrop-blur-sm flex items-center justify-center gap-3 opacity-0 group-hover:opacity-100 transition-opacity duration-300">
<Link to={member.linkedin} target="_blank">
<Linkedin className="w-5 h-5 cursor-pointer hover:text-primary transition-colors" />
</Link>
<Link to={member.twitter} target="_blank">
<Twitter className="w-5 h-5 cursor-pointer hover:text-primary transition-colors" />
</Link>
<Link to={member.github} target="_blank">
{" "}
<Github className="w-5 h-5 cursor-pointer hover:text-primary transition-colors" />
</Link>
</div>
</div>
<h3 className="text-xl font-bold mb-1 group-hover:text-primary transition-colors">
+1 -1
View File
@@ -17,7 +17,7 @@ const Index = () => {
<HeroSection />
<ServicesSection />
<ProjectsSection />
<TeamSection />
{/* <TeamSection /> */}
<AboutSection />
<TestimonialsSection />
{/* <BlogSection /> */}
+248 -282
View File
@@ -1,67 +1,77 @@
import PageTransition from "@/components/home/PageTransition";
import { Button } from "@/components/ui/button";
import { useProjectById } from "@/hooks/queires/useProjects";
import { motion } from "framer-motion";
import { motion, useReducedMotion } from "framer-motion";
import {
AlertCircle,
Calendar,
CheckCircle,
ChevronRight,
Clock,
ExternalLink,
Github,
Users,
HelpCircle,
Sliders,
Sparkles,
} 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;
// Standardizing backend data matching layer safely
interface BackendProjectPayload {
_id: string;
title: string;
description: string;
year: string | number;
duration: string;
team: string | number;
client: string;
liveUrl: string;
codeUrl?: string;
category: string[];
status: string;
isFeatured: boolean;
image: string;
results?: Result[];
fullDescription: string;
features?: string[];
technologies?: string[];
challenges?: string[];
gallery?: string[];
imageGallery: string[];
technologies: string[];
publishedYear: string | number;
problem: string;
solutions: string;
challenges: string;
workingDuration: string;
previewUrl: string;
features: 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 { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const shouldReduceMotion = useReducedMotion();
const { data, isLoading, isError } = useProjectById(id || "");
const project: ProjectData | null =
const project: BackendProjectPayload | null =
data?.data?.data || data?.data || data || null;
const faderUpVariants = {
hidden: { opacity: 0, y: shouldReduceMotion ? 0 : 20 },
visible: (custom: number) => ({
opacity: 1,
y: 0,
transition: {
duration: 0.5,
delay: custom * 0.08,
ease: [0.215, 0.61, 0.355, 1],
},
}),
};
if (isLoading) {
return (
<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"
role="status"
aria-live="polite"
>
<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...
<div className="relative w-10 h-10">
<div className="absolute inset-0 border-4 border-primary/10 rounded-full" />
<div className="absolute inset-0 border-4 border-primary border-t-transparent rounded-full animate-spin" />
</div>
<span className="text-xs font-semibold tracking-widest text-muted-foreground uppercase animate-pulse">
Parsing Techzaa Case Asset...
</span>
</div>
</div>
@@ -72,20 +82,25 @@ export default function ProjectDetails() {
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
<div className="min-h-screen bg-background flex items-center justify-center p-6">
<div className="text-center max-w-sm border border-destructive/20 bg-card p-8 rounded-2xl shadow-xl space-y-6">
<div className="inline-flex p-3 bg-destructive/10 rounded-full text-destructive">
<AlertCircle className="w-5 h-5" />
</div>
<div className="space-y-2">
<h1 className="text-xl font-bold tracking-tight">
Deployment Not Located
</h1>
<p className="text-muted-foreground mb-6">
The project you're looking for does not exist or could not be
loaded.
<p className="text-xs text-muted-foreground leading-relaxed">
The targeted project record configuration cannot be pulled from
the API ecosystem.
</p>
</div>
<Button
onClick={() => navigate("/projects")}
className="rounded-full w-full bg-primary text-primary-foreground hover:opacity-90 transition-all shadow-lg"
className="w-full rounded-xl text-xs font-semibold uppercase tracking-wider"
>
Back to Projects
Return to home
</Button>
</div>
</div>
@@ -95,272 +110,222 @@ export default function ProjectDetails() {
return (
<PageTransition>
<div className="min-h-screen bg-background text-foreground overflow-x-hidden selection:bg-primary/30 selection:text-primary">
{/* 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 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"
<div className="min-h-screen bg-background text-foreground selection:bg-primary/20 selection:text-primary antialiased">
{/* HERO SECTION - Architectural Dynamic Header */}
<section className="pt-36 pb-16 relative overflow-hidden">
<div className="container mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
{/* Dynamic Breadcrumbs */}
<nav aria-label="Breadcrumb" className="mb-10">
<ol className="flex items-center space-x-2 text-xs font-medium text-muted-foreground">
<li>
<Link
to="/"
className="hover:text-primary transition-colors focus-visible:outline-none focus-visible:underline"
>
<Link to="/" className="hover:text-primary transition-colors">
Home
</Link>
<ChevronRight className="w-3.5 h-3.5 opacity-50" />
</li>
<li className="flex items-center space-x-2">
<ChevronRight className="w-3 h-3 opacity-60" />
<Link
to="/projects"
className="hover:text-primary transition-colors"
className="hover:text-primary transition-colors focus-visible:outline-none focus-visible:underline"
>
Projects
</Link>
<ChevronRight className="w-3.5 h-3.5 opacity-50" />
<span className="text-foreground font-semibold truncate">
</li>
<li className="flex items-center space-x-2" aria-current="page">
<ChevronRight className="w-3 h-3 opacity-60" />
<span className="text-foreground font-semibold truncate max-w-[200px]">
{project.title}
</span>
</motion.nav>
</li>
</ol>
</nav>
<div className="grid lg:grid-cols-2 gap-12 items-center">
{/* Content Block */}
<div className="flex flex-col">
<motion.span
initial={{ opacity: 0, y: 15 }}
animate={{ opacity: 1, y: 0 }}
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"
{/* Asymmetric Split Layout */}
<div className="grid grid-cols-1 lg:grid-cols-12 gap-12 items-start">
{/* Primary Metas Block */}
<div className="lg:col-span-7 space-y-6">
<motion.div
initial="hidden"
animate="visible"
custom={0}
className="flex flex-wrap gap-1.5"
>
{CATEGORY_LABELS[project.category] || project.category}
</motion.span>
{Array.isArray(project.category) ? (
project.category.map((cat) => (
<span
key={cat}
className="inline-flex items-center gap-1 px-2.5 py-0.5 rounded bg-secondary text-secondary-foreground border border-border/80 text-[11px] font-semibold tracking-wide"
>
{cat}
</span>
))
) : (
<span className="inline-flex items-center gap-1 px-2.5 py-0.5 rounded bg-secondary text-secondary-foreground border border-border/80 text-[11px] font-semibold tracking-wide">
{project.category}
</span>
)}
</motion.div>
<motion.h1
initial={{ opacity: 0, y: 15 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.1 }}
className="text-4xl md:text-5xl lg:text-6xl font-black tracking-tight leading-none mb-6"
animate="visible"
custom={1}
className="text-3xl sm:text-4xl lg:text-5xl font-black tracking-tight text-foreground leading-[1.1]"
>
{project.title}
</motion.h1>
<motion.p
initial={{ opacity: 0, y: 15 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.2 }}
className="text-base md:text-lg text-muted-foreground leading-relaxed max-w-2xl mb-10"
initial="hidden"
animate="visible"
custom={2}
className="text-base sm:text-lg text-muted-foreground leading-relaxed max-w-2xl"
>
{project.description}
</motion.p>
{/* Project Metadata */}
{/* Primary Interaction Arrays */}
<motion.div
initial={{ opacity: 0, y: 15 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.3 }}
className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-10"
>
{[
{ 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>
{/* Call to Actions */}
<motion.div
initial={{ opacity: 0, y: 15 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.4 }}
className="flex flex-wrap gap-4"
>
<Link
to={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>
</Link>
{project.codeUrl && (
<Link
to={project.codeUrl}
target="_blank"
rel="noopener noreferrer"
className="w-full sm:w-auto"
initial="hidden"
animate="visible"
custom={3}
className="flex flex-col sm:flex-row gap-3 pt-2"
>
<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"
asChild
size="lg"
className="rounded-xl font-semibold tracking-wide shadow-lg shadow-primary/5 transition-transform active:scale-[0.98]"
>
<Github className="w-4 h-4 text-muted-foreground" />
View Code
<a
href={project.previewUrl}
target="_blank"
rel="noopener noreferrer"
>
<ExternalLink className="w-4 h-4 mr-2" />
Live Preview Link
</a>
</Button>
</Link>
)}
</motion.div>
</div>
{/* Project Cover Visual */}
{/* Cover Interactive Card Frame */}
<div className="lg:col-span-5 w-full">
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
initial={{ opacity: 0, scale: shouldReduceMotion ? 1 : 0.97 }}
animate={{ opacity: 1, scale: 1 }}
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]"
transition={{ duration: 0.5 }}
className="relative rounded-2xl overflow-hidden border border-border bg-card aspect-[4/3] shadow-lg transform-gpu"
>
<img
src={project.image}
alt={project.title}
className="w-full h-full object-cover object-center transition-transform duration-700 ease-out group-hover:scale-[1.02]"
loading="lazy"
alt={`Case Application mockup frame for ${project.title}`}
className="w-full h-full object-cover object-center"
loading="eager"
/>
<div className="absolute inset-0 bg-gradient-to-t from-background/80 via-transparent to-transparent opacity-80" />
</motion.div>
</div>
</div>
</div>
</section>
{/* 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, y: 15 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="text-center max-w-2xl mx-auto mb-14"
{/* METADATA BAR SECTION */}
<section
className="py-8 bg-secondary/20 border-y border-border/40"
aria-label="System parameters"
>
<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"
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
{[
{
label: "Deployment Year",
value: project.publishedYear,
icon: Calendar,
},
{
label: "Production Period",
value: project.workingDuration,
icon: Clock,
},
{
label: "Project Status",
value: project.status,
icon: Sliders,
},
].map((item) => (
<div
key={item.label}
className="p-4 bg-card border border-border/60 rounded-xl flex items-center justify-between shadow-sm"
>
<span className="text-3xl md:text-4xl font-black text-primary tracking-tight mb-2">
{result.value}
<div className="space-y-0.5 truncate mr-2">
<span className="block text-[10px] font-bold tracking-wider uppercase text-muted-foreground">
{item.label}
</span>
<span className="text-xs font-medium text-center text-muted-foreground">
{result.label}
<span className="block text-sm font-bold text-foreground truncate">
{item.value}
</span>
</motion.div>
</div>
<item.icon className="w-4 h-4 text-primary opacity-70 flex-shrink-0" />
</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
{/* COMPREHENSIVE CASE LOGIC SECTION */}
<section className="py-20 bg-background">
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-1 lg:grid-cols-12 gap-12 lg:gap-16 items-start">
{/* Engineering Metrics Column (Left side) */}
<div className="lg:col-span-6 space-y-10">
{project.problem && (
<div className="space-y-3">
<h3 className="text-lg font-bold tracking-tight text-foreground flex items-center gap-2">
<HelpCircle className="w-4 h-4 text-primary" />
The Structural Problem
</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-base md:text-lg text-muted-foreground leading-relaxed mb-6">
{children}
<p className="text-sm text-muted-foreground leading-relaxed bg-secondary/10 border border-border/40 p-4 rounded-xl">
{project.problem}
</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>
</div>
{/* 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>
)}
{/* Technologies Used */}
{project.solutions && (
<div className="space-y-3">
<h3 className="text-lg font-bold tracking-tight text-foreground flex items-center gap-2">
<CheckCircle className="w-4 h-4 text-primary" />
Our Applied Strategy
</h3>
<p className="text-sm text-muted-foreground leading-relaxed bg-primary/5 border border-primary/10 p-4 rounded-xl">
{project.solutions}
</p>
</div>
)}
{project.challenges && (
<div className="space-y-3 border-t border-border/40 pt-6">
<h3 className="text-lg font-bold tracking-tight text-foreground">
Critical Bottlenecks Resolved
</h3>
<p className="text-sm text-muted-foreground leading-relaxed">
{project.challenges}
</p>
</div>
)}
</div>
<div className="lg:col-span-6 space-y-10">
{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
<div className="space-y-4">
<h3 className="text-sm font-bold tracking-wider uppercase">
Tech Stack
</h3>
<div className="flex flex-wrap gap-2.5">
<div className="flex flex-wrap gap-1.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"
className="px-3 py-1 bg-card text-xs font-semibold rounded border border-border/80 text-foreground transition-colors cursor-default hover:bg-secondary/40"
>
{tech}
</span>
@@ -369,19 +334,20 @@ export default function ProjectDetails() {
</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
{project.features && project.features.length > 0 && (
<div className="space-y-4 border-t border-border/40 pt-6">
<h3 className="text-lg font-bold tracking-tight text-foreground flex items-center gap-2">
<Sparkles className="w-4 h-4 text-primary" />
Delivered Platform Capabilities
</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>
<ul className="grid grid-cols-1 sm:grid-cols-2 gap-2.5">
{project.features.map((feature, idx) => (
<li
key={idx}
className="flex items-center gap-2 text-xs text-muted-foreground bg-card border border-border/40 p-2.5 rounded-lg"
>
<div className="w-1.5 h-1.5 rounded-full bg-primary flex-shrink-0" />
<span className="truncate">{feature}</span>
</li>
))}
</ul>
@@ -392,36 +358,36 @@ export default function ProjectDetails() {
</div>
</section>
{/* 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
{/* PLATFORM PRESENTATION SHOWCASE GALLERY */}
{project.imageGallery && project.imageGallery.length > 0 && (
<section className="py-20 border-t border-border/40 bg-secondary/10">
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
<div className="max-w-xl mx-auto text-center mb-12 space-y-1">
<h2 className="text-xl sm:text-2xl font-bold tracking-tight">
Platform Interface Metrics
</h2>
<p className="text-sm text-muted-foreground">
A visual overview of the implementation and user interface.
<p className="text-xs text-muted-foreground">
Operational state validation screens captured during
deployment execution.
</p>
</div>
<div className="grid md:grid-cols-3 gap-6 max-w-6xl mx-auto">
{project.gallery.map((image, index) => (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{project.imageGallery.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.06, duration: 0.4 }}
className="group relative aspect-[4/3] rounded-3xl overflow-hidden border border-border/60 bg-background/40"
initial={{ opacity: 0, y: shouldReduceMotion ? 0 : 15 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-20px" }}
transition={{ delay: index * 0.05, duration: 0.4 }}
className="group relative aspect-[4/3] rounded-xl overflow-hidden border border-border bg-card shadow-sm transform-gpu"
>
<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"
alt={`Platform runtime verification graphic state capture ${index + 1}`}
className="w-full h-full object-cover object-center transition-transform duration-500 group-hover:scale-[1.02]"
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>
+165 -209
View File
@@ -1,312 +1,268 @@
import PageTransition from "@/components/home/PageTransition";
import { ProjectCard } from "@/components/home/ProjectCard";
import { Button } from "@/components/ui/button";
import { useProjects } from "@/hooks/queires/useProjects";
import { AnimatePresence, motion } from "framer-motion";
import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
import {
ArrowLeft,
Brain,
Cloud,
ExternalLink,
Github,
Globe,
Loader2,
Layers,
Search,
Smartphone,
Sparkles,
Workflow,
} from "lucide-react";
import { useMemo, useState } from "react";
import { useDeferredValue, useMemo, useState } from "react";
import { Link } from "react-router-dom";
const categories = [
{ id: "all", name: "All Projects", icon: null },
{ id: "web", name: "Web", icon: Globe },
const CATEGORIES = [
{ id: "all", name: "All Work", icon: Layers },
{ id: "web", name: "Web Systems", icon: Globe },
{ id: "mobile", name: "Mobile", icon: Smartphone },
{ id: "ai", name: "AI", icon: Brain },
{ id: "cloud", name: "Cloud", icon: Cloud },
{ id: "devops", name: "DEVOPS", icon: Workflow },
{ id: "ai", name: "AI & ML", icon: Brain },
{ id: "cloud", name: "Cloud Platforms", icon: Cloud },
{ id: "devops", name: "DevOps", icon: Workflow },
];
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.08,
},
transition: { staggerChildren: 0.04 },
},
};
export default function Projects() {
const { data: projectsData, isLoading, isError } = useProjects();
const shouldReduceMotion = useReducedMotion();
const [activeCategory, setActiveCategory] = useState("all");
const [searchQuery, setSearchQuery] = useState("");
const deferredSearchQuery = useDeferredValue(searchQuery);
const filteredProjects = useMemo(() => {
const projects = projectsData?.data?.data?.result || [];
if (!projects) return [];
const projects = projectsData?.data?.data || projectsData?.data || [];
if (!Array.isArray(projects)) return [];
return projects.filter((project) => {
const matchesCategory =
activeCategory === "all" || project.category === activeCategory;
activeCategory === "all" ||
(Array.isArray(project.category)
? project.category.some(
(cat: string) =>
cat.toLowerCase() === activeCategory.toLowerCase() ||
cat.toLowerCase().includes(activeCategory.toLowerCase()),
)
: project.category?.toLowerCase() === activeCategory.toLowerCase());
const normalizedSearch = deferredSearchQuery.trim().toLowerCase();
const matchesSearch =
searchQuery.trim() === "" ||
project.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
project.description.toLowerCase().includes(searchQuery.toLowerCase()) ||
normalizedSearch === "" ||
project.title?.toLowerCase().includes(normalizedSearch) ||
project.description?.toLowerCase().includes(normalizedSearch) ||
(Array.isArray(project.technologies) &&
project.technologies.some((tech: string) =>
tech.toLowerCase().includes(searchQuery.toLowerCase()),
);
tech.toLowerCase().includes(normalizedSearch),
));
return matchesCategory && matchesSearch;
});
}, [projectsData, activeCategory, searchQuery]);
}, [projectsData, activeCategory, deferredSearchQuery]);
return (
<PageTransition>
<div className="min-h-screen bg-background overflow-x-hidden text-foreground antialiased">
{/* Hero Section */}
<section className="pt-32 pb-16 relative overflow-hidden border-b border-primary/10">
<div className="absolute inset-0 pointer-events-none select-none">
<div className="absolute top-1/4 left-1/4 w-96 h-96 bg-primary/10 rounded-full blur-3xl" />
<div className="absolute bottom-0 right-1/4 w-64 h-64 bg-accent/20 rounded-full blur-3xl" />
</div>
<div className="min-h-screen bg-background text-foreground antialiased selection:bg-primary/20">
<section className="pt-32 pb-12 relative overflow-hidden border-b border-border/40">
<div className="container mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
<div className="container mx-auto px-4 relative z-10">
{/* Back Button */}
{/* Back Arrow Wrapper - Kept clean on left side */}
<motion.div
initial={{ opacity: 0, x: -20 }}
initial={{ opacity: 0, x: shouldReduceMotion ? 0 : -10 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.4 }}
transition={{ duration: 0.3 }}
className="w-full flex justify-start"
>
<Button
asChild
variant="ghost"
size="sm"
className="mb-6 group text-muted-foreground hover:text-foreground"
>
<Link to="/">
<Button variant="ghost" className="mb-8 group h-10 px-4">
<ArrowLeft className="w-4 h-4 mr-2 transition-transform group-hover:-translate-x-1" />
Back to Home
</Button>
</Link>
</Button>
</motion.div>
{/* Header */}
<motion.div
initial={{ opacity: 0, y: 15 }}
{/* Core Header Content Module - Flex-centered alignment */}
<div className="w-full flex flex-col items-center justify-center text-center">
<div className="max-w-3xl mb-12">
<motion.h1
initial={{ opacity: 0, y: shouldReduceMotion ? 0 : 15 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
className="text-center mb-10 max-w-3xl mx-auto"
className="text-4xl sm:text-5xl lg:text-6xl font-black tracking-tight leading-none mb-4"
>
<h1 className="text-4xl md:text-6xl font-extrabold tracking-tight mb-4 bg-gradient-to-r from-primary via-primary/80 to-accent bg-clip-text text-transparent">
Our Projects
</h1>
<p className="text-muted-foreground text-lg md:text-xl leading-relaxed max-w-2xl mx-auto">
Explore our portfolio of innovative solutions designed to scale
and transform businesses across industries.
</p>
</motion.div>
All Projects
</motion.h1>
<motion.p
initial={{ opacity: 0, y: shouldReduceMotion ? 0 : 15 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.05 }}
className="text-base sm:text-lg text-muted-foreground leading-relaxed max-w-2xl mx-auto"
>
A high-fidelity showroom tracking production ecosystems, AI
operations, and cloud architectures compiled by Techzaa.
</motion.p>
</div>
{/* Interactive Search and Filter Section */}
{/* Interface Control Bar Matrix - Fully Centered Substructures */}
<motion.div
initial={{ opacity: 0, y: 15 }}
initial={{ opacity: 0, y: shouldReduceMotion ? 0 : 15 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.1 }}
className="flex flex-col items-center gap-6 mb-12"
transition={{ delay: 0.1 }}
className="space-y-6 w-full pt-6 border-t border-border/40 flex flex-col items-center justify-center"
>
{/* Search Bar */}
<div className="relative w-full max-w-md">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-muted-foreground" />
{/* Search input container centralized down the layout grid */}
<div className="relative max-w-md w-full group mx-auto">
<Search className="absolute left-3.5 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground group-focus-within:text-primary transition-colors" />
<input
type="text"
placeholder="Search projects, tech, or keywords..."
placeholder="Query by parameter, core feature, or framework stack..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full pl-10 pr-4 py-2.5 rounded-full border border-border/50 bg-background/50 backdrop-blur-sm focus:outline-none focus:ring-2 focus:ring-primary/40 transition-all text-sm shadow-sm"
aria-label="Search projects"
className="w-full pl-10 pr-4 py-2 text-sm rounded-xl border border-border bg-card/60 placeholder:text-muted-foreground/60 focus:outline-none focus:ring-2 focus:ring-primary/30 transition-all shadow-sm"
aria-label="Search deployment records"
/>
</div>
{/* Filter Tabs */}
<div className="flex flex-wrap justify-center gap-2 max-w-4xl">
{categories.map((category) => {
const Icon = category.icon;
const isActive = activeCategory === category.id;
{/* Categories Tab Matrix - Wrapped with center axis parameters */}
<div
className="flex flex-wrap items-center justify-center gap-2 max-w-2xl w-full"
role="tablist"
aria-label="Project Filtering Options"
>
{CATEGORIES.map((cat) => {
const Icon = cat.icon;
const isActive = activeCategory === cat.id;
return (
<Button
key={category.id}
key={cat.id}
variant={isActive ? "default" : "outline"}
onClick={() => setActiveCategory(category.id)}
aria-pressed={isActive}
className={`rounded-full px-5 py-2 text-sm font-medium transition-all duration-300 ${
isActive
? "bg-primary text-primary-foreground shadow-sm shadow-primary/20"
: "border-border/60 hover:bg-muted/50"
}`}
onClick={() => setActiveCategory(cat.id)}
role="tab"
aria-selected={isActive}
className="rounded-xl text-xs font-semibold tracking-wide h-9 px-4 transition-all duration-200"
>
{Icon && <Icon className="w-4 h-4 mr-2" />}
{category.name}
<Icon className="w-3.5 h-3.5 mr-2 opacity-80" />
{cat.name}
</Button>
);
})}
</div>
</motion.div>
</div>
</div>
</section>
{/* Projects Grid */}
<section className="py-20 bg-background/50">
<div className="container mx-auto px-4">
{/* Loading / Error State */}
{/* Projects View Output Section */}
<section className="py-16 bg-background">
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
{isLoading && (
<div className="flex flex-col items-center justify-center py-24 gap-4 text-muted-foreground animate-pulse">
<Loader2 className="w-8 h-8 animate-spin text-primary" />
<span className="text-sm font-medium">Loading projects...</span>
<div
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8"
role="status"
aria-label="Loading project payload"
>
{[...Array(6)].map((_, idx) => (
<div
key={idx}
className="bg-card border border-border/40 rounded-2xl p-4 space-y-6 animate-pulse"
>
<div className="aspect-[16/10] bg-muted rounded-xl w-full" />
<div className="space-y-3 px-2">
<div className="h-4 bg-muted rounded-md w-1/3" />
<div className="h-6 bg-muted rounded-md w-3/4" />
<div className="h-4 bg-muted rounded-md w-full" />
</div>
</div>
))}
</div>
)}
{isError && (
<div className="text-center py-20 text-destructive">
<p className="text-lg font-semibold mb-2">
Failed to load projects.
<div className="text-center py-16 border border-destructive/20 bg-destructive/5 rounded-2xl max-w-lg mx-auto p-6 space-y-3">
<p className="text-base font-bold text-foreground">
API Synchronization Failure
</p>
<p className="text-sm text-muted-foreground">
Please check your network connection or try again later.
<p className="text-xs text-muted-foreground leading-normal">
Could not safely sync target records from Techzaa's data layer
cluster. Confirm connection status parameters.
</p>
</div>
)}
{!isLoading && !isError && (
<AnimatePresence mode="wait">
<AnimatePresence mode="popLayout">
{filteredProjects.length > 0 ? (
<motion.div
key={`${activeCategory}-${searchQuery}`}
key={`${activeCategory}-${deferredSearchQuery}`}
variants={containerVariants}
initial="hidden"
animate="visible"
exit="hidden"
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8"
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 items-stretch"
>
{filteredProjects.map((project) => (
<motion.article
key={project._id}
layout
className="group flex flex-col justify-between bg-card/40 border border-border/50 backdrop-blur-sm rounded-3xl overflow-hidden hover:border-primary/50 transition-all duration-500 hover:shadow-xl hover:shadow-accent/5 p-4"
>
<div>
{/* Project Image */}
<div className="relative h-56 rounded-2xl overflow-hidden mb-6">
<img
src={project.image}
alt={project.title}
loading="lazy"
className="w-full h-full object-cover transition-transform duration-700 ease-out group-hover:scale-105"
/>
<div className="absolute inset-0 bg-gradient-to-t from-background/90 via-background/20 to-transparent opacity-80" />
{/* Category Badge */}
<div className="absolute top-4 left-4">
<span className="px-3 py-1 rounded-full bg-primary/20 text-primary text-xs font-semibold backdrop-blur-md border border-primary/20">
{
categories.find(
(c) => c.id === project.category,
)?.name
}
</span>
</div>
{/* Quick External Links */}
<div className="absolute top-4 right-4 flex gap-2 opacity-0 group-hover:opacity-100 transition-opacity duration-300">
{project.liveUrl && (
<a
href={project.liveUrl}
target="_blank"
rel="noopener noreferrer"
className="p-2 rounded-full bg-background/80 backdrop-blur-sm border border-border/40 hover:bg-primary/20 transition-colors"
aria-label={`View live project: ${project.title}`}
>
<ExternalLink className="w-4 h-4" />
</a>
)}
{project.githubUrl && (
<a
href={project.githubUrl}
target="_blank"
rel="noopener noreferrer"
className="p-2 rounded-full bg-background/80 backdrop-blur-sm border border-border/40 hover:bg-primary/20 transition-colors"
aria-label={`View GitHub repository for: ${project.title}`}
>
<Github className="w-4 h-4" />
</a>
)}
</div>
</div>
{/* Title & Description */}
<div className="px-2">
<div className="flex items-center gap-3 text-xs font-medium text-muted-foreground mb-3">
<span>{project.client}</span>
<span className="w-1.5 h-1.5 rounded-full bg-muted-foreground/40" />
<span>{project.year}</span>
</div>
<h3 className="text-xl font-bold tracking-tight mb-3 group-hover:text-primary transition-colors line-clamp-1">
<Link to={`/projects/${project._id}`}>
{project.title}
</Link>
</h3>
<p className="text-muted-foreground text-sm leading-relaxed mb-6 line-clamp-3">
{project.description}
</p>
</div>
</div>
{/* Technologies and Detail Navigation */}
<div className="px-2">
<div className="flex flex-wrap gap-1.5 max-h-16 overflow-hidden mb-4">
{project.technologies
.slice(0, 5)
.map((tech: string) => (
<span
key={tech}
className="px-2.5 py-0.5 rounded-md bg-primary text-primary-foreground text-xs font-semibold tracking-wide"
>
{tech}
</span>
))}
{project.technologies.length > 5 && (
<span className="px-2.5 py-0.5 rounded-md bg-secondary text-secondary-foreground text-xs font-semibold tracking-wide">
+{project.technologies.length - 5}
</span>
)}
</div>
<div className="flex justify-end pt-2 border-t border-border/30">
<Link to={`/projects/${project._id}`}>
<Button
variant="link"
size="sm"
className="text-xs text-primary p-0 h-auto font-semibold group/btn"
>
View Details
</Button>
</Link>
</div>
</div>
</motion.article>
))}
</motion.div>
</AnimatePresence>
)}
{/* Empty State */}
{!isLoading && !isError && filteredProjects.length === 0 && (
{filteredProjects.map((project, idx) => (
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className="text-center py-20"
layout={!shouldReduceMotion}
key={project._id}
className="h-full"
>
<p className="text-muted-foreground text-lg">
No projects match your current search or category.
<ProjectCard
project={{
_id: project._id,
title: project.title,
category: Array.isArray(project.category)
? project.category
: [project.category],
isFeatured: project.isFeatured || false,
technologies: project.technologies || [],
publishedYear:
project.publishedYear || project.year || "2026",
previewUrl:
project.previewUrl || project.liveUrl || "#",
image: project.image,
}}
index={idx}
isInView={true}
/>
</motion.div>
))}
</motion.div>
) : (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="text-center py-24 border border-dashed border-border rounded-2xl max-w-md mx-auto p-8 space-y-2"
>
<Sparkles className="w-5 h-5 text-muted-foreground/60 mx-auto" />
<h3 className="text-sm font-bold text-foreground">
No Projects Located
</h3>
<p className="text-xs text-muted-foreground leading-relaxed">
Your lookup parameter variations produced zero operational
matches in our architecture records.
</p>
</motion.div>
)}
</AnimatePresence>
)}
</div>
</section>
</div>