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,237 @@
1
+ export const NITRO_PUBLIC_THEMES = [
2
+ {
3
+ id: "theme-nitro-mint-apple",
4
+ label: "Mint Apple",
5
+ preview:
6
+ "radial-gradient(circle at 12% 10%, #b2ffe1 0%, transparent 42%), linear-gradient(145deg, #2d8e74 0%, #70c76a 46%, #d5ef91 100%)",
7
+ },
8
+ {
9
+ id: "theme-nitro-citrus-sherbert",
10
+ label: "Citrus Sherbert",
11
+ preview:
12
+ "radial-gradient(circle at 10% 10%, #ffd37d 0%, transparent 42%), linear-gradient(145deg, #e67d35 0%, #f7bb54 46%, #fff0a3 100%)",
13
+ },
14
+ {
15
+ id: "theme-nitro-retro-raincloud",
16
+ label: "Retro Raincloud",
17
+ preview:
18
+ "radial-gradient(circle at 12% 10%, #b3c6dc 0%, transparent 42%), linear-gradient(145deg, #4e6077 0%, #6f86a0 46%, #98aec3 100%)",
19
+ },
20
+ {
21
+ id: "theme-nitro-hanami",
22
+ label: "Hanami",
23
+ preview:
24
+ "radial-gradient(circle at 12% 10%, #ffd5e7 0%, transparent 42%), linear-gradient(145deg, #995382 0%, #c77ca9 46%, #f0b7ce 100%)",
25
+ },
26
+ {
27
+ id: "theme-nitro-sunrise",
28
+ label: "Sunrise",
29
+ preview:
30
+ "radial-gradient(circle at 12% 10%, #ffb596 0%, transparent 42%), linear-gradient(145deg, #e25263 0%, #ef8a57 46%, #ffd07a 100%)",
31
+ },
32
+ {
33
+ id: "theme-nitro-cotton-candy",
34
+ label: "Cotton Candy",
35
+ preview:
36
+ "radial-gradient(circle at 12% 10%, #c8e9ff 0%, transparent 42%), linear-gradient(145deg, #5aa2ff 0%, #9b78f0 46%, #f39bca 100%)",
37
+ },
38
+ {
39
+ id: "theme-nitro-lofi-vibes",
40
+ label: "Lofi Vibes",
41
+ preview:
42
+ "radial-gradient(circle at 10% 10%, #8e97c6 0%, transparent 42%), linear-gradient(145deg, #3f476c 0%, #59608f 46%, #7a6f9f 100%)",
43
+ },
44
+ {
45
+ id: "theme-nitro-desert-khaki",
46
+ label: "Desert Khaki",
47
+ preview:
48
+ "radial-gradient(circle at 10% 10%, #d5bf92 0%, transparent 42%), linear-gradient(145deg, #6d5c49 0%, #8f7a5d 46%, #b49f76 100%)",
49
+ },
50
+ {
51
+ id: "theme-nitro-sunset",
52
+ label: "Sunset",
53
+ preview:
54
+ "radial-gradient(circle at 10% 10%, #d66a82 0%, transparent 42%), linear-gradient(145deg, #3f1b4d 0%, #8c335f 46%, #f4874f 100%)",
55
+ },
56
+ {
57
+ id: "theme-nitro-chroma-glow",
58
+ label: "Chroma Glow",
59
+ preview:
60
+ "radial-gradient(circle at 10% 10%, #7f8dff 0%, transparent 42%), linear-gradient(145deg, #2d3eff 0%, #a726fa 46%, #00c7ff 100%)",
61
+ },
62
+ {
63
+ id: "theme-nitro-forest",
64
+ label: "Forest",
65
+ preview:
66
+ "radial-gradient(circle at 10% 10%, #56be87 0%, transparent 42%), linear-gradient(145deg, #163f2e 0%, #246b49 46%, #59a86c 100%)",
67
+ },
68
+ {
69
+ id: "theme-nitro-crimson",
70
+ label: "Crimson",
71
+ preview:
72
+ "radial-gradient(circle at 10% 10%, #c23956 0%, transparent 42%), linear-gradient(145deg, #2d050f 0%, #681126 46%, #a82435 100%)",
73
+ },
74
+ {
75
+ id: "theme-nitro-midnight-blurple",
76
+ label: "Midnight Blurple",
77
+ preview:
78
+ "radial-gradient(circle at 10% 10%, #6d6eff 0%, transparent 42%), linear-gradient(145deg, #0f1232 0%, #25366f 46%, #5757dc 100%)",
79
+ },
80
+ {
81
+ id: "theme-nitro-mars",
82
+ label: "Mars",
83
+ preview:
84
+ "radial-gradient(circle at 10% 10%, #cc654c 0%, transparent 42%), linear-gradient(145deg, #2e140f 0%, #5e261d 46%, #9c422f 100%)",
85
+ },
86
+ {
87
+ id: "theme-nitro-dusk",
88
+ label: "Dusk",
89
+ preview:
90
+ "radial-gradient(circle at 10% 10%, #9475c1 0%, transparent 42%), linear-gradient(145deg, #1b1632 0%, #3b2d5b 46%, #745495 100%)",
91
+ },
92
+ {
93
+ id: "theme-nitro-under-the-sea",
94
+ label: "Under The Sea",
95
+ preview:
96
+ "radial-gradient(circle at 10% 10%, #3ea6b7 0%, transparent 42%), linear-gradient(145deg, #0b2242 0%, #0d4f69 46%, #2b848e 100%)",
97
+ },
98
+ {
99
+ id: "theme-nitro-retro-storm",
100
+ label: "Retro Storm",
101
+ preview:
102
+ "radial-gradient(circle at 10% 10%, #7e95ab 0%, transparent 42%), linear-gradient(145deg, #1d2b3a 0%, #354657 46%, #55667a 100%)",
103
+ },
104
+ {
105
+ id: "theme-nitro-neon-nights",
106
+ label: "Neon Nights",
107
+ preview:
108
+ "radial-gradient(circle at 10% 10%, #d13eff 0%, transparent 42%), linear-gradient(145deg, #05061a 0%, #180f52 46%, #00bde6 100%)",
109
+ },
110
+ {
111
+ id: "theme-nitro-strawberry-lemonade",
112
+ label: "Strawberry Lemonade",
113
+ preview:
114
+ "radial-gradient(circle at 10% 10%, #ff8aa8 0%, transparent 42%), linear-gradient(145deg, #8f1847 0%, #cc3f5e 46%, #efc141 100%)",
115
+ },
116
+ {
117
+ id: "theme-nitro-aurora",
118
+ label: "Aurora",
119
+ preview:
120
+ "radial-gradient(circle at 10% 10%, #41cbb1 0%, transparent 42%), linear-gradient(145deg, #083142 0%, #1b7e74 46%, #5fbf75 100%)",
121
+ },
122
+ {
123
+ id: "theme-nitro-sepia",
124
+ label: "Sepia",
125
+ preview:
126
+ "radial-gradient(circle at 10% 10%, #b99672 0%, transparent 42%), linear-gradient(145deg, #35261d 0%, #5d4636 46%, #927454 100%)",
127
+ },
128
+ ] as const;
129
+
130
+ export const MEMORY_LANE_THEME = {
131
+ id: "theme-nitro-memory-lane",
132
+ label: "Memory Lane",
133
+ preview:
134
+ "radial-gradient(circle at 10% 10%, #ba8fd4 0%, transparent 42%), linear-gradient(145deg, #462d42 0%, #684b75 35%, #4a7199 68%, #77ad9f 100%)",
135
+ } as const;
136
+
137
+ export const NITRO_ALL_THEMES = [
138
+ ...NITRO_PUBLIC_THEMES,
139
+ MEMORY_LANE_THEME,
140
+ ] as const;
141
+
142
+ export type ThemeId = (typeof NITRO_ALL_THEMES)[number]["id"];
143
+ export type ColorMode = "dark" | "light";
144
+
145
+ type ThemeTourProgress = {
146
+ viewedThemeIds: string[];
147
+ memoryLaneUnlocked: boolean;
148
+ };
149
+
150
+ const DEFAULT_THEME: ThemeId = "theme-nitro-midnight-blurple";
151
+ const DEFAULT_COLOR_MODE: ColorMode = "dark";
152
+ const THEME_STORAGE_KEY = "shadcn-gradient.theme";
153
+ const COLOR_MODE_STORAGE_KEY = "shadcn-gradient.color-mode";
154
+ const THEME_TOUR_STORAGE_KEY = "shadcn-gradient.theme-tour.v1";
155
+
156
+ const NITRO_THEME_IDS = new Set<string>(NITRO_ALL_THEMES.map((theme) => theme.id));
157
+
158
+ export const normalizeTheme = (theme?: string | null): ThemeId => {
159
+ if (!theme || !NITRO_THEME_IDS.has(theme)) {
160
+ return DEFAULT_THEME;
161
+ }
162
+
163
+ return theme as ThemeId;
164
+ };
165
+
166
+ export const normalizeColorMode = (mode?: string | null): ColorMode => {
167
+ if (mode === "light") return "light";
168
+ return DEFAULT_COLOR_MODE;
169
+ };
170
+
171
+ export const getStoredTheme = (): ThemeId => {
172
+ if (typeof window === "undefined") return DEFAULT_THEME;
173
+ return normalizeTheme(localStorage.getItem(THEME_STORAGE_KEY));
174
+ };
175
+
176
+ export const getStoredColorMode = (): ColorMode => {
177
+ if (typeof window === "undefined") return DEFAULT_COLOR_MODE;
178
+ return normalizeColorMode(localStorage.getItem(COLOR_MODE_STORAGE_KEY));
179
+ };
180
+
181
+ export const readTourProgress = (): ThemeTourProgress => {
182
+ if (typeof window === "undefined") {
183
+ return { viewedThemeIds: [], memoryLaneUnlocked: false };
184
+ }
185
+
186
+ const raw = localStorage.getItem(THEME_TOUR_STORAGE_KEY);
187
+ if (!raw) {
188
+ return { viewedThemeIds: [], memoryLaneUnlocked: false };
189
+ }
190
+
191
+ try {
192
+ const parsed = JSON.parse(raw) as ThemeTourProgress;
193
+ return {
194
+ viewedThemeIds: Array.isArray(parsed.viewedThemeIds)
195
+ ? parsed.viewedThemeIds.filter((id) => NITRO_THEME_IDS.has(id))
196
+ : [],
197
+ memoryLaneUnlocked: Boolean(parsed.memoryLaneUnlocked),
198
+ };
199
+ } catch {
200
+ return { viewedThemeIds: [], memoryLaneUnlocked: false };
201
+ }
202
+ };
203
+
204
+ export const persistTourProgress = (progress: ThemeTourProgress) => {
205
+ if (typeof window === "undefined") return;
206
+ localStorage.setItem(THEME_TOUR_STORAGE_KEY, JSON.stringify(progress));
207
+ };
208
+
209
+ export const applyTheme = (theme?: string | null, mode?: ColorMode) => {
210
+ if (typeof window === "undefined") return;
211
+
212
+ const normalizedTheme = normalizeTheme(theme);
213
+ const normalizedMode = normalizeColorMode(mode ?? getStoredColorMode());
214
+ const root = document.documentElement;
215
+
216
+ Array.from(root.classList).forEach((className) => {
217
+ if (
218
+ className === "dark" ||
219
+ className === "light" ||
220
+ className.startsWith("theme-nitro-")
221
+ ) {
222
+ root.classList.remove(className);
223
+ }
224
+ });
225
+
226
+ root.classList.add(normalizedMode, normalizedTheme);
227
+ root.setAttribute("data-theme", normalizedTheme);
228
+ root.setAttribute("data-color-mode", normalizedMode);
229
+
230
+ localStorage.setItem(THEME_STORAGE_KEY, normalizedTheme);
231
+ localStorage.setItem(COLOR_MODE_STORAGE_KEY, normalizedMode);
232
+ };
233
+
234
+ export const defaultTheme = DEFAULT_THEME;
235
+ export const defaultColorMode = DEFAULT_COLOR_MODE;
236
+ export const publicThemeIds = NITRO_PUBLIC_THEMES.map((theme) => theme.id);
237
+ export const publicThemeIdSet = new Set<string>(publicThemeIds);
@@ -0,0 +1,369 @@
1
+ "use client";
2
+
3
+ import { useMemo, useState } from "react";
4
+ import { Badge } from "@/components/ui/badge";
5
+ import { Button } from "@/components/ui/button";
6
+ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
7
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
8
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
9
+ import { Switch } from "@/components/ui/switch";
10
+ import { useThemeContext } from "./theme-context";
11
+ import { cn } from "@/lib/utils";
12
+ import {
13
+ Check,
14
+ Copy,
15
+ Download,
16
+ FileCode,
17
+ Palette,
18
+ Code2,
19
+ Braces,
20
+ FileJson,
21
+ Layers,
22
+ FileType,
23
+ AlertCircle,
24
+ Sun,
25
+ Moon
26
+ } from "lucide-react";
27
+ import { type ThemeId, NITRO_ALL_THEMES } from "./theme-engine";
28
+ import {
29
+ exportTokens,
30
+ downloadFile,
31
+ copyToClipboard,
32
+ exportFormats,
33
+ type ExportFormat,
34
+ generateAllThemesCSS,
35
+ getThemeTokens,
36
+ } from "./token-export-utils";
37
+
38
+ type CopyStatus = "idle" | "copying" | "copied" | "error";
39
+
40
+ interface ThemeExporterProps {
41
+ themeId?: ThemeId;
42
+ colorMode?: "dark" | "light";
43
+ }
44
+
45
+ export function ThemeExporter({
46
+ themeId: externalThemeId,
47
+ colorMode: externalColorMode
48
+ }: ThemeExporterProps) {
49
+ const { themeId: contextThemeId, colorMode: contextColorMode, setThemeId, setColorMode } = useThemeContext();
50
+
51
+ const themeId = externalThemeId ?? contextThemeId;
52
+ const colorMode = externalColorMode ?? contextColorMode;
53
+
54
+ const [selectedThemeId, setSelectedThemeId] = useState<ThemeId>(themeId);
55
+ const [selectedColorMode, setSelectedColorMode] = useState<"dark" | "light">(colorMode);
56
+ const [selectedFormat, setSelectedFormat] = useState<ExportFormat>("css");
57
+ const [copyState, setCopyState] = useState<Record<string, CopyStatus>>({});
58
+ const [activeTab, setActiveTab] = useState<string>("single");
59
+
60
+ const currentThemeId = externalThemeId ? themeId : selectedThemeId;
61
+ const currentColorMode = externalColorMode ? colorMode : selectedColorMode;
62
+
63
+ const exportResult = useMemo(() => {
64
+ return exportTokens({
65
+ format: selectedFormat,
66
+ themeId: currentThemeId,
67
+ colorMode: currentColorMode
68
+ });
69
+ }, [selectedFormat, currentThemeId, currentColorMode]);
70
+
71
+ const allThemesExport = useMemo(() => {
72
+ return generateAllThemesCSS();
73
+ }, []);
74
+
75
+ const tokens = useMemo(() => {
76
+ return getThemeTokens(currentThemeId);
77
+ }, [currentThemeId]);
78
+
79
+ const handleCopy = async (id: string, content: string) => {
80
+ setCopyState((prev) => ({ ...prev, [id]: "copying" }));
81
+ const success = await copyToClipboard(content);
82
+ setCopyState((prev) => ({ ...prev, [id]: success ? "copied" : "error" }));
83
+ window.setTimeout(() => {
84
+ setCopyState((prev) => ({ ...prev, [id]: "idle" }));
85
+ }, 1400);
86
+ };
87
+
88
+ const handleDownload = (result: ReturnType<typeof exportTokens>) => {
89
+ downloadFile(result);
90
+ };
91
+
92
+ const copyLabel = (status: CopyStatus) => {
93
+ if (status === "copying") return "Copying...";
94
+ if (status === "copied") return "Copied!";
95
+ if (status === "error") return "Failed";
96
+ return "Copy";
97
+ };
98
+
99
+ const getFormatIcon = (format: ExportFormat) => {
100
+ switch (format) {
101
+ case "css":
102
+ case "css-variables":
103
+ return <FileCode className="h-4 w-4" />;
104
+ case "scss":
105
+ return <Palette className="h-4 w-4" />;
106
+ case "json":
107
+ case "w3c-tokens":
108
+ case "figma-tokens":
109
+ return <FileJson className="h-4 w-4" />;
110
+ case "tailwind":
111
+ return <Layers className="h-4 w-4" />;
112
+ case "html-root":
113
+ return <FileType className="h-4 w-4" />;
114
+ default:
115
+ return <Code2 className="h-4 w-4" />;
116
+ }
117
+ };
118
+
119
+ const currentStatus = copyState["export"] ?? "idle";
120
+ const allThemesStatus = copyState["all-themes"] ?? "idle";
121
+
122
+ const handleThemeChange = (newThemeId: string) => {
123
+ const id = newThemeId as ThemeId;
124
+ setSelectedThemeId(id);
125
+ if (setThemeId && !externalThemeId) {
126
+ setThemeId(id);
127
+ }
128
+ };
129
+
130
+ const handleColorModeToggle = (checked: boolean) => {
131
+ const mode = checked ? "dark" : "light";
132
+ setSelectedColorMode(mode);
133
+ if (setColorMode && !externalColorMode) {
134
+ setColorMode(mode);
135
+ }
136
+ };
137
+
138
+ return (
139
+ <Card className="border-border/50 bg-background/60">
140
+ <CardHeader className="flex flex-col gap-2">
141
+ <div className="flex flex-wrap items-center justify-between gap-2">
142
+ <div>
143
+ <CardTitle>Theme Export</CardTitle>
144
+ <p className="text-sm text-muted-foreground mt-1">
145
+ Export {currentThemeId.replace("theme-nitro-", "").replace(/-/g, " ")} theme in multiple formats
146
+ </p>
147
+ </div>
148
+ <Badge variant="glass" className="gap-1">
149
+ <Braces className="h-3 w-3" /> Multi-Format
150
+ </Badge>
151
+ </div>
152
+
153
+ {/* Theme Selection & Color Mode Toggle */}
154
+ <div className="flex flex-wrap items-center gap-3 mt-2">
155
+ <div className="flex-1 min-w-[200px]">
156
+ <Select value={currentThemeId} onValueChange={handleThemeChange}>
157
+ <SelectTrigger className="w-full">
158
+ <SelectValue placeholder="Select a theme" />
159
+ </SelectTrigger>
160
+ <SelectContent>
161
+ {NITRO_ALL_THEMES.map((theme) => (
162
+ <SelectItem key={theme.id} value={theme.id}>
163
+ <div className="flex items-center gap-2">
164
+ <div
165
+ className="w-4 h-4 rounded-md border border-border/30"
166
+ style={{ background: theme.preview }}
167
+ />
168
+ <span>{theme.label}</span>
169
+ </div>
170
+ </SelectItem>
171
+ ))}
172
+ </SelectContent>
173
+ </Select>
174
+ </div>
175
+
176
+ <div className="flex items-center gap-2 bg-muted/50 rounded-lg px-3 py-1.5">
177
+ <Sun className={cn("h-4 w-4", currentColorMode === "light" ? "text-foreground" : "text-muted-foreground")} />
178
+ <Switch
179
+ checked={currentColorMode === "dark"}
180
+ onCheckedChange={handleColorModeToggle}
181
+ />
182
+ <Moon className={cn("h-4 w-4", currentColorMode === "dark" ? "text-foreground" : "text-muted-foreground")} />
183
+ </div>
184
+ </div>
185
+ </CardHeader>
186
+ <CardContent className="grid gap-4">
187
+ <Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
188
+ <TabsList className="grid w-full grid-cols-2">
189
+ <TabsTrigger value="single">Single Theme</TabsTrigger>
190
+ <TabsTrigger value="all">All Themes</TabsTrigger>
191
+ </TabsList>
192
+
193
+ <TabsContent value="single" className="space-y-4 mt-4">
194
+ {/* Format Selector */}
195
+ <div className="space-y-2">
196
+ <label className="text-sm font-medium">Export Format</label>
197
+ <Select value={selectedFormat} onValueChange={(v: string) => setSelectedFormat(v as ExportFormat)}>
198
+ <SelectTrigger className="w-full">
199
+ <SelectValue placeholder="Select format" />
200
+ </SelectTrigger>
201
+ <SelectContent>
202
+ {exportFormats.map((format) => (
203
+ <SelectItem key={format.value} value={format.value}>
204
+ <div className="flex items-center gap-2">
205
+ {getFormatIcon(format.value)}
206
+ <span>{format.label}</span>
207
+ <span className="text-muted-foreground text-xs ml-auto">.{format.extension}</span>
208
+ </div>
209
+ </SelectItem>
210
+ ))}
211
+ </SelectContent>
212
+ </Select>
213
+ <p className="text-xs text-muted-foreground">
214
+ {exportFormats.find((f) => f.value === selectedFormat)?.description}
215
+ </p>
216
+ </div>
217
+
218
+ {/* Color Swatches Preview */}
219
+ <div className="rounded-2xl border border-border/40 bg-background/50 p-4">
220
+ <h4 className="text-sm font-semibold mb-3 flex items-center gap-2">
221
+ <Palette className="h-4 w-4" />
222
+ Theme Colors
223
+ </h4>
224
+ <div className="grid grid-cols-8 gap-2">
225
+ {[
226
+ { name: "primary", value: tokens["--primary"] },
227
+ { name: "secondary", value: tokens["--secondary"] },
228
+ { name: "accent", value: tokens["--accent"] },
229
+ { name: "background", value: tokens["--background"] },
230
+ { name: "card", value: tokens["--card"] },
231
+ { name: "muted", value: tokens["--muted"] },
232
+ { name: "border", value: tokens["--border"] },
233
+ { name: "destructive", value: tokens["--destructive"] },
234
+ ].map((color) => (
235
+ <div key={color.name} className="group relative">
236
+ <div
237
+ className="w-full aspect-square rounded-lg border border-border/40 cursor-pointer transition-transform hover:scale-110"
238
+ style={{ backgroundColor: `hsl(${color.value})` }}
239
+ title={`${color.name}: ${color.value}`}
240
+ onClick={() => handleCopy(`color-${color.name}`, color.value)}
241
+ />
242
+ <span className="text-[10px] text-muted-foreground text-center block mt-1 truncate">
243
+ {color.name}
244
+ </span>
245
+ </div>
246
+ ))}
247
+ </div>
248
+ </div>
249
+
250
+ {/* Code Preview */}
251
+ <div className="rounded-2xl border border-border/40 bg-background/50 p-4">
252
+ <div className="flex flex-wrap items-center justify-between gap-3 mb-3">
253
+ <div className="flex items-center gap-2">
254
+ {getFormatIcon(selectedFormat)}
255
+ <span className="text-sm font-semibold">
256
+ {exportResult.filename}
257
+ </span>
258
+ </div>
259
+ <div className="flex gap-2">
260
+ <Button
261
+ size="sm"
262
+ variant="outline"
263
+ onClick={() => handleCopy("export", exportResult.content)}
264
+ disabled={currentStatus === "copying"}
265
+ >
266
+ {currentStatus === "copied" ? (
267
+ <Check className="h-4 w-4 mr-1" />
268
+ ) : currentStatus === "error" ? (
269
+ <AlertCircle className="h-4 w-4 mr-1" />
270
+ ) : (
271
+ <Copy className="h-4 w-4 mr-1" />
272
+ )}
273
+ {copyLabel(currentStatus)}
274
+ </Button>
275
+ <Button
276
+ size="sm"
277
+ variant="default"
278
+ onClick={() => handleDownload(exportResult)}
279
+ >
280
+ <Download className="h-4 w-4 mr-1" />
281
+ Download
282
+ </Button>
283
+ </div>
284
+ </div>
285
+ <pre
286
+ className={cn(
287
+ "overflow-x-auto rounded-2xl bg-black/80 p-4 text-xs text-white/90",
288
+ "border border-white/10 max-h-[400px] overflow-y-auto"
289
+ )}
290
+ >
291
+ <code>{exportResult.content}</code>
292
+ </pre>
293
+ </div>
294
+
295
+ {/* Quick Actions */}
296
+ <div className="grid grid-cols-2 gap-2">
297
+ <Button
298
+ variant="outline"
299
+ size="sm"
300
+ onClick={() => handleCopy("root-attrs", `<html class="${colorMode} ${themeId}" data-theme="${themeId}" data-color-mode="${colorMode}">`)}
301
+ >
302
+ <Code2 className="h-4 w-4 mr-2" />
303
+ Copy Root Attributes
304
+ </Button>
305
+ <Button
306
+ variant="outline"
307
+ size="sm"
308
+ onClick={() => handleCopy("surface-css", `.bg-card {
309
+ background-color: hsl(var(--background) / 0.34);
310
+ background-image: linear-gradient(var(--app-surface-tint), var(--app-surface-tint));
311
+ backdrop-filter: blur(16px);
312
+ }`)}
313
+ >
314
+ <Layers className="h-4 w-4 mr-2" />
315
+ Copy Surface CSS
316
+ </Button>
317
+ </div>
318
+ </TabsContent>
319
+
320
+ <TabsContent value="all" className="space-y-4 mt-4">
321
+ <div className="rounded-2xl border border-border/40 bg-background/50 p-4">
322
+ <div className="flex flex-wrap items-center justify-between gap-3 mb-3">
323
+ <div>
324
+ <h4 className="text-sm font-semibold">All Themes CSS</h4>
325
+ <p className="text-xs text-muted-foreground">
326
+ Export all {NITRO_ALL_THEMES.length} themes in a single CSS file
327
+ </p>
328
+ </div>
329
+ <div className="flex gap-2">
330
+ <Button
331
+ size="sm"
332
+ variant="outline"
333
+ onClick={() => handleCopy("all-themes", allThemesExport.content)}
334
+ disabled={allThemesStatus === "copying"}
335
+ >
336
+ {allThemesStatus === "copied" ? (
337
+ <Check className="h-4 w-4 mr-1" />
338
+ ) : allThemesStatus === "error" ? (
339
+ <AlertCircle className="h-4 w-4 mr-1" />
340
+ ) : (
341
+ <Copy className="h-4 w-4 mr-1" />
342
+ )}
343
+ {copyLabel(allThemesStatus)}
344
+ </Button>
345
+ <Button
346
+ size="sm"
347
+ variant="default"
348
+ onClick={() => handleDownload(allThemesExport)}
349
+ >
350
+ <Download className="h-4 w-4 mr-1" />
351
+ Download All
352
+ </Button>
353
+ </div>
354
+ </div>
355
+ <pre
356
+ className={cn(
357
+ "overflow-x-auto rounded-2xl bg-black/80 p-4 text-xs text-white/90",
358
+ "border border-white/10 max-h-[300px] overflow-y-auto"
359
+ )}
360
+ >
361
+ <code>{allThemesExport.content.substring(0, 1500)}...</code>
362
+ </pre>
363
+ </div>
364
+ </TabsContent>
365
+ </Tabs>
366
+ </CardContent>
367
+ </Card>
368
+ );
369
+ }