@matware/e2e-runner 1.3.1 → 1.5.1

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 (50) hide show
  1. package/.claude-plugin/marketplace.json +4 -4
  2. package/.claude-plugin/plugin.json +2 -2
  3. package/LICENSE +1 -1
  4. package/README.md +491 -225
  5. package/agents/test-creator.md +4 -2
  6. package/agents/test-improver.md +7 -4
  7. package/bin/cli.js +93 -19
  8. package/package.json +4 -3
  9. package/skills/e2e-testing/SKILL.md +5 -3
  10. package/skills/e2e-testing/references/action-types.md +35 -18
  11. package/skills/e2e-testing/references/test-json-format.md +23 -0
  12. package/skills/e2e-testing/references/troubleshooting.md +2 -26
  13. package/src/actions.js +181 -15
  14. package/src/config.js +6 -0
  15. package/src/dashboard.js +185 -9
  16. package/src/db.js +26 -0
  17. package/src/mcp-tools.js +238 -69
  18. package/src/module-analysis.js +247 -0
  19. package/src/module-resolver.js +35 -2
  20. package/src/narrate.js +33 -1
  21. package/src/pool-manager.js +46 -1
  22. package/src/pool.js +177 -20
  23. package/src/runner.js +144 -19
  24. package/src/visual-diff.js +74 -4
  25. package/src/websocket.js +14 -3
  26. package/src/wizard.js +184 -0
  27. package/templates/build-dashboard.js +3 -0
  28. package/templates/dashboard/js/api.js +60 -3
  29. package/templates/dashboard/js/init.js +46 -0
  30. package/templates/dashboard/js/keyboard.js +8 -7
  31. package/templates/dashboard/js/quicksearch.js +277 -0
  32. package/templates/dashboard/js/state.js +61 -7
  33. package/templates/dashboard/js/toast.js +1 -1
  34. package/templates/dashboard/js/utils.js +23 -2
  35. package/templates/dashboard/js/view-live.js +235 -42
  36. package/templates/dashboard/js/view-runs.js +469 -42
  37. package/templates/dashboard/js/view-tests.js +157 -16
  38. package/templates/dashboard/js/view-tools.js +234 -0
  39. package/templates/dashboard/js/view-watch.js +2 -2
  40. package/templates/dashboard/js/websocket.js +33 -3
  41. package/templates/dashboard/styles/base.css +489 -53
  42. package/templates/dashboard/styles/components.css +736 -84
  43. package/templates/dashboard/styles/view-live.css +459 -78
  44. package/templates/dashboard/styles/view-runs.css +826 -177
  45. package/templates/dashboard/styles/view-tests.css +440 -77
  46. package/templates/dashboard/styles/view-tools.css +206 -0
  47. package/templates/dashboard/styles/view-watch.css +198 -41
  48. package/templates/dashboard/template.html +356 -58
  49. package/templates/dashboard.html +5354 -722
  50. package/templates/docker-compose-lightpanda.yml +7 -0
@@ -1,97 +1,478 @@
1
- /* ── Live Execution ── */
2
- .live-panel{display:none;background:var(--surface);border:1px solid var(--purple);border-radius:var(--r);overflow:hidden;animation:fadeSlide .3s ease;flex-direction:column}
1
+ /* ═══════════════════════════════════════════════════════════════════
2
+ VIEW · LIVE Execution Stream, Screencast, Section Headers
3
+ ═══════════════════════════════════════════════════════════════════ */
4
+
5
+ .live-panel{
6
+ display:none;
7
+ background:var(--surface);
8
+ border:1px solid var(--radio);
9
+ border-radius:var(--r);
10
+ overflow:hidden;
11
+ animation:fadeSlide .3s ease;
12
+ flex-direction:column;
13
+ position:relative;
14
+ box-shadow:0 0 0 1px rgba(255,45,141,.15),0 0 40px rgba(255,45,141,.08);
15
+ }
16
+ .live-panel::before{
17
+ content:'LIVE·EXECUTION';
18
+ position:absolute;top:-9px;left:22px;z-index:3;
19
+ background:var(--bg);padding:0 10px;
20
+ font-family:var(--mono);font-size:9px;font-weight:700;
21
+ letter-spacing:.32em;color:var(--radio);
22
+ text-shadow:0 0 10px var(--radio-glow);
23
+ }
3
24
  .live-panel.active{display:flex;flex:1;min-height:0}
4
25
  @keyframes fadeSlide{from{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}
5
- .live-header{padding:14px 16px;display:flex;align-items:center;gap:16px;border-bottom:1px solid var(--border);background:var(--purple-dim)}
6
- .live-header .label{font-weight:600;color:var(--purple);font-size:12px;display:flex;align-items:center;gap:8px}
7
- .live-header .label .dot{width:8px;height:8px;border-radius:50%;background:var(--purple);animation:pulse 1.5s infinite}
8
- @keyframes pulse{0%,100%{opacity:1}50%{opacity:.3}}
9
- .live-project{display:flex;align-items:center;padding:2px 10px;background:rgba(255,255,255,.05);border-radius:4px;border:1px solid var(--border)}
10
- .live-stats{display:flex;gap:16px;font-size:11px;color:var(--text2);margin-left:auto}
11
- .live-stats span strong{color:var(--text)}
12
- .live-progress{height:3px;background:var(--surface3)}
13
- .live-progress-fill{height:100%;background:var(--purple);transition:width .4s;border-radius:0 2px 2px 0}
14
- .live-tests{padding:12px 16px;display:flex;flex-direction:column;gap:2px;overflow-y:auto;min-height:0;flex:1}
15
- .live-test{padding:10px 12px;border-radius:var(--r);border-left:3px solid var(--text3);background:var(--surface2);font-size:11px;transition:border-color .2s,padding .25s,max-height .35s cubic-bezier(.4,0,.2,1)}
16
- .live-test.running{border-left-color:var(--purple)}
17
- .live-test.passed{border-left-color:var(--green)}
18
- .live-test.failed{border-left-color:var(--red)}
19
- .live-test.collapsed{cursor:pointer;padding:6px 12px}
20
- .live-test.collapsed:hover{background:var(--surface3)}
26
+
27
+ .live-header{
28
+ padding:16px 20px;
29
+ display:flex;align-items:center;gap:18px;
30
+ border-bottom:1px solid var(--border);
31
+ background:
32
+ repeating-linear-gradient(0deg,transparent 0 2px,rgba(255,45,141,.02) 2px 3px),
33
+ linear-gradient(90deg,var(--radio-dim),transparent);
34
+ }
35
+ .live-header .label{
36
+ font-weight:700;color:var(--radio);
37
+ font-size:10px;letter-spacing:.24em;text-transform:uppercase;
38
+ display:flex;align-items:center;gap:10px;
39
+ text-shadow:0 0 10px var(--radio-glow);
40
+ }
41
+ .live-header .label .dot{
42
+ width:10px;height:10px;border-radius:50%;
43
+ background:var(--radio);
44
+ box-shadow:0 0 14px var(--radio-glow),0 0 3px var(--radio);
45
+ animation:radar 1.6s cubic-bezier(.5,.1,.5,1) infinite;
46
+ position:relative;
47
+ }
48
+ .live-header .label .dot::before{
49
+ content:'';position:absolute;inset:-4px;border-radius:50%;
50
+ border:1px solid var(--radio);
51
+ animation:ping 1.6s cubic-bezier(0,0,.2,1) infinite;
52
+ }
53
+ @keyframes radar{0%,100%{opacity:1}50%{opacity:.5}}
54
+ @keyframes ping{
55
+ 0%{transform:scale(1);opacity:.9}
56
+ 80%,100%{transform:scale(2.3);opacity:0}
57
+ }
58
+
59
+ .live-project{
60
+ display:flex;align-items:center;
61
+ padding:4px 12px;
62
+ background:var(--bg-2);border-radius:var(--r);
63
+ border:1px solid var(--border);
64
+ font-family:var(--mono);
65
+ }
66
+ .live-stats{
67
+ display:flex;gap:20px;font-size:10px;color:var(--text2);
68
+ margin-left:auto;font-family:var(--mono);
69
+ letter-spacing:.1em;text-transform:uppercase;font-weight:600;
70
+ }
71
+ .live-stats span{display:flex;align-items:center;gap:6px}
72
+ .live-stats span strong{
73
+ color:var(--text);font-family:var(--display);
74
+ font-size:16px;font-weight:400;letter-spacing:-.01em;
75
+ font-variant-numeric:tabular-nums;
76
+ }
77
+
78
+ .live-progress{
79
+ height:3px;background:var(--surface3);
80
+ position:relative;overflow:hidden;
81
+ }
82
+ .live-progress-fill{
83
+ height:100%;
84
+ background:linear-gradient(90deg,var(--radio),var(--phosphor));
85
+ transition:width .45s;border-radius:0 2px 2px 0;
86
+ box-shadow:0 0 10px var(--radio-glow);
87
+ }
88
+ .live-progress::after{
89
+ content:'';position:absolute;top:0;left:0;right:0;bottom:0;
90
+ background:repeating-linear-gradient(-45deg,transparent 0 6px,rgba(255,255,255,.06) 6px 12px);
91
+ animation:scroll-stripe 1.5s linear infinite;opacity:.4;
92
+ pointer-events:none;
93
+ }
94
+ @keyframes scroll-stripe{to{transform:translateX(12px)}}
95
+
96
+ .live-tests{
97
+ padding:14px 18px;
98
+ display:flex;flex-direction:column;gap:4px;
99
+ overflow-y:auto;min-height:0;flex:1;
100
+ }
101
+ .live-test{
102
+ padding:11px 14px;
103
+ border-radius:var(--r);
104
+ border-left:3px solid var(--text3);
105
+ background:var(--bg-2);
106
+ font-size:11px;
107
+ transition:border-color .2s,padding .25s,max-height .35s cubic-bezier(.4,0,.2,1),background .15s;
108
+ }
109
+ .live-test.running{border-left-color:var(--radio);box-shadow:inset 0 0 24px rgba(255,45,141,.04)}
110
+ .live-test.passed{border-left-color:var(--phosphor)}
111
+ .live-test.failed{border-left-color:var(--crimson);background:rgba(255,77,77,.03)}
112
+ .live-test.collapsed{cursor:pointer;padding:7px 14px}
113
+ .live-test.collapsed:hover{background:var(--surface2)}
21
114
  .live-test.collapsed .lt-meta,.live-test.collapsed .lt-actions,.live-test.collapsed .lt-screenshots{display:none}
22
115
  .live-test.collapsed .lt-name{margin-bottom:0}
23
116
  .live-test.collapsed .lt-summary{display:flex}
24
- .live-test .lt-name{font-weight:600;margin-bottom:4px;display:flex;align-items:center;gap:6px}
25
- .live-test .lt-summary{display:none;align-items:center;gap:8px;margin-left:auto;font-size:10px;color:var(--text3);font-family:var(--mono)}
26
- .live-test .lt-summary .lt-dur{color:var(--text2)}
27
- .live-test .lt-summary .lt-expand{color:var(--purple);font-size:9px;opacity:.6}
28
- .live-test .lt-meta{color:var(--text2);font-size:10px;margin-bottom:6px}
117
+
118
+ .live-test .lt-name{
119
+ font-family:var(--mono);
120
+ font-weight:600;margin-bottom:5px;
121
+ display:flex;align-items:center;gap:8px;
122
+ letter-spacing:.02em;
123
+ }
124
+ .live-test .lt-summary{
125
+ display:none;align-items:center;gap:10px;margin-left:auto;
126
+ font-size:10px;color:var(--text3);font-family:var(--mono);
127
+ letter-spacing:.04em;
128
+ }
129
+ .live-test .lt-summary .lt-dur{color:var(--text2);font-variant-numeric:tabular-nums}
130
+ .live-test .lt-summary .lt-expand{color:var(--radio);font-size:9px;opacity:.7}
131
+ .live-test .lt-meta{
132
+ color:var(--text2);font-size:10px;margin-bottom:7px;
133
+ font-family:var(--mono);letter-spacing:.04em;
134
+ }
29
135
  .live-test .lt-icon{font-size:12px}
30
- .lt-actions{overflow-y:auto;border-top:1px solid var(--border);padding-top:6px;margin-top:4px}
31
- .lt-step{display:flex;align-items:flex-start;gap:6px;padding:2px 0;font-size:10px;font-family:var(--mono);line-height:1.4}
136
+
137
+ .lt-actions{overflow-y:auto;border-top:1px solid var(--border);padding-top:8px;margin-top:6px}
138
+ .lt-step{
139
+ display:flex;align-items:flex-start;gap:8px;
140
+ padding:3px 0;font-size:10px;font-family:var(--mono);line-height:1.5;
141
+ letter-spacing:.01em;
142
+ }
32
143
  .lt-step .step-icon{flex-shrink:0;width:14px;text-align:center}
33
- .lt-step .step-icon.ok{color:var(--green)}
34
- .lt-step .step-icon.fail{color:var(--red)}
35
- .lt-step .step-icon.run{color:var(--purple)}
36
- .lt-step .step-type{color:var(--purple);font-weight:600;flex-shrink:0}
144
+ .lt-step .step-icon.ok{color:var(--phosphor)}
145
+ .lt-step .step-icon.fail{color:var(--crimson)}
146
+ .lt-step .step-icon.run{color:var(--radio)}
147
+ .lt-step .step-type{
148
+ color:var(--radio);font-weight:700;flex-shrink:0;
149
+ letter-spacing:.06em;text-transform:uppercase;font-size:9px;
150
+ min-width:80px;
151
+ }
37
152
  .lt-step .step-detail{color:var(--text2);flex:1;min-width:0;white-space:pre-wrap;word-break:break-word}
38
- .lt-step .step-dur{color:var(--text3);flex-shrink:0;margin-left:auto}
39
- .lt-step.pool-log{background:rgba(99,102,241,.06);border-left:2px solid rgba(99,102,241,.3);padding-left:8px}
40
- .lt-step.pool-log .step-icon{color:#818cf8}
41
- .lt-step.pool-log .step-type{color:#818cf8;font-weight:600}
42
- .lt-screenshots{border-top:1px solid var(--border);margin-top:6px;padding-top:6px}
43
- .lt-screenshots-toggle{display:flex;align-items:center;gap:6px;cursor:pointer;font-size:10px;color:var(--text3);font-family:var(--mono);padding:2px 0;user-select:none}
153
+ .lt-step .step-dur{color:var(--text3);flex-shrink:0;margin-left:auto;font-variant-numeric:tabular-nums}
154
+ .lt-step.pool-log{
155
+ background:var(--beacon-dim);
156
+ border-left:2px solid var(--beacon);
157
+ padding-left:10px;border-radius:2px;
158
+ }
159
+ .lt-step.pool-log .step-icon{color:var(--beacon)}
160
+ .lt-step.pool-log .step-type{color:var(--beacon)}
161
+
162
+ .lt-screenshots{border-top:1px solid var(--border);margin-top:8px;padding-top:8px}
163
+ .lt-screenshots-toggle{
164
+ display:flex;align-items:center;gap:8px;
165
+ cursor:pointer;font-size:9px;color:var(--text3);
166
+ font-family:var(--mono);padding:3px 0;user-select:none;
167
+ letter-spacing:.16em;text-transform:uppercase;font-weight:700;
168
+ }
44
169
  .lt-screenshots-toggle:hover{color:var(--text)}
45
- .lt-screenshots-toggle .ss-arrow{transition:transform .2s;font-size:8px}
46
- .lt-screenshots-toggle.open .ss-arrow{transform:rotate(90deg)}
47
- .lt-screenshots-grid{display:none;grid-template-columns:repeat(auto-fill,minmax(80px,1fr));gap:6px;padding-top:6px}
170
+ .lt-screenshots-toggle .ss-arrow{transition:transform .2s;font-size:9px}
171
+ .lt-screenshots-toggle.open .ss-arrow{transform:rotate(90deg);color:var(--phosphor)}
172
+ .lt-screenshots-grid{
173
+ display:none;
174
+ grid-template-columns:repeat(auto-fill,minmax(90px,1fr));
175
+ gap:8px;padding-top:8px;
176
+ }
48
177
  .lt-screenshots-toggle.open+.lt-screenshots-grid{display:grid}
49
- .lt-ss-thumb{position:relative;border-radius:4px;overflow:hidden;border:1px solid var(--border);cursor:pointer;aspect-ratio:16/10;background:var(--surface2)}
178
+ .lt-ss-thumb{
179
+ position:relative;border-radius:2px;overflow:hidden;
180
+ border:1px solid var(--border);cursor:pointer;
181
+ aspect-ratio:16/10;background:var(--bg-2);
182
+ transition:border-color .15s,transform .15s;
183
+ }
50
184
  .lt-ss-thumb img{width:100%;height:100%;object-fit:cover;display:block}
51
- .lt-ss-thumb:hover{border-color:var(--purple);box-shadow:0 0 0 1px var(--purple)}
52
- .lt-ss-label{font-size:8px;color:var(--text3);font-family:var(--mono);text-align:center;padding:2px 2px 0;display:flex;align-items:center;justify-content:center;gap:3px;flex-wrap:wrap}
185
+ .lt-ss-thumb:hover{
186
+ border-color:var(--radio);
187
+ transform:scale(1.02);
188
+ box-shadow:0 0 0 1px var(--radio),0 4px 12px rgba(0,0,0,.4);
189
+ }
190
+ .lt-ss-label{
191
+ font-size:8px;color:var(--text3);font-family:var(--mono);
192
+ text-align:center;padding:3px 2px 0;
193
+ display:flex;align-items:center;justify-content:center;gap:4px;flex-wrap:wrap;
194
+ letter-spacing:.04em;
195
+ }
53
196
 
54
- /* ── Live Run Sections ── */
55
- .lr-section-header{display:flex;align-items:center;justify-content:space-between;padding:8px 14px;margin:6px 0 2px;border-radius:6px;font-family:var(--mono);font-size:12px;font-weight:700;border-left:3px solid var(--purple)}
56
- .lr-section-header.running{border-color:var(--purple);background:rgba(139,92,246,.08);color:var(--purple)}
57
- .lr-section-header.pass{border-color:var(--green);background:rgba(52,211,153,.08);color:var(--green)}
58
- .lr-section-header.fail{border-color:var(--red);background:rgba(248,113,113,.08);color:var(--red)}
59
- .lr-section-stats{display:flex;align-items:center;gap:4px;font-size:10px;color:var(--text2);font-weight:400}
60
- .lr-project-name{letter-spacing:.5px}
61
- .lr-test-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:8px;padding:4px 0 8px}
62
- .live-done{background:var(--green-dim);color:var(--green);text-align:center;padding:10px;font-weight:600;font-size:12px}
63
- .live-done.has-failures{background:var(--red-dim);color:var(--red)}
64
- .live-close{padding:4px 10px;font-size:10px;background:transparent;border:1px solid var(--border);border-radius:var(--r);color:var(--text2);cursor:pointer}
197
+ /* ═══════════════════ Live Run Sections ═══════════════════ */
198
+ .lr-section-header{
199
+ display:flex;align-items:center;justify-content:space-between;
200
+ padding:10px 16px;margin:8px 0 4px;
201
+ border-radius:var(--r);
202
+ font-family:var(--mono);
203
+ font-size:11px;font-weight:700;
204
+ border-left:3px solid var(--radio);
205
+ letter-spacing:.08em;
206
+ }
207
+ .lr-section-header.running{
208
+ border-color:var(--radio);
209
+ background:var(--radio-dim);color:var(--radio);
210
+ text-shadow:0 0 10px var(--radio-glow);
211
+ }
212
+ .lr-section-header.pass{
213
+ border-color:var(--phosphor);
214
+ background:var(--phosphor-dim);color:var(--phosphor);
215
+ text-shadow:0 0 10px var(--phosphor-glow);
216
+ }
217
+ .lr-section-header.fail{
218
+ border-color:var(--crimson);
219
+ background:var(--crimson-dim);color:var(--crimson);
220
+ }
221
+ .lr-section-stats{
222
+ display:flex;align-items:center;gap:6px;
223
+ font-size:9px;color:var(--text2);font-weight:500;
224
+ letter-spacing:.1em;text-transform:uppercase;
225
+ font-variant-numeric:tabular-nums;
226
+ }
227
+ .lr-project-name{letter-spacing:.08em;text-transform:uppercase}
228
+ .lr-test-grid{
229
+ display:grid;
230
+ grid-template-columns:repeat(auto-fit,minmax(300px,1fr));
231
+ gap:10px;padding:6px 0 10px;
232
+ }
233
+ .live-done{
234
+ background:var(--phosphor-dim);color:var(--phosphor);
235
+ text-align:center;padding:12px;
236
+ font-family:var(--mono);font-weight:700;font-size:11px;
237
+ letter-spacing:.2em;text-transform:uppercase;
238
+ border-top:1px solid rgba(158,242,106,.2);
239
+ text-shadow:0 0 10px var(--phosphor-glow);
240
+ }
241
+ .live-done.has-failures{background:var(--crimson-dim);color:var(--crimson);text-shadow:none}
242
+ .live-close{
243
+ padding:5px 12px;font-size:9px;font-weight:700;
244
+ background:transparent;border:1px solid var(--border);
245
+ border-radius:var(--r);color:var(--text2);cursor:pointer;
246
+ letter-spacing:.14em;text-transform:uppercase;
247
+ font-family:var(--mono);
248
+ }
65
249
  .live-close:hover{color:var(--text);border-color:var(--border-hi)}
66
- .live-clear-btn{padding:5px 12px;font-size:10px;font-family:var(--mono);font-weight:500;background:var(--surface2);border:1px solid var(--border);border-radius:var(--r);color:var(--text2);cursor:pointer;display:none;transition:all .15s}
67
- .live-clear-btn:hover{color:var(--text);border-color:var(--border-hi);background:var(--surface3)}
68
- .lr-dismiss{padding:2px 6px;font-size:9px;font-family:var(--mono);background:transparent;border:1px solid transparent;border-radius:4px;color:var(--text3);cursor:pointer;transition:all .15s;margin-left:auto}
69
- .lr-dismiss:hover{color:var(--red);border-color:rgba(239,68,68,.3);background:var(--red-dim)}
250
+ .live-clear-btn{
251
+ padding:6px 14px;font-size:9px;font-family:var(--mono);font-weight:700;
252
+ background:transparent;border:1px solid var(--border);
253
+ border-radius:var(--r);color:var(--text2);cursor:pointer;
254
+ display:none;transition:all .15s;
255
+ letter-spacing:.16em;text-transform:uppercase;
256
+ }
257
+ .live-clear-btn:hover{color:var(--crimson);border-color:var(--crimson);background:var(--crimson-dim)}
258
+ .lr-dismiss{
259
+ padding:3px 7px;font-size:9px;font-family:var(--mono);
260
+ background:transparent;border:1px solid transparent;
261
+ border-radius:2px;color:var(--text3);cursor:pointer;
262
+ transition:all .15s;margin-left:auto;font-weight:700;
263
+ letter-spacing:.12em;text-transform:uppercase;
264
+ }
265
+ .lr-dismiss:hover{color:var(--crimson);border-color:rgba(255,77,77,.3);background:var(--crimson-dim)}
266
+
267
+ /* ═══════════════════ Screencast Panel ═══════════════════ */
268
+ /* Horizontal split: test log on top, screencast band on the bottom. */
269
+ .live-body{display:flex;flex-direction:column;flex:1;min-height:0;overflow:hidden}
270
+ .live-body .live-tests{flex:1;min-width:0;min-height:0;overflow-y:auto}
271
+ .screencast-panel{
272
+ order:-1; /* feed on top, above the test log */
273
+ width:auto;flex-shrink:0;display:flex;flex-direction:column;
274
+ height:clamp(230px,34vh,400px);
275
+ border-bottom:1px solid var(--border);background:var(--bg-2);
276
+ position:relative;
277
+ }
278
+ /* Floating label removed — the band header (📹 + AUTO·LIVE context) labels it. */
279
+ .screencast-panel::before{display:none}
280
+ .screencast-header{
281
+ display:flex;align-items:center;gap:12px;
282
+ padding:12px 16px;border-bottom:1px solid var(--border);
283
+ background:var(--surface2);
284
+ }
285
+ .screencast-label{
286
+ font-size:14px;color:var(--radio);
287
+ white-space:nowrap;text-shadow:0 0 10px var(--radio-glow);
288
+ flex-shrink:0;
289
+ }
290
+ /* Test chooser — pick which running test the feed follows (no more mixing). */
291
+ .screencast-test-select{
292
+ flex-shrink:0;max-width:280px;
293
+ padding:5px 10px;border-radius:var(--r);
294
+ border:1px solid var(--border);background:var(--surface);
295
+ color:var(--text);font-family:var(--mono);font-size:11px;
296
+ letter-spacing:.02em;cursor:pointer;appearance:auto;
297
+ transition:border-color .15s,background .15s;
298
+ }
299
+ .screencast-test-select:hover{background:var(--surface2);border-color:var(--border-hi)}
300
+ .screencast-test-select:focus{outline:none;border-color:var(--ui-accent);box-shadow:0 0 0 1px var(--ui-accent-dim)}
301
+ /* Context strip: PROJECT · testName · WATCHING|ENDED pill */
302
+ .screencast-context{
303
+ flex:1;min-width:0;display:flex;align-items:center;gap:8px;
304
+ font-family:var(--mono);font-size:10px;letter-spacing:.04em;
305
+ overflow:hidden;
306
+ }
307
+ .screencast-context.idle{color:var(--text3);font-style:italic}
308
+ .screencast-context.active{color:var(--text)}
309
+ .screencast-context.ended{color:var(--text2)}
310
+ .screencast-context .sc-ctx-proj{
311
+ color:var(--text3);text-transform:uppercase;letter-spacing:.12em;
312
+ padding:2px 6px;border:1px solid var(--border);border-radius:2px;
313
+ font-size:9px;font-weight:700;flex-shrink:0;
314
+ }
315
+ .screencast-context .sc-ctx-name{
316
+ color:var(--text);font-weight:600;
317
+ overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
318
+ min-width:0;flex:1;
319
+ }
320
+ .screencast-context .sc-ctx-pill{
321
+ padding:2px 8px;border-radius:10px;
322
+ font-size:9px;font-weight:700;letter-spacing:.16em;
323
+ white-space:nowrap;flex-shrink:0;
324
+ }
325
+ .screencast-context .sc-ctx-pill.running{
326
+ color:var(--radio);background:var(--radio-dim);
327
+ border:1px solid var(--radio);
328
+ box-shadow:0 0 8px var(--radio-glow);
329
+ animation:scWatchPulse 1.4s ease-in-out infinite;
330
+ }
331
+ .screencast-context .sc-ctx-pill.passed{
332
+ color:var(--phosphor);background:var(--phosphor-dim);
333
+ border:1px solid rgba(158,242,106,.4);
334
+ }
335
+ .screencast-context .sc-ctx-pill.failed{
336
+ color:var(--crimson);background:var(--crimson-dim);
337
+ border:1px solid rgba(255,77,77,.4);
338
+ }
339
+ .screencast-context .sc-ctx-pill.gone{
340
+ color:var(--text3);border:1px solid var(--border);
341
+ }
342
+ @keyframes scWatchPulse{0%,100%{box-shadow:0 0 8px var(--radio-glow)}50%{box-shadow:0 0 16px var(--radio-glow)}}
343
+ .screencast-stop-btn{
344
+ padding:3px 10px;font-size:9px;font-family:var(--mono);font-weight:700;
345
+ background:transparent;border:1px solid var(--border);
346
+ border-radius:var(--r);color:var(--text2);cursor:pointer;
347
+ letter-spacing:.16em;text-transform:uppercase;transition:all .15s;
348
+ flex-shrink:0;
349
+ }
350
+ .screencast-stop-btn:hover{color:var(--crimson);border-color:var(--crimson);background:var(--crimson-dim)}
351
+ /* Big single-frame viewport is replaced by the passing-thumbnail band. */
352
+ .screencast-viewport{display:none}
353
+ .screencast-viewport::after{display:none}
354
+ .screencast-viewport img{max-width:100%;max-height:100%;object-fit:contain;display:none;cursor:zoom-in;transition:filter .15s}
355
+ .screencast-viewport img:hover{filter:brightness(1.06)}
356
+ .screencast-placeholder{
357
+ display:flex;align-items:center;justify-content:center;
358
+ width:100%;height:100%;color:var(--text3);
359
+ font-size:11px;font-family:var(--mono);
360
+ letter-spacing:.16em;text-transform:uppercase;
361
+ }
362
+ /* zoom hint — only visible when a frame is shown and on hover */
363
+ .screencast-zoom-hint{
364
+ position:absolute;bottom:8px;right:10px;z-index:3;
365
+ font-family:var(--mono);font-size:9px;font-weight:700;
366
+ letter-spacing:.1em;text-transform:uppercase;
367
+ color:var(--text);background:rgba(0,0,0,.55);
368
+ border:1px solid rgba(255,255,255,.18);
369
+ padding:3px 8px;border-radius:999px;
370
+ opacity:0;transition:opacity .15s;pointer-events:none;
371
+ }
372
+ .screencast-viewport:hover .screencast-zoom-hint{opacity:.9}
373
+ .screencast-viewport.has-frame .screencast-zoom-hint{opacity:.55}
374
+ .screencast-viewport.has-frame:hover .screencast-zoom-hint{opacity:1}
70
375
 
71
- /* ── Screencast Panel ── */
72
- .live-body{display:flex;flex:1;min-height:0;overflow:hidden}
73
- .live-body .live-tests{flex:1;min-width:0}
74
- .screencast-panel{width:420px;flex-shrink:0;display:flex;flex-direction:column;border-left:1px solid var(--border);background:var(--surface2)}
75
- .screencast-header{display:flex;align-items:center;gap:10px;padding:10px 14px;border-bottom:1px solid var(--border);background:var(--surface3)}
76
- .screencast-label{font-size:11px;font-weight:600;color:var(--purple);white-space:nowrap}
77
- .screencast-select{flex:1;padding:4px 8px;font-size:10px;font-family:var(--mono);background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);outline:none;cursor:pointer}
78
- .screencast-select:focus{border-color:var(--purple)}
79
- .screencast-viewport{flex:1;display:flex;align-items:center;justify-content:center;overflow:hidden;background:#000;position:relative}
80
- .screencast-viewport img{max-width:100%;max-height:100%;object-fit:contain;display:none}
81
- .screencast-placeholder{display:flex;align-items:center;justify-content:center;width:100%;height:100%;color:var(--text3);font-size:12px;font-family:var(--mono)}
376
+ /* ── Screenshot band — recent frames passing by in fixed slots (no scroll) ── */
377
+ .screencast-film{
378
+ flex:1;min-height:0;
379
+ display:flex;gap:10px;align-items:center;justify-content:flex-start;
380
+ padding:12px 16px 16px;
381
+ background:var(--bg);
382
+ overflow:hidden;
383
+ }
384
+ .film-thumb{
385
+ flex-shrink:0;height:100%;aspect-ratio:16/10;
386
+ border:1px solid var(--border);border-radius:var(--r);
387
+ overflow:hidden;cursor:zoom-in;position:relative;
388
+ background:#000;transition:border-color .15s,transform .15s,box-shadow .15s;
389
+ }
390
+ .film-thumb:hover{border-color:var(--ui-accent);transform:translateY(-3px);box-shadow:0 8px 20px rgba(0,0,0,.45)}
391
+ .film-thumb img{width:100%;height:100%;object-fit:cover;display:block}
392
+ /* newest frame = the live one */
393
+ .film-thumb.is-live{border-color:var(--ui-accent);box-shadow:0 0 0 1px var(--ui-accent-dim)}
394
+ .film-thumb.is-live::before{
395
+ content:'● LIVE';position:absolute;top:4px;left:4px;z-index:2;
396
+ font-family:var(--mono);font-size:8px;font-weight:700;letter-spacing:.1em;
397
+ color:var(--ui-accent);background:rgba(0,0,0,.6);padding:1px 5px;border-radius:2px;
398
+ }
399
+ .film-thumb .film-idx{
400
+ position:absolute;bottom:3px;right:4px;
401
+ font-family:var(--mono);font-size:8px;font-weight:700;
402
+ color:var(--text);background:rgba(0,0,0,.6);
403
+ padding:0 4px;border-radius:2px;letter-spacing:.04em;
404
+ }
405
+ /* zoom affordance on hover */
406
+ .film-thumb::after{
407
+ content:'⤢';position:absolute;top:50%;left:50%;transform:translate(-50%,-50%) scale(.6);
408
+ font-size:22px;color:#fff;opacity:0;transition:opacity .15s,transform .15s;
409
+ text-shadow:0 1px 4px rgba(0,0,0,.8);pointer-events:none;
410
+ }
411
+ .film-thumb:hover::after{opacity:.95;transform:translate(-50%,-50%) scale(1)}
412
+ /* empty hint while waiting for frames */
413
+ .screencast-film-empty{
414
+ color:var(--text3);font-family:var(--mono);font-size:11px;
415
+ letter-spacing:.16em;text-transform:uppercase;
416
+ margin:auto;
417
+ }
82
418
 
83
- /* ── Screencast focus badge on test cards ── */
84
- .sc-focus-badge{cursor:pointer;font-size:10px;padding:1px 4px;border-radius:3px;opacity:.4;transition:all .15s}
85
- .sc-focus-badge:hover{opacity:.8}
86
- .sc-focus-badge.active{opacity:1;background:var(--purple-dim);border-radius:3px}
419
+ /* Per-card "watch" button on running tests */
420
+ .sc-watch-btn{
421
+ display:inline-flex;align-items:center;gap:5px;
422
+ padding:2px 8px;font-size:9px;font-family:var(--mono);font-weight:700;
423
+ letter-spacing:.14em;text-transform:uppercase;
424
+ background:transparent;border:1px solid var(--border);
425
+ border-radius:10px;color:var(--text3);cursor:pointer;
426
+ transition:all .15s;user-select:none;line-height:1.5;
427
+ }
428
+ .sc-watch-btn .sc-eye{font-size:11px;line-height:1}
429
+ .sc-watch-btn:hover{
430
+ color:var(--radio);border-color:var(--radio);background:var(--radio-dim);
431
+ }
432
+ .sc-watch-btn.active{
433
+ color:var(--radio);border-color:var(--radio);background:var(--radio-dim);
434
+ box-shadow:0 0 10px var(--radio-glow);
435
+ animation:scWatchPulse 1.4s ease-in-out infinite;
436
+ }
437
+ /* Card-level "is being watched" emphasis */
438
+ .live-test.sc-watching{
439
+ border-left-width:4px;
440
+ border-left-color:var(--radio);
441
+ background:linear-gradient(90deg,rgba(255,45,141,.06) 0%,var(--bg-2) 60%);
442
+ box-shadow:inset 0 0 32px rgba(255,45,141,.06),0 0 0 1px rgba(255,45,141,.18);
443
+ }
444
+ .live-test.sc-watching .lt-name{color:var(--radio)}
87
445
 
88
- /* ── Screencast toggle in Tests view ── */
89
- .screencast-toggle-label{display:flex;align-items:center;gap:4px;cursor:pointer;font-size:14px;padding:4px 8px;border-radius:4px;border:1px solid var(--border);background:var(--surface2);transition:all .15s;user-select:none}
90
- .screencast-toggle-label:hover{border-color:var(--purple);background:var(--surface3)}
446
+ .screencast-toggle-label{
447
+ display:flex;align-items:center;gap:5px;
448
+ cursor:pointer;font-size:14px;
449
+ padding:5px 10px;border-radius:var(--r);
450
+ border:1px solid var(--border);background:var(--surface);
451
+ transition:all .15s;user-select:none;
452
+ }
453
+ .screencast-toggle-label:hover{border-color:var(--radio);background:var(--surface2)}
91
454
  .screencast-toggle-label input{display:none}
92
- .screencast-toggle-label:has(input:checked){border-color:var(--purple);background:var(--purple-dim);color:var(--purple)}
455
+ .screencast-toggle-label:has(input:checked){
456
+ border-color:var(--radio);background:var(--radio-dim);color:var(--radio);
457
+ box-shadow:0 0 12px var(--radio-glow);
458
+ }
93
459
 
94
- .live-nav-dot{display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--purple);animation:pulse 1.5s infinite}
95
- .spinner{display:inline-block;width:12px;height:12px;border:2px solid var(--border);border-top-color:var(--purple);border-radius:50%;animation:spin .6s linear infinite;vertical-align:middle}
96
- .spinner-small{display:inline-block;width:8px;height:8px;border:1.5px solid var(--border);border-top-color:var(--purple);border-radius:50%;animation:spin .6s linear infinite;vertical-align:middle}
460
+ .live-nav-dot{
461
+ display:inline-block;width:8px;height:8px;border-radius:50%;
462
+ background:var(--radio);
463
+ box-shadow:0 0 10px var(--radio-glow);
464
+ animation:radar 1.6s ease-in-out infinite;
465
+ }
466
+ .spinner{
467
+ display:inline-block;width:12px;height:12px;
468
+ border:2px solid var(--border);
469
+ border-top-color:var(--radio);
470
+ border-radius:50%;animation:spin .6s linear infinite;vertical-align:middle;
471
+ }
472
+ .spinner-small{
473
+ display:inline-block;width:8px;height:8px;
474
+ border:1.5px solid var(--border);
475
+ border-top-color:var(--radio);
476
+ border-radius:50%;animation:spin .6s linear infinite;vertical-align:middle;
477
+ }
97
478
  @keyframes spin{to{transform:rotate(360deg)}}