active_canvas 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +318 -0
  4. data/Rakefile +6 -0
  5. data/app/assets/javascripts/active_canvas/editor/ai_panel.js +1607 -0
  6. data/app/assets/javascripts/active_canvas/editor/asset_manager.js +498 -0
  7. data/app/assets/javascripts/active_canvas/editor/blocks.js +1083 -0
  8. data/app/assets/javascripts/active_canvas/editor/code_panel.js +572 -0
  9. data/app/assets/javascripts/active_canvas/editor/component_toolbar.js +394 -0
  10. data/app/assets/javascripts/active_canvas/editor/panels.js +460 -0
  11. data/app/assets/javascripts/active_canvas/editor/utils.js +56 -0
  12. data/app/assets/javascripts/active_canvas/editor.js +295 -0
  13. data/app/assets/stylesheets/active_canvas/application.css +15 -0
  14. data/app/assets/stylesheets/active_canvas/editor.css +2929 -0
  15. data/app/controllers/active_canvas/admin/ai_controller.rb +181 -0
  16. data/app/controllers/active_canvas/admin/application_controller.rb +56 -0
  17. data/app/controllers/active_canvas/admin/media_controller.rb +61 -0
  18. data/app/controllers/active_canvas/admin/page_types_controller.rb +57 -0
  19. data/app/controllers/active_canvas/admin/page_versions_controller.rb +23 -0
  20. data/app/controllers/active_canvas/admin/pages_controller.rb +133 -0
  21. data/app/controllers/active_canvas/admin/partials_controller.rb +88 -0
  22. data/app/controllers/active_canvas/admin/settings_controller.rb +256 -0
  23. data/app/controllers/active_canvas/application_controller.rb +20 -0
  24. data/app/controllers/active_canvas/pages_controller.rb +18 -0
  25. data/app/controllers/concerns/active_canvas/current_user.rb +12 -0
  26. data/app/controllers/concerns/active_canvas/rate_limitable.rb +75 -0
  27. data/app/controllers/concerns/active_canvas/tailwind_compilation.rb +39 -0
  28. data/app/helpers/active_canvas/application_helper.rb +4 -0
  29. data/app/jobs/active_canvas/application_job.rb +4 -0
  30. data/app/jobs/active_canvas/compile_tailwind_job.rb +64 -0
  31. data/app/mailers/active_canvas/application_mailer.rb +6 -0
  32. data/app/models/active_canvas/ai_model.rb +136 -0
  33. data/app/models/active_canvas/application_record.rb +5 -0
  34. data/app/models/active_canvas/media.rb +141 -0
  35. data/app/models/active_canvas/page.rb +85 -0
  36. data/app/models/active_canvas/page_type.rb +22 -0
  37. data/app/models/active_canvas/page_version.rb +80 -0
  38. data/app/models/active_canvas/partial.rb +73 -0
  39. data/app/models/active_canvas/setting.rb +292 -0
  40. data/app/services/active_canvas/ai_configuration.rb +40 -0
  41. data/app/services/active_canvas/ai_models.rb +128 -0
  42. data/app/services/active_canvas/ai_service.rb +289 -0
  43. data/app/services/active_canvas/content_sanitizer.rb +112 -0
  44. data/app/services/active_canvas/tailwind_compiler.rb +156 -0
  45. data/app/views/active_canvas/admin/media/index.html.erb +401 -0
  46. data/app/views/active_canvas/admin/media/show.html.erb +297 -0
  47. data/app/views/active_canvas/admin/page_types/_form.html.erb +25 -0
  48. data/app/views/active_canvas/admin/page_types/edit.html.erb +13 -0
  49. data/app/views/active_canvas/admin/page_types/index.html.erb +29 -0
  50. data/app/views/active_canvas/admin/page_types/new.html.erb +9 -0
  51. data/app/views/active_canvas/admin/page_types/show.html.erb +18 -0
  52. data/app/views/active_canvas/admin/page_versions/show.html.erb +469 -0
  53. data/app/views/active_canvas/admin/pages/_form.html.erb +62 -0
  54. data/app/views/active_canvas/admin/pages/content.html.erb +139 -0
  55. data/app/views/active_canvas/admin/pages/edit.html.erb +335 -0
  56. data/app/views/active_canvas/admin/pages/editor.html.erb +710 -0
  57. data/app/views/active_canvas/admin/pages/index.html.erb +149 -0
  58. data/app/views/active_canvas/admin/pages/new.html.erb +19 -0
  59. data/app/views/active_canvas/admin/pages/show.html.erb +258 -0
  60. data/app/views/active_canvas/admin/pages/versions.html.erb +333 -0
  61. data/app/views/active_canvas/admin/partials/edit.html.erb +182 -0
  62. data/app/views/active_canvas/admin/partials/editor.html.erb +703 -0
  63. data/app/views/active_canvas/admin/partials/index.html.erb +131 -0
  64. data/app/views/active_canvas/admin/settings/show.html.erb +1864 -0
  65. data/app/views/active_canvas/pages/no_homepage.html.erb +45 -0
  66. data/app/views/active_canvas/pages/show.html.erb +113 -0
  67. data/app/views/layouts/active_canvas/admin/application.html.erb +960 -0
  68. data/app/views/layouts/active_canvas/admin/editor.html.erb +826 -0
  69. data/app/views/layouts/active_canvas/application.html.erb +55 -0
  70. data/config/routes.rb +48 -0
  71. data/db/migrate/20260202000001_create_active_canvas_tables.rb +113 -0
  72. data/db/migrate/20260202000002_create_active_canvas_ai_models.rb +26 -0
  73. data/lib/active_canvas/configuration.rb +232 -0
  74. data/lib/active_canvas/engine.rb +44 -0
  75. data/lib/active_canvas/version.rb +3 -0
  76. data/lib/active_canvas.rb +26 -0
  77. data/lib/generators/active_canvas/install/install_generator.rb +263 -0
  78. data/lib/generators/active_canvas/install/templates/initializer.rb.tt +163 -0
  79. data/lib/tasks/active_canvas_tasks.rake +69 -0
  80. metadata +150 -0
@@ -0,0 +1,960 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <title>ActiveCanvas Admin</title>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <meta charset="utf-8">
7
+ <%= csrf_meta_tags %>
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
11
+ <style>
12
+ :root {
13
+ --primary: #6366f1;
14
+ --primary-hover: #4f46e5;
15
+ --primary-light: #e0e7ff;
16
+ --sidebar-bg: #0f172a;
17
+ --sidebar-text: #94a3b8;
18
+ --sidebar-hover: #1e293b;
19
+ --sidebar-active: rgba(99, 102, 241, 0.1);
20
+ --sidebar-active-text: #6366f1;
21
+ --bg-main: #f8fafc;
22
+ --bg-card: #ffffff;
23
+ --border: #e2e8f0;
24
+ --text: #1e293b;
25
+ --text-muted: #64748b;
26
+ --text-light: #94a3b8;
27
+ --success: #10b981;
28
+ --success-light: #d1fae5;
29
+ --danger: #ef4444;
30
+ --danger-light: #fee2e2;
31
+ --warning: #f59e0b;
32
+ --warning-light: #fef3c7;
33
+ --info: #0ea5e9;
34
+ --info-light: #e0f2fe;
35
+ --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
36
+ --shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
37
+ --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
38
+ --radius: 0.5rem;
39
+ --radius-lg: 0.75rem;
40
+ }
41
+
42
+ * { box-sizing: border-box; margin: 0; padding: 0; }
43
+
44
+ html, body {
45
+ height: 100%;
46
+ }
47
+
48
+ body {
49
+ font-family: 'Inter', system-ui, -apple-system, sans-serif;
50
+ background: var(--bg-main);
51
+ color: var(--text);
52
+ line-height: 1.6;
53
+ min-height: 100vh;
54
+ overflow-y: auto;
55
+ }
56
+
57
+ /* Layout */
58
+ .admin-layout {
59
+ display: flex;
60
+ min-height: 100%;
61
+ }
62
+
63
+ /* Sidebar */
64
+ .admin-sidebar {
65
+ width: 260px;
66
+ background: var(--sidebar-bg);
67
+ display: flex;
68
+ flex-direction: column;
69
+ position: fixed;
70
+ top: 0;
71
+ left: 0;
72
+ bottom: 0;
73
+ z-index: 100;
74
+ }
75
+
76
+ .sidebar-header {
77
+ padding: 1.25rem 1.5rem;
78
+ border-bottom: 1px solid rgba(255,255,255,0.05);
79
+ }
80
+
81
+ .sidebar-logo {
82
+ display: flex;
83
+ align-items: center;
84
+ gap: 0.75rem;
85
+ text-decoration: none;
86
+ }
87
+
88
+ .sidebar-logo-icon {
89
+ width: 36px;
90
+ height: 36px;
91
+ background: linear-gradient(135deg, var(--primary) 0%, #8b5cf6 100%);
92
+ border-radius: 0.5rem;
93
+ display: flex;
94
+ align-items: center;
95
+ justify-content: center;
96
+ }
97
+
98
+ .sidebar-logo-icon svg {
99
+ width: 20px;
100
+ height: 20px;
101
+ color: white;
102
+ }
103
+
104
+ .sidebar-logo-text {
105
+ font-size: 1.125rem;
106
+ font-weight: 700;
107
+ color: white;
108
+ letter-spacing: -0.025em;
109
+ }
110
+
111
+ .sidebar-nav {
112
+ flex: 1;
113
+ padding: 1rem 0;
114
+ overflow-y: auto;
115
+ }
116
+
117
+ .nav-section {
118
+ margin-bottom: 1.5rem;
119
+ }
120
+
121
+ .nav-section-title {
122
+ padding: 0 1.5rem;
123
+ margin-bottom: 0.5rem;
124
+ font-size: 0.6875rem;
125
+ font-weight: 600;
126
+ text-transform: uppercase;
127
+ letter-spacing: 0.05em;
128
+ color: var(--sidebar-text);
129
+ opacity: 0.6;
130
+ }
131
+
132
+ .nav-link {
133
+ display: flex;
134
+ align-items: center;
135
+ gap: 0.75rem;
136
+ padding: 0.625rem 1.5rem;
137
+ color: var(--sidebar-text);
138
+ text-decoration: none;
139
+ font-size: 0.875rem;
140
+ font-weight: 500;
141
+ transition: all 0.15s ease;
142
+ border-left: 3px solid transparent;
143
+ }
144
+
145
+ .nav-link:hover {
146
+ background: var(--sidebar-hover);
147
+ color: #e2e8f0;
148
+ }
149
+
150
+ .nav-link.active {
151
+ background: var(--sidebar-active);
152
+ color: var(--sidebar-active-text);
153
+ border-left-color: var(--primary);
154
+ }
155
+
156
+ .nav-link svg {
157
+ width: 18px;
158
+ height: 18px;
159
+ opacity: 0.7;
160
+ flex-shrink: 0;
161
+ }
162
+
163
+ .nav-link.active svg {
164
+ opacity: 1;
165
+ }
166
+
167
+ .sidebar-footer {
168
+ padding: 1rem 1.5rem;
169
+ border-top: 1px solid rgba(255,255,255,0.05);
170
+ }
171
+
172
+ .sidebar-footer-text {
173
+ font-size: 0.75rem;
174
+ color: var(--sidebar-text);
175
+ opacity: 0.5;
176
+ }
177
+
178
+ /* Main Content */
179
+ .admin-main {
180
+ flex: 1;
181
+ margin-left: 260px;
182
+ min-height: 100vh;
183
+ display: flex;
184
+ flex-direction: column;
185
+ overflow-y: auto;
186
+ }
187
+
188
+ .admin-header {
189
+ background: var(--bg-card);
190
+ border-bottom: 1px solid var(--border);
191
+ padding: 1rem 2rem;
192
+ display: flex;
193
+ align-items: center;
194
+ justify-content: space-between;
195
+ position: sticky;
196
+ top: 0;
197
+ z-index: 50;
198
+ }
199
+
200
+ .admin-header-left {
201
+ display: flex;
202
+ align-items: center;
203
+ gap: 1rem;
204
+ }
205
+
206
+ .admin-breadcrumb {
207
+ display: flex;
208
+ align-items: center;
209
+ gap: 0.5rem;
210
+ font-size: 0.875rem;
211
+ color: var(--text-muted);
212
+ }
213
+
214
+ .admin-breadcrumb a {
215
+ color: var(--text-muted);
216
+ text-decoration: none;
217
+ }
218
+
219
+ .admin-breadcrumb a:hover {
220
+ color: var(--primary);
221
+ }
222
+
223
+ .admin-breadcrumb span {
224
+ color: var(--text);
225
+ font-weight: 500;
226
+ }
227
+
228
+ .admin-header-actions {
229
+ display: flex;
230
+ align-items: center;
231
+ gap: 0.75rem;
232
+ }
233
+
234
+ .container {
235
+ flex: 1;
236
+ padding: 2rem;
237
+ # max-width: 1400px;
238
+ }
239
+
240
+ /* Flash Messages */
241
+ .flash {
242
+ display: flex;
243
+ align-items: center;
244
+ gap: 0.75rem;
245
+ padding: 0.875rem 1rem;
246
+ border-radius: var(--radius);
247
+ margin-bottom: 1.5rem;
248
+ font-size: 0.875rem;
249
+ font-weight: 500;
250
+ }
251
+
252
+ .flash svg {
253
+ width: 18px;
254
+ height: 18px;
255
+ flex-shrink: 0;
256
+ }
257
+
258
+ .flash-notice {
259
+ background: var(--success-light);
260
+ color: #065f46;
261
+ border: 1px solid #a7f3d0;
262
+ }
263
+
264
+ .flash-alert {
265
+ background: var(--danger-light);
266
+ color: #991b1b;
267
+ border: 1px solid #fecaca;
268
+ }
269
+
270
+ /* Page Header */
271
+ .page-header {
272
+ display: flex;
273
+ justify-content: space-between;
274
+ align-items: center;
275
+ margin-bottom: 1.5rem;
276
+ }
277
+
278
+ .page-header-left {
279
+ display: flex;
280
+ flex-direction: column;
281
+ gap: 0.25rem;
282
+ }
283
+
284
+ .page-header h2 {
285
+ font-size: 1.5rem;
286
+ font-weight: 700;
287
+ color: var(--text);
288
+ letter-spacing: -0.025em;
289
+ }
290
+
291
+ .page-header-subtitle {
292
+ font-size: 0.875rem;
293
+ color: var(--text-muted);
294
+ }
295
+
296
+ .page-header-actions {
297
+ display: flex;
298
+ gap: 0.75rem;
299
+ }
300
+
301
+ /* Buttons */
302
+ .btn {
303
+ display: inline-flex;
304
+ align-items: center;
305
+ justify-content: center;
306
+ gap: 0.5rem;
307
+ padding: 0.5rem 1rem;
308
+ border-radius: var(--radius);
309
+ font-size: 0.875rem;
310
+ font-weight: 500;
311
+ text-decoration: none;
312
+ cursor: pointer;
313
+ border: none;
314
+ transition: all 0.15s ease;
315
+ white-space: nowrap;
316
+ }
317
+
318
+ .btn svg {
319
+ width: 16px;
320
+ height: 16px;
321
+ }
322
+
323
+ .btn-primary {
324
+ background: var(--primary);
325
+ color: white;
326
+ box-shadow: var(--shadow-sm);
327
+ }
328
+
329
+ .btn-primary:hover {
330
+ background: var(--primary-hover);
331
+ box-shadow: var(--shadow);
332
+ }
333
+
334
+ .btn-secondary {
335
+ background: var(--bg-card);
336
+ color: var(--text);
337
+ border: 1px solid var(--border);
338
+ }
339
+
340
+ .btn-secondary:hover {
341
+ background: var(--bg-main);
342
+ border-color: #cbd5e1;
343
+ }
344
+
345
+ .btn-ghost {
346
+ background: transparent;
347
+ color: var(--text-muted);
348
+ }
349
+
350
+ .btn-ghost:hover {
351
+ background: var(--bg-main);
352
+ color: var(--text);
353
+ }
354
+
355
+ .btn-danger {
356
+ background: var(--danger-light);
357
+ color: var(--danger);
358
+ border: 1px solid #fecaca;
359
+ }
360
+
361
+ .btn-danger:hover {
362
+ background: #fecaca;
363
+ }
364
+
365
+ .btn-success {
366
+ background: var(--success-light);
367
+ color: var(--success);
368
+ border: 1px solid #a7f3d0;
369
+ }
370
+
371
+ .btn-success:hover {
372
+ background: #a7f3d0;
373
+ }
374
+
375
+ .btn-sm {
376
+ padding: 0.375rem 0.75rem;
377
+ font-size: 0.8125rem;
378
+ }
379
+
380
+ .btn-icon {
381
+ padding: 0.5rem;
382
+ }
383
+
384
+ /* Cards */
385
+ .card {
386
+ background: var(--bg-card);
387
+ border-radius: var(--radius-lg);
388
+ box-shadow: var(--shadow-sm);
389
+ border: 1px solid var(--border);
390
+ overflow: hidden;
391
+ }
392
+
393
+ .card-header {
394
+ padding: 1rem 1.25rem;
395
+ border-bottom: 1px solid var(--border);
396
+ display: flex;
397
+ align-items: center;
398
+ justify-content: space-between;
399
+ }
400
+
401
+ .card-title {
402
+ font-size: 0.9375rem;
403
+ font-weight: 600;
404
+ color: var(--text);
405
+ }
406
+
407
+ .card-body {
408
+ padding: 1.25rem;
409
+ }
410
+
411
+ /* Tables */
412
+ table {
413
+ width: 100%;
414
+ border-collapse: collapse;
415
+ }
416
+
417
+ th, td {
418
+ padding: 0.875rem 1.25rem;
419
+ text-align: left;
420
+ }
421
+
422
+ th {
423
+ background: var(--bg-main);
424
+ font-weight: 600;
425
+ font-size: 0.75rem;
426
+ text-transform: uppercase;
427
+ letter-spacing: 0.05em;
428
+ color: var(--text-muted);
429
+ border-bottom: 1px solid var(--border);
430
+ }
431
+
432
+ td {
433
+ border-bottom: 1px solid var(--border);
434
+ font-size: 0.875rem;
435
+ color: var(--text);
436
+ }
437
+
438
+ tr:last-child td {
439
+ border-bottom: none;
440
+ }
441
+
442
+ tbody tr:hover {
443
+ background: var(--bg-main);
444
+ }
445
+
446
+ td a:not(.btn) {
447
+ color: var(--primary);
448
+ text-decoration: none;
449
+ font-weight: 500;
450
+ }
451
+
452
+ td a:not(.btn):hover {
453
+ text-decoration: underline;
454
+ }
455
+
456
+ td code {
457
+ font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
458
+ font-size: 0.8125rem;
459
+ padding: 0.125rem 0.375rem;
460
+ background: var(--bg-main);
461
+ border-radius: 0.25rem;
462
+ color: var(--text-muted);
463
+ }
464
+
465
+ /* Badges */
466
+ .badge {
467
+ display: inline-flex;
468
+ align-items: center;
469
+ gap: 0.375rem;
470
+ padding: 0.25rem 0.625rem;
471
+ border-radius: 9999px;
472
+ font-size: 0.75rem;
473
+ font-weight: 500;
474
+ }
475
+
476
+ .badge-dot {
477
+ width: 6px;
478
+ height: 6px;
479
+ border-radius: 50%;
480
+ }
481
+
482
+ .badge-success {
483
+ background: var(--success-light);
484
+ color: #065f46;
485
+ }
486
+
487
+ .badge-success .badge-dot {
488
+ background: var(--success);
489
+ }
490
+
491
+ .badge-gray {
492
+ background: #f1f5f9;
493
+ color: var(--text-muted);
494
+ }
495
+
496
+ .badge-gray .badge-dot {
497
+ background: #94a3b8;
498
+ }
499
+
500
+ .badge-warning {
501
+ background: var(--warning-light);
502
+ color: #92400e;
503
+ }
504
+
505
+ /* Forms */
506
+ .form-group {
507
+ margin-bottom: 1.25rem;
508
+ }
509
+
510
+ .form-group label {
511
+ display: block;
512
+ font-weight: 500;
513
+ margin-bottom: 0.375rem;
514
+ font-size: 0.875rem;
515
+ color: var(--text);
516
+ }
517
+
518
+ .form-control {
519
+ width: 100%;
520
+ padding: 0.5rem 0.75rem;
521
+ border: 1px solid var(--border);
522
+ border-radius: var(--radius);
523
+ font-size: 0.875rem;
524
+ color: var(--text);
525
+ background: var(--bg-card);
526
+ transition: all 0.15s ease;
527
+ }
528
+
529
+ .form-control:focus {
530
+ outline: none;
531
+ border-color: var(--primary);
532
+ box-shadow: 0 0 0 3px var(--primary-light);
533
+ }
534
+
535
+ textarea.form-control {
536
+ min-height: 120px;
537
+ font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
538
+ resize: vertical;
539
+ }
540
+
541
+ select.form-control {
542
+ cursor: pointer;
543
+ appearance: none;
544
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
545
+ background-position: right 0.5rem center;
546
+ background-repeat: no-repeat;
547
+ background-size: 1.5em 1.5em;
548
+ padding-right: 2.5rem;
549
+ }
550
+
551
+ .form-check {
552
+ display: flex;
553
+ align-items: center;
554
+ gap: 0.5rem;
555
+ }
556
+
557
+ .form-check input[type="checkbox"] {
558
+ width: 1rem;
559
+ height: 1rem;
560
+ accent-color: var(--primary);
561
+ cursor: pointer;
562
+ }
563
+
564
+ .form-actions {
565
+ display: flex;
566
+ gap: 0.75rem;
567
+ margin-top: 1.5rem;
568
+ padding-top: 1.5rem;
569
+ border-top: 1px solid var(--border);
570
+ }
571
+
572
+ .help-text {
573
+ font-size: 0.75rem;
574
+ color: var(--text-muted);
575
+ margin-top: 0.375rem;
576
+ }
577
+
578
+ .error-messages {
579
+ background: var(--danger-light);
580
+ color: #991b1b;
581
+ padding: 1rem;
582
+ border-radius: var(--radius);
583
+ margin-bottom: 1rem;
584
+ border: 1px solid #fecaca;
585
+ }
586
+
587
+ .error-messages ul {
588
+ margin-left: 1rem;
589
+ font-size: 0.875rem;
590
+ }
591
+
592
+ /* Actions */
593
+ .actions {
594
+ display: flex;
595
+ gap: 0.5rem;
596
+ justify-content: flex-end;
597
+ }
598
+
599
+ /* Detail View */
600
+ .detail-grid {
601
+ display: grid;
602
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
603
+ gap: 1.5rem;
604
+ }
605
+
606
+ .detail-item {
607
+ padding: 1rem;
608
+ background: var(--bg-main);
609
+ border-radius: var(--radius);
610
+ }
611
+
612
+ .detail-label {
613
+ font-size: 0.6875rem;
614
+ text-transform: uppercase;
615
+ letter-spacing: 0.05em;
616
+ color: var(--text-muted);
617
+ font-weight: 600;
618
+ margin-bottom: 0.375rem;
619
+ }
620
+
621
+ .detail-value {
622
+ font-size: 0.9375rem;
623
+ color: var(--text);
624
+ font-weight: 500;
625
+ }
626
+
627
+ .detail-row {
628
+ padding: 1rem 1.25rem;
629
+ border-bottom: 1px solid var(--border);
630
+ }
631
+
632
+ .detail-row:last-child {
633
+ border-bottom: none;
634
+ }
635
+
636
+ .content-preview {
637
+ background: var(--sidebar-bg);
638
+ color: #e2e8f0;
639
+ padding: 1rem;
640
+ border-radius: var(--radius);
641
+ font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
642
+ font-size: 0.8125rem;
643
+ white-space: pre-wrap;
644
+ max-height: 300px;
645
+ overflow: auto;
646
+ }
647
+
648
+ /* Stats Cards */
649
+ .stats-grid {
650
+ display: grid;
651
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
652
+ gap: 1rem;
653
+ margin-bottom: 2rem;
654
+ }
655
+
656
+ .stat-card {
657
+ background: var(--bg-card);
658
+ border: 1px solid var(--border);
659
+ border-radius: var(--radius-lg);
660
+ padding: 1.25rem;
661
+ display: flex;
662
+ align-items: flex-start;
663
+ gap: 1rem;
664
+ }
665
+
666
+ .stat-icon {
667
+ width: 40px;
668
+ height: 40px;
669
+ border-radius: var(--radius);
670
+ display: flex;
671
+ align-items: center;
672
+ justify-content: center;
673
+ flex-shrink: 0;
674
+ }
675
+
676
+ .stat-icon svg {
677
+ width: 20px;
678
+ height: 20px;
679
+ }
680
+
681
+ .stat-icon-primary {
682
+ background: var(--primary-light);
683
+ color: var(--primary);
684
+ }
685
+
686
+ .stat-icon-success {
687
+ background: var(--success-light);
688
+ color: var(--success);
689
+ }
690
+
691
+ .stat-icon-warning {
692
+ background: var(--warning-light);
693
+ color: var(--warning);
694
+ }
695
+
696
+ .stat-icon-info {
697
+ background: var(--info-light);
698
+ color: var(--info);
699
+ }
700
+
701
+ .stat-content h3 {
702
+ font-size: 1.5rem;
703
+ font-weight: 700;
704
+ color: var(--text);
705
+ line-height: 1.2;
706
+ }
707
+
708
+ .stat-content p {
709
+ font-size: 0.8125rem;
710
+ color: var(--text-muted);
711
+ margin-top: 0.125rem;
712
+ }
713
+
714
+ /* Empty State */
715
+ .empty-state {
716
+ text-align: center;
717
+ padding: 3rem 2rem;
718
+ color: var(--text-muted);
719
+ }
720
+
721
+ .empty-state svg {
722
+ width: 48px;
723
+ height: 48px;
724
+ margin-bottom: 1rem;
725
+ opacity: 0.5;
726
+ }
727
+
728
+ .empty-state h3 {
729
+ font-size: 1rem;
730
+ font-weight: 600;
731
+ color: var(--text);
732
+ margin-bottom: 0.5rem;
733
+ }
734
+
735
+ .empty-state p {
736
+ font-size: 0.875rem;
737
+ margin-bottom: 1.5rem;
738
+ }
739
+
740
+ /* Settings Tabs */
741
+ .settings-tabs {
742
+ display: flex;
743
+ gap: 0;
744
+ margin-bottom: 1.5rem;
745
+ border-bottom: 1px solid var(--border);
746
+ background: var(--bg-card);
747
+ padding: 0 0.5rem;
748
+ border-radius: var(--radius-lg) var(--radius-lg) 0 0;
749
+ }
750
+
751
+ .settings-tab {
752
+ padding: 1rem 1.5rem;
753
+ color: var(--text-muted);
754
+ text-decoration: none;
755
+ font-weight: 500;
756
+ font-size: 0.875rem;
757
+ border-bottom: 2px solid transparent;
758
+ margin-bottom: -1px;
759
+ transition: all 0.15s ease;
760
+ }
761
+
762
+ .settings-tab:hover {
763
+ color: var(--text);
764
+ }
765
+
766
+ .settings-tab.active {
767
+ color: var(--primary);
768
+ border-bottom-color: var(--primary);
769
+ }
770
+
771
+ .settings-code-card {
772
+ padding: 0;
773
+ overflow: hidden;
774
+ }
775
+
776
+ .settings-code-header {
777
+ padding: 1rem 1.5rem;
778
+ border-bottom: 1px solid var(--border);
779
+ }
780
+
781
+ .settings-code-header h3 {
782
+ margin: 0 0 0.25rem 0;
783
+ font-size: 1rem;
784
+ font-weight: 600;
785
+ }
786
+
787
+ .settings-monaco-editor {
788
+ height: 500px;
789
+ border-bottom: 1px solid var(--border);
790
+ }
791
+
792
+ .settings-code-actions {
793
+ padding: 1rem 1.5rem;
794
+ display: flex;
795
+ justify-content: flex-end;
796
+ gap: 0.75rem;
797
+ background: var(--bg-main);
798
+ }
799
+
800
+ /* Responsive */
801
+ @media (max-width: 1024px) {
802
+ .admin-sidebar {
803
+ width: 80px;
804
+ }
805
+
806
+ .sidebar-logo-text,
807
+ .nav-section-title,
808
+ .nav-link span,
809
+ .sidebar-footer-text {
810
+ display: none;
811
+ }
812
+
813
+ .nav-link {
814
+ justify-content: center;
815
+ padding: 0.75rem;
816
+ }
817
+
818
+ .admin-main {
819
+ margin-left: 80px;
820
+ }
821
+ }
822
+
823
+ @media (max-width: 768px) {
824
+ .admin-sidebar {
825
+ transform: translateX(-100%);
826
+ }
827
+
828
+ .admin-main {
829
+ margin-left: 0;
830
+ }
831
+
832
+ .container {
833
+ padding: 1rem;
834
+ }
835
+ }
836
+ </style>
837
+ </head>
838
+ <body>
839
+ <div class="admin-layout">
840
+ <!-- Sidebar -->
841
+ <aside class="admin-sidebar">
842
+ <div class="sidebar-header">
843
+ <%= link_to admin_pages_path, class: "sidebar-logo" do %>
844
+ <div class="sidebar-logo-icon">
845
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
846
+ <polygon points="12 2 2 7 12 12 22 7 12 2"/>
847
+ <polyline points="2 17 12 22 22 17"/>
848
+ <polyline points="2 12 12 17 22 12"/>
849
+ </svg>
850
+ </div>
851
+ <span class="sidebar-logo-text">ActiveCanvas</span>
852
+ <% end %>
853
+ </div>
854
+
855
+ <nav class="sidebar-nav">
856
+ <div class="nav-section">
857
+ <div class="nav-section-title">Content</div>
858
+ <%= link_to admin_pages_path, class: "nav-link #{controller_name == 'pages' ? 'active' : ''}" do %>
859
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
860
+ <path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/>
861
+ <polyline points="14 2 14 8 20 8"/>
862
+ </svg>
863
+ <span>Pages</span>
864
+ <% end %>
865
+ <%= link_to admin_media_path, class: "nav-link #{controller_name == 'media' ? 'active' : ''}" do %>
866
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
867
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
868
+ <circle cx="8.5" cy="8.5" r="1.5"/>
869
+ <polyline points="21 15 16 10 5 21"/>
870
+ </svg>
871
+ <span>Media</span>
872
+ <% end %>
873
+ <%= link_to admin_partials_path, class: "nav-link #{controller_name == 'partials' ? 'active' : ''}" do %>
874
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
875
+ <rect x="3" y="3" width="18" height="6" rx="1"/>
876
+ <rect x="3" y="15" width="18" height="6" rx="1"/>
877
+ </svg>
878
+ <span>Header & Footer</span>
879
+ <% end %>
880
+ </div>
881
+
882
+ <div class="nav-section">
883
+ <div class="nav-section-title">Configure</div>
884
+ <%= link_to admin_settings_path, class: "nav-link #{controller_name == 'settings' ? 'active' : ''}" do %>
885
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
886
+ <circle cx="12" cy="12" r="3"/>
887
+ <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>
888
+ </svg>
889
+ <span>Settings</span>
890
+ <% end %>
891
+ </div>
892
+ </nav>
893
+
894
+ <div class="sidebar-footer">
895
+ <p class="sidebar-footer-text">ActiveCanvas v1.0</p>
896
+ </div>
897
+ </aside>
898
+
899
+ <!-- Main Content -->
900
+ <main class="admin-main">
901
+ <header class="admin-header">
902
+ <div class="admin-header-left">
903
+ <nav class="admin-breadcrumb">
904
+ <%= link_to "Dashboard", admin_pages_path %>
905
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
906
+ <polyline points="9 18 15 12 9 6"/>
907
+ </svg>
908
+ <span><%= yield(:page_title).presence || controller_name.titleize %></span>
909
+ </nav>
910
+ </div>
911
+ <div class="admin-header-actions">
912
+ <% if controller_name == 'pages' && action_name == 'index' %>
913
+ <%= link_to new_admin_page_path, class: "btn btn-primary" do %>
914
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
915
+ <line x1="12" y1="5" x2="12" y2="19"/>
916
+ <line x1="5" y1="12" x2="19" y2="12"/>
917
+ </svg>
918
+ New Page
919
+ <% end %>
920
+ <% end %>
921
+ <% if controller_name == 'media' && action_name == 'index' %>
922
+ <button type="button" class="btn btn-primary" id="header-upload-btn" onclick="document.getElementById('media-upload-input')?.click()">
923
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
924
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
925
+ <polyline points="17 8 12 3 7 8"/>
926
+ <line x1="12" y1="3" x2="12" y2="15"/>
927
+ </svg>
928
+ Upload Media
929
+ </button>
930
+ <% end %>
931
+ </div>
932
+ </header>
933
+
934
+ <div class="container">
935
+ <% if notice %>
936
+ <div class="flash flash-notice">
937
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
938
+ <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>
939
+ <polyline points="22 4 12 14.01 9 11.01"/>
940
+ </svg>
941
+ <%= notice %>
942
+ </div>
943
+ <% end %>
944
+ <% if alert %>
945
+ <div class="flash flash-alert">
946
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
947
+ <circle cx="12" cy="12" r="10"/>
948
+ <line x1="12" y1="8" x2="12" y2="12"/>
949
+ <line x1="12" y1="16" x2="12.01" y2="16"/>
950
+ </svg>
951
+ <%= alert %>
952
+ </div>
953
+ <% end %>
954
+
955
+ <%= yield %>
956
+ </div>
957
+ </main>
958
+ </div>
959
+ </body>
960
+ </html>