=== TEST RESULT COLLECTION === 1318: if (window.a11yBuiltByScripts) notes.push('The page was loaded with its own scripts running and the finished page was audited, as your visitors see it after it loads.'); 1329: issues.push({type:'error',label:`${f.length} text element${f.length>1?'s':''} below WCAG contrast minimum`,desc:`Text must have a contrast ratio of at least 4.5:1 against its background (3:1 for large text: 24px, or 18.66px bold). Examples: ${ex}${exList.length>5?'; …':''}`,fix:'Darken the text colour or lighten the background (or the reverse on dark backgrounds) until each pair reaches the minimum ratio.'}); 1331: passes.push(`Text contrast meets WCAG AA (${contrast.checked} text element${contrast.checked>1?'s':''} measured)`); 1338: issues.push({type:'error',label:`${cf.length} control${cf.length>1?'s':''} with a low-contrast edge`,desc:`Buttons, form fields and other controls whose edge is shown by a border or fill need that edge at a contrast ratio of at least 3:1 against the colour around it, so people with low vision can see where to click. This scanner applies that rule to every control; WCAG 1.4.11 requires it where the edge is what identifies the control, such as a form field. Examples: ${cex}${cexList.length>5?'; …':''}`,fix:'Darken the border (or the control fill) until it reaches at least 3:1 against the background around the control, in every colour scheme the page offers.'}); 1340: passes.push(`Control edges meet WCAG 3:1 (${contrast.ctlChecked} control${contrast.ctlChecked>1?'s':''} measured)`); 1342: if (contrast.unmeasurable) notes.push(`${contrast.unmeasurable} text element${contrast.unmeasurable>1?'s sit':' sits'} over an image, gradient or unsupported colour, so contrast could not be measured automatically — check ${contrast.unmeasurable>1?'them':'it'} by eye.`); 1343: if (contrast.aaa && contrast.aaa.length) { const aq = contrast.aaa, am = new Map(); aq.forEach(x => { const k = `"${x.text}" ${x.ratio}:1 (AAA needs ${x.need}:1, ${x.fg} on ${x.bg})`; am.set(k, (am.get(k) || 0) + 1); }); const al = [...am].map(([k, c]) => k + (c > 1 ? ` (${c} elements)` : '')); notes.push(`Optional, Level AAA (not scored): ${aq.length} text element${aq.length > 1 ? 's pass' : ' passes'} the AA contrast minimum but ${aq.length > 1 ? 'are' : 'is'} below the AAA enhanced level (WCAG 1.4.6: 7:1, or 4.5:1 for large text). AAA is not required by UK or EU accessibility rules, which ask for AA. Examples: ${al.slice(0, 5).join('; ')}${al.length > 5 ? '; …' : ''}`); } 1344: if (contrast.sameColour && contrast.sameColour.length) { const sc = contrast.sameColour, ex = [...new Set(sc)].slice(0, 3).map(t => `"${t}"`).join(', '); notes.push(`${sc.length} text element${sc.length > 1 ? 's are' : ' is'} drawn in exactly the same colour as ${sc.length > 1 ? 'their' : 'its'} background, so no one can see ${sc.length > 1 ? 'them' : 'it'} as drawn (e.g. ${ex}). WCAG 1.4.3 sets no contrast requirement for text that is not visible to anyone, so ${sc.length > 1 ? 'these are' : 'this is'} not counted as a failure; if ${sc.length > 1 ? 'they are' : 'it is'} meant to be read (for example coloured by the site's own scripts, which this check does not run), check by eye.`); } 1345: notes.push(window.a11yBuiltByScripts ? 'Contrast is measured on the finished page in its default colour scheme; colours changed by a dark-mode toggle or later clicks are not included.' : 'Contrast is measured in the page\'s default colour scheme with scripts turned off; colours changed by JavaScript or a dark-mode toggle are not included.'); 1347: notes.push(`Colour contrast not checked: ${contrast.reason}.`); 1366: if (noAlt.length) issues.push({type:'error',label:`${noAlt.length} image${noAlt.length>1?'s':''} missing alt attribute`,desc:'Screen readers cannot describe images without alt attributes. Add meaningful alt text to every img tag.',fix:'Add alt="descriptive text" to each image. Use alt="" only for purely decorative images.'}); 1367: else if (imgs.length) passes.push(imgs.length === 1 ? `The 1 image has an alt attribute` : `All ${imgs.length} images have alt attributes`); 1368: if (filenameAlt.length) issues.push({type:'warning',label:`${filenameAlt.length} image${filenameAlt.length>1?'s':''} using filename as alt text`,desc:'Alt text like "hero-image.jpg" is not descriptive. Screen readers read it as a filename.',fix:'Replace filename alt text with a meaningful description of what the image shows.'}); 1369: if (emptyAlt.length) passes.push(`${emptyAlt.length} decorative image${emptyAlt.length>1?'s':''} correctly ${emptyAlt.length>1?'use':'uses'} empty alt`); 1433: if (emptyLinks.length) issues.push({type:'error',label:`${emptyLinks.length} empty link${emptyLinks.length>1?'s':''}`,desc:'Links with no text or aria-label are invisible to screen readers and keyboard users.',fix:'Add descriptive text between a tags or add aria-label="description" to the link.'}); 1434: if (genericLinks.length) issues.push({type:'warning',label:`${genericLinks.length} non-descriptive link${genericLinks.length>1?'s':''} ("click here", "read more" etc)`,desc:'Links like "click here" or "read more" are meaningless out of context for screen reader users.',fix:'Replace with descriptive text: "Read our SEO guide" instead of "Read more".'}); 1435: if (noHrefLinks.length) issues.push({type:'warning',label:`${noHrefLinks.length} clickable a tag${noHrefLinks.length>1?'s':''} without href`,desc:'These a tags have a click handler but no href, so keyboard users cannot Tab to them or activate them.',fix:'Use a button element, or add href plus role="button" and Enter/Space key handling.'}); 1436: if (!emptyLinks.length && !genericLinks.length && links.length) passes.push(links.length === 1 ? `The 1 link has descriptive text` : `All ${links.length} links have descriptive text`); 1449: if (totalUnlabelled > 0) issues.push({type:'error',label:`${totalUnlabelled} form field${totalUnlabelled>1?'s':''} missing ${totalUnlabelled>1?'labels':'a label'}`,desc:'Form inputs, textareas and selects must have associated label tags or aria-label attributes. Placeholder text is not a substitute.',fix:'Add or aria-label="Label text" to each unlabelled form field.'}); 1450: if (placeholderOnly.length) issues.push({type:'warning',label:`${placeholderOnly.length} form field${placeholderOnly.length>1?'s':''} named only by placeholder text`,desc:'These fields have no label: their only name is the placeholder (' + placeholderOnly.slice(0, 3).map(el => '"' + a11yCut(el.getAttribute('placeholder').trim(), 40) + '"').join(', ') + '). Screen readers read it as the name, so this is not a failure on its own, but the placeholder disappears as soon as someone types, so visitors can lose track of what the field is for (WCAG 3.3.2 Labels or Instructions).',fix:'Add a visible