create-flexireact 1.3.2 → 2.0.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/index.js DELETED
@@ -1,1653 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * create-flexireact
5
- * Create FlexiReact apps with one command
6
- *
7
- * Usage: npx create-flexireact@latest my-app
8
- */
9
-
10
- import fs from 'fs';
11
- import path from 'path';
12
- import { execSync, spawn } from 'child_process';
13
- import readline from 'readline';
14
- import { fileURLToPath } from 'url';
15
-
16
- const __filename = fileURLToPath(import.meta.url);
17
- const __dirname = path.dirname(__filename);
18
-
19
- // ============================================================================
20
- // Colors & Styling
21
- // ============================================================================
22
-
23
- const c = {
24
- reset: '\x1b[0m',
25
- bold: '\x1b[1m',
26
- dim: '\x1b[2m',
27
- green: '\x1b[32m',
28
- cyan: '\x1b[36m',
29
- yellow: '\x1b[33m',
30
- red: '\x1b[31m',
31
- magenta: '\x1b[35m',
32
- white: '\x1b[37m',
33
- bgGreen: '\x1b[42m',
34
- bgCyan: '\x1b[46m',
35
- };
36
-
37
- // ============================================================================
38
- // ASCII Art & Branding
39
- // ============================================================================
40
-
41
- const BANNER = `
42
- ${c.green} ╭─────────────────────────────────────────────────╮${c.reset}
43
- ${c.green} │${c.reset} ${c.green}│${c.reset}
44
- ${c.green} │${c.reset} ${c.green}⚡${c.reset} ${c.bold}${c.white}C R E A T E - F L E X I R E A C T${c.reset} ${c.green}│${c.reset}
45
- ${c.green} │${c.reset} ${c.green}│${c.reset}
46
- ${c.green} │${c.reset} ${c.dim}The Modern React Framework${c.reset} ${c.green}│${c.reset}
47
- ${c.green} │${c.reset} ${c.dim}TypeScript • Tailwind • SSR • Islands${c.reset} ${c.green}│${c.reset}
48
- ${c.green} │${c.reset} ${c.green}│${c.reset}
49
- ${c.green} ╰─────────────────────────────────────────────────╯${c.reset}
50
- `;
51
-
52
- const SUCCESS_BANNER = (projectName) => `
53
- ${c.green} ╭─────────────────────────────────────────────────╮${c.reset}
54
- ${c.green} │${c.reset} ${c.green}│${c.reset}
55
- ${c.green} │${c.reset} ${c.green}✓${c.reset} ${c.bold}Project created successfully!${c.reset} ${c.green}│${c.reset}
56
- ${c.green} │${c.reset} ${c.green}│${c.reset}
57
- ${c.green} ╰─────────────────────────────────────────────────╯${c.reset}
58
-
59
- ${c.dim}Next steps:${c.reset}
60
-
61
- ${c.cyan}cd${c.reset} ${projectName}
62
- ${c.cyan}npm${c.reset} install
63
- ${c.cyan}npm${c.reset} run dev
64
-
65
- ${c.dim}Then open${c.reset} ${c.cyan}http://localhost:3000${c.reset}
66
-
67
- ${c.dim}Documentation:${c.reset} ${c.cyan}https://github.com/flexireact/flexireact${c.reset}
68
- `;
69
-
70
- // ============================================================================
71
- // Templates
72
- // ============================================================================
73
-
74
- const TEMPLATES = {
75
- default: {
76
- name: 'Default',
77
- description: 'Premium template with modern UI, animations & dark mode',
78
- },
79
- 'flexi-ui': {
80
- name: 'Flexi UI',
81
- description: 'Showcase template with @flexireact/flexi-ui components',
82
- },
83
- minimal: {
84
- name: 'Minimal',
85
- description: 'Bare minimum FlexiReact setup',
86
- },
87
- };
88
-
89
- // ============================================================================
90
- // Utilities
91
- // ============================================================================
92
-
93
- function log(msg) {
94
- console.log(` ${msg}`);
95
- }
96
-
97
- function success(msg) {
98
- console.log(` ${c.green}✓${c.reset} ${msg}`);
99
- }
100
-
101
- function error(msg) {
102
- console.log(` ${c.red}✗${c.reset} ${c.red}${msg}${c.reset}`);
103
- }
104
-
105
- function info(msg) {
106
- console.log(` ${c.cyan}ℹ${c.reset} ${msg}`);
107
- }
108
-
109
- // Spinner
110
- class Spinner {
111
- constructor(message) {
112
- this.message = message;
113
- this.frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
114
- this.current = 0;
115
- this.interval = null;
116
- }
117
-
118
- start() {
119
- process.stdout.write(` ${this.frames[0]} ${this.message}`);
120
- this.interval = setInterval(() => {
121
- this.current = (this.current + 1) % this.frames.length;
122
- process.stdout.clearLine(0);
123
- process.stdout.cursorTo(0);
124
- process.stdout.write(` ${c.cyan}${this.frames[this.current]}${c.reset} ${this.message}`);
125
- }, 80);
126
- }
127
-
128
- stop(success = true) {
129
- clearInterval(this.interval);
130
- process.stdout.clearLine(0);
131
- process.stdout.cursorTo(0);
132
- const icon = success ? `${c.green}✓${c.reset}` : `${c.red}✗${c.reset}`;
133
- console.log(` ${icon} ${this.message}`);
134
- }
135
- }
136
-
137
- // Prompt
138
- async function prompt(question, defaultValue = '') {
139
- const rl = readline.createInterface({
140
- input: process.stdin,
141
- output: process.stdout,
142
- });
143
-
144
- return new Promise((resolve) => {
145
- const defaultStr = defaultValue ? ` ${c.dim}(${defaultValue})${c.reset}` : '';
146
- rl.question(` ${c.cyan}?${c.reset} ${question}${defaultStr}: `, (answer) => {
147
- rl.close();
148
- resolve(answer.trim() || defaultValue);
149
- });
150
- });
151
- }
152
-
153
- // Select
154
- async function select(question, options) {
155
- console.log(` ${c.cyan}?${c.reset} ${question}`);
156
- console.log('');
157
-
158
- options.forEach((opt, i) => {
159
- console.log(` ${c.cyan}${i + 1}.${c.reset} ${c.bold}${opt.name}${c.reset}`);
160
- console.log(` ${c.dim}${opt.description}${c.reset}`);
161
- });
162
-
163
- console.log('');
164
-
165
- const rl = readline.createInterface({
166
- input: process.stdin,
167
- output: process.stdout,
168
- });
169
-
170
- return new Promise((resolve) => {
171
- rl.question(` ${c.dim}Enter number (1-${options.length}):${c.reset} `, (answer) => {
172
- rl.close();
173
- const index = parseInt(answer) - 1;
174
- if (index >= 0 && index < options.length) {
175
- resolve(options[index]);
176
- } else {
177
- resolve(options[0]);
178
- }
179
- });
180
- });
181
- }
182
-
183
- // Check if directory is empty
184
- function isDirEmpty(dir) {
185
- if (!fs.existsSync(dir)) return true;
186
- return fs.readdirSync(dir).length === 0;
187
- }
188
-
189
- // ============================================================================
190
- // Template Files
191
- // ============================================================================
192
-
193
- const TEMPLATE_FILES = {
194
- 'package.json': (name, template) => JSON.stringify({
195
- name: name,
196
- version: "1.0.0",
197
- private: true,
198
- type: "module",
199
- scripts: {
200
- dev: "npm run css && flexireact dev",
201
- build: "npm run css && flexireact build",
202
- start: "flexireact start",
203
- css: "npx tailwindcss -i ./app/styles/globals.css -o ./public/styles.css --minify"
204
- },
205
- dependencies: {
206
- "react": "^18.2.0",
207
- "react-dom": "^18.2.0",
208
- "@flexireact/core": "^1.0.0",
209
- "framer-motion": "^11.0.0",
210
- "lucide-react": "^0.400.0",
211
- "clsx": "^2.1.0",
212
- "tailwind-merge": "^2.2.0"
213
- },
214
- devDependencies: {
215
- "@types/react": "^18.2.0",
216
- "@types/react-dom": "^18.2.0",
217
- "typescript": "^5.3.0",
218
- "tailwindcss": "^3.4.0",
219
- "postcss": "^8.4.32",
220
- "autoprefixer": "^10.4.16"
221
- }
222
- }, null, 2),
223
-
224
- 'tsconfig.json': () => JSON.stringify({
225
- compilerOptions: {
226
- target: "ES2020",
227
- lib: ["DOM", "DOM.Iterable", "ES2020"],
228
- module: "ESNext",
229
- moduleResolution: "bundler",
230
- jsx: "react-jsx",
231
- strict: true,
232
- skipLibCheck: true,
233
- esModuleInterop: true,
234
- allowSyntheticDefaultImports: true,
235
- resolveJsonModule: true,
236
- isolatedModules: true,
237
- noEmit: true,
238
- baseUrl: ".",
239
- paths: {
240
- "@/*": ["./*"]
241
- }
242
- },
243
- include: ["**/*.ts", "**/*.tsx"],
244
- exclude: ["node_modules", ".flexi"]
245
- }, null, 2),
246
-
247
- 'tailwind.config.js': () => `/** @type {import('tailwindcss').Config} */
248
- module.exports = {
249
- darkMode: 'class',
250
- content: [
251
- './app/**/*.{js,ts,jsx,tsx}',
252
- './pages/**/*.{js,ts,jsx,tsx}',
253
- './components/**/*.{js,ts,jsx,tsx}',
254
- './layouts/**/*.{js,ts,jsx,tsx}',
255
- ],
256
- theme: {
257
- extend: {
258
- fontFamily: {
259
- sans: ['Inter', 'system-ui', 'sans-serif'],
260
- },
261
- colors: {
262
- border: 'hsl(240 3.7% 15.9%)',
263
- input: 'hsl(240 3.7% 15.9%)',
264
- ring: 'hsl(142.1 76.2% 36.3%)',
265
- background: 'hsl(240 10% 3.9%)',
266
- foreground: 'hsl(0 0% 98%)',
267
- primary: {
268
- DEFAULT: 'hsl(142.1 76.2% 36.3%)',
269
- foreground: 'hsl(144.9 80.4% 10%)',
270
- },
271
- secondary: {
272
- DEFAULT: 'hsl(240 3.7% 15.9%)',
273
- foreground: 'hsl(0 0% 98%)',
274
- },
275
- muted: {
276
- DEFAULT: 'hsl(240 3.7% 15.9%)',
277
- foreground: 'hsl(240 5% 64.9%)',
278
- },
279
- accent: {
280
- DEFAULT: 'hsl(240 3.7% 15.9%)',
281
- foreground: 'hsl(0 0% 98%)',
282
- },
283
- card: {
284
- DEFAULT: 'hsl(240 10% 3.9%)',
285
- foreground: 'hsl(0 0% 98%)',
286
- },
287
- },
288
- borderRadius: {
289
- lg: '0.75rem',
290
- md: '0.5rem',
291
- sm: '0.25rem',
292
- },
293
- animation: {
294
- 'fade-in': 'fadeIn 0.5s ease-out',
295
- 'fade-up': 'fadeUp 0.5s ease-out',
296
- 'scale-in': 'scaleIn 0.3s ease-out',
297
- 'glow': 'glow 2s ease-in-out infinite alternate',
298
- },
299
- keyframes: {
300
- fadeIn: {
301
- '0%': { opacity: '0' },
302
- '100%': { opacity: '1' },
303
- },
304
- fadeUp: {
305
- '0%': { opacity: '0', transform: 'translateY(20px)' },
306
- '100%': { opacity: '1', transform: 'translateY(0)' },
307
- },
308
- scaleIn: {
309
- '0%': { opacity: '0', transform: 'scale(0.95)' },
310
- '100%': { opacity: '1', transform: 'scale(1)' },
311
- },
312
- glow: {
313
- '0%': { boxShadow: '0 0 20px rgba(16, 185, 129, 0.2)' },
314
- '100%': { boxShadow: '0 0 40px rgba(16, 185, 129, 0.4)' },
315
- },
316
- },
317
- },
318
- },
319
- plugins: [],
320
- };
321
- `,
322
-
323
- 'postcss.config.js': () => `module.exports = {
324
- plugins: {
325
- tailwindcss: {},
326
- autoprefixer: {},
327
- },
328
- };
329
- `,
330
-
331
- 'flexireact.config.js': () => `/** @type {import('@flexireact/core').Config} */
332
- export default {
333
- styles: [
334
- '/styles.css',
335
- 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap'
336
- ],
337
- favicon: '/favicon.svg',
338
- server: {
339
- port: 3000
340
- },
341
- islands: {
342
- enabled: true
343
- }
344
- };
345
- `,
346
-
347
- // ============================================================================
348
- // Components
349
- // ============================================================================
350
-
351
- 'components/ui/button.tsx': () => `import React from 'react';
352
- import { cn } from '../../lib/utils';
353
-
354
- interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
355
- variant?: 'default' | 'outline' | 'ghost';
356
- size?: 'default' | 'sm' | 'lg';
357
- children: React.ReactNode;
358
- }
359
-
360
- const baseStyles = 'inline-flex items-center justify-center rounded-lg ' +
361
- 'font-medium transition-all duration-200 focus-visible:outline-none ' +
362
- 'focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50';
363
-
364
- const variants = {
365
- default: 'bg-primary text-black hover:bg-primary/90',
366
- outline: 'border border-border bg-transparent hover:bg-secondary',
367
- ghost: 'hover:bg-secondary hover:text-foreground',
368
- };
369
-
370
- const sizes = {
371
- default: 'h-10 px-4 py-2 text-sm',
372
- sm: 'h-9 px-3 text-sm',
373
- lg: 'h-12 px-8 text-base',
374
- };
375
-
376
- export function Button({
377
- className,
378
- variant = 'default',
379
- size = 'default',
380
- children,
381
- ...props
382
- }: ButtonProps) {
383
- return (
384
- <button
385
- className={cn(baseStyles, variants[variant], sizes[size], className)}
386
- {...props}
387
- >
388
- {children}
389
- </button>
390
- );
391
- }
392
- `,
393
-
394
- 'components/ui/card.tsx': () => `import React from 'react';
395
- import { cn } from '../../lib/utils';
396
-
397
- interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
398
- children: React.ReactNode;
399
- }
400
-
401
- const cardStyles = 'rounded-xl border border-border bg-card ' +
402
- 'text-card-foreground shadow-sm transition-all duration-300 ' +
403
- 'hover:border-primary/50';
404
-
405
- export function Card({ className, children, ...props }: CardProps) {
406
- return (
407
- <div className={cn(cardStyles, className)} {...props}>
408
- {children}
409
- </div>
410
- );
411
- }
412
-
413
- export function CardHeader({ className, children, ...props }: CardProps) {
414
- return (
415
- <div className={cn('flex flex-col space-y-1.5 p-6', className)} {...props}>
416
- {children}
417
- </div>
418
- );
419
- }
420
-
421
- export function CardTitle({ className, children, ...props }: CardProps) {
422
- return (
423
- <h3 className={cn('text-lg font-semibold', className)} {...props}>
424
- {children}
425
- </h3>
426
- );
427
- }
428
-
429
- export function CardDescription({ className, children, ...props }: CardProps) {
430
- return (
431
- <p className={cn('text-sm text-muted-foreground', className)} {...props}>
432
- {children}
433
- </p>
434
- );
435
- }
436
-
437
- export function CardContent({ className, children, ...props }: CardProps) {
438
- return (
439
- <div className={cn('p-6 pt-0', className)} {...props}>
440
- {children}
441
- </div>
442
- );
443
- }
444
- `,
445
-
446
- 'components/ui/badge.tsx': () => `import React from 'react';
447
- import { cn } from '../../lib/utils';
448
-
449
- interface BadgeProps extends React.HTMLAttributes<HTMLDivElement> {
450
- variant?: 'default' | 'secondary' | 'outline';
451
- children: React.ReactNode;
452
- }
453
-
454
- export function Badge({ className, variant = 'default', children, ...props }: BadgeProps) {
455
- return (
456
- <div
457
- className={cn(
458
- 'inline-flex items-center rounded-full px-3 py-1 text-xs font-medium transition-colors',
459
- {
460
- 'bg-primary/10 text-primary border border-primary/20': variant === 'default',
461
- 'bg-secondary text-secondary-foreground': variant === 'secondary',
462
- 'border border-border text-foreground': variant === 'outline',
463
- },
464
- className
465
- )}
466
- {...props}
467
- >
468
- {children}
469
- </div>
470
- );
471
- }
472
- `,
473
-
474
- 'components/Navbar.tsx': () => `import React from 'react';
475
-
476
- export function Navbar() {
477
- return (
478
- <header className="navbar">
479
- <nav className="navbar-inner">
480
- <a href="/" className="logo">
481
- <div className="logo-icon">⚡</div>
482
- <span className="logo-text">FlexiReact</span>
483
- </a>
484
- <div className="nav-links">
485
- <a href="https://github.com/flexireact/flexireact">Docs</a>
486
- <a href="https://github.com/flexireact/flexireact">GitHub</a>
487
- </div>
488
- </nav>
489
- </header>
490
- );
491
- }
492
- `,
493
-
494
- 'components/Hero.tsx': () => `import React from 'react';
495
-
496
- export function Hero() {
497
- return (
498
- <section className="hero">
499
- <div className="hero-glow" />
500
- <div className="hero-content">
501
- <span className="hero-badge">⚡ The Modern React Framework</span>
502
- <h1 className="hero-title">
503
- Build <span className="gradient-text">blazing fast</span> web apps
504
- </h1>
505
- <p className="hero-subtitle">
506
- A modern React framework with TypeScript, Tailwind CSS,
507
- SSR, SSG, Islands architecture, and file-based routing.
508
- </p>
509
- <div className="hero-buttons">
510
- <a href="https://github.com/flexireact/flexireact" className="btn-primary">
511
- Get Started →
512
- </a>
513
- <a href="https://github.com/flexireact/flexireact" className="btn-outline">
514
- GitHub
515
- </a>
516
- </div>
517
- <div className="terminal">
518
- <div className="terminal-header">
519
- <span className="dot red"></span>
520
- <span className="dot yellow"></span>
521
- <span className="dot green"></span>
522
- </div>
523
- <pre className="terminal-body">
524
- <span className="dim">$</span> <span className="green">npx</span> create-flexireact my-app
525
- <span className="dim">$</span> <span className="green">cd</span> my-app
526
- <span className="dim">$</span> <span className="green">npm</span> run dev
527
-
528
- <span className="green">✓</span> Ready in 38ms
529
- </pre>
530
- </div>
531
- </div>
532
- </section>
533
- );
534
- }
535
- `,
536
-
537
- 'components/Features.tsx': () => `import React from 'react';
538
-
539
- const features = [
540
- { icon: '⚡', title: 'Lightning Fast', desc: 'Powered by esbuild for instant builds.' },
541
- { icon: '📁', title: 'File Routing', desc: 'Create a file, get a route automatically.' },
542
- { icon: '🏝️', title: 'Islands', desc: 'Partial hydration for minimal JavaScript.' },
543
- { icon: '🚀', title: 'SSR & SSG', desc: 'Server rendering out of the box.' },
544
- ];
545
-
546
- export function Features() {
547
- return (
548
- <section className="features">
549
- <h2 className="features-title">Everything you need</h2>
550
- <p className="features-subtitle">
551
- A complete toolkit for building modern web apps.
552
- </p>
553
- <div className="features-grid">
554
- {features.map((f, i) => (
555
- <div key={i} className="feature-card">
556
- <span className="feature-icon">{f.icon}</span>
557
- <h3 className="feature-title">{f.title}</h3>
558
- <p className="feature-desc">{f.desc}</p>
559
- </div>
560
- ))}
561
- </div>
562
- </section>
563
- );
564
- }
565
- `,
566
-
567
- 'components/Footer.tsx': () => `import React from 'react';
568
-
569
- export function Footer() {
570
- return (
571
- <footer className="footer">
572
- <div className="footer-inner">
573
- <span>Built with ❤️ using FlexiReact</span>
574
- <div className="footer-links">
575
- <a href="https://github.com/flexireact/flexireact">GitHub</a>
576
- <a href="https://github.com/flexireact/flexireact">Docs</a>
577
- </div>
578
- </div>
579
- </footer>
580
- );
581
- }
582
- `,
583
-
584
- 'components/index.ts': () => `export { Navbar } from './Navbar';
585
- export { Hero } from './Hero';
586
- export { Features } from './Features';
587
- export { Footer } from './Footer';
588
- export { Button } from './ui/button';
589
- export { Card, CardHeader, CardTitle, CardDescription, CardContent } from './ui/card';
590
- export { Badge } from './ui/badge';
591
- `,
592
-
593
- // ============================================================================
594
- // Lib
595
- // ============================================================================
596
-
597
- 'lib/utils.ts': () => `import { clsx, type ClassValue } from 'clsx';
598
- import { twMerge } from 'tailwind-merge';
599
-
600
- export function cn(...inputs: ClassValue[]) {
601
- return twMerge(clsx(inputs));
602
- }
603
- `,
604
-
605
- // ============================================================================
606
- // Pages & Layouts
607
- // ============================================================================
608
-
609
- 'layouts/root.tsx': () => `import React from 'react';
610
- import { Navbar } from '../components/Navbar';
611
- import { Footer } from '../components/Footer';
612
-
613
- interface LayoutProps {
614
- children: React.ReactNode;
615
- }
616
-
617
- export default function RootLayout({ children }: LayoutProps) {
618
- return (
619
- <div className="app">
620
- <Navbar />
621
- <main>{children}</main>
622
- <Footer />
623
- </div>
624
- );
625
- }
626
- `,
627
-
628
- 'pages/index.tsx': () => `import React from 'react';
629
- import { Hero } from '../components/Hero';
630
- import { Features } from '../components/Features';
631
-
632
- export default function HomePage() {
633
- return (
634
- <>
635
- <Hero />
636
- <Features />
637
- </>
638
- );
639
- }
640
- `,
641
-
642
- // ============================================================================
643
- // Styles
644
- // ============================================================================
645
-
646
- 'app/styles/globals.css': () => `/* FlexiReact Default Template Styles */
647
-
648
- :root {
649
- --bg: #0a0a0a;
650
- --fg: #fafafa;
651
- --primary: #10b981;
652
- --primary-light: #34d399;
653
- --accent: #06b6d4;
654
- --muted: #71717a;
655
- --border: #27272a;
656
- --card: #18181b;
657
- }
658
-
659
- *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
660
-
661
- html { scroll-behavior: smooth; }
662
-
663
- body {
664
- font-family: 'Inter', system-ui, -apple-system, sans-serif;
665
- background: var(--bg);
666
- color: var(--fg);
667
- min-height: 100vh;
668
- -webkit-font-smoothing: antialiased;
669
- line-height: 1.6;
670
- }
671
-
672
- a { color: inherit; text-decoration: none; transition: color 0.2s; }
673
- a:hover { color: var(--primary); }
674
-
675
- /* Layout */
676
- .app { min-height: 100vh; }
677
-
678
- /* Navbar */
679
- .navbar {
680
- position: sticky;
681
- top: 0;
682
- z-index: 50;
683
- border-bottom: 1px solid var(--border);
684
- background: rgba(10,10,10,0.85);
685
- backdrop-filter: blur(12px);
686
- }
687
- .navbar-inner {
688
- max-width: 1200px;
689
- margin: 0 auto;
690
- padding: 1rem 1.5rem;
691
- display: flex;
692
- justify-content: space-between;
693
- align-items: center;
694
- }
695
- .logo { display: flex; align-items: center; gap: 0.5rem; }
696
- .logo-icon {
697
- width: 32px;
698
- height: 32px;
699
- background: linear-gradient(135deg, var(--primary), var(--accent));
700
- border-radius: 8px;
701
- display: flex;
702
- align-items: center;
703
- justify-content: center;
704
- font-size: 1rem;
705
- }
706
- .logo-text { font-weight: 700; font-size: 1.125rem; }
707
- .nav-links { display: flex; gap: 2rem; }
708
- .nav-links a { color: var(--muted); font-size: 0.9375rem; font-weight: 500; }
709
- .nav-links a:hover { color: var(--fg); }
710
-
711
- /* Hero */
712
- .hero {
713
- position: relative;
714
- min-height: 90vh;
715
- display: flex;
716
- align-items: center;
717
- justify-content: center;
718
- overflow: hidden;
719
- padding: 4rem 1.5rem;
720
- }
721
- .hero-glow {
722
- position: absolute;
723
- top: 50%;
724
- left: 50%;
725
- transform: translate(-50%, -50%);
726
- width: 700px;
727
- height: 700px;
728
- background: radial-gradient(circle, rgba(16,185,129,0.12), transparent 70%);
729
- pointer-events: none;
730
- }
731
- .hero-content {
732
- position: relative;
733
- z-index: 1;
734
- text-align: center;
735
- padding: 2rem;
736
- max-width: 800px;
737
- }
738
- .hero-badge {
739
- display: inline-flex;
740
- align-items: center;
741
- gap: 0.5rem;
742
- padding: 0.5rem 1.25rem;
743
- background: rgba(16,185,129,0.1);
744
- border: 1px solid rgba(16,185,129,0.2);
745
- border-radius: 9999px;
746
- font-size: 0.875rem;
747
- font-weight: 500;
748
- color: var(--primary);
749
- margin-bottom: 2rem;
750
- }
751
- .hero-title {
752
- font-size: clamp(2.5rem, 7vw, 4.5rem);
753
- font-weight: 800;
754
- line-height: 1.1;
755
- letter-spacing: -0.02em;
756
- margin-bottom: 1.5rem;
757
- }
758
- .gradient-text {
759
- background: linear-gradient(135deg, var(--primary), var(--accent));
760
- -webkit-background-clip: text;
761
- -webkit-text-fill-color: transparent;
762
- background-clip: text;
763
- }
764
- .hero-subtitle {
765
- font-size: 1.125rem;
766
- color: var(--muted);
767
- line-height: 1.7;
768
- max-width: 600px;
769
- margin: 0 auto 2.5rem;
770
- }
771
- .hero-buttons {
772
- display: flex;
773
- gap: 1rem;
774
- justify-content: center;
775
- flex-wrap: wrap;
776
- margin-bottom: 3rem;
777
- }
778
- .btn-primary {
779
- display: inline-flex;
780
- align-items: center;
781
- gap: 0.5rem;
782
- padding: 0.875rem 2rem;
783
- background: linear-gradient(135deg, var(--primary), var(--accent));
784
- color: #000;
785
- font-weight: 600;
786
- border-radius: 10px;
787
- transition: all 0.2s;
788
- }
789
- .btn-primary:hover {
790
- transform: translateY(-2px);
791
- box-shadow: 0 10px 30px rgba(16,185,129,0.3);
792
- color: #000;
793
- }
794
- .btn-outline {
795
- display: inline-flex;
796
- align-items: center;
797
- gap: 0.5rem;
798
- padding: 0.875rem 2rem;
799
- border: 1px solid var(--border);
800
- border-radius: 10px;
801
- font-weight: 600;
802
- transition: all 0.2s;
803
- }
804
- .btn-outline:hover {
805
- border-color: var(--primary);
806
- background: rgba(16,185,129,0.1);
807
- color: var(--fg);
808
- }
809
-
810
- /* Terminal */
811
- .terminal {
812
- background: var(--card);
813
- border: 1px solid var(--border);
814
- border-radius: 12px;
815
- max-width: 500px;
816
- margin: 0 auto;
817
- overflow: hidden;
818
- text-align: left;
819
- }
820
- .terminal-header {
821
- padding: 0.875rem 1rem;
822
- border-bottom: 1px solid var(--border);
823
- display: flex;
824
- gap: 0.5rem;
825
- background: rgba(255,255,255,0.02);
826
- }
827
- .dot { width: 12px; height: 12px; border-radius: 50%; }
828
- .dot.red { background: #ef4444; }
829
- .dot.yellow { background: #eab308; }
830
- .dot.green { background: #22c55e; }
831
- .terminal-body {
832
- padding: 1.25rem;
833
- font-family: 'JetBrains Mono', 'Fira Code', monospace;
834
- font-size: 0.875rem;
835
- line-height: 1.8;
836
- }
837
- .dim { color: var(--muted); }
838
- .green { color: var(--primary); }
839
-
840
- /* Features */
841
- .features {
842
- padding: 6rem 1.5rem;
843
- max-width: 1200px;
844
- margin: 0 auto;
845
- }
846
- .features-title {
847
- font-size: 2rem;
848
- font-weight: 700;
849
- text-align: center;
850
- margin-bottom: 0.75rem;
851
- }
852
- .features-subtitle {
853
- text-align: center;
854
- color: var(--muted);
855
- margin-bottom: 3rem;
856
- font-size: 1.0625rem;
857
- }
858
- .features-grid {
859
- display: grid;
860
- grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
861
- gap: 1.5rem;
862
- }
863
- .feature-card {
864
- background: var(--card);
865
- border: 1px solid var(--border);
866
- border-radius: 16px;
867
- padding: 1.75rem;
868
- transition: all 0.3s;
869
- }
870
- .feature-card:hover {
871
- border-color: var(--primary);
872
- transform: translateY(-4px);
873
- }
874
- .feature-icon {
875
- font-size: 2rem;
876
- margin-bottom: 1rem;
877
- display: block;
878
- width: 48px;
879
- height: 48px;
880
- display: flex;
881
- align-items: center;
882
- justify-content: center;
883
- background: rgba(16,185,129,0.1);
884
- border-radius: 12px;
885
- }
886
- .feature-title { font-weight: 600; font-size: 1.0625rem; margin-bottom: 0.5rem; }
887
- .feature-desc { color: var(--muted); font-size: 0.9375rem; line-height: 1.6; }
888
-
889
- /* Footer */
890
- .footer {
891
- border-top: 1px solid var(--border);
892
- padding: 2rem 1.5rem;
893
- margin-top: 2rem;
894
- }
895
- .footer-inner {
896
- max-width: 1200px;
897
- margin: 0 auto;
898
- display: flex;
899
- justify-content: space-between;
900
- align-items: center;
901
- flex-wrap: wrap;
902
- gap: 1rem;
903
- font-size: 0.875rem;
904
- color: var(--muted);
905
- }
906
- .footer-links { display: flex; gap: 1.5rem; }
907
- .footer-links a:hover { color: var(--primary); }
908
-
909
- /* Responsive */
910
- @media (max-width: 768px) {
911
- .nav-links { display: none; }
912
- .hero { min-height: auto; padding: 6rem 1.5rem 4rem; }
913
- .hero-buttons { flex-direction: column; align-items: center; }
914
- .btn-primary, .btn-outline { width: 100%; justify-content: center; }
915
- .footer-inner { flex-direction: column; text-align: center; }
916
- }
917
- `,
918
-
919
- // ============================================================================
920
- // Public Assets
921
- // ============================================================================
922
-
923
- 'public/.gitkeep': () => '',
924
-
925
- 'public/favicon.svg': () => `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
926
- <defs>
927
- <linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
928
- <stop offset="0%" style="stop-color:#10b981"/>
929
- <stop offset="100%" style="stop-color:#06b6d4"/>
930
- </linearGradient>
931
- </defs>
932
- <rect width="100" height="100" rx="20" fill="#0a0a0a"/>
933
- <path d="M50 20L30 55h15v25l20-35H50V20z" fill="url(#grad)"/>
934
- </svg>`,
935
- };
936
-
937
- // Minimal template files
938
- const MINIMAL_FILES = {
939
- 'package.json': (name) => JSON.stringify({
940
- name: name,
941
- version: "1.0.0",
942
- private: true,
943
- type: "module",
944
- scripts: {
945
- dev: "flexireact dev",
946
- build: "flexireact build",
947
- start: "flexireact start"
948
- },
949
- dependencies: {
950
- "react": "^18.2.0",
951
- "react-dom": "^18.2.0",
952
- "@flexireact/core": "^1.0.0"
953
- },
954
- devDependencies: {
955
- "@types/react": "^18.2.0",
956
- "@types/react-dom": "^18.2.0",
957
- "typescript": "^5.3.0"
958
- }
959
- }, null, 2),
960
-
961
- 'tsconfig.json': TEMPLATE_FILES['tsconfig.json'],
962
-
963
- 'flexireact.config.js': () => `export default {
964
- server: { port: 3000 }
965
- };
966
- `,
967
-
968
- 'pages/index.tsx': () => `import React from 'react';
969
-
970
- export default function HomePage() {
971
- return (
972
- <div style={{ padding: '2rem', fontFamily: 'system-ui' }}>
973
- <h1>Welcome to FlexiReact</h1>
974
- <p>Edit pages/index.tsx to get started.</p>
975
- </div>
976
- );
977
- }
978
- `,
979
-
980
- 'public/.gitkeep': () => '',
981
- };
982
-
983
- // ============================================================================
984
- // Flexi UI Template Files
985
- // ============================================================================
986
-
987
- const FLEXI_UI_FILES = {
988
- 'package.json': (name) => JSON.stringify({
989
- name: name,
990
- version: "1.0.0",
991
- private: true,
992
- type: "module",
993
- scripts: {
994
- dev: "npm run css && flexireact dev",
995
- build: "npm run css && flexireact build",
996
- start: "flexireact start",
997
- css: "npx tailwindcss -i ./app/styles/globals.css -o ./public/styles.css --minify"
998
- },
999
- dependencies: {
1000
- "react": "^18.2.0",
1001
- "react-dom": "^18.2.0",
1002
- "@flexireact/core": "^1.0.0",
1003
- "@flexireact/flexi-ui": "^1.0.0",
1004
- "lucide-react": "^0.400.0"
1005
- },
1006
- devDependencies: {
1007
- "@types/react": "^18.2.0",
1008
- "@types/react-dom": "^18.2.0",
1009
- "typescript": "^5.3.0",
1010
- "tailwindcss": "^3.4.0",
1011
- "postcss": "^8.4.32",
1012
- "autoprefixer": "^10.4.16"
1013
- }
1014
- }, null, 2),
1015
-
1016
- 'tsconfig.json': TEMPLATE_FILES['tsconfig.json'],
1017
-
1018
- 'tailwind.config.js': () => `/** @type {import('tailwindcss').Config} */
1019
- const { flexiUIPlugin } = require('@flexireact/flexi-ui/tailwind');
1020
-
1021
- module.exports = {
1022
- darkMode: 'class',
1023
- content: [
1024
- './app/**/*.{js,ts,jsx,tsx}',
1025
- './pages/**/*.{js,ts,jsx,tsx}',
1026
- './components/**/*.{js,ts,jsx,tsx}',
1027
- './layouts/**/*.{js,ts,jsx,tsx}',
1028
- './node_modules/@flexireact/flexi-ui/dist/**/*.js',
1029
- ],
1030
- theme: {
1031
- extend: {
1032
- fontFamily: {
1033
- sans: ['Inter', 'system-ui', 'sans-serif'],
1034
- },
1035
- animation: {
1036
- 'fade-in': 'fadeIn 0.6s ease-out forwards',
1037
- 'fade-up': 'fadeUp 0.6s ease-out forwards',
1038
- 'scale-in': 'scaleIn 0.4s ease-out forwards',
1039
- 'glow-pulse': 'glowPulse 3s ease-in-out infinite',
1040
- },
1041
- keyframes: {
1042
- fadeIn: {
1043
- '0%': { opacity: '0' },
1044
- '100%': { opacity: '1' },
1045
- },
1046
- fadeUp: {
1047
- '0%': { opacity: '0', transform: 'translateY(30px)' },
1048
- '100%': { opacity: '1', transform: 'translateY(0)' },
1049
- },
1050
- scaleIn: {
1051
- '0%': { opacity: '0', transform: 'scale(0.9)' },
1052
- '100%': { opacity: '1', transform: 'scale(1)' },
1053
- },
1054
- glowPulse: {
1055
- '0%, 100%': { boxShadow: '0 0 20px rgba(0, 255, 156, 0.15)' },
1056
- '50%': { boxShadow: '0 0 40px rgba(0, 255, 156, 0.3)' },
1057
- },
1058
- },
1059
- },
1060
- },
1061
- plugins: [flexiUIPlugin],
1062
- };
1063
- `,
1064
-
1065
- 'postcss.config.js': () => `module.exports = {
1066
- plugins: {
1067
- tailwindcss: {},
1068
- autoprefixer: {},
1069
- },
1070
- };
1071
- `,
1072
-
1073
- 'flexireact.config.js': () => `/** @type {import('@flexireact/core').Config} */
1074
- export default {
1075
- styles: [
1076
- '/styles.css',
1077
- 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap'
1078
- ],
1079
- favicon: '/favicon.svg',
1080
- server: {
1081
- port: 3000
1082
- },
1083
- islands: {
1084
- enabled: true
1085
- }
1086
- };
1087
- `,
1088
-
1089
- // ============================================================================
1090
- // Components - Simple Landing Page
1091
- // ============================================================================
1092
-
1093
- 'components/Hero.tsx': () => `import React from 'react';
1094
- import { Button, Badge } from '@flexireact/flexi-ui';
1095
-
1096
- export function Hero() {
1097
- return (
1098
- <section className="hero">
1099
- <div className="hero-glow" />
1100
- <div className="hero-content">
1101
- <Badge variant="success" className="mb-6">
1102
- ⚡ Flexi UI Components
1103
- </Badge>
1104
- <h1 className="hero-title">
1105
- Build <span className="gradient-text">beautiful</span> apps
1106
- </h1>
1107
- <p className="hero-subtitle">
1108
- A stunning component library with neon emerald accents,
1109
- dark-first design, and seamless React integration.
1110
- </p>
1111
- <div className="hero-buttons">
1112
- <Button size="lg">Get Started</Button>
1113
- <Button variant="outline" size="lg">GitHub</Button>
1114
- </div>
1115
- </div>
1116
- </section>
1117
- );
1118
- }
1119
- `,
1120
-
1121
- 'components/Features.tsx': () => `import React from 'react';
1122
- import { Card } from '@flexireact/flexi-ui';
1123
-
1124
- const features = [
1125
- { icon: '⚡', title: 'Fast', desc: 'Powered by esbuild.' },
1126
- { icon: '📁', title: 'File Routing', desc: 'Automatic routes.' },
1127
- { icon: '🏝️', title: 'Islands', desc: 'Partial hydration.' },
1128
- { icon: '🎨', title: 'Beautiful', desc: 'Dark mode first.' },
1129
- ];
1130
-
1131
- export function Features() {
1132
- return (
1133
- <section className="features">
1134
- <h2 className="features-title">Features</h2>
1135
- <div className="features-grid">
1136
- {features.map((f, i) => (
1137
- <Card key={i} className="feature-card">
1138
- <span className="feature-icon">{f.icon}</span>
1139
- <h3>{f.title}</h3>
1140
- <p>{f.desc}</p>
1141
- </Card>
1142
- ))}
1143
- </div>
1144
- </section>
1145
- );
1146
- }
1147
- `,
1148
-
1149
- 'components/Footer.tsx': () => `import React from 'react';
1150
-
1151
- export function Footer() {
1152
- return (
1153
- <footer className="footer">
1154
- <span>Built with Flexi UI</span>
1155
- <a href="https://github.com/flexireact/flexi-ui">GitHub</a>
1156
- </footer>
1157
- );
1158
- }
1159
- `,
1160
-
1161
- 'components/Navbar.tsx': () => `import React from 'react';
1162
- import { Button, Badge } from '@flexireact/flexi-ui';
1163
-
1164
- export function Navbar() {
1165
- return (
1166
- <header className="navbar">
1167
- <nav className="navbar-inner">
1168
- <a href="/" className="logo">
1169
- <span className="logo-icon">F</span>
1170
- <span className="logo-text">Flexi UI</span>
1171
- <Badge variant="outline" className="ml-2">v1.0</Badge>
1172
- </a>
1173
- <div className="nav-links">
1174
- <Button variant="ghost" size="sm">Docs</Button>
1175
- <Button variant="outline" size="sm">GitHub</Button>
1176
- </div>
1177
- </nav>
1178
- </header>
1179
- );
1180
- }
1181
- `,
1182
-
1183
- 'components/index.ts': () => `export { Hero } from './Hero';
1184
- export { Features } from './Features';
1185
- export { Footer } from './Footer';
1186
- export { Navbar } from './Navbar';
1187
- `,
1188
-
1189
- // ============================================================================
1190
- // Pages & Layouts
1191
- // ============================================================================
1192
-
1193
- 'layouts/root.tsx': () => `import React from 'react';
1194
- import { ThemeProvider } from '@flexireact/flexi-ui';
1195
- import { Navbar } from '../components/Navbar';
1196
- import { Footer } from '../components/Footer';
1197
-
1198
- interface LayoutProps {
1199
- children: React.ReactNode;
1200
- }
1201
-
1202
- export default function RootLayout({ children }: LayoutProps) {
1203
- return (
1204
- <ThemeProvider defaultTheme="dark">
1205
- <div className="min-h-screen bg-[#0a0a0a] text-white antialiased">
1206
- <Navbar />
1207
- <main className="pt-16">{children}</main>
1208
- <Footer />
1209
- </div>
1210
- </ThemeProvider>
1211
- );
1212
- }
1213
- `,
1214
-
1215
- 'pages/index.tsx': () => `import React from 'react';
1216
- import { Hero } from '../components/Hero';
1217
- import { Features } from '../components/Features';
1218
-
1219
- export default function HomePage() {
1220
- return (
1221
- <>
1222
- <Hero />
1223
- <Features />
1224
- </>
1225
- );
1226
- }
1227
- `,
1228
-
1229
- // ============================================================================
1230
- // Styles
1231
- // ============================================================================
1232
-
1233
- 'app/styles/globals.css': () => `/* Flexi UI Template Styles */
1234
-
1235
- :root {
1236
- --bg: #0a0a0a;
1237
- --fg: #fafafa;
1238
- --primary: #00FF9C;
1239
- --primary-dark: #10b981;
1240
- --muted: #94a3b8;
1241
- --border: #1e293b;
1242
- --card: #0f0f0f;
1243
- }
1244
-
1245
- *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
1246
-
1247
- html { scroll-behavior: smooth; }
1248
-
1249
- body {
1250
- font-family: 'Inter', system-ui, -apple-system, sans-serif;
1251
- background: var(--bg);
1252
- color: var(--fg);
1253
- min-height: 100vh;
1254
- -webkit-font-smoothing: antialiased;
1255
- line-height: 1.6;
1256
- }
1257
-
1258
- a { color: inherit; text-decoration: none; transition: color 0.2s; }
1259
- a:hover { color: var(--primary); }
1260
-
1261
- /* Buttons */
1262
- button {
1263
- font-family: inherit;
1264
- cursor: pointer;
1265
- }
1266
-
1267
- .btn, [class*="btn-"] {
1268
- display: inline-flex;
1269
- align-items: center;
1270
- justify-content: center;
1271
- gap: 0.5rem;
1272
- padding: 0.75rem 1.5rem;
1273
- font-size: 0.9375rem;
1274
- font-weight: 600;
1275
- border-radius: 8px;
1276
- transition: all 0.2s;
1277
- border: none;
1278
- cursor: pointer;
1279
- }
1280
-
1281
- .btn-primary, button[class*="primary"] {
1282
- background: linear-gradient(135deg, var(--primary), var(--primary-dark));
1283
- color: #000;
1284
- }
1285
-
1286
- .btn-primary:hover, button[class*="primary"]:hover {
1287
- transform: translateY(-2px);
1288
- box-shadow: 0 8px 25px rgba(0, 255, 156, 0.3);
1289
- }
1290
-
1291
- .btn-outline, button[class*="outline"] {
1292
- background: transparent;
1293
- border: 1px solid var(--border);
1294
- color: var(--fg);
1295
- }
1296
-
1297
- .btn-outline:hover, button[class*="outline"]:hover {
1298
- border-color: var(--primary);
1299
- background: rgba(0, 255, 156, 0.1);
1300
- }
1301
-
1302
- .btn-ghost, button[class*="ghost"] {
1303
- background: transparent;
1304
- border: 1px solid var(--border);
1305
- color: var(--fg);
1306
- }
1307
-
1308
- .btn-ghost:hover, button[class*="ghost"]:hover {
1309
- background: rgba(255, 255, 255, 0.05);
1310
- border-color: var(--muted);
1311
- }
1312
-
1313
- .btn-sm, button[class*="sm"] {
1314
- padding: 0.5rem 1rem;
1315
- font-size: 0.875rem;
1316
- }
1317
-
1318
- /* Badge */
1319
- [class*="badge"], .badge {
1320
- display: inline-flex;
1321
- align-items: center;
1322
- padding: 0.25rem 0.75rem;
1323
- font-size: 0.75rem;
1324
- font-weight: 600;
1325
- border-radius: 9999px;
1326
- background: rgba(0, 255, 156, 0.1);
1327
- color: var(--primary);
1328
- border: 1px solid rgba(0, 255, 156, 0.2);
1329
- }
1330
-
1331
- /* Card */
1332
- [class*="card"], .card {
1333
- background: var(--card);
1334
- border: 1px solid var(--border);
1335
- border-radius: 12px;
1336
- transition: all 0.3s;
1337
- }
1338
-
1339
- [class*="card"]:hover, .card:hover {
1340
- border-color: var(--primary);
1341
- }
1342
-
1343
- /* Navbar */
1344
- .navbar {
1345
- position: fixed;
1346
- top: 0;
1347
- left: 0;
1348
- right: 0;
1349
- z-index: 50;
1350
- border-bottom: 1px solid var(--border);
1351
- background: rgba(10,10,10,0.85);
1352
- backdrop-filter: blur(12px);
1353
- }
1354
- .navbar-inner {
1355
- max-width: 1200px;
1356
- margin: 0 auto;
1357
- padding: 1rem 1.5rem;
1358
- display: flex;
1359
- justify-content: space-between;
1360
- align-items: center;
1361
- }
1362
- .logo { display: flex; align-items: center; gap: 0.5rem; }
1363
- .logo-icon {
1364
- width: 32px;
1365
- height: 32px;
1366
- background: linear-gradient(135deg, var(--primary), var(--primary-dark));
1367
- border-radius: 8px;
1368
- display: flex;
1369
- align-items: center;
1370
- justify-content: center;
1371
- font-weight: 900;
1372
- font-size: 1rem;
1373
- color: #000;
1374
- }
1375
- .logo-text { font-weight: 700; font-size: 1.125rem; }
1376
- .nav-links { display: flex; gap: 0.75rem; align-items: center; }
1377
-
1378
- /* Hero */
1379
- .hero {
1380
- position: relative;
1381
- min-height: 100vh;
1382
- display: flex;
1383
- align-items: center;
1384
- justify-content: center;
1385
- padding: 6rem 1.5rem;
1386
- overflow: hidden;
1387
- }
1388
- .hero-glow {
1389
- position: absolute;
1390
- top: 50%;
1391
- left: 50%;
1392
- transform: translate(-50%, -50%);
1393
- width: 700px;
1394
- height: 700px;
1395
- background: radial-gradient(circle, rgba(0,255,156,0.12), transparent 70%);
1396
- pointer-events: none;
1397
- }
1398
- .hero-content {
1399
- position: relative;
1400
- z-index: 1;
1401
- text-align: center;
1402
- padding: 2rem;
1403
- max-width: 800px;
1404
- }
1405
- .hero-title {
1406
- font-size: clamp(2.5rem, 7vw, 4.5rem);
1407
- font-weight: 800;
1408
- line-height: 1.1;
1409
- letter-spacing: -0.02em;
1410
- margin-bottom: 1.5rem;
1411
- }
1412
- .gradient-text {
1413
- background: linear-gradient(135deg, var(--primary), var(--primary-dark));
1414
- -webkit-background-clip: text;
1415
- -webkit-text-fill-color: transparent;
1416
- background-clip: text;
1417
- }
1418
- .hero-subtitle {
1419
- font-size: 1.125rem;
1420
- color: var(--muted);
1421
- line-height: 1.7;
1422
- max-width: 600px;
1423
- margin: 0 auto 2.5rem;
1424
- }
1425
- .hero-buttons {
1426
- display: flex;
1427
- gap: 1rem;
1428
- justify-content: center;
1429
- flex-wrap: wrap;
1430
- }
1431
-
1432
- /* Features */
1433
- .features {
1434
- padding: 6rem 1.5rem;
1435
- max-width: 1200px;
1436
- margin: 0 auto;
1437
- }
1438
- .features-title {
1439
- font-size: 2rem;
1440
- font-weight: 700;
1441
- text-align: center;
1442
- margin-bottom: 3rem;
1443
- }
1444
- .features-grid {
1445
- display: grid;
1446
- grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
1447
- gap: 1.5rem;
1448
- }
1449
- .feature-card {
1450
- padding: 1.75rem;
1451
- text-align: center;
1452
- background: var(--card);
1453
- border: 1px solid var(--border);
1454
- border-radius: 16px;
1455
- transition: all 0.3s;
1456
- }
1457
- .feature-card:hover {
1458
- border-color: var(--primary);
1459
- transform: translateY(-4px);
1460
- }
1461
- .feature-icon {
1462
- font-size: 2rem;
1463
- margin-bottom: 1rem;
1464
- display: block;
1465
- }
1466
- .feature-card h3 { font-weight: 600; margin-bottom: 0.5rem; }
1467
- .feature-card p { color: var(--muted); font-size: 0.9375rem; line-height: 1.6; }
1468
-
1469
- /* Footer */
1470
- .footer {
1471
- border-top: 1px solid var(--border);
1472
- padding: 2rem 1.5rem;
1473
- display: flex;
1474
- justify-content: space-between;
1475
- align-items: center;
1476
- max-width: 1200px;
1477
- margin: 0 auto;
1478
- color: var(--muted);
1479
- font-size: 0.875rem;
1480
- }
1481
- .footer a:hover { color: var(--primary); }
1482
-
1483
- /* Responsive */
1484
- @media (max-width: 768px) {
1485
- .nav-links { display: none; }
1486
- .hero { min-height: auto; padding: 8rem 1.5rem 4rem; }
1487
- .hero-buttons { flex-direction: column; align-items: center; }
1488
- .hero-buttons button, .hero-buttons a { width: 100%; }
1489
- .footer { flex-direction: column; gap: 1rem; text-align: center; }
1490
- }
1491
- `,
1492
-
1493
- // ============================================================================
1494
- // Public Assets
1495
- // ============================================================================
1496
-
1497
- 'public/.gitkeep': () => '',
1498
-
1499
- 'public/favicon.svg': () => `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
1500
- <defs>
1501
- <linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
1502
- <stop offset="0%" style="stop-color:#00FF9C"/>
1503
- <stop offset="100%" style="stop-color:#10b981"/>
1504
- </linearGradient>
1505
- </defs>
1506
- <rect width="100" height="100" rx="20" fill="#0a0a0a"/>
1507
- <text x="50" y="68" font-family="system-ui" font-size="50" font-weight="900" fill="url(#grad)" text-anchor="middle">F</text>
1508
- </svg>`,
1509
- };
1510
-
1511
- // ============================================================================
1512
- // Main
1513
- // ============================================================================
1514
-
1515
- async function main() {
1516
- console.clear();
1517
- console.log(BANNER);
1518
-
1519
- // Get project name from args or prompt
1520
- let projectName = process.argv[2];
1521
-
1522
- if (!projectName) {
1523
- projectName = await prompt('Project name', 'my-flexireact-app');
1524
- }
1525
-
1526
- // Validate project name
1527
- if (!/^[a-zA-Z0-9-_]+$/.test(projectName)) {
1528
- error('Project name can only contain letters, numbers, dashes, and underscores');
1529
- process.exit(1);
1530
- }
1531
-
1532
- const projectPath = path.resolve(process.cwd(), projectName);
1533
-
1534
- // Check if directory exists
1535
- if (fs.existsSync(projectPath) && !isDirEmpty(projectPath)) {
1536
- error(`Directory ${projectName} already exists and is not empty`);
1537
- process.exit(1);
1538
- }
1539
-
1540
- // Select template
1541
- console.log('');
1542
- const templateOptions = Object.entries(TEMPLATES).map(([key, value]) => ({
1543
- key,
1544
- ...value,
1545
- }));
1546
-
1547
- const selectedTemplate = await select('Select a template:', templateOptions);
1548
- const templateKey = selectedTemplate.key;
1549
-
1550
- console.log('');
1551
- log(`Creating project in ${c.cyan}${projectPath}${c.reset}`);
1552
- console.log('');
1553
-
1554
- // Create project directory
1555
- const spinner1 = new Spinner('Creating project structure...');
1556
- spinner1.start();
1557
-
1558
- try {
1559
- fs.mkdirSync(projectPath, { recursive: true });
1560
-
1561
- // Create subdirectories
1562
- let dirs;
1563
- if (templateKey === 'minimal') {
1564
- dirs = ['pages', 'public'];
1565
- } else if (templateKey === 'flexi-ui') {
1566
- dirs = ['pages', 'public', 'components', 'layouts', 'app/styles'];
1567
- } else {
1568
- dirs = ['pages', 'public', 'components', 'components/ui', 'layouts', 'app/styles', 'lib'];
1569
- }
1570
-
1571
- for (const dir of dirs) {
1572
- fs.mkdirSync(path.join(projectPath, dir), { recursive: true });
1573
- }
1574
-
1575
- spinner1.stop(true);
1576
- } catch (err) {
1577
- spinner1.stop(false);
1578
- error(`Failed to create project structure: ${err.message}`);
1579
- process.exit(1);
1580
- }
1581
-
1582
- // Write template files
1583
- const spinner2 = new Spinner('Writing template files...');
1584
- spinner2.start();
1585
-
1586
- try {
1587
- let files;
1588
- if (templateKey === 'minimal') {
1589
- files = MINIMAL_FILES;
1590
- } else if (templateKey === 'flexi-ui') {
1591
- files = FLEXI_UI_FILES;
1592
- } else {
1593
- files = TEMPLATE_FILES;
1594
- }
1595
-
1596
- for (const [filePath, contentFn] of Object.entries(files)) {
1597
- const fullPath = path.join(projectPath, filePath);
1598
- const dir = path.dirname(fullPath);
1599
-
1600
- if (!fs.existsSync(dir)) {
1601
- fs.mkdirSync(dir, { recursive: true });
1602
- }
1603
-
1604
- const content = contentFn(projectName, templateKey);
1605
- fs.writeFileSync(fullPath, content);
1606
- }
1607
-
1608
- spinner2.stop(true);
1609
- } catch (err) {
1610
- spinner2.stop(false);
1611
- error(`Failed to write template files: ${err.message}`);
1612
- process.exit(1);
1613
- }
1614
-
1615
- // Create README
1616
- const spinner3 = new Spinner('Creating configuration files...');
1617
- spinner3.start();
1618
-
1619
- try {
1620
- const readmeContent = `# ${projectName}
1621
-
1622
- A modern web application built with [FlexiReact](https://github.com/flexireact/flexireact).
1623
-
1624
- ## Getting Started
1625
-
1626
- \`\`\`bash
1627
- npm install
1628
- npm run dev
1629
- \`\`\`
1630
-
1631
- Open [http://localhost:3000](http://localhost:3000) in your browser.
1632
-
1633
- ## Learn More
1634
-
1635
- - [FlexiReact Documentation](https://github.com/flexireact/flexireact)
1636
- `;
1637
-
1638
- fs.writeFileSync(path.join(projectPath, 'README.md'), readmeContent);
1639
- spinner3.stop(true);
1640
- } catch (err) {
1641
- spinner3.stop(false);
1642
- error(`Failed to create config files: ${err.message}`);
1643
- process.exit(1);
1644
- }
1645
-
1646
- // Success message
1647
- console.log(SUCCESS_BANNER(projectName));
1648
- }
1649
-
1650
- main().catch((err) => {
1651
- error(err.message);
1652
- process.exit(1);
1653
- });