@bendyline/squisq-editor-react 1.5.3 → 1.6.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 (165) hide show
  1. package/dist/index.d.ts +757 -20
  2. package/dist/index.js +16875 -6883
  3. package/dist/index.js.map +1 -1
  4. package/package.json +4 -4
  5. package/src/BlockCardView.tsx +121 -0
  6. package/src/BlockPreviewPanel.tsx +69 -0
  7. package/src/BlockPropertiesPopover.tsx +191 -0
  8. package/src/EditorContext.tsx +143 -0
  9. package/src/EditorShell.tsx +200 -120
  10. package/src/FolderView.tsx +131 -0
  11. package/src/Icon.tsx +26 -0
  12. package/src/ImageEditor.tsx +69 -22
  13. package/src/OutlinePanel.tsx +38 -3
  14. package/src/PlainHtmlPreview.tsx +30 -3
  15. package/src/PreviewControls.tsx +180 -8
  16. package/src/RawEditor.tsx +169 -13
  17. package/src/RecorderEntry.tsx +9 -16
  18. package/src/TemplateAnnotation.ts +44 -0
  19. package/src/TemplatePicker.tsx +329 -54
  20. package/src/ThemeCustomizerPanel.tsx +30 -336
  21. package/src/ThemePicker.tsx +112 -3
  22. package/src/TimelineBlockPreview.tsx +37 -0
  23. package/src/TimelineTrack.tsx +671 -0
  24. package/src/Toolbar.tsx +528 -174
  25. package/src/Tooltip.tsx +22 -4
  26. package/src/TransitionPicker.tsx +351 -0
  27. package/src/VersionHistoryPanel.tsx +2 -14
  28. package/src/ViewMenuPanel.tsx +17 -14
  29. package/src/WysiwygEditor.tsx +161 -65
  30. package/src/__tests__/blockProperties.test.ts +92 -0
  31. package/src/__tests__/blockRange.test.ts +105 -0
  32. package/src/__tests__/buildPreviewDocTransition.test.ts +73 -0
  33. package/src/__tests__/createShapeLayer.test.ts +46 -0
  34. package/src/__tests__/drawingShapeRoundTrip.test.ts +49 -0
  35. package/src/__tests__/embeddedMedia.test.ts +48 -0
  36. package/src/__tests__/headingTransition.test.ts +138 -0
  37. package/src/__tests__/layoutChildRoundTrip.test.ts +71 -0
  38. package/src/__tests__/plainHtmlPreview.test.tsx +10 -8
  39. package/src/__tests__/recorderMediaInsert.test.ts +86 -0
  40. package/src/__tests__/templateAnnotationRoundTrip.test.ts +18 -0
  41. package/src/__tests__/templatePickerMetadata.test.ts +32 -0
  42. package/src/__tests__/timelineSource.test.ts +134 -0
  43. package/src/__tests__/tiptapBridge.test.ts +92 -0
  44. package/src/__tests__/tiptapBridgeConformance.test.ts +47 -0
  45. package/src/__tests__/tooltip.test.tsx +72 -0
  46. package/src/__tests__/transitionCatalog.test.ts +64 -0
  47. package/src/__tests__/useBlockNavigator.test.tsx +67 -0
  48. package/src/__tests__/useMediaRecorder.test.ts +24 -0
  49. package/src/__tests__/useTimelineClock.test.ts +21 -0
  50. package/src/blockProperties.ts +88 -0
  51. package/src/blockRange.ts +132 -0
  52. package/src/buildPreviewDoc.ts +98 -9
  53. package/src/customTemplates/AddBin.tsx +126 -0
  54. package/src/customTemplates/CustomLayoutManager.tsx +233 -0
  55. package/src/customTemplates/CustomTemplateContext.tsx +182 -0
  56. package/src/customTemplates/LayerToolbar.tsx +580 -0
  57. package/src/customTemplates/ShapeGlyph.tsx +47 -0
  58. package/src/customTemplates/TemplateDesigner.tsx +430 -0
  59. package/src/customTemplates/__tests__/library.test.ts +88 -0
  60. package/src/customTemplates/__tests__/normalizePositions.test.ts +109 -0
  61. package/src/customTemplates/__tests__/shapeDefs.test.ts +49 -0
  62. package/src/customTemplates/__tests__/useMemoryLayerAdapter.test.ts +95 -0
  63. package/src/customTemplates/designer.css +673 -0
  64. package/src/customTemplates/index.ts +31 -0
  65. package/src/customTemplates/library.ts +97 -0
  66. package/src/customTemplates/normalizePositions.ts +75 -0
  67. package/src/customTemplates/shapeDefs.ts +131 -0
  68. package/src/customTemplates/thumbnail.tsx +63 -0
  69. package/src/customTemplates/tokenDefs.ts +60 -0
  70. package/src/customTemplates/useDocCustomTemplates.ts +52 -0
  71. package/src/customTemplates/useMemoryLayerAdapter.ts +123 -0
  72. package/src/customThemes/CustomThemeContext.tsx +179 -0
  73. package/src/customThemes/CustomThemeDialog.tsx +286 -0
  74. package/src/customThemes/__tests__/CustomThemeContext.test.tsx +64 -0
  75. package/src/customThemes/__tests__/CustomThemeDialog.test.tsx +47 -0
  76. package/src/customThemes/__tests__/customThemeLibrary.test.ts +51 -0
  77. package/src/customThemes/customThemeLibrary.ts +97 -0
  78. package/src/customThemes/index.ts +31 -0
  79. package/src/customThemes/themeControls.tsx +229 -0
  80. package/src/customThemes/themeDraft.ts +272 -0
  81. package/src/customThemes/useDocCustomThemes.ts +49 -0
  82. package/src/diagram/DiagramCanvas.tsx +240 -0
  83. package/src/diagram/DiagramExtension.ts +209 -0
  84. package/src/diagram/DiagramMaximizedOverlay.tsx +46 -0
  85. package/src/diagram/DiagramWidget.tsx +270 -0
  86. package/src/diagram/diagramCommands.ts +604 -0
  87. package/src/diagram/diagramConstants.ts +17 -0
  88. package/src/diagram/useDiagramData.ts +126 -0
  89. package/src/embeddedMedia.ts +78 -0
  90. package/src/frontmatter.ts +29 -0
  91. package/src/headingTransition.ts +231 -0
  92. package/src/imageEditor/CanvasSurface.tsx +383 -88
  93. package/src/imageEditor/PropertiesPanel.tsx +47 -1
  94. package/src/imageEditor/Toolbar.tsx +229 -16
  95. package/src/imageEditor/createShapeLayer.ts +280 -0
  96. package/src/imageEditor/icons.tsx +34 -114
  97. package/src/imageEditor/image-editor.css +54 -5
  98. package/src/imageEditor/state.ts +23 -3
  99. package/src/index.ts +77 -0
  100. package/src/recorder/RecorderModal.tsx +120 -53
  101. package/src/recorder/RecorderPanel.tsx +2 -26
  102. package/src/recorder/hooks/useMediaRecorder.ts +8 -1
  103. package/src/recorder/insertMediaBlock.ts +30 -0
  104. package/src/resolveBlockVisual.ts +33 -0
  105. package/src/scene/Scene.tsx +540 -0
  106. package/src/scene/SceneBlockExtension.ts +198 -0
  107. package/src/scene/SceneBlockToolbar.tsx +201 -0
  108. package/src/scene/SceneBlockWidget.tsx +434 -0
  109. package/src/scene/ScenePropsBar.tsx +85 -0
  110. package/src/scene/SceneSelection.tsx +107 -0
  111. package/src/scene/SceneViewport.tsx +102 -0
  112. package/src/scene/ShapePalette.tsx +181 -0
  113. package/src/scene/__tests__/DiagramAdapter.test.ts +56 -0
  114. package/src/scene/__tests__/bezierEdit.test.ts +85 -0
  115. package/src/scene/__tests__/blockLayers.test.ts +57 -0
  116. package/src/scene/__tests__/shapeLayers.test.ts +106 -0
  117. package/src/scene/__tests__/useSceneHitTest.test.ts +90 -0
  118. package/src/scene/__tests__/useScenePanZoom.test.ts +103 -0
  119. package/src/scene/adapters/DiagramAdapter.ts +168 -0
  120. package/src/scene/adapters/DrawingAdapter.ts +415 -0
  121. package/src/scene/adapters/LayoutAdapter.ts +310 -0
  122. package/src/scene/adapters/blockLayers.ts +159 -0
  123. package/src/scene/commands/SceneCommand.ts +70 -0
  124. package/src/scene/commands/drawingCommands.ts +318 -0
  125. package/src/scene/commands/layoutCommands.ts +301 -0
  126. package/src/scene/hooks/useSceneHitTest.ts +105 -0
  127. package/src/scene/hooks/useScenePanZoom.ts +147 -0
  128. package/src/scene/hooks/useSceneSelection.ts +62 -0
  129. package/src/scene/index.ts +95 -0
  130. package/src/scene/layers/DiagramEdges.tsx +127 -0
  131. package/src/scene/layers/edgeGeometry.ts +77 -0
  132. package/src/scene/layers/nodeCard.tsx +145 -0
  133. package/src/scene/layers/renderLayer.tsx +70 -0
  134. package/src/scene/layers/shapeLayers.ts +201 -0
  135. package/src/scene/paths/bezierEdit.ts +208 -0
  136. package/src/scene/scene.css +649 -0
  137. package/src/scene/text/SceneTextOverlay.tsx +161 -0
  138. package/src/scene/text/sceneTextChannel.ts +40 -0
  139. package/src/scene/text/sceneTextConfig.ts +27 -0
  140. package/src/scene/text/sceneTiptap.ts +36 -0
  141. package/src/scene/text/useSceneTextEditing.ts +39 -0
  142. package/src/scene/tools/ConnectTool.ts +111 -0
  143. package/src/scene/tools/DrawingConnectTool.ts +161 -0
  144. package/src/scene/tools/PathTool.ts +158 -0
  145. package/src/scene/tools/PlaceTool.ts +47 -0
  146. package/src/scene/tools/SceneTool.ts +75 -0
  147. package/src/scene/tools/SelectTool.ts +284 -0
  148. package/src/scene/tools/ShapeTool.ts +144 -0
  149. package/src/scene/tools/TextTool.ts +72 -0
  150. package/src/scene/tools/TokenTool.ts +95 -0
  151. package/src/scene/tools/createDrawShapeTool.ts +82 -0
  152. package/src/styles/diagram.css +183 -0
  153. package/src/styles/editor.css +1615 -203
  154. package/src/styles/folder-view.css +210 -0
  155. package/src/styles/image-edit-affordance.css +2 -2
  156. package/src/styles/index.css +4 -0
  157. package/src/timelineSource.ts +244 -0
  158. package/src/tiptapBridge.ts +115 -34
  159. package/src/tooltipPlacement.ts +13 -0
  160. package/src/transitionCatalog.ts +159 -0
  161. package/src/types/monaco-shims.d.ts +10 -0
  162. package/src/useBlockNavigator.ts +153 -0
  163. package/src/useMonacoLoader.ts +23 -1
  164. package/src/useTimelineClock.ts +76 -0
  165. package/src/utils/dropUtils.ts +1 -1
@@ -107,6 +107,11 @@
107
107
  background: rgba(0, 0, 0, 0.07);
108
108
  container-type: inline-size;
109
109
  container-name: squisq-toolbar;
110
+ /* Icon glyphs render a touch lighter than the dark text labels (#374151)
111
+ so the solid Font Awesome icons read less heavy. Hosts can override.
112
+ Only the Free solid family ships these glyphs (no light/thin in Free),
113
+ so we soften the color rather than the stroke weight. */
114
+ --squisq-toolbar-icon: #6b7280;
110
115
  }
111
116
 
112
117
  /* ─── View Tabs (inside toolbar) ─────────────────────── */
@@ -251,6 +256,22 @@
251
256
  color: #1d4ed8;
252
257
  }
253
258
 
259
+ /* Font Awesome glyphs sit a touch larger than the 13px text labels so
260
+ icon buttons read at a comfortable weight against H1–H6 etc. They
261
+ render in a lighter color than the text labels by default. */
262
+ .squisq-toolbar-button > i {
263
+ font-size: 15px;
264
+ line-height: 1;
265
+ color: var(--squisq-toolbar-icon);
266
+ }
267
+
268
+ /* On hover / active, glyphs follow the button's color so the existing
269
+ hover and toggled-on feedback still applies. */
270
+ .squisq-toolbar-button:hover > i,
271
+ .squisq-toolbar-button--active > i {
272
+ color: inherit;
273
+ }
274
+
254
275
  /* ─── Toolbar: narrow screen adjustments ───────────────── */
255
276
 
256
277
  @media (max-width: 768px) {
@@ -360,6 +381,47 @@
360
381
  max-width: 100%;
361
382
  }
362
383
 
384
+ /* ─── Insert Menu Dropdown ──────────────────────────── */
385
+
386
+ .squisq-insert-menu {
387
+ z-index: 100;
388
+ min-width: 200px;
389
+ padding: 4px 0;
390
+ background: #fff;
391
+ border: 1px solid #e5e7eb;
392
+ border-radius: 6px;
393
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
394
+ }
395
+
396
+ .squisq-insert-menu-header {
397
+ padding: 6px 12px 4px;
398
+ font-size: 11px;
399
+ font-weight: 600;
400
+ letter-spacing: 0.04em;
401
+ text-transform: uppercase;
402
+ color: #9ca3af;
403
+ user-select: none;
404
+ }
405
+
406
+ /* Portaled outside squisq-editor-shell, so dark mode is applied via data-theme. */
407
+ .squisq-insert-menu[data-theme='dark'] {
408
+ background: #1f2937;
409
+ border-color: #374151;
410
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
411
+ }
412
+
413
+ .squisq-insert-menu[data-theme='dark'] .squisq-toolbar-overflow-item {
414
+ color: #d1d5db;
415
+ }
416
+
417
+ .squisq-insert-menu[data-theme='dark'] .squisq-toolbar-overflow-item:hover {
418
+ background: #374151;
419
+ }
420
+
421
+ .squisq-insert-menu[data-theme='dark'] .squisq-insert-menu-header {
422
+ color: #6b7280;
423
+ }
424
+
363
425
  /* ─── Template Picker (toolbar) ──────────────────────── */
364
426
 
365
427
  .squisq-template-picker {
@@ -480,7 +542,11 @@
480
542
  .squisq-template-gallery {
481
543
  width: 420px;
482
544
  max-width: calc(100vw - 24px);
483
- max-height: 480px;
545
+ /* Cap to the viewport (less a top+bottom margin) rather than a fixed
546
+ pixel height, so the panel never grows past the bottom of the page.
547
+ Its own overflow-y gives it a self-contained scrollbar instead of
548
+ stretching alongside the page scrollbar. */
549
+ max-height: calc(100vh - 72px);
484
550
  overflow-y: auto;
485
551
  background: #fff;
486
552
  border: 1px solid #e5e7eb;
@@ -503,6 +569,55 @@
503
569
  }
504
570
  }
505
571
 
572
+ /* Search input at the top of the gallery — sticky so it stays visible while
573
+ the user scrolls a long template list. */
574
+ .squisq-template-gallery-search {
575
+ position: sticky;
576
+ top: 0;
577
+ z-index: 1;
578
+ display: flex;
579
+ align-items: center;
580
+ gap: 6px;
581
+ padding: 4px 8px;
582
+ margin: -4px -4px 8px;
583
+ background: #fff;
584
+ border-bottom: 1px solid #f3f4f6;
585
+ }
586
+
587
+ .squisq-template-gallery-search-icon {
588
+ color: #9ca3af;
589
+ flex-shrink: 0;
590
+ }
591
+
592
+ .squisq-template-gallery-search-input {
593
+ flex: 1;
594
+ min-width: 0;
595
+ padding: 6px 4px;
596
+ border: none;
597
+ outline: none;
598
+ font: inherit;
599
+ font-size: 13px;
600
+ color: #111827;
601
+ background: transparent;
602
+ }
603
+
604
+ .squisq-template-gallery-search-input::placeholder {
605
+ color: #9ca3af;
606
+ }
607
+
608
+ /* Hide the native clear button on type="search" — the popover handles
609
+ clearing implicitly when it closes. */
610
+ .squisq-template-gallery-search-input::-webkit-search-cancel-button {
611
+ appearance: none;
612
+ }
613
+
614
+ .squisq-template-gallery-empty {
615
+ padding: 24px 12px;
616
+ text-align: center;
617
+ font-size: 12px;
618
+ color: #6b7280;
619
+ }
620
+
506
621
  /* "— none —" row at the top */
507
622
  .squisq-template-gallery-none {
508
623
  display: flex;
@@ -550,7 +665,6 @@
550
665
  @media (min-width: 900px) {
551
666
  .squisq-template-gallery {
552
667
  width: 780px;
553
- max-height: 880px;
554
668
  }
555
669
 
556
670
  .squisq-template-gallery-grid {
@@ -620,6 +734,65 @@
620
734
  line-height: 1.4;
621
735
  }
622
736
 
737
+ /* "+ New custom template" card pinned at the top of the gallery */
738
+ .squisq-template-gallery-new {
739
+ display: flex;
740
+ align-items: center;
741
+ gap: 10px;
742
+ width: 100%;
743
+ padding: 8px 12px;
744
+ margin-bottom: 6px;
745
+ border: 1px dashed #a5b4fc;
746
+ border-radius: 7px;
747
+ background: #f5f3ff;
748
+ cursor: pointer;
749
+ text-align: left;
750
+ transition:
751
+ background 0.1s,
752
+ border-color 0.1s;
753
+ }
754
+
755
+ .squisq-template-gallery-new:hover {
756
+ background: #ede9fe;
757
+ border-color: #818cf8;
758
+ border-style: solid;
759
+ }
760
+
761
+ .squisq-template-gallery-new-plus {
762
+ display: inline-flex;
763
+ align-items: center;
764
+ justify-content: center;
765
+ width: 28px;
766
+ height: 28px;
767
+ flex-shrink: 0;
768
+ font-size: 20px;
769
+ font-weight: 600;
770
+ line-height: 1;
771
+ color: #6366f1;
772
+ background: #ffffff;
773
+ border: 1px solid #c4b5fd;
774
+ border-radius: 6px;
775
+ }
776
+
777
+ .squisq-template-gallery-new-body {
778
+ display: flex;
779
+ flex-direction: column;
780
+ gap: 2px;
781
+ min-width: 0;
782
+ }
783
+
784
+ .squisq-template-gallery-new-label {
785
+ font-size: 12px;
786
+ font-weight: 600;
787
+ color: #4338ca;
788
+ }
789
+
790
+ .squisq-template-gallery-new-desc {
791
+ font-size: 11px;
792
+ color: #6b7280;
793
+ line-height: 1.4;
794
+ }
795
+
623
796
  /* Recommended / All templates segmentation */
624
797
  .squisq-template-gallery-section + .squisq-template-gallery-section {
625
798
  margin-top: 14px;
@@ -649,7 +822,8 @@
649
822
  /* ─── Tooltip (portal) ────────────────────────────────── */
650
823
 
651
824
  .squisq-tooltip {
652
- transform: translateX(-50%);
825
+ width: max-content;
826
+ max-width: calc(100vw - 16px);
653
827
  padding: 4px 8px;
654
828
  font-size: 12px;
655
829
  font-weight: 500;
@@ -657,7 +831,9 @@
657
831
  background: #1f2937;
658
832
  border-radius: 4px;
659
833
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.18);
660
- white-space: nowrap;
834
+ line-height: 1.3;
835
+ white-space: normal;
836
+ overflow-wrap: anywhere;
661
837
  pointer-events: none;
662
838
  z-index: 2000;
663
839
  animation: squisq-tooltip-fade 0.1s ease-out;
@@ -746,10 +922,171 @@
746
922
  /* Hide block-template chips when the View menu toggles them off. The
747
923
  * underlying `data-template` heading attribute is untouched — only the
748
924
  * inline visual affordance disappears. */
749
- .squisq-wysiwyg-container[data-block-tags='hidden'] .squisq-template-badge {
925
+ .squisq-wysiwyg-container[data-block-tags='hidden'] .squisq-template-badge,
926
+ .squisq-wysiwyg-container[data-block-tags='hidden'] .squisq-props-badge {
750
927
  display: none;
751
928
  }
752
929
 
930
+ /* ─── Block-properties badge (sibling of the template badge) ───────────
931
+ The on-canvas entry point to the block-properties palette: a small
932
+ sliders chip just after the template badge. Quiet by default like the
933
+ empty template badge, brightening on heading hover / direct hover. */
934
+ .squisq-props-badge {
935
+ display: inline-flex;
936
+ align-items: center;
937
+ margin-left: 4px;
938
+ padding: 1px 8px;
939
+ min-height: 19px;
940
+ font-family:
941
+ ui-sans-serif,
942
+ system-ui,
943
+ -apple-system,
944
+ sans-serif;
945
+ font-size: 11px;
946
+ font-weight: 600;
947
+ color: #94a3b8;
948
+ background: transparent;
949
+ border: 1px solid #d1d5db;
950
+ border-radius: 10px;
951
+ vertical-align: middle;
952
+ user-select: none;
953
+ cursor: pointer;
954
+ opacity: 0.5;
955
+ transition:
956
+ background 0.15s ease,
957
+ border-color 0.15s ease,
958
+ opacity 0.15s ease;
959
+ }
960
+
961
+ /* Sliders glyph painted via CSS so the badge span stays empty in the DOM
962
+ (see propsBadgeSpec — a real child could leak into serialized markdown).
963
+ `\f1de` is Font Awesome's "sliders"; the font is already loaded for the
964
+ editor's inline-icon glyph decorations. */
965
+ .squisq-props-badge::before {
966
+ content: '\f1de';
967
+ font-family: 'Font Awesome 6 Free';
968
+ font-weight: 900;
969
+ font-size: 10px;
970
+ line-height: 1;
971
+ }
972
+
973
+ /* When properties are set, the badge carries a `data-props-summary` and shows
974
+ it after the glyph — and reads as an active (not just affordance) chip. */
975
+ .squisq-props-badge[data-props-summary] {
976
+ opacity: 1;
977
+ color: #6d28d9;
978
+ background: #f5f3ff;
979
+ border-color: #c4b5fd;
980
+ }
981
+
982
+ .squisq-props-badge[data-props-summary]::after {
983
+ content: attr(data-props-summary);
984
+ margin-left: 5px;
985
+ font-weight: 500;
986
+ white-space: nowrap;
987
+ }
988
+
989
+ :is(h1, h2, h3, h4, h5, h6):hover > .squisq-props-badge {
990
+ opacity: 0.9;
991
+ }
992
+
993
+ .squisq-props-badge:hover,
994
+ .squisq-props-badge:focus-visible {
995
+ opacity: 1;
996
+ color: #6d28d9;
997
+ background: #ede9fe;
998
+ border-color: #c4b5fd;
999
+ outline: none;
1000
+ }
1001
+
1002
+ /* ─── Block-properties palette popover (portaled to <body>) ──────────── */
1003
+ .squisq-block-props-popover {
1004
+ width: 280px;
1005
+ max-width: calc(100vw - 24px);
1006
+ padding: 10px 12px;
1007
+ display: flex;
1008
+ flex-direction: column;
1009
+ gap: 10px;
1010
+ background: #fff;
1011
+ border: 1px solid #e5e7eb;
1012
+ border-radius: 10px;
1013
+ box-shadow:
1014
+ 0 8px 24px rgba(0, 0, 0, 0.12),
1015
+ 0 2px 6px rgba(0, 0, 0, 0.08);
1016
+ }
1017
+
1018
+ .squisq-block-props-row {
1019
+ display: flex;
1020
+ align-items: center;
1021
+ justify-content: space-between;
1022
+ gap: 10px;
1023
+ }
1024
+
1025
+ .squisq-block-props-label {
1026
+ font-size: 11px;
1027
+ font-weight: 600;
1028
+ color: #6b7280;
1029
+ flex-shrink: 0;
1030
+ }
1031
+
1032
+ .squisq-block-props-numfield {
1033
+ display: inline-flex;
1034
+ align-items: center;
1035
+ gap: 6px;
1036
+ }
1037
+
1038
+ .squisq-block-props-input {
1039
+ width: 72px;
1040
+ padding: 4px 6px;
1041
+ font-size: 12px;
1042
+ color: #111827;
1043
+ border: 1px solid #d1d5db;
1044
+ border-radius: 5px;
1045
+ outline: none;
1046
+ }
1047
+
1048
+ .squisq-block-props-input:focus {
1049
+ border-color: #6366f1;
1050
+ }
1051
+
1052
+ .squisq-block-props-unit {
1053
+ font-size: 11px;
1054
+ color: #9ca3af;
1055
+ }
1056
+
1057
+ /* Dark theme */
1058
+ .squisq-editor-shell[data-theme='dark'] .squisq-props-badge {
1059
+ border-color: #4b5563;
1060
+ color: #9ca3af;
1061
+ }
1062
+
1063
+ .squisq-editor-shell[data-theme='dark'] .squisq-props-badge[data-props-summary] {
1064
+ color: #ddd6fe;
1065
+ background: #3730a3;
1066
+ border-color: #6d5fd6;
1067
+ }
1068
+
1069
+ .squisq-editor-shell[data-theme='dark'] .squisq-props-badge:hover,
1070
+ .squisq-editor-shell[data-theme='dark'] .squisq-props-badge:focus-visible {
1071
+ color: #ede9fe;
1072
+ background: #4c1d95;
1073
+ border-color: #7c3aed;
1074
+ }
1075
+
1076
+ .squisq-editor-shell[data-theme='dark'] .squisq-block-props-popover {
1077
+ background: #1f2937;
1078
+ border-color: #374151;
1079
+ box-shadow:
1080
+ 0 8px 24px rgba(0, 0, 0, 0.4),
1081
+ 0 2px 6px rgba(0, 0, 0, 0.2);
1082
+ }
1083
+
1084
+ .squisq-editor-shell[data-theme='dark'] .squisq-block-props-input {
1085
+ background: #111827;
1086
+ color: #f9fafb;
1087
+ border-color: #4b5563;
1088
+ }
1089
+
753
1090
  /* ─── Status Bar ─────────────────────────────────────── */
754
1091
 
755
1092
  .squisq-status-bar {
@@ -853,29 +1190,40 @@
853
1190
  font-family: var(--squisq-theme-title-font, var(--squisq-theme-body-font, inherit));
854
1191
  }
855
1192
 
1193
+ /* Headings start blocks, so they get generous space ABOVE (a clear break from
1194
+ the previous block) and a tighter margin BELOW so the heading stays visually
1195
+ grouped with the content it introduces. */
856
1196
  .squisq-wysiwyg-editor h1 {
857
1197
  font-size: 2em;
858
- margin: 0.67em 0;
1198
+ margin: 1em 0 0.4em;
859
1199
  }
860
1200
  .squisq-wysiwyg-editor h2 {
861
1201
  font-size: 1.5em;
862
- margin: 0.75em 0;
1202
+ margin: 1.15em 0 0.35em;
863
1203
  }
864
1204
  .squisq-wysiwyg-editor h3 {
865
1205
  font-size: 1.17em;
866
- margin: 0.83em 0;
1206
+ margin: 1.3em 0 0.3em;
867
1207
  }
868
1208
  .squisq-wysiwyg-editor h4 {
869
1209
  font-size: 1em;
870
- margin: 1em 0;
1210
+ margin: 1.4em 0 0.3em;
871
1211
  }
872
1212
  .squisq-wysiwyg-editor h5 {
873
1213
  font-size: 0.83em;
874
- margin: 1.17em 0;
1214
+ margin: 1.5em 0 0.3em;
875
1215
  }
876
1216
  .squisq-wysiwyg-editor h6 {
877
1217
  font-size: 0.67em;
878
- margin: 1.33em 0;
1218
+ margin: 1.6em 0 0.3em;
1219
+ }
1220
+
1221
+ /* The first block in the document has nothing above it — don't push the top of
1222
+ the editor down with the section-break margin. (The editable ProseMirror node
1223
+ itself carries the `squisq-wysiwyg-editor` class, so its blocks are direct
1224
+ children.) */
1225
+ .squisq-wysiwyg-editor > :first-child {
1226
+ margin-top: 0;
879
1227
  }
880
1228
 
881
1229
  .squisq-wysiwyg-editor p {
@@ -930,6 +1278,43 @@
930
1278
  margin: 0.25em 0;
931
1279
  }
932
1280
 
1281
+ /* Task lists (GFM checkboxes). TaskItem renders
1282
+ `<li data-type="taskItem"><label><input type="checkbox"><span></span></label><div>…</div></li>`
1283
+ inside `<ul data-type="taskList">`. Drop the bullet/indent and lay each item
1284
+ out as a checkbox + content row. */
1285
+ .squisq-wysiwyg-editor ul[data-type='taskList'] {
1286
+ list-style: none;
1287
+ padding-left: 0;
1288
+ }
1289
+
1290
+ .squisq-wysiwyg-editor ul[data-type='taskList'] li {
1291
+ display: flex;
1292
+ align-items: flex-start;
1293
+ gap: 0.5em;
1294
+ }
1295
+
1296
+ .squisq-wysiwyg-editor ul[data-type='taskList'] li > label {
1297
+ flex: 0 0 auto;
1298
+ margin-top: 0.15em;
1299
+ user-select: none;
1300
+ }
1301
+
1302
+ .squisq-wysiwyg-editor ul[data-type='taskList'] li > div {
1303
+ flex: 1 1 auto;
1304
+ min-width: 0;
1305
+ }
1306
+
1307
+ .squisq-wysiwyg-editor ul[data-type='taskList'] li > div > p {
1308
+ margin: 0;
1309
+ }
1310
+
1311
+ .squisq-wysiwyg-editor ul[data-type='taskList'] input[type='checkbox'] {
1312
+ width: 1em;
1313
+ height: 1em;
1314
+ cursor: pointer;
1315
+ accent-color: #2563eb;
1316
+ }
1317
+
933
1318
  .squisq-wysiwyg-editor hr {
934
1319
  border: none;
935
1320
  border-top: 2px solid #e5e7eb;
@@ -1187,6 +1572,8 @@
1187
1572
  /* Toolbar */
1188
1573
  .squisq-editor-shell[data-theme='dark'] .squisq-toolbar {
1189
1574
  background: rgba(255, 255, 255, 0.08);
1575
+ /* Muted, but lighter than the dark-theme label color (#d1d5db). */
1576
+ --squisq-toolbar-icon: #9ca3af;
1190
1577
  }
1191
1578
 
1192
1579
  .squisq-editor-shell[data-theme='dark'] .squisq-toolbar-view-tabs {
@@ -1308,8 +1695,25 @@
1308
1695
  0 2px 6px rgba(0, 0, 0, 0.2);
1309
1696
  }
1310
1697
 
1311
- .squisq-editor-shell[data-theme='dark'] .squisq-template-gallery-none:hover {
1312
- background: #374151;
1698
+ .squisq-editor-shell[data-theme='dark'] .squisq-template-gallery-search {
1699
+ background: #1f2937;
1700
+ border-bottom-color: #374151;
1701
+ }
1702
+
1703
+ .squisq-editor-shell[data-theme='dark'] .squisq-template-gallery-search-input {
1704
+ color: #f9fafb;
1705
+ }
1706
+
1707
+ .squisq-editor-shell[data-theme='dark'] .squisq-template-gallery-search-input::placeholder {
1708
+ color: #6b7280;
1709
+ }
1710
+
1711
+ .squisq-editor-shell[data-theme='dark'] .squisq-template-gallery-empty {
1712
+ color: #9ca3af;
1713
+ }
1714
+
1715
+ .squisq-editor-shell[data-theme='dark'] .squisq-template-gallery-none:hover {
1716
+ background: #374151;
1313
1717
  }
1314
1718
 
1315
1719
  .squisq-editor-shell[data-theme='dark']
@@ -2806,219 +3210,721 @@
2806
3210
  min-height: 0;
2807
3211
  }
2808
3212
 
2809
- /* All gutter chrome is parameterized through CSS custom properties so
2810
- hosts can re-theme without overriding individual rules. Defaults
2811
- below match the original light-mode values; the dark-mode block
2812
- further down rebinds them. Hosts override at any selector level
2813
- (e.g. `.db-shell[data-theme='dark'] .squisq-editor-shell { ... }`). */
2814
- .squisq-editor-shell {
2815
- --squisq-preview-accent: #6366f1;
2816
- --squisq-preview-accent-muted: #94a3b8;
2817
- --squisq-preview-gutter-bg: #dcd8d0;
2818
- --squisq-preview-card-bg: #ffffff;
2819
- --squisq-preview-card-border: #e5e7eb;
2820
- --squisq-preview-card-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
2821
- --squisq-preview-card-title: #111827;
2822
- --squisq-preview-card-svg-bg: #f1f5f9;
2823
- --squisq-preview-dot-halo: #ffffff;
2824
- --squisq-preview-empty-bg: #ffffff;
2825
- --squisq-preview-empty-border: #d1d5db;
2826
- --squisq-preview-empty-text: #6b7280;
3213
+ /* ─── Block-at-a-time card ───────────────────────────────
3214
+ Wraps a single block's editor surface with Prev / Next / Add chrome.
3215
+ The card itself is the flex child that takes the remaining width; its
3216
+ body holds the editor (which fills the height) and the nav bar pins to
3217
+ the bottom. */
3218
+ .squisq-editor-with-gutter > .squisq-block-card {
3219
+ flex: 1 1 auto;
3220
+ min-width: 0;
3221
+ min-height: 0;
2827
3222
  }
2828
3223
 
2829
- .squisq-inline-preview-gutter {
2830
- /* Tinted to match the WYSIWYG container's "desk" color so this pane
2831
- reads as part of the same surrounding chrome the Monaco gutter
2832
- blends into on the other side of the canvas. */
2833
- padding: 0;
2834
- background: var(--squisq-preview-gutter-bg);
2835
- border: none;
2836
- box-sizing: border-box;
2837
- font-family: inherit;
3224
+ /* ─── Block preview panel ─────────────────────────────────
3225
+ A large live preview of the active block, beside the card in
3226
+ block / timeline mode when "Show block previews" is on. Shares the
3227
+ editing area roughly 50/50 with the card; the framed slide inside
3228
+ fills the panel width at the active viewport's aspect ratio. */
3229
+ .squisq-editor-with-gutter > .squisq-block-preview-panel {
3230
+ flex: 1 1 0;
3231
+ min-width: 260px;
3232
+ min-height: 0;
2838
3233
  }
2839
3234
 
2840
- .squisq-inline-preview-empty {
2841
- position: absolute;
2842
- top: 12px;
2843
- left: 12px;
2844
- right: 12px;
2845
- color: var(--squisq-preview-empty-text);
2846
- font-size: 12px;
2847
- line-height: 1.4;
2848
- padding: 12px;
2849
- text-align: center;
2850
- border: 1px dashed var(--squisq-preview-empty-border);
2851
- border-radius: 8px;
2852
- background: var(--squisq-preview-empty-bg);
3235
+ .squisq-block-preview-panel {
3236
+ display: flex;
3237
+ align-items: center;
3238
+ justify-content: center;
3239
+ padding: 12px 16px;
3240
+ /* Same beige "desk" as the block card so the framed slide sits on it. */
3241
+ background: #dcd8d0;
3242
+ border-left: 1px solid #b0a99a;
3243
+ overflow: auto;
2853
3244
  }
2854
3245
 
2855
- .squisq-inline-preview-empty p {
2856
- margin: 0;
3246
+ .squisq-block-preview-frame {
3247
+ width: 100%;
3248
+ max-width: 760px;
3249
+ max-height: 100%;
3250
+ overflow: hidden;
3251
+ background: var(--squisq-preview-card-svg-bg, #f1f5f9);
3252
+ border: 1px solid #b0a99a;
3253
+ border-radius: 6px;
3254
+ box-shadow: 0 1px 3px rgba(15, 23, 42, 0.1);
2857
3255
  }
2858
3256
 
2859
- .squisq-inline-preview-card {
3257
+ .squisq-block-preview-frame svg {
3258
+ display: block;
3259
+ width: 100%;
3260
+ height: 100%;
3261
+ }
3262
+
3263
+ .squisq-editor-shell[data-theme='dark'] .squisq-block-preview-panel {
3264
+ background: #0f1219;
3265
+ border-left-color: #2a3144;
3266
+ }
3267
+
3268
+ .squisq-editor-shell[data-theme='dark'] .squisq-block-preview-frame {
3269
+ border-color: #2a3144;
3270
+ }
3271
+
3272
+ .squisq-block-card {
2860
3273
  display: flex;
2861
3274
  flex-direction: column;
2862
- gap: 6px;
2863
- padding: 8px;
2864
- background: var(--squisq-preview-card-bg);
2865
- border: 1px solid var(--squisq-preview-card-border);
2866
- border-radius: 8px;
2867
- box-shadow: var(--squisq-preview-card-shadow);
2868
- transition: top 120ms ease-out;
3275
+ height: 100%;
3276
+ min-height: 0;
3277
+ /* Matches the editor "desk" so the card body's top/bottom margin reads as
3278
+ the same beige as the left/right sides rather than white. */
3279
+ background: #dcd8d0;
2869
3280
  }
2870
3281
 
2871
- .squisq-inline-preview-card-label {
3282
+ .squisq-block-card-body {
3283
+ flex: 1 1 auto;
3284
+ min-height: 0;
3285
+ position: relative;
2872
3286
  display: flex;
2873
- flex-direction: row;
2874
- align-items: baseline;
2875
- gap: 6px;
2876
- font-size: 12px;
2877
- line-height: 1.3;
2878
- white-space: nowrap;
2879
- overflow: hidden;
3287
+ /* A few px of beige "desk" above and below the block, so the framed card
3288
+ sits off the toolbar and nav bar. The body background is transparent, so
3289
+ this reveals the card's desk color. */
3290
+ padding: 6px 0;
2880
3291
  }
2881
3292
 
2882
- .squisq-inline-preview-card-template {
2883
- font-weight: 600;
2884
- color: var(--squisq-preview-accent);
2885
- flex-shrink: 0;
3293
+ .squisq-block-card-body > .squisq-raw-editor-container,
3294
+ .squisq-block-card-body > .squisq-wysiwyg-container {
3295
+ flex: 1 1 auto;
3296
+ min-width: 0;
3297
+ min-height: 0;
2886
3298
  }
2887
3299
 
2888
- .squisq-inline-preview-card-sep {
2889
- color: var(--squisq-preview-accent-muted);
2890
- flex-shrink: 0;
3300
+ /* Frame just the editable surface — top and bottom — so a single block reads
3301
+ as a discrete card. Scoped to the surface (not the full-width body) so the
3302
+ line tracks the white page rather than stretching across the beige desk.
3303
+ Uses the same seam color as the Monaco frame so it reads against the desk. */
3304
+ .squisq-block-card-body .squisq-wysiwyg-editor {
3305
+ border-top: 1px solid #b0a99a;
3306
+ border-bottom: 1px solid #b0a99a;
2891
3307
  }
2892
3308
 
2893
- .squisq-inline-preview-card-title {
2894
- color: var(--squisq-preview-card-title);
2895
- font-weight: 500;
2896
- overflow: hidden;
2897
- text-overflow: ellipsis;
3309
+ .squisq-block-card-body > .squisq-raw-editor-container {
3310
+ border-top: 1px solid #b0a99a;
3311
+ border-bottom: 1px solid #b0a99a;
2898
3312
  }
2899
3313
 
2900
- .squisq-inline-preview-card-svg {
2901
- width: 100%;
2902
- overflow: hidden;
2903
- border-radius: 4px;
2904
- background: var(--squisq-preview-card-svg-bg);
3314
+ .squisq-block-card-nav {
3315
+ display: flex;
3316
+ align-items: center;
3317
+ gap: 10px;
3318
+ padding: 6px 12px;
3319
+ background: #f9fafb;
3320
+ flex-shrink: 0;
2905
3321
  }
2906
3322
 
2907
- /* Vertical bracket line showing each block's editor extent. Lives in the
2908
- connector strip, anchored to its right edge so the cards sit
2909
- immediately to its right. The diagonal connector bridges from the
2910
- vertical line to its card at the heading row. */
2911
- .squisq-inline-preview-extent {
2912
- width: 4px;
2913
- background: var(--squisq-preview-accent);
2914
- border-radius: 2px;
2915
- transition:
2916
- top 120ms ease-out,
2917
- height 120ms ease-out;
3323
+ .squisq-block-card-position {
3324
+ font-size: 12px;
3325
+ color: #6b7280;
3326
+ white-space: nowrap;
3327
+ margin: 0 auto;
2918
3328
  }
2919
3329
 
2920
- /* Untagged blocks get a thinner, lower-contrast bar — they still surface
2921
- the block's structural reach but recede behind the strong accent bars
2922
- that pair with cards. */
2923
- .squisq-inline-preview-extent--untagged {
2924
- width: 3px;
2925
- background: var(--squisq-preview-accent-muted);
2926
- border-radius: 1.5px;
3330
+ .squisq-block-card-button {
3331
+ display: inline-flex;
3332
+ align-items: center;
3333
+ gap: 5px;
3334
+ padding: 4px 10px;
3335
+ border: 1px solid #d1d5db;
3336
+ border-radius: 6px;
3337
+ background: #ffffff;
3338
+ color: #374151;
3339
+ font-size: 12px;
3340
+ cursor: pointer;
2927
3341
  }
2928
3342
 
2929
- /* Diagonal connector SVG colors — the JSX uses inline `stroke`/`fill`
2930
- presentation attributes so the geometry stays self-contained, but
2931
- those attributes are weaker than CSS rules and are overridden here
2932
- so all preview accents flow through the same custom property. */
2933
- .squisq-inline-preview-connector-svg line {
2934
- stroke: var(--squisq-preview-accent);
2935
- }
2936
- .squisq-inline-preview-connector-svg circle {
2937
- fill: var(--squisq-preview-accent);
2938
- stroke: var(--squisq-preview-dot-halo);
3343
+ .squisq-block-card-button:hover:not(:disabled) {
3344
+ background: #f3f4f6;
2939
3345
  }
2940
3346
 
2941
- /* Diagonal connector SVG — drawn directly via <line>/<circle> elements
2942
- in InlinePreviewGutter; CSS is intentionally minimal so the JSX
2943
- coordinates remain the source of truth. */
2944
- .squisq-inline-preview-connector-svg line,
2945
- .squisq-inline-preview-connector-svg circle {
2946
- transition:
2947
- cx 120ms ease-out,
2948
- cy 120ms ease-out,
2949
- x1 120ms ease-out,
2950
- y1 120ms ease-out,
2951
- x2 120ms ease-out,
2952
- y2 120ms ease-out;
3347
+ .squisq-block-card-button:disabled {
3348
+ opacity: 0.45;
3349
+ cursor: default;
2953
3350
  }
2954
3351
 
2955
- /* Auto-hide the gutter (and its connector strip) when the editor body is
2956
- too narrow to host both surfaces comfortably. */
2957
- @container squisq-editor-body (max-width: 720px) {
2958
- .squisq-inline-preview-gutter,
2959
- .squisq-inline-preview-connectors,
2960
- .squisq-outline {
2961
- display: none;
2962
- }
3352
+ .squisq-block-card-add {
3353
+ margin-left: 8px;
2963
3354
  }
2964
3355
 
2965
- /* ─── Outline pane ─────────────────────────────────────────────────
2966
- Left-side companion to the inline preview gutter — a hierarchical
2967
- tree of the document's headings. Sits as a flex child inside
2968
- `.squisq-editor-with-gutter`; the editor takes whatever space remains. */
3356
+ .squisq-editor-shell[data-theme='dark'] .squisq-block-card {
3357
+ background: #0f1219;
3358
+ }
2969
3359
 
2970
- .squisq-outline {
2971
- padding: 12px 8px;
2972
- background: #dcd8d0;
2973
- font-size: 13px;
2974
- color: #1f2937;
2975
- box-sizing: border-box;
2976
- overflow-y: auto;
3360
+ .squisq-editor-shell[data-theme='dark'] .squisq-block-card-body .squisq-wysiwyg-editor,
3361
+ .squisq-editor-shell[data-theme='dark'] .squisq-block-card-body > .squisq-raw-editor-container {
3362
+ border-top-color: #2a3144;
3363
+ border-bottom-color: #2a3144;
2977
3364
  }
2978
3365
 
2979
- .squisq-outline-empty {
2980
- padding: 16px 12px;
2981
- font-size: 12px;
2982
- color: #6b7280;
2983
- text-align: center;
3366
+ .squisq-editor-shell[data-theme='dark'] .squisq-block-card-nav {
3367
+ background: #1f2937;
2984
3368
  }
2985
3369
 
2986
- .squisq-outline-tree {
2987
- list-style: none;
2988
- margin: 0;
2989
- padding: 0;
3370
+ .squisq-editor-shell[data-theme='dark'] .squisq-block-card-position {
3371
+ color: #9ca3af;
2990
3372
  }
2991
3373
 
2992
- .squisq-outline-item {
2993
- margin: 0;
3374
+ .squisq-editor-shell[data-theme='dark'] .squisq-block-card-button {
3375
+ border-color: #374151;
3376
+ background: #111827;
3377
+ color: #d1d5db;
2994
3378
  }
2995
3379
 
2996
- .squisq-outline-row {
2997
- display: flex;
2998
- align-items: center;
2999
- gap: 6px;
3000
- width: 100%;
3001
- padding: 4px 6px;
3002
- border: none;
3003
- background: transparent;
3004
- text-align: left;
3005
- font: inherit;
3006
- color: inherit;
3007
- cursor: pointer;
3008
- border-radius: 4px;
3009
- white-space: nowrap;
3010
- overflow: hidden;
3380
+ .squisq-editor-shell[data-theme='dark'] .squisq-block-card-button:hover:not(:disabled) {
3381
+ background: #1f2937;
3011
3382
  }
3012
3383
 
3013
- .squisq-outline-row:hover {
3014
- background: #f3f4f6;
3384
+ /* ─── Timeline track ─────────────────────────────────────
3385
+ Horizontal strip of block + media bars shown below the editor in Timeline
3386
+ view. Scrolls horizontally; bars are absolutely positioned by time. */
3387
+ .squisq-timeline {
3388
+ position: relative;
3389
+ flex-shrink: 0;
3390
+ display: flex;
3391
+ flex-direction: row;
3392
+ background: #efece6;
3393
+ border-top: 1px solid #b0a99a;
3394
+ user-select: none;
3015
3395
  }
3016
3396
 
3017
- .squisq-outline-row-text {
3018
- overflow: hidden;
3019
- text-overflow: ellipsis;
3020
- flex: 1;
3021
- min-width: 0;
3397
+ .squisq-timeline-controls {
3398
+ display: flex;
3399
+ flex-direction: column;
3400
+ align-items: center;
3401
+ gap: 6px;
3402
+ padding: 8px 6px;
3403
+ flex-shrink: 0;
3404
+ border-right: 1px solid #b0a99a;
3405
+ }
3406
+
3407
+ .squisq-timeline-zoom-button {
3408
+ display: inline-flex;
3409
+ align-items: center;
3410
+ justify-content: center;
3411
+ width: 24px;
3412
+ height: 24px;
3413
+ padding: 0;
3414
+ border: 1px solid #d1d5db;
3415
+ border-radius: 6px;
3416
+ background: #ffffff;
3417
+ color: #374151;
3418
+ font-size: 16px;
3419
+ line-height: 1;
3420
+ cursor: pointer;
3421
+ }
3422
+
3423
+ .squisq-timeline-zoom-button:hover:not(:disabled) {
3424
+ background: #f3f4f6;
3425
+ }
3426
+
3427
+ .squisq-timeline-zoom-button:disabled {
3428
+ opacity: 0.45;
3429
+ cursor: default;
3430
+ }
3431
+
3432
+ .squisq-timeline-play-button {
3433
+ font-size: 12px;
3434
+ color: #2563eb;
3435
+ }
3436
+
3437
+ .squisq-timeline-time {
3438
+ font-size: 10px;
3439
+ color: #6b7280;
3440
+ text-align: center;
3441
+ white-space: nowrap;
3442
+ font-variant-numeric: tabular-nums;
3443
+ }
3444
+
3445
+ /* Off-screen host for the timed-media playback elements. */
3446
+ .squisq-timeline-media-host {
3447
+ position: absolute;
3448
+ width: 0;
3449
+ height: 0;
3450
+ overflow: hidden;
3451
+ }
3452
+
3453
+ .squisq-timeline-scroll {
3454
+ flex: 1;
3455
+ overflow-x: auto;
3456
+ overflow-y: hidden;
3457
+ padding: 8px 0 0;
3458
+ min-width: 0;
3459
+ min-height: 0;
3460
+ }
3461
+
3462
+ .squisq-timeline-inner {
3463
+ position: relative;
3464
+ display: flex;
3465
+ flex-direction: column;
3466
+ min-width: 100%;
3467
+ min-height: 100%;
3468
+ box-sizing: border-box;
3469
+ /* Leave room at the top for the playhead's knob/notch, which is anchored
3470
+ to the padding-box top (absolute `top: 0`) and so stays above the rows
3471
+ while this padding pushes the track down — the notch reads as a handle
3472
+ sitting above the timeline rather than overlapping the first block. */
3473
+ padding-top: 14px;
3474
+ }
3475
+
3476
+ /* Playhead — draggable scrubber. A wide transparent column is the hit area;
3477
+ the 2px red line is drawn down its center via ::before. */
3478
+ .squisq-timeline-playhead {
3479
+ position: absolute;
3480
+ top: 0;
3481
+ bottom: 0;
3482
+ width: 14px;
3483
+ margin-left: -7px;
3484
+ z-index: 20;
3485
+ cursor: ew-resize;
3486
+ touch-action: none;
3487
+ }
3488
+
3489
+ .squisq-timeline-playhead::before {
3490
+ content: '';
3491
+ position: absolute;
3492
+ top: 0;
3493
+ bottom: 0;
3494
+ left: 6px;
3495
+ width: 2px;
3496
+ background: #ef4444;
3497
+ pointer-events: none;
3498
+ }
3499
+
3500
+ .squisq-timeline-playhead-knob {
3501
+ position: absolute;
3502
+ top: 0;
3503
+ left: 2px;
3504
+ width: 10px;
3505
+ height: 9px;
3506
+ border-radius: 2px;
3507
+ background: #ef4444;
3508
+ pointer-events: none;
3509
+ }
3510
+
3511
+ .squisq-timeline-row--ruler {
3512
+ cursor: pointer;
3513
+ }
3514
+
3515
+ .squisq-timeline-row {
3516
+ position: relative;
3517
+ height: 40px;
3518
+ margin: 0 0 6px;
3519
+ }
3520
+
3521
+ .squisq-timeline-row--media {
3522
+ height: 26px;
3523
+ margin-bottom: 0;
3524
+ }
3525
+
3526
+ .squisq-timeline-block {
3527
+ position: absolute;
3528
+ top: 0;
3529
+ height: 100%;
3530
+ box-sizing: border-box;
3531
+ display: flex;
3532
+ align-items: center;
3533
+ padding: 0 8px;
3534
+ border: 1px solid #b0a99a;
3535
+ border-radius: 5px;
3536
+ background: #ffffff;
3537
+ color: #374151;
3538
+ font-size: 12px;
3539
+ cursor: pointer;
3540
+ overflow: hidden;
3541
+ }
3542
+
3543
+ .squisq-timeline-block--active {
3544
+ border-color: var(--squisq-outline-accent, #6c5ce7);
3545
+ box-shadow: 0 0 0 1px var(--squisq-outline-accent, #6c5ce7) inset;
3546
+ }
3547
+
3548
+ /* Per-bar slideshow thumbnail — right-aligned and inset with a small margin
3549
+ so it reads as a card tucked into the track. Width follows the 16:9 viewport
3550
+ from the fixed (margin-inset) height. */
3551
+ .squisq-timeline-block-thumb {
3552
+ position: absolute;
3553
+ top: 4px;
3554
+ bottom: 4px;
3555
+ right: 4px;
3556
+ aspect-ratio: 16 / 9;
3557
+ z-index: 0;
3558
+ overflow: hidden;
3559
+ border-radius: 3px;
3560
+ border: 1px solid rgba(0, 0, 0, 0.12);
3561
+ pointer-events: none;
3562
+ }
3563
+
3564
+ .squisq-timeline-block-thumb svg {
3565
+ display: block;
3566
+ width: 100%;
3567
+ height: 100%;
3568
+ }
3569
+
3570
+ .squisq-timeline-block-label {
3571
+ position: relative;
3572
+ z-index: 1;
3573
+ white-space: nowrap;
3574
+ overflow: hidden;
3575
+ text-overflow: ellipsis;
3576
+ pointer-events: none;
3577
+ /* Legible over the thumbnail. */
3578
+ background: rgba(255, 255, 255, 0.82);
3579
+ border-radius: 3px;
3580
+ padding: 0 4px;
3581
+ }
3582
+
3583
+ .squisq-timeline-clip {
3584
+ position: absolute;
3585
+ top: 0;
3586
+ height: 100%;
3587
+ box-sizing: border-box;
3588
+ display: flex;
3589
+ align-items: center;
3590
+ padding: 0 6px;
3591
+ border-radius: 4px;
3592
+ font-size: 11px;
3593
+ color: #ffffff;
3594
+ cursor: grab;
3595
+ overflow: hidden;
3596
+ }
3597
+
3598
+ .squisq-timeline-clip--audio {
3599
+ background: #2f855a;
3600
+ }
3601
+
3602
+ .squisq-timeline-clip--video {
3603
+ background: #2b6cb0;
3604
+ }
3605
+
3606
+ /* Body-embedded media (recordings, dropped files): snapped to its block,
3607
+ outlined to distinguish from authored `{[…]}` clips. Dragging re-times it
3608
+ (and converts it to a clip annotation). */
3609
+ .squisq-timeline-clip--embedded {
3610
+ opacity: 0.8;
3611
+ border: 1px dashed rgba(255, 255, 255, 0.7);
3612
+ }
3613
+
3614
+ .squisq-timeline-clip--document {
3615
+ opacity: 0.85;
3616
+ background-image: repeating-linear-gradient(
3617
+ 45deg,
3618
+ rgba(255, 255, 255, 0.12) 0,
3619
+ rgba(255, 255, 255, 0.12) 6px,
3620
+ transparent 6px,
3621
+ transparent 12px
3622
+ );
3623
+ }
3624
+
3625
+ .squisq-timeline-clip-label {
3626
+ white-space: nowrap;
3627
+ overflow: hidden;
3628
+ text-overflow: ellipsis;
3629
+ pointer-events: none;
3630
+ }
3631
+
3632
+ /* Drag handles on a bar's edges. */
3633
+ .squisq-timeline-edge {
3634
+ position: absolute;
3635
+ top: 0;
3636
+ width: 7px;
3637
+ height: 100%;
3638
+ cursor: ew-resize;
3639
+ }
3640
+
3641
+ .squisq-timeline-edge--left {
3642
+ left: -1px;
3643
+ }
3644
+
3645
+ .squisq-timeline-edge--right {
3646
+ right: -1px;
3647
+ }
3648
+
3649
+ /* Time ruler pinned to the bottom of the track — spans the full width. */
3650
+ .squisq-timeline-row--ruler {
3651
+ height: 18px;
3652
+ margin-top: auto;
3653
+ flex-shrink: 0;
3654
+ overflow: hidden;
3655
+ border-top: 1px solid #c9c2b4;
3656
+ }
3657
+
3658
+ .squisq-timeline-tick {
3659
+ position: absolute;
3660
+ top: 0;
3661
+ height: 100%;
3662
+ border-left: 1px solid #c9c2b4;
3663
+ padding-left: 3px;
3664
+ }
3665
+
3666
+ .squisq-timeline-tick-label {
3667
+ font-size: 10px;
3668
+ color: #6b7280;
3669
+ white-space: nowrap;
3670
+ pointer-events: none;
3671
+ }
3672
+
3673
+ .squisq-editor-shell[data-theme='dark'] .squisq-timeline {
3674
+ background: #0b0e14;
3675
+ border-top-color: #2a3144;
3676
+ }
3677
+
3678
+ .squisq-editor-shell[data-theme='dark'] .squisq-timeline-block {
3679
+ background: #111827;
3680
+ border-color: #2a3144;
3681
+ color: #d1d5db;
3682
+ }
3683
+
3684
+ .squisq-editor-shell[data-theme='dark'] .squisq-timeline-block-label {
3685
+ background: rgba(17, 24, 39, 0.82);
3686
+ }
3687
+
3688
+ .squisq-editor-shell[data-theme='dark'] .squisq-timeline-zoom-button {
3689
+ border-color: #374151;
3690
+ background: #111827;
3691
+ color: #d1d5db;
3692
+ }
3693
+
3694
+ .squisq-editor-shell[data-theme='dark'] .squisq-timeline-time {
3695
+ color: #9ca3af;
3696
+ }
3697
+
3698
+ .squisq-editor-shell[data-theme='dark'] .squisq-timeline-play-button {
3699
+ color: #60a5fa;
3700
+ }
3701
+
3702
+ .squisq-editor-shell[data-theme='dark'] .squisq-timeline-zoom-button:hover:not(:disabled) {
3703
+ background: #1f2937;
3704
+ }
3705
+
3706
+ .squisq-editor-shell[data-theme='dark'] .squisq-timeline-row--ruler,
3707
+ .squisq-editor-shell[data-theme='dark'] .squisq-timeline-tick {
3708
+ border-color: #2a3144;
3709
+ }
3710
+
3711
+ .squisq-editor-shell[data-theme='dark'] .squisq-timeline-tick-label {
3712
+ color: #9ca3af;
3713
+ }
3714
+
3715
+ /* All gutter chrome is parameterized through CSS custom properties so
3716
+ hosts can re-theme without overriding individual rules. Defaults
3717
+ below match the original light-mode values; the dark-mode block
3718
+ further down rebinds them. Hosts override at any selector level
3719
+ (e.g. `.db-shell[data-theme='dark'] .squisq-editor-shell { ... }`). */
3720
+ .squisq-editor-shell {
3721
+ --squisq-preview-accent: #6366f1;
3722
+ --squisq-preview-accent-muted: #94a3b8;
3723
+ --squisq-preview-gutter-bg: #dcd8d0;
3724
+ --squisq-preview-card-bg: #ffffff;
3725
+ --squisq-preview-card-border: #e5e7eb;
3726
+ --squisq-preview-card-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
3727
+ --squisq-preview-card-title: #111827;
3728
+ --squisq-preview-card-svg-bg: #f1f5f9;
3729
+ --squisq-preview-dot-halo: #ffffff;
3730
+ --squisq-preview-empty-bg: #ffffff;
3731
+ --squisq-preview-empty-border: #d1d5db;
3732
+ --squisq-preview-empty-text: #6b7280;
3733
+ }
3734
+
3735
+ .squisq-inline-preview-gutter {
3736
+ /* Tinted to match the WYSIWYG container's "desk" color so this pane
3737
+ reads as part of the same surrounding chrome the Monaco gutter
3738
+ blends into on the other side of the canvas. */
3739
+ padding: 0;
3740
+ background: var(--squisq-preview-gutter-bg);
3741
+ border: none;
3742
+ box-sizing: border-box;
3743
+ font-family: inherit;
3744
+ }
3745
+
3746
+ .squisq-inline-preview-empty {
3747
+ position: absolute;
3748
+ top: 12px;
3749
+ left: 12px;
3750
+ right: 12px;
3751
+ color: var(--squisq-preview-empty-text);
3752
+ font-size: 12px;
3753
+ line-height: 1.4;
3754
+ padding: 12px;
3755
+ text-align: center;
3756
+ border: 1px dashed var(--squisq-preview-empty-border);
3757
+ border-radius: 8px;
3758
+ background: var(--squisq-preview-empty-bg);
3759
+ }
3760
+
3761
+ .squisq-inline-preview-empty p {
3762
+ margin: 0;
3763
+ }
3764
+
3765
+ .squisq-inline-preview-card {
3766
+ display: flex;
3767
+ flex-direction: column;
3768
+ gap: 6px;
3769
+ padding: 8px;
3770
+ background: var(--squisq-preview-card-bg);
3771
+ border: 1px solid var(--squisq-preview-card-border);
3772
+ border-radius: 8px;
3773
+ box-shadow: var(--squisq-preview-card-shadow);
3774
+ transition: top 120ms ease-out;
3775
+ }
3776
+
3777
+ .squisq-inline-preview-card-label {
3778
+ display: flex;
3779
+ flex-direction: row;
3780
+ align-items: baseline;
3781
+ gap: 6px;
3782
+ font-size: 12px;
3783
+ line-height: 1.3;
3784
+ white-space: nowrap;
3785
+ overflow: hidden;
3786
+ }
3787
+
3788
+ .squisq-inline-preview-card-template {
3789
+ font-weight: 600;
3790
+ color: var(--squisq-preview-accent);
3791
+ flex-shrink: 0;
3792
+ }
3793
+
3794
+ .squisq-inline-preview-card-sep {
3795
+ color: var(--squisq-preview-accent-muted);
3796
+ flex-shrink: 0;
3797
+ }
3798
+
3799
+ .squisq-inline-preview-card-title {
3800
+ color: var(--squisq-preview-card-title);
3801
+ font-weight: 500;
3802
+ overflow: hidden;
3803
+ text-overflow: ellipsis;
3804
+ }
3805
+
3806
+ .squisq-inline-preview-card-svg {
3807
+ width: 100%;
3808
+ overflow: hidden;
3809
+ border-radius: 4px;
3810
+ background: var(--squisq-preview-card-svg-bg);
3811
+ }
3812
+
3813
+ /* Vertical bracket line showing each block's editor extent. Lives in the
3814
+ connector strip, anchored to its right edge so the cards sit
3815
+ immediately to its right. The diagonal connector bridges from the
3816
+ vertical line to its card at the heading row. */
3817
+ .squisq-inline-preview-extent {
3818
+ width: 4px;
3819
+ background: var(--squisq-preview-accent);
3820
+ border-radius: 2px;
3821
+ transition:
3822
+ top 120ms ease-out,
3823
+ height 120ms ease-out;
3824
+ }
3825
+
3826
+ /* Untagged blocks get a thinner, lower-contrast bar — they still surface
3827
+ the block's structural reach but recede behind the strong accent bars
3828
+ that pair with cards. */
3829
+ .squisq-inline-preview-extent--untagged {
3830
+ width: 3px;
3831
+ background: var(--squisq-preview-accent-muted);
3832
+ border-radius: 1.5px;
3833
+ }
3834
+
3835
+ /* Diagonal connector SVG colors — the JSX uses inline `stroke`/`fill`
3836
+ presentation attributes so the geometry stays self-contained, but
3837
+ those attributes are weaker than CSS rules and are overridden here
3838
+ so all preview accents flow through the same custom property. */
3839
+ .squisq-inline-preview-connector-svg line {
3840
+ stroke: var(--squisq-preview-accent);
3841
+ }
3842
+ .squisq-inline-preview-connector-svg circle {
3843
+ fill: var(--squisq-preview-accent);
3844
+ stroke: var(--squisq-preview-dot-halo);
3845
+ }
3846
+
3847
+ /* Diagonal connector SVG — drawn directly via <line>/<circle> elements
3848
+ in InlinePreviewGutter; CSS is intentionally minimal so the JSX
3849
+ coordinates remain the source of truth. */
3850
+ .squisq-inline-preview-connector-svg line,
3851
+ .squisq-inline-preview-connector-svg circle {
3852
+ transition:
3853
+ cx 120ms ease-out,
3854
+ cy 120ms ease-out,
3855
+ x1 120ms ease-out,
3856
+ y1 120ms ease-out,
3857
+ x2 120ms ease-out,
3858
+ y2 120ms ease-out;
3859
+ }
3860
+
3861
+ /* Auto-hide the gutter (and its connector strip) when the editor body is
3862
+ too narrow to host both surfaces comfortably. */
3863
+ @container squisq-editor-body (max-width: 720px) {
3864
+ .squisq-inline-preview-gutter,
3865
+ .squisq-inline-preview-connectors,
3866
+ .squisq-outline {
3867
+ display: none;
3868
+ }
3869
+ }
3870
+
3871
+ /* ─── Outline pane ─────────────────────────────────────────────────
3872
+ Left-side companion to the inline preview gutter — a hierarchical
3873
+ tree of the document's headings. Sits as a flex child inside
3874
+ `.squisq-editor-with-gutter`; the editor takes whatever space remains. */
3875
+
3876
+ .squisq-outline {
3877
+ padding: 12px 8px;
3878
+ background: #dcd8d0;
3879
+ font-size: 13px;
3880
+ color: #1f2937;
3881
+ box-sizing: border-box;
3882
+ overflow-y: auto;
3883
+ }
3884
+
3885
+ .squisq-outline-empty {
3886
+ padding: 16px 12px;
3887
+ font-size: 12px;
3888
+ color: #6b7280;
3889
+ text-align: center;
3890
+ }
3891
+
3892
+ .squisq-outline-tree {
3893
+ list-style: none;
3894
+ margin: 0;
3895
+ padding: 0;
3896
+ }
3897
+
3898
+ .squisq-outline-item {
3899
+ margin: 0;
3900
+ }
3901
+
3902
+ .squisq-outline-row {
3903
+ display: flex;
3904
+ align-items: center;
3905
+ gap: 6px;
3906
+ width: 100%;
3907
+ padding: 4px 6px;
3908
+ border: none;
3909
+ background: transparent;
3910
+ text-align: left;
3911
+ font: inherit;
3912
+ color: inherit;
3913
+ cursor: pointer;
3914
+ border-radius: 4px;
3915
+ white-space: nowrap;
3916
+ overflow: hidden;
3917
+ }
3918
+
3919
+ .squisq-outline-row:hover {
3920
+ background: #f3f4f6;
3921
+ }
3922
+
3923
+ .squisq-outline-row-text {
3924
+ overflow: hidden;
3925
+ text-overflow: ellipsis;
3926
+ flex: 1;
3927
+ min-width: 0;
3022
3928
  }
3023
3929
 
3024
3930
  /* Depth-based indent. The outermost level still pays a small base
@@ -3636,15 +4542,19 @@
3636
4542
  .squisq-theme-picker-popover {
3637
4543
  max-height: min(70vh, 520px);
3638
4544
  overflow-y: auto;
4545
+ box-sizing: border-box;
3639
4546
  background: var(--squisq-bg, #fff);
3640
4547
  color: var(--squisq-text, #1f2937);
3641
4548
  border: 1px solid var(--squisq-border, #e5e7eb);
3642
4549
  border-radius: 8px;
3643
4550
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.18);
3644
4551
  padding: 6px;
3645
- display: flex;
3646
- flex-direction: column;
3647
- gap: 2px;
4552
+ /* Card grid: each theme is a compact card, packed into as many columns
4553
+ as the (viewport-clamped) popover width allows. Collapses to a single
4554
+ column on narrow windows so the popover never overruns the viewport. */
4555
+ display: grid;
4556
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
4557
+ gap: 4px;
3648
4558
  animation: squisq-theme-picker-appear 0.12s ease-out;
3649
4559
  }
3650
4560
 
@@ -3661,8 +4571,9 @@
3661
4571
 
3662
4572
  .squisq-theme-picker-row {
3663
4573
  display: flex;
3664
- align-items: center;
3665
- gap: 10px;
4574
+ flex-direction: column;
4575
+ align-items: stretch;
4576
+ gap: 6px;
3666
4577
  padding: 8px;
3667
4578
  border-radius: 6px;
3668
4579
  background: transparent;
@@ -3692,8 +4603,9 @@
3692
4603
  }
3693
4604
 
3694
4605
  .squisq-theme-picker-row .squisq-theme-picker-name-chip {
3695
- flex-shrink: 0;
3696
- min-width: 110px;
4606
+ width: 100%;
4607
+ min-width: 0;
4608
+ box-sizing: border-box;
3697
4609
  text-align: center;
3698
4610
  padding: 6px 10px;
3699
4611
  font-size: 13px;
@@ -3716,24 +4628,206 @@
3716
4628
  .squisq-theme-picker-row-desc {
3717
4629
  font-size: 11px;
3718
4630
  color: var(--squisq-text-muted, #6b7280);
3719
- line-height: 1.35;
3720
- display: -webkit-box;
3721
- -webkit-line-clamp: 2;
3722
- -webkit-box-orient: vertical;
3723
- overflow: hidden;
4631
+ line-height: 1.35;
4632
+ display: -webkit-box;
4633
+ -webkit-line-clamp: 2;
4634
+ -webkit-box-orient: vertical;
4635
+ overflow: hidden;
4636
+ }
4637
+
4638
+ /* Dark editor-shell variants for the trigger / popover */
4639
+ .squisq-editor-shell[data-theme='dark'] .squisq-theme-picker-trigger {
4640
+ background: #111827;
4641
+ border-color: #374151;
4642
+ color: #e5e7eb;
4643
+ }
4644
+
4645
+ .squisq-editor-shell[data-theme='dark'] .squisq-theme-picker-popover {
4646
+ background: #1f2937;
4647
+ border-color: #374151;
4648
+ color: #e5e7eb;
4649
+ }
4650
+
4651
+ /* ─── Theme "edit" pencil (beside the toolbar theme dropdown) ────── */
4652
+
4653
+ .squisq-theme-edit-btn {
4654
+ display: inline-flex;
4655
+ align-items: center;
4656
+ justify-content: center;
4657
+ width: 24px;
4658
+ height: 24px;
4659
+ padding: 0;
4660
+ flex-shrink: 0;
4661
+ border: 1px solid var(--squisq-border, #d1d5db);
4662
+ border-radius: 4px;
4663
+ background: var(--squisq-input-bg, #fff);
4664
+ color: var(--squisq-text-muted, #6b7280);
4665
+ cursor: pointer;
4666
+ }
4667
+ .squisq-theme-edit-btn:hover {
4668
+ color: var(--squisq-text, #1f2937);
4669
+ border-color: var(--squisq-text-muted, #9ca3af);
4670
+ }
4671
+ .squisq-editor-shell[data-theme='dark'] .squisq-theme-edit-btn {
4672
+ background: #111827;
4673
+ border-color: #374151;
4674
+ color: #9ca3af;
4675
+ }
4676
+ .squisq-editor-shell[data-theme='dark'] .squisq-theme-edit-btn:hover {
4677
+ color: #e5e7eb;
4678
+ border-color: #4b5563;
4679
+ }
4680
+
4681
+ /* ─── Theme picker: custom-theme group, cards, create row ───────── */
4682
+
4683
+ /* Group label + create row span the full grid width. */
4684
+ .squisq-theme-picker-group-label {
4685
+ grid-column: 1 / -1;
4686
+ padding: 8px 8px 2px;
4687
+ font-size: 11px;
4688
+ font-weight: 600;
4689
+ text-transform: uppercase;
4690
+ letter-spacing: 0.04em;
4691
+ color: var(--squisq-text-muted, #6b7280);
4692
+ }
4693
+
4694
+ /* A custom card is wrapped so its edit/delete actions can overlay the corner
4695
+ without nesting interactive elements inside the selectable button. */
4696
+ .squisq-theme-picker-custom-wrap {
4697
+ position: relative;
4698
+ display: flex;
4699
+ }
4700
+ .squisq-theme-picker-custom-wrap .squisq-theme-picker-row {
4701
+ flex: 1;
4702
+ }
4703
+ .squisq-theme-picker-card-actions {
4704
+ position: absolute;
4705
+ top: 4px;
4706
+ right: 4px;
4707
+ display: flex;
4708
+ gap: 2px;
4709
+ opacity: 0;
4710
+ transition: opacity 0.1s ease;
4711
+ }
4712
+ .squisq-theme-picker-custom-wrap:hover .squisq-theme-picker-card-actions {
4713
+ opacity: 1;
4714
+ }
4715
+ .squisq-theme-picker-card-action {
4716
+ width: 20px;
4717
+ height: 20px;
4718
+ display: inline-flex;
4719
+ align-items: center;
4720
+ justify-content: center;
4721
+ border: 1px solid var(--squisq-border, #e5e7eb);
4722
+ border-radius: 4px;
4723
+ background: var(--squisq-bg, #fff);
4724
+ color: var(--squisq-text-muted, #6b7280);
4725
+ font-size: 12px;
4726
+ cursor: pointer;
4727
+ padding: 0;
4728
+ }
4729
+ .squisq-theme-picker-card-action:hover {
4730
+ color: var(--squisq-text, #1f2937);
4731
+ border-color: var(--squisq-text-muted, #9ca3af);
4732
+ }
4733
+
4734
+ .squisq-theme-picker-create {
4735
+ grid-column: 1 / -1;
4736
+ justify-content: flex-start;
4737
+ gap: 10px;
4738
+ border-style: dashed;
4739
+ border-color: var(--squisq-border, #d1d5db);
4740
+ color: var(--squisq-text, #1f2937);
4741
+ }
4742
+ .squisq-theme-picker-create-plus {
4743
+ flex-shrink: 0;
4744
+ width: 28px;
4745
+ height: 28px;
4746
+ display: inline-flex;
4747
+ align-items: center;
4748
+ justify-content: center;
4749
+ border-radius: 6px;
4750
+ background: var(--squisq-row-hover, #f3f4f6);
4751
+ font-size: 18px;
4752
+ line-height: 1;
4753
+ }
4754
+
4755
+ /* ─── Custom theme designer: right-docked pane + accents ────────── */
4756
+
4757
+ /* Docked into the editor's content row as a fixed-width, full-height flex item
4758
+ (mirrors `.squisq-media-bin`), so the preview flexes narrower beside it — no
4759
+ overlay, no covering the toolbars. The WYSIWYG preview stays fully visible
4760
+ and interactive while the user edits the theme. */
4761
+ .squisq-custom-theme-pane {
4762
+ width: 380px;
4763
+ max-width: 100%;
4764
+ flex-shrink: 0;
4765
+ display: flex;
4766
+ flex-direction: column;
4767
+ background: var(--squisq-bg, #fff);
4768
+ color: var(--squisq-text, #1f2937);
4769
+ border-left: 1px solid var(--squisq-border, #e5e7eb);
4770
+ overflow: hidden;
4771
+ }
4772
+
4773
+ .squisq-custom-theme-pane .squisq-doc-settings-header,
4774
+ .squisq-custom-theme-pane .squisq-doc-settings-footer {
4775
+ flex-shrink: 0;
4776
+ }
4777
+
4778
+ /* Body flex-grows and scrolls; footer/header stay pinned. */
4779
+ .squisq-custom-theme-pane-body {
4780
+ flex: 1;
4781
+ overflow-y: auto;
4782
+ max-height: none;
4783
+ }
4784
+
4785
+ /* Three buttons in a narrow pane — wrap and right-align rather than overflow. */
4786
+ .squisq-custom-theme-pane-footer {
4787
+ flex-wrap: wrap;
4788
+ justify-content: flex-end;
4789
+ gap: 6px;
4790
+ }
4791
+
4792
+ .squisq-editor-shell[data-theme='dark'] .squisq-custom-theme-pane {
4793
+ background: #1f2937;
4794
+ border-color: #374151;
4795
+ color: #e5e7eb;
4796
+ }
4797
+
4798
+ .squisq-theme-customizer-accents {
4799
+ display: flex;
4800
+ flex-direction: column;
4801
+ gap: 6px;
4802
+ }
4803
+ .squisq-theme-customizer-row--accent {
4804
+ display: flex;
4805
+ align-items: center;
4806
+ gap: 8px;
4807
+ }
4808
+ .squisq-theme-customizer-accent-remove {
4809
+ width: 24px;
4810
+ height: 24px;
4811
+ flex-shrink: 0;
4812
+ display: inline-flex;
4813
+ align-items: center;
4814
+ justify-content: center;
4815
+ border: 1px solid var(--squisq-border, #e5e7eb);
4816
+ border-radius: 4px;
4817
+ background: transparent;
4818
+ color: var(--squisq-text-muted, #6b7280);
4819
+ font-size: 16px;
4820
+ line-height: 1;
4821
+ cursor: pointer;
4822
+ padding: 0;
3724
4823
  }
3725
-
3726
- /* Dark editor-shell variants for the trigger / popover */
3727
- .squisq-editor-shell[data-theme='dark'] .squisq-theme-picker-trigger {
3728
- background: #111827;
3729
- border-color: #374151;
3730
- color: #e5e7eb;
4824
+ .squisq-theme-customizer-accent-remove:hover {
4825
+ color: #dc2626;
4826
+ border-color: #fca5a5;
3731
4827
  }
3732
-
3733
- .squisq-editor-shell[data-theme='dark'] .squisq-theme-picker-popover {
3734
- background: #1f2937;
3735
- border-color: #374151;
3736
- color: #e5e7eb;
4828
+ .squisq-theme-customizer-accent-add {
4829
+ align-self: flex-start;
4830
+ margin-top: 2px;
3737
4831
  }
3738
4832
 
3739
4833
  /* ─── Document settings dialog ───────────────────────── */
@@ -3874,3 +4968,321 @@
3874
4968
  background: #2563eb;
3875
4969
  border-color: #2563eb;
3876
4970
  }
4971
+
4972
+ /* ── Transition picker ──────────────────────────────────────────────
4973
+ Toolbar control + portal flyout for choosing a block transition. The
4974
+ trigger reuses the template picker's visual language; the flyout is its
4975
+ own grouped grid with direction + duration sub-controls. */
4976
+ .squisq-transition-picker {
4977
+ display: inline-flex;
4978
+ }
4979
+
4980
+ .squisq-transition-picker-trigger {
4981
+ display: inline-flex;
4982
+ align-items: center;
4983
+ gap: 5px;
4984
+ padding: 3px 8px;
4985
+ font-size: 12px;
4986
+ font-weight: 500;
4987
+ color: #374151;
4988
+ background: #fff;
4989
+ border: 1px solid #d1d5db;
4990
+ border-radius: 5px;
4991
+ cursor: pointer;
4992
+ white-space: nowrap;
4993
+ transition:
4994
+ background 0.1s,
4995
+ border-color 0.1s;
4996
+ }
4997
+
4998
+ .squisq-transition-picker-trigger:hover {
4999
+ background: #f3f4f6;
5000
+ border-color: #9ca3af;
5001
+ }
5002
+
5003
+ .squisq-transition-picker-trigger--open {
5004
+ background: #f3f4f6;
5005
+ border-color: #6366f1;
5006
+ outline: 2px solid rgba(99, 102, 241, 0.25);
5007
+ outline-offset: -1px;
5008
+ }
5009
+
5010
+ .squisq-transition-picker-trigger-label {
5011
+ color: #6b7280;
5012
+ }
5013
+
5014
+ .squisq-transition-picker-trigger-value {
5015
+ color: #111827;
5016
+ font-weight: 400;
5017
+ max-width: 110px;
5018
+ overflow: hidden;
5019
+ text-overflow: ellipsis;
5020
+ }
5021
+
5022
+ .squisq-transition-picker-trigger svg {
5023
+ color: #9ca3af;
5024
+ flex-shrink: 0;
5025
+ }
5026
+
5027
+ @container squisq-toolbar (max-width: 900px) {
5028
+ .squisq-transition-picker-trigger-label {
5029
+ display: none;
5030
+ }
5031
+ }
5032
+
5033
+ /* Flyout panel — portaled with fixed positioning (component supplies top/left). */
5034
+ .squisq-transition-flyout {
5035
+ /* Three columns of options keep the panel short enough to avoid running off
5036
+ the viewport. `max-height` is set inline by the component to the actual
5037
+ space available at its anchored position; the list scrolls within it. */
5038
+ width: 460px;
5039
+ max-width: calc(100vw - 24px);
5040
+ display: flex;
5041
+ flex-direction: column;
5042
+ background: #fff;
5043
+ border: 1px solid #e5e7eb;
5044
+ border-radius: 10px;
5045
+ box-shadow:
5046
+ 0 8px 24px rgba(0, 0, 0, 0.12),
5047
+ 0 2px 6px rgba(0, 0, 0, 0.08);
5048
+ overflow: hidden;
5049
+ }
5050
+
5051
+ .squisq-transition-flyout-search {
5052
+ padding: 8px;
5053
+ border-bottom: 1px solid #f0f0f0;
5054
+ }
5055
+
5056
+ .squisq-transition-flyout-search-input {
5057
+ width: 100%;
5058
+ padding: 6px 8px;
5059
+ font-size: 13px;
5060
+ color: #111827;
5061
+ border: 1px solid #d1d5db;
5062
+ border-radius: 6px;
5063
+ outline: none;
5064
+ }
5065
+
5066
+ .squisq-transition-flyout-search-input:focus {
5067
+ border-color: #6366f1;
5068
+ }
5069
+
5070
+ .squisq-transition-flyout-list {
5071
+ /* `min-height: 0` lets this flex child shrink below its content so it scrolls
5072
+ within the panel's capped height instead of overflowing it. The search box
5073
+ above and the direction/duration controls below stay pinned. */
5074
+ min-height: 0;
5075
+ overflow-y: auto;
5076
+ padding: 6px 8px;
5077
+ }
5078
+
5079
+ .squisq-transition-flyout-group {
5080
+ margin-top: 6px;
5081
+ }
5082
+
5083
+ .squisq-transition-flyout-group-title {
5084
+ margin: 6px 2px 4px;
5085
+ font-size: 10px;
5086
+ font-weight: 600;
5087
+ letter-spacing: 0.04em;
5088
+ text-transform: uppercase;
5089
+ color: #9ca3af;
5090
+ }
5091
+
5092
+ .squisq-transition-flyout-grid {
5093
+ display: grid;
5094
+ grid-template-columns: repeat(3, 1fr);
5095
+ gap: 4px;
5096
+ }
5097
+
5098
+ .squisq-transition-option {
5099
+ display: block;
5100
+ width: 100%;
5101
+ text-align: left;
5102
+ padding: 6px 8px;
5103
+ font-size: 12px;
5104
+ color: #374151;
5105
+ background: #fff;
5106
+ border: 1px solid #e5e7eb;
5107
+ border-radius: 6px;
5108
+ cursor: pointer;
5109
+ white-space: nowrap;
5110
+ overflow: hidden;
5111
+ text-overflow: ellipsis;
5112
+ }
5113
+
5114
+ .squisq-transition-option:hover {
5115
+ background: #f3f4f6;
5116
+ border-color: #d1d5db;
5117
+ }
5118
+
5119
+ .squisq-transition-option--selected {
5120
+ background: #eef2ff;
5121
+ border-color: #6366f1;
5122
+ color: #4338ca;
5123
+ font-weight: 600;
5124
+ }
5125
+
5126
+ /* On hover, the label plays the real entering transition (the option button
5127
+ clips it with overflow:hidden; perspective gives the 3D transitions depth).
5128
+ The label is inline-block so transforms apply without disturbing layout, and
5129
+ we override `--transition-duration` to keep previews snappy. */
5130
+ .squisq-transition-option {
5131
+ perspective: 220px;
5132
+ }
5133
+
5134
+ .squisq-transition-option-label {
5135
+ display: inline-block;
5136
+ --transition-duration: 0.55s;
5137
+ transform-origin: center;
5138
+ backface-visibility: hidden;
5139
+ }
5140
+
5141
+ @media (prefers-reduced-motion: reduce) {
5142
+ .squisq-transition-option-label {
5143
+ /* Respect reduced-motion: show the label statically, no entrance preview. */
5144
+ animation: none !important;
5145
+ }
5146
+ }
5147
+
5148
+ .squisq-transition-flyout-empty {
5149
+ padding: 12px 8px;
5150
+ font-size: 12px;
5151
+ color: #9ca3af;
5152
+ text-align: center;
5153
+ }
5154
+
5155
+ /* Direction + duration controls, shown below the list once a type is set. */
5156
+ .squisq-transition-flyout-controls {
5157
+ border-top: 1px solid #f0f0f0;
5158
+ padding: 10px 12px;
5159
+ display: flex;
5160
+ flex-direction: column;
5161
+ gap: 10px;
5162
+ }
5163
+
5164
+ .squisq-transition-flyout-control {
5165
+ display: flex;
5166
+ align-items: center;
5167
+ justify-content: space-between;
5168
+ gap: 10px;
5169
+ }
5170
+
5171
+ .squisq-transition-flyout-control-label {
5172
+ font-size: 11px;
5173
+ font-weight: 600;
5174
+ color: #6b7280;
5175
+ }
5176
+
5177
+ .squisq-transition-direction-row {
5178
+ display: inline-flex;
5179
+ gap: 4px;
5180
+ }
5181
+
5182
+ .squisq-transition-direction-button {
5183
+ padding: 4px 8px;
5184
+ font-size: 11px;
5185
+ color: #374151;
5186
+ background: #fff;
5187
+ border: 1px solid #d1d5db;
5188
+ border-radius: 5px;
5189
+ cursor: pointer;
5190
+ }
5191
+
5192
+ .squisq-transition-direction-button:hover {
5193
+ background: #f3f4f6;
5194
+ }
5195
+
5196
+ .squisq-transition-direction-button--selected {
5197
+ background: #eef2ff;
5198
+ border-color: #6366f1;
5199
+ color: #4338ca;
5200
+ font-weight: 600;
5201
+ }
5202
+
5203
+ .squisq-transition-duration-row {
5204
+ display: inline-flex;
5205
+ align-items: center;
5206
+ gap: 6px;
5207
+ }
5208
+
5209
+ .squisq-transition-duration-input {
5210
+ width: 64px;
5211
+ padding: 4px 6px;
5212
+ font-size: 12px;
5213
+ color: #111827;
5214
+ border: 1px solid #d1d5db;
5215
+ border-radius: 5px;
5216
+ outline: none;
5217
+ }
5218
+
5219
+ .squisq-transition-duration-input:focus {
5220
+ border-color: #6366f1;
5221
+ }
5222
+
5223
+ .squisq-transition-duration-unit {
5224
+ font-size: 11px;
5225
+ color: #9ca3af;
5226
+ }
5227
+
5228
+ /* Dark theme */
5229
+ .squisq-editor-shell[data-theme='dark'] .squisq-transition-picker-trigger {
5230
+ background: #1f2937;
5231
+ color: #e5e7eb;
5232
+ border-color: #4b5563;
5233
+ }
5234
+
5235
+ .squisq-editor-shell[data-theme='dark'] .squisq-transition-picker-trigger:hover {
5236
+ background: #374151;
5237
+ border-color: #6b7280;
5238
+ }
5239
+
5240
+ .squisq-editor-shell[data-theme='dark'] .squisq-transition-picker-trigger--open {
5241
+ background: #374151;
5242
+ border-color: #818cf8;
5243
+ }
5244
+
5245
+ .squisq-editor-shell[data-theme='dark'] .squisq-transition-picker-trigger-value {
5246
+ color: #f9fafb;
5247
+ }
5248
+
5249
+ /* Dark styling for the portaled flyout — keyed off the editor shell the same
5250
+ way the template gallery popover is (see `.squisq-template-gallery` above). */
5251
+ .squisq-editor-shell[data-theme='dark'] .squisq-transition-flyout {
5252
+ background: #1f2937;
5253
+ border-color: #374151;
5254
+ box-shadow:
5255
+ 0 8px 24px rgba(0, 0, 0, 0.4),
5256
+ 0 2px 6px rgba(0, 0, 0, 0.2);
5257
+ }
5258
+
5259
+ .squisq-editor-shell[data-theme='dark'] .squisq-transition-flyout-search,
5260
+ .squisq-editor-shell[data-theme='dark'] .squisq-transition-flyout-controls {
5261
+ border-color: #374151;
5262
+ }
5263
+
5264
+ .squisq-editor-shell[data-theme='dark'] .squisq-transition-flyout-search-input,
5265
+ .squisq-editor-shell[data-theme='dark'] .squisq-transition-duration-input {
5266
+ background: #111827;
5267
+ color: #f9fafb;
5268
+ border-color: #4b5563;
5269
+ }
5270
+
5271
+ .squisq-editor-shell[data-theme='dark'] .squisq-transition-option,
5272
+ .squisq-editor-shell[data-theme='dark'] .squisq-transition-direction-button {
5273
+ background: #1f2937;
5274
+ color: #e5e7eb;
5275
+ border-color: #4b5563;
5276
+ }
5277
+
5278
+ .squisq-editor-shell[data-theme='dark'] .squisq-transition-option:hover,
5279
+ .squisq-editor-shell[data-theme='dark'] .squisq-transition-direction-button:hover {
5280
+ background: #374151;
5281
+ }
5282
+
5283
+ .squisq-editor-shell[data-theme='dark'] .squisq-transition-option--selected,
5284
+ .squisq-editor-shell[data-theme='dark'] .squisq-transition-direction-button--selected {
5285
+ background: #3730a3;
5286
+ border-color: #818cf8;
5287
+ color: #e0e7ff;
5288
+ }