/* Utility functions — logging, progress, theme */ function addLog(message, type = 'info') { const logContent = document.getElementById('logContent'); const entry = document.createElement('div'); entry.className = `log-entry ${type}`; entry.setAttribute('role', type === 'error' ? 'alert' : 'status'); const timestamp = new Date().toLocaleTimeString(); entry.innerHTML = `${timestamp} ${message}`; logContent.appendChild(entry); logContent.scrollTop = logContent.scrollHeight; } function clearLog() { const logContent = document.getElementById('logContent'); logContent.innerHTML = '
Initializing...
'; } function updateProgress(percent, message) { const fill = document.getElementById('progressFill'); const pct = document.getElementById('progressPercent'); const txt = document.getElementById('progressText'); fill.style.width = percent + '%'; const progressBar = document.getElementById('progressContainer'); if (progressBar) progressBar.setAttribute('aria-valuenow', percent); pct.textContent = percent + '%'; txt.textContent = message; } /* Dark mode toggle */ function toggleDarkMode() { const root = document.documentElement; const isDark = root.getAttribute('data-theme') === 'dark'; root.setAttribute('data-theme', isDark ? 'light' : 'dark'); localStorage.setItem('theme', isDark ? 'light' : 'dark'); const btn = document.getElementById('themeToggle'); if (btn) btn.textContent = isDark ? 'Dark' : 'Light'; } function loadTheme() { const saved = localStorage.getItem('theme'); if (saved === 'dark') { document.documentElement.setAttribute('data-theme', 'dark'); const btn = document.getElementById('themeToggle'); if (btn) btn.textContent = 'Light'; } } /* Severity helpers */ function getSeverityColor(severity) { const map = { CRITICAL: '#dc2626', ERROR: '#ef4444', WARNING: '#f59e0b', INFO: '#3b82f6', SUCCESS: '#10b981' }; return map[severity] || '#3b82f6'; } function getSeverityIcon(severity) { const map = { CRITICAL: '\u{1F6A8}', ERROR: '\u274C', WARNING: '\u26A0\uFE0F', INFO: '\u2139\uFE0F', SUCCESS: '\u2705' }; 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 'PDF/UA'; } 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 `WCAG ${num}`; } return `WCAG ${num}`; }).join(', '); } function escapeAttr(str) { return String(str).replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/"/g, '"'); } function getCategoryIcon(category) { const icons = { 'Document Structure': '\u{1F3D7}\uFE0F', 'Metadata': '\u{1F4CB}', 'Language': '\u{1F310}', 'Text Accessibility': '\u{1F4DD}', 'Images': '\u{1F5BC}\uFE0F', 'Color Contrast': '\u{1F3A8}', 'Readability': '\u{1F4DA}', 'Link Text': '\u{1F517}', 'Forms': '\u{1F4C4}', 'Tables': '\u{1F4CA}', 'Headings': '\u{1F4D1}', 'Navigation': '\u{1F9ED}', 'Fonts': '\u{1F524}', 'Security': '\u{1F512}', 'OCR Quality': '\u{1F50D}' }; const key = Object.keys(icons).find(k => category.includes(k)); return key ? icons[key] : '\u{1F4CC}'; }