tuffgal 0.1.0-alpha.8 → 0.2.0-alpha.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.
- package/CHANGELOG.md +140 -1
- package/README.md +82 -4
- package/dist/cli.d.ts +50 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +204 -18
- package/dist/cli.js.map +1 -1
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +19 -2
- package/dist/commands/init.js.map +1 -1
- package/dist/config.d.ts +121 -10
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +114 -9
- package/dist/config.js.map +1 -1
- package/dist/coverage/screens.d.ts +11 -6
- package/dist/coverage/screens.d.ts.map +1 -1
- package/dist/coverage/screens.js +12 -7
- package/dist/coverage/screens.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/reporter/assets/report.css +330 -50
- package/dist/reporter/assets/report.js +361 -55
- package/dist/reporter/template.d.ts +8 -1
- package/dist/reporter/template.d.ts.map +1 -1
- package/dist/reporter/template.js +447 -78
- package/dist/reporter/template.js.map +1 -1
- package/dist/reporter/writeReport.d.ts +2 -2
- package/dist/reporter/writeReport.d.ts.map +1 -1
- package/dist/reporter/writeReport.js +3 -3
- package/dist/reporter/writeReport.js.map +1 -1
- package/dist/runner/approve.d.ts +34 -9
- package/dist/runner/approve.d.ts.map +1 -1
- package/dist/runner/approve.js +54 -10
- package/dist/runner/approve.js.map +1 -1
- package/dist/runner/approveFrom.d.ts +58 -0
- package/dist/runner/approveFrom.d.ts.map +1 -0
- package/dist/runner/approveFrom.js +384 -0
- package/dist/runner/approveFrom.js.map +1 -0
- package/dist/runner/breakpointPasses.d.ts +39 -0
- package/dist/runner/breakpointPasses.d.ts.map +1 -0
- package/dist/runner/breakpointPasses.js +107 -0
- package/dist/runner/breakpointPasses.js.map +1 -0
- package/dist/runner/bridges/database.d.ts +4 -3
- package/dist/runner/bridges/database.d.ts.map +1 -1
- package/dist/runner/bridges/database.js +4 -3
- package/dist/runner/bridges/database.js.map +1 -1
- package/dist/runner/exitCode.d.ts +34 -0
- package/dist/runner/exitCode.d.ts.map +1 -0
- package/dist/runner/exitCode.js +41 -0
- package/dist/runner/exitCode.js.map +1 -0
- package/dist/runner/manifest.d.ts +149 -0
- package/dist/runner/manifest.d.ts.map +1 -0
- package/dist/runner/manifest.js +229 -0
- package/dist/runner/manifest.js.map +1 -0
- package/dist/runner/mode.d.ts +41 -0
- package/dist/runner/mode.d.ts.map +1 -0
- package/dist/runner/mode.js +43 -0
- package/dist/runner/mode.js.map +1 -0
- package/dist/runner/orphanScan.d.ts +68 -0
- package/dist/runner/orphanScan.d.ts.map +1 -0
- package/dist/runner/orphanScan.js +150 -0
- package/dist/runner/orphanScan.js.map +1 -0
- package/dist/runner/run.d.ts +85 -1
- package/dist/runner/run.d.ts.map +1 -1
- package/dist/runner/run.js +267 -41
- package/dist/runner/run.js.map +1 -1
- package/dist/runner/runAction.d.ts +22 -0
- package/dist/runner/runAction.d.ts.map +1 -1
- package/dist/runner/runAction.js +146 -24
- package/dist/runner/runAction.js.map +1 -1
- package/dist/runner/runStory.d.ts +40 -2
- package/dist/runner/runStory.d.ts.map +1 -1
- package/dist/runner/runStory.js +195 -62
- package/dist/runner/runStory.js.map +1 -1
- package/dist/runner/storyFilter.d.ts +8 -0
- package/dist/runner/storyFilter.d.ts.map +1 -1
- package/dist/runner/storyFilter.js +11 -0
- package/dist/runner/storyFilter.js.map +1 -1
- package/dist/schema/action.d.ts +11 -11
- package/dist/schema/result.d.ts +124 -6
- package/dist/schema/result.d.ts.map +1 -1
- package/dist/schema/result.js.map +1 -1
- package/dist/schema/story.d.ts +15 -4
- package/dist/schema/story.d.ts.map +1 -1
- package/dist/schema/story.js +35 -11
- package/dist/schema/story.js.map +1 -1
- package/dist/screenshots/baselineStore.d.ts +111 -5
- package/dist/screenshots/baselineStore.d.ts.map +1 -1
- package/dist/screenshots/baselineStore.js +132 -20
- package/dist/screenshots/baselineStore.js.map +1 -1
- package/dist/screenshots/capture.d.ts +10 -5
- package/dist/screenshots/capture.d.ts.map +1 -1
- package/dist/screenshots/capture.js +31 -18
- package/dist/screenshots/capture.js.map +1 -1
- package/package.json +1 -1
|
@@ -57,6 +57,15 @@
|
|
|
57
57
|
box-sizing: border-box;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
/* The story filter prunes stories, breakpoint groups, and actions by toggling
|
|
61
|
+
the native `hidden` attribute. Those are layout containers, so a future
|
|
62
|
+
`display:` rule on `.action`/`.breakpoint-group` would outrank the UA
|
|
63
|
+
`[hidden]` default and silently resurrect filtered rows as visible and
|
|
64
|
+
keyboard-focusable. Pin it so `hidden` always wins. */
|
|
65
|
+
[hidden] {
|
|
66
|
+
display: none !important;
|
|
67
|
+
}
|
|
68
|
+
|
|
60
69
|
html {
|
|
61
70
|
-webkit-font-smoothing: antialiased;
|
|
62
71
|
-moz-osx-font-smoothing: grayscale;
|
|
@@ -144,14 +153,59 @@ h2 {
|
|
|
144
153
|
text-wrap: balance;
|
|
145
154
|
}
|
|
146
155
|
|
|
147
|
-
|
|
156
|
+
/* Environment-mismatch banner, first inside <main>. The "warning" meaning is
|
|
157
|
+
carried by the heading word + body copy, never by color alone (1.4.1). Body
|
|
158
|
+
text stays --text (≥4.5:1); the red accent lives only on the left border (a
|
|
159
|
+
non-text ≥3:1 cue, mirroring .action-error). The list of diverging keys reads
|
|
160
|
+
as monospace <code> tokens. */
|
|
161
|
+
.env-mismatch {
|
|
148
162
|
margin-top: 0.5rem;
|
|
163
|
+
padding: 0.75rem 1rem;
|
|
164
|
+
background: var(--bg-elevated);
|
|
165
|
+
border: 1px solid var(--border);
|
|
166
|
+
border-left: 3px solid var(--failed);
|
|
149
167
|
}
|
|
150
168
|
|
|
151
|
-
.
|
|
169
|
+
.env-mismatch h2 {
|
|
170
|
+
margin-top: 0.25rem;
|
|
171
|
+
color: var(--changed);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.env-mismatch p {
|
|
175
|
+
margin: 0.5rem 0;
|
|
176
|
+
color: var(--text);
|
|
177
|
+
font-family: var(--font-sans);
|
|
178
|
+
font-size: 0.875rem;
|
|
179
|
+
text-wrap: pretty;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.env-mismatch ul {
|
|
152
183
|
display: flex;
|
|
153
184
|
flex-wrap: wrap;
|
|
154
|
-
gap:
|
|
185
|
+
gap: 0.5rem;
|
|
186
|
+
margin: 0.5rem 0 0;
|
|
187
|
+
padding: 0;
|
|
188
|
+
list-style: none;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.env-mismatch li {
|
|
192
|
+
color: var(--changed);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/* Deleted (orphaned-baseline) section, a peer landmark after the stories. */
|
|
196
|
+
.deleted {
|
|
197
|
+
margin-top: 0.5rem;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.deleted p {
|
|
201
|
+
margin: 0 0 0.5rem;
|
|
202
|
+
color: var(--text-muted);
|
|
203
|
+
font-family: var(--font-sans);
|
|
204
|
+
font-size: 0.875rem;
|
|
205
|
+
text-wrap: pretty;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.deleted ul {
|
|
155
209
|
margin: 0;
|
|
156
210
|
padding: 0.75rem 1rem;
|
|
157
211
|
list-style: none;
|
|
@@ -159,8 +213,24 @@ h2 {
|
|
|
159
213
|
border: 1px solid var(--border);
|
|
160
214
|
}
|
|
161
215
|
|
|
162
|
-
.
|
|
163
|
-
|
|
216
|
+
.deleted li {
|
|
217
|
+
padding: 0.15rem 0;
|
|
218
|
+
color: var(--text);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.summary {
|
|
222
|
+
margin-top: 0.5rem;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.summary-list {
|
|
226
|
+
display: flex;
|
|
227
|
+
flex-wrap: wrap;
|
|
228
|
+
gap: 0.5rem;
|
|
229
|
+
margin: 0;
|
|
230
|
+
padding: 0.75rem 1rem;
|
|
231
|
+
list-style: none;
|
|
232
|
+
background: var(--bg-elevated);
|
|
233
|
+
border: 1px solid var(--border);
|
|
164
234
|
}
|
|
165
235
|
|
|
166
236
|
.summary-item {
|
|
@@ -172,27 +242,77 @@ h2 {
|
|
|
172
242
|
|
|
173
243
|
.summary-item .count {
|
|
174
244
|
font-weight: 600;
|
|
245
|
+
color: var(--text-subtle);
|
|
175
246
|
}
|
|
176
247
|
|
|
177
|
-
|
|
178
|
-
|
|
248
|
+
/* Each status total is a single-select filter styled as a plain text link. The
|
|
249
|
+
word ("passed") is the only underlined link; its sibling count precedes it
|
|
250
|
+
outside the button. Resting is plain --text (1.4.3 text contrast, not the
|
|
251
|
+
1.4.11 non-text floor — no border to measure); hover and pressed tint the word
|
|
252
|
+
AND its underline the status color (text-decoration inherits currentColor, so
|
|
253
|
+
they carry a same-ratio underline). The visible count + word always show, and
|
|
254
|
+
the pressed state is never color-alone (1.4.1) — bold weight + a double
|
|
255
|
+
underline carry it independently. Target size ≥24×24 via min-height/min-width
|
|
256
|
+
(2.5.8). Stays a <button>: it filters in place, never navigates. */
|
|
257
|
+
.summary-filter {
|
|
258
|
+
display: inline-flex;
|
|
259
|
+
align-items: center;
|
|
260
|
+
gap: 0.5rem;
|
|
261
|
+
min-height: 24px;
|
|
262
|
+
min-width: 24px;
|
|
263
|
+
padding: 0 0.15rem;
|
|
264
|
+
line-height: 1.2;
|
|
265
|
+
background: none;
|
|
266
|
+
border: 0;
|
|
267
|
+
color: var(--text);
|
|
268
|
+
font: inherit;
|
|
269
|
+
cursor: pointer;
|
|
270
|
+
text-decoration: underline;
|
|
271
|
+
text-decoration-thickness: 1px;
|
|
272
|
+
text-underline-offset: 4.5px;
|
|
179
273
|
}
|
|
180
274
|
|
|
181
|
-
.summary-
|
|
275
|
+
.summary-filter[aria-pressed='true'] {
|
|
276
|
+
cursor: default;
|
|
277
|
+
user-select: none;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/* Hover + pressed tint the word its status color. The "all" total stays --text
|
|
281
|
+
on both: --accent equals --new, so tinting "all" blue would falsely imply the
|
|
282
|
+
"new" filter. Status colors clear 4.5:1 on --bg-elevated in both themes. */
|
|
283
|
+
.summary-item[data-status='pass'] .summary-filter:hover,
|
|
284
|
+
.summary-item[data-status='pass'] .summary-filter[aria-pressed='true'] {
|
|
182
285
|
color: var(--pass);
|
|
183
286
|
}
|
|
184
287
|
|
|
185
|
-
.summary-item[data-status='changed'] .
|
|
288
|
+
.summary-item[data-status='changed'] .summary-filter:hover,
|
|
289
|
+
.summary-item[data-status='changed'] .summary-filter[aria-pressed='true'] {
|
|
186
290
|
color: var(--changed);
|
|
187
291
|
}
|
|
188
292
|
|
|
189
|
-
.summary-item[data-status='failed'] .
|
|
293
|
+
.summary-item[data-status='failed'] .summary-filter:hover,
|
|
294
|
+
.summary-item[data-status='failed'] .summary-filter[aria-pressed='true'] {
|
|
190
295
|
color: var(--failed);
|
|
191
296
|
}
|
|
192
297
|
|
|
193
|
-
.summary-item
|
|
194
|
-
|
|
195
|
-
|
|
298
|
+
.summary-item[data-status='new'] .summary-filter:hover,
|
|
299
|
+
.summary-item[data-status='new'] .summary-filter[aria-pressed='true'] {
|
|
300
|
+
color: var(--new);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.summary-item[data-status='all'] .summary-filter:hover,
|
|
304
|
+
.summary-item[data-status='all'] .summary-filter[aria-pressed='true'] {
|
|
305
|
+
color: var(--text);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.summary-filter:hover,
|
|
309
|
+
.summary-filter[aria-pressed='true'] {
|
|
310
|
+
text-decoration-style: double;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.summary-filter:focus-visible {
|
|
314
|
+
outline: 2px solid var(--accent);
|
|
315
|
+
outline-offset: 2px;
|
|
196
316
|
}
|
|
197
317
|
|
|
198
318
|
.chip {
|
|
@@ -236,64 +356,72 @@ h2 {
|
|
|
236
356
|
}
|
|
237
357
|
}
|
|
238
358
|
|
|
239
|
-
|
|
359
|
+
/* The expand-all / collapse-all pair now lives at the right end of the summary
|
|
360
|
+
row (the slot the coverage stats vacated), pushed there by margin-left:auto
|
|
361
|
+
while staying the LAST flex children so DOM order matches the visual order. */
|
|
362
|
+
.story-bulk-toggle {
|
|
240
363
|
display: flex;
|
|
241
|
-
flex-wrap: wrap;
|
|
242
364
|
align-items: center;
|
|
243
|
-
gap: 0.
|
|
244
|
-
margin:
|
|
365
|
+
gap: 0.25rem;
|
|
366
|
+
margin-left: auto;
|
|
367
|
+
line-height: 24px;
|
|
368
|
+
font-size: 0.8125rem;
|
|
245
369
|
}
|
|
246
370
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
371
|
+
/* Link-styled verbs ("Expand" / "Collapse") — still <button>s, they act in
|
|
372
|
+
place. The underline is the affordance; verbs exceed 24px wide intrinsically,
|
|
373
|
+
so min-height alone carries 2.5.8. */
|
|
374
|
+
.story-bulk-toggle-button {
|
|
375
|
+
display: inline-flex;
|
|
250
376
|
align-items: center;
|
|
251
|
-
|
|
252
|
-
margin: 0;
|
|
377
|
+
min-height: 24px;
|
|
253
378
|
padding: 0;
|
|
379
|
+
background: none;
|
|
254
380
|
border: 0;
|
|
381
|
+
color: var(--text);
|
|
382
|
+
font: inherit;
|
|
383
|
+
cursor: pointer;
|
|
384
|
+
vertical-align: middle;
|
|
385
|
+
text-decoration: underline;
|
|
386
|
+
text-decoration-thickness: 1px;
|
|
387
|
+
text-underline-offset: 4.5px;
|
|
255
388
|
}
|
|
256
389
|
|
|
257
|
-
.story-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
font-size: 0.75rem;
|
|
261
|
-
letter-spacing: 0.05em;
|
|
262
|
-
text-transform: uppercase;
|
|
390
|
+
.story-bulk-toggle-button:hover:not(:disabled) {
|
|
391
|
+
color: var(--accent);
|
|
392
|
+
text-decoration-thickness: 2px;
|
|
263
393
|
}
|
|
264
394
|
|
|
265
|
-
.story-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
pointer-events: none;
|
|
395
|
+
.story-bulk-toggle-button:focus-visible {
|
|
396
|
+
outline: 2px solid var(--accent);
|
|
397
|
+
outline-offset: 1px;
|
|
269
398
|
}
|
|
270
399
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
400
|
+
/* Disabled verb: removing the underline is the color-independent cue (no
|
|
401
|
+
opacity — it can compound below the contrast floor). Disabled controls are
|
|
402
|
+
1.4.3/1.4.11 exempt, but the state stays perceptible without color. */
|
|
403
|
+
.story-bulk-toggle-button:disabled {
|
|
404
|
+
color: var(--text-subtle);
|
|
405
|
+
text-decoration: none;
|
|
406
|
+
cursor: not-allowed;
|
|
276
407
|
}
|
|
277
408
|
|
|
278
|
-
.
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
font-size: 0.8125rem;
|
|
282
|
-
min-height: 24px;
|
|
409
|
+
.bulk-scope,
|
|
410
|
+
.bulk-sep {
|
|
411
|
+
color: var(--text-subtle);
|
|
283
412
|
}
|
|
284
413
|
|
|
285
|
-
.story-bulk-toggle-
|
|
286
|
-
|
|
287
|
-
border-color: var(--accent);
|
|
414
|
+
.summary-item:has(+ .story-bulk-toggle) .bulk-sep {
|
|
415
|
+
display: none;
|
|
288
416
|
}
|
|
289
417
|
|
|
290
|
-
.
|
|
291
|
-
|
|
292
|
-
|
|
418
|
+
.bulk-scope {
|
|
419
|
+
cursor: default;
|
|
420
|
+
margin-inline-start: 0.25rem;
|
|
293
421
|
}
|
|
294
422
|
|
|
295
423
|
.story-filter-status {
|
|
296
|
-
margin: 0;
|
|
424
|
+
margin: 0 0 0.75rem;
|
|
297
425
|
color: var(--text-subtle);
|
|
298
426
|
font-size: 0.8125rem;
|
|
299
427
|
}
|
|
@@ -331,6 +459,10 @@ h2 {
|
|
|
331
459
|
box-shadow: inset 3px 0 0 var(--failed);
|
|
332
460
|
}
|
|
333
461
|
|
|
462
|
+
.story[data-status='new'] {
|
|
463
|
+
box-shadow: inset 3px 0 0 var(--new);
|
|
464
|
+
}
|
|
465
|
+
|
|
334
466
|
.story[data-status='changed'] {
|
|
335
467
|
box-shadow: inset 3px 0 0 var(--changed);
|
|
336
468
|
}
|
|
@@ -357,6 +489,11 @@ h2 {
|
|
|
357
489
|
color: var(--pass);
|
|
358
490
|
}
|
|
359
491
|
|
|
492
|
+
.story[data-status='new'] .story-marker,
|
|
493
|
+
.story[data-status='new'] .story-file {
|
|
494
|
+
color: var(--new);
|
|
495
|
+
}
|
|
496
|
+
|
|
360
497
|
.story[data-status='changed'] .story-marker,
|
|
361
498
|
.story[data-status='changed'] .story-file {
|
|
362
499
|
color: var(--changed);
|
|
@@ -380,6 +517,34 @@ h2 {
|
|
|
380
517
|
text-wrap: pretty;
|
|
381
518
|
}
|
|
382
519
|
|
|
520
|
+
/* `:not([hidden])` on both sides so a filter that hides the first group does
|
|
521
|
+
not strand its separator on the new first-visible group. */
|
|
522
|
+
.breakpoint-group:not([hidden]) + .breakpoint-group:not([hidden]) {
|
|
523
|
+
margin-top: 0.75rem;
|
|
524
|
+
padding-top: 0.75rem;
|
|
525
|
+
border-top: 1px dashed var(--border);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
.breakpoint-label {
|
|
529
|
+
display: flex;
|
|
530
|
+
align-items: baseline;
|
|
531
|
+
gap: 0.5rem;
|
|
532
|
+
margin: 0 0 0.25rem;
|
|
533
|
+
font-size: 0.75rem;
|
|
534
|
+
letter-spacing: 0.05em;
|
|
535
|
+
text-transform: uppercase;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
.breakpoint-name {
|
|
539
|
+
color: var(--accent);
|
|
540
|
+
font-weight: 600;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
.breakpoint-dimensions {
|
|
544
|
+
color: var(--text-subtle);
|
|
545
|
+
text-transform: none;
|
|
546
|
+
}
|
|
547
|
+
|
|
383
548
|
.actions {
|
|
384
549
|
position: relative;
|
|
385
550
|
margin: 0;
|
|
@@ -483,6 +648,28 @@ h2 {
|
|
|
483
648
|
overflow-x: auto;
|
|
484
649
|
}
|
|
485
650
|
|
|
651
|
+
/* Supplementary per-action notes (candidate / a11y-drift). Both plain prose
|
|
652
|
+
inside li.action; an a11y-only changed row stacks both. Indented to align
|
|
653
|
+
under the action name (past the status badge), muted like other secondary
|
|
654
|
+
prose. Stacked cleanly with a small gap when both render. */
|
|
655
|
+
.candidate-note,
|
|
656
|
+
.a11y-drift-note {
|
|
657
|
+
margin: 0.35rem 0 0 2.2rem;
|
|
658
|
+
color: var(--text-muted);
|
|
659
|
+
font-family: var(--font-sans);
|
|
660
|
+
font-size: 0.8125rem;
|
|
661
|
+
text-wrap: pretty;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
.candidate-note + .a11y-drift-note {
|
|
665
|
+
margin-top: 0.15rem;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
.a11y-drift-note code {
|
|
669
|
+
font-family: var(--font-mono);
|
|
670
|
+
color: var(--text);
|
|
671
|
+
}
|
|
672
|
+
|
|
486
673
|
summary.action-row {
|
|
487
674
|
list-style: none;
|
|
488
675
|
cursor: pointer;
|
|
@@ -561,11 +748,13 @@ summary.action-row:focus-visible {
|
|
|
561
748
|
padding: 0.5rem;
|
|
562
749
|
border: 1px solid var(--border);
|
|
563
750
|
background: var(--bg);
|
|
751
|
+
width: fit-content;
|
|
752
|
+
max-width: 100%;
|
|
564
753
|
}
|
|
565
754
|
|
|
566
755
|
.shot-panel img {
|
|
567
756
|
display: block;
|
|
568
|
-
width: 100%;
|
|
757
|
+
max-width: 100%;
|
|
569
758
|
height: auto;
|
|
570
759
|
outline: 1px solid var(--img-outline);
|
|
571
760
|
outline-offset: -1px;
|
|
@@ -601,6 +790,97 @@ summary.action-row:focus-visible {
|
|
|
601
790
|
color: var(--text-subtle);
|
|
602
791
|
}
|
|
603
792
|
|
|
793
|
+
/* No diff image to show (dimension mismatch). The note IS the primary content
|
|
794
|
+
of this slot, so it keeps full `--text` weight; italic alone marks the
|
|
795
|
+
"no data" state. */
|
|
796
|
+
.diff-stats--unavailable .label {
|
|
797
|
+
font-style: italic;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/* Interactive screenshot viewer (interactiveMode). The radio group + caption
|
|
801
|
+
sit in this fieldset, which lives OUTSIDE the clipped `.shot-stage`, so the
|
|
802
|
+
chip focus ring is never clipped by the stage overflow nor painted over
|
|
803
|
+
screenshot pixels. The chips reuse `.chip--toggle` (border `--border-bright`,
|
|
804
|
+
`--accent` focus ring), so no new focus tokens are introduced here. */
|
|
805
|
+
.shot-interactive {
|
|
806
|
+
display: flex;
|
|
807
|
+
flex-wrap: wrap;
|
|
808
|
+
align-items: center;
|
|
809
|
+
gap: 0.5rem;
|
|
810
|
+
margin: 0.5rem 0 0.5rem 2.2rem;
|
|
811
|
+
padding: 0;
|
|
812
|
+
border: 0;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
/* interactiveMode: chip buttons collapsed by default — sighted mouse users
|
|
816
|
+
drive the viewer with the hover/press gesture on the stage. Clip (never
|
|
817
|
+
display/visibility/inert) so the nested radio stays focusable and keeps
|
|
818
|
+
its accessible name. Mirrors .sr-only. */
|
|
819
|
+
.shot-interactive .shot-radio-label {
|
|
820
|
+
position: absolute;
|
|
821
|
+
width: 1px;
|
|
822
|
+
height: 1px;
|
|
823
|
+
margin: -1px;
|
|
824
|
+
overflow: hidden;
|
|
825
|
+
clip: rect(0 0 0 0);
|
|
826
|
+
white-space: nowrap;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
/* Reveal the whole chip group when a keyboard user focuses any radio.
|
|
830
|
+
:focus-within needs no :has() fallback and never fires for mouse/touch
|
|
831
|
+
(label clipped, input pointer-events:none → radios are keyboard-only
|
|
832
|
+
reachable). Specificity (0,3,0) beats the collapse rule (0,2,0). */
|
|
833
|
+
.shot-interactive:focus-within .shot-radio-label {
|
|
834
|
+
position: static;
|
|
835
|
+
width: auto;
|
|
836
|
+
height: auto;
|
|
837
|
+
margin: 0;
|
|
838
|
+
overflow: visible;
|
|
839
|
+
clip: auto;
|
|
840
|
+
white-space: normal;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
/* Persistent caption naming the committed variant. Text on `--text` (≥4.5:1),
|
|
844
|
+
so the active variant is never conveyed by color alone — it pairs the checked
|
|
845
|
+
chip's accent border with this readable label. aria-hidden keeps it visual
|
|
846
|
+
only: the checked radio is the single accessible source of committed state. */
|
|
847
|
+
.shot-caption {
|
|
848
|
+
margin: 0 0 0 0.25rem;
|
|
849
|
+
color: var(--text);
|
|
850
|
+
font-size: 0.8125rem;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
.shot-caption-variant {
|
|
854
|
+
font-weight: 600;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
/* The stage clips the screenshot to the viewport height. Its border uses
|
|
858
|
+
`--border-bright` (3:1 non-text contrast). overflow:hidden only ever clips
|
|
859
|
+
the image — the controls live in the sibling fieldset above. */
|
|
860
|
+
.shot-stage {
|
|
861
|
+
margin-top: 0.5rem;
|
|
862
|
+
margin-left: 2.2rem;
|
|
863
|
+
padding: 0.5rem;
|
|
864
|
+
border: 1px solid var(--border-bright);
|
|
865
|
+
background: var(--bg);
|
|
866
|
+
width: fit-content;
|
|
867
|
+
max-width: 100%;
|
|
868
|
+
overflow: hidden;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/* Scale the screenshot to fit within the viewport height (and the panel width)
|
|
872
|
+
so a tall capture never forces the page to scroll. width/height:auto preserve
|
|
873
|
+
the aspect ratio while `max-height` caps it to the viewport. */
|
|
874
|
+
.shot-image {
|
|
875
|
+
display: block;
|
|
876
|
+
width: auto;
|
|
877
|
+
height: auto;
|
|
878
|
+
max-width: 100%;
|
|
879
|
+
max-height: 85vh;
|
|
880
|
+
outline: 1px solid var(--img-outline);
|
|
881
|
+
outline-offset: -1px;
|
|
882
|
+
}
|
|
883
|
+
|
|
604
884
|
@media (prefers-reduced-motion: reduce) {
|
|
605
885
|
.skip-link,
|
|
606
886
|
summary.action-row::after,
|