@fuzionx/framework 0.1.29 → 0.1.30

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 (41) hide show
  1. package/cli/index.js +90 -109
  2. package/cli/templates/{app/fuzionx → make/app}/controllers/HomeController.js +1 -0
  3. package/cli/templates/{app/tester → make/app}/views/default/errors/404.html +0 -4
  4. package/cli/templates/{app/fuzionx/views/default/errors/404.html → make/app/views/default/errors/500.html} +1 -2
  5. package/cli/templates/make/app/views/default/pages/home.html +11 -0
  6. package/lib/core/Application.js +4 -1
  7. package/package.json +2 -2
  8. package/cli/templates/app/.env.example.tpl +0 -14
  9. package/cli/templates/app/.gitignore.tpl +0 -4
  10. package/cli/templates/app/app.js.tpl +0 -6
  11. package/cli/templates/app/database/models/User.js +0 -9
  12. package/cli/templates/app/fuzionx/views/default/errors/500.html +0 -14
  13. package/cli/templates/app/fuzionx/views/default/pages/home.html +0 -188
  14. package/cli/templates/app/fuzionx.yaml.tpl +0 -202
  15. package/cli/templates/app/locales/en.json +0 -52
  16. package/cli/templates/app/locales/ko.json +0 -52
  17. package/cli/templates/app/package.json.tpl +0 -16
  18. package/cli/templates/app/shared/events/userEvents.js +0 -10
  19. package/cli/templates/app/shared/jobs/CleanupJob.js +0 -18
  20. package/cli/templates/app/shared/jobs/EmailTask.js +0 -17
  21. package/cli/templates/app/shared/jobs/VideoPreviewTask.js +0 -47
  22. package/cli/templates/app/shared/workers/heavy.js +0 -18
  23. package/cli/templates/app/tester/controllers/FileController.js +0 -288
  24. package/cli/templates/app/tester/controllers/HomeController.js +0 -36
  25. package/cli/templates/app/tester/controllers/UserController.js +0 -43
  26. package/cli/templates/app/tester/middleware/RequestLogger.js +0 -13
  27. package/cli/templates/app/tester/routes/api.js +0 -397
  28. package/cli/templates/app/tester/routes/web.js +0 -8
  29. package/cli/templates/app/tester/services/UserService.js +0 -52
  30. package/cli/templates/app/tester/views/default/errors/500.html +0 -14
  31. package/cli/templates/app/tester/views/default/layouts/main.html +0 -82
  32. package/cli/templates/app/tester/views/default/pages/home.html +0 -56
  33. package/cli/templates/app/tester/views/default/pages/i18n.html +0 -104
  34. package/cli/templates/app/tester/views/default/pages/upload.html +0 -149
  35. package/cli/templates/app/tester/views/default/pages/websocket.html +0 -239
  36. package/cli/templates/app/tester/views/default/partials/footer.html +0 -8
  37. package/cli/templates/app/tester/views/default/partials/header.html +0 -20
  38. package/cli/templates/app/tester/ws/ChatHandler.js +0 -98
  39. /package/cli/templates/{app/fuzionx/routes/api.js.tpl → make/app/routes/api.js} +0 -0
  40. /package/cli/templates/{app/fuzionx/routes/web.js.tpl → make/app/routes/web.js} +0 -0
  41. /package/cli/templates/{app/fuzionx → make/app}/views/default/layouts/main.html +0 -0
package/cli/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * CLI — fx 명령어 핸들러
3
3
  *
4
- * fx new <name> 스캐폴딩
4
+ * fx make:app <name> 디렉토리 생성
5
5
  * fx make:controller <Name> 컨트롤러 생성
6
6
  * fx make:service <Name> 서비스 생성
7
7
  * fx make:model <Name> 모델 생성
@@ -120,24 +120,9 @@ export async function makeFile(type, name, baseDir = '.', appName) {
120
120
  }
121
121
 
122
122
  // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
123
- // fx new <name> — 앱 스캐폴딩
123
+ // 유틸리티
124
124
  // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
125
125
 
126
- const APP_FILES = [
127
- { tpl: 'app/package.json.tpl', dest: 'package.json' },
128
- { tpl: 'app/fuzionx.yaml.tpl', dest: 'fuzionx.yaml' },
129
- { tpl: 'app/app.js.tpl', dest: 'app.js' },
130
- { tpl: 'app/.env.example.tpl', dest: '.env.example' },
131
- { tpl: 'app/.env.example.tpl', dest: '.env' },
132
- { tpl: 'app/.gitignore.tpl', dest: '.gitignore' },
133
- ];
134
-
135
- /** fuzionx 앱 템플릿 (.tpl 파일은 치환, 그 외는 직접 복사) */
136
- const FUZIONX_TPL_FILES = [
137
- { tpl: 'app/fuzionx/routes/web.js.tpl', dest: 'app/fuzionx/routes/web.js' },
138
- { tpl: 'app/fuzionx/routes/api.js.tpl', dest: 'app/fuzionx/routes/api.js' },
139
- ];
140
-
141
126
  /** 디렉토리 재귀 복사 (템플릿 → 프로젝트) */
142
127
  async function copyDirRecursive(src, dst) {
143
128
  await fs.mkdir(dst, { recursive: true });
@@ -153,91 +138,6 @@ async function copyDirRecursive(src, dst) {
153
138
  }
154
139
  }
155
140
 
156
- /** 스캐폴딩 디렉토리 (빈 디렉토리 + .gitkeep) */
157
- const APP_DIRS = [
158
- // fuzionx 앱 (개발자 작업 영역)
159
- 'app/fuzionx/services',
160
- 'app/fuzionx/middleware',
161
- 'app/fuzionx/ws',
162
- // tester 앱 (빈 폴더가 아닌 것은 copyDirRecursive로 처리)
163
- // 인프라
164
- 'database/migrations',
165
- 'database/seeds',
166
- 'storage/logs',
167
- 'storage/uploads',
168
- 'public/css',
169
- 'public/js',
170
- 'tests',
171
- ];
172
-
173
- /**
174
- * fx new <name> — 앱 스캐폴딩 (fuzionx + tester 2-app 구조)
175
- * @param {string} name - 프로젝트 이름
176
- * @param {string} [targetDir] - 기본: ./<name>
177
- */
178
- export async function createApp(name, targetDir) {
179
- const dir = targetDir || path.resolve(name);
180
- const vars = {
181
- name,
182
- dbName: name.replace(/-/g, '_'),
183
- };
184
-
185
- // 빈 디렉토리 생성 (.gitkeep 포함)
186
- for (const d of APP_DIRS) {
187
- const fullDir = path.join(dir, d);
188
- await fs.mkdir(fullDir, { recursive: true });
189
- const files = await fs.readdir(fullDir).catch(() => []);
190
- if (files.length === 0) {
191
- await fs.writeFile(path.join(fullDir, '.gitkeep'), '');
192
- }
193
- }
194
-
195
- // 루트 템플릿 파일 생성 (.tpl 치환)
196
- for (const { tpl, dest } of APP_FILES) {
197
- const content = await loadTemplate(tpl, vars);
198
- const fullPath = path.join(dir, dest);
199
- await fs.mkdir(path.dirname(fullPath), { recursive: true });
200
- await fs.writeFile(fullPath, content);
201
- }
202
-
203
- // fuzionx 앱 — .tpl 파일 (치환 필요)
204
- for (const { tpl, dest } of FUZIONX_TPL_FILES) {
205
- const content = await loadTemplate(tpl, vars);
206
- const fullPath = path.join(dir, dest);
207
- await fs.mkdir(path.dirname(fullPath), { recursive: true });
208
- await fs.writeFile(fullPath, content);
209
- }
210
-
211
- // fuzionx 앱 — 직접 복사 (controllers, views)
212
- const fuzionxSrc = path.join(TPL_DIR, 'app/fuzionx');
213
- await copyDirRecursive(
214
- path.join(fuzionxSrc, 'controllers'),
215
- path.join(dir, 'app/fuzionx/controllers'),
216
- );
217
- await copyDirRecursive(
218
- path.join(fuzionxSrc, 'views'),
219
- path.join(dir, 'app/fuzionx/views'),
220
- );
221
-
222
- // tester 앱 — 전체 복사
223
- const testerSrc = path.join(TPL_DIR, 'app/tester');
224
- await copyDirRecursive(testerSrc, path.join(dir, 'app/tester'));
225
-
226
- // shared — 전체 복사
227
- const sharedSrc = path.join(TPL_DIR, 'app/shared');
228
- await copyDirRecursive(sharedSrc, path.join(dir, 'shared'));
229
-
230
- // database/models — 복사
231
- const dbSrc = path.join(TPL_DIR, 'app/database');
232
- await copyDirRecursive(dbSrc, path.join(dir, 'database'));
233
-
234
- // locales — 복사
235
- const localesSrc = path.join(TPL_DIR, 'app/locales');
236
- await copyDirRecursive(localesSrc, path.join(dir, 'locales'));
237
-
238
- return dir;
239
- }
240
-
241
141
  // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
242
142
  // CLI 엔트리
243
143
  // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
@@ -249,14 +149,29 @@ export async function createApp(name, targetDir) {
249
149
  export async function run(args) {
250
150
  const [command, ...rest] = args;
251
151
 
152
+ // ── fx new → create-fuzionx 안내 ──
252
153
  if (command === 'new') {
154
+ console.log(`\n 프로젝트 생성은 create-fuzionx를 사용하세요:\n`);
155
+ console.log(` npx create-fuzionx <name>\n`);
156
+ return;
157
+ }
158
+
159
+ if (command === 'make:app') {
253
160
  const name = rest[0];
254
- if (!name) { console.error('Usage: fx new <name>'); process.exit(1); }
255
- const dir = await createApp(name);
256
- console.log(`✅ Created ${name} at ${dir}`);
257
- console.log(`\n cd ${name}`);
258
- console.log(` npm install`);
259
- console.log(` npm run dev\n`);
161
+ if (!name) { console.error('Usage: fx make:app <appName>'); process.exit(1); }
162
+ const appDir = path.join('.', 'app', name);
163
+ // 디렉토리 생성
164
+ for (const d of ['services', 'middleware', 'ws']) {
165
+ const fullDir = path.join(appDir, d);
166
+ await fs.mkdir(fullDir, { recursive: true });
167
+ await fs.writeFile(path.join(fullDir, '.gitkeep'), '');
168
+ }
169
+ // 기본 파일 복사 (HomeController, routes, views)
170
+ const appTplDir = path.join(TPL_DIR, 'make/app');
171
+ await copyDirRecursive(appTplDir, appDir);
172
+ console.log(`✅ Created app/${name}/`);
173
+ console.log(`\n fuzionx.yaml의 apps에 추가하세요:`);
174
+ console.log(` "127.0.0.1:49080": ${name}\n`);
260
175
  return;
261
176
  }
262
177
 
@@ -284,6 +199,69 @@ export async function run(args) {
284
199
  return;
285
200
  }
286
201
 
202
+ // ── fx stop — 서버 종료 ──
203
+ if (command === 'stop') {
204
+ const pidFile = path.resolve('fuzionx.pid');
205
+ try {
206
+ const pid = parseInt(await fs.readFile(pidFile, 'utf-8'), 10);
207
+ console.log(`🛑 Stopping PID ${pid}...`);
208
+ process.kill(pid, 'SIGTERM');
209
+ for (let i = 0; i < 50; i++) {
210
+ await new Promise(r => setTimeout(r, 100));
211
+ try { process.kill(pid, 0); } catch { break; }
212
+ }
213
+ await fs.unlink(pidFile).catch(() => {});
214
+ console.log('✅ Server stopped.');
215
+ } catch (err) {
216
+ if (err.code === 'ENOENT') {
217
+ console.log('⚠️ fuzionx.pid 없음 — 서버가 실행 중이 아닙니다.');
218
+ } else if (err.code === 'ESRCH') {
219
+ await fs.unlink(pidFile).catch(() => {});
220
+ console.log('⚠️ PID 프로세스가 이미 종료됨. PID 파일 삭제.');
221
+ } else {
222
+ console.error('❌ 종료 실패:', err.message);
223
+ }
224
+ }
225
+ return;
226
+ }
227
+
228
+ // ── fx restart — 서버 재시작 ──
229
+ if (command === 'restart') {
230
+ const pidFile = path.resolve('fuzionx.pid');
231
+ try {
232
+ const pid = parseInt(await fs.readFile(pidFile, 'utf-8'), 10);
233
+ console.log(`🔄 Stopping PID ${pid}...`);
234
+ process.kill(pid, 'SIGTERM');
235
+ for (let i = 0; i < 50; i++) {
236
+ await new Promise(r => setTimeout(r, 100));
237
+ try { process.kill(pid, 0); } catch { break; }
238
+ }
239
+ } catch (err) {
240
+ if (err.code === 'ENOENT') {
241
+ console.log('⚠️ fuzionx.pid 없음 — 새로 시작합니다.');
242
+ } else if (err.code === 'ESRCH') {
243
+ console.log('⚠️ PID 프로세스가 이미 종료됨.');
244
+ } else {
245
+ console.error('❌ 종료 실패:', err.message);
246
+ }
247
+ }
248
+ // 백그라운드로 재시작
249
+ const { spawn } = await import('node:child_process');
250
+ const logDir = path.resolve('storage/logs');
251
+ await fs.mkdir(logDir, { recursive: true });
252
+ const logFile = path.join(logDir, 'server.log');
253
+ const { openSync } = await import('node:fs');
254
+ const out = openSync(logFile, 'a');
255
+ const child = spawn('node', ['app.js'], {
256
+ cwd: process.cwd(),
257
+ detached: true,
258
+ stdio: ['ignore', out, out],
259
+ });
260
+ child.unref();
261
+ console.log(`🚀 Restarted (PID ${child.pid}) — logs: storage/logs/server.log`);
262
+ return;
263
+ }
264
+
287
265
  // ── fx test — 테스트 실행 ──
288
266
  if (command === 'test') {
289
267
  const { execSync } = await import('node:child_process');
@@ -357,7 +335,8 @@ export async function run(args) {
357
335
  }
358
336
 
359
337
  console.log(`
360
- fx new <name> Create new app (multi-app structure)
338
+ npx create-fuzionx <name> Create new project
339
+ fx make:app <name> Create new app directory structure
361
340
  fx make:controller <Name> --app= Create controller (app-specific)
362
341
  fx make:service <Name> --app= Create service (app-specific)
363
342
  fx make:model <Name> Create model (database/models)
@@ -369,6 +348,8 @@ export async function run(args) {
369
348
  fx make:worker <Name> Create worker (shared/workers)
370
349
  fx make:test <Name> Create test
371
350
  fx dev Start dev server (--watch)
351
+ fx stop Stop server (graceful)
352
+ fx restart Restart server (graceful)
372
353
  fx test Run tests
373
354
  fx routes Print route table
374
355
  fx config Print fuzionx.yaml
@@ -7,6 +7,7 @@ import { Controller } from '@fuzionx/framework';
7
7
  export default class HomeController extends Controller {
8
8
 
9
9
  /** 홈 페이지 */
10
+ static index;
10
11
  async index(ctx) {
11
12
  ctx.render('home');
12
13
  }
@@ -6,10 +6,6 @@
6
6
  <div style="text-align:center;padding:80px 20px;">
7
7
  <h1 style="font-size:72px;color:#e74c3c;">{{ error.code }}</h1>
8
8
  <p style="font-size:20px;margin:16px 0;">{{ error.message }}</p>
9
- <p style="color:#888;">요청: {{ request.url }}</p>
10
- {% if config.debug %}
11
- <pre style="text-align:left;max-width:600px;margin:24px auto;background:#f5f5f5;padding:16px;border-radius:8px;overflow:auto;">{{ error.stack }}</pre>
12
- {% endif %}
13
9
  <a href="/" style="display:inline-block;margin-top:24px;padding:12px 24px;background:#e74c3c;color:#fff;text-decoration:none;border-radius:6px;">홈으로 돌아가기</a>
14
10
  </div>
15
11
  {% endblock %}
@@ -1,12 +1,11 @@
1
1
  {% extends "layouts/main.html" %}
2
2
 
3
- {% block title %}404찾을 수 없습니다{% endblock %}
3
+ {% block title %}500서버 오류{% endblock %}
4
4
 
5
5
  {% block content %}
6
6
  <div style="text-align:center;padding:80px 20px;">
7
7
  <h1 style="font-size:72px;color:#e74c3c;">{{ error.code }}</h1>
8
8
  <p style="font-size:20px;margin:16px 0;">{{ error.message }}</p>
9
- <p style="color:#888;">요청: {{ request.url }}</p>
10
9
  {% if config.debug %}
11
10
  <pre style="text-align:left;max-width:600px;margin:24px auto;background:#f5f5f5;padding:16px;border-radius:8px;overflow:auto;">{{ error.stack }}</pre>
12
11
  {% endif %}
@@ -0,0 +1,11 @@
1
+ {% extends "layouts/main.html" %}
2
+
3
+ {% block title %}Welcome{% endblock %}
4
+
5
+ {% block content %}
6
+ <div style="text-align:center;padding:80px 20px;">
7
+ <h1 style="font-size:48px;color:#333;">🚀 Welcome</h1>
8
+ <p style="font-size:18px;color:#666;margin:16px 0;">Your new app is ready.</p>
9
+ <p style="color:#999;font-size:14px;">Edit <code style="background:#f0f0f0;padding:2px 8px;border-radius:4px;">routes/web.js</code> to get started</p>
10
+ </div>
11
+ {% endblock %}
@@ -8,6 +8,7 @@
8
8
  * @see docs/framework/04-bootstrap-lifecycle.md
9
9
  */
10
10
  import path from 'node:path';
11
+ import { promises as fs } from 'node:fs';
11
12
  import Config from './Config.js';
12
13
  import Context from './Context.js';
13
14
  import Router from '../http/Router.js';
@@ -427,10 +428,12 @@ export default class Application {
427
428
 
428
429
  if (!this._booted) await this.boot();
429
430
 
430
- // 포트 사용 여부 확인 — primary에서만 (워커는 SO_REUSEPORT로 공유)
431
+ // PID 파일 생성 (primary)
431
432
  const cluster = await import('node:cluster');
432
433
  if (!cluster.default?.isWorker) {
433
434
  await this._checkPort(port);
435
+ const pidPath = path.join(this.baseDir, 'fuzionx.pid');
436
+ await fs.writeFile(pidPath, String(process.pid));
434
437
  }
435
438
 
436
439
  await this.emit('ready');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzionx/framework",
3
- "version": "0.1.29",
3
+ "version": "0.1.30",
4
4
  "type": "module",
5
5
  "description": "Full-stack MVC framework built on @fuzionx/core — Controller, Service, Model, Middleware, DI, EventBus",
6
6
  "main": "index.js",
@@ -34,7 +34,7 @@
34
34
  "url": "https://github.com/saytohenry/fuzionx"
35
35
  },
36
36
  "dependencies": {
37
- "@fuzionx/core": "^0.1.29",
37
+ "@fuzionx/core": "^0.1.30",
38
38
  "better-sqlite3": "^12.8.0",
39
39
  "knex": "^3.2.5",
40
40
  "mongoose": "^9.3.2",
@@ -1,14 +0,0 @@
1
- # .env — 이 파일을 .env로 복사 후 값을 채우세요
2
-
3
- # Database
4
- DB_HOST=127.0.0.1
5
- DB_PORT=3306
6
- DB_USER=root
7
- DB_PASSWORD=
8
- DB_NAME={{dbName}}
9
-
10
- # Auth
11
- JWT_SECRET=
12
-
13
- # Redis (세션/큐 공용)
14
- REDIS_URL=
@@ -1,4 +0,0 @@
1
- .env
2
- node_modules/
3
- storage/logs/
4
- storage/uploads/
@@ -1,6 +0,0 @@
1
- import { Application } from '@fuzionx/framework';
2
-
3
- const app = new Application({ configPath: './fuzionx.yaml' });
4
-
5
- await app.boot();
6
- await app.listen();
@@ -1,9 +0,0 @@
1
- import { MariaModel } from '@fuzionx/framework';
2
-
3
- export default class User extends MariaModel {
4
- static table = 'users';
5
- static connection = 'main';
6
- static softDelete = true;
7
- static hidden = ['password'];
8
- static fillable = ['name', 'email', 'password', 'role'];
9
- }
@@ -1,14 +0,0 @@
1
- {% extends "layouts/main.html" %}
2
-
3
- {% block title %}500 — 서버 오류{% endblock %}
4
-
5
- {% block content %}
6
- <div style="text-align:center;padding:80px 20px;">
7
- <h1 style="font-size:72px;color:#e74c3c;">500</h1>
8
- <p style="font-size:20px;margin:16px 0;">{{ error.message | default(value='Internal Server Error') }}</p>
9
- {% if config.debug and error.stack %}
10
- <pre style="text-align:left;max-width:600px;margin:24px auto;background:#f5f5f5;padding:16px;border-radius:8px;overflow:auto;">{{ error.stack }}</pre>
11
- {% endif %}
12
- <a href="/" style="display:inline-block;margin-top:24px;padding:12px 24px;background:#e74c3c;color:#fff;text-decoration:none;border-radius:6px;">홈으로 돌아가기</a>
13
- </div>
14
- {% endblock %}
@@ -1,188 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="ko">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>FuzionX</title>
7
- <style>
8
- * { margin: 0; padding: 0; box-sizing: border-box; }
9
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;800&display=swap');
10
-
11
- body {
12
- font-family: 'Inter', system-ui, -apple-system, sans-serif;
13
- min-height: 100vh;
14
- display: flex;
15
- align-items: center;
16
- justify-content: center;
17
- background: linear-gradient(135deg, #0f0c29 0%, #1a1a3e 40%, #24243e 100%);
18
- color: #e0e0e0;
19
- overflow: hidden;
20
- }
21
-
22
- .container {
23
- text-align: center;
24
- z-index: 1;
25
- animation: fadeInUp 0.8s ease-out;
26
- }
27
-
28
- .logo {
29
- font-size: 4rem;
30
- font-weight: 800;
31
- background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%);
32
- -webkit-background-clip: text;
33
- background-clip: text;
34
- -webkit-text-fill-color: transparent;
35
- letter-spacing: -2px;
36
- margin-bottom: 0.5rem;
37
- }
38
-
39
- .subtitle {
40
- font-size: 1.1rem;
41
- font-weight: 300;
42
- color: rgba(255, 255, 255, 0.5);
43
- margin-bottom: 2.5rem;
44
- letter-spacing: 2px;
45
- }
46
-
47
- .card {
48
- background: rgba(255, 255, 255, 0.05);
49
- backdrop-filter: blur(20px);
50
- border: 1px solid rgba(255, 255, 255, 0.1);
51
- border-radius: 16px;
52
- padding: 2rem 3rem;
53
- max-width: 480px;
54
- margin: 0 auto 2rem;
55
- }
56
-
57
- .version {
58
- display: inline-block;
59
- background: linear-gradient(135deg, #667eea, #764ba2);
60
- color: white;
61
- padding: 4px 14px;
62
- border-radius: 20px;
63
- font-size: 0.75rem;
64
- font-weight: 600;
65
- letter-spacing: 1px;
66
- margin-bottom: 1.5rem;
67
- }
68
-
69
- .features {
70
- display: grid;
71
- grid-template-columns: 1fr 1fr;
72
- gap: 1rem;
73
- text-align: left;
74
- margin-top: 1.5rem;
75
- }
76
-
77
- .feature {
78
- display: flex;
79
- align-items: center;
80
- gap: 8px;
81
- font-size: 0.85rem;
82
- color: rgba(255, 255, 255, 0.7);
83
- }
84
-
85
- .feature span {
86
- font-size: 1.1rem;
87
- }
88
-
89
- .links {
90
- display: flex;
91
- gap: 1rem;
92
- justify-content: center;
93
- margin-top: 1rem;
94
- }
95
-
96
- .links a {
97
- color: rgba(255, 255, 255, 0.6);
98
- text-decoration: none;
99
- font-size: 0.85rem;
100
- padding: 8px 20px;
101
- border: 1px solid rgba(255, 255, 255, 0.15);
102
- border-radius: 8px;
103
- transition: all 0.3s ease;
104
- }
105
-
106
- .links a:hover {
107
- color: #fff;
108
- border-color: #667eea;
109
- background: rgba(102, 126, 234, 0.1);
110
- transform: translateY(-2px);
111
- }
112
-
113
- .hint {
114
- margin-top: 2rem;
115
- font-size: 0.75rem;
116
- color: rgba(255, 255, 255, 0.3);
117
- }
118
-
119
- .hint code {
120
- background: rgba(255, 255, 255, 0.08);
121
- padding: 2px 8px;
122
- border-radius: 4px;
123
- font-family: 'JetBrains Mono', monospace;
124
- }
125
-
126
- /* Background orbs */
127
- .orb {
128
- position: fixed;
129
- border-radius: 50%;
130
- filter: blur(80px);
131
- opacity: 0.3;
132
- animation: float 8s ease-in-out infinite;
133
- }
134
- .orb-1 { width: 400px; height: 400px; background: #667eea; top: -100px; right: -100px; }
135
- .orb-2 { width: 300px; height: 300px; background: #764ba2; bottom: -80px; left: -80px; animation-delay: -4s; }
136
- .orb-3 { width: 200px; height: 200px; background: #f093fb; top: 50%; left: 60%; animation-delay: -2s; }
137
-
138
- @keyframes fadeInUp {
139
- from { opacity: 0; transform: translateY(30px); }
140
- to { opacity: 1; transform: translateY(0); }
141
- }
142
-
143
- @keyframes float {
144
- 0%, 100% { transform: translate(0, 0); }
145
- 50% { transform: translate(30px, -30px); }
146
- }
147
-
148
- @media (max-width: 600px) {
149
- .logo { font-size: 2.5rem; }
150
- .card { padding: 1.5rem; margin: 0 1rem; }
151
- .features { grid-template-columns: 1fr; }
152
- }
153
- </style>
154
- </head>
155
- <body>
156
- <div class="orb orb-1"></div>
157
- <div class="orb orb-2"></div>
158
- <div class="orb orb-3"></div>
159
-
160
- <div class="container">
161
- <h1 class="logo">FuzionX</h1>
162
- <p class="subtitle">HIGH-PERFORMANCE NODE.JS FRAMEWORK</p>
163
-
164
- <div class="card">
165
- <div class="version">v0.1.0 · POWERED BY RUST</div>
166
-
167
- <div class="features">
168
- <div class="feature"><span>⚡</span> 500K+ RPS</div>
169
- <div class="feature"><span>🦀</span> Rust N-API Bridge</div>
170
- <div class="feature"><span>🎯</span> MVC Architecture</div>
171
- <div class="feature"><span>🔌</span> WebSocket</div>
172
- <div class="feature"><span>🗄️</span> Multi-DB ORM</div>
173
- <div class="feature"><span>🔐</span> Auth & Session</div>
174
- <div class="feature"><span>📡</span> Event System</div>
175
- <div class="feature"><span>⏰</span> Job Scheduler</div>
176
- </div>
177
- </div>
178
-
179
- <div class="links">
180
- <a href="https://github.com/saytohenry/fuzionx">GitHub</a>
181
- <a href="/docs">API Docs</a>
182
- <a href="/api/health">Health Check</a>
183
- </div>
184
-
185
- <p class="hint">Edit <code>routes/web.js</code> to get started</p>
186
- </div>
187
- </body>
188
- </html>