@fuzionx/framework 0.1.29 → 0.1.31

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 (63) hide show
  1. package/cli/index.js +230 -114
  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/index.js +3 -0
  7. package/lib/core/Application.js +31 -6
  8. package/lib/core/Context.js +30 -1
  9. package/lib/helpers/I18nHelper.js +10 -6
  10. package/lib/middleware/apiAuth.js +79 -0
  11. package/lib/middleware/auth.js +42 -0
  12. package/lib/middleware/bodyParser.js +19 -0
  13. package/lib/middleware/cors.js +47 -0
  14. package/lib/middleware/csrf.js +32 -0
  15. package/lib/middleware/index.js +8 -277
  16. package/lib/middleware/session.js +27 -0
  17. package/lib/middleware/theme.js +20 -0
  18. package/lib/schedule/Job.js +4 -0
  19. package/lib/schedule/Queue.js +20 -8
  20. package/lib/schedule/Scheduler.js +84 -75
  21. package/lib/utilities/ArrUtil.js +112 -0
  22. package/lib/utilities/DateUtil.js +98 -0
  23. package/lib/utilities/FunctionUtil.js +119 -0
  24. package/lib/utilities/NumUtil.js +75 -0
  25. package/lib/utilities/ObjectUtil.js +170 -0
  26. package/lib/utilities/PaginationUtil.js +81 -0
  27. package/lib/utilities/StrUtil.js +105 -0
  28. package/lib/utilities/index.js +18 -0
  29. package/package.json +2 -2
  30. package/cli/templates/app/.env.example.tpl +0 -14
  31. package/cli/templates/app/.gitignore.tpl +0 -4
  32. package/cli/templates/app/app.js.tpl +0 -6
  33. package/cli/templates/app/database/models/User.js +0 -9
  34. package/cli/templates/app/fuzionx/views/default/errors/500.html +0 -14
  35. package/cli/templates/app/fuzionx/views/default/pages/home.html +0 -188
  36. package/cli/templates/app/fuzionx.yaml.tpl +0 -202
  37. package/cli/templates/app/locales/en.json +0 -52
  38. package/cli/templates/app/locales/ko.json +0 -52
  39. package/cli/templates/app/package.json.tpl +0 -16
  40. package/cli/templates/app/shared/events/userEvents.js +0 -10
  41. package/cli/templates/app/shared/jobs/CleanupJob.js +0 -18
  42. package/cli/templates/app/shared/jobs/EmailTask.js +0 -17
  43. package/cli/templates/app/shared/jobs/VideoPreviewTask.js +0 -47
  44. package/cli/templates/app/shared/workers/heavy.js +0 -18
  45. package/cli/templates/app/tester/controllers/FileController.js +0 -288
  46. package/cli/templates/app/tester/controllers/HomeController.js +0 -36
  47. package/cli/templates/app/tester/controllers/UserController.js +0 -43
  48. package/cli/templates/app/tester/middleware/RequestLogger.js +0 -13
  49. package/cli/templates/app/tester/routes/api.js +0 -397
  50. package/cli/templates/app/tester/routes/web.js +0 -8
  51. package/cli/templates/app/tester/services/UserService.js +0 -52
  52. package/cli/templates/app/tester/views/default/errors/500.html +0 -14
  53. package/cli/templates/app/tester/views/default/layouts/main.html +0 -82
  54. package/cli/templates/app/tester/views/default/pages/home.html +0 -56
  55. package/cli/templates/app/tester/views/default/pages/i18n.html +0 -104
  56. package/cli/templates/app/tester/views/default/pages/upload.html +0 -149
  57. package/cli/templates/app/tester/views/default/pages/websocket.html +0 -239
  58. package/cli/templates/app/tester/views/default/partials/footer.html +0 -8
  59. package/cli/templates/app/tester/views/default/partials/header.html +0 -20
  60. package/cli/templates/app/tester/ws/ChatHandler.js +0 -98
  61. /package/cli/templates/{app/fuzionx/routes/api.js.tpl → make/app/routes/api.js} +0 -0
  62. /package/cli/templates/{app/fuzionx/routes/web.js.tpl → make/app/routes/web.js} +0 -0
  63. /package/cli/templates/{app/fuzionx → make/app}/views/default/layouts/main.html +0 -0
@@ -0,0 +1,170 @@
1
+ /**
2
+ * ObjectUtil — 객체 순수 유틸리티
3
+ */
4
+
5
+ /**
6
+ * Deep clone (structuredClone 폴백)
7
+ * @param {*} obj
8
+ * @returns {*}
9
+ */
10
+ export function deepClone(obj) {
11
+ if (typeof structuredClone === 'function') return structuredClone(obj);
12
+ return JSON.parse(JSON.stringify(obj));
13
+ }
14
+
15
+ /**
16
+ * Deep merge — 비파괴 병합
17
+ * @param {object} target
18
+ * @param {...object} sources
19
+ * @returns {object}
20
+ */
21
+ export function deepMerge(target, ...sources) {
22
+ const result = { ...target };
23
+ for (const source of sources) {
24
+ if (!source) continue;
25
+ for (const key of Object.keys(source)) {
26
+ const sv = source[key];
27
+ const tv = result[key];
28
+ if (isPlainObject(sv) && isPlainObject(tv)) {
29
+ result[key] = deepMerge(tv, sv);
30
+ } else {
31
+ result[key] = sv;
32
+ }
33
+ }
34
+ }
35
+ return result;
36
+ }
37
+
38
+ /**
39
+ * 특정 키만 선택
40
+ * @param {object} obj
41
+ * @param {string[]} keys
42
+ * @returns {object}
43
+ *
44
+ * @example pick({ a:1, b:2, c:3 }, ['a', 'c']) → { a:1, c:3 }
45
+ */
46
+ export function pick(obj, keys) {
47
+ const result = {};
48
+ for (const key of keys) {
49
+ if (key in obj) result[key] = obj[key];
50
+ }
51
+ return result;
52
+ }
53
+
54
+ /**
55
+ * 특정 키 제외
56
+ * @param {object} obj
57
+ * @param {string[]} keys
58
+ * @returns {object}
59
+ */
60
+ export function omit(obj, keys) {
61
+ const keySet = new Set(keys);
62
+ const result = {};
63
+ for (const key of Object.keys(obj)) {
64
+ if (!keySet.has(key)) result[key] = obj[key];
65
+ }
66
+ return result;
67
+ }
68
+
69
+ /**
70
+ * Flatten — 중첩 객체를 dot notation으로 평탄화
71
+ * @param {object} obj
72
+ * @param {string} [prefix='']
73
+ * @returns {object}
74
+ *
75
+ * @example flatten({ a: { b: 1, c: { d: 2 } } }) → { 'a.b': 1, 'a.c.d': 2 }
76
+ */
77
+ export function flatten(obj, prefix = '') {
78
+ const result = {};
79
+ for (const key of Object.keys(obj)) {
80
+ const path = prefix ? `${prefix}.${key}` : key;
81
+ const val = obj[key];
82
+ if (isPlainObject(val)) {
83
+ Object.assign(result, flatten(val, path));
84
+ } else {
85
+ result[path] = val;
86
+ }
87
+ }
88
+ return result;
89
+ }
90
+
91
+ /**
92
+ * Unflatten — dot notation → 중첩 객체
93
+ * @param {object} obj
94
+ * @returns {object}
95
+ */
96
+ export function unflatten(obj) {
97
+ const result = {};
98
+ for (const [key, val] of Object.entries(obj)) {
99
+ const parts = key.split('.');
100
+ let current = result;
101
+ for (let i = 0; i < parts.length - 1; i++) {
102
+ if (!current[parts[i]] || typeof current[parts[i]] !== 'object') {
103
+ current[parts[i]] = {};
104
+ }
105
+ current = current[parts[i]];
106
+ }
107
+ current[parts[parts.length - 1]] = val;
108
+ }
109
+ return result;
110
+ }
111
+
112
+ /**
113
+ * 객체가 비어있는지 확인
114
+ * @param {*} obj
115
+ * @returns {boolean}
116
+ */
117
+ export function isEmpty(obj) {
118
+ if (!obj) return true;
119
+ return Object.keys(obj).length === 0;
120
+ }
121
+
122
+ /**
123
+ * dot notation으로 값 접근
124
+ * @param {object} obj
125
+ * @param {string} path
126
+ * @param {*} [defaultValue]
127
+ * @returns {*}
128
+ *
129
+ * @example get({ a: { b: { c: 42 } } }, 'a.b.c') → 42
130
+ */
131
+ export function get(obj, path, defaultValue) {
132
+ const parts = path.split('.');
133
+ let current = obj;
134
+ for (const part of parts) {
135
+ if (current == null || typeof current !== 'object') return defaultValue;
136
+ current = current[part];
137
+ }
138
+ return current !== undefined ? current : defaultValue;
139
+ }
140
+
141
+ /**
142
+ * dot notation으로 값 설정 (비파괴 — 새 객체 반환)
143
+ * @param {object} obj
144
+ * @param {string} path
145
+ * @param {*} value
146
+ * @returns {object}
147
+ */
148
+ export function set(obj, path, value) {
149
+ const result = deepClone(obj);
150
+ const parts = path.split('.');
151
+ let current = result;
152
+ for (let i = 0; i < parts.length - 1; i++) {
153
+ if (!current[parts[i]] || typeof current[parts[i]] !== 'object') {
154
+ current[parts[i]] = {};
155
+ }
156
+ current = current[parts[i]];
157
+ }
158
+ current[parts[parts.length - 1]] = value;
159
+ return result;
160
+ }
161
+
162
+ /**
163
+ * 순수 객체인지 확인 (Array, Date 등 제외)
164
+ * @param {*} val
165
+ * @returns {boolean}
166
+ */
167
+ export function isPlainObject(val) {
168
+ return val !== null && typeof val === 'object' && !Array.isArray(val)
169
+ && Object.getPrototypeOf(val) === Object.prototype;
170
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * PaginationUtil — 페이지네이션 순수 유틸리티
3
+ *
4
+ * Stateless 함수들. DB Pagination 클래스와 달리
5
+ * 인메모리 배열/수동 메타 계산에 사용.
6
+ *
7
+ * @see lib/database/Pagination.js — DB 쿼리 결과용
8
+ */
9
+
10
+ /**
11
+ * 메타 계산 — total, page, perPage로 페이지네이션 정보 생성
12
+ * @param {number} total - 전체 레코드 수
13
+ * @param {number} page - 현재 페이지 (1-based)
14
+ * @param {number} perPage - 페이지당 수
15
+ * @returns {{ total, page, perPage, lastPage, hasMore, from, to }}
16
+ */
17
+ export function buildMeta(total, page = 1, perPage = 20) {
18
+ const lastPage = Math.ceil(total / perPage) || 1;
19
+ const from = (page - 1) * perPage + 1;
20
+ const to = Math.min(page * perPage, total);
21
+ return {
22
+ total,
23
+ page,
24
+ perPage,
25
+ lastPage,
26
+ hasMore: page < lastPage,
27
+ from: total > 0 ? from : 0,
28
+ to: total > 0 ? to : 0,
29
+ };
30
+ }
31
+
32
+ /**
33
+ * 인메모리 배열 페이지네이션
34
+ * @param {Array} items - 전체 배열
35
+ * @param {number} page - 현재 페이지 (1-based)
36
+ * @param {number} perPage - 페이지당 수
37
+ * @returns {{ data: Array, meta: object }}
38
+ */
39
+ export function paginate(items, page = 1, perPage = 20) {
40
+ const total = items.length;
41
+ const start = (page - 1) * perPage;
42
+ const data = items.slice(start, start + perPage);
43
+ return { data, meta: buildMeta(total, page, perPage) };
44
+ }
45
+
46
+ /**
47
+ * offset/limit 계산
48
+ * @param {number} page
49
+ * @param {number} perPage
50
+ * @returns {{ offset: number, limit: number }}
51
+ */
52
+ export function offsetLimit(page = 1, perPage = 20) {
53
+ return {
54
+ offset: (page - 1) * perPage,
55
+ limit: perPage,
56
+ };
57
+ }
58
+
59
+ /**
60
+ * 페이지 번호 배열 생성 (페이지 네비게이션 UI용)
61
+ * @param {number} current - 현재 페이지
62
+ * @param {number} last - 마지막 페이지
63
+ * @param {number} [delta=2] - 현재 페이지 앞뒤로 표시할 수
64
+ * @returns {Array<number|'...'>}
65
+ *
66
+ * @example
67
+ * pageNumbers(5, 20, 2) → [1, '...', 3, 4, 5, 6, 7, '...', 20]
68
+ */
69
+ export function pageNumbers(current, last, delta = 2) {
70
+ const pages = [];
71
+ const rangeStart = Math.max(2, current - delta);
72
+ const rangeEnd = Math.min(last - 1, current + delta);
73
+
74
+ pages.push(1);
75
+ if (rangeStart > 2) pages.push('...');
76
+ for (let i = rangeStart; i <= rangeEnd; i++) pages.push(i);
77
+ if (rangeEnd < last - 1) pages.push('...');
78
+ if (last > 1) pages.push(last);
79
+
80
+ return pages;
81
+ }
@@ -0,0 +1,105 @@
1
+ /**
2
+ * StrUtil — 문자열 순수 유틸리티
3
+ */
4
+
5
+ /**
6
+ * URL-safe slug 생성
7
+ * @param {string} str
8
+ * @returns {string}
9
+ */
10
+ export function slug(str) {
11
+ return str
12
+ .toLowerCase()
13
+ .trim()
14
+ .replace(/[^\w\s가-힣-]/g, '')
15
+ .replace(/[\s_]+/g, '-')
16
+ .replace(/^-+|-+$/g, '');
17
+ }
18
+
19
+ /**
20
+ * 말줄임 (한글/이모지 안전)
21
+ * @param {string} str
22
+ * @param {number} len
23
+ * @param {string} [suffix='…']
24
+ * @returns {string}
25
+ */
26
+ export function truncate(str, len, suffix = '…') {
27
+ if (!str || str.length <= len) return str || '';
28
+ return str.slice(0, len) + suffix;
29
+ }
30
+
31
+ /**
32
+ * camelCase 변환
33
+ * @param {string} str
34
+ * @returns {string}
35
+ */
36
+ export function camelCase(str) {
37
+ return str
38
+ .replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : '')
39
+ .replace(/^[A-Z]/, c => c.toLowerCase());
40
+ }
41
+
42
+ /**
43
+ * snake_case 변환
44
+ * @param {string} str
45
+ * @returns {string}
46
+ */
47
+ export function snakeCase(str) {
48
+ return str
49
+ .replace(/([a-z])([A-Z])/g, '$1_$2')
50
+ .replace(/[-\s]+/g, '_')
51
+ .toLowerCase();
52
+ }
53
+
54
+ /**
55
+ * PascalCase 변환
56
+ * @param {string} str
57
+ * @returns {string}
58
+ */
59
+ export function pascalCase(str) {
60
+ return str
61
+ .replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : '')
62
+ .replace(/^[a-z]/, c => c.toUpperCase());
63
+ }
64
+
65
+ /**
66
+ * 첫 글자 대문자
67
+ * @param {string} str
68
+ * @returns {string}
69
+ */
70
+ export function capitalize(str) {
71
+ if (!str) return '';
72
+ return str.charAt(0).toUpperCase() + str.slice(1);
73
+ }
74
+
75
+ /**
76
+ * 랜덤 문자열 생성
77
+ * @param {number} len
78
+ * @param {string} [chars]
79
+ * @returns {string}
80
+ */
81
+ export function random(len = 16, chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789') {
82
+ let result = '';
83
+ for (let i = 0; i < len; i++) {
84
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
85
+ }
86
+ return result;
87
+ }
88
+
89
+ /**
90
+ * 마스킹 (이메일, 전화번호 등)
91
+ * @param {string} str
92
+ * @param {number} [visibleStart=2]
93
+ * @param {number} [visibleEnd=2]
94
+ * @param {string} [mask='*']
95
+ * @returns {string}
96
+ *
97
+ * @example mask('hello@test.com', 2, 4) → 'he*****t.com'
98
+ */
99
+ export function mask(str, visibleStart = 2, visibleEnd = 2, maskChar = '*') {
100
+ if (!str || str.length <= visibleStart + visibleEnd) return str || '';
101
+ const start = str.slice(0, visibleStart);
102
+ const end = str.slice(-visibleEnd);
103
+ const middle = maskChar.repeat(str.length - visibleStart - visibleEnd);
104
+ return start + middle + end;
105
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Utilities — 순수 함수 유틸리티 배럴 export
3
+ *
4
+ * Helper와 달리 Bridge/App 의존 없이 import 즉시 사용 가능.
5
+ *
6
+ * @example
7
+ * import { PaginationUtil, StrUtil, ObjectUtil } from '@fuzionx/framework';
8
+ * const meta = PaginationUtil.buildMeta(100, 1, 20);
9
+ * const s = StrUtil.slug('Hello World');
10
+ * const cleaned = ObjectUtil.omit(user, ['password']);
11
+ */
12
+ export * as PaginationUtil from './PaginationUtil.js';
13
+ export * as StrUtil from './StrUtil.js';
14
+ export * as NumUtil from './NumUtil.js';
15
+ export * as DateUtil from './DateUtil.js';
16
+ export * as ArrUtil from './ArrUtil.js';
17
+ export * as FunctionUtil from './FunctionUtil.js';
18
+ export * as ObjectUtil from './ObjectUtil.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzionx/framework",
3
- "version": "0.1.29",
3
+ "version": "0.1.31",
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.31",
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>