snap-ally 0.0.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.
@@ -0,0 +1,1412 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Snap Ally - Accessibility Audit</title>
8
+ <!-- Modern Typography -->
9
+ <link rel="preconnect" href="https://fonts.googleapis.com">
10
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Outfit:wght@600;700&display=swap"
12
+ rel="stylesheet">
13
+ <!-- Bootstrap for functional components only (modals/accordions), custom CSS for styling -->
14
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
15
+ <link rel="stylesheet"
16
+ href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
17
+ <style>
18
+ :root {
19
+ --bg-color: #f8fafc;
20
+ --primary: #6366f1;
21
+ --primary-dark: #4f46e5;
22
+ --primary-soft: #eef2ff;
23
+ --text-main: #0f172a;
24
+ --text-muted: #64748b;
25
+
26
+ --critical: <%=data.criticalColor || '#dc2626' %>;
27
+ --serious: <%=data.seriousColor || '#ea580c' %>;
28
+ --moderate: <%=data.moderateColor || '#f59e0b' %>;
29
+ --minor: <%=data.minorColor || '#0ea5e9' %>;
30
+
31
+ --glass-bg: rgba(255, 255, 255, 0.85);
32
+ --glass-border: rgba(255, 255, 255, 0.6);
33
+ --glass-shadow: 0 12px 40px rgba(0, 0, 0, 0.08);
34
+ --card-radius: 24px;
35
+ }
36
+
37
+ body {
38
+ font-family: 'Inter', sans-serif;
39
+ background-color: var(--bg-color);
40
+ color: var(--text-main);
41
+ padding: 100px 20px 0;
42
+ margin: 0;
43
+ min-height: 100vh;
44
+ display: flex;
45
+ flex-direction: column;
46
+ }
47
+
48
+ /* Fixed Header */
49
+ header {
50
+ position: fixed;
51
+ top: 0;
52
+ left: 0;
53
+ right: 0;
54
+ z-index: 1000;
55
+ background: rgba(255, 255, 255, 0.8);
56
+ backdrop-filter: blur(20px) saturate(180%);
57
+ -webkit-backdrop-filter: blur(20px) saturate(180%);
58
+ border-bottom: 1px solid var(--glass-border);
59
+ padding: 14px 40px;
60
+ display: flex;
61
+ justify-content: space-between;
62
+ align-items: center;
63
+ box-shadow: 0 4px 30px rgba(0, 0, 0, 0.03);
64
+ }
65
+
66
+ .brand {
67
+ display: flex;
68
+ align-items: center;
69
+ gap: 12px;
70
+ text-decoration: none;
71
+ }
72
+
73
+ .brand-icon {
74
+ width: 38px;
75
+ height: 38px;
76
+ background: linear-gradient(135deg, var(--primary), var(--primary-dark));
77
+ border-radius: 12px;
78
+ display: flex;
79
+ align-items: center;
80
+ justify-content: center;
81
+ color: white;
82
+ box-shadow: 0 4px 15px rgba(99, 102, 241, 0.25);
83
+ }
84
+
85
+ .brand-text {
86
+ font-family: 'Outfit', sans-serif;
87
+ font-size: 1.4rem;
88
+ font-weight: 700;
89
+ margin: 0;
90
+ background: linear-gradient(to right, var(--primary-dark), #818cf8);
91
+ -webkit-background-clip: text;
92
+ background-clip: text;
93
+ -webkit-text-fill-color: transparent;
94
+ }
95
+
96
+ footer {
97
+ background: rgba(255, 255, 255, 0.7);
98
+ backdrop-filter: blur(12px);
99
+ -webkit-backdrop-filter: blur(12px);
100
+ border-top: 1px solid var(--glass-border);
101
+ padding: 24px 40px;
102
+ margin-top: auto;
103
+ display: flex;
104
+ justify-content: space-between;
105
+ align-items: center;
106
+ color: var(--text-muted);
107
+ font-size: 0.9rem;
108
+ }
109
+
110
+ .powered-by {
111
+ display: flex;
112
+ align-items: center;
113
+ gap: 8px;
114
+ font-weight: 500;
115
+ }
116
+
117
+ .badge-tool {
118
+ background: #f1f5f9;
119
+ padding: 2px 10px;
120
+ border-radius: 8px;
121
+ font-weight: 700;
122
+ color: #334155;
123
+ font-size: 0.8rem;
124
+ }
125
+
126
+ .verified-icon {
127
+ font-size: 18px;
128
+ color: var(--primary);
129
+ }
130
+
131
+ /* Container Overrides */
132
+ .container {
133
+ max-width: 1200px !important;
134
+ margin: 0 auto;
135
+ width: 100%;
136
+ padding-bottom: 40px;
137
+ }
138
+
139
+ /* Summary Section */
140
+ .page-hero {
141
+ background: linear-gradient(135deg, #fff 0%, #f1f5f9 100%);
142
+ border: 1px solid var(--glass-border);
143
+ border-radius: var(--card-radius);
144
+ padding: 32px;
145
+ margin-bottom: 32px;
146
+ box-shadow: var(--glass-shadow);
147
+ display: flex;
148
+ justify-content: space-between;
149
+ align-items: center;
150
+ gap: 24px;
151
+ }
152
+
153
+ .hero-content h2 {
154
+ font-size: 0.75rem;
155
+ letter-spacing: 0.15em;
156
+ color: var(--text-muted);
157
+ margin-bottom: 8px;
158
+ font-weight: 700;
159
+ }
160
+
161
+ .page-url {
162
+ font-family: 'Outfit', sans-serif;
163
+ font-size: 1.25rem;
164
+ font-weight: 600;
165
+ word-break: break-all;
166
+ color: var(--text-main);
167
+ margin: 0;
168
+ }
169
+
170
+ .stats-pills {
171
+ display: flex;
172
+ gap: 12px;
173
+ }
174
+
175
+ .stat-pill {
176
+ padding: 8px 16px;
177
+ border-radius: 12px;
178
+ background: white;
179
+ border: 1px solid #e2e8f0;
180
+ display: flex;
181
+ align-items: center;
182
+ gap: 8px;
183
+ font-weight: 600;
184
+ font-size: 0.9rem;
185
+ }
186
+
187
+ .stat-pill-passed {
188
+ background: #ecfdf5;
189
+ border-color: #d1fae5;
190
+ color: #047857;
191
+ }
192
+
193
+ .stat-pill-icon {
194
+ color: var(--primary);
195
+ }
196
+
197
+ /* Violation Cards */
198
+ .violation-card {
199
+ background: var(--glass-bg);
200
+ backdrop-filter: blur(8px);
201
+ border-radius: var(--card-radius);
202
+ border: 1px solid var(--glass-border);
203
+ overflow: hidden;
204
+ margin-bottom: 24px;
205
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.04);
206
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
207
+ }
208
+
209
+ .violation-card:hover {
210
+ transform: translateY(-2px);
211
+ box-shadow: 0 12px 30px rgba(0, 0, 0, 0.08);
212
+ }
213
+
214
+ .violation-header {
215
+ padding: 12px 20px;
216
+ display: flex;
217
+ justify-content: space-between;
218
+ align-items: center;
219
+ gap: 20px;
220
+ background: rgba(255, 255, 255, 0.5);
221
+ border-bottom: 1px solid rgba(0, 0, 0, 0.05);
222
+ }
223
+
224
+ .title-meta {
225
+ display: flex;
226
+ align-items: center;
227
+ gap: 16px;
228
+ flex-wrap: wrap;
229
+ }
230
+
231
+ .violation-title {
232
+ font-family: 'Outfit', sans-serif;
233
+ font-size: 1.25rem;
234
+ font-weight: 700;
235
+ color: var(--text-main);
236
+ display: block;
237
+ margin: 0;
238
+ }
239
+
240
+ .violation-rule-info {
241
+ font-size: 0.85rem;
242
+ color: var(--text-muted);
243
+ margin: 0;
244
+ display: flex;
245
+ align-items: center;
246
+ gap: 8px;
247
+ }
248
+
249
+ .rule-name {
250
+ font-weight: 700;
251
+ color: var(--text-main);
252
+ }
253
+
254
+ .rule-sep {
255
+ opacity: 0.5;
256
+ }
257
+
258
+ .severity-badge {
259
+ padding: 6px 16px;
260
+ border-radius: 10px;
261
+ font-size: 0.75rem;
262
+ font-weight: 800;
263
+ letter-spacing: 0.5px;
264
+ color: #fff;
265
+ white-space: nowrap;
266
+ }
267
+
268
+ .severity-badge.critical {
269
+ background: var(--critical);
270
+ }
271
+
272
+ .severity-badge.serious {
273
+ background: var(--serious);
274
+ }
275
+
276
+ .severity-badge.moderate {
277
+ background: var(--moderate);
278
+ }
279
+
280
+ .severity-badge.minor {
281
+ background: var(--minor);
282
+ }
283
+
284
+ .violation-body {
285
+ padding: 16px 20px;
286
+ }
287
+
288
+ .fix-suggestion {
289
+ background: #f0fdf4;
290
+ border: 1px solid #bbf7d0;
291
+ padding: 12px 16px;
292
+ border-radius: 12px;
293
+ margin-bottom: 24px;
294
+ font-size: 0.95rem;
295
+ color: #166534;
296
+ display: flex;
297
+ gap: 12px;
298
+ align-items: flex-start;
299
+ }
300
+
301
+ .fix-icon {
302
+ color: #059669;
303
+ font-size: 20px;
304
+ }
305
+
306
+ .fix-title {
307
+ font-weight: 700;
308
+ margin-bottom: 4px;
309
+ }
310
+
311
+ .fix-link {
312
+ margin-left: 8px;
313
+ color: #059669;
314
+ font-weight: 600;
315
+ text-decoration: none;
316
+ border-bottom: 1.5px solid rgba(5, 150, 105, 0.3);
317
+ }
318
+
319
+ .fix-link-icon {
320
+ font-size: 14px;
321
+ vertical-align: middle;
322
+ }
323
+
324
+ /* Instances */
325
+ .instances-title {
326
+ font-size: 0.9rem;
327
+ font-weight: 700;
328
+ color: var(--text-main);
329
+ margin-bottom: 16px;
330
+ display: flex;
331
+ align-items: center;
332
+ gap: 8px;
333
+ }
334
+
335
+ .instances-icon {
336
+ font-size: 18px;
337
+ color: var(--primary);
338
+ }
339
+
340
+ /* Bug Preview Card Styling */
341
+ .bug-preview-card {
342
+ background: #fff;
343
+ border: 1px solid #e2e8f0;
344
+ border-radius: 12px;
345
+ overflow: hidden;
346
+ margin-bottom: 16px;
347
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.02);
348
+ transition: all 0.2s ease;
349
+ }
350
+
351
+ .bug-preview-card:hover {
352
+ border-color: var(--primary);
353
+ box-shadow: 0 4px 12px rgba(99, 102, 241, 0.1);
354
+ }
355
+
356
+ .bug-header {
357
+ background: #f8fafc;
358
+ padding: 12px 16px;
359
+ border-bottom: 1px solid #e2e8f0;
360
+ display: flex;
361
+ justify-content: space-between;
362
+ align-items: center;
363
+ }
364
+
365
+ .bug-title-tag {
366
+ font-family: 'JetBrains Mono', monospace;
367
+ font-size: 0.8rem;
368
+ color: var(--primary-dark);
369
+ background: var(--primary-soft);
370
+ padding: 4px 10px;
371
+ border-radius: 6px;
372
+ font-weight: 600;
373
+ }
374
+
375
+ .bug-body {
376
+ padding: 16px;
377
+ display: grid;
378
+ grid-template-columns: 1fr 1fr;
379
+ gap: 24px;
380
+ }
381
+
382
+ @media (max-width: 768px) {
383
+ .bug-body {
384
+ grid-template-columns: 1fr;
385
+ }
386
+ }
387
+
388
+ .bug-section-title {
389
+ font-size: 0.75rem;
390
+ text-transform: uppercase;
391
+ letter-spacing: 0.05em;
392
+ color: var(--text-muted);
393
+ font-weight: 700;
394
+ margin-bottom: 8px;
395
+ }
396
+
397
+ .bug-content-box {
398
+ background: #f8fafc;
399
+ border-radius: 8px;
400
+ padding: 12px;
401
+ font-size: 0.9rem;
402
+ color: var(--text-main);
403
+ border: 1px solid #f1f5f9;
404
+ }
405
+
406
+ /* Video Section */
407
+ .video-card {
408
+ background: white;
409
+ border: 1px solid var(--glass-border);
410
+ border-radius: var(--card-radius);
411
+ padding: 32px;
412
+ margin-top: 40px;
413
+ box-shadow: var(--glass-shadow);
414
+ }
415
+
416
+ .video-header {
417
+ display: flex;
418
+ align-items: center;
419
+ gap: 12px;
420
+ margin-bottom: 24px;
421
+ }
422
+
423
+ .video-icon-wrap {
424
+ width: 40px;
425
+ height: 40px;
426
+ background: #fee2e2;
427
+ border-radius: 12px;
428
+ display: flex;
429
+ align-items: center;
430
+ justify-content: center;
431
+ color: #ef4444;
432
+ }
433
+
434
+ .video-title {
435
+ font-family: 'Outfit', sans-serif;
436
+ font-size: 1.4rem;
437
+ font-weight: 700;
438
+ margin: 0;
439
+ }
440
+
441
+ .video-container {
442
+ border-radius: 16px;
443
+ overflow: hidden;
444
+ box-shadow: 0 20px 50px rgba(0, 0, 0, 0.1);
445
+ background: #000;
446
+ border: 1px solid #000;
447
+ }
448
+
449
+ .video-container video {
450
+ width: 100%;
451
+ height: 100%;
452
+ }
453
+
454
+ #loader-overlay {
455
+ position: fixed;
456
+ inset: 0;
457
+ background: #fff;
458
+ z-index: 10000;
459
+ display: flex;
460
+ flex-direction: column;
461
+ justify-content: center;
462
+ align-items: center;
463
+ transition: opacity 0.5s ease;
464
+ }
465
+
466
+ .loader-pulse {
467
+ width: 64px;
468
+ height: 64px;
469
+ background: var(--primary);
470
+ border-radius: 16px;
471
+ animation: pulse 1.5s infinite ease-in-out;
472
+ display: flex;
473
+ align-items: center;
474
+ justify-content: center;
475
+ color: white;
476
+ }
477
+
478
+ .loader-text {
479
+ font-family: 'Outfit';
480
+ font-weight: 700;
481
+ font-size: 1.2rem;
482
+ color: var(--text-main);
483
+ margin-top: 24px;
484
+ }
485
+
486
+ @keyframes pulse {
487
+ 0% {
488
+ transform: scale(0.95);
489
+ box-shadow: 0 0 0 0 rgba(99, 102, 241, 0.5);
490
+ }
491
+
492
+ 70% {
493
+ transform: scale(1);
494
+ box-shadow: 0 0 0 20px rgba(99, 102, 241, 0);
495
+ }
496
+
497
+ 100% {
498
+ transform: scale(0.95);
499
+ box-shadow: 0 0 0 0 rgba(99, 102, 241, 0);
500
+ }
501
+ }
502
+
503
+ /* Success Hero */
504
+ .success-verified-card {
505
+ text-align: center;
506
+ padding: 80px 0;
507
+ background: white;
508
+ border-radius: 32px;
509
+ border: 1px solid var(--glass-border);
510
+ box-shadow: var(--glass-shadow);
511
+ }
512
+
513
+ .success-hero-icon {
514
+ font-size: 80px;
515
+ color: #10b981;
516
+ margin-bottom: 24px;
517
+ filter: drop-shadow(0 4px 12px rgba(16, 185, 129, 0.2));
518
+ }
519
+
520
+ .success-hero-title {
521
+ font-weight: 700;
522
+ font-size: 1.8rem;
523
+ font-family: 'Outfit', sans-serif;
524
+ }
525
+
526
+ .success-hero-desc {
527
+ max-width: 400px;
528
+ margin: 0 auto;
529
+ color: var(--text-muted);
530
+ }
531
+
532
+ /* Modal Styling */
533
+ .modal-content {
534
+ border-radius: 28px;
535
+ border: none;
536
+ }
537
+
538
+ .modal-header-custom {
539
+ border-bottom: none;
540
+ padding: 32px 32px 16px;
541
+ }
542
+
543
+ .modal-title-custom {
544
+ font-weight: 700;
545
+ font-size: 1.5rem;
546
+ font-family: 'Outfit', sans-serif;
547
+ }
548
+
549
+ .modal-body-custom {
550
+ padding: 0 32px 24px;
551
+ }
552
+
553
+ .modal-desc {
554
+ font-size: 0.95rem;
555
+ color: var(--text-muted);
556
+ margin-bottom: 24px;
557
+ }
558
+
559
+ .modal-footer-custom {
560
+ border-top: none;
561
+ padding: 0 32px 32px;
562
+ }
563
+
564
+ .form-control:focus {
565
+ box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.1);
566
+ border-color: var(--primary);
567
+ }
568
+
569
+ .token-input-wrap {
570
+ position: relative;
571
+ }
572
+
573
+ .token-icon {
574
+ position: absolute;
575
+ left: 12px;
576
+ top: 50%;
577
+ transform: translateY(-50%);
578
+ color: var(--text-muted);
579
+ font-size: 20px;
580
+ }
581
+
582
+ .token-input {
583
+ border-radius: 12px;
584
+ padding: 14px 14px 14px 44px;
585
+ background: #f8fafc;
586
+ }
587
+
588
+ .btn-init {
589
+ border-radius: 14px;
590
+ font-weight: 700;
591
+ padding: 16px;
592
+ background: var(--primary);
593
+ }
594
+
595
+ /* Premium Buttons */
596
+ .btn-premium {
597
+ border-radius: 8px;
598
+ padding: 8px 16px;
599
+ font-weight: 600;
600
+ font-size: 0.85rem;
601
+ letter-spacing: 0.3px;
602
+ display: inline-flex;
603
+ align-items: center;
604
+ gap: 6px;
605
+ transition: all 0.2s ease;
606
+ cursor: pointer;
607
+ }
608
+
609
+ .btn-bug {
610
+ background: linear-gradient(135deg, var(--primary), var(--primary-dark));
611
+ color: #fff;
612
+ border: none;
613
+ box-shadow: 0 4px 10px rgba(99, 102, 241, 0.2);
614
+ }
615
+
616
+ .btn-bug:hover {
617
+ transform: translateY(-1px);
618
+ box-shadow: 0 6px 15px rgba(99, 102, 241, 0.3);
619
+ color: #fff;
620
+ }
621
+
622
+ .btn-back {
623
+ text-decoration: none;
624
+ color: var(--text-muted);
625
+ font-weight: 600;
626
+ display: flex;
627
+ align-items: center;
628
+ gap: 6px;
629
+ }
630
+
631
+ .btn-back-icon {
632
+ font-size: 20px;
633
+ }
634
+
635
+ .btn-token {
636
+ border-radius: 10px;
637
+ font-weight: 700;
638
+ padding: 8px 16px;
639
+ }
640
+
641
+ .btn-token-icon {
642
+ vertical-align: middle;
643
+ font-size: 18px;
644
+ margin-right: 4px;
645
+ }
646
+
647
+ .screenshot-container {
648
+ margin-top: 0;
649
+ border-radius: 8px;
650
+ overflow: hidden;
651
+ border: 1px solid #e2e8f0;
652
+ background: #000;
653
+ position: relative;
654
+ }
655
+
656
+ .screenshot-container img {
657
+ width: 100%;
658
+ height: auto;
659
+ display: block;
660
+ }
661
+
662
+ /* ADO Modal Premium Standardized */
663
+ .ado-modal-premium .modal-content {
664
+ border-radius: 20px;
665
+ border: 1px solid var(--glass-border);
666
+ box-shadow: 0 25px 50px rgba(0, 0, 0, 0.15);
667
+ background: rgba(255, 255, 255, 0.98);
668
+ backdrop-filter: blur(10px);
669
+ overflow: hidden;
670
+ }
671
+
672
+ .ado-modal-premium .modal-header {
673
+ background: #f8fafc;
674
+ padding: 24px 32px;
675
+ border-bottom: 1px solid var(--glass-border);
676
+ }
677
+
678
+ .ado-modal-premium .modal-title {
679
+ font-family: 'Outfit', sans-serif;
680
+ font-weight: 700;
681
+ font-size: 1.25rem;
682
+ color: var(--text-main);
683
+ display: flex;
684
+ align-items: center;
685
+ gap: 12px;
686
+ }
687
+
688
+ .ado-modal-premium .modal-body {
689
+ padding: 24px 32px;
690
+ max-height: 550px;
691
+ overflow-y: auto;
692
+ }
693
+
694
+ .ado-modal-premium .modal-footer {
695
+ padding: 12px 32px;
696
+ background: #f8fafc;
697
+ border-top: 1px solid var(--glass-border);
698
+ }
699
+
700
+ .ado-field-group {
701
+ margin-bottom: 20px;
702
+ }
703
+
704
+ .ado-form-label {
705
+ display: block;
706
+ font-weight: 700;
707
+ font-size: 0.8rem;
708
+ color: var(--text-muted);
709
+ margin-bottom: 8px;
710
+ text-transform: uppercase;
711
+ letter-spacing: 0.5px;
712
+ }
713
+
714
+ .ado-form-input {
715
+ display: block;
716
+ width: 100%;
717
+ padding: 12px 16px;
718
+ font-size: 0.95rem;
719
+ font-weight: 400;
720
+ line-height: 1.5;
721
+ color: var(--text-main);
722
+ background-color: #fff;
723
+ background-clip: padding-box;
724
+ border: 1.5px solid #e2e8f0;
725
+ appearance: none;
726
+ border-radius: 12px;
727
+ transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out;
728
+ }
729
+
730
+ .ado-form-input:focus {
731
+ color: var(--text-main);
732
+ background-color: #fff;
733
+ border-color: var(--primary);
734
+ outline: 0;
735
+ box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.1);
736
+ }
737
+
738
+ .ado-repro-preview {
739
+ background: #f8fafc;
740
+ border: 1.5px solid #e2e8f0;
741
+ padding: 16px;
742
+ border-radius: 12px;
743
+ font-size: 0.9rem;
744
+ line-height: 1.6;
745
+ color: var(--text-main);
746
+ }
747
+
748
+ .visual-evidence-grid {
749
+ display: grid;
750
+ grid-template-columns: 1fr 1fr;
751
+ gap: 16px;
752
+ margin-top: 12px;
753
+ }
754
+
755
+ .visual-item {
756
+ background: #f8fafc;
757
+ border: 1px solid #e2e8f0;
758
+ border-radius: 12px;
759
+ padding: 8px;
760
+ }
761
+
762
+ .visual-label {
763
+ font-size: 0.7rem;
764
+ font-weight: 700;
765
+ color: var(--text-muted);
766
+ text-transform: uppercase;
767
+ margin-bottom: 6px;
768
+ display: flex;
769
+ align-items: center;
770
+ gap: 4px;
771
+ }
772
+
773
+ .screenshot-thumb {
774
+ width: 100%;
775
+ height: auto;
776
+ max-height: 480px;
777
+ border-radius: 12px;
778
+ object-fit: contain;
779
+ background: #000;
780
+ border: 1.5px solid #e2e8f0;
781
+ display: block;
782
+ }
783
+
784
+ .video-preview-player {
785
+ width: 100%;
786
+ height: 280px;
787
+ border-radius: 12px;
788
+ background: #000;
789
+ border: 1.5px solid #e2e8f0;
790
+ display: block;
791
+ object-fit: contain;
792
+ }
793
+
794
+ .ado-repro-preview b {
795
+ color: var(--primary-dark);
796
+ }
797
+
798
+ .ado-repro-preview hr {
799
+ margin: 16px 0;
800
+ opacity: 0.1;
801
+ }
802
+
803
+ .btn-ado-primary {
804
+ background: var(--primary);
805
+ color: #fff;
806
+ border: none;
807
+ padding: 12px 28px;
808
+ border-radius: 12px;
809
+ font-weight: 700;
810
+ transition: all 0.2s;
811
+ }
812
+
813
+ .btn-ado-primary:hover {
814
+ background: var(--primary-dark);
815
+ transform: translateY(-1px);
816
+ box-shadow: 0 4px 12px rgba(99, 102, 241, 0.25);
817
+ }
818
+
819
+ .btn-ado-secondary {
820
+ background: #fff;
821
+ color: var(--text-main);
822
+ border: 1.5px solid #e2e8f0;
823
+ padding: 12px 28px;
824
+ border-radius: 12px;
825
+ font-weight: 700;
826
+ transition: all 0.2s;
827
+ }
828
+
829
+ .btn-ado-secondary:hover {
830
+ background: #f8fafc;
831
+ border-color: #cbd5e1;
832
+ }
833
+
834
+ /* Custom select arrow for ado-form-input */
835
+ select.ado-form-input {
836
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
837
+ background-repeat: no-repeat;
838
+ background-position: right 1rem center;
839
+ background-size: 16px 12px;
840
+ }
841
+
842
+ .modal-xl {
843
+ max-width: 1000px !important;
844
+ width: 95% !important;
845
+ }
846
+
847
+ /* Restoration of Bug List UI */
848
+ .bug-list-item {
849
+ border: 1px solid #e2e8f0;
850
+ border-radius: 12px;
851
+ margin-bottom: 12px;
852
+ overflow: hidden;
853
+ background: #fff;
854
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
855
+ }
856
+
857
+ .bug-item-header {
858
+ background: #fff;
859
+ padding: 14px 20px;
860
+ display: flex;
861
+ justify-content: space-between;
862
+ align-items: center;
863
+ cursor: pointer;
864
+ transition: background 0.2s;
865
+ }
866
+
867
+ .bug-item-header:hover {
868
+ background: #f8fafc;
869
+ }
870
+
871
+ .bug-item-header .chevron {
872
+ transition: transform 0.3s;
873
+ color: var(--text-muted);
874
+ }
875
+
876
+ .bug-item-header[aria-expanded="true"] .chevron {
877
+ transform: rotate(180deg);
878
+ }
879
+
880
+ .bug-snippet {
881
+ font-family: 'JetBrains Mono', monospace;
882
+ font-size: 0.85rem;
883
+ color: var(--primary-dark);
884
+ background: var(--primary-soft);
885
+ padding: 6px 12px;
886
+ border-radius: 8px;
887
+ max-width: 65%;
888
+ white-space: nowrap;
889
+ overflow: hidden;
890
+ text-overflow: ellipsis;
891
+ border: 1px solid rgba(99, 102, 241, 0.1);
892
+ }
893
+
894
+ .bug-details-body {
895
+ padding: 20px;
896
+ background: #f8fafc;
897
+ border-top: 1px solid #f1f5f9;
898
+ }
899
+
900
+ .log-bug-btn {
901
+ display: inline-flex !important;
902
+ align-items: center !important;
903
+ justify-content: center !important;
904
+ gap: 8px !important;
905
+ padding: 6px 16px !important;
906
+ }
907
+
908
+ .log-bug-btn span {
909
+ font-size: 18px !important;
910
+ margin: 0 !important;
911
+ display: inline-block;
912
+ vertical-align: middle;
913
+ }
914
+ </style>
915
+ </head>
916
+
917
+ <body>
918
+ <div id="loader-overlay">
919
+ <div class="loader-pulse">
920
+ <span class="material-symbols-outlined" style="font-size: 32px;">query_stats</span>
921
+ </div>
922
+ <div class="loader-text">Compiling Audit Results</div>
923
+ </div>
924
+
925
+ <header>
926
+ <a href="#" class="brand" style="cursor: default;">
927
+ <div class="brand-icon">
928
+ <span class="material-symbols-outlined" style="font-size: 22px;">accessibility_new</span>
929
+ </div>
930
+ <h1 class="brand-text">Accessibility Audit</h1>
931
+ </a>
932
+ <div class="header-actions" style="display: flex; gap: 16px; align-items: center;">
933
+ <a href="../summary.html" class="btn btn-link btn-sm btn-back">
934
+ <span class="material-symbols-outlined btn-back-icon">arrow_back</span>
935
+ Back to Summary
936
+ </a>
937
+ <button type="button" class="btn btn-outline-primary btn-sm btn-token" data-bs-toggle="modal"
938
+ data-bs-target="#tokenModal">
939
+ <span class="material-symbols-outlined btn-token-icon">key</span>
940
+ ADO Token
941
+ </button>
942
+ </div>
943
+ </header>
944
+
945
+ <main class="container">
946
+ <section class="page-hero">
947
+ <div class="hero-content">
948
+ <h2>Target Resource</h2>
949
+ <p class="page-url">
950
+ <%= data.pageKey %>
951
+ </p>
952
+ </div>
953
+ <div class="stats-pills">
954
+ <% if (!data.errors || data.errors.length===0) { %>
955
+ <div class="stat-pill stat-pill-passed">
956
+ <span class="material-symbols-outlined text-success">check_circle</span>
957
+ <span>Passed</span>
958
+ </div>
959
+ <% } %>
960
+ <div class="stat-pill">
961
+ <span class="material-symbols-outlined stat-pill-icon">bug_report</span>
962
+ <span>
963
+ <%= (data.errors ? data.errors.reduce((acc, err)=> acc + err.total, 0) : 0) %> Issues
964
+ </span>
965
+ </div>
966
+ </div>
967
+ </section>
968
+
969
+ <!-- Modal for ADO Token -->
970
+ <div class="modal fade" id="tokenModal" tabindex="-1">
971
+ <div class="modal-dialog modal-dialog-centered">
972
+ <div class="modal-content">
973
+ <form id="tokenForm">
974
+ <div class="modal-header modal-header-custom">
975
+ <h5 class="modal-title modal-title-custom">Azure DevOps Access</h5>
976
+ <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
977
+ </div>
978
+ <div class="modal-body modal-body-custom">
979
+ <p class="modal-desc">Securely provide your Personal Access Token (PAT) to enable automated bug reporting.
980
+ </p>
981
+ <div class="mb-3">
982
+ <div class="token-input-wrap">
983
+ <span class="material-symbols-outlined token-icon">lock</span>
984
+ <input type="password" class="form-control token-input" id="tokenInput" placeholder="Enter PAT Token"
985
+ required>
986
+ </div>
987
+ </div>
988
+ </div>
989
+ <div class="modal-footer modal-footer-custom">
990
+ <button type="submit" class="btn btn-primary w-100 btn-init">Initialize Session</button>
991
+ </div>
992
+ </form>
993
+ </div>
994
+ </div>
995
+ </div>
996
+
997
+ <% if(data.errors && data.errors.length) { %>
998
+ <div class="violation-section">
999
+ <% data.errors.forEach(function(error, errorIndex) { %>
1000
+ <div class="violation-card">
1001
+ <div class="violation-header">
1002
+ <div class="title-meta">
1003
+ <span class="violation-title">
1004
+ <%= error.id %>
1005
+ </span>
1006
+ <span class="violation-rule-info">
1007
+ <span class="rule-name">
1008
+ <%= error.wcagRule %>
1009
+ </span>
1010
+ <span class="rule-sep">•</span>
1011
+ <span>
1012
+ <%= error.description %>
1013
+ </span>
1014
+ </span>
1015
+ </div>
1016
+ <span class="severity-badge <%= error.severity %>">
1017
+ <%= error.total %>
1018
+ <%= error.severity %>
1019
+ </span>
1020
+ </div>
1021
+
1022
+ <div class="violation-body">
1023
+ <div class="fix-suggestion">
1024
+ <span class="material-symbols-outlined fix-icon">lightbulb</span>
1025
+ <div>
1026
+ <div class="fix-title">Recommended Action</div>
1027
+ <%= error.help %>
1028
+ <a href="<%= error.helpUrl %>" target="_blank" class="fix-link">Technical Docs <span
1029
+ class="material-symbols-outlined fix-link-icon">open_in_new</span></a>
1030
+ </div>
1031
+ </div>
1032
+
1033
+ <div class="instances-list" style="margin-top: 16px;">
1034
+ <% if(error.target && error.target.length) { %>
1035
+ <h4 class="instances-title">
1036
+ <span class="material-symbols-outlined instances-icon">preview</span>
1037
+ Bug Candidates (<%= error.target.length %>)
1038
+ </h4>
1039
+
1040
+ <% error.target.forEach(function(target, index) { %>
1041
+ <div class="bug-list-item">
1042
+ <div class="bug-item-header collapsed" data-bs-toggle="collapse"
1043
+ data-bs-target="#details-<%= errorIndex %>-<%= index %>" aria-expanded="false">
1044
+ <div class="d-flex align-items-center gap-3" style="max-width: 75%;">
1045
+ <span class="material-symbols-outlined chevron">expand_more</span>
1046
+ <div class="bug-snippet" title="<%= error.help %>: <%= target.snippet || target.element %>">
1047
+ <strong style="color: var(--primary);">
1048
+ <%= error.help %>
1049
+ </strong>: <%= target.snippet || target.element %>
1050
+ </div>
1051
+ </div>
1052
+ <div class="d-flex gap-2">
1053
+ <button class="btn btn-sm btn-bug log-bug-btn" type="button"
1054
+ onclick="handleLogBugClick(this, event)"
1055
+ data-title="<%= error.help %> (<%= target.snippet || target.element %>)"
1056
+ data-axe-id="<%= error.id %>" data-help="<%= error.help %>"
1057
+ data-severity="<%= error.severity %>" data-steps="<%= target.stepsJson || '[]' %>"
1058
+ data-screenshot="<%= target.screenshotBase64 %>" data-wcag="<%= error.wcagRule %>">
1059
+ <span class="material-symbols-outlined" style="font-size: 16px;">bug_report</span>
1060
+ Log Bug
1061
+ </button>
1062
+ </div>
1063
+ </div>
1064
+
1065
+ <div id="details-<%= errorIndex %>-<%= index %>" class="collapse">
1066
+ <div class="bug-details-body">
1067
+ <!-- Left: Repro Steps -->
1068
+ <div style="margin-bottom: 16px;">
1069
+ <div class="bug-section-title">Reproduction Steps</div>
1070
+ <div class="bug-content-box">
1071
+ <% if(target.steps && target.steps.length) { %>
1072
+ <ol style="margin: 0; padding-left: 18px; line-height: 1.5;">
1073
+ <% target.steps.forEach(function(step) { %>
1074
+ <li>
1075
+ <%= step %>
1076
+ </li>
1077
+ <% }); %>
1078
+ </ol>
1079
+ <% } else { %>
1080
+ <span style="color: var(--text-muted); font-style: italic;">No interaction steps
1081
+ recorded. Issue found via static scan.</span>
1082
+ <% } %>
1083
+ </div>
1084
+ </div>
1085
+
1086
+ <!-- Right: Visual -->
1087
+ <div>
1088
+ <div class="bug-section-title">Visual Evidence</div>
1089
+ <div class="screenshot-container">
1090
+ <img src="<%= target.screenshot %>" alt="Violation Evidence">
1091
+ </div>
1092
+ </div>
1093
+ </div>
1094
+ </div>
1095
+ </div>
1096
+ <% }); %>
1097
+ <% } %>
1098
+ </div>
1099
+ </div>
1100
+ </div>
1101
+ <% }); %>
1102
+ </div>
1103
+
1104
+ <!-- ADO Bug Preview Modal -->
1105
+ <div class="modal fade ado-modal-premium" id="bugPreviewModal" tabindex="-1">
1106
+ <div class="modal-dialog modal-xl modal-dialog-centered">
1107
+ <div class="modal-content">
1108
+ <div class="modal-header">
1109
+ <div class="modal-title">
1110
+ <span class="material-symbols-outlined" style="color: #cc293d; font-size: 24px;">bug_report</span>
1111
+ New Bug Entry - Azure DevOps
1112
+ </div>
1113
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
1114
+ </div>
1115
+ <div class="modal-body">
1116
+ <div class="ado-field-group">
1117
+ <label class="ado-form-label">Title</label>
1118
+ <input type="text" id="bugTitleInput" class="ado-form-input" placeholder="Enter bug title">
1119
+ </div>
1120
+
1121
+ <div class="row">
1122
+ <div class="col-md-6">
1123
+ <div class="ado-field-group">
1124
+ <label class="ado-form-label">Severity</label>
1125
+ <select id="bugSeverityInput" class="ado-form-input">
1126
+ <option value="critical">Critical (Priority 1)</option>
1127
+ <option value="serious">Serious (Priority 2)</option>
1128
+ <option value="moderate">Moderate (Priority 3)</option>
1129
+ <option value="minor">Minor (Priority 4)</option>
1130
+ </select>
1131
+ </div>
1132
+ </div>
1133
+ <div class="col-md-6">
1134
+ <div class="ado-field-group">
1135
+ <label class="ado-form-label">Target Area</label>
1136
+ <input type="text" id="bugAreaInput" class="ado-form-input" value="Accessibility">
1137
+ </div>
1138
+ </div>
1139
+ </div>
1140
+
1141
+ <div class="ado-field-group">
1142
+ <label class="ado-form-label">Reproduction Steps & Description</label>
1143
+ <div id="bugReproPreview" class="ado-repro-preview" style="min-height: 150px;"></div>
1144
+ </div>
1145
+
1146
+ <!-- Visual Evidence - Single Column Full Width -->
1147
+ <div class="ado-field-group" id="screenshotThumbContainer" style="display: none;">
1148
+ <label class="ado-form-label">
1149
+ <span class="material-symbols-outlined" style="font-size: 14px; vertical-align: middle;">image</span>
1150
+ Violation Screenshot
1151
+ </label>
1152
+ <img id="bugScreenshotPreview" class="screenshot-thumb" src="" alt="Screenshot">
1153
+ </div>
1154
+
1155
+ <div class="ado-field-group" id="videoThumbContainer" style="display: none;">
1156
+ <label class="ado-form-label">
1157
+ <span class="material-symbols-outlined"
1158
+ style="font-size: 14px; vertical-align: middle;">videocam</span>
1159
+ Session Recording
1160
+ </label>
1161
+ <video id="bugVideoPreview" class="video-preview-player" controls>
1162
+ Your browser does not support the video tag.
1163
+ </video>
1164
+ </div>
1165
+
1166
+ <div class="ado-field-group" style="margin-bottom: 0;">
1167
+ <div class="ado-repro-preview"
1168
+ style="font-size: 0.75rem; background: #fffcf0; border-color: #f9f0c3; padding: 12px; line-height: 1.5;">
1169
+ <b>Environment:</b> Playwright engine • <b>URL:</b> <span
1170
+ style="word-break: break-all; color: var(--primary);">
1171
+ <%= data.pageKey %>
1172
+ </span>
1173
+ </div>
1174
+ </div>
1175
+ </div>
1176
+ <div class="modal-footer">
1177
+ <button type="button" class="btn-ado-secondary" data-bs-dismiss="modal">Cancel</button>
1178
+ <button type="button" id="confirmBugBtn" class="btn-ado-primary">Create Bug</button>
1179
+ </div>
1180
+ </div>
1181
+ </div>
1182
+ </div>
1183
+ <% } else { %>
1184
+ <div class="success-verified-card">
1185
+ <span class="material-symbols-outlined success-hero-icon">check_circle</span>
1186
+ <h3 class="success-hero-title">Compliance Verified</h3>
1187
+ <p class="success-hero-desc">Excellent! No accessibility violations were detected for this target resource.
1188
+ </p>
1189
+ </div>
1190
+ <% } %>
1191
+
1192
+ <% if(data.video) { %>
1193
+ <section class="video-card">
1194
+ <div class="video-header">
1195
+ <div class="video-icon-wrap">
1196
+ <span class="material-symbols-outlined">videocam</span>
1197
+ </div>
1198
+ <h2 class="video-title">Audit Session Recording</h2>
1199
+ </div>
1200
+ <div class="ratio ratio-16x9 video-container">
1201
+ <video controls>
1202
+ <source src="<%= data.video %>" type="video/webm">
1203
+ Your browser does not support the video tag.
1204
+ </video>
1205
+ </div>
1206
+ </section>
1207
+ <% } %>
1208
+ </main>
1209
+
1210
+ <footer>
1211
+ <div class="powered-by">
1212
+ Generated by <span class="badge-tool">Snap Ally</span>
1213
+ </div>
1214
+ <div>
1215
+ <span style="opacity: 0.7;">&copy; <%= new Date().getFullYear() %> Snap Ally</span>
1216
+ </div>
1217
+ </footer>
1218
+
1219
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
1220
+ <script>
1221
+ window.addEventListener('load', function () {
1222
+ const loader = document.getElementById('loader-overlay');
1223
+ loader.style.opacity = '0';
1224
+ setTimeout(() => { loader.style.display = 'none'; }, 500);
1225
+ });
1226
+
1227
+ document.getElementById('tokenForm').addEventListener('submit', function (e) {
1228
+ e.preventDefault();
1229
+ const token = document.getElementById('tokenInput').value;
1230
+ if (token) {
1231
+ sessionStorage.setItem('userToken', token);
1232
+ bootstrap.Modal.getInstance(document.getElementById('tokenModal')).hide();
1233
+ }
1234
+ });
1235
+
1236
+ async function uploadAttachment(blob, name) {
1237
+ const org = "<%= data.adoOrganization %>";
1238
+ const proj = "<%= data.adoProject %>";
1239
+ const pat = sessionStorage.getItem('userToken');
1240
+ if (!org || !proj || !pat) return null;
1241
+
1242
+ const url = `https://dev.azure.com/${org}/${proj}/_apis/wit/attachments?fileName=${name}&api-version=7.1`;
1243
+
1244
+ const res = await fetch(url, {
1245
+ method: 'POST',
1246
+ headers: { 'Content-Type': 'application/octet-stream', 'Authorization': `Basic ${btoa(':' + pat)}` },
1247
+ body: blob
1248
+ });
1249
+ return res.ok ? (await res.json()).url : null;
1250
+ }
1251
+
1252
+ function handleLogBugClick(btn, event) {
1253
+ if (event) {
1254
+ event.preventDefault();
1255
+ event.stopPropagation();
1256
+ }
1257
+ const d = btn.dataset;
1258
+ createAzureDevOpsBug(d.title, d.axeId, d.help, d.severity, d.steps, d.screenshot, d.wcag);
1259
+ }
1260
+
1261
+ function createAzureDevOpsBug(title, axeId, help, severity, stepsJson, screenshotBase64, wcag) {
1262
+ const pat = sessionStorage.getItem('userToken');
1263
+ if (!pat) {
1264
+ const tokenModal = bootstrap.Modal.getOrCreateInstance(document.getElementById('tokenModal'));
1265
+ tokenModal.show();
1266
+ return;
1267
+ }
1268
+
1269
+ // Populate Modal Fields
1270
+ document.getElementById('bugTitleInput').value = `[A11y] ${title}`;
1271
+ document.getElementById('bugSeverityInput').value = severity;
1272
+
1273
+ const steps = JSON.parse(stepsJson);
1274
+ const stepsHtml = steps.length
1275
+ ? `<ol>${steps.map(s => `<li>${s}</li>`).join('')}</ol>`
1276
+ : `<i>No interaction steps. Issue identified via static scan.</i>`;
1277
+
1278
+ document.getElementById('bugReproPreview').innerHTML = `
1279
+ <div style="margin-bottom: 4px;"><b>Rule:</b> ${axeId} (${wcag})</div>
1280
+ <div style="margin-bottom: 8px;"><b>Recommendation:</b> ${help}</div>
1281
+ <div style="border-top: 1px solid #e2e8f0; margin: 8px 0; padding-top: 8px;"><b>Steps:</b></div>
1282
+ ${stepsHtml}
1283
+ `;
1284
+
1285
+ // Visual Evidence
1286
+ const screenshotPreview = document.getElementById('bugScreenshotPreview');
1287
+ if (screenshotBase64) {
1288
+ screenshotPreview.src = `data:image/png;base64,${screenshotBase64}`;
1289
+ document.getElementById('screenshotThumbContainer').style.display = 'block';
1290
+ } else {
1291
+ document.getElementById('screenshotThumbContainer').style.display = 'none';
1292
+ }
1293
+
1294
+ const videoRawPath = "<%= data.video %>";
1295
+ if (videoRawPath) {
1296
+ // Fix: Video is in the same folder as the report, no need for '../'
1297
+ const videoFinalPath = videoRawPath.startsWith('http') ? videoRawPath : videoRawPath;
1298
+ const videoPreview = document.getElementById('bugVideoPreview');
1299
+ videoPreview.src = videoFinalPath;
1300
+ document.getElementById('videoThumbContainer').style.display = 'block';
1301
+ } else {
1302
+ document.getElementById('videoThumbContainer').style.display = 'none';
1303
+ }
1304
+
1305
+ // Store data for submission
1306
+ window.currentBugData = { axeId, wcag, help, stepsHtml, screenshotBase64 };
1307
+
1308
+ const modalElement = document.getElementById('bugPreviewModal');
1309
+ const modal = bootstrap.Modal.getOrCreateInstance(modalElement);
1310
+ modal.show();
1311
+ }
1312
+
1313
+ // Force focus on title input when modal opens to ensure editability
1314
+ const bugModalEl = document.getElementById('bugPreviewModal');
1315
+ if (bugModalEl) {
1316
+ bugModalEl.addEventListener('shown.bs.modal', function () {
1317
+ const input = document.getElementById('bugTitleInput');
1318
+ if (input) {
1319
+ input.focus();
1320
+ // Select text for quick editing
1321
+ input.select();
1322
+ // Ensure not inert/disabled
1323
+ input.removeAttribute('readonly');
1324
+ input.removeAttribute('disabled');
1325
+ }
1326
+ });
1327
+ }
1328
+
1329
+ // Single Global Event Listener for Confirmation
1330
+ document.getElementById('confirmBugBtn').addEventListener('click', async () => {
1331
+ const btn = document.getElementById('confirmBugBtn');
1332
+ const modal = bootstrap.Modal.getInstance(document.getElementById('bugPreviewModal'));
1333
+
1334
+ btn.disabled = true;
1335
+ btn.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Creating...';
1336
+
1337
+ try {
1338
+ await submitFinalBug();
1339
+ modal.hide();
1340
+ } catch (err) {
1341
+ console.error(err);
1342
+ } finally {
1343
+ btn.disabled = false;
1344
+ btn.innerHTML = 'Create Bug';
1345
+ }
1346
+ });
1347
+
1348
+ async function submitFinalBug() {
1349
+ const org = "<%= data.adoOrganization %>";
1350
+ const proj = "<%= data.adoProject %>";
1351
+ const pat = sessionStorage.getItem('userToken');
1352
+ const { axeId, wcag, help, stepsHtml, screenshotBase64 } = window.currentBugData;
1353
+
1354
+ const title = document.getElementById('bugTitleInput').value;
1355
+ const severity = document.getElementById('bugSeverityInput').value;
1356
+
1357
+ let screenshotUrl = null;
1358
+ if (screenshotBase64) {
1359
+ const screenshotBlob = await fetch(`data:image/png;base64,${screenshotBase64}`).then(res => res.blob());
1360
+ screenshotUrl = await uploadAttachment(screenshotBlob, 'a11y-issue.png');
1361
+ }
1362
+
1363
+ const videoRawPath = "<%= data.video %>";
1364
+ let videoUrl = null;
1365
+ if (videoRawPath) {
1366
+ try {
1367
+ // Fix: Video is in the same folder as the report
1368
+ const videoFinalPath = videoRawPath.startsWith('http') ? videoRawPath : videoRawPath;
1369
+ const videoBlob = await fetch(videoFinalPath).then(res => res.blob());
1370
+ videoUrl = await uploadAttachment(videoBlob, 'session-recording.webm');
1371
+ } catch (e) {
1372
+ console.error("Failed to video upload", e);
1373
+ }
1374
+ }
1375
+
1376
+ const combinedReproHtml = `
1377
+ <div style="margin-bottom: 12px;"><b>Rule:</b> ${axeId} (${wcag})</div>
1378
+ <div style="margin-bottom: 12px;"><b>Recommendation:</b> ${help}</div>
1379
+ <hr>
1380
+ <div style="margin-bottom: 8px;"><b>Steps to Reproduce:</b></div>
1381
+ ${stepsHtml}
1382
+ `;
1383
+
1384
+ const payload = [
1385
+ { op: "add", path: "/fields/System.Title", value: title },
1386
+ { op: "add", path: "/fields/Microsoft.VSTS.TCM.ReproSteps", value: combinedReproHtml },
1387
+ { op: "add", path: "/fields/System.Description", value: `Found at: <a href="${window.location.href}">${window.location.href}</a>` },
1388
+ { op: "add", path: "/fields/Microsoft.VSTS.Common.Priority", value: severity === 'critical' ? 1 : (severity === 'serious' ? 2 : 3) },
1389
+ { op: "add", path: "/fields/System.Tags", value: "A11y;SnapAlly;UI-Test" }
1390
+ ];
1391
+
1392
+ if (screenshotUrl) {
1393
+ payload.push({ op: "add", path: "/relations/-", value: { rel: "AttachedFile", url: screenshotUrl, attributes: { comment: "Accessibility Violation Screenshot" } } });
1394
+ }
1395
+
1396
+ if (videoUrl) {
1397
+ payload.push({ op: "add", path: "/relations/-", value: { rel: "AttachedFile", url: videoUrl, attributes: { comment: "Audit Session Recording" } } });
1398
+ }
1399
+
1400
+ const res = await fetch(`https://dev.azure.com/${org}/${proj}/_apis/wit/workitems/$Bug?api-version=7.1`, {
1401
+ method: 'POST',
1402
+ headers: { 'Content-Type': 'application/json-patch+json', 'Authorization': `Basic ${btoa(':' + pat)}` },
1403
+ body: JSON.stringify(payload)
1404
+ });
1405
+
1406
+ if (res.ok) alert('Bug successfully filed in Azure DevOps!');
1407
+ else alert('Failed to create bug. Check token and organization settings.');
1408
+ }
1409
+ </script>
1410
+ </body>
1411
+
1412
+ </html>