@dailies-studio/shot-editor 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.
- package/dist/assets/index-B7notge7.css +1 -0
- package/dist/assets/index-BBE6Ta9L.js +52 -0
- package/dist/index.html +13 -0
- package/package.json +45 -0
- package/src/api.ts +289 -0
- package/src/app/App.tsx +394 -0
- package/src/audio/index.tsx +74 -0
- package/src/audio/preview.ts +364 -0
- package/src/bridge.ts +45 -0
- package/src/bridgeTypes.ts +582 -0
- package/src/channels.ts +40 -0
- package/src/main.tsx +15 -0
- package/src/panels/ResizeHandle.tsx +113 -0
- package/src/panels/agent/AgentPanel.tsx +478 -0
- package/src/panels/agent/agent.css +322 -0
- package/src/panels/export/ExportDialog.tsx +284 -0
- package/src/panels/files/FilePanel.tsx +564 -0
- package/src/panels/files/McpSection.tsx +66 -0
- package/src/panels/files/icons.tsx +118 -0
- package/src/panels/header/DocSwitcher.tsx +85 -0
- package/src/panels/header/Header.tsx +132 -0
- package/src/panels/header/HistoryControls.tsx +20 -0
- package/src/panels/header/StatusBadges.tsx +49 -0
- package/src/panels/header/TransportBar.tsx +86 -0
- package/src/panels/header/ZoomControls.tsx +38 -0
- package/src/panels/help/HelpSection.tsx +190 -0
- package/src/panels/inspector/FramingRows.tsx +69 -0
- package/src/panels/inspector/InspectorPanel.tsx +178 -0
- package/src/panels/inspector/MarkerSection.tsx +63 -0
- package/src/panels/inspector/ParamRow.tsx +71 -0
- package/src/panels/inspector/animate/BlockTiming.tsx +341 -0
- package/src/panels/inspector/animate/DuringBlocks.tsx +85 -0
- package/src/panels/inspector/animate/InOutPresets.tsx +57 -0
- package/src/panels/inspector/animate/ResolvedChannels.tsx +150 -0
- package/src/panels/inspector/animate/ShotSection.tsx +126 -0
- package/src/panels/inspector/animate/useBlocks.ts +43 -0
- package/src/panels/inspector/design/AlignRow.tsx +63 -0
- package/src/panels/inspector/design/AppearanceSection.tsx +49 -0
- package/src/panels/inspector/design/ClonerSection.tsx +142 -0
- package/src/panels/inspector/design/ComponentSection.tsx +94 -0
- package/src/panels/inspector/design/CornerSection.tsx +52 -0
- package/src/panels/inspector/design/FillSection.tsx +89 -0
- package/src/panels/inspector/design/HtmlSection.tsx +50 -0
- package/src/panels/inspector/design/InstanceSection.tsx +302 -0
- package/src/panels/inspector/design/MediaSection.tsx +251 -0
- package/src/panels/inspector/design/MeshSection.tsx +172 -0
- package/src/panels/inspector/design/ShadowSection.tsx +73 -0
- package/src/panels/inspector/design/StrokeSection.tsx +85 -0
- package/src/panels/inspector/design/TransformSection.tsx +91 -0
- package/src/panels/inspector/design/TypographySection.tsx +170 -0
- package/src/panels/inspector/scene/CameraSection.tsx +59 -0
- package/src/panels/inspector/scene/EffectsSection.tsx +123 -0
- package/src/panels/inspector/scene/SceneSection.tsx +71 -0
- package/src/panels/inspector/scene/TransitionSection.tsx +136 -0
- package/src/panels/inspector/useDocWrite.ts +268 -0
- package/src/panels/material/MaterialPicker.tsx +172 -0
- package/src/panels/stage/StageMenu.tsx +38 -0
- package/src/panels/stage/StagePanel.tsx +131 -0
- package/src/panels/timeline/AudioLane.tsx +340 -0
- package/src/panels/timeline/BlockRow.tsx +242 -0
- package/src/panels/timeline/ChannelRow.tsx +266 -0
- package/src/panels/timeline/LifespanRow.tsx +165 -0
- package/src/panels/timeline/MarkerLane.tsx +146 -0
- package/src/panels/timeline/SceneStrip.tsx +145 -0
- package/src/panels/timeline/TimelineDock.tsx +488 -0
- package/src/panels/timeline/model.ts +641 -0
- package/src/panels/timeline/snap.ts +191 -0
- package/src/panels/toolbar/AddMenu.tsx +230 -0
- package/src/panels/toolbar/CameraViewBar.tsx +70 -0
- package/src/panels/toolbar/ShadingBar.tsx +42 -0
- package/src/panels/toolbar/ToolDockBar.tsx +110 -0
- package/src/panels/toolbar/ViewModeBar.tsx +49 -0
- package/src/shotTokens.css +131 -0
- package/src/stage/FrustumGizmo.tsx +184 -0
- package/src/stage/GridPlane.tsx +274 -0
- package/src/stage/MoveGizmo.tsx +411 -0
- package/src/stage/PerfHud.tsx +206 -0
- package/src/stage/SplitRenderer.tsx +511 -0
- package/src/stage/StageApp.tsx +3423 -0
- package/src/stage/StageTools.tsx +581 -0
- package/src/stage/bridgeClient.ts +278 -0
- package/src/stage/feed.ts +243 -0
- package/src/stage/frustumGeometry.ts +172 -0
- package/src/stage/gizmoMath.ts +494 -0
- package/src/stage/handles.ts +151 -0
- package/src/stage/instanceModules.ts +152 -0
- package/src/stage/interactions/move.ts +172 -0
- package/src/stage/interactions/panZoom.ts +60 -0
- package/src/stage/isolation.ts +155 -0
- package/src/stage/main.tsx +110 -0
- package/src/stage/modalState.ts +427 -0
- package/src/stage/overlays/Overlays.tsx +1046 -0
- package/src/stage/panes.ts +121 -0
- package/src/stage/penState.ts +112 -0
- package/src/stage/project.ts +402 -0
- package/src/stage/shading.ts +55 -0
- package/src/stage/tokenColor.ts +26 -0
- package/src/stage/view.ts +182 -0
- package/src/stage/workingView.ts +636 -0
- package/src/state/agentActions.ts +203 -0
- package/src/state/clientId.ts +25 -0
- package/src/state/context.tsx +33 -0
- package/src/state/insert.ts +507 -0
- package/src/state/keymap.ts +560 -0
- package/src/state/menu.ts +141 -0
- package/src/state/panelSize.ts +26 -0
- package/src/state/renderEvents.ts +100 -0
- package/src/state/store.ts +4094 -0
- package/src/state/triangles.ts +70 -0
- package/src/state/types.ts +384 -0
- package/src/styles.css +2948 -0
- package/src/tools.ts +181 -0
package/src/styles.css
ADDED
|
@@ -0,0 +1,2948 @@
|
|
|
1
|
+
/* The shot chrome's shell.
|
|
2
|
+
*
|
|
3
|
+
* THE RULE THAT MATTERS, and the reason this block is at the top: no ancestor
|
|
4
|
+
* of `.stage-frame` may carry `transform`, `filter`, `backdrop-filter`,
|
|
5
|
+
* `contain` or `will-change`. Each of those makes the element a containing
|
|
6
|
+
* block for fixed-position descendants, and the iframe is `position: fixed;
|
|
7
|
+
* inset: 0`. Add one to `.shell` for a shadow or an animation and the stage
|
|
8
|
+
* silently sizes itself to whichever panel became the containing block —
|
|
9
|
+
* which looks like a layout bug and is a stacking-context bug.
|
|
10
|
+
*
|
|
11
|
+
* ── WHY THIS IMPORTS styles.css AND NOT tokens.css ──────────────────────
|
|
12
|
+
*
|
|
13
|
+
* It used to import only tokens.css, which gave this chrome the PALETTE and
|
|
14
|
+
* none of the RULES — so every shared widget it renders (NumberField, Toggle,
|
|
15
|
+
* SegmentedControl, InspectorSection, InspectorRow) fell back to raw
|
|
16
|
+
* user-agent styling: native inputs, sliders as 12px stubs, labels colliding
|
|
17
|
+
* with values, no section rhythm at all. The lane looked unfinished because
|
|
18
|
+
* half of its stylesheet was never loaded.
|
|
19
|
+
*
|
|
20
|
+
* styles.css @imports tokens.css itself, so this one line is both halves. It
|
|
21
|
+
* is what canvas-editor and scene-editor have always done.
|
|
22
|
+
*/
|
|
23
|
+
@import "@dailies-studio/ui/styles.css";
|
|
24
|
+
|
|
25
|
+
/* ── THE LANE'S PALETTE ──────────────────────────────────────────────────
|
|
26
|
+
*
|
|
27
|
+
* The rules below were written against names that DO NOT EXIST in tokens.css
|
|
28
|
+
* — --bg, --panel, --text, --input-bg, --control, --font-ui and the
|
|
29
|
+
* timeline's colours. Every one silently resolved to its hardcoded fallback,
|
|
30
|
+
* so this chrome ran on a private palette while the four names that DID
|
|
31
|
+
* resolve (accent, border, danger, warn) tracked the real one. A chrome half
|
|
32
|
+
* on one palette and half on another is the definition of incoherent.
|
|
33
|
+
*
|
|
34
|
+
* The palette itself lives in shotTokens.css because the STAGE needs the same
|
|
35
|
+
* one and is a separate document. See that file for why it overrides the
|
|
36
|
+
* house accent. */
|
|
37
|
+
@import "./shotTokens.css";
|
|
38
|
+
|
|
39
|
+
:root {
|
|
40
|
+
/* Chrome-only: the shared widgets read these, re-pointed so NumberField,
|
|
41
|
+
Toggle and SegmentedControl land on the lane surfaces rather than the
|
|
42
|
+
house ones. The stage renders no widgets, so they stay out of the shared
|
|
43
|
+
palette file. */
|
|
44
|
+
--control-bg: var(--control);
|
|
45
|
+
--control-bg-hover: var(--control-hover);
|
|
46
|
+
--control-border: transparent;
|
|
47
|
+
--control-border-hover: var(--border);
|
|
48
|
+
--control-border-focus: var(--accent);
|
|
49
|
+
--control-radius: 8px;
|
|
50
|
+
--control-h: 30px;
|
|
51
|
+
--panel-bg: var(--panel);
|
|
52
|
+
--divider: var(--border-soft);
|
|
53
|
+
--focus-ring: 0 0 0 3px var(--accent-soft);
|
|
54
|
+
--label-w: 76px;
|
|
55
|
+
|
|
56
|
+
/* THE SLIDER FILL, QUIETED. NumberField paints a progress fill behind its
|
|
57
|
+
label to advertise drag-to-scrub. At the house 24% accent it read as a
|
|
58
|
+
solid coloured block sitting on top of the label, which was the single
|
|
59
|
+
most broken-looking thing in this panel. It stays, because the affordance
|
|
60
|
+
is real, at an alpha that says "draggable" rather than "selected". */
|
|
61
|
+
--slider-fill: rgba(255, 255, 255, 0.05);
|
|
62
|
+
--slider-thumb: rgba(255, 255, 255, 0.14);
|
|
63
|
+
|
|
64
|
+
--shadow-panel: 0 12px 40px rgba(0, 0, 0, 0.55), 0 2px 8px rgba(0, 0, 0, 0.4);
|
|
65
|
+
--panel-radius: 12px;
|
|
66
|
+
|
|
67
|
+
/* THE TWO SIDE RAILS' WIDTHS, AS VARIABLES, because five other boxes are
|
|
68
|
+
positioned off them: the control strip and the timeline dock both clear
|
|
69
|
+
the rails, and collapsing a rail has to slide them over rather than leave
|
|
70
|
+
a hole. One number per rail, set here and overridden by the collapse
|
|
71
|
+
classes below, so there is no arithmetic to keep in sync by hand. */
|
|
72
|
+
--rail-w: 232px;
|
|
73
|
+
--inspector-w: 288px;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* Collapsed rails are zero-width, not `display: none`: the boxes that track
|
|
77
|
+
them do arithmetic on these, and a missing value would break the sum. The
|
|
78
|
+
panels themselves are hidden by their own rules. */
|
|
79
|
+
.shell.is-rail-collapsed {
|
|
80
|
+
--rail-w: 0px;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.shell.is-inspector-collapsed {
|
|
84
|
+
--inspector-w: 0px;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.shell.is-rail-collapsed > .left-rail,
|
|
88
|
+
.shell.is-inspector-collapsed > .inspector,
|
|
89
|
+
.shell.is-inspector-collapsed > .agent-panel {
|
|
90
|
+
display: none;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
* {
|
|
94
|
+
box-sizing: border-box;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
html,
|
|
98
|
+
body,
|
|
99
|
+
#root {
|
|
100
|
+
height: 100%;
|
|
101
|
+
margin: 0;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
body {
|
|
105
|
+
font: var(--font);
|
|
106
|
+
color: var(--text);
|
|
107
|
+
background: var(--bg);
|
|
108
|
+
-webkit-font-smoothing: antialiased;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.shell {
|
|
112
|
+
position: relative;
|
|
113
|
+
height: 100%;
|
|
114
|
+
/* No transform, no filter, no contain. See the header. */
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.stage-frame {
|
|
118
|
+
position: fixed;
|
|
119
|
+
inset: 0;
|
|
120
|
+
width: 100%;
|
|
121
|
+
height: 100%;
|
|
122
|
+
border: 0;
|
|
123
|
+
display: block;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/* Every floating panel shares one recipe, so a new panel cannot invent its
|
|
127
|
+
own shade of "panel". */
|
|
128
|
+
.shell-header,
|
|
129
|
+
.left-rail,
|
|
130
|
+
.inspector,
|
|
131
|
+
.agent-panel,
|
|
132
|
+
.timeline-dock,
|
|
133
|
+
.tool-dock,
|
|
134
|
+
.view-mode-bar,
|
|
135
|
+
.camera-view-bar,
|
|
136
|
+
.tool-flyout,
|
|
137
|
+
.overlay-message {
|
|
138
|
+
background: var(--panel);
|
|
139
|
+
border: 1px solid var(--border);
|
|
140
|
+
border-radius: var(--panel-radius);
|
|
141
|
+
box-shadow: var(--shadow-panel);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* ---- buttons ------------------------------------------------------------ */
|
|
145
|
+
/* One button vocabulary for the whole chrome. `.btn` is the shared class the
|
|
146
|
+
markup already uses; the bare-element rules below catch the panels that
|
|
147
|
+
render plain <button>s. */
|
|
148
|
+
.btn,
|
|
149
|
+
.shell-header button,
|
|
150
|
+
.dock-head button,
|
|
151
|
+
.export-dialog button {
|
|
152
|
+
display: inline-flex;
|
|
153
|
+
align-items: center;
|
|
154
|
+
justify-content: center;
|
|
155
|
+
gap: 6px;
|
|
156
|
+
height: 28px;
|
|
157
|
+
padding: 0 10px;
|
|
158
|
+
font: inherit;
|
|
159
|
+
font-weight: 500;
|
|
160
|
+
color: var(--text);
|
|
161
|
+
background: var(--control);
|
|
162
|
+
border: 1px solid transparent;
|
|
163
|
+
border-radius: 8px;
|
|
164
|
+
cursor: pointer;
|
|
165
|
+
white-space: nowrap;
|
|
166
|
+
transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.btn:hover:not(:disabled),
|
|
170
|
+
.shell-header button:hover:not(:disabled),
|
|
171
|
+
.dock-head button:hover:not(:disabled),
|
|
172
|
+
.export-dialog button:hover:not(:disabled) {
|
|
173
|
+
background: var(--control-hover);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.btn:disabled,
|
|
177
|
+
.shell-header button:disabled,
|
|
178
|
+
.export-dialog button:disabled {
|
|
179
|
+
opacity: 0.35;
|
|
180
|
+
cursor: default;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.btn.is-icon,
|
|
184
|
+
.shell-header button.is-icon {
|
|
185
|
+
width: 28px;
|
|
186
|
+
padding: 0;
|
|
187
|
+
font-size: 13px;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/* The one accent control per surface. Raylight has exactly one blue button on
|
|
191
|
+
screen at a time (Share); ours is Export. */
|
|
192
|
+
.btn.is-primary,
|
|
193
|
+
.shell-header button.is-primary {
|
|
194
|
+
background: var(--accent);
|
|
195
|
+
border-color: transparent;
|
|
196
|
+
color: #fff;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.btn.is-primary:hover:not(:disabled) {
|
|
200
|
+
background: var(--accent-hover);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.btn.is-active,
|
|
204
|
+
.btn[aria-pressed="true"] {
|
|
205
|
+
background: var(--accent-soft);
|
|
206
|
+
color: #cfe4ff;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.btn.is-danger {
|
|
210
|
+
color: var(--danger-text);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.btn:focus-visible,
|
|
214
|
+
.shell-header button:focus-visible {
|
|
215
|
+
outline: none;
|
|
216
|
+
box-shadow: var(--focus-ring);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/* ---- header ------------------------------------------------------------- */
|
|
220
|
+
.shell-header {
|
|
221
|
+
position: fixed;
|
|
222
|
+
top: 12px;
|
|
223
|
+
left: 12px;
|
|
224
|
+
right: 12px;
|
|
225
|
+
z-index: 10;
|
|
226
|
+
display: flex;
|
|
227
|
+
align-items: center;
|
|
228
|
+
gap: 8px;
|
|
229
|
+
height: 48px;
|
|
230
|
+
padding: 0 10px;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.shell-header-spacer {
|
|
234
|
+
flex: 1;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.doc-switcher {
|
|
238
|
+
display: flex;
|
|
239
|
+
gap: 6px;
|
|
240
|
+
align-items: center;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/* A native <select> paints its own OS chrome — a light popup arrow and a
|
|
244
|
+
system font on a dark panel, which is exactly what "not designed" looks
|
|
245
|
+
like. appearance:none plus a drawn chevron is the whole fix. */
|
|
246
|
+
.doc-switcher-select,
|
|
247
|
+
.doc-switcher-input,
|
|
248
|
+
.select {
|
|
249
|
+
appearance: none;
|
|
250
|
+
-webkit-appearance: none;
|
|
251
|
+
height: 30px;
|
|
252
|
+
min-width: 156px;
|
|
253
|
+
padding: 0 28px 0 10px;
|
|
254
|
+
font: inherit;
|
|
255
|
+
font-weight: 500;
|
|
256
|
+
color: var(--text);
|
|
257
|
+
background-color: var(--control);
|
|
258
|
+
background-image: linear-gradient(45deg, transparent 50%, var(--text-dim) 50%),
|
|
259
|
+
linear-gradient(135deg, var(--text-dim) 50%, transparent 50%);
|
|
260
|
+
background-position: calc(100% - 15px) 13px, calc(100% - 10px) 13px;
|
|
261
|
+
background-size: 5px 5px, 5px 5px;
|
|
262
|
+
background-repeat: no-repeat;
|
|
263
|
+
border: 1px solid transparent;
|
|
264
|
+
border-radius: 8px;
|
|
265
|
+
cursor: pointer;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.doc-switcher-input {
|
|
269
|
+
background-image: none;
|
|
270
|
+
padding-right: 10px;
|
|
271
|
+
cursor: text;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.doc-switcher-select:hover,
|
|
275
|
+
.doc-switcher-input:hover {
|
|
276
|
+
background-color: var(--control-hover);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.doc-switcher-select:focus-visible,
|
|
280
|
+
.doc-switcher-input:focus {
|
|
281
|
+
outline: none;
|
|
282
|
+
border-color: var(--accent);
|
|
283
|
+
box-shadow: var(--focus-ring);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.doc-switcher-select option {
|
|
287
|
+
background: var(--panel-raised);
|
|
288
|
+
color: var(--text);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.history-controls,
|
|
292
|
+
.zoom-controls,
|
|
293
|
+
.status-badges,
|
|
294
|
+
.transport {
|
|
295
|
+
display: flex;
|
|
296
|
+
align-items: center;
|
|
297
|
+
gap: 4px;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.transport {
|
|
301
|
+
gap: 8px;
|
|
302
|
+
margin-left: 4px;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/* The scrub is the widest thing in the header and it was a raw range input.
|
|
306
|
+
Styled here rather than in the shared sheet because no other chrome puts a
|
|
307
|
+
transport in its header. */
|
|
308
|
+
.transport-scrub {
|
|
309
|
+
appearance: none;
|
|
310
|
+
-webkit-appearance: none;
|
|
311
|
+
width: 240px;
|
|
312
|
+
height: 18px;
|
|
313
|
+
background: transparent;
|
|
314
|
+
cursor: pointer;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.transport-scrub::-webkit-slider-runnable-track {
|
|
318
|
+
height: 4px;
|
|
319
|
+
border-radius: 999px;
|
|
320
|
+
background: var(--control);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.transport-scrub::-webkit-slider-thumb {
|
|
324
|
+
appearance: none;
|
|
325
|
+
-webkit-appearance: none;
|
|
326
|
+
width: 12px;
|
|
327
|
+
height: 12px;
|
|
328
|
+
margin-top: -4px;
|
|
329
|
+
border-radius: 999px;
|
|
330
|
+
background: #fff;
|
|
331
|
+
border: 0;
|
|
332
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.transport-scrub::-moz-range-track {
|
|
336
|
+
height: 4px;
|
|
337
|
+
border-radius: 999px;
|
|
338
|
+
background: var(--control);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.transport-scrub::-moz-range-thumb {
|
|
342
|
+
width: 12px;
|
|
343
|
+
height: 12px;
|
|
344
|
+
border: 0;
|
|
345
|
+
border-radius: 999px;
|
|
346
|
+
background: #fff;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.transport-scrub:focus-visible {
|
|
350
|
+
outline: none;
|
|
351
|
+
box-shadow: var(--focus-ring);
|
|
352
|
+
border-radius: 999px;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/* Play/pause is the transport's one primary control, the same call the motion
|
|
356
|
+
and canvas chromes make. */
|
|
357
|
+
.transport-play {
|
|
358
|
+
min-width: 40px;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
.transport-readout,
|
|
362
|
+
.zoom-readout,
|
|
363
|
+
.dock-readout {
|
|
364
|
+
font-variant-numeric: tabular-nums;
|
|
365
|
+
color: var(--text-dim);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.transport-readout {
|
|
369
|
+
min-width: 64px;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
.zoom-readout {
|
|
373
|
+
min-width: 48px;
|
|
374
|
+
text-align: center;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
.badge {
|
|
378
|
+
display: inline-flex;
|
|
379
|
+
align-items: center;
|
|
380
|
+
height: 22px;
|
|
381
|
+
padding: 0 8px;
|
|
382
|
+
border-radius: 999px;
|
|
383
|
+
border: 1px solid var(--border);
|
|
384
|
+
color: var(--text-dim);
|
|
385
|
+
white-space: nowrap;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
.badge-quiet {
|
|
389
|
+
color: var(--text-faint);
|
|
390
|
+
border-color: transparent;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.badge-warn {
|
|
394
|
+
border-color: rgba(245, 165, 36, 0.4);
|
|
395
|
+
color: var(--warn);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
.badge-error {
|
|
399
|
+
border-color: rgba(255, 77, 79, 0.45);
|
|
400
|
+
color: var(--danger-text);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/* ---- overlays ----------------------------------------------------------- */
|
|
404
|
+
.overlay-strip {
|
|
405
|
+
position: fixed;
|
|
406
|
+
left: 50%;
|
|
407
|
+
transform: translateX(-50%);
|
|
408
|
+
bottom: 76px;
|
|
409
|
+
z-index: 20;
|
|
410
|
+
display: flex;
|
|
411
|
+
flex-direction: column;
|
|
412
|
+
gap: 6px;
|
|
413
|
+
pointer-events: none;
|
|
414
|
+
max-width: 560px;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.overlay-message {
|
|
418
|
+
pointer-events: auto;
|
|
419
|
+
display: flex;
|
|
420
|
+
gap: 10px;
|
|
421
|
+
align-items: flex-start;
|
|
422
|
+
padding: 10px 12px;
|
|
423
|
+
line-height: 1.5;
|
|
424
|
+
border-color: rgba(255, 77, 79, 0.5);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
.overlay-message button {
|
|
428
|
+
font: inherit;
|
|
429
|
+
font-size: 14px;
|
|
430
|
+
color: var(--text-dim);
|
|
431
|
+
background: transparent;
|
|
432
|
+
border: 0;
|
|
433
|
+
cursor: pointer;
|
|
434
|
+
padding: 0;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
.overlay-message button:hover {
|
|
438
|
+
color: var(--text);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
.empty-state {
|
|
442
|
+
position: fixed;
|
|
443
|
+
inset: 0;
|
|
444
|
+
z-index: 5;
|
|
445
|
+
display: grid;
|
|
446
|
+
place-content: center;
|
|
447
|
+
text-align: center;
|
|
448
|
+
gap: 6px;
|
|
449
|
+
pointer-events: none;
|
|
450
|
+
color: var(--text-faint);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
.empty-state h1 {
|
|
454
|
+
font-size: 20px;
|
|
455
|
+
font-weight: 600;
|
|
456
|
+
margin: 0;
|
|
457
|
+
color: var(--text-dim);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
.empty-state p {
|
|
461
|
+
margin: 0;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/* ---- left rail ---------------------------------------------------------- */
|
|
465
|
+
.left-rail {
|
|
466
|
+
position: fixed;
|
|
467
|
+
top: 72px;
|
|
468
|
+
left: 12px;
|
|
469
|
+
bottom: 12px;
|
|
470
|
+
width: var(--rail-w);
|
|
471
|
+
z-index: 10;
|
|
472
|
+
overflow: auto;
|
|
473
|
+
/* A column so the manual can pin itself to the bottom with margin-top: auto.
|
|
474
|
+
Children keep their natural height (flex-shrink: 0 below) — when the tree
|
|
475
|
+
outgrows the rail, the rail scrolls; nothing compresses. */
|
|
476
|
+
display: flex;
|
|
477
|
+
flex-direction: column;
|
|
478
|
+
padding: 10px;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
.left-rail:empty {
|
|
482
|
+
display: none;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
.left-rail > * {
|
|
486
|
+
flex-shrink: 0;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/* ---- the manual ---------------------------------------------------------- */
|
|
490
|
+
/* Bottom-left, the corner nothing else claims. `margin-top: auto` pins it
|
|
491
|
+
there while the tree is short; once the rail scrolls it is simply last. */
|
|
492
|
+
.help-section {
|
|
493
|
+
margin-top: auto;
|
|
494
|
+
padding-top: 12px;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
.help-summary {
|
|
498
|
+
cursor: pointer;
|
|
499
|
+
user-select: none;
|
|
500
|
+
list-style: none;
|
|
501
|
+
margin: 0;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
.help-summary::-webkit-details-marker {
|
|
505
|
+
display: none;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
.help-summary::before {
|
|
509
|
+
content: "▸ ";
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
.help-section[open] > .help-summary::before {
|
|
513
|
+
content: "▾ ";
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
.help-basics {
|
|
517
|
+
list-style: disc;
|
|
518
|
+
margin: 6px 0;
|
|
519
|
+
padding: 0 4px 0 18px;
|
|
520
|
+
color: var(--text-dim);
|
|
521
|
+
font-size: 11px;
|
|
522
|
+
line-height: 1.5;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
.help-basics li + li {
|
|
526
|
+
margin-top: 4px;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
.help-rows {
|
|
530
|
+
list-style: none;
|
|
531
|
+
margin: 0 0 4px;
|
|
532
|
+
padding: 0 4px;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
.help-row {
|
|
536
|
+
display: flex;
|
|
537
|
+
align-items: baseline;
|
|
538
|
+
justify-content: space-between;
|
|
539
|
+
gap: 8px;
|
|
540
|
+
padding: 2px 0;
|
|
541
|
+
color: var(--text-dim);
|
|
542
|
+
font-size: 11px;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
.help-row kbd,
|
|
546
|
+
.help-note code {
|
|
547
|
+
font: inherit;
|
|
548
|
+
font-size: 10px;
|
|
549
|
+
color: var(--text);
|
|
550
|
+
background: var(--control);
|
|
551
|
+
border-radius: 5px;
|
|
552
|
+
padding: 1px 5px;
|
|
553
|
+
white-space: nowrap;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
.help-note {
|
|
557
|
+
margin: 0 0 6px;
|
|
558
|
+
padding: 0 4px;
|
|
559
|
+
color: var(--text-faint);
|
|
560
|
+
font-size: 11px;
|
|
561
|
+
line-height: 1.45;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
.help-command {
|
|
565
|
+
display: flex;
|
|
566
|
+
align-items: flex-start;
|
|
567
|
+
gap: 6px;
|
|
568
|
+
margin: 0 0 6px;
|
|
569
|
+
padding: 0 4px;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
.help-command code {
|
|
573
|
+
flex: 1;
|
|
574
|
+
font-size: 10px;
|
|
575
|
+
line-height: 1.5;
|
|
576
|
+
color: var(--text);
|
|
577
|
+
background: var(--control);
|
|
578
|
+
border-radius: 5px;
|
|
579
|
+
padding: 4px 6px;
|
|
580
|
+
/* One long claude-mcp-add line must wrap inside a 232px rail, not scroll. */
|
|
581
|
+
word-break: break-all;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
.help-command button {
|
|
585
|
+
font: inherit;
|
|
586
|
+
font-size: 10px;
|
|
587
|
+
color: var(--text-dim);
|
|
588
|
+
background: var(--control);
|
|
589
|
+
border: 0;
|
|
590
|
+
border-radius: 6px;
|
|
591
|
+
padding: 3px 7px;
|
|
592
|
+
cursor: pointer;
|
|
593
|
+
white-space: nowrap;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
.file-search {
|
|
597
|
+
margin-bottom: 4px;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
.file-search input {
|
|
601
|
+
width: 100%;
|
|
602
|
+
height: 30px;
|
|
603
|
+
font: inherit;
|
|
604
|
+
color: var(--text);
|
|
605
|
+
background: var(--control);
|
|
606
|
+
border: 1px solid transparent;
|
|
607
|
+
border-radius: 8px;
|
|
608
|
+
padding: 0 10px;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
.file-search input::placeholder {
|
|
612
|
+
color: var(--text-faint);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
.file-search input:focus {
|
|
616
|
+
outline: none;
|
|
617
|
+
border-color: var(--accent);
|
|
618
|
+
box-shadow: var(--focus-ring);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
.file-section-label {
|
|
622
|
+
margin: 14px 0 6px;
|
|
623
|
+
padding: 0 4px;
|
|
624
|
+
color: var(--text-faint);
|
|
625
|
+
text-transform: uppercase;
|
|
626
|
+
letter-spacing: 0.07em;
|
|
627
|
+
font-size: 10px;
|
|
628
|
+
font-weight: 600;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
.scene-list,
|
|
632
|
+
.layer-list,
|
|
633
|
+
.layer-children {
|
|
634
|
+
list-style: none;
|
|
635
|
+
margin: 0;
|
|
636
|
+
padding: 0;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
.layer-children {
|
|
640
|
+
padding-left: 12px;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
.scene-row,
|
|
644
|
+
.layer-name {
|
|
645
|
+
font: inherit;
|
|
646
|
+
font-weight: 500;
|
|
647
|
+
color: var(--text);
|
|
648
|
+
background: transparent;
|
|
649
|
+
border: 0;
|
|
650
|
+
padding: 0 8px;
|
|
651
|
+
height: 28px;
|
|
652
|
+
width: 100%;
|
|
653
|
+
text-align: left;
|
|
654
|
+
cursor: pointer;
|
|
655
|
+
border-radius: 7px;
|
|
656
|
+
white-space: nowrap;
|
|
657
|
+
overflow: hidden;
|
|
658
|
+
text-overflow: ellipsis;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/* A shot row carries its own icon, so it lays out like a layer row: icon,
|
|
662
|
+
then a label that ellipsizes on its own (a flex container ignores
|
|
663
|
+
text-overflow on a bare text node). */
|
|
664
|
+
.scene-row {
|
|
665
|
+
display: flex;
|
|
666
|
+
align-items: center;
|
|
667
|
+
gap: 6px;
|
|
668
|
+
padding-left: 6px;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
.row-label {
|
|
672
|
+
flex: 1;
|
|
673
|
+
min-width: 0;
|
|
674
|
+
overflow: hidden;
|
|
675
|
+
text-overflow: ellipsis;
|
|
676
|
+
white-space: nowrap;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
.scene-row:hover,
|
|
680
|
+
.layer-row:hover {
|
|
681
|
+
background: var(--control);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
.scene-row.is-current {
|
|
685
|
+
background: var(--accent-soft);
|
|
686
|
+
color: #cfe4ff;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
.layer-row {
|
|
690
|
+
display: flex;
|
|
691
|
+
align-items: center;
|
|
692
|
+
gap: 2px;
|
|
693
|
+
padding-right: 4px;
|
|
694
|
+
border-radius: 7px;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
.layer-row.is-selected {
|
|
698
|
+
background: var(--accent-soft);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
.layer-row.is-selected .layer-name {
|
|
702
|
+
color: #cfe4ff;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/* The kind icon. Fixed width so every name in the tree starts on the same
|
|
706
|
+
column. ACCENT, not dim — the Framer read: the icon says what a row IS at a
|
|
707
|
+
glance, the 500-weight name says which one. */
|
|
708
|
+
.layer-kind {
|
|
709
|
+
flex: none;
|
|
710
|
+
width: 16px;
|
|
711
|
+
display: inline-flex;
|
|
712
|
+
align-items: center;
|
|
713
|
+
justify-content: center;
|
|
714
|
+
color: var(--accent);
|
|
715
|
+
user-select: none;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
.layer-kind svg {
|
|
719
|
+
width: 14px;
|
|
720
|
+
height: 14px;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/* The disclosure chevron. Always visible on a row that can fold (unlike the
|
|
724
|
+
hover-only flags: a fold changes what the LIST shows, so its affordance
|
|
725
|
+
cannot be a secret), pointing right and rotating open — the ui package's
|
|
726
|
+
IconChevron convention. The spacer keeps leaf rows' kind icons on the same
|
|
727
|
+
column. */
|
|
728
|
+
.layer-toggle {
|
|
729
|
+
display: inline-flex;
|
|
730
|
+
align-items: center;
|
|
731
|
+
justify-content: center;
|
|
732
|
+
color: var(--text-dim);
|
|
733
|
+
background: transparent;
|
|
734
|
+
border: 0;
|
|
735
|
+
cursor: pointer;
|
|
736
|
+
border-radius: 5px;
|
|
737
|
+
padding: 2px;
|
|
738
|
+
flex: none;
|
|
739
|
+
width: 14px;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
.layer-toggle svg {
|
|
743
|
+
width: 10px;
|
|
744
|
+
height: 10px;
|
|
745
|
+
transition: transform 80ms ease;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
.layer-toggle.is-open svg {
|
|
749
|
+
transform: rotate(90deg);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
.layer-toggle:hover {
|
|
753
|
+
color: var(--text);
|
|
754
|
+
background: var(--control-hover);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
.layer-toggle-spacer {
|
|
758
|
+
flex: none;
|
|
759
|
+
width: 14px;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
/* The eye and the lock only appear on hover or when they are ON, so a
|
|
763
|
+
forty-layer tree is names rather than eighty grey glyphs. */
|
|
764
|
+
.layer-flag {
|
|
765
|
+
display: inline-flex;
|
|
766
|
+
align-items: center;
|
|
767
|
+
justify-content: center;
|
|
768
|
+
color: var(--text-dim);
|
|
769
|
+
background: transparent;
|
|
770
|
+
border: 0;
|
|
771
|
+
cursor: pointer;
|
|
772
|
+
opacity: 0;
|
|
773
|
+
border-radius: 5px;
|
|
774
|
+
padding: 3px 4px;
|
|
775
|
+
flex: none;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
.layer-flag svg {
|
|
779
|
+
width: 13px;
|
|
780
|
+
height: 13px;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
.layer-row:hover .layer-flag,
|
|
784
|
+
.layer-flag[aria-pressed="true"],
|
|
785
|
+
.layer-flag:focus-visible {
|
|
786
|
+
opacity: 0.75;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
.layer-flag:hover {
|
|
790
|
+
opacity: 1 !important;
|
|
791
|
+
background: var(--control-hover);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
.layer-rename {
|
|
795
|
+
flex: 1;
|
|
796
|
+
min-width: 0;
|
|
797
|
+
font: inherit;
|
|
798
|
+
color: var(--text);
|
|
799
|
+
background: var(--input-bg);
|
|
800
|
+
border: 1px solid var(--accent);
|
|
801
|
+
border-radius: 6px;
|
|
802
|
+
padding: 3px 6px;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
.layer-rename:focus {
|
|
806
|
+
outline: none;
|
|
807
|
+
box-shadow: var(--focus-ring);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
/* ---- MCP (connect an agent) ---------------------------------------------- */
|
|
811
|
+
/* Reuses the manual's note/command rules — same prose, same rail. */
|
|
812
|
+
.mcp-section {
|
|
813
|
+
margin-top: 4px;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
.panel-title {
|
|
817
|
+
font-weight: 600;
|
|
818
|
+
color: var(--text);
|
|
819
|
+
margin: 0 0 6px;
|
|
820
|
+
padding: 0 4px;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
.panel-empty {
|
|
824
|
+
color: var(--text-faint);
|
|
825
|
+
padding: 8px 4px;
|
|
826
|
+
line-height: 1.5;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
/* ---- tool dock ---------------------------------------------------------- */
|
|
830
|
+
/* ---- resizable edges ---------------------------------------------------- */
|
|
831
|
+
/* 8px of hit area for a 1px seam. Anything thinner is a target you have to
|
|
832
|
+
aim at; the visible line is drawn on hover only, so at rest the seam is
|
|
833
|
+
whatever the panel's own border already was.
|
|
834
|
+
|
|
835
|
+
`touch-action: none` is what makes a pen or touch drag resize instead of
|
|
836
|
+
scrolling the page out from under the gesture. */
|
|
837
|
+
.rail-resize,
|
|
838
|
+
.dock-resize,
|
|
839
|
+
.split-resize {
|
|
840
|
+
z-index: 12;
|
|
841
|
+
touch-action: none;
|
|
842
|
+
background: transparent;
|
|
843
|
+
transition: background 120ms ease;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
.rail-resize:hover,
|
|
847
|
+
.dock-resize:hover,
|
|
848
|
+
.split-resize:hover {
|
|
849
|
+
background: var(--accent-soft);
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/* The stage's own divider between the working and camera panes. It sits ON the
|
|
853
|
+
seam the scissor draws to, whose position is `--split-x` — written by the
|
|
854
|
+
shell's imperative writer from the same `splitRatio` `paneRects` reads, so
|
|
855
|
+
the handle and the rendered boundary cannot end up a column apart.
|
|
856
|
+
|
|
857
|
+
BELOW the top control strip and above the dock, so it never overlaps either.
|
|
858
|
+
Full height would put it under the header, where it is unreachable anyway. */
|
|
859
|
+
.split-resize {
|
|
860
|
+
position: fixed;
|
|
861
|
+
top: 128px;
|
|
862
|
+
bottom: calc(var(--dock-h) + 24px);
|
|
863
|
+
left: var(--split-x);
|
|
864
|
+
width: 8px;
|
|
865
|
+
margin-left: -4px;
|
|
866
|
+
cursor: col-resize;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
/* The rail's inner edge: rail sits at left:12 and is --rail-w wide, so its
|
|
870
|
+
right edge is exactly that sum. Centred on the seam by half its width. */
|
|
871
|
+
.rail-resize {
|
|
872
|
+
position: fixed;
|
|
873
|
+
top: 72px;
|
|
874
|
+
bottom: 12px;
|
|
875
|
+
left: calc(var(--rail-w) + 12px);
|
|
876
|
+
width: 8px;
|
|
877
|
+
margin-left: -4px;
|
|
878
|
+
cursor: col-resize;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
/* The dock's top edge. Inside the dock (which is `position: fixed`), because
|
|
882
|
+
the dock's height is content-driven up to its max and only it knows where
|
|
883
|
+
its top actually is. */
|
|
884
|
+
.dock-resize {
|
|
885
|
+
position: absolute;
|
|
886
|
+
top: 0;
|
|
887
|
+
left: 0;
|
|
888
|
+
right: 0;
|
|
889
|
+
height: 8px;
|
|
890
|
+
cursor: row-resize;
|
|
891
|
+
border-top-left-radius: var(--panel-radius);
|
|
892
|
+
border-top-right-radius: var(--panel-radius);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
/* ---- panel toggles (header) --------------------------------------------- */
|
|
896
|
+
/* One button per panel, plus "hide everything". Grouped tight so they read as
|
|
897
|
+
one control rather than four buttons that happen to be adjacent. */
|
|
898
|
+
.panel-toggles {
|
|
899
|
+
display: flex;
|
|
900
|
+
align-items: center;
|
|
901
|
+
gap: 2px;
|
|
902
|
+
padding: 0 4px;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
.btn-icon {
|
|
906
|
+
min-width: 28px;
|
|
907
|
+
padding: 0 6px;
|
|
908
|
+
justify-content: center;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
/* ---- the control strip -------------------------------------------------- */
|
|
912
|
+
/* One row along the top of the stage holding the view modes, the camera verbs
|
|
913
|
+
and the tools. It is the ONLY positioned box of the three; each bar inside
|
|
914
|
+
is a plain flex child, which is what lets them sit as one continuous strip
|
|
915
|
+
and wrap together on a narrow window instead of overlapping each other.
|
|
916
|
+
|
|
917
|
+
`left` tracks the left rail through --rail-w, so collapsing the rail slides
|
|
918
|
+
the strip over rather than leaving a hole. */
|
|
919
|
+
.control-strip {
|
|
920
|
+
position: fixed;
|
|
921
|
+
top: 72px;
|
|
922
|
+
left: calc(var(--rail-w) + 24px);
|
|
923
|
+
right: calc(var(--inspector-w) + 24px);
|
|
924
|
+
z-index: 12;
|
|
925
|
+
display: flex;
|
|
926
|
+
align-items: flex-start;
|
|
927
|
+
flex-wrap: wrap;
|
|
928
|
+
gap: 8px;
|
|
929
|
+
/* Only the bars themselves take pointer events; the gaps between them are
|
|
930
|
+
stage, and a full-width invisible box swallowing clicks along the top of
|
|
931
|
+
the canvas is the classic version of this bug. */
|
|
932
|
+
pointer-events: none;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
.control-strip > * {
|
|
936
|
+
pointer-events: auto;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
.tool-dock {
|
|
940
|
+
display: flex;
|
|
941
|
+
align-items: center;
|
|
942
|
+
gap: 2px;
|
|
943
|
+
padding: 5px;
|
|
944
|
+
border-radius: 12px;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
/* Both are flex children of .control-strip now, not positioned boxes. They
|
|
948
|
+
keep their own padding and radius so they still read as separate pills
|
|
949
|
+
inside the one row. */
|
|
950
|
+
.view-mode-bar {
|
|
951
|
+
display: flex;
|
|
952
|
+
align-items: center;
|
|
953
|
+
gap: 2px;
|
|
954
|
+
padding: 4px;
|
|
955
|
+
border-radius: 10px;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
/* Beside the view-mode bar: these act on what the pane it selects is showing,
|
|
959
|
+
so they read as belonging to it. The badge the follow toggle arms is drawn
|
|
960
|
+
inside the pane itself, not here. */
|
|
961
|
+
.camera-view-bar {
|
|
962
|
+
display: flex;
|
|
963
|
+
align-items: center;
|
|
964
|
+
gap: 4px;
|
|
965
|
+
padding: 4px;
|
|
966
|
+
border-radius: 10px;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
/* Armed follow is the GIZMO's yellow and not the accent: it is the same subject
|
|
970
|
+
as the frustum and the in-pane badge, and an accent wash here would say
|
|
971
|
+
"selected" about a mode. */
|
|
972
|
+
.camera-view-bar .btn.is-armed {
|
|
973
|
+
background: rgba(234, 179, 8, 0.18);
|
|
974
|
+
border-color: rgba(234, 179, 8, 0.5);
|
|
975
|
+
color: var(--frustum);
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
.tool-button {
|
|
979
|
+
display: inline-flex;
|
|
980
|
+
align-items: center;
|
|
981
|
+
justify-content: center;
|
|
982
|
+
width: 32px;
|
|
983
|
+
height: 32px;
|
|
984
|
+
font: inherit;
|
|
985
|
+
font-size: 14px;
|
|
986
|
+
color: var(--text-dim);
|
|
987
|
+
background: transparent;
|
|
988
|
+
border: 0;
|
|
989
|
+
border-radius: 8px;
|
|
990
|
+
cursor: pointer;
|
|
991
|
+
transition: background 120ms ease, color 120ms ease;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
.tool-button:hover {
|
|
995
|
+
background: var(--control);
|
|
996
|
+
color: var(--text);
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
/* The armed tool is a soft accent wash, not a solid slab. A saturated fill on
|
|
1000
|
+
a 32px button next to seven neutral ones reads as an error badge. */
|
|
1001
|
+
.tool-button.is-armed {
|
|
1002
|
+
background: var(--accent-soft);
|
|
1003
|
+
color: #cfe4ff;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
/* Animate mode is a MODE, not a tool, so it gets the warm colour rather than
|
|
1007
|
+
the accent — the whole point is that it does not look like an armed tool. */
|
|
1008
|
+
.tool-button.tool-animate[aria-pressed="true"] {
|
|
1009
|
+
background: rgba(245, 165, 36, 0.16);
|
|
1010
|
+
color: var(--accent-warm);
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
.tool-separator {
|
|
1014
|
+
width: 1px;
|
|
1015
|
+
align-self: stretch;
|
|
1016
|
+
margin: 4px 4px;
|
|
1017
|
+
background: var(--border);
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
.tool-flyout-anchor {
|
|
1021
|
+
position: relative;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
.tool-flyout {
|
|
1025
|
+
position: absolute;
|
|
1026
|
+
bottom: calc(100% + 8px);
|
|
1027
|
+
left: 0;
|
|
1028
|
+
display: flex;
|
|
1029
|
+
flex-direction: column;
|
|
1030
|
+
gap: 2px;
|
|
1031
|
+
padding: 5px;
|
|
1032
|
+
border-radius: 10px;
|
|
1033
|
+
white-space: nowrap;
|
|
1034
|
+
z-index: 1;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
/* A flyout row is a LABELLED row, so it sizes to its text. `.tool-button`'s
|
|
1038
|
+
32px square is for the dock's icon strip, and a label inside one overflows
|
|
1039
|
+
its own background. */
|
|
1040
|
+
.tool-flyout .tool-button {
|
|
1041
|
+
width: auto;
|
|
1042
|
+
min-width: 32px;
|
|
1043
|
+
justify-content: flex-start;
|
|
1044
|
+
gap: 8px;
|
|
1045
|
+
padding: 0 10px;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
.tool-flyout .tool-button:disabled {
|
|
1049
|
+
opacity: 0.4;
|
|
1050
|
+
cursor: default;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
/* ---- timeline dock ------------------------------------------------------ */
|
|
1054
|
+
/* Right edge clears the inspector rather than sliding under it: both are
|
|
1055
|
+
`position: fixed`, so the dock has no way to know the rail is there and a
|
|
1056
|
+
plain `right: 12px` put the last two seconds of every timeline behind the
|
|
1057
|
+
effects list. 288 (panel) + 12 (its gutter) + 12 (the gap between them). */
|
|
1058
|
+
.timeline-dock {
|
|
1059
|
+
position: fixed;
|
|
1060
|
+
left: calc(var(--rail-w) + 24px);
|
|
1061
|
+
right: calc(var(--inspector-w) + 24px);
|
|
1062
|
+
/* Sits on the bottom edge now that the tool dock has left it. */
|
|
1063
|
+
bottom: 12px;
|
|
1064
|
+
z-index: 11;
|
|
1065
|
+
display: flex;
|
|
1066
|
+
flex-direction: column;
|
|
1067
|
+
/* Dragged, in px. The fallback is the old 42vh, so a dock that somehow has
|
|
1068
|
+
no variable still lands somewhere sane rather than at zero. */
|
|
1069
|
+
max-height: var(--dock-h, 42vh);
|
|
1070
|
+
overflow: hidden;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
.timeline-dock.is-collapsed {
|
|
1074
|
+
max-height: none;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
.dock-head {
|
|
1078
|
+
display: flex;
|
|
1079
|
+
align-items: center;
|
|
1080
|
+
gap: 8px;
|
|
1081
|
+
height: 34px;
|
|
1082
|
+
padding: 0 10px;
|
|
1083
|
+
border-bottom: 1px solid var(--border-soft);
|
|
1084
|
+
flex: none;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
.dock-collapse {
|
|
1088
|
+
font: inherit;
|
|
1089
|
+
color: var(--text-dim);
|
|
1090
|
+
background: transparent;
|
|
1091
|
+
border: 0;
|
|
1092
|
+
cursor: pointer;
|
|
1093
|
+
padding: 2px 4px;
|
|
1094
|
+
border-radius: 5px;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
.dock-collapse:hover {
|
|
1098
|
+
color: var(--text);
|
|
1099
|
+
background: var(--control);
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
.dock-title {
|
|
1103
|
+
color: var(--text-dim);
|
|
1104
|
+
font-weight: 500;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
.dock-play,
|
|
1108
|
+
.dock-fit {
|
|
1109
|
+
font: inherit;
|
|
1110
|
+
color: var(--text-dim);
|
|
1111
|
+
background: transparent;
|
|
1112
|
+
border: 0;
|
|
1113
|
+
cursor: pointer;
|
|
1114
|
+
padding: 2px 6px;
|
|
1115
|
+
border-radius: 5px;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
.dock-play:hover,
|
|
1119
|
+
.dock-fit:hover {
|
|
1120
|
+
color: var(--text);
|
|
1121
|
+
background: var(--control);
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
/* The one number the author reads while scrubbing, so it is the biggest thing
|
|
1125
|
+
in the head and it is monospaced — a proportional readout jitters sideways
|
|
1126
|
+
on every frame and reads as movement that is not there. */
|
|
1127
|
+
.dock-time {
|
|
1128
|
+
font: var(--mono);
|
|
1129
|
+
font-size: 12px;
|
|
1130
|
+
color: var(--text);
|
|
1131
|
+
min-width: 56px;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
.dock-span {
|
|
1135
|
+
font: var(--mono);
|
|
1136
|
+
font-size: 11px;
|
|
1137
|
+
color: var(--text-faint);
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
/* MONITORING, pushed to the right of the head beside the frame readout, so it
|
|
1141
|
+
reads as a property of this browser tab rather than of the shot. */
|
|
1142
|
+
.dock-mute {
|
|
1143
|
+
font: inherit;
|
|
1144
|
+
font-size: 11px;
|
|
1145
|
+
color: var(--text-dim);
|
|
1146
|
+
background: transparent;
|
|
1147
|
+
border: 0;
|
|
1148
|
+
cursor: pointer;
|
|
1149
|
+
padding: 2px 4px;
|
|
1150
|
+
border-radius: 5px;
|
|
1151
|
+
margin-left: auto;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
.dock-mute:hover,
|
|
1155
|
+
.dock-mute[aria-pressed="true"] {
|
|
1156
|
+
color: var(--text);
|
|
1157
|
+
background: var(--control);
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
.dock-volume {
|
|
1161
|
+
width: 72px;
|
|
1162
|
+
accent-color: var(--accent);
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
.dock-readout {
|
|
1166
|
+
color: var(--text-faint);
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
.dock-body {
|
|
1170
|
+
display: flex;
|
|
1171
|
+
flex-direction: column;
|
|
1172
|
+
min-height: 0;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
/* THE `hidden` ATTRIBUTE ONLY WORKS IF THIS RULE EXISTS. `[hidden]
|
|
1176
|
+
{ display: none }` is a USER-AGENT rule and the author `display: flex`
|
|
1177
|
+
above outranks it, so a collapsed dock kept every row laid out — and
|
|
1178
|
+
`.timeline-dock.is-collapsed` drops the `max-height` cap on purpose, on the
|
|
1179
|
+
assumption the body is gone. On a document with hundreds of layers the two
|
|
1180
|
+
compounded: the dock grew to full content height and, anchored at
|
|
1181
|
+
`bottom: 12px`, covered the stage. Focus mode collapses the dock on entering
|
|
1182
|
+
the working view, which is exactly where it was worst.
|
|
1183
|
+
|
|
1184
|
+
`dockCollapse.test.tsx` asserted the ATTRIBUTE, which was always correct —
|
|
1185
|
+
jsdom loads no stylesheet, so nothing in the suite could see that it meant
|
|
1186
|
+
nothing. That test now reads this file. */
|
|
1187
|
+
.dock-body[hidden] {
|
|
1188
|
+
display: none;
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
.dock-scroll {
|
|
1192
|
+
overflow: auto;
|
|
1193
|
+
min-height: 0;
|
|
1194
|
+
padding-bottom: 6px;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
.dock-ruler {
|
|
1198
|
+
position: relative;
|
|
1199
|
+
height: 24px;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
/* ── THE RULER'S OWN RULES ───────────────────────────────────────────────
|
|
1203
|
+
*
|
|
1204
|
+
* `Ruler` comes from @dailies-studio/timeline but its CSS lives in
|
|
1205
|
+
* canvas-editor's LOCAL stylesheet, so this chrome rendered the component
|
|
1206
|
+
* with no rules at all: every tick stacked at x=0 and the ruler read as the
|
|
1207
|
+
* single run-on number "0102030405060". Restated here rather than moved into
|
|
1208
|
+
* the shared sheet, because this lane's timeline is a BLOCK timeline and its
|
|
1209
|
+
* ruler is shorter and quieter than the canvas dope sheet's. */
|
|
1210
|
+
.tl-ruler {
|
|
1211
|
+
position: relative;
|
|
1212
|
+
height: 100%;
|
|
1213
|
+
overflow: hidden;
|
|
1214
|
+
cursor: ew-resize;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
.tl-tick {
|
|
1218
|
+
position: absolute;
|
|
1219
|
+
top: 0;
|
|
1220
|
+
bottom: 0;
|
|
1221
|
+
padding-left: 6px;
|
|
1222
|
+
font: var(--mono);
|
|
1223
|
+
font-size: 10px;
|
|
1224
|
+
line-height: 20px;
|
|
1225
|
+
color: var(--text-faint);
|
|
1226
|
+
pointer-events: none;
|
|
1227
|
+
white-space: nowrap;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
.tl-tick::before {
|
|
1231
|
+
content: "";
|
|
1232
|
+
position: absolute;
|
|
1233
|
+
left: 0;
|
|
1234
|
+
bottom: 4px;
|
|
1235
|
+
width: 1px;
|
|
1236
|
+
height: 6px;
|
|
1237
|
+
background: var(--border);
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
.dock-playhead,
|
|
1241
|
+
/* The cut marker: a small diamond ON the seam, hollow for a hard cut and
|
|
1242
|
+
filled when the shot before it blends. Centred by a half-width translate so
|
|
1243
|
+
it sits on the boundary rather than after it. Below the playhead (z 4) so the
|
|
1244
|
+
frame you are on always wins. */
|
|
1245
|
+
.scene-cut {
|
|
1246
|
+
position: absolute;
|
|
1247
|
+
top: 50%;
|
|
1248
|
+
width: 9px;
|
|
1249
|
+
height: 9px;
|
|
1250
|
+
padding: 0;
|
|
1251
|
+
transform: translate(-50%, -50%) rotate(45deg);
|
|
1252
|
+
border: 1px solid var(--text-faint);
|
|
1253
|
+
border-radius: 1px;
|
|
1254
|
+
background: var(--panel);
|
|
1255
|
+
cursor: pointer;
|
|
1256
|
+
z-index: 3;
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
.scene-cut:hover {
|
|
1260
|
+
border-color: var(--text);
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
.scene-cut.is-blend {
|
|
1264
|
+
border-color: var(--accent);
|
|
1265
|
+
background: var(--accent);
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
.scene-playhead {
|
|
1269
|
+
position: absolute;
|
|
1270
|
+
top: 0;
|
|
1271
|
+
bottom: 0;
|
|
1272
|
+
width: 2px;
|
|
1273
|
+
background: var(--playhead);
|
|
1274
|
+
pointer-events: none;
|
|
1275
|
+
z-index: 4;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
/* The playhead's grab head, so the line reads as a control rather than a
|
|
1279
|
+
stray rule. Only on the ruler copy; the track copy stays a bare line. */
|
|
1280
|
+
.dock-ruler .dock-playhead::after {
|
|
1281
|
+
content: "";
|
|
1282
|
+
position: absolute;
|
|
1283
|
+
top: 0;
|
|
1284
|
+
left: 50%;
|
|
1285
|
+
transform: translateX(-50%);
|
|
1286
|
+
width: 9px;
|
|
1287
|
+
height: 9px;
|
|
1288
|
+
border-radius: 3px;
|
|
1289
|
+
background: var(--playhead);
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
.dock-tracks {
|
|
1293
|
+
display: flex;
|
|
1294
|
+
flex-direction: column;
|
|
1295
|
+
position: relative;
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
/* One overlay for everything drawn THROUGH the tracks: the scene boundaries,
|
|
1299
|
+
the snap guide and the playhead. It starts where the time area does and takes
|
|
1300
|
+
no pointer events, so a boundary line can never eat a bar's drag. */
|
|
1301
|
+
.dock-overlay {
|
|
1302
|
+
position: absolute;
|
|
1303
|
+
top: 0;
|
|
1304
|
+
bottom: 0;
|
|
1305
|
+
right: 0;
|
|
1306
|
+
pointer-events: none;
|
|
1307
|
+
z-index: 3;
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
.dock-boundary {
|
|
1311
|
+
position: absolute;
|
|
1312
|
+
top: 0;
|
|
1313
|
+
bottom: 0;
|
|
1314
|
+
width: 1px;
|
|
1315
|
+
background: var(--border);
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
/* The snap guide is the accent, and it only exists while a drag is ON a target
|
|
1319
|
+
— a guide that showed for a drag that merely rounded would teach the author
|
|
1320
|
+
that snapping is always on top of them. */
|
|
1321
|
+
.dock-snap-guide {
|
|
1322
|
+
position: absolute;
|
|
1323
|
+
top: 0;
|
|
1324
|
+
bottom: 0;
|
|
1325
|
+
width: 1px;
|
|
1326
|
+
background: var(--accent);
|
|
1327
|
+
box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 30%, transparent);
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
/* Groups are separated by the boundary line above; this is the horizontal half
|
|
1331
|
+
of the same statement, for a reader scanning the label column. */
|
|
1332
|
+
.dock-scene-group + .dock-scene-group {
|
|
1333
|
+
border-top: 1px solid var(--border);
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
/* ---- scene strip -------------------------------------------------------- */
|
|
1337
|
+
/* Slim, and it SCROLLS WITH THE TRACKS: its bars and the boundary lines through
|
|
1338
|
+
the track area are the same prefix sum, so anything that moved one without
|
|
1339
|
+
the other would be drawing two different timelines. */
|
|
1340
|
+
.scene-strip {
|
|
1341
|
+
display: flex;
|
|
1342
|
+
align-items: stretch;
|
|
1343
|
+
height: 26px;
|
|
1344
|
+
border-bottom: 1px solid var(--border);
|
|
1345
|
+
flex: none;
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
.scene-strip-label {
|
|
1349
|
+
display: flex;
|
|
1350
|
+
align-items: center;
|
|
1351
|
+
gap: 6px;
|
|
1352
|
+
padding-left: 10px;
|
|
1353
|
+
color: var(--text-faint);
|
|
1354
|
+
text-transform: uppercase;
|
|
1355
|
+
letter-spacing: 0.06em;
|
|
1356
|
+
font-size: 10px;
|
|
1357
|
+
font-weight: 600;
|
|
1358
|
+
flex: none;
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
.scene-strip-add {
|
|
1362
|
+
font: inherit;
|
|
1363
|
+
font-size: 13px;
|
|
1364
|
+
line-height: 1;
|
|
1365
|
+
color: var(--text-faint);
|
|
1366
|
+
background: transparent;
|
|
1367
|
+
border: 0;
|
|
1368
|
+
padding: 2px 6px;
|
|
1369
|
+
border-radius: 4px;
|
|
1370
|
+
cursor: pointer;
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
.scene-strip-add:hover {
|
|
1374
|
+
color: var(--text);
|
|
1375
|
+
background: var(--control);
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
.scene-strip-lane {
|
|
1379
|
+
position: relative;
|
|
1380
|
+
flex: 1;
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
.scene-bar {
|
|
1384
|
+
position: absolute;
|
|
1385
|
+
top: 4px;
|
|
1386
|
+
bottom: 4px;
|
|
1387
|
+
display: flex;
|
|
1388
|
+
align-items: center;
|
|
1389
|
+
padding: 0 8px;
|
|
1390
|
+
border-radius: 6px;
|
|
1391
|
+
background: color-mix(in srgb, var(--scene-bar) 14%, transparent);
|
|
1392
|
+
border: 1px solid color-mix(in srgb, var(--scene-bar) 55%, transparent);
|
|
1393
|
+
color: color-mix(in srgb, var(--scene-bar) 45%, #ffffff);
|
|
1394
|
+
cursor: pointer;
|
|
1395
|
+
overflow: hidden;
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
.scene-bar:hover {
|
|
1399
|
+
background: color-mix(in srgb, var(--scene-bar) 22%, transparent);
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
.scene-bar.is-current {
|
|
1403
|
+
border-color: var(--scene-bar);
|
|
1404
|
+
background: color-mix(in srgb, var(--scene-bar) 26%, transparent);
|
|
1405
|
+
color: #fff;
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
.scene-bar-name {
|
|
1409
|
+
white-space: nowrap;
|
|
1410
|
+
overflow: hidden;
|
|
1411
|
+
text-overflow: ellipsis;
|
|
1412
|
+
font-weight: 500;
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
.scene-trim {
|
|
1416
|
+
position: absolute;
|
|
1417
|
+
right: 0;
|
|
1418
|
+
top: 0;
|
|
1419
|
+
bottom: 0;
|
|
1420
|
+
width: 7px;
|
|
1421
|
+
cursor: col-resize;
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
/* ---- lifespan rows ------------------------------------------------------ */
|
|
1425
|
+
/* The layer's own bar: taller and quieter than a block bar, because it is the
|
|
1426
|
+
thing a block sits INSIDE. Solid-ish fill rather than the block's outline,
|
|
1427
|
+
so a row reads as one object with intervals on it rather than as a pile of
|
|
1428
|
+
equal-weight rectangles. */
|
|
1429
|
+
.lifespan-row {
|
|
1430
|
+
display: flex;
|
|
1431
|
+
align-items: stretch;
|
|
1432
|
+
height: 28px;
|
|
1433
|
+
border-bottom: 1px solid var(--border-soft);
|
|
1434
|
+
/* Fixed-height rows are isolation boundaries: a style change inside one
|
|
1435
|
+
(a bar drag, a hover) must not invalidate layout or repaint outside it.
|
|
1436
|
+
Legal here — the containment ban (styles.css:5) covers only ancestors of
|
|
1437
|
+
the stage iframe, and the dock is its sibling. */
|
|
1438
|
+
contain: layout paint;
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
.lifespan-row.is-selected {
|
|
1442
|
+
background: var(--accent-quiet);
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
.lifespan-label-cell {
|
|
1446
|
+
display: flex;
|
|
1447
|
+
align-items: center;
|
|
1448
|
+
flex: none;
|
|
1449
|
+
overflow: hidden;
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
.lifespan-disclosure {
|
|
1453
|
+
font: inherit;
|
|
1454
|
+
font-size: 9px;
|
|
1455
|
+
line-height: 1;
|
|
1456
|
+
color: var(--text-faint);
|
|
1457
|
+
background: transparent;
|
|
1458
|
+
border: 0;
|
|
1459
|
+
padding: 4px 4px;
|
|
1460
|
+
cursor: pointer;
|
|
1461
|
+
flex: none;
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
.lifespan-disclosure:hover {
|
|
1465
|
+
color: var(--text);
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
.lifespan-name {
|
|
1469
|
+
font: inherit;
|
|
1470
|
+
color: var(--text-dim);
|
|
1471
|
+
background: transparent;
|
|
1472
|
+
border: 0;
|
|
1473
|
+
text-align: left;
|
|
1474
|
+
padding: 0 6px;
|
|
1475
|
+
cursor: pointer;
|
|
1476
|
+
flex: 1;
|
|
1477
|
+
min-width: 0;
|
|
1478
|
+
white-space: nowrap;
|
|
1479
|
+
overflow: hidden;
|
|
1480
|
+
text-overflow: ellipsis;
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
.lifespan-name:hover,
|
|
1484
|
+
.lifespan-row.is-selected .lifespan-name {
|
|
1485
|
+
color: var(--text);
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
.lifespan-lane {
|
|
1489
|
+
position: relative;
|
|
1490
|
+
flex: 1;
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
.lifespan-bar {
|
|
1494
|
+
position: absolute;
|
|
1495
|
+
top: 5px;
|
|
1496
|
+
bottom: 5px;
|
|
1497
|
+
display: flex;
|
|
1498
|
+
align-items: center;
|
|
1499
|
+
padding: 0 10px;
|
|
1500
|
+
border-radius: 7px;
|
|
1501
|
+
background: color-mix(in srgb, var(--text) 10%, transparent);
|
|
1502
|
+
border: 1px solid color-mix(in srgb, var(--text) 22%, transparent);
|
|
1503
|
+
color: var(--text-dim);
|
|
1504
|
+
cursor: grab;
|
|
1505
|
+
overflow: hidden;
|
|
1506
|
+
white-space: nowrap;
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
.lifespan-bar:hover {
|
|
1510
|
+
background: color-mix(in srgb, var(--text) 16%, transparent);
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
/* SELECTION IS THE ACCENT, here and nowhere else in the lane: it is the one
|
|
1514
|
+
colour that means "this is the thing the inspector is about". */
|
|
1515
|
+
.lifespan-bar.is-selected {
|
|
1516
|
+
background: color-mix(in srgb, var(--accent) 22%, transparent);
|
|
1517
|
+
border-color: var(--accent);
|
|
1518
|
+
color: #fff;
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
.lifespan-bar-name {
|
|
1522
|
+
overflow: hidden;
|
|
1523
|
+
text-overflow: ellipsis;
|
|
1524
|
+
pointer-events: none;
|
|
1525
|
+
font-weight: 500;
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
.lifespan-handle {
|
|
1529
|
+
position: absolute;
|
|
1530
|
+
top: 0;
|
|
1531
|
+
bottom: 0;
|
|
1532
|
+
width: 8px;
|
|
1533
|
+
cursor: col-resize;
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
.lifespan-handle-in {
|
|
1537
|
+
left: 0;
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
.lifespan-handle-out {
|
|
1541
|
+
right: 0;
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
/* ---- block rows --------------------------------------------------------- */
|
|
1545
|
+
.block-row {
|
|
1546
|
+
display: flex;
|
|
1547
|
+
align-items: stretch;
|
|
1548
|
+
height: 26px;
|
|
1549
|
+
border-bottom: 1px solid var(--border-soft);
|
|
1550
|
+
/* Same isolation boundary as .lifespan-row, same legality note. */
|
|
1551
|
+
contain: layout paint;
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
.block-row.is-selected {
|
|
1555
|
+
background: var(--accent-quiet);
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
/* The label column: the name, and on the camera row a create button beside it.
|
|
1559
|
+
Fixed width from the dock's LABEL_WIDTH, so the lanes of every row still
|
|
1560
|
+
start on the same x as the ruler. */
|
|
1561
|
+
.block-row-label-cell {
|
|
1562
|
+
display: flex;
|
|
1563
|
+
align-items: stretch;
|
|
1564
|
+
flex: none;
|
|
1565
|
+
overflow: hidden;
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
.block-row-label {
|
|
1569
|
+
font: inherit;
|
|
1570
|
+
color: var(--text-dim);
|
|
1571
|
+
background: transparent;
|
|
1572
|
+
border: 0;
|
|
1573
|
+
text-align: left;
|
|
1574
|
+
padding: 0 10px;
|
|
1575
|
+
cursor: pointer;
|
|
1576
|
+
flex: 1;
|
|
1577
|
+
min-width: 0;
|
|
1578
|
+
white-space: nowrap;
|
|
1579
|
+
overflow: hidden;
|
|
1580
|
+
text-overflow: ellipsis;
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
/* Quiet until the row is hovered: it is a create button on a lane the author
|
|
1584
|
+
spends most of their time reading, and a permanently lit one reads as the
|
|
1585
|
+
row's subject. */
|
|
1586
|
+
.block-row-add {
|
|
1587
|
+
font: inherit;
|
|
1588
|
+
font-size: 11px;
|
|
1589
|
+
color: var(--text-faint);
|
|
1590
|
+
background: transparent;
|
|
1591
|
+
border: 0;
|
|
1592
|
+
padding: 0 8px;
|
|
1593
|
+
cursor: pointer;
|
|
1594
|
+
flex: none;
|
|
1595
|
+
white-space: nowrap;
|
|
1596
|
+
opacity: 0;
|
|
1597
|
+
transition: opacity 120ms ease, color 120ms ease;
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
.block-row:hover .block-row-add,
|
|
1601
|
+
.block-row-add:focus-visible {
|
|
1602
|
+
opacity: 1;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
.block-row-add:hover {
|
|
1606
|
+
color: var(--text);
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
.block-row-label:hover {
|
|
1610
|
+
color: var(--text);
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
.block-row.is-selected .block-row-label {
|
|
1614
|
+
color: var(--text);
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
.block-row-lane {
|
|
1618
|
+
position: relative;
|
|
1619
|
+
flex: 1;
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
/* Dark fill, bright border — Raylight's block. A solid bar per block turns a
|
|
1623
|
+
busy scene into a colour field where nothing is readable; an outline keeps
|
|
1624
|
+
forty of them distinct and lets overlaps show through. */
|
|
1625
|
+
.block-bar {
|
|
1626
|
+
position: absolute;
|
|
1627
|
+
top: 4px;
|
|
1628
|
+
bottom: 4px;
|
|
1629
|
+
display: flex;
|
|
1630
|
+
align-items: center;
|
|
1631
|
+
padding: 0 8px;
|
|
1632
|
+
border-radius: 6px;
|
|
1633
|
+
background: color-mix(in srgb, var(--block-during) 14%, transparent);
|
|
1634
|
+
border: 1px solid color-mix(in srgb, var(--block-during) 60%, transparent);
|
|
1635
|
+
color: color-mix(in srgb, var(--block-during) 40%, #ffffff);
|
|
1636
|
+
cursor: grab;
|
|
1637
|
+
overflow: visible;
|
|
1638
|
+
white-space: nowrap;
|
|
1639
|
+
transition: background 120ms ease;
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
.block-bar:hover {
|
|
1643
|
+
background: color-mix(in srgb, var(--block-during) 24%, transparent);
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
.block-bar.slot-in {
|
|
1647
|
+
background: color-mix(in srgb, var(--block-in) 14%, transparent);
|
|
1648
|
+
border-color: color-mix(in srgb, var(--block-in) 60%, transparent);
|
|
1649
|
+
color: color-mix(in srgb, var(--block-in) 40%, #ffffff);
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
.block-bar.slot-in:hover {
|
|
1653
|
+
background: color-mix(in srgb, var(--block-in) 24%, transparent);
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
.block-bar.slot-out {
|
|
1657
|
+
background: color-mix(in srgb, var(--block-out) 14%, transparent);
|
|
1658
|
+
border-color: color-mix(in srgb, var(--block-out) 60%, transparent);
|
|
1659
|
+
color: color-mix(in srgb, var(--block-out) 40%, #ffffff);
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
.block-bar.slot-out:hover {
|
|
1663
|
+
background: color-mix(in srgb, var(--block-out) 24%, transparent);
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
.block-bar.is-selected {
|
|
1667
|
+
outline: 1px solid #fff;
|
|
1668
|
+
outline-offset: 1px;
|
|
1669
|
+
color: #fff;
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
/* A block whose window has fallen outside its layer's lifespan. KEPT, dimmed,
|
|
1673
|
+
and the dead part greyed in place — shortening a lifespan is a trim, not a
|
|
1674
|
+
request to throw an animation away, and the author needs to see how far to
|
|
1675
|
+
pull the edge back. */
|
|
1676
|
+
.block-bar.is-clipped {
|
|
1677
|
+
opacity: 0.5;
|
|
1678
|
+
border-style: dashed;
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
.block-dead {
|
|
1682
|
+
position: absolute;
|
|
1683
|
+
top: 0;
|
|
1684
|
+
bottom: 0;
|
|
1685
|
+
pointer-events: none;
|
|
1686
|
+
background: repeating-linear-gradient(
|
|
1687
|
+
-45deg,
|
|
1688
|
+
color-mix(in srgb, var(--text) 16%, transparent) 0 3px,
|
|
1689
|
+
transparent 3px 6px
|
|
1690
|
+
);
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
.block-label {
|
|
1694
|
+
overflow: hidden;
|
|
1695
|
+
text-overflow: ellipsis;
|
|
1696
|
+
pointer-events: none;
|
|
1697
|
+
font-weight: 500;
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
.block-handle {
|
|
1701
|
+
position: absolute;
|
|
1702
|
+
top: 0;
|
|
1703
|
+
bottom: 0;
|
|
1704
|
+
width: 7px;
|
|
1705
|
+
cursor: col-resize;
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
.block-handle-in {
|
|
1709
|
+
left: -1px;
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
.block-handle-out {
|
|
1713
|
+
right: -1px;
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
/* A spring keeps moving past its own window. The tail says so, and the OUT
|
|
1717
|
+
handle is absent because dragging it would clip rather than retime. */
|
|
1718
|
+
.block-spring-tail {
|
|
1719
|
+
position: absolute;
|
|
1720
|
+
right: -18px;
|
|
1721
|
+
top: 50%;
|
|
1722
|
+
width: 18px;
|
|
1723
|
+
border-top: 1px dashed currentColor;
|
|
1724
|
+
opacity: 0.6;
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
/* ---- channel rows (one animated property per row — the Lottie read) ------ */
|
|
1728
|
+
.channel-row {
|
|
1729
|
+
display: flex;
|
|
1730
|
+
align-items: stretch;
|
|
1731
|
+
height: 20px;
|
|
1732
|
+
border-bottom: 1px solid var(--border-soft);
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
.channel-label-cell {
|
|
1736
|
+
display: flex;
|
|
1737
|
+
align-items: center;
|
|
1738
|
+
gap: 6px;
|
|
1739
|
+
flex: none;
|
|
1740
|
+
overflow: hidden;
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
.channel-name {
|
|
1744
|
+
color: var(--text-faint);
|
|
1745
|
+
font-size: 10px;
|
|
1746
|
+
white-space: nowrap;
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
/* The resolved value at the playhead. Tabular, so playback reads as a counter
|
|
1750
|
+
rather than a word that keeps changing width. */
|
|
1751
|
+
.channel-value {
|
|
1752
|
+
margin-left: auto;
|
|
1753
|
+
padding-right: 8px;
|
|
1754
|
+
color: var(--text-dim);
|
|
1755
|
+
font-size: 10px;
|
|
1756
|
+
font-variant-numeric: tabular-nums;
|
|
1757
|
+
white-space: nowrap;
|
|
1758
|
+
overflow: hidden;
|
|
1759
|
+
text-overflow: ellipsis;
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
.channel-lane {
|
|
1763
|
+
position: relative;
|
|
1764
|
+
flex: 1;
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
/* One block's footprint on one channel: two keyframe diamonds joined by a
|
|
1768
|
+
line, in the slot's hue — the same in/during/out vocabulary the block bars
|
|
1769
|
+
speak, one register quieter. */
|
|
1770
|
+
.channel-span {
|
|
1771
|
+
position: absolute;
|
|
1772
|
+
top: 0;
|
|
1773
|
+
bottom: 0;
|
|
1774
|
+
--slot: var(--block-during);
|
|
1775
|
+
cursor: grab;
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
.channel-span.slot-in {
|
|
1779
|
+
--slot: var(--block-in);
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
.channel-span.slot-out {
|
|
1783
|
+
--slot: var(--block-out);
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
.channel-connector {
|
|
1787
|
+
position: absolute;
|
|
1788
|
+
left: 0;
|
|
1789
|
+
right: 0;
|
|
1790
|
+
top: 50%;
|
|
1791
|
+
border-top: 1px solid color-mix(in srgb, var(--slot) 55%, transparent);
|
|
1792
|
+
pointer-events: none;
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
.channel-span:hover .channel-connector {
|
|
1796
|
+
border-top-color: var(--slot);
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
/* SOLID diamonds, the Lottie/Figma mark. Filled with the slot's hue so a
|
|
1800
|
+
keyframe reads as a keyframe at a glance; selection turns them white, the
|
|
1801
|
+
one colour that means "the inspector is about this". */
|
|
1802
|
+
.channel-key {
|
|
1803
|
+
position: absolute;
|
|
1804
|
+
top: 50%;
|
|
1805
|
+
width: 9px;
|
|
1806
|
+
height: 9px;
|
|
1807
|
+
transform: translate(-50%, -50%) rotate(45deg);
|
|
1808
|
+
background: var(--slot);
|
|
1809
|
+
border: 1px solid color-mix(in srgb, var(--slot) 55%, #000);
|
|
1810
|
+
border-radius: 1px;
|
|
1811
|
+
cursor: col-resize;
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
.channel-key:hover {
|
|
1815
|
+
background: color-mix(in srgb, var(--slot) 70%, #fff);
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
.channel-key-in {
|
|
1819
|
+
left: 0;
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
.channel-key-out {
|
|
1823
|
+
left: 100%;
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
.channel-span.is-selected .channel-key {
|
|
1827
|
+
background: #fff;
|
|
1828
|
+
border-color: #fff;
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
.channel-span.is-selected .channel-connector {
|
|
1832
|
+
border-top-color: var(--slot);
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
/* The part of a span the lifespan no longer covers: kept, dashed in place —
|
|
1836
|
+
the block lane's "drawn dead" rule, restated on a line. */
|
|
1837
|
+
.channel-span.is-clipped .channel-key {
|
|
1838
|
+
opacity: 0.55;
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
.channel-dead {
|
|
1842
|
+
position: absolute;
|
|
1843
|
+
top: 50%;
|
|
1844
|
+
border-top: 1px dashed color-mix(in srgb, var(--text) 45%, transparent);
|
|
1845
|
+
pointer-events: none;
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
.channel-spring-tail {
|
|
1849
|
+
position: absolute;
|
|
1850
|
+
left: 100%;
|
|
1851
|
+
top: 50%;
|
|
1852
|
+
width: 14px;
|
|
1853
|
+
border-top: 1px dashed color-mix(in srgb, var(--slot) 70%, transparent);
|
|
1854
|
+
pointer-events: none;
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
/* The expanded-but-empty layer: say where the first keyframe comes from. */
|
|
1858
|
+
.channel-empty-hint {
|
|
1859
|
+
display: flex;
|
|
1860
|
+
align-items: center;
|
|
1861
|
+
padding-left: 10px;
|
|
1862
|
+
color: var(--text-faint);
|
|
1863
|
+
font-size: 10px;
|
|
1864
|
+
white-space: nowrap;
|
|
1865
|
+
overflow: hidden;
|
|
1866
|
+
text-overflow: ellipsis;
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
/* ---- audio lane --------------------------------------------------------- */
|
|
1870
|
+
/* A DISTINCT COLOURED BAND under the video rows. Taller than a block row,
|
|
1871
|
+
because the waveform inside it is the thing the author is reading — a
|
|
1872
|
+
28px-high envelope of a voiceover shows nothing at all. */
|
|
1873
|
+
.audio-lane {
|
|
1874
|
+
display: flex;
|
|
1875
|
+
align-items: stretch;
|
|
1876
|
+
position: relative;
|
|
1877
|
+
height: 38px;
|
|
1878
|
+
border-top: 1px solid var(--border);
|
|
1879
|
+
background: color-mix(in srgb, var(--audio-clip) 4%, transparent);
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
/* A file dragged over the lane. The whole lane lights, not the row under the
|
|
1883
|
+
pointer: the clip lands at the PLAYHEAD (see AudioLane), so highlighting a
|
|
1884
|
+
position would promise a drop point the import does not honour. */
|
|
1885
|
+
.audio-lane.is-over {
|
|
1886
|
+
background: color-mix(in srgb, var(--audio-clip) 14%, transparent);
|
|
1887
|
+
outline: 1px dashed var(--audio-clip);
|
|
1888
|
+
outline-offset: -1px;
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
.audio-label-cell {
|
|
1892
|
+
display: flex;
|
|
1893
|
+
align-items: center;
|
|
1894
|
+
gap: 6px;
|
|
1895
|
+
flex: none;
|
|
1896
|
+
padding-left: 8px;
|
|
1897
|
+
overflow: hidden;
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
.audio-lane-name {
|
|
1901
|
+
color: var(--text-dim);
|
|
1902
|
+
flex: 1;
|
|
1903
|
+
min-width: 0;
|
|
1904
|
+
white-space: nowrap;
|
|
1905
|
+
overflow: hidden;
|
|
1906
|
+
text-overflow: ellipsis;
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
.audio-add {
|
|
1910
|
+
font: inherit;
|
|
1911
|
+
color: var(--text-dim);
|
|
1912
|
+
background: var(--control);
|
|
1913
|
+
border: 0;
|
|
1914
|
+
border-radius: 5px;
|
|
1915
|
+
padding: 1px 7px;
|
|
1916
|
+
margin-right: 6px;
|
|
1917
|
+
cursor: pointer;
|
|
1918
|
+
flex: none;
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
.audio-add:hover:not(:disabled) {
|
|
1922
|
+
color: var(--text);
|
|
1923
|
+
background: var(--control-hover);
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
.audio-track {
|
|
1927
|
+
position: relative;
|
|
1928
|
+
flex: 1;
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
.audio-empty {
|
|
1932
|
+
position: absolute;
|
|
1933
|
+
left: 10px;
|
|
1934
|
+
top: 50%;
|
|
1935
|
+
transform: translateY(-50%);
|
|
1936
|
+
color: var(--text-faint);
|
|
1937
|
+
pointer-events: none;
|
|
1938
|
+
white-space: nowrap;
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
/* Same dark-fill/bright-border grammar as a block bar, in the lane's own hue. */
|
|
1942
|
+
.audio-clip {
|
|
1943
|
+
position: absolute;
|
|
1944
|
+
top: 5px;
|
|
1945
|
+
bottom: 5px;
|
|
1946
|
+
border-radius: 6px;
|
|
1947
|
+
background: color-mix(in srgb, var(--audio-clip) 16%, transparent);
|
|
1948
|
+
border: 1px solid color-mix(in srgb, var(--audio-clip) 65%, transparent);
|
|
1949
|
+
color: color-mix(in srgb, var(--audio-clip) 35%, #ffffff);
|
|
1950
|
+
cursor: grab;
|
|
1951
|
+
overflow: hidden;
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
.audio-clip:hover {
|
|
1955
|
+
background: color-mix(in srgb, var(--audio-clip) 26%, transparent);
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
/* A muted clip is KEPT and dimmed, never hidden: mute is a mix decision the
|
|
1959
|
+
author has to be able to see in order to undo. */
|
|
1960
|
+
.audio-clip.is-muted {
|
|
1961
|
+
opacity: 0.45;
|
|
1962
|
+
border-style: dashed;
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
.audio-wave {
|
|
1966
|
+
position: absolute;
|
|
1967
|
+
inset: 0;
|
|
1968
|
+
width: 100%;
|
|
1969
|
+
height: 100%;
|
|
1970
|
+
pointer-events: none;
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
.audio-fade-curve {
|
|
1974
|
+
position: absolute;
|
|
1975
|
+
inset: 0;
|
|
1976
|
+
width: 100%;
|
|
1977
|
+
height: 100%;
|
|
1978
|
+
pointer-events: none;
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
.audio-fade-curve polyline {
|
|
1982
|
+
stroke: var(--audio-line);
|
|
1983
|
+
stroke-width: 1;
|
|
1984
|
+
vector-effect: non-scaling-stroke;
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
.audio-clip-name {
|
|
1988
|
+
position: absolute;
|
|
1989
|
+
left: 8px;
|
|
1990
|
+
top: 1px;
|
|
1991
|
+
pointer-events: none;
|
|
1992
|
+
font-weight: 500;
|
|
1993
|
+
max-width: calc(100% - 46px);
|
|
1994
|
+
overflow: hidden;
|
|
1995
|
+
text-overflow: ellipsis;
|
|
1996
|
+
white-space: nowrap;
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
/* The rubber band. A hairline across the whole clip with a fat invisible grab
|
|
2000
|
+
area, because a 1px drag target is a control nobody can hit. */
|
|
2001
|
+
.audio-gain-band {
|
|
2002
|
+
position: absolute;
|
|
2003
|
+
left: 0;
|
|
2004
|
+
right: 0;
|
|
2005
|
+
height: 9px;
|
|
2006
|
+
margin-bottom: -4px;
|
|
2007
|
+
cursor: ns-resize;
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
.audio-gain-band::after {
|
|
2011
|
+
content: "";
|
|
2012
|
+
position: absolute;
|
|
2013
|
+
left: 0;
|
|
2014
|
+
right: 0;
|
|
2015
|
+
top: 4px;
|
|
2016
|
+
height: 1px;
|
|
2017
|
+
background: var(--audio-line);
|
|
2018
|
+
opacity: 0.8;
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
.audio-handle {
|
|
2022
|
+
position: absolute;
|
|
2023
|
+
top: 0;
|
|
2024
|
+
bottom: 0;
|
|
2025
|
+
width: 7px;
|
|
2026
|
+
cursor: col-resize;
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
.audio-handle-in {
|
|
2030
|
+
left: -1px;
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
.audio-handle-out {
|
|
2034
|
+
right: -1px;
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
/* The fade corners ride ON the ramp's top end, so the handle is where the
|
|
2038
|
+
author sees the fade finish rather than at the edge of the clip. */
|
|
2039
|
+
.audio-fade {
|
|
2040
|
+
position: absolute;
|
|
2041
|
+
top: -1px;
|
|
2042
|
+
width: 11px;
|
|
2043
|
+
height: 11px;
|
|
2044
|
+
margin-left: -5px;
|
|
2045
|
+
border-radius: 50%;
|
|
2046
|
+
cursor: ew-resize;
|
|
2047
|
+
background: color-mix(in srgb, var(--audio-line) 70%, transparent);
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
.audio-clip-buttons {
|
|
2051
|
+
position: absolute;
|
|
2052
|
+
right: 2px;
|
|
2053
|
+
top: 1px;
|
|
2054
|
+
display: flex;
|
|
2055
|
+
gap: 2px;
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
.audio-clip-btn {
|
|
2059
|
+
font: inherit;
|
|
2060
|
+
font-size: 10px;
|
|
2061
|
+
line-height: 1;
|
|
2062
|
+
color: var(--text-dim);
|
|
2063
|
+
background: transparent;
|
|
2064
|
+
border: 0;
|
|
2065
|
+
padding: 2px 3px;
|
|
2066
|
+
border-radius: 4px;
|
|
2067
|
+
cursor: pointer;
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
.audio-clip-btn:hover {
|
|
2071
|
+
color: var(--text);
|
|
2072
|
+
background: var(--control);
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
.audio-error {
|
|
2076
|
+
position: absolute;
|
|
2077
|
+
left: 8px;
|
|
2078
|
+
bottom: 2px;
|
|
2079
|
+
margin: 0;
|
|
2080
|
+
color: var(--danger-text);
|
|
2081
|
+
pointer-events: none;
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2084
|
+
/* ---- marker lane -------------------------------------------------------- */
|
|
2085
|
+
/* A THIN ORANGE STRIP over the audio. Short on purpose: a marker carries no
|
|
2086
|
+
duration, so there is nothing here that gets more readable with height —
|
|
2087
|
+
every pixel it takes is a pixel off the tracks, which do. */
|
|
2088
|
+
.marker-lane {
|
|
2089
|
+
display: flex;
|
|
2090
|
+
align-items: stretch;
|
|
2091
|
+
position: relative;
|
|
2092
|
+
height: 20px;
|
|
2093
|
+
border-top: 1px solid var(--border);
|
|
2094
|
+
background: color-mix(in srgb, var(--marker) 10%, transparent);
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
.marker-label-cell {
|
|
2098
|
+
display: flex;
|
|
2099
|
+
align-items: center;
|
|
2100
|
+
gap: 6px;
|
|
2101
|
+
flex: none;
|
|
2102
|
+
padding-left: 8px;
|
|
2103
|
+
overflow: hidden;
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2106
|
+
.marker-lane-name {
|
|
2107
|
+
color: var(--text-dim);
|
|
2108
|
+
flex: 1;
|
|
2109
|
+
min-width: 0;
|
|
2110
|
+
white-space: nowrap;
|
|
2111
|
+
overflow: hidden;
|
|
2112
|
+
text-overflow: ellipsis;
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
.marker-add {
|
|
2116
|
+
font: inherit;
|
|
2117
|
+
line-height: 1;
|
|
2118
|
+
color: var(--text-dim);
|
|
2119
|
+
background: var(--control);
|
|
2120
|
+
border: 0;
|
|
2121
|
+
border-radius: 5px;
|
|
2122
|
+
padding: 1px 7px;
|
|
2123
|
+
margin-right: 6px;
|
|
2124
|
+
cursor: pointer;
|
|
2125
|
+
flex: none;
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2128
|
+
.marker-add:hover {
|
|
2129
|
+
color: var(--text);
|
|
2130
|
+
background: var(--control-hover);
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
.marker-track {
|
|
2134
|
+
position: relative;
|
|
2135
|
+
flex: 1;
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
.marker-empty {
|
|
2139
|
+
position: absolute;
|
|
2140
|
+
left: 10px;
|
|
2141
|
+
top: 50%;
|
|
2142
|
+
transform: translateY(-50%);
|
|
2143
|
+
color: var(--text-faint);
|
|
2144
|
+
pointer-events: none;
|
|
2145
|
+
white-space: nowrap;
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
/* CENTRED ON ITS FRAME, which is why the pin is a zero-width anchor with the
|
|
2149
|
+
diamond translated out of it: `left` is the frame's exact x, so a diamond
|
|
2150
|
+
given a width would sit a few px late and drift further as the author zoomed
|
|
2151
|
+
in — the one thing a marker must never do. */
|
|
2152
|
+
.marker-pin {
|
|
2153
|
+
position: absolute;
|
|
2154
|
+
top: 0;
|
|
2155
|
+
bottom: 0;
|
|
2156
|
+
width: 0;
|
|
2157
|
+
display: flex;
|
|
2158
|
+
align-items: center;
|
|
2159
|
+
cursor: grab;
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2162
|
+
.marker-diamond {
|
|
2163
|
+
width: 8px;
|
|
2164
|
+
height: 8px;
|
|
2165
|
+
margin-left: -4px;
|
|
2166
|
+
background: var(--marker-pin);
|
|
2167
|
+
border: 1px solid color-mix(in srgb, var(--marker) 70%, #000000);
|
|
2168
|
+
transform: rotate(45deg);
|
|
2169
|
+
flex: none;
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
.marker-pin.is-selected .marker-diamond {
|
|
2173
|
+
background: var(--marker);
|
|
2174
|
+
border-color: var(--marker-pin);
|
|
2175
|
+
box-shadow: 0 0 0 2px color-mix(in srgb, var(--marker) 45%, transparent);
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
.marker-pin.is-at-playhead .marker-diamond {
|
|
2179
|
+
border-color: var(--playhead);
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
/* HOVER (and selection) only. Always-on labels turn a strip of beats into a
|
|
2183
|
+
wall of overlapping text at any zoom the author actually works at. */
|
|
2184
|
+
.marker-pin-label {
|
|
2185
|
+
display: none;
|
|
2186
|
+
position: absolute;
|
|
2187
|
+
left: 8px;
|
|
2188
|
+
white-space: nowrap;
|
|
2189
|
+
padding: 0 4px;
|
|
2190
|
+
border-radius: 3px;
|
|
2191
|
+
color: var(--text);
|
|
2192
|
+
background: var(--panel);
|
|
2193
|
+
border: 1px solid var(--border);
|
|
2194
|
+
pointer-events: none;
|
|
2195
|
+
z-index: 1;
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2198
|
+
.marker-pin:hover .marker-pin-label,
|
|
2199
|
+
.marker-pin.is-selected .marker-pin-label {
|
|
2200
|
+
display: block;
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
/* ---- inspector ---------------------------------------------------------- */
|
|
2204
|
+
/* One right rail, two occupants. They share the BOX and nothing else: the
|
|
2205
|
+
inspector is a scrolling stack of sections, the agent drawer is a flex
|
|
2206
|
+
column that scrolls only its transcript and pins its composer to the
|
|
2207
|
+
bottom. */
|
|
2208
|
+
.inspector,
|
|
2209
|
+
.shell > .agent-panel {
|
|
2210
|
+
position: fixed;
|
|
2211
|
+
top: 72px;
|
|
2212
|
+
right: 12px;
|
|
2213
|
+
bottom: 12px;
|
|
2214
|
+
width: var(--inspector-w);
|
|
2215
|
+
z-index: 10;
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
.inspector {
|
|
2219
|
+
overflow: auto;
|
|
2220
|
+
padding: 12px;
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
/* `.shell >` for SPECIFICITY, not for structure. agent.css is imported by the
|
|
2224
|
+
panel component and so lands after this file, and its own
|
|
2225
|
+
`.agent-panel { position: relative; height: 100% }` would otherwise win.
|
|
2226
|
+
`height: 100%` on a fixed element resolves against the VIEWPORT, not
|
|
2227
|
+
against the top/bottom box above, so the column ran taller than its own
|
|
2228
|
+
panel and pushed the composer clean off the bottom edge — the drawer looked
|
|
2229
|
+
like it had no input at all. */
|
|
2230
|
+
.shell > .agent-panel {
|
|
2231
|
+
position: fixed;
|
|
2232
|
+
height: auto;
|
|
2233
|
+
overflow: hidden;
|
|
2234
|
+
padding: 0;
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
/* agent.css leaves the title row unpadded because the motion editor wraps it
|
|
2238
|
+
in a padded header. This drawer IS the panel, so the padding belongs here.
|
|
2239
|
+
It also WRAPS: the row carries a label, one button per model, the key
|
|
2240
|
+
control, New and close, which does not fit 288px — unwrapped they overlap
|
|
2241
|
+
and the model names clip to "Op". A second line is cheaper than a rail wide
|
|
2242
|
+
enough to never need one. */
|
|
2243
|
+
.shell > .agent-panel > .agent-title {
|
|
2244
|
+
padding: 10px 12px;
|
|
2245
|
+
border-bottom: 1px solid var(--border-soft);
|
|
2246
|
+
flex: none;
|
|
2247
|
+
flex-wrap: wrap;
|
|
2248
|
+
row-gap: 6px;
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
.shell > .agent-panel .agent-models {
|
|
2252
|
+
flex: 1 1 100%;
|
|
2253
|
+
order: 1;
|
|
2254
|
+
}
|
|
2255
|
+
|
|
2256
|
+
.shell > .agent-panel .agent-title-label {
|
|
2257
|
+
font-weight: 600;
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2260
|
+
.shell > .agent-panel .agent-composer {
|
|
2261
|
+
padding: 10px 12px;
|
|
2262
|
+
border-top: 1px solid var(--border-soft);
|
|
2263
|
+
flex: none;
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
/* The mode switch is the panel's first thing and it sits alone on its row,
|
|
2267
|
+
left-aligned and pill-shaped, exactly as Raylight's Design/Animate does. */
|
|
2268
|
+
.inspector > .segmented {
|
|
2269
|
+
margin-bottom: 4px;
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
.inspector-empty {
|
|
2273
|
+
margin: 16px 4px;
|
|
2274
|
+
color: var(--text-faint);
|
|
2275
|
+
line-height: 1.6;
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
.section-action {
|
|
2279
|
+
font: inherit;
|
|
2280
|
+
font-size: 14px;
|
|
2281
|
+
line-height: 1;
|
|
2282
|
+
color: var(--text-dim);
|
|
2283
|
+
background: transparent;
|
|
2284
|
+
border: 0;
|
|
2285
|
+
cursor: pointer;
|
|
2286
|
+
padding: 4px 6px;
|
|
2287
|
+
border-radius: 6px;
|
|
2288
|
+
}
|
|
2289
|
+
|
|
2290
|
+
.section-action:hover {
|
|
2291
|
+
color: var(--text);
|
|
2292
|
+
background: var(--control);
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2295
|
+
/* ---- align / distribute ------------------------------------------------- */
|
|
2296
|
+
/* Two rows of four: six alignments then two distributions, the grouping every
|
|
2297
|
+
design tool uses so the muscle memory transfers. */
|
|
2298
|
+
.align-grid {
|
|
2299
|
+
display: grid;
|
|
2300
|
+
grid-template-columns: repeat(4, 1fr);
|
|
2301
|
+
gap: 3px;
|
|
2302
|
+
margin: 2px 0 8px;
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
.align-button {
|
|
2306
|
+
display: inline-flex;
|
|
2307
|
+
align-items: center;
|
|
2308
|
+
justify-content: center;
|
|
2309
|
+
height: 28px;
|
|
2310
|
+
padding: 0;
|
|
2311
|
+
color: var(--text-dim);
|
|
2312
|
+
background: var(--control);
|
|
2313
|
+
border: 1px solid transparent;
|
|
2314
|
+
border-radius: 7px;
|
|
2315
|
+
cursor: pointer;
|
|
2316
|
+
transition: background 120ms ease, color 120ms ease;
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2319
|
+
.align-button:hover:not(:disabled) {
|
|
2320
|
+
background: var(--control-hover);
|
|
2321
|
+
color: var(--text);
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2324
|
+
.align-button:disabled {
|
|
2325
|
+
opacity: 0.3;
|
|
2326
|
+
cursor: default;
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2329
|
+
.align-button:focus-visible {
|
|
2330
|
+
outline: none;
|
|
2331
|
+
border-color: var(--accent);
|
|
2332
|
+
box-shadow: var(--focus-ring);
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
/* The rule is the EDGE being aligned to and the boxes are the layers. Two
|
|
2336
|
+
weights, so the icon reads as "these, against that" at 16px. */
|
|
2337
|
+
.align-icon-rule {
|
|
2338
|
+
fill: currentColor;
|
|
2339
|
+
}
|
|
2340
|
+
|
|
2341
|
+
.align-icon-box {
|
|
2342
|
+
fill: currentColor;
|
|
2343
|
+
opacity: 0.45;
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
.effect-row + .effect-row {
|
|
2347
|
+
margin-top: 1px;
|
|
2348
|
+
}
|
|
2349
|
+
|
|
2350
|
+
/* The params of a switched-on effect, indented under their toggle so the
|
|
2351
|
+
grouping is visible without a box around every effect. */
|
|
2352
|
+
.effect-params {
|
|
2353
|
+
display: flex;
|
|
2354
|
+
gap: 6px;
|
|
2355
|
+
padding: 2px 0 10px 10px;
|
|
2356
|
+
border-left: 1px solid var(--border-soft);
|
|
2357
|
+
margin-left: 2px;
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2360
|
+
.effect-params > * {
|
|
2361
|
+
flex: 1 1 0;
|
|
2362
|
+
min-width: 0;
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
.insp-dim {
|
|
2366
|
+
color: var(--text-faint);
|
|
2367
|
+
}
|
|
2368
|
+
|
|
2369
|
+
/* ---- the Animate panel -------------------------------------------------- */
|
|
2370
|
+
|
|
2371
|
+
/* The resolved readout. Deliberately NOT shaped like a field: no box, no
|
|
2372
|
+
border, no hover — an author must be able to tell at a glance that these
|
|
2373
|
+
numbers are the composition's output and there is nothing here to type into.
|
|
2374
|
+
The grid is fixed-width on the label so seven rows of tabular numerals line
|
|
2375
|
+
up into a column that can be read down. */
|
|
2376
|
+
.resolved-list {
|
|
2377
|
+
margin: 0;
|
|
2378
|
+
padding: 2px 0 6px;
|
|
2379
|
+
display: flex;
|
|
2380
|
+
flex-direction: column;
|
|
2381
|
+
gap: 1px;
|
|
2382
|
+
}
|
|
2383
|
+
|
|
2384
|
+
.resolved-row {
|
|
2385
|
+
display: grid;
|
|
2386
|
+
grid-template-columns: 58px 1fr auto;
|
|
2387
|
+
align-items: baseline;
|
|
2388
|
+
gap: 8px;
|
|
2389
|
+
padding: 2px 2px;
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2392
|
+
.resolved-label {
|
|
2393
|
+
font-size: 11px;
|
|
2394
|
+
color: var(--text-faint);
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
.resolved-value {
|
|
2398
|
+
margin: 0;
|
|
2399
|
+
font-size: 11px;
|
|
2400
|
+
color: var(--text);
|
|
2401
|
+
font-variant-numeric: tabular-nums;
|
|
2402
|
+
overflow: hidden;
|
|
2403
|
+
text-overflow: ellipsis;
|
|
2404
|
+
white-space: nowrap;
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
/* The mark. It carries the preset names rather than a dot, because "a block is
|
|
2408
|
+
writing this" and "WHICH block" are the same question the moment there is
|
|
2409
|
+
more than one on the layer. */
|
|
2410
|
+
.resolved-by {
|
|
2411
|
+
margin: 0;
|
|
2412
|
+
font-size: 10px;
|
|
2413
|
+
color: var(--accent);
|
|
2414
|
+
white-space: nowrap;
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2417
|
+
/* A written channel lifts its LABEL, not its number. Recolouring the value
|
|
2418
|
+
would put four accent numerals in a column of seven and read as "these are
|
|
2419
|
+
links"; the mark beside them already says which is which. */
|
|
2420
|
+
.resolved-row[data-live="true"] .resolved-label {
|
|
2421
|
+
color: var(--text-dim);
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2424
|
+
.animate-palette {
|
|
2425
|
+
display: flex;
|
|
2426
|
+
flex-wrap: wrap;
|
|
2427
|
+
gap: 5px;
|
|
2428
|
+
padding: 4px 0 8px;
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2431
|
+
.animate-preset {
|
|
2432
|
+
font: inherit;
|
|
2433
|
+
font-size: 11px;
|
|
2434
|
+
font-weight: 500;
|
|
2435
|
+
color: var(--text-dim);
|
|
2436
|
+
padding: 5px 10px;
|
|
2437
|
+
border-radius: 999px;
|
|
2438
|
+
cursor: pointer;
|
|
2439
|
+
background: var(--control);
|
|
2440
|
+
border: 1px solid transparent;
|
|
2441
|
+
transition: background 120ms ease, color 120ms ease;
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2444
|
+
.animate-preset:hover {
|
|
2445
|
+
background: var(--control-hover);
|
|
2446
|
+
color: var(--text);
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2449
|
+
.animate-preset[aria-pressed="true"] {
|
|
2450
|
+
background: var(--accent);
|
|
2451
|
+
border-color: transparent;
|
|
2452
|
+
color: #fff;
|
|
2453
|
+
}
|
|
2454
|
+
|
|
2455
|
+
.animate-blocks {
|
|
2456
|
+
list-style: none;
|
|
2457
|
+
margin: 0;
|
|
2458
|
+
padding: 0;
|
|
2459
|
+
display: flex;
|
|
2460
|
+
flex-direction: column;
|
|
2461
|
+
gap: 2px;
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2464
|
+
.animate-blocks li {
|
|
2465
|
+
display: flex;
|
|
2466
|
+
align-items: center;
|
|
2467
|
+
gap: 2px;
|
|
2468
|
+
}
|
|
2469
|
+
|
|
2470
|
+
.animate-block-row {
|
|
2471
|
+
flex: 1;
|
|
2472
|
+
min-width: 0;
|
|
2473
|
+
display: flex;
|
|
2474
|
+
justify-content: space-between;
|
|
2475
|
+
align-items: center;
|
|
2476
|
+
gap: 8px;
|
|
2477
|
+
font: inherit;
|
|
2478
|
+
font-size: 11px;
|
|
2479
|
+
color: var(--text);
|
|
2480
|
+
text-align: left;
|
|
2481
|
+
padding: 6px 8px;
|
|
2482
|
+
border-radius: 7px;
|
|
2483
|
+
cursor: pointer;
|
|
2484
|
+
background: var(--control);
|
|
2485
|
+
border: 1px solid transparent;
|
|
2486
|
+
}
|
|
2487
|
+
|
|
2488
|
+
.animate-block-row:hover {
|
|
2489
|
+
background: var(--control-hover);
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
.animate-block-row[aria-pressed="true"] {
|
|
2493
|
+
background: var(--accent-soft);
|
|
2494
|
+
border-color: var(--accent);
|
|
2495
|
+
}
|
|
2496
|
+
|
|
2497
|
+
.animate-block-name {
|
|
2498
|
+
font-weight: 500;
|
|
2499
|
+
overflow: hidden;
|
|
2500
|
+
text-overflow: ellipsis;
|
|
2501
|
+
}
|
|
2502
|
+
|
|
2503
|
+
.animate-block-owner {
|
|
2504
|
+
font-style: normal;
|
|
2505
|
+
color: var(--text-faint);
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2508
|
+
.animate-block-span {
|
|
2509
|
+
color: var(--text-dim);
|
|
2510
|
+
font-variant-numeric: tabular-nums;
|
|
2511
|
+
flex: none;
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2514
|
+
.animate-block-remove {
|
|
2515
|
+
font: inherit;
|
|
2516
|
+
font-size: 12px;
|
|
2517
|
+
color: var(--text-faint);
|
|
2518
|
+
background: transparent;
|
|
2519
|
+
border: 0;
|
|
2520
|
+
cursor: pointer;
|
|
2521
|
+
padding: 4px 6px;
|
|
2522
|
+
border-radius: 6px;
|
|
2523
|
+
flex: none;
|
|
2524
|
+
}
|
|
2525
|
+
|
|
2526
|
+
.animate-block-remove:hover {
|
|
2527
|
+
color: var(--danger-text);
|
|
2528
|
+
background: var(--control);
|
|
2529
|
+
}
|
|
2530
|
+
|
|
2531
|
+
/* The bezier pad and the settle preview, sharing the shape the other two
|
|
2532
|
+
chromes draw so an ease reads the same everywhere. */
|
|
2533
|
+
.insp-pad {
|
|
2534
|
+
display: block;
|
|
2535
|
+
margin: 6px 0;
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
.insp-pad-box {
|
|
2539
|
+
fill: none;
|
|
2540
|
+
stroke: var(--border);
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2543
|
+
.insp-pad-curve {
|
|
2544
|
+
fill: none;
|
|
2545
|
+
stroke: var(--accent);
|
|
2546
|
+
stroke-width: 1.5;
|
|
2547
|
+
}
|
|
2548
|
+
|
|
2549
|
+
.insp-pad-arm {
|
|
2550
|
+
stroke: var(--border);
|
|
2551
|
+
stroke-width: 1;
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2554
|
+
.insp-pad-handle {
|
|
2555
|
+
fill: var(--accent);
|
|
2556
|
+
cursor: grab;
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
.insp-preview {
|
|
2560
|
+
display: block;
|
|
2561
|
+
margin: 4px 0;
|
|
2562
|
+
}
|
|
2563
|
+
|
|
2564
|
+
.insp-preview polyline {
|
|
2565
|
+
fill: none;
|
|
2566
|
+
stroke: var(--accent);
|
|
2567
|
+
stroke-width: 1.5;
|
|
2568
|
+
}
|
|
2569
|
+
|
|
2570
|
+
.insp-preview polyline.is-spring {
|
|
2571
|
+
stroke: var(--accent-warm);
|
|
2572
|
+
}
|
|
2573
|
+
|
|
2574
|
+
/* The part of a spring that falls OUTSIDE the block window. Dashed, because
|
|
2575
|
+
the block stops contributing there — the motion is still settling, but the
|
|
2576
|
+
document is holding the clipped value. */
|
|
2577
|
+
.insp-preview polyline.is-tail {
|
|
2578
|
+
stroke: var(--accent-warm);
|
|
2579
|
+
stroke-dasharray: 3 3;
|
|
2580
|
+
opacity: 0.55;
|
|
2581
|
+
}
|
|
2582
|
+
|
|
2583
|
+
.animate-preview-edge {
|
|
2584
|
+
stroke: var(--border);
|
|
2585
|
+
stroke-width: 1;
|
|
2586
|
+
stroke-dasharray: 2 2;
|
|
2587
|
+
}
|
|
2588
|
+
|
|
2589
|
+
.animate-preview-note {
|
|
2590
|
+
display: block;
|
|
2591
|
+
font-size: 10px;
|
|
2592
|
+
color: var(--text-faint);
|
|
2593
|
+
line-height: 1.5;
|
|
2594
|
+
}
|
|
2595
|
+
|
|
2596
|
+
/* The components list. Same row rhythm as the layers below it, with the
|
|
2597
|
+
instance count pulling right so a definition's reach is readable at a
|
|
2598
|
+
glance — that number is what "editing this changes N things" means. */
|
|
2599
|
+
.component-row {
|
|
2600
|
+
display: flex;
|
|
2601
|
+
align-items: center;
|
|
2602
|
+
gap: 4px;
|
|
2603
|
+
min-height: 24px;
|
|
2604
|
+
padding-left: 8px;
|
|
2605
|
+
border-radius: 4px;
|
|
2606
|
+
}
|
|
2607
|
+
|
|
2608
|
+
.component-row.is-current {
|
|
2609
|
+
background: var(--control);
|
|
2610
|
+
}
|
|
2611
|
+
|
|
2612
|
+
.component-count {
|
|
2613
|
+
margin-left: auto;
|
|
2614
|
+
color: var(--text-faint);
|
|
2615
|
+
font-variant-numeric: tabular-nums;
|
|
2616
|
+
}
|
|
2617
|
+
|
|
2618
|
+
/* A button that reads as a link: the definition's name in the instance
|
|
2619
|
+
inspector, which navigates rather than edits. */
|
|
2620
|
+
.link-button {
|
|
2621
|
+
padding: 0;
|
|
2622
|
+
border: 0;
|
|
2623
|
+
background: transparent;
|
|
2624
|
+
color: var(--accent);
|
|
2625
|
+
cursor: pointer;
|
|
2626
|
+
text-align: left;
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2629
|
+
.link-button:hover {
|
|
2630
|
+
text-decoration: underline;
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2633
|
+
/* The isolation path. Reads as a trail rather than a list: the last crumb is
|
|
2634
|
+
where you are, the ones before it are one click away. */
|
|
2635
|
+
.isolation-crumbs {
|
|
2636
|
+
display: flex;
|
|
2637
|
+
flex-wrap: wrap;
|
|
2638
|
+
align-items: center;
|
|
2639
|
+
gap: 2px;
|
|
2640
|
+
padding: 2px 8px 6px;
|
|
2641
|
+
font-size: 10px;
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2644
|
+
.isolation-crumbs .crumb {
|
|
2645
|
+
padding: 2px 4px;
|
|
2646
|
+
border: 0;
|
|
2647
|
+
border-radius: 4px;
|
|
2648
|
+
background: transparent;
|
|
2649
|
+
color: var(--text-dim);
|
|
2650
|
+
cursor: pointer;
|
|
2651
|
+
}
|
|
2652
|
+
|
|
2653
|
+
.isolation-crumbs .crumb + .crumb::before {
|
|
2654
|
+
content: "›";
|
|
2655
|
+
margin-right: 4px;
|
|
2656
|
+
color: var(--text-faint);
|
|
2657
|
+
}
|
|
2658
|
+
|
|
2659
|
+
.isolation-crumbs .crumb:hover {
|
|
2660
|
+
color: var(--text);
|
|
2661
|
+
}
|
|
2662
|
+
|
|
2663
|
+
.isolation-crumbs .crumb.is-current {
|
|
2664
|
+
color: var(--text);
|
|
2665
|
+
cursor: default;
|
|
2666
|
+
}
|
|
2667
|
+
|
|
2668
|
+
/* Outline drop feedback. A LINE for "beside this row", a filled ring for
|
|
2669
|
+
"inside it" — the two gestures have to look different before the drop, or
|
|
2670
|
+
reparenting happens by accident. */
|
|
2671
|
+
.layer-row.is-drop-before {
|
|
2672
|
+
box-shadow: inset 0 2px 0 var(--accent);
|
|
2673
|
+
}
|
|
2674
|
+
|
|
2675
|
+
.layer-row.is-drop-after {
|
|
2676
|
+
box-shadow: inset 0 -2px 0 var(--accent);
|
|
2677
|
+
}
|
|
2678
|
+
|
|
2679
|
+
.layer-row.is-drop-into {
|
|
2680
|
+
box-shadow: inset 0 0 0 2px var(--accent);
|
|
2681
|
+
}
|
|
2682
|
+
|
|
2683
|
+
/* A section's own aside — a fact about the value above it, not a warning. */
|
|
2684
|
+
.inspector-note {
|
|
2685
|
+
margin: 0 0 2px;
|
|
2686
|
+
font-size: 10px;
|
|
2687
|
+
color: var(--text-faint);
|
|
2688
|
+
line-height: 1.5;
|
|
2689
|
+
}
|
|
2690
|
+
|
|
2691
|
+
/* ---- export ------------------------------------------------------------- */
|
|
2692
|
+
|
|
2693
|
+
.header-export.is-running {
|
|
2694
|
+
font-variant-numeric: tabular-nums;
|
|
2695
|
+
}
|
|
2696
|
+
|
|
2697
|
+
/* The WIDTH GOES ON THE DIALOG, not on its body. `.dialog` is a hard 320px,
|
|
2698
|
+
which is right for a confirm and too narrow for three labelled rows plus a
|
|
2699
|
+
size RANGE; widening the body instead just overflowed the box and clipped
|
|
2700
|
+
the range mid-string, and "911 KB–2" reads as a finished number rather than
|
|
2701
|
+
a truncated one. */
|
|
2702
|
+
.dialog.shot-export-dialog {
|
|
2703
|
+
width: 420px;
|
|
2704
|
+
}
|
|
2705
|
+
|
|
2706
|
+
/* Same reasoning as the export dialog's width, further: this one is a GRID of
|
|
2707
|
+
thumbnails, and 320px fits one column. Four across at 96px is the width at
|
|
2708
|
+
which a card reads as a picture rather than as a swatch. */
|
|
2709
|
+
.dialog.material-picker {
|
|
2710
|
+
width: 520px;
|
|
2711
|
+
max-height: 70vh;
|
|
2712
|
+
}
|
|
2713
|
+
|
|
2714
|
+
.material-search {
|
|
2715
|
+
width: 100%;
|
|
2716
|
+
padding: 6px 8px;
|
|
2717
|
+
background: var(--surface-sunken, #131316);
|
|
2718
|
+
border: 1px solid var(--border-strong, #35353d);
|
|
2719
|
+
border-radius: var(--radius-md, 6px);
|
|
2720
|
+
color: var(--text-primary, #e7e7ea);
|
|
2721
|
+
font: inherit;
|
|
2722
|
+
}
|
|
2723
|
+
|
|
2724
|
+
.material-shelf {
|
|
2725
|
+
display: flex;
|
|
2726
|
+
flex-direction: column;
|
|
2727
|
+
gap: 8px;
|
|
2728
|
+
margin-bottom: 14px;
|
|
2729
|
+
}
|
|
2730
|
+
|
|
2731
|
+
.material-shelf h3 {
|
|
2732
|
+
margin: 0;
|
|
2733
|
+
font-size: var(--text-xs, 11px);
|
|
2734
|
+
font-weight: var(--weight-semibold, 600);
|
|
2735
|
+
letter-spacing: 0.06em;
|
|
2736
|
+
text-transform: uppercase;
|
|
2737
|
+
color: var(--text-secondary, #9a9aa5);
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2740
|
+
.material-grid {
|
|
2741
|
+
display: grid;
|
|
2742
|
+
grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
|
|
2743
|
+
gap: 8px;
|
|
2744
|
+
}
|
|
2745
|
+
|
|
2746
|
+
.material-card {
|
|
2747
|
+
position: relative;
|
|
2748
|
+
display: flex;
|
|
2749
|
+
flex-direction: column;
|
|
2750
|
+
gap: 4px;
|
|
2751
|
+
padding: 0;
|
|
2752
|
+
background: none;
|
|
2753
|
+
border: 1px solid transparent;
|
|
2754
|
+
border-radius: var(--radius-md, 6px);
|
|
2755
|
+
cursor: pointer;
|
|
2756
|
+
text-align: left;
|
|
2757
|
+
}
|
|
2758
|
+
|
|
2759
|
+
.material-card:hover,
|
|
2760
|
+
.material-card:focus-visible {
|
|
2761
|
+
border-color: var(--accent, #3b82f6);
|
|
2762
|
+
}
|
|
2763
|
+
|
|
2764
|
+
.material-thumb {
|
|
2765
|
+
display: block;
|
|
2766
|
+
width: 100%;
|
|
2767
|
+
aspect-ratio: 16 / 9;
|
|
2768
|
+
object-fit: cover;
|
|
2769
|
+
border-radius: var(--radius-sm, 4px);
|
|
2770
|
+
background: var(--surface-sunken, #131316);
|
|
2771
|
+
}
|
|
2772
|
+
|
|
2773
|
+
.material-name {
|
|
2774
|
+
padding: 0 2px 2px;
|
|
2775
|
+
font-size: var(--text-xs, 11px);
|
|
2776
|
+
color: var(--text-primary, #e7e7ea);
|
|
2777
|
+
overflow: hidden;
|
|
2778
|
+
text-overflow: ellipsis;
|
|
2779
|
+
white-space: nowrap;
|
|
2780
|
+
}
|
|
2781
|
+
|
|
2782
|
+
/* The swangle warning, ON the card rather than in a tooltip alone: the point is
|
|
2783
|
+
that an author sees it while choosing, not after a render has stalled. */
|
|
2784
|
+
.mesh-material-row {
|
|
2785
|
+
display: flex;
|
|
2786
|
+
align-items: center;
|
|
2787
|
+
gap: 4px;
|
|
2788
|
+
min-width: 0;
|
|
2789
|
+
}
|
|
2790
|
+
|
|
2791
|
+
.mesh-material-name {
|
|
2792
|
+
flex: 1 1 auto;
|
|
2793
|
+
min-width: 0;
|
|
2794
|
+
padding: 4px 8px;
|
|
2795
|
+
background: var(--surface-sunken, #131316);
|
|
2796
|
+
border: 1px solid var(--border-strong, #35353d);
|
|
2797
|
+
border-radius: var(--radius-md, 6px);
|
|
2798
|
+
color: var(--text-primary, #e7e7ea);
|
|
2799
|
+
font: inherit;
|
|
2800
|
+
text-align: left;
|
|
2801
|
+
overflow: hidden;
|
|
2802
|
+
text-overflow: ellipsis;
|
|
2803
|
+
white-space: nowrap;
|
|
2804
|
+
cursor: pointer;
|
|
2805
|
+
}
|
|
2806
|
+
|
|
2807
|
+
.mesh-material-clear {
|
|
2808
|
+
flex: 0 0 auto;
|
|
2809
|
+
width: 22px;
|
|
2810
|
+
padding: 0;
|
|
2811
|
+
background: none;
|
|
2812
|
+
border: none;
|
|
2813
|
+
color: var(--text-secondary, #9a9aa5);
|
|
2814
|
+
cursor: pointer;
|
|
2815
|
+
}
|
|
2816
|
+
|
|
2817
|
+
/* The model's triangle count, on its outline row. Muted until the scene as a
|
|
2818
|
+
whole crosses the soft budget, at which point every measured row tints — the
|
|
2819
|
+
claim is about the SCENE, so highlighting one row would name the wrong
|
|
2820
|
+
subject. */
|
|
2821
|
+
.layer-tris {
|
|
2822
|
+
flex: 0 0 auto;
|
|
2823
|
+
padding: 0 4px;
|
|
2824
|
+
font-size: 10px;
|
|
2825
|
+
font-variant-numeric: tabular-nums;
|
|
2826
|
+
color: var(--text-tertiary, #6b6b76);
|
|
2827
|
+
}
|
|
2828
|
+
|
|
2829
|
+
.layer-tris.is-over-budget {
|
|
2830
|
+
color: var(--warn, #eab308);
|
|
2831
|
+
}
|
|
2832
|
+
|
|
2833
|
+
.material-badge {
|
|
2834
|
+
position: absolute;
|
|
2835
|
+
top: 4px;
|
|
2836
|
+
right: 4px;
|
|
2837
|
+
padding: 1px 5px;
|
|
2838
|
+
border-radius: 999px;
|
|
2839
|
+
background: var(--warn, #eab308);
|
|
2840
|
+
color: #1c1c20;
|
|
2841
|
+
font-size: 10px;
|
|
2842
|
+
font-weight: var(--weight-semibold, 600);
|
|
2843
|
+
}
|
|
2844
|
+
|
|
2845
|
+
.export-dialog {
|
|
2846
|
+
display: flex;
|
|
2847
|
+
flex-direction: column;
|
|
2848
|
+
gap: 10px;
|
|
2849
|
+
min-width: 0;
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2852
|
+
.export-row {
|
|
2853
|
+
display: flex;
|
|
2854
|
+
align-items: center;
|
|
2855
|
+
justify-content: space-between;
|
|
2856
|
+
gap: 12px;
|
|
2857
|
+
min-height: 30px;
|
|
2858
|
+
}
|
|
2859
|
+
|
|
2860
|
+
.export-row > span {
|
|
2861
|
+
color: var(--text-dim);
|
|
2862
|
+
}
|
|
2863
|
+
|
|
2864
|
+
/* The omitted-blend note. Dim rather than alarming: the render is correct, it
|
|
2865
|
+
just contains less than the timeline does. */
|
|
2866
|
+
.export-note {
|
|
2867
|
+
margin: -2px 0 0;
|
|
2868
|
+
color: var(--text-faint);
|
|
2869
|
+
line-height: 1.45;
|
|
2870
|
+
}
|
|
2871
|
+
|
|
2872
|
+
.export-estimate {
|
|
2873
|
+
display: grid;
|
|
2874
|
+
grid-template-columns: max-content minmax(0, 1fr);
|
|
2875
|
+
gap: 6px 16px;
|
|
2876
|
+
margin: 2px 0 0;
|
|
2877
|
+
padding: 12px;
|
|
2878
|
+
border-radius: 8px;
|
|
2879
|
+
background: var(--control);
|
|
2880
|
+
}
|
|
2881
|
+
|
|
2882
|
+
.export-estimate dt {
|
|
2883
|
+
color: var(--text-faint);
|
|
2884
|
+
}
|
|
2885
|
+
|
|
2886
|
+
.export-estimate dd {
|
|
2887
|
+
margin: 0;
|
|
2888
|
+
min-width: 0;
|
|
2889
|
+
text-align: right;
|
|
2890
|
+
font-variant-numeric: tabular-nums;
|
|
2891
|
+
/* Wraps rather than clips. The range strings are the whole value of the
|
|
2892
|
+
row, and half a range is a wrong number rather than a short one. */
|
|
2893
|
+
overflow-wrap: anywhere;
|
|
2894
|
+
}
|
|
2895
|
+
|
|
2896
|
+
.export-progress {
|
|
2897
|
+
margin: 0;
|
|
2898
|
+
color: var(--text-dim);
|
|
2899
|
+
}
|
|
2900
|
+
|
|
2901
|
+
.export-progress.is-done {
|
|
2902
|
+
color: var(--text);
|
|
2903
|
+
}
|
|
2904
|
+
|
|
2905
|
+
.export-progress code {
|
|
2906
|
+
word-break: break-all;
|
|
2907
|
+
color: var(--text-dim);
|
|
2908
|
+
}
|
|
2909
|
+
|
|
2910
|
+
.export-error {
|
|
2911
|
+
margin: 0;
|
|
2912
|
+
color: var(--danger-text);
|
|
2913
|
+
}
|
|
2914
|
+
|
|
2915
|
+
/* ---- scrollbars --------------------------------------------------------- */
|
|
2916
|
+
/* Default scrollbars on a near-black panel are a bright grey slab. */
|
|
2917
|
+
.left-rail::-webkit-scrollbar,
|
|
2918
|
+
.inspector::-webkit-scrollbar,
|
|
2919
|
+
.agent-panel::-webkit-scrollbar,
|
|
2920
|
+
.dock-scroll::-webkit-scrollbar {
|
|
2921
|
+
width: 10px;
|
|
2922
|
+
height: 10px;
|
|
2923
|
+
}
|
|
2924
|
+
|
|
2925
|
+
.left-rail::-webkit-scrollbar-thumb,
|
|
2926
|
+
.inspector::-webkit-scrollbar-thumb,
|
|
2927
|
+
.agent-panel::-webkit-scrollbar-thumb,
|
|
2928
|
+
.dock-scroll::-webkit-scrollbar-thumb {
|
|
2929
|
+
background: #2a2a30;
|
|
2930
|
+
border: 3px solid transparent;
|
|
2931
|
+
background-clip: content-box;
|
|
2932
|
+
border-radius: 999px;
|
|
2933
|
+
}
|
|
2934
|
+
|
|
2935
|
+
.left-rail::-webkit-scrollbar-thumb:hover,
|
|
2936
|
+
.inspector::-webkit-scrollbar-thumb:hover,
|
|
2937
|
+
.agent-panel::-webkit-scrollbar-thumb:hover,
|
|
2938
|
+
.dock-scroll::-webkit-scrollbar-thumb:hover {
|
|
2939
|
+
background: #3a3a42;
|
|
2940
|
+
background-clip: content-box;
|
|
2941
|
+
}
|
|
2942
|
+
|
|
2943
|
+
.left-rail::-webkit-scrollbar-track,
|
|
2944
|
+
.inspector::-webkit-scrollbar-track,
|
|
2945
|
+
.agent-panel::-webkit-scrollbar-track,
|
|
2946
|
+
.dock-scroll::-webkit-scrollbar-track {
|
|
2947
|
+
background: transparent;
|
|
2948
|
+
}
|