@emberkit/cli 0.5.1 → 0.6.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.
@@ -1,263 +1,3 @@
1
- export const routeTemplate = `import type { RouteComponent } from '@emberkit/core';
2
-
3
- const {{name}}: RouteComponent = () => {
4
- return (
5
- <div>
6
- <h1>{{name}}</h1>
7
- </div>
8
- );
9
- };
10
-
11
- export default {{name}};
12
- `;
13
- export const componentTemplate = `interface {{name}}Props {
14
- className?: string;
15
- }
16
-
17
- const {{name}} = ({ className = '' }: {{name}}Props) => {
18
- return (
19
- <div className={className}>
20
- {{name}} component
21
- </div>
22
- );
23
- };
24
-
25
- export default {{name}};
26
- `;
27
- export const layoutTemplate = `import type { RouteComponent } from '@emberkit/core';
28
-
29
- const {{name}}Layout: RouteComponent = ({ children }) => {
30
- return (
31
- <div>
32
- <header>
33
- <nav>{{name}} Navigation</nav>
34
- </header>
35
- <main>{children}</main>
36
- <footer>Footer</footer>
37
- </div>
38
- );
39
- };
40
-
41
- export default {{name}}Layout;
42
- `;
43
- export const errorBoundaryTemplate = `import type { RouteComponent } from '@emberkit/core';
44
-
45
- interface {{name}}ErrorProps {
46
- error: Error;
47
- }
48
-
49
- const {{name}}Error: RouteComponent<{{name}}ErrorProps> = ({ error }) => {
50
- return (
51
- <div className="error-boundary">
52
- <h2>Something went wrong</h2>
53
- <p>{error.message}</p>
54
- </div>
55
- );
56
- };
57
-
58
- export default {{name}}Error;
59
- `;
60
- export const loaderTemplate = `import type { LoaderFunction, LoaderResult } from '@emberkit/core';
61
-
62
- export const loader: LoaderFunction = async ({ params, query, request }) => {
63
- return {
64
- data: {
65
- // Add your data here
66
- },
67
- } as LoaderResult<unknown>;
68
- };
69
- `;
70
- export const actionTemplate = `import type { ActionFunction, LoaderResult } from '@emberkit/core';
71
-
72
- export const action: ActionFunction = async ({ params, request }) => {
73
- const formData = await request.formData();
74
-
75
- return {
76
- data: {
77
- success: true,
78
- },
79
- } as LoaderResult<unknown>;
80
- };
81
- `;
82
- export const apiRouteTemplate = `import type { LoaderFunction, LoaderResult } from '@emberkit/core';
83
-
84
- export const GET: LoaderFunction = async ({ params, query, request }) => {
85
- return {
86
- data: {
87
- message: 'Hello from API',
88
- },
89
- } as LoaderResult<unknown>;
90
- };
91
-
92
- export const POST: LoaderFunction = async ({ request }) => {
93
- const body = await request.json();
94
-
95
- return {
96
- data: {
97
- received: body,
98
- },
99
- } as LoaderResult<unknown>;
100
- };
101
- `;
102
- export const configTemplate = `import { defineConfig } from '@emberkit/core';
103
-
104
- export default defineConfig({
105
- mode: 'hybrid',
106
- server: {
107
- port: 3000,
108
- },
109
- build: {
110
- outDir: 'dist',
111
- target: 'esnext',
112
- },
113
- });
114
- `;
115
- export const indexTemplate = `import { render } from '@emberkit/core';
116
- import { routes } from 'virtual:emberkit-routes';
117
- import App from './routes/_layout';
118
- import './styles.css';
119
-
120
- const root = document.getElementById('app');
121
-
122
- if (root) {
123
- try {
124
- render(App, root, { routes });
125
- } catch (error) {
126
- console.error('[entry] Render error:', error);
127
- }
128
- }
129
- `;
130
- export const layoutRoutesTemplate = `// EmberKit uses file-based routing.
131
- // Routes are automatically discovered from the src/routes directory.
132
- // - src/routes/index.tsx → /
133
- // - src/routes/about.tsx → /about
134
- // - src/routes/[slug].tsx → /:slug
135
- // - src/routes/[...rest].tsx → catch-all
136
-
137
- export {};
138
- `;
139
- export const signalTemplate = `import { signal, computed, effect } from '@emberkit/core';
140
-
141
- // Writable signal
142
- const count = signal(0);
143
-
144
- // Computed value
145
- const doubled = computed(() => count.value * 2);
146
-
147
- // Side effect
148
- effect(() => {
149
- console.log('Count changed to:', count.value);
150
- });
151
-
152
- // Update
153
- count.value++;
154
-
155
- // Batch updates
156
- import { batch } from '@emberkit/core';
157
-
158
- batch(() => {
159
- count.value = 10;
160
- });
161
-
162
- export { count, doubled };
163
- `;
164
- export const contextTemplate = `import { createContext, useContext } from '@emberkit/core';
165
-
166
- interface {{name}}Context {
167
- // Define your context shape
168
- value: string;
169
- }
170
-
171
- const {{name}}Context = createContext<{{name}}Context>({
172
- value: 'default',
173
- });
174
-
175
- // Provider usage:
176
- // <{{name}}Context.Provider value={{ value: 'hello' }}>
177
- // {children}
178
- // </{{name}}Context.Provider>
179
-
180
- // Consumer usage:
181
- // const ctx = useContext({{name}}Context);
182
-
183
- export { {{name}}Context };
184
- `;
185
- export const formTemplate = `import { signal } from '@emberkit/core';
186
-
187
- const {{name}}Form = () => {
188
- const email = signal('');
189
- const password = signal('');
190
- const error = signal<string | null>(null);
191
- const loading = signal(false);
192
-
193
- const handleSubmit = async (e: Event) => {
194
- e.preventDefault();
195
- error.value = null;
196
- loading.value = true;
197
-
198
- try {
199
- const response = await fetch('/api/auth/login', {
200
- method: 'POST',
201
- headers: { 'Content-Type': 'application/json' },
202
- body: JSON.stringify({
203
- email: email.value,
204
- password: password.value,
205
- }),
206
- });
207
-
208
- if (!response.ok) {
209
- throw new Error('Login failed');
210
- }
211
-
212
- // Handle success
213
- } catch (err) {
214
- error.value = err instanceof Error ? err.message : 'Unknown error';
215
- } finally {
216
- loading.value = false;
217
- }
218
- };
219
-
220
- return (
221
- <form onSubmit={handleSubmit}>
222
- <input
223
- type="email"
224
- value={email.value}
225
- onInput={(e) => { email.value = e.currentTarget.value; }}
226
- placeholder="Email"
227
- />
228
- <input
229
- type="password"
230
- value={password.value}
231
- onInput={(e) => { password.value = e.currentTarget.value; }}
232
- placeholder="Password"
233
- />
234
- {error.value && <p className="text-red-500">{error.value}</p>}
235
- <button type="submit" disabled={loading.value}>
236
- {loading.value ? 'Loading...' : 'Submit'}
237
- </button>
238
- </form>
239
- );
240
- };
241
-
242
- export default {{name}}Form;
243
- `;
244
- export function getTemplate(type) {
245
- const templates = {
246
- route: routeTemplate,
247
- component: componentTemplate,
248
- layout: layoutTemplate,
249
- error: errorBoundaryTemplate,
250
- loader: loaderTemplate,
251
- action: actionTemplate,
252
- api: apiRouteTemplate,
253
- config: configTemplate,
254
- index: indexTemplate,
255
- signal: signalTemplate,
256
- context: contextTemplate,
257
- form: formTemplate,
258
- };
259
- return templates[type] ?? routeTemplate;
260
- }
261
1
  export function formatTemplate(template, params) {
262
2
  let result = template;
263
3
  for (const [key, value] of Object.entries(params)) {
@@ -0,0 +1,16 @@
1
+ export const layoutTemplate = `import type { RouteComponent } from '@emberkit/core';
2
+
3
+ const {{name}}Layout: RouteComponent = ({ children }) => {
4
+ return (
5
+ <div>
6
+ <header>
7
+ <nav>{{name}} Navigation</nav>
8
+ </header>
9
+ <main>{children}</main>
10
+ <footer>Footer</footer>
11
+ </div>
12
+ );
13
+ };
14
+
15
+ export default {{name}}Layout;
16
+ `;
@@ -0,0 +1,9 @@
1
+ export const layoutRoutesTemplate = `// EmberKit uses file-based routing.
2
+ // Routes are automatically discovered from the src/routes directory.
3
+ // - src/routes/index.tsx → /
4
+ // - src/routes/about.tsx → /about
5
+ // - src/routes/[slug].tsx → /:slug
6
+ // - src/routes/[...rest].tsx → catch-all
7
+
8
+ export {};
9
+ `;
@@ -0,0 +1,10 @@
1
+ export const loaderTemplate = `import type { LoaderFunction, LoaderResult } from '@emberkit/core';
2
+
3
+ export const loader: LoaderFunction = async ({ params, query, request }) => {
4
+ return {
5
+ data: {
6
+ // Add your data here
7
+ },
8
+ } as LoaderResult<unknown>;
9
+ };
10
+ `;
@@ -0,0 +1,89 @@
1
+ export const minimalTemplate = {
2
+ "package.json": `{
3
+ "name": "{{name}}",
4
+ "version": "0.1.0",
5
+ "private": true,
6
+ "type": "module",
7
+ "scripts": {
8
+ "dev": "emberkit dev",
9
+ "build": "emberkit build",
10
+ "preview": "emberkit preview"
11
+ },
12
+ "dependencies": {
13
+ "@emberkit/core": "^0.2.4"
14
+ },
15
+ "devDependencies": {
16
+ "@emberkit/cli": "^0.2.4",
17
+ "typescript": "^5.7.0",
18
+ "vite": "^6.0.0"
19
+ }
20
+ }`,
21
+ "tsconfig.json": `{
22
+ "compilerOptions": {
23
+ "target": "ES2022",
24
+ "module": "ESNext",
25
+ "moduleResolution": "bundler",
26
+ "jsx": "react-jsx",
27
+ "jsxImportSource": "@emberkit/core",
28
+ "strict": true,
29
+ "esModuleInterop": true,
30
+ "skipLibCheck": true,
31
+ "forceConsistentCasingInFileNames": true,
32
+ "resolveJsonModule": true,
33
+ "isolatedModules": true,
34
+ "noEmit": true,
35
+ "lib": ["ES2022", "DOM", "DOM.Iterable"]
36
+ },
37
+ "include": ["src"],
38
+ "exclude": ["node_modules", "dist"]
39
+ }`,
40
+ "vite.config.ts": `import { defineConfig } from 'vite';
41
+ import { emberkitVitePlugin } from '@emberkit/core/vite-plugin';
42
+
43
+ export default defineConfig({
44
+ plugins: [emberkitVitePlugin()],
45
+ server: {
46
+ port: 3000,
47
+ host: 'localhost',
48
+ },
49
+ esbuild: {
50
+ jsxImportSource: '@emberkit/core',
51
+ },
52
+ });`,
53
+ "index.html": `<!DOCTYPE html>
54
+ <html lang="en">
55
+ <head>
56
+ <meta charset="UTF-8">
57
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
58
+ <title>{{name}}</title>
59
+ </head>
60
+ <body id="app">
61
+ <script type="module" src="/src/index.tsx"></script>
62
+ </body>
63
+ </html>`,
64
+ "src/index.tsx": `import { render } from '@emberkit/core';
65
+ import { routes } from 'virtual:emberkit-routes';
66
+ import App from './routes/index';
67
+
68
+ const root = document.getElementById('app');
69
+
70
+ if (root) {
71
+ try {
72
+ render(App, root, { routes });
73
+ } catch (error) {
74
+ console.error('[entry] Render error:', error);
75
+ }
76
+ }`,
77
+ "src/routes/index.tsx": `import type { RouteComponent } from '@emberkit/core';
78
+
79
+ const Home: RouteComponent = () => {
80
+ return (
81
+ <div style={{ fontFamily: 'system-ui, sans-serif', maxWidth: '600px', margin: '2rem auto', padding: '0 1rem' }}>
82
+ <h1>{{name}}</h1>
83
+ <p>Built with EmberKit</p>
84
+ </div>
85
+ );
86
+ };
87
+
88
+ export default Home;`,
89
+ };
@@ -0,0 +1,286 @@
1
+ export const starterFiles = {
2
+ "package.json": `{
3
+ "name": "{{name}}",
4
+ "version": "0.1.0",
5
+ "private": true,
6
+ "type": "module",
7
+ "scripts": {
8
+ "dev": "emberkit dev",
9
+ "build": "emberkit build",
10
+ "preview": "emberkit preview"
11
+ },
12
+ "dependencies": {
13
+ "@emberkit/core": "^0.2.4"
14
+ },
15
+ "devDependencies": {
16
+ "@emberkit/cli": "^0.2.4",
17
+ "typescript": "^5.7.0",
18
+ "vite": "^6.0.0",
19
+ "tailwindcss": "^4.0.0",
20
+ "@tailwindcss/vite": "^4.0.0"
21
+ }
22
+ }`,
23
+ "tsconfig.json": `{
24
+ "compilerOptions": {
25
+ "target": "ES2022",
26
+ "module": "ESNext",
27
+ "moduleResolution": "bundler",
28
+ "jsx": "react-jsx",
29
+ "jsxImportSource": "@emberkit/core",
30
+ "strict": true,
31
+ "esModuleInterop": true,
32
+ "skipLibCheck": true,
33
+ "forceConsistentCasingInFileNames": true,
34
+ "resolveJsonModule": true,
35
+ "isolatedModules": true,
36
+ "noEmit": true,
37
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
38
+ "paths": {
39
+ "@/*": ["./src/*"]
40
+ }
41
+ },
42
+ "include": ["src"],
43
+ "exclude": ["node_modules", "dist"]
44
+ }`,
45
+ "vite.config.ts": `import { defineConfig } from 'vite';
46
+ import { emberkitVitePlugin } from '@emberkit/core/vite-plugin';
47
+ import tailwindcss from '@tailwindcss/vite';
48
+
49
+ export default defineConfig({
50
+ plugins: [emberkitVitePlugin(), tailwindcss()],
51
+ server: {
52
+ port: 3000,
53
+ host: 'localhost',
54
+ },
55
+ esbuild: {
56
+ jsxImportSource: '@emberkit/core',
57
+ },
58
+ });`,
59
+ "index.html": `<!DOCTYPE html>
60
+ <html lang="en">
61
+ <head>
62
+ <meta charset="UTF-8">
63
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
64
+ <title>{{name}}</title>
65
+ <link rel="preconnect" href="https://fonts.googleapis.com">
66
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
67
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
68
+ </head>
69
+ <body id="app">
70
+ <script type="module" src="/src/index.tsx"></script>
71
+ </body>
72
+ </html>`,
73
+ "src/index.tsx": `import { render } from '@emberkit/core';
74
+ import { routes } from 'virtual:emberkit-routes';
75
+ import App from './routes/_layout';
76
+ import './styles.css';
77
+
78
+ const root = document.getElementById('app');
79
+
80
+ if (root) {
81
+ try {
82
+ render(App, root, { routes });
83
+ } catch (error) {
84
+ console.error('[entry] Render error:', error);
85
+ }
86
+ }`,
87
+ "src/styles.css": `@import "tailwindcss";
88
+
89
+ @theme {
90
+ --color-ember-50: #fff7ed;
91
+ --color-ember-100: #ffedd5;
92
+ --color-ember-200: #fed7aa;
93
+ --color-ember-300: #fdba74;
94
+ --color-ember-400: #fb923c;
95
+ --color-ember-500: #f97316;
96
+ --color-ember-600: #ea580c;
97
+ --color-ember-700: #c2410c;
98
+ --color-ember-800: #9a3412;
99
+ --color-ember-900: #7c2d12;
100
+ --font-sans: 'Inter', system-ui, sans-serif;
101
+ }
102
+
103
+ @keyframes float {
104
+ 0%, 100% { transform: translateY(0px); }
105
+ 50% { transform: translateY(-20px); }
106
+ }
107
+
108
+ @keyframes fade-in-up {
109
+ from { opacity: 0; transform: translateY(20px); }
110
+ to { opacity: 1; transform: translateY(0); }
111
+ }
112
+
113
+ @keyframes pulse-glow {
114
+ 0%, 100% { opacity: 0.4; transform: scale(1); }
115
+ 50% { opacity: 0.6; transform: scale(1.05); }
116
+ }
117
+
118
+ body {
119
+ @apply bg-[#0b0f19] text-slate-200 font-sans min-h-screen;
120
+ }
121
+
122
+ a {
123
+ @apply text-inherit no-underline transition-colors;
124
+ }
125
+
126
+ .animate-float { animation: float 6s ease-in-out infinite; }
127
+ .animate-fade-in-up { animation: fade-in-up 0.6s ease-out forwards; }
128
+ .animate-pulse-glow { animation: pulse-glow 4s ease-in-out infinite; }`,
129
+ "src/routes/_layout.tsx": `import type { RouteComponent } from '@emberkit/core';
130
+ import { Head } from '@emberkit/core';
131
+
132
+ const Layout: RouteComponent = ({ children }) => {
133
+ return (
134
+ <>
135
+ <Head>
136
+ <title>{{name}}</title>
137
+ <meta name="description" content="Built with EmberKit" />
138
+ </Head>
139
+ <div className="relative min-h-screen flex flex-col">
140
+ {/* Ambient background */}
141
+ <div className="pointer-events-none fixed inset-0 overflow-hidden">
142
+ <div className="absolute -top-40 -right-40 w-96 h-96 rounded-full bg-ember-500/10 blur-[120px] animate-pulse-glow" />
143
+ <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' }} />
144
+ </div>
145
+
146
+ <header className="relative z-50 border-b border-slate-800/50 bg-[#0b0f19]/80 backdrop-blur-xl sticky top-0">
147
+ <div className="max-w-6xl mx-auto px-6 py-4 flex items-center justify-between">
148
+ <a href="/" className="flex items-center gap-2 group">
149
+ <span className="text-2xl animate-float">&#128293;</span>
150
+ <span className="text-xl font-bold bg-gradient-to-r from-ember-400 to-ember-500 bg-clip-text text-transparent">
151
+ {{name}}
152
+ </span>
153
+ </a>
154
+ <nav className="flex items-center gap-6">
155
+ <a href="/" className="text-slate-400 hover:text-ember-500 font-medium transition-colors">Home</a>
156
+ <a href="/about" className="text-slate-400 hover:text-ember-500 font-medium transition-colors">About</a>
157
+ <a href="https://emberkit.dev/docs" target="_blank" className="text-slate-400 hover:text-ember-500 font-medium transition-colors">
158
+ Docs <span className="text-xs">&#8599;</span>
159
+ </a>
160
+ </nav>
161
+ </div>
162
+ </header>
163
+ <main className="relative z-10 flex-1">{children}</main>
164
+ <footer className="relative z-10 border-t border-slate-800/50 py-8 text-center text-sm text-slate-500">
165
+ <p>Built with <a href="https://emberkit.dev" className="text-ember-500 hover:underline">EmberKit</a></p>
166
+ </footer>
167
+ </div>
168
+ </>
169
+ );
170
+ };
171
+
172
+ export default Layout;`,
173
+ "src/routes/index.tsx": `import type { RouteComponent } from '@emberkit/core';
174
+ import { signal } from '@emberkit/core';
175
+
176
+ const HomePage: RouteComponent = () => {
177
+ const count = signal(0);
178
+
179
+ return (
180
+ <div className="relative max-w-6xl mx-auto px-6 py-16 space-y-24">
181
+ {/* Hero Section */}
182
+ <section className="relative text-center space-y-6 animate-fade-in-down">
183
+ <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" />
184
+ <h1 className="relative z-10 text-5xl md:text-6xl font-extrabold tracking-tight">
185
+ Welcome to <span className="bg-gradient-to-r from-ember-400 via-ember-500 to-amber-500 bg-clip-text text-transparent">{{name}}</span>
186
+ </h1>
187
+ <p className="relative z-10 text-xl text-slate-400 max-w-2xl mx-auto">
188
+ A minimalist TypeScript-first JSX framework built for speed and simplicity.
189
+ Get started in seconds with hot module replacement and zero-config routing.
190
+ </p>
191
+ <div className="relative z-10 flex gap-4 justify-center pt-4">
192
+ <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">
193
+ Learn More
194
+ </a>
195
+ <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">
196
+ Read Docs &#8594;
197
+ </a>
198
+ </div>
199
+ </section>
200
+
201
+ {/* Features Grid */}
202
+ <section className="grid md:grid-cols-3 gap-6">
203
+ {[
204
+ { icon: '&#9889;', title: 'Lightning Fast', desc: 'Sub-10KB runtime with tree-shakeable architecture' },
205
+ { icon: '&#128303;', title: 'TypeScript First', desc: 'Full type safety with intelligent autocomplete' },
206
+ { icon: '&#128726;', title: 'File-Based Routing', desc: 'Routes automatically created from your file structure' },
207
+ ].map((f, i) => (
208
+ <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: \`\[PHONE REDACTED]ms\` }}>
209
+ <div className="text-3xl mb-4 group-hover:scale-110 transition-transform">{f.icon}</div>
210
+ <h3 className="text-lg font-semibold mb-2">{f.title}</h3>
211
+ <p className="text-slate-400">{f.desc}</p>
212
+ </div>
213
+ ))}
214
+ </section>
215
+
216
+ {/* Counter Demo */}
217
+ <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">
218
+ <h2 className="text-2xl font-bold mb-6">Interactive Counter Demo</h2>
219
+ <div className="flex items-center justify-center gap-6">
220
+ <button
221
+ 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"
222
+ onClick={() => count.value--}
223
+ >
224
+ &#8722;
225
+ </button>
226
+ <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>
227
+ <button
228
+ 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"
229
+ onClick={() => count.value++}
230
+ >
231
+ +
232
+ </button>
233
+ </div>
234
+ <p className="text-slate-500 mt-4 text-sm">Try clicking the buttons!</p>
235
+ </section>
236
+
237
+ <section className="text-center py-8">
238
+ <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">
239
+ <span className="w-2 h-2 rounded-full bg-ember-500 animate-pulse"></span>
240
+ <span className="text-sm font-medium">Ready to build something amazing?</span>
241
+ </div>
242
+ </section>
243
+ </div>
244
+ );
245
+ };
246
+
247
+ export default HomePage;`,
248
+ "src/routes/about.tsx": `import type { RouteComponent } from '@emberkit/core';
249
+ import { Head } from '@emberkit/core';
250
+
251
+ const AboutPage: RouteComponent = () => {
252
+ return (
253
+ <div className="max-w-3xl mx-auto px-6 py-16 space-y-12 animate-fade-in-up">
254
+ <Head>
255
+ <title>About - {{name}}</title>
256
+ </Head>
257
+ <div className="text-center">
258
+ <h1 className="text-4xl font-bold mb-6">About {{name}}</h1>
259
+ <p className="text-slate-400 text-lg leading-relaxed max-w-2xl mx-auto">
260
+ EmberKit is a minimalist TypeScript-first JSX framework built for speed and simplicity.
261
+ It combines the best of modern frontend development with a lightweight runtime.
262
+ </p>
263
+ </div>
264
+ <div className="grid sm:grid-cols-3 gap-4">
265
+ {[
266
+ { label: 'SPA & SSR', desc: 'Works in both modes' },
267
+ { label: 'Zero Config', desc: 'Sensible defaults' },
268
+ { label: 'HMR', desc: 'Hot module replacement' },
269
+ ].map((f) => (
270
+ <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">
271
+ <span className="text-ember-500 text-sm font-semibold">{f.label}</span>
272
+ <p className="text-slate-500 text-sm mt-1">{f.desc}</p>
273
+ </div>
274
+ ))}
275
+ </div>
276
+ <div className="text-center">
277
+ <a href="/" className="inline-flex items-center gap-2 text-ember-500 hover:text-ember-400 font-medium transition-colors">
278
+ &#8592; Back to Home
279
+ </a>
280
+ </div>
281
+ </div>
282
+ );
283
+ };
284
+
285
+ export default AboutPage;`,
286
+ };