@accesslint/core 0.3.4 → 0.3.5
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 pe(){O=new WeakMap}function z(t){var i;const a=t.tagName.toLowerCase(),e=(i=t.getAttribute("type"))==null?void 0:i.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 L(t){var n;const a=O.get(t);if(a!==void 0)return a;const i=((n=t.getAttribute("role"))==null?void 0:n.trim().toLowerCase())||null||z(t);return O.set(t,i),i}let B=new WeakMap;function Se(){B=new WeakMap}function f(t){const a=B.get(t);if(a!==void 0)return a;const e=xe(t);return B.set(t,e),e}function xe(t){var r,o,s,u,h;const a=t.getAttribute("aria-labelledby");if(a){const l=a.split(/\s+/).map(m=>{const g=t.ownerDocument.getElementById(m);return g?w(g).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;if(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement||t instanceof HTMLSelectElement){if(t.id){const g=t.ownerDocument.querySelector(`label[for="${CSS.escape(t.id)}"]`),b=g?w(g).trim():"";if(b)return b}const l=t.closest("label"),m=l?w(l).trim():"";if(m)return m}const i=(o=t.getAttribute("title"))==null?void 0:o.trim();if(i)return i;if(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement){const l=(s=t.getAttribute("placeholder"))==null?void 0:s.trim();if(l)return l}const n=t.tagName.toLowerCase();if(n==="fieldset"){const l=t.querySelector(":scope > legend");if(l){const m=w(l).trim();if(m)return m}}if(n==="table"){const l=t.querySelector(":scope > caption");if(l){const m=w(l).trim();if(m)return m}}if(!(t instanceof HTMLInputElement)){const l=w(t).trim();if(l)return l}return t instanceof HTMLImageElement||t instanceof HTMLAreaElement?((u=t.alt)==null?void 0:u.trim())??"":t instanceof HTMLInputElement&&t.type==="image"?((h=t.alt)==null?void 0:h.trim())??"":""}const ke=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 ge(t){const a=t.trim().toLowerCase().replace(/[\u201C\u201D\u2018\u2019\u00AB\u00BB]/g,"");return ke.has(a)}let W=new WeakMap;function be(){W=new WeakMap}function p(t){const a=W.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=p(t.parentElement):e=!1,W.set(t,e),e}function Ie(t){return!!(t.getAttribute("aria-hidden")==="true"||t instanceof HTMLElement&&(t.hidden||t.style.display==="none"))}function w(t){var e,i,n,r,o;let a="";for(const s of t.childNodes)if(s.nodeType===3)a+=s.textContent??"";else if(s.nodeType===1){const u=s;if(!Ie(u)){const h=(e=u.tagName)==null?void 0:e.toLowerCase();if(h==="img"||h==="area"){const l=u.getAttribute("aria-labelledby");if(l){const m=l.split(/\s+/).map(g=>{var b,v;return((v=(b=u.ownerDocument.getElementById(g))==null?void 0:b.textContent)==null?void 0:v.trim())??""}).filter(Boolean);if(m.length){a+=m.join(" ");continue}}a+=((i=u.getAttribute("aria-label"))==null?void 0:i.trim())??u.getAttribute("alt")??((n=u.getAttribute("title"))==null?void 0:n.trim())??""}else if(h==="svg"){const l=(r=u.getAttribute("aria-label"))==null?void 0:r.trim();if(l)a+=l;else{const m=u.querySelector("title");m&&(a+=m.textContent??"")}}else(o=u.getAttribute("aria-label"))!=null&&o.trim()?a+=u.getAttribute("aria-label").trim():a+=w(u)}}return a}let _=new WeakMap;function Ee(){_=new WeakMap}function Te(t){return t.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}const Ce=["data-testid","data-test-id","data-cy","data-id","name","href","for","aria-label"];function Le(t){const a=t.tagName.toLowerCase();for(const i of Ce){const n=t.getAttribute(i);if(n!=null&&n.length>0&&n.length<100)return`${a}[${i}="${Te(n)}"]`}const e=t.parentElement;if(e){let i=0,n=0;for(let r=0;r<e.children.length;r++)e.children[r].tagName===t.tagName&&(i++,e.children[r]===t&&(n=i));if(i>1)return`${a}:nth-of-type(${n})`}return a}function H(t){if(t.id)return`#${CSS.escape(t.id)}`;const a=t.getRootNode(),e=a instanceof ShadowRoot?null:a.documentElement,i=[];let n=t;for(;n&&n!==e;){if(n!==t&&n.id){i.unshift(`#${CSS.escape(n.id)}`);break}if(i.unshift(Le(n)),i.length>=2){const r=i.join(" > ");try{const o=a.querySelectorAll(r);if(o.length===1&&o[0]===t)return r}catch{}}n=n.parentElement}return i.join(" > ")}function d(t){var r;const a=_.get(t);if(a!==void 0)return a;const e=[];let i=t;for(;i;){const o=i.getRootNode();if(o instanceof ShadowRoot)e.unshift({selector:H(i),delimiter:" >>> "}),i=o.host;else{const s=(r=o.defaultView)==null?void 0:r.frameElement;if(s)e.unshift({selector:H(i),delimiter:" >>>iframe> "}),i=s;else{e.unshift({selector:H(i),delimiter:""});break}}}const n=e.map((o,s)=>(s===0?"":o.delimiter)+o.selector).join("");return _.set(t,n),n}function qe(t){const a=[],e=[];let i=t;for(;i;){const r=i.indexOf(" >>>iframe> "),o=i.indexOf(" >>> ");if(r!==-1&&(o===-1||r<=o))a.push(i.slice(0,r).trim()),e.push("iframe"),i=i.slice(r+12);else if(o!==-1)a.push(i.slice(0,o).trim()),e.push("shadow"),i=i.slice(o+5);else{a.push(i.trim());break}}let n=document;for(let r=0;r<a.length;r++){const o=n.querySelector(a[r]);if(!o)return null;if(r<a.length-1)if(e[r]==="iframe"){const s=o.contentDocument;if(!s)return null;n=s}else{const s=o.shadowRoot;if(!s)return null;n=s}else return o}return null}function c(t){const a=t.outerHTML;return a.length>200?a.slice(0,200)+"...":a}const Re=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"]),Q=new Set(["aria-atomic","aria-busy","aria-disabled","aria-grabbed","aria-hidden","aria-modal","aria-multiline","aria-multiselectable","aria-readonly","aria-required"]),J=new Set(["aria-checked","aria-pressed"]),Ne=new Set(["aria-colcount","aria-colindex","aria-colspan","aria-level","aria-posinset","aria-rowcount","aria-rowindex","aria-rowspan","aria-setsize"]),Me=new Set(["aria-valuemax","aria-valuemin","aria-valuenow"]),Z={"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"])},ee=new Set(["caption","code","deletion","emphasis","generic","insertion","mark","none","paragraph","presentation","strong","subscript","superscript","suggestion","term","time"]),He={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},$e={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,C=null;function fe(){T=null,C=null}function U(t){var n;if(C&&(T==null?void 0:T.deref())===t)return C;const a=[],e=[],i=[];for(const r of t.querySelectorAll("*")){let o=!1;for(const l of r.attributes)if(l.name.startsWith("aria-")){o=!0;break}if(!o)continue;let s,u;const h=()=>(s===void 0&&(s=d(r),u=c(r)),{selector:s,html:u});for(const l of r.attributes)if(l.name.startsWith("aria-")&&!Re.has(l.name)){const m=h();a.push({ruleId:"aria-valid-attr",selector:m.selector,html:m.html,impact:"critical",message:`Invalid ARIA attribute "${l.name}".`});break}for(const l of r.attributes){if(!l.name.startsWith("aria-"))continue;const m=l.value.trim();if(!(m===""&&!Q.has(l.name)&&!J.has(l.name))){if(Q.has(l.name)){if(m!=="true"&&m!=="false"){const g=h();e.push({ruleId:"aria-valid-attr-value",selector:g.selector,html:g.html,impact:"critical",message:`${l.name} must be "true" or "false", got "${m}".`})}}else if(J.has(l.name)){if(m!=="true"&&m!=="false"&&m!=="mixed"){const g=h();e.push({ruleId:"aria-valid-attr-value",selector:g.selector,html:g.html,impact:"critical",message:`${l.name} must be "true", "false", or "mixed", got "${m}".`})}}else if(Ne.has(l.name)){if(m===""||!/^-?\d+$/.test(m)){const g=h();e.push({ruleId:"aria-valid-attr-value",selector:g.selector,html:g.html,impact:"critical",message:`${l.name} must be an integer, got "${m}".`})}}else if(Me.has(l.name)){if(m===""||isNaN(Number(m))){const g=h();e.push({ruleId:"aria-valid-attr-value",selector:g.selector,html:g.html,impact:"critical",message:`${l.name} must be a number, got "${m}".`})}}else if(Z[l.name]){const g=m.split(/\s+/);for(const b of g)if(!Z[l.name].has(b)){const v=h();e.push({ruleId:"aria-valid-attr-value",selector:v.selector,html:v.html,impact:"critical",message:`Invalid value "${m}" for ${l.name}.`});break}}}}if(!p(r)){const l=(n=r.getAttribute("role"))==null?void 0:n.trim().toLowerCase(),m=r.tagName.toLowerCase();if(!l&&He[m]){const g=r.hasAttribute("aria-label"),b=r.hasAttribute("aria-labelledby");if(g||b){const v=h();i.push({ruleId:"aria-prohibited-attr",selector:v.selector,html:v.html,impact:"serious",message:`aria-label and aria-labelledby are prohibited on <${m}> elements.`})}}else if(l){if(ee.has(l)){const b=r.hasAttribute("aria-label"),v=r.hasAttribute("aria-labelledby");if(b||v){const y=h();i.push({ruleId:"aria-prohibited-attr",selector:y.selector,html:y.html,impact:"serious",message:`aria-label and aria-labelledby are prohibited on role "${l}".`})}}const g=$e[l];if(g){for(const b of r.attributes)if(b.name.startsWith("aria-")&&g.has(b.name)){if((b.name==="aria-label"||b.name==="aria-labelledby")&&ee.has(l))continue;const v=h();i.push({ruleId:"aria-prohibited-attr",selector:v.selector,html:v.html,impact:"serious",message:`Attribute "${b.name}" is prohibited on role "${l}".`})}}}}}return T=new WeakRef(t),C={validAttr:a,validAttrValue:e,prohibitedAttr:i},C}let F=new WeakMap,j=new WeakMap,P=new WeakMap;function ve(){F=new WeakMap,j=new WeakMap,P=new WeakMap}function A(t){let a=F.get(t);return a||(a=getComputedStyle(t),F.set(t,a),a)}function q(t,a,e){const[i,n,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*i+.7152*n+.0722*r}function ye(t,a){const e=Math.max(t,a),i=Math.min(t,a);return(e+.05)/(i+.05)}const te={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 R(t){const a=t.trim().toLowerCase();if(te[a])return te[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 i=a.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/);if(i)return[parseInt(i[1],16),parseInt(i[2],16),parseInt(i[3],16)];const n=t.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*[\d.]+)?\s*\)/);if(n)return[parseInt(n[1]),parseInt(n[2]),parseInt(n[3])];const 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 De(t){const a=j.get(t);if(a!==void 0)return a;const e=Oe(t);return j.set(t,e),e}function Oe(t){let a=t;for(;a;){const e=A(a),i=e.backgroundImage;if(i&&i!=="none"&&i!=="initial")return null;const n=e.backgroundColor;if(n==="transparent"||n==="rgba(0, 0, 0, 0)"||n==="rgba(0 0 0 / 0)"){a=a.parentElement;continue}const r=n.match(/rgba\(.+?,\s*([\d.]+)\s*\)/)||n.match(/rgba?\(.+?\/\s*([\d.]+%?)\s*\)/);if(r&&(r[1].endsWith("%")?parseFloat(r[1])/100:parseFloat(r[1]))<.1){a=a.parentElement;continue}return R(n)}return[255,255,255]}const Be=new Set(["IMG","PICTURE","VIDEO","SVG"]);function We(t){const a=P.get(t);if(a!==void 0)return a;const e=_e(t);return P.set(t,e),e}function _e(t){let a=t,e=!1;for(;a;){const i=A(a).position;if((i==="absolute"||i==="fixed")&&(e=!0),a!==t&&i!=="static"){for(const n of a.children)if(!(n===t||n.contains(t))&&Be.has(n.tagName)){if(e)return!0;const r=A(n).position;if(r==="absolute"||r==="fixed")return!0}if(e)break}a=a.parentElement}return!1}function Fe(t){const a=parseFloat(t);return t.endsWith("pt")?a*(4/3):a}function je(t){const a=A(t),e=Fe(a.fontSize),i=parseInt(a.fontWeight)||(a.fontWeight==="bold"?700:400);return e>=23.5||e>=18.5&&i>=700}function $(t){var r,o;const a=[],e=t.closest("a");if(e){const s=e.getAttribute("href");s&&a.push(`Link href: ${s}`)}const i=t.closest("figure");if(i){const s=i.querySelector("figcaption");(r=s==null?void 0:s.textContent)!=null&&r.trim()&&a.push(`Figcaption: ${s.textContent.trim().slice(0,100)}`)}const n=t.parentElement;if(n&&n!==e){const s=t instanceof HTMLImageElement&&t.alt||"",u=(o=n.textContent)==null?void 0:o.replace(s,"").trim().slice(0,100);u&&a.push(`Adjacent text: ${u}`)}return a.length>0?a.join(`
|
|
2
|
-
`):void 0}function ae(t){let a=t;for(;a;){if(a instanceof HTMLElement&&a.style.visibility==="hidden")return!0;a=a.parentElement}return!1}const Pe={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(p(e)||ae(e))continue;const i=e.getAttribute("role");if(i==="presentation"||i==="none"){const r=e.getAttribute("tabindex");if(!r||r==="-1")continue}const n=e.getAttribute("alt");if(n!==null&&n.trim()===""&&n!==""){a.push({ruleId:"img-alt",selector:d(e),html:c(e),impact:"critical",message:'Image has whitespace-only alt text. Use alt="" for decorative images or provide descriptive text.',context:$(e)});continue}!e.hasAttribute("alt")&&!f(e)&&a.push({ruleId:"img-alt",selector:d(e),html:c(e),impact:"critical",message:"Image element missing alt attribute.",context:$(e)})}for(const e of t.querySelectorAll('[role="img"]:not(img):not(svg)'))p(e)||ae(e)||f(e)||a.push({ruleId:"img-alt",selector:d(e),html:c(e),impact:"critical",message:'Element with role="img" has no accessible name. Add aria-label or aria-labelledby.',context:$(e)});return a}};function Ve(t){var r,o,s;const a=t.getAttribute("aria-labelledby");if(a){const u=a.split(/\s+/).map(h=>{var l,m;return((m=(l=t.ownerDocument.getElementById(h))==null?void 0:l.textContent)==null?void 0:m.trim())??""}).filter(Boolean);if(u.length)return u.join(" ")}const e=(r=t.getAttribute("aria-label"))==null?void 0:r.trim();if(e)return e;const i=t.querySelector("title");if((o=i==null?void 0:i.textContent)!=null&&o.trim())return i.textContent.trim();const n=(s=t.getAttribute("title"))==null?void 0:s.trim();return n||""}const ze={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 i of t.querySelectorAll(e)){if(p(i))continue;if(!Ve(i)){const r=i.getAttribute("role");a.push({ruleId:"svg-img-alt",selector:d(i),html:c(i),impact:"serious",message:`${i.tagName.toLowerCase()} with role='${r}' has no accessible name.`})}}return a}},Ue={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"]'))p(e)||f(e)||a.push({ruleId:"input-image-alt",selector:d(e),html:c(e),impact:"critical",message:"Image input missing alt text."});return a}},Ge={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:"Show the duplicated text and suggest either an empty alt or a complementary description.",run(t){var e;const a=[];for(const i of t.querySelectorAll("img[alt]")){const n=i.getAttribute("alt").trim().toLowerCase();if(!n)continue;const r=i.closest("a, button");if(r){const o=((e=r.textContent)==null?void 0:e.trim().toLowerCase())||"";o&&o===n&&a.push({ruleId:"image-redundant-alt",selector:d(i),html:c(i),impact:"minor",message:`Alt text "${i.getAttribute("alt")}" duplicates surrounding ${r.tagName.toLowerCase()} text.`})}}return a}},Xe=["image","picture","photo","graphic","icon","img"],Ye={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:"Identify the redundant word(s) in the alt text and show the corrected version with those words removed.",run(t){const a=[];for(const e of t.querySelectorAll("img[alt]")){const i=e.getAttribute("alt").toLowerCase();i&&Xe.some(n=>i.split(/\s+/).includes(n))&&a.push({ruleId:"image-alt-redundant-words",selector:d(e),html:c(e),impact:"minor",message:`Alt text "${e.getAttribute("alt")}" contains redundant word(s).`})}return a}},Ke={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(p(e))continue;f(e)||a.push({ruleId:"area-alt",selector:d(e),html:c(e),impact:"critical",message:"Image map <area> element is missing alternative text."})}return a}};function Qe(t){var n,r;const a=t.getAttribute("aria-labelledby");if(a){const o=a.split(/\s+/).map(s=>{var u,h;return((h=(u=t.ownerDocument.getElementById(s))==null?void 0:u.textContent)==null?void 0:h.trim())??""}).filter(Boolean);if(o.length)return o.join(" ")}const e=(n=t.getAttribute("aria-label"))==null?void 0:n.trim();if(e)return e;const i=(r=t.getAttribute("title"))==null?void 0:r.trim();return i||""}const Je={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 i of t.querySelectorAll("object")){if(p(i)||i instanceof HTMLElement&&i.style.visibility==="hidden")continue;let n=i.parentElement,r=!1;for(;n;){if(n instanceof HTMLElement&&n.style.visibility==="hidden"){r=!0;break}n=n.parentElement}if(r||i.getAttribute("role")==="presentation"||i.getAttribute("role")==="none"||Qe(i))continue;const o=i.getAttribute("data")||"";if(!((i.getAttribute("type")||"").startsWith("image/")||/\.(png|jpg|jpeg|gif|svg|webp|bmp|ico)$/i.test(o))){const h=i.querySelector("img[alt]");if(h&&((e=h.getAttribute("alt"))!=null&&e.trim()))continue}a.push({ruleId:"object-alt",selector:d(i),html:c(i),impact:"serious",message:"<object> element is missing alternative text. Add aria-label, aria-labelledby, or a title attribute."})}return a}},Ze={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(p(e)||e.tagName.toLowerCase()==="svg"||e.tagName.toLowerCase()==="img")continue;f(e)||a.push({ruleId:"role-img-alt",selector:d(e),html:c(e),impact:"serious",message:"Element with role='img' has no accessible name. Add aria-label or aria-labelledby."})}return a}};function et(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 n=tt(e);return n||null}function tt(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 k(t,a,e){let i=t;if(i.includes("{{tag}}")&&(i=i.replace(/\{\{tag\}\}/g,a.tagName.toLowerCase())),i.includes("{{value}}")){let n="";"attribute"in e&&e.attribute&&(n=a.getAttribute(e.attribute)??""),i=i.replace(/\{\{value\}\}/g,n)}return i}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){const i=[];switch(t.check.type){case"selector-exists":{for(const n of e.querySelectorAll(t.selector))a&&p(n)||i.push({ruleId:t.id,selector:d(n),html:c(n),impact:t.impact,message:k(t.message,n,t.check),element:n});break}case"attribute-value":{const{attribute:n,operator:r,value:o}=t.check;for(const s of e.querySelectorAll(t.selector)){if(a&&p(s))continue;const u=s.getAttribute(n);u!==null&&at(u,r,o)&&i.push({ruleId:t.id,selector:d(s),html:c(s),impact:t.impact,message:k(t.message,s,t.check),element:s})}break}case"attribute-missing":{const{attribute:n}=t.check;for(const r of e.querySelectorAll(t.selector))a&&p(r)||r.hasAttribute(n)||i.push({ruleId:t.id,selector:d(r),html:c(r),impact:t.impact,message:k(t.message,r,t.check),element:r});break}case"attribute-regex":{const{attribute:n,pattern:r,flags:o,shouldMatch:s}=t.check;let u;try{u=new RegExp(r,o)}catch{break}for(const h of e.querySelectorAll(t.selector)){if(a&&p(h))continue;const l=h.getAttribute(n);if(l===null)continue;const m=u.test(l);s&&!m?i.push({ruleId:t.id,selector:d(h),html:c(h),impact:t.impact,message:k(t.message,h,t.check),element:h}):!s&&m&&i.push({ruleId:t.id,selector:d(h),html:c(h),impact:t.impact,message:k(t.message,h,t.check),element:h})}break}case"child-required":{const{childSelector:n}=t.check;for(const r of e.querySelectorAll(t.selector))a&&p(r)||r.querySelector(n)||i.push({ruleId:t.id,selector:d(r),html:c(r),impact:t.impact,message:k(t.message,r,t.check),element:r});break}case"child-invalid":{const n=new Set(t.check.allowedChildren.map(r=>r.toLowerCase()));for(const r of e.querySelectorAll(t.selector))if(!(a&&p(r))){for(const o of r.children)if(!n.has(o.tagName.toLowerCase())){i.push({ruleId:t.id,selector:d(o),html:c(o),impact:t.impact,message:k(t.message,o,t.check),element:o});break}}break}}return i}}}function at(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 it={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."},nt=S(it),rt=['[role="checkbox"]','[role="combobox"]','[role="listbox"]','[role="menuitemcheckbox"]','[role="menuitemradio"]','[role="radio"]','[role="searchbox"]','[role="slider"]','[role="spinbutton"]','[role="switch"]','[role="textbox"]'].join(", "),ot=new Set(["checkbox","menuitemcheckbox","menuitemradio","radio","switch"]),st=new Set(["combobox","listbox","searchbox","slider","spinbutton","textbox"]);function lt(t){var o,s,u,h;const a=(o=t.getAttribute("role"))==null?void 0:o.trim().toLowerCase();if(a&&ot.has(a)||(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement)&&!(a&&st.has(a)))return f(t);const i=t.getAttribute("aria-labelledby");if(i){const l=i.split(/\s+/).map(m=>{const g=t.ownerDocument.getElementById(m);return g?w(g).trim():""}).filter(Boolean);if(l.length)return l.join(" ")}const n=(s=t.getAttribute("aria-label"))==null?void 0:s.trim();if(n)return n;if(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement||t instanceof HTMLSelectElement){if(t.id){const m=t.ownerDocument.querySelector(`label[for="${CSS.escape(t.id)}"]`);if(m){const g=w(m).trim();if(g)return g}}const l=t.closest("label");if(l){const m=w(l).trim();if(m)return m}}const r=(u=t.getAttribute("title"))==null?void 0:u.trim();if(r)return r;if(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement){const l=(h=t.getAttribute("placeholder"))==null?void 0:h.trim();if(l)return l}return""}const ct={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:"Based on the input type, name attribute, or placeholder, suggest a label element with appropriate text, or an aria-label.",run(t){var n;const a=[],i=t.querySelectorAll(`input:not([type="hidden"]):not([type="submit"]):not([type="button"]):not([type="reset"]):not([type="image"]), textarea, select, ${rt}`);for(const r of i){if(p(r)||r instanceof HTMLElement&&(r.hidden||r.style.display==="none"))continue;const o=(n=r.getAttribute("role"))==null?void 0:n.trim().toLowerCase();if(o==="presentation"||o==="none")continue;lt(r)||a.push({ruleId:"label",selector:d(r),html:c(r),impact:"critical",message:"Form element has no accessible label."})}return a}},ut={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 i of e){if(p(i)||!i.id)continue;const n=t.querySelectorAll(`label[for="${CSS.escape(i.id)}"]`);let r=0,o=i.parentElement;for(;o;){if(o.tagName.toLowerCase()==="label"&&!o.hasAttribute("for")){r++;break}o=o.parentElement}const s=n.length+r;s>1&&a.push({ruleId:"form-field-multiple-labels",selector:d(i),html:c(i),impact:"moderate",message:`Form field has ${s} labels. Use a single label element.`})}return a}},dt={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"))p(e)||f(e)||a.push({ruleId:"select-name",selector:d(e),html:c(e),impact:"critical",message:"Select element has no accessible name."});return a}},mt={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,i;const a=[];for(const n of t.querySelectorAll('input[type="submit"], input[type="button"], input[type="reset"]')){if(p(n))continue;const r=(e=n.getAttribute("value"))==null?void 0:e.trim(),o=(i=n.getAttribute("type"))==null?void 0:i.toLowerCase(),s=(o==="submit"||o==="reset")&&!n.hasAttribute("value");!r&&!s&&!f(n)&&a.push({ruleId:"input-button-name",selector:d(n),html:c(n),impact:"critical",message:"Input button has no discernible text."})}return a}},ht=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"]),pt=new Set(["tel","tel-country-code","tel-national","tel-area-code","tel-local","tel-extension","email","impp"]),gt=new Set(["home","work","mobile","fax","pager"]),bt=new Set(["shipping","billing"]),ft=new Set(["webauthn"]);function vt(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&&bt.has(a[e])&&e++;let i=!1;if(e<a.length&>.has(a[e])&&(i=!0,e++),e>=a.length)return!1;const n=a[e];return!ht.has(n)||i&&!pt.has(n)?!1:(e++,e<a.length&&ft.has(a[e])&&e++,e===a.length)}const yt={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(p(e)||e instanceof HTMLElement&&e.style.display==="none"||e.disabled||e.getAttribute("aria-disabled")==="true")continue;const i=e.getAttribute("autocomplete").trim();i&&(vt(i)||a.push({ruleId:"autocomplete-valid",selector:d(e),html:c(e),impact:"serious",message:`Invalid autocomplete value "${i}".`}))}return a}};function ie(t){return t.toLowerCase().replace(/\s+/g," ").trim()}function ne(t,a){const e=ie(t),i=ie(a);if(!e||!i||e.includes(i)||i.includes(e))return!0;const n=i.split(/\s+/).map(r=>r.replace(/[.,;:!?\u2026]+$/g,"")).filter(r=>r.length>2);return n.length>=2&&n.filter(o=>e.includes(o)).length/n.length>.5}function V(t){let a="";for(const e of t.childNodes)if(e.nodeType===3)a+=e.textContent??"";else if(e.nodeType===1){const i=e,n=i.tagName.toLowerCase();if(n==="style"||n==="script"||n==="svg"||i.getAttribute("aria-hidden")==="true"||i instanceof HTMLElement&&i.style.display==="none")continue;const r=i.getAttribute("role");if(r==="img"||r==="presentation"||r==="none")continue;a+=V(i)}return a}const wt={id:"label-content-name-mismatch",wcag:["2.5.3"],level:"A",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(p(e))continue;const i=f(e);if(!i)continue;let n="";e instanceof HTMLInputElement?n=e.value||"":n=V(e);const r=n.trim();if(!r||r.length<=2)continue;const o=e.hasAttribute("aria-label"),s=e.hasAttribute("aria-labelledby");!o&&!s||ne(i,n)||a.push({ruleId:"label-content-name-mismatch",selector:d(e),html:c(e),impact:"serious",message:`Accessible name "${i}" does not contain visible text "${n.trim()}".`})}for(const e of t.querySelectorAll("input, select, textarea")){if(p(e)||e instanceof HTMLInputElement&&["hidden","submit","button","image"].includes(e.type))continue;const i=f(e);if(!i||!e.hasAttribute("aria-label"))continue;const r=e.id;let o="";if(r){const s=t.querySelector(`label[for="${CSS.escape(r)}"]`);s&&(o=V(s))}o.trim()&&(ne(i,o)||a.push({ruleId:"label-content-name-mismatch",selector:d(e),html:c(e),impact:"serious",message:`Accessible name "${i}" does not contain visible label "${o.trim()}".`}))}return a}},At={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 i,n,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(p(s))continue;const u=s.hasAttribute("title")&&((i=s.getAttribute("title"))==null?void 0:i.trim()),h=s.hasAttribute("aria-label")&&((n=s.getAttribute("aria-label"))==null?void 0:n.trim()),l=s.hasAttribute("aria-labelledby");let m=!1;const g=s.id;if(g){const v=s.ownerDocument.querySelector(`label[for="${CSS.escape(g)}"]`);(r=v==null?void 0:v.textContent)!=null&&r.trim()&&(m=!0)}const b=s.closest("label");(o=b==null?void 0:b.textContent)!=null&&o.trim()&&(m=!0),u&&!h&&!l&&!m&&a.push({ruleId:"label-title-only",selector:d(s),html:c(s),impact:"serious",message:"Form element uses title attribute as only label. Use <label>, aria-label, or aria-labelledby instead."})}return a}},St={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."},xt=S(St),kt=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"]),It={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 i=e.tagName.toLowerCase();if(!kt.has(i))continue;e.getAttribute("role")||a.push({ruleId:"focus-order-semantics",selector:d(e),html:c(e),impact:"moderate",message:`Non-interactive <${i}> with tabindex="0" has no interactive role.`})}return a}},Et=new Set(["a","audio","button","img","input","select","textarea","video"]),Tt=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"]),Ct={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 Lt(t,a){var n,r,o;const e=(n=t.getAttribute("role"))==null?void 0:n.toLowerCase(),i=(r=a.getAttribute("role"))==null?void 0:r.toLowerCase();return!e||!i?!1:((o=Ct[e])==null?void 0:o.has(i))??!1}function qt(t){var n;const a=t.tagName.toLowerCase();if(Et.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=(n=t.getAttribute("role"))==null?void 0:n.toLowerCase();if(e&&Tt.has(e))return!0;const i=t.getAttribute("tabindex");return i!==null&&i!=="-1"||t.getAttribute("contenteditable")==="true"}function Rt(t){const a=t.tagName.toLowerCase();return!!(a==="a"&&t.hasAttribute("href")||a==="button"&&!t.disabled)}const Nt={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 n=(t.body?t:t.ownerDocument).createTreeWalker(e,NodeFilter.SHOW_ELEMENT),r=[];let o=n.currentNode;for(;o;){for(;r.length>0&&!r[r.length-1].contains(o);)r.pop();if(!p(o)&&qt(o)){if(r.length>0){const s=r[r.length-1];Lt(s,o)||a.push({ruleId:"nested-interactive",selector:d(o),html:c(o),impact:"serious",message:`Interactive element <${o.tagName.toLowerCase()}> is nested inside <${s.tagName.toLowerCase()}>.`})}Rt(o)&&r.push(o)}o=n.nextNode()}return a}},Mt={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){const a=[];for(const e of t.querySelectorAll("*")){if(p(e)||!(e instanceof HTMLElement))continue;const i=A(e),n=i.overflowX,r=i.overflowY;if(!(n==="scroll"||n==="auto"||r==="scroll"||r==="auto"))continue;if(e.scrollHeight>0||e.clientHeight>0){if(e.scrollHeight<=e.clientHeight&&e.scrollWidth<=e.clientWidth)continue}else{const l=i.height!==""||i.maxHeight!=="",m=e.textContent!=null&&e.textContent.trim().length>0;if(!l||!m)continue}const u=e.getAttribute("tabindex");u!==null&&u!=="-1"||e.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:d(e),html:c(e),impact:"serious",message:"Scrollable region is not keyboard accessible. Add tabindex='0' or include focusable elements."})}return a}},Ht={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 i;const a=[],e=new Map;for(const n of t.querySelectorAll("[accesskey]")){if(p(n))continue;const r=(i=n.getAttribute("accesskey"))==null?void 0:i.trim().toLowerCase();if(!r)continue;const o=e.get(r)||[];o.push(n),e.set(r,o)}for(const[n,r]of e)if(r.length>1)for(const o of r.slice(1))a.push({ruleId:"accesskeys",selector:d(o),html:c(o),impact:"serious",message:`Duplicate accesskey "${n}". Each accesskey must be unique.`});return a}},$t={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 i=0,n=null;for(const r of e){if(p(r))continue;let o;r.hasAttribute("aria-level")?o=parseInt(r.getAttribute("aria-level"),10):o=parseInt(r.tagName[1],10),i>0&&o>i+1&&a.push({ruleId:"heading-order",selector:d(r),html:c(r),impact:"moderate",message:`Heading level ${o} skipped from level ${i}.`,context:n?`Previous heading: ${c(n)}`:void 0}),i=o,n=r}return a}},N='article, aside, main, nav, section, [role="article"], [role="complementary"], [role="main"], [role="navigation"], [role="region"]',re='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"]',Dt={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:d(e),html:c(e),impact:"moderate",message:"Page has multiple main landmarks."})):[]}},Ot={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"]'),i=Array.from(e).filter(n=>!n.closest(N));return i.length>1&&i.slice(1).forEach(n=>a.push({ruleId:"landmark-no-duplicate-banner",selector:d(n),html:c(n),impact:"moderate",message:"Page has multiple banner landmarks."})),a}},Bt={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"]'),i=Array.from(e).filter(n=>!n.closest(N));return i.length>1&&i.slice(1).forEach(n=>a.push({ruleId:"landmark-no-duplicate-contentinfo",selector:d(n),html:c(n),impact:"moderate",message:"Page has multiple contentinfo landmarks."})),a}},Wt={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(i=>a.push({ruleId:"landmark-no-duplicate-main",selector:d(i),html:c(i),impact:"moderate",message:"Page has multiple main landmarks."})),a}},_t={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 i of e)i.closest(N)&&a.push({ruleId:"landmark-banner-is-top-level",selector:d(i),html:c(i),impact:"moderate",message:"Banner landmark is nested within another landmark."});return a}},Ft={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 i of e)i.closest(N)&&a.push({ruleId:"landmark-contentinfo-is-top-level",selector:d(i),html:c(i),impact:"moderate",message:"Contentinfo landmark is nested within another landmark."});return a}},jt={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 i of e){const n=i.parentElement;n!=null&&n.closest('article, aside, nav, section, [role="article"], [role="complementary"], [role="navigation"], [role="region"]')&&a.push({ruleId:"landmark-main-is-top-level",selector:d(i),html:c(i),impact:"moderate",message:"Main landmark is nested within another landmark."})}return a}},Pt={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 i of e){const n=i.parentElement;n&&!n.matches('body, main, [role="main"]')&&i.closest('article, nav, section, [role="article"], [role="navigation"], [role="region"]')&&a.push({ruleId:"landmark-complementary-is-top-level",selector:d(i),html:c(i),impact:"moderate",message:"Complementary landmark should be top-level."})}return a}},Vt={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:i,type:n}of e){const r=Array.from(t.querySelectorAll(i)).filter(s=>!p(s));if(r.length<=1)continue;const o=new Map;for(const s of r){const u=f(s).toLowerCase()||"",h=o.get(u)||[];h.push(s),o.set(u,h)}for(const[s,u]of o)if(u.length>1)for(const h of u.slice(1))a.push({ruleId:"landmark-unique",selector:d(h),html:c(h),impact:"moderate",message:s?`Multiple ${n} landmarks have the same label "${s}".`:`Multiple ${n} landmarks have no label. Add unique aria-label attributes.`})}return a}},zt={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 i;const a=[],e=t.body;if(!e)return[];for(const n of e.children){if(p(n)||n instanceof HTMLScriptElement||n instanceof HTMLStyleElement||n.tagName==="NOSCRIPT"||n instanceof HTMLElement&&n.hidden||n.matches('a[href^="#"]'))continue;const r=n.matches(re),o=(i=n.textContent)==null?void 0:i.trim();!r&&o&&(n.querySelector(re)||a.push({ruleId:"region",selector:d(n),html:c(n),impact:"moderate",message:"Content is not contained within a landmark region."}))}return a}},Ut={id:"list",selector:"ul, ol",check:{type:"child-invalid",allowedChildren:["li","script","template"]},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."},Gt=S(Ut),Xt={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:d(e),html:c(e),impact:"serious",message:`<${e.tagName.toLowerCase()}> is not contained in a <dl>.`});return a}},Yt={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."},Kt=S(Yt),Qt={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:"Suggest a descriptive page title based on the visible content.",run(t){var e;const a=t.querySelector("title");return!a||!((e=a.textContent)!=null&&e.trim())?[{ruleId:"document-title",selector:"html",html:"<html>",impact:"serious",message:a?"Document <title> element is empty.":"Document is missing a <title> element."}]:[]}},Jt={id:"bypass",wcag:["2.4.1"],level:"A",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:"Explain whether to add a skip link or a <main> landmark based on the page structure.",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 n=e.getAttribute("href");if(n&&n.length>1){const r=n.slice(1);if(t.getElementById(r))return[]}}return t.querySelector("h1, h2, h3, [role='heading']")?[]:[{ruleId:"bypass",selector:"html",html:"<html>",impact:"serious",message:"Page has no mechanism to bypass repeated content. Add a <main> landmark or skip link."}]}},Zt={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:"Suggest appropriate h1 text based on the page's visible content.",run(t){const a=t.querySelector("h1");if(a&&f(a))return[];const e=t.querySelectorAll('[role="heading"][aria-level="1"]');for(const i of e)if(f(i))return[];return[{ruleId:"page-has-heading-one",selector:"html",html:"<html>",impact:"moderate",message:"Page does not contain a level-one heading."}]}},ea={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:"Suggest a descriptive title based on the frame's src URL or visible content.",run(t){const a=[];for(const e of t.querySelectorAll("iframe, frame")){if(p(e))continue;f(e)||a.push({ruleId:"frame-title",selector:d(e),html:c(e),impact:"serious",message:"Frame is missing an accessible name. Add a title attribute."})}return a}},ta={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 n;const a=[],e=Array.from(t.querySelectorAll("iframe[title], frame[title]")),i=new Map;for(const r of e){if(p(r))continue;const o=r.getAttribute("width"),s=r.getAttribute("height");if(o==="0"||s==="0"||r instanceof HTMLElement&&r.style.display==="none"||r instanceof HTMLElement&&r.style.visibility==="hidden")continue;const u=(n=r.getAttribute("title"))==null?void 0:n.trim().toLowerCase();if(u){const h=i.get(u)||[];h.push(r),i.set(u,h)}}for(const[,r]of i)if(r.length>1)for(const o of r.slice(1))a.push({ruleId:"frame-title-unique",selector:d(o),html:c(o),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:"Suggest appropriate heading text or explain why to use a different element.",run(t){const a=[],e=t.querySelectorAll('h1, h2, h3, h4, h5, h6, [role="heading"]');for(const i of e)p(i)||f(i)||a.push({ruleId:"empty-heading",selector:d(i),html:c(i),impact:"minor",message:"Heading is empty. Add text content or remove the heading element."});return a}},ia={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:"Explain which viewport restrictions to remove and show the corrected meta tag.",run(t){var r;const a=[],e=t.querySelector('meta[name="viewport"]');if(!e)return[];const i=((r=e.getAttribute("content"))==null?void 0:r.toLowerCase())||"";(/user-scalable\s*=\s*no/i.test(i)||/user-scalable\s*=\s*0/i.test(i))&&a.push({ruleId:"meta-viewport",selector:d(e),html:c(e),impact:"critical",message:"Viewport disables user scaling. Remove user-scalable=no."});const n=i.match(/maximum-scale\s*=\s*([\d.]+|yes)/i);if(n){const o=n[1],s=o.toLowerCase()==="yes"?1:parseFloat(o);s<2&&a.push({ruleId:"meta-viewport",selector:d(e),html:c(e),impact:"critical",message:`Viewport maximum-scale=${s} restricts zooming. Set to at least 2 or remove.`})}return a}},na={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")||"",i=e.match(/^(\d+)/);if(!i)continue;const n=parseInt(i[1],10);if(/^\d+\s*[;,]\s*url\s*=/i.test(e)||/^\d+\s*[;,]\s*['"]?\s*https?:/i.test(e))return n>0&&n<=72e3?[{ruleId:"meta-refresh",selector:d(a),html:c(a),impact:"critical",message:`Page redirects after ${n} seconds without warning. Use server-side redirect.`}]:[];if(n>0&&n<=72e3)return[{ruleId:"meta-refresh",selector:d(a),html:c(a),impact:"critical",message:`Page auto-refreshes after ${n} seconds. Provide user control over refresh.`}]}return[]}},ra={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."},oa=S(ra),sa={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."},la=S(sa),ca={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,i;const a=[];for(const n of t.querySelectorAll("p")){if(p(n))continue;const r=n.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),u=((e=n.className)==null?void 0:e.toLowerCase())||"",h=/\bh[1-6]\b|\bheading\b/.test(u),l=((i=n.textContent)==null?void 0:i.trim())||"",m=l.length>0&&l.length<50,g=!l.match(/[.!?,;:]$/);if((o&&s||o&&h)&&m&&g){const y=n.nextElementSibling;y&&(y.tagName==="P"||y.tagName==="DIV"||y.tagName==="UL")&&a.push({ruleId:"p-as-heading",selector:d(n),html:c(n),impact:"serious",message:"Paragraph appears to be styled as a heading. Use an h1-h6 element instead."})}}return a}},ua={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=>ge(s))&&r.length>0&&a.push({ruleId:"aria-roles",selector:d(e),html:c(e),impact:"critical",message:`Invalid ARIA role "${r[0]}".`})}return a}},da={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 U(t).validAttr}},ma={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 U(t).validAttrValue}},ha={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"]},pa={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(p(e)||e instanceof HTMLElement&&e.style.display==="none")continue;const i=e.getAttribute("role").trim().toLowerCase(),n=ha[i];if(n&&!(i==="checkbox"&&e instanceof HTMLInputElement&&e.type==="checkbox")&&!(i==="radio"&&e instanceof HTMLInputElement&&e.type==="radio")&&!(i==="option"&&e instanceof HTMLOptionElement)&&!(i==="heading"&&/^h[1-6]$/i.test(e.tagName))){if(i==="separator"){const r=e.getAttribute("tabindex");if(!r||r==="-1")continue}if(!(e.tagName.toLowerCase()==="hr"&&!e.hasAttribute("role"))){for(const r of n)if(!e.hasAttribute(r)){a.push({ruleId:"aria-required-attr",selector:d(e),html:c(e),impact:"critical",message:`Role "${i}" requires attribute "${r}".`});break}}}}return a}};function ga(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 i=t.closest("form");if(i){const u=i.getAttribute("aria-label")||((o=(r=i.querySelector("legend"))==null?void 0:r.textContent)==null?void 0:o.trim());u&&a.push(`Form: ${u.slice(0,60)}`)}const n=t.parentElement;if(n){const u=n.closest("h1, h2, h3, h4, h5, h6")||n.querySelector("h1, h2, h3, h4, h5, h6");(s=u==null?void 0:u.textContent)!=null&&s.trim()&&a.push(`Nearby heading: ${u.textContent.trim().slice(0,60)}`)}return a.length>0?a.join(`
|
|
3
|
-
`):void 0}const ba={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(p(e))continue;const i=e.getAttribute("role");if((i==="none"||i==="presentation")&&!(e.matches('button:not([disabled]), [tabindex]:not([tabindex="-1"])')||e.tagName.toLowerCase()==="button"&&!e.disabled)||e.getRootNode()instanceof ShadowRoot)continue;f(e)||a.push({ruleId:"button-name",selector:d(e),html:c(e),impact:"critical",message:"Button has no discernible text.",context:ga(e)})}return a}},fa={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"])},va=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"]),ya={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:"State which attribute is not allowed on this role and recommend removing it or using a different element/role that supports the attribute.",run(t){const a=[];for(const e of t.querySelectorAll("[role], [aria-*]")){if(p(e))continue;const i=L(e);if(!i)continue;const n=fa[i];if(n)for(const r of e.attributes)r.name.startsWith("aria-")&&(va.has(r.name)||n.has(r.name)||a.push({ruleId:"aria-allowed-attr",selector:d(e),html:c(e),impact:"critical",message:`ARIA attribute "${r.name}" is not allowed on role "${i}".`}))}return a}},wa=new Set(["base","col","colgroup","head","html","keygen","meta","param","script","source","style","template","title","track"]),E={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 Aa(t){var e;const a=t.tagName.toLowerCase();if(wa.has(a))return"none";if(a==="a"&&t.hasAttribute("href"))return E["a[href]"];if(a==="img"&&t.getAttribute("alt")==="")return E["img[alt='']"];if(a==="input"){const n=`input[type=${((e=t.getAttribute("type"))==null?void 0:e.toLowerCase())||"text"}]`;return n in E?E[n]:"none"}return E[a]||"any"}const Sa={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 i of t.querySelectorAll("[role]")){if(p(i))continue;const n=(e=i.getAttribute("role"))==null?void 0:e.trim().toLowerCase();if(!n)continue;const r=z(i);if(r&&n===r)continue;const o=Aa(i);o==="none"?a.push({ruleId:"aria-allowed-role",selector:d(i),html:c(i),impact:"minor",message:`Element <${i.tagName.toLowerCase()}> should not have an explicit role.`}):o!=="any"&&!o.has(n)&&a.push({ruleId:"aria-allowed-role",selector:d(i),html:c(i),impact:"minor",message:`Role "${n}" is not allowed on element <${i.tagName.toLowerCase()}>.`})}return a}},oe={combobox:[["listbox","tree","grid","dialog","textbox"]],feed:[["article"]],grid:[["row","rowgroup"]],list:[["listitem","group"]],listbox:[["option","group"]],menu:[["menuitem","menuitemcheckbox","menuitemradio","group"]],menubar:[["menuitem","menuitemcheckbox","menuitemradio","group"]],radiogroup:[["radio"]],row:[["cell","columnheader","gridcell","rowheader"]],rowgroup:[["row"]],table:[["row","rowgroup"]],tablist:[["tab"]],tree:[["treeitem","group"]],treegrid:[["row","rowgroup"]]},se={caption:["figure","table","grid","treegrid"],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"],tab:["tablist"],treeitem:["tree","group"]};function xa(t,a){var r;const e=((r=t.getAttribute("aria-owns"))==null?void 0:r.split(/\s+/))||[],i=t.ownerDocument,n=new Set;for(const o of t.querySelectorAll("*")){const s=L(o);s&&!p(o)&&n.add(s)}for(const o of e){const s=i.getElementById(o);if(s){const u=L(s);u&&!p(s)&&n.add(u)}}for(const o of a)if(!o.some(u=>n.has(u)))return!1;return!0}const ka={id:"aria-required-children",wcag:["4.1.2"],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 i of t.querySelectorAll("[role]")){if(p(i))continue;const n=(e=i.getAttribute("role"))==null?void 0:e.trim().toLowerCase();if(!n||!(n in oe))continue;const r=oe[n];if(!xa(i,r)){const o=r.map(s=>s.join(" or ")).join(", ");a.push({ruleId:"aria-required-children",selector:d(i),html:c(i),impact:"critical",message:`Role "${n}" requires children with role: ${o}.`})}}return a}},Ia={id:"aria-required-parent",wcag:["4.1.2"],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 i of t.querySelectorAll("[role]")){if(p(i))continue;const n=(e=i.getAttribute("role"))==null?void 0:e.trim().toLowerCase();if(!n||!(n in se))continue;const r=se[n];let o=i.parentElement,s=!1;for(;o&&o!==t.documentElement;){const u=L(o);if(u&&r.includes(u)){s=!0;break}o=o.parentElement}s||a.push({ruleId:"aria-required-parent",selector:d(i),html:c(i),impact:"critical",message:`Role "${n}" must be contained within: ${r.join(", ")}.`})}return a}},le=["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 Ea(t){let a=t;const e=t.ownerDocument,i=e.defaultView;for(;a&&a!==e.body;){if(a.style.display==="none"||a.style.visibility==="hidden")return!1;if(i){const n=i.getComputedStyle(a);if(n.display==="none"||n.visibility==="hidden")return!1}a=a.parentElement}return!0}const Ta={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},Ca=S(Ta),La={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:"Suggest adding tabindex='-1' to this focusable element, or moving it outside the aria-hidden region, or removing aria-hidden from the ancestor.",run(t){const a=[];for(const e of t.querySelectorAll('[aria-hidden="true"]')){if(e===t.body)continue;const i=[...e.querySelectorAll(le)];e.matches(le)&&i.push(e);for(const n of i)if(n instanceof HTMLElement){if(n.getAttribute("tabindex")==="-1"||n.disabled||n instanceof HTMLInputElement&&n.type==="hidden"||!Ea(n))continue;a.push({ruleId:"aria-hidden-focus",selector:d(n),html:c(n),impact:"serious",message:"Focusable element is inside an aria-hidden region."})}}return a}},qa={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 i of t.querySelectorAll('[role="button"], [role="link"], [role="menuitem"]')){if(p(i)||i.tagName.toLowerCase()==="button"||i.tagName.toLowerCase()==="a")continue;if(!f(i)){const r=i.querySelector("img[alt]");if((e=r==null?void 0:r.getAttribute("alt"))!=null&&e.trim())continue;a.push({ruleId:"aria-command-name",selector:d(i),html:c(i),impact:"serious",message:"ARIA command has no accessible name."})}}return a}},Ra={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 i of t.querySelectorAll(e)){if(p(i)||i.matches("input, select, textarea"))continue;f(i)||a.push({ruleId:"aria-input-field-name",selector:d(i),html:c(i),impact:"serious",message:"ARIA input field has no accessible name."})}return a}},Na={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 i of t.querySelectorAll(e)){if(p(i)||i.matches('input[type="checkbox"], input[type="radio"]'))continue;f(i)||a.push({ruleId:"aria-toggle-field-name",selector:d(i),html:c(i),impact:"serious",message:"ARIA toggle field has no accessible name."})}return a}},Ma={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(p(e))continue;f(e)||a.push({ruleId:"aria-meter-name",selector:d(e),html:c(e),impact:"serious",message:"Meter has no accessible name."})}return a}},Ha={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(p(e))continue;f(e)||a.push({ruleId:"aria-progressbar-name",selector:d(e),html:c(e),impact:"serious",message:"Progressbar has no accessible name."})}return a}},$a={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(p(e))continue;f(e)||a.push({ruleId:"aria-dialog-name",selector:d(e),html:c(e),impact:"serious",message:"Dialog has no accessible name."})}return a}},Da={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(p(e))continue;f(e)||a.push({ruleId:"aria-tooltip-name",selector:d(e),html:c(e),impact:"serious",message:"Tooltip has no accessible name."})}return a}},Oa={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(p(e))continue;f(e)||a.push({ruleId:"aria-treeitem-name",selector:d(e),html:c(e),impact:"serious",message:"Treeitem has no accessible name."})}return a}},Ba={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 U(t).prohibitedAttr}},Wa=["a[href]","button:not([disabled])",'input:not([disabled]):not([type="hidden"])',"select:not([disabled])","textarea:not([disabled])",'[tabindex]:not([tabindex="-1"])'].join(", "),_a=["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 ce(t){const a=[];t.matches(Wa)&&a.push("element is focusable");for(const e of _a)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 Fa={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(p(e))continue;const i=ce(e);i.length>0&&a.push({ruleId:"presentation-role-conflict",selector:d(e),html:c(e),impact:"serious",message:`Presentation role conflicts with: ${i.join(", ")}. The role will be ignored.`})}for(const e of t.querySelectorAll('img[alt=""]')){if(p(e)||e.hasAttribute("role"))continue;const i=ce(e);i.length>0&&a.push({ruleId:"presentation-role-conflict",selector:d(e),html:c(e),impact:"serious",message:`Element with implicit presentation role (alt="") conflicts with: ${i.join(", ")}. The decorative role will be ignored.`})}return a}},ja={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(p(e))continue;f(e)||a.push({ruleId:"summary-name",selector:d(e),html:c(e),impact:"serious",message:"<summary> element has no accessible name. Add descriptive text."})}return a}};function Pa(t){var n,r;const a=[],e=t.getAttribute("href");e&&a.push(`href: ${e}`);const i=t.parentElement;if(i){const o=i.closest("h1, h2, h3, h4, h5, h6");if((n=o==null?void 0:o.textContent)!=null&&n.trim())a.push(`Nearby heading: ${o.textContent.trim().slice(0,80)}`);else{const s=(r=i.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 Va={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(p(e))continue;f(e)||a.push({ruleId:"link-name",selector:d(e),html:c(e),impact:"serious",message:"Link has no discernible text.",context:Pa(e)})}return a}},za={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 i of e){const n=i.getAttribute("href");if(!n||n==="#")continue;const r=w(i).toLowerCase();if(!(r.includes("skip")||r.includes("jump")||r.includes("main content")||r.includes("navigation")))continue;const s=n.slice(1);t.getElementById(s)||a.push({ruleId:"skip-link",selector:d(i),html:c(i),impact:"moderate",message:`Skip link points to "#${s}" which does not exist on the page.`})}return a}},Ua=new Set(["block","flex","grid","table","table-cell","list-item","flow-root"]),Ga=new Set(["inline","inline-block","inline-flex","inline-grid"]);function Xa(t){let a=t.parentElement;for(;a;){const e=A(a).display;if(Ua.has(e)&&Ya(a))return a;a=a.parentElement}return null}function Ya(t){const a=t.ownerDocument.createTreeWalker(t,NodeFilter.SHOW_TEXT);let e;for(;e=a.nextNode();){if(!e.data.trim())continue;let i=e.parentElement,n=!1;for(;i&&i!==t;){if(i.tagName==="A"){n=!0;break}i=i.parentElement}if(!n)return!0}return!1}function Ka(t,a){const e=t.ownerDocument.createTreeWalker(t,NodeFilter.SHOW_TEXT);let i;for(;i=e.nextNode();){if(!i.data.trim())continue;let n=i.parentElement,r=!1,o=n;for(;o&&o!==t;){if(o.tagName==="A"){r=!0;break}o=o.parentElement}if(!r&&n)return R(A(n).color)}return null}function Qa(t,a){const e=t.textDecorationLine||t.textDecoration||"",i=a.textDecorationLine||a.textDecoration||"";if((e.includes("underline")||e.includes("line-through"))&&e!==i)return!0;const n=parseFloat(t.borderBottomWidth)||0,r=t.borderBottomStyle||"";if(n>0&&r!=="none"&&r!=="hidden")return!0;const o=parseFloat(t.outlineWidth)||0,s=t.outlineStyle||"";if(o>0&&s!=="none")return!0;const u=t.backgroundImage||"";if(u&&u!=="none"&&u!=="initial")return!0;const h=ue(t.fontWeight),l=ue(a.fontWeight);if(Math.abs(h-l)>=300||t.fontStyle!==a.fontStyle)return!0;const m=parseFloat(t.fontSize)||16,g=parseFloat(a.fontSize)||16;return g>0&&m/g>=1.2}function ue(t){return t==="bold"?700:t==="normal"?400:parseInt(t)||400}function de(t,a,e){return"#"+[t,a,e].map(i=>i.toString(16).padStart(2,"0")).join("")}const Ja={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(p(e)||!w(e).trim())continue;const i=A(e),n=i.display||"inline";if(!Ga.has(n))continue;const r=Xa(e);if(!r)continue;const o=A(r);if(Qa(i,o))continue;const s=R(i.color),u=Ka(r);if(!s||!u)continue;const h=q(...s),l=q(...u),m=ye(h,l);if(m>=3)continue;const g=de(...s),b=de(...u),v=`link color: ${g} rgb(${s.join(", ")}), surrounding text: ${b} rgb(${u.join(", ")}), ratio: ${m.toFixed(2)}:1`;a.push({ruleId:"link-in-text-block",selector:d(e),html:c(e),impact:"serious",message:"Link in text block is not visually distinguishable from surrounding text. Add an underline, border, or ensure 3:1 color contrast with surrounding text.",context:v})}return a}},Za={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:"Determine the page's primary language and suggest the appropriate lang value.",run(t){var e;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(n=>n.tagName.toLowerCase()==="svg"||n.tagName.toLowerCase()==="math"))return[]}return(e=a.getAttribute("lang"))!=null&&e.trim()?[]:[{ruleId:"html-has-lang",selector:d(a),html:c(a),impact:"serious",message:"<html> element missing lang attribute."}]}},ei=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(" ")),ti=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(" ")),ai=/^[a-z]{2,8}(-[a-z0-9]{1,8})*$/i;function we(t){if(!ai.test(t))return!1;const a=t.split("-")[0].toLowerCase();return a.length===2?ei.has(a):a.length===3?!ti.has(a):!1}const ii={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&&!we(a)?[{ruleId:"html-lang-valid",selector:"html",html:c(t.documentElement),impact:"serious",message:`Invalid lang attribute value "${a}".`}]:[]}};function me(t){var i;const a=t.ownerDocument.createTreeWalker(t,NodeFilter.SHOW_TEXT);let e;for(;e=a.nextNode();){if(!e.data.trim())continue;const n=e.parentElement;if(!n||n instanceof HTMLElement&&(n.hidden||n.style.display==="none"))continue;let r=n,o=!1;for(;r&&r!==t;){if(r.hasAttribute("lang")){o=!0;break}r=r.parentElement}if(!o)return!0}for(const n of t.querySelectorAll("img[alt]")){if(!((i=n.getAttribute("alt"))==null?void 0:i.trim()))continue;let o=n.parentElement,s=!1;for(;o&&o!==t;){if(o.hasAttribute("lang")){s=!0;break}o=o.parentElement}if(!s)return!0}return!1}const ni={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(p(e)||e===t.documentElement)continue;const i=e.getAttribute("lang"),n=i==null?void 0:i.trim();if(i&&!n){me(e)&&a.push({ruleId:"valid-lang",selector:d(e),html:c(e),impact:"serious",message:"Empty lang attribute value."});continue}n&&me(e)&&(we(n)||a.push({ruleId:"valid-lang",selector:d(e),html:c(e),impact:"serious",message:`Invalid lang attribute value "${n}".`}))}return a}},ri={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 n,r;const a=t.documentElement,e=(n=a.getAttribute("lang"))==null?void 0:n.trim().toLowerCase(),i=(r=a.getAttribute("xml:lang"))==null?void 0:r.trim().toLowerCase();if(e&&i){const o=e.split("-")[0],s=i.split("-")[0];if(o!==s)return[{ruleId:"html-xml-lang-mismatch",selector:"html",html:c(a),impact:"moderate",message:`lang="${e}" and xml:lang="${i}" do not match.`}]}return[]}},oi={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(p(e))continue;const i=e.closest("table");if(!i)continue;const n=e.getAttribute("id"),r=e.getAttribute("headers").split(/\s+/);for(const o of r){if(o===n){a.push({ruleId:"td-headers-attr",selector:d(e),html:c(e),impact:"serious",message:`Headers attribute references the cell itself ("${o}").`});break}if(!i.querySelector(`th#${CSS.escape(o)}, td#${CSS.escape(o)}`)){a.push({ruleId:"td-headers-attr",selector:d(e),html:c(e),impact:"serious",message:`Headers attribute references non-existent ID "${o}".`});break}}}return a}},si={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(p(e)||e.getAttribute("role")==="presentation"||e.getAttribute("role")==="none")continue;const i=e.querySelectorAll("th"),n=e.querySelectorAll("td");i.length>0&&n.length===0&&a.push({ruleId:"th-has-data-cells",selector:d(e),html:c(e),impact:"serious",message:"Table has header cells but no data cells."})}return a}},li={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,i;const a=[];for(const n of t.querySelectorAll("table")){if(p(n)||n.getAttribute("role")==="presentation"||n.getAttribute("role")==="none")continue;const r=n.querySelectorAll("tr"),o=r.length;let s=0;for(const m of r){const g=m.querySelectorAll("td, th");let b=0;for(const v of g)b+=parseInt(v.getAttribute("colspan")||"1",10);s=Math.max(s,b)}if(o<=3&&s<=3)continue;const u=n.querySelector("th")!==null,h=n.querySelector("th[scope]")!==null,l=n.querySelector("td[headers]")!==null;if(u)for(const m of n.querySelectorAll("td")){if(p(m)||m.hasAttribute("headers"))continue;const g=m.closest("tr");if(!g)continue;const b=g.querySelector("th")!==null,v=Array.from(g.children).indexOf(m);let y=!1;const I=n.querySelector("thead");if(I){const x=I.querySelector("tr");x&&((e=x.querySelectorAll("th, td")[v])==null?void 0:e.tagName.toLowerCase())==="th"&&(y=!0)}if(!y){const x=n.querySelector("tbody > tr, tr");x&&((i=x.querySelectorAll("th, td")[v])==null?void 0:i.tagName.toLowerCase())==="th"&&(y=!0)}if(!b&&!y&&!h&&!l){a.push({ruleId:"td-has-header",selector:d(m),html:c(m),impact:"serious",message:"Data cell has no associated header. Add th elements with scope, or headers attribute."});break}}}return a}},ci={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 i;const a=[],e=new Set(["row","col","rowgroup","colgroup"]);for(const n of t.querySelectorAll("th[scope]")){if(p(n))continue;const r=(i=n.getAttribute("scope"))==null?void 0:i.toLowerCase();r&&!e.has(r)&&a.push({ruleId:"scope-attr-valid",selector:d(n),html:c(n),impact:"moderate",message:`Invalid scope value "${r}". Use row, col, rowgroup, or colgroup.`})}return a}},ui={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(p(e))continue;const i=e.closest("table");(i==null?void 0:i.getAttribute("role"))==="presentation"||(i==null?void 0:i.getAttribute("role"))==="none"||f(e)||a.push({ruleId:"empty-table-header",selector:d(e),html:c(e),impact:"minor",message:"Table header cell is empty. Add text or use aria-label."})}return a}},D=["aria-labelledby","aria-describedby","aria-controls","aria-owns","aria-flowto"],di={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 n of t.querySelectorAll("[aria-labelledby], [aria-describedby], [aria-controls], [aria-owns], [aria-flowto]"))for(const r of D){const o=n.getAttribute(r);o&&o.split(/\s+/).forEach(s=>e.add(s))}for(const n of t.querySelectorAll("label[for]")){const r=n.getAttribute("for");r&&e.add(r)}const i=new Map;for(const n of t.querySelectorAll("[id]"))e.has(n.id)&&(n instanceof HTMLElement&&(n.style.display==="none"||n.style.visibility==="hidden"||n.hidden)||i.set(n.id,(i.get(n.id)??0)+1));for(const[n,r]of i){if(r<=1)continue;const o=t.querySelectorAll(`#${CSS.escape(n)}`),s=t.querySelector(D.map(l=>`[${l}~="${CSS.escape(n)}"]`).join(", ")),u=t.querySelector(`label[for="${CSS.escape(n)}"]`);let h;if(s){const l=D.find(m=>{var g;return(g=s.getAttribute(m))==null?void 0:g.split(/\s+/).includes(n)});l&&(h=l)}else u&&(h="label[for]");a.push({ruleId:"duplicate-id-aria",selector:d(o[1]),html:c(o[1]),impact:"critical",message:`Duplicate ID "${n}" referenced by ${h??"an accessibility attribute"}.`,context:`First element: ${c(o[0])}${h?`
|
|
5
|
-
Referenced by: ${h}`:""}`})}return a}},
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});let O=new WeakMap;function pe(){O=new WeakMap}function z(t){var i;const a=t.tagName.toLowerCase(),e=(i=t.getAttribute("type"))==null?void 0:i.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 L(t){var n;const a=O.get(t);if(a!==void 0)return a;const i=((n=t.getAttribute("role"))==null?void 0:n.trim().toLowerCase())||null||z(t);return O.set(t,i),i}let B=new WeakMap;function xe(){B=new WeakMap}function f(t){const a=B.get(t);if(a!==void 0)return a;const e=ke(t);return B.set(t,e),e}function ke(t){var r,o,s,d,h;const a=t.getAttribute("aria-labelledby");if(a){const l=a.split(/\s+/).map(m=>{const g=t.ownerDocument.getElementById(m);return g?w(g).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;if(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement||t instanceof HTMLSelectElement){if(t.id){const g=t.ownerDocument.querySelector(`label[for="${CSS.escape(t.id)}"]`),b=g?w(g).trim():"";if(b)return b}const l=t.closest("label"),m=l?w(l).trim():"";if(m)return m}const i=(o=t.getAttribute("title"))==null?void 0:o.trim();if(i)return i;if(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement){const l=(s=t.getAttribute("placeholder"))==null?void 0:s.trim();if(l)return l}const n=t.tagName.toLowerCase();if(n==="fieldset"){const l=t.querySelector(":scope > legend");if(l){const m=w(l).trim();if(m)return m}}if(n==="table"){const l=t.querySelector(":scope > caption");if(l){const m=w(l).trim();if(m)return m}}if(!(t instanceof HTMLInputElement)){const l=w(t).trim();if(l)return l}return t instanceof HTMLImageElement||t instanceof HTMLAreaElement?((d=t.alt)==null?void 0:d.trim())??"":t instanceof HTMLInputElement&&t.type==="image"?((h=t.alt)==null?void 0:h.trim())??"":""}const Ie=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 ge(t){const a=t.trim().toLowerCase().replace(/[\u201C\u201D\u2018\u2019\u00AB\u00BB]/g,"");return Ie.has(a)}let W=new WeakMap;function be(){W=new WeakMap}function p(t){const a=W.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=p(t.parentElement):e=!1,W.set(t,e),e}function Ee(t){return!!(t.getAttribute("aria-hidden")==="true"||t instanceof HTMLElement&&(t.hidden||t.style.display==="none"))}function w(t){var e,i,n,r,o;let a="";for(const s of t.childNodes)if(s.nodeType===3)a+=s.textContent??"";else if(s.nodeType===1){const d=s;if(!Ee(d)){const h=(e=d.tagName)==null?void 0:e.toLowerCase();if(h==="img"||h==="area"){const l=d.getAttribute("aria-labelledby");if(l){const m=l.split(/\s+/).map(g=>{var b,v;return((v=(b=d.ownerDocument.getElementById(g))==null?void 0:b.textContent)==null?void 0:v.trim())??""}).filter(Boolean);if(m.length){a+=m.join(" ");continue}}a+=((i=d.getAttribute("aria-label"))==null?void 0:i.trim())??d.getAttribute("alt")??((n=d.getAttribute("title"))==null?void 0:n.trim())??""}else if(h==="svg"){const l=(r=d.getAttribute("aria-label"))==null?void 0:r.trim();if(l)a+=l;else{const m=d.querySelector("title");m&&(a+=m.textContent??"")}}else(o=d.getAttribute("aria-label"))!=null&&o.trim()?a+=d.getAttribute("aria-label").trim():a+=w(d)}}return a}let F=new WeakMap;function Te(){F=new WeakMap}function Ce(t){return t.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}const Le=["data-testid","data-test-id","data-cy","data-id","name","href","for","aria-label"];function qe(t){const a=t.tagName.toLowerCase();for(const i of Le){const n=t.getAttribute(i);if(n!=null&&n.length>0&&n.length<100)return`${a}[${i}="${Ce(n)}"]`}const e=t.parentElement;if(e){let i=0,n=0;for(let r=0;r<e.children.length;r++)e.children[r].tagName===t.tagName&&(i++,e.children[r]===t&&(n=i));if(i>1)return`${a}:nth-of-type(${n})`}return a}function H(t){if(t.id)return`#${CSS.escape(t.id)}`;const a=t.getRootNode(),e=a instanceof ShadowRoot?null:a.documentElement,i=[];let n=t;for(;n&&n!==e;){if(n!==t&&n.id){i.unshift(`#${CSS.escape(n.id)}`);break}if(i.unshift(qe(n)),i.length>=2){const r=i.join(" > ");try{const o=a.querySelectorAll(r);if(o.length===1&&o[0]===t)return r}catch{}}n=n.parentElement}return i.join(" > ")}function u(t){var r;const a=F.get(t);if(a!==void 0)return a;const e=[];let i=t;for(;i;){const o=i.getRootNode();if(o instanceof ShadowRoot)e.unshift({selector:H(i),delimiter:" >>> "}),i=o.host;else{const s=(r=o.defaultView)==null?void 0:r.frameElement;if(s)e.unshift({selector:H(i),delimiter:" >>>iframe> "}),i=s;else{e.unshift({selector:H(i),delimiter:""});break}}}const n=e.map((o,s)=>(s===0?"":o.delimiter)+o.selector).join("");return F.set(t,n),n}function Re(t){const a=[],e=[];let i=t;for(;i;){const r=i.indexOf(" >>>iframe> "),o=i.indexOf(" >>> ");if(r!==-1&&(o===-1||r<=o))a.push(i.slice(0,r).trim()),e.push("iframe"),i=i.slice(r+12);else if(o!==-1)a.push(i.slice(0,o).trim()),e.push("shadow"),i=i.slice(o+5);else{a.push(i.trim());break}}let n=document;for(let r=0;r<a.length;r++){const o=n.querySelector(a[r]);if(!o)return null;if(r<a.length-1)if(e[r]==="iframe"){const s=o.contentDocument;if(!s)return null;n=s}else{const s=o.shadowRoot;if(!s)return null;n=s}else return o}return null}function c(t){const a=t.outerHTML;return a.length>200?a.slice(0,200)+"...":a}const Ne=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"]),Q=new Set(["aria-atomic","aria-busy","aria-disabled","aria-grabbed","aria-hidden","aria-modal","aria-multiline","aria-multiselectable","aria-readonly","aria-required"]),J=new Set(["aria-checked","aria-pressed"]),Me=new Set(["aria-colcount","aria-colindex","aria-colspan","aria-level","aria-posinset","aria-rowcount","aria-rowindex","aria-rowspan","aria-setsize"]),He=new Set(["aria-valuemax","aria-valuemin","aria-valuenow"]),Z={"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"])},ee=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},De={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,C=null;function fe(){T=null,C=null}function U(t){var n;if(C&&(T==null?void 0:T.deref())===t)return C;const a=[],e=[],i=[];for(const r of t.querySelectorAll("*")){let o=!1;for(const l of r.attributes)if(l.name.startsWith("aria-")){o=!0;break}if(!o)continue;let s,d;const h=()=>(s===void 0&&(s=u(r),d=c(r)),{selector:s,html:d});for(const l of r.attributes)if(l.name.startsWith("aria-")&&!Ne.has(l.name)){const m=h();a.push({ruleId:"aria-valid-attr",selector:m.selector,html:m.html,impact:"critical",message:`Invalid ARIA attribute "${l.name}".`});break}for(const l of r.attributes){if(!l.name.startsWith("aria-"))continue;const m=l.value.trim();if(!(m===""&&!Q.has(l.name)&&!J.has(l.name))){if(Q.has(l.name)){if(m!=="true"&&m!=="false"){const g=h();e.push({ruleId:"aria-valid-attr-value",selector:g.selector,html:g.html,impact:"critical",message:`${l.name} must be "true" or "false", got "${m}".`})}}else if(J.has(l.name)){if(m!=="true"&&m!=="false"&&m!=="mixed"){const g=h();e.push({ruleId:"aria-valid-attr-value",selector:g.selector,html:g.html,impact:"critical",message:`${l.name} must be "true", "false", or "mixed", got "${m}".`})}}else if(Me.has(l.name)){if(m===""||!/^-?\d+$/.test(m)){const g=h();e.push({ruleId:"aria-valid-attr-value",selector:g.selector,html:g.html,impact:"critical",message:`${l.name} must be an integer, got "${m}".`})}}else if(He.has(l.name)){if(m===""||isNaN(Number(m))){const g=h();e.push({ruleId:"aria-valid-attr-value",selector:g.selector,html:g.html,impact:"critical",message:`${l.name} must be a number, got "${m}".`})}}else if(Z[l.name]){const g=m.split(/\s+/);for(const b of g)if(!Z[l.name].has(b)){const v=h();e.push({ruleId:"aria-valid-attr-value",selector:v.selector,html:v.html,impact:"critical",message:`Invalid value "${m}" for ${l.name}.`});break}}}}if(!p(r)){const l=(n=r.getAttribute("role"))==null?void 0:n.trim().toLowerCase(),m=r.tagName.toLowerCase();if(!l&&$e[m]){const g=r.hasAttribute("aria-label"),b=r.hasAttribute("aria-labelledby");if(g||b){const v=h();i.push({ruleId:"aria-prohibited-attr",selector:v.selector,html:v.html,impact:"serious",message:`aria-label and aria-labelledby are prohibited on <${m}> elements.`})}}else if(l){if(ee.has(l)){const b=r.hasAttribute("aria-label"),v=r.hasAttribute("aria-labelledby");if(b||v){const y=h();i.push({ruleId:"aria-prohibited-attr",selector:y.selector,html:y.html,impact:"serious",message:`aria-label and aria-labelledby are prohibited on role "${l}".`})}}const g=De[l];if(g){for(const b of r.attributes)if(b.name.startsWith("aria-")&&g.has(b.name)){if((b.name==="aria-label"||b.name==="aria-labelledby")&&ee.has(l))continue;const v=h();i.push({ruleId:"aria-prohibited-attr",selector:v.selector,html:v.html,impact:"serious",message:`Attribute "${b.name}" is prohibited on role "${l}".`})}}}}}return T=new WeakRef(t),C={validAttr:a,validAttrValue:e,prohibitedAttr:i},C}let _=new WeakMap,j=new WeakMap,P=new WeakMap;function ve(){_=new WeakMap,j=new WeakMap,P=new WeakMap}function A(t){let a=_.get(t);return a||(a=getComputedStyle(t),_.set(t,a),a)}function q(t,a,e){const[i,n,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*i+.7152*n+.0722*r}function ye(t,a){const e=Math.max(t,a),i=Math.min(t,a);return(e+.05)/(i+.05)}const te={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 R(t){const a=t.trim().toLowerCase();if(te[a])return te[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 i=a.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/);if(i)return[parseInt(i[1],16),parseInt(i[2],16),parseInt(i[3],16)];const n=t.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*[\d.]+)?\s*\)/);if(n)return[parseInt(n[1]),parseInt(n[2]),parseInt(n[3])];const 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 Oe(t){const a=j.get(t);if(a!==void 0)return a;const e=Be(t);return j.set(t,e),e}function Be(t){let a=t;for(;a;){const e=A(a),i=e.backgroundImage;if(i&&i!=="none"&&i!=="initial")return null;const n=e.backgroundColor;if(n==="transparent"||n==="rgba(0, 0, 0, 0)"||n==="rgba(0 0 0 / 0)"){a=a.parentElement;continue}const r=n.match(/rgba\(.+?,\s*([\d.]+)\s*\)/)||n.match(/rgba?\(.+?\/\s*([\d.]+%?)\s*\)/);if(r&&(r[1].endsWith("%")?parseFloat(r[1])/100:parseFloat(r[1]))<.1){a=a.parentElement;continue}return R(n)}return[255,255,255]}const We=new Set(["IMG","PICTURE","VIDEO","SVG"]);function Fe(t){const a=P.get(t);if(a!==void 0)return a;const e=_e(t);return P.set(t,e),e}function _e(t){let a=t,e=!1;for(;a;){const i=A(a).position;if((i==="absolute"||i==="fixed")&&(e=!0),a!==t&&i!=="static"){for(const n of a.children)if(!(n===t||n.contains(t))&&We.has(n.tagName)){if(e)return!0;const r=A(n).position;if(r==="absolute"||r==="fixed")return!0}if(e)break}a=a.parentElement}return!1}function je(t){const a=parseFloat(t);return t.endsWith("pt")?a*(4/3):a}function Pe(t){const a=A(t),e=je(a.fontSize),i=parseInt(a.fontWeight)||(a.fontWeight==="bold"?700:400);return e>=23.5||e>=18.5&&i>=700}function $(t){var r,o;const a=[],e=t.closest("a");if(e){const s=e.getAttribute("href");s&&a.push(`Link href: ${s}`)}const i=t.closest("figure");if(i){const s=i.querySelector("figcaption");(r=s==null?void 0:s.textContent)!=null&&r.trim()&&a.push(`Figcaption: ${s.textContent.trim().slice(0,100)}`)}const n=t.parentElement;if(n&&n!==e){const s=t instanceof HTMLImageElement&&t.alt||"",d=(o=n.textContent)==null?void 0:o.replace(s,"").trim().slice(0,100);d&&a.push(`Adjacent text: ${d}`)}return a.length>0?a.join(`
|
|
2
|
+
`):void 0}function ae(t){let a=t;for(;a;){if(a instanceof HTMLElement&&a.style.visibility==="hidden")return!0;a=a.parentElement}return!1}const Ve={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(p(e)||ae(e))continue;const i=e.getAttribute("role");if(i==="presentation"||i==="none"){const r=e.getAttribute("tabindex");if(!r||r==="-1")continue}const n=e.getAttribute("alt");if(n!==null&&n.trim()===""&&n!==""){a.push({ruleId:"img-alt",selector:u(e),html:c(e),impact:"critical",message:'Image has whitespace-only alt text. Use alt="" for decorative images or provide descriptive text.',context:$(e)});continue}!e.hasAttribute("alt")&&!f(e)&&a.push({ruleId:"img-alt",selector:u(e),html:c(e),impact:"critical",message:"Image element missing alt attribute.",context:$(e)})}for(const e of t.querySelectorAll('[role="img"]:not(img):not(svg)'))p(e)||ae(e)||f(e)||a.push({ruleId:"img-alt",selector:u(e),html:c(e),impact:"critical",message:'Element with role="img" has no accessible name. Add aria-label or aria-labelledby.',context:$(e)});return a}};function ze(t){var r,o,s;const a=t.getAttribute("aria-labelledby");if(a){const d=a.split(/\s+/).map(h=>{var l,m;return((m=(l=t.ownerDocument.getElementById(h))==null?void 0:l.textContent)==null?void 0:m.trim())??""}).filter(Boolean);if(d.length)return d.join(" ")}const e=(r=t.getAttribute("aria-label"))==null?void 0:r.trim();if(e)return e;const i=t.querySelector("title");if((o=i==null?void 0:i.textContent)!=null&&o.trim())return i.textContent.trim();const n=(s=t.getAttribute("title"))==null?void 0:s.trim();return n||""}const Ue={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 i of t.querySelectorAll(e)){if(p(i))continue;if(!ze(i)){const r=i.getAttribute("role");a.push({ruleId:"svg-img-alt",selector:u(i),html:c(i),impact:"serious",message:`${i.tagName.toLowerCase()} with role='${r}' has no accessible name.`})}}return a}},Ge={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"]'))p(e)||f(e)||a.push({ruleId:"input-image-alt",selector:u(e),html:c(e),impact:"critical",message:"Image input missing alt text."});return a}},Xe={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:"Show the duplicated text and suggest either an empty alt or a complementary description.",run(t){var e;const a=[];for(const i of t.querySelectorAll("img[alt]")){const n=i.getAttribute("alt").trim().toLowerCase();if(!n)continue;const r=i.closest("a, button");if(r){const o=((e=r.textContent)==null?void 0:e.trim().toLowerCase())||"";o&&o===n&&a.push({ruleId:"image-redundant-alt",selector:u(i),html:c(i),impact:"minor",message:`Alt text "${i.getAttribute("alt")}" duplicates surrounding ${r.tagName.toLowerCase()} text.`})}}return a}},Ye=["image","picture","photo","graphic","icon","img"],Ke={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:"Identify the redundant word(s) in the alt text and show the corrected version with those words removed.",run(t){const a=[];for(const e of t.querySelectorAll("img[alt]")){const i=e.getAttribute("alt").toLowerCase();i&&Ye.some(n=>i.split(/\s+/).includes(n))&&a.push({ruleId:"image-alt-redundant-words",selector:u(e),html:c(e),impact:"minor",message:`Alt text "${e.getAttribute("alt")}" contains redundant word(s).`})}return a}},Qe={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(p(e))continue;f(e)||a.push({ruleId:"area-alt",selector:u(e),html:c(e),impact:"critical",message:"Image map <area> element is missing alternative text."})}return a}};function Je(t){var n,r;const a=t.getAttribute("aria-labelledby");if(a){const o=a.split(/\s+/).map(s=>{var d,h;return((h=(d=t.ownerDocument.getElementById(s))==null?void 0:d.textContent)==null?void 0:h.trim())??""}).filter(Boolean);if(o.length)return o.join(" ")}const e=(n=t.getAttribute("aria-label"))==null?void 0:n.trim();if(e)return e;const i=(r=t.getAttribute("title"))==null?void 0:r.trim();return i||""}const Ze={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 i of t.querySelectorAll("object")){if(p(i)||i instanceof HTMLElement&&i.style.visibility==="hidden")continue;let n=i.parentElement,r=!1;for(;n;){if(n instanceof HTMLElement&&n.style.visibility==="hidden"){r=!0;break}n=n.parentElement}if(r||i.getAttribute("role")==="presentation"||i.getAttribute("role")==="none"||Je(i))continue;const o=i.getAttribute("data")||"";if(!((i.getAttribute("type")||"").startsWith("image/")||/\.(png|jpg|jpeg|gif|svg|webp|bmp|ico)$/i.test(o))){const h=i.querySelector("img[alt]");if(h&&((e=h.getAttribute("alt"))!=null&&e.trim()))continue}a.push({ruleId:"object-alt",selector:u(i),html:c(i),impact:"serious",message:"<object> element is missing alternative text. Add aria-label, aria-labelledby, or a title attribute."})}return a}},et={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(p(e)||e.tagName.toLowerCase()==="svg"||e.tagName.toLowerCase()==="img")continue;f(e)||a.push({ruleId:"role-img-alt",selector:u(e),html:c(e),impact:"serious",message:"Element with role='img' has no accessible name. Add aria-label or aria-labelledby."})}return a}};function tt(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 n=at(e);return n||null}function at(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 k(t,a,e){let i=t;if(i.includes("{{tag}}")&&(i=i.replace(/\{\{tag\}\}/g,a.tagName.toLowerCase())),i.includes("{{value}}")){let n="";"attribute"in e&&e.attribute&&(n=a.getAttribute(e.attribute)??""),i=i.replace(/\{\{value\}\}/g,n)}return i}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){const i=[];switch(t.check.type){case"selector-exists":{for(const n of e.querySelectorAll(t.selector))a&&p(n)||i.push({ruleId:t.id,selector:u(n),html:c(n),impact:t.impact,message:k(t.message,n,t.check),element:n});break}case"attribute-value":{const{attribute:n,operator:r,value:o}=t.check;for(const s of e.querySelectorAll(t.selector)){if(a&&p(s))continue;const d=s.getAttribute(n);d!==null&&it(d,r,o)&&i.push({ruleId:t.id,selector:u(s),html:c(s),impact:t.impact,message:k(t.message,s,t.check),element:s})}break}case"attribute-missing":{const{attribute:n}=t.check;for(const r of e.querySelectorAll(t.selector))a&&p(r)||r.hasAttribute(n)||i.push({ruleId:t.id,selector:u(r),html:c(r),impact:t.impact,message:k(t.message,r,t.check),element:r});break}case"attribute-regex":{const{attribute:n,pattern:r,flags:o,shouldMatch:s}=t.check;let d;try{d=new RegExp(r,o)}catch{break}for(const h of e.querySelectorAll(t.selector)){if(a&&p(h))continue;const l=h.getAttribute(n);if(l===null)continue;const m=d.test(l);s&&!m?i.push({ruleId:t.id,selector:u(h),html:c(h),impact:t.impact,message:k(t.message,h,t.check),element:h}):!s&&m&&i.push({ruleId:t.id,selector:u(h),html:c(h),impact:t.impact,message:k(t.message,h,t.check),element:h})}break}case"child-required":{const{childSelector:n}=t.check;for(const r of e.querySelectorAll(t.selector))a&&p(r)||r.querySelector(n)||i.push({ruleId:t.id,selector:u(r),html:c(r),impact:t.impact,message:k(t.message,r,t.check),element:r});break}case"child-invalid":{const n=new Set(t.check.allowedChildren.map(r=>r.toLowerCase()));for(const r of e.querySelectorAll(t.selector))if(!(a&&p(r))){for(const o of r.children)if(!n.has(o.tagName.toLowerCase())){i.push({ruleId:t.id,selector:u(o),html:c(o),impact:t.impact,message:k(t.message,o,t.check),element:o});break}}break}}return i}}}function it(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 nt={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."},rt=S(nt),ot=['[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"]),lt=new Set(["combobox","listbox","searchbox","slider","spinbutton","textbox"]);function ct(t){var o,s,d,h;const a=(o=t.getAttribute("role"))==null?void 0:o.trim().toLowerCase();if(a&&st.has(a)||(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement)&&!(a&<.has(a)))return f(t);const i=t.getAttribute("aria-labelledby");if(i){const l=i.split(/\s+/).map(m=>{const g=t.ownerDocument.getElementById(m);return g?w(g).trim():""}).filter(Boolean);if(l.length)return l.join(" ")}const n=(s=t.getAttribute("aria-label"))==null?void 0:s.trim();if(n)return n;if(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement||t instanceof HTMLSelectElement){if(t.id){const m=t.ownerDocument.querySelector(`label[for="${CSS.escape(t.id)}"]`);if(m){const g=w(m).trim();if(g)return g}}const l=t.closest("label");if(l){const m=w(l).trim();if(m)return m}}const r=(d=t.getAttribute("title"))==null?void 0:d.trim();if(r)return r;if(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement){const l=(h=t.getAttribute("placeholder"))==null?void 0:h.trim();if(l)return l}return""}const ut={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:"Based on the input type, name attribute, or placeholder, suggest a label element with appropriate text, or an aria-label.",run(t){var n;const a=[],i=t.querySelectorAll(`input:not([type="hidden"]):not([type="submit"]):not([type="button"]):not([type="reset"]):not([type="image"]), textarea, select, ${ot}`);for(const r of i){if(p(r)||r instanceof HTMLElement&&(r.hidden||r.style.display==="none"))continue;const o=(n=r.getAttribute("role"))==null?void 0:n.trim().toLowerCase();if(o==="presentation"||o==="none")continue;ct(r)||a.push({ruleId:"label",selector:u(r),html:c(r),impact:"critical",message:"Form element has no accessible label."})}return a}},dt={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 i of e){if(p(i)||!i.id)continue;const n=t.querySelectorAll(`label[for="${CSS.escape(i.id)}"]`);let r=0,o=i.parentElement;for(;o;){if(o.tagName.toLowerCase()==="label"&&!o.hasAttribute("for")){r++;break}o=o.parentElement}const s=n.length+r;s>1&&a.push({ruleId:"form-field-multiple-labels",selector:u(i),html:c(i),impact:"moderate",message:`Form field has ${s} labels. Use a single label element.`})}return a}},mt={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"))p(e)||f(e)||a.push({ruleId:"select-name",selector:u(e),html:c(e),impact:"critical",message:"Select element has no accessible name."});return a}},ht={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,i;const a=[];for(const n of t.querySelectorAll('input[type="submit"], input[type="button"], input[type="reset"]')){if(p(n))continue;const r=(e=n.getAttribute("value"))==null?void 0:e.trim(),o=(i=n.getAttribute("type"))==null?void 0:i.toLowerCase(),s=(o==="submit"||o==="reset")&&!n.hasAttribute("value");!r&&!s&&!f(n)&&a.push({ruleId:"input-button-name",selector:u(n),html:c(n),impact:"critical",message:"Input button has no discernible text."})}return a}},pt=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"]),gt=new Set(["tel","tel-country-code","tel-national","tel-area-code","tel-local","tel-extension","email","impp"]),bt=new Set(["home","work","mobile","fax","pager"]),ft=new Set(["shipping","billing"]),vt=new Set(["webauthn"]);function yt(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&&ft.has(a[e])&&e++;let i=!1;if(e<a.length&&bt.has(a[e])&&(i=!0,e++),e>=a.length)return!1;const n=a[e];return!pt.has(n)||i&&!gt.has(n)?!1:(e++,e<a.length&&vt.has(a[e])&&e++,e===a.length)}const wt={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(p(e)||e instanceof HTMLElement&&e.style.display==="none"||e.disabled||e.getAttribute("aria-disabled")==="true")continue;const i=e.getAttribute("autocomplete").trim();i&&(yt(i)||a.push({ruleId:"autocomplete-valid",selector:u(e),html:c(e),impact:"serious",message:`Invalid autocomplete value "${i}".`}))}return a}};function ie(t){return t.toLowerCase().replace(/\s+/g," ").trim()}function ne(t,a){const e=ie(t),i=ie(a);if(!e||!i||e.includes(i)||i.includes(e))return!0;const n=i.split(/\s+/).map(r=>r.replace(/[.,;:!?\u2026]+$/g,"")).filter(r=>r.length>2);return n.length>=2&&n.filter(o=>e.includes(o)).length/n.length>.5}function V(t){let a="";for(const e of t.childNodes)if(e.nodeType===3)a+=e.textContent??"";else if(e.nodeType===1){const i=e,n=i.tagName.toLowerCase();if(n==="style"||n==="script"||n==="svg"||i.getAttribute("aria-hidden")==="true"||i instanceof HTMLElement&&i.style.display==="none")continue;const r=i.getAttribute("role");if(r==="img"||r==="presentation"||r==="none")continue;a+=V(i)}return a}const At={id:"label-content-name-mismatch",wcag:["2.5.3"],level:"A",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(p(e))continue;const i=f(e);if(!i)continue;let n="";e instanceof HTMLInputElement?n=e.value||"":n=V(e);const r=n.trim();if(!r||r.length<=2)continue;const o=e.hasAttribute("aria-label"),s=e.hasAttribute("aria-labelledby");!o&&!s||ne(i,n)||a.push({ruleId:"label-content-name-mismatch",selector:u(e),html:c(e),impact:"serious",message:`Accessible name "${i}" does not contain visible text "${n.trim()}".`})}for(const e of t.querySelectorAll("input, select, textarea")){if(p(e)||e instanceof HTMLInputElement&&["hidden","submit","button","image"].includes(e.type))continue;const i=f(e);if(!i||!e.hasAttribute("aria-label"))continue;const r=e.id;let o="";if(r){const s=t.querySelector(`label[for="${CSS.escape(r)}"]`);s&&(o=V(s))}o.trim()&&(ne(i,o)||a.push({ruleId:"label-content-name-mismatch",selector:u(e),html:c(e),impact:"serious",message:`Accessible name "${i}" does not contain visible label "${o.trim()}".`}))}return a}},St={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 i,n,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(p(s))continue;const d=s.hasAttribute("title")&&((i=s.getAttribute("title"))==null?void 0:i.trim()),h=s.hasAttribute("aria-label")&&((n=s.getAttribute("aria-label"))==null?void 0:n.trim()),l=s.hasAttribute("aria-labelledby");let m=!1;const g=s.id;if(g){const v=s.ownerDocument.querySelector(`label[for="${CSS.escape(g)}"]`);(r=v==null?void 0:v.textContent)!=null&&r.trim()&&(m=!0)}const b=s.closest("label");(o=b==null?void 0:b.textContent)!=null&&o.trim()&&(m=!0),d&&!h&&!l&&!m&&a.push({ruleId:"label-title-only",selector:u(s),html:c(s),impact:"serious",message:"Form element uses title attribute as only label. Use <label>, aria-label, or aria-labelledby instead."})}return a}},xt={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."},kt=S(xt),It=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"]),Et={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 i=e.tagName.toLowerCase();if(!It.has(i))continue;e.getAttribute("role")||a.push({ruleId:"focus-order-semantics",selector:u(e),html:c(e),impact:"moderate",message:`Non-interactive <${i}> with tabindex="0" has no interactive role.`})}return a}},Tt=new Set(["a","audio","button","img","input","select","textarea","video"]),Ct=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"]),Lt={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 qt(t,a){var n,r,o;const e=(n=t.getAttribute("role"))==null?void 0:n.toLowerCase(),i=(r=a.getAttribute("role"))==null?void 0:r.toLowerCase();return!e||!i?!1:((o=Lt[e])==null?void 0:o.has(i))??!1}function Rt(t){var n;const a=t.tagName.toLowerCase();if(Tt.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=(n=t.getAttribute("role"))==null?void 0:n.toLowerCase();if(e&&Ct.has(e))return!0;const i=t.getAttribute("tabindex");return i!==null&&i!=="-1"||t.getAttribute("contenteditable")==="true"}function Nt(t){const a=t.tagName.toLowerCase();return!!(a==="a"&&t.hasAttribute("href")||a==="button"&&!t.disabled)}const Mt={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 n=(t.body?t:t.ownerDocument).createTreeWalker(e,NodeFilter.SHOW_ELEMENT),r=[];let o=n.currentNode;for(;o;){for(;r.length>0&&!r[r.length-1].contains(o);)r.pop();if(!p(o)&&Rt(o)){if(r.length>0){const s=r[r.length-1];qt(s,o)||a.push({ruleId:"nested-interactive",selector:u(o),html:c(o),impact:"serious",message:`Interactive element <${o.tagName.toLowerCase()}> is nested inside <${s.tagName.toLowerCase()}>.`})}Nt(o)&&r.push(o)}o=n.nextNode()}return a}},Ht={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){const a=[];for(const e of t.querySelectorAll("*")){if(p(e)||!(e instanceof HTMLElement))continue;const i=A(e),n=i.overflowX,r=i.overflowY;if(!(n==="scroll"||n==="auto"||r==="scroll"||r==="auto"))continue;if(e.scrollHeight>0||e.clientHeight>0){if(e.scrollHeight<=e.clientHeight&&e.scrollWidth<=e.clientWidth)continue}else{const l=i.height!==""||i.maxHeight!=="",m=e.textContent!=null&&e.textContent.trim().length>0;if(!l||!m)continue}const d=e.getAttribute("tabindex");d!==null&&d!=="-1"||e.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:u(e),html:c(e),impact:"serious",message:"Scrollable region is not keyboard accessible. Add tabindex='0' or include focusable elements."})}return a}},$t={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 i;const a=[],e=new Map;for(const n of t.querySelectorAll("[accesskey]")){if(p(n))continue;const r=(i=n.getAttribute("accesskey"))==null?void 0:i.trim().toLowerCase();if(!r)continue;const o=e.get(r)||[];o.push(n),e.set(r,o)}for(const[n,r]of e)if(r.length>1)for(const o of r.slice(1))a.push({ruleId:"accesskeys",selector:u(o),html:c(o),impact:"serious",message:`Duplicate accesskey "${n}". Each accesskey must be unique.`});return a}},Dt={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 i=0,n=null;for(const r of e){if(p(r))continue;let o;r.hasAttribute("aria-level")?o=parseInt(r.getAttribute("aria-level"),10):o=parseInt(r.tagName[1],10),i>0&&o>i+1&&a.push({ruleId:"heading-order",selector:u(r),html:c(r),impact:"moderate",message:`Heading level ${o} skipped from level ${i}.`,context:n?`Previous heading: ${c(n)}`:void 0}),i=o,n=r}return a}},N='article, aside, main, nav, section, [role="article"], [role="complementary"], [role="main"], [role="navigation"], [role="region"]',re='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"]',Ot={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:u(e),html:c(e),impact:"moderate",message:"Page has multiple main landmarks."})):[]}},Bt={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"]'),i=Array.from(e).filter(n=>!n.closest(N));return i.length>1&&i.slice(1).forEach(n=>a.push({ruleId:"landmark-no-duplicate-banner",selector:u(n),html:c(n),impact:"moderate",message:"Page has multiple banner landmarks."})),a}},Wt={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"]'),i=Array.from(e).filter(n=>!n.closest(N));return i.length>1&&i.slice(1).forEach(n=>a.push({ruleId:"landmark-no-duplicate-contentinfo",selector:u(n),html:c(n),impact:"moderate",message:"Page has multiple contentinfo landmarks."})),a}},Ft={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(i=>a.push({ruleId:"landmark-no-duplicate-main",selector:u(i),html:c(i),impact:"moderate",message:"Page has multiple main landmarks."})),a}},_t={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 i of e)i.closest(N)&&a.push({ruleId:"landmark-banner-is-top-level",selector:u(i),html:c(i),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 i of e)i.closest(N)&&a.push({ruleId:"landmark-contentinfo-is-top-level",selector:u(i),html:c(i),impact:"moderate",message:"Contentinfo landmark is nested within another landmark."});return a}},Pt={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 i of e){const n=i.parentElement;n!=null&&n.closest('article, aside, nav, section, [role="article"], [role="complementary"], [role="navigation"], [role="region"]')&&a.push({ruleId:"landmark-main-is-top-level",selector:u(i),html:c(i),impact:"moderate",message:"Main landmark is nested within another landmark."})}return a}},Vt={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 i of e){const n=i.parentElement;n&&!n.matches('body, main, [role="main"]')&&i.closest('article, nav, section, [role="article"], [role="navigation"], [role="region"]')&&a.push({ruleId:"landmark-complementary-is-top-level",selector:u(i),html:c(i),impact:"moderate",message:"Complementary landmark should be top-level."})}return a}},zt={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:i,type:n}of e){const r=Array.from(t.querySelectorAll(i)).filter(s=>!p(s));if(r.length<=1)continue;const o=new Map;for(const s of r){const d=f(s).toLowerCase()||"",h=o.get(d)||[];h.push(s),o.set(d,h)}for(const[s,d]of o)if(d.length>1)for(const h of d.slice(1))a.push({ruleId:"landmark-unique",selector:u(h),html:c(h),impact:"moderate",message:s?`Multiple ${n} landmarks have the same label "${s}".`:`Multiple ${n} landmarks have no label. Add unique aria-label attributes.`})}return a}},Ut={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 i;const a=[],e=t.body;if(!e)return[];for(const n of e.children){if(p(n)||n instanceof HTMLScriptElement||n instanceof HTMLStyleElement||n.tagName==="NOSCRIPT"||n instanceof HTMLElement&&n.hidden||n.matches('a[href^="#"]'))continue;const r=n.matches(re),o=(i=n.textContent)==null?void 0:i.trim();!r&&o&&(n.querySelector(re)||a.push({ruleId:"region",selector:u(n),html:c(n),impact:"moderate",message:"Content is not contained within a landmark region."}))}return a}},Gt={id:"list",selector:"ul, ol",check:{type:"child-invalid",allowedChildren:["li","script","template"]},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."},Xt=S(Gt),Yt={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:u(e),html:c(e),impact:"serious",message:`<${e.tagName.toLowerCase()}> is not contained in a <dl>.`});return a}},Kt={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."},Qt=S(Kt),Jt={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:"Suggest a descriptive page title based on the visible content.",run(t){var e;const a=t.querySelector("title");return!a||!((e=a.textContent)!=null&&e.trim())?[{ruleId:"document-title",selector:"html",html:"<html>",impact:"serious",message:a?"Document <title> element is empty.":"Document is missing a <title> element."}]:[]}},Zt={id:"bypass",wcag:["2.4.1"],level:"A",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:"Explain whether to add a skip link or a <main> landmark based on the page structure.",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 n=e.getAttribute("href");if(n&&n.length>1){const r=n.slice(1);if(t.getElementById(r))return[]}}return t.querySelector("h1, h2, h3, [role='heading']")?[]:[{ruleId:"bypass",selector:"html",html:"<html>",impact:"serious",message:"Page has no mechanism to bypass repeated content. Add a <main> landmark or skip link."}]}},ea={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:"Suggest appropriate h1 text based on the page's visible content.",run(t){const a=t.querySelector("h1");if(a&&f(a))return[];const e=t.querySelectorAll('[role="heading"][aria-level="1"]');for(const i of e)if(f(i))return[];return[{ruleId:"page-has-heading-one",selector:"html",html:"<html>",impact:"moderate",message:"Page does not contain a level-one heading."}]}};function we(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 ta={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:"Suggest a descriptive title based on the frame's src URL or visible content.",run(t){const a=[];for(const e of t.querySelectorAll("iframe, frame")){if(p(e)||we(e))continue;f(e)||a.push({ruleId:"frame-title",selector:u(e),html:c(e),impact:"serious",message:"Frame is missing an accessible name. Add a title attribute."})}return a}},aa={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 n;const a=[],e=Array.from(t.querySelectorAll("iframe[title], frame[title]")),i=new Map;for(const r of e){if(p(r)||we(r))continue;const o=(n=r.getAttribute("title"))==null?void 0:n.trim().toLowerCase();if(o){const s=i.get(o)||[];s.push(r),i.set(o,s)}}for(const[,r]of i)if(r.length>1)for(const o of r.slice(1))a.push({ruleId:"frame-title-unique",selector:u(o),html:c(o),impact:"moderate",message:"Frame title is not unique. Use a distinct title for each frame."});return a}},ia={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:"Suggest appropriate heading text or explain why to use a different element.",run(t){const a=[],e=t.querySelectorAll('h1, h2, h3, h4, h5, h6, [role="heading"]');for(const i of e)p(i)||f(i)||a.push({ruleId:"empty-heading",selector:u(i),html:c(i),impact:"minor",message:"Heading is empty. Add text content or remove the heading element."});return a}},na={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:"Explain which viewport restrictions to remove and show the corrected meta tag.",run(t){var r;const a=[],e=t.querySelector('meta[name="viewport"]');if(!e)return[];const i=((r=e.getAttribute("content"))==null?void 0:r.toLowerCase())||"";(/user-scalable\s*=\s*no/i.test(i)||/user-scalable\s*=\s*0/i.test(i))&&a.push({ruleId:"meta-viewport",selector:u(e),html:c(e),impact:"critical",message:"Viewport disables user scaling. Remove user-scalable=no."});const n=i.match(/maximum-scale\s*=\s*([\d.]+|yes)/i);if(n){const o=n[1],s=o.toLowerCase()==="yes"?1:parseFloat(o);s<2&&a.push({ruleId:"meta-viewport",selector:u(e),html:c(e),impact:"critical",message:`Viewport maximum-scale=${s} restricts zooming. Set to at least 2 or remove.`})}return a}},ra={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")||"",i=e.match(/^(\d+)/);if(!i)continue;const n=parseInt(i[1],10);if(/^\d+\s*[;,]\s*url\s*=/i.test(e)||/^\d+\s*[;,]\s*['"]?\s*https?:/i.test(e))return n>0&&n<=72e3?[{ruleId:"meta-refresh",selector:u(a),html:c(a),impact:"critical",message:`Page redirects after ${n} seconds without warning. Use server-side redirect.`}]:[];if(n>0&&n<=72e3)return[{ruleId:"meta-refresh",selector:u(a),html:c(a),impact:"critical",message:`Page auto-refreshes after ${n} seconds. Provide user control over refresh.`}]}return[]}},oa={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."},sa=S(oa),la={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."},ca=S(la),ua={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,i;const a=[];for(const n of t.querySelectorAll("p")){if(p(n))continue;const r=n.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),d=((e=n.className)==null?void 0:e.toLowerCase())||"",h=/\bh[1-6]\b|\bheading\b/.test(d),l=((i=n.textContent)==null?void 0:i.trim())||"",m=l.length>0&&l.length<50,g=!l.match(/[.!?,;:]$/);if((o&&s||o&&h)&&m&&g){const y=n.nextElementSibling;y&&(y.tagName==="P"||y.tagName==="DIV"||y.tagName==="UL")&&a.push({ruleId:"p-as-heading",selector:u(n),html:c(n),impact:"serious",message:"Paragraph appears to be styled as a heading. Use an h1-h6 element instead."})}}return a}},da={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=>ge(s))&&r.length>0&&a.push({ruleId:"aria-roles",selector:u(e),html:c(e),impact:"critical",message:`Invalid ARIA role "${r[0]}".`})}return a}},ma={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 U(t).validAttr}},ha={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 U(t).validAttrValue}},pa={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"]},ga={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(p(e)||e instanceof HTMLElement&&e.style.display==="none")continue;const i=e.getAttribute("role").trim().toLowerCase(),n=pa[i];if(n&&!(i==="checkbox"&&e instanceof HTMLInputElement&&e.type==="checkbox")&&!(i==="radio"&&e instanceof HTMLInputElement&&e.type==="radio")&&!(i==="option"&&e instanceof HTMLOptionElement)&&!(i==="heading"&&/^h[1-6]$/i.test(e.tagName))){if(i==="separator"){const r=e.getAttribute("tabindex");if(!r||r==="-1")continue}if(!(e.tagName.toLowerCase()==="hr"&&!e.hasAttribute("role"))){for(const r of n)if(!e.hasAttribute(r)){a.push({ruleId:"aria-required-attr",selector:u(e),html:c(e),impact:"critical",message:`Role "${i}" requires attribute "${r}".`});break}}}}return a}};function ba(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 i=t.closest("form");if(i){const d=i.getAttribute("aria-label")||((o=(r=i.querySelector("legend"))==null?void 0:r.textContent)==null?void 0:o.trim());d&&a.push(`Form: ${d.slice(0,60)}`)}const n=t.parentElement;if(n){const d=n.closest("h1, h2, h3, h4, h5, h6")||n.querySelector("h1, h2, h3, h4, h5, h6");(s=d==null?void 0:d.textContent)!=null&&s.trim()&&a.push(`Nearby heading: ${d.textContent.trim().slice(0,60)}`)}return a.length>0?a.join(`
|
|
3
|
+
`):void 0}const fa={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(p(e))continue;const i=e.getAttribute("role");if((i==="none"||i==="presentation")&&!(e.matches('button:not([disabled]), [tabindex]:not([tabindex="-1"])')||e.tagName.toLowerCase()==="button"&&!e.disabled)||e.getRootNode()instanceof ShadowRoot)continue;f(e)||a.push({ruleId:"button-name",selector:u(e),html:c(e),impact:"critical",message:"Button has no discernible text.",context:ba(e)})}return a}},va={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"])},ya=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"]),wa={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:"State which attribute is not allowed on this role and recommend removing it or using a different element/role that supports the attribute.",run(t){const a=[];for(const e of t.querySelectorAll("[role], [aria-*]")){if(p(e))continue;const i=L(e);if(!i)continue;const n=va[i];if(n)for(const r of e.attributes)r.name.startsWith("aria-")&&(ya.has(r.name)||n.has(r.name)||a.push({ruleId:"aria-allowed-attr",selector:u(e),html:c(e),impact:"critical",message:`ARIA attribute "${r.name}" is not allowed on role "${i}".`}))}return a}},Aa=new Set(["base","col","colgroup","head","html","keygen","meta","param","script","source","style","template","title","track"]),E={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 Sa(t){var e;const a=t.tagName.toLowerCase();if(Aa.has(a))return"none";if(a==="a"&&t.hasAttribute("href"))return E["a[href]"];if(a==="img"&&t.getAttribute("alt")==="")return E["img[alt='']"];if(a==="input"){const n=`input[type=${((e=t.getAttribute("type"))==null?void 0:e.toLowerCase())||"text"}]`;return n in E?E[n]:"none"}return E[a]||"any"}const xa={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 i of t.querySelectorAll("[role]")){if(p(i))continue;const n=(e=i.getAttribute("role"))==null?void 0:e.trim().toLowerCase();if(!n)continue;const r=z(i);if(r&&n===r)continue;const o=Sa(i);o==="none"?a.push({ruleId:"aria-allowed-role",selector:u(i),html:c(i),impact:"minor",message:`Element <${i.tagName.toLowerCase()}> should not have an explicit role.`}):o!=="any"&&!o.has(n)&&a.push({ruleId:"aria-allowed-role",selector:u(i),html:c(i),impact:"minor",message:`Role "${n}" is not allowed on element <${i.tagName.toLowerCase()}>.`})}return a}},oe={combobox:[["listbox","tree","grid","dialog","textbox"]],feed:[["article"]],grid:[["row","rowgroup"]],list:[["listitem","group"]],listbox:[["option","group"]],menu:[["menuitem","menuitemcheckbox","menuitemradio","group"]],menubar:[["menuitem","menuitemcheckbox","menuitemradio","group"]],radiogroup:[["radio"]],row:[["cell","columnheader","gridcell","rowheader"]],rowgroup:[["row"]],table:[["row","rowgroup"]],tablist:[["tab"]],tree:[["treeitem","group"]],treegrid:[["row","rowgroup"]]},se={caption:["figure","table","grid","treegrid"],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"],tab:["tablist"],treeitem:["tree","group"]};function ka(t,a){var r;const e=((r=t.getAttribute("aria-owns"))==null?void 0:r.split(/\s+/))||[],i=t.ownerDocument,n=new Set;for(const o of t.querySelectorAll("*")){const s=L(o);s&&!p(o)&&n.add(s)}for(const o of e){const s=i.getElementById(o);if(s){const d=L(s);d&&!p(s)&&n.add(d)}}for(const o of a)if(!o.some(d=>n.has(d)))return!1;return!0}const Ia={id:"aria-required-children",wcag:["4.1.2"],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 i of t.querySelectorAll("[role]")){if(p(i))continue;const n=(e=i.getAttribute("role"))==null?void 0:e.trim().toLowerCase();if(!n||!(n in oe))continue;const r=oe[n];if(!ka(i,r)){const o=r.map(s=>s.join(" or ")).join(", ");a.push({ruleId:"aria-required-children",selector:u(i),html:c(i),impact:"critical",message:`Role "${n}" requires children with role: ${o}.`})}}return a}},Ea={id:"aria-required-parent",wcag:["4.1.2"],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 i of t.querySelectorAll("[role]")){if(p(i))continue;const n=(e=i.getAttribute("role"))==null?void 0:e.trim().toLowerCase();if(!n||!(n in se))continue;const r=se[n];let o=i.parentElement,s=!1;for(;o&&o!==t.documentElement;){const d=L(o);if(d&&r.includes(d)){s=!0;break}o=o.parentElement}s||a.push({ruleId:"aria-required-parent",selector:u(i),html:c(i),impact:"critical",message:`Role "${n}" must be contained within: ${r.join(", ")}.`})}return a}},le=["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 Ta(t){let a=t;const e=t.ownerDocument,i=e.defaultView;for(;a&&a!==e.body;){if(a.style.display==="none"||a.style.visibility==="hidden")return!1;if(i){const n=i.getComputedStyle(a);if(n.display==="none"||n.visibility==="hidden")return!1}a=a.parentElement}return!0}const Ca={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},La=S(Ca),qa={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:"Suggest adding tabindex='-1' to this focusable element, or moving it outside the aria-hidden region, or removing aria-hidden from the ancestor.",run(t){const a=[];for(const e of t.querySelectorAll('[aria-hidden="true"]')){if(e===t.body)continue;const i=[...e.querySelectorAll(le)];e.matches(le)&&i.push(e);for(const n of i)if(n instanceof HTMLElement){if(n.getAttribute("tabindex")==="-1"||n.disabled||n instanceof HTMLInputElement&&n.type==="hidden"||!Ta(n))continue;a.push({ruleId:"aria-hidden-focus",selector:u(n),html:c(n),impact:"serious",message:"Focusable element is inside an aria-hidden region."})}}return a}},Ra={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 i of t.querySelectorAll('[role="button"], [role="link"], [role="menuitem"]')){if(p(i)||i.tagName.toLowerCase()==="button"||i.tagName.toLowerCase()==="a")continue;if(!f(i)){const r=i.querySelector("img[alt]");if((e=r==null?void 0:r.getAttribute("alt"))!=null&&e.trim())continue;a.push({ruleId:"aria-command-name",selector:u(i),html:c(i),impact:"serious",message:"ARIA command has no accessible name."})}}return a}},Na={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 i of t.querySelectorAll(e)){if(p(i)||i.matches("input, select, textarea"))continue;f(i)||a.push({ruleId:"aria-input-field-name",selector:u(i),html:c(i),impact:"serious",message:"ARIA input field has no accessible name."})}return a}},Ma={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 i of t.querySelectorAll(e)){if(p(i)||i.matches('input[type="checkbox"], input[type="radio"]'))continue;f(i)||a.push({ruleId:"aria-toggle-field-name",selector:u(i),html:c(i),impact:"serious",message:"ARIA toggle field has no accessible name."})}return a}},Ha={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(p(e))continue;f(e)||a.push({ruleId:"aria-meter-name",selector:u(e),html:c(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(p(e))continue;f(e)||a.push({ruleId:"aria-progressbar-name",selector:u(e),html:c(e),impact:"serious",message:"Progressbar has no accessible name."})}return a}},Da={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(p(e))continue;f(e)||a.push({ruleId:"aria-dialog-name",selector:u(e),html:c(e),impact:"serious",message:"Dialog has no accessible name."})}return a}},Oa={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(p(e))continue;f(e)||a.push({ruleId:"aria-tooltip-name",selector:u(e),html:c(e),impact:"serious",message:"Tooltip has no accessible name."})}return a}},Ba={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(p(e))continue;f(e)||a.push({ruleId:"aria-treeitem-name",selector:u(e),html:c(e),impact:"serious",message:"Treeitem has no accessible name."})}return a}},Wa={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 U(t).prohibitedAttr}},Fa=["a[href]","button:not([disabled])",'input:not([disabled]):not([type="hidden"])',"select:not([disabled])","textarea:not([disabled])",'[tabindex]:not([tabindex="-1"])'].join(", "),_a=["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 ce(t){const a=[];t.matches(Fa)&&a.push("element is focusable");for(const e of _a)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 ja={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(p(e))continue;const i=ce(e);i.length>0&&a.push({ruleId:"presentation-role-conflict",selector:u(e),html:c(e),impact:"serious",message:`Presentation role conflicts with: ${i.join(", ")}. The role will be ignored.`})}for(const e of t.querySelectorAll('img[alt=""]')){if(p(e)||e.hasAttribute("role"))continue;const i=ce(e);i.length>0&&a.push({ruleId:"presentation-role-conflict",selector:u(e),html:c(e),impact:"serious",message:`Element with implicit presentation role (alt="") conflicts with: ${i.join(", ")}. The decorative role will be ignored.`})}return a}},Pa={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(p(e))continue;f(e)||a.push({ruleId:"summary-name",selector:u(e),html:c(e),impact:"serious",message:"<summary> element has no accessible name. Add descriptive text."})}return a}};function Va(t){var n,r;const a=[],e=t.getAttribute("href");e&&a.push(`href: ${e}`);const i=t.parentElement;if(i){const o=i.closest("h1, h2, h3, h4, h5, h6");if((n=o==null?void 0:o.textContent)!=null&&n.trim())a.push(`Nearby heading: ${o.textContent.trim().slice(0,80)}`);else{const s=(r=i.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 za={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(p(e))continue;f(e)||a.push({ruleId:"link-name",selector:u(e),html:c(e),impact:"serious",message:"Link has no discernible text.",context:Va(e)})}return a}},Ua={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 i of e){const n=i.getAttribute("href");if(!n||n==="#")continue;const r=w(i).toLowerCase();if(!(r.includes("skip")||r.includes("jump")||r.includes("main content")||r.includes("navigation")))continue;const s=n.slice(1);t.getElementById(s)||a.push({ruleId:"skip-link",selector:u(i),html:c(i),impact:"moderate",message:`Skip link points to "#${s}" which does not exist on the page.`})}return a}},Ga=new Set(["block","flex","grid","table","table-cell","list-item","flow-root"]),Xa=new Set(["inline","inline-block","inline-flex","inline-grid"]);function Ya(t){let a=t.parentElement;for(;a;){const e=A(a).display;if(Ga.has(e))return Ka(a)?a:null;a=a.parentElement}return null}function Ka(t){const a=t.ownerDocument.createTreeWalker(t,NodeFilter.SHOW_TEXT);let e;for(;e=a.nextNode();){if(!e.data.trim())continue;let i=e.parentElement,n=!1;for(;i&&i!==t;){if(i.tagName==="A"){n=!0;break}i=i.parentElement}if(!n)return!0}return!1}function Qa(t,a){const e=t.ownerDocument.createTreeWalker(t,NodeFilter.SHOW_TEXT);let i;for(;i=e.nextNode();){if(!i.data.trim())continue;let n=i.parentElement,r=!1,o=n;for(;o&&o!==t;){if(o.tagName==="A"){r=!0;break}o=o.parentElement}if(!r&&n)return R(A(n).color)}return null}function Ja(t,a){const e=t.textDecorationLine||t.textDecoration||"",i=a.textDecorationLine||a.textDecoration||"";if((e.includes("underline")||e.includes("line-through"))&&e!==i)return!0;const n=parseFloat(t.borderBottomWidth)||0,r=t.borderBottomStyle||"";if(n>0&&r!=="none"&&r!=="hidden")return!0;const o=parseFloat(t.outlineWidth)||0,s=t.outlineStyle||"";if(o>0&&s!=="none")return!0;const d=t.backgroundImage||"";if(d&&d!=="none"&&d!=="initial")return!0;const h=ue(t.fontWeight),l=ue(a.fontWeight);if(Math.abs(h-l)>=300||t.fontStyle!==a.fontStyle)return!0;const m=parseFloat(t.fontSize)||16,g=parseFloat(a.fontSize)||16;return g>0&&m/g>=1.2}function ue(t){return t==="bold"?700:t==="normal"?400:parseInt(t)||400}function de(t,a,e){return"#"+[t,a,e].map(i=>i.toString(16).padStart(2,"0")).join("")}const Za={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(p(e)||!w(e).trim()||e.closest('nav, [role="navigation"], [role="banner"], [role="contentinfo"]'))continue;const i=A(e),n=i.display||"inline";if(!Xa.has(n))continue;const r=Ya(e);if(!r)continue;const o=A(r);if(Ja(i,o))continue;const s=R(i.color),d=Qa(r);if(!s||!d)continue;const h=q(...s),l=q(...d),m=ye(h,l);if(m>=3)continue;const g=de(...s),b=de(...d),v=`link color: ${g} rgb(${s.join(", ")}), surrounding text: ${b} rgb(${d.join(", ")}), ratio: ${m.toFixed(2)}:1`;a.push({ruleId:"link-in-text-block",selector:u(e),html:c(e),impact:"serious",message:"Link in text block is not visually distinguishable from surrounding text. Add an underline, border, or ensure 3:1 color contrast with surrounding text.",context:v})}return a}},ei={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:"Determine the page's primary language and suggest the appropriate lang value.",run(t){var e;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(n=>n.tagName.toLowerCase()==="svg"||n.tagName.toLowerCase()==="math"))return[]}return(e=a.getAttribute("lang"))!=null&&e.trim()?[]:[{ruleId:"html-has-lang",selector:u(a),html:c(a),impact:"serious",message:"<html> element missing lang attribute."}]}},ti=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(" ")),ai=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(" ")),ii=/^[a-z]{2,8}(-[a-z0-9]{1,8})*$/i;function Ae(t){if(!ii.test(t))return!1;const a=t.split("-")[0].toLowerCase();return a.length===2?ti.has(a):a.length===3?!ai.has(a):!1}const ni={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&&!Ae(a)?[{ruleId:"html-lang-valid",selector:"html",html:c(t.documentElement),impact:"serious",message:`Invalid lang attribute value "${a}".`}]:[]}};function me(t){var i;const a=t.ownerDocument.createTreeWalker(t,NodeFilter.SHOW_TEXT);let e;for(;e=a.nextNode();){if(!e.data.trim())continue;const n=e.parentElement;if(!n||n instanceof HTMLElement&&(n.hidden||n.style.display==="none"))continue;let r=n,o=!1;for(;r&&r!==t;){if(r.hasAttribute("lang")){o=!0;break}r=r.parentElement}if(!o)return!0}for(const n of t.querySelectorAll("img[alt]")){if(!((i=n.getAttribute("alt"))==null?void 0:i.trim()))continue;let o=n.parentElement,s=!1;for(;o&&o!==t;){if(o.hasAttribute("lang")){s=!0;break}o=o.parentElement}if(!s)return!0}return!1}const ri={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(p(e)||e===t.documentElement)continue;const i=e.getAttribute("lang"),n=i==null?void 0:i.trim();if(i&&!n){me(e)&&a.push({ruleId:"valid-lang",selector:u(e),html:c(e),impact:"serious",message:"Empty lang attribute value."});continue}n&&me(e)&&(Ae(n)||a.push({ruleId:"valid-lang",selector:u(e),html:c(e),impact:"serious",message:`Invalid lang attribute value "${n}".`}))}return a}},oi={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 n,r;const a=t.documentElement,e=(n=a.getAttribute("lang"))==null?void 0:n.trim().toLowerCase(),i=(r=a.getAttribute("xml:lang"))==null?void 0:r.trim().toLowerCase();if(e&&i){const o=e.split("-")[0],s=i.split("-")[0];if(o!==s)return[{ruleId:"html-xml-lang-mismatch",selector:"html",html:c(a),impact:"moderate",message:`lang="${e}" and xml:lang="${i}" do not match.`}]}return[]}},si={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(p(e))continue;const i=e.closest("table");if(!i)continue;const n=e.getAttribute("id"),r=e.getAttribute("headers").split(/\s+/);for(const o of r){if(o===n){a.push({ruleId:"td-headers-attr",selector:u(e),html:c(e),impact:"serious",message:`Headers attribute references the cell itself ("${o}").`});break}if(!i.querySelector(`th#${CSS.escape(o)}, td#${CSS.escape(o)}`)){a.push({ruleId:"td-headers-attr",selector:u(e),html:c(e),impact:"serious",message:`Headers attribute references non-existent ID "${o}".`});break}}}return a}},li={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(p(e)||e.getAttribute("role")==="presentation"||e.getAttribute("role")==="none")continue;const i=e.querySelectorAll("th"),n=e.querySelectorAll("td");i.length>0&&n.length===0&&a.push({ruleId:"th-has-data-cells",selector:u(e),html:c(e),impact:"serious",message:"Table has header cells but no data cells."})}return a}},ci={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,i;const a=[];for(const n of t.querySelectorAll("table")){if(p(n)||n.getAttribute("role")==="presentation"||n.getAttribute("role")==="none")continue;const r=n.querySelectorAll("tr"),o=r.length;let s=0;for(const m of r){const g=m.querySelectorAll("td, th");let b=0;for(const v of g)b+=parseInt(v.getAttribute("colspan")||"1",10);s=Math.max(s,b)}if(o<=3&&s<=3)continue;const d=n.querySelector("th")!==null,h=n.querySelector("th[scope]")!==null,l=n.querySelector("td[headers]")!==null;if(d)for(const m of n.querySelectorAll("td")){if(p(m)||m.hasAttribute("headers"))continue;const g=m.closest("tr");if(!g)continue;const b=g.querySelector("th")!==null,v=Array.from(g.children).indexOf(m);let y=!1;const I=n.querySelector("thead");if(I){const x=I.querySelector("tr");x&&((e=x.querySelectorAll("th, td")[v])==null?void 0:e.tagName.toLowerCase())==="th"&&(y=!0)}if(!y){const x=n.querySelector("tbody > tr, tr");x&&((i=x.querySelectorAll("th, td")[v])==null?void 0:i.tagName.toLowerCase())==="th"&&(y=!0)}if(!b&&!y&&!h&&!l){a.push({ruleId:"td-has-header",selector:u(m),html:c(m),impact:"serious",message:"Data cell has no associated header. Add th elements with scope, or headers attribute."});break}}}return a}},ui={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 i;const a=[],e=new Set(["row","col","rowgroup","colgroup"]);for(const n of t.querySelectorAll("th[scope]")){if(p(n))continue;const r=(i=n.getAttribute("scope"))==null?void 0:i.toLowerCase();r&&!e.has(r)&&a.push({ruleId:"scope-attr-valid",selector:u(n),html:c(n),impact:"moderate",message:`Invalid scope value "${r}". Use row, col, rowgroup, or colgroup.`})}return a}},di={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(p(e))continue;const i=e.closest("table");(i==null?void 0:i.getAttribute("role"))==="presentation"||(i==null?void 0:i.getAttribute("role"))==="none"||f(e)||a.push({ruleId:"empty-table-header",selector:u(e),html:c(e),impact:"minor",message:"Table header cell is empty. Add text or use aria-label."})}return a}},D=["aria-labelledby","aria-describedby","aria-controls","aria-owns","aria-flowto"],mi={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 n of t.querySelectorAll("[aria-labelledby], [aria-describedby], [aria-controls], [aria-owns], [aria-flowto]"))for(const r of D){const o=n.getAttribute(r);o&&o.split(/\s+/).forEach(s=>e.add(s))}for(const n of t.querySelectorAll("label[for]")){const r=n.getAttribute("for");r&&e.add(r)}const i=new Map;for(const n of t.querySelectorAll("[id]"))e.has(n.id)&&(n instanceof HTMLElement&&(n.style.display==="none"||n.style.visibility==="hidden"||n.hidden)||i.set(n.id,(i.get(n.id)??0)+1));for(const[n,r]of i){if(r<=1)continue;const o=t.querySelectorAll(`#${CSS.escape(n)}`),s=t.querySelector(D.map(l=>`[${l}~="${CSS.escape(n)}"]`).join(", ")),d=t.querySelector(`label[for="${CSS.escape(n)}"]`);let h;if(s){const l=D.find(m=>{var g;return(g=s.getAttribute(m))==null?void 0:g.split(/\s+/).includes(n)});l&&(h=l)}else d&&(h="label[for]");a.push({ruleId:"duplicate-id-aria",selector:u(o[1]),html:c(o[1]),impact:"critical",message:`Duplicate ID "${n}" referenced by ${h??"an accessibility attribute"}.`,context:`First element: ${c(o[0])}${h?`
|
|
5
|
+
Referenced by: ${h}`:""}`})}return a}},hi={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(p(e)||e.hasAttribute("muted")||e.hasAttribute("autoplay"))continue;e.querySelector('track[kind="captions"], track[kind="subtitles"]')||a.push({ruleId:"video-caption",selector:u(e),html:c(e),impact:"critical",message:"Video element has no captions track."})}return a}},pi={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(p(e)||e.querySelector('track[kind="captions"], track[kind="descriptions"]')||e.hasAttribute("aria-describedby"))continue;const n=e.parentElement;n&&n.querySelector('a[href*="transcript"], a[href*="text"]')||a.push({ruleId:"audio-caption",selector:u(e),html:c(e),impact:"critical",message:"Audio element has no transcript or text alternative. Add a transcript or track element."})}return a}},gi=new Set(["SCRIPT","STYLE","NOSCRIPT","TEMPLATE","IFRAME","OBJECT","EMBED","SVG","CANVAS","VIDEO","AUDIO","IMG","BR","HR"]);function he([t,a,e]){return"#"+[t,a,e].map(i=>i.toString(16).padStart(2,"0")).join("")}function bi(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 fi(t,a){if(t.tagName!=="LABEL")return!1;const e=t,i=e.htmlFor;if(i){const o=a.getElementById(i);if(o&&(o.disabled||o.getAttribute("aria-disabled")==="true"))return!0}const n=e.querySelector("input, select, textarea, button");if(n&&(n.disabled||n.getAttribute("aria-disabled")==="true"))return!0;const r=e.id;return!!(r&&a.querySelector(`[aria-labelledby~="${r}"][aria-disabled="true"]`))}function vi(t){const a=t.clip;if(a&&a.startsWith("rect(")){const i=a.match(/[\d.]+/g);if(!i||i.every(n=>parseFloat(n)===0))return!0}const e=t.clipPath;if(e==="inset(50%)"||e==="inset(100%)")return!0;if(t.overflow==="hidden"&&t.position==="absolute"){const i=parseFloat(t.width),n=parseFloat(t.height);if(i<=1&&n<=1)return!0}return!1}function yi(t){if(p(t))return!0;let a=t;for(;a;){const e=A(a);if(e.display==="none"||e.visibility==="hidden"||vi(e))return!0;a=a.parentElement}return!1}const wi={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 i=t.createTreeWalker(e,NodeFilter.SHOW_TEXT),n=new Set;let r;for(;r=i.nextNode();){if(!r.textContent||!r.textContent.trim())continue;const o=r.parentElement;if(!o||n.has(o)||(n.add(o),gi.has(o.tagName))||bi(o)||fi(o,t)||yi(o))continue;const s=A(o);if(parseFloat(s.opacity)===0)continue;const d=s.textShadow;if(d&&d!=="none"&&d!=="initial")continue;const h=R(s.color);if(!h)continue;const l=s.color.match(/rgba\(.+?,\s*([\d.]+)\s*\)/)||s.color.match(/rgba?\(.+?\/\s*([\d.]+%?)\s*\)/);if(l&&(l[1].endsWith("%")?parseFloat(l[1])/100:parseFloat(l[1]))===0||Fe(o))continue;const m=Oe(o);if(!m)continue;const g=q(h[0],h[1],h[2]),b=q(m[0],m[1],m[2]),v=ye(g,b),y=Pe(o)?3:4.5;if(v<y){const I=Math.round(v*100)/100,x=he(h),M=he(m);a.push({ruleId:"color-contrast",selector:u(o),html:c(o),impact:"serious",message:`Insufficient color contrast ratio of ${I}:1 (required ${y}:1).`,context:`foreground: ${x} rgb(${h.join(", ")}), background: ${M} rgb(${m.join(", ")}), ratio: ${I}:1, required: ${y}:1`})}}return a}},G=[Jt,Zt,ea,ta,aa,na,ra,sa,ca,Ve,Ue,Ge,Xe,Ke,Qe,Ze,et,rt,ut,dt,mt,ht,wt,At,St,kt,Et,Mt,Ht,$t,Dt,ia,ua,Ot,Bt,Wt,Ft,_t,jt,Pt,Vt,zt,Ut,Xt,Yt,Qt,da,ma,ha,ga,wa,xa,Ia,Ea,La,qa,Ra,Na,Ma,Ha,$a,Da,Oa,Ba,Wa,ja,fa,Pa,za,Ua,Za,ei,ni,ri,oi,si,li,ci,ui,di,mi,hi,pi,wi];let X=[],Se=new Set;function Ai(t){t.additionalRules&&(X=t.additionalRules),t.disabledRules&&(Se=new Set(t.disabledRules))}function Y(){return G.filter(a=>!Se.has(a.id)).concat(X)}function Si(t){K();const a=Y(),e=[];let i=0;return{processChunk(n){const r=performance.now();for(;i<a.length;){try{e.push(...a[i].run(t))}catch{}if(i++,performance.now()-r>=n)break}return i<a.length},getViolations(){return e}}}function K(){be(),pe(),xe(),ve(),fe(),Te()}function xi(t){var i;K();const a=Y(),e=[];for(const n of a)try{e.push(...n.run(t))}catch{}return{url:((i=t.location)==null?void 0:i.href)??"",timestamp:Date.now(),violations:e,ruleCount:a.length}}const ki=new Map(G.map(t=>[t.id,t]));function Ii(t){const a=ki.get(t);return a||X.find(e=>e.id===t)}exports.clearAllCaches=K;exports.clearAriaAttrAuditCache=fe;exports.clearAriaHiddenCache=be;exports.clearColorCaches=ve;exports.clearComputedRoleCache=pe;exports.compileDeclarativeRule=S;exports.configureRules=Ai;exports.createChunkedAudit=Si;exports.getAccessibleName=f;exports.getAccessibleTextContent=w;exports.getActiveRules=Y;exports.getComputedRole=L;exports.getHtmlSnippet=c;exports.getImplicitRole=z;exports.getRuleById=Ii;exports.getSelector=u;exports.isAriaHidden=p;exports.isValidRole=ge;exports.querySelectorShadowAware=Re;exports.rules=G;exports.runAudit=xi;exports.validateDeclarativeRule=tt;
|