@accesslint/core 0.8.6 → 0.8.7
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.
- package/dist/index.cjs +2 -2
- package/dist/index.iife.js +1 -1
- package/dist/index.js +8 -8
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const W=["a[href]","button:not([disabled])",'input:not([disabled]):not([type="hidden"])',"select:not([disabled])","textarea:not([disabled])",'[tabindex]:not([tabindex="-1"])',"audio[controls]","video[controls]",'[contenteditable]:not([contenteditable="false"])',"details > summary:first-of-type","iframe","object","embed","area[href]"].join(", ");let Q=new WeakMap;function Fe(){Q=new WeakMap}function le(e){var i;const a=e.tagName.toLowerCase(),t=(i=e.getAttribute("type"))==null?void 0:i.toLowerCase();switch(a){case"a":return e.hasAttribute("href")?"link":null;case"area":return e.hasAttribute("href")?"link":null;case"article":return"article";case"aside":return"complementary";case"button":return"button";case"datalist":return"listbox";case"details":return"group";case"dialog":return"dialog";case"fieldset":return"group";case"figure":return"figure";case"footer":return e.closest("article, aside, main, nav, section")?null:"contentinfo";case"form":return"form";case"h1":case"h2":case"h3":case"h4":case"h5":case"h6":return"heading";case"header":return e.closest("article, aside, main, nav, section")?null:"banner";case"hr":return"separator";case"img":return e.getAttribute("alt")===""?"presentation":"img";case"input":switch(t){case"button":case"image":case"reset":case"submit":return"button";case"checkbox":return"checkbox";case"email":case"tel":case"text":case"url":case null:case void 0:return"textbox";case"number":return"spinbutton";case"radio":return"radio";case"range":return"slider";case"search":return"searchbox";default:return"textbox"}case"li":return e.closest("ul, ol, menu")?"listitem":null;case"main":return"main";case"math":return"math";case"menu":return"list";case"meter":return"meter";case"nav":return"navigation";case"ol":case"ul":return"list";case"optgroup":return"group";case"option":return"option";case"output":return"status";case"progress":return"progressbar";case"section":return e.hasAttribute("aria-label")||e.hasAttribute("aria-labelledby")?"region":null;case"select":return e.hasAttribute("multiple")||e.size>1?"listbox":"combobox";case"summary":return"button";case"table":return"table";case"tbody":case"tfoot":case"thead":return"rowgroup";case"td":return"cell";case"textarea":return"textbox";case"th":return"columnheader";case"tr":return"row";default:return null}}function H(e){var n;const a=Q.get(e);if(a!==void 0)return a;const i=((n=e.getAttribute("role"))==null?void 0:n.trim().toLowerCase())||null||le(e);return Q.set(e,i),i}let Z=new WeakMap;function ut(){Z=new WeakMap}function y(e){const a=Z.get(e);if(a!==void 0)return a;const t=mt(e);return Z.set(e,t),t}function mt(e){var o,r,s,l,d;const a=e.getAttribute("aria-labelledby");if(a){const c=a.split(/\s+/).map(u=>{const b=e.ownerDocument.getElementById(u);return b?k(b).trim():""}).filter(Boolean);if(c.length)return c.join(" ")}const t=(o=e.getAttribute("aria-label"))==null?void 0:o.trim();if(t)return t;if(e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement||e instanceof HTMLSelectElement){if(e.id){const b=e.ownerDocument.querySelector(`label[for="${CSS.escape(e.id)}"]`),g=b?k(b).trim():"";if(g)return g}const c=e.closest("label"),u=c?k(c).trim():"";if(u)return u}const i=(r=e.getAttribute("title"))==null?void 0:r.trim();if(i)return i;if(e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement){const c=(s=e.getAttribute("placeholder"))==null?void 0:s.trim();if(c)return c}const n=e.tagName.toLowerCase();if(n==="fieldset"){const c=e.querySelector(":scope > legend");if(c){const u=k(c).trim();if(u)return u}}if(n==="table"){const c=e.querySelector(":scope > caption");if(c){const u=k(c).trim();if(u)return u}}if(!(e instanceof HTMLInputElement)){const c=k(e).trim();if(c)return c}return e instanceof HTMLImageElement||e instanceof HTMLAreaElement?((l=e.alt)==null?void 0:l.trim())??"":e instanceof HTMLInputElement&&e.type==="image"?((d=e.alt)==null?void 0:d.trim())??"":""}const pt=new Set(["alert","alertdialog","application","article","banner","blockquote","button","caption","cell","checkbox","code","columnheader","combobox","complementary","contentinfo","definition","deletion","dialog","directory","document","emphasis","feed","figure","form","generic","grid","gridcell","group","heading","img","insertion","link","list","listbox","listitem","log","main","marquee","math","menu","menubar","menuitem","menuitemcheckbox","menuitemradio","meter","navigation","none","note","option","paragraph","presentation","progressbar","radio","radiogroup","region","row","rowgroup","rowheader","scrollbar","search","searchbox","separator","slider","spinbutton","status","strong","subscript","superscript","switch","tab","table","tablist","tabpanel","term","textbox","time","timer","toolbar","tooltip","tree","treegrid","treeitem"]);function ze(e){const a=e.trim().toLowerCase().replace(/[\u201C\u201D\u2018\u2019\u00AB\u00BB]/g,"");return pt.has(a)}const bt=new Set(["aria-atomic","aria-braillelabel","aria-brailleroledescription","aria-busy","aria-controls","aria-current","aria-describedby","aria-details","aria-disabled","aria-dropeffect","aria-errormessage","aria-flowto","aria-grabbed","aria-haspopup","aria-hidden","aria-invalid","aria-keyshortcuts","aria-label","aria-labelledby","aria-live","aria-owns","aria-relevant","aria-roledescription"]);function I(e){let a=e;for(;a;){if(We(a))return!0;a=a.parentElement}return!1}let ee=new WeakMap;function je(){ee=new WeakMap}function h(e){const a=ee.get(e);if(a!==void 0)return a;let t;return e.getAttribute("aria-hidden")==="true"||e instanceof HTMLElement&&(e.hidden||e.style.display==="none")?t=!0:e.parentElement?t=h(e.parentElement):t=!1,ee.set(e,t),t}function We(e){if(e.getAttribute("aria-hidden")==="true"||e instanceof HTMLElement&&e.hidden)return!0;if(typeof getComputedStyle=="function"){const a=getComputedStyle(e);if(a.display==="none"||a.visibility==="hidden")return!0}else if(e instanceof HTMLElement&&e.style.display==="none")return!0;return!1}function k(e){var t,i,n,o,r;let a="";for(const s of e.childNodes)if(s.nodeType===3)a+=s.textContent??"";else if(s.nodeType===1){const l=s;if(!We(l)){const d=(t=l.tagName)==null?void 0:t.toLowerCase();if(d==="img"||d==="area"){const c=l.getAttribute("aria-labelledby");if(c){const u=c.split(/\s+/).map(b=>{var g,f;return((f=(g=l.ownerDocument.getElementById(b))==null?void 0:g.textContent)==null?void 0:f.trim())??""}).filter(Boolean);if(u.length){a+=u.join(" ");continue}}a+=((i=l.getAttribute("aria-label"))==null?void 0:i.trim())??l.getAttribute("alt")??((n=l.getAttribute("title"))==null?void 0:n.trim())??""}else if(d==="svg"){const c=(o=l.getAttribute("aria-label"))==null?void 0:o.trim();if(c)a+=c;else{const u=l.querySelector("title");u&&(a+=u.textContent??"")}}else(r=l.getAttribute("aria-label"))!=null&&r.trim()?a+=l.getAttribute("aria-label").trim():a+=k(l)}}return a}function te(e){let a="";for(const t of e.childNodes)if(t.nodeType===3)a+=t.textContent??"";else if(t.nodeType===1){const i=t,n=i.tagName.toLowerCase();if(n==="style"||n==="script"||n==="svg"||i.getAttribute("aria-hidden")==="true"||i instanceof HTMLElement&&i.style.display==="none")continue;const o=i.getAttribute("role");if(o==="img"||o==="presentation"||o==="none")continue;a+=te(i)}return a}function Ue(e){let a=e;for(;a;){if(a instanceof HTMLElement&&a.style.visibility==="hidden")return!0;a=a.parentElement}return!1}function Oe(e){var n,o;const a=e.getAttribute("aria-labelledby");if(a){const r=a.split(/\s+/).map(s=>{var l,d;return((d=(l=e.ownerDocument.getElementById(s))==null?void 0:l.textContent)==null?void 0:d.trim())??""}).filter(Boolean);if(r.length)return r.join(" ")}const t=(n=e.getAttribute("aria-label"))==null?void 0:n.trim();if(t)return t;const i=(o=e.getAttribute("title"))==null?void 0:o.trim();return i||""}function ce(e){return e.getRootNode()instanceof ShadowRoot}let ae=new WeakMap;function ht(){ae=new WeakMap}function gt(e){return e.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}const ft=["data-testid","data-test-id","data-cy","data-id","name","href","for","aria-label"];function vt(e){const a=e.tagName.toLowerCase();for(const i of ft){const n=e.getAttribute(i);if(n!=null&&n.length>0&&n.length<100)return`${a}[${i}="${gt(n)}"]`}const t=e.parentElement;if(t){let i=0,n=0;for(let o=0;o<t.children.length;o++)t.children[o].tagName===e.tagName&&(i++,t.children[o]===e&&(n=i));if(i>1)return`${a}:nth-of-type(${n})`}return a}function X(e){if(e.id)return`#${CSS.escape(e.id)}`;const a=e.getRootNode(),t=a instanceof ShadowRoot?null:a.documentElement;if(e===t)return e.tagName.toLowerCase();const i=[];let n=e;for(;n&&n!==t;){if(n!==e&&n.id){i.unshift(`#${CSS.escape(n.id)}`);break}if(i.unshift(vt(n)),i.length>=2){const o=i.join(" > ");try{const r=a.querySelectorAll(o);if(r.length===1&&r[0]===e)return o}catch{}}n=n.parentElement}return i.join(" > ")}function p(e){var o;const a=ae.get(e);if(a!==void 0)return a;const t=[];let i=e;for(;i;){const r=i.getRootNode();if(r instanceof ShadowRoot)t.unshift({selector:X(i),delimiter:" >>> "}),i=r.host;else{const s=(o=r.defaultView)==null?void 0:o.frameElement;if(s)t.unshift({selector:X(i),delimiter:" >>>iframe> "}),i=s;else{t.unshift({selector:X(i),delimiter:""});break}}}const n=t.map((r,s)=>(s===0?"":r.delimiter)+r.selector).join("");return ae.set(e,n),n}function yt(e){const a=[],t=[];let i=e;for(;i;){const o=i.indexOf(" >>>iframe> "),r=i.indexOf(" >>> ");if(o!==-1&&(r===-1||o<=r))a.push(i.slice(0,o).trim()),t.push("iframe"),i=i.slice(o+12);else if(r!==-1)a.push(i.slice(0,r).trim()),t.push("shadow"),i=i.slice(r+5);else{a.push(i.trim());break}}let n=document;for(let o=0;o<a.length;o++){const r=n.querySelector(a[o]);if(!r)return null;if(o<a.length-1)if(t[o]==="iframe"){const s=r.contentDocument;if(!s)return null;n=s}else{const s=r.shadowRoot;if(!s)return null;n=s}else return r}return null}function m(e){const a=e.outerHTML;return a.length>200?a.slice(0,200)+"...":a}const wt=new Set(["aria-activedescendant","aria-atomic","aria-autocomplete","aria-braillelabel","aria-brailleroledescription","aria-busy","aria-checked","aria-colcount","aria-colindex","aria-colindextext","aria-colspan","aria-controls","aria-current","aria-describedby","aria-description","aria-details","aria-disabled","aria-dropeffect","aria-errormessage","aria-expanded","aria-flowto","aria-grabbed","aria-haspopup","aria-hidden","aria-invalid","aria-keyshortcuts","aria-label","aria-labelledby","aria-level","aria-live","aria-modal","aria-multiline","aria-multiselectable","aria-orientation","aria-owns","aria-placeholder","aria-posinset","aria-pressed","aria-readonly","aria-relevant","aria-required","aria-roledescription","aria-rowcount","aria-rowindex","aria-rowindextext","aria-rowspan","aria-selected","aria-setsize","aria-sort","aria-valuemax","aria-valuemin","aria-valuenow","aria-valuetext"]),ye=new Set(["aria-atomic","aria-busy","aria-disabled","aria-grabbed","aria-hidden","aria-modal","aria-multiline","aria-multiselectable","aria-readonly","aria-required"]),we=new Set(["aria-checked","aria-pressed"]),xt=new Set(["aria-colcount","aria-colindex","aria-colspan","aria-level","aria-posinset","aria-rowcount","aria-rowindex","aria-rowspan","aria-setsize"]),At=new Set(["aria-valuemax","aria-valuemin","aria-valuenow"]),J={"aria-autocomplete":new Set(["inline","list","both","none"]),"aria-expanded":new Set(["true","false","undefined"]),"aria-current":new Set(["page","step","location","date","time","true","false"]),"aria-dropeffect":new Set(["copy","execute","link","move","none","popup"]),"aria-haspopup":new Set(["true","false","menu","listbox","tree","grid","dialog"]),"aria-invalid":new Set(["grammar","false","spelling","true"]),"aria-live":new Set(["assertive","off","polite"]),"aria-orientation":new Set(["horizontal","vertical","undefined"]),"aria-relevant":new Set(["additions","all","removals","text"]),"aria-sort":new Set(["ascending","descending","none","other"])},xe=new Set(["caption","code","deletion","emphasis","generic","insertion","mark","none","paragraph","presentation","strong","subscript","superscript","suggestion","term","time"]),kt={abbr:!0,bdi:!0,bdo:!0,br:!0,cite:!0,code:!0,data:!0,del:!0,dfn:!0,em:!0,ins:!0,kbd:!0,mark:!0,q:!0,rp:!0,rt:!0,ruby:!0,s:!0,samp:!0,small:!0,strong:!0,sub:!0,sup:!0,time:!0,u:!0,var:!0,wbr:!0},St={alert:new Set(["aria-disabled","aria-errormessage","aria-haspopup","aria-invalid"]),article:new Set(["aria-disabled","aria-errormessage","aria-haspopup","aria-invalid"]),banner:new Set(["aria-disabled","aria-errormessage","aria-haspopup","aria-invalid"]),blockquote:new Set(["aria-disabled","aria-errormessage","aria-haspopup","aria-invalid"]),caption:new Set(["aria-disabled","aria-errormessage","aria-haspopup","aria-invalid"]),code:new Set(["aria-disabled","aria-errormessage","aria-haspopup","aria-invalid"]),complementary:new Set(["aria-disabled","aria-errormessage","aria-haspopup","aria-invalid"]),contentinfo:new Set(["aria-disabled","aria-errormessage","aria-haspopup","aria-invalid"]),definition:new Set(["aria-disabled","aria-errormessage","aria-haspopup","aria-invalid"]),deletion:new Set(["aria-disabled","aria-errormessage","aria-haspopup","aria-invalid"]),emphasis:new Set(["aria-disabled","aria-errormessage","aria-haspopup","aria-invalid"]),generic:new Set(["aria-disabled","aria-errormessage","aria-haspopup","aria-invalid","aria-label","aria-labelledby","aria-roledescription"]),img:new Set(["aria-disabled","aria-errormessage","aria-haspopup","aria-invalid"]),insertion:new Set(["aria-disabled","aria-errormessage","aria-haspopup","aria-invalid"]),main:new Set(["aria-disabled","aria-errormessage","aria-haspopup","aria-invalid"]),mark:new Set(["aria-disabled","aria-errormessage","aria-haspopup","aria-invalid"]),math:new Set(["aria-disabled","aria-errormessage","aria-haspopup","aria-invalid"]),navigation:new Set(["aria-disabled","aria-errormessage","aria-haspopup","aria-invalid"]),none:new Set(["aria-label","aria-labelledby"]),note:new Set(["aria-disabled","aria-errormessage","aria-haspopup","aria-invalid"]),paragraph:new Set(["aria-disabled","aria-errormessage","aria-haspopup","aria-invalid"]),presentation:new Set(["aria-label","aria-labelledby"]),region:new Set(["aria-disabled","aria-errormessage","aria-haspopup","aria-invalid"]),search:new Set(["aria-disabled","aria-errormessage","aria-haspopup","aria-invalid"]),status:new Set(["aria-disabled","aria-errormessage","aria-haspopup","aria-invalid"]),strong:new Set(["aria-disabled","aria-errormessage","aria-haspopup","aria-invalid"]),subscript:new Set(["aria-disabled","aria-errormessage","aria-haspopup","aria-invalid"]),superscript:new Set(["aria-disabled","aria-errormessage","aria-haspopup","aria-invalid"]),term:new Set(["aria-disabled","aria-errormessage","aria-haspopup","aria-invalid"]),time:new Set(["aria-disabled","aria-errormessage","aria-haspopup","aria-invalid"]),tooltip:new Set(["aria-disabled","aria-errormessage","aria-haspopup","aria-invalid"])};let F=null,z=null;function Ve(){F=null,z=null}function de(e){var n;if(z&&(F==null?void 0:F.deref())===e)return z;const a=[],t=[],i=[];for(const o of e.querySelectorAll("*")){let r=!1;for(const c of o.attributes)if(c.name.startsWith("aria-")){r=!0;break}if(!r)continue;let s,l;const d=()=>(s===void 0&&(s=p(o),l=m(o)),{selector:s,html:l});for(const c of o.attributes)if(c.name.startsWith("aria-")&&!wt.has(c.name)){const u=d();a.push({ruleId:"aria/aria-valid-attr",selector:u.selector,html:u.html,impact:"critical",message:`Invalid ARIA attribute "${c.name}".`,fix:{type:"remove-attribute",attribute:c.name}});break}for(const c of o.attributes){if(!c.name.startsWith("aria-"))continue;const u=c.value.trim();if(!(u===""&&!ye.has(c.name)&&!we.has(c.name))){if(ye.has(c.name)){if(u!=="true"&&u!=="false"){const b=d();t.push({ruleId:"aria/aria-valid-attr-value",selector:b.selector,html:b.html,impact:"critical",message:`${c.name} must be "true" or "false", got "${u}".`,fix:{type:"set-attribute",attribute:c.name,value:"false"}})}}else if(we.has(c.name)){if(u!=="true"&&u!=="false"&&u!=="mixed"){const b=d();t.push({ruleId:"aria/aria-valid-attr-value",selector:b.selector,html:b.html,impact:"critical",message:`${c.name} must be "true", "false", or "mixed", got "${u}".`,fix:{type:"set-attribute",attribute:c.name,value:"false"}})}}else if(xt.has(c.name)){if(u===""||!/^-?\d+$/.test(u)){const b=d();t.push({ruleId:"aria/aria-valid-attr-value",selector:b.selector,html:b.html,impact:"critical",message:`${c.name} must be an integer, got "${u}".`,fix:{type:"suggest",suggestion:`Set ${c.name} to a valid integer value`}})}}else if(At.has(c.name)){if(u===""||isNaN(Number(u))){const b=d();t.push({ruleId:"aria/aria-valid-attr-value",selector:b.selector,html:b.html,impact:"critical",message:`${c.name} must be a number, got "${u}".`,fix:{type:"suggest",suggestion:`Set ${c.name} to a valid number value`}})}}else if(J[c.name]){const b=u.split(/\s+/);for(const g of b)if(!J[c.name].has(g)){const f=d();t.push({ruleId:"aria/aria-valid-attr-value",selector:f.selector,html:f.html,impact:"critical",message:`Invalid value "${u}" for ${c.name}.`,fix:{type:"suggest",suggestion:`Set ${c.name} to one of: ${[...J[c.name]].join(", ")}`}});break}}}}if(!h(o)){const c=(n=o.getAttribute("role"))==null?void 0:n.trim().toLowerCase(),u=o.tagName.toLowerCase();if(!c&&kt[u]){const b=o.hasAttribute("aria-label"),g=o.hasAttribute("aria-labelledby");if(b||g){const f=d(),v=b?"aria-label":"aria-labelledby";i.push({ruleId:"aria/aria-prohibited-attr",selector:f.selector,html:f.html,impact:"serious",message:`aria-label and aria-labelledby are prohibited on <${u}> elements.`,fix:{type:"remove-attribute",attribute:v}})}}else if(c){if(xe.has(c)){const g=o.hasAttribute("aria-label"),f=o.hasAttribute("aria-labelledby");if(g||f){const v=d(),w=g?"aria-label":"aria-labelledby";i.push({ruleId:"aria/aria-prohibited-attr",selector:v.selector,html:v.html,impact:"serious",message:`aria-label and aria-labelledby are prohibited on role "${c}".`,fix:{type:"remove-attribute",attribute:w}})}}const b=St[c];if(b){for(const g of o.attributes)if(g.name.startsWith("aria-")&&b.has(g.name)){if((g.name==="aria-label"||g.name==="aria-labelledby")&&xe.has(c))continue;const f=d();i.push({ruleId:"aria/aria-prohibited-attr",selector:f.selector,html:f.html,impact:"serious",message:`Attribute "${g.name}" is prohibited on role "${c}".`,fix:{type:"remove-attribute",attribute:g.name}})}}}}}return F=new WeakRef(e),z={validAttr:a,validAttrValue:t,prohibitedAttr:i},z}let ie=new WeakMap,ne=new WeakMap,oe=new WeakMap;function Be(){ie=new WeakMap,ne=new WeakMap,oe=new WeakMap}function x(e){let a=ie.get(e);return a||(a=getComputedStyle(e),ie.set(e,a),a)}function q(e,a,t){const[i,n,o]=[e,a,t].map(r=>{const s=r/255;return s<=.04045?s/12.92:Math.pow((s+.055)/1.055,2.4)});return .2126*i+.7152*n+.0722*o}function $(e,a){const t=Math.max(e,a),i=Math.min(e,a);return(t+.05)/(i+.05)}const Ae={black:[0,0,0],white:[255,255,255],red:[255,0,0],green:[0,128,0],blue:[0,0,255],yellow:[255,255,0],orange:[255,165,0],purple:[128,0,128],gray:[128,128,128],grey:[128,128,128],silver:[192,192,192],maroon:[128,0,0],navy:[0,0,128],teal:[0,128,128],aqua:[0,255,255],fuchsia:[255,0,255],lime:[0,255,0],olive:[128,128,0]};function T(e){const a=e.trim().toLowerCase();if(Ae[a])return Ae[a];const t=a.match(/^#([0-9a-f])([0-9a-f])([0-9a-f])$/);if(t)return[parseInt(t[1]+t[1],16),parseInt(t[2]+t[2],16),parseInt(t[3]+t[3],16)];const i=a.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/);if(i)return[parseInt(i[1],16),parseInt(i[2],16),parseInt(i[3],16)];const n=e.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*[\d.]+)?\s*\)/);if(n)return[parseInt(n[1]),parseInt(n[2]),parseInt(n[3])];const o=e.match(/rgba?\(\s*(\d+)\s+(\d+)\s+(\d+)\s*(?:\/\s*[\d.]+%?)?\s*\)/);return o?[parseInt(o[1]),parseInt(o[2]),parseInt(o[3])]:null}function U(e){const a=e.match(/rgba\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*,\s*([\d.]+)\s*\)/);if(a)return parseFloat(a[1]);const t=e.match(/rgba?\([^)]+\/\s*([\d.]+%?)\s*\)/);if(t){const i=t[1];return i.endsWith("%")?parseFloat(i)/100:parseFloat(i)}return 1}function C(e,a,t){return[Math.round(e[0]*t+a[0]*(1-t)),Math.round(e[1]*t+a[1]*(1-t)),Math.round(e[2]*t+a[2]*(1-t))]}function ke(e){const a=ne.get(e);if(a!==void 0)return a;const t=It(e);return ne.set(e,t),t}function K(e,a){let t=a;for(let i=e.length-1;i>=0;i--)t=C(e[i].color,t,e[i].alpha);return t}function It(e){const a=[];let t=e;for(;t;){const n=x(t),o=n.backgroundImage;if(o&&o!=="none"&&o!=="initial"){const d=n.backgroundColor;if(d&&d!=="transparent"&&d!=="rgba(0, 0, 0, 0)"&&d!=="rgba(0 0 0 / 0)"){const c=T(d);if(c)return a.length>0?K(a,c):c}return null}const r=n.backgroundColor;if(r==="transparent"||r==="rgba(0, 0, 0, 0)"||r==="rgba(0 0 0 / 0)"){t=t.parentElement;continue}const s=U(r);if(s<.01){t=t.parentElement;continue}const l=T(r);if(!l){t=t.parentElement;continue}if(s>=1)return a.length>0?K(a,l):l;a.push({color:l,alpha:s}),t=t.parentElement}const i=[255,255,255];return a.length>0?K(a,i):i}function _e(e){const a=[];let t=0,i=0;for(let n=0;n<e.length;n++)e[n]==="("?t++:e[n]===")"?t--:e[n]===","&&t===0&&(a.push(e.slice(i,n).trim()),i=n+1);return a.push(e.slice(i).trim()),a}function qt(e,a=[255,255,255]){const t=[],i=e.search(/(?:linear|radial|conic)-gradient\(/);if(i===-1)return t;const n=e.indexOf("(",i);if(n===-1)return t;let o=1,r=n+1;for(;r<e.length&&o>0;r++)e[r]==="("?o++:e[r]===")"&&o--;const s=e.slice(n+1,r-1),l=_e(s);for(const d of l){const c=d.trim();if(/^(to\s|[\d.]+deg|[\d.]+turn|[\d.]+rad)/i.test(c))continue;if(c==="transparent"||c.startsWith("transparent ")){t.push(a);continue}const u=c.replace(/\s+[\d.]+(%|em|px|rem|vh|vw).*$/i,"").trim(),b=T(u);b&&t.push(b)}return t}const Et=new Set(["IMG","PICTURE","VIDEO","SVG"]);function Lt(e){const a=oe.get(e);if(a!==void 0)return a;const t=Ct(e);return oe.set(e,t),t}function Rt(e){return Et.has(e.tagName)?!0:!!e.querySelector("img, picture, video, svg")}function Ct(e){let a=e,t=!1;for(;a;){const i=x(a).position;if((i==="absolute"||i==="fixed")&&(t=!0),a!==e&&i!=="static"){for(const n of a.children){if(n===e||n.contains(e))continue;if(Rt(n)){if(t)return!0;const r=x(n).position;if(r==="absolute"||r==="fixed")return!0}const o=x(n);if(o.position==="absolute"||o.position==="fixed"){const r=o.backgroundImage;if(r&&r!=="none"&&r!=="initial")return!0}}if(t)break}a=a.parentElement}return!1}function Tt(e){const a=parseFloat(e);return e.endsWith("pt")?a*(4/3):a}function Se(e){const a=x(e),t=Tt(a.fontSize),i=parseInt(a.fontWeight)||(a.fontWeight==="bold"?700:400);return t>=23.5||t>=18.5&&i>=700}function Nt(e){const a=_e(e),t=[];for(const i of a){const n=i.trim();if(!n)continue;const o=n.match(/rgba?\([^)]+\)/),r=o?T(o[0]):null;if(!r)return null;const s=n.replace(/rgba?\([^)]+\)/,"").match(/[\d.]+px/g),l=s&&s.length>=3?parseFloat(s[2]):0;t.push({color:r,blur:l})}return t.length>0?t:null}function Ie(e){return e==="transparent"||e==="rgba(0, 0, 0, 0)"||e==="rgba(0 0 0 / 0)"}function _([e,a,t]){return"#"+[e,a,t].map(i=>i.toString(16).padStart(2,"0")).join("")}function Mt(e,a,t){const i=q(e[0],e[1],e[2]),n=q(a[0],a[1],a[2]);let o=$(i,n);for(const r of t){const s=q(r.color[0],r.color[1],r.color[2]);o=Math.max(o,$(i,s),$(s,n))}return o}function $t(e){let a=1,t=e;for(;t;){const i=x(t),n=parseFloat(i.opacity);isNaN(n)||(a*=n),t=t.parentElement}return a}function Ht(e){let a=e;for(;a;){for(const n of["::before","::after"])try{const o=getComputedStyle(a,n),r=o.content;if(!r||r==="none"||r==="normal"||r==='""')continue;const s=o.backgroundColor;if(s&&!Ie(s)&&U(s)>=.1)return!0;const l=o.backgroundImage;if(l&&l!=="none"&&l!=="initial")return!0;const d=o.position;if(d==="absolute"||d==="fixed"){const c=parseFloat(o.width),u=parseFloat(o.height);if(c>1&&u>1)return!0}}catch{}const i=x(a).backgroundColor;if(i&&!Ie(i)&&U(i)>=1)break;a=a.parentElement}return!1}const G=new Map;function Pt(e,a){G.set(e,a),re.delete(e)}function Dt(e,a){const t=G.get(a);return t?e.map(i=>{const n=t[i.id];return n?{...i,description:n.description,guidance:n.guidance!==void 0?n.guidance:i.guidance}:i}):e}const re=new Map;function Ft(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function zt(e){const a=e.split(/\{(\d+)\}/);let t="^";for(let i=0;i<a.length;i++)i%2===0?t+=Ft(a[i]):t+="(.+?)";return t+="$",new RegExp(t)}function jt(e,a){let t=re.get(e);if(t||(t=new Map,re.set(e,t)),t.has(a))return t.get(a);const i=G.get(e);if(!i)return;const n=i[a];if(!(n!=null&&n.messages))return t.set(a,[]),[];const o=[];for(const[r,s]of Object.entries(n.messages))o.push({regex:zt(r),translated:s});return t.set(a,o),o}function Wt(e,a,t){const i=jt(t,e);if(!i)return a;for(const{regex:n,translated:o}of i){const r=a.match(n);if(r)return o.replace(/\{(\d+)\}/g,(s,l)=>{const d=parseInt(l,10);return d+1<r.length?r[d+1]:`{${l}}`})}return a}function ue(e,a){return G.has(a)?e.map(t=>{const i=Wt(t.ruleId,t.message,a);return i===t.message?t:{...t,message:i}}):e}function qe(e){var o,r;const a=[],t=e.closest("a");if(t){const s=t.getAttribute("href");s&&a.push(`Link href: ${s}`)}const i=e.closest("figure");if(i){const s=i.querySelector("figcaption");(o=s==null?void 0:s.textContent)!=null&&o.trim()&&a.push(`Figcaption: ${s.textContent.trim().slice(0,100)}`)}const n=e.parentElement;if(n&&n!==t){const s=e instanceof HTMLImageElement&&e.alt||"",l=(r=n.textContent)==null?void 0:r.replace(s,"").trim().slice(0,100);l&&a.push(`Adjacent text: ${l}`)}return a.length>0?a.join(`
|
|
2
|
-
`):void 0}const Ut={id:"text-alternatives/img-alt",category:"text-alternatives",actRuleIds:["23a2a8"],wcag:["1.1.1"],level:"A",fixability:"contextual",browserHint:"Screenshot the image to describe its visual content for alt text.",description:`Images must have alternate text. Add an alt attribute to <img> elements. Decorative images may use an empty alt attribute (alt=""), role='none', or role='presentation'.`,guidance:"Every image needs an alt attribute. For informative images, describe the content or function concisely. For decorative images (backgrounds, spacers, purely visual flourishes), use alt='' to hide them from screen readers. Never omit alt entirely—screen readers may read the filename instead. When an image is inside a link or button that already has text, use alt='' if the image is decorative in that context, or write alt text that complements (not duplicates) the existing text.",run(e){const a=[];for(const t of e.querySelectorAll("img")){if(h(t)||Ue(t))continue;const i=t.getAttribute("role");if(i==="presentation"||i==="none"){const o=t.getAttribute("tabindex");if(!o||o==="-1")continue}const n=t.getAttribute("alt");if(n!==null&&n.trim()===""&&n!==""){a.push({ruleId:"text-alternatives/img-alt",selector:p(t),html:m(t),impact:"critical",message:'Image has whitespace-only alt text. Use alt="" for decorative images or provide descriptive text.',context:qe(t),fix:{type:"set-attribute",attribute:"alt",value:""}});continue}!t.hasAttribute("alt")&&!y(t)&&a.push({ruleId:"text-alternatives/img-alt",selector:p(t),html:m(t),impact:"critical",message:"Image element missing alt attribute.",context:qe(t),fix:{type:"add-attribute",attribute:"alt",value:""}})}return a}};function Ot(e){var i;const a=Oe(e);if(a)return a;const t=e.querySelector("title");return(i=t==null?void 0:t.textContent)!=null&&i.trim()?t.textContent.trim():""}const Vt={id:"text-alternatives/svg-img-alt",category:"text-alternatives",actRuleIds:["7d6734"],wcag:["1.1.1"],level:"A",fixability:"contextual",browserHint:"Screenshot the SVG to understand its content, then add a title element or aria-label.",description:"SVG elements with an img, graphics-document, or graphics-symbol role must have an accessible name via a <title> element, aria-label, or aria-labelledby.",guidance:"Inline SVGs with role='img' need accessible names. Add a <title> element as the first child of the SVG (screen readers will announce it), or use aria-label on the SVG element. For complex SVGs, use aria-labelledby referencing both a <title> and <desc> element. Decorative SVGs should use aria-hidden='true' instead.",run(e){const a=[],t='svg[role="img"], [role="graphics-document"], [role="graphics-symbol"]';for(const i of e.querySelectorAll(t)){if(h(i))continue;if(!Ot(i)){const o=i.getAttribute("role");a.push({ruleId:"text-alternatives/svg-img-alt",selector:p(i),html:m(i),impact:"serious",message:`${i.tagName.toLowerCase()} with role='${o}' has no accessible name.`,fix:{type:"add-attribute",attribute:"aria-label",value:""}})}}return a}},Bt={id:"text-alternatives/input-image-alt",category:"text-alternatives",actRuleIds:["59796f"],wcag:["1.1.1","4.1.2"],level:"A",fixability:"contextual",browserHint:"Screenshot the image button to see its icon, then set alt to describe the action (e.g., 'Search', 'Submit').",description:'Image inputs (<input type="image">) must have alternate text describing the button action.',guidance:"Image buttons (<input type='image'>) act as submit buttons with a custom image. Add alt text via alt, aria-label, or aria-labelledby that describes the action (e.g. alt='Search' or alt='Submit order'), not the image itself. Without it, screen readers announce only 'image' or the filename, giving no clue what the button does.",run(e){const a=[];for(const t of e.querySelectorAll('input[type="image"]'))h(t)||y(t)||a.push({ruleId:"text-alternatives/input-image-alt",selector:p(t),html:m(t),impact:"critical",message:"Image input missing alt text.",fix:{type:"add-attribute",attribute:"alt",value:""}});return a}},_t={id:"text-alternatives/image-redundant-alt",category:"text-alternatives",wcag:["1.1.1"],level:"A",tags:["best-practice"],fixability:"contextual",description:"Image alt text should not duplicate adjacent link or button text. When alt text repeats surrounding text, screen reader users hear the same information twice.",guidance:"When an image is inside a link or button that also has text, make the alt text complementary rather than identical. If the image is purely decorative in that context, use alt='' to avoid repetition.",run(e){var t;const a=[];for(const i of e.querySelectorAll("img[alt]")){const n=i.getAttribute("alt").trim().toLowerCase();if(!n)continue;const o=i.closest("a, button");if(o){const r=((t=o.textContent)==null?void 0:t.trim().toLowerCase())||"";if(r&&r===n){const s=o.tagName.toLowerCase(),l=o.getAttribute("href");a.push({ruleId:"text-alternatives/image-redundant-alt",selector:p(i),html:m(i),impact:"minor",message:`Alt text "${i.getAttribute("alt")}" duplicates surrounding ${s} text.`,context:`Duplicated text: "${i.getAttribute("alt")}", parent element: <${s}>${l?` href="${l}"`:""}`,fix:{type:"suggest",suggestion:'Set alt="" if the image is decorative in this context, or provide complementary alt text that adds information the visible text does not convey'}})}}}return a}},Gt=/^(image|picture|photo|graphic|icon|img)(\s+of\b|\s*[:\u2013\u2014-]|\s*$)/i,Yt={id:"text-alternatives/image-alt-words",category:"text-alternatives",wcag:["1.1.1"],level:"A",tags:["best-practice"],fixability:"contextual",browserHint:"Screenshot the image to verify the alt text accurately describes it without filler words like 'image of'.",description:"Image alt text should not start with words like 'image of', 'photo of', or 'picture of' — screen readers already announce the element type.",guidance:"Screen readers already announce 'image' or 'graphic' before reading alt text, so phrases like 'image of', 'photo of', or 'picture of' are redundant. Remove these words and describe what the image shows. For example, change 'image of a dog' to 'golden retriever playing fetch'.",run(e){const a=[];for(const t of e.querySelectorAll("img[alt]")){const i=t.getAttribute("alt").trim();if(!i)continue;const n=i.match(Gt);if(n){const o=n[1].toLowerCase();a.push({ruleId:"text-alternatives/image-alt-words",selector:p(t),html:m(t),impact:"minor",message:`Alt text "${i}" starts with redundant prefix "${o}".`,context:`Current alt: "${i}", redundant prefix: "${o}"`,fix:{type:"suggest",suggestion:"Remove the redundant prefix from the alt text; screen readers already announce the element as an image"}})}}return a}},Xt={id:"text-alternatives/area-alt",category:"text-alternatives",wcag:["1.1.1","4.1.2"],level:"A",fixability:"contextual",description:"Image map <area> elements must have alternative text.",guidance:"Each clickable region in an image map needs alternative text so screen reader users know what the region represents. Add an alt attribute to every <area> element describing its purpose. For complex image maps, consider using alternative approaches like SVG with embedded links, or a list of text links.",run(e){const a=[];for(const t of e.querySelectorAll("area[href]")){if(h(t))continue;y(t)||a.push({ruleId:"text-alternatives/area-alt",selector:p(t),html:m(t),impact:"critical",message:"Image map <area> element is missing alternative text.",fix:{type:"add-attribute",attribute:"alt",value:""}})}return a}},Jt={id:"text-alternatives/object-alt",category:"text-alternatives",actRuleIds:["8fc3b6"],wcag:["1.1.1"],level:"A",fixability:"contextual",browserHint:"Screenshot the embedded object to see its content, then add aria-label or title describing it.",description:"<object> elements must have alternative text.",guidance:"Object elements embed external content that may not be accessible to all users. Provide alternative text via aria-label, aria-labelledby, or a title attribute. The fallback content inside <object> is only shown when the object fails to load and does not serve as an accessible name.",run(e){var t;const a=[];for(const i of e.querySelectorAll("object")){if(h(i)||Ue(i)||i.getAttribute("role")==="presentation"||i.getAttribute("role")==="none"||Oe(i))continue;const n=i.getAttribute("data")||"";if(!((i.getAttribute("type")||"").startsWith("image/")||/\.(png|jpg|jpeg|gif|svg|webp|bmp|ico)$/i.test(n))){const s=i.querySelector("img[alt]");if(s&&((t=s.getAttribute("alt"))!=null&&t.trim()))continue}a.push({ruleId:"text-alternatives/object-alt",selector:p(i),html:m(i),impact:"serious",message:"<object> element is missing alternative text. Add aria-label, aria-labelledby, or a title attribute.",fix:{type:"add-attribute",attribute:"aria-label",value:""}})}return a}},Kt={id:"text-alternatives/role-img-alt",category:"text-alternatives",actRuleIds:["23a2a8"],wcag:["1.1.1"],level:"A",fixability:"contextual",browserHint:"Screenshot the element to see its visual appearance, then provide an aria-label describing what it represents.",description:"Elements with role='img' must have an accessible name.",guidance:"When you assign role='img' to an element (like a div containing icon fonts or CSS backgrounds), you must provide an accessible name via aria-label or aria-labelledby. Without this, screen reader users have no way to understand what the image represents. If the image is decorative, use role='presentation' or role='none' instead.",run(e){const a=[];for(const t of e.querySelectorAll('[role="img"]')){if(h(t)||t.tagName.toLowerCase()==="svg"||t.tagName.toLowerCase()==="img")continue;y(t)||a.push({ruleId:"text-alternatives/role-img-alt",selector:p(t),html:m(t),impact:"serious",message:"Element with role='img' has no accessible name. Add aria-label or aria-labelledby.",fix:{type:"add-attribute",attribute:"aria-label",value:""}})}return a}},Qt={id:"time-based-media/video-captions",category:"time-based-media",actRuleIds:["eac66b"],wcag:["1.2.2"],level:"A",fixability:"contextual",browserHint:"Screenshot the video element to see its poster or content for context when writing captions.",description:"Video elements must have captions via <track kind='captions'> or <track kind='subtitles'>.",guidance:"Captions provide text alternatives for audio content in videos, benefiting deaf users and those who cannot hear audio. Add a <track> element with kind='captions' pointing to a WebVTT caption file. Captions should include both dialogue and important sound effects.",run(e){const a=[];for(const t of e.querySelectorAll("video")){if(h(t)||I(t)||t.hasAttribute("muted")||t.hasAttribute("autoplay"))continue;t.querySelector('track[kind="captions"], track[kind="subtitles"]')||a.push({ruleId:"time-based-media/video-captions",selector:p(t),html:m(t),impact:"critical",message:"Video element has no captions track."})}return a}},Zt={id:"time-based-media/audio-transcript",category:"time-based-media",actRuleIds:["e7aa44"],wcag:["1.2.1"],level:"A",fixability:"contextual",browserHint:"Inspect the page around the audio element for existing transcript links or associated text content.",description:"Audio elements should have a text alternative or transcript.",guidance:"Audio-only content like podcasts or recordings needs a text alternative for deaf users. Provide a transcript either on the same page or linked nearby. The transcript should include all spoken content and descriptions of relevant sounds.",run(e){const a=[];for(const t of e.querySelectorAll("audio")){if(h(t)||I(t)||t.querySelector('track[kind="captions"], track[kind="descriptions"]')||t.hasAttribute("aria-describedby"))continue;const n=t.parentElement;n&&n.querySelector('a[href*="transcript"], a[href*="text"]')||a.push({ruleId:"time-based-media/audio-transcript",selector:p(t),html:m(t),impact:"critical",message:"Audio element has no transcript or text alternative. Add a transcript or track element."})}return a}},ea=new Set(["off","on","name","honorific-prefix","given-name","additional-name","family-name","honorific-suffix","nickname","email","username","new-password","current-password","one-time-code","organization-title","organization","street-address","address-line1","address-line2","address-line3","address-level4","address-level3","address-level2","address-level1","country","country-name","postal-code","cc-name","cc-given-name","cc-additional-name","cc-family-name","cc-number","cc-exp","cc-exp-month","cc-exp-year","cc-csc","cc-type","transaction-currency","transaction-amount","language","bday","bday-day","bday-month","bday-year","sex","tel","tel-country-code","tel-national","tel-area-code","tel-local","tel-extension","impp","url","photo"]),ta=new Set(["tel","tel-country-code","tel-national","tel-area-code","tel-local","tel-extension","email","impp"]),aa=new Set(["home","work","mobile","fax","pager"]),ia=new Set(["shipping","billing"]),na=new Set(["webauthn"]);function oa(e){const a=e.toLowerCase().split(/\s+/).filter(Boolean);if(a.length===0)return!0;let t=0;a[t].startsWith("section-")&&t++,t<a.length&&ia.has(a[t])&&t++;let i=!1;if(t<a.length&&aa.has(a[t])&&(i=!0,t++),t>=a.length)return!1;const n=a[t];return!ea.has(n)||i&&!ta.has(n)?!1:(t++,t<a.length&&na.has(a[t])&&t++,t===a.length)}const ra={id:"adaptable/autocomplete-valid",category:"adaptable",actRuleIds:["73f2c2"],wcag:["1.3.5"],level:"AA",fixability:"contextual",description:"Autocomplete attribute must use valid values from the HTML specification.",guidance:"The autocomplete attribute helps users fill forms by identifying input purposes. Use standard values like 'name', 'email', 'tel', 'street-address', 'postal-code', 'cc-number'. This benefits users with cognitive disabilities, motor impairments, and anyone using password managers or autofill. Check the HTML specification for the complete list of valid tokens.",run(e){const a=[];for(const t of e.querySelectorAll("[autocomplete]")){if(h(t)||I(t)||t.disabled||t.getAttribute("aria-disabled")==="true")continue;const i=t.getAttribute("autocomplete").trim();i&&(oa(i)||a.push({ruleId:"adaptable/autocomplete-valid",selector:p(t),html:m(t),impact:"serious",message:`Invalid autocomplete value "${i}".`}))}return a}};function sa(e){if(typeof e!="object"||e===null)return"Rule spec must be an object";const a=e;if(typeof a.id!="string"||a.id.length===0)return"Rule must have a non-empty string id";if(typeof a.selector!="string"||a.selector.length===0)return"Rule must have a non-empty string selector";if(typeof a.check!="object"||a.check===null)return"Rule must have a check object";const t=a.check;if(!["selector-exists","attribute-value","attribute-missing","attribute-regex","child-required","child-invalid"].includes(t.type))return`Invalid check type: ${String(t.type)}`;if(typeof a.impact!="string"||!["critical","serious","moderate","minor"].includes(a.impact))return"Rule must have a valid impact (critical|serious|moderate|minor)";if(typeof a.message!="string"||a.message.length===0)return"Rule must have a non-empty message";if(typeof a.description!="string")return"Rule must have a description string";if(!Array.isArray(a.wcag))return"Rule must have a wcag array";if(typeof a.level!="string"||!["A","AA"].includes(a.level))return"Rule must have level A or AA";const n=la(t);return n||null}function la(e){switch(e.type){case"selector-exists":return null;case"attribute-value":return typeof e.attribute!="string"?"attribute-value check requires attribute string":[">","<","=","!=","in","not-in"].includes(e.operator)?e.value===void 0?"attribute-value check requires value":null:"attribute-value check requires valid operator";case"attribute-missing":return typeof e.attribute!="string"?"attribute-missing check requires attribute string":null;case"attribute-regex":return typeof e.attribute!="string"?"attribute-regex check requires attribute string":typeof e.pattern!="string"?"attribute-regex check requires pattern string":typeof e.shouldMatch!="boolean"?"attribute-regex check requires shouldMatch boolean":null;case"child-required":return typeof e.childSelector!="string"?"child-required check requires childSelector string":null;case"child-invalid":return Array.isArray(e.allowedChildren)?null:"child-invalid check requires allowedChildren array";default:return`Unknown check type: ${String(e.type)}`}}function R(e,a,t){let i=e;if(i.includes("{{tag}}")&&(i=i.replace(/\{\{tag\}\}/g,a.tagName.toLowerCase())),i.includes("{{value}}")){let n="";"attribute"in t&&t.attribute&&(n=a.getAttribute(t.attribute)??""),i=i.replace(/\{\{value\}\}/g,n)}return i}function E(e){const a=e.skipAriaHidden!==!1;return{id:e.id,category:e.id.split("/")[0],actRuleIds:e.actRuleIds,wcag:e.wcag,level:e.level,tags:e.tags,fixability:e.fixability,browserHint:e.browserHint,description:e.description,guidance:e.guidance,run(t){var n,o;const i=[];switch(e.check.type){case"selector-exists":{for(const r of t.querySelectorAll(e.selector))a&&h(r)||i.push({ruleId:e.id,selector:p(r),html:m(r),impact:e.impact,message:R(e.message,r,e.check),...e.fix?{fix:e.fix}:{},element:r});break}case"attribute-value":{const{attribute:r,operator:s,value:l}=e.check;for(const d of t.querySelectorAll(e.selector)){if(a&&h(d))continue;const c=d.getAttribute(r);c!==null&&ca(c,s,l)&&i.push({ruleId:e.id,selector:p(d),html:m(d),impact:e.impact,message:R(e.message,d,e.check),...e.fix?{fix:e.fix}:{},element:d})}break}case"attribute-missing":{const{attribute:r}=e.check;for(const s of t.querySelectorAll(e.selector))a&&h(s)||s.hasAttribute(r)||i.push({ruleId:e.id,selector:p(s),html:m(s),impact:e.impact,message:R(e.message,s,e.check),...e.fix?{fix:e.fix}:{},element:s});break}case"attribute-regex":{const{attribute:r,pattern:s,flags:l,shouldMatch:d}=e.check;let c;try{c=new RegExp(s,l)}catch{break}for(const u of t.querySelectorAll(e.selector)){if(a&&h(u))continue;const b=u.getAttribute(r);if(b===null)continue;const g=c.test(b);d&&!g?i.push({ruleId:e.id,selector:p(u),html:m(u),impact:e.impact,message:R(e.message,u,e.check),...e.fix?{fix:e.fix}:{},element:u}):!d&&g&&i.push({ruleId:e.id,selector:p(u),html:m(u),impact:e.impact,message:R(e.message,u,e.check),...e.fix?{fix:e.fix}:{},element:u})}break}case"child-required":{const{childSelector:r}=e.check;for(const s of t.querySelectorAll(e.selector))a&&h(s)||s.querySelector(r)||i.push({ruleId:e.id,selector:p(s),html:m(s),impact:e.impact,message:R(e.message,s,e.check),...e.fix?{fix:e.fix}:{},element:s});break}case"child-invalid":{const r=new Set(e.check.allowedChildren.map(l=>l.toLowerCase())),s=e.check.allowedChildRoles?new Set(e.check.allowedChildRoles.map(l=>l.toLowerCase())):null;for(const l of t.querySelectorAll(e.selector)){if(a&&h(l))continue;const d=(n=l.getAttribute("role"))==null?void 0:n.trim().toLowerCase();if(d==="presentation"||d==="none")continue;let c=!1;const u=e.check.allowedChildren.filter(b=>b!=="script"&&b!=="template");for(const b of l.childNodes)if(b.nodeType===3&&b.textContent&&b.textContent.trim()){const g=u.map(f=>`<${f}>`).join(" or ");i.push({ruleId:e.id,selector:p(l),html:m(l),impact:e.impact,message:`<${l.tagName.toLowerCase()}> contains direct text content. Wrap in ${g}.`,...e.fix?{fix:e.fix}:{},element:l}),c=!0;break}if(!c)for(const b of l.children){if(r.has(b.tagName.toLowerCase()))continue;const g=(o=b.getAttribute("role"))==null?void 0:o.trim().toLowerCase();if(!(g&&(s!=null&&s.has(g)))&&!(g==="presentation"||g==="none")){i.push({ruleId:e.id,selector:p(b),html:m(b),impact:e.impact,message:R(e.message,b,e.check),...e.fix?{fix:e.fix}:{},element:b});break}}}break}}return i}}}function ca(e,a,t){switch(a){case">":return parseFloat(e)>t;case"<":return parseFloat(e)<t;case"=":return e===String(t);case"!=":return e!==String(t);case"in":return Array.isArray(t)&&t.includes(e);case"not-in":return Array.isArray(t)&&!t.includes(e);default:return!1}}const da={id:"adaptable/list-children",selector:"ul, ol",check:{type:"child-invalid",allowedChildren:["li","script","template"],allowedChildRoles:["listitem"]},impact:"serious",message:"List contains non-<li> child <{{tag}}>.",description:"<ul> and <ol> must only contain <li>, <script>, or <template> as direct children.",wcag:["1.3.1"],level:"A",fixability:"contextual",guidance:"Screen readers announce list structure ('list with 5 items') based on proper markup. Placing non-<li> elements directly inside <ul> or <ol> breaks this structure. Wrap content in <li> elements, or if you need wrapper divs for styling, apply styles to <li> elements directly and remove the wrapper (e.g., change <ul><div>item</div></ul> to <ul><li>item</li></ul>)."},ua=E(da),ma={id:"adaptable/listitem-parent",category:"adaptable",wcag:["1.3.1"],level:"A",fixability:"contextual",description:"<li> elements must be contained in a <ul>, <ol>, or <menu>.",guidance:"List items (<li>) only have semantic meaning inside a list container (<ul>, <ol>, or <menu>). Without a list parent, screen readers will not announce 'list with N items' or allow users to skip between items using list navigation shortcuts. Wrap <li> elements in the appropriate list container — <ul> for unordered lists, <ol> for ordered/numbered lists.",run(e){var t;const a=[];for(const i of e.querySelectorAll("li")){if(h(i))continue;const n=i.parentElement;if(!n)continue;const o=n.tagName.toLowerCase();o==="ul"||o==="ol"||o==="menu"||((t=n.getAttribute("role"))==null?void 0:t.trim().toLowerCase())==="list"||a.push({ruleId:"adaptable/listitem-parent",selector:p(i),html:m(i),impact:"serious",message:"<li> is not contained in a <ul>, <ol>, or <menu>."})}return a}},pa={id:"adaptable/dl-children",category:"adaptable",wcag:["1.3.1"],level:"A",fixability:"contextual",description:"<dt> and <dd> elements must be contained in a <dl>.",guidance:"Definition terms (<dt>) and definitions (<dd>) only have semantic meaning inside a definition list (<dl>). Outside of <dl>, they're treated as generic text. Wrap related <dt> and <dd> pairs in a <dl> element to convey the term/definition relationship to assistive technologies.",run(e){var t;const a=[];for(const i of e.querySelectorAll("dt, dd")){const n=i.parentElement,o=n==null?void 0:n.tagName.toLowerCase();(!n||o!=="dl"&&!(o==="div"&&((t=n.parentElement)==null?void 0:t.tagName.toLowerCase())==="dl"))&&a.push({ruleId:"adaptable/dl-children",selector:p(i),html:m(i),impact:"serious",message:`<${i.tagName.toLowerCase()}> is not contained in a <dl>.`})}return a}},ba={id:"adaptable/definition-list",selector:"dl",check:{type:"child-invalid",allowedChildren:["dt","dd","div","script","template"]},impact:"serious",message:"<dl> contains invalid child <{{tag}}>.",description:"<dl> elements must only contain <dt>, <dd>, <div>, <script>, or <template>.",wcag:["1.3.1"],level:"A",fixability:"contextual",guidance:"Definition lists have strict content requirements. Only <dt> (terms), <dd> (definitions), and <div> (for grouping dt/dd pairs) are valid children. Other elements break the list structure for screen readers. Move invalid elements outside the <dl>, or if they represent a term change to <dt>, if a definition change to <dd>. Styling wrappers should be replaced with <div> elements containing <dt>/<dd> pairs."},ha=E(ba);function Ge(e,a){switch(a.toLowerCase()){case"deg":return e;case"rad":return e*180/Math.PI;case"turn":return e*360;case"grad":return e*.9;default:return NaN}}function B(e){return isNaN(e)?!1:(e=(e%360+360)%360,e>=85&&e<=95||e>=265&&e<=275)}function ga(e){const a=e.match(/rotate[Z]?\(\s*(-?[\d.]+)(deg|rad|turn|grad)\s*\)/i);if(a){const n=Ge(parseFloat(a[1]),a[2]);if(B(n))return!0}const t=e.match(/matrix\(\s*(-?[\d.e]+)\s*,\s*(-?[\d.e]+)\s*,\s*(-?[\d.e]+)\s*,\s*(-?[\d.e]+)/i);if(t){const n=parseFloat(t[1]),o=parseFloat(t[2]),r=Math.atan2(o,n)*(180/Math.PI);if(B(r))return!0}const i=e.match(/matrix3d\(\s*(-?[\d.e]+)\s*,\s*(-?[\d.e]+)\s*,\s*(-?[\d.e]+)\s*,\s*(-?[\d.e]+)\s*,\s*(-?[\d.e]+)\s*,\s*(-?[\d.e]+)/i);if(i){const n=parseFloat(i[1]),o=parseFloat(i[2]),r=Math.atan2(o,n)*(180/Math.PI);if(B(r))return!0}return!1}function fa(e){const a=e.match(/(-?[\d.]+)(deg|rad|turn|grad)/i);if(!a)return!1;const t=Ge(parseFloat(a[1]),a[2]);return B(t)}const va={id:"adaptable/orientation-lock",category:"adaptable",actRuleIds:["b33eff"],wcag:["1.3.4"],level:"AA",tags:["page-level"],fixability:"contextual",description:"Page orientation must not be restricted using CSS transforms.",guidance:"Users with motor disabilities may mount their device in a fixed orientation. Using CSS transforms with @media (orientation: portrait/landscape) to rotate content 90° effectively locks the page to one orientation. Remove the orientation-dependent transform and use responsive design instead.",run(e){const a=[];for(const t of e.querySelectorAll("style")){const i=t.textContent||"",n=/@media[^{]*\b(orientation)\s*:\s*(portrait|landscape)\b[^{]*\{([^}]*\{[^}]*\}[^}]*)\}/gi;let o;for(;o=n.exec(i);){const r=o[3];let s=!1;const l=r.match(/transform\s*:\s*([^;]+)/i);if(l&&ga(l[1])&&(s=!0),!s){const d=r.match(/(?:^|[{;\s])rotate\s*:\s*([^;]+)/i);d&&fa(d[1])&&(s=!0)}s&&a.push({ruleId:"adaptable/orientation-lock",selector:p(t),html:m(t),impact:"serious",message:`CSS locks page orientation via @media (orientation: ${o[2]}) with a 90° transform.`})}}return a}},Ee={combobox:[["listbox","tree","grid","dialog","textbox"]],feed:[["article"]],grid:[["row","rowgroup"]],list:[["listitem","group"]],listbox:[["option","group"]],menu:[["menuitem","menuitemcheckbox","menuitemradio","group","menu","separator"]],menubar:[["menuitem","menuitemcheckbox","menuitemradio","group","menu","separator"]],radiogroup:[["radio"]],row:[["cell","columnheader","gridcell","rowheader"]],rowgroup:[["row"]],table:[["row","rowgroup"]],tablist:[["tab"]],tree:[["treeitem","group"]],treegrid:[["row","rowgroup"]]},ya=new Set(["doc-bibliography","doc-endnotes","grid","group","list","listbox","rowgroup","table","tablist","tree","treegrid"]);function wa(e,a){var r;const t=((r=e.getAttribute("aria-owns"))==null?void 0:r.split(/\s+/))||[],i=e.ownerDocument,n=new Set;let o=!1;for(const s of e.querySelectorAll("*")){if(h(s))continue;o=!0;const l=H(s);l&&n.add(l)}for(const s of t){const l=i.getElementById(s);if(l&&!h(l)){o=!0;const d=H(l);d&&n.add(d)}}if(!o)return"empty";for(const s of a)if(!s.some(l=>n.has(l)))return"fail";return"pass"}const xa={id:"adaptable/aria-required-children",category:"adaptable",actRuleIds:["bc4a75"],wcag:["1.3.1"],level:"A",fixability:"contextual",description:"Certain ARIA roles require specific child roles to be present.",guidance:"Some ARIA roles represent containers that must contain specific child roles for proper semantics. For example, a list must contain listitems, a menu must contain menuitems. Add the required child elements with appropriate roles, or use native HTML elements that provide these semantics implicitly (e.g., <ul> with <li>).",run(e){var t;const a=[];for(const i of e.querySelectorAll("[role]")){if(h(i))continue;const n=(t=i.getAttribute("role"))==null?void 0:t.trim().toLowerCase();if(!n||!(n in Ee)||i.getAttribute("aria-busy")==="true")continue;if(n==="combobox"){if(i.getAttribute("aria-expanded")!=="true")continue;const l=i.tagName.toLowerCase();if(l==="input"||l==="textarea")continue}const o=Ee[n],r=wa(i,o);if(r==="pass"||r==="empty"&&ya.has(n))continue;const s=o.map(l=>l.join(" or ")).join(", ");a.push({ruleId:"adaptable/aria-required-children",selector:p(i),html:m(i),impact:"critical",message:`Role "${n}" requires children with role: ${s}.`})}return a}},Le={caption:["figure","table","grid","treegrid"],cell:["row"],columnheader:["row"],gridcell:["row"],listitem:["list","group"],menuitem:["menu","menubar","group"],menuitemcheckbox:["menu","menubar","group"],menuitemradio:["menu","menubar","group"],option:["listbox","group"],row:["table","grid","treegrid","rowgroup"],rowgroup:["table","grid","treegrid"],rowheader:["row"],tab:["tablist"],treeitem:["tree","group"]},Aa={id:"adaptable/aria-required-parent",category:"adaptable",actRuleIds:["ff89c9"],wcag:["1.3.1"],level:"A",fixability:"contextual",description:"Certain ARIA roles must be contained within specific parent roles.",guidance:"Some ARIA roles represent items that must exist within specific container roles. For example, a listitem must be within a list, a tab must be within a tablist. Wrap the element in the appropriate parent, or use native HTML elements that provide this structure (e.g., <li> inside <ul>).",run(e){var t;const a=[];for(const i of e.querySelectorAll("[role]")){if(h(i))continue;const n=(t=i.getAttribute("role"))==null?void 0:t.trim().toLowerCase();if(!n||!(n in Le))continue;const o=Le[n];let r=i.parentElement,s=!1;for(;r&&r!==e.documentElement;){const l=H(r);if(l&&o.includes(l)){s=!0;break}r=r.parentElement}s||a.push({ruleId:"adaptable/aria-required-parent",selector:p(i),html:m(i),impact:"critical",message:`Role "${n}" must be contained within: ${o.join(", ")}.`})}return a}},ka={id:"adaptable/td-headers-attr",category:"adaptable",actRuleIds:["a25f45"],wcag:["1.3.1"],level:"A",fixability:"contextual",description:"All cells in a table using headers attribute must reference valid header IDs.",guidance:"The headers attribute on table cells must reference IDs of header cells (th or td) within the same table. This creates explicit associations for screen readers. Verify all referenced IDs exist and spell them correctly. For simple tables, consider using scope on th elements instead.",run(e){const a=[];for(const t of e.querySelectorAll("td[headers]")){if(h(t))continue;const i=t.closest("table");if(!i)continue;const n=t.getAttribute("id"),o=t.getAttribute("headers").split(/\s+/);for(const r of o){if(r===n){a.push({ruleId:"adaptable/td-headers-attr",selector:p(t),html:m(t),impact:"serious",message:`Headers attribute references the cell itself ("${r}").`});break}if(!i.querySelector(`th#${CSS.escape(r)}, td#${CSS.escape(r)}`)){a.push({ruleId:"adaptable/td-headers-attr",selector:p(t),html:m(t),impact:"serious",message:`Headers attribute references non-existent ID "${r}".`});break}}}return a}},Sa={id:"adaptable/th-has-data-cells",category:"adaptable",actRuleIds:["d0f69e"],wcag:["1.3.1"],level:"A",fixability:"contextual",description:"Table headers should be associated with data cells.",guidance:"Screen readers use <th> elements to announce column or row headers when navigating table cells — for example, reading 'Name: John' when moving to a cell. A table with <th> but no <td> elements means headers describe nothing, and screen readers cannot associate data with headers. Either add <td> data cells, or if this is not tabular data, use non-table markup instead.",run(e){const a=[];for(const t of e.querySelectorAll("table")){if(h(t)||t.getAttribute("role")==="presentation"||t.getAttribute("role")==="none")continue;const i=t.querySelectorAll("th"),n=t.querySelectorAll("td");i.length>0&&n.length===0&&a.push({ruleId:"adaptable/th-has-data-cells",selector:p(t),html:m(t),impact:"serious",message:"Table has header cells but no data cells."})}return a}};function Ia(e){var i;const a=e.getAttribute("role");if(a==="presentation"||a==="none")return!1;if(a==="table"||a==="grid"||a==="treegrid"||e.querySelector("caption")||e.getAttribute("summary")||e.querySelector("thead, tfoot, colgroup")||e.querySelector("th[scope]")||e.querySelector("td[headers]"))return!0;const t=e.querySelectorAll("th");if(t.length===0)return!1;for(const n of t)if((i=n.textContent)!=null&&i.trim())return!0;return!1}function Re(e){let a=0,t=e.previousElementSibling;for(;t;)a+=parseInt(t.getAttribute("colspan")||"1",10),t=t.previousElementSibling;return a}const qa={id:"adaptable/td-has-header",category:"adaptable",wcag:["1.3.1"],level:"A",fixability:"contextual",browserHint:"Screenshot the table to understand its visual layout, then add scope or headers attributes to associate data cells with headers.",description:"Data cells in tables larger than 3x3 should have associated headers.",guidance:"In complex tables, screen reader users need header associations to understand data cells. Use th elements with scope attribute, or the headers attribute on td elements. For simple tables (≤3x3), this is less critical as context is usually clear.",run(e){var t;const a=[];for(const i of e.querySelectorAll("table")){if(h(i)||!Ia(i))continue;const n=i.querySelectorAll("tr"),o=n.length;let r=0;for(const d of n){const c=d.querySelectorAll("td, th");let u=0;for(const b of c)u+=parseInt(b.getAttribute("colspan")||"1",10);r=Math.max(r,u)}if(o<=3&&r<=3)continue;const s=i.querySelector("th[scope]")!==null,l=i.querySelector("td[headers]")!==null;for(const d of i.querySelectorAll("td")){if(h(d)||!((t=d.textContent)!=null&&t.trim())&&!d.querySelector("img, svg, input, select, textarea")||d.hasAttribute("aria-label")||d.hasAttribute("aria-labelledby")||d.hasAttribute("headers"))continue;const c=d.closest("tr");if(!c)continue;const u=c.querySelector("th")!==null,b=Re(d);let g=!1;const f=i.querySelector("thead"),v=(f==null?void 0:f.querySelector("tr"))??i.querySelector("tbody > tr, tr");if(v)for(const w of v.querySelectorAll("th, td")){const A=Re(w),S=parseInt(w.getAttribute("colspan")||"1",10);if(w.tagName.toLowerCase()==="th"&&b>=A&&b<A+S){g=!0;break}}if(!u&&!g&&!s&&!l){a.push({ruleId:"adaptable/td-has-header",selector:p(d),html:m(d),impact:"serious",message:"Data cell has no associated header. Add th elements with scope, or headers attribute."});break}}}return a}},Ea={id:"adaptable/scope-attr-valid",category:"adaptable",wcag:["1.3.1"],level:"A",fixability:"mechanical",description:"The scope attribute on table headers must have a valid value.",guidance:"The scope attribute tells screen readers which cells a header applies to. Valid values are: row, col, rowgroup, colgroup. Using invalid values breaks the association between headers and cells.",run(e){var i;const a=[],t=new Set(["row","col","rowgroup","colgroup"]);for(const n of e.querySelectorAll("th[scope]")){if(h(n))continue;const o=(i=n.getAttribute("scope"))==null?void 0:i.toLowerCase();o&&!t.has(o)&&a.push({ruleId:"adaptable/scope-attr-valid",selector:p(n),html:m(n),impact:"moderate",message:`Invalid scope value "${o}". Use row, col, rowgroup, or colgroup.`})}return a}},La={id:"adaptable/empty-table-header",category:"adaptable",wcag:["1.3.1"],level:"A",tags:["best-practice"],fixability:"contextual",browserHint:"Screenshot the table to see which header cells are visually empty, then add text content or aria-label.",description:"Table header cells should have visible text.",guidance:"Empty table headers provide no information to screen reader users. Either add descriptive text to the header, or if the header is intentionally empty (like a corner cell), consider using a td element instead or adding a visually hidden label.",run(e){const a=[];for(const t of e.querySelectorAll("th")){if(h(t))continue;const i=t.closest("table");(i==null?void 0:i.getAttribute("role"))==="presentation"||(i==null?void 0:i.getAttribute("role"))==="none"||y(t)||a.push({ruleId:"adaptable/empty-table-header",selector:p(t),html:m(t),impact:"minor",message:"Table header cell is empty. Add text or use aria-label."})}return a}},Ra={id:"distinguishable/meta-viewport",category:"distinguishable",actRuleIds:["b4f0c3"],wcag:["1.4.4"],level:"AA",tags:["page-level"],fixability:"mechanical",browserHint:"After fixing the viewport meta tag, resize the viewport to 320px wide and screenshot to verify content remains readable and usable.",description:"Viewport meta tag must not disable user scaling.",guidance:"Users with low vision need to zoom content up to 200% or more. Setting user-scalable=no or maximum-scale=1 prevents zooming and fails WCAG. Remove these restrictions. If your layout breaks at high zoom, fix the responsive design rather than preventing zoom.",run(e){const a=[],t=e.querySelector('meta[name="viewport"]');if(!t)return[];const i=t.getAttribute("content")||"",n=i.toLowerCase(),o=n.match(/user-scalable\s*=\s*([^\s,;]+)/i);if(o){const s=o[1],l=parseFloat(s);(s==="no"||!isNaN(l)&&l>-1&&l<1)&&a.push({ruleId:"distinguishable/meta-viewport",selector:p(t),html:m(t),impact:"critical",message:`Viewport disables user scaling (user-scalable=${s}). Remove this restriction.`,context:`content: "${i}"`,fix:{type:"suggest",suggestion:"Remove user-scalable=no from the viewport meta content attribute"}})}const r=n.match(/maximum-scale\s*=\s*([\d.]+|yes)/i);if(r){const s=r[1],l=s.toLowerCase()==="yes"?1:parseFloat(s);l<2&&a.push({ruleId:"distinguishable/meta-viewport",selector:p(t),html:m(t),impact:"critical",message:`Viewport maximum-scale=${l} restricts zooming. Set to at least 2 or remove.`,context:`content: "${i}"`,fix:{type:"suggest",suggestion:"Remove maximum-scale or set it to at least 2 in the viewport meta content attribute"}})}return a}};function me(e){return e.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&")}function Ye(e,a){const t=e.getAttribute("style");if(!t)return null;const i=new RegExp(`${me(a)}\\s*:\\s*([^;!]+)\\s*!\\s*important`,"gi");let n=null,o;for(;o=i.exec(t);)n=o;if(!n)return null;const r=n[1].trim();if(/^(inherit|unset|revert)$/i.test(r))return null;if(/^(normal|initial)$/i.test(r))return{em:0,px:null};const s=r.match(/^(-?[\d.]+)\s*em$/i);if(s)return{em:parseFloat(s[1]),px:null};const l=r.match(/^(-?[\d.]+)$/);if(l)return{em:parseFloat(l[1]),px:null};const d=r.match(/^(-?[\d.]+)\s*%$/);if(d)return{em:parseFloat(d[1])/100,px:null};const c=r.match(/^(-?[\d.]+)\s*(px|pt|cm|mm|in)$/i);if(c){const u=parseFloat(c[1]),b=c[2].toLowerCase();let g;switch(b){case"px":g=u;break;case"pt":g=u*(4/3);break;case"cm":g=u*(96/2.54);break;case"mm":g=u*(96/25.4);break;case"in":g=u*96;break;default:return null}return{em:null,px:g}}return null}function Xe(e,a,t,i){function n(o){var r;if(o!==e){const s=o.getAttribute("style")||"";if(new RegExp(`${me(a)}\\s*:\\s*[^;!]+\\s*!\\s*important`,"i").test(s))return!1}for(const s of o.childNodes)if(s.nodeType===3&&((r=s.textContent)!=null&&r.trim())){const l=parseFloat(x(o).fontSize);if(l>0&&t/l<i)return!0;break}for(const s of o.children)if(n(s))return!0;return!1}return n(e)}function Ca(e){var a;for(const t of e.childNodes)if(t.nodeType===3&&((a=t.textContent)!=null&&a.trim()))return!0;return!1}function Je(e){return!e.closest("svg")&&!e.closest("math")}function Ke(e){const a=e.getAttribute("style");if(!a)return!1;if(/position\s*:\s*(absolute|fixed)/i.test(a)){const t=a.match(/top\s*:\s*(-[\d.]+)(em|px|%)/i);if(t&&parseFloat(t[1])<-100)return!0;const i=a.match(/left\s*:\s*(-[\d.]+)(em|px|%)/i);if(i&&parseFloat(i[1])<-100)return!0}return!1}function pe(e,a){if(Ca(e))return!0;for(const t of e.children){const i=t.getAttribute("style")||"";if(!new RegExp(`${me(a)}\\s*:\\s*[^;!]+\\s*!\\s*important`,"i").test(i)&&pe(t,a))return!0}return!1}function Qe(e,a,t,i){const n=[];for(const o of e.querySelectorAll("[style]")){if(h(o)||!Je(o)||Ke(o)||!pe(o,t))continue;const r=Ye(o,t);if(!r)continue;let s=!1;if(r.em!==null?s=r.em<i:r.px!==null&&(s=Xe(o,t,r.px,i)),s){const l=r.em!==null?`${r.em}${t==="line-height"?"":"em"}`:`${r.px}px`;n.push({ruleId:a,selector:p(o),html:m(o),impact:"serious",message:`${t} ${l} with !important is below the ${i}${t==="line-height"?"":"em"} minimum.`})}}return n}function Ta(e){let a=e,t=!1;for(;a;){const i=x(a);parseFloat(i.width)>500&&(t=!0),(i.whiteSpace==="nowrap"||i.whiteSpace==="pre")&&(t=!0);const o=i.overflowX,r=i.overflowY;if((o==="scroll"||o==="auto")&&r!=="scroll"&&r!=="auto")return t;a=a.parentElement}return!1}const Na={id:"distinguishable/letter-spacing",category:"distinguishable",actRuleIds:["24afc2"],wcag:["1.4.12"],level:"AA",fixability:"mechanical",description:"Letter spacing set with !important in style attributes must be at least 0.12em.",guidance:"WCAG 1.4.12 requires users to be able to override text spacing. Using !important on letter-spacing with a value below 0.12em prevents this. Either increase the value to at least 0.12em or remove !important.",run(e){return Qe(e,"distinguishable/letter-spacing","letter-spacing",.12)}},Ma={id:"distinguishable/line-height",category:"distinguishable",actRuleIds:["78fd32"],wcag:["1.4.12"],level:"AA",fixability:"mechanical",description:"Line height set with !important in style attributes must be at least 1.5.",guidance:"WCAG 1.4.12 requires users to be able to override text spacing. Using !important on line-height with a value below 1.5 prevents this. Either increase the value to at least 1.5 or remove !important.",run(e){const a=[];for(const t of e.querySelectorAll("[style]")){if(h(t)||!Je(t)||Ke(t)||!pe(t,"line-height")||Ta(t))continue;if(t instanceof HTMLElement&&t.scrollHeight>0){const o=parseFloat(x(t).lineHeight);if(o>0&&t.scrollHeight<=o*1.5)continue}const i=Ye(t,"line-height");if(!i)continue;let n=!1;if(i.em!==null?n=i.em<1.5:i.px!==null&&(n=Xe(t,"line-height",i.px,1.5)),n){const o=i.em!==null?`${i.em}`:`${i.px}px`;a.push({ruleId:"distinguishable/line-height",selector:p(t),html:m(t),impact:"serious",message:`Line height ${o} with !important is below the 1.5 minimum.`})}}return a}},$a={id:"distinguishable/word-spacing",category:"distinguishable",actRuleIds:["9e45ec"],wcag:["1.4.12"],level:"AA",fixability:"mechanical",description:"Word spacing set with !important in style attributes must be at least 0.16em.",guidance:"WCAG 1.4.12 requires users to be able to override text spacing. Using !important on word-spacing with a value below 0.16em prevents this. Either increase the value to at least 0.16em or remove !important.",run(e){return Qe(e,"distinguishable/word-spacing","word-spacing",.16)}},Ha=new Set(["block","flex","grid","table","table-cell","list-item","flow-root"]),Pa=new Set(["inline","inline-block","inline-flex","inline-grid"]);function Da(e){let a=e.parentElement;for(;a&&!Ha.has(x(a).display);)a=a.parentElement;if(!a)return null;const t=a.ownerDocument.createTreeWalker(a,NodeFilter.SHOW_TEXT);let i="",n=null,o;for(;o=t.nextNode();){if(!o.data.trim())continue;let s=o.parentElement,l=!1;for(;s&&s!==a;){if(s.tagName==="A"){l=!0;break}s=s.parentElement}l||(i+=o.data,!n&&o.parentElement&&(n=T(x(o.parentElement).color)))}const r=i.match(new RegExp("\\p{L}{3,}","gu"));return!n||!r||r.length<2?null:{block:a,textColor:n}}function Ce(e,a){const t=e.textDecorationLine||e.textDecoration||"";return(t.includes("underline")||t.includes("line-through"))&&t!==a}function V(e){return e==="bold"?700:e==="normal"?400:parseInt(e)||400}function Fa(e){const a=e.ownerDocument.createTreeWalker(e,NodeFilter.SHOW_TEXT);let t;for(;t=a.nextNode();)if(t.data.trim())return!1;return!0}const za={id:"distinguishable/link-in-text-block",category:"distinguishable",wcag:["1.4.1"],level:"A",fixability:"visual",browserHint:"Screenshot the text block to see how the link blends with surrounding text, then verify your fix (e.g., underline or border) makes the link visually distinct.",description:"Links within text blocks must be distinguishable by more than color alone.",guidance:"Users who cannot perceive color differences need other visual cues to identify links. Links in text should have underlines or other non-color indicators. If using color alone, ensure 3:1 contrast with surrounding text AND provide additional indication on focus/hover.",run(e){const a=[];for(const t of e.querySelectorAll("a[href]")){if(h(t)||!k(t).trim()||Fa(t)||t.closest('nav, header, footer, aside, [role="navigation"], [role="banner"], [role="contentinfo"], [role="complementary"]'))continue;const i=x(t);if(!Pa.has(i.display||"inline"))continue;const n=Da(t);if(!n)continue;const o=x(n.block),r=o.textDecorationLine||o.textDecoration||"";if(Ce(i,r)||(parseFloat(i.borderBottomWidth)||0)>0&&i.borderBottomStyle!=="none"&&i.borderBottomStyle!=="hidden"||Math.abs(V(i.fontWeight)-V(o.fontWeight))>=300||i.fontStyle!==o.fontStyle)continue;const l=parseFloat(i.fontSize)||16,d=parseFloat(o.fontSize)||16;if(d>0&&l/d>=1.2)continue;let c=!1;for(const w of t.querySelectorAll("*")){const A=x(w);if(Ce(A,r)||Math.abs(V(A.fontWeight)-V(o.fontWeight))>=300){c=!0;break}}if(c)continue;const u=T(i.color);if(!u)continue;const b=q(...u),g=q(...n.textColor),f=$(b,g);if(f<1.1||f>=3)continue;const v=w=>"#"+w.map(A=>A.toString(16).padStart(2,"0")).join("");a.push({ruleId:"distinguishable/link-in-text-block",selector:p(t),html:m(t),impact:"serious",message:"Link in text block is not visually distinguishable from surrounding text. Add a non-color visual indicator such as an underline or border.",context:`link color: ${v(u)} rgb(${u.join(", ")}), surrounding text: ${v(n.textColor)} rgb(${n.textColor.join(", ")}), ratio: ${f.toFixed(2)}:1`,fix:{type:"suggest",suggestion:"Add text-decoration: underline to the link, or add a visible border-bottom. If relying on color contrast alone, ensure at least 3:1 ratio between the link color and surrounding text color."}})}return a}},ja=new Set(["SCRIPT","STYLE","NOSCRIPT","TEMPLATE","IFRAME","OBJECT","EMBED","SVG","CANVAS","VIDEO","AUDIO","IMG","BR","HR"]);function Wa(e){const a=e.clip;if(a&&a.startsWith("rect(")){const i=a.match(/[\d.]+/g);if(!i||i.every(n=>parseFloat(n)===0))return!0}const t=e.clipPath;if(t==="inset(50%)"||t==="inset(100%)")return!0;if(e.overflow==="hidden"&&e.position==="absolute"){const i=parseFloat(e.width),n=parseFloat(e.height);if(i<=1&&n<=1)return!0}return!1}function Ua(e){if(h(e))return!0;let a=e;for(;a;){const t=x(a);if(t.display==="none"||t.visibility==="hidden"||Wa(t))return!0;a=a.parentElement}return!1}function Oa(e){return e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement||e instanceof HTMLSelectElement||e instanceof HTMLButtonElement?e.disabled:!!(e.closest("fieldset[disabled]")||e.getAttribute("aria-disabled")==="true")}function Va(e,a){if(e.tagName!=="LABEL")return!1;const t=e,i=t.htmlFor;if(i){const r=a.getElementById(i);if(r&&(r.disabled||r.getAttribute("aria-disabled")==="true"))return!0}const n=t.querySelector("input, select, textarea, button");if(n&&(n.disabled||n.getAttribute("aria-disabled")==="true"))return!0;const o=t.id;return!!(o&&a.querySelector(`[aria-labelledby~="${o}"][aria-disabled="true"]`))}function Ba(e){return e.closest("select")!==null}function _a(e){const a=e.replace(/\s/g,"");return a?!new RegExp("\\p{L}","u").test(a):!0}function Ga(e){return e.closest('[aria-disabled="true"]')!==null}const Ya={grayscale:0,blur:0,"hue-rotate":0,invert:0,sepia:0,brightness:1,contrast:1,saturate:1,opacity:1};function Xa(e){const a=parseFloat(e);return isNaN(a)?NaN:e.trim().endsWith("%")?a/100:a}const Te=/([a-z-]+)\(([^)]*)\)/g;function Ne(e){let a,t=!1;for(Te.lastIndex=0;a=Te.exec(e);){t=!0;const i=Ya[a[1]];if(i===void 0||Xa(a[2])!==i)return!1}return t}function Ja(e){let a=e;for(;a;){const t=x(a),i=t.filter;if(i&&i!=="none"&&i!=="initial"&&!Ne(i))return!0;const n=t.mixBlendMode;if(n&&n!=="normal"&&n!=="initial")return!0;const o=t.backdropFilter;if(o&&o!=="none"&&o!=="initial"&&!Ne(o))return!0;a=a.parentElement}return!1}function Ka(e){let a=e;for(;a;){const t=x(a),i=t.backgroundImage;if(i&&i!=="none"&&i!=="initial")return i.includes("gradient(")?{bgImage:i,gradientEl:a}:null;const n=t.backgroundColor;if(!n||n==="transparent"||n==="rgba(0, 0, 0, 0)"||n==="rgba(0 0 0 / 0)"){a=a.parentElement;continue}if(U(n)<.01){a=a.parentElement;continue}return null}return null}function Qa(e,a,t,i,n,o,r,s,l){const d=qt(s,l);if(d.length===0)return null;let c=0,u=d[0];for(const f of d){let v=a;t<1&&(v=C(a,f,t)),i<1&&(v=C(v,f,i));const w=$(q(v[0],v[1],v[2]),q(f[0],f[1],f[2]));w>c&&(c=w,u=f)}if(c>=n)return null;let b=a;t<1&&(b=C(a,u,t)),i<1&&(b=C(b,u,i));const g=Math.round(c*100)/100;return{ruleId:o,selector:p(e),html:m(e),impact:"serious",message:`Insufficient${r==="AAA"?" enhanced":""} color contrast ratio of ${g}:1 (required ${n}:1).`,context:`foreground: ${_(b)} rgb(${b.join(", ")}), background: gradient, ratio: ${g}:1, required: ${n}:1`,fix:{type:"suggest",suggestion:`Change the text color or gradient background so the contrast ratio meets ${n}:1. The current foreground is ${_(b)}.`}}}function Ze(e,a,t){const i=[],n=e.body;if(!n)return[];const o=e.createTreeWalker(n,NodeFilter.SHOW_TEXT),r=new Set;let s;for(;s=o.nextNode();){if(!s.textContent||!s.textContent.trim()||_a(s.textContent))continue;const l=s.parentElement;if(!l||r.has(l)||(r.add(l),ja.has(l.tagName)))continue;const d=l.tagName;if(d==="BODY"||d==="HTML"||Ba(l)||Oa(l)||Va(l,e)||Ga(l)||Ua(l))continue;const c=x(l);if(parseFloat(c.opacity)===0)continue;const u=$t(l);if(u<.1)continue;const b=c.textShadow;let g=null;if(b&&b!=="none"&&b!=="initial"&&(g=Nt(b),!g)||Ja(l)||Ht(l))continue;const f=T(c.color);if(!f)continue;const v=U(c.color);if(v===0||Lt(l))continue;const w=t==="AAA"?Se(l)?4.5:7:Se(l)?3:4.5;let A=ke(l);if(!A){if(g)continue;const L=Ka(l);if(L){const O=L.gradientEl.parentElement?ke(L.gradientEl.parentElement):null,P=Qa(l,f,v,u,w,a,t,L.bgImage,O??[255,255,255]);P&&i.push(P)}continue}let S=f;v<1&&(S=C(f,A,v)),u<1&&(S=C(S,A,u));const ct=q(S[0],S[1],S[2]),dt=q(A[0],A[1],A[2]),ve=g?Mt(S,A,g):$(ct,dt);if(ve<w){const L=Math.round(ve*100)/100,O=_(S),P=_(A);i.push({ruleId:a,selector:p(l),html:m(l),impact:"serious",message:`Insufficient${t==="AAA"?" enhanced":""} color contrast ratio of ${L}:1 (required ${w}:1).`,context:`foreground: ${O} rgb(${S.join(", ")}), background: ${P} rgb(${A.join(", ")}), ratio: ${L}:1, required: ${w}:1`,fix:{type:"suggest",suggestion:`Change the text color or background color so the contrast ratio meets ${w}:1. Current foreground is ${O}, background is ${P}.`}})}}return i}const Za={id:"distinguishable/color-contrast",category:"distinguishable",actRuleIds:["afw4f7"],wcag:["1.4.3"],level:"AA",fixability:"visual",description:"Text elements must have sufficient color contrast against the background.",browserHint:"Violation context includes computed colors and ratio. After changing colors, use JavaScript to read getComputedStyle() on the element and recalculate the contrast ratio. Screenshot the element to verify the fix looks correct in context.",guidance:"WCAG SC 1.4.3 requires a contrast ratio of at least 4.5:1 for normal text and 3:1 for large text (>=24px or >=18.66px bold). Increase the contrast by darkening the text or lightening the background, or vice versa.",run(e){return Ze(e,"distinguishable/color-contrast","AA")}},ei={id:"distinguishable/color-contrast-enhanced",category:"distinguishable",actRuleIds:["09o5cg"],wcag:["1.4.6"],level:"AAA",fixability:"visual",description:"Text elements must have enhanced color contrast against the background (WCAG AAA).",browserHint:"Violation context includes computed colors and ratio. After changing colors, use JavaScript to read getComputedStyle() on the element and recalculate the contrast ratio. Screenshot the element to verify the fix looks correct in context.",guidance:"WCAG SC 1.4.6 (AAA) requires a contrast ratio of at least 7:1 for normal text and 4.5:1 for large text (>=24px or >=18.66px bold). Higher contrast benefits users with low vision, aging eyes, or poor screen conditions. Increase the contrast by darkening the text or lightening the background, or vice versa.",run(e){return Ze(e,"distinguishable/color-contrast-enhanced","AAA")}},ti={id:"keyboard-accessible/server-image-map",selector:"img[ismap], input[type='image'][ismap]",check:{type:"selector-exists"},impact:"minor",message:"Server-side image map detected. Use client-side image map with <map> and <area> elements instead.",description:"Server-side image maps must not be used.",wcag:["2.1.1"],level:"A",fixability:"contextual",guidance:"Server-side image maps (using ismap attribute) send click coordinates to the server, which is inaccessible to keyboard users and screen readers who can't precisely click specific regions. Replace with client-side image maps (<map> with <area> elements) that provide keyboard access and accessible names, or use linked images/buttons instead.",fix:{type:"remove-attribute",attribute:"ismap"}},ai=E(ti),ii={id:"keyboard-accessible/tabindex",selector:"[tabindex]",check:{type:"attribute-value",attribute:"tabindex",operator:">",value:0},impact:"serious",message:'Element has tabindex="{{value}}" which disrupts tab order.',description:"Elements should not have tabindex greater than 0, which disrupts natural tab order.",wcag:[],level:"A",tags:["best-practice"],fixability:"mechanical",guidance:"Positive tabindex values force elements to the front of the tab order regardless of DOM position, creating unpredictable navigation for keyboard users. Use tabindex='0' to add elements to the natural tab order, or tabindex='-1' to make elements programmatically focusable but not in tab order. Rely on DOM order for tab sequence.",fix:{type:"set-attribute",attribute:"tabindex",value:"0"}},ni=E(ii),oi=new Set(["div","span","p","section","article","header","footer","main","nav","aside","h1","h2","h3","h4","h5","h6","ul","ol","li","dl","dt","dd","table","tr","td","th"]),ri={id:"keyboard-accessible/focus-order",category:"keyboard-accessible",wcag:[],tags:["best-practice"],level:"A",fixability:"contextual",description:"Non-interactive elements with tabindex='0' must have an interactive ARIA role so assistive technologies can convey their purpose.",guidance:"When adding tabindex='0' to non-interactive elements like <div> or <span>, screen readers announce them generically. Add an appropriate role (button, link, tab, etc.) so users understand the element's purpose. Also add keyboard event handlers (Enter/Space for buttons, Enter for links). Consider using native interactive elements instead.",run(e){const a=[];for(const t of e.querySelectorAll('[tabindex="0"]')){const i=t.tagName.toLowerCase();if(!oi.has(i))continue;t.getAttribute("role")||a.push({ruleId:"keyboard-accessible/focus-order",selector:p(t),html:m(t),impact:"moderate",message:`Non-interactive <${i}> with tabindex="0" has no interactive role.`})}return a}},si=new Set(["a","audio","button","img","input","select","textarea","video"]),li=new Set(["button","checkbox","combobox","gridcell","link","listbox","menu","menubar","menuitem","menuitemcheckbox","menuitemradio","option","progressbar","radio","scrollbar","searchbox","slider","spinbutton","switch","tab","tabpanel","textbox","treeitem"]),ci={grid:new Set(["gridcell","row","columnheader","rowheader"]),listbox:new Set(["option"]),menu:new Set(["menuitem","menuitemcheckbox","menuitemradio"]),menubar:new Set(["menuitem","menuitemcheckbox","menuitemradio"]),radiogroup:new Set(["radio"]),tablist:new Set(["tab"]),tree:new Set(["treeitem"]),treegrid:new Set(["gridcell","row","columnheader","rowheader","treeitem"])};function di(e,a){var n,o,r;const t=(n=e.getAttribute("role"))==null?void 0:n.toLowerCase(),i=(o=a.getAttribute("role"))==null?void 0:o.toLowerCase();return!t||!i?!1:((r=ci[t])==null?void 0:r.has(i))??!1}function ui(e){var n;const a=e.tagName.toLowerCase();if(si.has(a))return a==="a"&&!e.hasAttribute("href")?!1:a==="audio"||a==="video"?e.hasAttribute("controls"):!(a==="img"&&!e.hasAttribute("usemap")||a==="input"&&e.type==="hidden"||e.disabled);const t=(n=e.getAttribute("role"))==null?void 0:n.toLowerCase();if(t&&li.has(t))return!0;const i=e.getAttribute("tabindex");return i!==null&&i!=="-1"||e.getAttribute("contenteditable")==="true"}function mi(e){const a=e.tagName.toLowerCase();return!!(a==="a"&&e.hasAttribute("href")||a==="button"&&!e.disabled)}const pi={id:"keyboard-accessible/nested-interactive",category:"keyboard-accessible",wcag:["4.1.2"],level:"A",fixability:"contextual",description:"Interactive controls must not be nested inside each other.",guidance:"Nesting interactive elements (like a button inside a link, or a link inside a button) creates unpredictable behavior and confuses assistive technologies. The browser may remove the inner element from the accessibility tree. Restructure the HTML so interactive elements are siblings, not nested. If you need a clickable card, use CSS and JavaScript rather than nesting.",run(e){const a=[],t=e.body??e;if(!t)return a;const n=(e.body?e:e.ownerDocument).createTreeWalker(t,NodeFilter.SHOW_ELEMENT),o=[];let r=n.currentNode;for(;r;){for(;o.length>0&&!o[o.length-1].contains(r);)o.pop();if(!h(r)&&ui(r)){if(o.length>0){const s=o[o.length-1];di(s,r)||a.push({ruleId:"keyboard-accessible/nested-interactive",selector:p(r),html:m(r),impact:"serious",message:`Interactive element <${r.tagName.toLowerCase()}> is nested inside <${s.tagName.toLowerCase()}>.`,fix:{type:"suggest",suggestion:"Move the nested interactive element outside its interactive parent so they are siblings instead of nested"}})}mi(r)&&o.push(r)}r=n.nextNode()}return a}},bi={id:"keyboard-accessible/scrollable-region",category:"keyboard-accessible",actRuleIds:["0ssw9k"],wcag:["2.1.1"],level:"A",fixability:"contextual",browserHint:"Tab to the scrollable region and verify keyboard scrolling works with arrow keys.",description:"Scrollable regions must be keyboard accessible.",guidance:"Content that scrolls must be accessible to keyboard users. If a region has overflow:scroll or overflow:auto and contains scrollable content, it needs either tabindex='0' to be focusable, or it must contain focusable elements. Without this, keyboard users cannot scroll the content.",run(e){var t;const a=[];for(const i of e.querySelectorAll("*")){if(h(i)||!(i instanceof HTMLElement))continue;const n=i.tagName.toLowerCase();if(n==="body"||n==="html")continue;const o=i.getAttribute("role");if(o==="presentation"||o==="none"||o==="listbox"||o==="menu"||o==="tree"||o==="tabpanel")continue;const r=x(i),s=r.overflowX,l=r.overflowY;if(!(s==="scroll"||s==="auto"||l==="scroll"||l==="auto"))continue;if(i.scrollHeight>0||i.clientHeight>0){const g=i.scrollHeight-i.clientHeight,f=i.scrollWidth-i.clientWidth;if(g<=0&&f<=0||g<14&&f<14||i.clientWidth<64&&i.clientHeight<64)continue;const v=((t=i.textContent)==null?void 0:t.trim().length)??0,w=i.querySelector("img, svg, video, canvas, picture")!==null;if(v===0&&!w)continue}else continue;const u=i.getAttribute("tabindex");u!==null&&u!=="-1"||i.querySelector(W)||a.push({ruleId:"keyboard-accessible/scrollable-region",selector:p(i),html:m(i),impact:"serious",message:"Scrollable region is not keyboard accessible. Add tabindex='0' or include focusable elements."})}return a}},hi={id:"keyboard-accessible/accesskeys",category:"keyboard-accessible",wcag:[],level:"A",tags:["best-practice"],fixability:"mechanical",description:"Accesskey attribute values must be unique.",guidance:"When multiple elements share the same accesskey, browser behavior becomes unpredictable - usually only the first element is activated. Ensure each accesskey value is unique within the page. Also consider that accesskeys can conflict with browser and screen reader shortcuts, so use them sparingly.",run(e){var i;const a=[],t=new Map;for(const n of e.querySelectorAll("[accesskey]")){if(h(n))continue;const o=(i=n.getAttribute("accesskey"))==null?void 0:i.trim().toLowerCase();if(!o)continue;const r=t.get(o)||[];r.push(n),t.set(o,r)}for(const[n,o]of t)if(o.length>1)for(const r of o.slice(1))a.push({ruleId:"keyboard-accessible/accesskeys",selector:p(r),html:m(r),impact:"serious",message:`Duplicate accesskey "${n}". Each accesskey must be unique.`});return a}},gi={id:"keyboard-accessible/focus-visible",category:"keyboard-accessible",actRuleIds:["oj04fd"],wcag:["2.4.7"],level:"AA",fixability:"visual",browserHint:"Tab to the element and screenshot to verify a visible focus indicator appears. Check that the indicator has sufficient contrast against the background.",description:"Elements in sequential focus order must have a visible focus indicator.",guidance:"Keyboard users need to see which element has focus. Do not remove the default focus outline (outline: none) without providing an alternative visible indicator. Use :focus-visible or :focus styles to ensure focus is always perceivable.",run(e){const a=[];for(const t of e.querySelectorAll(W)){if(h(t)||!(t instanceof HTMLElement))continue;const i=t.getAttribute("style")||"";if(/outline\s*:\s*(none|0)\s*(;|$|!)/i.test(i)){const o=/border\s*:/i.test(i),r=/box-shadow\s*:/i.test(i);!o&&!r&&a.push({ruleId:"keyboard-accessible/focus-visible",selector:p(t),html:m(t),impact:"serious",message:"Focusable element has outline removed without a visible focus alternative."})}}return a}};function et(e){if(!(e instanceof HTMLElement))return!1;if(e.style.display==="none"||e.style.visibility==="hidden")return!0;const a=e.getAttribute("width"),t=e.getAttribute("height");return(a==="0"||a==="1")&&(t==="0"||t==="1")}function tt(e){const a=e.match(/^(\d+)/);if(!a)return null;const t=parseInt(a[1],10),i=/^\d+\s*[;,]\s*url\s*=/i.test(e)||/^\d+\s*[;,]\s*['"]?\s*https?:/i.test(e);return{seconds:t,hasValidUrl:i}}const at='article, aside, main, nav, section, [role="article"], [role="complementary"], [role="main"], [role="navigation"], [role="region"]',Me='main, [role="main"], header, [role="banner"], footer, [role="contentinfo"], nav, [role="navigation"], aside, [role="complementary"], section[aria-label], section[aria-labelledby], [role="region"][aria-label], [role="region"][aria-labelledby], form[aria-label], form[aria-labelledby], [role="form"][aria-label], [role="form"][aria-labelledby], [role="search"]';function it(e){return{id:e.id,category:e.id.split("/")[0],wcag:[],level:"A",tags:["best-practice","page-level"],fixability:"contextual",description:e.description,guidance:e.guidance,run(a){const t=[];for(const i of a.querySelectorAll(e.selector))i.closest(at)&&t.push({ruleId:e.id,selector:p(i),html:m(i),impact:"moderate",message:`${e.landmarkName} landmark is nested within another landmark.`});return t}}}function be(e){return{id:e.id,category:e.id.split("/")[0],wcag:[],level:"A",tags:["best-practice","page-level"],fixability:"contextual",description:e.description,guidance:e.guidance,run(a){const t=[],i=a.querySelectorAll(e.selector),n=e.filterTopLevel?Array.from(i).filter(o=>!o.closest(at)):Array.from(i);return n.length>1&&n.slice(1).forEach(o=>t.push({ruleId:e.id,selector:p(o),html:m(o),impact:"moderate",message:`Page has multiple ${e.landmarkName} landmarks.`})),t}}}const fi={id:"enough-time/meta-refresh",category:"enough-time",actRuleIds:["bc659a"],wcag:["2.2.1"],level:"A",tags:["page-level"],fixability:"mechanical",description:"Meta refresh must not redirect or refresh automatically.",guidance:"Automatic page refreshes or redirects can disorient users, especially those using screen readers or with cognitive disabilities. They may lose their place or not have time to read content. If a redirect is needed, use a server-side redirect (HTTP 301/302) instead. For timed refreshes, provide user controls.",run(e){for(const a of e.querySelectorAll('meta[http-equiv="refresh"]')){const t=a.getAttribute("content")||"",i=tt(t);if(i){if(i.hasValidUrl)return i.seconds>0&&i.seconds<=72e3?[{ruleId:"enough-time/meta-refresh",selector:p(a),html:m(a),impact:"critical",message:`Page redirects after ${i.seconds} seconds without warning. Use server-side redirect.`,fix:{type:"remove-element"}}]:[];if(i.seconds>0&&i.seconds<=72e3)return[{ruleId:"enough-time/meta-refresh",selector:p(a),html:m(a),impact:"critical",message:`Page auto-refreshes after ${i.seconds} seconds. Provide user control over refresh.`,fix:{type:"remove-element"}}]}}return[]}},vi={id:"enough-time/meta-refresh-no-exception",category:"enough-time",actRuleIds:["bisz58"],wcag:["2.2.1"],level:"A",tags:["page-level"],fixability:"mechanical",description:"Meta refresh must not be used with a delay (no exceptions).",guidance:"Automatic page refreshes and delayed redirects disorient users. Instant redirects (delay=0) are acceptable, but any positive delay is not. Use server-side redirects instead.",run(e){for(const a of e.querySelectorAll('meta[http-equiv="refresh"]')){const t=a.getAttribute("content")||"",i=tt(t);if(i){if(i.hasValidUrl)return i.seconds>0?[{ruleId:"enough-time/meta-refresh-no-exception",selector:p(a),html:m(a),impact:"critical",message:`Page has a ${i.seconds}-second meta refresh delay. Use a server-side redirect instead.`,fix:{type:"remove-element"}}]:[];if(i.seconds>0)return[{ruleId:"enough-time/meta-refresh-no-exception",selector:p(a),html:m(a),impact:"critical",message:`Page has a ${i.seconds}-second meta refresh delay. Remove the auto-refresh or provide user control.`,fix:{type:"remove-element"}}]}}return[]}},yi={id:"enough-time/blink",selector:"blink",check:{type:"selector-exists"},impact:"serious",message:"The <blink> element causes accessibility issues. Remove it entirely.",description:"The <blink> element must not be used.",wcag:["2.2.2"],level:"A",fixability:"mechanical",guidance:"Blinking content can cause seizures in users with photosensitive epilepsy and is distracting for users with attention disorders. The <blink> element is deprecated and should never be used. If you need to draw attention to content, use less intrusive methods like color, borders, or icons.",fix:{type:"remove-element"}},wi=E(yi),xi={id:"enough-time/marquee",selector:"marquee",check:{type:"selector-exists"},impact:"serious",message:"The <marquee> element causes accessibility issues. Replace with static content.",description:"The <marquee> element must not be used.",wcag:["2.2.2"],level:"A",fixability:"mechanical",guidance:"Scrolling or moving content is difficult for many users to read, especially those with cognitive or visual disabilities. The <marquee> element is deprecated. Replace scrolling text with static content. If content must scroll, provide pause/stop controls and ensure it stops after 5 seconds.",fix:{type:"remove-element"}},Ai=E(xi),ki={id:"navigable/document-title",category:"navigable",actRuleIds:["2779a5"],wcag:["2.4.2"],level:"A",tags:["page-level"],fixability:"contextual",description:"Documents must have a <title> element to provide users with an overview of content.",guidance:"Screen reader users rely on page titles to identify and navigate between tabs/windows. Add a descriptive <title> element in <head> that summarizes the page purpose. Keep titles unique across the site, placing specific content before the site name (e.g., 'Contact Us - Acme Corp').",run(e){var t,i,n;const a=e.querySelector("title");if(!a||!((t=a.textContent)!=null&&t.trim())){let o;const r=e.querySelector("h1");if((i=r==null?void 0:r.textContent)!=null&&i.trim())o=`h1: "${r.textContent.trim().slice(0,100)}"`;else if(e.body){const s=((n=e.body.textContent)==null?void 0:n.trim().replace(/\s+/g," "))||"";s&&(o=`Page text: "${s.slice(0,150)}"`)}return[{ruleId:"navigable/document-title",selector:"html",html:"<html>",impact:"serious",message:a?"Document <title> element is empty.":"Document is missing a <title> element.",context:o,fix:{type:"add-element",tag:"title",parent:"head",textContent:""}}]}return[]}},Si={id:"navigable/bypass",category:"navigable",actRuleIds:["cf77f2"],wcag:["2.4.1"],level:"A",tags:["best-practice","page-level"],fixability:"contextual",description:"Page must have a mechanism to bypass repeated blocks of content.",guidance:'Keyboard users must be able to skip repetitive content like navigation. Provide a skip link at the top of the page that links to the main content (e.g., <a href="#main">Skip to main content</a>), or use a <main> landmark. Screen readers can jump directly to landmarks, so a properly marked-up <main> element satisfies this requirement.',run(e){if(e.querySelector('main, [role="main"], nav, [role="navigation"], aside, [role="complementary"], header, [role="banner"], footer, [role="contentinfo"], [role="search"], [role="region"]'))return[];const t=e.querySelector('a[href^="#"]');if(t){const o=t.getAttribute("href");if(o&&o.length>1){const r=o.slice(1);if(e.getElementById(r))return[]}}if(e.querySelector("h1, h2, h3, [role='heading']"))return[];const n=[];return n.push("no landmarks (<main>, <nav>, <header>, <footer>)"),n.push("no skip link"),n.push("no headings"),[{ruleId:"navigable/bypass",selector:"html",html:"<html>",impact:"serious",message:"Page has no mechanism to bypass repeated content. Add a <main> landmark or skip link.",context:`Missing: ${n.join(", ")}`}]}},Ii={id:"navigable/page-has-heading-one",category:"navigable",wcag:[],level:"A",tags:["best-practice","page-level"],fixability:"contextual",description:"Page should contain a level-one heading.",guidance:"A level-one heading (<h1> or role='heading' with aria-level='1') helps users understand the page topic and provides a landmark for screen reader navigation. Each page should have at least one level-one heading that describes the main content, typically matching or similar to the page title.",run(e){var r,s,l;const a=e.querySelector("h1");if(a&&y(a))return[];const t=e.querySelectorAll('[role="heading"][aria-level="1"]');for(const d of t)if(y(d))return[];const i=[],n=(s=(r=e.querySelector("title"))==null?void 0:r.textContent)==null?void 0:s.trim();n&&i.push(`Page title: "${n}"`);const o=e.querySelector("main");if(o){const d=((l=o.textContent)==null?void 0:l.trim().replace(/\s+/g," "))||"";d&&i.push(`Main content: "${d.slice(0,100)}"`)}return[{ruleId:"navigable/page-has-heading-one",selector:"html",html:"<html>",impact:"moderate",message:"Page does not contain a level-one heading.",context:i.length>0?i.join(", "):void 0}]}},qi={id:"navigable/heading-order",category:"navigable",wcag:[],level:"A",tags:["best-practice"],fixability:"contextual",browserHint:"Screenshot the page to see the visual hierarchy, then take an accessibility tree snapshot to map heading levels to visual sections.",description:"Heading levels should increase by one; skipping levels (e.g. h2 to h4) makes navigation harder.",guidance:"Screen reader users navigate by headings to understand page structure. Skipping levels (h2 to h4) suggests missing content and creates confusion. Start with h1 for the page title, then use h2 for main sections, h3 for subsections, etc. You can go back up (h3 to h2) when starting a new section.",run(e){const a=[],t=e.querySelectorAll("h1, h2, h3, h4, h5, h6, [role='heading']");let i=0,n=null;for(const o of t){if(h(o))continue;let r;o.hasAttribute("aria-level")?r=parseInt(o.getAttribute("aria-level"),10):r=parseInt(o.tagName[1],10),i>0&&r>i+1&&a.push({ruleId:"navigable/heading-order",selector:p(o),html:m(o),impact:"moderate",message:`Heading level ${r} skipped from level ${i}. Use h${i+1} instead.`,context:n?`Previous heading: ${m(n)}`:void 0,fix:{type:"suggest",suggestion:`Change this heading to an h${i+1} element to maintain proper heading hierarchy`}}),i=r,n=o}return a}},Ei={id:"navigable/empty-heading",category:"navigable",actRuleIds:["ffd0e9"],wcag:["2.4.6"],level:"A",tags:["best-practice"],fixability:"contextual",browserHint:"Screenshot the heading area to verify it's visually empty, then add meaningful text or remove the heading element.",description:"Headings must have discernible text.",guidance:"Screen reader users navigate pages by headings, so empty headings create confusing navigation points. Ensure all headings contain visible text or accessible names. If a heading is used purely for visual styling, use CSS instead of heading elements.",run(e){var i;const a=[],t=e.querySelectorAll('h1, h2, h3, h4, h5, h6, [role="heading"]');for(const n of t)if(!h(n)&&!y(n)){let o;const r=n.nextElementSibling;if(r){const s=((i=r.textContent)==null?void 0:i.trim().replace(/\s+/g," "))||"";s&&(o=s.slice(0,100))}a.push({ruleId:"navigable/empty-heading",selector:p(n),html:m(n),impact:"minor",message:"Heading is empty. Add text content or remove the heading element.",context:o?`Following content: "${o}"`:void 0,fix:{type:"add-text-content"}})}return a}},Li={id:"navigable/p-as-heading",category:"navigable",wcag:["1.3.1"],level:"A",tags:["best-practice"],fixability:"contextual",browserHint:"Screenshot the page to verify the paragraph visually functions as a heading and choose the correct heading level.",description:"Paragraphs should not be styled to look like headings.",guidance:"When paragraphs are styled with bold, large fonts to look like headings, screen reader users miss the semantic structure. Use proper heading elements (h1-h6) instead of styled paragraphs. If you need specific styling, apply CSS to the heading elements while maintaining proper heading hierarchy.",run(e){var t,i;const a=[];for(const n of e.querySelectorAll("p")){if(h(n))continue;const o=n.getAttribute("style")||"",r=/font-weight\s*:\s*(bold|[6-9]00)/i.test(o),s=/font-size\s*:\s*(\d+)\s*(px|em|rem)/i.test(o),l=((t=n.className)==null?void 0:t.toLowerCase())||"",d=/\bh[1-6]\b|\bheading\b/.test(l),c=((i=n.textContent)==null?void 0:i.trim())||"",u=c.length>0&&c.length<50,b=!c.match(/[.!?,;:]$/);if((r&&s||r&&d)&&u&&b){const v=n.nextElementSibling;v&&(v.tagName==="P"||v.tagName==="DIV"||v.tagName==="UL")&&a.push({ruleId:"navigable/p-as-heading",selector:p(n),html:m(n),impact:"serious",message:"Paragraph appears to be styled as a heading. Use an h1-h6 element instead.",fix:{type:"suggest",suggestion:"Replace the <p> element with the appropriate heading level (h1-h6) based on the document outline. Preserve the text content and move any inline styles to a CSS class on the new heading element."}})}}return a}};function Ri(e){var n,o;const a=[],t=e.getAttribute("href");t&&a.push(`href: ${t}`);const i=e.parentElement;if(i){const r=i.closest("h1, h2, h3, h4, h5, h6");if((n=r==null?void 0:r.textContent)!=null&&n.trim())a.push(`Nearby heading: ${r.textContent.trim().slice(0,80)}`);else{const s=(o=i.textContent)==null?void 0:o.trim().slice(0,100);s&&a.push(`Parent text: ${s}`)}}return a.length>0?a.join(`
|
|
2
|
+
`):void 0}const Ut={id:"text-alternatives/img-alt",category:"text-alternatives",actRuleIds:["23a2a8"],wcag:["1.1.1"],level:"A",fixability:"contextual",browserHint:"Screenshot the image to describe its visual content for alt text.",description:`Images must have alternate text. Add an alt attribute to <img> elements. Decorative images may use an empty alt attribute (alt=""), role='none', or role='presentation'.`,guidance:"Every image needs an alt attribute. For informative images, describe the content or function concisely. For decorative images (backgrounds, spacers, purely visual flourishes), use alt='' to hide them from screen readers. Never omit alt entirely—screen readers may read the filename instead. When an image is inside a link or button that already has text, use alt='' if the image is decorative in that context, or write alt text that complements (not duplicates) the existing text.",run(e){const a=[];for(const t of e.querySelectorAll("img")){if(h(t)||Ue(t))continue;const i=t.getAttribute("role");if(i==="presentation"||i==="none"){const o=t.getAttribute("tabindex");if(!o||o==="-1")continue}const n=t.getAttribute("alt");if(n!==null&&n.trim()===""&&n!==""){a.push({ruleId:"text-alternatives/img-alt",selector:p(t),html:m(t),impact:"critical",message:'Image has whitespace-only alt text. Use alt="" for decorative images or provide descriptive text.',context:qe(t),fix:{type:"set-attribute",attribute:"alt",value:""}});continue}!t.hasAttribute("alt")&&!y(t)&&a.push({ruleId:"text-alternatives/img-alt",selector:p(t),html:m(t),impact:"critical",message:"Image element missing alt attribute.",context:qe(t),fix:{type:"add-attribute",attribute:"alt",value:""}})}return a}};function Ot(e){var i;const a=Oe(e);if(a)return a;const t=e.querySelector("title");return(i=t==null?void 0:t.textContent)!=null&&i.trim()?t.textContent.trim():""}const Vt={id:"text-alternatives/svg-img-alt",category:"text-alternatives",actRuleIds:["7d6734"],wcag:["1.1.1"],level:"A",fixability:"contextual",browserHint:"Screenshot the SVG to understand its content, then add a title element or aria-label.",description:"SVG elements with an img, graphics-document, or graphics-symbol role must have an accessible name via a <title> element, aria-label, or aria-labelledby.",guidance:"Inline SVGs with role='img' need accessible names. Add a <title> element as the first child of the SVG (screen readers will announce it), or use aria-label on the SVG element. For complex SVGs, use aria-labelledby referencing both a <title> and <desc> element. Decorative SVGs should use aria-hidden='true' instead.",run(e){const a=[],t='svg[role="img"], [role="graphics-document"], [role="graphics-symbol"]';for(const i of e.querySelectorAll(t)){if(h(i))continue;if(!Ot(i)){const o=i.getAttribute("role");a.push({ruleId:"text-alternatives/svg-img-alt",selector:p(i),html:m(i),impact:"serious",message:`${i.tagName.toLowerCase()} with role='${o}' has no accessible name.`,fix:{type:"add-attribute",attribute:"aria-label",value:""}})}}return a}},Bt={id:"text-alternatives/input-image-alt",category:"text-alternatives",actRuleIds:["59796f"],wcag:["1.1.1","4.1.2"],level:"A",fixability:"contextual",browserHint:"Screenshot the image button to see its icon, then set alt to describe the action (e.g., 'Search', 'Submit').",description:'Image inputs (<input type="image">) must have alternate text describing the button action.',guidance:"Image buttons (<input type='image'>) act as submit buttons with a custom image. Add alt text via alt, aria-label, or aria-labelledby that describes the action (e.g. alt='Search' or alt='Submit order'), not the image itself. Without it, screen readers announce only 'image' or the filename, giving no clue what the button does.",run(e){const a=[];for(const t of e.querySelectorAll('input[type="image"]'))h(t)||y(t)||a.push({ruleId:"text-alternatives/input-image-alt",selector:p(t),html:m(t),impact:"critical",message:"Image input missing alt text.",fix:{type:"add-attribute",attribute:"alt",value:""}});return a}},_t={id:"text-alternatives/image-redundant-alt",category:"text-alternatives",wcag:["1.1.1"],level:"A",tags:["best-practice"],fixability:"contextual",description:"Image alt text should not duplicate adjacent link or button text. When alt text repeats surrounding text, screen reader users hear the same information twice.",guidance:"When an image is inside a link or button that also has text, make the alt text complementary rather than identical. If the image is purely decorative in that context, use alt='' to avoid repetition.",run(e){var t;const a=[];for(const i of e.querySelectorAll("img[alt]")){const n=i.getAttribute("alt").trim().toLowerCase();if(!n)continue;const o=i.closest("a, button");if(o){const r=((t=o.textContent)==null?void 0:t.trim().toLowerCase())||"";if(r&&r===n){const s=o.tagName.toLowerCase(),l=o.getAttribute("href");a.push({ruleId:"text-alternatives/image-redundant-alt",selector:p(i),html:m(i),impact:"minor",message:`Alt text "${i.getAttribute("alt")}" duplicates surrounding ${s} text.`,context:`Duplicated text: "${i.getAttribute("alt")}", parent element: <${s}>${l?` href="${l}"`:""}`,fix:{type:"suggest",suggestion:'Set alt="" if the image is decorative in this context, or provide complementary alt text that adds information the visible text does not convey'}})}}}return a}},Gt=/^(image|picture|photo|graphic|icon|img)(\s+of\b|\s*[:\u2013\u2014-]|\s*$)/i,Yt={id:"text-alternatives/image-alt-words",category:"text-alternatives",wcag:["1.1.1"],level:"A",tags:["best-practice"],fixability:"contextual",browserHint:"Screenshot the image to verify the alt text accurately describes it without filler words like 'image of'.",description:"Image alt text should not start with words like 'image of', 'photo of', or 'picture of' — screen readers already announce the element type.",guidance:"Screen readers already announce 'image' or 'graphic' before reading alt text, so phrases like 'image of', 'photo of', or 'picture of' are redundant. Remove these words and describe what the image shows. For example, change 'image of a dog' to 'golden retriever playing fetch'.",run(e){const a=[];for(const t of e.querySelectorAll("img[alt]")){const i=t.getAttribute("alt").trim();if(!i)continue;const n=i.match(Gt);if(n){const o=n[1].toLowerCase();a.push({ruleId:"text-alternatives/image-alt-words",selector:p(t),html:m(t),impact:"minor",message:`Alt text "${i}" starts with redundant prefix "${o}".`,context:`Current alt: "${i}", redundant prefix: "${o}"`,fix:{type:"suggest",suggestion:"Remove the redundant prefix from the alt text; screen readers already announce the element as an image"}})}}return a}},Xt={id:"text-alternatives/area-alt",category:"text-alternatives",wcag:["1.1.1","4.1.2"],level:"A",fixability:"contextual",description:"Image map <area> elements must have alternative text.",guidance:"Each clickable region in an image map needs alternative text so screen reader users know what the region represents. Add an alt attribute to every <area> element describing its purpose. For complex image maps, consider using alternative approaches like SVG with embedded links, or a list of text links.",run(e){const a=[];for(const t of e.querySelectorAll("area[href]")){if(h(t))continue;y(t)||a.push({ruleId:"text-alternatives/area-alt",selector:p(t),html:m(t),impact:"critical",message:"Image map <area> element is missing alternative text.",fix:{type:"add-attribute",attribute:"alt",value:""}})}return a}},Jt={id:"text-alternatives/object-alt",category:"text-alternatives",actRuleIds:["8fc3b6"],wcag:["1.1.1"],level:"A",fixability:"contextual",browserHint:"Screenshot the embedded object to see its content, then add aria-label or title describing it.",description:"<object> elements must have alternative text.",guidance:"Object elements embed external content that may not be accessible to all users. Provide alternative text via aria-label, aria-labelledby, or a title attribute. The fallback content inside <object> is only shown when the object fails to load and does not serve as an accessible name.",run(e){var t;const a=[];for(const i of e.querySelectorAll("object")){if(h(i)||Ue(i)||i.getAttribute("role")==="presentation"||i.getAttribute("role")==="none"||Oe(i))continue;const n=i.getAttribute("data")||"";if(!((i.getAttribute("type")||"").startsWith("image/")||/\.(png|jpg|jpeg|gif|svg|webp|bmp|ico)$/i.test(n))){const s=i.querySelector("img[alt]");if(s&&((t=s.getAttribute("alt"))!=null&&t.trim()))continue}a.push({ruleId:"text-alternatives/object-alt",selector:p(i),html:m(i),impact:"serious",message:"<object> element is missing alternative text. Add aria-label, aria-labelledby, or a title attribute.",fix:{type:"add-attribute",attribute:"aria-label",value:""}})}return a}},Kt={id:"text-alternatives/role-img-alt",category:"text-alternatives",actRuleIds:["23a2a8"],wcag:["1.1.1"],level:"A",fixability:"contextual",browserHint:"Screenshot the element to see its visual appearance, then provide an aria-label describing what it represents.",description:"Elements with role='img' must have an accessible name.",guidance:"When you assign role='img' to an element (like a div containing icon fonts or CSS backgrounds), you must provide an accessible name via aria-label or aria-labelledby. Without this, screen reader users have no way to understand what the image represents. If the image is decorative, use role='presentation' or role='none' instead.",run(e){const a=[];for(const t of e.querySelectorAll('[role="img"]')){if(h(t)||t.tagName.toLowerCase()==="svg"||t.tagName.toLowerCase()==="img")continue;y(t)||a.push({ruleId:"text-alternatives/role-img-alt",selector:p(t),html:m(t),impact:"serious",message:"Element with role='img' has no accessible name. Add aria-label or aria-labelledby.",fix:{type:"add-attribute",attribute:"aria-label",value:""}})}return a}},Qt={id:"time-based-media/video-captions",category:"time-based-media",actRuleIds:["eac66b"],wcag:["1.2.2"],level:"A",fixability:"contextual",browserHint:"Screenshot the video element to see its poster or content for context when writing captions.",description:"Video elements must have captions via <track kind='captions'> or <track kind='subtitles'>.",guidance:"Captions provide text alternatives for audio content in videos, benefiting deaf users and those who cannot hear audio. Add a <track> element with kind='captions' pointing to a WebVTT caption file. Captions should include both dialogue and important sound effects.",run(e){const a=[];for(const t of e.querySelectorAll("video")){if(h(t)||I(t)||t.hasAttribute("muted")||t.hasAttribute("autoplay"))continue;t.querySelector('track[kind="captions"], track[kind="subtitles"]')||a.push({ruleId:"time-based-media/video-captions",selector:p(t),html:m(t),impact:"critical",message:"Video element has no captions track."})}return a}},Zt={id:"time-based-media/audio-transcript",category:"time-based-media",actRuleIds:["e7aa44"],wcag:["1.2.1"],level:"A",fixability:"contextual",browserHint:"Inspect the page around the audio element for existing transcript links or associated text content.",description:"Audio elements should have a text alternative or transcript.",guidance:"Audio-only content like podcasts or recordings needs a text alternative for deaf users. Provide a transcript either on the same page or linked nearby. The transcript should include all spoken content and descriptions of relevant sounds.",run(e){const a=[];for(const t of e.querySelectorAll("audio")){if(h(t)||I(t)||t.querySelector('track[kind="captions"], track[kind="descriptions"]')||t.hasAttribute("aria-describedby"))continue;const n=t.parentElement;n&&n.querySelector('a[href*="transcript"], a[href*="text"]')||a.push({ruleId:"time-based-media/audio-transcript",selector:p(t),html:m(t),impact:"critical",message:"Audio element has no transcript or text alternative. Add a transcript or track element."})}return a}},ea=new Set(["off","on","name","honorific-prefix","given-name","additional-name","family-name","honorific-suffix","nickname","email","username","new-password","current-password","one-time-code","organization-title","organization","street-address","address-line1","address-line2","address-line3","address-level4","address-level3","address-level2","address-level1","country","country-name","postal-code","cc-name","cc-given-name","cc-additional-name","cc-family-name","cc-number","cc-exp","cc-exp-month","cc-exp-year","cc-csc","cc-type","transaction-currency","transaction-amount","language","bday","bday-day","bday-month","bday-year","sex","tel","tel-country-code","tel-national","tel-area-code","tel-local","tel-extension","impp","url","photo"]),ta=new Set(["tel","tel-country-code","tel-national","tel-area-code","tel-local","tel-extension","email","impp"]),aa=new Set(["home","work","mobile","fax","pager"]),ia=new Set(["shipping","billing"]),na=new Set(["webauthn"]);function oa(e){const a=e.toLowerCase().split(/\s+/).filter(Boolean);if(a.length===0)return!0;let t=0;a[t].startsWith("section-")&&t++,t<a.length&&ia.has(a[t])&&t++;let i=!1;if(t<a.length&&aa.has(a[t])&&(i=!0,t++),t>=a.length)return!1;const n=a[t];return!ea.has(n)||i&&!ta.has(n)?!1:(t++,t<a.length&&na.has(a[t])&&t++,t===a.length)}const ra={id:"adaptable/autocomplete-valid",category:"adaptable",actRuleIds:["73f2c2"],wcag:["1.3.5"],level:"AA",fixability:"contextual",description:"Autocomplete attribute must use valid values from the HTML specification.",guidance:"The autocomplete attribute helps users fill forms by identifying input purposes. Use standard values like 'name', 'email', 'tel', 'street-address', 'postal-code', 'cc-number'. This benefits users with cognitive disabilities, motor impairments, and anyone using password managers or autofill. Check the HTML specification for the complete list of valid tokens.",run(e){const a=[];for(const t of e.querySelectorAll("[autocomplete]")){if(h(t)||I(t)||t.disabled||t.getAttribute("aria-disabled")==="true")continue;const i=t.getAttribute("autocomplete").trim();i&&(oa(i)||a.push({ruleId:"adaptable/autocomplete-valid",selector:p(t),html:m(t),impact:"serious",message:`Invalid autocomplete value "${i}".`}))}return a}};function sa(e){if(typeof e!="object"||e===null)return"Rule spec must be an object";const a=e;if(typeof a.id!="string"||a.id.length===0)return"Rule must have a non-empty string id";if(typeof a.selector!="string"||a.selector.length===0)return"Rule must have a non-empty string selector";if(typeof a.check!="object"||a.check===null)return"Rule must have a check object";const t=a.check;if(!["selector-exists","attribute-value","attribute-missing","attribute-regex","child-required","child-invalid"].includes(t.type))return`Invalid check type: ${String(t.type)}`;if(typeof a.impact!="string"||!["critical","serious","moderate","minor"].includes(a.impact))return"Rule must have a valid impact (critical|serious|moderate|minor)";if(typeof a.message!="string"||a.message.length===0)return"Rule must have a non-empty message";if(typeof a.description!="string")return"Rule must have a description string";if(!Array.isArray(a.wcag))return"Rule must have a wcag array";if(typeof a.level!="string"||!["A","AA"].includes(a.level))return"Rule must have level A or AA";const n=la(t);return n||null}function la(e){switch(e.type){case"selector-exists":return null;case"attribute-value":return typeof e.attribute!="string"?"attribute-value check requires attribute string":[">","<","=","!=","in","not-in"].includes(e.operator)?e.value===void 0?"attribute-value check requires value":null:"attribute-value check requires valid operator";case"attribute-missing":return typeof e.attribute!="string"?"attribute-missing check requires attribute string":null;case"attribute-regex":return typeof e.attribute!="string"?"attribute-regex check requires attribute string":typeof e.pattern!="string"?"attribute-regex check requires pattern string":typeof e.shouldMatch!="boolean"?"attribute-regex check requires shouldMatch boolean":null;case"child-required":return typeof e.childSelector!="string"?"child-required check requires childSelector string":null;case"child-invalid":return Array.isArray(e.allowedChildren)?null:"child-invalid check requires allowedChildren array";default:return`Unknown check type: ${String(e.type)}`}}function R(e,a,t){let i=e;if(i.includes("{{tag}}")&&(i=i.replace(/\{\{tag\}\}/g,a.tagName.toLowerCase())),i.includes("{{value}}")){let n="";"attribute"in t&&t.attribute&&(n=a.getAttribute(t.attribute)??""),i=i.replace(/\{\{value\}\}/g,n)}return i}function E(e){const a=e.skipAriaHidden!==!1;return{id:e.id,category:e.id.split("/")[0],actRuleIds:e.actRuleIds,wcag:e.wcag,level:e.level,tags:e.tags,fixability:e.fixability,browserHint:e.browserHint,description:e.description,guidance:e.guidance,run(t){var n,o;const i=[];switch(e.check.type){case"selector-exists":{for(const r of t.querySelectorAll(e.selector))a&&h(r)||i.push({ruleId:e.id,selector:p(r),html:m(r),impact:e.impact,message:R(e.message,r,e.check),...e.fix?{fix:e.fix}:{},element:r});break}case"attribute-value":{const{attribute:r,operator:s,value:l}=e.check;for(const d of t.querySelectorAll(e.selector)){if(a&&h(d))continue;const c=d.getAttribute(r);c!==null&&ca(c,s,l)&&i.push({ruleId:e.id,selector:p(d),html:m(d),impact:e.impact,message:R(e.message,d,e.check),...e.fix?{fix:e.fix}:{},element:d})}break}case"attribute-missing":{const{attribute:r}=e.check;for(const s of t.querySelectorAll(e.selector))a&&h(s)||s.hasAttribute(r)||i.push({ruleId:e.id,selector:p(s),html:m(s),impact:e.impact,message:R(e.message,s,e.check),...e.fix?{fix:e.fix}:{},element:s});break}case"attribute-regex":{const{attribute:r,pattern:s,flags:l,shouldMatch:d}=e.check;let c;try{c=new RegExp(s,l)}catch{break}for(const u of t.querySelectorAll(e.selector)){if(a&&h(u))continue;const b=u.getAttribute(r);if(b===null)continue;const g=c.test(b);d&&!g?i.push({ruleId:e.id,selector:p(u),html:m(u),impact:e.impact,message:R(e.message,u,e.check),...e.fix?{fix:e.fix}:{},element:u}):!d&&g&&i.push({ruleId:e.id,selector:p(u),html:m(u),impact:e.impact,message:R(e.message,u,e.check),...e.fix?{fix:e.fix}:{},element:u})}break}case"child-required":{const{childSelector:r}=e.check;for(const s of t.querySelectorAll(e.selector))a&&h(s)||s.querySelector(r)||i.push({ruleId:e.id,selector:p(s),html:m(s),impact:e.impact,message:R(e.message,s,e.check),...e.fix?{fix:e.fix}:{},element:s});break}case"child-invalid":{const r=new Set(e.check.allowedChildren.map(l=>l.toLowerCase())),s=e.check.allowedChildRoles?new Set(e.check.allowedChildRoles.map(l=>l.toLowerCase())):null;for(const l of t.querySelectorAll(e.selector)){if(a&&h(l))continue;const d=(n=l.getAttribute("role"))==null?void 0:n.trim().toLowerCase();if(d==="presentation"||d==="none")continue;let c=!1;const u=e.check.allowedChildren.filter(b=>b!=="script"&&b!=="template");for(const b of l.childNodes)if(b.nodeType===3&&b.textContent&&b.textContent.trim()){const g=u.map(f=>`<${f}>`).join(" or ");i.push({ruleId:e.id,selector:p(l),html:m(l),impact:e.impact,message:`<${l.tagName.toLowerCase()}> contains direct text content. Wrap in ${g}.`,...e.fix?{fix:e.fix}:{},element:l}),c=!0;break}if(!c)for(const b of l.children){if(r.has(b.tagName.toLowerCase()))continue;const g=(o=b.getAttribute("role"))==null?void 0:o.trim().toLowerCase();if(!(g&&(s!=null&&s.has(g)))&&!(g==="presentation"||g==="none")){i.push({ruleId:e.id,selector:p(b),html:m(b),impact:e.impact,message:R(e.message,b,e.check),...e.fix?{fix:e.fix}:{},element:b});break}}}break}}return i}}}function ca(e,a,t){switch(a){case">":return parseFloat(e)>t;case"<":return parseFloat(e)<t;case"=":return e===String(t);case"!=":return e!==String(t);case"in":return Array.isArray(t)&&t.includes(e);case"not-in":return Array.isArray(t)&&!t.includes(e);default:return!1}}const da={id:"adaptable/list-children",selector:"ul, ol",check:{type:"child-invalid",allowedChildren:["li","script","template","style"],allowedChildRoles:["listitem"]},impact:"serious",message:"List contains non-<li> child <{{tag}}>.",description:"<ul> and <ol> must only contain <li>, <script>, <template>, or <style> as direct children.",wcag:["1.3.1"],level:"A",fixability:"contextual",guidance:"Screen readers announce list structure ('list with 5 items') based on proper markup. Placing non-<li> elements directly inside <ul> or <ol> breaks this structure. Wrap content in <li> elements, or if you need wrapper divs for styling, apply styles to <li> elements directly and remove the wrapper (e.g., change <ul><div>item</div></ul> to <ul><li>item</li></ul>)."},ua=E(da),ma={id:"adaptable/listitem-parent",category:"adaptable",wcag:["1.3.1"],level:"A",fixability:"contextual",description:"<li> elements must be contained in a <ul>, <ol>, or <menu>.",guidance:"List items (<li>) only have semantic meaning inside a list container (<ul>, <ol>, or <menu>). Without a list parent, screen readers will not announce 'list with N items' or allow users to skip between items using list navigation shortcuts. Wrap <li> elements in the appropriate list container — <ul> for unordered lists, <ol> for ordered/numbered lists.",run(e){var t;const a=[];for(const i of e.querySelectorAll("li")){if(h(i))continue;const n=i.parentElement;if(!n)continue;const o=n.tagName.toLowerCase();o==="ul"||o==="ol"||o==="menu"||((t=n.getAttribute("role"))==null?void 0:t.trim().toLowerCase())==="list"||a.push({ruleId:"adaptable/listitem-parent",selector:p(i),html:m(i),impact:"serious",message:"<li> is not contained in a <ul>, <ol>, or <menu>."})}return a}},pa={id:"adaptable/dl-children",category:"adaptable",wcag:["1.3.1"],level:"A",fixability:"contextual",description:"<dt> and <dd> elements must be contained in a <dl>.",guidance:"Definition terms (<dt>) and definitions (<dd>) only have semantic meaning inside a definition list (<dl>). Outside of <dl>, they're treated as generic text. Wrap related <dt> and <dd> pairs in a <dl> element to convey the term/definition relationship to assistive technologies.",run(e){var t;const a=[];for(const i of e.querySelectorAll("dt, dd")){const n=i.parentElement,o=n==null?void 0:n.tagName.toLowerCase();(!n||o!=="dl"&&!(o==="div"&&((t=n.parentElement)==null?void 0:t.tagName.toLowerCase())==="dl"))&&a.push({ruleId:"adaptable/dl-children",selector:p(i),html:m(i),impact:"serious",message:`<${i.tagName.toLowerCase()}> is not contained in a <dl>.`})}return a}},ba={id:"adaptable/definition-list",selector:"dl",check:{type:"child-invalid",allowedChildren:["dt","dd","div","script","template","style"]},impact:"serious",message:"<dl> contains invalid child <{{tag}}>.",description:"<dl> elements must only contain <dt>, <dd>, <div>, <script>, <template>, or <style>.",wcag:["1.3.1"],level:"A",fixability:"contextual",guidance:"Definition lists have strict content requirements. Only <dt> (terms), <dd> (definitions), and <div> (for grouping dt/dd pairs) are valid children. Other elements break the list structure for screen readers. Move invalid elements outside the <dl>, or if they represent a term change to <dt>, if a definition change to <dd>. Styling wrappers should be replaced with <div> elements containing <dt>/<dd> pairs."},ha=E(ba);function Ge(e,a){switch(a.toLowerCase()){case"deg":return e;case"rad":return e*180/Math.PI;case"turn":return e*360;case"grad":return e*.9;default:return NaN}}function B(e){return isNaN(e)?!1:(e=(e%360+360)%360,e>=85&&e<=95||e>=265&&e<=275)}function ga(e){const a=e.match(/rotate[Z]?\(\s*(-?[\d.]+)(deg|rad|turn|grad)\s*\)/i);if(a){const n=Ge(parseFloat(a[1]),a[2]);if(B(n))return!0}const t=e.match(/matrix\(\s*(-?[\d.e]+)\s*,\s*(-?[\d.e]+)\s*,\s*(-?[\d.e]+)\s*,\s*(-?[\d.e]+)/i);if(t){const n=parseFloat(t[1]),o=parseFloat(t[2]),r=Math.atan2(o,n)*(180/Math.PI);if(B(r))return!0}const i=e.match(/matrix3d\(\s*(-?[\d.e]+)\s*,\s*(-?[\d.e]+)\s*,\s*(-?[\d.e]+)\s*,\s*(-?[\d.e]+)\s*,\s*(-?[\d.e]+)\s*,\s*(-?[\d.e]+)/i);if(i){const n=parseFloat(i[1]),o=parseFloat(i[2]),r=Math.atan2(o,n)*(180/Math.PI);if(B(r))return!0}return!1}function fa(e){const a=e.match(/(-?[\d.]+)(deg|rad|turn|grad)/i);if(!a)return!1;const t=Ge(parseFloat(a[1]),a[2]);return B(t)}const va={id:"adaptable/orientation-lock",category:"adaptable",actRuleIds:["b33eff"],wcag:["1.3.4"],level:"AA",tags:["page-level"],fixability:"contextual",description:"Page orientation must not be restricted using CSS transforms.",guidance:"Users with motor disabilities may mount their device in a fixed orientation. Using CSS transforms with @media (orientation: portrait/landscape) to rotate content 90° effectively locks the page to one orientation. Remove the orientation-dependent transform and use responsive design instead.",run(e){const a=[];for(const t of e.querySelectorAll("style")){const i=t.textContent||"",n=/@media[^{]*\b(orientation)\s*:\s*(portrait|landscape)\b[^{]*\{([^}]*\{[^}]*\}[^}]*)\}/gi;let o;for(;o=n.exec(i);){const r=o[3];let s=!1;const l=r.match(/transform\s*:\s*([^;]+)/i);if(l&&ga(l[1])&&(s=!0),!s){const d=r.match(/(?:^|[{;\s])rotate\s*:\s*([^;]+)/i);d&&fa(d[1])&&(s=!0)}s&&a.push({ruleId:"adaptable/orientation-lock",selector:p(t),html:m(t),impact:"serious",message:`CSS locks page orientation via @media (orientation: ${o[2]}) with a 90° transform.`})}}return a}},Ee={combobox:[["listbox","tree","grid","dialog","textbox"]],feed:[["article"]],grid:[["row","rowgroup"]],list:[["listitem","group"]],listbox:[["option","group"]],menu:[["menuitem","menuitemcheckbox","menuitemradio","group","menu","separator"]],menubar:[["menuitem","menuitemcheckbox","menuitemradio","group","menu","separator"]],radiogroup:[["radio"]],row:[["cell","columnheader","gridcell","rowheader"]],rowgroup:[["row"]],table:[["row","rowgroup"]],tablist:[["tab"]],tree:[["treeitem","group"]],treegrid:[["row","rowgroup"]]},ya=new Set(["doc-bibliography","doc-endnotes","grid","group","list","listbox","rowgroup","table","tablist","tree","treegrid"]);function wa(e,a){var r;const t=((r=e.getAttribute("aria-owns"))==null?void 0:r.split(/\s+/))||[],i=e.ownerDocument,n=new Set;let o=!1;for(const s of e.querySelectorAll("*")){if(h(s))continue;o=!0;const l=H(s);l&&n.add(l)}for(const s of t){const l=i.getElementById(s);if(l&&!h(l)){o=!0;const d=H(l);d&&n.add(d)}}if(!o)return"empty";for(const s of a)if(!s.some(l=>n.has(l)))return"fail";return"pass"}const xa={id:"adaptable/aria-required-children",category:"adaptable",actRuleIds:["bc4a75"],wcag:["1.3.1"],level:"A",fixability:"contextual",description:"Certain ARIA roles require specific child roles to be present.",guidance:"Some ARIA roles represent containers that must contain specific child roles for proper semantics. For example, a list must contain listitems, a menu must contain menuitems. Add the required child elements with appropriate roles, or use native HTML elements that provide these semantics implicitly (e.g., <ul> with <li>).",run(e){var t;const a=[];for(const i of e.querySelectorAll("[role]")){if(h(i))continue;const n=(t=i.getAttribute("role"))==null?void 0:t.trim().toLowerCase();if(!n||!(n in Ee)||i.getAttribute("aria-busy")==="true")continue;if(n==="combobox"){if(i.getAttribute("aria-expanded")!=="true")continue;const l=i.tagName.toLowerCase();if(l==="input"||l==="textarea")continue}const o=Ee[n],r=wa(i,o);if(r==="pass"||r==="empty"&&ya.has(n))continue;const s=o.map(l=>l.join(" or ")).join(", ");a.push({ruleId:"adaptable/aria-required-children",selector:p(i),html:m(i),impact:"critical",message:`Role "${n}" requires children with role: ${s}.`})}return a}},Le={caption:["figure","table","grid","treegrid"],cell:["row"],columnheader:["row"],gridcell:["row"],listitem:["list","group"],menuitem:["menu","menubar","group"],menuitemcheckbox:["menu","menubar","group"],menuitemradio:["menu","menubar","group"],option:["listbox","group"],row:["table","grid","treegrid","rowgroup"],rowgroup:["table","grid","treegrid"],rowheader:["row"],tab:["tablist"],treeitem:["tree","group"]},Aa={id:"adaptable/aria-required-parent",category:"adaptable",actRuleIds:["ff89c9"],wcag:["1.3.1"],level:"A",fixability:"contextual",description:"Certain ARIA roles must be contained within specific parent roles.",guidance:"Some ARIA roles represent items that must exist within specific container roles. For example, a listitem must be within a list, a tab must be within a tablist. Wrap the element in the appropriate parent, or use native HTML elements that provide this structure (e.g., <li> inside <ul>).",run(e){var t;const a=[];for(const i of e.querySelectorAll("[role]")){if(h(i))continue;const n=(t=i.getAttribute("role"))==null?void 0:t.trim().toLowerCase();if(!n||!(n in Le))continue;const o=Le[n];let r=i.parentElement,s=!1;for(;r&&r!==e.documentElement;){const l=H(r);if(l&&o.includes(l)){s=!0;break}r=r.parentElement}s||a.push({ruleId:"adaptable/aria-required-parent",selector:p(i),html:m(i),impact:"critical",message:`Role "${n}" must be contained within: ${o.join(", ")}.`})}return a}},ka={id:"adaptable/td-headers-attr",category:"adaptable",actRuleIds:["a25f45"],wcag:["1.3.1"],level:"A",fixability:"contextual",description:"All cells in a table using headers attribute must reference valid header IDs.",guidance:"The headers attribute on table cells must reference IDs of header cells (th or td) within the same table. This creates explicit associations for screen readers. Verify all referenced IDs exist and spell them correctly. For simple tables, consider using scope on th elements instead.",run(e){const a=[];for(const t of e.querySelectorAll("td[headers]")){if(h(t))continue;const i=t.closest("table");if(!i)continue;const n=t.getAttribute("id"),o=t.getAttribute("headers").split(/\s+/);for(const r of o){if(r===n){a.push({ruleId:"adaptable/td-headers-attr",selector:p(t),html:m(t),impact:"serious",message:`Headers attribute references the cell itself ("${r}").`});break}if(!i.querySelector(`th#${CSS.escape(r)}, td#${CSS.escape(r)}`)){a.push({ruleId:"adaptable/td-headers-attr",selector:p(t),html:m(t),impact:"serious",message:`Headers attribute references non-existent ID "${r}".`});break}}}return a}},Sa={id:"adaptable/th-has-data-cells",category:"adaptable",actRuleIds:["d0f69e"],wcag:["1.3.1"],level:"A",fixability:"contextual",description:"Table headers should be associated with data cells.",guidance:"Screen readers use <th> elements to announce column or row headers when navigating table cells — for example, reading 'Name: John' when moving to a cell. A table with <th> but no <td> elements means headers describe nothing, and screen readers cannot associate data with headers. Either add <td> data cells, or if this is not tabular data, use non-table markup instead.",run(e){const a=[];for(const t of e.querySelectorAll("table")){if(h(t)||t.getAttribute("role")==="presentation"||t.getAttribute("role")==="none")continue;const i=t.querySelectorAll("th"),n=t.querySelectorAll("td");i.length>0&&n.length===0&&a.push({ruleId:"adaptable/th-has-data-cells",selector:p(t),html:m(t),impact:"serious",message:"Table has header cells but no data cells."})}return a}};function Ia(e){var i;const a=e.getAttribute("role");if(a==="presentation"||a==="none")return!1;if(a==="table"||a==="grid"||a==="treegrid"||e.querySelector("caption")||e.getAttribute("summary")||e.querySelector("thead, tfoot, colgroup")||e.querySelector("th[scope]")||e.querySelector("td[headers]"))return!0;const t=e.querySelectorAll("th");if(t.length===0)return!1;for(const n of t)if((i=n.textContent)!=null&&i.trim())return!0;return!1}function Re(e){let a=0,t=e.previousElementSibling;for(;t;)a+=parseInt(t.getAttribute("colspan")||"1",10),t=t.previousElementSibling;return a}const qa={id:"adaptable/td-has-header",category:"adaptable",wcag:["1.3.1"],level:"A",fixability:"contextual",browserHint:"Screenshot the table to understand its visual layout, then add scope or headers attributes to associate data cells with headers.",description:"Data cells in tables larger than 3x3 should have associated headers.",guidance:"In complex tables, screen reader users need header associations to understand data cells. Use th elements with scope attribute, or the headers attribute on td elements. For simple tables (≤3x3), this is less critical as context is usually clear.",run(e){var t;const a=[];for(const i of e.querySelectorAll("table")){if(h(i)||!Ia(i))continue;const n=i.querySelectorAll("tr"),o=n.length;let r=0;for(const d of n){const c=d.querySelectorAll("td, th");let u=0;for(const b of c)u+=parseInt(b.getAttribute("colspan")||"1",10);r=Math.max(r,u)}if(o<=3&&r<=3)continue;const s=i.querySelector("th[scope]")!==null,l=i.querySelector("td[headers]")!==null;for(const d of i.querySelectorAll("td")){if(h(d)||!((t=d.textContent)!=null&&t.trim())&&!d.querySelector("img, svg, input, select, textarea")||d.hasAttribute("aria-label")||d.hasAttribute("aria-labelledby")||d.hasAttribute("headers"))continue;const c=d.closest("tr");if(!c)continue;const u=c.querySelector("th")!==null,b=Re(d);let g=!1;const f=i.querySelector("thead"),v=(f==null?void 0:f.querySelector("tr"))??i.querySelector("tbody > tr, tr");if(v)for(const w of v.querySelectorAll("th, td")){const A=Re(w),S=parseInt(w.getAttribute("colspan")||"1",10);if(w.tagName.toLowerCase()==="th"&&b>=A&&b<A+S){g=!0;break}}if(!u&&!g&&!s&&!l){a.push({ruleId:"adaptable/td-has-header",selector:p(d),html:m(d),impact:"serious",message:"Data cell has no associated header. Add th elements with scope, or headers attribute."});break}}}return a}},Ea={id:"adaptable/scope-attr-valid",category:"adaptable",wcag:["1.3.1"],level:"A",fixability:"mechanical",description:"The scope attribute on table headers must have a valid value.",guidance:"The scope attribute tells screen readers which cells a header applies to. Valid values are: row, col, rowgroup, colgroup. Using invalid values breaks the association between headers and cells.",run(e){var i;const a=[],t=new Set(["row","col","rowgroup","colgroup"]);for(const n of e.querySelectorAll("th[scope]")){if(h(n))continue;const o=(i=n.getAttribute("scope"))==null?void 0:i.toLowerCase();o&&!t.has(o)&&a.push({ruleId:"adaptable/scope-attr-valid",selector:p(n),html:m(n),impact:"moderate",message:`Invalid scope value "${o}". Use row, col, rowgroup, or colgroup.`})}return a}},La={id:"adaptable/empty-table-header",category:"adaptable",wcag:["1.3.1"],level:"A",tags:["best-practice"],fixability:"contextual",browserHint:"Screenshot the table to see which header cells are visually empty, then add text content or aria-label.",description:"Table header cells should have visible text.",guidance:"Empty table headers provide no information to screen reader users. Either add descriptive text to the header, or if the header is intentionally empty (like a corner cell), consider using a td element instead or adding a visually hidden label.",run(e){const a=[];for(const t of e.querySelectorAll("th")){if(h(t))continue;const i=t.closest("table");(i==null?void 0:i.getAttribute("role"))==="presentation"||(i==null?void 0:i.getAttribute("role"))==="none"||y(t)||a.push({ruleId:"adaptable/empty-table-header",selector:p(t),html:m(t),impact:"minor",message:"Table header cell is empty. Add text or use aria-label."})}return a}},Ra={id:"distinguishable/meta-viewport",category:"distinguishable",actRuleIds:["b4f0c3"],wcag:["1.4.4"],level:"AA",tags:["page-level"],fixability:"mechanical",browserHint:"After fixing the viewport meta tag, resize the viewport to 320px wide and screenshot to verify content remains readable and usable.",description:"Viewport meta tag must not disable user scaling.",guidance:"Users with low vision need to zoom content up to 200% or more. Setting user-scalable=no or maximum-scale=1 prevents zooming and fails WCAG. Remove these restrictions. If your layout breaks at high zoom, fix the responsive design rather than preventing zoom.",run(e){const a=[],t=e.querySelector('meta[name="viewport"]');if(!t)return[];const i=t.getAttribute("content")||"",n=i.toLowerCase(),o=n.match(/user-scalable\s*=\s*([^\s,;]+)/i);if(o){const s=o[1],l=parseFloat(s);(s==="no"||!isNaN(l)&&l>-1&&l<1)&&a.push({ruleId:"distinguishable/meta-viewport",selector:p(t),html:m(t),impact:"critical",message:`Viewport disables user scaling (user-scalable=${s}). Remove this restriction.`,context:`content: "${i}"`,fix:{type:"suggest",suggestion:"Remove user-scalable=no from the viewport meta content attribute"}})}const r=n.match(/maximum-scale\s*=\s*([\d.]+|yes)/i);if(r){const s=r[1],l=s.toLowerCase()==="yes"?1:parseFloat(s);l<2&&a.push({ruleId:"distinguishable/meta-viewport",selector:p(t),html:m(t),impact:"critical",message:`Viewport maximum-scale=${l} restricts zooming. Set to at least 2 or remove.`,context:`content: "${i}"`,fix:{type:"suggest",suggestion:"Remove maximum-scale or set it to at least 2 in the viewport meta content attribute"}})}return a}};function me(e){return e.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&")}function Ye(e,a){const t=e.getAttribute("style");if(!t)return null;const i=new RegExp(`${me(a)}\\s*:\\s*([^;!]+)\\s*!\\s*important`,"gi");let n=null,o;for(;o=i.exec(t);)n=o;if(!n)return null;const r=n[1].trim();if(/^(inherit|unset|revert)$/i.test(r))return null;if(/^(normal|initial)$/i.test(r))return{em:0,px:null};const s=r.match(/^(-?[\d.]+)\s*em$/i);if(s)return{em:parseFloat(s[1]),px:null};const l=r.match(/^(-?[\d.]+)$/);if(l)return{em:parseFloat(l[1]),px:null};const d=r.match(/^(-?[\d.]+)\s*%$/);if(d)return{em:parseFloat(d[1])/100,px:null};const c=r.match(/^(-?[\d.]+)\s*(px|pt|cm|mm|in)$/i);if(c){const u=parseFloat(c[1]),b=c[2].toLowerCase();let g;switch(b){case"px":g=u;break;case"pt":g=u*(4/3);break;case"cm":g=u*(96/2.54);break;case"mm":g=u*(96/25.4);break;case"in":g=u*96;break;default:return null}return{em:null,px:g}}return null}function Xe(e,a,t,i){function n(o){var r;if(o!==e){const s=o.getAttribute("style")||"";if(new RegExp(`${me(a)}\\s*:\\s*[^;!]+\\s*!\\s*important`,"i").test(s))return!1}for(const s of o.childNodes)if(s.nodeType===3&&((r=s.textContent)!=null&&r.trim())){const l=parseFloat(x(o).fontSize);if(l>0&&t/l<i)return!0;break}for(const s of o.children)if(n(s))return!0;return!1}return n(e)}function Ca(e){var a;for(const t of e.childNodes)if(t.nodeType===3&&((a=t.textContent)!=null&&a.trim()))return!0;return!1}function Je(e){return!e.closest("svg")&&!e.closest("math")}function Ke(e){const a=e.getAttribute("style");if(!a)return!1;if(/position\s*:\s*(absolute|fixed)/i.test(a)){const t=a.match(/top\s*:\s*(-[\d.]+)(em|px|%)/i);if(t&&parseFloat(t[1])<-100)return!0;const i=a.match(/left\s*:\s*(-[\d.]+)(em|px|%)/i);if(i&&parseFloat(i[1])<-100)return!0}return!1}function pe(e,a){if(Ca(e))return!0;for(const t of e.children){const i=t.getAttribute("style")||"";if(!new RegExp(`${me(a)}\\s*:\\s*[^;!]+\\s*!\\s*important`,"i").test(i)&&pe(t,a))return!0}return!1}function Qe(e,a,t,i){const n=[];for(const o of e.querySelectorAll("[style]")){if(h(o)||!Je(o)||Ke(o)||!pe(o,t))continue;const r=Ye(o,t);if(!r)continue;let s=!1;if(r.em!==null?s=r.em<i:r.px!==null&&(s=Xe(o,t,r.px,i)),s){const l=r.em!==null?`${r.em}${t==="line-height"?"":"em"}`:`${r.px}px`;n.push({ruleId:a,selector:p(o),html:m(o),impact:"serious",message:`${t} ${l} with !important is below the ${i}${t==="line-height"?"":"em"} minimum.`})}}return n}function Ta(e){let a=e,t=!1;for(;a;){const i=x(a);parseFloat(i.width)>500&&(t=!0),(i.whiteSpace==="nowrap"||i.whiteSpace==="pre")&&(t=!0);const o=i.overflowX,r=i.overflowY;if((o==="scroll"||o==="auto")&&r!=="scroll"&&r!=="auto")return t;a=a.parentElement}return!1}const Na={id:"distinguishable/letter-spacing",category:"distinguishable",actRuleIds:["24afc2"],wcag:["1.4.12"],level:"AA",fixability:"mechanical",description:"Letter spacing set with !important in style attributes must be at least 0.12em.",guidance:"WCAG 1.4.12 requires users to be able to override text spacing. Using !important on letter-spacing with a value below 0.12em prevents this. Either increase the value to at least 0.12em or remove !important.",run(e){return Qe(e,"distinguishable/letter-spacing","letter-spacing",.12)}},Ma={id:"distinguishable/line-height",category:"distinguishable",actRuleIds:["78fd32"],wcag:["1.4.12"],level:"AA",fixability:"mechanical",description:"Line height set with !important in style attributes must be at least 1.5.",guidance:"WCAG 1.4.12 requires users to be able to override text spacing. Using !important on line-height with a value below 1.5 prevents this. Either increase the value to at least 1.5 or remove !important.",run(e){const a=[];for(const t of e.querySelectorAll("[style]")){if(h(t)||!Je(t)||Ke(t)||!pe(t,"line-height")||Ta(t))continue;if(t instanceof HTMLElement&&t.scrollHeight>0){const o=parseFloat(x(t).lineHeight);if(o>0&&t.scrollHeight<=o*1.5)continue}const i=Ye(t,"line-height");if(!i)continue;let n=!1;if(i.em!==null?n=i.em<1.5:i.px!==null&&(n=Xe(t,"line-height",i.px,1.5)),n){const o=i.em!==null?`${i.em}`:`${i.px}px`;a.push({ruleId:"distinguishable/line-height",selector:p(t),html:m(t),impact:"serious",message:`Line height ${o} with !important is below the 1.5 minimum.`})}}return a}},$a={id:"distinguishable/word-spacing",category:"distinguishable",actRuleIds:["9e45ec"],wcag:["1.4.12"],level:"AA",fixability:"mechanical",description:"Word spacing set with !important in style attributes must be at least 0.16em.",guidance:"WCAG 1.4.12 requires users to be able to override text spacing. Using !important on word-spacing with a value below 0.16em prevents this. Either increase the value to at least 0.16em or remove !important.",run(e){return Qe(e,"distinguishable/word-spacing","word-spacing",.16)}},Ha=new Set(["block","flex","grid","table","table-cell","list-item","flow-root"]),Pa=new Set(["inline","inline-block","inline-flex","inline-grid"]);function Da(e){let a=e.parentElement;for(;a&&!Ha.has(x(a).display);)a=a.parentElement;if(!a)return null;const t=a.ownerDocument.createTreeWalker(a,NodeFilter.SHOW_TEXT);let i="",n=null,o;for(;o=t.nextNode();){if(!o.data.trim())continue;let s=o.parentElement,l=!1;for(;s&&s!==a;){if(s.tagName==="A"){l=!0;break}s=s.parentElement}l||(i+=o.data,!n&&o.parentElement&&(n=T(x(o.parentElement).color)))}const r=i.match(new RegExp("\\p{L}{3,}","gu"));return!n||!r||r.length<2?null:{block:a,textColor:n}}function Ce(e,a){const t=e.textDecorationLine||e.textDecoration||"";return(t.includes("underline")||t.includes("line-through"))&&t!==a}function V(e){return e==="bold"?700:e==="normal"?400:parseInt(e)||400}function Fa(e){const a=e.ownerDocument.createTreeWalker(e,NodeFilter.SHOW_TEXT);let t;for(;t=a.nextNode();)if(t.data.trim())return!1;return!0}const za={id:"distinguishable/link-in-text-block",category:"distinguishable",wcag:["1.4.1"],level:"A",fixability:"visual",browserHint:"Screenshot the text block to see how the link blends with surrounding text, then verify your fix (e.g., underline or border) makes the link visually distinct.",description:"Links within text blocks must be distinguishable by more than color alone.",guidance:"Users who cannot perceive color differences need other visual cues to identify links. Links in text should have underlines or other non-color indicators. If using color alone, ensure 3:1 contrast with surrounding text AND provide additional indication on focus/hover.",run(e){const a=[];for(const t of e.querySelectorAll("a[href]")){if(h(t)||!k(t).trim()||Fa(t)||t.closest('nav, header, footer, aside, [role="navigation"], [role="banner"], [role="contentinfo"], [role="complementary"]'))continue;const i=x(t);if(!Pa.has(i.display||"inline"))continue;const n=Da(t);if(!n)continue;const o=x(n.block),r=o.textDecorationLine||o.textDecoration||"";if(Ce(i,r)||(parseFloat(i.borderBottomWidth)||0)>0&&i.borderBottomStyle!=="none"&&i.borderBottomStyle!=="hidden"||Math.abs(V(i.fontWeight)-V(o.fontWeight))>=300||i.fontStyle!==o.fontStyle)continue;const l=parseFloat(i.fontSize)||16,d=parseFloat(o.fontSize)||16;if(d>0&&l/d>=1.2)continue;let c=!1;for(const w of t.querySelectorAll("*")){const A=x(w);if(Ce(A,r)||Math.abs(V(A.fontWeight)-V(o.fontWeight))>=300){c=!0;break}}if(c)continue;const u=T(i.color);if(!u)continue;const b=q(...u),g=q(...n.textColor),f=$(b,g);if(f<1.1||f>=3)continue;const v=w=>"#"+w.map(A=>A.toString(16).padStart(2,"0")).join("");a.push({ruleId:"distinguishable/link-in-text-block",selector:p(t),html:m(t),impact:"serious",message:"Link in text block is not visually distinguishable from surrounding text. Add a non-color visual indicator such as an underline or border.",context:`link color: ${v(u)} rgb(${u.join(", ")}), surrounding text: ${v(n.textColor)} rgb(${n.textColor.join(", ")}), ratio: ${f.toFixed(2)}:1`,fix:{type:"suggest",suggestion:"Add text-decoration: underline to the link, or add a visible border-bottom. If relying on color contrast alone, ensure at least 3:1 ratio between the link color and surrounding text color."}})}return a}},ja=new Set(["SCRIPT","STYLE","NOSCRIPT","TEMPLATE","IFRAME","OBJECT","EMBED","SVG","CANVAS","VIDEO","AUDIO","IMG","BR","HR"]);function Wa(e){const a=e.clip;if(a&&a.startsWith("rect(")){const i=a.match(/[\d.]+/g);if(!i||i.every(n=>parseFloat(n)===0))return!0}const t=e.clipPath;if(t==="inset(50%)"||t==="inset(100%)")return!0;if(e.overflow==="hidden"&&e.position==="absolute"){const i=parseFloat(e.width),n=parseFloat(e.height);if(i<=1&&n<=1)return!0}return!1}function Ua(e){if(h(e))return!0;let a=e;for(;a;){const t=x(a);if(t.display==="none"||t.visibility==="hidden"||Wa(t))return!0;a=a.parentElement}return!1}function Oa(e){return e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement||e instanceof HTMLSelectElement||e instanceof HTMLButtonElement?e.disabled:!!(e.closest("fieldset[disabled]")||e.getAttribute("aria-disabled")==="true")}function Va(e,a){if(e.tagName!=="LABEL")return!1;const t=e,i=t.htmlFor;if(i){const r=a.getElementById(i);if(r&&(r.disabled||r.getAttribute("aria-disabled")==="true"))return!0}const n=t.querySelector("input, select, textarea, button");if(n&&(n.disabled||n.getAttribute("aria-disabled")==="true"))return!0;const o=t.id;return!!(o&&a.querySelector(`[aria-labelledby~="${o}"][aria-disabled="true"]`))}function Ba(e){return e.closest("select")!==null}function _a(e){const a=e.replace(/\s/g,"");return a?!new RegExp("\\p{L}","u").test(a):!0}function Ga(e){return e.closest('[aria-disabled="true"]')!==null}const Ya={grayscale:0,blur:0,"hue-rotate":0,invert:0,sepia:0,brightness:1,contrast:1,saturate:1,opacity:1};function Xa(e){const a=parseFloat(e);return isNaN(a)?NaN:e.trim().endsWith("%")?a/100:a}const Te=/([a-z-]+)\(([^)]*)\)/g;function Ne(e){let a,t=!1;for(Te.lastIndex=0;a=Te.exec(e);){t=!0;const i=Ya[a[1]];if(i===void 0||Xa(a[2])!==i)return!1}return t}function Ja(e){let a=e;for(;a;){const t=x(a),i=t.filter;if(i&&i!=="none"&&i!=="initial"&&!Ne(i))return!0;const n=t.mixBlendMode;if(n&&n!=="normal"&&n!=="initial")return!0;const o=t.backdropFilter;if(o&&o!=="none"&&o!=="initial"&&!Ne(o))return!0;a=a.parentElement}return!1}function Ka(e){let a=e;for(;a;){const t=x(a),i=t.backgroundImage;if(i&&i!=="none"&&i!=="initial")return i.includes("gradient(")?{bgImage:i,gradientEl:a}:null;const n=t.backgroundColor;if(!n||n==="transparent"||n==="rgba(0, 0, 0, 0)"||n==="rgba(0 0 0 / 0)"){a=a.parentElement;continue}if(U(n)<.01){a=a.parentElement;continue}return null}return null}function Qa(e,a,t,i,n,o,r,s,l){const d=qt(s,l);if(d.length===0)return null;let c=0,u=d[0];for(const f of d){let v=a;t<1&&(v=C(a,f,t)),i<1&&(v=C(v,f,i));const w=$(q(v[0],v[1],v[2]),q(f[0],f[1],f[2]));w>c&&(c=w,u=f)}if(c>=n)return null;let b=a;t<1&&(b=C(a,u,t)),i<1&&(b=C(b,u,i));const g=Math.round(c*100)/100;return{ruleId:o,selector:p(e),html:m(e),impact:"serious",message:`Insufficient${r==="AAA"?" enhanced":""} color contrast ratio of ${g}:1 (required ${n}:1).`,context:`foreground: ${_(b)} rgb(${b.join(", ")}), background: gradient, ratio: ${g}:1, required: ${n}:1`,fix:{type:"suggest",suggestion:`Change the text color or gradient background so the contrast ratio meets ${n}:1. The current foreground is ${_(b)}.`}}}function Ze(e,a,t){const i=[],n=e.body;if(!n)return[];const o=e.createTreeWalker(n,NodeFilter.SHOW_TEXT),r=new Set;let s;for(;s=o.nextNode();){if(!s.textContent||!s.textContent.trim()||_a(s.textContent))continue;const l=s.parentElement;if(!l||r.has(l)||(r.add(l),ja.has(l.tagName)))continue;const d=l.tagName;if(d==="BODY"||d==="HTML"||Ba(l)||Oa(l)||Va(l,e)||Ga(l)||Ua(l))continue;const c=x(l);if(parseFloat(c.opacity)===0)continue;const u=$t(l);if(u<.1)continue;const b=c.textShadow;let g=null;if(b&&b!=="none"&&b!=="initial"&&(g=Nt(b),!g)||Ja(l)||Ht(l))continue;const f=T(c.color);if(!f)continue;const v=U(c.color);if(v===0||Lt(l))continue;const w=t==="AAA"?Se(l)?4.5:7:Se(l)?3:4.5;let A=ke(l);if(!A){if(g)continue;const L=Ka(l);if(L){const O=L.gradientEl.parentElement?ke(L.gradientEl.parentElement):null,P=Qa(l,f,v,u,w,a,t,L.bgImage,O??[255,255,255]);P&&i.push(P)}continue}let S=f;v<1&&(S=C(f,A,v)),u<1&&(S=C(S,A,u));const ct=q(S[0],S[1],S[2]),dt=q(A[0],A[1],A[2]),ve=g?Mt(S,A,g):$(ct,dt);if(ve<w){const L=Math.round(ve*100)/100,O=_(S),P=_(A);i.push({ruleId:a,selector:p(l),html:m(l),impact:"serious",message:`Insufficient${t==="AAA"?" enhanced":""} color contrast ratio of ${L}:1 (required ${w}:1).`,context:`foreground: ${O} rgb(${S.join(", ")}), background: ${P} rgb(${A.join(", ")}), ratio: ${L}:1, required: ${w}:1`,fix:{type:"suggest",suggestion:`Change the text color or background color so the contrast ratio meets ${w}:1. Current foreground is ${O}, background is ${P}.`}})}}return i}const Za={id:"distinguishable/color-contrast",category:"distinguishable",actRuleIds:["afw4f7"],wcag:["1.4.3"],level:"AA",fixability:"visual",description:"Text elements must have sufficient color contrast against the background.",browserHint:"Violation context includes computed colors and ratio. After changing colors, use JavaScript to read getComputedStyle() on the element and recalculate the contrast ratio. Screenshot the element to verify the fix looks correct in context.",guidance:"WCAG SC 1.4.3 requires a contrast ratio of at least 4.5:1 for normal text and 3:1 for large text (>=24px or >=18.66px bold). Increase the contrast by darkening the text or lightening the background, or vice versa.",run(e){return Ze(e,"distinguishable/color-contrast","AA")}},ei={id:"distinguishable/color-contrast-enhanced",category:"distinguishable",actRuleIds:["09o5cg"],wcag:["1.4.6"],level:"AAA",fixability:"visual",description:"Text elements must have enhanced color contrast against the background (WCAG AAA).",browserHint:"Violation context includes computed colors and ratio. After changing colors, use JavaScript to read getComputedStyle() on the element and recalculate the contrast ratio. Screenshot the element to verify the fix looks correct in context.",guidance:"WCAG SC 1.4.6 (AAA) requires a contrast ratio of at least 7:1 for normal text and 4.5:1 for large text (>=24px or >=18.66px bold). Higher contrast benefits users with low vision, aging eyes, or poor screen conditions. Increase the contrast by darkening the text or lightening the background, or vice versa.",run(e){return Ze(e,"distinguishable/color-contrast-enhanced","AAA")}},ti={id:"keyboard-accessible/server-image-map",selector:"img[ismap], input[type='image'][ismap]",check:{type:"selector-exists"},impact:"minor",message:"Server-side image map detected. Use client-side image map with <map> and <area> elements instead.",description:"Server-side image maps must not be used.",wcag:["2.1.1"],level:"A",fixability:"contextual",guidance:"Server-side image maps (using ismap attribute) send click coordinates to the server, which is inaccessible to keyboard users and screen readers who can't precisely click specific regions. Replace with client-side image maps (<map> with <area> elements) that provide keyboard access and accessible names, or use linked images/buttons instead.",fix:{type:"remove-attribute",attribute:"ismap"}},ai=E(ti),ii={id:"keyboard-accessible/tabindex",selector:"[tabindex]",check:{type:"attribute-value",attribute:"tabindex",operator:">",value:0},impact:"serious",message:'Element has tabindex="{{value}}" which disrupts tab order.',description:"Elements should not have tabindex greater than 0, which disrupts natural tab order.",wcag:[],level:"A",tags:["best-practice"],fixability:"mechanical",guidance:"Positive tabindex values force elements to the front of the tab order regardless of DOM position, creating unpredictable navigation for keyboard users. Use tabindex='0' to add elements to the natural tab order, or tabindex='-1' to make elements programmatically focusable but not in tab order. Rely on DOM order for tab sequence.",fix:{type:"set-attribute",attribute:"tabindex",value:"0"}},ni=E(ii),oi=new Set(["div","span","p","section","article","header","footer","main","nav","aside","h1","h2","h3","h4","h5","h6","ul","ol","li","dl","dt","dd","table","tr","td","th"]),ri={id:"keyboard-accessible/focus-order",category:"keyboard-accessible",wcag:[],tags:["best-practice"],level:"A",fixability:"contextual",description:"Non-interactive elements with tabindex='0' must have an interactive ARIA role so assistive technologies can convey their purpose.",guidance:"When adding tabindex='0' to non-interactive elements like <div> or <span>, screen readers announce them generically. Add an appropriate role (button, link, tab, etc.) so users understand the element's purpose. Also add keyboard event handlers (Enter/Space for buttons, Enter for links). Consider using native interactive elements instead.",run(e){const a=[];for(const t of e.querySelectorAll('[tabindex="0"]')){const i=t.tagName.toLowerCase();if(!oi.has(i))continue;t.getAttribute("role")||a.push({ruleId:"keyboard-accessible/focus-order",selector:p(t),html:m(t),impact:"moderate",message:`Non-interactive <${i}> with tabindex="0" has no interactive role.`})}return a}},si=new Set(["a","audio","button","img","input","select","textarea","video"]),li=new Set(["button","checkbox","combobox","gridcell","link","listbox","menu","menubar","menuitem","menuitemcheckbox","menuitemradio","option","progressbar","radio","scrollbar","searchbox","slider","spinbutton","switch","tab","tabpanel","textbox","treeitem"]),ci={grid:new Set(["gridcell","row","columnheader","rowheader"]),listbox:new Set(["option"]),menu:new Set(["menuitem","menuitemcheckbox","menuitemradio"]),menubar:new Set(["menuitem","menuitemcheckbox","menuitemradio"]),radiogroup:new Set(["radio"]),tablist:new Set(["tab"]),tree:new Set(["treeitem"]),treegrid:new Set(["gridcell","row","columnheader","rowheader","treeitem"])};function di(e,a){var n,o,r;const t=(n=e.getAttribute("role"))==null?void 0:n.toLowerCase(),i=(o=a.getAttribute("role"))==null?void 0:o.toLowerCase();return!t||!i?!1:((r=ci[t])==null?void 0:r.has(i))??!1}function ui(e){var n;const a=e.tagName.toLowerCase();if(si.has(a))return a==="a"&&!e.hasAttribute("href")?!1:a==="audio"||a==="video"?e.hasAttribute("controls"):!(a==="img"&&!e.hasAttribute("usemap")||a==="input"&&e.type==="hidden"||e.disabled);const t=(n=e.getAttribute("role"))==null?void 0:n.toLowerCase();if(t&&li.has(t))return!0;const i=e.getAttribute("tabindex");return i!==null&&i!=="-1"||e.getAttribute("contenteditable")==="true"}function mi(e){const a=e.tagName.toLowerCase();return!!(a==="a"&&e.hasAttribute("href")||a==="button"&&!e.disabled)}const pi={id:"keyboard-accessible/nested-interactive",category:"keyboard-accessible",wcag:["4.1.2"],level:"A",fixability:"contextual",description:"Interactive controls must not be nested inside each other.",guidance:"Nesting interactive elements (like a button inside a link, or a link inside a button) creates unpredictable behavior and confuses assistive technologies. The browser may remove the inner element from the accessibility tree. Restructure the HTML so interactive elements are siblings, not nested. If you need a clickable card, use CSS and JavaScript rather than nesting.",run(e){const a=[],t=e.body??e;if(!t)return a;const n=(e.body?e:e.ownerDocument).createTreeWalker(t,NodeFilter.SHOW_ELEMENT),o=[];let r=n.currentNode;for(;r;){for(;o.length>0&&!o[o.length-1].contains(r);)o.pop();if(!h(r)&&ui(r)){if(o.length>0){const s=o[o.length-1];di(s,r)||a.push({ruleId:"keyboard-accessible/nested-interactive",selector:p(r),html:m(r),impact:"serious",message:`Interactive element <${r.tagName.toLowerCase()}> is nested inside <${s.tagName.toLowerCase()}>.`,fix:{type:"suggest",suggestion:"Move the nested interactive element outside its interactive parent so they are siblings instead of nested"}})}mi(r)&&o.push(r)}r=n.nextNode()}return a}},bi={id:"keyboard-accessible/scrollable-region",category:"keyboard-accessible",actRuleIds:["0ssw9k"],wcag:["2.1.1"],level:"A",fixability:"contextual",browserHint:"Tab to the scrollable region and verify keyboard scrolling works with arrow keys.",description:"Scrollable regions must be keyboard accessible.",guidance:"Content that scrolls must be accessible to keyboard users. If a region has overflow:scroll or overflow:auto and contains scrollable content, it needs either tabindex='0' to be focusable, or it must contain focusable elements. Without this, keyboard users cannot scroll the content.",run(e){var t;const a=[];for(const i of e.querySelectorAll("*")){if(h(i)||!(i instanceof HTMLElement))continue;const n=i.tagName.toLowerCase();if(n==="body"||n==="html")continue;const o=i.getAttribute("role");if(o==="presentation"||o==="none"||o==="listbox"||o==="menu"||o==="tree"||o==="tabpanel")continue;const r=x(i),s=r.overflowX,l=r.overflowY;if(!(s==="scroll"||s==="auto"||l==="scroll"||l==="auto"))continue;if(i.scrollHeight>0||i.clientHeight>0){const g=i.scrollHeight-i.clientHeight,f=i.scrollWidth-i.clientWidth;if(g<=0&&f<=0||g<14&&f<14||i.clientWidth<64&&i.clientHeight<64)continue;const v=((t=i.textContent)==null?void 0:t.trim().length)??0,w=i.querySelector("img, svg, video, canvas, picture")!==null;if(v===0&&!w)continue}else continue;const u=i.getAttribute("tabindex");u!==null&&u!=="-1"||i.querySelector(W)||a.push({ruleId:"keyboard-accessible/scrollable-region",selector:p(i),html:m(i),impact:"serious",message:"Scrollable region is not keyboard accessible. Add tabindex='0' or include focusable elements."})}return a}},hi={id:"keyboard-accessible/accesskeys",category:"keyboard-accessible",wcag:[],level:"A",tags:["best-practice"],fixability:"mechanical",description:"Accesskey attribute values must be unique.",guidance:"When multiple elements share the same accesskey, browser behavior becomes unpredictable - usually only the first element is activated. Ensure each accesskey value is unique within the page. Also consider that accesskeys can conflict with browser and screen reader shortcuts, so use them sparingly.",run(e){var i;const a=[],t=new Map;for(const n of e.querySelectorAll("[accesskey]")){if(h(n))continue;const o=(i=n.getAttribute("accesskey"))==null?void 0:i.trim().toLowerCase();if(!o)continue;const r=t.get(o)||[];r.push(n),t.set(o,r)}for(const[n,o]of t)if(o.length>1)for(const r of o.slice(1))a.push({ruleId:"keyboard-accessible/accesskeys",selector:p(r),html:m(r),impact:"serious",message:`Duplicate accesskey "${n}". Each accesskey must be unique.`});return a}},gi={id:"keyboard-accessible/focus-visible",category:"keyboard-accessible",actRuleIds:["oj04fd"],wcag:["2.4.7"],level:"AA",fixability:"visual",browserHint:"Tab to the element and screenshot to verify a visible focus indicator appears. Check that the indicator has sufficient contrast against the background.",description:"Elements in sequential focus order must have a visible focus indicator.",guidance:"Keyboard users need to see which element has focus. Do not remove the default focus outline (outline: none) without providing an alternative visible indicator. Use :focus-visible or :focus styles to ensure focus is always perceivable.",run(e){const a=[];for(const t of e.querySelectorAll(W)){if(h(t)||!(t instanceof HTMLElement))continue;const i=t.getAttribute("style")||"";if(/outline\s*:\s*(none|0)\s*(;|$|!)/i.test(i)){const o=/border\s*:/i.test(i),r=/box-shadow\s*:/i.test(i);!o&&!r&&a.push({ruleId:"keyboard-accessible/focus-visible",selector:p(t),html:m(t),impact:"serious",message:"Focusable element has outline removed without a visible focus alternative."})}}return a}};function et(e){if(!(e instanceof HTMLElement))return!1;if(e.style.display==="none"||e.style.visibility==="hidden")return!0;const a=e.getAttribute("width"),t=e.getAttribute("height");return(a==="0"||a==="1")&&(t==="0"||t==="1")}function tt(e){const a=e.match(/^(\d+)/);if(!a)return null;const t=parseInt(a[1],10),i=/^\d+\s*[;,]\s*url\s*=/i.test(e)||/^\d+\s*[;,]\s*['"]?\s*https?:/i.test(e);return{seconds:t,hasValidUrl:i}}const at='article, aside, main, nav, section, [role="article"], [role="complementary"], [role="main"], [role="navigation"], [role="region"]',Me='main, [role="main"], header, [role="banner"], footer, [role="contentinfo"], nav, [role="navigation"], aside, [role="complementary"], section[aria-label], section[aria-labelledby], [role="region"][aria-label], [role="region"][aria-labelledby], form[aria-label], form[aria-labelledby], [role="form"][aria-label], [role="form"][aria-labelledby], [role="search"]';function it(e){return{id:e.id,category:e.id.split("/")[0],wcag:[],level:"A",tags:["best-practice","page-level"],fixability:"contextual",description:e.description,guidance:e.guidance,run(a){const t=[];for(const i of a.querySelectorAll(e.selector))i.closest(at)&&t.push({ruleId:e.id,selector:p(i),html:m(i),impact:"moderate",message:`${e.landmarkName} landmark is nested within another landmark.`});return t}}}function be(e){return{id:e.id,category:e.id.split("/")[0],wcag:[],level:"A",tags:["best-practice","page-level"],fixability:"contextual",description:e.description,guidance:e.guidance,run(a){const t=[],i=a.querySelectorAll(e.selector),n=e.filterTopLevel?Array.from(i).filter(o=>!o.closest(at)):Array.from(i);return n.length>1&&n.slice(1).forEach(o=>t.push({ruleId:e.id,selector:p(o),html:m(o),impact:"moderate",message:`Page has multiple ${e.landmarkName} landmarks.`})),t}}}const fi={id:"enough-time/meta-refresh",category:"enough-time",actRuleIds:["bc659a"],wcag:["2.2.1"],level:"A",tags:["page-level"],fixability:"mechanical",description:"Meta refresh must not redirect or refresh automatically.",guidance:"Automatic page refreshes or redirects can disorient users, especially those using screen readers or with cognitive disabilities. They may lose their place or not have time to read content. If a redirect is needed, use a server-side redirect (HTTP 301/302) instead. For timed refreshes, provide user controls.",run(e){for(const a of e.querySelectorAll('meta[http-equiv="refresh"]')){const t=a.getAttribute("content")||"",i=tt(t);if(i){if(i.hasValidUrl)return i.seconds>0&&i.seconds<=72e3?[{ruleId:"enough-time/meta-refresh",selector:p(a),html:m(a),impact:"critical",message:`Page redirects after ${i.seconds} seconds without warning. Use server-side redirect.`,fix:{type:"remove-element"}}]:[];if(i.seconds>0&&i.seconds<=72e3)return[{ruleId:"enough-time/meta-refresh",selector:p(a),html:m(a),impact:"critical",message:`Page auto-refreshes after ${i.seconds} seconds. Provide user control over refresh.`,fix:{type:"remove-element"}}]}}return[]}},vi={id:"enough-time/meta-refresh-no-exception",category:"enough-time",actRuleIds:["bisz58"],wcag:["2.2.1"],level:"A",tags:["page-level"],fixability:"mechanical",description:"Meta refresh must not be used with a delay (no exceptions).",guidance:"Automatic page refreshes and delayed redirects disorient users. Instant redirects (delay=0) are acceptable, but any positive delay is not. Use server-side redirects instead.",run(e){for(const a of e.querySelectorAll('meta[http-equiv="refresh"]')){const t=a.getAttribute("content")||"",i=tt(t);if(i){if(i.hasValidUrl)return i.seconds>0?[{ruleId:"enough-time/meta-refresh-no-exception",selector:p(a),html:m(a),impact:"critical",message:`Page has a ${i.seconds}-second meta refresh delay. Use a server-side redirect instead.`,fix:{type:"remove-element"}}]:[];if(i.seconds>0)return[{ruleId:"enough-time/meta-refresh-no-exception",selector:p(a),html:m(a),impact:"critical",message:`Page has a ${i.seconds}-second meta refresh delay. Remove the auto-refresh or provide user control.`,fix:{type:"remove-element"}}]}}return[]}},yi={id:"enough-time/blink",selector:"blink",check:{type:"selector-exists"},impact:"serious",message:"The <blink> element causes accessibility issues. Remove it entirely.",description:"The <blink> element must not be used.",wcag:["2.2.2"],level:"A",fixability:"mechanical",guidance:"Blinking content can cause seizures in users with photosensitive epilepsy and is distracting for users with attention disorders. The <blink> element is deprecated and should never be used. If you need to draw attention to content, use less intrusive methods like color, borders, or icons.",fix:{type:"remove-element"}},wi=E(yi),xi={id:"enough-time/marquee",selector:"marquee",check:{type:"selector-exists"},impact:"serious",message:"The <marquee> element causes accessibility issues. Replace with static content.",description:"The <marquee> element must not be used.",wcag:["2.2.2"],level:"A",fixability:"mechanical",guidance:"Scrolling or moving content is difficult for many users to read, especially those with cognitive or visual disabilities. The <marquee> element is deprecated. Replace scrolling text with static content. If content must scroll, provide pause/stop controls and ensure it stops after 5 seconds.",fix:{type:"remove-element"}},Ai=E(xi),ki={id:"navigable/document-title",category:"navigable",actRuleIds:["2779a5"],wcag:["2.4.2"],level:"A",tags:["page-level"],fixability:"contextual",description:"Documents must have a <title> element to provide users with an overview of content.",guidance:"Screen reader users rely on page titles to identify and navigate between tabs/windows. Add a descriptive <title> element in <head> that summarizes the page purpose. Keep titles unique across the site, placing specific content before the site name (e.g., 'Contact Us - Acme Corp').",run(e){var t,i,n;const a=e.querySelector("title");if(!a||!((t=a.textContent)!=null&&t.trim())){let o;const r=e.querySelector("h1");if((i=r==null?void 0:r.textContent)!=null&&i.trim())o=`h1: "${r.textContent.trim().slice(0,100)}"`;else if(e.body){const s=((n=e.body.textContent)==null?void 0:n.trim().replace(/\s+/g," "))||"";s&&(o=`Page text: "${s.slice(0,150)}"`)}return[{ruleId:"navigable/document-title",selector:"html",html:"<html>",impact:"serious",message:a?"Document <title> element is empty.":"Document is missing a <title> element.",context:o,fix:{type:"add-element",tag:"title",parent:"head",textContent:""}}]}return[]}},Si={id:"navigable/bypass",category:"navigable",actRuleIds:["cf77f2"],wcag:["2.4.1"],level:"A",tags:["best-practice","page-level"],fixability:"contextual",description:"Page must have a mechanism to bypass repeated blocks of content.",guidance:'Keyboard users must be able to skip repetitive content like navigation. Provide a skip link at the top of the page that links to the main content (e.g., <a href="#main">Skip to main content</a>), or use a <main> landmark. Screen readers can jump directly to landmarks, so a properly marked-up <main> element satisfies this requirement.',run(e){if(e.querySelector('main, [role="main"], nav, [role="navigation"], aside, [role="complementary"], header, [role="banner"], footer, [role="contentinfo"], [role="search"], [role="region"]'))return[];const t=e.querySelector('a[href^="#"]');if(t){const o=t.getAttribute("href");if(o&&o.length>1){const r=o.slice(1);if(e.getElementById(r))return[]}}if(e.querySelector("h1, h2, h3, [role='heading']"))return[];const n=[];return n.push("no landmarks (<main>, <nav>, <header>, <footer>)"),n.push("no skip link"),n.push("no headings"),[{ruleId:"navigable/bypass",selector:"html",html:"<html>",impact:"serious",message:"Page has no mechanism to bypass repeated content. Add a <main> landmark or skip link.",context:`Missing: ${n.join(", ")}`}]}},Ii={id:"navigable/page-has-heading-one",category:"navigable",wcag:[],level:"A",tags:["best-practice","page-level"],fixability:"contextual",description:"Page should contain a level-one heading.",guidance:"A level-one heading (<h1> or role='heading' with aria-level='1') helps users understand the page topic and provides a landmark for screen reader navigation. Each page should have at least one level-one heading that describes the main content, typically matching or similar to the page title.",run(e){var r,s,l;const a=e.querySelector("h1");if(a&&y(a))return[];const t=e.querySelectorAll('[role="heading"][aria-level="1"]');for(const d of t)if(y(d))return[];const i=[],n=(s=(r=e.querySelector("title"))==null?void 0:r.textContent)==null?void 0:s.trim();n&&i.push(`Page title: "${n}"`);const o=e.querySelector("main");if(o){const d=((l=o.textContent)==null?void 0:l.trim().replace(/\s+/g," "))||"";d&&i.push(`Main content: "${d.slice(0,100)}"`)}return[{ruleId:"navigable/page-has-heading-one",selector:"html",html:"<html>",impact:"moderate",message:"Page does not contain a level-one heading.",context:i.length>0?i.join(", "):void 0}]}},qi={id:"navigable/heading-order",category:"navigable",wcag:[],level:"A",tags:["best-practice"],fixability:"contextual",browserHint:"Screenshot the page to see the visual hierarchy, then take an accessibility tree snapshot to map heading levels to visual sections.",description:"Heading levels should increase by one; skipping levels (e.g. h2 to h4) makes navigation harder.",guidance:"Screen reader users navigate by headings to understand page structure. Skipping levels (h2 to h4) suggests missing content and creates confusion. Start with h1 for the page title, then use h2 for main sections, h3 for subsections, etc. You can go back up (h3 to h2) when starting a new section.",run(e){const a=[],t=e.querySelectorAll("h1, h2, h3, h4, h5, h6, [role='heading']");let i=0,n=null;for(const o of t){if(h(o))continue;let r;o.hasAttribute("aria-level")?r=parseInt(o.getAttribute("aria-level"),10):r=parseInt(o.tagName[1],10),i>0&&r>i+1&&a.push({ruleId:"navigable/heading-order",selector:p(o),html:m(o),impact:"moderate",message:`Heading level ${r} skipped from level ${i}. Use h${i+1} instead.`,context:n?`Previous heading: ${m(n)}`:void 0,fix:{type:"suggest",suggestion:`Change this heading to an h${i+1} element to maintain proper heading hierarchy`}}),i=r,n=o}return a}},Ei={id:"navigable/empty-heading",category:"navigable",actRuleIds:["ffd0e9"],wcag:["2.4.6"],level:"A",tags:["best-practice"],fixability:"contextual",browserHint:"Screenshot the heading area to verify it's visually empty, then add meaningful text or remove the heading element.",description:"Headings must have discernible text.",guidance:"Screen reader users navigate pages by headings, so empty headings create confusing navigation points. Ensure all headings contain visible text or accessible names. If a heading is used purely for visual styling, use CSS instead of heading elements.",run(e){var i;const a=[],t=e.querySelectorAll('h1, h2, h3, h4, h5, h6, [role="heading"]');for(const n of t)if(!h(n)&&!y(n)){let o;const r=n.nextElementSibling;if(r){const s=((i=r.textContent)==null?void 0:i.trim().replace(/\s+/g," "))||"";s&&(o=s.slice(0,100))}a.push({ruleId:"navigable/empty-heading",selector:p(n),html:m(n),impact:"minor",message:"Heading is empty. Add text content or remove the heading element.",context:o?`Following content: "${o}"`:void 0,fix:{type:"add-text-content"}})}return a}},Li={id:"navigable/p-as-heading",category:"navigable",wcag:["1.3.1"],level:"A",tags:["best-practice"],fixability:"contextual",browserHint:"Screenshot the page to verify the paragraph visually functions as a heading and choose the correct heading level.",description:"Paragraphs should not be styled to look like headings.",guidance:"When paragraphs are styled with bold, large fonts to look like headings, screen reader users miss the semantic structure. Use proper heading elements (h1-h6) instead of styled paragraphs. If you need specific styling, apply CSS to the heading elements while maintaining proper heading hierarchy.",run(e){var t,i;const a=[];for(const n of e.querySelectorAll("p")){if(h(n))continue;const o=n.getAttribute("style")||"",r=/font-weight\s*:\s*(bold|[6-9]00)/i.test(o),s=/font-size\s*:\s*(\d+)\s*(px|em|rem)/i.test(o),l=((t=n.className)==null?void 0:t.toLowerCase())||"",d=/\bh[1-6]\b|\bheading\b/.test(l),c=((i=n.textContent)==null?void 0:i.trim())||"",u=c.length>0&&c.length<50,b=!c.match(/[.!?,;:]$/);if((r&&s||r&&d)&&u&&b){const v=n.nextElementSibling;v&&(v.tagName==="P"||v.tagName==="DIV"||v.tagName==="UL")&&a.push({ruleId:"navigable/p-as-heading",selector:p(n),html:m(n),impact:"serious",message:"Paragraph appears to be styled as a heading. Use an h1-h6 element instead.",fix:{type:"suggest",suggestion:"Replace the <p> element with the appropriate heading level (h1-h6) based on the document outline. Preserve the text content and move any inline styles to a CSS class on the new heading element."}})}}return a}};function Ri(e){var n,o;const a=[],t=e.getAttribute("href");t&&a.push(`href: ${t}`);const i=e.parentElement;if(i){const r=i.closest("h1, h2, h3, h4, h5, h6");if((n=r==null?void 0:r.textContent)!=null&&n.trim())a.push(`Nearby heading: ${r.textContent.trim().slice(0,80)}`);else{const s=(o=i.textContent)==null?void 0:o.trim().slice(0,100);s&&a.push(`Parent text: ${s}`)}}return a.length>0?a.join(`
|
|
3
3
|
`):void 0}const Ci={id:"navigable/link-name",category:"navigable",actRuleIds:["c487ae"],wcag:["2.4.4","4.1.2"],level:"A",fixability:"contextual",browserHint:"Screenshot the link in context to understand its destination, then write descriptive link text.",description:"Links must have discernible text via content, aria-label, or aria-labelledby.",guidance:"Screen reader users need to know where a link goes. Add descriptive text content, aria-label, or use aria-labelledby. For image links, ensure the image has alt text describing the link destination. Avoid generic text like 'click here' or 'read more'—link text should make sense out of context.",run(e){const a=[];for(const t of e.querySelectorAll('a[href], area[href], [role="link"]')){if(h(t)||I(t)||t.getRootNode()instanceof ShadowRoot)continue;y(t)||a.push({ruleId:"navigable/link-name",selector:p(t),html:m(t),impact:"serious",message:"Link has no discernible text.",context:Ri(t),fix:{type:"add-text-content"}})}return a}},Ti={id:"navigable/skip-link",category:"navigable",wcag:["2.4.1"],level:"A",tags:["best-practice","page-level"],fixability:"mechanical",description:"Skip links must point to a valid target on the page.",guidance:"Skip links allow keyboard users to bypass repetitive navigation and jump directly to main content. The skip link should be the first focusable element on the page, link to the main content (e.g., href='#main'), and become visible when focused. It can be visually hidden until focused using CSS.",run(e){const a=[],t=e.querySelectorAll('a[href^="#"]');for(const i of t){const n=i.getAttribute("href");if(!n||n==="#")continue;const o=k(i).toLowerCase();if(!(o.includes("skip")||o.includes("jump")||o.includes("main content")||o.includes("navigation")))continue;const s=n.slice(1);e.getElementById(s)||a.push({ruleId:"navigable/skip-link",selector:p(i),html:m(i),impact:"moderate",message:`Skip link points to "#${s}" which does not exist on the page.`})}return a}},Ni={id:"landmarks/landmark-main",category:"landmarks",wcag:[],level:"A",tags:["best-practice","page-level"],fixability:"contextual",description:"Page should have exactly one main landmark.",guidance:"The main landmark contains the primary content of the page. Screen readers allow users to jump directly to main content. Use a single <main> element (or role='main') to wrap the central content, excluding headers, footers, and navigation.",run(e){const a=e.querySelectorAll('main, [role="main"]');return a.length===0?[{ruleId:"landmarks/landmark-main",selector:"html",html:"<html>",impact:"moderate",message:"Page has no main landmark."}]:a.length>1?Array.from(a).slice(1).map(t=>({ruleId:"landmarks/landmark-main",selector:p(t),html:m(t),impact:"moderate",message:"Page has multiple main landmarks."})):[]}},Mi=be({id:"landmarks/no-duplicate-banner",selector:'header, [role="banner"]',landmarkName:"banner",description:"Page should not have more than one banner landmark.",guidance:"The banner landmark (typically <header>) identifies site-oriented content like logos and search. Only one top-level banner is allowed per page. If you need multiple headers, nest them inside sectioning elements (article, section, aside) where they become scoped headers rather than page-level banners.",filterTopLevel:!0}),$i=be({id:"landmarks/no-duplicate-contentinfo",selector:'footer, [role="contentinfo"]',landmarkName:"contentinfo",description:"Page should not have more than one contentinfo landmark.",guidance:"The contentinfo landmark (typically <footer>) contains information about the page like copyright and contact info. Only one top-level contentinfo is allowed per page. Nest additional footers inside sectioning elements to scope them.",filterTopLevel:!0}),Hi=be({id:"landmarks/no-duplicate-main",selector:'main, [role="main"]',landmarkName:"main",description:"Page should not have more than one main landmark.",guidance:"Only one main landmark should exist per page. The main landmark identifies the primary content area. If you have multiple content sections, use <section> with appropriate headings instead of multiple main elements.",filterTopLevel:!1}),Pi=it({id:"landmarks/banner-is-top-level",selector:'[role="banner"]',landmarkName:"Banner",description:"Banner landmark should not be nested within another landmark.",guidance:"The banner landmark should be a top-level landmark, not nested inside article, aside, main, nav, or section. If a header is inside these elements, it automatically becomes a generic header rather than a banner. Remove explicit role='banner' from nested headers or restructure the page."}),Di=it({id:"landmarks/contentinfo-is-top-level",selector:'[role="contentinfo"]',landmarkName:"Contentinfo",description:"Contentinfo landmark should not be nested within another landmark.",guidance:"The contentinfo landmark should be a top-level landmark. A footer inside article, aside, main, nav, or section becomes a scoped footer, not a contentinfo landmark. Remove explicit role='contentinfo' from nested footers or move the footer outside sectioning elements."}),Fi={id:"landmarks/main-is-top-level",category:"landmarks",wcag:[],level:"A",tags:["best-practice","page-level"],fixability:"contextual",description:"Main landmark should not be nested within another landmark.",guidance:"Screen readers provide a shortcut to jump directly to the main landmark. When <main> is nested inside another landmark (article, aside, nav, or section), some screen readers may not list it as a top-level landmark, making it harder to find. Move <main> outside any sectioning elements so it sits at the top level of the document.",run(e){const a=[],t=e.querySelectorAll('main, [role="main"]');for(const i of t){const n=i.parentElement;n!=null&&n.closest('article, aside, nav, section[aria-label], section[aria-labelledby], [role="article"], [role="complementary"], [role="navigation"], [role="region"]')&&a.push({ruleId:"landmarks/main-is-top-level",selector:p(i),html:m(i),impact:"moderate",message:"Main landmark is nested within another landmark."})}return a}},zi={id:"landmarks/complementary-is-top-level",category:"landmarks",wcag:[],level:"A",tags:["best-practice","page-level"],fixability:"contextual",description:"Aside (complementary) landmark should be top-level or directly inside main.",guidance:"The complementary landmark (aside) should be top-level or a direct child of main. Nesting aside deep within other landmarks reduces its discoverability for screen reader users navigating by landmarks.",run(e){const a=[],t=e.querySelectorAll('aside, [role="complementary"]');for(const i of t){const n=i.parentElement;n&&!n.matches('body, main, [role="main"]')&&i.closest('article, nav, section[aria-label], section[aria-labelledby], [role="article"], [role="navigation"], [role="region"]')&&a.push({ruleId:"landmarks/complementary-is-top-level",selector:p(i),html:m(i),impact:"moderate",message:"Complementary landmark should be top-level."})}return a}},ji={id:"landmarks/landmark-unique",category:"landmarks",wcag:[],level:"A",tags:["best-practice","page-level"],fixability:"contextual",description:"Landmarks should have unique labels when there are multiple of the same type.",guidance:"When a page has multiple landmarks of the same type (e.g., multiple nav elements), each should have a unique accessible name via aria-label or aria-labelledby. This helps screen reader users distinguish between them (e.g., 'Main navigation' vs 'Footer navigation').",run(e){const a=[],t=[{selector:'nav, [role="navigation"]',type:"navigation"},{selector:'aside, [role="complementary"]',type:"complementary"},{selector:'section[aria-label], section[aria-labelledby], [role="region"]',type:"region"},{selector:'form[aria-label], form[aria-labelledby], [role="form"], [role="search"]',type:"form"}];for(const{selector:i,type:n}of t){const o=Array.from(e.querySelectorAll(i)).filter(s=>!h(s));if(o.length<=1)continue;const r=new Map;for(const s of o){const l=y(s).toLowerCase()||"",d=r.get(l)||[];d.push(s),r.set(l,d)}for(const[s,l]of r)if(l.length>1)for(const d of l.slice(1))a.push({ruleId:"landmarks/landmark-unique",selector:p(d),html:m(d),impact:"moderate",message:s?`Multiple ${n} landmarks have the same label "${s}".`:`Multiple ${n} landmarks have no label. Add unique aria-label attributes.`})}return a}},Wi={id:"landmarks/region",category:"landmarks",wcag:[],level:"A",tags:["best-practice","page-level"],fixability:"contextual",description:"All page content should be contained within landmarks.",guidance:"Screen reader users navigate pages by landmarks. Content outside landmarks is harder to find and understand. Wrap all visible content in appropriate landmarks: <header>, <nav>, <main>, <aside>, <footer>, or <section> with a label. Skip links may exist outside landmarks.",run(e){var i;const a=[],t=e.body;if(!t)return[];for(const n of t.children){if(h(n)||n instanceof HTMLScriptElement||n instanceof HTMLStyleElement||n.tagName==="NOSCRIPT"||n instanceof HTMLElement&&n.hidden||n.matches('a[href^="#"]'))continue;const o=n.matches(Me),r=(i=n.textContent)==null?void 0:i.trim();!o&&r&&(n.querySelector(Me)||a.push({ruleId:"landmarks/region",selector:p(n),html:m(n),impact:"moderate",message:"Content is not contained within a landmark region."}))}return a}},Ui={id:"readable/html-has-lang",category:"readable",actRuleIds:["b5c3f8"],wcag:["3.1.1"],level:"A",tags:["page-level"],fixability:"mechanical",description:"The <html> element must have a lang attribute.",guidance:"Screen readers use the lang attribute to determine which language rules and pronunciation to use. Without it, content may be mispronounced. Set lang to the primary language of the page using a BCP 47 code (e.g., 'en' for English, 'es' for Spanish, 'fr' for French, 'de' for German, 'ja' for Japanese, 'zh' for Chinese, 'pt' for Portuguese, 'ar' for Arabic).",run(e){var t,i;const a=e.documentElement;if(a.tagName.toLowerCase()!=="html")return[];if(!e.doctype&&e.body){const n=e.body.children;if(n.length>0&&Array.from(n).every(o=>o.tagName.toLowerCase()==="svg"||o.tagName.toLowerCase()==="math"))return[]}if(!((t=a.getAttribute("lang"))!=null&&t.trim())){let n;if(e.body){const o=((i=e.body.textContent)==null?void 0:i.trim().replace(/\s+/g," "))||"";o&&(n=o.slice(0,200))}return[{ruleId:"readable/html-has-lang",selector:p(a),html:m(a),impact:"serious",message:"<html> element missing lang attribute.",context:n?`Page text sample: "${n}"`:void 0,fix:{type:"add-attribute",attribute:"lang",value:"en"}}]}return[]}},Oi=new Set("aa ab ae af ak am an ar as av ay az ba be bg bh bi bm bn bo br bs ca ce ch co cr cs cu cv cy da de dv dz ee el en eo es et eu fa ff fi fj fo fr fy ga gd gl gn gu gv ha he hi ho hr ht hu hy hz ia id ie ig ii ik io is it iu ja jv ka kg ki kj kk kl km kn ko kr ks ku kv kw ky la lb lg li ln lo lt lu lv mg mh mi mk ml mn mr ms mt my na nb nd ne ng nl nn no nr nv ny oc oj om or os pa pi pl ps pt qu rm rn ro ru rw sa sc sd se sg si sk sl sm sn so sq sr ss st su sv sw ta te tg th ti tk tl tn to tr ts tt tw ty ug uk ur uz ve vi vo wa wo xh yi yo za zh zu".split(" ")),Vi=new Set("aar abk afr aka amh ara arg asm ava ave aym aze bak bam bel ben bih bis bod bos bre bul cat ces cha che chu chv cor cos cre cym dan deu div dzo ell eng epo est eus ewe fao fas fij fin fra fry ful gla gle glg glv grn guj hat hau hbs heb her hin hmo hrv hun hye ibo iii iku ile ina ind ipk isl ita jav jpn kal kan kas kat kau kaz khm kik kin kir kom kon kor kua kur lao lat lav lim lin lit ltz lub lug mah mal mar mkd mlg mlt mon mri msa mya nau nav nbl nde ndo nep nld nno nob nor nya oci oji ori orm oss pan pli pol por pus que roh ron run rus sag san sin slk slv sme smo sna snd som sot spa sqi srd srp ssw sun swa swe tah tam tat tel tgk tgl tha tir ton tsn tso tuk tur twi uig ukr urd uzb ven vie vol wln wol xho yid yor zha zho zul".split(" ")),Bi=/^[a-z]{2,8}(-[a-z0-9]{1,8})*$/i;function nt(e){if(!Bi.test(e))return!1;const a=e.split("-")[0].toLowerCase();return a.length===2?Oi.has(a):a.length===3?!Vi.has(a):!1}function $e(e){var i;const a=e.ownerDocument.createTreeWalker(e,NodeFilter.SHOW_TEXT);let t;for(;t=a.nextNode();){if(!t.data.trim())continue;const n=t.parentElement;if(!n||n instanceof HTMLElement&&(n.hidden||n.style.display==="none"))continue;let o=n,r=!1;for(;o&&o!==e;){if(o.hasAttribute("lang")){r=!0;break}o=o.parentElement}if(!r)return!0}for(const n of e.querySelectorAll("img[alt]")){if(!((i=n.getAttribute("alt"))==null?void 0:i.trim()))continue;let r=n.parentElement,s=!1;for(;r&&r!==e;){if(r.hasAttribute("lang")){s=!0;break}r=r.parentElement}if(!s)return!0}return!1}const _i={id:"readable/html-lang-valid",category:"readable",actRuleIds:["bf051a"],wcag:["3.1.1"],level:"A",tags:["page-level"],fixability:"mechanical",description:"The lang attribute on <html> must have a valid value.",guidance:"The lang attribute must use a valid BCP 47 language tag. Use a 2 or 3 letter language code (e.g., 'en', 'fr', 'zh'), optionally followed by a region code (e.g., 'en-US', 'pt-BR'). Invalid tags prevent screen readers from correctly pronouncing content.",run(e){var t;const a=(t=e.documentElement.getAttribute("lang"))==null?void 0:t.trim();return a&&!nt(a)?[{ruleId:"readable/html-lang-valid",selector:"html",html:m(e.documentElement),impact:"serious",message:`Invalid lang attribute value "${a}".`,fix:{type:"set-attribute",attribute:"lang",value:"en"}}]:[]}},Gi={id:"readable/valid-lang",category:"readable",actRuleIds:["de46e4"],wcag:["3.1.2"],level:"AA",fixability:"mechanical",description:"The lang attribute must have a valid value on all elements.",guidance:"When content in a different language appears within a page (e.g., a French quote in an English document), wrap it with a lang attribute to ensure correct pronunciation. The lang value must be a valid BCP 47 tag. Common codes: en, es, fr, de, zh, ja, pt, ar, ru.",run(e){const a=[];for(const t of e.querySelectorAll("[lang]")){if(h(t)||t===e.documentElement)continue;const i=t.getAttribute("lang"),n=i==null?void 0:i.trim();if(i&&!n){$e(t)&&a.push({ruleId:"readable/valid-lang",selector:p(t),html:m(t),impact:"serious",message:"Empty lang attribute value."});continue}n&&$e(t)&&(nt(n)||a.push({ruleId:"readable/valid-lang",selector:p(t),html:m(t),impact:"serious",message:`Invalid lang attribute value "${n}".`}))}return a}},Yi={id:"readable/html-xml-lang-mismatch",category:"readable",wcag:["3.1.1"],level:"A",tags:["page-level"],fixability:"mechanical",description:"The lang and xml:lang attributes on <html> must match.",guidance:"In XHTML documents, if both lang and xml:lang are present, they must specify the same base language. Mismatched values confuse assistive technologies. Either remove xml:lang (preferred for HTML5) or ensure both attributes have identical values.",run(e){var n,o;const a=e.documentElement,t=(n=a.getAttribute("lang"))==null?void 0:n.trim().toLowerCase(),i=(o=a.getAttribute("xml:lang"))==null?void 0:o.trim().toLowerCase();if(t&&i){const r=t.split("-")[0],s=i.split("-")[0];if(r!==s)return[{ruleId:"readable/html-xml-lang-mismatch",selector:"html",html:m(a),impact:"moderate",message:`lang="${t}" and xml:lang="${i}" do not match.`,fix:{type:"remove-attribute",attribute:"xml:lang"}}]}return[]}},Xi={id:"labels-and-names/frame-title",category:"labels-and-names",actRuleIds:["cae760"],wcag:["4.1.2"],level:"A",fixability:"contextual",browserHint:"Screenshot the iframe to see what content it displays, then add a title describing its purpose.",description:"Frames must have an accessible name.",guidance:"Screen readers announce frame titles when users navigate frames. Add a title attribute to <iframe> and <frame> elements that describes the frame's purpose (e.g., <iframe title='Video player'>). Avoid generic titles like 'frame' or 'iframe'. If the frame is decorative, use aria-hidden='true'.",run(e){const a=[];for(const t of e.querySelectorAll("iframe, frame")){if(h(t)||et(t))continue;if(!y(t)){const n=t.getAttribute("src");a.push({ruleId:"labels-and-names/frame-title",selector:p(t),html:m(t),impact:"serious",message:"Frame is missing an accessible name. Add a title attribute.",context:n?`src: "${n}"`:void 0,fix:{type:"add-attribute",attribute:"title",value:""}})}}return a}},Ji={id:"labels-and-names/frame-title-unique",category:"labels-and-names",wcag:["4.1.2"],level:"A",tags:["best-practice"],fixability:"contextual",description:"Frame titles should be unique.",guidance:"When multiple frames have identical titles, screen reader users cannot distinguish between them. Give each frame a unique, descriptive title that explains its specific purpose or content.",run(e){var n;const a=[],t=Array.from(e.querySelectorAll("iframe[title], frame[title]")),i=new Map;for(const o of t){if(h(o)||et(o))continue;const r=(n=o.getAttribute("title"))==null?void 0:n.trim().toLowerCase();if(r){const s=i.get(r)||[];s.push(o),i.set(r,s)}}for(const[,o]of i)if(o.length>1)for(const r of o.slice(1))a.push({ruleId:"labels-and-names/frame-title-unique",selector:p(r),html:m(r),impact:"moderate",message:"Frame title is not unique. Use a distinct title for each frame."});return a}},ot='input:not([type="hidden"]):not([type="submit"]):not([type="button"]):not([type="reset"]):not([type="image"]), textarea, select';function Ki(e){if(e.id){const t=e.ownerDocument.querySelector(`label[for="${CSS.escape(e.id)}"]`);if(t)return t}return e.closest("label")}function Qi(e){if(e.id){const t=e.ownerDocument.querySelector(`label[for="${CSS.escape(e.id)}"]`);if(t){const i=k(t).trim();if(i)return i}}const a=e.closest("label");if(a){const t=k(a).trim();if(t)return t}return""}const Zi=['[role="checkbox"]','[role="combobox"]','[role="listbox"]','[role="menuitemcheckbox"]','[role="menuitemradio"]','[role="radio"]','[role="searchbox"]','[role="slider"]','[role="spinbutton"]','[role="switch"]','[role="textbox"]'].join(", "),en=new Set(["checkbox","menuitemcheckbox","menuitemradio","radio","switch"]),tn=new Set(["combobox","listbox","searchbox","slider","spinbutton","textbox"]);function an(e){var r,s,l,d;const a=(r=e.getAttribute("role"))==null?void 0:r.trim().toLowerCase();if(a&&en.has(a)||(e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement)&&!(a&&tn.has(a)))return y(e);const i=e.getAttribute("aria-labelledby");if(i){const c=i.split(/\s+/).map(u=>{const b=e.ownerDocument.getElementById(u);return b?k(b).trim():""}).filter(Boolean);if(c.length)return c.join(" ")}const n=(s=e.getAttribute("aria-label"))==null?void 0:s.trim();if(n)return n;if(e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement||e instanceof HTMLSelectElement){const c=Qi(e);if(c)return c}const o=(l=e.getAttribute("title"))==null?void 0:l.trim();if(o)return o;if(e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement){const c=(d=e.getAttribute("placeholder"))==null?void 0:d.trim();if(c)return c}return""}const nn={id:"labels-and-names/form-label",category:"labels-and-names",actRuleIds:["e086e5"],wcag:["4.1.2"],level:"A",fixability:"contextual",browserHint:"Screenshot the form to see visual label placement relative to the input, then associate them with a label element or aria-labelledby.",description:"Form elements must have labels. Use <label>, aria-label, or aria-labelledby.",guidance:"Every form input needs an accessible label so users understand what information to enter. Use a <label> element with a for attribute matching the input's id, wrap the input in a <label>, or use aria-label/aria-labelledby for custom components. Placeholders are not sufficient as labels since they disappear when typing. Labels should describe the information requested, not the field type (e.g., 'Email address', 'Search', 'Phone number').",run(e){var i;const a=[],t=e.querySelectorAll(`${ot}, ${Zi}`);for(const n of t){if(h(n)||I(n))continue;const o=(i=n.getAttribute("role"))==null?void 0:i.trim().toLowerCase();if(o==="presentation"||o==="none")continue;if(!an(n)){const s=[],l=n.tagName.toLowerCase(),d=n.getAttribute("type");d&&l==="input"&&s.push(`type: ${d}`);const c=n.getAttribute("name");c&&s.push(`name: "${c}"`);const u=n.getAttribute("placeholder");u&&s.push(`placeholder: "${u}"`),o&&s.push(`role: ${o}`);const b=n.getAttribute("id");b&&s.push(`id: "${b}"`),a.push({ruleId:"labels-and-names/form-label",selector:p(n),html:m(n),impact:"critical",message:"Form element has no accessible label.",context:s.length>0?s.join(", "):void 0,fix:{type:"suggest",suggestion:"Add a <label> element associated via the for attribute, or add an aria-label attribute"}})}}return a}},on={id:"labels-and-names/multiple-labels",category:"labels-and-names",wcag:["4.1.2"],level:"A",tags:["best-practice"],fixability:"contextual",description:"Form fields should not have multiple label elements.",guidance:"When a form field has multiple <label> elements pointing to it, assistive technologies may announce only one label or behave inconsistently. Use a single <label> and combine any additional text into it, or use aria-describedby for supplementary information.",run(e){const a=[],t=e.querySelectorAll('input:not([type="hidden"]), textarea, select');for(const i of t){if(h(i)||!i.id)continue;const n=e.querySelectorAll(`label[for="${CSS.escape(i.id)}"]`);let o=0,r=i.parentElement;for(;r;){if(r.tagName.toLowerCase()==="label"&&!r.hasAttribute("for")){o++;break}r=r.parentElement}const s=n.length+o;s>1&&a.push({ruleId:"labels-and-names/multiple-labels",selector:p(i),html:m(i),impact:"moderate",message:`Form field has ${s} labels. Use a single label element.`,fix:{type:"suggest",suggestion:"Consolidate multiple labels into a single <label> element, and use aria-describedby for supplementary text"}})}return a}},rn={id:"labels-and-names/input-button-name",category:"labels-and-names",wcag:["4.1.2"],level:"A",fixability:"contextual",browserHint:"Screenshot the button to see its visual purpose, then set value or aria-label to describe the action.",description:"Input buttons must have discernible text via value, aria-label, or aria-labelledby.",guidance:"Input buttons (<input type='submit'>, type='button', type='reset'>) need accessible names so users know what action the button performs. Add a value attribute with descriptive text (e.g., value='Submit Form'), or use aria-label if the value must differ from the accessible name.",run(e){var t,i;const a=[];for(const n of e.querySelectorAll('input[type="submit"], input[type="button"], input[type="reset"]')){if(h(n)||I(n))continue;const o=(t=n.getAttribute("value"))==null?void 0:t.trim(),r=(i=n.getAttribute("type"))==null?void 0:i.toLowerCase(),s=(r==="submit"||r==="reset")&&!n.hasAttribute("value");!o&&!s&&!y(n)&&a.push({ruleId:"labels-and-names/input-button-name",selector:p(n),html:m(n),impact:"critical",message:"Input button has no discernible text.",fix:{type:"add-attribute",attribute:"value",value:""}})}return a}};function He(e){return e.toLowerCase().replace(/\s+/g," ").trim()}function Pe(e,a){const t=He(e),i=He(a);if(!t||!i||t.includes(i)||i.includes(t))return!0;const n=i.split(/\s+/).map(o=>o.replace(/[.,;:!?\u2026]+$/g,"")).filter(o=>o.length>2);return n.length>=2&&n.filter(r=>t.includes(r)).length/n.length>.5}const sn={id:"labels-and-names/label-content-mismatch",category:"labels-and-names",actRuleIds:["2ee8b8"],wcag:["2.5.3"],level:"A",tags:["best-practice"],fixability:"contextual",browserHint:"Screenshot the control to see its visible label, then ensure aria-label starts with that visible text.",description:"Interactive elements with visible text must have accessible names that contain that text.",guidance:"For voice control users who activate controls by speaking their visible label, the accessible name must include the visible text. If aria-label is 'Submit form' but the button shows 'Send', voice users saying 'click Send' won't activate it. Ensure aria-label/aria-labelledby contains or matches the visible text.",run(e){const a=[];for(const t of e.querySelectorAll('button, [role="button"], a[href], input[type="submit"], input[type="button"]')){if(h(t))continue;const i=y(t);if(!i)continue;let n="";t instanceof HTMLInputElement?n=t.value||"":n=te(t);const o=n.trim();if(!o||o.length<=2)continue;const r=t.hasAttribute("aria-label"),s=t.hasAttribute("aria-labelledby");!r&&!s||Pe(i,n)||a.push({ruleId:"labels-and-names/label-content-mismatch",selector:p(t),html:m(t),impact:"serious",message:`Accessible name "${i}" does not contain visible text "${n.trim()}".`,fix:{type:"suggest",suggestion:"Update aria-label to include the visible text content so voice control users can activate this element by speaking its label"}})}for(const t of e.querySelectorAll("input, select, textarea")){if(h(t)||t instanceof HTMLInputElement&&["hidden","submit","button","image"].includes(t.type))continue;const i=y(t);if(!i||!t.hasAttribute("aria-label"))continue;const o=t.id;let r="";if(o){const s=e.querySelector(`label[for="${CSS.escape(o)}"]`);s&&(r=te(s))}r.trim()&&(Pe(i,r)||a.push({ruleId:"labels-and-names/label-content-mismatch",selector:p(t),html:m(t),impact:"serious",message:`Accessible name "${i}" does not contain visible label "${r.trim()}".`,fix:{type:"suggest",suggestion:"Update aria-label to include the visible label text so voice control users can activate this element by speaking its label"}}))}return a}},ln={id:"labels-and-names/label-title-only",category:"labels-and-names",wcag:["4.1.2"],level:"A",tags:["best-practice"],fixability:"contextual",description:"Form elements should not use title attribute as the only accessible name.",guidance:"The title attribute is unreliable as a label because it only appears on hover/focus (not visible to touch users) and is often ignored by assistive technologies. Use a visible <label> element, aria-label, or aria-labelledby instead. Title can supplement a label but should not replace it.",run(e){var i,n,o;const a=[],t=e.querySelectorAll(ot);for(const r of t){if(h(r))continue;const s=r.hasAttribute("title")&&((i=r.getAttribute("title"))==null?void 0:i.trim()),l=r.hasAttribute("aria-label")&&((n=r.getAttribute("aria-label"))==null?void 0:n.trim()),d=r.hasAttribute("aria-labelledby"),c=Ki(r),u=!!((o=c==null?void 0:c.textContent)!=null&&o.trim());s&&!l&&!d&&!u&&a.push({ruleId:"labels-and-names/label-title-only",selector:p(r),html:m(r),impact:"serious",message:"Form element uses title attribute as only label. Use <label>, aria-label, or aria-labelledby instead.",fix:{type:"suggest",suggestion:"Add a visible <label> element or aria-label attribute, and optionally keep the title as supplementary text"}})}return a}},cn={id:"labels-and-names/aria-command-name",category:"labels-and-names",actRuleIds:["m6b1q3"],wcag:["4.1.2"],level:"A",fixability:"contextual",description:"ARIA commands must have an accessible name.",guidance:"Interactive ARIA command roles (button, link, menuitem) must have accessible names so users know what action they perform. Add visible text content, aria-label, or aria-labelledby to provide a name.",run(e){var t;const a=[];for(const i of e.querySelectorAll('[role="button"], [role="link"], [role="menuitem"]')){if(h(i)||I(i)||ce(i))continue;const n=i.getAttribute("role");if((i.tagName.toLowerCase()==="button"||i.tagName.toLowerCase()==="a")&&n!=="menuitem")continue;if(!y(i)){const r=i.querySelector("img[alt]");if((t=r==null?void 0:r.getAttribute("alt"))!=null&&t.trim())continue;a.push({ruleId:"labels-and-names/aria-command-name",selector:p(i),html:m(i),impact:"serious",message:"ARIA command has no accessible name.",fix:{type:"add-attribute",attribute:"aria-label",value:""}})}}return a}};function M(e){return{id:e.id,category:e.id.split("/")[0],...e.actRuleIds?{actRuleIds:e.actRuleIds}:{},wcag:["4.1.2"],level:"A",...e.fixability?{fixability:e.fixability}:{},description:e.description,guidance:e.guidance,run(a){var i;const t=[];for(const n of a.querySelectorAll(e.selector)){if(h(n)||e.checkComputedHidden&&I(n)||e.checkShadowDOM&&ce(n))continue;if(e.roleSet){const r=(i=n.getAttribute("role"))==null?void 0:i.trim().toLowerCase();if(!r||!e.roleSet.has(r))continue}if(e.skipNative&&n.matches(e.skipNative))continue;y(n)||t.push({ruleId:e.id,selector:p(n),html:m(n),impact:"serious",message:e.message,...e.fix?{fix:e.fix}:{}})}return t}}}const dn=M({id:"labels-and-names/aria-input-field-name",selector:'[role="combobox"], [role="listbox"], [role="searchbox"], [role="slider"], [role="spinbutton"], [role="textbox"]',message:"ARIA input field has no accessible name.",fixability:"contextual",description:"ARIA input fields must have an accessible name.",guidance:"ARIA input widgets (combobox, listbox, searchbox, slider, spinbutton, textbox) must have accessible names so users understand what data to enter. Add a visible label with aria-labelledby, or use aria-label if a visible label is not possible.",fix:{type:"add-attribute",attribute:"aria-label",value:""},checkComputedHidden:!0,checkShadowDOM:!0,skipNative:"input, select, textarea"}),un=M({id:"labels-and-names/aria-toggle-field-name",selector:'[role="checkbox"], [role="switch"], [role="radio"], [role="menuitemcheckbox"], [role="menuitemradio"]',message:"ARIA toggle field has no accessible name.",fixability:"contextual",description:"ARIA toggle fields must have an accessible name.",guidance:"ARIA toggle controls (checkbox, switch, radio, menuitemcheckbox, menuitemradio) must have accessible names so users understand what option they're selecting. Add visible text content, aria-label, or use aria-labelledby to reference a visible label.",fix:{type:"add-attribute",attribute:"aria-label",value:""},checkComputedHidden:!0,checkShadowDOM:!0,skipNative:'input[type="checkbox"], input[type="radio"]'}),mn=M({id:"labels-and-names/aria-meter-name",fixability:"contextual",description:"ARIA meter elements must have an accessible name.",guidance:"Meter elements display a value within a known range (like disk usage or password strength). They must have accessible names so screen reader users understand what is being measured. Use aria-label or aria-labelledby to provide context.",selector:'[role="meter"], meter',message:"Meter has no accessible name.",fix:{type:"add-attribute",attribute:"aria-label",value:""}}),pn=M({id:"labels-and-names/aria-progressbar-name",fixability:"contextual",description:"ARIA progressbar elements must have an accessible name.",guidance:"Progress indicators must have accessible names so screen reader users understand what process is being tracked. Use aria-label (e.g., 'File upload progress') or aria-labelledby to reference a visible heading or label.",selector:'[role="progressbar"], progress',message:"Progressbar has no accessible name.",fix:{type:"add-attribute",attribute:"aria-label",value:""}}),bn=M({id:"labels-and-names/aria-dialog-name",fixability:"contextual",description:"ARIA dialogs must have an accessible name.",guidance:"Dialog and alertdialog elements must have accessible names so screen reader users understand the dialog's purpose when it opens. Use aria-label or aria-labelledby pointing to the dialog's heading. Native <dialog> elements should also have an accessible name.",selector:'[role="dialog"], [role="alertdialog"], dialog',message:"Dialog has no accessible name.",fix:{type:"add-attribute",attribute:"aria-label",value:""}}),hn=M({id:"labels-and-names/aria-tooltip-name",fixability:"contextual",description:"ARIA tooltips must have an accessible name.",guidance:"Tooltip elements must have accessible names (usually their text content). The tooltip content itself typically serves as the accessible name. Ensure the tooltip contains descriptive text content or has aria-label.",selector:'[role="tooltip"]',message:"Tooltip has no accessible name.",fix:{type:"add-text-content"}}),gn=M({id:"labels-and-names/aria-treeitem-name",fixability:"contextual",description:"ARIA treeitem elements must have an accessible name.",guidance:"Tree items must have accessible names so screen reader users can understand the tree structure and navigate it effectively. Provide text content, aria-label, or aria-labelledby for each treeitem.",selector:'[role="treeitem"]',message:"Treeitem has no accessible name.",fix:{type:"add-text-content"}});function fn(e){var o,r,s;const a=[],t=e.className;t&&typeof t=="string"&&t.trim()&&a.push(`Classes: ${t.trim().slice(0,100)}`);const i=e.closest("form");if(i){const l=i.getAttribute("aria-label")||((r=(o=i.querySelector("legend"))==null?void 0:o.textContent)==null?void 0:r.trim());l&&a.push(`Form: ${l.slice(0,60)}`)}const n=e.parentElement;if(n){const l=n.closest("h1, h2, h3, h4, h5, h6")||n.querySelector("h1, h2, h3, h4, h5, h6");(s=l==null?void 0:l.textContent)!=null&&s.trim()&&a.push(`Nearby heading: ${l.textContent.trim().slice(0,60)}`)}return a.length>0?a.join(`
|
|
4
4
|
`):void 0}const vn={id:"labels-and-names/button-name",category:"labels-and-names",actRuleIds:["97a4e1"],wcag:["4.1.2"],level:"A",fixability:"contextual",browserHint:"Screenshot the button to identify its icon or visual label, then add a matching aria-label.",description:"Buttons must have discernible text.",guidance:"Screen reader users need to know what a button does. Add visible text content, aria-label, or aria-labelledby. For icon buttons, use aria-label describing the action (e.g., aria-label='Close'). If the button contains an image, ensure the image has alt text describing the button's action.",run(e){const a=[];for(const t of e.querySelectorAll('button, [role="button"]')){if(h(t)||I(t))continue;const i=t.getAttribute("role");if((i==="none"||i==="presentation")&&!(t.matches('button:not([disabled]), [tabindex]:not([tabindex="-1"])')||t.tagName.toLowerCase()==="button"&&!t.disabled)||ce(t))continue;y(t)||a.push({ruleId:"labels-and-names/button-name",selector:p(t),html:m(t),impact:"critical",message:"Button has no discernible text.",context:fn(t),fix:{type:"add-text-content"}})}return a}},yn={id:"labels-and-names/summary-name",category:"labels-and-names",actRuleIds:["2t702h"],wcag:["4.1.2"],level:"A",fixability:"contextual",description:"<summary> elements must have an accessible name.",guidance:"The <summary> element provides the visible label for a <details> disclosure widget. It must have descriptive text content so screen reader users understand what will be revealed when expanded. Add clear, concise text that indicates what content is contained in the details section.",run(e){const a=[];for(const t of e.querySelectorAll("details > summary:first-of-type")){if(h(t))continue;y(t)||a.push({ruleId:"labels-and-names/summary-name",selector:p(t),html:m(t),impact:"serious",message:"<summary> element has no accessible name. Add descriptive text.",fix:{type:"add-text-content"}})}return a}},se=["aria-labelledby","aria-describedby","aria-controls","aria-owns","aria-flowto"];function wn(e){const a=new Set;for(const t of e.querySelectorAll("[aria-labelledby], [aria-describedby], [aria-controls], [aria-owns], [aria-flowto]"))for(const i of se){const n=t.getAttribute(i);n&&n.split(/\s+/).forEach(o=>{o&&a.add(o)})}for(const t of e.querySelectorAll("label[for]")){const i=t.getAttribute("for");i!=null&&i.trim()&&a.add(i.trim())}return a}const xn={id:"labels-and-names/duplicate-id-aria",category:"labels-and-names",wcag:["4.1.2"],level:"A",fixability:"contextual",description:"IDs used in ARIA and label associations must be unique to avoid broken references.",guidance:"When aria-labelledby, aria-describedby, aria-controls, or label[for] reference a duplicate ID, only the first matching element is used. This breaks the intended relationship and may leave controls unnamed or descriptions missing. Ensure IDs referenced by ARIA attributes and label associations are unique throughout the document.",run(e){const a=[],t=wn(e),i=new Map;for(const n of e.querySelectorAll("[id]"))t.has(n.id)&&(n instanceof HTMLElement&&(n.style.display==="none"||n.style.visibility==="hidden"||n.hidden)||i.set(n.id,(i.get(n.id)??0)+1));for(const[n,o]of i){if(o<=1)continue;const r=e.querySelectorAll(`#${CSS.escape(n)}`),s=e.querySelector(se.map(c=>`[${c}~="${CSS.escape(n)}"]`).join(", ")),l=e.querySelector(`label[for="${CSS.escape(n)}"]`);let d;if(s){const c=se.find(u=>{var b;return(b=s.getAttribute(u))==null?void 0:b.split(/\s+/).includes(n)});c&&(d=c)}else l&&(d="label[for]");a.push({ruleId:"labels-and-names/duplicate-id-aria",selector:p(r[1]),html:m(r[1]),impact:"critical",message:`Duplicate ID "${n}" referenced by ${d??"an accessibility attribute"}.`,context:`First element: ${m(r[0])}${d?`
|
|
5
|
-
Referenced by: ${d}`:""}`,fix:{type:"suggest",suggestion:"Change the duplicate ID to a unique value so the ARIA or label reference points to the correct element"}})}return a}},An={id:"input-assistance/accessible-authentication",category:"input-assistance",wcag:["3.3.8"],level:"AA",fixability:"mechanical",description:'Password inputs must not block password managers. Avoid autocomplete="off" and allow pasting.',guidance:'WCAG 2.2 SC 3.3.8 requires that authentication steps either avoid cognitive function tests or provide a mechanism to assist users. Password managers are a key assistive mechanism. Setting autocomplete="off" on password fields prevents password managers from filling credentials. Blocking paste via onpaste attributes prevents users from pasting stored passwords. Set autocomplete to "current-password" for login forms or "new-password" for registration/change-password forms, and do not block paste on password fields.',run(e){var t;const a=[];for(const i of e.querySelectorAll('input[type="password"]')){if(h(i)||I(i)||i.disabled||i.getAttribute("aria-disabled")==="true")continue;if(((t=i.getAttribute("autocomplete"))==null?void 0:t.trim().toLowerCase())==="off"){a.push({ruleId:"input-assistance/accessible-authentication",selector:p(i),html:m(i),impact:"critical",message:'Password field has autocomplete="off" which blocks password managers.',fix:{type:"set-attribute",attribute:"autocomplete",value:"current-password"}});continue}const o=i.getAttribute("onpaste");o&&/return\s+false|preventDefault/.test(o)&&a.push({ruleId:"input-assistance/accessible-authentication",selector:p(i),html:m(i),impact:"critical",message:"Password field blocks pasting, preventing password manager use.",fix:{type:"remove-attribute",attribute:"onpaste"}})}return a}},kn={id:"aria/aria-roles",category:"aria",actRuleIds:["674b10"],wcag:["4.1.2"],level:"A",fixability:"contextual",description:"ARIA role values must be valid.",guidance:"Invalid role values are ignored by assistive technologies, meaning the element will not have the intended semantics. Check the spelling and use only roles defined in the WAI-ARIA specification. Common roles include: button, link, navigation, main, dialog, alert, tab, tabpanel, menu, menuitem.",run(e){const a=[];for(const t of e.querySelectorAll("[role]")){const o=t.getAttribute("role").replace(/[\u201C\u201D\u2018\u2019\u00AB\u00BB]/g,"").split(/\s+/).filter(Boolean);!o.some(s=>ze(s))&&o.length>0&&a.push({ruleId:"aria/aria-roles",selector:p(t),html:m(t),impact:"critical",message:`Invalid ARIA role "${o[0]}".`,fix:{type:"remove-attribute",attribute:"role"}})}return a}},Sn={id:"aria/aria-valid-attr",category:"aria",actRuleIds:["5f99a7"],wcag:["4.1.2"],level:"A",fixability:"mechanical",description:"ARIA attributes must be valid (correctly spelled).",guidance:"Misspelled ARIA attributes are ignored by assistive technologies. Check the spelling against the WAI-ARIA specification. Common mistakes: aria-labeledby (should be aria-labelledby), aria-role (should be role), aria-description (valid in ARIA 1.3+).",run(e){return de(e).validAttr}},In={id:"aria/aria-valid-attr-value",category:"aria",actRuleIds:["6a7281"],wcag:["4.1.2"],level:"A",fixability:"contextual",description:"ARIA attributes must have valid values.",guidance:"Each ARIA attribute accepts specific value types. Boolean attributes (aria-hidden, aria-disabled) accept only 'true' or 'false'. Tristate attributes (aria-checked, aria-pressed) also accept 'mixed'. Token attributes (aria-live, aria-autocomplete) accept predefined values. ID reference attributes (aria-labelledby, aria-describedby) must reference existing element IDs.",run(e){return de(e).validAttrValue}},qn={checkbox:["aria-checked"],combobox:["aria-expanded"],heading:["aria-level"],menuitemcheckbox:["aria-checked"],menuitemradio:["aria-checked"],meter:["aria-valuenow"],radio:["aria-checked"],scrollbar:["aria-controls","aria-valuenow"],separator:["aria-valuenow"],slider:["aria-valuenow"],spinbutton:["aria-valuenow"],switch:["aria-checked"]},En={id:"aria/aria-required-attr",category:"aria",actRuleIds:["4e8ab6"],wcag:["4.1.2"],level:"A",fixability:"contextual",description:"Elements with ARIA roles must have all required ARIA attributes.",guidance:"Some ARIA roles require specific attributes to function correctly. For example, checkbox requires aria-checked, slider requires aria-valuenow, heading requires aria-level. Without these attributes, assistive technologies cannot convey the element's state or value to users. Add the missing required attribute with an appropriate value.",run(e){const a=[];for(const t of e.querySelectorAll("[role]")){if(h(t)||t instanceof HTMLElement&&t.style.display==="none")continue;const i=t.getAttribute("role").trim().toLowerCase(),n=qn[i];if(n&&!(i==="checkbox"&&t instanceof HTMLInputElement&&t.type==="checkbox")&&!(i==="radio"&&t instanceof HTMLInputElement&&t.type==="radio")&&!(i==="option"&&t instanceof HTMLOptionElement)&&!(i==="heading"&&/^h[1-6]$/i.test(t.tagName))){if(i==="separator"){const o=t.getAttribute("tabindex");if(!o||o==="-1")continue}if(!(t.tagName.toLowerCase()==="hr"&&!t.hasAttribute("role"))){for(const o of n)if(!t.hasAttribute(o)){a.push({ruleId:"aria/aria-required-attr",selector:p(t),html:m(t),impact:"critical",message:`Role "${i}" requires attribute "${o}".`,fix:{type:"add-attribute",attribute:o,value:""}});break}}}}return a}},Ln={alert:new Set(["aria-atomic","aria-busy","aria-live","aria-relevant"]),alertdialog:new Set(["aria-describedby","aria-modal"]),application:new Set(["aria-activedescendant","aria-disabled","aria-errormessage","aria-expanded","aria-haspopup","aria-invalid"]),article:new Set(["aria-posinset","aria-setsize"]),banner:new Set([]),button:new Set(["aria-disabled","aria-expanded","aria-haspopup","aria-pressed"]),cell:new Set(["aria-colindex","aria-colspan","aria-rowindex","aria-rowspan"]),checkbox:new Set(["aria-checked","aria-disabled","aria-errormessage","aria-expanded","aria-invalid","aria-readonly","aria-required"]),columnheader:new Set(["aria-colindex","aria-colspan","aria-disabled","aria-errormessage","aria-expanded","aria-haspopup","aria-invalid","aria-readonly","aria-required","aria-rowindex","aria-rowspan","aria-selected","aria-sort"]),combobox:new Set(["aria-activedescendant","aria-autocomplete","aria-controls","aria-disabled","aria-errormessage","aria-expanded","aria-haspopup","aria-invalid","aria-readonly","aria-required"]),complementary:new Set([]),contentinfo:new Set([]),definition:new Set([]),dialog:new Set(["aria-describedby","aria-modal"]),directory:new Set([]),document:new Set(["aria-expanded"]),feed:new Set(["aria-busy"]),figure:new Set([]),form:new Set([]),grid:new Set(["aria-activedescendant","aria-colcount","aria-disabled","aria-multiselectable","aria-readonly","aria-rowcount"]),gridcell:new Set(["aria-colindex","aria-colspan","aria-disabled","aria-errormessage","aria-expanded","aria-haspopup","aria-invalid","aria-readonly","aria-required","aria-rowindex","aria-rowspan","aria-selected"]),group:new Set(["aria-activedescendant","aria-disabled"]),heading:new Set(["aria-level"]),img:new Set([]),link:new Set(["aria-disabled","aria-expanded","aria-haspopup"]),list:new Set([]),listbox:new Set(["aria-activedescendant","aria-disabled","aria-errormessage","aria-expanded","aria-invalid","aria-multiselectable","aria-orientation","aria-readonly","aria-required"]),listitem:new Set(["aria-level","aria-posinset","aria-setsize"]),log:new Set(["aria-atomic","aria-busy","aria-live","aria-relevant"]),main:new Set([]),marquee:new Set([]),math:new Set([]),menu:new Set(["aria-activedescendant","aria-disabled","aria-orientation"]),menubar:new Set(["aria-activedescendant","aria-disabled","aria-orientation"]),menuitem:new Set(["aria-disabled","aria-expanded","aria-haspopup","aria-posinset","aria-setsize"]),menuitemcheckbox:new Set(["aria-checked","aria-disabled","aria-expanded","aria-haspopup","aria-posinset","aria-setsize"]),menuitemradio:new Set(["aria-checked","aria-disabled","aria-expanded","aria-haspopup","aria-posinset","aria-setsize"]),meter:new Set(["aria-valuemax","aria-valuemin","aria-valuenow","aria-valuetext"]),navigation:new Set([]),none:new Set([]),note:new Set([]),option:new Set(["aria-checked","aria-disabled","aria-posinset","aria-selected","aria-setsize"]),paragraph:new Set([]),presentation:new Set([]),progressbar:new Set(["aria-valuemax","aria-valuemin","aria-valuenow","aria-valuetext"]),radio:new Set(["aria-checked","aria-disabled","aria-posinset","aria-setsize"]),radiogroup:new Set(["aria-activedescendant","aria-disabled","aria-errormessage","aria-invalid","aria-orientation","aria-readonly","aria-required"]),region:new Set([]),row:new Set(["aria-activedescendant","aria-colindex","aria-disabled","aria-expanded","aria-level","aria-posinset","aria-rowindex","aria-selected","aria-setsize"]),rowgroup:new Set([]),rowheader:new Set(["aria-colindex","aria-colspan","aria-disabled","aria-errormessage","aria-expanded","aria-haspopup","aria-invalid","aria-readonly","aria-required","aria-rowindex","aria-rowspan","aria-selected","aria-sort"]),scrollbar:new Set(["aria-controls","aria-disabled","aria-orientation","aria-valuemax","aria-valuemin","aria-valuenow"]),search:new Set([]),searchbox:new Set(["aria-activedescendant","aria-autocomplete","aria-disabled","aria-errormessage","aria-haspopup","aria-invalid","aria-multiline","aria-placeholder","aria-readonly","aria-required"]),separator:new Set(["aria-disabled","aria-orientation","aria-valuemax","aria-valuemin","aria-valuenow"]),slider:new Set(["aria-disabled","aria-errormessage","aria-haspopup","aria-invalid","aria-orientation","aria-readonly","aria-valuemax","aria-valuemin","aria-valuenow","aria-valuetext"]),spinbutton:new Set(["aria-activedescendant","aria-disabled","aria-errormessage","aria-invalid","aria-readonly","aria-required","aria-valuemax","aria-valuemin","aria-valuenow","aria-valuetext"]),status:new Set(["aria-atomic","aria-live","aria-relevant"]),switch:new Set(["aria-checked","aria-disabled","aria-errormessage","aria-invalid","aria-readonly","aria-required"]),tab:new Set(["aria-disabled","aria-expanded","aria-haspopup","aria-posinset","aria-selected","aria-setsize"]),table:new Set(["aria-colcount","aria-rowcount"]),tablist:new Set(["aria-activedescendant","aria-disabled","aria-multiselectable","aria-orientation"]),tabpanel:new Set([]),term:new Set([]),textbox:new Set(["aria-activedescendant","aria-autocomplete","aria-disabled","aria-errormessage","aria-haspopup","aria-invalid","aria-multiline","aria-placeholder","aria-readonly","aria-required"]),timer:new Set(["aria-atomic","aria-live","aria-relevant"]),toolbar:new Set(["aria-activedescendant","aria-disabled","aria-orientation"]),tooltip:new Set([]),tree:new Set(["aria-activedescendant","aria-disabled","aria-errormessage","aria-invalid","aria-multiselectable","aria-orientation","aria-required"]),treegrid:new Set(["aria-activedescendant","aria-colcount","aria-disabled","aria-errormessage","aria-invalid","aria-multiselectable","aria-orientation","aria-readonly","aria-required","aria-rowcount"]),treeitem:new Set(["aria-checked","aria-disabled","aria-expanded","aria-haspopup","aria-level","aria-posinset","aria-selected","aria-setsize"])},Rn={id:"aria/aria-allowed-attr",category:"aria",actRuleIds:["5c01ea"],wcag:["4.1.2"],level:"A",fixability:"mechanical",description:"ARIA attributes must be allowed for the element's role.",guidance:"Each ARIA role supports specific attributes. Using unsupported attributes creates confusion for assistive technologies. Check the ARIA specification for which attributes are valid for each role, or remove the attribute if it's not needed.",run(e){const a=[],t=new Set(e.querySelectorAll("[role]")),i=e.createTreeWalker(e.body||e.documentElement,1);let n=i.currentNode;for(;n;){if(n instanceof Element){for(const o of n.attributes)if(o.name.startsWith("aria-")){t.add(n);break}}n=i.nextNode()}for(const o of t){if(h(o))continue;const r=H(o);if(!r)continue;const s=Ln[r];if(s)for(const l of o.attributes){if(!l.name.startsWith("aria-")||bt.has(l.name)||s.has(l.name))continue;const d=s.size>0?[...s].join(", "):"none (only global ARIA attributes)";a.push({ruleId:"aria/aria-allowed-attr",selector:p(o),html:m(o),impact:"critical",message:`ARIA attribute "${l.name}" is not allowed on role "${r}".`,context:`Attribute: ${l.name}="${l.value}", role: ${r}, allowed role-specific attributes: ${d}`,fix:{type:"remove-attribute",attribute:l.name}})}}return a}},Cn=new Set(["base","col","colgroup","head","html","keygen","meta","param","script","source","style","template","title","track"]),D={a:new Set(["button","checkbox","menuitem","menuitemcheckbox","menuitemradio","option","radio","switch","tab","treeitem","link"]),"a[href]":new Set(["button","checkbox","menuitem","menuitemcheckbox","menuitemradio","option","radio","switch","tab","treeitem"]),abbr:"any",address:"any",article:new Set(["application","document","feed","main","none","presentation","region"]),aside:new Set(["doc-dedication","doc-example","doc-footnote","doc-glossary","doc-pullquote","doc-tip","feed","none","note","presentation","region","search"]),audio:new Set(["application"]),b:"any",bdi:"any",bdo:"any",blockquote:"any",body:"none",br:new Set(["none","presentation"]),button:new Set(["checkbox","combobox","link","menuitem","menuitemcheckbox","menuitemradio","option","radio","slider","switch","tab"]),canvas:"any",caption:"none",cite:"any",code:"any",data:"any",datalist:new Set(["listbox"]),dd:"none",del:"any",details:new Set(["group"]),dfn:"any",dialog:new Set(["alertdialog"]),div:"any",dl:new Set(["group","list","none","presentation"]),dt:new Set(["listitem"]),em:"any",embed:new Set(["application","document","img","none","presentation"]),fieldset:new Set(["group","none","presentation","radiogroup"]),figcaption:new Set(["group","none","presentation"]),figure:new Set(["doc-example","none","presentation"]),footer:new Set(["doc-footnote","group","none","presentation"]),form:new Set(["none","presentation","search"]),h1:new Set(["doc-subtitle","none","presentation","tab"]),h2:new Set(["doc-subtitle","none","presentation","tab"]),h3:new Set(["doc-subtitle","none","presentation","tab"]),h4:new Set(["doc-subtitle","none","presentation","tab"]),h5:new Set(["doc-subtitle","none","presentation","tab"]),h6:new Set(["doc-subtitle","none","presentation","tab"]),header:new Set(["group","none","presentation"]),hgroup:"any",hr:new Set(["doc-pagebreak","none","presentation"]),i:"any",iframe:new Set(["application","document","img","none","presentation"]),img:"any","img[alt='']":new Set(["none","presentation"]),input:"none","input[type=button]":new Set(["checkbox","combobox","link","menuitem","menuitemcheckbox","menuitemradio","option","radio","slider","spinbutton","switch","tab"]),"input[type=checkbox]":new Set(["button","menuitemcheckbox","option","switch"]),"input[type=image]":new Set(["link","menuitem","menuitemcheckbox","menuitemradio","radio","switch"]),"input[type=radio]":new Set(["menuitemradio"]),"input[type=search]":new Set(["combobox","searchbox"]),"input[type=text]":new Set(["combobox","searchbox","spinbutton"]),ins:"any",kbd:"any",label:"none",legend:"none",li:new Set(["doc-biblioentry","doc-endnote","menuitem","menuitemcheckbox","menuitemradio","none","option","presentation","radio","separator","tab","treeitem"]),main:"none",map:"none",mark:"any",menu:new Set(["directory","group","listbox","menu","menubar","none","presentation","radiogroup","tablist","toolbar","tree"]),meter:"none",nav:new Set(["doc-index","doc-pagelist","doc-toc","menu","menubar","none","presentation","tablist"]),noscript:"none",object:new Set(["application","document","img"]),ol:new Set(["directory","group","listbox","menu","menubar","none","presentation","radiogroup","tablist","toolbar","tree"]),optgroup:new Set(["group"]),option:"none",output:new Set(["status"]),p:"any",picture:"none",pre:"any",progress:"none",q:"any",rp:"any",rt:"any",ruby:"any",s:"any",samp:"any",section:new Set(["alert","alertdialog","application","banner","complementary","contentinfo","dialog","doc-abstract","doc-acknowledgments","doc-afterword","doc-appendix","doc-bibliography","doc-chapter","doc-colophon","doc-conclusion","doc-credit","doc-credits","doc-dedication","doc-endnotes","doc-epigraph","doc-epilogue","doc-errata","doc-example","doc-foreword","doc-glossary","doc-index","doc-introduction","doc-notice","doc-pagelist","doc-part","doc-preface","doc-prologue","doc-pullquote","doc-qna","doc-toc","document","feed","group","log","main","marquee","navigation","none","note","presentation","region","search","status","tabpanel"]),select:new Set(["menu"]),small:"any",span:"any",strong:"any",sub:"any",summary:"none",sup:"any",svg:new Set(["application","document","img","none","presentation"]),table:"any",tbody:"any",td:"any",tfoot:"any",th:"any",thead:"any",time:"any",tr:"any",u:"any",ul:new Set(["directory","group","listbox","menu","menubar","none","presentation","radiogroup","tablist","toolbar","tree"]),var:"any",video:new Set(["application"]),wbr:new Set(["none","presentation"])};function Tn(e){var t;const a=e.tagName.toLowerCase();if(Cn.has(a))return"none";if(a==="a"&&e.hasAttribute("href"))return D["a[href]"];if(a==="img"&&e.getAttribute("alt")==="")return D["img[alt='']"];if(a==="input"){const n=`input[type=${((t=e.getAttribute("type"))==null?void 0:t.toLowerCase())||"text"}]`;return n in D?D[n]:"none"}return D[a]||"any"}const Nn={id:"aria/aria-allowed-role",category:"aria",wcag:["4.1.2"],level:"A",fixability:"contextual",description:"ARIA role must be appropriate for the element.",guidance:"Not all ARIA roles can be applied to all HTML elements. Many elements have implicit roles (e.g., <header> is implicitly banner, <nav> is navigation, <main> is main). Adding an explicit role that matches the implicit role is redundant. Adding a conflicting role breaks semantics. Either remove the role attribute or use a different element.",run(e){var t;const a=[];for(const i of e.querySelectorAll("[role]")){if(h(i))continue;const n=(t=i.getAttribute("role"))==null?void 0:t.trim().toLowerCase();if(!n)continue;const o=le(i);if(o&&n===o)continue;const r=Tn(i);r==="none"?a.push({ruleId:"aria/aria-allowed-role",selector:p(i),html:m(i),impact:"minor",message:`Element <${i.tagName.toLowerCase()}> should not have an explicit role.`}):r!=="any"&&!r.has(n)&&a.push({ruleId:"aria/aria-allowed-role",selector:p(i),html:m(i),impact:"minor",message:`Role "${n}" is not allowed on element <${i.tagName.toLowerCase()}>.`})}return a}},Mn={id:"aria/aria-hidden-body",selector:'body[aria-hidden="true"]',check:{type:"selector-exists"},impact:"critical",message:"aria-hidden='true' on body hides all content from assistive technologies.",description:"aria-hidden='true' must not be present on the document body.",wcag:["4.1.2"],level:"A",tags:["page-level"],fixability:"mechanical",guidance:"Setting aria-hidden='true' on the body element hides all page content from assistive technologies, making the page completely inaccessible to screen reader users. Remove aria-hidden from the body element. If you need to hide content temporarily (e.g., behind a modal), use aria-hidden on specific sections instead.",skipAriaHidden:!1},$n=E(Mn);function Hn(e){let a=e;const t=e.ownerDocument,i=t.defaultView;for(;a&&a!==t.body;){if(a.style.display==="none"||a.style.visibility==="hidden")return!1;if(i){const n=i.getComputedStyle(a);if(n.display==="none"||n.visibility==="hidden")return!1}a=a.parentElement}return!0}function Pn(e){const a=e.ownerDocument.defaultView;if(!a)return!1;const t=a.getComputedStyle(e),i=t.position;if(i!=="absolute"&&i!=="fixed")return!1;const n=parseFloat(t.top),o=parseFloat(t.left);if(!(!isNaN(n)&&n<-500||!isNaN(o)&&o<-500))return!1;const s=e.id;if(!s)return!1;const l=s.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),d=new RegExp(`getElementById\\s*\\(\\s*['"]${l}['"]\\s*\\)\\s*\\.\\s*addEventListener\\s*\\(\\s*['"]focus['"]`);for(const c of e.ownerDocument.querySelectorAll("script")){const u=c.textContent||"";if(d.test(u)&&/\.focus\s*\(/.test(u))return!0}return!1}const Dn={id:"aria/aria-hidden-focus",category:"aria",actRuleIds:["6cfa84"],wcag:["4.1.2"],level:"A",fixability:"contextual",description:"Elements with aria-hidden='true' must not contain focusable elements.",guidance:"When aria-hidden='true' hides an element from assistive technologies but the element contains focusable children, keyboard users can focus those children but screen reader users won't know they exist. Either remove focusable elements from the hidden region, add tabindex='-1' to them, or remove aria-hidden.",run(e){const a=[];for(const t of e.querySelectorAll('[aria-hidden="true"]')){if(t===e.body)continue;const i=[...t.querySelectorAll(W)];t.matches(W)&&i.push(t);for(const n of i)if(n instanceof HTMLElement){const o=n.getAttribute("tabindex");if(o==="-1"||n.disabled||n instanceof HTMLInputElement&&n.type==="hidden"||!Hn(n))continue;const r=n.getAttribute("onfocus")||"";if(/\.focus\s*\(/.test(r)||Pn(n))continue;const s=n.tagName.toLowerCase();let l;o!==null?l=`has tabindex="${o}"`:s==="a"&&n.hasAttribute("href")?l="is a link with href":s==="button"?l="is a <button>":s==="input"?l=`is an <input type="${n.type}">`:s==="select"?l="is a <select>":s==="textarea"?l="is a <textarea>":s==="iframe"?l="is an <iframe>":l=`is a natively focusable <${s}>`;const d=n===t?n:n.closest('[aria-hidden="true"]');a.push({ruleId:"aria/aria-hidden-focus",selector:p(n),html:m(n),impact:"serious",message:"Focusable element is inside an aria-hidden region.",context:`Focusable because: ${l}. aria-hidden ancestor: ${d?m(d):"unknown"}`,fix:{type:"suggest",suggestion:'Add tabindex="-1" to remove from tab order, or move the element outside the aria-hidden region'}})}}return a}},Fn={id:"aria/aria-prohibited-attr",category:"aria",actRuleIds:["kb1m8s"],wcag:["4.1.2"],level:"A",fixability:"mechanical",description:"ARIA attributes must not be prohibited for the element's role.",guidance:"Some ARIA roles prohibit certain attributes. For example, roles like 'none', 'presentation', 'generic', and text-level roles (code, emphasis, strong) prohibit aria-label and aria-labelledby because naming is not supported for these roles. Remove the prohibited attributes or change the role.",run(e){return de(e).prohibitedAttr}},zn=["a[href]","button:not([disabled])",'input:not([disabled]):not([type="hidden"])',"select:not([disabled])","textarea:not([disabled])",'[tabindex]:not([tabindex="-1"])'].join(", "),jn=["aria-atomic","aria-busy","aria-controls","aria-describedby","aria-details","aria-dropeffect","aria-flowto","aria-grabbed","aria-haspopup","aria-keyshortcuts","aria-live","aria-owns","aria-relevant"];function De(e){const a=[];e.matches(zn)&&a.push("element is focusable");for(const t of jn)if(e.hasAttribute(t)){a.push(`has ${t}`);break}return(e.hasAttribute("aria-label")||e.hasAttribute("aria-labelledby"))&&a.push("has accessible name"),a}const Wn={id:"aria/presentation-role-conflict",category:"aria",actRuleIds:["46ca7f"],wcag:["4.1.2"],level:"A",fixability:"contextual",description:"Elements with role='presentation' or role='none' must not be focusable or have global ARIA attributes.",guidance:"When an element has role='presentation' or role='none', it's marked as decorative and removed from the accessibility tree. However, if the element is focusable or has certain ARIA attributes, the presentation role is ignored and the element remains accessible. This creates confusion. Either remove the presentation role, or remove the focusability/ARIA attributes.",run(e){const a=[];for(const t of e.querySelectorAll('[role="presentation"], [role="none"]')){if(h(t))continue;const i=De(t);i.length>0&&a.push({ruleId:"aria/presentation-role-conflict",selector:p(t),html:m(t),impact:"serious",message:`Presentation role conflicts with: ${i.join(", ")}. The role will be ignored.`,fix:{type:"suggest",suggestion:"Remove the presentation/none role, or remove the conflicting focusability and ARIA attributes"}})}for(const t of e.querySelectorAll('img[alt=""]')){if(h(t)||t.hasAttribute("role"))continue;const i=De(t);i.length>0&&a.push({ruleId:"aria/presentation-role-conflict",selector:p(t),html:m(t),impact:"serious",message:`Element with implicit presentation role (alt="") conflicts with: ${i.join(", ")}. The decorative role will be ignored.`,fix:{type:"suggest",suggestion:"Remove the conflicting focusability and ARIA attributes, or add descriptive alt text if the image is not decorative"}})}return a}},Un=new Set(["button","checkbox","img","link","math","menuitemcheckbox","menuitemradio","meter","option","progressbar","radio","scrollbar","separator","slider","spinbutton","switch","tab"]),On={id:"aria/presentational-children-focusable",category:"aria",actRuleIds:["307n5z"],wcag:["4.1.2"],level:"A",fixability:"contextual",description:"Elements with a role that makes children presentational must not contain focusable content.",guidance:"Roles like button, checkbox, img, tab, and others make their children presentational — hidden from assistive technologies. If those children are focusable, keyboard users can reach elements that screen reader users cannot perceive. Move focusable content outside the parent or remove the focusability.",run(e){const a=[];for(const t of e.querySelectorAll("*")){if(h(t))continue;const i=H(t);if(!(!i||!Un.has(i))){for(const n of t.querySelectorAll(W))if(n!==t&&!n.disabled&&n.getAttribute("tabindex")!=="-1"){a.push({ruleId:"aria/presentational-children-focusable",selector:p(n),html:m(n),impact:"serious",message:`Focusable element inside a "${i}" role whose children are presentational.`});break}}}return a}},he=[Ut,Vt,Bt,_t,Yt,Xt,Jt,Kt,Qt,Zt,ra,ua,ma,pa,ha,va,xa,Aa,ka,Sa,qa,Ea,La,Ra,Na,Ma,$a,za,Za,ei,ai,ni,ri,pi,bi,hi,gi,fi,vi,wi,Ai,ki,Si,Ii,qi,Ei,Li,Ci,Ti,Ni,Mi,$i,Hi,Pi,Di,Fi,zi,ji,Wi,Ui,_i,Gi,Yi,Xi,Ji,nn,on,rn,sn,ln,cn,dn,un,mn,pn,bn,hn,gn,vn,yn,xn,An,kn,Sn,In,En,Rn,Nn,$n,Dn,Fn,Wn,On];let ge=[],rt=new Set,st=!1,lt=!1,N,j;function Vn(e){e.additionalRules&&(ge=e.additionalRules),e.disabledRules&&(rt=new Set(e.disabledRules)),"includeAAA"in e&&(st=!!e.includeAAA),"componentMode"in e&&(lt=!!e.componentMode),"locale"in e&&(N=e.locale||void 0),j=void 0}function Y(){if(j)return j;const a=he.filter(t=>{var i;return!(rt.has(t.id)||t.level==="AAA"&&!st||lt&&((i=t.tags)!=null&&i.includes("page-level")))}).concat(ge);return N?(j=Dt(a,N),j):a}function Bn(e){fe();const a=Y(),t=N,i=[],n=[];let o=0;return{processChunk(r){const s=performance.now();for(;o<a.length;){const l=a[o];try{i.push(...l.run(e))}catch(d){n.push({ruleId:l.id,error:d instanceof Error?d.message:String(d)})}if(o++,performance.now()-s>=r)break}return o<a.length},getViolations(){return t?ue(i,t):i},getSkippedRules(){return n}}}function fe(){je(),Fe(),ut(),Be(),Ve(),ht()}function _n(e){var n;fe();const a=Y(),t=[],i=[];for(const o of a)try{t.push(...o.run(e))}catch(r){i.push({ruleId:o.id,error:r instanceof Error?r.message:String(r)})}return{url:((n=e.location)==null?void 0:n.href)??"",timestamp:Date.now(),violations:N?ue(t,N):t,ruleCount:a.length,skippedRules:i}}function Gn(e,a){const t=l=>`${l.ruleId}\0${l.selector}`,i=new Map;for(const l of e.violations)i.set(t(l),l);const n=new Map;for(const l of a.violations)n.set(t(l),l);const o=[],r=[];for(const[l,d]of n)i.has(l)?r.push(d):o.push(d);const s=[];for(const[l,d]of i)n.has(l)||s.push(d);return{added:o,fixed:s,unchanged:r}}const Yn=new Map(he.map(e=>[e.id,e]));function Xn(e){if(N)return Y().find(i=>i.id===e);const a=Yn.get(e);return a||ge.find(t=>t.id===e)}const Jn={"navigable/document-title":{description:"Documents must have a <title> element to provide users with an overview of content.",guidance:"Screen reader users rely on page titles to identify and navigate between tabs/windows. Add a descriptive <title> element in <head> that summarizes the page purpose. Keep titles unique across the site, placing specific content before the site name (e.g., 'Contact Us - Acme Corp').",messages:{"Document <title> element is empty.":"Document <title> element is empty.","Document is missing a <title> element.":"Document is missing a <title> element."}},"navigable/bypass":{description:"Page must have a mechanism to bypass repeated blocks of content.",guidance:'Keyboard users must be able to skip repetitive content like navigation. Provide a skip link at the top of the page that links to the main content (e.g., <a href="#main">Skip to main content</a>), or use a <main> landmark. Screen readers can jump directly to landmarks, so a properly marked-up <main> element satisfies this requirement.',messages:{"Page has no mechanism to bypass repeated content. Add a <main> landmark or skip link.":"Page has no mechanism to bypass repeated content. Add a <main> landmark or skip link."}},"navigable/page-has-heading-one":{description:"Page should contain a level-one heading.",guidance:"A level-one heading (<h1> or role='heading' with aria-level='1') helps users understand the page topic and provides a landmark for screen reader navigation. Each page should have at least one level-one heading that describes the main content, typically matching or similar to the page title.",messages:{"Page does not contain a level-one heading.":"Page does not contain a level-one heading."}},"labels-and-names/frame-title":{description:"Frames must have an accessible name.",guidance:"Screen readers announce frame titles when users navigate frames. Add a title attribute to <iframe> and <frame> elements that describes the frame's purpose (e.g., <iframe title='Video player'>). Avoid generic titles like 'frame' or 'iframe'. If the frame is decorative, use aria-hidden='true'.",messages:{"Frame is missing an accessible name. Add a title attribute.":"Frame is missing an accessible name. Add a title attribute."}},"labels-and-names/frame-title-unique":{description:"Frame titles should be unique.",guidance:"When multiple frames have identical titles, screen reader users cannot distinguish between them. Give each frame a unique, descriptive title that explains its specific purpose or content.",messages:{"Frame title is not unique. Use a distinct title for each frame.":"Frame title is not unique. Use a distinct title for each frame."}},"distinguishable/meta-viewport":{description:"Viewport meta tag must not disable user scaling.",guidance:"Users with low vision need to zoom content up to 200% or more. Setting user-scalable=no or maximum-scale=1 prevents zooming and fails WCAG. Remove these restrictions. If your layout breaks at high zoom, fix the responsive design rather than preventing zoom.",messages:{"Viewport disables user scaling (user-scalable={0}). Remove this restriction.":"Viewport disables user scaling (user-scalable={0}). Remove this restriction.","Viewport maximum-scale={0} restricts zooming. Set to at least 2 or remove.":"Viewport maximum-scale={0} restricts zooming. Set to at least 2 or remove."}},"enough-time/meta-refresh":{description:"Meta refresh must not redirect or refresh automatically.",guidance:"Automatic page refreshes or redirects can disorient users, especially those using screen readers or with cognitive disabilities. They may lose their place or not have time to read content. If a redirect is needed, use a server-side redirect (HTTP 301/302) instead. For timed refreshes, provide user controls.",messages:{"Page redirects after {0} seconds without warning. Use server-side redirect.":"Page redirects after {0} seconds without warning. Use server-side redirect.","Page auto-refreshes after {0} seconds. Provide user control over refresh.":"Page auto-refreshes after {0} seconds. Provide user control over refresh."}},"enough-time/blink":{description:"The <blink> element must not be used.",guidance:"Blinking content can cause seizures in users with photosensitive epilepsy and is distracting for users with attention disorders. The <blink> element is deprecated and should never be used. If you need to draw attention to content, use less intrusive methods like color, borders, or icons.",messages:{"The <blink> element causes accessibility issues. Remove it entirely.":"The <blink> element causes accessibility issues. Remove it entirely."}},"enough-time/marquee":{description:"The <marquee> element must not be used.",guidance:"Scrolling or moving content is difficult for many users to read, especially those with cognitive or visual disabilities. The <marquee> element is deprecated. Replace scrolling text with static content. If content must scroll, provide pause/stop controls and ensure it stops after 5 seconds.",messages:{"The <marquee> element causes accessibility issues. Replace with static content.":"The <marquee> element causes accessibility issues. Replace with static content."}},"text-alternatives/img-alt":{description:`Images must have alternate text. Add an alt attribute to <img> elements. Decorative images may use an empty alt attribute (alt=""), role='none', or role='presentation'.`,guidance:"Every image needs an alt attribute. For informative images, describe the content or function concisely. For decorative images (backgrounds, spacers, purely visual flourishes), use alt='' to hide them from screen readers. Never omit alt entirely—screen readers may read the filename instead. When an image is inside a link or button that already has text, use alt='' if the image is decorative in that context, or write alt text that complements (not duplicates) the existing text.",messages:{'Image has whitespace-only alt text. Use alt="" for decorative images or provide descriptive text.':'Image has whitespace-only alt text. Use alt="" for decorative images or provide descriptive text.',"Image element missing alt attribute.":"Image element missing alt attribute.",'Element with role="img" has no accessible name. Add aria-label or aria-labelledby.':'Element with role="img" has no accessible name. Add aria-label or aria-labelledby.'}},"text-alternatives/svg-img-alt":{description:"SVG elements with an img, graphics-document, or graphics-symbol role must have an accessible name via a <title> element, aria-label, or aria-labelledby.",guidance:"Inline SVGs with role='img' need accessible names. Add a <title> element as the first child of the SVG (screen readers will announce it), or use aria-label on the SVG element. For complex SVGs, use aria-labelledby referencing both a <title> and <desc> element. Decorative SVGs should use aria-hidden='true' instead.",messages:{"{0} with role='{1}' has no accessible name.":"{0} with role='{1}' has no accessible name."}},"text-alternatives/input-image-alt":{description:'Image inputs (<input type="image">) must have alternate text describing the button action.',guidance:"Image buttons (<input type='image'>) act as submit buttons with a custom image. Add alt text via alt, aria-label, or aria-labelledby that describes the action (e.g. alt='Search' or alt='Submit order'), not the image itself. Without it, screen readers announce only 'image' or the filename, giving no clue what the button does.",messages:{"Image input missing alt text.":"Image input missing alt text."}},"text-alternatives/image-redundant-alt":{description:"Image alt text should not duplicate adjacent link or button text. When alt text repeats surrounding text, screen reader users hear the same information twice.",guidance:"When an image is inside a link or button that also has text, make the alt text complementary rather than identical. If the image is purely decorative in that context, use alt='' to avoid repetition.",messages:{'Alt text "{0}" duplicates surrounding {1} text.':'Alt text "{0}" duplicates surrounding {1} text.'}},"text-alternatives/image-alt-words":{description:"Image alt text should not start with words like 'image of', 'photo of', or 'picture of' — screen readers already announce the element type.",guidance:"Screen readers already announce 'image' or 'graphic' before reading alt text, so phrases like 'image of', 'photo of', or 'picture of' are redundant. Remove these words and describe what the image shows. For example, change 'image of a dog' to 'golden retriever playing fetch'.",messages:{'Alt text "{0}" contains redundant word(s): {1}.':'Alt text "{0}" contains redundant word(s): {1}.'}},"text-alternatives/area-alt":{description:"Image map <area> elements must have alternative text.",guidance:"Each clickable region in an image map needs alternative text so screen reader users know what the region represents. Add an alt attribute to every <area> element describing its purpose. For complex image maps, consider using alternative approaches like SVG with embedded links, or a list of text links.",messages:{"Image map <area> element is missing alternative text.":"Image map <area> element is missing alternative text."}},"text-alternatives/object-alt":{description:"<object> elements must have alternative text.",guidance:"Object elements embed external content that may not be accessible to all users. Provide alternative text via aria-label, aria-labelledby, or a title attribute. The fallback content inside <object> is only shown when the object fails to load and does not serve as an accessible name.",messages:{"<object> element is missing alternative text. Add aria-label, aria-labelledby, or a title attribute.":"<object> element is missing alternative text. Add aria-label, aria-labelledby, or a title attribute."}},"text-alternatives/role-img-alt":{description:"Elements with role='img' must have an accessible name.",guidance:"When you assign role='img' to an element (like a div containing icon fonts or CSS backgrounds), you must provide an accessible name via aria-label or aria-labelledby. Without this, screen reader users have no way to understand what the image represents. If the image is decorative, use role='presentation' or role='none' instead.",messages:{"Element with role='img' has no accessible name. Add aria-label or aria-labelledby.":"Element with role='img' has no accessible name. Add aria-label or aria-labelledby."}},"keyboard-accessible/server-image-map":{description:"Server-side image maps must not be used.",guidance:"Server-side image maps (using ismap attribute) send click coordinates to the server, which is inaccessible to keyboard users and screen readers who can't precisely click specific regions. Replace with client-side image maps (<map> with <area> elements) that provide keyboard access and accessible names, or use linked images/buttons instead.",messages:{"Server-side image map detected. Use client-side image map with <map> and <area> elements instead.":"Server-side image map detected. Use client-side image map with <map> and <area> elements instead."}},"labels-and-names/form-label":{description:"Form elements must have labels. Use <label>, aria-label, or aria-labelledby.",guidance:"Every form input needs an accessible label so users understand what information to enter. Use a <label> element with a for attribute matching the input's id, wrap the input in a <label>, or use aria-label/aria-labelledby for custom components. Placeholders are not sufficient as labels since they disappear when typing. Labels should describe the information requested, not the field type (e.g., 'Email address', 'Search', 'Phone number').",messages:{"Form element has no accessible label.":"Form element has no accessible label."}},"labels-and-names/multiple-labels":{description:"Form fields should not have multiple label elements.",guidance:"When a form field has multiple <label> elements pointing to it, assistive technologies may announce only one label or behave inconsistently. Use a single <label> and combine any additional text into it, or use aria-describedby for supplementary information.",messages:{"Form field has {0} labels. Use a single label element.":"Form field has {0} labels. Use a single label element."}},"labels-and-names/input-button-name":{description:"Input buttons must have discernible text via value, aria-label, or aria-labelledby.",guidance:"Input buttons (<input type='submit'>, type='button', type='reset'>) need accessible names so users know what action the button performs. Add a value attribute with descriptive text (e.g., value='Submit Form'), or use aria-label if the value must differ from the accessible name.",messages:{"Input button has no discernible text.":"Input button has no discernible text."}},"adaptable/autocomplete-valid":{description:"Autocomplete attribute must use valid values from the HTML specification.",guidance:"The autocomplete attribute helps users fill forms by identifying input purposes. Use standard values like 'name', 'email', 'tel', 'street-address', 'postal-code', 'cc-number'. This benefits users with cognitive disabilities, motor impairments, and anyone using password managers or autofill. Check the HTML specification for the complete list of valid tokens.",messages:{'Invalid autocomplete value "{0}".':'Invalid autocomplete value "{0}".'}},"labels-and-names/label-content-mismatch":{description:"Interactive elements with visible text must have accessible names that contain that text.",guidance:"For voice control users who activate controls by speaking their visible label, the accessible name must include the visible text. If aria-label is 'Submit form' but the button shows 'Send', voice users saying 'click Send' won't activate it. Ensure aria-label/aria-labelledby contains or matches the visible text.",messages:{'Accessible name "{0}" does not contain visible text "{1}".':'Accessible name "{0}" does not contain visible text "{1}".'}},"labels-and-names/label-title-only":{description:"Form elements should not use title attribute as the only accessible name.",guidance:"The title attribute is unreliable as a label because it only appears on hover/focus (not visible to touch users) and is often ignored by assistive technologies. Use a visible <label> element, aria-label, or aria-labelledby instead. Title can supplement a label but should not replace it.",messages:{"Form element uses title attribute as only label. Use <label>, aria-label, or aria-labelledby instead.":"Form element uses title attribute as only label. Use <label>, aria-label, or aria-labelledby instead."}},"keyboard-accessible/tabindex":{description:"Elements should not have tabindex greater than 0, which disrupts natural tab order.",guidance:"Positive tabindex values force elements to the front of the tab order regardless of DOM position, creating unpredictable navigation for keyboard users. Use tabindex='0' to add elements to the natural tab order, or tabindex='-1' to make elements programmatically focusable but not in tab order. Rely on DOM order for tab sequence.",messages:{'Element has tabindex="{0}" which disrupts tab order.':'Element has tabindex="{0}" which disrupts tab order.'}},"keyboard-accessible/focus-order":{description:"Non-interactive elements with tabindex='0' must have an interactive ARIA role so assistive technologies can convey their purpose.",guidance:"When adding tabindex='0' to non-interactive elements like <div> or <span>, screen readers announce them generically. Add an appropriate role (button, link, tab, etc.) so users understand the element's purpose. Also add keyboard event handlers (Enter/Space for buttons, Enter for links). Consider using native interactive elements instead.",messages:{'Non-interactive <{0}> with tabindex="0" has no interactive role.':'Non-interactive <{0}> with tabindex="0" has no interactive role.'}},"keyboard-accessible/nested-interactive":{description:"Interactive controls must not be nested inside each other.",guidance:"Nesting interactive elements (like a button inside a link, or a link inside a button) creates unpredictable behavior and confuses assistive technologies. The browser may remove the inner element from the accessibility tree. Restructure the HTML so interactive elements are siblings, not nested. If you need a clickable card, use CSS and JavaScript rather than nesting.",messages:{"Interactive element <{0}> is nested inside <{1}>.":"Interactive element <{0}> is nested inside <{1}>."}},"keyboard-accessible/scrollable-region":{description:"Scrollable regions must be keyboard accessible.",guidance:"Content that scrolls must be accessible to keyboard users. If a region has overflow:scroll or overflow:auto and contains scrollable content, it needs either tabindex='0' to be focusable, or it must contain focusable elements. Without this, keyboard users cannot scroll the content.",messages:{"Scrollable region is not keyboard accessible. Add tabindex='0' or include focusable elements.":"Scrollable region is not keyboard accessible. Add tabindex='0' or include focusable elements."}},"keyboard-accessible/accesskeys":{description:"Accesskey attribute values must be unique.",guidance:"When multiple elements share the same accesskey, browser behavior becomes unpredictable - usually only the first element is activated. Ensure each accesskey value is unique within the page. Also consider that accesskeys can conflict with browser and screen reader shortcuts, so use them sparingly.",messages:{'Duplicate accesskey "{0}". Each accesskey must be unique.':'Duplicate accesskey "{0}". Each accesskey must be unique.'}},"navigable/heading-order":{description:"Heading levels should increase by one; skipping levels (e.g. h2 to h4) makes navigation harder.",guidance:"Screen reader users navigate by headings to understand page structure. Skipping levels (h2 to h4) suggests missing content and creates confusion. Start with h1 for the page title, then use h2 for main sections, h3 for subsections, etc. You can go back up (h3 to h2) when starting a new section.",messages:{"Heading level {0} skipped from level {1}. Use h{2} instead.":"Heading level {0} skipped from level {1}. Use h{2} instead."}},"navigable/empty-heading":{description:"Headings must have discernible text.",guidance:"Screen reader users navigate pages by headings, so empty headings create confusing navigation points. Ensure all headings contain visible text or accessible names. If a heading is used purely for visual styling, use CSS instead of heading elements.",messages:{"Heading is empty. Add text content or remove the heading element.":"Heading is empty. Add text content or remove the heading element."}},"navigable/p-as-heading":{description:"Paragraphs should not be styled to look like headings.",guidance:"When paragraphs are styled with bold, large fonts to look like headings, screen reader users miss the semantic structure. Use proper heading elements (h1-h6) instead of styled paragraphs. If you need specific styling, apply CSS to the heading elements while maintaining proper heading hierarchy.",messages:{"Paragraph appears to be styled as a heading. Use an h1-h6 element instead.":"Paragraph appears to be styled as a heading. Use an h1-h6 element instead."}},"landmarks/landmark-main":{description:"Page should have exactly one main landmark.",guidance:"The main landmark contains the primary content of the page. Screen readers allow users to jump directly to main content. Use a single <main> element (or role='main') to wrap the central content, excluding headers, footers, and navigation.",messages:{"Page has no main landmark.":"Page has no main landmark.","Page has multiple main landmarks.":"Page has multiple main landmarks."}},"landmarks/no-duplicate-banner":{description:"Page should not have more than one banner landmark.",guidance:"The banner landmark (typically <header>) identifies site-oriented content like logos and search. Only one top-level banner is allowed per page. If you need multiple headers, nest them inside sectioning elements (article, section, aside) where they become scoped headers rather than page-level banners.",messages:{"Page has multiple banner landmarks.":"Page has multiple banner landmarks."}},"landmarks/no-duplicate-contentinfo":{description:"Page should not have more than one contentinfo landmark.",guidance:"The contentinfo landmark (typically <footer>) contains information about the page like copyright and contact info. Only one top-level contentinfo is allowed per page. Nest additional footers inside sectioning elements to scope them.",messages:{"Page has multiple contentinfo landmarks.":"Page has multiple contentinfo landmarks."}},"landmarks/no-duplicate-main":{description:"Page should not have more than one main landmark.",guidance:"Only one main landmark should exist per page. The main landmark identifies the primary content area. If you have multiple content sections, use <section> with appropriate headings instead of multiple main elements.",messages:{"Page has multiple main landmarks.":"Page has multiple main landmarks."}},"landmarks/banner-is-top-level":{description:"Banner landmark should not be nested within another landmark.",guidance:"The banner landmark should be a top-level landmark, not nested inside article, aside, main, nav, or section. If a header is inside these elements, it automatically becomes a generic header rather than a banner. Remove explicit role='banner' from nested headers or restructure the page.",messages:{"Banner landmark is nested within another landmark.":"Banner landmark is nested within another landmark."}},"landmarks/contentinfo-is-top-level":{description:"Contentinfo landmark should not be nested within another landmark.",guidance:"The contentinfo landmark should be a top-level landmark. A footer inside article, aside, main, nav, or section becomes a scoped footer, not a contentinfo landmark. Remove explicit role='contentinfo' from nested footers or move the footer outside sectioning elements.",messages:{"Contentinfo landmark is nested within another landmark.":"Contentinfo landmark is nested within another landmark."}},"landmarks/main-is-top-level":{description:"Main landmark should not be nested within another landmark.",guidance:"Screen readers provide a shortcut to jump directly to the main landmark. When <main> is nested inside another landmark (article, aside, nav, or section), some screen readers may not list it as a top-level landmark, making it harder to find. Move <main> outside any sectioning elements so it sits at the top level of the document.",messages:{"Main landmark is nested within another landmark.":"Main landmark is nested within another landmark."}},"landmarks/complementary-is-top-level":{description:"Aside (complementary) landmark should be top-level or directly inside main.",guidance:"The complementary landmark (aside) should be top-level or a direct child of main. Nesting aside deep within other landmarks reduces its discoverability for screen reader users navigating by landmarks.",messages:{"Complementary landmark should be top-level.":"Complementary landmark should be top-level."}},"landmarks/landmark-unique":{description:"Landmarks should have unique labels when there are multiple of the same type.",guidance:"When a page has multiple landmarks of the same type (e.g., multiple nav elements), each should have a unique accessible name via aria-label or aria-labelledby. This helps screen reader users distinguish between them (e.g., 'Main navigation' vs 'Footer navigation').",messages:{'Multiple {0} landmarks have the same label "{1}".':'Multiple {0} landmarks have the same label "{1}".',"Multiple {0} landmarks have no label. Add unique aria-label attributes.":"Multiple {0} landmarks have no label. Add unique aria-label attributes."}},"landmarks/region":{description:"All page content should be contained within landmarks.",guidance:"Screen reader users navigate pages by landmarks. Content outside landmarks is harder to find and understand. Wrap all visible content in appropriate landmarks: <header>, <nav>, <main>, <aside>, <footer>, or <section> with a label. Skip links may exist outside landmarks.",messages:{"Content is not contained within a landmark region.":"Content is not contained within a landmark region."}},"adaptable/list-children":{description:"<ul> and <ol> must only contain <li>, <script>, or <template> as direct children.",guidance:"Screen readers announce list structure ('list with 5 items') based on proper markup. Placing non-<li> elements directly inside <ul> or <ol> breaks this structure. Wrap content in <li> elements, or if you need wrapper divs for styling, apply styles to <li> elements directly and remove the wrapper (e.g., change <ul><div>item</div></ul> to <ul><li>item</li></ul>).",messages:{"List contains non-<li> child <{0}>.":"List contains non-<li> child <{0}>."}},"adaptable/listitem-parent":{description:"<li> elements must be contained in a <ul>, <ol>, or <menu>.",guidance:"List items (<li>) only have semantic meaning inside a list container (<ul>, <ol>, or <menu>). Without a list parent, screen readers will not announce 'list with N items' or allow users to skip between items using list navigation shortcuts. Wrap <li> elements in the appropriate list container — <ul> for unordered lists, <ol> for ordered/numbered lists.",messages:{"<li> is not contained in a <ul>, <ol>, or <menu>.":"<li> is not contained in a <ul>, <ol>, or <menu>."}},"adaptable/dl-children":{description:"<dt> and <dd> elements must be contained in a <dl>.",guidance:"Definition terms (<dt>) and definitions (<dd>) only have semantic meaning inside a definition list (<dl>). Outside of <dl>, they're treated as generic text. Wrap related <dt> and <dd> pairs in a <dl> element to convey the term/definition relationship to assistive technologies.",messages:{"<{0}> is not contained in a <dl>.":"<{0}> is not contained in a <dl>."}},"adaptable/definition-list":{description:"<dl> elements must only contain <dt>, <dd>, <div>, <script>, or <template>.",guidance:"Definition lists have strict content requirements. Only <dt> (terms), <dd> (definitions), and <div> (for grouping dt/dd pairs) are valid children. Other elements break the list structure for screen readers. Move invalid elements outside the <dl>, or if they represent a term change to <dt>, if a definition change to <dd>. Styling wrappers should be replaced with <div> elements containing <dt>/<dd> pairs.",messages:{"<dl> contains invalid child <{0}>.":"<dl> contains invalid child <{0}>."}},"aria/aria-roles":{description:"ARIA role values must be valid.",guidance:"Invalid role values are ignored by assistive technologies, meaning the element will not have the intended semantics. Check the spelling and use only roles defined in the WAI-ARIA specification. Common roles include: button, link, navigation, main, dialog, alert, tab, tabpanel, menu, menuitem.",messages:{'Invalid ARIA role "{0}".':'Invalid ARIA role "{0}".'}},"aria/aria-valid-attr":{description:"ARIA attributes must be valid (correctly spelled).",guidance:"Misspelled ARIA attributes are ignored by assistive technologies. Check the spelling against the WAI-ARIA specification. Common mistakes: aria-labeledby (should be aria-labelledby), aria-role (should be role), aria-description (valid in ARIA 1.3+).",messages:{'Invalid ARIA attribute "{0}".':'Invalid ARIA attribute "{0}".'}},"aria/aria-valid-attr-value":{description:"ARIA attributes must have valid values.",guidance:"Each ARIA attribute accepts specific value types. Boolean attributes (aria-hidden, aria-disabled) accept only 'true' or 'false'. Tristate attributes (aria-checked, aria-pressed) also accept 'mixed'. Token attributes (aria-live, aria-autocomplete) accept predefined values. ID reference attributes (aria-labelledby, aria-describedby) must reference existing element IDs.",messages:{'{0} must be "true" or "false", got "{1}".':'{0} must be "true" or "false", got "{1}".','{0} must be "true", "false", or "mixed", got "{1}".':'{0} must be "true", "false", or "mixed", got "{1}".','{0} must be an integer, got "{1}".':'{0} must be an integer, got "{1}".','{0} must be a number, got "{1}".':'{0} must be a number, got "{1}".','Invalid value "{0}" for {1}.':'Invalid value "{0}" for {1}.'}},"aria/aria-required-attr":{description:"Elements with ARIA roles must have all required ARIA attributes.",guidance:"Some ARIA roles require specific attributes to function correctly. For example, checkbox requires aria-checked, slider requires aria-valuenow, heading requires aria-level. Without these attributes, assistive technologies cannot convey the element's state or value to users. Add the missing required attribute with an appropriate value.",messages:{'Role "{0}" requires attribute "{1}".':'Role "{0}" requires attribute "{1}".'}},"aria/aria-allowed-attr":{description:"ARIA attributes must be allowed for the element's role.",guidance:"Each ARIA role supports specific attributes. Using unsupported attributes creates confusion for assistive technologies. Check the ARIA specification for which attributes are valid for each role, or remove the attribute if it's not needed.",messages:{'ARIA attribute "{0}" is not allowed on role "{1}".':'ARIA attribute "{0}" is not allowed on role "{1}".'}},"aria/aria-allowed-role":{description:"ARIA role must be appropriate for the element.",guidance:"Not all ARIA roles can be applied to all HTML elements. Many elements have implicit roles (e.g., <header> is implicitly banner, <nav> is navigation, <main> is main). Adding an explicit role that matches the implicit role is redundant. Adding a conflicting role breaks semantics. Either remove the role attribute or use a different element.",messages:{"Element <{0}> should not have an explicit role.":"Element <{0}> should not have an explicit role.",'Role "{0}" is not allowed on element <{1}>.':'Role "{0}" is not allowed on element <{1}>.'}},"adaptable/aria-required-children":{description:"Certain ARIA roles require specific child roles to be present.",guidance:"Some ARIA roles represent containers that must contain specific child roles for proper semantics. For example, a list must contain listitems, a menu must contain menuitems. Add the required child elements with appropriate roles, or use native HTML elements that provide these semantics implicitly (e.g., <ul> with <li>).",messages:{'Role "{0}" requires children with role: {1}.':'Role "{0}" requires children with role: {1}.'}},"adaptable/aria-required-parent":{description:"Certain ARIA roles must be contained within specific parent roles.",guidance:"Some ARIA roles represent items that must exist within specific container roles. For example, a listitem must be within a list, a tab must be within a tablist. Wrap the element in the appropriate parent, or use native HTML elements that provide this structure (e.g., <li> inside <ul>).",messages:{'Role "{0}" must be contained within: {1}.':'Role "{0}" must be contained within: {1}.'}},"aria/aria-hidden-body":{description:"aria-hidden='true' must not be present on the document body.",guidance:"Setting aria-hidden='true' on the body element hides all page content from assistive technologies, making the page completely inaccessible to screen reader users. Remove aria-hidden from the body element. If you need to hide content temporarily (e.g., behind a modal), use aria-hidden on specific sections instead.",messages:{"aria-hidden='true' on body hides all content from assistive technologies.":"aria-hidden='true' on body hides all content from assistive technologies."}},"aria/aria-hidden-focus":{description:"Elements with aria-hidden='true' must not contain focusable elements.",guidance:"When aria-hidden='true' hides an element from assistive technologies but the element contains focusable children, keyboard users can focus those children but screen reader users won't know they exist. Either remove focusable elements from the hidden region, add tabindex='-1' to them, or remove aria-hidden.",messages:{"Focusable element is inside an aria-hidden region.":"Focusable element is inside an aria-hidden region."}},"labels-and-names/aria-command-name":{description:"ARIA commands must have an accessible name.",guidance:"Interactive ARIA command roles (button, link, menuitem) must have accessible names so users know what action they perform. Add visible text content, aria-label, or aria-labelledby to provide a name.",messages:{"ARIA command has no accessible name.":"ARIA command has no accessible name."}},"labels-and-names/aria-input-field-name":{description:"ARIA input fields must have an accessible name.",guidance:"ARIA input widgets (combobox, listbox, searchbox, slider, spinbutton, textbox) must have accessible names so users understand what data to enter. Add a visible label with aria-labelledby, or use aria-label if a visible label is not possible.",messages:{"ARIA input field has no accessible name.":"ARIA input field has no accessible name."}},"labels-and-names/aria-toggle-field-name":{description:"ARIA toggle fields must have an accessible name.",guidance:"ARIA toggle controls (checkbox, switch, radio, menuitemcheckbox, menuitemradio) must have accessible names so users understand what option they're selecting. Add visible text content, aria-label, or use aria-labelledby to reference a visible label.",messages:{"ARIA toggle field has no accessible name.":"ARIA toggle field has no accessible name."}},"labels-and-names/aria-meter-name":{description:"ARIA meter elements must have an accessible name.",guidance:"Meter elements display a value within a known range (like disk usage or password strength). They must have accessible names so screen reader users understand what is being measured. Use aria-label or aria-labelledby to provide context.",messages:{"Meter has no accessible name.":"Meter has no accessible name."}},"labels-and-names/aria-progressbar-name":{description:"ARIA progressbar elements must have an accessible name.",guidance:"Progress indicators must have accessible names so screen reader users understand what process is being tracked. Use aria-label (e.g., 'File upload progress') or aria-labelledby to reference a visible heading or label.",messages:{"Progressbar has no accessible name.":"Progressbar has no accessible name."}},"labels-and-names/aria-dialog-name":{description:"ARIA dialogs must have an accessible name.",guidance:"Dialog and alertdialog elements must have accessible names so screen reader users understand the dialog's purpose when it opens. Use aria-label or aria-labelledby pointing to the dialog's heading. Native <dialog> elements should also have an accessible name.",messages:{"Dialog has no accessible name.":"Dialog has no accessible name."}},"labels-and-names/aria-tooltip-name":{description:"ARIA tooltips must have an accessible name.",guidance:"Tooltip elements must have accessible names (usually their text content). The tooltip content itself typically serves as the accessible name. Ensure the tooltip contains descriptive text content or has aria-label.",messages:{"Tooltip has no accessible name.":"Tooltip has no accessible name."}},"labels-and-names/aria-treeitem-name":{description:"ARIA treeitem elements must have an accessible name.",guidance:"Tree items must have accessible names so screen reader users can understand the tree structure and navigate it effectively. Provide text content, aria-label, or aria-labelledby for each treeitem.",messages:{"Treeitem has no accessible name.":"Treeitem has no accessible name."}},"aria/aria-prohibited-attr":{description:"ARIA attributes must not be prohibited for the element's role.",guidance:"Some ARIA roles prohibit certain attributes. For example, roles like 'none', 'presentation', 'generic', and text-level roles (code, emphasis, strong) prohibit aria-label and aria-labelledby because naming is not supported for these roles. Remove the prohibited attributes or change the role.",messages:{"aria-label and aria-labelledby are prohibited on <{0}> elements.":"aria-label and aria-labelledby are prohibited on <{0}> elements.",'aria-label and aria-labelledby are prohibited on role "{0}".':'aria-label and aria-labelledby are prohibited on role "{0}".','Attribute "{0}" is prohibited on role "{1}".':'Attribute "{0}" is prohibited on role "{1}".'}},"aria/presentation-role-conflict":{description:"Elements with role='presentation' or role='none' must not be focusable or have global ARIA attributes.",guidance:"When an element has role='presentation' or role='none', it's marked as decorative and removed from the accessibility tree. However, if the element is focusable or has certain ARIA attributes, the presentation role is ignored and the element remains accessible. This creates confusion. Either remove the presentation role, or remove the focusability/ARIA attributes.",messages:{"Presentation role conflicts with: {0}. The role will be ignored.":"Presentation role conflicts with: {0}. The role will be ignored.",'Element with implicit presentation role (alt="") conflicts with: {0}. The decorative role will be ignored.':'Element with implicit presentation role (alt="") conflicts with: {0}. The decorative role will be ignored.'}},"labels-and-names/button-name":{description:"Buttons must have discernible text.",guidance:"Screen reader users need to know what a button does. Add visible text content, aria-label, or aria-labelledby. For icon buttons, use aria-label describing the action (e.g., aria-label='Close'). If the button contains an image, ensure the image has alt text describing the button's action.",messages:{"Button has no discernible text.":"Button has no discernible text."}},"labels-and-names/summary-name":{description:"<summary> elements must have an accessible name.",guidance:"The <summary> element provides the visible label for a <details> disclosure widget. It must have descriptive text content so screen reader users understand what will be revealed when expanded. Add clear, concise text that indicates what content is contained in the details section.",messages:{"<summary> element has no accessible name. Add descriptive text.":"<summary> element has no accessible name. Add descriptive text."}},"navigable/link-name":{description:"Links must have discernible text via content, aria-label, or aria-labelledby.",guidance:"Screen reader users need to know where a link goes. Add descriptive text content, aria-label, or use aria-labelledby. For image links, ensure the image has alt text describing the link destination. Avoid generic text like 'click here' or 'read more'—link text should make sense out of context.",messages:{"Link has no discernible text.":"Link has no discernible text."}},"navigable/skip-link":{description:"Skip links must point to a valid target on the page.",guidance:"Skip links allow keyboard users to bypass repetitive navigation and jump directly to main content. The skip link should be the first focusable element on the page, link to the main content (e.g., href='#main'), and become visible when focused. It can be visually hidden until focused using CSS.",messages:{'Skip link points to "#{0}" which does not exist on the page.':'Skip link points to "#{0}" which does not exist on the page.'}},"distinguishable/link-in-text-block":{description:"Links within text blocks must be distinguishable by more than color alone.",guidance:"Users who cannot perceive color differences need other visual cues to identify links. Links in text should have underlines or other non-color indicators. If using color alone, ensure 3:1 contrast with surrounding text AND provide additional indication on focus/hover.",messages:{"Link in text block is not visually distinguishable from surrounding text. Add a non-color visual indicator such as an underline or border.":"Link in text block is not visually distinguishable from surrounding text. Add a non-color visual indicator such as an underline or border."}},"readable/html-has-lang":{description:"The <html> element must have a lang attribute.",guidance:"Screen readers use the lang attribute to determine which language rules and pronunciation to use. Without it, content may be mispronounced. Set lang to the primary language of the page using a BCP 47 code (e.g., 'en' for English, 'es' for Spanish, 'fr' for French, 'de' for German, 'ja' for Japanese, 'zh' for Chinese, 'pt' for Portuguese, 'ar' for Arabic).",messages:{"<html> element missing lang attribute.":"<html> element missing lang attribute."}},"readable/html-lang-valid":{description:"The lang attribute on <html> must have a valid value.",guidance:"The lang attribute must use a valid BCP 47 language tag. Use a 2 or 3 letter language code (e.g., 'en', 'fr', 'zh'), optionally followed by a region code (e.g., 'en-US', 'pt-BR'). Invalid tags prevent screen readers from correctly pronouncing content.",messages:{'Invalid lang attribute value "{0}".':'Invalid lang attribute value "{0}".'}},"readable/valid-lang":{description:"The lang attribute must have a valid value on all elements.",guidance:"When content in a different language appears within a page (e.g., a French quote in an English document), wrap it with a lang attribute to ensure correct pronunciation. The lang value must be a valid BCP 47 tag. Common codes: en, es, fr, de, zh, ja, pt, ar, ru.",messages:{"Empty lang attribute value.":"Empty lang attribute value.",'Invalid lang attribute value "{0}".':'Invalid lang attribute value "{0}".'}},"readable/html-xml-lang-mismatch":{description:"The lang and xml:lang attributes on <html> must match.",guidance:"In XHTML documents, if both lang and xml:lang are present, they must specify the same base language. Mismatched values confuse assistive technologies. Either remove xml:lang (preferred for HTML5) or ensure both attributes have identical values.",messages:{'lang="{0}" and xml:lang="{1}" do not match.':'lang="{0}" and xml:lang="{1}" do not match.'}},"adaptable/td-headers-attr":{description:"All cells in a table using headers attribute must reference valid header IDs.",guidance:"The headers attribute on table cells must reference IDs of header cells (th or td) within the same table. This creates explicit associations for screen readers. Verify all referenced IDs exist and spell them correctly. For simple tables, consider using scope on th elements instead.",messages:{'Headers attribute references the cell itself ("{0}").':'Headers attribute references the cell itself ("{0}").','Headers attribute references non-existent ID "{0}".':'Headers attribute references non-existent ID "{0}".'}},"adaptable/th-has-data-cells":{description:"Table headers should be associated with data cells.",guidance:"Screen readers use <th> elements to announce column or row headers when navigating table cells — for example, reading 'Name: John' when moving to a cell. A table with <th> but no <td> elements means headers describe nothing, and screen readers cannot associate data with headers. Either add <td> data cells, or if this is not tabular data, use non-table markup instead.",messages:{"Table has header cells but no data cells.":"Table has header cells but no data cells."}},"adaptable/td-has-header":{description:"Data cells in tables larger than 3x3 should have associated headers.",guidance:"In complex tables, screen reader users need header associations to understand data cells. Use th elements with scope attribute, or the headers attribute on td elements. For simple tables (≤3x3), this is less critical as context is usually clear.",messages:{"Data cell has no associated header. Add th elements with scope, or headers attribute.":"Data cell has no associated header. Add th elements with scope, or headers attribute."}},"adaptable/scope-attr-valid":{description:"The scope attribute on table headers must have a valid value.",guidance:"The scope attribute tells screen readers which cells a header applies to. Valid values are: row, col, rowgroup, colgroup. Using invalid values breaks the association between headers and cells.",messages:{'Invalid scope value "{0}". Use row, col, rowgroup, or colgroup.':'Invalid scope value "{0}". Use row, col, rowgroup, or colgroup.'}},"adaptable/empty-table-header":{description:"Table header cells should have visible text.",guidance:"Empty table headers provide no information to screen reader users. Either add descriptive text to the header, or if the header is intentionally empty (like a corner cell), consider using a td element instead or adding a visually hidden label.",messages:{"Table header cell is empty. Add text or use aria-label.":"Table header cell is empty. Add text or use aria-label."}},"labels-and-names/duplicate-id-aria":{description:"IDs used in ARIA and label associations must be unique to avoid broken references.",guidance:"When aria-labelledby, aria-describedby, aria-controls, or label[for] reference a duplicate ID, only the first matching element is used. This breaks the intended relationship and may leave controls unnamed or descriptions missing. Ensure IDs referenced by ARIA attributes and label associations are unique throughout the document.",messages:{'Duplicate ID "{0}" referenced by {1}.':'Duplicate ID "{0}" referenced by {1}.'}},"time-based-media/video-captions":{description:"Video elements must have captions via <track kind='captions'> or <track kind='subtitles'>.",guidance:"Captions provide text alternatives for audio content in videos, benefiting deaf users and those who cannot hear audio. Add a <track> element with kind='captions' pointing to a WebVTT caption file. Captions should include both dialogue and important sound effects.",messages:{"Video element has no captions track.":"Video element has no captions track."}},"time-based-media/audio-transcript":{description:"Audio elements should have a text alternative or transcript.",guidance:"Audio-only content like podcasts or recordings needs a text alternative for deaf users. Provide a transcript either on the same page or linked nearby. The transcript should include all spoken content and descriptions of relevant sounds.",messages:{"Audio element has no transcript or text alternative. Add a transcript or track element.":"Audio element has no transcript or text alternative. Add a transcript or track element."}},"distinguishable/color-contrast":{description:"Text elements must have sufficient color contrast against the background.",guidance:"WCAG SC 1.4.3 requires a contrast ratio of at least 4.5:1 for normal text and 3:1 for large text (>=24px or >=18.66px bold). Increase the contrast by darkening the text or lightening the background, or vice versa.",messages:{"Insufficient color contrast ratio of {0}:1 (required {1}:1).":"Insufficient color contrast ratio of {0}:1 (required {1}:1)."}},"distinguishable/color-contrast-enhanced":{description:"Text elements must have enhanced color contrast against the background (WCAG AAA).",guidance:"WCAG SC 1.4.6 (AAA) requires a contrast ratio of at least 7:1 for normal text and 4.5:1 for large text (>=24px or >=18.66px bold). Higher contrast benefits users with low vision, aging eyes, or poor screen conditions. Increase the contrast by darkening the text or lightening the background, or vice versa.",messages:{"Insufficient enhanced contrast ratio of {0}:1 (required {1}:1).":"Insufficient enhanced contrast ratio of {0}:1 (required {1}:1)."}},"enough-time/meta-refresh-no-exception":{description:"Meta refresh must not be used with a delay (no exceptions).",guidance:"Automatic page refreshes and delayed redirects disorient users. Instant redirects (delay=0) are acceptable, but any positive delay is not. Use server-side redirects instead.",messages:{"Page has a {0}-second meta refresh delay. Use a server-side redirect instead.":"Page has a {0}-second meta refresh delay. Use a server-side redirect instead.","Page has a {0}-second meta refresh delay. Remove the auto-refresh or provide user control.":"Page has a {0}-second meta refresh delay. Remove the auto-refresh or provide user control."}},"distinguishable/letter-spacing":{description:"Letter spacing set with !important in style attributes must be at least 0.12em.",guidance:"WCAG 1.4.12 requires users to be able to override text spacing. Using !important on letter-spacing with a value below 0.12em prevents this. Either increase the value to at least 0.12em or remove !important.",messages:{"Letter spacing {0}em with !important is below the 0.12em minimum.":"Letter spacing {0}em with !important is below the 0.12em minimum."}},"distinguishable/line-height":{description:"Line height set with !important in style attributes must be at least 1.5.",guidance:"WCAG 1.4.12 requires users to be able to override text spacing. Using !important on line-height with a value below 1.5 prevents this. Either increase the value to at least 1.5 or remove !important.",messages:{"Line height {0} with !important is below the 1.5 minimum.":"Line height {0} with !important is below the 1.5 minimum."}},"distinguishable/word-spacing":{description:"Word spacing set with !important in style attributes must be at least 0.16em.",guidance:"WCAG 1.4.12 requires users to be able to override text spacing. Using !important on word-spacing with a value below 0.16em prevents this. Either increase the value to at least 0.16em or remove !important.",messages:{"Word spacing {0}em with !important is below the 0.16em minimum.":"Word spacing {0}em with !important is below the 0.16em minimum."}},"adaptable/orientation-lock":{description:"Page orientation must not be restricted using CSS transforms.",guidance:"Users with motor disabilities may mount their device in a fixed orientation. Using CSS transforms with @media (orientation: portrait/landscape) to rotate content 90° effectively locks the page to one orientation. Remove the orientation-dependent transform and use responsive design instead.",messages:{"CSS locks page orientation via @media (orientation: {0}) with a 90° transform.":"CSS locks page orientation via @media (orientation: {0}) with a 90° transform."}},"aria/presentational-children-focusable":{description:"Elements with a role that makes children presentational must not contain focusable content.",guidance:"Roles like button, checkbox, img, tab, and others make their children presentational — hidden from assistive technologies. If those children are focusable, keyboard users can reach elements that screen reader users cannot perceive. Move focusable content outside the parent or remove the focusability.",messages:{'Focusable element inside a "{0}" role whose children are presentational.':'Focusable element inside a "{0}" role whose children are presentational.'}},"keyboard-accessible/focus-visible":{description:"Elements in sequential focus order must have a visible focus indicator.",guidance:"Keyboard users need to see which element has focus. Do not remove the default focus outline (outline: none) without providing an alternative visible indicator. Use :focus-visible or :focus styles to ensure focus is always perceivable.",messages:{"Focusable element has outline removed without a visible focus alternative.":"Focusable element has outline removed without a visible focus alternative."}},"input-assistance/accessible-authentication":{description:'Password inputs must not block password managers. Avoid autocomplete="off" and allow pasting.',guidance:'WCAG 2.2 SC 3.3.8 requires that authentication steps either avoid cognitive function tests or provide a mechanism to assist users. Password managers are a key assistive mechanism. Setting autocomplete="off" on password fields prevents password managers from filling credentials. Blocking paste via onpaste attributes prevents users from pasting stored passwords. Set autocomplete to "current-password" for login forms or "new-password" for registration/change-password forms, and do not block paste on password fields.',messages:{'Password field has autocomplete="off" which blocks password managers.':'Password field has autocomplete="off" which blocks password managers.',"Password field blocks pasting, preventing password manager use.":"Password field blocks pasting, preventing password manager use."}}},Kn={"navigable/document-title":{description:"Los documentos deben tener un elemento <title> para proporcionar a los usuarios una vista general del contenido.",guidance:"Los usuarios de lectores de pantalla dependen de los títulos de página para identificar y navegar entre pestañas/ventanas. Agregue un elemento <title> descriptivo en <head> que resuma el propósito de la página. Mantenga los títulos únicos en todo el sitio, colocando el contenido específico antes del nombre del sitio (por ejemplo, 'Contáctenos - Acme Corp').",messages:{"Document <title> element is empty.":"El elemento <title> del documento está vacío.","Document is missing a <title> element.":"Al documento le falta un elemento <title>."}},"navigable/bypass":{description:"La página debe tener un mecanismo para omitir bloques de contenido repetidos.",guidance:'Los usuarios de teclado deben poder omitir contenido repetitivo como la navegación. Proporcione un enlace de salto en la parte superior de la página que enlace al contenido principal (por ejemplo, <a href="#main">Saltar al contenido principal</a>), o use un landmark <main>. Los lectores de pantalla pueden saltar directamente a los landmarks, por lo que un elemento <main> correctamente marcado satisface este requisito.',messages:{"Page has no mechanism to bypass repeated content. Add a <main> landmark or skip link.":"La página no tiene un mecanismo para omitir contenido repetido. Agregue un landmark <main> o un enlace de salto."}},"navigable/page-has-heading-one":{description:"La página debe contener un encabezado de nivel uno.",guidance:"Un encabezado de nivel uno (<h1> o role='heading' con aria-level='1') ayuda a los usuarios a comprender el tema de la página y proporciona un punto de referencia para la navegación con lector de pantalla. Cada página debe tener exactamente un h1 que describa el contenido principal, típicamente coincidiendo o similar al título de la página.",messages:{"Page does not contain a level-one heading.":"La página no contiene un encabezado de nivel uno."}},"labels-and-names/frame-title":{description:"Los marcos deben tener un nombre accesible.",guidance:"Los lectores de pantalla anuncian los títulos de los marcos cuando los usuarios navegan por ellos. Agregue un atributo title a los elementos <iframe> y <frame> que describa el propósito del marco (por ejemplo, <iframe title='Reproductor de video'>). Evite títulos genéricos como 'marco' o 'iframe'. Si el marco es decorativo, use aria-hidden='true'.",messages:{"Frame is missing an accessible name. Add a title attribute.":"Al marco le falta un nombre accesible. Agregue un atributo title."}},"labels-and-names/frame-title-unique":{description:"Los títulos de los marcos deben ser únicos.",guidance:"Cuando varios marcos tienen títulos idénticos, los usuarios de lectores de pantalla no pueden distinguirlos. Dé a cada marco un título único y descriptivo que explique su propósito específico o contenido.",messages:{"Frame title is not unique. Use a distinct title for each frame.":"El título del marco no es único. Use un título distinto para cada marco."}},"distinguishable/meta-viewport":{description:"La etiqueta meta viewport no debe deshabilitar el zoom del usuario.",guidance:"Los usuarios con baja visión necesitan ampliar el contenido al 200% o más. Establecer user-scalable=no o maximum-scale=1 impide el zoom y no cumple con WCAG. Elimine estas restricciones. Si su diseño se rompe con zoom alto, corrija el diseño responsivo en lugar de impedir el zoom.",messages:{"Viewport disables user scaling (user-scalable={0}). Remove this restriction.":"El viewport deshabilita el escalado del usuario (user-scalable={0}). Elimine esta restricción.","Viewport maximum-scale={0} restricts zooming. Set to at least 2 or remove.":"El viewport maximum-scale={0} restringe el zoom. Establezca al menos 2 o elimínelo."}},"enough-time/meta-refresh":{description:"La etiqueta meta refresh no debe redirigir o actualizar automáticamente.",guidance:"Las actualizaciones o redirecciones automáticas de página pueden desorientar a los usuarios, especialmente aquellos que usan lectores de pantalla o con discapacidades cognitivas. Pueden perder su lugar o no tener tiempo para leer el contenido. Si se necesita una redirección, use una redirección del lado del servidor (HTTP 301/302). Para actualizaciones temporizadas, proporcione controles al usuario.",messages:{"Page redirects after {0} seconds without warning. Use server-side redirect.":"La página redirige después de {0} segundos sin advertencia. Use redirección del lado del servidor.","Page auto-refreshes after {0} seconds. Provide user control over refresh.":"La página se actualiza automáticamente después de {0} segundos. Proporcione control al usuario sobre la actualización."}},"enough-time/blink":{description:"El elemento <blink> no debe usarse.",guidance:"El contenido parpadeante puede causar convulsiones en usuarios con epilepsia fotosensible y es una distracción para usuarios con trastornos de atención. El elemento <blink> está obsoleto y nunca debe usarse. Si necesita llamar la atención sobre el contenido, use métodos menos intrusivos como color, bordes o iconos.",messages:{"The <blink> element causes accessibility issues. Remove it entirely.":"El elemento <blink> causa problemas de accesibilidad. Elimínelo por completo."}},"enough-time/marquee":{description:"El elemento <marquee> no debe usarse.",guidance:"El contenido que se desplaza o se mueve es difícil de leer para muchos usuarios, especialmente aquellos con discapacidades cognitivas o visuales. El elemento <marquee> está obsoleto. Reemplace el texto en movimiento con contenido estático. Si el contenido debe desplazarse, proporcione controles de pausa/detención y asegúrese de que se detenga después de 5 segundos.",messages:{"The <marquee> element causes accessibility issues. Replace with static content.":"El elemento <marquee> causa problemas de accesibilidad. Reemplace con contenido estático."}},"text-alternatives/img-alt":{description:`Las imágenes deben tener texto alternativo. Agregue un atributo alt a los elementos <img>. Las imágenes decorativas pueden usar un atributo alt vacío (alt=""), role='none' o role='presentation'.`,guidance:"Cada imagen necesita un atributo alt. Para imágenes informativas, describa el contenido o la función de manera concisa. Para imágenes decorativas (fondos, espaciadores, adornos puramente visuales), use alt='' para ocultarlas de los lectores de pantalla. Nunca omita alt por completo: los lectores de pantalla pueden leer el nombre del archivo en su lugar.",messages:{'Image has whitespace-only alt text. Use alt="" for decorative images or provide descriptive text.':'La imagen tiene texto alt solo con espacios en blanco. Use alt="" para imágenes decorativas o proporcione texto descriptivo.',"Image element missing alt attribute.":"Al elemento de imagen le falta el atributo alt.",'Element with role="img" has no accessible name. Add aria-label or aria-labelledby.':'El elemento con role="img" no tiene nombre accesible. Agregue aria-label o aria-labelledby.'}},"text-alternatives/svg-img-alt":{description:"Los elementos SVG con rol img, graphics-document o graphics-symbol deben tener un nombre accesible mediante un elemento <title>, aria-label o aria-labelledby.",guidance:"Los SVG en línea con role='img' necesitan nombres accesibles. Agregue un elemento <title> como primer hijo del SVG (los lectores de pantalla lo anunciarán), o use aria-label en el elemento SVG. Para SVGs complejos, use aria-labelledby haciendo referencia tanto a un elemento <title> como a un elemento <desc>. Los SVGs decorativos deben usar aria-hidden='true' en su lugar.",messages:{"{0} with role='{1}' has no accessible name.":"{0} con role='{1}' no tiene nombre accesible."}},"text-alternatives/input-image-alt":{description:'Las entradas de imagen (<input type="image">) deben tener texto alternativo mediante alt, aria-label o aria-labelledby. El texto debe describir la acción del botón, no la imagen.',guidance:"Los botones de imagen (<input type='image'>) deben tener texto alternativo mediante alt, aria-label o aria-labelledby. El texto debe describir la acción del botón, no la imagen.",messages:{"Image input missing alt text.":"A la entrada de imagen le falta texto alt."}},"text-alternatives/image-redundant-alt":{description:"El texto alternativo de la imagen no debe duplicar el texto del enlace o botón adyacente. Cuando el texto alt repite el texto circundante, los usuarios de lectores de pantalla escuchan la misma información dos veces.",guidance:"Cuando una imagen está dentro de un enlace o botón que también tiene texto, haga que el texto alt sea complementario en lugar de idéntico. Si la imagen es puramente decorativa en ese contexto, use alt='' para evitar la repetición.",messages:{'Alt text "{0}" duplicates surrounding {1} text.':'El texto alt "{0}" duplica el texto del {1} circundante.'}},"text-alternatives/image-alt-words":{description:"El texto alternativo de la imagen no debe comenzar con palabras como 'imagen de', 'foto de' o 'fotografía de': los lectores de pantalla ya anuncian el tipo de elemento.",guidance:"Los lectores de pantalla ya anuncian 'imagen' o 'gráfico' antes de leer el texto alt, por lo que frases como 'imagen de', 'foto de' o 'fotografía de' son redundantes. Elimine estas palabras y describa lo que muestra la imagen. Por ejemplo, cambie 'imagen de un perro' a 'golden retriever jugando a buscar'.",messages:{'Alt text "{0}" contains redundant word(s): {1}.':'El texto alt "{0}" contiene palabra(s) redundante(s): {1}.'}},"text-alternatives/area-alt":{description:"Los elementos <area> del mapa de imagen deben tener texto alternativo.",guidance:"Cada región clicable en un mapa de imagen necesita texto alternativo para que los usuarios de lectores de pantalla sepan qué representa la región. Agregue un atributo alt a cada elemento <area> describiendo su propósito. Para mapas de imagen complejos, considere usar enfoques alternativos como SVG con enlaces incrustados, o una lista de enlaces de texto.",messages:{"Image map <area> element is missing alternative text.":"Al elemento <area> del mapa de imagen le falta texto alternativo."}},"text-alternatives/object-alt":{description:"Los elementos <object> deben tener texto alternativo.",guidance:"Los elementos object incrustan contenido externo que puede no ser accesible para todos los usuarios. Proporcione texto alternativo mediante aria-label, aria-labelledby o un atributo title. El contenido de respaldo dentro de <object> solo se muestra cuando el objeto no se carga y no sirve como nombre accesible.",messages:{"<object> element is missing alternative text. Add aria-label, aria-labelledby, or a title attribute.":"Al elemento <object> le falta texto alternativo. Agregue aria-label, aria-labelledby o un atributo title."}},"text-alternatives/role-img-alt":{description:"Los elementos con role='img' deben tener un nombre accesible.",guidance:"Cuando asigna role='img' a un elemento (como un div que contiene fuentes de iconos o fondos CSS), debe proporcionar un nombre accesible mediante aria-label o aria-labelledby. Sin esto, los usuarios de lectores de pantalla no tienen forma de entender lo que representa la imagen. Si la imagen es decorativa, use role='presentation' o role='none' en su lugar.",messages:{"Element with role='img' has no accessible name. Add aria-label or aria-labelledby.":"El elemento con role='img' no tiene nombre accesible. Agregue aria-label o aria-labelledby."}},"keyboard-accessible/server-image-map":{description:"No se deben usar mapas de imagen del lado del servidor.",guidance:"Los mapas de imagen del lado del servidor (usando el atributo ismap) envían coordenadas de clic al servidor, lo cual es inaccesible para usuarios de teclado y lectores de pantalla que no pueden hacer clic con precisión en regiones específicas. Reemplace con mapas de imagen del lado del cliente (elementos <map> con <area>) que proporcionan acceso por teclado y nombres accesibles, o use imágenes/botones enlazados.",messages:{"Server-side image map detected. Use client-side image map with <map> and <area> elements instead.":"Se detectó un mapa de imagen del lado del servidor. Use un mapa de imagen del lado del cliente con elementos <map> y <area> en su lugar."}},"labels-and-names/form-label":{description:"Los elementos de formulario deben tener etiquetas. Use <label>, aria-label o aria-labelledby.",guidance:"Cada entrada de formulario necesita una etiqueta accesible para que los usuarios comprendan qué información ingresar. Use un elemento <label> con un atributo for que coincida con el id de la entrada, envuelva la entrada en un <label>, o use aria-label/aria-labelledby para componentes personalizados. Los placeholders no son suficientes como etiquetas ya que desaparecen al escribir. Las etiquetas deben describir la información solicitada, no el tipo de campo (por ejemplo, 'Dirección de correo', 'Buscar', 'Número de teléfono').",messages:{"Form element has no accessible label.":"El elemento de formulario no tiene etiqueta accesible."}},"labels-and-names/multiple-labels":{description:"Los campos de formulario no deben tener múltiples elementos label.",guidance:"Cuando un campo de formulario tiene múltiples elementos <label> apuntando a él, las tecnologías de asistencia pueden anunciar solo una etiqueta o comportarse de manera inconsistente. Use un solo <label> y combine cualquier texto adicional en él, o use aria-describedby para información complementaria.",messages:{"Form field has {0} labels. Use a single label element.":"El campo de formulario tiene {0} etiquetas. Use un solo elemento label."}},"labels-and-names/input-button-name":{description:"Los botones de entrada deben tener texto discernible mediante value, aria-label o aria-labelledby.",guidance:"Los botones de entrada (<input type='submit'>, type='button', type='reset'>) necesitan nombres accesibles para que los usuarios sepan qué acción realiza el botón. Agregue un atributo value con texto descriptivo (por ejemplo, value='Enviar formulario'), o use aria-label si el valor debe diferir del nombre accesible.",messages:{"Input button has no discernible text.":"El botón de entrada no tiene texto discernible."}},"adaptable/autocomplete-valid":{description:"El atributo autocomplete debe usar valores válidos de la especificación HTML.",guidance:"El atributo autocomplete ayuda a los usuarios a completar formularios identificando los propósitos de las entradas. Use valores estándar como 'name', 'email', 'tel', 'street-address', 'postal-code', 'cc-number'. Esto beneficia a usuarios con discapacidades cognitivas, impedimentos motores y cualquier persona que use administradores de contraseñas o autocompletado. Consulte la especificación HTML para la lista completa de tokens válidos.",messages:{'Invalid autocomplete value "{0}".':'Valor de autocomplete inválido "{0}".'}},"labels-and-names/label-content-mismatch":{description:"Los elementos interactivos con texto visible deben tener nombres accesibles que contengan ese texto.",guidance:"Para los usuarios de control por voz que activan controles hablando su etiqueta visible, el nombre accesible debe incluir el texto visible. Si aria-label es 'Enviar formulario' pero el botón muestra 'Enviar', los usuarios de voz que digan 'clic Enviar' no lo activarán. Asegúrese de que aria-label/aria-labelledby contenga o coincida con el texto visible.",messages:{'Accessible name "{0}" does not contain visible text "{1}".':'El nombre accesible "{0}" no contiene el texto visible "{1}".'}},"labels-and-names/label-title-only":{description:"Los elementos de formulario no deben usar el atributo title como único nombre accesible.",guidance:"El atributo title no es confiable como etiqueta porque solo aparece al pasar el cursor/enfocar (no visible para usuarios táctiles) y a menudo es ignorado por las tecnologías de asistencia. Use un elemento <label> visible, aria-label o aria-labelledby en su lugar. El title puede complementar una etiqueta pero no debe reemplazarla.",messages:{"Form element uses title attribute as only label. Use <label>, aria-label, or aria-labelledby instead.":"El elemento de formulario usa el atributo title como única etiqueta. Use <label>, aria-label o aria-labelledby en su lugar."}},"keyboard-accessible/tabindex":{description:"Los elementos no deben tener tabindex mayor que 0, lo cual altera el orden natural de tabulación.",guidance:"Los valores positivos de tabindex fuerzan a los elementos al frente del orden de tabulación independientemente de la posición en el DOM, creando una navegación impredecible para los usuarios de teclado. Use tabindex='0' para agregar elementos al orden natural de tabulación, o tabindex='-1' para hacer elementos enfocables programáticamente pero no en el orden de tabulación. Confíe en el orden del DOM para la secuencia de tabulación.",messages:{'Element has tabindex="{0}" which disrupts tab order.':'El elemento tiene tabindex="{0}" lo cual altera el orden de tabulación.'}},"keyboard-accessible/focus-order":{description:"Los elementos que reciben el foco del teclado deben tener un rol apropiado para que las tecnologías de asistencia puedan transmitir su propósito. Los elementos no interactivos con tabindex='0' necesitan un rol ARIA interactivo válido.",guidance:"Al agregar tabindex='0' a elementos no interactivos como <div> o <span>, los lectores de pantalla los anuncian genéricamente. Agregue un rol apropiado (button, link, tab, etc.) para que los usuarios comprendan el propósito del elemento. También agregue manejadores de eventos de teclado (Enter/Espacio para botones, Enter para enlaces). Considere usar elementos interactivos nativos en su lugar.",messages:{'Non-interactive <{0}> with tabindex="0" has no interactive role.':'El elemento no interactivo <{0}> con tabindex="0" no tiene un rol interactivo.'}},"keyboard-accessible/nested-interactive":{description:"Los controles interactivos no deben estar anidados dentro de otros.",guidance:"Anidar elementos interactivos (como un botón dentro de un enlace, o un enlace dentro de un botón) crea un comportamiento impredecible y confunde a las tecnologías de asistencia. El navegador puede eliminar el elemento interno del árbol de accesibilidad. Reestructure el HTML para que los elementos interactivos sean hermanos, no anidados. Si necesita una tarjeta clicable, use CSS y JavaScript en lugar de anidar.",messages:{"Interactive element <{0}> is nested inside <{1}>.":"El elemento interactivo <{0}> está anidado dentro de <{1}>."}},"keyboard-accessible/scrollable-region":{description:"Las regiones desplazables deben ser accesibles por teclado.",guidance:"El contenido que se desplaza debe ser accesible para los usuarios de teclado. Si una región tiene overflow:scroll u overflow:auto y contiene contenido desplazable, necesita tabindex='0' para ser enfocable, o debe contener elementos enfocables. Sin esto, los usuarios de teclado no pueden desplazar el contenido.",messages:{"Scrollable region is not keyboard accessible. Add tabindex='0' or include focusable elements.":"La región desplazable no es accesible por teclado. Agregue tabindex='0' o incluya elementos enfocables."}},"keyboard-accessible/accesskeys":{description:"Los valores del atributo accesskey deben ser únicos.",guidance:"Cuando múltiples elementos comparten la misma accesskey, el comportamiento del navegador se vuelve impredecible; generalmente solo se activa el primer elemento. Asegúrese de que cada valor de accesskey sea único dentro de la página. También considere que las accesskeys pueden entrar en conflicto con los atajos del navegador y del lector de pantalla, así que úselas con moderación.",messages:{'Duplicate accesskey "{0}". Each accesskey must be unique.':'Accesskey duplicada "{0}". Cada accesskey debe ser única.'}},"navigable/heading-order":{description:"Los niveles de encabezado deben incrementarse de uno en uno; saltarse niveles (por ejemplo, h2 a h4) dificulta la navegación.",guidance:"Los usuarios de lectores de pantalla navegan por encabezados para comprender la estructura de la página. Saltarse niveles (h2 a h4) sugiere contenido faltante y crea confusión. Comience con h1 para el título de la página, luego use h2 para secciones principales, h3 para subsecciones, etc. Puede volver a subir (h3 a h2) al comenzar una nueva sección.",messages:{"Heading level {0} skipped from level {1}. Use h{2} instead.":"Nivel de encabezado {0} saltado desde el nivel {1}. Use h{2} en su lugar."}},"navigable/empty-heading":{description:"Los encabezados deben tener texto discernible.",guidance:"Los usuarios de lectores de pantalla navegan las páginas por encabezados, por lo que los encabezados vacíos crean puntos de navegación confusos. Asegúrese de que todos los encabezados contengan texto visible o nombres accesibles. Si un encabezado se usa puramente para estilo visual, use CSS en lugar de elementos de encabezado.",messages:{"Heading is empty. Add text content or remove the heading element.":"El encabezado está vacío. Agregue contenido de texto o elimine el elemento de encabezado."}},"navigable/p-as-heading":{description:"Los párrafos no deben estilizarse para parecer encabezados.",guidance:"Cuando los párrafos se estilizan con negrita y fuentes grandes para parecer encabezados, los usuarios de lectores de pantalla pierden la estructura semántica. Use elementos de encabezado apropiados (h1-h6) en lugar de párrafos estilizados. Si necesita un estilo específico, aplique CSS a los elementos de encabezado manteniendo la jerarquía adecuada de encabezados.",messages:{"Paragraph appears to be styled as a heading. Use an h1-h6 element instead.":"El párrafo parece estar estilizado como un encabezado. Use un elemento h1-h6 en su lugar."}},"landmarks/landmark-main":{description:"La página debe tener exactamente un landmark main.",guidance:"El landmark main contiene el contenido principal de la página. Los lectores de pantalla permiten a los usuarios saltar directamente al contenido principal. Use un solo elemento <main> (o role='main') para envolver el contenido central, excluyendo encabezados, pies de página y navegación.",messages:{"Page has no main landmark.":"La página no tiene un landmark main.","Page has multiple main landmarks.":"La página tiene múltiples landmarks main."}},"landmarks/no-duplicate-banner":{description:"La página no debe tener más de un landmark banner.",guidance:"El landmark banner (típicamente <header>) identifica contenido orientado al sitio como logotipos y búsqueda. Solo se permite un banner de nivel superior por página. Si necesita múltiples encabezados, anídelos dentro de elementos de sección (article, section, aside) donde se convierten en encabezados de alcance en lugar de banners de nivel de página.",messages:{"Page has multiple banner landmarks.":"La página tiene múltiples landmarks banner."}},"landmarks/no-duplicate-contentinfo":{description:"La página no debe tener más de un landmark contentinfo.",guidance:"El landmark contentinfo (típicamente <footer>) contiene información sobre la página como derechos de autor e información de contacto. Solo se permite un contentinfo de nivel superior por página. Anide pies de página adicionales dentro de elementos de sección para delimitar su alcance.",messages:{"Page has multiple contentinfo landmarks.":"La página tiene múltiples landmarks contentinfo."}},"landmarks/no-duplicate-main":{description:"La página no debe tener más de un landmark main.",guidance:"Solo debe existir un landmark main por página. El landmark main identifica el área de contenido principal. Si tiene múltiples secciones de contenido, use <section> con encabezados apropiados en lugar de múltiples elementos main.",messages:{"Page has multiple main landmarks.":"La página tiene múltiples landmarks main."}},"landmarks/banner-is-top-level":{description:"El landmark banner no debe estar anidado dentro de otro landmark.",guidance:"El landmark banner debe ser un landmark de nivel superior, no anidado dentro de article, aside, main, nav o section. Si un header está dentro de estos elementos, automáticamente se convierte en un encabezado genérico en lugar de un banner. Elimine el role='banner' explícito de los headers anidados o reestructure la página.",messages:{"Banner landmark is nested within another landmark.":"El landmark banner está anidado dentro de otro landmark."}},"landmarks/contentinfo-is-top-level":{description:"El landmark contentinfo no debe estar anidado dentro de otro landmark.",guidance:"El landmark contentinfo debe ser un landmark de nivel superior. Un footer dentro de article, aside, main, nav o section se convierte en un pie de página de alcance, no en un landmark contentinfo. Elimine el role='contentinfo' explícito de los footers anidados o mueva el footer fuera de los elementos de sección.",messages:{"Contentinfo landmark is nested within another landmark.":"El landmark contentinfo está anidado dentro de otro landmark."}},"landmarks/main-is-top-level":{description:"El landmark main no debe estar anidado dentro de otro landmark.",guidance:"El landmark main debe ser un landmark de nivel superior ya que representa el contenido principal de la página. No anide <main> o role='main' dentro de elementos article, aside, nav o section.",messages:{"Main landmark is nested within another landmark.":"El landmark main está anidado dentro de otro landmark."}},"landmarks/complementary-is-top-level":{description:"El landmark aside (complementary) debe ser de nivel superior o estar directamente dentro de main.",guidance:"El landmark complementary (aside) debe ser de nivel superior o un hijo directo de main. Anidar aside profundamente dentro de otros landmarks reduce su descubrimiento para los usuarios de lectores de pantalla que navegan por landmarks.",messages:{"Complementary landmark should be top-level.":"El landmark complementary debe ser de nivel superior."}},"landmarks/landmark-unique":{description:"Los landmarks deben tener etiquetas únicas cuando hay múltiples del mismo tipo.",guidance:"Cuando una página tiene múltiples landmarks del mismo tipo (por ejemplo, múltiples elementos nav), cada uno debe tener un nombre accesible único mediante aria-label o aria-labelledby. Esto ayuda a los usuarios de lectores de pantalla a distinguirlos (por ejemplo, 'Navegación principal' vs 'Navegación del pie de página').",messages:{'Multiple {0} landmarks have the same label "{1}".':'Múltiples landmarks {0} tienen la misma etiqueta "{1}".',"Multiple {0} landmarks have no label. Add unique aria-label attributes.":"Múltiples landmarks {0} no tienen etiqueta. Agregue atributos aria-label únicos."}},"landmarks/region":{description:"Todo el contenido de la página debe estar contenido dentro de landmarks.",guidance:"Los usuarios de lectores de pantalla navegan las páginas por landmarks. El contenido fuera de los landmarks es más difícil de encontrar y comprender. Envuelva todo el contenido visible en landmarks apropiados: <header>, <nav>, <main>, <aside>, <footer>, o <section> con una etiqueta. Los enlaces de salto pueden existir fuera de los landmarks.",messages:{"Content is not contained within a landmark region.":"El contenido no está contenido dentro de una región landmark."}},"adaptable/list-children":{description:"<ul> y <ol> solo deben contener <li>, <script> o <template> como hijos directos.",guidance:"Los lectores de pantalla anuncian la estructura de la lista ('lista con 5 elementos') basándose en el marcado correcto. Colocar elementos que no son <li> directamente dentro de <ul> u <ol> rompe esta estructura. Envuelva el contenido en elementos <li>, o si necesita divs envolventes para el estilo, aplique los estilos a los elementos <li> directamente y elimine el envoltorio (por ejemplo, cambie <ul><div>item</div></ul> a <ul><li>item</li></ul>).",messages:{"List contains non-<li> child <{0}>.":"La lista contiene un hijo no <li>: <{0}>."}},"adaptable/listitem-parent":{description:"Los elementos <li> deben estar contenidos en un <ul>, <ol> o <menu>.",guidance:"Los elementos de lista (<li>) solo tienen significado semántico dentro de un contenedor de lista (<ul>, <ol> o <menu>). Fuera de estos contenedores, las tecnologías de asistencia no pueden transmitir la relación de lista. Envuelva los elementos <li> en el contenedor de lista apropiado.",messages:{"<li> is not contained in a <ul>, <ol>, or <menu>.":"<li> no está contenido en un <ul>, <ol> o <menu>."}},"adaptable/dl-children":{description:"Los elementos <dt> y <dd> deben estar contenidos en un <dl>.",guidance:"Los términos de definición (<dt>) y las definiciones (<dd>) solo tienen significado semántico dentro de una lista de definiciones (<dl>). Fuera de <dl>, se tratan como texto genérico. Envuelva los pares relacionados de <dt> y <dd> en un elemento <dl> para transmitir la relación término/definición a las tecnologías de asistencia.",messages:{"<{0}> is not contained in a <dl>.":"<{0}> no está contenido en un <dl>."}},"adaptable/definition-list":{description:"Los elementos <dl> solo deben contener <dt>, <dd>, <div>, <script> o <template>.",guidance:"Las listas de definiciones tienen requisitos estrictos de contenido. Solo <dt> (términos), <dd> (definiciones) y <div> (para agrupar pares dt/dd) son hijos válidos. Otros elementos rompen la estructura de la lista para los lectores de pantalla. Mueva los elementos inválidos fuera del <dl>, o si representan un término cámbielos a <dt>, si son una definición cámbielos a <dd>. Los envoltorios de estilo deben reemplazarse con elementos <div> que contengan pares <dt>/<dd>.",messages:{"<dl> contains invalid child <{0}>.":"<dl> contiene un hijo inválido <{0}>."}},"aria/aria-roles":{description:"Los valores de rol ARIA deben ser válidos.",guidance:"Los valores de rol inválidos son ignorados por las tecnologías de asistencia, lo que significa que el elemento no tendrá la semántica prevista. Verifique la ortografía y use solo roles definidos en la especificación WAI-ARIA. Los roles comunes incluyen: button, link, navigation, main, dialog, alert, tab, tabpanel, menu, menuitem.",messages:{'Invalid ARIA role "{0}".':'Rol ARIA inválido "{0}".'}},"aria/aria-valid-attr":{description:"Los atributos ARIA deben ser válidos (correctamente escritos).",guidance:"Los atributos ARIA mal escritos son ignorados por las tecnologías de asistencia. Verifique la ortografía contra la especificación WAI-ARIA. Errores comunes: aria-labeledby (debe ser aria-labelledby), aria-role (debe ser role), aria-description (válido en ARIA 1.3+).",messages:{'Invalid ARIA attribute "{0}".':'Atributo ARIA inválido "{0}".'}},"aria/aria-valid-attr-value":{description:"Los atributos ARIA deben tener valores válidos.",guidance:"Cada atributo ARIA acepta tipos de valores específicos. Los atributos booleanos (aria-hidden, aria-disabled) aceptan solo 'true' o 'false'. Los atributos triestado (aria-checked, aria-pressed) también aceptan 'mixed'. Los atributos de token (aria-live, aria-autocomplete) aceptan valores predefinidos. Los atributos de referencia de ID (aria-labelledby, aria-describedby) deben referenciar IDs de elementos existentes.",messages:{'{0} must be "true" or "false", got "{1}".':'{0} debe ser "true" o "false", se obtuvo "{1}".','{0} must be "true", "false", or "mixed", got "{1}".':'{0} debe ser "true", "false" o "mixed", se obtuvo "{1}".','{0} must be an integer, got "{1}".':'{0} debe ser un entero, se obtuvo "{1}".','{0} must be a number, got "{1}".':'{0} debe ser un número, se obtuvo "{1}".','Invalid value "{0}" for {1}.':'Valor inválido "{0}" para {1}.'}},"aria/aria-required-attr":{description:"Los elementos con roles ARIA deben tener todos los atributos ARIA requeridos.",guidance:"Algunos roles ARIA requieren atributos específicos para funcionar correctamente. Por ejemplo, checkbox requiere aria-checked, slider requiere aria-valuenow, heading requiere aria-level. Sin estos atributos, las tecnologías de asistencia no pueden transmitir el estado o valor del elemento a los usuarios. Agregue el atributo requerido faltante con un valor apropiado.",messages:{'Role "{0}" requires attribute "{1}".':'El rol "{0}" requiere el atributo "{1}".'}},"aria/aria-allowed-attr":{description:"Los atributos ARIA deben estar permitidos para el rol del elemento.",guidance:"Cada rol ARIA admite atributos específicos. Usar atributos no admitidos crea confusión para las tecnologías de asistencia. Consulte la especificación ARIA para saber qué atributos son válidos para cada rol, o elimine el atributo si no es necesario.",messages:{'ARIA attribute "{0}" is not allowed on role "{1}".':'El atributo ARIA "{0}" no está permitido en el rol "{1}".'}},"aria/aria-allowed-role":{description:"El rol ARIA debe ser apropiado para el elemento.",guidance:"No todos los roles ARIA se pueden aplicar a todos los elementos HTML. Muchos elementos tienen roles implícitos (por ejemplo, <header> es implícitamente banner, <nav> es navigation, <main> es main). Agregar un rol explícito que coincida con el rol implícito es redundante. Agregar un rol conflictivo rompe la semántica. Elimine el atributo role o use un elemento diferente.",messages:{"Element <{0}> should not have an explicit role.":"El elemento <{0}> no debería tener un rol explícito.",'Role "{0}" is not allowed on element <{1}>.':'El rol "{0}" no está permitido en el elemento <{1}>.'}},"adaptable/aria-required-children":{description:"Ciertos roles ARIA requieren que estén presentes roles hijos específicos.",guidance:"Algunos roles ARIA representan contenedores que deben contener roles hijos específicos para una semántica adecuada. Por ejemplo, una lista debe contener listitems, un menú debe contener menuitems. Agregue los elementos hijos requeridos con roles apropiados, o use elementos HTML nativos que proporcionen esta semántica implícitamente (por ejemplo, <ul> con <li>).",messages:{'Role "{0}" requires children with role: {1}.':'El rol "{0}" requiere hijos con rol: {1}.'}},"adaptable/aria-required-parent":{description:"Ciertos roles ARIA deben estar contenidos dentro de roles padre específicos.",guidance:"Algunos roles ARIA representan elementos que deben existir dentro de roles de contenedor específicos. Por ejemplo, un listitem debe estar dentro de una lista, un tab debe estar dentro de un tablist. Envuelva el elemento en el padre apropiado, o use elementos HTML nativos que proporcionen esta estructura (por ejemplo, <li> dentro de <ul>).",messages:{'Role "{0}" must be contained within: {1}.':'El rol "{0}" debe estar contenido dentro de: {1}.'}},"aria/aria-hidden-body":{description:"aria-hidden='true' no debe estar presente en el body del documento.",guidance:"Establecer aria-hidden='true' en el elemento body oculta todo el contenido de la página de las tecnologías de asistencia, haciendo la página completamente inaccesible para los usuarios de lectores de pantalla. Elimine aria-hidden del elemento body. Si necesita ocultar contenido temporalmente (por ejemplo, detrás de un modal), use aria-hidden en secciones específicas en su lugar.",messages:{"aria-hidden='true' on body hides all content from assistive technologies.":"aria-hidden='true' en el body oculta todo el contenido de las tecnologías de asistencia."}},"aria/aria-hidden-focus":{description:"Los elementos con aria-hidden='true' no deben contener elementos enfocables.",guidance:"Cuando aria-hidden='true' oculta un elemento de las tecnologías de asistencia pero el elemento contiene hijos enfocables, los usuarios de teclado pueden enfocar esos hijos pero los usuarios de lectores de pantalla no sabrán que existen. Elimine los elementos enfocables de la región oculta, agregue tabindex='-1' a ellos, o elimine aria-hidden.",messages:{"Focusable element is inside an aria-hidden region.":"Un elemento enfocable está dentro de una región aria-hidden."}},"labels-and-names/aria-command-name":{description:"Los comandos ARIA deben tener un nombre accesible.",guidance:"Los roles de comando ARIA interactivos (button, link, menuitem) deben tener nombres accesibles para que los usuarios sepan qué acción realizan. Agregue contenido de texto visible, aria-label o aria-labelledby para proporcionar un nombre.",messages:{"ARIA command has no accessible name.":"El comando ARIA no tiene nombre accesible."}},"labels-and-names/aria-input-field-name":{description:"Los campos de entrada ARIA deben tener un nombre accesible.",guidance:"Los widgets de entrada ARIA (combobox, listbox, searchbox, slider, spinbutton, textbox) deben tener nombres accesibles para que los usuarios comprendan qué datos ingresar. Agregue una etiqueta visible con aria-labelledby, o use aria-label si una etiqueta visible no es posible.",messages:{"ARIA input field has no accessible name.":"El campo de entrada ARIA no tiene nombre accesible."}},"labels-and-names/aria-toggle-field-name":{description:"Los campos de alternancia ARIA deben tener un nombre accesible.",guidance:"Los controles de alternancia ARIA (checkbox, switch, radio, menuitemcheckbox, menuitemradio) deben tener nombres accesibles para que los usuarios comprendan qué opción están seleccionando. Agregue contenido de texto visible, aria-label, o use aria-labelledby para referenciar una etiqueta visible.",messages:{"ARIA toggle field has no accessible name.":"El campo de alternancia ARIA no tiene nombre accesible."}},"labels-and-names/aria-meter-name":{description:"Los elementos meter ARIA deben tener un nombre accesible.",guidance:"Los elementos meter muestran un valor dentro de un rango conocido (como uso de disco o fortaleza de contraseña). Deben tener nombres accesibles para que los usuarios de lectores de pantalla comprendan qué se está midiendo. Use aria-label o aria-labelledby para proporcionar contexto.",messages:{"Meter has no accessible name.":"El meter no tiene nombre accesible."}},"labels-and-names/aria-progressbar-name":{description:"Los elementos progressbar ARIA deben tener un nombre accesible.",guidance:"Los indicadores de progreso deben tener nombres accesibles para que los usuarios de lectores de pantalla comprendan qué proceso se está rastreando. Use aria-label (por ejemplo, 'Progreso de carga de archivo') o aria-labelledby para referenciar un encabezado o etiqueta visible.",messages:{"Progressbar has no accessible name.":"La barra de progreso no tiene nombre accesible."}},"labels-and-names/aria-dialog-name":{description:"Los diálogos ARIA deben tener un nombre accesible.",guidance:"Los elementos dialog y alertdialog deben tener nombres accesibles para que los usuarios de lectores de pantalla comprendan el propósito del diálogo cuando se abre. Use aria-label o aria-labelledby apuntando al encabezado del diálogo. Los elementos nativos <dialog> también deben tener un nombre accesible.",messages:{"Dialog has no accessible name.":"El diálogo no tiene nombre accesible."}},"labels-and-names/aria-tooltip-name":{description:"Los tooltips ARIA deben tener un nombre accesible.",guidance:"Los elementos tooltip deben tener nombres accesibles (generalmente su contenido de texto). El contenido del tooltip típicamente sirve como el nombre accesible. Asegúrese de que el tooltip contenga contenido de texto descriptivo o tenga aria-label.",messages:{"Tooltip has no accessible name.":"El tooltip no tiene nombre accesible."}},"labels-and-names/aria-treeitem-name":{description:"Los elementos treeitem ARIA deben tener un nombre accesible.",guidance:"Los elementos de árbol deben tener nombres accesibles para que los usuarios de lectores de pantalla puedan comprender la estructura del árbol y navegarlo eficazmente. Proporcione contenido de texto, aria-label o aria-labelledby para cada treeitem.",messages:{"Treeitem has no accessible name.":"El treeitem no tiene nombre accesible."}},"aria/aria-prohibited-attr":{description:"Los atributos ARIA no deben estar prohibidos para el rol del elemento.",guidance:"Algunos roles ARIA prohíben ciertos atributos. Por ejemplo, roles como 'none', 'presentation', 'generic' y roles de nivel de texto (code, emphasis, strong) prohíben aria-label y aria-labelledby porque el nombramiento no está soportado para estos roles. Elimine los atributos prohibidos o cambie el rol.",messages:{"aria-label and aria-labelledby are prohibited on <{0}> elements.":"aria-label y aria-labelledby están prohibidos en elementos <{0}>.",'aria-label and aria-labelledby are prohibited on role "{0}".':'aria-label y aria-labelledby están prohibidos en el rol "{0}".','Attribute "{0}" is prohibited on role "{1}".':'El atributo "{0}" está prohibido en el rol "{1}".'}},"aria/presentation-role-conflict":{description:"Los elementos con role='presentation' o role='none' no deben ser enfocables ni tener atributos ARIA globales.",guidance:"Cuando un elemento tiene role='presentation' o role='none', está marcado como decorativo y se elimina del árbol de accesibilidad. Sin embargo, si el elemento es enfocable o tiene ciertos atributos ARIA, el rol de presentación se ignora y el elemento permanece accesible. Esto crea confusión. Elimine el rol de presentación, o elimine la enfocabilidad/atributos ARIA.",messages:{"Presentation role conflicts with: {0}. The role will be ignored.":"El rol de presentación entra en conflicto con: {0}. El rol será ignorado.",'Element with implicit presentation role (alt="") conflicts with: {0}. The decorative role will be ignored.':'El elemento con rol de presentación implícito (alt="") entra en conflicto con: {0}. El rol decorativo será ignorado.'}},"labels-and-names/button-name":{description:"Los botones deben tener texto discernible.",guidance:"Los usuarios de lectores de pantalla necesitan saber qué hace un botón. Agregue contenido de texto visible, aria-label o aria-labelledby. Para botones de icono, use aria-label describiendo la acción (por ejemplo, aria-label='Cerrar'). Si el botón contiene una imagen, asegúrese de que la imagen tenga texto alt describiendo la acción del botón.",messages:{"Button has no discernible text.":"El botón no tiene texto discernible."}},"labels-and-names/summary-name":{description:"Los elementos <summary> deben tener un nombre accesible.",guidance:"El elemento <summary> proporciona la etiqueta visible para un widget de divulgación <details>. Debe tener contenido de texto descriptivo para que los usuarios de lectores de pantalla comprendan qué se revelará al expandirse. Agregue texto claro y conciso que indique qué contenido está en la sección de detalles.",messages:{"<summary> element has no accessible name. Add descriptive text.":"El elemento <summary> no tiene nombre accesible. Agregue texto descriptivo."}},"navigable/link-name":{description:"Los enlaces deben tener texto discernible mediante contenido, aria-label o aria-labelledby.",guidance:"Los usuarios de lectores de pantalla necesitan saber a dónde lleva un enlace. Agregue contenido de texto descriptivo, aria-label, o use aria-labelledby. Para enlaces de imagen, asegúrese de que la imagen tenga texto alt describiendo el destino del enlace. Evite texto genérico como 'haga clic aquí' o 'leer más': el texto del enlace debe tener sentido fuera de contexto.",messages:{"Link has no discernible text.":"El enlace no tiene texto discernible."}},"navigable/skip-link":{description:"Los enlaces de salto deben apuntar a un destino válido en la página.",guidance:"Los enlaces de salto permiten a los usuarios de teclado omitir la navegación repetitiva y saltar directamente al contenido principal. El enlace de salto debe ser el primer elemento enfocable en la página, enlazar al contenido principal (por ejemplo, href='#main'), y hacerse visible cuando se enfoca. Puede estar visualmente oculto hasta enfocarse usando CSS.",messages:{'Skip link points to "#{0}" which does not exist on the page.':'El enlace de salto apunta a "#{0}" que no existe en la página.'}},"distinguishable/link-in-text-block":{description:"Los enlaces dentro de bloques de texto deben distinguirse por algo más que solo el color.",guidance:"Los usuarios que no pueden percibir diferencias de color necesitan otras señales visuales para identificar enlaces. Los enlaces en texto deben tener subrayados u otros indicadores no cromáticos. Si usa solo color, asegure un contraste de 3:1 con el texto circundante Y proporcione una indicación adicional al enfocar/pasar el cursor.",messages:{"Link in text block is not visually distinguishable from surrounding text. Add a non-color visual indicator such as an underline or border.":"El enlace en el bloque de texto no es visualmente distinguible del texto circundante. Agregue un indicador visual no cromático como un subrayado o borde."}},"readable/html-has-lang":{description:"El elemento <html> debe tener un atributo lang.",guidance:"Los lectores de pantalla usan el atributo lang para determinar qué reglas de idioma y pronunciación usar. Sin él, el contenido puede pronunciarse incorrectamente. Establezca lang en el idioma principal de la página usando un código BCP 47 (por ejemplo, 'en' para inglés, 'es' para español, 'fr' para francés, 'de' para alemán, 'ja' para japonés, 'zh' para chino, 'pt' para portugués, 'ar' para árabe).",messages:{"<html> element missing lang attribute.":"Al elemento <html> le falta el atributo lang."}},"readable/html-lang-valid":{description:"El atributo lang en <html> debe tener un valor válido.",guidance:"El atributo lang debe usar una etiqueta de idioma BCP 47 válida. Use un código de idioma de 2 o 3 letras (por ejemplo, 'en', 'fr', 'zh'), opcionalmente seguido de un código de región (por ejemplo, 'en-US', 'pt-BR'). Las etiquetas inválidas impiden que los lectores de pantalla pronuncien correctamente el contenido.",messages:{'Invalid lang attribute value "{0}".':'Valor de atributo lang inválido "{0}".'}},"readable/valid-lang":{description:"El atributo lang debe tener un valor válido en todos los elementos.",guidance:"Cuando aparece contenido en un idioma diferente dentro de una página (por ejemplo, una cita en francés en un documento en inglés), envuélvalo con un atributo lang para asegurar la pronunciación correcta. El valor de lang debe ser una etiqueta BCP 47 válida. Códigos comunes: en, es, fr, de, zh, ja, pt, ar, ru.",messages:{"Empty lang attribute value.":"Valor de atributo lang vacío.",'Invalid lang attribute value "{0}".':'Valor de atributo lang inválido "{0}".'}},"readable/html-xml-lang-mismatch":{description:"Los atributos lang y xml:lang en <html> deben coincidir.",guidance:"En documentos XHTML, si tanto lang como xml:lang están presentes, deben especificar el mismo idioma base. Los valores no coincidentes confunden a las tecnologías de asistencia. Elimine xml:lang (preferido para HTML5) o asegúrese de que ambos atributos tengan valores idénticos.",messages:{'lang="{0}" and xml:lang="{1}" do not match.':'lang="{0}" y xml:lang="{1}" no coinciden.'}},"adaptable/td-headers-attr":{description:"Todas las celdas en una tabla que usan el atributo headers deben referenciar IDs de encabezado válidos.",guidance:"El atributo headers en las celdas de tabla debe referenciar IDs de celdas de encabezado (th o td) dentro de la misma tabla. Esto crea asociaciones explícitas para lectores de pantalla. Verifique que todos los IDs referenciados existan y estén escritos correctamente. Para tablas simples, considere usar scope en elementos th en su lugar.",messages:{'Headers attribute references the cell itself ("{0}").':'El atributo headers referencia a la propia celda ("{0}").','Headers attribute references non-existent ID "{0}".':'El atributo headers referencia un ID inexistente "{0}".'}},"adaptable/th-has-data-cells":{description:"Los encabezados de tabla deben estar asociados con celdas de datos.",guidance:"Una tabla con celdas de encabezado (th) pero sin celdas de datos (td) probablemente es un mal uso del marcado de tabla para diseño o tiene contenido faltante. Agregue celdas de datos que los encabezados describan, o use marcado apropiado que no sea de tabla si estos no son datos tabulares.",messages:{"Table has header cells but no data cells.":"La tabla tiene celdas de encabezado pero no celdas de datos."}},"adaptable/td-has-header":{description:"Las celdas de datos en tablas mayores de 3x3 deben tener encabezados asociados.",guidance:"En tablas complejas, los usuarios de lectores de pantalla necesitan asociaciones de encabezados para comprender las celdas de datos. Use elementos th con atributo scope, o el atributo headers en elementos td. Para tablas simples (3x3 o menos), esto es menos crítico ya que el contexto generalmente es claro.",messages:{"Data cell has no associated header. Add th elements with scope, or headers attribute.":"La celda de datos no tiene encabezado asociado. Agregue elementos th con scope o el atributo headers."}},"adaptable/scope-attr-valid":{description:"El atributo scope en los encabezados de tabla debe tener un valor válido.",guidance:"El atributo scope indica a los lectores de pantalla a qué celdas se aplica un encabezado. Los valores válidos son: row, col, rowgroup, colgroup. Usar valores inválidos rompe la asociación entre encabezados y celdas.",messages:{'Invalid scope value "{0}". Use row, col, rowgroup, or colgroup.':'Valor de scope inválido "{0}". Use row, col, rowgroup o colgroup.'}},"adaptable/empty-table-header":{description:"Las celdas de encabezado de tabla deben tener texto visible.",guidance:"Los encabezados de tabla vacíos no proporcionan información a los usuarios de lectores de pantalla. Agregue texto descriptivo al encabezado, o si el encabezado está intencionalmente vacío (como una celda de esquina), considere usar un elemento td en su lugar o agregar una etiqueta visualmente oculta.",messages:{"Table header cell is empty. Add text or use aria-label.":"La celda de encabezado de tabla está vacía. Agregue texto o use aria-label."}},"labels-and-names/duplicate-id-aria":{description:"Los IDs usados en asociaciones ARIA y label deben ser únicos para evitar referencias rotas.",guidance:"Cuando aria-labelledby, aria-describedby, aria-controls o label[for] referencian un ID duplicado, solo se usa el primer elemento coincidente. Esto rompe la relación prevista y puede dejar controles sin nombre o descripciones faltantes. Asegúrese de que los IDs referenciados por atributos ARIA y asociaciones de etiquetas sean únicos en todo el documento.",messages:{'Duplicate ID "{0}" referenced by {1}.':'ID duplicado "{0}" referenciado por {1}.'}},"time-based-media/video-captions":{description:"Los elementos de video deben tener subtítulos mediante <track kind='captions'>.",guidance:"Los subtítulos proporcionan alternativas de texto para el contenido de audio en videos, beneficiando a usuarios sordos y aquellos que no pueden escuchar el audio. Agregue un elemento <track> con kind='captions' apuntando a un archivo de subtítulos WebVTT. Los subtítulos deben incluir tanto el diálogo como los efectos de sonido importantes.",messages:{"Video element has no captions track.":"El elemento de video no tiene pista de subtítulos."}},"time-based-media/audio-transcript":{description:"Los elementos de audio deben tener una alternativa de texto o transcripción.",guidance:"El contenido solo de audio como podcasts o grabaciones necesita una alternativa de texto para usuarios sordos. Proporcione una transcripción en la misma página o enlazada cerca. La transcripción debe incluir todo el contenido hablado y descripciones de sonidos relevantes.",messages:{"Audio element has no transcript or text alternative. Add a transcript or track element.":"El elemento de audio no tiene transcripción o alternativa de texto. Agregue una transcripción o elemento track."}},"distinguishable/color-contrast":{description:"Los elementos de texto deben tener suficiente contraste de color contra el fondo.",guidance:"WCAG SC 1.4.3 requiere una relación de contraste de al menos 4.5:1 para texto normal y 3:1 para texto grande (>=24px o >=18.66px en negrita). Aumente el contraste oscureciendo el texto o aclarando el fondo, o viceversa.",messages:{"Insufficient color contrast ratio of {0}:1 (required {1}:1).":"Relación de contraste de color insuficiente de {0}:1 (requerido {1}:1)."}},"distinguishable/color-contrast-enhanced":{description:"Los elementos de texto deben tener contraste de color mejorado contra el fondo (WCAG AAA).",guidance:"WCAG SC 1.4.6 (AAA) requiere una relación de contraste de al menos 7:1 para texto normal y 4.5:1 para texto grande (>=24px o >=18.66px en negrita).",messages:{"Insufficient enhanced contrast ratio of {0}:1 (required {1}:1).":"Relación de contraste mejorado insuficiente de {0}:1 (requerido {1}:1)."}},"enough-time/meta-refresh-no-exception":{description:"La etiqueta meta refresh no debe usarse con un retraso (sin excepciones).",guidance:"Las actualizaciones automáticas de página y las redirecciones con retraso desorientan a los usuarios. Las redirecciones instantáneas (delay=0) son aceptables, pero cualquier retraso positivo no lo es. Use redirecciones del lado del servidor en su lugar.",messages:{"Page has a {0}-second meta refresh delay. Use a server-side redirect instead.":"La página tiene un retraso de meta refresh de {0} segundos. Use una redirección del lado del servidor en su lugar.","Page has a {0}-second meta refresh delay. Remove the auto-refresh or provide user control.":"La página tiene un retraso de meta refresh de {0} segundos. Elimine la actualización automática o proporcione control al usuario."}},"distinguishable/letter-spacing":{description:"El espaciado de letras establecido con !important en atributos de estilo debe ser al menos 0.12em.",guidance:"WCAG 1.4.12 requiere que los usuarios puedan anular el espaciado de texto. Usar !important en letter-spacing con un valor inferior a 0.12em lo impide. Aumente el valor a al menos 0.12em o elimine !important.",messages:{"Letter spacing {0}em with !important is below the 0.12em minimum.":"El espaciado de letras {0}em con !important está por debajo del mínimo de 0.12em."}},"distinguishable/line-height":{description:"La altura de línea establecida con !important en atributos de estilo debe ser al menos 1.5.",guidance:"WCAG 1.4.12 requiere que los usuarios puedan anular el espaciado de texto. Usar !important en line-height con un valor inferior a 1.5 lo impide. Aumente el valor a al menos 1.5 o elimine !important.",messages:{"Line height {0} with !important is below the 1.5 minimum.":"La altura de línea {0} con !important está por debajo del mínimo de 1.5."}},"distinguishable/word-spacing":{description:"El espaciado de palabras establecido con !important en atributos de estilo debe ser al menos 0.16em.",guidance:"WCAG 1.4.12 requiere que los usuarios puedan anular el espaciado de texto. Usar !important en word-spacing con un valor inferior a 0.16em lo impide. Aumente el valor a al menos 0.16em o elimine !important.",messages:{"Word spacing {0}em with !important is below the 0.16em minimum.":"El espaciado de palabras {0}em con !important está por debajo del mínimo de 0.16em."}},"adaptable/orientation-lock":{description:"La orientación de la página no debe restringirse usando transformaciones CSS.",guidance:"Los usuarios con discapacidades motoras pueden montar su dispositivo en una orientación fija. Usar transformaciones CSS con @media (orientation: portrait/landscape) para rotar el contenido 90° bloquea efectivamente la página a una orientación. Elimine la transformación dependiente de la orientación y use diseño responsivo en su lugar.",messages:{"CSS locks page orientation via @media (orientation: {0}) with a 90° transform.":"CSS bloquea la orientación de la página mediante @media (orientation: {0}) con una transformación de 90°."}},"aria/presentational-children-focusable":{description:"Los elementos con un rol que hace a los hijos presentacionales no deben contener contenido enfocable.",guidance:"Roles como button, checkbox, img, tab y otros hacen que sus hijos sean presentacionales — ocultos de las tecnologías de asistencia. Si esos hijos son enfocables, los usuarios de teclado pueden alcanzar elementos que los usuarios de lectores de pantalla no pueden percibir. Mueva el contenido enfocable fuera del padre o elimine la enfocabilidad.",messages:{'Focusable element inside a "{0}" role whose children are presentational.':'Elemento enfocable dentro de un rol "{0}" cuyos hijos son presentacionales.'}},"keyboard-accessible/focus-visible":{description:"Los elementos en orden de foco secuencial deben tener un indicador de foco visible.",guidance:"Los usuarios de teclado necesitan ver qué elemento tiene el foco. No elimine el contorno de foco predeterminado (outline: none) sin proporcionar un indicador visible alternativo. Use estilos :focus-visible o :focus para asegurar que el foco siempre sea perceptible.",messages:{"Focusable element has outline removed without a visible focus alternative.":"El elemento enfocable tiene el contorno eliminado sin una alternativa de foco visible."}},"input-assistance/accessible-authentication":{description:'Los campos de contraseña no deben bloquear los administradores de contraseñas. Evite autocomplete="off" y permita pegar.',guidance:'WCAG 2.2 SC 3.3.8 requiere que los pasos de autenticación eviten pruebas de función cognitiva o proporcionen un mecanismo para asistir a los usuarios. Los administradores de contraseñas son un mecanismo de asistencia clave. Establecer autocomplete="off" en campos de contraseña impide que los administradores de contraseñas completen las credenciales. Bloquear el pegado mediante atributos onpaste impide que los usuarios peguen contraseñas almacenadas. Establezca autocomplete en "current-password" para formularios de inicio de sesión o "new-password" para formularios de registro/cambio de contraseña, y no bloquee el pegado en campos de contraseña.',messages:{'Password field has autocomplete="off" which blocks password managers.':'El campo de contraseña tiene autocomplete="off" lo cual bloquea los administradores de contraseñas.',"Password field blocks pasting, preventing password manager use.":"El campo de contraseña bloquea el pegado, impidiendo el uso de administradores de contraseñas."}}};exports.clearAllCaches=fe;exports.clearAriaAttrAuditCache=Ve;exports.clearAriaHiddenCache=je;exports.clearColorCaches=Be;exports.clearComputedRoleCache=Fe;exports.compileDeclarativeRule=E;exports.configureRules=Vn;exports.createChunkedAudit=Bn;exports.diffAudit=Gn;exports.getAccessibleName=y;exports.getAccessibleTextContent=k;exports.getActiveRules=Y;exports.getComputedRole=H;exports.getHtmlSnippet=m;exports.getImplicitRole=le;exports.getRuleById=Xn;exports.getSelector=p;exports.isAriaHidden=h;exports.isValidRole=ze;exports.localeEn=Jn;exports.localeEs=Kn;exports.querySelectorShadowAware=yt;exports.registerLocale=Pt;exports.rules=he;exports.runAudit=_n;exports.translateViolations=ue;exports.validateDeclarativeRule=sa;
|
|
5
|
+
Referenced by: ${d}`:""}`,fix:{type:"suggest",suggestion:"Change the duplicate ID to a unique value so the ARIA or label reference points to the correct element"}})}return a}},An={id:"input-assistance/accessible-authentication",category:"input-assistance",wcag:["3.3.8"],level:"AA",fixability:"mechanical",description:'Password inputs must not block password managers. Avoid autocomplete="off" and allow pasting.',guidance:'WCAG 2.2 SC 3.3.8 requires that authentication steps either avoid cognitive function tests or provide a mechanism to assist users. Password managers are a key assistive mechanism. Setting autocomplete="off" on password fields prevents password managers from filling credentials. Blocking paste via onpaste attributes prevents users from pasting stored passwords. Set autocomplete to "current-password" for login forms or "new-password" for registration/change-password forms, and do not block paste on password fields.',run(e){var t;const a=[];for(const i of e.querySelectorAll('input[type="password"]')){if(h(i)||I(i)||i.disabled||i.getAttribute("aria-disabled")==="true")continue;if(((t=i.getAttribute("autocomplete"))==null?void 0:t.trim().toLowerCase())==="off"){a.push({ruleId:"input-assistance/accessible-authentication",selector:p(i),html:m(i),impact:"critical",message:'Password field has autocomplete="off" which blocks password managers.',fix:{type:"set-attribute",attribute:"autocomplete",value:"current-password"}});continue}const o=i.getAttribute("onpaste");o&&/return\s+false|preventDefault/.test(o)&&a.push({ruleId:"input-assistance/accessible-authentication",selector:p(i),html:m(i),impact:"critical",message:"Password field blocks pasting, preventing password manager use.",fix:{type:"remove-attribute",attribute:"onpaste"}})}return a}},kn={id:"aria/aria-roles",category:"aria",actRuleIds:["674b10"],wcag:["4.1.2"],level:"A",fixability:"contextual",description:"ARIA role values must be valid.",guidance:"Invalid role values are ignored by assistive technologies, meaning the element will not have the intended semantics. Check the spelling and use only roles defined in the WAI-ARIA specification. Common roles include: button, link, navigation, main, dialog, alert, tab, tabpanel, menu, menuitem.",run(e){const a=[];for(const t of e.querySelectorAll("[role]")){const o=t.getAttribute("role").replace(/[\u201C\u201D\u2018\u2019\u00AB\u00BB]/g,"").split(/\s+/).filter(Boolean);!o.some(s=>ze(s))&&o.length>0&&a.push({ruleId:"aria/aria-roles",selector:p(t),html:m(t),impact:"critical",message:`Invalid ARIA role "${o[0]}".`,fix:{type:"remove-attribute",attribute:"role"}})}return a}},Sn={id:"aria/aria-valid-attr",category:"aria",actRuleIds:["5f99a7"],wcag:["4.1.2"],level:"A",fixability:"mechanical",description:"ARIA attributes must be valid (correctly spelled).",guidance:"Misspelled ARIA attributes are ignored by assistive technologies. Check the spelling against the WAI-ARIA specification. Common mistakes: aria-labeledby (should be aria-labelledby), aria-role (should be role), aria-description (valid in ARIA 1.3+).",run(e){return de(e).validAttr}},In={id:"aria/aria-valid-attr-value",category:"aria",actRuleIds:["6a7281"],wcag:["4.1.2"],level:"A",fixability:"contextual",description:"ARIA attributes must have valid values.",guidance:"Each ARIA attribute accepts specific value types. Boolean attributes (aria-hidden, aria-disabled) accept only 'true' or 'false'. Tristate attributes (aria-checked, aria-pressed) also accept 'mixed'. Token attributes (aria-live, aria-autocomplete) accept predefined values. ID reference attributes (aria-labelledby, aria-describedby) must reference existing element IDs.",run(e){return de(e).validAttrValue}},qn={checkbox:["aria-checked"],combobox:["aria-expanded"],heading:["aria-level"],menuitemcheckbox:["aria-checked"],menuitemradio:["aria-checked"],meter:["aria-valuenow"],radio:["aria-checked"],scrollbar:["aria-controls","aria-valuenow"],separator:["aria-valuenow"],slider:["aria-valuenow"],spinbutton:["aria-valuenow"],switch:["aria-checked"]},En={id:"aria/aria-required-attr",category:"aria",actRuleIds:["4e8ab6"],wcag:["4.1.2"],level:"A",fixability:"contextual",description:"Elements with ARIA roles must have all required ARIA attributes.",guidance:"Some ARIA roles require specific attributes to function correctly. For example, checkbox requires aria-checked, slider requires aria-valuenow, heading requires aria-level. Without these attributes, assistive technologies cannot convey the element's state or value to users. Add the missing required attribute with an appropriate value.",run(e){const a=[];for(const t of e.querySelectorAll("[role]")){if(h(t)||t instanceof HTMLElement&&t.style.display==="none")continue;const i=t.getAttribute("role").trim().toLowerCase(),n=qn[i];if(n&&!(i==="checkbox"&&t instanceof HTMLInputElement&&t.type==="checkbox")&&!(i==="radio"&&t instanceof HTMLInputElement&&t.type==="radio")&&!(i==="option"&&t instanceof HTMLOptionElement)&&!(i==="heading"&&/^h[1-6]$/i.test(t.tagName))){if(i==="separator"){const o=t.getAttribute("tabindex");if(!o||o==="-1")continue}if(!(t.tagName.toLowerCase()==="hr"&&!t.hasAttribute("role"))){for(const o of n)if(!t.hasAttribute(o)){a.push({ruleId:"aria/aria-required-attr",selector:p(t),html:m(t),impact:"critical",message:`Role "${i}" requires attribute "${o}".`,fix:{type:"add-attribute",attribute:o,value:""}});break}}}}return a}},Ln={alert:new Set(["aria-atomic","aria-busy","aria-live","aria-relevant"]),alertdialog:new Set(["aria-describedby","aria-modal"]),application:new Set(["aria-activedescendant","aria-disabled","aria-errormessage","aria-expanded","aria-haspopup","aria-invalid"]),article:new Set(["aria-posinset","aria-setsize"]),banner:new Set([]),button:new Set(["aria-disabled","aria-expanded","aria-haspopup","aria-pressed"]),cell:new Set(["aria-colindex","aria-colspan","aria-rowindex","aria-rowspan"]),checkbox:new Set(["aria-checked","aria-disabled","aria-errormessage","aria-expanded","aria-invalid","aria-readonly","aria-required"]),columnheader:new Set(["aria-colindex","aria-colspan","aria-disabled","aria-errormessage","aria-expanded","aria-haspopup","aria-invalid","aria-readonly","aria-required","aria-rowindex","aria-rowspan","aria-selected","aria-sort"]),combobox:new Set(["aria-activedescendant","aria-autocomplete","aria-controls","aria-disabled","aria-errormessage","aria-expanded","aria-haspopup","aria-invalid","aria-readonly","aria-required"]),complementary:new Set([]),contentinfo:new Set([]),definition:new Set([]),dialog:new Set(["aria-describedby","aria-modal"]),directory:new Set([]),document:new Set(["aria-expanded"]),feed:new Set(["aria-busy"]),figure:new Set([]),form:new Set([]),grid:new Set(["aria-activedescendant","aria-colcount","aria-disabled","aria-multiselectable","aria-readonly","aria-rowcount"]),gridcell:new Set(["aria-colindex","aria-colspan","aria-disabled","aria-errormessage","aria-expanded","aria-haspopup","aria-invalid","aria-readonly","aria-required","aria-rowindex","aria-rowspan","aria-selected"]),group:new Set(["aria-activedescendant","aria-disabled"]),heading:new Set(["aria-level"]),img:new Set([]),link:new Set(["aria-disabled","aria-expanded","aria-haspopup"]),list:new Set([]),listbox:new Set(["aria-activedescendant","aria-disabled","aria-errormessage","aria-expanded","aria-invalid","aria-multiselectable","aria-orientation","aria-readonly","aria-required"]),listitem:new Set(["aria-level","aria-posinset","aria-setsize"]),log:new Set(["aria-atomic","aria-busy","aria-live","aria-relevant"]),main:new Set([]),marquee:new Set([]),math:new Set([]),menu:new Set(["aria-activedescendant","aria-disabled","aria-orientation"]),menubar:new Set(["aria-activedescendant","aria-disabled","aria-orientation"]),menuitem:new Set(["aria-disabled","aria-expanded","aria-haspopup","aria-posinset","aria-setsize"]),menuitemcheckbox:new Set(["aria-checked","aria-disabled","aria-expanded","aria-haspopup","aria-posinset","aria-setsize"]),menuitemradio:new Set(["aria-checked","aria-disabled","aria-expanded","aria-haspopup","aria-posinset","aria-setsize"]),meter:new Set(["aria-valuemax","aria-valuemin","aria-valuenow","aria-valuetext"]),navigation:new Set([]),none:new Set([]),note:new Set([]),option:new Set(["aria-checked","aria-disabled","aria-posinset","aria-selected","aria-setsize"]),paragraph:new Set([]),presentation:new Set([]),progressbar:new Set(["aria-valuemax","aria-valuemin","aria-valuenow","aria-valuetext"]),radio:new Set(["aria-checked","aria-disabled","aria-posinset","aria-setsize"]),radiogroup:new Set(["aria-activedescendant","aria-disabled","aria-errormessage","aria-invalid","aria-orientation","aria-readonly","aria-required"]),region:new Set([]),row:new Set(["aria-activedescendant","aria-colindex","aria-disabled","aria-expanded","aria-level","aria-posinset","aria-rowindex","aria-selected","aria-setsize"]),rowgroup:new Set([]),rowheader:new Set(["aria-colindex","aria-colspan","aria-disabled","aria-errormessage","aria-expanded","aria-haspopup","aria-invalid","aria-readonly","aria-required","aria-rowindex","aria-rowspan","aria-selected","aria-sort"]),scrollbar:new Set(["aria-controls","aria-disabled","aria-orientation","aria-valuemax","aria-valuemin","aria-valuenow"]),search:new Set([]),searchbox:new Set(["aria-activedescendant","aria-autocomplete","aria-disabled","aria-errormessage","aria-haspopup","aria-invalid","aria-multiline","aria-placeholder","aria-readonly","aria-required"]),separator:new Set(["aria-disabled","aria-orientation","aria-valuemax","aria-valuemin","aria-valuenow"]),slider:new Set(["aria-disabled","aria-errormessage","aria-haspopup","aria-invalid","aria-orientation","aria-readonly","aria-valuemax","aria-valuemin","aria-valuenow","aria-valuetext"]),spinbutton:new Set(["aria-activedescendant","aria-disabled","aria-errormessage","aria-invalid","aria-readonly","aria-required","aria-valuemax","aria-valuemin","aria-valuenow","aria-valuetext"]),status:new Set(["aria-atomic","aria-live","aria-relevant"]),switch:new Set(["aria-checked","aria-disabled","aria-errormessage","aria-invalid","aria-readonly","aria-required"]),tab:new Set(["aria-disabled","aria-expanded","aria-haspopup","aria-posinset","aria-selected","aria-setsize"]),table:new Set(["aria-colcount","aria-rowcount"]),tablist:new Set(["aria-activedescendant","aria-disabled","aria-multiselectable","aria-orientation"]),tabpanel:new Set([]),term:new Set([]),textbox:new Set(["aria-activedescendant","aria-autocomplete","aria-disabled","aria-errormessage","aria-haspopup","aria-invalid","aria-multiline","aria-placeholder","aria-readonly","aria-required"]),timer:new Set(["aria-atomic","aria-live","aria-relevant"]),toolbar:new Set(["aria-activedescendant","aria-disabled","aria-orientation"]),tooltip:new Set([]),tree:new Set(["aria-activedescendant","aria-disabled","aria-errormessage","aria-invalid","aria-multiselectable","aria-orientation","aria-required"]),treegrid:new Set(["aria-activedescendant","aria-colcount","aria-disabled","aria-errormessage","aria-invalid","aria-multiselectable","aria-orientation","aria-readonly","aria-required","aria-rowcount"]),treeitem:new Set(["aria-checked","aria-disabled","aria-expanded","aria-haspopup","aria-level","aria-posinset","aria-selected","aria-setsize"])},Rn={id:"aria/aria-allowed-attr",category:"aria",actRuleIds:["5c01ea"],wcag:["4.1.2"],level:"A",fixability:"mechanical",description:"ARIA attributes must be allowed for the element's role.",guidance:"Each ARIA role supports specific attributes. Using unsupported attributes creates confusion for assistive technologies. Check the ARIA specification for which attributes are valid for each role, or remove the attribute if it's not needed.",run(e){const a=[],t=new Set(e.querySelectorAll("[role]")),i=e.createTreeWalker(e.body||e.documentElement,1);let n=i.currentNode;for(;n;){if(n instanceof Element){for(const o of n.attributes)if(o.name.startsWith("aria-")){t.add(n);break}}n=i.nextNode()}for(const o of t){if(h(o))continue;const r=H(o);if(!r)continue;const s=Ln[r];if(s)for(const l of o.attributes){if(!l.name.startsWith("aria-")||bt.has(l.name)||s.has(l.name))continue;const d=s.size>0?[...s].join(", "):"none (only global ARIA attributes)";a.push({ruleId:"aria/aria-allowed-attr",selector:p(o),html:m(o),impact:"critical",message:`ARIA attribute "${l.name}" is not allowed on role "${r}".`,context:`Attribute: ${l.name}="${l.value}", role: ${r}, allowed role-specific attributes: ${d}`,fix:{type:"remove-attribute",attribute:l.name}})}}return a}},Cn=new Set(["base","col","colgroup","head","html","keygen","meta","param","script","source","style","template","title","track"]),D={a:new Set(["button","checkbox","menuitem","menuitemcheckbox","menuitemradio","option","radio","switch","tab","treeitem","link"]),"a[href]":new Set(["button","checkbox","menuitem","menuitemcheckbox","menuitemradio","option","radio","switch","tab","treeitem"]),abbr:"any",address:"any",article:new Set(["application","document","feed","main","none","presentation","region"]),aside:new Set(["doc-dedication","doc-example","doc-footnote","doc-glossary","doc-pullquote","doc-tip","feed","none","note","presentation","region","search"]),audio:new Set(["application"]),b:"any",bdi:"any",bdo:"any",blockquote:"any",body:"none",br:new Set(["none","presentation"]),button:new Set(["checkbox","combobox","link","menuitem","menuitemcheckbox","menuitemradio","option","radio","slider","switch","tab"]),canvas:"any",caption:"none",cite:"any",code:"any",data:"any",datalist:new Set(["listbox"]),dd:"none",del:"any",details:new Set(["group"]),dfn:"any",dialog:new Set(["alertdialog"]),div:"any",dl:new Set(["group","list","none","presentation"]),dt:new Set(["listitem"]),em:"any",embed:new Set(["application","document","img","none","presentation"]),fieldset:new Set(["group","none","presentation","radiogroup"]),figcaption:new Set(["group","none","presentation"]),figure:new Set(["doc-example","none","presentation"]),footer:new Set(["doc-footnote","group","none","presentation"]),form:new Set(["none","presentation","search"]),h1:new Set(["doc-subtitle","none","presentation","tab"]),h2:new Set(["doc-subtitle","none","presentation","tab"]),h3:new Set(["doc-subtitle","none","presentation","tab"]),h4:new Set(["doc-subtitle","none","presentation","tab"]),h5:new Set(["doc-subtitle","none","presentation","tab"]),h6:new Set(["doc-subtitle","none","presentation","tab"]),header:new Set(["group","none","presentation"]),hgroup:"any",hr:new Set(["doc-pagebreak","none","presentation"]),i:"any",iframe:new Set(["application","document","img","none","presentation"]),img:"any","img[alt='']":new Set(["none","presentation"]),input:"none","input[type=button]":new Set(["checkbox","combobox","link","menuitem","menuitemcheckbox","menuitemradio","option","radio","slider","spinbutton","switch","tab"]),"input[type=checkbox]":new Set(["button","menuitemcheckbox","option","switch"]),"input[type=image]":new Set(["link","menuitem","menuitemcheckbox","menuitemradio","radio","switch"]),"input[type=radio]":new Set(["menuitemradio"]),"input[type=search]":new Set(["combobox","searchbox"]),"input[type=text]":new Set(["combobox","searchbox","spinbutton"]),ins:"any",kbd:"any",label:"none",legend:"none",li:new Set(["doc-biblioentry","doc-endnote","menuitem","menuitemcheckbox","menuitemradio","none","option","presentation","radio","separator","tab","treeitem"]),main:"none",map:"none",mark:"any",menu:new Set(["directory","group","listbox","menu","menubar","none","presentation","radiogroup","tablist","toolbar","tree"]),meter:"none",nav:new Set(["doc-index","doc-pagelist","doc-toc","menu","menubar","none","presentation","tablist"]),noscript:"none",object:new Set(["application","document","img"]),ol:new Set(["directory","group","listbox","menu","menubar","none","presentation","radiogroup","tablist","toolbar","tree"]),optgroup:new Set(["group"]),option:"none",output:new Set(["status"]),p:"any",picture:"none",pre:"any",progress:"none",q:"any",rp:"any",rt:"any",ruby:"any",s:"any",samp:"any",section:new Set(["alert","alertdialog","application","banner","complementary","contentinfo","dialog","doc-abstract","doc-acknowledgments","doc-afterword","doc-appendix","doc-bibliography","doc-chapter","doc-colophon","doc-conclusion","doc-credit","doc-credits","doc-dedication","doc-endnotes","doc-epigraph","doc-epilogue","doc-errata","doc-example","doc-foreword","doc-glossary","doc-index","doc-introduction","doc-notice","doc-pagelist","doc-part","doc-preface","doc-prologue","doc-pullquote","doc-qna","doc-toc","document","feed","group","log","main","marquee","navigation","none","note","presentation","region","search","status","tabpanel"]),select:new Set(["menu"]),small:"any",span:"any",strong:"any",sub:"any",summary:"none",sup:"any",svg:new Set(["application","document","img","none","presentation"]),table:"any",tbody:"any",td:"any",tfoot:"any",th:"any",thead:"any",time:"any",tr:"any",u:"any",ul:new Set(["directory","group","listbox","menu","menubar","none","presentation","radiogroup","tablist","toolbar","tree"]),var:"any",video:new Set(["application"]),wbr:new Set(["none","presentation"])};function Tn(e){var t;const a=e.tagName.toLowerCase();if(Cn.has(a))return"none";if(a==="a"&&e.hasAttribute("href"))return D["a[href]"];if(a==="img"&&e.getAttribute("alt")==="")return D["img[alt='']"];if(a==="input"){const n=`input[type=${((t=e.getAttribute("type"))==null?void 0:t.toLowerCase())||"text"}]`;return n in D?D[n]:"none"}return D[a]||"any"}const Nn={id:"aria/aria-allowed-role",category:"aria",wcag:["4.1.2"],level:"A",fixability:"contextual",description:"ARIA role must be appropriate for the element.",guidance:"Not all ARIA roles can be applied to all HTML elements. Many elements have implicit roles (e.g., <header> is implicitly banner, <nav> is navigation, <main> is main). Adding an explicit role that matches the implicit role is redundant. Adding a conflicting role breaks semantics. Either remove the role attribute or use a different element.",run(e){var t;const a=[];for(const i of e.querySelectorAll("[role]")){if(h(i))continue;const n=(t=i.getAttribute("role"))==null?void 0:t.trim().toLowerCase();if(!n)continue;const o=le(i);if(o&&n===o)continue;const r=Tn(i);r==="none"?a.push({ruleId:"aria/aria-allowed-role",selector:p(i),html:m(i),impact:"minor",message:`Element <${i.tagName.toLowerCase()}> should not have an explicit role.`}):r!=="any"&&!r.has(n)&&a.push({ruleId:"aria/aria-allowed-role",selector:p(i),html:m(i),impact:"minor",message:`Role "${n}" is not allowed on element <${i.tagName.toLowerCase()}>.`})}return a}},Mn={id:"aria/aria-hidden-body",selector:'body[aria-hidden="true"]',check:{type:"selector-exists"},impact:"critical",message:"aria-hidden='true' on body hides all content from assistive technologies.",description:"aria-hidden='true' must not be present on the document body.",wcag:["4.1.2"],level:"A",tags:["page-level"],fixability:"mechanical",guidance:"Setting aria-hidden='true' on the body element hides all page content from assistive technologies, making the page completely inaccessible to screen reader users. Remove aria-hidden from the body element. If you need to hide content temporarily (e.g., behind a modal), use aria-hidden on specific sections instead.",skipAriaHidden:!1},$n=E(Mn);function Hn(e){let a=e;const t=e.ownerDocument,i=t.defaultView;for(;a&&a!==t.body;){if(a.style.display==="none"||a.style.visibility==="hidden")return!1;if(i){const n=i.getComputedStyle(a);if(n.display==="none"||n.visibility==="hidden")return!1}a=a.parentElement}return!0}function Pn(e){const a=e.ownerDocument.defaultView;if(!a)return!1;const t=a.getComputedStyle(e),i=t.position;if(i!=="absolute"&&i!=="fixed")return!1;const n=parseFloat(t.top),o=parseFloat(t.left);if(!(!isNaN(n)&&n<-500||!isNaN(o)&&o<-500))return!1;const s=e.id;if(!s)return!1;const l=s.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),d=new RegExp(`getElementById\\s*\\(\\s*['"]${l}['"]\\s*\\)\\s*\\.\\s*addEventListener\\s*\\(\\s*['"]focus['"]`);for(const c of e.ownerDocument.querySelectorAll("script")){const u=c.textContent||"";if(d.test(u)&&/\.focus\s*\(/.test(u))return!0}return!1}const Dn={id:"aria/aria-hidden-focus",category:"aria",actRuleIds:["6cfa84"],wcag:["4.1.2"],level:"A",fixability:"contextual",description:"Elements with aria-hidden='true' must not contain focusable elements.",guidance:"When aria-hidden='true' hides an element from assistive technologies but the element contains focusable children, keyboard users can focus those children but screen reader users won't know they exist. Either remove focusable elements from the hidden region, add tabindex='-1' to them, or remove aria-hidden.",run(e){const a=[];for(const t of e.querySelectorAll('[aria-hidden="true"]')){if(t===e.body)continue;const i=[...t.querySelectorAll(W)];t.matches(W)&&i.push(t);for(const n of i)if(n instanceof HTMLElement){const o=n.getAttribute("tabindex");if(o==="-1"||n.disabled||n instanceof HTMLInputElement&&n.type==="hidden"||!Hn(n))continue;const r=n.getAttribute("onfocus")||"";if(/\.focus\s*\(/.test(r)||Pn(n))continue;const s=n.tagName.toLowerCase();let l;o!==null?l=`has tabindex="${o}"`:s==="a"&&n.hasAttribute("href")?l="is a link with href":s==="button"?l="is a <button>":s==="input"?l=`is an <input type="${n.type}">`:s==="select"?l="is a <select>":s==="textarea"?l="is a <textarea>":s==="iframe"?l="is an <iframe>":l=`is a natively focusable <${s}>`;const d=n===t?n:n.closest('[aria-hidden="true"]');a.push({ruleId:"aria/aria-hidden-focus",selector:p(n),html:m(n),impact:"serious",message:"Focusable element is inside an aria-hidden region.",context:`Focusable because: ${l}. aria-hidden ancestor: ${d?m(d):"unknown"}`,fix:{type:"suggest",suggestion:'Add tabindex="-1" to remove from tab order, or move the element outside the aria-hidden region'}})}}return a}},Fn={id:"aria/aria-prohibited-attr",category:"aria",actRuleIds:["kb1m8s"],wcag:["4.1.2"],level:"A",fixability:"mechanical",description:"ARIA attributes must not be prohibited for the element's role.",guidance:"Some ARIA roles prohibit certain attributes. For example, roles like 'none', 'presentation', 'generic', and text-level roles (code, emphasis, strong) prohibit aria-label and aria-labelledby because naming is not supported for these roles. Remove the prohibited attributes or change the role.",run(e){return de(e).prohibitedAttr}},zn=["a[href]","button:not([disabled])",'input:not([disabled]):not([type="hidden"])',"select:not([disabled])","textarea:not([disabled])",'[tabindex]:not([tabindex="-1"])'].join(", "),jn=["aria-atomic","aria-busy","aria-controls","aria-describedby","aria-details","aria-dropeffect","aria-flowto","aria-grabbed","aria-haspopup","aria-keyshortcuts","aria-live","aria-owns","aria-relevant"];function De(e){const a=[];e.matches(zn)&&a.push("element is focusable");for(const t of jn)if(e.hasAttribute(t)){a.push(`has ${t}`);break}return(e.hasAttribute("aria-label")||e.hasAttribute("aria-labelledby"))&&a.push("has accessible name"),a}const Wn={id:"aria/presentation-role-conflict",category:"aria",actRuleIds:["46ca7f"],wcag:["4.1.2"],level:"A",fixability:"contextual",description:"Elements with role='presentation' or role='none' must not be focusable or have global ARIA attributes.",guidance:"When an element has role='presentation' or role='none', it's marked as decorative and removed from the accessibility tree. However, if the element is focusable or has certain ARIA attributes, the presentation role is ignored and the element remains accessible. This creates confusion. Either remove the presentation role, or remove the focusability/ARIA attributes.",run(e){const a=[];for(const t of e.querySelectorAll('[role="presentation"], [role="none"]')){if(h(t))continue;const i=De(t);i.length>0&&a.push({ruleId:"aria/presentation-role-conflict",selector:p(t),html:m(t),impact:"serious",message:`Presentation role conflicts with: ${i.join(", ")}. The role will be ignored.`,fix:{type:"suggest",suggestion:"Remove the presentation/none role, or remove the conflicting focusability and ARIA attributes"}})}for(const t of e.querySelectorAll('img[alt=""]')){if(h(t)||t.hasAttribute("role"))continue;const i=De(t);i.length>0&&a.push({ruleId:"aria/presentation-role-conflict",selector:p(t),html:m(t),impact:"serious",message:`Element with implicit presentation role (alt="") conflicts with: ${i.join(", ")}. The decorative role will be ignored.`,fix:{type:"suggest",suggestion:"Remove the conflicting focusability and ARIA attributes, or add descriptive alt text if the image is not decorative"}})}return a}},Un=new Set(["button","checkbox","img","link","math","menuitemcheckbox","menuitemradio","meter","option","progressbar","radio","scrollbar","separator","slider","spinbutton","switch","tab"]),On={id:"aria/presentational-children-focusable",category:"aria",actRuleIds:["307n5z"],wcag:["4.1.2"],level:"A",fixability:"contextual",description:"Elements with a role that makes children presentational must not contain focusable content.",guidance:"Roles like button, checkbox, img, tab, and others make their children presentational — hidden from assistive technologies. If those children are focusable, keyboard users can reach elements that screen reader users cannot perceive. Move focusable content outside the parent or remove the focusability.",run(e){const a=[];for(const t of e.querySelectorAll("*")){if(h(t))continue;const i=H(t);if(!(!i||!Un.has(i))){for(const n of t.querySelectorAll(W))if(n!==t&&!n.disabled&&n.getAttribute("tabindex")!=="-1"){a.push({ruleId:"aria/presentational-children-focusable",selector:p(n),html:m(n),impact:"serious",message:`Focusable element inside a "${i}" role whose children are presentational.`});break}}}return a}},he=[Ut,Vt,Bt,_t,Yt,Xt,Jt,Kt,Qt,Zt,ra,ua,ma,pa,ha,va,xa,Aa,ka,Sa,qa,Ea,La,Ra,Na,Ma,$a,za,Za,ei,ai,ni,ri,pi,bi,hi,gi,fi,vi,wi,Ai,ki,Si,Ii,qi,Ei,Li,Ci,Ti,Ni,Mi,$i,Hi,Pi,Di,Fi,zi,ji,Wi,Ui,_i,Gi,Yi,Xi,Ji,nn,on,rn,sn,ln,cn,dn,un,mn,pn,bn,hn,gn,vn,yn,xn,An,kn,Sn,In,En,Rn,Nn,$n,Dn,Fn,Wn,On];let ge=[],rt=new Set,st=!1,lt=!1,N,j;function Vn(e){e.additionalRules&&(ge=e.additionalRules),e.disabledRules&&(rt=new Set(e.disabledRules)),"includeAAA"in e&&(st=!!e.includeAAA),"componentMode"in e&&(lt=!!e.componentMode),"locale"in e&&(N=e.locale||void 0),j=void 0}function Y(){if(j)return j;const a=he.filter(t=>{var i;return!(rt.has(t.id)||t.level==="AAA"&&!st||lt&&((i=t.tags)!=null&&i.includes("page-level")))}).concat(ge);return N?(j=Dt(a,N),j):a}function Bn(e){fe();const a=Y(),t=N,i=[],n=[];let o=0;return{processChunk(r){const s=performance.now();for(;o<a.length;){const l=a[o];try{i.push(...l.run(e))}catch(d){n.push({ruleId:l.id,error:d instanceof Error?d.message:String(d)})}if(o++,performance.now()-s>=r)break}return o<a.length},getViolations(){return t?ue(i,t):i},getSkippedRules(){return n}}}function fe(){je(),Fe(),ut(),Be(),Ve(),ht()}function _n(e){var n;fe();const a=Y(),t=[],i=[];for(const o of a)try{t.push(...o.run(e))}catch(r){i.push({ruleId:o.id,error:r instanceof Error?r.message:String(r)})}return{url:((n=e.location)==null?void 0:n.href)??"",timestamp:Date.now(),violations:N?ue(t,N):t,ruleCount:a.length,skippedRules:i}}function Gn(e,a){const t=l=>`${l.ruleId}\0${l.selector}`,i=new Map;for(const l of e.violations)i.set(t(l),l);const n=new Map;for(const l of a.violations)n.set(t(l),l);const o=[],r=[];for(const[l,d]of n)i.has(l)?r.push(d):o.push(d);const s=[];for(const[l,d]of i)n.has(l)||s.push(d);return{added:o,fixed:s,unchanged:r}}const Yn=new Map(he.map(e=>[e.id,e]));function Xn(e){if(N)return Y().find(i=>i.id===e);const a=Yn.get(e);return a||ge.find(t=>t.id===e)}const Jn={"navigable/document-title":{description:"Documents must have a <title> element to provide users with an overview of content.",guidance:"Screen reader users rely on page titles to identify and navigate between tabs/windows. Add a descriptive <title> element in <head> that summarizes the page purpose. Keep titles unique across the site, placing specific content before the site name (e.g., 'Contact Us - Acme Corp').",messages:{"Document <title> element is empty.":"Document <title> element is empty.","Document is missing a <title> element.":"Document is missing a <title> element."}},"navigable/bypass":{description:"Page must have a mechanism to bypass repeated blocks of content.",guidance:'Keyboard users must be able to skip repetitive content like navigation. Provide a skip link at the top of the page that links to the main content (e.g., <a href="#main">Skip to main content</a>), or use a <main> landmark. Screen readers can jump directly to landmarks, so a properly marked-up <main> element satisfies this requirement.',messages:{"Page has no mechanism to bypass repeated content. Add a <main> landmark or skip link.":"Page has no mechanism to bypass repeated content. Add a <main> landmark or skip link."}},"navigable/page-has-heading-one":{description:"Page should contain a level-one heading.",guidance:"A level-one heading (<h1> or role='heading' with aria-level='1') helps users understand the page topic and provides a landmark for screen reader navigation. Each page should have at least one level-one heading that describes the main content, typically matching or similar to the page title.",messages:{"Page does not contain a level-one heading.":"Page does not contain a level-one heading."}},"labels-and-names/frame-title":{description:"Frames must have an accessible name.",guidance:"Screen readers announce frame titles when users navigate frames. Add a title attribute to <iframe> and <frame> elements that describes the frame's purpose (e.g., <iframe title='Video player'>). Avoid generic titles like 'frame' or 'iframe'. If the frame is decorative, use aria-hidden='true'.",messages:{"Frame is missing an accessible name. Add a title attribute.":"Frame is missing an accessible name. Add a title attribute."}},"labels-and-names/frame-title-unique":{description:"Frame titles should be unique.",guidance:"When multiple frames have identical titles, screen reader users cannot distinguish between them. Give each frame a unique, descriptive title that explains its specific purpose or content.",messages:{"Frame title is not unique. Use a distinct title for each frame.":"Frame title is not unique. Use a distinct title for each frame."}},"distinguishable/meta-viewport":{description:"Viewport meta tag must not disable user scaling.",guidance:"Users with low vision need to zoom content up to 200% or more. Setting user-scalable=no or maximum-scale=1 prevents zooming and fails WCAG. Remove these restrictions. If your layout breaks at high zoom, fix the responsive design rather than preventing zoom.",messages:{"Viewport disables user scaling (user-scalable={0}). Remove this restriction.":"Viewport disables user scaling (user-scalable={0}). Remove this restriction.","Viewport maximum-scale={0} restricts zooming. Set to at least 2 or remove.":"Viewport maximum-scale={0} restricts zooming. Set to at least 2 or remove."}},"enough-time/meta-refresh":{description:"Meta refresh must not redirect or refresh automatically.",guidance:"Automatic page refreshes or redirects can disorient users, especially those using screen readers or with cognitive disabilities. They may lose their place or not have time to read content. If a redirect is needed, use a server-side redirect (HTTP 301/302) instead. For timed refreshes, provide user controls.",messages:{"Page redirects after {0} seconds without warning. Use server-side redirect.":"Page redirects after {0} seconds without warning. Use server-side redirect.","Page auto-refreshes after {0} seconds. Provide user control over refresh.":"Page auto-refreshes after {0} seconds. Provide user control over refresh."}},"enough-time/blink":{description:"The <blink> element must not be used.",guidance:"Blinking content can cause seizures in users with photosensitive epilepsy and is distracting for users with attention disorders. The <blink> element is deprecated and should never be used. If you need to draw attention to content, use less intrusive methods like color, borders, or icons.",messages:{"The <blink> element causes accessibility issues. Remove it entirely.":"The <blink> element causes accessibility issues. Remove it entirely."}},"enough-time/marquee":{description:"The <marquee> element must not be used.",guidance:"Scrolling or moving content is difficult for many users to read, especially those with cognitive or visual disabilities. The <marquee> element is deprecated. Replace scrolling text with static content. If content must scroll, provide pause/stop controls and ensure it stops after 5 seconds.",messages:{"The <marquee> element causes accessibility issues. Replace with static content.":"The <marquee> element causes accessibility issues. Replace with static content."}},"text-alternatives/img-alt":{description:`Images must have alternate text. Add an alt attribute to <img> elements. Decorative images may use an empty alt attribute (alt=""), role='none', or role='presentation'.`,guidance:"Every image needs an alt attribute. For informative images, describe the content or function concisely. For decorative images (backgrounds, spacers, purely visual flourishes), use alt='' to hide them from screen readers. Never omit alt entirely—screen readers may read the filename instead. When an image is inside a link or button that already has text, use alt='' if the image is decorative in that context, or write alt text that complements (not duplicates) the existing text.",messages:{'Image has whitespace-only alt text. Use alt="" for decorative images or provide descriptive text.':'Image has whitespace-only alt text. Use alt="" for decorative images or provide descriptive text.',"Image element missing alt attribute.":"Image element missing alt attribute.",'Element with role="img" has no accessible name. Add aria-label or aria-labelledby.':'Element with role="img" has no accessible name. Add aria-label or aria-labelledby.'}},"text-alternatives/svg-img-alt":{description:"SVG elements with an img, graphics-document, or graphics-symbol role must have an accessible name via a <title> element, aria-label, or aria-labelledby.",guidance:"Inline SVGs with role='img' need accessible names. Add a <title> element as the first child of the SVG (screen readers will announce it), or use aria-label on the SVG element. For complex SVGs, use aria-labelledby referencing both a <title> and <desc> element. Decorative SVGs should use aria-hidden='true' instead.",messages:{"{0} with role='{1}' has no accessible name.":"{0} with role='{1}' has no accessible name."}},"text-alternatives/input-image-alt":{description:'Image inputs (<input type="image">) must have alternate text describing the button action.',guidance:"Image buttons (<input type='image'>) act as submit buttons with a custom image. Add alt text via alt, aria-label, or aria-labelledby that describes the action (e.g. alt='Search' or alt='Submit order'), not the image itself. Without it, screen readers announce only 'image' or the filename, giving no clue what the button does.",messages:{"Image input missing alt text.":"Image input missing alt text."}},"text-alternatives/image-redundant-alt":{description:"Image alt text should not duplicate adjacent link or button text. When alt text repeats surrounding text, screen reader users hear the same information twice.",guidance:"When an image is inside a link or button that also has text, make the alt text complementary rather than identical. If the image is purely decorative in that context, use alt='' to avoid repetition.",messages:{'Alt text "{0}" duplicates surrounding {1} text.':'Alt text "{0}" duplicates surrounding {1} text.'}},"text-alternatives/image-alt-words":{description:"Image alt text should not start with words like 'image of', 'photo of', or 'picture of' — screen readers already announce the element type.",guidance:"Screen readers already announce 'image' or 'graphic' before reading alt text, so phrases like 'image of', 'photo of', or 'picture of' are redundant. Remove these words and describe what the image shows. For example, change 'image of a dog' to 'golden retriever playing fetch'.",messages:{'Alt text "{0}" contains redundant word(s): {1}.':'Alt text "{0}" contains redundant word(s): {1}.'}},"text-alternatives/area-alt":{description:"Image map <area> elements must have alternative text.",guidance:"Each clickable region in an image map needs alternative text so screen reader users know what the region represents. Add an alt attribute to every <area> element describing its purpose. For complex image maps, consider using alternative approaches like SVG with embedded links, or a list of text links.",messages:{"Image map <area> element is missing alternative text.":"Image map <area> element is missing alternative text."}},"text-alternatives/object-alt":{description:"<object> elements must have alternative text.",guidance:"Object elements embed external content that may not be accessible to all users. Provide alternative text via aria-label, aria-labelledby, or a title attribute. The fallback content inside <object> is only shown when the object fails to load and does not serve as an accessible name.",messages:{"<object> element is missing alternative text. Add aria-label, aria-labelledby, or a title attribute.":"<object> element is missing alternative text. Add aria-label, aria-labelledby, or a title attribute."}},"text-alternatives/role-img-alt":{description:"Elements with role='img' must have an accessible name.",guidance:"When you assign role='img' to an element (like a div containing icon fonts or CSS backgrounds), you must provide an accessible name via aria-label or aria-labelledby. Without this, screen reader users have no way to understand what the image represents. If the image is decorative, use role='presentation' or role='none' instead.",messages:{"Element with role='img' has no accessible name. Add aria-label or aria-labelledby.":"Element with role='img' has no accessible name. Add aria-label or aria-labelledby."}},"keyboard-accessible/server-image-map":{description:"Server-side image maps must not be used.",guidance:"Server-side image maps (using ismap attribute) send click coordinates to the server, which is inaccessible to keyboard users and screen readers who can't precisely click specific regions. Replace with client-side image maps (<map> with <area> elements) that provide keyboard access and accessible names, or use linked images/buttons instead.",messages:{"Server-side image map detected. Use client-side image map with <map> and <area> elements instead.":"Server-side image map detected. Use client-side image map with <map> and <area> elements instead."}},"labels-and-names/form-label":{description:"Form elements must have labels. Use <label>, aria-label, or aria-labelledby.",guidance:"Every form input needs an accessible label so users understand what information to enter. Use a <label> element with a for attribute matching the input's id, wrap the input in a <label>, or use aria-label/aria-labelledby for custom components. Placeholders are not sufficient as labels since they disappear when typing. Labels should describe the information requested, not the field type (e.g., 'Email address', 'Search', 'Phone number').",messages:{"Form element has no accessible label.":"Form element has no accessible label."}},"labels-and-names/multiple-labels":{description:"Form fields should not have multiple label elements.",guidance:"When a form field has multiple <label> elements pointing to it, assistive technologies may announce only one label or behave inconsistently. Use a single <label> and combine any additional text into it, or use aria-describedby for supplementary information.",messages:{"Form field has {0} labels. Use a single label element.":"Form field has {0} labels. Use a single label element."}},"labels-and-names/input-button-name":{description:"Input buttons must have discernible text via value, aria-label, or aria-labelledby.",guidance:"Input buttons (<input type='submit'>, type='button', type='reset'>) need accessible names so users know what action the button performs. Add a value attribute with descriptive text (e.g., value='Submit Form'), or use aria-label if the value must differ from the accessible name.",messages:{"Input button has no discernible text.":"Input button has no discernible text."}},"adaptable/autocomplete-valid":{description:"Autocomplete attribute must use valid values from the HTML specification.",guidance:"The autocomplete attribute helps users fill forms by identifying input purposes. Use standard values like 'name', 'email', 'tel', 'street-address', 'postal-code', 'cc-number'. This benefits users with cognitive disabilities, motor impairments, and anyone using password managers or autofill. Check the HTML specification for the complete list of valid tokens.",messages:{'Invalid autocomplete value "{0}".':'Invalid autocomplete value "{0}".'}},"labels-and-names/label-content-mismatch":{description:"Interactive elements with visible text must have accessible names that contain that text.",guidance:"For voice control users who activate controls by speaking their visible label, the accessible name must include the visible text. If aria-label is 'Submit form' but the button shows 'Send', voice users saying 'click Send' won't activate it. Ensure aria-label/aria-labelledby contains or matches the visible text.",messages:{'Accessible name "{0}" does not contain visible text "{1}".':'Accessible name "{0}" does not contain visible text "{1}".'}},"labels-and-names/label-title-only":{description:"Form elements should not use title attribute as the only accessible name.",guidance:"The title attribute is unreliable as a label because it only appears on hover/focus (not visible to touch users) and is often ignored by assistive technologies. Use a visible <label> element, aria-label, or aria-labelledby instead. Title can supplement a label but should not replace it.",messages:{"Form element uses title attribute as only label. Use <label>, aria-label, or aria-labelledby instead.":"Form element uses title attribute as only label. Use <label>, aria-label, or aria-labelledby instead."}},"keyboard-accessible/tabindex":{description:"Elements should not have tabindex greater than 0, which disrupts natural tab order.",guidance:"Positive tabindex values force elements to the front of the tab order regardless of DOM position, creating unpredictable navigation for keyboard users. Use tabindex='0' to add elements to the natural tab order, or tabindex='-1' to make elements programmatically focusable but not in tab order. Rely on DOM order for tab sequence.",messages:{'Element has tabindex="{0}" which disrupts tab order.':'Element has tabindex="{0}" which disrupts tab order.'}},"keyboard-accessible/focus-order":{description:"Non-interactive elements with tabindex='0' must have an interactive ARIA role so assistive technologies can convey their purpose.",guidance:"When adding tabindex='0' to non-interactive elements like <div> or <span>, screen readers announce them generically. Add an appropriate role (button, link, tab, etc.) so users understand the element's purpose. Also add keyboard event handlers (Enter/Space for buttons, Enter for links). Consider using native interactive elements instead.",messages:{'Non-interactive <{0}> with tabindex="0" has no interactive role.':'Non-interactive <{0}> with tabindex="0" has no interactive role.'}},"keyboard-accessible/nested-interactive":{description:"Interactive controls must not be nested inside each other.",guidance:"Nesting interactive elements (like a button inside a link, or a link inside a button) creates unpredictable behavior and confuses assistive technologies. The browser may remove the inner element from the accessibility tree. Restructure the HTML so interactive elements are siblings, not nested. If you need a clickable card, use CSS and JavaScript rather than nesting.",messages:{"Interactive element <{0}> is nested inside <{1}>.":"Interactive element <{0}> is nested inside <{1}>."}},"keyboard-accessible/scrollable-region":{description:"Scrollable regions must be keyboard accessible.",guidance:"Content that scrolls must be accessible to keyboard users. If a region has overflow:scroll or overflow:auto and contains scrollable content, it needs either tabindex='0' to be focusable, or it must contain focusable elements. Without this, keyboard users cannot scroll the content.",messages:{"Scrollable region is not keyboard accessible. Add tabindex='0' or include focusable elements.":"Scrollable region is not keyboard accessible. Add tabindex='0' or include focusable elements."}},"keyboard-accessible/accesskeys":{description:"Accesskey attribute values must be unique.",guidance:"When multiple elements share the same accesskey, browser behavior becomes unpredictable - usually only the first element is activated. Ensure each accesskey value is unique within the page. Also consider that accesskeys can conflict with browser and screen reader shortcuts, so use them sparingly.",messages:{'Duplicate accesskey "{0}". Each accesskey must be unique.':'Duplicate accesskey "{0}". Each accesskey must be unique.'}},"navigable/heading-order":{description:"Heading levels should increase by one; skipping levels (e.g. h2 to h4) makes navigation harder.",guidance:"Screen reader users navigate by headings to understand page structure. Skipping levels (h2 to h4) suggests missing content and creates confusion. Start with h1 for the page title, then use h2 for main sections, h3 for subsections, etc. You can go back up (h3 to h2) when starting a new section.",messages:{"Heading level {0} skipped from level {1}. Use h{2} instead.":"Heading level {0} skipped from level {1}. Use h{2} instead."}},"navigable/empty-heading":{description:"Headings must have discernible text.",guidance:"Screen reader users navigate pages by headings, so empty headings create confusing navigation points. Ensure all headings contain visible text or accessible names. If a heading is used purely for visual styling, use CSS instead of heading elements.",messages:{"Heading is empty. Add text content or remove the heading element.":"Heading is empty. Add text content or remove the heading element."}},"navigable/p-as-heading":{description:"Paragraphs should not be styled to look like headings.",guidance:"When paragraphs are styled with bold, large fonts to look like headings, screen reader users miss the semantic structure. Use proper heading elements (h1-h6) instead of styled paragraphs. If you need specific styling, apply CSS to the heading elements while maintaining proper heading hierarchy.",messages:{"Paragraph appears to be styled as a heading. Use an h1-h6 element instead.":"Paragraph appears to be styled as a heading. Use an h1-h6 element instead."}},"landmarks/landmark-main":{description:"Page should have exactly one main landmark.",guidance:"The main landmark contains the primary content of the page. Screen readers allow users to jump directly to main content. Use a single <main> element (or role='main') to wrap the central content, excluding headers, footers, and navigation.",messages:{"Page has no main landmark.":"Page has no main landmark.","Page has multiple main landmarks.":"Page has multiple main landmarks."}},"landmarks/no-duplicate-banner":{description:"Page should not have more than one banner landmark.",guidance:"The banner landmark (typically <header>) identifies site-oriented content like logos and search. Only one top-level banner is allowed per page. If you need multiple headers, nest them inside sectioning elements (article, section, aside) where they become scoped headers rather than page-level banners.",messages:{"Page has multiple banner landmarks.":"Page has multiple banner landmarks."}},"landmarks/no-duplicate-contentinfo":{description:"Page should not have more than one contentinfo landmark.",guidance:"The contentinfo landmark (typically <footer>) contains information about the page like copyright and contact info. Only one top-level contentinfo is allowed per page. Nest additional footers inside sectioning elements to scope them.",messages:{"Page has multiple contentinfo landmarks.":"Page has multiple contentinfo landmarks."}},"landmarks/no-duplicate-main":{description:"Page should not have more than one main landmark.",guidance:"Only one main landmark should exist per page. The main landmark identifies the primary content area. If you have multiple content sections, use <section> with appropriate headings instead of multiple main elements.",messages:{"Page has multiple main landmarks.":"Page has multiple main landmarks."}},"landmarks/banner-is-top-level":{description:"Banner landmark should not be nested within another landmark.",guidance:"The banner landmark should be a top-level landmark, not nested inside article, aside, main, nav, or section. If a header is inside these elements, it automatically becomes a generic header rather than a banner. Remove explicit role='banner' from nested headers or restructure the page.",messages:{"Banner landmark is nested within another landmark.":"Banner landmark is nested within another landmark."}},"landmarks/contentinfo-is-top-level":{description:"Contentinfo landmark should not be nested within another landmark.",guidance:"The contentinfo landmark should be a top-level landmark. A footer inside article, aside, main, nav, or section becomes a scoped footer, not a contentinfo landmark. Remove explicit role='contentinfo' from nested footers or move the footer outside sectioning elements.",messages:{"Contentinfo landmark is nested within another landmark.":"Contentinfo landmark is nested within another landmark."}},"landmarks/main-is-top-level":{description:"Main landmark should not be nested within another landmark.",guidance:"Screen readers provide a shortcut to jump directly to the main landmark. When <main> is nested inside another landmark (article, aside, nav, or section), some screen readers may not list it as a top-level landmark, making it harder to find. Move <main> outside any sectioning elements so it sits at the top level of the document.",messages:{"Main landmark is nested within another landmark.":"Main landmark is nested within another landmark."}},"landmarks/complementary-is-top-level":{description:"Aside (complementary) landmark should be top-level or directly inside main.",guidance:"The complementary landmark (aside) should be top-level or a direct child of main. Nesting aside deep within other landmarks reduces its discoverability for screen reader users navigating by landmarks.",messages:{"Complementary landmark should be top-level.":"Complementary landmark should be top-level."}},"landmarks/landmark-unique":{description:"Landmarks should have unique labels when there are multiple of the same type.",guidance:"When a page has multiple landmarks of the same type (e.g., multiple nav elements), each should have a unique accessible name via aria-label or aria-labelledby. This helps screen reader users distinguish between them (e.g., 'Main navigation' vs 'Footer navigation').",messages:{'Multiple {0} landmarks have the same label "{1}".':'Multiple {0} landmarks have the same label "{1}".',"Multiple {0} landmarks have no label. Add unique aria-label attributes.":"Multiple {0} landmarks have no label. Add unique aria-label attributes."}},"landmarks/region":{description:"All page content should be contained within landmarks.",guidance:"Screen reader users navigate pages by landmarks. Content outside landmarks is harder to find and understand. Wrap all visible content in appropriate landmarks: <header>, <nav>, <main>, <aside>, <footer>, or <section> with a label. Skip links may exist outside landmarks.",messages:{"Content is not contained within a landmark region.":"Content is not contained within a landmark region."}},"adaptable/list-children":{description:"<ul> and <ol> must only contain <li>, <script>, <template>, or <style> as direct children.",guidance:"Screen readers announce list structure ('list with 5 items') based on proper markup. Placing non-<li> elements directly inside <ul> or <ol> breaks this structure. Wrap content in <li> elements, or if you need wrapper divs for styling, apply styles to <li> elements directly and remove the wrapper (e.g., change <ul><div>item</div></ul> to <ul><li>item</li></ul>).",messages:{"List contains non-<li> child <{0}>.":"List contains non-<li> child <{0}>."}},"adaptable/listitem-parent":{description:"<li> elements must be contained in a <ul>, <ol>, or <menu>.",guidance:"List items (<li>) only have semantic meaning inside a list container (<ul>, <ol>, or <menu>). Without a list parent, screen readers will not announce 'list with N items' or allow users to skip between items using list navigation shortcuts. Wrap <li> elements in the appropriate list container — <ul> for unordered lists, <ol> for ordered/numbered lists.",messages:{"<li> is not contained in a <ul>, <ol>, or <menu>.":"<li> is not contained in a <ul>, <ol>, or <menu>."}},"adaptable/dl-children":{description:"<dt> and <dd> elements must be contained in a <dl>.",guidance:"Definition terms (<dt>) and definitions (<dd>) only have semantic meaning inside a definition list (<dl>). Outside of <dl>, they're treated as generic text. Wrap related <dt> and <dd> pairs in a <dl> element to convey the term/definition relationship to assistive technologies.",messages:{"<{0}> is not contained in a <dl>.":"<{0}> is not contained in a <dl>."}},"adaptable/definition-list":{description:"<dl> elements must only contain <dt>, <dd>, <div>, <script>, <template>, or <style>.",guidance:"Definition lists have strict content requirements. Only <dt> (terms), <dd> (definitions), and <div> (for grouping dt/dd pairs) are valid children. Other elements break the list structure for screen readers. Move invalid elements outside the <dl>, or if they represent a term change to <dt>, if a definition change to <dd>. Styling wrappers should be replaced with <div> elements containing <dt>/<dd> pairs.",messages:{"<dl> contains invalid child <{0}>.":"<dl> contains invalid child <{0}>."}},"aria/aria-roles":{description:"ARIA role values must be valid.",guidance:"Invalid role values are ignored by assistive technologies, meaning the element will not have the intended semantics. Check the spelling and use only roles defined in the WAI-ARIA specification. Common roles include: button, link, navigation, main, dialog, alert, tab, tabpanel, menu, menuitem.",messages:{'Invalid ARIA role "{0}".':'Invalid ARIA role "{0}".'}},"aria/aria-valid-attr":{description:"ARIA attributes must be valid (correctly spelled).",guidance:"Misspelled ARIA attributes are ignored by assistive technologies. Check the spelling against the WAI-ARIA specification. Common mistakes: aria-labeledby (should be aria-labelledby), aria-role (should be role), aria-description (valid in ARIA 1.3+).",messages:{'Invalid ARIA attribute "{0}".':'Invalid ARIA attribute "{0}".'}},"aria/aria-valid-attr-value":{description:"ARIA attributes must have valid values.",guidance:"Each ARIA attribute accepts specific value types. Boolean attributes (aria-hidden, aria-disabled) accept only 'true' or 'false'. Tristate attributes (aria-checked, aria-pressed) also accept 'mixed'. Token attributes (aria-live, aria-autocomplete) accept predefined values. ID reference attributes (aria-labelledby, aria-describedby) must reference existing element IDs.",messages:{'{0} must be "true" or "false", got "{1}".':'{0} must be "true" or "false", got "{1}".','{0} must be "true", "false", or "mixed", got "{1}".':'{0} must be "true", "false", or "mixed", got "{1}".','{0} must be an integer, got "{1}".':'{0} must be an integer, got "{1}".','{0} must be a number, got "{1}".':'{0} must be a number, got "{1}".','Invalid value "{0}" for {1}.':'Invalid value "{0}" for {1}.'}},"aria/aria-required-attr":{description:"Elements with ARIA roles must have all required ARIA attributes.",guidance:"Some ARIA roles require specific attributes to function correctly. For example, checkbox requires aria-checked, slider requires aria-valuenow, heading requires aria-level. Without these attributes, assistive technologies cannot convey the element's state or value to users. Add the missing required attribute with an appropriate value.",messages:{'Role "{0}" requires attribute "{1}".':'Role "{0}" requires attribute "{1}".'}},"aria/aria-allowed-attr":{description:"ARIA attributes must be allowed for the element's role.",guidance:"Each ARIA role supports specific attributes. Using unsupported attributes creates confusion for assistive technologies. Check the ARIA specification for which attributes are valid for each role, or remove the attribute if it's not needed.",messages:{'ARIA attribute "{0}" is not allowed on role "{1}".':'ARIA attribute "{0}" is not allowed on role "{1}".'}},"aria/aria-allowed-role":{description:"ARIA role must be appropriate for the element.",guidance:"Not all ARIA roles can be applied to all HTML elements. Many elements have implicit roles (e.g., <header> is implicitly banner, <nav> is navigation, <main> is main). Adding an explicit role that matches the implicit role is redundant. Adding a conflicting role breaks semantics. Either remove the role attribute or use a different element.",messages:{"Element <{0}> should not have an explicit role.":"Element <{0}> should not have an explicit role.",'Role "{0}" is not allowed on element <{1}>.':'Role "{0}" is not allowed on element <{1}>.'}},"adaptable/aria-required-children":{description:"Certain ARIA roles require specific child roles to be present.",guidance:"Some ARIA roles represent containers that must contain specific child roles for proper semantics. For example, a list must contain listitems, a menu must contain menuitems. Add the required child elements with appropriate roles, or use native HTML elements that provide these semantics implicitly (e.g., <ul> with <li>).",messages:{'Role "{0}" requires children with role: {1}.':'Role "{0}" requires children with role: {1}.'}},"adaptable/aria-required-parent":{description:"Certain ARIA roles must be contained within specific parent roles.",guidance:"Some ARIA roles represent items that must exist within specific container roles. For example, a listitem must be within a list, a tab must be within a tablist. Wrap the element in the appropriate parent, or use native HTML elements that provide this structure (e.g., <li> inside <ul>).",messages:{'Role "{0}" must be contained within: {1}.':'Role "{0}" must be contained within: {1}.'}},"aria/aria-hidden-body":{description:"aria-hidden='true' must not be present on the document body.",guidance:"Setting aria-hidden='true' on the body element hides all page content from assistive technologies, making the page completely inaccessible to screen reader users. Remove aria-hidden from the body element. If you need to hide content temporarily (e.g., behind a modal), use aria-hidden on specific sections instead.",messages:{"aria-hidden='true' on body hides all content from assistive technologies.":"aria-hidden='true' on body hides all content from assistive technologies."}},"aria/aria-hidden-focus":{description:"Elements with aria-hidden='true' must not contain focusable elements.",guidance:"When aria-hidden='true' hides an element from assistive technologies but the element contains focusable children, keyboard users can focus those children but screen reader users won't know they exist. Either remove focusable elements from the hidden region, add tabindex='-1' to them, or remove aria-hidden.",messages:{"Focusable element is inside an aria-hidden region.":"Focusable element is inside an aria-hidden region."}},"labels-and-names/aria-command-name":{description:"ARIA commands must have an accessible name.",guidance:"Interactive ARIA command roles (button, link, menuitem) must have accessible names so users know what action they perform. Add visible text content, aria-label, or aria-labelledby to provide a name.",messages:{"ARIA command has no accessible name.":"ARIA command has no accessible name."}},"labels-and-names/aria-input-field-name":{description:"ARIA input fields must have an accessible name.",guidance:"ARIA input widgets (combobox, listbox, searchbox, slider, spinbutton, textbox) must have accessible names so users understand what data to enter. Add a visible label with aria-labelledby, or use aria-label if a visible label is not possible.",messages:{"ARIA input field has no accessible name.":"ARIA input field has no accessible name."}},"labels-and-names/aria-toggle-field-name":{description:"ARIA toggle fields must have an accessible name.",guidance:"ARIA toggle controls (checkbox, switch, radio, menuitemcheckbox, menuitemradio) must have accessible names so users understand what option they're selecting. Add visible text content, aria-label, or use aria-labelledby to reference a visible label.",messages:{"ARIA toggle field has no accessible name.":"ARIA toggle field has no accessible name."}},"labels-and-names/aria-meter-name":{description:"ARIA meter elements must have an accessible name.",guidance:"Meter elements display a value within a known range (like disk usage or password strength). They must have accessible names so screen reader users understand what is being measured. Use aria-label or aria-labelledby to provide context.",messages:{"Meter has no accessible name.":"Meter has no accessible name."}},"labels-and-names/aria-progressbar-name":{description:"ARIA progressbar elements must have an accessible name.",guidance:"Progress indicators must have accessible names so screen reader users understand what process is being tracked. Use aria-label (e.g., 'File upload progress') or aria-labelledby to reference a visible heading or label.",messages:{"Progressbar has no accessible name.":"Progressbar has no accessible name."}},"labels-and-names/aria-dialog-name":{description:"ARIA dialogs must have an accessible name.",guidance:"Dialog and alertdialog elements must have accessible names so screen reader users understand the dialog's purpose when it opens. Use aria-label or aria-labelledby pointing to the dialog's heading. Native <dialog> elements should also have an accessible name.",messages:{"Dialog has no accessible name.":"Dialog has no accessible name."}},"labels-and-names/aria-tooltip-name":{description:"ARIA tooltips must have an accessible name.",guidance:"Tooltip elements must have accessible names (usually their text content). The tooltip content itself typically serves as the accessible name. Ensure the tooltip contains descriptive text content or has aria-label.",messages:{"Tooltip has no accessible name.":"Tooltip has no accessible name."}},"labels-and-names/aria-treeitem-name":{description:"ARIA treeitem elements must have an accessible name.",guidance:"Tree items must have accessible names so screen reader users can understand the tree structure and navigate it effectively. Provide text content, aria-label, or aria-labelledby for each treeitem.",messages:{"Treeitem has no accessible name.":"Treeitem has no accessible name."}},"aria/aria-prohibited-attr":{description:"ARIA attributes must not be prohibited for the element's role.",guidance:"Some ARIA roles prohibit certain attributes. For example, roles like 'none', 'presentation', 'generic', and text-level roles (code, emphasis, strong) prohibit aria-label and aria-labelledby because naming is not supported for these roles. Remove the prohibited attributes or change the role.",messages:{"aria-label and aria-labelledby are prohibited on <{0}> elements.":"aria-label and aria-labelledby are prohibited on <{0}> elements.",'aria-label and aria-labelledby are prohibited on role "{0}".':'aria-label and aria-labelledby are prohibited on role "{0}".','Attribute "{0}" is prohibited on role "{1}".':'Attribute "{0}" is prohibited on role "{1}".'}},"aria/presentation-role-conflict":{description:"Elements with role='presentation' or role='none' must not be focusable or have global ARIA attributes.",guidance:"When an element has role='presentation' or role='none', it's marked as decorative and removed from the accessibility tree. However, if the element is focusable or has certain ARIA attributes, the presentation role is ignored and the element remains accessible. This creates confusion. Either remove the presentation role, or remove the focusability/ARIA attributes.",messages:{"Presentation role conflicts with: {0}. The role will be ignored.":"Presentation role conflicts with: {0}. The role will be ignored.",'Element with implicit presentation role (alt="") conflicts with: {0}. The decorative role will be ignored.':'Element with implicit presentation role (alt="") conflicts with: {0}. The decorative role will be ignored.'}},"labels-and-names/button-name":{description:"Buttons must have discernible text.",guidance:"Screen reader users need to know what a button does. Add visible text content, aria-label, or aria-labelledby. For icon buttons, use aria-label describing the action (e.g., aria-label='Close'). If the button contains an image, ensure the image has alt text describing the button's action.",messages:{"Button has no discernible text.":"Button has no discernible text."}},"labels-and-names/summary-name":{description:"<summary> elements must have an accessible name.",guidance:"The <summary> element provides the visible label for a <details> disclosure widget. It must have descriptive text content so screen reader users understand what will be revealed when expanded. Add clear, concise text that indicates what content is contained in the details section.",messages:{"<summary> element has no accessible name. Add descriptive text.":"<summary> element has no accessible name. Add descriptive text."}},"navigable/link-name":{description:"Links must have discernible text via content, aria-label, or aria-labelledby.",guidance:"Screen reader users need to know where a link goes. Add descriptive text content, aria-label, or use aria-labelledby. For image links, ensure the image has alt text describing the link destination. Avoid generic text like 'click here' or 'read more'—link text should make sense out of context.",messages:{"Link has no discernible text.":"Link has no discernible text."}},"navigable/skip-link":{description:"Skip links must point to a valid target on the page.",guidance:"Skip links allow keyboard users to bypass repetitive navigation and jump directly to main content. The skip link should be the first focusable element on the page, link to the main content (e.g., href='#main'), and become visible when focused. It can be visually hidden until focused using CSS.",messages:{'Skip link points to "#{0}" which does not exist on the page.':'Skip link points to "#{0}" which does not exist on the page.'}},"distinguishable/link-in-text-block":{description:"Links within text blocks must be distinguishable by more than color alone.",guidance:"Users who cannot perceive color differences need other visual cues to identify links. Links in text should have underlines or other non-color indicators. If using color alone, ensure 3:1 contrast with surrounding text AND provide additional indication on focus/hover.",messages:{"Link in text block is not visually distinguishable from surrounding text. Add a non-color visual indicator such as an underline or border.":"Link in text block is not visually distinguishable from surrounding text. Add a non-color visual indicator such as an underline or border."}},"readable/html-has-lang":{description:"The <html> element must have a lang attribute.",guidance:"Screen readers use the lang attribute to determine which language rules and pronunciation to use. Without it, content may be mispronounced. Set lang to the primary language of the page using a BCP 47 code (e.g., 'en' for English, 'es' for Spanish, 'fr' for French, 'de' for German, 'ja' for Japanese, 'zh' for Chinese, 'pt' for Portuguese, 'ar' for Arabic).",messages:{"<html> element missing lang attribute.":"<html> element missing lang attribute."}},"readable/html-lang-valid":{description:"The lang attribute on <html> must have a valid value.",guidance:"The lang attribute must use a valid BCP 47 language tag. Use a 2 or 3 letter language code (e.g., 'en', 'fr', 'zh'), optionally followed by a region code (e.g., 'en-US', 'pt-BR'). Invalid tags prevent screen readers from correctly pronouncing content.",messages:{'Invalid lang attribute value "{0}".':'Invalid lang attribute value "{0}".'}},"readable/valid-lang":{description:"The lang attribute must have a valid value on all elements.",guidance:"When content in a different language appears within a page (e.g., a French quote in an English document), wrap it with a lang attribute to ensure correct pronunciation. The lang value must be a valid BCP 47 tag. Common codes: en, es, fr, de, zh, ja, pt, ar, ru.",messages:{"Empty lang attribute value.":"Empty lang attribute value.",'Invalid lang attribute value "{0}".':'Invalid lang attribute value "{0}".'}},"readable/html-xml-lang-mismatch":{description:"The lang and xml:lang attributes on <html> must match.",guidance:"In XHTML documents, if both lang and xml:lang are present, they must specify the same base language. Mismatched values confuse assistive technologies. Either remove xml:lang (preferred for HTML5) or ensure both attributes have identical values.",messages:{'lang="{0}" and xml:lang="{1}" do not match.':'lang="{0}" and xml:lang="{1}" do not match.'}},"adaptable/td-headers-attr":{description:"All cells in a table using headers attribute must reference valid header IDs.",guidance:"The headers attribute on table cells must reference IDs of header cells (th or td) within the same table. This creates explicit associations for screen readers. Verify all referenced IDs exist and spell them correctly. For simple tables, consider using scope on th elements instead.",messages:{'Headers attribute references the cell itself ("{0}").':'Headers attribute references the cell itself ("{0}").','Headers attribute references non-existent ID "{0}".':'Headers attribute references non-existent ID "{0}".'}},"adaptable/th-has-data-cells":{description:"Table headers should be associated with data cells.",guidance:"Screen readers use <th> elements to announce column or row headers when navigating table cells — for example, reading 'Name: John' when moving to a cell. A table with <th> but no <td> elements means headers describe nothing, and screen readers cannot associate data with headers. Either add <td> data cells, or if this is not tabular data, use non-table markup instead.",messages:{"Table has header cells but no data cells.":"Table has header cells but no data cells."}},"adaptable/td-has-header":{description:"Data cells in tables larger than 3x3 should have associated headers.",guidance:"In complex tables, screen reader users need header associations to understand data cells. Use th elements with scope attribute, or the headers attribute on td elements. For simple tables (≤3x3), this is less critical as context is usually clear.",messages:{"Data cell has no associated header. Add th elements with scope, or headers attribute.":"Data cell has no associated header. Add th elements with scope, or headers attribute."}},"adaptable/scope-attr-valid":{description:"The scope attribute on table headers must have a valid value.",guidance:"The scope attribute tells screen readers which cells a header applies to. Valid values are: row, col, rowgroup, colgroup. Using invalid values breaks the association between headers and cells.",messages:{'Invalid scope value "{0}". Use row, col, rowgroup, or colgroup.':'Invalid scope value "{0}". Use row, col, rowgroup, or colgroup.'}},"adaptable/empty-table-header":{description:"Table header cells should have visible text.",guidance:"Empty table headers provide no information to screen reader users. Either add descriptive text to the header, or if the header is intentionally empty (like a corner cell), consider using a td element instead or adding a visually hidden label.",messages:{"Table header cell is empty. Add text or use aria-label.":"Table header cell is empty. Add text or use aria-label."}},"labels-and-names/duplicate-id-aria":{description:"IDs used in ARIA and label associations must be unique to avoid broken references.",guidance:"When aria-labelledby, aria-describedby, aria-controls, or label[for] reference a duplicate ID, only the first matching element is used. This breaks the intended relationship and may leave controls unnamed or descriptions missing. Ensure IDs referenced by ARIA attributes and label associations are unique throughout the document.",messages:{'Duplicate ID "{0}" referenced by {1}.':'Duplicate ID "{0}" referenced by {1}.'}},"time-based-media/video-captions":{description:"Video elements must have captions via <track kind='captions'> or <track kind='subtitles'>.",guidance:"Captions provide text alternatives for audio content in videos, benefiting deaf users and those who cannot hear audio. Add a <track> element with kind='captions' pointing to a WebVTT caption file. Captions should include both dialogue and important sound effects.",messages:{"Video element has no captions track.":"Video element has no captions track."}},"time-based-media/audio-transcript":{description:"Audio elements should have a text alternative or transcript.",guidance:"Audio-only content like podcasts or recordings needs a text alternative for deaf users. Provide a transcript either on the same page or linked nearby. The transcript should include all spoken content and descriptions of relevant sounds.",messages:{"Audio element has no transcript or text alternative. Add a transcript or track element.":"Audio element has no transcript or text alternative. Add a transcript or track element."}},"distinguishable/color-contrast":{description:"Text elements must have sufficient color contrast against the background.",guidance:"WCAG SC 1.4.3 requires a contrast ratio of at least 4.5:1 for normal text and 3:1 for large text (>=24px or >=18.66px bold). Increase the contrast by darkening the text or lightening the background, or vice versa.",messages:{"Insufficient color contrast ratio of {0}:1 (required {1}:1).":"Insufficient color contrast ratio of {0}:1 (required {1}:1)."}},"distinguishable/color-contrast-enhanced":{description:"Text elements must have enhanced color contrast against the background (WCAG AAA).",guidance:"WCAG SC 1.4.6 (AAA) requires a contrast ratio of at least 7:1 for normal text and 4.5:1 for large text (>=24px or >=18.66px bold). Higher contrast benefits users with low vision, aging eyes, or poor screen conditions. Increase the contrast by darkening the text or lightening the background, or vice versa.",messages:{"Insufficient enhanced contrast ratio of {0}:1 (required {1}:1).":"Insufficient enhanced contrast ratio of {0}:1 (required {1}:1)."}},"enough-time/meta-refresh-no-exception":{description:"Meta refresh must not be used with a delay (no exceptions).",guidance:"Automatic page refreshes and delayed redirects disorient users. Instant redirects (delay=0) are acceptable, but any positive delay is not. Use server-side redirects instead.",messages:{"Page has a {0}-second meta refresh delay. Use a server-side redirect instead.":"Page has a {0}-second meta refresh delay. Use a server-side redirect instead.","Page has a {0}-second meta refresh delay. Remove the auto-refresh or provide user control.":"Page has a {0}-second meta refresh delay. Remove the auto-refresh or provide user control."}},"distinguishable/letter-spacing":{description:"Letter spacing set with !important in style attributes must be at least 0.12em.",guidance:"WCAG 1.4.12 requires users to be able to override text spacing. Using !important on letter-spacing with a value below 0.12em prevents this. Either increase the value to at least 0.12em or remove !important.",messages:{"Letter spacing {0}em with !important is below the 0.12em minimum.":"Letter spacing {0}em with !important is below the 0.12em minimum."}},"distinguishable/line-height":{description:"Line height set with !important in style attributes must be at least 1.5.",guidance:"WCAG 1.4.12 requires users to be able to override text spacing. Using !important on line-height with a value below 1.5 prevents this. Either increase the value to at least 1.5 or remove !important.",messages:{"Line height {0} with !important is below the 1.5 minimum.":"Line height {0} with !important is below the 1.5 minimum."}},"distinguishable/word-spacing":{description:"Word spacing set with !important in style attributes must be at least 0.16em.",guidance:"WCAG 1.4.12 requires users to be able to override text spacing. Using !important on word-spacing with a value below 0.16em prevents this. Either increase the value to at least 0.16em or remove !important.",messages:{"Word spacing {0}em with !important is below the 0.16em minimum.":"Word spacing {0}em with !important is below the 0.16em minimum."}},"adaptable/orientation-lock":{description:"Page orientation must not be restricted using CSS transforms.",guidance:"Users with motor disabilities may mount their device in a fixed orientation. Using CSS transforms with @media (orientation: portrait/landscape) to rotate content 90° effectively locks the page to one orientation. Remove the orientation-dependent transform and use responsive design instead.",messages:{"CSS locks page orientation via @media (orientation: {0}) with a 90° transform.":"CSS locks page orientation via @media (orientation: {0}) with a 90° transform."}},"aria/presentational-children-focusable":{description:"Elements with a role that makes children presentational must not contain focusable content.",guidance:"Roles like button, checkbox, img, tab, and others make their children presentational — hidden from assistive technologies. If those children are focusable, keyboard users can reach elements that screen reader users cannot perceive. Move focusable content outside the parent or remove the focusability.",messages:{'Focusable element inside a "{0}" role whose children are presentational.':'Focusable element inside a "{0}" role whose children are presentational.'}},"keyboard-accessible/focus-visible":{description:"Elements in sequential focus order must have a visible focus indicator.",guidance:"Keyboard users need to see which element has focus. Do not remove the default focus outline (outline: none) without providing an alternative visible indicator. Use :focus-visible or :focus styles to ensure focus is always perceivable.",messages:{"Focusable element has outline removed without a visible focus alternative.":"Focusable element has outline removed without a visible focus alternative."}},"input-assistance/accessible-authentication":{description:'Password inputs must not block password managers. Avoid autocomplete="off" and allow pasting.',guidance:'WCAG 2.2 SC 3.3.8 requires that authentication steps either avoid cognitive function tests or provide a mechanism to assist users. Password managers are a key assistive mechanism. Setting autocomplete="off" on password fields prevents password managers from filling credentials. Blocking paste via onpaste attributes prevents users from pasting stored passwords. Set autocomplete to "current-password" for login forms or "new-password" for registration/change-password forms, and do not block paste on password fields.',messages:{'Password field has autocomplete="off" which blocks password managers.':'Password field has autocomplete="off" which blocks password managers.',"Password field blocks pasting, preventing password manager use.":"Password field blocks pasting, preventing password manager use."}}},Kn={"navigable/document-title":{description:"Los documentos deben tener un elemento <title> para proporcionar a los usuarios una vista general del contenido.",guidance:"Los usuarios de lectores de pantalla dependen de los títulos de página para identificar y navegar entre pestañas/ventanas. Agregue un elemento <title> descriptivo en <head> que resuma el propósito de la página. Mantenga los títulos únicos en todo el sitio, colocando el contenido específico antes del nombre del sitio (por ejemplo, 'Contáctenos - Acme Corp').",messages:{"Document <title> element is empty.":"El elemento <title> del documento está vacío.","Document is missing a <title> element.":"Al documento le falta un elemento <title>."}},"navigable/bypass":{description:"La página debe tener un mecanismo para omitir bloques de contenido repetidos.",guidance:'Los usuarios de teclado deben poder omitir contenido repetitivo como la navegación. Proporcione un enlace de salto en la parte superior de la página que enlace al contenido principal (por ejemplo, <a href="#main">Saltar al contenido principal</a>), o use un landmark <main>. Los lectores de pantalla pueden saltar directamente a los landmarks, por lo que un elemento <main> correctamente marcado satisface este requisito.',messages:{"Page has no mechanism to bypass repeated content. Add a <main> landmark or skip link.":"La página no tiene un mecanismo para omitir contenido repetido. Agregue un landmark <main> o un enlace de salto."}},"navigable/page-has-heading-one":{description:"La página debe contener un encabezado de nivel uno.",guidance:"Un encabezado de nivel uno (<h1> o role='heading' con aria-level='1') ayuda a los usuarios a comprender el tema de la página y proporciona un punto de referencia para la navegación con lector de pantalla. Cada página debe tener exactamente un h1 que describa el contenido principal, típicamente coincidiendo o similar al título de la página.",messages:{"Page does not contain a level-one heading.":"La página no contiene un encabezado de nivel uno."}},"labels-and-names/frame-title":{description:"Los marcos deben tener un nombre accesible.",guidance:"Los lectores de pantalla anuncian los títulos de los marcos cuando los usuarios navegan por ellos. Agregue un atributo title a los elementos <iframe> y <frame> que describa el propósito del marco (por ejemplo, <iframe title='Reproductor de video'>). Evite títulos genéricos como 'marco' o 'iframe'. Si el marco es decorativo, use aria-hidden='true'.",messages:{"Frame is missing an accessible name. Add a title attribute.":"Al marco le falta un nombre accesible. Agregue un atributo title."}},"labels-and-names/frame-title-unique":{description:"Los títulos de los marcos deben ser únicos.",guidance:"Cuando varios marcos tienen títulos idénticos, los usuarios de lectores de pantalla no pueden distinguirlos. Dé a cada marco un título único y descriptivo que explique su propósito específico o contenido.",messages:{"Frame title is not unique. Use a distinct title for each frame.":"El título del marco no es único. Use un título distinto para cada marco."}},"distinguishable/meta-viewport":{description:"La etiqueta meta viewport no debe deshabilitar el zoom del usuario.",guidance:"Los usuarios con baja visión necesitan ampliar el contenido al 200% o más. Establecer user-scalable=no o maximum-scale=1 impide el zoom y no cumple con WCAG. Elimine estas restricciones. Si su diseño se rompe con zoom alto, corrija el diseño responsivo en lugar de impedir el zoom.",messages:{"Viewport disables user scaling (user-scalable={0}). Remove this restriction.":"El viewport deshabilita el escalado del usuario (user-scalable={0}). Elimine esta restricción.","Viewport maximum-scale={0} restricts zooming. Set to at least 2 or remove.":"El viewport maximum-scale={0} restringe el zoom. Establezca al menos 2 o elimínelo."}},"enough-time/meta-refresh":{description:"La etiqueta meta refresh no debe redirigir o actualizar automáticamente.",guidance:"Las actualizaciones o redirecciones automáticas de página pueden desorientar a los usuarios, especialmente aquellos que usan lectores de pantalla o con discapacidades cognitivas. Pueden perder su lugar o no tener tiempo para leer el contenido. Si se necesita una redirección, use una redirección del lado del servidor (HTTP 301/302). Para actualizaciones temporizadas, proporcione controles al usuario.",messages:{"Page redirects after {0} seconds without warning. Use server-side redirect.":"La página redirige después de {0} segundos sin advertencia. Use redirección del lado del servidor.","Page auto-refreshes after {0} seconds. Provide user control over refresh.":"La página se actualiza automáticamente después de {0} segundos. Proporcione control al usuario sobre la actualización."}},"enough-time/blink":{description:"El elemento <blink> no debe usarse.",guidance:"El contenido parpadeante puede causar convulsiones en usuarios con epilepsia fotosensible y es una distracción para usuarios con trastornos de atención. El elemento <blink> está obsoleto y nunca debe usarse. Si necesita llamar la atención sobre el contenido, use métodos menos intrusivos como color, bordes o iconos.",messages:{"The <blink> element causes accessibility issues. Remove it entirely.":"El elemento <blink> causa problemas de accesibilidad. Elimínelo por completo."}},"enough-time/marquee":{description:"El elemento <marquee> no debe usarse.",guidance:"El contenido que se desplaza o se mueve es difícil de leer para muchos usuarios, especialmente aquellos con discapacidades cognitivas o visuales. El elemento <marquee> está obsoleto. Reemplace el texto en movimiento con contenido estático. Si el contenido debe desplazarse, proporcione controles de pausa/detención y asegúrese de que se detenga después de 5 segundos.",messages:{"The <marquee> element causes accessibility issues. Replace with static content.":"El elemento <marquee> causa problemas de accesibilidad. Reemplace con contenido estático."}},"text-alternatives/img-alt":{description:`Las imágenes deben tener texto alternativo. Agregue un atributo alt a los elementos <img>. Las imágenes decorativas pueden usar un atributo alt vacío (alt=""), role='none' o role='presentation'.`,guidance:"Cada imagen necesita un atributo alt. Para imágenes informativas, describa el contenido o la función de manera concisa. Para imágenes decorativas (fondos, espaciadores, adornos puramente visuales), use alt='' para ocultarlas de los lectores de pantalla. Nunca omita alt por completo: los lectores de pantalla pueden leer el nombre del archivo en su lugar.",messages:{'Image has whitespace-only alt text. Use alt="" for decorative images or provide descriptive text.':'La imagen tiene texto alt solo con espacios en blanco. Use alt="" para imágenes decorativas o proporcione texto descriptivo.',"Image element missing alt attribute.":"Al elemento de imagen le falta el atributo alt.",'Element with role="img" has no accessible name. Add aria-label or aria-labelledby.':'El elemento con role="img" no tiene nombre accesible. Agregue aria-label o aria-labelledby.'}},"text-alternatives/svg-img-alt":{description:"Los elementos SVG con rol img, graphics-document o graphics-symbol deben tener un nombre accesible mediante un elemento <title>, aria-label o aria-labelledby.",guidance:"Los SVG en línea con role='img' necesitan nombres accesibles. Agregue un elemento <title> como primer hijo del SVG (los lectores de pantalla lo anunciarán), o use aria-label en el elemento SVG. Para SVGs complejos, use aria-labelledby haciendo referencia tanto a un elemento <title> como a un elemento <desc>. Los SVGs decorativos deben usar aria-hidden='true' en su lugar.",messages:{"{0} with role='{1}' has no accessible name.":"{0} con role='{1}' no tiene nombre accesible."}},"text-alternatives/input-image-alt":{description:'Las entradas de imagen (<input type="image">) deben tener texto alternativo mediante alt, aria-label o aria-labelledby. El texto debe describir la acción del botón, no la imagen.',guidance:"Los botones de imagen (<input type='image'>) deben tener texto alternativo mediante alt, aria-label o aria-labelledby. El texto debe describir la acción del botón, no la imagen.",messages:{"Image input missing alt text.":"A la entrada de imagen le falta texto alt."}},"text-alternatives/image-redundant-alt":{description:"El texto alternativo de la imagen no debe duplicar el texto del enlace o botón adyacente. Cuando el texto alt repite el texto circundante, los usuarios de lectores de pantalla escuchan la misma información dos veces.",guidance:"Cuando una imagen está dentro de un enlace o botón que también tiene texto, haga que el texto alt sea complementario en lugar de idéntico. Si la imagen es puramente decorativa en ese contexto, use alt='' para evitar la repetición.",messages:{'Alt text "{0}" duplicates surrounding {1} text.':'El texto alt "{0}" duplica el texto del {1} circundante.'}},"text-alternatives/image-alt-words":{description:"El texto alternativo de la imagen no debe comenzar con palabras como 'imagen de', 'foto de' o 'fotografía de': los lectores de pantalla ya anuncian el tipo de elemento.",guidance:"Los lectores de pantalla ya anuncian 'imagen' o 'gráfico' antes de leer el texto alt, por lo que frases como 'imagen de', 'foto de' o 'fotografía de' son redundantes. Elimine estas palabras y describa lo que muestra la imagen. Por ejemplo, cambie 'imagen de un perro' a 'golden retriever jugando a buscar'.",messages:{'Alt text "{0}" contains redundant word(s): {1}.':'El texto alt "{0}" contiene palabra(s) redundante(s): {1}.'}},"text-alternatives/area-alt":{description:"Los elementos <area> del mapa de imagen deben tener texto alternativo.",guidance:"Cada región clicable en un mapa de imagen necesita texto alternativo para que los usuarios de lectores de pantalla sepan qué representa la región. Agregue un atributo alt a cada elemento <area> describiendo su propósito. Para mapas de imagen complejos, considere usar enfoques alternativos como SVG con enlaces incrustados, o una lista de enlaces de texto.",messages:{"Image map <area> element is missing alternative text.":"Al elemento <area> del mapa de imagen le falta texto alternativo."}},"text-alternatives/object-alt":{description:"Los elementos <object> deben tener texto alternativo.",guidance:"Los elementos object incrustan contenido externo que puede no ser accesible para todos los usuarios. Proporcione texto alternativo mediante aria-label, aria-labelledby o un atributo title. El contenido de respaldo dentro de <object> solo se muestra cuando el objeto no se carga y no sirve como nombre accesible.",messages:{"<object> element is missing alternative text. Add aria-label, aria-labelledby, or a title attribute.":"Al elemento <object> le falta texto alternativo. Agregue aria-label, aria-labelledby o un atributo title."}},"text-alternatives/role-img-alt":{description:"Los elementos con role='img' deben tener un nombre accesible.",guidance:"Cuando asigna role='img' a un elemento (como un div que contiene fuentes de iconos o fondos CSS), debe proporcionar un nombre accesible mediante aria-label o aria-labelledby. Sin esto, los usuarios de lectores de pantalla no tienen forma de entender lo que representa la imagen. Si la imagen es decorativa, use role='presentation' o role='none' en su lugar.",messages:{"Element with role='img' has no accessible name. Add aria-label or aria-labelledby.":"El elemento con role='img' no tiene nombre accesible. Agregue aria-label o aria-labelledby."}},"keyboard-accessible/server-image-map":{description:"No se deben usar mapas de imagen del lado del servidor.",guidance:"Los mapas de imagen del lado del servidor (usando el atributo ismap) envían coordenadas de clic al servidor, lo cual es inaccesible para usuarios de teclado y lectores de pantalla que no pueden hacer clic con precisión en regiones específicas. Reemplace con mapas de imagen del lado del cliente (elementos <map> con <area>) que proporcionan acceso por teclado y nombres accesibles, o use imágenes/botones enlazados.",messages:{"Server-side image map detected. Use client-side image map with <map> and <area> elements instead.":"Se detectó un mapa de imagen del lado del servidor. Use un mapa de imagen del lado del cliente con elementos <map> y <area> en su lugar."}},"labels-and-names/form-label":{description:"Los elementos de formulario deben tener etiquetas. Use <label>, aria-label o aria-labelledby.",guidance:"Cada entrada de formulario necesita una etiqueta accesible para que los usuarios comprendan qué información ingresar. Use un elemento <label> con un atributo for que coincida con el id de la entrada, envuelva la entrada en un <label>, o use aria-label/aria-labelledby para componentes personalizados. Los placeholders no son suficientes como etiquetas ya que desaparecen al escribir. Las etiquetas deben describir la información solicitada, no el tipo de campo (por ejemplo, 'Dirección de correo', 'Buscar', 'Número de teléfono').",messages:{"Form element has no accessible label.":"El elemento de formulario no tiene etiqueta accesible."}},"labels-and-names/multiple-labels":{description:"Los campos de formulario no deben tener múltiples elementos label.",guidance:"Cuando un campo de formulario tiene múltiples elementos <label> apuntando a él, las tecnologías de asistencia pueden anunciar solo una etiqueta o comportarse de manera inconsistente. Use un solo <label> y combine cualquier texto adicional en él, o use aria-describedby para información complementaria.",messages:{"Form field has {0} labels. Use a single label element.":"El campo de formulario tiene {0} etiquetas. Use un solo elemento label."}},"labels-and-names/input-button-name":{description:"Los botones de entrada deben tener texto discernible mediante value, aria-label o aria-labelledby.",guidance:"Los botones de entrada (<input type='submit'>, type='button', type='reset'>) necesitan nombres accesibles para que los usuarios sepan qué acción realiza el botón. Agregue un atributo value con texto descriptivo (por ejemplo, value='Enviar formulario'), o use aria-label si el valor debe diferir del nombre accesible.",messages:{"Input button has no discernible text.":"El botón de entrada no tiene texto discernible."}},"adaptable/autocomplete-valid":{description:"El atributo autocomplete debe usar valores válidos de la especificación HTML.",guidance:"El atributo autocomplete ayuda a los usuarios a completar formularios identificando los propósitos de las entradas. Use valores estándar como 'name', 'email', 'tel', 'street-address', 'postal-code', 'cc-number'. Esto beneficia a usuarios con discapacidades cognitivas, impedimentos motores y cualquier persona que use administradores de contraseñas o autocompletado. Consulte la especificación HTML para la lista completa de tokens válidos.",messages:{'Invalid autocomplete value "{0}".':'Valor de autocomplete inválido "{0}".'}},"labels-and-names/label-content-mismatch":{description:"Los elementos interactivos con texto visible deben tener nombres accesibles que contengan ese texto.",guidance:"Para los usuarios de control por voz que activan controles hablando su etiqueta visible, el nombre accesible debe incluir el texto visible. Si aria-label es 'Enviar formulario' pero el botón muestra 'Enviar', los usuarios de voz que digan 'clic Enviar' no lo activarán. Asegúrese de que aria-label/aria-labelledby contenga o coincida con el texto visible.",messages:{'Accessible name "{0}" does not contain visible text "{1}".':'El nombre accesible "{0}" no contiene el texto visible "{1}".'}},"labels-and-names/label-title-only":{description:"Los elementos de formulario no deben usar el atributo title como único nombre accesible.",guidance:"El atributo title no es confiable como etiqueta porque solo aparece al pasar el cursor/enfocar (no visible para usuarios táctiles) y a menudo es ignorado por las tecnologías de asistencia. Use un elemento <label> visible, aria-label o aria-labelledby en su lugar. El title puede complementar una etiqueta pero no debe reemplazarla.",messages:{"Form element uses title attribute as only label. Use <label>, aria-label, or aria-labelledby instead.":"El elemento de formulario usa el atributo title como única etiqueta. Use <label>, aria-label o aria-labelledby en su lugar."}},"keyboard-accessible/tabindex":{description:"Los elementos no deben tener tabindex mayor que 0, lo cual altera el orden natural de tabulación.",guidance:"Los valores positivos de tabindex fuerzan a los elementos al frente del orden de tabulación independientemente de la posición en el DOM, creando una navegación impredecible para los usuarios de teclado. Use tabindex='0' para agregar elementos al orden natural de tabulación, o tabindex='-1' para hacer elementos enfocables programáticamente pero no en el orden de tabulación. Confíe en el orden del DOM para la secuencia de tabulación.",messages:{'Element has tabindex="{0}" which disrupts tab order.':'El elemento tiene tabindex="{0}" lo cual altera el orden de tabulación.'}},"keyboard-accessible/focus-order":{description:"Los elementos que reciben el foco del teclado deben tener un rol apropiado para que las tecnologías de asistencia puedan transmitir su propósito. Los elementos no interactivos con tabindex='0' necesitan un rol ARIA interactivo válido.",guidance:"Al agregar tabindex='0' a elementos no interactivos como <div> o <span>, los lectores de pantalla los anuncian genéricamente. Agregue un rol apropiado (button, link, tab, etc.) para que los usuarios comprendan el propósito del elemento. También agregue manejadores de eventos de teclado (Enter/Espacio para botones, Enter para enlaces). Considere usar elementos interactivos nativos en su lugar.",messages:{'Non-interactive <{0}> with tabindex="0" has no interactive role.':'El elemento no interactivo <{0}> con tabindex="0" no tiene un rol interactivo.'}},"keyboard-accessible/nested-interactive":{description:"Los controles interactivos no deben estar anidados dentro de otros.",guidance:"Anidar elementos interactivos (como un botón dentro de un enlace, o un enlace dentro de un botón) crea un comportamiento impredecible y confunde a las tecnologías de asistencia. El navegador puede eliminar el elemento interno del árbol de accesibilidad. Reestructure el HTML para que los elementos interactivos sean hermanos, no anidados. Si necesita una tarjeta clicable, use CSS y JavaScript en lugar de anidar.",messages:{"Interactive element <{0}> is nested inside <{1}>.":"El elemento interactivo <{0}> está anidado dentro de <{1}>."}},"keyboard-accessible/scrollable-region":{description:"Las regiones desplazables deben ser accesibles por teclado.",guidance:"El contenido que se desplaza debe ser accesible para los usuarios de teclado. Si una región tiene overflow:scroll u overflow:auto y contiene contenido desplazable, necesita tabindex='0' para ser enfocable, o debe contener elementos enfocables. Sin esto, los usuarios de teclado no pueden desplazar el contenido.",messages:{"Scrollable region is not keyboard accessible. Add tabindex='0' or include focusable elements.":"La región desplazable no es accesible por teclado. Agregue tabindex='0' o incluya elementos enfocables."}},"keyboard-accessible/accesskeys":{description:"Los valores del atributo accesskey deben ser únicos.",guidance:"Cuando múltiples elementos comparten la misma accesskey, el comportamiento del navegador se vuelve impredecible; generalmente solo se activa el primer elemento. Asegúrese de que cada valor de accesskey sea único dentro de la página. También considere que las accesskeys pueden entrar en conflicto con los atajos del navegador y del lector de pantalla, así que úselas con moderación.",messages:{'Duplicate accesskey "{0}". Each accesskey must be unique.':'Accesskey duplicada "{0}". Cada accesskey debe ser única.'}},"navigable/heading-order":{description:"Los niveles de encabezado deben incrementarse de uno en uno; saltarse niveles (por ejemplo, h2 a h4) dificulta la navegación.",guidance:"Los usuarios de lectores de pantalla navegan por encabezados para comprender la estructura de la página. Saltarse niveles (h2 a h4) sugiere contenido faltante y crea confusión. Comience con h1 para el título de la página, luego use h2 para secciones principales, h3 para subsecciones, etc. Puede volver a subir (h3 a h2) al comenzar una nueva sección.",messages:{"Heading level {0} skipped from level {1}. Use h{2} instead.":"Nivel de encabezado {0} saltado desde el nivel {1}. Use h{2} en su lugar."}},"navigable/empty-heading":{description:"Los encabezados deben tener texto discernible.",guidance:"Los usuarios de lectores de pantalla navegan las páginas por encabezados, por lo que los encabezados vacíos crean puntos de navegación confusos. Asegúrese de que todos los encabezados contengan texto visible o nombres accesibles. Si un encabezado se usa puramente para estilo visual, use CSS en lugar de elementos de encabezado.",messages:{"Heading is empty. Add text content or remove the heading element.":"El encabezado está vacío. Agregue contenido de texto o elimine el elemento de encabezado."}},"navigable/p-as-heading":{description:"Los párrafos no deben estilizarse para parecer encabezados.",guidance:"Cuando los párrafos se estilizan con negrita y fuentes grandes para parecer encabezados, los usuarios de lectores de pantalla pierden la estructura semántica. Use elementos de encabezado apropiados (h1-h6) en lugar de párrafos estilizados. Si necesita un estilo específico, aplique CSS a los elementos de encabezado manteniendo la jerarquía adecuada de encabezados.",messages:{"Paragraph appears to be styled as a heading. Use an h1-h6 element instead.":"El párrafo parece estar estilizado como un encabezado. Use un elemento h1-h6 en su lugar."}},"landmarks/landmark-main":{description:"La página debe tener exactamente un landmark main.",guidance:"El landmark main contiene el contenido principal de la página. Los lectores de pantalla permiten a los usuarios saltar directamente al contenido principal. Use un solo elemento <main> (o role='main') para envolver el contenido central, excluyendo encabezados, pies de página y navegación.",messages:{"Page has no main landmark.":"La página no tiene un landmark main.","Page has multiple main landmarks.":"La página tiene múltiples landmarks main."}},"landmarks/no-duplicate-banner":{description:"La página no debe tener más de un landmark banner.",guidance:"El landmark banner (típicamente <header>) identifica contenido orientado al sitio como logotipos y búsqueda. Solo se permite un banner de nivel superior por página. Si necesita múltiples encabezados, anídelos dentro de elementos de sección (article, section, aside) donde se convierten en encabezados de alcance en lugar de banners de nivel de página.",messages:{"Page has multiple banner landmarks.":"La página tiene múltiples landmarks banner."}},"landmarks/no-duplicate-contentinfo":{description:"La página no debe tener más de un landmark contentinfo.",guidance:"El landmark contentinfo (típicamente <footer>) contiene información sobre la página como derechos de autor e información de contacto. Solo se permite un contentinfo de nivel superior por página. Anide pies de página adicionales dentro de elementos de sección para delimitar su alcance.",messages:{"Page has multiple contentinfo landmarks.":"La página tiene múltiples landmarks contentinfo."}},"landmarks/no-duplicate-main":{description:"La página no debe tener más de un landmark main.",guidance:"Solo debe existir un landmark main por página. El landmark main identifica el área de contenido principal. Si tiene múltiples secciones de contenido, use <section> con encabezados apropiados en lugar de múltiples elementos main.",messages:{"Page has multiple main landmarks.":"La página tiene múltiples landmarks main."}},"landmarks/banner-is-top-level":{description:"El landmark banner no debe estar anidado dentro de otro landmark.",guidance:"El landmark banner debe ser un landmark de nivel superior, no anidado dentro de article, aside, main, nav o section. Si un header está dentro de estos elementos, automáticamente se convierte en un encabezado genérico en lugar de un banner. Elimine el role='banner' explícito de los headers anidados o reestructure la página.",messages:{"Banner landmark is nested within another landmark.":"El landmark banner está anidado dentro de otro landmark."}},"landmarks/contentinfo-is-top-level":{description:"El landmark contentinfo no debe estar anidado dentro de otro landmark.",guidance:"El landmark contentinfo debe ser un landmark de nivel superior. Un footer dentro de article, aside, main, nav o section se convierte en un pie de página de alcance, no en un landmark contentinfo. Elimine el role='contentinfo' explícito de los footers anidados o mueva el footer fuera de los elementos de sección.",messages:{"Contentinfo landmark is nested within another landmark.":"El landmark contentinfo está anidado dentro de otro landmark."}},"landmarks/main-is-top-level":{description:"El landmark main no debe estar anidado dentro de otro landmark.",guidance:"El landmark main debe ser un landmark de nivel superior ya que representa el contenido principal de la página. No anide <main> o role='main' dentro de elementos article, aside, nav o section.",messages:{"Main landmark is nested within another landmark.":"El landmark main está anidado dentro de otro landmark."}},"landmarks/complementary-is-top-level":{description:"El landmark aside (complementary) debe ser de nivel superior o estar directamente dentro de main.",guidance:"El landmark complementary (aside) debe ser de nivel superior o un hijo directo de main. Anidar aside profundamente dentro de otros landmarks reduce su descubrimiento para los usuarios de lectores de pantalla que navegan por landmarks.",messages:{"Complementary landmark should be top-level.":"El landmark complementary debe ser de nivel superior."}},"landmarks/landmark-unique":{description:"Los landmarks deben tener etiquetas únicas cuando hay múltiples del mismo tipo.",guidance:"Cuando una página tiene múltiples landmarks del mismo tipo (por ejemplo, múltiples elementos nav), cada uno debe tener un nombre accesible único mediante aria-label o aria-labelledby. Esto ayuda a los usuarios de lectores de pantalla a distinguirlos (por ejemplo, 'Navegación principal' vs 'Navegación del pie de página').",messages:{'Multiple {0} landmarks have the same label "{1}".':'Múltiples landmarks {0} tienen la misma etiqueta "{1}".',"Multiple {0} landmarks have no label. Add unique aria-label attributes.":"Múltiples landmarks {0} no tienen etiqueta. Agregue atributos aria-label únicos."}},"landmarks/region":{description:"Todo el contenido de la página debe estar contenido dentro de landmarks.",guidance:"Los usuarios de lectores de pantalla navegan las páginas por landmarks. El contenido fuera de los landmarks es más difícil de encontrar y comprender. Envuelva todo el contenido visible en landmarks apropiados: <header>, <nav>, <main>, <aside>, <footer>, o <section> con una etiqueta. Los enlaces de salto pueden existir fuera de los landmarks.",messages:{"Content is not contained within a landmark region.":"El contenido no está contenido dentro de una región landmark."}},"adaptable/list-children":{description:"<ul> y <ol> solo deben contener <li>, <script>, <template> o <style> como hijos directos.",guidance:"Los lectores de pantalla anuncian la estructura de la lista ('lista con 5 elementos') basándose en el marcado correcto. Colocar elementos que no son <li> directamente dentro de <ul> u <ol> rompe esta estructura. Envuelva el contenido en elementos <li>, o si necesita divs envolventes para el estilo, aplique los estilos a los elementos <li> directamente y elimine el envoltorio (por ejemplo, cambie <ul><div>item</div></ul> a <ul><li>item</li></ul>).",messages:{"List contains non-<li> child <{0}>.":"La lista contiene un hijo no <li>: <{0}>."}},"adaptable/listitem-parent":{description:"Los elementos <li> deben estar contenidos en un <ul>, <ol> o <menu>.",guidance:"Los elementos de lista (<li>) solo tienen significado semántico dentro de un contenedor de lista (<ul>, <ol> o <menu>). Fuera de estos contenedores, las tecnologías de asistencia no pueden transmitir la relación de lista. Envuelva los elementos <li> en el contenedor de lista apropiado.",messages:{"<li> is not contained in a <ul>, <ol>, or <menu>.":"<li> no está contenido en un <ul>, <ol> o <menu>."}},"adaptable/dl-children":{description:"Los elementos <dt> y <dd> deben estar contenidos en un <dl>.",guidance:"Los términos de definición (<dt>) y las definiciones (<dd>) solo tienen significado semántico dentro de una lista de definiciones (<dl>). Fuera de <dl>, se tratan como texto genérico. Envuelva los pares relacionados de <dt> y <dd> en un elemento <dl> para transmitir la relación término/definición a las tecnologías de asistencia.",messages:{"<{0}> is not contained in a <dl>.":"<{0}> no está contenido en un <dl>."}},"adaptable/definition-list":{description:"Los elementos <dl> solo deben contener <dt>, <dd>, <div>, <script>, <template> o <style>.",guidance:"Las listas de definiciones tienen requisitos estrictos de contenido. Solo <dt> (términos), <dd> (definiciones) y <div> (para agrupar pares dt/dd) son hijos válidos. Otros elementos rompen la estructura de la lista para los lectores de pantalla. Mueva los elementos inválidos fuera del <dl>, o si representan un término cámbielos a <dt>, si son una definición cámbielos a <dd>. Los envoltorios de estilo deben reemplazarse con elementos <div> que contengan pares <dt>/<dd>.",messages:{"<dl> contains invalid child <{0}>.":"<dl> contiene un hijo inválido <{0}>."}},"aria/aria-roles":{description:"Los valores de rol ARIA deben ser válidos.",guidance:"Los valores de rol inválidos son ignorados por las tecnologías de asistencia, lo que significa que el elemento no tendrá la semántica prevista. Verifique la ortografía y use solo roles definidos en la especificación WAI-ARIA. Los roles comunes incluyen: button, link, navigation, main, dialog, alert, tab, tabpanel, menu, menuitem.",messages:{'Invalid ARIA role "{0}".':'Rol ARIA inválido "{0}".'}},"aria/aria-valid-attr":{description:"Los atributos ARIA deben ser válidos (correctamente escritos).",guidance:"Los atributos ARIA mal escritos son ignorados por las tecnologías de asistencia. Verifique la ortografía contra la especificación WAI-ARIA. Errores comunes: aria-labeledby (debe ser aria-labelledby), aria-role (debe ser role), aria-description (válido en ARIA 1.3+).",messages:{'Invalid ARIA attribute "{0}".':'Atributo ARIA inválido "{0}".'}},"aria/aria-valid-attr-value":{description:"Los atributos ARIA deben tener valores válidos.",guidance:"Cada atributo ARIA acepta tipos de valores específicos. Los atributos booleanos (aria-hidden, aria-disabled) aceptan solo 'true' o 'false'. Los atributos triestado (aria-checked, aria-pressed) también aceptan 'mixed'. Los atributos de token (aria-live, aria-autocomplete) aceptan valores predefinidos. Los atributos de referencia de ID (aria-labelledby, aria-describedby) deben referenciar IDs de elementos existentes.",messages:{'{0} must be "true" or "false", got "{1}".':'{0} debe ser "true" o "false", se obtuvo "{1}".','{0} must be "true", "false", or "mixed", got "{1}".':'{0} debe ser "true", "false" o "mixed", se obtuvo "{1}".','{0} must be an integer, got "{1}".':'{0} debe ser un entero, se obtuvo "{1}".','{0} must be a number, got "{1}".':'{0} debe ser un número, se obtuvo "{1}".','Invalid value "{0}" for {1}.':'Valor inválido "{0}" para {1}.'}},"aria/aria-required-attr":{description:"Los elementos con roles ARIA deben tener todos los atributos ARIA requeridos.",guidance:"Algunos roles ARIA requieren atributos específicos para funcionar correctamente. Por ejemplo, checkbox requiere aria-checked, slider requiere aria-valuenow, heading requiere aria-level. Sin estos atributos, las tecnologías de asistencia no pueden transmitir el estado o valor del elemento a los usuarios. Agregue el atributo requerido faltante con un valor apropiado.",messages:{'Role "{0}" requires attribute "{1}".':'El rol "{0}" requiere el atributo "{1}".'}},"aria/aria-allowed-attr":{description:"Los atributos ARIA deben estar permitidos para el rol del elemento.",guidance:"Cada rol ARIA admite atributos específicos. Usar atributos no admitidos crea confusión para las tecnologías de asistencia. Consulte la especificación ARIA para saber qué atributos son válidos para cada rol, o elimine el atributo si no es necesario.",messages:{'ARIA attribute "{0}" is not allowed on role "{1}".':'El atributo ARIA "{0}" no está permitido en el rol "{1}".'}},"aria/aria-allowed-role":{description:"El rol ARIA debe ser apropiado para el elemento.",guidance:"No todos los roles ARIA se pueden aplicar a todos los elementos HTML. Muchos elementos tienen roles implícitos (por ejemplo, <header> es implícitamente banner, <nav> es navigation, <main> es main). Agregar un rol explícito que coincida con el rol implícito es redundante. Agregar un rol conflictivo rompe la semántica. Elimine el atributo role o use un elemento diferente.",messages:{"Element <{0}> should not have an explicit role.":"El elemento <{0}> no debería tener un rol explícito.",'Role "{0}" is not allowed on element <{1}>.':'El rol "{0}" no está permitido en el elemento <{1}>.'}},"adaptable/aria-required-children":{description:"Ciertos roles ARIA requieren que estén presentes roles hijos específicos.",guidance:"Algunos roles ARIA representan contenedores que deben contener roles hijos específicos para una semántica adecuada. Por ejemplo, una lista debe contener listitems, un menú debe contener menuitems. Agregue los elementos hijos requeridos con roles apropiados, o use elementos HTML nativos que proporcionen esta semántica implícitamente (por ejemplo, <ul> con <li>).",messages:{'Role "{0}" requires children with role: {1}.':'El rol "{0}" requiere hijos con rol: {1}.'}},"adaptable/aria-required-parent":{description:"Ciertos roles ARIA deben estar contenidos dentro de roles padre específicos.",guidance:"Algunos roles ARIA representan elementos que deben existir dentro de roles de contenedor específicos. Por ejemplo, un listitem debe estar dentro de una lista, un tab debe estar dentro de un tablist. Envuelva el elemento en el padre apropiado, o use elementos HTML nativos que proporcionen esta estructura (por ejemplo, <li> dentro de <ul>).",messages:{'Role "{0}" must be contained within: {1}.':'El rol "{0}" debe estar contenido dentro de: {1}.'}},"aria/aria-hidden-body":{description:"aria-hidden='true' no debe estar presente en el body del documento.",guidance:"Establecer aria-hidden='true' en el elemento body oculta todo el contenido de la página de las tecnologías de asistencia, haciendo la página completamente inaccesible para los usuarios de lectores de pantalla. Elimine aria-hidden del elemento body. Si necesita ocultar contenido temporalmente (por ejemplo, detrás de un modal), use aria-hidden en secciones específicas en su lugar.",messages:{"aria-hidden='true' on body hides all content from assistive technologies.":"aria-hidden='true' en el body oculta todo el contenido de las tecnologías de asistencia."}},"aria/aria-hidden-focus":{description:"Los elementos con aria-hidden='true' no deben contener elementos enfocables.",guidance:"Cuando aria-hidden='true' oculta un elemento de las tecnologías de asistencia pero el elemento contiene hijos enfocables, los usuarios de teclado pueden enfocar esos hijos pero los usuarios de lectores de pantalla no sabrán que existen. Elimine los elementos enfocables de la región oculta, agregue tabindex='-1' a ellos, o elimine aria-hidden.",messages:{"Focusable element is inside an aria-hidden region.":"Un elemento enfocable está dentro de una región aria-hidden."}},"labels-and-names/aria-command-name":{description:"Los comandos ARIA deben tener un nombre accesible.",guidance:"Los roles de comando ARIA interactivos (button, link, menuitem) deben tener nombres accesibles para que los usuarios sepan qué acción realizan. Agregue contenido de texto visible, aria-label o aria-labelledby para proporcionar un nombre.",messages:{"ARIA command has no accessible name.":"El comando ARIA no tiene nombre accesible."}},"labels-and-names/aria-input-field-name":{description:"Los campos de entrada ARIA deben tener un nombre accesible.",guidance:"Los widgets de entrada ARIA (combobox, listbox, searchbox, slider, spinbutton, textbox) deben tener nombres accesibles para que los usuarios comprendan qué datos ingresar. Agregue una etiqueta visible con aria-labelledby, o use aria-label si una etiqueta visible no es posible.",messages:{"ARIA input field has no accessible name.":"El campo de entrada ARIA no tiene nombre accesible."}},"labels-and-names/aria-toggle-field-name":{description:"Los campos de alternancia ARIA deben tener un nombre accesible.",guidance:"Los controles de alternancia ARIA (checkbox, switch, radio, menuitemcheckbox, menuitemradio) deben tener nombres accesibles para que los usuarios comprendan qué opción están seleccionando. Agregue contenido de texto visible, aria-label, o use aria-labelledby para referenciar una etiqueta visible.",messages:{"ARIA toggle field has no accessible name.":"El campo de alternancia ARIA no tiene nombre accesible."}},"labels-and-names/aria-meter-name":{description:"Los elementos meter ARIA deben tener un nombre accesible.",guidance:"Los elementos meter muestran un valor dentro de un rango conocido (como uso de disco o fortaleza de contraseña). Deben tener nombres accesibles para que los usuarios de lectores de pantalla comprendan qué se está midiendo. Use aria-label o aria-labelledby para proporcionar contexto.",messages:{"Meter has no accessible name.":"El meter no tiene nombre accesible."}},"labels-and-names/aria-progressbar-name":{description:"Los elementos progressbar ARIA deben tener un nombre accesible.",guidance:"Los indicadores de progreso deben tener nombres accesibles para que los usuarios de lectores de pantalla comprendan qué proceso se está rastreando. Use aria-label (por ejemplo, 'Progreso de carga de archivo') o aria-labelledby para referenciar un encabezado o etiqueta visible.",messages:{"Progressbar has no accessible name.":"La barra de progreso no tiene nombre accesible."}},"labels-and-names/aria-dialog-name":{description:"Los diálogos ARIA deben tener un nombre accesible.",guidance:"Los elementos dialog y alertdialog deben tener nombres accesibles para que los usuarios de lectores de pantalla comprendan el propósito del diálogo cuando se abre. Use aria-label o aria-labelledby apuntando al encabezado del diálogo. Los elementos nativos <dialog> también deben tener un nombre accesible.",messages:{"Dialog has no accessible name.":"El diálogo no tiene nombre accesible."}},"labels-and-names/aria-tooltip-name":{description:"Los tooltips ARIA deben tener un nombre accesible.",guidance:"Los elementos tooltip deben tener nombres accesibles (generalmente su contenido de texto). El contenido del tooltip típicamente sirve como el nombre accesible. Asegúrese de que el tooltip contenga contenido de texto descriptivo o tenga aria-label.",messages:{"Tooltip has no accessible name.":"El tooltip no tiene nombre accesible."}},"labels-and-names/aria-treeitem-name":{description:"Los elementos treeitem ARIA deben tener un nombre accesible.",guidance:"Los elementos de árbol deben tener nombres accesibles para que los usuarios de lectores de pantalla puedan comprender la estructura del árbol y navegarlo eficazmente. Proporcione contenido de texto, aria-label o aria-labelledby para cada treeitem.",messages:{"Treeitem has no accessible name.":"El treeitem no tiene nombre accesible."}},"aria/aria-prohibited-attr":{description:"Los atributos ARIA no deben estar prohibidos para el rol del elemento.",guidance:"Algunos roles ARIA prohíben ciertos atributos. Por ejemplo, roles como 'none', 'presentation', 'generic' y roles de nivel de texto (code, emphasis, strong) prohíben aria-label y aria-labelledby porque el nombramiento no está soportado para estos roles. Elimine los atributos prohibidos o cambie el rol.",messages:{"aria-label and aria-labelledby are prohibited on <{0}> elements.":"aria-label y aria-labelledby están prohibidos en elementos <{0}>.",'aria-label and aria-labelledby are prohibited on role "{0}".':'aria-label y aria-labelledby están prohibidos en el rol "{0}".','Attribute "{0}" is prohibited on role "{1}".':'El atributo "{0}" está prohibido en el rol "{1}".'}},"aria/presentation-role-conflict":{description:"Los elementos con role='presentation' o role='none' no deben ser enfocables ni tener atributos ARIA globales.",guidance:"Cuando un elemento tiene role='presentation' o role='none', está marcado como decorativo y se elimina del árbol de accesibilidad. Sin embargo, si el elemento es enfocable o tiene ciertos atributos ARIA, el rol de presentación se ignora y el elemento permanece accesible. Esto crea confusión. Elimine el rol de presentación, o elimine la enfocabilidad/atributos ARIA.",messages:{"Presentation role conflicts with: {0}. The role will be ignored.":"El rol de presentación entra en conflicto con: {0}. El rol será ignorado.",'Element with implicit presentation role (alt="") conflicts with: {0}. The decorative role will be ignored.':'El elemento con rol de presentación implícito (alt="") entra en conflicto con: {0}. El rol decorativo será ignorado.'}},"labels-and-names/button-name":{description:"Los botones deben tener texto discernible.",guidance:"Los usuarios de lectores de pantalla necesitan saber qué hace un botón. Agregue contenido de texto visible, aria-label o aria-labelledby. Para botones de icono, use aria-label describiendo la acción (por ejemplo, aria-label='Cerrar'). Si el botón contiene una imagen, asegúrese de que la imagen tenga texto alt describiendo la acción del botón.",messages:{"Button has no discernible text.":"El botón no tiene texto discernible."}},"labels-and-names/summary-name":{description:"Los elementos <summary> deben tener un nombre accesible.",guidance:"El elemento <summary> proporciona la etiqueta visible para un widget de divulgación <details>. Debe tener contenido de texto descriptivo para que los usuarios de lectores de pantalla comprendan qué se revelará al expandirse. Agregue texto claro y conciso que indique qué contenido está en la sección de detalles.",messages:{"<summary> element has no accessible name. Add descriptive text.":"El elemento <summary> no tiene nombre accesible. Agregue texto descriptivo."}},"navigable/link-name":{description:"Los enlaces deben tener texto discernible mediante contenido, aria-label o aria-labelledby.",guidance:"Los usuarios de lectores de pantalla necesitan saber a dónde lleva un enlace. Agregue contenido de texto descriptivo, aria-label, o use aria-labelledby. Para enlaces de imagen, asegúrese de que la imagen tenga texto alt describiendo el destino del enlace. Evite texto genérico como 'haga clic aquí' o 'leer más': el texto del enlace debe tener sentido fuera de contexto.",messages:{"Link has no discernible text.":"El enlace no tiene texto discernible."}},"navigable/skip-link":{description:"Los enlaces de salto deben apuntar a un destino válido en la página.",guidance:"Los enlaces de salto permiten a los usuarios de teclado omitir la navegación repetitiva y saltar directamente al contenido principal. El enlace de salto debe ser el primer elemento enfocable en la página, enlazar al contenido principal (por ejemplo, href='#main'), y hacerse visible cuando se enfoca. Puede estar visualmente oculto hasta enfocarse usando CSS.",messages:{'Skip link points to "#{0}" which does not exist on the page.':'El enlace de salto apunta a "#{0}" que no existe en la página.'}},"distinguishable/link-in-text-block":{description:"Los enlaces dentro de bloques de texto deben distinguirse por algo más que solo el color.",guidance:"Los usuarios que no pueden percibir diferencias de color necesitan otras señales visuales para identificar enlaces. Los enlaces en texto deben tener subrayados u otros indicadores no cromáticos. Si usa solo color, asegure un contraste de 3:1 con el texto circundante Y proporcione una indicación adicional al enfocar/pasar el cursor.",messages:{"Link in text block is not visually distinguishable from surrounding text. Add a non-color visual indicator such as an underline or border.":"El enlace en el bloque de texto no es visualmente distinguible del texto circundante. Agregue un indicador visual no cromático como un subrayado o borde."}},"readable/html-has-lang":{description:"El elemento <html> debe tener un atributo lang.",guidance:"Los lectores de pantalla usan el atributo lang para determinar qué reglas de idioma y pronunciación usar. Sin él, el contenido puede pronunciarse incorrectamente. Establezca lang en el idioma principal de la página usando un código BCP 47 (por ejemplo, 'en' para inglés, 'es' para español, 'fr' para francés, 'de' para alemán, 'ja' para japonés, 'zh' para chino, 'pt' para portugués, 'ar' para árabe).",messages:{"<html> element missing lang attribute.":"Al elemento <html> le falta el atributo lang."}},"readable/html-lang-valid":{description:"El atributo lang en <html> debe tener un valor válido.",guidance:"El atributo lang debe usar una etiqueta de idioma BCP 47 válida. Use un código de idioma de 2 o 3 letras (por ejemplo, 'en', 'fr', 'zh'), opcionalmente seguido de un código de región (por ejemplo, 'en-US', 'pt-BR'). Las etiquetas inválidas impiden que los lectores de pantalla pronuncien correctamente el contenido.",messages:{'Invalid lang attribute value "{0}".':'Valor de atributo lang inválido "{0}".'}},"readable/valid-lang":{description:"El atributo lang debe tener un valor válido en todos los elementos.",guidance:"Cuando aparece contenido en un idioma diferente dentro de una página (por ejemplo, una cita en francés en un documento en inglés), envuélvalo con un atributo lang para asegurar la pronunciación correcta. El valor de lang debe ser una etiqueta BCP 47 válida. Códigos comunes: en, es, fr, de, zh, ja, pt, ar, ru.",messages:{"Empty lang attribute value.":"Valor de atributo lang vacío.",'Invalid lang attribute value "{0}".':'Valor de atributo lang inválido "{0}".'}},"readable/html-xml-lang-mismatch":{description:"Los atributos lang y xml:lang en <html> deben coincidir.",guidance:"En documentos XHTML, si tanto lang como xml:lang están presentes, deben especificar el mismo idioma base. Los valores no coincidentes confunden a las tecnologías de asistencia. Elimine xml:lang (preferido para HTML5) o asegúrese de que ambos atributos tengan valores idénticos.",messages:{'lang="{0}" and xml:lang="{1}" do not match.':'lang="{0}" y xml:lang="{1}" no coinciden.'}},"adaptable/td-headers-attr":{description:"Todas las celdas en una tabla que usan el atributo headers deben referenciar IDs de encabezado válidos.",guidance:"El atributo headers en las celdas de tabla debe referenciar IDs de celdas de encabezado (th o td) dentro de la misma tabla. Esto crea asociaciones explícitas para lectores de pantalla. Verifique que todos los IDs referenciados existan y estén escritos correctamente. Para tablas simples, considere usar scope en elementos th en su lugar.",messages:{'Headers attribute references the cell itself ("{0}").':'El atributo headers referencia a la propia celda ("{0}").','Headers attribute references non-existent ID "{0}".':'El atributo headers referencia un ID inexistente "{0}".'}},"adaptable/th-has-data-cells":{description:"Los encabezados de tabla deben estar asociados con celdas de datos.",guidance:"Una tabla con celdas de encabezado (th) pero sin celdas de datos (td) probablemente es un mal uso del marcado de tabla para diseño o tiene contenido faltante. Agregue celdas de datos que los encabezados describan, o use marcado apropiado que no sea de tabla si estos no son datos tabulares.",messages:{"Table has header cells but no data cells.":"La tabla tiene celdas de encabezado pero no celdas de datos."}},"adaptable/td-has-header":{description:"Las celdas de datos en tablas mayores de 3x3 deben tener encabezados asociados.",guidance:"En tablas complejas, los usuarios de lectores de pantalla necesitan asociaciones de encabezados para comprender las celdas de datos. Use elementos th con atributo scope, o el atributo headers en elementos td. Para tablas simples (3x3 o menos), esto es menos crítico ya que el contexto generalmente es claro.",messages:{"Data cell has no associated header. Add th elements with scope, or headers attribute.":"La celda de datos no tiene encabezado asociado. Agregue elementos th con scope o el atributo headers."}},"adaptable/scope-attr-valid":{description:"El atributo scope en los encabezados de tabla debe tener un valor válido.",guidance:"El atributo scope indica a los lectores de pantalla a qué celdas se aplica un encabezado. Los valores válidos son: row, col, rowgroup, colgroup. Usar valores inválidos rompe la asociación entre encabezados y celdas.",messages:{'Invalid scope value "{0}". Use row, col, rowgroup, or colgroup.':'Valor de scope inválido "{0}". Use row, col, rowgroup o colgroup.'}},"adaptable/empty-table-header":{description:"Las celdas de encabezado de tabla deben tener texto visible.",guidance:"Los encabezados de tabla vacíos no proporcionan información a los usuarios de lectores de pantalla. Agregue texto descriptivo al encabezado, o si el encabezado está intencionalmente vacío (como una celda de esquina), considere usar un elemento td en su lugar o agregar una etiqueta visualmente oculta.",messages:{"Table header cell is empty. Add text or use aria-label.":"La celda de encabezado de tabla está vacía. Agregue texto o use aria-label."}},"labels-and-names/duplicate-id-aria":{description:"Los IDs usados en asociaciones ARIA y label deben ser únicos para evitar referencias rotas.",guidance:"Cuando aria-labelledby, aria-describedby, aria-controls o label[for] referencian un ID duplicado, solo se usa el primer elemento coincidente. Esto rompe la relación prevista y puede dejar controles sin nombre o descripciones faltantes. Asegúrese de que los IDs referenciados por atributos ARIA y asociaciones de etiquetas sean únicos en todo el documento.",messages:{'Duplicate ID "{0}" referenced by {1}.':'ID duplicado "{0}" referenciado por {1}.'}},"time-based-media/video-captions":{description:"Los elementos de video deben tener subtítulos mediante <track kind='captions'>.",guidance:"Los subtítulos proporcionan alternativas de texto para el contenido de audio en videos, beneficiando a usuarios sordos y aquellos que no pueden escuchar el audio. Agregue un elemento <track> con kind='captions' apuntando a un archivo de subtítulos WebVTT. Los subtítulos deben incluir tanto el diálogo como los efectos de sonido importantes.",messages:{"Video element has no captions track.":"El elemento de video no tiene pista de subtítulos."}},"time-based-media/audio-transcript":{description:"Los elementos de audio deben tener una alternativa de texto o transcripción.",guidance:"El contenido solo de audio como podcasts o grabaciones necesita una alternativa de texto para usuarios sordos. Proporcione una transcripción en la misma página o enlazada cerca. La transcripción debe incluir todo el contenido hablado y descripciones de sonidos relevantes.",messages:{"Audio element has no transcript or text alternative. Add a transcript or track element.":"El elemento de audio no tiene transcripción o alternativa de texto. Agregue una transcripción o elemento track."}},"distinguishable/color-contrast":{description:"Los elementos de texto deben tener suficiente contraste de color contra el fondo.",guidance:"WCAG SC 1.4.3 requiere una relación de contraste de al menos 4.5:1 para texto normal y 3:1 para texto grande (>=24px o >=18.66px en negrita). Aumente el contraste oscureciendo el texto o aclarando el fondo, o viceversa.",messages:{"Insufficient color contrast ratio of {0}:1 (required {1}:1).":"Relación de contraste de color insuficiente de {0}:1 (requerido {1}:1)."}},"distinguishable/color-contrast-enhanced":{description:"Los elementos de texto deben tener contraste de color mejorado contra el fondo (WCAG AAA).",guidance:"WCAG SC 1.4.6 (AAA) requiere una relación de contraste de al menos 7:1 para texto normal y 4.5:1 para texto grande (>=24px o >=18.66px en negrita).",messages:{"Insufficient enhanced contrast ratio of {0}:1 (required {1}:1).":"Relación de contraste mejorado insuficiente de {0}:1 (requerido {1}:1)."}},"enough-time/meta-refresh-no-exception":{description:"La etiqueta meta refresh no debe usarse con un retraso (sin excepciones).",guidance:"Las actualizaciones automáticas de página y las redirecciones con retraso desorientan a los usuarios. Las redirecciones instantáneas (delay=0) son aceptables, pero cualquier retraso positivo no lo es. Use redirecciones del lado del servidor en su lugar.",messages:{"Page has a {0}-second meta refresh delay. Use a server-side redirect instead.":"La página tiene un retraso de meta refresh de {0} segundos. Use una redirección del lado del servidor en su lugar.","Page has a {0}-second meta refresh delay. Remove the auto-refresh or provide user control.":"La página tiene un retraso de meta refresh de {0} segundos. Elimine la actualización automática o proporcione control al usuario."}},"distinguishable/letter-spacing":{description:"El espaciado de letras establecido con !important en atributos de estilo debe ser al menos 0.12em.",guidance:"WCAG 1.4.12 requiere que los usuarios puedan anular el espaciado de texto. Usar !important en letter-spacing con un valor inferior a 0.12em lo impide. Aumente el valor a al menos 0.12em o elimine !important.",messages:{"Letter spacing {0}em with !important is below the 0.12em minimum.":"El espaciado de letras {0}em con !important está por debajo del mínimo de 0.12em."}},"distinguishable/line-height":{description:"La altura de línea establecida con !important en atributos de estilo debe ser al menos 1.5.",guidance:"WCAG 1.4.12 requiere que los usuarios puedan anular el espaciado de texto. Usar !important en line-height con un valor inferior a 1.5 lo impide. Aumente el valor a al menos 1.5 o elimine !important.",messages:{"Line height {0} with !important is below the 1.5 minimum.":"La altura de línea {0} con !important está por debajo del mínimo de 1.5."}},"distinguishable/word-spacing":{description:"El espaciado de palabras establecido con !important en atributos de estilo debe ser al menos 0.16em.",guidance:"WCAG 1.4.12 requiere que los usuarios puedan anular el espaciado de texto. Usar !important en word-spacing con un valor inferior a 0.16em lo impide. Aumente el valor a al menos 0.16em o elimine !important.",messages:{"Word spacing {0}em with !important is below the 0.16em minimum.":"El espaciado de palabras {0}em con !important está por debajo del mínimo de 0.16em."}},"adaptable/orientation-lock":{description:"La orientación de la página no debe restringirse usando transformaciones CSS.",guidance:"Los usuarios con discapacidades motoras pueden montar su dispositivo en una orientación fija. Usar transformaciones CSS con @media (orientation: portrait/landscape) para rotar el contenido 90° bloquea efectivamente la página a una orientación. Elimine la transformación dependiente de la orientación y use diseño responsivo en su lugar.",messages:{"CSS locks page orientation via @media (orientation: {0}) with a 90° transform.":"CSS bloquea la orientación de la página mediante @media (orientation: {0}) con una transformación de 90°."}},"aria/presentational-children-focusable":{description:"Los elementos con un rol que hace a los hijos presentacionales no deben contener contenido enfocable.",guidance:"Roles como button, checkbox, img, tab y otros hacen que sus hijos sean presentacionales — ocultos de las tecnologías de asistencia. Si esos hijos son enfocables, los usuarios de teclado pueden alcanzar elementos que los usuarios de lectores de pantalla no pueden percibir. Mueva el contenido enfocable fuera del padre o elimine la enfocabilidad.",messages:{'Focusable element inside a "{0}" role whose children are presentational.':'Elemento enfocable dentro de un rol "{0}" cuyos hijos son presentacionales.'}},"keyboard-accessible/focus-visible":{description:"Los elementos en orden de foco secuencial deben tener un indicador de foco visible.",guidance:"Los usuarios de teclado necesitan ver qué elemento tiene el foco. No elimine el contorno de foco predeterminado (outline: none) sin proporcionar un indicador visible alternativo. Use estilos :focus-visible o :focus para asegurar que el foco siempre sea perceptible.",messages:{"Focusable element has outline removed without a visible focus alternative.":"El elemento enfocable tiene el contorno eliminado sin una alternativa de foco visible."}},"input-assistance/accessible-authentication":{description:'Los campos de contraseña no deben bloquear los administradores de contraseñas. Evite autocomplete="off" y permita pegar.',guidance:'WCAG 2.2 SC 3.3.8 requiere que los pasos de autenticación eviten pruebas de función cognitiva o proporcionen un mecanismo para asistir a los usuarios. Los administradores de contraseñas son un mecanismo de asistencia clave. Establecer autocomplete="off" en campos de contraseña impide que los administradores de contraseñas completen las credenciales. Bloquear el pegado mediante atributos onpaste impide que los usuarios peguen contraseñas almacenadas. Establezca autocomplete en "current-password" para formularios de inicio de sesión o "new-password" para formularios de registro/cambio de contraseña, y no bloquee el pegado en campos de contraseña.',messages:{'Password field has autocomplete="off" which blocks password managers.':'El campo de contraseña tiene autocomplete="off" lo cual bloquea los administradores de contraseñas.',"Password field blocks pasting, preventing password manager use.":"El campo de contraseña bloquea el pegado, impidiendo el uso de administradores de contraseñas."}}};exports.clearAllCaches=fe;exports.clearAriaAttrAuditCache=Ve;exports.clearAriaHiddenCache=je;exports.clearColorCaches=Be;exports.clearComputedRoleCache=Fe;exports.compileDeclarativeRule=E;exports.configureRules=Vn;exports.createChunkedAudit=Bn;exports.diffAudit=Gn;exports.getAccessibleName=y;exports.getAccessibleTextContent=k;exports.getActiveRules=Y;exports.getComputedRole=H;exports.getHtmlSnippet=m;exports.getImplicitRole=le;exports.getRuleById=Xn;exports.getSelector=p;exports.isAriaHidden=h;exports.isValidRole=ze;exports.localeEn=Jn;exports.localeEs=Kn;exports.querySelectorShadowAware=yt;exports.registerLocale=Pt;exports.rules=he;exports.runAudit=_n;exports.translateViolations=ue;exports.validateDeclarativeRule=sa;
|