millas 0.1.4 → 0.1.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "millas",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "A modern batteries-included backend framework for Node.js — built on Express, inspired by Laravel, Django, and FastAPI",
5
5
  "main": "src/index.js",
6
6
  "exports": {
@@ -11,7 +11,7 @@ module.exports = function (program) {
11
11
  .description('Detect model changes and generate migration files')
12
12
  .action(async () => {
13
13
  const ctx = getProjectContext();
14
- const { ModelInspector } = require('../orm/migration/ModelInspector');
14
+ const ModelInspector = require('../orm/migration/ModelInspector');
15
15
  const inspector = new ModelInspector(
16
16
  ctx.modelsPath,
17
17
  ctx.migrationsPath,
@@ -9,382 +9,658 @@ const WELCOME_PAGE = `<!DOCTYPE html>
9
9
  <meta charset="UTF-8">
10
10
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
11
11
  <title>Millas — Installation successful!</title>
12
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" integrity="sha512-Avb2QiuDEEvB4bZJYdft2mNjVShBftLdPG8FJ0V7irTLQ8Uo0qcPxh4Plh7eecDqFDBs4pIAczCMWwKY3KDg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
13
+ <link rel="preconnect" href="https://fonts.googleapis.com">
14
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
15
+ <link href="https://fonts.googleapis.com/css2?family=Libre+Baskerville:ital,wght@0,400;0,700;1,400&family=DM+Mono:wght@400;500&family=DM+Sans:wght@300;400;500;600&display=swap" rel="stylesheet">
12
16
  <style>
13
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap');
14
-
15
17
  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
16
18
 
17
19
  :root {
18
- --bg: #080a12;
19
- --surface: #0f1220;
20
- --surface2: #161929;
21
- --border: #1e2235;
22
- --border2: #252840;
23
- --primary: #6366f1;
24
- --primary-h: #818cf8;
25
- --glow: rgba(99,102,241,0.15);
26
- --text: #e2e8f0;
27
- --muted: #475569;
28
- --soft: #94a3b8;
29
- --green: #22c55e;
30
- --yellow: #f59e0b;
20
+ --bg: #f5f3ee;
21
+ --bg2: #edeae3;
22
+ --white: #ffffff;
23
+ --border: #d6d0c4;
24
+ --border2: #c4bdb0;
25
+ --primary: #2563eb;
26
+ --primary-dk: #1d4ed8;
27
+ --primary-lt: #eff6ff;
28
+ --accent: #059669;
29
+ --accent-lt: #ecfdf5;
30
+ --warn: #d97706;
31
+ --warn-lt: #fffbeb;
32
+ --text: #1c1917;
33
+ --text2: #44403c;
34
+ --muted: #78716c;
35
+ --faint: #a8a29e;
31
36
  }
32
37
 
33
38
  html { scroll-behavior: smooth; }
34
39
 
35
40
  body {
36
- font-family: 'Inter', system-ui, sans-serif;
41
+ font-family: 'DM Sans', system-ui, sans-serif;
37
42
  background: var(--bg);
38
43
  color: var(--text);
39
44
  min-height: 100vh;
40
45
  line-height: 1.6;
41
- overflow-x: hidden;
42
46
  }
43
47
 
44
- /* ── Animated background grid ── */
45
- body::before {
46
- content: '';
47
- position: fixed;
48
- inset: 0;
49
- background-image:
50
- linear-gradient(rgba(99,102,241,0.03) 1px, transparent 1px),
51
- linear-gradient(90deg, rgba(99,102,241,0.03) 1px, transparent 1px);
52
- background-size: 40px 40px;
53
- pointer-events: none;
54
- z-index: 0;
55
- }
56
-
57
- /* ── Glow orbs ── */
58
- .orb {
59
- position: fixed;
60
- border-radius: 50%;
61
- filter: blur(80px);
62
- pointer-events: none;
63
- z-index: 0;
64
- opacity: 0.5;
65
- }
66
- .orb-1 { width: 500px; height: 500px; background: radial-gradient(circle, rgba(99,102,241,0.15), transparent 70%); top: -100px; left: -100px; animation: drift1 18s ease-in-out infinite; }
67
- .orb-2 { width: 400px; height: 400px; background: radial-gradient(circle, rgba(168,85,247,0.1), transparent 70%); bottom: -80px; right: -80px; animation: drift2 22s ease-in-out infinite; }
68
- @keyframes drift1 { 0%,100%{transform:translate(0,0)} 50%{transform:translate(60px,40px)} }
69
- @keyframes drift2 { 0%,100%{transform:translate(0,0)} 50%{transform:translate(-40px,-60px)} }
70
-
71
- /* ── Layout ── */
72
- .page { position: relative; z-index: 1; max-width: 900px; margin: 0 auto; padding: 60px 24px 80px; }
73
-
74
- /* ── Hero ── */
75
- .hero { text-align: center; margin-bottom: 64px; }
76
-
77
- .logo-wrap {
78
- display: inline-flex; align-items: center; justify-content: center;
79
- width: 80px; height: 80px; border-radius: 22px;
80
- background: linear-gradient(135deg, #6366f1, #a855f7);
81
- font-size: 36px; margin-bottom: 28px;
82
- box-shadow: 0 0 40px rgba(99,102,241,0.3), 0 0 80px rgba(99,102,241,0.1);
83
- animation: pulse 3s ease-in-out infinite;
84
- }
85
- @keyframes pulse { 0%,100%{box-shadow:0 0 40px rgba(99,102,241,0.3),0 0 80px rgba(99,102,241,0.1)} 50%{box-shadow:0 0 60px rgba(99,102,241,0.5),0 0 120px rgba(99,102,241,0.2)} }
86
-
87
- .version-badge {
88
- display: inline-flex; align-items: center; gap: 6px;
89
- background: var(--surface2); border: 1px solid var(--border2);
90
- color: var(--primary-h); border-radius: 99px;
91
- padding: 4px 14px; font-size: 12px; font-weight: 600;
92
- margin-bottom: 24px; letter-spacing: 0.3px;
93
- }
94
- .version-dot { width: 6px; height: 6px; background: var(--green); border-radius: 50%; animation: blink 2s ease-in-out infinite; }
95
- @keyframes blink { 0%,100%{opacity:1} 50%{opacity:0.3} }
96
-
97
- h1 {
98
- font-size: clamp(36px, 6vw, 56px);
99
- font-weight: 700;
100
- letter-spacing: -1.5px;
101
- line-height: 1.1;
102
- margin-bottom: 20px;
103
- background: linear-gradient(135deg, #fff 0%, #94a3b8 100%);
104
- -webkit-background-clip: text;
105
- -webkit-text-fill-color: transparent;
106
- background-clip: text;
107
- }
108
- h1 span {
109
- background: linear-gradient(135deg, #6366f1, #a855f7);
110
- -webkit-background-clip: text;
111
- -webkit-text-fill-color: transparent;
112
- background-clip: text;
113
- }
114
-
115
- .hero-sub {
48
+ /* ── Top bar ── */
49
+ .topbar {
50
+ background: var(--white);
51
+ border-bottom: 1px solid var(--border);
52
+ padding: 0 32px;
53
+ display: flex;
54
+ align-items: center;
55
+ justify-content: space-between;
56
+ height: 52px;
57
+ position: sticky;
58
+ top: 0;
59
+ z-index: 100;
60
+ }
61
+ .topbar-brand {
62
+ display: flex;
63
+ align-items: center;
64
+ gap: 10px;
65
+ font-family: 'Libre Baskerville', Georgia, serif;
116
66
  font-size: 17px;
117
- color: var(--soft);
118
- max-width: 520px;
119
- margin: 0 auto 36px;
120
- font-weight: 400;
67
+ font-weight: 700;
68
+ color: var(--text);
69
+ text-decoration: none;
70
+ letter-spacing: -0.2px;
71
+ }
72
+ .topbar-brand .brand-icon {
73
+ width: 28px;
74
+ height: 28px;
75
+ background: var(--primary);
76
+ border-radius: 6px;
77
+ display: flex;
78
+ align-items: center;
79
+ justify-content: center;
80
+ color: white;
81
+ font-size: 13px;
121
82
  }
122
-
123
- .hero-actions {
124
- display: flex; gap: 12px; justify-content: center; flex-wrap: wrap;
83
+ .topbar-links {
84
+ display: flex;
85
+ align-items: center;
86
+ gap: 4px;
87
+ }
88
+ .topbar-links a {
89
+ display: flex;
90
+ align-items: center;
91
+ gap: 6px;
92
+ padding: 6px 12px;
93
+ border-radius: 6px;
94
+ text-decoration: none;
95
+ font-size: 13px;
96
+ font-weight: 500;
97
+ color: var(--muted);
98
+ transition: background .15s, color .15s;
125
99
  }
126
- .btn {
127
- display: inline-flex; align-items: center; gap: 8px;
128
- padding: 11px 22px; border-radius: 10px;
129
- font-size: 14px; font-weight: 600;
130
- text-decoration: none; transition: all .2s;
131
- font-family: inherit; cursor: pointer; border: none;
100
+ .topbar-links a:hover { background: var(--bg); color: var(--text); }
101
+ .topbar-links a i { font-size: 12px; }
102
+
103
+ /* ── Layout ── */
104
+ .layout {
105
+ display: grid;
106
+ grid-template-columns: 220px 1fr;
107
+ min-height: calc(100vh - 52px);
108
+ }
109
+
110
+ /* ── Sidebar ── */
111
+ .sidebar {
112
+ background: var(--white);
113
+ border-right: 1px solid var(--border);
114
+ padding: 28px 0;
115
+ position: sticky;
116
+ top: 52px;
117
+ height: calc(100vh - 52px);
118
+ overflow-y: auto;
119
+ }
120
+ .sidebar-section {
121
+ padding: 0 16px 20px;
122
+ }
123
+ .sidebar-label {
124
+ font-size: 10px;
125
+ font-weight: 600;
126
+ letter-spacing: 0.8px;
127
+ text-transform: uppercase;
128
+ color: var(--faint);
129
+ padding: 0 8px;
130
+ margin-bottom: 6px;
131
+ }
132
+ .sidebar-link {
133
+ display: flex;
134
+ align-items: center;
135
+ gap: 9px;
136
+ padding: 7px 10px;
137
+ border-radius: 7px;
138
+ text-decoration: none;
139
+ font-size: 13.5px;
140
+ font-weight: 500;
141
+ color: var(--text2);
142
+ transition: background .15s, color .15s;
143
+ margin-bottom: 1px;
144
+ }
145
+ .sidebar-link i {
146
+ width: 16px;
147
+ text-align: center;
148
+ font-size: 13px;
149
+ color: var(--faint);
150
+ flex-shrink: 0;
151
+ }
152
+ .sidebar-link:hover { background: var(--bg); color: var(--text); }
153
+ .sidebar-link:hover i { color: var(--primary); }
154
+ .sidebar-link.active { background: var(--primary-lt); color: var(--primary); }
155
+ .sidebar-link.active i { color: var(--primary); }
156
+ .sidebar-divider {
157
+ height: 1px;
158
+ background: var(--border);
159
+ margin: 4px 16px 20px;
160
+ }
161
+
162
+ /* ── Main content ── */
163
+ .main {
164
+ padding: 40px 48px 60px;
165
+ max-width: 820px;
166
+ }
167
+
168
+ /* ── Success banner ── */
169
+ .success-banner {
170
+ background: var(--accent-lt);
171
+ border: 1px solid #a7f3d0;
172
+ border-left: 4px solid var(--accent);
173
+ border-radius: 8px;
174
+ padding: 14px 18px;
175
+ display: flex;
176
+ align-items: flex-start;
177
+ gap: 12px;
178
+ margin-bottom: 36px;
179
+ }
180
+ .success-banner i {
181
+ color: var(--accent);
182
+ font-size: 16px;
183
+ margin-top: 1px;
184
+ flex-shrink: 0;
185
+ }
186
+ .success-banner-text { font-size: 14px; color: #065f46; line-height: 1.5; }
187
+ .success-banner-text strong { font-weight: 600; display: block; margin-bottom: 2px; }
188
+
189
+ /* ── Page heading ── */
190
+ .page-heading {
191
+ margin-bottom: 36px;
192
+ padding-bottom: 28px;
193
+ border-bottom: 1px solid var(--border);
132
194
  }
133
- .btn-primary {
134
- background: linear-gradient(135deg, #6366f1, #7c3aed);
135
- color: #fff;
136
- box-shadow: 0 4px 20px rgba(99,102,241,0.3);
195
+ .page-heading h1 {
196
+ font-family: 'Libre Baskerville', Georgia, serif;
197
+ font-size: 30px;
198
+ font-weight: 700;
199
+ letter-spacing: -0.5px;
200
+ line-height: 1.2;
201
+ color: var(--text);
202
+ margin-bottom: 8px;
137
203
  }
138
- .btn-primary:hover { transform: translateY(-2px); box-shadow: 0 8px 30px rgba(99,102,241,0.4); }
139
- .btn-ghost {
140
- background: var(--surface); color: var(--soft);
141
- border: 1px solid var(--border2);
204
+ .page-heading p {
205
+ font-size: 15px;
206
+ color: var(--muted);
207
+ max-width: 560px;
208
+ }
209
+ .page-heading p code {
210
+ font-family: 'DM Mono', monospace;
211
+ font-size: 12.5px;
212
+ background: var(--bg2);
213
+ color: var(--primary);
214
+ padding: 1px 6px;
215
+ border-radius: 4px;
216
+ border: 1px solid var(--border);
142
217
  }
143
- .btn-ghost:hover { background: var(--surface2); color: var(--text); border-color: var(--primary); }
144
218
 
145
- /* ── Divider ── */
146
- .divider {
147
- text-align: center; position: relative; margin: 48px 0;
148
- color: var(--muted); font-size: 12px; text-transform: uppercase;
149
- letter-spacing: 1px; font-weight: 600;
219
+ /* ── Info strip ── */
220
+ .info-strip {
221
+ display: grid;
222
+ grid-template-columns: repeat(3, 1fr);
223
+ gap: 14px;
224
+ margin-bottom: 36px;
150
225
  }
151
- .divider::before {
226
+ .info-card {
227
+ background: var(--white);
228
+ border: 1px solid var(--border);
229
+ border-radius: 9px;
230
+ padding: 16px 18px;
231
+ }
232
+ .info-card-label {
233
+ font-size: 11px;
234
+ font-weight: 600;
235
+ letter-spacing: 0.5px;
236
+ text-transform: uppercase;
237
+ color: var(--faint);
238
+ margin-bottom: 6px;
239
+ display: flex;
240
+ align-items: center;
241
+ gap: 6px;
242
+ }
243
+ .info-card-label i { font-size: 10px; }
244
+ .info-card-value {
245
+ font-size: 14px;
246
+ font-weight: 600;
247
+ color: var(--text);
248
+ display: flex;
249
+ align-items: center;
250
+ gap: 7px;
251
+ }
252
+ .dot-green {
253
+ width: 7px; height: 7px; border-radius: 50%;
254
+ background: var(--accent);
255
+ box-shadow: 0 0 6px rgba(5,150,105,0.4);
256
+ flex-shrink: 0;
257
+ }
258
+ .info-card-value a {
259
+ color: var(--primary);
260
+ text-decoration: none;
261
+ font-weight: 600;
262
+ }
263
+ .info-card-value a:hover { text-decoration: underline; }
264
+
265
+ /* ── Section titles ── */
266
+ .section-title {
267
+ font-size: 12px;
268
+ font-weight: 600;
269
+ letter-spacing: 0.6px;
270
+ text-transform: uppercase;
271
+ color: var(--faint);
272
+ margin-bottom: 14px;
273
+ display: flex;
274
+ align-items: center;
275
+ gap: 8px;
276
+ }
277
+ .section-title::after {
152
278
  content: '';
153
- position: absolute; top: 50%; left: 0; right: 0;
154
- height: 1px; background: var(--border);
279
+ flex: 1;
280
+ height: 1px;
281
+ background: var(--border);
155
282
  }
156
- .divider span { background: var(--bg); padding: 0 16px; position: relative; }
157
283
 
158
- /* ── Feature grid ── */
159
- .features {
284
+ /* ── Feature table ── */
285
+ .feature-table {
286
+ background: var(--white);
287
+ border: 1px solid var(--border);
288
+ border-radius: 10px;
289
+ overflow: hidden;
290
+ margin-bottom: 36px;
291
+ }
292
+ .feature-table-row {
160
293
  display: grid;
161
- grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
162
- gap: 16px;
163
- margin-bottom: 48px;
294
+ grid-template-columns: 40px 1fr;
295
+ align-items: start;
296
+ padding: 14px 18px;
297
+ gap: 14px;
298
+ border-bottom: 1px solid var(--border);
299
+ transition: background .12s;
164
300
  }
165
- .feature {
166
- background: var(--surface);
301
+ .feature-table-row:last-child { border-bottom: none; }
302
+ .feature-table-row:hover { background: var(--bg); }
303
+ .feature-icon-wrap {
304
+ width: 34px; height: 34px;
305
+ background: var(--bg2);
167
306
  border: 1px solid var(--border);
168
- border-radius: 14px;
169
- padding: 24px;
170
- transition: border-color .2s, transform .2s;
171
- }
172
- .feature:hover { border-color: var(--primary); transform: translateY(-3px); }
173
- .feature-icon { font-size: 26px; margin-bottom: 12px; }
174
- .feature-title { font-size: 14px; font-weight: 600; color: var(--text); margin-bottom: 6px; }
175
- .feature-desc { font-size: 13px; color: var(--muted); line-height: 1.6; }
176
-
177
- /* ── Code block ── */
178
- .code-section { margin-bottom: 48px; }
179
- .code-section h2 { font-size: 16px; font-weight: 600; margin-bottom: 16px; color: var(--soft); }
180
- .code-block {
181
- background: var(--surface);
307
+ border-radius: 8px;
308
+ display: flex;
309
+ align-items: center;
310
+ justify-content: center;
311
+ flex-shrink: 0;
312
+ margin-top: 1px;
313
+ }
314
+ .feature-icon-wrap i {
315
+ font-size: 14px;
316
+ color: var(--primary);
317
+ }
318
+ .feature-body {}
319
+ .feature-name {
320
+ font-size: 14px;
321
+ font-weight: 600;
322
+ color: var(--text);
323
+ margin-bottom: 2px;
324
+ }
325
+ .feature-desc {
326
+ font-size: 13px;
327
+ color: var(--muted);
328
+ line-height: 1.55;
329
+ }
330
+ .feature-desc code {
331
+ font-family: 'DM Mono', monospace;
332
+ font-size: 11.5px;
333
+ background: var(--bg2);
334
+ color: var(--primary);
335
+ padding: 1px 5px;
336
+ border-radius: 4px;
337
+ border: 1px solid var(--border);
338
+ }
339
+
340
+ /* ── Code blocks ── */
341
+ .code-wrap {
342
+ background: var(--white);
182
343
  border: 1px solid var(--border);
183
- border-radius: 12px;
344
+ border-radius: 10px;
184
345
  overflow: hidden;
346
+ margin-bottom: 36px;
185
347
  }
186
348
  .code-header {
187
- background: var(--surface2);
188
- padding: 10px 16px;
349
+ background: var(--bg2);
189
350
  border-bottom: 1px solid var(--border);
190
- display: flex; align-items: center; gap: 8px;
191
- font-size: 12px; color: var(--muted);
351
+ padding: 9px 16px;
352
+ display: flex;
353
+ align-items: center;
354
+ justify-content: space-between;
355
+ gap: 10px;
356
+ }
357
+ .code-header-left {
358
+ display: flex;
359
+ align-items: center;
360
+ gap: 8px;
361
+ }
362
+ .code-header-left i {
363
+ color: var(--faint);
364
+ font-size: 12px;
365
+ }
366
+ .code-filename {
367
+ font-family: 'DM Mono', monospace;
368
+ font-size: 12px;
369
+ color: var(--text2);
370
+ font-weight: 500;
371
+ }
372
+ .code-lang {
373
+ font-size: 11px;
374
+ font-weight: 600;
375
+ letter-spacing: 0.4px;
376
+ text-transform: uppercase;
377
+ color: var(--faint);
378
+ background: var(--border);
379
+ padding: 2px 7px;
380
+ border-radius: 4px;
192
381
  }
193
- .code-dots { display: flex; gap: 6px; }
194
- .code-dot { width: 10px; height: 10px; border-radius: 50%; }
195
- .code-dot:nth-child(1){background:#ef4444} .code-dot:nth-child(2){background:#f59e0b} .code-dot:nth-child(3){background:#22c55e}
196
- .code-filename { font-family: 'JetBrains Mono', monospace; margin-left: 4px; }
197
382
  pre {
198
- padding: 20px 24px;
199
- font-family: 'JetBrains Mono', monospace;
200
- font-size: 13px;
201
- line-height: 1.7;
383
+ padding: 20px 22px;
384
+ font-family: 'DM Mono', monospace;
385
+ font-size: 12.5px;
386
+ line-height: 1.75;
202
387
  overflow-x: auto;
203
- color: #94a3b8;
204
- }
205
- .kw { color: #c084fc; } /* keywords */
206
- .fn { color: #818cf8; } /* functions */
207
- .str { color: #34d399; } /* strings */
208
- .cm { color: #475569; font-style: italic; } /* comments */
209
- .cl { color: #f8fafc; } /* class names */
210
- .pm { color: #f59e0b; } /* parameters */
211
-
212
- /* ── Status strip ── */
213
- .status-strip {
214
- display: flex; gap: 12px; flex-wrap: wrap;
215
- margin-bottom: 48px;
216
- }
217
- .status-item {
218
- flex: 1; min-width: 160px;
219
- background: var(--surface);
220
- border: 1px solid var(--border);
221
- border-radius: 12px;
222
- padding: 16px 20px;
223
- display: flex; align-items: center; gap: 12px;
388
+ color: var(--text2);
389
+ }
390
+ .kw { color: #7c3aed; font-weight: 500; }
391
+ .fn { color: #2563eb; }
392
+ .str { color: #059669; }
393
+ .cm { color: var(--faint); font-style: italic; }
394
+ .cl { color: #b45309; }
395
+ .pm { color: var(--text); }
396
+ .num { color: #dc2626; }
397
+
398
+ /* ── Warning notice ── */
399
+ .notice {
400
+ background: var(--warn-lt);
401
+ border: 1px solid #fde68a;
402
+ border-left: 4px solid var(--warn);
403
+ border-radius: 8px;
404
+ padding: 13px 16px;
405
+ display: flex;
406
+ align-items: flex-start;
407
+ gap: 10px;
408
+ margin-bottom: 36px;
409
+ font-size: 13.5px;
410
+ color: #78350f;
411
+ line-height: 1.55;
412
+ }
413
+ .notice i { color: var(--warn); font-size: 14px; margin-top: 2px; flex-shrink: 0; }
414
+ .notice code {
415
+ font-family: 'DM Mono', monospace;
416
+ font-size: 11.5px;
417
+ background: #fef3c7;
418
+ padding: 1px 5px;
419
+ border-radius: 4px;
420
+ border: 1px solid #fde68a;
421
+ color: #92400e;
224
422
  }
225
- .status-icon { font-size: 20px; }
226
- .status-label { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; font-weight: 600; }
227
- .status-value { font-size: 14px; font-weight: 600; color: var(--text); }
228
- .status-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--green); box-shadow: 0 0 8px rgba(34,197,94,0.5); flex-shrink: 0; }
229
423
 
230
424
  /* ── Footer ── */
231
- footer {
232
- text-align: center;
233
- padding-top: 40px;
425
+ .page-footer {
426
+ padding-top: 28px;
234
427
  border-top: 1px solid var(--border);
235
- color: var(--muted);
428
+ display: flex;
429
+ align-items: center;
430
+ justify-content: space-between;
431
+ flex-wrap: wrap;
432
+ gap: 12px;
433
+ }
434
+ .page-footer-left {
236
435
  font-size: 13px;
237
- display: flex; flex-direction: column; gap: 12px; align-items: center;
436
+ color: var(--faint);
238
437
  }
239
- .footer-links { display: flex; gap: 24px; }
240
- .footer-links a { color: var(--muted); text-decoration: none; font-size: 13px; }
241
- .footer-links a:hover { color: var(--primary-h); }
242
-
243
- /* ── Notice ── */
244
- .notice {
245
- background: linear-gradient(135deg, rgba(99,102,241,0.08), rgba(168,85,247,0.05));
246
- border: 1px solid rgba(99,102,241,0.2);
247
- border-radius: 12px;
248
- padding: 16px 20px;
438
+ .page-footer-links {
439
+ display: flex;
440
+ gap: 16px;
441
+ }
442
+ .page-footer-links a {
249
443
  font-size: 13px;
250
- color: var(--soft);
251
- margin-bottom: 40px;
252
- display: flex; align-items: flex-start; gap: 12px;
444
+ color: var(--muted);
445
+ text-decoration: none;
446
+ display: flex;
447
+ align-items: center;
448
+ gap: 5px;
449
+ transition: color .15s;
253
450
  }
254
- .notice-icon { font-size: 16px; flex-shrink: 0; margin-top: 1px; }
255
- code { font-family: 'JetBrains Mono', monospace; background: var(--surface2); color: var(--primary-h); padding: 1px 7px; border-radius: 4px; font-size: 12px; }
451
+ .page-footer-links a i { font-size: 11px; }
452
+ .page-footer-links a:hover { color: var(--primary); }
256
453
 
257
- @media(max-width:600px) {
258
- .page { padding: 40px 16px 60px; }
259
- h1 { font-size: 32px; }
260
- .features { grid-template-columns: 1fr; }
454
+ @media(max-width: 768px) {
455
+ .layout { grid-template-columns: 1fr; }
456
+ .sidebar { display: none; }
457
+ .main { padding: 28px 20px 48px; }
458
+ .info-strip { grid-template-columns: 1fr; }
261
459
  }
262
460
  </style>
263
461
  </head>
264
462
  <body>
265
- <div class="orb orb-1"></div>
266
- <div class="orb orb-2"></div>
267
463
 
268
- <div class="page">
464
+ <!-- Top bar -->
465
+ <nav class="topbar">
466
+ <a href="/" class="topbar-brand">
467
+ <div class="brand-icon"><i class="fa-solid fa-bolt"></i></div>
468
+ Millas
469
+ </a>
470
+ <div class="topbar-links">
471
+ <a href="/admin"><i class="fa-solid fa-gauge-high"></i> Admin</a>
472
+ <a href="/api/health"><i class="fa-solid fa-heart-pulse"></i> Health</a>
473
+ <a href="https://github.com/millas-framework/millas" target="_blank"><i class="fa-brands fa-github"></i> GitHub</a>
474
+ </div>
475
+ </nav>
476
+
477
+ <div class="layout">
478
+
479
+ <!-- Sidebar -->
480
+ <aside class="sidebar">
481
+ <div class="sidebar-section">
482
+ <div class="sidebar-label">Navigation</div>
483
+ <a href="/admin" class="sidebar-link">
484
+ <i class="fa-solid fa-gauge-high"></i> Admin Panel
485
+ </a>
486
+ <a href="/api/health" class="sidebar-link">
487
+ <i class="fa-solid fa-heart-pulse"></i> API Health
488
+ </a>
489
+ </div>
269
490
 
270
- <!-- Hero -->
271
- <div class="hero">
272
- <div class="logo-wrap">⚡</div>
273
- <div class="version-badge">
274
- <span class="version-dot"></span>
275
- Millas v0.1.2 Running
491
+ <div class="sidebar-divider"></div>
492
+
493
+ <div class="sidebar-section">
494
+ <div class="sidebar-label">Framework</div>
495
+ <a href="#routing" class="sidebar-link active">
496
+ <i class="fa-solid fa-route"></i> Routing
497
+ </a>
498
+ <a href="#database" class="sidebar-link">
499
+ <i class="fa-solid fa-database"></i> Database / ORM
500
+ </a>
501
+ <a href="#auth" class="sidebar-link">
502
+ <i class="fa-solid fa-lock"></i> Authentication
503
+ </a>
504
+ <a href="#mail" class="sidebar-link">
505
+ <i class="fa-solid fa-envelope"></i> Mail
506
+ </a>
507
+ <a href="#queue" class="sidebar-link">
508
+ <i class="fa-solid fa-layer-group"></i> Queue
509
+ </a>
510
+ <a href="#cache" class="sidebar-link">
511
+ <i class="fa-solid fa-bolt"></i> Cache
512
+ </a>
513
+ <a href="#storage" class="sidebar-link">
514
+ <i class="fa-solid fa-folder-open"></i> Storage
515
+ </a>
276
516
  </div>
277
- <h1>The installation<br>worked. <span>Congratulations.</span></h1>
278
- <p class="hero-sub">
279
- You're looking at the default Millas welcome page.
280
- This page disappears the moment you define a route at <code>/</code>.
281
- </p>
282
- <div class="hero-actions">
283
- <a href="/admin" class="btn btn-primary">⚡ Open Admin Panel</a>
284
- <a href="/api/health" class="btn btn-ghost">🔍 API Health Check</a>
517
+
518
+ <div class="sidebar-divider"></div>
519
+
520
+ <div class="sidebar-section">
521
+ <div class="sidebar-label">External</div>
522
+ <a href="https://www.npmjs.com/package/millas" target="_blank" class="sidebar-link">
523
+ <i class="fa-brands fa-npm"></i> npm package
524
+ </a>
525
+ <a href="https://github.com/millas-framework/millas" target="_blank" class="sidebar-link">
526
+ <i class="fa-brands fa-github"></i> GitHub
527
+ </a>
285
528
  </div>
286
- </div>
529
+ </aside>
287
530
 
288
- <!-- Notice -->
289
- <div class="notice">
290
- <span class="notice-icon">💡</span>
291
- <span>
292
- To remove this page, define your own route in <code>routes/web.js</code>:
293
- <br>
294
- <code style="margin-top:6px;display:inline-block">Route.get('/', (req, res) => res.json({ hello: 'world' }))</code>
295
- </span>
296
- </div>
531
+ <!-- Main -->
532
+ <main class="main">
297
533
 
298
- <!-- Status strip -->
299
- <div class="status-strip">
300
- <div class="status-item">
301
- <span class="status-dot"></span>
302
- <div>
303
- <div class="status-label">Server</div>
304
- <div class="status-value">Running</div>
534
+ <!-- Success banner -->
535
+ <div class="success-banner">
536
+ <i class="fa-solid fa-circle-check"></i>
537
+ <div class="success-banner-text">
538
+ <strong>Installation successful!</strong>
539
+ Millas v0.1.2 is running. Define a route at <code style="font-family:'DM Mono',monospace;font-size:12px;background:rgba(5,150,105,0.08);color:#065f46;padding:1px 6px;border-radius:4px;border:1px solid #a7f3d0">GET /</code> in <code style="font-family:'DM Mono',monospace;font-size:12px;background:rgba(5,150,105,0.08);color:#065f46;padding:1px 6px;border-radius:4px;border:1px solid #a7f3d0">routes/web.js</code> to replace this page.
305
540
  </div>
306
541
  </div>
307
- <div class="status-item">
308
- <span class="status-icon">🗄️</span>
309
- <div>
310
- <div class="status-label">Admin Panel</div>
311
- <div class="status-value"><a href="/admin" style="color:var(--primary-h);text-decoration:none">/admin →</a></div>
312
- </div>
542
+
543
+ <!-- Heading -->
544
+ <div class="page-heading">
545
+ <h1>Millas Framework</h1>
546
+ <p>
547
+ A Node.js web framework built on Express. This welcome page is shown
548
+ because no route is registered at <code>/</code>. Add one to get started.
549
+ </p>
313
550
  </div>
314
- <div class="status-item">
315
- <span class="status-icon">🌐</span>
316
- <div>
317
- <div class="status-label">API</div>
318
- <div class="status-value"><a href="/api/health" style="color:var(--primary-h);text-decoration:none">/api/health →</a></div>
551
+
552
+ <!-- Status strip -->
553
+ <div class="info-strip">
554
+ <div class="info-card">
555
+ <div class="info-card-label"><i class="fa-solid fa-server"></i> Server</div>
556
+ <div class="info-card-value"><span class="dot-green"></span> Running</div>
319
557
  </div>
320
- </div>
321
- <div class="status-item">
322
- <span class="status-icon">📦</span>
323
- <div>
324
- <div class="status-label">Framework</div>
325
- <div class="status-value">Millas 0.1.2</div>
558
+ <div class="info-card">
559
+ <div class="info-card-label"><i class="fa-solid fa-gauge-high"></i> Admin panel</div>
560
+ <div class="info-card-value"><a href="/admin">/admin &rarr;</a></div>
561
+ </div>
562
+ <div class="info-card">
563
+ <div class="info-card-label"><i class="fa-solid fa-heart-pulse"></i> API health</div>
564
+ <div class="info-card-value"><a href="/api/health">/api/health &rarr;</a></div>
326
565
  </div>
327
566
  </div>
328
- </div>
329
-
330
- <div class="divider"><span>What's included</span></div>
331
567
 
332
- <!-- Features -->
333
- <div class="features">
334
- <div class="feature">
335
- <div class="feature-icon">🛣️</div>
336
- <div class="feature-title">Expressive Router</div>
337
- <div class="feature-desc">Route groups, prefixes, middleware, resource routes, and the <code>Route.auth()</code> shortcut.</div>
338
- </div>
339
- <div class="feature">
340
- <div class="feature-icon">🗃️</div>
341
- <div class="feature-title">ORM + Migrations</div>
342
- <div class="feature-desc">Model-driven schema with <code>millas makemigrations</code> and <code>millas migrate</code>.</div>
343
- </div>
344
- <div class="feature">
345
- <div class="feature-icon">🔐</div>
346
- <div class="feature-title">Authentication</div>
347
- <div class="feature-desc">JWT out of the box. Register, login, refresh, password reset — all via <code>Auth.login()</code>.</div>
348
- </div>
349
- <div class="feature">
350
- <div class="feature-icon">📬</div>
351
- <div class="feature-title">Mail</div>
352
- <div class="feature-desc">SMTP, SendGrid, Mailgun. Template engine with <code>{{ variable }}</code>, loops, and conditionals.</div>
353
- </div>
354
- <div class="feature">
355
- <div class="feature-icon">⚙️</div>
356
- <div class="feature-title">Queue System</div>
357
- <div class="feature-desc">Background jobs with <code>dispatch(new Job())</code>. Database and sync drivers included.</div>
358
- </div>
359
- <div class="feature">
360
- <div class="feature-icon">📡</div>
361
- <div class="feature-title">Event System</div>
362
- <div class="feature-desc">Fire and listen to events. Listeners can run inline or through the queue.</div>
363
- </div>
364
- <div class="feature">
365
- <div class="feature-icon">⚡</div>
366
- <div class="feature-title">Cache</div>
367
- <div class="feature-desc">Memory, file, and null drivers. Tag-based invalidation with <code>Cache.tags('users').flush()</code>.</div>
368
- </div>
369
- <div class="feature">
370
- <div class="feature-icon">🗂️</div>
371
- <div class="feature-title">File Storage</div>
372
- <div class="feature-desc">Local disk with multiple named disks. Upload, copy, move, list, stream files.</div>
568
+ <!-- Notice -->
569
+ <div class="notice">
570
+ <i class="fa-solid fa-triangle-exclamation"></i>
571
+ <span>
572
+ This welcome page is only visible while no <code>GET /</code> route is defined.
573
+ As soon as you add one to <code>routes/web.js</code>, it disappears automatically.
574
+ </span>
373
575
  </div>
374
- <div class="feature">
375
- <div class="feature-icon">🛡️</div>
376
- <div class="feature-title">Admin Panel</div>
377
- <div class="feature-desc">Register any model and get a full CRUD dashboard at <a href="/admin" style="color:var(--primary-h)">/admin</a> automatically.</div>
576
+
577
+ <!-- Included features -->
578
+ <div class="section-title">What&rsquo;s included</div>
579
+ <div class="feature-table">
580
+
581
+ <div class="feature-table-row" id="routing">
582
+ <div class="feature-icon-wrap"><i class="fa-solid fa-route"></i></div>
583
+ <div class="feature-body">
584
+ <div class="feature-name">Expressive Router</div>
585
+ <div class="feature-desc">Route groups, prefixes, middleware chains, resource routes, and the <code>Route.auth()</code> shortcut for all auth endpoints in one line.</div>
586
+ </div>
587
+ </div>
588
+
589
+ <div class="feature-table-row" id="database">
590
+ <div class="feature-icon-wrap"><i class="fa-solid fa-database"></i></div>
591
+ <div class="feature-body">
592
+ <div class="feature-name">ORM &amp; Migrations</div>
593
+ <div class="feature-desc">Model-driven schema management. Run <code>millas makemigrations</code> to detect changes and <code>millas migrate</code> to apply them.</div>
594
+ </div>
595
+ </div>
596
+
597
+ <div class="feature-table-row" id="auth">
598
+ <div class="feature-icon-wrap"><i class="fa-solid fa-lock"></i></div>
599
+ <div class="feature-body">
600
+ <div class="feature-name">Authentication</div>
601
+ <div class="feature-desc">JWT out of the box. Register, login, token refresh, and password reset — all accessible via <code>Auth.login()</code>.</div>
602
+ </div>
603
+ </div>
604
+
605
+ <div class="feature-table-row" id="mail">
606
+ <div class="feature-icon-wrap"><i class="fa-solid fa-envelope"></i></div>
607
+ <div class="feature-body">
608
+ <div class="feature-name">Mail</div>
609
+ <div class="feature-desc">SMTP, SendGrid, and Mailgun drivers. Template engine with <code>{{ variable }}</code> interpolation, loops, and conditionals.</div>
610
+ </div>
611
+ </div>
612
+
613
+ <div class="feature-table-row" id="queue">
614
+ <div class="feature-icon-wrap"><i class="fa-solid fa-layer-group"></i></div>
615
+ <div class="feature-body">
616
+ <div class="feature-name">Queue System</div>
617
+ <div class="feature-desc">Background job processing with <code>dispatch(new Job())</code>. Database and synchronous drivers included.</div>
618
+ </div>
619
+ </div>
620
+
621
+ <div class="feature-table-row">
622
+ <div class="feature-icon-wrap"><i class="fa-solid fa-satellite-dish"></i></div>
623
+ <div class="feature-body">
624
+ <div class="feature-name">Event System</div>
625
+ <div class="feature-desc">Fire and listen to events across your application. Listeners can run inline or be pushed through the queue.</div>
626
+ </div>
627
+ </div>
628
+
629
+ <div class="feature-table-row" id="cache">
630
+ <div class="feature-icon-wrap"><i class="fa-solid fa-bolt"></i></div>
631
+ <div class="feature-body">
632
+ <div class="feature-name">Cache</div>
633
+ <div class="feature-desc">Memory, file, and null drivers. Tag-based invalidation with <code>Cache.tags('users').flush()</code>.</div>
634
+ </div>
635
+ </div>
636
+
637
+ <div class="feature-table-row" id="storage">
638
+ <div class="feature-icon-wrap"><i class="fa-solid fa-folder-open"></i></div>
639
+ <div class="feature-body">
640
+ <div class="feature-name">File Storage</div>
641
+ <div class="feature-desc">Local disk with multiple named disks. Upload, copy, move, list, and stream files.</div>
642
+ </div>
643
+ </div>
644
+
645
+ <div class="feature-table-row">
646
+ <div class="feature-icon-wrap"><i class="fa-solid fa-shield-halved"></i></div>
647
+ <div class="feature-body">
648
+ <div class="feature-name">Admin Panel</div>
649
+ <div class="feature-desc">Register any model and get a full CRUD dashboard at <a href="/admin" style="color:var(--primary)">/admin</a> automatically — no extra configuration required.</div>
650
+ </div>
651
+ </div>
652
+
378
653
  </div>
379
- </div>
380
654
 
381
- <!-- Quick start code -->
382
- <div class="code-section">
383
- <h2>Your first route</h2>
384
- <div class="code-block">
655
+ <!-- Quick start code -->
656
+ <div class="section-title">Quick start</div>
657
+ <div class="code-wrap">
385
658
  <div class="code-header">
386
- <div class="code-dots"><div class="code-dot"></div><div class="code-dot"></div><div class="code-dot"></div></div>
387
- <span class="code-filename">routes/web.js</span>
659
+ <div class="code-header-left">
660
+ <i class="fa-regular fa-file-code"></i>
661
+ <span class="code-filename">routes/web.js</span>
662
+ </div>
663
+ <span class="code-lang">JavaScript</span>
388
664
  </div>
389
665
  <pre><span class="cm">// Replace this welcome page by defining GET /</span>
390
666
  <span class="kw">module</span>.<span class="fn">exports</span> = <span class="kw">function</span> (<span class="pm">Route</span>) {
@@ -406,15 +682,16 @@ const WELCOME_PAGE = `<!DOCTYPE html>
406
682
 
407
683
  };</pre>
408
684
  </div>
409
- </div>
410
685
 
411
- <!-- CLI quick reference -->
412
- <div class="code-section">
413
- <h2>Useful CLI commands</h2>
414
- <div class="code-block">
686
+ <!-- CLI reference -->
687
+ <div class="section-title">CLI reference</div>
688
+ <div class="code-wrap">
415
689
  <div class="code-header">
416
- <div class="code-dots"><div class="code-dot"></div><div class="code-dot"></div><div class="code-dot"></div></div>
417
- <span class="code-filename">terminal</span>
690
+ <div class="code-header-left">
691
+ <i class="fa-solid fa-terminal"></i>
692
+ <span class="code-filename">terminal</span>
693
+ </div>
694
+ <span class="code-lang">Shell</span>
418
695
  </div>
419
696
  <pre><span class="cm"># Generate files</span>
420
697
  millas make:controller <span class="cl">PostController</span> --resource
@@ -431,20 +708,21 @@ millas migrate:rollback <span class="cm"># undo last batch</span>
431
708
  millas route:list <span class="cm"># show all registered routes</span>
432
709
  millas queue:work <span class="cm"># start background job worker</span></pre>
433
710
  </div>
434
- </div>
435
711
 
436
- <!-- Footer -->
437
- <footer>
438
- <div class="footer-links">
439
- <a href="/admin">Admin Panel</a>
440
- <a href="/api/health">API Health</a>
441
- <a href="https://www.npmjs.com/package/millas" target="_blank">npm</a>
442
- <a href="https://github.com/millas-framework/millas" target="_blank">GitHub</a>
712
+ <!-- Footer -->
713
+ <div class="page-footer">
714
+ <div class="page-footer-left">Millas v0.1.2 &mdash; Built on Node.js &amp; Express</div>
715
+ <div class="page-footer-links">
716
+ <a href="/admin"><i class="fa-solid fa-gauge-high"></i> Admin</a>
717
+ <a href="/api/health"><i class="fa-solid fa-heart-pulse"></i> Health</a>
718
+ <a href="https://www.npmjs.com/package/millas" target="_blank"><i class="fa-brands fa-npm"></i> npm</a>
719
+ <a href="https://github.com/millas-framework/millas" target="_blank"><i class="fa-brands fa-github"></i> GitHub</a>
720
+ </div>
443
721
  </div>
444
- <span>Built with Millas · Node.js · Express</span>
445
- </footer>
446
722
 
723
+ </main>
447
724
  </div>
725
+
448
726
  </body>
449
727
  </html>`;
450
728