@waterwx/dsh-novel-forge 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +180 -0
  3. package/cordis.patch.yml +13 -0
  4. package/lib/client.js +3924 -0
  5. package/lib/client.js.map +1 -0
  6. package/lib/index.js +3120 -0
  7. package/lib/index.js.map +1 -0
  8. package/lib/types/assets.d.ts +35 -0
  9. package/lib/types/assistant.d.ts +43 -0
  10. package/lib/types/bookshelf.d.ts +35 -0
  11. package/lib/types/client/api.d.ts +68 -0
  12. package/lib/types/client/docx.d.ts +15 -0
  13. package/lib/types/client/index.d.ts +14 -0
  14. package/lib/types/client/locales.d.ts +139 -0
  15. package/lib/types/client/mount.d.ts +9 -0
  16. package/lib/types/client/panel/AssetsTab.d.ts +7 -0
  17. package/lib/types/client/panel/AssistantTab.d.ts +7 -0
  18. package/lib/types/client/panel/BookshelfBar.d.ts +11 -0
  19. package/lib/types/client/panel/NovelPanel.d.ts +13 -0
  20. package/lib/types/client/panel/controller.d.ts +19 -0
  21. package/lib/types/client/panel/helpers.d.ts +8 -0
  22. package/lib/types/client/sidebar-entry.d.ts +13 -0
  23. package/lib/types/docx.d.ts +19 -0
  24. package/lib/types/engine.d.ts +95 -0
  25. package/lib/types/index.d.ts +55 -0
  26. package/lib/types/protocol.d.ts +521 -0
  27. package/lib/types/routes.d.ts +29 -0
  28. package/package.json +105 -0
  29. package/src/assets.ts +518 -0
  30. package/src/assistant.ts +547 -0
  31. package/src/bookshelf.ts +137 -0
  32. package/src/client/api.ts +254 -0
  33. package/src/client/css-modules.d.ts +8 -0
  34. package/src/client/docx.ts +69 -0
  35. package/src/client/index.ts +34 -0
  36. package/src/client/locales.ts +271 -0
  37. package/src/client/mount.tsx +97 -0
  38. package/src/client/panel/AssetsTab.tsx +341 -0
  39. package/src/client/panel/AssistantTab.tsx +188 -0
  40. package/src/client/panel/BookshelfBar.tsx +116 -0
  41. package/src/client/panel/NovelPanel.tsx +990 -0
  42. package/src/client/panel/controller.ts +45 -0
  43. package/src/client/panel/helpers.ts +17 -0
  44. package/src/client/panel/panel.module.css +894 -0
  45. package/src/client/sidebar-entry.ts +122 -0
  46. package/src/docx.ts +83 -0
  47. package/src/engine.ts +1019 -0
  48. package/src/index.ts +184 -0
  49. package/src/protocol.ts +539 -0
  50. package/src/routes.ts +955 -0
@@ -0,0 +1,894 @@
1
+ /* dsh-novel-forge panel styles (CSS module — auto-injected by the client bundle).
2
+ * Uses the real dsh theme alias variables (--dsw-alias-*) so both light and
3
+ * dark mode render correctly. */
4
+
5
+ /* ---------------------------------------------------------------- palette */
6
+
7
+ /* Sidebar entry row (DOM-injected, mirroring family plugins). Lives outside
8
+ * the panel root, so it defines its own minimal palette (inherits the shell's
9
+ * theme via body[data-ds-dark-theme]). */
10
+ .entry {
11
+ display: flex;
12
+ align-items: center;
13
+ gap: 8px;
14
+ width: 100%;
15
+ padding: 8px 12px;
16
+ border: none;
17
+ background: transparent;
18
+ color: var(--dsw-alias-label-primary, #1f1f23);
19
+ cursor: pointer;
20
+ font-size: 13px;
21
+ text-align: left;
22
+ border-radius: 6px;
23
+ transition: background 0.15s ease;
24
+ }
25
+
26
+ body[data-ds-dark-theme] .entry {
27
+ color: var(--dsw-alias-label-primary, #ececf1);
28
+ }
29
+
30
+ .entry:hover {
31
+ background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, 0.12));
32
+ }
33
+
34
+ .entry[data-active] {
35
+ background: var(--dsw-alias-interactive-bg-active, rgba(127, 127, 127, 0.2));
36
+ }
37
+
38
+ .entryIcon {
39
+ display: inline-flex;
40
+ align-items: center;
41
+ justify-content: center;
42
+ flex-shrink: 0;
43
+ }
44
+
45
+ .entryLabel {
46
+ overflow: hidden;
47
+ text-overflow: ellipsis;
48
+ white-space: nowrap;
49
+ }
50
+
51
+ /* Center-column overlay shell (mirrors family plugins' view pattern). */
52
+ [data-pane="conversation"] {
53
+ position: relative;
54
+ }
55
+
56
+ /* The panel root carries its own resolved palette so it never depends on
57
+ * inherited alias variables (which live on body / body[data-ds-dark-theme]
58
+ * and can be shadowed by the shell). We set local vars per theme here. */
59
+ .view {
60
+ z-index: 60;
61
+ display: none;
62
+ position: absolute;
63
+ inset: 0;
64
+ overflow: auto;
65
+ color-scheme: light;
66
+ /* light palette */
67
+ --nf-bg: #fafafc;
68
+ --nf-bg-raise: #ffffff;
69
+ --nf-bg-inset: #f1f1f5;
70
+ --nf-border: rgba(0, 0, 0, 0.1);
71
+ --nf-border-strong: rgba(0, 0, 0, 0.22);
72
+ --nf-text: #1f1f23;
73
+ --nf-text-2: #5b5b66;
74
+ --nf-text-3: #8a8a94;
75
+ --nf-accent: #4d6bfe;
76
+ --nf-accent-hover: #3d5bf0;
77
+ --nf-accent-soft: rgba(77, 107, 254, 0.12);
78
+ --nf-accent-fg: #ffffff;
79
+ --nf-hover: rgba(0, 0, 0, 0.06);
80
+ --nf-success: #16a34a;
81
+ --nf-error: #e5484d;
82
+ --nf-warn: #d97706;
83
+ --nf-info: #7c5cf6;
84
+ --nf-shadow: 0 1px 2px rgba(16, 24, 40, 0.05), 0 4px 14px rgba(16, 24, 40, 0.06);
85
+ --nf-shadow-lg: 0 4px 10px rgba(16, 24, 40, 0.06), 0 12px 28px rgba(16, 24, 40, 0.1);
86
+ background: var(--nf-bg);
87
+ color: var(--nf-text);
88
+ }
89
+
90
+ body[data-ds-dark-theme] .view {
91
+ color-scheme: dark;
92
+ /* dark palette */
93
+ --nf-bg: #121216;
94
+ --nf-bg-raise: #1a1a20;
95
+ --nf-bg-inset: #232329;
96
+ --nf-border: rgba(255, 255, 255, 0.1);
97
+ --nf-border-strong: rgba(255, 255, 255, 0.24);
98
+ --nf-text: #ececf1;
99
+ --nf-text-2: #b0b0ba;
100
+ --nf-text-3: #7d7d88;
101
+ --nf-accent: #5b7cff;
102
+ --nf-accent-hover: #7390ff;
103
+ --nf-accent-soft: rgba(91, 124, 255, 0.18);
104
+ --nf-accent-fg: #ffffff;
105
+ --nf-hover: rgba(255, 255, 255, 0.08);
106
+ --nf-success: #4ade80;
107
+ --nf-error: #f87171;
108
+ --nf-warn: #fbbf24;
109
+ --nf-info: #a78bfa;
110
+ --nf-shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 4px 14px rgba(0, 0, 0, 0.35);
111
+ --nf-shadow-lg: 0 4px 10px rgba(0, 0, 0, 0.4), 0 12px 28px rgba(0, 0, 0, 0.5);
112
+ }
113
+
114
+ html[data-dsh-novelforge-active]:not([data-dsh-taskboard-active]):not([data-dsh-ssh-active]) .view {
115
+ display: block;
116
+ }
117
+
118
+ /* ------------------------------------------------------------ panel shell */
119
+
120
+ .panel {
121
+ display: flex;
122
+ flex-direction: column;
123
+ height: 100%;
124
+ min-width: 0;
125
+ font-size: 14px;
126
+ line-height: 1.6;
127
+ }
128
+
129
+ .panelHeader {
130
+ display: flex;
131
+ align-items: center;
132
+ justify-content: space-between;
133
+ padding: 12px 18px;
134
+ border-bottom: 1px solid var(--nf-border);
135
+ flex-shrink: 0;
136
+ background: var(--nf-bg-raise);
137
+ position: sticky;
138
+ top: 0;
139
+ z-index: 5;
140
+ }
141
+
142
+ .panelTitle {
143
+ margin: 0;
144
+ font-size: 16px;
145
+ font-weight: 700;
146
+ letter-spacing: 0.2px;
147
+ display: flex;
148
+ align-items: center;
149
+ gap: 8px;
150
+ }
151
+
152
+ .panelTitle::before {
153
+ content: '';
154
+ width: 4px;
155
+ height: 16px;
156
+ border-radius: 2px;
157
+ background: linear-gradient(180deg, var(--nf-accent), var(--nf-info));
158
+ }
159
+
160
+ .iconButton {
161
+ border: none;
162
+ background: transparent;
163
+ cursor: pointer;
164
+ font-size: 16px;
165
+ color: var(--nf-text-2);
166
+ padding: 4px 9px;
167
+ border-radius: 8px;
168
+ transition: background 0.15s ease, color 0.15s ease;
169
+ }
170
+
171
+ .iconButton:hover {
172
+ background: var(--nf-hover);
173
+ color: var(--nf-text);
174
+ }
175
+
176
+ /* ------------------------------------------------------------ tab bar */
177
+
178
+ .tabBar {
179
+ display: flex;
180
+ gap: 2px;
181
+ padding: 8px 14px 0;
182
+ border-bottom: 1px solid var(--nf-border);
183
+ flex-shrink: 0;
184
+ background: var(--nf-bg-raise);
185
+ position: sticky;
186
+ top: 45px;
187
+ z-index: 4;
188
+ }
189
+
190
+ .tab {
191
+ border: none;
192
+ background: transparent;
193
+ padding: 7px 13px;
194
+ cursor: pointer;
195
+ border-radius: 8px 8px 0 0;
196
+ font-size: 13.5px;
197
+ color: var(--nf-text-2);
198
+ transition: color 0.15s ease, background 0.15s ease;
199
+ position: relative;
200
+ font-weight: 500;
201
+ }
202
+
203
+ .tab:hover {
204
+ color: var(--nf-text);
205
+ background: var(--nf-hover);
206
+ }
207
+
208
+ .tab[data-active] {
209
+ color: var(--nf-accent);
210
+ font-weight: 600;
211
+ }
212
+
213
+ .tab[data-active]::after {
214
+ content: '';
215
+ position: absolute;
216
+ left: 10px;
217
+ right: 10px;
218
+ bottom: -1px;
219
+ height: 2.5px;
220
+ border-radius: 2px 2px 0 0;
221
+ background: var(--nf-accent);
222
+ }
223
+
224
+ .panelContent {
225
+ flex: 1;
226
+ overflow: auto;
227
+ padding: 16px 18px 20px;
228
+ display: flex;
229
+ flex-direction: column;
230
+ gap: 14px;
231
+ }
232
+
233
+ /* ------------------------------------------------------------ cards */
234
+
235
+ .card {
236
+ border: 1px solid var(--nf-border);
237
+ border-radius: 12px;
238
+ padding: 14px 16px;
239
+ display: flex;
240
+ flex-direction: column;
241
+ gap: 10px;
242
+ background: var(--nf-bg-raise);
243
+ box-shadow: var(--nf-shadow);
244
+ transition: border-color 0.2s ease, box-shadow 0.2s ease;
245
+ }
246
+
247
+ .cardTitle {
248
+ margin: 0;
249
+ font-size: 14px;
250
+ font-weight: 650;
251
+ color: var(--nf-text);
252
+ display: flex;
253
+ align-items: center;
254
+ gap: 8px;
255
+ }
256
+
257
+ /* ------------------------------------------------------------ buttons */
258
+
259
+ .button {
260
+ border: 1px solid var(--nf-border-strong);
261
+ background: var(--nf-bg-inset);
262
+ color: var(--nf-text);
263
+ padding: 6px 14px;
264
+ border-radius: 8px;
265
+ cursor: pointer;
266
+ font-size: 13px;
267
+ font-weight: 500;
268
+ transition: background 0.15s ease, border-color 0.15s ease, transform 0.1s ease, box-shadow 0.15s ease;
269
+ white-space: nowrap;
270
+ }
271
+
272
+ .button:hover:not([disabled]) {
273
+ background: var(--nf-hover);
274
+ border-color: var(--nf-border-strong);
275
+ }
276
+
277
+ .button:active:not([disabled]) {
278
+ transform: translateY(1px);
279
+ }
280
+
281
+ .button[disabled] {
282
+ opacity: 0.45;
283
+ cursor: not-allowed;
284
+ }
285
+
286
+ .buttonPrimary {
287
+ border-color: var(--nf-accent);
288
+ background: linear-gradient(180deg, var(--nf-accent), var(--nf-accent-hover));
289
+ color: var(--nf-accent-fg);
290
+ box-shadow: 0 1px 3px rgba(77, 107, 254, 0.25);
291
+ }
292
+
293
+ .buttonPrimary:hover:not([disabled]) {
294
+ background: linear-gradient(180deg, var(--nf-accent-hover), var(--nf-accent));
295
+ border-color: var(--nf-accent-hover);
296
+ }
297
+
298
+ .buttonDanger {
299
+ border-color: var(--nf-error);
300
+ color: var(--nf-error);
301
+ }
302
+
303
+ .buttonDanger:hover:not([disabled]) {
304
+ background: rgba(229, 72, 77, 0.1);
305
+ }
306
+
307
+ .buttonSmall {
308
+ padding: 3px 10px;
309
+ font-size: 12px;
310
+ border-radius: 6px;
311
+ }
312
+
313
+ /* ------------------------------------------------------------ forms */
314
+
315
+ .field {
316
+ display: flex;
317
+ flex-direction: column;
318
+ gap: 5px;
319
+ }
320
+
321
+ .fieldLabel {
322
+ font-size: 12px;
323
+ color: var(--nf-text-2);
324
+ font-weight: 500;
325
+ }
326
+
327
+ .input {
328
+ border: 1px solid var(--nf-border-strong);
329
+ background: var(--nf-bg-inset);
330
+ color: var(--nf-text);
331
+ border-radius: 8px;
332
+ padding: 7px 11px;
333
+ font-size: 13px;
334
+ width: 100%;
335
+ box-sizing: border-box;
336
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
337
+ }
338
+
339
+ .input:focus,
340
+ .textarea:focus {
341
+ outline: none;
342
+ border-color: var(--nf-accent);
343
+ box-shadow: 0 0 0 3px var(--nf-accent-soft);
344
+ }
345
+
346
+ .input::placeholder,
347
+ .textarea::placeholder {
348
+ color: var(--nf-text-3);
349
+ }
350
+
351
+ .textarea {
352
+ border: 1px solid var(--nf-border-strong);
353
+ background: var(--nf-bg-inset);
354
+ color: var(--nf-text);
355
+ border-radius: 8px;
356
+ padding: 9px 11px;
357
+ font-size: 13px;
358
+ width: 100%;
359
+ box-sizing: border-box;
360
+ min-height: 200px;
361
+ font-family: inherit;
362
+ resize: vertical;
363
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
364
+ line-height: 1.7;
365
+ }
366
+
367
+ .row {
368
+ display: flex;
369
+ align-items: center;
370
+ gap: 8px;
371
+ flex-wrap: wrap;
372
+ }
373
+
374
+ .spaceBetween {
375
+ justify-content: space-between;
376
+ }
377
+
378
+ /* ------------------------------------------------------------ chapter list */
379
+
380
+ .chapterList {
381
+ display: flex;
382
+ flex-direction: column;
383
+ gap: 8px;
384
+ }
385
+
386
+ .chapter {
387
+ display: flex;
388
+ align-items: center;
389
+ gap: 10px;
390
+ padding: 9px 12px;
391
+ border: 1px solid var(--nf-border);
392
+ border-radius: 10px;
393
+ background: var(--nf-bg);
394
+ transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.1s ease;
395
+ }
396
+
397
+ .chapter:hover {
398
+ border-color: var(--nf-border-strong);
399
+ box-shadow: var(--nf-shadow);
400
+ }
401
+
402
+ .chapterNum {
403
+ display: inline-flex;
404
+ align-items: center;
405
+ justify-content: center;
406
+ min-width: 28px;
407
+ height: 28px;
408
+ padding: 0 8px;
409
+ border-radius: 8px;
410
+ background: var(--nf-bg-inset);
411
+ border: 1px solid var(--nf-border);
412
+ font-size: 12px;
413
+ font-weight: 700;
414
+ color: var(--nf-text-2);
415
+ flex-shrink: 0;
416
+ }
417
+
418
+ .chapterMain {
419
+ flex: 1;
420
+ min-width: 0;
421
+ }
422
+
423
+ .chapterTitle {
424
+ font-weight: 600;
425
+ font-size: 13px;
426
+ display: flex;
427
+ align-items: center;
428
+ gap: 8px;
429
+ color: var(--nf-text);
430
+ }
431
+
432
+ .chapterBeats {
433
+ font-size: 12px;
434
+ color: var(--nf-text-2);
435
+ opacity: 0.85;
436
+ overflow: hidden;
437
+ text-overflow: ellipsis;
438
+ white-space: nowrap;
439
+ }
440
+
441
+ .chapterActions {
442
+ display: flex;
443
+ gap: 4px;
444
+ flex-shrink: 0;
445
+ opacity: 0.85;
446
+ transition: opacity 0.15s ease;
447
+ }
448
+
449
+ .chapter:hover .chapterActions {
450
+ opacity: 1;
451
+ }
452
+
453
+ /* ------------------------------------------------------------ badges */
454
+
455
+ .badge {
456
+ font-size: 11px;
457
+ padding: 2px 9px;
458
+ border-radius: 999px;
459
+ border: 1px solid;
460
+ white-space: nowrap;
461
+ font-weight: 600;
462
+ letter-spacing: 0.2px;
463
+ }
464
+
465
+ .badgePending {
466
+ color: var(--nf-info);
467
+ border-color: var(--nf-info);
468
+ background: color-mix(in srgb, var(--nf-info) 10%, transparent);
469
+ }
470
+
471
+ .badgeGenerating {
472
+ color: var(--nf-accent);
473
+ border-color: var(--nf-accent);
474
+ background: color-mix(in srgb, var(--nf-accent) 12%, transparent);
475
+ animation: pulse 1.2s ease-in-out infinite;
476
+ }
477
+
478
+ .badgeWritten {
479
+ color: var(--nf-warn);
480
+ border-color: var(--nf-warn);
481
+ background: color-mix(in srgb, var(--nf-warn) 10%, transparent);
482
+ }
483
+
484
+ .badgeRejected {
485
+ color: var(--nf-error);
486
+ border-color: var(--nf-error);
487
+ background: color-mix(in srgb, var(--nf-error) 10%, transparent);
488
+ }
489
+
490
+ .badgeDone {
491
+ color: var(--nf-success);
492
+ border-color: var(--nf-success);
493
+ background: color-mix(in srgb, var(--nf-success) 10%, transparent);
494
+ }
495
+
496
+ .badgeError {
497
+ color: var(--nf-error);
498
+ border-color: var(--nf-error);
499
+ background: color-mix(in srgb, var(--nf-error) 10%, transparent);
500
+ }
501
+
502
+ /* ------------------------------------------------------------ review & preview */
503
+
504
+ .reviewBox {
505
+ border: 1px solid var(--nf-border);
506
+ border-radius: 10px;
507
+ padding: 10px 12px;
508
+ background: var(--nf-bg-inset);
509
+ display: flex;
510
+ flex-direction: column;
511
+ gap: 6px;
512
+ font-size: 12.5px;
513
+ }
514
+
515
+ .chapterPreview {
516
+ max-height: 320px;
517
+ overflow: auto;
518
+ white-space: pre-wrap;
519
+ word-break: break-all;
520
+ font-size: 12.5px;
521
+ line-height: 1.8;
522
+ font-family: inherit;
523
+ color: var(--nf-text);
524
+ background: var(--nf-bg-inset);
525
+ border: 1px solid var(--nf-border);
526
+ border-radius: 10px;
527
+ padding: 10px 12px;
528
+ margin: 0;
529
+ }
530
+
531
+ /* ------------------------------------------------------------ assistant chat */
532
+
533
+ .chatScroll {
534
+ flex: 1;
535
+ min-height: 240px;
536
+ max-height: 480px;
537
+ overflow-y: auto;
538
+ display: flex;
539
+ flex-direction: column;
540
+ gap: 10px;
541
+ padding: 14px;
542
+ border: 1px solid var(--nf-border);
543
+ border-radius: 12px;
544
+ background: var(--nf-bg-inset);
545
+ }
546
+
547
+ .chatBubbleUser {
548
+ align-self: flex-end;
549
+ max-width: 90%;
550
+ background: linear-gradient(180deg, var(--nf-accent), var(--nf-accent-hover));
551
+ color: var(--nf-accent-fg);
552
+ border-radius: 14px 14px 4px 14px;
553
+ padding: 9px 14px;
554
+ font-size: 13px;
555
+ box-shadow: 0 2px 6px rgba(77, 107, 254, 0.2);
556
+ }
557
+
558
+ .chatBubbleAssistant {
559
+ align-self: flex-start;
560
+ max-width: 92%;
561
+ background: var(--nf-bg-raise);
562
+ color: var(--nf-text);
563
+ border: 1px solid var(--nf-border);
564
+ border-radius: 14px 14px 14px 4px;
565
+ padding: 9px 14px;
566
+ font-size: 13px;
567
+ box-shadow: var(--nf-shadow);
568
+ }
569
+
570
+ .chatRole {
571
+ font-size: 11px;
572
+ color: var(--nf-text-3);
573
+ margin-bottom: 3px;
574
+ font-weight: 600;
575
+ }
576
+
577
+ /* Live tool output (text streaming while a chapter is generated/rewritten). */
578
+ .toolLive {
579
+ margin-top: 6px;
580
+ padding: 8px 10px;
581
+ border-radius: 8px;
582
+ background: var(--nf-bg-inset);
583
+ border: 1px dashed var(--nf-border-strong);
584
+ font-size: 12px;
585
+ line-height: 1.7;
586
+ white-space: pre-wrap;
587
+ word-break: break-all;
588
+ color: var(--nf-text-2);
589
+ max-height: 180px;
590
+ overflow-y: auto;
591
+ }
592
+
593
+ /* ------------------------------------------------------------ progress console */
594
+
595
+ .progress {
596
+ border: 1px solid var(--nf-border);
597
+ border-radius: 10px;
598
+ padding: 10px 14px;
599
+ font-size: 12px;
600
+ min-height: 60px;
601
+ max-height: 220px;
602
+ overflow: auto;
603
+ white-space: pre-wrap;
604
+ word-break: break-all;
605
+ color: var(--nf-text-2);
606
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
607
+ background: var(--nf-bg-inset);
608
+ line-height: 1.8;
609
+ }
610
+
611
+ .progressLine {
612
+ color: var(--nf-text-2);
613
+ opacity: 0.9;
614
+ }
615
+
616
+ .progressLineDone {
617
+ color: var(--nf-success);
618
+ font-weight: 600;
619
+ }
620
+
621
+ .progressLineError {
622
+ color: var(--nf-error);
623
+ font-weight: 600;
624
+ }
625
+
626
+ /* ------------------------------------------------------------ misc */
627
+
628
+ .meta {
629
+ font-size: 12px;
630
+ color: var(--nf-text-2);
631
+ opacity: 0.9;
632
+ }
633
+
634
+ .fileList {
635
+ display: flex;
636
+ flex-direction: column;
637
+ gap: 3px;
638
+ font-size: 12px;
639
+ color: var(--nf-text-2);
640
+ opacity: 0.9;
641
+ word-break: break-all;
642
+ }
643
+
644
+ /* Custom scrollbar (webkit) */
645
+ .panelContent::-webkit-scrollbar,
646
+ .chatScroll::-webkit-scrollbar,
647
+ .chapterPreview::-webkit-scrollbar,
648
+ .progress::-webkit-scrollbar,
649
+ .toolLive::-webkit-scrollbar {
650
+ width: 8px;
651
+ height: 8px;
652
+ }
653
+
654
+ .panelContent::-webkit-scrollbar-thumb,
655
+ .chatScroll::-webkit-scrollbar-thumb,
656
+ .chapterPreview::-webkit-scrollbar-thumb,
657
+ .progress::-webkit-scrollbar-thumb,
658
+ .toolLive::-webkit-scrollbar-thumb {
659
+ background: var(--nf-border-strong);
660
+ border-radius: 4px;
661
+ }
662
+
663
+ .panelContent::-webkit-scrollbar-thumb:hover,
664
+ .chatScroll::-webkit-scrollbar-thumb:hover,
665
+ .chapterPreview::-webkit-scrollbar-thumb:hover,
666
+ .progress::-webkit-scrollbar-thumb:hover,
667
+ .toolLive::-webkit-scrollbar-thumb:hover {
668
+ background: var(--nf-text-3);
669
+ }
670
+
671
+ /* Workflow timeline */
672
+ .workflowList {
673
+ display: flex;
674
+ flex-direction: column;
675
+ gap: 0;
676
+ }
677
+
678
+ .workflowRow {
679
+ display: flex;
680
+ align-items: flex-start;
681
+ gap: 12px;
682
+ padding: 8px 4px;
683
+ position: relative;
684
+ }
685
+
686
+ .workflowRow::before {
687
+ content: '';
688
+ position: absolute;
689
+ left: 13px;
690
+ top: 34px;
691
+ bottom: -8px;
692
+ width: 2px;
693
+ background: var(--nf-border);
694
+ }
695
+
696
+ .workflowRow:last-child::before {
697
+ display: none;
698
+ }
699
+
700
+ .workflowDot {
701
+ width: 28px;
702
+ height: 28px;
703
+ border-radius: 50%;
704
+ display: inline-flex;
705
+ align-items: center;
706
+ justify-content: center;
707
+ font-size: 12px;
708
+ font-weight: 700;
709
+ flex-shrink: 0;
710
+ border: 2px solid var(--nf-border-strong);
711
+ background: var(--nf-bg-raise);
712
+ color: var(--nf-text-2);
713
+ z-index: 1;
714
+ }
715
+
716
+ .workflowDotDone {
717
+ border-color: var(--nf-success);
718
+ background: color-mix(in srgb, var(--nf-success) 15%, var(--nf-bg-raise));
719
+ color: var(--nf-success);
720
+ }
721
+
722
+ .workflowDotActive {
723
+ border-color: var(--nf-accent);
724
+ background: var(--nf-accent);
725
+ color: var(--nf-accent-fg);
726
+ box-shadow: 0 0 0 4px var(--nf-accent-soft);
727
+ }
728
+
729
+ .workflowBody {
730
+ flex: 1;
731
+ min-width: 0;
732
+ display: flex;
733
+ flex-direction: column;
734
+ gap: 4px;
735
+ padding-top: 2px;
736
+ }
737
+
738
+ .workflowLabel {
739
+ font-size: 13px;
740
+ font-weight: 600;
741
+ color: var(--nf-text);
742
+ }
743
+
744
+ .workflowHint {
745
+ font-size: 12px;
746
+ color: var(--nf-text-2);
747
+ }
748
+
749
+ @keyframes pulse {
750
+ 0%, 100% { opacity: 1; }
751
+ 50% { opacity: 0.45; }
752
+ }
753
+
754
+ @keyframes fadeIn {
755
+ from { opacity: 0; transform: translateY(4px); }
756
+ to { opacity: 1; transform: translateY(0); }
757
+ }
758
+
759
+ .card {
760
+ animation: fadeIn 0.2s ease;
761
+ }
762
+
763
+ /* ------------------------------------------------------------ bookshelf */
764
+
765
+ .bookshelf {
766
+ display: flex;
767
+ align-items: center;
768
+ gap: 10px;
769
+ padding: 8px 18px;
770
+ border-bottom: 1px solid var(--nf-border);
771
+ background: var(--nf-bg-raise);
772
+ flex-shrink: 0;
773
+ flex-wrap: wrap;
774
+ }
775
+
776
+ .bookshelfLabel {
777
+ font-size: 12px;
778
+ font-weight: 700;
779
+ color: var(--nf-text-3);
780
+ letter-spacing: 1px;
781
+ flex-shrink: 0;
782
+ }
783
+
784
+ .bookshelfList {
785
+ display: flex;
786
+ align-items: center;
787
+ gap: 6px;
788
+ flex-wrap: wrap;
789
+ flex: 1;
790
+ min-width: 0;
791
+ }
792
+
793
+ .bookChip {
794
+ display: flex;
795
+ align-items: center;
796
+ gap: 6px;
797
+ padding: 4px 8px 4px 10px;
798
+ border: 1px solid var(--nf-border);
799
+ border-radius: 999px;
800
+ background: var(--nf-bg-inset);
801
+ cursor: pointer;
802
+ font-size: 12px;
803
+ transition: border-color 0.15s ease, background 0.15s ease;
804
+ max-width: 220px;
805
+ }
806
+
807
+ .bookChip:hover {
808
+ border-color: var(--nf-border-strong);
809
+ }
810
+
811
+ .bookChipActive {
812
+ border-color: var(--nf-accent);
813
+ background: var(--nf-accent-soft);
814
+ color: var(--nf-accent);
815
+ }
816
+
817
+ .bookChipName {
818
+ font-weight: 600;
819
+ overflow: hidden;
820
+ text-overflow: ellipsis;
821
+ white-space: nowrap;
822
+ }
823
+
824
+ .bookChipMeta {
825
+ font-size: 11px;
826
+ opacity: 0.7;
827
+ white-space: nowrap;
828
+ }
829
+
830
+ .bookChipRemove {
831
+ border: none;
832
+ background: transparent;
833
+ color: var(--nf-text-3);
834
+ cursor: pointer;
835
+ font-size: 13px;
836
+ line-height: 1;
837
+ padding: 0 2px;
838
+ border-radius: 4px;
839
+ }
840
+
841
+ .bookChipRemove:hover {
842
+ color: var(--nf-error);
843
+ }
844
+
845
+ .bookAdd {
846
+ border: 1px dashed var(--nf-border-strong);
847
+ background: transparent;
848
+ color: var(--nf-text-2);
849
+ cursor: pointer;
850
+ font-size: 12px;
851
+ padding: 4px 12px;
852
+ border-radius: 999px;
853
+ transition: border-color 0.15s ease, color 0.15s ease;
854
+ }
855
+
856
+ .bookAdd:hover {
857
+ border-color: var(--nf-accent);
858
+ color: var(--nf-accent);
859
+ }
860
+
861
+ .bookCreateForm {
862
+ display: flex;
863
+ align-items: center;
864
+ gap: 6px;
865
+ }
866
+
867
+ /* ------------------------------------------------------------ dropzone */
868
+
869
+ .dropzone {
870
+ border: 2px dashed var(--nf-border-strong);
871
+ border-radius: 10px;
872
+ padding: 18px 14px;
873
+ text-align: center;
874
+ font-size: 13px;
875
+ color: var(--nf-text-2);
876
+ cursor: pointer;
877
+ transition: border-color 0.15s ease, background 0.15s ease;
878
+ display: flex;
879
+ flex-direction: column;
880
+ align-items: center;
881
+ gap: 6px;
882
+ }
883
+
884
+ .dropzone:hover,
885
+ .dropzoneActive {
886
+ border-color: var(--nf-accent);
887
+ background: var(--nf-accent-soft);
888
+ color: var(--nf-accent);
889
+ }
890
+
891
+ .dropzoneIcon {
892
+ font-size: 22px;
893
+ line-height: 1;
894
+ }