Make WCAG criterion badges clickable links to Understanding pages

Each issue card's WCAG criterion (e.g. "1.4.3") is now a link to the
WAI Understanding page at w3.org. Comma-separated multi-criteria and
PDF/UA are handled separately. Links open in a new tab.

- js/utils.js: WCAG_SLUGS map + wcagCriterionLinks() helper
- js/results.js: issue-meta now calls wcagCriterionLinks()
- css/styles.css: .wcag-link style (dotted underline, hover accent)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Vadym Samoilenko 2026-03-13 14:05:55 +00:00
parent 8b70da3584
commit c932e8b7e1
3 changed files with 88 additions and 1 deletions

View file

@ -753,6 +753,19 @@ h1::before {
font-weight: 500;
}
.wcag-link {
color: var(--info);
text-decoration: none;
font-weight: 600;
border-bottom: 1px dotted var(--info);
transition: color 0.15s, border-color 0.15s;
}
.wcag-link:hover {
color: var(--accent);
border-bottom-color: var(--accent);
}
.issue-recommendation {
background: var(--success-bg);
padding: 10px 12px;

View file

@ -143,7 +143,7 @@ function createIssueCard(issue, issueNumber, globalIndex) {
</div>
</div>
<div class="issue-description">${issue.description}</div>
${issue.wcag_criterion ? `<div class="issue-meta"><span>WCAG ${issue.wcag_criterion}</span></div>` : ''}
${issue.wcag_criterion ? `<div class="issue-meta">${wcagCriterionLinks(issue.wcag_criterion)}</div>` : ''}
${issue.recommendation ? `<div class="issue-recommendation"><strong>Tip:</strong> ${issue.recommendation}</div>` : ''}
</div>`;
}

View file

@ -59,6 +59,80 @@ function getSeverityIcon(severity) {
return map[severity] || '\u2022';
}
/* WCAG 2.1 criterion → Understanding page slug */
const WCAG_SLUGS = {
'1.1.1': 'non-text-content',
'1.2.1': 'audio-only-and-video-only-prerecorded',
'1.2.2': 'captions-prerecorded',
'1.2.3': 'audio-description-or-media-alternative-prerecorded',
'1.2.4': 'captions-live',
'1.2.5': 'audio-description-prerecorded',
'1.3.1': 'info-and-relationships',
'1.3.2': 'meaningful-sequence',
'1.3.3': 'sensory-characteristics',
'1.3.4': 'orientation',
'1.3.5': 'identify-input-purpose',
'1.4.1': 'use-of-color',
'1.4.2': 'audio-control',
'1.4.3': 'contrast-minimum',
'1.4.4': 'resize-text',
'1.4.5': 'images-of-text',
'1.4.6': 'contrast-enhanced',
'1.4.10': 'reflow',
'1.4.11': 'non-text-contrast',
'1.4.12': 'text-spacing',
'1.4.13': 'content-on-hover-or-focus',
'2.1.1': 'keyboard',
'2.1.2': 'no-keyboard-trap',
'2.2.1': 'timing-adjustable',
'2.2.2': 'pause-stop-hide',
'2.3.1': 'three-flashes-or-below-threshold',
'2.4.1': 'bypass-blocks',
'2.4.2': 'page-titled',
'2.4.3': 'focus-order',
'2.4.4': 'link-purpose-in-context',
'2.4.5': 'multiple-ways',
'2.4.6': 'headings-and-labels',
'2.4.7': 'focus-visible',
'2.5.3': 'label-in-name',
'3.1.1': 'language-of-page',
'3.1.2': 'language-of-parts',
'3.1.5': 'reading-level',
'3.2.1': 'on-focus',
'3.2.2': 'on-input',
'3.2.3': 'consistent-navigation',
'3.2.4': 'consistent-identification',
'3.3.1': 'error-identification',
'3.3.2': 'labels-or-instructions',
'3.3.3': 'error-suggestion',
'3.3.4': 'error-prevention-legal-financial-data',
'4.1.1': 'parsing',
'4.1.2': 'name-role-value',
'4.1.3': 'status-messages',
};
/**
* Returns an HTML string of clickable WCAG criterion links.
* Handles comma-separated criteria (e.g. "1.3.1, 4.1.2") and "PDF/UA".
*/
function wcagCriterionLinks(criterion) {
if (!criterion) return '';
if (criterion.trim().toUpperCase() === 'PDF/UA') {
return '<a href="https://www.pdfa.org/pdfua/" target="_blank" rel="noopener" class="wcag-link">PDF/UA</a>';
}
return criterion.split(',').map(part => {
const num = part.trim();
const slug = WCAG_SLUGS[num];
if (slug) {
const url = `https://www.w3.org/WAI/WCAG21/Understanding/${slug}`;
return `<a href="${url}" target="_blank" rel="noopener" class="wcag-link">WCAG ${num}</a>`;
}
return `WCAG ${num}`;
}).join(', ');
}
function escapeAttr(str) {
return String(str).replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/"/g, '&quot;');
}