@emberkit/cli 0.6.0 → 0.6.1-alpha.1
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/dist/cli.js +44 -10
- package/dist/commands/create.js +10 -22
- package/dist/templates/action/index.js +12 -0
- package/dist/templates/apiRoute/index.js +20 -0
- package/dist/templates/component/index.js +14 -0
- package/dist/templates/config/index.js +13 -0
- package/dist/templates/context/index.js +21 -0
- package/dist/templates/entry/index.js +15 -0
- package/dist/templates/errorBoundary/index.js +17 -0
- package/dist/templates/form/index.js +59 -0
- package/dist/templates/layout/index.js +16 -0
- package/dist/templates/layoutRoutes/index.js +9 -0
- package/dist/templates/loader/index.js +10 -0
- package/dist/templates/project-templates/_shared/base.js +139 -0
- package/dist/templates/project-templates/api/api.js +209 -0
- package/dist/templates/project-templates/blog/blog.js +283 -0
- package/dist/templates/project-templates/dashboard/dashboard.js +331 -0
- package/dist/templates/project-templates/minimal/minimal.js +21 -0
- package/dist/templates/project-templates/saas/saas.js +461 -0
- package/dist/templates/project-templates/starter-kit/starter.js +209 -0
- package/dist/templates/project-templates/starter-kit/with-ui.js +375 -0
- package/dist/templates/route/index.js +12 -0
- package/dist/templates/signal/index.js +25 -0
- package/dist/utils/generator.js +13 -13
- package/package.json +1 -2
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
import { buildPackageJson, buildTsConfig, buildViteConfig, buildIndexHtml, buildEntryFile, GITIGNORE, } from "../_shared/base.js";
|
|
2
|
+
const INTER_FONT = "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap";
|
|
3
|
+
export const saasTemplate = {
|
|
4
|
+
"package.json": buildPackageJson({ hasTailwind: true, hasUI: true }),
|
|
5
|
+
"tsconfig.json": buildTsConfig(),
|
|
6
|
+
"vite.config.ts": buildViteConfig(true),
|
|
7
|
+
"index.html": buildIndexHtml({ fonts: [INTER_FONT] }),
|
|
8
|
+
".gitignore": GITIGNORE,
|
|
9
|
+
"src/index.tsx": buildEntryFile({ hasLayout: true, hasCss: true }),
|
|
10
|
+
"src/styles.css": `@import "tailwindcss";
|
|
11
|
+
|
|
12
|
+
@theme {
|
|
13
|
+
--color-brand-50: #eff6ff;
|
|
14
|
+
--color-brand-100: #dbeafe;
|
|
15
|
+
--color-brand-200: #bfdbfe;
|
|
16
|
+
--color-brand-300: #93c5fd;
|
|
17
|
+
--color-brand-400: #60a5fa;
|
|
18
|
+
--color-brand-500: #3b82f6;
|
|
19
|
+
--color-brand-600: #2563eb;
|
|
20
|
+
--color-brand-700: #1d4ed8;
|
|
21
|
+
--color-brand-800: #1e40af;
|
|
22
|
+
--color-brand-900: #1e3a8a;
|
|
23
|
+
--font-sans: 'Inter', system-ui, sans-serif;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
body {
|
|
27
|
+
@apply bg-white text-slate-900 font-sans;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
a {
|
|
31
|
+
@apply text-inherit no-underline;
|
|
32
|
+
}`,
|
|
33
|
+
"src/routes/_layout.tsx": `import type { RouteComponent } from '@emberkit/core';
|
|
34
|
+
import { Head } from '@emberkit/core';
|
|
35
|
+
|
|
36
|
+
const Layout: RouteComponent = ({ children }) => {
|
|
37
|
+
return (
|
|
38
|
+
<>
|
|
39
|
+
<Head>
|
|
40
|
+
<title>{{name}}</title>
|
|
41
|
+
<meta name="description" content="A modern SaaS application built with EmberKit" />
|
|
42
|
+
</Head>
|
|
43
|
+
<div className="min-h-screen flex flex-col">
|
|
44
|
+
<header className="border-b border-slate-200 sticky top-0 bg-white/80 backdrop-blur z-50">
|
|
45
|
+
<div className="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between">
|
|
46
|
+
<a href="/" className="flex items-center gap-2">
|
|
47
|
+
<span className="text-2xl">⚡</span>
|
|
48
|
+
<span className="text-xl font-bold text-brand-600">{{name}}</span>
|
|
49
|
+
</a>
|
|
50
|
+
<nav className="hidden md:flex items-center gap-8 text-sm font-medium text-slate-600">
|
|
51
|
+
<a href="/#features" className="hover:text-brand-600">Features</a>
|
|
52
|
+
<a href="/#pricing" className="hover:text-brand-600">Pricing</a>
|
|
53
|
+
<a href="/about" className="hover:text-brand-600">About</a>
|
|
54
|
+
</nav>
|
|
55
|
+
<div className="flex items-center gap-4">
|
|
56
|
+
<a href="/login" className="text-sm font-medium text-slate-600 hover:text-brand-600">Sign in</a>
|
|
57
|
+
<a href="/signup" className="px-4 py-2 bg-brand-600 hover:bg-brand-700 text-white text-sm font-semibold rounded-lg transition-colors">
|
|
58
|
+
Get Started
|
|
59
|
+
</a>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</header>
|
|
63
|
+
<main className="flex-1">{children}</main>
|
|
64
|
+
<footer className="border-t border-slate-200 bg-slate-50 py-12">
|
|
65
|
+
<div className="max-w-7xl mx-auto px-6 grid grid-cols-2 md:grid-cols-4 gap-8">
|
|
66
|
+
<div>
|
|
67
|
+
<h4 className="font-semibold mb-4">Product</h4>
|
|
68
|
+
<ul className="space-y-2 text-sm text-slate-600">
|
|
69
|
+
<li><a href="/#features" className="hover:text-brand-600">Features</a></li>
|
|
70
|
+
<li><a href="/#pricing" className="hover:text-brand-600">Pricing</a></li>
|
|
71
|
+
</ul>
|
|
72
|
+
</div>
|
|
73
|
+
<div>
|
|
74
|
+
<h4 className="font-semibold mb-4">Company</h4>
|
|
75
|
+
<ul className="space-y-2 text-sm text-slate-600">
|
|
76
|
+
<li><a href="/about" className="hover:text-brand-600">About</a></li>
|
|
77
|
+
<li><a href="/blog" className="hover:text-brand-600">Blog</a></li>
|
|
78
|
+
</ul>
|
|
79
|
+
</div>
|
|
80
|
+
<div>
|
|
81
|
+
<h4 className="font-semibold mb-4">Legal</h4>
|
|
82
|
+
<ul className="space-y-2 text-sm text-slate-600">
|
|
83
|
+
<li><a href="/privacy" className="hover:text-brand-600">Privacy</a></li>
|
|
84
|
+
<li><a href="/terms" className="hover:text-brand-600">Terms</a></li>
|
|
85
|
+
</ul>
|
|
86
|
+
</div>
|
|
87
|
+
<div>
|
|
88
|
+
<h4 className="font-semibold mb-4">Support</h4>
|
|
89
|
+
<ul className="space-y-2 text-sm text-slate-600">
|
|
90
|
+
<li><a href="/docs" className="hover:text-brand-600">Docs</a></li>
|
|
91
|
+
<li><a href="/contact" className="hover:text-brand-600">Contact</a></li>
|
|
92
|
+
</ul>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
<div className="max-w-7xl mx-auto px-6 mt-8 pt-8 border-t border-slate-200 text-center text-sm text-slate-500">
|
|
96
|
+
<p>© 2026 {{name}}. Built with EmberKit.</p>
|
|
97
|
+
</div>
|
|
98
|
+
</footer>
|
|
99
|
+
</div>
|
|
100
|
+
</>
|
|
101
|
+
);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export default Layout;`,
|
|
105
|
+
"src/routes/index.tsx": `import type { RouteComponent } from '@emberkit/core';
|
|
106
|
+
import { signal } from '@emberkit/core';
|
|
107
|
+
import { Button, Card, Badge } from '@emberkit/ui';
|
|
108
|
+
|
|
109
|
+
const HomePage: RouteComponent = () => {
|
|
110
|
+
const annual = signal(false);
|
|
111
|
+
|
|
112
|
+
const features = [
|
|
113
|
+
{ icon: '⚡', title: 'Lightning Fast', desc: 'Sub-10KB runtime with tree-shakeable architecture' },
|
|
114
|
+
{ icon: '🔐', title: 'Secure by Default', desc: 'Built-in security best practices and protections' },
|
|
115
|
+
{ icon: '📈', title: 'Analytics Built-in', desc: 'Track user behavior without third-party scripts' },
|
|
116
|
+
{ icon: '🌎', title: 'Global CDN', desc: 'Deploy to the edge for minimal latency worldwide' },
|
|
117
|
+
{ icon: '🔌', title: 'Real-time Sync', desc: 'WebSocket support for live collaboration' },
|
|
118
|
+
{ icon: '⚙', title: 'Customizable', desc: 'Extend everything with our plugin system' },
|
|
119
|
+
];
|
|
120
|
+
|
|
121
|
+
const plans = [
|
|
122
|
+
{
|
|
123
|
+
name: 'Starter',
|
|
124
|
+
price: annual.value ? '$0' : '$0',
|
|
125
|
+
period: annual.value ? '/year' : '/month',
|
|
126
|
+
desc: 'Perfect for side projects',
|
|
127
|
+
features: ['1 user', '5 projects', '1GB storage', 'Community support'],
|
|
128
|
+
cta: 'Get Started Free',
|
|
129
|
+
popular: false,
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: 'Pro',
|
|
133
|
+
price: annual.value ? '$199' : '$19',
|
|
134
|
+
period: annual.value ? '/year' : '/month',
|
|
135
|
+
desc: 'For growing teams',
|
|
136
|
+
features: ['10 users', 'Unlimited projects', '50GB storage', 'Priority support', 'Analytics', 'API access'],
|
|
137
|
+
cta: 'Start Free Trial',
|
|
138
|
+
popular: true,
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: 'Enterprise',
|
|
142
|
+
price: 'Custom',
|
|
143
|
+
period: '',
|
|
144
|
+
desc: 'For large organizations',
|
|
145
|
+
features: ['Unlimited users', 'Unlimited everything', 'SSO & SAML', 'Dedicated support', 'SLA guarantee', 'Custom integrations'],
|
|
146
|
+
cta: 'Contact Sales',
|
|
147
|
+
popular: false,
|
|
148
|
+
},
|
|
149
|
+
];
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<div>
|
|
153
|
+
{/* Hero */}
|
|
154
|
+
<section className="relative overflow-hidden bg-gradient-to-b from-brand-50 to-white py-24">
|
|
155
|
+
<div className="max-w-5xl mx-auto px-6 text-center">
|
|
156
|
+
<Badge variant="primary" className="mb-6">Now in Public Beta</Badge>
|
|
157
|
+
<h1 className="text-5xl md:text-6xl font-extrabold tracking-tight mb-6">
|
|
158
|
+
Build faster with {' '}
|
|
159
|
+
<span className="bg-gradient-to-r from-brand-500 to-brand-700 bg-clip-text text-transparent">{{name}}</span>
|
|
160
|
+
</h1>
|
|
161
|
+
<p className="text-xl text-slate-600 max-w-2xl mx-auto mb-10">
|
|
162
|
+
The modern SaaS platform that helps teams ship products faster.
|
|
163
|
+
Stop reinventing the wheel and focus on what matters.
|
|
164
|
+
</p>
|
|
165
|
+
<div className="flex gap-4 justify-center">
|
|
166
|
+
<Button variant="primary" size="lg">Start Free Trial</Button>
|
|
167
|
+
<Button variant="secondary" size="lg">Watch Demo</Button>
|
|
168
|
+
</div>
|
|
169
|
+
<p className="mt-4 text-sm text-slate-500">No credit card required · 14-day free trial</p>
|
|
170
|
+
</div>
|
|
171
|
+
</section>
|
|
172
|
+
|
|
173
|
+
{/* Logos */}
|
|
174
|
+
<section className="py-12 border-b border-slate-200">
|
|
175
|
+
<div className="max-w-7xl mx-auto px-6 text-center">
|
|
176
|
+
<p className="text-sm font-medium text-slate-500 mb-8">Trusted by teams at</p>
|
|
177
|
+
<div className="flex justify-center gap-12 opacity-50 grayscale">
|
|
178
|
+
{['Acme', 'Globex', 'Initech', 'Umbrella', 'Stark'].map((name) => (
|
|
179
|
+
<span key={name} className="text-xl font-bold text-slate-400">{name}</span>
|
|
180
|
+
))}
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
</section>
|
|
184
|
+
|
|
185
|
+
{/* Features */}
|
|
186
|
+
<section id="features" className="py-24">
|
|
187
|
+
<div className="max-w-7xl mx-auto px-6">
|
|
188
|
+
<div className="text-center mb-16">
|
|
189
|
+
<h2 className="text-4xl font-bold mb-4">Everything you need</h2>
|
|
190
|
+
<p className="text-lg text-slate-600 max-w-2xl mx-auto">
|
|
191
|
+
Powerful features to help your team build, ship, and scale.
|
|
192
|
+
</p>
|
|
193
|
+
</div>
|
|
194
|
+
<div className="grid md:grid-cols-3 gap-8">
|
|
195
|
+
{features.map((f) => (
|
|
196
|
+
<Card key={f.title} padding="lg" className="hover:border-brand-300 transition-colors">
|
|
197
|
+
<div className="text-3xl mb-4">{f.icon}</div>
|
|
198
|
+
<h3 className="text-lg font-semibold mb-2">{f.title}</h3>
|
|
199
|
+
<p className="text-slate-600">{f.desc}</p>
|
|
200
|
+
</Card>
|
|
201
|
+
))}
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
</section>
|
|
205
|
+
|
|
206
|
+
{/* Pricing */}
|
|
207
|
+
<section id="pricing" className="py-24 bg-slate-50">
|
|
208
|
+
<div className="max-w-7xl mx-auto px-6">
|
|
209
|
+
<div className="text-center mb-12">
|
|
210
|
+
<h2 className="text-4xl font-bold mb-4">Simple, transparent pricing</h2>
|
|
211
|
+
<p className="text-lg text-slate-600 mb-8">Choose the plan that works for your team</p>
|
|
212
|
+
<div className="inline-flex items-center gap-3 p-1 bg-white rounded-lg border border-slate-200">
|
|
213
|
+
<button
|
|
214
|
+
className={\`px-4 py-2 text-sm font-medium rounded-md transition-colors \${!annual.value ? 'bg-brand-600 text-white' : 'text-slate-600'}\`}
|
|
215
|
+
onClick={() => { annual.value = false; }}
|
|
216
|
+
>
|
|
217
|
+
Monthly
|
|
218
|
+
</button>
|
|
219
|
+
<button
|
|
220
|
+
className={\`px-4 py-2 text-sm font-medium rounded-md transition-colors \${annual.value ? 'bg-brand-600 text-white' : 'text-slate-600'}\`}
|
|
221
|
+
onClick={() => { annual.value = true; }}
|
|
222
|
+
>
|
|
223
|
+
Annual <span className="text-brand-600 ml-1 font-semibold">-20%</span>
|
|
224
|
+
</button>
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
<div className="grid md:grid-cols-3 gap-8">
|
|
228
|
+
{plans.map((plan) => (
|
|
229
|
+
<Card
|
|
230
|
+
key={plan.name}
|
|
231
|
+
padding="lg"
|
|
232
|
+
className={\`relative \${plan.popular ? 'border-brand-500 shadow-lg' : ''}\`}
|
|
233
|
+
>
|
|
234
|
+
{plan.popular && (
|
|
235
|
+
<Badge variant="primary" className="absolute -top-3 left-6">Most Popular</Badge>
|
|
236
|
+
)}
|
|
237
|
+
<h3 className="text-lg font-semibold mb-2">{plan.name}</h3>
|
|
238
|
+
<p className="text-sm text-slate-600 mb-4">{plan.desc}</p>
|
|
239
|
+
<div className="mb-6">
|
|
240
|
+
<span className="text-4xl font-bold">{plan.price}</span>
|
|
241
|
+
<span className="text-slate-600">{plan.period}</span>
|
|
242
|
+
</div>
|
|
243
|
+
<ul className="space-y-3 mb-8">
|
|
244
|
+
{plan.features.map((f) => (
|
|
245
|
+
<li key={f} className="flex items-center gap-2 text-sm">
|
|
246
|
+
<span className="text-green-500">✓</span>
|
|
247
|
+
{f}
|
|
248
|
+
</li>
|
|
249
|
+
))}
|
|
250
|
+
</ul>
|
|
251
|
+
<Button
|
|
252
|
+
variant={plan.popular ? 'primary' : 'secondary'}
|
|
253
|
+
className="w-full"
|
|
254
|
+
>
|
|
255
|
+
{plan.cta}
|
|
256
|
+
</Button>
|
|
257
|
+
</Card>
|
|
258
|
+
))}
|
|
259
|
+
</div>
|
|
260
|
+
</div>
|
|
261
|
+
</section>
|
|
262
|
+
|
|
263
|
+
{/* CTA */}
|
|
264
|
+
<section className="py-24 bg-brand-600 text-white">
|
|
265
|
+
<div className="max-w-3xl mx-auto px-6 text-center">
|
|
266
|
+
<h2 className="text-4xl font-bold mb-4">Ready to get started?</h2>
|
|
267
|
+
<p className="text-lg text-brand-100 mb-8">
|
|
268
|
+
Join thousands of teams already using {{name}}.
|
|
269
|
+
</p>
|
|
270
|
+
<Button variant="secondary" size="lg" className="bg-white text-brand-600 hover:bg-brand-50">
|
|
271
|
+
Start Free Trial
|
|
272
|
+
</Button>
|
|
273
|
+
</div>
|
|
274
|
+
</section>
|
|
275
|
+
</div>
|
|
276
|
+
);
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
export default HomePage;`,
|
|
280
|
+
"src/routes/login.tsx": `import type { RouteComponent } from '@emberkit/core';
|
|
281
|
+
import { Head } from '@emberkit/core';
|
|
282
|
+
import { Button, Input, Card } from '@emberkit/ui';
|
|
283
|
+
import { signal } from '@emberkit/core';
|
|
284
|
+
|
|
285
|
+
const LoginPage: RouteComponent = () => {
|
|
286
|
+
const email = signal('');
|
|
287
|
+
const password = signal('');
|
|
288
|
+
const error = signal<string | null>(null);
|
|
289
|
+
|
|
290
|
+
const handleSubmit = (e: Event) => {
|
|
291
|
+
e.preventDefault();
|
|
292
|
+
error.value = null;
|
|
293
|
+
|
|
294
|
+
if (!email.value || !password.value) {
|
|
295
|
+
error.value = 'Please fill in all fields';
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Handle login
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
return (
|
|
303
|
+
<>
|
|
304
|
+
<Head>
|
|
305
|
+
<title>Sign In - {{name}}</title>
|
|
306
|
+
</Head>
|
|
307
|
+
<div className="min-h-[80vh] flex items-center justify-center px-6">
|
|
308
|
+
<Card padding="xl" className="w-full max-w-md">
|
|
309
|
+
<div className="text-center mb-8">
|
|
310
|
+
<h1 className="text-2xl font-bold mb-2">Welcome back</h1>
|
|
311
|
+
<p className="text-slate-600">Sign in to your account</p>
|
|
312
|
+
</div>
|
|
313
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
314
|
+
<Input
|
|
315
|
+
label="Email"
|
|
316
|
+
type="email"
|
|
317
|
+
placeholder="you@example.com"
|
|
318
|
+
value={email.value}
|
|
319
|
+
onInput={(e) => { email.value = e.currentTarget.value; }}
|
|
320
|
+
/>
|
|
321
|
+
<Input
|
|
322
|
+
label="Password"
|
|
323
|
+
type="password"
|
|
324
|
+
placeholder="••••••••"
|
|
325
|
+
value={password.value}
|
|
326
|
+
onInput={(e) => { password.value = e.currentTarget.value; }}
|
|
327
|
+
/>
|
|
328
|
+
{error.value && (
|
|
329
|
+
<p className="text-red-500 text-sm">{error.value}</p>
|
|
330
|
+
)}
|
|
331
|
+
<div className="flex items-center justify-between text-sm">
|
|
332
|
+
<label className="flex items-center gap-2">
|
|
333
|
+
<input type="checkbox" className="rounded" />
|
|
334
|
+
Remember me
|
|
335
|
+
</label>
|
|
336
|
+
<a href="/forgot-password" className="text-brand-600 hover:underline">Forgot password?</a>
|
|
337
|
+
</div>
|
|
338
|
+
<Button variant="primary" className="w-full">Sign In</Button>
|
|
339
|
+
</form>
|
|
340
|
+
<p className="text-center text-sm text-slate-600 mt-6">
|
|
341
|
+
Don't have an account? <a href="/signup" className="text-brand-600 font-medium hover:underline">Sign up</a>
|
|
342
|
+
</p>
|
|
343
|
+
</Card>
|
|
344
|
+
</div>
|
|
345
|
+
</>
|
|
346
|
+
);
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
export default LoginPage;`,
|
|
350
|
+
"src/routes/signup.tsx": `import type { RouteComponent } from '@emberkit/core';
|
|
351
|
+
import { Head } from '@emberkit/core';
|
|
352
|
+
import { Button, Input, Card } from '@emberkit/ui';
|
|
353
|
+
import { signal } from '@emberkit/core';
|
|
354
|
+
|
|
355
|
+
const SignupPage: RouteComponent = () => {
|
|
356
|
+
const name = signal('');
|
|
357
|
+
const email = signal('');
|
|
358
|
+
const password = signal('');
|
|
359
|
+
const error = signal<string | null>(null);
|
|
360
|
+
|
|
361
|
+
const handleSubmit = (e: Event) => {
|
|
362
|
+
e.preventDefault();
|
|
363
|
+
error.value = null;
|
|
364
|
+
|
|
365
|
+
if (!name.value || !email.value || !password.value) {
|
|
366
|
+
error.value = 'Please fill in all fields';
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Handle signup
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
return (
|
|
374
|
+
<>
|
|
375
|
+
<Head>
|
|
376
|
+
<title>Sign Up - {{name}}</title>
|
|
377
|
+
</Head>
|
|
378
|
+
<div className="min-h-[80vh] flex items-center justify-center px-6">
|
|
379
|
+
<Card padding="xl" className="w-full max-w-md">
|
|
380
|
+
<div className="text-center mb-8">
|
|
381
|
+
<h1 className="text-2xl font-bold mb-2">Create an account</h1>
|
|
382
|
+
<p className="text-slate-600">Start your 14-day free trial</p>
|
|
383
|
+
</div>
|
|
384
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
385
|
+
<Input
|
|
386
|
+
label="Full Name"
|
|
387
|
+
placeholder="John Doe"
|
|
388
|
+
value={name.value}
|
|
389
|
+
onInput={(e) => { name.value = e.currentTarget.value; }}
|
|
390
|
+
/>
|
|
391
|
+
<Input
|
|
392
|
+
label="Email"
|
|
393
|
+
type="email"
|
|
394
|
+
placeholder="you@example.com"
|
|
395
|
+
value={email.value}
|
|
396
|
+
onInput={(e) => { email.value = e.currentTarget.value; }}
|
|
397
|
+
/>
|
|
398
|
+
<Input
|
|
399
|
+
label="Password"
|
|
400
|
+
type="password"
|
|
401
|
+
placeholder="8+ characters"
|
|
402
|
+
value={password.value}
|
|
403
|
+
onInput={(e) => { password.value = e.currentTarget.value; }}
|
|
404
|
+
/>
|
|
405
|
+
{error.value && (
|
|
406
|
+
<p className="text-red-500 text-sm">{error.value}</p>
|
|
407
|
+
)}
|
|
408
|
+
<p className="text-xs text-slate-500">
|
|
409
|
+
By signing up, you agree to our {' '}
|
|
410
|
+
<a href="/terms" className="text-brand-600 hover:underline">Terms</a> {' '}
|
|
411
|
+
and {' '}
|
|
412
|
+
<a href="/privacy" className="text-brand-600 hover:underline">Privacy Policy</a>
|
|
413
|
+
</p>
|
|
414
|
+
<Button variant="primary" className="w-full">Create Account</Button>
|
|
415
|
+
</form>
|
|
416
|
+
<p className="text-center text-sm text-slate-600 mt-6">
|
|
417
|
+
Already have an account? <a href="/login" className="text-brand-600 font-medium hover:underline">Sign in</a>
|
|
418
|
+
</p>
|
|
419
|
+
</Card>
|
|
420
|
+
</div>
|
|
421
|
+
</>
|
|
422
|
+
);
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
export default SignupPage;`,
|
|
426
|
+
"src/routes/about.tsx": `import type { RouteComponent } from '@emberkit/core';
|
|
427
|
+
import { Head } from '@emberkit/core';
|
|
428
|
+
|
|
429
|
+
const AboutPage: RouteComponent = () => {
|
|
430
|
+
return (
|
|
431
|
+
<>
|
|
432
|
+
<Head>
|
|
433
|
+
<title>About - {{name}}</title>
|
|
434
|
+
</Head>
|
|
435
|
+
<div className="max-w-3xl mx-auto px-6 py-16">
|
|
436
|
+
<h1 className="text-4xl font-bold mb-6">About {{name}}</h1>
|
|
437
|
+
<div className="prose prose-slate">
|
|
438
|
+
<p className="text-lg text-slate-600 mb-6">
|
|
439
|
+
We're on a mission to make software development faster and more enjoyable.
|
|
440
|
+
Our platform provides everything you need to build, deploy, and scale modern web applications.
|
|
441
|
+
</p>
|
|
442
|
+
<h2 className="text-2xl font-bold mt-8 mb-4">Our Story</h2>
|
|
443
|
+
<p className="text-slate-600 mb-4">
|
|
444
|
+
Founded in 2026, {{name}} was born from the frustration of dealing with complex,
|
|
445
|
+
bloated frameworks. We believe in simplicity, performance, and developer experience.
|
|
446
|
+
</p>
|
|
447
|
+
<h2 className="text-2xl font-bold mt-8 mb-4">Values</h2>
|
|
448
|
+
<ul className="list-disc pl-6 space-y-2 text-slate-600">
|
|
449
|
+
<li>Developer experience first</li>
|
|
450
|
+
<li>Performance is a feature</li>
|
|
451
|
+
<li>Simplicity over complexity</li>
|
|
452
|
+
<li>Open and transparent</li>
|
|
453
|
+
</ul>
|
|
454
|
+
</div>
|
|
455
|
+
</div>
|
|
456
|
+
</>
|
|
457
|
+
);
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
export default AboutPage;`,
|
|
461
|
+
};
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { buildPackageJson, buildTsConfig, buildViteConfig, buildIndexHtml, buildEntryFile, GITIGNORE, } from "../_shared/base.js";
|
|
2
|
+
const INTER_FONT = "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap";
|
|
3
|
+
export const starterFiles = {
|
|
4
|
+
"package.json": buildPackageJson({ hasTailwind: true }),
|
|
5
|
+
"tsconfig.json": buildTsConfig(),
|
|
6
|
+
"vite.config.ts": buildViteConfig(true),
|
|
7
|
+
"index.html": buildIndexHtml({ fonts: [INTER_FONT] }),
|
|
8
|
+
".gitignore": GITIGNORE,
|
|
9
|
+
"src/index.tsx": buildEntryFile({ hasLayout: true, hasCss: true }),
|
|
10
|
+
"src/styles.css": `@import "tailwindcss";
|
|
11
|
+
|
|
12
|
+
@theme {
|
|
13
|
+
--color-ember-50: #fff7ed;
|
|
14
|
+
--color-ember-100: #ffedd5;
|
|
15
|
+
--color-ember-200: #fed7aa;
|
|
16
|
+
--color-ember-300: #fdba74;
|
|
17
|
+
--color-ember-400: #fb923c;
|
|
18
|
+
--color-ember-500: #f97316;
|
|
19
|
+
--color-ember-600: #ea580c;
|
|
20
|
+
--color-ember-700: #c2410c;
|
|
21
|
+
--color-ember-800: #9a3412;
|
|
22
|
+
--color-ember-900: #7c2d12;
|
|
23
|
+
--font-sans: 'Inter', system-ui, sans-serif;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@keyframes float {
|
|
27
|
+
0%, 100% { transform: translateY(0px); }
|
|
28
|
+
50% { transform: translateY(-20px); }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@keyframes fade-in-up {
|
|
32
|
+
from { opacity: 0; transform: translateY(20px); }
|
|
33
|
+
to { opacity: 1; transform: translateY(0); }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@keyframes pulse-glow {
|
|
37
|
+
0%, 100% { opacity: 0.4; transform: scale(1); }
|
|
38
|
+
50% { opacity: 0.6; transform: scale(1.05); }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
body {
|
|
42
|
+
@apply bg-[#0b0f19] text-slate-200 font-sans min-h-screen;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
a {
|
|
46
|
+
@apply text-inherit no-underline transition-colors;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.animate-float { animation: float 6s ease-in-out infinite; }
|
|
50
|
+
.animate-fade-in-up { animation: fade-in-up 0.6s ease-out forwards; }
|
|
51
|
+
.animate-pulse-glow { animation: pulse-glow 4s ease-in-out infinite; }`,
|
|
52
|
+
"src/routes/_layout.tsx": `import type { RouteComponent } from '@emberkit/core';
|
|
53
|
+
import { Head } from '@emberkit/core';
|
|
54
|
+
|
|
55
|
+
const Layout: RouteComponent = ({ children }) => {
|
|
56
|
+
return (
|
|
57
|
+
<>
|
|
58
|
+
<Head>
|
|
59
|
+
<title>{{name}}</title>
|
|
60
|
+
<meta name="description" content="Built with EmberKit" />
|
|
61
|
+
</Head>
|
|
62
|
+
<div className="relative min-h-screen flex flex-col">
|
|
63
|
+
{/* Ambient background */}
|
|
64
|
+
<div className="pointer-events-none fixed inset-0 overflow-hidden">
|
|
65
|
+
<div className="absolute -top-40 -right-40 w-96 h-96 rounded-full bg-ember-500/10 blur-[120px] animate-pulse-glow" />
|
|
66
|
+
<div className="absolute -bottom-40 -left-40 w-96 h-96 rounded-full bg-amber-500/5 blur-[120px] animate-pulse-glow" style={{ animationDelay: '2s' }} />
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<header className="relative z-50 border-b border-slate-800/50 bg-[#0b0f19]/80 backdrop-blur-xl sticky top-0">
|
|
70
|
+
<div className="max-w-6xl mx-auto px-6 py-4 flex items-center justify-between">
|
|
71
|
+
<a href="/" className="flex items-center gap-2 group">
|
|
72
|
+
<span className="text-2xl animate-float">🔥</span>
|
|
73
|
+
<span className="text-xl font-bold bg-gradient-to-r from-ember-400 to-ember-500 bg-clip-text text-transparent">
|
|
74
|
+
{{name}}
|
|
75
|
+
</span>
|
|
76
|
+
</a>
|
|
77
|
+
<nav className="flex items-center gap-6">
|
|
78
|
+
<a href="/" className="text-slate-400 hover:text-ember-500 font-medium transition-colors">Home</a>
|
|
79
|
+
<a href="/about" className="text-slate-400 hover:text-ember-500 font-medium transition-colors">About</a>
|
|
80
|
+
<a href="https://emberkit.dev/docs" target="_blank" className="text-slate-400 hover:text-ember-500 font-medium transition-colors">
|
|
81
|
+
Docs <span className="text-xs">↗</span>
|
|
82
|
+
</a>
|
|
83
|
+
</nav>
|
|
84
|
+
</div>
|
|
85
|
+
</header>
|
|
86
|
+
<main className="relative z-10 flex-1">{children}</main>
|
|
87
|
+
<footer className="relative z-10 border-t border-slate-800/50 py-8 text-center text-sm text-slate-500">
|
|
88
|
+
<p>Built with <a href="https://emberkit.dev" className="text-ember-500 hover:underline">EmberKit</a></p>
|
|
89
|
+
</footer>
|
|
90
|
+
</div>
|
|
91
|
+
</>
|
|
92
|
+
);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export default Layout;`,
|
|
96
|
+
"src/routes/index.tsx": `import type { RouteComponent } from '@emberkit/core';
|
|
97
|
+
import { signal } from '@emberkit/core';
|
|
98
|
+
|
|
99
|
+
const HomePage: RouteComponent = () => {
|
|
100
|
+
const count = signal(0);
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<div className="relative max-w-6xl mx-auto px-6 py-16 space-y-24">
|
|
104
|
+
{/* Hero Section */}
|
|
105
|
+
<section className="relative text-center space-y-6 animate-fade-in-down">
|
|
106
|
+
<div className="pointer-events-none absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 h-[400px] w-[400px] rounded-full bg-ember-500/10 blur-[120px] animate-pulse-glow" />
|
|
107
|
+
<h1 className="relative z-10 text-5xl md:text-6xl font-extrabold tracking-tight">
|
|
108
|
+
Welcome to <span className="bg-gradient-to-r from-ember-400 via-ember-500 to-amber-500 bg-clip-text text-transparent">{{name}}</span>
|
|
109
|
+
</h1>
|
|
110
|
+
<p className="relative z-10 text-xl text-slate-400 max-w-2xl mx-auto">
|
|
111
|
+
A minimalist TypeScript-first JSX framework built for speed and simplicity.
|
|
112
|
+
Get started in seconds with hot module replacement and zero-config routing.
|
|
113
|
+
</p>
|
|
114
|
+
<div className="relative z-10 flex gap-4 justify-center pt-4">
|
|
115
|
+
<a href="/about" className="px-6 py-3 bg-ember-500 hover:bg-ember-600 text-white font-semibold rounded-lg transition-all hover:scale-105 shadow-lg shadow-ember-500/20">
|
|
116
|
+
Learn More
|
|
117
|
+
</a>
|
|
118
|
+
<a href="https://emberkit.dev/docs" target="_blank" className="px-6 py-3 border border-slate-700 hover:border-ember-500 text-slate-300 hover:text-ember-500 font-semibold rounded-lg transition-all">
|
|
119
|
+
Read Docs →
|
|
120
|
+
</a>
|
|
121
|
+
</div>
|
|
122
|
+
</section>
|
|
123
|
+
|
|
124
|
+
{/* Features Grid */}
|
|
125
|
+
<section className="grid md:grid-cols-3 gap-6">
|
|
126
|
+
{[
|
|
127
|
+
{ icon: '⚡', title: 'Lightning Fast', desc: 'Sub-10KB runtime with tree-shakeable architecture' },
|
|
128
|
+
{ icon: '🔯', title: 'TypeScript First', desc: 'Full type safety with intelligent autocomplete' },
|
|
129
|
+
{ icon: '🛖', title: 'File-Based Routing', desc: 'Routes automatically created from your file structure' },
|
|
130
|
+
].map((f, i) => (
|
|
131
|
+
<div key={f.title} className="p-6 rounded-xl border border-slate-800 bg-slate-800/30 hover:border-ember-500/50 transition-all duration-300 hover:-translate-y-1 group animate-fade-in-up" style={{ animationDelay: \`\${i * 100}ms\` }}>
|
|
132
|
+
<div className="text-3xl mb-4 group-hover:scale-110 transition-transform">{f.icon}</div>
|
|
133
|
+
<h3 className="text-lg font-semibold mb-2">{f.title}</h3>
|
|
134
|
+
<p className="text-slate-400">{f.desc}</p>
|
|
135
|
+
</div>
|
|
136
|
+
))}
|
|
137
|
+
</section>
|
|
138
|
+
|
|
139
|
+
{/* Counter Demo */}
|
|
140
|
+
<section className="relative p-8 rounded-3xl bg-gradient-to-br from-slate-800/50 to-slate-900/50 border border-slate-700/50 text-center backdrop-blur-sm">
|
|
141
|
+
<h2 className="text-2xl font-bold mb-6">Interactive Counter Demo</h2>
|
|
142
|
+
<div className="flex items-center justify-center gap-6">
|
|
143
|
+
<button
|
|
144
|
+
className="w-12 h-12 rounded-lg bg-slate-800 border border-slate-700 hover:bg-ember-500 hover:border-ember-500 text-ember-500 hover:text-white text-xl transition-all hover:scale-110"
|
|
145
|
+
onClick={() => count.value--}
|
|
146
|
+
>
|
|
147
|
+
−
|
|
148
|
+
</button>
|
|
149
|
+
<span className="text-5xl font-bold tabular-nums min-w-[80px] bg-clip-text text-transparent bg-gradient-to-b from-white to-slate-400">{count}</span>
|
|
150
|
+
<button
|
|
151
|
+
className="w-12 h-12 rounded-lg bg-slate-800 border border-slate-700 hover:bg-ember-500 hover:border-ember-500 text-ember-500 hover:text-white text-xl transition-all hover:scale-110"
|
|
152
|
+
onClick={() => count.value++}
|
|
153
|
+
>
|
|
154
|
+
+
|
|
155
|
+
</button>
|
|
156
|
+
</div>
|
|
157
|
+
<p className="text-slate-500 mt-4 text-sm">Try clicking the buttons!</p>
|
|
158
|
+
</section>
|
|
159
|
+
|
|
160
|
+
<section className="text-center py-8">
|
|
161
|
+
<div className="inline-flex items-center gap-3 px-4 py-2 rounded-full bg-ember-500/10 border border-ember-500/20 text-ember-400">
|
|
162
|
+
<span className="w-2 h-2 rounded-full bg-ember-500 animate-pulse"></span>
|
|
163
|
+
<span className="text-sm font-medium">Ready to build something amazing?</span>
|
|
164
|
+
</div>
|
|
165
|
+
</section>
|
|
166
|
+
</div>
|
|
167
|
+
);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
export default HomePage;`,
|
|
171
|
+
"src/routes/about.tsx": `import type { RouteComponent } from '@emberkit/core';
|
|
172
|
+
import { Head } from '@emberkit/core';
|
|
173
|
+
|
|
174
|
+
const AboutPage: RouteComponent = () => {
|
|
175
|
+
return (
|
|
176
|
+
<div className="max-w-3xl mx-auto px-6 py-16 space-y-12 animate-fade-in-up">
|
|
177
|
+
<Head>
|
|
178
|
+
<title>About - {{name}}</title>
|
|
179
|
+
</Head>
|
|
180
|
+
<div className="text-center">
|
|
181
|
+
<h1 className="text-4xl font-bold mb-6">About {{name}}</h1>
|
|
182
|
+
<p className="text-slate-400 text-lg leading-relaxed max-w-2xl mx-auto">
|
|
183
|
+
EmberKit is a minimalist TypeScript-first JSX framework built for speed and simplicity.
|
|
184
|
+
It combines the best of modern frontend development with a lightweight runtime.
|
|
185
|
+
</p>
|
|
186
|
+
</div>
|
|
187
|
+
<div className="grid sm:grid-cols-3 gap-4">
|
|
188
|
+
{[
|
|
189
|
+
{ label: 'SPA & SSR', desc: 'Works in both modes' },
|
|
190
|
+
{ label: 'Zero Config', desc: 'Sensible defaults' },
|
|
191
|
+
{ label: 'HMR', desc: 'Hot module replacement' },
|
|
192
|
+
].map((f) => (
|
|
193
|
+
<div key={f.label} className="p-4 rounded-xl bg-slate-800/30 border border-slate-800 hover:border-ember-500/50 transition-all hover:-translate-y-1 group">
|
|
194
|
+
<span className="text-ember-500 text-sm font-semibold">{f.label}</span>
|
|
195
|
+
<p className="text-slate-500 text-sm mt-1">{f.desc}</p>
|
|
196
|
+
</div>
|
|
197
|
+
))}
|
|
198
|
+
</div>
|
|
199
|
+
<div className="text-center">
|
|
200
|
+
<a href="/" className="inline-flex items-center gap-2 text-ember-500 hover:text-ember-400 font-medium transition-colors">
|
|
201
|
+
← Back to Home
|
|
202
|
+
</a>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
);
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
export default AboutPage;`,
|
|
209
|
+
};
|