@rimelight/cms 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 (87) hide show
  1. package/README.md +19 -0
  2. package/package.json +69 -0
  3. package/src/astro/BlockRenderer.astro +111 -0
  4. package/src/astro/PageRenderer.astro +23 -0
  5. package/src/astro/blocks/CalloutBlock.astro +78 -0
  6. package/src/astro/blocks/CardBlock.astro +43 -0
  7. package/src/astro/blocks/CardsBlock.astro +24 -0
  8. package/src/astro/blocks/CodeBlock.astro +16 -0
  9. package/src/astro/blocks/DialogueBlock.astro +23 -0
  10. package/src/astro/blocks/FileTreeBlock.astro +45 -0
  11. package/src/astro/blocks/ImageBlock.astro +17 -0
  12. package/src/astro/blocks/OrderedListBlock.astro +55 -0
  13. package/src/astro/blocks/ParagraphBlock.astro +65 -0
  14. package/src/astro/blocks/SceneBlock.astro +65 -0
  15. package/src/astro/blocks/ScriptBlock.astro +59 -0
  16. package/src/astro/blocks/SectionBlock.astro +46 -0
  17. package/src/astro/blocks/StepItemBlock.astro +25 -0
  18. package/src/astro/blocks/StepsBlock.astro +14 -0
  19. package/src/astro/blocks/TabItemBlock.astro +19 -0
  20. package/src/astro/blocks/TableBlock.astro +49 -0
  21. package/src/astro/blocks/TabsBlock.astro +67 -0
  22. package/src/astro/blocks/UnorderedListBlock.astro +55 -0
  23. package/src/auth/editor-guards.ts +148 -0
  24. package/src/auth/filter-blocks.ts +44 -0
  25. package/src/auth/filter-templates.ts +81 -0
  26. package/src/auth/permissions.ts +40 -0
  27. package/src/cache/purge.ts +37 -0
  28. package/src/client/components/RimelightBlock.tsx +487 -0
  29. package/src/client/components/RimelightBlockPicker.tsx +305 -0
  30. package/src/client/components/RimelightEditor.tsx +131 -0
  31. package/src/client/components/RimelightEditorLayout.tsx +143 -0
  32. package/src/client/components/RimelightPreview.tsx +354 -0
  33. package/src/client/components/RimelightPropertiesPanel.tsx +408 -0
  34. package/src/client/components/RimelightTemplateEditor.tsx +245 -0
  35. package/src/client/components/editors/CalloutEditor.tsx +185 -0
  36. package/src/client/components/editors/CardEditor.tsx +60 -0
  37. package/src/client/components/editors/CardsEditor.tsx +132 -0
  38. package/src/client/components/editors/CodeEditor.tsx +101 -0
  39. package/src/client/components/editors/DialogueEditor.tsx +52 -0
  40. package/src/client/components/editors/FileTreeEditor.tsx +65 -0
  41. package/src/client/components/editors/ImageEditor.tsx +70 -0
  42. package/src/client/components/editors/ParagraphEditor.tsx +314 -0
  43. package/src/client/components/editors/SceneEditor.tsx +245 -0
  44. package/src/client/components/editors/ScriptEditor.tsx +245 -0
  45. package/src/client/components/editors/SectionEditor.tsx +139 -0
  46. package/src/client/components/editors/StepItemEditor.tsx +117 -0
  47. package/src/client/components/editors/StepsEditor.tsx +105 -0
  48. package/src/client/components/editors/TabItemEditor.tsx +113 -0
  49. package/src/client/components/editors/TableEditor.tsx +181 -0
  50. package/src/client/components/editors/TabsEditor.tsx +105 -0
  51. package/src/client/components/index.ts +7 -0
  52. package/src/client/loader.ts +19 -0
  53. package/src/client/state/autosave.ts +122 -0
  54. package/src/client/state/editor-store.ts +411 -0
  55. package/src/client/state/history.ts +251 -0
  56. package/src/client/state/lock-manager.ts +89 -0
  57. package/src/client/styles/cms-editor.css +653 -0
  58. package/src/client/toast.ts +12 -0
  59. package/src/client/utils/sortable.ts +184 -0
  60. package/src/client/utils/splitpane.ts +87 -0
  61. package/src/client/utils/tree-sanitizer.ts +33 -0
  62. package/src/core/page-definitions.ts +502 -0
  63. package/src/core/registry.ts +99 -0
  64. package/src/core/template-sync.ts +75 -0
  65. package/src/core/types/blocks.ts +367 -0
  66. package/src/core/types/inline.ts +23 -0
  67. package/src/core/types/pages.ts +36 -0
  68. package/src/core/types/templates.ts +32 -0
  69. package/src/core/types/versioning.ts +44 -0
  70. package/src/core/validator.ts +28 -0
  71. package/src/env.d.ts +4 -0
  72. package/src/index.ts +47 -0
  73. package/src/markdown/index.ts +2 -0
  74. package/src/markdown/parser.ts +347 -0
  75. package/src/markdown/serializer.ts +219 -0
  76. package/src/migration.ts +155 -0
  77. package/src/schema/index.ts +8 -0
  78. package/src/schema/page_draft_locks.ts +13 -0
  79. package/src/schema/page_drafts.ts +18 -0
  80. package/src/schema/page_templates.ts +27 -0
  81. package/src/schema/page_version_approvals.ts +12 -0
  82. package/src/schema/page_version_comments.ts +14 -0
  83. package/src/schema/page_versions.ts +34 -0
  84. package/src/schema/pages.ts +42 -0
  85. package/src/schema/search.ts +23 -0
  86. package/src/search/query.ts +10 -0
  87. package/src/search/sync.ts +32 -0
@@ -0,0 +1,89 @@
1
+ export interface LockState {
2
+ isLocked: boolean
3
+ lockedByUserId?: string
4
+ lockedByUserName?: string
5
+ expiresAt?: string
6
+ }
7
+
8
+ export class LockManager {
9
+ private pageId: string
10
+ private heartbeatInterval: ReturnType<typeof setInterval> | null = null
11
+ private heartbeatMs: number
12
+
13
+ constructor(pageId: string, heartbeatMs = 30000) {
14
+ this.pageId = pageId
15
+ this.heartbeatMs = heartbeatMs
16
+ }
17
+
18
+ async acquireCheckout(
19
+ userId: string,
20
+ userName: string
21
+ ): Promise<{ success: boolean; lockState: LockState }> {
22
+ try {
23
+ const res = await fetch(`/api/cms/pages/${this.pageId}/lock/checkout`, {
24
+ method: "POST",
25
+ headers: { "Content-Type": "application/json" },
26
+ body: JSON.stringify({ userId, userName })
27
+ })
28
+
29
+ const data = await res.json()
30
+ if (res.ok && data.success) {
31
+ this.startHeartbeat(userId)
32
+ return {
33
+ success: true,
34
+ lockState: { isLocked: false }
35
+ }
36
+ } else {
37
+ return {
38
+ success: false,
39
+ lockState: {
40
+ isLocked: true,
41
+ lockedByUserId: data.lockedByUserId,
42
+ lockedByUserName: data.lockedByUserName,
43
+ expiresAt: data.expiresAt
44
+ }
45
+ }
46
+ }
47
+ } catch {
48
+ return {
49
+ success: false,
50
+ lockState: { isLocked: true, lockedByUserName: "Unknown (Network Error)" }
51
+ }
52
+ }
53
+ }
54
+
55
+ private startHeartbeat(userId: string): void {
56
+ this.stopHeartbeat()
57
+ this.heartbeatInterval = setInterval(async () => {
58
+ try {
59
+ await fetch(`/api/cms/pages/${this.pageId}/lock/heartbeat`, {
60
+ method: "POST",
61
+ headers: { "Content-Type": "application/json" },
62
+ body: JSON.stringify({ userId })
63
+ })
64
+ } catch {
65
+ // Silently retry on next heartbeat interval
66
+ }
67
+ }, this.heartbeatMs)
68
+ }
69
+
70
+ stopHeartbeat(): void {
71
+ if (this.heartbeatInterval !== null) {
72
+ clearInterval(this.heartbeatInterval)
73
+ this.heartbeatInterval = null
74
+ }
75
+ }
76
+
77
+ async releaseCheckout(userId: string): Promise<void> {
78
+ this.stopHeartbeat()
79
+ try {
80
+ await fetch(`/api/cms/pages/${this.pageId}/lock/release`, {
81
+ method: "POST",
82
+ headers: { "Content-Type": "application/json" },
83
+ body: JSON.stringify({ userId })
84
+ })
85
+ } catch {
86
+ // Ignore cleanup error
87
+ }
88
+ }
89
+ }
@@ -0,0 +1,653 @@
1
+ /* Custom Elements display & sizing defaults */
2
+ rimelight-block,
3
+ rimelight-paragraph-editor,
4
+ rimelight-section-editor,
5
+ rimelight-callout-editor,
6
+ rimelight-image-editor,
7
+ rimelight-code-editor,
8
+ rimelight-script-editor,
9
+ rimelight-scene-editor,
10
+ rimelight-dialogue-editor {
11
+ display: block;
12
+ width: 100%;
13
+ box-sizing: border-box;
14
+ }
15
+
16
+ rimelight-properties-panel {
17
+ display: block;
18
+ width: 18rem;
19
+ flex-shrink: 0;
20
+ height: 100%;
21
+ box-sizing: border-box;
22
+ }
23
+
24
+ :host,
25
+ rimelight-editor-layout,
26
+ .rimelight-cms-editor-container {
27
+ display: flex;
28
+ flex-direction: row;
29
+ flex: 1 1 100%;
30
+ width: 100%;
31
+ height: 100%;
32
+ min-height: 600px;
33
+ box-sizing: border-box;
34
+ font-family:
35
+ system-ui,
36
+ -apple-system,
37
+ BlinkMacSystemFont,
38
+ "Segoe UI",
39
+ Roboto,
40
+ sans-serif;
41
+ background-color: transparent;
42
+ color: inherit;
43
+ }
44
+
45
+ .cms-split-layout {
46
+ display: flex;
47
+ flex-direction: row;
48
+ flex: 1 1 100%;
49
+ width: 100%;
50
+ height: 100%;
51
+ min-height: 600px;
52
+ overflow: hidden;
53
+ position: relative;
54
+ background-color: transparent;
55
+ }
56
+
57
+ /* Astro's client:only wraps the component in <astro-island> which defaults to display:inline.
58
+ Scope to .cms-editor-main so only the editor island is affected, not the properties panel. */
59
+ .cms-editor-main > astro-island {
60
+ display: flex;
61
+ flex: 1 1 100%;
62
+ width: 100%;
63
+ height: 100%;
64
+ }
65
+
66
+ rimelight-editor,
67
+ .cms-editor-pane {
68
+ flex: 1 1 100%;
69
+ width: 100%;
70
+ height: 100%;
71
+ min-width: 300px;
72
+ display: flex;
73
+ flex-direction: column;
74
+ background-color: transparent;
75
+ border-right: 1px solid var(--rl-border, rgba(128, 128, 128, 0.2));
76
+ overflow: visible;
77
+ box-sizing: border-box;
78
+ }
79
+
80
+ rimelight-preview,
81
+ .cms-preview-pane {
82
+ display: flex;
83
+ flex: 1 1 100%;
84
+ width: 100%;
85
+ height: 100%;
86
+ min-width: 0;
87
+ background-color: transparent;
88
+ overflow-y: auto;
89
+ box-sizing: border-box;
90
+ }
91
+
92
+ /* Canvas & scroll containers */
93
+ #cms-canvas-scroll,
94
+ .cms-block-canvas {
95
+ flex: 1 1 100%;
96
+ width: 100%;
97
+ height: 100%;
98
+ padding: 1.5rem;
99
+ overflow-y: auto;
100
+ display: flex;
101
+ flex-direction: column;
102
+ gap: 1rem;
103
+ box-sizing: border-box;
104
+ background-color: transparent;
105
+ }
106
+
107
+ #cms-canvas {
108
+ width: 100%;
109
+ flex: 1 1 auto;
110
+ display: flex;
111
+ flex-direction: column;
112
+ gap: 0.75rem;
113
+ box-sizing: border-box;
114
+ }
115
+
116
+ #cms-footer {
117
+ width: 100%;
118
+ box-sizing: border-box;
119
+ }
120
+
121
+ .cms-block-node {
122
+ width: 100%;
123
+ box-sizing: border-box;
124
+ background-color: transparent;
125
+ border: none;
126
+ padding: 0.5rem 0;
127
+ position: relative;
128
+ }
129
+
130
+ /* Ensure inputs in block content span 100% width and shrink-safe */
131
+ .cms-block-content input[type="text"],
132
+ .cms-block-content textarea,
133
+ .cms-block-content select {
134
+ width: 100%;
135
+ min-width: 0;
136
+ box-sizing: border-box;
137
+ }
138
+
139
+ .cms-resize-handle {
140
+ display: flex;
141
+ align-items: center;
142
+ justify-content: center;
143
+ width: 14px;
144
+ cursor: col-resize;
145
+ background-color: transparent;
146
+ border-left: 1px solid rgba(75, 85, 99, 0.3);
147
+ border-right: 1px solid rgba(75, 85, 99, 0.3);
148
+ transition: background-color 0.15s ease;
149
+ z-index: 20;
150
+ user-select: none;
151
+ flex-shrink: 0;
152
+ }
153
+
154
+ .cms-resize-handle:hover,
155
+ .cms-resize-handle.active {
156
+ background-color: rgba(59, 130, 246, 0.15);
157
+ }
158
+
159
+ .cms-resize-pill {
160
+ width: 4px;
161
+ height: 32px;
162
+ border-radius: 9999px;
163
+ background-color: #4b5563;
164
+ transition: background-color 0.15s ease;
165
+ }
166
+
167
+ .cms-resize-handle:hover .cms-resize-pill,
168
+ .cms-resize-handle.active .cms-resize-pill {
169
+ background-color: #3b82f6;
170
+ }
171
+
172
+ /* Editor Header & Status Bar */
173
+ .cms-editor-header {
174
+ display: flex;
175
+ align-items: center;
176
+ justify-content: space-between;
177
+ padding: 0.75rem 1rem;
178
+ background-color: #1f2937;
179
+ border-bottom: 1px solid #374151;
180
+ }
181
+
182
+ .cms-status-badge {
183
+ display: inline-flex;
184
+ align-items: center;
185
+ gap: 0.375rem;
186
+ padding: 0.25rem 0.625rem;
187
+ border-radius: 9999px;
188
+ font-size: 0.75rem;
189
+ font-weight: 600;
190
+ }
191
+
192
+ .cms-status-saved {
193
+ background-color: rgba(16, 185, 129, 0.15);
194
+ color: #34d399;
195
+ }
196
+ .cms-status-unsaved {
197
+ background-color: rgba(245, 158, 11, 0.15);
198
+ color: #fbbf24;
199
+ }
200
+ .cms-status-saving {
201
+ background-color: rgba(59, 130, 246, 0.15);
202
+ color: #60a5fa;
203
+ }
204
+ .cms-status-error {
205
+ background-color: rgba(239, 68, 68, 0.15);
206
+ color: #f87171;
207
+ }
208
+
209
+ /* Perforce Checkout Lock Banner */
210
+ .cms-lock-banner {
211
+ background-color: #7c2d12;
212
+ color: #ffedd5;
213
+ padding: 0.5rem 1rem;
214
+ font-size: 0.875rem;
215
+ display: flex;
216
+ align-items: center;
217
+ justify-content: space-between;
218
+ border-bottom: 1px solid #9a3412;
219
+ }
220
+
221
+ /* Block Item Canvas */
222
+ .cms-block-canvas {
223
+ flex: 1 1 auto;
224
+ width: 100%;
225
+ padding: 1.5rem;
226
+ overflow-y: auto;
227
+ display: flex;
228
+ flex-direction: column;
229
+ gap: 1rem;
230
+ box-sizing: border-box;
231
+ }
232
+
233
+ #cms-canvas {
234
+ width: 100%;
235
+ flex: 1 1 auto;
236
+ display: flex;
237
+ flex-direction: column;
238
+ gap: 0.75rem;
239
+ box-sizing: border-box;
240
+ }
241
+
242
+ #cms-footer {
243
+ width: 100%;
244
+ box-sizing: border-box;
245
+ }
246
+
247
+ .cms-contenteditable {
248
+ outline: none;
249
+ min-height: 1.5rem;
250
+ }
251
+
252
+ .cms-contenteditable:focus {
253
+ background-color: rgba(255, 255, 255, 0.03);
254
+ border-radius: 0.25rem;
255
+ }
256
+
257
+ /* ─── Block hover grip & context menu ──────────────────────────────────────── */
258
+
259
+ .cms-block-wrap {
260
+ position: relative;
261
+ width: 100%;
262
+ }
263
+
264
+ .cms-block-grip {
265
+ position: absolute;
266
+ left: -1.75rem;
267
+ top: 0.25rem;
268
+ z-index: 30;
269
+ opacity: 0;
270
+ transition: opacity 0.15s ease;
271
+ }
272
+
273
+ .cms-block-wrap:hover .cms-block-grip,
274
+ .cms-block-wrap:focus-within .cms-block-grip {
275
+ opacity: 1;
276
+ }
277
+
278
+ .cms-grip-btn {
279
+ display: flex;
280
+ align-items: center;
281
+ justify-content: center;
282
+ border-radius: 0.375rem;
283
+ cursor: grab;
284
+ color: var(--rl-text-muted, #9ca3af);
285
+ background: transparent;
286
+ border: none;
287
+ font-size: 1rem;
288
+ user-select: none;
289
+ transition:
290
+ background-color 0.15s ease,
291
+ color 0.15s ease;
292
+ }
293
+
294
+ .cms-grip-btn:hover {
295
+ background-color: var(--rl-bg-hover, rgba(128, 128, 128, 0.1));
296
+ color: var(--rl-text-highlighted, #f3f4f6);
297
+ }
298
+
299
+ .cms-grip-btn:active {
300
+ cursor: grabbing;
301
+ }
302
+
303
+ /* ─── SortableJS Ghost Drop Indicator ─────────────────────────────────────── */
304
+
305
+ .cms-sortable-ghost {
306
+ opacity: 0.85 !important;
307
+ background-color: rgba(59, 130, 246, 0.12) !important;
308
+ border: 2px dashed #3b82f6 !important;
309
+ box-shadow: 0 0 12px rgba(59, 130, 246, 0.3) !important;
310
+ border-radius: 0.5rem !important;
311
+ min-height: 48px !important;
312
+ position: relative !important;
313
+ }
314
+
315
+ .cms-sortable-ghost * {
316
+ opacity: 0.5 !important;
317
+ }
318
+
319
+ .cms-sortable-chosen {
320
+ background-color: #1f2937 !important;
321
+ opacity: 0.9 !important;
322
+ }
323
+
324
+ .cms-sortable-drag {
325
+ opacity: 0.95 !important;
326
+ transform: scale(1.02);
327
+ box-shadow: 0 12px 30px -5px rgba(0, 0, 0, 0.7) !important;
328
+ cursor: grabbing !important;
329
+ z-index: 9999 !important;
330
+ background-color: #111827 !important;
331
+ border: 1px solid #3b82f6 !important;
332
+ border-radius: 0.5rem !important;
333
+ }
334
+
335
+ .cms-block-menu {
336
+ position: absolute;
337
+ left: 0;
338
+ top: 2rem;
339
+ z-index: 50;
340
+ width: 12rem;
341
+ background-color: #111827;
342
+ border: 1px solid #374151;
343
+ border-radius: 0.75rem;
344
+ box-shadow:
345
+ 0 20px 25px -5px rgba(0, 0, 0, 0.5),
346
+ 0 10px 10px -5px rgba(0, 0, 0, 0.2);
347
+ overflow: hidden;
348
+ padding: 0.25rem;
349
+ }
350
+
351
+ .cms-menu-item {
352
+ display: flex;
353
+ align-items: center;
354
+ gap: 0.5rem;
355
+ width: 100%;
356
+ padding: 0.375rem 0.75rem;
357
+ font-size: 0.75rem;
358
+ text-align: left;
359
+ background: transparent;
360
+ border: none;
361
+ border-radius: 0.375rem;
362
+ color: #d1d5db;
363
+ cursor: pointer;
364
+ transition: background-color 0.1s ease;
365
+ }
366
+
367
+ .cms-menu-item:hover {
368
+ background-color: #1f2937;
369
+ }
370
+
371
+ .cms-menu-item.danger {
372
+ color: #f87171;
373
+ }
374
+
375
+ .cms-menu-item.danger:hover {
376
+ background-color: #450a0a;
377
+ }
378
+
379
+ .cms-menu-separator {
380
+ height: 1px;
381
+ background-color: #1f2937;
382
+ margin: 0.25rem 0;
383
+ }
384
+
385
+ /* ─── Block content padding for grip handle ─────────────────────────────────── */
386
+
387
+ .cms-block-canvas .cms-block-node {
388
+ padding-left: 2.5rem;
389
+ position: relative;
390
+ }
391
+
392
+ /* ─── Block picker modal ────────────────────────────────────────────────────── */
393
+
394
+ .cms-block-picker-overlay {
395
+ position: fixed;
396
+ inset: 0;
397
+ z-index: 9999;
398
+ display: flex;
399
+ align-items: center;
400
+ justify-content: center;
401
+ padding: 1rem;
402
+ background-color: rgba(0, 0, 0, 0.6);
403
+ backdrop-filter: blur(4px);
404
+ }
405
+
406
+ .cms-block-picker-panel {
407
+ background-color: #111827;
408
+ border: 1px solid #374151;
409
+ border-radius: 1rem;
410
+ width: 100%;
411
+ max-width: 28rem;
412
+ max-height: 80vh;
413
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.8);
414
+ display: flex;
415
+ flex-direction: column;
416
+ overflow: hidden;
417
+ }
418
+
419
+ .cms-block-picker-search {
420
+ padding: 0.75rem 1rem;
421
+ border-bottom: 1px solid #1f2937;
422
+ }
423
+
424
+ .cms-block-picker-search input {
425
+ width: 100%;
426
+ background-color: #1f2937;
427
+ border: 1px solid #374151;
428
+ border-radius: 0.5rem;
429
+ padding: 0.5rem 0.75rem;
430
+ font-size: 0.875rem;
431
+ color: #f3f4f6;
432
+ outline: none;
433
+ }
434
+
435
+ .cms-block-picker-search input:focus {
436
+ border-color: #3b82f6;
437
+ }
438
+
439
+ .cms-block-picker-list {
440
+ overflow-y: auto;
441
+ flex: 1;
442
+ padding-bottom: 0.5rem;
443
+ }
444
+
445
+ .cms-block-picker-category {
446
+ font-size: 0.625rem;
447
+ font-weight: 700;
448
+ letter-spacing: 0.1em;
449
+ text-transform: uppercase;
450
+ color: #6b7280;
451
+ padding: 0.75rem 1rem 0.25rem;
452
+ }
453
+
454
+ .cms-block-picker-item {
455
+ display: flex;
456
+ align-items: center;
457
+ gap: 0.75rem;
458
+ padding: 0.625rem 1rem;
459
+ cursor: pointer;
460
+ transition: background-color 0.1s ease;
461
+ }
462
+
463
+ .cms-block-picker-item:hover,
464
+ .cms-block-picker-item.active {
465
+ background-color: #1f2937;
466
+ }
467
+
468
+ .cms-block-picker-item-icon {
469
+ width: 2rem;
470
+ height: 2rem;
471
+ display: flex;
472
+ align-items: center;
473
+ justify-content: center;
474
+ font-size: 1.125rem;
475
+ flex-shrink: 0;
476
+ }
477
+
478
+ /* ─── Properties panel ──────────────────────────────────────────────────────── */
479
+
480
+ .cms-properties-panel {
481
+ min-width: 280px;
482
+ max-width: 320px;
483
+ }
484
+
485
+ .cms-prop-group-header {
486
+ display: flex;
487
+ align-items: center;
488
+ justify-content: space-between;
489
+ width: 100%;
490
+ padding: 0.5rem 0;
491
+ background: transparent;
492
+ border: none;
493
+ color: #9ca3af;
494
+ font-size: 0.75rem;
495
+ font-weight: 600;
496
+ text-transform: uppercase;
497
+ letter-spacing: 0.05em;
498
+ cursor: pointer;
499
+ border-bottom: 1px solid #1f2937;
500
+ }
501
+
502
+ .cms-prop-group-header:hover {
503
+ color: #d1d5db;
504
+ }
505
+
506
+ /* ─── Inline editors ──────────────────────────────────────────────────────────*/
507
+
508
+ .cms-inline-input {
509
+ width: 100%;
510
+ background-color: #1f2937;
511
+ border: 1px solid #374151;
512
+ border-radius: 0.375rem;
513
+ padding: 0.375rem 0.625rem;
514
+ font-size: 0.875rem;
515
+ color: #f3f4f6;
516
+ outline: none;
517
+ transition: border-color 0.15s ease;
518
+ }
519
+
520
+ .cms-inline-input:focus {
521
+ border-color: #3b82f6;
522
+ }
523
+
524
+ .cms-inline-label {
525
+ display: block;
526
+ font-size: 0.625rem;
527
+ font-weight: 600;
528
+ letter-spacing: 0.1em;
529
+ text-transform: uppercase;
530
+ color: #6b7280;
531
+ margin-bottom: 0.25rem;
532
+ }
533
+
534
+ /* ─── Editor layout with properties panel ────────────────────────────────────*/
535
+
536
+ .cms-editor-with-panel {
537
+ display: flex;
538
+ width: 100%;
539
+ height: 100%;
540
+ overflow: hidden;
541
+ }
542
+
543
+ .cms-editor-main {
544
+ flex: 1;
545
+ min-width: 0;
546
+ overflow: hidden;
547
+ display: flex;
548
+ flex-direction: column;
549
+ }
550
+
551
+ /* ─── Paragraph editor toolbar ───────────────────────────────────────────────*/
552
+
553
+ .cms-para-toolbar {
554
+ display: flex;
555
+ align-items: center;
556
+ gap: 0.25rem;
557
+ padding: 0.25rem 0.5rem;
558
+ background-color: #1f2937;
559
+ border: 1px solid #374151;
560
+ border-bottom: none;
561
+ border-radius: 0.375rem 0.375rem 0 0;
562
+ }
563
+
564
+ .cms-para-toolbar-btn {
565
+ padding: 0.25rem 0.5rem;
566
+ border-radius: 0.25rem;
567
+ border: none;
568
+ background: transparent;
569
+ color: #9ca3af;
570
+ font-size: 0.75rem;
571
+ font-weight: 700;
572
+ cursor: pointer;
573
+ transition:
574
+ background-color 0.1s ease,
575
+ color 0.1s ease;
576
+ }
577
+
578
+ .cms-para-toolbar-btn:hover {
579
+ background-color: #374151;
580
+ color: #f3f4f6;
581
+ }
582
+
583
+ /* ─── Empty state ────────────────────────────────────────────────────────────*/
584
+
585
+ .cms-empty-state {
586
+ display: flex;
587
+ flex-direction: column;
588
+ align-items: center;
589
+ justify-content: center;
590
+ width: 100%;
591
+ flex: 1 1 auto;
592
+ min-height: 300px;
593
+ padding: 4rem 1rem;
594
+ text-align: center;
595
+ color: #6b7280;
596
+ box-sizing: border-box;
597
+ }
598
+
599
+ .cms-empty-state h4 {
600
+ font-size: 0.875rem;
601
+ font-weight: 500;
602
+ color: #9ca3af;
603
+ margin: 0.5rem 0 0.25rem;
604
+ }
605
+
606
+ .cms-empty-state p {
607
+ font-size: 0.75rem;
608
+ margin-bottom: 1rem;
609
+ }
610
+
611
+ .cms-add-first-block {
612
+ padding: 0.5rem 1rem;
613
+ background-color: #2563eb;
614
+ color: #fff;
615
+ font-size: 0.75rem;
616
+ font-weight: 500;
617
+ border: none;
618
+ border-radius: 0.5rem;
619
+ cursor: pointer;
620
+ transition: background-color 0.15s ease;
621
+ }
622
+
623
+ .cms-add-first-block:hover {
624
+ background-color: #1d4ed8;
625
+ }
626
+
627
+ .cms-add-block-footer {
628
+ display: flex;
629
+ align-items: center;
630
+ justify-content: center;
631
+ padding: 1rem;
632
+ }
633
+
634
+ .cms-add-block-btn {
635
+ display: flex;
636
+ align-items: center;
637
+ gap: 0.5rem;
638
+ padding: 0.5rem 1rem;
639
+ border: 1px dashed #374151;
640
+ border-radius: 0.5rem;
641
+ background: transparent;
642
+ color: #6b7280;
643
+ font-size: 0.75rem;
644
+ cursor: pointer;
645
+ transition:
646
+ border-color 0.15s ease,
647
+ color 0.15s ease;
648
+ }
649
+
650
+ .cms-add-block-btn:hover {
651
+ border-color: #3b82f6;
652
+ color: #60a5fa;
653
+ }