@govtechsg/oobee 0.10.93 → 0.10.94

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 (61) hide show
  1. package/AGENTS.md +20 -0
  2. package/dist/cli.js +3 -2
  3. package/dist/combine.js +3 -3
  4. package/dist/constants/common.js +119 -52
  5. package/dist/crawlers/commonCrawlerFunc.js +11 -2
  6. package/dist/crawlers/crawlDomain.js +4 -6
  7. package/dist/crawlers/crawlSitemap.js +14 -2
  8. package/dist/crawlers/custom/utils.js +22 -9
  9. package/dist/crawlers/guards/urlGuard.js +19 -1
  10. package/dist/static/ejs/partials/components/allIssues/CategoryBadges.ejs +3 -0
  11. package/dist/static/ejs/partials/components/allIssues/IssuesTable.ejs +3 -3
  12. package/dist/static/ejs/partials/components/header/aboutScanModal/AboutScanModal.ejs +1 -1
  13. package/dist/static/ejs/partials/components/header/aboutScanModal/ScanConfiguration.ejs +3 -3
  14. package/dist/static/ejs/partials/components/header/aboutScanModal/ScanDetails.ejs +34 -27
  15. package/dist/static/ejs/partials/components/ruleModal/ruleOffcanvas.ejs +1 -0
  16. package/dist/static/ejs/partials/components/scannedPagesSegmentedTabs.ejs +7 -0
  17. package/dist/static/ejs/partials/components/wcagCoverageDetails.ejs +5 -5
  18. package/dist/static/ejs/partials/scripts/header/aboutScanModal/AboutScanModal.ejs +3 -3
  19. package/dist/static/ejs/partials/scripts/prioritiseIssues/PrioritiseIssues.ejs +21 -19
  20. package/dist/static/ejs/partials/scripts/ruleModal/pageAccordionBuilder.ejs +39 -8
  21. package/dist/static/ejs/partials/scripts/scannedPagesSegmentedTabs.ejs +11 -5
  22. package/dist/static/ejs/partials/scripts/screenshotLightbox.ejs +49 -31
  23. package/dist/static/ejs/partials/styles/header/SiteInfo.ejs +1 -1
  24. package/dist/static/ejs/partials/styles/header/aboutScanModal/ScanDetails.ejs +36 -16
  25. package/dist/static/ejs/partials/styles/prioritiseIssues/PrioritiseIssues.ejs +22 -1
  26. package/dist/static/ejs/partials/styles/styles.ejs +1 -1
  27. package/dist/static/ejs/partials/styles/wcagCompliance/WcagGaugeBar.ejs +6 -0
  28. package/dist/static/ejs/partials/styles/wcagCompliance.ejs +5 -4
  29. package/dist/static/ejs/partials/styles/wcagCoverageDetails.ejs +6 -1
  30. package/oobee-client-scanner.js +2 -2
  31. package/package.json +1 -1
  32. package/src/cli.ts +3 -2
  33. package/src/combine.ts +3 -2
  34. package/src/constants/common.ts +112 -36
  35. package/src/crawlers/commonCrawlerFunc.ts +11 -2
  36. package/src/crawlers/crawlDomain.ts +4 -5
  37. package/src/crawlers/crawlSitemap.ts +19 -2
  38. package/src/crawlers/custom/utils.ts +26 -13
  39. package/src/crawlers/guards/urlGuard.ts +18 -1
  40. package/src/static/ejs/partials/components/allIssues/CategoryBadges.ejs +3 -0
  41. package/src/static/ejs/partials/components/allIssues/IssuesTable.ejs +3 -3
  42. package/src/static/ejs/partials/components/header/aboutScanModal/AboutScanModal.ejs +1 -1
  43. package/src/static/ejs/partials/components/header/aboutScanModal/ScanConfiguration.ejs +3 -3
  44. package/src/static/ejs/partials/components/header/aboutScanModal/ScanDetails.ejs +34 -27
  45. package/src/static/ejs/partials/components/ruleModal/ruleOffcanvas.ejs +1 -0
  46. package/src/static/ejs/partials/components/scannedPagesSegmentedTabs.ejs +7 -0
  47. package/src/static/ejs/partials/components/wcagCoverageDetails.ejs +5 -5
  48. package/src/static/ejs/partials/scripts/header/aboutScanModal/AboutScanModal.ejs +3 -3
  49. package/src/static/ejs/partials/scripts/prioritiseIssues/PrioritiseIssues.ejs +21 -19
  50. package/src/static/ejs/partials/scripts/ruleModal/pageAccordionBuilder.ejs +39 -8
  51. package/src/static/ejs/partials/scripts/scannedPagesSegmentedTabs.ejs +11 -5
  52. package/src/static/ejs/partials/scripts/screenshotLightbox.ejs +49 -31
  53. package/src/static/ejs/partials/styles/header/SiteInfo.ejs +1 -1
  54. package/src/static/ejs/partials/styles/header/aboutScanModal/ScanDetails.ejs +36 -16
  55. package/src/static/ejs/partials/styles/prioritiseIssues/PrioritiseIssues.ejs +22 -1
  56. package/src/static/ejs/partials/styles/styles.ejs +1 -1
  57. package/src/static/ejs/partials/styles/wcagCompliance/WcagGaugeBar.ejs +6 -0
  58. package/src/static/ejs/partials/styles/wcagCompliance.ejs +5 -4
  59. package/src/static/ejs/partials/styles/wcagCoverageDetails.ejs +6 -1
  60. package/testStaticJSScanner.html +1 -1
  61. /package/{7339fae5-e8ed-4b50-af13-317847620dbf.txt → 67e8137b-1939-4253-8f11-a82bc833cfcb.txt} +0 -0
@@ -5,17 +5,17 @@
5
5
  </h2>
6
6
  <%- include('../../scannedPagesSegmentedTabs') %>
7
7
  <div class="seg-panels">
8
- <div id="pages-scanned" role="tabpanel">
8
+ <div id="pages-scanned" role="tabpanel" aria-labelledby="seg-scanned">
9
9
  <ul id="pagesScannedList" class="unbulleted-list">
10
10
  <!-- dynamically populated -->
11
11
  </ul>
12
12
  </div>
13
- <div id="pages-not-scanned" role="tabpanel" hidden>
13
+ <div id="pages-not-scanned" role="tabpanel" aria-labelledby="seg-not-scanned" hidden>
14
14
  <ul id="pagesNotScannedList" class="unbulleted-list">
15
15
  <!-- dynamically populated -->
16
16
  </ul>
17
17
  </div>
18
- <div id="pages-unsupported" role="tabpanel" hidden>
18
+ <div id="pages-unsupported" role="tabpanel" aria-labelledby="seg-unsupported" hidden>
19
19
  <ul id="unsupportedDocsList" class="unbulleted-list">
20
20
  <!-- dynamically populated -->
21
21
  </ul>
@@ -1,5 +1,5 @@
1
1
  <aside id="scan-about" class="about-scan-details-left">
2
- <h1>About this scan</h1>
2
+ <h1 id="aboutScanModalLabel">About this scan</h1>
3
3
  <ul>
4
4
  <li>
5
5
  <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -33,7 +33,9 @@
33
33
  </span>
34
34
  </li>
35
35
 
36
- <hr class="about-scan-divider" />
36
+ <li class="about-scan-divider-item" aria-hidden="true">
37
+ <hr class="about-scan-divider" />
38
+ </li>
37
39
 
38
40
  <% if (viewport !== null) { %>
39
41
  <li>
@@ -64,7 +66,7 @@
64
66
  type="button"
65
67
  class="js-view-btn about-scan-toggle"
66
68
  aria-controls="view-crawl"
67
- aria-selected="true"
69
+ aria-expanded="true"
68
70
  >
69
71
  <div class="d-flex">
70
72
  <div class="about-scan-link-row">
@@ -82,30 +84,33 @@
82
84
  <path d="M13.2346 8.78333C13.2668 8.53333 13.2828 8.275 13.2828 8C13.2828 7.73333 13.2668 7.46667 13.2266 7.21667L14.857 5.9C15.0016 5.78333 15.0418 5.55833 14.9534 5.39167L13.4113 2.625C13.315 2.44167 13.1142 2.38333 12.9375 2.44167L11.0179 3.24167C10.6163 2.925 10.1906 2.65833 9.71675 2.45833L9.42761 0.341667C9.39548 0.141667 9.23485 0 9.04209 0H5.95791C5.76515 0 5.61255 0.141667 5.58042 0.341667L5.29128 2.45833C4.81741 2.65833 4.3837 2.93333 3.99015 3.24167L2.07057 2.44167C1.89387 2.375 1.69308 2.44167 1.5967 2.625L0.0626475 5.39167C-0.0337329 5.56667 -0.00160615 5.78333 0.159028 5.9L1.78946 7.21667C1.7493 7.46667 1.71718 7.74167 1.71718 8C1.71718 8.25833 1.73324 8.53333 1.7734 8.78333L0.142964 10.1C-0.00160614 10.2167 -0.0417645 10.4417 0.0465841 10.6083L1.58867 13.375C1.68505 13.5583 1.88584 13.6167 2.06254 13.5583L3.98212 12.7583C4.3837 13.075 4.80938 13.3417 5.28325 13.5417L5.57239 15.6583C5.61255 15.8583 5.76515 16 5.95791 16H9.04209C9.23485 16 9.39548 15.8583 9.41958 15.6583L9.70872 13.5417C10.1826 13.3417 10.6163 13.075 11.0099 12.7583L12.9294 13.5583C13.1061 13.625 13.3069 13.5583 13.4033 13.375L14.9454 10.6083C15.0418 10.425 15.0016 10.2167 14.849 10.1L13.2346 8.78333ZM7.5 11C5.90972 11 4.60859 9.65 4.60859 8C4.60859 6.35 5.90972 5 7.5 5C9.09028 5 10.3914 6.35 10.3914 8C10.3914 9.65 9.09028 11 7.5 11Z" fill="#686868"/>
83
85
  </svg>
84
86
 
85
- <div>
86
- Advanced scan options enabled
87
+ <div class="advanced-group-content">
88
+ <div>
89
+ Advanced scan options enabled
90
+ </div>
91
+
92
+ <ul class="advanced-sublist">
93
+ <% if (advancedScanOptionsSummaryItems.showIncludeScreenshots) { %>
94
+ <li class="advanced-sublist-li">Include screenshots</li>
95
+ <% } %>
96
+ <% if (advancedScanOptionsSummaryItems.showAllowSubdomains) { %>
97
+ <li class="advanced-sublist-li">Allow subdomains for scans</li>
98
+ <% } %>
99
+ <% if (advancedScanOptionsSummaryItems.showEnableCustomChecks) { %>
100
+ <li class="advanced-sublist-li">Enable custom checks</li>
101
+ <% } %>
102
+ <% if (advancedScanOptionsSummaryItems.showEnableWcagAaa) { %>
103
+ <li class="advanced-sublist-li">Enable WCAG AAA checks</li>
104
+ <% } %>
105
+ <% if (advancedScanOptionsSummaryItems.showSlowScanMode) { %>
106
+ <li class="advanced-sublist-li">Slow scan mode</li>
107
+ <% } %>
108
+ <% if (advancedScanOptionsSummaryItems.showAdhereRobots) { %>
109
+ <li class="advanced-sublist-li">Adhere to robots.txt</li>
110
+ <% } %>
111
+ </ul>
87
112
  </div>
88
113
  </li>
89
- <ul class="advanced-sublist">
90
- <% if (advancedScanOptionsSummaryItems.showIncludeScreenshots) { %>
91
- <li class="advanced-sublist-li">Include screenshots</li>
92
- <% } %>
93
- <% if (advancedScanOptionsSummaryItems.showAllowSubdomains) { %>
94
- <li class="advanced-sublist-li">Allow subdomains for scans</li>
95
- <% } %>
96
- <% if (advancedScanOptionsSummaryItems.showEnableCustomChecks) { %>
97
- <li class="advanced-sublist-li">Enable custom checks</li>
98
- <% } %>
99
- <% if (advancedScanOptionsSummaryItems.showEnableWcagAaa) { %>
100
- <li class="advanced-sublist-li">Enable WCAG AAA checks</li>
101
- <% } %>
102
- <% if (advancedScanOptionsSummaryItems.showSlowScanMode) { %>
103
- <li class="advanced-sublist-li">Slow scan mode</li>
104
- <% } %>
105
- <% if (advancedScanOptionsSummaryItems.showAdhereRobots) { %>
106
- <li class="advanced-sublist-li">Adhere to robots.txt</li>
107
- <% } %>
108
- </ul>
109
114
 
110
115
  <li>
111
116
  <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -115,7 +120,7 @@
115
120
  type="button"
116
121
  class="js-view-btn about-scan-toggle"
117
122
  aria-controls="view-wcag"
118
- aria-selected="false"
123
+ aria-expanded="false"
119
124
  >
120
125
  <span class="wcag-criteria-label">
121
126
  WCAG Automated Testing
@@ -131,7 +136,9 @@
131
136
  </button>
132
137
  </li>
133
138
 
134
- <hr class="about-scan-divider" />
139
+ <li class="about-scan-divider-item" aria-hidden="true">
140
+ <hr class="about-scan-divider" />
141
+ </li>
135
142
 
136
143
  <li>
137
144
  <span id="oobeeAppVersion" class="oobee-version-text">N/A</span>
@@ -129,6 +129,7 @@
129
129
  <div
130
130
  id="pagesAccordionContent"
131
131
  class="accordion-collapse collapse"
132
+ role="region"
132
133
  aria-labelledby="pagesAccordionHeader"
133
134
  data-bs-parent="#pagesAccordion"
134
135
  >
@@ -1,9 +1,12 @@
1
1
  <div class="segmented-tabs" role="tablist" aria-label="Crawl views">
2
2
  <button
3
3
  type="button"
4
+ id="seg-scanned"
4
5
  class="seg-pill"
6
+ role="tab"
5
7
  aria-controls="pages-scanned"
6
8
  aria-selected="true"
9
+ tabindex="0"
7
10
  data-tab-target="#pages-scanned"
8
11
  >
9
12
  <span id="totalPagesScannedLabel">
@@ -18,8 +21,10 @@
18
21
  type="button"
19
22
  id="seg-not-scanned"
20
23
  class="seg-pill"
24
+ role="tab"
21
25
  aria-controls="pages-not-scanned"
22
26
  aria-selected="false"
27
+ tabindex="-1"
23
28
  data-tab-target="#pages-not-scanned"
24
29
  >
25
30
  <span id="totalPagesNotScannedLabel">
@@ -34,8 +39,10 @@
34
39
  type="button"
35
40
  id="seg-unsupported"
36
41
  class="seg-pill"
42
+ role="tab"
37
43
  aria-controls="pages-unsupported"
38
44
  aria-selected="false"
45
+ tabindex="-1"
39
46
  data-tab-target="#pages-unsupported"
40
47
  >
41
48
  <span id="totalUnsupportedDocsLabel">
@@ -1,15 +1,15 @@
1
1
  <div id="wcagCoverage" class="my-3">
2
- <h5 class="fw-semibold mb-2">
3
- <span id="wcagAALabelCount">20</span> (A &amp; AA) WCAG Success Criteria
4
- </h5>
2
+ <h3 class="wcag-criteria-heading fw-semibold mb-2">
3
+ <span id="wcagAALabelCount">20</span> (A & AA) WCAG Success Criteria
4
+ </h3>
5
5
  <div class="wcag-box">
6
6
  <ul id="wcagLinksListAA" class="wcag-grid list-unstyled m-0">
7
7
  <!-- dynamically populated -->
8
8
  </ul>
9
9
  </div>
10
- <h5 class="fw-semibold mt-4 mb-2">
10
+ <h3 class="wcag-criteria-heading fw-semibold mt-4 mb-2">
11
11
  <span id="wcagAAALabelCount">6</span> (AAA) WCAG Success Criteria
12
- </h5>
12
+ </h3>
13
13
  <div class="wcag-box">
14
14
  <ul id="wcagLinksListAAA" class="wcag-grid list-unstyled m-0">
15
15
  <!-- dynamically populated -->
@@ -6,8 +6,8 @@
6
6
  function showPane(targetId) {
7
7
  // hide all panes
8
8
  panes.forEach(p => p.hidden = true);
9
- // deselect all buttons
10
- btns.forEach(b => b.setAttribute('aria-selected', 'false'));
9
+ // collapse all buttons
10
+ btns.forEach(b => b.setAttribute('aria-expanded', 'false'));
11
11
 
12
12
  // show the requested pane
13
13
  const pane = document.getElementById(targetId);
@@ -16,7 +16,7 @@
16
16
 
17
17
  // mark the controlling button active
18
18
  const btn = btns.find(b => b.getAttribute('aria-controls') === targetId);
19
- if (btn) btn.setAttribute('aria-selected', 'true');
19
+ if (btn) btn.setAttribute('aria-expanded', 'true');
20
20
 
21
21
  // move focus to the pane heading for screen readers/keyboard users
22
22
  const h = pane.querySelector('h4, h3, h2, [role="heading"]');
@@ -84,11 +84,13 @@
84
84
  : '';
85
85
 
86
86
  return `
87
- <li class="priority-issue-item d-flex g-one" data-rule-id="${issue.ruleId}" role="button" tabindex="0">
88
- <div class="d-flex justify-content-between align-items-center w-90">
89
- <div class="priority-issue-title">${issue.description}</div>
90
- ${disabilityBadges}
91
- </div>
87
+ <li class="priority-issue-item" data-rule-id="${issue.ruleId}">
88
+ <button type="button" class="priority-issue-action d-flex g-one" aria-pressed="false">
89
+ <div class="d-flex justify-content-between align-items-center w-90">
90
+ <div class="priority-issue-title">${issue.description}</div>
91
+ ${disabilityBadges}
92
+ </div>
93
+ </button>
92
94
  </li>
93
95
  `;
94
96
  })
@@ -122,7 +124,7 @@
122
124
  data-bs-parent="#prioritiseIssuesAccordion"
123
125
  >
124
126
  <div class="accordion-body">
125
- <ul class="priority-issues-list">
127
+ <ul class="priority-issues-list" aria-label="Priority issues">
126
128
  ${issuesListHTML}
127
129
  </ul>
128
130
  </div>
@@ -135,19 +137,25 @@
135
137
  accordionContainer.innerHTML = accordionHTML;
136
138
 
137
139
  document.querySelectorAll('.priority-issue-item').forEach(item => {
140
+ const actionButton = item.querySelector('.priority-issue-action');
141
+ if (!actionButton) return;
142
+
138
143
  const selectIssue = function () {
139
144
  const ruleId = item.getAttribute('data-rule-id');
140
145
 
141
146
  document.querySelectorAll('.priority-issue-item').forEach(el => {
142
147
  el.classList.remove('active');
143
- el.setAttribute('aria-selected', 'false');
148
+ const button = el.querySelector('.priority-issue-action');
149
+ if (button) {
150
+ button.setAttribute('aria-pressed', 'false');
151
+ }
144
152
  });
145
153
  document.querySelectorAll('.priority-issue-title').forEach(el => {
146
154
  el.classList.remove('active');
147
155
  });
148
156
 
149
157
  item.classList.add('active');
150
- item.setAttribute('aria-selected', 'true');
158
+ actionButton.setAttribute('aria-pressed', 'true');
151
159
  const title = item.querySelector('.priority-issue-title');
152
160
  if (title) {
153
161
  title.classList.add('active');
@@ -167,16 +175,7 @@
167
175
  }
168
176
  };
169
177
 
170
- // Click handler
171
- item.addEventListener('click', selectIssue);
172
-
173
- // Keyboard handler
174
- item.addEventListener('keydown', function (event) {
175
- if (event.key === 'Enter' || event.key === ' ') {
176
- event.preventDefault();
177
- selectIssue();
178
- }
179
- });
178
+ actionButton.addEventListener('click', selectIssue);
180
179
  });
181
180
 
182
181
  // Automatically open first accordion and select first issue
@@ -197,7 +196,10 @@
197
196
  const firstIssueItem = document.querySelector('.priority-issue-item');
198
197
  if (firstIssueItem) {
199
198
  firstIssueItem.classList.add('active');
200
- firstIssueItem.setAttribute('aria-selected', 'true');
199
+ const firstIssueButton = firstIssueItem.querySelector('.priority-issue-action');
200
+ if (firstIssueButton) {
201
+ firstIssueButton.setAttribute('aria-pressed', 'true');
202
+ }
201
203
  const firstIssueTitle = firstIssueItem.querySelector('.priority-issue-title');
202
204
  if (firstIssueTitle) {
203
205
  firstIssueTitle.classList.add('active');
@@ -451,7 +451,7 @@
451
451
  <div id="${accordionId}-content" class="accordion-collapse collapse" aria-labelledby="${accordionId}-title">
452
452
  <div class="accordion-body p-3">
453
453
  ${isCustomFlow
454
- ? createCustomFlowContent(page)
454
+ ? createCustomFlowContent(page, ruleInCategory)
455
455
  :
456
456
  `<div class="d-flex align-items-center gap-1 accordion-link" style=" word-wrap: break-word; word-break: break-word;">
457
457
  <a href="${page.url}" target="_blank">${page.url}
@@ -486,16 +486,46 @@
486
486
  return accordion;
487
487
  }
488
488
 
489
- function createCustomFlowContent(page) {
489
+ function getCustomFlowPageScreenshot(page, ruleInCategory) {
490
+ if (page && page.pageImagePath) return page.pageImagePath;
491
+
492
+ // Use same source-of-truth as scanAboutModal.
493
+ const scannedPages =
494
+ typeof scanData !== 'undefined' &&
495
+ scanData &&
496
+ Array.isArray(scanData.pagesScanned)
497
+ ? scanData.pagesScanned
498
+ : [];
499
+
500
+ if (page) {
501
+ const matchByUrl = scannedPages.find((p) => p && p.url === page.url && p.pageImagePath);
502
+ if (matchByUrl) return matchByUrl.pageImagePath;
503
+
504
+ const matchByTitle = scannedPages.find(
505
+ (p) => p && p.pageTitle === page.pageTitle && p.pageImagePath,
506
+ );
507
+ if (matchByTitle) return matchByTitle.pageImagePath;
508
+ }
509
+
510
+ return '';
511
+ }
512
+
513
+ function createCustomFlowContent(page, ruleInCategory) {
514
+ const pageScreenshot = getCustomFlowPageScreenshot(page, ruleInCategory);
515
+
490
516
  return `
491
517
  <div class="custom-flow-screenshot-container">
492
518
  <div class="custom-flow-thumb">
493
- <img
494
- src="${page.pageImagePath}"
495
- alt="Screenshot of ${page.url}"
496
- class="custom-flow-screenshot"
497
- onerror="this.onerror = null; this.remove();"
498
- >
519
+ ${
520
+ pageScreenshot
521
+ ? `<img
522
+ src="${pageScreenshot}"
523
+ alt="Screenshot of ${page.url}"
524
+ class="custom-flow-screenshot"
525
+ onerror="this.onerror = null; this.remove();"
526
+ >`
527
+ : ''
528
+ }
499
529
  </div>
500
530
  <div class="display-url-container">
501
531
  <div><a href="${page.url}" target="_blank">${page.url}</a></div>
@@ -538,6 +568,7 @@
538
568
 
539
569
  function setupCustomFlowScreenshot(accordion, page) {
540
570
  const customScreenshotElem = accordion.getElementsByClassName('custom-flow-screenshot')[0];
571
+ if (!customScreenshotElem) return;
541
572
 
542
573
  customScreenshotElem.onerror = function(event) {
543
574
  this.onerror = null;
@@ -1,21 +1,27 @@
1
1
  <script>
2
2
  document.addEventListener('click', function (e) {
3
- const btn = e.target.closest('.seg-pill');
3
+ const btn = e.target.closest('.seg-pill[role="tab"]');
4
4
  if (!btn) return;
5
5
 
6
- const container = btn.closest('.segmented-tabs');
6
+ const container = btn.closest('.segmented-tabs[role="tablist"]');
7
+ if (!container) return;
8
+
7
9
  const targetSel = btn.getAttribute('data-tab-target');
8
10
  const panel = document.querySelector(targetSel);
9
11
  if (!panel) return;
10
12
 
11
- container.querySelectorAll('.seg-pill').forEach((p) =>
12
- p.setAttribute('aria-selected', 'false')
13
- );
13
+ container.querySelectorAll('.seg-pill[role="tab"]').forEach((p) => {
14
+ p.setAttribute('aria-selected', 'false');
15
+ p.setAttribute('tabindex', '-1');
16
+ });
17
+
14
18
  btn.setAttribute('aria-selected', 'true');
19
+ btn.setAttribute('tabindex', '0');
15
20
 
16
21
  document
17
22
  .querySelectorAll('.seg-panels > [role="tabpanel"]')
18
23
  .forEach((p) => (p.hidden = true));
24
+
19
25
  panel.hidden = false;
20
26
  });
21
27
 
@@ -6,18 +6,32 @@
6
6
  const lightboxContent = document.getElementsByClassName('lightbox-content')[0];
7
7
  const lightboxImg = document.getElementById('lightbox-image');
8
8
 
9
- var customFlowScreenshots = document.getElementsByClassName('custom-flow-screenshot');
10
- Array.from(customFlowScreenshots).forEach(screenshot => {
11
- screenshot.onerror = function (event) {
12
- screenshot.onerror = null;
13
- screenshot.remove();
14
- };
15
- screenshot.onclick = event => {
16
- event.preventDefault();
17
- const pageTitle = screenshot.parentNode.getElementsByTagName('a')[0].textContent;
18
- const pageUrl = screenshot.parentNode.getElementsByTagName('a')[0].href;
19
- openLightbox(screenshot.src, pageTitle, pageUrl);
20
- };
9
+ // Handle dynamically inserted custom-flow screenshots (about scan modal content is rendered later)
10
+ document.addEventListener(
11
+ 'error',
12
+ event => {
13
+ const target = event.target;
14
+ if (target && target.classList && target.classList.contains('custom-flow-screenshot')) {
15
+ target.onerror = null;
16
+ target.remove();
17
+ }
18
+ },
19
+ true,
20
+ );
21
+
22
+ document.addEventListener('click', event => {
23
+ const screenshot = event.target.closest('.custom-flow-screenshot');
24
+ if (!screenshot) return;
25
+
26
+ event.preventDefault();
27
+
28
+ const container = screenshot.closest('.custom-flow-screenshot-container');
29
+ const link = container ? container.querySelector('.display-url-container a') : null;
30
+
31
+ const pageTitle = link?.textContent?.trim() || screenshot.getAttribute('alt') || 'Screenshot';
32
+ const pageUrl = link?.href || '';
33
+
34
+ openLightbox(screenshot.src, pageTitle, pageUrl);
21
35
  });
22
36
 
23
37
  lightbox.addEventListener('click', event => {
@@ -31,8 +45,9 @@
31
45
  });
32
46
 
33
47
  const offcanvasElem = document.getElementsByClassName('offcanvas')[0];
48
+ let offcanvasItem = null;
34
49
  if (offcanvasElem) {
35
- const offcanvasItem = new bootstrap.Offcanvas(offcanvasElem);
50
+ offcanvasItem = new bootstrap.Offcanvas(offcanvasElem);
36
51
  offcanvasItem._config.keyboard = false; // Disable default keyboard handling
37
52
  }
38
53
 
@@ -43,27 +58,30 @@
43
58
  pagesScannedModalItem._config.keyboard = false;
44
59
  }
45
60
 
46
- document.addEventListener('keydown', event => {
47
- if (event.key === 'Escape') {
48
- if (offcanvasItem._isShown) {
49
- if (lightbox.style.display === 'block') {
50
- event.preventDefault(); // Prevent default bootstrap behaviour
51
- closeLightbox();
52
- } else {
53
- offcanvasItem.hide();
54
- }
61
+ // Use capture phase so lightbox handles Escape before Bootstrap modal/offcanvas handlers.
62
+ document.addEventListener(
63
+ 'keydown',
64
+ event => {
65
+ if (event.key !== 'Escape') return;
66
+
67
+ if (lightbox.style.display === 'block') {
68
+ event.preventDefault();
69
+ event.stopPropagation();
70
+ event.stopImmediatePropagation();
71
+ closeLightbox();
72
+ return;
55
73
  }
56
74
 
57
- if (pagesScannedModalItem._isShown) {
58
- if (lightbox.style.display === 'block') {
59
- event.preventDefault(); // Prevent default bootstrap behaviour
60
- closeLightbox();
61
- } else {
62
- pagesScannedModalItem.hide();
63
- }
75
+ if (offcanvasItem && offcanvasItem._isShown) {
76
+ offcanvasItem.hide();
64
77
  }
65
- }
66
- });
78
+
79
+ if (pagesScannedModalItem && pagesScannedModalItem._isShown) {
80
+ pagesScannedModalItem.hide();
81
+ }
82
+ },
83
+ true,
84
+ );
67
85
 
68
86
  function openLightbox(imgSrc, pageTitle, pageUrl) {
69
87
  lightbox.style.display = 'block';
@@ -112,7 +112,7 @@
112
112
 
113
113
  .link {
114
114
  color: var(--a11y-majorelle-blue);
115
- text-decoration: none;
115
+ text-decoration: underline;
116
116
  }
117
117
 
118
118
  .link:hover {
@@ -3,12 +3,16 @@
3
3
  word-break: break-word;
4
4
  }
5
5
 
6
- #scan-about > ul > li:first-child {
7
- padding-top: 0.5rem;
6
+ #aboutScanModalLabel {
7
+ margin-bottom: 1rem;
8
8
  }
9
9
 
10
- #scan-about ul {
10
+ #scan-about > ul {
11
11
  padding-left: 0;
12
+ padding-top: 0;
13
+ padding-bottom: 1rem;
14
+ margin-top: 0;
15
+ margin-bottom: 0;
12
16
  }
13
17
 
14
18
  #scan-about li {
@@ -38,21 +42,28 @@
38
42
  margin-bottom: 0.3rem;
39
43
  }
40
44
 
41
- .advanced-group {
42
- display: flex;
43
- align-items: center;
44
- margin-bottom: 0 !important;
45
+ #scan-about li.advanced-group {
46
+ align-items: flex-start;
47
+ margin-bottom: 0;
48
+ }
49
+
50
+ #scan-about li.advanced-group > .advanced-group-content {
51
+ min-width: 0;
52
+ flex: 1;
45
53
  }
46
54
 
47
- .advanced-group + .advanced-sublist,
48
- .advanced-group + ul {
49
- list-style: disc !important;
50
- margin-left: 3.125rem;
55
+ #scan-about li.advanced-group > .advanced-group-content > ul.advanced-sublist {
56
+ list-style: disc;
57
+ list-style-position: outside;
58
+ margin-left: 8px;
59
+ margin-top: 0.5rem;
60
+ padding-left: 12px;
61
+ text-indent: -2px;
51
62
  }
52
63
 
53
- .advanced-group + .advanced-sublist li,
54
- .advanced-group + ul li {
64
+ #scan-about li.advanced-group > .advanced-group-content > ul.advanced-sublist li {
55
65
  margin: 0;
66
+ list-style-position: outside !important;
56
67
  }
57
68
 
58
69
  .advanced-sublist-li {
@@ -85,10 +96,14 @@
85
96
 
86
97
  .about-scan-toggle .about-us-type-of-scan-text {
87
98
  color: var(--a11y-majorelle-blue) !important;
88
- display: inline !important;
89
- text-decoration: none;
99
+ display: inline;
100
+ text-decoration: underline;
90
101
  cursor: pointer;
91
102
  }
103
+
104
+ #pagesScannedModalToggleTxt {
105
+ color: var(--a11y-majorelle-blue) !important;
106
+ }
92
107
  .about-scan-toggle:hover .about-us-type-of-scan-text,
93
108
  .about-scan-toggle .about-us-type-of-scan-text:focus-visible {
94
109
  text-decoration: underline;
@@ -112,9 +127,14 @@
112
127
  line-height: 1.2;
113
128
  }
114
129
 
130
+ .about-scan-divider-item {
131
+ display: block !important;
132
+ margin-bottom: 2rem !important;
133
+ }
134
+
115
135
  .about-scan-link-text {
116
136
  color: var(--a11y-majorelle-blue);
117
- text-decoration: none;
137
+ text-decoration: underline;
118
138
  cursor: pointer;
119
139
  }
120
140
 
@@ -120,15 +120,36 @@
120
120
 
121
121
  .priority-issue-item {
122
122
  padding: 0.75rem;
123
- cursor: pointer;
124
123
  position: relative;
125
124
  padding-left: 1.5rem;
125
+ display: flex;
126
+ align-items: flex-start;
127
+ }
128
+
129
+ .priority-issue-action {
130
+ appearance: none;
131
+ border: 0;
132
+ background: transparent;
133
+ width: 100%;
134
+ padding: 0;
135
+ margin: 0;
136
+ display: flex;
137
+ text-align: left;
138
+ cursor: pointer;
139
+ color: inherit;
140
+ line-height: 1.5;
141
+ }
142
+
143
+ .priority-issue-action > .w-90 {
144
+ width: 100% !important;
126
145
  }
127
146
 
128
147
  .priority-issue-item::before {
129
148
  counter-increment: issue-counter;
130
149
  content: counter(issue-counter) '.';
131
150
  line-height: 1.5;
151
+ margin-right: 0.5rem;
152
+ flex-shrink: 0;
132
153
  }
133
154
 
134
155
  .priority-issue-item:hover:not(.active) {
@@ -98,7 +98,7 @@
98
98
 
99
99
  a {
100
100
  color: var(--a11y-majorelle-blue);
101
- text-decoration: none;
101
+ text-decoration: underline;
102
102
  }
103
103
 
104
104
  a:hover {