@three-flatland/devtools 0.1.0-alpha.3

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 (148) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +82 -0
  3. package/dist/buffers-modal.cjs +491 -0
  4. package/dist/buffers-modal.cjs.map +1 -0
  5. package/dist/buffers-modal.d.cts +62 -0
  6. package/dist/buffers-modal.d.ts +62 -0
  7. package/dist/buffers-modal.js +467 -0
  8. package/dist/buffers-modal.js.map +1 -0
  9. package/dist/buffers-view.cjs +269 -0
  10. package/dist/buffers-view.cjs.map +1 -0
  11. package/dist/buffers-view.d.cts +39 -0
  12. package/dist/buffers-view.d.ts +39 -0
  13. package/dist/buffers-view.js +245 -0
  14. package/dist/buffers-view.js.map +1 -0
  15. package/dist/create-pane.cjs +203 -0
  16. package/dist/create-pane.cjs.map +1 -0
  17. package/dist/create-pane.d.cts +66 -0
  18. package/dist/create-pane.d.ts +66 -0
  19. package/dist/create-pane.js +178 -0
  20. package/dist/create-pane.js.map +1 -0
  21. package/dist/dashboard/app.tsx +42 -0
  22. package/dist/dashboard/client.ts +21 -0
  23. package/dist/dashboard/export.ts +84 -0
  24. package/dist/dashboard/hooks.ts +75 -0
  25. package/dist/dashboard/index.html +1149 -0
  26. package/dist/dashboard/index.tsx +18 -0
  27. package/dist/dashboard/panels/batches.tsx +282 -0
  28. package/dist/dashboard/panels/buffers.tsx +425 -0
  29. package/dist/dashboard/panels/env.tsx +85 -0
  30. package/dist/dashboard/panels/header-stats.tsx +30 -0
  31. package/dist/dashboard/panels/producer-select.tsx +81 -0
  32. package/dist/dashboard/panels/protocol-log.tsx +743 -0
  33. package/dist/dashboard/panels/registry.tsx +642 -0
  34. package/dist/dashboard/panels/stats.tsx +431 -0
  35. package/dist/dashboard/protocol-store.ts +312 -0
  36. package/dist/dashboard/tsconfig.json +19 -0
  37. package/dist/dashboard/vendor/hooks.module.js +1 -0
  38. package/dist/dashboard/vendor/jsx-runtime.js +22 -0
  39. package/dist/dashboard/vendor/preact.module.js +12 -0
  40. package/dist/devtools-client.cjs +870 -0
  41. package/dist/devtools-client.cjs.map +1 -0
  42. package/dist/devtools-client.d.cts +362 -0
  43. package/dist/devtools-client.d.ts +362 -0
  44. package/dist/devtools-client.js +852 -0
  45. package/dist/devtools-client.js.map +1 -0
  46. package/dist/index.cjs +42 -0
  47. package/dist/index.cjs.map +1 -0
  48. package/dist/index.d.cts +8 -0
  49. package/dist/index.d.ts +8 -0
  50. package/dist/index.js +13 -0
  51. package/dist/index.js.map +1 -0
  52. package/dist/perf-trace.cjs +103 -0
  53. package/dist/perf-trace.cjs.map +1 -0
  54. package/dist/perf-trace.d.cts +27 -0
  55. package/dist/perf-trace.d.ts +27 -0
  56. package/dist/perf-trace.js +79 -0
  57. package/dist/perf-trace.js.map +1 -0
  58. package/dist/plugins.cjs +44 -0
  59. package/dist/plugins.cjs.map +1 -0
  60. package/dist/plugins.d.cts +7 -0
  61. package/dist/plugins.d.ts +7 -0
  62. package/dist/plugins.js +9 -0
  63. package/dist/plugins.js.map +1 -0
  64. package/dist/provider-switcher.cjs +95 -0
  65. package/dist/provider-switcher.cjs.map +1 -0
  66. package/dist/provider-switcher.d.cts +18 -0
  67. package/dist/provider-switcher.d.ts +18 -0
  68. package/dist/provider-switcher.js +71 -0
  69. package/dist/provider-switcher.js.map +1 -0
  70. package/dist/react/devtools-provider.cjs +59 -0
  71. package/dist/react/devtools-provider.cjs.map +1 -0
  72. package/dist/react/devtools-provider.d.cts +39 -0
  73. package/dist/react/devtools-provider.d.ts +39 -0
  74. package/dist/react/devtools-provider.js +38 -0
  75. package/dist/react/devtools-provider.js.map +1 -0
  76. package/dist/react/use-fps-graph.cjs +49 -0
  77. package/dist/react/use-fps-graph.cjs.map +1 -0
  78. package/dist/react/use-fps-graph.d.cts +15 -0
  79. package/dist/react/use-fps-graph.d.ts +15 -0
  80. package/dist/react/use-fps-graph.js +25 -0
  81. package/dist/react/use-fps-graph.js.map +1 -0
  82. package/dist/react/use-pane-button.cjs +58 -0
  83. package/dist/react/use-pane-button.cjs.map +1 -0
  84. package/dist/react/use-pane-button.d.cts +10 -0
  85. package/dist/react/use-pane-button.d.ts +10 -0
  86. package/dist/react/use-pane-button.js +34 -0
  87. package/dist/react/use-pane-button.js.map +1 -0
  88. package/dist/react/use-pane-folder.cjs +48 -0
  89. package/dist/react/use-pane-folder.cjs.map +1 -0
  90. package/dist/react/use-pane-folder.d.cts +15 -0
  91. package/dist/react/use-pane-folder.d.ts +15 -0
  92. package/dist/react/use-pane-folder.js +24 -0
  93. package/dist/react/use-pane-folder.js.map +1 -0
  94. package/dist/react/use-pane-input.cjs +70 -0
  95. package/dist/react/use-pane-input.cjs.map +1 -0
  96. package/dist/react/use-pane-input.d.cts +44 -0
  97. package/dist/react/use-pane-input.d.ts +44 -0
  98. package/dist/react/use-pane-input.js +46 -0
  99. package/dist/react/use-pane-input.js.map +1 -0
  100. package/dist/react/use-pane-radio-grid.cjs +78 -0
  101. package/dist/react/use-pane-radio-grid.cjs.map +1 -0
  102. package/dist/react/use-pane-radio-grid.d.cts +35 -0
  103. package/dist/react/use-pane-radio-grid.d.ts +35 -0
  104. package/dist/react/use-pane-radio-grid.js +54 -0
  105. package/dist/react/use-pane-radio-grid.js.map +1 -0
  106. package/dist/react/use-pane.cjs +45 -0
  107. package/dist/react/use-pane.cjs.map +1 -0
  108. package/dist/react/use-pane.d.cts +40 -0
  109. package/dist/react/use-pane.d.ts +40 -0
  110. package/dist/react/use-pane.js +21 -0
  111. package/dist/react/use-pane.js.map +1 -0
  112. package/dist/react.cjs +50 -0
  113. package/dist/react.cjs.map +1 -0
  114. package/dist/react.d.cts +12 -0
  115. package/dist/react.d.ts +12 -0
  116. package/dist/react.js +19 -0
  117. package/dist/react.js.map +1 -0
  118. package/dist/registry-view.cjs +371 -0
  119. package/dist/registry-view.cjs.map +1 -0
  120. package/dist/registry-view.d.cts +24 -0
  121. package/dist/registry-view.d.ts +24 -0
  122. package/dist/registry-view.js +347 -0
  123. package/dist/registry-view.js.map +1 -0
  124. package/dist/stats-graph.cjs +297 -0
  125. package/dist/stats-graph.cjs.map +1 -0
  126. package/dist/stats-graph.d.cts +40 -0
  127. package/dist/stats-graph.d.ts +40 -0
  128. package/dist/stats-graph.js +273 -0
  129. package/dist/stats-graph.js.map +1 -0
  130. package/dist/stats-row.cjs +153 -0
  131. package/dist/stats-row.cjs.map +1 -0
  132. package/dist/stats-row.d.cts +24 -0
  133. package/dist/stats-row.d.ts +24 -0
  134. package/dist/stats-row.js +129 -0
  135. package/dist/stats-row.js.map +1 -0
  136. package/dist/theme.cjs +260 -0
  137. package/dist/theme.cjs.map +1 -0
  138. package/dist/theme.d.cts +10 -0
  139. package/dist/theme.d.ts +10 -0
  140. package/dist/theme.js +235 -0
  141. package/dist/theme.js.map +1 -0
  142. package/dist/vite-plugin.cjs +113 -0
  143. package/dist/vite-plugin.cjs.map +1 -0
  144. package/dist/vite-plugin.d.cts +9 -0
  145. package/dist/vite-plugin.d.ts +9 -0
  146. package/dist/vite-plugin.js +88 -0
  147. package/dist/vite-plugin.js.map +1 -0
  148. package/package.json +124 -0
@@ -0,0 +1,1149 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
6
+ <title>three-flatland devtools</title>
7
+ <style>
8
+ :root {
9
+ color-scheme: dark;
10
+ --bg: #0e1016;
11
+ --bg-elev: #14171e;
12
+ --bg-elev-2: #1b1f29;
13
+ --border: #2a2e37;
14
+ --fg: #e4e6eb;
15
+ --fg-dim: #9aa0ab;
16
+ --accent: #5eb0ff;
17
+ --live: #5ed38a;
18
+ }
19
+ html, body { height: 100%; margin: 0; overflow: hidden; }
20
+ body {
21
+ background: var(--bg);
22
+ color: var(--fg);
23
+ font: 12px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace;
24
+ }
25
+ button { font: inherit; color: inherit; }
26
+
27
+ /* Shared scroll styling across every scrollable region in the
28
+ * dashboard — thin scrollbars, muted thumbs, contain-by-default so
29
+ * wheel events don't chain into ancestor containers. */
30
+ .dashboard-root * {
31
+ scrollbar-width: thin;
32
+ scrollbar-color: rgba(255, 255, 255, 0.12) transparent;
33
+ }
34
+ .dashboard-root *::-webkit-scrollbar { width: 8px; height: 8px; }
35
+ .dashboard-root *::-webkit-scrollbar-track { background: transparent; }
36
+ .dashboard-root *::-webkit-scrollbar-thumb {
37
+ background: rgba(255, 255, 255, 0.12);
38
+ border-radius: 4px;
39
+ }
40
+ .dashboard-root *::-webkit-scrollbar-thumb:hover {
41
+ background: rgba(255, 255, 255, 0.2);
42
+ }
43
+
44
+ /* Lock the root to the viewport — no page-level scrolling, each
45
+ * panel owns its own scroll region. `100dvh` (with a `100vh`
46
+ * fallback) keeps mobile Safari's dynamic toolbar from clipping the
47
+ * bottom panel. `min-height: 0` lets descendant flex items shrink
48
+ * below their content. */
49
+ .dashboard-root {
50
+ display: flex;
51
+ flex-direction: column;
52
+ height: 100vh;
53
+ height: 100dvh;
54
+ min-height: 0;
55
+ }
56
+ .dashboard-header {
57
+ padding: 6px 12px;
58
+ border-bottom: 1px solid var(--border);
59
+ background: var(--bg-elev);
60
+ display: flex;
61
+ align-items: center;
62
+ gap: 12px;
63
+ }
64
+ .brand { font-weight: 600; letter-spacing: 0.02em; flex: 0 0 auto; }
65
+ .dashboard-main { flex: 1; display: flex; min-height: 0; }
66
+ .dashboard-content {
67
+ flex: 1;
68
+ padding: 8px;
69
+ min-width: 0;
70
+ min-height: 0;
71
+ /* Column flex so the stats strip sits on top and the split grows
72
+ * to absorb the remaining vertical space. No outer overflow —
73
+ * each panel manages its own scrolling so the page doesn't
74
+ * expand past the viewport. */
75
+ display: flex;
76
+ flex-direction: column;
77
+ gap: 8px;
78
+ overflow: hidden;
79
+ }
80
+ /*
81
+ * Main content split: on wide viewports, buffers + registry live in
82
+ * a left column while protocol takes the right. Below ~1150px (too
83
+ * narrow for both a fullscreen canvas and a usable protocol list)
84
+ * they fall back into one stacked column.
85
+ */
86
+ .dashboard-split {
87
+ display: flex;
88
+ gap: 8px;
89
+ min-height: 0;
90
+ /* Let the split stretch vertically so the protocol column can
91
+ * actually grow — otherwise it just hugs its min-height. */
92
+ flex: 1;
93
+ }
94
+ .dashboard-split-col {
95
+ display: flex;
96
+ flex-direction: column;
97
+ gap: 8px;
98
+ min-width: 0;
99
+ min-height: 0;
100
+ flex: 1;
101
+ }
102
+ .producer-select { position: relative; }
103
+ .producer-button {
104
+ display: inline-flex;
105
+ align-items: center;
106
+ gap: 8px;
107
+ padding: 4px 10px;
108
+ background: var(--bg);
109
+ color: var(--fg);
110
+ border: 1px solid var(--border);
111
+ border-radius: 3px;
112
+ cursor: pointer;
113
+ min-width: 220px;
114
+ }
115
+ .producer-button:disabled { opacity: 0.5; cursor: default; }
116
+ .producer-button-open { border-color: var(--accent); }
117
+ .producer-button .producer-label {
118
+ flex: 1;
119
+ overflow: hidden;
120
+ text-overflow: ellipsis;
121
+ white-space: nowrap;
122
+ text-align: left;
123
+ }
124
+ .producer-button .producer-kind {
125
+ font-size: 9px;
126
+ text-transform: uppercase;
127
+ color: var(--fg-dim);
128
+ letter-spacing: 0.06em;
129
+ }
130
+ .producer-caret { color: var(--fg-dim); font-size: 8px; }
131
+ .producer-menu {
132
+ position: absolute;
133
+ top: calc(100% + 4px);
134
+ left: 0;
135
+ right: 0;
136
+ margin: 0;
137
+ padding: 4px 0;
138
+ list-style: none;
139
+ background: var(--bg-elev-2);
140
+ border: 1px solid var(--border);
141
+ border-radius: 3px;
142
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
143
+ z-index: 100;
144
+ max-height: 320px;
145
+ overflow-y: auto;
146
+ }
147
+ .producer-option {
148
+ display: grid;
149
+ grid-template-columns: 16px 1fr auto;
150
+ gap: 8px;
151
+ align-items: center;
152
+ width: 100%;
153
+ padding: 4px 10px;
154
+ background: transparent;
155
+ color: var(--fg);
156
+ border: none;
157
+ cursor: pointer;
158
+ text-align: left;
159
+ }
160
+ .producer-option:hover { background: rgba(255, 255, 255, 0.04); }
161
+ .producer-option-active { color: var(--accent); }
162
+ .producer-check { text-align: center; color: var(--accent); }
163
+
164
+ .header-stats {
165
+ display: inline-flex;
166
+ gap: 12px;
167
+ margin-left: auto;
168
+ color: var(--fg-dim);
169
+ font-variant-numeric: tabular-nums;
170
+ font-size: 11px;
171
+ }
172
+ .header-stats span[title] { cursor: help; }
173
+ /* Match protocol-log direction arrow palette: green in, amber out. */
174
+ .header-arrow-in { color: #5ed38a; font-weight: 700; }
175
+ .header-arrow-out { color: #f0b429; font-weight: 700; }
176
+
177
+ .panel {
178
+ background: var(--bg-elev-2);
179
+ border: 1px solid var(--border);
180
+ border-radius: 4px;
181
+ /* Gap-based spacing is owned by the layout containers now; the
182
+ * per-panel margin would stack with `gap` and push total height
183
+ * past the viewport, causing the log to grow the page down. */
184
+ }
185
+ .panel-header {
186
+ padding: 6px 10px;
187
+ font-size: 11px;
188
+ text-transform: uppercase;
189
+ letter-spacing: 0.06em;
190
+ color: var(--fg-dim);
191
+ border-bottom: 1px solid var(--border);
192
+ background: rgba(255, 255, 255, 0.02);
193
+ }
194
+ .panel-body { padding: 8px 10px; }
195
+ .panel-empty { margin: 4px 0; color: var(--fg-dim); }
196
+
197
+ .producers-list { list-style: none; margin: 0; padding: 0; }
198
+ .producers-list li + li { margin-top: 2px; }
199
+ .producer-row {
200
+ display: grid;
201
+ grid-template-columns: auto 1fr auto;
202
+ gap: 8px;
203
+ align-items: center;
204
+ width: 100%;
205
+ padding: 6px 8px;
206
+ background: transparent;
207
+ border: 1px solid transparent;
208
+ border-radius: 3px;
209
+ cursor: pointer;
210
+ text-align: left;
211
+ }
212
+ .producer-row:hover { background: rgba(255, 255, 255, 0.03); }
213
+ .producer-row-selected {
214
+ background: rgba(94, 176, 255, 0.08);
215
+ border-color: rgba(94, 176, 255, 0.3);
216
+ }
217
+ .producer-kind {
218
+ font-size: 10px;
219
+ text-transform: uppercase;
220
+ color: var(--fg-dim);
221
+ }
222
+ .producer-label { color: var(--fg); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
223
+ .dot {
224
+ width: 8px;
225
+ height: 8px;
226
+ border-radius: 50%;
227
+ background: #3a3f4b;
228
+ }
229
+ .dot-live { background: var(--live); box-shadow: 0 0 6px rgba(94, 211, 138, 0.6); }
230
+
231
+ .stats-strip {
232
+ display: grid;
233
+ grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
234
+ gap: 4px;
235
+ /* Keep top/bottom padding in line with other panels so the gap
236
+ * between Stats and the panel beneath doesn't read larger than
237
+ * the inter-panel gap elsewhere. */
238
+ padding: 4px 10px;
239
+ }
240
+ .stat {
241
+ position: relative;
242
+ display: flex;
243
+ flex-direction: column;
244
+ gap: 2px;
245
+ padding: 6px 10px;
246
+ background: var(--bg);
247
+ border: 1px solid transparent;
248
+ border-radius: 3px;
249
+ overflow: hidden;
250
+ min-height: 52px;
251
+ cursor: pointer;
252
+ text-align: left;
253
+ color: inherit;
254
+ font: inherit;
255
+ appearance: none;
256
+ -webkit-appearance: none;
257
+ }
258
+ .stat:focus { outline: none; }
259
+ .stat:focus-visible { outline: 1px solid var(--accent); outline-offset: -1px; }
260
+ .stat-canvas {
261
+ position: absolute;
262
+ inset: 0;
263
+ width: 100%;
264
+ height: 100%;
265
+ pointer-events: none;
266
+ opacity: 0.6;
267
+ }
268
+ .stat-label {
269
+ position: relative;
270
+ font-size: 10px;
271
+ text-transform: uppercase;
272
+ letter-spacing: 0.06em;
273
+ color: var(--stat-color, var(--fg-dim));
274
+ }
275
+ .stat-value {
276
+ position: relative;
277
+ font-size: 15px;
278
+ font-variant-numeric: tabular-nums;
279
+ font-weight: 500;
280
+ }
281
+ .stat:hover { background: rgba(255, 255, 255, 0.02); }
282
+ .stat-selected { box-shadow: inset 0 0 0 1px var(--stat-color, var(--accent)); }
283
+
284
+ .stat-detail {
285
+ margin: 0 10px 8px;
286
+ padding: 8px 10px;
287
+ background: var(--bg);
288
+ border: 1px solid var(--border);
289
+ border-top: 2px solid var(--stat-color, var(--accent));
290
+ border-radius: 3px;
291
+ }
292
+ .stat-detail-header {
293
+ display: flex;
294
+ align-items: center;
295
+ gap: 12px;
296
+ margin-bottom: 6px;
297
+ }
298
+ .stat-detail-label {
299
+ font-size: 11px;
300
+ text-transform: uppercase;
301
+ letter-spacing: 0.06em;
302
+ color: var(--stat-color, var(--fg));
303
+ }
304
+ .stat-detail-samples { font-size: 10px; color: var(--fg-dim); }
305
+ .stat-detail-close {
306
+ margin-left: auto;
307
+ background: transparent;
308
+ border: none;
309
+ color: var(--fg-dim);
310
+ cursor: pointer;
311
+ font-size: 16px;
312
+ line-height: 1;
313
+ padding: 0 4px;
314
+ }
315
+ .stat-detail-close:hover { color: var(--fg); }
316
+ .stat-detail-metrics {
317
+ display: grid;
318
+ grid-template-columns: repeat(6, 1fr);
319
+ gap: 4px;
320
+ margin-bottom: 6px;
321
+ }
322
+ .stat-metric {
323
+ display: flex;
324
+ flex-direction: column;
325
+ gap: 2px;
326
+ padding: 4px 6px;
327
+ background: var(--bg-elev-2);
328
+ border-radius: 2px;
329
+ }
330
+ .stat-metric-label {
331
+ font-size: 9px;
332
+ text-transform: uppercase;
333
+ color: var(--fg-dim);
334
+ letter-spacing: 0.06em;
335
+ }
336
+ .stat-metric-value {
337
+ font-size: 13px;
338
+ font-variant-numeric: tabular-nums;
339
+ }
340
+ .stat-histo {
341
+ display: block;
342
+ width: 100%;
343
+ height: 80px;
344
+ }
345
+
346
+ .protocol-panel {
347
+ display: flex;
348
+ flex-direction: column;
349
+ min-height: 0;
350
+ /* Fill the column; scroll internally via .protocol-body so the
351
+ * log can't push the page taller as messages accumulate. */
352
+ flex: 1;
353
+ }
354
+ .protocol-header {
355
+ display: flex;
356
+ align-items: center;
357
+ gap: 8px;
358
+ text-transform: none;
359
+ letter-spacing: 0;
360
+ }
361
+ .protocol-header > span:first-child {
362
+ font-size: 11px;
363
+ text-transform: uppercase;
364
+ letter-spacing: 0.06em;
365
+ flex: 0 0 auto;
366
+ }
367
+ .protocol-filter {
368
+ flex: 1;
369
+ min-width: 0;
370
+ background: var(--bg);
371
+ color: var(--fg);
372
+ border: 1px solid var(--border);
373
+ border-radius: 3px;
374
+ padding: 3px 6px;
375
+ font: inherit;
376
+ }
377
+ .protocol-btn {
378
+ background: var(--bg);
379
+ color: var(--fg-dim);
380
+ border: 1px solid var(--border);
381
+ border-radius: 3px;
382
+ padding: 3px 8px;
383
+ cursor: pointer;
384
+ }
385
+ .protocol-btn:hover { color: var(--fg); }
386
+ .protocol-btn-on { color: var(--accent); border-color: rgba(94, 176, 255, 0.5); }
387
+ .protocol-btn[disabled] { opacity: 0.4; cursor: default; }
388
+ .protocol-match-count {
389
+ font-size: 10px;
390
+ color: var(--fg-dim);
391
+ font-variant-numeric: tabular-nums;
392
+ padding: 0 6px;
393
+ background: rgba(94, 176, 255, 0.1);
394
+ border: 1px solid rgba(94, 176, 255, 0.3);
395
+ border-radius: 3px;
396
+ line-height: 18px;
397
+ }
398
+
399
+ .protocol-layout { flex: 1; display: flex; min-height: 0; }
400
+ .protocol-body {
401
+ flex: 1;
402
+ min-width: 0;
403
+ /* `min-height: 0` lets the flex item contract below its
404
+ * virtualization spacer and actually activate the scroll. */
405
+ min-height: 0;
406
+ overflow: auto;
407
+ padding: 0;
408
+ position: relative;
409
+ /* `auto` so scroll gestures chain to the page when the log is
410
+ * already at its top / bottom edge — matches how native mobile
411
+ * nested scroll containers behave. */
412
+ overscroll-behavior: auto;
413
+ scrollbar-width: thin;
414
+ scrollbar-color: var(--border) transparent;
415
+ }
416
+ .protocol-body::-webkit-scrollbar { width: 8px; height: 8px; }
417
+ .protocol-body::-webkit-scrollbar-track { background: transparent; }
418
+ .protocol-body::-webkit-scrollbar-thumb {
419
+ background: rgba(255, 255, 255, 0.12);
420
+ border-radius: 4px;
421
+ }
422
+ .protocol-body::-webkit-scrollbar-thumb:hover {
423
+ background: rgba(255, 255, 255, 0.2);
424
+ }
425
+ .protocol-spacer { position: relative; width: 100%; }
426
+ .protocol-row {
427
+ position: absolute;
428
+ top: 0;
429
+ left: 0;
430
+ right: 0;
431
+ display: grid;
432
+ grid-template-columns: 14px 90px 60px 120px 1fr 56px;
433
+ gap: 8px;
434
+ padding: 0 10px;
435
+ background: transparent;
436
+ border: none;
437
+ cursor: pointer;
438
+ text-align: left;
439
+ font-size: 11px;
440
+ font-variant-numeric: tabular-nums;
441
+ align-items: center;
442
+ white-space: nowrap;
443
+ will-change: transform;
444
+ }
445
+ .protocol-frame { color: var(--fg-dim); font-size: 10px; }
446
+ .protocol-size { color: var(--fg-dim); text-align: right; }
447
+ .protocol-row-hover { background: rgba(255, 255, 255, 0.04); }
448
+ .protocol-row-selected {
449
+ background: rgba(94, 176, 255, 0.1) !important;
450
+ box-shadow: inset 2px 0 0 var(--accent);
451
+ }
452
+ .protocol-row-placeholder {
453
+ color: rgba(255, 255, 255, 0.2);
454
+ }
455
+ .protocol-dir { font-weight: 700; }
456
+ .protocol-row-in .protocol-dir { color: #5ed38a; }
457
+ .protocol-row-out .protocol-dir { color: #f0b429; }
458
+ .protocol-time { color: var(--fg-dim); }
459
+ .protocol-type { color: var(--fg); }
460
+ .protocol-tag {
461
+ color: var(--fg-dim);
462
+ overflow: hidden;
463
+ text-overflow: ellipsis;
464
+ white-space: nowrap;
465
+ }
466
+
467
+ .protocol-detail {
468
+ flex: 0 0 420px;
469
+ display: flex;
470
+ flex-direction: column;
471
+ border-left: 1px solid var(--border);
472
+ background: var(--bg);
473
+ min-width: 0;
474
+ }
475
+ .protocol-detail-header {
476
+ display: flex;
477
+ align-items: center;
478
+ justify-content: space-between;
479
+ gap: 8px;
480
+ padding: 6px 10px;
481
+ border-bottom: 1px solid var(--border);
482
+ }
483
+ .protocol-detail-title {
484
+ overflow: hidden;
485
+ text-overflow: ellipsis;
486
+ white-space: nowrap;
487
+ }
488
+ .protocol-detail-meta {
489
+ display: flex;
490
+ gap: 12px;
491
+ padding: 4px 10px;
492
+ font-size: 10px;
493
+ color: var(--fg-dim);
494
+ border-bottom: 1px solid var(--border);
495
+ }
496
+ .protocol-detail-body {
497
+ flex: 1;
498
+ margin: 0;
499
+ padding: 8px 10px;
500
+ overflow: auto;
501
+ font-size: 11px;
502
+ color: var(--fg-dim);
503
+ white-space: pre;
504
+ }
505
+ .json-key { color: #8ab4ff; }
506
+ .json-str { color: #c3e88d; }
507
+ .json-num { color: #f78c6c; }
508
+ .json-bool { color: #c792ea; }
509
+ .json-null { color: #89ddff; font-style: italic; }
510
+
511
+ .registry-panel {
512
+ display: flex;
513
+ flex-direction: column;
514
+ flex: 2;
515
+ min-height: 0;
516
+ }
517
+ .registry-header {
518
+ display: flex;
519
+ align-items: center;
520
+ gap: 8px;
521
+ text-transform: none;
522
+ letter-spacing: 0;
523
+ }
524
+ .registry-header > span:first-child {
525
+ font-size: 11px;
526
+ text-transform: uppercase;
527
+ letter-spacing: 0.06em;
528
+ }
529
+ .registry-count {
530
+ font-size: 10px;
531
+ color: var(--fg-dim);
532
+ font-variant-numeric: tabular-nums;
533
+ }
534
+ .registry-layout { flex: 1; display: flex; min-height: 0; min-width: 0; }
535
+ .registry-list {
536
+ list-style: none;
537
+ margin: 0;
538
+ padding: 4px 0;
539
+ overflow-y: auto;
540
+ flex: 0 0 260px;
541
+ border-right: 1px solid var(--border);
542
+ background: var(--bg-elev-2);
543
+ }
544
+ .registry-row {
545
+ display: grid;
546
+ grid-template-columns: auto 1fr auto auto;
547
+ gap: 8px;
548
+ align-items: center;
549
+ width: 100%;
550
+ padding: 4px 10px;
551
+ background: transparent;
552
+ border: none;
553
+ cursor: pointer;
554
+ text-align: left;
555
+ font-size: 11px;
556
+ color: var(--fg);
557
+ }
558
+ .registry-sparkline { image-rendering: pixelated; }
559
+ .registry-viz-wrap {
560
+ /* Wrapper supplies the horizontal breathing room; the canvas
561
+ * below expands to fill it. Previous version applied `margin: 0
562
+ * 10px` directly to the canvas alongside `width: 100%`, which
563
+ * added up to `100% + 20px` and pushed the graph past the panel
564
+ * border on every render. */
565
+ padding: 0 10px;
566
+ box-sizing: border-box;
567
+ width: 100%;
568
+ }
569
+ .registry-viz-stack {
570
+ position: relative;
571
+ width: 100%;
572
+ height: 80px;
573
+ }
574
+ .registry-viz {
575
+ display: block;
576
+ width: 100%;
577
+ height: 100%;
578
+ background: rgba(0, 2, 28, 0.35);
579
+ border-radius: 2px;
580
+ }
581
+ .registry-viz-stack { cursor: crosshair; }
582
+ .registry-viz-cursor-line {
583
+ position: absolute;
584
+ top: 0;
585
+ bottom: 0;
586
+ width: 1px;
587
+ background: rgba(255, 255, 255, 0.9);
588
+ pointer-events: none;
589
+ display: none;
590
+ /* Imperative positioning driven directly by mousemove — no React
591
+ * re-render per frame, just a style.left update. */
592
+ will-change: left;
593
+ }
594
+ .registry-row:hover { background: rgba(255, 255, 255, 0.03); }
595
+ .registry-row-selected {
596
+ background: rgba(94, 176, 255, 0.1);
597
+ box-shadow: inset 2px 0 0 var(--accent);
598
+ }
599
+ .registry-kind {
600
+ font-size: 9px;
601
+ text-transform: uppercase;
602
+ color: var(--fg-dim);
603
+ letter-spacing: 0.04em;
604
+ }
605
+ .registry-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
606
+ .registry-count-pill {
607
+ color: var(--fg-dim);
608
+ font-variant-numeric: tabular-nums;
609
+ font-size: 10px;
610
+ }
611
+
612
+ .registry-detail {
613
+ flex: 1;
614
+ min-width: 0;
615
+ display: flex;
616
+ flex-direction: column;
617
+ background: var(--bg);
618
+ }
619
+ .registry-detail-header {
620
+ display: flex;
621
+ gap: 8px;
622
+ padding: 6px 10px;
623
+ border-bottom: 1px solid var(--border);
624
+ align-items: baseline;
625
+ }
626
+ .registry-detail-name { font-weight: 600; }
627
+ .registry-detail-label { color: var(--fg-dim); font-size: 11px; }
628
+ .registry-detail-meta {
629
+ display: flex;
630
+ gap: 12px;
631
+ padding: 4px 10px;
632
+ font-size: 10px;
633
+ color: var(--fg-dim);
634
+ border-bottom: 1px solid var(--border);
635
+ }
636
+ /* Flex-column so the inner `.sample-grid-scroller` can `flex: 1` +
637
+ * overflow inside. Previously `overflow: auto` on this element
638
+ * itself made it the scroller, while the inner scroller had no
639
+ * flex context and sized to content — `scrollTo` on the inner was
640
+ * a no-op because the outer was what actually scrolled. */
641
+ .registry-detail-body {
642
+ flex: 1;
643
+ min-height: 0;
644
+ display: flex;
645
+ flex-direction: column;
646
+ padding: 6px 10px;
647
+ font-size: 11px;
648
+ }
649
+
650
+ .sample-grid-scroller {
651
+ flex: 1;
652
+ min-height: 0;
653
+ overflow: auto;
654
+ }
655
+ .sample-grid {
656
+ position: relative;
657
+ /* Fixed-width decimal "hex-viewer"-style grid — monospace font,
658
+ * tabular figures, and each cell padded to the same char count by
659
+ * the formatter. Raw value lives in each cell's `title` so hover
660
+ * shows full precision. */
661
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
662
+ font-variant-numeric: tabular-nums;
663
+ white-space: pre;
664
+ }
665
+ .sample-row {
666
+ position: absolute;
667
+ left: 0;
668
+ right: 0;
669
+ display: grid;
670
+ /* 8-char cell + 1 char padding via ch units so columns line up
671
+ * regardless of zoom / font-size. */
672
+ grid-template-columns: 5ch repeat(8, 9ch);
673
+ gap: 6px;
674
+ align-items: center;
675
+ will-change: transform;
676
+ }
677
+ .sample-index { color: var(--fg-dim); font-size: 10px; text-align: right; padding-right: 4px; }
678
+ .sample-cell {
679
+ text-align: right;
680
+ color: var(--fg);
681
+ padding: 1px 4px;
682
+ border-radius: 2px;
683
+ cursor: pointer;
684
+ }
685
+ .sample-cell:hover { background: rgba(255, 255, 255, 0.04); }
686
+ .sample-cell-cursor {
687
+ background: rgba(255, 255, 255, 0.12);
688
+ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.3);
689
+ }
690
+ .registry-cursor-label {
691
+ color: var(--fg);
692
+ background: rgba(255, 255, 255, 0.08);
693
+ border-radius: 2px;
694
+ padding: 0 4px;
695
+ }
696
+
697
+ .buffers-panel {
698
+ display: flex;
699
+ flex-direction: column;
700
+ /* Share the column with Registry — 3:2 split is a good default
701
+ * for the canvas/preview + entry list rhythm. Both need
702
+ * `min-height: 0` so their internal overflow containers can
703
+ * actually scroll instead of expanding the panel. */
704
+ flex: 3;
705
+ min-height: 0;
706
+ }
707
+ .buffers-header {
708
+ display: flex;
709
+ align-items: center;
710
+ gap: 8px;
711
+ text-transform: none;
712
+ letter-spacing: 0;
713
+ }
714
+ .buffers-header > span:first-child {
715
+ font-size: 11px;
716
+ text-transform: uppercase;
717
+ letter-spacing: 0.06em;
718
+ }
719
+ .buffers-layout { flex: 1; display: flex; min-height: 0; min-width: 0; }
720
+ .buffers-list {
721
+ list-style: none;
722
+ margin: 0;
723
+ padding: 4px 0;
724
+ overflow-y: auto;
725
+ flex: 0 0 260px;
726
+ border-right: 1px solid var(--border);
727
+ background: var(--bg-elev-2);
728
+ }
729
+ .buffers-row {
730
+ display: flex;
731
+ flex-direction: column;
732
+ gap: 2px;
733
+ width: 100%;
734
+ padding: 6px 10px;
735
+ background: transparent;
736
+ border: none;
737
+ cursor: pointer;
738
+ text-align: left;
739
+ font-size: 11px;
740
+ color: var(--fg);
741
+ }
742
+ .buffers-row:hover { background: rgba(255, 255, 255, 0.03); }
743
+ .buffers-row-selected {
744
+ background: rgba(94, 176, 255, 0.1);
745
+ box-shadow: inset 2px 0 0 var(--accent);
746
+ }
747
+ .buffers-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
748
+ .buffers-meta {
749
+ color: var(--fg-dim);
750
+ font-size: 10px;
751
+ font-variant-numeric: tabular-nums;
752
+ }
753
+ .buffers-stage {
754
+ flex: 1;
755
+ min-width: 0;
756
+ position: relative;
757
+ background: var(--bg);
758
+ display: flex;
759
+ align-items: center;
760
+ justify-content: center;
761
+ padding: 8px;
762
+ overflow: hidden;
763
+ }
764
+ .buffers-canvas {
765
+ image-rendering: pixelated;
766
+ display: block;
767
+ background: rgba(0, 2, 28, 0.6);
768
+ border-radius: 2px;
769
+ }
770
+ .buffers-info {
771
+ position: absolute;
772
+ bottom: 8px;
773
+ left: 12px;
774
+ right: 12px;
775
+ display: flex;
776
+ gap: 12px;
777
+ font-size: 10px;
778
+ color: var(--fg-dim);
779
+ background: rgba(0, 0, 0, 0.5);
780
+ border-radius: 3px;
781
+ padding: 3px 8px;
782
+ pointer-events: none;
783
+ }
784
+ .buffers-warn { color: #f0b429; }
785
+ .buffers-close {
786
+ position: absolute;
787
+ top: 6px;
788
+ right: 8px;
789
+ background: rgba(0, 0, 0, 0.5);
790
+ color: var(--fg-dim);
791
+ border: 1px solid var(--border);
792
+ border-radius: 3px;
793
+ cursor: pointer;
794
+ padding: 0 6px;
795
+ line-height: 18px;
796
+ font-size: 14px;
797
+ }
798
+ .buffers-close:hover { color: var(--fg); background: rgba(0, 0, 0, 0.7); }
799
+
800
+ .env-popover { position: relative; }
801
+ .env-trigger {
802
+ display: inline-flex;
803
+ align-items: center;
804
+ gap: 6px;
805
+ padding: 4px 10px;
806
+ background: var(--bg);
807
+ color: var(--fg);
808
+ border: 1px solid var(--border);
809
+ border-radius: 3px;
810
+ cursor: pointer;
811
+ font-size: 11px;
812
+ }
813
+ .env-trigger-open { border-color: var(--accent); }
814
+ .env-trigger-summary { color: var(--fg-dim); }
815
+ .env-caret { color: var(--fg-dim); font-size: 8px; }
816
+ .env-menu {
817
+ position: absolute;
818
+ top: calc(100% + 6px);
819
+ right: 0;
820
+ min-width: 280px;
821
+ padding: 8px 12px;
822
+ background: var(--bg-elev-2);
823
+ border: 1px solid var(--border);
824
+ border-radius: 3px;
825
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
826
+ z-index: 100;
827
+ }
828
+ .env-row {
829
+ display: flex;
830
+ justify-content: space-between;
831
+ gap: 16px;
832
+ padding: 2px 0;
833
+ font-size: 11px;
834
+ }
835
+ .env-label { color: var(--fg-dim); }
836
+ .env-value { font-variant-numeric: tabular-nums; color: var(--fg); }
837
+
838
+ .buffers-canvas {
839
+ transform-origin: center center;
840
+ will-change: transform;
841
+ }
842
+ .buffers-zoom { margin-left: auto; color: var(--fg-dim); }
843
+ .buffers-probe {
844
+ position: absolute;
845
+ top: 6px;
846
+ left: 8px;
847
+ display: inline-flex;
848
+ align-items: center;
849
+ gap: 8px;
850
+ padding: 3px 8px;
851
+ background: rgba(0, 0, 0, 0.6);
852
+ border: 1px solid var(--border);
853
+ border-radius: 3px;
854
+ color: var(--fg);
855
+ font-size: 10px;
856
+ font-variant-numeric: tabular-nums;
857
+ pointer-events: none;
858
+ }
859
+ .probe-chip {
860
+ width: 10px;
861
+ height: 10px;
862
+ border-radius: 2px;
863
+ border: 1px solid rgba(255, 255, 255, 0.2);
864
+ }
865
+ .buffers-reset {
866
+ position: absolute;
867
+ top: 6px;
868
+ right: 38px;
869
+ background: rgba(0, 0, 0, 0.5);
870
+ color: var(--fg-dim);
871
+ border: 1px solid var(--border);
872
+ border-radius: 3px;
873
+ cursor: pointer;
874
+ padding: 0 6px;
875
+ line-height: 18px;
876
+ font-size: 14px;
877
+ }
878
+ .buffers-reset:hover { color: var(--fg); background: rgba(0, 0, 0, 0.7); }
879
+ .buffers-stage { user-select: none; }
880
+
881
+ .protocol-dir-toggle { display: inline-flex; gap: 2px; }
882
+ .protocol-filter-menu-wrap { position: relative; }
883
+ .protocol-filter-menu {
884
+ position: absolute;
885
+ top: calc(100% + 6px);
886
+ right: 0;
887
+ min-width: 260px;
888
+ padding: 8px;
889
+ background: var(--bg-elev-2);
890
+ border: 1px solid var(--border);
891
+ border-radius: 3px;
892
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
893
+ z-index: 100;
894
+ }
895
+ .protocol-filter-menu-section { margin-bottom: 10px; }
896
+ .protocol-filter-menu-section:last-child { margin-bottom: 0; }
897
+ .protocol-filter-menu-title {
898
+ display: flex;
899
+ justify-content: space-between;
900
+ align-items: center;
901
+ font-size: 10px;
902
+ text-transform: uppercase;
903
+ color: var(--fg-dim);
904
+ letter-spacing: 0.06em;
905
+ margin-bottom: 4px;
906
+ }
907
+ .protocol-filter-menu-clear {
908
+ padding: 0 6px;
909
+ font-size: 10px;
910
+ }
911
+ .protocol-filter-menu-value {
912
+ display: inline-block;
913
+ margin-left: 8px;
914
+ font-size: 11px;
915
+ color: var(--fg-dim);
916
+ font-variant-numeric: tabular-nums;
917
+ }
918
+ .protocol-filter-menu input[type="range"] {
919
+ vertical-align: middle;
920
+ width: 160px;
921
+ }
922
+ .protocol-filter-types {
923
+ list-style: none;
924
+ margin: 0;
925
+ padding: 0;
926
+ max-height: 240px;
927
+ overflow-y: auto;
928
+ }
929
+ .protocol-filter-type {
930
+ display: flex;
931
+ align-items: center;
932
+ gap: 6px;
933
+ padding: 2px 4px;
934
+ font-size: 11px;
935
+ cursor: pointer;
936
+ }
937
+ .protocol-filter-type:hover { background: rgba(255, 255, 255, 0.03); }
938
+ .protocol-filter-type input { margin: 0; }
939
+
940
+ /*
941
+ * Narrow viewports (<= 1150px): give up the viewport-bound layout.
942
+ * Stacking four panels in one column squeezes everything below
943
+ * the usable threshold — canvas previews, registry graphs, and
944
+ * protocol rows all need real height. Let the page scroll with
945
+ * per-panel fixed heights so cascading doesn't un-do them.
946
+ *
947
+ * NOTE: This block sits at the very end of the stylesheet on
948
+ * purpose — the cascade goes top-down at equal specificity, so
949
+ * placing panel height overrides above the default `.buffers-panel
950
+ * { flex: 3 }` / `.registry-panel { flex: 2 }` rules means those
951
+ * rules win and the narrow-mode sizes get discarded. Keep it last.
952
+ */
953
+ /* ── Batches panel ──────────────────────────────────────────────── */
954
+ .batches-panel {
955
+ display: flex;
956
+ flex-direction: column;
957
+ flex: 2;
958
+ min-height: 0;
959
+ overflow: hidden;
960
+ }
961
+ .batches-header {
962
+ display: flex;
963
+ align-items: baseline;
964
+ justify-content: space-between;
965
+ gap: 8px;
966
+ text-transform: none;
967
+ letter-spacing: 0;
968
+ }
969
+ .batches-header > span:first-child {
970
+ font-size: 11px;
971
+ text-transform: uppercase;
972
+ letter-spacing: 0.06em;
973
+ }
974
+ .batches-header-meta {
975
+ font-size: 10px;
976
+ color: var(--fg-dim);
977
+ font-variant-numeric: tabular-nums;
978
+ }
979
+ /* Single scroll container for both sections so the panel scrolls
980
+ * naturally inside `.dashboard-split-col` without nested scrollers. */
981
+ .batches-scroll { flex: 1; overflow-y: auto; min-height: 0; }
982
+ .batches-section { border-top: 1px solid var(--border); }
983
+ .batches-section:first-of-type { border-top: none; }
984
+ .batches-section-title {
985
+ display: flex;
986
+ align-items: center;
987
+ gap: 6px;
988
+ width: 100%;
989
+ padding: 4px 10px;
990
+ font-size: 10px;
991
+ text-transform: uppercase;
992
+ letter-spacing: 0.06em;
993
+ color: var(--fg-dim);
994
+ /* Solid fill — sticky headers mask scrolling content beneath.
995
+ * Layering the subtle highlight over the panel base color keeps
996
+ * the existing shade while blocking the rows behind. */
997
+ background: var(--bg-elev-2);
998
+ background-image: linear-gradient(rgba(255, 255, 255, 0.02), rgba(255, 255, 255, 0.02));
999
+ position: sticky;
1000
+ top: 0;
1001
+ z-index: 2;
1002
+ border-bottom: 1px solid var(--border);
1003
+ }
1004
+ .batches-section-count {
1005
+ margin-left: auto;
1006
+ color: var(--fg-dim);
1007
+ font-variant-numeric: tabular-nums;
1008
+ text-transform: none;
1009
+ letter-spacing: 0;
1010
+ }
1011
+ /* Per-pass collapse caret in the tree. Styled as a tiny inline
1012
+ * button; `--leaf` variant is an invisible placeholder so leaf
1013
+ * and parent rows share the same label x-offset. */
1014
+ .batches-tree-caret {
1015
+ display: inline-flex;
1016
+ align-items: center;
1017
+ justify-content: center;
1018
+ width: 12px;
1019
+ height: 12px;
1020
+ margin-right: 2px;
1021
+ padding: 0;
1022
+ border: 0;
1023
+ background: transparent;
1024
+ box-shadow: none;
1025
+ -webkit-appearance: none;
1026
+ appearance: none;
1027
+ color: var(--fg-dim);
1028
+ font: inherit;
1029
+ font-size: 9px;
1030
+ line-height: 1;
1031
+ cursor: pointer;
1032
+ flex-shrink: 0;
1033
+ }
1034
+ .batches-tree-caret:hover { color: var(--fg); }
1035
+ .batches-tree-caret:focus { outline: none; }
1036
+ .batches-tree-caret:focus-visible { outline: 1px dotted var(--fg-dim); outline-offset: 1px; }
1037
+ .batches-tree-caret--leaf { cursor: default; visibility: hidden; }
1038
+ .batches-pass-label {
1039
+ display: flex;
1040
+ align-items: center;
1041
+ gap: 2px;
1042
+ min-width: 0;
1043
+ }
1044
+ .batches-pass-label > span:last-child {
1045
+ overflow: hidden;
1046
+ text-overflow: ellipsis;
1047
+ white-space: nowrap;
1048
+ }
1049
+ .batches-pass-row--parent { font-weight: 500; }
1050
+ .batches-table-head,
1051
+ .batches-pass-row,
1052
+ .batches-total-row,
1053
+ .batches-run-head,
1054
+ .batches-batch-row {
1055
+ display: grid;
1056
+ gap: 8px;
1057
+ padding: 3px 10px;
1058
+ font-size: 11px;
1059
+ font-variant-numeric: tabular-nums;
1060
+ align-items: center;
1061
+ }
1062
+ /* Passes section: label | calls | tris | ms (4 columns). */
1063
+ .batches-table-head--passes,
1064
+ .batches-pass-row,
1065
+ .batches-total-row {
1066
+ grid-template-columns: 1fr 60px 70px 60px;
1067
+ }
1068
+ /* Runs section: label | layer | sprites (3 columns). Header + run
1069
+ * rows + per-batch rows all use the same template so labels line up. */
1070
+ .batches-table-head--runs,
1071
+ .batches-run-head,
1072
+ .batches-batch-row {
1073
+ grid-template-columns: 1fr 60px 70px;
1074
+ }
1075
+ .batches-batch-row {
1076
+ padding-left: 22px;
1077
+ }
1078
+ .batches-table-head {
1079
+ color: var(--fg-dim);
1080
+ font-size: 10px;
1081
+ text-transform: uppercase;
1082
+ letter-spacing: 0.04em;
1083
+ border-bottom: 1px solid var(--border);
1084
+ padding-top: 4px;
1085
+ padding-bottom: 4px;
1086
+ }
1087
+ .batches-col-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1088
+ .batches-col-num { text-align: right; color: var(--fg); }
1089
+ .batches-table-head .batches-col-num { color: var(--fg-dim); }
1090
+ .batches-pass-list,
1091
+ .batches-run-list { list-style: none; margin: 0; padding: 0; }
1092
+ .batches-pass-row:nth-child(even) { background: rgba(255, 255, 255, 0.02); }
1093
+ .batches-total-row {
1094
+ border-top: 1px solid var(--border);
1095
+ background: rgba(255, 255, 255, 0.03);
1096
+ color: var(--fg);
1097
+ font-weight: 600;
1098
+ }
1099
+ .batches-run { border-bottom: 1px dotted rgba(255, 255, 255, 0.06); }
1100
+ .batches-run-label { display: flex; gap: 6px; align-items: baseline; }
1101
+ .batches-kind {
1102
+ display: inline-block;
1103
+ padding: 0 5px;
1104
+ margin-right: 4px;
1105
+ border-radius: 3px;
1106
+ font-size: 9px;
1107
+ text-transform: uppercase;
1108
+ letter-spacing: 0.04em;
1109
+ line-height: 14px;
1110
+ color: var(--fg-dim);
1111
+ background: rgba(255, 255, 255, 0.06);
1112
+ border: 1px solid var(--border);
1113
+ flex-shrink: 0;
1114
+ }
1115
+ .batches-kind--sprite { color: #47cc6a; border-color: rgba(71, 204, 106, 0.35); }
1116
+ .batches-kind--tilechunk { color: #9d7aff; border-color: rgba(157, 122, 255, 0.35); }
1117
+ .batches-kind--mesh { color: #ffa347; border-color: rgba(255, 163, 71, 0.35); }
1118
+ .batches-mat { color: var(--fg); }
1119
+ .batches-run-sub {
1120
+ font-size: 10px;
1121
+ color: var(--fg-dim);
1122
+ font-variant-numeric: tabular-nums;
1123
+ }
1124
+ .batches-run-children { list-style: none; margin: 0; padding: 0; }
1125
+ .batches-batch-label { color: var(--fg-dim); }
1126
+ .batches-empty { padding: 8px 10px; color: var(--fg-dim); font-size: 11px; }
1127
+
1128
+ @media (max-width: 1150px) {
1129
+ html, body { overflow: auto; }
1130
+ .dashboard-root {
1131
+ height: auto;
1132
+ min-height: 100vh;
1133
+ min-height: 100dvh;
1134
+ }
1135
+ .dashboard-content { overflow: visible; min-height: 0; }
1136
+ .dashboard-split { flex-direction: column; flex: initial; }
1137
+ .dashboard-split-col { flex: initial; }
1138
+ .buffers-panel { flex: initial; height: 340px; }
1139
+ .batches-panel { flex: initial; height: 360px; }
1140
+ .registry-panel { flex: initial; height: 300px; }
1141
+ .protocol-panel { flex: initial; height: 420px; }
1142
+ }
1143
+ </style>
1144
+ </head>
1145
+ <body>
1146
+ <div id="root"></div>
1147
+ <script type="module" src="./index.tsx"></script>
1148
+ </body>
1149
+ </html>