gradient-forge 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 (56) hide show
  1. package/.eslintrc.json +3 -0
  2. package/.github/FUNDING.yml +2 -0
  3. package/README.md +140 -0
  4. package/app/docs/page.tsx +417 -0
  5. package/app/gallery/page.tsx +398 -0
  6. package/app/globals.css +1155 -0
  7. package/app/layout.tsx +36 -0
  8. package/app/page.tsx +600 -0
  9. package/app/showcase/page.tsx +730 -0
  10. package/app/studio/page.tsx +1310 -0
  11. package/cli/index.mjs +1141 -0
  12. package/cli/templates/theme-context.tsx +120 -0
  13. package/cli/templates/theme-engine.ts +237 -0
  14. package/cli/templates/themes.css +512 -0
  15. package/components/site/component-showcase.tsx +623 -0
  16. package/components/site/site-data.ts +103 -0
  17. package/components/site/site-header.tsx +270 -0
  18. package/components/templates/blog.tsx +198 -0
  19. package/components/templates/components-showcase.tsx +298 -0
  20. package/components/templates/dashboard.tsx +246 -0
  21. package/components/templates/ecommerce.tsx +199 -0
  22. package/components/templates/mail.tsx +275 -0
  23. package/components/templates/saas-landing.tsx +169 -0
  24. package/components/theme/studio-code-panel.tsx +485 -0
  25. package/components/theme/theme-context.tsx +120 -0
  26. package/components/theme/theme-engine.ts +237 -0
  27. package/components/theme/theme-exporter.tsx +369 -0
  28. package/components/theme/theme-panel.tsx +268 -0
  29. package/components/theme/token-export-utils.ts +1211 -0
  30. package/components/ui/animated.tsx +55 -0
  31. package/components/ui/avatar.tsx +38 -0
  32. package/components/ui/badge.tsx +32 -0
  33. package/components/ui/button.tsx +65 -0
  34. package/components/ui/card.tsx +56 -0
  35. package/components/ui/checkbox.tsx +19 -0
  36. package/components/ui/command-palette.tsx +245 -0
  37. package/components/ui/gsap-animated.tsx +436 -0
  38. package/components/ui/input.tsx +17 -0
  39. package/components/ui/select.tsx +176 -0
  40. package/components/ui/skeleton.tsx +102 -0
  41. package/components/ui/switch.tsx +43 -0
  42. package/components/ui/tabs.tsx +115 -0
  43. package/components/ui/toast.tsx +119 -0
  44. package/gradient-forge/theme-context.tsx +119 -0
  45. package/gradient-forge/theme-engine.ts +236 -0
  46. package/gradient-forge/themes.css +556 -0
  47. package/lib/animations.ts +50 -0
  48. package/lib/gsap.ts +426 -0
  49. package/lib/utils.ts +6 -0
  50. package/next-env.d.ts +6 -0
  51. package/next.config.mjs +6 -0
  52. package/package.json +53 -0
  53. package/postcss.config.mjs +5 -0
  54. package/tailwind.config.ts +15 -0
  55. package/tsconfig.json +43 -0
  56. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,55 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { motion } from "framer-motion";
5
+ import { fadeInUp, staggerContainer, staggerItem } from "@/lib/animations";
6
+
7
+ interface AnimatedSectionProps {
8
+ children: React.ReactNode;
9
+ className?: string;
10
+ }
11
+
12
+ export function AnimatedSection({ children, className = "" }: AnimatedSectionProps) {
13
+ return (
14
+ <motion.section
15
+ initial="hidden"
16
+ whileInView="visible"
17
+ viewport={{ once: true, margin: "-100px" }}
18
+ variants={fadeInUp}
19
+ className={className}
20
+ >
21
+ {children}
22
+ </motion.section>
23
+ );
24
+ }
25
+
26
+ interface StaggerContainerProps {
27
+ children: React.ReactNode;
28
+ className?: string;
29
+ }
30
+
31
+ export function StaggerContainer({ children, className = "" }: StaggerContainerProps) {
32
+ return (
33
+ <motion.div
34
+ initial="hidden"
35
+ animate="visible"
36
+ variants={staggerContainer}
37
+ className={className}
38
+ >
39
+ {children}
40
+ </motion.div>
41
+ );
42
+ }
43
+
44
+ interface StaggerItemProps {
45
+ children: React.ReactNode;
46
+ className?: string;
47
+ }
48
+
49
+ export function StaggerItem({ children, className = "" }: StaggerItemProps) {
50
+ return (
51
+ <motion.div variants={staggerItem} className={className}>
52
+ {children}
53
+ </motion.div>
54
+ );
55
+ }
@@ -0,0 +1,38 @@
1
+ import * as React from "react";
2
+ import { cn } from "@/lib/utils";
3
+
4
+ interface AvatarProps {
5
+ className?: string;
6
+ children?: React.ReactNode;
7
+ }
8
+
9
+ export function Avatar({ className, children }: AvatarProps) {
10
+ return (
11
+ <div
12
+ className={cn(
13
+ "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
14
+ className
15
+ )}
16
+ >
17
+ {children}
18
+ </div>
19
+ );
20
+ }
21
+
22
+ interface AvatarFallbackProps {
23
+ className?: string;
24
+ children?: React.ReactNode;
25
+ }
26
+
27
+ export function AvatarFallback({ className, children }: AvatarFallbackProps) {
28
+ return (
29
+ <div
30
+ className={cn(
31
+ "flex h-full w-full items-center justify-center rounded-full bg-muted",
32
+ className
33
+ )}
34
+ >
35
+ {children}
36
+ </div>
37
+ );
38
+ }
@@ -0,0 +1,32 @@
1
+ import * as React from "react";
2
+ import { cn } from "@/lib/utils";
3
+
4
+ type BadgeVariant = "default" | "outline" | "glass";
5
+
6
+ type BadgeProps = React.HTMLAttributes<HTMLDivElement> & {
7
+ variant?: BadgeVariant;
8
+ };
9
+
10
+ const variantClasses: Record<BadgeVariant, string> = {
11
+ default: "bg-primary/15 text-primary border border-primary/30",
12
+ outline: "border border-border/60 text-foreground",
13
+ glass:
14
+ "bg-background/30 border border-border/30 backdrop-blur text-foreground",
15
+ };
16
+
17
+ export function Badge({
18
+ className,
19
+ variant = "default",
20
+ ...props
21
+ }: BadgeProps) {
22
+ return (
23
+ <div
24
+ className={cn(
25
+ "inline-flex items-center gap-2 rounded-full px-3 py-1 text-xs font-semibold uppercase tracking-[0.2em]",
26
+ variantClasses[variant],
27
+ className,
28
+ )}
29
+ {...props}
30
+ />
31
+ );
32
+ }
@@ -0,0 +1,65 @@
1
+ import * as React from "react";
2
+ import { cn } from "@/lib/utils";
3
+
4
+ type ButtonVariant =
5
+ | "default"
6
+ | "secondary"
7
+ | "ghost"
8
+ | "outline"
9
+ | "glow";
10
+
11
+ type ButtonSize = "sm" | "md" | "lg";
12
+
13
+ type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
14
+ variant?: ButtonVariant;
15
+ size?: ButtonSize;
16
+ asChild?: boolean;
17
+ };
18
+
19
+ const variantClasses: Record<ButtonVariant, string> = {
20
+ default:
21
+ "bg-primary text-primary-foreground shadow-sm hover:shadow-md hover:translate-y-[-1px]",
22
+ secondary:
23
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
24
+ ghost: "bg-transparent hover:bg-muted/70",
25
+ outline:
26
+ "border border-border/70 bg-transparent hover:bg-muted/70 text-foreground",
27
+ glow:
28
+ "bg-primary text-primary-foreground shadow-[0_0_25px_hsl(var(--primary)_/_0.35)] hover:shadow-[0_0_35px_hsl(var(--primary)_/_0.55)]",
29
+ };
30
+
31
+ const sizeClasses: Record<ButtonSize, string> = {
32
+ sm: "h-8 px-3 text-xs",
33
+ md: "h-10 px-4 text-sm",
34
+ lg: "h-12 px-5 text-base",
35
+ };
36
+
37
+ export function Button({
38
+ className,
39
+ variant = "default",
40
+ size = "md",
41
+ asChild = false,
42
+ children,
43
+ ...props
44
+ }: ButtonProps) {
45
+ const classes = cn(
46
+ "inline-flex items-center justify-center gap-2 rounded-full font-semibold transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50",
47
+ variantClasses[variant],
48
+ sizeClasses[size],
49
+ className,
50
+ );
51
+
52
+ if (asChild && React.isValidElement(children)) {
53
+ const child = children as React.ReactElement<{ className?: string }>;
54
+ return React.cloneElement(child, {
55
+ className: cn(classes, child.props.className),
56
+ ...(props as Record<string, unknown>),
57
+ });
58
+ }
59
+
60
+ return (
61
+ <button className={classes} {...props}>
62
+ {children}
63
+ </button>
64
+ );
65
+ }
@@ -0,0 +1,56 @@
1
+ import * as React from "react";
2
+ import { cn } from "@/lib/utils";
3
+
4
+ export function Card({
5
+ className,
6
+ ...props
7
+ }: React.HTMLAttributes<HTMLDivElement>) {
8
+ return (
9
+ <div
10
+ className={cn(
11
+ "rounded-3xl border border-border/40 bg-card/70 text-card-foreground shadow-[0_20px_80px_-60px_rgba(0,0,0,0.5)]",
12
+ className,
13
+ )}
14
+ {...props}
15
+ />
16
+ );
17
+ }
18
+
19
+ export function CardHeader({
20
+ className,
21
+ ...props
22
+ }: React.HTMLAttributes<HTMLDivElement>) {
23
+ return (
24
+ <div className={cn("p-6 pb-3", className)} {...props} />
25
+ );
26
+ }
27
+
28
+ export function CardTitle({
29
+ className,
30
+ ...props
31
+ }: React.HTMLAttributes<HTMLHeadingElement>) {
32
+ return (
33
+ <h3
34
+ className={cn("text-lg font-semibold tracking-tight", className)}
35
+ {...props}
36
+ />
37
+ );
38
+ }
39
+
40
+ export function CardDescription({
41
+ className,
42
+ ...props
43
+ }: React.HTMLAttributes<HTMLParagraphElement>) {
44
+ return (
45
+ <p className={cn("text-sm text-muted-foreground", className)} {...props} />
46
+ );
47
+ }
48
+
49
+ export function CardContent({
50
+ className,
51
+ ...props
52
+ }: React.HTMLAttributes<HTMLDivElement>) {
53
+ return (
54
+ <div className={cn("p-6 pt-0", className)} {...props} />
55
+ );
56
+ }
@@ -0,0 +1,19 @@
1
+ import * as React from "react";
2
+ import { cn } from "@/lib/utils";
3
+
4
+ interface CheckboxProps extends React.InputHTMLAttributes<HTMLInputElement> {
5
+ className?: string;
6
+ }
7
+
8
+ export function Checkbox({ className, ...props }: CheckboxProps) {
9
+ return (
10
+ <input
11
+ type="checkbox"
12
+ className={cn(
13
+ "h-4 w-4 rounded border border-border/50 bg-background text-primary focus:outline-none focus:ring-2 focus:ring-primary/20",
14
+ className
15
+ )}
16
+ {...props}
17
+ />
18
+ );
19
+ }
@@ -0,0 +1,245 @@
1
+ "use client";
2
+
3
+ import { useState, useEffect, useCallback, useMemo } from "react";
4
+ import { motion, AnimatePresence } from "framer-motion";
5
+ import { Search, Command, Sun, Moon, Palette, Code, LayoutTemplate, Image, FileText, Home, X, Sparkles } from "lucide-react";
6
+ import { useRouter } from "next/navigation";
7
+ import { useThemeContext } from "@/components/theme/theme-context";
8
+ import { NITRO_ALL_THEMES, type ThemeId } from "@/components/theme/theme-engine";
9
+
10
+ interface CommandItem {
11
+ id: string;
12
+ label: string;
13
+ shortcut?: string;
14
+ icon: React.ReactNode;
15
+ action: () => void;
16
+ category: string;
17
+ }
18
+
19
+ export function CommandPalette() {
20
+ const [isOpen, setIsOpen] = useState(false);
21
+ const [search, setSearch] = useState("");
22
+ const router = useRouter();
23
+ const { themeId, setThemeId, colorMode, setColorMode } = useThemeContext();
24
+
25
+ const toggle = useCallback(() => setIsOpen((prev) => !prev), []);
26
+ const close = useCallback(() => {
27
+ setIsOpen(false);
28
+ setSearch("");
29
+ }, []);
30
+
31
+ // Keyboard shortcut listener
32
+ useEffect(() => {
33
+ const handleKeyDown = (e: KeyboardEvent) => {
34
+ if ((e.metaKey || e.ctrlKey) && e.key === "k") {
35
+ e.preventDefault();
36
+ toggle();
37
+ }
38
+ if (e.key === "Escape") {
39
+ close();
40
+ }
41
+ };
42
+
43
+ window.addEventListener("keydown", handleKeyDown);
44
+ return () => window.removeEventListener("keydown", handleKeyDown);
45
+ }, [toggle, close]);
46
+
47
+ const commands: CommandItem[] = useMemo(() => [
48
+ // Navigation
49
+ {
50
+ id: "nav-home",
51
+ label: "Go to Home",
52
+ shortcut: "H",
53
+ icon: <Home className="h-4 w-4" />,
54
+ action: () => { router.push("/"); close(); },
55
+ category: "Navigation",
56
+ },
57
+ {
58
+ id: "nav-studio",
59
+ label: "Open Studio",
60
+ shortcut: "T",
61
+ icon: <Palette className="h-4 w-4" />,
62
+ action: () => { router.push("/studio"); close(); },
63
+ category: "Navigation",
64
+ },
65
+ {
66
+ id: "nav-gallery",
67
+ label: "View Gallery",
68
+ shortcut: "G",
69
+ icon: <Image className="h-4 w-4" />,
70
+ action: () => { router.push("/gallery"); close(); },
71
+ category: "Navigation",
72
+ },
73
+ {
74
+ id: "nav-showcase",
75
+ label: "View Showcase",
76
+ shortcut: "S",
77
+ icon: <LayoutTemplate className="h-4 w-4" />,
78
+ action: () => { router.push("/showcase"); close(); },
79
+ category: "Navigation",
80
+ },
81
+ {
82
+ id: "nav-docs",
83
+ label: "Read Documentation",
84
+ shortcut: "D",
85
+ icon: <FileText className="h-4 w-4" />,
86
+ action: () => { router.push("/docs"); close(); },
87
+ category: "Navigation",
88
+ },
89
+ // Theme
90
+ {
91
+ id: "theme-toggle",
92
+ label: `Switch to ${colorMode === "dark" ? "Light" : "Dark"} Mode`,
93
+ shortcut: "T",
94
+ icon: colorMode === "dark" ? <Sun className="h-4 w-4" /> : <Moon className="h-4 w-4" />,
95
+ action: () => { setColorMode(colorMode === "dark" ? "light" : "dark"); close(); },
96
+ category: "Theme",
97
+ },
98
+ // Export
99
+ {
100
+ id: "export-css",
101
+ label: "Export Theme as CSS",
102
+ icon: <Code className="h-4 w-4" />,
103
+ action: () => { router.push("/studio"); close(); },
104
+ category: "Export",
105
+ },
106
+ ], [router, close, colorMode, setColorMode]);
107
+
108
+ // Theme commands
109
+ const themeCommands: CommandItem[] = useMemo(() =>
110
+ NITRO_ALL_THEMES.map((theme) => ({
111
+ id: `theme-${theme.id}`,
112
+ label: `Apply ${theme.label} Theme`,
113
+ icon: <Sparkles className="h-4 w-4" />,
114
+ action: () => { setThemeId(theme.id as ThemeId); close(); },
115
+ category: "Themes",
116
+ }))
117
+ , [setThemeId, close]);
118
+
119
+ const allCommands = useMemo(() => [...commands, ...themeCommands], [commands, themeCommands]);
120
+
121
+ const filteredCommands = useMemo(() => {
122
+ if (!search.trim()) return allCommands;
123
+ const query = search.toLowerCase();
124
+ return allCommands.filter((cmd) =>
125
+ cmd.label.toLowerCase().includes(query) ||
126
+ cmd.category.toLowerCase().includes(query)
127
+ );
128
+ }, [allCommands, search]);
129
+
130
+ const groupedCommands = useMemo(() => {
131
+ const groups: Record<string, CommandItem[]> = {};
132
+ filteredCommands.forEach((cmd) => {
133
+ if (!groups[cmd.category]) groups[cmd.category] = [];
134
+ groups[cmd.category].push(cmd);
135
+ });
136
+ return groups;
137
+ }, [filteredCommands]);
138
+
139
+ if (!isOpen) return null;
140
+
141
+ return (
142
+ <AnimatePresence>
143
+ <motion.div
144
+ initial={{ opacity: 0 }}
145
+ animate={{ opacity: 1 }}
146
+ exit={{ opacity: 0 }}
147
+ className="fixed inset-0 z-50 bg-black/50 backdrop-blur-sm"
148
+ onClick={close}
149
+ >
150
+ <motion.div
151
+ initial={{ opacity: 0, scale: 0.95, y: 20 }}
152
+ animate={{ opacity: 1, scale: 1, y: 0 }}
153
+ exit={{ opacity: 0, scale: 0.95, y: 20 }}
154
+ transition={{ type: "spring", stiffness: 400, damping: 30 }}
155
+ className="fixed left-1/2 top-[20%] -translate-x-1/2 w-full max-w-2xl px-4"
156
+ onClick={(e) => e.stopPropagation()}
157
+ >
158
+ <div className="bg-background/95 backdrop-blur-xl rounded-3xl border border-border/50 shadow-2xl overflow-hidden">
159
+ {/* Search header */}
160
+ <div className="flex items-center gap-3 px-6 py-4 border-b border-border/50">
161
+ <Search className="h-5 w-5 text-muted-foreground" />
162
+ <input
163
+ type="text"
164
+ placeholder="Search commands, themes, or pages..."
165
+ value={search}
166
+ onChange={(e) => setSearch(e.target.value)}
167
+ className="flex-1 bg-transparent text-lg outline-none placeholder:text-muted-foreground"
168
+ autoFocus
169
+ />
170
+ <div className="flex items-center gap-2">
171
+ <kbd className="hidden sm:inline-flex items-center gap-1 px-2 py-1 text-xs font-mono bg-muted rounded-lg border">
172
+ <Command className="h-3 w-3" /> K
173
+ </kbd>
174
+ <button
175
+ onClick={close}
176
+ className="p-2 rounded-full hover:bg-muted/50 transition-colors"
177
+ >
178
+ <X className="h-4 w-4" />
179
+ </button>
180
+ </div>
181
+ </div>
182
+
183
+ {/* Results */}
184
+ <div className="max-h-[400px] overflow-y-auto p-2">
185
+ {Object.entries(groupedCommands).map(([category, items]) => (
186
+ items.length > 0 && (
187
+ <div key={category} className="mb-2">
188
+ <div className="px-4 py-2 text-xs font-semibold text-muted-foreground uppercase tracking-wider">
189
+ {category}
190
+ </div>
191
+ {items.map((cmd) => (
192
+ <CommandItem key={cmd.id} item={cmd} onSelect={cmd.action} />
193
+ ))}
194
+ </div>
195
+ )
196
+ ))}
197
+
198
+ {filteredCommands.length === 0 && (
199
+ <div className="px-4 py-8 text-center text-muted-foreground">
200
+ <p>No commands found</p>
201
+ <p className="text-sm mt-1">Try a different search term</p>
202
+ </div>
203
+ )}
204
+ </div>
205
+
206
+ {/* Footer */}
207
+ <div className="px-6 py-3 border-t border-border/50 text-xs text-muted-foreground flex items-center justify-between">
208
+ <div className="flex items-center gap-4">
209
+ <span className="flex items-center gap-1">
210
+ <kbd className="px-1.5 py-0.5 bg-muted rounded border">↑</kbd>
211
+ <kbd className="px-1.5 py-0.5 bg-muted rounded border">↓</kbd>
212
+ <span className="ml-1">Navigate</span>
213
+ </span>
214
+ <span className="flex items-center gap-1">
215
+ <kbd className="px-1.5 py-0.5 bg-muted rounded border">Enter</kbd>
216
+ <span className="ml-1">Select</span>
217
+ </span>
218
+ </div>
219
+ <span>{filteredCommands.length} results</span>
220
+ </div>
221
+ </div>
222
+ </motion.div>
223
+ </motion.div>
224
+ </AnimatePresence>
225
+ );
226
+ }
227
+
228
+ function CommandItem({ item, onSelect }: { item: CommandItem; onSelect: () => void }) {
229
+ return (
230
+ <button
231
+ onClick={onSelect}
232
+ className="w-full flex items-center gap-3 px-4 py-2.5 rounded-xl text-left hover:bg-accent hover:text-accent-foreground transition-colors group"
233
+ >
234
+ <span className="text-muted-foreground group-hover:text-accent-foreground">
235
+ {item.icon}
236
+ </span>
237
+ <span className="flex-1 font-medium">{item.label}</span>
238
+ {item.shortcut && (
239
+ <kbd className="px-2 py-0.5 text-xs font-mono bg-muted rounded border group-hover:bg-accent-foreground/10">
240
+ {item.shortcut}
241
+ </kbd>
242
+ )}
243
+ </button>
244
+ );
245
+ }