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,148 @@
1
+ "use client"
2
+
3
+ import Link from "next/link"
4
+ import { SocialStories } from "@/components/ui/social-stories"
5
+ import { bolivia } from "@/lib/fonts"
6
+ import { cn } from "@/lib/utils"
7
+ import { useState } from "react"
8
+ import { MobileMenu } from "./mobile-menu"
9
+ import { usePathname } from "next/navigation"
10
+
11
+ export function NavBar() {
12
+ const [isMenuOpen, setIsMenuOpen] = useState(false)
13
+ const pathname = usePathname()
14
+
15
+ // Hide Navbar on specific route
16
+ if (
17
+ pathname?.includes("/pickup/recycle04") ||
18
+ pathname?.startsWith("/gaming") ||
19
+ pathname?.includes("/github/abhisheks04") ||
20
+ pathname?.startsWith("/works/") ||
21
+ pathname?.startsWith("/admin")
22
+ ) return null;
23
+
24
+ const handleLogoClick = (e: React.MouseEvent) => {
25
+ if (pathname === "/") {
26
+ e.preventDefault()
27
+ window.scrollTo({ top: 0, behavior: "smooth" })
28
+ }
29
+ }
30
+
31
+ return (
32
+ <>
33
+ <nav
34
+ className="
35
+ fixed z-50 transition-all duration-300
36
+ top-4 left-8 right-8 h-13 rounded-full
37
+ bg-[#0a0a0a]/60 backdrop-blur-xl
38
+ border border-white/5 shadow-sm
39
+
40
+ pl-4 pr-2
41
+ flex items-center justify-between
42
+
43
+ md:top-0 md:left-0 md:right-0 md:w-full md:h-16 md:rounded-none
44
+ md:bg-[#0a0a0a]/70 md:border-b md:border-white/5 md:shadow-none
45
+ md:px-8
46
+ "
47
+ >
48
+ {/* Left: Logo */}
49
+ <Link
50
+ href="/"
51
+ onClick={handleLogoClick}
52
+ className="flex items-center group"
53
+ >
54
+ <span
55
+ className={cn(
56
+ bolivia.className,
57
+ "text-3xl md:text-4xl text-white/90 group-hover:text-[#007AFF] transition-colors duration-300 tracking-wide pt-1"
58
+ )}
59
+ >
60
+ Abhishek
61
+ </span>
62
+ </Link>
63
+
64
+ {/* Center (Desktop only) */}
65
+ <div className="hidden md:flex items-center gap-1 absolute left-1/2 -translate-x-1/2">
66
+ {["Home", "Works", "About"].map(item => (
67
+ <Link
68
+ key={item}
69
+ href={item === "Home" ? "/" : `/${item.toLowerCase()}`}
70
+ onClick={(e) => {
71
+ if (item === "Home" && pathname === "/") {
72
+ e.preventDefault()
73
+ window.scrollTo({ top: 0, behavior: "smooth" })
74
+ }
75
+ }}
76
+ className="px-4 py-2 rounded-full text-sm font-medium text-white/60 hover:text-white hover:bg-white/5 transition-all uppercase tracking-wider"
77
+ >
78
+ {item}
79
+ </Link>
80
+ ))}
81
+ </div>
82
+
83
+ {/* Right Actions */}
84
+ <div className="flex items-center">
85
+ {/* MOBILE ACTION PILL */}
86
+ <div
87
+ className="
88
+ md:hidden
89
+ flex items-center
90
+ gap-1.5
91
+ p-1
92
+ "
93
+ >
94
+ {/* Hamburger */}
95
+ <button
96
+ onClick={() => setIsMenuOpen(true)}
97
+ aria-label="Open Menu"
98
+ className="
99
+ w-8 h-8
100
+ flex items-center justify-center
101
+ transition-colors
102
+ text-white/90
103
+ hover:text-white
104
+ "
105
+ >
106
+ <svg
107
+ xmlns="http://www.w3.org/2000/svg"
108
+ width="16"
109
+ height="16"
110
+ viewBox="0 0 24 24"
111
+ fill="none"
112
+ stroke="currentColor"
113
+ strokeWidth="2"
114
+ strokeLinecap="round"
115
+ strokeLinejoin="round"
116
+ >
117
+ <line x1="4" x2="20" y1="7" y2="7" />
118
+ <line x1="4" x2="20" y1="12" y2="12" />
119
+ <line x1="4" x2="20" y1="17" y2="17" />
120
+ </svg>
121
+ </button>
122
+
123
+ {/* Social Stories */}
124
+ <div
125
+ className="
126
+ w-10 h-10
127
+ rounded-full
128
+ overflow-hidden
129
+ flex items-center justify-center
130
+ "
131
+ >
132
+ <SocialStories id="mobile" />
133
+ </div>
134
+ </div>
135
+
136
+ {/* DESKTOP */}
137
+ <div className="hidden md:flex items-center gap-4">
138
+ <div className="w-10 h-10 rounded-full overflow-hidden border border-white/10 hover:border-white/20 transition-colors">
139
+ <SocialStories id="desktop" />
140
+ </div>
141
+ </div>
142
+ </div>
143
+ </nav>
144
+
145
+ <MobileMenu isOpen={isMenuOpen} onClose={() => setIsMenuOpen(false)} />
146
+ </>
147
+ )
148
+ }
@@ -0,0 +1,24 @@
1
+ "use client";
2
+
3
+ import { motion } from "motion/react";
4
+ import { usePathname } from "next/navigation";
5
+
6
+ export function PageTransition({ children }: { children: React.ReactNode }) {
7
+ const pathname = usePathname();
8
+
9
+ return (
10
+ <motion.div
11
+ key={pathname}
12
+ initial={{ opacity: 0, y: 10, filter: "blur(5px)" }}
13
+ animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
14
+ transition={{
15
+ duration: 0.25,
16
+ ease: "easeOut",
17
+ }}
18
+ className="w-full h-full"
19
+ style={{ willChange: "transform, opacity, filter" }}
20
+ >
21
+ {children}
22
+ </motion.div>
23
+ );
24
+ }
@@ -0,0 +1,110 @@
1
+ "use client";
2
+
3
+ import { ArrowRight } from "lucide-react";
4
+ import { Button } from "@/components/ui/button";
5
+ import Link from "next/link";
6
+
7
+ interface Error404Props {
8
+ postcardImage?: string;
9
+ postcardAlt?: string;
10
+ curvedTextTop?: string;
11
+ curvedTextBottom?: string;
12
+ heading?: string;
13
+ subtext?: string;
14
+ backButtonLabel?: string;
15
+ backButtonHref?: string;
16
+ }
17
+
18
+ export function Error404({
19
+ postcardImage = "https://images.unsplash.com/photo-1485871981521-5b1fd3805eee?q=80&w=2070&auto=format&fit=crop",
20
+ postcardAlt = "New York City Postcard",
21
+ curvedTextTop = "The General Intelligence",
22
+ curvedTextBottom = "Company of New York",
23
+ heading = "(404) Looks like the page you're looking for got lost somewhere.",
24
+ subtext = "But hey — in New York, even the unexpected detours lead somewhere.",
25
+ backButtonLabel = "Back to Home",
26
+ backButtonHref = "/",
27
+ }: Error404Props) {
28
+ return (
29
+ <div className="min-h-screen flex items-center justify-center px-4 py-16 bg-[#0a0a0a]">
30
+ {/* Font import moved to layout or globals is better, but keeping here for single file portability if needed */}
31
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
32
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
33
+ <link href="https://fonts.googleapis.com/css2?family=Doto:wght@100..900&display=swap" rel="stylesheet" />
34
+
35
+ <div className="flex flex-col items-center">
36
+ <div className="relative mb-16">
37
+ <svg
38
+ className="absolute -top-16 -left-12 w-[140px] h-[140px] pointer-events-none z-20 animate-spin-slow"
39
+ viewBox="0 0 140 140"
40
+ >
41
+ <defs>
42
+ <path id="circlePath" d="M 70,70 m -50,0 a 50,50 0 1,1 100,0 a 50,50 0 1,1 -100,0" fill="transparent" />
43
+ </defs>
44
+ <text
45
+ className="text-[11px] fill-white/60 font-serif uppercase"
46
+ style={{ fontWeight: 400, letterSpacing: "0.15em" }}
47
+ >
48
+ <textPath href="#circlePath" startOffset="0%">
49
+ {curvedTextTop} • {curvedTextBottom} •
50
+ </textPath>
51
+ </text>
52
+ </svg>
53
+
54
+ <div className="relative z-10">
55
+ <div
56
+ className="relative p-3 shadow-2xl rotate-[4deg] hover:rotate-0 transition-transform duration-300 bg-[#1a1a1a] border border-white/10"
57
+ >
58
+ <div className="relative overflow-hidden bg-black">
59
+ <img
60
+ src={postcardImage}
61
+ alt={postcardAlt}
62
+ className="w-[360px] h-[220px] object-cover opacity-80"
63
+ />
64
+ {/* Overlay to make it feel more "postcard-y" */}
65
+ <div className="absolute inset-0 bg-black/20 pointer-events-none" />
66
+ </div>
67
+ </div>
68
+
69
+ {/* Postal cancellation marks */}
70
+ <svg className="absolute -right-16 top-1/2 -translate-y-1/2 w-28 h-20 opacity-30 invert" viewBox="0 0 100 60">
71
+ <path
72
+ d="M 10 15 Q 20 10 30 15 Q 40 20 50 15 Q 60 10 70 15 Q 80 20 90 15"
73
+ stroke="currentColor"
74
+ strokeWidth="1.5"
75
+ fill="none"
76
+ />
77
+ <path
78
+ d="M 10 25 Q 20 20 30 25 Q 40 30 50 25 Q 60 20 70 25 Q 80 30 90 25"
79
+ stroke="currentColor"
80
+ strokeWidth="1.5"
81
+ fill="none"
82
+ />
83
+ <path
84
+ d="M 10 35 Q 20 30 30 35 Q 40 40 50 35 Q 60 30 70 35 Q 80 40 90 35"
85
+ stroke="currentColor"
86
+ strokeWidth="1.5"
87
+ fill="none"
88
+ />
89
+ </svg>
90
+ </div>
91
+ </div>
92
+
93
+ <div className="text-center max-w-2xl">
94
+ <h1 className="text-4xl md:text-5xl font-['Doto'] mb-6 text-white text-balance leading-tight">
95
+ {heading}
96
+ </h1>
97
+ <p className="text-white/60 text-base md:text-lg mb-10 font-sans">
98
+ {subtext}
99
+ </p>
100
+ <Button asChild className="rounded-full px-8 py-6 bg-white text-black hover:bg-white/90">
101
+ <Link href={backButtonHref} className="flex items-center gap-2">
102
+ {backButtonLabel}
103
+ <ArrowRight className="w-4 h-4" />
104
+ </Link>
105
+ </Button>
106
+ </div>
107
+ </div>
108
+ </div>
109
+ );
110
+ }
@@ -0,0 +1,102 @@
1
+ "use client";
2
+
3
+ import { useState, useEffect, createContext, useContext } from "react";
4
+ import { usePathname } from "next/navigation";
5
+ import { motion, AnimatePresence } from "framer-motion";
6
+ import { Preloader } from "./preloader";
7
+
8
+ // Module-level variable to persist across route changes within the same session
9
+ let globalHasShownPreloader = false;
10
+
11
+ interface PreloaderContextType {
12
+ hasShown: boolean;
13
+ }
14
+
15
+ const PreloaderContext = createContext<PreloaderContextType | undefined>(undefined);
16
+
17
+ export function PreloaderWrapper({ children }: { children: React.ReactNode }) {
18
+ const pathname = usePathname();
19
+
20
+ // Initialize state based on session to prevent flash.
21
+ // Default to TRUE if not in admin, not shown yet, and on a VALID route.
22
+ const [isVisible, setIsVisible] = useState(() => {
23
+ if (typeof window !== 'undefined' && globalHasShownPreloader) return false;
24
+
25
+ const validRoutes = ["/", "/about", "/works", "/contact"];
26
+ const isDynamicWork = pathname?.startsWith("/works/");
27
+ const isAdmin = pathname?.startsWith("/admin");
28
+
29
+ const isValidRoute = validRoutes.includes(pathname || "") || isDynamicWork || isAdmin;
30
+
31
+ // Skip preloader for admin or invalid (404) routes
32
+ if (isAdmin || !isValidRoute) return false;
33
+
34
+ return true;
35
+ });
36
+
37
+ useEffect(() => {
38
+ // Remove static splash screen if it exists
39
+ const staticSplash = document.getElementById('static-splash');
40
+ if (staticSplash) {
41
+ staticSplash.style.opacity = '0';
42
+ setTimeout(() => {
43
+ staticSplash.remove();
44
+ }, 300);
45
+ }
46
+ }, []);
47
+
48
+ useEffect(() => {
49
+ // Handle overflow lock
50
+ if (isVisible) {
51
+ document.body.style.overflow = "hidden";
52
+ } else {
53
+ document.body.style.overflow = "";
54
+ }
55
+ }, [isVisible]);
56
+
57
+ const handleComplete = () => {
58
+ setIsVisible(false);
59
+ globalHasShownPreloader = true;
60
+ document.body.style.overflow = "";
61
+ };
62
+
63
+ return (
64
+ <PreloaderContext.Provider value={{ hasShown: !isVisible }}>
65
+ <AnimatePresence mode="wait">
66
+ {isVisible && (
67
+ <motion.div
68
+ key="global-preloader"
69
+ initial={{ y: 0 }}
70
+ exit={{
71
+ y: "-100%",
72
+ transition: {
73
+ duration: 1.2, // Slower for more elegance
74
+ ease: [0.83, 0, 0.17, 1] // "Quint" like easing, very dramatic slow start/end
75
+ }
76
+ }}
77
+ className="fixed inset-0 z-[9999] bg-[#050805] flex items-center justify-center"
78
+ >
79
+ <Preloader onComplete={handleComplete} />
80
+ </motion.div>
81
+ )}
82
+ </AnimatePresence>
83
+
84
+ {/*
85
+ Render children immediately.
86
+ We REMOVED the opacity toggle so the browser paints the LCP element
87
+ instantly behind the preloader layer. This fixes the LCP score.
88
+ */}
89
+ <div className="relative">
90
+ {children}
91
+ </div>
92
+ </PreloaderContext.Provider>
93
+ );
94
+ }
95
+
96
+ export const usePreloader = () => {
97
+ const context = useContext(PreloaderContext);
98
+ if (context === undefined) {
99
+ throw new Error("usePreloader must be used within a PreloaderWrapper");
100
+ }
101
+ return context;
102
+ };
@@ -0,0 +1,104 @@
1
+ "use client";
2
+
3
+ import { AnimatePresence, motion } from "framer-motion";
4
+ import { useEffect, useState } from "react";
5
+ import { cn } from "@/lib/utils";
6
+
7
+ interface Greeting {
8
+ text: string;
9
+ language: string;
10
+ }
11
+
12
+ const greetings: Greeting[] = [
13
+ { text: "Hello", language: "English" },
14
+ { text: "Hola", language: "Spanish" },
15
+ { text: "안녕하세요", language: "Korean" },
16
+ { text: "Ciao", language: "Italian" },
17
+ { text: "নমস্কার", language: "Bengali" },
18
+ { text: "नमस्ते", "language": "Hindi" },
19
+ ];
20
+
21
+ interface PreloaderProps {
22
+ onComplete: () => void;
23
+ }
24
+
25
+ export const Preloader = ({ onComplete }: PreloaderProps) => {
26
+ const [currentIndex, setCurrentIndex] = useState(0);
27
+ const [dimension, setDimension] = useState({ width: 0, height: 0 });
28
+
29
+ useEffect(() => {
30
+ setDimension({ width: window.innerWidth, height: window.innerHeight });
31
+ }, []);
32
+
33
+ useEffect(() => {
34
+ if (currentIndex === greetings.length - 1) {
35
+ // Smoothest exit: hold the last word slightly longer then complete
36
+ const timeout = setTimeout(() => {
37
+ onComplete();
38
+ }, 1000); // 1s hold for the last word
39
+ return () => clearTimeout(timeout);
40
+ }
41
+
42
+ const isMobile = window.innerWidth < 768;
43
+ // 1.4s per word as requested
44
+ const stepDuration = 1400;
45
+
46
+ const timeout = setTimeout(() => {
47
+ setCurrentIndex((prev) => prev + 1);
48
+ }, stepDuration);
49
+
50
+ return () => clearTimeout(timeout);
51
+ }, [currentIndex, onComplete]);
52
+
53
+ const textVariants = {
54
+ initial: {
55
+ opacity: 0,
56
+ y: 40, // Increased movement for more "drama"
57
+ filter: "blur(12px)", // Stronger blur
58
+ },
59
+ animate: {
60
+ opacity: 1,
61
+ y: 0,
62
+ filter: "blur(0px)",
63
+ transition: {
64
+ // Reduced duration to fit within 1.4s interval (Wait Mode: Exit + Enter + Static = Interval)
65
+ // 0.4s Exit + 0.6s Enter = 1.0s. Leaves 0.4s for static readability.
66
+ duration: 0.6,
67
+ ease: [0.25, 1, 0.5, 1] as const,
68
+ }
69
+ },
70
+ exit: {
71
+ opacity: 0,
72
+ y: -40, // Match entrance movement
73
+ filter: "blur(12px)",
74
+ transition: {
75
+ duration: 0.4,
76
+ ease: [0.25, 1, 0.5, 1] as const
77
+ }
78
+ },
79
+ };
80
+
81
+ return (
82
+ <div className="fixed inset-0 z-[100] flex items-center justify-center bg-[#050805] cursor-none overflow-hidden">
83
+ {/*
84
+ Use absolute positioning for the text container to prevent any flex-based layout shifts
85
+ during size changes (though text is mostly centered, this is safer).
86
+ */}
87
+ <AnimatePresence mode="wait">
88
+ {greetings[currentIndex] && (
89
+ <motion.p
90
+ key={currentIndex}
91
+ variants={textVariants}
92
+ initial="initial"
93
+ animate="animate"
94
+ exit="exit"
95
+ className="absolute text-4xl md:text-7xl font-light tracking-tight text-white font-sans mix-blend-difference"
96
+ style={{ willChange: "transform, opacity, filter" }}
97
+ >
98
+ {greetings[currentIndex].text}
99
+ </motion.p>
100
+ )}
101
+ </AnimatePresence>
102
+ </div>
103
+ );
104
+ };
@@ -0,0 +1,57 @@
1
+ import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
2
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
3
+ import Link from 'next/link'
4
+
5
+ export interface Contributor {
6
+ id?: string
7
+ name: string
8
+ avatar_url: string
9
+ fallback: string
10
+ social_url?: string
11
+ role?: string
12
+ }
13
+
14
+ interface ProjectContributorsProps {
15
+ contributors: Contributor[]
16
+ className?: string
17
+ size?: 'sm' | 'md' | 'lg'
18
+ }
19
+
20
+ const ProjectContributors = ({ contributors, className = '', size = 'md' }: ProjectContributorsProps) => {
21
+ const sizeClasses = {
22
+ sm: 'size-8',
23
+ md: 'size-10',
24
+ lg: 'size-12'
25
+ }
26
+
27
+ return (
28
+ <div className={`flex -space-x-3 ${className}`}>
29
+ <TooltipProvider delayDuration={0}>
30
+ {contributors.map((contributor, index) => (
31
+ <Tooltip key={index}>
32
+ <TooltipTrigger asChild>
33
+ <Link
34
+ href={contributor.social_url || '#'}
35
+ target={contributor.social_url ? "_blank" : "_self"}
36
+ className={`relative transition-transform hover:z-10 hover:scale-110`}
37
+ >
38
+ <Avatar className={`ring-background ring-2 ${sizeClasses[size]}`}>
39
+ <AvatarImage src={contributor.avatar_url} alt={contributor.name} className="object-cover" />
40
+ <AvatarFallback>{contributor.fallback}</AvatarFallback>
41
+ </Avatar>
42
+ </Link>
43
+ </TooltipTrigger>
44
+ <TooltipContent>
45
+ <div className="flex flex-col items-center">
46
+ <span className="font-semibold text-xs">{contributor.name}</span>
47
+ {contributor.role && <span className="text-[10px] text-muted-foreground">{contributor.role}</span>}
48
+ </div>
49
+ </TooltipContent>
50
+ </Tooltip>
51
+ ))}
52
+ </TooltipProvider>
53
+ </div>
54
+ )
55
+ }
56
+
57
+ export default ProjectContributors
@@ -0,0 +1,117 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
5
+ import { cva, type VariantProps } from "class-variance-authority";
6
+ import { cn } from "@/lib/utils";
7
+
8
+ const scrollAreaVariants = cva("relative overflow-hidden", {
9
+ variants: {
10
+ orientation: {
11
+ vertical: "h-full",
12
+ horizontal: "w-full",
13
+ both: "h-full w-full",
14
+ },
15
+ },
16
+ defaultVariants: {
17
+ orientation: "vertical",
18
+ },
19
+ });
20
+
21
+ const scrollBarVariants = cva("flex touch-none select-none transition-colors", {
22
+ variants: {
23
+ orientation: {
24
+ vertical: "h-full w-2.5 border-l border-l-transparent p-[1px]",
25
+ horizontal: "h-2.5 w-full border-t border-t-transparent p-[1px]",
26
+ },
27
+ },
28
+ defaultVariants: {
29
+ orientation: "vertical",
30
+ },
31
+ });
32
+
33
+ export interface ScrollAreaProps
34
+ extends React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>,
35
+ VariantProps<typeof scrollAreaVariants> {
36
+ scrollHideDelay?: number;
37
+ type?: "auto" | "always" | "scroll" | "hover";
38
+ }
39
+
40
+ const ScrollArea = React.forwardRef<
41
+ React.ElementRef<typeof ScrollAreaPrimitive.Root>,
42
+ ScrollAreaProps
43
+ >(
44
+ (
45
+ {
46
+ className,
47
+ children,
48
+ orientation,
49
+ scrollHideDelay = 600,
50
+ type = "hover",
51
+ ...props
52
+ },
53
+ ref
54
+ ) => (
55
+ <ScrollAreaPrimitive.Root
56
+ ref={ref}
57
+ className={cn(scrollAreaVariants({ orientation }), className)}
58
+ {...props}
59
+ >
60
+ <ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
61
+ {children}
62
+ </ScrollAreaPrimitive.Viewport>
63
+ <ScrollBar
64
+ orientation="vertical"
65
+ type={type}
66
+ scrollHideDelay={scrollHideDelay}
67
+ />
68
+ {(orientation === "horizontal" || orientation === "both") && (
69
+ <ScrollBar
70
+ orientation="horizontal"
71
+ type={type}
72
+ scrollHideDelay={scrollHideDelay}
73
+ />
74
+ )}
75
+ <ScrollAreaPrimitive.Corner />
76
+ </ScrollAreaPrimitive.Root>
77
+ )
78
+ );
79
+
80
+ ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
81
+
82
+ interface ScrollBarProps
83
+ extends React.ComponentPropsWithoutRef<
84
+ typeof ScrollAreaPrimitive.ScrollAreaScrollbar
85
+ > {
86
+ scrollHideDelay?: number;
87
+ type?: "auto" | "always" | "scroll" | "hover";
88
+ }
89
+
90
+ const ScrollBar = React.forwardRef<
91
+ React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
92
+ ScrollBarProps
93
+ >(
94
+ (
95
+ { className, orientation = "vertical", scrollHideDelay, type, ...props },
96
+ ref
97
+ ) => {
98
+ return (
99
+ <ScrollAreaPrimitive.ScrollAreaScrollbar
100
+ ref={ref}
101
+ orientation={orientation}
102
+ className={cn(
103
+ scrollBarVariants({ orientation }),
104
+ "hover:bg-accent",
105
+ className
106
+ )}
107
+ {...props}
108
+ >
109
+ <ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border hover:bg-foreground/30 transition-colors" />
110
+ </ScrollAreaPrimitive.ScrollAreaScrollbar>
111
+ );
112
+ }
113
+ );
114
+
115
+ ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
116
+
117
+ export { ScrollArea, ScrollBar, scrollAreaVariants };