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.
Files changed (101) hide show
  1. package/README.md +59 -0
  2. package/bin/cli.js +54 -0
  3. package/package.json +27 -0
  4. package/template/components.json +22 -0
  5. package/template/next.config.ts +79 -0
  6. package/template/package.json +43 -0
  7. package/template/postcss.config.js +6 -0
  8. package/template/public/BoliviaSignature-ZpWnz.ttf +0 -0
  9. package/template/public/Gemini_Generated_Image_xc97toxc97toxc97.png +0 -0
  10. package/template/public/Hendrigo.otf +0 -0
  11. package/template/public/audiomass-output.mp3 +0 -0
  12. package/template/public/file.svg +1 -0
  13. package/template/public/globe.svg +1 -0
  14. package/template/public/googlec77e59474f5a09cb.html +1 -0
  15. package/template/public/icon-192x192.png +0 -0
  16. package/template/public/icon-512x512.png +0 -0
  17. package/template/public/next.svg +1 -0
  18. package/template/public/paper sound .mpeg +0 -0
  19. package/template/public/removebg.png +0 -0
  20. package/template/public/resume.pdf +0 -0
  21. package/template/public/sw.js +1 -0
  22. package/template/public/swe-worker-5c72df51bb1f6ee0.js +1 -0
  23. package/template/public/vercel.svg +1 -0
  24. package/template/public/window.svg +1 -0
  25. package/template/public/workbox-f1770938.js +1 -0
  26. package/template/src/app/about/page.tsx +91 -0
  27. package/template/src/app/actions/optimize-text.ts +54 -0
  28. package/template/src/app/gaming/page.tsx +308 -0
  29. package/template/src/app/github/[username]/page.tsx +97 -0
  30. package/template/src/app/globals.css +321 -0
  31. package/template/src/app/layout.tsx +39 -0
  32. package/template/src/app/manifest.ts +25 -0
  33. package/template/src/app/not-found.tsx +16 -0
  34. package/template/src/app/page.tsx +28 -0
  35. package/template/src/app/robots.ts +12 -0
  36. package/template/src/app/sitemap.ts +38 -0
  37. package/template/src/app/template.tsx +5 -0
  38. package/template/src/app/works/[slug]/page.tsx +50 -0
  39. package/template/src/app/works/client.tsx +44 -0
  40. package/template/src/app/works/page.tsx +24 -0
  41. package/template/src/components/about/about-client.tsx +259 -0
  42. package/template/src/components/home/bento-gallery.tsx +52 -0
  43. package/template/src/components/home/contact-section.tsx +34 -0
  44. package/template/src/components/home/craft-card.tsx +18 -0
  45. package/template/src/components/home/featured-projects.tsx +186 -0
  46. package/template/src/components/home/focus-card.tsx +171 -0
  47. package/template/src/components/home/identity-card.tsx +45 -0
  48. package/template/src/components/home/philosophy-card.tsx +104 -0
  49. package/template/src/components/home/skills-in-motion.tsx +109 -0
  50. package/template/src/components/home/tech-stack-marquee.tsx +56 -0
  51. package/template/src/components/ui/3d-folder.tsx +569 -0
  52. package/template/src/components/ui/avatar.tsx +50 -0
  53. package/template/src/components/ui/badge.tsx +36 -0
  54. package/template/src/components/ui/basic-avatar.tsx +12 -0
  55. package/template/src/components/ui/button.tsx +117 -0
  56. package/template/src/components/ui/clipboard-secret.tsx +39 -0
  57. package/template/src/components/ui/command-menu.tsx +519 -0
  58. package/template/src/components/ui/command-palette.tsx +152 -0
  59. package/template/src/components/ui/consciousness-mode.tsx +200 -0
  60. package/template/src/components/ui/copy-code-button.tsx +135 -0
  61. package/template/src/components/ui/display-cards.tsx +70 -0
  62. package/template/src/components/ui/dotted-map.tsx +128 -0
  63. package/template/src/components/ui/dropdown-menu.tsx +200 -0
  64. package/template/src/components/ui/emoji-rating.tsx +123 -0
  65. package/template/src/components/ui/exit-message.tsx +50 -0
  66. package/template/src/components/ui/image-zoom-overlay.tsx +178 -0
  67. package/template/src/components/ui/input-otp.tsx +71 -0
  68. package/template/src/components/ui/kbd.tsx +87 -0
  69. package/template/src/components/ui/location-tag.tsx +232 -0
  70. package/template/src/components/ui/minimal-testimonial.tsx +97 -0
  71. package/template/src/components/ui/mobile-menu.tsx +191 -0
  72. package/template/src/components/ui/navbar.tsx +148 -0
  73. package/template/src/components/ui/page-transition.tsx +24 -0
  74. package/template/src/components/ui/pixeleted-404-not-found.tsx +110 -0
  75. package/template/src/components/ui/preloader-wrapper.tsx +102 -0
  76. package/template/src/components/ui/preloader.tsx +104 -0
  77. package/template/src/components/ui/project-contributors.tsx +57 -0
  78. package/template/src/components/ui/scroll-area.tsx +117 -0
  79. package/template/src/components/ui/signature.tsx +173 -0
  80. package/template/src/components/ui/smooth-scroll.tsx +31 -0
  81. package/template/src/components/ui/social-icons.tsx +103 -0
  82. package/template/src/components/ui/social-stories.tsx +394 -0
  83. package/template/src/components/ui/sound-constants.ts +1 -0
  84. package/template/src/components/ui/text-explode.tsx +188 -0
  85. package/template/src/components/ui/toast.tsx +80 -0
  86. package/template/src/components/ui/tooltip.tsx +30 -0
  87. package/template/src/components/ui/user-location.tsx +151 -0
  88. package/template/src/components/ui/vertical-image-stack.tsx +345 -0
  89. package/template/src/components/works/changelog-overlay.tsx +212 -0
  90. package/template/src/components/works/currently-working-card.tsx +130 -0
  91. package/template/src/components/works/project-details-view.tsx +464 -0
  92. package/template/src/components/works/project-grid.tsx +81 -0
  93. package/template/src/fonts/BoliviaSignature-ZpWnz.ttf +0 -0
  94. package/template/src/fonts/Hendrigo.otf +0 -0
  95. package/template/src/lib/data.ts +61 -0
  96. package/template/src/lib/fonts.ts +14 -0
  97. package/template/src/lib/github.ts +15 -0
  98. package/template/src/lib/supabase.ts +11 -0
  99. package/template/src/lib/utils.ts +6 -0
  100. package/template/tailwind.config.ts +31 -0
  101. package/template/tsconfig.json +34 -0
@@ -0,0 +1,464 @@
1
+ "use client";
2
+
3
+ import { useState, useRef, useEffect } from "react";
4
+ import Link from "next/link";
5
+ import Image from "next/image";
6
+ import { motion, useScroll, useTransform, useMotionValueEvent, AnimatePresence, Variants } from "framer-motion";
7
+ import { ArrowLeft, ArrowUpRight, Play, Pause, Volume2, VolumeX } from "lucide-react";
8
+ import { FaGithub, FaTerminal } from "react-icons/fa6";
9
+ import ProjectContributors from "@/components/ui/project-contributors";
10
+ import { ChangelogOverlay } from "./changelog-overlay";
11
+ import { ImageZoomOverlay } from "@/components/ui/image-zoom-overlay";
12
+
13
+ interface Contributor {
14
+ name: string;
15
+ avatar_url: string;
16
+ role: string;
17
+ social_url?: string;
18
+ }
19
+
20
+ interface ProjectDetailsViewProps {
21
+ project: any;
22
+ contributors?: Contributor[];
23
+ }
24
+
25
+ function CustomVideoPlayer({ videoUrl, posterUrl }: { videoUrl: string, posterUrl: string }) {
26
+ const videoRef = useRef<HTMLVideoElement>(null);
27
+ const [isPlaying, setIsPlaying] = useState(false);
28
+ const [isMuted, setIsMuted] = useState(false); // Default unmuted since it's user initiated? Or default muted? Let's default unmuted but handle autoplay policies if needed. Actually, for manual play, unmuted is fine.
29
+ const [progress, setProgress] = useState(0);
30
+ const [isHovered, setIsHovered] = useState(false);
31
+
32
+ const togglePlay = () => {
33
+ if (!videoRef.current) return;
34
+ if (isPlaying) {
35
+ videoRef.current.pause();
36
+ } else {
37
+ videoRef.current.play();
38
+ }
39
+ setIsPlaying(!isPlaying);
40
+ };
41
+
42
+ const toggleMute = (e: React.MouseEvent) => {
43
+ e.stopPropagation();
44
+ if (!videoRef.current) return;
45
+ videoRef.current.muted = !isMuted;
46
+ setIsMuted(!isMuted);
47
+ };
48
+
49
+ const handleTimeUpdate = () => {
50
+ if (videoRef.current) {
51
+ const progress = (videoRef.current.currentTime / videoRef.current.duration) * 100;
52
+ setProgress(progress);
53
+ }
54
+ };
55
+
56
+ const handleSeek = (e: React.MouseEvent<HTMLDivElement>) => {
57
+ e.stopPropagation();
58
+ if (!videoRef.current) return;
59
+ const progressBar = e.currentTarget;
60
+ const rect = progressBar.getBoundingClientRect();
61
+ const x = e.clientX - rect.left;
62
+ const clickedValue = (x / rect.width) * videoRef.current.duration;
63
+ videoRef.current.currentTime = clickedValue;
64
+ };
65
+
66
+ // Auto-hide controls when playing and not hovered
67
+ const showControls = !isPlaying || isHovered;
68
+
69
+ return (
70
+ <div
71
+ className="relative w-full h-full group cursor-pointer"
72
+ onMouseEnter={() => setIsHovered(true)}
73
+ onMouseLeave={() => setIsHovered(false)}
74
+ onClick={togglePlay}
75
+ >
76
+ <video
77
+ ref={videoRef}
78
+ src={videoUrl}
79
+ poster={posterUrl}
80
+ className="w-full h-full object-cover"
81
+ onTimeUpdate={handleTimeUpdate}
82
+ onEnded={() => setIsPlaying(false)}
83
+ playsInline
84
+ />
85
+
86
+ {/* Overlay Gradient for contrast */}
87
+ <div className={`absolute inset-0 bg-black/20 transition-opacity duration-300 ${showControls ? 'opacity-100' : 'opacity-0'}`} />
88
+
89
+ {/* Center Play Button (Only show when paused or hovered? User said minimal. Usually big play button hides when playing) */}
90
+ <div className={`absolute inset-0 flex items-center justify-center transition-all duration-300 ${!isPlaying ? 'opacity-100 scale-100' : 'opacity-0 scale-90 pointer-events-none'}`}>
91
+ <div className="w-16 h-16 md:w-20 md:h-20 rounded-full bg-white/10 backdrop-blur-md border border-white/20 flex items-center justify-center shadow-2xl transition-transform duration-300 group-hover:scale-110">
92
+ <Play className="w-6 h-6 md:w-8 md:h-8 text-white fill-white translate-x-0.5" />
93
+ </div>
94
+ </div>
95
+
96
+ {/* Bottom Controls Bar */}
97
+ <div className={`absolute bottom-0 inset-x-0 p-4 md:p-6 bg-gradient-to-t from-black/80 via-black/40 to-transparent transition-all duration-300 ${showControls ? 'translate-y-0 opacity-100' : 'translate-y-4 opacity-0 pointer-events-none'}`}>
98
+ <div className="flex items-center gap-4">
99
+
100
+ {/* Tiny Play/Pause for bottom bar */}
101
+ <button onClick={(e) => { e.stopPropagation(); togglePlay(); }} className="text-white/80 hover:text-white transition-colors">
102
+ {isPlaying ? <Pause className="w-5 h-5 fill-current" /> : <Play className="w-5 h-5 fill-current" />}
103
+ </button>
104
+
105
+ {/* Progress Bar */}
106
+ <div
107
+ className="flex-1 h-1 bg-white/20 rounded-full overflow-hidden cursor-pointer hover:h-1.5 transition-all"
108
+ onClick={handleSeek}
109
+ >
110
+ <div
111
+ className="h-full bg-blue-400/80 rounded-full relative"
112
+ style={{ width: `${progress}%` }}
113
+ >
114
+ <div className="absolute right-0 top-1/2 -translate-y-1/2 w-2 h-2 bg-blue-200 rounded-full shadow-[0_0_10px_rgba(59,130,246,0.5)] opacity-0 group-hover:opacity-100" />
115
+ </div>
116
+ </div>
117
+
118
+ {/* Mute Toggle */}
119
+ <button onClick={toggleMute} className="text-white/80 hover:text-white transition-colors">
120
+ {isMuted ? <VolumeX className="w-5 h-5" /> : <Volume2 className="w-5 h-5" />}
121
+ </button>
122
+ </div>
123
+ </div>
124
+ </div>
125
+ );
126
+ }
127
+
128
+ export function ProjectDetailsView({ project, contributors }: ProjectDetailsViewProps) {
129
+ const { scrollY } = useScroll();
130
+ const [showBottomNav, setShowBottomNav] = useState(false);
131
+ const [showChangelog, setShowChangelog] = useState(false);
132
+ const [zoomImage, setZoomImage] = useState<string | null>(null);
133
+ const mediaRef = useRef<HTMLDivElement>(null);
134
+
135
+ // Show mobile bottom nav only after scrolling past the hero
136
+ useMotionValueEvent(scrollY, "change", (latest) => {
137
+ if (latest > 500) {
138
+ setShowBottomNav(true);
139
+ } else {
140
+ setShowBottomNav(false);
141
+ }
142
+ });
143
+
144
+ const fadeInUp: Variants = {
145
+ hidden: { opacity: 0, y: 30 },
146
+ visible: { opacity: 1, y: 0, transition: { duration: 1, ease: [0.22, 1, 0.36, 1] } }
147
+ };
148
+
149
+ const staggerContainer: Variants = {
150
+ hidden: { opacity: 0 },
151
+ visible: {
152
+ opacity: 1,
153
+ transition: {
154
+ staggerChildren: 0.15,
155
+ delayChildren: 0.2
156
+ }
157
+ }
158
+ };
159
+
160
+ const TextBlock = ({ text }: { text: string }) => {
161
+ if (!text) return null;
162
+ return (
163
+ <div className="space-y-6 text-lg md:text-xl text-[#c0c0c0] leading-relaxed font-light break-words min-w-0">
164
+ {text.split('\n\n').map((paragraph, i) => (
165
+ <p key={i}>{paragraph}</p>
166
+ ))}
167
+ </div>
168
+ );
169
+ };
170
+
171
+ return (
172
+ <main className="relative min-h-screen bg-transparent text-[#e5e5e5] selection:bg-emerald-500/20 selection:text-emerald-100 pb-40 overflow-x-hidden">
173
+
174
+ {/* --- Subtle Ambient Glow (Luxury/Fog) - Updated to Green Tones --- */}
175
+ <div className="fixed inset-0 z-0 pointer-events-none overflow-hidden">
176
+ <div className="absolute -top-[20%] -left-[10%] w-[80vw] h-[80vw] bg-emerald-900/[0.03] rounded-full blur-[150px] mix-blend-screen opacity-40" />
177
+ <div className="absolute top-[30%] -right-[10%] w-[60vw] h-[60vw] bg-teal-900/[0.02] rounded-full blur-[120px] mix-blend-screen opacity-30" />
178
+ </div>
179
+
180
+ {/* --- 1. Top Navigation (Fixed) --- */}
181
+ <motion.nav
182
+ className="fixed top-8 left-6 md:left-10 z-[60] pointer-events-auto"
183
+ initial={{ opacity: 0, y: -20 }}
184
+ animate={{ opacity: 1, y: 0 }}
185
+ transition={{ duration: 0.8, delay: 0.5 }}
186
+ >
187
+ <Link
188
+ href="/works"
189
+ className="group inline-flex items-center gap-3 px-5 py-2.5 rounded-full bg-black/40 hover:bg-black/60 border border-white/10 backdrop-blur-md transition-all duration-500 hover:border-white/20 shadow-[0_0_20px_rgba(0,0,0,0.3)]"
190
+ >
191
+ <ArrowLeft className="w-4 h-4 text-white/60 group-hover:-translate-x-1 transition-transform duration-300" />
192
+ <span className="text-sm font-medium text-white/80 tracking-wide group-hover:text-white transition-colors">Back to Works</span>
193
+ </Link>
194
+ </motion.nav>
195
+
196
+ <article className="relative z-10 max-w-[1400px] mx-auto pt-32 md:pt-48 px-6 md:px-12 lg:px-20">
197
+
198
+ {/* --- 2. Hero Section --- */}
199
+ <motion.header
200
+ initial="hidden"
201
+ animate="visible"
202
+ variants={staggerContainer}
203
+ className="mb-24 md:mb-32 space-y-12"
204
+ >
205
+ {/* Meta Row */}
206
+ <motion.div variants={fadeInUp} className="flex flex-wrap items-center gap-3 text-xs font-medium tracking-[0.2em] uppercase text-white/40">
207
+ <span className="px-3 py-1 rounded-full bg-blue-500/10 border border-blue-500/20 text-blue-300 shadow-[0_0_15px_rgba(59,130,246,0.1)]">
208
+ {project.project_type === "Client" ? "Client Work" : "Personal"}
209
+ </span>
210
+ <span className="opacity-30">/</span>
211
+ <span>{new Date(project.created_at).getFullYear()}</span>
212
+ {project.client_name && (
213
+ <>
214
+ <span className="opacity-30">/</span>
215
+ <span>{project.client_name}</span>
216
+ </>
217
+ )}
218
+ </motion.div>
219
+
220
+ {/* Title */}
221
+ <motion.h1 variants={fadeInUp} className="text-5xl md:text-7xl lg:text-8xl xl:text-9xl font-medium tracking-tighter text-white leading-[0.95] max-w-6xl">
222
+ {project.title}
223
+ </motion.h1>
224
+
225
+ <motion.div variants={fadeInUp} className="w-full h-px bg-gradient-to-r from-blue-500/30 via-white/5 to-transparent mt-16 mb-16" />
226
+
227
+ <motion.div variants={fadeInUp} className="grid grid-cols-1 lg:grid-cols-12 gap-12 lg:gap-24">
228
+ <div className="lg:col-span-7">
229
+ <p className="text-xl md:text-3xl text-[#d4d4d4] leading-relaxed font-light max-w-prose">
230
+ {project.description}
231
+ </p>
232
+ </div>
233
+ <div className="lg:col-span-5 flex flex-wrap content-start gap-2">
234
+ {project.tech_stack?.map((tech: string) => (
235
+ <span key={tech} className="px-4 py-2 rounded-full text-xs font-medium tracking-wider uppercase text-blue-100/60 bg-blue-900/10 border border-blue-500/10 backdrop-blur-sm">
236
+ {tech}
237
+ </span>
238
+ ))}
239
+ </div>
240
+ </motion.div>
241
+ </motion.header>
242
+
243
+ {/* --- 3. Primary Media (Cinematic & Clean) --- */}
244
+ <motion.section
245
+ ref={mediaRef}
246
+ initial={{ opacity: 0, scale: 0.98, y: 20 }}
247
+ whileInView={{ opacity: 1, scale: 1, y: 0 }}
248
+ viewport={{ once: true, margin: "-10%" }}
249
+ transition={{ duration: 1.2, ease: [0.22, 1, 0.36, 1] }}
250
+ className="mb-32 md:mb-48 w-full group relative"
251
+ >
252
+ {/* Subtle Atmospheric Glow */}
253
+ <div className="absolute inset-x-0 -bottom-20 h-40 bg-blue-500/[0.03] blur-[80px] rounded-full pointer-events-none" />
254
+
255
+ <div className="relative w-full overflow-hidden rounded-3xl bg-[#0a0a0a] shadow-2xl ring-1 ring-white/10">
256
+ {project.media_mode === 'video_first' && project.video_url ? (
257
+ <div className="relative w-full aspect-video">
258
+ <CustomVideoPlayer videoUrl={project.video_url} posterUrl={project.image_url} />
259
+ </div>
260
+ ) : (
261
+ <div className="relative w-full">
262
+ {/* eslint-disable-next-line @next/next/no-img-element */}
263
+ <img
264
+ src={project.image_url}
265
+ alt={project.title}
266
+ onClick={() => setZoomImage(project.image_url)}
267
+ className="w-full h-auto object-cover transition-transform duration-[2s] ease-out group-hover:scale-[1.01] cursor-zoom-in"
268
+ />
269
+ </div>
270
+ )}
271
+ </div>
272
+ </motion.section>
273
+
274
+ {/* --- 4. Content Grid --- */}
275
+ <div className="grid grid-cols-1 lg:grid-cols-12 gap-12 lg:gap-24 mb-32 relative">
276
+
277
+ {/* LEFT: Deep Dive */}
278
+ <div className="lg:col-span-7 space-y-24 md:space-y-32">
279
+ {[
280
+ { title: 'Overview', number: '01', content: project.overview },
281
+ { title: 'The Challenge', number: '02', content: project.problem_statement },
282
+ { title: 'Approach', number: '03', content: project.approach }
283
+ ].map((section, idx) => (
284
+ section.content && (
285
+ <motion.section
286
+ key={idx}
287
+ initial={{ opacity: 0, y: 20 }}
288
+ whileInView={{ opacity: 1, y: 0 }}
289
+ viewport={{ once: true, margin: "-10%" }}
290
+ transition={{ duration: 0.8 }}
291
+ >
292
+ <div className="flex items-center gap-4 mb-8 opacity-80">
293
+ <span className="text-xs font-mono text-blue-300">{section.number}</span>
294
+ <div className="h-px w-8 bg-blue-500/30" />
295
+ <h3 className="text-xs font-bold text-blue-200 uppercase tracking-[0.2em]">{section.title}</h3>
296
+ </div>
297
+ <TextBlock text={section.content} />
298
+ </motion.section>
299
+ )
300
+ ))}
301
+
302
+ {project.outcome && (
303
+ <motion.section
304
+ initial={{ opacity: 0, y: 20 }}
305
+ whileInView={{ opacity: 1, y: 0 }}
306
+ viewport={{ once: true }}
307
+ className="relative py-12 border-t border-white/10"
308
+ >
309
+ <h3 className="text-xs font-bold text-blue-200 uppercase tracking-[0.2em] mb-8">Outcome</h3>
310
+ <p className="text-xl md:text-2xl text-white/90 font-light italic leading-relaxed">
311
+ "{project.outcome}"
312
+ </p>
313
+ </motion.section>
314
+ )}
315
+ </div>
316
+
317
+ {/* RIGHT: Supporting Info (Sticky) */}
318
+ <aside className="lg:col-span-4 lg:col-start-9 h-fit lg:sticky lg:top-32 space-y-16">
319
+
320
+ {/* Links */}
321
+ <motion.div
322
+ initial={{ opacity: 0 }}
323
+ whileInView={{ opacity: 1 }}
324
+ viewport={{ once: true }}
325
+ transition={{ delay: 0.2 }}
326
+ className="flex flex-col gap-4"
327
+ >
328
+ {(project.external_link_url || (project.project_url && project.project_url.startsWith('http'))) && (
329
+ <a
330
+ href={project.external_link_url || project.project_url}
331
+ target="_blank"
332
+ rel="noopener noreferrer"
333
+ className="group w-full py-5 px-6 border-b border-white/20 hover:border-white transition-colors flex items-center justify-between"
334
+ >
335
+ <span className="text-lg font-medium tracking-tight">Visit Live Site</span>
336
+ <ArrowUpRight className="w-5 h-5 opacity-40 group-hover:opacity-100 transition-all duration-500 group-hover:-translate-y-1 group-hover:translate-x-1" />
337
+ </a>
338
+ )}
339
+ {project.github_url && (
340
+ <>
341
+ <a
342
+ href={project.github_url}
343
+ target="_blank"
344
+ rel="noopener noreferrer"
345
+ className="group w-full py-5 px-6 border-b border-white/10 hover:border-white/40 transition-colors flex items-center justify-between text-white/50 hover:text-white"
346
+ >
347
+ <span className="text-sm font-medium tracking-wide">Source Code</span>
348
+ <FaGithub className="w-4 h-4 opacity-40 group-hover:opacity-100 transition-all duration-300" />
349
+ </a>
350
+
351
+ <button
352
+ onClick={() => setShowChangelog(true)}
353
+ className="group w-full py-5 px-6 border-b border-white/10 hover:border-emerald-500/40 transition-colors flex items-center justify-between text-white/50 hover:text-emerald-400"
354
+ >
355
+ <span className="text-sm font-medium tracking-wide">View Change Log</span>
356
+ <FaTerminal className="w-4 h-4 opacity-40 group-hover:opacity-100 group-hover:rotate-[-10deg] transition-all" />
357
+ </button>
358
+ </>
359
+ )}
360
+ </motion.div>
361
+
362
+ {/* Features */}
363
+ {project.features && (
364
+ <motion.div
365
+ initial={{ opacity: 0 }}
366
+ whileInView={{ opacity: 1 }}
367
+ viewport={{ once: true }}
368
+ transition={{ delay: 0.3 }}
369
+ className="border-t border-white/5"
370
+ >
371
+ <h4 className="px-6 py-4 text-xs font-bold text-blue-200/80 uppercase tracking-[0.2em] border-b border-white/5">Highlights</h4>
372
+ <ul className="px-6 py-6 space-y-3">
373
+ {project.features.split('\n').filter(Boolean).map((feature: string, i: number) => (
374
+ <li key={i} className="flex gap-3 text-sm text-neutral-400 leading-relaxed font-light">
375
+ <span className="text-blue-400/60 mt-1.5">•</span>
376
+ <span>{feature.replace(/^•\s*/, '')}</span>
377
+ </li>
378
+ ))}
379
+ </ul>
380
+ </motion.div>
381
+ )}
382
+
383
+ {/* Contributors */}
384
+ {contributors && contributors.length > 0 && (
385
+ <motion.div
386
+ initial={{ opacity: 0 }}
387
+ whileInView={{ opacity: 1 }}
388
+ viewport={{ once: true }}
389
+ transition={{ delay: 0.4 }}
390
+ className="border-t border-white/5"
391
+ >
392
+ <h4 className="px-6 py-4 text-xs font-bold text-blue-200/80 uppercase tracking-[0.2em] border-b border-white/5">Team</h4>
393
+ <div className="px-6 py-6">
394
+ <ProjectContributors
395
+ contributors={contributors.map((c: any) => ({
396
+ name: c.name,
397
+ avatar_url: c.avatar_url,
398
+ role: c.role,
399
+ social_url: c.social_url,
400
+ fallback: c.name.substring(0, 2).toUpperCase()
401
+ }))}
402
+ />
403
+ </div>
404
+ </motion.div>
405
+ )}
406
+ </aside>
407
+ </div>
408
+
409
+ {/* --- 5. Gallery --- */}
410
+ {project.gallery_images && project.gallery_images.length > 0 && (
411
+ <section className="mb-0">
412
+ <motion.div
413
+ initial={{ opacity: 0 }}
414
+ whileInView={{ opacity: 1 }}
415
+ viewport={{ once: true }}
416
+ className="mb-12 border-b border-white/10 pb-6 flex justify-between items-end"
417
+ >
418
+ <h2 className="text-2xl font-light text-white">Project Gallery</h2>
419
+ <span className="text-xs font-mono text-white/30">0{project.gallery_images.length}</span>
420
+ </motion.div>
421
+
422
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4 md:gap-8 mb-24">
423
+ {project.gallery_images.map((img: string, idx: number) => (
424
+ <motion.div
425
+ key={idx}
426
+ initial={{ opacity: 0, y: 30 }}
427
+ whileInView={{ opacity: 1, y: 0 }}
428
+ viewport={{ once: true }}
429
+ transition={{ duration: 0.8, delay: idx * 0.1 }}
430
+ className="relative bg-[#111] aspect-[4/3] group overflow-hidden rounded-2xl md:rounded-3xl shadow-lg ring-1 ring-white/5"
431
+ >
432
+ <Image
433
+ src={img}
434
+ alt={`Gallery ${idx}`}
435
+ fill
436
+ sizes="(max-width: 768px) 100vw, 50vw"
437
+ onClick={() => setZoomImage(img)}
438
+ className="object-cover opacity-80 group-hover:opacity-100 transition-opacity duration-700 ease-out cursor-zoom-in"
439
+ />
440
+ </motion.div>
441
+ ))}
442
+ </div>
443
+ </section>
444
+ )}
445
+
446
+ </article>
447
+
448
+ <ChangelogOverlay
449
+ isOpen={showChangelog}
450
+ onClose={() => setShowChangelog(false)}
451
+ githubUrl={project.github_url}
452
+ projectTitle={project.title}
453
+ />
454
+
455
+ <ImageZoomOverlay
456
+ isOpen={!!zoomImage}
457
+ onClose={() => setZoomImage(null)}
458
+ imageUrl={zoomImage || ""}
459
+ altText={project.title}
460
+ />
461
+
462
+ </main>
463
+ );
464
+ }
@@ -0,0 +1,81 @@
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
+ export function ProjectGrid({ projects }: { projects: Project[] }) {
10
+ if (!projects.length) return null;
11
+
12
+ return (
13
+ <div className="w-full flex justify-center">
14
+ <div className="w-full max-w-[1200px]">
15
+ <div className="flex items-center gap-3 mb-8">
16
+ <span className="text-xs font-bold uppercase tracking-widest text-white/40">All Projects</span>
17
+ <div className="h-px flex-1 bg-white/5" />
18
+ </div>
19
+
20
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
21
+ {projects.map((project, index) => (
22
+ <motion.div
23
+ key={project.id}
24
+ initial={{ opacity: 0, y: 20 }}
25
+ whileInView={{ opacity: 1, y: 0 }}
26
+ viewport={{ once: true }}
27
+ transition={{ delay: index * 0.1 }}
28
+ >
29
+ <div className="group relative block h-full">
30
+ <Link href={`/works/${project.slug || '#'}`} className="block h-full">
31
+ <div className="h-full bg-[#111] border border-white/5 rounded-[32px] overflow-hidden flex flex-col group-hover:border-white/10 hover:shadow-2xl hover:shadow-white/5 transition-all duration-300">
32
+
33
+ {/* Image */}
34
+ <div className="aspect-[4/3] w-full relative bg-black/50 overflow-hidden">
35
+ <Image
36
+ src={project.image_url}
37
+ alt={project.title}
38
+ fill
39
+ className="object-cover transition-transform duration-500 group-hover:scale-110 opacity-80 group-hover:opacity-100"
40
+ />
41
+ <div className="absolute inset-0 bg-gradient-to-t from-[#111] via-transparent to-transparent opacity-80" />
42
+
43
+ {/* Hover Overlay Icon */}
44
+ <div className="absolute top-4 right-4 p-2 rounded-full bg-white/10 backdrop-blur-md opacity-0 transform translate-y-2 group-hover:opacity-100 group-hover:translate-y-0 transition-all duration-300">
45
+ <ArrowUpRight className="w-4 h-4 text-white" />
46
+ </div>
47
+ </div>
48
+
49
+ {/* Content */}
50
+ <div className="p-6 flex flex-col flex-1">
51
+ <h3 className="text-xl font-bold text-white mb-2 group-hover:text-blue-200/90 transition-colors">
52
+ {project.title}
53
+ </h3>
54
+ <p className="text-white/40 text-sm leading-relaxed mb-6 flex-1 line-clamp-2">
55
+ {project.description}
56
+ </p>
57
+
58
+ {/* Footer / Tech */}
59
+ <div className="flex items-center justify-between mt-auto">
60
+ <div className="flex flex-wrap gap-2">
61
+ {project.tech_stack.slice(0, 3).map((t) => (
62
+ <span key={t} className="text-[10px] uppercase font-medium tracking-wider text-white/30 px-2 py-1 bg-white/5 rounded border border-white/5">
63
+ {t}
64
+ </span>
65
+ ))}
66
+ {project.tech_stack.length > 3 && (
67
+ <span className="text-[10px] text-white/20">+{project.tech_stack.length - 3}</span>
68
+ )}
69
+ </div>
70
+ </div>
71
+ </div>
72
+ </div>
73
+ </Link>
74
+ </div>
75
+ </motion.div>
76
+ ))}
77
+ </div>
78
+ </div>
79
+ </div>
80
+ );
81
+ }
Binary file
@@ -0,0 +1,61 @@
1
+
2
+ export interface Project {
3
+ id: string;
4
+ title: string;
5
+ description: string;
6
+ techStack: string[];
7
+ image: string;
8
+ link: string;
9
+ }
10
+
11
+ export const USER_DATA = {
12
+ name: "Abhishek Singh",
13
+ role: "Product Engineer",
14
+ bio: "I build high-performance web applications with a focus on interaction design.",
15
+ avatar: "/avatar.png",
16
+ socials: {
17
+ github: "https://github.com/AbhishekS04",
18
+ linkedin: "https://linkedin.com/in/AbhishekS04",
19
+ twitter: "https://twitter.com/AbhishekS04",
20
+ }
21
+ };
22
+
23
+ export const FEATURED_PROJECTS: Project[] = [
24
+ {
25
+ id: "1",
26
+ title: "Bento Portfolio Template",
27
+ description: "A premium portfolio template featuring a responsive bento grid and smooth framer-motion animations.",
28
+ techStack: ["Next.js", "Framer Motion", "Tailwind CSS"],
29
+ image: "https://images.unsplash.com/photo-1550751827-4bd374c3f58b?q=80&w=2670&auto=format&fit=crop",
30
+ link: "#",
31
+ },
32
+ {
33
+ id: "2",
34
+ title: "Dynamic UI Components",
35
+ description: "A collection of reusable, highly interactive components for modern web apps.",
36
+ techStack: ["React", "TypeScript", "Lucide"],
37
+ image: "https://images.unsplash.com/photo-1611974765270-ca12586343bb?q=80&w=2576&auto=format&fit=crop",
38
+ link: "#",
39
+ },
40
+ {
41
+ id: "3",
42
+ title: "AI Project Scaffolder",
43
+ description: "CLI tool for generating optimized project structures with pre-configured tools.",
44
+ techStack: ["Node.js", "CLI", "Shell"],
45
+ image: "https://images.unsplash.com/photo-1677442136019-21780ecad995?q=80&w=2664&auto=format&fit=crop",
46
+ link: "#",
47
+ },
48
+ ];
49
+
50
+ export const SKILLS = [
51
+ "Product Engineering",
52
+ "UI Systems & Animations",
53
+ "Next.js & React Architecture",
54
+ "Tailwind CSS Expert",
55
+ "Framer Motion Specialist",
56
+ ];
57
+
58
+ export const WHY_I_BUILD = {
59
+ primary: "I enjoy turning complex ideas into clean, usable systems.",
60
+ secondary: "I focus on clarity, performance, and scalability rather than visual noise.",
61
+ };
@@ -0,0 +1,14 @@
1
+ // ... existing imports
2
+ import localFont from "next/font/local";
3
+ import { Sacramento } from "next/font/google";
4
+
5
+ export const sacramento = Sacramento({
6
+ subsets: ["latin"],
7
+ weight: "400",
8
+ });
9
+
10
+ export const bolivia = localFont({
11
+ src: "../fonts/BoliviaSignature-ZpWnz.ttf",
12
+ variable: "--font-bolivia",
13
+ display: "swap",
14
+ });
@@ -0,0 +1,15 @@
1
+ // Mock GitHub service for template
2
+ export async function getGithubStats() {
3
+ return {
4
+ followers: 100,
5
+ repos: 20,
6
+ stars: 50,
7
+ };
8
+ }
9
+
10
+ export async function getLatestRepos() {
11
+ return [
12
+ { name: "Awesome Project", description: "Bento Layout with Next.js", url: "#" },
13
+ { name: "Portfolio Template", description: "Premium Animations", url: "#" },
14
+ ];
15
+ }
@@ -0,0 +1,11 @@
1
+ // Mock Supabase client for template
2
+ export const supabase = {
3
+ from: () => ({
4
+ select: () => ({
5
+ order: () => ({
6
+ data: [],
7
+ error: null,
8
+ }),
9
+ }),
10
+ }),
11
+ };
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from "clsx"
2
+ import { twMerge } from "tailwind-merge"
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }