abhishek-portfolio-template 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -0
- package/bin/cli.js +54 -0
- package/package.json +27 -0
- package/template/components.json +22 -0
- package/template/next.config.ts +79 -0
- package/template/package.json +43 -0
- package/template/postcss.config.js +6 -0
- package/template/public/BoliviaSignature-ZpWnz.ttf +0 -0
- package/template/public/Gemini_Generated_Image_xc97toxc97toxc97.png +0 -0
- package/template/public/Hendrigo.otf +0 -0
- package/template/public/audiomass-output.mp3 +0 -0
- package/template/public/file.svg +1 -0
- package/template/public/globe.svg +1 -0
- package/template/public/googlec77e59474f5a09cb.html +1 -0
- package/template/public/icon-192x192.png +0 -0
- package/template/public/icon-512x512.png +0 -0
- package/template/public/next.svg +1 -0
- package/template/public/paper sound .mpeg +0 -0
- package/template/public/removebg.png +0 -0
- package/template/public/resume.pdf +0 -0
- package/template/public/sw.js +1 -0
- package/template/public/swe-worker-5c72df51bb1f6ee0.js +1 -0
- package/template/public/vercel.svg +1 -0
- package/template/public/window.svg +1 -0
- package/template/public/workbox-f1770938.js +1 -0
- package/template/src/app/about/page.tsx +91 -0
- package/template/src/app/actions/optimize-text.ts +54 -0
- package/template/src/app/gaming/page.tsx +308 -0
- package/template/src/app/github/[username]/page.tsx +97 -0
- package/template/src/app/globals.css +321 -0
- package/template/src/app/layout.tsx +39 -0
- package/template/src/app/manifest.ts +25 -0
- package/template/src/app/not-found.tsx +16 -0
- package/template/src/app/page.tsx +28 -0
- package/template/src/app/robots.ts +12 -0
- package/template/src/app/sitemap.ts +38 -0
- package/template/src/app/template.tsx +5 -0
- package/template/src/app/works/[slug]/page.tsx +50 -0
- package/template/src/app/works/client.tsx +44 -0
- package/template/src/app/works/page.tsx +24 -0
- package/template/src/components/about/about-client.tsx +259 -0
- package/template/src/components/home/bento-gallery.tsx +52 -0
- package/template/src/components/home/contact-section.tsx +34 -0
- package/template/src/components/home/craft-card.tsx +18 -0
- package/template/src/components/home/featured-projects.tsx +186 -0
- package/template/src/components/home/focus-card.tsx +171 -0
- package/template/src/components/home/identity-card.tsx +45 -0
- package/template/src/components/home/philosophy-card.tsx +104 -0
- package/template/src/components/home/skills-in-motion.tsx +109 -0
- package/template/src/components/home/tech-stack-marquee.tsx +56 -0
- package/template/src/components/ui/3d-folder.tsx +569 -0
- package/template/src/components/ui/avatar.tsx +50 -0
- package/template/src/components/ui/badge.tsx +36 -0
- package/template/src/components/ui/basic-avatar.tsx +12 -0
- package/template/src/components/ui/button.tsx +117 -0
- package/template/src/components/ui/clipboard-secret.tsx +39 -0
- package/template/src/components/ui/command-menu.tsx +519 -0
- package/template/src/components/ui/command-palette.tsx +152 -0
- package/template/src/components/ui/consciousness-mode.tsx +200 -0
- package/template/src/components/ui/copy-code-button.tsx +135 -0
- package/template/src/components/ui/display-cards.tsx +70 -0
- package/template/src/components/ui/dotted-map.tsx +128 -0
- package/template/src/components/ui/dropdown-menu.tsx +200 -0
- package/template/src/components/ui/emoji-rating.tsx +123 -0
- package/template/src/components/ui/exit-message.tsx +50 -0
- package/template/src/components/ui/image-zoom-overlay.tsx +178 -0
- package/template/src/components/ui/input-otp.tsx +71 -0
- package/template/src/components/ui/kbd.tsx +87 -0
- package/template/src/components/ui/location-tag.tsx +232 -0
- package/template/src/components/ui/minimal-testimonial.tsx +97 -0
- package/template/src/components/ui/mobile-menu.tsx +191 -0
- package/template/src/components/ui/navbar.tsx +148 -0
- package/template/src/components/ui/page-transition.tsx +24 -0
- package/template/src/components/ui/pixeleted-404-not-found.tsx +110 -0
- package/template/src/components/ui/preloader-wrapper.tsx +102 -0
- package/template/src/components/ui/preloader.tsx +104 -0
- package/template/src/components/ui/project-contributors.tsx +57 -0
- package/template/src/components/ui/scroll-area.tsx +117 -0
- package/template/src/components/ui/signature.tsx +173 -0
- package/template/src/components/ui/smooth-scroll.tsx +31 -0
- package/template/src/components/ui/social-icons.tsx +103 -0
- package/template/src/components/ui/social-stories.tsx +394 -0
- package/template/src/components/ui/sound-constants.ts +1 -0
- package/template/src/components/ui/text-explode.tsx +188 -0
- package/template/src/components/ui/toast.tsx +80 -0
- package/template/src/components/ui/tooltip.tsx +30 -0
- package/template/src/components/ui/user-location.tsx +151 -0
- package/template/src/components/ui/vertical-image-stack.tsx +345 -0
- package/template/src/components/works/changelog-overlay.tsx +212 -0
- package/template/src/components/works/currently-working-card.tsx +130 -0
- package/template/src/components/works/project-details-view.tsx +464 -0
- package/template/src/components/works/project-grid.tsx +81 -0
- package/template/src/fonts/BoliviaSignature-ZpWnz.ttf +0 -0
- package/template/src/fonts/Hendrigo.otf +0 -0
- package/template/src/lib/data.ts +61 -0
- package/template/src/lib/fonts.ts +14 -0
- package/template/src/lib/github.ts +15 -0
- package/template/src/lib/supabase.ts +11 -0
- package/template/src/lib/utils.ts +6 -0
- package/template/tailwind.config.ts +31 -0
- package/template/tsconfig.json +34 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { motion, AnimatePresence } from "framer-motion";
|
|
4
|
+
import { X, Loader2 } from "lucide-react";
|
|
5
|
+
import { FaGithub, FaCalendarDays, FaCodeCommit, FaUser, FaArrowUpRightFromSquare } from "react-icons/fa6";
|
|
6
|
+
import { SiGithubactions } from "react-icons/si";
|
|
7
|
+
import { useEffect, useState } from "react";
|
|
8
|
+
import { fetchGithubCommits, GithubCommit } from "@/utils/github";
|
|
9
|
+
|
|
10
|
+
interface ChangelogOverlayProps {
|
|
11
|
+
isOpen: boolean;
|
|
12
|
+
onClose: () => void;
|
|
13
|
+
githubUrl?: string;
|
|
14
|
+
projectTitle: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
import { createPortal } from "react-dom";
|
|
18
|
+
|
|
19
|
+
export function ChangelogOverlay({ isOpen, onClose, githubUrl, projectTitle }: ChangelogOverlayProps) {
|
|
20
|
+
const [commits, setCommits] = useState<GithubCommit[]>([]);
|
|
21
|
+
const [loading, setLoading] = useState(false);
|
|
22
|
+
const [currentPage, setCurrentPage] = useState(1);
|
|
23
|
+
const [mounted, setMounted] = useState(false);
|
|
24
|
+
const itemsPerPage = 3; // Fixed number of items to prevent scrolling
|
|
25
|
+
|
|
26
|
+
// Mount state for portal
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
setMounted(true);
|
|
29
|
+
}, []);
|
|
30
|
+
|
|
31
|
+
// Body scroll lock
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (isOpen) {
|
|
34
|
+
document.body.style.overflow = 'hidden';
|
|
35
|
+
const preventDefault = (e: TouchEvent) => {
|
|
36
|
+
// Only prevent if we are touch-moving on the backdrop or non-scrollable parts
|
|
37
|
+
// But since we want NO scroll at all, we'll be strict.
|
|
38
|
+
e.preventDefault();
|
|
39
|
+
};
|
|
40
|
+
document.addEventListener('touchmove', preventDefault, { passive: false });
|
|
41
|
+
return () => {
|
|
42
|
+
document.body.style.overflow = '';
|
|
43
|
+
document.removeEventListener('touchmove', preventDefault);
|
|
44
|
+
};
|
|
45
|
+
} else {
|
|
46
|
+
document.body.style.overflow = '';
|
|
47
|
+
}
|
|
48
|
+
return () => {
|
|
49
|
+
document.body.style.overflow = '';
|
|
50
|
+
};
|
|
51
|
+
}, [isOpen]);
|
|
52
|
+
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (isOpen && githubUrl) {
|
|
55
|
+
setLoading(true);
|
|
56
|
+
fetchGithubCommits(githubUrl).then(data => {
|
|
57
|
+
setCommits(data);
|
|
58
|
+
setLoading(false);
|
|
59
|
+
setCurrentPage(1);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}, [isOpen, githubUrl]);
|
|
63
|
+
|
|
64
|
+
if (!mounted) return null;
|
|
65
|
+
|
|
66
|
+
// Calculate pagination with safety check
|
|
67
|
+
const isCommitsArray = Array.isArray(commits);
|
|
68
|
+
const totalPages = isCommitsArray ? Math.ceil(commits.length / itemsPerPage) : 0;
|
|
69
|
+
const currentCommits = isCommitsArray
|
|
70
|
+
? commits.slice((currentPage - 1) * itemsPerPage, currentPage * itemsPerPage)
|
|
71
|
+
: [];
|
|
72
|
+
|
|
73
|
+
const overlayContent = (
|
|
74
|
+
<AnimatePresence>
|
|
75
|
+
{isOpen && (
|
|
76
|
+
<div
|
|
77
|
+
className="fixed inset-0 z-[99999] flex items-center justify-center p-4 sm:p-6 lg:p-8 overflow-hidden touch-none overscroll-none"
|
|
78
|
+
style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0 }}
|
|
79
|
+
>
|
|
80
|
+
<motion.div
|
|
81
|
+
initial={{ opacity: 0 }}
|
|
82
|
+
animate={{ opacity: 1 }}
|
|
83
|
+
exit={{ opacity: 0 }}
|
|
84
|
+
onClick={onClose}
|
|
85
|
+
className="absolute inset-0 bg-black/98 backdrop-blur-3xl"
|
|
86
|
+
/>
|
|
87
|
+
|
|
88
|
+
{/* Scanline Effect */}
|
|
89
|
+
<div className="absolute inset-0 pointer-events-none bg-[linear-gradient(rgba(18,16,16,0)_50%,rgba(0,0,0,0.25)_50%),linear-gradient(90deg,rgba(255,0,0,0.06),rgba(0,255,0,0.02),rgba(0,0,255,0.06))] bg-[length:100%_2px,3px_100%] z-[101] opacity-30" />
|
|
90
|
+
|
|
91
|
+
<motion.div
|
|
92
|
+
initial={{ opacity: 0, scale: 0.95, y: 20 }}
|
|
93
|
+
animate={{ opacity: 1, scale: 1, y: 0 }}
|
|
94
|
+
exit={{ opacity: 0, scale: 0.95, y: 20 }}
|
|
95
|
+
className="relative w-full max-w-2xl bg-[#050505] border border-white/10 rounded-[40px] shadow-[0_0_80px_rgba(0,0,0,0.8)] flex flex-col h-auto max-h-[95vh] sm:max-h-[85vh] z-[102] touch-auto overflow-hidden"
|
|
96
|
+
onClick={(e) => e.stopPropagation()}
|
|
97
|
+
>
|
|
98
|
+
{/* Header */}
|
|
99
|
+
<div className="p-6 sm:p-8 border-b border-white/10 flex items-center justify-between relative overflow-hidden group">
|
|
100
|
+
<div className="absolute inset-0 bg-gradient-to-r from-emerald-500/5 via-transparent to-blue-500/5 opacity-50" />
|
|
101
|
+
<div className="flex items-center gap-5 relative z-10">
|
|
102
|
+
<div className="p-3.5 rounded-2xl bg-[#111] border border-white/10">
|
|
103
|
+
<FaGithub className="w-5 h-5 text-emerald-400" />
|
|
104
|
+
</div>
|
|
105
|
+
<div className="flex-1 min-w-0">
|
|
106
|
+
<h2 className="text-xl sm:text-2xl font-black text-white tracking-tighter uppercase italic truncate">{projectTitle}</h2>
|
|
107
|
+
<p className="text-[10px] uppercase font-black tracking-[0.3em] text-white/30 hidden sm:block">System_Protocol::V_HISTORY</p>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<button
|
|
112
|
+
onClick={onClose}
|
|
113
|
+
className="relative z-10 p-3 rounded-full bg-white/5 text-white/40 hover:text-white transition-all group/close"
|
|
114
|
+
aria-label="Close"
|
|
115
|
+
>
|
|
116
|
+
<X className="w-6 h-6 group-hover/close:rotate-90 transition-transform duration-500" />
|
|
117
|
+
</button>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
{/* Content Area */}
|
|
121
|
+
<div className="flex-1 p-6 sm:p-8 space-y-6 overflow-hidden min-h-[400px]">
|
|
122
|
+
{loading ? (
|
|
123
|
+
<div className="h-80 flex flex-col items-center justify-center gap-6">
|
|
124
|
+
<Loader2 className="w-12 h-12 animate-spin text-emerald-500/50" />
|
|
125
|
+
<p className="text-[10px] uppercase font-black tracking-[0.5em] text-white/20 animate-pulse italic">Accessing_Encrypted_Logs...</p>
|
|
126
|
+
</div>
|
|
127
|
+
) : currentCommits.length > 0 ? (
|
|
128
|
+
<div className="space-y-4 h-full">
|
|
129
|
+
{currentCommits.map((commit, idx) => (
|
|
130
|
+
<motion.div
|
|
131
|
+
key={commit.sha}
|
|
132
|
+
initial={{ opacity: 0, x: -10 }}
|
|
133
|
+
animate={{ opacity: 1, x: 0 }}
|
|
134
|
+
transition={{ delay: idx * 0.05 }}
|
|
135
|
+
className="bg-[#0c0c0c] border border-white/5 rounded-[24px] p-5 sm:p-6 group hover:bg-[#0e0e0e] hover:border-white/10 transition-all duration-300 relative overflow-hidden"
|
|
136
|
+
>
|
|
137
|
+
<div className="flex justify-between items-start gap-4 mb-4 relative z-10">
|
|
138
|
+
<p className="text-sm sm:text-base text-white/90 font-medium leading-normal flex-1 tracking-tight line-clamp-2">
|
|
139
|
+
{commit.commit.message.split('\n')[0]}
|
|
140
|
+
</p>
|
|
141
|
+
<a
|
|
142
|
+
href={commit.html_url}
|
|
143
|
+
target="_blank"
|
|
144
|
+
rel="noopener noreferrer"
|
|
145
|
+
className="p-2 rounded-xl bg-white/5 text-white/30 hover:text-emerald-400 transition-all"
|
|
146
|
+
>
|
|
147
|
+
<FaArrowUpRightFromSquare className="w-3.5 h-3.5" />
|
|
148
|
+
</a>
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
<div className="flex items-center justify-between text-[9px] uppercase font-black tracking-[0.15em] relative z-10 text-white/30">
|
|
152
|
+
<div className="flex items-center gap-2">
|
|
153
|
+
<span className="text-emerald-400">{commit.author?.login || "System"}</span>
|
|
154
|
+
<span className="opacity-20">•</span>
|
|
155
|
+
<span>
|
|
156
|
+
{new Date(commit.commit.author.date).toLocaleDateString(undefined, {
|
|
157
|
+
month: 'short',
|
|
158
|
+
day: 'numeric'
|
|
159
|
+
})}
|
|
160
|
+
</span>
|
|
161
|
+
</div>
|
|
162
|
+
<div className="font-mono opacity-50">#{commit.sha.substring(0, 7)}</div>
|
|
163
|
+
</div>
|
|
164
|
+
</motion.div>
|
|
165
|
+
))}
|
|
166
|
+
</div>
|
|
167
|
+
) : (
|
|
168
|
+
<div className="h-60 flex flex-col items-center justify-center text-white/10 uppercase font-black tracking-widest text-xs italic">
|
|
169
|
+
Null_Reference::Log_Empty
|
|
170
|
+
</div>
|
|
171
|
+
)}
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
{/* Paginated Hud Footer */}
|
|
175
|
+
<div className="px-6 sm:p-8 py-5 bg-[#080808] border-t border-white/5 flex items-center justify-between">
|
|
176
|
+
<div className="text-[10px] uppercase font-black tracking-[0.4em] text-white/20 flex items-center gap-4">
|
|
177
|
+
<button
|
|
178
|
+
onClick={() => setCurrentPage(p => Math.max(1, p - 1))}
|
|
179
|
+
disabled={currentPage === 1 || loading}
|
|
180
|
+
className="p-2.5 rounded-xl bg-white/5 border border-white/5 text-white/40 disabled:opacity-20 hover:text-emerald-400 hover:border-emerald-500/30 transition-all cursor-pointer"
|
|
181
|
+
>
|
|
182
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3"><path d="m15 18-6-6 6-6" /></svg>
|
|
183
|
+
</button>
|
|
184
|
+
|
|
185
|
+
<span className="text-white/40">
|
|
186
|
+
<span className="text-emerald-500">{currentPage.toString().padStart(2, '0')}</span>
|
|
187
|
+
<span className="mx-2 opacity-20">/</span>
|
|
188
|
+
{totalPages.toString().padStart(2, '0')}
|
|
189
|
+
</span>
|
|
190
|
+
|
|
191
|
+
<button
|
|
192
|
+
onClick={() => setCurrentPage(p => Math.min(totalPages, p + 1))}
|
|
193
|
+
disabled={currentPage === totalPages || loading}
|
|
194
|
+
className="p-2.5 rounded-xl bg-white/5 border border-white/5 text-white/40 disabled:opacity-20 hover:text-emerald-400 hover:border-emerald-500/30 transition-all cursor-pointer"
|
|
195
|
+
>
|
|
196
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3"><path d="m9 18 6-6-6-6" /></svg>
|
|
197
|
+
</button>
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
<div className="flex items-center gap-2 text-[8px] uppercase font-black tracking-[0.4em] text-emerald-500/40">
|
|
201
|
+
<span className="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse" />
|
|
202
|
+
<span className="hidden sm:inline">LIVE_FEED_SYNC</span>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
</motion.div>
|
|
206
|
+
</div>
|
|
207
|
+
)}
|
|
208
|
+
</AnimatePresence>
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
return createPortal(overlayContent, document.body);
|
|
212
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { motion } from "framer-motion";
|
|
4
|
+
import Image from "next/image";
|
|
5
|
+
import Link from "next/link";
|
|
6
|
+
import { ArrowUpRight } from "lucide-react";
|
|
7
|
+
import { Project } from "@/types/project";
|
|
8
|
+
|
|
9
|
+
interface StatusConfigItem {
|
|
10
|
+
width: string;
|
|
11
|
+
color: string;
|
|
12
|
+
glow: string;
|
|
13
|
+
animate: boolean;
|
|
14
|
+
pulseDuration?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const statusConfig: Record<string, StatusConfigItem> = {
|
|
18
|
+
'Not Started': {
|
|
19
|
+
width: '5%',
|
|
20
|
+
color: 'bg-white/10',
|
|
21
|
+
glow: 'shadow-none',
|
|
22
|
+
animate: false
|
|
23
|
+
},
|
|
24
|
+
'In Progress': {
|
|
25
|
+
width: '45%',
|
|
26
|
+
color: 'bg-yellow-500',
|
|
27
|
+
glow: 'shadow-[0_0_15px_rgba(234,179,8,0.4)]',
|
|
28
|
+
animate: true,
|
|
29
|
+
pulseDuration: 3
|
|
30
|
+
},
|
|
31
|
+
'Near Completion': {
|
|
32
|
+
width: '85%',
|
|
33
|
+
color: 'bg-emerald-400',
|
|
34
|
+
glow: 'shadow-[0_0_20px_rgba(52,211,153,0.5)]',
|
|
35
|
+
animate: true,
|
|
36
|
+
pulseDuration: 1.5
|
|
37
|
+
},
|
|
38
|
+
'Completed': {
|
|
39
|
+
width: '100%',
|
|
40
|
+
color: 'bg-blue-500',
|
|
41
|
+
glow: 'shadow-none',
|
|
42
|
+
animate: false
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export function CurrentlyWorkingCard({ project }: { project: Project }) {
|
|
47
|
+
const config = statusConfig[project.status] || statusConfig['Not Started'];
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<div className="w-full flex justify-center">
|
|
51
|
+
<div className="w-full max-w-[1200px]">
|
|
52
|
+
<div className="flex items-center gap-3 mb-6 pl-2">
|
|
53
|
+
<div className="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse" />
|
|
54
|
+
<span className="text-xs font-bold uppercase tracking-widest text-white/40">Currently Working</span>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<div className="relative group overflow-visible">
|
|
58
|
+
<Link href={`/works/${project.slug}`} className="block">
|
|
59
|
+
<div className="relative w-full bg-[#111] rounded-[32px] border border-white/5 overflow-hidden flex flex-col md:flex-row hover:border-white/10 transition-colors duration-500 min-h-[400px]">
|
|
60
|
+
|
|
61
|
+
{/* LEFT: Image Preview */}
|
|
62
|
+
<div className="w-full md:w-[55%] h-[300px] md:h-auto relative bg-black/50 overflow-hidden">
|
|
63
|
+
<Image
|
|
64
|
+
src={project.image_url}
|
|
65
|
+
alt={project.title}
|
|
66
|
+
fill
|
|
67
|
+
className="object-cover transition-transform duration-700 group-hover:scale-105"
|
|
68
|
+
/>
|
|
69
|
+
{/* Inner Shadow Overlay for depth */}
|
|
70
|
+
<div className="absolute inset-0 bg-gradient-to-t from-[#111] via-transparent to-transparent md:bg-gradient-to-r md:from-transparent md:to-[#111]" />
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
{/* RIGHT: Project Info */}
|
|
74
|
+
<div className="flex-1 p-8 md:p-12 flex flex-col justify-center relative">
|
|
75
|
+
{/* Content */}
|
|
76
|
+
<div className="space-y-6 z-10">
|
|
77
|
+
<div>
|
|
78
|
+
<h2 className="text-3xl md:text-4xl font-bold text-white mb-2 leading-tight group-hover:text-blue-200/90 transition-colors">
|
|
79
|
+
{project.title}
|
|
80
|
+
</h2>
|
|
81
|
+
<p className="text-lg text-white/50 leading-relaxed max-w-md">
|
|
82
|
+
{project.description}
|
|
83
|
+
</p>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
{/* Tech Stack Chips */}
|
|
87
|
+
<div className="flex flex-wrap gap-2">
|
|
88
|
+
{project.tech_stack.slice(0, 4).map((tech, i) => (
|
|
89
|
+
<span key={i} className="px-3 py-1.5 rounded-full bg-white/5 border border-white/5 text-xs text-white/60 font-medium">
|
|
90
|
+
{tech}
|
|
91
|
+
</span>
|
|
92
|
+
))}
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
{/* Divider */}
|
|
96
|
+
<div className="w-full h-px bg-white/5 my-4" />
|
|
97
|
+
|
|
98
|
+
{/* Progress Section */}
|
|
99
|
+
<div className="space-y-3">
|
|
100
|
+
<div className="flex justify-between items-center text-xs font-medium uppercase tracking-widest text-white/30">
|
|
101
|
+
<span>Progress</span>
|
|
102
|
+
<span className={config.animate ? "text-white" : ""}>{project.status}</span>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
{/* Bar Container */}
|
|
106
|
+
<div className="h-2 w-full bg-white/5 rounded-full overflow-hidden relative">
|
|
107
|
+
{/* Animated Fill */}
|
|
108
|
+
<motion.div
|
|
109
|
+
initial={{ width: 0 }}
|
|
110
|
+
whileInView={{ width: config.width }}
|
|
111
|
+
transition={{ duration: 1.5, ease: "easeOut" }}
|
|
112
|
+
className={`h-full rounded-full ${config.color} ${config.glow}`}
|
|
113
|
+
viewport={{ once: true }}
|
|
114
|
+
/>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
{/* Arrow Icon */}
|
|
120
|
+
<div className="absolute top-8 right-8 text-white/20 group-hover:text-white transition-colors">
|
|
121
|
+
<ArrowUpRight className="w-6 h-6" />
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
</Link>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
);
|
|
130
|
+
}
|