@fuzionx/framework 0.1.45 → 0.1.47

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 (92) hide show
  1. package/README.md +29 -2
  2. package/cli/index.js +37 -8
  3. package/cli/templates/make/app-spa/controllers/AuthController.js +114 -0
  4. package/cli/templates/make/app-spa/controllers/HomeController.js +66 -0
  5. package/cli/templates/make/app-spa/controllers/PostController.js +191 -0
  6. package/cli/templates/make/app-spa/controllers/UserController.js +43 -0
  7. package/cli/templates/make/app-spa/public/css/style.css +1011 -0
  8. package/cli/templates/make/app-spa/routes/api.js +31 -0
  9. package/cli/templates/make/app-spa/routes/web.js +19 -0
  10. package/cli/templates/make/app-spa/services/AuthService.js +48 -0
  11. package/cli/templates/make/app-spa/services/PostService.js +372 -0
  12. package/cli/templates/make/app-spa/services/UserService.js +48 -0
  13. package/cli/templates/make/app-spa/views/default/errors/404.html +11 -0
  14. package/cli/templates/make/app-spa/views/default/errors/500.html +11 -0
  15. package/cli/templates/make/app-spa/views/default/layouts/main.html +34 -0
  16. package/cli/templates/make/app-spa/views/default/pages/home.html +22 -0
  17. package/cli/templates/make/app-spa/views/default/spa/index.html +13 -0
  18. package/cli/templates/make/app-spa/views/default/spa/package.json +20 -0
  19. package/cli/templates/make/app-spa/views/default/spa/src/App.vue +41 -0
  20. package/cli/templates/make/app-spa/views/default/spa/src/assets/landing.css +220 -0
  21. package/cli/templates/make/app-spa/views/default/spa/src/assets/style.css +1156 -0
  22. package/cli/templates/make/app-spa/views/default/spa/src/components/AlertDialog.vue +179 -0
  23. package/cli/templates/make/app-spa/views/default/spa/src/components/CodeBlock.vue +33 -0
  24. package/cli/templates/make/app-spa/views/default/spa/src/components/EditorToolbar.vue +54 -0
  25. package/cli/templates/make/app-spa/views/default/spa/src/components/FileUpload.vue +161 -0
  26. package/cli/templates/make/app-spa/views/default/spa/src/components/FlashMessage.vue +39 -0
  27. package/cli/templates/make/app-spa/views/default/spa/src/components/LanguageSwitcher.vue +108 -0
  28. package/cli/templates/make/app-spa/views/default/spa/src/components/Lightbox.vue +62 -0
  29. package/cli/templates/make/app-spa/views/default/spa/src/components/Navbar.vue +68 -0
  30. package/cli/templates/make/app-spa/views/default/spa/src/components/Pagination.vue +166 -0
  31. package/cli/templates/make/app-spa/views/default/spa/src/components/ToastContainer.vue +135 -0
  32. package/cli/templates/make/app-spa/views/default/spa/src/composables/useApi.js +129 -0
  33. package/cli/templates/make/app-spa/views/default/spa/src/composables/useClipboard.js +44 -0
  34. package/cli/templates/make/app-spa/views/default/spa/src/composables/useDate.js +73 -0
  35. package/cli/templates/make/app-spa/views/default/spa/src/composables/useDebounce.js +59 -0
  36. package/cli/templates/make/app-spa/views/default/spa/src/composables/useFlash.js +46 -0
  37. package/cli/templates/make/app-spa/views/default/spa/src/composables/useHeartbeat.js +45 -0
  38. package/cli/templates/make/app-spa/views/default/spa/src/composables/useLocalStorage.js +43 -0
  39. package/cli/templates/make/app-spa/views/default/spa/src/composables/useLocale.js +79 -0
  40. package/cli/templates/make/app-spa/views/default/spa/src/composables/useWebSocket.js +93 -0
  41. package/cli/templates/make/app-spa/views/default/spa/src/main.js +106 -0
  42. package/cli/templates/make/app-spa/views/default/spa/src/plugins/alert.js +96 -0
  43. package/cli/templates/make/app-spa/views/default/spa/src/plugins/toast.js +79 -0
  44. package/cli/templates/make/app-spa/views/default/spa/src/router/index.js +29 -0
  45. package/cli/templates/make/app-spa/views/default/spa/src/stores/auth.js +58 -0
  46. package/cli/templates/make/app-spa/views/default/spa/src/views/BoardDetail.vue +169 -0
  47. package/cli/templates/make/app-spa/views/default/spa/src/views/BoardForm.vue +192 -0
  48. package/cli/templates/make/app-spa/views/default/spa/src/views/BoardList.vue +129 -0
  49. package/cli/templates/make/app-spa/views/default/spa/src/views/ChatView.vue +317 -0
  50. package/cli/templates/make/app-spa/views/default/spa/src/views/FeaturesView.vue +242 -0
  51. package/cli/templates/make/app-spa/views/default/spa/src/views/HomeView.vue +215 -0
  52. package/cli/templates/make/app-spa/views/default/spa/src/views/Login.vue +82 -0
  53. package/cli/templates/make/app-spa/views/default/spa/src/views/Profile.vue +85 -0
  54. package/cli/templates/make/app-spa/views/default/spa/src/views/Register.vue +84 -0
  55. package/cli/templates/make/app-spa/views/default/spa/vite.config.js +28 -0
  56. package/cli/templates/make/app-spa/views/default/spa/yarn.lock +633 -0
  57. package/cli/templates/make/app-spa/ws/ChatHandler.js +138 -0
  58. package/cli/templates/make/app-ssr/controllers/AuthController.js +119 -0
  59. package/cli/templates/make/app-ssr/controllers/ChatController.js +15 -0
  60. package/cli/templates/make/app-ssr/controllers/FeaturesController.js +15 -0
  61. package/cli/templates/make/app-ssr/controllers/HomeController.js +21 -0
  62. package/cli/templates/make/app-ssr/controllers/PostController.js +214 -0
  63. package/cli/templates/make/app-ssr/controllers/UserController.js +48 -0
  64. package/cli/templates/make/app-ssr/public/css/fx-ui.css +43 -0
  65. package/cli/templates/make/app-ssr/public/css/landing.css +220 -0
  66. package/cli/templates/make/app-ssr/public/css/style.css +1011 -0
  67. package/cli/templates/make/app-ssr/public/js/fx-client.js +107 -0
  68. package/cli/templates/make/app-ssr/public/js/fx-ui.js +124 -0
  69. package/cli/templates/make/app-ssr/routes/web.js +46 -0
  70. package/cli/templates/make/app-ssr/services/AuthService.js +48 -0
  71. package/cli/templates/make/app-ssr/services/PostService.js +372 -0
  72. package/cli/templates/make/app-ssr/services/UserService.js +48 -0
  73. package/cli/templates/make/app-ssr/views/default/errors/404.html +11 -0
  74. package/cli/templates/make/app-ssr/views/default/errors/500.html +48 -0
  75. package/cli/templates/make/app-ssr/views/default/layouts/main.html +96 -0
  76. package/cli/templates/make/app-ssr/views/default/pages/board/form.html +240 -0
  77. package/cli/templates/make/app-ssr/views/default/pages/board/index.html +73 -0
  78. package/cli/templates/make/app-ssr/views/default/pages/board/show.html +148 -0
  79. package/cli/templates/make/app-ssr/views/default/pages/chat.html +288 -0
  80. package/cli/templates/make/app-ssr/views/default/pages/features.html +373 -0
  81. package/cli/templates/make/app-ssr/views/default/pages/home.html +258 -0
  82. package/cli/templates/make/app-ssr/views/default/pages/login.html +27 -0
  83. package/cli/templates/make/app-ssr/views/default/pages/profile.html +36 -0
  84. package/cli/templates/make/app-ssr/views/default/pages/register.html +35 -0
  85. package/cli/templates/make/app-ssr/views/default/partials/pagination.html +75 -0
  86. package/cli/templates/make/app-ssr/ws/ChatHandler.js +138 -0
  87. package/lib/core/Application.js +425 -138
  88. package/lib/core/Context.js +540 -236
  89. package/lib/middleware/auth.js +1 -1
  90. package/lib/middleware/csrf.js +1 -1
  91. package/lib/middleware/session.js +5 -4
  92. package/package.json +2 -2
@@ -0,0 +1,1011 @@
1
+ /* ═══════════════════════════════════════════════
2
+ * FuzionX — Glassmorphism Dark Theme
3
+ * ═══════════════════════════════════════════════ */
4
+
5
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
6
+
7
+ /* ── Reset ───────────────────────────────────── */
8
+ *, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
9
+
10
+ /* ── Root Variables ──────────────────────────── */
11
+ :root {
12
+ --bg-gradient: linear-gradient(135deg, #0f0c29 0%, #1a1a3e 40%, #24243e 100%);
13
+ --glass-bg: rgba(255, 255, 255, 0.05);
14
+ --glass-border: rgba(255, 255, 255, 0.1);
15
+ --glass-blur: blur(20px);
16
+ --text-primary: #e0e0e0;
17
+ --text-secondary: rgba(255, 255, 255, 0.5);
18
+ --text-muted: rgba(255, 255, 255, 0.3);
19
+ --accent-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%);
20
+ --accent-solid: #667eea;
21
+ --accent-hover: #7b93ff;
22
+ --danger: #e74c3c;
23
+ --success: #2ecc71;
24
+ --radius: 16px;
25
+ --radius-sm: 8px;
26
+ --font: 'Inter', system-ui, -apple-system, sans-serif;
27
+ --nav-height: 64px;
28
+ --transition: all 0.3s ease;
29
+ }
30
+
31
+ /* ── Body ────────────────────────────────────── */
32
+ body {
33
+ font-family: var(--font);
34
+ min-height: 100vh;
35
+ background: var(--bg-gradient);
36
+ color: var(--text-primary);
37
+ line-height: 1.6;
38
+ overflow-x: hidden;
39
+ }
40
+
41
+ /* ── Background Orbs ─────────────────────────── */
42
+ .orb {
43
+ position: fixed;
44
+ border-radius: 50%;
45
+ filter: blur(80px);
46
+ opacity: 0.3;
47
+ animation: float 8s ease-in-out infinite;
48
+ pointer-events: none;
49
+ z-index: 0;
50
+ }
51
+ .orb-1 { width: 400px; height: 400px; background: #667eea; top: -100px; right: -100px; }
52
+ .orb-2 { width: 300px; height: 300px; background: #764ba2; bottom: -80px; left: -80px; animation-delay: -4s; }
53
+ .orb-3 { width: 200px; height: 200px; background: #f093fb; top: 50%; left: 60%; animation-delay: -2s; }
54
+
55
+ @keyframes float {
56
+ 0%, 100% { transform: translate(0, 0); }
57
+ 50% { transform: translate(30px, -30px); }
58
+ }
59
+
60
+ /* ── Navbar ──────────────────────────────────── */
61
+ .navbar {
62
+ position: fixed;
63
+ top: 0;
64
+ left: 0;
65
+ right: 0;
66
+ height: var(--nav-height);
67
+ background: rgba(15, 12, 41, 0.6);
68
+ backdrop-filter: var(--glass-blur);
69
+ -webkit-backdrop-filter: var(--glass-blur);
70
+ border-bottom: 1px solid var(--glass-border);
71
+ z-index: 100;
72
+ }
73
+
74
+ .nav-container {
75
+ max-width: 1200px;
76
+ margin: 0 auto;
77
+ height: 100%;
78
+ display: flex;
79
+ align-items: center;
80
+ justify-content: space-between;
81
+ padding: 0 2rem;
82
+ }
83
+
84
+ .nav-brand {
85
+ font-size: 1.5rem;
86
+ font-weight: 800;
87
+ background: var(--accent-gradient);
88
+ -webkit-background-clip: text;
89
+ background-clip: text;
90
+ -webkit-text-fill-color: transparent;
91
+ text-decoration: none;
92
+ letter-spacing: -1px;
93
+ }
94
+
95
+ .nav-links {
96
+ display: flex;
97
+ align-items: center;
98
+ gap: 0.5rem;
99
+ }
100
+
101
+ .nav-link {
102
+ color: var(--text-secondary);
103
+ text-decoration: none;
104
+ font-size: 0.9rem;
105
+ padding: 8px 16px;
106
+ border-radius: var(--radius-sm);
107
+ transition: var(--transition);
108
+ border: none;
109
+ background: none;
110
+ cursor: pointer;
111
+ font-family: var(--font);
112
+ }
113
+
114
+ .nav-link:hover,
115
+ .nav-link.router-link-active {
116
+ color: #fff;
117
+ background: rgba(102, 126, 234, 0.15);
118
+ }
119
+
120
+ .nav-logout { display: inline; }
121
+
122
+ .btn-link {
123
+ border: none;
124
+ background: none;
125
+ cursor: pointer;
126
+ font-family: var(--font);
127
+ }
128
+
129
+ /* ── Main Content ────────────────────────────── */
130
+ .main-content {
131
+ min-height: 100vh;
132
+ position: relative;
133
+ z-index: 1;
134
+ }
135
+
136
+ .main-content.has-nav {
137
+ padding-top: calc(var(--nav-height) + 2rem);
138
+ }
139
+
140
+ .container {
141
+ max-width: 1140px;
142
+ margin: 0 auto;
143
+ padding: 2rem;
144
+ }
145
+
146
+ /* ── Glass Card ──────────────────────────────── */
147
+ .glass-card {
148
+ background: var(--glass-bg);
149
+ backdrop-filter: var(--glass-blur);
150
+ -webkit-backdrop-filter: var(--glass-blur);
151
+ border: 1px solid var(--glass-border);
152
+ border-radius: var(--radius);
153
+ padding: 2rem;
154
+ animation: fadeInUp 0.5s ease-out;
155
+ }
156
+
157
+ @keyframes fadeInUp {
158
+ from { opacity: 0; transform: translateY(20px); }
159
+ to { opacity: 1; transform: translateY(0); }
160
+ }
161
+
162
+ /* ── Auth Pages ──────────────────────────────── */
163
+ .auth-container {
164
+ min-height: 100vh;
165
+ display: flex;
166
+ align-items: center;
167
+ justify-content: center;
168
+ position: relative;
169
+ z-index: 1;
170
+ }
171
+
172
+ .auth-card {
173
+ width: 100%;
174
+ max-width: 420px;
175
+ text-align: center;
176
+ margin: 1rem;
177
+ }
178
+
179
+ .auth-logo {
180
+ font-size: 2.5rem;
181
+ font-weight: 800;
182
+ background: var(--accent-gradient);
183
+ -webkit-background-clip: text;
184
+ background-clip: text;
185
+ -webkit-text-fill-color: transparent;
186
+ letter-spacing: -2px;
187
+ margin-bottom: 0.5rem;
188
+ }
189
+
190
+ .auth-subtitle {
191
+ font-size: 1rem;
192
+ color: var(--text-secondary);
193
+ margin-bottom: 2rem;
194
+ }
195
+
196
+ .auth-form { text-align: left; }
197
+
198
+ .auth-footer {
199
+ margin-top: 1.5rem;
200
+ font-size: 0.85rem;
201
+ color: var(--text-secondary);
202
+ }
203
+
204
+ .auth-footer a {
205
+ color: var(--accent-solid);
206
+ text-decoration: none;
207
+ }
208
+
209
+ .auth-footer a:hover {
210
+ text-decoration: underline;
211
+ }
212
+
213
+ /* ── Forms ────────────────────────────────────── */
214
+ .form-group {
215
+ margin-bottom: 1.25rem;
216
+ }
217
+
218
+ .form-group label {
219
+ display: block;
220
+ font-size: 0.85rem;
221
+ font-weight: 500;
222
+ color: var(--text-secondary);
223
+ margin-bottom: 0.4rem;
224
+ }
225
+
226
+ .form-group input,
227
+ .form-group textarea,
228
+ .form-group select {
229
+ width: 100%;
230
+ padding: 10px 14px;
231
+ background: rgba(255, 255, 255, 0.08);
232
+ border: 1px solid var(--glass-border);
233
+ border-radius: var(--radius-sm);
234
+ color: var(--text-primary);
235
+ font-family: var(--font);
236
+ font-size: 0.95rem;
237
+ transition: var(--transition);
238
+ outline: none;
239
+ }
240
+
241
+ .form-group input:focus,
242
+ .form-group textarea:focus {
243
+ border-color: var(--accent-solid);
244
+ background: rgba(255, 255, 255, 0.12);
245
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.15);
246
+ }
247
+
248
+ .form-group input::placeholder,
249
+ .form-group textarea::placeholder {
250
+ color: var(--text-muted);
251
+ }
252
+
253
+ .form-actions {
254
+ display: flex;
255
+ gap: 0.75rem;
256
+ margin-top: 1.5rem;
257
+ }
258
+
259
+ /* ── Buttons ─────────────────────────────────── */
260
+ .btn {
261
+ display: inline-block;
262
+ padding: 10px 24px;
263
+ border-radius: var(--radius-sm);
264
+ font-family: var(--font);
265
+ font-size: 0.9rem;
266
+ font-weight: 600;
267
+ border: none;
268
+ cursor: pointer;
269
+ text-decoration: none;
270
+ transition: var(--transition);
271
+ text-align: center;
272
+ }
273
+
274
+ .btn-primary {
275
+ background: linear-gradient(135deg, #667eea, #764ba2);
276
+ color: #fff;
277
+ }
278
+
279
+ .btn-primary:hover {
280
+ transform: translateY(-2px);
281
+ box-shadow: 0 8px 20px rgba(102, 126, 234, 0.25);
282
+ }
283
+
284
+ .btn-primary:disabled {
285
+ opacity: 0.5;
286
+ cursor: not-allowed;
287
+ transform: none;
288
+ }
289
+
290
+ .btn-full { width: 100%; }
291
+
292
+ .btn-outline {
293
+ background: transparent;
294
+ border: 1px solid var(--glass-border);
295
+ color: var(--text-primary);
296
+ }
297
+
298
+ .btn-outline:hover {
299
+ border-color: var(--accent-solid);
300
+ color: #fff;
301
+ background: rgba(102, 126, 234, 0.1);
302
+ }
303
+
304
+ .btn-danger {
305
+ background: var(--danger);
306
+ color: #fff;
307
+ }
308
+
309
+ .btn-danger:hover {
310
+ background: #c0392b;
311
+ transform: translateY(-2px);
312
+ }
313
+
314
+ /* ── Alert ────────────────────────────────────── */
315
+ .alert {
316
+ padding: 10px 16px;
317
+ border-radius: var(--radius-sm);
318
+ margin-bottom: 1.25rem;
319
+ font-size: 0.9rem;
320
+ }
321
+
322
+ .alert-error {
323
+ background: rgba(231, 76, 60, 0.15);
324
+ border: 1px solid rgba(231, 76, 60, 0.3);
325
+ color: #e74c3c;
326
+ }
327
+
328
+ .alert-success {
329
+ background: rgba(46, 204, 113, 0.15);
330
+ border: 1px solid rgba(46, 204, 113, 0.3);
331
+ color: #2ecc71;
332
+ }
333
+
334
+ /* ── Page Header ─────────────────────────────── */
335
+ .page-header {
336
+ display: flex;
337
+ align-items: center;
338
+ justify-content: space-between;
339
+ margin-bottom: 1.5rem;
340
+ }
341
+
342
+ .page-title {
343
+ font-size: 1.5rem;
344
+ font-weight: 700;
345
+ letter-spacing: -0.5px;
346
+ }
347
+
348
+ .page-subtitle {
349
+ color: var(--text-secondary);
350
+ margin-top: 0.5rem;
351
+ }
352
+
353
+ .page-actions {
354
+ display: flex;
355
+ gap: 0.5rem;
356
+ }
357
+
358
+ /* ── Stats Grid (Dashboard) ──────────────────── */
359
+ .stats-grid {
360
+ display: grid;
361
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
362
+ gap: 1rem;
363
+ margin-top: 2rem;
364
+ }
365
+
366
+ .stat-card {
367
+ display: flex;
368
+ align-items: center;
369
+ gap: 1rem;
370
+ padding: 1.25rem;
371
+ background: rgba(255, 255, 255, 0.03);
372
+ border: 1px solid var(--glass-border);
373
+ border-radius: var(--radius);
374
+ transition: var(--transition);
375
+ text-decoration: none;
376
+ color: inherit;
377
+ position: relative;
378
+ }
379
+
380
+ .stat-card:hover {
381
+ background: rgba(102, 126, 234, 0.08);
382
+ border-color: rgba(102, 126, 234, 0.3);
383
+ transform: translateY(-2px);
384
+ }
385
+
386
+ .stat-icon { font-size: 2rem; }
387
+ .stat-label { display: block; font-size: 0.8rem; color: var(--text-secondary); }
388
+ .stat-value { display: block; font-size: 1.1rem; font-weight: 600; margin-top: 0.25rem; }
389
+ .stat-link { position: absolute; inset: 0; }
390
+
391
+ /* ── Table ────────────────────────────────────── */
392
+ .table {
393
+ width: 100%;
394
+ border-collapse: collapse;
395
+ }
396
+
397
+ .table th {
398
+ text-align: left;
399
+ font-size: 0.8rem;
400
+ font-weight: 600;
401
+ text-transform: uppercase;
402
+ letter-spacing: 0.5px;
403
+ color: var(--text-secondary);
404
+ padding: 12px 16px;
405
+ border-bottom: 1px solid var(--glass-border);
406
+ }
407
+
408
+ .table td {
409
+ padding: 12px 16px;
410
+ border-bottom: 1px solid rgba(255, 255, 255, 0.05);
411
+ font-size: 0.9rem;
412
+ }
413
+
414
+ .table tr:hover td {
415
+ background: rgba(255, 255, 255, 0.02);
416
+ }
417
+
418
+ .table-link {
419
+ color: var(--accent-hover);
420
+ text-decoration: none;
421
+ }
422
+
423
+ .table-link:hover {
424
+ text-decoration: underline;
425
+ }
426
+
427
+ .table-empty {
428
+ text-align: center;
429
+ color: var(--text-muted);
430
+ padding: 2rem !important;
431
+ }
432
+
433
+ /* ── Pagination ──────────────────────────────── */
434
+ .pagination {
435
+ display: flex;
436
+ align-items: center;
437
+ justify-content: center;
438
+ gap: 4px;
439
+ margin-top: 1.5rem;
440
+ flex-wrap: wrap;
441
+ }
442
+
443
+ .page-btn {
444
+ display: inline-flex;
445
+ align-items: center;
446
+ justify-content: center;
447
+ min-width: 36px;
448
+ height: 36px;
449
+ padding: 0 8px;
450
+ border: 1px solid var(--glass-border);
451
+ border-radius: 8px;
452
+ background: rgba(255,255,255,0.05);
453
+ color: var(--text-secondary);
454
+ font-size: 0.85rem;
455
+ font-weight: 500;
456
+ cursor: pointer;
457
+ transition: all 0.2s;
458
+ font-family: var(--font);
459
+ text-decoration: none;
460
+ }
461
+
462
+ .page-btn:hover:not(.disabled):not(.active) {
463
+ background: rgba(255,255,255,0.12);
464
+ color: #fff;
465
+ border-color: rgba(102,126,234,0.4);
466
+ }
467
+
468
+ .page-btn.disabled {
469
+ opacity: 0.35;
470
+ cursor: not-allowed;
471
+ pointer-events: none;
472
+ }
473
+
474
+ .page-btn.active {
475
+ background: linear-gradient(135deg, #667eea, #764ba2);
476
+ color: #fff;
477
+ border-color: transparent;
478
+ font-weight: 700;
479
+ box-shadow: 0 2px 8px rgba(102,126,234,0.3);
480
+ }
481
+
482
+ .page-ellipsis {
483
+ display: inline-flex;
484
+ align-items: center;
485
+ justify-content: center;
486
+ width: 36px;
487
+ height: 36px;
488
+ color: var(--text-muted);
489
+ font-size: 0.9rem;
490
+ user-select: none;
491
+ }
492
+
493
+ .page-info-mobile {
494
+ display: none;
495
+ }
496
+
497
+ /* Legacy support */
498
+ .page-link {
499
+ padding: 6px 16px;
500
+ border-radius: var(--radius-sm);
501
+ border: 1px solid var(--glass-border);
502
+ background: transparent;
503
+ color: var(--text-primary);
504
+ cursor: pointer;
505
+ font-family: var(--font);
506
+ font-size: 0.85rem;
507
+ transition: var(--transition);
508
+ text-decoration: none;
509
+ }
510
+
511
+ .page-link:hover:not(:disabled) {
512
+ border-color: var(--accent-solid);
513
+ color: #fff;
514
+ }
515
+
516
+ .page-info {
517
+ font-size: 0.85rem;
518
+ color: var(--text-secondary);
519
+ }
520
+
521
+ @media (max-width: 480px) {
522
+ .page-num, .page-ellipsis { display: none; }
523
+ .page-info-mobile {
524
+ display: inline-flex;
525
+ font-size: 0.85rem;
526
+ color: var(--text-secondary);
527
+ padding: 0 0.5rem;
528
+ }
529
+ }
530
+
531
+ /* ── Post Detail ─────────────────────────────── */
532
+ .post-meta {
533
+ display: flex;
534
+ gap: 1rem;
535
+ font-size: 0.85rem;
536
+ color: var(--text-secondary);
537
+ margin-bottom: 1.5rem;
538
+ padding-bottom: 1rem;
539
+ border-bottom: 1px solid var(--glass-border);
540
+ }
541
+
542
+ .post-content {
543
+ white-space: pre-wrap;
544
+ line-height: 1.8;
545
+ margin-bottom: 2rem;
546
+ }
547
+
548
+ .post-footer {
549
+ border-top: 1px solid var(--glass-border);
550
+ padding-top: 1rem;
551
+ }
552
+
553
+ /* ── Responsive ──────────────────────────────── */
554
+ @media (max-width: 768px) {
555
+ .container { padding: 1rem; }
556
+ .glass-card { padding: 1.5rem; }
557
+ .nav-container { padding: 0 1rem; }
558
+ .page-header { flex-direction: column; align-items: flex-start; gap: 1rem; }
559
+ .stats-grid { grid-template-columns: 1fr; }
560
+ .table { font-size: 0.85rem; }
561
+ .table th, .table td { padding: 8px 10px; }
562
+ }
563
+
564
+ @media (max-width: 480px) {
565
+ .auth-card { margin: 0.5rem; padding: 1.5rem 1rem; }
566
+ .auth-logo { font-size: 2rem; }
567
+ .nav-links { gap: 0.25rem; }
568
+ .nav-link { padding: 6px 10px; font-size: 0.8rem; }
569
+ }
570
+
571
+ /* ── 뱃지 ── */
572
+ .badge {
573
+ display: inline-block;
574
+ padding: 2px 8px;
575
+ border-radius: 12px;
576
+ font-size: 0.7rem;
577
+ font-weight: 600;
578
+ vertical-align: middle;
579
+ }
580
+ .badge-processing {
581
+ background: rgba(241, 196, 15, 0.2);
582
+ color: #f1c40f;
583
+ animation: pulse-badge 2s infinite;
584
+ }
585
+ @keyframes pulse-badge {
586
+ 0%, 100% { opacity: 1; }
587
+ 50% { opacity: 0.5; }
588
+ }
589
+
590
+ /* ── 업로드 진행률 ── */
591
+ .upload-progress {
592
+ margin: 1rem 0;
593
+ }
594
+ .progress-bar-track {
595
+ background: rgba(255,255,255,0.1);
596
+ border-radius: 8px;
597
+ height: 12px;
598
+ overflow: hidden;
599
+ }
600
+ .progress-bar-fill {
601
+ height: 100%;
602
+ width: 0;
603
+ background: linear-gradient(90deg, #667eea, #764ba2);
604
+ border-radius: 8px;
605
+ transition: width 0.3s ease;
606
+ }
607
+ .progress-info {
608
+ display: flex;
609
+ justify-content: space-between;
610
+ margin-top: 4px;
611
+ font-size: 0.8rem;
612
+ color: var(--text-secondary, #aaa);
613
+ }
614
+
615
+ /* ── 인라인 비디오 ── */
616
+ .inline-video {
617
+ margin: 1.5rem 0;
618
+ }
619
+ .inline-video video {
620
+ background: #000;
621
+ }
622
+ .inline-video-name {
623
+ font-size: 0.85rem;
624
+ color: var(--text-secondary, #aaa);
625
+ margin-top: 0.5rem;
626
+ }
627
+
628
+ /* ── 대시보드 ── */
629
+ .dashboard-card { padding: 2rem; }
630
+ .dashboard-section {
631
+ margin-top: 2.5rem;
632
+ }
633
+ .dashboard-section h2 {
634
+ font-size: 1.3rem;
635
+ margin-bottom: 1rem;
636
+ color: var(--text-primary, #e0e0e0);
637
+ }
638
+ .stat-info {
639
+ display: flex;
640
+ flex-direction: column;
641
+ }
642
+ .feature-grid {
643
+ display: grid;
644
+ grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
645
+ gap: 1rem;
646
+ }
647
+ .feature-card {
648
+ background: var(--glass-bg, rgba(255,255,255,0.05));
649
+ border: 1px solid var(--glass-border, rgba(255,255,255,0.1));
650
+ border-radius: 12px;
651
+ padding: 1.25rem;
652
+ transition: transform 0.2s, box-shadow 0.2s;
653
+ }
654
+ .feature-card:hover {
655
+ transform: translateY(-2px);
656
+ box-shadow: 0 8px 24px rgba(0,0,0,0.3);
657
+ }
658
+ .feature-card h3 {
659
+ font-size: 1rem;
660
+ margin-bottom: 0.5rem;
661
+ color: var(--text-primary, #e0e0e0);
662
+ }
663
+ .feature-card p {
664
+ font-size: 0.85rem;
665
+ color: var(--text-secondary, rgba(255,255,255,0.5));
666
+ line-height: 1.5;
667
+ }
668
+ .code-example {
669
+ background: rgba(0,0,0,0.4);
670
+ border: 1px solid rgba(255,255,255,0.08);
671
+ border-radius: 8px;
672
+ padding: 0.75rem 1rem;
673
+ margin-top: 0.75rem;
674
+ font-family: 'Fira Code', 'Consolas', monospace;
675
+ font-size: 0.8rem;
676
+ color: #a0cfff;
677
+ white-space: pre-wrap;
678
+ overflow-x: auto;
679
+ }
680
+ .badge-rust { background: rgba(222,110,75,0.2); color: #de6e4b; }
681
+ .badge-node { background: rgba(104,159,56,0.2); color: #68a038; }
682
+
683
+ /* ── 게시판 목록 ── */
684
+ .post-list {
685
+ display: flex;
686
+ flex-direction: column;
687
+ gap: 0.75rem;
688
+ }
689
+ .post-list-empty {
690
+ text-align: center;
691
+ padding: 3rem 1rem;
692
+ color: var(--text-muted, rgba(255,255,255,0.3));
693
+ }
694
+ .post-card {
695
+ display: flex;
696
+ gap: 1rem;
697
+ padding: 1rem 1.25rem;
698
+ background: var(--glass-bg, rgba(255,255,255,0.05));
699
+ border: 1px solid var(--glass-border, rgba(255,255,255,0.1));
700
+ border-radius: 12px;
701
+ text-decoration: none;
702
+ color: inherit;
703
+ transition: transform 0.15s, background 0.15s;
704
+ }
705
+ .post-card:hover {
706
+ transform: translateX(4px);
707
+ background: rgba(255,255,255,0.08);
708
+ }
709
+ .post-card-thumb {
710
+ width: 80px;
711
+ height: 60px;
712
+ border-radius: 8px;
713
+ overflow: hidden;
714
+ flex-shrink: 0;
715
+ background: rgba(0,0,0,0.3);
716
+ }
717
+ .post-card-thumb img {
718
+ width: 100%;
719
+ height: 100%;
720
+ object-fit: cover;
721
+ }
722
+ .post-card-body {
723
+ flex: 1;
724
+ min-width: 0;
725
+ }
726
+ .post-card-title {
727
+ font-weight: 600;
728
+ font-size: 1rem;
729
+ color: var(--text-primary, #e0e0e0);
730
+ white-space: nowrap;
731
+ overflow: hidden;
732
+ text-overflow: ellipsis;
733
+ }
734
+ .post-card-meta {
735
+ font-size: 0.8rem;
736
+ color: var(--text-muted, rgba(255,255,255,0.3));
737
+ margin-top: 4px;
738
+ }
739
+
740
+ /* ── 첨부파일 그리드 (보기 페이지) ── */
741
+ .post-attachments {
742
+ margin-top: 2rem;
743
+ padding-top: 1.5rem;
744
+ border-top: 1px solid var(--glass-border, rgba(255,255,255,0.1));
745
+ }
746
+ .post-attachments h3 {
747
+ margin-bottom: 1rem;
748
+ font-size: 1rem;
749
+ }
750
+ .attachment-grid {
751
+ display: grid;
752
+ grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
753
+ gap: 0.75rem;
754
+ }
755
+ .attachment-card {
756
+ background: var(--glass-bg, rgba(255,255,255,0.05));
757
+ border: 1px solid var(--glass-border, rgba(255,255,255,0.1));
758
+ border-radius: 10px;
759
+ overflow: hidden;
760
+ transition: transform 0.15s;
761
+ }
762
+ .attachment-card:hover { transform: translateY(-2px); }
763
+ .attachment-card a {
764
+ text-decoration: none;
765
+ color: inherit;
766
+ display: block;
767
+ }
768
+ .attachment-thumb {
769
+ width: 100%;
770
+ height: 100px;
771
+ overflow: hidden;
772
+ background: rgba(0,0,0,0.3);
773
+ display: flex;
774
+ align-items: center;
775
+ justify-content: center;
776
+ position: relative;
777
+ }
778
+ .attachment-thumb img {
779
+ width: 100%;
780
+ height: 100%;
781
+ object-fit: cover;
782
+ }
783
+ .attachment-info {
784
+ padding: 0.5rem 0.6rem;
785
+ }
786
+ .attachment-name {
787
+ font-size: 0.75rem;
788
+ color: var(--text-secondary, rgba(255,255,255,0.5));
789
+ display: block;
790
+ overflow: hidden;
791
+ text-overflow: ellipsis;
792
+ white-space: nowrap;
793
+ }
794
+ .attachment-download {
795
+ text-decoration: none;
796
+ color: inherit;
797
+ display: block;
798
+ }
799
+ .thumb-placeholder {
800
+ font-size: 2rem;
801
+ opacity: 0.5;
802
+ }
803
+ .play-overlay {
804
+ position: absolute;
805
+ top: 50%;
806
+ left: 50%;
807
+ transform: translate(-50%, -50%);
808
+ font-size: 2rem;
809
+ background: rgba(0,0,0,0.5);
810
+ border-radius: 50%;
811
+ width: 40px;
812
+ height: 40px;
813
+ display: flex;
814
+ align-items: center;
815
+ justify-content: center;
816
+ color: #fff;
817
+ pointer-events: none;
818
+ }
819
+
820
+ /* ── 에디터 툴바 ── */
821
+ .editor-toolbar {
822
+ display: flex;
823
+ gap: 4px;
824
+ padding: 6px 8px;
825
+ background: rgba(0,0,0,0.3);
826
+ border: 1px solid var(--glass-border, rgba(255,255,255,0.1));
827
+ border-bottom: none;
828
+ border-radius: 8px 8px 0 0;
829
+ }
830
+ .editor-toolbar button {
831
+ background: transparent;
832
+ border: 1px solid transparent;
833
+ color: var(--text-secondary, rgba(255,255,255,0.5));
834
+ border-radius: 4px;
835
+ padding: 4px 8px;
836
+ cursor: pointer;
837
+ font-size: 0.85rem;
838
+ transition: background 0.15s, color 0.15s;
839
+ }
840
+ .editor-toolbar button:hover {
841
+ background: rgba(255,255,255,0.1);
842
+ color: var(--text-primary, #e0e0e0);
843
+ }
844
+ .editor-toolbar .separator {
845
+ width: 1px;
846
+ background: var(--glass-border, rgba(255,255,255,0.1));
847
+ margin: 2px 4px;
848
+ }
849
+ textarea.has-toolbar {
850
+ border-radius: 0 0 8px 8px;
851
+ }
852
+
853
+ /* ── 파일 업로드 영역 ── */
854
+ .file-upload-area {
855
+ border: 2px dashed var(--glass-border, rgba(255,255,255,0.15));
856
+ border-radius: 12px;
857
+ padding: 2rem;
858
+ text-align: center;
859
+ cursor: pointer;
860
+ transition: border-color 0.2s, background 0.2s;
861
+ }
862
+ .file-upload-area:hover,
863
+ .file-upload-area.dragover {
864
+ border-color: var(--accent-solid, #667eea);
865
+ background: rgba(102,126,234,0.05);
866
+ }
867
+ .upload-icon { font-size: 2rem; margin-bottom: 0.5rem; }
868
+ .upload-text {
869
+ font-size: 0.9rem;
870
+ color: var(--text-primary, #e0e0e0);
871
+ margin-bottom: 0.25rem;
872
+ }
873
+ .upload-hint {
874
+ font-size: 0.75rem;
875
+ color: var(--text-muted, rgba(255,255,255,0.3));
876
+ }
877
+ .file-list {
878
+ margin-top: 0.75rem;
879
+ display: flex;
880
+ flex-direction: column;
881
+ gap: 0.5rem;
882
+ }
883
+ .file-item {
884
+ display: flex;
885
+ align-items: center;
886
+ gap: 0.75rem;
887
+ padding: 0.5rem 0.75rem;
888
+ background: var(--glass-bg, rgba(255,255,255,0.05));
889
+ border: 1px solid var(--glass-border, rgba(255,255,255,0.1));
890
+ border-radius: 8px;
891
+ }
892
+ .file-preview {
893
+ width: 40px;
894
+ height: 40px;
895
+ object-fit: cover;
896
+ border-radius: 6px;
897
+ }
898
+ .file-preview-icon {
899
+ font-size: 1.5rem;
900
+ width: 40px;
901
+ text-align: center;
902
+ }
903
+ .file-name {
904
+ flex: 1;
905
+ font-size: 0.85rem;
906
+ overflow: hidden;
907
+ text-overflow: ellipsis;
908
+ white-space: nowrap;
909
+ }
910
+ .file-size {
911
+ font-size: 0.75rem;
912
+ color: var(--text-muted, rgba(255,255,255,0.3));
913
+ }
914
+ .file-remove {
915
+ background: transparent;
916
+ border: none;
917
+ color: var(--text-muted, rgba(255,255,255,0.3));
918
+ cursor: pointer;
919
+ font-size: 1rem;
920
+ padding: 2px 6px;
921
+ border-radius: 4px;
922
+ transition: color 0.15s;
923
+ }
924
+ .file-remove:hover { color: #e74c3c; }
925
+
926
+ /* ── 기존 첨부파일 그리드 (수정 폼) ── */
927
+ .existing-files-grid {
928
+ display: grid;
929
+ grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
930
+ gap: 0.75rem;
931
+ }
932
+ .existing-file-card {
933
+ background: var(--glass-bg, rgba(255,255,255,0.05));
934
+ border: 1px solid var(--glass-border, rgba(255,255,255,0.1));
935
+ border-radius: 10px;
936
+ overflow: hidden;
937
+ }
938
+ .existing-file-thumb {
939
+ width: 100%;
940
+ height: 80px;
941
+ overflow: hidden;
942
+ background: rgba(0,0,0,0.3);
943
+ display: flex;
944
+ align-items: center;
945
+ justify-content: center;
946
+ }
947
+ .existing-file-thumb img {
948
+ width: 100%;
949
+ height: 100%;
950
+ object-fit: cover;
951
+ }
952
+ .existing-file-info {
953
+ display: flex;
954
+ align-items: center;
955
+ justify-content: space-between;
956
+ padding: 0.4rem 0.5rem;
957
+ }
958
+
959
+ /* ── Lightbox 모달 ── */
960
+ .lightbox-overlay {
961
+ position: fixed;
962
+ top: 0; left: 0; right: 0; bottom: 0;
963
+ background: rgba(0,0,0,0.85);
964
+ z-index: 9999;
965
+ align-items: center;
966
+ justify-content: center;
967
+ }
968
+ .lightbox-content {
969
+ position: relative;
970
+ max-width: 90vw;
971
+ max-height: 90vh;
972
+ }
973
+ .lightbox-close {
974
+ position: absolute;
975
+ top: -40px;
976
+ right: 0;
977
+ background: transparent;
978
+ border: none;
979
+ color: #fff;
980
+ font-size: 1.5rem;
981
+ cursor: pointer;
982
+ z-index: 10000;
983
+ }
984
+ .lightbox-trigger {
985
+ cursor: pointer;
986
+ }
987
+
988
+ /* ── 에러 페이지 ── */
989
+ .error-message {
990
+ font-size: 1.2rem;
991
+ color: #e74c3c;
992
+ margin-bottom: 1rem;
993
+ }
994
+ .error-detail {
995
+ font-size: 0.9rem;
996
+ color: var(--text-secondary, rgba(255,255,255,0.5));
997
+ margin-bottom: 1rem;
998
+ }
999
+ .error-stack {
1000
+ background: rgba(0,0,0,0.4);
1001
+ border: 1px solid rgba(255,255,255,0.08);
1002
+ border-radius: 8px;
1003
+ padding: 1rem;
1004
+ font-family: monospace;
1005
+ font-size: 0.8rem;
1006
+ color: #a0cfff;
1007
+ white-space: pre-wrap;
1008
+ overflow-x: auto;
1009
+ max-height: 400px;
1010
+ overflow-y: auto;
1011
+ }