apex-ui-kit 2.1.3 → 2.3.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 (64) hide show
  1. package/dist/ApexUI-Kit/templates/AiChat/AiChat.jsx +171 -0
  2. package/dist/ApexUI-Kit/templates/AiModelPrompt/AiModelPrompt.jsx +221 -0
  3. package/dist/ApexUI-Kit/templates/AiModelPrompt/index.js +2 -0
  4. package/dist/ApexUI-Kit/templates/AiModelPrompt/models.js +15 -0
  5. package/dist/ApexUI-Kit/templates/AiPromptBox/AiPromptBox.jsx +173 -0
  6. package/dist/ApexUI-Kit/templates/AiResponse/AiResponse.jsx +172 -0
  7. package/dist/ApexUI-Kit/templates/AiThinking/AiThinking.jsx +106 -0
  8. package/dist/ApexUI-Kit/templates/AiVoiceBars/AiVoiceBars.jsx +78 -0
  9. package/dist/ApexUI-Kit/templates/AiVoiceBlob/AiVoiceBlob.jsx +105 -0
  10. package/dist/ApexUI-Kit/templates/AiVoiceOrb/AiVoiceOrb.jsx +74 -0
  11. package/dist/ApexUI-Kit/templates/AiVoiceRing/AiVoiceRing.jsx +120 -0
  12. package/dist/ApexUI-Kit/templates/AiVoiceWave/AiVoiceWave.jsx +107 -0
  13. package/dist/ApexUI-Kit/templates/AnimatedButton/AnimatedButton.jsx +251 -0
  14. package/dist/ApexUI-Kit/templates/AnimatedSwitch/AnimatedSwitch.jsx +80 -0
  15. package/dist/ApexUI-Kit/templates/AnimatedTabs/AnimatedTabs.jsx +107 -0
  16. package/dist/ApexUI-Kit/templates/AuroraBackground/AuroraBackground.jsx +48 -0
  17. package/dist/ApexUI-Kit/templates/BlurRevealText/BlurRevealText.jsx +60 -0
  18. package/dist/ApexUI-Kit/templates/BounceCards/BounceCards.jsx +53 -0
  19. package/dist/ApexUI-Kit/templates/CardSwap3D/CardSwap3D.jsx +80 -0
  20. package/dist/ApexUI-Kit/templates/Carousel3D/Carousel3D.jsx +222 -0
  21. package/dist/ApexUI-Kit/templates/CircularText/CircularText.jsx +49 -0
  22. package/dist/ApexUI-Kit/templates/ClickSpark/ClickSpark.jsx +72 -0
  23. package/dist/ApexUI-Kit/templates/Coverflow3D/Coverflow3D.jsx +108 -0
  24. package/dist/ApexUI-Kit/templates/CubeGallery/CubeGallery.jsx +166 -0
  25. package/dist/ApexUI-Kit/templates/CursorTrail/CursorTrail.jsx +200 -0
  26. package/dist/ApexUI-Kit/templates/DotGlobe/DotGlobe.jsx +289 -0
  27. package/dist/ApexUI-Kit/templates/ElasticSlider/ElasticSlider.jsx +152 -0
  28. package/dist/ApexUI-Kit/templates/ExtrudedText/ExtrudedText.jsx +78 -0
  29. package/dist/ApexUI-Kit/templates/FaqAccordion/FaqAccordion.jsx +88 -0
  30. package/dist/ApexUI-Kit/templates/FlipBook/FlipBook.jsx +191 -0
  31. package/dist/ApexUI-Kit/templates/FlipClock/FlipClock.jsx +121 -0
  32. package/dist/ApexUI-Kit/templates/FloatingNavbar/FloatingNavbar.jsx +162 -0
  33. package/dist/ApexUI-Kit/templates/Folder/Folder.jsx +101 -0
  34. package/dist/ApexUI-Kit/templates/GlitchText/GlitchText.jsx +56 -0
  35. package/dist/ApexUI-Kit/templates/GooeyNav/GooeyNav.jsx +310 -0
  36. package/dist/ApexUI-Kit/templates/HoldToConfirm/HoldToConfirm.jsx +169 -0
  37. package/dist/ApexUI-Kit/templates/HoldToConfirm/UndoNotice.jsx +58 -0
  38. package/dist/ApexUI-Kit/templates/HoloCard/HoloCard.jsx +92 -0
  39. package/dist/ApexUI-Kit/templates/ImageCompare/ImageCompare.jsx +94 -0
  40. package/dist/ApexUI-Kit/templates/IsoStack/IsoStack.jsx +86 -0
  41. package/dist/ApexUI-Kit/templates/LiquidGradient/LiquidGradient.jsx +238 -0
  42. package/dist/ApexUI-Kit/templates/LoadingScreen/LoadingScreen.jsx +805 -0
  43. package/dist/ApexUI-Kit/templates/MagnetLines/MagnetLines.jsx +67 -0
  44. package/dist/ApexUI-Kit/templates/MagneticCursor/MagneticCursor.jsx +148 -0
  45. package/dist/ApexUI-Kit/templates/PixelTransition/PixelTransition.jsx +75 -0
  46. package/dist/ApexUI-Kit/templates/PricingCards/PricingCards.jsx +115 -0
  47. package/dist/ApexUI-Kit/templates/ProfileCard/ProfileCard.jsx +213 -0
  48. package/dist/ApexUI-Kit/templates/ProximityText/ProximityText.jsx +66 -0
  49. package/dist/ApexUI-Kit/templates/RotatingText/RotatingText.jsx +50 -0
  50. package/dist/ApexUI-Kit/templates/ShinyText/ShinyText.jsx +30 -0
  51. package/dist/ApexUI-Kit/templates/SidebarNav/SidebarNav.jsx +220 -0
  52. package/dist/ApexUI-Kit/templates/SpotlightGrid/SpotlightGrid.jsx +110 -0
  53. package/dist/ApexUI-Kit/templates/StarBorder/StarBorder.jsx +33 -0
  54. package/dist/ApexUI-Kit/templates/Stepper/Stepper.jsx +129 -0
  55. package/dist/ApexUI-Kit/templates/SwipeCards/SwipeCards.jsx +123 -0
  56. package/dist/ApexUI-Kit/templates/TextRing3D/TextRing3D.jsx +73 -0
  57. package/dist/ApexUI-Kit/templates/ToastStack/ToastStack.jsx +108 -0
  58. package/dist/ApexUI-Kit/templates/ToolTip/ToolTip.jsx +5 -3
  59. package/dist/ApexUI-Kit/templates/TrueFocus/TrueFocus.jsx +64 -0
  60. package/dist/ApexUI-Kit/templates/TypewriterText/TypewriterText.jsx +70 -0
  61. package/dist/ApexUI-Kit/templates/WebGLCarousel/WebGLCarousel.jsx +326 -0
  62. package/dist/ApexUI-Kit/templates/WorldMap/WorldMap.jsx +359 -0
  63. package/dist/cli/index.js +63 -0
  64. package/package.json +1 -1
@@ -0,0 +1,66 @@
1
+ import React, { useEffect, useRef } from "react";
2
+
3
+ /*
4
+ * Proximity Text: letters swell, lift and light up as the pointer gets
5
+ * close, falling off smoothly with distance — like a magnifier sliding
6
+ * over the line. Works on touch too (drag a finger across).
7
+ */
8
+
9
+ const ProximityText = ({ text = "Hover over these letters", radius = 110, maxScale = 1.6, lift = 0.18, color = "currentColor", activeColor = "#a3e635", className = "" }) => {
10
+ const wrap = useRef(null);
11
+ const letters = useRef([]);
12
+ const pointer = useRef(null);
13
+
14
+ useEffect(() => {
15
+ let raf = 0;
16
+ const amounts = letters.current.map(() => 0);
17
+ const tick = () => {
18
+ const p = pointer.current;
19
+ letters.current.forEach((el, i) => {
20
+ if (!el) return;
21
+ let target = 0;
22
+ if (p) {
23
+ const r = el.getBoundingClientRect();
24
+ const d = Math.hypot(p.x - (r.left + r.width / 2), p.y - (r.top + r.height / 2));
25
+ target = Math.max(0, 1 - d / radius);
26
+ target = target * target * (3 - 2 * target); // smoothstep
27
+ }
28
+ amounts[i] += (target - amounts[i]) * 0.2;
29
+ const a = amounts[i];
30
+ el.style.transform = `translateY(${-a * lift}em) scale(${1 + a * (maxScale - 1)})`;
31
+ el.style.color = a > 0.02 ? `color-mix(in srgb, ${activeColor} ${Math.round(a * 100)}%, ${color})` : "";
32
+ });
33
+ raf = requestAnimationFrame(tick);
34
+ };
35
+ raf = requestAnimationFrame(tick);
36
+ return () => cancelAnimationFrame(raf);
37
+ }, [radius, maxScale, lift, color, activeColor]);
38
+
39
+ const words = text.split(" ");
40
+ let n = 0;
41
+ return (
42
+ <span
43
+ ref={wrap}
44
+ className={`inline-flex touch-none flex-wrap justify-center gap-x-[0.3em] font-bold ${className}`}
45
+ style={{ color }}
46
+ onPointerMove={(e) => (pointer.current = { x: e.clientX, y: e.clientY })}
47
+ onPointerLeave={() => (pointer.current = null)}
48
+ aria-label={text}
49
+ >
50
+ {words.map((w, wi) => (
51
+ <span key={wi} className="inline-flex" aria-hidden="true">
52
+ {Array.from(w).map((c) => {
53
+ const k = n++;
54
+ return (
55
+ <span key={k} ref={(el) => (letters.current[k] = el)} className="inline-block origin-bottom will-change-transform">
56
+ {c}
57
+ </span>
58
+ );
59
+ })}
60
+ </span>
61
+ ))}
62
+ </span>
63
+ );
64
+ };
65
+
66
+ export default ProximityText;
@@ -0,0 +1,50 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import { AnimatePresence, motion } from "framer-motion";
3
+
4
+ /*
5
+ * Rotating Text: a phrase whose last word swaps on a timer. Each letter of
6
+ * the new word springs up from below while the old one slides out, and the
7
+ * highlight pill resizes smoothly to fit.
8
+ */
9
+
10
+ const RotatingText = ({ prefix = "Build", words = ["faster", "smarter", "together", "with ApexUI"], interval = 2200, stagger = 0.025, from = "first", accentColor = "#a3e635", textColor = "#0a0a0a", className = "" }) => {
11
+ const [i, setI] = useState(0);
12
+ useEffect(() => {
13
+ const id = setInterval(() => setI((n) => (n + 1) % words.length), interval);
14
+ return () => clearInterval(id);
15
+ }, [words.length, interval]);
16
+
17
+ const word = words[i];
18
+ const chars = Array.from(word);
19
+ const delayFor = (k) => {
20
+ const n = chars.length - 1;
21
+ const order = from === "last" ? n - k : from === "center" ? Math.abs(k - n / 2) : k;
22
+ return order * stagger;
23
+ };
24
+
25
+ return (
26
+ <span className={`inline-flex flex-wrap items-center gap-x-[0.3em] font-bold ${className}`}>
27
+ {prefix && <span>{prefix}</span>}
28
+ <motion.span layout transition={{ type: "spring", stiffness: 300, damping: 30 }} className="relative inline-flex overflow-hidden rounded-[0.35em] px-[0.35em] py-[0.08em]" style={{ background: accentColor, color: textColor }}>
29
+ <AnimatePresence mode="popLayout" initial={false}>
30
+ <motion.span key={word} className="inline-flex whitespace-pre" aria-label={word}>
31
+ {chars.map((c, k) => (
32
+ <motion.span
33
+ key={k}
34
+ aria-hidden="true"
35
+ className="inline-block"
36
+ initial={{ y: "110%", opacity: 0 }}
37
+ animate={{ y: 0, opacity: 1, transition: { type: "spring", stiffness: 420, damping: 30, delay: delayFor(k) } }}
38
+ exit={{ y: "-120%", opacity: 0, transition: { duration: 0.22, ease: "easeIn", delay: delayFor(k) * 0.6 } }}
39
+ >
40
+ {c}
41
+ </motion.span>
42
+ ))}
43
+ </motion.span>
44
+ </AnimatePresence>
45
+ </motion.span>
46
+ </span>
47
+ );
48
+ };
49
+
50
+ export default RotatingText;
@@ -0,0 +1,30 @@
1
+ import React from "react";
2
+ import { motion, useReducedMotion } from "framer-motion";
3
+
4
+ /*
5
+ * Shiny Text: a bright band of light sweeps across the text on a loop, like
6
+ * light catching metal. The text keeps its own color between sweeps.
7
+ */
8
+
9
+ const ShinyText = ({ text = "Shipping soon — join the waitlist", as = "span", speed = 2.8, color = "#71717a", shineColor = "#ffffff", spread = 22, disabled = false, className = "" }) => {
10
+ const reduced = useReducedMotion();
11
+ const Tag = motion[as] ?? motion.span;
12
+ const still = disabled || reduced;
13
+ return (
14
+ <Tag
15
+ className={`inline-block bg-clip-text text-transparent ${className}`}
16
+ style={{
17
+ backgroundImage: `linear-gradient(110deg, ${color} ${50 - spread}%, ${shineColor} 50%, ${color} ${50 + spread}%)`,
18
+ backgroundSize: "250% 100%",
19
+ WebkitBackgroundClip: "text",
20
+ }}
21
+ initial={{ backgroundPosition: "100% 0" }}
22
+ animate={still ? { backgroundPosition: "100% 0" } : { backgroundPosition: ["100% 0", "0% 0"] }}
23
+ transition={still ? undefined : { duration: speed, ease: "linear", repeat: Infinity, repeatDelay: speed * 0.25 }}
24
+ >
25
+ {text}
26
+ </Tag>
27
+ );
28
+ };
29
+
30
+ export default ShinyText;
@@ -0,0 +1,220 @@
1
+ import React, { useId, useRef, useState } from "react";
2
+ import { AnimatePresence, LayoutGroup, motion } from "framer-motion";
3
+ import { BarChart3, Bell, ChevronDown, FolderKanban, Home, Inbox, LifeBuoy, PanelLeftClose, PanelLeftOpen, Settings, Users } from "lucide-react";
4
+
5
+ /*
6
+ * Sidebar Nav: an app sidebar that collapses to an icon rail. The active
7
+ * pill slides between items, groups open and close, badges show counts, and
8
+ * the rail shows each name as a tooltip.
9
+ */
10
+
11
+ const DEFAULT_SECTIONS = [
12
+ {
13
+ title: "Main",
14
+ items: [
15
+ { id: "home", label: "Dashboard", icon: Home },
16
+ { id: "inbox", label: "Inbox", icon: Inbox, badge: 4 },
17
+ { id: "analytics", label: "Analytics", icon: BarChart3 },
18
+ {
19
+ id: "projects",
20
+ label: "Projects",
21
+ icon: FolderKanban,
22
+ children: [
23
+ { id: "apex", label: "ApexUI" },
24
+ { id: "website", label: "Website" },
25
+ { id: "mobile", label: "Mobile app" },
26
+ ],
27
+ },
28
+ { id: "team", label: "Team", icon: Users },
29
+ ],
30
+ },
31
+ {
32
+ title: "Other",
33
+ items: [
34
+ { id: "notifications", label: "Notifications", icon: Bell, badge: 12 },
35
+ { id: "settings", label: "Settings", icon: Settings },
36
+ { id: "help", label: "Help", icon: LifeBuoy },
37
+ ],
38
+ },
39
+ ];
40
+
41
+ const THEMES = {
42
+ dark: {
43
+ panel: "bg-neutral-950 text-white border-white/10",
44
+ muted: "text-white/45",
45
+ item: "text-white/70 hover:text-white hover:bg-white/[0.05]",
46
+ pill: "bg-white/[0.08]",
47
+ tip: "bg-white text-neutral-900",
48
+ },
49
+ light: {
50
+ panel: "bg-white text-neutral-900 border-black/10",
51
+ muted: "text-neutral-400",
52
+ item: "text-neutral-600 hover:text-neutral-900 hover:bg-black/[0.04]",
53
+ pill: "bg-black/[0.06]",
54
+ tip: "bg-neutral-900 text-white",
55
+ },
56
+ };
57
+
58
+ const SidebarNav = ({
59
+ sections = DEFAULT_SECTIONS,
60
+ brand = { name: "Apex", logo: null },
61
+ user = { name: "Mohan Dalei", email: "mohan@apex-ui.in", avatar: "https://ik.imagekit.io/ny6yinyut/apexui/assets/captainamerica.png?tr=f-auto,q-80,w-96" },
62
+ defaultActive = "home",
63
+ defaultCollapsed = false,
64
+ accentColor = "#a3e635",
65
+ theme = "dark",
66
+ onSelect,
67
+ className = "",
68
+ }) => {
69
+ const t = THEMES[theme] ?? THEMES.dark;
70
+ const pillId = useId();
71
+ const [collapsed, setCollapsed] = useState(defaultCollapsed);
72
+ const [active, setActive] = useState(defaultActive);
73
+ const [open, setOpen] = useState(() => new Set(["projects"]));
74
+ const [tip, setTip] = useState(null);
75
+ const asideRef = useRef(null);
76
+
77
+ const select = (item) => {
78
+ setActive(item.id);
79
+ onSelect?.(item);
80
+ };
81
+ const toggle = (id) =>
82
+ setOpen((prev) => {
83
+ const next = new Set(prev);
84
+ if (next.has(id)) next.delete(id);
85
+ else next.add(id);
86
+ return next;
87
+ });
88
+
89
+ // A render function (not a component) so rows keep their identity between renders.
90
+ const renderRow = (item, depth = 0) => {
91
+ const Icon = item.icon;
92
+ const hasChildren = Boolean(item.children?.length);
93
+ const isOpen = open.has(item.id);
94
+ const isActive = active === item.id || (hasChildren && collapsed && item.children.some((c) => c.id === active));
95
+ return (
96
+ <li key={item.id}>
97
+ <button
98
+ type="button"
99
+ onClick={() => (hasChildren && !collapsed ? toggle(item.id) : select(hasChildren ? item.children[0] : item))}
100
+ onMouseEnter={(e) => {
101
+ if (!collapsed) return;
102
+ const r = e.currentTarget.getBoundingClientRect();
103
+ setTip({ label: item.label, top: r.top + r.height / 2 - asideRef.current.getBoundingClientRect().top });
104
+ }}
105
+ onMouseLeave={() => setTip(null)}
106
+ aria-label={item.label}
107
+ aria-current={isActive ? "page" : undefined}
108
+ aria-expanded={hasChildren && !collapsed ? isOpen : undefined}
109
+ className={`relative flex h-9 w-full items-center gap-3 rounded-lg text-[0.86rem] font-medium transition-colors ${collapsed ? "justify-center px-0" : "px-2.5"} ${isActive ? "" : t.item}`}
110
+ style={{ paddingLeft: !collapsed && depth ? "2.6rem" : undefined }}
111
+ >
112
+ {isActive && (
113
+ <motion.span layoutId={`${pillId}-pill`} className={`absolute inset-0 rounded-lg ${t.pill}`} transition={{ type: "spring", stiffness: 500, damping: 38 }}>
114
+ <span className="absolute inset-y-2 left-0 w-[3px] rounded-full" style={{ background: accentColor }} />
115
+ </motion.span>
116
+ )}
117
+ {Icon && <Icon className="relative h-[1.1rem] w-[1.1rem] shrink-0" style={isActive ? { color: accentColor } : undefined} />}
118
+ {!collapsed && <span className="relative min-w-0 flex-1 truncate text-left">{item.label}</span>}
119
+ {!collapsed && item.badge != null && (
120
+ <span className="relative rounded-full px-1.5 py-px text-[0.68rem] font-semibold text-black" style={{ background: accentColor }}>
121
+ {item.badge}
122
+ </span>
123
+ )}
124
+ {collapsed && item.badge != null && <span className="absolute right-2 top-1.5 h-1.5 w-1.5 rounded-full" style={{ background: accentColor }} />}
125
+ {!collapsed && hasChildren && <ChevronDown className={`relative h-4 w-4 opacity-60 transition-transform ${isOpen ? "rotate-180" : ""}`} />}
126
+ </button>
127
+ <AnimatePresence initial={false}>
128
+ {hasChildren && isOpen && !collapsed && (
129
+ <motion.ul
130
+ initial={{ height: 0, opacity: 0 }}
131
+ animate={{ height: "auto", opacity: 1 }}
132
+ exit={{ height: 0, opacity: 0 }}
133
+ transition={{ duration: 0.22, ease: [0.22, 1, 0.36, 1] }}
134
+ className="relative mt-0.5 space-y-0.5 overflow-hidden"
135
+ >
136
+ <span aria-hidden="true" className={`absolute bottom-2 left-[1.3rem] top-1 w-px ${theme === "light" ? "bg-black/10" : "bg-white/10"}`} />
137
+ {item.children.map((child) => renderRow(child, depth + 1))}
138
+ </motion.ul>
139
+ )}
140
+ </AnimatePresence>
141
+ </li>
142
+ );
143
+ };
144
+
145
+ return (
146
+ <motion.aside
147
+ ref={asideRef}
148
+ aria-label="Sidebar"
149
+ animate={{ width: collapsed ? "4.25rem" : "15.5rem" }}
150
+ transition={{ type: "spring", stiffness: 400, damping: 40 }}
151
+ className={`relative flex h-full shrink-0 flex-col border-r ${t.panel} ${className}`}
152
+ >
153
+ <div className={`flex items-center gap-2.5 px-3.5 ${collapsed ? "flex-col justify-center gap-2 py-3" : "h-14"}`}>
154
+ <span className="grid h-8 w-8 shrink-0 place-items-center rounded-lg font-bold text-black" style={{ background: accentColor }}>
155
+ {brand.logo ? <img src={brand.logo} alt="" className="h-5 w-5" /> : brand.name?.[0]}
156
+ </span>
157
+ {!collapsed && <span className="truncate text-[0.95rem] font-semibold">{brand.name}</span>}
158
+ <button
159
+ type="button"
160
+ aria-label={collapsed ? "Expand sidebar" : "Collapse sidebar"}
161
+ onClick={() => {
162
+ setCollapsed((c) => !c);
163
+ setTip(null);
164
+ }}
165
+ className={`grid h-8 w-8 place-items-center rounded-lg ${t.item} ${collapsed ? "" : "ml-auto"}`}
166
+ >
167
+ {collapsed ? <PanelLeftOpen className="h-4 w-4" /> : <PanelLeftClose className="h-4 w-4" />}
168
+ </button>
169
+ </div>
170
+
171
+ <LayoutGroup id={pillId}>
172
+ <nav className="relative flex-1 overflow-y-auto overflow-x-hidden px-2.5 pb-3" style={{ scrollbarWidth: "thin", scrollbarColor: `${theme === "light" ? "rgba(0,0,0,.15)" : "rgba(255,255,255,.15)"} transparent` }}>
173
+ {sections.map((section) => (
174
+ <div key={section.title} className="mt-3 first:mt-1">
175
+ {!collapsed ? (
176
+ <p className={`mb-1.5 px-2.5 text-[0.68rem] font-semibold uppercase tracking-[0.14em] ${t.muted}`}>{section.title}</p>
177
+ ) : (
178
+ <span aria-hidden="true" className={`mx-auto mb-2 block h-px w-6 ${theme === "light" ? "bg-black/10" : "bg-white/10"}`} />
179
+ )}
180
+ <ul className="space-y-0.5">
181
+ {section.items.map((item) => renderRow(item))}
182
+ </ul>
183
+ </div>
184
+ ))}
185
+ </nav>
186
+ </LayoutGroup>
187
+
188
+ <AnimatePresence>
189
+ {collapsed && tip && (
190
+ <motion.span
191
+ key={tip.label}
192
+ role="tooltip"
193
+ initial={{ opacity: 0, x: -4 }}
194
+ animate={{ opacity: 1, x: 0 }}
195
+ exit={{ opacity: 0 }}
196
+ transition={{ duration: 0.12 }}
197
+ className={`pointer-events-none absolute left-[4.6rem] z-50 whitespace-nowrap rounded-md px-2 py-1 text-[0.75rem] font-medium shadow-lg ${t.tip}`}
198
+ style={{ top: tip.top, translateY: "-50%" }}
199
+ >
200
+ {tip.label}
201
+ </motion.span>
202
+ )}
203
+ </AnimatePresence>
204
+
205
+ {user && (
206
+ <div className={`flex items-center gap-3 border-t p-3 ${theme === "light" ? "border-black/10" : "border-white/10"} ${collapsed ? "justify-center" : ""}`}>
207
+ <img src={user.avatar} alt="" className="h-9 w-9 shrink-0 rounded-full object-cover ring-2" style={{ "--tw-ring-color": accentColor }} />
208
+ {!collapsed && (
209
+ <div className="min-w-0">
210
+ <p className="truncate text-[0.85rem] font-semibold">{user.name}</p>
211
+ <p className={`truncate text-[0.75rem] ${t.muted}`}>{user.email}</p>
212
+ </div>
213
+ )}
214
+ </div>
215
+ )}
216
+ </motion.aside>
217
+ );
218
+ };
219
+
220
+ export default SidebarNav;
@@ -0,0 +1,110 @@
1
+ import React, { useEffect, useRef, useState } from "react";
2
+ import { motion } from "framer-motion";
3
+ import { BarChart3, Globe2, Lock, Rocket, Sparkles, Zap } from "lucide-react";
4
+
5
+ /*
6
+ * Spotlight Grid: a bento grid of cards sharing one pointer spotlight. The
7
+ * light lands on every card's border and surface at once, so moving across
8
+ * the grid feels like sweeping a torch over it.
9
+ */
10
+
11
+ const DEFAULT_ITEMS = [
12
+ { title: "Lightning fast", description: "Ships as tiny chunks and loads only what a page needs.", icon: Zap, span: 2 },
13
+ { title: "Secure by default", description: "No tracking, no cookies, nothing to configure.", icon: Lock },
14
+ { title: "Global edge", description: "Served from 300+ cities close to your users.", icon: Globe2 },
15
+ { title: "Insights", description: "Real-time numbers for every component you add.", icon: BarChart3 },
16
+ { title: "Launch in minutes", description: "One CLI command copies the code into your project.", icon: Rocket },
17
+ { title: "Delightful motion", description: "Spring physics and GPU-friendly transforms everywhere.", icon: Sparkles, span: 2 },
18
+ ];
19
+
20
+ const THEMES = {
21
+ dark: { card: "bg-neutral-950 text-white", base: "rgba(255,255,255,0.08)", muted: "text-white/55", icon: "bg-white/[0.06] text-white", edge: (c) => c, glow: 12 },
22
+ // On white, a bright accent washes out: darken the border light and strengthen the glow.
23
+ light: { card: "bg-white text-neutral-900 shadow-[0_1px_2px_rgba(0,0,0,0.04)]", base: "rgba(0,0,0,0.09)", muted: "text-neutral-500", icon: "bg-black/[0.04] text-neutral-900", edge: (c) => `color-mix(in srgb, ${c} 70%, #000)`, glow: 24 },
24
+ };
25
+
26
+ // "auto" follows the visitor's light / dark system setting.
27
+ function useResolvedTheme(theme) {
28
+ const [system, setSystem] = useState("dark");
29
+ useEffect(() => {
30
+ if (theme !== "auto") return undefined;
31
+ const mq = window.matchMedia("(prefers-color-scheme: dark)");
32
+ const update = () => setSystem(mq.matches ? "dark" : "light");
33
+ update();
34
+ mq.addEventListener("change", update);
35
+ return () => mq.removeEventListener("change", update);
36
+ }, [theme]);
37
+ return theme === "auto" ? system : theme;
38
+ }
39
+
40
+ const SpotlightGrid = ({ items = DEFAULT_ITEMS, color = "#a3e635", size = 360, theme = "auto", minCardWidth = 12, className = "" }) => {
41
+ const ref = useRef(null);
42
+ const t = THEMES[useResolvedTheme(theme)] ?? THEMES.dark;
43
+
44
+ // Every card gets the pointer position in its own coordinates.
45
+ const onMove = (e) => {
46
+ ref.current.querySelectorAll("[data-spot]").forEach((card) => {
47
+ const r = card.getBoundingClientRect();
48
+ card.style.setProperty("--mx", `${e.clientX - r.left}px`);
49
+ card.style.setProperty("--my", `${e.clientY - r.top}px`);
50
+ card.style.setProperty("--spot", "1");
51
+ });
52
+ };
53
+ const onLeave = () => ref.current.querySelectorAll("[data-spot]").forEach((card) => card.style.setProperty("--spot", "0"));
54
+
55
+ return (
56
+ <div
57
+ ref={ref}
58
+ onPointerMove={onMove}
59
+ onPointerLeave={onLeave}
60
+ className={`grid w-full gap-3 ${className}`}
61
+ style={{ gridTemplateColumns: `repeat(auto-fit, minmax(min(100%, ${minCardWidth}rem), 1fr))` }}
62
+ >
63
+ {items.map((item, i) => {
64
+ const Icon = item.icon;
65
+ return (
66
+ <motion.article
67
+ key={item.title ?? i}
68
+ data-spot=""
69
+ initial={{ opacity: 0, y: 12 }}
70
+ whileInView={{ opacity: 1, y: 0 }}
71
+ viewport={{ once: true }}
72
+ transition={{ duration: 0.45, delay: i * 0.05 }}
73
+ className={`relative overflow-hidden rounded-2xl p-5 ${item.span > 1 ? "sm:col-span-2" : ""} ${t.card}`}
74
+ style={{ "--spot": 0, boxShadow: `inset 0 0 0 1px ${t.base}` }}
75
+ >
76
+ {/* Border light: a ring-shaped mask over a radial gradient. */}
77
+ <span
78
+ aria-hidden="true"
79
+ className="pointer-events-none absolute inset-0 rounded-[inherit] p-px transition-opacity duration-300"
80
+ style={{
81
+ opacity: "var(--spot)",
82
+ background: `radial-gradient(${size}px circle at var(--mx) var(--my), ${t.edge(color)}, transparent 45%)`,
83
+ WebkitMask: "linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0)",
84
+ WebkitMaskComposite: "xor",
85
+ mask: "linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0)",
86
+ }}
87
+ />
88
+ {/* Surface glow. */}
89
+ <span
90
+ aria-hidden="true"
91
+ className="pointer-events-none absolute inset-0 transition-opacity duration-300"
92
+ style={{ opacity: "var(--spot)", background: `radial-gradient(${size * 1.2}px circle at var(--mx) var(--my), color-mix(in srgb, ${color} ${t.glow}%, transparent), transparent 45%)` }}
93
+ />
94
+ <div className="relative">
95
+ {Icon && (
96
+ <span className={`mb-4 grid h-10 w-10 place-items-center rounded-xl ${t.icon}`}>
97
+ <Icon className="h-5 w-5" />
98
+ </span>
99
+ )}
100
+ <h3 className="text-[1rem] font-semibold">{item.title}</h3>
101
+ {item.description && <p className={`mt-1.5 text-[0.85rem] leading-relaxed ${t.muted}`}>{item.description}</p>}
102
+ </div>
103
+ </motion.article>
104
+ );
105
+ })}
106
+ </div>
107
+ );
108
+ };
109
+
110
+ export default SpotlightGrid;
@@ -0,0 +1,33 @@
1
+ import React from "react";
2
+ import { motion, useReducedMotion } from "framer-motion";
3
+
4
+ /*
5
+ * Star Border: two soft points of light travel along the top and bottom
6
+ * edges of a button in opposite directions, like stars sliding around its
7
+ * rim. Renders a <button> by default; pass `as="a"` for a link.
8
+ */
9
+
10
+ const StarBorder = ({ as = "button", color = "#a3e635", speed = 5, thickness = 1.5, theme = "dark", children = "Get started", className = "", ...rest }) => {
11
+ const reduced = useReducedMotion();
12
+ const Tag = as;
13
+ const dark = theme !== "light";
14
+ const star = (dir) => ({
15
+ background: `radial-gradient(circle, ${color}, transparent 10%)`,
16
+ animate: reduced ? undefined : { x: dir > 0 ? ["-35%", "35%"] : ["35%", "-35%"], opacity: [0, 1, 1, 0] },
17
+ transition: { duration: speed, ease: "linear", repeat: Infinity, times: [0, 0.15, 0.85, 1] },
18
+ });
19
+ const top = star(-1);
20
+ const bottom = star(1);
21
+
22
+ return (
23
+ <Tag className={`group relative inline-block overflow-hidden rounded-[1.1rem] ${className}`} style={{ padding: `${thickness}px 0` }} {...(as === "button" ? { type: "button" } : {})} {...rest}>
24
+ <motion.span aria-hidden="true" className="pointer-events-none absolute -bottom-[11px] left-[-100%] h-1/2 w-[300%] opacity-70" style={{ background: bottom.background }} animate={bottom.animate} transition={bottom.transition} />
25
+ <motion.span aria-hidden="true" className="pointer-events-none absolute -top-[10px] left-[-100%] h-1/2 w-[300%] opacity-70" style={{ background: top.background }} animate={top.animate} transition={top.transition} />
26
+ <span className={`relative z-[1] block rounded-[1.1rem] border px-7 py-3.5 text-center text-[0.95rem] font-semibold transition-colors ${dark ? "border-white/10 bg-gradient-to-b from-neutral-900 to-black text-white group-hover:border-white/20" : "border-black/10 bg-gradient-to-b from-white to-neutral-100 text-neutral-900 group-hover:border-black/20"}`}>
27
+ {children}
28
+ </span>
29
+ </Tag>
30
+ );
31
+ };
32
+
33
+ export default StarBorder;
@@ -0,0 +1,129 @@
1
+ import React, { useId, useState } from "react";
2
+ import { AnimatePresence, motion } from "framer-motion";
3
+ import { Check } from "lucide-react";
4
+
5
+ /*
6
+ * Stepper: a multi-step flow. The indicators fill and tick as you move on,
7
+ * the connector lines fill in between, and each step's content slides in
8
+ * from the side you are heading while the panel height follows smoothly.
9
+ */
10
+
11
+ const DEFAULT_STEPS = [
12
+ { title: "Account", content: <p>Welcome! Pick a username to get started — you can change it later.</p> },
13
+ { title: "Profile", content: <p>Add a photo and a short bio so people know who you are.</p> },
14
+ { title: "Theme", content: <p>Choose light, dark or follow your system. Your choice syncs everywhere.</p> },
15
+ { title: "Done", content: <p>All set. Press Complete to finish onboarding.</p> },
16
+ ];
17
+
18
+ const variants = {
19
+ enter: (dir) => ({ x: dir > 0 ? "40%" : "-40%", opacity: 0 }),
20
+ center: { x: 0, opacity: 1 },
21
+ exit: (dir) => ({ x: dir > 0 ? "-40%" : "40%", opacity: 0 }),
22
+ };
23
+
24
+ const Stepper = ({ steps = DEFAULT_STEPS, initialStep = 0, onStepChange, onComplete, accentColor = "#a3e635", theme = "dark", className = "" }) => {
25
+ const id = useId();
26
+ const [step, setStep] = useState(initialStep);
27
+ const [dir, setDir] = useState(1);
28
+ const [done, setDone] = useState(false);
29
+ const dark = theme !== "light";
30
+ const last = step === steps.length - 1;
31
+
32
+ const go = (to) => {
33
+ if (to < 0 || to >= steps.length) return;
34
+ setDir(to > step ? 1 : -1);
35
+ setStep(to);
36
+ onStepChange?.(to);
37
+ };
38
+
39
+ return (
40
+ <div className={`w-full max-w-md rounded-[1.6rem] border p-6 ${dark ? "border-white/10 bg-neutral-950 text-white" : "border-black/10 bg-white text-neutral-900"} ${className}`}>
41
+ <div className="flex items-center">
42
+ {steps.map((s, i) => {
43
+ const complete = done || i < step;
44
+ const current = !done && i === step;
45
+ return (
46
+ <React.Fragment key={i}>
47
+ <button type="button" onClick={() => !done && go(i)} aria-label={`Step ${i + 1}: ${s.title}`} aria-current={current ? "step" : undefined} className="relative grid h-9 w-9 shrink-0 place-items-center rounded-full text-sm font-semibold">
48
+ <motion.span
49
+ className="absolute inset-0 rounded-full"
50
+ initial={false}
51
+ animate={{ backgroundColor: complete || current ? accentColor : dark ? "rgba(255,255,255,0.08)" : "rgba(0,0,0,0.06)", scale: current ? 1.08 : 1 }}
52
+ transition={{ duration: 0.3 }}
53
+ />
54
+ {current && <motion.span layoutId={`${id}-ring`} className="absolute -inset-1 rounded-full border-2" style={{ borderColor: `color-mix(in srgb, ${accentColor} 45%, transparent)` }} />}
55
+ <span className={`relative ${complete || current ? "text-black" : "opacity-60"}`}>
56
+ {complete ? (
57
+ <motion.span initial={{ scale: 0, rotate: -45 }} animate={{ scale: 1, rotate: 0 }} className="block">
58
+ <Check className="h-4 w-4" strokeWidth={3} />
59
+ </motion.span>
60
+ ) : (
61
+ i + 1
62
+ )}
63
+ </span>
64
+ </button>
65
+ {i < steps.length - 1 && (
66
+ <div className={`mx-2 h-1 flex-1 overflow-hidden rounded-full ${dark ? "bg-white/[0.08]" : "bg-black/[0.06]"}`}>
67
+ <motion.div className="h-full rounded-full" style={{ background: accentColor }} initial={false} animate={{ width: done || i < step ? "100%" : "0%" }} transition={{ duration: 0.4, ease: [0.22, 1, 0.36, 1] }} />
68
+ </div>
69
+ )}
70
+ </React.Fragment>
71
+ );
72
+ })}
73
+ </div>
74
+
75
+ <motion.div layout className="relative mt-6 overflow-hidden" transition={{ type: "spring", stiffness: 300, damping: 32 }}>
76
+ <AnimatePresence mode="popLayout" initial={false} custom={dir}>
77
+ <motion.div key={done ? "done" : step} custom={dir} variants={variants} initial="enter" animate="center" exit="exit" transition={{ type: "spring", stiffness: 300, damping: 32 }} className="pb-1">
78
+ {done ? (
79
+ <div className="py-4 text-center">
80
+ <p className="text-lg font-semibold">You are all set 🎉</p>
81
+ <p className={`mt-1 text-sm ${dark ? "text-white/55" : "text-neutral-500"}`}>Every step is complete.</p>
82
+ </div>
83
+ ) : (
84
+ <>
85
+ <p className="text-[0.72rem] font-semibold uppercase tracking-[0.18em]" style={{ color: accentColor }}>
86
+ Step {step + 1} of {steps.length}
87
+ </p>
88
+ <h3 className="mt-1 text-xl font-semibold">{steps[step].title}</h3>
89
+ <div className={`mt-2 text-[0.92rem] leading-relaxed ${dark ? "text-white/65" : "text-neutral-600"}`}>{steps[step].content}</div>
90
+ </>
91
+ )}
92
+ </motion.div>
93
+ </AnimatePresence>
94
+ </motion.div>
95
+
96
+ <div className="mt-6 flex items-center justify-between">
97
+ {done ? (
98
+ <button type="button" onClick={() => {
99
+ setDone(false);
100
+ go(0);
101
+ }} className={`rounded-full px-4 py-2 text-sm font-medium ${dark ? "bg-white/[0.06] hover:bg-white/10" : "bg-black/[0.05] hover:bg-black/[0.08]"}`}>
102
+ Start over
103
+ </button>
104
+ ) : (
105
+ <>
106
+ <button type="button" onClick={() => go(step - 1)} disabled={step === 0} className={`rounded-full px-4 py-2 text-sm font-medium transition disabled:pointer-events-none disabled:opacity-0 ${dark ? "text-white/70 hover:text-white" : "text-neutral-500 hover:text-neutral-900"}`}>
107
+ Back
108
+ </button>
109
+ <motion.button
110
+ whileTap={{ scale: 0.95 }}
111
+ type="button"
112
+ onClick={() => {
113
+ if (!last) return go(step + 1);
114
+ setDone(true);
115
+ onComplete?.();
116
+ }}
117
+ className="rounded-full px-5 py-2 text-sm font-semibold text-black"
118
+ style={{ background: accentColor }}
119
+ >
120
+ {last ? "Complete" : "Continue"}
121
+ </motion.button>
122
+ </>
123
+ )}
124
+ </div>
125
+ </div>
126
+ );
127
+ };
128
+
129
+ export default Stepper;