@bendyline/squisq-editor-react 1.6.2 → 2.0.0

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 (200) hide show
  1. package/README.md +12 -3
  2. package/dist/index.d.ts +1229 -179
  3. package/dist/index.js +18017 -11231
  4. package/dist/index.js.map +1 -1
  5. package/dist/monaco.d.ts +1 -0
  6. package/dist/monaco.js +4 -0
  7. package/dist/monaco.js.map +1 -0
  8. package/dist/styles/index.css +1471 -245
  9. package/package.json +9 -5
  10. package/src/BlockPropertiesPopover.tsx +30 -15
  11. package/src/DocumentSettingsDialog.tsx +13 -21
  12. package/src/EditorContext.tsx +199 -42
  13. package/src/EditorShell.tsx +348 -260
  14. package/src/ImageEditor.tsx +22 -1
  15. package/src/InlinePreviewGutter.tsx +16 -8
  16. package/src/MediaBin.tsx +107 -14
  17. package/src/OutlinePanel.tsx +202 -7
  18. package/src/PlainHtmlPreview.tsx +40 -1
  19. package/src/PreviewControls.tsx +309 -69
  20. package/src/PreviewPanel.tsx +312 -78
  21. package/src/RawEditor.tsx +13 -2
  22. package/src/StatusBar.tsx +5 -2
  23. package/src/TemplateAnnotation.ts +12 -3
  24. package/src/TemplatePicker.tsx +75 -21
  25. package/src/ThemeCustomizerPanel.tsx +8 -0
  26. package/src/ThemePicker.tsx +10 -5
  27. package/src/TimelineBlockPreview.tsx +1 -1
  28. package/src/Toolbar.tsx +202 -41
  29. package/src/TransitionPicker.tsx +36 -4
  30. package/src/ViewMenuPanel.tsx +12 -14
  31. package/src/WysiwygEditor.tsx +124 -91
  32. package/src/__tests__/blockPropertiesPopoverTheme.test.tsx +33 -0
  33. package/src/__tests__/blockTagActivity.test.ts +183 -0
  34. package/src/__tests__/buildDocumentPreviewMarkdown.test.ts +75 -0
  35. package/src/__tests__/buildPreviewDocContent.test.ts +48 -0
  36. package/src/__tests__/buildPreviewDocTransition.test.ts +21 -1
  37. package/src/__tests__/canvasSurfaceTextEditing.test.tsx +60 -0
  38. package/src/__tests__/customLayoutManagerTheme.test.tsx +34 -0
  39. package/src/__tests__/documentSettingsDialog.test.tsx +29 -1
  40. package/src/__tests__/editorScrollHandoff.test.tsx +102 -0
  41. package/src/__tests__/editorScrollSync.test.ts +65 -0
  42. package/src/__tests__/editorShellProps.test.tsx +123 -1
  43. package/src/__tests__/emojiPicker.test.tsx +2 -42
  44. package/src/__tests__/headingTransition.test.ts +0 -12
  45. package/src/__tests__/imageEditAffordance.test.tsx +5 -1
  46. package/src/__tests__/imageEditModalContract.test.tsx +119 -0
  47. package/src/__tests__/imageEditorShell.test.tsx +15 -1
  48. package/src/__tests__/jsonEditor.test.tsx +56 -1
  49. package/src/__tests__/layersPanel.test.tsx +75 -5
  50. package/src/__tests__/majorApiTypes.test.ts +26 -0
  51. package/src/__tests__/mediaBinDrop.test.tsx +90 -0
  52. package/src/__tests__/narrationSave.test.ts +148 -0
  53. package/src/__tests__/outlinePanel.test.tsx +93 -3
  54. package/src/__tests__/outlineSource.test.ts +217 -0
  55. package/src/__tests__/plainHtmlPreview.test.tsx +17 -1
  56. package/src/__tests__/presentationMode.test.tsx +290 -0
  57. package/src/__tests__/previewControls.test.tsx +218 -5
  58. package/src/__tests__/previewPanelAudioFailure.test.tsx +46 -0
  59. package/src/__tests__/previewPanelPresentation.test.tsx +126 -0
  60. package/src/__tests__/previewPanelTransforms.test.tsx +117 -0
  61. package/src/__tests__/rawEditorModelIsolation.test.ts +19 -0
  62. package/src/__tests__/resolveBlockVisual.test.ts +43 -0
  63. package/src/__tests__/statusBar.test.tsx +27 -0
  64. package/src/__tests__/teleprompter.test.tsx +190 -0
  65. package/src/__tests__/templateAnnotationRoundTrip.test.ts +26 -0
  66. package/src/__tests__/templatePickerPortal.test.tsx +62 -0
  67. package/src/__tests__/tiptapBridge.test.ts +23 -0
  68. package/src/__tests__/tiptapCodeBlockRoundTrip.test.ts +109 -0
  69. package/src/__tests__/transformStyleId.test.ts +13 -0
  70. package/src/__tests__/useImageEditor.test.tsx +67 -0
  71. package/src/__tests__/useMediaRecorder.test.ts +75 -0
  72. package/src/__tests__/viewMenuPanel.test.tsx +130 -0
  73. package/src/__tests__/wysiwygImageUpload.test.ts +69 -0
  74. package/src/__tests__/wysiwygTemplateBadgeFocus.test.tsx +56 -0
  75. package/src/asciiDiagram/AsciiDiagramExtension.ts +343 -0
  76. package/src/asciiDiagram/AsciiDiagramWidget.tsx +224 -0
  77. package/src/asciiDiagram/RepairableDiagramExtension.ts +200 -0
  78. package/src/asciiDiagram/__tests__/AsciiDiagramExtension.test.ts +145 -0
  79. package/src/asciiDiagram/__tests__/RepairableDiagramExtension.test.ts +159 -0
  80. package/src/asciiDiagram/__tests__/asciiDiagramCommands.test.ts +227 -0
  81. package/src/asciiDiagram/__tests__/asciiDiagramOps.test.ts +114 -0
  82. package/src/asciiDiagram/__tests__/asciiPaste.test.ts +43 -0
  83. package/src/asciiDiagram/asciiDiagramCommands.ts +148 -0
  84. package/src/asciiDiagram/asciiDiagramData.ts +116 -0
  85. package/src/asciiDiagram/asciiDiagramOps.ts +219 -0
  86. package/src/asciiDiagram/asciiPaste.ts +19 -0
  87. package/src/blockTagActivity.ts +233 -0
  88. package/src/buildDocumentPreviewMarkdown.ts +168 -0
  89. package/src/buildPreviewDoc.ts +19 -12
  90. package/src/customTemplates/CustomLayoutManager.tsx +3 -2
  91. package/src/customTemplates/CustomTemplateContext.tsx +6 -0
  92. package/src/customTemplates/TemplateDesigner.tsx +7 -4
  93. package/src/customTemplates/__tests__/library.test.ts +8 -0
  94. package/src/customTemplates/designer.css +158 -99
  95. package/src/customTemplates/library.ts +16 -3
  96. package/src/customThemes/CustomThemeContext.tsx +6 -0
  97. package/src/customThemes/CustomThemeDialog.tsx +35 -5
  98. package/src/customThemes/ImportThemeSection.tsx +178 -0
  99. package/src/customThemes/__tests__/customThemeLibrary.test.ts +9 -0
  100. package/src/customThemes/__tests__/importThemeSection.test.tsx +192 -0
  101. package/src/customThemes/customThemeLibrary.ts +7 -4
  102. package/src/customThemes/index.ts +7 -1
  103. package/src/customThemes/themeDraft.ts +23 -7
  104. package/src/diagram/DiagramCanvas.tsx +9 -4
  105. package/src/diagram/types.ts +35 -0
  106. package/src/editorScrollSync.ts +73 -0
  107. package/src/emojiData.ts +3 -23
  108. package/src/headingTransition.ts +0 -15
  109. package/src/imageEditor/CanvasSurface.tsx +29 -23
  110. package/src/imageEditor/LayersPanel.tsx +78 -3
  111. package/src/imageEditor/Toolbar.tsx +5 -1
  112. package/src/imageEditor/icons.tsx +4 -0
  113. package/src/imageEditor/image-editor.css +75 -0
  114. package/src/imageEditor/layers/SelectionHandles.tsx +1 -1
  115. package/src/imageEditor/useImageEditor.ts +71 -14
  116. package/src/index.ts +191 -19
  117. package/src/jsonEditor/JsonEditorContext.tsx +12 -6
  118. package/src/jsonEditor/RenderNode.tsx +24 -3
  119. package/src/jsonEditor/editors.tsx +86 -19
  120. package/src/mediaEntries.ts +12 -0
  121. package/src/monaco.ts +35 -0
  122. package/src/monacoWorkers.ts +89 -0
  123. package/src/outlineSource.ts +171 -0
  124. package/src/presentation/PresentationMode.tsx +596 -0
  125. package/src/rawEditorIsolation.ts +18 -0
  126. package/src/recorder/hooks/useMediaRecorder.ts +97 -53
  127. package/src/resolveBlockVisual.ts +10 -4
  128. package/src/scene/Scene.tsx +48 -18
  129. package/src/scene/SceneBlockExtension.ts +17 -10
  130. package/src/scene/SceneBlockWidget.tsx +5 -1
  131. package/src/scene/SceneViewport.tsx +7 -3
  132. package/src/scene/ShapePalette.tsx +17 -2
  133. package/src/scene/__tests__/DiagramAdapter.test.ts +86 -0
  134. package/src/scene/__tests__/SceneBlockExtension.test.ts +33 -0
  135. package/src/scene/__tests__/sceneIsolation.test.tsx +93 -0
  136. package/src/scene/adapters/DiagramAdapter.ts +12 -101
  137. package/src/scene/commands/SceneCommand.ts +4 -1
  138. package/src/scene/index.ts +1 -6
  139. package/src/scene/layers/DiagramEdges.tsx +21 -7
  140. package/src/scene/layers/edgeGeometry.ts +9 -1
  141. package/src/scene/layers/nodeCard.tsx +27 -8
  142. package/src/scene/scene.css +5 -2
  143. package/src/scene/text/SceneTextOverlay.tsx +5 -5
  144. package/src/scene/text/sceneTextChannel.ts +26 -20
  145. package/src/scene/text/sceneTextConfig.ts +4 -0
  146. package/src/scene/tools/ConnectTool.ts +13 -4
  147. package/src/scene/tools/SceneTool.ts +8 -0
  148. package/src/scene/tools/SelectTool.ts +34 -18
  149. package/src/scene/tools/ShapeTool.ts +2 -3
  150. package/src/styles/ascii-diagram.css +79 -0
  151. package/src/styles/ascii-timeline.css +499 -0
  152. package/src/styles/editor.css +706 -120
  153. package/src/styles/index.css +3 -0
  154. package/src/styles/tree-view.css +139 -0
  155. package/src/teleprompter/TeleprompterControls.tsx +218 -0
  156. package/src/teleprompter/TeleprompterSelfView.tsx +22 -0
  157. package/src/teleprompter/TeleprompterSurface.tsx +267 -0
  158. package/src/teleprompter/TeleprompterView.tsx +338 -0
  159. package/src/teleprompter/canvasRenderer.ts +161 -0
  160. package/src/teleprompter/floatingWindow.ts +352 -0
  161. package/src/teleprompter/index.ts +61 -0
  162. package/src/teleprompter/pcmWorklet.ts +62 -0
  163. package/src/teleprompter/recording/insertPreamble.ts +80 -0
  164. package/src/teleprompter/recording/narrationSave.ts +134 -0
  165. package/src/teleprompter/recording/useNarrationRecorder.ts +367 -0
  166. package/src/teleprompter/scrollModel.ts +85 -0
  167. package/src/teleprompter/teleprompterTheme.ts +303 -0
  168. package/src/teleprompter/types.ts +38 -0
  169. package/src/teleprompter/useFloatingWindow.ts +74 -0
  170. package/src/teleprompter/useMicAnalysis.ts +211 -0
  171. package/src/teleprompter/useTeleprompter.ts +421 -0
  172. package/src/templateContentPreviewResolver.ts +12 -5
  173. package/src/timeline/TimelineEditorWidget.tsx +702 -0
  174. package/src/timeline/TimelineViewExtension.ts +252 -0
  175. package/src/timeline/__tests__/TimelineEditorWidget.test.tsx +355 -0
  176. package/src/timeline/__tests__/TimelineViewExtension.test.ts +163 -0
  177. package/src/timeline/__tests__/timelineCommands.test.ts +327 -0
  178. package/src/timeline/__tests__/timelineOps.test.ts +222 -0
  179. package/src/timeline/__tests__/timelinePaste.test.ts +34 -0
  180. package/src/timeline/timelineCommands.ts +367 -0
  181. package/src/timeline/timelineData.ts +54 -0
  182. package/src/timeline/timelineOps.ts +277 -0
  183. package/src/timeline/timelinePaste.ts +8 -0
  184. package/src/tiptapBridge.ts +24 -16
  185. package/src/transformStyleId.ts +17 -0
  186. package/src/treeview/TreeOutlineWidget.tsx +240 -0
  187. package/src/treeview/TreeViewExtension.ts +250 -0
  188. package/src/treeview/__tests__/TreeViewExtension.test.ts +122 -0
  189. package/src/treeview/__tests__/treeOps.test.ts +125 -0
  190. package/src/treeview/__tests__/treePaste.test.ts +40 -0
  191. package/src/treeview/__tests__/treeViewCommands.test.ts +163 -0
  192. package/src/treeview/treeOps.ts +172 -0
  193. package/src/treeview/treePaste.ts +13 -0
  194. package/src/treeview/treeViewCommands.ts +94 -0
  195. package/src/treeview/treeViewData.ts +41 -0
  196. package/src/useMonacoLoader.ts +11 -34
  197. package/src/wysiwygImageUpload.ts +113 -0
  198. package/src/diagram/DiagramExtension.ts +0 -209
  199. package/src/diagram/DiagramWidget.tsx +0 -272
  200. package/src/diagram/useDiagramData.ts +0 -126
@@ -4,6 +4,61 @@
4
4
  * at the top, and save/cancel actions at the bottom.
5
5
  */
6
6
 
7
+ /* Both entry points are portaled to <body>, so they establish their own
8
+ editor-shell theme scope. These tokens keep the light palette unchanged
9
+ while giving every piece of designer chrome a dark-mode counterpart. */
10
+ .squisq-template-designer-overlay,
11
+ .squisq-layout-manager-overlay {
12
+ color-scheme: light;
13
+ --squisq-layout-panel: #ffffff;
14
+ --squisq-layout-chrome: #f9fafb;
15
+ --squisq-layout-subtle: #fafafa;
16
+ --squisq-layout-workspace: #f3f4f6;
17
+ --squisq-layout-input: #ffffff;
18
+ --squisq-layout-border: #e5e7eb;
19
+ --squisq-layout-border-strong: #d1d5db;
20
+ --squisq-layout-text: #111827;
21
+ --squisq-layout-text-soft: #1f2937;
22
+ --squisq-layout-muted: #6b7280;
23
+ --squisq-layout-faint: #9ca3af;
24
+ --squisq-layout-hover: #f1f5f9;
25
+ --squisq-layout-accent-text: #4338ca;
26
+ --squisq-layout-accent-bg: #4f46e5;
27
+ --squisq-layout-accent-bg-hover: #4338ca;
28
+ --squisq-layout-accent-border: #6366f1;
29
+ --squisq-layout-accent-soft: #eef2ff;
30
+ --squisq-layout-accent-soft-hover: #e0e7ff;
31
+ --squisq-layout-accent-soft-border: #c7d2fe;
32
+ --squisq-layout-accent-hover-border: #a5b4fc;
33
+ --squisq-layout-thumb: #f8fafc;
34
+ }
35
+
36
+ .squisq-template-designer-overlay[data-theme='dark'],
37
+ .squisq-layout-manager-overlay[data-theme='dark'] {
38
+ color-scheme: dark;
39
+ --squisq-layout-panel: #111827;
40
+ --squisq-layout-chrome: #1f2937;
41
+ --squisq-layout-subtle: #18212f;
42
+ --squisq-layout-workspace: #0f172a;
43
+ --squisq-layout-input: #111827;
44
+ --squisq-layout-border: #374151;
45
+ --squisq-layout-border-strong: #4b5563;
46
+ --squisq-layout-text: #f3f4f6;
47
+ --squisq-layout-text-soft: #e5e7eb;
48
+ --squisq-layout-muted: #9ca3af;
49
+ --squisq-layout-faint: #6b7280;
50
+ --squisq-layout-hover: #334155;
51
+ --squisq-layout-accent-text: #c7d2fe;
52
+ --squisq-layout-accent-bg: #4f46e5;
53
+ --squisq-layout-accent-bg-hover: #6366f1;
54
+ --squisq-layout-accent-border: #818cf8;
55
+ --squisq-layout-accent-soft: #312e81;
56
+ --squisq-layout-accent-soft-hover: #3730a3;
57
+ --squisq-layout-accent-soft-border: #4f46e5;
58
+ --squisq-layout-accent-hover-border: #6366f1;
59
+ --squisq-layout-thumb: #0f172a;
60
+ }
61
+
7
62
  .squisq-template-designer-overlay {
8
63
  position: fixed;
9
64
  inset: 0;
@@ -20,7 +75,8 @@
20
75
  flex-direction: column;
21
76
  width: min(1200px, 100%);
22
77
  height: min(800px, 100%);
23
- background: #ffffff;
78
+ color: var(--squisq-layout-text-soft);
79
+ background: var(--squisq-layout-panel);
24
80
  border-radius: 12px;
25
81
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.3);
26
82
  overflow: hidden;
@@ -54,15 +110,15 @@
54
110
  align-items: center;
55
111
  justify-content: space-between;
56
112
  padding: 14px 20px;
57
- border-bottom: 1px solid #e5e7eb;
58
- background: #f9fafb;
113
+ border-bottom: 1px solid var(--squisq-layout-border);
114
+ background: var(--squisq-layout-chrome);
59
115
  }
60
116
 
61
117
  .squisq-template-designer-title {
62
118
  margin: 0;
63
119
  font-size: 16px;
64
120
  font-weight: 600;
65
- color: #111827;
121
+ color: var(--squisq-layout-text);
66
122
  }
67
123
 
68
124
  .squisq-template-designer-close {
@@ -73,7 +129,7 @@
73
129
  justify-content: center;
74
130
  font-size: 24px;
75
131
  line-height: 1;
76
- color: #6b7280;
132
+ color: var(--squisq-layout-muted);
77
133
  background: transparent;
78
134
  border: 1px solid transparent;
79
135
  border-radius: 6px;
@@ -81,9 +137,9 @@
81
137
  }
82
138
 
83
139
  .squisq-template-designer-close:hover {
84
- color: #111827;
85
- background: #f1f5f9;
86
- border-color: #cbd5e1;
140
+ color: var(--squisq-layout-text);
141
+ background: var(--squisq-layout-hover);
142
+ border-color: var(--squisq-layout-border-strong);
87
143
  }
88
144
 
89
145
  .squisq-template-designer-meta {
@@ -91,8 +147,8 @@
91
147
  grid-template-columns: minmax(160px, 1fr) minmax(160px, 1fr) minmax(240px, 2fr);
92
148
  gap: 12px;
93
149
  padding: 12px 20px;
94
- border-bottom: 1px solid #e5e7eb;
95
- background: #fdfdfd;
150
+ border-bottom: 1px solid var(--squisq-layout-border);
151
+ background: var(--squisq-layout-panel);
96
152
  }
97
153
 
98
154
  .squisq-template-designer-field {
@@ -106,21 +162,21 @@
106
162
  font-weight: 600;
107
163
  text-transform: uppercase;
108
164
  letter-spacing: 0.04em;
109
- color: #6b7280;
165
+ color: var(--squisq-layout-muted);
110
166
  }
111
167
 
112
168
  .squisq-template-designer-field input {
113
169
  padding: 6px 10px;
114
170
  font-size: 13px;
115
- color: #111827;
116
- background: #ffffff;
117
- border: 1px solid #d1d5db;
171
+ color: var(--squisq-layout-text);
172
+ background: var(--squisq-layout-input);
173
+ border: 1px solid var(--squisq-layout-border-strong);
118
174
  border-radius: 6px;
119
175
  outline: none;
120
176
  }
121
177
 
122
178
  .squisq-template-designer-field input:focus {
123
- border-color: #6366f1;
179
+ border-color: var(--squisq-layout-accent-border);
124
180
  box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.15);
125
181
  }
126
182
 
@@ -129,7 +185,7 @@
129
185
  font-weight: 400;
130
186
  text-transform: none;
131
187
  letter-spacing: normal;
132
- color: #9ca3af;
188
+ color: var(--squisq-layout-faint);
133
189
  }
134
190
 
135
191
  .squisq-template-designer-body {
@@ -142,8 +198,8 @@
142
198
  width: 220px;
143
199
  flex-shrink: 0;
144
200
  padding: 16px;
145
- border-right: 1px solid #e5e7eb;
146
- background: #fafafa;
201
+ border-right: 1px solid var(--squisq-layout-border);
202
+ background: var(--squisq-layout-subtle);
147
203
  overflow-y: auto;
148
204
  }
149
205
 
@@ -153,13 +209,13 @@
153
209
  font-weight: 700;
154
210
  text-transform: uppercase;
155
211
  letter-spacing: 0.06em;
156
- color: #4338ca;
212
+ color: var(--squisq-layout-accent-text);
157
213
  }
158
214
 
159
215
  .squisq-template-designer-palette-hint {
160
216
  margin: 0 0 12px;
161
217
  font-size: 11px;
162
- color: #6b7280;
218
+ color: var(--squisq-layout-muted);
163
219
  line-height: 1.4;
164
220
  }
165
221
 
@@ -185,7 +241,7 @@
185
241
  font-weight: 700;
186
242
  text-transform: uppercase;
187
243
  letter-spacing: 0.06em;
188
- color: #6b7280;
244
+ color: var(--squisq-layout-muted);
189
245
  }
190
246
 
191
247
  /* Shapes are compact icon tiles in a grid. */
@@ -201,11 +257,11 @@
201
257
  justify-content: center;
202
258
  aspect-ratio: 1 / 1;
203
259
  padding: 4px;
204
- background: #ffffff;
205
- border: 1px solid #e5e7eb;
260
+ background: var(--squisq-layout-input);
261
+ border: 1px solid var(--squisq-layout-border);
206
262
  border-radius: 6px;
207
263
  cursor: grab;
208
- color: #475569;
264
+ color: var(--squisq-layout-muted);
209
265
  transition:
210
266
  background 0.1s,
211
267
  border-color 0.1s;
@@ -216,13 +272,13 @@
216
272
  }
217
273
 
218
274
  .squisq-template-designer-shape-item:hover {
219
- background: #eef2ff;
220
- border-color: #c7d2fe;
275
+ background: var(--squisq-layout-accent-soft);
276
+ border-color: var(--squisq-layout-accent-soft-border);
221
277
  }
222
278
 
223
279
  .squisq-template-designer-shape-item--active {
224
- background: #eef2ff;
225
- border-color: #6366f1;
280
+ background: var(--squisq-layout-accent-soft);
281
+ border-color: var(--squisq-layout-accent-border);
226
282
  }
227
283
 
228
284
  .squisq-template-designer-palette-media-add {
@@ -231,22 +287,22 @@
231
287
  padding: 8px 10px;
232
288
  font-size: 12px;
233
289
  font-weight: 600;
234
- color: #4338ca;
235
- background: #eef2ff;
236
- border: 1px dashed #c7d2fe;
290
+ color: var(--squisq-layout-accent-text);
291
+ background: var(--squisq-layout-accent-soft);
292
+ border: 1px dashed var(--squisq-layout-accent-soft-border);
237
293
  border-radius: 6px;
238
294
  cursor: pointer;
239
295
  }
240
296
 
241
297
  .squisq-template-designer-palette-media-add:hover:not(:disabled) {
242
- background: #e0e7ff;
243
- border-color: #a5b4fc;
298
+ background: var(--squisq-layout-accent-soft-hover);
299
+ border-color: var(--squisq-layout-accent-hover-border);
244
300
  }
245
301
 
246
302
  .squisq-template-designer-palette-media-add:disabled {
247
- color: #9ca3af;
248
- background: #f3f4f6;
249
- border-color: #e5e7eb;
303
+ color: var(--squisq-layout-faint);
304
+ background: var(--squisq-layout-workspace);
305
+ border-color: var(--squisq-layout-border);
250
306
  cursor: not-allowed;
251
307
  }
252
308
 
@@ -256,8 +312,9 @@
256
312
  align-items: flex-start;
257
313
  gap: 2px;
258
314
  padding: 8px 10px;
259
- background: #ffffff;
260
- border: 1px solid #e5e7eb;
315
+ color: var(--squisq-layout-text-soft);
316
+ background: var(--squisq-layout-input);
317
+ border: 1px solid var(--squisq-layout-border);
261
318
  border-radius: 6px;
262
319
  /* Draggable onto the canvas — `grab` hints the drag affordance. */
263
320
  cursor: grab;
@@ -272,24 +329,24 @@
272
329
  }
273
330
 
274
331
  .squisq-template-designer-palette-item:hover {
275
- background: #eef2ff;
276
- border-color: #c7d2fe;
332
+ background: var(--squisq-layout-accent-soft);
333
+ border-color: var(--squisq-layout-accent-soft-border);
277
334
  }
278
335
 
279
336
  .squisq-template-designer-palette-item--active {
280
- background: #eef2ff;
281
- border-color: #6366f1;
337
+ background: var(--squisq-layout-accent-soft);
338
+ border-color: var(--squisq-layout-accent-border);
282
339
  }
283
340
 
284
341
  .squisq-template-designer-palette-item-preview {
285
342
  font-family: 'JetBrains Mono', 'SF Mono', 'Menlo', monospace;
286
343
  font-size: 12px;
287
- color: #4338ca;
344
+ color: var(--squisq-layout-accent-text);
288
345
  }
289
346
 
290
347
  .squisq-template-designer-palette-item-label {
291
348
  font-size: 11px;
292
- color: #6b7280;
349
+ color: var(--squisq-layout-muted);
293
350
  }
294
351
 
295
352
  .squisq-template-designer-stage {
@@ -299,7 +356,7 @@
299
356
  min-width: 0;
300
357
  padding: 12px;
301
358
  gap: 8px;
302
- background: #f3f4f6;
359
+ background: var(--squisq-layout-workspace);
303
360
  }
304
361
 
305
362
  /* Unified controls bar above the canvas: the compact aspect-ratio
@@ -314,8 +371,8 @@
314
371
  align-items: center;
315
372
  gap: 5px;
316
373
  padding: 6px 8px;
317
- background: #ffffff;
318
- border: 1px solid #e5e7eb;
374
+ background: var(--squisq-layout-panel);
375
+ border: 1px solid var(--squisq-layout-border);
319
376
  border-radius: 6px;
320
377
  align-self: stretch;
321
378
  overflow-x: auto;
@@ -327,7 +384,7 @@
327
384
  font-weight: 600;
328
385
  text-transform: uppercase;
329
386
  letter-spacing: 0.04em;
330
- color: #6b7280;
387
+ color: var(--squisq-layout-muted);
331
388
  }
332
389
 
333
390
  /* ─── Contextual layer styling controls ─────────────────── */
@@ -365,7 +422,7 @@
365
422
  width: 28px;
366
423
  height: 28px;
367
424
  font-size: 13px;
368
- color: #374151;
425
+ color: var(--squisq-layout-text-soft);
369
426
  background: transparent;
370
427
  border: 1px solid transparent;
371
428
  border-radius: 4px;
@@ -373,22 +430,22 @@
373
430
  }
374
431
 
375
432
  .squisq-layer-toolbar-btn:hover {
376
- background: #f1f5f9;
433
+ background: var(--squisq-layout-hover);
377
434
  }
378
435
 
379
436
  .squisq-layer-toolbar-btn--active {
380
- background: #eef2ff;
381
- border-color: #6366f1;
382
- color: #4338ca;
437
+ background: var(--squisq-layout-accent-soft);
438
+ border-color: var(--squisq-layout-accent-border);
439
+ color: var(--squisq-layout-accent-text);
383
440
  }
384
441
 
385
442
  .squisq-layer-toolbar-select {
386
443
  height: 28px;
387
444
  padding: 0 6px;
388
445
  font-size: 12px;
389
- color: #1f2937;
390
- background: #ffffff;
391
- border: 1px solid #d1d5db;
446
+ color: var(--squisq-layout-text-soft);
447
+ background: var(--squisq-layout-input);
448
+ border: 1px solid var(--squisq-layout-border-strong);
392
449
  border-radius: 4px;
393
450
  cursor: pointer;
394
451
  }
@@ -398,9 +455,9 @@
398
455
  height: 28px;
399
456
  padding: 0 4px 0 6px;
400
457
  font-size: 12px;
401
- color: #1f2937;
402
- background: #ffffff;
403
- border: 1px solid #d1d5db;
458
+ color: var(--squisq-layout-text-soft);
459
+ background: var(--squisq-layout-input);
460
+ border: 1px solid var(--squisq-layout-border-strong);
404
461
  border-radius: 4px;
405
462
  }
406
463
 
@@ -417,7 +474,7 @@
417
474
  .squisq-layer-toolbar-label {
418
475
  display: inline-flex;
419
476
  align-items: center;
420
- color: #6b7280;
477
+ color: var(--squisq-layout-muted);
421
478
  font-size: 13px;
422
479
  }
423
480
 
@@ -431,9 +488,9 @@
431
488
  width: 28px;
432
489
  height: 28px;
433
490
  padding: 0;
434
- border: 1px solid #d1d5db;
491
+ border: 1px solid var(--squisq-layout-border-strong);
435
492
  border-radius: 4px;
436
- background: #ffffff;
493
+ background: var(--squisq-layout-input);
437
494
  cursor: pointer;
438
495
  }
439
496
 
@@ -441,14 +498,14 @@
441
498
  width: 1px;
442
499
  align-self: stretch;
443
500
  margin: 2px 2px;
444
- background: #e5e7eb;
501
+ background: var(--squisq-layout-border);
445
502
  }
446
503
 
447
504
  .squisq-template-designer-scene {
448
505
  flex: 1;
449
506
  min-height: 0;
450
- background: #ffffff;
451
- border: 1px solid #e5e7eb;
507
+ background: var(--squisq-layout-panel);
508
+ border: 1px solid var(--squisq-layout-border);
452
509
  border-radius: 8px;
453
510
  overflow: hidden;
454
511
  }
@@ -458,14 +515,14 @@
458
515
  align-items: center;
459
516
  justify-content: space-between;
460
517
  padding: 12px 20px;
461
- border-top: 1px solid #e5e7eb;
462
- background: #f9fafb;
518
+ border-top: 1px solid var(--squisq-layout-border);
519
+ background: var(--squisq-layout-chrome);
463
520
  gap: 12px;
464
521
  }
465
522
 
466
523
  .squisq-template-designer-footer-hint {
467
524
  font-size: 11px;
468
- color: #6b7280;
525
+ color: var(--squisq-layout-muted);
469
526
  flex: 1;
470
527
  min-width: 0;
471
528
  }
@@ -479,26 +536,26 @@
479
536
  padding: 6px 14px;
480
537
  font-size: 13px;
481
538
  font-weight: 500;
482
- color: #1f2937;
483
- background: #ffffff;
484
- border: 1px solid #d1d5db;
539
+ color: var(--squisq-layout-text-soft);
540
+ background: var(--squisq-layout-input);
541
+ border: 1px solid var(--squisq-layout-border-strong);
485
542
  border-radius: 6px;
486
543
  cursor: pointer;
487
544
  transition: background 0.1s;
488
545
  }
489
546
 
490
547
  .squisq-template-designer-btn:hover {
491
- background: #f1f5f9;
548
+ background: var(--squisq-layout-hover);
492
549
  }
493
550
 
494
551
  .squisq-template-designer-btn--primary {
495
552
  color: #ffffff;
496
- background: #4f46e5;
497
- border-color: #4338ca;
553
+ background: var(--squisq-layout-accent-bg);
554
+ border-color: var(--squisq-layout-accent-bg-hover);
498
555
  }
499
556
 
500
557
  .squisq-template-designer-btn--primary:hover {
501
- background: #4338ca;
558
+ background: var(--squisq-layout-accent-bg-hover);
502
559
  }
503
560
 
504
561
  /* ─── Custom Layout Manager ────────────────────────────────
@@ -521,7 +578,8 @@
521
578
  flex-direction: column;
522
579
  width: min(1600px, 96vw);
523
580
  height: 92vh;
524
- background: #ffffff;
581
+ color: var(--squisq-layout-text-soft);
582
+ background: var(--squisq-layout-panel);
525
583
  border-radius: 12px;
526
584
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.3);
527
585
  overflow: hidden;
@@ -532,15 +590,15 @@
532
590
  align-items: center;
533
591
  justify-content: space-between;
534
592
  padding: 14px 20px;
535
- border-bottom: 1px solid #e5e7eb;
536
- background: #f9fafb;
593
+ border-bottom: 1px solid var(--squisq-layout-border);
594
+ background: var(--squisq-layout-chrome);
537
595
  }
538
596
 
539
597
  .squisq-layout-manager-title {
540
598
  margin: 0;
541
599
  font-size: 16px;
542
600
  font-weight: 600;
543
- color: #111827;
601
+ color: var(--squisq-layout-text);
544
602
  }
545
603
 
546
604
  .squisq-layout-manager-body {
@@ -556,8 +614,8 @@
556
614
  flex-direction: column;
557
615
  gap: 8px;
558
616
  padding: 16px;
559
- border-right: 1px solid #e5e7eb;
560
- background: #fafafa;
617
+ border-right: 1px solid var(--squisq-layout-border);
618
+ background: var(--squisq-layout-subtle);
561
619
  overflow-y: auto;
562
620
  }
563
621
 
@@ -565,22 +623,22 @@
565
623
  padding: 10px 12px;
566
624
  font-size: 13px;
567
625
  font-weight: 600;
568
- color: #4338ca;
569
- background: #eef2ff;
570
- border: 1px dashed #c7d2fe;
626
+ color: var(--squisq-layout-accent-text);
627
+ background: var(--squisq-layout-accent-soft);
628
+ border: 1px dashed var(--squisq-layout-accent-soft-border);
571
629
  border-radius: 8px;
572
630
  cursor: pointer;
573
631
  text-align: center;
574
632
  }
575
633
 
576
634
  .squisq-layout-manager-new:hover {
577
- background: #e0e7ff;
578
- border-color: #a5b4fc;
635
+ background: var(--squisq-layout-accent-soft-hover);
636
+ border-color: var(--squisq-layout-accent-hover-border);
579
637
  }
580
638
 
581
639
  .squisq-layout-manager-new--active {
582
640
  border-style: solid;
583
- border-color: #6366f1;
641
+ border-color: var(--squisq-layout-accent-border);
584
642
  }
585
643
 
586
644
  .squisq-layout-manager-section-title {
@@ -589,7 +647,7 @@
589
647
  font-weight: 700;
590
648
  text-transform: uppercase;
591
649
  letter-spacing: 0.06em;
592
- color: #6b7280;
650
+ color: var(--squisq-layout-muted);
593
651
  }
594
652
 
595
653
  .squisq-layout-manager-list {
@@ -603,8 +661,9 @@
603
661
  align-items: center;
604
662
  gap: 10px;
605
663
  padding: 8px;
606
- background: #ffffff;
607
- border: 1px solid #e5e7eb;
664
+ color: var(--squisq-layout-text-soft);
665
+ background: var(--squisq-layout-input);
666
+ border: 1px solid var(--squisq-layout-border);
608
667
  border-radius: 8px;
609
668
  cursor: pointer;
610
669
  text-align: left;
@@ -614,13 +673,13 @@
614
673
  }
615
674
 
616
675
  .squisq-layout-manager-item:hover {
617
- background: #eef2ff;
618
- border-color: #c7d2fe;
676
+ background: var(--squisq-layout-accent-soft);
677
+ border-color: var(--squisq-layout-accent-soft-border);
619
678
  }
620
679
 
621
680
  .squisq-layout-manager-item--active {
622
- background: #eef2ff;
623
- border-color: #6366f1;
681
+ background: var(--squisq-layout-accent-soft);
682
+ border-color: var(--squisq-layout-accent-border);
624
683
  }
625
684
 
626
685
  .squisq-layout-manager-item-thumb {
@@ -628,10 +687,10 @@
628
687
  display: inline-flex;
629
688
  width: 56px;
630
689
  height: 40px;
631
- border: 1px solid #e5e7eb;
690
+ border: 1px solid var(--squisq-layout-border);
632
691
  border-radius: 4px;
633
692
  overflow: hidden;
634
- background: #f8fafc;
693
+ background: var(--squisq-layout-thumb);
635
694
  }
636
695
 
637
696
  .squisq-layout-manager-item-text {
@@ -644,7 +703,7 @@
644
703
  .squisq-layout-manager-item-label {
645
704
  font-size: 13px;
646
705
  font-weight: 600;
647
- color: #111827;
706
+ color: var(--squisq-layout-text);
648
707
  white-space: nowrap;
649
708
  overflow: hidden;
650
709
  text-overflow: ellipsis;
@@ -653,7 +712,7 @@
653
712
  .squisq-layout-manager-item-name {
654
713
  font-family: 'JetBrains Mono', 'SF Mono', 'Menlo', monospace;
655
714
  font-size: 11px;
656
- color: #6b7280;
715
+ color: var(--squisq-layout-muted);
657
716
  white-space: nowrap;
658
717
  overflow: hidden;
659
718
  text-overflow: ellipsis;
@@ -663,7 +722,7 @@
663
722
  margin: 8px 2px;
664
723
  font-size: 12px;
665
724
  line-height: 1.5;
666
- color: #6b7280;
725
+ color: var(--squisq-layout-muted);
667
726
  }
668
727
 
669
728
  .squisq-layout-manager-main {
@@ -37,12 +37,12 @@ function safeStorage(): Storage | null {
37
37
  function readPayload(): LibraryPayload {
38
38
  const storage = safeStorage();
39
39
  if (!storage) return { version: 1, templates: [] };
40
- const raw = storage.getItem(LIBRARY_STORAGE_KEY);
41
- if (!raw) return { version: 1, templates: [] };
42
40
  try {
41
+ const raw = storage.getItem(LIBRARY_STORAGE_KEY);
42
+ if (!raw) return { version: 1, templates: [] };
43
43
  const parsed = JSON.parse(raw) as Partial<LibraryPayload>;
44
44
  if (parsed && Array.isArray(parsed.templates)) {
45
- return { version: 1, templates: parsed.templates };
45
+ return { version: 1, templates: parsed.templates.filter(isTemplateDefinition) };
46
46
  }
47
47
  } catch {
48
48
  // Corrupt payload — drop it.
@@ -50,6 +50,19 @@ function readPayload(): LibraryPayload {
50
50
  return { version: 1, templates: [] };
51
51
  }
52
52
 
53
+ function isTemplateDefinition(value: unknown): value is CustomTemplateDefinition {
54
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
55
+ const candidate = value as Partial<CustomTemplateDefinition>;
56
+ return (
57
+ typeof candidate.name === 'string' &&
58
+ typeof candidate.label === 'string' &&
59
+ !!candidate.viewport &&
60
+ Number.isFinite(candidate.viewport.width) &&
61
+ Number.isFinite(candidate.viewport.height) &&
62
+ Array.isArray(candidate.layers)
63
+ );
64
+ }
65
+
53
66
  function writePayload(payload: LibraryPayload): void {
54
67
  const storage = safeStorage();
55
68
  if (!storage) return;
@@ -34,6 +34,7 @@ import {
34
34
  listLibraryThemes,
35
35
  saveLibraryTheme as saveLibraryThemeRaw,
36
36
  deleteLibraryTheme as deleteLibraryThemeRaw,
37
+ THEME_LIBRARY_STORAGE_KEY,
37
38
  } from './customThemeLibrary';
38
39
 
39
40
  export interface CustomThemeContextValue {
@@ -96,6 +97,11 @@ export function CustomThemeProvider({
96
97
  // which is enough for the single-tab common case.
97
98
  useEffect(() => {
98
99
  setLibraryThemes(listLibraryThemes());
100
+ const onStorage = (event: StorageEvent) => {
101
+ if (event.key === THEME_LIBRARY_STORAGE_KEY) setLibraryThemes(listLibraryThemes());
102
+ };
103
+ window.addEventListener('storage', onStorage);
104
+ return () => window.removeEventListener('storage', onStorage);
99
105
  }, []);
100
106
 
101
107
  const upsertDocTheme = useCallback(