@accesslint/core 0.4.0 → 0.5.1

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 CHANGED
@@ -1,5 +1,5 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});let O=new WeakMap;function ye(){O=new WeakMap}function K(t){var n;const a=t.tagName.toLowerCase(),e=(n=t.getAttribute("type"))==null?void 0:n.toLowerCase();switch(a){case"a":return t.hasAttribute("href")?"link":null;case"area":return t.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 t.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 t.closest("article, aside, main, nav, section")?null:"banner";case"hr":return"separator";case"img":return t.getAttribute("alt")===""?"presentation":"img";case"input":switch(e){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 t.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 t.hasAttribute("aria-label")||t.hasAttribute("aria-labelledby")?"region":null;case"select":return t.hasAttribute("multiple")||t.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 R(t){var i;const a=O.get(t);if(a!==void 0)return a;const n=((i=t.getAttribute("role"))==null?void 0:i.trim().toLowerCase())||null||K(t);return O.set(t,n),n}let B=new WeakMap;function Re(){B=new WeakMap}function v(t){const a=B.get(t);if(a!==void 0)return a;const e=Ne(t);return B.set(t,e),e}function Ne(t){var r,o,s,l,p;const a=t.getAttribute("aria-labelledby");if(a){const c=a.split(/\s+/).map(d=>{const h=t.ownerDocument.getElementById(d);return h?A(h).trim():""}).filter(Boolean);if(c.length)return c.join(" ")}const e=(r=t.getAttribute("aria-label"))==null?void 0:r.trim();if(e)return e;if(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement||t instanceof HTMLSelectElement){if(t.id){const h=t.ownerDocument.querySelector(`label[for="${CSS.escape(t.id)}"]`),g=h?A(h).trim():"";if(g)return g}const c=t.closest("label"),d=c?A(c).trim():"";if(d)return d}const n=(o=t.getAttribute("title"))==null?void 0:o.trim();if(n)return n;if(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement){const c=(s=t.getAttribute("placeholder"))==null?void 0:s.trim();if(c)return c}const i=t.tagName.toLowerCase();if(i==="fieldset"){const c=t.querySelector(":scope > legend");if(c){const d=A(c).trim();if(d)return d}}if(i==="table"){const c=t.querySelector(":scope > caption");if(c){const d=A(c).trim();if(d)return d}}if(!(t instanceof HTMLInputElement)){const c=A(t).trim();if(c)return c}return t instanceof HTMLImageElement||t instanceof HTMLAreaElement?((l=t.alt)==null?void 0:l.trim())??"":t instanceof HTMLInputElement&&t.type==="image"?((p=t.alt)==null?void 0:p.trim())??"":""}const Me=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 we(t){const a=t.trim().toLowerCase().replace(/[\u201C\u201D\u2018\u2019\u00AB\u00BB]/g,"");return Me.has(a)}function q(t){let a=t;for(;a;){if(xe(a))return!0;a=a.parentElement}return!1}let U=new WeakMap;function Ae(){U=new WeakMap}function b(t){const a=U.get(t);if(a!==void 0)return a;let e;return t.getAttribute("aria-hidden")==="true"||t instanceof HTMLElement&&(t.hidden||t.style.display==="none")?e=!0:t.parentElement?e=b(t.parentElement):e=!1,U.set(t,e),e}function xe(t){if(t.getAttribute("aria-hidden")==="true"||t instanceof HTMLElement&&t.hidden)return!0;if(typeof getComputedStyle=="function"){const a=getComputedStyle(t);if(a.display==="none"||a.visibility==="hidden")return!0}else if(t instanceof HTMLElement&&t.style.display==="none")return!0;return!1}function A(t){var e,n,i,r,o;let a="";for(const s of t.childNodes)if(s.nodeType===3)a+=s.textContent??"";else if(s.nodeType===1){const l=s;if(!xe(l)){const p=(e=l.tagName)==null?void 0:e.toLowerCase();if(p==="img"||p==="area"){const c=l.getAttribute("aria-labelledby");if(c){const d=c.split(/\s+/).map(h=>{var g,f;return((f=(g=l.ownerDocument.getElementById(h))==null?void 0:g.textContent)==null?void 0:f.trim())??""}).filter(Boolean);if(d.length){a+=d.join(" ");continue}}a+=((n=l.getAttribute("aria-label"))==null?void 0:n.trim())??l.getAttribute("alt")??((i=l.getAttribute("title"))==null?void 0:i.trim())??""}else if(p==="svg"){const c=(r=l.getAttribute("aria-label"))==null?void 0:r.trim();if(c)a+=c;else{const d=l.querySelector("title");d&&(a+=d.textContent??"")}}else(o=l.getAttribute("aria-label"))!=null&&o.trim()?a+=l.getAttribute("aria-label").trim():a+=A(l)}}return a}let V=new WeakMap;function $e(){V=new WeakMap}function He(t){return t.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}const De=["data-testid","data-test-id","data-cy","data-id","name","href","for","aria-label"];function ze(t){const a=t.tagName.toLowerCase();for(const n of De){const i=t.getAttribute(n);if(i!=null&&i.length>0&&i.length<100)return`${a}[${n}="${He(i)}"]`}const e=t.parentElement;if(e){let n=0,i=0;for(let r=0;r<e.children.length;r++)e.children[r].tagName===t.tagName&&(n++,e.children[r]===t&&(i=n));if(n>1)return`${a}:nth-of-type(${i})`}return a}function j(t){if(t.id)return`#${CSS.escape(t.id)}`;const a=t.getRootNode(),e=a instanceof ShadowRoot?null:a.documentElement,n=[];let i=t;for(;i&&i!==e;){if(i!==t&&i.id){n.unshift(`#${CSS.escape(i.id)}`);break}if(n.unshift(ze(i)),n.length>=2){const r=n.join(" > ");try{const o=a.querySelectorAll(r);if(o.length===1&&o[0]===t)return r}catch{}}i=i.parentElement}return n.join(" > ")}function m(t){var r;const a=V.get(t);if(a!==void 0)return a;const e=[];let n=t;for(;n;){const o=n.getRootNode();if(o instanceof ShadowRoot)e.unshift({selector:j(n),delimiter:" >>> "}),n=o.host;else{const s=(r=o.defaultView)==null?void 0:r.frameElement;if(s)e.unshift({selector:j(n),delimiter:" >>>iframe> "}),n=s;else{e.unshift({selector:j(n),delimiter:""});break}}}const i=e.map((o,s)=>(s===0?"":o.delimiter)+o.selector).join("");return V.set(t,i),i}function je(t){const a=[],e=[];let n=t;for(;n;){const r=n.indexOf(" >>>iframe> "),o=n.indexOf(" >>> ");if(r!==-1&&(o===-1||r<=o))a.push(n.slice(0,r).trim()),e.push("iframe"),n=n.slice(r+12);else if(o!==-1)a.push(n.slice(0,o).trim()),e.push("shadow"),n=n.slice(o+5);else{a.push(n.trim());break}}let i=document;for(let r=0;r<a.length;r++){const o=i.querySelector(a[r]);if(!o)return null;if(r<a.length-1)if(e[r]==="iframe"){const s=o.contentDocument;if(!s)return null;i=s}else{const s=o.shadowRoot;if(!s)return null;i=s}else return o}return null}function u(t){const a=t.outerHTML;return a.length>200?a.slice(0,200)+"...":a}const We=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"]),te=new Set(["aria-atomic","aria-busy","aria-disabled","aria-grabbed","aria-hidden","aria-modal","aria-multiline","aria-multiselectable","aria-readonly","aria-required"]),ae=new Set(["aria-checked","aria-pressed"]),Fe=new Set(["aria-colcount","aria-colindex","aria-colspan","aria-level","aria-posinset","aria-rowcount","aria-rowindex","aria-rowspan","aria-setsize"]),Pe=new Set(["aria-valuemax","aria-valuemin","aria-valuenow"]),ne={"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"])},ie=new Set(["caption","code","deletion","emphasis","generic","insertion","mark","none","paragraph","presentation","strong","subscript","superscript","suggestion","term","time"]),Oe={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},Be={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 E=null,T=null;function ke(){E=null,T=null}function J(t){var i;if(T&&(E==null?void 0:E.deref())===t)return T;const a=[],e=[],n=[];for(const r of t.querySelectorAll("*")){let o=!1;for(const c of r.attributes)if(c.name.startsWith("aria-")){o=!0;break}if(!o)continue;let s,l;const p=()=>(s===void 0&&(s=m(r),l=u(r)),{selector:s,html:l});for(const c of r.attributes)if(c.name.startsWith("aria-")&&!We.has(c.name)){const d=p();a.push({ruleId:"aria-valid-attr",selector:d.selector,html:d.html,impact:"critical",message:`Invalid ARIA attribute "${c.name}".`});break}for(const c of r.attributes){if(!c.name.startsWith("aria-"))continue;const d=c.value.trim();if(!(d===""&&!te.has(c.name)&&!ae.has(c.name))){if(te.has(c.name)){if(d!=="true"&&d!=="false"){const h=p();e.push({ruleId:"aria-valid-attr-value",selector:h.selector,html:h.html,impact:"critical",message:`${c.name} must be "true" or "false", got "${d}".`})}}else if(ae.has(c.name)){if(d!=="true"&&d!=="false"&&d!=="mixed"){const h=p();e.push({ruleId:"aria-valid-attr-value",selector:h.selector,html:h.html,impact:"critical",message:`${c.name} must be "true", "false", or "mixed", got "${d}".`})}}else if(Fe.has(c.name)){if(d===""||!/^-?\d+$/.test(d)){const h=p();e.push({ruleId:"aria-valid-attr-value",selector:h.selector,html:h.html,impact:"critical",message:`${c.name} must be an integer, got "${d}".`})}}else if(Pe.has(c.name)){if(d===""||isNaN(Number(d))){const h=p();e.push({ruleId:"aria-valid-attr-value",selector:h.selector,html:h.html,impact:"critical",message:`${c.name} must be a number, got "${d}".`})}}else if(ne[c.name]){const h=d.split(/\s+/);for(const g of h)if(!ne[c.name].has(g)){const f=p();e.push({ruleId:"aria-valid-attr-value",selector:f.selector,html:f.html,impact:"critical",message:`Invalid value "${d}" for ${c.name}.`});break}}}}if(!b(r)){const c=(i=r.getAttribute("role"))==null?void 0:i.trim().toLowerCase(),d=r.tagName.toLowerCase();if(!c&&Oe[d]){const h=r.hasAttribute("aria-label"),g=r.hasAttribute("aria-labelledby");if(h||g){const f=p();n.push({ruleId:"aria-prohibited-attr",selector:f.selector,html:f.html,impact:"serious",message:`aria-label and aria-labelledby are prohibited on <${d}> elements.`})}}else if(c){if(ie.has(c)){const g=r.hasAttribute("aria-label"),f=r.hasAttribute("aria-labelledby");if(g||f){const y=p();n.push({ruleId:"aria-prohibited-attr",selector:y.selector,html:y.html,impact:"serious",message:`aria-label and aria-labelledby are prohibited on role "${c}".`})}}const h=Be[c];if(h){for(const g of r.attributes)if(g.name.startsWith("aria-")&&h.has(g.name)){if((g.name==="aria-label"||g.name==="aria-labelledby")&&ie.has(c))continue;const f=p();n.push({ruleId:"aria-prohibited-attr",selector:f.selector,html:f.html,impact:"serious",message:`Attribute "${g.name}" is prohibited on role "${c}".`})}}}}}return E=new WeakRef(t),T={validAttr:a,validAttrValue:e,prohibitedAttr:n},T}let _=new WeakMap,G=new WeakMap,Y=new WeakMap;function Se(){_=new WeakMap,G=new WeakMap,Y=new WeakMap}function w(t){let a=_.get(t);return a||(a=getComputedStyle(t),_.set(t,a),a)}function N(t,a,e){const[n,i,r]=[t,a,e].map(o=>{const s=o/255;return s<=.04045?s/12.92:Math.pow((s+.055)/1.055,2.4)});return .2126*n+.7152*i+.0722*r}function Ie(t,a){const e=Math.max(t,a),n=Math.min(t,a);return(e+.05)/(n+.05)}const re={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){const a=t.trim().toLowerCase();if(re[a])return re[a];const e=a.match(/^#([0-9a-f])([0-9a-f])([0-9a-f])$/);if(e)return[parseInt(e[1]+e[1],16),parseInt(e[2]+e[2],16),parseInt(e[3]+e[3],16)];const n=a.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/);if(n)return[parseInt(n[1],16),parseInt(n[2],16),parseInt(n[3],16)];const i=t.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*[\d.]+)?\s*\)/);if(i)return[parseInt(i[1]),parseInt(i[2]),parseInt(i[3])];const r=t.match(/rgba?\(\s*(\d+)\s+(\d+)\s+(\d+)\s*(?:\/\s*[\d.]+%?)?\s*\)/);return r?[parseInt(r[1]),parseInt(r[2]),parseInt(r[3])]:null}function Ue(t){const a=G.get(t);if(a!==void 0)return a;const e=Ve(t);return G.set(t,e),e}function Ve(t){let a=t;for(;a;){const e=w(a),n=e.backgroundImage;if(n&&n!=="none"&&n!=="initial")return null;const i=e.backgroundColor;if(i==="transparent"||i==="rgba(0, 0, 0, 0)"||i==="rgba(0 0 0 / 0)"){a=a.parentElement;continue}const r=i.match(/rgba\(.+?,\s*([\d.]+)\s*\)/)||i.match(/rgba?\(.+?\/\s*([\d.]+%?)\s*\)/);if(r&&(r[1].endsWith("%")?parseFloat(r[1])/100:parseFloat(r[1]))<.1){a=a.parentElement;continue}return $(i)}return[255,255,255]}const _e=new Set(["IMG","PICTURE","VIDEO","SVG"]);function Ge(t){const a=Y.get(t);if(a!==void 0)return a;const e=Ye(t);return Y.set(t,e),e}function Ye(t){let a=t,e=!1;for(;a;){const n=w(a).position;if((n==="absolute"||n==="fixed")&&(e=!0),a!==t&&n!=="static"){for(const i of a.children)if(!(i===t||i.contains(t))&&_e.has(i.tagName)){if(e)return!0;const r=w(i).position;if(r==="absolute"||r==="fixed")return!0}if(e)break}a=a.parentElement}return!1}function Xe(t){const a=parseFloat(t);return t.endsWith("pt")?a*(4/3):a}function Ke(t){const a=w(t),e=Xe(a.fontSize),n=parseInt(a.fontWeight)||(a.fontWeight==="bold"?700:400);return e>=23.5||e>=18.5&&n>=700}const qe=new Map;function Je(t,a){qe.set(t,a)}function Qe(t,a){const e=qe.get(a);return e?t.map(n=>{const i=e[n.id];return i?{...n,description:i.description,guidance:i.guidance!==void 0?i.guidance:n.guidance}:n}):t}function W(t){var r,o;const a=[],e=t.closest("a");if(e){const s=e.getAttribute("href");s&&a.push(`Link href: ${s}`)}const n=t.closest("figure");if(n){const s=n.querySelector("figcaption");(r=s==null?void 0:s.textContent)!=null&&r.trim()&&a.push(`Figcaption: ${s.textContent.trim().slice(0,100)}`)}const i=t.parentElement;if(i&&i!==e){const s=t instanceof HTMLImageElement&&t.alt||"",l=(o=i.textContent)==null?void 0:o.replace(s,"").trim().slice(0,100);l&&a.push(`Adjacent text: ${l}`)}return a.length>0?a.join(`
2
- `):void 0}function oe(t){let a=t;for(;a;){if(a instanceof HTMLElement&&a.style.visibility==="hidden")return!0;a=a.parentElement}return!1}const Ze={id:"img-alt",wcag:["1.1.1"],level:"A",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.",prompt:"Describe what alt text to add. If the image appears decorative based on context (spacer, background, icon next to text that already describes it), recommend alt=''. Otherwise suggest descriptive alt text based on the src or surrounding context.",run(t){const a=[];for(const e of t.querySelectorAll("img")){if(b(e)||oe(e))continue;const n=e.getAttribute("role");if(n==="presentation"||n==="none"){const r=e.getAttribute("tabindex");if(!r||r==="-1")continue}const i=e.getAttribute("alt");if(i!==null&&i.trim()===""&&i!==""){a.push({ruleId:"img-alt",selector:m(e),html:u(e),impact:"critical",message:'Image has whitespace-only alt text. Use alt="" for decorative images or provide descriptive text.',context:W(e)});continue}!e.hasAttribute("alt")&&!v(e)&&a.push({ruleId:"img-alt",selector:m(e),html:u(e),impact:"critical",message:"Image element missing alt attribute.",context:W(e)})}for(const e of t.querySelectorAll('[role="img"]:not(img):not(svg)'))b(e)||oe(e)||v(e)||a.push({ruleId:"img-alt",selector:m(e),html:u(e),impact:"critical",message:'Element with role="img" has no accessible name. Add aria-label or aria-labelledby.',context:W(e)});return a}};function et(t){var r,o,s;const a=t.getAttribute("aria-labelledby");if(a){const l=a.split(/\s+/).map(p=>{var c,d;return((d=(c=t.ownerDocument.getElementById(p))==null?void 0:c.textContent)==null?void 0:d.trim())??""}).filter(Boolean);if(l.length)return l.join(" ")}const e=(r=t.getAttribute("aria-label"))==null?void 0:r.trim();if(e)return e;const n=t.querySelector("title");if((o=n==null?void 0:n.textContent)!=null&&o.trim())return n.textContent.trim();const i=(s=t.getAttribute("title"))==null?void 0:s.trim();return i||""}const tt={id:"svg-img-alt",wcag:["1.1.1"],level:"A",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.",prompt:"Based on the SVG content or context, suggest either adding aria-label with a description, or if decorative, replacing role='img' with aria-hidden='true'.",run(t){const a=[],e='svg[role="img"], [role="graphics-document"], [role="graphics-symbol"]';for(const n of t.querySelectorAll(e)){if(b(n))continue;if(!et(n)){const r=n.getAttribute("role");a.push({ruleId:"svg-img-alt",selector:m(n),html:u(n),impact:"serious",message:`${n.tagName.toLowerCase()} with role='${r}' has no accessible name.`})}}return a}},at={id:"input-image-alt",wcag:["1.1.1","4.1.2"],level:"A",description:'Image inputs (<input type="image">) must have alternate text via alt, aria-label, or aria-labelledby. The text should describe the button action, not the image.',guidance:"Image buttons (<input type='image'>) must have alternate text via alt, aria-label, or aria-labelledby. The text should describe the button action, not the image.",prompt:"Based on the src attribute or form context, suggest alt text describing the button's action (e.g., 'Submit', 'Search', 'Go').",run(t){const a=[];for(const e of t.querySelectorAll('input[type="image"]'))b(e)||v(e)||a.push({ruleId:"input-image-alt",selector:m(e),html:u(e),impact:"critical",message:"Image input missing alt text."});return a}},nt={id:"image-redundant-alt",wcag:[],level:"A",tags:["best-practice"],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.",prompt:"The image alt text is identical to the text already visible in the parent link or button. Screen reader users hear the same words twice. If the image is decorative in this context (e.g. an icon next to a label), recommend alt=''. Otherwise suggest brief complementary alt text that adds information the visible text doesn't convey — for example what the image depicts, not what the link says.",run(t){var e;const a=[];for(const n of t.querySelectorAll("img[alt]")){const i=n.getAttribute("alt").trim().toLowerCase();if(!i)continue;const r=n.closest("a, button");if(r){const o=((e=r.textContent)==null?void 0:e.trim().toLowerCase())||"";if(o&&o===i){const s=r.tagName.toLowerCase(),l=r.getAttribute("href");a.push({ruleId:"image-redundant-alt",selector:m(n),html:u(n),impact:"minor",message:`Alt text "${n.getAttribute("alt")}" duplicates surrounding ${s} text.`,context:`Duplicated text: "${n.getAttribute("alt")}", parent element: <${s}>${l?` href="${l}"`:""}`})}}}return a}},it=["image","picture","photo","graphic","icon","img"],rt={id:"image-alt-redundant-words",wcag:[],level:"A",tags:["best-practice"],description:"Image alt text should not contain words like 'image', 'photo', or 'picture' — 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'.",prompt:"The alt text contains a word like 'image', 'photo', or 'picture' that is already announced by the screen reader. Rewrite the alt text with the redundant word removed while keeping the description meaningful. For example: 'image of a sunset over the ocean' → 'sunset over the ocean'; 'photo of team members' → 'team members at the 2024 offsite'; 'icon for settings' → 'settings'.",run(t){const a=[];for(const e of t.querySelectorAll("img[alt]")){const n=e.getAttribute("alt").toLowerCase();if(!n)continue;const i=it.filter(r=>n.split(/\s+/).includes(r));i.length>0&&a.push({ruleId:"image-alt-redundant-words",selector:m(e),html:u(e),impact:"minor",message:`Alt text "${e.getAttribute("alt")}" contains redundant word(s): ${i.join(", ")}.`,context:`Current alt: "${e.getAttribute("alt")}", redundant word(s): ${i.join(", ")}`})}return a}},ot={id:"area-alt",wcag:["1.1.1","4.1.2"],level:"A",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.",prompt:"Based on the href or shape/coords, suggest alt text describing where this area links or what it represents.",run(t){const a=[];for(const e of t.querySelectorAll("area[href]")){if(b(e))continue;v(e)||a.push({ruleId:"area-alt",selector:m(e),html:u(e),impact:"critical",message:"Image map <area> element is missing alternative text."})}return a}};function st(t){var i,r;const a=t.getAttribute("aria-labelledby");if(a){const o=a.split(/\s+/).map(s=>{var l,p;return((p=(l=t.ownerDocument.getElementById(s))==null?void 0:l.textContent)==null?void 0:p.trim())??""}).filter(Boolean);if(o.length)return o.join(" ")}const e=(i=t.getAttribute("aria-label"))==null?void 0:i.trim();if(e)return e;const n=(r=t.getAttribute("title"))==null?void 0:r.trim();return n||""}const lt={id:"object-alt",wcag:["1.1.1"],level:"A",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.",prompt:"Based on the data/type attributes, suggest adding aria-label or a title attribute describing what the embedded content represents.",run(t){var e;const a=[];for(const n of t.querySelectorAll("object")){if(b(n)||n instanceof HTMLElement&&n.style.visibility==="hidden")continue;let i=n.parentElement,r=!1;for(;i;){if(i instanceof HTMLElement&&i.style.visibility==="hidden"){r=!0;break}i=i.parentElement}if(r||n.getAttribute("role")==="presentation"||n.getAttribute("role")==="none"||st(n))continue;const o=n.getAttribute("data")||"";if(!((n.getAttribute("type")||"").startsWith("image/")||/\.(png|jpg|jpeg|gif|svg|webp|bmp|ico)$/i.test(o))){const p=n.querySelector("img[alt]");if(p&&((e=p.getAttribute("alt"))!=null&&e.trim()))continue}a.push({ruleId:"object-alt",selector:m(n),html:u(n),impact:"serious",message:"<object> element is missing alternative text. Add aria-label, aria-labelledby, or a title attribute."})}return a}},ct={id:"role-img-alt",wcag:["1.1.1"],level:"A",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.",prompt:"Based on the element's content or class names, suggest either an aria-label describing the image, or if decorative, recommend removing role='img' or adding aria-hidden='true'.",run(t){const a=[];for(const e of t.querySelectorAll('[role="img"]')){if(b(e)||e.tagName.toLowerCase()==="svg"||e.tagName.toLowerCase()==="img")continue;v(e)||a.push({ruleId:"role-img-alt",selector:m(e),html:u(e),impact:"serious",message:"Element with role='img' has no accessible name. Add aria-label or aria-labelledby."})}return a}};function dt(t){if(typeof t!="object"||t===null)return"Rule spec must be an object";const a=t;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 e=a.check;if(!["selector-exists","attribute-value","attribute-missing","attribute-regex","child-required","child-invalid"].includes(e.type))return`Invalid check type: ${String(e.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 i=ut(e);return i||null}function ut(t){switch(t.type){case"selector-exists":return null;case"attribute-value":return typeof t.attribute!="string"?"attribute-value check requires attribute string":[">","<","=","!=","in","not-in"].includes(t.operator)?t.value===void 0?"attribute-value check requires value":null:"attribute-value check requires valid operator";case"attribute-missing":return typeof t.attribute!="string"?"attribute-missing check requires attribute string":null;case"attribute-regex":return typeof t.attribute!="string"?"attribute-regex check requires attribute string":typeof t.pattern!="string"?"attribute-regex check requires pattern string":typeof t.shouldMatch!="boolean"?"attribute-regex check requires shouldMatch boolean":null;case"child-required":return typeof t.childSelector!="string"?"child-required check requires childSelector string":null;case"child-invalid":return Array.isArray(t.allowedChildren)?null:"child-invalid check requires allowedChildren array";default:return`Unknown check type: ${String(t.type)}`}}function I(t,a,e){let n=t;if(n.includes("{{tag}}")&&(n=n.replace(/\{\{tag\}\}/g,a.tagName.toLowerCase())),n.includes("{{value}}")){let i="";"attribute"in e&&e.attribute&&(i=a.getAttribute(e.attribute)??""),n=n.replace(/\{\{value\}\}/g,i)}return n}function S(t){const a=t.skipAriaHidden!==!1;return{id:t.id,wcag:t.wcag,level:t.level,tags:t.tags,description:t.description,guidance:t.guidance,prompt:t.prompt,run(e){var i,r;const n=[];switch(t.check.type){case"selector-exists":{for(const o of e.querySelectorAll(t.selector))a&&b(o)||n.push({ruleId:t.id,selector:m(o),html:u(o),impact:t.impact,message:I(t.message,o,t.check),element:o});break}case"attribute-value":{const{attribute:o,operator:s,value:l}=t.check;for(const p of e.querySelectorAll(t.selector)){if(a&&b(p))continue;const c=p.getAttribute(o);c!==null&&mt(c,s,l)&&n.push({ruleId:t.id,selector:m(p),html:u(p),impact:t.impact,message:I(t.message,p,t.check),element:p})}break}case"attribute-missing":{const{attribute:o}=t.check;for(const s of e.querySelectorAll(t.selector))a&&b(s)||s.hasAttribute(o)||n.push({ruleId:t.id,selector:m(s),html:u(s),impact:t.impact,message:I(t.message,s,t.check),element:s});break}case"attribute-regex":{const{attribute:o,pattern:s,flags:l,shouldMatch:p}=t.check;let c;try{c=new RegExp(s,l)}catch{break}for(const d of e.querySelectorAll(t.selector)){if(a&&b(d))continue;const h=d.getAttribute(o);if(h===null)continue;const g=c.test(h);p&&!g?n.push({ruleId:t.id,selector:m(d),html:u(d),impact:t.impact,message:I(t.message,d,t.check),element:d}):!p&&g&&n.push({ruleId:t.id,selector:m(d),html:u(d),impact:t.impact,message:I(t.message,d,t.check),element:d})}break}case"child-required":{const{childSelector:o}=t.check;for(const s of e.querySelectorAll(t.selector))a&&b(s)||s.querySelector(o)||n.push({ruleId:t.id,selector:m(s),html:u(s),impact:t.impact,message:I(t.message,s,t.check),element:s});break}case"child-invalid":{const o=new Set(t.check.allowedChildren.map(l=>l.toLowerCase())),s=t.check.allowedChildRoles?new Set(t.check.allowedChildRoles.map(l=>l.toLowerCase())):null;for(const l of e.querySelectorAll(t.selector)){if(a&&b(l))continue;const p=(i=l.getAttribute("role"))==null?void 0:i.trim().toLowerCase();if(p==="presentation"||p==="none")continue;let c=!1;const d=t.check.allowedChildren.filter(h=>h!=="script"&&h!=="template");for(const h of l.childNodes)if(h.nodeType===3&&h.textContent&&h.textContent.trim()){const g=d.map(f=>`<${f}>`).join(" or ");n.push({ruleId:t.id,selector:m(l),html:u(l),impact:t.impact,message:`<${l.tagName.toLowerCase()}> contains direct text content. Wrap in ${g}.`,element:l}),c=!0;break}if(!c)for(const h of l.children){if(o.has(h.tagName.toLowerCase()))continue;const g=(r=h.getAttribute("role"))==null?void 0:r.trim().toLowerCase();if(!(g&&(s!=null&&s.has(g)))&&!(g==="presentation"||g==="none")){n.push({ruleId:t.id,selector:m(h),html:u(h),impact:t.impact,message:I(t.message,h,t.check),element:h});break}}}break}}return n}}}function mt(t,a,e){switch(a){case">":return parseFloat(t)>e;case"<":return parseFloat(t)<e;case"=":return t===String(e);case"!=":return t!==String(e);case"in":return Array.isArray(e)&&e.includes(t);case"not-in":return Array.isArray(e)&&!e.includes(t);default:return!1}}const pt={id:"server-side-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",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.",prompt:"Explain that the ismap attribute should be removed and the functionality replaced with a client-side <map> element with <area> children, or separate linked images/buttons."},ht=S(pt),bt=['[role="checkbox"]','[role="combobox"]','[role="listbox"]','[role="menuitemcheckbox"]','[role="menuitemradio"]','[role="radio"]','[role="searchbox"]','[role="slider"]','[role="spinbutton"]','[role="switch"]','[role="textbox"]'].join(", "),gt=new Set(["checkbox","menuitemcheckbox","menuitemradio","radio","switch"]),ft=new Set(["combobox","listbox","searchbox","slider","spinbutton","textbox"]);function vt(t){var o,s,l,p;const a=(o=t.getAttribute("role"))==null?void 0:o.trim().toLowerCase();if(a&&gt.has(a)||(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement)&&!(a&&ft.has(a)))return v(t);const n=t.getAttribute("aria-labelledby");if(n){const c=n.split(/\s+/).map(d=>{const h=t.ownerDocument.getElementById(d);return h?A(h).trim():""}).filter(Boolean);if(c.length)return c.join(" ")}const i=(s=t.getAttribute("aria-label"))==null?void 0:s.trim();if(i)return i;if(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement||t instanceof HTMLSelectElement){if(t.id){const d=t.ownerDocument.querySelector(`label[for="${CSS.escape(t.id)}"]`);if(d){const h=A(d).trim();if(h)return h}}const c=t.closest("label");if(c){const d=A(c).trim();if(d)return d}}const r=(l=t.getAttribute("title"))==null?void 0:l.trim();if(r)return r;if(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement){const c=(p=t.getAttribute("placeholder"))==null?void 0:p.trim();if(c)return c}return""}const yt={id:"label",wcag:["4.1.2"],level:"A",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.",prompt:`This form field has no accessible label. Based on the context (input type, name attribute, placeholder, or surrounding elements), suggest adding a <label for="id"> element with descriptive text, or an aria-label attribute. The label should describe what information the user should enter, not the field type. For example: 'Email address', 'Search', 'Phone number'.`,run(t){var i;const a=[],n=t.querySelectorAll(`input:not([type="hidden"]):not([type="submit"]):not([type="button"]):not([type="reset"]):not([type="image"]), textarea, select, ${bt}`);for(const r of n){if(b(r)||q(r)||(r instanceof HTMLInputElement||r instanceof HTMLTextAreaElement||r instanceof HTMLSelectElement||r instanceof HTMLButtonElement)&&r.disabled||r.closest("fieldset[disabled]")||r.getAttribute("aria-disabled")==="true")continue;const o=(i=r.getAttribute("role"))==null?void 0:i.trim().toLowerCase();if(o==="presentation"||o==="none")continue;if(!vt(r)){const l=[],p=r.tagName.toLowerCase(),c=r.getAttribute("type");c&&p==="input"&&l.push(`type: ${c}`);const d=r.getAttribute("name");d&&l.push(`name: "${d}"`);const h=r.getAttribute("placeholder");h&&l.push(`placeholder: "${h}"`),o&&l.push(`role: ${o}`);const g=r.getAttribute("id");g&&l.push(`id: "${g}"`),a.push({ruleId:"label",selector:m(r),html:u(r),impact:"critical",message:"Form element has no accessible label.",context:l.length>0?l.join(", "):void 0})}}return a}},wt={id:"form-field-multiple-labels",wcag:[],level:"A",tags:["best-practice"],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.",prompt:"Identify the multiple labels and recommend consolidating them into a single <label> element or using aria-describedby for supplementary text.",run(t){const a=[],e=t.querySelectorAll('input:not([type="hidden"]), textarea, select');for(const n of e){if(b(n)||!n.id)continue;const i=t.querySelectorAll(`label[for="${CSS.escape(n.id)}"]`);let r=0,o=n.parentElement;for(;o;){if(o.tagName.toLowerCase()==="label"&&!o.hasAttribute("for")){r++;break}o=o.parentElement}const s=i.length+r;s>1&&a.push({ruleId:"form-field-multiple-labels",selector:m(n),html:u(n),impact:"moderate",message:`Form field has ${s} labels. Use a single label element.`})}return a}},At={id:"select-name",wcag:["4.1.2"],level:"A",description:"Select elements must have a programmatically associated label via <label>, aria-label, or aria-labelledby.",guidance:"Select dropdowns need labels so users understand what choice they're making. Use a <label> element with a for attribute matching the select's id, or wrap the select in a <label>. For selects without visible labels, use aria-label. The first <option> is not a substitute for a proper label.",prompt:"Based on the options or context, suggest a label element or aria-label describing what this select controls.",run(t){const a=[];for(const e of t.querySelectorAll("select"))b(e)||v(e)||a.push({ruleId:"select-name",selector:m(e),html:u(e),impact:"critical",message:"Select element has no accessible name."});return a}},xt={id:"input-button-name",wcag:["4.1.2"],level:"A",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.",prompt:"Based on the input type and form context, suggest a value attribute describing the button's action.",run(t){var e,n;const a=[];for(const i of t.querySelectorAll('input[type="submit"], input[type="button"], input[type="reset"]')){if(b(i))continue;const r=(e=i.getAttribute("value"))==null?void 0:e.trim(),o=(n=i.getAttribute("type"))==null?void 0:n.toLowerCase(),s=(o==="submit"||o==="reset")&&!i.hasAttribute("value");!r&&!s&&!v(i)&&a.push({ruleId:"input-button-name",selector:m(i),html:u(i),impact:"critical",message:"Input button has no discernible text."})}return a}},kt=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"]),St=new Set(["tel","tel-country-code","tel-national","tel-area-code","tel-local","tel-extension","email","impp"]),It=new Set(["home","work","mobile","fax","pager"]),qt=new Set(["shipping","billing"]),Lt=new Set(["webauthn"]);function Et(t){const a=t.toLowerCase().split(/\s+/).filter(Boolean);if(a.length===0)return!0;let e=0;a[e].startsWith("section-")&&e++,e<a.length&&qt.has(a[e])&&e++;let n=!1;if(e<a.length&&It.has(a[e])&&(n=!0,e++),e>=a.length)return!1;const i=a[e];return!kt.has(i)||n&&!St.has(i)?!1:(e++,e<a.length&&Lt.has(a[e])&&e++,e===a.length)}const Tt={id:"autocomplete-valid",wcag:["1.3.5"],level:"AA",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.",prompt:"Show the invalid autocomplete value and suggest the correct standard value based on the input's apparent purpose.",run(t){const a=[];for(const e of t.querySelectorAll("[autocomplete]")){if(b(e)||e instanceof HTMLElement&&e.style.display==="none"||e.disabled||e.getAttribute("aria-disabled")==="true")continue;const n=e.getAttribute("autocomplete").trim();n&&(Et(n)||a.push({ruleId:"autocomplete-valid",selector:m(e),html:u(e),impact:"serious",message:`Invalid autocomplete value "${n}".`}))}return a}};function se(t){return t.toLowerCase().replace(/\s+/g," ").trim()}function le(t,a){const e=se(t),n=se(a);if(!e||!n||e.includes(n)||n.includes(e))return!0;const i=n.split(/\s+/).map(r=>r.replace(/[.,;:!?\u2026]+$/g,"")).filter(r=>r.length>2);return i.length>=2&&i.filter(o=>e.includes(o)).length/i.length>.5}function X(t){let a="";for(const e of t.childNodes)if(e.nodeType===3)a+=e.textContent??"";else if(e.nodeType===1){const n=e,i=n.tagName.toLowerCase();if(i==="style"||i==="script"||i==="svg"||n.getAttribute("aria-hidden")==="true"||n instanceof HTMLElement&&n.style.display==="none")continue;const r=n.getAttribute("role");if(r==="img"||r==="presentation"||r==="none")continue;a+=X(n)}return a}const Ct={id:"label-content-name-mismatch",wcag:[],level:"A",tags:["best-practice"],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.",prompt:"Show the mismatch between the visible text and accessible name, and suggest updating aria-label to include the visible text.",run(t){const a=[];for(const e of t.querySelectorAll('button, [role="button"], a[href], input[type="submit"], input[type="button"]')){if(b(e))continue;const n=v(e);if(!n)continue;let i="";e instanceof HTMLInputElement?i=e.value||"":i=X(e);const r=i.trim();if(!r||r.length<=2)continue;const o=e.hasAttribute("aria-label"),s=e.hasAttribute("aria-labelledby");!o&&!s||le(n,i)||a.push({ruleId:"label-content-name-mismatch",selector:m(e),html:u(e),impact:"serious",message:`Accessible name "${n}" does not contain visible text "${i.trim()}".`})}for(const e of t.querySelectorAll("input, select, textarea")){if(b(e)||e instanceof HTMLInputElement&&["hidden","submit","button","image"].includes(e.type))continue;const n=v(e);if(!n||!e.hasAttribute("aria-label"))continue;const r=e.id;let o="";if(r){const s=t.querySelector(`label[for="${CSS.escape(r)}"]`);s&&(o=X(s))}o.trim()&&(le(n,o)||a.push({ruleId:"label-content-name-mismatch",selector:m(e),html:u(e),impact:"serious",message:`Accessible name "${n}" does not contain visible label "${o.trim()}".`}))}return a}},Rt={id:"label-title-only",wcag:[],level:"A",tags:["best-practice"],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.",prompt:"The title attribute text should be moved to a visible <label> element or aria-label. Show what text to use based on the current title.",run(t){var n,i,r,o;const a=[],e=t.querySelectorAll('input:not([type="hidden"]):not([type="submit"]):not([type="button"]):not([type="reset"]):not([type="image"]), textarea, select');for(const s of e){if(b(s))continue;const l=s.hasAttribute("title")&&((n=s.getAttribute("title"))==null?void 0:n.trim()),p=s.hasAttribute("aria-label")&&((i=s.getAttribute("aria-label"))==null?void 0:i.trim()),c=s.hasAttribute("aria-labelledby");let d=!1;const h=s.id;if(h){const f=s.ownerDocument.querySelector(`label[for="${CSS.escape(h)}"]`);(r=f==null?void 0:f.textContent)!=null&&r.trim()&&(d=!0)}const g=s.closest("label");(o=g==null?void 0:g.textContent)!=null&&o.trim()&&(d=!0),l&&!p&&!c&&!d&&a.push({ruleId:"label-title-only",selector:m(s),html:u(s),impact:"serious",message:"Form element uses title attribute as only label. Use <label>, aria-label, or aria-labelledby instead."})}return a}},Nt={id:"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"],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.",prompt:"Change the positive tabindex value to tabindex='0' and rely on DOM order for tab sequence instead."},Mt=S(Nt),$t=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"]),Ht={id:"focus-order-semantics",wcag:[],tags:["best-practice"],level:"A",description:"Elements that receive keyboard focus must have an appropriate role so assistive technologies can convey their purpose. Non-interactive elements with tabindex='0' need a valid interactive ARIA role.",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.",prompt:"Based on the element's apparent purpose, suggest adding an appropriate role attribute (button, link, etc.) or converting to a native interactive element.",run(t){const a=[];for(const e of t.querySelectorAll('[tabindex="0"]')){const n=e.tagName.toLowerCase();if(!$t.has(n))continue;e.getAttribute("role")||a.push({ruleId:"focus-order-semantics",selector:m(e),html:u(e),impact:"moderate",message:`Non-interactive <${n}> with tabindex="0" has no interactive role.`})}return a}},Dt=new Set(["a","audio","button","img","input","select","textarea","video"]),zt=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"]),jt={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 Wt(t,a){var i,r,o;const e=(i=t.getAttribute("role"))==null?void 0:i.toLowerCase(),n=(r=a.getAttribute("role"))==null?void 0:r.toLowerCase();return!e||!n?!1:((o=jt[e])==null?void 0:o.has(n))??!1}function Ft(t){var i;const a=t.tagName.toLowerCase();if(Dt.has(a))return a==="a"&&!t.hasAttribute("href")?!1:a==="audio"||a==="video"?t.hasAttribute("controls"):!(a==="img"&&!t.hasAttribute("usemap")||a==="input"&&t.type==="hidden"||t.disabled);const e=(i=t.getAttribute("role"))==null?void 0:i.toLowerCase();if(e&&zt.has(e))return!0;const n=t.getAttribute("tabindex");return n!==null&&n!=="-1"||t.getAttribute("contenteditable")==="true"}function Pt(t){const a=t.tagName.toLowerCase();return!!(a==="a"&&t.hasAttribute("href")||a==="button"&&!t.disabled)}const Ot={id:"nested-interactive",wcag:["4.1.2"],level:"A",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.",prompt:"Identify which elements are nested and suggest restructuring them as siblings instead.",run(t){const a=[],e=t.body??t;if(!e)return a;const i=(t.body?t:t.ownerDocument).createTreeWalker(e,NodeFilter.SHOW_ELEMENT),r=[];let o=i.currentNode;for(;o;){for(;r.length>0&&!r[r.length-1].contains(o);)r.pop();if(!b(o)&&Ft(o)){if(r.length>0){const s=r[r.length-1];Wt(s,o)||a.push({ruleId:"nested-interactive",selector:m(o),html:u(o),impact:"serious",message:`Interactive element <${o.tagName.toLowerCase()}> is nested inside <${s.tagName.toLowerCase()}>.`})}Pt(o)&&r.push(o)}o=i.nextNode()}return a}},Bt={id:"scrollable-region-focusable",wcag:["2.1.1"],level:"A",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.",prompt:"Explain how to make this scrollable region keyboard accessible.",run(t){var e;const a=[];for(const n of t.querySelectorAll("*")){if(b(n)||!(n instanceof HTMLElement))continue;const i=n.tagName.toLowerCase();if(i==="body"||i==="html")continue;const r=n.getAttribute("role");if(r==="presentation"||r==="none")continue;const o=w(n),s=o.overflowX,l=o.overflowY;if(!(s==="scroll"||s==="auto"||l==="scroll"||l==="auto"))continue;if(n.scrollHeight>0||n.clientHeight>0){const g=n.scrollHeight-n.clientHeight,f=n.scrollWidth-n.clientWidth;if(g<=0&&f<=0||g<14&&f<14||n.clientWidth<64&&n.clientHeight<64)continue}else{const g=o.height!==""||o.maxHeight!=="",f=((e=n.textContent)==null?void 0:e.trim().length)??0;if(!g||f<=50)continue}const d=n.getAttribute("tabindex");d!==null&&d!=="-1"||n.querySelector('a[href], button:not([disabled]), input:not([disabled]):not([type="hidden"]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])')||a.push({ruleId:"scrollable-region-focusable",selector:m(n),html:u(n),impact:"serious",message:"Scrollable region is not keyboard accessible. Add tabindex='0' or include focusable elements."})}return a}},Ut={id:"accesskeys",wcag:[],level:"A",tags:["best-practice"],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.",prompt:"Suggest removing or changing this duplicate accesskey to a unique value.",run(t){var n;const a=[],e=new Map;for(const i of t.querySelectorAll("[accesskey]")){if(b(i))continue;const r=(n=i.getAttribute("accesskey"))==null?void 0:n.trim().toLowerCase();if(!r)continue;const o=e.get(r)||[];o.push(i),e.set(r,o)}for(const[i,r]of e)if(r.length>1)for(const o of r.slice(1))a.push({ruleId:"accesskeys",selector:m(o),html:u(o),impact:"serious",message:`Duplicate accesskey "${i}". Each accesskey must be unique.`});return a}},Vt={id:"heading-order",wcag:[],level:"A",tags:["best-practice"],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.",prompt:"State which heading level was expected and suggest changing this heading to the appropriate level.",run(t){const a=[],e=t.querySelectorAll("h1, h2, h3, h4, h5, h6, [role='heading']");let n=0,i=null;for(const r of e){if(b(r))continue;let o;r.hasAttribute("aria-level")?o=parseInt(r.getAttribute("aria-level"),10):o=parseInt(r.tagName[1],10),n>0&&o>n+1&&a.push({ruleId:"heading-order",selector:m(r),html:u(r),impact:"moderate",message:`Heading level ${o} skipped from level ${n}.`,context:i?`Previous heading: ${u(i)}`:void 0}),n=o,i=r}return a}},H='article, aside, main, nav, section, [role="article"], [role="complementary"], [role="main"], [role="navigation"], [role="region"]',ce='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"]',_t={id:"landmark-one-main",wcag:[],level:"A",tags:["best-practice"],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.",prompt:"Identify the primary content area and explain how to wrap it in a <main> element.",run(t){const a=t.querySelectorAll('main, [role="main"]');return a.length===0?[{ruleId:"landmark-one-main",selector:"html",html:"<html>",impact:"moderate",message:"Page has no main landmark."}]:a.length>1?Array.from(a).slice(1).map(e=>({ruleId:"landmark-one-main",selector:m(e),html:u(e),impact:"moderate",message:"Page has multiple main landmarks."})):[]}},Gt={id:"landmark-no-duplicate-banner",wcag:[],level:"A",tags:["best-practice"],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.",prompt:"Explain whether to remove this duplicate banner or nest it inside a sectioning element.",run(t){const a=[],e=t.querySelectorAll('header, [role="banner"]'),n=Array.from(e).filter(i=>!i.closest(H));return n.length>1&&n.slice(1).forEach(i=>a.push({ruleId:"landmark-no-duplicate-banner",selector:m(i),html:u(i),impact:"moderate",message:"Page has multiple banner landmarks."})),a}},Yt={id:"landmark-no-duplicate-contentinfo",wcag:[],level:"A",tags:["best-practice"],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.",prompt:"Explain whether to remove this duplicate footer or nest it inside a sectioning element.",run(t){const a=[],e=t.querySelectorAll('footer, [role="contentinfo"]'),n=Array.from(e).filter(i=>!i.closest(H));return n.length>1&&n.slice(1).forEach(i=>a.push({ruleId:"landmark-no-duplicate-contentinfo",selector:m(i),html:u(i),impact:"moderate",message:"Page has multiple contentinfo landmarks."})),a}},Xt={id:"landmark-no-duplicate-main",wcag:[],level:"A",tags:["best-practice"],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.",prompt:"Explain which main landmark to keep and how to restructure the duplicate.",run(t){const a=[],e=t.querySelectorAll('main, [role="main"]');return e.length>1&&Array.from(e).slice(1).forEach(n=>a.push({ruleId:"landmark-no-duplicate-main",selector:m(n),html:u(n),impact:"moderate",message:"Page has multiple main landmarks."})),a}},Kt={id:"landmark-banner-is-top-level",wcag:[],level:"A",tags:["best-practice"],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.",prompt:"Explain why this banner is incorrectly nested and how to fix it.",run(t){const a=[],e=t.querySelectorAll('[role="banner"]');for(const n of e)n.closest(H)&&a.push({ruleId:"landmark-banner-is-top-level",selector:m(n),html:u(n),impact:"moderate",message:"Banner landmark is nested within another landmark."});return a}},Jt={id:"landmark-contentinfo-is-top-level",wcag:[],level:"A",tags:["best-practice"],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.",prompt:"Explain why this contentinfo is incorrectly nested and how to fix it.",run(t){const a=[],e=t.querySelectorAll('[role="contentinfo"]');for(const n of e)n.closest(H)&&a.push({ruleId:"landmark-contentinfo-is-top-level",selector:m(n),html:u(n),impact:"moderate",message:"Contentinfo landmark is nested within another landmark."});return a}},Qt={id:"landmark-main-is-top-level",wcag:[],level:"A",tags:["best-practice"],description:"Main landmark should not be nested within another landmark.",guidance:"The main landmark must be a top-level landmark since it represents the primary content of the page. Do not nest <main> or role='main' inside article, aside, nav, or section elements.",prompt:"Explain why the main landmark must be top-level and where to move it.",run(t){const a=[],e=t.querySelectorAll('main, [role="main"]');for(const n of e){const i=n.parentElement;i!=null&&i.closest('article, aside, nav, section, [role="article"], [role="complementary"], [role="navigation"], [role="region"]')&&a.push({ruleId:"landmark-main-is-top-level",selector:m(n),html:u(n),impact:"moderate",message:"Main landmark is nested within another landmark."})}return a}},Zt={id:"landmark-complementary-is-top-level",wcag:[],level:"A",tags:["best-practice"],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.",prompt:"Explain why this aside should be repositioned and suggest where to move it.",run(t){const a=[],e=t.querySelectorAll('aside, [role="complementary"]');for(const n of e){const i=n.parentElement;i&&!i.matches('body, main, [role="main"]')&&n.closest('article, nav, section, [role="article"], [role="navigation"], [role="region"]')&&a.push({ruleId:"landmark-complementary-is-top-level",selector:m(n),html:u(n),impact:"moderate",message:"Complementary landmark should be top-level."})}return a}},ea={id:"landmark-unique",wcag:[],level:"A",tags:["best-practice"],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').",prompt:"Suggest a unique aria-label that distinguishes this landmark based on its purpose.",run(t){const a=[],e=[{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:n,type:i}of e){const r=Array.from(t.querySelectorAll(n)).filter(s=>!b(s));if(r.length<=1)continue;const o=new Map;for(const s of r){const l=v(s).toLowerCase()||"",p=o.get(l)||[];p.push(s),o.set(l,p)}for(const[s,l]of o)if(l.length>1)for(const p of l.slice(1))a.push({ruleId:"landmark-unique",selector:m(p),html:u(p),impact:"moderate",message:s?`Multiple ${i} landmarks have the same label "${s}".`:`Multiple ${i} landmarks have no label. Add unique aria-label attributes.`})}return a}},ta={id:"region",wcag:[],level:"A",tags:["best-practice"],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.",prompt:"Based on the content, suggest which landmark element would be most appropriate.",run(t){var n;const a=[],e=t.body;if(!e)return[];for(const i of e.children){if(b(i)||i instanceof HTMLScriptElement||i instanceof HTMLStyleElement||i.tagName==="NOSCRIPT"||i instanceof HTMLElement&&i.hidden||i.matches('a[href^="#"]'))continue;const r=i.matches(ce),o=(n=i.textContent)==null?void 0:n.trim();!r&&o&&(i.querySelector(ce)||a.push({ruleId:"region",selector:m(i),html:u(i),impact:"moderate",message:"Content is not contained within a landmark region."}))}return a}},aa={id:"list",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",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, restructure your CSS to style the <li> elements directly.",prompt:"Explain how to restructure this element within the list properly."},na=S(aa),ia={id:"dlitem",wcag:["1.3.1"],level:"A",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.",prompt:"Explain how to properly structure this term/definition content.",run(t){const a=[];for(const e of t.querySelectorAll("dt, dd"))(!e.parentElement||e.parentElement.tagName.toLowerCase()!=="dl")&&a.push({ruleId:"dlitem",selector:m(e),html:u(e),impact:"serious",message:`<${e.tagName.toLowerCase()}> is not contained in a <dl>.`});return a}},ra={id:"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",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 restructure using proper definition list markup.",prompt:"Explain whether to move this element outside the <dl> or convert it to dt/dd."},oa=S(ra),sa={id:"listitem",wcag:["1.3.1"],level:"A",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>). Outside of these containers, assistive technologies cannot convey the list relationship. Wrap <li> elements in the appropriate list container.",prompt:"Explain that this <li> must be placed inside a <ul>, <ol>, or <menu> element.",run(t){var e;const a=[];for(const n of t.querySelectorAll("li")){if(b(n))continue;const i=n.parentElement;if(!i)continue;const r=i.tagName.toLowerCase();r==="ul"||r==="ol"||r==="menu"||((e=i.getAttribute("role"))==null?void 0:e.trim().toLowerCase())==="list"||a.push({ruleId:"listitem",selector:m(n),html:u(n),impact:"serious",message:"<li> is not contained in a <ul>, <ol>, or <menu>."})}return a}},la={id:"document-title",wcag:["2.4.2"],level:"A",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').",prompt:"The page has no title or an empty title. Suggest a concise, descriptive <title> based on the page content sample in context. Good titles are specific and front-load the unique part: 'Product Details - Store Name' rather than 'Store Name - Product Details'.",run(t){var e,n,i;const a=t.querySelector("title");if(!a||!((e=a.textContent)!=null&&e.trim())){let r;const o=t.querySelector("h1");if((n=o==null?void 0:o.textContent)!=null&&n.trim())r=`h1: "${o.textContent.trim().slice(0,100)}"`;else if(t.body){const s=((i=t.body.textContent)==null?void 0:i.trim().replace(/\s+/g," "))||"";s&&(r=`Page text: "${s.slice(0,150)}"`)}return[{ruleId:"document-title",selector:"html",html:"<html>",impact:"serious",message:a?"Document <title> element is empty.":"Document is missing a <title> element.",context:r}]}return[]}},ca={id:"bypass",wcag:[],level:"A",tags:["best-practice"],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.',prompt:'The page has no mechanism for keyboard users to skip repeated content. The simplest fix is to wrap the primary content area in a <main> element — screen readers can jump directly to it. Alternatively, add a skip link as the first element in <body>: <a href="#main" class="skip-link">Skip to main content</a>, with a matching id on the target element. Use the context to understand what the page is missing.',run(t){if(t.querySelector('main, [role="main"], nav, [role="navigation"], aside, [role="complementary"], header, [role="banner"], footer, [role="contentinfo"], [role="search"], [role="region"]'))return[];const e=t.querySelector('a[href^="#"]');if(e){const r=e.getAttribute("href");if(r&&r.length>1){const o=r.slice(1);if(t.getElementById(o))return[]}}if(t.querySelector("h1, h2, h3, [role='heading']"))return[];const i=[];return i.push("no landmarks (<main>, <nav>, <header>, <footer>)"),i.push("no skip link"),i.push("no headings"),[{ruleId:"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: ${i.join(", ")}`}]}},da={id:"page-has-heading-one",wcag:[],level:"A",tags:["best-practice"],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 exactly one h1 that describes the main content, typically matching or similar to the page title.",prompt:"The page has no <h1> heading. Suggest appropriate h1 text based on the page title or content sample in context. The h1 should describe the page's main topic and typically be placed at the start of the main content area.",run(t){var o,s,l;const a=t.querySelector("h1");if(a&&v(a))return[];const e=t.querySelectorAll('[role="heading"][aria-level="1"]');for(const p of e)if(v(p))return[];const n=[],i=(s=(o=t.querySelector("title"))==null?void 0:o.textContent)==null?void 0:s.trim();i&&n.push(`Page title: "${i}"`);const r=t.querySelector("main");if(r){const p=((l=r.textContent)==null?void 0:l.trim().replace(/\s+/g," "))||"";p&&n.push(`Main content: "${p.slice(0,100)}"`)}return[{ruleId:"page-has-heading-one",selector:"html",html:"<html>",impact:"moderate",message:"Page does not contain a level-one heading.",context:n.length>0?n.join(", "):void 0}]}};function Le(t){if(!(t instanceof HTMLElement))return!1;if(t.style.display==="none"||t.style.visibility==="hidden")return!0;const a=t.getAttribute("width"),e=t.getAttribute("height");return(a==="0"||a==="1")&&(e==="0"||e==="1")}const ua={id:"frame-title",wcag:["4.1.2"],level:"A",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'.",prompt:"This iframe has no accessible name. Based on the src URL in context, suggest a descriptive title attribute that tells screen reader users what the frame contains. For example: 'YouTube video player', 'Google Map', 'Payment form', 'Chat widget'. If the frame appears decorative or non-essential, recommend adding aria-hidden='true' instead.",run(t){const a=[];for(const e of t.querySelectorAll("iframe, frame")){if(b(e)||Le(e))continue;if(!v(e)){const i=e.getAttribute("src");a.push({ruleId:"frame-title",selector:m(e),html:u(e),impact:"serious",message:"Frame is missing an accessible name. Add a title attribute.",context:i?`src: "${i}"`:void 0})}}return a}},ma={id:"frame-title-unique",wcag:["4.1.2"],level:"A",tags:["best-practice"],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.",prompt:"Suggest a more specific title to distinguish this frame from others.",run(t){var i;const a=[],e=Array.from(t.querySelectorAll("iframe[title], frame[title]")),n=new Map;for(const r of e){if(b(r)||Le(r))continue;const o=(i=r.getAttribute("title"))==null?void 0:i.trim().toLowerCase();if(o){const s=n.get(o)||[];s.push(r),n.set(o,s)}}for(const[,r]of n)if(r.length>1)for(const o of r.slice(1))a.push({ruleId:"frame-title-unique",selector:m(o),html:u(o),impact:"moderate",message:"Frame title is not unique. Use a distinct title for each frame."});return a}},pa={id:"empty-heading",wcag:[],level:"A",tags:["best-practice"],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.",prompt:"This heading element has no text content, so screen reader users encounter a blank heading when navigating. Either add descriptive text that summarizes the following section, or if this element is used only for visual styling, replace it with a styled <p> or <div> and use CSS for appearance. The context includes nearby content to help suggest appropriate heading text.",run(t){var n;const a=[],e=t.querySelectorAll('h1, h2, h3, h4, h5, h6, [role="heading"]');for(const i of e)if(!b(i)&&!v(i)){let r;const o=i.nextElementSibling;if(o){const s=((n=o.textContent)==null?void 0:n.trim().replace(/\s+/g," "))||"";s&&(r=s.slice(0,100))}a.push({ruleId:"empty-heading",selector:m(i),html:u(i),impact:"minor",message:"Heading is empty. Add text content or remove the heading element.",context:r?`Following content: "${r}"`:void 0})}return a}},ha={id:"meta-viewport",wcag:["1.4.4"],level:"AA",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.",prompt:"The viewport meta tag restricts zooming, which prevents low-vision users from enlarging content. Show the current content attribute and a corrected version with the problematic properties removed. Keep other viewport properties (like width=device-width, initial-scale=1) intact — only remove user-scalable=no and maximum-scale restrictions.",run(t){const a=[],e=t.querySelector('meta[name="viewport"]');if(!e)return[];const n=e.getAttribute("content")||"",i=n.toLowerCase(),r=i.match(/user-scalable\s*=\s*([^\s,;]+)/i);if(r){const s=r[1],l=parseFloat(s);(s==="no"||!isNaN(l)&&l>-1&&l<1)&&a.push({ruleId:"meta-viewport",selector:m(e),html:u(e),impact:"critical",message:`Viewport disables user scaling (user-scalable=${s}). Remove this restriction.`,context:`content: "${n}"`})}const o=i.match(/maximum-scale\s*=\s*([\d.]+|yes)/i);if(o){const s=o[1],l=s.toLowerCase()==="yes"?1:parseFloat(s);l<2&&a.push({ruleId:"meta-viewport",selector:m(e),html:u(e),impact:"critical",message:`Viewport maximum-scale=${l} restricts zooming. Set to at least 2 or remove.`,context:`content: "${n}"`})}return a}},ba={id:"meta-refresh",wcag:["2.2.1","2.2.4","3.2.5"],level:"A",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.",prompt:"Explain why meta refresh is problematic and suggest server-side alternatives.",run(t){for(const a of t.querySelectorAll('meta[http-equiv="refresh"]')){const e=a.getAttribute("content")||"",n=e.match(/^(\d+)/);if(!n)continue;const i=parseInt(n[1],10);if(/^\d+\s*[;,]\s*url\s*=/i.test(e)||/^\d+\s*[;,]\s*['"]?\s*https?:/i.test(e))return i>0&&i<=72e3?[{ruleId:"meta-refresh",selector:m(a),html:u(a),impact:"critical",message:`Page redirects after ${i} seconds without warning. Use server-side redirect.`}]:[];if(i>0&&i<=72e3)return[{ruleId:"meta-refresh",selector:m(a),html:u(a),impact:"critical",message:`Page auto-refreshes after ${i} seconds. Provide user control over refresh.`}]}return[]}},ga={id:"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",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.",prompt:"Suggest static alternatives to the blinking effect."},fa=S(ga),va={id:"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",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.",prompt:"Suggest static alternatives or accessible carousel patterns."},ya=S(va),wa={id:"p-as-heading",wcag:[],level:"A",tags:["best-practice"],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.",prompt:"Suggest the appropriate heading level based on the document structure.",run(t){var e,n;const a=[];for(const i of t.querySelectorAll("p")){if(b(i))continue;const r=i.getAttribute("style")||"",o=/font-weight\s*:\s*(bold|[6-9]00)/i.test(r),s=/font-size\s*:\s*(\d+)\s*(px|em|rem)/i.test(r),l=((e=i.className)==null?void 0:e.toLowerCase())||"",p=/\bh[1-6]\b|\bheading\b/.test(l),c=((n=i.textContent)==null?void 0:n.trim())||"",d=c.length>0&&c.length<50,h=!c.match(/[.!?,;:]$/);if((o&&s||o&&p)&&d&&h){const y=i.nextElementSibling;y&&(y.tagName==="P"||y.tagName==="DIV"||y.tagName==="UL")&&a.push({ruleId:"p-as-heading",selector:m(i),html:u(i),impact:"serious",message:"Paragraph appears to be styled as a heading. Use an h1-h6 element instead."})}}return a}},Aa={id:"aria-roles",wcag:["4.1.2"],level:"A",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.",prompt:"Identify the invalid role and suggest the correct spelling or a valid alternative role that matches the intended purpose.",run(t){const a=[];for(const e of t.querySelectorAll("[role]")){const r=e.getAttribute("role").replace(/[\u201C\u201D\u2018\u2019\u00AB\u00BB]/g,"").split(/\s+/).filter(Boolean);!r.some(s=>we(s))&&r.length>0&&a.push({ruleId:"aria-roles",selector:m(e),html:u(e),impact:"critical",message:`Invalid ARIA role "${r[0]}".`})}return a}},xa={id:"aria-valid-attr",wcag:["4.1.2"],level:"A",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+).",prompt:"Identify the misspelled attribute and provide the correct spelling.",run(t){return J(t).validAttr}},ka={id:"aria-valid-attr-value",wcag:["4.1.2"],level:"A",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.",prompt:"Show the invalid value and list the valid values for this specific attribute.",run(t){return J(t).validAttrValue}},Sa={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"]},Ia={id:"aria-required-attr",wcag:["4.1.2"],level:"A",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.",prompt:"State which attribute is required for this role and suggest an appropriate value based on the element's apparent state.",run(t){const a=[];for(const e of t.querySelectorAll("[role]")){if(b(e)||e instanceof HTMLElement&&e.style.display==="none")continue;const n=e.getAttribute("role").trim().toLowerCase(),i=Sa[n];if(i&&!(n==="checkbox"&&e instanceof HTMLInputElement&&e.type==="checkbox")&&!(n==="radio"&&e instanceof HTMLInputElement&&e.type==="radio")&&!(n==="option"&&e instanceof HTMLOptionElement)&&!(n==="heading"&&/^h[1-6]$/i.test(e.tagName))){if(n==="separator"){const r=e.getAttribute("tabindex");if(!r||r==="-1")continue}if(!(e.tagName.toLowerCase()==="hr"&&!e.hasAttribute("role"))){for(const r of i)if(!e.hasAttribute(r)){a.push({ruleId:"aria-required-attr",selector:m(e),html:u(e),impact:"critical",message:`Role "${n}" requires attribute "${r}".`});break}}}}return a}};function qa(t){var r,o,s;const a=[],e=t.className;e&&typeof e=="string"&&e.trim()&&a.push(`Classes: ${e.trim().slice(0,100)}`);const n=t.closest("form");if(n){const l=n.getAttribute("aria-label")||((o=(r=n.querySelector("legend"))==null?void 0:r.textContent)==null?void 0:o.trim());l&&a.push(`Form: ${l.slice(0,60)}`)}const i=t.parentElement;if(i){const l=i.closest("h1, h2, h3, h4, h5, h6")||i.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(`
3
- `):void 0}const La={id:"button-name",wcag:["4.1.2"],level:"A",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.",prompt:"Based on the button's content, class, or context, suggest an appropriate aria-label describing the action it performs.",run(t){const a=[];for(const e of t.querySelectorAll('button, [role="button"]')){if(b(e)||q(e))continue;const n=e.getAttribute("role");if((n==="none"||n==="presentation")&&!(e.matches('button:not([disabled]), [tabindex]:not([tabindex="-1"])')||e.tagName.toLowerCase()==="button"&&!e.disabled)||e.getRootNode()instanceof ShadowRoot)continue;v(e)||a.push({ruleId:"button-name",selector:m(e),html:u(e),impact:"critical",message:"Button has no discernible text.",context:qa(e)})}return a}},Ea={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"])},Ta=new Set(["aria-atomic","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","aria-braillelabel","aria-brailleroledescription"]),Ca={id:"aria-allowed-attr",wcag:["4.1.2"],level:"A",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.",prompt:"The ARIA attribute listed in context is not supported on this element's role. Either remove the attribute (if the behavior it describes isn't needed), or change the element's role to one that supports it. The context lists which attributes ARE allowed on this role — use that to suggest alternatives if applicable.",run(t){const a=[];for(const e of t.querySelectorAll("[role], [aria-*]")){if(b(e))continue;const n=R(e);if(!n)continue;const i=Ea[n];if(i)for(const r of e.attributes){if(!r.name.startsWith("aria-")||Ta.has(r.name)||i.has(r.name))continue;const o=i.size>0?[...i].join(", "):"none (only global ARIA attributes)";a.push({ruleId:"aria-allowed-attr",selector:m(e),html:u(e),impact:"critical",message:`ARIA attribute "${r.name}" is not allowed on role "${n}".`,context:`Attribute: ${r.name}="${r.value}", role: ${n}, allowed role-specific attributes: ${o}`})}}return a}},Ra=new Set(["base","col","colgroup","head","html","keygen","meta","param","script","source","style","template","title","track"]),L={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=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 Na(t){var e;const a=t.tagName.toLowerCase();if(Ra.has(a))return"none";if(a==="a"&&t.hasAttribute("href"))return L["a[href]"];if(a==="img"&&t.getAttribute("alt")==="")return L["img[alt='']"];if(a==="input"){const i=`input[type=${((e=t.getAttribute("type"))==null?void 0:e.toLowerCase())||"text"}]`;return i in L?L[i]:"none"}return L[a]||"any"}const Ma={id:"aria-allowed-role",wcag:["4.1.2"],level:"A",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.",prompt:"Consider implicit roles: header=banner, nav=navigation, main=main, footer=contentinfo, aside=complementary, article=article, section=region (when labeled). Explain if this role is redundant (matches implicit) or invalid (conflicts). Suggest removing it or restructuring.",run(t){var e;const a=[];for(const n of t.querySelectorAll("[role]")){if(b(n))continue;const i=(e=n.getAttribute("role"))==null?void 0:e.trim().toLowerCase();if(!i)continue;const r=K(n);if(r&&i===r)continue;const o=Na(n);o==="none"?a.push({ruleId:"aria-allowed-role",selector:m(n),html:u(n),impact:"minor",message:`Element <${n.tagName.toLowerCase()}> should not have an explicit role.`}):o!=="any"&&!o.has(i)&&a.push({ruleId:"aria-allowed-role",selector:m(n),html:u(n),impact:"minor",message:`Role "${i}" is not allowed on element <${n.tagName.toLowerCase()}>.`})}return a}},de={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"]]},$a=new Set(["doc-bibliography","doc-endnotes","grid","group","list","listbox","rowgroup","table","tablist","tree","treegrid"]),ue={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"]};function Ha(t,a){var o;const e=((o=t.getAttribute("aria-owns"))==null?void 0:o.split(/\s+/))||[],n=t.ownerDocument,i=new Set;let r=!1;for(const s of t.querySelectorAll("*")){if(b(s))continue;r=!0;const l=R(s);l&&i.add(l)}for(const s of e){const l=n.getElementById(s);if(l&&!b(l)){r=!0;const p=R(l);p&&i.add(p)}}if(!r)return"empty";for(const s of a)if(!s.some(l=>i.has(l)))return"fail";return"pass"}const Da={id:"aria-required-children",wcag:["1.3.1"],level:"A",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>).",prompt:"State which child role(s) are required and suggest adding elements with those roles, or using equivalent native HTML elements.",run(t){var e;const a=[];for(const n of t.querySelectorAll("[role]")){if(b(n))continue;const i=(e=n.getAttribute("role"))==null?void 0:e.trim().toLowerCase();if(!i||!(i in de)||n.getAttribute("aria-busy")==="true")continue;if(i==="combobox"){if(n.getAttribute("aria-expanded")!=="true")continue;const l=n.tagName.toLowerCase();if(l==="input"||l==="textarea")continue}const r=de[i],o=Ha(n,r);if(o==="pass"||o==="empty"&&$a.has(i))continue;const s=r.map(l=>l.join(" or ")).join(", ");a.push({ruleId:"aria-required-children",selector:m(n),html:u(n),impact:"critical",message:`Role "${i}" requires children with role: ${s}.`})}return a}},za={id:"aria-required-parent",wcag:["1.3.1"],level:"A",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>).",prompt:"State which parent role is required and suggest wrapping in an element with that role, or using equivalent native HTML structure.",run(t){var e;const a=[];for(const n of t.querySelectorAll("[role]")){if(b(n))continue;const i=(e=n.getAttribute("role"))==null?void 0:e.trim().toLowerCase();if(!i||!(i in ue))continue;const r=ue[i];let o=n.parentElement,s=!1;for(;o&&o!==t.documentElement;){const l=R(o);if(l&&r.includes(l)){s=!0;break}o=o.parentElement}s||a.push({ruleId:"aria-required-parent",selector:m(n),html:u(n),impact:"critical",message:`Role "${i}" must be contained within: ${r.join(", ")}.`})}return a}},me=["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(", ");function ja(t){let a=t;const e=t.ownerDocument,n=e.defaultView;for(;a&&a!==e.body;){if(a.style.display==="none"||a.style.visibility==="hidden")return!1;if(n){const i=n.getComputedStyle(a);if(i.display==="none"||i.visibility==="hidden")return!1}a=a.parentElement}return!0}const Wa={id:"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",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.",prompt:"Instruct to remove aria-hidden='true' from the body element.",skipAriaHidden:!1},Fa=S(Wa),Pa={id:"aria-hidden-focus",wcag:["4.1.2"],level:"A",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.",prompt:"This element can receive keyboard focus but is inside an aria-hidden region, making it invisible to screen readers. The context explains why it's focusable. Fix by either: (1) adding tabindex='-1' to remove it from tab order, (2) moving it outside the aria-hidden region, or (3) removing aria-hidden='true' from the ancestor if the content should be accessible.",run(t){const a=[];for(const e of t.querySelectorAll('[aria-hidden="true"]')){if(e===t.body)continue;const n=[...e.querySelectorAll(me)];e.matches(me)&&n.push(e);for(const i of n)if(i instanceof HTMLElement){const r=i.getAttribute("tabindex");if(r==="-1"||i.disabled||i instanceof HTMLInputElement&&i.type==="hidden"||!ja(i))continue;const o=i.tagName.toLowerCase();let s;r!==null?s=`has tabindex="${r}"`:o==="a"&&i.hasAttribute("href")?s="is a link with href":o==="button"?s="is a <button>":o==="input"?s=`is an <input type="${i.type}">`:o==="select"?s="is a <select>":o==="textarea"?s="is a <textarea>":o==="iframe"?s="is an <iframe>":s=`is a natively focusable <${o}>`;const l=i===e?i:i.closest('[aria-hidden="true"]');a.push({ruleId:"aria-hidden-focus",selector:m(i),html:u(i),impact:"serious",message:"Focusable element is inside an aria-hidden region.",context:`Focusable because: ${s}. aria-hidden ancestor: ${l?u(l):"unknown"}`})}}return a}},Oa={id:"aria-command-name",wcag:["4.1.2"],level:"A",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.",prompt:"Based on the element's content or context, suggest an aria-label describing what this command does.",run(t){var e;const a=[];for(const n of t.querySelectorAll('[role="button"], [role="link"], [role="menuitem"]')){if(b(n)||q(n)||n.getRootNode()instanceof ShadowRoot||n.tagName.toLowerCase()==="button"||n.tagName.toLowerCase()==="a")continue;if(!v(n)){const r=n.querySelector("img[alt]");if((e=r==null?void 0:r.getAttribute("alt"))!=null&&e.trim())continue;a.push({ruleId:"aria-command-name",selector:m(n),html:u(n),impact:"serious",message:"ARIA command has no accessible name."})}}return a}},Ba={id:"aria-input-field-name",wcag:["4.1.2"],level:"A",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.",prompt:"Based on the context, suggest an aria-label describing what data this input field accepts.",run(t){const a=[],e='[role="combobox"], [role="listbox"], [role="searchbox"], [role="slider"], [role="spinbutton"], [role="textbox"]';for(const n of t.querySelectorAll(e)){if(b(n)||q(n)||n.getRootNode()instanceof ShadowRoot||n.matches("input, select, textarea"))continue;v(n)||a.push({ruleId:"aria-input-field-name",selector:m(n),html:u(n),impact:"serious",message:"ARIA input field has no accessible name."})}return a}},Ua={id:"aria-toggle-field-name",wcag:["4.1.2"],level:"A",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.",prompt:"Based on the context, suggest an aria-label describing what option this toggle controls.",run(t){const a=[],e='[role="checkbox"], [role="switch"], [role="radio"], [role="menuitemcheckbox"], [role="menuitemradio"]';for(const n of t.querySelectorAll(e)){if(b(n)||q(n)||n.getRootNode()instanceof ShadowRoot||n.matches('input[type="checkbox"], input[type="radio"]'))continue;v(n)||a.push({ruleId:"aria-toggle-field-name",selector:m(n),html:u(n),impact:"serious",message:"ARIA toggle field has no accessible name."})}return a}},Va={id:"aria-meter-name",wcag:["4.1.2"],level:"A",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.",prompt:"Based on the context or value attributes, suggest an aria-label describing what this meter measures.",run(t){const a=[];for(const e of t.querySelectorAll('[role="meter"], meter')){if(b(e))continue;v(e)||a.push({ruleId:"aria-meter-name",selector:m(e),html:u(e),impact:"serious",message:"Meter has no accessible name."})}return a}},_a={id:"aria-progressbar-name",wcag:["4.1.2"],level:"A",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.",prompt:"Based on the context, suggest an aria-label describing what process this progressbar tracks.",run(t){const a=[];for(const e of t.querySelectorAll('[role="progressbar"], progress')){if(b(e))continue;v(e)||a.push({ruleId:"aria-progressbar-name",selector:m(e),html:u(e),impact:"serious",message:"Progressbar has no accessible name."})}return a}},Ga={id:"aria-dialog-name",wcag:["4.1.2"],level:"A",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.",prompt:"Suggest adding aria-labelledby pointing to the dialog's heading element, or an aria-label describing the dialog's purpose.",run(t){const a=[];for(const e of t.querySelectorAll('[role="dialog"], [role="alertdialog"], dialog')){if(b(e))continue;v(e)||a.push({ruleId:"aria-dialog-name",selector:m(e),html:u(e),impact:"serious",message:"Dialog has no accessible name."})}return a}},Ya={id:"aria-tooltip-name",wcag:["4.1.2"],level:"A",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.",prompt:"Add text content to the tooltip describing the information it provides, or add aria-label.",run(t){const a=[];for(const e of t.querySelectorAll('[role="tooltip"]')){if(b(e))continue;v(e)||a.push({ruleId:"aria-tooltip-name",selector:m(e),html:u(e),impact:"serious",message:"Tooltip has no accessible name."})}return a}},Xa={id:"aria-treeitem-name",wcag:["4.1.2"],level:"A",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.",prompt:"Add text content describing this tree item, or add aria-label.",run(t){const a=[];for(const e of t.querySelectorAll('[role="treeitem"]')){if(b(e))continue;v(e)||a.push({ruleId:"aria-treeitem-name",selector:m(e),html:u(e),impact:"serious",message:"Treeitem has no accessible name."})}return a}},Ka={id:"aria-prohibited-attr",wcag:["4.1.2"],level:"A",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.",prompt:"Identify the prohibited attribute and recommend removing it from this element.",run(t){return J(t).prohibitedAttr}},Ja=["a[href]","button:not([disabled])",'input:not([disabled]):not([type="hidden"])',"select:not([disabled])","textarea:not([disabled])",'[tabindex]:not([tabindex="-1"])'].join(", "),Qa=["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 pe(t){const a=[];t.matches(Ja)&&a.push("element is focusable");for(const e of Qa)if(t.hasAttribute(e)){a.push(`has ${e}`);break}return(t.hasAttribute("aria-label")||t.hasAttribute("aria-labelledby"))&&a.push("has accessible name"),a}const Za={id:"presentation-role-conflict",wcag:["4.1.2"],level:"A",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.",prompt:"Identify the conflict (focusable or ARIA attribute) and suggest either removing the presentation role or removing the conflicting attribute/focusability.",run(t){const a=[];for(const e of t.querySelectorAll('[role="presentation"], [role="none"]')){if(b(e))continue;const n=pe(e);n.length>0&&a.push({ruleId:"presentation-role-conflict",selector:m(e),html:u(e),impact:"serious",message:`Presentation role conflicts with: ${n.join(", ")}. The role will be ignored.`})}for(const e of t.querySelectorAll('img[alt=""]')){if(b(e)||e.hasAttribute("role"))continue;const n=pe(e);n.length>0&&a.push({ruleId:"presentation-role-conflict",selector:m(e),html:u(e),impact:"serious",message:`Element with implicit presentation role (alt="") conflicts with: ${n.join(", ")}. The decorative role will be ignored.`})}return a}},en={id:"summary-name",wcag:["4.1.2"],level:"A",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.",prompt:"Based on the surrounding context or details content, suggest text to add inside the <summary> element.",run(t){const a=[];for(const e of t.querySelectorAll("details > summary:first-of-type")){if(b(e))continue;v(e)||a.push({ruleId:"summary-name",selector:m(e),html:u(e),impact:"serious",message:"<summary> element has no accessible name. Add descriptive text."})}return a}};function tn(t){var i,r;const a=[],e=t.getAttribute("href");e&&a.push(`href: ${e}`);const n=t.parentElement;if(n){const o=n.closest("h1, h2, h3, h4, h5, h6");if((i=o==null?void 0:o.textContent)!=null&&i.trim())a.push(`Nearby heading: ${o.textContent.trim().slice(0,80)}`);else{const s=(r=n.textContent)==null?void 0:r.trim().slice(0,100);s&&a.push(`Parent text: ${s}`)}}return a.length>0?a.join(`
4
- `):void 0}const an={id:"link-name",wcag:["2.4.4","4.1.2"],level:"A",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.",prompt:"Based on the href or surrounding context, suggest descriptive link text or an aria-label.",run(t){const a=[];for(const e of t.querySelectorAll('a[href], area[href], [role="link"]')){if(b(e)||q(e)||e.getRootNode()instanceof ShadowRoot)continue;v(e)||a.push({ruleId:"link-name",selector:m(e),html:u(e),impact:"serious",message:"Link has no discernible text.",context:tn(e)})}return a}},nn={id:"skip-link",wcag:["2.4.1"],level:"A",tags:["best-practice"],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.",prompt:"A skip link is a single <a href='#main'>Skip to main content</a> as the first element in <body>. It can be visually hidden with CSS until focused. Explain this simple pattern.",run(t){const a=[],e=t.querySelectorAll('a[href^="#"]');for(const n of e){const i=n.getAttribute("href");if(!i||i==="#")continue;const r=A(n).toLowerCase();if(!(r.includes("skip")||r.includes("jump")||r.includes("main content")||r.includes("navigation")))continue;const s=i.slice(1);t.getElementById(s)||a.push({ruleId:"skip-link",selector:m(n),html:u(n),impact:"moderate",message:`Skip link points to "#${s}" which does not exist on the page.`})}return a}},rn=new Set(["block","flex","grid","table","table-cell","list-item","flow-root"]),on=new Set(["inline","inline-block","inline-flex","inline-grid"]);function sn(t){let a=t.parentElement;for(;a;){const e=w(a).display;if(rn.has(e))return ln(a)?a:null;a=a.parentElement}return null}function ln(t){const a=t.ownerDocument.createTreeWalker(t,NodeFilter.SHOW_TEXT);let e="",n;for(;n=a.nextNode();){if(!n.data.trim())continue;let i=n.parentElement,r=!1;for(;i&&i!==t;){if(i.tagName==="A"){r=!0;break}i=i.parentElement}r||(e+=n.data)}return new RegExp("\\p{L}{2,}","u").test(e)}function cn(t,a){const e=t.ownerDocument.createTreeWalker(t,NodeFilter.SHOW_TEXT);let n;for(;n=e.nextNode();){if(!n.data.trim())continue;let i=n.parentElement,r=!1,o=i;for(;o&&o!==t;){if(o.tagName==="A"){r=!0;break}o=o.parentElement}if(!r&&i)return $(w(i).color)}return null}function dn(t,a,e){const n=e.textDecorationLine||e.textDecoration||"",i=a.textDecorationLine||a.textDecoration||"";if((i.includes("underline")||i.includes("line-through"))&&i!==n)return!0;const r=parseFloat(a.borderBottomWidth)||0,o=a.borderBottomStyle||"";if(r>0&&o!=="none"&&o!=="hidden")return!0;const s=parseFloat(a.outlineWidth)||0,l=a.outlineStyle||"";if(s>0&&l!=="none")return!0;const p=a.backgroundImage||"";if(p&&p!=="none"&&p!=="initial")return!0;const c=F(e.fontWeight),d=F(a.fontWeight);if(Math.abs(d-c)>=300||a.fontStyle!==e.fontStyle)return!0;const h=parseFloat(a.fontSize)||16,g=parseFloat(e.fontSize)||16;if(g>0&&h/g>=1.2)return!0;for(const f of t.querySelectorAll("*")){const y=w(f),x=y.textDecorationLine||y.textDecoration||"";if((x.includes("underline")||x.includes("line-through"))&&x!==n||Math.abs(F(y.fontWeight)-c)>=300)return!0}return!1}function F(t){return t==="bold"?700:t==="normal"?400:parseInt(t)||400}function he(t,a,e){return"#"+[t,a,e].map(n=>n.toString(16).padStart(2,"0")).join("")}const un={id:"link-in-text-block",wcag:["1.4.1"],level:"A",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.",prompt:"Explain how to make this link visually distinguishable without relying on color alone.",run(t){const a=[];for(const e of t.querySelectorAll("a[href]")){if(b(e)||!A(e).trim()||e.closest('nav, header, footer, [role="navigation"], [role="banner"], [role="contentinfo"]'))continue;const n=w(e),i=n.display||"inline";if(!on.has(i))continue;const r=sn(e);if(!r)continue;const o=w(r);if(dn(e,n,o))continue;const s=$(n.color),l=cn(r);if(!s||!l)continue;const p=N(...s),c=N(...l),d=Ie(p,c);if(d<1.1||d>=3)continue;const h=he(...s),g=he(...l),f=`link color: ${h} rgb(${s.join(", ")}), surrounding text: ${g} rgb(${l.join(", ")}), ratio: ${d.toFixed(2)}:1`;a.push({ruleId:"link-in-text-block",selector:m(e),html:u(e),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:f})}return a}},mn={id:"html-has-lang",wcag:["3.1.1"],level:"A",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 (e.g., lang='en' for English, lang='es' for Spanish).",prompt:`The page is missing a lang attribute on <html>. Use the text sample in context to determine the primary language and suggest the correct 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). Add lang to the <html> element: <html lang="...">.`,run(t){var e,n;const a=t.documentElement;if(a.tagName.toLowerCase()!=="html")return[];if(!t.doctype&&t.body){const i=t.body.children;if(i.length>0&&Array.from(i).every(r=>r.tagName.toLowerCase()==="svg"||r.tagName.toLowerCase()==="math"))return[]}if(!((e=a.getAttribute("lang"))!=null&&e.trim())){let i;if(t.body){const r=((n=t.body.textContent)==null?void 0:n.trim().replace(/\s+/g," "))||"";r&&(i=r.slice(0,200))}return[{ruleId:"html-has-lang",selector:m(a),html:u(a),impact:"serious",message:"<html> element missing lang attribute.",context:i?`Page text sample: "${i}"`:void 0}]}return[]}},pn=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(" ")),hn=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(" ")),bn=/^[a-z]{2,8}(-[a-z0-9]{1,8})*$/i;function Ee(t){if(!bn.test(t))return!1;const a=t.split("-")[0].toLowerCase();return a.length===2?pn.has(a):a.length===3?!hn.has(a):!1}const gn={id:"html-lang-valid",wcag:["3.1.1"],level:"A",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.",prompt:"Suggest the correct BCP 47 language tag based on the invalid value provided.",run(t){var e;const a=(e=t.documentElement.getAttribute("lang"))==null?void 0:e.trim();return a&&!Ee(a)?[{ruleId:"html-lang-valid",selector:"html",html:u(t.documentElement),impact:"serious",message:`Invalid lang attribute value "${a}".`}]:[]}};function be(t){var n;const a=t.ownerDocument.createTreeWalker(t,NodeFilter.SHOW_TEXT);let e;for(;e=a.nextNode();){if(!e.data.trim())continue;const i=e.parentElement;if(!i||i instanceof HTMLElement&&(i.hidden||i.style.display==="none"))continue;let r=i,o=!1;for(;r&&r!==t;){if(r.hasAttribute("lang")){o=!0;break}r=r.parentElement}if(!o)return!0}for(const i of t.querySelectorAll("img[alt]")){if(!((n=i.getAttribute("alt"))==null?void 0:n.trim()))continue;let o=i.parentElement,s=!1;for(;o&&o!==t;){if(o.hasAttribute("lang")){s=!0;break}o=o.parentElement}if(!s)return!0}return!1}const fn={id:"valid-lang",wcag:["3.1.2"],level:"AA",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.",prompt:"Identify the content's language and suggest the correct BCP 47 tag.",run(t){const a=[];for(const e of t.querySelectorAll("[lang]")){if(b(e)||e===t.documentElement)continue;const n=e.getAttribute("lang"),i=n==null?void 0:n.trim();if(n&&!i){be(e)&&a.push({ruleId:"valid-lang",selector:m(e),html:u(e),impact:"serious",message:"Empty lang attribute value."});continue}i&&be(e)&&(Ee(i)||a.push({ruleId:"valid-lang",selector:m(e),html:u(e),impact:"serious",message:`Invalid lang attribute value "${i}".`}))}return a}},vn={id:"html-xml-lang-mismatch",wcag:["3.1.1"],level:"A",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.",prompt:"Explain whether to remove xml:lang or align it with the lang value.",run(t){var i,r;const a=t.documentElement,e=(i=a.getAttribute("lang"))==null?void 0:i.trim().toLowerCase(),n=(r=a.getAttribute("xml:lang"))==null?void 0:r.trim().toLowerCase();if(e&&n){const o=e.split("-")[0],s=n.split("-")[0];if(o!==s)return[{ruleId:"html-xml-lang-mismatch",selector:"html",html:u(a),impact:"moderate",message:`lang="${e}" and xml:lang="${n}" do not match.`}]}return[]}},yn={id:"td-headers-attr",wcag:["1.3.1"],level:"A",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.",prompt:"Identify the invalid header ID reference and suggest the correct ID or how to fix it.",run(t){const a=[];for(const e of t.querySelectorAll("td[headers]")){if(b(e))continue;const n=e.closest("table");if(!n)continue;const i=e.getAttribute("id"),r=e.getAttribute("headers").split(/\s+/);for(const o of r){if(o===i){a.push({ruleId:"td-headers-attr",selector:m(e),html:u(e),impact:"serious",message:`Headers attribute references the cell itself ("${o}").`});break}if(!n.querySelector(`th#${CSS.escape(o)}, td#${CSS.escape(o)}`)){a.push({ruleId:"td-headers-attr",selector:m(e),html:u(e),impact:"serious",message:`Headers attribute references non-existent ID "${o}".`});break}}}return a}},wn={id:"th-has-data-cells",wcag:["1.3.1"],level:"A",description:"Table headers should be associated with data cells.",guidance:"A table with header cells (th) but no data cells (td) is likely a misuse of table markup for layout or has missing content. Either add data cells that the headers describe, or use appropriate non-table markup if this is not tabular data.",prompt:"Explain whether this table needs data cells or if non-table layout would be more appropriate.",run(t){const a=[];for(const e of t.querySelectorAll("table")){if(b(e)||e.getAttribute("role")==="presentation"||e.getAttribute("role")==="none")continue;const n=e.querySelectorAll("th"),i=e.querySelectorAll("td");n.length>0&&i.length===0&&a.push({ruleId:"th-has-data-cells",selector:m(e),html:u(e),impact:"serious",message:"Table has header cells but no data cells."})}return a}},An={id:"td-has-header",wcag:["1.3.1"],level:"A",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.",prompt:"Explain whether to use scope attributes on headers or headers attribute on this cell.",run(t){var e,n;const a=[];for(const i of t.querySelectorAll("table")){if(b(i)||i.getAttribute("role")==="presentation"||i.getAttribute("role")==="none")continue;const r=i.querySelectorAll("tr"),o=r.length;let s=0;for(const d of r){const h=d.querySelectorAll("td, th");let g=0;for(const f of h)g+=parseInt(f.getAttribute("colspan")||"1",10);s=Math.max(s,g)}if(o<=3&&s<=3)continue;const l=i.querySelector("th")!==null,p=i.querySelector("th[scope]")!==null,c=i.querySelector("td[headers]")!==null;if(l)for(const d of i.querySelectorAll("td")){if(b(d)||d.hasAttribute("headers"))continue;const h=d.closest("tr");if(!h)continue;const g=h.querySelector("th")!==null,f=Array.from(h.children).indexOf(d);let y=!1;const x=i.querySelector("thead");if(x){const k=x.querySelector("tr");k&&((e=k.querySelectorAll("th, td")[f])==null?void 0:e.tagName.toLowerCase())==="th"&&(y=!0)}if(!y){const k=i.querySelector("tbody > tr, tr");k&&((n=k.querySelectorAll("th, td")[f])==null?void 0:n.tagName.toLowerCase())==="th"&&(y=!0)}if(!g&&!y&&!p&&!c){a.push({ruleId:"td-has-header",selector:m(d),html:u(d),impact:"serious",message:"Data cell has no associated header. Add th elements with scope, or headers attribute."});break}}}return a}},xn={id:"scope-attr-valid",wcag:["1.3.1"],level:"A",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.",prompt:"Explain which scope value (row, col, rowgroup, colgroup) is appropriate for this header.",run(t){var n;const a=[],e=new Set(["row","col","rowgroup","colgroup"]);for(const i of t.querySelectorAll("th[scope]")){if(b(i))continue;const r=(n=i.getAttribute("scope"))==null?void 0:n.toLowerCase();r&&!e.has(r)&&a.push({ruleId:"scope-attr-valid",selector:m(i),html:u(i),impact:"moderate",message:`Invalid scope value "${r}". Use row, col, rowgroup, or colgroup.`})}return a}},kn={id:"empty-table-header",wcag:[],level:"A",tags:["best-practice"],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.",prompt:"Suggest header text based on the column/row content, or explain if this should be a td instead.",run(t){const a=[];for(const e of t.querySelectorAll("th")){if(b(e))continue;const n=e.closest("table");(n==null?void 0:n.getAttribute("role"))==="presentation"||(n==null?void 0:n.getAttribute("role"))==="none"||v(e)||a.push({ruleId:"empty-table-header",selector:m(e),html:u(e),impact:"minor",message:"Table header cell is empty. Add text or use aria-label."})}return a}},P=["aria-labelledby","aria-describedby","aria-controls","aria-owns","aria-flowto"],Sn={id:"duplicate-id-aria",wcag:["4.1.2"],level:"A",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.",prompt:"Identify which attribute references this ID and suggest a unique replacement.",run(t){const a=[],e=new Set;for(const i of t.querySelectorAll("[aria-labelledby], [aria-describedby], [aria-controls], [aria-owns], [aria-flowto]"))for(const r of P){const o=i.getAttribute(r);o&&o.split(/\s+/).forEach(s=>e.add(s))}for(const i of t.querySelectorAll("label[for]")){const r=i.getAttribute("for");r&&e.add(r)}const n=new Map;for(const i of t.querySelectorAll("[id]"))e.has(i.id)&&(i instanceof HTMLElement&&(i.style.display==="none"||i.style.visibility==="hidden"||i.hidden)||n.set(i.id,(n.get(i.id)??0)+1));for(const[i,r]of n){if(r<=1)continue;const o=t.querySelectorAll(`#${CSS.escape(i)}`),s=t.querySelector(P.map(c=>`[${c}~="${CSS.escape(i)}"]`).join(", ")),l=t.querySelector(`label[for="${CSS.escape(i)}"]`);let p;if(s){const c=P.find(d=>{var h;return(h=s.getAttribute(d))==null?void 0:h.split(/\s+/).includes(i)});c&&(p=c)}else l&&(p="label[for]");a.push({ruleId:"duplicate-id-aria",selector:m(o[1]),html:u(o[1]),impact:"critical",message:`Duplicate ID "${i}" referenced by ${p??"an accessibility attribute"}.`,context:`First element: ${u(o[0])}${p?`
5
- Referenced by: ${p}`:""}`})}return a}},In={id:"video-caption",wcag:["1.2.2"],level:"A",description:"Video elements must have captions via <track kind='captions'>.",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.",prompt:"Explain how to add a captions track element to this video.",run(t){const a=[];for(const e of t.querySelectorAll("video")){if(b(e)||e.hasAttribute("muted")||e.hasAttribute("autoplay"))continue;e.querySelector('track[kind="captions"], track[kind="subtitles"]')||a.push({ruleId:"video-caption",selector:m(e),html:u(e),impact:"critical",message:"Video element has no captions track."})}return a}},qn={id:"audio-caption",wcag:["1.2.1"],level:"A",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.",prompt:"Explain options for providing a text alternative: transcript link or aria-describedby.",run(t){const a=[];for(const e of t.querySelectorAll("audio")){if(b(e)||e.querySelector('track[kind="captions"], track[kind="descriptions"]')||e.hasAttribute("aria-describedby"))continue;const i=e.parentElement;i&&i.querySelector('a[href*="transcript"], a[href*="text"]')||a.push({ruleId:"audio-caption",selector:m(e),html:u(e),impact:"critical",message:"Audio element has no transcript or text alternative. Add a transcript or track element."})}return a}},Ln=new Set(["SCRIPT","STYLE","NOSCRIPT","TEMPLATE","IFRAME","OBJECT","EMBED","SVG","CANVAS","VIDEO","AUDIO","IMG","BR","HR"]);function ge([t,a,e]){return"#"+[t,a,e].map(n=>n.toString(16).padStart(2,"0")).join("")}function En(t){return t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement||t instanceof HTMLSelectElement||t instanceof HTMLButtonElement?t.disabled:!!(t.closest("fieldset[disabled]")||t.getAttribute("aria-disabled")==="true")}function Tn(t,a){if(t.tagName!=="LABEL")return!1;const e=t,n=e.htmlFor;if(n){const o=a.getElementById(n);if(o&&(o.disabled||o.getAttribute("aria-disabled")==="true"))return!0}const i=e.querySelector("input, select, textarea, button");if(i&&(i.disabled||i.getAttribute("aria-disabled")==="true"))return!0;const r=e.id;return!!(r&&a.querySelector(`[aria-labelledby~="${r}"][aria-disabled="true"]`))}function Cn(t){const a=t.clip;if(a&&a.startsWith("rect(")){const n=a.match(/[\d.]+/g);if(!n||n.every(i=>parseFloat(i)===0))return!0}const e=t.clipPath;if(e==="inset(50%)"||e==="inset(100%)")return!0;if(t.overflow==="hidden"&&t.position==="absolute"){const n=parseFloat(t.width),i=parseFloat(t.height);if(n<=1&&i<=1)return!0}return!1}function Rn(t){if(b(t))return!0;let a=t;for(;a;){const e=w(a);if(e.display==="none"||e.visibility==="hidden"||Cn(e))return!0;a=a.parentElement}return!1}function Nn(t){let a=1,e=t;for(;e;){const n=w(e),i=parseFloat(n.opacity);isNaN(i)||(a*=i),e=e.parentElement}return a}const Mn={grayscale:0,blur:0,"hue-rotate":0,invert:0,sepia:0,brightness:1,contrast:1,saturate:1,opacity:1};function $n(t){const a=parseFloat(t);return isNaN(a)?NaN:t.trim().endsWith("%")?a/100:a}const fe=/([a-z-]+)\(([^)]*)\)/g;function ve(t){let a,e=!1;for(fe.lastIndex=0;a=fe.exec(t);){e=!0;const n=Mn[a[1]];if(n===void 0||$n(a[2])!==n)return!1}return e}function Hn(t){let a=t;for(;a;){const e=w(a),n=e.filter;if(n&&n!=="none"&&n!=="initial"&&!ve(n))return!0;const i=e.mixBlendMode;if(i&&i!=="normal"&&i!=="initial")return!0;const r=e.backdropFilter;if(r&&r!=="none"&&r!=="initial"&&!ve(r))return!0;a=a.parentElement}return!1}function Dn(t){return t.closest("select")!==null}const zn={id:"color-contrast",wcag:["1.4.3"],level:"AA",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.",prompt:"Suggest changing the text or background color to meet the minimum contrast ratio.",run(t){const a=[],e=t.body;if(!e)return[];const n=t.createTreeWalker(e,NodeFilter.SHOW_TEXT),i=new Set;let r;for(;r=n.nextNode();){if(!r.textContent||!r.textContent.trim())continue;const o=r.parentElement;if(!o||i.has(o)||(i.add(o),Ln.has(o.tagName)))continue;const s=o.tagName;if(s==="BODY"||s==="HTML"||Dn(o)||En(o)||Tn(o,t)||Rn(o))continue;const l=w(o);if(parseFloat(l.opacity)===0||Nn(o)<.1)continue;const p=l.textShadow;if(p&&p!=="none"&&p!=="initial"||Hn(o))continue;const c=$(l.color);if(!c)continue;const d=l.color.match(/rgba\(.+?,\s*([\d.]+)\s*\)/)||l.color.match(/rgba?\(.+?\/\s*([\d.]+%?)\s*\)/);if(d&&(d[1].endsWith("%")?parseFloat(d[1])/100:parseFloat(d[1]))===0||Ge(o))continue;const h=Ue(o);if(!h)continue;const g=N(c[0],c[1],c[2]),f=N(h[0],h[1],h[2]),y=Ie(g,f),x=Ke(o)?3:4.5;if(y<x){const k=Math.round(y*100)/100,z=ge(c),Ce=ge(h);a.push({ruleId:"color-contrast",selector:m(o),html:u(o),impact:"serious",message:`Insufficient color contrast ratio of ${k}:1 (required ${x}:1).`,context:`foreground: ${z} rgb(${c.join(", ")}), background: ${Ce} rgb(${h.join(", ")}), ratio: ${k}:1, required: ${x}:1`})}}return a}},Q=[la,ca,da,ua,ma,ha,ba,fa,ya,Ze,tt,at,nt,rt,ot,lt,ct,ht,yt,wt,At,xt,Tt,Ct,Rt,Mt,Ht,Ot,Bt,Ut,Vt,pa,wa,_t,Gt,Yt,Xt,Kt,Jt,Qt,Zt,ea,ta,na,sa,ia,oa,Aa,xa,ka,Ia,Ca,Ma,Da,za,Fa,Pa,Oa,Ba,Ua,Va,_a,Ga,Ya,Xa,Ka,Za,La,en,an,nn,un,mn,gn,fn,vn,yn,wn,An,xn,kn,Sn,In,qn,zn];let Z=[],Te=new Set,M,C;function jn(t){t.additionalRules&&(Z=t.additionalRules),t.disabledRules&&(Te=new Set(t.disabledRules)),"locale"in t&&(M=t.locale||void 0),C=void 0}function D(){if(C)return C;const a=Q.filter(e=>!Te.has(e.id)).concat(Z);return M?(C=Qe(a,M),C):a}function Wn(t){ee();const a=D(),e=[];let n=0;return{processChunk(i){const r=performance.now();for(;n<a.length;){try{e.push(...a[n].run(t))}catch{}if(n++,performance.now()-r>=i)break}return n<a.length},getViolations(){return e}}}function ee(){Ae(),ye(),Re(),Se(),ke(),$e()}function Fn(t){var n;ee();const a=D(),e=[];for(const i of a)try{e.push(...i.run(t))}catch{}return{url:((n=t.location)==null?void 0:n.href)??"",timestamp:Date.now(),violations:e,ruleCount:a.length}}const Pn=new Map(Q.map(t=>[t.id,t]));function On(t){if(M)return D().find(n=>n.id===t);const a=Pn.get(t);return a||Z.find(e=>e.id===t)}const Bn={"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')."},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.'},"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 exactly one h1 that describes the main content, typically matching or similar to the page title."},"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'."},"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."},"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."},"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."},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."},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."},"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."},"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."},"input-image-alt":{description:'Image inputs (<input type="image">) must have alternate text via alt, aria-label, or aria-labelledby. The text should describe the button action, not the image.',guidance:"Image buttons (<input type='image'>) must have alternate text via alt, aria-label, or aria-labelledby. The text should describe the button action, not the image."},"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."},"image-alt-redundant-words":{description:"Image alt text should not contain words like 'image', 'photo', or 'picture' — 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'."},"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."},"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."},"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."},"server-side-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."},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."},"form-field-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."},"select-name":{description:"Select elements must have a programmatically associated label via <label>, aria-label, or aria-labelledby.",guidance:"Select dropdowns need labels so users understand what choice they're making. Use a <label> element with a for attribute matching the select's id, or wrap the select in a <label>. For selects without visible labels, use aria-label. The first <option> is not a substitute for a proper label."},"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."},"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."},"label-content-name-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."},"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."},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."},"focus-order-semantics":{description:"Elements that receive keyboard focus must have an appropriate role so assistive technologies can convey their purpose. Non-interactive elements with tabindex='0' need a valid interactive ARIA role.",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."},"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."},"scrollable-region-focusable":{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."},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."},"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."},"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."},"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."},"landmark-one-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."},"landmark-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."},"landmark-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."},"landmark-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."},"landmark-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."},"landmark-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."},"landmark-main-is-top-level":{description:"Main landmark should not be nested within another landmark.",guidance:"The main landmark must be a top-level landmark since it represents the primary content of the page. Do not nest <main> or role='main' inside article, aside, nav, or section elements."},"landmark-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."},"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')."},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."},list:{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, restructure your CSS to style the <li> elements directly."},listitem:{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>). Outside of these containers, assistive technologies cannot convey the list relationship. Wrap <li> elements in the appropriate list container."},dlitem:{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."},"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 restructure using proper definition list markup."},"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."},"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+)."},"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."},"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."},"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."},"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."},"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>)."},"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>)."},"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."},"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."},"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."},"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."},"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."},"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."},"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."},"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."},"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."},"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."},"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."},"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."},"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."},"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."},"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."},"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."},"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."},"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 (e.g., lang='en' for English, lang='es' for Spanish)."},"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."},"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."},"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."},"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."},"th-has-data-cells":{description:"Table headers should be associated with data cells.",guidance:"A table with header cells (th) but no data cells (td) is likely a misuse of table markup for layout or has missing content. Either add data cells that the headers describe, or use appropriate non-table markup if this is not tabular data."},"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."},"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."},"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."},"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."},"video-caption":{description:"Video elements must have captions via <track kind='captions'>.",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."},"audio-caption":{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."},"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."}},Un={"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')."},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.'},"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."},"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'."},"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."},"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."},"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."},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."},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."},"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."},"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."},"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."},"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."},"image-alt-redundant-words":{description:"El texto alternativo de la imagen no debe contener palabras como 'imagen', 'foto' o 'fotografía': 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'."},"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."},"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."},"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."},"server-side-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."},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."},"form-field-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."},"select-name":{description:"Los elementos select deben tener una etiqueta asociada programáticamente mediante <label>, aria-label o aria-labelledby.",guidance:"Los menús desplegables select necesitan etiquetas para que los usuarios comprendan qué elección están haciendo. Use un elemento <label> con un atributo for que coincida con el id del select, o envuelva el select en un <label>. Para selects sin etiquetas visibles, use aria-label. La primera <option> no es un sustituto de una etiqueta adecuada."},"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."},"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."},"label-content-name-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."},"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."},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."},"focus-order-semantics":{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."},"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."},"scrollable-region-focusable":{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."},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."},"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."},"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."},"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."},"landmark-one-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."},"landmark-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."},"landmark-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."},"landmark-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."},"landmark-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."},"landmark-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."},"landmark-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."},"landmark-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."},"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')."},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."},list:{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, reestructure su CSS para estilizar los elementos <li> directamente."},listitem:{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."},dlitem:{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."},"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 reestructure usando el marcado adecuado de lista de definiciones."},"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."},"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+)."},"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."},"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."},"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."},"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."},"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>)."},"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>)."},"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."},"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."},"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."},"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."},"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."},"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."},"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."},"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."},"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."},"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."},"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."},"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."},"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."},"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."},"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."},"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."},"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."},"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 (por ejemplo, lang='en' para inglés, lang='es' para español)."},"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."},"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."},"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."},"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."},"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."},"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."},"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."},"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."},"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."},"video-caption":{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."},"audio-caption":{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."},"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."}};exports.clearAllCaches=ee;exports.clearAriaAttrAuditCache=ke;exports.clearAriaHiddenCache=Ae;exports.clearColorCaches=Se;exports.clearComputedRoleCache=ye;exports.compileDeclarativeRule=S;exports.configureRules=jn;exports.createChunkedAudit=Wn;exports.getAccessibleName=v;exports.getAccessibleTextContent=A;exports.getActiveRules=D;exports.getComputedRole=R;exports.getHtmlSnippet=u;exports.getImplicitRole=K;exports.getRuleById=On;exports.getSelector=m;exports.isAriaHidden=b;exports.isValidRole=we;exports.localeEn=Bn;exports.localeEs=Un;exports.querySelectorShadowAware=je;exports.registerLocale=Je;exports.rules=Q;exports.runAudit=Fn;exports.validateDeclarativeRule=dt;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});let B=new WeakMap;function xe(){B=new WeakMap}function Q(t){var n;const a=t.tagName.toLowerCase(),e=(n=t.getAttribute("type"))==null?void 0:n.toLowerCase();switch(a){case"a":return t.hasAttribute("href")?"link":null;case"area":return t.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 t.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 t.closest("article, aside, main, nav, section")?null:"banner";case"hr":return"separator";case"img":return t.getAttribute("alt")===""?"presentation":"img";case"input":switch(e){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 t.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 t.hasAttribute("aria-label")||t.hasAttribute("aria-labelledby")?"region":null;case"select":return t.hasAttribute("multiple")||t.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 N(t){var i;const a=B.get(t);if(a!==void 0)return a;const n=((i=t.getAttribute("role"))==null?void 0:i.trim().toLowerCase())||null||Q(t);return B.set(t,n),n}let O=new WeakMap;function He(){O=new WeakMap}function v(t){const a=O.get(t);if(a!==void 0)return a;const e=$e(t);return O.set(t,e),e}function $e(t){var o,r,s,l,p;const a=t.getAttribute("aria-labelledby");if(a){const c=a.split(/\s+/).map(d=>{const h=t.ownerDocument.getElementById(d);return h?A(h).trim():""}).filter(Boolean);if(c.length)return c.join(" ")}const e=(o=t.getAttribute("aria-label"))==null?void 0:o.trim();if(e)return e;if(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement||t instanceof HTMLSelectElement){if(t.id){const h=t.ownerDocument.querySelector(`label[for="${CSS.escape(t.id)}"]`),g=h?A(h).trim():"";if(g)return g}const c=t.closest("label"),d=c?A(c).trim():"";if(d)return d}const n=(r=t.getAttribute("title"))==null?void 0:r.trim();if(n)return n;if(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement){const c=(s=t.getAttribute("placeholder"))==null?void 0:s.trim();if(c)return c}const i=t.tagName.toLowerCase();if(i==="fieldset"){const c=t.querySelector(":scope > legend");if(c){const d=A(c).trim();if(d)return d}}if(i==="table"){const c=t.querySelector(":scope > caption");if(c){const d=A(c).trim();if(d)return d}}if(!(t instanceof HTMLInputElement)){const c=A(t).trim();if(c)return c}return t instanceof HTMLImageElement||t instanceof HTMLAreaElement?((l=t.alt)==null?void 0:l.trim())??"":t instanceof HTMLInputElement&&t.type==="image"?((p=t.alt)==null?void 0:p.trim())??"":""}const Pe=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 ke(t){const a=t.trim().toLowerCase().replace(/[\u201C\u201D\u2018\u2019\u00AB\u00BB]/g,"");return Pe.has(a)}function q(t){let a=t;for(;a;){if(Ie(a))return!0;a=a.parentElement}return!1}let V=new WeakMap;function Se(){V=new WeakMap}function b(t){const a=V.get(t);if(a!==void 0)return a;let e;return t.getAttribute("aria-hidden")==="true"||t instanceof HTMLElement&&(t.hidden||t.style.display==="none")?e=!0:t.parentElement?e=b(t.parentElement):e=!1,V.set(t,e),e}function Ie(t){if(t.getAttribute("aria-hidden")==="true"||t instanceof HTMLElement&&t.hidden)return!0;if(typeof getComputedStyle=="function"){const a=getComputedStyle(t);if(a.display==="none"||a.visibility==="hidden")return!0}else if(t instanceof HTMLElement&&t.style.display==="none")return!0;return!1}function A(t){var e,n,i,o,r;let a="";for(const s of t.childNodes)if(s.nodeType===3)a+=s.textContent??"";else if(s.nodeType===1){const l=s;if(!Ie(l)){const p=(e=l.tagName)==null?void 0:e.toLowerCase();if(p==="img"||p==="area"){const c=l.getAttribute("aria-labelledby");if(c){const d=c.split(/\s+/).map(h=>{var g,f;return((f=(g=l.ownerDocument.getElementById(h))==null?void 0:g.textContent)==null?void 0:f.trim())??""}).filter(Boolean);if(d.length){a+=d.join(" ");continue}}a+=((n=l.getAttribute("aria-label"))==null?void 0:n.trim())??l.getAttribute("alt")??((i=l.getAttribute("title"))==null?void 0:i.trim())??""}else if(p==="svg"){const c=(o=l.getAttribute("aria-label"))==null?void 0:o.trim();if(c)a+=c;else{const d=l.querySelector("title");d&&(a+=d.textContent??"")}}else(r=l.getAttribute("aria-label"))!=null&&r.trim()?a+=l.getAttribute("aria-label").trim():a+=A(l)}}return a}let _=new WeakMap;function ze(){_=new WeakMap}function Fe(t){return t.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}const je=["data-testid","data-test-id","data-cy","data-id","name","href","for","aria-label"];function We(t){const a=t.tagName.toLowerCase();for(const n of je){const i=t.getAttribute(n);if(i!=null&&i.length>0&&i.length<100)return`${a}[${n}="${Fe(i)}"]`}const e=t.parentElement;if(e){let n=0,i=0;for(let o=0;o<e.children.length;o++)e.children[o].tagName===t.tagName&&(n++,e.children[o]===t&&(i=n));if(n>1)return`${a}:nth-of-type(${i})`}return a}function F(t){if(t.id)return`#${CSS.escape(t.id)}`;const a=t.getRootNode(),e=a instanceof ShadowRoot?null:a.documentElement,n=[];let i=t;for(;i&&i!==e;){if(i!==t&&i.id){n.unshift(`#${CSS.escape(i.id)}`);break}if(n.unshift(We(i)),n.length>=2){const o=n.join(" > ");try{const r=a.querySelectorAll(o);if(r.length===1&&r[0]===t)return o}catch{}}i=i.parentElement}return n.join(" > ")}function m(t){var o;const a=_.get(t);if(a!==void 0)return a;const e=[];let n=t;for(;n;){const r=n.getRootNode();if(r instanceof ShadowRoot)e.unshift({selector:F(n),delimiter:" >>> "}),n=r.host;else{const s=(o=r.defaultView)==null?void 0:o.frameElement;if(s)e.unshift({selector:F(n),delimiter:" >>>iframe> "}),n=s;else{e.unshift({selector:F(n),delimiter:""});break}}}const i=e.map((r,s)=>(s===0?"":r.delimiter)+r.selector).join("");return _.set(t,i),i}function Ue(t){const a=[],e=[];let n=t;for(;n;){const o=n.indexOf(" >>>iframe> "),r=n.indexOf(" >>> ");if(o!==-1&&(r===-1||o<=r))a.push(n.slice(0,o).trim()),e.push("iframe"),n=n.slice(o+12);else if(r!==-1)a.push(n.slice(0,r).trim()),e.push("shadow"),n=n.slice(r+5);else{a.push(n.trim());break}}let i=document;for(let o=0;o<a.length;o++){const r=i.querySelector(a[o]);if(!r)return null;if(o<a.length-1)if(e[o]==="iframe"){const s=r.contentDocument;if(!s)return null;i=s}else{const s=r.shadowRoot;if(!s)return null;i=s}else return r}return null}function u(t){const a=t.outerHTML;return a.length>200?a.slice(0,200)+"...":a}const Be=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"]),ie=new Set(["aria-atomic","aria-busy","aria-disabled","aria-grabbed","aria-hidden","aria-modal","aria-multiline","aria-multiselectable","aria-readonly","aria-required"]),oe=new Set(["aria-checked","aria-pressed"]),Oe=new Set(["aria-colcount","aria-colindex","aria-colspan","aria-level","aria-posinset","aria-rowcount","aria-rowindex","aria-rowspan","aria-setsize"]),Ve=new Set(["aria-valuemax","aria-valuemin","aria-valuenow"]),re={"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"])},se=new Set(["caption","code","deletion","emphasis","generic","insertion","mark","none","paragraph","presentation","strong","subscript","superscript","suggestion","term","time"]),_e={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},Ge={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 T=null,R=null;function Ee(){T=null,R=null}function Z(t){var i;if(R&&(T==null?void 0:T.deref())===t)return R;const a=[],e=[],n=[];for(const o of t.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 p=()=>(s===void 0&&(s=m(o),l=u(o)),{selector:s,html:l});for(const c of o.attributes)if(c.name.startsWith("aria-")&&!Be.has(c.name)){const d=p();a.push({ruleId:"aria-valid-attr",selector:d.selector,html:d.html,impact:"critical",message:`Invalid ARIA attribute "${c.name}".`});break}for(const c of o.attributes){if(!c.name.startsWith("aria-"))continue;const d=c.value.trim();if(!(d===""&&!ie.has(c.name)&&!oe.has(c.name))){if(ie.has(c.name)){if(d!=="true"&&d!=="false"){const h=p();e.push({ruleId:"aria-valid-attr-value",selector:h.selector,html:h.html,impact:"critical",message:`${c.name} must be "true" or "false", got "${d}".`})}}else if(oe.has(c.name)){if(d!=="true"&&d!=="false"&&d!=="mixed"){const h=p();e.push({ruleId:"aria-valid-attr-value",selector:h.selector,html:h.html,impact:"critical",message:`${c.name} must be "true", "false", or "mixed", got "${d}".`})}}else if(Oe.has(c.name)){if(d===""||!/^-?\d+$/.test(d)){const h=p();e.push({ruleId:"aria-valid-attr-value",selector:h.selector,html:h.html,impact:"critical",message:`${c.name} must be an integer, got "${d}".`})}}else if(Ve.has(c.name)){if(d===""||isNaN(Number(d))){const h=p();e.push({ruleId:"aria-valid-attr-value",selector:h.selector,html:h.html,impact:"critical",message:`${c.name} must be a number, got "${d}".`})}}else if(re[c.name]){const h=d.split(/\s+/);for(const g of h)if(!re[c.name].has(g)){const f=p();e.push({ruleId:"aria-valid-attr-value",selector:f.selector,html:f.html,impact:"critical",message:`Invalid value "${d}" for ${c.name}.`});break}}}}if(!b(o)){const c=(i=o.getAttribute("role"))==null?void 0:i.trim().toLowerCase(),d=o.tagName.toLowerCase();if(!c&&_e[d]){const h=o.hasAttribute("aria-label"),g=o.hasAttribute("aria-labelledby");if(h||g){const f=p();n.push({ruleId:"aria-prohibited-attr",selector:f.selector,html:f.html,impact:"serious",message:`aria-label and aria-labelledby are prohibited on <${d}> elements.`})}}else if(c){if(se.has(c)){const g=o.hasAttribute("aria-label"),f=o.hasAttribute("aria-labelledby");if(g||f){const y=p();n.push({ruleId:"aria-prohibited-attr",selector:y.selector,html:y.html,impact:"serious",message:`aria-label and aria-labelledby are prohibited on role "${c}".`})}}const h=Ge[c];if(h){for(const g of o.attributes)if(g.name.startsWith("aria-")&&h.has(g.name)){if((g.name==="aria-label"||g.name==="aria-labelledby")&&se.has(c))continue;const f=p();n.push({ruleId:"aria-prohibited-attr",selector:f.selector,html:f.html,impact:"serious",message:`Attribute "${g.name}" is prohibited on role "${c}".`})}}}}}return T=new WeakRef(t),R={validAttr:a,validAttrValue:e,prohibitedAttr:n},R}let G=new WeakMap,Y=new WeakMap,X=new WeakMap;function qe(){G=new WeakMap,Y=new WeakMap,X=new WeakMap}function w(t){let a=G.get(t);return a||(a=getComputedStyle(t),G.set(t,a),a)}function M(t,a,e){const[n,i,o]=[t,a,e].map(r=>{const s=r/255;return s<=.04045?s/12.92:Math.pow((s+.055)/1.055,2.4)});return .2126*n+.7152*i+.0722*o}function Le(t,a){const e=Math.max(t,a),n=Math.min(t,a);return(e+.05)/(n+.05)}const le={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 D(t){const a=t.trim().toLowerCase();if(le[a])return le[a];const e=a.match(/^#([0-9a-f])([0-9a-f])([0-9a-f])$/);if(e)return[parseInt(e[1]+e[1],16),parseInt(e[2]+e[2],16),parseInt(e[3]+e[3],16)];const n=a.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/);if(n)return[parseInt(n[1],16),parseInt(n[2],16),parseInt(n[3],16)];const i=t.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*[\d.]+)?\s*\)/);if(i)return[parseInt(i[1]),parseInt(i[2]),parseInt(i[3])];const o=t.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 Ye(t){const a=Y.get(t);if(a!==void 0)return a;const e=Xe(t);return Y.set(t,e),e}function Xe(t){let a=t;for(;a;){const e=w(a),n=e.backgroundImage;if(n&&n!=="none"&&n!=="initial")return null;const i=e.backgroundColor;if(i==="transparent"||i==="rgba(0, 0, 0, 0)"||i==="rgba(0 0 0 / 0)"){a=a.parentElement;continue}const o=i.match(/rgba\(.+?,\s*([\d.]+)\s*\)/)||i.match(/rgba?\(.+?\/\s*([\d.]+%?)\s*\)/);if(o&&(o[1].endsWith("%")?parseFloat(o[1])/100:parseFloat(o[1]))<.1){a=a.parentElement;continue}return D(i)}return[255,255,255]}const Ke=new Set(["IMG","PICTURE","VIDEO","SVG"]);function Je(t){const a=X.get(t);if(a!==void 0)return a;const e=Qe(t);return X.set(t,e),e}function Qe(t){let a=t,e=!1;for(;a;){const n=w(a).position;if((n==="absolute"||n==="fixed")&&(e=!0),a!==t&&n!=="static"){for(const i of a.children)if(!(i===t||i.contains(t))&&Ke.has(i.tagName)){if(e)return!0;const o=w(i).position;if(o==="absolute"||o==="fixed")return!0}if(e)break}a=a.parentElement}return!1}function Ze(t){const a=parseFloat(t);return t.endsWith("pt")?a*(4/3):a}function et(t){const a=w(t),e=Ze(a.fontSize),n=parseInt(a.fontWeight)||(a.fontWeight==="bold"?700:400);return e>=23.5||e>=18.5&&n>=700}const H=new Map;function tt(t,a){H.set(t,a),K.delete(t)}function at(t,a){const e=H.get(a);return e?t.map(n=>{const i=e[n.id];return i?{...n,description:i.description,guidance:i.guidance!==void 0?i.guidance:n.guidance}:n}):t}const K=new Map;function nt(t){return t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function it(t){const a=t.split(/\{(\d+)\}/);let e="^";for(let n=0;n<a.length;n++)n%2===0?e+=nt(a[n]):e+="(.+?)";return e+="$",new RegExp(e)}function ot(t,a){let e=K.get(t);if(e||(e=new Map,K.set(t,e)),e.has(a))return e.get(a);const n=H.get(t);if(!n)return;const i=n[a];if(!(i!=null&&i.messages))return e.set(a,[]),[];const o=[];for(const[r,s]of Object.entries(i.messages))o.push({regex:it(r),translated:s});return e.set(a,o),o}function rt(t,a,e){const n=ot(e,t);if(!n)return a;for(const{regex:i,translated:o}of n){const r=a.match(i);if(r)return o.replace(/\{(\d+)\}/g,(s,l)=>{const p=parseInt(l,10);return p+1<r.length?r[p+1]:`{${l}}`})}return a}function ee(t,a){return H.has(a)?t.map(e=>{const n=rt(e.ruleId,e.message,a);return n===e.message?e:{...e,message:n}}):t}function j(t){var o,r;const a=[],e=t.closest("a");if(e){const s=e.getAttribute("href");s&&a.push(`Link href: ${s}`)}const n=t.closest("figure");if(n){const s=n.querySelector("figcaption");(o=s==null?void 0:s.textContent)!=null&&o.trim()&&a.push(`Figcaption: ${s.textContent.trim().slice(0,100)}`)}const i=t.parentElement;if(i&&i!==e){const s=t instanceof HTMLImageElement&&t.alt||"",l=(r=i.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}function ce(t){let a=t;for(;a;){if(a instanceof HTMLElement&&a.style.visibility==="hidden")return!0;a=a.parentElement}return!1}const st={id:"img-alt",wcag:["1.1.1"],level:"A",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.",prompt:"Describe what alt text to add. If the image appears decorative based on context (spacer, background, icon next to text that already describes it), recommend alt=''. Otherwise suggest descriptive alt text based on the src or surrounding context.",run(t){const a=[];for(const e of t.querySelectorAll("img")){if(b(e)||ce(e))continue;const n=e.getAttribute("role");if(n==="presentation"||n==="none"){const o=e.getAttribute("tabindex");if(!o||o==="-1")continue}const i=e.getAttribute("alt");if(i!==null&&i.trim()===""&&i!==""){a.push({ruleId:"img-alt",selector:m(e),html:u(e),impact:"critical",message:'Image has whitespace-only alt text. Use alt="" for decorative images or provide descriptive text.',context:j(e)});continue}!e.hasAttribute("alt")&&!v(e)&&a.push({ruleId:"img-alt",selector:m(e),html:u(e),impact:"critical",message:"Image element missing alt attribute.",context:j(e)})}for(const e of t.querySelectorAll('[role="img"]:not(img):not(svg)'))b(e)||ce(e)||v(e)||a.push({ruleId:"img-alt",selector:m(e),html:u(e),impact:"critical",message:'Element with role="img" has no accessible name. Add aria-label or aria-labelledby.',context:j(e)});return a}};function lt(t){var o,r,s;const a=t.getAttribute("aria-labelledby");if(a){const l=a.split(/\s+/).map(p=>{var c,d;return((d=(c=t.ownerDocument.getElementById(p))==null?void 0:c.textContent)==null?void 0:d.trim())??""}).filter(Boolean);if(l.length)return l.join(" ")}const e=(o=t.getAttribute("aria-label"))==null?void 0:o.trim();if(e)return e;const n=t.querySelector("title");if((r=n==null?void 0:n.textContent)!=null&&r.trim())return n.textContent.trim();const i=(s=t.getAttribute("title"))==null?void 0:s.trim();return i||""}const ct={id:"svg-img-alt",wcag:["1.1.1"],level:"A",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.",prompt:"Based on the SVG content or context, suggest either adding aria-label with a description, or if decorative, replacing role='img' with aria-hidden='true'.",run(t){const a=[],e='svg[role="img"], [role="graphics-document"], [role="graphics-symbol"]';for(const n of t.querySelectorAll(e)){if(b(n))continue;if(!lt(n)){const o=n.getAttribute("role");a.push({ruleId:"svg-img-alt",selector:m(n),html:u(n),impact:"serious",message:`${n.tagName.toLowerCase()} with role='${o}' has no accessible name.`})}}return a}},dt={id:"input-image-alt",wcag:["1.1.1","4.1.2"],level:"A",description:'Image inputs (<input type="image">) must have alternate text via alt, aria-label, or aria-labelledby. The text should describe the button action, not the image.',guidance:"Image buttons (<input type='image'>) must have alternate text via alt, aria-label, or aria-labelledby. The text should describe the button action, not the image.",prompt:"Based on the src attribute or form context, suggest alt text describing the button's action (e.g., 'Submit', 'Search', 'Go').",run(t){const a=[];for(const e of t.querySelectorAll('input[type="image"]'))b(e)||v(e)||a.push({ruleId:"input-image-alt",selector:m(e),html:u(e),impact:"critical",message:"Image input missing alt text."});return a}},ut={id:"image-redundant-alt",wcag:[],level:"A",tags:["best-practice"],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.",prompt:"The image alt text is identical to the text already visible in the parent link or button. Screen reader users hear the same words twice. If the image is decorative in this context (e.g. an icon next to a label), recommend alt=''. Otherwise suggest brief complementary alt text that adds information the visible text doesn't convey — for example what the image depicts, not what the link says.",run(t){var e;const a=[];for(const n of t.querySelectorAll("img[alt]")){const i=n.getAttribute("alt").trim().toLowerCase();if(!i)continue;const o=n.closest("a, button");if(o){const r=((e=o.textContent)==null?void 0:e.trim().toLowerCase())||"";if(r&&r===i){const s=o.tagName.toLowerCase(),l=o.getAttribute("href");a.push({ruleId:"image-redundant-alt",selector:m(n),html:u(n),impact:"minor",message:`Alt text "${n.getAttribute("alt")}" duplicates surrounding ${s} text.`,context:`Duplicated text: "${n.getAttribute("alt")}", parent element: <${s}>${l?` href="${l}"`:""}`})}}}return a}},mt=["image","picture","photo","graphic","icon","img"],pt={id:"image-alt-redundant-words",wcag:[],level:"A",tags:["best-practice"],description:"Image alt text should not contain words like 'image', 'photo', or 'picture' — 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'.",prompt:"The alt text contains a word like 'image', 'photo', or 'picture' that is already announced by the screen reader. Rewrite the alt text with the redundant word removed while keeping the description meaningful. For example: 'image of a sunset over the ocean' → 'sunset over the ocean'; 'photo of team members' → 'team members at the 2024 offsite'; 'icon for settings' → 'settings'.",run(t){const a=[];for(const e of t.querySelectorAll("img[alt]")){const n=e.getAttribute("alt").toLowerCase();if(!n)continue;const i=mt.filter(o=>n.split(/\s+/).includes(o));i.length>0&&a.push({ruleId:"image-alt-redundant-words",selector:m(e),html:u(e),impact:"minor",message:`Alt text "${e.getAttribute("alt")}" contains redundant word(s): ${i.join(", ")}.`,context:`Current alt: "${e.getAttribute("alt")}", redundant word(s): ${i.join(", ")}`})}return a}},ht={id:"area-alt",wcag:["1.1.1","4.1.2"],level:"A",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.",prompt:"Based on the href or shape/coords, suggest alt text describing where this area links or what it represents.",run(t){const a=[];for(const e of t.querySelectorAll("area[href]")){if(b(e))continue;v(e)||a.push({ruleId:"area-alt",selector:m(e),html:u(e),impact:"critical",message:"Image map <area> element is missing alternative text."})}return a}};function bt(t){var i,o;const a=t.getAttribute("aria-labelledby");if(a){const r=a.split(/\s+/).map(s=>{var l,p;return((p=(l=t.ownerDocument.getElementById(s))==null?void 0:l.textContent)==null?void 0:p.trim())??""}).filter(Boolean);if(r.length)return r.join(" ")}const e=(i=t.getAttribute("aria-label"))==null?void 0:i.trim();if(e)return e;const n=(o=t.getAttribute("title"))==null?void 0:o.trim();return n||""}const gt={id:"object-alt",wcag:["1.1.1"],level:"A",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.",prompt:"Based on the data/type attributes, suggest adding aria-label or a title attribute describing what the embedded content represents.",run(t){var e;const a=[];for(const n of t.querySelectorAll("object")){if(b(n)||n instanceof HTMLElement&&n.style.visibility==="hidden")continue;let i=n.parentElement,o=!1;for(;i;){if(i instanceof HTMLElement&&i.style.visibility==="hidden"){o=!0;break}i=i.parentElement}if(o||n.getAttribute("role")==="presentation"||n.getAttribute("role")==="none"||bt(n))continue;const r=n.getAttribute("data")||"";if(!((n.getAttribute("type")||"").startsWith("image/")||/\.(png|jpg|jpeg|gif|svg|webp|bmp|ico)$/i.test(r))){const p=n.querySelector("img[alt]");if(p&&((e=p.getAttribute("alt"))!=null&&e.trim()))continue}a.push({ruleId:"object-alt",selector:m(n),html:u(n),impact:"serious",message:"<object> element is missing alternative text. Add aria-label, aria-labelledby, or a title attribute."})}return a}},ft={id:"role-img-alt",wcag:["1.1.1"],level:"A",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.",prompt:"Based on the element's content or class names, suggest either an aria-label describing the image, or if decorative, recommend removing role='img' or adding aria-hidden='true'.",run(t){const a=[];for(const e of t.querySelectorAll('[role="img"]')){if(b(e)||e.tagName.toLowerCase()==="svg"||e.tagName.toLowerCase()==="img")continue;v(e)||a.push({ruleId:"role-img-alt",selector:m(e),html:u(e),impact:"serious",message:"Element with role='img' has no accessible name. Add aria-label or aria-labelledby."})}return a}};function vt(t){if(typeof t!="object"||t===null)return"Rule spec must be an object";const a=t;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 e=a.check;if(!["selector-exists","attribute-value","attribute-missing","attribute-regex","child-required","child-invalid"].includes(e.type))return`Invalid check type: ${String(e.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 i=yt(e);return i||null}function yt(t){switch(t.type){case"selector-exists":return null;case"attribute-value":return typeof t.attribute!="string"?"attribute-value check requires attribute string":[">","<","=","!=","in","not-in"].includes(t.operator)?t.value===void 0?"attribute-value check requires value":null:"attribute-value check requires valid operator";case"attribute-missing":return typeof t.attribute!="string"?"attribute-missing check requires attribute string":null;case"attribute-regex":return typeof t.attribute!="string"?"attribute-regex check requires attribute string":typeof t.pattern!="string"?"attribute-regex check requires pattern string":typeof t.shouldMatch!="boolean"?"attribute-regex check requires shouldMatch boolean":null;case"child-required":return typeof t.childSelector!="string"?"child-required check requires childSelector string":null;case"child-invalid":return Array.isArray(t.allowedChildren)?null:"child-invalid check requires allowedChildren array";default:return`Unknown check type: ${String(t.type)}`}}function I(t,a,e){let n=t;if(n.includes("{{tag}}")&&(n=n.replace(/\{\{tag\}\}/g,a.tagName.toLowerCase())),n.includes("{{value}}")){let i="";"attribute"in e&&e.attribute&&(i=a.getAttribute(e.attribute)??""),n=n.replace(/\{\{value\}\}/g,i)}return n}function S(t){const a=t.skipAriaHidden!==!1;return{id:t.id,wcag:t.wcag,level:t.level,tags:t.tags,description:t.description,guidance:t.guidance,prompt:t.prompt,run(e){var i,o;const n=[];switch(t.check.type){case"selector-exists":{for(const r of e.querySelectorAll(t.selector))a&&b(r)||n.push({ruleId:t.id,selector:m(r),html:u(r),impact:t.impact,message:I(t.message,r,t.check),element:r});break}case"attribute-value":{const{attribute:r,operator:s,value:l}=t.check;for(const p of e.querySelectorAll(t.selector)){if(a&&b(p))continue;const c=p.getAttribute(r);c!==null&&wt(c,s,l)&&n.push({ruleId:t.id,selector:m(p),html:u(p),impact:t.impact,message:I(t.message,p,t.check),element:p})}break}case"attribute-missing":{const{attribute:r}=t.check;for(const s of e.querySelectorAll(t.selector))a&&b(s)||s.hasAttribute(r)||n.push({ruleId:t.id,selector:m(s),html:u(s),impact:t.impact,message:I(t.message,s,t.check),element:s});break}case"attribute-regex":{const{attribute:r,pattern:s,flags:l,shouldMatch:p}=t.check;let c;try{c=new RegExp(s,l)}catch{break}for(const d of e.querySelectorAll(t.selector)){if(a&&b(d))continue;const h=d.getAttribute(r);if(h===null)continue;const g=c.test(h);p&&!g?n.push({ruleId:t.id,selector:m(d),html:u(d),impact:t.impact,message:I(t.message,d,t.check),element:d}):!p&&g&&n.push({ruleId:t.id,selector:m(d),html:u(d),impact:t.impact,message:I(t.message,d,t.check),element:d})}break}case"child-required":{const{childSelector:r}=t.check;for(const s of e.querySelectorAll(t.selector))a&&b(s)||s.querySelector(r)||n.push({ruleId:t.id,selector:m(s),html:u(s),impact:t.impact,message:I(t.message,s,t.check),element:s});break}case"child-invalid":{const r=new Set(t.check.allowedChildren.map(l=>l.toLowerCase())),s=t.check.allowedChildRoles?new Set(t.check.allowedChildRoles.map(l=>l.toLowerCase())):null;for(const l of e.querySelectorAll(t.selector)){if(a&&b(l))continue;const p=(i=l.getAttribute("role"))==null?void 0:i.trim().toLowerCase();if(p==="presentation"||p==="none")continue;let c=!1;const d=t.check.allowedChildren.filter(h=>h!=="script"&&h!=="template");for(const h of l.childNodes)if(h.nodeType===3&&h.textContent&&h.textContent.trim()){const g=d.map(f=>`<${f}>`).join(" or ");n.push({ruleId:t.id,selector:m(l),html:u(l),impact:t.impact,message:`<${l.tagName.toLowerCase()}> contains direct text content. Wrap in ${g}.`,element:l}),c=!0;break}if(!c)for(const h of l.children){if(r.has(h.tagName.toLowerCase()))continue;const g=(o=h.getAttribute("role"))==null?void 0:o.trim().toLowerCase();if(!(g&&(s!=null&&s.has(g)))&&!(g==="presentation"||g==="none")){n.push({ruleId:t.id,selector:m(h),html:u(h),impact:t.impact,message:I(t.message,h,t.check),element:h});break}}}break}}return n}}}function wt(t,a,e){switch(a){case">":return parseFloat(t)>e;case"<":return parseFloat(t)<e;case"=":return t===String(e);case"!=":return t!==String(e);case"in":return Array.isArray(e)&&e.includes(t);case"not-in":return Array.isArray(e)&&!e.includes(t);default:return!1}}const At={id:"server-side-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",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.",prompt:"Explain that the ismap attribute should be removed and the functionality replaced with a client-side <map> element with <area> children, or separate linked images/buttons."},xt=S(At),kt=['[role="checkbox"]','[role="combobox"]','[role="listbox"]','[role="menuitemcheckbox"]','[role="menuitemradio"]','[role="radio"]','[role="searchbox"]','[role="slider"]','[role="spinbutton"]','[role="switch"]','[role="textbox"]'].join(", "),St=new Set(["checkbox","menuitemcheckbox","menuitemradio","radio","switch"]),It=new Set(["combobox","listbox","searchbox","slider","spinbutton","textbox"]);function Et(t){var r,s,l,p;const a=(r=t.getAttribute("role"))==null?void 0:r.trim().toLowerCase();if(a&&St.has(a)||(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement)&&!(a&&It.has(a)))return v(t);const n=t.getAttribute("aria-labelledby");if(n){const c=n.split(/\s+/).map(d=>{const h=t.ownerDocument.getElementById(d);return h?A(h).trim():""}).filter(Boolean);if(c.length)return c.join(" ")}const i=(s=t.getAttribute("aria-label"))==null?void 0:s.trim();if(i)return i;if(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement||t instanceof HTMLSelectElement){if(t.id){const d=t.ownerDocument.querySelector(`label[for="${CSS.escape(t.id)}"]`);if(d){const h=A(d).trim();if(h)return h}}const c=t.closest("label");if(c){const d=A(c).trim();if(d)return d}}const o=(l=t.getAttribute("title"))==null?void 0:l.trim();if(o)return o;if(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement){const c=(p=t.getAttribute("placeholder"))==null?void 0:p.trim();if(c)return c}return""}const qt={id:"label",wcag:["4.1.2"],level:"A",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.",prompt:`This form field has no accessible label. Based on the context (input type, name attribute, placeholder, or surrounding elements), suggest adding a <label for="id"> element with descriptive text, or an aria-label attribute. The label should describe what information the user should enter, not the field type. For example: 'Email address', 'Search', 'Phone number'.`,run(t){var i;const a=[],n=t.querySelectorAll(`input:not([type="hidden"]):not([type="submit"]):not([type="button"]):not([type="reset"]):not([type="image"]), textarea, select, ${kt}`);for(const o of n){if(b(o)||q(o)||(o instanceof HTMLInputElement||o instanceof HTMLTextAreaElement||o instanceof HTMLSelectElement||o instanceof HTMLButtonElement)&&o.disabled||o.closest("fieldset[disabled]")||o.getAttribute("aria-disabled")==="true")continue;const r=(i=o.getAttribute("role"))==null?void 0:i.trim().toLowerCase();if(r==="presentation"||r==="none")continue;if(!Et(o)){const l=[],p=o.tagName.toLowerCase(),c=o.getAttribute("type");c&&p==="input"&&l.push(`type: ${c}`);const d=o.getAttribute("name");d&&l.push(`name: "${d}"`);const h=o.getAttribute("placeholder");h&&l.push(`placeholder: "${h}"`),r&&l.push(`role: ${r}`);const g=o.getAttribute("id");g&&l.push(`id: "${g}"`),a.push({ruleId:"label",selector:m(o),html:u(o),impact:"critical",message:"Form element has no accessible label.",context:l.length>0?l.join(", "):void 0})}}return a}},Lt={id:"form-field-multiple-labels",wcag:[],level:"A",tags:["best-practice"],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.",prompt:"Identify the multiple labels and recommend consolidating them into a single <label> element or using aria-describedby for supplementary text.",run(t){const a=[],e=t.querySelectorAll('input:not([type="hidden"]), textarea, select');for(const n of e){if(b(n)||!n.id)continue;const i=t.querySelectorAll(`label[for="${CSS.escape(n.id)}"]`);let o=0,r=n.parentElement;for(;r;){if(r.tagName.toLowerCase()==="label"&&!r.hasAttribute("for")){o++;break}r=r.parentElement}const s=i.length+o;s>1&&a.push({ruleId:"form-field-multiple-labels",selector:m(n),html:u(n),impact:"moderate",message:`Form field has ${s} labels. Use a single label element.`})}return a}},Tt={id:"select-name",wcag:["4.1.2"],level:"A",description:"Select elements must have a programmatically associated label via <label>, aria-label, or aria-labelledby.",guidance:"Select dropdowns need labels so users understand what choice they're making. Use a <label> element with a for attribute matching the select's id, or wrap the select in a <label>. For selects without visible labels, use aria-label. The first <option> is not a substitute for a proper label.",prompt:"Based on the options or context, suggest a label element or aria-label describing what this select controls.",run(t){const a=[];for(const e of t.querySelectorAll("select"))b(e)||v(e)||a.push({ruleId:"select-name",selector:m(e),html:u(e),impact:"critical",message:"Select element has no accessible name."});return a}},Rt={id:"input-button-name",wcag:["4.1.2"],level:"A",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.",prompt:"Based on the input type and form context, suggest a value attribute describing the button's action.",run(t){var e,n;const a=[];for(const i of t.querySelectorAll('input[type="submit"], input[type="button"], input[type="reset"]')){if(b(i))continue;const o=(e=i.getAttribute("value"))==null?void 0:e.trim(),r=(n=i.getAttribute("type"))==null?void 0:n.toLowerCase(),s=(r==="submit"||r==="reset")&&!i.hasAttribute("value");!o&&!s&&!v(i)&&a.push({ruleId:"input-button-name",selector:m(i),html:u(i),impact:"critical",message:"Input button has no discernible text."})}return a}},Ct=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"]),Nt=new Set(["tel","tel-country-code","tel-national","tel-area-code","tel-local","tel-extension","email","impp"]),Mt=new Set(["home","work","mobile","fax","pager"]),Dt=new Set(["shipping","billing"]),Ht=new Set(["webauthn"]);function $t(t){const a=t.toLowerCase().split(/\s+/).filter(Boolean);if(a.length===0)return!0;let e=0;a[e].startsWith("section-")&&e++,e<a.length&&Dt.has(a[e])&&e++;let n=!1;if(e<a.length&&Mt.has(a[e])&&(n=!0,e++),e>=a.length)return!1;const i=a[e];return!Ct.has(i)||n&&!Nt.has(i)?!1:(e++,e<a.length&&Ht.has(a[e])&&e++,e===a.length)}const Pt={id:"autocomplete-valid",wcag:["1.3.5"],level:"AA",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.",prompt:"Show the invalid autocomplete value and suggest the correct standard value based on the input's apparent purpose.",run(t){const a=[];for(const e of t.querySelectorAll("[autocomplete]")){if(b(e)||e instanceof HTMLElement&&e.style.display==="none"||e.disabled||e.getAttribute("aria-disabled")==="true")continue;const n=e.getAttribute("autocomplete").trim();n&&($t(n)||a.push({ruleId:"autocomplete-valid",selector:m(e),html:u(e),impact:"serious",message:`Invalid autocomplete value "${n}".`}))}return a}};function de(t){return t.toLowerCase().replace(/\s+/g," ").trim()}function ue(t,a){const e=de(t),n=de(a);if(!e||!n||e.includes(n)||n.includes(e))return!0;const i=n.split(/\s+/).map(o=>o.replace(/[.,;:!?\u2026]+$/g,"")).filter(o=>o.length>2);return i.length>=2&&i.filter(r=>e.includes(r)).length/i.length>.5}function J(t){let a="";for(const e of t.childNodes)if(e.nodeType===3)a+=e.textContent??"";else if(e.nodeType===1){const n=e,i=n.tagName.toLowerCase();if(i==="style"||i==="script"||i==="svg"||n.getAttribute("aria-hidden")==="true"||n instanceof HTMLElement&&n.style.display==="none")continue;const o=n.getAttribute("role");if(o==="img"||o==="presentation"||o==="none")continue;a+=J(n)}return a}const zt={id:"label-content-name-mismatch",wcag:[],level:"A",tags:["best-practice"],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.",prompt:"Show the mismatch between the visible text and accessible name, and suggest updating aria-label to include the visible text.",run(t){const a=[];for(const e of t.querySelectorAll('button, [role="button"], a[href], input[type="submit"], input[type="button"]')){if(b(e))continue;const n=v(e);if(!n)continue;let i="";e instanceof HTMLInputElement?i=e.value||"":i=J(e);const o=i.trim();if(!o||o.length<=2)continue;const r=e.hasAttribute("aria-label"),s=e.hasAttribute("aria-labelledby");!r&&!s||ue(n,i)||a.push({ruleId:"label-content-name-mismatch",selector:m(e),html:u(e),impact:"serious",message:`Accessible name "${n}" does not contain visible text "${i.trim()}".`})}for(const e of t.querySelectorAll("input, select, textarea")){if(b(e)||e instanceof HTMLInputElement&&["hidden","submit","button","image"].includes(e.type))continue;const n=v(e);if(!n||!e.hasAttribute("aria-label"))continue;const o=e.id;let r="";if(o){const s=t.querySelector(`label[for="${CSS.escape(o)}"]`);s&&(r=J(s))}r.trim()&&(ue(n,r)||a.push({ruleId:"label-content-name-mismatch",selector:m(e),html:u(e),impact:"serious",message:`Accessible name "${n}" does not contain visible label "${r.trim()}".`}))}return a}},Ft={id:"label-title-only",wcag:[],level:"A",tags:["best-practice"],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.",prompt:"The title attribute text should be moved to a visible <label> element or aria-label. Show what text to use based on the current title.",run(t){var n,i,o,r;const a=[],e=t.querySelectorAll('input:not([type="hidden"]):not([type="submit"]):not([type="button"]):not([type="reset"]):not([type="image"]), textarea, select');for(const s of e){if(b(s))continue;const l=s.hasAttribute("title")&&((n=s.getAttribute("title"))==null?void 0:n.trim()),p=s.hasAttribute("aria-label")&&((i=s.getAttribute("aria-label"))==null?void 0:i.trim()),c=s.hasAttribute("aria-labelledby");let d=!1;const h=s.id;if(h){const f=s.ownerDocument.querySelector(`label[for="${CSS.escape(h)}"]`);(o=f==null?void 0:f.textContent)!=null&&o.trim()&&(d=!0)}const g=s.closest("label");(r=g==null?void 0:g.textContent)!=null&&r.trim()&&(d=!0),l&&!p&&!c&&!d&&a.push({ruleId:"label-title-only",selector:m(s),html:u(s),impact:"serious",message:"Form element uses title attribute as only label. Use <label>, aria-label, or aria-labelledby instead."})}return a}},jt={id:"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"],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.",prompt:"Change the positive tabindex value to tabindex='0' and rely on DOM order for tab sequence instead."},Wt=S(jt),Ut=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"]),Bt={id:"focus-order-semantics",wcag:[],tags:["best-practice"],level:"A",description:"Elements that receive keyboard focus must have an appropriate role so assistive technologies can convey their purpose. Non-interactive elements with tabindex='0' need a valid interactive ARIA role.",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.",prompt:"Based on the element's apparent purpose, suggest adding an appropriate role attribute (button, link, etc.) or converting to a native interactive element.",run(t){const a=[];for(const e of t.querySelectorAll('[tabindex="0"]')){const n=e.tagName.toLowerCase();if(!Ut.has(n))continue;e.getAttribute("role")||a.push({ruleId:"focus-order-semantics",selector:m(e),html:u(e),impact:"moderate",message:`Non-interactive <${n}> with tabindex="0" has no interactive role.`})}return a}},Ot=new Set(["a","audio","button","img","input","select","textarea","video"]),Vt=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"]),_t={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 Gt(t,a){var i,o,r;const e=(i=t.getAttribute("role"))==null?void 0:i.toLowerCase(),n=(o=a.getAttribute("role"))==null?void 0:o.toLowerCase();return!e||!n?!1:((r=_t[e])==null?void 0:r.has(n))??!1}function Yt(t){var i;const a=t.tagName.toLowerCase();if(Ot.has(a))return a==="a"&&!t.hasAttribute("href")?!1:a==="audio"||a==="video"?t.hasAttribute("controls"):!(a==="img"&&!t.hasAttribute("usemap")||a==="input"&&t.type==="hidden"||t.disabled);const e=(i=t.getAttribute("role"))==null?void 0:i.toLowerCase();if(e&&Vt.has(e))return!0;const n=t.getAttribute("tabindex");return n!==null&&n!=="-1"||t.getAttribute("contenteditable")==="true"}function Xt(t){const a=t.tagName.toLowerCase();return!!(a==="a"&&t.hasAttribute("href")||a==="button"&&!t.disabled)}const Kt={id:"nested-interactive",wcag:["4.1.2"],level:"A",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.",prompt:"Identify which elements are nested and suggest restructuring them as siblings instead.",run(t){const a=[],e=t.body??t;if(!e)return a;const i=(t.body?t:t.ownerDocument).createTreeWalker(e,NodeFilter.SHOW_ELEMENT),o=[];let r=i.currentNode;for(;r;){for(;o.length>0&&!o[o.length-1].contains(r);)o.pop();if(!b(r)&&Yt(r)){if(o.length>0){const s=o[o.length-1];Gt(s,r)||a.push({ruleId:"nested-interactive",selector:m(r),html:u(r),impact:"serious",message:`Interactive element <${r.tagName.toLowerCase()}> is nested inside <${s.tagName.toLowerCase()}>.`})}Xt(r)&&o.push(r)}r=i.nextNode()}return a}},Jt={id:"scrollable-region-focusable",wcag:["2.1.1"],level:"A",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.",prompt:"Explain how to make this scrollable region keyboard accessible.",run(t){var e;const a=[];for(const n of t.querySelectorAll("*")){if(b(n)||!(n instanceof HTMLElement))continue;const i=n.tagName.toLowerCase();if(i==="body"||i==="html")continue;const o=n.getAttribute("role");if(o==="presentation"||o==="none")continue;const r=w(n),s=r.overflowX,l=r.overflowY;if(!(s==="scroll"||s==="auto"||l==="scroll"||l==="auto"))continue;if(n.scrollHeight>0||n.clientHeight>0){const g=n.scrollHeight-n.clientHeight,f=n.scrollWidth-n.clientWidth;if(g<=0&&f<=0||g<14&&f<14||n.clientWidth<64&&n.clientHeight<64)continue}else{const g=r.height!==""||r.maxHeight!=="",f=((e=n.textContent)==null?void 0:e.trim().length)??0;if(!g||f<=50)continue}const d=n.getAttribute("tabindex");d!==null&&d!=="-1"||n.querySelector('a[href], button:not([disabled]), input:not([disabled]):not([type="hidden"]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])')||a.push({ruleId:"scrollable-region-focusable",selector:m(n),html:u(n),impact:"serious",message:"Scrollable region is not keyboard accessible. Add tabindex='0' or include focusable elements."})}return a}},Qt={id:"accesskeys",wcag:[],level:"A",tags:["best-practice"],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.",prompt:"Suggest removing or changing this duplicate accesskey to a unique value.",run(t){var n;const a=[],e=new Map;for(const i of t.querySelectorAll("[accesskey]")){if(b(i))continue;const o=(n=i.getAttribute("accesskey"))==null?void 0:n.trim().toLowerCase();if(!o)continue;const r=e.get(o)||[];r.push(i),e.set(o,r)}for(const[i,o]of e)if(o.length>1)for(const r of o.slice(1))a.push({ruleId:"accesskeys",selector:m(r),html:u(r),impact:"serious",message:`Duplicate accesskey "${i}". Each accesskey must be unique.`});return a}},Zt={id:"heading-order",wcag:[],level:"A",tags:["best-practice"],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.",prompt:"State which heading level was expected and suggest changing this heading to the appropriate level.",run(t){const a=[],e=t.querySelectorAll("h1, h2, h3, h4, h5, h6, [role='heading']");let n=0,i=null;for(const o of e){if(b(o))continue;let r;o.hasAttribute("aria-level")?r=parseInt(o.getAttribute("aria-level"),10):r=parseInt(o.tagName[1],10),n>0&&r>n+1&&a.push({ruleId:"heading-order",selector:m(o),html:u(o),impact:"moderate",message:`Heading level ${r} skipped from level ${n}.`,context:i?`Previous heading: ${u(i)}`:void 0}),n=r,i=o}return a}},$='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"]',ea={id:"landmark-one-main",wcag:[],level:"A",tags:["best-practice"],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.",prompt:"Identify the primary content area and explain how to wrap it in a <main> element.",run(t){const a=t.querySelectorAll('main, [role="main"]');return a.length===0?[{ruleId:"landmark-one-main",selector:"html",html:"<html>",impact:"moderate",message:"Page has no main landmark."}]:a.length>1?Array.from(a).slice(1).map(e=>({ruleId:"landmark-one-main",selector:m(e),html:u(e),impact:"moderate",message:"Page has multiple main landmarks."})):[]}},ta={id:"landmark-no-duplicate-banner",wcag:[],level:"A",tags:["best-practice"],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.",prompt:"Explain whether to remove this duplicate banner or nest it inside a sectioning element.",run(t){const a=[],e=t.querySelectorAll('header, [role="banner"]'),n=Array.from(e).filter(i=>!i.closest($));return n.length>1&&n.slice(1).forEach(i=>a.push({ruleId:"landmark-no-duplicate-banner",selector:m(i),html:u(i),impact:"moderate",message:"Page has multiple banner landmarks."})),a}},aa={id:"landmark-no-duplicate-contentinfo",wcag:[],level:"A",tags:["best-practice"],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.",prompt:"Explain whether to remove this duplicate footer or nest it inside a sectioning element.",run(t){const a=[],e=t.querySelectorAll('footer, [role="contentinfo"]'),n=Array.from(e).filter(i=>!i.closest($));return n.length>1&&n.slice(1).forEach(i=>a.push({ruleId:"landmark-no-duplicate-contentinfo",selector:m(i),html:u(i),impact:"moderate",message:"Page has multiple contentinfo landmarks."})),a}},na={id:"landmark-no-duplicate-main",wcag:[],level:"A",tags:["best-practice"],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.",prompt:"Explain which main landmark to keep and how to restructure the duplicate.",run(t){const a=[],e=t.querySelectorAll('main, [role="main"]');return e.length>1&&Array.from(e).slice(1).forEach(n=>a.push({ruleId:"landmark-no-duplicate-main",selector:m(n),html:u(n),impact:"moderate",message:"Page has multiple main landmarks."})),a}},ia={id:"landmark-banner-is-top-level",wcag:[],level:"A",tags:["best-practice"],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.",prompt:"Explain why this banner is incorrectly nested and how to fix it.",run(t){const a=[],e=t.querySelectorAll('[role="banner"]');for(const n of e)n.closest($)&&a.push({ruleId:"landmark-banner-is-top-level",selector:m(n),html:u(n),impact:"moderate",message:"Banner landmark is nested within another landmark."});return a}},oa={id:"landmark-contentinfo-is-top-level",wcag:[],level:"A",tags:["best-practice"],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.",prompt:"Explain why this contentinfo is incorrectly nested and how to fix it.",run(t){const a=[],e=t.querySelectorAll('[role="contentinfo"]');for(const n of e)n.closest($)&&a.push({ruleId:"landmark-contentinfo-is-top-level",selector:m(n),html:u(n),impact:"moderate",message:"Contentinfo landmark is nested within another landmark."});return a}},ra={id:"landmark-main-is-top-level",wcag:[],level:"A",tags:["best-practice"],description:"Main landmark should not be nested within another landmark.",guidance:"The main landmark must be a top-level landmark since it represents the primary content of the page. Do not nest <main> or role='main' inside article, aside, nav, or section elements.",prompt:"Explain why the main landmark must be top-level and where to move it.",run(t){const a=[],e=t.querySelectorAll('main, [role="main"]');for(const n of e){const i=n.parentElement;i!=null&&i.closest('article, aside, nav, section, [role="article"], [role="complementary"], [role="navigation"], [role="region"]')&&a.push({ruleId:"landmark-main-is-top-level",selector:m(n),html:u(n),impact:"moderate",message:"Main landmark is nested within another landmark."})}return a}},sa={id:"landmark-complementary-is-top-level",wcag:[],level:"A",tags:["best-practice"],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.",prompt:"Explain why this aside should be repositioned and suggest where to move it.",run(t){const a=[],e=t.querySelectorAll('aside, [role="complementary"]');for(const n of e){const i=n.parentElement;i&&!i.matches('body, main, [role="main"]')&&n.closest('article, nav, section, [role="article"], [role="navigation"], [role="region"]')&&a.push({ruleId:"landmark-complementary-is-top-level",selector:m(n),html:u(n),impact:"moderate",message:"Complementary landmark should be top-level."})}return a}},la={id:"landmark-unique",wcag:[],level:"A",tags:["best-practice"],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').",prompt:"Suggest a unique aria-label that distinguishes this landmark based on its purpose.",run(t){const a=[],e=[{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:n,type:i}of e){const o=Array.from(t.querySelectorAll(n)).filter(s=>!b(s));if(o.length<=1)continue;const r=new Map;for(const s of o){const l=v(s).toLowerCase()||"",p=r.get(l)||[];p.push(s),r.set(l,p)}for(const[s,l]of r)if(l.length>1)for(const p of l.slice(1))a.push({ruleId:"landmark-unique",selector:m(p),html:u(p),impact:"moderate",message:s?`Multiple ${i} landmarks have the same label "${s}".`:`Multiple ${i} landmarks have no label. Add unique aria-label attributes.`})}return a}},ca={id:"region",wcag:[],level:"A",tags:["best-practice"],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.",prompt:"Based on the content, suggest which landmark element would be most appropriate.",run(t){var n;const a=[],e=t.body;if(!e)return[];for(const i of e.children){if(b(i)||i instanceof HTMLScriptElement||i instanceof HTMLStyleElement||i.tagName==="NOSCRIPT"||i instanceof HTMLElement&&i.hidden||i.matches('a[href^="#"]'))continue;const o=i.matches(me),r=(n=i.textContent)==null?void 0:n.trim();!o&&r&&(i.querySelector(me)||a.push({ruleId:"region",selector:m(i),html:u(i),impact:"moderate",message:"Content is not contained within a landmark region."}))}return a}},da={id:"list",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",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, restructure your CSS to style the <li> elements directly.",prompt:"Explain how to restructure this element within the list properly."},ua=S(da),ma={id:"dlitem",wcag:["1.3.1"],level:"A",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.",prompt:"Explain how to properly structure this term/definition content.",run(t){const a=[];for(const e of t.querySelectorAll("dt, dd"))(!e.parentElement||e.parentElement.tagName.toLowerCase()!=="dl")&&a.push({ruleId:"dlitem",selector:m(e),html:u(e),impact:"serious",message:`<${e.tagName.toLowerCase()}> is not contained in a <dl>.`});return a}},pa={id:"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",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 restructure using proper definition list markup.",prompt:"Explain whether to move this element outside the <dl> or convert it to dt/dd."},ha=S(pa),ba={id:"listitem",wcag:["1.3.1"],level:"A",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>). Outside of these containers, assistive technologies cannot convey the list relationship. Wrap <li> elements in the appropriate list container.",prompt:"Explain that this <li> must be placed inside a <ul>, <ol>, or <menu> element.",run(t){var e;const a=[];for(const n of t.querySelectorAll("li")){if(b(n))continue;const i=n.parentElement;if(!i)continue;const o=i.tagName.toLowerCase();o==="ul"||o==="ol"||o==="menu"||((e=i.getAttribute("role"))==null?void 0:e.trim().toLowerCase())==="list"||a.push({ruleId:"listitem",selector:m(n),html:u(n),impact:"serious",message:"<li> is not contained in a <ul>, <ol>, or <menu>."})}return a}},ga={id:"document-title",wcag:["2.4.2"],level:"A",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').",prompt:"The page has no title or an empty title. Suggest a concise, descriptive <title> based on the page content sample in context. Good titles are specific and front-load the unique part: 'Product Details - Store Name' rather than 'Store Name - Product Details'.",run(t){var e,n,i;const a=t.querySelector("title");if(!a||!((e=a.textContent)!=null&&e.trim())){let o;const r=t.querySelector("h1");if((n=r==null?void 0:r.textContent)!=null&&n.trim())o=`h1: "${r.textContent.trim().slice(0,100)}"`;else if(t.body){const s=((i=t.body.textContent)==null?void 0:i.trim().replace(/\s+/g," "))||"";s&&(o=`Page text: "${s.slice(0,150)}"`)}return[{ruleId:"document-title",selector:"html",html:"<html>",impact:"serious",message:a?"Document <title> element is empty.":"Document is missing a <title> element.",context:o}]}return[]}},fa={id:"bypass",wcag:[],level:"A",tags:["best-practice"],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.',prompt:'The page has no mechanism for keyboard users to skip repeated content. The simplest fix is to wrap the primary content area in a <main> element — screen readers can jump directly to it. Alternatively, add a skip link as the first element in <body>: <a href="#main" class="skip-link">Skip to main content</a>, with a matching id on the target element. Use the context to understand what the page is missing.',run(t){if(t.querySelector('main, [role="main"], nav, [role="navigation"], aside, [role="complementary"], header, [role="banner"], footer, [role="contentinfo"], [role="search"], [role="region"]'))return[];const e=t.querySelector('a[href^="#"]');if(e){const o=e.getAttribute("href");if(o&&o.length>1){const r=o.slice(1);if(t.getElementById(r))return[]}}if(t.querySelector("h1, h2, h3, [role='heading']"))return[];const i=[];return i.push("no landmarks (<main>, <nav>, <header>, <footer>)"),i.push("no skip link"),i.push("no headings"),[{ruleId:"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: ${i.join(", ")}`}]}},va={id:"page-has-heading-one",wcag:[],level:"A",tags:["best-practice"],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 exactly one h1 that describes the main content, typically matching or similar to the page title.",prompt:"The page has no <h1> heading. Suggest appropriate h1 text based on the page title or content sample in context. The h1 should describe the page's main topic and typically be placed at the start of the main content area.",run(t){var r,s,l;const a=t.querySelector("h1");if(a&&v(a))return[];const e=t.querySelectorAll('[role="heading"][aria-level="1"]');for(const p of e)if(v(p))return[];const n=[],i=(s=(r=t.querySelector("title"))==null?void 0:r.textContent)==null?void 0:s.trim();i&&n.push(`Page title: "${i}"`);const o=t.querySelector("main");if(o){const p=((l=o.textContent)==null?void 0:l.trim().replace(/\s+/g," "))||"";p&&n.push(`Main content: "${p.slice(0,100)}"`)}return[{ruleId:"page-has-heading-one",selector:"html",html:"<html>",impact:"moderate",message:"Page does not contain a level-one heading.",context:n.length>0?n.join(", "):void 0}]}};function Te(t){if(!(t instanceof HTMLElement))return!1;if(t.style.display==="none"||t.style.visibility==="hidden")return!0;const a=t.getAttribute("width"),e=t.getAttribute("height");return(a==="0"||a==="1")&&(e==="0"||e==="1")}const ya={id:"frame-title",wcag:["4.1.2"],level:"A",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'.",prompt:"This iframe has no accessible name. Based on the src URL in context, suggest a descriptive title attribute that tells screen reader users what the frame contains. For example: 'YouTube video player', 'Google Map', 'Payment form', 'Chat widget'. If the frame appears decorative or non-essential, recommend adding aria-hidden='true' instead.",run(t){const a=[];for(const e of t.querySelectorAll("iframe, frame")){if(b(e)||Te(e))continue;if(!v(e)){const i=e.getAttribute("src");a.push({ruleId:"frame-title",selector:m(e),html:u(e),impact:"serious",message:"Frame is missing an accessible name. Add a title attribute.",context:i?`src: "${i}"`:void 0})}}return a}},wa={id:"frame-title-unique",wcag:["4.1.2"],level:"A",tags:["best-practice"],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.",prompt:"Suggest a more specific title to distinguish this frame from others.",run(t){var i;const a=[],e=Array.from(t.querySelectorAll("iframe[title], frame[title]")),n=new Map;for(const o of e){if(b(o)||Te(o))continue;const r=(i=o.getAttribute("title"))==null?void 0:i.trim().toLowerCase();if(r){const s=n.get(r)||[];s.push(o),n.set(r,s)}}for(const[,o]of n)if(o.length>1)for(const r of o.slice(1))a.push({ruleId:"frame-title-unique",selector:m(r),html:u(r),impact:"moderate",message:"Frame title is not unique. Use a distinct title for each frame."});return a}},Aa={id:"empty-heading",wcag:[],level:"A",tags:["best-practice"],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.",prompt:"This heading element has no text content, so screen reader users encounter a blank heading when navigating. Either add descriptive text that summarizes the following section, or if this element is used only for visual styling, replace it with a styled <p> or <div> and use CSS for appearance. The context includes nearby content to help suggest appropriate heading text.",run(t){var n;const a=[],e=t.querySelectorAll('h1, h2, h3, h4, h5, h6, [role="heading"]');for(const i of e)if(!b(i)&&!v(i)){let o;const r=i.nextElementSibling;if(r){const s=((n=r.textContent)==null?void 0:n.trim().replace(/\s+/g," "))||"";s&&(o=s.slice(0,100))}a.push({ruleId:"empty-heading",selector:m(i),html:u(i),impact:"minor",message:"Heading is empty. Add text content or remove the heading element.",context:o?`Following content: "${o}"`:void 0})}return a}},xa={id:"meta-viewport",wcag:["1.4.4"],level:"AA",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.",prompt:"The viewport meta tag restricts zooming, which prevents low-vision users from enlarging content. Show the current content attribute and a corrected version with the problematic properties removed. Keep other viewport properties (like width=device-width, initial-scale=1) intact — only remove user-scalable=no and maximum-scale restrictions.",run(t){const a=[],e=t.querySelector('meta[name="viewport"]');if(!e)return[];const n=e.getAttribute("content")||"",i=n.toLowerCase(),o=i.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:"meta-viewport",selector:m(e),html:u(e),impact:"critical",message:`Viewport disables user scaling (user-scalable=${s}). Remove this restriction.`,context:`content: "${n}"`})}const r=i.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:"meta-viewport",selector:m(e),html:u(e),impact:"critical",message:`Viewport maximum-scale=${l} restricts zooming. Set to at least 2 or remove.`,context:`content: "${n}"`})}return a}},ka={id:"meta-refresh",wcag:["2.2.1","2.2.4","3.2.5"],level:"A",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.",prompt:"Explain why meta refresh is problematic and suggest server-side alternatives.",run(t){for(const a of t.querySelectorAll('meta[http-equiv="refresh"]')){const e=a.getAttribute("content")||"",n=e.match(/^(\d+)/);if(!n)continue;const i=parseInt(n[1],10);if(/^\d+\s*[;,]\s*url\s*=/i.test(e)||/^\d+\s*[;,]\s*['"]?\s*https?:/i.test(e))return i>0&&i<=72e3?[{ruleId:"meta-refresh",selector:m(a),html:u(a),impact:"critical",message:`Page redirects after ${i} seconds without warning. Use server-side redirect.`}]:[];if(i>0&&i<=72e3)return[{ruleId:"meta-refresh",selector:m(a),html:u(a),impact:"critical",message:`Page auto-refreshes after ${i} seconds. Provide user control over refresh.`}]}return[]}},Sa={id:"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",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.",prompt:"Suggest static alternatives to the blinking effect."},Ia=S(Sa),Ea={id:"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",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.",prompt:"Suggest static alternatives or accessible carousel patterns."},qa=S(Ea),La={id:"p-as-heading",wcag:[],level:"A",tags:["best-practice"],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.",prompt:"Suggest the appropriate heading level based on the document structure.",run(t){var e,n;const a=[];for(const i of t.querySelectorAll("p")){if(b(i))continue;const o=i.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=((e=i.className)==null?void 0:e.toLowerCase())||"",p=/\bh[1-6]\b|\bheading\b/.test(l),c=((n=i.textContent)==null?void 0:n.trim())||"",d=c.length>0&&c.length<50,h=!c.match(/[.!?,;:]$/);if((r&&s||r&&p)&&d&&h){const y=i.nextElementSibling;y&&(y.tagName==="P"||y.tagName==="DIV"||y.tagName==="UL")&&a.push({ruleId:"p-as-heading",selector:m(i),html:u(i),impact:"serious",message:"Paragraph appears to be styled as a heading. Use an h1-h6 element instead."})}}return a}},Ta={id:"aria-roles",wcag:["4.1.2"],level:"A",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.",prompt:"Identify the invalid role and suggest the correct spelling or a valid alternative role that matches the intended purpose.",run(t){const a=[];for(const e of t.querySelectorAll("[role]")){const o=e.getAttribute("role").replace(/[\u201C\u201D\u2018\u2019\u00AB\u00BB]/g,"").split(/\s+/).filter(Boolean);!o.some(s=>ke(s))&&o.length>0&&a.push({ruleId:"aria-roles",selector:m(e),html:u(e),impact:"critical",message:`Invalid ARIA role "${o[0]}".`})}return a}},Ra={id:"aria-valid-attr",wcag:["4.1.2"],level:"A",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+).",prompt:"Identify the misspelled attribute and provide the correct spelling.",run(t){return Z(t).validAttr}},Ca={id:"aria-valid-attr-value",wcag:["4.1.2"],level:"A",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.",prompt:"Show the invalid value and list the valid values for this specific attribute.",run(t){return Z(t).validAttrValue}},Na={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"]},Ma={id:"aria-required-attr",wcag:["4.1.2"],level:"A",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.",prompt:"State which attribute is required for this role and suggest an appropriate value based on the element's apparent state.",run(t){const a=[];for(const e of t.querySelectorAll("[role]")){if(b(e)||e instanceof HTMLElement&&e.style.display==="none")continue;const n=e.getAttribute("role").trim().toLowerCase(),i=Na[n];if(i&&!(n==="checkbox"&&e instanceof HTMLInputElement&&e.type==="checkbox")&&!(n==="radio"&&e instanceof HTMLInputElement&&e.type==="radio")&&!(n==="option"&&e instanceof HTMLOptionElement)&&!(n==="heading"&&/^h[1-6]$/i.test(e.tagName))){if(n==="separator"){const o=e.getAttribute("tabindex");if(!o||o==="-1")continue}if(!(e.tagName.toLowerCase()==="hr"&&!e.hasAttribute("role"))){for(const o of i)if(!e.hasAttribute(o)){a.push({ruleId:"aria-required-attr",selector:m(e),html:u(e),impact:"critical",message:`Role "${n}" requires attribute "${o}".`});break}}}}return a}};function Da(t){var o,r,s;const a=[],e=t.className;e&&typeof e=="string"&&e.trim()&&a.push(`Classes: ${e.trim().slice(0,100)}`);const n=t.closest("form");if(n){const l=n.getAttribute("aria-label")||((r=(o=n.querySelector("legend"))==null?void 0:o.textContent)==null?void 0:r.trim());l&&a.push(`Form: ${l.slice(0,60)}`)}const i=t.parentElement;if(i){const l=i.closest("h1, h2, h3, h4, h5, h6")||i.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(`
3
+ `):void 0}const Ha={id:"button-name",wcag:["4.1.2"],level:"A",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.",prompt:"Based on the button's content, class, or context, suggest an appropriate aria-label describing the action it performs.",run(t){const a=[];for(const e of t.querySelectorAll('button, [role="button"]')){if(b(e)||q(e))continue;const n=e.getAttribute("role");if((n==="none"||n==="presentation")&&!(e.matches('button:not([disabled]), [tabindex]:not([tabindex="-1"])')||e.tagName.toLowerCase()==="button"&&!e.disabled)||e.getRootNode()instanceof ShadowRoot)continue;v(e)||a.push({ruleId:"button-name",selector:m(e),html:u(e),impact:"critical",message:"Button has no discernible text.",context:Da(e)})}return a}},$a={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"])},Pa=new Set(["aria-atomic","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","aria-braillelabel","aria-brailleroledescription"]),za={id:"aria-allowed-attr",wcag:["4.1.2"],level:"A",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.",prompt:"The ARIA attribute listed in context is not supported on this element's role. Either remove the attribute (if the behavior it describes isn't needed), or change the element's role to one that supports it. The context lists which attributes ARE allowed on this role — use that to suggest alternatives if applicable.",run(t){const a=[];for(const e of t.querySelectorAll("[role], [aria-*]")){if(b(e))continue;const n=N(e);if(!n)continue;const i=$a[n];if(i)for(const o of e.attributes){if(!o.name.startsWith("aria-")||Pa.has(o.name)||i.has(o.name))continue;const r=i.size>0?[...i].join(", "):"none (only global ARIA attributes)";a.push({ruleId:"aria-allowed-attr",selector:m(e),html:u(e),impact:"critical",message:`ARIA attribute "${o.name}" is not allowed on role "${n}".`,context:`Attribute: ${o.name}="${o.value}", role: ${n}, allowed role-specific attributes: ${r}`})}}return a}},Fa=new Set(["base","col","colgroup","head","html","keygen","meta","param","script","source","style","template","title","track"]),L={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=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 ja(t){var e;const a=t.tagName.toLowerCase();if(Fa.has(a))return"none";if(a==="a"&&t.hasAttribute("href"))return L["a[href]"];if(a==="img"&&t.getAttribute("alt")==="")return L["img[alt='']"];if(a==="input"){const i=`input[type=${((e=t.getAttribute("type"))==null?void 0:e.toLowerCase())||"text"}]`;return i in L?L[i]:"none"}return L[a]||"any"}const Wa={id:"aria-allowed-role",wcag:["4.1.2"],level:"A",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.",prompt:"Consider implicit roles: header=banner, nav=navigation, main=main, footer=contentinfo, aside=complementary, article=article, section=region (when labeled). Explain if this role is redundant (matches implicit) or invalid (conflicts). Suggest removing it or restructuring.",run(t){var e;const a=[];for(const n of t.querySelectorAll("[role]")){if(b(n))continue;const i=(e=n.getAttribute("role"))==null?void 0:e.trim().toLowerCase();if(!i)continue;const o=Q(n);if(o&&i===o)continue;const r=ja(n);r==="none"?a.push({ruleId:"aria-allowed-role",selector:m(n),html:u(n),impact:"minor",message:`Element <${n.tagName.toLowerCase()}> should not have an explicit role.`}):r!=="any"&&!r.has(i)&&a.push({ruleId:"aria-allowed-role",selector:m(n),html:u(n),impact:"minor",message:`Role "${i}" is not allowed on element <${n.tagName.toLowerCase()}>.`})}return a}},pe={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"]]},Ua=new Set(["doc-bibliography","doc-endnotes","grid","group","list","listbox","rowgroup","table","tablist","tree","treegrid"]),he={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"]};function Ba(t,a){var r;const e=((r=t.getAttribute("aria-owns"))==null?void 0:r.split(/\s+/))||[],n=t.ownerDocument,i=new Set;let o=!1;for(const s of t.querySelectorAll("*")){if(b(s))continue;o=!0;const l=N(s);l&&i.add(l)}for(const s of e){const l=n.getElementById(s);if(l&&!b(l)){o=!0;const p=N(l);p&&i.add(p)}}if(!o)return"empty";for(const s of a)if(!s.some(l=>i.has(l)))return"fail";return"pass"}const Oa={id:"aria-required-children",wcag:["1.3.1"],level:"A",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>).",prompt:"State which child role(s) are required and suggest adding elements with those roles, or using equivalent native HTML elements.",run(t){var e;const a=[];for(const n of t.querySelectorAll("[role]")){if(b(n))continue;const i=(e=n.getAttribute("role"))==null?void 0:e.trim().toLowerCase();if(!i||!(i in pe)||n.getAttribute("aria-busy")==="true")continue;if(i==="combobox"){if(n.getAttribute("aria-expanded")!=="true")continue;const l=n.tagName.toLowerCase();if(l==="input"||l==="textarea")continue}const o=pe[i],r=Ba(n,o);if(r==="pass"||r==="empty"&&Ua.has(i))continue;const s=o.map(l=>l.join(" or ")).join(", ");a.push({ruleId:"aria-required-children",selector:m(n),html:u(n),impact:"critical",message:`Role "${i}" requires children with role: ${s}.`})}return a}},Va={id:"aria-required-parent",wcag:["1.3.1"],level:"A",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>).",prompt:"State which parent role is required and suggest wrapping in an element with that role, or using equivalent native HTML structure.",run(t){var e;const a=[];for(const n of t.querySelectorAll("[role]")){if(b(n))continue;const i=(e=n.getAttribute("role"))==null?void 0:e.trim().toLowerCase();if(!i||!(i in he))continue;const o=he[i];let r=n.parentElement,s=!1;for(;r&&r!==t.documentElement;){const l=N(r);if(l&&o.includes(l)){s=!0;break}r=r.parentElement}s||a.push({ruleId:"aria-required-parent",selector:m(n),html:u(n),impact:"critical",message:`Role "${i}" must be contained within: ${o.join(", ")}.`})}return a}},be=["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(", ");function _a(t){let a=t;const e=t.ownerDocument,n=e.defaultView;for(;a&&a!==e.body;){if(a.style.display==="none"||a.style.visibility==="hidden")return!1;if(n){const i=n.getComputedStyle(a);if(i.display==="none"||i.visibility==="hidden")return!1}a=a.parentElement}return!0}const Ga={id:"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",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.",prompt:"Instruct to remove aria-hidden='true' from the body element.",skipAriaHidden:!1},Ya=S(Ga),Xa={id:"aria-hidden-focus",wcag:["4.1.2"],level:"A",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.",prompt:"This element can receive keyboard focus but is inside an aria-hidden region, making it invisible to screen readers. The context explains why it's focusable. Fix by either: (1) adding tabindex='-1' to remove it from tab order, (2) moving it outside the aria-hidden region, or (3) removing aria-hidden='true' from the ancestor if the content should be accessible.",run(t){const a=[];for(const e of t.querySelectorAll('[aria-hidden="true"]')){if(e===t.body)continue;const n=[...e.querySelectorAll(be)];e.matches(be)&&n.push(e);for(const i of n)if(i instanceof HTMLElement){const o=i.getAttribute("tabindex");if(o==="-1"||i.disabled||i instanceof HTMLInputElement&&i.type==="hidden"||!_a(i))continue;const r=i.tagName.toLowerCase();let s;o!==null?s=`has tabindex="${o}"`:r==="a"&&i.hasAttribute("href")?s="is a link with href":r==="button"?s="is a <button>":r==="input"?s=`is an <input type="${i.type}">`:r==="select"?s="is a <select>":r==="textarea"?s="is a <textarea>":r==="iframe"?s="is an <iframe>":s=`is a natively focusable <${r}>`;const l=i===e?i:i.closest('[aria-hidden="true"]');a.push({ruleId:"aria-hidden-focus",selector:m(i),html:u(i),impact:"serious",message:"Focusable element is inside an aria-hidden region.",context:`Focusable because: ${s}. aria-hidden ancestor: ${l?u(l):"unknown"}`})}}return a}},Ka={id:"aria-command-name",wcag:["4.1.2"],level:"A",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.",prompt:"Based on the element's content or context, suggest an aria-label describing what this command does.",run(t){var e;const a=[];for(const n of t.querySelectorAll('[role="button"], [role="link"], [role="menuitem"]')){if(b(n)||q(n)||n.getRootNode()instanceof ShadowRoot||n.tagName.toLowerCase()==="button"||n.tagName.toLowerCase()==="a")continue;if(!v(n)){const o=n.querySelector("img[alt]");if((e=o==null?void 0:o.getAttribute("alt"))!=null&&e.trim())continue;a.push({ruleId:"aria-command-name",selector:m(n),html:u(n),impact:"serious",message:"ARIA command has no accessible name."})}}return a}},Ja={id:"aria-input-field-name",wcag:["4.1.2"],level:"A",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.",prompt:"Based on the context, suggest an aria-label describing what data this input field accepts.",run(t){const a=[],e='[role="combobox"], [role="listbox"], [role="searchbox"], [role="slider"], [role="spinbutton"], [role="textbox"]';for(const n of t.querySelectorAll(e)){if(b(n)||q(n)||n.getRootNode()instanceof ShadowRoot||n.matches("input, select, textarea"))continue;v(n)||a.push({ruleId:"aria-input-field-name",selector:m(n),html:u(n),impact:"serious",message:"ARIA input field has no accessible name."})}return a}},Qa={id:"aria-toggle-field-name",wcag:["4.1.2"],level:"A",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.",prompt:"Based on the context, suggest an aria-label describing what option this toggle controls.",run(t){const a=[],e='[role="checkbox"], [role="switch"], [role="radio"], [role="menuitemcheckbox"], [role="menuitemradio"]';for(const n of t.querySelectorAll(e)){if(b(n)||q(n)||n.getRootNode()instanceof ShadowRoot||n.matches('input[type="checkbox"], input[type="radio"]'))continue;v(n)||a.push({ruleId:"aria-toggle-field-name",selector:m(n),html:u(n),impact:"serious",message:"ARIA toggle field has no accessible name."})}return a}},Za={id:"aria-meter-name",wcag:["4.1.2"],level:"A",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.",prompt:"Based on the context or value attributes, suggest an aria-label describing what this meter measures.",run(t){const a=[];for(const e of t.querySelectorAll('[role="meter"], meter')){if(b(e))continue;v(e)||a.push({ruleId:"aria-meter-name",selector:m(e),html:u(e),impact:"serious",message:"Meter has no accessible name."})}return a}},en={id:"aria-progressbar-name",wcag:["4.1.2"],level:"A",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.",prompt:"Based on the context, suggest an aria-label describing what process this progressbar tracks.",run(t){const a=[];for(const e of t.querySelectorAll('[role="progressbar"], progress')){if(b(e))continue;v(e)||a.push({ruleId:"aria-progressbar-name",selector:m(e),html:u(e),impact:"serious",message:"Progressbar has no accessible name."})}return a}},tn={id:"aria-dialog-name",wcag:["4.1.2"],level:"A",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.",prompt:"Suggest adding aria-labelledby pointing to the dialog's heading element, or an aria-label describing the dialog's purpose.",run(t){const a=[];for(const e of t.querySelectorAll('[role="dialog"], [role="alertdialog"], dialog')){if(b(e))continue;v(e)||a.push({ruleId:"aria-dialog-name",selector:m(e),html:u(e),impact:"serious",message:"Dialog has no accessible name."})}return a}},an={id:"aria-tooltip-name",wcag:["4.1.2"],level:"A",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.",prompt:"Add text content to the tooltip describing the information it provides, or add aria-label.",run(t){const a=[];for(const e of t.querySelectorAll('[role="tooltip"]')){if(b(e))continue;v(e)||a.push({ruleId:"aria-tooltip-name",selector:m(e),html:u(e),impact:"serious",message:"Tooltip has no accessible name."})}return a}},nn={id:"aria-treeitem-name",wcag:["4.1.2"],level:"A",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.",prompt:"Add text content describing this tree item, or add aria-label.",run(t){const a=[];for(const e of t.querySelectorAll('[role="treeitem"]')){if(b(e))continue;v(e)||a.push({ruleId:"aria-treeitem-name",selector:m(e),html:u(e),impact:"serious",message:"Treeitem has no accessible name."})}return a}},on={id:"aria-prohibited-attr",wcag:["4.1.2"],level:"A",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.",prompt:"Identify the prohibited attribute and recommend removing it from this element.",run(t){return Z(t).prohibitedAttr}},rn=["a[href]","button:not([disabled])",'input:not([disabled]):not([type="hidden"])',"select:not([disabled])","textarea:not([disabled])",'[tabindex]:not([tabindex="-1"])'].join(", "),sn=["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 ge(t){const a=[];t.matches(rn)&&a.push("element is focusable");for(const e of sn)if(t.hasAttribute(e)){a.push(`has ${e}`);break}return(t.hasAttribute("aria-label")||t.hasAttribute("aria-labelledby"))&&a.push("has accessible name"),a}const ln={id:"presentation-role-conflict",wcag:["4.1.2"],level:"A",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.",prompt:"Identify the conflict (focusable or ARIA attribute) and suggest either removing the presentation role or removing the conflicting attribute/focusability.",run(t){const a=[];for(const e of t.querySelectorAll('[role="presentation"], [role="none"]')){if(b(e))continue;const n=ge(e);n.length>0&&a.push({ruleId:"presentation-role-conflict",selector:m(e),html:u(e),impact:"serious",message:`Presentation role conflicts with: ${n.join(", ")}. The role will be ignored.`})}for(const e of t.querySelectorAll('img[alt=""]')){if(b(e)||e.hasAttribute("role"))continue;const n=ge(e);n.length>0&&a.push({ruleId:"presentation-role-conflict",selector:m(e),html:u(e),impact:"serious",message:`Element with implicit presentation role (alt="") conflicts with: ${n.join(", ")}. The decorative role will be ignored.`})}return a}},cn={id:"summary-name",wcag:["4.1.2"],level:"A",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.",prompt:"Based on the surrounding context or details content, suggest text to add inside the <summary> element.",run(t){const a=[];for(const e of t.querySelectorAll("details > summary:first-of-type")){if(b(e))continue;v(e)||a.push({ruleId:"summary-name",selector:m(e),html:u(e),impact:"serious",message:"<summary> element has no accessible name. Add descriptive text."})}return a}};function dn(t){var i,o;const a=[],e=t.getAttribute("href");e&&a.push(`href: ${e}`);const n=t.parentElement;if(n){const r=n.closest("h1, h2, h3, h4, h5, h6");if((i=r==null?void 0:r.textContent)!=null&&i.trim())a.push(`Nearby heading: ${r.textContent.trim().slice(0,80)}`);else{const s=(o=n.textContent)==null?void 0:o.trim().slice(0,100);s&&a.push(`Parent text: ${s}`)}}return a.length>0?a.join(`
4
+ `):void 0}const un={id:"link-name",wcag:["2.4.4","4.1.2"],level:"A",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.",prompt:"Based on the href or surrounding context, suggest descriptive link text or an aria-label.",run(t){const a=[];for(const e of t.querySelectorAll('a[href], area[href], [role="link"]')){if(b(e)||q(e)||e.getRootNode()instanceof ShadowRoot)continue;v(e)||a.push({ruleId:"link-name",selector:m(e),html:u(e),impact:"serious",message:"Link has no discernible text.",context:dn(e)})}return a}},mn={id:"skip-link",wcag:["2.4.1"],level:"A",tags:["best-practice"],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.",prompt:"A skip link is a single <a href='#main'>Skip to main content</a> as the first element in <body>. It can be visually hidden with CSS until focused. Explain this simple pattern.",run(t){const a=[],e=t.querySelectorAll('a[href^="#"]');for(const n of e){const i=n.getAttribute("href");if(!i||i==="#")continue;const o=A(n).toLowerCase();if(!(o.includes("skip")||o.includes("jump")||o.includes("main content")||o.includes("navigation")))continue;const s=i.slice(1);t.getElementById(s)||a.push({ruleId:"skip-link",selector:m(n),html:u(n),impact:"moderate",message:`Skip link points to "#${s}" which does not exist on the page.`})}return a}},pn=new Set(["block","flex","grid","table","table-cell","list-item","flow-root"]),hn=new Set(["inline","inline-block","inline-flex","inline-grid"]);function bn(t){let a=t.parentElement;for(;a;){const e=w(a).display;if(pn.has(e))return gn(a)?a:null;a=a.parentElement}return null}function gn(t){const a=t.ownerDocument.createTreeWalker(t,NodeFilter.SHOW_TEXT);let e="",n;for(;n=a.nextNode();){if(!n.data.trim())continue;let i=n.parentElement,o=!1;for(;i&&i!==t;){if(i.tagName==="A"){o=!0;break}i=i.parentElement}o||(e+=n.data)}return new RegExp("\\p{L}{2,}","u").test(e)}function fn(t,a){const e=t.ownerDocument.createTreeWalker(t,NodeFilter.SHOW_TEXT);let n;for(;n=e.nextNode();){if(!n.data.trim())continue;let i=n.parentElement,o=!1,r=i;for(;r&&r!==t;){if(r.tagName==="A"){o=!0;break}r=r.parentElement}if(!o&&i)return D(w(i).color)}return null}function vn(t,a,e){const n=e.textDecorationLine||e.textDecoration||"",i=a.textDecorationLine||a.textDecoration||"";if((i.includes("underline")||i.includes("line-through"))&&i!==n)return!0;const o=parseFloat(a.borderBottomWidth)||0,r=a.borderBottomStyle||"";if(o>0&&r!=="none"&&r!=="hidden")return!0;const s=parseFloat(a.outlineWidth)||0,l=a.outlineStyle||"";if(s>0&&l!=="none")return!0;const p=a.backgroundImage||"";if(p&&p!=="none"&&p!=="initial")return!0;const c=W(e.fontWeight),d=W(a.fontWeight);if(Math.abs(d-c)>=300||a.fontStyle!==e.fontStyle)return!0;const h=parseFloat(a.fontSize)||16,g=parseFloat(e.fontSize)||16;if(g>0&&h/g>=1.2)return!0;for(const f of t.querySelectorAll("*")){const y=w(f),x=y.textDecorationLine||y.textDecoration||"";if((x.includes("underline")||x.includes("line-through"))&&x!==n||Math.abs(W(y.fontWeight)-c)>=300)return!0}return!1}function W(t){return t==="bold"?700:t==="normal"?400:parseInt(t)||400}function fe(t,a,e){return"#"+[t,a,e].map(n=>n.toString(16).padStart(2,"0")).join("")}const yn={id:"link-in-text-block",wcag:["1.4.1"],level:"A",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.",prompt:"Explain how to make this link visually distinguishable without relying on color alone.",run(t){const a=[];for(const e of t.querySelectorAll("a[href]")){if(b(e)||!A(e).trim()||e.closest('nav, header, footer, [role="navigation"], [role="banner"], [role="contentinfo"]'))continue;const n=w(e),i=n.display||"inline";if(!hn.has(i))continue;const o=bn(e);if(!o)continue;const r=w(o);if(vn(e,n,r))continue;const s=D(n.color),l=fn(o);if(!s||!l)continue;const p=M(...s),c=M(...l),d=Le(p,c);if(d<1.1||d>=3)continue;const h=fe(...s),g=fe(...l),f=`link color: ${h} rgb(${s.join(", ")}), surrounding text: ${g} rgb(${l.join(", ")}), ratio: ${d.toFixed(2)}:1`;a.push({ruleId:"link-in-text-block",selector:m(e),html:u(e),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:f})}return a}},wn={id:"html-has-lang",wcag:["3.1.1"],level:"A",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 (e.g., lang='en' for English, lang='es' for Spanish).",prompt:`The page is missing a lang attribute on <html>. Use the text sample in context to determine the primary language and suggest the correct 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). Add lang to the <html> element: <html lang="...">.`,run(t){var e,n;const a=t.documentElement;if(a.tagName.toLowerCase()!=="html")return[];if(!t.doctype&&t.body){const i=t.body.children;if(i.length>0&&Array.from(i).every(o=>o.tagName.toLowerCase()==="svg"||o.tagName.toLowerCase()==="math"))return[]}if(!((e=a.getAttribute("lang"))!=null&&e.trim())){let i;if(t.body){const o=((n=t.body.textContent)==null?void 0:n.trim().replace(/\s+/g," "))||"";o&&(i=o.slice(0,200))}return[{ruleId:"html-has-lang",selector:m(a),html:u(a),impact:"serious",message:"<html> element missing lang attribute.",context:i?`Page text sample: "${i}"`:void 0}]}return[]}},An=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(" ")),xn=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(" ")),kn=/^[a-z]{2,8}(-[a-z0-9]{1,8})*$/i;function Re(t){if(!kn.test(t))return!1;const a=t.split("-")[0].toLowerCase();return a.length===2?An.has(a):a.length===3?!xn.has(a):!1}const Sn={id:"html-lang-valid",wcag:["3.1.1"],level:"A",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.",prompt:"Suggest the correct BCP 47 language tag based on the invalid value provided.",run(t){var e;const a=(e=t.documentElement.getAttribute("lang"))==null?void 0:e.trim();return a&&!Re(a)?[{ruleId:"html-lang-valid",selector:"html",html:u(t.documentElement),impact:"serious",message:`Invalid lang attribute value "${a}".`}]:[]}};function ve(t){var n;const a=t.ownerDocument.createTreeWalker(t,NodeFilter.SHOW_TEXT);let e;for(;e=a.nextNode();){if(!e.data.trim())continue;const i=e.parentElement;if(!i||i instanceof HTMLElement&&(i.hidden||i.style.display==="none"))continue;let o=i,r=!1;for(;o&&o!==t;){if(o.hasAttribute("lang")){r=!0;break}o=o.parentElement}if(!r)return!0}for(const i of t.querySelectorAll("img[alt]")){if(!((n=i.getAttribute("alt"))==null?void 0:n.trim()))continue;let r=i.parentElement,s=!1;for(;r&&r!==t;){if(r.hasAttribute("lang")){s=!0;break}r=r.parentElement}if(!s)return!0}return!1}const In={id:"valid-lang",wcag:["3.1.2"],level:"AA",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.",prompt:"Identify the content's language and suggest the correct BCP 47 tag.",run(t){const a=[];for(const e of t.querySelectorAll("[lang]")){if(b(e)||e===t.documentElement)continue;const n=e.getAttribute("lang"),i=n==null?void 0:n.trim();if(n&&!i){ve(e)&&a.push({ruleId:"valid-lang",selector:m(e),html:u(e),impact:"serious",message:"Empty lang attribute value."});continue}i&&ve(e)&&(Re(i)||a.push({ruleId:"valid-lang",selector:m(e),html:u(e),impact:"serious",message:`Invalid lang attribute value "${i}".`}))}return a}},En={id:"html-xml-lang-mismatch",wcag:["3.1.1"],level:"A",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.",prompt:"Explain whether to remove xml:lang or align it with the lang value.",run(t){var i,o;const a=t.documentElement,e=(i=a.getAttribute("lang"))==null?void 0:i.trim().toLowerCase(),n=(o=a.getAttribute("xml:lang"))==null?void 0:o.trim().toLowerCase();if(e&&n){const r=e.split("-")[0],s=n.split("-")[0];if(r!==s)return[{ruleId:"html-xml-lang-mismatch",selector:"html",html:u(a),impact:"moderate",message:`lang="${e}" and xml:lang="${n}" do not match.`}]}return[]}},qn={id:"td-headers-attr",wcag:["1.3.1"],level:"A",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.",prompt:"Identify the invalid header ID reference and suggest the correct ID or how to fix it.",run(t){const a=[];for(const e of t.querySelectorAll("td[headers]")){if(b(e))continue;const n=e.closest("table");if(!n)continue;const i=e.getAttribute("id"),o=e.getAttribute("headers").split(/\s+/);for(const r of o){if(r===i){a.push({ruleId:"td-headers-attr",selector:m(e),html:u(e),impact:"serious",message:`Headers attribute references the cell itself ("${r}").`});break}if(!n.querySelector(`th#${CSS.escape(r)}, td#${CSS.escape(r)}`)){a.push({ruleId:"td-headers-attr",selector:m(e),html:u(e),impact:"serious",message:`Headers attribute references non-existent ID "${r}".`});break}}}return a}},Ln={id:"th-has-data-cells",wcag:["1.3.1"],level:"A",description:"Table headers should be associated with data cells.",guidance:"A table with header cells (th) but no data cells (td) is likely a misuse of table markup for layout or has missing content. Either add data cells that the headers describe, or use appropriate non-table markup if this is not tabular data.",prompt:"Explain whether this table needs data cells or if non-table layout would be more appropriate.",run(t){const a=[];for(const e of t.querySelectorAll("table")){if(b(e)||e.getAttribute("role")==="presentation"||e.getAttribute("role")==="none")continue;const n=e.querySelectorAll("th"),i=e.querySelectorAll("td");n.length>0&&i.length===0&&a.push({ruleId:"th-has-data-cells",selector:m(e),html:u(e),impact:"serious",message:"Table has header cells but no data cells."})}return a}},Tn={id:"td-has-header",wcag:["1.3.1"],level:"A",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.",prompt:"Explain whether to use scope attributes on headers or headers attribute on this cell.",run(t){var e,n;const a=[];for(const i of t.querySelectorAll("table")){if(b(i)||i.getAttribute("role")==="presentation"||i.getAttribute("role")==="none")continue;const o=i.querySelectorAll("tr"),r=o.length;let s=0;for(const d of o){const h=d.querySelectorAll("td, th");let g=0;for(const f of h)g+=parseInt(f.getAttribute("colspan")||"1",10);s=Math.max(s,g)}if(r<=3&&s<=3)continue;const l=i.querySelector("th")!==null,p=i.querySelector("th[scope]")!==null,c=i.querySelector("td[headers]")!==null;if(l)for(const d of i.querySelectorAll("td")){if(b(d)||d.hasAttribute("headers"))continue;const h=d.closest("tr");if(!h)continue;const g=h.querySelector("th")!==null,f=Array.from(h.children).indexOf(d);let y=!1;const x=i.querySelector("thead");if(x){const k=x.querySelector("tr");k&&((e=k.querySelectorAll("th, td")[f])==null?void 0:e.tagName.toLowerCase())==="th"&&(y=!0)}if(!y){const k=i.querySelector("tbody > tr, tr");k&&((n=k.querySelectorAll("th, td")[f])==null?void 0:n.tagName.toLowerCase())==="th"&&(y=!0)}if(!g&&!y&&!p&&!c){a.push({ruleId:"td-has-header",selector:m(d),html:u(d),impact:"serious",message:"Data cell has no associated header. Add th elements with scope, or headers attribute."});break}}}return a}},Rn={id:"scope-attr-valid",wcag:["1.3.1"],level:"A",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.",prompt:"Explain which scope value (row, col, rowgroup, colgroup) is appropriate for this header.",run(t){var n;const a=[],e=new Set(["row","col","rowgroup","colgroup"]);for(const i of t.querySelectorAll("th[scope]")){if(b(i))continue;const o=(n=i.getAttribute("scope"))==null?void 0:n.toLowerCase();o&&!e.has(o)&&a.push({ruleId:"scope-attr-valid",selector:m(i),html:u(i),impact:"moderate",message:`Invalid scope value "${o}". Use row, col, rowgroup, or colgroup.`})}return a}},Cn={id:"empty-table-header",wcag:[],level:"A",tags:["best-practice"],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.",prompt:"Suggest header text based on the column/row content, or explain if this should be a td instead.",run(t){const a=[];for(const e of t.querySelectorAll("th")){if(b(e))continue;const n=e.closest("table");(n==null?void 0:n.getAttribute("role"))==="presentation"||(n==null?void 0:n.getAttribute("role"))==="none"||v(e)||a.push({ruleId:"empty-table-header",selector:m(e),html:u(e),impact:"minor",message:"Table header cell is empty. Add text or use aria-label."})}return a}},U=["aria-labelledby","aria-describedby","aria-controls","aria-owns","aria-flowto"],Nn={id:"duplicate-id-aria",wcag:["4.1.2"],level:"A",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.",prompt:"Identify which attribute references this ID and suggest a unique replacement.",run(t){const a=[],e=new Set;for(const i of t.querySelectorAll("[aria-labelledby], [aria-describedby], [aria-controls], [aria-owns], [aria-flowto]"))for(const o of U){const r=i.getAttribute(o);r&&r.split(/\s+/).forEach(s=>e.add(s))}for(const i of t.querySelectorAll("label[for]")){const o=i.getAttribute("for");o&&e.add(o)}const n=new Map;for(const i of t.querySelectorAll("[id]"))e.has(i.id)&&(i instanceof HTMLElement&&(i.style.display==="none"||i.style.visibility==="hidden"||i.hidden)||n.set(i.id,(n.get(i.id)??0)+1));for(const[i,o]of n){if(o<=1)continue;const r=t.querySelectorAll(`#${CSS.escape(i)}`),s=t.querySelector(U.map(c=>`[${c}~="${CSS.escape(i)}"]`).join(", ")),l=t.querySelector(`label[for="${CSS.escape(i)}"]`);let p;if(s){const c=U.find(d=>{var h;return(h=s.getAttribute(d))==null?void 0:h.split(/\s+/).includes(i)});c&&(p=c)}else l&&(p="label[for]");a.push({ruleId:"duplicate-id-aria",selector:m(r[1]),html:u(r[1]),impact:"critical",message:`Duplicate ID "${i}" referenced by ${p??"an accessibility attribute"}.`,context:`First element: ${u(r[0])}${p?`
5
+ Referenced by: ${p}`:""}`})}return a}},Mn={id:"video-caption",wcag:["1.2.2"],level:"A",description:"Video elements must have captions via <track kind='captions'>.",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.",prompt:"Explain how to add a captions track element to this video.",run(t){const a=[];for(const e of t.querySelectorAll("video")){if(b(e)||e.hasAttribute("muted")||e.hasAttribute("autoplay"))continue;e.querySelector('track[kind="captions"], track[kind="subtitles"]')||a.push({ruleId:"video-caption",selector:m(e),html:u(e),impact:"critical",message:"Video element has no captions track."})}return a}},Dn={id:"audio-caption",wcag:["1.2.1"],level:"A",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.",prompt:"Explain options for providing a text alternative: transcript link or aria-describedby.",run(t){const a=[];for(const e of t.querySelectorAll("audio")){if(b(e)||e.querySelector('track[kind="captions"], track[kind="descriptions"]')||e.hasAttribute("aria-describedby"))continue;const i=e.parentElement;i&&i.querySelector('a[href*="transcript"], a[href*="text"]')||a.push({ruleId:"audio-caption",selector:m(e),html:u(e),impact:"critical",message:"Audio element has no transcript or text alternative. Add a transcript or track element."})}return a}},Hn=new Set(["SCRIPT","STYLE","NOSCRIPT","TEMPLATE","IFRAME","OBJECT","EMBED","SVG","CANVAS","VIDEO","AUDIO","IMG","BR","HR"]);function ye([t,a,e]){return"#"+[t,a,e].map(n=>n.toString(16).padStart(2,"0")).join("")}function $n(t){return t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement||t instanceof HTMLSelectElement||t instanceof HTMLButtonElement?t.disabled:!!(t.closest("fieldset[disabled]")||t.getAttribute("aria-disabled")==="true")}function Pn(t,a){if(t.tagName!=="LABEL")return!1;const e=t,n=e.htmlFor;if(n){const r=a.getElementById(n);if(r&&(r.disabled||r.getAttribute("aria-disabled")==="true"))return!0}const i=e.querySelector("input, select, textarea, button");if(i&&(i.disabled||i.getAttribute("aria-disabled")==="true"))return!0;const o=e.id;return!!(o&&a.querySelector(`[aria-labelledby~="${o}"][aria-disabled="true"]`))}function zn(t){const a=t.clip;if(a&&a.startsWith("rect(")){const n=a.match(/[\d.]+/g);if(!n||n.every(i=>parseFloat(i)===0))return!0}const e=t.clipPath;if(e==="inset(50%)"||e==="inset(100%)")return!0;if(t.overflow==="hidden"&&t.position==="absolute"){const n=parseFloat(t.width),i=parseFloat(t.height);if(n<=1&&i<=1)return!0}return!1}function Fn(t){if(b(t))return!0;let a=t;for(;a;){const e=w(a);if(e.display==="none"||e.visibility==="hidden"||zn(e))return!0;a=a.parentElement}return!1}function jn(t){let a=1,e=t;for(;e;){const n=w(e),i=parseFloat(n.opacity);isNaN(i)||(a*=i),e=e.parentElement}return a}const Wn={grayscale:0,blur:0,"hue-rotate":0,invert:0,sepia:0,brightness:1,contrast:1,saturate:1,opacity:1};function Un(t){const a=parseFloat(t);return isNaN(a)?NaN:t.trim().endsWith("%")?a/100:a}const we=/([a-z-]+)\(([^)]*)\)/g;function Ae(t){let a,e=!1;for(we.lastIndex=0;a=we.exec(t);){e=!0;const n=Wn[a[1]];if(n===void 0||Un(a[2])!==n)return!1}return e}function Bn(t){let a=t;for(;a;){const e=w(a),n=e.filter;if(n&&n!=="none"&&n!=="initial"&&!Ae(n))return!0;const i=e.mixBlendMode;if(i&&i!=="normal"&&i!=="initial")return!0;const o=e.backdropFilter;if(o&&o!=="none"&&o!=="initial"&&!Ae(o))return!0;a=a.parentElement}return!1}function On(t){return t.closest("select")!==null}const Vn={id:"color-contrast",wcag:["1.4.3"],level:"AA",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.",prompt:"Suggest changing the text or background color to meet the minimum contrast ratio.",run(t){const a=[],e=t.body;if(!e)return[];const n=t.createTreeWalker(e,NodeFilter.SHOW_TEXT),i=new Set;let o;for(;o=n.nextNode();){if(!o.textContent||!o.textContent.trim())continue;const r=o.parentElement;if(!r||i.has(r)||(i.add(r),Hn.has(r.tagName)))continue;const s=r.tagName;if(s==="BODY"||s==="HTML"||On(r)||$n(r)||Pn(r,t)||Fn(r))continue;const l=w(r);if(parseFloat(l.opacity)===0||jn(r)<.1)continue;const p=l.textShadow;if(p&&p!=="none"&&p!=="initial"||Bn(r))continue;const c=D(l.color);if(!c)continue;const d=l.color.match(/rgba\(.+?,\s*([\d.]+)\s*\)/)||l.color.match(/rgba?\(.+?\/\s*([\d.]+%?)\s*\)/);if(d&&(d[1].endsWith("%")?parseFloat(d[1])/100:parseFloat(d[1]))===0||Je(r))continue;const h=Ye(r);if(!h)continue;const g=M(c[0],c[1],c[2]),f=M(h[0],h[1],h[2]),y=Le(g,f),x=et(r)?3:4.5;if(y<x){const k=Math.round(y*100)/100,z=ye(c),De=ye(h);a.push({ruleId:"color-contrast",selector:m(r),html:u(r),impact:"serious",message:`Insufficient color contrast ratio of ${k}:1 (required ${x}:1).`,context:`foreground: ${z} rgb(${c.join(", ")}), background: ${De} rgb(${h.join(", ")}), ratio: ${k}:1, required: ${x}:1`})}}return a}},te=[ga,fa,va,ya,wa,xa,ka,Ia,qa,st,ct,dt,ut,pt,ht,gt,ft,xt,qt,Lt,Tt,Rt,Pt,zt,Ft,Wt,Bt,Kt,Jt,Qt,Zt,Aa,La,ea,ta,aa,na,ia,oa,ra,sa,la,ca,ua,ba,ma,ha,Ta,Ra,Ca,Ma,za,Wa,Oa,Va,Ya,Xa,Ka,Ja,Qa,Za,en,tn,an,nn,on,ln,Ha,cn,un,mn,yn,wn,Sn,In,En,qn,Ln,Tn,Rn,Cn,Nn,Mn,Dn,Vn],Ce=new Set(["aria-allowed-role","aria-progressbar-name","aria-prohibited-attr","aria-required-children","aria-required-parent","aria-roles","aria-tooltip-name","aria-valid-attr","autocomplete-valid","definition-list","dlitem","duplicate-id-aria","link-in-text-block","list","listitem","scrollable-region-focusable","svg-img-alt","td-has-header","th-has-data-cells","valid-lang"]);let ae=[],Ne=new Set,Me=new Set,E,C;function _n(t){t.additionalRules&&(ae=t.additionalRules),t.disabledRules&&(Ne=new Set(t.disabledRules)),t.enabledRules&&(Me=new Set(t.enabledRules)),"locale"in t&&(E=t.locale||void 0),C=void 0}function P(){if(C)return C;const a=te.filter(e=>Ne.has(e.id)?!1:Me.has(e.id)?!0:!Ce.has(e.id)).concat(ae);return E?(C=at(a,E),C):a}function Gn(t){ne();const a=P(),e=E,n=[];let i=0;return{processChunk(o){const r=performance.now();for(;i<a.length;){try{n.push(...a[i].run(t))}catch{}if(i++,performance.now()-r>=o)break}return i<a.length},getViolations(){return e?ee(n,e):n}}}function ne(){Se(),xe(),He(),qe(),Ee(),ze()}function Yn(t){var n;ne();const a=P(),e=[];for(const i of a)try{e.push(...i.run(t))}catch{}return{url:((n=t.location)==null?void 0:n.href)??"",timestamp:Date.now(),violations:E?ee(e,E):e,ruleCount:a.length}}const Xn=new Map(te.map(t=>[t.id,t]));function Kn(t){if(E)return P().find(n=>n.id===t);const a=Xn.get(t);return a||ae.find(e=>e.id===t)}const Jn={"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."}},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."}},"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 exactly one h1 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."}},"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."}},"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."}},"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."}},"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."}},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."}},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."}},"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.",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.'}},"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."}},"input-image-alt":{description:'Image inputs (<input type="image">) must have alternate text via alt, aria-label, or aria-labelledby. The text should describe the button action, not the image.',guidance:"Image buttons (<input type='image'>) must have alternate text via alt, aria-label, or aria-labelledby. The text should describe the button action, not the image.",messages:{"Image input missing alt text.":"Image input missing alt text."}},"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.'}},"image-alt-redundant-words":{description:"Image alt text should not contain words like 'image', 'photo', or 'picture' — 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}.'}},"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."}},"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."}},"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."}},"server-side-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."}},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.",messages:{"Form element has no accessible label.":"Form element has no accessible label."}},"form-field-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."}},"select-name":{description:"Select elements must have a programmatically associated label via <label>, aria-label, or aria-labelledby.",guidance:"Select dropdowns need labels so users understand what choice they're making. Use a <label> element with a for attribute matching the select's id, or wrap the select in a <label>. For selects without visible labels, use aria-label. The first <option> is not a substitute for a proper label.",messages:{"Select element has no accessible name.":"Select element has no accessible name."}},"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."}},"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}".'}},"label-content-name-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}".'}},"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."}},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.'}},"focus-order-semantics":{description:"Elements that receive keyboard focus must have an appropriate role so assistive technologies can convey their purpose. Non-interactive elements with tabindex='0' need a valid interactive ARIA role.",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.'}},"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}>."}},"scrollable-region-focusable":{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."}},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.'}},"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}.":"Heading level {0} skipped from level {1}."}},"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."}},"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."}},"landmark-one-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."}},"landmark-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."}},"landmark-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."}},"landmark-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."}},"landmark-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."}},"landmark-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."}},"landmark-main-is-top-level":{description:"Main landmark should not be nested within another landmark.",guidance:"The main landmark must be a top-level landmark since it represents the primary content of the page. Do not nest <main> or role='main' inside article, aside, nav, or section elements.",messages:{"Main landmark is nested within another landmark.":"Main landmark is nested within another landmark."}},"landmark-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."}},"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."}},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."}},list:{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, restructure your CSS to style the <li> elements directly.",messages:{"List contains non-<li> child <{0}>.":"List contains non-<li> child <{0}>."}},listitem:{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>). Outside of these containers, assistive technologies cannot convey the list relationship. Wrap <li> elements in the appropriate list container.",messages:{"<li> is not contained in a <ul>, <ol>, or <menu>.":"<li> is not contained in a <ul>, <ol>, or <menu>."}},dlitem:{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>."}},"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 restructure using proper definition list markup.",messages:{"<dl> contains invalid child <{0}>.":"<dl> contains invalid child <{0}>."}},"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-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-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-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-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-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}>.'}},"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}.'}},"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-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-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."}},"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."}},"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."}},"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."}},"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."}},"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."}},"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."}},"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."}},"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-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}".'}},"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.'}},"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."}},"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."}},"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."}},"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.'}},"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."}},"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 (e.g., lang='en' for English, lang='es' for Spanish).",messages:{"<html> element missing lang attribute.":"<html> element missing lang attribute."}},"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}".'}},"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}".'}},"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.'}},"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}".'}},"th-has-data-cells":{description:"Table headers should be associated with data cells.",guidance:"A table with header cells (th) but no data cells (td) is likely a misuse of table markup for layout or has missing content. Either add data cells that the headers describe, or use appropriate non-table markup if this is not tabular data.",messages:{"Table has header cells but no data cells.":"Table has header cells but no data cells."}},"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."}},"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.'}},"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."}},"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}.'}},"video-caption":{description:"Video elements must have captions via <track kind='captions'>.",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."}},"audio-caption":{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."}},"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)."}}},Qn={"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>."}},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."}},"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."}},"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."}},"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."}},"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."}},"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."}},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."}},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."}},"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.'}},"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."}},"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."}},"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.'}},"image-alt-redundant-words":{description:"El texto alternativo de la imagen no debe contener palabras como 'imagen', 'foto' o 'fotografía': 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}.'}},"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."}},"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."}},"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."}},"server-side-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."}},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.",messages:{"Form element has no accessible label.":"El elemento de formulario no tiene etiqueta accesible."}},"form-field-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."}},"select-name":{description:"Los elementos select deben tener una etiqueta asociada programáticamente mediante <label>, aria-label o aria-labelledby.",guidance:"Los menús desplegables select necesitan etiquetas para que los usuarios comprendan qué elección están haciendo. Use un elemento <label> con un atributo for que coincida con el id del select, o envuelva el select en un <label>. Para selects sin etiquetas visibles, use aria-label. La primera <option> no es un sustituto de una etiqueta adecuada.",messages:{"Select element has no accessible name.":"El elemento select no tiene nombre accesible."}},"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."}},"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}".'}},"label-content-name-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}".'}},"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."}},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.'}},"focus-order-semantics":{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.'}},"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}>."}},"scrollable-region-focusable":{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."}},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.'}},"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}.":"Nivel de encabezado {0} saltado desde el nivel {1}."}},"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."}},"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."}},"landmark-one-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."}},"landmark-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."}},"landmark-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."}},"landmark-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."}},"landmark-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."}},"landmark-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."}},"landmark-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."}},"landmark-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."}},"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."}},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."}},list:{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, reestructure su CSS para estilizar los elementos <li> directamente.",messages:{"List contains non-<li> child <{0}>.":"La lista contiene un hijo no <li>: <{0}>."}},listitem:{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>."}},dlitem:{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>."}},"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 reestructure usando el marcado adecuado de lista de definiciones.",messages:{"<dl> contains invalid child <{0}>.":"<dl> contiene un hijo inválido <{0}>."}},"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-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-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-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-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-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}>.'}},"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}.'}},"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-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-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."}},"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."}},"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."}},"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."}},"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."}},"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."}},"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."}},"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."}},"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-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}".'}},"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.'}},"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."}},"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."}},"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."}},"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.'}},"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."}},"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 (por ejemplo, lang='en' para inglés, lang='es' para español).",messages:{"<html> element missing lang attribute.":"Al elemento <html> le falta el atributo lang."}},"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}".'}},"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}".'}},"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.'}},"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}".'}},"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."}},"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."}},"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.'}},"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."}},"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}.'}},"video-caption":{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."}},"audio-caption":{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."}},"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)."}}};exports.clearAllCaches=ne;exports.clearAriaAttrAuditCache=Ee;exports.clearAriaHiddenCache=Se;exports.clearColorCaches=qe;exports.clearComputedRoleCache=xe;exports.compileDeclarativeRule=S;exports.configureRules=_n;exports.createChunkedAudit=Gn;exports.defaultDisabledRuleIds=Ce;exports.getAccessibleName=v;exports.getAccessibleTextContent=A;exports.getActiveRules=P;exports.getComputedRole=N;exports.getHtmlSnippet=u;exports.getImplicitRole=Q;exports.getRuleById=Kn;exports.getSelector=m;exports.isAriaHidden=b;exports.isValidRole=ke;exports.localeEn=Jn;exports.localeEs=Qn;exports.querySelectorShadowAware=Ue;exports.registerLocale=tt;exports.rules=te;exports.runAudit=Yn;exports.translateViolations=ee;exports.validateDeclarativeRule=vt;