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,569 @@
1
+ "use client"
2
+
3
+ import { useState, useRef, useEffect, useLayoutEffect, useCallback, forwardRef } from "react"
4
+ import { cn } from "@/lib/utils"
5
+ import { X, ExternalLink, ChevronLeft, ChevronRight } from "lucide-react"
6
+
7
+ interface Project {
8
+ id: string
9
+ image: string
10
+ title: string
11
+ }
12
+
13
+ interface AnimatedFolderProps {
14
+ title: string
15
+ projects: Project[]
16
+ className?: string
17
+ }
18
+
19
+ export function AnimatedFolder({ title, projects, className }: AnimatedFolderProps) {
20
+ const [isHovered, setIsHovered] = useState(false)
21
+ const [selectedIndex, setSelectedIndex] = useState<number | null>(null)
22
+ const [sourceRect, setSourceRect] = useState<DOMRect | null>(null)
23
+ const [hiddenCardId, setHiddenCardId] = useState<string | null>(null)
24
+ const cardRefs = useRef<(HTMLDivElement | null)[]>([])
25
+
26
+ const handleProjectClick = (project: Project, index: number) => {
27
+ const cardEl = cardRefs.current[index]
28
+ if (cardEl) {
29
+ setSourceRect(cardEl.getBoundingClientRect())
30
+ }
31
+ setSelectedIndex(index)
32
+ setHiddenCardId(project.id)
33
+ }
34
+
35
+ const handleCloseLightbox = () => {
36
+ setSelectedIndex(null)
37
+ setSourceRect(null)
38
+ }
39
+
40
+ const handleCloseComplete = () => {
41
+ setHiddenCardId(null)
42
+ }
43
+
44
+ const handleNavigate = (newIndex: number) => {
45
+ setSelectedIndex(newIndex)
46
+ setHiddenCardId(projects[newIndex]?.id || null)
47
+ }
48
+
49
+ return (
50
+ <>
51
+ <div
52
+ className={cn(
53
+ "relative flex flex-col items-center justify-center",
54
+ "p-8 rounded-2xl cursor-pointer",
55
+ "bg-card border border-border",
56
+ "transition-all duration-500 ease-out",
57
+ "hover:shadow-2xl hover:shadow-accent/10",
58
+ "hover:border-accent/30",
59
+ "group",
60
+ className,
61
+ )}
62
+ style={{
63
+ minWidth: "100%",
64
+ minHeight: "auto",
65
+ padding: "1rem",
66
+ perspective: "1000px",
67
+ }}
68
+ onMouseEnter={() => setIsHovered(true)}
69
+ onMouseLeave={() => setIsHovered(false)}
70
+ >
71
+ {/* Subtle background glow on hover */}
72
+ <div
73
+ className="absolute inset-0 rounded-2xl transition-opacity duration-500"
74
+ style={{
75
+ background: "radial-gradient(circle at 50% 70%, var(--accent) 0%, transparent 70%)",
76
+ opacity: isHovered ? 0.08 : 0,
77
+ }}
78
+ />
79
+
80
+ <div className="relative flex items-center justify-center mb-4" style={{ height: "160px", width: "200px" }}>
81
+ {/* Folder back layer - z-index 10 */}
82
+ <div
83
+ className="absolute w-32 h-24 bg-folder-back rounded-lg shadow-md"
84
+ style={{
85
+ transformOrigin: "bottom center",
86
+ transform: isHovered ? "rotateX(-15deg)" : "rotateX(0deg)",
87
+ transition: "transform 500ms cubic-bezier(0.34, 1.56, 0.64, 1)",
88
+ zIndex: 10,
89
+ }}
90
+ />
91
+
92
+ {/* Folder tab - z-index 10 */}
93
+ <div
94
+ className="absolute w-12 h-4 bg-folder-tab rounded-t-md"
95
+ style={{
96
+ top: "calc(50% - 48px - 12px)",
97
+ left: "calc(50% - 64px + 16px)",
98
+ transformOrigin: "bottom center",
99
+ transform: isHovered ? "rotateX(-25deg) translateY(-2px)" : "rotateX(0deg)",
100
+ transition: "transform 500ms cubic-bezier(0.34, 1.56, 0.64, 1)",
101
+ zIndex: 10,
102
+ }}
103
+ />
104
+
105
+ {/* Project cards - z-index 20, between back and front */}
106
+ <div
107
+ className="absolute"
108
+ style={{
109
+ top: "50%",
110
+ left: "50%",
111
+ transform: "translate(-50%, -50%)",
112
+ zIndex: 20,
113
+ }}
114
+ >
115
+ {projects.slice(0, 3).map((project, index) => (
116
+ <ProjectCard
117
+ key={project.id}
118
+ ref={(el) => {
119
+ cardRefs.current[index] = el
120
+ }}
121
+ image={project.image}
122
+ title={project.title}
123
+ delay={index * 80}
124
+ isVisible={isHovered}
125
+ index={index}
126
+ onClick={() => handleProjectClick(project, index)}
127
+ isSelected={hiddenCardId === project.id}
128
+ />
129
+ ))}
130
+ </div>
131
+
132
+ {/* Folder front layer - z-index 30 */}
133
+ <div
134
+ className="absolute w-32 h-24 bg-folder-front rounded-lg shadow-lg"
135
+ style={{
136
+ top: "calc(50% - 48px + 4px)",
137
+ transformOrigin: "bottom center",
138
+ transform: isHovered ? "rotateX(25deg) translateY(8px)" : "rotateX(0deg)",
139
+ transition: "transform 500ms cubic-bezier(0.34, 1.56, 0.64, 1)",
140
+ zIndex: 30,
141
+ }}
142
+ />
143
+
144
+ {/* Folder shine effect - z-index 31 */}
145
+ <div
146
+ className="absolute w-32 h-24 rounded-lg overflow-hidden pointer-events-none"
147
+ style={{
148
+ top: "calc(50% - 48px + 4px)",
149
+ background: "linear-gradient(135deg, rgba(255,255,255,0.3) 0%, transparent 50%)",
150
+ transformOrigin: "bottom center",
151
+ transform: isHovered ? "rotateX(25deg) translateY(8px)" : "rotateX(0deg)",
152
+ transition: "transform 500ms cubic-bezier(0.34, 1.56, 0.64, 1)",
153
+ zIndex: 31,
154
+ }}
155
+ />
156
+ </div>
157
+ </div>
158
+
159
+ <ImageLightbox
160
+ projects={projects.slice(0, 3)}
161
+ currentIndex={selectedIndex ?? 0}
162
+ isOpen={selectedIndex !== null}
163
+ onClose={handleCloseLightbox}
164
+ sourceRect={sourceRect}
165
+ onCloseComplete={handleCloseComplete}
166
+ onNavigate={handleNavigate}
167
+ />
168
+ </>
169
+ )
170
+ }
171
+
172
+ interface ImageLightboxProps {
173
+ projects: Project[]
174
+ currentIndex: number
175
+ isOpen: boolean
176
+ onClose: () => void
177
+ sourceRect: DOMRect | null
178
+ onCloseComplete?: () => void
179
+ onNavigate: (index: number) => void
180
+ }
181
+
182
+ export function ImageLightbox({
183
+ projects,
184
+ currentIndex,
185
+ isOpen,
186
+ onClose,
187
+ sourceRect,
188
+ onCloseComplete,
189
+ onNavigate,
190
+ }: ImageLightboxProps) {
191
+ const [animationPhase, setAnimationPhase] = useState<"initial" | "animating" | "complete">("initial")
192
+ const [isClosing, setIsClosing] = useState(false)
193
+ const [shouldRender, setShouldRender] = useState(false)
194
+ const [internalIndex, setInternalIndex] = useState(currentIndex)
195
+ const [prevIndex, setPrevIndex] = useState(currentIndex)
196
+ const [isSliding, setIsSliding] = useState(false)
197
+ const [slideDirection, setSlideDirection] = useState<"left" | "right">("right")
198
+ const containerRef = useRef<HTMLDivElement>(null)
199
+
200
+ const totalProjects = projects.length
201
+ const hasNext = internalIndex < totalProjects - 1
202
+ const hasPrev = internalIndex > 0
203
+
204
+ const currentProject = projects[internalIndex]
205
+
206
+ useEffect(() => {
207
+ if (isOpen && currentIndex !== internalIndex && !isSliding) {
208
+ const direction = currentIndex > internalIndex ? "left" : "right"
209
+ setSlideDirection(direction)
210
+ setPrevIndex(internalIndex)
211
+ setIsSliding(true)
212
+
213
+ const timer = setTimeout(() => {
214
+ setInternalIndex(currentIndex)
215
+ setIsSliding(false)
216
+ }, 400)
217
+
218
+ return () => clearTimeout(timer)
219
+ }
220
+ }, [currentIndex, isOpen])
221
+
222
+ useEffect(() => {
223
+ if (isOpen) {
224
+ setInternalIndex(currentIndex)
225
+ setPrevIndex(currentIndex)
226
+ setIsSliding(false)
227
+ }
228
+ }, [isOpen])
229
+
230
+ const navigateNext = useCallback(() => {
231
+ if (internalIndex >= totalProjects - 1 || isSliding) return
232
+ onNavigate(internalIndex + 1)
233
+ }, [internalIndex, totalProjects, isSliding, onNavigate])
234
+
235
+ const navigatePrev = useCallback(() => {
236
+ if (internalIndex <= 0 || isSliding) return
237
+ onNavigate(internalIndex - 1)
238
+ }, [internalIndex, isSliding, onNavigate])
239
+
240
+ const handleClose = useCallback(() => {
241
+ setIsClosing(true)
242
+ onClose()
243
+ setTimeout(() => {
244
+ setIsClosing(false)
245
+ setShouldRender(false)
246
+ setAnimationPhase("initial")
247
+ onCloseComplete?.()
248
+ }, 400)
249
+ }, [onClose, onCloseComplete])
250
+
251
+ useEffect(() => {
252
+ const handleKeyDown = (e: KeyboardEvent) => {
253
+ if (!isOpen) return
254
+ if (e.key === "Escape") handleClose()
255
+ if (e.key === "ArrowRight") navigateNext()
256
+ if (e.key === "ArrowLeft") navigatePrev()
257
+ }
258
+
259
+ window.addEventListener("keydown", handleKeyDown)
260
+ if (isOpen) {
261
+ document.body.style.overflow = "hidden"
262
+ }
263
+
264
+ return () => {
265
+ window.removeEventListener("keydown", handleKeyDown)
266
+ document.body.style.overflow = ""
267
+ }
268
+ }, [isOpen, handleClose, navigateNext, navigatePrev])
269
+
270
+ useLayoutEffect(() => {
271
+ if (isOpen && sourceRect) {
272
+ setShouldRender(true)
273
+ setAnimationPhase("initial")
274
+ setIsClosing(false)
275
+ requestAnimationFrame(() => {
276
+ requestAnimationFrame(() => {
277
+ setAnimationPhase("animating")
278
+ })
279
+ })
280
+ const timer = setTimeout(() => {
281
+ setAnimationPhase("complete")
282
+ }, 500)
283
+ return () => clearTimeout(timer)
284
+ }
285
+ }, [isOpen, sourceRect])
286
+
287
+ const handleDotClick = (idx: number) => {
288
+ if (isSliding || idx === internalIndex) return
289
+ onNavigate(idx)
290
+ }
291
+
292
+ if (!shouldRender || !currentProject) return null
293
+
294
+ const getInitialStyles = (): React.CSSProperties => {
295
+ if (!sourceRect) return {}
296
+
297
+ const viewportWidth = window.innerWidth
298
+ const viewportHeight = window.innerHeight
299
+ const targetWidth = Math.min(768, viewportWidth - 64)
300
+ const targetHeight = Math.min(viewportHeight * 0.85, 600)
301
+
302
+ const targetX = (viewportWidth - targetWidth) / 2
303
+ const targetY = (viewportHeight - targetHeight) / 2
304
+
305
+ const scaleX = sourceRect.width / targetWidth
306
+ const scaleY = sourceRect.height / targetHeight
307
+ const scale = Math.max(scaleX, scaleY)
308
+
309
+ const translateX = sourceRect.left + sourceRect.width / 2 - (targetX + targetWidth / 2)
310
+ const translateY = sourceRect.top + sourceRect.height / 2 - (targetY + targetHeight / 2)
311
+
312
+ return {
313
+ transform: `translate(${translateX}px, ${translateY}px) scale(${scale})`,
314
+ opacity: 1,
315
+ }
316
+ }
317
+
318
+ const getFinalStyles = (): React.CSSProperties => {
319
+ return {
320
+ transform: "translate(0, 0) scale(1)",
321
+ opacity: 1,
322
+ }
323
+ }
324
+
325
+ const currentStyles = animationPhase === "initial" && !isClosing ? getInitialStyles() : getFinalStyles()
326
+
327
+ return (
328
+ <div
329
+ className={cn("fixed inset-0 z-50 flex items-center justify-center p-4 md:p-8")}
330
+ onClick={handleClose}
331
+ style={{
332
+ opacity: isClosing ? 0 : 1,
333
+ transition: "opacity 400ms cubic-bezier(0.16, 1, 0.3, 1)",
334
+ }}
335
+ >
336
+ <div
337
+ className="absolute inset-0 bg-background/80 backdrop-blur-xl"
338
+ style={{
339
+ opacity: animationPhase === "initial" && !isClosing ? 0 : 1,
340
+ transition: "opacity 400ms cubic-bezier(0.16, 1, 0.3, 1)",
341
+ }}
342
+ />
343
+
344
+ {/* Close button */}
345
+ <button
346
+ onClick={(e) => {
347
+ e.stopPropagation()
348
+ handleClose()
349
+ }}
350
+ className={cn(
351
+ "absolute top-5 right-5 z-50",
352
+ "w-10 h-10 flex items-center justify-center",
353
+ "rounded-full bg-muted/50 backdrop-blur-md",
354
+ "border border-border",
355
+ "text-muted-foreground hover:text-foreground hover:bg-muted",
356
+ "transition-all duration-300 ease-out hover:scale-105 active:scale-95",
357
+ )}
358
+ style={{
359
+ opacity: animationPhase === "complete" && !isClosing ? 1 : 0,
360
+ transform: animationPhase === "complete" && !isClosing ? "translateY(0)" : "translateY(-10px)",
361
+ transition: "opacity 300ms ease-out, transform 300ms ease-out",
362
+ }}
363
+ >
364
+ <X className="w-4 h-4" strokeWidth={2.5} />
365
+ </button>
366
+
367
+ <button
368
+ onClick={(e) => {
369
+ e.stopPropagation()
370
+ navigatePrev()
371
+ }}
372
+ disabled={!hasPrev || isSliding}
373
+ className={cn(
374
+ "absolute left-4 md:left-8 z-50",
375
+ "w-12 h-12 flex items-center justify-center",
376
+ "rounded-full bg-muted/50 backdrop-blur-md",
377
+ "border border-border",
378
+ "text-muted-foreground hover:text-foreground hover:bg-muted",
379
+ "transition-all duration-300 ease-out hover:scale-110 active:scale-95",
380
+ "disabled:opacity-0 disabled:pointer-events-none",
381
+ )}
382
+ style={{
383
+ opacity: animationPhase === "complete" && !isClosing && hasPrev ? 1 : 0,
384
+ transform: animationPhase === "complete" && !isClosing ? "translateX(0)" : "translateX(-20px)",
385
+ transition: "opacity 300ms ease-out 150ms, transform 300ms ease-out 150ms",
386
+ }}
387
+ >
388
+ <ChevronLeft className="w-5 h-5" strokeWidth={2.5} />
389
+ </button>
390
+
391
+ <button
392
+ onClick={(e) => {
393
+ e.stopPropagation()
394
+ navigateNext()
395
+ }}
396
+ disabled={!hasNext || isSliding}
397
+ className={cn(
398
+ "absolute right-4 md:right-8 z-50",
399
+ "w-12 h-12 flex items-center justify-center",
400
+ "rounded-full bg-muted/50 backdrop-blur-md",
401
+ "border border-border",
402
+ "text-muted-foreground hover:text-foreground hover:bg-muted",
403
+ "transition-all duration-300 ease-out hover:scale-110 active:scale-95",
404
+ "disabled:opacity-0 disabled:pointer-events-none",
405
+ )}
406
+ style={{
407
+ opacity: animationPhase === "complete" && !isClosing && hasNext ? 1 : 0,
408
+ transform: animationPhase === "complete" && !isClosing ? "translateX(0)" : "translateX(20px)",
409
+ transition: "opacity 300ms ease-out 150ms, transform 300ms ease-out 150ms",
410
+ }}
411
+ >
412
+ <ChevronRight className="w-5 h-5" strokeWidth={2.5} />
413
+ </button>
414
+
415
+ <div
416
+ ref={containerRef}
417
+ className="relative z-10 w-full max-w-[95vw]"
418
+ onClick={(e) => e.stopPropagation()}
419
+ style={{
420
+ ...currentStyles,
421
+ transform: isClosing ? "translate(0, 0) scale(0.95)" : currentStyles.transform,
422
+ transition:
423
+ animationPhase === "initial" && !isClosing
424
+ ? "none"
425
+ : "transform 400ms cubic-bezier(0.16, 1, 0.3, 1), opacity 400ms ease-out",
426
+ transformOrigin: "center center",
427
+ }}
428
+ >
429
+ <div
430
+ className={cn("relative overflow-hidden", "rounded-2xl", "bg-card", "ring-1 ring-border", "shadow-2xl")}
431
+ style={{
432
+ borderRadius: animationPhase === "initial" && !isClosing ? "8px" : "16px",
433
+ transition: "border-radius 500ms cubic-bezier(0.16, 1, 0.3, 1)",
434
+ }}
435
+ >
436
+ <div className="relative overflow-hidden">
437
+ <div
438
+ className="flex transition-transform duration-400 ease-out"
439
+ style={{
440
+ transform: `translateX(-${internalIndex * 100}%)`,
441
+ transition: isSliding ? "transform 400ms cubic-bezier(0.32, 0.72, 0, 1)" : "none",
442
+ }}
443
+ >
444
+ {projects.map((project, idx) => (
445
+ <img
446
+ key={project.id}
447
+ src={project.image || "/placeholder.svg"}
448
+ alt={project.title}
449
+ className="w-full h-auto max-h-[85vh] object-contain bg-background flex-shrink-0"
450
+ style={{ minWidth: "100%" }}
451
+ />
452
+ ))}
453
+ </div>
454
+
455
+ {/* Subtle vignette effect */}
456
+ <div className="absolute inset-0 pointer-events-none bg-gradient-to-t from-card/20 via-transparent to-card/10" />
457
+ </div>
458
+
459
+ <div
460
+ className={cn("px-6 py-5", "bg-card", "border-t border-border")}
461
+ style={{
462
+ opacity: animationPhase === "complete" && !isClosing ? 1 : 0,
463
+ transform: animationPhase === "complete" && !isClosing ? "translateY(0)" : "translateY(20px)",
464
+ transition: "opacity 300ms ease-out 100ms, transform 300ms ease-out 100ms",
465
+ }}
466
+ >
467
+ <div className="flex items-start justify-between gap-4">
468
+ <div className="flex-1 min-w-0">
469
+ <h3 className="text-lg font-medium text-foreground tracking-tight truncate h-7">
470
+ {currentProject?.title}
471
+ </h3>
472
+ <div className="flex items-center gap-3 mt-1">
473
+ <p className="text-sm text-muted-foreground">
474
+ <kbd className="px-1.5 py-0.5 mx-0.5 text-xs font-medium bg-muted text-muted-foreground rounded border border-border">
475
+
476
+ </kbd>
477
+ <kbd className="px-1.5 py-0.5 mx-0.5 text-xs font-medium bg-muted text-muted-foreground rounded border border-border">
478
+
479
+ </kbd>{" "}
480
+ to navigate
481
+ </p>
482
+ <div className="flex items-center gap-1.5">
483
+ {projects.map((_, idx) => (
484
+ <button
485
+ key={idx}
486
+ onClick={() => handleDotClick(idx)}
487
+ className={cn(
488
+ "w-2 h-2 rounded-full transition-all duration-300",
489
+ idx === internalIndex
490
+ ? "bg-foreground scale-110"
491
+ : "bg-muted-foreground/40 hover:bg-muted-foreground/60",
492
+ )}
493
+ />
494
+ ))}
495
+ </div>
496
+ </div>
497
+ </div>
498
+
499
+ <button
500
+ className={cn(
501
+ "flex items-center gap-2 px-4 py-2",
502
+ "text-sm font-medium text-muted-foreground",
503
+ "bg-muted/50 hover:bg-muted",
504
+ "rounded-lg border border-border",
505
+ "transition-all duration-200 ease-out",
506
+ "hover:text-foreground",
507
+ )}
508
+ >
509
+ <span>View</span>
510
+ <ExternalLink className="w-3.5 h-3.5" />
511
+ </button>
512
+ </div>
513
+ </div>
514
+ </div>
515
+ </div>
516
+ </div>
517
+ )
518
+ }
519
+
520
+ interface ProjectCardProps {
521
+ image: string
522
+ title: string
523
+ delay: number
524
+ isVisible: boolean
525
+ index: number
526
+ onClick: () => void
527
+ isSelected: boolean
528
+ }
529
+
530
+ export const ProjectCard = forwardRef<HTMLDivElement, ProjectCardProps>(
531
+ ({ image, title, delay, isVisible, index, onClick, isSelected }, ref) => {
532
+ const rotations = [-12, 0, 12]
533
+ const translations = [-55, 0, 55]
534
+
535
+ return (
536
+ <div
537
+ ref={ref}
538
+ className={cn(
539
+ "absolute w-20 h-28 rounded-lg overflow-hidden shadow-xl",
540
+ "bg-card border border-border",
541
+ "cursor-pointer hover:ring-2 hover:ring-accent/50",
542
+ isSelected && "opacity-0",
543
+ )}
544
+ style={{
545
+ transform: isVisible
546
+ ? `translateY(-90px) translateX(${translations[index]}px) rotate(${rotations[index]}deg) scale(1)`
547
+ : "translateY(0px) translateX(0px) rotate(0deg) scale(0.5)",
548
+ opacity: isSelected ? 0 : isVisible ? 1 : 0,
549
+ transition: `all 600ms cubic-bezier(0.34, 1.56, 0.64, 1) ${delay}ms`,
550
+ zIndex: 10 - index,
551
+ left: "-40px",
552
+ top: "-56px",
553
+ }}
554
+ onClick={(e) => {
555
+ e.stopPropagation()
556
+ onClick()
557
+ }}
558
+ >
559
+ <img src={image || "/placeholder.svg"} alt={title} className="w-full h-full object-cover" />
560
+ <div className="absolute inset-0 bg-gradient-to-t from-foreground/60 to-transparent" />
561
+ <p className="absolute bottom-1.5 left-1.5 right-1.5 text-[10px] font-medium text-foreground truncate">
562
+ {title}
563
+ </p>
564
+ </div>
565
+ )
566
+ },
567
+ )
568
+
569
+ ProjectCard.displayName = "ProjectCard"
@@ -0,0 +1,50 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as AvatarPrimitive from "@radix-ui/react-avatar"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ const Avatar = React.forwardRef<
9
+ React.ElementRef<typeof AvatarPrimitive.Root>,
10
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
11
+ >(({ className, ...props }, ref) => (
12
+ <AvatarPrimitive.Root
13
+ ref={ref}
14
+ className={cn(
15
+ "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
16
+ className,
17
+ )}
18
+ {...props}
19
+ />
20
+ ))
21
+ Avatar.displayName = AvatarPrimitive.Root.displayName
22
+
23
+ const AvatarImage = React.forwardRef<
24
+ React.ElementRef<typeof AvatarPrimitive.Image>,
25
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
26
+ >(({ className, ...props }, ref) => (
27
+ <AvatarPrimitive.Image
28
+ ref={ref}
29
+ className={cn("aspect-square h-full w-full", className)}
30
+ {...props}
31
+ />
32
+ ))
33
+ AvatarImage.displayName = AvatarPrimitive.Image.displayName
34
+
35
+ const AvatarFallback = React.forwardRef<
36
+ React.ElementRef<typeof AvatarPrimitive.Fallback>,
37
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
38
+ >(({ className, ...props }, ref) => (
39
+ <AvatarPrimitive.Fallback
40
+ ref={ref}
41
+ className={cn(
42
+ "flex h-full w-full items-center justify-center rounded-full bg-muted",
43
+ className,
44
+ )}
45
+ {...props}
46
+ />
47
+ ))
48
+ AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
49
+
50
+ export { Avatar, AvatarImage, AvatarFallback }
@@ -0,0 +1,36 @@
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const badgeVariants = cva(
7
+ "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default:
12
+ "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
13
+ secondary:
14
+ "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15
+ destructive:
16
+ "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
17
+ outline: "text-foreground",
18
+ },
19
+ },
20
+ defaultVariants: {
21
+ variant: "default",
22
+ },
23
+ },
24
+ )
25
+
26
+ export interface BadgeProps
27
+ extends React.HTMLAttributes<HTMLDivElement>,
28
+ VariantProps<typeof badgeVariants> { }
29
+
30
+ function Badge({ className, variant, ...props }: BadgeProps) {
31
+ return (
32
+ <div className={cn(badgeVariants({ variant }), className)} {...props} />
33
+ )
34
+ }
35
+
36
+ export { Badge, badgeVariants }
@@ -0,0 +1,12 @@
1
+ import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
2
+
3
+ const AvatarDemo = () => {
4
+ return (
5
+ <Avatar>
6
+ <AvatarImage src='https://cdn.shadcnstudio.com/ss-assets/avatar/avatar-5.png' alt='Hallie Richards' />
7
+ <AvatarFallback className='text-xs'>HR</AvatarFallback>
8
+ </Avatar>
9
+ )
10
+ }
11
+
12
+ export default AvatarDemo