@orhancodestudio/ocsm-core 0.1.0-alpha.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 (67) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +47 -0
  3. package/package.json +53 -0
  4. package/src/admin/admin.module.css +1312 -0
  5. package/src/admin/admin.types.ts +85 -0
  6. package/src/admin/components/access-denied.tsx +12 -0
  7. package/src/admin/components/admin-shell.tsx +168 -0
  8. package/src/admin/components/content-list-view.tsx +83 -0
  9. package/src/admin/components/dashboard-view.tsx +113 -0
  10. package/src/admin/components/data-table.tsx +80 -0
  11. package/src/admin/components/document-delete-button.tsx +44 -0
  12. package/src/admin/components/icons.tsx +150 -0
  13. package/src/admin/components/modal.tsx +78 -0
  14. package/src/admin/components/page-builder.tsx +1334 -0
  15. package/src/admin/components/settings-view.tsx +334 -0
  16. package/src/admin/components/sign-out-button.tsx +22 -0
  17. package/src/admin/components/system-view.tsx +77 -0
  18. package/src/admin/components/users-panel.tsx +321 -0
  19. package/src/admin/index.ts +20 -0
  20. package/src/admin/ocsm-admin.tsx +259 -0
  21. package/src/auth/authenticate.ts +76 -0
  22. package/src/auth/index.ts +9 -0
  23. package/src/auth/password.ts +22 -0
  24. package/src/auth/permissions.ts +27 -0
  25. package/src/auth/session.ts +103 -0
  26. package/src/blocks/block-renderer.tsx +428 -0
  27. package/src/blocks/block.types.ts +401 -0
  28. package/src/blocks/index.ts +15 -0
  29. package/src/blocks/markdown.tsx +11 -0
  30. package/src/config/config.schema.ts +28 -0
  31. package/src/config/config.types.ts +16 -0
  32. package/src/config/define-config.ts +19 -0
  33. package/src/config/index.ts +13 -0
  34. package/src/config/resolve-config.ts +10 -0
  35. package/src/content/content-repository.ts +66 -0
  36. package/src/content/content-store.interface.ts +23 -0
  37. package/src/content/content.types.ts +25 -0
  38. package/src/content/create-content-store.ts +18 -0
  39. package/src/content/frontmatter.ts +25 -0
  40. package/src/content/index.ts +12 -0
  41. package/src/index.ts +10 -0
  42. package/src/layout/index.ts +1 -0
  43. package/src/layout/layout-store.ts +27 -0
  44. package/src/roles/index.ts +10 -0
  45. package/src/roles/role-store.ts +95 -0
  46. package/src/roles/role.types.ts +86 -0
  47. package/src/server/create-ocsm.ts +67 -0
  48. package/src/server/documents.ts +28 -0
  49. package/src/server/index.ts +59 -0
  50. package/src/server/render-mdx.tsx +14 -0
  51. package/src/storage/create-file-backend.ts +26 -0
  52. package/src/storage/file-backend.ts +26 -0
  53. package/src/storage/fs-file-backend.ts +43 -0
  54. package/src/storage/github-file-backend.ts +97 -0
  55. package/src/storage/index.ts +8 -0
  56. package/src/storage/json-store.ts +23 -0
  57. package/src/theme/css.ts +28 -0
  58. package/src/theme/index.ts +8 -0
  59. package/src/theme/theme-store.ts +19 -0
  60. package/src/theme/theme.types.ts +53 -0
  61. package/src/types/css-modules.d.ts +4 -0
  62. package/src/update/check-for-updates.ts +50 -0
  63. package/src/update/index.ts +1 -0
  64. package/src/users/index.ts +6 -0
  65. package/src/users/user-store.ts +120 -0
  66. package/src/users/user.types.ts +18 -0
  67. package/src/version.ts +11 -0
@@ -0,0 +1,1312 @@
1
+ .app {
2
+ --p-bg: #f1f5f9;
3
+ --p-surface: #ffffff;
4
+ --p-border: #e2e8f0;
5
+ --p-text: #0f172a;
6
+ --p-muted: #64748b;
7
+ --p-primary: #2563eb;
8
+ --p-primary-weak: #eff6ff;
9
+ --p-danger: #dc2626;
10
+ --p-sidebar: #0b1220;
11
+ --p-sidebar-2: #131c31;
12
+ --p-sidebar-text: #cbd5e1;
13
+ --p-radius: 10px;
14
+
15
+ display: grid;
16
+ grid-template-columns: 248px 1fr;
17
+ height: 100vh;
18
+ overflow: hidden;
19
+ background: var(--p-bg);
20
+ color: var(--p-text);
21
+ font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
22
+ font-size: 14px;
23
+ }
24
+
25
+ /* ---------- Sidebar ---------- */
26
+ .sidebar {
27
+ background: var(--p-sidebar);
28
+ color: var(--p-sidebar-text);
29
+ display: flex;
30
+ flex-direction: column;
31
+ padding: 20px 14px;
32
+ height: 100vh;
33
+ overflow-y: auto;
34
+ }
35
+
36
+ .brand {
37
+ display: flex;
38
+ flex-direction: column;
39
+ gap: 2px;
40
+ padding: 8px 10px 18px;
41
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
42
+ margin-bottom: 14px;
43
+ }
44
+
45
+ .brandName {
46
+ font-weight: 700;
47
+ font-size: 16px;
48
+ color: #fff;
49
+ }
50
+
51
+ .brandMeta {
52
+ font-size: 11px;
53
+ color: #64748b;
54
+ letter-spacing: 0.02em;
55
+ }
56
+
57
+ .nav {
58
+ display: flex;
59
+ flex-direction: column;
60
+ gap: 2px;
61
+ overflow-y: auto;
62
+ }
63
+
64
+ .navSectionTitle {
65
+ font-size: 10px;
66
+ text-transform: uppercase;
67
+ letter-spacing: 0.08em;
68
+ color: #475569;
69
+ padding: 16px 10px 6px;
70
+ font-weight: 600;
71
+ }
72
+
73
+ .navItem {
74
+ display: flex;
75
+ align-items: center;
76
+ gap: 10px;
77
+ padding: 9px 10px;
78
+ border-radius: 8px;
79
+ color: var(--p-sidebar-text);
80
+ text-decoration: none;
81
+ font-size: 13.5px;
82
+ transition: background 0.12s, color 0.12s;
83
+ }
84
+
85
+ .navItem:hover {
86
+ background: var(--p-sidebar-2);
87
+ color: #fff;
88
+ }
89
+
90
+ .navItemActive {
91
+ background: var(--p-primary);
92
+ color: #fff;
93
+ }
94
+
95
+ .navItemActive:hover {
96
+ background: var(--p-primary);
97
+ color: #fff;
98
+ }
99
+
100
+ .navIcon {
101
+ display: inline-flex;
102
+ width: 18px;
103
+ height: 18px;
104
+ flex-shrink: 0;
105
+ }
106
+
107
+ .sidebarFooter {
108
+ margin-top: auto;
109
+ padding-top: 14px;
110
+ }
111
+
112
+ .updateBadge {
113
+ display: block;
114
+ font-size: 12px;
115
+ background: var(--p-sidebar-2);
116
+ color: #fbbf24;
117
+ padding: 9px 11px;
118
+ border-radius: 8px;
119
+ text-decoration: none;
120
+ }
121
+
122
+ .versionTag {
123
+ font-size: 11px;
124
+ color: #475569;
125
+ padding: 8px 10px 0;
126
+ }
127
+
128
+ /* ---------- Main ---------- */
129
+ .main {
130
+ display: flex;
131
+ flex-direction: column;
132
+ min-width: 0;
133
+ height: 100vh;
134
+ overflow-y: auto;
135
+ }
136
+
137
+ .topbar {
138
+ display: flex;
139
+ align-items: center;
140
+ justify-content: space-between;
141
+ gap: 16px;
142
+ padding: 14px 28px;
143
+ background: var(--p-surface);
144
+ border-bottom: 1px solid var(--p-border);
145
+ position: sticky;
146
+ top: 0;
147
+ z-index: 5;
148
+ }
149
+
150
+ .topbarTitle {
151
+ font-size: 16px;
152
+ font-weight: 600;
153
+ }
154
+
155
+ .topbarRight {
156
+ display: flex;
157
+ align-items: center;
158
+ gap: 14px;
159
+ }
160
+
161
+ .userChip {
162
+ display: flex;
163
+ align-items: center;
164
+ gap: 9px;
165
+ }
166
+
167
+ .userAvatar {
168
+ width: 30px;
169
+ height: 30px;
170
+ border-radius: 50%;
171
+ background: var(--p-primary);
172
+ color: #fff;
173
+ display: inline-flex;
174
+ align-items: center;
175
+ justify-content: center;
176
+ font-size: 13px;
177
+ font-weight: 600;
178
+ }
179
+
180
+ .userMeta {
181
+ display: flex;
182
+ flex-direction: column;
183
+ line-height: 1.25;
184
+ }
185
+
186
+ .userName {
187
+ font-size: 13px;
188
+ font-weight: 600;
189
+ }
190
+
191
+ .roleBadge {
192
+ display: inline-block;
193
+ font-size: 10.5px;
194
+ font-weight: 600;
195
+ padding: 1px 7px;
196
+ border-radius: 999px;
197
+ width: fit-content;
198
+ }
199
+
200
+ .roleAdmin {
201
+ background: #ede9fe;
202
+ color: #6d28d9;
203
+ }
204
+
205
+ .roleEditor {
206
+ background: #dcfce7;
207
+ color: #047857;
208
+ }
209
+
210
+ .content {
211
+ padding: 28px;
212
+ }
213
+
214
+ /* ---------- Page header ---------- */
215
+ .pageHeader {
216
+ display: flex;
217
+ align-items: flex-start;
218
+ justify-content: space-between;
219
+ gap: 16px;
220
+ margin-bottom: 22px;
221
+ }
222
+
223
+ .pageTitle {
224
+ font-size: 22px;
225
+ font-weight: 700;
226
+ margin: 0;
227
+ }
228
+
229
+ .pageSubtitle {
230
+ color: var(--p-muted);
231
+ margin: 4px 0 0;
232
+ font-size: 13.5px;
233
+ }
234
+
235
+ /* ---------- Cards ---------- */
236
+ .cardGrid {
237
+ display: grid;
238
+ grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
239
+ gap: 16px;
240
+ margin-bottom: 24px;
241
+ }
242
+
243
+ .card {
244
+ background: var(--p-surface);
245
+ border: 1px solid var(--p-border);
246
+ border-radius: var(--p-radius);
247
+ padding: 18px;
248
+ text-decoration: none;
249
+ color: inherit;
250
+ display: flex;
251
+ flex-direction: column;
252
+ gap: 6px;
253
+ transition: border-color 0.12s, box-shadow 0.12s;
254
+ }
255
+
256
+ a.card:hover {
257
+ border-color: var(--p-primary);
258
+ box-shadow: 0 1px 0 var(--p-primary);
259
+ }
260
+
261
+ .cardLabel {
262
+ font-size: 12.5px;
263
+ color: var(--p-muted);
264
+ display: flex;
265
+ align-items: center;
266
+ gap: 8px;
267
+ }
268
+
269
+ .cardValue {
270
+ font-size: 26px;
271
+ font-weight: 700;
272
+ }
273
+
274
+ .cardHint {
275
+ font-size: 12px;
276
+ color: var(--p-muted);
277
+ }
278
+
279
+ /* ---------- Panel block ---------- */
280
+ .panel {
281
+ background: var(--p-surface);
282
+ border: 1px solid var(--p-border);
283
+ border-radius: var(--p-radius);
284
+ overflow: hidden;
285
+ }
286
+
287
+ .panelHeader {
288
+ display: flex;
289
+ align-items: center;
290
+ justify-content: space-between;
291
+ padding: 14px 18px;
292
+ border-bottom: 1px solid var(--p-border);
293
+ }
294
+
295
+ .panelTitle {
296
+ font-size: 14px;
297
+ font-weight: 600;
298
+ margin: 0;
299
+ }
300
+
301
+ .panelBody {
302
+ padding: 18px;
303
+ }
304
+
305
+ /* ---------- Table ---------- */
306
+ .table {
307
+ width: 100%;
308
+ border-collapse: collapse;
309
+ }
310
+
311
+ .table th {
312
+ text-align: left;
313
+ font-size: 11.5px;
314
+ text-transform: uppercase;
315
+ letter-spacing: 0.04em;
316
+ color: var(--p-muted);
317
+ padding: 10px 18px;
318
+ border-bottom: 1px solid var(--p-border);
319
+ font-weight: 600;
320
+ }
321
+
322
+ .table td {
323
+ padding: 13px 18px;
324
+ border-bottom: 1px solid var(--p-border);
325
+ font-size: 13.5px;
326
+ }
327
+
328
+ .table tr:last-child td {
329
+ border-bottom: none;
330
+ }
331
+
332
+ .tableActions {
333
+ display: flex;
334
+ gap: 8px;
335
+ justify-content: flex-end;
336
+ }
337
+
338
+ .emptyState {
339
+ padding: 40px 18px;
340
+ text-align: center;
341
+ color: var(--p-muted);
342
+ }
343
+
344
+ /* ---------- Buttons ---------- */
345
+ .btn {
346
+ display: inline-flex;
347
+ align-items: center;
348
+ gap: 7px;
349
+ padding: 9px 15px;
350
+ border-radius: 8px;
351
+ border: 1px solid var(--p-border);
352
+ background: var(--p-surface);
353
+ color: var(--p-text);
354
+ font-size: 13.5px;
355
+ font-weight: 500;
356
+ cursor: pointer;
357
+ text-decoration: none;
358
+ transition: background 0.12s, border-color 0.12s, opacity 0.12s;
359
+ }
360
+
361
+ .btn:hover {
362
+ background: #f8fafc;
363
+ }
364
+
365
+ .btn:disabled {
366
+ opacity: 0.6;
367
+ cursor: not-allowed;
368
+ }
369
+
370
+ .btnPrimary {
371
+ background: var(--p-primary);
372
+ border-color: var(--p-primary);
373
+ color: #fff;
374
+ }
375
+
376
+ .btnPrimary:hover {
377
+ background: #1d4ed8;
378
+ }
379
+
380
+ .btnDanger {
381
+ color: var(--p-danger);
382
+ border-color: transparent;
383
+ background: transparent;
384
+ }
385
+
386
+ .btnDanger:hover {
387
+ background: #fef2f2;
388
+ }
389
+
390
+ .btnGhost {
391
+ border-color: transparent;
392
+ background: transparent;
393
+ }
394
+
395
+ .btnSm {
396
+ padding: 6px 11px;
397
+ font-size: 12.5px;
398
+ }
399
+
400
+ /* ---------- Forms ---------- */
401
+ .form {
402
+ max-width: 760px;
403
+ }
404
+
405
+ .formGrid {
406
+ display: grid;
407
+ grid-template-columns: repeat(2, 1fr);
408
+ gap: 16px;
409
+ }
410
+
411
+ .field {
412
+ display: flex;
413
+ flex-direction: column;
414
+ gap: 6px;
415
+ margin-bottom: 16px;
416
+ }
417
+
418
+ .label {
419
+ font-size: 12.5px;
420
+ font-weight: 600;
421
+ }
422
+
423
+ .input,
424
+ .textarea,
425
+ .select {
426
+ width: 100%;
427
+ padding: 10px 12px;
428
+ border: 1px solid #cbd5e1;
429
+ border-radius: 8px;
430
+ font-size: 13.5px;
431
+ background: var(--p-surface);
432
+ color: var(--p-text);
433
+ box-sizing: border-box;
434
+ font-family: inherit;
435
+ }
436
+
437
+ .input:focus,
438
+ .textarea:focus,
439
+ .select:focus {
440
+ outline: none;
441
+ border-color: var(--p-primary);
442
+ box-shadow: 0 0 0 3px var(--p-primary-weak);
443
+ }
444
+
445
+ .textarea {
446
+ resize: vertical;
447
+ font-family: ui-monospace, "SF Mono", Menlo, monospace;
448
+ line-height: 1.6;
449
+ }
450
+
451
+ .help {
452
+ font-size: 11.5px;
453
+ color: var(--p-muted);
454
+ }
455
+
456
+ .formActions {
457
+ display: flex;
458
+ align-items: center;
459
+ gap: 14px;
460
+ margin-top: 20px;
461
+ }
462
+
463
+ .statusText {
464
+ font-size: 13px;
465
+ color: var(--p-muted);
466
+ }
467
+
468
+ .errorText {
469
+ font-size: 13px;
470
+ color: var(--p-danger);
471
+ }
472
+
473
+ /* ---------- Misc ---------- */
474
+ .badge {
475
+ display: inline-block;
476
+ font-size: 11px;
477
+ font-weight: 600;
478
+ padding: 2px 9px;
479
+ border-radius: 999px;
480
+ background: #f1f5f9;
481
+ color: var(--p-muted);
482
+ }
483
+
484
+ .badgeOk {
485
+ background: #dcfce7;
486
+ color: #047857;
487
+ }
488
+
489
+ .badgeWarn {
490
+ background: #fef9c3;
491
+ color: #a16207;
492
+ }
493
+
494
+ .notice {
495
+ padding: 14px 16px;
496
+ border-radius: var(--p-radius);
497
+ background: var(--p-primary-weak);
498
+ border: 1px solid #bfdbfe;
499
+ color: #1e40af;
500
+ font-size: 13.5px;
501
+ margin-bottom: 18px;
502
+ }
503
+
504
+ .accessDenied {
505
+ text-align: center;
506
+ padding: 60px 20px;
507
+ color: var(--p-muted);
508
+ }
509
+
510
+ /* ---------- Theme editor ---------- */
511
+ .themeLayout {
512
+ display: grid;
513
+ grid-template-columns: 1fr 1fr;
514
+ gap: 24px;
515
+ align-items: start;
516
+ }
517
+
518
+ .colorField {
519
+ display: flex;
520
+ align-items: center;
521
+ gap: 10px;
522
+ }
523
+
524
+ .colorSwatch {
525
+ width: 38px;
526
+ height: 38px;
527
+ padding: 0;
528
+ border: 1px solid #cbd5e1;
529
+ border-radius: 8px;
530
+ cursor: pointer;
531
+ background: none;
532
+ }
533
+
534
+ .previewSticky {
535
+ position: sticky;
536
+ top: 90px;
537
+ }
538
+
539
+ .previewFrame {
540
+ border: 1px solid var(--p-border);
541
+ border-radius: var(--p-radius);
542
+ overflow: hidden;
543
+ }
544
+
545
+ .previewBar {
546
+ font-size: 11.5px;
547
+ color: var(--p-muted);
548
+ padding: 8px 12px;
549
+ background: #f8fafc;
550
+ border-bottom: 1px solid var(--p-border);
551
+ }
552
+
553
+ .previewSurface {
554
+ padding: 28px;
555
+ }
556
+
557
+ .previewHeading {
558
+ margin: 0 0 10px;
559
+ }
560
+
561
+ .previewText {
562
+ margin: 0 0 18px;
563
+ line-height: 1.6;
564
+ }
565
+
566
+ .previewButton {
567
+ display: inline-block;
568
+ padding: 9px 16px;
569
+ color: #fff;
570
+ font-weight: 600;
571
+ border: none;
572
+ }
573
+
574
+ @media (max-width: 860px) {
575
+ .app {
576
+ grid-template-columns: 1fr;
577
+ height: auto;
578
+ overflow: visible;
579
+ }
580
+ .sidebar {
581
+ height: auto;
582
+ overflow-y: visible;
583
+ }
584
+ .main {
585
+ height: auto;
586
+ overflow-y: visible;
587
+ }
588
+ .themeLayout {
589
+ grid-template-columns: 1fr;
590
+ }
591
+ .formGrid {
592
+ grid-template-columns: 1fr;
593
+ }
594
+ .builderLayout {
595
+ grid-template-columns: 1fr;
596
+ }
597
+ }
598
+
599
+ /* ---------- Full-screen page builder ---------- */
600
+ .fsBuilder {
601
+ position: fixed;
602
+ inset: 0;
603
+ display: grid;
604
+ grid-template-rows: 56px 1fr;
605
+ background: #0b1220;
606
+ font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
607
+ color: #0f172a;
608
+ z-index: 50;
609
+ }
610
+
611
+ .fsTop {
612
+ display: flex;
613
+ align-items: center;
614
+ gap: 14px;
615
+ padding: 0 16px;
616
+ background: #0b1220;
617
+ color: #e2e8f0;
618
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
619
+ }
620
+
621
+ .fsExit {
622
+ display: inline-flex;
623
+ align-items: center;
624
+ justify-content: center;
625
+ width: 36px;
626
+ height: 32px;
627
+ color: #cbd5e1;
628
+ text-decoration: none;
629
+ border: 1px solid #243049;
630
+ background: #131c31;
631
+ border-radius: 8px;
632
+ flex-shrink: 0;
633
+ }
634
+
635
+ .fsExit:hover {
636
+ background: #1e293b;
637
+ color: #fff;
638
+ border-color: #334155;
639
+ }
640
+
641
+ .fsDeviceToggle {
642
+ display: inline-flex;
643
+ gap: 2px;
644
+ padding: 2px;
645
+ background: #131c31;
646
+ border: 1px solid #243049;
647
+ border-radius: 8px;
648
+ }
649
+
650
+ .fsDeviceBtn {
651
+ display: inline-flex;
652
+ align-items: center;
653
+ justify-content: center;
654
+ width: 34px;
655
+ height: 26px;
656
+ border: none;
657
+ background: transparent;
658
+ color: #94a3b8;
659
+ border-radius: 6px;
660
+ cursor: pointer;
661
+ }
662
+
663
+ .fsDeviceBtn:hover {
664
+ color: #e2e8f0;
665
+ }
666
+
667
+ .fsDeviceBtnActive {
668
+ background: #2563eb;
669
+ color: #fff;
670
+ }
671
+
672
+ .fsTitleInput {
673
+ flex: 1;
674
+ max-width: 210px;
675
+ background: #131c31;
676
+ border: 1px solid #243049;
677
+ color: #fff;
678
+ padding: 8px 12px;
679
+ border-radius: 8px;
680
+ font-size: 14px;
681
+ }
682
+
683
+ .fsTitleInput:focus {
684
+ outline: none;
685
+ border-color: #3b82f6;
686
+ }
687
+
688
+ .fsTopRight {
689
+ margin-left: auto;
690
+ display: flex;
691
+ align-items: center;
692
+ gap: 12px;
693
+ }
694
+
695
+ .fsStatus {
696
+ font-size: 13px;
697
+ color: #94a3b8;
698
+ }
699
+
700
+ .fsDirty {
701
+ font-size: 12.5px;
702
+ font-weight: 600;
703
+ color: #fbbf24;
704
+ }
705
+
706
+ .fsBody {
707
+ display: grid;
708
+ grid-template-columns: 286px 1fr;
709
+ min-height: 0;
710
+ }
711
+
712
+ .fsPanel {
713
+ background: #fff;
714
+ border-right: 1px solid #e2e8f0;
715
+ overflow: hidden;
716
+ display: flex;
717
+ flex-direction: column;
718
+ min-height: 0;
719
+ height: 100%;
720
+ }
721
+
722
+ /* Top pane: selected block inspector (resizable) */
723
+ .fsInspectorPane {
724
+ display: flex;
725
+ flex-direction: column;
726
+ min-height: 0;
727
+ overflow: hidden;
728
+ flex-shrink: 0;
729
+ }
730
+
731
+ .fsPaneHead {
732
+ display: flex;
733
+ align-items: center;
734
+ justify-content: space-between;
735
+ padding: 9px 12px 9px 16px;
736
+ border-bottom: 1px solid #eef2f7;
737
+ font-size: 12px;
738
+ font-weight: 700;
739
+ color: #334155;
740
+ flex-shrink: 0;
741
+ }
742
+
743
+ .fsPaneBody {
744
+ overflow-y: auto;
745
+ min-height: 0;
746
+ padding: 16px 16px 18px;
747
+ }
748
+
749
+ /* Drag handle between the two panes */
750
+ .fsResizer {
751
+ flex-shrink: 0;
752
+ height: 10px;
753
+ border: none;
754
+ border-top: 1px solid #e2e8f0;
755
+ border-bottom: 1px solid #e2e8f0;
756
+ background: #f8fafc;
757
+ cursor: row-resize;
758
+ display: flex;
759
+ align-items: center;
760
+ justify-content: center;
761
+ padding: 0;
762
+ }
763
+
764
+ .fsResizer:hover {
765
+ background: #eff6ff;
766
+ }
767
+
768
+ .fsResizerGrip {
769
+ width: 36px;
770
+ height: 3px;
771
+ border-radius: 999px;
772
+ background: #cbd5e1;
773
+ }
774
+
775
+ .fsResizer:hover .fsResizerGrip {
776
+ background: #2563eb;
777
+ }
778
+
779
+ /* Bottom pane: sections list */
780
+ .fsSectionsPane {
781
+ flex: 1;
782
+ overflow-y: auto;
783
+ min-height: 0;
784
+ }
785
+
786
+ .fsPanelHead {
787
+ font-size: 11px;
788
+ text-transform: uppercase;
789
+ letter-spacing: 0.06em;
790
+ color: #94a3b8;
791
+ font-weight: 700;
792
+ padding: 14px 16px 8px;
793
+ }
794
+
795
+ .fsAddGrid {
796
+ display: grid;
797
+ grid-template-columns: repeat(3, 1fr);
798
+ gap: 8px;
799
+ padding: 8px 0 4px;
800
+ }
801
+
802
+ .fsAddCard {
803
+ width: 100%;
804
+ padding: 12px;
805
+ border: 1px dashed #cbd5e1;
806
+ border-radius: 8px;
807
+ background: #f8fafc;
808
+ color: #2563eb;
809
+ font-size: 13px;
810
+ font-weight: 600;
811
+ cursor: pointer;
812
+ }
813
+
814
+ .fsAddCard:hover {
815
+ border-color: #2563eb;
816
+ background: #eff6ff;
817
+ }
818
+
819
+ .fsPresets {
820
+ display: flex;
821
+ flex-wrap: wrap;
822
+ gap: 6px;
823
+ }
824
+
825
+ .fsPreset {
826
+ width: 34px;
827
+ height: 26px;
828
+ border: 1px solid #e2e8f0;
829
+ border-radius: 6px;
830
+ cursor: pointer;
831
+ padding: 0;
832
+ }
833
+
834
+ .fsPreset:hover {
835
+ outline: 2px solid #2563eb;
836
+ outline-offset: 1px;
837
+ }
838
+
839
+ .fsAddBtn {
840
+ display: flex;
841
+ flex-direction: column;
842
+ align-items: center;
843
+ gap: 6px;
844
+ padding: 12px 6px;
845
+ border: 1px solid #e2e8f0;
846
+ border-radius: 9px;
847
+ background: #f8fafc;
848
+ cursor: pointer;
849
+ font-size: 11.5px;
850
+ color: #334155;
851
+ }
852
+
853
+ .fsAddBtn:hover {
854
+ border-color: #2563eb;
855
+ color: #2563eb;
856
+ background: #eff6ff;
857
+ }
858
+
859
+ .fsAddIcon {
860
+ font-size: 18px;
861
+ line-height: 1;
862
+ }
863
+
864
+ .fsList {
865
+ padding: 6px 12px 10px;
866
+ display: flex;
867
+ flex-direction: column;
868
+ gap: 4px;
869
+ }
870
+
871
+ .fsGroupSection {
872
+ border-bottom: 1px solid #eef2f7;
873
+ }
874
+
875
+ .fsGroupHead {
876
+ display: flex;
877
+ align-items: baseline;
878
+ justify-content: space-between;
879
+ gap: 8px;
880
+ padding: 11px 16px 2px;
881
+ }
882
+
883
+ .fsGroupName {
884
+ font-size: 11px;
885
+ font-weight: 700;
886
+ letter-spacing: 0.04em;
887
+ text-transform: uppercase;
888
+ color: #475569;
889
+ }
890
+
891
+ .fsGroupNote {
892
+ font-size: 10px;
893
+ color: #94a3b8;
894
+ }
895
+
896
+ .fsRow {
897
+ display: flex;
898
+ align-items: center;
899
+ gap: 7px;
900
+ padding: 7px 9px;
901
+ border: 1px solid #e8edf3;
902
+ border-radius: 7px;
903
+ cursor: pointer;
904
+ background: #fff;
905
+ }
906
+
907
+ .fsRow:hover {
908
+ border-color: #cbd5e1;
909
+ background: #f8fafc;
910
+ }
911
+
912
+ .fsRowActive {
913
+ border-color: #2563eb;
914
+ background: #eff6ff;
915
+ }
916
+
917
+ .fsRowDrag {
918
+ border-color: #2563eb;
919
+ box-shadow: inset 0 2px 0 0 #2563eb;
920
+ }
921
+
922
+ .fsGrip {
923
+ display: inline-flex;
924
+ align-items: center;
925
+ color: #cbd5e1;
926
+ cursor: grab;
927
+ flex-shrink: 0;
928
+ }
929
+
930
+ .fsRow:hover .fsGrip {
931
+ color: #94a3b8;
932
+ }
933
+
934
+ .fsGrip:active {
935
+ cursor: grabbing;
936
+ }
937
+
938
+ .fsRowLabel {
939
+ flex: 1;
940
+ font-size: 13px;
941
+ font-weight: 600;
942
+ }
943
+
944
+ .fsRowBtns {
945
+ display: flex;
946
+ gap: 2px;
947
+ }
948
+
949
+ .fsMiniBtn {
950
+ width: 24px;
951
+ height: 24px;
952
+ display: inline-flex;
953
+ align-items: center;
954
+ justify-content: center;
955
+ border: 1px solid #e2e8f0;
956
+ background: #fff;
957
+ border-radius: 6px;
958
+ cursor: pointer;
959
+ color: #64748b;
960
+ font-size: 12px;
961
+ }
962
+
963
+ .fsMiniBtn:hover {
964
+ background: #f1f5f9;
965
+ color: #0f172a;
966
+ }
967
+
968
+ .fsMiniBtn:disabled {
969
+ opacity: 0.35;
970
+ cursor: not-allowed;
971
+ }
972
+
973
+ .fsInspector {
974
+ padding: 14px 16px 28px;
975
+ }
976
+
977
+ .fsGroup {
978
+ margin-top: 18px;
979
+ }
980
+
981
+ .fsGroup:first-child {
982
+ margin-top: 0;
983
+ }
984
+
985
+ .fsGroupTitle {
986
+ font-size: 11px;
987
+ text-transform: uppercase;
988
+ letter-spacing: 0.05em;
989
+ color: #94a3b8;
990
+ font-weight: 700;
991
+ margin: 0 0 10px;
992
+ }
993
+
994
+ .fsControl {
995
+ margin-bottom: 12px;
996
+ }
997
+
998
+ .fsControlLabel {
999
+ display: block;
1000
+ font-size: 11.5px;
1001
+ font-weight: 600;
1002
+ color: #475569;
1003
+ margin-bottom: 5px;
1004
+ }
1005
+
1006
+ .fsInput,
1007
+ .fsSelect,
1008
+ .fsTextarea {
1009
+ width: 100%;
1010
+ padding: 8px 10px;
1011
+ border: 1px solid #cbd5e1;
1012
+ border-radius: 7px;
1013
+ font-size: 13px;
1014
+ box-sizing: border-box;
1015
+ font-family: inherit;
1016
+ background: #fff;
1017
+ color: #0f172a;
1018
+ }
1019
+
1020
+ .fsTextarea {
1021
+ resize: vertical;
1022
+ font-family: ui-monospace, monospace;
1023
+ line-height: 1.55;
1024
+ }
1025
+
1026
+ .fsInput:focus,
1027
+ .fsSelect:focus,
1028
+ .fsTextarea:focus {
1029
+ outline: none;
1030
+ border-color: #2563eb;
1031
+ box-shadow: 0 0 0 3px #eff6ff;
1032
+ }
1033
+
1034
+ .fsRowGrid {
1035
+ display: grid;
1036
+ grid-template-columns: 1fr 1fr;
1037
+ gap: 10px;
1038
+ }
1039
+
1040
+ .fsColorRow {
1041
+ display: flex;
1042
+ align-items: center;
1043
+ gap: 8px;
1044
+ }
1045
+
1046
+ .fsSwatch {
1047
+ width: 34px;
1048
+ height: 34px;
1049
+ padding: 0;
1050
+ border: 1px solid #cbd5e1;
1051
+ border-radius: 7px;
1052
+ cursor: pointer;
1053
+ background: none;
1054
+ flex-shrink: 0;
1055
+ }
1056
+
1057
+ .fsClearBtn {
1058
+ border: 1px solid #e2e8f0;
1059
+ background: #fff;
1060
+ border-radius: 6px;
1061
+ padding: 0 9px;
1062
+ height: 34px;
1063
+ cursor: pointer;
1064
+ color: #64748b;
1065
+ font-size: 12px;
1066
+ }
1067
+
1068
+ .fsClearBtn:hover {
1069
+ background: #f1f5f9;
1070
+ }
1071
+
1072
+ .fsSeg {
1073
+ display: inline-flex;
1074
+ border: 1px solid #cbd5e1;
1075
+ border-radius: 7px;
1076
+ overflow: hidden;
1077
+ }
1078
+
1079
+ .fsSegBtn {
1080
+ padding: 7px 12px;
1081
+ background: #fff;
1082
+ border: none;
1083
+ border-right: 1px solid #e2e8f0;
1084
+ cursor: pointer;
1085
+ font-size: 13px;
1086
+ color: #475569;
1087
+ }
1088
+
1089
+ .fsSegBtn:last-child {
1090
+ border-right: none;
1091
+ }
1092
+
1093
+ .fsSegBtnActive {
1094
+ background: #2563eb;
1095
+ color: #fff;
1096
+ }
1097
+
1098
+ .fsEmpty {
1099
+ padding: 30px 16px;
1100
+ text-align: center;
1101
+ color: #94a3b8;
1102
+ font-size: 13px;
1103
+ }
1104
+
1105
+ .fsCanvas {
1106
+ overflow-y: auto;
1107
+ padding: 0;
1108
+ background: #334155;
1109
+ }
1110
+
1111
+ .fsCanvas[data-device="mobile"] {
1112
+ padding: 24px 0;
1113
+ }
1114
+
1115
+ .fsStage {
1116
+ width: 100%;
1117
+ max-width: 100%;
1118
+ margin: 0 auto;
1119
+ min-height: 100%;
1120
+ transition:
1121
+ width 0.3s ease,
1122
+ max-width 0.3s ease,
1123
+ min-height 0.3s ease,
1124
+ border-radius 0.3s ease,
1125
+ box-shadow 0.3s ease;
1126
+ }
1127
+
1128
+ .fsStage[data-device="mobile"] {
1129
+ width: 390px;
1130
+ max-width: calc(100% - 32px);
1131
+ min-height: 720px;
1132
+ border-radius: 20px;
1133
+ overflow: hidden;
1134
+ box-shadow: 0 16px 50px rgba(0, 0, 0, 0.45);
1135
+ }
1136
+
1137
+ .fsSelectable {
1138
+ position: relative;
1139
+ cursor: pointer;
1140
+ outline: 2px solid transparent;
1141
+ outline-offset: -2px;
1142
+ transition: outline-color 0.12s;
1143
+ }
1144
+
1145
+ .fsSelectable:hover {
1146
+ outline-color: #93c5fd;
1147
+ }
1148
+
1149
+ .fsSelectableActive {
1150
+ outline-color: #2563eb;
1151
+ }
1152
+
1153
+ .fsSelectableActive::before {
1154
+ content: attr(data-label);
1155
+ position: absolute;
1156
+ top: 0;
1157
+ left: 0;
1158
+ z-index: 2;
1159
+ background: #2563eb;
1160
+ color: #fff;
1161
+ font-size: 10.5px;
1162
+ font-weight: 700;
1163
+ padding: 2px 7px;
1164
+ border-radius: 0 0 6px 0;
1165
+ }
1166
+
1167
+ .fsCanvasEmpty {
1168
+ padding: 90px 24px;
1169
+ text-align: center;
1170
+ color: #94a3b8;
1171
+ }
1172
+
1173
+ @media (max-width: 860px) {
1174
+ .fsBody {
1175
+ grid-template-columns: 1fr;
1176
+ }
1177
+ }
1178
+
1179
+ /* ---------- Modal ---------- */
1180
+ .modalOverlay {
1181
+ position: fixed;
1182
+ inset: 0;
1183
+ background: rgba(15, 23, 42, 0.55);
1184
+ display: flex;
1185
+ align-items: center;
1186
+ justify-content: center;
1187
+ padding: 24px;
1188
+ z-index: 100;
1189
+ }
1190
+
1191
+ .modalCard {
1192
+ width: 100%;
1193
+ background: #fff;
1194
+ border-radius: 14px;
1195
+ box-shadow: 0 24px 70px rgba(0, 0, 0, 0.3);
1196
+ display: flex;
1197
+ flex-direction: column;
1198
+ max-height: calc(100vh - 48px);
1199
+ overflow: hidden;
1200
+ }
1201
+
1202
+ .modalHeader {
1203
+ display: flex;
1204
+ align-items: center;
1205
+ justify-content: space-between;
1206
+ padding: 16px 20px;
1207
+ border-bottom: 1px solid var(--p-border);
1208
+ }
1209
+
1210
+ .modalTitle {
1211
+ margin: 0;
1212
+ font-size: 16px;
1213
+ font-weight: 700;
1214
+ }
1215
+
1216
+ .modalClose {
1217
+ border: none;
1218
+ background: transparent;
1219
+ font-size: 16px;
1220
+ color: var(--p-muted);
1221
+ cursor: pointer;
1222
+ width: 30px;
1223
+ height: 30px;
1224
+ border-radius: 7px;
1225
+ }
1226
+
1227
+ .modalClose:hover {
1228
+ background: #f1f5f9;
1229
+ color: var(--p-text);
1230
+ }
1231
+
1232
+ .modalBody {
1233
+ padding: 20px;
1234
+ overflow-y: auto;
1235
+ }
1236
+
1237
+ .modalFooter {
1238
+ display: flex;
1239
+ justify-content: flex-end;
1240
+ gap: 10px;
1241
+ padding: 14px 20px;
1242
+ border-top: 1px solid var(--p-border);
1243
+ background: #f8fafc;
1244
+ }
1245
+
1246
+ /* ---------- Tabs ---------- */
1247
+ .tabs {
1248
+ display: flex;
1249
+ gap: 4px;
1250
+ border-bottom: 1px solid var(--p-border);
1251
+ margin-bottom: 22px;
1252
+ }
1253
+
1254
+ .tab {
1255
+ padding: 10px 16px;
1256
+ border: none;
1257
+ background: transparent;
1258
+ font-size: 14px;
1259
+ font-weight: 600;
1260
+ color: var(--p-muted);
1261
+ cursor: pointer;
1262
+ border-bottom: 2px solid transparent;
1263
+ margin-bottom: -1px;
1264
+ }
1265
+
1266
+ .tab:hover {
1267
+ color: var(--p-text);
1268
+ }
1269
+
1270
+ .tabActive {
1271
+ color: var(--p-primary);
1272
+ border-bottom-color: var(--p-primary);
1273
+ }
1274
+
1275
+ /* ---------- Permission list ---------- */
1276
+ .permList {
1277
+ display: flex;
1278
+ flex-direction: column;
1279
+ gap: 8px;
1280
+ margin-top: 8px;
1281
+ }
1282
+
1283
+ .permItem {
1284
+ display: flex;
1285
+ gap: 10px;
1286
+ align-items: flex-start;
1287
+ padding: 11px 12px;
1288
+ border: 1px solid var(--p-border);
1289
+ border-radius: 9px;
1290
+ cursor: pointer;
1291
+ }
1292
+
1293
+ .permItem:hover {
1294
+ background: #f8fafc;
1295
+ }
1296
+
1297
+ .permItem input {
1298
+ margin-top: 2px;
1299
+ }
1300
+
1301
+ .permLabel {
1302
+ display: block;
1303
+ font-size: 13.5px;
1304
+ font-weight: 600;
1305
+ }
1306
+
1307
+ .permDesc {
1308
+ display: block;
1309
+ font-size: 12px;
1310
+ color: var(--p-muted);
1311
+ margin-top: 2px;
1312
+ }