@govtechsg/oobee 0.10.76 → 0.10.77

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 (134) hide show
  1. package/.github/workflows/publish.yml +8 -1
  2. package/INTEGRATION.md +7 -3
  3. package/dist/cli.js +252 -0
  4. package/dist/combine.js +221 -0
  5. package/dist/constants/cliFunctions.js +306 -0
  6. package/dist/constants/common.js +1669 -0
  7. package/dist/constants/constants.js +913 -0
  8. package/dist/constants/errorMeta.json +319 -0
  9. package/dist/constants/itemTypeDescription.js +7 -0
  10. package/dist/constants/oobeeAi.js +121 -0
  11. package/dist/constants/questions.js +151 -0
  12. package/dist/constants/sampleData.js +176 -0
  13. package/dist/crawlers/commonCrawlerFunc.js +428 -0
  14. package/dist/crawlers/crawlDomain.js +613 -0
  15. package/dist/crawlers/crawlIntelligentSitemap.js +135 -0
  16. package/dist/crawlers/crawlLocalFile.js +151 -0
  17. package/dist/crawlers/crawlSitemap.js +303 -0
  18. package/dist/crawlers/custom/escapeCssSelector.js +10 -0
  19. package/dist/crawlers/custom/evaluateAltText.js +11 -0
  20. package/dist/crawlers/custom/extractAndGradeText.js +44 -0
  21. package/dist/crawlers/custom/extractText.js +27 -0
  22. package/dist/crawlers/custom/findElementByCssSelector.js +36 -0
  23. package/dist/crawlers/custom/flagUnlabelledClickableElements.js +963 -0
  24. package/dist/crawlers/custom/framesCheck.js +37 -0
  25. package/dist/crawlers/custom/getAxeConfiguration.js +111 -0
  26. package/dist/crawlers/custom/gradeReadability.js +23 -0
  27. package/dist/crawlers/custom/utils.js +1024 -0
  28. package/dist/crawlers/custom/xPathToCss.js +147 -0
  29. package/dist/crawlers/guards/urlGuard.js +71 -0
  30. package/dist/crawlers/pdfScanFunc.js +276 -0
  31. package/dist/crawlers/runCustom.js +89 -0
  32. package/dist/exclusions.txt +7 -0
  33. package/dist/generateHtmlReport.js +144 -0
  34. package/dist/index.js +62 -0
  35. package/dist/logs.js +84 -0
  36. package/dist/mergeAxeResults.js +1571 -0
  37. package/dist/npmIndex.js +429 -0
  38. package/dist/proxyService.js +360 -0
  39. package/dist/runGenerateJustHtmlReport.js +16 -0
  40. package/dist/screenshotFunc/htmlScreenshotFunc.js +355 -0
  41. package/dist/screenshotFunc/pdfScreenshotFunc.js +645 -0
  42. package/dist/services/s3Uploader.js +127 -0
  43. package/dist/static/ejs/partials/components/allIssues/AllIssues.ejs +9 -0
  44. package/dist/static/ejs/partials/components/allIssues/CategoryBadges.ejs +82 -0
  45. package/dist/static/ejs/partials/components/allIssues/FilterBar.ejs +33 -0
  46. package/dist/static/ejs/partials/components/allIssues/IssuesTable.ejs +41 -0
  47. package/dist/static/ejs/partials/components/header/SiteInfo.ejs +119 -0
  48. package/dist/static/ejs/partials/components/header/aboutScanModal/AboutScanModal.ejs +15 -0
  49. package/dist/static/ejs/partials/components/header/aboutScanModal/ScanConfiguration.ejs +44 -0
  50. package/dist/static/ejs/partials/components/header/aboutScanModal/ScanDetails.ejs +142 -0
  51. package/dist/static/ejs/partials/components/prioritiseIssues/IssueDetailCard.ejs +36 -0
  52. package/dist/static/ejs/partials/components/prioritiseIssues/PrioritiseIssues.ejs +47 -0
  53. package/dist/static/ejs/partials/components/ruleModal/ruleOffcanvas.ejs +196 -0
  54. package/dist/static/ejs/partials/components/scannedPagesSegmentedTabs.ejs +48 -0
  55. package/dist/static/ejs/partials/components/screenshotLightbox.ejs +13 -0
  56. package/dist/static/ejs/partials/components/shared/InfoAlert.ejs +3 -0
  57. package/dist/static/ejs/partials/components/summaryScanAbout.ejs +141 -0
  58. package/dist/static/ejs/partials/components/summaryScanResults.ejs +16 -0
  59. package/dist/static/ejs/partials/components/summaryTable.ejs +20 -0
  60. package/dist/static/ejs/partials/components/summaryWcagCompliance.ejs +94 -0
  61. package/dist/static/ejs/partials/components/topTen.ejs +6 -0
  62. package/dist/static/ejs/partials/components/wcagCompliance/FailedCriteria.ejs +47 -0
  63. package/dist/static/ejs/partials/components/wcagCompliance/WcagCompliance.ejs +16 -0
  64. package/dist/static/ejs/partials/components/wcagCompliance/WcagGaugeBar.ejs +16 -0
  65. package/dist/static/ejs/partials/components/wcagCoverageDetails.ejs +18 -0
  66. package/dist/static/ejs/partials/footer.ejs +24 -0
  67. package/dist/static/ejs/partials/header.ejs +14 -0
  68. package/dist/static/ejs/partials/main.ejs +29 -0
  69. package/dist/static/ejs/partials/scripts/allIssues/AllIssues.ejs +376 -0
  70. package/dist/static/ejs/partials/scripts/bootstrap.ejs +8 -0
  71. package/dist/static/ejs/partials/scripts/categorySummary.ejs +141 -0
  72. package/dist/static/ejs/partials/scripts/decodeUnzipParse.ejs +3 -0
  73. package/dist/static/ejs/partials/scripts/header/SiteInfo.ejs +44 -0
  74. package/dist/static/ejs/partials/scripts/header/aboutScanModal/AboutScanModal.ejs +51 -0
  75. package/dist/static/ejs/partials/scripts/header/aboutScanModal/ScanConfiguration.ejs +127 -0
  76. package/dist/static/ejs/partials/scripts/header/aboutScanModal/ScanDetails.ejs +60 -0
  77. package/dist/static/ejs/partials/scripts/highlightjs.ejs +335 -0
  78. package/dist/static/ejs/partials/scripts/popper.ejs +7 -0
  79. package/dist/static/ejs/partials/scripts/prioritiseIssues/IssueDetailCard.ejs +137 -0
  80. package/dist/static/ejs/partials/scripts/prioritiseIssues/PrioritiseIssues.ejs +214 -0
  81. package/dist/static/ejs/partials/scripts/prioritiseIssues/wcagSvgMap.ejs +861 -0
  82. package/dist/static/ejs/partials/scripts/ruleModal/constants.ejs +957 -0
  83. package/dist/static/ejs/partials/scripts/ruleModal/itemCardRenderer.ejs +353 -0
  84. package/dist/static/ejs/partials/scripts/ruleModal/pageAccordionBuilder.ejs +468 -0
  85. package/dist/static/ejs/partials/scripts/ruleModal/ruleOffcanvas.ejs +306 -0
  86. package/dist/static/ejs/partials/scripts/ruleModal/utilities.ejs +483 -0
  87. package/dist/static/ejs/partials/scripts/scannedPagesSegmentedTabs.ejs +35 -0
  88. package/dist/static/ejs/partials/scripts/screenshotLightbox.ejs +75 -0
  89. package/dist/static/ejs/partials/scripts/summaryScanResults.ejs +14 -0
  90. package/dist/static/ejs/partials/scripts/summaryTable.ejs +78 -0
  91. package/dist/static/ejs/partials/scripts/topTen.ejs +61 -0
  92. package/dist/static/ejs/partials/scripts/utils.ejs +453 -0
  93. package/dist/static/ejs/partials/scripts/wcagCompliance/FailedCriteria.ejs +103 -0
  94. package/dist/static/ejs/partials/scripts/wcagCompliance/WcagGaugeBar.ejs +47 -0
  95. package/dist/static/ejs/partials/scripts/wcagCompliance.ejs +15 -0
  96. package/dist/static/ejs/partials/scripts/wcagCoverageDetails.ejs +75 -0
  97. package/dist/static/ejs/partials/styles/allIssues/AllIssues.ejs +384 -0
  98. package/dist/static/ejs/partials/styles/bootstrap.ejs +12391 -0
  99. package/dist/static/ejs/partials/styles/header/SiteInfo.ejs +121 -0
  100. package/dist/static/ejs/partials/styles/header/aboutScanModal/AboutScanModal.ejs +82 -0
  101. package/dist/static/ejs/partials/styles/header/aboutScanModal/ScanConfiguration.ejs +50 -0
  102. package/dist/static/ejs/partials/styles/header/aboutScanModal/ScanDetails.ejs +149 -0
  103. package/dist/static/ejs/partials/styles/header.ejs +7 -0
  104. package/dist/static/ejs/partials/styles/highlightjs.ejs +54 -0
  105. package/dist/static/ejs/partials/styles/prioritiseIssues/IssueDetailCard.ejs +141 -0
  106. package/dist/static/ejs/partials/styles/prioritiseIssues/PrioritiseIssues.ejs +204 -0
  107. package/dist/static/ejs/partials/styles/ruleModal/ruleOffcanvas.ejs +456 -0
  108. package/dist/static/ejs/partials/styles/scannedPagesSegmentedTabs.ejs +46 -0
  109. package/dist/static/ejs/partials/styles/shared/InfoAlert.ejs +12 -0
  110. package/dist/static/ejs/partials/styles/styles.ejs +1607 -0
  111. package/dist/static/ejs/partials/styles/summaryBootstrap.ejs +12458 -0
  112. package/dist/static/ejs/partials/styles/topTenCard.ejs +44 -0
  113. package/dist/static/ejs/partials/styles/wcagCompliance/FailedCriteria.ejs +59 -0
  114. package/dist/static/ejs/partials/styles/wcagCompliance/WcagGaugeBar.ejs +62 -0
  115. package/dist/static/ejs/partials/styles/wcagCompliance.ejs +36 -0
  116. package/dist/static/ejs/partials/styles/wcagCoverageDetails.ejs +33 -0
  117. package/dist/static/ejs/partials/summaryHeader.ejs +70 -0
  118. package/dist/static/ejs/partials/summaryMain.ejs +49 -0
  119. package/dist/static/ejs/report.ejs +226 -0
  120. package/dist/static/ejs/summary.ejs +47 -0
  121. package/dist/types/types.js +1 -0
  122. package/dist/utils.js +1070 -0
  123. package/examples/oobee-cypress-integration-js/cypress/support/e2e.js +36 -6
  124. package/examples/oobee-cypress-integration-js/cypress.config.js +45 -1
  125. package/examples/oobee-cypress-integration-ts/cypress.config.ts +47 -1
  126. package/examples/oobee-cypress-integration-ts/src/cypress/support/e2e.ts +36 -6
  127. package/examples/oobee-playwright-integration-js/oobee-playwright-demo.js +2 -1
  128. package/examples/oobee-playwright-integration-ts/src/oobee-playwright-demo.ts +2 -1
  129. package/package.json +9 -3
  130. package/src/constants/common.ts +2 -2
  131. package/src/constants/constants.ts +3 -1
  132. package/src/crawlers/crawlDomain.ts +1 -0
  133. package/src/crawlers/runCustom.ts +0 -1
  134. package/src/npmIndex.ts +42 -24
@@ -0,0 +1,103 @@
1
+ <script>
2
+ (function populateFailedCriteria() {
3
+ const wcagLinks = scanData?.wcagLinks || {};
4
+ const wcagViolations = scanData?.wcagViolations || [];
5
+ const wcagClauses = scanData?.wcagClauses || {};
6
+ const wcagCriteriaLabels = scanData?.wcagCriteriaLabels || {};
7
+
8
+ const failedCriteria = [];
9
+
10
+ wcagViolations.forEach(violation => {
11
+ const formattedId = formatWcagId(violation);
12
+
13
+ const level = wcagCriteriaLabels[formattedId];
14
+
15
+ if (level === 'AAA') {
16
+ return;
17
+ }
18
+
19
+ const wcagInfo = wcagLinks[formattedId];
20
+
21
+ if (wcagInfo) {
22
+ const clauseKey = formattedId.replace('WCAG ', '');
23
+
24
+ failedCriteria.push({
25
+ id: formattedId,
26
+ name: wcagClauses[clauseKey] || formattedId,
27
+ });
28
+ }
29
+ });
30
+
31
+ console.log('wcagViolations 111', wcagViolations)
32
+
33
+ failedCriteria.sort((a, b) => {
34
+ const aNum = a.id.replace('WCAG ', '').split('.').map(Number);
35
+ const bNum = b.id.replace('WCAG ', '').split('.').map(Number);
36
+
37
+ for (let i = 0; i < Math.max(aNum.length, bNum.length); i++) {
38
+ if ((aNum[i] || 0) !== (bNum[i] || 0)) {
39
+ return (aNum[i] || 0) - (bNum[i] || 0);
40
+ }
41
+ }
42
+ return 0;
43
+ });
44
+
45
+ const countElement = document.getElementById('failedCriteriaCount');
46
+ if (countElement) {
47
+ countElement.textContent = failedCriteria.length;
48
+ }
49
+
50
+ const modalCountElement = document.getElementById('failedCriteriaModalCount');
51
+ if (modalCountElement) {
52
+ modalCountElement.textContent = failedCriteria.length;
53
+ }
54
+
55
+ const listElement = document.getElementById('failedCriteriaList');
56
+ const cardElement = document.getElementById('failedCriteriaCard');
57
+
58
+ if (listElement && failedCriteria.length > 0) {
59
+ const maxDisplay = 5;
60
+ const displayCriteria = failedCriteria.slice(0, maxDisplay);
61
+
62
+ const listHTML = displayCriteria
63
+ .map(
64
+ criteria => `
65
+ <li class="d-flex justify-content-between">
66
+ <span>${criteria.id} – ${criteria.name}</span>
67
+ </li>
68
+ `,
69
+ )
70
+ .join('');
71
+
72
+ listElement.innerHTML = listHTML;
73
+
74
+ // Populate modal with all criteria
75
+ const modalListElement = document.getElementById('failedCriteriaModalList');
76
+ if (modalListElement) {
77
+ const allListHTML = failedCriteria
78
+ .map(
79
+ criteria => `
80
+ <li class="d-flex justify-content-between">
81
+ <span>${criteria.id} – ${criteria.name}</span>
82
+ </li>
83
+ `,
84
+ )
85
+ .join('');
86
+
87
+ modalListElement.innerHTML = allListHTML;
88
+ }
89
+
90
+ // Show "View all" button if there are more than maxDisplay criteria
91
+ if (failedCriteria.length > maxDisplay) {
92
+ const viewAllButton = document.getElementById('viewAllFailedCriteria');
93
+ if (viewAllButton) {
94
+ viewAllButton.hidden = false;
95
+ }
96
+ }
97
+ } else {
98
+ if (cardElement) {
99
+ cardElement.style.display = 'none';
100
+ }
101
+ }
102
+ })();
103
+ </script>
@@ -0,0 +1,47 @@
1
+ <script>
2
+ function renderWcagGaugeFromScanData() {
3
+ const wcagTotals = scanData?.wcagPassPercentage ?? {};
4
+ const totalChecks = Number(wcagTotals.totalWcagChecksAA ?? 0);
5
+ const violations = Number(wcagTotals.totalWcagViolationsAA ?? 0);
6
+ const checksPassed = Math.max(0, totalChecks - violations);
7
+
8
+ const numEl = document.getElementById('gaugeValueNumber');
9
+ const totalEl = document.getElementById('gaugeValueTotal');
10
+ const targetEl = document.getElementById('gaugeTarget');
11
+
12
+ if (numEl) numEl.textContent = String(checksPassed);
13
+ if (totalEl) totalEl.textContent = String(totalChecks);
14
+ if (targetEl) targetEl.textContent = String(totalChecks);
15
+
16
+ const gaugeFillPath = document.getElementById('gaugeFill');
17
+ const ratio = totalChecks > 0 ? checksPassed / totalChecks : 0;
18
+ const isPerfectWcagScore = totalChecks > 0 && violations === 0;
19
+
20
+ if (isPerfectWcagScore) {
21
+ numEl.classList.toggle('perfect-score', isPerfectWcagScore);
22
+ }
23
+
24
+ if (gaugeFillPath && typeof gaugeFillPath.getTotalLength === 'function') {
25
+ const len = gaugeFillPath.getTotalLength();
26
+ gaugeFillPath.style.strokeDasharray = String(len);
27
+ gaugeFillPath.style.strokeDashoffset = String(len);
28
+
29
+ gaugeFillPath.classList.remove('bad', 'good');
30
+ gaugeFillPath.classList.add(isPerfectWcagScore ? 'good' : 'bad');
31
+
32
+ requestAnimationFrame(() => {
33
+ gaugeFillPath.style.strokeDashoffset = String(len * (1 - ratio));
34
+ });
35
+ }
36
+
37
+ const gauge = document.getElementById('wcagGauge');
38
+ if (gauge) {
39
+ gauge.setAttribute(
40
+ 'aria-label',
41
+ `WCAG A & AA automated checks passed: ${checksPassed} out of ${totalChecks}`
42
+ );
43
+ }
44
+ }
45
+
46
+ renderWcagGaugeFromScanData();
47
+ </script>
@@ -0,0 +1,15 @@
1
+ <script>
2
+ function renderNotCompliantTextIfAny() {
3
+ const wcagTotals = scanData?.wcagPassPercentage ?? {};
4
+ const totalChecks = Number(wcagTotals.totalWcagChecksAA ?? 0);
5
+ const violations = Number(wcagTotals.totalWcagViolationsAA ?? 0);
6
+ const isPerfectWcagScore = totalChecks > 0 && violations === 0;
7
+ const statusEl = document.getElementById('wcagStatus');
8
+
9
+ if (statusEl) {
10
+ statusEl.hidden = isPerfectWcagScore;
11
+ }
12
+ }
13
+
14
+ renderNotCompliantTextIfAny();
15
+ </script>
@@ -0,0 +1,75 @@
1
+ <script>
2
+ const makeExternalIcon = () => {
3
+ const ns = "http://www.w3.org/2000/svg";
4
+ const svg = document.createElementNS(ns, "svg");
5
+ svg.setAttribute("width", "16");
6
+ svg.setAttribute("height", "12");
7
+ svg.setAttribute("viewBox", "0 0 8 8");
8
+ svg.setAttribute("aria-hidden", "true");
9
+ svg.setAttribute("focusable", "false");
10
+ svg.setAttribute("style", "margin-left: 0.3rem");
11
+ svg.setAttribute("class", "link-external-icon");
12
+
13
+ const path = document.createElementNS(ns, "path");
14
+ path.setAttribute(
15
+ "d",
16
+ "M7.11111 7.11111H0.888889V0.888889H4V0H0.888889C0.395556 0 0 0.4 0 0.888889V7.11111C0 7.6 0.395556 8 0.888889 8H7.11111C7.6 8 8 7.6 8 7.11111V4H7.11111V7.11111ZM4.88889 0V0.888889H6.48444L2.11556 5.25778L2.74222 5.88444L7.11111 1.51556V3.11111H8V0H4.88889Z"
17
+ );
18
+ path.setAttribute("fill", "#5735DF");
19
+ svg.appendChild(path);
20
+ return svg;
21
+ };
22
+
23
+ const scanDataWCAGList = () => {
24
+ const linksMap = scanData.wcagLinks || {};
25
+ const labelMap = scanData.wcagCriteriaLabels || {};
26
+ const norm = (s) => String(s||"").normalize("NFKC").replace(/\s+/g," ").trim().toUpperCase();
27
+
28
+ const labelLookup = new Map(
29
+ Object.entries(labelMap).map(([k,v]) => [norm(k), String(v).toUpperCase()])
30
+ );
31
+
32
+ const aaa = [];
33
+ const aaOrA = [];
34
+ Object.entries(linksMap).forEach(([code, url]) => {
35
+ const level = labelLookup.get(norm(code));
36
+ (level === "AAA" ? aaa : aaOrA).push([code, url]);
37
+ });
38
+
39
+ const wcagSort = ([a],[b]) => a.localeCompare(b, undefined, {numeric:true});
40
+ aaOrA.sort(wcagSort);
41
+ aaa.sort(wcagSort);
42
+
43
+ const listAA = document.getElementById("wcagLinksListAA");
44
+ const listAAA = document.getElementById("wcagLinksListAAA");
45
+ const countAA = document.getElementById("wcagAALabelCount");
46
+ const countAAA = document.getElementById("wcagAAALabelCount");
47
+
48
+ const render = (ul, items) => {
49
+ if (!ul) return;
50
+ ul.innerHTML = "";
51
+ items.forEach(([code, url]) => {
52
+ const li = document.createElement("li");
53
+
54
+ const a = document.createElement("a");
55
+ a.className = "wcag-link";
56
+ a.href = url;
57
+ a.target = "_blank";
58
+ a.rel = "noopener";
59
+
60
+ a.append(document.createTextNode(code), makeExternalIcon()); // text + icon both clickable
61
+
62
+ li.appendChild(a);
63
+ ul.appendChild(li);
64
+ });
65
+ };
66
+
67
+ render(listAA, aaOrA);
68
+ render(listAAA, aaa);
69
+
70
+ if (countAA) countAA.textContent = String(aaOrA.length);
71
+ if (countAAA) countAAA.textContent = String(aaa.length);
72
+ };
73
+
74
+ scanDataWCAGList();
75
+ </script>
@@ -0,0 +1,384 @@
1
+ <style>
2
+ #allIssuesSection {
3
+ margin-bottom: 1.5rem;
4
+ border-color: var(--soft-blue);
5
+ box-shadow: 0 2px 4px 0 rgb(77, 52, 191, 10%);
6
+ }
7
+
8
+ #allIssuesSection .card-body {
9
+ padding: 1.5rem;
10
+ }
11
+
12
+ /* Category Badges */
13
+ .category-badges-container {
14
+ display: flex;
15
+ gap: 1rem;
16
+ align-items: center;
17
+ }
18
+
19
+ .category-badge {
20
+ display: inline-flex;
21
+ align-items: center;
22
+ justify-content: center;
23
+ border-radius: 5px;
24
+ font-size: 12px;
25
+ font-weight: 600;
26
+ padding: 0 4px;
27
+ width: fit-content;
28
+ min-height: 20px;
29
+ height: 20px;
30
+ margin-right: 0.5rem;
31
+ }
32
+
33
+ .category-badge.must-fix {
34
+ background: var(--light-carmine-pink);
35
+ color: var(--true-white);
36
+ border: 1px solid var(--light-carmine-pink);
37
+ }
38
+
39
+ .category-badge.good-to-fix {
40
+ background: var(--strong-orange);
41
+ color: var(--true-white);
42
+ border: 1px solid var(--strong-orange);
43
+ }
44
+
45
+ .category-badge.needs-review {
46
+ background: var(--very-dark-gray);
47
+ color: var(--true-white);
48
+ border: 1px solid var(--very-dark-gray);
49
+ }
50
+
51
+ .category-item {
52
+ border-right: 1px solid var(--chinese-silver);
53
+ padding-right: 1rem;
54
+ line-height: 1.375rem;
55
+ }
56
+
57
+ .category-item:last-child {
58
+ border-right: none;
59
+ padding-right: 0;
60
+ }
61
+
62
+ .category-item-inner {
63
+ display: inline-flex;
64
+ align-items: center;
65
+ }
66
+
67
+ .category-tooltip-wrapper {
68
+ position: relative;
69
+ display: inline-flex;
70
+ align-items: center;
71
+ margin-right: 0.75rem;
72
+ }
73
+
74
+ .category-tooltip-icon {
75
+ border: none;
76
+ background: transparent;
77
+ padding: 0;
78
+ margin: 0;
79
+ display: inline-flex;
80
+ cursor: pointer;
81
+ }
82
+
83
+ .category-tooltip-icon svg {
84
+ display: block;
85
+ }
86
+
87
+ .category-tooltip-bubble {
88
+ position: absolute;
89
+ bottom: calc(100% + 6px);
90
+ left: 50%;
91
+ transform: translateX(-50%);
92
+ min-width: 220px;
93
+ max-width: 260px;
94
+ white-space: normal;
95
+ background: #222;
96
+ color: #fff;
97
+ padding: 0.4rem 0.6rem;
98
+ border-radius: 4px;
99
+ font-size: 0.75rem;
100
+ line-height: 1.3;
101
+ box-shadow: 0 2px 6px rgba(0,0,0,0.16);
102
+ opacity: 0;
103
+ visibility: hidden;
104
+ pointer-events: none;
105
+ transition: opacity 0.15s ease;
106
+ z-index: 20;
107
+ font-size: 0.875rem;
108
+ }
109
+
110
+ /* Little arrow */
111
+ .category-tooltip-bubble::after {
112
+ content: "";
113
+ position: absolute;
114
+ top: 100%;
115
+ left: 50%;
116
+ transform: translateX(-50%);
117
+ border-width: 5px;
118
+ border-style: solid;
119
+ border-color: #222 transparent transparent transparent;
120
+ }
121
+
122
+ .category-tooltip-wrapper:hover .category-tooltip-bubble,
123
+ .category-tooltip-wrapper:focus-within .category-tooltip-bubble {
124
+ opacity: 1;
125
+ visibility: visible;
126
+ }
127
+
128
+ /* Filter Bar */
129
+ .filter-bar {
130
+ display: flex;
131
+ gap: 1rem;
132
+ margin-bottom: 1.5rem;
133
+ align-items: center;
134
+ flex-wrap: wrap;
135
+ }
136
+
137
+ .filter-dropdowns {
138
+ display: flex;
139
+ gap: 0.75rem;
140
+ }
141
+
142
+ .filter-dropdown {
143
+ padding: 0.5rem 1rem;
144
+ border: 1px solid var(--chinese-silver);
145
+ border-radius: 5px;
146
+ cursor: pointer;
147
+ min-width: 150px;
148
+ }
149
+
150
+ .filter-dropdown:focus {
151
+ outline: 2px solid var(--ocean-blue);
152
+ outline-offset: 2px;
153
+ }
154
+
155
+ .search-container {
156
+ position: relative;
157
+ min-width: 250px;
158
+ flex: 1;
159
+ }
160
+
161
+ .search-icon {
162
+ position: absolute;
163
+ left: 12px;
164
+ top: 50%;
165
+ transform: translateY(-50%);
166
+ pointer-events: none;
167
+ }
168
+
169
+ .search-input {
170
+ width: 100%;
171
+ padding: 0.5rem 1rem 0.5rem 1rem;
172
+ border: 1px solid var(--chinese-silver);
173
+ border-radius: 5px;
174
+ line-height: 1.5;
175
+ }
176
+
177
+ .search-input:focus {
178
+ outline: 2px solid var(--ocean-blue);
179
+ outline-offset: 2px;
180
+ border-color: var(--ocean-blue);
181
+ }
182
+
183
+ /* Issues Table */
184
+ .issues-table-container {
185
+ position: relative;
186
+ max-height: 67vh;
187
+ overflow-y: auto;
188
+ border-radius: 5px;
189
+ width: 100%;
190
+ max-width: 100%;
191
+ }
192
+
193
+ .issues-table {
194
+ width: 100%;
195
+ border-collapse: separate;
196
+ border-spacing: 0;
197
+ table-layout: fixed;
198
+ }
199
+
200
+ .issues-table thead th {
201
+ padding: 0.75rem 1rem;
202
+ text-align: left;
203
+ font-size: 1rem;
204
+ font-weight: 600;
205
+ background: var(--true-white);
206
+ position: sticky;
207
+ top: 0;
208
+ z-index: 10;
209
+ overflow: hidden;
210
+ text-overflow: ellipsis;
211
+ }
212
+
213
+ .issues-table th.sortable {
214
+ cursor: pointer;
215
+ user-select: none;
216
+ }
217
+
218
+ .issues-table th.sortable:hover {
219
+ background: #f0f0f0;
220
+ }
221
+
222
+ .issues-table th span {
223
+ display: inline-flex;
224
+ align-items: center;
225
+ gap: 0.5rem;
226
+ }
227
+
228
+ .sort-icon {
229
+ display: inline-block;
230
+ vertical-align: middle;
231
+ }
232
+
233
+ .sort-icon.active {
234
+ opacity: 1;
235
+ }
236
+
237
+ .issues-table tbody tr {
238
+ cursor: pointer;
239
+ transition: background 0.2s;
240
+ }
241
+
242
+ .issues-table tbody tr:hover {
243
+ background: #f9f9f9;
244
+ }
245
+
246
+ .issues-table tbody td {
247
+ border-bottom: 1px solid var(--chinese-silver);
248
+ padding: 1rem;
249
+ font-size: 1rem;
250
+ }
251
+
252
+ .issues-table tbody td:last-child {
253
+ text-align: right;
254
+ padding-right: 1rem;
255
+ }
256
+
257
+ .issue-name-cell {
258
+ display: flex;
259
+ flex-direction: column;
260
+ gap: 0.5rem;
261
+ }
262
+
263
+ .issue-description {
264
+ color: #333;
265
+ }
266
+
267
+ .issue-conformance-badges {
268
+ display: flex;
269
+ gap: 0.5rem;
270
+ flex-wrap: wrap;
271
+ margin-top: 0.25rem;
272
+ }
273
+
274
+ .conformance-badge {
275
+ background-color: white;
276
+ border-radius: 5px;
277
+ font-size: 0.75rem;
278
+ white-space: nowrap;
279
+ border: 1px solid var(--a11y-black-100);
280
+ line-height: 1.5;
281
+ font-weight: 700;
282
+ }
283
+
284
+ .occurrence-count {
285
+ font-weight: 600;
286
+ color: var(--light-carmine-pink);
287
+ }
288
+
289
+ .empty-state {
290
+ text-align: center;
291
+ padding: 3rem 1rem;
292
+ color: var(--a11y-black-300);
293
+ }
294
+
295
+ .total-issues-count {
296
+ padding: 0.75rem 1rem;
297
+ background: var(--true-white);
298
+ border-radius: 5px;
299
+ font-size: 1rem;
300
+ position: sticky;
301
+ bottom: 0;
302
+ z-index: 10;
303
+ margin-top: 0;
304
+ }
305
+
306
+ @media (max-width: 768px) {
307
+ .category-badges-container {
308
+ flex-direction: column;
309
+ align-items: flex-start;
310
+ }
311
+
312
+ .category-badge {
313
+ width: 100%;
314
+ height: fit-content;
315
+ }
316
+
317
+ .filter-bar {
318
+ flex-direction: column;
319
+ align-items: stretch;
320
+ }
321
+
322
+ .filter-dropdowns {
323
+ flex-direction: column;
324
+ }
325
+
326
+ .filter-dropdown,
327
+ .search-container {
328
+ width: 100%;
329
+ }
330
+
331
+ .issues-table-container {
332
+ overflow-x: auto;
333
+ }
334
+
335
+ .issues-table {
336
+ font-size: 0.8rem;
337
+ min-width: 100%;
338
+ table-layout: auto;
339
+ }
340
+
341
+ .issues-table thead th {
342
+ padding: 0.5rem 0.25rem;
343
+ font-size: 0.75rem;
344
+ white-space: nowrap;
345
+ }
346
+
347
+ .issues-table thead th span {
348
+ gap: 0.25rem;
349
+ font-size: 0.75rem;
350
+ }
351
+
352
+ .issues-table thead th .sort-icon {
353
+ width: 16px;
354
+ height: 16px;
355
+ }
356
+
357
+ .issues-table tbody td {
358
+ padding: 0.5rem 0.25rem;
359
+ font-size: 0.8rem;
360
+ }
361
+
362
+ .issues-table tbody td:last-child {
363
+ padding-right: 0.5rem;
364
+ }
365
+
366
+ .issues-table thead th:last-child {
367
+ width: 10%;
368
+ min-width: 40px;
369
+ }
370
+
371
+ .category-item {
372
+ border-right: 0;
373
+ }
374
+
375
+ .issue-conformance-badges {
376
+ gap: 0.25rem;
377
+ }
378
+
379
+ .conformance-badge {
380
+ font-size: 0.65rem;
381
+ padding: 2px 4px;
382
+ }
383
+ }
384
+ </style>