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)
+137 -66
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(0deg) rotateY(0deg)", ? `perspective(1000px) rotateX(${(50 - mousePosition.y) * 0.06}deg) rotateY(${(mousePosition.x - 50) * 0.06}deg)`
: "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 */} <img
<div className="aspect-[16/13] overflow-hidden relative"> src={projectImage}
<img alt={`Interface screenshot overview for ${project.title}`}
src={project.image} className="w-full h-full object-cover transition-transform duration-700 ease-out group-hover:scale-[1.04]"
alt={project.title} loading="lazy"
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110"
/>
{/* Dynamic Shine Overlay */}
<div
className="absolute inset-0 opacity-0 group-hover:opacity-30 transition-opacity duration-300 pointer-events-none"
style={{
background: `radial-gradient(circle at ${mousePosition.x}% ${mousePosition.y}%, rgba(255,255,255,0.8) 0%, transparent 60%)`,
}}
/>
</div>
{/* Gradient Overlay */}
<div
className={`absolute inset-0 bg-gradient-to-t ${color} via-black/40 to-transparent opacity-60 group-hover:opacity-90 transition-all duration-500`}
/> />
{/* Content */} {/* Techzaa Radial Spotlight Overlay */}
<div className="absolute inset-0 flex flex-col justify-end p-8"> {!shouldReduceMotion && (
<div className="space-y-4 transform transition-all duration-500 group-hover:translate-y-0"> <div
{/* Category */} className="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity duration-500 pointer-events-none mix-blend-screen"
<motion.span style={{
initial={{ opacity: 0, y: 20 }} background: `radial-gradient(circle 180px at ${mousePosition.x}% ${mousePosition.y}%, rgba(var(--primary-rgb, 99, 102, 241), 0.15), transparent 80%)`,
animate={ }}
isHovered ? { opacity: 1, y: 0 } : { opacity: 0.7, y: 10 } />
} )}
className="inline-block px-4 py-1.5 rounded-full bg-white/10 backdrop-blur-md text-white text-xs font-medium border border-white/20"
>
{project.category}
</motion.span>
{/* Title */} {/* Absolute Year Floating Tag */}
<h3 className="text-2xl md:text-3xl font-bold text-white leading-tight tracking-tight"> <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.title} {project.publishedYear}
</h3> </span>
</div>
{/* View Project Link */} {/* Structured Informational Body Content */}
<Link <div className="p-6 flex flex-col flex-grow justify-between space-y-6">
to={project.liveUrl} <div className="space-y-3">
className="flex items-center gap-3 text-white/80 group-hover:text-white transition-colors" {/* Category Chip List Mapping handles strings or arrays cleanly */}
> <div
<span className="font-medium text-sm tracking-wider">live</span> className="flex flex-wrap gap-1"
<ExternalLink className="w-5 h-5 transition-transform group-hover:rotate-45" /> aria-label="Project classifications"
</Link> >
{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}
</span>
)}
</div> </div>
{/* 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>
{/* 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"
>
{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> </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>
); );
} }
+9 -9
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" });
@@ -50,9 +50,12 @@ export default function ProjectsSection() {
transition={{ duration: 0.7 }} transition={{ duration: 0.7 }}
className="text-center mb-20" 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"> <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 /> */}
+273 -307
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" />
</h1> </div>
<p className="text-muted-foreground mb-6"> <div className="space-y-2">
The project you're looking for does not exist or could not be <h1 className="text-xl font-bold tracking-tight">
loaded. Deployment Not Located
</p> </h1>
<p className="text-xs text-muted-foreground leading-relaxed">
The targeted project record configuration cannot be pulled from
the API ecosystem.
</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">
<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"
>
Home
</Link>
</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 focus-visible:outline-none focus-visible:underline"
>
Projects
</Link>
</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>
</li>
</ol>
</nav>
{/* Asymmetric Split Layout */}
{/* Hero Section */} <div className="grid grid-cols-1 lg:grid-cols-12 gap-12 items-start">
<section className="pt-36 pb-20 relative overflow-hidden"> {/* Primary Metas Block */}
{/* Ambient Background Glows */} <div className="lg:col-span-7 space-y-6">
<div className="absolute inset-0 pointer-events-none z-0"> <motion.div
<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]" /> initial="hidden"
<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" /> animate="visible"
</div> custom={0}
className="flex flex-wrap gap-1.5"
<div className="container mx-auto px-4 md:px-6 relative z-10">
{/* Breadcrumb Navigation */}
<motion.nav
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4 }}
className="flex items-center gap-2 text-xs font-medium text-muted-foreground/80 mb-10"
aria-label="Breadcrumb"
>
<Link to="/" className="hover:text-primary transition-colors">
Home
</Link>
<ChevronRight className="w-3.5 h-3.5 opacity-50" />
<Link
to="/projects"
className="hover:text-primary transition-colors"
>
Projects
</Link>
<ChevronRight className="w-3.5 h-3.5 opacity-50" />
<span className="text-foreground font-semibold truncate">
{project.title}
</span>
</motion.nav>
<div className="grid lg:grid-cols-2 gap-12 items-center">
{/* Content 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"
> >
{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"
> >
{[ <Button
{ label: "Year", value: project.year, icon: Calendar }, asChild
{ label: "Duration", value: project.duration, icon: Clock }, size="lg"
{ label: "Team Size", value: project.team, icon: Users }, className="rounded-xl font-semibold tracking-wide shadow-lg shadow-primary/5 transition-transform active:scale-[0.98]"
{ 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"> <a
<ExternalLink className="w-4 h-4" /> href={project.previewUrl}
View Live
</Button>
</Link>
{project.codeUrl && (
<Link
to={project.codeUrl}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="w-full sm:w-auto"
> >
<Button <ExternalLink className="w-4 h-4 mr-2" />
variant="outline" Live Preview Link
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" </a>
> </Button>
<Github className="w-4 h-4 text-muted-foreground" />
View Code
</Button>
</Link>
)}
</motion.div> </motion.div>
</div> </div>
{/* Project Cover Visual */} {/* Cover Interactive Card Frame */}
<motion.div <div className="lg:col-span-5 w-full">
initial={{ opacity: 0, scale: 0.95 }} <motion.div
animate={{ opacity: 1, scale: 1 }} initial={{ opacity: 0, scale: shouldReduceMotion ? 1 : 0.97 }}
transition={{ duration: 0.6, delay: 0.2 }} animate={{ opacity: 1, scale: 1 }}
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} <img
alt={project.title} src={project.image}
className="w-full h-full object-cover object-center transition-transform duration-700 ease-out group-hover:scale-[1.02]" alt={`Case Application mockup frame for ${project.title}`}
loading="lazy" 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> </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 }} <div className="container mx-auto px-4 sm:px-6 lg:px-8">
whileInView={{ opacity: 1, y: 0 }} <div className="grid grid-cols-2 md:grid-cols-3 gap-4">
viewport={{ once: true }} {[
className="text-center max-w-2xl mx-auto mb-14" {
> label: "Deployment Year",
<h2 className="text-3xl font-bold tracking-tight mb-3"> value: project.publishedYear,
Key Performance Outcomes icon: Calendar,
</h2> },
<p className="text-sm text-muted-foreground"> {
Measurable impact and business value generated by this label: "Production Period",
project. value: project.workingDuration,
</p> icon: Clock,
</motion.div> },
{
<div className="grid grid-cols-2 md:grid-cols-4 gap-6 max-w-5xl mx-auto"> label: "Project Status",
{project.results.map((result, index) => ( value: project.status,
<motion.div icon: Sliders,
key={result.label} },
initial={{ opacity: 0, y: 25 }} ].map((item) => (
whileInView={{ opacity: 1, y: 0 }} <div
viewport={{ once: true }} key={item.label}
transition={{ delay: index * 0.08, duration: 0.4 }} className="p-4 bg-card border border-border/60 rounded-xl flex items-center justify-between shadow-sm"
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="space-y-0.5 truncate mr-2">
<span className="text-3xl md:text-4xl font-black text-primary tracking-tight mb-2"> <span className="block text-[10px] font-bold tracking-wider uppercase text-muted-foreground">
{result.value} {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>
</section>
)}
{/* Main Details Section */}
<section className="py-28">
<div className="container mx-auto px-4 md:px-6">
<div className="grid lg:grid-cols-12 gap-16 max-w-7xl mx-auto">
{/* Main Text Content */}
<div className="lg:col-span-7 flex flex-col justify-start">
<h3 className="text-3xl font-bold tracking-tight mb-8">
About the Project
</h3>
<div className="prose prose-neutral dark:prose-invert max-w-none text-muted-foreground leading-relaxed">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={{
p: ({ children }) => (
<p className="text-base md:text-lg text-muted-foreground leading-relaxed mb-6">
{children}
</p>
),
strong: ({ children }) => (
<strong className="font-semibold text-foreground">
{children}
</strong>
),
ul: ({ children }) => (
<ul className="list-disc list-inside space-y-2 mb-6 ml-2 text-muted-foreground">
{children}
</ul>
),
li: ({ children }) => (
<li className="text-muted-foreground">{children}</li>
),
}}
>
{project.fullDescription}
</ReactMarkdown>
</div> </div>
</div> ))}
</div>
</div>
</section>
{/* Sidebar Capabilities */} {/* COMPREHENSIVE CASE LOGIC SECTION */}
<div className="lg:col-span-5 flex flex-col gap-12"> <section className="py-20 bg-background">
{/* Features */} <div className="container mx-auto px-4 sm:px-6 lg:px-8">
{project.features && project.features.length > 0 && ( <div className="grid grid-cols-1 lg:grid-cols-12 gap-12 lg:gap-16 items-start">
<div> {/* Engineering Metrics Column (Left side) */}
<h3 className="text-xl font-bold tracking-tight mb-6"> <div className="lg:col-span-6 space-y-10">
Key Capabilities {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> </h3>
<ul className="space-y-4"> <p className="text-sm text-muted-foreground leading-relaxed bg-secondary/10 border border-border/40 p-4 rounded-xl">
{project.features.map((feature, index) => ( {project.problem}
<li key={index} className="flex items-start gap-4"> </p>
<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 && (
{project.technologies && project.technologies.length > 0 && ( <div className="space-y-3">
<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"> <CheckCircle className="w-4 h-4 text-primary" />
Technologies Used Our Applied Strategy
</h3> </h3>
<div className="flex flex-wrap gap-2.5"> <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="space-y-4">
<h3 className="text-sm font-bold tracking-wider uppercase">
Tech Stack
</h3>
<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>
+199 -243
View File
@@ -1,315 +1,271 @@
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) ||
project.technologies.some((tech: string) => (Array.isArray(project.technologies) &&
tech.toLowerCase().includes(searchQuery.toLowerCase()), project.technologies.some((tech: string) =>
); 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" /> {/* Back Arrow Wrapper - Kept clean on left side */}
<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 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"
> >
<Link to="/"> <Button
<Button variant="ghost" className="mb-8 group h-10 px-4"> asChild
variant="ghost"
size="sm"
className="mb-6 group text-muted-foreground hover:text-foreground"
>
<Link to="/">
<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">
animate={{ opacity: 1, y: 0 }} <motion.h1
transition={{ duration: 0.5 }} initial={{ opacity: 0, y: shouldReduceMotion ? 0 : 15 }}
className="text-center mb-10 max-w-3xl mx-auto" animate={{ opacity: 1, y: 0 }}
> 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 All Projects
</h1> </motion.h1>
<p className="text-muted-foreground text-lg md:text-xl leading-relaxed max-w-2xl mx-auto"> <motion.p
Explore our portfolio of innovative solutions designed to scale initial={{ opacity: 0, y: shouldReduceMotion ? 0 : 15 }}
and transform businesses across industries. animate={{ opacity: 1, y: 0 }}
</p> transition={{ delay: 0.05 }}
</motion.div> className="text-base sm:text-lg text-muted-foreground leading-relaxed max-w-2xl mx-auto"
>
{/* Interactive Search and Filter Section */} A high-fidelity showroom tracking production ecosystems, AI
<motion.div operations, and cloud architectures compiled by Techzaa.
initial={{ opacity: 0, y: 15 }} </motion.p>
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.1 }}
className="flex flex-col items-center gap-6 mb-12"
>
{/* 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" />
<input
type="text"
placeholder="Search projects, tech, or keywords..."
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"
/>
</div> </div>
{/* Filter Tabs */} {/* Interface Control Bar Matrix - Fully Centered Substructures */}
<div className="flex flex-wrap justify-center gap-2 max-w-4xl"> <motion.div
{categories.map((category) => { initial={{ opacity: 0, y: shouldReduceMotion ? 0 : 15 }}
const Icon = category.icon; animate={{ opacity: 1, y: 0 }}
const isActive = activeCategory === category.id; 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 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="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 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>
{/* 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={cat.id}
variant={isActive ? "default" : "outline"}
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 className="w-3.5 h-3.5 mr-2 opacity-80" />
{cat.name}
</Button>
);
})}
</div>
</motion.div>
</div>
return (
<Button
key={category.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"
}`}
>
{Icon && <Icon className="w-4 h-4 mr-2" />}
{category.name}
</Button>
);
})}
</div>
</motion.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">
<motion.div {filteredProjects.length > 0 ? (
key={`${activeCategory}-${searchQuery}`} <motion.div
variants={containerVariants} key={`${activeCategory}-${deferredSearchQuery}`}
initial="hidden" variants={containerVariants}
animate="visible" initial="hidden"
exit="hidden" animate="visible"
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8" exit="hidden"
> className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 items-stretch"
{filteredProjects.map((project) => ( >
<motion.article {filteredProjects.map((project, idx) => (
key={project._id} <motion.div
layout layout={!shouldReduceMotion}
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" key={project._id}
> className="h-full"
<div> >
{/* Project Image */} <ProjectCard
<div className="relative h-56 rounded-2xl overflow-hidden mb-6"> project={{
<img _id: project._id,
src={project.image} title: project.title,
alt={project.title} category: Array.isArray(project.category)
loading="lazy" ? project.category
className="w-full h-full object-cover transition-transform duration-700 ease-out group-hover:scale-105" : [project.category],
/> isFeatured: project.isFeatured || false,
<div className="absolute inset-0 bg-gradient-to-t from-background/90 via-background/20 to-transparent opacity-80" /> technologies: project.technologies || [],
publishedYear:
{/* Category Badge */} project.publishedYear || project.year || "2026",
<div className="absolute top-4 left-4"> previewUrl:
<span className="px-3 py-1 rounded-full bg-primary/20 text-primary text-xs font-semibold backdrop-blur-md border border-primary/20"> project.previewUrl || project.liveUrl || "#",
{ image: project.image,
categories.find( }}
(c) => c.id === project.category, index={idx}
)?.name isInView={true}
} />
</span> </motion.div>
</div> ))}
</motion.div>
{/* Quick External Links */} ) : (
<div className="absolute top-4 right-4 flex gap-2 opacity-0 group-hover:opacity-100 transition-opacity duration-300"> <motion.div
{project.liveUrl && ( initial={{ opacity: 0 }}
<a animate={{ opacity: 1 }}
href={project.liveUrl} className="text-center py-24 border border-dashed border-border rounded-2xl max-w-md mx-auto p-8 space-y-2"
target="_blank" >
rel="noopener noreferrer" <Sparkles className="w-5 h-5 text-muted-foreground/60 mx-auto" />
className="p-2 rounded-full bg-background/80 backdrop-blur-sm border border-border/40 hover:bg-primary/20 transition-colors" <h3 className="text-sm font-bold text-foreground">
aria-label={`View live project: ${project.title}`} No Projects Located
> </h3>
<ExternalLink className="w-4 h-4" /> <p className="text-xs text-muted-foreground leading-relaxed">
</a> Your lookup parameter variations produced zero operational
)} matches in our architecture records.
{project.githubUrl && ( </p>
<a </motion.div>
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> </AnimatePresence>
)} )}
{/* Empty State */}
{!isLoading && !isError && filteredProjects.length === 0 && (
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className="text-center py-20"
>
<p className="text-muted-foreground text-lg">
No projects match your current search or category.
</p>
</motion.div>
)}
</div> </div>
</section> </section>
</div> </div>
</PageTransition> </PageTransition>
); );
} }