@foresthubai/workflow-builder 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/LICENSE +661 -661
  2. package/NOTICE +16 -16
  3. package/README.md +110 -93
  4. package/dist/components/ui/command.d.ts +2 -2
  5. package/dist/components/ui/input.d.ts +1 -1
  6. package/dist/components/ui/resizable.d.ts +1 -1
  7. package/dist/components/ui/textarea.d.ts +1 -1
  8. package/dist/graph/BaseNode.js +10 -10
  9. package/dist/graph/reactFlowRegistry.d.ts.map +1 -1
  10. package/dist/lib/utils.d.ts +3 -0
  11. package/dist/lib/utils.d.ts.map +1 -0
  12. package/dist/lib/utils.js +6 -0
  13. package/dist/lib/utils.js.map +1 -0
  14. package/dist/stores/editorStore.d.ts +0 -1
  15. package/dist/stores/editorStore.d.ts.map +1 -1
  16. package/dist/stores/editorStore.js +1 -11
  17. package/dist/stores/editorStore.js.map +1 -1
  18. package/dist/toolbars/CanvasTabsToolbar.d.ts +11 -0
  19. package/dist/toolbars/CanvasTabsToolbar.d.ts.map +1 -0
  20. package/dist/toolbars/CanvasTabsToolbar.js +101 -0
  21. package/dist/toolbars/CanvasTabsToolbar.js.map +1 -0
  22. package/package.json +2 -2
  23. package/src/BuilderLayout.tsx +345 -345
  24. package/src/Canvas.tsx +261 -261
  25. package/src/CanvasEditor.tsx +142 -142
  26. package/src/CanvasTabsToolbar.tsx +176 -176
  27. package/src/RightConfigPanel.tsx +266 -266
  28. package/src/WorkflowBuilder.tsx +412 -412
  29. package/src/cn.ts +6 -6
  30. package/src/components/ui/add-button.tsx +39 -39
  31. package/src/components/ui/alert-dialog.tsx +141 -141
  32. package/src/components/ui/alert.tsx +59 -59
  33. package/src/components/ui/badge.tsx +36 -36
  34. package/src/components/ui/button.tsx +85 -85
  35. package/src/components/ui/card.tsx +79 -79
  36. package/src/components/ui/checkbox.tsx +28 -28
  37. package/src/components/ui/collapsible.tsx +9 -9
  38. package/src/components/ui/command.tsx +153 -153
  39. package/src/components/ui/delete-button.tsx +23 -23
  40. package/src/components/ui/dialog.tsx +125 -125
  41. package/src/components/ui/dropdown-menu.tsx +198 -198
  42. package/src/components/ui/input.tsx +55 -55
  43. package/src/components/ui/label.tsx +24 -24
  44. package/src/components/ui/readonly-banner.tsx +15 -15
  45. package/src/components/ui/resizable.tsx +43 -43
  46. package/src/components/ui/scroll-area.tsx +102 -102
  47. package/src/components/ui/select.tsx +160 -160
  48. package/src/components/ui/separator.tsx +29 -29
  49. package/src/components/ui/switch.tsx +27 -27
  50. package/src/components/ui/textarea.tsx +51 -51
  51. package/src/components/ui/toast.tsx +127 -127
  52. package/src/components/ui/toaster.tsx +33 -33
  53. package/src/components/ui/toggle-group.tsx +59 -59
  54. package/src/components/ui/toggle.tsx +43 -43
  55. package/src/components/ui/tooltip.tsx +32 -32
  56. package/src/dialogs/NodePickerDialog.tsx +84 -84
  57. package/src/dialogs/ValidationDialog.tsx +184 -184
  58. package/src/graph/BaseNode.tsx +557 -557
  59. package/src/graph/CustomEdge.tsx +185 -185
  60. package/src/graph/CustomNode.tsx +16 -16
  61. package/src/graph/FunctionCallNode.tsx +30 -30
  62. package/src/graph/PortHandle.tsx +189 -189
  63. package/src/graph/reactFlowRegistry.ts +26 -26
  64. package/src/hooks/use-toast.ts +125 -125
  65. package/src/hooks/useAvailableVariables.ts +20 -20
  66. package/src/hooks/useCanvasHistory.ts +22 -22
  67. package/src/hooks/useCanvasTabs.ts +168 -168
  68. package/src/hooks/useFunctionDiagnosticsSync.ts +40 -40
  69. package/src/hooks/useFunctionRegistry.ts +26 -26
  70. package/src/hooks/useFunctions.ts +44 -44
  71. package/src/hooks/useGraph.ts +161 -161
  72. package/src/hooks/useNodeDefinitions.ts +82 -82
  73. package/src/hooks/useParamErrors.ts +26 -26
  74. package/src/hooks/useResolvedTheme.ts +30 -30
  75. package/src/hooks/useResourceDiagnosticsSync.ts +58 -58
  76. package/src/hooks/useSuppressThemeTransition.ts +79 -79
  77. package/src/hooks/useWorkflowSerialization.ts +127 -127
  78. package/src/i18n/index.ts +53 -53
  79. package/src/i18n/locales/de.json +501 -501
  80. package/src/i18n/locales/en.json +557 -557
  81. package/src/index.ts +27 -27
  82. package/src/inputs/ExpressionInput.tsx +297 -297
  83. package/src/inputs/ParameterEditor.tsx +515 -515
  84. package/src/inputs/PortSection.tsx +144 -144
  85. package/src/panels/BuilderSidebar.tsx +301 -301
  86. package/src/panels/ChannelConfigPanel.tsx +49 -49
  87. package/src/panels/ChannelsPanel.tsx +28 -28
  88. package/src/panels/DebugConsolePanel.tsx +73 -73
  89. package/src/panels/DebugContextPanel.tsx +77 -77
  90. package/src/panels/DebugExternalIOPanel.tsx +180 -180
  91. package/src/panels/DiagnosticsPanel.tsx +170 -170
  92. package/src/panels/EdgeConfigPanel.tsx +104 -104
  93. package/src/panels/FunctionConfigPanel.tsx +179 -179
  94. package/src/panels/FunctionListPanel.tsx +45 -45
  95. package/src/panels/MemoryConfigPanel.tsx +55 -55
  96. package/src/panels/MemoryPanel.tsx +40 -40
  97. package/src/panels/ModelConfigPanel.tsx +41 -41
  98. package/src/panels/ModelsPanel.tsx +36 -36
  99. package/src/panels/NodeConfigPanel.tsx +630 -630
  100. package/src/panels/NodeLibrary.tsx +288 -288
  101. package/src/panels/ResourceConfigPanel.tsx +132 -132
  102. package/src/panels/ResourceListPanel.tsx +113 -113
  103. package/src/panels/VariableConfigPanel.tsx +161 -161
  104. package/src/panels/VariablesPanel.tsx +145 -145
  105. package/src/stores/canvasStore.test.ts +44 -44
  106. package/src/stores/canvasStore.ts +245 -245
  107. package/src/stores/debugStore.ts +74 -74
  108. package/src/stores/diagnosticsStore.ts +130 -130
  109. package/src/stores/editorStore.ts +190 -202
  110. package/src/styles/index.css +526 -526
  111. package/src/utils/categoryConstants.ts +26 -26
  112. package/src/utils/channelOperations.ts +86 -86
  113. package/src/utils/connectionRules.ts +137 -137
  114. package/src/utils/functionOperations.ts +179 -179
  115. package/src/utils/graphOperations.ts +550 -550
  116. package/src/utils/history.ts +207 -207
  117. package/src/utils/memoryOperations.ts +57 -57
  118. package/src/utils/migrateFunctionNodes.ts +107 -107
  119. package/src/utils/modelOperations.ts +55 -55
  120. package/src/utils/paramDisplay.ts +71 -71
  121. package/src/utils/resourceHelpers.ts +32 -32
  122. package/src/utils/translation.ts +28 -28
  123. package/src/utils/variableOperations.ts +75 -75
  124. package/tailwind-preset.ts +166 -166
@@ -1,526 +1,526 @@
1
- /* ReactFlow's required CSS — without this, nodes can't be dragged/selected and
2
- the canvas chrome (controls, minimap, viewport transforms) renders unstyled. */
3
- @import "@xyflow/react/dist/style.css";
4
-
5
- @tailwind base;
6
- @tailwind components;
7
- @tailwind utilities;
8
-
9
- /* Color-mode switch must snap, not fade. useSuppressThemeTransition adds
10
- `theme-changing` while the embedder flips the `light` class on <html> and
11
- removes it once React has committed the new colorMode, killing every
12
- color/background/border transition in between (canvas tabs, the builder
13
- sidebar, ReactFlow's controls and node chrome) so they change in lockstep
14
- with the rest of the UI instead of animating to the new tokens. */
15
- .theme-changing,
16
- .theme-changing *,
17
- .theme-changing *::before,
18
- .theme-changing *::after {
19
- transition: none !important;
20
- }
21
-
22
- /* Definition of the design system. All colors, gradients, fonts, etc should be defined here.
23
- All colors MUST be HSL.
24
- */
25
-
26
- @layer base {
27
- /*
28
- * Token system: two layers
29
- *
30
- * 1. SURFACES (independent per mode) — background, card, popover, border, etc.
31
- * These are *materials*: white card vs dark card aren't the same hue shifted,
32
- * they're different identities. Defined separately in :root and .light.
33
- *
34
- * 2. SIGNALS (canonical hue, per-mode lightness) — node categories, channel
35
- * types, badge severity. These are *identity markers*:
36
- * "logic" should be recognizably the same green in both modes. Hue/sat
37
- * stay fixed; only --signal-l changes per mode.
38
- *
39
- * Consumers using opacity (hsl(var(--node-X) / 0.3)) automatically tint
40
- * toward the surface behind them, so the same canonical color reads well
41
- * on both dark and light surfaces.
42
- */
43
- :root {
44
- /* ════════════════════════════════════════════════════════════
45
- * GENERAL UI — surfaces, text, controls. (DARK MODE = default)
46
- *
47
- * These are the colors an embedding app SHARES with the builder:
48
- * buttons, panels, inputs, menus, status. A host that reuses this
49
- * preset reuses everything in this block. Hue 40, saturation ~2%:
50
- * a faint warm gray, premium and nearly neutral.
51
- * (.light overrides these values further down.)
52
- * ════════════════════════════════════════════════════════════ */
53
-
54
- /* App shell / page background, and the default text drawn on it. */
55
- --background: 40 2% 8%;
56
- --foreground: 0 0% 95%;
57
-
58
- /* Card surface — builder panels, list rows, AND node bodies. Matches
59
- --sidebar-background and --panel-background so all chrome shares one
60
- panel color. --card-foreground is text drawn on a card. */
61
- --card: 40 2% 18%;
62
- --card-foreground: 0 0% 95%;
63
-
64
- /* Popover / dropdown / menu surface (lifted slightly above cards). */
65
- --popover: 40 2% 22%;
66
- --popover-foreground: 0 0% 95%;
67
-
68
- /* PRIMARY = the vibrant brand green. Active text/icons (text-primary),
69
- primary buttons, the focus ring. --primary-foreground is text ON it. */
70
- --primary: 158 53% 42%;
71
- --primary-foreground: 0 0% 100%;
72
- --primary-glow: 158 53% 52%;
73
-
74
- /* Secondary neutral surface — secondary buttons. */
75
- --secondary: 40 2% 24%;
76
- --secondary-foreground: 0 0% 95%;
77
-
78
- /* MUTED = subtle neutral fill (hover backgrounds, bg-muted/50). Its
79
- --muted-foreground is the canonical SUBTEXT color: port labels,
80
- descriptions, "<arg>:" hints, and any secondary text. */
81
- --muted: 40 2% 22%;
82
- --muted-foreground: 0 0% 65%;
83
-
84
- /* ACCENT = the muted/selection green. Fills selected list rows and
85
- focused menu/command/select items (bg-accent). The calm "this is
86
- selected" wash; vibrant interactions use --primary instead. */
87
- --accent: 158 16% 22%;
88
- --accent-foreground: 0 0% 95%;
89
-
90
- /* DESTRUCTIVE = error red — delete buttons, error text/badges/borders. */
91
- --destructive: 0 78% 58%;
92
- --destructive-foreground: 0 0% 100%;
93
-
94
- /* BORDER = every DOM border: panels, cards, separators, AND form-field
95
- borders (the preset aliases border-input to this — one border color). */
96
- --border: 40 2% 26%;
97
- /* RING = focus highlight around inputs/buttons. */
98
- --ring: 158 53% 42%;
99
- /* OVERLAY = dialog/backdrop scrim. */
100
- --overlay: 0 0% 0% / 0.8;
101
-
102
- /* FIELD = form-input fill, darker than --card so inputs read as recessed
103
- (--border draws their outline). */
104
- --field: 40 2% 14%;
105
-
106
- --radius: 0.5rem;
107
-
108
- /* Status accents (semantic, full-saturation). */
109
- --success: 158 53% 42%;
110
- --success-foreground: 0 0% 100%;
111
- --warning: 38 92% 50%;
112
- --warning-foreground: 0 0% 100%;
113
-
114
- /* Sidebar — app-shell chrome, shared with the host's AppSidebar. */
115
- --sidebar-background: 40 2% 18%;
116
- --sidebar-foreground: 0 0% 95%;
117
- --sidebar-primary: 158 53% 42%;
118
- --sidebar-primary-foreground: 0 0% 100%;
119
- --sidebar-accent: 40 2% 24%;
120
- --sidebar-accent-foreground: 0 0% 95%;
121
- --sidebar-border: 40 2% 26%;
122
- --sidebar-ring: 158 53% 42%;
123
-
124
- /* Gradients — page background and glass components. */
125
- --gradient-subtle: linear-gradient(135deg, hsl(40 2% 8%), hsl(40 2% 18%));
126
- --gradient-glass: linear-gradient(135deg, var(--glass-bg-light), var(--glass-bg-medium));
127
-
128
- /* Shadows — black at moderate alpha; in dark mode the eye can't see much
129
- against near-black surfaces, so elevated states lean on the glow tokens. */
130
- --shadow-sm: 0 2px 4px 0 hsl(0 0% 0% / 0.4);
131
- --shadow-md: 0 4px 12px -2px hsl(0 0% 0% / 0.5);
132
- --shadow-lg: 0 12px 24px -4px hsl(0 0% 0% / 0.6);
133
- --shadow-glow: 0 0 20px hsl(158 53% 42% / 0.4);
134
-
135
- /* ════════════════════════════════════════════════════════════
136
- * BUILDER / GRAPH ONLY — canvas, edges, ports, node categories.
137
- *
138
- * Embedding apps do NOT use these; they're specific to the React
139
- * Flow editing surface and are mostly consumed INLINE by the graph
140
- * layer (CustomEdge / PortHandle / BaseNode), not via Tailwind classes.
141
- * ════════════════════════════════════════════════════════════ */
142
-
143
- /* Canvas = editing surface behind nodes (sits between --background and
144
- --card for a subtle elevation). */
145
- --canvas-background: 40 2% 14%;
146
-
147
- /* Edge/connection color — ALSO the node OUTLINE color, so edges and node
148
- borders share one graph-line color. */
149
- --edge-default: 40 5% 58%;
150
-
151
- /* Builder panel chrome (inspectors/sidebars inside the editor). */
152
- --panel-background: 40 2% 18%;
153
- --panel-border: 40 2% 26%;
154
-
155
- /* Selection glow — uniform halo around selected nodes/edges, independent
156
- of category color so multi-selection stays calm. */
157
- --selection-glow: 0 0% 100%;
158
-
159
- /* Drop shadow cast under nodes. */
160
- --node-shadow: 40 2% 5%;
161
-
162
- /* SIGNAL TUNING — node category hues are fixed; only saturation and
163
- lightness shift per mode (see .light). */
164
- --signal-s: 70%; /* signal saturation */
165
- --signal-l: 58%; /* signal lightness (dark mode) */
166
-
167
- /* NODE CATEGORY SIGNALS — one identity color per node type. */
168
- --node-agent: 273 var(--signal-s) var(--signal-l);
169
- --node-input: 195 var(--signal-s) var(--signal-l);
170
- --node-output: 142 var(--signal-s) var(--signal-l);
171
- --node-trigger: 32 88% var(--signal-l);
172
- --node-logic: 175 var(--signal-s) var(--signal-l);
173
- --node-data: 42 85% var(--signal-l);
174
- --node-tool: 200 80% var(--signal-l);
175
- --node-function: 320 var(--signal-s) var(--signal-l);
176
- }
177
-
178
- .light {
179
- /* ═══ GENERAL UI (light overrides — a different material, not a shift) ═══ */
180
- --background: 220 14% 96%;
181
- --foreground: 220 15% 15%;
182
- --card: 0 0% 100%;
183
- --card-foreground: 220 15% 15%;
184
- --popover: 0 0% 100%;
185
- --popover-foreground: 220 15% 15%;
186
- --primary: 158 53% 38%;
187
- --primary-foreground: 0 0% 100%;
188
- --primary-glow: 158 53% 48%;
189
- --secondary: 220 12% 92%;
190
- --secondary-foreground: 220 15% 15%;
191
- --muted: 220 12% 94%;
192
- --muted-foreground: 220 8% 46%;
193
- --accent: 158 40% 93%;
194
- --accent-foreground: 220 15% 15%;
195
- --destructive: 0 72% 45%;
196
- --destructive-foreground: 0 0% 100%;
197
- --border: 220 13% 88%;
198
- --ring: 158 53% 42%;
199
- --overlay: 0 0% 0% / 0.5;
200
- /* Field fill — clearly darker than the white card so inputs read recessed. */
201
- --field: 220 14% 92%;
202
- --success: 158 53% 38%;
203
- --success-foreground: 0 0% 100%;
204
- --warning: 32 92% 42%;
205
- --warning-foreground: 0 0% 100%;
206
- --sidebar-background: 220 14% 98%;
207
- --sidebar-foreground: 220 15% 15%;
208
- --sidebar-primary: 158 53% 38%;
209
- --sidebar-primary-foreground: 0 0% 100%;
210
- --sidebar-accent: 220 12% 94%;
211
- --sidebar-accent-foreground: 220 15% 15%;
212
- --sidebar-border: 220 13% 88%;
213
- --sidebar-ring: 158 53% 38%;
214
- /* Shadows — dark ink on light surfaces. Alpha kept high enough that cards
215
- read with clear elevation (matching dark mode's 0.4-alpha prominence),
216
- while staying soft so they don't smudge on white. */
217
- --shadow-sm: 0 1px 3px 0 hsl(220 15% 25% / 0.16), 0 2px 6px -1px hsl(220 15% 25% / 0.12);
218
- --shadow-md: 0 4px 8px -1px hsl(220 15% 25% / 0.18), 0 8px 16px -2px hsl(220 15% 25% / 0.14);
219
- --shadow-lg: 0 4px 8px -2px hsl(220 15% 25% / 0.12), 0 12px 24px -4px hsl(220 15% 25% / 0.18);
220
- --shadow-glow: 0 0 24px hsl(158 53% 42% / 0.25);
221
-
222
- /* ═══ BUILDER / GRAPH ONLY (light overrides) ═══ */
223
- /* Canvas sits deeper than the app shell so the editing surface is
224
- clearly distinct from chrome. */
225
- --canvas-background: 220 14% 92%;
226
- --edge-default: 220 8% 45%;
227
- --panel-background: 0 0% 100%;
228
- --panel-border: 220 13% 88%;
229
- /* Selection glow — dark in light mode for high contrast on white. */
230
- --selection-glow: 220 15% 15%;
231
- /* Same node hues as dark; only saturation/lightness drop for legibility. */
232
- --signal-s: 68%;
233
- --signal-l: 42%;
234
- }
235
- }
236
-
237
- @layer base {
238
- * {
239
- @apply border-border;
240
- }
241
-
242
- /* Base look is scoped to the builder's own root — NOT <body>. The host owns
243
- page chrome (background, page font, overscroll); the builder only dresses
244
- itself. font-sans (Poppins) is inherited from the .fh-builder root;
245
- headings get the Inter display face, scoped so it can't restyle host pages. */
246
- .fh-builder :is(h1, h2, h3, h4, h5, h6) {
247
- font-family: Inter, system-ui, sans-serif;
248
- }
249
- }
250
-
251
- /* Glass Forest Design System */
252
- @layer components {
253
- /* Core Glass Components - Verstärkte Glass-Effekte */
254
- .glass-forest-card {
255
- @apply bg-card/60 backdrop-blur-xl border border-white/10 rounded-2xl shadow-lg transition-all duration-300;
256
- }
257
-
258
- .glass-forest-card:hover {
259
- @apply bg-card/70 shadow-xl scale-[1.02] border-primary/20;
260
- }
261
-
262
- .glass-forest-panel {
263
- @apply bg-card/70 backdrop-blur-2xl border border-white/10 shadow-xl;
264
- }
265
-
266
- .glass-forest-button {
267
- @apply bg-card/40 backdrop-blur-md border border-border rounded-full transition-all duration-300;
268
- }
269
-
270
- .glass-forest-button:hover {
271
- @apply bg-card/60 border-primary/30 shadow-lg -translate-y-0.5;
272
- }
273
-
274
- .glass-forest-button-primary {
275
- @apply bg-primary text-primary-foreground rounded-full shadow-sm border-0;
276
- }
277
-
278
- .glass-forest-button-primary:hover {
279
- @apply shadow-xl scale-[1.02] -translate-y-0.5;
280
- }
281
-
282
- .glass-forest-input {
283
- @apply bg-card/40 backdrop-blur-lg border border-border rounded-2xl transition-all duration-300;
284
- }
285
-
286
- .glass-forest-input:focus {
287
- @apply bg-card/60 border-primary outline-none ring-2 ring-primary/20;
288
- }
289
-
290
- /* Organic Hover Effects */
291
- .hover-glow {
292
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
293
- }
294
-
295
- .hover-glow:hover {
296
- box-shadow: var(--glow-green);
297
- transform: translateY(-2px);
298
- }
299
-
300
- .hover-lift {
301
- transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
302
- }
303
-
304
- .hover-lift:hover {
305
- transform: translateY(-4px) scale(1.02);
306
- }
307
-
308
- /* Workflow Builder Glass Styles - Mehr Transparenz */
309
- .workflow-builder-panel {
310
- @apply bg-card/70 backdrop-blur-2xl border border-white/10 shadow-xl;
311
- }
312
-
313
- .block-card {
314
- @apply bg-card/60 backdrop-blur-xl border border-white/10 rounded-2xl shadow-lg cursor-pointer transition-all duration-300;
315
- }
316
-
317
- .block-card:hover {
318
- @apply bg-card/70 shadow-xl scale-[1.02];
319
- }
320
-
321
- .block-node-glass {
322
- @apply bg-card/70 backdrop-blur-xl rounded-2xl border border-white/10 shadow-xl transition-all duration-300;
323
- }
324
-
325
- .block-node-glass:hover {
326
- @apply shadow-2xl scale-[1.02] bg-card/80;
327
- }
328
-
329
- /* Loading & Status */
330
- .loading-organic {
331
- @apply animate-pulse-organic rounded-full bg-primary/20;
332
- }
333
-
334
- .status-dot {
335
- @apply w-2 h-2 rounded-full animate-pulse-organic;
336
- }
337
-
338
- .status-success .status-dot {
339
- @apply bg-success;
340
- box-shadow: var(--glow-green);
341
- }
342
-
343
- .status-warning .status-dot {
344
- @apply bg-warning;
345
- }
346
- }
347
-
348
- @layer utilities {
349
- /* Organic Animations */
350
- @keyframes pulse-organic {
351
- 0%,
352
- 100% {
353
- opacity: 1;
354
- transform: scale(1);
355
- }
356
- 50% {
357
- opacity: 0.8;
358
- transform: scale(1.05);
359
- }
360
- }
361
-
362
- @keyframes ripple-expand {
363
- 0% {
364
- transform: scale(0.8);
365
- opacity: 0;
366
- }
367
- 50% {
368
- opacity: 0.6;
369
- }
370
- 100% {
371
- transform: scale(2);
372
- opacity: 0;
373
- }
374
- }
375
-
376
- @keyframes light-trail {
377
- 0% {
378
- opacity: 0;
379
- transform: translateY(0);
380
- }
381
- 50% {
382
- opacity: 1;
383
- }
384
- 100% {
385
- opacity: 0;
386
- transform: translateY(-20px);
387
- }
388
- }
389
-
390
- @keyframes fade-in {
391
- from {
392
- opacity: 0;
393
- transform: translateY(10px);
394
- }
395
- to {
396
- opacity: 1;
397
- transform: translateY(0);
398
- }
399
- }
400
-
401
- @keyframes glow-pulse {
402
- 0%,
403
- 100% {
404
- box-shadow: var(--glow-green);
405
- }
406
- 50% {
407
- box-shadow: var(--glow-combined);
408
- }
409
- }
410
-
411
- /* Animation Classes */
412
- .animate-pulse-organic {
413
- animation: pulse-organic 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
414
- }
415
-
416
- .animate-ripple {
417
- animation: ripple-expand 1.5s ease-out;
418
- }
419
-
420
- .animate-glow-pulse {
421
- animation: glow-pulse 2s ease-in-out infinite;
422
- }
423
-
424
- .animate-fade-in {
425
- animation: fade-in 0.3s ease-out;
426
- }
427
-
428
- /* Line clamp utilities */
429
- .line-clamp-1 {
430
- overflow: hidden;
431
- display: -webkit-box;
432
- -webkit-box-orient: vertical;
433
- -webkit-line-clamp: 1;
434
- }
435
-
436
- .line-clamp-2 {
437
- overflow: hidden;
438
- display: -webkit-box;
439
- -webkit-box-orient: vertical;
440
- -webkit-line-clamp: 2;
441
- }
442
-
443
- .line-clamp-3 {
444
- overflow: hidden;
445
- display: -webkit-box;
446
- -webkit-box-orient: vertical;
447
- -webkit-line-clamp: 3;
448
- }
449
- }
450
-
451
- /* ════════════════════════════════════════════════════════════
452
- * Themed thin scrollbars — uniform across every scrollable region
453
- * inside the builder (sidebar, right panel, tabs toolbar, dropdowns,
454
- * dialogs, debug console).
455
- *
456
- * Two scopes, both opt-in:
457
- * - `.fh-builder` covers everything rendered inside the
458
- * builder DOM subtree.
459
- * - `.fh-builder-portal` covers Radix Content nodes that portal to
460
- * <body> (DropdownMenu, Dialog, AlertDialog, Select, Tooltip), so
461
- * they don't drop back to the host's native scrollbar just because
462
- * they live outside the builder root in the DOM. Same marker used
463
- * to pin `font-sans` on those nodes.
464
- *
465
- * Host pages are never touched: every rule is anchored on one of these
466
- * two classes; no `body`/`html`/universal selector escapes them. Thumb
467
- * uses --muted-foreground at low opacity so it reads as a faint hint of
468
- * the subtext color on both card and popover surfaces, in both modes.
469
- *
470
- * Width 6px: thin enough that the 6px reflow when it appears is barely
471
- * perceptible, while still big enough to grab. We deliberately don't
472
- * use `scrollbar-gutter: stable` (which would reserve the gutter even
473
- * when nothing overflows) — most panels never need a scrollbar, and
474
- * the permanent gap looks worse than the rare reflow.
475
- * ════════════════════════════════════════════════════════════ */
476
- .fh-builder,
477
- .fh-builder *,
478
- .fh-builder-portal,
479
- .fh-builder-portal * {
480
- scrollbar-width: thin;
481
- scrollbar-color: hsl(var(--muted-foreground) / 0.35) transparent;
482
- }
483
- .fh-builder ::-webkit-scrollbar,
484
- .fh-builder-portal::-webkit-scrollbar,
485
- .fh-builder-portal ::-webkit-scrollbar {
486
- width: 6px;
487
- height: 6px;
488
- }
489
- .fh-builder ::-webkit-scrollbar-track,
490
- .fh-builder-portal::-webkit-scrollbar-track,
491
- .fh-builder-portal ::-webkit-scrollbar-track {
492
- background: transparent;
493
- }
494
- .fh-builder ::-webkit-scrollbar-thumb,
495
- .fh-builder-portal::-webkit-scrollbar-thumb,
496
- .fh-builder-portal ::-webkit-scrollbar-thumb {
497
- background-color: hsl(var(--muted-foreground) / 0.3);
498
- border-radius: 9999px;
499
- }
500
- .fh-builder ::-webkit-scrollbar-thumb:hover,
501
- .fh-builder-portal::-webkit-scrollbar-thumb:hover,
502
- .fh-builder-portal ::-webkit-scrollbar-thumb:hover {
503
- background-color: hsl(var(--muted-foreground) / 0.55);
504
- }
505
- .fh-builder ::-webkit-scrollbar-corner,
506
- .fh-builder-portal::-webkit-scrollbar-corner,
507
- .fh-builder-portal ::-webkit-scrollbar-corner {
508
- background: transparent;
509
- }
510
-
511
- /* Suppress ReactFlow's built-in selection outline — we use custom glow via editorStore */
512
- .react-flow__node.selected,
513
- .react-flow__edge.selected {
514
- outline: none !important;
515
- }
516
-
517
- /* Debug cursor pulse animation for node highlighting */
518
- @keyframes debug-cursor-pulse {
519
- 0%,
520
- 100% {
521
- opacity: 1;
522
- }
523
- 50% {
524
- opacity: 0.6;
525
- }
526
- }
1
+ /* ReactFlow's required CSS — without this, nodes can't be dragged/selected and
2
+ the canvas chrome (controls, minimap, viewport transforms) renders unstyled. */
3
+ @import "@xyflow/react/dist/style.css";
4
+
5
+ @tailwind base;
6
+ @tailwind components;
7
+ @tailwind utilities;
8
+
9
+ /* Color-mode switch must snap, not fade. useSuppressThemeTransition adds
10
+ `theme-changing` while the embedder flips the `light` class on <html> and
11
+ removes it once React has committed the new colorMode, killing every
12
+ color/background/border transition in between (canvas tabs, the builder
13
+ sidebar, ReactFlow's controls and node chrome) so they change in lockstep
14
+ with the rest of the UI instead of animating to the new tokens. */
15
+ .theme-changing,
16
+ .theme-changing *,
17
+ .theme-changing *::before,
18
+ .theme-changing *::after {
19
+ transition: none !important;
20
+ }
21
+
22
+ /* Definition of the design system. All colors, gradients, fonts, etc should be defined here.
23
+ All colors MUST be HSL.
24
+ */
25
+
26
+ @layer base {
27
+ /*
28
+ * Token system: two layers
29
+ *
30
+ * 1. SURFACES (independent per mode) — background, card, popover, border, etc.
31
+ * These are *materials*: white card vs dark card aren't the same hue shifted,
32
+ * they're different identities. Defined separately in :root and .light.
33
+ *
34
+ * 2. SIGNALS (canonical hue, per-mode lightness) — node categories, channel
35
+ * types, badge severity. These are *identity markers*:
36
+ * "logic" should be recognizably the same green in both modes. Hue/sat
37
+ * stay fixed; only --signal-l changes per mode.
38
+ *
39
+ * Consumers using opacity (hsl(var(--node-X) / 0.3)) automatically tint
40
+ * toward the surface behind them, so the same canonical color reads well
41
+ * on both dark and light surfaces.
42
+ */
43
+ :root {
44
+ /* ════════════════════════════════════════════════════════════
45
+ * GENERAL UI — surfaces, text, controls. (DARK MODE = default)
46
+ *
47
+ * These are the colors an embedding app SHARES with the builder:
48
+ * buttons, panels, inputs, menus, status. A host that reuses this
49
+ * preset reuses everything in this block. Hue 40, saturation ~2%:
50
+ * a faint warm gray, premium and nearly neutral.
51
+ * (.light overrides these values further down.)
52
+ * ════════════════════════════════════════════════════════════ */
53
+
54
+ /* App shell / page background, and the default text drawn on it. */
55
+ --background: 40 2% 8%;
56
+ --foreground: 0 0% 95%;
57
+
58
+ /* Card surface — builder panels, list rows, AND node bodies. Matches
59
+ --sidebar-background and --panel-background so all chrome shares one
60
+ panel color. --card-foreground is text drawn on a card. */
61
+ --card: 40 2% 18%;
62
+ --card-foreground: 0 0% 95%;
63
+
64
+ /* Popover / dropdown / menu surface (lifted slightly above cards). */
65
+ --popover: 40 2% 22%;
66
+ --popover-foreground: 0 0% 95%;
67
+
68
+ /* PRIMARY = the vibrant brand green. Active text/icons (text-primary),
69
+ primary buttons, the focus ring. --primary-foreground is text ON it. */
70
+ --primary: 158 53% 42%;
71
+ --primary-foreground: 0 0% 100%;
72
+ --primary-glow: 158 53% 52%;
73
+
74
+ /* Secondary neutral surface — secondary buttons. */
75
+ --secondary: 40 2% 24%;
76
+ --secondary-foreground: 0 0% 95%;
77
+
78
+ /* MUTED = subtle neutral fill (hover backgrounds, bg-muted/50). Its
79
+ --muted-foreground is the canonical SUBTEXT color: port labels,
80
+ descriptions, "<arg>:" hints, and any secondary text. */
81
+ --muted: 40 2% 22%;
82
+ --muted-foreground: 0 0% 65%;
83
+
84
+ /* ACCENT = the muted/selection green. Fills selected list rows and
85
+ focused menu/command/select items (bg-accent). The calm "this is
86
+ selected" wash; vibrant interactions use --primary instead. */
87
+ --accent: 158 16% 22%;
88
+ --accent-foreground: 0 0% 95%;
89
+
90
+ /* DESTRUCTIVE = error red — delete buttons, error text/badges/borders. */
91
+ --destructive: 0 78% 58%;
92
+ --destructive-foreground: 0 0% 100%;
93
+
94
+ /* BORDER = every DOM border: panels, cards, separators, AND form-field
95
+ borders (the preset aliases border-input to this — one border color). */
96
+ --border: 40 2% 26%;
97
+ /* RING = focus highlight around inputs/buttons. */
98
+ --ring: 158 53% 42%;
99
+ /* OVERLAY = dialog/backdrop scrim. */
100
+ --overlay: 0 0% 0% / 0.8;
101
+
102
+ /* FIELD = form-input fill, darker than --card so inputs read as recessed
103
+ (--border draws their outline). */
104
+ --field: 40 2% 14%;
105
+
106
+ --radius: 0.5rem;
107
+
108
+ /* Status accents (semantic, full-saturation). */
109
+ --success: 158 53% 42%;
110
+ --success-foreground: 0 0% 100%;
111
+ --warning: 38 92% 50%;
112
+ --warning-foreground: 0 0% 100%;
113
+
114
+ /* Sidebar — app-shell chrome, shared with the host's AppSidebar. */
115
+ --sidebar-background: 40 2% 18%;
116
+ --sidebar-foreground: 0 0% 95%;
117
+ --sidebar-primary: 158 53% 42%;
118
+ --sidebar-primary-foreground: 0 0% 100%;
119
+ --sidebar-accent: 40 2% 24%;
120
+ --sidebar-accent-foreground: 0 0% 95%;
121
+ --sidebar-border: 40 2% 26%;
122
+ --sidebar-ring: 158 53% 42%;
123
+
124
+ /* Gradients — page background and glass components. */
125
+ --gradient-subtle: linear-gradient(135deg, hsl(40 2% 8%), hsl(40 2% 18%));
126
+ --gradient-glass: linear-gradient(135deg, var(--glass-bg-light), var(--glass-bg-medium));
127
+
128
+ /* Shadows — black at moderate alpha; in dark mode the eye can't see much
129
+ against near-black surfaces, so elevated states lean on the glow tokens. */
130
+ --shadow-sm: 0 2px 4px 0 hsl(0 0% 0% / 0.4);
131
+ --shadow-md: 0 4px 12px -2px hsl(0 0% 0% / 0.5);
132
+ --shadow-lg: 0 12px 24px -4px hsl(0 0% 0% / 0.6);
133
+ --shadow-glow: 0 0 20px hsl(158 53% 42% / 0.4);
134
+
135
+ /* ════════════════════════════════════════════════════════════
136
+ * BUILDER / GRAPH ONLY — canvas, edges, ports, node categories.
137
+ *
138
+ * Embedding apps do NOT use these; they're specific to the React
139
+ * Flow editing surface and are mostly consumed INLINE by the graph
140
+ * layer (CustomEdge / PortHandle / BaseNode), not via Tailwind classes.
141
+ * ════════════════════════════════════════════════════════════ */
142
+
143
+ /* Canvas = editing surface behind nodes (sits between --background and
144
+ --card for a subtle elevation). */
145
+ --canvas-background: 40 2% 14%;
146
+
147
+ /* Edge/connection color — ALSO the node OUTLINE color, so edges and node
148
+ borders share one graph-line color. */
149
+ --edge-default: 40 5% 58%;
150
+
151
+ /* Builder panel chrome (inspectors/sidebars inside the editor). */
152
+ --panel-background: 40 2% 18%;
153
+ --panel-border: 40 2% 26%;
154
+
155
+ /* Selection glow — uniform halo around selected nodes/edges, independent
156
+ of category color so multi-selection stays calm. */
157
+ --selection-glow: 0 0% 100%;
158
+
159
+ /* Drop shadow cast under nodes. */
160
+ --node-shadow: 40 2% 5%;
161
+
162
+ /* SIGNAL TUNING — node category hues are fixed; only saturation and
163
+ lightness shift per mode (see .light). */
164
+ --signal-s: 70%; /* signal saturation */
165
+ --signal-l: 58%; /* signal lightness (dark mode) */
166
+
167
+ /* NODE CATEGORY SIGNALS — one identity color per node type. */
168
+ --node-agent: 273 var(--signal-s) var(--signal-l);
169
+ --node-input: 195 var(--signal-s) var(--signal-l);
170
+ --node-output: 142 var(--signal-s) var(--signal-l);
171
+ --node-trigger: 32 88% var(--signal-l);
172
+ --node-logic: 175 var(--signal-s) var(--signal-l);
173
+ --node-data: 42 85% var(--signal-l);
174
+ --node-tool: 200 80% var(--signal-l);
175
+ --node-function: 320 var(--signal-s) var(--signal-l);
176
+ }
177
+
178
+ .light {
179
+ /* ═══ GENERAL UI (light overrides — a different material, not a shift) ═══ */
180
+ --background: 220 14% 96%;
181
+ --foreground: 220 15% 15%;
182
+ --card: 0 0% 100%;
183
+ --card-foreground: 220 15% 15%;
184
+ --popover: 0 0% 100%;
185
+ --popover-foreground: 220 15% 15%;
186
+ --primary: 158 53% 38%;
187
+ --primary-foreground: 0 0% 100%;
188
+ --primary-glow: 158 53% 48%;
189
+ --secondary: 220 12% 92%;
190
+ --secondary-foreground: 220 15% 15%;
191
+ --muted: 220 12% 94%;
192
+ --muted-foreground: 220 8% 46%;
193
+ --accent: 158 40% 93%;
194
+ --accent-foreground: 220 15% 15%;
195
+ --destructive: 0 72% 45%;
196
+ --destructive-foreground: 0 0% 100%;
197
+ --border: 220 13% 88%;
198
+ --ring: 158 53% 42%;
199
+ --overlay: 0 0% 0% / 0.5;
200
+ /* Field fill — clearly darker than the white card so inputs read recessed. */
201
+ --field: 220 14% 92%;
202
+ --success: 158 53% 38%;
203
+ --success-foreground: 0 0% 100%;
204
+ --warning: 32 92% 42%;
205
+ --warning-foreground: 0 0% 100%;
206
+ --sidebar-background: 220 14% 98%;
207
+ --sidebar-foreground: 220 15% 15%;
208
+ --sidebar-primary: 158 53% 38%;
209
+ --sidebar-primary-foreground: 0 0% 100%;
210
+ --sidebar-accent: 220 12% 94%;
211
+ --sidebar-accent-foreground: 220 15% 15%;
212
+ --sidebar-border: 220 13% 88%;
213
+ --sidebar-ring: 158 53% 38%;
214
+ /* Shadows — dark ink on light surfaces. Alpha kept high enough that cards
215
+ read with clear elevation (matching dark mode's 0.4-alpha prominence),
216
+ while staying soft so they don't smudge on white. */
217
+ --shadow-sm: 0 1px 3px 0 hsl(220 15% 25% / 0.16), 0 2px 6px -1px hsl(220 15% 25% / 0.12);
218
+ --shadow-md: 0 4px 8px -1px hsl(220 15% 25% / 0.18), 0 8px 16px -2px hsl(220 15% 25% / 0.14);
219
+ --shadow-lg: 0 4px 8px -2px hsl(220 15% 25% / 0.12), 0 12px 24px -4px hsl(220 15% 25% / 0.18);
220
+ --shadow-glow: 0 0 24px hsl(158 53% 42% / 0.25);
221
+
222
+ /* ═══ BUILDER / GRAPH ONLY (light overrides) ═══ */
223
+ /* Canvas sits deeper than the app shell so the editing surface is
224
+ clearly distinct from chrome. */
225
+ --canvas-background: 220 14% 92%;
226
+ --edge-default: 220 8% 45%;
227
+ --panel-background: 0 0% 100%;
228
+ --panel-border: 220 13% 88%;
229
+ /* Selection glow — dark in light mode for high contrast on white. */
230
+ --selection-glow: 220 15% 15%;
231
+ /* Same node hues as dark; only saturation/lightness drop for legibility. */
232
+ --signal-s: 68%;
233
+ --signal-l: 42%;
234
+ }
235
+ }
236
+
237
+ @layer base {
238
+ * {
239
+ @apply border-border;
240
+ }
241
+
242
+ /* Base look is scoped to the builder's own root — NOT <body>. The host owns
243
+ page chrome (background, page font, overscroll); the builder only dresses
244
+ itself. font-sans (Poppins) is inherited from the .fh-builder root;
245
+ headings get the Inter display face, scoped so it can't restyle host pages. */
246
+ .fh-builder :is(h1, h2, h3, h4, h5, h6) {
247
+ font-family: Inter, system-ui, sans-serif;
248
+ }
249
+ }
250
+
251
+ /* Glass Forest Design System */
252
+ @layer components {
253
+ /* Core Glass Components - Verstärkte Glass-Effekte */
254
+ .glass-forest-card {
255
+ @apply bg-card/60 backdrop-blur-xl border border-white/10 rounded-2xl shadow-lg transition-all duration-300;
256
+ }
257
+
258
+ .glass-forest-card:hover {
259
+ @apply bg-card/70 shadow-xl scale-[1.02] border-primary/20;
260
+ }
261
+
262
+ .glass-forest-panel {
263
+ @apply bg-card/70 backdrop-blur-2xl border border-white/10 shadow-xl;
264
+ }
265
+
266
+ .glass-forest-button {
267
+ @apply bg-card/40 backdrop-blur-md border border-border rounded-full transition-all duration-300;
268
+ }
269
+
270
+ .glass-forest-button:hover {
271
+ @apply bg-card/60 border-primary/30 shadow-lg -translate-y-0.5;
272
+ }
273
+
274
+ .glass-forest-button-primary {
275
+ @apply bg-primary text-primary-foreground rounded-full shadow-sm border-0;
276
+ }
277
+
278
+ .glass-forest-button-primary:hover {
279
+ @apply shadow-xl scale-[1.02] -translate-y-0.5;
280
+ }
281
+
282
+ .glass-forest-input {
283
+ @apply bg-card/40 backdrop-blur-lg border border-border rounded-2xl transition-all duration-300;
284
+ }
285
+
286
+ .glass-forest-input:focus {
287
+ @apply bg-card/60 border-primary outline-none ring-2 ring-primary/20;
288
+ }
289
+
290
+ /* Organic Hover Effects */
291
+ .hover-glow {
292
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
293
+ }
294
+
295
+ .hover-glow:hover {
296
+ box-shadow: var(--glow-green);
297
+ transform: translateY(-2px);
298
+ }
299
+
300
+ .hover-lift {
301
+ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
302
+ }
303
+
304
+ .hover-lift:hover {
305
+ transform: translateY(-4px) scale(1.02);
306
+ }
307
+
308
+ /* Workflow Builder Glass Styles - Mehr Transparenz */
309
+ .workflow-builder-panel {
310
+ @apply bg-card/70 backdrop-blur-2xl border border-white/10 shadow-xl;
311
+ }
312
+
313
+ .block-card {
314
+ @apply bg-card/60 backdrop-blur-xl border border-white/10 rounded-2xl shadow-lg cursor-pointer transition-all duration-300;
315
+ }
316
+
317
+ .block-card:hover {
318
+ @apply bg-card/70 shadow-xl scale-[1.02];
319
+ }
320
+
321
+ .block-node-glass {
322
+ @apply bg-card/70 backdrop-blur-xl rounded-2xl border border-white/10 shadow-xl transition-all duration-300;
323
+ }
324
+
325
+ .block-node-glass:hover {
326
+ @apply shadow-2xl scale-[1.02] bg-card/80;
327
+ }
328
+
329
+ /* Loading & Status */
330
+ .loading-organic {
331
+ @apply animate-pulse-organic rounded-full bg-primary/20;
332
+ }
333
+
334
+ .status-dot {
335
+ @apply w-2 h-2 rounded-full animate-pulse-organic;
336
+ }
337
+
338
+ .status-success .status-dot {
339
+ @apply bg-success;
340
+ box-shadow: var(--glow-green);
341
+ }
342
+
343
+ .status-warning .status-dot {
344
+ @apply bg-warning;
345
+ }
346
+ }
347
+
348
+ @layer utilities {
349
+ /* Organic Animations */
350
+ @keyframes pulse-organic {
351
+ 0%,
352
+ 100% {
353
+ opacity: 1;
354
+ transform: scale(1);
355
+ }
356
+ 50% {
357
+ opacity: 0.8;
358
+ transform: scale(1.05);
359
+ }
360
+ }
361
+
362
+ @keyframes ripple-expand {
363
+ 0% {
364
+ transform: scale(0.8);
365
+ opacity: 0;
366
+ }
367
+ 50% {
368
+ opacity: 0.6;
369
+ }
370
+ 100% {
371
+ transform: scale(2);
372
+ opacity: 0;
373
+ }
374
+ }
375
+
376
+ @keyframes light-trail {
377
+ 0% {
378
+ opacity: 0;
379
+ transform: translateY(0);
380
+ }
381
+ 50% {
382
+ opacity: 1;
383
+ }
384
+ 100% {
385
+ opacity: 0;
386
+ transform: translateY(-20px);
387
+ }
388
+ }
389
+
390
+ @keyframes fade-in {
391
+ from {
392
+ opacity: 0;
393
+ transform: translateY(10px);
394
+ }
395
+ to {
396
+ opacity: 1;
397
+ transform: translateY(0);
398
+ }
399
+ }
400
+
401
+ @keyframes glow-pulse {
402
+ 0%,
403
+ 100% {
404
+ box-shadow: var(--glow-green);
405
+ }
406
+ 50% {
407
+ box-shadow: var(--glow-combined);
408
+ }
409
+ }
410
+
411
+ /* Animation Classes */
412
+ .animate-pulse-organic {
413
+ animation: pulse-organic 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
414
+ }
415
+
416
+ .animate-ripple {
417
+ animation: ripple-expand 1.5s ease-out;
418
+ }
419
+
420
+ .animate-glow-pulse {
421
+ animation: glow-pulse 2s ease-in-out infinite;
422
+ }
423
+
424
+ .animate-fade-in {
425
+ animation: fade-in 0.3s ease-out;
426
+ }
427
+
428
+ /* Line clamp utilities */
429
+ .line-clamp-1 {
430
+ overflow: hidden;
431
+ display: -webkit-box;
432
+ -webkit-box-orient: vertical;
433
+ -webkit-line-clamp: 1;
434
+ }
435
+
436
+ .line-clamp-2 {
437
+ overflow: hidden;
438
+ display: -webkit-box;
439
+ -webkit-box-orient: vertical;
440
+ -webkit-line-clamp: 2;
441
+ }
442
+
443
+ .line-clamp-3 {
444
+ overflow: hidden;
445
+ display: -webkit-box;
446
+ -webkit-box-orient: vertical;
447
+ -webkit-line-clamp: 3;
448
+ }
449
+ }
450
+
451
+ /* ════════════════════════════════════════════════════════════
452
+ * Themed thin scrollbars — uniform across every scrollable region
453
+ * inside the builder (sidebar, right panel, tabs toolbar, dropdowns,
454
+ * dialogs, debug console).
455
+ *
456
+ * Two scopes, both opt-in:
457
+ * - `.fh-builder` covers everything rendered inside the
458
+ * builder DOM subtree.
459
+ * - `.fh-builder-portal` covers Radix Content nodes that portal to
460
+ * <body> (DropdownMenu, Dialog, AlertDialog, Select, Tooltip), so
461
+ * they don't drop back to the host's native scrollbar just because
462
+ * they live outside the builder root in the DOM. Same marker used
463
+ * to pin `font-sans` on those nodes.
464
+ *
465
+ * Host pages are never touched: every rule is anchored on one of these
466
+ * two classes; no `body`/`html`/universal selector escapes them. Thumb
467
+ * uses --muted-foreground at low opacity so it reads as a faint hint of
468
+ * the subtext color on both card and popover surfaces, in both modes.
469
+ *
470
+ * Width 6px: thin enough that the 6px reflow when it appears is barely
471
+ * perceptible, while still big enough to grab. We deliberately don't
472
+ * use `scrollbar-gutter: stable` (which would reserve the gutter even
473
+ * when nothing overflows) — most panels never need a scrollbar, and
474
+ * the permanent gap looks worse than the rare reflow.
475
+ * ════════════════════════════════════════════════════════════ */
476
+ .fh-builder,
477
+ .fh-builder *,
478
+ .fh-builder-portal,
479
+ .fh-builder-portal * {
480
+ scrollbar-width: thin;
481
+ scrollbar-color: hsl(var(--muted-foreground) / 0.35) transparent;
482
+ }
483
+ .fh-builder ::-webkit-scrollbar,
484
+ .fh-builder-portal::-webkit-scrollbar,
485
+ .fh-builder-portal ::-webkit-scrollbar {
486
+ width: 6px;
487
+ height: 6px;
488
+ }
489
+ .fh-builder ::-webkit-scrollbar-track,
490
+ .fh-builder-portal::-webkit-scrollbar-track,
491
+ .fh-builder-portal ::-webkit-scrollbar-track {
492
+ background: transparent;
493
+ }
494
+ .fh-builder ::-webkit-scrollbar-thumb,
495
+ .fh-builder-portal::-webkit-scrollbar-thumb,
496
+ .fh-builder-portal ::-webkit-scrollbar-thumb {
497
+ background-color: hsl(var(--muted-foreground) / 0.3);
498
+ border-radius: 9999px;
499
+ }
500
+ .fh-builder ::-webkit-scrollbar-thumb:hover,
501
+ .fh-builder-portal::-webkit-scrollbar-thumb:hover,
502
+ .fh-builder-portal ::-webkit-scrollbar-thumb:hover {
503
+ background-color: hsl(var(--muted-foreground) / 0.55);
504
+ }
505
+ .fh-builder ::-webkit-scrollbar-corner,
506
+ .fh-builder-portal::-webkit-scrollbar-corner,
507
+ .fh-builder-portal ::-webkit-scrollbar-corner {
508
+ background: transparent;
509
+ }
510
+
511
+ /* Suppress ReactFlow's built-in selection outline — we use custom glow via editorStore */
512
+ .react-flow__node.selected,
513
+ .react-flow__edge.selected {
514
+ outline: none !important;
515
+ }
516
+
517
+ /* Debug cursor pulse animation for node highlighting */
518
+ @keyframes debug-cursor-pulse {
519
+ 0%,
520
+ 100% {
521
+ opacity: 1;
522
+ }
523
+ 50% {
524
+ opacity: 0.6;
525
+ }
526
+ }