chrometools-mcp 2.4.2 → 3.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CHANGELOG.md +540 -0
  2. package/COMPONENT_MAPPING_SPEC.md +1217 -0
  3. package/README.md +494 -38
  4. package/bridge/bridge-client.js +472 -0
  5. package/bridge/bridge-service.js +399 -0
  6. package/bridge/install.js +241 -0
  7. package/browser/browser-manager.js +107 -2
  8. package/browser/page-manager.js +226 -69
  9. package/docs/CHROME_EXTENSION.md +219 -0
  10. package/docs/PAGE_OBJECT_MODEL_CONCEPT.md +1756 -0
  11. package/element-finder-utils.js +138 -28
  12. package/extension/background.js +643 -0
  13. package/extension/content.js +715 -0
  14. package/extension/icons/create-icons.js +164 -0
  15. package/extension/icons/icon128.png +0 -0
  16. package/extension/icons/icon16.png +0 -0
  17. package/extension/icons/icon48.png +0 -0
  18. package/extension/manifest.json +58 -0
  19. package/extension/popup/popup.css +437 -0
  20. package/extension/popup/popup.html +102 -0
  21. package/extension/popup/popup.js +415 -0
  22. package/extension/recorder-overlay.css +93 -0
  23. package/figma-tools.js +120 -0
  24. package/index.js +3347 -2518
  25. package/models/BaseInputModel.js +93 -0
  26. package/models/CheckboxGroupModel.js +199 -0
  27. package/models/CheckboxModel.js +103 -0
  28. package/models/ColorInputModel.js +53 -0
  29. package/models/DateInputModel.js +67 -0
  30. package/models/RadioGroupModel.js +126 -0
  31. package/models/RangeInputModel.js +60 -0
  32. package/models/SelectModel.js +97 -0
  33. package/models/TextInputModel.js +34 -0
  34. package/models/TextareaModel.js +59 -0
  35. package/models/TimeInputModel.js +49 -0
  36. package/models/index.js +122 -0
  37. package/package.json +3 -2
  38. package/pom/apom-converter.js +267 -0
  39. package/pom/apom-tree-converter.js +515 -0
  40. package/pom/element-id-generator.js +175 -0
  41. package/recorder/page-object-generator.js +16 -0
  42. package/recorder/scenario-executor.js +80 -2
  43. package/server/tool-definitions.js +839 -656
  44. package/server/tool-groups.js +3 -2
  45. package/server/tool-schemas.js +367 -296
  46. package/server/websocket-bridge.js +447 -0
  47. package/utils/selector-resolver.js +186 -0
  48. package/utils/ui-framework-detector.js +392 -0
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Create ChromeTools extension icons
3
+ * Run with: node create-icons.js
4
+ *
5
+ * Creates a happy robot icon with Chrome colors
6
+ */
7
+
8
+ import Jimp from 'jimp';
9
+ import path from 'path';
10
+ import { fileURLToPath } from 'url';
11
+
12
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
+
14
+ // Colors (RGBA format for Jimp)
15
+ const CHROME_BLUE = 0x4285F4FF;
16
+ const CHROME_GREEN = 0x34A853FF;
17
+ const CHROME_YELLOW = 0xFBBC05FF;
18
+ const CHROME_RED = 0xEA4335FF;
19
+ const ROBOT_BODY = 0x5C6BC0FF; // Nice indigo/purple
20
+ const ROBOT_FACE = 0x7986CBFF; // Lighter purple for face
21
+ const ROBOT_LIGHT = 0x9FA8DAFF; // Highlight
22
+ const WHITE = 0xFFFFFFFF;
23
+ const BLACK = 0x333333FF;
24
+ const SMILE_COLOR = 0xFFFFFFFF;
25
+ const ANTENNA_COLOR = 0xFFD54FFF; // Golden antenna ball
26
+ const TRANSPARENT = 0x00000000;
27
+
28
+ async function createIcon(size) {
29
+ const image = new Jimp(size, size, TRANSPARENT);
30
+
31
+ const center = size / 2;
32
+ const headRadius = size * 0.38;
33
+
34
+ // Draw robot head (rounded rectangle approximation with circle)
35
+ for (let y = 0; y < size; y++) {
36
+ for (let x = 0; x < size; x++) {
37
+ const dx = x - center;
38
+ const dy = y - center + size * 0.05; // Slightly lower
39
+ const dist = Math.sqrt(dx * dx + dy * dy);
40
+
41
+ // Head shape (circle with flat bottom)
42
+ if (dist <= headRadius && y < center + headRadius * 0.85) {
43
+ // Gradient effect - lighter at top
44
+ const gradientFactor = 1 - (y / size) * 0.3;
45
+ if (dy < -headRadius * 0.3) {
46
+ image.setPixelColor(ROBOT_LIGHT, x, y);
47
+ } else {
48
+ image.setPixelColor(ROBOT_FACE, x, y);
49
+ }
50
+ }
51
+ }
52
+ }
53
+
54
+ // Draw ears (Chrome colored circles on sides)
55
+ const earRadius = size * 0.12;
56
+ const earY = center;
57
+ drawCircle(image, center - headRadius - earRadius * 0.3, earY, earRadius, CHROME_BLUE);
58
+ drawCircle(image, center + headRadius + earRadius * 0.3, earY, earRadius, CHROME_GREEN);
59
+
60
+ // Draw antenna
61
+ const antennaX = center;
62
+ const antennaTopY = center - headRadius - size * 0.12;
63
+ const antennaBottomY = center - headRadius + size * 0.05;
64
+
65
+ // Antenna stem
66
+ for (let y = antennaTopY + size * 0.06; y <= antennaBottomY; y++) {
67
+ const thickness = Math.max(1, size * 0.03);
68
+ for (let dx = -thickness; dx <= thickness; dx++) {
69
+ image.setPixelColor(ROBOT_LIGHT, Math.round(antennaX + dx), Math.round(y));
70
+ }
71
+ }
72
+
73
+ // Antenna ball (golden/yellow)
74
+ drawCircle(image, antennaX, antennaTopY + size * 0.03, size * 0.07, ANTENNA_COLOR);
75
+
76
+ // Draw happy eyes (big and expressive)
77
+ const eyeRadius = size * 0.11;
78
+ const eyeOffset = size * 0.15;
79
+ const eyeY = center - size * 0.05;
80
+
81
+ // Eye whites
82
+ drawCircle(image, center - eyeOffset, eyeY, eyeRadius, WHITE);
83
+ drawCircle(image, center + eyeOffset, eyeY, eyeRadius, WHITE);
84
+
85
+ // Pupils (looking slightly up and to the side - friendly look)
86
+ const pupilRadius = eyeRadius * 0.5;
87
+ const pupilOffsetX = size * 0.02;
88
+ const pupilOffsetY = -size * 0.02;
89
+ drawCircle(image, center - eyeOffset + pupilOffsetX, eyeY + pupilOffsetY, pupilRadius, CHROME_BLUE);
90
+ drawCircle(image, center + eyeOffset + pupilOffsetX, eyeY + pupilOffsetY, pupilRadius, CHROME_BLUE);
91
+
92
+ // Eye shine (small white dot)
93
+ if (size >= 32) {
94
+ const shineRadius = Math.max(1, size * 0.025);
95
+ drawCircle(image, center - eyeOffset + pupilOffsetX - size * 0.02, eyeY + pupilOffsetY - size * 0.02, shineRadius, WHITE);
96
+ drawCircle(image, center + eyeOffset + pupilOffsetX - size * 0.02, eyeY + pupilOffsetY - size * 0.02, shineRadius, WHITE);
97
+ }
98
+
99
+ // Draw big happy smile
100
+ const smileY = center + size * 0.12;
101
+ const smileWidth = size * 0.22;
102
+ const smileHeight = size * 0.08;
103
+
104
+ for (let x = center - smileWidth; x <= center + smileWidth; x++) {
105
+ // Curved smile (arc)
106
+ const normalizedX = (x - center) / smileWidth;
107
+ const curveY = smileHeight * (1 - normalizedX * normalizedX);
108
+
109
+ for (let dy = 0; dy <= curveY; dy++) {
110
+ const py = Math.round(smileY + dy);
111
+ if (py < size) {
112
+ image.setPixelColor(WHITE, Math.round(x), py);
113
+ }
114
+ }
115
+ }
116
+
117
+ // Rosy cheeks (small pink circles for larger sizes)
118
+ if (size >= 48) {
119
+ const cheekRadius = size * 0.04;
120
+ const cheekY = center + size * 0.08;
121
+ const cheekColor = 0xFFAB91FF; // Soft coral/pink
122
+
123
+ drawCircle(image, center - eyeOffset - size * 0.1, cheekY, cheekRadius, cheekColor);
124
+ drawCircle(image, center + eyeOffset + size * 0.1, cheekY, cheekRadius, cheekColor);
125
+ }
126
+
127
+ return image;
128
+ }
129
+
130
+ function drawCircle(image, cx, cy, radius, color) {
131
+ const r2 = radius * radius;
132
+ for (let y = cy - radius - 1; y <= cy + radius + 1; y++) {
133
+ for (let x = cx - radius - 1; x <= cx + radius + 1; x++) {
134
+ const dx = x - cx;
135
+ const dy = y - cy;
136
+ if (dx * dx + dy * dy <= r2) {
137
+ if (x >= 0 && x < image.getWidth() && y >= 0 && y < image.getHeight()) {
138
+ image.setPixelColor(color, Math.round(x), Math.round(y));
139
+ }
140
+ }
141
+ }
142
+ }
143
+ }
144
+
145
+ async function main() {
146
+ console.log('Creating ChromeTools icons (happy robot)...');
147
+
148
+ // Create icons
149
+ const icon16 = await createIcon(16);
150
+ const icon48 = await createIcon(48);
151
+ const icon128 = await createIcon(128);
152
+
153
+ // Save icons
154
+ await icon16.writeAsync(path.join(__dirname, 'icon16.png'));
155
+ await icon48.writeAsync(path.join(__dirname, 'icon48.png'));
156
+ await icon128.writeAsync(path.join(__dirname, 'icon128.png'));
157
+
158
+ console.log('✅ Icons created successfully!');
159
+ console.log(' - icon16.png (happy robot)');
160
+ console.log(' - icon48.png (happy robot)');
161
+ console.log(' - icon128.png (happy robot)');
162
+ }
163
+
164
+ main().catch(console.error);
Binary file
Binary file
Binary file
@@ -0,0 +1,58 @@
1
+ {
2
+ "manifest_version": 3,
3
+ "name": "ChromeTools MCP",
4
+ "version": "3.1.2",
5
+ "description": "Tab tracking and scenario recording for chrometools-mcp",
6
+
7
+ "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxLLqg7Nu1h9ogRVgQoVMRPv8Jp7uRJugZSGUh++Niq0xm3khJefBuJ3L0dSG6xb9tkjTdgqUyg81VUgJBDVw9Bxu6iz1uL17VnEGHDZKe5wpsEpG8o6ZsTWtKRDeoxmkCGSOSDsh/ihlJe8mFaqpBYz6RBaO28R89TNobVhSobTQPB1ptyEND7W7JnsnMOiMcTo9l6j9HrIHLoHj7tO42DHNI4tEyLxI7C6R3i5dLIdwwxJMj0Hhrx4Ncmh24AzPyZypxVvpa1V7HP3sAXGBoUjLd/SEaY8j50lnaIQI3AkYv86pS9l6EZ6y3XCuW7C7W9guTTL/7ZNawYoE2bJ1HwIDAQAB",
8
+
9
+ "permissions": [
10
+ "tabs",
11
+ "activeTab",
12
+ "scripting",
13
+ "storage",
14
+ "webNavigation",
15
+ "nativeMessaging"
16
+ ],
17
+
18
+ "host_permissions": [
19
+ "<all_urls>"
20
+ ],
21
+
22
+ "background": {
23
+ "service_worker": "background.js",
24
+ "type": "module"
25
+ },
26
+
27
+ "content_scripts": [
28
+ {
29
+ "matches": ["<all_urls>"],
30
+ "js": ["content.js"],
31
+ "css": ["recorder-overlay.css"],
32
+ "run_at": "document_start",
33
+ "all_frames": false
34
+ }
35
+ ],
36
+
37
+ "action": {
38
+ "default_popup": "popup/popup.html",
39
+ "default_icon": {
40
+ "16": "icons/icon16.png",
41
+ "48": "icons/icon48.png",
42
+ "128": "icons/icon128.png"
43
+ }
44
+ },
45
+
46
+ "icons": {
47
+ "16": "icons/icon16.png",
48
+ "48": "icons/icon48.png",
49
+ "128": "icons/icon128.png"
50
+ },
51
+
52
+ "web_accessible_resources": [
53
+ {
54
+ "resources": ["recorder-overlay.css"],
55
+ "matches": ["<all_urls>"]
56
+ }
57
+ ]
58
+ }
@@ -0,0 +1,437 @@
1
+ /* ChromeTools MCP Extension - Popup Styles */
2
+
3
+ * {
4
+ margin: 0;
5
+ padding: 0;
6
+ box-sizing: border-box;
7
+ }
8
+
9
+ body {
10
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
11
+ font-size: 13px;
12
+ color: #1f2937;
13
+ background: #f9fafb;
14
+ min-width: 320px;
15
+ max-width: 320px;
16
+ }
17
+
18
+ .popup-container {
19
+ display: flex;
20
+ flex-direction: column;
21
+ min-height: 400px;
22
+ }
23
+
24
+ /* Header */
25
+ .header {
26
+ display: flex;
27
+ justify-content: space-between;
28
+ align-items: center;
29
+ padding: 12px 16px;
30
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
31
+ color: white;
32
+ }
33
+
34
+ .logo {
35
+ display: flex;
36
+ align-items: center;
37
+ gap: 8px;
38
+ }
39
+
40
+ .logo-icon {
41
+ width: 28px;
42
+ height: 28px;
43
+ background: rgba(255, 255, 255, 0.2);
44
+ border-radius: 6px;
45
+ display: flex;
46
+ align-items: center;
47
+ justify-content: center;
48
+ font-weight: 700;
49
+ font-size: 11px;
50
+ }
51
+
52
+ .logo-text {
53
+ font-weight: 600;
54
+ font-size: 14px;
55
+ }
56
+
57
+ .connection-status {
58
+ display: flex;
59
+ align-items: center;
60
+ gap: 6px;
61
+ font-size: 11px;
62
+ opacity: 0.9;
63
+ }
64
+
65
+ .status-dot {
66
+ width: 8px;
67
+ height: 8px;
68
+ border-radius: 50%;
69
+ background: #ef4444;
70
+ }
71
+
72
+ .connection-status.connected .status-dot {
73
+ background: #10b981;
74
+ }
75
+
76
+ /* Tabs Navigation */
77
+ .tabs {
78
+ display: flex;
79
+ background: white;
80
+ border-bottom: 1px solid #e5e7eb;
81
+ }
82
+
83
+ .tab {
84
+ flex: 1;
85
+ padding: 10px 16px;
86
+ border: none;
87
+ background: none;
88
+ font-size: 13px;
89
+ font-weight: 500;
90
+ color: #6b7280;
91
+ cursor: pointer;
92
+ transition: all 0.2s;
93
+ border-bottom: 2px solid transparent;
94
+ }
95
+
96
+ .tab:hover {
97
+ color: #374151;
98
+ background: #f3f4f6;
99
+ }
100
+
101
+ .tab.active {
102
+ color: #667eea;
103
+ border-bottom-color: #667eea;
104
+ }
105
+
106
+ /* Tab Content */
107
+ .tab-content {
108
+ display: none;
109
+ padding: 16px;
110
+ flex: 1;
111
+ background: white;
112
+ }
113
+
114
+ .tab-content.active {
115
+ display: block;
116
+ }
117
+
118
+ /* Recording Status */
119
+ .recording-status {
120
+ display: flex;
121
+ align-items: center;
122
+ justify-content: center;
123
+ gap: 8px;
124
+ padding: 12px;
125
+ background: #f3f4f6;
126
+ border-radius: 8px;
127
+ margin-bottom: 16px;
128
+ }
129
+
130
+ .recording-dot {
131
+ width: 10px;
132
+ height: 10px;
133
+ border-radius: 50%;
134
+ background: #9ca3af;
135
+ }
136
+
137
+ .recording-status.recording .recording-dot {
138
+ background: #ef4444;
139
+ animation: pulse 1.5s ease-in-out infinite;
140
+ }
141
+
142
+ .recording-status.paused .recording-dot {
143
+ background: #f59e0b;
144
+ animation: none;
145
+ }
146
+
147
+ @keyframes pulse {
148
+ 0%, 100% { opacity: 1; transform: scale(1); }
149
+ 50% { opacity: 0.5; transform: scale(1.1); }
150
+ }
151
+
152
+ .recording-text {
153
+ font-weight: 500;
154
+ color: #374151;
155
+ }
156
+
157
+ /* Metadata Form */
158
+ .metadata-form {
159
+ margin-bottom: 16px;
160
+ }
161
+
162
+ .form-group {
163
+ margin-bottom: 12px;
164
+ }
165
+
166
+ .form-group label {
167
+ display: block;
168
+ font-size: 11px;
169
+ font-weight: 500;
170
+ color: #6b7280;
171
+ margin-bottom: 4px;
172
+ text-transform: uppercase;
173
+ letter-spacing: 0.5px;
174
+ }
175
+
176
+ .form-group input {
177
+ width: 100%;
178
+ padding: 10px 12px;
179
+ border: 1px solid #e5e7eb;
180
+ border-radius: 6px;
181
+ font-size: 13px;
182
+ transition: border-color 0.2s, box-shadow 0.2s;
183
+ }
184
+
185
+ .form-group input:focus {
186
+ outline: none;
187
+ border-color: #667eea;
188
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
189
+ }
190
+
191
+ .form-group input::placeholder {
192
+ color: #9ca3af;
193
+ }
194
+
195
+ /* Action Count Display */
196
+ .action-count-display {
197
+ text-align: center;
198
+ padding: 24px;
199
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
200
+ border-radius: 8px;
201
+ color: white;
202
+ margin-bottom: 16px;
203
+ }
204
+
205
+ .count-number {
206
+ display: block;
207
+ font-size: 48px;
208
+ font-weight: 700;
209
+ line-height: 1;
210
+ }
211
+
212
+ .count-label {
213
+ font-size: 12px;
214
+ opacity: 0.8;
215
+ text-transform: uppercase;
216
+ letter-spacing: 1px;
217
+ }
218
+
219
+ /* Buttons */
220
+ .controls {
221
+ display: flex;
222
+ flex-direction: column;
223
+ gap: 8px;
224
+ }
225
+
226
+ .btn {
227
+ display: flex;
228
+ align-items: center;
229
+ justify-content: center;
230
+ gap: 8px;
231
+ padding: 12px 16px;
232
+ border: none;
233
+ border-radius: 8px;
234
+ font-size: 13px;
235
+ font-weight: 600;
236
+ cursor: pointer;
237
+ transition: all 0.2s;
238
+ }
239
+
240
+ .btn:hover {
241
+ transform: translateY(-1px);
242
+ }
243
+
244
+ .btn:active {
245
+ transform: translateY(0);
246
+ }
247
+
248
+ .btn:disabled {
249
+ opacity: 0.5;
250
+ cursor: not-allowed;
251
+ transform: none;
252
+ }
253
+
254
+ .btn-icon {
255
+ font-size: 10px;
256
+ }
257
+
258
+ .btn-primary {
259
+ background: linear-gradient(135deg, #10b981 0%, #059669 100%);
260
+ color: white;
261
+ box-shadow: 0 2px 8px rgba(16, 185, 129, 0.3);
262
+ }
263
+
264
+ .btn-primary:hover {
265
+ box-shadow: 0 4px 12px rgba(16, 185, 129, 0.4);
266
+ }
267
+
268
+ .btn-secondary {
269
+ background: #f3f4f6;
270
+ color: #374151;
271
+ }
272
+
273
+ .btn-secondary:hover {
274
+ background: #e5e7eb;
275
+ }
276
+
277
+ .btn-danger {
278
+ background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
279
+ color: white;
280
+ box-shadow: 0 2px 8px rgba(239, 68, 68, 0.3);
281
+ }
282
+
283
+ .btn-danger:hover {
284
+ box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4);
285
+ }
286
+
287
+ .btn-ghost {
288
+ background: transparent;
289
+ color: #6b7280;
290
+ }
291
+
292
+ .btn-ghost:hover {
293
+ background: #f3f4f6;
294
+ }
295
+
296
+ .btn-reset {
297
+ background: #fef2f2;
298
+ color: #dc2626;
299
+ border: 1px solid #fecaca;
300
+ }
301
+
302
+ .btn-reset:hover {
303
+ background: #fee2e2;
304
+ }
305
+
306
+ /* Actions Preview */
307
+ .actions-preview {
308
+ margin-top: 16px;
309
+ border-top: 1px solid #e5e7eb;
310
+ padding-top: 16px;
311
+ }
312
+
313
+ .actions-preview h4 {
314
+ font-size: 11px;
315
+ font-weight: 600;
316
+ color: #6b7280;
317
+ text-transform: uppercase;
318
+ letter-spacing: 0.5px;
319
+ margin-bottom: 8px;
320
+ }
321
+
322
+ .actions-list {
323
+ list-style: none;
324
+ max-height: 150px;
325
+ overflow-y: auto;
326
+ }
327
+
328
+ .actions-list li {
329
+ padding: 8px;
330
+ background: #f9fafb;
331
+ border-radius: 4px;
332
+ margin-bottom: 4px;
333
+ font-size: 11px;
334
+ display: flex;
335
+ justify-content: space-between;
336
+ align-items: center;
337
+ }
338
+
339
+ .action-type {
340
+ font-weight: 600;
341
+ color: #667eea;
342
+ }
343
+
344
+ .action-details {
345
+ color: #6b7280;
346
+ font-size: 10px;
347
+ max-width: 150px;
348
+ overflow: hidden;
349
+ text-overflow: ellipsis;
350
+ white-space: nowrap;
351
+ }
352
+
353
+ /* Tabs List */
354
+ .tabs-list {
355
+ max-height: 300px;
356
+ overflow-y: auto;
357
+ }
358
+
359
+ .tabs-loading {
360
+ text-align: center;
361
+ padding: 32px;
362
+ color: #9ca3af;
363
+ }
364
+
365
+ .tab-item {
366
+ display: flex;
367
+ align-items: center;
368
+ gap: 12px;
369
+ padding: 10px;
370
+ border-radius: 6px;
371
+ cursor: pointer;
372
+ transition: background 0.2s;
373
+ }
374
+
375
+ .tab-item:hover {
376
+ background: #f3f4f6;
377
+ }
378
+
379
+ .tab-item.active {
380
+ background: rgba(102, 126, 234, 0.1);
381
+ }
382
+
383
+ .tab-favicon {
384
+ width: 16px;
385
+ height: 16px;
386
+ border-radius: 2px;
387
+ background: #e5e7eb;
388
+ }
389
+
390
+ .tab-info {
391
+ flex: 1;
392
+ min-width: 0;
393
+ }
394
+
395
+ .tab-title {
396
+ font-weight: 500;
397
+ color: #1f2937;
398
+ white-space: nowrap;
399
+ overflow: hidden;
400
+ text-overflow: ellipsis;
401
+ }
402
+
403
+ .tab-url {
404
+ font-size: 11px;
405
+ color: #9ca3af;
406
+ white-space: nowrap;
407
+ overflow: hidden;
408
+ text-overflow: ellipsis;
409
+ }
410
+
411
+ .tab-active-badge {
412
+ font-size: 9px;
413
+ padding: 2px 6px;
414
+ background: #10b981;
415
+ color: white;
416
+ border-radius: 10px;
417
+ text-transform: uppercase;
418
+ font-weight: 600;
419
+ }
420
+
421
+ /* Footer */
422
+ .footer {
423
+ padding: 8px 16px;
424
+ background: #f9fafb;
425
+ border-top: 1px solid #e5e7eb;
426
+ text-align: center;
427
+ }
428
+
429
+ .version {
430
+ font-size: 10px;
431
+ color: #9ca3af;
432
+ }
433
+
434
+ /* Utility */
435
+ .hidden {
436
+ display: none !important;
437
+ }