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 techzaa.alpha@gmail.com,
</br>
## Project info TechZaa@@##123
**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)
+122 -51
View File
@@ -1,102 +1,173 @@
import { motion } from "framer-motion"; import { motion, useReducedMotion } from "framer-motion";
import { ExternalLink } from "lucide-react"; import { ArrowUpRight, Layers } from "lucide-react";
import { useState } from "react"; import { useId, useState } from "react";
import { Link } from "react-router-dom"; 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 [mousePosition, setMousePosition] = useState({ x: 50, y: 50 });
const [isHovered, setIsHovered] = useState(false); const [isHovered, setIsHovered] = useState(false);
const shouldReduceMotion = useReducedMotion();
const titleId = useId();
const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => { const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
if (shouldReduceMotion) return;
const rect = e.currentTarget.getBoundingClientRect(); const rect = e.currentTarget.getBoundingClientRect();
const x = ((e.clientX - rect.left) / rect.width) * 100; const x = ((e.clientX - rect.left) / rect.width) * 100;
const y = ((e.clientY - rect.top) / rect.height) * 100; const y = ((e.clientY - rect.top) / rect.height) * 100;
setMousePosition({ x, y }); 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 ( return (
<Link to={`/projects/${project._id}`}>
<motion.div <motion.div
initial={{ opacity: 0, y: 60, rotateX: 15 }} initial={{ opacity: 0, y: shouldReduceMotion ? 0 : 40 }}
animate={isInView ? { opacity: 1, y: 0, rotateX: 0 } : {}} animate={isInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, delay: index * 0.08 }} transition={{
whileHover={{ scale: 1.02 }} duration: 0.5,
className="group relative rounded-3xl overflow-hidden cursor-pointer h-full" 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} onMouseMove={handleMouseMove}
onMouseEnter={() => setIsHovered(true)} onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => { onMouseLeave={() => {
setIsHovered(false); setIsHovered(false);
setMousePosition({ x: 50, y: 50 }); setMousePosition({ x: 50, y: 50 });
}} }}
aria-labelledby={titleId}
> >
{/* Main Card Container with 3D Tilt */} {/* Interactive Visual Window */}
<div <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={{ style={{
transform: isHovered transform:
? `perspective(1000px) rotateX(${(50 - mousePosition.y) * 0.12}deg) rotateY(${(mousePosition.x - 50) * 0.15}deg)` isHovered && !shouldReduceMotion
? `perspective(1000px) rotateX(${(50 - mousePosition.y) * 0.06}deg) rotateY(${(mousePosition.x - 50) * 0.06}deg)`
: "perspective(1000px) rotateX(0deg) rotateY(0deg)", : "perspective(1000px) rotateX(0deg) rotateY(0deg)",
transition: isHovered transition: isHovered
? "transform 0.1s ease-out" ? "transform 0.05s ease-out"
: "transform 0.4s ease-out", : "transform 0.3s ease-out",
}} }}
> >
{/* Image Container */}
<div className="aspect-[16/13] overflow-hidden relative">
<img <img
src={project.image} src={projectImage}
alt={project.title} alt={`Interface screenshot overview for ${project.title}`}
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110" 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 <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={{ 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> </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 <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`} className="flex flex-wrap gap-1"
/> aria-label="Project classifications"
{/* 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"
> >
{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} {project.category}
</motion.span> </span>
)}
</div>
{/* Title */} {/* Title Identifier linked to parent card components via standard a11y specs */}
<h3 className="text-2xl md:text-3xl font-bold text-white leading-tight tracking-tight"> <h3
id={titleId}
className="text-xl font-bold tracking-tight text-foreground leading-snug group-hover:text-primary transition-colors duration-200"
>
{project.title} {project.title}
</h3> </h3>
{/* View Project Link */} {/* Previewing active Framework stacks cleanly underneath */}
<Link {project.technologies && project.technologies.length > 0 && (
to={project.liveUrl} <div className="flex flex-wrap gap-1 pt-1">
className="flex items-center gap-3 text-white/80 group-hover:text-white transition-colors" {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> {tech}
<ExternalLink className="w-5 h-5 transition-transform group-hover:rotate-45" /> </span>
</Link> ))}
{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>
)}
</div> </div>
{/* Neon Border Glow */} {/* Action Controls Matrix Bar */}
<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 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> </div>
</motion.div> </motion.div>
</Link>
); );
} }
+8 -8
View File
@@ -4,8 +4,8 @@ import { motion, useInView } from "framer-motion";
import { ArrowRight } from "lucide-react"; import { ArrowRight } from "lucide-react";
import { useRef } from "react"; import { useRef } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { ProjectCard } from "./ProjectCard";
import PremiumBadge from "../shared/PremiumBadge"; import PremiumBadge from "../shared/PremiumBadge";
import { ProjectCard } from "./ProjectCard";
const gradientColors = [ const gradientColors = [
"from-neon-blue/90", "from-neon-blue/90",
@@ -19,9 +19,9 @@ export default function ProjectsSection() {
fields: "category, title, image, liveUrl", fields: "category, title, image, liveUrl",
limit: 6, limit: 6,
}); });
console.log(projectsData?.data.data);
const projects = projectsData?.data.data.result || []; const projects = projectsData?.data.data || [];
console.log("project from homepage", projects);
const ref = useRef<HTMLElement>(null); const ref = useRef<HTMLElement>(null);
const isInView = useInView(ref, { once: true, margin: "-100px" }); const isInView = useInView(ref, { once: true, margin: "-100px" });
@@ -52,7 +52,10 @@ export default function ProjectsSection() {
> >
<PremiumBadge text="our projects" /> <PremiumBadge text="our projects" />
<h2 className="text-4xl md:text-5xl lg:text-6xl font-bold mb-6"> <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> </h2>
<p className="text-muted-foreground max-w-2xl mx-auto text-lg"> <p className="text-muted-foreground max-w-2xl mx-auto text-lg">
Crafted with passion. Delivered with excellence. Crafted with passion. Delivered with excellence.
@@ -62,13 +65,10 @@ export default function ProjectsSection() {
{/* Projects Grid */} {/* Projects Grid */}
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8 mb-16"> <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8 mb-16">
{projects.map((project, index) => { {projects.map((project, index) => {
const color = gradientColors[index % gradientColors.length];
return ( return (
<ProjectCard <ProjectCard
key={project._id} key={project._id}
project={project} project={project}
color={color}
index={index} index={index}
isInView={isInView} isInView={isInView}
/> />
+2 -13
View File
@@ -59,7 +59,7 @@ export default function TeamSection() {
} }
pagination={{ clickable: true }} pagination={{ clickable: true }}
modules={[Autoplay, Pagination]} modules={[Autoplay, Pagination]}
className="pb-12" // Space for pagination dots className="pb-12"
breakpoints={{ breakpoints={{
640: { slidesPerView: 2 }, 640: { slidesPerView: 2 },
1024: { slidesPerView: 3 }, 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" className="absolute inset-0 w-full h-full object-cover object-top transition-transform duration-500 group-hover:scale-110"
/> />
</div> </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> </div>
<h3 className="text-xl font-bold mb-1 group-hover:text-primary transition-colors"> <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 /> <HeroSection />
<ServicesSection /> <ServicesSection />
<ProjectsSection /> <ProjectsSection />
<TeamSection /> {/* <TeamSection /> */}
<AboutSection /> <AboutSection />
<TestimonialsSection /> <TestimonialsSection />
{/* <BlogSection /> */} {/* <BlogSection /> */}
+248 -282
View File
@@ -1,67 +1,77 @@
import PageTransition from "@/components/home/PageTransition"; import PageTransition from "@/components/home/PageTransition";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { useProjectById } from "@/hooks/queires/useProjects"; import { useProjectById } from "@/hooks/queires/useProjects";
import { motion } from "framer-motion"; import { motion, useReducedMotion } from "framer-motion";
import { import {
AlertCircle,
Calendar, Calendar,
CheckCircle,
ChevronRight, ChevronRight,
Clock, Clock,
ExternalLink, ExternalLink,
Github, HelpCircle,
Users, Sliders,
Sparkles,
} from "lucide-react"; } from "lucide-react";
import ReactMarkdown from "react-markdown";
import { Link, useNavigate, useParams } from "react-router-dom"; import { Link, useNavigate, useParams } from "react-router-dom";
import remarkGfm from "remark-gfm";
interface Result { // Standardizing backend data matching layer safely
label: string; interface BackendProjectPayload {
value: string | number; _id: string;
}
interface ProjectData {
id: string;
category: string;
title: string; title: string;
description: string; description: string;
year: string | number; category: string[];
duration: string; status: string;
team: string | number; isFeatured: boolean;
client: string;
liveUrl: string;
codeUrl?: string;
image: string; image: string;
results?: Result[]; imageGallery: string[];
fullDescription: string; technologies: string[];
features?: string[]; publishedYear: string | number;
technologies?: string[]; problem: string;
challenges?: string[]; solutions: string;
gallery?: 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() { export default function ProjectDetails() {
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
const navigate = useNavigate(); const navigate = useNavigate();
const shouldReduceMotion = useReducedMotion();
const { data, isLoading, isError } = useProjectById(id || ""); const { data, isLoading, isError } = useProjectById(id || "");
const project: ProjectData | null = const project: BackendProjectPayload | null =
data?.data?.data || data?.data || data || 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) { 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"
role="status"
aria-live="polite"
>
<div className="flex flex-col items-center gap-4"> <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" /> <div className="relative w-10 h-10">
<span className="text-sm font-medium text-muted-foreground animate-pulse"> <div className="absolute inset-0 border-4 border-primary/10 rounded-full" />
Loading project details... <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> </span>
</div> </div>
</div> </div>
@@ -72,20 +82,25 @@ export default function ProjectDetails() {
if (isError || !project) { if (isError || !project) {
return ( return (
<PageTransition> <PageTransition>
<div className="min-h-screen bg-background flex items-center justify-center p-4"> <div className="min-h-screen bg-background flex items-center justify-center p-6">
<div className="text-center max-w-md border border-primary/20 bg-secondary/30 backdrop-blur-xl p-8 rounded-3xl shadow-2xl"> <div className="text-center max-w-sm border border-destructive/20 bg-card p-8 rounded-2xl shadow-xl space-y-6">
<h1 className="text-3xl font-bold tracking-tight mb-3"> <div className="inline-flex p-3 bg-destructive/10 rounded-full text-destructive">
Project Not Found <AlertCircle className="w-5 h-5" />
</div>
<div className="space-y-2">
<h1 className="text-xl font-bold tracking-tight">
Deployment Not Located
</h1> </h1>
<p className="text-muted-foreground mb-6"> <p className="text-xs text-muted-foreground leading-relaxed">
The project you're looking for does not exist or could not be The targeted project record configuration cannot be pulled from
loaded. the API ecosystem.
</p> </p>
</div>
<Button <Button
onClick={() => navigate("/projects")} 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> </Button>
</div> </div>
</div> </div>
@@ -95,272 +110,222 @@ export default function ProjectDetails() {
return ( return (
<PageTransition> <PageTransition>
<div className="min-h-screen bg-background text-foreground overflow-x-hidden selection:bg-primary/30 selection:text-primary"> <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">
{/* Hero Section */} <div className="container mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
<section className="pt-36 pb-20 relative overflow-hidden"> {/* Dynamic Breadcrumbs */}
{/* Ambient Background Glows */} <nav aria-label="Breadcrumb" className="mb-10">
<div className="absolute inset-0 pointer-events-none z-0"> <ol className="flex items-center space-x-2 text-xs font-medium text-muted-foreground">
<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]" /> <li>
<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" /> <Link
</div> to="/"
className="hover:text-primary transition-colors focus-visible:outline-none focus-visible:underline"
<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 Home
</Link> </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 <Link
to="/projects" to="/projects"
className="hover:text-primary transition-colors" className="hover:text-primary transition-colors focus-visible:outline-none focus-visible:underline"
> >
Projects Projects
</Link> </Link>
<ChevronRight className="w-3.5 h-3.5 opacity-50" /> </li>
<span className="text-foreground font-semibold truncate"> <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} {project.title}
</span> </span>
</motion.nav> </li>
</ol>
</nav>
<div className="grid lg:grid-cols-2 gap-12 items-center"> {/* Asymmetric Split Layout */}
{/* Content Block */} <div className="grid grid-cols-1 lg:grid-cols-12 gap-12 items-start">
<div className="flex flex-col"> {/* Primary Metas Block */}
<motion.span <div className="lg:col-span-7 space-y-6">
initial={{ opacity: 0, y: 15 }} <motion.div
animate={{ opacity: 1, y: 0 }} initial="hidden"
transition={{ duration: 0.4 }} animate="visible"
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" custom={0}
className="flex flex-wrap gap-1.5"
> >
{CATEGORY_LABELS[project.category] || project.category} {Array.isArray(project.category) ? (
</motion.span> 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 <motion.h1
initial={{ opacity: 0, y: 15 }} animate="visible"
animate={{ opacity: 1, y: 0 }} custom={1}
transition={{ duration: 0.5, delay: 0.1 }} className="text-3xl sm:text-4xl lg:text-5xl font-black tracking-tight text-foreground leading-[1.1]"
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: 15 }} initial="hidden"
animate={{ opacity: 1, y: 0 }} animate="visible"
transition={{ duration: 0.5, delay: 0.2 }} custom={2}
className="text-base md:text-lg text-muted-foreground leading-relaxed max-w-2xl mb-10" className="text-base sm:text-lg text-muted-foreground leading-relaxed max-w-2xl"
> >
{project.description} {project.description}
</motion.p> </motion.p>
{/* Project Metadata */} {/* Primary Interaction Arrays */}
<motion.div <motion.div
initial={{ opacity: 0, y: 15 }} initial="hidden"
animate={{ opacity: 1, y: 0 }} animate="visible"
transition={{ duration: 0.5, delay: 0.3 }} custom={3}
className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-10" className="flex flex-col sm:flex-row gap-3 pt-2"
>
{[
{ 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"
> >
<Button <Button
variant="outline" asChild
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" 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" /> <a
View Code href={project.previewUrl}
target="_blank"
rel="noopener noreferrer"
>
<ExternalLink className="w-4 h-4 mr-2" />
Live Preview Link
</a>
</Button> </Button>
</Link>
)}
</motion.div> </motion.div>
</div> </div>
{/* Project Cover Visual */} {/* Cover Interactive Card Frame */}
<div className="lg:col-span-5 w-full">
<motion.div <motion.div
initial={{ opacity: 0, scale: 0.95 }} initial={{ opacity: 0, scale: shouldReduceMotion ? 1 : 0.97 }}
animate={{ opacity: 1, scale: 1 }} animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.6, delay: 0.2 }} transition={{ duration: 0.5 }}
className="relative group rounded-3xl overflow-hidden border border-border/60 shadow-2xl bg-secondary/30 aspect-[4/3] lg:aspect-auto h-[450px]" className="relative rounded-2xl overflow-hidden border border-border bg-card aspect-[4/3] shadow-lg transform-gpu"
> >
<img <img
src={project.image} src={project.image}
alt={project.title} alt={`Case Application mockup frame for ${project.title}`}
className="w-full h-full object-cover object-center transition-transform duration-700 ease-out group-hover:scale-[1.02]" className="w-full h-full object-cover object-center"
loading="lazy" loading="eager"
/> />
<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>
</div>
</section> </section>
{/* Key Results Section */} {/* METADATA BAR SECTION */}
{project.results && project.results.length > 0 && ( <section
<section className="py-20 border-t border-b border-border/50 bg-secondary/20"> className="py-8 bg-secondary/20 border-y border-border/40"
<div className="container mx-auto px-4 md:px-6"> aria-label="System parameters"
<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"
> >
<h2 className="text-3xl font-bold tracking-tight mb-3"> <div className="container mx-auto px-4 sm:px-6 lg:px-8">
Key Performance Outcomes <div className="grid grid-cols-2 md:grid-cols-3 gap-4">
</h2> {[
<p className="text-sm text-muted-foreground"> {
Measurable impact and business value generated by this label: "Deployment Year",
project. value: project.publishedYear,
</p> icon: Calendar,
</motion.div> },
{
<div className="grid grid-cols-2 md:grid-cols-4 gap-6 max-w-5xl mx-auto"> label: "Production Period",
{project.results.map((result, index) => ( value: project.workingDuration,
<motion.div icon: Clock,
key={result.label} },
initial={{ opacity: 0, y: 25 }} {
whileInView={{ opacity: 1, y: 0 }} label: "Project Status",
viewport={{ once: true }} value: project.status,
transition={{ delay: index * 0.08, duration: 0.4 }} icon: Sliders,
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" },
].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"> <div className="space-y-0.5 truncate mr-2">
{result.value} <span className="block text-[10px] font-bold tracking-wider uppercase text-muted-foreground">
{item.label}
</span> </span>
<span className="text-xs font-medium text-center text-muted-foreground"> <span className="block text-sm font-bold text-foreground truncate">
{result.label} {item.value}
</span> </span>
</motion.div> </div>
<item.icon className="w-4 h-4 text-primary opacity-70 flex-shrink-0" />
</div>
))} ))}
</div> </div>
</div> </div>
</section> </section>
)}
{/* Main Details Section */} {/* COMPREHENSIVE CASE LOGIC SECTION */}
<section className="py-28"> <section className="py-20 bg-background">
<div className="container mx-auto px-4 md:px-6"> <div className="container mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid lg:grid-cols-12 gap-16 max-w-7xl mx-auto"> <div className="grid grid-cols-1 lg:grid-cols-12 gap-12 lg:gap-16 items-start">
{/* Main Text Content */} {/* Engineering Metrics Column (Left side) */}
<div className="lg:col-span-7 flex flex-col justify-start"> <div className="lg:col-span-6 space-y-10">
<h3 className="text-3xl font-bold tracking-tight mb-8"> {project.problem && (
About the Project <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> </h3>
<div className="prose prose-neutral dark:prose-invert max-w-none text-muted-foreground leading-relaxed"> <p className="text-sm text-muted-foreground leading-relaxed bg-secondary/10 border border-border/40 p-4 rounded-xl">
<ReactMarkdown {project.problem}
remarkPlugins={[remarkGfm]}
components={{
p: ({ children }) => (
<p className="text-base md:text-lg text-muted-foreground leading-relaxed mb-6">
{children}
</p> </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> </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 && ( {project.technologies && project.technologies.length > 0 && (
<div className="border-t border-border/40 pt-8"> <div className="space-y-4">
<h3 className="text-xl font-bold tracking-tight mb-6"> <h3 className="text-sm font-bold tracking-wider uppercase">
Technologies Used Tech Stack
</h3> </h3>
<div className="flex flex-wrap gap-2.5"> <div className="flex flex-wrap gap-1.5">
{project.technologies.map((tech) => ( {project.technologies.map((tech) => (
<span <span
key={tech} 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} {tech}
</span> </span>
@@ -369,19 +334,20 @@ export default function ProjectDetails() {
</div> </div>
)} )}
{/* Challenges Solved */} {project.features && project.features.length > 0 && (
{project.challenges && project.challenges.length > 0 && ( <div className="space-y-4 border-t border-border/40 pt-6">
<div className="border-t border-border/40 pt-8"> <h3 className="text-lg font-bold tracking-tight text-foreground flex items-center gap-2">
<h3 className="text-xl font-bold tracking-tight mb-6"> <Sparkles className="w-4 h-4 text-primary" />
Challenges Solved Delivered Platform Capabilities
</h3> </h3>
<ul className="space-y-4"> <ul className="grid grid-cols-1 sm:grid-cols-2 gap-2.5">
{project.challenges.map((challenge, index) => ( {project.features.map((feature, idx) => (
<li key={index} className="flex items-start gap-4"> <li
<span className="flex-shrink-0 w-1.5 h-1.5 rounded-full bg-purple-500 mt-2.5" /> key={idx}
<span className="text-sm md:text-base text-muted-foreground leading-snug"> className="flex items-center gap-2 text-xs text-muted-foreground bg-card border border-border/40 p-2.5 rounded-lg"
{challenge} >
</span> <div className="w-1.5 h-1.5 rounded-full bg-primary flex-shrink-0" />
<span className="truncate">{feature}</span>
</li> </li>
))} ))}
</ul> </ul>
@@ -392,36 +358,36 @@ export default function ProjectDetails() {
</div> </div>
</section> </section>
{/* Gallery Section */} {/* PLATFORM PRESENTATION SHOWCASE GALLERY */}
{project.gallery && project.gallery.length > 0 && ( {project.imageGallery && project.imageGallery.length > 0 && (
<section className="py-20 border-t border-border/50 bg-secondary/30"> <section className="py-20 border-t border-border/40 bg-secondary/10">
<div className="container mx-auto px-4 md:px-6"> <div className="container mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center max-w-2xl mx-auto mb-14"> <div className="max-w-xl mx-auto text-center mb-12 space-y-1">
<h2 className="text-3xl font-bold tracking-tight mb-3"> <h2 className="text-xl sm:text-2xl font-bold tracking-tight">
Project Gallery Platform Interface Metrics
</h2> </h2>
<p className="text-sm text-muted-foreground"> <p className="text-xs text-muted-foreground">
A visual overview of the implementation and user interface. Operational state validation screens captured during
deployment execution.
</p> </p>
</div> </div>
<div className="grid md:grid-cols-3 gap-6 max-w-6xl mx-auto"> <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{project.gallery.map((image, index) => ( {project.imageGallery.map((image, index) => (
<motion.div <motion.div
key={index} key={index}
initial={{ opacity: 0, scale: 0.97 }} initial={{ opacity: 0, y: shouldReduceMotion ? 0 : 15 }}
whileInView={{ opacity: 1, scale: 1 }} whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }} viewport={{ once: true, margin: "-20px" }}
transition={{ delay: index * 0.06, duration: 0.4 }} transition={{ delay: index * 0.05, duration: 0.4 }}
className="group relative aspect-[4/3] rounded-3xl overflow-hidden border border-border/60 bg-background/40" className="group relative aspect-[4/3] rounded-xl overflow-hidden border border-border bg-card shadow-sm transform-gpu"
> >
<img <img
src={image} src={image}
alt={`${project.title} visual display ${index + 1}`} alt={`Platform runtime verification graphic state capture ${index + 1}`}
className="w-full h-full object-cover object-center transition-all duration-500 group-hover:scale-105" className="w-full h-full object-cover object-center transition-transform duration-500 group-hover:scale-[1.02]"
loading="lazy" 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> </motion.div>
))} ))}
</div> </div>
+165 -209
View File
@@ -1,312 +1,268 @@
import PageTransition from "@/components/home/PageTransition"; import PageTransition from "@/components/home/PageTransition";
import { ProjectCard } from "@/components/home/ProjectCard";
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 { AnimatePresence, motion } from "framer-motion"; import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
import { import {
ArrowLeft, ArrowLeft,
Brain, Brain,
Cloud, Cloud,
ExternalLink,
Github,
Globe, Globe,
Loader2, Layers,
Search, Search,
Smartphone, Smartphone,
Sparkles,
Workflow, Workflow,
} from "lucide-react"; } from "lucide-react";
import { useMemo, useState } from "react"; import { useDeferredValue, useMemo, useState } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
const categories = [ const CATEGORIES = [
{ id: "all", name: "All Projects", icon: null }, { id: "all", name: "All Work", icon: Layers },
{ id: "web", name: "Web", icon: Globe }, { id: "web", name: "Web Systems", icon: Globe },
{ id: "mobile", name: "Mobile", icon: Smartphone }, { id: "mobile", name: "Mobile", icon: Smartphone },
{ id: "ai", name: "AI", icon: Brain }, { id: "ai", name: "AI & ML", icon: Brain },
{ id: "cloud", name: "Cloud", icon: Cloud }, { id: "cloud", name: "Cloud Platforms", icon: Cloud },
{ id: "devops", name: "DEVOPS", icon: Workflow }, { id: "devops", name: "DevOps", icon: Workflow },
]; ];
const containerVariants = { const containerVariants = {
hidden: { opacity: 0 }, hidden: { opacity: 0 },
visible: { visible: {
opacity: 1, opacity: 1,
transition: { transition: { staggerChildren: 0.04 },
staggerChildren: 0.08,
},
}, },
}; };
export default function Projects() { export default function Projects() {
const { data: projectsData, isLoading, isError } = useProjects(); const { data: projectsData, isLoading, isError } = useProjects();
const shouldReduceMotion = useReducedMotion();
const [activeCategory, setActiveCategory] = useState("all"); const [activeCategory, setActiveCategory] = useState("all");
const [searchQuery, setSearchQuery] = useState(""); const [searchQuery, setSearchQuery] = useState("");
const deferredSearchQuery = useDeferredValue(searchQuery);
const filteredProjects = useMemo(() => { const filteredProjects = useMemo(() => {
const projects = projectsData?.data?.data?.result || []; const projects = projectsData?.data?.data || projectsData?.data || [];
if (!projects) return []; if (!Array.isArray(projects)) return [];
return projects.filter((project) => { return projects.filter((project) => {
const matchesCategory = 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 = const matchesSearch =
searchQuery.trim() === "" || normalizedSearch === "" ||
project.title.toLowerCase().includes(searchQuery.toLowerCase()) || project.title?.toLowerCase().includes(normalizedSearch) ||
project.description.toLowerCase().includes(searchQuery.toLowerCase()) || project.description?.toLowerCase().includes(normalizedSearch) ||
(Array.isArray(project.technologies) &&
project.technologies.some((tech: string) => project.technologies.some((tech: string) =>
tech.toLowerCase().includes(searchQuery.toLowerCase()), tech.toLowerCase().includes(normalizedSearch),
); ));
return matchesCategory && matchesSearch; return matchesCategory && matchesSearch;
}); });
}, [projectsData, activeCategory, searchQuery]); }, [projectsData, activeCategory, deferredSearchQuery]);
return ( return (
<PageTransition> <PageTransition>
<div className="min-h-screen bg-background overflow-x-hidden text-foreground antialiased"> <div className="min-h-screen bg-background text-foreground antialiased selection:bg-primary/20">
{/* Hero Section */} <section className="pt-32 pb-12 relative overflow-hidden border-b border-border/40">
<section className="pt-32 pb-16 relative overflow-hidden border-b border-primary/10"> <div className="container mx-auto px-4 sm:px-6 lg:px-8 relative z-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="container mx-auto px-4 relative z-10"> {/* Back Arrow Wrapper - Kept clean on left side */}
{/* Back Button */}
<motion.div <motion.div
initial={{ opacity: 0, x: -20 }} initial={{ opacity: 0, x: shouldReduceMotion ? 0 : -10 }}
animate={{ opacity: 1, x: 0 }} 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="/"> <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" /> <ArrowLeft className="w-4 h-4 mr-2 transition-transform group-hover:-translate-x-1" />
Back to Home Back to Home
</Button>
</Link> </Link>
</Button>
</motion.div> </motion.div>
{/* Header */} {/* Core Header Content Module - Flex-centered alignment */}
<motion.div <div className="w-full flex flex-col items-center justify-center text-center">
initial={{ opacity: 0, y: 15 }} <div className="max-w-3xl mb-12">
<motion.h1
initial={{ opacity: 0, y: shouldReduceMotion ? 0 : 15 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }} className="text-4xl sm:text-5xl lg:text-6xl font-black tracking-tight leading-none mb-4"
className="text-center mb-10 max-w-3xl mx-auto"
> >
<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"> All Projects
Our Projects </motion.h1>
</h1> <motion.p
<p className="text-muted-foreground text-lg md:text-xl leading-relaxed max-w-2xl mx-auto"> initial={{ opacity: 0, y: shouldReduceMotion ? 0 : 15 }}
Explore our portfolio of innovative solutions designed to scale animate={{ opacity: 1, y: 0 }}
and transform businesses across industries. transition={{ delay: 0.05 }}
</p> className="text-base sm:text-lg text-muted-foreground leading-relaxed max-w-2xl mx-auto"
</motion.div> >
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 <motion.div
initial={{ opacity: 0, y: 15 }} initial={{ opacity: 0, y: shouldReduceMotion ? 0 : 15 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.1 }} transition={{ delay: 0.1 }}
className="flex flex-col items-center gap-6 mb-12" className="space-y-6 w-full pt-6 border-t border-border/40 flex flex-col items-center justify-center"
> >
{/* Search Bar */} {/* Search input container centralized down the layout grid */}
<div className="relative w-full max-w-md"> <div className="relative max-w-md w-full group mx-auto">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-muted-foreground" /> <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 <input
type="text" type="text"
placeholder="Search projects, tech, or keywords..." placeholder="Query by parameter, core feature, or framework stack..."
value={searchQuery} value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)} 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" 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 projects" aria-label="Search deployment records"
/> />
</div> </div>
{/* Filter Tabs */} {/* Categories Tab Matrix - Wrapped with center axis parameters */}
<div className="flex flex-wrap justify-center gap-2 max-w-4xl"> <div
{categories.map((category) => { className="flex flex-wrap items-center justify-center gap-2 max-w-2xl w-full"
const Icon = category.icon; role="tablist"
const isActive = activeCategory === category.id; aria-label="Project Filtering Options"
>
{CATEGORIES.map((cat) => {
const Icon = cat.icon;
const isActive = activeCategory === cat.id;
return ( return (
<Button <Button
key={category.id} key={cat.id}
variant={isActive ? "default" : "outline"} variant={isActive ? "default" : "outline"}
onClick={() => setActiveCategory(category.id)} onClick={() => setActiveCategory(cat.id)}
aria-pressed={isActive} role="tab"
className={`rounded-full px-5 py-2 text-sm font-medium transition-all duration-300 ${ aria-selected={isActive}
isActive className="rounded-xl text-xs font-semibold tracking-wide h-9 px-4 transition-all duration-200"
? "bg-primary text-primary-foreground shadow-sm shadow-primary/20"
: "border-border/60 hover:bg-muted/50"
}`}
> >
{Icon && <Icon className="w-4 h-4 mr-2" />} <Icon className="w-3.5 h-3.5 mr-2 opacity-80" />
{category.name} {cat.name}
</Button> </Button>
); );
})} })}
</div> </div>
</motion.div> </motion.div>
</div> </div>
</div>
</section> </section>
{/* Projects Grid */} {/* Projects View Output Section */}
<section className="py-20 bg-background/50"> <section className="py-16 bg-background">
<div className="container mx-auto px-4"> <div className="container mx-auto px-4 sm:px-6 lg:px-8">
{/* Loading / Error State */}
{isLoading && ( {isLoading && (
<div className="flex flex-col items-center justify-center py-24 gap-4 text-muted-foreground animate-pulse"> <div
<Loader2 className="w-8 h-8 animate-spin text-primary" /> className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8"
<span className="text-sm font-medium">Loading projects...</span> 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> </div>
)} )}
{isError && ( {isError && (
<div className="text-center py-20 text-destructive"> <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-lg font-semibold mb-2"> <p className="text-base font-bold text-foreground">
Failed to load projects. API Synchronization Failure
</p> </p>
<p className="text-sm text-muted-foreground"> <p className="text-xs text-muted-foreground leading-normal">
Please check your network connection or try again later. Could not safely sync target records from Techzaa's data layer
cluster. Confirm connection status parameters.
</p> </p>
</div> </div>
)} )}
{!isLoading && !isError && ( {!isLoading && !isError && (
<AnimatePresence mode="wait"> <AnimatePresence mode="popLayout">
{filteredProjects.length > 0 ? (
<motion.div <motion.div
key={`${activeCategory}-${searchQuery}`} key={`${activeCategory}-${deferredSearchQuery}`}
variants={containerVariants} variants={containerVariants}
initial="hidden" initial="hidden"
animate="visible" animate="visible"
exit="hidden" 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) => ( {filteredProjects.map((project, idx) => (
<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 && (
<motion.div <motion.div
initial={{ opacity: 0, y: 10 }} layout={!shouldReduceMotion}
animate={{ opacity: 1, y: 0 }} key={project._id}
className="text-center py-20" className="h-full"
> >
<p className="text-muted-foreground text-lg"> <ProjectCard
No projects match your current search or category. 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> </p>
</motion.div> </motion.div>
)} )}
</AnimatePresence>
)}
</div> </div>
</section> </section>
</div> </div>