@stevederico/skateboard-ui 0.7.3 → 0.7.6
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/.claude/settings.local.json +8 -0
- package/CHANGELOG.md +17 -0
- package/LandingView.jsx +395 -23
- package/LandingViewSimple.jsx +43 -0
- package/package.json +1 -1
- package/shadcn/ui/sidebar.jsx +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
0.7.6
|
|
4
|
+
|
|
5
|
+
Reduce sidebar width
|
|
6
|
+
|
|
7
|
+
0.7.5
|
|
8
|
+
|
|
9
|
+
Use constants for features
|
|
10
|
+
Update pricing section text
|
|
11
|
+
Simplify feature rendering
|
|
12
|
+
|
|
13
|
+
0.7.4
|
|
14
|
+
|
|
15
|
+
Enhanced landing page design
|
|
16
|
+
Add dark mode toggle
|
|
17
|
+
Implement modern hero section
|
|
18
|
+
Add features pricing sections
|
|
19
|
+
|
|
3
20
|
0.7.3
|
|
4
21
|
auth improvements
|
|
5
22
|
|
package/LandingView.jsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { useNavigate } from 'react-router-dom';
|
|
2
3
|
import constants from "@/constants.json";
|
|
3
4
|
import * as LucideIcons from "lucide-react";
|
|
4
5
|
|
|
@@ -11,33 +12,404 @@ const DynamicIcon = ({ name, size = 24, color = 'currentColor', strokeWidth = 2,
|
|
|
11
12
|
};
|
|
12
13
|
|
|
13
14
|
export default function LandingView() {
|
|
15
|
+
const navigate = useNavigate();
|
|
16
|
+
const [isDarkMode, setIsDarkMode] = useState(false);
|
|
17
|
+
|
|
18
|
+
// Initialize dark mode from localStorage or system preference
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const savedMode = localStorage.getItem('darkMode');
|
|
21
|
+
const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
22
|
+
|
|
23
|
+
if (savedMode !== null) {
|
|
24
|
+
setIsDarkMode(savedMode === 'true');
|
|
25
|
+
} else {
|
|
26
|
+
setIsDarkMode(systemPrefersDark);
|
|
27
|
+
}
|
|
28
|
+
}, []);
|
|
29
|
+
|
|
30
|
+
// Apply dark mode to document
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (isDarkMode) {
|
|
33
|
+
document.documentElement.classList.add('dark');
|
|
34
|
+
} else {
|
|
35
|
+
document.documentElement.classList.remove('dark');
|
|
36
|
+
}
|
|
37
|
+
localStorage.setItem('darkMode', isDarkMode.toString());
|
|
38
|
+
}, [isDarkMode]);
|
|
39
|
+
|
|
40
|
+
const toggleDarkMode = () => {
|
|
41
|
+
setIsDarkMode(!isDarkMode);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const renderHeroContent = () => {
|
|
45
|
+
return (
|
|
46
|
+
<div className="max-w-7xl mx-auto px-8 md:px-6">
|
|
47
|
+
<div className="relative min-h-[120px] md:h-[140px] flex flex-col items-center justify-center py-8">
|
|
48
|
+
{/* CTA in Hero */}
|
|
49
|
+
<div className="text-center">
|
|
50
|
+
<button
|
|
51
|
+
onClick={() => navigate('/app')}
|
|
52
|
+
className="relative group bg-gradient-to-br text-white px-6 py-3 sm:px-8 sm:py-4 md:px-12 md:py-6 rounded-2xl font-semibold text-base sm:text-lg md:text-xl lg:text-2xl transition-all duration-300 shadow-xl backdrop-blur-sm overflow-hidden cursor-pointer"
|
|
53
|
+
style={{
|
|
54
|
+
backgroundImage: `linear-gradient(to bottom right,
|
|
55
|
+
var(--color-app),
|
|
56
|
+
oklch(from var(--color-app) calc(l - 0.05) c h),
|
|
57
|
+
oklch(from var(--color-app) calc(l - 0.08) c h),
|
|
58
|
+
oklch(from var(--color-app) calc(l - 0.12) c h))`,
|
|
59
|
+
boxShadow: `0 25px 50px -12px var(--shadow-color)`
|
|
60
|
+
}}
|
|
61
|
+
onMouseEnter={(e) => {
|
|
62
|
+
e.currentTarget.style.backgroundImage = `linear-gradient(to bottom right,
|
|
63
|
+
oklch(from var(--color-app) calc(l - 0.05) c h),
|
|
64
|
+
oklch(from var(--color-app) calc(l - 0.08) c h),
|
|
65
|
+
oklch(from var(--color-app) calc(l - 0.12) c h),
|
|
66
|
+
oklch(from var(--color-app) calc(l - 0.16) c h))`;
|
|
67
|
+
e.currentTarget.style.boxShadow = '0 25px 50px -12px var(--shadow-color)'
|
|
68
|
+
}}
|
|
69
|
+
onMouseLeave={(e) => {
|
|
70
|
+
e.currentTarget.style.backgroundImage = `linear-gradient(to bottom right,
|
|
71
|
+
var(--color-app),
|
|
72
|
+
oklch(from var(--color-app) calc(l - 0.05) c h),
|
|
73
|
+
oklch(from var(--color-app) calc(l - 0.08) c h),
|
|
74
|
+
oklch(from var(--color-app) calc(l - 0.12) c h))`;
|
|
75
|
+
e.currentTarget.style.boxShadow = '0 25px 50px -12px var(--shadow-color)'
|
|
76
|
+
}}
|
|
77
|
+
>
|
|
78
|
+
<span className="relative z-20 flex items-center justify-center gap-3 md:gap-4 drop-shadow-sm">
|
|
79
|
+
<DynamicIcon name="sparkles" size={20} color="currentColor" strokeWidth={2} className="animate-pulse -ml-1 md:w-6 md:h-6 lg:w-7 lg:h-7" />
|
|
80
|
+
{constants.cta}
|
|
81
|
+
</span>
|
|
82
|
+
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/15 to-transparent -translate-x-full group-hover:translate-x-full transition-transform duration-800 skew-x-12"></div>
|
|
83
|
+
</button>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<style jsx>{`
|
|
88
|
+
@keyframes springOutLong {
|
|
89
|
+
0% {
|
|
90
|
+
opacity: 0;
|
|
91
|
+
transform: translateY(-60px) scale(0.1);
|
|
92
|
+
}
|
|
93
|
+
50% {
|
|
94
|
+
opacity: 1;
|
|
95
|
+
transform: translateY(0) scale(1.2);
|
|
96
|
+
}
|
|
97
|
+
100% {
|
|
98
|
+
opacity: 1;
|
|
99
|
+
transform: translateY(0) scale(1);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@keyframes rollingGrid {
|
|
104
|
+
0% {
|
|
105
|
+
transform: rotateX(75deg) translateZ(-100px) scale(3) translateY(0px);
|
|
106
|
+
}
|
|
107
|
+
100% {
|
|
108
|
+
transform: rotateX(75deg) translateZ(-100px) scale(3) translateY(-300px);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.perspective-grid-container {
|
|
113
|
+
perspective: 800px;
|
|
114
|
+
overflow: hidden;
|
|
115
|
+
z-index: 1;
|
|
116
|
+
transform: translateY(-150px);
|
|
117
|
+
pointer-events: none;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.perspective-grid {
|
|
121
|
+
position: absolute;
|
|
122
|
+
top: 0;
|
|
123
|
+
left: -200vw;
|
|
124
|
+
right: -200vw;
|
|
125
|
+
bottom: 0;
|
|
126
|
+
width: 500vw;
|
|
127
|
+
background-image:
|
|
128
|
+
linear-gradient(color-mix(in srgb, var(--color-app) 50%, transparent 50%) 5px, transparent 5px),
|
|
129
|
+
linear-gradient(90deg, color-mix(in srgb, var(--color-app) 40%, transparent 60%) 5px, transparent 5px);
|
|
130
|
+
background-size: 300px 300px;
|
|
131
|
+
animation: rollingGrid 6s linear infinite;
|
|
132
|
+
transform: none;
|
|
133
|
+
transform-origin: center bottom;
|
|
134
|
+
mask-image: linear-gradient(to bottom,
|
|
135
|
+
transparent 0%,
|
|
136
|
+
rgba(0,0,0,0.1) 20%,
|
|
137
|
+
rgba(0,0,0,0.8) 40%,
|
|
138
|
+
rgba(0,0,0,0.3) 80%,
|
|
139
|
+
transparent 100%);
|
|
140
|
+
-webkit-mask-image: linear-gradient(to bottom,
|
|
141
|
+
transparent 0%,
|
|
142
|
+
rgba(0,0,0,0.1) 20%,
|
|
143
|
+
rgba(0,0,0,0.8) 40%,
|
|
144
|
+
rgba(0,0,0,0.3) 80%,
|
|
145
|
+
transparent 100%);
|
|
146
|
+
height: 300%;
|
|
147
|
+
}
|
|
148
|
+
`}</style>
|
|
149
|
+
</div>
|
|
150
|
+
);
|
|
151
|
+
};
|
|
152
|
+
|
|
14
153
|
return (
|
|
15
|
-
<div
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
154
|
+
<div
|
|
155
|
+
className="min-h-screen bg-white dark:bg-gray-900 text-gray-900 dark:text-white relative transition-colors duration-300"
|
|
156
|
+
style={{
|
|
157
|
+
'--gradient-from': 'color-mix(in srgb, var(--color-app) 90%, white 10%)',
|
|
158
|
+
'--gradient-via': 'var(--color-app)',
|
|
159
|
+
'--gradient-to': 'oklch(from var(--color-app) calc(l - 0.1) calc(c * 1.2) calc(h + 30))',
|
|
160
|
+
'--gradient-light': 'color-mix(in srgb, var(--color-app) 10%, white 90%)',
|
|
161
|
+
'--shadow-color': 'color-mix(in srgb, var(--color-app) 40%, transparent 60%)',
|
|
162
|
+
'--text-gradient-from': 'oklch(from var(--color-app) calc(l - 0.05) c h)',
|
|
163
|
+
'--text-gradient-to': 'oklch(from var(--color-app) l c calc(h + 30))'
|
|
164
|
+
}}
|
|
165
|
+
>
|
|
166
|
+
|
|
167
|
+
{/* Subtle Grid Pattern */}
|
|
168
|
+
<div className="absolute inset-0">
|
|
169
|
+
<div className="absolute inset-0" style={{
|
|
170
|
+
backgroundImage: `
|
|
171
|
+
linear-gradient(color-mix(in srgb, var(--color-app) 2%, transparent 98%) 1px, transparent 1px),
|
|
172
|
+
linear-gradient(90deg, color-mix(in srgb, var(--color-app) 2%, transparent 98%) 1px, transparent 1px)
|
|
173
|
+
`,
|
|
174
|
+
backgroundSize: '80px 80px'
|
|
175
|
+
}}></div>
|
|
176
|
+
</div>
|
|
177
|
+
|
|
178
|
+
{/* Header */}
|
|
179
|
+
<header className="relative z-10 pt-4 px-6">
|
|
180
|
+
<nav className="max-w-6xl mx-auto bg-white/80 dark:bg-gray-800/80 backdrop-blur-sm border border-gray-200 dark:border-gray-700 rounded-2xl shadow-lg px-6 py-4 flex justify-between items-center transition-colors duration-300">
|
|
181
|
+
<div className="flex items-center gap-2">
|
|
182
|
+
<DynamicIcon name={constants.appIcon} size={28} color="var(--color-app)" strokeWidth={2} />
|
|
183
|
+
<div
|
|
184
|
+
className="text-2xl font-bold bg-clip-text text-transparent"
|
|
185
|
+
style={{
|
|
186
|
+
backgroundImage: `linear-gradient(90deg, var(--text-gradient-from), var(--text-gradient-to))`
|
|
187
|
+
}}
|
|
188
|
+
>
|
|
189
|
+
{constants.appName}
|
|
20
190
|
</div>
|
|
21
|
-
<div className="font-semibold ml-2 text-2xl text-gray-700">{constants.appName}</div>
|
|
22
191
|
</div>
|
|
192
|
+
|
|
193
|
+
<ul className="hidden md:flex gap-8 list-none">
|
|
194
|
+
<li><a href="#features" className="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition-colors font-semibold">Features</a></li>
|
|
195
|
+
<li><a href="#pricing" className="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition-colors font-semibold">Pricing</a></li>
|
|
196
|
+
<li><a href="/terms" className="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition-colors font-semibold">Terms</a></li>
|
|
197
|
+
</ul>
|
|
198
|
+
|
|
199
|
+
<div className="flex gap-3 items-center">
|
|
200
|
+
{/* Dark Mode Toggle */}
|
|
201
|
+
<button
|
|
202
|
+
onClick={toggleDarkMode}
|
|
203
|
+
className="p-2 rounded-lg bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors duration-200"
|
|
204
|
+
aria-label="Toggle dark mode"
|
|
205
|
+
>
|
|
206
|
+
<DynamicIcon
|
|
207
|
+
name={isDarkMode ? "sun" : "moon"}
|
|
208
|
+
size={18}
|
|
209
|
+
color="currentColor"
|
|
210
|
+
strokeWidth={2}
|
|
211
|
+
className="text-gray-600 dark:text-gray-300"
|
|
212
|
+
/>
|
|
213
|
+
</button>
|
|
214
|
+
|
|
215
|
+
<button
|
|
216
|
+
onClick={() => navigate('/app')}
|
|
217
|
+
className="relative group bg-gradient-to-br text-white px-4 py-3 rounded-xl font-semibold text-sm transition-all duration-300 shadow-lg backdrop-blur-sm overflow-hidden cursor-pointer"
|
|
218
|
+
style={{
|
|
219
|
+
backgroundImage: `linear-gradient(to bottom right,
|
|
220
|
+
var(--color-app),
|
|
221
|
+
oklch(from var(--color-app) calc(l - 0.05) c h),
|
|
222
|
+
oklch(from var(--color-app) calc(l - 0.08) c h),
|
|
223
|
+
oklch(from var(--color-app) calc(l - 0.12) c h))`,
|
|
224
|
+
boxShadow: `0 8px 32px var(--shadow-color)`
|
|
225
|
+
}}
|
|
226
|
+
onMouseEnter={(e) => {
|
|
227
|
+
e.currentTarget.style.backgroundImage = `linear-gradient(to bottom right,
|
|
228
|
+
oklch(from var(--color-app) calc(l - 0.05) c h),
|
|
229
|
+
oklch(from var(--color-app) calc(l - 0.08) c h),
|
|
230
|
+
oklch(from var(--color-app) calc(l - 0.12) c h),
|
|
231
|
+
oklch(from var(--color-app) calc(l - 0.16) c h))`;
|
|
232
|
+
e.currentTarget.style.boxShadow = '0 8px 32px var(--shadow-color)'
|
|
233
|
+
}}
|
|
234
|
+
onMouseLeave={(e) => {
|
|
235
|
+
e.currentTarget.style.backgroundImage = `linear-gradient(to bottom right,
|
|
236
|
+
var(--color-app),
|
|
237
|
+
oklch(from var(--color-app) calc(l - 0.05) c h),
|
|
238
|
+
oklch(from var(--color-app) calc(l - 0.08) c h),
|
|
239
|
+
oklch(from var(--color-app) calc(l - 0.12) c h))`;
|
|
240
|
+
e.currentTarget.style.boxShadow = '0 8px 32px var(--shadow-color)'
|
|
241
|
+
}}
|
|
242
|
+
>
|
|
243
|
+
<span className="relative z-20 flex items-center justify-center gap-2 drop-shadow-sm">
|
|
244
|
+
<DynamicIcon name="sparkles" size={14} color="currentColor" strokeWidth={2} className="animate-pulse" />
|
|
245
|
+
{constants.cta}
|
|
246
|
+
</span>
|
|
247
|
+
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/15 to-transparent -translate-x-full group-hover:translate-x-full transition-transform duration-800 skew-x-12"></div>
|
|
248
|
+
</button>
|
|
249
|
+
</div>
|
|
250
|
+
</nav>
|
|
23
251
|
</header>
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
252
|
+
|
|
253
|
+
<main className="relative z-10">
|
|
254
|
+
{/* Hero Section */}
|
|
255
|
+
<section className="text-center flex flex-col justify-start relative pt-48 pb-12 md:pt-56 md:min-h-screen md:pb-0">
|
|
256
|
+
{/* Perspective Grid Background */}
|
|
257
|
+
<div className="absolute inset-0 perspective-grid-container hidden md:block">
|
|
258
|
+
<div className="perspective-grid"></div>
|
|
259
|
+
</div>
|
|
260
|
+
<div className="max-w-7xl mx-auto px-6 relative z-20 flex flex-col justify-center min-h-[30vh] md:min-h-[40vh]">
|
|
261
|
+
<div className="max-w-4xl mx-auto mb-12">
|
|
262
|
+
<h1
|
|
263
|
+
className="text-5xl sm:text-6xl md:text-7xl lg:text-8xl font-bold mb-0 leading-tight bg-clip-text text-transparent"
|
|
264
|
+
style={{
|
|
265
|
+
backgroundImage: `linear-gradient(90deg, var(--text-gradient-from), var(--text-gradient-to))`
|
|
266
|
+
}}
|
|
267
|
+
>
|
|
268
|
+
{constants.tagline}
|
|
269
|
+
</h1>
|
|
270
|
+
</div>
|
|
271
|
+
|
|
272
|
+
{/* Hero Content */}
|
|
273
|
+
{renderHeroContent()}
|
|
274
|
+
</div>
|
|
275
|
+
</section>
|
|
276
|
+
|
|
277
|
+
{/* Features Section */}
|
|
278
|
+
<section id="features" className="bg-slate-100 dark:bg-gray-800 py-12 md:py-20 transition-colors duration-300">
|
|
279
|
+
<div className="max-w-7xl mx-auto px-6">
|
|
280
|
+
<h2 className="text-center text-4xl md:text-5xl font-bold mb-16 text-gray-900 dark:text-white">{constants.features.title}</h2>
|
|
281
|
+
|
|
282
|
+
{/* Feature Grid */}
|
|
283
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 max-w-5xl mx-auto">
|
|
284
|
+
{constants.features.items.map((feature, index) => (
|
|
285
|
+
<div key={index} className="bg-white dark:bg-gray-700 rounded-2xl p-8 shadow-lg text-center transition-colors duration-300">
|
|
286
|
+
<div className="text-4xl mb-6">{feature.icon}</div>
|
|
287
|
+
<h3 className="text-xl font-bold mb-4 text-gray-900 dark:text-white">{feature.title}</h3>
|
|
288
|
+
<p className="text-gray-600 dark:text-gray-300">{feature.description}</p>
|
|
289
|
+
</div>
|
|
290
|
+
))}
|
|
291
|
+
</div>
|
|
292
|
+
</div>
|
|
293
|
+
</section>
|
|
294
|
+
|
|
295
|
+
{/* Pricing Section */}
|
|
296
|
+
<section
|
|
297
|
+
id="pricing"
|
|
298
|
+
className="py-20 transition-colors duration-300"
|
|
299
|
+
style={{
|
|
300
|
+
background: isDarkMode
|
|
301
|
+
? 'linear-gradient(135deg, oklch(0.25 0 0), oklch(0.2 0 0))'
|
|
302
|
+
: `linear-gradient(135deg, color-mix(in srgb, var(--gradient-light) 80%, white 20%), color-mix(in srgb, var(--gradient-light) 60%, white 40%))`
|
|
303
|
+
}}
|
|
304
|
+
>
|
|
305
|
+
<div className="max-w-7xl mx-auto px-6">
|
|
306
|
+
<h2 className="text-center text-4xl md:text-5xl font-bold mb-16 text-gray-900 dark:text-white">Pricing</h2>
|
|
307
|
+
<div className="max-w-md mx-auto">
|
|
308
|
+
<div
|
|
309
|
+
className="bg-white dark:bg-gray-700 rounded-2xl p-8 shadow-lg border-2 transition-colors duration-300"
|
|
310
|
+
style={{
|
|
311
|
+
borderColor: isDarkMode
|
|
312
|
+
? `color-mix(in srgb, var(--color-app) 40%, transparent 60%)`
|
|
313
|
+
: `color-mix(in srgb, var(--color-app) 20%, transparent 80%)`
|
|
314
|
+
}}
|
|
315
|
+
>
|
|
316
|
+
<div className="text-center">
|
|
317
|
+
<h3 className="text-2xl font-bold mb-4 text-gray-900 dark:text-white">{constants.stripeProducts[0]?.title || 'Monthly Plan'}</h3>
|
|
318
|
+
<div
|
|
319
|
+
className="text-5xl font-bold mb-2"
|
|
320
|
+
style={{ color: 'var(--color-app)' }}
|
|
321
|
+
>{constants.stripeProducts[0]?.price || '$5.00'}</div>
|
|
322
|
+
<p className="text-gray-600 dark:text-gray-300 mb-8">per month</p>
|
|
323
|
+
<ul className="text-left space-y-4 mb-8">
|
|
324
|
+
{constants.features.items.map((feature, index) => (
|
|
325
|
+
<li key={index} className="flex items-center">
|
|
326
|
+
✅ {feature.title}
|
|
327
|
+
</li>
|
|
328
|
+
))}
|
|
329
|
+
<li className="flex items-center">
|
|
330
|
+
✅ Priority Customer Support
|
|
331
|
+
</li>
|
|
332
|
+
<li className="flex items-center">
|
|
333
|
+
✅ Cancel anytime
|
|
334
|
+
</li>
|
|
335
|
+
</ul>
|
|
336
|
+
<button
|
|
337
|
+
onClick={() => navigate('/app')}
|
|
338
|
+
className="relative group w-full bg-gradient-to-br text-white text-lg px-8 py-4 rounded-xl font-semibold transition-all duration-300 shadow-xl backdrop-blur-sm overflow-hidden cursor-pointer"
|
|
339
|
+
style={{
|
|
340
|
+
backgroundImage: `linear-gradient(to bottom right,
|
|
341
|
+
var(--color-app),
|
|
342
|
+
oklch(from var(--color-app) calc(l - 0.05) c h),
|
|
343
|
+
oklch(from var(--color-app) calc(l - 0.08) c h),
|
|
344
|
+
oklch(from var(--color-app) calc(l - 0.12) c h))`,
|
|
345
|
+
boxShadow: `0 25px 50px -12px var(--shadow-color)`
|
|
346
|
+
}}
|
|
347
|
+
onMouseEnter={(e) => {
|
|
348
|
+
e.currentTarget.style.backgroundImage = `linear-gradient(to bottom right,
|
|
349
|
+
oklch(from var(--color-app) calc(l - 0.05) c h),
|
|
350
|
+
oklch(from var(--color-app) calc(l - 0.08) c h),
|
|
351
|
+
oklch(from var(--color-app) calc(l - 0.12) c h),
|
|
352
|
+
oklch(from var(--color-app) calc(l - 0.16) c h))`;
|
|
353
|
+
e.currentTarget.style.boxShadow = '0 25px 50px -12px var(--shadow-color)'
|
|
354
|
+
}}
|
|
355
|
+
onMouseLeave={(e) => {
|
|
356
|
+
e.currentTarget.style.backgroundImage = `linear-gradient(to bottom right,
|
|
357
|
+
var(--color-app),
|
|
358
|
+
oklch(from var(--color-app) calc(l - 0.05) c h),
|
|
359
|
+
oklch(from var(--color-app) calc(l - 0.08) c h),
|
|
360
|
+
oklch(from var(--color-app) calc(l - 0.12) c h))`;
|
|
361
|
+
e.currentTarget.style.boxShadow = '0 25px 50px -12px var(--shadow-color)'
|
|
362
|
+
}}
|
|
363
|
+
>
|
|
364
|
+
<span className="relative z-20 flex items-center justify-center gap-2 drop-shadow-sm">
|
|
365
|
+
<DynamicIcon name="sparkles" size={16} color="currentColor" strokeWidth={2} className="animate-pulse" />
|
|
366
|
+
{constants.cta}
|
|
367
|
+
</span>
|
|
368
|
+
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/15 to-transparent -translate-x-full group-hover:translate-x-full transition-transform duration-800 skew-x-12"></div>
|
|
369
|
+
</button>
|
|
370
|
+
</div>
|
|
371
|
+
</div>
|
|
372
|
+
</div>
|
|
373
|
+
</div>
|
|
374
|
+
</section>
|
|
375
|
+
|
|
376
|
+
{/* CTA Section */}
|
|
377
|
+
<section className="py-20">
|
|
378
|
+
<div className="max-w-7xl mx-auto px-6">
|
|
379
|
+
<div
|
|
380
|
+
className="py-16 text-center rounded-3xl text-white"
|
|
381
|
+
style={{
|
|
382
|
+
background: `linear-gradient(90deg, var(--gradient-from), var(--gradient-to))`
|
|
383
|
+
}}
|
|
384
|
+
>
|
|
385
|
+
<h2 className="text-4xl md:text-5xl font-bold mb-10">Ready To Build?</h2>
|
|
386
|
+
<button
|
|
387
|
+
onClick={() => navigate('/app')}
|
|
388
|
+
className="relative group bg-white hover:bg-gray-50 text-lg px-8 py-4 rounded-xl font-semibold transition-all duration-300 shadow-xl hover:shadow-white/40 backdrop-blur-sm overflow-hidden border-2 border-white/20 cursor-pointer"
|
|
389
|
+
style={{ color: 'var(--color-app)' }}
|
|
390
|
+
>
|
|
391
|
+
<span className="relative z-20 flex items-center justify-center gap-2 drop-shadow-sm">
|
|
392
|
+
<DynamicIcon name="sparkles" size={16} color="currentColor" strokeWidth={2} className="animate-pulse" />
|
|
393
|
+
{constants.cta}
|
|
394
|
+
</span>
|
|
395
|
+
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/15 to-transparent -translate-x-full group-hover:translate-x-full transition-transform duration-800 skew-x-12"></div>
|
|
396
|
+
</button>
|
|
397
|
+
</div>
|
|
398
|
+
</div>
|
|
399
|
+
</section>
|
|
31
400
|
</main>
|
|
32
|
-
<footer className="py-4 mx-3">
|
|
33
|
-
<div className="flex gap-3 text-gray-500 hover:text-gray-600 cursor-pointer">
|
|
34
|
-
<div className="mr-auto">© {new Date().getFullYear()} {constants.companyName}</div>
|
|
35
|
-
<a href={'/privacy'} target="_blank">Privacy</a>
|
|
36
|
-
<a href={'/terms'} target="_blank">Terms</a>
|
|
37
|
-
<a href={'/eula'} target="_blank" className="mr-3">EULA</a>
|
|
38
|
-
</div>
|
|
39
401
|
|
|
402
|
+
{/* Footer */}
|
|
403
|
+
<footer className="border-t border-gray-200 dark:border-gray-700 py-10 text-center text-gray-600 dark:text-gray-300 relative z-10 bg-white dark:bg-gray-900 transition-colors duration-300">
|
|
404
|
+
<div className="max-w-7xl mx-auto px-6">
|
|
405
|
+
<div className="flex justify-center gap-8 mb-6">
|
|
406
|
+
<a href="/privacy" className="hover:text-gray-900 dark:hover:text-white transition-colors font-semibold">Privacy</a>
|
|
407
|
+
<a href="/terms" className="hover:text-gray-900 dark:hover:text-white transition-colors font-semibold">Terms</a>
|
|
408
|
+
<a href="/eula" className="hover:text-gray-900 dark:hover:text-white transition-colors font-semibold">EULA</a>
|
|
409
|
+
</div>
|
|
410
|
+
<p>© {new Date().getFullYear()} {constants.companyName}. All rights reserved.</p>
|
|
411
|
+
</div>
|
|
40
412
|
</footer>
|
|
41
413
|
</div>
|
|
42
|
-
)
|
|
43
|
-
}
|
|
414
|
+
);
|
|
415
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import constants from "@/constants.json";
|
|
3
|
+
import * as LucideIcons from "lucide-react";
|
|
4
|
+
|
|
5
|
+
// Dynamic Icon Component
|
|
6
|
+
const DynamicIcon = ({ name, size = 24, color = 'currentColor', strokeWidth = 2, ...props }) => {
|
|
7
|
+
const toPascalCase = (str) => str.split(/[-_\s]/).map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join('');
|
|
8
|
+
const possibleNames = [name, toPascalCase(name), name.charAt(0).toUpperCase() + name.slice(1)];
|
|
9
|
+
const LucideIcon = possibleNames.find(n => LucideIcons[n]) ? LucideIcons[possibleNames.find(n => LucideIcons[n])] : null;
|
|
10
|
+
return LucideIcon ? React.createElement(LucideIcon, { size, color, strokeWidth, ...props }) : null;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default function LandingView() {
|
|
14
|
+
return (
|
|
15
|
+
<div className="flex flex-col bg-white h-screen">
|
|
16
|
+
<header className="py-2 ">
|
|
17
|
+
<div className="flex items-center m-2 mx-auto">
|
|
18
|
+
<div className="bg-app rounded-lg flex items-center justify-center ml-3 w-8 h-8">
|
|
19
|
+
<DynamicIcon name={constants.appIcon} size={18} color="white" strokeWidth={2} />
|
|
20
|
+
</div>
|
|
21
|
+
<div className="font-semibold ml-2 text-2xl text-gray-700">{constants.appName}</div>
|
|
22
|
+
</div>
|
|
23
|
+
</header>
|
|
24
|
+
<main className="py-24 md:py-48 bg-app">
|
|
25
|
+
<div className="flex flex-col items-center mb-6">
|
|
26
|
+
<h1 className="text-center tracking-tight font-bold text-5xl md:text-7xl mb-10 text-white">{constants.tagline}</h1>
|
|
27
|
+
<a href={'/app'} target="_blank" className="mx-auto bg-white font-medium text-app shadow-sm rounded-3xl px-4 md:px-8 py-4 cursor-pointer">
|
|
28
|
+
Get Started
|
|
29
|
+
</a>
|
|
30
|
+
</div>
|
|
31
|
+
</main>
|
|
32
|
+
<footer className="py-4 mx-3">
|
|
33
|
+
<div className="flex gap-3 text-gray-500 hover:text-gray-600 cursor-pointer">
|
|
34
|
+
<div className="mr-auto">© {new Date().getFullYear()} {constants.companyName}</div>
|
|
35
|
+
<a href={'/privacy'} target="_blank">Privacy</a>
|
|
36
|
+
<a href={'/terms'} target="_blank">Terms</a>
|
|
37
|
+
<a href={'/eula'} target="_blank" className="mr-3">EULA</a>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
</footer>
|
|
41
|
+
</div>
|
|
42
|
+
)
|
|
43
|
+
}
|
package/package.json
CHANGED
package/shadcn/ui/sidebar.jsx
CHANGED
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
|
|
26
26
|
const SIDEBAR_COOKIE_NAME = "sidebar_state"
|
|
27
27
|
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
|
|
28
|
-
const SIDEBAR_WIDTH = "
|
|
28
|
+
const SIDEBAR_WIDTH = "12rem"
|
|
29
29
|
const SIDEBAR_WIDTH_MOBILE = "18rem"
|
|
30
30
|
const SIDEBAR_WIDTH_ICON = "3rem"
|
|
31
31
|
const SIDEBAR_KEYBOARD_SHORTCUT = "b"
|