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.
- package/.eslintrc.json +3 -0
- package/.github/FUNDING.yml +2 -0
- package/README.md +140 -0
- package/app/docs/page.tsx +417 -0
- package/app/gallery/page.tsx +398 -0
- package/app/globals.css +1155 -0
- package/app/layout.tsx +36 -0
- package/app/page.tsx +600 -0
- package/app/showcase/page.tsx +730 -0
- package/app/studio/page.tsx +1310 -0
- package/cli/index.mjs +1141 -0
- package/cli/templates/theme-context.tsx +120 -0
- package/cli/templates/theme-engine.ts +237 -0
- package/cli/templates/themes.css +512 -0
- package/components/site/component-showcase.tsx +623 -0
- package/components/site/site-data.ts +103 -0
- package/components/site/site-header.tsx +270 -0
- package/components/templates/blog.tsx +198 -0
- package/components/templates/components-showcase.tsx +298 -0
- package/components/templates/dashboard.tsx +246 -0
- package/components/templates/ecommerce.tsx +199 -0
- package/components/templates/mail.tsx +275 -0
- package/components/templates/saas-landing.tsx +169 -0
- package/components/theme/studio-code-panel.tsx +485 -0
- package/components/theme/theme-context.tsx +120 -0
- package/components/theme/theme-engine.ts +237 -0
- package/components/theme/theme-exporter.tsx +369 -0
- package/components/theme/theme-panel.tsx +268 -0
- package/components/theme/token-export-utils.ts +1211 -0
- package/components/ui/animated.tsx +55 -0
- package/components/ui/avatar.tsx +38 -0
- package/components/ui/badge.tsx +32 -0
- package/components/ui/button.tsx +65 -0
- package/components/ui/card.tsx +56 -0
- package/components/ui/checkbox.tsx +19 -0
- package/components/ui/command-palette.tsx +245 -0
- package/components/ui/gsap-animated.tsx +436 -0
- package/components/ui/input.tsx +17 -0
- package/components/ui/select.tsx +176 -0
- package/components/ui/skeleton.tsx +102 -0
- package/components/ui/switch.tsx +43 -0
- package/components/ui/tabs.tsx +115 -0
- package/components/ui/toast.tsx +119 -0
- package/gradient-forge/theme-context.tsx +119 -0
- package/gradient-forge/theme-engine.ts +236 -0
- package/gradient-forge/themes.css +556 -0
- package/lib/animations.ts +50 -0
- package/lib/gsap.ts +426 -0
- package/lib/utils.ts +6 -0
- package/next-env.d.ts +6 -0
- package/next.config.mjs +6 -0
- package/package.json +53 -0
- package/postcss.config.mjs +5 -0
- package/tailwind.config.ts +15 -0
- package/tsconfig.json +43 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
export const navItems = [
|
|
2
|
+
{ label: "Home", href: "/" },
|
|
3
|
+
{ label: "Themes", href: "/gallery" },
|
|
4
|
+
{ label: "Showcase", href: "/showcase" },
|
|
5
|
+
{ label: "Studio", href: "/studio" },
|
|
6
|
+
{ label: "Docs", href: "/docs" },
|
|
7
|
+
];
|
|
8
|
+
|
|
9
|
+
export const featureCards = [
|
|
10
|
+
{
|
|
11
|
+
title: "Theme Engine",
|
|
12
|
+
description:
|
|
13
|
+
"Persisted theme + color mode with data attributes ready for shadcn component tokens.",
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
title: "Surface-aware Layers",
|
|
17
|
+
description:
|
|
18
|
+
"Cards, sidebars, and overlays inherit subtle tints for depth without losing contrast.",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
title: "Memory Lane Unlock",
|
|
22
|
+
description:
|
|
23
|
+
"Preview all public palettes to unlock the secret theme for your portfolio or product demo.",
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
title: "Copy-ready Exports",
|
|
27
|
+
description:
|
|
28
|
+
"Grab the exact CSS + Tailwind aliases used here and drop them into any app.",
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
export const workflowSteps = [
|
|
33
|
+
{
|
|
34
|
+
title: "1. Pick a theme",
|
|
35
|
+
description: "Browse /studio or /gallery to find your perfect gradient.",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
title: "2. Copy the code",
|
|
39
|
+
description: "Click 'Get Theme' to copy CSS + React code instantly.",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
title: "3. Paste & go",
|
|
43
|
+
description: "Add CSS to globals, wrap with ThemeProvider, done!",
|
|
44
|
+
},
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
export const useCases = [
|
|
48
|
+
"SaaS dashboards with polished gradient theming",
|
|
49
|
+
"Portfolio demos for UI engineering roles",
|
|
50
|
+
"Design system spikes for shadcn component suites",
|
|
51
|
+
"Startup landing pages with cinematic brand color",
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
export const roadmapItems = [
|
|
55
|
+
{
|
|
56
|
+
title: "Theme Studio",
|
|
57
|
+
detail: "Interactive theme preview with live component showcase.",
|
|
58
|
+
status: "done",
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
title: "Theme Gallery",
|
|
62
|
+
detail: "Browse all 21 gradient themes with one-click copy.",
|
|
63
|
+
status: "done",
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
title: "Export Formats",
|
|
67
|
+
detail: "CSS, SCSS, JSON, Tailwind, W3C tokens, Figma tokens.",
|
|
68
|
+
status: "done",
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
title: "CLI Tool",
|
|
72
|
+
detail: "npm install gradient-forge for automatic setup.",
|
|
73
|
+
status: "coming-soon",
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
title: "Preset Editor",
|
|
77
|
+
detail: "Fine-tune gradient stops and export a new palette.",
|
|
78
|
+
status: "coming-soon",
|
|
79
|
+
},
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
export const faqItems = [
|
|
83
|
+
{
|
|
84
|
+
question: "Does the theme engine work outside Next.js?",
|
|
85
|
+
answer:
|
|
86
|
+
"Yes. The engine is plain CSS variables plus a small React context for state. You can reuse the CSS and write your own theme toggle in any framework.",
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
question: "How do I add my own palettes?",
|
|
90
|
+
answer:
|
|
91
|
+
"Add a new entry in the theme list and a matching CSS class in globals. The gallery and picker update automatically.",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
question: "Is this compatible with shadcn/ui defaults?",
|
|
95
|
+
answer:
|
|
96
|
+
"Yes. The tokens map directly to shadcn defaults and the surface overlay keeps contrast predictable.",
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
question: "Can I use this as a design system starter?",
|
|
100
|
+
answer:
|
|
101
|
+
"Absolutely. The token layer is stable and the demo shows cards, nav, inputs, and forms for guidance.",
|
|
102
|
+
},
|
|
103
|
+
];
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
import gsap from "gsap";
|
|
5
|
+
import { Badge } from "@/components/ui/badge";
|
|
6
|
+
import { Button } from "@/components/ui/button";
|
|
7
|
+
import { navItems } from "@/components/site/site-data";
|
|
8
|
+
import { useThemeContext } from "@/components/theme/theme-context";
|
|
9
|
+
import {
|
|
10
|
+
Wand2,
|
|
11
|
+
Command,
|
|
12
|
+
Github,
|
|
13
|
+
Coffee,
|
|
14
|
+
Menu,
|
|
15
|
+
X,
|
|
16
|
+
Sparkles,
|
|
17
|
+
Sun,
|
|
18
|
+
Moon,
|
|
19
|
+
} from "lucide-react";
|
|
20
|
+
import { usePathname } from "next/navigation";
|
|
21
|
+
import { cn } from "@/lib/utils";
|
|
22
|
+
import { MagneticButton } from "@/components/ui/gsap-animated";
|
|
23
|
+
|
|
24
|
+
export function SiteHeader() {
|
|
25
|
+
const pathname = usePathname();
|
|
26
|
+
const { colorMode, setColorMode } = useThemeContext();
|
|
27
|
+
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
|
28
|
+
const headerRef = useRef<HTMLElement>(null);
|
|
29
|
+
const mobileMenuRef = useRef<HTMLDivElement>(null);
|
|
30
|
+
const isLightMode = colorMode === "light";
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
const header = headerRef.current;
|
|
34
|
+
if (!header) return;
|
|
35
|
+
|
|
36
|
+
// Header entrance animation
|
|
37
|
+
gsap.fromTo(
|
|
38
|
+
header,
|
|
39
|
+
{ opacity: 0, y: -20 },
|
|
40
|
+
{ opacity: 1, y: 0, duration: 0.6, ease: "power3.out" }
|
|
41
|
+
);
|
|
42
|
+
}, []);
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (mobileMenuOpen && mobileMenuRef.current) {
|
|
46
|
+
// Animate mobile menu open
|
|
47
|
+
gsap.fromTo(
|
|
48
|
+
mobileMenuRef.current,
|
|
49
|
+
{ opacity: 0, y: -10 },
|
|
50
|
+
{ opacity: 1, y: 0, duration: 0.3, ease: "power2.out" }
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// Animate menu items
|
|
54
|
+
const items = mobileMenuRef.current.querySelectorAll(".mobile-nav-item");
|
|
55
|
+
gsap.fromTo(
|
|
56
|
+
items,
|
|
57
|
+
{ opacity: 0, x: -20 },
|
|
58
|
+
{ opacity: 1, x: 0, duration: 0.3, stagger: 0.05, ease: "power2.out", delay: 0.1 }
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}, [mobileMenuOpen]);
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<header ref={headerRef} className="relative z-50">
|
|
65
|
+
<div className="rounded-2xl sm:rounded-full border border-border/40 bg-background/50 px-3 sm:px-4 py-2 sm:py-3 backdrop-blur-xl">
|
|
66
|
+
<div className="flex items-center justify-between gap-3">
|
|
67
|
+
{/* Logo */}
|
|
68
|
+
<div className="flex items-center gap-2 sm:gap-3">
|
|
69
|
+
<MagneticButton strength={0.1}>
|
|
70
|
+
<Badge variant="glass" className="gap-1.5 sm:gap-2 cursor-pointer text-xs sm:text-sm py-1 sm:py-1.5">
|
|
71
|
+
<Wand2 className="h-3 w-3 sm:h-4 sm:w-4" />
|
|
72
|
+
<span className="hidden sm:inline">Gradient Forge</span>
|
|
73
|
+
<span className="sm:hidden">GF</span>
|
|
74
|
+
</Badge>
|
|
75
|
+
</MagneticButton>
|
|
76
|
+
<span className="hidden lg:inline text-xs text-muted-foreground">
|
|
77
|
+
Nitro-inspired theming studio
|
|
78
|
+
</span>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
{/* Desktop Navigation */}
|
|
82
|
+
<nav className="hidden md:flex items-center gap-1">
|
|
83
|
+
{navItems.map((item) => {
|
|
84
|
+
const isActive = pathname === item.href;
|
|
85
|
+
return (
|
|
86
|
+
<MagneticButton key={item.label} strength={0.05}>
|
|
87
|
+
<Button
|
|
88
|
+
variant={isActive ? "default" : "ghost"}
|
|
89
|
+
size="sm"
|
|
90
|
+
className={cn(
|
|
91
|
+
"text-xs sm:text-sm h-8 sm:h-9",
|
|
92
|
+
isActive && "shadow-md"
|
|
93
|
+
)}
|
|
94
|
+
asChild
|
|
95
|
+
>
|
|
96
|
+
<a href={item.href}>{item.label}</a>
|
|
97
|
+
</Button>
|
|
98
|
+
</MagneticButton>
|
|
99
|
+
);
|
|
100
|
+
})}
|
|
101
|
+
|
|
102
|
+
<div className="h-4 w-px bg-border mx-1" />
|
|
103
|
+
|
|
104
|
+
<div className="flex items-center rounded-full border border-border/60 bg-background/60 p-1">
|
|
105
|
+
<Button
|
|
106
|
+
variant={isLightMode ? "default" : "ghost"}
|
|
107
|
+
size="sm"
|
|
108
|
+
className="h-7 gap-1 px-2 text-xs"
|
|
109
|
+
onClick={() => setColorMode("light")}
|
|
110
|
+
aria-pressed={isLightMode}
|
|
111
|
+
>
|
|
112
|
+
<Sun className="h-3.5 w-3.5" />
|
|
113
|
+
<span>Light</span>
|
|
114
|
+
</Button>
|
|
115
|
+
<Button
|
|
116
|
+
variant={!isLightMode ? "default" : "ghost"}
|
|
117
|
+
size="sm"
|
|
118
|
+
className="h-7 gap-1 px-2 text-xs"
|
|
119
|
+
onClick={() => setColorMode("dark")}
|
|
120
|
+
aria-pressed={!isLightMode}
|
|
121
|
+
>
|
|
122
|
+
<Moon className="h-3.5 w-3.5" />
|
|
123
|
+
<span>Dark</span>
|
|
124
|
+
</Button>
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
{/* Command Palette Trigger */}
|
|
128
|
+
<MagneticButton strength={0.05}>
|
|
129
|
+
<Button
|
|
130
|
+
variant="ghost"
|
|
131
|
+
size="sm"
|
|
132
|
+
className="gap-1.5 text-muted-foreground text-xs sm:text-sm h-8 sm:h-9"
|
|
133
|
+
onClick={() => {
|
|
134
|
+
window.dispatchEvent(new KeyboardEvent("keydown", { key: "k", metaKey: true }));
|
|
135
|
+
}}
|
|
136
|
+
>
|
|
137
|
+
<Command className="h-3 w-3 sm:h-3.5 sm:w-3.5" />
|
|
138
|
+
<span className="hidden lg:inline text-xs">Cmd K</span>
|
|
139
|
+
</Button>
|
|
140
|
+
</MagneticButton>
|
|
141
|
+
|
|
142
|
+
{/* Social Links */}
|
|
143
|
+
<MagneticButton strength={0.05}>
|
|
144
|
+
<Button
|
|
145
|
+
variant="ghost"
|
|
146
|
+
size="sm"
|
|
147
|
+
className="px-2 h-8 sm:h-9"
|
|
148
|
+
asChild
|
|
149
|
+
>
|
|
150
|
+
<a href="https://github.com" target="_blank" rel="noopener noreferrer">
|
|
151
|
+
<Github className="h-3.5 w-3.5 sm:h-4 sm:w-4" />
|
|
152
|
+
</a>
|
|
153
|
+
</Button>
|
|
154
|
+
</MagneticButton>
|
|
155
|
+
|
|
156
|
+
<MagneticButton strength={0.05}>
|
|
157
|
+
<Button
|
|
158
|
+
variant="ghost"
|
|
159
|
+
size="sm"
|
|
160
|
+
className="px-2 h-8 sm:h-9"
|
|
161
|
+
asChild
|
|
162
|
+
>
|
|
163
|
+
<a href="https://buymeacoffee.com/karannn" target="_blank" rel="noopener noreferrer">
|
|
164
|
+
<Coffee className="h-3.5 w-3.5 sm:h-4 sm:w-4" />
|
|
165
|
+
</a>
|
|
166
|
+
</Button>
|
|
167
|
+
</MagneticButton>
|
|
168
|
+
</nav>
|
|
169
|
+
|
|
170
|
+
{/* Mobile Menu Button */}
|
|
171
|
+
<div className="flex md:hidden items-center gap-1">
|
|
172
|
+
<div className="flex items-center rounded-full border border-border/60 bg-background/60 p-1">
|
|
173
|
+
<Button
|
|
174
|
+
variant={isLightMode ? "default" : "ghost"}
|
|
175
|
+
size="sm"
|
|
176
|
+
className="h-7 px-2 text-[11px]"
|
|
177
|
+
onClick={() => setColorMode("light")}
|
|
178
|
+
aria-pressed={isLightMode}
|
|
179
|
+
>
|
|
180
|
+
Light
|
|
181
|
+
</Button>
|
|
182
|
+
<Button
|
|
183
|
+
variant={!isLightMode ? "default" : "ghost"}
|
|
184
|
+
size="sm"
|
|
185
|
+
className="h-7 px-2 text-[11px]"
|
|
186
|
+
onClick={() => setColorMode("dark")}
|
|
187
|
+
aria-pressed={!isLightMode}
|
|
188
|
+
>
|
|
189
|
+
Dark
|
|
190
|
+
</Button>
|
|
191
|
+
</div>
|
|
192
|
+
<MagneticButton strength={0.1}>
|
|
193
|
+
<Button
|
|
194
|
+
variant="ghost"
|
|
195
|
+
size="sm"
|
|
196
|
+
className="h-8 w-8 p-0"
|
|
197
|
+
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
|
198
|
+
>
|
|
199
|
+
{mobileMenuOpen ? (
|
|
200
|
+
<X className="h-4 w-4" />
|
|
201
|
+
) : (
|
|
202
|
+
<Menu className="h-4 w-4" />
|
|
203
|
+
)}
|
|
204
|
+
</Button>
|
|
205
|
+
</MagneticButton>
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
</div>
|
|
209
|
+
|
|
210
|
+
{/* Mobile Menu */}
|
|
211
|
+
{mobileMenuOpen && (
|
|
212
|
+
<div
|
|
213
|
+
ref={mobileMenuRef}
|
|
214
|
+
className="absolute top-full left-0 right-0 mt-2 mx-3 rounded-xl border border-border/40 bg-background/95 backdrop-blur-xl shadow-xl overflow-hidden md:hidden"
|
|
215
|
+
>
|
|
216
|
+
<nav className="flex flex-col p-2">
|
|
217
|
+
{navItems.map((item) => {
|
|
218
|
+
const isActive = pathname === item.href;
|
|
219
|
+
return (
|
|
220
|
+
<a
|
|
221
|
+
key={item.label}
|
|
222
|
+
href={item.href}
|
|
223
|
+
className={cn(
|
|
224
|
+
"mobile-nav-item flex items-center justify-between px-3 py-2.5 rounded-lg text-sm transition-colors",
|
|
225
|
+
isActive
|
|
226
|
+
? "bg-primary text-primary-foreground"
|
|
227
|
+
: "text-foreground hover:bg-muted"
|
|
228
|
+
)}
|
|
229
|
+
onClick={() => setMobileMenuOpen(false)}
|
|
230
|
+
>
|
|
231
|
+
{item.label}
|
|
232
|
+
{isActive && <Sparkles className="h-3 w-3" />}
|
|
233
|
+
</a>
|
|
234
|
+
);
|
|
235
|
+
})}
|
|
236
|
+
|
|
237
|
+
<div className="h-px bg-border my-2" />
|
|
238
|
+
|
|
239
|
+
<button
|
|
240
|
+
className="mobile-nav-item flex items-center gap-2 px-3 py-2.5 rounded-lg text-sm text-muted-foreground hover:bg-muted transition-colors"
|
|
241
|
+
onClick={() => {
|
|
242
|
+
setMobileMenuOpen(false);
|
|
243
|
+
window.dispatchEvent(new KeyboardEvent("keydown", { key: "k", metaKey: true }));
|
|
244
|
+
}}
|
|
245
|
+
>
|
|
246
|
+
<Command className="h-4 w-4" />
|
|
247
|
+
Command Palette
|
|
248
|
+
<span className="ml-auto text-xs bg-muted px-1.5 py-0.5 rounded">Cmd K</span>
|
|
249
|
+
</button>
|
|
250
|
+
|
|
251
|
+
<div className="flex items-center gap-2 px-3 py-2 mt-1">
|
|
252
|
+
<Button variant="ghost" size="sm" className="flex-1 h-9" asChild>
|
|
253
|
+
<a href="https://github.com" target="_blank" rel="noopener noreferrer">
|
|
254
|
+
<Github className="h-4 w-4 mr-2" />
|
|
255
|
+
GitHub
|
|
256
|
+
</a>
|
|
257
|
+
</Button>
|
|
258
|
+
<Button variant="ghost" size="sm" className="flex-1 h-9" asChild>
|
|
259
|
+
<a href="https://buymeacoffee.com/karannn" target="_blank" rel="noopener noreferrer">
|
|
260
|
+
<Coffee className="h-4 w-4 mr-2" />
|
|
261
|
+
Coffee
|
|
262
|
+
</a>
|
|
263
|
+
</Button>
|
|
264
|
+
</div>
|
|
265
|
+
</nav>
|
|
266
|
+
</div>
|
|
267
|
+
)}
|
|
268
|
+
</header>
|
|
269
|
+
);
|
|
270
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Card, CardContent } from "@/components/ui/card";
|
|
4
|
+
import { Badge } from "@/components/ui/badge";
|
|
5
|
+
import { Button } from "@/components/ui/button";
|
|
6
|
+
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
|
7
|
+
import {
|
|
8
|
+
Calendar,
|
|
9
|
+
Clock,
|
|
10
|
+
ArrowRight,
|
|
11
|
+
ChevronLeft,
|
|
12
|
+
ChevronRight,
|
|
13
|
+
Search,
|
|
14
|
+
Tag
|
|
15
|
+
} from "lucide-react";
|
|
16
|
+
import { cn } from "@/lib/utils";
|
|
17
|
+
|
|
18
|
+
interface BlogTemplateProps {
|
|
19
|
+
preview?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function BlogTemplate({ preview }: BlogTemplateProps) {
|
|
23
|
+
const featuredPost = {
|
|
24
|
+
title: "Building Beautiful UIs with Gradient Forge",
|
|
25
|
+
excerpt: "Learn how to create stunning gradient themes and apply them to your shadcn/ui components for a cohesive design system.",
|
|
26
|
+
author: "Sarah Chen",
|
|
27
|
+
date: "January 15, 2024",
|
|
28
|
+
readTime: "8 min read",
|
|
29
|
+
category: "Design",
|
|
30
|
+
image: "bg-gradient-to-br from-primary/30 to-accent/30"
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const posts = [
|
|
34
|
+
{
|
|
35
|
+
id: 1,
|
|
36
|
+
title: "The Future of CSS Variables in Modern Web Design",
|
|
37
|
+
excerpt: "Explore how CSS custom properties are revolutionizing the way we build and maintain design systems.",
|
|
38
|
+
author: "Alex Rivera",
|
|
39
|
+
date: "Jan 12, 2024",
|
|
40
|
+
readTime: "5 min",
|
|
41
|
+
category: "Development",
|
|
42
|
+
image: "bg-muted"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: 2,
|
|
46
|
+
title: "Creating Accessible Color Palettes",
|
|
47
|
+
excerpt: "A comprehensive guide to building color systems that work for everyone, including WCAG compliance tips.",
|
|
48
|
+
author: "Emily Watson",
|
|
49
|
+
date: "Jan 10, 2024",
|
|
50
|
+
readTime: "6 min",
|
|
51
|
+
category: "Accessibility",
|
|
52
|
+
image: "bg-muted"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: 3,
|
|
56
|
+
title: "Mastering Dark Mode Design",
|
|
57
|
+
excerpt: "Best practices for implementing dark mode that looks great and reduces eye strain for your users.",
|
|
58
|
+
author: "Michael Park",
|
|
59
|
+
date: "Jan 8, 2024",
|
|
60
|
+
readTime: "4 min",
|
|
61
|
+
category: "UI Design",
|
|
62
|
+
image: "bg-muted"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: 4,
|
|
66
|
+
title: "React Server Components: A Deep Dive",
|
|
67
|
+
excerpt: "Understanding the architecture and benefits of React Server Components in Next.js applications.",
|
|
68
|
+
author: "Lisa Johnson",
|
|
69
|
+
date: "Jan 5, 2024",
|
|
70
|
+
readTime: "10 min",
|
|
71
|
+
category: "Engineering",
|
|
72
|
+
image: "bg-muted"
|
|
73
|
+
}
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
const tags = ["Design", "Development", "UI/UX", "Accessibility", "React", "CSS", "TypeScript"];
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<div className="min-h-full bg-background">
|
|
80
|
+
{/* Header */}
|
|
81
|
+
<header className="border-b border-border/50 bg-background/80 backdrop-blur-xl sticky top-0 z-50">
|
|
82
|
+
<div className="max-w-4xl mx-auto h-14 flex items-center justify-between px-4">
|
|
83
|
+
<h1 className="font-semibold text-lg">The Gradient Blog</h1>
|
|
84
|
+
<div className="flex items-center gap-3">
|
|
85
|
+
<div className="relative hidden sm:block">
|
|
86
|
+
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
87
|
+
<input
|
|
88
|
+
type="text"
|
|
89
|
+
placeholder="Search..."
|
|
90
|
+
className="pl-9 pr-4 py-2 rounded-full border border-border/50 bg-background text-sm focus:outline-none focus:ring-2 focus:ring-primary/20 w-48"
|
|
91
|
+
/>
|
|
92
|
+
</div>
|
|
93
|
+
<Button variant="outline" size="sm" className="text-xs">
|
|
94
|
+
Subscribe
|
|
95
|
+
</Button>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
</header>
|
|
99
|
+
|
|
100
|
+
<main className="max-w-4xl mx-auto px-4 py-8 sm:py-12">
|
|
101
|
+
{/* Featured Article */}
|
|
102
|
+
<article className="mb-12 sm:mb-16">
|
|
103
|
+
<div className={cn("h-48 sm:h-80 rounded-2xl mb-6 overflow-hidden", featuredPost.image)}>
|
|
104
|
+
<div className="h-full w-full bg-gradient-to-t from-background/80 to-transparent flex items-end p-6">
|
|
105
|
+
<Badge variant="glass" className="text-xs">{featuredPost.category}</Badge>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
<h2 className="text-2xl sm:text-4xl font-bold mb-4 leading-tight">
|
|
110
|
+
{featuredPost.title}
|
|
111
|
+
</h2>
|
|
112
|
+
|
|
113
|
+
<p className="text-base sm:text-lg text-muted-foreground mb-4">
|
|
114
|
+
{featuredPost.excerpt}
|
|
115
|
+
</p>
|
|
116
|
+
|
|
117
|
+
<div className="flex items-center justify-between">
|
|
118
|
+
<div className="flex items-center gap-3">
|
|
119
|
+
<Avatar className="h-9 w-9">
|
|
120
|
+
<AvatarFallback className="bg-primary/20 text-xs">
|
|
121
|
+
{featuredPost.author.split(" ").map(n => n[0]).join("")}
|
|
122
|
+
</AvatarFallback>
|
|
123
|
+
</Avatar>
|
|
124
|
+
<div>
|
|
125
|
+
<p className="text-sm font-medium">{featuredPost.author}</p>
|
|
126
|
+
<div className="flex items-center gap-3 text-xs text-muted-foreground">
|
|
127
|
+
<span className="flex items-center gap-1">
|
|
128
|
+
<Calendar className="h-3 w-3" /> {featuredPost.date}
|
|
129
|
+
</span>
|
|
130
|
+
<span className="flex items-center gap-1">
|
|
131
|
+
<Clock className="h-3 w-3" /> {featuredPost.readTime}
|
|
132
|
+
</span>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
<Button variant="ghost" size="sm" className="gap-1 text-xs hidden sm:flex">
|
|
137
|
+
Read More <ArrowRight className="h-3.5 w-3.5" />
|
|
138
|
+
</Button>
|
|
139
|
+
</div>
|
|
140
|
+
</article>
|
|
141
|
+
|
|
142
|
+
{/* Tags */}
|
|
143
|
+
<div className="flex flex-wrap gap-2 mb-8 sm:mb-12">
|
|
144
|
+
<span className="text-sm text-muted-foreground flex items-center gap-1">
|
|
145
|
+
<Tag className="h-4 w-4" /> Popular:
|
|
146
|
+
</span>
|
|
147
|
+
{tags.map((tag) => (
|
|
148
|
+
<Badge key={tag} variant="outline" className="cursor-pointer hover:bg-primary/10 text-xs">
|
|
149
|
+
{tag}
|
|
150
|
+
</Badge>
|
|
151
|
+
))}
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
{/* Recent Articles */}
|
|
155
|
+
<div>
|
|
156
|
+
<h3 className="text-xl font-semibold mb-6">Recent Articles</h3>
|
|
157
|
+
<div className="grid sm:grid-cols-2 gap-4 sm:gap-6">
|
|
158
|
+
{posts.map((post) => (
|
|
159
|
+
<Card key={post.id} className="border-border/50 cursor-pointer hover:shadow-lg transition-shadow group">
|
|
160
|
+
<CardContent className="p-4 sm:p-5">
|
|
161
|
+
<div className={cn("h-32 sm:h-40 rounded-lg mb-4", post.image)} />
|
|
162
|
+
<Badge variant="glass" className="mb-2 text-[10px]">
|
|
163
|
+
{post.category}
|
|
164
|
+
</Badge>
|
|
165
|
+
<h4 className="font-semibold text-sm sm:text-base mb-2 line-clamp-2 group-hover:text-primary transition-colors">
|
|
166
|
+
{post.title}
|
|
167
|
+
</h4>
|
|
168
|
+
<p className="text-xs sm:text-sm text-muted-foreground mb-3 line-clamp-2">
|
|
169
|
+
{post.excerpt}
|
|
170
|
+
</p>
|
|
171
|
+
<div className="flex items-center justify-between text-xs text-muted-foreground">
|
|
172
|
+
<span>{post.author}</span>
|
|
173
|
+
<span className="flex items-center gap-1">
|
|
174
|
+
<Clock className="h-3 w-3" /> {post.readTime}
|
|
175
|
+
</span>
|
|
176
|
+
</div>
|
|
177
|
+
</CardContent>
|
|
178
|
+
</Card>
|
|
179
|
+
))}
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
|
|
183
|
+
{/* Pagination */}
|
|
184
|
+
<div className="flex items-center justify-center gap-2 mt-8 sm:mt-12">
|
|
185
|
+
<Button variant="outline" size="sm" className="h-8 w-8 p-0">
|
|
186
|
+
<ChevronLeft className="h-4 w-4" />
|
|
187
|
+
</Button>
|
|
188
|
+
<Button variant="default" size="sm" className="h-8 w-8 p-0 text-xs">1</Button>
|
|
189
|
+
<Button variant="outline" size="sm" className="h-8 w-8 p-0 text-xs">2</Button>
|
|
190
|
+
<Button variant="outline" size="sm" className="h-8 w-8 p-0 text-xs">3</Button>
|
|
191
|
+
<Button variant="outline" size="sm" className="h-8 w-8 p-0">
|
|
192
|
+
<ChevronRight className="h-4 w-4" />
|
|
193
|
+
</Button>
|
|
194
|
+
</div>
|
|
195
|
+
</main>
|
|
196
|
+
</div>
|
|
197
|
+
);
|
|
198
|
+
}
|