@hugpy/video-intelligence-ui 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 (177) hide show
  1. package/LICENSE +41 -0
  2. package/assets/sampels/images/bycycle/bd8603bad35d4f179f4d1134d8faf2ee.mp4 +0 -0
  3. package/assets/sampels/images/bycycle/frame_00000.png +0 -0
  4. package/assets/sampels/images/bycycle/frame_00001.png +0 -0
  5. package/assets/sampels/images/bycycle/frame_00002.png +0 -0
  6. package/assets/sampels/images/bycycle/frame_00003.png +0 -0
  7. package/assets/sampels/images/bycycle/frame_00004.png +0 -0
  8. package/assets/sampels/images/bycycle/frame_00005.png +0 -0
  9. package/assets/sampels/images/forrest/ff23090611b844a6b85c224f388bd542.mp4 +0 -0
  10. package/assets/sampels/images/forrest/frame_00000.png +0 -0
  11. package/assets/sampels/images/forrest/frame_00001.png +0 -0
  12. package/assets/sampels/images/forrest/frame_00002.png +0 -0
  13. package/assets/sampels/images/forrest/frame_00003.png +0 -0
  14. package/assets/sampels/images/forrest/frame_00004.png +0 -0
  15. package/assets/sampels/images/forrest/frame_00005.png +0 -0
  16. package/assets/sampels/images/shenzo/e1a4fcd623084ab4a986d11c8cd00db8.mp4 +0 -0
  17. package/assets/sampels/images/shenzo/frame_00000.png +0 -0
  18. package/assets/sampels/images/shenzo/frame_00001.png +0 -0
  19. package/assets/sampels/images/shenzo/frame_00002.png +0 -0
  20. package/assets/sampels/images/shenzo/frame_00003.png +0 -0
  21. package/assets/sampels/images/shenzo/frame_00004.png +0 -0
  22. package/assets/sampels/images/shenzo/frame_00005.png +0 -0
  23. package/assets/sampels/scenes/man.mp4 +0 -0
  24. package/assets/sampels/scenes/woman.mp4 +0 -0
  25. package/assets/sampels/studio_movies/caverns/_assembly/contrib_00.mp4 +0 -0
  26. package/assets/sampels/studio_movies/caverns/movie.json +67 -0
  27. package/assets/sampels/studio_movies/caverns/movie.mp4 +0 -0
  28. package/assets/sampels/studio_movies/caverns/segment_00/5eea7770a554fef0c3bdc38f8f38b925dcf75f13c015d423e8eadec47d41bf6b/clip.mp4 +0 -0
  29. package/assets/sampels/studio_movies/caverns/segment_00/5eea7770a554fef0c3bdc38f8f38b925dcf75f13c015d423e8eadec47d41bf6b/manifest.json +87 -0
  30. package/assets/sampels/studio_movies/caverns/segment_00/5eea7770a554fef0c3bdc38f8f38b925dcf75f13c015d423e8eadec47d41bf6b/provenance.json +7 -0
  31. package/assets/sampels/studio_movies/caverns/segment_01/branch.png +0 -0
  32. package/assets/sampels/studio_movies/keeper-matrix/_assembly/concat_list.txt +2 -0
  33. package/assets/sampels/studio_movies/keeper-matrix/_assembly/contrib_00.mp4 +0 -0
  34. package/assets/sampels/studio_movies/keeper-matrix/_assembly/contrib_01.mp4 +0 -0
  35. package/assets/sampels/studio_movies/keeper-matrix/movie.json +81 -0
  36. package/assets/sampels/studio_movies/keeper-matrix/movie.mp4 +0 -0
  37. package/assets/sampels/studio_movies/keeper-matrix/segment_00/64854c6a21d47ce10b23e884e12de448b25bfb0a375d33238dd0bec8056c076d/clip.mp4 +0 -0
  38. package/assets/sampels/studio_movies/keeper-matrix/segment_00/64854c6a21d47ce10b23e884e12de448b25bfb0a375d33238dd0bec8056c076d/manifest.json +87 -0
  39. package/assets/sampels/studio_movies/keeper-matrix/segment_00/64854c6a21d47ce10b23e884e12de448b25bfb0a375d33238dd0bec8056c076d/provenance.json +7 -0
  40. package/assets/sampels/studio_movies/keeper-matrix/segment_01/branch.png +0 -0
  41. package/assets/sampels/studio_movies/keeper-matrix/segment_01/cc926b44a42b90d7d291a4e50e3859458306c207a17c718086a3466e4049d6c6/clip.mp4 +0 -0
  42. package/assets/sampels/studio_movies/keeper-matrix/segment_01/cc926b44a42b90d7d291a4e50e3859458306c207a17c718086a3466e4049d6c6/manifest.json +87 -0
  43. package/assets/sampels/studio_movies/keeper-matrix/segment_01/cc926b44a42b90d7d291a4e50e3859458306c207a17c718086a3466e4049d6c6/provenance.json +7 -0
  44. package/assets/sampels/videos/a_red_paper_boat_drifting_on_a_calm_pond__gentle_ripples__mo_a62d6c28.mp4 +0 -0
  45. package/assets/sampels/videos/aurora.mp4 +0 -0
  46. package/assets/sampels/videos/dark_night.mp4 +0 -0
  47. package/assets/sampels/videos/fire_and_soul.mp4 +0 -0
  48. package/assets/sampels/videos/harsh_seas.mp4 +0 -0
  49. package/entry.tsx +74 -0
  50. package/index.html +53 -0
  51. package/package.json +42 -0
  52. package/src/assets/hugpy-mark.png +0 -0
  53. package/src/config.ts +676 -0
  54. package/src/demo/DemoBanner.tsx +54 -0
  55. package/src/demo/assets/demo-audio.mp3 +0 -0
  56. package/src/demo/assets/demo-clip.mp4 +0 -0
  57. package/src/demo/assets/demo-crop.mp4 +0 -0
  58. package/src/demo/assets/demo-poster.jpg +0 -0
  59. package/src/demo/assets/frame-1.jpg +0 -0
  60. package/src/demo/assets/frame-2.jpg +0 -0
  61. package/src/demo/assets/frame-3.jpg +0 -0
  62. package/src/demo/assets/frame-4.jpg +0 -0
  63. package/src/demo/assets/frame-5.jpg +0 -0
  64. package/src/demo/assets/frame-6.jpg +0 -0
  65. package/src/demo/assets/gen-1.jpg +0 -0
  66. package/src/demo/assets/gen-2.jpg +0 -0
  67. package/src/demo/assets.d.ts +14 -0
  68. package/src/demo/demo.css +64 -0
  69. package/src/demo/demoFetch.ts +305 -0
  70. package/src/demo/fixtures.ts +814 -0
  71. package/src/demo/index.ts +43 -0
  72. package/src/demo/mode.ts +81 -0
  73. package/src/demo/promptAssist.ts +181 -0
  74. package/src/demo/seed.ts +97 -0
  75. package/src/nav/BrandMark.css +66 -0
  76. package/src/nav/BrandMark.tsx +40 -0
  77. package/src/nav/Navbar.css +13 -0
  78. package/src/nav/Navbar.tsx +68 -0
  79. package/src/regions/SpatialRegionEditor.tsx +375 -0
  80. package/src/regions/TemporalRegionEditor.tsx +485 -0
  81. package/src/regions/types.ts +85 -0
  82. package/src/session.ts +85 -0
  83. package/src/share.ts +77 -0
  84. package/src/stations/ActivePanel.tsx +55 -0
  85. package/src/stations/ActiveProcessesStation.tsx +272 -0
  86. package/src/stations/ActiveProcessesStation.tsx.bak-sessions-20260812 +228 -0
  87. package/src/stations/AddFileBar.tsx +59 -0
  88. package/src/stations/AudioCropStation.tsx +157 -0
  89. package/src/stations/ComfyPanel.tsx +210 -0
  90. package/src/stations/ComfyStation.tsx +196 -0
  91. package/src/stations/CondensedKnobStrip.tsx +245 -0
  92. package/src/stations/ConsolePanel.tsx +82 -0
  93. package/src/stations/FrameExtractStation.tsx +148 -0
  94. package/src/stations/GenIdentityBar.tsx +412 -0
  95. package/src/stations/GenerateStation.tsx +5603 -0
  96. package/src/stations/GenerateStation.tsx.bak-tester-removal +5170 -0
  97. package/src/stations/GoalComposer.tsx +139 -0
  98. package/src/stations/ImageCropStation.tsx +153 -0
  99. package/src/stations/KnobInfo.tsx +64 -0
  100. package/src/stations/PlaceholderStation.tsx +26 -0
  101. package/src/stations/SectionTabs.tsx +276 -0
  102. package/src/stations/SharePanel.tsx +298 -0
  103. package/src/stations/SidebarRegistry.tsx +105 -0
  104. package/src/stations/StationShell.tsx +96 -0
  105. package/src/stations/StudioClipsStation.tsx +28 -0
  106. package/src/stations/WorkbenchDrawer.tsx +66 -0
  107. package/src/stations/WorkbenchSidebar.tsx +1224 -0
  108. package/src/stations/WorkbenchStation.tsx +91 -0
  109. package/src/stations/comfyEmbed.ts +79 -0
  110. package/src/stations/drawerShell.tsx +419 -0
  111. package/src/stations/ops/AudioCropCore.tsx +218 -0
  112. package/src/stations/ops/CharacterGroupsPanel.tsx +716 -0
  113. package/src/stations/ops/FrameExtractCore.tsx +695 -0
  114. package/src/stations/ops/ImageCropCore.tsx +164 -0
  115. package/src/stations/ops/VideoCropCore.tsx +193 -0
  116. package/src/stations/ops/useVideoCropJobs.ts +224 -0
  117. package/src/stations/pollCaps.ts +87 -0
  118. package/src/stations/registry.ts +191 -0
  119. package/src/stations/studio/AngleRingEditor.tsx +159 -0
  120. package/src/stations/studio/IdentitiesStation.tsx +2455 -0
  121. package/src/stations/studio/IdentityProfileControls.tsx +262 -0
  122. package/src/stations/studio/StudioActiveProcesses.tsx +224 -0
  123. package/src/stations/studio/StudioAssistLogPanel.tsx +220 -0
  124. package/src/stations/studio/StudioGenerateMode.tsx +29 -0
  125. package/src/stations/studio/StudioGenerateTab.tsx +2688 -0
  126. package/src/stations/studio/StudioItemCard.tsx +334 -0
  127. package/src/stations/studio/StudioLibraryTab.tsx +506 -0
  128. package/src/stations/studio/StudioMovieComposer.tsx +2875 -0
  129. package/src/stations/studio/StudioMovieComposer.tsx.bak-sessions-20260812 +2729 -0
  130. package/src/stations/studio/StudioMovieSessions.tsx +553 -0
  131. package/src/stations/studio/StudioMovieSessions.tsx.bak-sessions-20260812 +417 -0
  132. package/src/stations/studio/StudioPlane.tsx +302 -0
  133. package/src/stations/studio/StudioSplicedRow.tsx +476 -0
  134. package/src/stations/studio/StudioViewer.tsx +339 -0
  135. package/src/stations/studio/TurntableViewer.tsx +220 -0
  136. package/src/stations/studio/movieTimeline.ts +198 -0
  137. package/src/stations/studio/promptCard.tsx +498 -0
  138. package/src/stations/studio/studioItem.ts +87 -0
  139. package/src/stations/studio/studioShared.tsx +821 -0
  140. package/src/stations/studio/useIdentityProfiles.ts +575 -0
  141. package/src/stations/studio/useReconstruction.ts +283 -0
  142. package/src/stations/types.ts +43 -0
  143. package/src/stations/useAudioCropJobs.ts +202 -0
  144. package/src/stations/useAudioExtractJob.ts +144 -0
  145. package/src/stations/useCropJobs.ts +206 -0
  146. package/src/stations/useFrameJobs.ts +140 -0
  147. package/src/stations/useGenerateJob.ts +151 -0
  148. package/src/stations/useGenerateMovieJob.ts +177 -0
  149. package/src/stations/useGenerateRowJobs.ts +184 -0
  150. package/src/stations/useGenerateSceneJob.ts +156 -0
  151. package/src/stations/useGenerateSceneRowJobs.ts +282 -0
  152. package/src/style/app.css +4974 -0
  153. package/src/transport/client.ts +262 -0
  154. package/src/transport/telemetry.ts +97 -0
  155. package/src/video/DropReceptacle.tsx +122 -0
  156. package/src/video/PromptAssistButtons.tsx +118 -0
  157. package/src/video/composerBridge.ts +66 -0
  158. package/src/video/contract.ts +599 -0
  159. package/src/video/jobTracker.ts +702 -0
  160. package/src/video/mediaLibrary.ts +328 -0
  161. package/src/video/renderCompat.ts +512 -0
  162. package/src/video/sessionForm.ts +189 -0
  163. package/src/video/spreadAssist.ts +392 -0
  164. package/src/video/studioBridge.ts +82 -0
  165. package/src/video/useMediaJobs.ts +279 -0
  166. package/src/video/useModels.ts +413 -0
  167. package/src/video/useMoviePresets.ts +97 -0
  168. package/src/video/useMovieSessions.ts +292 -0
  169. package/src/video/usePresets.ts +167 -0
  170. package/src/video/useProjects.ts +87 -0
  171. package/src/video/usePromptAssist.ts +394 -0
  172. package/src/video/useRenderPresets.ts +221 -0
  173. package/src/video/useStudioAssistLog.ts +266 -0
  174. package/src/video/useStudioPresets.ts +128 -0
  175. package/src/video/useWorkerCalls.ts +165 -0
  176. package/tsconfig.json +17 -0
  177. package/vite.config.ts +33 -0
@@ -0,0 +1,4974 @@
1
+ @import "tailwindcss";
2
+
3
+ /* Video-intelligence arm — dark editorial-terminal tokens, matching the media
4
+ arm's family: Newsreader serif for headlines, IBM Plex Mono for labels
5
+ (fonts loaded in index.html). Kept as plain CSS component classes so the
6
+ Phase 0 shell has zero styling logic in components. */
7
+
8
+ :root {
9
+ --vi-surface: #050505;
10
+ --vi-surface-raised: #101012;
11
+ --vi-border: #26262a;
12
+ --vi-text: #e8e6e1;
13
+ --vi-text-dim: #9b988f;
14
+ --vi-accent: #8ab4ff;
15
+ --vi-mono: "IBM Plex Mono", ui-monospace, monospace;
16
+ --vi-serif: "Newsreader", Georgia, serif;
17
+ }
18
+
19
+ html,
20
+ body {
21
+ margin: 0;
22
+ background: var(--vi-surface);
23
+ color: var(--vi-text);
24
+ overflow-x: hidden;
25
+ }
26
+
27
+ #root {
28
+ min-height: 100dvh;
29
+ background: var(--vi-surface);
30
+ overflow-x: hidden;
31
+ }
32
+
33
+ .vi-shell {
34
+ height: 100dvh;
35
+ display: flex;
36
+ flex-direction: column;
37
+ font-family: var(--vi-serif);
38
+ }
39
+
40
+ .vi-header {
41
+ display: flex;
42
+ flex-wrap: wrap;
43
+ align-items: baseline;
44
+ gap: 1rem 2rem;
45
+ padding: 1rem 1.5rem;
46
+ border-bottom: 1px solid var(--vi-border);
47
+ }
48
+
49
+ .vi-brand {
50
+ display: flex;
51
+ align-items: baseline;
52
+ gap: 0.6rem;
53
+ font-family: var(--vi-mono);
54
+ }
55
+
56
+ .vi-brand-home {
57
+ color: var(--vi-accent);
58
+ text-decoration: none;
59
+ font-weight: 500;
60
+ }
61
+
62
+ .vi-brand-arm {
63
+ color: var(--vi-text);
64
+ letter-spacing: 0.04em;
65
+ }
66
+
67
+ .vi-brand-stage {
68
+ color: var(--vi-text-dim);
69
+ font-size: 0.75rem;
70
+ border: 1px solid var(--vi-border);
71
+ border-radius: 999px;
72
+ padding: 0.1rem 0.6rem;
73
+ }
74
+
75
+ .vi-tabs {
76
+ display: flex;
77
+ gap: 0.25rem;
78
+ margin-left: auto;
79
+ font-family: var(--vi-mono);
80
+ font-size: 0.85rem;
81
+ }
82
+
83
+ .vi-tab {
84
+ color: var(--vi-text-dim);
85
+ text-decoration: none;
86
+ padding: 0.35rem 0.8rem;
87
+ border-radius: 6px;
88
+ border: 1px solid transparent;
89
+ }
90
+
91
+ .vi-tab:hover {
92
+ color: var(--vi-text);
93
+ }
94
+
95
+ .vi-tab-active {
96
+ color: var(--vi-text);
97
+ background: var(--vi-surface-raised);
98
+ border-color: var(--vi-border);
99
+ }
100
+
101
+ .vi-main {
102
+ flex: 1;
103
+ min-height: 0;
104
+ display: flex;
105
+ justify-content: center;
106
+ padding: 1.25rem 1.5rem;
107
+ overflow: hidden;
108
+ }
109
+
110
+ .station-card {
111
+ max-width: 44rem;
112
+ width: 100%;
113
+ }
114
+
115
+ .station-phase {
116
+ font-family: var(--vi-mono);
117
+ font-size: 0.75rem;
118
+ color: var(--vi-accent);
119
+ letter-spacing: 0.06em;
120
+ text-transform: uppercase;
121
+ }
122
+
123
+ .station-card h2 {
124
+ margin: 0.4rem 0 0.5rem;
125
+ font-weight: 500;
126
+ font-size: 2rem;
127
+ }
128
+
129
+ .station-blurb {
130
+ color: var(--vi-text-dim);
131
+ margin: 0 0 1.5rem;
132
+ font-size: 1.05rem;
133
+ line-height: 1.5;
134
+ }
135
+
136
+ .station-planned {
137
+ list-style: none;
138
+ margin: 0;
139
+ padding: 0;
140
+ border-top: 1px solid var(--vi-border);
141
+ }
142
+
143
+ .station-planned li {
144
+ padding: 0.7rem 0.2rem;
145
+ border-bottom: 1px solid var(--vi-border);
146
+ color: var(--vi-text);
147
+ font-size: 0.95rem;
148
+ }
149
+
150
+ .station-planned li::before {
151
+ content: "○ ";
152
+ color: var(--vi-text-dim);
153
+ font-family: var(--vi-mono);
154
+ }
155
+
156
+ .station-foot {
157
+ margin-top: 1.25rem;
158
+ color: var(--vi-text-dim);
159
+ font-size: 0.85rem;
160
+ }
161
+
162
+ .station-foot code {
163
+ font-family: var(--vi-mono);
164
+ font-size: 0.8rem;
165
+ color: var(--vi-text);
166
+ }
167
+
168
+ /* ------------------------------------------------------------------------- */
169
+ /* Phase 3 — Image Crop station: buttons, spatial editor, region list, */
170
+ /* result grid. Same dark editorial-terminal tokens as the Phase 0 shell. */
171
+ /* ------------------------------------------------------------------------- */
172
+
173
+ /* The real stations want more width than the placeholder reading column. */
174
+ .station-wide {
175
+ max-width: 68rem;
176
+ }
177
+
178
+ /* ---- buttons (shared) ---- */
179
+ .vi-btn {
180
+ font-family: var(--vi-mono);
181
+ font-size: 0.85rem;
182
+ padding: 0.4rem 0.9rem;
183
+ border-radius: 6px;
184
+ border: 1px solid var(--vi-border);
185
+ background: var(--vi-surface-raised);
186
+ color: var(--vi-text);
187
+ cursor: pointer;
188
+ line-height: 1.2;
189
+ }
190
+
191
+ .vi-btn:hover {
192
+ border-color: var(--vi-accent);
193
+ }
194
+
195
+ .vi-btn:disabled {
196
+ opacity: 0.45;
197
+ cursor: not-allowed;
198
+ border-color: var(--vi-border);
199
+ }
200
+
201
+ .vi-btn-accent {
202
+ color: var(--vi-surface);
203
+ background: var(--vi-accent);
204
+ border-color: var(--vi-accent);
205
+ font-weight: 500;
206
+ }
207
+
208
+ .vi-btn-accent:hover {
209
+ filter: brightness(1.08);
210
+ }
211
+
212
+ .vi-btn-ghost {
213
+ background: transparent;
214
+ color: var(--vi-text-dim);
215
+ }
216
+
217
+ .vi-btn-ghost:hover {
218
+ color: var(--vi-text);
219
+ }
220
+
221
+ .vi-btn-sm {
222
+ font-size: 0.75rem;
223
+ padding: 0.2rem 0.55rem;
224
+ }
225
+
226
+ /* ---- source bar / file picker ---- */
227
+ .vi-crop-source-bar {
228
+ display: flex;
229
+ flex-wrap: wrap;
230
+ align-items: center;
231
+ gap: 0.75rem 1rem;
232
+ margin-bottom: 1rem;
233
+ }
234
+
235
+ .vi-file-label {
236
+ display: inline-flex;
237
+ align-items: center;
238
+ }
239
+
240
+ .vi-file-input {
241
+ position: absolute;
242
+ width: 1px;
243
+ height: 1px;
244
+ opacity: 0;
245
+ pointer-events: none;
246
+ }
247
+
248
+ .vi-crop-status,
249
+ .vi-crop-meta {
250
+ font-family: var(--vi-mono);
251
+ font-size: 0.8rem;
252
+ color: var(--vi-text-dim);
253
+ }
254
+
255
+ .vi-crop-meta code {
256
+ font-family: var(--vi-mono);
257
+ color: var(--vi-text);
258
+ }
259
+
260
+ .vi-error {
261
+ color: #ff8a8a;
262
+ font-family: var(--vi-mono);
263
+ font-size: 0.85rem;
264
+ margin: 0.5rem 0;
265
+ }
266
+
267
+ .vi-crop-warn {
268
+ color: var(--vi-text-dim);
269
+ font-family: var(--vi-mono);
270
+ font-size: 0.85rem;
271
+ }
272
+
273
+ /* ---- shared drop receptacle (all four stations' empty item-view) ----
274
+ The station passes its existing upload handler as onFile; this is intake UI
275
+ only. Sized to fill the studio preview area (~22rem); a --compact variant is
276
+ used in the Generate video-part picker. */
277
+ .vi-dropzone {
278
+ display: flex;
279
+ flex-direction: column;
280
+ align-items: center;
281
+ justify-content: center;
282
+ gap: 0.5rem;
283
+ width: 100%;
284
+ min-height: 22rem;
285
+ padding: 1.5rem;
286
+ box-sizing: border-box;
287
+ border: 2px dashed var(--vi-border);
288
+ border-radius: 8px;
289
+ background: var(--vi-surface-raised);
290
+ color: var(--vi-text-dim);
291
+ font-family: var(--vi-mono);
292
+ text-align: center;
293
+ cursor: pointer;
294
+ transition: border-color 140ms ease, background 140ms ease, transform 140ms ease;
295
+ }
296
+
297
+ .vi-dropzone:hover,
298
+ .vi-dropzone--active {
299
+ border-color: var(--vi-accent);
300
+ background: rgba(138, 180, 255, 0.08);
301
+ transform: translateY(-1px);
302
+ }
303
+
304
+ .vi-dropzone:focus-visible {
305
+ outline: 2px solid var(--vi-accent);
306
+ outline-offset: 2px;
307
+ }
308
+
309
+ .vi-dropzone--busy {
310
+ cursor: progress;
311
+ opacity: 0.7;
312
+ }
313
+
314
+ .vi-dropzone--compact {
315
+ min-height: 9rem;
316
+ padding: 1rem;
317
+ }
318
+
319
+ .vi-dropzone-title {
320
+ font-size: 0.95rem;
321
+ color: var(--vi-text);
322
+ }
323
+
324
+ .vi-dropzone-hint {
325
+ font-size: 0.75rem;
326
+ color: var(--vi-text-dim);
327
+ line-height: 1.4;
328
+ max-width: 28rem;
329
+ }
330
+
331
+ /* ---- required-input attention (Studio Generate preformed shapes) ----
332
+ When a picked generator REQUIRES an input (an i2v start image, a v2v source
333
+ clip) and it is still empty, the input is given the eye: an accent solid
334
+ border + a faint accent fill, matching the dropzone hover language. Optional
335
+ inputs stay subdued (no class). Tokenized so it reads as one system. */
336
+ .vi-input-required {
337
+ border: 2px solid var(--vi-accent);
338
+ background: rgba(138, 180, 255, 0.08);
339
+ border-radius: 8px;
340
+ padding: 1rem 1.1rem;
341
+ box-sizing: border-box;
342
+ }
343
+
344
+ .vi-input-callout {
345
+ color: var(--vi-accent);
346
+ font-weight: 600;
347
+ margin: 0 0 0.5rem;
348
+ font-size: 0.9rem;
349
+ }
350
+
351
+ /* An accent-forced dropzone: the hero start-image drop surface when it is the
352
+ required-and-empty input (border solid + tinted even at rest, not only hover). */
353
+ .vi-dropzone--required {
354
+ border-style: solid;
355
+ border-color: var(--vi-accent);
356
+ background: rgba(138, 180, 255, 0.08);
357
+ }
358
+
359
+ /* ---- spatial editor ---- */
360
+ .vi-crop-editor {
361
+ display: flex;
362
+ flex-wrap: wrap;
363
+ gap: 1.5rem;
364
+ align-items: flex-start;
365
+ }
366
+
367
+ .vi-crop-canvas {
368
+ position: relative;
369
+ display: inline-block;
370
+ line-height: 0;
371
+ max-width: 100%;
372
+ border: 1px solid var(--vi-border);
373
+ background: #000;
374
+ flex: 1 1 22rem;
375
+ min-width: 16rem;
376
+ }
377
+
378
+ .vi-crop-img {
379
+ display: block;
380
+ max-width: 100%;
381
+ height: auto;
382
+ user-select: none;
383
+ -webkit-user-drag: none;
384
+ }
385
+
386
+ /* Blank canvas placeholder shown when no image is loaded — keeps the editor
387
+ chrome laid out (aspect-ratio set inline from the default dimensions). */
388
+ .vi-crop-img--blank {
389
+ width: 100%;
390
+ min-height: 12rem;
391
+ background: #000;
392
+ }
393
+
394
+ .vi-crop-overlay {
395
+ position: absolute;
396
+ inset: 0;
397
+ overflow: hidden;
398
+ }
399
+
400
+ .vi-crop-box {
401
+ position: absolute;
402
+ box-sizing: border-box;
403
+ border: 1px solid var(--vi-accent);
404
+ background: rgba(138, 180, 255, 0.12);
405
+ box-shadow: 0 0 0 100vmax rgba(0, 0, 0, 0.45);
406
+ cursor: move;
407
+ touch-action: none;
408
+ }
409
+
410
+ .vi-crop-box-active {
411
+ background: rgba(138, 180, 255, 0.2);
412
+ }
413
+
414
+ .vi-crop-handle {
415
+ position: absolute;
416
+ width: 12px;
417
+ height: 12px;
418
+ background: var(--vi-accent);
419
+ border: 1px solid var(--vi-surface);
420
+ border-radius: 2px;
421
+ touch-action: none;
422
+ }
423
+
424
+ .vi-crop-handle-nw {
425
+ top: -6px;
426
+ left: -6px;
427
+ cursor: nwse-resize;
428
+ }
429
+ .vi-crop-handle-ne {
430
+ top: -6px;
431
+ right: -6px;
432
+ cursor: nesw-resize;
433
+ }
434
+ .vi-crop-handle-sw {
435
+ bottom: -6px;
436
+ left: -6px;
437
+ cursor: nesw-resize;
438
+ }
439
+ .vi-crop-handle-se {
440
+ bottom: -6px;
441
+ right: -6px;
442
+ cursor: nwse-resize;
443
+ }
444
+
445
+ /* ---- editor controls ---- */
446
+ .vi-crop-controls {
447
+ flex: 1 1 18rem;
448
+ min-width: 14rem;
449
+ display: flex;
450
+ flex-direction: column;
451
+ gap: 1rem;
452
+ font-family: var(--vi-mono);
453
+ }
454
+
455
+ .vi-crop-presets {
456
+ display: flex;
457
+ flex-wrap: wrap;
458
+ gap: 0.4rem;
459
+ align-items: center;
460
+ }
461
+
462
+ .vi-crop-lock {
463
+ display: inline-flex;
464
+ align-items: center;
465
+ gap: 0.35rem;
466
+ font-size: 0.8rem;
467
+ color: var(--vi-text-dim);
468
+ margin-left: 0.25rem;
469
+ }
470
+
471
+ .vi-crop-fields {
472
+ display: flex;
473
+ flex-wrap: wrap;
474
+ gap: 0.5rem;
475
+ align-items: flex-end;
476
+ }
477
+
478
+ .vi-crop-field {
479
+ display: flex;
480
+ flex-direction: column;
481
+ gap: 0.2rem;
482
+ font-size: 0.72rem;
483
+ color: var(--vi-text-dim);
484
+ text-transform: uppercase;
485
+ letter-spacing: 0.05em;
486
+ }
487
+
488
+ .vi-crop-field input {
489
+ width: 4.5rem;
490
+ padding: 0.3rem 0.4rem;
491
+ font-family: var(--vi-mono);
492
+ font-size: 0.85rem;
493
+ color: var(--vi-text);
494
+ background: var(--vi-surface-raised);
495
+ border: 1px solid var(--vi-border);
496
+ border-radius: 4px;
497
+ }
498
+
499
+ .vi-crop-native {
500
+ font-size: 0.75rem;
501
+ color: var(--vi-text-dim);
502
+ padding-bottom: 0.4rem;
503
+ }
504
+
505
+ /* ---- region queue list ---- */
506
+ .vi-region-list {
507
+ list-style: none;
508
+ margin: 0;
509
+ padding: 0;
510
+ border-top: 1px solid var(--vi-border);
511
+ }
512
+
513
+ .vi-region-item {
514
+ display: flex;
515
+ align-items: center;
516
+ gap: 0.6rem;
517
+ padding: 0.45rem 0.1rem;
518
+ border-bottom: 1px solid var(--vi-border);
519
+ font-size: 0.82rem;
520
+ }
521
+
522
+ .vi-region-idx {
523
+ color: var(--vi-accent);
524
+ font-family: var(--vi-mono);
525
+ font-size: 0.78rem;
526
+ }
527
+
528
+ .vi-region-dims {
529
+ font-family: var(--vi-mono);
530
+ color: var(--vi-text);
531
+ font-size: 0.78rem;
532
+ margin-right: auto;
533
+ }
534
+
535
+ /* ---- run bar ---- */
536
+ .vi-crop-run {
537
+ display: flex;
538
+ flex-wrap: wrap;
539
+ align-items: center;
540
+ gap: 0.75rem 1rem;
541
+ margin: 1.25rem 0;
542
+ }
543
+
544
+ .vi-crop-hint,
545
+ .vi-crop-actions {
546
+ font-family: var(--vi-mono);
547
+ }
548
+
549
+ .vi-crop-hint {
550
+ font-size: 0.78rem;
551
+ color: var(--vi-text-dim);
552
+ }
553
+
554
+ /* ---- result grid ---- */
555
+ .vi-result-grid {
556
+ display: grid;
557
+ grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
558
+ gap: 1rem;
559
+ margin-top: 1rem;
560
+ border-top: 1px solid var(--vi-border);
561
+ padding-top: 1.25rem;
562
+ }
563
+
564
+ .vi-result-card {
565
+ margin: 0;
566
+ border: 1px solid var(--vi-border);
567
+ border-radius: 6px;
568
+ background: var(--vi-surface-raised);
569
+ overflow: hidden;
570
+ }
571
+
572
+ .vi-result-head {
573
+ display: flex;
574
+ align-items: center;
575
+ gap: 0.5rem;
576
+ padding: 0.5rem 0.6rem;
577
+ border-bottom: 1px solid var(--vi-border);
578
+ font-family: var(--vi-mono);
579
+ }
580
+
581
+ .vi-status {
582
+ font-size: 0.72rem;
583
+ text-transform: uppercase;
584
+ letter-spacing: 0.05em;
585
+ color: var(--vi-text-dim);
586
+ margin-right: auto;
587
+ }
588
+
589
+ .vi-status-running,
590
+ .vi-status-claimed,
591
+ .vi-status-queued,
592
+ /* In-flight statuses from the fleet-wide /llm/jobs feed (worker-call rows in the
593
+ Active Processes panel) — same accent as the video-bus in-flight states. */
594
+ .vi-status-pending,
595
+ .vi-status-processing,
596
+ .vi-status-streaming {
597
+ color: var(--vi-accent);
598
+ }
599
+
600
+ .vi-status-done {
601
+ color: #7fd88f;
602
+ }
603
+
604
+ .vi-status-failed {
605
+ color: #ff8a8a;
606
+ }
607
+
608
+ .vi-result-body {
609
+ display: flex;
610
+ align-items: center;
611
+ justify-content: center;
612
+ min-height: 8rem;
613
+ padding: 0.5rem;
614
+ }
615
+
616
+ .vi-result-img {
617
+ max-width: 100%;
618
+ height: auto;
619
+ display: block;
620
+ }
621
+
622
+ .vi-result-pending {
623
+ font-family: var(--vi-mono);
624
+ font-size: 0.8rem;
625
+ color: var(--vi-text-dim);
626
+ }
627
+
628
+ /* ------------------------------------------------------------------------- */
629
+ /* Phase 4 — Frames & Models station: left-rail knob panel + frame grid. */
630
+ /* Same dark editorial-terminal tokens; reuses .vi-btn / .vi-status / .vi-error. */
631
+ /* ------------------------------------------------------------------------- */
632
+
633
+ .vi-frames-layout {
634
+ display: flex;
635
+ flex-wrap: wrap;
636
+ gap: 1.5rem;
637
+ align-items: flex-start;
638
+ }
639
+
640
+ /* ---- left rail (the sample image's knob column) ---- */
641
+ .vi-frames-rail {
642
+ flex: 0 0 17rem;
643
+ min-width: 15rem;
644
+ display: flex;
645
+ flex-direction: column;
646
+ gap: 1rem;
647
+ font-family: var(--vi-mono);
648
+ border: 1px solid var(--vi-border);
649
+ border-radius: 6px;
650
+ background: var(--vi-surface-raised);
651
+ padding: 1rem;
652
+ }
653
+
654
+ .vi-knob {
655
+ display: flex;
656
+ flex-direction: column;
657
+ gap: 0.3rem;
658
+ }
659
+
660
+ .vi-knob > label,
661
+ .vi-knob-label-row > label {
662
+ font-size: 0.72rem;
663
+ color: var(--vi-text-dim);
664
+ text-transform: uppercase;
665
+ letter-spacing: 0.05em;
666
+ }
667
+
668
+ /* ---- knob ROW — pairs (or trios) of compact numeric knobs sharing one line
669
+ instead of each stacking full-width (width+height, steps+guidance+seed).
670
+ Wraps gracefully if the host rail is too narrow for all children at once. ---- */
671
+ .vi-knob-row {
672
+ display: flex;
673
+ flex-wrap: wrap;
674
+ gap: 0.6rem;
675
+ }
676
+
677
+ .vi-knob-row > .vi-knob {
678
+ flex: 1 1 5.5rem;
679
+ min-width: 0;
680
+ }
681
+
682
+ /* Label + the "ⓘ" info affordance on one line, info pinned to the right. */
683
+ .vi-knob-label-row {
684
+ display: flex;
685
+ align-items: center;
686
+ justify-content: space-between;
687
+ gap: 0.35rem;
688
+ }
689
+
690
+ /* ---- knob info affordance (see KnobInfo.tsx) — hover (desktop) / tap (mobile)
691
+ reveal for the verbose explanation that used to sit permanently under a
692
+ compacted knob. One pattern, reused by every compacted knob. ---- */
693
+ .vi-knob-info {
694
+ position: relative;
695
+ display: inline-flex;
696
+ flex-shrink: 0;
697
+ }
698
+
699
+ .vi-knob-info-btn {
700
+ all: unset;
701
+ cursor: pointer;
702
+ font-size: 0.8rem;
703
+ line-height: 1;
704
+ color: var(--vi-text-dim);
705
+ padding: 0.05rem 0.2rem;
706
+ border-radius: 3px;
707
+ }
708
+
709
+ .vi-knob-info-btn:focus-visible {
710
+ color: var(--vi-accent);
711
+ outline: 1px solid var(--vi-accent);
712
+ outline-offset: 1px;
713
+ }
714
+
715
+ .vi-knob-info-pop {
716
+ display: none;
717
+ position: absolute;
718
+ z-index: 5;
719
+ top: 1.3rem;
720
+ right: 0;
721
+ width: max-content;
722
+ max-width: 11rem;
723
+ padding: 0.4rem 0.55rem;
724
+ font-size: 0.72rem;
725
+ line-height: 1.35;
726
+ font-family: var(--vi-mono);
727
+ color: var(--vi-text);
728
+ background: var(--vi-surface);
729
+ border: 1px solid var(--vi-border);
730
+ border-radius: 4px;
731
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
732
+ }
733
+
734
+ /* Tabbing to the ⓘ reveals it on any device (native focus, not touch-tap —
735
+ KnobInfo blurs the button on close, so this never gets stuck open). */
736
+ .vi-knob-info:focus-within .vi-knob-info-pop {
737
+ display: block;
738
+ }
739
+
740
+ /* Touch/mobile-equivalent: tapping the ⓘ toggles `.vi-knob-info-open`
741
+ (KnobInfo.tsx) — a fully independent path from :hover below. */
742
+ .vi-knob-info-open .vi-knob-info-pop {
743
+ display: block;
744
+ }
745
+
746
+ /* Desktop hover — gated to real hover-capable pointers (mouse/trackpad).
747
+ Touchscreens are excluded on purpose: many mobile browsers latch `:hover`
748
+ on tap and don't clear it until the next tap elsewhere ("sticky hover"),
749
+ which would fight the tap-toggle above. Touch gets ONLY the toggle path. */
750
+ @media (hover: hover) {
751
+ .vi-knob-info-btn:hover {
752
+ color: var(--vi-accent);
753
+ outline: 1px solid var(--vi-accent);
754
+ outline-offset: 1px;
755
+ }
756
+
757
+ .vi-knob-info:hover .vi-knob-info-pop {
758
+ display: block;
759
+ }
760
+ }
761
+
762
+ .vi-knob-input,
763
+ .vi-knob-select {
764
+ width: 100%;
765
+ box-sizing: border-box;
766
+ padding: 0.35rem 0.45rem;
767
+ font-family: var(--vi-mono);
768
+ font-size: 0.85rem;
769
+ color: var(--vi-text);
770
+ background: var(--vi-surface);
771
+ border: 1px solid var(--vi-border);
772
+ border-radius: 4px;
773
+ }
774
+
775
+ .vi-knob-input:focus,
776
+ .vi-knob-select:focus {
777
+ outline: none;
778
+ border-color: var(--vi-accent);
779
+ }
780
+
781
+ .vi-knob-input-sm {
782
+ width: 5.5rem;
783
+ }
784
+
785
+ /* Seed: numbers-or-blank, but a seed can be a long integer — cap it wider than
786
+ .vi-knob-input-sm so a big value never clips, while still reading as compact
787
+ (not a full-width block) against the ~14rem rail. */
788
+ .vi-knob-input-compact {
789
+ width: auto;
790
+ max-width: 9rem;
791
+ }
792
+
793
+ .vi-knob-hint {
794
+ font-size: 0.72rem;
795
+ color: var(--vi-text-dim);
796
+ line-height: 1.35;
797
+ }
798
+
799
+ .vi-knob-range {
800
+ color: var(--vi-accent);
801
+ }
802
+
803
+ .vi-knob-flag {
804
+ font-size: 0.72rem;
805
+ color: #ffcf8a;
806
+ line-height: 1.35;
807
+ }
808
+
809
+ /* Intelligent-default resolution hint (t23) — the honest "using N (auto: …)"
810
+ note shown when a knob is blank and resolving to its source-derived default.
811
+ Reads as an accent-tinted affirmative, distinct from the amber .vi-knob-flag
812
+ warning. */
813
+ .vi-knob-auto {
814
+ display: block;
815
+ margin-top: 0.15rem;
816
+ font-size: 0.72rem;
817
+ color: var(--vi-accent);
818
+ line-height: 1.35;
819
+ }
820
+
821
+ .vi-knob-check {
822
+ display: inline-flex;
823
+ align-items: center;
824
+ gap: 0.4rem;
825
+ font-size: 0.72rem;
826
+ color: var(--vi-text-dim);
827
+ text-transform: uppercase;
828
+ letter-spacing: 0.05em;
829
+ }
830
+
831
+ .vi-knob-window {
832
+ display: flex;
833
+ gap: 0.6rem;
834
+ }
835
+
836
+ .vi-knob-sub {
837
+ display: flex;
838
+ flex-direction: column;
839
+ gap: 0.2rem;
840
+ font-size: 0.68rem;
841
+ color: var(--vi-text-dim);
842
+ text-transform: uppercase;
843
+ letter-spacing: 0.05em;
844
+ }
845
+
846
+ /* Generic run row (button + status, or the poll-cap "Check again" affordance).
847
+ Lives in the studio bottom prompt bar for both Frames and Generate. */
848
+ .vi-frames-run {
849
+ display: flex;
850
+ flex-wrap: wrap;
851
+ align-items: center;
852
+ gap: 0.6rem;
853
+ }
854
+
855
+ /* ---- main column: frame grid ---- */
856
+ .vi-frames-main {
857
+ flex: 1 1 24rem;
858
+ min-width: 18rem;
859
+ }
860
+
861
+ .vi-frames-toolbar {
862
+ display: flex;
863
+ flex-wrap: wrap;
864
+ align-items: center;
865
+ gap: 0.5rem 1rem;
866
+ margin-bottom: 0.9rem;
867
+ }
868
+
869
+ .vi-frames-summary {
870
+ font-family: var(--vi-mono);
871
+ font-size: 0.8rem;
872
+ color: var(--vi-text-dim);
873
+ margin-right: auto;
874
+ }
875
+
876
+ .vi-frames-actions {
877
+ display: flex;
878
+ flex-wrap: wrap;
879
+ gap: 0.4rem;
880
+ }
881
+
882
+ /* The frame grid renders as a single-row horizontal thumbnail strip (film-strip)
883
+ under the center preview. Pagination is unchanged — each page is one strip. */
884
+ .vi-frames-grid {
885
+ display: flex;
886
+ gap: 0.6rem;
887
+ overflow-x: auto;
888
+ padding-bottom: 0.4rem;
889
+ scrollbar-width: thin;
890
+ }
891
+
892
+ .vi-frame-cell {
893
+ position: relative;
894
+ display: block;
895
+ flex: 0 0 9rem;
896
+ padding: 0;
897
+ margin: 0;
898
+ border: 1px solid var(--vi-border);
899
+ border-radius: 4px;
900
+ background: #000;
901
+ overflow: hidden;
902
+ cursor: pointer;
903
+ line-height: 0;
904
+ }
905
+
906
+ .vi-frame-cell:hover {
907
+ border-color: var(--vi-accent);
908
+ }
909
+
910
+ .vi-frame-cell-selected {
911
+ border-color: var(--vi-accent);
912
+ box-shadow: 0 0 0 2px var(--vi-accent) inset;
913
+ }
914
+
915
+ .vi-frame-img {
916
+ display: block;
917
+ width: 100%;
918
+ height: auto;
919
+ aspect-ratio: 16 / 9;
920
+ object-fit: cover;
921
+ }
922
+
923
+ .vi-frame-tag {
924
+ position: absolute;
925
+ top: 0.25rem;
926
+ left: 0.25rem;
927
+ font-family: var(--vi-mono);
928
+ font-size: 0.65rem;
929
+ color: var(--vi-text);
930
+ background: rgba(0, 0, 0, 0.6);
931
+ padding: 0.05rem 0.3rem;
932
+ border-radius: 3px;
933
+ line-height: 1.2;
934
+ }
935
+
936
+ .vi-frame-check {
937
+ position: absolute;
938
+ top: 0.25rem;
939
+ right: 0.25rem;
940
+ font-family: var(--vi-mono);
941
+ font-size: 0.7rem;
942
+ color: var(--vi-surface);
943
+ background: var(--vi-accent);
944
+ width: 1.1rem;
945
+ height: 1.1rem;
946
+ display: flex;
947
+ align-items: center;
948
+ justify-content: center;
949
+ border-radius: 50%;
950
+ line-height: 1;
951
+ }
952
+
953
+ .vi-frame-saved {
954
+ position: absolute;
955
+ bottom: 0.25rem;
956
+ left: 0.25rem;
957
+ font-family: var(--vi-mono);
958
+ font-size: 0.6rem;
959
+ color: #7fd88f;
960
+ background: rgba(0, 0, 0, 0.6);
961
+ padding: 0.05rem 0.3rem;
962
+ border-radius: 3px;
963
+ line-height: 1.2;
964
+ text-transform: uppercase;
965
+ letter-spacing: 0.04em;
966
+ }
967
+
968
+ .vi-pager {
969
+ display: flex;
970
+ align-items: center;
971
+ gap: 0.75rem;
972
+ margin-top: 1rem;
973
+ justify-content: center;
974
+ }
975
+
976
+ .vi-pager-label {
977
+ font-family: var(--vi-mono);
978
+ font-size: 0.78rem;
979
+ color: var(--vi-text-dim);
980
+ }
981
+
982
+ /* ------------------------------------------------------------------------- */
983
+ /* Phases 6–7 — Generate station: left-rail knobs (reuses .vi-frames-rail / */
984
+ /* .vi-knob*) + main column with the center preview and the ordered prompt */
985
+ /* composer. Reuses .vi-btn / .vi-status / .vi-error / .vi-result-img. */
986
+ /* ------------------------------------------------------------------------- */
987
+
988
+ /* ---- center preview / result ---- */
989
+ .vi-gen-preview {
990
+ display: flex;
991
+ align-items: center;
992
+ justify-content: center;
993
+ min-height: 14rem;
994
+ border: 1px solid var(--vi-border);
995
+ border-radius: 6px;
996
+ background: var(--vi-surface-raised);
997
+ padding: 1rem;
998
+ margin-bottom: 1.25rem;
999
+ }
1000
+
1001
+ .vi-gen-result {
1002
+ margin: 0;
1003
+ display: flex;
1004
+ flex-direction: column;
1005
+ align-items: center;
1006
+ gap: 0.6rem;
1007
+ }
1008
+
1009
+ .vi-gen-note {
1010
+ font-family: var(--vi-mono);
1011
+ font-size: 0.72rem;
1012
+ color: #7fd88f;
1013
+ text-align: center;
1014
+ }
1015
+
1016
+ /* The auto-archive "Saved to <name> · <dir>" line, appended to the result note. */
1017
+ .vi-gen-saved {
1018
+ color: var(--vi-text-dim);
1019
+ }
1020
+ .vi-gen-saved code {
1021
+ color: #7fd88f;
1022
+ }
1023
+
1024
+ .vi-gen-preview-pending {
1025
+ display: flex;
1026
+ flex-direction: column;
1027
+ align-items: center;
1028
+ gap: 0.75rem;
1029
+ width: 100%;
1030
+ font-family: var(--vi-mono);
1031
+ font-size: 0.9rem;
1032
+ text-align: center;
1033
+ }
1034
+
1035
+ /* ------------------------------------------------------------------------- */
1036
+ /* Responsive Options / Add-file / Components splitter (SectionTabs). */
1037
+ /* Desktop (>=62rem): the Options rail (when present) sits beside a single */
1038
+ /* flowing work column — compact add-file on top, then the working */
1039
+ /* components, then the in-panel Run footer. No tab strip, no dead gap. */
1040
+ /* Mobile (<62rem): the parts collapse behind a [ Options | Add file | */
1041
+ /* Components ] tab strip reusing the .vi-gen-mode idiom below. */
1042
+ /* ------------------------------------------------------------------------- */
1043
+ .vi-section-tabs {
1044
+ display: flex;
1045
+ flex-direction: column;
1046
+ gap: 1.1rem;
1047
+ min-width: 0;
1048
+ }
1049
+
1050
+ /* The tab strip itself — same look as .vi-gen-mode, but allowed to shrink and
1051
+ (on narrow viewports) scroll rather than force horizontal overflow. */
1052
+ .vi-section-tablist {
1053
+ max-width: 100%;
1054
+ }
1055
+
1056
+ .vi-section-tabpanel {
1057
+ min-width: 0;
1058
+ }
1059
+
1060
+ .vi-section-tabpanel[hidden] {
1061
+ display: none;
1062
+ }
1063
+
1064
+ .vi-section-tabpanel:focus-visible,
1065
+ .vi-section-region:focus-visible {
1066
+ outline: 2px solid var(--vi-accent);
1067
+ outline-offset: 3px;
1068
+ border-radius: 4px;
1069
+ }
1070
+
1071
+ /* Desktop landmark regions. */
1072
+ .vi-section-region {
1073
+ min-width: 0;
1074
+ }
1075
+
1076
+ /* Work column: a single flowing column (compact add-file → components → Run
1077
+ footer). Fills the space beside the options rail — no empty middle band. */
1078
+ .vi-section-region--work {
1079
+ display: flex;
1080
+ flex-direction: column;
1081
+ gap: 1rem;
1082
+ min-width: 0;
1083
+ }
1084
+
1085
+ .vi-section-addfile {
1086
+ min-width: 0;
1087
+ }
1088
+
1089
+ /* Compact file-intake bar (AddFileBar) — the ONLY intake affordance: a
1090
+ "Choose …" button + filename/meta + optional Clear. No dropbox anywhere. */
1091
+ .vi-addbar {
1092
+ display: flex;
1093
+ flex-wrap: wrap;
1094
+ align-items: center;
1095
+ gap: 0.5rem 0.75rem;
1096
+ min-width: 0;
1097
+ }
1098
+
1099
+ /* Selected filename — ellipsized so a long name never overflows at 390px. */
1100
+ .vi-addbar-name {
1101
+ font-family: var(--vi-mono);
1102
+ font-size: 0.8rem;
1103
+ color: var(--vi-text);
1104
+ max-width: 100%;
1105
+ min-width: 0;
1106
+ overflow: hidden;
1107
+ text-overflow: ellipsis;
1108
+ white-space: nowrap;
1109
+ }
1110
+
1111
+ /* A station's working components — always a stacked column (preview / grid /
1112
+ results / footer). Used inside the desktop work column AND the mobile
1113
+ Components tab, so its internal spacing is layout-independent. */
1114
+ .vi-station-components {
1115
+ display: flex;
1116
+ flex-direction: column;
1117
+ gap: 1rem;
1118
+ min-width: 0;
1119
+ }
1120
+
1121
+ /* Character tools group (t25) — a titled area inside the Frames station that
1122
+ hosts character-centric work on the loaded video (char360 first; the t19
1123
+ aggregation modes land here later). A bordered section with a header so it
1124
+ reads as its own first-class group, separated from the frame grid above. */
1125
+ .vi-character-tools {
1126
+ display: flex;
1127
+ flex-direction: column;
1128
+ gap: 0.5rem;
1129
+ padding: 0.75rem;
1130
+ border: 1px solid var(--vi-border);
1131
+ border-radius: 8px;
1132
+ background: var(--vi-surface-raised);
1133
+ }
1134
+ .vi-character-tools-head {
1135
+ display: flex;
1136
+ flex-direction: column;
1137
+ gap: 0.15rem;
1138
+ }
1139
+
1140
+ /* In-panel footer action row — the Run/primary action + any toolbar. Sticky
1141
+ bottom bar (operator 2026-07-13: "the user should not have to scroll down to
1142
+ press the Run button on any page"): `.vi-workbench-body` (app.css, the
1143
+ "scroll container" block below) is the ONE element that actually scrolls a
1144
+ station's content — the page/navbar/sidebar never do. `position: sticky`
1145
+ only gets real travel from the element's IMMEDIATE parent (its CSS
1146
+ containing block), not from any taller ancestor further up — verified with a
1147
+ throwaway sticky testbed (a short direct parent gives zero effective pin
1148
+ range; sticking this outer row works because its own parent
1149
+ (.vi-station-components / .vi-studio-generate) is the tall flowing column
1150
+ that actually needs scrolling). Sticking only the inner `.vi-station-footer-
1151
+ run` button column (its literal ask) would NOT have worked for that reason,
1152
+ so the whole row (feedback text + Run) is the sticky unit — shared verbatim
1153
+ by GenerateStation, FrameExtractCore, ImageCropCore, AudioCropCore,
1154
+ VideoCropCore and StudioGenerateTab (`vi-station-footer vi-studio-run`). See
1155
+ `.vi-run-bar` below for the bespoke (non-.vi-station-footer) stations. */
1156
+ .vi-station-footer,
1157
+ .vi-run-bar {
1158
+ display: flex;
1159
+ flex-wrap: wrap;
1160
+ gap: 1rem 1.5rem;
1161
+ align-items: flex-start;
1162
+ justify-content: space-between;
1163
+ border-top: 1px solid var(--vi-border);
1164
+ padding: 1rem 0;
1165
+ position: sticky;
1166
+ bottom: 0;
1167
+ z-index: 5;
1168
+ /* This app has one (dark) theme only — no --vi-bg/light tokens exist
1169
+ (verified: no prefers-color-scheme or data-theme rule anywhere in this
1170
+ file) — --vi-surface IS the page background, so this blends exactly. */
1171
+ background: var(--vi-surface);
1172
+ }
1173
+
1174
+ .vi-station-footer-main {
1175
+ flex: 1 1 20rem;
1176
+ min-width: 0;
1177
+ display: flex;
1178
+ flex-direction: column;
1179
+ gap: 0.75rem;
1180
+ }
1181
+
1182
+ .vi-station-footer-run {
1183
+ flex: 0 0 auto;
1184
+ min-width: 12rem;
1185
+ display: flex;
1186
+ flex-direction: column;
1187
+ gap: 0.6rem;
1188
+ align-items: stretch;
1189
+ }
1190
+
1191
+ /* Studio run rows (StudioGenerateTab + StudioMovieComposer) tag the footer with
1192
+ BOTH `vi-station-footer` and `vi-studio-run`. `.vi-studio-run` (defined below,
1193
+ part of the ARCHIVED `.vi-studio-prompt` bottom-bar layout — never rendered
1194
+ standalone; verified the class only ever appears combined with
1195
+ `.vi-station-footer`) sets `flex-direction: column`, which flipped the footer to
1196
+ a VERTICAL stack. In a column flex, `.vi-station-footer-main`'s `flex: 1 1 20rem`
1197
+ basis is read as a 320px HEIGHT, so the row ballooned to ~393px with the button
1198
+ floating ~320px below the inputs — measured live 2026-07-13 with Playwright. That
1199
+ was the pre-existing "dead space between the inputs and Run"; once the footer went
1200
+ `position: sticky` (opaque, bottom:0) that 393px block began OCCLUDING the bottom
1201
+ inputs. Pin the combined selector back to the compact ROW footer (0,2,0 specificity
1202
+ beats `.vi-studio-run`'s 0,1,0 regardless of source order), so the studio run rows
1203
+ match every other station's footer: main (flags/status) on the left, Run on the
1204
+ right, ~a button tall, sitting directly under the inputs. No JSX change and the
1205
+ archived standalone `.vi-studio-run` rule is untouched. */
1206
+ .vi-station-footer.vi-studio-run {
1207
+ flex-direction: row;
1208
+ align-items: flex-start;
1209
+ min-width: 0;
1210
+ gap: 1rem 1.5rem;
1211
+ }
1212
+
1213
+ /* ---- Desktop (>=62rem): rail beside the work column, no tab strip ---- */
1214
+ @media (min-width: 62rem) {
1215
+ /* Belt-and-suspenders — the component also skips rendering the strip here. */
1216
+ .vi-section-tablist {
1217
+ display: none;
1218
+ }
1219
+
1220
+ /* "beside": the fluid work column on the LEFT + the ~17rem settings rail on
1221
+ the RIGHT. */
1222
+ .vi-section-tabs--beside {
1223
+ display: grid;
1224
+ grid-template-columns: minmax(0, 1fr) 17rem;
1225
+ gap: 1.25rem 1.5rem;
1226
+ align-items: start;
1227
+ }
1228
+ /* "stacked" (crop stations, no rail): the work column spans full width. */
1229
+ .vi-section-tabs--stacked {
1230
+ display: flex;
1231
+ flex-direction: column;
1232
+ }
1233
+ }
1234
+
1235
+ /* ---- Mobile (<62rem): stacked tab panels ---- */
1236
+ @media (max-width: 62rem) {
1237
+ /* Keep the strip on one line but let it scroll instead of overflowing the
1238
+ 390px viewport; the (2–3) short tabs never actually need to scroll. */
1239
+ .vi-section-tablist {
1240
+ overflow-x: auto;
1241
+ -webkit-overflow-scrolling: touch;
1242
+ }
1243
+
1244
+ /* The Run footer stacks its columns on narrow screens. */
1245
+ .vi-station-footer-run {
1246
+ min-width: 0;
1247
+ width: 100%;
1248
+ }
1249
+ }
1250
+
1251
+ /* ---- mode switch (Image | Scene) — segmented control above the studio ---- */
1252
+ .vi-gen-mode {
1253
+ display: inline-flex;
1254
+ gap: 0;
1255
+ margin: 0 0 1rem;
1256
+ border: 1px solid var(--vi-border);
1257
+ border-radius: 6px;
1258
+ overflow: hidden;
1259
+ }
1260
+
1261
+ .vi-gen-mode-btn {
1262
+ font-family: var(--vi-mono);
1263
+ font-size: 0.8rem;
1264
+ padding: 0.35rem 1.1rem;
1265
+ border: 0;
1266
+ background: var(--vi-surface-raised);
1267
+ color: var(--vi-text-dim);
1268
+ cursor: pointer;
1269
+ line-height: 1.2;
1270
+ }
1271
+
1272
+ .vi-gen-mode-btn + .vi-gen-mode-btn {
1273
+ border-left: 1px solid var(--vi-border);
1274
+ }
1275
+
1276
+ .vi-gen-mode-btn:hover {
1277
+ color: var(--vi-text);
1278
+ }
1279
+
1280
+ .vi-gen-mode-btn-active {
1281
+ background: var(--vi-accent);
1282
+ color: var(--vi-surface);
1283
+ font-weight: 500;
1284
+ }
1285
+
1286
+ /* ---- scene result: full-width strip + clip stacked in the studio preview ---- */
1287
+ .vi-scene-result {
1288
+ width: 100%;
1289
+ align-items: stretch;
1290
+ }
1291
+
1292
+ /* Non-interactive frame tile (mirrors .vi-frame-cell without the hover/cursor). */
1293
+ .vi-scene-frame-cell {
1294
+ position: relative;
1295
+ display: block;
1296
+ flex: 0 0 9rem;
1297
+ padding: 0;
1298
+ margin: 0;
1299
+ border: 1px solid var(--vi-border);
1300
+ border-radius: 4px;
1301
+ background: #000;
1302
+ overflow: hidden;
1303
+ line-height: 0;
1304
+ }
1305
+
1306
+ .vi-scene-frame-img {
1307
+ display: block;
1308
+ width: 100%;
1309
+ height: auto;
1310
+ aspect-ratio: 16 / 9;
1311
+ object-fit: cover;
1312
+ }
1313
+
1314
+ /* The scene result lives INSIDE .vi-studio-preview (overflow:hidden, centering
1315
+ flex). Without an explicit min-width:0 the figure's default min-width:auto
1316
+ lets it grow to the frame strip's full (non-wrapping) content width, which the
1317
+ preview then CLIPS — so frames past the visible width were unreachable. Cap the
1318
+ figure at the preview width so the inner strip's overflow-x:auto engages. */
1319
+ .vi-scene-result {
1320
+ min-width: 0;
1321
+ max-width: 100%;
1322
+ }
1323
+
1324
+ /* The scene frame strip: a horizontally-scrollable film-strip (fixed-width,
1325
+ non-wrapping frames). overscroll-behavior-x:contain keeps a sideways scroll
1326
+ inside the strip so the page body never scrolls horizontally. */
1327
+ .vi-scene-frames {
1328
+ flex-wrap: nowrap;
1329
+ overscroll-behavior-x: contain;
1330
+ }
1331
+
1332
+ /* Per-frame download — revealed on hover/focus of its tile (mirrors the
1333
+ .vi-frame-tag corner-chip look, top-right). */
1334
+ .vi-frame-dl {
1335
+ position: absolute;
1336
+ top: 0.25rem;
1337
+ right: 0.25rem;
1338
+ display: flex;
1339
+ align-items: center;
1340
+ justify-content: center;
1341
+ width: 1.3rem;
1342
+ height: 1.3rem;
1343
+ font-family: var(--vi-mono);
1344
+ font-size: 0.85rem;
1345
+ line-height: 1;
1346
+ text-decoration: none;
1347
+ color: var(--vi-text);
1348
+ background: rgba(0, 0, 0, 0.65);
1349
+ border: 1px solid var(--vi-border);
1350
+ border-radius: 4px;
1351
+ opacity: 0;
1352
+ transition: opacity 120ms ease;
1353
+ }
1354
+
1355
+ .vi-scene-frame-cell:hover .vi-frame-dl,
1356
+ .vi-frame-dl:focus-visible {
1357
+ opacity: 1;
1358
+ }
1359
+
1360
+ .vi-frame-dl:hover {
1361
+ border-color: var(--vi-accent);
1362
+ color: var(--vi-accent);
1363
+ }
1364
+
1365
+ /* Assembled-clip block: the video player + its Download MP4 action beneath. */
1366
+ .vi-scene-clip {
1367
+ display: flex;
1368
+ flex-direction: column;
1369
+ align-items: center;
1370
+ gap: 0.5rem;
1371
+ width: 100%;
1372
+ }
1373
+
1374
+ .vi-scene-clip .vi-btn {
1375
+ text-decoration: none;
1376
+ }
1377
+
1378
+ /* ---- first video player (modeled on the audio <audio controls> pattern) ---- */
1379
+ .vi-video-clip {
1380
+ width: 100%;
1381
+ display: flex;
1382
+ justify-content: center;
1383
+ }
1384
+
1385
+ .vi-video-player {
1386
+ width: 100%;
1387
+ min-width: 10rem;
1388
+ max-height: 30rem;
1389
+ border-radius: 6px;
1390
+ background: #000;
1391
+ }
1392
+
1393
+ /* ---- composer ---- */
1394
+ /* Lives in the stage column (under the preview + add bar), not the bottom
1395
+ prompt bar — no row-flex basis. */
1396
+ .vi-gen-composer {
1397
+ display: flex;
1398
+ flex-direction: column;
1399
+ gap: 0.9rem;
1400
+ min-width: 0;
1401
+ }
1402
+
1403
+ .vi-gen-add-bar {
1404
+ display: flex;
1405
+ flex-wrap: wrap;
1406
+ align-items: center;
1407
+ gap: 0.5rem;
1408
+ }
1409
+
1410
+ /* Capability-driven requirement banner — states prompt + start-image posture for
1411
+ the selected model, and hosts the explicit required start-image control. */
1412
+ .vi-gen-req {
1413
+ display: flex;
1414
+ flex-wrap: wrap;
1415
+ align-items: center;
1416
+ gap: 0.6rem;
1417
+ padding: 0.5rem 0.7rem;
1418
+ border: 1px solid var(--vi-border);
1419
+ border-radius: 6px;
1420
+ background: var(--vi-surface);
1421
+ }
1422
+
1423
+ .vi-gen-req-line {
1424
+ font-family: var(--vi-mono);
1425
+ font-size: 0.78rem;
1426
+ color: var(--vi-text-dim);
1427
+ line-height: 1.4;
1428
+ }
1429
+
1430
+ /* Emphasize when an edit model still needs its required start image. */
1431
+ .vi-gen-req-missing {
1432
+ border-color: var(--vi-accent);
1433
+ }
1434
+
1435
+ .vi-gen-req-missing .vi-gen-req-line {
1436
+ color: var(--vi-text);
1437
+ }
1438
+
1439
+ /* Run button + status chip, pinned stationary at the right end of the add row */
1440
+ .vi-gen-add-run {
1441
+ margin-left: auto;
1442
+ display: flex;
1443
+ align-items: center;
1444
+ gap: 0.5rem;
1445
+ }
1446
+
1447
+ /* run feedback (validation flag / job error / poll-cap resume) under the row */
1448
+ .vi-gen-run-notes {
1449
+ display: flex;
1450
+ flex-direction: column;
1451
+ gap: 0.4rem;
1452
+ }
1453
+
1454
+ .vi-gen-run-notes .vi-error {
1455
+ margin: 0;
1456
+ }
1457
+
1458
+ .vi-gen-empty {
1459
+ font-family: var(--vi-mono);
1460
+ font-size: 0.78rem;
1461
+ color: var(--vi-text-dim);
1462
+ line-height: 1.4;
1463
+ border: 1px dashed var(--vi-border);
1464
+ border-radius: 6px;
1465
+ padding: 0.9rem;
1466
+ margin: 0;
1467
+ }
1468
+
1469
+ /* ---- LLM prompt assist (Enhance / Generate) toolbar ----
1470
+ A subtle toolbar pinned at the top of the image/scene composer. The two ✨
1471
+ buttons reuse .vi-btn-sm/.vi-btn-ghost; only the lead label + inline dismissible
1472
+ error notice are bespoke, and both stay on the surrounding vi-* tokens. */
1473
+ .vi-prompt-assist {
1474
+ display: flex;
1475
+ flex-wrap: wrap;
1476
+ align-items: center;
1477
+ gap: 0.4rem 0.5rem;
1478
+ padding-bottom: 0.35rem;
1479
+ border-bottom: 1px solid var(--vi-border);
1480
+ }
1481
+
1482
+ .vi-prompt-assist-lead {
1483
+ font-family: var(--vi-mono);
1484
+ font-size: 0.7rem;
1485
+ letter-spacing: 0.04em;
1486
+ text-transform: uppercase;
1487
+ color: var(--vi-text-dim);
1488
+ margin-right: 0.15rem;
1489
+ }
1490
+
1491
+ /* Inline, dismissible failure notice — mono red like .vi-error, but sized to sit
1492
+ in the toolbar row rather than as a standalone block. */
1493
+ .vi-prompt-assist-error {
1494
+ display: inline-flex;
1495
+ align-items: center;
1496
+ gap: 0.35rem;
1497
+ font-family: var(--vi-mono);
1498
+ font-size: 0.78rem;
1499
+ color: #ff8a8a;
1500
+ }
1501
+
1502
+ .vi-prompt-assist-dismiss {
1503
+ display: inline-flex;
1504
+ align-items: center;
1505
+ justify-content: center;
1506
+ padding: 0 0.25rem;
1507
+ border: none;
1508
+ background: transparent;
1509
+ color: inherit;
1510
+ font-size: 0.85rem;
1511
+ line-height: 1;
1512
+ cursor: pointer;
1513
+ }
1514
+
1515
+ .vi-prompt-assist-dismiss:hover {
1516
+ color: var(--vi-text);
1517
+ }
1518
+
1519
+ /* ---- library picker panel ---- */
1520
+ .vi-gen-picker {
1521
+ border: 1px solid var(--vi-border);
1522
+ border-radius: 6px;
1523
+ background: var(--vi-surface-raised);
1524
+ padding: 0.9rem;
1525
+ display: flex;
1526
+ flex-direction: column;
1527
+ gap: 0.75rem;
1528
+ }
1529
+
1530
+ .vi-gen-video-upload {
1531
+ display: flex;
1532
+ flex-wrap: wrap;
1533
+ align-items: center;
1534
+ gap: 0.75rem;
1535
+ }
1536
+
1537
+ /* Library picker renders as a horizontal thumbnail strip in the studio stage. */
1538
+ .vi-gen-picker-grid {
1539
+ display: flex;
1540
+ gap: 0.6rem;
1541
+ overflow-x: auto;
1542
+ padding-bottom: 0.4rem;
1543
+ scrollbar-width: thin;
1544
+ }
1545
+
1546
+ .vi-gen-pick-cell {
1547
+ display: flex;
1548
+ flex-direction: column;
1549
+ flex: 0 0 8rem;
1550
+ gap: 0.3rem;
1551
+ padding: 0.35rem;
1552
+ margin: 0;
1553
+ border: 1px solid var(--vi-border);
1554
+ border-radius: 4px;
1555
+ background: var(--vi-surface);
1556
+ cursor: pointer;
1557
+ text-align: left;
1558
+ }
1559
+
1560
+ .vi-gen-pick-cell:hover {
1561
+ border-color: var(--vi-accent);
1562
+ }
1563
+
1564
+ .vi-gen-pick-thumb {
1565
+ display: block;
1566
+ width: 100%;
1567
+ height: auto;
1568
+ aspect-ratio: 16 / 9;
1569
+ object-fit: cover;
1570
+ border-radius: 3px;
1571
+ }
1572
+
1573
+ .vi-gen-pick-videoicon {
1574
+ display: flex;
1575
+ align-items: center;
1576
+ justify-content: center;
1577
+ aspect-ratio: 16 / 9;
1578
+ background: #000;
1579
+ border-radius: 3px;
1580
+ color: var(--vi-accent);
1581
+ font-size: 1.4rem;
1582
+ }
1583
+
1584
+ .vi-gen-pick-meta {
1585
+ font-family: var(--vi-mono);
1586
+ font-size: 0.65rem;
1587
+ color: var(--vi-text-dim);
1588
+ line-height: 1.3;
1589
+ word-break: break-word;
1590
+ }
1591
+
1592
+ .vi-gen-pick-meta code {
1593
+ color: var(--vi-text);
1594
+ }
1595
+
1596
+ /* ---- ordered parts list ---- */
1597
+ .vi-gen-parts {
1598
+ list-style: none;
1599
+ margin: 0;
1600
+ padding: 0;
1601
+ display: flex;
1602
+ flex-direction: column;
1603
+ gap: 0.6rem;
1604
+ }
1605
+
1606
+ .vi-gen-part {
1607
+ border: 1px solid var(--vi-border);
1608
+ border-radius: 6px;
1609
+ background: var(--vi-surface-raised);
1610
+ padding: 0.6rem 0.7rem;
1611
+ display: flex;
1612
+ flex-direction: column;
1613
+ gap: 0.5rem;
1614
+ }
1615
+
1616
+ .vi-gen-part-head {
1617
+ display: flex;
1618
+ align-items: center;
1619
+ gap: 0.5rem;
1620
+ }
1621
+
1622
+ .vi-gen-badge {
1623
+ font-family: var(--vi-mono);
1624
+ font-size: 0.65rem;
1625
+ text-transform: uppercase;
1626
+ letter-spacing: 0.05em;
1627
+ padding: 0.1rem 0.4rem;
1628
+ border-radius: 3px;
1629
+ border: 1px solid var(--vi-border);
1630
+ color: var(--vi-text-dim);
1631
+ }
1632
+
1633
+ .vi-gen-badge-text {
1634
+ color: var(--vi-accent);
1635
+ border-color: var(--vi-accent);
1636
+ }
1637
+
1638
+ .vi-gen-badge-image {
1639
+ color: #7fd88f;
1640
+ border-color: #7fd88f;
1641
+ }
1642
+
1643
+ .vi-gen-badge-video {
1644
+ color: #ffcf8a;
1645
+ border-color: #ffcf8a;
1646
+ }
1647
+
1648
+ .vi-gen-part-idx {
1649
+ font-family: var(--vi-mono);
1650
+ font-size: 0.72rem;
1651
+ color: var(--vi-text-dim);
1652
+ }
1653
+
1654
+ .vi-gen-part-actions {
1655
+ display: flex;
1656
+ gap: 0.3rem;
1657
+ margin-left: auto;
1658
+ }
1659
+
1660
+ .vi-gen-part-text {
1661
+ width: 100%;
1662
+ box-sizing: border-box;
1663
+ resize: vertical;
1664
+ padding: 0.45rem 0.5rem;
1665
+ font-family: var(--vi-serif);
1666
+ font-size: 0.95rem;
1667
+ line-height: 1.4;
1668
+ color: var(--vi-text);
1669
+ background: var(--vi-surface);
1670
+ border: 1px solid var(--vi-border);
1671
+ border-radius: 4px;
1672
+ }
1673
+
1674
+ .vi-gen-part-text:focus {
1675
+ outline: none;
1676
+ border-color: var(--vi-accent);
1677
+ }
1678
+
1679
+ .vi-gen-part-media {
1680
+ display: flex;
1681
+ align-items: center;
1682
+ gap: 0.75rem;
1683
+ }
1684
+
1685
+ /* per-part "attach image / attach video" row on text-only parts */
1686
+ .vi-gen-part-attach {
1687
+ display: flex;
1688
+ flex-wrap: wrap;
1689
+ gap: 0.5rem;
1690
+ }
1691
+
1692
+ .vi-gen-part-thumb {
1693
+ width: 6rem;
1694
+ height: auto;
1695
+ aspect-ratio: 16 / 9;
1696
+ object-fit: cover;
1697
+ border: 1px solid var(--vi-border);
1698
+ border-radius: 4px;
1699
+ background: #000;
1700
+ flex: 0 0 auto;
1701
+ }
1702
+
1703
+ .vi-gen-part-videoicon {
1704
+ display: flex;
1705
+ align-items: center;
1706
+ justify-content: center;
1707
+ width: 6rem;
1708
+ aspect-ratio: 16 / 9;
1709
+ background: #000;
1710
+ border: 1px solid var(--vi-border);
1711
+ border-radius: 4px;
1712
+ color: var(--vi-accent);
1713
+ font-size: 1.4rem;
1714
+ flex: 0 0 auto;
1715
+ }
1716
+
1717
+ .vi-gen-part-meta {
1718
+ font-family: var(--vi-mono);
1719
+ font-size: 0.72rem;
1720
+ color: var(--vi-text-dim);
1721
+ line-height: 1.4;
1722
+ word-break: break-word;
1723
+ }
1724
+
1725
+ .vi-gen-part-meta code {
1726
+ color: var(--vi-text);
1727
+ }
1728
+
1729
+ /* ------------------------------------------------------------------------- */
1730
+ /* Generation-wide IDENTITY bar — the compact per-part character affordance, */
1731
+ /* sibling of .vi-gen-part-attach. Deliberately slimmer than the Studio-sized */
1732
+ /* IdentityProfileControls block: it sits inside every .vi-gen-part card. */
1733
+ .vi-gen-part-identity {
1734
+ display: flex;
1735
+ flex-direction: column;
1736
+ gap: 0.4rem;
1737
+ border-top: 1px dashed var(--vi-border);
1738
+ padding-top: 0.5rem;
1739
+ }
1740
+
1741
+ .vi-gen-part-identity-row {
1742
+ display: flex;
1743
+ flex-wrap: wrap;
1744
+ align-items: center;
1745
+ gap: 0.5rem;
1746
+ }
1747
+
1748
+ /* The bound character, shown identically on EVERY part (one identity per run). */
1749
+ .vi-gen-identity-chip {
1750
+ display: inline-flex;
1751
+ align-items: center;
1752
+ gap: 0.3rem;
1753
+ font-family: var(--vi-mono);
1754
+ font-size: 0.7rem;
1755
+ padding: 0.15rem 0.45rem;
1756
+ border-radius: 3px;
1757
+ border: 1px solid var(--vi-accent);
1758
+ color: var(--vi-accent);
1759
+ }
1760
+
1761
+ .vi-gen-identity-chip-icon {
1762
+ font-size: 0.8rem;
1763
+ line-height: 1;
1764
+ }
1765
+
1766
+ .vi-gen-identity-note {
1767
+ font-family: var(--vi-mono);
1768
+ font-size: 0.68rem;
1769
+ color: var(--vi-text-dim);
1770
+ line-height: 1.4;
1771
+ }
1772
+
1773
+ /* Profile picker — a wrapping strip of small thumb+name options. Capped short so
1774
+ it never dwarfs the part card it lives in (the Studio panel uses 12rem). */
1775
+ .vi-gen-identity-picker {
1776
+ display: flex;
1777
+ flex-wrap: wrap;
1778
+ gap: 0.4rem;
1779
+ max-height: 7.5rem;
1780
+ overflow-y: auto;
1781
+ }
1782
+
1783
+ .vi-gen-identity-option {
1784
+ display: flex;
1785
+ align-items: center;
1786
+ gap: 0.35rem;
1787
+ padding: 0.25rem 0.4rem 0.25rem 0.25rem;
1788
+ background: var(--vi-surface);
1789
+ border: 1px solid var(--vi-border);
1790
+ border-radius: 4px;
1791
+ color: var(--vi-text);
1792
+ font-size: 0.75rem;
1793
+ cursor: pointer;
1794
+ }
1795
+
1796
+ .vi-gen-identity-option:hover:not(:disabled) {
1797
+ border-color: var(--vi-accent);
1798
+ }
1799
+
1800
+ .vi-gen-identity-option:disabled {
1801
+ opacity: 0.5;
1802
+ cursor: default;
1803
+ }
1804
+
1805
+ .vi-gen-identity-option-thumb {
1806
+ width: 1.6rem;
1807
+ height: 1.6rem;
1808
+ object-fit: cover;
1809
+ border-radius: 3px;
1810
+ background: #000;
1811
+ flex: 0 0 auto;
1812
+ }
1813
+
1814
+ /* A profile with no canonical 3D-model views yet (bind would fail loudly) —
1815
+ flagged in the picker itself rather than only after a failed click. */
1816
+ .vi-gen-identity-option-thumb-empty {
1817
+ display: flex;
1818
+ align-items: center;
1819
+ justify-content: center;
1820
+ color: #ffcf8a;
1821
+ font-size: 0.85rem;
1822
+ }
1823
+
1824
+ .vi-gen-identity-option-name {
1825
+ max-width: 7rem;
1826
+ overflow: hidden;
1827
+ text-overflow: ellipsis;
1828
+ white-space: nowrap;
1829
+ }
1830
+
1831
+ /* CANONICAL VIEW ROW (step 2 of the picker) — the identity's canonical views, of
1832
+ which the user picks the ONE that carries the character. Sized to stay compact
1833
+ inside a .vi-gen-part card while holding an UNKNOWN number of views: 4 today, 8
1834
+ once the azimuth slice lands, N later. Hence scroll-x + no wrap — the row never
1835
+ grows the card's height regardless of count. */
1836
+ .vi-gen-identity-views-head {
1837
+ display: flex;
1838
+ flex-wrap: wrap;
1839
+ align-items: center;
1840
+ gap: 0.5rem;
1841
+ }
1842
+
1843
+ .vi-gen-identity-views {
1844
+ display: flex;
1845
+ flex-wrap: nowrap;
1846
+ gap: 0.4rem;
1847
+ overflow-x: auto;
1848
+ padding-bottom: 0.25rem;
1849
+ }
1850
+
1851
+ .vi-gen-identity-view {
1852
+ display: flex;
1853
+ flex-direction: column;
1854
+ align-items: center;
1855
+ gap: 0.25rem;
1856
+ flex: 0 0 auto;
1857
+ padding: 0.3rem;
1858
+ background: transparent;
1859
+ border: 1px solid var(--vi-border);
1860
+ border-radius: 4px;
1861
+ cursor: pointer;
1862
+ }
1863
+
1864
+ .vi-gen-identity-view:hover:not(:disabled),
1865
+ .vi-gen-identity-view:focus-visible {
1866
+ border-color: var(--vi-accent);
1867
+ }
1868
+
1869
+ .vi-gen-identity-view:disabled {
1870
+ opacity: 0.5;
1871
+ cursor: default;
1872
+ }
1873
+
1874
+ /* Big enough to judge the ANGLE at a glance — that is the whole point of the pick. */
1875
+ .vi-gen-identity-view-thumb {
1876
+ width: 3.4rem;
1877
+ height: 3.4rem;
1878
+ object-fit: cover;
1879
+ border-radius: 3px;
1880
+ background: #000;
1881
+ display: block;
1882
+ }
1883
+
1884
+ /* "45° right profile" when the view has real azimuth provenance, else the honest
1885
+ ordinal ("view 3") — see GenIdentityBar.tsx's viewLabel. nowrap + a max-width
1886
+ pegged to the thumbnail keeps the row compact at 8+ views (it already scrolls
1887
+ horizontally via .vi-gen-identity-views) rather than growing tall or wide. */
1888
+ .vi-gen-identity-view-label {
1889
+ font-family: var(--vi-mono);
1890
+ font-size: 0.65rem;
1891
+ color: var(--vi-text-dim);
1892
+ white-space: nowrap;
1893
+ max-width: 5.2rem;
1894
+ overflow: hidden;
1895
+ text-overflow: ellipsis;
1896
+ }
1897
+
1898
+ /* The carrying view's label (real angle when known, else the honest ordinal),
1899
+ shown on the bound chip so the choice stays visible without reopening the
1900
+ picker. */
1901
+ .vi-gen-identity-chip-view {
1902
+ font-family: var(--vi-mono);
1903
+ font-size: 0.65rem;
1904
+ opacity: 0.75;
1905
+ margin-left: 0.3rem;
1906
+ }
1907
+
1908
+ /* The composer's own add-part bar (image + scene), docked under the parts list —
1909
+ the sibling of .vi-movie-timeline-foot's "+ Add goal". */
1910
+ .vi-gen-parts-foot {
1911
+ margin-top: 0.6rem;
1912
+ }
1913
+
1914
+ /* ------------------------------------------------------------------------- */
1915
+ /* Movie mode — goal-timeline editor, director panel, nested progress strip. */
1916
+ /* Reuses the .vi-gen-part / .vi-knob-window / .vi-status-* families + tokens. */
1917
+ /* ------------------------------------------------------------------------- */
1918
+
1919
+ /* Goal badge — a fourth badge colour alongside text/image/video. */
1920
+ .vi-gen-badge.vi-movie-badge {
1921
+ color: var(--vi-accent);
1922
+ border-color: var(--vi-accent);
1923
+ }
1924
+
1925
+ /* Goal frame-window row: the two numeric inputs + the derived span hint. */
1926
+ .vi-movie-window {
1927
+ flex-wrap: wrap;
1928
+ align-items: flex-end;
1929
+ }
1930
+
1931
+ .vi-movie-window-span {
1932
+ font-family: var(--vi-mono);
1933
+ font-size: 0.7rem;
1934
+ color: var(--vi-text-dim);
1935
+ padding-bottom: 0.35rem;
1936
+ }
1937
+
1938
+ /* The timeline container + its footer (Add goal + total-frames summary). */
1939
+ .vi-movie-timeline {
1940
+ display: flex;
1941
+ flex-direction: column;
1942
+ gap: 0.7rem;
1943
+ }
1944
+
1945
+ .vi-movie-timeline-foot {
1946
+ display: flex;
1947
+ flex-wrap: wrap;
1948
+ align-items: center;
1949
+ gap: 0.7rem;
1950
+ }
1951
+
1952
+ .vi-movie-summary {
1953
+ font-family: var(--vi-mono);
1954
+ font-size: 0.75rem;
1955
+ color: var(--vi-accent);
1956
+ }
1957
+
1958
+ /* Running preview: the current-segment block (readout + live frame grid). */
1959
+ .vi-movie-current {
1960
+ display: flex;
1961
+ flex-direction: column;
1962
+ gap: 0.5rem;
1963
+ margin-top: 0.5rem;
1964
+ width: 100%;
1965
+ }
1966
+
1967
+ /* The per-segment strip — a horizontally-scrollable row of segment cells. */
1968
+ .vi-movie-seg-strip {
1969
+ display: flex;
1970
+ flex-wrap: nowrap;
1971
+ gap: 0.5rem;
1972
+ margin-top: 0.6rem;
1973
+ padding-bottom: 0.3rem;
1974
+ width: 100%;
1975
+ overflow-x: auto;
1976
+ overscroll-behavior-x: contain;
1977
+ }
1978
+
1979
+ .vi-movie-seg {
1980
+ flex: 0 0 11rem;
1981
+ display: flex;
1982
+ flex-direction: column;
1983
+ gap: 0.3rem;
1984
+ padding: 0.5rem 0.6rem;
1985
+ border: 1px solid var(--vi-border);
1986
+ border-left: 3px solid var(--vi-border);
1987
+ border-radius: 4px;
1988
+ background: var(--vi-surface-raised);
1989
+ }
1990
+
1991
+ .vi-movie-seg-idx {
1992
+ font-family: var(--vi-mono);
1993
+ font-size: 0.68rem;
1994
+ color: var(--vi-text-dim);
1995
+ }
1996
+
1997
+ .vi-movie-seg-label {
1998
+ font-family: var(--vi-serif);
1999
+ font-size: 0.82rem;
2000
+ color: var(--vi-text);
2001
+ line-height: 1.3;
2002
+ }
2003
+
2004
+ .vi-movie-seg-meta {
2005
+ display: flex;
2006
+ align-items: center;
2007
+ gap: 0.4rem;
2008
+ font-family: var(--vi-mono);
2009
+ font-size: 0.68rem;
2010
+ color: var(--vi-text-dim);
2011
+ }
2012
+
2013
+ .vi-movie-seg-status {
2014
+ text-transform: uppercase;
2015
+ letter-spacing: 0.04em;
2016
+ }
2017
+
2018
+ .vi-movie-seg-attempt {
2019
+ color: var(--vi-text-dim);
2020
+ }
2021
+
2022
+ /* Score badge — a small chip; muted when there is no score yet. */
2023
+ .vi-movie-score {
2024
+ margin-left: auto;
2025
+ min-width: 1.6rem;
2026
+ text-align: center;
2027
+ padding: 0.05rem 0.35rem;
2028
+ border-radius: 3px;
2029
+ border: 1px solid #7fd88f;
2030
+ color: #7fd88f;
2031
+ font-family: var(--vi-mono);
2032
+ font-size: 0.72rem;
2033
+ }
2034
+
2035
+ .vi-movie-score.vi-movie-score-none {
2036
+ border-color: var(--vi-border);
2037
+ color: var(--vi-text-dim);
2038
+ }
2039
+
2040
+ /* Per-status accent on the cell's left border (reusing the status palette). */
2041
+ .vi-movie-seg-done {
2042
+ border-left-color: #7fd88f;
2043
+ }
2044
+ .vi-movie-seg-generating,
2045
+ .vi-movie-seg-scoring,
2046
+ .vi-movie-seg-resumed {
2047
+ border-left-color: var(--vi-accent);
2048
+ }
2049
+ .vi-movie-seg-skipped {
2050
+ border-left-color: #ffcf8a;
2051
+ }
2052
+ .vi-movie-seg-pending {
2053
+ border-left-color: var(--vi-border);
2054
+ }
2055
+
2056
+ /* Director panel — the opt-in vision knobs, indented under its toggle. */
2057
+ .vi-director-body {
2058
+ display: flex;
2059
+ flex-direction: column;
2060
+ gap: 0.9rem;
2061
+ margin-top: 0.6rem;
2062
+ padding-left: 0.7rem;
2063
+ border-left: 2px solid var(--vi-border);
2064
+ }
2065
+
2066
+ /* ------------------------------------------------------------------------- */
2067
+ /* Phase 5 — Audio Crop station: waveform stage, draggable temporal region, */
2068
+ /* playhead, clip results. Reuses .vi-crop-editor / .vi-crop-controls / */
2069
+ /* .vi-crop-fields / .vi-region-list / .vi-result-* from the Image Crop CSS. */
2070
+ /* ------------------------------------------------------------------------- */
2071
+
2072
+ .vi-audio-stage {
2073
+ position: relative;
2074
+ flex: 1 1 22rem;
2075
+ min-width: 16rem;
2076
+ border: 1px solid var(--vi-border);
2077
+ background: #000;
2078
+ }
2079
+
2080
+ .vi-audio-canvas {
2081
+ display: block;
2082
+ width: 100%;
2083
+ height: 128px;
2084
+ }
2085
+
2086
+ .vi-audio-overlay {
2087
+ position: absolute;
2088
+ inset: 0;
2089
+ overflow: hidden;
2090
+ }
2091
+
2092
+ .vi-audio-region {
2093
+ position: absolute;
2094
+ top: 0;
2095
+ bottom: 0;
2096
+ box-sizing: border-box;
2097
+ border-left: 1px solid var(--vi-accent);
2098
+ border-right: 1px solid var(--vi-accent);
2099
+ background: rgba(138, 180, 255, 0.14);
2100
+ box-shadow: 0 0 0 100vmax rgba(0, 0, 0, 0.45);
2101
+ cursor: move;
2102
+ touch-action: none;
2103
+ }
2104
+
2105
+ .vi-audio-region-active {
2106
+ background: rgba(138, 180, 255, 0.22);
2107
+ }
2108
+
2109
+ .vi-audio-handle {
2110
+ position: absolute;
2111
+ top: 0;
2112
+ bottom: 0;
2113
+ width: 10px;
2114
+ background: var(--vi-accent);
2115
+ opacity: 0.85;
2116
+ touch-action: none;
2117
+ }
2118
+
2119
+ .vi-audio-handle-start {
2120
+ left: -5px;
2121
+ cursor: ew-resize;
2122
+ }
2123
+
2124
+ .vi-audio-handle-end {
2125
+ right: -5px;
2126
+ cursor: ew-resize;
2127
+ }
2128
+
2129
+ .vi-audio-playhead {
2130
+ position: absolute;
2131
+ top: 0;
2132
+ bottom: 0;
2133
+ width: 1px;
2134
+ background: #ffcf8a;
2135
+ pointer-events: none;
2136
+ }
2137
+
2138
+ .vi-audio-note {
2139
+ position: absolute;
2140
+ left: 0.5rem;
2141
+ bottom: 0.4rem;
2142
+ font-family: var(--vi-mono);
2143
+ font-size: 0.72rem;
2144
+ color: var(--vi-text-dim);
2145
+ background: rgba(0, 0, 0, 0.55);
2146
+ padding: 0.1rem 0.4rem;
2147
+ border-radius: 3px;
2148
+ }
2149
+
2150
+ .vi-audio-note-warn {
2151
+ color: #ffcf8a;
2152
+ }
2153
+
2154
+ .vi-audio-clip {
2155
+ display: flex;
2156
+ flex-direction: column;
2157
+ align-items: center;
2158
+ gap: 0.4rem;
2159
+ width: 100%;
2160
+ }
2161
+
2162
+ .vi-audio-player {
2163
+ width: 100%;
2164
+ min-width: 10rem;
2165
+ }
2166
+
2167
+ /* ------------------------------------------------------------------------- */
2168
+ /* Studio composition (Frames + Generate) — the reference layout: a left */
2169
+ /* settings rail, a large center preview stacked over a horizontal thumbnail */
2170
+ /* strip, and a bottom prompt/action bar spanning the full width. Pure layout */
2171
+ /* primitives; the knob / preview / strip / part classes above are reused as-is */
2172
+ /* and the image-crop / audio-crop stations do not use any .vi-studio* class. */
2173
+ /* ------------------------------------------------------------------------- */
2174
+
2175
+ .vi-studio {
2176
+ display: grid;
2177
+ grid-template-columns: 17rem minmax(0, 1fr);
2178
+ grid-template-areas:
2179
+ "rail stage"
2180
+ "prompt prompt";
2181
+ gap: 1.25rem 1.5rem;
2182
+ align-items: start;
2183
+ min-height: 0;
2184
+ }
2185
+
2186
+ @media (max-width: 62rem) {
2187
+ .vi-studio {
2188
+ grid-template-columns: 1fr;
2189
+ grid-template-areas:
2190
+ "rail"
2191
+ "stage"
2192
+ "prompt";
2193
+ }
2194
+ }
2195
+
2196
+ .vi-studio__rail {
2197
+ grid-area: rail;
2198
+ }
2199
+
2200
+ /* center column: large preview stacked over the thumbnail strip */
2201
+ .vi-studio-stage {
2202
+ grid-area: stage;
2203
+ min-width: 0;
2204
+ display: flex;
2205
+ flex-direction: column;
2206
+ gap: 1rem;
2207
+ }
2208
+
2209
+ .vi-studio-preview {
2210
+ display: flex;
2211
+ align-items: center;
2212
+ justify-content: center;
2213
+ min-height: 22rem;
2214
+ border: 1px solid var(--vi-border);
2215
+ border-radius: 8px;
2216
+ background: #000;
2217
+ padding: 0.75rem;
2218
+ overflow: hidden;
2219
+ text-align: center;
2220
+ }
2221
+
2222
+ .vi-studio-preview img {
2223
+ max-width: 100%;
2224
+ max-height: 30rem;
2225
+ height: auto;
2226
+ object-fit: contain;
2227
+ display: block;
2228
+ }
2229
+
2230
+ /* thumbnail strip wrapper — the frame grid / library picker scroll within it */
2231
+ .vi-studio-strip {
2232
+ min-width: 0;
2233
+ }
2234
+
2235
+ /* bottom prompt bar: composer / toolbar on the left, primary action on right */
2236
+ .vi-studio-prompt {
2237
+ grid-area: prompt;
2238
+ display: flex;
2239
+ flex-wrap: wrap;
2240
+ gap: 1rem 1.5rem;
2241
+ align-items: flex-start;
2242
+ border-top: 1px solid var(--vi-border);
2243
+ padding-top: 1.1rem;
2244
+ }
2245
+
2246
+ .vi-studio-prompt-main {
2247
+ flex: 1 1 22rem;
2248
+ min-width: 0;
2249
+ display: flex;
2250
+ flex-direction: column;
2251
+ gap: 0.75rem;
2252
+ }
2253
+
2254
+ .vi-studio-run {
2255
+ flex: 0 0 auto;
2256
+ min-width: 12rem;
2257
+ display: flex;
2258
+ flex-direction: column;
2259
+ gap: 0.6rem;
2260
+ align-items: stretch;
2261
+ }
2262
+
2263
+ /* ------------------------------------------------------------------------- */
2264
+ /* Studio workbench — the [ sidebar | tabbed main ] shell that hosts the */
2265
+ /* three studio stations as sub-tabs beside the static session-library queue. */
2266
+ /* Sits inside .vi-main (which centers). Reuses the dark tokens + the */
2267
+ /* @media (max-width:62rem) breakpoint convention. Wrapped station bodies keep */
2268
+ /* their own .station-wide 68rem cap inside the main column. */
2269
+ /* ------------------------------------------------------------------------- */
2270
+
2271
+ .vi-workbench {
2272
+ display: grid;
2273
+ grid-template-columns: 18rem minmax(0, 1fr);
2274
+ gap: 1.5rem;
2275
+ align-items: stretch;
2276
+ width: 100%;
2277
+ max-width: 96rem;
2278
+ height: 100%;
2279
+ min-height: 0;
2280
+ }
2281
+
2282
+ .vi-workbench-main {
2283
+ min-width: 0;
2284
+ min-height: 0;
2285
+ height: 100%;
2286
+ display: flex;
2287
+ flex-direction: column;
2288
+ }
2289
+
2290
+ /* The scroll container: every sub-tab is exactly the sidebar's height and
2291
+ scrolls internally, so the page and navbar never scroll. */
2292
+ .vi-workbench-body {
2293
+ min-width: 0;
2294
+ min-height: 0;
2295
+ flex: 1;
2296
+ overflow-y: auto;
2297
+ }
2298
+
2299
+ /* Mobile-only hamburger — leading control of the sub-tab strip, toggling the
2300
+ off-canvas drawer. Hidden at >=62rem (the sidebar owns its grid column); the
2301
+ narrow @media block below flips it to inline-flex. */
2302
+ .vi-hamburger {
2303
+ display: none;
2304
+ align-items: center;
2305
+ justify-content: center;
2306
+ flex: 0 0 auto;
2307
+ padding: 0.3rem 0.5rem;
2308
+ margin-right: 0.4rem;
2309
+ border-radius: 6px;
2310
+ border: 1px solid var(--vi-border);
2311
+ background: var(--vi-surface-raised);
2312
+ color: var(--vi-text);
2313
+ cursor: pointer;
2314
+ line-height: 0;
2315
+ }
2316
+
2317
+ .vi-hamburger:hover {
2318
+ border-color: var(--vi-accent);
2319
+ }
2320
+
2321
+ /* Scrim behind the mobile drawer — hidden on desktop; the @media block shows it. */
2322
+ .vi-scrim {
2323
+ display: none;
2324
+ }
2325
+
2326
+ .vi-workbench-sidebar {
2327
+ border: 1px solid var(--vi-border);
2328
+ border-radius: 8px;
2329
+ background: var(--vi-surface-raised);
2330
+ padding: 1rem;
2331
+ min-height: 0;
2332
+ height: 100%;
2333
+ overflow: auto;
2334
+ scrollbar-width: thin;
2335
+ }
2336
+
2337
+ /* ---- sub-tab strip (studio stations) — visually mirrors .vi-tab, own class
2338
+ so it stays independently themeable. ---- */
2339
+ .vi-subtabs {
2340
+ display: flex;
2341
+ flex-wrap: wrap;
2342
+ align-items: center;
2343
+ gap: 0.25rem;
2344
+ margin-bottom: 0.9rem;
2345
+ padding-bottom: 0.6rem;
2346
+ border-bottom: 1px solid var(--vi-border);
2347
+ font-family: var(--vi-mono);
2348
+ font-size: 0.85rem;
2349
+ flex-shrink: 0;
2350
+ }
2351
+
2352
+ .vi-subtab {
2353
+ color: var(--vi-text-dim);
2354
+ text-decoration: none;
2355
+ padding: 0.35rem 0.8rem;
2356
+ border-radius: 6px;
2357
+ border: 1px solid transparent;
2358
+ }
2359
+
2360
+ .vi-subtab:hover {
2361
+ color: var(--vi-text);
2362
+ }
2363
+
2364
+ .vi-subtab-active {
2365
+ color: var(--vi-text);
2366
+ background: var(--vi-surface);
2367
+ border-color: var(--vi-border);
2368
+ }
2369
+
2370
+ /* ---- session-library queue (the sidebar body) ---- */
2371
+ .vi-lib {
2372
+ display: flex;
2373
+ flex-direction: column;
2374
+ gap: 0.9rem;
2375
+ font-family: var(--vi-mono);
2376
+ }
2377
+
2378
+ .vi-lib-header {
2379
+ display: flex;
2380
+ align-items: center;
2381
+ gap: 0.5rem;
2382
+ padding-bottom: 0.6rem;
2383
+ border-bottom: 1px solid var(--vi-border);
2384
+ }
2385
+
2386
+ .vi-lib-title {
2387
+ margin: 0;
2388
+ font-family: var(--vi-serif);
2389
+ font-weight: 500;
2390
+ font-size: 1.05rem;
2391
+ }
2392
+
2393
+ .vi-lib-count {
2394
+ font-size: 0.72rem;
2395
+ color: var(--vi-text-dim);
2396
+ border: 1px solid var(--vi-border);
2397
+ border-radius: 999px;
2398
+ padding: 0.05rem 0.5rem;
2399
+ }
2400
+
2401
+ .vi-lib-header .vi-btn {
2402
+ margin-left: auto;
2403
+ }
2404
+
2405
+ .vi-lib-empty {
2406
+ margin: 0;
2407
+ padding: 0.9rem;
2408
+ font-size: 0.78rem;
2409
+ color: var(--vi-text-dim);
2410
+ line-height: 1.45;
2411
+ border: 1px dashed var(--vi-border);
2412
+ border-radius: 6px;
2413
+ }
2414
+
2415
+ .vi-lib-list {
2416
+ list-style: none;
2417
+ margin: 0;
2418
+ padding: 0;
2419
+ display: flex;
2420
+ flex-direction: column;
2421
+ gap: 0.75rem;
2422
+ }
2423
+
2424
+ .vi-lib-item {
2425
+ display: flex;
2426
+ flex-direction: column;
2427
+ gap: 0.5rem;
2428
+ padding: 0.6rem 0.7rem;
2429
+ border: 1px solid var(--vi-border);
2430
+ border-radius: 6px;
2431
+ background: var(--vi-surface);
2432
+ }
2433
+
2434
+ .vi-lib-top {
2435
+ display: flex;
2436
+ align-items: center;
2437
+ gap: 0.5rem;
2438
+ }
2439
+
2440
+ .vi-lib-badge {
2441
+ flex: 0 0 auto;
2442
+ font-size: 0.62rem;
2443
+ text-transform: uppercase;
2444
+ letter-spacing: 0.05em;
2445
+ padding: 0.1rem 0.4rem;
2446
+ border-radius: 3px;
2447
+ border: 1px solid var(--vi-border);
2448
+ color: var(--vi-accent);
2449
+ }
2450
+
2451
+ .vi-lib-name {
2452
+ font-size: 0.72rem;
2453
+ color: var(--vi-text-dim);
2454
+ overflow: hidden;
2455
+ text-overflow: ellipsis;
2456
+ white-space: nowrap;
2457
+ }
2458
+
2459
+ .vi-lib-thumb {
2460
+ border: 1px solid var(--vi-border);
2461
+ border-radius: 4px;
2462
+ background: #000;
2463
+ overflow: hidden;
2464
+ }
2465
+
2466
+ .vi-lib-thumb img,
2467
+ .vi-lib-thumb video {
2468
+ max-width: 100%;
2469
+ height: auto;
2470
+ display: block;
2471
+ object-fit: contain;
2472
+ }
2473
+
2474
+ .vi-lib-placeholder {
2475
+ display: flex;
2476
+ align-items: center;
2477
+ justify-content: center;
2478
+ aspect-ratio: 16 / 9;
2479
+ border: 1px solid var(--vi-border);
2480
+ border-radius: 4px;
2481
+ background: #000;
2482
+ color: var(--vi-text-dim);
2483
+ font-size: 0.75rem;
2484
+ text-transform: uppercase;
2485
+ letter-spacing: 0.05em;
2486
+ }
2487
+
2488
+ .vi-lib-meta {
2489
+ display: flex;
2490
+ flex-wrap: wrap;
2491
+ gap: 0.5rem;
2492
+ font-size: 0.68rem;
2493
+ color: var(--vi-text-dim);
2494
+ line-height: 1.3;
2495
+ word-break: break-word;
2496
+ }
2497
+
2498
+ .vi-lib-meta code {
2499
+ color: var(--vi-text);
2500
+ }
2501
+
2502
+ .vi-lib-actions {
2503
+ display: flex;
2504
+ flex-wrap: wrap;
2505
+ align-items: center;
2506
+ gap: 0.4rem;
2507
+ }
2508
+
2509
+ .vi-lib-hint {
2510
+ font-size: 0.62rem;
2511
+ color: var(--vi-text-dim);
2512
+ text-transform: uppercase;
2513
+ letter-spacing: 0.04em;
2514
+ }
2515
+
2516
+ /* A failed studio-clip archive (Remove on a durable clip row) — mirrors
2517
+ .vi-proc-err's color; the row itself is restored, this just explains why. */
2518
+ .vi-lib-error {
2519
+ margin: 0;
2520
+ font-size: 0.68rem;
2521
+ color: #ff8a8a;
2522
+ line-height: 1.35;
2523
+ }
2524
+
2525
+ /* ---- studio "Source clip" picker: thumbnailed, previewable rows ----
2526
+ The source-clip summary + each picker row show a poster-frame still (a compact
2527
+ 16:9 cover tile) that plays inline on demand, mirroring the Session Library's
2528
+ thumbnail idiom. A row is a flex container (thumb | body) — NOT one big button —
2529
+ so a <video controls> can live inside it. Kept separate from the .vi-lib-* rules. */
2530
+ .vi-studio-src-selected {
2531
+ display: flex;
2532
+ align-items: center;
2533
+ gap: 0.6rem;
2534
+ flex-wrap: wrap;
2535
+ }
2536
+
2537
+ .vi-studio-src-row {
2538
+ display: flex;
2539
+ align-items: flex-start;
2540
+ gap: 0.6rem;
2541
+ padding: 0.4rem;
2542
+ margin-bottom: 0.4rem;
2543
+ border: 1px solid var(--vi-border);
2544
+ border-radius: 6px;
2545
+ background: var(--vi-surface);
2546
+ }
2547
+
2548
+ .vi-studio-src-thumb {
2549
+ flex: 0 0 auto;
2550
+ width: 104px;
2551
+ padding: 0;
2552
+ position: relative;
2553
+ border: 1px solid var(--vi-border);
2554
+ border-radius: 4px;
2555
+ background: #000;
2556
+ overflow: hidden;
2557
+ }
2558
+
2559
+ .vi-studio-src-thumb video {
2560
+ display: block;
2561
+ width: 100%;
2562
+ height: 58px;
2563
+ object-fit: cover;
2564
+ }
2565
+
2566
+ button.vi-studio-src-thumb {
2567
+ cursor: pointer;
2568
+ color: inherit;
2569
+ }
2570
+
2571
+ /* Playing state: the tile grows so the native <video controls> are usable, and the
2572
+ frame is shown whole (contain) rather than cropped. */
2573
+ .vi-studio-src-thumb--playing {
2574
+ width: 220px;
2575
+ max-width: 100%;
2576
+ }
2577
+
2578
+ .vi-studio-src-thumb--playing video {
2579
+ height: auto;
2580
+ object-fit: contain;
2581
+ }
2582
+
2583
+ /* ▶ overlay on the poster (non-interactive — the wrapping button takes the click). */
2584
+ .vi-studio-src-play {
2585
+ position: absolute;
2586
+ inset: 0;
2587
+ display: flex;
2588
+ align-items: center;
2589
+ justify-content: center;
2590
+ font-size: 1.3rem;
2591
+ color: #fff;
2592
+ background: rgba(0, 0, 0, 0.28);
2593
+ pointer-events: none;
2594
+ }
2595
+
2596
+ /* The placeholder tile (a clip with no streamable output) reuses .vi-lib-placeholder
2597
+ for its look, but must match the fixed poster width, not the 16:9 auto-height. */
2598
+ .vi-studio-src-thumb.vi-lib-placeholder {
2599
+ height: 58px;
2600
+ aspect-ratio: auto;
2601
+ }
2602
+
2603
+ .vi-studio-src-body {
2604
+ flex: 1 1 auto;
2605
+ min-width: 0;
2606
+ display: flex;
2607
+ flex-direction: column;
2608
+ gap: 0.35rem;
2609
+ }
2610
+
2611
+ .vi-studio-src-meta {
2612
+ display: flex;
2613
+ flex-wrap: wrap;
2614
+ gap: 0.5rem;
2615
+ font-size: 0.72rem;
2616
+ color: var(--vi-text-dim);
2617
+ line-height: 1.3;
2618
+ font-variant-numeric: tabular-nums;
2619
+ word-break: break-word;
2620
+ }
2621
+
2622
+ .vi-studio-src-meta code {
2623
+ color: var(--vi-text);
2624
+ }
2625
+
2626
+ .vi-studio-src-actions {
2627
+ display: flex;
2628
+ flex-wrap: wrap;
2629
+ gap: 0.35rem;
2630
+ }
2631
+
2632
+ /* ---- active-processes section (the session-wide "memory of processes") ----
2633
+ Sits above the library list in the sidebar; visible from every sub-tab. Reuses
2634
+ the .vi-lib* look + the .vi-status-* color conventions. */
2635
+ .vi-proc {
2636
+ display: flex;
2637
+ flex-direction: column;
2638
+ gap: 0.6rem;
2639
+ font-family: var(--vi-mono);
2640
+ padding-bottom: 0.9rem;
2641
+ margin-bottom: 0.3rem;
2642
+ border-bottom: 1px solid var(--vi-border);
2643
+ }
2644
+
2645
+ .vi-proc-header {
2646
+ display: flex;
2647
+ align-items: center;
2648
+ gap: 0.5rem;
2649
+ }
2650
+
2651
+ .vi-proc-title {
2652
+ margin: 0;
2653
+ font-family: var(--vi-serif);
2654
+ font-weight: 500;
2655
+ font-size: 1.05rem;
2656
+ }
2657
+
2658
+ .vi-proc-count {
2659
+ font-size: 0.72rem;
2660
+ color: var(--vi-text-dim);
2661
+ border: 1px solid var(--vi-border);
2662
+ border-radius: 999px;
2663
+ padding: 0.05rem 0.5rem;
2664
+ }
2665
+
2666
+ .vi-proc-header .vi-btn {
2667
+ margin-left: auto;
2668
+ }
2669
+
2670
+ .vi-proc-list {
2671
+ list-style: none;
2672
+ margin: 0;
2673
+ padding: 0;
2674
+ display: flex;
2675
+ flex-direction: column;
2676
+ gap: 0.5rem;
2677
+ }
2678
+
2679
+ .vi-proc-item {
2680
+ display: flex;
2681
+ flex-direction: column;
2682
+ gap: 0.35rem;
2683
+ padding: 0.5rem 0.6rem;
2684
+ border: 1px solid var(--vi-border);
2685
+ border-radius: 6px;
2686
+ background: var(--vi-surface);
2687
+ }
2688
+
2689
+ .vi-proc-link {
2690
+ display: flex;
2691
+ flex-direction: column;
2692
+ gap: 0.25rem;
2693
+ text-decoration: none;
2694
+ color: inherit;
2695
+ }
2696
+
2697
+ .vi-proc-link:hover .vi-proc-station {
2698
+ color: var(--vi-accent);
2699
+ }
2700
+
2701
+ .vi-proc-top {
2702
+ display: flex;
2703
+ align-items: center;
2704
+ gap: 0.45rem;
2705
+ }
2706
+
2707
+ .vi-proc-station {
2708
+ font-size: 0.74rem;
2709
+ color: var(--vi-text);
2710
+ }
2711
+
2712
+ .vi-proc-top .vi-status {
2713
+ margin-right: 0;
2714
+ }
2715
+
2716
+ .vi-proc-progress {
2717
+ font-size: 0.68rem;
2718
+ color: var(--vi-text-dim);
2719
+ font-variant-numeric: tabular-nums;
2720
+ white-space: nowrap;
2721
+ }
2722
+
2723
+ .vi-proc-elapsed {
2724
+ margin-left: auto;
2725
+ font-size: 0.7rem;
2726
+ color: var(--vi-text-dim);
2727
+ font-variant-numeric: tabular-nums;
2728
+ }
2729
+
2730
+ .vi-proc-label {
2731
+ font-size: 0.68rem;
2732
+ color: var(--vi-text-dim);
2733
+ overflow: hidden;
2734
+ text-overflow: ellipsis;
2735
+ white-space: nowrap;
2736
+ }
2737
+
2738
+ .vi-proc-msg {
2739
+ display: flex;
2740
+ flex-wrap: wrap;
2741
+ align-items: center;
2742
+ gap: 0.4rem;
2743
+ font-size: 0.68rem;
2744
+ color: var(--vi-text-dim);
2745
+ line-height: 1.35;
2746
+ }
2747
+
2748
+ .vi-proc-err {
2749
+ color: #ff8a8a;
2750
+ }
2751
+
2752
+ .vi-proc-btn {
2753
+ flex: 0 0 auto;
2754
+ }
2755
+
2756
+ /* A spinning ring while a process runs; a static dot otherwise. */
2757
+ .vi-proc-spinner {
2758
+ flex: 0 0 auto;
2759
+ width: 0.72rem;
2760
+ height: 0.72rem;
2761
+ border-radius: 50%;
2762
+ border: 2px solid var(--vi-border);
2763
+ border-top-color: var(--vi-accent);
2764
+ animation: vi-proc-spin 0.7s linear infinite;
2765
+ }
2766
+
2767
+ .vi-proc-dot {
2768
+ flex: 0 0 auto;
2769
+ width: 0.72rem;
2770
+ height: 0.72rem;
2771
+ border-radius: 50%;
2772
+ border: 2px solid var(--vi-border);
2773
+ }
2774
+
2775
+ @keyframes vi-proc-spin {
2776
+ to {
2777
+ transform: rotate(360deg);
2778
+ }
2779
+ }
2780
+
2781
+ /* ── Worker-call rows: expandable progress / stage / stalled / log ──────────── */
2782
+
2783
+ /* The disclosure caret — a bare, keyboard-focusable button that toggles the panel.
2784
+ Reuses the row's mono type; sized to sit inline with the spinner + status. */
2785
+ .vi-proc-toggle {
2786
+ flex: 0 0 auto;
2787
+ display: inline-flex;
2788
+ align-items: center;
2789
+ justify-content: center;
2790
+ width: 1.1rem;
2791
+ height: 1.1rem;
2792
+ padding: 0;
2793
+ border: 0;
2794
+ background: none;
2795
+ color: var(--vi-text-dim);
2796
+ cursor: pointer;
2797
+ }
2798
+
2799
+ .vi-proc-toggle:hover .vi-proc-caret {
2800
+ color: var(--vi-accent);
2801
+ }
2802
+
2803
+ .vi-proc-caret {
2804
+ font-size: 0.7rem;
2805
+ line-height: 1;
2806
+ }
2807
+
2808
+ /* Stalled indicator — replaces the accent spinner so a wedged render reads AMBER,
2809
+ not live. A slow pulse (never the spinner's active spin) says "stuck", not "busy". */
2810
+ .vi-proc-stall-dot {
2811
+ flex: 0 0 auto;
2812
+ width: 0.72rem;
2813
+ height: 0.72rem;
2814
+ border-radius: 50%;
2815
+ background: #ffcf8a;
2816
+ animation: vi-proc-stall-pulse 1.6s ease-in-out infinite;
2817
+ }
2818
+
2819
+ .vi-proc-stall-badge {
2820
+ font-size: 0.62rem;
2821
+ text-transform: uppercase;
2822
+ letter-spacing: 0.05em;
2823
+ color: #ffcf8a;
2824
+ border: 1px solid #ffcf8a;
2825
+ border-radius: 999px;
2826
+ padding: 0.02rem 0.4rem;
2827
+ }
2828
+
2829
+ @keyframes vi-proc-stall-pulse {
2830
+ 0%,
2831
+ 100% {
2832
+ opacity: 1;
2833
+ }
2834
+ 50% {
2835
+ opacity: 0.35;
2836
+ }
2837
+ }
2838
+
2839
+ /* The expanded detail panel: progress bar + stage, message, last-movement, log. */
2840
+ .vi-proc-detail {
2841
+ display: flex;
2842
+ flex-direction: column;
2843
+ gap: 0.45rem;
2844
+ padding: 0.5rem 0 0.15rem;
2845
+ border-top: 1px solid var(--vi-border);
2846
+ }
2847
+
2848
+ .vi-proc-progbar-row {
2849
+ display: flex;
2850
+ align-items: center;
2851
+ gap: 0.5rem;
2852
+ }
2853
+
2854
+ .vi-proc-progbar {
2855
+ position: relative;
2856
+ flex: 1 1 auto;
2857
+ height: 0.4rem;
2858
+ border-radius: 999px;
2859
+ background: var(--vi-surface-raised);
2860
+ border: 1px solid var(--vi-border);
2861
+ overflow: hidden;
2862
+ }
2863
+
2864
+ .vi-proc-progbar-fill {
2865
+ display: block;
2866
+ height: 100%;
2867
+ width: 0;
2868
+ background: var(--vi-accent);
2869
+ transition: width 0.4s ease;
2870
+ }
2871
+
2872
+ /* Unknown progress → a sweeping band (indeterminate "working"), no fabricated %. */
2873
+ .vi-proc-progbar-indeterminate .vi-proc-progbar-fill {
2874
+ width: 35%;
2875
+ animation: vi-proc-indeterminate 1.3s ease-in-out infinite;
2876
+ }
2877
+
2878
+ /* Stalled → the bar goes amber (determinate or indeterminate) and stops sweeping. */
2879
+ .vi-proc-progbar-stalled .vi-proc-progbar-fill {
2880
+ background: #ffcf8a;
2881
+ animation: none;
2882
+ }
2883
+
2884
+ .vi-proc-progbar-stalled.vi-proc-progbar-indeterminate .vi-proc-progbar-fill {
2885
+ width: 100%;
2886
+ opacity: 0.5;
2887
+ }
2888
+
2889
+ @keyframes vi-proc-indeterminate {
2890
+ 0% {
2891
+ transform: translateX(-120%);
2892
+ }
2893
+ 100% {
2894
+ transform: translateX(320%);
2895
+ }
2896
+ }
2897
+
2898
+ .vi-proc-stage {
2899
+ flex: 0 0 auto;
2900
+ font-size: 0.66rem;
2901
+ color: var(--vi-text-dim);
2902
+ font-variant-numeric: tabular-nums;
2903
+ white-space: nowrap;
2904
+ }
2905
+
2906
+ .vi-proc-detail-msg {
2907
+ margin: 0;
2908
+ font-size: 0.68rem;
2909
+ color: var(--vi-text);
2910
+ line-height: 1.35;
2911
+ word-break: break-word;
2912
+ }
2913
+
2914
+ .vi-proc-detail-meta {
2915
+ display: flex;
2916
+ align-items: center;
2917
+ gap: 0.4rem;
2918
+ }
2919
+
2920
+ .vi-proc-lastmove {
2921
+ font-size: 0.66rem;
2922
+ color: var(--vi-text-dim);
2923
+ font-variant-numeric: tabular-nums;
2924
+ }
2925
+
2926
+ .vi-proc-lastmove-stalled {
2927
+ color: #ffcf8a;
2928
+ }
2929
+
2930
+ /* The scrolling log tail — monospace, capped height with its own scroll, pinned to
2931
+ the newest (bottom) line by the row's effect. */
2932
+ .vi-proc-log {
2933
+ margin: 0;
2934
+ max-height: 9rem;
2935
+ overflow: auto;
2936
+ padding: 0.4rem 0.5rem;
2937
+ background: var(--vi-surface-raised);
2938
+ border: 1px solid var(--vi-border);
2939
+ border-radius: 4px;
2940
+ font-family: var(--vi-mono);
2941
+ font-size: 0.64rem;
2942
+ line-height: 1.4;
2943
+ color: var(--vi-text-dim);
2944
+ white-space: pre-wrap;
2945
+ word-break: break-word;
2946
+ }
2947
+
2948
+ .vi-proc-log-empty {
2949
+ margin: 0;
2950
+ font-size: 0.66rem;
2951
+ color: var(--vi-text-dim);
2952
+ font-style: italic;
2953
+ }
2954
+
2955
+ @media (max-width: 62rem) {
2956
+ .vi-workbench {
2957
+ grid-template-columns: 1fr;
2958
+ }
2959
+
2960
+ /* The tab-strip hamburger replaces the retired in-flow toggle. */
2961
+ .vi-hamburger {
2962
+ display: inline-flex;
2963
+ }
2964
+
2965
+ /* Off-canvas session-library drawer: slid out by default, in when .is-open. */
2966
+ .vi-workbench-sidebar {
2967
+ position: fixed;
2968
+ top: 0;
2969
+ left: 0;
2970
+ height: 100dvh;
2971
+ width: min(20rem, 85vw);
2972
+ z-index: 40;
2973
+ border-radius: 0;
2974
+ box-shadow: 0 0 2.5rem rgba(0, 0, 0, 0.6);
2975
+ overflow: auto;
2976
+ transform: translateX(-100%);
2977
+ transition: transform 160ms ease;
2978
+ }
2979
+ .vi-workbench.is-open .vi-workbench-sidebar {
2980
+ transform: translateX(0);
2981
+ }
2982
+
2983
+ /* Scrim sits above content (z 35) and below the drawer (z 40). */
2984
+ .vi-scrim {
2985
+ display: block;
2986
+ position: fixed;
2987
+ inset: 0;
2988
+ z-index: 35;
2989
+ background: rgba(0, 0, 0, 0.45);
2990
+ border: 0;
2991
+ }
2992
+ }
2993
+
2994
+ /* ------------------------------------------------------------------------- */
2995
+ /* Sidebar tab split — [ Active Processes | Session Library ] switcher above */
2996
+ /* the two tab bodies. Reuses the segmented .vi-gen-mode control. */
2997
+ /* ------------------------------------------------------------------------- */
2998
+ .vi-side-tabs {
2999
+ display: flex;
3000
+ width: 100%;
3001
+ margin: 0 0 0.9rem;
3002
+ }
3003
+
3004
+ .vi-side-tabs .vi-gen-mode-btn {
3005
+ flex: 1 1 0;
3006
+ display: inline-flex;
3007
+ align-items: center;
3008
+ justify-content: center;
3009
+ gap: 0.4rem;
3010
+ padding: 0.4rem 0.6rem;
3011
+ min-width: 0;
3012
+ }
3013
+
3014
+ .vi-side-tab-badge {
3015
+ flex: 0 0 auto;
3016
+ font-size: 0.62rem;
3017
+ line-height: 1;
3018
+ padding: 0.1rem 0.35rem;
3019
+ border-radius: 999px;
3020
+ border: 1px solid currentColor;
3021
+ }
3022
+
3023
+ /* The Active Processes tab body no longer separates from a library section
3024
+ below it, so drop the divider the old stacked layout carried. */
3025
+ .vi-proc {
3026
+ border-bottom: 0;
3027
+ padding-bottom: 0;
3028
+ margin-bottom: 0;
3029
+ }
3030
+
3031
+ /* ---- Session Library: generation-run groups ---- */
3032
+ .vi-lib-group {
3033
+ display: flex;
3034
+ flex-direction: column;
3035
+ gap: 0.5rem;
3036
+ padding: 0.6rem 0.7rem;
3037
+ border: 1px solid var(--vi-border);
3038
+ border-radius: 6px;
3039
+ background: var(--vi-surface);
3040
+ }
3041
+
3042
+ .vi-lib-group-toggle {
3043
+ display: flex;
3044
+ align-items: center;
3045
+ gap: 0.5rem;
3046
+ width: 100%;
3047
+ padding: 0;
3048
+ border: 0;
3049
+ background: none;
3050
+ color: inherit;
3051
+ font-family: var(--vi-mono);
3052
+ cursor: pointer;
3053
+ text-align: left;
3054
+ }
3055
+
3056
+ .vi-lib-group-caret {
3057
+ flex: 0 0 auto;
3058
+ font-size: 0.7rem;
3059
+ color: var(--vi-text-dim);
3060
+ }
3061
+
3062
+ .vi-lib-group-toggle:hover .vi-lib-group-caret {
3063
+ color: var(--vi-accent);
3064
+ }
3065
+
3066
+ .vi-lib-group-model {
3067
+ flex: 1 1 auto;
3068
+ min-width: 0;
3069
+ font-size: 0.72rem;
3070
+ color: var(--vi-text);
3071
+ overflow: hidden;
3072
+ text-overflow: ellipsis;
3073
+ white-space: nowrap;
3074
+ }
3075
+
3076
+ .vi-lib-group-toggle .vi-lib-count {
3077
+ flex: 0 0 auto;
3078
+ margin-left: auto;
3079
+ }
3080
+
3081
+ /* Collapsed group header: the representative poster sits left of the prompt. */
3082
+ .vi-lib-group-head {
3083
+ display: flex;
3084
+ align-items: flex-start;
3085
+ gap: 0.5rem;
3086
+ }
3087
+
3088
+ /* A compact representative poster (image or video still) for the collapsed group
3089
+ header. Small fixed square tile; the inner img/video fill it (cover) so a mix
3090
+ of aspect ratios still reads as a tidy tile. */
3091
+ .vi-lib-group-thumb {
3092
+ flex: 0 0 auto;
3093
+ width: 48px;
3094
+ height: 48px;
3095
+ border: 1px solid var(--vi-border);
3096
+ border-radius: 4px;
3097
+ background: #000;
3098
+ overflow: hidden;
3099
+ }
3100
+
3101
+ .vi-lib-group-thumb img,
3102
+ .vi-lib-group-thumb video {
3103
+ width: 100%;
3104
+ height: 100%;
3105
+ display: block;
3106
+ object-fit: cover;
3107
+ }
3108
+
3109
+ .vi-lib-group-prompt {
3110
+ margin: 0;
3111
+ flex: 1 1 auto;
3112
+ min-width: 0;
3113
+ font-size: 0.7rem;
3114
+ color: var(--vi-text-dim);
3115
+ line-height: 1.4;
3116
+ display: -webkit-box;
3117
+ -webkit-line-clamp: 2;
3118
+ -webkit-box-orient: vertical;
3119
+ overflow: hidden;
3120
+ }
3121
+
3122
+ /* Nested output list inside an expanded group — inset from the group frame. */
3123
+ .vi-lib-group-list {
3124
+ margin-top: 0.2rem;
3125
+ padding-left: 0.6rem;
3126
+ border-left: 1px solid var(--vi-border);
3127
+ }
3128
+
3129
+ /* ------------------------------------------------------------------------- */
3130
+ /* ComfyUI viewer station — the editable endpoint bar + the embedded frame. */
3131
+ /* Fills the workbench body height so the embedded ComfyUI has real room. Same */
3132
+ /* dark editorial-terminal tokens; reuses .vi-btn / .vi-error / .vi-knob-input.*/
3133
+ /* ------------------------------------------------------------------------- */
3134
+ .vi-comfy {
3135
+ display: flex;
3136
+ flex-direction: column;
3137
+ gap: 0.75rem;
3138
+ height: 100%;
3139
+ min-height: 0;
3140
+ }
3141
+
3142
+ .vi-comfy-head {
3143
+ flex: 0 0 auto;
3144
+ }
3145
+
3146
+ .vi-comfy-head h2 {
3147
+ margin: 0.3rem 0 0.4rem;
3148
+ font-weight: 500;
3149
+ font-size: 1.6rem;
3150
+ }
3151
+
3152
+ .vi-comfy-head .station-blurb {
3153
+ margin: 0;
3154
+ font-size: 0.9rem;
3155
+ }
3156
+
3157
+ .vi-comfy-head code {
3158
+ font-family: var(--vi-mono);
3159
+ font-size: 0.82em;
3160
+ color: var(--vi-text);
3161
+ }
3162
+
3163
+ /* Endpoint bar: field grows, actions sit at the end; wraps on narrow widths. */
3164
+ .vi-comfy-bar {
3165
+ flex: 0 0 auto;
3166
+ display: flex;
3167
+ flex-wrap: wrap;
3168
+ align-items: flex-end;
3169
+ gap: 0.6rem 0.75rem;
3170
+ }
3171
+
3172
+ .vi-comfy-field {
3173
+ flex: 1 1 20rem;
3174
+ min-width: 0;
3175
+ display: flex;
3176
+ flex-direction: column;
3177
+ gap: 0.25rem;
3178
+ }
3179
+
3180
+ /* Identities settings surface (t17 master-detail): the SettingsPanel renders
3181
+ .vi-comfy-bar as a flex COLUMN, but .vi-comfy-field is authored for a flex
3182
+ ROW bar — `flex: 1 1 20rem` there makes flex-basis the field's HEIGHT and lets
3183
+ each field GROW, so every DIRECT-CHILD field reserved ~its flex-basis of
3184
+ vertical space and the panel bled dead space between rows. In this column
3185
+ context a direct-child field must be its natural content height: no grow,
3186
+ basis auto. Scoped to DIRECT children only, so the fields nested inside the
3187
+ inner ROW groups (Frames/FPS/W/H, which are laid out side-by-side and rely on
3188
+ their flex-basis as WIDTH) keep their row behavior untouched. Purely
3189
+ spacing/structure; the fields' own styling is unchanged. */
3190
+ .vi-id-settings > .vi-comfy-field {
3191
+ flex: 0 0 auto;
3192
+ flex-basis: auto;
3193
+ }
3194
+ /* The direct-child horizontal groups (checkbox row, W/H/frames/fps row,
3195
+ front-ref row) are natural-height too — no phantom vertical basis. */
3196
+ .vi-id-settings > div {
3197
+ flex: 0 0 auto;
3198
+ }
3199
+ /* The "⚙ Settings & advanced" disclosure (active/session tabs): its children
3200
+ (summary, the SettingsPanel, the legacy-action buttons) flow as blocks — give
3201
+ them a small consistent gap so they read at the detail page's density instead
3202
+ of butting together or drifting apart. */
3203
+ .vi-id-settings-disclosure[open] {
3204
+ display: flex;
3205
+ flex-direction: column;
3206
+ gap: 0.4rem;
3207
+ }
3208
+ .vi-id-settings-disclosure > summary {
3209
+ margin-bottom: 0.2rem;
3210
+ }
3211
+
3212
+ .vi-comfy-label {
3213
+ font-size: 0.72rem;
3214
+ color: var(--vi-text-dim);
3215
+ text-transform: uppercase;
3216
+ letter-spacing: 0.05em;
3217
+ }
3218
+
3219
+ .vi-comfy-input {
3220
+ width: 100%;
3221
+ }
3222
+
3223
+ .vi-comfy-input[aria-invalid="true"] {
3224
+ border-color: #ff8a8a;
3225
+ }
3226
+
3227
+ .vi-comfy-bar-actions {
3228
+ display: flex;
3229
+ align-items: center;
3230
+ gap: 0.5rem;
3231
+ }
3232
+
3233
+ .vi-comfy-open-disabled {
3234
+ opacity: 0.45;
3235
+ cursor: not-allowed;
3236
+ text-decoration: none;
3237
+ }
3238
+
3239
+ /* Warning (mixed content / loopback) — amber, distinct from the red .vi-error. */
3240
+ .vi-comfy-warn {
3241
+ flex: 0 0 auto;
3242
+ margin: 0;
3243
+ padding: 0.55rem 0.7rem;
3244
+ border: 1px solid #6b5626;
3245
+ border-radius: 6px;
3246
+ background: rgba(255, 207, 138, 0.08);
3247
+ color: #ffcf8a;
3248
+ font-family: var(--vi-mono);
3249
+ font-size: 0.8rem;
3250
+ line-height: 1.45;
3251
+ }
3252
+
3253
+ .vi-comfy-warn code {
3254
+ color: #ffe3b8;
3255
+ }
3256
+
3257
+ /* The frame fills the remaining height of the workbench body. */
3258
+ .vi-comfy-frame {
3259
+ flex: 1 1 auto;
3260
+ min-height: 24rem;
3261
+ border: 1px solid var(--vi-border);
3262
+ border-radius: 8px;
3263
+ overflow: hidden;
3264
+ background: var(--vi-surface-raised);
3265
+ }
3266
+
3267
+ .vi-comfy-iframe {
3268
+ display: block;
3269
+ width: 100%;
3270
+ height: 100%;
3271
+ min-height: 24rem;
3272
+ border: 0;
3273
+ background: #fff; /* ComfyUI's own canvas paints over this; avoids a black flash */
3274
+ }
3275
+
3276
+ /* Empty state when no valid URL is loaded — mirrors the dropzone framing. */
3277
+ .vi-comfy-empty {
3278
+ flex: 1 1 auto;
3279
+ display: flex;
3280
+ flex-direction: column;
3281
+ align-items: center;
3282
+ justify-content: center;
3283
+ gap: 0.4rem;
3284
+ min-height: 22rem;
3285
+ padding: 1.5rem;
3286
+ border: 2px dashed var(--vi-border);
3287
+ border-radius: 8px;
3288
+ background: var(--vi-surface-raised);
3289
+ color: var(--vi-text-dim);
3290
+ font-family: var(--vi-mono);
3291
+ text-align: center;
3292
+ }
3293
+
3294
+ .vi-comfy-empty-title {
3295
+ margin: 0;
3296
+ color: var(--vi-text);
3297
+ font-size: 1rem;
3298
+ }
3299
+
3300
+ .vi-comfy-empty-sub {
3301
+ margin: 0;
3302
+ max-width: 32rem;
3303
+ font-size: 0.82rem;
3304
+ line-height: 1.5;
3305
+ }
3306
+
3307
+ .vi-comfy-empty code {
3308
+ color: var(--vi-text);
3309
+ }
3310
+
3311
+ .vi-comfy-hint {
3312
+ flex: 0 0 auto;
3313
+ margin: 0;
3314
+ color: var(--vi-text-dim);
3315
+ font-family: var(--vi-mono);
3316
+ font-size: 0.75rem;
3317
+ line-height: 1.45;
3318
+ }
3319
+
3320
+ .vi-comfy-hint code {
3321
+ color: var(--vi-text);
3322
+ }
3323
+
3324
+ /* ------------------------------------------------------------------------- */
3325
+ /* Studio PLANE — visual-kinship pass (Round 6). The single Studio plane */
3326
+ /* (generate controls + inline viewer/list) is restyled to read as part of the */
3327
+ /* same system as the scene/video tab: house section eyebrows (mirror */
3328
+ /* .station-phase), border-top section separators (mirror .vi-studio-prompt), */
3329
+ /* and a player-forward viewer frame (mirror .vi-studio-preview). Purely */
3330
+ /* additive — no existing rule changes; the plane's structure + preset shapes */
3331
+ /* are intact. */
3332
+ /* ------------------------------------------------------------------------- */
3333
+
3334
+ /* One flowing column, like .vi-section-region--work. */
3335
+ .vi-studio-plane {
3336
+ display: flex;
3337
+ flex-direction: column;
3338
+ gap: 1rem;
3339
+ min-width: 0;
3340
+ }
3341
+
3342
+ /* Section eyebrow — the mono / accent / uppercase idiom of .station-phase, used to
3343
+ title the Generate and Library regions so the plane reads as workbench sections. */
3344
+ .vi-studio-plane-eyebrow {
3345
+ display: block;
3346
+ font-family: var(--vi-mono);
3347
+ font-size: 0.75rem;
3348
+ color: var(--vi-accent);
3349
+ letter-spacing: 0.06em;
3350
+ text-transform: uppercase;
3351
+ margin: 0 0 0.6rem;
3352
+ }
3353
+
3354
+ .vi-studio-plane-eyebrow-sub {
3355
+ color: var(--vi-text-dim);
3356
+ letter-spacing: normal;
3357
+ text-transform: none;
3358
+ font-size: 0.72rem;
3359
+ }
3360
+
3361
+ /* Section separator — the border-top divider idiom of .vi-studio-prompt, replacing
3362
+ the plane's former ad-hoc inline separator between the controls and the viewer. */
3363
+ .vi-studio-plane-sep {
3364
+ border-top: 1px solid var(--vi-border);
3365
+ padding-top: 1.25rem;
3366
+ margin-top: 0.25rem;
3367
+ }
3368
+
3369
+ /* Player-forward viewer frame — mirrors the scene/video tab's .vi-studio-preview stage:
3370
+ a large centered black bordered panel the clip plays inside, so the studio player
3371
+ matches the main video preview. The empty state keeps the house .vi-comfy-empty frame
3372
+ (same 22rem footprint — no layout jump between empty and playing). */
3373
+ .vi-studio-viewer {
3374
+ display: flex;
3375
+ align-items: center;
3376
+ justify-content: center;
3377
+ min-height: 22rem;
3378
+ border: 1px solid var(--vi-border);
3379
+ border-radius: 8px;
3380
+ background: #000;
3381
+ padding: 0.75rem;
3382
+ overflow: hidden;
3383
+ }
3384
+
3385
+ .vi-studio-viewer video {
3386
+ max-width: 100%;
3387
+ max-height: 30rem;
3388
+ height: auto;
3389
+ display: block;
3390
+ border-radius: 0.35rem;
3391
+ }
3392
+
3393
+ /* ------------------------------------------------------------------------- */
3394
+ /* Studio WORKBENCH (Round 7) — the two-column [ tabbed sidebar | generator ] */
3395
+ /* layout that reads as a sibling of the Movie / video tab, plus the Movie */
3396
+ /* knob-grid idiom, the shared goal composer, and the Active-renders panel. */
3397
+ /* Additive — no existing rule is changed. On <62rem everything is one tablist */
3398
+ /* over a single panel, so nothing overflows a phone viewport. */
3399
+ /* ------------------------------------------------------------------------- */
3400
+
3401
+ /* Mobile-first: a single flowing column (the tablist + one panel). */
3402
+ .vi-studio-workbench {
3403
+ display: flex;
3404
+ flex-direction: column;
3405
+ gap: 1.1rem;
3406
+ min-width: 0;
3407
+ }
3408
+
3409
+ /* Round 9: the studio no longer carries its OWN sidebar — it folds into the arm shell's
3410
+ ONE left sidebar (Settings / Active / Library, via the sidebar registry). So the
3411
+ workbench is a SINGLE centre column (viewer on top → generator below), the Movie
3412
+ skeleton. The former two-column grid + the __sidebar/__generator rules below are kept,
3413
+ ARCHIVED (the plane no longer emits those elements), per the no-delete house rule. */
3414
+ @media (min-width: 62rem) {
3415
+ .vi-studio-workbench {
3416
+ max-width: 62rem;
3417
+ }
3418
+ }
3419
+
3420
+ .vi-studio-workbench__sidebar {
3421
+ min-width: 0;
3422
+ display: flex;
3423
+ flex-direction: column;
3424
+ gap: 0.75rem;
3425
+ }
3426
+
3427
+ .vi-studio-workbench__generator {
3428
+ min-width: 0;
3429
+ display: flex;
3430
+ flex-direction: column;
3431
+ gap: 1.1rem;
3432
+ }
3433
+
3434
+ .vi-studio-tabpanel {
3435
+ min-width: 0;
3436
+ }
3437
+
3438
+ .vi-studio-tablist {
3439
+ max-width: 100%;
3440
+ overflow-x: auto;
3441
+ -webkit-overflow-scrolling: touch;
3442
+ }
3443
+
3444
+ /* The Settings tab body — the studio surface portals its knob grid in here. */
3445
+ .vi-studio-settings-host {
3446
+ min-width: 0;
3447
+ }
3448
+
3449
+ /* The center COMPOSER (prompt + negative), where the knob grid used to sit. */
3450
+ .vi-studio-composer {
3451
+ min-width: 0;
3452
+ display: flex;
3453
+ flex-direction: column;
3454
+ gap: 0.6rem;
3455
+ }
3456
+
3457
+ /* Round 9: the shell sidebar's guarded "Settings" tab body — the studio surface portals
3458
+ its knob grid (template dropdown + project combobox + geometry/sampler/model) in here.
3459
+ In the narrow shell rail the knob grid collapses to two columns (was one) — compact
3460
+ numeric knobs (width/height/fps/seed/steps/cfg/...) pair up instead of each stacking
3461
+ full-width; `.vi-studio-knob-wide` cells still span both via `grid-column: 1 / -1`. */
3462
+ .vi-side-settings {
3463
+ min-width: 0;
3464
+ display: flex;
3465
+ flex-direction: column;
3466
+ gap: 0.75rem;
3467
+ padding: 0.25rem 0 0.5rem;
3468
+ }
3469
+ .vi-side-settings .vi-studio-knobs {
3470
+ grid-template-columns: repeat(2, minmax(0, 1fr));
3471
+ }
3472
+
3473
+ /* Round 9: studio's in-flight renders portal into the foot of the shared Active
3474
+ Processes tab — a light separator when it follows tracker rows. */
3475
+ .vi-proc-extra:not(:empty) {
3476
+ margin-top: 0.5rem;
3477
+ padding-top: 0.5rem;
3478
+ border-top: 1px solid var(--vi-border, rgba(128, 128, 128, 0.25));
3479
+ }
3480
+
3481
+ /* Round 9: required-input callout beside the studio composer (the tier cards that used
3482
+ to carry the "needs input" cue are retired). Accent-toned so it reads as a prompt. */
3483
+ .vi-studio-required-callout {
3484
+ margin: 0;
3485
+ padding: 0.5rem 0.7rem;
3486
+ border-radius: 0.4rem;
3487
+ font-size: 0.82rem;
3488
+ line-height: 1.4;
3489
+ color: var(--vi-accent, #d98a2b);
3490
+ background: var(--vi-accent-soft, rgba(217, 138, 43, 0.12));
3491
+ border: 1px solid var(--vi-accent, rgba(217, 138, 43, 0.5));
3492
+ }
3493
+
3494
+ /* ---- template / tier cards (the primary selector) ---- */
3495
+ .vi-studio-templates {
3496
+ min-width: 0;
3497
+ }
3498
+
3499
+ .vi-studio-card-grid {
3500
+ display: flex;
3501
+ flex-wrap: wrap;
3502
+ gap: 0.5rem;
3503
+ min-width: 0;
3504
+ }
3505
+
3506
+ .vi-knob-headline {
3507
+ margin: 0 0 0.5rem;
3508
+ font-family: var(--vi-mono);
3509
+ font-size: 0.85rem;
3510
+ color: var(--vi-text);
3511
+ }
3512
+
3513
+ .vi-knob-subhead {
3514
+ margin: 0.9rem 0 0.4rem;
3515
+ font-family: var(--vi-mono);
3516
+ font-size: 0.8rem;
3517
+ color: var(--vi-text);
3518
+ }
3519
+
3520
+ .vi-knob-headline-sub {
3521
+ color: var(--vi-text-dim);
3522
+ font-weight: 400;
3523
+ font-size: 0.75rem;
3524
+ }
3525
+
3526
+ /* ---- Movie-idiom knob GRID (inline in the generator column, NOT a side rail —
3527
+ so the shape-morphing heroes below stay in-flow + dominant) ---- */
3528
+ .vi-studio-knobs {
3529
+ display: grid;
3530
+ grid-template-columns: repeat(auto-fit, minmax(8.5rem, 1fr));
3531
+ gap: 0.85rem 1rem;
3532
+ align-items: start;
3533
+ min-width: 0;
3534
+ }
3535
+
3536
+ /* Full-width knob cells (capability, model pin, and the prompt/negative composer). */
3537
+ .vi-studio-knob-wide {
3538
+ grid-column: 1 / -1;
3539
+ min-width: 0;
3540
+ }
3541
+
3542
+ .vi-studio-shape {
3543
+ min-width: 0;
3544
+ }
3545
+
3546
+ /* ---- shared goal composer ---- */
3547
+ .vi-goal-composer {
3548
+ min-width: 0;
3549
+ }
3550
+
3551
+ .vi-goal-composer-field {
3552
+ position: relative;
3553
+ min-width: 0;
3554
+ }
3555
+
3556
+ .vi-goal-composer-input {
3557
+ width: 100%;
3558
+ box-sizing: border-box;
3559
+ resize: vertical;
3560
+ }
3561
+
3562
+ .vi-goal-composer-toolbar {
3563
+ display: flex;
3564
+ flex-wrap: wrap;
3565
+ gap: 0.35rem;
3566
+ margin-top: 0.35rem;
3567
+ }
3568
+
3569
+ .vi-goal-composer-chips {
3570
+ display: flex;
3571
+ flex-wrap: wrap;
3572
+ gap: 0.4rem;
3573
+ margin-top: 0.4rem;
3574
+ }
3575
+
3576
+ .vi-goal-composer-chip {
3577
+ display: inline-flex;
3578
+ align-items: center;
3579
+ gap: 0.3rem;
3580
+ padding: 0.15rem 0.35rem;
3581
+ border: 1px solid var(--vi-border);
3582
+ border-radius: 4px;
3583
+ background: var(--vi-surface-raised);
3584
+ font-size: 0.72rem;
3585
+ color: var(--vi-text-dim);
3586
+ max-width: 100%;
3587
+ }
3588
+
3589
+ .vi-goal-composer-chip-thumb {
3590
+ width: 1.6rem;
3591
+ height: 1.6rem;
3592
+ object-fit: cover;
3593
+ border-radius: 3px;
3594
+ background: #000;
3595
+ }
3596
+
3597
+ .vi-goal-composer-chip-label {
3598
+ overflow: hidden;
3599
+ text-overflow: ellipsis;
3600
+ white-space: nowrap;
3601
+ max-width: 8rem;
3602
+ }
3603
+
3604
+ .vi-goal-composer-chip-x {
3605
+ border: 0;
3606
+ background: none;
3607
+ color: var(--vi-text-dim);
3608
+ cursor: pointer;
3609
+ padding: 0 0.15rem;
3610
+ font-size: 0.8rem;
3611
+ line-height: 1;
3612
+ }
3613
+
3614
+ .vi-goal-composer-chip-x:hover {
3615
+ color: var(--vi-text);
3616
+ }
3617
+
3618
+ /* ---- Active-renders panel ---- */
3619
+ .vi-studio-active {
3620
+ min-width: 0;
3621
+ }
3622
+
3623
+ .vi-studio-active-list {
3624
+ list-style: none;
3625
+ margin: 0.5rem 0 0;
3626
+ padding: 0;
3627
+ display: flex;
3628
+ flex-direction: column;
3629
+ gap: 0.5rem;
3630
+ }
3631
+
3632
+ .vi-studio-active-row {
3633
+ display: flex;
3634
+ flex-wrap: wrap;
3635
+ align-items: center;
3636
+ gap: 0.4rem 0.6rem;
3637
+ padding: 0.5rem 0.6rem;
3638
+ border: 1px solid var(--vi-border);
3639
+ border-radius: 6px;
3640
+ background: var(--vi-surface-raised);
3641
+ min-width: 0;
3642
+ }
3643
+
3644
+ .vi-studio-active-head {
3645
+ display: flex;
3646
+ align-items: center;
3647
+ gap: 0.4rem;
3648
+ min-width: 0;
3649
+ }
3650
+
3651
+ .vi-studio-active-id {
3652
+ font-family: var(--vi-mono);
3653
+ font-size: 0.8rem;
3654
+ color: var(--vi-text);
3655
+ }
3656
+
3657
+ .vi-studio-active-meta {
3658
+ display: flex;
3659
+ gap: 0.4rem;
3660
+ margin-right: auto;
3661
+ font-family: var(--vi-mono);
3662
+ font-size: 0.72rem;
3663
+ color: var(--vi-text-dim);
3664
+ font-variant-numeric: tabular-nums;
3665
+ }
3666
+
3667
+ /* ── live PROGRESS BAR + latest event line (console-wide Active Processes) ──
3668
+ Both take a full row of their own (the active row is flex-wrap) and sit BEFORE
3669
+ the timeline panel. The bar renders only when the server sent a number, so an
3670
+ empty track never reads as "0%". */
3671
+ .vi-job-progress {
3672
+ order: 2;
3673
+ flex-basis: 100%;
3674
+ width: 100%;
3675
+ min-width: 0;
3676
+ display: flex;
3677
+ align-items: center;
3678
+ gap: 0.5rem;
3679
+ }
3680
+ .vi-job-progress-track {
3681
+ flex: 1 1 auto;
3682
+ height: 4px;
3683
+ border-radius: 999px;
3684
+ background: var(--vi-border);
3685
+ overflow: hidden;
3686
+ }
3687
+ .vi-job-progress-fill {
3688
+ height: 100%;
3689
+ border-radius: 999px;
3690
+ background: var(--vi-accent, #d08b3c);
3691
+ transition: width 0.4s linear;
3692
+ }
3693
+ .vi-job-progress-label {
3694
+ flex: 0 0 auto;
3695
+ font-family: var(--vi-mono);
3696
+ font-size: 0.7rem;
3697
+ color: var(--vi-text-dim);
3698
+ font-variant-numeric: tabular-nums;
3699
+ }
3700
+ .vi-job-latest {
3701
+ order: 2;
3702
+ flex-basis: 100%;
3703
+ width: 100%;
3704
+ min-width: 0;
3705
+ margin: 0.15rem 0 0;
3706
+ font-family: var(--vi-mono);
3707
+ font-size: 0.72rem;
3708
+ color: var(--vi-text-dim);
3709
+ word-break: break-word;
3710
+ }
3711
+ .vi-job-filter {
3712
+ display: block;
3713
+ margin: 0 0 0.5rem;
3714
+ }
3715
+
3716
+ /* ── the per-process STAGE TIMELINE expandable (Active Processes + Library) ── */
3717
+ /* Keep the Cancel button on the header line and let the timeline panel take a full
3718
+ row of its own (the row is flex-wrap). */
3719
+ .vi-studio-active-row > .vi-btn {
3720
+ order: 1;
3721
+ }
3722
+ .vi-timeline-panel {
3723
+ order: 2;
3724
+ flex-basis: 100%;
3725
+ width: 100%;
3726
+ min-width: 0;
3727
+ margin-top: 0.35rem;
3728
+ padding-top: 0.45rem;
3729
+ border-top: 1px solid var(--vi-border);
3730
+ }
3731
+
3732
+ .vi-timeline-toggle {
3733
+ background: none;
3734
+ border: none;
3735
+ cursor: pointer;
3736
+ color: var(--vi-text-dim);
3737
+ padding: 0 0.15rem;
3738
+ font-size: 0.8rem;
3739
+ line-height: 1;
3740
+ }
3741
+ .vi-timeline-toggle:hover {
3742
+ color: var(--vi-text);
3743
+ }
3744
+ .vi-timeline-caret {
3745
+ display: inline-block;
3746
+ width: 0.7rem;
3747
+ }
3748
+
3749
+ .vi-timeline-stall-badge {
3750
+ font-size: 0.62rem;
3751
+ text-transform: uppercase;
3752
+ letter-spacing: 0.05em;
3753
+ color: #ffcf8a;
3754
+ border: 1px solid #ffcf8a;
3755
+ border-radius: 999px;
3756
+ padding: 0.02rem 0.4rem;
3757
+ }
3758
+
3759
+ .vi-timeline {
3760
+ display: flex;
3761
+ flex-direction: column;
3762
+ gap: 0.5rem;
3763
+ font-size: 0.8rem;
3764
+ }
3765
+ .vi-timeline-empty {
3766
+ font-size: 0.78rem;
3767
+ color: var(--vi-text-dim);
3768
+ margin: 0;
3769
+ }
3770
+
3771
+ /* FAILURE — the exact where-it-broke, loud + first. */
3772
+ .vi-timeline-failure {
3773
+ padding: 0.4rem 0.55rem;
3774
+ border-radius: 0.4rem;
3775
+ border: 1px solid rgba(255, 138, 138, 0.5);
3776
+ background: rgba(255, 90, 90, 0.1);
3777
+ color: #ff8a8a;
3778
+ line-height: 1.4;
3779
+ }
3780
+ .vi-timeline-retry {
3781
+ color: var(--vi-text-dim);
3782
+ font-weight: 400;
3783
+ }
3784
+ .vi-timeline-failmsg {
3785
+ color: var(--vi-text);
3786
+ margin-top: 0.2rem;
3787
+ word-break: break-word;
3788
+ font-family: var(--vi-mono);
3789
+ font-size: 0.74rem;
3790
+ }
3791
+
3792
+ /* CURRENT ACTIVITY — the live "frame 12/48" / "awaiting capacity …" line. */
3793
+ .vi-timeline-current {
3794
+ display: flex;
3795
+ align-items: baseline;
3796
+ flex-wrap: wrap;
3797
+ gap: 0.35rem 0.5rem;
3798
+ padding: 0.3rem 0.5rem;
3799
+ border-radius: 0.4rem;
3800
+ background: rgba(138, 180, 255, 0.1);
3801
+ }
3802
+ .vi-timeline-current-stalled {
3803
+ background: rgba(255, 207, 138, 0.12);
3804
+ }
3805
+ .vi-timeline-current-label {
3806
+ font-size: 0.6rem;
3807
+ text-transform: uppercase;
3808
+ letter-spacing: 0.06em;
3809
+ color: var(--vi-accent);
3810
+ }
3811
+ .vi-timeline-current-stalled .vi-timeline-current-label {
3812
+ color: #ffcf8a;
3813
+ }
3814
+ .vi-timeline-current-text {
3815
+ color: var(--vi-text);
3816
+ font-weight: 500;
3817
+ }
3818
+ .vi-timeline-stall {
3819
+ color: #ffcf8a;
3820
+ font-size: 0.72rem;
3821
+ }
3822
+ .vi-timeline-move {
3823
+ color: var(--vi-text-dim);
3824
+ font-size: 0.72rem;
3825
+ }
3826
+
3827
+ /* THE TIMELINE list — one row per stage the render moved through. */
3828
+ .vi-timeline-list {
3829
+ list-style: none;
3830
+ margin: 0;
3831
+ padding: 0;
3832
+ display: flex;
3833
+ flex-direction: column;
3834
+ gap: 0.3rem;
3835
+ }
3836
+ .vi-timeline-item {
3837
+ display: flex;
3838
+ align-items: baseline;
3839
+ gap: 0.5rem;
3840
+ min-width: 0;
3841
+ }
3842
+ .vi-timeline-mark {
3843
+ flex: 0 0 auto;
3844
+ width: 1rem;
3845
+ text-align: center;
3846
+ font-size: 0.7rem;
3847
+ line-height: 1.3;
3848
+ }
3849
+ .vi-timeline-mark-done {
3850
+ color: var(--vi-accent);
3851
+ opacity: 0.7;
3852
+ }
3853
+ .vi-timeline-mark-current {
3854
+ color: var(--vi-accent);
3855
+ animation: vi-timeline-pulse 1.4s ease-in-out infinite;
3856
+ }
3857
+ .vi-timeline-mark-stalled {
3858
+ color: #ffcf8a;
3859
+ animation: none;
3860
+ }
3861
+ .vi-timeline-mark-fail {
3862
+ color: #ff8a8a;
3863
+ }
3864
+ @keyframes vi-timeline-pulse {
3865
+ 0%,
3866
+ 100% {
3867
+ opacity: 1;
3868
+ }
3869
+ 50% {
3870
+ opacity: 0.35;
3871
+ }
3872
+ }
3873
+ .vi-timeline-body {
3874
+ display: flex;
3875
+ flex-direction: column;
3876
+ min-width: 0;
3877
+ flex: 1 1 auto;
3878
+ }
3879
+ .vi-timeline-stage {
3880
+ color: var(--vi-text);
3881
+ text-transform: capitalize;
3882
+ }
3883
+ .vi-timeline-count {
3884
+ color: var(--vi-text-dim);
3885
+ font-variant-numeric: tabular-nums;
3886
+ }
3887
+ .vi-timeline-detail {
3888
+ color: var(--vi-text-dim);
3889
+ font-size: 0.74rem;
3890
+ word-break: break-word;
3891
+ font-family: var(--vi-mono);
3892
+ }
3893
+ .vi-timeline-time {
3894
+ flex: 0 0 auto;
3895
+ color: var(--vi-text-dim);
3896
+ font-size: 0.68rem;
3897
+ font-variant-numeric: tabular-nums;
3898
+ white-space: nowrap;
3899
+ }
3900
+
3901
+ /* Placement line (Active Processes) — WHERE a job physically runs. */
3902
+ .vi-placement {
3903
+ font-family: var(--vi-mono);
3904
+ font-size: 0.72rem;
3905
+ color: var(--vi-text-dim);
3906
+ }
3907
+ .vi-placement-external {
3908
+ color: var(--vi-accent, #d08b3c);
3909
+ font-weight: 600;
3910
+ }
3911
+ /* awaiting_capacity — the reservation HOLD phase chip (amber "waiting"). */
3912
+ .vi-status-awaiting_capacity {
3913
+ background: rgba(208, 139, 60, 0.16);
3914
+ color: var(--vi-accent, #d08b3c);
3915
+ border: 1px solid rgba(208, 139, 60, 0.4);
3916
+ }
3917
+
3918
+ .vi-studio-viewer-wrap {
3919
+ min-width: 0;
3920
+ }
3921
+
3922
+ /* ── Studio item cards (Studio redesign, slice 2b) ──────────────────────────────
3923
+ The strip of first-class StudioItems that sits above the generate surface. Each
3924
+ card shows the item's working preview + an immutable provenance line + the inline
3925
+ ops menu, and expands a drawer hosting the matching op Core. Tokens mirror the
3926
+ existing .vi-studio-src-row / .vi-station-components surfaces. */
3927
+ .vi-studio-item-strip {
3928
+ display: flex;
3929
+ flex-direction: column;
3930
+ gap: 0.6rem;
3931
+ margin: 0.8rem 0;
3932
+ }
3933
+
3934
+ .vi-studio-item {
3935
+ border: 1px solid var(--vi-border);
3936
+ border-radius: 8px;
3937
+ background: var(--vi-surface-raised);
3938
+ overflow: hidden;
3939
+ }
3940
+
3941
+ .vi-studio-item-body {
3942
+ display: flex;
3943
+ align-items: flex-start;
3944
+ gap: 0.8rem;
3945
+ padding: 0.6rem;
3946
+ }
3947
+
3948
+ .vi-studio-item-media {
3949
+ flex: 0 0 auto;
3950
+ width: 140px;
3951
+ max-width: 40%;
3952
+ border: 1px solid var(--vi-border);
3953
+ border-radius: 6px;
3954
+ background: #000;
3955
+ overflow: hidden;
3956
+ }
3957
+
3958
+ .vi-studio-item-img {
3959
+ display: block;
3960
+ width: 100%;
3961
+ height: 84px;
3962
+ object-fit: cover;
3963
+ }
3964
+
3965
+ .vi-studio-item-audio {
3966
+ display: block;
3967
+ width: 100%;
3968
+ padding: 0.3rem;
3969
+ }
3970
+
3971
+ .vi-studio-item-meta {
3972
+ flex: 1 1 auto;
3973
+ min-width: 0;
3974
+ display: flex;
3975
+ flex-direction: column;
3976
+ gap: 0.5rem;
3977
+ }
3978
+
3979
+ /* The immutable "where it came from" line — read-only, muted, never editable. */
3980
+ .vi-studio-item-prov {
3981
+ display: flex;
3982
+ flex-wrap: wrap;
3983
+ align-items: center;
3984
+ gap: 0.4rem;
3985
+ font-size: 0.82rem;
3986
+ color: var(--vi-text-dim);
3987
+ }
3988
+
3989
+ .vi-studio-item-prov-origin {
3990
+ font-weight: 600;
3991
+ color: var(--vi-text);
3992
+ }
3993
+
3994
+ .vi-studio-item-prov-kind {
3995
+ font-family: var(--vi-mono);
3996
+ font-size: 0.72rem;
3997
+ padding: 0.05rem 0.35rem;
3998
+ border: 1px solid var(--vi-border);
3999
+ border-radius: 999px;
4000
+ }
4001
+
4002
+ .vi-studio-item-prov-parent {
4003
+ font-family: var(--vi-mono);
4004
+ font-size: 0.72rem;
4005
+ max-width: 100%;
4006
+ overflow: hidden;
4007
+ text-overflow: ellipsis;
4008
+ white-space: nowrap;
4009
+ }
4010
+
4011
+ .vi-studio-item-ops {
4012
+ display: flex;
4013
+ flex-wrap: wrap;
4014
+ gap: 0.4rem;
4015
+ }
4016
+
4017
+ /* The inline op drawer — the mounted Core lives here; it closes when the op resolves. */
4018
+ .vi-studio-item-drawer {
4019
+ border-top: 1px solid var(--vi-border);
4020
+ background: var(--vi-surface);
4021
+ padding: 0.6rem;
4022
+ }
4023
+
4024
+ .vi-studio-item-drawer-head {
4025
+ display: flex;
4026
+ align-items: center;
4027
+ justify-content: space-between;
4028
+ gap: 0.6rem;
4029
+ margin-bottom: 0.5rem;
4030
+ }
4031
+
4032
+ .vi-studio-item-drawer-title {
4033
+ font-weight: 600;
4034
+ color: var(--vi-text);
4035
+ }
4036
+
4037
+ /* ── Identity 360° turntable — drag-to-rotate stage + scrub slider ───────────── */
4038
+ .vi-turntable-stage {
4039
+ cursor: grab;
4040
+ }
4041
+ .vi-turntable-stage:active {
4042
+ cursor: grabbing;
4043
+ }
4044
+ .vi-turntable-stage:focus-visible {
4045
+ outline: 2px solid var(--vi-accent);
4046
+ outline-offset: 2px;
4047
+ }
4048
+ .vi-turntable-scrub {
4049
+ accent-color: var(--vi-accent);
4050
+ }
4051
+
4052
+ /* ------------------------------------------------------------------------- */
4053
+ /* t52 — the sidebar's Console panel: a same-origin iframe embed of the hugpy */
4054
+ /* console that expands OVER the screen so the operator never has to keep a */
4055
+ /* second tab open. .vi-side-toolbar hosts the collapsed toggle (always */
4056
+ /* visible above the sidebar tabs); .vi-console-panel/-scrim are portaled to */
4057
+ /* <body> (see ConsolePanel.tsx) so they escape the sidebar's own box. */
4058
+ /* z-index sits above the mobile off-canvas drawer (40) and its scrim (35) */
4059
+ /* so the console always layers on top when both are open. */
4060
+ /* ------------------------------------------------------------------------- */
4061
+ .vi-side-toolbar {
4062
+ display: flex;
4063
+ /* WRAP is load-bearing since ComfyUI joined the row (2026-08-04). The sidebar
4064
+ column is 18rem with 1rem padding a side (~16rem of content); three toggles
4065
+ at `flex: 1 1 0` with the default `min-width: auto` cannot shrink below their
4066
+ label width, so without wrapping the row pushes WIDER than the sidebar
4067
+ instead of reflowing. Share is also conditional (SharePanel renders null for
4068
+ a non-operator session), so this row legitimately holds 2 OR 3 buttons —
4069
+ never assume a fixed count here. */
4070
+ flex-wrap: wrap;
4071
+ gap: 0.5rem;
4072
+ margin-bottom: 0.9rem;
4073
+ padding-bottom: 0.9rem;
4074
+ border-bottom: 1px solid var(--vi-border);
4075
+ }
4076
+
4077
+ /* Console + Share + ComfyUI + Active share the toolbar row (k9, then 2026-08-04,
4078
+ then 2026-08-06). Each grows to fill; when Share is hidden (a non-operator
4079
+ session) the rest fill the row on their own. `min-width: 0` lets them actually
4080
+ shrink — the same override .vi-side-tabs .vi-gen-mode-btn already needs — so a
4081
+ long label truncates within the column rather than widening it. The row's
4082
+ `flex-wrap` above is what keeps 4 toggles inside an 18rem sidebar. */
4083
+ .vi-console-toggle,
4084
+ .vi-share-toggle,
4085
+ .vi-comfy-toggle,
4086
+ .vi-active-toggle {
4087
+ flex: 1 1 auto;
4088
+ min-width: 0;
4089
+ width: auto;
4090
+ justify-content: center;
4091
+ display: inline-flex;
4092
+ align-items: center;
4093
+ gap: 0.4rem;
4094
+ letter-spacing: 0.01em;
4095
+ }
4096
+
4097
+ .vi-console-scrim {
4098
+ position: fixed;
4099
+ inset: 0;
4100
+ z-index: 90;
4101
+ border: 0;
4102
+ background: rgba(0, 0, 0, 0.55);
4103
+ cursor: pointer;
4104
+ }
4105
+
4106
+ .vi-console-panel {
4107
+ position: fixed;
4108
+ top: 0;
4109
+ right: 0;
4110
+ height: 100dvh;
4111
+ z-index: 100;
4112
+ display: flex;
4113
+ flex-direction: column;
4114
+ background: var(--vi-surface-raised);
4115
+ border-left: 1px solid var(--vi-border);
4116
+ box-shadow: 0 0 3rem rgba(0, 0, 0, 0.6);
4117
+ transition: width 160ms ease;
4118
+ }
4119
+
4120
+ /* "Half": a large share of the viewport (~70%, per spec) — the underlying
4121
+ station stays mounted and visible in the remaining strip, so switching
4122
+ between the console and the station you were on is a glance, not a tab
4123
+ switch. Capped so it doesn't sprawl absurdly wide on an ultrawide monitor,
4124
+ floored so it stays usable on a narrow one. */
4125
+ .vi-console-panel-half {
4126
+ width: min(70vw, 84rem);
4127
+ min-width: min(22rem, 100vw);
4128
+ }
4129
+
4130
+ /* "Full": edge-to-edge, the whole viewport. */
4131
+ .vi-console-panel-full {
4132
+ width: 100vw;
4133
+ }
4134
+
4135
+ .vi-console-panel-bar {
4136
+ display: flex;
4137
+ align-items: center;
4138
+ justify-content: space-between;
4139
+ gap: 0.6rem;
4140
+ flex: 0 0 auto;
4141
+ padding: 0.6rem 0.8rem;
4142
+ border-bottom: 1px solid var(--vi-border);
4143
+ font-family: var(--vi-mono);
4144
+ /* The bar now carries a TAB STRIP where it used to carry a single title, and
4145
+ on the ComfyUI tab an endpoint form as well — so it reflows on a narrow
4146
+ drawer rather than squeezing either to nothing. (.vi-comfy-panel already
4147
+ asked for this; it belongs on the shared bar now that the strip is always
4148
+ there.) */
4149
+ flex-wrap: wrap;
4150
+ row-gap: 0.5rem;
4151
+ }
4152
+
4153
+ /* The in-window tab strip (Console | ComfyUI | Active) — 2026-08-06, the operator
4154
+ ask that collapsed three drawers into one window. It reuses the segmented
4155
+ `.vi-gen-mode` control the sidebar's own tab strip uses, so the two read alike;
4156
+ only the block margin that control carries in a page column has to go. */
4157
+ .vi-drawer-tabs {
4158
+ flex: 0 0 auto;
4159
+ margin: 0;
4160
+ }
4161
+
4162
+ .vi-console-panel-title {
4163
+ font-family: var(--vi-serif);
4164
+ font-weight: 500;
4165
+ color: var(--vi-text);
4166
+ }
4167
+
4168
+ .vi-console-panel-actions {
4169
+ display: flex;
4170
+ align-items: center;
4171
+ gap: 0.4rem;
4172
+ }
4173
+
4174
+ /* One tab's body. It replaces the panel's direct children as the flex context
4175
+ the bodies were written against (iframe `flex: 1 1 auto`, Comfy's `flex: 0 0
4176
+ auto` message strips, `.vi-active-panel-body`'s own scroll region), so the
4177
+ column and the min-height:0 have to be repeated here or a long body would
4178
+ refuse to shrink. `display:flex` would otherwise beat the `hidden` attribute's
4179
+ UA `display:none`, hence the explicit [hidden] rule — that hiding is what lets
4180
+ the Console/ComfyUI iframes stay alive across a tab switch. */
4181
+ .vi-drawer-tabpanel {
4182
+ flex: 1 1 auto;
4183
+ min-height: 0;
4184
+ display: flex;
4185
+ flex-direction: column;
4186
+ }
4187
+
4188
+ .vi-drawer-tabpanel[hidden] {
4189
+ display: none;
4190
+ }
4191
+
4192
+ .vi-console-iframe {
4193
+ display: block;
4194
+ flex: 1 1 auto;
4195
+ width: 100%;
4196
+ height: 100%;
4197
+ border: 0;
4198
+ background: #fff; /* the console's own theme paints over this; avoids a black flash while it loads */
4199
+ }
4200
+
4201
+ @media (max-width: 62rem) {
4202
+ /* Below the desktop breakpoint there's no spare viewport to split — the
4203
+ console panel always fills the screen, matching the mobile drawer's own
4204
+ off-canvas-becomes-full posture. The full/half toggle stays available
4205
+ (harmless no-op at this width: both classes now resolve to 100vw). */
4206
+ .vi-console-panel-half,
4207
+ .vi-console-panel-full {
4208
+ width: 100vw;
4209
+ }
4210
+ }
4211
+
4212
+ /* ------------------------------------------------------------------------- */
4213
+ /* k9 — the Share panel: a small centered modal (portaled to <body>) where an */
4214
+ /* operator mints/lists/revokes video share links. Distinct from the Console */
4215
+ /* drawer (which fills the screen) — this is a focused dialog. */
4216
+ /* ------------------------------------------------------------------------- */
4217
+ .vi-share-scrim {
4218
+ position: fixed;
4219
+ inset: 0;
4220
+ z-index: 90;
4221
+ border: 0;
4222
+ background: rgba(0, 0, 0, 0.55);
4223
+ cursor: pointer;
4224
+ }
4225
+
4226
+ .vi-share-panel {
4227
+ position: fixed;
4228
+ top: 50%;
4229
+ left: 50%;
4230
+ transform: translate(-50%, -50%);
4231
+ width: min(34rem, 92vw);
4232
+ max-height: 88dvh;
4233
+ overflow: auto;
4234
+ z-index: 100;
4235
+ display: flex;
4236
+ flex-direction: column;
4237
+ background: var(--vi-surface-raised);
4238
+ border: 1px solid var(--vi-border);
4239
+ border-radius: 0.5rem;
4240
+ box-shadow: 0 0 3rem rgba(0, 0, 0, 0.6);
4241
+ }
4242
+
4243
+ .vi-share-bar {
4244
+ display: flex;
4245
+ align-items: center;
4246
+ justify-content: space-between;
4247
+ gap: 0.6rem;
4248
+ padding: 0.8rem 1rem;
4249
+ border-bottom: 1px solid var(--vi-border);
4250
+ }
4251
+
4252
+ .vi-share-title {
4253
+ font-family: var(--vi-serif);
4254
+ font-size: 1.05rem;
4255
+ color: var(--vi-text);
4256
+ }
4257
+
4258
+ .vi-share-body {
4259
+ padding: 1rem;
4260
+ display: flex;
4261
+ flex-direction: column;
4262
+ gap: 0.9rem;
4263
+ }
4264
+
4265
+ .vi-share-hint {
4266
+ margin: 0;
4267
+ color: var(--vi-text-dim);
4268
+ font-size: 0.85rem;
4269
+ line-height: 1.45;
4270
+ }
4271
+
4272
+ .vi-share-form {
4273
+ display: flex;
4274
+ flex-wrap: wrap;
4275
+ align-items: flex-end;
4276
+ gap: 0.6rem;
4277
+ }
4278
+
4279
+ .vi-share-field {
4280
+ display: flex;
4281
+ flex-direction: column;
4282
+ gap: 0.25rem;
4283
+ flex: 1 1 10rem;
4284
+ font-size: 0.78rem;
4285
+ color: var(--vi-text-dim);
4286
+ }
4287
+
4288
+ .vi-share-field input,
4289
+ .vi-share-field select {
4290
+ background: var(--vi-surface);
4291
+ color: var(--vi-text);
4292
+ border: 1px solid var(--vi-border);
4293
+ border-radius: 0.35rem;
4294
+ padding: 0.4rem 0.5rem;
4295
+ font-family: var(--vi-mono);
4296
+ font-size: 0.85rem;
4297
+ }
4298
+
4299
+ .vi-share-error {
4300
+ margin: 0;
4301
+ color: #ff9b9b;
4302
+ font-size: 0.85rem;
4303
+ }
4304
+
4305
+ .vi-share-minted {
4306
+ border: 1px solid var(--vi-accent);
4307
+ border-radius: 0.4rem;
4308
+ padding: 0.7rem;
4309
+ background: rgba(138, 180, 255, 0.06);
4310
+ }
4311
+
4312
+ .vi-share-minted-note {
4313
+ margin: 0 0 0.5rem;
4314
+ color: var(--vi-accent);
4315
+ font-size: 0.8rem;
4316
+ }
4317
+
4318
+ .vi-share-minted-row {
4319
+ display: flex;
4320
+ gap: 0.5rem;
4321
+ }
4322
+
4323
+ .vi-share-link {
4324
+ flex: 1 1 auto;
4325
+ min-width: 0;
4326
+ background: var(--vi-surface);
4327
+ color: var(--vi-text);
4328
+ border: 1px solid var(--vi-border);
4329
+ border-radius: 0.35rem;
4330
+ padding: 0.4rem 0.5rem;
4331
+ font-family: var(--vi-mono);
4332
+ font-size: 0.8rem;
4333
+ }
4334
+
4335
+ .vi-share-list-title {
4336
+ margin: 0 0 0.4rem;
4337
+ font-size: 0.8rem;
4338
+ color: var(--vi-text-dim);
4339
+ text-transform: uppercase;
4340
+ letter-spacing: 0.06em;
4341
+ }
4342
+
4343
+ .vi-share-list ul {
4344
+ list-style: none;
4345
+ margin: 0;
4346
+ padding: 0;
4347
+ display: flex;
4348
+ flex-direction: column;
4349
+ gap: 0.4rem;
4350
+ }
4351
+
4352
+ .vi-share-list-item {
4353
+ display: flex;
4354
+ align-items: center;
4355
+ gap: 0.5rem;
4356
+ font-size: 0.82rem;
4357
+ }
4358
+
4359
+ .vi-share-item-label {
4360
+ flex: 1 1 auto;
4361
+ min-width: 0;
4362
+ overflow: hidden;
4363
+ text-overflow: ellipsis;
4364
+ white-space: nowrap;
4365
+ color: var(--vi-text);
4366
+ }
4367
+
4368
+ .vi-share-item-prefix {
4369
+ font-family: var(--vi-mono);
4370
+ color: var(--vi-text-dim);
4371
+ font-size: 0.75rem;
4372
+ }
4373
+
4374
+ .vi-share-item-exp {
4375
+ color: var(--vi-text-dim);
4376
+ font-size: 0.75rem;
4377
+ }
4378
+
4379
+ .vi-share-item-exp-dead {
4380
+ color: #ff9b9b;
4381
+ }
4382
+
4383
+ /* ── STUDIO-ASSIST LIVE LOG (operator directive, 2026-07-31) ──────────────────
4384
+ The live generation log under the studio generator: one row per prompt-generate
4385
+ attempt, the raw model reply expandable. Same dark editorial-terminal tokens as
4386
+ the rest of the arm; the outcome badge is the scan target. */
4387
+ .vi-assistlog {
4388
+ margin-top: 14px;
4389
+ border: 1px solid var(--vi-border);
4390
+ border-radius: 8px;
4391
+ background: var(--vi-surface-raised);
4392
+ font-family: var(--vi-mono);
4393
+ font-size: 12px;
4394
+ color: var(--vi-text);
4395
+ }
4396
+ .vi-assistlog-toolbar {
4397
+ display: flex;
4398
+ align-items: center;
4399
+ gap: 8px;
4400
+ padding: 7px 10px;
4401
+ border-bottom: 1px solid var(--vi-border);
4402
+ }
4403
+ .vi-assistlog-collapse {
4404
+ background: none;
4405
+ border: none;
4406
+ color: var(--vi-text);
4407
+ font-family: var(--vi-mono);
4408
+ font-size: 12px;
4409
+ letter-spacing: 0.04em;
4410
+ text-transform: uppercase;
4411
+ cursor: pointer;
4412
+ padding: 0;
4413
+ }
4414
+ .vi-assistlog-spacer { flex: 1 1 auto; }
4415
+ .vi-assistlog-dot {
4416
+ width: 8px;
4417
+ height: 8px;
4418
+ border-radius: 50%;
4419
+ background: var(--vi-text-dim);
4420
+ }
4421
+ .vi-assistlog-dot--live { background: #6ee7a8; }
4422
+ .vi-assistlog-dot--connecting { background: #ffd479; }
4423
+ .vi-assistlog-dot--disconnected { background: #ff9b9b; }
4424
+ .vi-assistlog-conn { color: var(--vi-text-dim); }
4425
+ .vi-assistlog-filter {
4426
+ background: var(--vi-surface);
4427
+ border: 1px solid var(--vi-border);
4428
+ border-radius: 5px;
4429
+ color: var(--vi-text);
4430
+ font-family: var(--vi-mono);
4431
+ font-size: 12px;
4432
+ padding: 3px 7px;
4433
+ min-width: 180px;
4434
+ }
4435
+ .vi-assistlog-reconnect,
4436
+ .vi-assistlog-count {
4437
+ color: var(--vi-text-dim);
4438
+ background: none;
4439
+ border: none;
4440
+ font-family: var(--vi-mono);
4441
+ font-size: 12px;
4442
+ }
4443
+ .vi-assistlog-reconnect {
4444
+ cursor: pointer;
4445
+ text-decoration: underline;
4446
+ }
4447
+ .vi-assistlog-error,
4448
+ .vi-assistlog-empty {
4449
+ padding: 10px;
4450
+ color: var(--vi-text-dim);
4451
+ }
4452
+ .vi-assistlog-error { color: #ff9b9b; }
4453
+ .vi-assistlog-list {
4454
+ max-height: 340px;
4455
+ overflow-y: auto;
4456
+ }
4457
+ .vi-assistlog-row {
4458
+ border-bottom: 1px solid var(--vi-border);
4459
+ }
4460
+ .vi-assistlog-row:last-child { border-bottom: none; }
4461
+ .vi-assistlog-head {
4462
+ display: flex;
4463
+ align-items: center;
4464
+ gap: 8px;
4465
+ width: 100%;
4466
+ padding: 6px 10px;
4467
+ background: none;
4468
+ border: none;
4469
+ color: var(--vi-text);
4470
+ font-family: var(--vi-mono);
4471
+ font-size: 12px;
4472
+ cursor: pointer;
4473
+ text-align: left;
4474
+ }
4475
+ .vi-assistlog-head:hover { background: rgba(255, 255, 255, 0.03); }
4476
+ .vi-assistlog-caret { color: var(--vi-text-dim); width: 10px; }
4477
+ .vi-assistlog-time { color: var(--vi-text-dim); }
4478
+ .vi-assistlog-model {
4479
+ color: var(--vi-accent);
4480
+ white-space: nowrap;
4481
+ overflow: hidden;
4482
+ text-overflow: ellipsis;
4483
+ max-width: 260px;
4484
+ }
4485
+ .vi-assistlog-mode { color: var(--vi-text-dim); }
4486
+ .vi-assistlog-chip {
4487
+ color: #ffd479;
4488
+ border: 1px solid #5a4a20;
4489
+ border-radius: 4px;
4490
+ padding: 0 5px;
4491
+ font-size: 11px;
4492
+ }
4493
+ .vi-assistlog-elapsed { color: var(--vi-text-dim); }
4494
+ .vi-assistlog-badge {
4495
+ border-radius: 4px;
4496
+ padding: 1px 7px;
4497
+ font-size: 11px;
4498
+ letter-spacing: 0.03em;
4499
+ text-transform: uppercase;
4500
+ border: 1px solid var(--vi-border);
4501
+ color: var(--vi-text-dim);
4502
+ }
4503
+ .vi-assistlog-badge--ok {
4504
+ color: #052e18;
4505
+ background: #6ee7a8;
4506
+ border-color: #6ee7a8;
4507
+ }
4508
+ .vi-assistlog-badge--warn {
4509
+ color: #3a2c05;
4510
+ background: #ffd479;
4511
+ border-color: #ffd479;
4512
+ }
4513
+ .vi-assistlog-badge--err {
4514
+ color: #2e0606;
4515
+ background: #ff9b9b;
4516
+ border-color: #ff9b9b;
4517
+ }
4518
+ .vi-assistlog-errline {
4519
+ padding: 0 10px 6px 28px;
4520
+ color: #ff9b9b;
4521
+ white-space: nowrap;
4522
+ overflow: hidden;
4523
+ text-overflow: ellipsis;
4524
+ }
4525
+ .vi-assistlog-detail {
4526
+ padding: 4px 10px 12px 28px;
4527
+ display: flex;
4528
+ flex-direction: column;
4529
+ gap: 8px;
4530
+ }
4531
+ .vi-assistlog-field { display: flex; flex-direction: column; gap: 3px; }
4532
+ .vi-assistlog-label {
4533
+ color: var(--vi-text-dim);
4534
+ font-size: 11px;
4535
+ letter-spacing: 0.04em;
4536
+ text-transform: uppercase;
4537
+ }
4538
+ .vi-assistlog-raw,
4539
+ .vi-assistlog-text,
4540
+ .vi-assistlog-reasoning,
4541
+ .vi-assistlog-errbox {
4542
+ margin: 0;
4543
+ padding: 7px 9px;
4544
+ background: var(--vi-surface);
4545
+ border: 1px solid var(--vi-border);
4546
+ border-radius: 5px;
4547
+ white-space: pre-wrap;
4548
+ word-break: break-word;
4549
+ max-height: 300px;
4550
+ overflow-y: auto;
4551
+ font-family: var(--vi-mono);
4552
+ font-size: 12px;
4553
+ line-height: 1.5;
4554
+ }
4555
+ .vi-assistlog-raw { color: var(--vi-text); }
4556
+ .vi-assistlog-text { color: #6ee7a8; }
4557
+ .vi-assistlog-reasoning { color: var(--vi-text-dim); }
4558
+ .vi-assistlog-errbox { color: #ff9b9b; }
4559
+
4560
+ /* ---- IMAGE TESTER (Generate station, Image mode) --------------------------
4561
+ Per-prompt settings: each row is its own prompt + its own knobs + its own job,
4562
+ laid out so a sweep reads top-to-bottom and the results line up for comparison.
4563
+ Rows reuse .vi-gen-part / .vi-knob / .vi-knob-row, so only the tester-specific
4564
+ layout lives here. */
4565
+ .vi-gen-tester {
4566
+ display: flex;
4567
+ flex-direction: column;
4568
+ gap: 0.6rem;
4569
+ margin-top: 0.8rem;
4570
+ padding-top: 0.8rem;
4571
+ border-top: 1px solid var(--vi-border);
4572
+ }
4573
+
4574
+ .vi-gen-tester-head {
4575
+ display: flex;
4576
+ align-items: baseline;
4577
+ flex-wrap: wrap;
4578
+ gap: 0.5rem;
4579
+ }
4580
+
4581
+ .vi-gen-tester-rows {
4582
+ display: flex;
4583
+ flex-direction: column;
4584
+ gap: 0.6rem;
4585
+ list-style: none;
4586
+ margin: 0;
4587
+ padding: 0;
4588
+ }
4589
+
4590
+ /* The negative prompt is free text, so give it the whole row rather than letting
4591
+ it share a 5.5rem flex basis with the numeric knobs. */
4592
+ .vi-knob-row > .vi-knob.vi-gen-tester-neg,
4593
+ .vi-knob-strip-line > .vi-gen-tester-neg {
4594
+ flex: 1 1 100%;
4595
+ }
4596
+
4597
+ /* ---- CONDENSED KNOB STRIP (CondensedKnobStrip, operator ask 2026-08-04 k63) --
4598
+ The generation settings, absorbed into the prompt component: one inline line
4599
+ — [model ▾] ☑ size [512] × [512] ☑ steps [4] ☑ guidance [0] ☑ seed […] —
4600
+ instead of a column of full-width .vi-knob blocks down the settings rail.
4601
+ Rendered by BOTH the base composer and every tester row, so these rules are the
4602
+ whole visual language for "settings that belong to a prompt". */
4603
+ .vi-knob-strip {
4604
+ display: flex;
4605
+ flex-direction: column;
4606
+ gap: 0.3rem;
4607
+ }
4608
+
4609
+ .vi-knob-strip-line {
4610
+ display: flex;
4611
+ flex-wrap: wrap;
4612
+ align-items: center;
4613
+ gap: 0.35rem 0.8rem;
4614
+ }
4615
+
4616
+ /* One setting = tick + label + input(s) + ⓘ, kept together when the line wraps. */
4617
+ .vi-knob-mini-group {
4618
+ display: inline-flex;
4619
+ align-items: center;
4620
+ gap: 0.3rem;
4621
+ min-width: 0;
4622
+ }
4623
+
4624
+ .vi-knob-mini-label {
4625
+ font-size: 0.72rem;
4626
+ color: var(--vi-text-dim);
4627
+ text-transform: uppercase;
4628
+ letter-spacing: 0.05em;
4629
+ white-space: nowrap;
4630
+ }
4631
+
4632
+ /* The mini inputs are the point of the condensing: sized to their CONTENT (3–4
4633
+ digits), not to the container. `width` in ch + a tight padding override, since
4634
+ .vi-knob-input is width:100% by default. */
4635
+ .vi-knob-input.vi-knob-mini {
4636
+ width: 5ch;
4637
+ box-sizing: content-box;
4638
+ padding: 0.2rem 0.3rem;
4639
+ font-size: 0.8rem;
4640
+ text-align: center;
4641
+ }
4642
+
4643
+ /* Seeds can be long integers — give the seed a little more room than 5ch while
4644
+ keeping it visibly a mini input. */
4645
+ .vi-knob-input.vi-knob-mini-seed {
4646
+ width: 7ch;
4647
+ text-align: left;
4648
+ }
4649
+
4650
+ /* The literal × between width and height. */
4651
+ .vi-knob-mini-x {
4652
+ font-size: 0.75rem;
4653
+ color: var(--vi-text-dim);
4654
+ }
4655
+
4656
+ /* The model select can't shrink to a few ch (model ids are long), but it must not
4657
+ claim the whole line either. */
4658
+ .vi-knob-select-mini {
4659
+ width: auto;
4660
+ max-width: 16rem;
4661
+ padding: 0.2rem 0.3rem;
4662
+ font-size: 0.8rem;
4663
+ }
4664
+
4665
+ /* A tester row's negative is free text — it takes the remaining width of its line. */
4666
+ .vi-knob-input.vi-knob-mini-neg {
4667
+ flex: 1 1 8rem;
4668
+ width: auto;
4669
+ min-width: 0;
4670
+ padding: 0.2rem 0.3rem;
4671
+ font-size: 0.8rem;
4672
+ }
4673
+
4674
+ /* Validation flags stack UNDER the strip so a warning never breaks its single
4675
+ line (the flags themselves keep the shared amber .vi-knob-flag treatment). */
4676
+ .vi-knob-strip-flags {
4677
+ display: flex;
4678
+ flex-direction: column;
4679
+ gap: 0.15rem;
4680
+ }
4681
+
4682
+ /* ---- CARRY TICKS (k63) ----------------------------------------------------
4683
+ "Carry this setting into the component created from this one." They ride in
4684
+ FRONT of each label and must stay visually quiet — a condensed strip that
4685
+ grows a row of checkbox captions is no longer condensed. Small, dim until
4686
+ checked, and only the accent when on. */
4687
+ .vi-carry-tick {
4688
+ flex: 0 0 auto;
4689
+ width: 0.75rem;
4690
+ height: 0.75rem;
4691
+ margin: 0;
4692
+ accent-color: var(--vi-accent);
4693
+ cursor: pointer;
4694
+ }
4695
+
4696
+ .vi-carry-tick:not(:checked) {
4697
+ opacity: 0.5;
4698
+ }
4699
+
4700
+ /* The one place a tick gets a word: beside the negative-prompt composer, which is
4701
+ a block field rather than a strip item. */
4702
+ .vi-carry-tick-label {
4703
+ display: inline-flex;
4704
+ align-items: center;
4705
+ gap: 0.3rem;
4706
+ font-size: 0.72rem;
4707
+ color: var(--vi-text-dim);
4708
+ text-transform: uppercase;
4709
+ letter-spacing: 0.05em;
4710
+ cursor: pointer;
4711
+ }
4712
+
4713
+ .vi-gen-negative-wrap {
4714
+ display: flex;
4715
+ flex-direction: column;
4716
+ gap: 0.25rem;
4717
+ }
4718
+
4719
+ /* Result tiles stay small: the point is comparing several at once, not one hero
4720
+ image. Cap the height so N rows remain on screen together. */
4721
+ .vi-gen-tester-result .vi-result-img {
4722
+ max-height: 14rem;
4723
+ width: auto;
4724
+ object-fit: contain;
4725
+ }
4726
+
4727
+ .vi-gen-tester-actions {
4728
+ display: flex;
4729
+ flex-wrap: wrap;
4730
+ align-items: center;
4731
+ gap: 0.5rem;
4732
+ }
4733
+
4734
+ /* ---- ComfyUI OVERLAY (ComfyPanel) ----------------------------------------
4735
+ ComfyUI moved out of the studio tab strip and into a drawer beside Console
4736
+ (operator ask 2026-08-04), and then into a TAB of the one shared drawer
4737
+ window (2026-08-06). It reuses the shell — .vi-console-panel / -scrim / -bar /
4738
+ -actions / -iframe, plus .vi-drawer-tabpanel — so "the same as console" is
4739
+ literal, not approximate. Only the endpoint bar and the message strips are
4740
+ Comfy-specific: the bar's own wrap (which the endpoint form needs on a narrow
4741
+ drawer) moved onto the shared .vi-console-panel-bar with the tab strip, since
4742
+ every tab needs it now.
4743
+
4744
+ NB the .vi-comfy-* namespace is NOT ComfyUI-private: .vi-comfy-bar/-field/
4745
+ -label/-hint are generic form-bar classes reused by unrelated panels (e.g.
4746
+ ops/CharacterGroupsPanel). Everything added here is scoped under
4747
+ .vi-comfy-panel* to stay clear of that. */
4748
+
4749
+ .vi-comfy-panel-url {
4750
+ display: flex;
4751
+ align-items: center;
4752
+ gap: 0.4rem;
4753
+ flex: 1 1 18rem;
4754
+ min-width: 0;
4755
+ }
4756
+
4757
+ .vi-comfy-panel-url-label {
4758
+ font-size: 0.78rem;
4759
+ color: var(--vi-text-dim);
4760
+ white-space: nowrap;
4761
+ }
4762
+
4763
+ .vi-comfy-panel-url .vi-comfy-input {
4764
+ flex: 1 1 auto;
4765
+ min-width: 0;
4766
+ }
4767
+
4768
+ /* Warning / error strips sit under the bar, above the frame, and must not eat
4769
+ the frame's height. */
4770
+ .vi-comfy-panel-msg {
4771
+ flex: 0 0 auto;
4772
+ margin: 0;
4773
+ padding: 0.5rem 0.8rem;
4774
+ }
4775
+
4776
+ .vi-comfy-panel-empty {
4777
+ flex: 1 1 auto;
4778
+ display: flex;
4779
+ flex-direction: column;
4780
+ align-items: center;
4781
+ justify-content: center;
4782
+ gap: 0.4rem;
4783
+ text-align: center;
4784
+ padding: 2rem 1rem;
4785
+ }
4786
+
4787
+ /* ---- ACTIVE PROCESSES OVERLAY (ActivePanel) ------------------------------
4788
+ Active Processes joined Console + ComfyUI as a drawer, then as the third TAB
4789
+ of the one shared drawer window (operator ask 2026-08-06). Same shell classes
4790
+ — .vi-console-panel / -scrim / -bar / -actions / .vi-drawer-tabpanel — so
4791
+ nothing here re-styles the drawer itself. The one difference is the BODY:
4792
+ Console and ComfyUI fill it with an iframe (which scrolls itself), while this
4793
+ one holds a real `.station-card`, so it needs its own scroll region or a long
4794
+ job list would overflow the fixed-height panel. */
4795
+ .vi-active-panel-body {
4796
+ flex: 1 1 auto;
4797
+ min-height: 0; /* without this a flex child refuses to shrink → no scrollbar */
4798
+ overflow-y: auto;
4799
+ padding: 0.9rem;
4800
+ }
4801
+
4802
+ /* The station card inside the drawer is already framed by the panel — drop its
4803
+ own outer chrome so it doesn't read as a card-in-a-card. */
4804
+ .vi-active-panel-body .station-card {
4805
+ margin: 0;
4806
+ border: 0;
4807
+ background: transparent;
4808
+ box-shadow: none;
4809
+ }
4810
+
4811
+ /* Cross-model tester button — deliberately distinct from ghost so it reads as
4812
+ its own affordance on every generate tab (operator: "not seeing the tester"). */
4813
+ .vi-btn.vi-btn-tester {
4814
+ border: 1px solid #7a6cff;
4815
+ color: #b9b0ff;
4816
+ background: rgba(122, 108, 255, 0.10);
4817
+ }
4818
+ .vi-btn.vi-btn-tester:hover:not(:disabled) {
4819
+ background: rgba(122, 108, 255, 0.22);
4820
+ color: #d8d2ff;
4821
+ }
4822
+ .vi-btn.vi-btn-tester:disabled { opacity: 0.55; }
4823
+
4824
+ /* ═══ SHARED PROMPT-CARD SYSTEM (k88 — Studio declutter) ══════════════════════
4825
+ The uniform toolbar / row-card / field-tabs / settings-expander structure the
4826
+ Cinema composer and the Movie goal timeline both render (Scene + Clip follow
4827
+ in k89). Everything leans on the existing .vi-gen-part / .vi-knob / .vi-btn
4828
+ language; only the genuinely new pieces get rules here. */
4829
+
4830
+ /* Toolbar above the list: [count][select all][✨ prompts][✨ negatives][standard]. */
4831
+ .vi-prompt-toolbar {
4832
+ display: flex;
4833
+ flex-wrap: wrap;
4834
+ align-items: center;
4835
+ gap: 0.6rem;
4836
+ margin-bottom: 0.6rem;
4837
+ }
4838
+ .vi-prompt-toolbar-count {
4839
+ max-width: 8.5rem;
4840
+ }
4841
+ .vi-prompt-toolbar-selectall {
4842
+ display: flex;
4843
+ align-items: center;
4844
+ gap: 0.35rem;
4845
+ }
4846
+
4847
+ /* [Prompt][Negative] tab strip over the single editor pane. */
4848
+ .vi-prompt-tabs {
4849
+ display: flex;
4850
+ align-items: center;
4851
+ gap: 0.3rem;
4852
+ }
4853
+ .vi-prompt-tab {
4854
+ font-family: var(--vi-mono);
4855
+ font-size: 0.72rem;
4856
+ letter-spacing: 0.04em;
4857
+ text-transform: uppercase;
4858
+ color: var(--vi-text-dim);
4859
+ background: none;
4860
+ border: none;
4861
+ border-bottom: 2px solid transparent;
4862
+ padding: 0.15rem 0.4rem;
4863
+ cursor: pointer;
4864
+ }
4865
+ .vi-prompt-tab[aria-selected="true"] {
4866
+ color: var(--vi-accent);
4867
+ border-bottom-color: var(--vi-accent);
4868
+ }
4869
+ .vi-prompt-tab:disabled {
4870
+ opacity: 0.45;
4871
+ cursor: not-allowed;
4872
+ }
4873
+ /* The content marker on the Negative tab — a written negative must stay visible
4874
+ even while the Prompt pane is showing. */
4875
+ .vi-prompt-tab-dot {
4876
+ display: inline-block;
4877
+ width: 6px;
4878
+ height: 6px;
4879
+ border-radius: 50%;
4880
+ background: var(--vi-accent);
4881
+ margin-left: 0.3rem;
4882
+ vertical-align: middle;
4883
+ }
4884
+ /* The inline [standard negative] tick, pushed to the strip's right edge. */
4885
+ .vi-prompt-tab-std {
4886
+ display: flex;
4887
+ align-items: center;
4888
+ gap: 0.35rem;
4889
+ margin-left: auto;
4890
+ }
4891
+
4892
+ /* The uniform Prompt Assist row — the treat-as cluster and the Enhance/Generate
4893
+ cluster read as ONE line (each keeps its own .vi-prompt-assist internals). */
4894
+ .vi-prompt-card-assist {
4895
+ display: flex;
4896
+ flex-wrap: wrap;
4897
+ align-items: center;
4898
+ gap: 0.4rem 0.75rem;
4899
+ }
4900
+ .vi-prompt-card-assist > .vi-prompt-assist {
4901
+ border-bottom: none;
4902
+ padding-bottom: 0;
4903
+ }
4904
+
4905
+ /* The per-card settings expander — collapsed by default, condensed-strip rhythm
4906
+ (mono summary line, knobs flowing inline once open). */
4907
+ .vi-prompt-card-settings {
4908
+ border-top: 1px dashed var(--vi-border);
4909
+ padding-top: 0.3rem;
4910
+ }
4911
+ .vi-prompt-card-settings-summary {
4912
+ cursor: pointer;
4913
+ list-style: none;
4914
+ font-family: var(--vi-mono);
4915
+ font-size: 0.72rem;
4916
+ letter-spacing: 0.04em;
4917
+ color: var(--vi-text-dim);
4918
+ }
4919
+ .vi-prompt-card-settings-summary::-webkit-details-marker {
4920
+ display: none;
4921
+ }
4922
+ .vi-prompt-card-settings-summary::before {
4923
+ content: "▸ ";
4924
+ }
4925
+ .vi-prompt-card-settings[open] > .vi-prompt-card-settings-summary::before {
4926
+ content: "▾ ";
4927
+ }
4928
+ .vi-prompt-card-settings-body {
4929
+ display: flex;
4930
+ flex-wrap: wrap;
4931
+ align-items: flex-start;
4932
+ gap: 0.75rem;
4933
+ padding-top: 0.4rem;
4934
+ }
4935
+
4936
+ /* "About <tab>" sidebar expander (k89) — the condensed tab prose, collapsed by
4937
+ default at the top of each tab's Settings content. Same <details> rhythm as
4938
+ .vi-prompt-card-settings, boxed to sit against the sidebar's knob column. */
4939
+ .vi-about-expander {
4940
+ border: 1px solid var(--vi-border);
4941
+ border-radius: 0.4rem;
4942
+ padding: 0.35rem 0.55rem;
4943
+ margin-bottom: 0.6rem;
4944
+ }
4945
+ .vi-about-summary {
4946
+ cursor: pointer;
4947
+ list-style: none;
4948
+ font-family: var(--vi-mono);
4949
+ font-size: 0.72rem;
4950
+ letter-spacing: 0.04em;
4951
+ text-transform: uppercase;
4952
+ color: var(--vi-text-dim);
4953
+ }
4954
+ .vi-about-summary::-webkit-details-marker {
4955
+ display: none;
4956
+ }
4957
+ .vi-about-summary::before {
4958
+ content: "▸ ";
4959
+ }
4960
+ .vi-about-expander[open] > .vi-about-summary::before {
4961
+ content: "▾ ";
4962
+ }
4963
+ .vi-about-body {
4964
+ display: flex;
4965
+ flex-direction: column;
4966
+ gap: 0.3rem;
4967
+ padding-top: 0.4rem;
4968
+ font-size: 0.8rem;
4969
+ line-height: 1.4;
4970
+ color: var(--vi-text-dim);
4971
+ }
4972
+ .vi-about-body p {
4973
+ margin: 0;
4974
+ }