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,152 @@
1
+
2
+ "use client"
3
+
4
+ import * as React from "react"
5
+ import { useRouter } from "next/navigation"
6
+ import {
7
+ CommandMenu,
8
+ CommandMenuContent,
9
+ CommandMenuInput,
10
+ CommandMenuList,
11
+ CommandMenuGroup,
12
+ CommandMenuItem,
13
+ CommandMenuSeparator,
14
+ useCommandMenuShortcut,
15
+ } from "@/components/ui/command-menu"
16
+ import {
17
+ Home,
18
+ User,
19
+ Briefcase,
20
+ Github,
21
+ Twitter,
22
+ Linkedin,
23
+ Mail,
24
+ FileText,
25
+ Copy,
26
+ ArrowRight
27
+ } from "lucide-react"
28
+
29
+ export function CommandPalette() {
30
+ const [open, setOpen] = React.useState(false)
31
+ const router = useRouter()
32
+
33
+ useCommandMenuShortcut(() => setOpen(true))
34
+
35
+ const runCommand = React.useCallback((command: () => void) => {
36
+ setOpen(false)
37
+ command()
38
+ }, [])
39
+
40
+ return (
41
+ <CommandMenu open={open} onOpenChange={setOpen}>
42
+ {/* Trigger is handled globally by shortcut, so no visual trigger needed here */}
43
+
44
+ <CommandMenuContent>
45
+ <CommandMenuInput placeholder="Type a command or search..." />
46
+ <CommandMenuList>
47
+ <CommandMenuGroup heading="Explore the Verse">
48
+ <CommandMenuItem
49
+ icon={<Home className="text-blue-400" />}
50
+ index={0}
51
+ keywords={["home", "hq", "index", "return"]}
52
+ onSelect={() => runCommand(() => router.push("/"))}
53
+ >
54
+ <span className="font-medium text-white/90">Return to HQ</span>
55
+ <span className="ml-2 text-xs text-white/40 hidden sm:inline-block">Home Page</span>
56
+ </CommandMenuItem>
57
+ <CommandMenuItem
58
+ icon={<User className="text-purple-400" />}
59
+ index={1}
60
+ keywords={["about", "bio", "lore", "who", "story"]}
61
+ onSelect={() => runCommand(() => router.push("/about"))}
62
+ >
63
+ <span className="font-medium text-white/90">Who is Abhishek?</span>
64
+ <span className="ml-2 text-xs text-white/40 hidden sm:inline-block">The lore behind the dev</span>
65
+ </CommandMenuItem>
66
+ <CommandMenuItem
67
+ icon={<Briefcase className="text-yellow-400" />}
68
+ index={2}
69
+ keywords={["works", "projects", "portfolio", "archives", "case studies"]}
70
+ onSelect={() => runCommand(() => router.push("/works"))}
71
+ >
72
+ <span className="font-medium text-white/90">Inspect the Archives</span>
73
+ <span className="ml-2 text-xs text-white/40 hidden sm:inline-block">My finest works</span>
74
+ </CommandMenuItem>
75
+ <CommandMenuItem
76
+ icon={<Github className="text-green-400" />}
77
+ index={3}
78
+ keywords={["github", "code", "source", "repo", "git"]}
79
+ onSelect={() => runCommand(() => router.push("/github/AbhishekS04"))}
80
+ >
81
+ <span className="font-medium text-white/90">Analyze Source Code</span>
82
+ <span className="ml-2 text-xs text-white/40 hidden sm:inline-block">View GitHub without leaving</span>
83
+ </CommandMenuItem>
84
+ </CommandMenuGroup>
85
+
86
+ <CommandMenuSeparator />
87
+
88
+ <CommandMenuGroup heading="Connect">
89
+ <CommandMenuItem
90
+ icon={<Github />}
91
+ index={4}
92
+ keywords={["github", "profile", "social"]}
93
+ onSelect={() => runCommand(() => window.open("https://github.com/AbhishekS04", "_blank"))}
94
+ >
95
+ GitHub Profile
96
+ </CommandMenuItem>
97
+ <CommandMenuItem
98
+ icon={<Twitter />}
99
+ index={5}
100
+ keywords={["twitter", "x", "social", "tweet"]}
101
+ onSelect={() => runCommand(() => window.open("https://twitter.com/AbhishekS04", "_blank"))}
102
+ >
103
+ Twitter / X
104
+ </CommandMenuItem>
105
+ <CommandMenuItem
106
+ icon={<Linkedin />}
107
+ index={6}
108
+ keywords={["linkedin", "network", "social", "career"]}
109
+ onSelect={() => runCommand(() => window.open("https://linkedin.com/in/AbhishekS04", "_blank"))}
110
+ >
111
+ LinkedIn Network
112
+ </CommandMenuItem>
113
+ </CommandMenuGroup>
114
+
115
+ <CommandMenuSeparator />
116
+
117
+ <CommandMenuGroup heading="Protocol">
118
+ <CommandMenuItem
119
+ icon={<Copy />}
120
+ index={7}
121
+ shortcut="cmd+c"
122
+ keywords={["copy", "email", "address", "contact"]}
123
+ onSelect={() => runCommand(() => {
124
+ navigator.clipboard.writeText("your.email@example.com")
125
+ })}
126
+ >
127
+ Copy Coordinates (Email)
128
+ </CommandMenuItem>
129
+ <CommandMenuItem
130
+ icon={<FileText />}
131
+ index={8}
132
+ shortcut="cmd+r"
133
+ keywords={["resume", "cv", "pdf", "dossier", "download"]}
134
+ onSelect={() => runCommand(() => window.open("/resume.pdf", "_blank"))}
135
+ >
136
+ Download Dossier (Resume)
137
+ </CommandMenuItem>
138
+ <CommandMenuItem
139
+ icon={<Mail />}
140
+ index={9}
141
+ keywords={["send", "mail", "contact", "message", "write"]}
142
+ onSelect={() => runCommand(() => window.location.href = "mailto:your.email@example.com")}
143
+ >
144
+ Establish Comms (Mailto)
145
+ </CommandMenuItem>
146
+ </CommandMenuGroup>
147
+ </CommandMenuList>
148
+ </CommandMenuContent>
149
+ </CommandMenu>
150
+ )
151
+ }
152
+
@@ -0,0 +1,200 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState, useRef } from "react";
4
+ import { motion, AnimatePresence } from "framer-motion";
5
+
6
+ export const ConsciousnessMode = () => {
7
+ const [isActive, setIsActive] = useState(false);
8
+ const [progress, setProgress] = useState(0);
9
+ const [isLocked, setIsLocked] = useState(true); // Default locked until checked
10
+ const progressRef = useRef(0);
11
+ const lastScrollY = useRef(0);
12
+ const lastTime = useRef(0);
13
+ const inactivityTimer = useRef<NodeJS.Timeout | null>(null);
14
+
15
+ useEffect(() => {
16
+ // Check persistence
17
+ const witnessed = localStorage.getItem("consciousness_witnessed");
18
+ if (!witnessed) setIsLocked(false);
19
+ }, []);
20
+
21
+ useEffect(() => {
22
+ const handleScroll = (e: Event) => {
23
+ if (isActive) return;
24
+
25
+ const now = Date.now();
26
+ const currentScrollY = window.scrollY;
27
+ const deltaY = Math.abs(currentScrollY - lastScrollY.current);
28
+ const deltaTime = now - lastTime.current;
29
+
30
+ // Update refs
31
+ lastScrollY.current = currentScrollY;
32
+ lastTime.current = now;
33
+
34
+ // LOGIC:
35
+ // 1. Shift Key must be held (we check via a separate listener or just window.event if possible, but React safer to track key state)
36
+ // Actually, we can check keyboard state via a ref tracker.
37
+ };
38
+
39
+ // We need a key tracker
40
+ // But simpler: We can check MouseEvent modifiers? No, scroll is an Event or WheelEvent.
41
+ // WheelEvent has shiftKey. But 'scroll' event does not.
42
+ // Let's listen to 'wheel' for the trigger?
43
+ // - "scroll" event fires AFTER layout change.
44
+ // - "wheel" event fires ON input.
45
+ // User said "Scroll slowly". Wheel is better for detecting "Intent".
46
+
47
+ }, [isActive]);
48
+
49
+ // Better Logic:
50
+ // Track Shift Key State
51
+ const [isShiftPressed, setIsShiftPressed] = useState(false);
52
+ useEffect(() => {
53
+ if (isLocked) return;
54
+ const handleKeyDown = (e: KeyboardEvent) => {
55
+ if (e.key === "Shift") setIsShiftPressed(true);
56
+ };
57
+ const handleKeyUp = (e: KeyboardEvent) => {
58
+ if (e.key === "Shift") {
59
+ setIsShiftPressed(false);
60
+ if (!isActive) {
61
+ setProgress(0); // Reset if let go before completion
62
+ progressRef.current = 0;
63
+ }
64
+ }
65
+ };
66
+ window.addEventListener("keydown", handleKeyDown);
67
+ window.addEventListener("keyup", handleKeyUp);
68
+ return () => {
69
+ window.removeEventListener("keydown", handleKeyDown);
70
+ window.removeEventListener("keyup", handleKeyUp);
71
+ };
72
+ }, [isActive, isLocked]);
73
+
74
+ // Track Scroll
75
+ useEffect(() => {
76
+ if (isLocked || !isShiftPressed || isActive) return;
77
+
78
+ let animationFrame: number;
79
+
80
+ const checkScroll = () => {
81
+ // We need to detect "Active Scrolling" but "Slow".
82
+ // Since scroll events are discrete, let's just decay the progress if NO scroll happens.
83
+ };
84
+
85
+ const handleWheel = (e: WheelEvent) => {
86
+ if (isLocked || !isShiftPressed || isActive) return;
87
+
88
+ // Speed Check
89
+ // Typical fast scroll is > 50-100 delta.
90
+ // Slow scroll is < 20.
91
+ const speed = Math.abs(e.deltaY);
92
+
93
+ if (speed > 0 && speed < 30) {
94
+ // Good speed. Increment.
95
+ // Target: 7 seconds.
96
+ // Assuming ~60 wheel events per second for continuous smooth scrolling (trackpad), or ~10 for ratcheted mouse.
97
+ // Let's aim safely for a mix. 0.4 per event is roughly 250 events.
98
+ // If 60hz -> 4 seconds. If 30hz -> 8 seconds.
99
+ // Let's try 0.25 to be safe for 7s on smooth trackpads.
100
+ progressRef.current += 0.25;
101
+
102
+ if (progressRef.current > 100) {
103
+ progressRef.current = 100;
104
+ setIsActive(true);
105
+ localStorage.setItem("consciousness_witnessed", "true");
106
+ }
107
+ setProgress(progressRef.current);
108
+ } else if (speed > 50) {
109
+ // Too fast! Reset punishment.
110
+ progressRef.current = Math.max(0, progressRef.current - 5);
111
+ setProgress(progressRef.current);
112
+ }
113
+
114
+ // Reset inactivity timer
115
+ if (inactivityTimer.current) clearTimeout(inactivityTimer.current);
116
+ inactivityTimer.current = setTimeout(() => {
117
+ // Decay if stopped
118
+ const decay = setInterval(() => {
119
+ progressRef.current -= 2;
120
+ if (progressRef.current <= 0) {
121
+ progressRef.current = 0;
122
+ clearInterval(decay);
123
+ }
124
+ setProgress(progressRef.current);
125
+ }, 50);
126
+ }, 500);
127
+ };
128
+
129
+ window.addEventListener("wheel", handleWheel);
130
+ return () => window.removeEventListener("wheel", handleWheel);
131
+ }, [isShiftPressed, isActive, isLocked]);
132
+
133
+
134
+ // Auto-dismiss after activation
135
+ useEffect(() => {
136
+ if (isActive) {
137
+ const timer = setTimeout(() => {
138
+ setIsActive(false);
139
+ setProgress(0);
140
+ progressRef.current = 0;
141
+ }, 4500); // Wait for text to fully play out
142
+ return () => clearTimeout(timer);
143
+ }
144
+ }, [isActive]);
145
+
146
+ return (
147
+ <AnimatePresence>
148
+ {/* PROGRESS FEEDBACK (Subtle) */}
149
+ {progress > 5 && !isActive && (
150
+ <motion.div
151
+ className="fixed bottom-0 left-0 h-1 bg-white/20 z-[9999]"
152
+ style={{ width: `${progress}%` }}
153
+ initial={{ opacity: 0 }}
154
+ animate={{ opacity: 1 }}
155
+ exit={{ opacity: 0 }}
156
+ />
157
+ )}
158
+
159
+ {isActive && (
160
+ <motion.div
161
+ initial={{ opacity: 0 }}
162
+ animate={{ opacity: 1 }}
163
+ exit={{ opacity: 0 }}
164
+ transition={{ duration: 1 }}
165
+ className="fixed inset-0 z-[1000] pointer-events-none flex items-center justify-center"
166
+ >
167
+ {/* BLUR OVERLAY */}
168
+ <motion.div
169
+ initial={{ backdropFilter: "blur(0px)", backgroundColor: "rgba(0,0,0,0)" }}
170
+ animate={{ backdropFilter: "blur(12px)", backgroundColor: "rgba(0,0,0,0.4)" }}
171
+ exit={{ backdropFilter: "blur(0px)", backgroundColor: "rgba(0,0,0,0)" }}
172
+ transition={{ duration: 1.5, ease: "easeOut" }}
173
+ className="absolute inset-0"
174
+ />
175
+
176
+ {/* TEXT */}
177
+ <motion.div
178
+ initial={{ opacity: 0, scale: 0.95, filter: "blur(10px)" }}
179
+ animate={{ opacity: 1, scale: 1, filter: "blur(0px)" }}
180
+ exit={{ opacity: 0, scale: 1.05, filter: "blur(5px)" }}
181
+ transition={{ duration: 1.5, ease: "easeOut" }}
182
+ className="relative z-10 text-center px-4"
183
+ >
184
+ <h2 className="text-3xl md:text-5xl font-light tracking-widest text-[#e2e2e2] font-serif italic mb-4">
185
+ You’re paying attention.
186
+ </h2>
187
+ <motion.p
188
+ initial={{ opacity: 0 }}
189
+ animate={{ opacity: 1 }}
190
+ transition={{ delay: 1.5, duration: 1 }}
191
+ className="text-sm uppercase tracking-[0.3em] text-white/50"
192
+ >
193
+ I like that.
194
+ </motion.p>
195
+ </motion.div>
196
+ </motion.div>
197
+ )}
198
+ </AnimatePresence>
199
+ );
200
+ };
@@ -0,0 +1,135 @@
1
+ "use client";
2
+
3
+ import { useState, useEffect } from "react";
4
+
5
+ interface CopyCodeProps {
6
+ code: string;
7
+ }
8
+
9
+ export function CopyCode({ code }: CopyCodeProps) {
10
+ const [copied, setCopied] = useState(false);
11
+ const [showConfirmation, setShowConfirmation] = useState(false);
12
+ const [progress, setProgress] = useState(0);
13
+ const duration = 2000; // Reduced to 2s for email copy (4s is too long)
14
+
15
+ useEffect(() => {
16
+ if (copied) {
17
+ // Delay showing confirmation to allow blur-out animation
18
+ const showTimer = setTimeout(() => {
19
+ setShowConfirmation(true);
20
+ }, 400);
21
+
22
+ setProgress(0);
23
+ const startTime = Date.now();
24
+
25
+ const interval = setInterval(() => {
26
+ const elapsed = Date.now() - startTime;
27
+ const newProgress = Math.min((elapsed / duration) * 100, 100);
28
+ setProgress(newProgress);
29
+
30
+ if (elapsed >= duration) {
31
+ clearInterval(interval);
32
+ setShowConfirmation(false);
33
+ setTimeout(() => {
34
+ setCopied(false);
35
+ setProgress(0);
36
+ }, 400);
37
+ }
38
+ }, 16);
39
+
40
+ return () => {
41
+ clearInterval(interval);
42
+ clearTimeout(showTimer);
43
+ };
44
+ }
45
+ }, [copied]);
46
+
47
+ const handleCopy = async () => {
48
+ try {
49
+ await navigator.clipboard.writeText(code);
50
+ } catch (err) {
51
+ // Fallback
52
+ const textArea = document.createElement('textarea');
53
+ textArea.value = code;
54
+ document.body.appendChild(textArea);
55
+ textArea.select();
56
+ document.execCommand('copy');
57
+ document.body.removeChild(textArea);
58
+ }
59
+ setCopied(true);
60
+ };
61
+
62
+ return (
63
+ <div className="relative overflow-hidden flex items-center justify-between bg-[#111] border border-white/10 rounded-full px-8 py-3 w-fit min-w-[300px] h-16">
64
+ {/* Progress background */}
65
+ <div
66
+ className="absolute left-0 top-0 bottom-0 bg-white/10"
67
+ style={{
68
+ width: `${progress}%`,
69
+ opacity: copied ? 1 : 0,
70
+ transition: 'opacity 0.5s cubic-bezier(0.4, 0, 0.2, 1)',
71
+ }}
72
+ />
73
+
74
+ {/* Original content - code and button */}
75
+ <div
76
+ className="absolute inset-0 flex items-center justify-between px-8"
77
+ style={{
78
+ opacity: copied ? 0 : 1,
79
+ filter: copied ? 'blur(12px)' : 'blur(0px)',
80
+ transform: copied ? 'scale(0.92)' : 'scale(1)',
81
+ transition: 'all 0.5s cubic-bezier(0.4, 0, 0.2, 1)',
82
+ pointerEvents: copied ? 'none' : 'auto',
83
+ zIndex: copied ? 0 : 20,
84
+ }}
85
+ >
86
+ <span className="text-xl font-medium tracking-wide text-white/80 select-all truncate max-w-[200px]">
87
+ {code}
88
+ </span>
89
+ <button
90
+ onClick={handleCopy}
91
+ className="ml-4 bg-white/10 hover:bg-white/20 text-white/90 text-sm font-medium px-4 py-2 rounded-full transition-all duration-300 active:scale-95 cursor-pointer select-none"
92
+ >
93
+ Copy
94
+ </button>
95
+ </div>
96
+
97
+ {/* Confirmation content - Code Copied! */}
98
+ <div
99
+ className="absolute inset-0 flex items-center justify-center gap-3"
100
+ style={{
101
+ opacity: showConfirmation ? 1 : 0,
102
+ filter: showConfirmation ? 'blur(0px)' : 'blur(12px)',
103
+ transform: showConfirmation ? 'scale(1)' : 'scale(1.08)',
104
+ transition: 'all 0.8s cubic-bezier(0.4, 0, 0.2, 1)',
105
+ pointerEvents: 'none',
106
+ zIndex: 10,
107
+ }}
108
+ >
109
+ <div className="w-6 h-6 bg-emerald-500 rounded-full flex items-center justify-center">
110
+ <svg
111
+ className="w-3.5 h-3.5 text-black"
112
+ fill="none"
113
+ stroke="currentColor"
114
+ viewBox="0 0 24 24"
115
+ >
116
+ <path
117
+ strokeLinecap="round"
118
+ strokeLinejoin="round"
119
+ strokeWidth={3}
120
+ d="M5 13l4 4L19 7"
121
+ style={{
122
+ strokeDasharray: 24,
123
+ strokeDashoffset: showConfirmation ? 0 : 24,
124
+ transition: 'stroke-dashoffset 0.6s cubic-bezier(0.4, 0, 0.2, 1) 0.3s',
125
+ }}
126
+ />
127
+ </svg>
128
+ </div>
129
+ <span className="text-lg font-medium text-white">
130
+ Email Copied!
131
+ </span>
132
+ </div>
133
+ </div>
134
+ );
135
+ }
@@ -0,0 +1,70 @@
1
+ "use client";
2
+
3
+ import { cn } from "@/lib/utils";
4
+ import { Sparkles } from "lucide-react";
5
+
6
+ interface DisplayCardProps {
7
+ className?: string;
8
+ icon?: React.ReactNode;
9
+ title?: string;
10
+ description?: string;
11
+ date?: string;
12
+ iconClassName?: string;
13
+ titleClassName?: string;
14
+ }
15
+
16
+ function DisplayCard({
17
+ className,
18
+ icon = <Sparkles className="size-4 text-blue-300" />,
19
+ title = "Featured",
20
+ description = "Discover amazing content",
21
+ date = "Just now",
22
+ iconClassName = "text-blue-500",
23
+ titleClassName = "text-blue-500",
24
+ }: DisplayCardProps) {
25
+ return (
26
+ <div
27
+ className={cn(
28
+ "relative flex h-36 w-full max-w-[22rem] -skew-y-[8deg] select-none flex-col justify-between rounded-xl border-2 bg-muted/70 backdrop-blur-sm px-4 py-3 transition-all duration-700 after:absolute after:-right-1 after:top-[-5%] after:h-[110%] after:w-[20rem] after:bg-gradient-to-l after:from-background after:to-transparent after:content-[''] hover:border-white/20 hover:bg-muted [&>*]:flex [&>*]:items-center [&>*]:gap-2",
29
+ className
30
+ )}
31
+ >
32
+ <div>
33
+ <span className="relative inline-block rounded-full bg-blue-800 p-1">
34
+ {icon}
35
+ </span>
36
+ <p className={cn("text-lg font-medium", titleClassName)}>{title}</p>
37
+ </div>
38
+ <p className="whitespace-nowrap text-lg">{description}</p>
39
+ <p className="text-muted-foreground">{date}</p>
40
+ </div>
41
+ );
42
+ }
43
+
44
+ interface DisplayCardsProps {
45
+ cards?: DisplayCardProps[];
46
+ }
47
+
48
+ export default function DisplayCards({ cards }: DisplayCardsProps) {
49
+ const defaultCards = [
50
+ {
51
+ className: "[grid-area:stack] hover:-translate-y-10 before:absolute before:w-[100%] before:outline-1 before:rounded-xl before:outline-border before:h-[100%] before:content-[''] before:bg-blend-overlay before:bg-background/50 grayscale-[100%] hover:before:opacity-0 before:transition-opacity before:duration:700 hover:grayscale-0 before:left-0 before:top-0",
52
+ },
53
+ {
54
+ className: "[grid-area:stack] translate-x-16 translate-y-10 hover:-translate-y-1 before:absolute before:w-[100%] before:outline-1 before:rounded-xl before:outline-border before:h-[100%] before:content-[''] before:bg-blend-overlay before:bg-background/50 grayscale-[100%] hover:before:opacity-0 before:transition-opacity before:duration:700 hover:grayscale-0 before:left-0 before:top-0",
55
+ },
56
+ {
57
+ className: "[grid-area:stack] translate-x-32 translate-y-20 hover:translate-y-10",
58
+ },
59
+ ];
60
+
61
+ const displayCards = cards || defaultCards;
62
+
63
+ return (
64
+ <div className="grid [grid-template-areas:'stack'] place-items-center opacity-100 animate-in fade-in-0 duration-700">
65
+ {displayCards.map((cardProps, index) => (
66
+ <DisplayCard key={index} {...cardProps} />
67
+ ))}
68
+ </div>
69
+ );
70
+ }
@@ -0,0 +1,128 @@
1
+ import * as React from "react"
2
+ import { createMap } from "svg-dotted-map"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ interface Marker {
7
+ lat: number
8
+ lng: number
9
+ size?: number
10
+ }
11
+
12
+ export interface DottedMapProps extends React.SVGProps<SVGSVGElement> {
13
+ width?: number
14
+ height?: number
15
+ mapSamples?: number
16
+ markers?: Marker[]
17
+ dotColor?: string
18
+ markerColor?: string
19
+ dotRadius?: number
20
+ stagger?: boolean
21
+ }
22
+
23
+ export function DottedMap({
24
+ width = 150,
25
+ height = 75,
26
+ mapSamples = 5000,
27
+ markers = [],
28
+ markerColor = "#FF6900",
29
+ dotRadius = 0.2,
30
+ stagger = true,
31
+ className,
32
+ style,
33
+ }: DottedMapProps) {
34
+ const { points, addMarkers } = createMap({
35
+ width,
36
+ height,
37
+ mapSamples,
38
+ })
39
+
40
+ const processedMarkers = addMarkers(markers)
41
+
42
+ // Compute stagger helpers in a single, simple pass
43
+ const { xStep, yToRowIndex } = React.useMemo(() => {
44
+ const sorted = [...points].sort((a, b) => a.y - b.y || a.x - b.x)
45
+ const rowMap = new Map<number, number>()
46
+ let step = 0
47
+ let prevY = Number.NaN
48
+ let prevXInRow = Number.NaN
49
+
50
+ for (const p of sorted) {
51
+ if (p.y !== prevY) {
52
+ // new row
53
+ prevY = p.y
54
+ prevXInRow = Number.NaN
55
+ if (!rowMap.has(p.y)) rowMap.set(p.y, rowMap.size)
56
+ }
57
+ if (!Number.isNaN(prevXInRow)) {
58
+ const delta = p.x - prevXInRow
59
+ if (delta > 0) step = step === 0 ? delta : Math.min(step, delta)
60
+ }
61
+ prevXInRow = p.x
62
+ }
63
+
64
+ return { xStep: step || 1, yToRowIndex: rowMap }
65
+ }, [points])
66
+
67
+ const [isEggActive, setIsEggActive] = React.useState(false);
68
+
69
+ React.useEffect(() => {
70
+ if (typeof window !== "undefined") {
71
+ const purged = localStorage.getItem("mapEgg");
72
+ if (purged !== "purged") setIsEggActive(true);
73
+ }
74
+ }, []);
75
+
76
+ const handleEggClick = (e: React.MouseEvent) => {
77
+ e.stopPropagation();
78
+ e.preventDefault();
79
+ setIsEggActive(false);
80
+ localStorage.setItem("mapEgg", "purged");
81
+ };
82
+
83
+ return (
84
+ <svg
85
+ viewBox={`0 0 ${width} ${height}`}
86
+ className={cn("text-gray-500 dark:text-gray-500", className)}
87
+ style={{ width: "100%", height: "100%", ...style }}
88
+ >
89
+ {points.map((point, index) => {
90
+ const rowIndex = yToRowIndex.get(point.y) ?? 0
91
+ const offsetX = stagger && rowIndex % 2 === 1 ? xStep / 2 : 0
92
+ return (
93
+ <circle
94
+ cx={point.x + offsetX}
95
+ cy={point.y}
96
+ r={dotRadius}
97
+ fill="currentColor"
98
+ key={`${point.x}-${point.y}-${index}`}
99
+ />
100
+ )
101
+ })}
102
+ {processedMarkers.map((marker, index) => {
103
+ const rowIndex = yToRowIndex.get(marker.y) ?? 0
104
+ const offsetX = stagger && rowIndex % 2 === 1 ? xStep / 2 : 0
105
+ return (
106
+ <g key={`${marker.x}-${marker.y}-${index}`}>
107
+ <circle
108
+ cx={marker.x + offsetX}
109
+ cy={marker.y}
110
+ r={marker.size ?? dotRadius}
111
+ fill={markerColor}
112
+ />
113
+ {isEggActive && (
114
+ <circle
115
+ cx={marker.x + offsetX}
116
+ cy={marker.y}
117
+ r={(marker.size ?? dotRadius) * 4} // ~4x radius for comfortable click area
118
+ fill="transparent"
119
+ className="cursor-default pointer-events-auto"
120
+ onClick={handleEggClick}
121
+ />
122
+ )}
123
+ </g>
124
+ )
125
+ })}
126
+ </svg>
127
+ )
128
+ }