@dickpy/dsh-imagegen 1.5.8 → 1.5.10

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.
@@ -1,1189 +1,1226 @@
1
- /* Infinite canvas workspace — node-graph canvas styled after
2
- * basketikun/infinite-canvas: floating docks, card nodes, wire connections. */
3
-
4
- .root {
5
- position: relative;
6
- display: flex;
7
- flex: 1 1 auto;
8
- flex-direction: column;
9
- height: 100%;
10
- min-width: 0;
11
- min-height: 0;
12
- overflow: hidden;
13
- border: 1px solid var(--dsw-alias-border-l1);
14
- border-radius: 10px;
15
- background: var(--dsw-alias-bg-layer-1);
16
- color: var(--dsw-alias-label-primary);
17
- font-family: var(--dsw-font-family, inherit);
18
- user-select: none;
19
- }
20
-
21
- .selectCursor { cursor: default; }
22
- .panCursor { cursor: grab; }
23
- .panCursor:active { cursor: grabbing; }
24
-
25
- /* ------------------------------------------------------------- top bar */
26
-
27
- .topBar {
28
- display: flex;
29
- align-items: center;
30
- gap: 8px;
31
- padding: 8px 12px;
32
- border-bottom: 1px solid var(--dsw-alias-border-l1);
33
- flex: none;
34
- z-index: 40;
35
- background: var(--dsw-alias-bg-layer-1);
36
- }
37
-
38
- .projectSelect {
39
- max-width: 180px;
40
- height: 30px;
41
- padding: 0 8px;
42
- border-radius: 8px;
43
- border: 1px solid var(--dsw-alias-border-l1);
44
- background: transparent;
45
- color: var(--dsw-alias-label-primary);
46
- font-size: 12.5px;
47
- }
48
-
49
- .titleButton {
50
- height: 30px;
51
- padding: 0 10px;
52
- border: 1px solid transparent;
53
- border-radius: 8px;
54
- background: transparent;
55
- color: var(--dsw-alias-label-primary);
56
- font-size: 13px;
57
- font-weight: 600;
58
- max-width: 260px;
59
- overflow: hidden;
60
- text-overflow: ellipsis;
61
- white-space: nowrap;
62
- cursor: text;
63
- }
64
-
65
- .titleButton:hover { border-color: var(--dsw-alias-border-l1); }
66
-
67
- .titleInput {
68
- height: 30px;
69
- padding: 0 10px;
70
- border-radius: 8px;
71
- border: 1px solid var(--dsw-alias-brand-primary);
72
- background: transparent;
73
- color: var(--dsw-alias-label-primary);
74
- font-size: 13px;
75
- font-weight: 600;
76
- width: 240px;
77
- outline: none;
78
- }
79
-
80
- .topBarSpacer { flex: 1; }
81
-
82
- .saveState {
83
- font-size: 11.5px;
84
- color: var(--dsw-alias-label-tertiary);
85
- white-space: nowrap;
86
- }
87
-
88
- .saveState[data-state="error"] { color: var(--dsw-alias-label-error); }
89
- .saveState[data-state="saving"] { color: var(--dsw-alias-state-warn-primary, var(--dsw-alias-label-secondary)); }
90
-
91
- /* ---------------------------------------------------------- icon button */
92
-
93
- .iconButton {
94
- display: inline-flex;
95
- align-items: center;
96
- justify-content: center;
97
- gap: 5px;
98
- width: 30px;
99
- height: 30px;
100
- flex: none;
101
- border: none;
102
- border-radius: 8px;
103
- background: transparent;
104
- color: var(--dsw-alias-label-secondary);
105
- cursor: pointer;
106
- padding: 0;
107
- }
108
-
109
- .iconButton:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }
110
- .iconButton[data-active] { background: color-mix(in srgb, var(--dsw-alias-brand-primary) 16%, transparent); color: var(--dsw-alias-brand-primary); }
111
- .iconButton:disabled { opacity: 0.35; cursor: not-allowed; }
112
- .iconButton:disabled:hover { background: transparent; color: var(--dsw-alias-label-secondary); }
113
-
114
- /* (node tools moved to the bottom-center dock above) */
115
-
116
- /* ------------------------------------------------------------- viewport */
117
-
118
- .viewport {
119
- position: relative;
120
- flex: 1;
121
- min-height: 0;
122
- overflow: hidden;
123
- touch-action: none;
124
- }
125
-
126
- .grid {
127
- position: absolute;
128
- inset: 0;
129
- pointer-events: none;
130
- opacity: 0.5;
131
- }
132
-
133
- /* Interactive fluid flowmap dot field lives in a <canvas>; aurora is pure CSS.
134
- * Both render themselves at full strength (the 0.5 grid dimming stays off). */
135
- .flowCanvas {
136
- position: absolute;
137
- inset: 0;
138
- width: 100%;
139
- height: 100%;
140
- }
141
-
142
- .grid[data-mode="flow"],
143
- .grid[data-mode="aurora"] {
144
- opacity: 1;
145
- }
146
-
147
- .grid[data-mode="aurora"] {
148
- background-image:
149
- radial-gradient(560px 380px at 22% 28%, color-mix(in srgb, var(--dsw-alias-brand-primary) 24%, transparent), transparent 72%),
150
- radial-gradient(680px 480px at 78% 62%, rgb(167 139 250 / 20%), transparent 72%),
151
- radial-gradient(520px 400px at 48% 88%, rgb(34 211 238 / 17%), transparent 74%);
152
- background-size: 220% 220%;
153
- background-position: 0% 0%;
154
- animation: auroraDrift 20s ease-in-out infinite alternate;
155
- }
156
-
157
- @keyframes auroraDrift {
158
- from { background-position: 0% 0%; }
159
- to { background-position: 100% 100%; }
160
- }
161
-
162
- @media (prefers-reduced-motion: reduce) {
163
- .grid[data-mode="aurora"] { animation: none; }
164
- }
165
-
166
- .grid[data-mode="dots"] {
167
- background-image: radial-gradient(circle, color-mix(in srgb, var(--dsw-alias-label-primary) 30%, transparent) 1.1px, transparent 1.4px);
168
- }
169
-
170
- .grid[data-mode="lines"] {
171
- background-image:
172
- linear-gradient(color-mix(in srgb, var(--dsw-alias-label-primary) 9%, transparent) 1px, transparent 1px),
173
- linear-gradient(90deg, color-mix(in srgb, var(--dsw-alias-label-primary) 9%, transparent) 1px, transparent 1px);
174
- }
175
-
176
- .grid[data-mode="blank"] { background-image: none; }
177
-
178
- .grid[data-mode="diagonal"] {
179
- background-image: repeating-linear-gradient(45deg, color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent) 0 1px, transparent 1px 16px);
180
- }
181
-
182
- .grid[data-mode="checker"] {
183
- background-image:
184
- conic-gradient(color-mix(in srgb, var(--dsw-alias-label-primary) 7%, transparent) 90deg, transparent 90deg 180deg, color-mix(in srgb, var(--dsw-alias-label-primary) 7%, transparent) 180deg 270deg, transparent 270deg);
185
- background-size: 48px 48px;
186
- }
187
-
188
- .grid[data-mode="image"] { background-image: none; }
189
-
190
- .gridScrim {
191
- position: absolute;
192
- inset: 0;
193
- background: var(--dsw-alias-bg-layer-1);
194
- opacity: 0.6;
195
- }
196
-
197
- /* ------------------------------------------------ background menu */
198
-
199
- .backgroundMenu {
200
- position: absolute;
201
- transform: translate(-50%, -100%);
202
- z-index: 45;
203
- min-width: 168px;
204
- padding: 5px;
205
- border-radius: 11px;
206
- border: 1px solid var(--dsw-alias-border-l1);
207
- background: color-mix(in srgb, var(--dsw-alias-bg-layer-1) 96%, transparent);
208
- box-shadow: 0 16px 40px rgb(15 23 42 / 20%);
209
- display: flex;
210
- flex-direction: column;
211
- }
212
-
213
- .backgroundMenu button {
214
- display: flex;
215
- align-items: center;
216
- justify-content: flex-start;
217
- border: none;
218
- border-radius: 7px;
219
- background: transparent;
220
- color: var(--dsw-alias-label-primary);
221
- font-size: 12.5px;
222
- padding: 7px 10px;
223
- cursor: pointer;
224
- text-align: left;
225
- }
226
-
227
- .backgroundMenu button:hover { background: var(--dsw-alias-interactive-bg-hover); }
228
-
229
- .backgroundMenu button[data-active] {
230
- color: var(--dsw-alias-brand-primary);
231
- background: color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent);
232
- }
233
-
234
- .backgroundMenuDivider {
235
- height: 1px;
236
- margin: 4px 6px;
237
- background: var(--dsw-alias-border-l1);
238
- }
239
-
240
- .world {
241
- position: absolute;
242
- left: 0;
243
- top: 0;
244
- transform-origin: 0 0;
245
- }
246
-
247
- .marquee {
248
- position: absolute;
249
- z-index: 20;
250
- border: 1px solid var(--dsw-alias-brand-primary);
251
- background: color-mix(in srgb, var(--dsw-alias-brand-primary) 8%, transparent);
252
- border-radius: 4px;
253
- pointer-events: none;
254
- }
255
-
256
- /* ------------------------------------------------------------ nodes */
257
-
258
- .node {
259
- position: absolute;
260
- display: flex;
261
- flex-direction: column;
262
- border-radius: 14px;
263
- border: 1.5px solid var(--dsw-alias-border-l1);
264
- background: var(--dsw-alias-bg-layer-1);
265
- box-shadow: 0 8px 24px rgb(15 23 42 / 12%);
266
- overflow: visible;
267
- cursor: grab;
268
- touch-action: none;
269
- }
270
-
271
- .node:active { cursor: grabbing; }
272
-
273
- .nodeGlow {
274
- position: absolute;
275
- inset: -6px;
276
- border-radius: 18px;
277
- pointer-events: none;
278
- opacity: 0;
279
- transition: opacity 0.15s ease;
280
- }
281
-
282
- .nodeSelected {
283
- border-color: var(--dsw-alias-brand-primary);
284
- z-index: 2;
285
- }
286
-
287
- .nodeSelected .nodeGlow {
288
- opacity: 1;
289
- box-shadow: 0 0 0 3px color-mix(in srgb, var(--dsw-alias-brand-primary) 22%, transparent);
290
- }
291
-
292
- .nodeRelated { border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, transparent); }
293
-
294
- .nodeConnectTarget {
295
- border-color: var(--dsw-alias-brand-primary);
296
- box-shadow: 0 0 0 4px color-mix(in srgb, var(--dsw-alias-brand-primary) 26%, transparent);
297
- }
298
-
299
- .nodeHeader {
300
- display: flex;
301
- align-items: center;
302
- gap: 6px;
303
- padding: 7px 12px 5px;
304
- font-size: 11.5px;
305
- color: var(--dsw-alias-label-secondary);
306
- cursor: inherit;
307
- flex: none;
308
- }
309
-
310
- .nodeTitle {
311
- font-weight: 600;
312
- overflow: hidden;
313
- text-overflow: ellipsis;
314
- white-space: nowrap;
315
- }
316
-
317
- .nodeSize {
318
- margin-left: auto;
319
- font-size: 10.5px;
320
- color: var(--dsw-alias-label-tertiary);
321
- font-variant-numeric: tabular-nums;
322
- }
323
-
324
- .nodeSpinner {
325
- width: 11px;
326
- height: 11px;
327
- flex: none;
328
- border-radius: 50%;
329
- border: 1.5px solid color-mix(in srgb, var(--dsw-alias-brand-primary) 30%, transparent);
330
- border-top-color: var(--dsw-alias-brand-primary);
331
- animation: canvasSpin 0.8s linear infinite;
332
- }
333
-
334
- .nodeBody {
335
- flex: 1;
336
- min-height: 0;
337
- margin: 0 8px 8px;
338
- border-radius: 9px;
339
- overflow: hidden;
340
- background: color-mix(in srgb, var(--dsw-alias-label-primary) 6%, transparent);
341
- display: flex;
342
- align-items: center;
343
- justify-content: center;
344
- pointer-events: none;
345
- }
346
-
347
- .nodeBody img {
348
- width: 100%;
349
- height: 100%;
350
- object-fit: contain;
351
- display: block;
352
- }
353
-
354
- .imageInfo {
355
- position: absolute;
356
- top: 6px;
357
- z-index: 2;
358
- padding: 3px 8px;
359
- border-radius: 6px;
360
- background: color-mix(in srgb, var(--dsw-alias-label-primary) 62%, transparent);
361
- color: var(--dsw-alias-bg-layer-1);
362
- font-size: 10.5px;
363
- line-height: 1.4;
364
- pointer-events: none;
365
- backdrop-filter: blur(3px);
366
- max-width: calc(100% - 90px);
367
- overflow: hidden;
368
- text-overflow: ellipsis;
369
- white-space: nowrap;
370
- }
371
-
372
- .imageInfoLeft { left: 6px; font-weight: 600; }
373
-
374
- .imageInfoRight {
375
- right: 6px;
376
- left: auto;
377
- font-variant-numeric: tabular-nums;
378
- opacity: 0.85;
379
- }
380
-
381
- .nodeState,
382
- .nodeStateError {
383
- display: flex;
384
- flex-direction: column;
385
- align-items: center;
386
- gap: 8px;
387
- font-size: 12px;
388
- color: var(--dsw-alias-label-secondary);
389
- padding: 12px;
390
- text-align: center;
391
- pointer-events: auto;
392
- }
393
-
394
- .nodeStateError { color: var(--dsw-alias-label-error); }
395
-
396
- .nodeStateError button {
397
- border: none;
398
- border-radius: 7px;
399
- padding: 4px 12px;
400
- background: var(--dsw-alias-interactive-bg-hover);
401
- color: var(--dsw-alias-label-primary);
402
- font-size: 12px;
403
- cursor: pointer;
404
- }
405
-
406
- .nodeEmpty {
407
- display: flex;
408
- flex-direction: column;
409
- align-items: center;
410
- gap: 8px;
411
- border: 1.5px dashed color-mix(in srgb, var(--dsw-alias-label-primary) 24%, transparent);
412
- border-radius: 9px;
413
- margin: 6px;
414
- padding: 14px;
415
- background: transparent;
416
- color: var(--dsw-alias-label-tertiary);
417
- font-size: 12px;
418
- cursor: pointer;
419
- /* .nodeBody disables pointer events for node dragging; the upload button
420
- * inside must opt back in or its onClick never fires. */
421
- pointer-events: auto;
422
- }
423
-
424
- .nodeEmpty:hover {
425
- border-color: var(--dsw-alias-brand-primary);
426
- color: var(--dsw-alias-brand-primary);
427
- }
428
-
429
- .textNode { cursor: default; }
430
-
431
- /* image nodes render as bare cards: the picture fills the frame */
432
- .imageNode .nodeBody { margin: 6px; }
433
-
434
- .textArea {
435
- flex: 1;
436
- min-height: 0;
437
- margin: 0 8px 8px;
438
- border: none;
439
- resize: none;
440
- outline: none;
441
- background: transparent;
442
- color: var(--dsw-alias-label-primary);
443
- font-size: 13px;
444
- line-height: 1.55;
445
- padding: 4px 6px;
446
- cursor: text;
447
- pointer-events: auto;
448
- }
449
-
450
- .textArea::placeholder { color: var(--dsw-alias-label-tertiary); }
451
-
452
- /* --------------------------------------------------- bottom dock */
453
-
454
- .dock {
455
- position: absolute;
456
- left: 50%;
457
- bottom: 14px;
458
- transform: translateX(-50%);
459
- z-index: 30;
460
- display: flex;
461
- align-items: center;
462
- gap: 2px;
463
- padding: 6px;
464
- border-radius: 13px;
465
- border: 1px solid var(--dsw-alias-border-l1);
466
- background: color-mix(in srgb, var(--dsw-alias-bg-layer-1) 92%, transparent);
467
- box-shadow: 0 10px 30px rgb(15 23 42 / 14%);
468
- backdrop-filter: blur(10px);
469
- }
470
-
471
- /* macOS-dock magnification: the pointermove handler on .dock feeds a gaussian
472
- * falloff into --dock-scale / --dock-lift, so the hovered icon swells and lifts
473
- * while neighbours rise and fall like a zipper as the pointer slides across. */
474
- .dock .iconButton {
475
- position: relative;
476
- width: 40px;
477
- height: 40px;
478
- border-radius: 11px;
479
- transform: translateY(var(--dock-lift, 0px)) scale(var(--dock-scale, 1));
480
- transform-origin: 50% 100%;
481
- transition: transform 0.16s cubic-bezier(0.33, 1, 0.68, 1), background 0.15s ease, color 0.15s ease, box-shadow 0.18s ease;
482
- }
483
-
484
- .dock .iconButton:not(:disabled):hover {
485
- z-index: 2;
486
- box-shadow: 0 6px 14px color-mix(in srgb, var(--dsw-alias-label-primary) 20%, transparent);
487
- }
488
-
489
- .dock .iconButton:not(:disabled):active {
490
- transform: translateY(var(--dock-lift, 0px)) scale(0.94);
491
- }
492
-
493
- .dock .iconButton[data-active] {
494
- background: var(--dsw-alias-label-primary);
495
- color: var(--dsw-alias-bg-layer-1);
496
- }
497
-
498
- .dock .iconButton[data-active]:hover { background: var(--dsw-alias-label-primary); color: var(--dsw-alias-bg-layer-1); }
499
-
500
- .dockDivider {
501
- width: 1px;
502
- height: 18px;
503
- margin: 0 4px;
504
- background: var(--dsw-alias-border-l1);
505
- }
506
-
507
- /* ------------------------------------------------- floating composer */
508
-
509
- .composer {
510
- position: absolute;
511
- z-index: 35;
512
- width: 560px;
513
- display: flex;
514
- flex-direction: column;
515
- gap: 8px;
516
- padding: 10px;
517
- border-radius: 14px;
518
- border: 1px solid var(--dsw-alias-border-l1);
519
- background: color-mix(in srgb, var(--dsw-alias-bg-layer-1) 96%, transparent);
520
- box-shadow: 0 16px 44px rgb(15 23 42 / 18%);
521
- backdrop-filter: blur(10px);
522
- }
523
-
524
- .composerPrompt {
525
- width: 100%;
526
- min-height: 38px;
527
- max-height: 120px;
528
- resize: none;
529
- border: none;
530
- outline: none;
531
- background: transparent;
532
- color: var(--dsw-alias-label-primary);
533
- font-size: 13px;
534
- line-height: 1.5;
535
- padding: 2px 4px;
536
- cursor: text;
537
- }
538
-
539
- .composerPrompt::placeholder { color: var(--dsw-alias-label-tertiary); }
540
-
541
- .composerMeta { display: flex; }
542
-
543
- .composerChip {
544
- display: inline-flex;
545
- align-items: center;
546
- gap: 5px;
547
- font-size: 11px;
548
- padding: 2px 9px;
549
- border-radius: 999px;
550
- border: 1px solid color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, transparent);
551
- color: var(--dsw-alias-brand-primary);
552
- background: color-mix(in srgb, var(--dsw-alias-brand-primary) 8%, transparent);
553
- }
554
-
555
- .composerControls {
556
- display: flex;
557
- align-items: center;
558
- gap: 6px;
559
- }
560
-
561
- .composerControls select {
562
- height: 28px;
563
- border-radius: 999px;
564
- border: 1px solid var(--dsw-alias-border-l1);
565
- background: transparent;
566
- color: var(--dsw-alias-label-primary);
567
- font-size: 11.5px;
568
- padding: 0 8px;
569
- min-width: 0;
570
- max-width: 128px;
571
- }
572
-
573
- /* custom dropdown button + popup (styled replacement for native <select>) */
574
- .composerSelect {
575
- display: inline-flex;
576
- align-items: center;
577
- gap: 6px;
578
- height: 28px;
579
- padding: 0 8px 0 11px;
580
- border-radius: 999px;
581
- border: 1px solid var(--dsw-alias-border-l1);
582
- background: transparent;
583
- color: var(--dsw-alias-label-primary);
584
- font-size: 11.5px;
585
- min-width: 0;
586
- max-width: 138px;
587
- cursor: pointer;
588
- transition: background 0.15s ease, border-color 0.15s ease;
589
- }
590
-
591
- .composerSelect:hover { background: var(--dsw-alias-interactive-bg-hover); }
592
- .composerSelect[data-open] { border-color: var(--dsw-alias-brand-primary); color: var(--dsw-alias-brand-primary); }
593
- .composerSelect svg { flex: none; opacity: 0.65; }
594
- .composerSelect[data-open] svg { opacity: 0.9; }
595
-
596
- .composerSelectValue {
597
- overflow: hidden;
598
- text-overflow: ellipsis;
599
- white-space: nowrap;
600
- min-width: 0;
601
- }
602
-
603
- .composerSelectMenu {
604
- position: fixed;
605
- z-index: 95;
606
- max-height: 264px;
607
- overflow: auto;
608
- padding: 5px;
609
- border-radius: 11px;
610
- border: 1px solid var(--dsw-alias-border-l1);
611
- background: color-mix(in srgb, var(--dsw-alias-bg-layer-1) 97%, transparent);
612
- box-shadow: 0 14px 38px rgb(15 23 42 / 18%);
613
- backdrop-filter: blur(10px);
614
- }
615
-
616
- .composerSelectMenu button {
617
- display: block;
618
- width: 100%;
619
- border: none;
620
- border-radius: 7px;
621
- background: transparent;
622
- color: var(--dsw-alias-label-primary);
623
- font-size: 12.5px;
624
- line-height: 1.45;
625
- padding: 7px 10px;
626
- cursor: pointer;
627
- text-align: left;
628
- white-space: nowrap;
629
- }
630
-
631
- .composerSelectMenu button:hover { background: var(--dsw-alias-interactive-bg-hover); }
632
- .composerSelectMenu button[data-selected] {
633
- color: var(--dsw-alias-brand-primary);
634
- background: color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent);
635
- }
636
-
637
- .composerSend {
638
- margin-left: auto;
639
- width: 34px;
640
- height: 34px;
641
- flex: none;
642
- border: none;
643
- border-radius: 50%;
644
- background: var(--dsw-alias-label-primary);
645
- color: var(--dsw-alias-bg-layer-1);
646
- cursor: pointer;
647
- display: inline-flex;
648
- align-items: center;
649
- justify-content: center;
650
- }
651
-
652
- .composerSend:hover { opacity: 0.88; }
653
- .composerSend:disabled { opacity: 0.35; cursor: not-allowed; }
654
-
655
- /* ------------------------------------------------------ config node */
656
-
657
- .configNode {
658
- padding: 0 14px 14px;
659
- cursor: grab;
660
- }
661
-
662
- .configNode .nodeHeader { padding: 10px 2px 6px; font-size: 12px; }
663
-
664
- .configLinks {
665
- display: flex;
666
- align-items: center;
667
- margin: 4px 0 10px;
668
- flex: none;
669
- }
670
-
671
- .configLinks .composerChip {
672
- font-size: 11px;
673
- padding: 3px 10px;
674
- }
675
-
676
- .configHint {
677
- flex: 1;
678
- min-height: 0;
679
- margin: 0;
680
- font-size: 11.5px;
681
- line-height: 1.7;
682
- color: var(--dsw-alias-label-tertiary);
683
- overflow: auto;
684
- }
685
-
686
- /* connection handles */
687
-
688
- .handle {
689
- position: absolute;
690
- top: 50%;
691
- width: 12px;
692
- height: 12px;
693
- border-radius: 50%;
694
- transform: translateY(-50%) scale(0.6);
695
- background: var(--dsw-alias-bg-layer-1);
696
- border: 2px solid var(--dsw-alias-brand-primary);
697
- opacity: 0;
698
- transition: opacity 0.12s ease, transform 0.12s ease;
699
- cursor: crosshair;
700
- z-index: 3;
701
- }
702
-
703
- .handleLeft { left: -7px; }
704
- .handleRight { right: -7px; }
705
-
706
- .node:hover .handle,
707
- .nodeSelected .handle,
708
- .nodeConnectTarget .handle {
709
- opacity: 1;
710
- transform: translateY(-50%) scale(1);
711
- }
712
-
713
- .handle:hover { background: var(--dsw-alias-brand-primary); }
714
-
715
- /* The source handle doubles as a quick-add button (hover opens the node
716
- * picker, drag connects) so it reads as a Dify-style big ⊕. */
717
- .handleRight {
718
- display: flex;
719
- align-items: center;
720
- justify-content: center;
721
- width: 20px;
722
- height: 20px;
723
- right: -10px;
724
- font-size: 13px;
725
- font-weight: 600;
726
- line-height: 1;
727
- color: var(--dsw-alias-brand-primary);
728
- }
729
-
730
- .handleRight::after { content: '+'; }
731
-
732
- .handleRight:hover { color: var(--dsw-alias-bg-layer-1); transform: translateY(-50%) scale(1.12); }
733
-
734
- .resizeHandle {
735
- position: absolute;
736
- right: -5px;
737
- bottom: -5px;
738
- width: 14px;
739
- height: 14px;
740
- border-radius: 4px;
741
- background: var(--dsw-alias-brand-primary);
742
- border: 2px solid var(--dsw-alias-bg-layer-1);
743
- cursor: nwse-resize;
744
- z-index: 3;
745
- }
746
-
747
- /* hover toolbar */
748
-
749
- .hoverToolbar {
750
- position: absolute;
751
- top: -36px;
752
- left: 50%;
753
- transform: translateX(-50%);
754
- display: flex;
755
- gap: 2px;
756
- padding: 4px;
757
- border-radius: 10px;
758
- border: 1px solid var(--dsw-alias-border-l1);
759
- background: color-mix(in srgb, var(--dsw-alias-bg-layer-1) 92%, transparent);
760
- box-shadow: 0 8px 22px rgb(15 23 42 / 14%);
761
- opacity: 0;
762
- pointer-events: none;
763
- transition: opacity 0.12s ease;
764
- z-index: 4;
765
- }
766
-
767
- .node:hover .hoverToolbar,
768
- .nodeSelected .hoverToolbar {
769
- opacity: 1;
770
- pointer-events: auto;
771
- }
772
-
773
- /* -------------------------------------------------------- connections */
774
-
775
- .connectionLayer {
776
- position: absolute;
777
- overflow: visible;
778
- pointer-events: none;
779
- z-index: 0;
780
- }
781
-
782
- .connectionPath {
783
- fill: none;
784
- stroke-width: 1.8;
785
- stroke-opacity: 0.9;
786
- pointer-events: none;
787
- }
788
-
789
- .connectionActive {
790
- stroke-width: 2.6;
791
- stroke-opacity: 1;
792
- filter: drop-shadow(0 0 6px color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, transparent));
793
- }
794
-
795
- .connectionPreview {
796
- fill: none;
797
- stroke: var(--dsw-alias-brand-primary);
798
- stroke-width: 2;
799
- stroke-dasharray: 5 5;
800
- pointer-events: none;
801
- }
802
-
803
- /* Flow overlay: a soft light band glides from the path start (source) toward
804
- * its end (target), echoing the direction of the gradient and arrowhead. */
805
- .connectionFlow {
806
- fill: none;
807
- stroke: var(--dsw-alias-brand-primary);
808
- stroke-width: 2;
809
- stroke-linecap: round;
810
- stroke-dasharray: 30 90;
811
- opacity: 0.4;
812
- pointer-events: none;
813
- animation: connectionFlow 1.6s linear infinite;
814
- }
815
-
816
- .connectionFlowActive {
817
- stroke: var(--dsw-alias-brand-primary);
818
- opacity: 0.85;
819
- }
820
-
821
- @keyframes connectionFlow {
822
- to { stroke-dashoffset: -120; }
823
- }
824
-
825
- @media (prefers-reduced-motion: reduce) {
826
- .connectionFlow { animation: none; opacity: 0.35; }
827
- }
828
-
829
- /* --------------------------------------------------------- empty hint */
830
-
831
- .emptyHint {
832
- position: absolute;
833
- left: 50%;
834
- top: 44%;
835
- transform: translate(-50%, -50%);
836
- display: flex;
837
- flex-direction: column;
838
- align-items: center;
839
- gap: 6px;
840
- text-align: center;
841
- color: var(--dsw-alias-label-tertiary);
842
- pointer-events: none;
843
- z-index: 5;
844
- }
845
-
846
- .emptyHint strong {
847
- font-size: 16px;
848
- color: var(--dsw-alias-label-secondary);
849
- }
850
-
851
- .emptyHint span { font-size: 12.5px; max-width: 320px; line-height: 1.6; }
852
-
853
- /* ------------------------------------------------------------ zoom dock */
854
-
855
- .zoomDock {
856
- position: absolute;
857
- left: 14px;
858
- bottom: 14px;
859
- z-index: 30;
860
- display: flex;
861
- align-items: center;
862
- gap: 6px;
863
- padding: 6px 8px;
864
- border-radius: 12px;
865
- border: 1px solid var(--dsw-alias-border-l1);
866
- background: color-mix(in srgb, var(--dsw-alias-bg-layer-1) 88%, transparent);
867
- box-shadow: 0 10px 30px rgb(15 23 42 / 12%);
868
- backdrop-filter: blur(10px);
869
- }
870
-
871
- .zoomDock input[type="range"] {
872
- width: 96px;
873
- accent-color: var(--dsw-alias-brand-primary);
874
- }
875
-
876
- .zoomValue {
877
- width: 40px;
878
- text-align: right;
879
- font-size: 11.5px;
880
- color: var(--dsw-alias-label-secondary);
881
- font-variant-numeric: tabular-nums;
882
- }
883
-
884
- /* -------------------------------------------------------------- minimap */
885
-
886
- .minimap {
887
- position: absolute;
888
- left: 14px;
889
- bottom: 62px;
890
- z-index: 30;
891
- width: 220px;
892
- height: 150px;
893
- border-radius: 12px;
894
- border: 1px solid var(--dsw-alias-border-l1);
895
- background: color-mix(in srgb, var(--dsw-alias-bg-layer-1) 88%, transparent);
896
- box-shadow: 0 10px 30px rgb(15 23 42 / 12%);
897
- overflow: hidden;
898
- backdrop-filter: blur(10px);
899
- }
900
-
901
- .minimapCanvas {
902
- position: relative;
903
- width: 100%;
904
- height: 100%;
905
- cursor: crosshair;
906
- }
907
-
908
- .minimapNode {
909
- position: absolute;
910
- border-radius: 2px;
911
- opacity: 0.8;
912
- }
913
-
914
- .minimapImage { background: var(--dsw-alias-brand-primary); }
915
- .minimapText { background: color-mix(in srgb, var(--dsw-alias-label-primary) 40%, transparent); }
916
- .minimapSelected { outline: 1.5px solid var(--dsw-alias-brand-primary); }
917
-
918
- .minimapViewport {
919
- position: absolute;
920
- border: 1.5px solid var(--dsw-alias-brand-primary);
921
- background: color-mix(in srgb, var(--dsw-alias-brand-primary) 10%, transparent);
922
- border-radius: 2px;
923
- pointer-events: none;
924
- }
925
-
926
- /* ------------------------------------------------------------ inspector */
927
-
928
- .inspectorRow {
929
- display: flex;
930
- gap: 8px;
931
- }
932
-
933
- .inspectorRow > * { flex: 1; min-width: 0; }
934
-
935
- /* --------------------------------------------------------- context menu */
936
-
937
- .contextMenu {
938
- position: fixed;
939
- z-index: 90;
940
- min-width: 176px;
941
- padding: 5px;
942
- border-radius: 11px;
943
- border: 1px solid var(--dsw-alias-border-l1);
944
- background: color-mix(in srgb, var(--dsw-alias-bg-layer-1) 96%, transparent);
945
- box-shadow: 0 16px 40px rgb(15 23 42 / 20%);
946
- display: flex;
947
- flex-direction: column;
948
- }
949
-
950
- .contextMenu button {
951
- display: flex;
952
- align-items: center;
953
- gap: 8px;
954
- border: none;
955
- border-radius: 7px;
956
- background: transparent;
957
- color: var(--dsw-alias-label-primary);
958
- font-size: 12.5px;
959
- padding: 7px 10px;
960
- cursor: pointer;
961
- text-align: left;
962
- }
963
-
964
- .contextMenu button:hover { background: var(--dsw-alias-interactive-bg-hover); }
965
- .contextMenu button[data-danger] { color: var(--dsw-alias-label-error); }
966
-
967
- /* ---------------------------------------------------------- error toast */
968
-
969
- .errorToast {
970
- position: absolute;
971
- left: 50%;
972
- bottom: 18px;
973
- transform: translateX(-50%);
974
- z-index: 60;
975
- display: flex;
976
- align-items: center;
977
- gap: 10px;
978
- max-width: min(560px, 80%);
979
- padding: 9px 12px;
980
- border-radius: 10px;
981
- border: 1px solid color-mix(in srgb, var(--dsw-alias-label-error) 35%, transparent);
982
- background: color-mix(in srgb, var(--dsw-alias-label-error) 12%, var(--dsw-alias-bg-layer-1));
983
- color: var(--dsw-alias-label-error);
984
- font-size: 12.5px;
985
- box-shadow: 0 12px 30px rgb(15 23 42 / 18%);
986
- }
987
-
988
- .errorToast button {
989
- border: none;
990
- background: transparent;
991
- color: inherit;
992
- cursor: pointer;
993
- padding: 2px;
994
- display: inline-flex;
995
- flex: none;
996
- }
997
-
998
- /* ---------------------------------------------------------- picker modal */
999
-
1000
- .modalBackdrop {
1001
- position: absolute;
1002
- inset: 0;
1003
- z-index: 70;
1004
- background: color-mix(in srgb, var(--dsw-alias-label-primary) 32%, transparent);
1005
- display: flex;
1006
- align-items: center;
1007
- justify-content: center;
1008
- }
1009
-
1010
- .picker {
1011
- width: min(720px, 92%);
1012
- max-height: 84%;
1013
- display: flex;
1014
- flex-direction: column;
1015
- border-radius: 14px;
1016
- border: 1px solid var(--dsw-alias-border-l1);
1017
- background: var(--dsw-alias-bg-layer-1);
1018
- box-shadow: 0 24px 60px rgb(15 23 42 / 26%);
1019
- overflow: hidden;
1020
- }
1021
-
1022
- .pickerHeader {
1023
- display: flex;
1024
- align-items: center;
1025
- justify-content: space-between;
1026
- padding: 12px 14px;
1027
- font-size: 14px;
1028
- border-bottom: 1px solid var(--dsw-alias-border-l1);
1029
- }
1030
-
1031
- .pickerHeader button {
1032
- border: none;
1033
- background: transparent;
1034
- color: var(--dsw-alias-label-secondary);
1035
- font-size: 18px;
1036
- cursor: pointer;
1037
- padding: 0 4px;
1038
- }
1039
-
1040
- .pickerTabs {
1041
- display: flex;
1042
- gap: 4px;
1043
- padding: 10px 14px 0;
1044
- }
1045
-
1046
- .pickerTabs button {
1047
- border: none;
1048
- border-radius: 8px 8px 0 0;
1049
- background: transparent;
1050
- color: var(--dsw-alias-label-secondary);
1051
- font-size: 12.5px;
1052
- padding: 7px 12px;
1053
- cursor: pointer;
1054
- }
1055
-
1056
- .pickerTabs button[data-active] {
1057
- background: var(--dsw-alias-interactive-bg-hover);
1058
- color: var(--dsw-alias-brand-primary);
1059
- }
1060
-
1061
- .pickerBody {
1062
- flex: 1;
1063
- min-height: 0;
1064
- overflow: auto;
1065
- padding: 14px;
1066
- }
1067
-
1068
- .pickerGrid {
1069
- display: grid;
1070
- grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
1071
- gap: 10px;
1072
- }
1073
-
1074
- .pickerCard {
1075
- display: flex;
1076
- flex-direction: column;
1077
- gap: 6px;
1078
- border: 1.5px solid var(--dsw-alias-border-l1);
1079
- border-radius: 10px;
1080
- background: transparent;
1081
- padding: 6px;
1082
- cursor: pointer;
1083
- text-align: left;
1084
- color: var(--dsw-alias-label-primary);
1085
- }
1086
-
1087
- .pickerCard[data-selected] {
1088
- border-color: var(--dsw-alias-brand-primary);
1089
- box-shadow: 0 0 0 2px color-mix(in srgb, var(--dsw-alias-brand-primary) 25%, transparent);
1090
- }
1091
-
1092
- .pickerCard img {
1093
- width: 100%;
1094
- height: 110px;
1095
- object-fit: cover;
1096
- border-radius: 7px;
1097
- }
1098
-
1099
- .pickerCardPrompt {
1100
- font-size: 11.5px;
1101
- overflow: hidden;
1102
- text-overflow: ellipsis;
1103
- white-space: nowrap;
1104
- }
1105
-
1106
- .pickerCard small {
1107
- color: var(--dsw-alias-label-tertiary);
1108
- font-size: 10.5px;
1109
- }
1110
-
1111
- .pickerFooter {
1112
- display: flex;
1113
- align-items: center;
1114
- justify-content: space-between;
1115
- padding-top: 12px;
1116
- font-size: 12.5px;
1117
- color: var(--dsw-alias-label-secondary);
1118
- }
1119
-
1120
- .pickerFooter button,
1121
- .generateForm button {
1122
- height: 32px;
1123
- padding: 0 16px;
1124
- border: none;
1125
- border-radius: 8px;
1126
- background: var(--dsw-alias-brand-primary);
1127
- color: var(--dsw-alias-bg-base, #fff);
1128
- font-size: 12.5px;
1129
- font-weight: 600;
1130
- cursor: pointer;
1131
- display: inline-flex;
1132
- align-items: center;
1133
- gap: 6px;
1134
- }
1135
-
1136
- .pickerFooter button:disabled,
1137
- .generateForm button:disabled { opacity: 0.45; cursor: not-allowed; }
1138
-
1139
- .uploadBox {
1140
- display: flex;
1141
- flex-direction: column;
1142
- align-items: center;
1143
- justify-content: center;
1144
- gap: 8px;
1145
- min-height: 220px;
1146
- border: 1.5px dashed color-mix(in srgb, var(--dsw-alias-label-primary) 22%, transparent);
1147
- border-radius: 12px;
1148
- color: var(--dsw-alias-label-secondary);
1149
- cursor: pointer;
1150
- text-align: center;
1151
- }
1152
-
1153
- .uploadBox:hover { border-color: var(--dsw-alias-brand-primary); }
1154
- .uploadBox input { display: none; }
1155
- .uploadBox small { color: var(--dsw-alias-label-tertiary); font-size: 11px; }
1156
-
1157
- .uploadIcon { color: var(--dsw-alias-brand-primary); }
1158
-
1159
- .generateForm {
1160
- display: flex;
1161
- flex-direction: column;
1162
- gap: 10px;
1163
- }
1164
-
1165
- .generateForm textarea {
1166
- min-height: 110px;
1167
- resize: vertical;
1168
- border-radius: 9px;
1169
- border: 1px solid var(--dsw-alias-border-l1);
1170
- background: transparent;
1171
- color: var(--dsw-alias-label-primary);
1172
- font-size: 12.5px;
1173
- padding: 8px 10px;
1174
- outline: none;
1175
- }
1176
-
1177
- .generateForm select {
1178
- height: 30px;
1179
- border-radius: 8px;
1180
- border: 1px solid var(--dsw-alias-border-l1);
1181
- background: transparent;
1182
- color: var(--dsw-alias-label-primary);
1183
- font-size: 12.5px;
1184
- padding: 0 8px;
1185
- }
1186
-
1187
- @keyframes canvasSpin {
1188
- to { transform: rotate(360deg); }
1189
- }
1
+ /* Infinite canvas workspace — node-graph canvas styled after
2
+ * basketikun/infinite-canvas: floating docks, card nodes, wire connections. */
3
+
4
+ .root {
5
+ position: relative;
6
+ display: flex;
7
+ flex: 1 1 auto;
8
+ flex-direction: column;
9
+ height: 100%;
10
+ min-width: 0;
11
+ min-height: 0;
12
+ overflow: hidden;
13
+ border: 1px solid var(--dsw-alias-border-l1);
14
+ border-radius: 10px;
15
+ background: var(--dsw-alias-bg-layer-1);
16
+ color: var(--dsw-alias-label-primary);
17
+ font-family: var(--dsw-font-family, inherit);
18
+ user-select: none;
19
+ }
20
+
21
+ .selectCursor { cursor: default; }
22
+ .panCursor { cursor: grab; }
23
+ .panCursor:active { cursor: grabbing; }
24
+
25
+ /* ------------------------------------------------------------- top bar */
26
+
27
+ .topBar {
28
+ display: flex;
29
+ align-items: center;
30
+ gap: 8px;
31
+ padding: 8px 12px;
32
+ border-bottom: 1px solid var(--dsw-alias-border-l1);
33
+ flex: none;
34
+ z-index: 40;
35
+ background: var(--dsw-alias-bg-layer-1);
36
+ }
37
+
38
+ .projectSelect {
39
+ max-width: 180px;
40
+ height: 30px;
41
+ padding: 0 8px;
42
+ border-radius: 8px;
43
+ border: 1px solid var(--dsw-alias-border-l1);
44
+ background: transparent;
45
+ color: var(--dsw-alias-label-primary);
46
+ font-size: 12.5px;
47
+ }
48
+
49
+ .titleButton {
50
+ height: 30px;
51
+ padding: 0 10px;
52
+ border: 1px solid transparent;
53
+ border-radius: 8px;
54
+ background: transparent;
55
+ color: var(--dsw-alias-label-primary);
56
+ font-size: 13px;
57
+ font-weight: 600;
58
+ max-width: 260px;
59
+ overflow: hidden;
60
+ text-overflow: ellipsis;
61
+ white-space: nowrap;
62
+ cursor: text;
63
+ }
64
+
65
+ .titleButton:hover { border-color: var(--dsw-alias-border-l1); }
66
+
67
+ .titleInput {
68
+ height: 30px;
69
+ padding: 0 10px;
70
+ border-radius: 8px;
71
+ border: 1px solid var(--dsw-alias-brand-primary);
72
+ background: transparent;
73
+ color: var(--dsw-alias-label-primary);
74
+ font-size: 13px;
75
+ font-weight: 600;
76
+ width: 240px;
77
+ outline: none;
78
+ }
79
+
80
+ .topBarSpacer { flex: 1; }
81
+
82
+ .saveState {
83
+ font-size: 11.5px;
84
+ color: var(--dsw-alias-label-tertiary);
85
+ white-space: nowrap;
86
+ }
87
+
88
+ .saveState[data-state="error"] { color: var(--dsw-alias-label-error); }
89
+ .saveState[data-state="saving"] { color: var(--dsw-alias-state-warn-primary, var(--dsw-alias-label-secondary)); }
90
+
91
+ /* ---------------------------------------------------------- icon button */
92
+
93
+ .iconButton {
94
+ display: inline-flex;
95
+ align-items: center;
96
+ justify-content: center;
97
+ gap: 5px;
98
+ width: 30px;
99
+ height: 30px;
100
+ flex: none;
101
+ border: none;
102
+ border-radius: 8px;
103
+ background: transparent;
104
+ color: var(--dsw-alias-label-secondary);
105
+ cursor: pointer;
106
+ padding: 0;
107
+ }
108
+
109
+ .iconButton:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }
110
+ .iconButton[data-active] { background: color-mix(in srgb, var(--dsw-alias-brand-primary) 16%, transparent); color: var(--dsw-alias-brand-primary); }
111
+ .iconButton:disabled { opacity: 0.35; cursor: not-allowed; }
112
+ .iconButton:disabled:hover { background: transparent; color: var(--dsw-alias-label-secondary); }
113
+
114
+ /* (node tools moved to the bottom-center dock above) */
115
+
116
+ /* ------------------------------------------------------------- viewport */
117
+
118
+ .viewport {
119
+ position: relative;
120
+ flex: 1;
121
+ min-height: 0;
122
+ overflow: hidden;
123
+ touch-action: none;
124
+ }
125
+
126
+ .grid {
127
+ position: absolute;
128
+ inset: 0;
129
+ pointer-events: none;
130
+ opacity: 0.5;
131
+ }
132
+
133
+ /* Interactive backdrops live in <canvas> elements (the fluid flowmap dot
134
+ * field, the WebGL fluid sim, and the reactbits-style shader backdrops);
135
+ * they render themselves at full strength (the 0.5 grid dimming stays off). */
136
+ .flowCanvas {
137
+ position: absolute;
138
+ inset: 0;
139
+ width: 100%;
140
+ height: 100%;
141
+ }
142
+
143
+ /* WebGL "liquid ether" fluid simulation, same placement as the flow canvas. */
144
+ .liquidCanvas {
145
+ position: absolute;
146
+ inset: 0;
147
+ width: 100%;
148
+ height: 100%;
149
+ }
150
+
151
+ /* Fullscreen-shader backdrops (floating lines / galaxy / silk) and the 2D
152
+ * wave lines share one placement; floating lines screen-blends over dark
153
+ * surfaces (set from JS, the light theme uses its own light backdrop). */
154
+ .sceneCanvas {
155
+ position: absolute;
156
+ inset: 0;
157
+ width: 100%;
158
+ height: 100%;
159
+ }
160
+
161
+ .grid[data-mode="flow"],
162
+ .grid[data-mode="liquid"],
163
+ .grid[data-mode="floatingLines"],
164
+ .grid[data-mode="galaxy"],
165
+ .grid[data-mode="silk"],
166
+ .grid[data-mode="waves"],
167
+ .grid[data-mode="faultyTerminal"],
168
+ .grid[data-mode="dotField"],
169
+ .grid[data-mode="dotGrid"],
170
+ .grid[data-mode="shapeGrid"] {
171
+ opacity: 1;
172
+ }
173
+
174
+ .grid[data-mode="dots"] {
175
+ background-image: radial-gradient(circle, color-mix(in srgb, var(--dsw-alias-label-primary) 30%, transparent) 1.1px, transparent 1.4px);
176
+ }
177
+
178
+ .grid[data-mode="lines"] {
179
+ background-image:
180
+ linear-gradient(color-mix(in srgb, var(--dsw-alias-label-primary) 9%, transparent) 1px, transparent 1px),
181
+ linear-gradient(90deg, color-mix(in srgb, var(--dsw-alias-label-primary) 9%, transparent) 1px, transparent 1px);
182
+ }
183
+
184
+ .grid[data-mode="blank"] { background-image: none; }
185
+
186
+ .grid[data-mode="image"] { background-image: none; }
187
+
188
+ .gridScrim {
189
+ position: absolute;
190
+ inset: 0;
191
+ background: var(--dsw-alias-bg-layer-1);
192
+ opacity: 0.6;
193
+ }
194
+
195
+ /* ------------------------------------------------ background menu */
196
+
197
+ .backgroundMenu {
198
+ position: absolute;
199
+ transform: translate(-50%, -100%);
200
+ z-index: 45;
201
+ min-width: 168px;
202
+ padding: 5px;
203
+ border-radius: 11px;
204
+ border: 1px solid var(--dsw-alias-border-l1);
205
+ background: color-mix(in srgb, var(--dsw-alias-bg-layer-1) 96%, transparent);
206
+ box-shadow: 0 16px 40px rgb(15 23 42 / 20%);
207
+ display: flex;
208
+ flex-direction: column;
209
+ }
210
+
211
+ .backgroundMenu button {
212
+ display: flex;
213
+ align-items: center;
214
+ justify-content: flex-start;
215
+ border: none;
216
+ border-radius: 7px;
217
+ background: transparent;
218
+ color: var(--dsw-alias-label-primary);
219
+ font-size: 12.5px;
220
+ padding: 7px 10px;
221
+ cursor: pointer;
222
+ text-align: left;
223
+ }
224
+
225
+ .backgroundMenu button:hover { background: var(--dsw-alias-interactive-bg-hover); }
226
+
227
+ .backgroundMenu button[data-active] {
228
+ color: var(--dsw-alias-brand-primary);
229
+ background: color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent);
230
+ }
231
+
232
+ .backgroundMenuDivider {
233
+ height: 1px;
234
+ margin: 4px 6px;
235
+ background: var(--dsw-alias-border-l1);
236
+ }
237
+
238
+ .world {
239
+ position: absolute;
240
+ left: 0;
241
+ top: 0;
242
+ transform-origin: 0 0;
243
+ }
244
+
245
+ .marquee {
246
+ position: absolute;
247
+ z-index: 20;
248
+ border: 1px solid var(--dsw-alias-brand-primary);
249
+ background: color-mix(in srgb, var(--dsw-alias-brand-primary) 8%, transparent);
250
+ border-radius: 4px;
251
+ pointer-events: none;
252
+ }
253
+
254
+ /* ------------------------------------------------------------ nodes */
255
+
256
+ .node {
257
+ position: absolute;
258
+ display: flex;
259
+ flex-direction: column;
260
+ border-radius: 14px;
261
+ border: 1.5px solid var(--dsw-alias-border-l1);
262
+ background: var(--dsw-alias-bg-layer-1);
263
+ box-shadow: 0 8px 24px rgb(15 23 42 / 12%);
264
+ overflow: visible;
265
+ cursor: grab;
266
+ touch-action: none;
267
+ }
268
+
269
+ .node:active { cursor: grabbing; }
270
+
271
+ .nodeGlow {
272
+ position: absolute;
273
+ inset: -6px;
274
+ border-radius: 18px;
275
+ pointer-events: none;
276
+ opacity: 0;
277
+ transition: opacity 0.15s ease;
278
+ }
279
+
280
+ .nodeSelected {
281
+ border-color: var(--dsw-alias-brand-primary);
282
+ z-index: 2;
283
+ }
284
+
285
+ .nodeSelected .nodeGlow {
286
+ opacity: 1;
287
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--dsw-alias-brand-primary) 22%, transparent);
288
+ }
289
+
290
+ .nodeRelated { border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, transparent); }
291
+
292
+ .nodeConnectTarget {
293
+ border-color: var(--dsw-alias-brand-primary);
294
+ box-shadow: 0 0 0 4px color-mix(in srgb, var(--dsw-alias-brand-primary) 26%, transparent);
295
+ }
296
+
297
+ .nodeHeader {
298
+ display: flex;
299
+ align-items: center;
300
+ gap: 6px;
301
+ padding: 7px 12px 5px;
302
+ font-size: 11.5px;
303
+ color: var(--dsw-alias-label-secondary);
304
+ cursor: inherit;
305
+ flex: none;
306
+ }
307
+
308
+ .nodeTitle {
309
+ font-weight: 600;
310
+ overflow: hidden;
311
+ text-overflow: ellipsis;
312
+ white-space: nowrap;
313
+ }
314
+
315
+ .nodeSize {
316
+ margin-left: auto;
317
+ font-size: 10.5px;
318
+ color: var(--dsw-alias-label-tertiary);
319
+ font-variant-numeric: tabular-nums;
320
+ }
321
+
322
+ .nodeSpinner {
323
+ width: 11px;
324
+ height: 11px;
325
+ flex: none;
326
+ border-radius: 50%;
327
+ border: 1.5px solid color-mix(in srgb, var(--dsw-alias-brand-primary) 30%, transparent);
328
+ border-top-color: var(--dsw-alias-brand-primary);
329
+ animation: canvasSpin 0.8s linear infinite;
330
+ }
331
+
332
+ .nodeBody {
333
+ flex: 1;
334
+ min-height: 0;
335
+ margin: 0 8px 8px;
336
+ border-radius: 9px;
337
+ overflow: hidden;
338
+ background: color-mix(in srgb, var(--dsw-alias-label-primary) 6%, transparent);
339
+ display: flex;
340
+ align-items: center;
341
+ justify-content: center;
342
+ pointer-events: none;
343
+ }
344
+
345
+ .nodeBody img {
346
+ width: 100%;
347
+ height: 100%;
348
+ object-fit: contain;
349
+ display: block;
350
+ }
351
+
352
+ .imageInfo {
353
+ position: absolute;
354
+ top: 6px;
355
+ z-index: 2;
356
+ padding: 3px 8px;
357
+ border-radius: 6px;
358
+ background: color-mix(in srgb, var(--dsw-alias-label-primary) 62%, transparent);
359
+ color: var(--dsw-alias-bg-layer-1);
360
+ font-size: 10.5px;
361
+ line-height: 1.4;
362
+ pointer-events: none;
363
+ backdrop-filter: blur(3px);
364
+ max-width: calc(100% - 90px);
365
+ overflow: hidden;
366
+ text-overflow: ellipsis;
367
+ white-space: nowrap;
368
+ }
369
+
370
+ .imageInfoLeft { left: 6px; font-weight: 600; }
371
+
372
+ .imageInfoRight {
373
+ right: 6px;
374
+ left: auto;
375
+ font-variant-numeric: tabular-nums;
376
+ opacity: 0.85;
377
+ }
378
+
379
+ .nodeState,
380
+ .nodeStateError {
381
+ display: flex;
382
+ flex-direction: column;
383
+ align-items: center;
384
+ gap: 8px;
385
+ font-size: 12px;
386
+ color: var(--dsw-alias-label-secondary);
387
+ padding: 12px;
388
+ text-align: center;
389
+ pointer-events: auto;
390
+ }
391
+
392
+ .nodeStateError { color: var(--dsw-alias-label-error); }
393
+
394
+ .nodeStateError button {
395
+ border: none;
396
+ border-radius: 7px;
397
+ padding: 4px 12px;
398
+ background: var(--dsw-alias-interactive-bg-hover);
399
+ color: var(--dsw-alias-label-primary);
400
+ font-size: 12px;
401
+ cursor: pointer;
402
+ }
403
+
404
+ .nodeEmpty {
405
+ display: flex;
406
+ flex-direction: column;
407
+ align-items: center;
408
+ gap: 8px;
409
+ border: 1.5px dashed color-mix(in srgb, var(--dsw-alias-label-primary) 24%, transparent);
410
+ border-radius: 9px;
411
+ margin: 6px;
412
+ padding: 14px;
413
+ background: transparent;
414
+ color: var(--dsw-alias-label-tertiary);
415
+ font-size: 12px;
416
+ cursor: pointer;
417
+ /* .nodeBody disables pointer events for node dragging; the upload button
418
+ * inside must opt back in or its onClick never fires. */
419
+ pointer-events: auto;
420
+ }
421
+
422
+ .nodeEmpty:hover {
423
+ border-color: var(--dsw-alias-brand-primary);
424
+ color: var(--dsw-alias-brand-primary);
425
+ }
426
+
427
+ .textNode { cursor: default; }
428
+
429
+ /* image nodes render as bare cards: the picture fills the frame */
430
+ .imageNode .nodeBody { margin: 6px; }
431
+
432
+ .textArea {
433
+ flex: 1;
434
+ min-height: 0;
435
+ margin: 0 8px 8px;
436
+ border: none;
437
+ resize: none;
438
+ outline: none;
439
+ background: transparent;
440
+ color: var(--dsw-alias-label-primary);
441
+ font-size: 13px;
442
+ line-height: 1.55;
443
+ padding: 4px 6px;
444
+ cursor: text;
445
+ pointer-events: auto;
446
+ }
447
+
448
+ .textArea::placeholder { color: var(--dsw-alias-label-tertiary); }
449
+
450
+ /* --------------------------------------------------- bottom dock */
451
+
452
+ /* reactbits.dev "Dock" port: dark pill panel; a spring engine (see the dock
453
+ * effect in CanvasWorkspace) scales each tile by pointer distance so items
454
+ * part apart like the macOS dock, and the outer wrapper breathes taller on
455
+ * hover to make room for the floating labels. */
456
+ .dockOuter {
457
+ position: absolute;
458
+ left: 0;
459
+ right: 0;
460
+ bottom: 14px;
461
+ z-index: 30;
462
+ display: flex;
463
+ justify-content: center;
464
+ align-items: flex-end;
465
+ height: 42px;
466
+ pointer-events: none;
467
+ }
468
+
469
+ .dock {
470
+ pointer-events: auto;
471
+ position: relative;
472
+ display: flex;
473
+ align-items: flex-end;
474
+ gap: 6px;
475
+ padding: 0 7px 7px;
476
+ border-radius: 15px;
477
+ border: 1px solid var(--dsw-alias-border-l1);
478
+ background: color-mix(in srgb, var(--dsw-alias-bg-layer-1) 92%, transparent);
479
+ backdrop-filter: blur(10px);
480
+ box-shadow: 0 14px 34px rgb(15 23 42 / 16%);
481
+ }
482
+
483
+ .dockItem {
484
+ position: relative;
485
+ display: inline-flex;
486
+ align-items: center;
487
+ justify-content: center;
488
+ width: 34px;
489
+ height: 34px;
490
+ border-radius: 10px;
491
+ cursor: pointer;
492
+ }
493
+
494
+ .dockItem:hover { background: var(--dsw-alias-interactive-bg-hover); }
495
+
496
+ .dockItem::after {
497
+ content: attr(data-label);
498
+ position: absolute;
499
+ top: -26px;
500
+ left: 50%;
501
+ transform: translateX(-50%);
502
+ padding: 2px 8px;
503
+ border-radius: 6px;
504
+ border: 1px solid var(--dsw-alias-border-l1);
505
+ background: var(--dsw-alias-bg-layer-1);
506
+ color: var(--dsw-alias-label-primary);
507
+ font-size: 11px;
508
+ line-height: 16px;
509
+ white-space: nowrap;
510
+ opacity: 0;
511
+ transition: opacity 0.15s ease;
512
+ pointer-events: none;
513
+ }
514
+
515
+ .dockItem:hover::after { opacity: 1; }
516
+
517
+ .dock .iconButton {
518
+ width: 100%;
519
+ height: 100%;
520
+ border-radius: 10px;
521
+ }
522
+
523
+ .dock .iconButton:not(:disabled):hover {
524
+ background: transparent;
525
+ }
526
+
527
+ .dock .iconButton[data-active] {
528
+ background: var(--dsw-alias-label-primary);
529
+ color: var(--dsw-alias-bg-layer-1);
530
+ }
531
+
532
+ .dock .iconButton[data-active]:hover { background: var(--dsw-alias-label-primary); color: var(--dsw-alias-bg-layer-1); }
533
+
534
+ .dock .iconButton:disabled { opacity: 0.3; cursor: not-allowed; }
535
+ .dock .iconButton:disabled:hover { background: transparent; color: var(--dsw-alias-label-secondary); }
536
+
537
+ .dockDivider {
538
+ align-self: center;
539
+ width: 1px;
540
+ height: 18px;
541
+ background: var(--dsw-alias-border-l1);
542
+ }
543
+
544
+ /* ------------------------------------------------- floating composer */
545
+
546
+ .composer {
547
+ position: absolute;
548
+ z-index: 35;
549
+ width: 560px;
550
+ display: flex;
551
+ flex-direction: column;
552
+ gap: 8px;
553
+ padding: 10px;
554
+ border-radius: 14px;
555
+ border: 1px solid var(--dsw-alias-border-l1);
556
+ background: color-mix(in srgb, var(--dsw-alias-bg-layer-1) 96%, transparent);
557
+ box-shadow: 0 16px 44px rgb(15 23 42 / 18%);
558
+ backdrop-filter: blur(10px);
559
+ }
560
+
561
+ .composerPrompt {
562
+ width: 100%;
563
+ min-height: 38px;
564
+ max-height: 120px;
565
+ resize: none;
566
+ border: none;
567
+ outline: none;
568
+ background: transparent;
569
+ color: var(--dsw-alias-label-primary);
570
+ font-size: 13px;
571
+ line-height: 1.5;
572
+ padding: 2px 4px;
573
+ cursor: text;
574
+ }
575
+
576
+ .composerPrompt::placeholder { color: var(--dsw-alias-label-tertiary); }
577
+
578
+ .composerMeta { display: flex; }
579
+
580
+ .composerChip {
581
+ display: inline-flex;
582
+ align-items: center;
583
+ gap: 5px;
584
+ font-size: 11px;
585
+ padding: 2px 9px;
586
+ border-radius: 999px;
587
+ border: 1px solid color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, transparent);
588
+ color: var(--dsw-alias-brand-primary);
589
+ background: color-mix(in srgb, var(--dsw-alias-brand-primary) 8%, transparent);
590
+ }
591
+
592
+ .composerControls {
593
+ display: flex;
594
+ align-items: center;
595
+ gap: 6px;
596
+ }
597
+
598
+ .composerControls select {
599
+ height: 28px;
600
+ border-radius: 999px;
601
+ border: 1px solid var(--dsw-alias-border-l1);
602
+ background: transparent;
603
+ color: var(--dsw-alias-label-primary);
604
+ font-size: 11.5px;
605
+ padding: 0 8px;
606
+ min-width: 0;
607
+ max-width: 128px;
608
+ }
609
+
610
+ /* custom dropdown button + popup (styled replacement for native <select>) */
611
+ .composerSelect {
612
+ display: inline-flex;
613
+ align-items: center;
614
+ gap: 6px;
615
+ height: 28px;
616
+ padding: 0 8px 0 11px;
617
+ border-radius: 999px;
618
+ border: 1px solid var(--dsw-alias-border-l1);
619
+ background: transparent;
620
+ color: var(--dsw-alias-label-primary);
621
+ font-size: 11.5px;
622
+ min-width: 0;
623
+ max-width: 138px;
624
+ cursor: pointer;
625
+ transition: background 0.15s ease, border-color 0.15s ease;
626
+ }
627
+
628
+ .composerSelect:hover { background: var(--dsw-alias-interactive-bg-hover); }
629
+ .composerSelect[data-open] { border-color: var(--dsw-alias-brand-primary); color: var(--dsw-alias-brand-primary); }
630
+ .composerSelect svg { flex: none; opacity: 0.65; }
631
+ .composerSelect[data-open] svg { opacity: 0.9; }
632
+
633
+ .composerSelectValue {
634
+ overflow: hidden;
635
+ text-overflow: ellipsis;
636
+ white-space: nowrap;
637
+ min-width: 0;
638
+ }
639
+
640
+ .composerSelectMenu {
641
+ position: fixed;
642
+ z-index: 95;
643
+ max-height: 264px;
644
+ overflow: auto;
645
+ padding: 5px;
646
+ border-radius: 11px;
647
+ border: 1px solid var(--dsw-alias-border-l1);
648
+ background: color-mix(in srgb, var(--dsw-alias-bg-layer-1) 97%, transparent);
649
+ box-shadow: 0 14px 38px rgb(15 23 42 / 18%);
650
+ backdrop-filter: blur(10px);
651
+ }
652
+
653
+ .composerSelectMenu button {
654
+ display: block;
655
+ width: 100%;
656
+ border: none;
657
+ border-radius: 7px;
658
+ background: transparent;
659
+ color: var(--dsw-alias-label-primary);
660
+ font-size: 12.5px;
661
+ line-height: 1.45;
662
+ padding: 7px 10px;
663
+ cursor: pointer;
664
+ text-align: left;
665
+ white-space: nowrap;
666
+ }
667
+
668
+ .composerSelectMenu button:hover { background: var(--dsw-alias-interactive-bg-hover); }
669
+ .composerSelectMenu button[data-selected] {
670
+ color: var(--dsw-alias-brand-primary);
671
+ background: color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent);
672
+ }
673
+
674
+ .composerSend {
675
+ margin-left: auto;
676
+ width: 34px;
677
+ height: 34px;
678
+ flex: none;
679
+ border: none;
680
+ border-radius: 50%;
681
+ background: var(--dsw-alias-label-primary);
682
+ color: var(--dsw-alias-bg-layer-1);
683
+ cursor: pointer;
684
+ display: inline-flex;
685
+ align-items: center;
686
+ justify-content: center;
687
+ }
688
+
689
+ .composerSend:hover { opacity: 0.88; }
690
+ .composerSend:disabled { opacity: 0.35; cursor: not-allowed; }
691
+
692
+ /* ------------------------------------------------------ config node */
693
+
694
+ .configNode {
695
+ padding: 0 14px 14px;
696
+ cursor: grab;
697
+ }
698
+
699
+ .configNode .nodeHeader { padding: 10px 2px 6px; font-size: 12px; }
700
+
701
+ .configLinks {
702
+ display: flex;
703
+ align-items: center;
704
+ margin: 4px 0 10px;
705
+ flex: none;
706
+ }
707
+
708
+ .configLinks .composerChip {
709
+ font-size: 11px;
710
+ padding: 3px 10px;
711
+ }
712
+
713
+ .configHint {
714
+ flex: 1;
715
+ min-height: 0;
716
+ margin: 0;
717
+ font-size: 11.5px;
718
+ line-height: 1.7;
719
+ color: var(--dsw-alias-label-tertiary);
720
+ overflow: auto;
721
+ }
722
+
723
+ /* connection handles */
724
+
725
+ .handle {
726
+ position: absolute;
727
+ top: 50%;
728
+ width: 12px;
729
+ height: 12px;
730
+ border-radius: 50%;
731
+ transform: translateY(-50%) scale(0.6);
732
+ background: var(--dsw-alias-bg-layer-1);
733
+ border: 2px solid var(--dsw-alias-brand-primary);
734
+ opacity: 0;
735
+ transition: opacity 0.12s ease, transform 0.12s ease;
736
+ cursor: crosshair;
737
+ z-index: 3;
738
+ }
739
+
740
+ .handleLeft { left: -7px; }
741
+ .handleRight { right: -7px; }
742
+
743
+ .node:hover .handle,
744
+ .nodeSelected .handle,
745
+ .nodeConnectTarget .handle {
746
+ opacity: 1;
747
+ transform: translateY(-50%) scale(1);
748
+ }
749
+
750
+ .handle:hover { background: var(--dsw-alias-brand-primary); }
751
+
752
+ /* The source handle doubles as a quick-add button (hover opens the node
753
+ * picker, drag connects) so it reads as a Dify-style big ⊕. */
754
+ .handleRight {
755
+ display: flex;
756
+ align-items: center;
757
+ justify-content: center;
758
+ width: 20px;
759
+ height: 20px;
760
+ right: -10px;
761
+ font-size: 13px;
762
+ font-weight: 600;
763
+ line-height: 1;
764
+ color: var(--dsw-alias-brand-primary);
765
+ }
766
+
767
+ .handleRight::after { content: '+'; }
768
+
769
+ .handleRight:hover { color: var(--dsw-alias-bg-layer-1); transform: translateY(-50%) scale(1.12); }
770
+
771
+ .resizeHandle {
772
+ position: absolute;
773
+ right: -5px;
774
+ bottom: -5px;
775
+ width: 14px;
776
+ height: 14px;
777
+ border-radius: 4px;
778
+ background: var(--dsw-alias-brand-primary);
779
+ border: 2px solid var(--dsw-alias-bg-layer-1);
780
+ cursor: nwse-resize;
781
+ z-index: 3;
782
+ }
783
+
784
+ /* hover toolbar */
785
+
786
+ .hoverToolbar {
787
+ position: absolute;
788
+ top: -36px;
789
+ left: 50%;
790
+ transform: translateX(-50%);
791
+ display: flex;
792
+ gap: 2px;
793
+ padding: 4px;
794
+ border-radius: 10px;
795
+ border: 1px solid var(--dsw-alias-border-l1);
796
+ background: color-mix(in srgb, var(--dsw-alias-bg-layer-1) 92%, transparent);
797
+ box-shadow: 0 8px 22px rgb(15 23 42 / 14%);
798
+ opacity: 0;
799
+ pointer-events: none;
800
+ transition: opacity 0.12s ease;
801
+ z-index: 4;
802
+ }
803
+
804
+ .node:hover .hoverToolbar,
805
+ .nodeSelected .hoverToolbar {
806
+ opacity: 1;
807
+ pointer-events: auto;
808
+ }
809
+
810
+ /* -------------------------------------------------------- connections */
811
+
812
+ .connectionLayer {
813
+ position: absolute;
814
+ overflow: visible;
815
+ pointer-events: none;
816
+ z-index: 0;
817
+ }
818
+
819
+ .connectionPath {
820
+ fill: none;
821
+ stroke-width: 1.8;
822
+ stroke-opacity: 0.9;
823
+ pointer-events: none;
824
+ }
825
+
826
+ .connectionActive {
827
+ stroke-width: 2.6;
828
+ stroke-opacity: 1;
829
+ filter: drop-shadow(0 0 6px color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, transparent));
830
+ }
831
+
832
+ .connectionPreview {
833
+ fill: none;
834
+ stroke: var(--dsw-alias-brand-primary);
835
+ stroke-width: 2;
836
+ stroke-dasharray: 5 5;
837
+ pointer-events: none;
838
+ }
839
+
840
+ /* Flow overlay: a soft light band glides from the path start (source) toward
841
+ * its end (target), echoing the direction of the gradient and arrowhead. */
842
+ .connectionFlow {
843
+ fill: none;
844
+ stroke: var(--dsw-alias-brand-primary);
845
+ stroke-width: 2;
846
+ stroke-linecap: round;
847
+ stroke-dasharray: 30 90;
848
+ opacity: 0.4;
849
+ pointer-events: none;
850
+ animation: connectionFlow 1.6s linear infinite;
851
+ }
852
+
853
+ .connectionFlowActive {
854
+ stroke: var(--dsw-alias-brand-primary);
855
+ opacity: 0.85;
856
+ }
857
+
858
+ @keyframes connectionFlow {
859
+ to { stroke-dashoffset: -120; }
860
+ }
861
+
862
+ @media (prefers-reduced-motion: reduce) {
863
+ .connectionFlow { animation: none; opacity: 0.35; }
864
+ }
865
+
866
+ /* --------------------------------------------------------- empty hint */
867
+
868
+ .emptyHint {
869
+ position: absolute;
870
+ left: 50%;
871
+ top: 44%;
872
+ transform: translate(-50%, -50%);
873
+ display: flex;
874
+ flex-direction: column;
875
+ align-items: center;
876
+ gap: 6px;
877
+ text-align: center;
878
+ color: var(--dsw-alias-label-tertiary);
879
+ pointer-events: none;
880
+ z-index: 5;
881
+ }
882
+
883
+ .emptyHint strong {
884
+ font-size: 16px;
885
+ color: var(--dsw-alias-label-secondary);
886
+ }
887
+
888
+ .emptyHint span { font-size: 12.5px; max-width: 320px; line-height: 1.6; }
889
+
890
+ /* ------------------------------------------------------------ zoom dock */
891
+
892
+ .zoomDock {
893
+ position: absolute;
894
+ left: 14px;
895
+ bottom: 14px;
896
+ z-index: 30;
897
+ display: flex;
898
+ align-items: center;
899
+ gap: 6px;
900
+ padding: 6px 8px;
901
+ border-radius: 12px;
902
+ border: 1px solid var(--dsw-alias-border-l1);
903
+ background: color-mix(in srgb, var(--dsw-alias-bg-layer-1) 88%, transparent);
904
+ box-shadow: 0 10px 30px rgb(15 23 42 / 12%);
905
+ backdrop-filter: blur(10px);
906
+ }
907
+
908
+ .zoomDock input[type="range"] {
909
+ width: 96px;
910
+ accent-color: var(--dsw-alias-brand-primary);
911
+ }
912
+
913
+ .zoomValue {
914
+ width: 40px;
915
+ text-align: right;
916
+ font-size: 11.5px;
917
+ color: var(--dsw-alias-label-secondary);
918
+ font-variant-numeric: tabular-nums;
919
+ }
920
+
921
+ /* -------------------------------------------------------------- minimap */
922
+
923
+ .minimap {
924
+ position: absolute;
925
+ left: 14px;
926
+ bottom: 62px;
927
+ z-index: 30;
928
+ width: 220px;
929
+ height: 150px;
930
+ border-radius: 12px;
931
+ border: 1px solid var(--dsw-alias-border-l1);
932
+ background: color-mix(in srgb, var(--dsw-alias-bg-layer-1) 88%, transparent);
933
+ box-shadow: 0 10px 30px rgb(15 23 42 / 12%);
934
+ overflow: hidden;
935
+ backdrop-filter: blur(10px);
936
+ }
937
+
938
+ .minimapCanvas {
939
+ position: relative;
940
+ width: 100%;
941
+ height: 100%;
942
+ cursor: crosshair;
943
+ }
944
+
945
+ .minimapNode {
946
+ position: absolute;
947
+ border-radius: 2px;
948
+ opacity: 0.8;
949
+ }
950
+
951
+ .minimapImage { background: var(--dsw-alias-brand-primary); }
952
+ .minimapText { background: color-mix(in srgb, var(--dsw-alias-label-primary) 40%, transparent); }
953
+ .minimapSelected { outline: 1.5px solid var(--dsw-alias-brand-primary); }
954
+
955
+ .minimapViewport {
956
+ position: absolute;
957
+ border: 1.5px solid var(--dsw-alias-brand-primary);
958
+ background: color-mix(in srgb, var(--dsw-alias-brand-primary) 10%, transparent);
959
+ border-radius: 2px;
960
+ pointer-events: none;
961
+ }
962
+
963
+ /* ------------------------------------------------------------ inspector */
964
+
965
+ .inspectorRow {
966
+ display: flex;
967
+ gap: 8px;
968
+ }
969
+
970
+ .inspectorRow > * { flex: 1; min-width: 0; }
971
+
972
+ /* --------------------------------------------------------- context menu */
973
+
974
+ .contextMenu {
975
+ position: fixed;
976
+ z-index: 90;
977
+ min-width: 176px;
978
+ padding: 5px;
979
+ border-radius: 11px;
980
+ border: 1px solid var(--dsw-alias-border-l1);
981
+ background: color-mix(in srgb, var(--dsw-alias-bg-layer-1) 96%, transparent);
982
+ box-shadow: 0 16px 40px rgb(15 23 42 / 20%);
983
+ display: flex;
984
+ flex-direction: column;
985
+ }
986
+
987
+ .contextMenu button {
988
+ display: flex;
989
+ align-items: center;
990
+ gap: 8px;
991
+ border: none;
992
+ border-radius: 7px;
993
+ background: transparent;
994
+ color: var(--dsw-alias-label-primary);
995
+ font-size: 12.5px;
996
+ padding: 7px 10px;
997
+ cursor: pointer;
998
+ text-align: left;
999
+ }
1000
+
1001
+ .contextMenu button:hover { background: var(--dsw-alias-interactive-bg-hover); }
1002
+ .contextMenu button[data-danger] { color: var(--dsw-alias-label-error); }
1003
+
1004
+ /* ---------------------------------------------------------- error toast */
1005
+
1006
+ .errorToast {
1007
+ position: absolute;
1008
+ left: 50%;
1009
+ bottom: 18px;
1010
+ transform: translateX(-50%);
1011
+ z-index: 60;
1012
+ display: flex;
1013
+ align-items: center;
1014
+ gap: 10px;
1015
+ max-width: min(560px, 80%);
1016
+ padding: 9px 12px;
1017
+ border-radius: 10px;
1018
+ border: 1px solid color-mix(in srgb, var(--dsw-alias-label-error) 35%, transparent);
1019
+ background: color-mix(in srgb, var(--dsw-alias-label-error) 12%, var(--dsw-alias-bg-layer-1));
1020
+ color: var(--dsw-alias-label-error);
1021
+ font-size: 12.5px;
1022
+ box-shadow: 0 12px 30px rgb(15 23 42 / 18%);
1023
+ }
1024
+
1025
+ .errorToast button {
1026
+ border: none;
1027
+ background: transparent;
1028
+ color: inherit;
1029
+ cursor: pointer;
1030
+ padding: 2px;
1031
+ display: inline-flex;
1032
+ flex: none;
1033
+ }
1034
+
1035
+ /* ---------------------------------------------------------- picker modal */
1036
+
1037
+ .modalBackdrop {
1038
+ position: absolute;
1039
+ inset: 0;
1040
+ z-index: 70;
1041
+ background: color-mix(in srgb, var(--dsw-alias-label-primary) 32%, transparent);
1042
+ display: flex;
1043
+ align-items: center;
1044
+ justify-content: center;
1045
+ }
1046
+
1047
+ .picker {
1048
+ width: min(720px, 92%);
1049
+ max-height: 84%;
1050
+ display: flex;
1051
+ flex-direction: column;
1052
+ border-radius: 14px;
1053
+ border: 1px solid var(--dsw-alias-border-l1);
1054
+ background: var(--dsw-alias-bg-layer-1);
1055
+ box-shadow: 0 24px 60px rgb(15 23 42 / 26%);
1056
+ overflow: hidden;
1057
+ }
1058
+
1059
+ .pickerHeader {
1060
+ display: flex;
1061
+ align-items: center;
1062
+ justify-content: space-between;
1063
+ padding: 12px 14px;
1064
+ font-size: 14px;
1065
+ border-bottom: 1px solid var(--dsw-alias-border-l1);
1066
+ }
1067
+
1068
+ .pickerHeader button {
1069
+ border: none;
1070
+ background: transparent;
1071
+ color: var(--dsw-alias-label-secondary);
1072
+ font-size: 18px;
1073
+ cursor: pointer;
1074
+ padding: 0 4px;
1075
+ }
1076
+
1077
+ .pickerTabs {
1078
+ display: flex;
1079
+ gap: 4px;
1080
+ padding: 10px 14px 0;
1081
+ }
1082
+
1083
+ .pickerTabs button {
1084
+ border: none;
1085
+ border-radius: 8px 8px 0 0;
1086
+ background: transparent;
1087
+ color: var(--dsw-alias-label-secondary);
1088
+ font-size: 12.5px;
1089
+ padding: 7px 12px;
1090
+ cursor: pointer;
1091
+ }
1092
+
1093
+ .pickerTabs button[data-active] {
1094
+ background: var(--dsw-alias-interactive-bg-hover);
1095
+ color: var(--dsw-alias-brand-primary);
1096
+ }
1097
+
1098
+ .pickerBody {
1099
+ flex: 1;
1100
+ min-height: 0;
1101
+ overflow: auto;
1102
+ padding: 14px;
1103
+ }
1104
+
1105
+ .pickerGrid {
1106
+ display: grid;
1107
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
1108
+ gap: 10px;
1109
+ }
1110
+
1111
+ .pickerCard {
1112
+ display: flex;
1113
+ flex-direction: column;
1114
+ gap: 6px;
1115
+ border: 1.5px solid var(--dsw-alias-border-l1);
1116
+ border-radius: 10px;
1117
+ background: transparent;
1118
+ padding: 6px;
1119
+ cursor: pointer;
1120
+ text-align: left;
1121
+ color: var(--dsw-alias-label-primary);
1122
+ }
1123
+
1124
+ .pickerCard[data-selected] {
1125
+ border-color: var(--dsw-alias-brand-primary);
1126
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--dsw-alias-brand-primary) 25%, transparent);
1127
+ }
1128
+
1129
+ .pickerCard img {
1130
+ width: 100%;
1131
+ height: 110px;
1132
+ object-fit: cover;
1133
+ border-radius: 7px;
1134
+ }
1135
+
1136
+ .pickerCardPrompt {
1137
+ font-size: 11.5px;
1138
+ overflow: hidden;
1139
+ text-overflow: ellipsis;
1140
+ white-space: nowrap;
1141
+ }
1142
+
1143
+ .pickerCard small {
1144
+ color: var(--dsw-alias-label-tertiary);
1145
+ font-size: 10.5px;
1146
+ }
1147
+
1148
+ .pickerFooter {
1149
+ display: flex;
1150
+ align-items: center;
1151
+ justify-content: space-between;
1152
+ padding-top: 12px;
1153
+ font-size: 12.5px;
1154
+ color: var(--dsw-alias-label-secondary);
1155
+ }
1156
+
1157
+ .pickerFooter button,
1158
+ .generateForm button {
1159
+ height: 32px;
1160
+ padding: 0 16px;
1161
+ border: none;
1162
+ border-radius: 8px;
1163
+ background: var(--dsw-alias-brand-primary);
1164
+ color: var(--dsw-alias-bg-base, #fff);
1165
+ font-size: 12.5px;
1166
+ font-weight: 600;
1167
+ cursor: pointer;
1168
+ display: inline-flex;
1169
+ align-items: center;
1170
+ gap: 6px;
1171
+ }
1172
+
1173
+ .pickerFooter button:disabled,
1174
+ .generateForm button:disabled { opacity: 0.45; cursor: not-allowed; }
1175
+
1176
+ .uploadBox {
1177
+ display: flex;
1178
+ flex-direction: column;
1179
+ align-items: center;
1180
+ justify-content: center;
1181
+ gap: 8px;
1182
+ min-height: 220px;
1183
+ border: 1.5px dashed color-mix(in srgb, var(--dsw-alias-label-primary) 22%, transparent);
1184
+ border-radius: 12px;
1185
+ color: var(--dsw-alias-label-secondary);
1186
+ cursor: pointer;
1187
+ text-align: center;
1188
+ }
1189
+
1190
+ .uploadBox:hover { border-color: var(--dsw-alias-brand-primary); }
1191
+ .uploadBox input { display: none; }
1192
+ .uploadBox small { color: var(--dsw-alias-label-tertiary); font-size: 11px; }
1193
+
1194
+ .uploadIcon { color: var(--dsw-alias-brand-primary); }
1195
+
1196
+ .generateForm {
1197
+ display: flex;
1198
+ flex-direction: column;
1199
+ gap: 10px;
1200
+ }
1201
+
1202
+ .generateForm textarea {
1203
+ min-height: 110px;
1204
+ resize: vertical;
1205
+ border-radius: 9px;
1206
+ border: 1px solid var(--dsw-alias-border-l1);
1207
+ background: transparent;
1208
+ color: var(--dsw-alias-label-primary);
1209
+ font-size: 12.5px;
1210
+ padding: 8px 10px;
1211
+ outline: none;
1212
+ }
1213
+
1214
+ .generateForm select {
1215
+ height: 30px;
1216
+ border-radius: 8px;
1217
+ border: 1px solid var(--dsw-alias-border-l1);
1218
+ background: transparent;
1219
+ color: var(--dsw-alias-label-primary);
1220
+ font-size: 12.5px;
1221
+ padding: 0 8px;
1222
+ }
1223
+
1224
+ @keyframes canvasSpin {
1225
+ to { transform: rotate(360deg); }
1226
+ }