create-ereo 0.1.21 → 0.1.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +480 -84
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -44,7 +44,18 @@ function parseArgs(args) {
44
44
  process.exit(0);
45
45
  }
46
46
  if (arg === "-t" || arg === "--template") {
47
- options.template = args[++i];
47
+ if (i + 1 >= args.length) {
48
+ console.error(` \x1B[31m\u2717\x1B[0m --template requires a value (minimal, default, tailwind)
49
+ `);
50
+ process.exit(1);
51
+ }
52
+ const tmpl = args[++i];
53
+ if (tmpl !== "minimal" && tmpl !== "default" && tmpl !== "tailwind") {
54
+ console.error(` \x1B[31m\u2717\x1B[0m Unknown template "${tmpl}". Valid options: minimal, default, tailwind
55
+ `);
56
+ process.exit(1);
57
+ }
58
+ options.template = tmpl;
48
59
  } else if (arg === "--no-typescript") {
49
60
  options.typescript = false;
50
61
  } else if (arg === "--no-git") {
@@ -107,6 +118,88 @@ export default function RootLayout({ children }${typescript ? ": { children: Rea
107
118
  <meta charSet="utf-8" />
108
119
  <meta name="viewport" content="width=device-width, initial-scale=1" />
109
120
  <title>${projectName}</title>
121
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
122
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="" />
123
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
124
+ <style dangerouslySetInnerHTML={{ __html: \`
125
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
126
+ :root {
127
+ --brand-500: #6366f1;
128
+ --brand-600: #4f46e5;
129
+ --brand-400: #818cf8;
130
+ --purple-500: #8b5cf6;
131
+ --violet-500: #a855f7;
132
+ --bg: #ffffff;
133
+ --bg-soft: #f8fafc;
134
+ --bg-card: #ffffff;
135
+ --text: #0f172a;
136
+ --text-soft: #64748b;
137
+ --border: #e2e8f0;
138
+ --code-bg: #1e1e2e;
139
+ }
140
+ @media (prefers-color-scheme: dark) {
141
+ :root {
142
+ --bg: #0f172a;
143
+ --bg-soft: #1e293b;
144
+ --bg-card: #1e293b;
145
+ --text: #f1f5f9;
146
+ --text-soft: #94a3b8;
147
+ --border: #334155;
148
+ --code-bg: #0f172a;
149
+ }
150
+ }
151
+ body {
152
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
153
+ background: var(--bg);
154
+ color: var(--text);
155
+ line-height: 1.6;
156
+ -webkit-font-smoothing: antialiased;
157
+ }
158
+ @keyframes fadeInUp {
159
+ from { opacity: 0; transform: translateY(24px); }
160
+ to { opacity: 1; transform: translateY(0); }
161
+ }
162
+ @keyframes float {
163
+ 0%, 100% { transform: translateY(0); }
164
+ 50% { transform: translateY(-10px); }
165
+ }
166
+ @keyframes gradientShift {
167
+ 0%, 100% { background-position: 0% 50%; }
168
+ 50% { background-position: 100% 50%; }
169
+ }
170
+ .hero { min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 2rem; position: relative; overflow: hidden; }
171
+ .hero::before { content: ''; position: absolute; inset: 0; background: radial-gradient(ellipse at 50% 0%, rgba(99,102,241,0.15) 0%, transparent 70%); pointer-events: none; }
172
+ .hero-logo { animation: float 4s ease-in-out infinite; margin-bottom: 2rem; }
173
+ .gradient-text { background: linear-gradient(135deg, var(--brand-500), var(--purple-500), var(--violet-500)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; background-size: 200% 200%; animation: gradientShift 6s ease infinite; }
174
+ .hero h1 { font-size: clamp(2.5rem, 6vw, 4.5rem); font-weight: 800; letter-spacing: -0.03em; margin-bottom: 1rem; animation: fadeInUp 0.6s ease both; }
175
+ .hero p { font-size: 1.25rem; color: var(--text-soft); max-width: 540px; margin-bottom: 2rem; animation: fadeInUp 0.6s ease 0.15s both; }
176
+ .btn-group { display: flex; gap: 1rem; flex-wrap: wrap; justify-content: center; animation: fadeInUp 0.6s ease 0.3s both; }
177
+ .cta-btn { display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.75rem 1.75rem; border-radius: 0.75rem; font-weight: 600; font-size: 0.95rem; text-decoration: none; transition: all 0.2s; }
178
+ .cta-btn-primary { background: var(--brand-500); color: white; }
179
+ .cta-btn-primary:hover { background: var(--brand-600); transform: translateY(-1px); box-shadow: 0 8px 24px rgba(99,102,241,0.3); }
180
+ .cta-btn-secondary { border: 2px solid var(--border); color: var(--text); background: transparent; }
181
+ .cta-btn-secondary:hover { border-color: var(--brand-500); color: var(--brand-500); }
182
+ .feature-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5rem; max-width: 72rem; margin: 0 auto; padding: 5rem 2rem; }
183
+ .feature-card { background: var(--bg-card); border: 1px solid var(--border); border-radius: 1rem; padding: 2rem; transition: all 0.25s; }
184
+ .feature-card:hover { border-color: var(--brand-400); transform: translateY(-4px); box-shadow: 0 12px 32px rgba(99,102,241,0.1); }
185
+ .feature-icon { width: 48px; height: 48px; border-radius: 12px; display: flex; align-items: center; justify-content: center; margin-bottom: 1rem; background: linear-gradient(135deg, rgba(99,102,241,0.1), rgba(139,92,246,0.1)); }
186
+ .feature-card h3 { font-size: 1.15rem; font-weight: 700; margin-bottom: 0.5rem; }
187
+ .feature-card p { color: var(--text-soft); font-size: 0.925rem; }
188
+ .code-window { background: var(--code-bg); border-radius: 1rem; overflow: hidden; max-width: 540px; margin: 0 auto; text-align: left; animation: fadeInUp 0.6s ease 0.45s both; }
189
+ .code-header { display: flex; align-items: center; gap: 6px; padding: 0.875rem 1.25rem; background: rgba(255,255,255,0.05); }
190
+ .code-dot { width: 12px; height: 12px; border-radius: 50%; }
191
+ .code-dot-r { background: #ff5f57; }
192
+ .code-dot-y { background: #febc2e; }
193
+ .code-dot-g { background: #28c840; }
194
+ .code-body { padding: 1.25rem; font-family: 'JetBrains Mono', monospace; font-size: 0.875rem; color: #a5b4fc; line-height: 1.8; }
195
+ .code-body .prompt { color: #6ee7b7; }
196
+ .quickstart-section { background: var(--bg-soft); padding: 5rem 2rem; text-align: center; }
197
+ .quickstart-section h2 { font-size: 2rem; font-weight: 700; margin-bottom: 1rem; }
198
+ .quickstart-section .subtitle { color: var(--text-soft); margin-bottom: 2rem; }
199
+ .site-footer { text-align: center; padding: 3rem 2rem; color: var(--text-soft); font-size: 0.875rem; border-top: 1px solid var(--border); }
200
+ .site-footer a { color: var(--brand-500); text-decoration: none; }
201
+ .site-footer a:hover { text-decoration: underline; }
202
+ \` }} />
110
203
  </head>
111
204
  <body>
112
205
  {children}
@@ -119,10 +212,102 @@ export default function RootLayout({ children }${typescript ? ": { children: Rea
119
212
  const indexPage = `
120
213
  export default function HomePage() {
121
214
  return (
122
- <main>
123
- <h1>Welcome to EreoJS!</h1>
124
- <p>Edit app/routes/index.${ext} to get started.</p>
125
- </main>
215
+ <>
216
+ {/* Hero */}
217
+ <section className="hero">
218
+ <div className="hero-logo">
219
+ <svg width="72" height="72" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
220
+ <path d="M40 8L72 24V56L40 72L8 56V24L40 8Z" stroke="url(#logo-grad)" strokeWidth="3" fill="none" />
221
+ <path d="M40 20L60 30V50L40 60L20 50V30L40 20Z" fill="url(#logo-grad)" opacity="0.15" />
222
+ <path d="M40 28L52 34V46L40 52L28 46V34L40 28Z" fill="url(#logo-grad)" />
223
+ <defs>
224
+ <linearGradient id="logo-grad" x1="8" y1="8" x2="72" y2="72">
225
+ <stop stopColor="#6366f1" />
226
+ <stop offset="1" stopColor="#a855f7" />
227
+ </linearGradient>
228
+ </defs>
229
+ </svg>
230
+ </div>
231
+ <h1><span className="gradient-text">EreoJS</span></h1>
232
+ <p>A React fullstack framework built on Bun. Fast server-side rendering, file-based routing, and islands architecture.</p>
233
+ <div className="btn-group">
234
+ <a href="https://ereo.dev/docs" className="cta-btn cta-btn-primary">Get Started</a>
235
+ <a href="https://github.com/nicholasgriffintn/ereo" className="cta-btn cta-btn-secondary">
236
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/></svg>
237
+ GitHub
238
+ </a>
239
+ </div>
240
+ </section>
241
+
242
+ {/* Features */}
243
+ <div className="feature-grid">
244
+ <div className="feature-card">
245
+ <div className="feature-icon">
246
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#6366f1" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>
247
+ </div>
248
+ <h3>Bun-Powered</h3>
249
+ <p>Built on Bun for blazing-fast startup, builds, and runtime performance.</p>
250
+ </div>
251
+ <div className="feature-card">
252
+ <div className="feature-icon">
253
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#6366f1" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z"/></svg>
254
+ </div>
255
+ <h3>File Routing</h3>
256
+ <p>Intuitive file-based routing with nested layouts and dynamic segments.</p>
257
+ </div>
258
+ <div className="feature-card">
259
+ <div className="feature-icon">
260
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#6366f1" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
261
+ </div>
262
+ <h3>Server-Side Rendering</h3>
263
+ <p>Stream HTML from the server for fast, SEO-friendly initial page loads.</p>
264
+ </div>
265
+ <div className="feature-card">
266
+ <div className="feature-icon">
267
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#6366f1" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"/><path d="M8 12l2 2 4-4"/></svg>
268
+ </div>
269
+ <h3>Islands Architecture</h3>
270
+ <p>Selective hydration \u2014 only interactive parts ship JavaScript to the client.</p>
271
+ </div>
272
+ <div className="feature-card">
273
+ <div className="feature-icon">
274
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#6366f1" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
275
+ </div>
276
+ <h3>Loaders &amp; Actions</h3>
277
+ <p>Server-side data loading and form handling with simple async functions.</p>
278
+ </div>
279
+ <div className="feature-card">
280
+ <div className="feature-icon">
281
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#6366f1" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>
282
+ </div>
283
+ <h3>TypeScript First</h3>
284
+ <p>Full type safety out of the box with zero-config TypeScript support.</p>
285
+ </div>
286
+ </div>
287
+
288
+ {/* Quick Start */}
289
+ <section className="quickstart-section">
290
+ <h2>Get Started in Seconds</h2>
291
+ <p className="subtitle">One command to scaffold your project.</p>
292
+ <div className="code-window">
293
+ <div className="code-header">
294
+ <span className="code-dot code-dot-r" />
295
+ <span className="code-dot code-dot-y" />
296
+ <span className="code-dot code-dot-g" />
297
+ </div>
298
+ <div className="code-body">
299
+ <div><span className="prompt">$</span> bunx create-ereo my-app</div>
300
+ <div><span className="prompt">$</span> cd my-app</div>
301
+ <div><span className="prompt">$</span> bun run dev</div>
302
+ </div>
303
+ </div>
304
+ </section>
305
+
306
+ {/* Footer */}
307
+ <footer className="site-footer">
308
+ <p>Built with EreoJS &mdash; <a href="https://ereo.dev/docs">Docs</a> &middot; <a href="https://github.com/nicholasgriffintn/ereo">GitHub</a></p>
309
+ </footer>
310
+ </>
126
311
  );
127
312
  }
128
313
  `.trim();
@@ -249,18 +434,48 @@ export default {
249
434
  darkMode: 'class',
250
435
  theme: {
251
436
  extend: {
437
+ fontFamily: {
438
+ sans: ['Inter', 'system-ui', '-apple-system', 'sans-serif'],
439
+ mono: ['JetBrains Mono', 'Fira Code', 'monospace'],
440
+ },
252
441
  colors: {
253
442
  primary: {
254
- 50: '#eff6ff',
255
- 100: '#dbeafe',
256
- 200: '#bfdbfe',
257
- 300: '#93c5fd',
258
- 400: '#60a5fa',
259
- 500: '#3b82f6',
260
- 600: '#2563eb',
261
- 700: '#1d4ed8',
262
- 800: '#1e40af',
263
- 900: '#1e3a8a',
443
+ 50: '#eef2ff',
444
+ 100: '#e0e7ff',
445
+ 200: '#c7d2fe',
446
+ 300: '#a5b4fc',
447
+ 400: '#818cf8',
448
+ 500: '#6366f1',
449
+ 600: '#4f46e5',
450
+ 700: '#4338ca',
451
+ 800: '#3730a3',
452
+ 900: '#312e81',
453
+ 950: '#1e1b4b',
454
+ },
455
+ },
456
+ animation: {
457
+ 'fade-in': 'fadeIn 0.5s ease forwards',
458
+ 'slide-up': 'slideUp 0.6s ease forwards',
459
+ 'float': 'float 4s ease-in-out infinite',
460
+ 'gradient': 'gradientShift 6s ease infinite',
461
+ 'pulse-slow': 'pulse 3s ease-in-out infinite',
462
+ },
463
+ keyframes: {
464
+ fadeIn: {
465
+ '0%': { opacity: '0' },
466
+ '100%': { opacity: '1' },
467
+ },
468
+ slideUp: {
469
+ '0%': { opacity: '0', transform: 'translateY(20px)' },
470
+ '100%': { opacity: '1', transform: 'translateY(0)' },
471
+ },
472
+ float: {
473
+ '0%, 100%': { transform: 'translateY(0)' },
474
+ '50%': { transform: 'translateY(-10px)' },
475
+ },
476
+ gradientShift: {
477
+ '0%, 100%': { backgroundPosition: '0% 50%' },
478
+ '50%': { backgroundPosition: '100% 50%' },
264
479
  },
265
480
  },
266
481
  },
@@ -270,22 +485,24 @@ export default {
270
485
  `.trim();
271
486
  await Bun.write(join(projectDir, "tailwind.config.js"), tailwindConfig);
272
487
  const styles = `
488
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap');
489
+
273
490
  @tailwind base;
274
491
  @tailwind components;
275
492
  @tailwind utilities;
276
493
 
277
494
  @layer base {
278
495
  body {
279
- @apply antialiased;
496
+ @apply antialiased font-sans;
280
497
  }
281
498
  }
282
499
 
283
500
  @layer components {
284
501
  .btn {
285
- @apply px-4 py-2 rounded-lg font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2;
502
+ @apply px-4 py-2 rounded-lg font-medium transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2;
286
503
  }
287
504
  .btn-primary {
288
- @apply bg-primary-600 text-white hover:bg-primary-700 focus:ring-primary-500;
505
+ @apply bg-primary-600 text-white hover:bg-primary-700 focus:ring-primary-500 hover:-translate-y-0.5 hover:shadow-lg;
289
506
  }
290
507
  .btn-secondary {
291
508
  @apply bg-gray-200 text-gray-900 hover:bg-gray-300 focus:ring-gray-500 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600;
@@ -296,6 +513,33 @@ export default {
296
513
  .card {
297
514
  @apply bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6;
298
515
  }
516
+ .gradient-text {
517
+ @apply bg-clip-text text-transparent bg-gradient-to-r from-primary-500 via-purple-500 to-violet-500;
518
+ background-size: 200% 200%;
519
+ animation: gradientShift 6s ease infinite;
520
+ }
521
+ .code-window {
522
+ @apply rounded-xl overflow-hidden;
523
+ background: #1e1e2e;
524
+ }
525
+ .code-window-header {
526
+ @apply flex items-center gap-1.5 px-4 py-3;
527
+ background: rgba(255, 255, 255, 0.05);
528
+ }
529
+ .code-window-dot {
530
+ @apply w-3 h-3 rounded-full;
531
+ }
532
+ .glow {
533
+ box-shadow: 0 0 40px rgba(99, 102, 241, 0.15), 0 0 80px rgba(99, 102, 241, 0.05);
534
+ }
535
+ }
536
+
537
+ @layer utilities {
538
+ .delay-100 { animation-delay: 100ms; }
539
+ .delay-200 { animation-delay: 200ms; }
540
+ .delay-300 { animation-delay: 300ms; }
541
+ .delay-400 { animation-delay: 400ms; }
542
+ .delay-500 { animation-delay: 500ms; }
299
543
  }
300
544
  `.trim();
301
545
  await Bun.write(join(projectDir, "app/styles.css"), styles);
@@ -495,8 +739,17 @@ export function Navigation() {
495
739
  <div className="max-w-6xl mx-auto px-4">
496
740
  <div className="flex items-center justify-between h-16">
497
741
  {/* Logo */}
498
- <a href="/" className="flex items-center space-x-2">
499
- <span className="text-2xl">\u2B21</span>
742
+ <a href="/" className="flex items-center space-x-2 group">
743
+ <svg width="28" height="28" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg" className="transition-transform group-hover:scale-110">
744
+ <path d="M40 8L72 24V56L40 72L8 56V24L40 8Z" stroke="url(#nav-grad)" strokeWidth="3" fill="none" />
745
+ <path d="M40 28L52 34V46L40 52L28 46V34L40 28Z" fill="url(#nav-grad)" />
746
+ <defs>
747
+ <linearGradient id="nav-grad" x1="8" y1="8" x2="72" y2="72">
748
+ <stop stopColor="#6366f1" />
749
+ <stop offset="1" stopColor="#a855f7" />
750
+ </linearGradient>
751
+ </defs>
752
+ </svg>
500
753
  <span className="font-bold text-xl">EreoJS</span>
501
754
  </a>
502
755
 
@@ -671,6 +924,9 @@ export default function RootLayout({ children }${ts ? ": RootLayoutProps" : ""})
671
924
  <meta name="viewport" content="width=device-width, initial-scale=1" />
672
925
  <meta name="description" content="A modern web application built with EreoJS" />
673
926
  <title>${projectName}</title>
927
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
928
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="" />
929
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
674
930
  <link rel="stylesheet" href="/app/styles.css" />
675
931
  </head>
676
932
  <body className="min-h-screen flex flex-col bg-white dark:bg-gray-900 text-gray-900 dark:text-white">
@@ -726,100 +982,210 @@ export default function HomePage({ loaderData }${ts ? ": HomePageProps" : ""}) {
726
982
  const { featuredPost, stats } = loaderData;
727
983
 
728
984
  return (
729
- <div className="min-h-screen">
730
- {/* Hero Section */}
731
- <section className="py-20 px-4 bg-gradient-to-br from-primary-500 to-purple-600 text-white">
732
- <div className="max-w-4xl mx-auto text-center">
733
- <h1 className="text-5xl md:text-6xl font-bold mb-6">
734
- Welcome to EreoJS
985
+ <div>
986
+ {/* Hero */}
987
+ <section className="relative min-h-[90vh] flex flex-col items-center justify-center text-center px-4 overflow-hidden">
988
+ {/* Background */}
989
+ <div className="absolute inset-0 bg-gradient-to-b from-primary-50/50 via-transparent to-transparent dark:from-primary-950/30 dark:via-transparent dark:to-transparent" />
990
+ <div className="absolute inset-0" style={{ background: 'radial-gradient(ellipse at 50% 0%, rgba(99,102,241,0.12) 0%, transparent 60%)' }} />
991
+
992
+ <div className="relative z-10 max-w-4xl mx-auto">
993
+ {/* Logo */}
994
+ <div className="animate-float mb-8 opacity-0 animate-fade-in">
995
+ <svg width="72" height="72" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
996
+ <path d="M40 8L72 24V56L40 72L8 56V24L40 8Z" stroke="url(#hero-grad)" strokeWidth="3" fill="none" />
997
+ <path d="M40 20L60 30V50L40 60L20 50V30L40 20Z" fill="url(#hero-grad)" opacity="0.15" />
998
+ <path d="M40 28L52 34V46L40 52L28 46V34L40 28Z" fill="url(#hero-grad)" />
999
+ <defs>
1000
+ <linearGradient id="hero-grad" x1="8" y1="8" x2="72" y2="72">
1001
+ <stop stopColor="#6366f1" />
1002
+ <stop offset="1" stopColor="#a855f7" />
1003
+ </linearGradient>
1004
+ </defs>
1005
+ </svg>
1006
+ </div>
1007
+
1008
+ {/* Version Badge */}
1009
+ <div className="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-primary-100 dark:bg-primary-900/40 text-primary-700 dark:text-primary-300 text-sm font-medium mb-6 opacity-0 animate-slide-up">
1010
+ <span className="w-2 h-2 rounded-full bg-green-500 animate-pulse-slow" />
1011
+ v0.1.7
1012
+ </div>
1013
+
1014
+ <h1 className="text-5xl sm:text-6xl md:text-7xl font-extrabold tracking-tight mb-6 opacity-0 animate-slide-up delay-100">
1015
+ Build Faster with <span className="gradient-text">EreoJS</span>
735
1016
  </h1>
736
- <p className="text-xl md:text-2xl mb-8 text-primary-100">
737
- A React fullstack framework built on Bun.
738
- <br />
739
- Fast, simple, and powerful.
1017
+
1018
+ <p className="text-lg sm:text-xl text-gray-600 dark:text-gray-400 max-w-2xl mx-auto mb-10 opacity-0 animate-slide-up delay-200">
1019
+ A React fullstack framework built on Bun. Server-side rendering, file-based routing, and islands architecture out of the box.
740
1020
  </p>
741
- <div className="flex flex-wrap gap-4 justify-center">
742
- <a href="/blog" className="btn bg-white text-primary-600 hover:bg-primary-50">
743
- Read the Blog
1021
+
1022
+ {/* Terminal Preview */}
1023
+ <div className="code-window glow max-w-md mx-auto text-left opacity-0 animate-slide-up delay-300">
1024
+ <div className="code-window-header">
1025
+ <span className="code-window-dot bg-red-500" />
1026
+ <span className="code-window-dot bg-yellow-500" />
1027
+ <span className="code-window-dot bg-green-500" />
1028
+ </div>
1029
+ <div className="px-5 py-4 font-mono text-sm text-primary-300 leading-relaxed">
1030
+ <div><span className="text-emerald-400">$</span> bunx create-ereo my-app</div>
1031
+ <div><span className="text-emerald-400">$</span> cd my-app</div>
1032
+ <div><span className="text-emerald-400">$</span> bun run dev</div>
1033
+ </div>
1034
+ </div>
1035
+
1036
+ {/* CTA Buttons */}
1037
+ <div className="flex flex-wrap gap-4 justify-center mt-10 opacity-0 animate-slide-up delay-400">
1038
+ <a href="https://ereo.dev/docs" className="btn btn-primary text-base px-6 py-3">
1039
+ Get Started
744
1040
  </a>
745
- <a
746
- href="https://github.com/ereo-js/ereo"
747
- target="_blank"
748
- rel="noopener"
749
- className="btn border-2 border-white text-white hover:bg-white/10"
750
- >
751
- View on GitHub
1041
+ <a href="/blog" className="btn btn-secondary text-base px-6 py-3">
1042
+ Read the Blog
752
1043
  </a>
753
1044
  </div>
754
1045
  </div>
755
1046
  </section>
756
1047
 
757
- {/* Features Section */}
758
- <section className="py-16 px-4">
1048
+ {/* Features */}
1049
+ <section className="py-20 px-4">
759
1050
  <div className="max-w-6xl mx-auto">
760
- <h2 className="text-3xl font-bold text-center mb-12">Why EreoJS?</h2>
761
- <div className="grid md:grid-cols-3 gap-8">
762
- <div className="card text-center">
763
- <div className="text-4xl mb-4">\u26A1</div>
764
- <h3 className="text-xl font-bold mb-2">Blazing Fast</h3>
765
- <p className="text-gray-600 dark:text-gray-400">
766
- Built on Bun for exceptional performance. Server-side rendering with streaming support.
767
- </p>
768
- </div>
769
- <div className="card text-center">
770
- <div className="text-4xl mb-4">\uD83C\uDFAF</div>
771
- <h3 className="text-xl font-bold mb-2">Simple Data Loading</h3>
772
- <p className="text-gray-600 dark:text-gray-400">
773
- One pattern for data fetching. Loaders and actions make it easy to build dynamic apps.
774
- </p>
1051
+ <div className="text-center mb-16">
1052
+ <h2 className="text-3xl sm:text-4xl font-bold mb-4">Everything You Need</h2>
1053
+ <p className="text-gray-600 dark:text-gray-400 text-lg max-w-2xl mx-auto">
1054
+ EreoJS combines the best of React with Bun's performance to deliver a complete fullstack framework.
1055
+ </p>
1056
+ </div>
1057
+ <div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
1058
+ {[
1059
+ { icon: 'bolt', title: 'Bun-Powered', desc: 'Lightning-fast startup, builds, and runtime. 10x faster than Node.js for common operations.' },
1060
+ { icon: 'folder', title: 'File Routing', desc: 'Intuitive file-based routing with nested layouts, dynamic segments, and automatic code splitting.' },
1061
+ { icon: 'server', title: 'Server-Side Rendering', desc: 'Stream HTML from the server for fast initial loads with full SEO support.' },
1062
+ { icon: 'island', title: 'Islands Architecture', desc: 'Selective hydration means only interactive parts ship JavaScript to the client.' },
1063
+ { icon: 'data', title: 'Loaders & Actions', desc: 'Simple async functions for server-side data loading and form handling.' },
1064
+ { icon: 'ts', title: 'TypeScript First', desc: 'Full type safety with zero-config TypeScript. Types flow from server to client.' },
1065
+ ].map((feature) => (
1066
+ <div key={feature.title} className="group card hover:shadow-xl hover:-translate-y-1 transition-all duration-300 border border-transparent hover:border-primary-200 dark:hover:border-primary-800">
1067
+ <div className="w-12 h-12 rounded-xl flex items-center justify-center mb-4 bg-gradient-to-br from-primary-100 to-purple-100 dark:from-primary-900/40 dark:to-purple-900/40 group-hover:from-primary-200 group-hover:to-purple-200 dark:group-hover:from-primary-900/60 dark:group-hover:to-purple-900/60 transition-colors">
1068
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#6366f1" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
1069
+ {feature.icon === 'bolt' && <polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2" />}
1070
+ {feature.icon === 'folder' && <path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z" />}
1071
+ {feature.icon === 'server' && <><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></>}
1072
+ {feature.icon === 'island' && <><circle cx="12" cy="12" r="10"/><path d="M8 12l2 2 4-4"/></>}
1073
+ {feature.icon === 'data' && <><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></>}
1074
+ {feature.icon === 'ts' && <><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></>}
1075
+ </svg>
1076
+ </div>
1077
+ <h3 className="text-lg font-bold mb-2">{feature.title}</h3>
1078
+ <p className="text-gray-600 dark:text-gray-400 text-sm leading-relaxed">{feature.desc}</p>
1079
+ </div>
1080
+ ))}
1081
+ </div>
1082
+ </div>
1083
+ </section>
1084
+
1085
+ {/* Code Showcase */}
1086
+ <section className="py-20 px-4 bg-gray-50 dark:bg-gray-800/50">
1087
+ <div className="max-w-6xl mx-auto">
1088
+ <div className="text-center mb-16">
1089
+ <h2 className="text-3xl sm:text-4xl font-bold mb-4">Simple, Powerful APIs</h2>
1090
+ <p className="text-gray-600 dark:text-gray-400 text-lg">Loaders fetch data. Actions handle mutations. It's that simple.</p>
1091
+ </div>
1092
+ <div className="grid md:grid-cols-2 gap-6">
1093
+ <div className="code-window">
1094
+ <div className="code-window-header">
1095
+ <span className="code-window-dot bg-red-500" />
1096
+ <span className="code-window-dot bg-yellow-500" />
1097
+ <span className="code-window-dot bg-green-500" />
1098
+ <span className="ml-3 text-xs text-gray-500 font-mono">routes/users.tsx</span>
1099
+ </div>
1100
+ <pre className="px-5 py-4 font-mono text-sm text-primary-300 leading-relaxed overflow-x-auto">
1101
+ {\`// Data runs on the server
1102
+ export async function loader() {
1103
+ const users = await db.user.findMany();
1104
+ return { users };
1105
+ }
1106
+
1107
+ // Component renders on server + client
1108
+ export default function Users({ loaderData }) {
1109
+ return (
1110
+ <ul>
1111
+ {loaderData.users.map(user => (
1112
+ <li key={user.id}>{user.name}</li>
1113
+ ))}
1114
+ </ul>
1115
+ );
1116
+ }\`}
1117
+ </pre>
775
1118
  </div>
776
- <div className="card text-center">
777
- <div className="text-4xl mb-4">\uD83C\uDFDD\uFE0F</div>
778
- <h3 className="text-xl font-bold mb-2">Islands Architecture</h3>
779
- <p className="text-gray-600 dark:text-gray-400">
780
- Selective hydration means smaller bundles and faster interactivity where it matters.
781
- </p>
1119
+ <div className="code-window">
1120
+ <div className="code-window-header">
1121
+ <span className="code-window-dot bg-red-500" />
1122
+ <span className="code-window-dot bg-yellow-500" />
1123
+ <span className="code-window-dot bg-green-500" />
1124
+ <span className="ml-3 text-xs text-gray-500 font-mono">routes/contact.tsx</span>
1125
+ </div>
1126
+ <pre className="px-5 py-4 font-mono text-sm text-primary-300 leading-relaxed overflow-x-auto">
1127
+ {\`// Actions handle form submissions
1128
+ export async function action({ request }) {
1129
+ const form = await request.formData();
1130
+ await db.message.create({
1131
+ data: {
1132
+ name: form.get('name'),
1133
+ email: form.get('email'),
1134
+ body: form.get('message'),
1135
+ }
1136
+ });
1137
+ return { success: true };
1138
+ }\`}
1139
+ </pre>
782
1140
  </div>
783
1141
  </div>
784
1142
  </div>
785
1143
  </section>
786
1144
 
787
- {/* Interactive Demo Section */}
788
- <section className="py-16 px-4 bg-gray-50 dark:bg-gray-800">
789
- <div className="max-w-4xl mx-auto text-center">
790
- <h2 className="text-3xl font-bold mb-4">Interactive Islands</h2>
791
- <p className="text-gray-600 dark:text-gray-400 mb-8">
792
- This counter component is an "island" - only this part of the page is hydrated with JavaScript.
793
- </p>
794
- <div className="flex justify-center">
795
- <Counter initialCount={0} />
1145
+ {/* Interactive Demo */}
1146
+ <section className="py-20 px-4">
1147
+ <div className="max-w-4xl mx-auto">
1148
+ <div className="card text-center border border-gray-200 dark:border-gray-700">
1149
+ <div className="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-primary-100 dark:bg-primary-900/40 text-primary-700 dark:text-primary-300 text-xs font-medium mb-6">
1150
+ Interactive Island
1151
+ </div>
1152
+ <h2 className="text-3xl font-bold mb-3">Islands Architecture</h2>
1153
+ <p className="text-gray-600 dark:text-gray-400 mb-8 max-w-lg mx-auto">
1154
+ This counter is an island \u2014 it's the only part of this page that ships JavaScript. The rest is pure HTML from the server.
1155
+ </p>
1156
+ <div className="flex justify-center">
1157
+ <Counter initialCount={0} />
1158
+ </div>
796
1159
  </div>
797
1160
  </div>
798
1161
  </section>
799
1162
 
800
- {/* Server Data Section */}
801
- <section className="py-16 px-4">
1163
+ {/* Server Data */}
1164
+ <section className="py-20 px-4 bg-gray-50 dark:bg-gray-800/50">
802
1165
  <div className="max-w-4xl mx-auto">
803
- <div className="card">
804
- <h2 className="text-2xl font-bold mb-6">Server-Side Data</h2>
1166
+ <div className="card border border-gray-200 dark:border-gray-700">
1167
+ <div className="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-emerald-100 dark:bg-emerald-900/40 text-emerald-700 dark:text-emerald-300 text-xs font-medium mb-6">
1168
+ Server Loader Data
1169
+ </div>
1170
+ <h2 className="text-2xl font-bold mb-2">Loaded at Build Time</h2>
805
1171
  <p className="text-gray-600 dark:text-gray-400 mb-6">
806
- This data was loaded on the server using a loader function:
1172
+ This data was fetched on the server via a loader function \u2014 zero client-side fetching.
807
1173
  </p>
808
- <div className="grid md:grid-cols-2 gap-6">
809
- <div className="p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
1174
+ <div className="grid sm:grid-cols-2 gap-4 mb-6">
1175
+ <div className="p-5 bg-gray-50 dark:bg-gray-700/50 rounded-xl">
810
1176
  <div className="text-sm text-gray-500 dark:text-gray-400 mb-1">Blog Posts</div>
811
- <div className="text-3xl font-bold">{stats.posts}</div>
1177
+ <div className="text-4xl font-extrabold">{stats.posts}</div>
812
1178
  </div>
813
- <div className="p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
1179
+ <div className="p-5 bg-gray-50 dark:bg-gray-700/50 rounded-xl">
814
1180
  <div className="text-sm text-gray-500 dark:text-gray-400 mb-1">Rendered At</div>
815
- <div className="text-3xl font-bold">{stats.serverTime}</div>
1181
+ <div className="text-4xl font-extrabold font-mono">{stats.serverTime}</div>
816
1182
  </div>
817
1183
  </div>
818
1184
  {featuredPost && (
819
- <div className="mt-6 pt-6 border-t border-gray-200 dark:border-gray-700">
1185
+ <div className="pt-6 border-t border-gray-200 dark:border-gray-700">
820
1186
  <div className="text-sm text-gray-500 dark:text-gray-400 mb-2">Featured Post</div>
821
1187
  <h3 className="text-xl font-bold mb-2">
822
- <a href={\`/blog/\${featuredPost.slug}\`} className="hover:text-primary-600">
1188
+ <a href={\`/blog/\${featuredPost.slug}\`} className="hover:text-primary-600 transition-colors">
823
1189
  {featuredPost.title}
824
1190
  </a>
825
1191
  </h3>
@@ -829,6 +1195,26 @@ export default function HomePage({ loaderData }${ts ? ": HomePageProps" : ""}) {
829
1195
  </div>
830
1196
  </div>
831
1197
  </section>
1198
+
1199
+ {/* CTA Footer */}
1200
+ <section className="py-24 px-4 text-center">
1201
+ <h2 className="text-3xl sm:text-4xl font-bold mb-4">Ready to Build?</h2>
1202
+ <p className="text-gray-600 dark:text-gray-400 text-lg mb-8">Get started with a single command.</p>
1203
+ <div className="code-window glow max-w-sm mx-auto mb-8">
1204
+ <div className="code-window-header">
1205
+ <span className="code-window-dot bg-red-500" />
1206
+ <span className="code-window-dot bg-yellow-500" />
1207
+ <span className="code-window-dot bg-green-500" />
1208
+ </div>
1209
+ <div className="px-5 py-3 font-mono text-sm text-primary-300">
1210
+ <span className="text-emerald-400">$</span> bunx create-ereo my-app
1211
+ </div>
1212
+ </div>
1213
+ <div className="flex flex-wrap gap-4 justify-center">
1214
+ <a href="https://ereo.dev/docs" className="btn btn-primary text-base px-6 py-3">Documentation</a>
1215
+ <a href="https://github.com/ereo-js/ereo" target="_blank" rel="noopener" className="btn btn-secondary text-base px-6 py-3">GitHub</a>
1216
+ </div>
1217
+ </section>
832
1218
  </div>
833
1219
  );
834
1220
  }
@@ -1438,8 +1824,18 @@ async function main() {
1438
1824
  printHelp();
1439
1825
  process.exit(1);
1440
1826
  }
1827
+ if (/[<>:"|?*]/.test(projectName) || projectName.startsWith(".")) {
1828
+ console.error(` \x1B[31m\u2717\x1B[0m Invalid project name. Avoid special characters and leading dots.
1829
+ `);
1830
+ process.exit(1);
1831
+ }
1441
1832
  const finalOptions = { ...defaultOptions, ...options };
1442
1833
  const projectDir = resolve(process.cwd(), projectName);
1834
+ if (!projectDir.startsWith(process.cwd())) {
1835
+ console.error(` \x1B[31m\u2717\x1B[0m Invalid project name: path traversal detected.
1836
+ `);
1837
+ process.exit(1);
1838
+ }
1443
1839
  console.log(` Creating \x1B[36m${projectName}\x1B[0m...
1444
1840
  `);
1445
1841
  console.log(` Template: ${finalOptions.template}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-ereo",
3
- "version": "0.1.21",
3
+ "version": "0.1.23",
4
4
  "license": "MIT",
5
5
  "author": "Ereo Team",
6
6
  "homepage": "https://ereo.dev",