kingkont 0.7.39 → 0.7.40

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.
@@ -0,0 +1,1018 @@
1
+ * { box-sizing: border-box; margin: 0; padding: 0; }
2
+ html, body { height: 100%; }
3
+ body {
4
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
5
+ background: #1a1a1a; color: #e0e0e0; display: flex; overflow: hidden;
6
+ }
7
+
8
+ .sidebar { width: 280px; background: #222; border-right: 1px solid #333; display: flex; flex-direction: column; flex-shrink: 0; }
9
+ /* На macOS вверху сидят traffic-lights окна (titleBarStyle: hiddenInset),
10
+ поэтому верхняя зона sidebar-header пустая под них; и сама эта зона draggable. */
11
+ .sidebar-header { padding: 32px 12px 12px; border-bottom: 1px solid #333; display: flex; flex-direction: column; gap: 8px; -webkit-app-region: drag; }
12
+ .sidebar-header h2 { font-size: 14px; }
13
+ .sidebar-header .brand { display: flex; align-items: center; gap: 8px; }
14
+ .sidebar-header .brand img {
15
+ width: 36px; height: 36px; flex-shrink: 0; object-fit: contain;
16
+ background: #1a1a1a; border-radius: 8px; padding: 4px;
17
+ }
18
+ .sidebar-header .recent-film {
19
+ display: block; font-size: 12px; color: #888; text-decoration: none;
20
+ padding: 6px 10px; border-radius: 4px; word-break: break-all;
21
+ background: #262626; border: 1px solid #333;
22
+ transition: background 0.12s, color 0.12s, border-color 0.12s;
23
+ }
24
+ .sidebar-header .recent-film:hover {
25
+ background: #2e2e2e; color: #ddd; border-color: #444;
26
+ }
27
+ .sidebar-footer {
28
+ border-top: 1px solid #333; padding: 10px 12px;
29
+ display: flex; flex-direction: column; gap: 6px;
30
+ font-size: 11px; color: #777;
31
+ }
32
+ .sidebar-footer .hint { color: #777; font-size: 11px; line-height: 1.4; }
33
+ .sidebar-footer .jobs-info { color: #aaccdd; font-size: 11px; }
34
+ .sidebar-footer .balance-info {
35
+ display: flex; align-items: center; gap: 6px; font-size: 11px;
36
+ color: #c4c4c4; padding: 4px 8px; background: #2a2a2a;
37
+ border: 1px solid #3a3a3a; border-radius: 6px; cursor: pointer;
38
+ width: fit-content; max-width: 100%;
39
+ transition: background 0.1s;
40
+ }
41
+ .sidebar-footer .balance-info:hover { background: #333; }
42
+ .sidebar-footer .balance-info .dot {
43
+ width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0;
44
+ background: #16a34a;
45
+ }
46
+ .sidebar-footer .balance-info.low .dot { background: #f59e0b; }
47
+ .sidebar-footer .balance-info.empty .dot { background: #dc2626; }
48
+ .sidebar-footer .balance-info b { color: #fff; font-weight: 500; }
49
+ /* Когда проект ещё не открыт — секции с персонажами/локациями/сценами
50
+ не имеют смысла, скрываем их. Класс ставится в openFilm/closeFilm. */
51
+ body.no-project .sidebar > .sidebar-section,
52
+ body.no-project .sidebar-search-row { display: none; }
53
+ .sidebar-search-row { padding: 8px 12px; border-bottom: 1px solid #2a2a2a; }
54
+ .sidebar-search-row input {
55
+ width: 100%; padding: 5px 8px; font-size: 12px;
56
+ background: #1a1a1a; color: #ddd; border: 1px solid #333; border-radius: 4px;
57
+ outline: none;
58
+ }
59
+ .sidebar-search-row input:focus { border-color: #4a6a9a; }
60
+ .sidebar-list .item.search-hidden { display: none; }
61
+ .sidebar-header .brand .title { font-size: 14px; font-weight: 600; color: #e0e0e0; line-height: 1.1; }
62
+ .sidebar-header .brand .brand-version {
63
+ font-size: 10px; font-weight: 400; color: #666;
64
+ font-family: ui-monospace, 'SF Mono', monospace;
65
+ margin-left: 4px;
66
+ }
67
+ .sidebar-header .brand .sub { font-size: 10px; color: #888; letter-spacing: 0.5px; text-transform: uppercase; }
68
+ .sidebar-header .brand .sub.has-project { color: #aaccdd; text-transform: none; letter-spacing: 0; font-size: 11px; }
69
+ .sidebar-header .brand .board {
70
+ margin-top: 3px; font-size: 13px; font-weight: 600; color: #e0e0e0;
71
+ word-break: break-all; line-height: 1.2;
72
+ display: flex; align-items: center; gap: 6px;
73
+ }
74
+ .sidebar-header .brand .board .kind {
75
+ font-size: 9px; font-weight: 600; letter-spacing: 0.5px;
76
+ text-transform: uppercase; padding: 1px 5px; border-radius: 3px;
77
+ background: #3a3a3a; color: #aaa;
78
+ }
79
+ .sidebar-header .brand .board .kind.character { background: #5a3a6a; color: #e0b0f0; }
80
+ .sidebar-header .brand .board .kind.location { background: #3a5a4a; color: #b0e0c0; }
81
+ .sidebar-header .brand .board .kind.episode { background: #2a4a6a; color: #aac8e6; }
82
+ /* Когда проект открыт — primary «Открыть проект» больше не нужна. */
83
+ body:not(.no-project) #pickRoot { display: none; }
84
+ /* #repliquesBtn в toolbar спрятан — функцию вызываем автоматом для персонажа. */
85
+ #repliquesBtn { display: none !important; }
86
+
87
+ /* === Welcome-screen (виден только когда body.no-project) === */
88
+ .welcome { display: none; }
89
+ body.no-project .welcome {
90
+ display: flex; position: fixed; inset: 0; z-index: 50;
91
+ flex-direction: column; align-items: center;
92
+ background: #1a1a1a;
93
+ -webkit-app-region: drag;
94
+ padding-top: 64px;
95
+ overflow: hidden; /* recents скроллятся внутри своего блока */
96
+ }
97
+ body.no-project .sidebar, body.no-project .main, body.no-project .preview-panel { display: none !important; }
98
+ .welcome-inner {
99
+ display: flex; flex-direction: column; align-items: center; gap: 12px;
100
+ width: 100%; max-width: none; padding: 0;
101
+ flex: 1; min-height: 0; /* нужно для child overflow */
102
+ -webkit-app-region: no-drag;
103
+ }
104
+ .welcome-header {
105
+ display: flex; flex-direction: column; align-items: center; gap: 12px;
106
+ flex-shrink: 0; /* всегда сверху, не сжимается под recents */
107
+ padding: 0 32px;
108
+ }
109
+ .welcome-logo {
110
+ width: 96px; height: 96px; object-fit: contain;
111
+ background: #1f1f1f; border-radius: 16px; padding: 8px;
112
+ box-shadow: 0 4px 32px rgba(227, 51, 119, 0.18);
113
+ }
114
+ .welcome-title { font-size: 28px; font-weight: 600; color: #e0e0e0; margin: 8px 0 0; }
115
+ .welcome-version {
116
+ font-size: 13px; font-weight: 400; color: #666;
117
+ font-family: ui-monospace, 'SF Mono', monospace;
118
+ vertical-align: middle; margin-left: 6px;
119
+ }
120
+ .welcome-sub { font-size: 12px; color: #888; letter-spacing: 0.5px; text-transform: uppercase; }
121
+ .welcome-open {
122
+ margin-top: 16px;
123
+ padding: 12px 28px; font-size: 15px; font-weight: 600;
124
+ background: #3a5a8a; border-color: #4a6a9a; color: #fff;
125
+ border-radius: 6px; cursor: pointer; border: 1px solid #4a6a9a;
126
+ }
127
+ .welcome-open:hover { background: #4a6a9a; }
128
+ .welcome-recent {
129
+ margin-top: 36px; width: 100%;
130
+ flex: 1; min-height: 0;
131
+ display: flex; flex-direction: column;
132
+ }
133
+ .welcome-recent-title {
134
+ font-size: 11px; color: #666; text-transform: uppercase; letter-spacing: 0.6px;
135
+ margin-bottom: 14px; text-align: center;
136
+ flex-shrink: 0;
137
+ }
138
+ /* Recents в горизонтальную ленту со скроллом. Карточки фиксированной
139
+ ширины чтобы scroll работал предсказуемо. */
140
+ .welcome-recent-grid {
141
+ display: flex; flex-direction: row; gap: 16px;
142
+ overflow-x: auto; overflow-y: hidden;
143
+ padding: 8px 32px 24px;
144
+ scroll-snap-type: x proximity;
145
+ }
146
+ .welcome-recent-grid::-webkit-scrollbar { height: 8px; }
147
+
148
+ /* === Глобальные dark-scrollbars === */
149
+ /* Firefox */
150
+ * { scrollbar-color: #3a3a3a #1a1a1a; scrollbar-width: thin; }
151
+ /* WebKit (Electron/Chrome) — все scroll-области в приложении. */
152
+ ::-webkit-scrollbar { width: 10px; height: 10px; background: #1a1a1a; }
153
+ ::-webkit-scrollbar-track { background: #1a1a1a; }
154
+ ::-webkit-scrollbar-thumb {
155
+ background: #3a3a3a;
156
+ border-radius: 4px;
157
+ border: 2px solid #1a1a1a; /* gap, чтобы thumb «не прилипал» к краям */
158
+ }
159
+ ::-webkit-scrollbar-thumb:hover { background: #4a4a4a; }
160
+ ::-webkit-scrollbar-corner { background: #1a1a1a; }
161
+ .welcome-card {
162
+ background: #232323; border: 1px solid #333; border-radius: 8px;
163
+ overflow: hidden; cursor: pointer; transition: border-color 0.12s, transform 0.12s;
164
+ display: flex; flex-direction: column;
165
+ width: 240px; flex-shrink: 0;
166
+ scroll-snap-align: start;
167
+ }
168
+ /* Карточка «Открыть проект» — стилизована под обычную recent-карточку,
169
+ но контент не превью, а большая иконка + надпись. */
170
+ .welcome-card.open-card {
171
+ background: linear-gradient(135deg, #2a3e5c, #344a6e);
172
+ border-color: #4a6a9a;
173
+ }
174
+ .welcome-card.open-card:hover {
175
+ border-color: #6a8aba;
176
+ background: linear-gradient(135deg, #344a6e, #4a6a9a);
177
+ }
178
+ .welcome-card.open-card .welcome-card-thumb {
179
+ background: transparent; color: #c4d4ec; font-size: 56px; line-height: 1;
180
+ }
181
+ .welcome-card.open-card .welcome-card-name {
182
+ color: #e0e8f4; font-weight: 600;
183
+ }
184
+ .welcome-card.open-card .welcome-card-ts { color: #aac0d8; }
185
+ .welcome-card:hover { border-color: #4a6a9a; transform: translateY(-2px); }
186
+ .welcome-card-thumb {
187
+ width: 100%; aspect-ratio: 16 / 9; background: #1a1a1a;
188
+ display: flex; align-items: center; justify-content: center;
189
+ color: #444; font-size: 32px;
190
+ }
191
+ .welcome-card-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
192
+ .welcome-card-meta { padding: 8px 12px; }
193
+ .welcome-card-name { font-size: 13px; color: #ddd; word-break: break-all; }
194
+ .welcome-card-ts { font-size: 11px; color: #666; margin-top: 2px; }
195
+ .welcome-card-del {
196
+ position: absolute; top: 6px; right: 6px;
197
+ background: rgba(0,0,0,0.6); color: #aaa;
198
+ width: 22px; height: 22px; border-radius: 50%;
199
+ display: flex; align-items: center; justify-content: center;
200
+ font-size: 14px; line-height: 1; cursor: pointer; opacity: 0;
201
+ transition: opacity 0.12s, color 0.12s;
202
+ }
203
+ .welcome-card { position: relative; }
204
+ .welcome-card:hover .welcome-card-del { opacity: 1; }
205
+ .welcome-card-del:hover { color: #f88; }
206
+ /* Интерактивные элементы внутри drag-региона должны быть кликабельными. */
207
+ .sidebar-header button, .sidebar-header input, .sidebar-header a, .sidebar-header .item { -webkit-app-region: no-drag; }
208
+ /* Главный toolbar тоже — окно можно таскать за пустое место в шапке. */
209
+ .toolbar { -webkit-app-region: drag; }
210
+ .toolbar button, .toolbar input, .toolbar select, .toolbar .badge, .toolbar .zoom-ctl { -webkit-app-region: no-drag; }
211
+ .sidebar-section { padding: 8px; border-bottom: 1px solid #333; display: flex; flex-direction: column; min-height: 0; }
212
+ .sidebar-section:last-child { border-bottom: none; }
213
+ .sidebar-section.flex { flex: 1; overflow-y: auto; }
214
+ .sidebar-section h3 {
215
+ font-size: 11px; text-transform: uppercase; color: #888;
216
+ margin: 4px 8px 8px; letter-spacing: 0.5px;
217
+ display: flex; justify-content: space-between; align-items: center;
218
+ }
219
+ .sidebar-section h3 button { padding: 2px 8px; font-size: 13px; }
220
+ .sidebar-list { display: flex; flex-direction: column; gap: 2px; }
221
+
222
+ .item { padding: 6px 10px; cursor: pointer; border-radius: 4px; display: flex; align-items: center; font-size: 13px; gap: 6px; }
223
+ .item:hover { background: #2c2c2c; }
224
+ .item.active { background: #3a5a8a; color: #fff; }
225
+ .item .item-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
226
+ .item .item-delete {
227
+ opacity: 0; cursor: pointer; color: #888;
228
+ font-size: 16px; line-height: 1; padding: 0 4px;
229
+ flex-shrink: 0;
230
+ }
231
+ .item:hover .item-delete { opacity: 1; }
232
+ .item .item-delete:hover { color: #f88; }
233
+ .item .badge-mini { font-size: 10px; padding: 1px 4px; border-radius: 2px; background: #444; color: #aaa; }
234
+
235
+ button {
236
+ background: #3a3a3a; color: #e0e0e0; border: 1px solid #444;
237
+ padding: 6px 10px; border-radius: 4px; cursor: pointer; font-size: 13px;
238
+ }
239
+ button:hover:not(:disabled) { background: #4a4a4a; }
240
+ button.primary { background: #3a5a8a; border-color: #4a6a9a; color: #fff; }
241
+ button.primary:hover:not(:disabled) { background: #4a6a9a; }
242
+ button.danger { background: #6a3a3a; border-color: #8a4a4a; color: #fff; }
243
+ button.danger:hover:not(:disabled) { background: #8a4a4a; }
244
+ button:disabled { opacity: 0.4; cursor: not-allowed; }
245
+
246
+ .main { flex: 1; display: flex; flex-direction: column; min-width: 0; }
247
+ .toolbar { padding: 8px 16px; background: #222; border-bottom: 1px solid #333; display: flex; gap: 6px; align-items: center; min-height: 48px; flex-wrap: nowrap; overflow-x: auto; }
248
+ .toolbar button { white-space: nowrap; flex-shrink: 0; }
249
+ .toolbar > * { flex-shrink: 0; }
250
+ .toolbar .spacer { flex: 1 0 8px; }
251
+ .toolbar::-webkit-scrollbar { height: 0; display: none; }
252
+ .toolbar .path { color: #888; font-size: 13px; }
253
+ .toolbar .badge {
254
+ font-size: 10px; padding: 2px 6px; border-radius: 3px;
255
+ text-transform: uppercase; letter-spacing: 0.5px; font-weight: 600;
256
+ }
257
+ .toolbar .badge.episode { background: #2a4a6a; color: #aac8e6; }
258
+ .toolbar .badge.character { background: #5a3a6a; color: #e0b0f0; }
259
+ .toolbar .spacer { flex: 1; }
260
+ .toolbar .jobs-info { font-size: 12px; color: #aaa; display: flex; align-items: center; gap: 6px; }
261
+ .zoom-ctl { display: flex; gap: 0; }
262
+ .zoom-ctl button {
263
+ padding: 4px 10px; font-size: 13px; border-radius: 0;
264
+ border-right: none; min-width: 32px;
265
+ }
266
+ .zoom-ctl button:first-child { border-radius: 4px 0 0 4px; }
267
+ .zoom-ctl button:last-child { border-radius: 0 4px 4px 0; border-right: 1px solid #444; }
268
+ .zoom-ctl #zoomLabel { font-variant-numeric: tabular-nums; min-width: 56px; cursor: pointer; }
269
+
270
+ .canvas-wrap {
271
+ flex: 1; position: relative; overflow: auto; background: #181818;
272
+ /* Точечный паттерн фиксированного размера: при зуме НЕ ререндерим весь фон.
273
+ Раньше background-size = 24px * --zoom — каждый wheel-tick инвалидировал
274
+ весь видимый фон, GPU-коммит занимал сотни мс. */
275
+ background-image: radial-gradient(rgba(255,255,255,0.06) 1px, transparent 1px);
276
+ background-size: 24px 24px;
277
+ }
278
+ .canvas-frame {
279
+ position: relative;
280
+ width: 6000px; height: 4000px;
281
+ }
282
+ .canvas {
283
+ position: absolute; left: 0; top: 0; width: 6000px; height: 4000px;
284
+ transform-origin: 0 0;
285
+ /* `will-change: transform` — постоянная промоция в композитный слой.
286
+ Trade-off: hover/scroll иногда вызывают re-raster плитки (~200ms
287
+ коммиты), НО зум через transform идёт через GPU без layout/paint
288
+ на main-thread. Без will-change зум форсит full re-raster и layout
289
+ на каждый wheel-tick (видели 751ms Layout + 2900ms commits в трейсе).
290
+ Dynamic will-change (toggle on/off) — ещё хуже из-за promotion churn. */
291
+ will-change: transform;
292
+ }
293
+ .canvas-wrap.drag-over::after {
294
+ content: 'Отпусти, чтобы импортировать';
295
+ position: fixed; inset: 64px 16px 16px calc(280px + 16px);
296
+ border: 3px dashed #6a8aaa; border-radius: 12px;
297
+ display: flex; align-items: center; justify-content: center;
298
+ font-size: 22px; color: #6a8aaa; pointer-events: none;
299
+ background: rgba(58,90,138,0.05);
300
+ }
301
+
302
+ .node {
303
+ position: absolute; width: 280px; background: #2a2a2a;
304
+ border: 1px solid #444; border-radius: 8px;
305
+ box-shadow: 0 2px 6px rgba(0,0,0,0.5); user-select: none;
306
+ display: flex; flex-direction: column;
307
+ /* `content-visibility: auto` пропускает layout/style/paint для offscreen-нод
308
+ (на холсте 6000×4000 видимы единицы из сотен — это огромная экономия).
309
+ С будет работать паре `contain-intrinsic-size` — Chrome использует это
310
+ вместо реального измерения для skip-нод.
311
+ НЕ ставим явный `contain: paint` или просто `contain: layout style` —
312
+ первый плодит paint-property-tree (PrePaint >1s на зуме), второй не
313
+ изолирует размер и Chrome всё равно гоняет layout-walk через все ноды
314
+ в каждом frame (482ms layout с dirty=4 из 1446 в трейсе).
315
+ `isolation: isolate` — стекинг-контекст для .anchor (z-index:5),
316
+ чтобы он не пробивал поверх соседних нод. */
317
+ content-visibility: auto;
318
+ contain-intrinsic-size: 280px 200px;
319
+ isolation: isolate;
320
+ }
321
+ .node.dragging-to-timeline {
322
+ opacity: 0.5;
323
+ box-shadow: 0 0 0 2px #5aa8ff, 0 8px 24px rgba(90,168,255,0.4);
324
+ }
325
+ .node-body { flex: 1; min-height: 0; }
326
+ .node.text-node .node-body { padding: 0; display: flex; }
327
+ .node.text-node .node-body textarea {
328
+ width: 100%; height: 100%; resize: none; min-height: 80px;
329
+ border: none; border-radius: 0; padding: 12px; flex: 1;
330
+ }
331
+ .node.video-node .node-body { padding: 0; overflow: hidden; border-radius: 0 0 8px 8px; }
332
+ .node.video-node .node-body video { width: 100%; height: auto; max-height: none; background: transparent; border-radius: 0; display: block; }
333
+ .audio-speed { display: flex; gap: 2px; margin-top: 6px; }
334
+ .audio-speed button {
335
+ flex: 1; font-size: 11px; padding: 3px 4px;
336
+ background: #1e1e1e; border: 1px solid #383838; color: #aaa;
337
+ border-radius: 3px; cursor: pointer;
338
+ }
339
+ .audio-speed button:hover { background: #2c2c2c; color: #fff; }
340
+ .audio-speed button.active { background: #3a5a8a; color: #fff; border-color: #4a6a9a; }
341
+
342
+ /* Тоны-теги для голосовой генерации */
343
+ .tones-wrap { position: relative; }
344
+ .tones-tags { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 6px; }
345
+ .tones-tags:empty { min-height: 0; }
346
+ .tones-tags .chip {
347
+ display: inline-flex; align-items: center; gap: 4px;
348
+ background: #3a5a8a; color: #fff; border-radius: 11px;
349
+ padding: 2px 4px 2px 10px; font-size: 12px;
350
+ white-space: nowrap;
351
+ }
352
+ .tones-tags .chip .x {
353
+ background: rgba(0,0,0,0.25); border: none; color: #fff;
354
+ border-radius: 50%; width: 16px; height: 16px;
355
+ cursor: pointer; line-height: 1; padding: 0; font-size: 14px;
356
+ }
357
+ .tones-tags .chip .x:hover { background: rgba(0,0,0,0.5); }
358
+ .tones-tags .chip.ghost {
359
+ background: transparent; color: #aaa;
360
+ border: 1px dashed #555; padding: 2px 10px;
361
+ cursor: pointer; font: inherit; line-height: 1.4;
362
+ }
363
+ .tones-tags .chip.ghost:hover {
364
+ background: rgba(58,90,138,0.2); border-color: #6a8aaa; color: #ddd;
365
+ }
366
+ .tones-tags .chip.ghost:focus { outline: none; }
367
+ .tones-suggest {
368
+ position: absolute; left: 0; right: 0; top: 100%;
369
+ background: #1e1e1e; border: 1px solid #444; border-radius: 6px;
370
+ margin-top: 4px; max-height: 180px; overflow-y: auto;
371
+ z-index: 10; box-shadow: 0 4px 12px rgba(0,0,0,0.5);
372
+ }
373
+ .tones-suggest.hidden { display: none; }
374
+ .tones-suggest .opt {
375
+ padding: 6px 10px; cursor: pointer; font-size: 13px;
376
+ }
377
+ .tones-suggest .opt:hover, .tones-suggest .opt.sel { background: #3a5a8a; color: #fff; }
378
+ .resize-handle {
379
+ position: absolute; right: 0; bottom: 0;
380
+ width: 16px; height: 16px; cursor: nwse-resize;
381
+ background:
382
+ linear-gradient(135deg,
383
+ transparent 0%, transparent 55%,
384
+ #777 55%, #777 65%, transparent 65%,
385
+ transparent 75%, #777 75%, #777 85%, transparent 85%);
386
+ opacity: 0.45; border-bottom-right-radius: 8px;
387
+ }
388
+ .resize-handle:hover { opacity: 1; }
389
+
390
+ .node-footer {
391
+ display: flex; align-items: center; justify-content: center;
392
+ gap: 2px; padding: 4px;
393
+ border-top: 1px solid #383838;
394
+ font-size: 11px; color: #888;
395
+ }
396
+ .node-footer button {
397
+ background: transparent; border: 1px solid transparent;
398
+ color: #aaa; padding: 2px 8px; border-radius: 3px;
399
+ cursor: pointer; font-size: 12px; line-height: 1;
400
+ }
401
+ .node-footer button:hover:not(:disabled) { background: #3a3a3a; color: #fff; }
402
+ .node-footer button:disabled { opacity: 0.3; cursor: default; }
403
+ .node-footer .regen-btn { font-size: 14px; padding: 2px 10px; }
404
+ .node-footer .pos { padding: 0 6px; font-variant-numeric: tabular-nums; }
405
+
406
+ /* Якорь связи: выглядывает из правой грани ноды */
407
+ .node .anchor {
408
+ position: absolute; right: -10px; top: 50%; transform: translateY(-50%);
409
+ width: 18px; height: 18px; background: #6a8aaa;
410
+ border-radius: 50%; border: 2px solid #1a1a1a;
411
+ opacity: 0; cursor: crosshair; z-index: 5;
412
+ transition: opacity 0.12s, transform 0.12s, background 0.12s;
413
+ box-shadow: 0 0 0 1px rgba(106,138,170,0.3);
414
+ }
415
+ .node:hover .anchor { opacity: 1; }
416
+ .anchor:hover, .anchor.dragging { opacity: 1; background: #88aacc; transform: translateY(-50%) scale(1.3); }
417
+ .anchor:hover:not(.dragging) {
418
+ animation: anchorPulse 1.2s ease-out infinite;
419
+ }
420
+ @keyframes anchorPulse {
421
+ 0% { box-shadow: 0 0 0 1px rgba(106,138,170,0.4), 0 0 0 0 rgba(136,170,204,0.55); }
422
+ 100% { box-shadow: 0 0 0 1px rgba(106,138,170,0.4), 0 0 0 14px rgba(136,170,204,0); }
423
+ }
424
+
425
+ /* SVG-связи поверх холста */
426
+ .connections-layer {
427
+ position: absolute; left: 0; top: 0; pointer-events: none; overflow: visible;
428
+ }
429
+ .connections-layer .conn { pointer-events: auto; }
430
+ .connections-layer .hit { stroke: transparent; stroke-width: 16; fill: none; cursor: pointer; pointer-events: stroke; }
431
+ .connections-layer .line { stroke: #6a8aaa; stroke-width: 2; fill: none; pointer-events: none; }
432
+ .connections-layer .conn:hover .line { stroke: #f88; stroke-width: 3; }
433
+ .connections-layer .temp-line { stroke: #6a8aaa; stroke-width: 2; stroke-dasharray: 6 4; fill: none; pointer-events: none; }
434
+
435
+ /* Меню «что добавить» */
436
+ .add-menu {
437
+ position: fixed; background: #1e1e1e; border: 1px solid #444;
438
+ border-radius: 6px; padding: 4px; box-shadow: 0 4px 16px rgba(0,0,0,0.6);
439
+ z-index: 200; display: flex; flex-direction: column; min-width: 220px;
440
+ }
441
+ .add-menu.hidden { display: none; }
442
+ .add-menu button {
443
+ background: transparent; border: none; text-align: left;
444
+ padding: 8px 12px; border-radius: 4px; color: #ddd; font-size: 13px;
445
+ }
446
+ .add-menu button:hover { background: #2c2c2c; }
447
+
448
+ /* === Fullscreen viewer === */
449
+ .fs-modal {
450
+ position: fixed; inset: 0; background: rgba(0,0,0,0.95);
451
+ z-index: 1000; display: flex; align-items: center; justify-content: center;
452
+ }
453
+ .fs-modal.hidden { display: none; }
454
+ .fs-stage { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; padding: 32px; }
455
+ .fs-stage img, .fs-stage video {
456
+ max-width: 100%; max-height: 100%; object-fit: contain;
457
+ }
458
+ .fs-modal #fsClose {
459
+ position: absolute; top: 16px; right: 16px;
460
+ background: rgba(0,0,0,0.6); border: 1px solid #444; color: #fff;
461
+ font-size: 22px; line-height: 1; padding: 4px 12px; border-radius: 4px;
462
+ cursor: pointer;
463
+ }
464
+ .fs-modal #fsClose:hover { background: rgba(255,68,68,0.7); }
465
+
466
+ /* === Реплики (боковая панель справа) === */
467
+ .repliques-panel {
468
+ width: 380px; background: #1c1c1c; border-left: 1px solid #333;
469
+ display: flex; flex-direction: column; flex-shrink: 0; overflow: hidden;
470
+ }
471
+ .repliques-panel.hidden { display: none; }
472
+ .repliques-header {
473
+ padding: 10px 12px; border-bottom: 1px solid #333;
474
+ display: flex; align-items: center; gap: 6px; font-size: 13px;
475
+ }
476
+ .repliques-header .spacer { flex: 1; }
477
+ .repliques-header button {
478
+ background: transparent; border: none; color: #888;
479
+ font-size: 18px; padding: 0 6px; cursor: pointer; line-height: 1;
480
+ }
481
+ .repliques-header button:hover { color: #f88; }
482
+ .repliques-body {
483
+ flex: 1; padding: 10px 12px; display: flex; flex-direction: column;
484
+ gap: 8px; overflow-y: auto;
485
+ }
486
+ .repliques-body select {
487
+ background: #1e1e1e; color: #ddd; border: 1px solid #333;
488
+ padding: 6px 8px; border-radius: 3px; font-family: inherit;
489
+ }
490
+ .replique-row {
491
+ display: flex; gap: 6px; padding: 6px;
492
+ background: #1e1e1e; border: 1px solid #333; border-radius: 4px;
493
+ align-items: flex-start;
494
+ cursor: pointer; transition: border-color 0.1s;
495
+ }
496
+ .replique-row.selected { border-color: #6a8aaa; background: #1f2a3a; }
497
+ .replique-row textarea {
498
+ flex: 1; min-height: 36px; resize: vertical;
499
+ background: #161616; color: #ddd; border: 1px solid #333;
500
+ border-radius: 3px; padding: 4px 6px; font-family: inherit; font-size: 12px;
501
+ line-height: 1.3;
502
+ }
503
+ .replique-row .play-btn {
504
+ flex: 0 0 28px; width: 28px; height: 28px; padding: 0;
505
+ background: #2c2c2c; border: 1px solid #444; border-radius: 50%;
506
+ cursor: pointer; color: #ddd; font-size: 12px;
507
+ }
508
+ .replique-row .play-btn:hover { background: #3a5a8a; border-color: #4a6a9a; color: #fff; }
509
+ .replique-row .play-btn.playing { background: #c44; border-color: #d55; }
510
+ .replique-row .replique-status { font-size: 9px; color: #888; min-height: 10px; flex: 0 0 auto; }
511
+ .replique-row .replique-status.error { color: #e07a6a; }
512
+ .replique-actions {
513
+ display: flex; gap: 4px; flex-wrap: wrap;
514
+ padding: 8px 0; border-top: 1px solid #333; margin-top: 4px;
515
+ }
516
+ .replique-actions button { flex: 1; min-width: 100px; font-size: 12px; }
517
+
518
+ /* === Source frame в gen-модалке === */
519
+ .source-ref {
520
+ display: flex; align-items: center; gap: 10px;
521
+ padding: 8px; background: #1e1e1e; border: 1px solid #333; border-radius: 4px;
522
+ }
523
+ .source-ref img {
524
+ width: 64px; height: 64px; object-fit: cover; border-radius: 3px;
525
+ background: #000; flex-shrink: 0;
526
+ }
527
+ .source-ref .name {
528
+ flex: 1; font-size: 12px; color: #ccc; word-break: break-all;
529
+ line-height: 1.4;
530
+ }
531
+ .source-ref .name .hint { display: block; color: #888; font-size: 10px; margin-top: 2px; }
532
+ .modal-card label .hint { color: #777; font-size: 10px; text-transform: none; letter-spacing: 0; }
533
+ .source-ref button {
534
+ background: #2a2a2a; border: 1px solid #444; color: #ccc;
535
+ border-radius: 3px; padding: 4px 10px; cursor: pointer; font-size: 13px;
536
+ }
537
+ .source-ref button:hover { background: #383838; color: #fff; }
538
+ .source-ref.disabled { opacity: 0.4; }
539
+ .source-ref.disabled .name { text-decoration: line-through; }
540
+
541
+ /* === Picker chips для персонажей === */
542
+ .picker-chips {
543
+ display: flex; flex-wrap: wrap; gap: 6px;
544
+ padding: 8px; background: #1e1e1e; border: 1px solid #333; border-radius: 4px;
545
+ min-height: 38px;
546
+ }
547
+ .picker-chips:empty::before {
548
+ content: 'нет персонажей в проекте'; color: #666; font-size: 12px;
549
+ }
550
+ .picker-chip {
551
+ display: inline-flex; align-items: center; gap: 6px;
552
+ background: #2a2a2a; border: 1px solid #444; color: #ccc;
553
+ padding: 4px 10px; border-radius: 14px; cursor: pointer; font-size: 12px;
554
+ user-select: none;
555
+ }
556
+ .picker-chip:hover { background: #333; }
557
+ .picker-chip.active {
558
+ background: #2d4a6a; border-color: #6a8aaa; color: #fff;
559
+ }
560
+ .picker-chip .dot {
561
+ width: 6px; height: 6px; border-radius: 50%; background: #555;
562
+ }
563
+ .picker-chip.active .dot { background: #aae06a; }
564
+
565
+ /* === Раскладка main === */
566
+ .content-row { display: flex; flex: 1; min-height: 0; overflow: hidden; }
567
+ /* === Зафиксированная панель превью на правой стороне === */
568
+ :root { --preview-w: 420px; --preview-collapsed: 36px; }
569
+ /* БЕЗ transition на padding-right/width: оба не композируются,
570
+ анимация прогоняла full-tree layout ~470ms каждый кадр × 11 кадров =
571
+ 1+ секунды затыка при collapse-toggle. Snap-collapse — мгновенно. */
572
+ body { padding-right: var(--preview-w); }
573
+ body.preview-collapsed { padding-right: var(--preview-collapsed); }
574
+ .preview-panel {
575
+ position: fixed; top: 0; right: 0; bottom: 0;
576
+ width: var(--preview-w);
577
+ background: #1c1c1c; border-left: 1px solid #333;
578
+ z-index: 40; overflow: hidden;
579
+ display: flex; flex-direction: column;
580
+ }
581
+ .preview-panel.collapsed { width: var(--preview-collapsed); }
582
+ .preview-resize-handle {
583
+ position: absolute; left: 0; top: 0; bottom: 0; width: 5px;
584
+ cursor: ew-resize; z-index: 2;
585
+ }
586
+ .preview-resize-handle:hover { background: rgba(106,138,170,0.4); }
587
+ .preview-panel.collapsed .preview-resize-handle { display: none; }
588
+ .preview-header {
589
+ padding: 8px 10px; border-bottom: 1px solid #333;
590
+ display: flex; align-items: center; gap: 6px; font-size: 13px;
591
+ user-select: none; background: #232323;
592
+ flex-shrink: 0;
593
+ }
594
+ .preview-header .spacer { flex: 1; }
595
+ .preview-header button {
596
+ background: transparent; border: none; color: #888;
597
+ font-size: 16px; padding: 0 6px; cursor: pointer; line-height: 1;
598
+ }
599
+ .preview-header button:hover { color: #ddd; }
600
+ .preview-panel.collapsed .preview-header {
601
+ flex-direction: column; align-items: center; gap: 8px;
602
+ padding: 10px 4px; cursor: pointer;
603
+ height: 100%; border-bottom: none;
604
+ justify-content: flex-start;
605
+ }
606
+ .preview-panel.collapsed .preview-header strong {
607
+ writing-mode: vertical-rl; transform: rotate(180deg);
608
+ font-size: 12px; letter-spacing: 0.5px;
609
+ }
610
+ .preview-panel.collapsed .preview-header .spacer { display: none; }
611
+ .preview-panel.collapsed .preview-header #previewToggle { transform: rotate(180deg); }
612
+ .preview-panel .preview-stage {
613
+ flex: 1; position: relative; background: #000;
614
+ display: flex; align-items: center; justify-content: center;
615
+ min-height: 0;
616
+ }
617
+ .preview-panel.collapsed .preview-stage { display: none; }
618
+ .preview-panel .timeline-preview,
619
+ .preview-panel .timeline-preview-img {
620
+ position: absolute; inset: 0; width: 100%; height: 100%;
621
+ object-fit: contain; background: #000;
622
+ }
623
+ .preview-panel .timeline-preview-img.hidden,
624
+ .preview-panel .timeline-preview.hidden { display: none; }
625
+
626
+ /* === Таймлайн (нижняя панель, full-width) === */
627
+ .timeline-panel {
628
+ border-top: 1px solid #333; background: #1c1c1c;
629
+ display: flex; flex-direction: column; flex-shrink: 0;
630
+ max-height: 40vh; min-height: 240px; overflow: hidden;
631
+ }
632
+ .timeline-panel.hidden { display: none; }
633
+ .timeline-header {
634
+ padding: 10px 12px; border-bottom: 1px solid #333;
635
+ display: flex; align-items: center; gap: 6px; font-size: 13px;
636
+ }
637
+ .timeline-header .spacer { flex: 1; }
638
+ .timeline-body { padding: 8px 12px; display: flex; flex-direction: column; gap: 8px; flex: 1; overflow: hidden; }
639
+ .timeline-tracks {
640
+ position: relative;
641
+ display: flex; flex-direction: column; gap: 6px;
642
+ overflow: auto;
643
+ padding: 8px; background: #161616; border-radius: 4px;
644
+ flex: 1; min-height: 0;
645
+ }
646
+ .timeline-ruler {
647
+ position: sticky; top: 0; left: 0; height: 18px;
648
+ background: #161616; border-bottom: 1px solid #2a2a2a;
649
+ z-index: 8; min-width: 100%;
650
+ }
651
+ .timeline-ruler .ruler-content {
652
+ position: relative; height: 100%; padding-left: 138px;
653
+ }
654
+ .timeline-ruler .tick {
655
+ position: absolute; top: 0; bottom: 0;
656
+ color: #777; font-size: 9px; font-family: ui-monospace, monospace;
657
+ border-left: 1px solid #2a2a2a; padding: 2px 4px;
658
+ }
659
+ .timeline-playhead {
660
+ position: absolute; top: 0; bottom: 0; width: 2px;
661
+ background: #f55; pointer-events: none; z-index: 9;
662
+ }
663
+ .timeline-playhead .ph-handle {
664
+ position: absolute; top: 0; left: -6px;
665
+ width: 14px; height: 14px; background: #f55;
666
+ border-radius: 50%; cursor: ew-resize; pointer-events: auto;
667
+ box-shadow: 0 0 0 2px rgba(255,85,85,0.3);
668
+ }
669
+ .timeline-playhead .ph-handle:hover { transform: scale(1.15); }
670
+ .timeline-track {
671
+ display: flex; align-items: stretch; gap: 0;
672
+ background: #1c1c1c; border-radius: 4px;
673
+ min-height: 90px;
674
+ }
675
+ .timeline-track[data-track-kind="audio"] { min-height: 36px; }
676
+ .timeline-track[data-track-kind="audio"] .track-clips { min-height: 30px; padding: 2px 0; }
677
+ .timeline-track[data-track-kind="audio"] .timeline-clip {
678
+ font-size: 11px; padding: 3px 8px; gap: 0;
679
+ background: #2d7a3a; border-color: #1f5828; color: #fff;
680
+ }
681
+ .timeline-track[data-track-kind="audio"] .timeline-clip img,
682
+ .timeline-track[data-track-kind="audio"] .timeline-clip video,
683
+ .timeline-track[data-track-kind="audio"] .timeline-clip .clip-type,
684
+ .timeline-track[data-track-kind="audio"] .timeline-clip .clip-dur { display: none; }
685
+ .timeline-track[data-track-kind="audio"] .timeline-clip .clip-name {
686
+ color: #fff; font-size: 11px; padding: 0; line-height: 1.4;
687
+ font-weight: 500;
688
+ }
689
+ .timeline-track[data-track-kind="audio"] .track-meta { padding: 4px 8px; }
690
+ .timeline-track[data-track-kind="audio"] .track-meta .track-actions { display: none; }
691
+ .timeline-track[data-track-kind="audio"] .timeline-clip .clip-x {
692
+ width: 14px; height: 14px; font-size: 9px;
693
+ background: rgba(0,0,0,0.4);
694
+ }
695
+
696
+ /* Placeholder при перетаскивании клипа */
697
+ .timeline-clip-placeholder {
698
+ position: absolute; top: 4px; bottom: 4px;
699
+ background: rgba(106,138,170,0.2);
700
+ border: 2px dashed #6a8aaa; border-radius: 4px;
701
+ pointer-events: none; z-index: 1;
702
+ }
703
+ .timeline-clip-placeholder.audio {
704
+ background: rgba(45,122,58,0.25);
705
+ border-color: #2d7a3a;
706
+ }
707
+ .timeline-clip-placeholder.replace {
708
+ background: rgba(224,122,106,0.25);
709
+ border-color: #e07a6a;
710
+ border-style: solid;
711
+ }
712
+ .timeline-clip.replace-target {
713
+ outline: 2px solid #e07a6a; outline-offset: -2px;
714
+ }
715
+ .timeline-clip .clip-status {
716
+ position: absolute; inset: 0; z-index: 4;
717
+ display: flex; align-items: center; justify-content: center; gap: 6px;
718
+ background: rgba(0,0,0,0.55); border-radius: 3px; pointer-events: none;
719
+ font-size: 11px; color: #ddd; padding: 4px 8px; text-align: center;
720
+ }
721
+ .timeline-clip .clip-status.error { background: rgba(80,20,20,0.7); color: #f88; }
722
+ .timeline-clip .clip-status .spinner {
723
+ width: 14px; height: 14px; border-width: 2px;
724
+ }
725
+ .timeline-clip.is-generating { border-color: #6a8aaa; box-shadow: 0 0 0 1px rgba(106,138,170,0.4) inset; }
726
+ .timeline-clip.is-error { border-color: #b04040; }
727
+ .timeline-clip.disabled {
728
+ opacity: 0.45; filter: grayscale(0.7);
729
+ background-image: repeating-linear-gradient(45deg,
730
+ transparent 0 8px, rgba(0,0,0,0.25) 8px 16px);
731
+ }
732
+ .timeline-clip.disabled .clip-name { text-decoration: line-through; }
733
+ .timeline-clip.grouped {
734
+ border-top: 3px solid var(--group-color, #6a8aaa);
735
+ }
736
+ .timeline-clip.grouped::before {
737
+ content: '🔗'; position: absolute; top: 4px; right: 6px;
738
+ font-size: 10px; opacity: 0.6; pointer-events: none; z-index: 4;
739
+ }
740
+ .timeline-clip.selected {
741
+ box-shadow: 0 0 0 2px #6a8aaa, 0 0 8px rgba(106,138,170,0.5);
742
+ border-color: #6a8aaa;
743
+ }
744
+ .timeline-track.selected .track-meta {
745
+ background: #2d4a6a; border-right-color: #6a8aaa;
746
+ }
747
+ .timeline-clip .clip-handle {
748
+ position: absolute; top: 0; bottom: 0; width: 6px;
749
+ cursor: ew-resize; z-index: 3; background: transparent;
750
+ }
751
+ .timeline-clip .clip-handle:hover,
752
+ .timeline-clip .clip-handle.active { background: rgba(106,138,170,0.55); }
753
+ .timeline-clip .clip-handle.left { left: 0; border-left: 2px solid transparent; }
754
+ .timeline-clip .clip-handle.right { right: 0; border-right: 2px solid transparent; }
755
+ .timeline-clip .clip-handle.left:hover { border-left-color: #6a8aaa; }
756
+ .timeline-clip .clip-handle.right:hover { border-right-color: #6a8aaa; }
757
+ .timeline-track .track-meta {
758
+ position: sticky; left: 0; z-index: 5;
759
+ background: #16243a; padding: 6px 8px;
760
+ border-right: 1px solid #1f3251;
761
+ }
762
+ .timeline-track .track-meta {
763
+ width: 130px; flex-shrink: 0; display: flex; flex-direction: column;
764
+ gap: 4px; border-right: 1px solid #2a2a2a; padding-right: 8px;
765
+ }
766
+ .timeline-track .track-name {
767
+ background: transparent; border: 1px solid transparent; color: #ddd;
768
+ font-size: 12px; padding: 2px 4px; border-radius: 3px; outline: none;
769
+ }
770
+ .timeline-track .track-name:hover { border-color: #383838; }
771
+ .timeline-track .track-name:focus { background: #1e1e1e; border-color: #6a8aaa; }
772
+ .timeline-track .track-actions {
773
+ display: flex; gap: 4px; margin-top: auto;
774
+ }
775
+ .timeline-track .track-actions button {
776
+ flex: 1; background: transparent; border: 1px solid #383838; color: #888;
777
+ padding: 2px 4px; border-radius: 3px; font-size: 10px;
778
+ cursor: pointer; line-height: 1;
779
+ }
780
+ .timeline-track .track-actions button:hover { color: #ddd; border-color: #555; }
781
+ .timeline-track .track-kind {
782
+ font-size: 10px; color: #666; text-transform: uppercase; letter-spacing: 0.5px;
783
+ }
784
+ .timeline-track .track-clips {
785
+ flex: 1; position: relative; min-height: 80px; min-width: 0;
786
+ padding: 4px 0;
787
+ }
788
+ .timeline-track .track-clips:empty::before {
789
+ content: '— пусто —'; color: #555; font-size: 11px;
790
+ position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
791
+ }
792
+ .add-track-btn {
793
+ align-self: flex-start; background: transparent; color: #888;
794
+ border: 1px dashed #444; padding: 6px 12px; border-radius: 4px;
795
+ cursor: pointer; font-size: 12px;
796
+ }
797
+ .add-track-btn:hover { border-color: #666; color: #ddd; }
798
+ .timeline-clip {
799
+ position: relative; min-width: 40px; flex-shrink: 0;
800
+ background: #2a2a2a; border: 1px solid #444; border-radius: 4px;
801
+ padding: 4px; display: flex; flex-direction: column; gap: 4px;
802
+ cursor: grab; overflow: hidden;
803
+ }
804
+ .timeline-clip.dragging { opacity: 0.5; }
805
+ .timeline-clip img, .timeline-clip video {
806
+ width: 70px; height: 70px; object-fit: cover; border-radius: 2px;
807
+ background: #000; display: block;
808
+ position: absolute; left: 50%; top: 4px;
809
+ transform: translateX(-50%);
810
+ pointer-events: none;
811
+ }
812
+ .timeline-clip .clip-name {
813
+ font-size: 10px; color: #aaa; overflow: hidden; text-overflow: ellipsis;
814
+ white-space: nowrap;
815
+ }
816
+ /* В видео-треке имя поверх нижней части превью */
817
+ .timeline-track[data-track-kind="video"] .timeline-clip .clip-name {
818
+ position: absolute; left: 4px; right: 4px; bottom: 4px;
819
+ text-align: center; padding: 1px 4px;
820
+ background: rgba(0,0,0,0.7); border-radius: 2px;
821
+ color: #fff; z-index: 2;
822
+ }
823
+ .timeline-track[data-track-kind="video"] .timeline-clip .clip-type,
824
+ .timeline-track[data-track-kind="video"] .timeline-clip .clip-dur { display: none; }
825
+ .timeline-clip .clip-dur {
826
+ display: flex; align-items: center; gap: 4px; font-size: 10px; color: #888;
827
+ }
828
+ .timeline-clip .clip-dur input {
829
+ width: 48px; background: #1e1e1e; border: 1px solid #383838;
830
+ color: #ddd; font-size: 10px; padding: 1px 3px; border-radius: 2px;
831
+ font-family: ui-monospace, monospace;
832
+ }
833
+ .timeline-clip .clip-x {
834
+ position: absolute; top: 2px; right: 2px;
835
+ background: rgba(0,0,0,0.6); color: #fff; border: none; border-radius: 50%;
836
+ width: 18px; height: 18px; cursor: pointer; font-size: 11px;
837
+ line-height: 1; padding: 0; opacity: 0;
838
+ }
839
+ .timeline-clip:hover .clip-x { opacity: 1; }
840
+ .timeline-clip .clip-x:hover { background: #c44; }
841
+ .timeline-clip .clip-type {
842
+ position: absolute; top: 2px; left: 2px;
843
+ font-size: 9px; padding: 1px 4px; border-radius: 2px;
844
+ background: rgba(0,0,0,0.6); color: #fff; text-transform: uppercase;
845
+ }
846
+ .timeline-status { font-size: 11px; color: #888; min-height: 14px; }
847
+ .timeline-status.error { color: #e07a6a; }
848
+ .node.selected { border-color: #6a8aaa; box-shadow: 0 0 0 2px rgba(106,138,170,0.4), 0 4px 12px rgba(0,0,0,0.4); }
849
+ .node.picked { border-color: #f0a040; box-shadow: 0 0 0 2px rgba(240,160,64,0.55), 0 4px 12px rgba(0,0,0,0.4); }
850
+ .node-header {
851
+ padding: 4px 8px; border-bottom: 1px solid #383838;
852
+ display: flex; justify-content: space-between; align-items: center; gap: 6px;
853
+ min-height: 22px; cursor: move;
854
+ }
855
+ .node-header .name {
856
+ flex: 1; min-width: 0;
857
+ color: #ccc; font-size: 12px; line-height: 1.2;
858
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
859
+ user-select: none;
860
+ }
861
+ .node-header .name.empty { color: #555; font-style: italic; }
862
+ .node-header .delete { cursor: pointer; color: #888; font-size: 16px; line-height: 1; padding: 0 4px; flex-shrink: 0; }
863
+ .node-header .delete:hover { color: #f88; }
864
+
865
+ /* Имя ноды убрано из header — переименование через ПКМ → ✏ Переименовать.
866
+ Хедер теперь содержит только × delete (и слева пустое место для drag-grab). */
867
+ .node-body { padding: 12px; }
868
+ .node-body video, .node-body audio {
869
+ width: 100%; max-height: 220px; background: #000;
870
+ border-radius: 4px; display: block; object-fit: contain;
871
+ }
872
+ .node.image-node .node-body { padding: 0; overflow: hidden; border-radius: 0 0 8px 8px; }
873
+ .node.image-node .node-body img { width: 100%; height: auto; display: block; }
874
+ /* Placeholder отображается до lazy-гидрации media — на оффскрин-нодах
875
+ не дёргаем диск/декод. Размером совпадает с типичной картинкой/видео. */
876
+ .media-placeholder {
877
+ display: flex; align-items: center; justify-content: center;
878
+ min-height: 140px; font-size: 32px; opacity: 0.35;
879
+ background: #1e1e1e; border-radius: 4px;
880
+ }
881
+ .node.image-node .media-placeholder,
882
+ .node.video-node .media-placeholder { min-height: 200px; border-radius: 0; }
883
+ /* Thumbnail отображается до загрузки full media — мгновенный визуальный feedback. */
884
+ .media-thumb {
885
+ width: 100%; height: auto; display: block;
886
+ image-rendering: -webkit-optimize-contrast; /* slight crispness for upscaled thumb */
887
+ }
888
+ .node.image-node .media-thumb,
889
+ .node.video-node .media-thumb { display: block; }
890
+ .node-body textarea {
891
+ width: 100%; min-height: 100px; background: #1e1e1e; color: #e0e0e0;
892
+ border: 1px solid #383838; border-radius: 4px; padding: 8px;
893
+ font-family: inherit; font-size: 13px; resize: vertical; outline: none;
894
+ }
895
+ .node-body textarea:focus { border-color: #6a8aaa; }
896
+ .node-body .filename {
897
+ font-size: 11px; color: #888; margin-bottom: 8px; word-break: break-all;
898
+ font-family: ui-monospace, "SF Mono", Menlo, monospace;
899
+ }
900
+ .node-body .missing { color: #e07a6a; font-size: 12px; padding: 8px 0; }
901
+
902
+ .gen-pending {
903
+ display: flex; flex-direction: column; gap: 10px; padding: 18px;
904
+ align-items: center; justify-content: center;
905
+ border: 1px dashed #444; border-radius: 4px;
906
+ background: #1e1e1e; min-height: 140px;
907
+ }
908
+ /* В text-нодах pending занимает всё тело: убираем рамку, тянемся флексом. */
909
+ .node.text-node .node-body .gen-pending {
910
+ flex: 1; width: 100%; height: 100%; min-height: 100px;
911
+ border: none; border-radius: 0;
912
+ background: #141414;
913
+ }
914
+ .gen-pending .prompt-preview {
915
+ font-size: 11px; color: #aaa; line-height: 1.4; text-align: center;
916
+ max-height: 60px; overflow-y: auto; word-break: break-word;
917
+ }
918
+ .gen-pending .state-text { font-size: 11px; color: #888; }
919
+ .gen-error {
920
+ background: #3a1e1e; border: 1px solid #6a3a3a; border-radius: 4px;
921
+ padding: 10px; color: #e07a6a; font-size: 12px; line-height: 1.4;
922
+ word-break: break-word;
923
+ }
924
+ .gen-error button { margin-top: 8px; }
925
+
926
+ .empty {
927
+ position: absolute; inset: 0;
928
+ display: flex; align-items: center; justify-content: center;
929
+ color: #666; font-size: 15px; flex-direction: column; gap: 14px;
930
+ text-align: center; padding: 24px; pointer-events: none;
931
+ }
932
+ .empty h3 { color: #aaa; font-size: 18px; }
933
+ .empty p { max-width: 420px; line-height: 1.6; }
934
+ .empty.hidden { display: none; }
935
+
936
+ .unsupported { margin: auto; max-width: 540px; padding: 32px; background: #2a2a2a; border-radius: 12px; border: 1px solid #444; }
937
+ .unsupported h1 { margin-bottom: 12px; }
938
+ .unsupported p { line-height: 1.6; margin-bottom: 8px; color: #bbb; }
939
+ .unsupported code { background: #1e1e1e; padding: 2px 6px; border-radius: 3px; font-family: ui-monospace, monospace; }
940
+
941
+ /* === Modal === */
942
+ .modal { position: fixed; inset: 0; background: rgba(0,0,0,0.65); display: flex; align-items: center; justify-content: center; z-index: 100; }
943
+ .modal.hidden { display: none; }
944
+ .modal-card {
945
+ background: #2a2a2a; border: 1px solid #444; border-radius: 12px;
946
+ padding: 24px; width: 540px; max-width: 92vw; max-height: 90vh;
947
+ overflow-y: auto; display: flex; flex-direction: column; gap: 14px;
948
+ position: relative;
949
+ }
950
+ .modal-card h3 { font-size: 16px; }
951
+ .modal-card label { display: flex; flex-direction: column; gap: 6px; font-size: 12px; color: #aaa; text-transform: uppercase; letter-spacing: 0.5px; }
952
+ .modal-card input, .modal-card textarea, .modal-card select {
953
+ background: #1e1e1e; color: #e0e0e0; border: 1px solid #383838;
954
+ border-radius: 4px; padding: 8px 10px; font-family: inherit; font-size: 14px;
955
+ outline: none; text-transform: none; letter-spacing: normal;
956
+ }
957
+ .modal-card input:focus, .modal-card textarea:focus, .modal-card select:focus { border-color: #6a8aaa; }
958
+ .modal-card textarea { resize: vertical; min-height: 100px; }
959
+ .modal-actions { display: flex; gap: 8px; align-items: center; margin-top: 4px; }
960
+ .modal-actions .spacer { flex: 1; }
961
+ .seg-control { display: flex; }
962
+ .seg-control .seg { flex: 1; background: #1e1e1e; border: 1px solid #383838; border-radius: 0; }
963
+ .seg-control .seg:first-child { border-radius: 4px 0 0 4px; }
964
+ .seg-control .seg:last-child { border-radius: 0 4px 4px 0; border-left: none; }
965
+ .seg-control .seg.active { background: #3a5a8a; border-color: #4a6a9a; color: #fff; }
966
+ .status { font-size: 12px; color: #aaa; display: flex; align-items: center; gap: 8px; }
967
+ .status.error { color: #e07a6a; }
968
+ .spinner { width: 14px; height: 14px; border: 2px solid #444; border-top-color: #6a8aaa; border-radius: 50%; animation: spin 0.9s linear infinite; }
969
+ .spinner.lg { width: 32px; height: 32px; border-width: 3px; }
970
+ @keyframes spin { to { transform: rotate(360deg); } }
971
+
972
+ /* mention popup */
973
+ .mention-popup {
974
+ background: #1e1e1e; border: 1px solid #444; border-radius: 6px;
975
+ box-shadow: 0 4px 12px rgba(0,0,0,0.5);
976
+ max-height: 240px; overflow-y: auto;
977
+ margin-top: 6px;
978
+ }
979
+ .mention-popup.hidden { display: none; }
980
+ .mention-popup .mit {
981
+ padding: 6px 12px; cursor: pointer; font-size: 13px;
982
+ display: flex; gap: 8px; align-items: center;
983
+ }
984
+ .mention-popup .mit.selected { background: #3a5a8a; color: #fff; }
985
+ .mention-popup .mit .mtype { font-size: 9px; text-transform: uppercase; padding: 1px 5px; border-radius: 2px; background: #333; }
986
+ .mention-popup .mit .mtype.video { background: #2a4a6a; color: #aac8e6; }
987
+ .mention-popup .mit .mtype.audio { background: #6a4a2a; color: #e6c8aa; }
988
+ .mention-popup .mit .mtype.text { background: #2a6a2a; color: #c8e6aa; }
989
+ .mention-popup .mit .mtype.image { background: #5a2a6a; color: #e6aac8; }
990
+ .mention-popup .empty-msg { padding: 8px 12px; color: #666; font-size: 12px; }
991
+
992
+ .settings-row { display: flex; flex-direction: column; gap: 4px; }
993
+ .settings-row .lbl { font-size: 10px; text-transform: uppercase; color: #888; letter-spacing: 0.5px; }
994
+ .settings-row .val {
995
+ background: #1e1e1e; border: 1px solid #383838; border-radius: 4px;
996
+ padding: 8px 10px; font-size: 13px;
997
+ font-family: ui-monospace, "SF Mono", Menlo, monospace;
998
+ white-space: pre-wrap; word-break: break-word; max-height: 200px; overflow-y: auto;
999
+ }
1000
+ .settings-row .val.muted { color: #888; font-style: italic; }
1001
+ .settings-row .chips { display: flex; flex-wrap: wrap; gap: 4px; }
1002
+ .settings-row .chip {
1003
+ font-size: 11px; background: #1e1e1e; border: 1px solid #383838;
1004
+ padding: 3px 8px; border-radius: 11px;
1005
+ }
1006
+
1007
+ /* === second style block (originally lines 1221-1231) === */
1008
+ #txLogBody table { width: 100%; border-collapse: collapse; font-size: 12px; }
1009
+ #txLogBody th { text-align: left; padding: 8px 10px; background: #262626;
1010
+ color: #888; font-weight: 500; border-bottom: 1px solid #333;
1011
+ position: sticky; top: 0; }
1012
+ #txLogBody td { padding: 7px 10px; border-bottom: 1px solid #2a2a2a; color: #d4d4d4; vertical-align: top; }
1013
+ #txLogBody tr:hover td { background: #232323; }
1014
+ #txLogBody .tx-amount-neg { color: #ef4444; font-family: ui-monospace, monospace; text-align: right; }
1015
+ #txLogBody .tx-amount-pos { color: #16a34a; font-family: ui-monospace, monospace; text-align: right; }
1016
+ #txLogBody .tx-type { color: #888; font-size: 11px; }
1017
+ #txLogBody .tx-model { color: #aaa; font-size: 11px; font-family: ui-monospace, monospace; }
1018
+ #txLogBody .tx-empty { padding: 32px; text-align: center; color: #888; }