@hugobatist/smartcode 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/LICENSE +21 -0
- package/README.md +292 -0
- package/dist/cli.js +4324 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +374 -0
- package/dist/index.js +1167 -0
- package/dist/index.js.map +1 -0
- package/dist/static/annotations-panel.js +133 -0
- package/dist/static/annotations-svg.js +108 -0
- package/dist/static/annotations.css +367 -0
- package/dist/static/annotations.js +367 -0
- package/dist/static/app-init.js +497 -0
- package/dist/static/breakpoints.css +69 -0
- package/dist/static/breakpoints.js +197 -0
- package/dist/static/clipboard.js +94 -0
- package/dist/static/collapse-ui.js +325 -0
- package/dist/static/command-history.js +89 -0
- package/dist/static/context-menu.js +334 -0
- package/dist/static/custom-renderer.js +201 -0
- package/dist/static/dagre-layout.js +291 -0
- package/dist/static/diagram-dom.js +241 -0
- package/dist/static/diagram-editor.js +368 -0
- package/dist/static/editor-panel.js +107 -0
- package/dist/static/editor-popovers.js +187 -0
- package/dist/static/event-bus.js +57 -0
- package/dist/static/export.js +181 -0
- package/dist/static/file-tree.js +470 -0
- package/dist/static/ghost-paths.js +397 -0
- package/dist/static/heatmap.css +116 -0
- package/dist/static/heatmap.js +308 -0
- package/dist/static/icons.js +66 -0
- package/dist/static/inline-edit.js +294 -0
- package/dist/static/interaction-state.js +155 -0
- package/dist/static/interaction-tracker.js +93 -0
- package/dist/static/live.html +239 -0
- package/dist/static/main-layout.css +220 -0
- package/dist/static/main.css +334 -0
- package/dist/static/mcp-sessions.js +202 -0
- package/dist/static/modal.css +109 -0
- package/dist/static/modal.js +171 -0
- package/dist/static/node-drag.js +293 -0
- package/dist/static/pan-zoom.js +199 -0
- package/dist/static/renderer.js +280 -0
- package/dist/static/search.css +103 -0
- package/dist/static/search.js +304 -0
- package/dist/static/selection.js +353 -0
- package/dist/static/session-player.css +137 -0
- package/dist/static/session-player.js +411 -0
- package/dist/static/sidebar.css +248 -0
- package/dist/static/svg-renderer.js +313 -0
- package/dist/static/svg-shapes.js +218 -0
- package/dist/static/tokens.css +76 -0
- package/dist/static/vendor/dagre-bundle.js +43 -0
- package/dist/static/vendor/dagre.min.js +3 -0
- package/dist/static/vendor/graphlib.min.js +2 -0
- package/dist/static/viewport-transform.js +107 -0
- package/dist/static/workspace-switcher.js +202 -0
- package/dist/static/ws-client.js +71 -0
- package/dist/static/ws-handler.js +125 -0
- package/package.json +74 -0
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SmartCode Selection -- node/edge selection, visual SVG indicators, keyboard shortcuts.
|
|
3
|
+
* Manages the selection lifecycle: select, deselect, re-apply after re-render.
|
|
4
|
+
*
|
|
5
|
+
* Dependencies:
|
|
6
|
+
* - interaction-state.js (SmartCodeInteraction)
|
|
7
|
+
* - diagram-dom.js (DiagramDOM)
|
|
8
|
+
* - event-bus.js (SmartCodeEventBus)
|
|
9
|
+
* - diagram-editor.js (MmdEditor)
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* SmartCodeSelection.init();
|
|
13
|
+
* SmartCodeSelection.selectNode('A');
|
|
14
|
+
* SmartCodeSelection.deselectAll();
|
|
15
|
+
* SmartCodeSelection.getSelected(); // { id: 'A', type: 'node' } or null
|
|
16
|
+
*/
|
|
17
|
+
(function() {
|
|
18
|
+
'use strict';
|
|
19
|
+
|
|
20
|
+
var SVG_NS = 'http://www.w3.org/2000/svg';
|
|
21
|
+
|
|
22
|
+
// ── Internal State ──
|
|
23
|
+
var selectedNodeId = null;
|
|
24
|
+
var selectedType = null; // 'node' | 'edge'
|
|
25
|
+
var overlayGroup = null; // SVGGElement for selection indicator
|
|
26
|
+
|
|
27
|
+
// ── Core Functions ──
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Clear existing selection indicator from SVG.
|
|
31
|
+
*/
|
|
32
|
+
function clearIndicator() {
|
|
33
|
+
if (overlayGroup && overlayGroup.parentNode) {
|
|
34
|
+
overlayGroup.parentNode.removeChild(overlayGroup);
|
|
35
|
+
}
|
|
36
|
+
overlayGroup = null;
|
|
37
|
+
// Remove any .selected-edge classes
|
|
38
|
+
var svg = DiagramDOM.getSVG();
|
|
39
|
+
if (svg) {
|
|
40
|
+
var selectedEdges = svg.querySelectorAll('.selected-edge');
|
|
41
|
+
for (var i = 0; i < selectedEdges.length; i++) {
|
|
42
|
+
selectedEdges[i].classList.remove('selected-edge');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Select a node by ID. Creates blue dashed border + 4 corner handles.
|
|
49
|
+
* @param {string} nodeId
|
|
50
|
+
*/
|
|
51
|
+
/**
|
|
52
|
+
* Get the absolute position of an SVG element by walking its CTM.
|
|
53
|
+
* Returns { x, y, width, height } in SVG root coordinates.
|
|
54
|
+
*/
|
|
55
|
+
function getAbsoluteBBox(el, svg) {
|
|
56
|
+
var bbox = el.getBBox();
|
|
57
|
+
// Use getCTM to transform local bbox to SVG root coordinates
|
|
58
|
+
var ctm = el.getCTM();
|
|
59
|
+
var svgCtm = svg.getCTM();
|
|
60
|
+
if (ctm && svgCtm) {
|
|
61
|
+
var transform = svgCtm.inverse().multiply(ctm);
|
|
62
|
+
return {
|
|
63
|
+
x: transform.a * bbox.x + transform.e,
|
|
64
|
+
y: transform.d * bbox.y + transform.f,
|
|
65
|
+
width: bbox.width * transform.a,
|
|
66
|
+
height: bbox.height * transform.d
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return bbox;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function selectNode(nodeId) {
|
|
73
|
+
// Clear pending delete confirmation when selection changes
|
|
74
|
+
if (deleteConfirmTimer) {
|
|
75
|
+
clearTimeout(deleteConfirmTimer);
|
|
76
|
+
deleteConfirmTimer = null;
|
|
77
|
+
deleteConfirmNodeId = null;
|
|
78
|
+
}
|
|
79
|
+
clearIndicator();
|
|
80
|
+
|
|
81
|
+
var el = DiagramDOM.findNodeElement(nodeId);
|
|
82
|
+
if (!el) return;
|
|
83
|
+
|
|
84
|
+
var svg = DiagramDOM.getSVG();
|
|
85
|
+
if (!svg) return;
|
|
86
|
+
|
|
87
|
+
var bbox = getAbsoluteBBox(el, svg);
|
|
88
|
+
|
|
89
|
+
// Create selection indicator group
|
|
90
|
+
var g = document.createElementNS(SVG_NS, 'g');
|
|
91
|
+
g.setAttribute('class', 'selection-indicator');
|
|
92
|
+
|
|
93
|
+
// Blue dashed border rect
|
|
94
|
+
var rect = document.createElementNS(SVG_NS, 'rect');
|
|
95
|
+
rect.setAttribute('x', bbox.x - 4);
|
|
96
|
+
rect.setAttribute('y', bbox.y - 4);
|
|
97
|
+
rect.setAttribute('width', bbox.width + 8);
|
|
98
|
+
rect.setAttribute('height', bbox.height + 8);
|
|
99
|
+
rect.setAttribute('fill', 'none');
|
|
100
|
+
rect.setAttribute('stroke', '#3b82f6');
|
|
101
|
+
rect.setAttribute('stroke-width', '2');
|
|
102
|
+
rect.setAttribute('stroke-dasharray', '6,3');
|
|
103
|
+
rect.setAttribute('rx', '4');
|
|
104
|
+
g.appendChild(rect);
|
|
105
|
+
|
|
106
|
+
// Four corner handles (8x8 px)
|
|
107
|
+
var corners = [
|
|
108
|
+
[bbox.x - 8, bbox.y - 8],
|
|
109
|
+
[bbox.x + bbox.width, bbox.y - 8],
|
|
110
|
+
[bbox.x - 8, bbox.y + bbox.height],
|
|
111
|
+
[bbox.x + bbox.width, bbox.y + bbox.height],
|
|
112
|
+
];
|
|
113
|
+
for (var i = 0; i < corners.length; i++) {
|
|
114
|
+
var handle = document.createElementNS(SVG_NS, 'rect');
|
|
115
|
+
handle.setAttribute('x', corners[i][0]);
|
|
116
|
+
handle.setAttribute('y', corners[i][1]);
|
|
117
|
+
handle.setAttribute('width', '8');
|
|
118
|
+
handle.setAttribute('height', '8');
|
|
119
|
+
handle.setAttribute('fill', '#3b82f6');
|
|
120
|
+
handle.setAttribute('rx', '2');
|
|
121
|
+
g.appendChild(handle);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
svg.appendChild(g);
|
|
125
|
+
overlayGroup = g;
|
|
126
|
+
|
|
127
|
+
// Update state
|
|
128
|
+
selectedNodeId = nodeId;
|
|
129
|
+
selectedType = 'node';
|
|
130
|
+
if (window.SmartCodeInteraction) {
|
|
131
|
+
SmartCodeInteraction.select(nodeId, 'node');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Emit event
|
|
135
|
+
if (window.SmartCodeEventBus) {
|
|
136
|
+
SmartCodeEventBus.emit('selection:changed', { id: nodeId, type: 'node' });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Track click for heatmap frequency data
|
|
140
|
+
if (window.SmartCodeInteractionTracker) {
|
|
141
|
+
SmartCodeInteractionTracker.trackClick(nodeId);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Select an edge by ID. Adds .selected-edge CSS class (no handles).
|
|
147
|
+
* @param {string} edgeId
|
|
148
|
+
*/
|
|
149
|
+
function selectEdge(edgeId) {
|
|
150
|
+
clearIndicator();
|
|
151
|
+
|
|
152
|
+
var el = DiagramDOM.findEdgeElement(edgeId);
|
|
153
|
+
if (!el) return;
|
|
154
|
+
|
|
155
|
+
el.classList.add('selected-edge');
|
|
156
|
+
|
|
157
|
+
// Update state
|
|
158
|
+
selectedNodeId = edgeId;
|
|
159
|
+
selectedType = 'edge';
|
|
160
|
+
if (window.SmartCodeInteraction) {
|
|
161
|
+
SmartCodeInteraction.select(edgeId, 'edge');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Emit event
|
|
165
|
+
if (window.SmartCodeEventBus) {
|
|
166
|
+
SmartCodeEventBus.emit('selection:changed', { id: edgeId, type: 'edge' });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Deselect all. Removes indicators, clears state.
|
|
172
|
+
*/
|
|
173
|
+
function deselectAll() {
|
|
174
|
+
// Clear pending delete confirmation
|
|
175
|
+
if (deleteConfirmTimer) {
|
|
176
|
+
clearTimeout(deleteConfirmTimer);
|
|
177
|
+
deleteConfirmTimer = null;
|
|
178
|
+
deleteConfirmNodeId = null;
|
|
179
|
+
}
|
|
180
|
+
clearIndicator();
|
|
181
|
+
selectedNodeId = null;
|
|
182
|
+
selectedType = null;
|
|
183
|
+
if (window.SmartCodeInteraction) {
|
|
184
|
+
SmartCodeInteraction.clearSelection();
|
|
185
|
+
}
|
|
186
|
+
if (window.SmartCodeEventBus) {
|
|
187
|
+
SmartCodeEventBus.emit('selection:changed', null);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Re-apply selection after SVG re-render (called on diagram:rendered).
|
|
193
|
+
* If the selected element still exists, re-create the indicator.
|
|
194
|
+
* If it was removed, deselect and transition FSM to idle.
|
|
195
|
+
*/
|
|
196
|
+
function reapplySelection() {
|
|
197
|
+
if (!selectedNodeId) return;
|
|
198
|
+
|
|
199
|
+
if (selectedType === 'node') {
|
|
200
|
+
var nodeEl = DiagramDOM.findNodeElement(selectedNodeId);
|
|
201
|
+
if (nodeEl) {
|
|
202
|
+
// Re-create indicator on new SVG
|
|
203
|
+
var savedId = selectedNodeId;
|
|
204
|
+
clearIndicator();
|
|
205
|
+
selectedNodeId = savedId;
|
|
206
|
+
selectedType = 'node';
|
|
207
|
+
selectNode(savedId);
|
|
208
|
+
} else {
|
|
209
|
+
deselectAll();
|
|
210
|
+
if (window.SmartCodeInteraction && SmartCodeInteraction.getState() === 'selected') {
|
|
211
|
+
SmartCodeInteraction.forceState('idle');
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
} else if (selectedType === 'edge') {
|
|
215
|
+
var edgeEl = DiagramDOM.findEdgeElement(selectedNodeId);
|
|
216
|
+
if (edgeEl) {
|
|
217
|
+
var savedEdgeId = selectedNodeId;
|
|
218
|
+
clearIndicator();
|
|
219
|
+
selectedNodeId = savedEdgeId;
|
|
220
|
+
selectedType = 'edge';
|
|
221
|
+
selectEdge(savedEdgeId);
|
|
222
|
+
} else {
|
|
223
|
+
deselectAll();
|
|
224
|
+
if (window.SmartCodeInteraction && SmartCodeInteraction.getState() === 'selected') {
|
|
225
|
+
SmartCodeInteraction.forceState('idle');
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ── Click Handler (delegated on #preview-container) ──
|
|
232
|
+
|
|
233
|
+
function handleClick(e) {
|
|
234
|
+
// Check FSM blocking states
|
|
235
|
+
if (window.SmartCodeInteraction && SmartCodeInteraction.isBlocking()) return;
|
|
236
|
+
|
|
237
|
+
// Don't handle in modes that have their own click handlers
|
|
238
|
+
var fsmState = window.SmartCodeInteraction ? SmartCodeInteraction.getState() : 'idle';
|
|
239
|
+
if (fsmState === 'flagging' || fsmState === 'add-node' || fsmState === 'add-edge') return;
|
|
240
|
+
|
|
241
|
+
// Skip UI controls and overlays
|
|
242
|
+
if (e.target.closest('.zoom-controls') ||
|
|
243
|
+
e.target.closest('.flag-popover') ||
|
|
244
|
+
e.target.closest('.editor-popover') ||
|
|
245
|
+
e.target.closest('.context-menu') ||
|
|
246
|
+
e.target.closest('.inline-edit-overlay')) return;
|
|
247
|
+
|
|
248
|
+
// Detect what was clicked
|
|
249
|
+
var nodeInfo = DiagramDOM.extractNodeId(e.target);
|
|
250
|
+
|
|
251
|
+
if (nodeInfo && nodeInfo.type === 'node') {
|
|
252
|
+
selectNode(nodeInfo.id);
|
|
253
|
+
if (window.SmartCodeInteraction) SmartCodeInteraction.transition('click_node', nodeInfo);
|
|
254
|
+
} else if (nodeInfo && nodeInfo.type === 'edge') {
|
|
255
|
+
selectEdge(nodeInfo.id);
|
|
256
|
+
if (window.SmartCodeInteraction) SmartCodeInteraction.transition('click_edge', nodeInfo);
|
|
257
|
+
} else if (nodeInfo && nodeInfo.type === 'subgraph') {
|
|
258
|
+
// Treat subgraph like a node for selection
|
|
259
|
+
selectNode(nodeInfo.id);
|
|
260
|
+
if (window.SmartCodeInteraction) SmartCodeInteraction.transition('click_node', nodeInfo);
|
|
261
|
+
} else {
|
|
262
|
+
// Empty space click
|
|
263
|
+
if (selectedNodeId) {
|
|
264
|
+
deselectAll();
|
|
265
|
+
if (window.SmartCodeInteraction) SmartCodeInteraction.transition('click_empty');
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ── Keyboard Handler (document-level) ──
|
|
271
|
+
|
|
272
|
+
var deleteConfirmTimer = null;
|
|
273
|
+
var deleteConfirmNodeId = null;
|
|
274
|
+
|
|
275
|
+
function handleKeydown(e) {
|
|
276
|
+
// Don't handle if focus is on text inputs
|
|
277
|
+
if (e.target.tagName === 'TEXTAREA' || e.target.tagName === 'INPUT' ||
|
|
278
|
+
e.target.getAttribute('contenteditable')) return;
|
|
279
|
+
|
|
280
|
+
// Don't handle if inline edit is active
|
|
281
|
+
if (window.SmartCodeInlineEdit && SmartCodeInlineEdit.isActive()) return;
|
|
282
|
+
|
|
283
|
+
var fsmState = window.SmartCodeInteraction ? SmartCodeInteraction.getState() : 'idle';
|
|
284
|
+
|
|
285
|
+
if (fsmState === 'selected' && selectedNodeId) {
|
|
286
|
+
if (e.key === 'Delete' || e.key === 'Backspace') {
|
|
287
|
+
e.preventDefault();
|
|
288
|
+
// Require double-press within 2 seconds to confirm deletion
|
|
289
|
+
if (deleteConfirmNodeId === selectedNodeId && deleteConfirmTimer) {
|
|
290
|
+
clearTimeout(deleteConfirmTimer);
|
|
291
|
+
deleteConfirmTimer = null;
|
|
292
|
+
deleteConfirmNodeId = null;
|
|
293
|
+
if (selectedType === 'node' && window.MmdEditor) {
|
|
294
|
+
var nodeToRemove = selectedNodeId;
|
|
295
|
+
deselectAll();
|
|
296
|
+
MmdEditor.doRemoveNode(nodeToRemove);
|
|
297
|
+
}
|
|
298
|
+
if (window.SmartCodeInteraction) SmartCodeInteraction.transition('delete_node');
|
|
299
|
+
} else {
|
|
300
|
+
// First press: show confirmation toast
|
|
301
|
+
deleteConfirmNodeId = selectedNodeId;
|
|
302
|
+
if (deleteConfirmTimer) clearTimeout(deleteConfirmTimer);
|
|
303
|
+
deleteConfirmTimer = setTimeout(function() {
|
|
304
|
+
deleteConfirmTimer = null;
|
|
305
|
+
deleteConfirmNodeId = null;
|
|
306
|
+
}, 2000);
|
|
307
|
+
if (window.toast) window.toast('Press Delete again to remove the node');
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
if (e.key === 'Escape') {
|
|
311
|
+
deleteConfirmTimer = null;
|
|
312
|
+
deleteConfirmNodeId = null;
|
|
313
|
+
deselectAll();
|
|
314
|
+
if (window.SmartCodeInteraction) SmartCodeInteraction.transition('escape');
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// ── Init ──
|
|
320
|
+
|
|
321
|
+
function init() {
|
|
322
|
+
var container = document.getElementById('preview-container');
|
|
323
|
+
if (container) {
|
|
324
|
+
container.addEventListener('click', handleClick);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
document.addEventListener('keydown', handleKeydown);
|
|
328
|
+
|
|
329
|
+
// Re-apply selection after SVG re-render
|
|
330
|
+
if (window.SmartCodeEventBus) {
|
|
331
|
+
SmartCodeEventBus.on('diagram:rendered', reapplySelection);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Returns the current selection.
|
|
337
|
+
* @returns {{ id: string, type: string }|null}
|
|
338
|
+
*/
|
|
339
|
+
function getSelected() {
|
|
340
|
+
if (!selectedNodeId) return null;
|
|
341
|
+
return { id: selectedNodeId, type: selectedType };
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// ── Public API ──
|
|
345
|
+
window.SmartCodeSelection = {
|
|
346
|
+
init: init,
|
|
347
|
+
selectNode: selectNode,
|
|
348
|
+
selectEdge: selectEdge,
|
|
349
|
+
deselectAll: deselectAll,
|
|
350
|
+
reapplySelection: reapplySelection,
|
|
351
|
+
getSelected: getSelected,
|
|
352
|
+
};
|
|
353
|
+
})();
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/* Session Player -- timeline scrubber bar and diff highlight styles */
|
|
2
|
+
|
|
3
|
+
.session-player {
|
|
4
|
+
display: flex;
|
|
5
|
+
align-items: center;
|
|
6
|
+
gap: 8px;
|
|
7
|
+
padding: 8px 12px;
|
|
8
|
+
background: var(--surface-1);
|
|
9
|
+
border-top: 1px solid var(--border-default);
|
|
10
|
+
z-index: 50;
|
|
11
|
+
}
|
|
12
|
+
.session-player.hidden {
|
|
13
|
+
display: none;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.sp-btn {
|
|
17
|
+
background: var(--surface-2);
|
|
18
|
+
color: var(--text-secondary);
|
|
19
|
+
border: 1px solid var(--border-default);
|
|
20
|
+
border-radius: 4px;
|
|
21
|
+
padding: 4px 10px;
|
|
22
|
+
font-size: 12px;
|
|
23
|
+
cursor: pointer;
|
|
24
|
+
font-family: 'Inter', sans-serif;
|
|
25
|
+
}
|
|
26
|
+
.sp-btn:hover {
|
|
27
|
+
background: var(--surface-3);
|
|
28
|
+
color: var(--text-primary);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.sp-scrubber {
|
|
32
|
+
flex: 1;
|
|
33
|
+
-webkit-appearance: none;
|
|
34
|
+
appearance: none;
|
|
35
|
+
height: 6px;
|
|
36
|
+
background: var(--border-default);
|
|
37
|
+
border-radius: 3px;
|
|
38
|
+
outline: none;
|
|
39
|
+
cursor: pointer;
|
|
40
|
+
}
|
|
41
|
+
.sp-scrubber::-webkit-slider-thumb {
|
|
42
|
+
-webkit-appearance: none;
|
|
43
|
+
appearance: none;
|
|
44
|
+
width: 14px;
|
|
45
|
+
height: 14px;
|
|
46
|
+
background: var(--accent);
|
|
47
|
+
border-radius: 50%;
|
|
48
|
+
cursor: pointer;
|
|
49
|
+
}
|
|
50
|
+
.sp-scrubber::-moz-range-thumb {
|
|
51
|
+
width: 14px;
|
|
52
|
+
height: 14px;
|
|
53
|
+
background: var(--accent);
|
|
54
|
+
border-radius: 50%;
|
|
55
|
+
cursor: pointer;
|
|
56
|
+
border: none;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.sp-time {
|
|
60
|
+
font-family: 'JetBrains Mono', monospace;
|
|
61
|
+
font-size: 11px;
|
|
62
|
+
color: var(--text-secondary);
|
|
63
|
+
min-width: 60px;
|
|
64
|
+
text-align: center;
|
|
65
|
+
white-space: nowrap;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.sp-speed {
|
|
69
|
+
background: var(--surface-2);
|
|
70
|
+
color: var(--text-secondary);
|
|
71
|
+
border: 1px solid var(--border-default);
|
|
72
|
+
border-radius: 4px;
|
|
73
|
+
padding: 3px 6px;
|
|
74
|
+
font-size: 11px;
|
|
75
|
+
cursor: pointer;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* Diff highlighting */
|
|
79
|
+
.diff-added rect, .diff-added polygon, .diff-added circle,
|
|
80
|
+
.diff-added ellipse, .diff-added path {
|
|
81
|
+
stroke: var(--diff-added) !important;
|
|
82
|
+
stroke-width: 3px !important;
|
|
83
|
+
}
|
|
84
|
+
.diff-removed-ghost {
|
|
85
|
+
opacity: 0.3;
|
|
86
|
+
fill: var(--diff-removed);
|
|
87
|
+
stroke: var(--diff-removed);
|
|
88
|
+
stroke-dasharray: 6,3;
|
|
89
|
+
}
|
|
90
|
+
.diff-modified rect, .diff-modified polygon, .diff-modified circle,
|
|
91
|
+
.diff-modified ellipse, .diff-modified path {
|
|
92
|
+
stroke: var(--diff-modified) !important;
|
|
93
|
+
stroke-width: 3px !important;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* Session dropdown */
|
|
97
|
+
.session-dropdown {
|
|
98
|
+
position: absolute;
|
|
99
|
+
top: 36px;
|
|
100
|
+
right: 0;
|
|
101
|
+
width: 260px;
|
|
102
|
+
max-height: 300px;
|
|
103
|
+
overflow-y: auto;
|
|
104
|
+
background: var(--surface-1);
|
|
105
|
+
border: 1px solid var(--border-default);
|
|
106
|
+
border-radius: 6px;
|
|
107
|
+
z-index: 100;
|
|
108
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.4);
|
|
109
|
+
}
|
|
110
|
+
.session-dropdown.hidden {
|
|
111
|
+
display: none;
|
|
112
|
+
}
|
|
113
|
+
.session-list-item {
|
|
114
|
+
padding: 8px 12px;
|
|
115
|
+
font-size: 12px;
|
|
116
|
+
color: var(--text-secondary);
|
|
117
|
+
cursor: pointer;
|
|
118
|
+
border-bottom: 1px solid var(--border-default);
|
|
119
|
+
}
|
|
120
|
+
.session-list-item:hover {
|
|
121
|
+
background: var(--surface-2);
|
|
122
|
+
color: var(--text-primary);
|
|
123
|
+
}
|
|
124
|
+
.session-list-item:last-child {
|
|
125
|
+
border-bottom: none;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* Responsive stacking */
|
|
129
|
+
@media (max-width: 600px) {
|
|
130
|
+
.session-player {
|
|
131
|
+
flex-wrap: wrap;
|
|
132
|
+
}
|
|
133
|
+
.sp-scrubber {
|
|
134
|
+
order: 10;
|
|
135
|
+
flex-basis: 100%;
|
|
136
|
+
}
|
|
137
|
+
}
|