@oh-just-another/react-ui 0.57.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 (186) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/LICENSE +21 -0
  3. package/README.md +54 -0
  4. package/dist/.tsbuildinfo +1 -0
  5. package/dist/bottom-sheet.d.ts +26 -0
  6. package/dist/bottom-sheet.d.ts.map +1 -0
  7. package/dist/bottom-sheet.js +87 -0
  8. package/dist/bottom-sheet.js.map +1 -0
  9. package/dist/button-group.d.ts +14 -0
  10. package/dist/button-group.d.ts.map +1 -0
  11. package/dist/button-group.js +3 -0
  12. package/dist/button-group.js.map +1 -0
  13. package/dist/color-palette.d.ts +34 -0
  14. package/dist/color-palette.d.ts.map +1 -0
  15. package/dist/color-palette.js +77 -0
  16. package/dist/color-palette.js.map +1 -0
  17. package/dist/color-swatch-picker.d.ts +29 -0
  18. package/dist/color-swatch-picker.d.ts.map +1 -0
  19. package/dist/color-swatch-picker.js +56 -0
  20. package/dist/color-swatch-picker.js.map +1 -0
  21. package/dist/command-palette.d.ts +13 -0
  22. package/dist/command-palette.d.ts.map +1 -0
  23. package/dist/command-palette.js +146 -0
  24. package/dist/command-palette.js.map +1 -0
  25. package/dist/comments-panel.d.ts +27 -0
  26. package/dist/comments-panel.d.ts.map +1 -0
  27. package/dist/comments-panel.js +139 -0
  28. package/dist/comments-panel.js.map +1 -0
  29. package/dist/constants.d.ts +112 -0
  30. package/dist/constants.d.ts.map +1 -0
  31. package/dist/constants.js +119 -0
  32. package/dist/constants.js.map +1 -0
  33. package/dist/context-menu-controller.d.ts +41 -0
  34. package/dist/context-menu-controller.d.ts.map +1 -0
  35. package/dist/context-menu-controller.js +26 -0
  36. package/dist/context-menu-controller.js.map +1 -0
  37. package/dist/context-menu.d.ts +53 -0
  38. package/dist/context-menu.d.ts.map +1 -0
  39. package/dist/context-menu.js +333 -0
  40. package/dist/context-menu.js.map +1 -0
  41. package/dist/context.d.ts +44 -0
  42. package/dist/context.d.ts.map +1 -0
  43. package/dist/context.js +95 -0
  44. package/dist/context.js.map +1 -0
  45. package/dist/diagram-bars.d.ts +21 -0
  46. package/dist/diagram-bars.d.ts.map +1 -0
  47. package/dist/diagram-bars.js +4 -0
  48. package/dist/diagram-bars.js.map +1 -0
  49. package/dist/diagram-canvas.d.ts +39 -0
  50. package/dist/diagram-canvas.d.ts.map +1 -0
  51. package/dist/diagram-canvas.js +9 -0
  52. package/dist/diagram-canvas.js.map +1 -0
  53. package/dist/diagram-root.d.ts +70 -0
  54. package/dist/diagram-root.d.ts.map +1 -0
  55. package/dist/diagram-root.js +180 -0
  56. package/dist/diagram-root.js.map +1 -0
  57. package/dist/diff-panel.d.ts +25 -0
  58. package/dist/diff-panel.d.ts.map +1 -0
  59. package/dist/diff-panel.js +77 -0
  60. package/dist/diff-panel.js.map +1 -0
  61. package/dist/edge-style-panel.d.ts +19 -0
  62. package/dist/edge-style-panel.d.ts.map +1 -0
  63. package/dist/edge-style-panel.js +120 -0
  64. package/dist/edge-style-panel.js.map +1 -0
  65. package/dist/frame-name-editor-overlay.d.ts +13 -0
  66. package/dist/frame-name-editor-overlay.d.ts.map +1 -0
  67. package/dist/frame-name-editor-overlay.js +84 -0
  68. package/dist/frame-name-editor-overlay.js.map +1 -0
  69. package/dist/frame-panel.d.ts +19 -0
  70. package/dist/frame-panel.d.ts.map +1 -0
  71. package/dist/frame-panel.js +66 -0
  72. package/dist/frame-panel.js.map +1 -0
  73. package/dist/help-button.d.ts +16 -0
  74. package/dist/help-button.d.ts.map +1 -0
  75. package/dist/help-button.js +14 -0
  76. package/dist/help-button.js.map +1 -0
  77. package/dist/help-dialog.d.ts +56 -0
  78. package/dist/help-dialog.d.ts.map +1 -0
  79. package/dist/help-dialog.js +142 -0
  80. package/dist/help-dialog.js.map +1 -0
  81. package/dist/hooks.d.ts +54 -0
  82. package/dist/hooks.d.ts.map +1 -0
  83. package/dist/hooks.js +85 -0
  84. package/dist/hooks.js.map +1 -0
  85. package/dist/icon-button.d.ts +28 -0
  86. package/dist/icon-button.d.ts.map +1 -0
  87. package/dist/icon-button.js +14 -0
  88. package/dist/icon-button.js.map +1 -0
  89. package/dist/index.d.ts +45 -0
  90. package/dist/index.d.ts.map +1 -0
  91. package/dist/index.js +46 -0
  92. package/dist/index.js.map +1 -0
  93. package/dist/layer-panel.d.ts +13 -0
  94. package/dist/layer-panel.d.ts.map +1 -0
  95. package/dist/layer-panel.js +109 -0
  96. package/dist/layer-panel.js.map +1 -0
  97. package/dist/library-panel.d.ts +33 -0
  98. package/dist/library-panel.d.ts.map +1 -0
  99. package/dist/library-panel.js +37 -0
  100. package/dist/library-panel.js.map +1 -0
  101. package/dist/link-caption-editor.d.ts +8 -0
  102. package/dist/link-caption-editor.d.ts.map +1 -0
  103. package/dist/link-caption-editor.js +65 -0
  104. package/dist/link-caption-editor.js.map +1 -0
  105. package/dist/link-drop-shape-menu.d.ts +12 -0
  106. package/dist/link-drop-shape-menu.d.ts.map +1 -0
  107. package/dist/link-drop-shape-menu.js +55 -0
  108. package/dist/link-drop-shape-menu.js.map +1 -0
  109. package/dist/link-hover-popup.d.ts +2 -0
  110. package/dist/link-hover-popup.d.ts.map +1 -0
  111. package/dist/link-hover-popup.js +96 -0
  112. package/dist/link-hover-popup.js.map +1 -0
  113. package/dist/main-menu.d.ts +67 -0
  114. package/dist/main-menu.d.ts.map +1 -0
  115. package/dist/main-menu.js +243 -0
  116. package/dist/main-menu.js.map +1 -0
  117. package/dist/markdown.d.ts +10 -0
  118. package/dist/markdown.d.ts.map +1 -0
  119. package/dist/markdown.js +131 -0
  120. package/dist/markdown.js.map +1 -0
  121. package/dist/merge-dialog.d.ts +22 -0
  122. package/dist/merge-dialog.d.ts.map +1 -0
  123. package/dist/merge-dialog.js +46 -0
  124. package/dist/merge-dialog.js.map +1 -0
  125. package/dist/modal.d.ts +19 -0
  126. package/dist/modal.d.ts.map +1 -0
  127. package/dist/modal.js +87 -0
  128. package/dist/modal.js.map +1 -0
  129. package/dist/palette.d.ts +73 -0
  130. package/dist/palette.d.ts.map +1 -0
  131. package/dist/palette.js +330 -0
  132. package/dist/palette.js.map +1 -0
  133. package/dist/popover.d.ts +33 -0
  134. package/dist/popover.d.ts.map +1 -0
  135. package/dist/popover.js +107 -0
  136. package/dist/popover.js.map +1 -0
  137. package/dist/property-panel.d.ts +32 -0
  138. package/dist/property-panel.d.ts.map +1 -0
  139. package/dist/property-panel.js +728 -0
  140. package/dist/property-panel.js.map +1 -0
  141. package/dist/reset-to-content-button.d.ts +10 -0
  142. package/dist/reset-to-content-button.d.ts.map +1 -0
  143. package/dist/reset-to-content-button.js +62 -0
  144. package/dist/reset-to-content-button.js.map +1 -0
  145. package/dist/segmented-control.d.ts +23 -0
  146. package/dist/segmented-control.d.ts.map +1 -0
  147. package/dist/segmented-control.js +5 -0
  148. package/dist/segmented-control.js.map +1 -0
  149. package/dist/selection-floating-panel.d.ts +56 -0
  150. package/dist/selection-floating-panel.d.ts.map +1 -0
  151. package/dist/selection-floating-panel.js +251 -0
  152. package/dist/selection-floating-panel.js.map +1 -0
  153. package/dist/sidebar.d.ts +31 -0
  154. package/dist/sidebar.d.ts.map +1 -0
  155. package/dist/sidebar.js +66 -0
  156. package/dist/sidebar.js.map +1 -0
  157. package/dist/slider.d.ts +20 -0
  158. package/dist/slider.d.ts.map +1 -0
  159. package/dist/slider.js +9 -0
  160. package/dist/slider.js.map +1 -0
  161. package/dist/text-editor-overlay.d.ts +16 -0
  162. package/dist/text-editor-overlay.d.ts.map +1 -0
  163. package/dist/text-editor-overlay.js +112 -0
  164. package/dist/text-editor-overlay.js.map +1 -0
  165. package/dist/toast.d.ts +34 -0
  166. package/dist/toast.d.ts.map +1 -0
  167. package/dist/toast.js +89 -0
  168. package/dist/toast.js.map +1 -0
  169. package/dist/toolbar.d.ts +149 -0
  170. package/dist/toolbar.d.ts.map +1 -0
  171. package/dist/toolbar.js +282 -0
  172. package/dist/toolbar.js.map +1 -0
  173. package/dist/tooltip.d.ts +21 -0
  174. package/dist/tooltip.d.ts.map +1 -0
  175. package/dist/tooltip.js +217 -0
  176. package/dist/tooltip.js.map +1 -0
  177. package/dist/ui-layer.d.ts +22 -0
  178. package/dist/ui-layer.d.ts.map +1 -0
  179. package/dist/ui-layer.js +3 -0
  180. package/dist/ui-layer.js.map +1 -0
  181. package/dist/versioning.d.ts +32 -0
  182. package/dist/versioning.d.ts.map +1 -0
  183. package/dist/versioning.js +206 -0
  184. package/dist/versioning.js.map +1 -0
  185. package/package.json +81 -0
  186. package/src/styles.css +1502 -0
package/src/styles.css ADDED
@@ -0,0 +1,1502 @@
1
+ /**
2
+ * Diagram UI stylesheet. Imported once by the host:
3
+ *
4
+ * import "@oh-just-another/react-ui/styles.css";
5
+ *
6
+ * Provides:
7
+ * • CSS variables for theme (`:root` + `[data-theme="dark"]`)
8
+ * • Utility classes prefixed `du-` ("diagram ui") so they don't
9
+ * collide with host's own styles.
10
+ * • Per-component blocks (.du-icon-button, .du-top-bar, ...).
11
+ *
12
+ * Hosts can override any variable in their own stylesheet for
13
+ * theming — every component reads colours / sizes through these
14
+ * variables, never hard-coded.
15
+ *
16
+ * Colour values mirror `@oh-just-another/tokens` — CSS can't import
17
+ * TypeScript, so values are hand-copied with comments pointing
18
+ * back to the token name. Keep this in sync when you change a
19
+ * value in `packages/tokens/src/colors.ts`.
20
+ */
21
+
22
+ :root {
23
+ /* --- Surfaces (from UI_SURFACE.light) --- */
24
+ --du-canvas-bg: #f5f5f5; /* UI_SURFACE.light.canvas */
25
+ --du-ui-bg: rgba(255, 255, 255, 0.95); /* UI_SURFACE.light.bg */
26
+ --du-ui-bg-solid: #ffffff; /* UI_SURFACE.light.bgSolid */
27
+ --du-ui-border: rgba(0, 0, 0, 0.08); /* UI_SURFACE.light.border */
28
+ --du-ui-shadow: 0 1px 4px rgba(0, 0, 0, 0.08), 0 4px 12px rgba(0, 0, 0, 0.04);
29
+
30
+ /* --- Text (from UI_SURFACE.light) --- */
31
+ --du-text: #1a1a1a; /* UI_SURFACE.light.text */
32
+ --du-text-muted: #6b6b6b; /* UI_SURFACE.light.textMuted */
33
+ --du-text-on-accent: #ffffff;
34
+
35
+ /* --- Accent (from UI_ACCENT.light — Radix iris) --- */
36
+ --du-accent: #5b5bd6; /* UI_ACCENT.light.accent (iris.iris9) */
37
+ --du-accent-hover: #5151cd; /* UI_ACCENT.light.accentHover (iris.iris10) */
38
+ --du-danger: #e54d2e; /* UI_ACCENT.light.danger (tomato.tomato9) */
39
+
40
+ /* --- Selected (tonal, modern-style) --- */
41
+ /* Active toolbar / icon buttons get a *muted* tonal fill so they
42
+ read as "this state is selected" without hijacking the eye like
43
+ a saturated accent fill would. The icon switches to the accent
44
+ hue, the background stays close to the surface. */
45
+ --du-selected-bg: #e6e7ff; /* UI_ACCENT.light.selectedBg (iris.iris4) */
46
+ --du-selected-fg: #5753c6; /* UI_ACCENT.light.selectedFg (iris.iris11) */
47
+
48
+ /* --- Sizing --- */
49
+ --du-radius: 10px;
50
+ --du-radius-sm: 6px;
51
+ --du-radius-lg: 14px;
52
+ --du-button-size: 36px;
53
+ --du-button-size-sm: 32px;
54
+ --du-icon-size: 18px;
55
+ --du-gap: 8px;
56
+ --du-gap-sm: 4px;
57
+ --du-pad: 8px;
58
+ --du-pad-sm: 4px;
59
+ --du-hover-overlay: rgba(0, 0, 0, 0.05);
60
+ --du-bar-inset: 12px;
61
+ --du-z-canvas: 1;
62
+ --du-z-ui: 100;
63
+ --du-z-modal: 1000;
64
+ }
65
+
66
+ /**
67
+ * Theme resolution priority (highest wins):
68
+ * [data-theme="light"] — host forced light.
69
+ * [data-theme="dark"] — host forced dark.
70
+ * @media prefers-color-scheme: dark → :root — OS-derived dark when
71
+ * no data-theme is present.
72
+ * :root — light defaults.
73
+ *
74
+ * Hosts set `data-theme` via the Diagram component's `theme` prop
75
+ * ("light" / "dark" / "system" — "system" omits the attribute so
76
+ * the media query takes over).
77
+ */
78
+ @media (prefers-color-scheme: dark) {
79
+ :root {
80
+ /* All values mirror UI_SURFACE.dark + UI_ACCENT.dark from
81
+ `@oh-just-another/tokens`. */
82
+ --du-canvas-bg: #121212; /* UI_SURFACE.dark.canvas */
83
+ --du-ui-bg: rgba(35, 35, 35, 0.95); /* UI_SURFACE.dark.bg */
84
+ --du-ui-bg-solid: #252525; /* UI_SURFACE.dark.bgSolid */
85
+ --du-ui-border: rgba(255, 255, 255, 0.08); /* UI_SURFACE.dark.border */
86
+ --du-ui-shadow: 0 1px 4px rgba(0, 0, 0, 0.4), 0 4px 12px rgba(0, 0, 0, 0.3);
87
+ --du-text: #e8e8e8; /* UI_SURFACE.dark.text */
88
+ --du-text-muted: #9a9a9a; /* UI_SURFACE.dark.textMuted */
89
+ --du-accent: #5b5bd6; /* UI_ACCENT.dark.accent (irisDark.iris9) */
90
+ --du-accent-hover: #6e6ade; /* UI_ACCENT.dark.accentHover (irisDark.iris10) */
91
+ --du-hover-overlay: rgba(255, 255, 255, 0.06); /* UI_SURFACE.dark.hoverOverlay */
92
+ --du-selected-bg: #262a65; /* UI_ACCENT.dark.selectedBg (irisDark.iris4) */
93
+ --du-selected-fg: #b1a9ff; /* UI_ACCENT.dark.selectedFg (irisDark.iris11) */
94
+ }
95
+ }
96
+
97
+ [data-theme="light"] {
98
+ /* Same as :root — explicit override so a host that toggles
99
+ `data-theme` between "light" / "dark" / unset still picks
100
+ up the light palette under all conditions. Mirrors
101
+ UI_SURFACE.light + UI_ACCENT.light. */
102
+ --du-canvas-bg: #f5f5f5;
103
+ --du-ui-bg: rgba(255, 255, 255, 0.95);
104
+ --du-ui-bg-solid: #ffffff;
105
+ --du-ui-border: rgba(0, 0, 0, 0.08);
106
+ --du-ui-shadow: 0 1px 4px rgba(0, 0, 0, 0.08), 0 4px 12px rgba(0, 0, 0, 0.04);
107
+ --du-text: #1a1a1a;
108
+ --du-text-muted: #6b6b6b;
109
+ --du-accent: #5b5bd6; /* iris.iris9 */
110
+ --du-accent-hover: #5151cd; /* iris.iris10 */
111
+ --du-hover-overlay: rgba(0, 0, 0, 0.05);
112
+ --du-selected-bg: #e6e7ff; /* iris.iris4 */
113
+ --du-selected-fg: #5753c6; /* iris.iris11 */
114
+ }
115
+
116
+ /* ------------------------------------------------------------------ */
117
+ /* Legacy variable aliases — kept so the older components (MainMenu, */
118
+ /* ContextMenu, Modal, HelpDialog) that read `var(--panel)` / */
119
+ /* `var(--text)` / `var(--button-bg)` etc. still pick up the active */
120
+ /* theme without being rewritten to the `--du-*` namespace. */
121
+ /* */
122
+ /* Hosts can still override the legacy names individually — these */
123
+ /* are defaults that *forward* to the unified theme variables. */
124
+ /* ------------------------------------------------------------------ */
125
+ :root {
126
+ --bg: var(--du-canvas-bg);
127
+ --panel: var(--du-ui-bg-solid);
128
+ --surface: var(--du-ui-bg-solid);
129
+ --text: var(--du-text);
130
+ --muted: var(--du-text-muted);
131
+ --border: var(--du-ui-border);
132
+ --button-bg: var(--du-ui-bg-solid);
133
+ --button-hover-bg: var(--du-ui-bg);
134
+ --accent: var(--du-accent);
135
+ --menu-bg: var(--du-ui-bg-solid);
136
+ --menu-text: var(--du-text);
137
+ --menu-border: var(--du-ui-border);
138
+ --menu-divider: var(--du-ui-border);
139
+ }
140
+
141
+ [data-theme="dark"] {
142
+ /* Mirrors UI_SURFACE.dark + UI_ACCENT.dark from `@oh-just-another/tokens`. */
143
+ --du-canvas-bg: #121212;
144
+ --du-ui-bg: rgba(35, 35, 35, 0.95);
145
+ --du-ui-bg-solid: #252525;
146
+ --du-ui-border: rgba(255, 255, 255, 0.08);
147
+ --du-ui-shadow: 0 1px 4px rgba(0, 0, 0, 0.4), 0 4px 12px rgba(0, 0, 0, 0.3);
148
+ --du-text: #e8e8e8;
149
+ --du-text-muted: #9a9a9a;
150
+ --du-accent: #5b5bd6; /* irisDark.iris9 */
151
+ --du-accent-hover: #6e6ade; /* irisDark.iris10 */
152
+ --du-hover-overlay: rgba(255, 255, 255, 0.06);
153
+ --du-selected-bg: #262a65; /* irisDark.iris4 */
154
+ --du-selected-fg: #b1a9ff; /* irisDark.iris11 */
155
+ }
156
+
157
+ /* ------------------------------------------------------------------ */
158
+ /* UI layer — absolute overlay above the canvas surface. */
159
+ /* pointer-events: none on the wrapper, auto on children, so the */
160
+ /* canvas keeps receiving pointer events anywhere the UI isn't drawn. */
161
+ /* ------------------------------------------------------------------ */
162
+ .du-ui-layer {
163
+ position: absolute;
164
+ inset: 0;
165
+ pointer-events: none;
166
+ z-index: var(--du-z-ui);
167
+ }
168
+
169
+ .du-ui-layer > * {
170
+ pointer-events: auto;
171
+ }
172
+
173
+ /* ------------------------------------------------------------------ */
174
+ /* Bars — top / bottom 3-zone layout (left / center / right). */
175
+ /* ------------------------------------------------------------------ */
176
+ .du-bar {
177
+ position: absolute;
178
+ left: var(--du-bar-inset);
179
+ right: var(--du-bar-inset);
180
+ display: flex;
181
+ justify-content: space-between;
182
+ align-items: center;
183
+ gap: var(--du-gap);
184
+ pointer-events: none;
185
+ }
186
+
187
+ .du-bar > * {
188
+ pointer-events: auto;
189
+ }
190
+
191
+ .du-bar-top {
192
+ top: var(--du-bar-inset);
193
+ }
194
+
195
+ .du-bar-bottom {
196
+ bottom: var(--du-bar-inset);
197
+ }
198
+
199
+ .du-bar-zone {
200
+ display: inline-flex;
201
+ align-items: center;
202
+ gap: var(--du-gap);
203
+ }
204
+
205
+ .du-bar-zone-center {
206
+ margin: 0 auto;
207
+ }
208
+
209
+ /* ------------------------------------------------------------------ */
210
+ /* Icon button — square clickable. Two visual modes: */
211
+ /* 1. Standalone (outside `du-button-group`): full pill with */
212
+ /* surface, border, shadow. */
213
+ /* 2. Inside `du-button-group`: transparent surface, subtle radius, */
214
+ /* lets the group's pill chrome carry the framing. */
215
+ /* ------------------------------------------------------------------ */
216
+ .du-icon-button {
217
+ width: var(--du-button-size);
218
+ height: var(--du-button-size);
219
+ display: inline-flex;
220
+ align-items: center;
221
+ justify-content: center;
222
+ background: var(--du-ui-bg);
223
+ color: var(--du-text);
224
+ border: 1px solid var(--du-ui-border);
225
+ border-radius: var(--du-radius);
226
+ cursor: pointer;
227
+ font-size: 14px;
228
+ line-height: 1;
229
+ box-shadow: var(--du-ui-shadow);
230
+ transition: background 0.12s ease, color 0.12s ease, transform 0.05s ease;
231
+ padding: 0;
232
+ outline: none;
233
+ user-select: none;
234
+ }
235
+
236
+ .du-icon-button:hover:not(:disabled) {
237
+ background: var(--du-ui-bg-solid);
238
+ }
239
+
240
+ .du-icon-button:active:not(:disabled) {
241
+ transform: scale(0.96);
242
+ }
243
+
244
+ .du-icon-button:disabled {
245
+ opacity: 0.4;
246
+ cursor: not-allowed;
247
+ }
248
+
249
+ .du-icon-button:focus-visible {
250
+ outline: 2px solid var(--du-accent);
251
+ outline-offset: 2px;
252
+ }
253
+
254
+ .du-icon-button[aria-pressed="true"],
255
+ .du-icon-button.is-active {
256
+ background: var(--du-selected-bg);
257
+ color: var(--du-selected-fg);
258
+ border-color: var(--du-selected-bg);
259
+ }
260
+
261
+ .du-icon-button[aria-pressed="true"]:hover:not(:disabled),
262
+ .du-icon-button.is-active:hover:not(:disabled) {
263
+ /* Slight tint shift on hover — keeps the "selected" cue but
264
+ acknowledges the cursor. Same hue family, marginally darker. */
265
+ background: color-mix(in srgb, var(--du-selected-bg) 90%, var(--du-text) 10%);
266
+ }
267
+
268
+ .du-icon-button-sm {
269
+ width: var(--du-button-size-sm);
270
+ height: var(--du-button-size-sm);
271
+ font-size: 12px;
272
+ }
273
+
274
+ /**
275
+ * Flat modifier — strips the standalone pill chrome (background,
276
+ * border, shadow) and adopts the same look as an IconButton sitting
277
+ * inside a `du-button-group`. Use this when a button has to live
278
+ * inside a container that the descendant selector can't reach (e.g.
279
+ * the MainMenu trigger, which is wrapped in a positioning <div> so
280
+ * its dropdown can be `position: absolute`).
281
+ */
282
+ .du-icon-button-flat {
283
+ background: transparent;
284
+ border: none;
285
+ box-shadow: none;
286
+ border-radius: var(--du-radius-sm);
287
+ width: var(--du-button-size-sm);
288
+ height: var(--du-button-size-sm);
289
+ }
290
+
291
+ .du-icon-button-flat:hover:not(:disabled) {
292
+ background: var(--du-hover-overlay);
293
+ }
294
+
295
+ /* ------------------------------------------------------------------ */
296
+ /* Button group — pill that wraps one or more IconButtons. Group */
297
+ /* carries the surface, border, shadow; the buttons inside drop */
298
+ /* their own chrome so the row reads as one cohesive control. */
299
+ /* Padding + gap match standard's tool bar feel. */
300
+ /* ------------------------------------------------------------------ */
301
+ .du-button-group {
302
+ display: inline-flex;
303
+ align-items: center;
304
+ gap: var(--du-gap-sm);
305
+ background: var(--du-ui-bg);
306
+ border: 1px solid var(--du-ui-border);
307
+ border-radius: var(--du-radius);
308
+ box-shadow: var(--du-ui-shadow);
309
+ padding: var(--du-pad-sm);
310
+ /* No `overflow: hidden` — a child popup (MainMenu dropdown) must
311
+ be able to render outside the group's bounds. */
312
+ }
313
+
314
+ /* Vertical variant — the left creation dock. Stacks buttons in a
315
+ column; dividers (rendered inline by <Toolbar>) become horizontal. */
316
+ .du-button-group-vertical {
317
+ flex-direction: column;
318
+ align-items: stretch;
319
+ }
320
+
321
+ .du-button-group > .du-icon-button {
322
+ border: none;
323
+ box-shadow: none;
324
+ border-radius: var(--du-radius-sm);
325
+ background: transparent;
326
+ width: var(--du-button-size-sm);
327
+ height: var(--du-button-size-sm);
328
+ }
329
+
330
+ /* Vertical dock buttons are square + centred (icon-only tools). */
331
+ .du-button-group-vertical > .du-icon-button {
332
+ width: var(--du-button-size-sm) !important;
333
+ min-width: var(--du-button-size-sm) !important;
334
+ padding: 0 !important;
335
+ }
336
+
337
+ /* ------------------------------------------------------------------ */
338
+ /* Mobile / coarse-pointer chrome (keep the 640px breakpoint in sync */
339
+ /* with MOBILE_MAX_WIDTH_PX in constants.ts). */
340
+ /* ------------------------------------------------------------------ */
341
+ @media (pointer: coarse), (max-width: 640px) {
342
+ /* Bars clear the notch / home indicator + rounded corners. */
343
+ .du-bar-top {
344
+ top: calc(var(--du-bar-inset) + env(safe-area-inset-top, 0px));
345
+ }
346
+ .du-bar-bottom {
347
+ bottom: calc(var(--du-bar-inset) + env(safe-area-inset-bottom, 0px));
348
+ }
349
+ .du-bar {
350
+ left: calc(var(--du-bar-inset) + env(safe-area-inset-left, 0px));
351
+ right: calc(var(--du-bar-inset) + env(safe-area-inset-right, 0px));
352
+ }
353
+
354
+ /* Vertical creation dock: ≥44px touch targets; scroll inside the dock
355
+ when a short screen can't fit every tool. */
356
+ .du-button-group-vertical {
357
+ max-height: calc(
358
+ 100vh - 2 * var(--du-bar-inset) - env(safe-area-inset-top, 0px) -
359
+ env(safe-area-inset-bottom, 0px)
360
+ );
361
+ overflow-y: auto;
362
+ scrollbar-width: none;
363
+ }
364
+ .du-button-group-vertical::-webkit-scrollbar {
365
+ display: none;
366
+ }
367
+ .du-button-group-vertical > .du-icon-button {
368
+ width: 44px !important;
369
+ min-width: 44px !important;
370
+ height: 44px !important;
371
+ }
372
+ }
373
+
374
+ .du-button-group > .du-icon-button:hover:not(:disabled) {
375
+ background: var(--du-hover-overlay);
376
+ }
377
+
378
+ .du-button-group > .du-icon-button[aria-pressed="true"],
379
+ .du-button-group > .du-icon-button.is-active {
380
+ background: var(--du-selected-bg);
381
+ color: var(--du-selected-fg);
382
+ }
383
+
384
+ .du-button-group > .du-icon-button[aria-pressed="true"]:hover:not(:disabled),
385
+ .du-button-group > .du-icon-button.is-active:hover:not(:disabled) {
386
+ background: color-mix(in srgb, var(--du-selected-bg) 90%, var(--du-text) 10%);
387
+ }
388
+
389
+ /* ------------------------------------------------------------------ */
390
+ /* Floating side panel — used by Library / SelectedShapeActions. */
391
+ /* Fixed-width, fixed-height-with-scroll, sits flush against the */
392
+ /* corresponding side of the canvas with a `du-bar-inset` margin. */
393
+ /* ------------------------------------------------------------------ */
394
+ .du-side-panel {
395
+ position: absolute;
396
+ top: calc(var(--du-bar-inset) + var(--du-button-size) + var(--du-gap));
397
+ bottom: calc(var(--du-bar-inset) + var(--du-button-size) + var(--du-gap));
398
+ width: 240px;
399
+ background: var(--du-ui-bg);
400
+ color: var(--du-text);
401
+ border: 1px solid var(--du-ui-border);
402
+ border-radius: var(--du-radius);
403
+ box-shadow: var(--du-ui-shadow);
404
+ display: flex;
405
+ flex-direction: column;
406
+ overflow: hidden;
407
+ }
408
+
409
+ .du-side-panel-left {
410
+ left: var(--du-bar-inset);
411
+ }
412
+
413
+ .du-side-panel-right {
414
+ right: var(--du-bar-inset);
415
+ }
416
+
417
+ /* Sheet mode — fills the parent BottomSheet; the sheet provides the
418
+ surface, so drop the floating-pill position / border / shadow. */
419
+ .du-side-panel.du-side-panel-sheet {
420
+ position: static;
421
+ top: auto;
422
+ bottom: auto;
423
+ left: auto;
424
+ right: auto;
425
+ width: 100%;
426
+ height: 100%;
427
+ border: none;
428
+ border-radius: 0;
429
+ box-shadow: none;
430
+ background: transparent;
431
+ }
432
+
433
+ .du-side-panel-header {
434
+ display: flex;
435
+ align-items: center;
436
+ justify-content: flex-end;
437
+ gap: var(--du-gap-sm);
438
+ padding: var(--du-pad-sm) var(--du-pad-sm) var(--du-pad-sm) var(--du-pad);
439
+ font-size: 12px;
440
+ font-weight: 600;
441
+ text-transform: uppercase;
442
+ letter-spacing: 0.5px;
443
+ color: var(--du-text-muted);
444
+ }
445
+
446
+ /* Hide the header when it's empty (no action buttons configured). */
447
+ .du-side-panel-header:empty {
448
+ display: none;
449
+ }
450
+
451
+ /* Auto-height variant — content hugs its natural height instead
452
+ of stretching from top to bottom of the canvas area. Used by
453
+ the Properties panel so it shrinks to whatever the inspector
454
+ sections need. The Library panel keeps the default full-height
455
+ so its long grid can scroll. */
456
+ .du-side-panel-auto-height {
457
+ bottom: auto;
458
+ max-height: calc(
459
+ 100% - 2 * var(--du-bar-inset) - 2 * var(--du-button-size) - 2 * var(--du-gap)
460
+ );
461
+ }
462
+
463
+ .du-side-panel-body {
464
+ flex: 1;
465
+ overflow-y: auto;
466
+ padding: var(--du-pad);
467
+ }
468
+
469
+ /* Variant for hosts that lay out their own grid / list inside the
470
+ panel body (e.g. <Palette>) and would otherwise have to subtract
471
+ the body's padding from their own paddings. Keeps overflow + flex
472
+ behaviour. */
473
+ .du-side-panel-body-flush {
474
+ padding: 0;
475
+ }
476
+
477
+ /* ------------------------------------------------------------------ */
478
+ /* Palette (template library content) — sectioned grid of icon tiles. */
479
+ /* No per-tile borders, no per-section dividers — visual structure */
480
+ /* comes from spacing alone so the panel reads as one calm surface. */
481
+ /* ------------------------------------------------------------------ */
482
+ .du-palette {
483
+ display: flex;
484
+ flex-direction: column;
485
+ min-height: 0;
486
+ color: var(--du-text);
487
+ width: 100%;
488
+ }
489
+
490
+ .du-palette-body {
491
+ flex: 1 1 auto;
492
+ overflow-y: auto;
493
+ display: flex;
494
+ flex-direction: column;
495
+ }
496
+
497
+ .du-palette-section {
498
+ padding: 0 var(--du-pad) var(--du-pad-sm);
499
+ }
500
+
501
+ .du-palette-category {
502
+ width: 100%;
503
+ background: transparent;
504
+ color: var(--du-text-muted);
505
+ border: none;
506
+ padding: 10px 0 6px;
507
+ font: inherit;
508
+ font-size: 11px;
509
+ font-weight: 600;
510
+ text-transform: uppercase;
511
+ letter-spacing: 0.5px;
512
+ text-align: left;
513
+ cursor: pointer;
514
+ display: flex;
515
+ align-items: center;
516
+ justify-content: space-between;
517
+ gap: var(--du-gap-sm);
518
+ }
519
+
520
+ .du-palette-category-caret {
521
+ display: inline-flex;
522
+ align-items: center;
523
+ opacity: 0.6;
524
+ }
525
+
526
+ .du-palette-grid {
527
+ display: grid;
528
+ grid-template-columns: repeat(2, 1fr);
529
+ gap: var(--du-gap-sm);
530
+ align-content: start;
531
+ padding: 0 var(--du-pad) var(--du-pad-sm);
532
+ }
533
+
534
+ /* `.du-palette-section > .du-palette-grid` already gets padding via
535
+ .du-palette-section; remove the duplicate to avoid double margin. */
536
+ .du-palette-section > .du-palette-grid {
537
+ padding: 0;
538
+ }
539
+
540
+ /* Compact list layout (modern-style templates panel) — single column of
541
+ icon + name rows. Denser than the grid; the label reads left-aligned
542
+ beside a small icon. */
543
+ .du-palette-list {
544
+ display: flex;
545
+ flex-direction: column;
546
+ gap: 2px;
547
+ padding: 0 var(--du-pad) var(--du-pad-sm);
548
+ }
549
+ .du-palette-section > .du-palette-list {
550
+ padding: 0;
551
+ }
552
+ .du-palette-list .du-palette-item {
553
+ flex-direction: row;
554
+ align-items: center;
555
+ justify-content: flex-start;
556
+ aspect-ratio: auto;
557
+ gap: 8px;
558
+ padding: 5px 8px;
559
+ text-align: left;
560
+ font-size: 12px;
561
+ line-height: 1.2;
562
+ }
563
+ .du-palette-list .du-palette-item-icon {
564
+ width: 20px !important;
565
+ height: 20px !important;
566
+ flex: 0 0 auto;
567
+ }
568
+ .du-palette-list .du-palette-item-label {
569
+ flex: 1 1 auto;
570
+ white-space: nowrap;
571
+ overflow: hidden;
572
+ text-overflow: ellipsis;
573
+ }
574
+
575
+ .du-palette-empty {
576
+ padding: 24px 12px;
577
+ text-align: center;
578
+ font-size: 12px;
579
+ color: var(--du-text-muted);
580
+ }
581
+
582
+ .du-palette-item {
583
+ display: flex;
584
+ flex-direction: column;
585
+ align-items: center;
586
+ justify-content: center;
587
+ gap: 4px;
588
+ aspect-ratio: 1;
589
+ padding: 6px 4px;
590
+ background: transparent;
591
+ color: var(--du-text);
592
+ border: none;
593
+ border-radius: var(--du-radius-sm);
594
+ cursor: grab;
595
+ text-align: center;
596
+ font-size: 10px;
597
+ line-height: 1.1;
598
+ transition: background 0.12s ease;
599
+ }
600
+
601
+ .du-palette-item:hover {
602
+ background: var(--du-hover-overlay);
603
+ }
604
+
605
+ .du-palette-item:active {
606
+ cursor: grabbing;
607
+ }
608
+
609
+ .du-palette-item-icon {
610
+ display: inline-flex;
611
+ align-items: center;
612
+ justify-content: center;
613
+ color: var(--du-text);
614
+ }
615
+
616
+ .du-palette-item-icon svg {
617
+ width: 100%;
618
+ height: 100%;
619
+ display: block;
620
+ }
621
+
622
+ .du-palette-item-label {
623
+ /* Two-line clamp so long names don't push the tile out of square. */
624
+ display: -webkit-box;
625
+ -webkit-line-clamp: 2;
626
+ -webkit-box-orient: vertical;
627
+ overflow: hidden;
628
+ text-overflow: ellipsis;
629
+ }
630
+
631
+ /* ------------------------------------------------------------------ */
632
+ /* Property panel — modern-style sectioned inspector. */
633
+ /* Sections are stacked top-to-bottom; each one has a tiny uppercase */
634
+ /* label, then a row of controls (color swatches, segmented buttons, */
635
+ /* slider). No card chrome — visual structure comes from spacing. */
636
+ /* ------------------------------------------------------------------ */
637
+ .du-prop-panel {
638
+ display: flex;
639
+ flex-direction: column;
640
+ gap: 16px;
641
+ padding: 4px 0;
642
+ color: var(--du-text);
643
+ font-size: 12px;
644
+ }
645
+
646
+ .du-prop-section {
647
+ display: flex;
648
+ flex-direction: column;
649
+ gap: 6px;
650
+ }
651
+
652
+ .du-prop-section-label {
653
+ margin: 0;
654
+ font-size: 10px;
655
+ font-weight: 600;
656
+ text-transform: uppercase;
657
+ letter-spacing: 0.5px;
658
+ color: var(--du-text-muted);
659
+ }
660
+
661
+ .du-prop-row {
662
+ display: flex;
663
+ align-items: center;
664
+ gap: var(--du-gap-sm);
665
+ flex-wrap: wrap;
666
+ }
667
+
668
+ .du-prop-row-label {
669
+ font-size: 11px;
670
+ color: var(--du-text-muted);
671
+ min-width: 48px;
672
+ }
673
+
674
+ .du-prop-empty {
675
+ font-style: italic;
676
+ color: var(--du-text-muted);
677
+ font-size: 12px;
678
+ }
679
+
680
+ /* modern-style flat segmented control inside the panel:
681
+ no card chrome on the ButtonGroup wrapper. The buttons still
682
+ read as a row through their tonal `is-active` fill + hover. */
683
+ .du-prop-panel .du-button-group {
684
+ background: transparent;
685
+ border: none;
686
+ box-shadow: none;
687
+ padding: 0;
688
+ gap: 2px;
689
+ }
690
+
691
+ /* Tighter active state inside the panel (smaller buttons, same
692
+ tonal pair). Keeps the rest of the toolbar unaffected. */
693
+ .du-prop-panel .du-button-group > .du-icon-button {
694
+ width: var(--du-button-size-sm);
695
+ height: var(--du-button-size-sm);
696
+ }
697
+
698
+ /* Checkbox row — small native input + label. Used by Corners
699
+ "Auto radius" toggle. */
700
+ .du-prop-checkbox {
701
+ display: inline-flex;
702
+ align-items: center;
703
+ gap: 6px;
704
+ cursor: pointer;
705
+ font-size: 12px;
706
+ color: var(--du-text);
707
+ user-select: none;
708
+ }
709
+
710
+ .du-prop-checkbox input[type="checkbox"] {
711
+ width: 14px;
712
+ height: 14px;
713
+ margin: 0;
714
+ accent-color: var(--du-selected-fg);
715
+ cursor: pointer;
716
+ }
717
+
718
+ /* ------------------------------------------------------------------ */
719
+ /* Colour swatch grid — modern-style fixed 26 × 26 cell with a */
720
+ /* 24 × 24 fill that grows on hover to fill the cell. Selected cells */
721
+ /* get a 2 px tonal ring on the OUTER cell so the ring lands beside */
722
+ /* the colour, not over it. */
723
+ /* ------------------------------------------------------------------ */
724
+ .du-swatch-grid {
725
+ display: inline-flex;
726
+ flex-wrap: wrap;
727
+ gap: 4px;
728
+ max-width: 232px; /* 8 cells (26 × 8 = 208) + 7 gaps × 4 = ~232 */
729
+ }
730
+
731
+ .du-swatch {
732
+ position: relative;
733
+ width: 26px;
734
+ height: 26px;
735
+ padding: 0;
736
+ background: transparent;
737
+ border: none;
738
+ border-radius: var(--du-radius-sm);
739
+ cursor: pointer;
740
+ display: inline-flex;
741
+ align-items: center;
742
+ justify-content: center;
743
+ /* Reserve the ring space so width never changes on selection. */
744
+ box-shadow: inset 0 0 0 0 transparent;
745
+ transition: box-shadow 0.12s ease;
746
+ /* Suppress the UA tap-highlight on iOS so the cell looks clean
747
+ while the user drags through swatches. */
748
+ -webkit-tap-highlight-color: transparent;
749
+ }
750
+
751
+ .du-swatch:focus-visible {
752
+ outline: none;
753
+ box-shadow: inset 0 0 0 2px var(--du-accent);
754
+ }
755
+
756
+ .du-swatch[aria-checked="true"] {
757
+ box-shadow: inset 0 0 0 2px var(--du-selected-fg);
758
+ }
759
+
760
+ .du-swatch-fill {
761
+ display: inline-flex;
762
+ align-items: center;
763
+ justify-content: center;
764
+ width: 24px;
765
+ height: 24px;
766
+ border-radius: var(--du-radius-sm);
767
+ border: 1px solid var(--du-ui-border);
768
+ box-sizing: border-box;
769
+ transition: width 0.12s ease, height 0.12s ease, border-color 0.12s ease;
770
+ }
771
+
772
+ .du-swatch:hover .du-swatch-fill,
773
+ .du-swatch:focus-visible .du-swatch-fill {
774
+ width: 26px;
775
+ height: 26px;
776
+ }
777
+
778
+ /* Clear (×) + custom-picker (+) cells use the same chrome as a
779
+ regular swatch but with a neutral fill + glyph slot. */
780
+ .du-swatch-fill-blank {
781
+ background: var(--du-ui-bg-solid);
782
+ color: var(--du-text-muted);
783
+ }
784
+
785
+ .du-swatch-custom .du-swatch-fill {
786
+ background: var(--du-ui-bg-solid);
787
+ color: var(--du-text-muted);
788
+ }
789
+
790
+ .du-swatch-color-input {
791
+ position: absolute;
792
+ inset: 0;
793
+ opacity: 0;
794
+ cursor: pointer;
795
+ border: none;
796
+ padding: 0;
797
+ }
798
+
799
+ /* ------------------------------------------------------------------ */
800
+ /* Slider — themed native <input type=range> + value badge. */
801
+ /* ------------------------------------------------------------------ */
802
+ .du-slider {
803
+ display: flex;
804
+ align-items: center;
805
+ gap: 8px;
806
+ width: 100%;
807
+ }
808
+
809
+ .du-slider-track {
810
+ flex: 1;
811
+ -webkit-appearance: none;
812
+ appearance: none;
813
+ background: transparent;
814
+ height: 18px;
815
+ margin: 0;
816
+ padding: 0;
817
+ cursor: pointer;
818
+ }
819
+
820
+ .du-slider-track::-webkit-slider-runnable-track {
821
+ height: 4px;
822
+ background: var(--du-selected-bg);
823
+ border-radius: 2px;
824
+ }
825
+
826
+ .du-slider-track::-moz-range-track {
827
+ height: 4px;
828
+ background: var(--du-selected-bg);
829
+ border-radius: 2px;
830
+ border: none;
831
+ }
832
+
833
+ .du-slider-track::-webkit-slider-thumb {
834
+ -webkit-appearance: none;
835
+ appearance: none;
836
+ width: 14px;
837
+ height: 14px;
838
+ background: var(--du-selected-fg);
839
+ border-radius: 50%;
840
+ border: 2px solid var(--du-ui-bg-solid);
841
+ margin-top: -5px;
842
+ cursor: grab;
843
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
844
+ }
845
+
846
+ .du-slider-track::-moz-range-thumb {
847
+ width: 14px;
848
+ height: 14px;
849
+ background: var(--du-selected-fg);
850
+ border-radius: 50%;
851
+ border: 2px solid var(--du-ui-bg-solid);
852
+ cursor: grab;
853
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
854
+ }
855
+
856
+ .du-slider-track:focus {
857
+ outline: none;
858
+ }
859
+
860
+ .du-slider-track:focus-visible::-webkit-slider-thumb {
861
+ outline: 2px solid var(--du-accent);
862
+ outline-offset: 2px;
863
+ }
864
+
865
+ .du-slider-value {
866
+ min-width: 36px;
867
+ text-align: right;
868
+ font-variant-numeric: tabular-nums;
869
+ font-size: 11px;
870
+ color: var(--du-text-muted);
871
+ }
872
+
873
+ /* ------------------------------------------------------------------ */
874
+ /* Number input — small themed `<input type=number>`. */
875
+ /* ------------------------------------------------------------------ */
876
+ .du-number-input {
877
+ width: 64px;
878
+ height: 24px;
879
+ box-sizing: border-box;
880
+ background: var(--du-ui-bg-solid);
881
+ color: var(--du-text);
882
+ border: 1px solid var(--du-ui-border);
883
+ border-radius: var(--du-radius-sm);
884
+ padding: 0 6px;
885
+ font: inherit;
886
+ font-size: 11px;
887
+ font-variant-numeric: tabular-nums;
888
+ outline: none;
889
+ }
890
+
891
+ .du-number-input:focus {
892
+ border-color: var(--du-accent);
893
+ }
894
+
895
+ /* Search input row — sits between the header and the body. */
896
+ .du-side-panel-search {
897
+ position: relative;
898
+ padding: var(--du-pad-sm) var(--du-pad);
899
+ border-bottom: 1px solid var(--du-ui-border);
900
+ }
901
+
902
+ .du-side-panel-search input[type="search"] {
903
+ width: 100%;
904
+ height: 28px;
905
+ box-sizing: border-box;
906
+ background: var(--du-ui-bg-solid);
907
+ color: var(--du-text);
908
+ border: 1px solid var(--du-ui-border);
909
+ border-radius: var(--du-radius-sm);
910
+ padding: 0 8px 0 26px;
911
+ font: inherit;
912
+ font-size: 12px;
913
+ outline: none;
914
+ }
915
+
916
+ .du-side-panel-search input[type="search"]::placeholder {
917
+ color: var(--du-text-muted);
918
+ }
919
+
920
+ .du-side-panel-search input[type="search"]:focus {
921
+ border-color: var(--du-accent);
922
+ }
923
+
924
+ .du-side-panel-search-icon {
925
+ position: absolute;
926
+ left: calc(var(--du-pad) + 8px);
927
+ top: 50%;
928
+ transform: translateY(-50%);
929
+ color: var(--du-text-muted);
930
+ pointer-events: none;
931
+ }
932
+
933
+ /* ------------------------------------------------------------------ */
934
+ /* Toast for reset-to-content visibility hint. */
935
+ /* ------------------------------------------------------------------ */
936
+ .du-pill-button {
937
+ display: inline-flex;
938
+ align-items: center;
939
+ gap: var(--du-gap-sm);
940
+ padding: 0 12px;
941
+ height: var(--du-button-size);
942
+ background: var(--du-ui-bg);
943
+ color: var(--du-text);
944
+ border: 1px solid var(--du-ui-border);
945
+ border-radius: 999px;
946
+ box-shadow: var(--du-ui-shadow);
947
+ cursor: pointer;
948
+ font-size: 13px;
949
+ line-height: 1;
950
+ white-space: nowrap;
951
+ transition: background 0.15s ease, transform 0.05s ease;
952
+ }
953
+
954
+ .du-pill-button:hover {
955
+ background: var(--du-ui-bg-solid);
956
+ }
957
+
958
+ .du-pill-button:active {
959
+ transform: scale(0.96);
960
+ }
961
+
962
+ /* ------------------------------------------------------------------ */
963
+ /* Tooltip — singleton popup rendered via portal by <TooltipProvider>.*/
964
+ /* Pointer-events-none so it never blocks hover on neighbour buttons. */
965
+ /* ------------------------------------------------------------------ */
966
+ .du-tooltip {
967
+ background: var(--du-text);
968
+ color: var(--du-ui-bg-solid);
969
+ padding: 4px 8px;
970
+ border-radius: var(--du-radius-sm);
971
+ font-size: 12px;
972
+ line-height: 1.4;
973
+ white-space: nowrap;
974
+ box-shadow: var(--du-ui-shadow);
975
+ font-family: inherit;
976
+ max-width: 260px;
977
+ }
978
+
979
+ /* ------------------------------------------------------------------ */
980
+ /* Help dialog — modern-style hotkey cheatsheet. */
981
+ /* Each section is an "island" with bordered rows; each key is its */
982
+ /* own pill `<kbd>` rendered with the same tonal selected fill as */
983
+ /* active toolbar buttons so the visual language stays consistent. */
984
+ /* ------------------------------------------------------------------ */
985
+ .du-help-islands {
986
+ display: grid;
987
+ gap: 24px;
988
+ grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
989
+ }
990
+
991
+ .du-help-island {
992
+ display: flex;
993
+ flex-direction: column;
994
+ gap: 8px;
995
+ }
996
+
997
+ .du-help-island-title {
998
+ margin: 0;
999
+ font-size: 13px;
1000
+ font-weight: 600;
1001
+ color: var(--du-text);
1002
+ letter-spacing: 0.2px;
1003
+ }
1004
+
1005
+ .du-help-island-rows {
1006
+ border: 1px solid var(--du-ui-border);
1007
+ border-radius: var(--du-radius);
1008
+ overflow: hidden;
1009
+ }
1010
+
1011
+ .du-help-row {
1012
+ display: flex;
1013
+ align-items: center;
1014
+ justify-content: space-between;
1015
+ gap: 12px;
1016
+ padding: 8px 12px;
1017
+ font-size: 13px;
1018
+ color: var(--du-text);
1019
+ border-bottom: 1px solid var(--du-ui-border);
1020
+ }
1021
+
1022
+ .du-help-row:last-child {
1023
+ border-bottom: none;
1024
+ }
1025
+
1026
+ .du-help-row-label {
1027
+ /* Label may wrap on narrow viewports; keys stay on a single line. */
1028
+ min-width: 0;
1029
+ }
1030
+
1031
+ .du-help-keys {
1032
+ display: inline-flex;
1033
+ align-items: center;
1034
+ gap: 4px;
1035
+ flex-shrink: 0;
1036
+ }
1037
+
1038
+ .du-help-keys-separator {
1039
+ font-size: 11px;
1040
+ color: var(--du-text-muted);
1041
+ padding: 0 2px;
1042
+ }
1043
+
1044
+ .du-help-key {
1045
+ display: inline-flex;
1046
+ align-items: center;
1047
+ justify-content: center;
1048
+ min-width: 22px;
1049
+ height: 22px;
1050
+ padding: 0 6px;
1051
+ background: var(--du-selected-bg);
1052
+ color: var(--du-selected-fg);
1053
+ border-radius: var(--du-radius-sm);
1054
+ font-family: inherit;
1055
+ font-size: 11px;
1056
+ font-weight: 600;
1057
+ line-height: 1;
1058
+ white-space: nowrap;
1059
+ }
1060
+
1061
+ .du-help-platform-badge {
1062
+ font-size: 11px;
1063
+ color: var(--du-text-muted);
1064
+ text-transform: uppercase;
1065
+ letter-spacing: 0.5px;
1066
+ }
1067
+
1068
+ /* ------------------------------------------------------------------ */
1069
+ /* Selection floating panel — modern-style horizontal toolbar that */
1070
+ /* hovers above the selection bbox. Position is set inline via */
1071
+ /* `transform:translate(...)` by floating-ui — these rules cover only */
1072
+ /* the visual chrome (pill background, shadow, animation, layout of */
1073
+ /* inline controls + their popover triggers). */
1074
+ /* ------------------------------------------------------------------ */
1075
+ /* Fade-in is handled inline via the `positioned` state in
1076
+ <SelectionFloatingPanel> — a CSS animation here would override the
1077
+ inline `transform: translate(...)` floating-ui writes, causing
1078
+ the panel to flash at the (0, 0) origin before snapping into
1079
+ place. */
1080
+
1081
+ .du-sel-panel {
1082
+ display: inline-flex;
1083
+ align-items: center;
1084
+ gap: var(--du-gap-sm);
1085
+ background: var(--du-ui-bg-solid);
1086
+ border: 1px solid var(--du-ui-border);
1087
+ border-radius: var(--du-radius);
1088
+ box-shadow: var(--du-ui-shadow);
1089
+ padding: var(--du-pad-sm);
1090
+ font-size: 12px;
1091
+ color: var(--du-text);
1092
+ max-width: calc(100vw - 32px);
1093
+ /* Allow horizontal scroll within the pill when too many controls
1094
+ are visible; `shift` middleware clamps the outer position but
1095
+ a panel wider than viewport still needs internal overflow. */
1096
+ overflow-x: auto;
1097
+ overflow-y: visible;
1098
+ scrollbar-width: thin;
1099
+ }
1100
+
1101
+ /* ---- Mobile bottom-sheet property panel (modern-style) ---- */
1102
+ /* Fixed dock at the bottom edge; transparent container so only the
1103
+ panel itself catches touches. Honours the home-indicator safe area. */
1104
+ .du-sel-panel-dock {
1105
+ position: fixed;
1106
+ left: 0;
1107
+ right: 0;
1108
+ /* Sit ABOVE the bottom bar (zoom / help) + the home-indicator safe area
1109
+ so the two don't overlap. Mirrors the side-panel top offset. */
1110
+ bottom: calc(
1111
+ var(--du-bar-inset) + var(--du-button-size) + var(--du-gap) +
1112
+ env(safe-area-inset-bottom, 0px)
1113
+ );
1114
+ z-index: 1500;
1115
+ display: flex;
1116
+ justify-content: center;
1117
+ padding: 0 8px;
1118
+ pointer-events: none;
1119
+ }
1120
+
1121
+ /* Full-width sheet: a primary row + (when ⋮ is tapped) a wrapped
1122
+ overflow grid below. Overrides the floating pill's row/scroll chrome. */
1123
+ .du-sel-panel-mobile {
1124
+ pointer-events: auto;
1125
+ flex-direction: column;
1126
+ align-items: stretch;
1127
+ width: 100%;
1128
+ max-width: 560px;
1129
+ overflow: visible;
1130
+ gap: 6px;
1131
+ padding: 8px;
1132
+ }
1133
+ .du-sel-mobile-row {
1134
+ display: flex;
1135
+ align-items: center;
1136
+ gap: var(--du-gap-sm);
1137
+ width: 100%;
1138
+ }
1139
+ /* Primary controls scroll horizontally if they don't fit; the ⋮ button
1140
+ stays pinned to the right, always reachable. */
1141
+ .du-sel-mobile-primary {
1142
+ display: flex;
1143
+ align-items: center;
1144
+ gap: var(--du-gap-sm);
1145
+ flex: 1 1 auto;
1146
+ min-width: 0;
1147
+ overflow-x: auto;
1148
+ scrollbar-width: none;
1149
+ }
1150
+ .du-sel-mobile-primary::-webkit-scrollbar {
1151
+ display: none;
1152
+ }
1153
+ .du-sel-mobile-expand {
1154
+ flex: 0 0 auto;
1155
+ }
1156
+ .du-sel-mobile-overflow {
1157
+ display: flex;
1158
+ flex-wrap: wrap;
1159
+ align-items: center;
1160
+ gap: var(--du-gap-sm);
1161
+ border-top: 1px solid var(--du-ui-border);
1162
+ padding-top: 8px;
1163
+ }
1164
+ /* ≥44px touch targets on the mobile panel (Apple HIG). Standalone icon
1165
+ buttons + color triggers grow; grouped segmented buttons stay a touch
1166
+ smaller so wide controls (font-size S/M/L/XL) still fit one row. */
1167
+ .du-sel-panel-mobile .du-sel-icon-button,
1168
+ .du-sel-panel-mobile .du-sel-color-trigger {
1169
+ width: 44px;
1170
+ height: 44px;
1171
+ }
1172
+ .du-sel-panel-mobile .du-button-group > .du-icon-button {
1173
+ width: 38px;
1174
+ height: 38px;
1175
+ }
1176
+
1177
+ /* Flat segmented controls inside the floating panel — no group
1178
+ pill chrome (the outer panel already provides it). Reuses the
1179
+ button-group rules from PropertyPanel but with tighter gaps. */
1180
+ .du-sel-panel .du-button-group {
1181
+ background: transparent;
1182
+ border: none;
1183
+ box-shadow: none;
1184
+ padding: 0;
1185
+ gap: 2px;
1186
+ }
1187
+
1188
+ .du-sel-panel .du-button-group > .du-icon-button {
1189
+ width: var(--du-button-size-sm);
1190
+ height: var(--du-button-size-sm);
1191
+ }
1192
+
1193
+ /* Color swatch trigger — 28×28 with 20×20 colored square inside. */
1194
+ .du-sel-color-trigger {
1195
+ display: inline-flex;
1196
+ align-items: center;
1197
+ justify-content: center;
1198
+ width: var(--du-button-size-sm);
1199
+ height: var(--du-button-size-sm);
1200
+ padding: 0;
1201
+ background: transparent;
1202
+ border: none;
1203
+ border-radius: var(--du-radius-sm);
1204
+ cursor: pointer;
1205
+ -webkit-tap-highlight-color: transparent;
1206
+ }
1207
+
1208
+ .du-sel-color-trigger:hover:not(:disabled) {
1209
+ background: var(--du-hover-overlay);
1210
+ }
1211
+
1212
+ .du-sel-color-trigger:focus-visible {
1213
+ outline: 2px solid var(--du-accent);
1214
+ outline-offset: 1px;
1215
+ }
1216
+
1217
+ .du-sel-color-swatch {
1218
+ display: inline-block;
1219
+ width: 18px;
1220
+ height: 18px;
1221
+ border-radius: 4px;
1222
+ border: 1px solid var(--du-ui-border);
1223
+ box-sizing: border-box;
1224
+ }
1225
+
1226
+ /* Plain icon button used for corners / etc. Same chrome as
1227
+ color-trigger but icon inside. */
1228
+ .du-sel-icon-button {
1229
+ display: inline-flex;
1230
+ align-items: center;
1231
+ justify-content: center;
1232
+ width: var(--du-button-size-sm);
1233
+ height: var(--du-button-size-sm);
1234
+ padding: 0;
1235
+ background: transparent;
1236
+ border: none;
1237
+ border-radius: var(--du-radius-sm);
1238
+ cursor: pointer;
1239
+ color: var(--du-text);
1240
+ }
1241
+
1242
+ .du-sel-icon-button:hover:not(:disabled) {
1243
+ background: var(--du-hover-overlay);
1244
+ }
1245
+
1246
+ /* Pressed / on state for toggle buttons (Bold / Italic / Underline /
1247
+ Strikethrough in the text style popover). Without this the computed
1248
+ `is-active` class had no visual, so toggles never reflected the
1249
+ selection's current style. */
1250
+ .du-sel-icon-button.is-active {
1251
+ background: var(--du-accent-soft, rgba(80, 130, 255, 0.22));
1252
+ color: var(--du-accent, #5082ff);
1253
+ }
1254
+
1255
+ .du-sel-icon-button:focus-visible {
1256
+ outline: 2px solid var(--du-accent);
1257
+ outline-offset: 1px;
1258
+ }
1259
+
1260
+ /* Text-label button (opacity badge "75%"). */
1261
+ .du-sel-text-button {
1262
+ display: inline-flex;
1263
+ align-items: center;
1264
+ justify-content: center;
1265
+ height: var(--du-button-size-sm);
1266
+ min-width: var(--du-button-size-sm);
1267
+ padding: 0 8px;
1268
+ background: transparent;
1269
+ border: none;
1270
+ border-radius: var(--du-radius-sm);
1271
+ cursor: pointer;
1272
+ color: var(--du-text);
1273
+ font-size: 12px;
1274
+ font-variant-numeric: tabular-nums;
1275
+ }
1276
+
1277
+ .du-sel-text-button:hover:not(:disabled) {
1278
+ background: var(--du-hover-overlay);
1279
+ }
1280
+
1281
+ .du-sel-text-button:focus-visible {
1282
+ outline: 2px solid var(--du-accent);
1283
+ outline-offset: 1px;
1284
+ }
1285
+
1286
+ /* Visual divider between inline groups (z-order, actions). */
1287
+ .du-sel-divider {
1288
+ display: inline-block;
1289
+ width: 1px;
1290
+ height: 18px;
1291
+ background: var(--du-ui-border);
1292
+ margin: 0 2px;
1293
+ }
1294
+
1295
+ /* ------------------------------------------------------------------ */
1296
+ /* Popover — generic floating panel used by color picker / corners / */
1297
+ /* opacity slider inside the selection floating panel. */
1298
+ /* ------------------------------------------------------------------ */
1299
+ .du-popover {
1300
+ background: var(--du-ui-bg-solid);
1301
+ border: 1px solid var(--du-ui-border);
1302
+ border-radius: var(--du-radius);
1303
+ box-shadow: var(--du-ui-shadow);
1304
+ padding: var(--du-pad);
1305
+ font-size: 12px;
1306
+ color: var(--du-text);
1307
+ min-width: 120px;
1308
+ max-width: 320px;
1309
+ }
1310
+
1311
+ .du-sel-popover-section {
1312
+ display: flex;
1313
+ flex-direction: column;
1314
+ gap: 8px;
1315
+ min-width: 0;
1316
+ }
1317
+
1318
+ .du-sel-popover-label {
1319
+ margin: 0;
1320
+ font-size: 10px;
1321
+ font-weight: 600;
1322
+ text-transform: uppercase;
1323
+ letter-spacing: 0.5px;
1324
+ color: var(--du-text-muted);
1325
+ }
1326
+
1327
+ /* Vertical list row inside a selection popover (e.g. font-family
1328
+ picker). One full-width clickable row; `is-active` marks the
1329
+ current value. */
1330
+ .du-sel-menu-row {
1331
+ all: unset;
1332
+ display: block;
1333
+ width: 100%;
1334
+ box-sizing: border-box;
1335
+ padding: 6px 10px;
1336
+ border-radius: 4px;
1337
+ font-size: 13px;
1338
+ color: var(--du-text, #ddd);
1339
+ cursor: pointer;
1340
+ }
1341
+
1342
+ .du-sel-menu-row:hover {
1343
+ background: var(--du-ui-hover, #2a2a2a);
1344
+ }
1345
+
1346
+ .du-sel-menu-row.is-active {
1347
+ background: var(--du-accent-soft, rgba(80, 130, 255, 0.2));
1348
+ }
1349
+
1350
+ /* URL field inside the element-link popover. */
1351
+ .du-sel-link-input {
1352
+ width: 220px;
1353
+ max-width: 60vw;
1354
+ box-sizing: border-box;
1355
+ padding: 6px 8px;
1356
+ font-size: 13px;
1357
+ color: var(--du-text, #ddd);
1358
+ background: var(--du-ui-bg, #1a1a1a);
1359
+ border: 1px solid var(--du-ui-border, #333);
1360
+ border-radius: 4px;
1361
+ outline: none;
1362
+ }
1363
+
1364
+ .du-sel-link-input:focus-visible {
1365
+ outline: 2px solid var(--du-accent, #5082ff);
1366
+ outline-offset: -1px;
1367
+ }
1368
+
1369
+ /* Hover link popup (chip above a linked element). */
1370
+ .du-link-popup {
1371
+ z-index: 1400;
1372
+ margin-top: -6px;
1373
+ pointer-events: auto;
1374
+ }
1375
+
1376
+ .du-link-popup-url {
1377
+ display: inline-flex;
1378
+ align-items: center;
1379
+ gap: 6px;
1380
+ max-width: 280px;
1381
+ padding: 4px 8px;
1382
+ font: 12px system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
1383
+ color: var(--du-accent, #5082ff);
1384
+ text-decoration: none;
1385
+ background: var(--menu-bg, #1a1a1a);
1386
+ border: 1px solid var(--menu-border, #333);
1387
+ border-radius: 6px;
1388
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
1389
+ cursor: pointer;
1390
+ }
1391
+
1392
+ .du-link-popup-url:hover {
1393
+ background: var(--du-ui-hover, #2a2a2a);
1394
+ }
1395
+
1396
+ .du-link-popup-text {
1397
+ overflow: hidden;
1398
+ text-overflow: ellipsis;
1399
+ white-space: nowrap;
1400
+ max-width: 240px;
1401
+ }
1402
+
1403
+ /* Arrowhead picker popover (per-end). */
1404
+ .du-arrowhead-grid {
1405
+ display: grid;
1406
+ grid-template-columns: repeat(4, 1fr);
1407
+ gap: 2px;
1408
+ margin-top: 4px;
1409
+ }
1410
+ .du-arrowhead-option {
1411
+ display: inline-flex;
1412
+ align-items: center;
1413
+ justify-content: center;
1414
+ min-width: 30px;
1415
+ height: 26px;
1416
+ padding: 2px;
1417
+ border: 1px solid transparent;
1418
+ border-radius: 6px;
1419
+ background: transparent;
1420
+ cursor: pointer;
1421
+ color: var(--du-fg, #1c1c1e);
1422
+ }
1423
+ .du-arrowhead-option:hover {
1424
+ background: var(--du-hover, #f0f0f3);
1425
+ }
1426
+ .du-arrowhead-option--active {
1427
+ border-color: var(--du-accent, #5082ff);
1428
+ }
1429
+ .du-arrowhead-none {
1430
+ font-size: 13px;
1431
+ opacity: 0.6;
1432
+ }
1433
+ .du-arrowhead-erd-label {
1434
+ font-size: 9px;
1435
+ line-height: 1.05;
1436
+ text-align: center;
1437
+ }
1438
+ .du-arrowhead-erd-toggle {
1439
+ margin-top: 6px;
1440
+ width: 100%;
1441
+ text-align: left;
1442
+ border: none;
1443
+ background: transparent;
1444
+ cursor: pointer;
1445
+ font-size: 11px;
1446
+ color: var(--du-muted, #6b6b70);
1447
+ padding: 2px 0;
1448
+ }
1449
+ .du-arrowhead-erd-toggle:hover {
1450
+ color: var(--du-fg, #1c1c1e);
1451
+ }
1452
+
1453
+ /* Inline link caption editor. */
1454
+ .du-link-caption-input {
1455
+ min-width: 60px;
1456
+ max-width: 220px;
1457
+ padding: 2px 6px;
1458
+ font: 12px system-ui, sans-serif;
1459
+ text-align: center;
1460
+ color: var(--du-fg, #222);
1461
+ background: var(--du-panel-bg, #fff);
1462
+ border: 1px solid var(--du-accent, #5082ff);
1463
+ border-radius: 4px;
1464
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
1465
+ outline: none;
1466
+ }
1467
+
1468
+ /* Mini shape-picker shown after a link is dropped on empty canvas. */
1469
+ .du-link-drop-menu {
1470
+ display: flex;
1471
+ gap: 2px;
1472
+ padding: 4px;
1473
+ background: var(--du-panel-bg, #fff);
1474
+ border: 1px solid var(--du-border, #e3e3e6);
1475
+ border-radius: 8px;
1476
+ box-shadow: 0 6px 20px rgba(0, 0, 0, 0.18);
1477
+ }
1478
+ .du-link-drop-item {
1479
+ display: inline-flex;
1480
+ align-items: center;
1481
+ justify-content: center;
1482
+ width: 30px;
1483
+ height: 30px;
1484
+ padding: 4px;
1485
+ border: none;
1486
+ border-radius: 6px;
1487
+ background: transparent;
1488
+ cursor: pointer;
1489
+ color: var(--du-fg, #1c1c1e);
1490
+ }
1491
+ .du-link-drop-item:hover {
1492
+ background: var(--du-hover, #f0f0f3);
1493
+ }
1494
+ .du-link-drop-item svg {
1495
+ width: 100%;
1496
+ height: 100%;
1497
+ }
1498
+
1499
+ .du-sel-menu-row:focus-visible {
1500
+ outline: 2px solid var(--du-accent, #5082ff);
1501
+ outline-offset: -2px;
1502
+ }