@wcag-checkr/ci 1.0.0-rc.31 → 1.0.0-rc.310

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.
Files changed (104) hide show
  1. package/dist/assets/ErrorBoundary-C-kswn4E.js +594 -0
  2. package/dist/assets/ai-usage-log-BX3L6bKl.js +1 -0
  3. package/dist/assets/content-script.ts-FuMy_sE5.js +217 -0
  4. package/dist/assets/{content-script.ts-loader-Dfu1UEfD.js → content-script.ts-loader-CBHeu186.js} +1 -1
  5. package/dist/assets/copy-ai-fixer-prompt-DQYkHOv3.js +19 -0
  6. package/dist/assets/{crash-reporter-Dc5lvxvY.js → crash-reporter-Bu2p8K-p.js} +1 -1
  7. package/dist/assets/design-system-audit-DpxJrxnb.js +1 -0
  8. package/dist/assets/devtools-panel-DFQvqKKj.js +1 -0
  9. package/dist/assets/diff-DA41zYPc.js +1 -0
  10. package/dist/assets/dom-criterion-analyzers-DoUaJV5C.js +8 -0
  11. package/dist/assets/fraunces-latin-400-normal-6IfK1voy.woff2 +0 -0
  12. package/dist/assets/fraunces-latin-400-normal-NUPT2cO8.woff +0 -0
  13. package/dist/assets/fraunces-latin-500-normal-BTR4KCeb.woff +0 -0
  14. package/dist/assets/fraunces-latin-500-normal-DnGCNyPD.woff2 +0 -0
  15. package/dist/assets/fraunces-latin-600-normal-BFCDtZfi.woff2 +0 -0
  16. package/dist/assets/fraunces-latin-600-normal-DL5QCzvS.woff +0 -0
  17. package/dist/assets/fraunces-latin-ext-400-normal-D8gbi3Gu.woff2 +0 -0
  18. package/dist/assets/fraunces-latin-ext-400-normal-UihxqfOe.woff +0 -0
  19. package/dist/assets/fraunces-latin-ext-500-normal-BMcFk1Xs.woff +0 -0
  20. package/dist/assets/fraunces-latin-ext-500-normal-Z5DV8IzT.woff2 +0 -0
  21. package/dist/assets/fraunces-latin-ext-600-normal-B0Dy4lqi.woff +0 -0
  22. package/dist/assets/fraunces-latin-ext-600-normal-BtzmzP0X.woff2 +0 -0
  23. package/dist/assets/fraunces-vietnamese-400-normal-B65MOf9T.woff +0 -0
  24. package/dist/assets/fraunces-vietnamese-400-normal-CvGt0Ybw.woff2 +0 -0
  25. package/dist/assets/fraunces-vietnamese-500-normal-B-KbxExq.woff +0 -0
  26. package/dist/assets/fraunces-vietnamese-500-normal-GOH_-EGq.woff2 +0 -0
  27. package/dist/assets/fraunces-vietnamese-600-normal-BjlAJixd.woff2 +0 -0
  28. package/dist/assets/fraunces-vietnamese-600-normal-DlAl5EAR.woff +0 -0
  29. package/dist/assets/geist-sans-latin-400-normal-BOaIZNA2.woff +0 -0
  30. package/dist/assets/geist-sans-latin-400-normal-gapTbOY8.woff2 +0 -0
  31. package/dist/assets/geist-sans-latin-500-normal-CN2lyvyL.woff +0 -0
  32. package/dist/assets/geist-sans-latin-500-normal-uokXdC-Q.woff2 +0 -0
  33. package/dist/assets/geist-sans-latin-600-normal-CA1yjETN.woff +0 -0
  34. package/dist/assets/geist-sans-latin-600-normal-DFOURf8L.woff2 +0 -0
  35. package/dist/assets/geist-sans-latin-700-normal-BmN9tIp5.woff2 +0 -0
  36. package/dist/assets/geist-sans-latin-700-normal-CjScfYeH.woff +0 -0
  37. package/dist/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  38. package/dist/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  39. package/dist/assets/jetbrains-mono-cyrillic-500-normal-DJqRU3vO.woff +0 -0
  40. package/dist/assets/jetbrains-mono-cyrillic-500-normal-DmUKJPL_.woff2 +0 -0
  41. package/dist/assets/jetbrains-mono-cyrillic-600-normal-8K4wrrwR.woff +0 -0
  42. package/dist/assets/jetbrains-mono-cyrillic-600-normal-EVf6-Yzo.woff2 +0 -0
  43. package/dist/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  44. package/dist/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  45. package/dist/assets/jetbrains-mono-greek-500-normal-D7SFKleX.woff +0 -0
  46. package/dist/assets/jetbrains-mono-greek-500-normal-JpySY46c.woff2 +0 -0
  47. package/dist/assets/jetbrains-mono-greek-600-normal-H7WoG9Et.woff2 +0 -0
  48. package/dist/assets/jetbrains-mono-greek-600-normal-mc2nkWzM.woff +0 -0
  49. package/dist/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  50. package/dist/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  51. package/dist/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
  52. package/dist/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
  53. package/dist/assets/jetbrains-mono-latin-600-normal-BfsvjouI.woff +0 -0
  54. package/dist/assets/jetbrains-mono-latin-600-normal-C8RAYTDA.woff2 +0 -0
  55. package/dist/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  56. package/dist/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  57. package/dist/assets/jetbrains-mono-latin-ext-500-normal-Cut-4mMH.woff2 +0 -0
  58. package/dist/assets/jetbrains-mono-latin-ext-500-normal-ckzbgY84.woff +0 -0
  59. package/dist/assets/jetbrains-mono-latin-ext-600-normal-BfB_LPfz.woff2 +0 -0
  60. package/dist/assets/jetbrains-mono-latin-ext-600-normal-DObL3zCW.woff +0 -0
  61. package/dist/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  62. package/dist/assets/jetbrains-mono-vietnamese-500-normal-DNRqzVM1.woff +0 -0
  63. package/dist/assets/jetbrains-mono-vietnamese-600-normal-OWROknRo.woff +0 -0
  64. package/dist/assets/options-BPhjrbGI.js +6 -0
  65. package/dist/assets/parallel-tab-flow-Xk9RSjay.js +1 -0
  66. package/dist/assets/scheduled-audit-runner-DyKpb3zg.js +2167 -0
  67. package/dist/assets/service-worker.ts-CMkltOzu.js +2 -0
  68. package/dist/assets/side-panel-Ctm2yXeo.css +1 -0
  69. package/dist/assets/side-panel-f_X4NOJt.js +4 -0
  70. package/dist/assets/site-report-renderer-DNgytqhZ.js +189 -0
  71. package/dist/assets/{styles-C4Kq0zOO.js → styles-Cn731SYD.js} +13 -13
  72. package/dist/assets/styles-d5msFsnl.css +1 -0
  73. package/dist/assets/zip-encoder-CtULHXx_.js +1 -0
  74. package/dist/axe.min.js +12 -0
  75. package/dist/devtools/panel.html +9 -8
  76. package/dist/manifest.json +11 -8
  77. package/dist/options/options.html +5 -6
  78. package/dist/service-worker-loader.js +1 -1
  79. package/dist/side-panel/App.tsx +129 -5
  80. package/dist/side-panel/audit-launcher.ts +21 -1
  81. package/dist/side-panel/azure-devops-issue.test.ts +68 -0
  82. package/dist/side-panel/azure-devops-issue.ts +89 -0
  83. package/dist/side-panel/gitlab-issue.test.ts +53 -0
  84. package/dist/side-panel/gitlab-issue.ts +78 -0
  85. package/dist/side-panel/main.tsx +39 -2
  86. package/dist/side-panel/side-panel.html +11 -8
  87. package/dist/side-panel/store.ts +149 -13
  88. package/dist/side-panel/styles.css +39 -0
  89. package/dist/side-panel/wire-messaging.ts +146 -9
  90. package/package.json +1 -1
  91. package/wcagcheckr-ci.mjs +193 -32
  92. package/dist/assets/ErrorBoundary-BLcMSVSr.js +0 -524
  93. package/dist/assets/ai-usage-log-Dj9Ub_DT.js +0 -1
  94. package/dist/assets/content-script.ts-CwcUMq3e.js +0 -181
  95. package/dist/assets/devtools-panel-DQ3Bbomf.js +0 -1
  96. package/dist/assets/diff-D4sCAdXf.js +0 -1
  97. package/dist/assets/forensic-log-B1UCXZ23.js +0 -129
  98. package/dist/assets/options-BG2i5vFf.js +0 -6
  99. package/dist/assets/preload-helper-D7HrI6pR.js +0 -1
  100. package/dist/assets/service-worker.ts-CO86CV_p.js +0 -715
  101. package/dist/assets/side-panel-XSB07vDa.js +0 -1
  102. package/dist/assets/site-report-renderer-CyHkM6hB.js +0 -147
  103. package/dist/assets/state-PELIq3oj.js +0 -1
  104. package/dist/assets/styles-Cevp58mS.css +0 -1
@@ -0,0 +1,2167 @@
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/parallel-tab-flow-Xk9RSjay.js","assets/crash-reporter-Bu2p8K-p.js","assets/diff-DA41zYPc.js","assets/ai-usage-log-BX3L6bKl.js"])))=>i.map(i=>d[i]);
2
+ var Qi=Object.defineProperty;var Zi=(e,t,a)=>t in e?Qi(e,t,{enumerable:!0,configurable:!0,writable:!0,value:a}):e[t]=a;var Rt=(e,t,a)=>Zi(e,typeof t!="symbol"?t+"":t,a);import{m as Cn,d as er,T as tr,a as $n,c as tn,i as Tn,_ as an}from"./diff-DA41zYPc.js";import{c as J,o as fe,e as H,r as ye,d as yt}from"./crash-reporter-Bu2p8K-p.js";import{x as xo,y as En,T as ar,w as be,i as Na,z as nr,C as or,E as ir,F as rr,j as So,G as sr,H as cr,e as lr,g as dr,m as ur,k as hr}from"./ai-usage-log-BX3L6bKl.js";const La=(e,t)=>t.some(a=>e instanceof a);let Rn,On;function pr(){return Rn||(Rn=[IDBDatabase,IDBObjectStore,IDBIndex,IDBCursor,IDBTransaction])}function fr(){return On||(On=[IDBCursor.prototype.advance,IDBCursor.prototype.continue,IDBCursor.prototype.continuePrimaryKey])}const Pa=new WeakMap,ba=new WeakMap,Qt=new WeakMap;function gr(e){const t=new Promise((a,n)=>{const o=()=>{e.removeEventListener("success",i),e.removeEventListener("error",r)},i=()=>{a(Ge(e.result)),o()},r=()=>{n(e.error),o()};e.addEventListener("success",i),e.addEventListener("error",r)});return Qt.set(t,e),t}function mr(e){if(Pa.has(e))return;const t=new Promise((a,n)=>{const o=()=>{e.removeEventListener("complete",i),e.removeEventListener("error",r),e.removeEventListener("abort",r)},i=()=>{a(),o()},r=()=>{n(e.error||new DOMException("AbortError","AbortError")),o()};e.addEventListener("complete",i),e.addEventListener("error",r),e.addEventListener("abort",r)});Pa.set(e,t)}let Ua={get(e,t,a){if(e instanceof IDBTransaction){if(t==="done")return Pa.get(e);if(t==="store")return a.objectStoreNames[1]?void 0:a.objectStore(a.objectStoreNames[0])}return Ge(e[t])},set(e,t,a){return e[t]=a,!0},has(e,t){return e instanceof IDBTransaction&&(t==="done"||t==="store")?!0:t in e}};function Co(e){Ua=e(Ua)}function br(e){return fr().includes(e)?function(...t){return e.apply(_a(this),t),Ge(this.request)}:function(...t){return Ge(e.apply(_a(this),t))}}function yr(e){return typeof e=="function"?br(e):(e instanceof IDBTransaction&&mr(e),La(e,pr())?new Proxy(e,Ua):e)}function Ge(e){if(e instanceof IDBRequest)return gr(e);if(ba.has(e))return ba.get(e);const t=yr(e);return t!==e&&(ba.set(e,t),Qt.set(t,e)),t}const _a=e=>Qt.get(e);function $o(e,t,{blocked:a,upgrade:n,blocking:o,terminated:i}={}){const r=indexedDB.open(e,t),c=Ge(r);return n&&r.addEventListener("upgradeneeded",l=>{n(Ge(r.result),l.oldVersion,l.newVersion,Ge(r.transaction),l)}),a&&r.addEventListener("blocked",l=>a(l.oldVersion,l.newVersion,l)),c.then(l=>{i&&l.addEventListener("close",()=>i()),o&&l.addEventListener("versionchange",d=>o(d.oldVersion,d.newVersion,d))}).catch(()=>{}),c}const wr=["get","getKey","getAll","getAllKeys","count"],vr=["put","add","delete","clear"],ya=new Map;function Mn(e,t){if(!(e instanceof IDBDatabase&&!(t in e)&&typeof t=="string"))return;if(ya.get(t))return ya.get(t);const a=t.replace(/FromIndex$/,""),n=t!==a,o=vr.includes(a);if(!(a in(n?IDBIndex:IDBObjectStore).prototype)||!(o||wr.includes(a)))return;const i=async function(r,...c){const l=this.transaction(r,o?"readwrite":"readonly");let d=l.store;return n&&(d=d.index(c.shift())),(await Promise.all([d[a](...c),o&&l.done]))[0]};return ya.set(t,i),i}Co(e=>({...e,get:(t,a,n)=>Mn(t,a)||e.get(t,a,n),has:(t,a)=>!!Mn(t,a)||e.has(t,a)}));const Ar=["continue","continuePrimaryKey","advance"],Dn={},Fa=new WeakMap,To=new WeakMap,kr={get(e,t){if(!Ar.includes(t))return e[t];let a=Dn[t];return a||(a=Dn[t]=function(...n){Fa.set(this,To.get(this)[t](...n))}),a}};async function*Ir(...e){let t=this;if(t instanceof IDBCursor||(t=await t.openCursor(...e)),!t)return;t=t;const a=new Proxy(t,kr);for(To.set(a,t),Qt.set(a,_a(t));t;)yield a,t=await(Fa.get(a)||t.continue()),Fa.delete(a)}function Nn(e,t){return t===Symbol.asyncIterator&&La(e,[IDBIndex,IDBObjectStore,IDBCursor])||t==="iterate"&&La(e,[IDBIndex,IDBObjectStore])}Co(e=>({...e,get(t,a,n){return Nn(t,a)?Ir:e.get(t,a,n)},has(t,a){return Nn(t,a)||e.has(t,a)}}));const xr="wcag-component-auditor",Sr=3;let Ye=null;function oe(){return Ye||(Ye=$o(xr,Sr,{upgrade(e,t){if(t<1){const a=e.createObjectStore("baselines",{keyPath:"componentId"});a.createIndex("byUrl","snapshotMeta.url"),a.createIndex("byDate","lastUpdated");const n=e.createObjectStore("audit-history",{keyPath:"auditId",autoIncrement:!0});n.createIndex("byComponentId","componentId"),n.createIndex("byDate","runDate")}if(t<2&&e.createObjectStore("site-crawl-per-url",{keyPath:"slot"}),t<3){e.createObjectStore("scheduled-audits",{keyPath:"id"});const a=e.createObjectStore("scheduled-audit-results",{keyPath:"resultId",autoIncrement:!0});a.createIndex("byScheduleId","scheduleId"),a.createIndex("byRanAt","ranAt")}}})),Ye}async function Cr(){Ye&&(await Ye).close(),Ye=null}async function Zt(e){return(await oe()).get("baselines",e)}async function nn(e){await(await oe()).put("baselines",e)}async function Eo(e){await(await oe()).delete("baselines",e)}async function Ro(){return(await oe()).getAll("baselines")}async function $r(e){return(await oe()).getAllFromIndex("baselines","byUrl",e)}async function Tr(e){return(await oe()).add("audit-history",e)}async function Er(e){return(await oe()).getAllFromIndex("audit-history","byComponentId",e)}async function Oo(){return(await oe()).get("site-crawl-per-url","current")}async function Mo(e){await(await oe()).put("site-crawl-per-url",{...e,slot:"current"})}async function on(){await(await oe()).delete("site-crawl-per-url","current")}async function Do(){return(await oe()).getAll("scheduled-audits")}async function No(e){return(await oe()).get("scheduled-audits",e)}async function rn(e){await(await oe()).put("scheduled-audits",e)}async function Rr(e){await(await oe()).delete("scheduled-audits",e)}async function Lo(e){return(await oe()).add("scheduled-audit-results",e)}async function Or(e){return(await oe()).getAllFromIndex("scheduled-audit-results","byScheduleId",e)}async function Mr(){return(await oe()).getAll("scheduled-audit-results")}async function Po(e,t=50){const a=await oe(),n=await a.getAllFromIndex("scheduled-audit-results","byScheduleId",e);if(n.length<=t)return;n.sort((r,c)=>c.ranAt.localeCompare(r.ranAt));const o=n.slice(t),i=a.transaction("scheduled-audit-results","readwrite");for(const r of o)r.resultId!==void 0&&await i.store.delete(r.resultId);await i.done}async function Dr(e){const a=(await oe()).transaction("scheduled-audit-results","readwrite");let o=await a.store.index("byScheduleId").openCursor(IDBKeyRange.only(e));for(;o;)await o.delete(),o=await o.continue();await a.done}const Nr=Object.freeze(Object.defineProperty({__proto__:null,_resetDbForTesting:Cr,appendAuditHistory:Tr,appendScheduledAuditResult:Lo,deleteBaseline:Eo,deleteScheduledAudit:Rr,deleteScheduledAuditResultsForScheduleId:Dr,deleteSiteCrawlPerUrl:on,getBaseline:Zt,getDb:oe,getScheduledAudit:No,getSiteCrawlPerUrl:Oo,listAllScheduledAuditResults:Mr,listAuditHistoryForComponent:Er,listBaselines:Ro,listBaselinesByUrl:$r,listScheduledAuditResultsByScheduleId:Or,listScheduledAudits:Do,pruneScheduledAuditResults:Po,putScheduledAudit:rn,setBaseline:nn,setSiteCrawlPerUrl:Mo},Symbol.toStringTag,{value:"Module"})),qt="siteCrawlReport";async function Uo(){var a;if(typeof chrome>"u"||!((a=chrome.storage)!=null&&a.local))return null;const t=(await chrome.storage.local.get([qt]))[qt];return t&&typeof t=="object"?t:null}async function Lr(e){var t;if(!(typeof chrome>"u"||!((t=chrome.storage)!=null&&t.local))){if(e===null){await chrome.storage.local.remove([qt]),await on().catch(()=>{});return}await chrome.storage.local.set({[qt]:e})}}async function Wa(){try{const e=await Oo();return(e==null?void 0:e.perUrl)??null}catch{return null}}async function _o(e,t){if(e===null){await on();return}await Mo({startUrl:(t==null?void 0:t.startUrl)??"",finishedAt:(t==null?void 0:t.finishedAt)??new Date().toISOString(),perUrl:e})}const Eh=Object.freeze(Object.defineProperty({__proto__:null,loadSiteCrawlPerUrlResults:Wa,loadSiteCrawlReport:Uo,saveSiteCrawlPerUrlResults:_o,saveSiteCrawlReport:Lr},Symbol.toStringTag,{value:"Module"})),Gt="license:cache",Pr=60*60*1e3,Ur=24*60*60*1e3,_r=7*24*60*60*1e3;async function ea(){return(await chrome.storage.local.get(Gt))[Gt]??null}async function vt(e){await chrome.storage.local.set({[Gt]:e})}async function Ot(){await chrome.storage.local.remove(Gt)}function Fr(e,t=Date.now()){const a=t-e.validatedAt;return a<Pr?"fresh":a<Ur?"stale":a<_r?"grace":"expired"}const Te=J("license-gate"),Wr="https://api.wcagcheckr.com",Fo="wcagcheckr",qr=`${Wr}/v1/products/${Fo}/license/validate`,Gr=1e4,Vr=`https://${Fo}.com/upgrade`,wa="trialStartedAt",qa="licenseToken";async function Wo(){let t=(await chrome.storage.local.get(wa))[wa];return t||(t=Date.now(),await chrome.storage.local.set({[wa]:t})),t}function jr(e,t=Date.now()){return t-e>xo*864e5}async function et(){return(await chrome.storage.local.get(qa))[qa]??null}async function zr(e){await chrome.storage.local.set({[qa]:e})}async function va(e){const t=await fetch(qr,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({token:e}),signal:AbortSignal.timeout(Gr)});if(!t.ok)throw new Error(`validate http ${t.status}`);return{type:"LICENSE_VALIDATE_RESPONSE",...await t.json()}}async function wt(e){const t=await ea();if(t&&t.token===e){const n=Fr(t);if(n==="fresh")return t.response;if(n==="stale")return va(e).then(o=>vt({token:e,validatedAt:Date.now(),response:o})).catch(o=>Te.warn("background refresh failed",o)),t.response;if(n==="grace")try{const o=await va(e);return await vt({token:e,validatedAt:Date.now(),response:o}),o}catch(o){return Te.warn("grace-period revalidation failed; using last-known-good",o),t.response}}const a=await va(e);return await vt({token:e,validatedAt:Date.now(),response:a}),a}function Hr(e){return e==="solo"||e==="solo-yearly"||e==="solo-single-month"?"solo":e==="team"||e==="team-15"||e==="team-yearly"||e==="team-15-yearly"?"team":"free"}async function Me(){const e=await et();if(e)try{const a=await wt(e);if(a.valid&&a.plan)return Hr(a.plan)}catch(a){Te.warn("validation failed, falling back",a)}const t=await Wo();return jr(t)?"free":"trial"}async function Br(){const e=await ea(),t=e==null?void 0:e.response.trialEndsAt;if(t){const o=new Date(t).getTime()-Date.now();return Math.max(0,Math.ceil(o/864e5))}const a=await Wo(),n=xo*864e5-(Date.now()-a);return Math.max(0,Math.ceil(n/864e5))}async function Kr(){const e=await ea(),t=e==null?void 0:e.response.features;if(!t||typeof t!="object")return{};const a=t.seatsUsed,n=t.seatsTotal;return{seatsUsed:typeof a=="number"?a:void 0,seatsTotal:typeof n=="number"?n:void 0}}function Yr(e,t){const a=ar[e];switch(t){case"audit:multi-component":return a.maxComponents!==1;case"audit:state-matrix":return a.stateMatrix!=="default-only";case"storybook:auto-iterate":return a.storybookAutoIterate;case"export:json":return a.exportJson;case"export:sarif":return a.exportSarif;case"export:junit":return a.exportJunit;case"cloud-sync":return a.cloudSync}}function Rh(){const e=[];e.push(fe("TIER_GET",async o=>{o.forceRefresh&&await Ot();const i=await Me(),r={type:"TIER_GET_RESPONSE",tier:i};if(i==="trial")r.trialDaysRemaining=await Br();else if(i==="team"){const{seatsUsed:c,seatsTotal:l}=await Kr();c!==void 0&&(r.seatsUsed=c),l!==void 0&&(r.seatsTotal=l)}if(i!=="trial"){const c=await ea();if(c!=null&&c.response.valid&&c.response.plan){if(r.planCode=c.response.plan,c.response.plan==="solo-single-month"&&c.response.expiresAt){const l=new Date(c.response.expiresAt).getTime()-Date.now();r.licenseDaysRemaining=Math.max(0,Math.ceil(l/864e5))}c.response.pastDueSince&&(r.pastDue=!0)}}return r})),e.push(fe("LICENSE_CHECK_REQUEST",async o=>{const i=await Me(),r=Yr(i,o.feature);return{type:"LICENSE_CHECK_RESPONSE",allowed:r,tier:i,reason:r?void 0:`${o.feature} requires a higher tier`,upgradeUrl:r?void 0:Vr}})),e.push(fe("LICENSE_VALIDATE_REQUEST",async o=>{try{if(o.forceRefresh){await Ot();const i=await wt(o.token);return H({type:"LICENSE_CHANGED_EVENT",tier:await Me()}),i}return await wt(o.token)}catch(i){return Te.error("validate failed",i),{type:"LICENSE_VALIDATE_RESPONSE",valid:!1,plan:null,email:null,status:null,expiresAt:null}}})),e.push(fe("LICENSE_SET_REQUEST",async o=>{await zr(o.token),await Ot();let i=!1,r={ok:!1,reason:"not-attempted"};try{const l=await wt(o.token);if(await vt({token:o.token,validatedAt:Date.now(),response:l}),i=l.valid===!0,i){const d=await En(o.token);d.ok?r={ok:!0,seatsUsed:d.seatsUsed,seatsTotal:d.seatsTotal,overCapacity:d.overCapacity}:r=d}}catch(l){Te.warn("validation after set failed",l)}const c=await Me();return H({type:"LICENSE_CHANGED_EVENT",tier:c}),{type:"LICENSE_SET_RESPONSE",validated:i,seatClaim:r}})),(async()=>{try{const o=await et();o&&await En(o)}catch(o){Te.debug("boot-time seat claim failed",o)}})();const t="license:periodic-refresh",a=60;chrome.alarms.get(t,o=>{o||chrome.alarms.create(t,{periodInMinutes:a})});const n=async o=>{if(o.name===t)try{const i=await et();if(!i)return;await Ot();const r=await wt(i);await vt({token:i,validatedAt:Date.now(),response:r}),H({type:"LICENSE_CHANGED_EVENT",tier:await Me()}),Te.debug("periodic license refresh complete")}catch(i){Te.warn("periodic license refresh failed",i)}};return chrome.alarms.onAlarm.addListener(n),e.push(()=>chrome.alarms.onAlarm.removeListener(n)),Te.info("handlers registered"),()=>e.forEach(o=>o())}const Jr=J("headless-build-lockdown"),qo="team";async function je(){try{return await Me()!==qo}catch(e){return Jr.warn("tier resolution failed during lockout check — denying",e),!0}}function Oh(e){return e!==qo}const ta="HEADLESS_BUILD_LOCKOUT",tt="wcagcheckr is in private build phase. A team license is required for access. If you believe you should have access, contact cliff@locustware.com.";async function Go(){const e=await chrome.tabs.query({currentWindow:!0}),t=e.find(n=>n.active);if(t!=null&&t.id&&Ln(t.url))return t.id;const a=e.find(n=>n.id&&Ln(n.url));return(a==null?void 0:a.id)??null}function Ln(e){return typeof e=="string"&&/^https?:\/\//.test(e)}const Ga="acknowledgedFindings";async function aa(){var a;if(typeof chrome>"u"||!((a=chrome.storage)!=null&&a.local))return[];const t=(await chrome.storage.local.get([Ga]))[Ga];return Array.isArray(t)?t:[]}async function na(e){const t=await aa();return new Set(t.filter(a=>a.componentId===e).map(a=>a.matchKey))}async function Xr(e){const a=(await aa()).filter(n=>!(n.componentId===e.componentId&&n.matchKey===e.matchKey));a.push({...e,acknowledgedAt:new Date().toISOString()}),await Vo(a)}async function Qr(e,t){const n=(await aa()).filter(o=>!(o.componentId===e&&o.matchKey===t));await Vo(n)}async function Vo(e){var t;typeof chrome>"u"||!((t=chrome.storage)!=null&&t.local)||await chrome.storage.local.set({[Ga]:e})}const Zr=Object.freeze(Object.defineProperty({__proto__:null,acknowledgeFinding:Xr,loadAcknowledged:aa,loadAcknowledgedKeysForComponent:na,unacknowledgeFinding:Qr},Symbol.toStringTag,{value:"Module"})),es=/:nth-(?:last-)?(?:child|of-type)\([^)]+\)/g,ts=/\[id="[^"]*"\]/g,as=/\[data-testid="[^"]*"\]/g,ns=/#[\w-]*\d[\w-]*/g,os=/\[href(?:[\^$*~|]?=("|')[^"']*\1)?\]/g,is=/\[aria-label(?:[\^$*~|]?=("|')[^"']*\1)?\]/g,rs=new Set(["region","landmark-one-main","page-has-heading-one","landmark-no-duplicate-banner","landmark-no-duplicate-contentinfo","landmark-no-duplicate-main","landmark-main-is-top-level","landmark-banner-is-top-level","landmark-contentinfo-is-top-level","landmark-complementary-is-top-level","landmark-unique","bypass","document-title","html-has-lang","meta-viewport"]);function ss(e){return e&&e.replace(es,"").replace(ts,"").replace(as,"").replace(ns,"").replace(os,"").replace(is,"").replace(/\s+/g," ").trim()}function xe(e,t){return rs.has(e)?`${e}::__structural__`:`${e}::${ss(t)}`}const Pn={minor:0,moderate:1,serious:2,critical:3};function jo(e){const t=new Map;for(const a of e){const n=xe(a.ruleId,a.target.selector),o=t.get(n);if(!o){t.set(n,a);continue}(Pn[a.impact]??0)>(Pn[o.impact]??0)&&t.set(n,a)}return Array.from(t.values())}const zo=[{id:"contrast",label:"Color & contrast",axeRules:["color-contrast","color-contrast-enhanced","link-in-text-block"],igtWorkflowIds:["visual-indicators"]},{id:"alt-text",label:"Alt text",axeRules:["image-alt","input-image-alt","object-alt","svg-img-alt","area-alt","role-img-alt"],igtWorkflowIds:[]},{id:"forms",label:"Forms",axeRules:["label","select-name","aria-input-field-name","aria-toggle-field-name","form-field-multiple-labels","label-content-name-mismatch","autocomplete-valid","label-title-only","wcc-onfocus-context-change","wcc-oninput-context-change","wcc-cognitive-auth-challenge"],igtWorkflowIds:["forms"]},{id:"keyboard",label:"Keyboard",axeRules:["nested-interactive","tabindex","frame-focusable-content","no-focusable-content","focus-order-semantics","wcc-gesture-no-alternative","wcc-drag-no-alternative"],igtWorkflowIds:["keyboard","focus-management"]},{id:"tab-order",label:"Tab order",axeRules:[],igtWorkflowIds:[],verifiedByWorkflowIds:["keyboard"],heuristic:"tab-order"},{id:"screen-reader",label:"Screen reader / ARIA",axeRules:["aria-allowed-attr","aria-required-attr","aria-required-children","aria-required-parent","aria-roles","aria-valid-attr-value","aria-valid-attr","aria-hidden-body","aria-hidden-focus","button-name","input-button-name","link-name","frame-title","presentation-role-conflict","aria-allowed-role","aria-prohibited-attr","aria-text","aria-deprecated-role","aria-conditional-attr","aria-braille-equivalent","empty-button","empty-heading","empty-link"],igtWorkflowIds:["screen-reader"]},{id:"reading-order",label:"Reading order",axeRules:[],igtWorkflowIds:[],verifiedByWorkflowIds:["page-structure"],heuristic:"reading-order"},{id:"page-structure",label:"Page structure",axeRules:["heading-order","page-has-heading-one","landmark-one-main","landmark-no-duplicate-banner","landmark-no-duplicate-contentinfo","landmark-no-duplicate-main","region","bypass","empty-table-header","landmark-banner-is-top-level","landmark-complementary-is-top-level","landmark-contentinfo-is-top-level","landmark-main-is-top-level","landmark-unique"],igtWorkflowIds:["page-structure"]}];function Mh(e,t){const a=new Set;for(const n of t)n.wcagCriterion===e&&a.add(n.ruleId);if(a.size===0)return null;for(const n of zo)for(const o of n.axeRules)if(a.has(o))return n.id;return null}function cs(e){const t=zo.map(o=>ds(o,e)),a=ls(t.map(o=>o.letter)),n=t.filter(o=>o.letter==="D"||o.letter==="F");return{overallLetter:a,subGrades:t,warningAreas:n,isLawsuitRisk:n.length>0}}function ls(e){const t=e.filter(r=>r==="A").length,a=e.filter(r=>r==="C").length,n=e.filter(r=>r==="D").length,o=e.filter(r=>r==="F").length,i=e.length-t;return t===0&&n+o>a?"F":i===0?"A":i<=3?"B":i<=6?"C":"D"}function ds(e,t){const a=new Set(e.axeRules),n=t.violations.filter(M=>a.has(M.ruleId)),o=t.manualRuns.filter(M=>e.igtWorkflowIds.includes(M.workflowId)),i=e.igtWorkflowIds.map(M=>t.workflows.find(F=>F.id===M)).filter(M=>M!=null);let r=i.length>0;for(const M of i){const F=o.find(N=>N.workflowId===M.id);if((F?Object.keys(F.steps).length:0)!==M.steps.length){r=!1;break}}const c=o.some(M=>Object.keys(M.steps).length>0),l=e.verifiedByWorkflowIds??[],d=t.manualRuns.filter(M=>l.includes(M.workflowId)),u=l.map(M=>t.workflows.find(F=>F.id===M)).filter(M=>M!=null);let h=l.length>0&&u.length===l.length;for(const M of u){const F=d.find(N=>N.workflowId===M.id);if((F?Object.keys(F.steps).length:0)!==M.steps.length){h=!1;break}}let m=null;if(h)for(const M of d)M.completedAt&&(m===null||M.completedAt>m)&&(m=M.completedAt);const f=e.heuristic==="tab-order"?t.heuristicCounts.tabOrder??0:e.heuristic==="reading-order"?t.heuristicCounts.readingOrder??0:0,g=l[0]??e.igtWorkflowIds[0]??null,p=t.interactiveAuditVerdicts,b=[];p&&(e.id==="tab-order"&&p.focusOrder&&b.push({verdict:p.focusOrder,criterionId:"2.4.3"}),e.id==="keyboard"&&(p.keyboardTrap&&b.push({verdict:p.keyboardTrap,criterionId:"2.1.2"}),p.focusVisible&&b.push({verdict:p.focusVisible,criterionId:"2.4.7"})),e.id==="reading-order"&&p.readingOrder&&b.push({verdict:p.readingOrder,criterionId:"1.3.2"}),e.id==="contrast"&&p.nonTextContrast&&b.push({verdict:p.nonTextContrast,criterionId:"1.4.11"}));const w=b.some(M=>M.verdict==="fail");if(b.length>0&&b.every(M=>M.verdict==="pass")&&l.length>0&&!h)return{id:e.id,label:e.label,letter:"A",untested:!1,needsIgt:!1,igtCompleted:!1,verifiedByIgt:!1,verifiedAt:b.length>0?new Date().toISOString():null,suppressedHeuristicCount:f,primaryVerifierWorkflowId:g,counts:{critical:0,serious:0,moderate:0,minor:0},caps:{cappedAtC:!1,cappedAtB:!1},aiVerifiedCriteria:b.map(M=>M.criterionId)};if(h&&l.length>0)return{id:e.id,label:e.label,letter:"A",untested:!1,needsIgt:!1,igtCompleted:!0,verifiedByIgt:!0,verifiedAt:m,suppressedHeuristicCount:f,primaryVerifierWorkflowId:g,counts:{critical:0,serious:0,moderate:0,minor:0},caps:{cappedAtC:!1,cappedAtB:!1}};const y=e.axeRules.length>0||e.heuristic!=null,s=e.igtWorkflowIds.length>0;if(y?!t.auditRan:!c)return{id:e.id,label:e.label,letter:"F",untested:!0,needsIgt:s||l.length>0,igtCompleted:!1,verifiedByIgt:!1,verifiedAt:null,suppressedHeuristicCount:0,primaryVerifierWorkflowId:g,counts:{critical:0,serious:0,moderate:0,minor:0},caps:{cappedAtC:!1,cappedAtB:!1}};const E={critical:0,serious:0,moderate:0,minor:0},v=jo(n);for(const M of v)E[M.impact]++;const D=(t.aiResolvedFails??[]).filter(M=>a.has(M.ruleId));if(D.length>0){const M=new Set(v.map(F=>`${F.ruleId}::${F.target.selector}`));for(const F of D){const q=`${F.ruleId}::${F.selector}`;M.has(q)||(M.add(q),E[F.impact]++)}}const S=b.length>0&&b.some(M=>M.verdict==="pass"||M.verdict==="fail"),R=S?f:0;S||(E.serious+=f),w&&(E.serious+=b.filter(M=>M.verdict==="fail").length);for(const M of o){const F=i.find(q=>q.id===M.workflowId);if(F)for(const q of F.steps){const N=M.steps[q.id];(N==null?void 0:N.status)==="fail"&&(q.severity==="required"?E.serious++:E.moderate++)}}const P=E.critical>0,I=E.serious>0;let $;E.critical>=5?$="F":E.critical>=1?$="D":E.serious>=1?$="C":E.moderate>=1?$="B":$="A";const C=$!=="A"&&(s&&!r||l.length>0&&!h);return{id:e.id,label:e.label,letter:$,untested:!1,needsIgt:C,igtCompleted:r,verifiedByIgt:!1,verifiedAt:null,suppressedHeuristicCount:R,primaryVerifierWorkflowId:g,counts:E,caps:{cappedAtC:P,cappedAtB:I}}}const Je="incompleteResolutions",Un=2e3,_n=600;function sn(e,t,a){return`${e}::${t}::${a}`}function Ho(e){return e.reasoning&&e.reasoning.length>_n?{...e,reasoning:e.reasoning.slice(0,_n-1)+"…"}:e}function us(e){const t=Object.entries(e);if(t.length<=Un)return e;const a=t.sort(([,n],[,o])=>{const i=Date.parse(n.resolvedAt)||0;return(Date.parse(o.resolvedAt)||0)-i});return Object.fromEntries(a.slice(0,Un))}async function oa(){try{const t=(await chrome.storage.local.get(Je))[Je];return t&&typeof t=="object"?t:{}}catch{return{}}}async function cn(e){const t=us(e);try{await chrome.storage.local.set({[Je]:t})}catch(a){const n=Object.entries(t).sort(([,i],[,r])=>{const c=Date.parse(i.resolvedAt)||0;return(Date.parse(r.resolvedAt)||0)-c}),o=Object.fromEntries(n.slice(0,Math.floor(n.length/2)));try{await chrome.storage.local.set({[Je]:o}),console.warn("[incomplete-resolutions] storage quota hit; pruned to",Object.keys(o).length,"entries",a)}catch(i){await chrome.storage.local.remove(Je).catch(()=>{}),console.warn("[incomplete-resolutions] storage quota still exceeded after prune; wiped key",i)}}}async function Fe(e){const t=await oa();t[sn(e.pageUrl,e.ruleId,e.selector)]=Ho(e),await cn(t)}async function hs(e){if(e.length===0)return;const t=await oa();for(const a of e)t[sn(a.pageUrl,a.ruleId,a.selector)]=Ho(a);await cn(t)}async function Bo(){const e=await oa();return Object.values(e)}async function Ko(e){return(await Bo()).filter(a=>a.pageUrl===e)}async function ps(e,t,a){const n=await oa();delete n[sn(e,t,a)],await cn(n)}async function fs(){await chrome.storage.local.remove(Je)}function gs(e){if(!e.reasoning)return e;const t=e.reasoning.toLowerCase(),a=Mt(t,/verdict\s+corrected\s+to\s+pass|verdict:\s*pass\s+\(corrected\)/gi),n=Mt(t,/verdict\s+corrected\s+to\s+fail|verdict:\s*fail\s+\(corrected\)/gi);if(a>n&&a>=0)return{...e,verdict:"pass"};if(n>a&&n>=0)return{...e,verdict:"fail"};const o=Mt(t,/actually\s+passes|in\s+fact\s+passes|passes\s+aa/gi),i=Mt(t,/actually\s+fails|in\s+fact\s+fails|fails\s+aa/gi);return o<0&&i<0?e:o>i&&o/t.length>=.5?{...e,verdict:"pass"}:i>o&&i/t.length>=.5?{...e,verdict:"fail"}:e}function Mt(e,t){let a=-1;const n=t.flags.includes("g")?t.flags:t.flags+"g",o=new RegExp(t.source,n);let i;for(;(i=o.exec(e))!==null;)a=i.index,i.index===o.lastIndex&&o.lastIndex++;return a}function ms(e){if(e.ruleId!=="color-contrast"&&e.ruleId!=="color-contrast-enhanced"||!e.reasoning||e.verdict!=="fail")return e;const t=bs(e.reasoning);if(t.length<2)return e;const a=t[0],n=t[1];if(n.a<.999)return e;const o=a.a<.999?{r:Math.round(a.r*a.a+n.r*(1-a.a)),g:Math.round(a.g*a.a+n.g*(1-a.a)),b:Math.round(a.b*a.a+n.b*(1-a.a))}:{r:a.r,g:a.g,b:a.b},i=ws(o,{r:n.r,g:n.g,b:n.b}),r=ys(e.reasoning)??4.5;return i<r+.05?e:{...e,verdict:"pass",reasoning:`${e.reasoning}
3
+
4
+ (rc.299 math-salvage: re-computed from cited rgb(${a.r},${a.g},${a.b}) on rgb(${n.r},${n.g},${n.b}) → ${i.toFixed(2)}:1 against threshold ${r}:1 → PASS. Original AI verdict was FAIL but its arithmetic appears wrong on this element. One-way salvage: only flips fail→pass, never pass→fail.)`}}function bs(e){const t=/rgba?\(\s*([0-9.]+)[,\s]+([0-9.]+)[,\s]+([0-9.]+)(?:[,/\s]+([0-9.]+))?\s*\)/gi,a=[];let n;for(;(n=t.exec(e))!==null&&(a.push({r:Math.round(parseFloat(n[1])),g:Math.round(parseFloat(n[2])),b:Math.round(parseFloat(n[3])),a:n[4]!==void 0?parseFloat(n[4]):1}),!(a.length>=4)););return a}function ys(e){const t=/([34](?:\.\d+)?|7(?:\.\d+)?)\s*:\s*1/g,a=[];let n;for(;(n=t.exec(e))!==null;){const o=parseFloat(n[1]);(o===3||o===4.5||o===7)&&a.push(o)}return a.length>0?a[a.length-1]:null}function ws(e,t){const a=l=>{const d=l/255;return d<=.03928?d/12.92:Math.pow((d+.055)/1.055,2.4)},n=l=>.2126*a(l.r)+.7152*a(l.g)+.0722*a(l.b),o=n(e),i=n(t),r=Math.max(o,i),c=Math.min(o,i);return(r+.05)/(c+.05)}const Dh=Object.freeze(Object.defineProperty({__proto__:null,applyContrastMathSalvage:ms,applySelfCorrectionSalvage:gs,clearAllResolutions:fs,deleteResolution:ps,getAllResolutions:Bo,getResolutionsForPage:Ko,saveResolution:Fe,saveResolutions:hs},Symbol.toStringTag,{value:"Module"})),vs={id:"keyboard",name:"Keyboard navigation",blurb:"Verify everyone who relies on the keyboard can reach, operate, and escape every interactive element. WCAG 2.1.1, 2.1.2, 2.4.3, 2.4.7.",steps:[{id:"tab-reaches-all",prompt:"Tab through the component from start to finish. Compare against the Tab-order overlay.",question:"Did Tab reach every interactive element (button, link, form field, custom widget)?",wcag:"2.1.1",visualizer:"tab-order",severity:"required"},{id:"tab-order-logical",prompt:"Watch the focus indicator as you Tab. Compare visual order vs the Tab-order numbered overlay.",question:"Does focus move in a logical order matching the visual layout (left-to-right, top-to-bottom)? Red badges indicate visual mismatches.",wcag:"2.4.3",visualizer:"tab-order",severity:"required",autoDismissActivity:"tab-order"},{id:"focus-visible",prompt:"On each focused element, confirm a visible focus indicator (outline, ring, color change).",question:"Is the focus indicator clearly visible on every interactive element?",wcag:"2.4.7",relatedAxeRule:"focus-order-semantics",severity:"required"},{id:"no-trap",prompt:"Tab forward and Shift+Tab backward through any modal, dropdown, or custom widget.",question:"Can you escape the widget with Tab/Shift+Tab/Esc — no trapping?",wcag:"2.1.2",severity:"required"},{id:"enter-activates",prompt:"Focus a button or link and press Enter (and Space for buttons).",question:"Did Enter/Space activate the control as expected?",wcag:"2.1.1",severity:"required"},{id:"esc-closes",prompt:"Open any modal, dropdown, or popover and press Escape.",question:"Did Escape close the overlay and return focus to the trigger?",wcag:"2.1.2",severity:"required"},{id:"arrow-keys-grouped",prompt:"For grouped widgets (radio, tabs, listbox, menu): focus into the group, then use arrow keys.",question:"Do arrow keys move within the group as expected (and Tab moves out of it)?",wcag:"2.1.1",severity:"advisory"},{id:"character-key-shortcuts",prompt:'Identify any single-character keyboard shortcuts (e.g., "S" to open search, "/" to focus the search box, J/K for next/prev). If none exist, mark N/A.',question:"For every single-character shortcut: can it be turned off, remapped to a modifier-key combo, OR is it only active when the relevant control has focus? (Prevents accidental activation by speech-input users.)",wcag:"2.1.4",severity:"required"}]},As={id:"screen-reader",name:"Screen reader readiness",blurb:"Verify the component is operable and meaningful via a screen reader. Use NVDA (Windows), VoiceOver (macOS/iOS), or TalkBack (Android). WCAG 1.3.1, 4.1.2.",steps:[{id:"sr-announces-on-focus",prompt:"Activate your screen reader and Tab to each interactive element.",question:"Does the SR announce a meaningful name + role + state for every focused element?",wcag:"4.1.2",relatedAxeRule:"aria-required-attr",severity:"required"},{id:"sr-headings-navigate",prompt:"Use the SR's heading-navigation shortcut (NVDA: H, VoiceOver: VO+Cmd+H). Compare against the Outline overlay.",question:"Are all logical sections reachable via heading navigation, in correct hierarchy order?",wcag:"1.3.1",relatedAxeRule:"heading-order",visualizer:"outline",severity:"required"},{id:"sr-reading-order",prompt:"Read the page top-to-bottom using the SR's read-all command (NVDA: NumPad+Down, VoiceOver: VO+A). Compare against the Reading-order numbered overlay.",question:"Does the SR read content in the meaningful order implied by the visual layout (1.3.2 Meaningful Sequence)? Red badges in the Reading-order overlay show DOM-vs-visual divergences.",wcag:"1.3.2",visualizer:"reading-order",severity:"required",autoDismissActivity:"reading-order"},{id:"sr-landmarks",prompt:"Use the SR's landmark-navigation shortcut (NVDA: D, VoiceOver: VO+U → Landmarks). Compare against the Outline overlay.",question:"Can the user jump to main, nav, banner, contentinfo via landmark navigation?",wcag:"1.3.1",relatedAxeRule:"region",visualizer:"outline",severity:"required"},{id:"sr-live-region",prompt:"Trigger any dynamic update (form submission, status change, toast).",question:'Does the SR announce the change automatically (via aria-live or role="status"/role="alert")?',wcag:"4.1.3",severity:"required"},{id:"sr-form-errors",prompt:"Submit a form with an invalid field.",question:"Does the SR announce the error and which field it relates to?",wcag:"3.3.1",severity:"required"},{id:"sr-images-meaningful",prompt:"Navigate over each image (NVDA: G, VoiceOver: VO+Cmd+G).",question:"Does each meaningful image have descriptive alt text? Are decorative images silent?",wcag:"1.1.1",relatedAxeRule:"image-alt",severity:"required"}]},ks={id:"focus-management",name:"Focus management",blurb:"Verify focus moves and restores correctly across overlays, navigations, and dynamic content. WCAG 2.4.3, 2.4.7, 3.2.2.",steps:[{id:"modal-initial-focus",prompt:"Open a modal/dialog.",question:"Did focus move into the modal automatically (typically to the first focusable element or a heading)?",severity:"required"},{id:"modal-restored-on-close",prompt:"Close a modal/dialog (via close button or Esc).",question:"Was focus restored to the element that opened it?",severity:"required"},{id:"modal-trap-while-open",prompt:"While a modal is open, Tab/Shift+Tab through it.",question:"Is focus trapped inside the modal until it closes?",severity:"required"},{id:"route-change-focus",prompt:"Trigger a client-side route change.",question:"Did focus move sensibly (to the new page heading, or main landmark) — not to <body>?",severity:"required"},{id:"expanded-focus",prompt:"Open an expandable section (accordion, dropdown, collapsible).",question:"Is focus moved into or near the newly-revealed content (or kept on the trigger appropriately)?",severity:"advisory"},{id:"no-unexpected-focus-move",prompt:"Type or click in form fields without explicitly tabbing.",question:"Does focus stay where you put it (no auto-jump on input)?",wcag:"3.2.2",severity:"required"},{id:"no-context-change-on-focus",prompt:"Tab into each interactive element on the page.",question:"Does merely RECEIVING focus on an element NOT trigger a context change (auto-submit, open modal, navigate elsewhere, change focus to a different field)? (Context change on focus violates 3.2.1; activating a control via click/Enter is fine.)",wcag:"3.2.1",severity:"required"}]},Is={id:"forms",name:"Forms",blurb:"Verify form fields are properly labeled, errors are clear, and the experience works for assistive technology. WCAG 1.3.1, 3.3.1, 3.3.2, 4.1.2.",steps:[{id:"visible-labels",prompt:"Look at every form field.",question:"Does every field have a visible label (not just placeholder text)?",wcag:"3.3.2",relatedAxeRule:"label",severity:"required"},{id:"label-association",prompt:"Click each visible label.",question:"Did clicking the label focus the associated input?",wcag:"1.3.1",relatedAxeRule:"label",severity:"required"},{id:"required-marked",prompt:"Identify required fields.",question:"Are required fields marked both visually AND programmatically (aria-required or required attribute)?",wcag:"3.3.2",severity:"required"},{id:"autocomplete-set",prompt:"For personal-info fields (name, email, phone, address): inspect the autocomplete attribute.",question:'Is autocomplete set with a valid HTML5 token (e.g., "email", "given-name", "tel")?',wcag:"1.3.5",relatedAxeRule:"autocomplete-valid",severity:"required"},{id:"error-text",prompt:"Submit with invalid input.",question:'Are error messages specific (not just "invalid"), shown near the field, and programmatically associated (aria-describedby)?',wcag:"3.3.1",severity:"required"},{id:"no-color-only-error",prompt:"Look at how errors are signaled.",question:"Is the error indicated by more than just color (also icon, text, or border style)?",wcag:"1.4.1",severity:"required"},{id:"success-announced",prompt:"Submit a form successfully.",question:'Is the success state announced to screen readers (via aria-live or role="status")?',wcag:"4.1.3",severity:"advisory"},{id:"error-suggestion",prompt:"Submit a form with an obvious invalid input (wrong email format, password too short, missing required field).",question:'Does the error message INCLUDE a specific suggestion for fixing it (not just "invalid")?',wcag:"3.3.3",severity:"required"},{id:"redundant-entry",prompt:"Identify any multi-step or multi-form flow where the user re-enters previously provided information (shipping = billing, profile data repeated in another form).",question:"Is previously-entered information auto-populated, selectable from a list, or otherwise NOT manually re-entered?",wcag:"3.3.7",severity:"required"},{id:"accessible-authentication",prompt:"Identify the site's authentication flow (login, password reset, multi-factor).",question:'Does auth NOT require a cognitive function test (e.g., remembering / transcribing characters), OR is an accessible alternative offered (passkey, magic link, password-manager autofill, "show password" toggle)?',wcag:"3.3.8",severity:"required"}]},xs={id:"page-structure",name:"Page structure",blurb:"Verify the page declares its language, has a meaningful title, and exposes a sensible heading + landmark hierarchy. WCAG 2.4.2, 3.1.1, 3.1.2, 1.3.1, 2.4.6.",steps:[{id:"page-title",prompt:"Look at the browser tab — is the page title set and meaningful?",question:'Does the title describe the page topic / purpose (not just "Untitled" or the site name)?',wcag:"2.4.2",relatedAxeRule:"document-title",severity:"required"},{id:"page-lang",prompt:"Inspect the <html> element's lang attribute.",question:'Is lang set to a valid language code (e.g., "en", "es-MX")?',wcag:"3.1.1",relatedAxeRule:"html-has-lang",severity:"required"},{id:"lang-of-parts",prompt:"For any text not in the page's primary language (foreign quote, brand name in another language), inspect the parent element.",question:"Is lang attribute set on those elements to indicate the language change?",wcag:"3.1.2",severity:"advisory"},{id:"h1-present",prompt:"Use the Outline visualizer or SR heading shortcut to find the h1.",question:"Is there exactly one h1, and does it describe the main content of the page?",wcag:"1.3.1",relatedAxeRule:"page-has-heading-one",visualizer:"outline",severity:"required"},{id:"descriptive-headings",prompt:"Read each heading. Imagine someone listening only to the heading text.",question:"Does each heading describe its section meaningfully on its own?",wcag:"2.4.6",severity:"required"},{id:"main-landmark",prompt:"Use the Outline visualizer or SR landmark shortcut.",question:"Is there exactly one <main> landmark wrapping the page's primary content?",wcag:"1.3.1",relatedAxeRule:"landmark-one-main",visualizer:"outline",severity:"required"},{id:"parsing-well-formed",prompt:"Open DevTools → Console. Reload the page and check for HTML parse errors. Or run W3C validator on the URL.",question:"Is the page's markup well-formed (no duplicate IDs in scope, no invalid nesting, no missing required attributes)? WCAG 4.1.1 was obsoleted in 2.2 but still applies to 2.1 conformance claims.",wcag:"4.1.1",severity:"required"}]},Ss={id:"visual-indicators",name:"Visual indicators",blurb:"Verify nothing relies on color/shape/position alone, and non-text contrast meets thresholds for UI components and focus rings. WCAG 1.3.3, 1.4.1, 1.4.11.",steps:[{id:"no-color-only-info",prompt:"Scan instructions, error messages, charts, status indicators.",question:"Is information conveyed by something OTHER than color alone (text, icon, pattern, label)?",wcag:"1.4.1",severity:"required"},{id:"no-sensory-only",prompt:'Look for instructions referencing shape ("the round button"), location ("on the left"), sound ("the chime"), or visual cues alone.',question:"Are those instructions backed up by a programmatic identifier (label, role, accessible name)?",wcag:"1.3.3",severity:"required"},{id:"non-text-contrast",prompt:"For UI components (form-field borders, focus rings, icons-as-buttons), verify contrast vs adjacent colors. Use a contrast checker if needed.",question:"Do non-text UI components meet ≥3:1 contrast against adjacent colors?",wcag:"1.4.11",severity:"required"},{id:"focus-ring-contrast",prompt:"Tab through; for each focused element, verify the focus ring is visible against both the element and the page background.",question:"Does the focus ring meet ≥3:1 contrast on every focusable surface?",wcag:"1.4.11",visualizer:"tab-order",severity:"required"},{id:"state-changes-visible",prompt:"For toggleable controls (checkboxes, switches, expanded states): change them and observe.",question:"Is the state change conveyed visibly AND programmatically (aria-pressed/expanded/checked)?",wcag:"4.1.2",severity:"required"}]},Cs={id:"reflow",name:"Reflow & text spacing",blurb:"Verify the layout survives narrow viewports, browser zoom, and user-overridden text spacing. WCAG 1.4.4, 1.4.10, 1.4.12.",steps:[{id:"resize-320",prompt:"Resize the browser to 320px wide (or set DevTools device toolbar to 320×256).",question:"Does content reflow without two-dimensional scrolling? (Vertical scroll OK; horizontal scroll within content blocks NOT OK.)",wcag:"1.4.10",relatedAxeRule:"wcagcheckr-reflow",severity:"required"},{id:"zoom-200",prompt:"In a desktop viewport, zoom the browser to 200%.",question:"Is all content + functionality available, with no text clipping or unusable controls?",wcag:"1.4.4",severity:"required"},{id:"text-spacing-override",prompt:"Apply text-spacing override (line-height: 1.5, paragraph spacing 2x font-size, letter-spacing 0.12em, word-spacing 0.16em) via a bookmarklet or DevTools.",question:"Does content stay readable with no overlap or clipping?",wcag:"1.4.12",severity:"required"},{id:"no-fixed-pixel-text",prompt:"Inspect text styles for fixed px-based font sizes, line-heights.",question:"Are text dimensions set in relative units (rem/em/%) so they scale with user settings?",severity:"advisory"},{id:"orientation-not-locked",prompt:"Rotate a mobile device (or use DevTools device toolbar to rotate) between portrait and landscape.",question:"Does the layout adapt to both orientations? (Content not locked to one orientation unless essential.)",wcag:"1.3.4",severity:"required"},{id:"images-of-text",prompt:"Look for marketing banners, hero images, or any image that contains substantial text content as part of the raster.",question:"Is real HTML text used in preference to images-of-text, except for logos / decorative purposes? (Images-of-text fail to scale with zoom, can't be selected, and don't respect user text settings.)",wcag:"1.4.5",severity:"required"}]},$s={id:"hover-focus-content",name:"Content on hover or focus",blurb:"Tooltips, dropdown previews, and any content revealed on hover or focus must be dismissable, hoverable, and persistent. WCAG 1.4.13.",steps:[{id:"dismissable",prompt:"Hover or focus to reveal a tooltip / popover. Press Escape.",question:"Did Escape dismiss the revealed content without moving pointer or focus?",wcag:"1.4.13",severity:"required"},{id:"hoverable",prompt:"Hover to reveal a tooltip. Move the pointer over the tooltip itself (without losing the trigger's hover).",question:"Does the revealed content stay visible when the pointer enters it?",wcag:"1.4.13",severity:"required"},{id:"persistent",prompt:"Reveal a tooltip and don't do anything else.",question:"Does the content remain visible until the user dismisses it, moves focus/pointer away, or the trigger info is no longer valid?",wcag:"1.4.13",severity:"required"}]},Ts={id:"pointer",name:"Pointer interactions",blurb:"Verify gestures, target sizes, and pointer cancellation respect users with motor limitations. WCAG 2.5.1, 2.5.2, 2.5.5/2.5.8.",steps:[{id:"no-multi-point-gesture",prompt:"Identify any feature that requires a multi-point gesture (pinch, two-finger swipe) or path-based gesture (drag along a line).",question:"Is there a single-tap / single-click alternative for that feature?",wcag:"2.5.1",severity:"required"},{id:"pointer-cancellation",prompt:"Mouse-down on a button, drag the pointer OFF the button, then release.",question:"Was the action canceled (the button did NOT activate)? Activation should fire on up-event over the target, not down-event.",wcag:"2.5.2",severity:"required"},{id:"target-size-aa",prompt:"Toggle the Targets visualizer. Look for any red overlays (≥24×24 fail).",question:"Are all interactive targets ≥24×24 CSS px (or have sufficient spacing per WCAG 2.5.8)?",wcag:"2.5.8",relatedAxeRule:"target-size",visualizer:"targets",severity:"required"},{id:"label-in-name",prompt:"For every visually-labeled control (button, link), inspect its accessible name (use the SR or DevTools).",question:"Does the accessible name START with — or fully contain — the visible label text?",wcag:"2.5.3",relatedAxeRule:"label-content-name-mismatch",severity:"required"},{id:"motion-actuation-alt",prompt:"Identify any feature that triggers on device motion (shake to undo, tilt to scroll, step counter).",question:"Is there a non-motion alternative (button / control) for every motion-triggered function, AND can motion be disabled?",wcag:"2.5.4",severity:"required"},{id:"dragging-alternative",prompt:"Identify any feature that requires a dragging movement (slider thumb, drag-to-rearrange, swipe-to-delete).",question:"Is there a single-tap / single-click alternative for every dragging action (e.g., +/- buttons next to a slider, up/down buttons next to draggable list items)?",wcag:"2.5.7",severity:"required"}]},Es={id:"time-based",name:"Time-based behaviors",blurb:"Verify time limits, auto-updating content, and motion can be controlled by users. WCAG 2.2.1, 2.2.2, 2.3.1.",steps:[{id:"time-limits-adjustable",prompt:"Identify any time-limited interaction (session timeout, form auto-submit, OTP expiration).",question:"Can the user turn off, adjust, or extend the time limit before it expires (warning + control before the deadline)?",wcag:"2.2.1",severity:"required"},{id:"pause-moving-content",prompt:"Look for content that moves, blinks, scrolls, or auto-updates for >5 seconds (carousels, animated banners, news tickers, auto-refreshing feeds).",question:"Is there a control to pause / stop / hide the moving content?",wcag:"2.2.2",severity:"required"},{id:"no-flash",prompt:"Identify any flashing or blinking content (>3 flashes per second).",question:"Is flashing absent, OR limited below the general/red flash thresholds?",wcag:"2.3.1",severity:"required"},{id:"reduced-motion-honored",prompt:'Enable system "reduce motion" preference. Reload the page.',question:"Does the site honor prefers-reduced-motion (parallax / animations / spinners minimized or disabled)?",wcag:"2.3.3",severity:"advisory"}]},Rs={id:"error-prevention",name:"Error prevention",blurb:"Verify destructive, financial, or legal actions are reversible, checked, or confirmed. WCAG 3.3.4, 3.3.6.",steps:[{id:"destructive-confirm",prompt:"Identify destructive actions (delete account, cancel subscription, destroy data, send irreversible message).",question:'Is each destructive action reversible (undo), or checked (validation), or confirmed (explicit "Are you sure?" dialog)?',wcag:"3.3.4",severity:"required"},{id:"financial-confirm",prompt:"Identify financial / legal commitments (purchase, contract sign, transfer).",question:"Before submission, does the user see a review screen and an explicit confirmation step?",wcag:"3.3.4",severity:"required"},{id:"undo-available",prompt:'Test "destructive" actions on user content (delete a note, archive an item).',question:'Is there an undo / restore mechanism, even briefly (toast with "undo" button)?',wcag:"3.3.6",severity:"advisory"}]},Os={id:"consistency",name:"Consistency",blurb:"Verify navigation, identification, and user paths are consistent across pages. WCAG 2.4.5, 3.2.3, 3.2.4.",steps:[{id:"consistent-nav",prompt:"Compare the primary navigation across 3+ pages.",question:"Does the same navigation appear in the same order on every page?",wcag:"3.2.3",severity:"required"},{id:"consistent-id",prompt:'Compare the same UI element appearing on multiple pages (e.g., a search icon, a "Help" link).',question:"Does the same component have the same accessible name everywhere it appears?",wcag:"3.2.4",severity:"required"},{id:"multiple-ways",prompt:"Beyond the primary navigation, is there a second way to find a page (search, sitemap, related links, breadcrumbs)?",question:"Are there at least 2 ways to locate any page (single-process pages exempt)?",wcag:"2.4.5",severity:"required"},{id:"skip-link",prompt:"Tab once when the page loads.",question:'Does the first focusable element offer a "Skip to main content" link (or equivalent bypass)?',wcag:"2.4.1",relatedAxeRule:"bypass",severity:"required"},{id:"consistent-help",prompt:"Identify pages that surface a help mechanism (contact info, help link, FAQ link, chatbot trigger). Compare position + order across pages.",question:"When the same help mechanism appears on multiple pages, does it appear in the same relative order (e.g., always last in the header)?",wcag:"3.2.6",severity:"required"}]},Ms={id:"time-based-media",name:"Time-based media",blurb:'Verify audio + video have the alternatives required by WCAG. If the site has no time-based media, mark every step "Not applicable." WCAG 1.2.1, 1.2.2, 1.2.3, 1.2.4, 1.2.5, 1.4.2.',steps:[{id:"prerecorded-audio-only-alt",prompt:"Identify any prerecorded audio-only content (podcast embed, audio quote, voice message).",question:"Does each prerecorded audio-only file have a text transcript readable on the page (or linked from it)?",wcag:"1.2.1",severity:"required"},{id:"prerecorded-video-only-alt",prompt:"Identify any prerecorded silent video (animated GIF with content, silent screencast, animated explainer).",question:"Does each prerecorded video-only file have an equivalent text or audio description?",wcag:"1.2.1",severity:"required"},{id:"prerecorded-captions",prompt:"Identify any prerecorded video that includes audio (interviews, tutorials, marketing videos).",question:"Does each video provide synchronized captions for ALL spoken content + meaningful non-speech sound?",wcag:"1.2.2",severity:"required"},{id:"prerecorded-alt-for-video",prompt:"For each prerecorded video with audio, check if there's a full text transcript on the page (covering both audio dialog AND visual action) OR if audio description is provided.",question:"Does each prerecorded video have either a full text alternative OR an audio-description track?",wcag:"1.2.3",severity:"required"},{id:"prerecorded-audio-description",prompt:"Watch each prerecorded video without looking at it. Listen only.",question:"Is all visually-conveyed information also conveyed in the audio (either built into the narration OR via a separate audio-description track)?",wcag:"1.2.5",severity:"required"},{id:"live-captions",prompt:"Identify any LIVE audio/video stream (live shopping, webinar, conference broadcast).",question:"Does each live broadcast provide real-time captions?",wcag:"1.2.4",severity:"required"},{id:"audio-control",prompt:"Reload the page. Listen for audio that starts automatically and plays for >3 seconds (background music, autoplay video with sound).",question:"Is there a mechanism to pause / stop the audio, OR can the volume be controlled INDEPENDENTLY from the system volume?",wcag:"1.4.2",severity:"required"}]},Ee=[vs,As,ks,Is,xs,Ss,Cs,$s,Ts,Es,Ms,Rs,Os];function Nh(e,t){let a=0,n=0,o=0,i=0,r=0;for(const c of t.steps){const l=e.steps[c.id];if(!l){r++;continue}l.status==="pass"?a++:l.status==="fail"?n++:l.status==="not-applicable"?i++:l.status==="skip"?o++:r++}return{passed:a,failed:n,skipped:o,notApplicable:i,unanswered:r,total:t.steps.length,fullyAnswered:r===0}}const Vt="interactiveAuditResults";function ln(e,t){return`${e}::${t}`}async function xt(){try{const t=(await chrome.storage.local.get(Vt))[Vt];return t&&typeof t=="object"?t:{}}catch{return{}}}async function dn(e){await chrome.storage.local.set({[Vt]:e})}async function te(e){const t=await xt(),a={componentId:e.componentId,criterionId:e.criterionId,verdict:e.verdict,reasoning:e.reasoning,confidence:e.confidence,pageUrl:e.pageUrl,finishedAt:e.finishedAt,costUsd:e.costUsd,model:e.model,inputHash:e.inputHash};t[ln(e.componentId,e.criterionId)]=a,await dn(t)}async function Ne(e,t){return(await xt())[ln(e,t)]??null}async function Ds(){const e=await xt();return Object.values(e)}async function Va(e){return(await Ds()).filter(a=>a.componentId===e)}async function Lh(e,t){const a=await xt();delete a[ln(e,t)],await dn(a)}async function un(e){const t=new Set;for(const n of e)n&&t.add(n);if(t.size===0)return[];const a=[];for(const n of t)try{const o=await Va(n);for(const i of o)a.push({criterionId:i.criterionId,ruleId:`ai-interactive::${i.criterionId}`,pageUrl:i.pageUrl,verdict:i.verdict,reasoning:i.reasoning})}catch{}return a}async function Ph(){try{const e=await xt();let t=!1;for(const a of Object.keys(e)){const n=e[a];if(n.steps&&n.steps.length>0){const o={componentId:n.componentId,criterionId:n.criterionId,verdict:n.verdict,reasoning:n.reasoning,confidence:n.confidence,pageUrl:n.pageUrl,finishedAt:n.finishedAt,costUsd:n.costUsd,model:n.model};e[a]=o,t=!0}}t&&await dn(e)}catch{try{await chrome.storage.local.remove(Vt)}catch{}}}const Ns=J("walkthrough-acks"),ja="walkthroughAcks";function hn(e,t,a){return`${e}::${t}::${a}`}async function ia(){try{const t=(await chrome.storage.local.get(ja))[ja];return t&&typeof t=="object"?t:{}}catch{return{}}}async function Yo(e){await chrome.storage.local.set({[ja]:e})}async function Uh(e){if(!e.note||!e.note.trim())throw new Error("Acknowledgement requires a non-empty note explaining manual verification.");const t=await ia();t[hn(e.componentId,e.criterionId,e.pageUrl)]={...e,note:e.note.trim()},await Yo(t),Ns.info(`acknowledged ${e.criterionId} on ${e.pageUrl}`)}async function _h(e,t,a){const n=await ia();delete n[hn(e,t,a)],await Yo(n)}async function Ls(e,t,a){return(await ia())[hn(e,t,a)]??null}async function Ps(){const e=await ia();return Object.values(e)}async function pn(e){const t=new Set;for(const n of e)n&&t.add(n);return t.size===0?[]:(await Ps()).filter(n=>t.has(n.componentId))}const Us=[{id:"1.1.1",title:"Non-text Content",level:"A",addedIn:"2.0",understandingSlug:"non-text-content",shortDescription:"All non-text content has a text alternative."},{id:"1.2.1",title:"Audio-only and Video-only (Prerecorded)",level:"A",addedIn:"2.0",understandingSlug:"audio-only-and-video-only-prerecorded",shortDescription:"Alternatives for prerecorded audio-only or video-only."},{id:"1.2.2",title:"Captions (Prerecorded)",level:"A",addedIn:"2.0",understandingSlug:"captions-prerecorded",shortDescription:"Captions for prerecorded audio in synchronized media."},{id:"1.2.3",title:"Audio Description or Media Alternative (Prerecorded)",level:"A",addedIn:"2.0",understandingSlug:"audio-description-or-media-alternative-prerecorded",shortDescription:"Alternative for time-based media (prerecorded)."},{id:"1.2.4",title:"Captions (Live)",level:"AA",addedIn:"2.0",understandingSlug:"captions-live",shortDescription:"Captions for live synchronized media."},{id:"1.2.5",title:"Audio Description (Prerecorded)",level:"AA",addedIn:"2.0",understandingSlug:"audio-description-prerecorded",shortDescription:"Audio description for prerecorded video in synchronized media."},{id:"1.2.6",title:"Sign Language (Prerecorded)",level:"AAA",addedIn:"2.0",understandingSlug:"sign-language-prerecorded",shortDescription:"Sign-language interpretation for prerecorded audio."},{id:"1.2.7",title:"Extended Audio Description (Prerecorded)",level:"AAA",addedIn:"2.0",understandingSlug:"extended-audio-description-prerecorded",shortDescription:"Extended audio description for prerecorded video."},{id:"1.2.8",title:"Media Alternative (Prerecorded)",level:"AAA",addedIn:"2.0",understandingSlug:"media-alternative-prerecorded",shortDescription:"Alternative for time-based media (prerecorded)."},{id:"1.2.9",title:"Audio-only (Live)",level:"AAA",addedIn:"2.0",understandingSlug:"audio-only-live",shortDescription:"Alternative for live audio-only content."},{id:"1.3.1",title:"Info and Relationships",level:"A",addedIn:"2.0",understandingSlug:"info-and-relationships",shortDescription:"Information and structural relationships are programmatically determinable."},{id:"1.3.2",title:"Meaningful Sequence",level:"A",addedIn:"2.0",understandingSlug:"meaningful-sequence",shortDescription:"Reading sequence is programmatically determinable."},{id:"1.3.3",title:"Sensory Characteristics",level:"A",addedIn:"2.0",understandingSlug:"sensory-characteristics",shortDescription:"Instructions don't rely solely on shape, color, size, or sound."},{id:"1.3.4",title:"Orientation",level:"AA",addedIn:"2.1",understandingSlug:"orientation",shortDescription:"Content doesn't restrict view to portrait or landscape."},{id:"1.3.5",title:"Identify Input Purpose",level:"AA",addedIn:"2.1",understandingSlug:"identify-input-purpose",shortDescription:"Purpose of input fields is programmatically identifiable."},{id:"1.3.6",title:"Identify Purpose",level:"AAA",addedIn:"2.1",understandingSlug:"identify-purpose",shortDescription:"Purpose of UI components, icons, and regions is identifiable."},{id:"1.4.1",title:"Use of Color",level:"A",addedIn:"2.0",understandingSlug:"use-of-color",shortDescription:"Color is not the only means of conveying information."},{id:"1.4.2",title:"Audio Control",level:"A",addedIn:"2.0",understandingSlug:"audio-control",shortDescription:"Mechanism to pause/stop auto-playing audio over 3s."},{id:"1.4.3",title:"Contrast (Minimum)",level:"AA",addedIn:"2.0",understandingSlug:"contrast-minimum",shortDescription:"Text has 4.5:1 contrast (3:1 for large text)."},{id:"1.4.4",title:"Resize Text",level:"AA",addedIn:"2.0",understandingSlug:"resize-text",shortDescription:"Text resizable to 200% without loss of content/function."},{id:"1.4.5",title:"Images of Text",level:"AA",addedIn:"2.0",understandingSlug:"images-of-text",shortDescription:"Text used in preference to images of text where possible."},{id:"1.4.6",title:"Contrast (Enhanced)",level:"AAA",addedIn:"2.0",understandingSlug:"contrast-enhanced",shortDescription:"Text has 7:1 contrast (4.5:1 for large text)."},{id:"1.4.7",title:"Low or No Background Audio",level:"AAA",addedIn:"2.0",understandingSlug:"low-or-no-background-audio",shortDescription:"Background audio at least 20dB lower than speech."},{id:"1.4.8",title:"Visual Presentation",level:"AAA",addedIn:"2.0",understandingSlug:"visual-presentation",shortDescription:"Configurable visual presentation for blocks of text."},{id:"1.4.9",title:"Images of Text (No Exception)",level:"AAA",addedIn:"2.0",understandingSlug:"images-of-text-no-exception",shortDescription:"Images of text used only for decoration / essential."},{id:"1.4.10",title:"Reflow",level:"AA",addedIn:"2.1",understandingSlug:"reflow",shortDescription:"Content reflows at 320 CSS px without 2D scrolling."},{id:"1.4.11",title:"Non-text Contrast",level:"AA",addedIn:"2.1",understandingSlug:"non-text-contrast",shortDescription:"UI components and graphical objects have 3:1 contrast."},{id:"1.4.12",title:"Text Spacing",level:"AA",addedIn:"2.1",understandingSlug:"text-spacing",shortDescription:"Text spacing overrides don't cause content loss."},{id:"1.4.13",title:"Content on Hover or Focus",level:"AA",addedIn:"2.1",understandingSlug:"content-on-hover-or-focus",shortDescription:"Hover/focus tooltips are dismissible, hoverable, persistent."},{id:"2.1.1",title:"Keyboard",level:"A",addedIn:"2.0",understandingSlug:"keyboard",shortDescription:"All functionality available from the keyboard."},{id:"2.1.2",title:"No Keyboard Trap",level:"A",addedIn:"2.0",understandingSlug:"no-keyboard-trap",shortDescription:"Keyboard focus can move away from any component."},{id:"2.1.3",title:"Keyboard (No Exception)",level:"AAA",addedIn:"2.0",understandingSlug:"keyboard-no-exception",shortDescription:"All functionality is keyboard-operable without timing."},{id:"2.1.4",title:"Character Key Shortcuts",level:"A",addedIn:"2.1",understandingSlug:"character-key-shortcuts",shortDescription:"Single-key shortcuts can be turned off or remapped."},{id:"2.2.1",title:"Timing Adjustable",level:"A",addedIn:"2.0",understandingSlug:"timing-adjustable",shortDescription:"User can turn off, adjust, or extend time limits."},{id:"2.2.2",title:"Pause, Stop, Hide",level:"A",addedIn:"2.0",understandingSlug:"pause-stop-hide",shortDescription:"Moving/blinking/scrolling content can be paused."},{id:"2.2.3",title:"No Timing",level:"AAA",addedIn:"2.0",understandingSlug:"no-timing",shortDescription:"No timing essential to content/function (with exceptions)."},{id:"2.2.4",title:"Interruptions",level:"AAA",addedIn:"2.0",understandingSlug:"interruptions",shortDescription:"User can postpone/suppress non-emergency interruptions."},{id:"2.2.5",title:"Re-authenticating",level:"AAA",addedIn:"2.0",understandingSlug:"re-authenticating",shortDescription:"Authenticated session can be resumed without data loss."},{id:"2.2.6",title:"Timeouts",level:"AAA",addedIn:"2.1",understandingSlug:"timeouts",shortDescription:"Inactivity timeouts warned in advance."},{id:"2.3.1",title:"Three Flashes or Below Threshold",level:"A",addedIn:"2.0",understandingSlug:"three-flashes-or-below-threshold",shortDescription:"No content flashes more than 3 times per second."},{id:"2.3.2",title:"Three Flashes",level:"AAA",addedIn:"2.0",understandingSlug:"three-flashes",shortDescription:"No content flashes more than 3 times per second (no exceptions)."},{id:"2.3.3",title:"Animation from Interactions",level:"AAA",addedIn:"2.1",understandingSlug:"animation-from-interactions",shortDescription:"Motion animation from interactions can be disabled."},{id:"2.4.1",title:"Bypass Blocks",level:"A",addedIn:"2.0",understandingSlug:"bypass-blocks",shortDescription:"Mechanism to skip blocks of repeated content."},{id:"2.4.2",title:"Page Titled",level:"A",addedIn:"2.0",understandingSlug:"page-titled",shortDescription:"Pages have titles that describe topic/purpose."},{id:"2.4.3",title:"Focus Order",level:"A",addedIn:"2.0",understandingSlug:"focus-order",shortDescription:"Components receive focus in a meaningful order."},{id:"2.4.4",title:"Link Purpose (In Context)",level:"A",addedIn:"2.0",understandingSlug:"link-purpose-in-context",shortDescription:"Link purpose is clear from the link text + context."},{id:"2.4.5",title:"Multiple Ways",level:"AA",addedIn:"2.0",understandingSlug:"multiple-ways",shortDescription:"Two or more ways to locate a page within a set."},{id:"2.4.6",title:"Headings and Labels",level:"AA",addedIn:"2.0",understandingSlug:"headings-and-labels",shortDescription:"Headings and labels describe topic or purpose."},{id:"2.4.7",title:"Focus Visible",level:"AA",addedIn:"2.0",understandingSlug:"focus-visible",shortDescription:"Keyboard focus indicator is visible."},{id:"2.4.8",title:"Location",level:"AAA",addedIn:"2.0",understandingSlug:"location",shortDescription:"Information about user's location within a set is available."},{id:"2.4.9",title:"Link Purpose (Link Only)",level:"AAA",addedIn:"2.0",understandingSlug:"link-purpose-link-only",shortDescription:"Link purpose clear from the link text alone."},{id:"2.4.10",title:"Section Headings",level:"AAA",addedIn:"2.0",understandingSlug:"section-headings",shortDescription:"Section headings are used to organize content."},{id:"2.4.11",title:"Focus Not Obscured (Minimum)",level:"AA",addedIn:"2.2",understandingSlug:"focus-not-obscured-minimum",shortDescription:"Focused component not entirely hidden by other content."},{id:"2.4.12",title:"Focus Not Obscured (Enhanced)",level:"AAA",addedIn:"2.2",understandingSlug:"focus-not-obscured-enhanced",shortDescription:"Focused component fully visible."},{id:"2.4.13",title:"Focus Appearance",level:"AAA",addedIn:"2.2",understandingSlug:"focus-appearance",shortDescription:"Focus indicator meets specific size/contrast requirements."},{id:"2.5.1",title:"Pointer Gestures",level:"A",addedIn:"2.1",understandingSlug:"pointer-gestures",shortDescription:"Multi-point or path-based gestures have single-pointer alternatives."},{id:"2.5.2",title:"Pointer Cancellation",level:"A",addedIn:"2.1",understandingSlug:"pointer-cancellation",shortDescription:"Pointer actions can be aborted or undone."},{id:"2.5.3",title:"Label in Name",level:"A",addedIn:"2.1",understandingSlug:"label-in-name",shortDescription:"Accessible name includes the visible label text."},{id:"2.5.4",title:"Motion Actuation",level:"A",addedIn:"2.1",understandingSlug:"motion-actuation",shortDescription:"Motion-triggered functions have UI alternatives + can be disabled."},{id:"2.5.5",title:"Target Size (Enhanced)",level:"AAA",addedIn:"2.1",understandingSlug:"target-size-enhanced",shortDescription:"Pointer target size at least 44×44 CSS px."},{id:"2.5.6",title:"Concurrent Input Mechanisms",level:"AAA",addedIn:"2.1",understandingSlug:"concurrent-input-mechanisms",shortDescription:"Content doesn't restrict input modality."},{id:"2.5.7",title:"Dragging Movements",level:"AA",addedIn:"2.2",understandingSlug:"dragging-movements",shortDescription:"Dragging actions have single-pointer alternatives."},{id:"2.5.8",title:"Target Size (Minimum)",level:"AA",addedIn:"2.2",understandingSlug:"target-size-minimum",shortDescription:"Pointer target size at least 24×24 CSS px."},{id:"3.1.1",title:"Language of Page",level:"A",addedIn:"2.0",understandingSlug:"language-of-page",shortDescription:"Default human language of the page is programmatically set."},{id:"3.1.2",title:"Language of Parts",level:"AA",addedIn:"2.0",understandingSlug:"language-of-parts",shortDescription:"Language of each passage is programmatically set."},{id:"3.1.3",title:"Unusual Words",level:"AAA",addedIn:"2.0",understandingSlug:"unusual-words",shortDescription:"Definitions provided for jargon, idioms, etc."},{id:"3.1.4",title:"Abbreviations",level:"AAA",addedIn:"2.0",understandingSlug:"abbreviations",shortDescription:"Expanded form or meaning of abbreviations is available."},{id:"3.1.5",title:"Reading Level",level:"AAA",addedIn:"2.0",understandingSlug:"reading-level",shortDescription:"Supplemental content provided for advanced reading levels."},{id:"3.1.6",title:"Pronunciation",level:"AAA",addedIn:"2.0",understandingSlug:"pronunciation",shortDescription:"Mechanism for pronunciation of words where meaning is ambiguous."},{id:"3.2.1",title:"On Focus",level:"A",addedIn:"2.0",understandingSlug:"on-focus",shortDescription:"Focus does not trigger an unexpected context change."},{id:"3.2.2",title:"On Input",level:"A",addedIn:"2.0",understandingSlug:"on-input",shortDescription:"Input does not trigger an unexpected context change."},{id:"3.2.3",title:"Consistent Navigation",level:"AA",addedIn:"2.0",understandingSlug:"consistent-navigation",shortDescription:"Navigation is consistent across a set of pages."},{id:"3.2.4",title:"Consistent Identification",level:"AA",addedIn:"2.0",understandingSlug:"consistent-identification",shortDescription:"Components with the same function are identified consistently."},{id:"3.2.5",title:"Change on Request",level:"AAA",addedIn:"2.0",understandingSlug:"change-on-request",shortDescription:"Changes of context only on user request."},{id:"3.2.6",title:"Consistent Help",level:"A",addedIn:"2.2",understandingSlug:"consistent-help",shortDescription:"Help mechanisms appear in consistent order across pages."},{id:"3.3.1",title:"Error Identification",level:"A",addedIn:"2.0",understandingSlug:"error-identification",shortDescription:"Form errors are identified and described in text."},{id:"3.3.2",title:"Labels or Instructions",level:"A",addedIn:"2.0",understandingSlug:"labels-or-instructions",shortDescription:"Form fields have labels or instructions."},{id:"3.3.3",title:"Error Suggestion",level:"AA",addedIn:"2.0",understandingSlug:"error-suggestion",shortDescription:"Error suggestions provided when known."},{id:"3.3.4",title:"Error Prevention (Legal, Financial, Data)",level:"AA",addedIn:"2.0",understandingSlug:"error-prevention-legal-financial-data",shortDescription:"Submissions are reversible, checked, or confirmed."},{id:"3.3.5",title:"Help",level:"AAA",addedIn:"2.0",understandingSlug:"help",shortDescription:"Context-sensitive help is available."},{id:"3.3.6",title:"Error Prevention (All)",level:"AAA",addedIn:"2.0",understandingSlug:"error-prevention-all",shortDescription:"All submissions reversible, checked, or confirmed."},{id:"3.3.7",title:"Redundant Entry",level:"A",addedIn:"2.2",understandingSlug:"redundant-entry",shortDescription:"Previously-entered information is auto-populated or selectable."},{id:"3.3.8",title:"Accessible Authentication (Minimum)",level:"AA",addedIn:"2.2",understandingSlug:"accessible-authentication-minimum",shortDescription:"Auth doesn't require cognitive function test."},{id:"3.3.9",title:"Accessible Authentication (Enhanced)",level:"AAA",addedIn:"2.2",understandingSlug:"accessible-authentication-enhanced",shortDescription:"Auth doesn't require cognitive function test (no exceptions)."},{id:"4.1.1",title:"Parsing",level:"A",addedIn:"2.0",removedIn:"2.2",understandingSlug:"parsing",shortDescription:"Markup is well-formed (obsoleted in WCAG 2.2)."},{id:"4.1.2",title:"Name, Role, Value",level:"A",addedIn:"2.0",understandingSlug:"name-role-value",shortDescription:"UI components have programmatically determinable name + role + state."},{id:"4.1.3",title:"Status Messages",level:"AA",addedIn:"2.1",understandingSlug:"status-messages",shortDescription:"Status messages programmatically announced without focus change."}];function St(e,t){const a={A:1,AA:2,AAA:3},n={"2.0":1,"2.1":2,"2.2":3};return Us.filter(o=>n[o.addedIn]>n[e]||o.removedIn&&n[o.removedIn]<=n[e]?!1:a[o.level]<=a[t])}function Fh(e,t){return`https://www.w3.org/WAI/WCAG${t.replace(".","")}/Understanding/${e.understandingSlug}`}let Le="2.1";const fn="AA";function Wh(e){Le=e}async function qh(){var e,t;try{const n=(await chrome.storage.local.get("wcagTargetVersion")).wcagTargetVersion;(n==="2.1"||n==="2.2")&&(Le=n),(t=(e=chrome.storage.onChanged)==null?void 0:e.addListener)==null||t.call(e,(o,i)=>{if(i!=="local")return;const r=o.wcagTargetVersion;if(!r)return;const c=r.newValue;(c==="2.1"||c==="2.2")&&(Le=c)})}catch{}}const _s=["3.2.3","3.2.4","3.2.6","3.3.7"];function Fs(e){const t=St(Le,fn),a=new Map;for(const i of t)a.set(i.id,{criterionId:i.id,level:i.level,title:i.title,state:"not-evaluated",evidence:[]});const n=new Map;function o(i,r){let c=n.get(i);return c||(c=new Set,n.set(i,c)),c.has(r)?!0:(c.add(r),!1)}for(const i of e.audits){const r=i.pageUrl??i.scope;for(const l of i.violations){if(!l.wcagCriterion)continue;const d=a.get(l.wcagCriterion);if(!d)continue;const u=l.ruleId.startsWith("ai-"),h=l.target.selector;if(e.acknowledgedMatchKeys.has(l.matchKey)){const f=`ack::${l.ruleId}::${r}::${h}`;if(o(l.wcagCriterion,f))continue;d.evidence.push({source:u?"ai":"axe",ruleOrStepId:l.ruleId,outcome:"ack",pageUrl:r,selector:h});continue}if(l.needsReview){const f=`needsReview::${l.ruleId}::${r}::${h}`;if(o(l.wcagCriterion,f))continue;d.evidence.push({source:"ai",ruleOrStepId:l.ruleId,outcome:"incomplete",pageUrl:r,selector:h,resolutionHint:"Acknowledge in the Matrix view if not an issue, or fix the underlying alt text."});continue}const m=`fail::${l.ruleId}::${r}::${h}`;o(l.wcagCriterion,m)||d.evidence.push({source:u?"ai":"axe",ruleOrStepId:l.ruleId,outcome:"fail",pageUrl:r,selector:h,resolutionHint:`Fix ${l.ruleId} on ${h}.`})}const c=i.axeRulesEvaluated;if(c){for(const l of c.passed){const d=a.get(l.wcagCriterion);if(!d)continue;const u=`axe::${l.ruleId}::${r}::pass`;o(l.wcagCriterion,u)||d.evidence.push({source:"axe",ruleOrStepId:l.ruleId,outcome:"pass",pageUrl:r})}for(const l of c.inapplicable){const d=a.get(l.wcagCriterion);if(!d)continue;const u=`axe::${l.ruleId}::${r}::inapplicable`;o(l.wcagCriterion,u)||d.evidence.push({source:"axe",ruleOrStepId:l.ruleId,outcome:"inapplicable",pageUrl:r})}for(const l of c.incomplete){const d=a.get(l.wcagCriterion);if(!d)continue;const u=`axe::${l.ruleId}::${r}::incomplete`,h=d.evidence.findIndex(m=>m.source==="axe"&&m.outcome==="incomplete"&&m.ruleOrStepId===l.ruleId&&m.pageUrl===r);if(h>=0){if(l.elements&&l.elements.length>0){const m=d.evidence[h],f=new Set((m.elements??[]).map(g=>g.selector));for(const g of l.elements)f.has(g.selector)||((m.elements??(m.elements=[])).push(g),f.add(g.selector))}continue}o(l.wcagCriterion,u)||d.evidence.push({source:"axe",ruleOrStepId:l.ruleId,outcome:"incomplete",pageUrl:r,elements:l.elements?[...l.elements]:void 0,resolutionHint:"axe-core couldn't fully determine. Common causes: text against an image/gradient background, or aria-role on a hidden element. Review the page manually."})}}}if(e.incompleteResolutions&&e.incompleteResolutions.length>0){const i=new Map;for(const r of e.incompleteResolutions)i.set(`${r.pageUrl}::${r.ruleId}::${r.selector}`,r);for(const r of a.values()){const c=[];for(const l of r.evidence){if(l.source!=="axe"||l.outcome!=="incomplete"||!l.elements||l.elements.length===0){c.push(l);continue}const d=[];for(const u of l.elements){const h=`${l.pageUrl}::${l.ruleOrStepId}::${u.selector}`,m=i.get(h);if(!m){d.push(u);continue}m.verdict==="pass"?c.push({source:"ai",ruleOrStepId:`ai-resolved::${l.ruleOrStepId}`,outcome:"pass",pageUrl:l.pageUrl,selector:u.selector,notes:m.reasoning}):m.verdict==="fail"?c.push({source:"ai",ruleOrStepId:`ai-resolved::${l.ruleOrStepId}`,outcome:"fail",pageUrl:l.pageUrl,selector:u.selector,notes:m.reasoning,resolutionHint:`AI determined ${l.ruleOrStepId} fails on this element. Review and fix or override.`}):d.push(u)}d.length>0&&c.push({...l,elements:d})}r.evidence=c}}if(e.interactiveAuditResults&&e.interactiveAuditResults.length>0)for(const i of e.interactiveAuditResults){const r=a.get(i.criterionId);r&&r.evidence.push({source:"ai",ruleOrStepId:i.ruleId,outcome:i.verdict==="pass"?"pass":i.verdict==="fail"?"fail":"incomplete",pageUrl:i.pageUrl,notes:i.reasoning,resolutionHint:i.verdict==="fail"?"Review the AI walkthrough in the Compliance tab; fix the underlying focus-order issue and re-run.":i.verdict==="uncertain"?"Re-run the interactive audit, or manually verify via Guided Tests.":void 0})}for(const i of e.workflows)for(const r of i.steps){if(!r.wcag)continue;const c=a.get(r.wcag);if(c)for(const l of e.igtRuns){if(l.workflowId!==i.id)continue;const d=l.steps[r.id],u=`${i.id}.${r.id}`;if(!d){c.evidence.push({source:"igt",ruleOrStepId:u,outcome:"skip",resolutionHint:`Open Guided Tests → ${i.name} → answer "${r.id}" as Pass / Fail / N/A.`});continue}d.status==="pass"?c.evidence.push({source:"igt",ruleOrStepId:u,outcome:"pass",notes:d.notes}):d.status==="fail"?c.evidence.push({source:"igt",ruleOrStepId:u,outcome:"fail",notes:d.notes,resolutionHint:`Address the failing manual check: ${r.question}`}):d.status==="not-applicable"?c.evidence.push({source:"igt",ruleOrStepId:u,outcome:"n/a",notes:d.notes}):d.status==="skip"&&c.evidence.push({source:"igt",ruleOrStepId:u,outcome:"skip",notes:d.notes,resolutionHint:`Open Guided Tests → ${i.name} → re-answer "${r.id}" as Pass / Fail / N/A.`})}}for(const i of a.values())i.state=Ws(i.evidence);if(e.auditScope==="single-page")for(const i of _s){const r=a.get(i);r&&r.state==="not-evaluated"&&(r.state="not-applicable",r.evidence.push({source:"axe",ruleOrStepId:"wcc-multi-page-only-na",outcome:"n/a",notes:"Single-page audit cannot evaluate this cross-page criterion. Treated as N/A for this audit; run Site Crawl to evaluate."}))}if(e.humanCriterionVerdicts&&e.humanCriterionVerdicts.length>0)for(const i of e.humanCriterionVerdicts){const r=a.get(i.criterionId);if(!r)continue;const c=i.verdict==="na"?"n/a":i.verdict,l=i.verdict==="na"?"not-applicable":i.verdict==="fail"?"fail":"pass";r.state=l,r.evidence.push({source:"human",ruleOrStepId:"human-criterion-verdict",outcome:c,notes:`Human verified ${i.verdict.toUpperCase()} on ${i.verifiedAt}: ${i.note}`})}return a}function Ws(e){return e.length===0?"not-evaluated":e.some(a=>a.outcome==="fail")?"fail":e.some(a=>a.outcome==="incomplete"||a.outcome==="skip")?"inconclusive":e.some(a=>a.outcome==="pass"||a.outcome==="inapplicable"||a.outcome==="ack")?"pass":"not-applicable"}function qs(e){const t={pass:[],fail:[],inconclusive:[],notApplicable:[],notEvaluated:[]};for(const n of e.values())t[Gs(n.state)].push(n.criterionId);const a=e.size;return{pass:t.pass.length,fail:t.fail.length,inconclusive:t.inconclusive.length,notApplicable:t.notApplicable.length,notEvaluated:t.notEvaluated.length,totalApplicable:a,canClaimConformance:t.fail.length===0&&t.inconclusive.length===0&&t.notEvaluated.length===0,byState:t}}function Gs(e){return e==="not-applicable"?"notApplicable":e==="not-evaluated"?"notEvaluated":e}function Gh(e,t=Ee){const a=new Set,n=new Set,o=new Set;for(const r of e.values())for(const c of r.evidence)c.source==="axe"?a.add(r.criterionId):c.source==="ai"?n.add(r.criterionId):c.source==="igt"&&o.add(r.criterionId);for(const r of t)for(const c of r.steps)c.wcag&&e.has(c.wcag)&&o.add(c.wcag);function i(r,c){const l=Array.from(c).sort();let d=0;const u=[],h=[],m=[];for(const f of l){const g=e.get(f);if(!g)continue;const p=g.evidence.filter(y=>r==="automation"&&y.source==="axe"||r==="ai"&&y.source==="ai"||r==="human"&&y.source==="igt");if(p.length===0){m.push(f);continue}const b=p.some(y=>y.outcome==="fail"),w=p.some(y=>y.outcome==="incomplete"||y.outcome==="skip"),k=p.some(y=>y.outcome==="pass"||y.outcome==="inapplicable"||y.outcome==="ack"||y.outcome==="n/a");b?u.push(f):w?h.push(f):k?d++:m.push(f)}return{layer:r,coveredCriteria:l,cleared:d,failing:u,inconclusive:h,blocked:[...u,...h].sort(),unevaluated:m}}return{automation:i("automation",a),ai:i("ai",n),human:i("human",o)}}const Aa=[{id:"contrast",label:"Color contrast",rules:["color-contrast","color-contrast-enhanced","link-in-text-block"],needsManualCheck:!1},{id:"alt",label:"Image alt text",rules:["image-alt","input-image-alt","area-alt","svg-img-alt","object-alt","role-img-alt"],needsManualCheck:!1},{id:"labels",label:"Form labels",rules:["label","select-name","aria-input-field-name","aria-toggle-field-name","form-field-multiple-labels","label-content-name-mismatch","autocomplete-valid"],needsManualCheck:!1},{id:"names",label:"Buttons / links named",rules:["button-name","input-button-name","link-name","empty-button","empty-heading","empty-link"],needsManualCheck:!1},{id:"keyboard",label:"Keyboard operability",rules:["nested-interactive","tabindex","frame-focusable-content","no-focusable-content"],needsManualCheck:!0},{id:"structure",label:"Landmarks + heading structure",rules:["heading-order","page-has-heading-one","landmark-one-main","landmark-no-duplicate-banner","landmark-no-duplicate-contentinfo","landmark-no-duplicate-main","region","bypass"],needsManualCheck:!1}];function Vs(e){const t=new Map;for(const a of e){const n=`${a.ruleId}::${a.target.selector}`;t.has(n)||t.set(n,a)}return Array.from(t.values())}function js(e,t,a="2.1",n="AA",o,i,r,c,l){const d=Fs({audits:e??[],igtRuns:(t==null?void 0:t.runs)??[],workflows:(t==null?void 0:t.workflows)??Ee,acknowledgedMatchKeys:o??new Set,incompleteResolutions:i,interactiveAuditResults:r,auditScope:c,humanCriterionVerdicts:l}),u={},h={},m=[],f=[],g=[];let p=0;for(const w of d.values()){if(w.state==="not-evaluated"){m.push(w.criterionId);continue}if(p++,w.state==="fail"){g.push(w.criterionId);const k=[];for(const y of w.evidence)y.outcome==="fail"&&k.push({source:y.source,ruleOrStepId:y.ruleOrStepId,selector:y.selector,notes:y.notes,pageUrl:y.pageUrl});k.length>0&&(h[w.criterionId]=k)}if(w.state==="inconclusive"){f.push(w.criterionId);const k=[];for(const y of w.evidence)if(y.outcome==="incomplete"){const s=y.source==="ai"?"ai-uncertain":"axe-incomplete";k.push({source:s,ruleOrStepId:y.ruleOrStepId,resolutionHint:y.resolutionHint,elements:y.elements,pageUrl:y.pageUrl})}else y.outcome==="skip"&&k.push({source:"igt-skip",ruleOrStepId:y.ruleOrStepId,resolutionHint:y.resolutionHint});k.length>0&&(u[w.criterionId]=k)}}const b=qs(d);return{targetVersion:Le,targetLevel:fn,totalApplicable:d.size,evaluated:p,untestedCriteria:m,inconclusiveCriteria:f,failingCriteria:g,inconclusiveReasons:u,failingReasons:h,canClaimConformance:b.canClaimConformance}}function Se(e,t,a,n,o,i,r){const c=Vs(e),l=c.filter(S=>S.needsReview),d=c.filter(S=>!S.needsReview),u={critical:0,serious:0,moderate:0,minor:0,unique:d.length};for(const S of d)u[S.impact]++;let h;t&&(h=zs(t.runs,t.workflows),u.serious+=h.failedRequired,u.moderate+=h.failedAdvisory,u.unique+=h.failedRequired+h.failedAdvisory);const m=[],f=[];if(o&&o.length>0){const S=new Set;if(i)for(const P of i)S.add(`${P.criterionId}::${P.pageUrl}`);const R=new Set;for(const P of o){if(P.verdict==="pass")continue;const I=`${P.criterionId}::${P.pageUrl}`;if(R.has(I))continue;R.add(I);const $=P.verdict==="fail"?"serious":"moderate",C={criterionId:P.criterionId,pageUrl:P.pageUrl,verdict:P.verdict,impact:$,reasoning:P.reasoning};if(S.has(I)){f.push(C);continue}m.push(C),u[$]++,u.unique++}}const g=new Map;for(const S of Aa)for(const R of S.rules)g.set(R,S);const p=new Map;for(const S of Aa)p.set(S.id,{count:0,rules:new Set});for(const S of jo(d)){const R=g.get(S.ruleId);if(!R)continue;const P=p.get(R.id);P.count++,P.rules.add(S.ruleId)}const b=Aa.map(S=>{const R=p.get(S.id);let P="pass";return R.count>0?P="fail":S.needsManualCheck&&(P="unchecked"),{id:S.id,label:S.label,status:P,violationCount:R.count,rules:Array.from(R.rules),needsManualCheck:S.needsManualCheck}}),w=u.critical>0,k=u.serious>0,y=(()=>{if(!o||o.length===0||!i||i.length===0)return o;const S=new Set;for(const R of i)S.add(`${R.criterionId}::${R.pageUrl}`);return o.map(R=>S.has(`${R.criterionId}::${R.pageUrl}`)?{...R,verdict:"pass",reasoning:`Human verified: ${R.reasoning}`}:R)})(),s=a?js(a,t,"2.1","AA",n,void 0,y,void 0,r):void 0,O=s?!s.canClaimConformance:!1;let E;u.critical>=5?E="F":u.critical>=1?E="D":u.serious>=5||u.serious>=1?E="C":u.moderate>=3||u.moderate>=1?E="B":(u.minor>=1,E="A"),O&&E==="A"&&(E="B");const v=b.filter(S=>S.status==="fail").length;let D;return u.critical>0||v>=4?D="critical":u.serious>=3||v>=2?D="high":u.serious>=1||u.moderate>=3?D="moderate":D="low",{letter:E,risk:D,caps:{cappedAtC:w,cappedAtB:k,cappedAtBByCoverage:O},totals:u,categories:b,coverage:s,manual:h,reviewQueue:{count:l.length,rules:Array.from(new Set(l.map(S=>S.ruleId)))},walkthroughFindings:m,walkthroughAcknowledgements:f}}function zs(e,t){new Map(t.map(c=>[c.id,c]));const a=new Map(e.map(c=>[c.workflowId,c]));let n=0,o=0,i=0;const r=t.map(c=>{const l=a.get(c.id);let d=0,u=0,h=0,m=0,f=0;for(const g of c.steps){const p=l==null?void 0:l.steps[g.id];p&&(f++,p.status==="pass"?d++:p.status==="skip"?m++:p.status==="fail"&&(g.severity==="required"?u++:h++))}return f===c.steps.length&&i++,n+=u,o+=h,{workflowId:c.id,name:c.name,passed:d,failedRequired:u,failedAdvisory:h,skipped:m,answered:f,total:c.steps.length}});return{workflowsCompleted:i,workflowsTotal:t.length,failedRequired:n,failedAdvisory:o,perWorkflow:r}}const Hs={low:"Low exposure — no critical or serious violations across the three layers (axe-core, wcagcheckr DOM analyzers, AI walkthroughs). All 50 WCAG 2.1 AA criteria have an evaluation path; spot-check content-meaning criteria (caption accuracy, alt-text correctness) before any formal conformance claim.",moderate:"Moderate exposure — some serious or multiple moderate violations. Should be addressed but not lawsuit-emergency level.",high:"High exposure — multiple serious violations OR multiple lawsuit-magnet categories failing. ADA Title III suits target sites at this level.",critical:"Critical exposure — at least one critical-impact violation OR most lawsuit-magnet categories failing. Sites in this state are demand-letter targets."},Jo={low:"✓ Off the radar",moderate:"⚡ Some lawsuit exposure",high:"⚠️ High target risk",critical:"🚨 Lawsuit magnet"},Xo={low:"Your site clears the automated checks ADA-targeting tools use to identify lawsuit victims. wcagcheckr runs three layers (axe-core, our DOM analyzers, AI walkthroughs) covering all 50 WCAG 2.1 AA criteria — most demand-letter mills only run axe.",moderate:"Some failures. Not the easiest target, but the issues here are exactly what automated targeting tools look for. Switch to Developer mode for the issue list and fix-it walkthroughs.",high:"Multiple serious failures. Automated lawsuit-screening tools will flag this site. Real risk of being targeted with a demand letter — Developer mode has the full issue list and fix-it walkthroughs.",critical:"Your site fails the automated WCAG checks demand-letter mills run on thousands of sites to identify lawsuit targets. Without remediation, expect to be flagged. Developer mode has the full issue list plus fix-it guidance for each."},Vh={A:"No critical, serious, or moderate violations across the three layers (axe-core, wcagcheckr DOM analyzers, AI walkthroughs). All 50 WCAG 2.1 AA criteria evaluated; content-meaning checks (caption accuracy, alt-text correctness, sensory cues) benefit from a human spot-check before formal conformance claims.",B:"Only moderate-impact violations detected. No serious or critical findings.",C:"Has at least one serious-impact violation. Automatically capped here regardless of how few.",D:"Has at least one critical-impact violation. Automatically capped here regardless of how few.",F:"Multiple critical-impact violations detected. Lawsuit-magnet profile."},at={"ai-alt-misleading":{summary:`The AI vision model judged this image's alt text is empty, misleading, or generic when the image is actually informative or functional. Use the AI assessment (shown above this recipe) to understand what the image depicts, then write descriptive alt text conveying its meaning to a screen-reader user. Use alt="" only when the image is genuinely decorative (a divider, a flourish, an icon next to text that already labels the action). Decorative is rare — when in doubt, describe.`,snippetLang:"html",snippet:`<!-- Failing — empty alt on an informational logo -->
5
+ <img src="/assets/logo.png" alt="">
6
+
7
+ <!-- Passing — descriptive alt -->
8
+ <img src="/assets/logo.png" alt="Acme Corp logo">
9
+
10
+ <!-- Decorative (rare) — empty alt + aria-hidden -->
11
+ <img src="/divider.svg" alt="" aria-hidden="true">`},"ai-alt-uncertain":{summary:"The AI could not verify whether the existing alt text accurately matches the image (typically: a person's name on a portrait, a product SKU on a packshot — plausible but not provable from pixels). Surface this finding to a human content owner who knows the actual subject. Confirm or correct the alt. No code change needed if the alt is already accurate.",snippetLang:"html",snippet:`<!-- Example: AI sees a person, can't confirm identity -->
12
+ <img src="/team/cliff.jpg" alt="Cliff C. - Founder of Locustware">
13
+ <!-- Action: verify the named person matches the image. If yes, leave it. -->`},"ai-heading-not-descriptive":{summary:'WCAG 2.4.6 — Headings and labels must describe the topic or purpose of the section they introduce. The AI judged this heading is too generic ("More info", "Section 1", "Welcome") to convey what its section actually covers. Rewrite the heading to summarise the section content in a handful of words. Screen-reader users navigate by heading; vague headings make the page unscannable.',snippetLang:"html",snippet:`<!-- Failing -->
14
+ <h2>More info</h2>
15
+ <p>Our 2024 financial results show ...</p>
16
+
17
+ <!-- Passing -->
18
+ <h2>2024 financial results</h2>
19
+ <p>Our 2024 financial results show ...</p>`},"ai-sensory-instruction":{summary:'WCAG 1.3.3 — Instructions must not rely solely on sensory characteristics (shape, size, colour, position, sound). "Click the round green button" excludes users who cannot perceive colour or shape. Add a programmatic identifier — visible text label, accessible name, or labelled reference — that does not depend on visual or auditory perception.',snippetLang:"html",snippet:`<!-- Failing -->
20
+ <p>Click the green button on the right to proceed.</p>
21
+
22
+ <!-- Passing -->
23
+ <p>Click the <strong>"Proceed"</strong> button to continue.</p>`},"ai-aria-misuse":{summary:`WCAG 4.1.2 — ARIA role must match the element's actual behaviour. The AI judged this element uses an ARIA role that does not fit its DOM structure or behaviour. First rule of ARIA: don't use ARIA when a native element works. Prefer <button> over <div role="button">, <nav> over <div role="navigation">, etc. If the role IS appropriate, verify all required ARIA attributes (aria-expanded, aria-controls, aria-label, aria-selected, etc.) are present and accurate.`,snippetLang:"html",snippet:`<!-- Failing — div claims to be a button -->
24
+ <div role="button" onclick="...">Submit</div>
25
+
26
+ <!-- Passing — use the native element -->
27
+ <button type="button" onclick="...">Submit</button>`},"ai-image-of-text":{summary:"WCAG 1.4.5 — Text rendered into a raster image (banner copy, infographic labels, social-share text, quote graphics) does not scale, does not respect user font preferences, can't reflow, and is only reachable via alt text. Replace with real HTML text styled to match the previous design. Exceptions: logos, brand wordmarks, and product photography where text is incidental.",snippetLang:"html",snippet:`<!-- Failing — text baked into image -->
28
+ <img src="/banners/big-quote.png" alt="Customer testimonial: 'Best service ever.'">
29
+
30
+ <!-- Passing — real text -->
31
+ <figure class="testimonial">
32
+ <blockquote>Best service ever.</blockquote>
33
+ <figcaption>— a happy customer</figcaption>
34
+ </figure>`},"ai-color-only-meaning":{summary:"WCAG 1.4.1 — Information conveyed by colour alone is invisible to users with colour-vision deficiency, monochrome displays, or low-contrast environments. Pair every colour signal with a redundant text label, icon, shape, or pattern. Status indicators, form validation, chart series, table-row highlights — wherever colour carries meaning, add a secondary cue.",snippetLang:"html",snippet:`<!-- Failing — status conveyed only by background colour -->
35
+ <tr style="background:#fee">Failed run</tr>
36
+ <tr style="background:#efe">Passed run</tr>
37
+
38
+ <!-- Passing — colour + icon + text -->
39
+ <tr><td>❌ Failed</td>...</tr>
40
+ <tr><td>✅ Passed</td>...</tr>`},"ai-generic-link-text":{summary:'WCAG 2.4.4 — Link purpose must be clear from the link text alone OR the link text plus its immediate surrounding context. "Click here", "Learn more", "Read this", and similarly vague text fail when a screen-reader user navigates a list of links out of context. Rewrite the link to describe its destination, ensure the surrounding sentence makes the destination unambiguous, or use aria-label / aria-labelledby to attach a descriptive name.',snippetLang:"html",snippet:`<!-- Failing — link text alone is meaningless -->
41
+ <p>Our pricing has changed. <a href="/pricing">Click here</a>.</p>
42
+
43
+ <!-- Passing — link text is self-describing -->
44
+ <p>Our pricing has changed. <a href="/pricing">See our 2025 pricing tiers</a>.</p>`},"ai-wall-of-text":{summary:"WCAG 3.1.5 — Long unbroken prose with no scannable structure is exhausting to read and inaccessible to users with cognitive disabilities. Break the block into shorter paragraphs (aim for 50-150 words each), add a subheading every 2-4 paragraphs, convert lists-in-prose into actual <ul> / <ol>, and use plain language where possible.",snippetLang:"html",snippet:`<!-- Failing — one giant paragraph -->
45
+ <p>Our process is straightforward we start by gathering requirements then we design the solution then we implement it then we test it then we deploy it then we support it ongoing ...</p>
46
+
47
+ <!-- Passing — chunked with subheadings -->
48
+ <h3>1. Requirements</h3>
49
+ <p>We start by gathering your specifics.</p>
50
+ <h3>2. Design</h3>
51
+ <p>We map out the solution before writing code.</p>`},"ai-language-mismatch":{summary:`WCAG 3.1.1 / 3.1.2 — The page or passage's declared language attribute does not match the actual content language. Screen readers use the lang attribute to choose pronunciation rules; mismatch produces unintelligible speech. Either correct the <html lang=""> declaration to match the content, or add lang="" to any sub-passage that is in a different language than the page default.`,snippetLang:"html",snippet:`<!-- Failing — content is English but lang says French -->
52
+ <html lang="fr"><body>...English content...</body></html>
53
+
54
+ <!-- Passing — correct page-level declaration -->
55
+ <html lang="en"><body>...English content...</body></html>
56
+
57
+ <!-- Passing — passage-level override for non-default language -->
58
+ <p>The German term is <span lang="de">Schadenfreude</span>.</p>`},"wcagcheckr-reflow":{summary:"WCAG 1.4.10 Reflow requires content to be presentable at 320 CSS pixels wide without requiring horizontal scrolling, except for data tables, code blocks, maps, and other intrinsically wide content. The usual culprits: fixed widths in px, hard-coded layout containers, white-space: nowrap on long text, large images without max-width, and horizontal scroll containers that exceed the viewport.",snippetLang:"css",snippet:`/* Failing: fixed-width container forces horizontal scroll at 320px */
59
+ .container { width: 1024px; }
60
+
61
+ /* Passing: use max-width and let content reflow */
62
+ .container { max-width: 1024px; width: 100%; }
63
+
64
+ /* Prevent images / media from forcing overflow */
65
+ img, video, iframe { max-width: 100%; height: auto; }
66
+
67
+ /* Avoid hard nowrap on long inline content */
68
+ .long-string { overflow-wrap: anywhere; }`},"color-contrast":{summary:"Ensure foreground and background colors meet WCAG 2 AA contrast ratios: 4.5:1 for normal text, 3:1 for large text (18pt+ regular or 14pt+ bold). Check both default and interactive states (:hover, :focus) — a hover-only contrast failure is the most common regression.",snippetLang:"css",snippet:`/* Failing: light gray on white = 1.85:1 */
69
+ .muted { color: #d3d3d3; }
70
+
71
+ /* Passing: dark slate on white = 12.6:1 */
72
+ .muted { color: #475569; }`},"image-alt":{summary:'Every <img> must have an alt attribute. Use empty alt="" for decorative images (the browser then announces nothing); use descriptive text for informative images.',snippetLang:"html",snippet:`<!-- Decorative -->
73
+ <img src="divider.svg" alt="" />
74
+
75
+ <!-- Informative -->
76
+ <img src="chart.png" alt="Q3 revenue rose 42%" />`},"button-name":{summary:"Every <button> needs an accessible name. Provide visible text, an aria-label, or aria-labelledby. Icon-only buttons especially need aria-label so screen reader users hear what the button does.",snippetLang:"html",snippet:`<!-- ✅ Visible text -->
77
+ <button>Save changes</button>
78
+
79
+ <!-- ✅ Icon-only with aria-label -->
80
+ <button aria-label="Close dialog">
81
+ <svg aria-hidden="true">...</svg>
82
+ </button>`},"link-name":{summary:'Links must have discernible text. Avoid empty <a> tags or links containing only an icon without an aria-label. "Read more" links should reference what they read more about via aria-label or surrounding context.',snippetLang:"html",snippet:`<!-- ❌ Generic -->
83
+ <a href="/post/42">Read more</a>
84
+
85
+ <!-- ✅ Specific -->
86
+ <a href="/post/42">Read more about Q3 revenue</a>
87
+
88
+ <!-- ✅ Or with aria-label -->
89
+ <a href="/post/42" aria-label="Read more about Q3 revenue">Read more</a>`},"heading-order":{summary:"Don't skip heading levels. After an <h2>, the next heading should be <h2> or <h3> — never <h4>. Screen reader users navigate by heading level, and skipped levels imply missing structure.",snippetLang:"html",snippet:`<!-- ❌ -->
90
+ <h1>Page title</h1>
91
+ <h3>Section</h3>
92
+
93
+ <!-- ✅ -->
94
+ <h1>Page title</h1>
95
+ <h2>Section</h2>`},"landmark-one-main":{summary:`Every page should have exactly one <main> element (or one element with role="main"). It marks the start of the page's primary content for screen reader users.`,snippetLang:"html",snippet:`<body>
96
+ <header>...</header>
97
+ <main>
98
+ <!-- primary content -->
99
+ </main>
100
+ <footer>...</footer>
101
+ </body>`},"landmark-no-duplicate-banner":{summary:'A page should have at most one <header> at the top level (or one [role="banner"]). Multiple top-level banners confuse landmark navigation.'},"landmark-no-duplicate-contentinfo":{summary:'A page should have at most one <footer> at the top level (or one [role="contentinfo"]). Multiple top-level footers confuse landmark navigation.'},"aria-required-attr":{summary:'When using an ARIA role, you must include all required attributes for that role. For example, role="checkbox" requires aria-checked; role="combobox" requires aria-expanded.',snippetLang:"html",snippet:`<!-- ❌ Missing aria-checked -->
102
+ <div role="checkbox">Subscribe</div>
103
+
104
+ <!-- ✅ -->
105
+ <div role="checkbox" aria-checked="false" tabindex="0">Subscribe</div>`},"aria-allowed-attr":{summary:"Some ARIA attributes only apply to specific roles. For example, aria-required is only valid on form widgets; putting it on a <div> with no role is wrong. Either remove the attribute or add the appropriate role."},"aria-roles":{summary:'The role attribute value must be a valid ARIA role from the WAI-ARIA spec. Common typos: "buton", "tab-list", "menuitem-radio". Check the WAI-ARIA spec for the exact role name.'},"aria-valid-attr":{summary:"Use ARIA attribute names exactly as specified in WAI-ARIA. Common typos: aria-labeledby (should be aria-labelledby with two L's), aria-describeby (should be aria-describedby)."},"duplicate-id":{summary:"IDs must be unique within a document. Duplicate IDs break label association, fragment navigation, and aria-labelledby/aria-describedby references."},"duplicate-id-aria":{summary:"IDs referenced by ARIA attributes (aria-labelledby, aria-describedby, aria-controls) must be unique. Duplicate IDs cause assistive tech to associate the wrong element."},"empty-button":{summary:"A button with no text and no aria-label has no name for screen readers. Add visible text or an aria-label describing what the button does."},"empty-heading":{summary:"Heading elements should contain text. An empty heading provides no information and confuses screen reader heading navigation. Either add text or remove the heading."},"empty-link":{summary:'A link with no text and no aria-label has no name. Screen reader users hear "link" with no destination cue. Add link text or aria-label.'},"form-field-multiple-labels":{summary:'A form field should have one label, not multiple. Multiple labels (e.g., wrapping <label> + separate <label for="">) confuse assistive tech about which is the canonical name.'},"html-has-lang":{summary:"The root <html> element needs a lang attribute so screen readers use the correct pronunciation rules.",snippetLang:"html",snippet:'<html lang="en">'},"html-lang-valid":{summary:`The lang attribute value must be a valid IANA language tag. Use "en" for English, "es" for Spanish, "fr-CA" for Canadian French, etc. Don't invent codes.`},"input-button-name":{summary:'Input elements with type="button", type="submit", or type="reset" must have an accessible name via the value attribute or aria-label.',snippetLang:"html",snippet:'<input type="submit" value="Save changes" />'},"input-image-alt":{summary:"Image-type inputs must have alt text describing the button's action.",snippetLang:"html",snippet:'<input type="image" src="search.png" alt="Search" />'},"meta-viewport":{summary:"Don't set user-scalable=no or maximum-scale less than 5.0 on the viewport meta. That blocks low-vision users from zooming.",snippetLang:"html",snippet:`<!-- ✅ Allows zoom -->
106
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
107
+
108
+ <!-- ❌ Blocks zoom -->
109
+ <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />`},"nested-interactive":{summary:"Don't put interactive elements inside other interactive elements. A button inside a link, or a link inside a button, breaks keyboard activation and screen reader announcements."},"presentation-role-conflict":{summary:`role="presentation" or role="none" removes an element's semantics. Don't apply it to elements that have important interactive properties (focusable, named) — those will conflict and be ignored.`},"select-name":{summary:'Every <select> needs a programmatic label, just like <input> elements. Use <label for=""> or aria-label.'},"target-size":{summary:"Interactive targets must be at least 24×24 CSS px (WCAG 2.5.8 AA) or 44×44 (2.5.5 AAA). Small touch targets are hard to hit accurately, especially on mobile and for users with motor impairments. If the target itself can't be larger, ensure 24px of clear spacing between adjacent targets.",snippetLang:"css",snippet:`/* Pad the hit area without changing visual size */
110
+ .icon-button {
111
+ width: 24px;
112
+ height: 24px;
113
+ /* Ensures clickable area is 44×44 */
114
+ padding: 10px;
115
+ background-clip: content-box;
116
+ }`},"valid-lang":{summary:'lang attribute values on individual elements (e.g., <span lang="es">) must be valid IANA language tags, just like the root html lang.'},"autocomplete-valid":{summary:'autocomplete attribute values must come from the WHATWG list. Common valid values: "name", "email", "tel", "current-password", "new-password", "street-address", "postal-code". Browsers and password managers rely on these to suggest the right values.'},"wcc-consistency-needs-site-crawl":{summary:`WCAG 3.2.3 Consistent Navigation + 3.2.4 Consistent Identification + 3.2.6 Consistent Help require that navigation, brand link, search, and help affordances appear in the SAME ORDER and with the SAME ACCESSIBLE NAME across every page where they appear. A single-page audit can't verify this — it only sees one page. To remediate:
117
+
118
+ 1. Run the Site Crawl flow against a representative sample (≥ 5 pages covering the major templates: home, an interior page, a content page, a transactional page, a settings page). The crawler emits per-divergence findings naming exactly which element diverges and which pages disagree.
119
+
120
+ 2. If divergences come back, fix them in the SHARED LAYOUT components (header, footer, navigation menu) — not page-by-page. Same JSX or partial = same DOM order across every page = the criterion clears for the whole site in one change.
121
+
122
+ 3. If your site has truly different navigation per page-type (e.g., logged-in dashboard vs marketing site), document the role/template each nav serves and ensure WITHIN each role the nav is consistent. Per-template consistency satisfies 3.2.3 — the criterion isn't "every page identical," it's "every page where this set of items appears, they appear in the same order."
123
+
124
+ False-positive cases (the ONLY legitimate reasons to skip): genuinely-different page types (login form vs main app), conditional nav based on auth/feature-flag (still consistent within each branch — verify both states), responsive mobile-vs-desktop nav variants (must be consistent within each breakpoint).`,snippetLang:"html",snippet:`<!-- Anti-pattern — same nav re-written per page, divergence likely -->
125
+ <!-- pages/home.html -->
126
+ <nav><a href="/">Home</a><a href="/about">About</a><a href="/contact">Contact</a></nav>
127
+ <!-- pages/about.html -->
128
+ <nav><a href="/">Home</a><a href="/contact">Contact</a><a href="/about">About</a></nav>
129
+ <!-- ^^^ order changed -->
130
+
131
+ <!-- Pattern — shared layout component, one source of truth -->
132
+ <!-- components/SiteHeader.jsx -->
133
+ export function SiteHeader() {
134
+ return (
135
+ <nav aria-label="Primary">
136
+ <a href="/">Home</a>
137
+ <a href="/about">About</a>
138
+ <a href="/contact">Contact</a>
139
+ </nav>
140
+ );
141
+ }
142
+ <!-- Used on every page → DOM order is identical → 3.2.3 clears site-wide. -->`},"wcc-redundant-entry-needs-site-crawl":{summary:`WCAG 3.3.7 Redundant Entry (Level A, WCAG 2.2) requires that information the user has already provided in a multi-step process not be requested again — unless it's essential (re-entering a password for confirmation is allowed). A single-page audit can't verify this because the redundancy is across pages/steps. To remediate:
143
+
144
+ 1. Identify every multi-step flow on the site (registration, checkout, multi-page forms, application wizards). For each: list the form fields requested on each step.
145
+
146
+ 2. Look for fields that appear on Step N AND Step M (N ≠ M) with the same semantic meaning (email address, name, phone, address, etc.).
147
+
148
+ 3. Fix by pre-filling the second occurrence from the first OR by making it auto-fill-friendly (correct autocomplete attributes + name attributes so the browser fills it).
149
+
150
+ Allowed redundancy: password confirmation (essential), security challenges (essential), inputs where the user is correcting their own previously-entered value (the user is the cause, not the system). NOT allowed: re-typing the same email on a confirmation step, re-entering a shipping address that was already collected, re-typing a phone number from one step to the next.`,snippetLang:"html",snippet:`<!-- Step 1: collect contact info -->
151
+ <input type="email" name="email" autocomplete="email" required>
152
+ <input type="tel" name="phone" autocomplete="tel" required>
153
+
154
+ <!-- Step 2 — REDUNDANT: asks for the same email again -->
155
+ <!-- Anti-pattern -->
156
+ <label>Confirm your email address</label>
157
+ <input type="email" name="email_step2" required>
158
+
159
+ <!-- Pattern — pre-fill from step 1, let user edit if needed -->
160
+ <label>Email address (from previous step — edit if needed)</label>
161
+ <input type="email" name="email" value="\${user.email}" autocomplete="email" required>`},"wcc-title-tooltip-no-persistence":{summary:"WCAG 1.4.13 Content on Hover or Focus requires that hover-revealed content be DISMISSIBLE (Esc closes it), HOVERABLE (the user can move the pointer over it without it disappearing), and PERSISTENT (it doesn't auto-hide after a timeout unless the user dismisses it). The native HTML `title` attribute fails all three on most browsers — the tooltip vanishes the moment the pointer moves. Replace `title` with a proper tooltip component (Radix UI, ARIA Authoring Practices popover pattern, or a `<details>` summary with visible label) that satisfies all three behaviors.",snippetLang:"html",snippet:`<!-- Failing — native title attribute -->
162
+ <button title="Saves your work and exits the editor">Save & exit</button>
163
+ <!-- The tooltip flashes briefly and disappears on pointer move. -->
164
+
165
+ <!-- Passing — visible label as the primary affordance -->
166
+ <button>
167
+ Save & exit
168
+ <span class="hint">Saves your work and exits the editor</span>
169
+ </button>
170
+
171
+ <!-- Passing — Radix tooltip (or any APG popover pattern) -->
172
+ <Tooltip.Root>
173
+ <Tooltip.Trigger asChild>
174
+ <button aria-describedby="save-hint">Save & exit</button>
175
+ </Tooltip.Trigger>
176
+ <Tooltip.Content id="save-hint">
177
+ Saves your work and exits the editor
178
+ </Tooltip.Content>
179
+ </Tooltip.Root>`},"wcc-reflow-horizontal-scroll":{summary:"WCAG 1.4.10 — Page produced horizontal scroll at narrow viewport (≤360 CSS pixels). The spec requires content to fit within 320px without two-dimensional scrolling. Common causes: fixed-width container exceeding viewport, images without max-width:100%, long unbroken strings (URLs, code identifiers), wide tables without overflow wrappers, third-party widgets ignoring container constraints. Fix: identify the offending element (DevTools at 320px viewport, find widest descendant), then apply max-width:100% (images/iframes), overflow-x:auto wrapper (tables/code blocks), overflow-wrap:anywhere (long strings), or responsive width (fixed-width containers). WCAG allows individual widgets that REQUIRE two-dimensional layout (maps, data viz, gantt charts) to scroll inside their own wrapper — the page itself must not.",snippet:`/* Common fixes */
180
+ img, video, iframe { max-width: 100%; height: auto; }
181
+ .wide-table-wrapper { overflow-x: auto; max-width: 100%; }
182
+ pre { overflow-x: auto; }
183
+ .long-url { overflow-wrap: anywhere; word-break: break-word; }
184
+ .container { max-width: 1200px; width: 100%; margin: 0 auto; }`,snippetLang:"css"},label:{summary:'WCAG 1.3.1, 3.3.2 — Form control has no programmatically-determined label. Screen-reader users hear "edit, blank" instead of what to type. Fix: associate a `<label>` with the input via `for=` attribute pointing at the input\'s `id`, OR wrap the input inside the `<label>`, OR add `aria-label` / `aria-labelledby` when the label is visually elsewhere (modal title, table header). Avoid `placeholder=` as the only label — placeholders disappear on focus.',snippet:`<!-- Best: explicit label/for -->
185
+ <label for="email">Email address</label>
186
+ <input id="email" type="email">
187
+
188
+ <!-- Acceptable: implicit (wrapped) label -->
189
+ <label>
190
+ Phone
191
+ <input type="tel">
192
+ </label>
193
+
194
+ <!-- Acceptable when label is elsewhere -->
195
+ <input type="search" aria-label="Search the catalog">`,snippetLang:"html"},"label-content-name-mismatch":{summary:`WCAG 2.5.3 — The control's accessible name (aria-label) does NOT contain its visible text. Speech-recognition users saying "click [visible text]" can't activate the control. Fix: ensure the accessible name STARTS WITH the visible text. If a longer description is needed, use aria-describedby for the additional info, OR set aria-label to "[visible-text] — [extra context]".`,snippet:`<!-- Failing -->
196
+ <button aria-label="Close form">Cancel</button>
197
+
198
+ <!-- Passing — visible text is contained in accessible name -->
199
+ <button aria-label="Cancel — close the form">Cancel</button>
200
+
201
+ <!-- Better: just remove the aria-label; visible text IS the name -->
202
+ <button>Cancel</button>`,snippetLang:"html"},"label-title-only":{summary:"WCAG 1.3.1, 3.3.2 — Form control uses `title=` as its only label. Tooltips are inconsistent across browsers + screen readers + touch (no hover on mobile). Fix: replace with a visible `<label>` or, if no visible label is feasible, `aria-label`.",snippet:`<!-- Failing -->
203
+ <input type="search" title="Search the catalog">
204
+
205
+ <!-- Passing -->
206
+ <label for="q">Search</label>
207
+ <input id="q" type="search">`,snippetLang:"html"},"aria-required-children":{summary:'WCAG 1.3.1 — An ARIA role REQUIRES specific child roles, and one or more are missing. E.g., `role="list"` requires `role="listitem"` children; `role="tablist"` requires `role="tab"` children; `role="menu"` requires `role="menuitem"`. Fix: add the required child elements OR change the parent role to one that doesn\'t require those children OR remove the parent role entirely if native semantics (`<ul>`, `<select>`) already produce the right tree.',snippet:`<!-- Failing — role="list" without listitems -->
208
+ <div role="list">
209
+ <p>Item</p>
210
+ </div>
211
+
212
+ <!-- Passing — explicit listitem children -->
213
+ <div role="list">
214
+ <div role="listitem">Item</div>
215
+ </div>
216
+
217
+ <!-- Better: native -->
218
+ <ul>
219
+ <li>Item</li>
220
+ </ul>`,snippetLang:"html"},"aria-required-parent":{summary:'WCAG 1.3.1 — Element with an ARIA role REQUIRES a specific parent role. E.g., `role="tab"` requires parent `role="tablist"`; `role="treeitem"` requires `role="tree"`; `role="menuitem"` requires `role="menu"` or `role="menubar"`. Fix: wrap the element in the correct parent role, OR remove the role from this element and use a different pattern.',snippet:`<!-- Failing — orphan tab -->
221
+ <div role="tab">Tab 1</div>
222
+
223
+ <!-- Passing — wrapped in tablist -->
224
+ <div role="tablist">
225
+ <div role="tab" aria-controls="panel-1">Tab 1</div>
226
+ </div>`,snippetLang:"html"},"aria-valid-attr-value":{summary:'WCAG 4.1.2 — An ARIA attribute has an invalid value. Common: `aria-labelledby="non-existent-id"`, `aria-controls="missing"`, `aria-pressed="yes"` (must be true/false), `aria-expanded="undefined"`. Fix: ensure the value matches the spec for that attribute. Boolean attrs take `true`/`false`; ID-reference attrs require an element with that id to exist in the DOM.',snippet:`<!-- Failing -->
227
+ <button aria-pressed="yes">Toggle</button>
228
+ <button aria-labelledby="missing-id">Open</button>
229
+
230
+ <!-- Passing -->
231
+ <button aria-pressed="true">Toggle</button>
232
+ <button aria-labelledby="dialog-title">Open</button>
233
+ <h2 id="dialog-title">Settings</h2>`,snippetLang:"html"},"aria-hidden-focus":{summary:'WCAG 4.1.2 — Element with `aria-hidden="true"` contains focusable content. Screen-reader users can\'t see the element, but Tab still lands on its children — confusing + unusable. Fix: either remove the `aria-hidden`, OR add `tabindex="-1"` + `inert` to all focusable descendants, OR restructure so the hidden region has no focusable children.',snippet:`<!-- Failing -->
234
+ <div aria-hidden="true">
235
+ <button>Hidden button (Tab still lands here)</button>
236
+ </div>
237
+
238
+ <!-- Passing — inert blocks focus + click -->
239
+ <div aria-hidden="true" inert>
240
+ <button>Not reachable</button>
241
+ </div>`,snippetLang:"html"},"aria-hidden-body":{summary:'WCAG 4.1.2 — The `<body>` element has `aria-hidden="true"`, hiding the entire page from assistive tech. Almost always a bug (often left over from modal-management code that forgot to restore). Fix: remove `aria-hidden` from `<body>`. If you need to hide page content behind a modal, use `inert` on the background or `aria-hidden="true"` only on the modal\'s sibling containers.',snippet:`<!-- Failing -->
242
+ <body aria-hidden="true">...</body>
243
+
244
+ <!-- Passing — modal pattern -->
245
+ <body>
246
+ <main inert>...</main>
247
+ <div role="dialog" aria-modal="true">...</div>
248
+ </body>`,snippetLang:"html"},"aria-allowed-role":{summary:'WCAG 4.1.2 — An element has a `role=` attribute that conflicts with its native semantics. E.g., `<button role="link">` (use `<a>` instead), `<h1 role="paragraph">` (semantic loss). Fix: remove the `role` attribute if the native semantics are correct, OR change the element type to match the desired role.',snippet:`<!-- Failing -->
249
+ <button role="link">Go to home</button>
250
+
251
+ <!-- Passing -->
252
+ <a href="/">Go to home</a>
253
+
254
+ <!-- Or: button styled to look like a link -->
255
+ <button class="link-style" onclick="...">Action</button>`,snippetLang:"html"},"aria-prohibited-attr":{summary:'WCAG 4.1.2 — An ARIA attribute is used on an element that doesn\'t support it. E.g., `aria-label` on a `<span>` with no role, `aria-haspopup` on `<input type="text">`. Fix: either remove the prohibited attribute, OR add a role that allows it, OR change to an element that supports the attribute natively.',snippet:`<!-- Failing -->
256
+ <span aria-label="Logo">XYZ</span>
257
+
258
+ <!-- Passing -->
259
+ <img src="logo.svg" alt="XYZ logo">
260
+ <!-- or -->
261
+ <div role="img" aria-label="XYZ logo">XYZ</div>`,snippetLang:"html"},region:{summary:'WCAG 1.3.1 — Content sits outside any landmark region. Screen-reader users can\'t skip to "main content" because no `<main>` (or `role="main"`) wraps it. Fix: wrap top-level page sections in landmark elements — `<header>`, `<nav>`, `<main>`, `<aside>`, `<footer>` — or their ARIA equivalents.',snippet:`<!-- Failing -->
262
+ <body>
263
+ <div class="header">...</div>
264
+ <div class="content">...</div>
265
+ </body>
266
+
267
+ <!-- Passing -->
268
+ <body>
269
+ <header>...</header>
270
+ <main>...</main>
271
+ <footer>...</footer>
272
+ </body>`,snippetLang:"html"},bypass:{summary:'WCAG 2.4.1 — Page lacks a "skip to main content" mechanism for keyboard users. Without it, every keyboard user re-tabs through the entire nav on every page. Fix: add a skip-link as the first focusable element. Use CSS to visually hide it until focused.',snippet:`<!-- HTML -->
273
+ <a href="#main-content" class="skip-link">Skip to main content</a>
274
+ <header>...</header>
275
+ <main id="main-content">...</main>
276
+
277
+ <!-- CSS -->
278
+ .skip-link {
279
+ position: absolute;
280
+ left: -9999px;
281
+ }
282
+ .skip-link:focus {
283
+ left: 0;
284
+ top: 0;
285
+ z-index: 999;
286
+ padding: 0.5em 1em;
287
+ background: #000;
288
+ color: #fff;
289
+ }`,snippetLang:"html"},"page-has-heading-one":{summary:"WCAG 1.3.1 — Page has no `<h1>`. Screen-reader users use the H1 to identify the page's primary topic + as a navigation anchor. Fix: add exactly ONE `<h1>` to the page describing its primary content (typically near the top of `<main>`). Don't use `<h1>` for the site name on every page — that belongs in `<header>` as a `<p>` or styled span.",snippet:`<!-- Failing — site name is the only H1 -->
290
+ <header><h1>Acme Corp</h1></header>
291
+ <main>
292
+ <h2>Product details</h2>
293
+ </main>
294
+
295
+ <!-- Passing -->
296
+ <header><a href="/"><img src="logo.svg" alt="Acme Corp"></a></header>
297
+ <main>
298
+ <h1>iPhone 15 Pro Max — 256GB</h1>
299
+ </main>`,snippetLang:"html"},"landmark-unique":{summary:'WCAG 1.3.1 — Multiple landmarks of the same type with no distinguishing accessible name. Screen-reader users hear "navigation, navigation, navigation" with no way to tell them apart. Fix: add `aria-label` (or `aria-labelledby`) to each landmark with the same role, describing its purpose.',snippet:`<!-- Failing -->
300
+ <nav>Primary nav</nav>
301
+ <nav>Footer nav</nav>
302
+
303
+ <!-- Passing -->
304
+ <nav aria-label="Primary">...</nav>
305
+ <nav aria-label="Footer">...</nav>`,snippetLang:"html"},tabindex:{summary:'WCAG 2.4.3 — Element has a positive `tabindex` value (e.g., `tabindex="1"`), creating a parallel focus order that\'s a maintenance nightmare + confuses users. Fix: use only `tabindex="0"` (to add to natural order) or `tabindex="-1"` (to make programmatically focusable but skipped by Tab). NEVER use positive values.',snippet:`<!-- Failing -->
306
+ <button tabindex="1">First</button>
307
+ <button tabindex="2">Second</button>
308
+
309
+ <!-- Passing — natural DOM order -->
310
+ <button>First</button>
311
+ <button>Second</button>`,snippetLang:"html"},"focus-order-semantics":{summary:'WCAG 1.3.1, 2.4.3 — Element has `tabindex` making it focusable but has no role, so screen readers announce just "focusable, blank." Fix: either add a proper role (`role="button"`, `role="link"`, etc.) + Enter/Space handler, OR use the correct native element (`<button>`, `<a>`), OR remove the `tabindex` if the element shouldn\'t be focusable.',snippet:`<!-- Failing -->
312
+ <div tabindex="0" onclick="open()">Open</div>
313
+
314
+ <!-- Passing -->
315
+ <button type="button" onclick="open()">Open</button>`,snippetLang:"html"},"no-focusable-content":{summary:'WCAG 2.1.1 — Element has an interactive ARIA role (`role="button"`, `role="link"`) but isn\'t focusable. Keyboard users can\'t reach it. Fix: add `tabindex="0"` AND a keyboard handler (Enter/Space → activate). Better: replace with a native interactive element.',snippet:`<!-- Failing -->
316
+ <div role="button" onclick="run()">Run</div>
317
+
318
+ <!-- Passing — tabindex + keyboard handler -->
319
+ <div role="button" tabindex="0" onclick="run()" onkeydown="if(event.key==='Enter'||event.key===' '){event.preventDefault();run();}">Run</div>
320
+
321
+ <!-- Best -->
322
+ <button type="button" onclick="run()">Run</button>`,snippetLang:"html"},"frame-title":{summary:"WCAG 4.1.2, 2.4.1 — `<iframe>` or `<frame>` has no `title` attribute. Screen-reader users hear \"frame\" with no idea what's inside. Fix: add a descriptive `title` attribute summarizing the iframe's purpose.",snippet:`<!-- Failing -->
323
+ <iframe src="https://maps.example.com/embed?location=hq"></iframe>
324
+
325
+ <!-- Passing -->
326
+ <iframe src="..." title="Map showing our headquarters location"></iframe>`,snippetLang:"html"},"area-alt":{summary:'WCAG 1.1.1 — Image map `<area>` element has no alt text. Screen-reader users hear "link" with no destination context. Fix: add an `alt=` attribute describing where the link goes / what region it represents.',snippet:`<!-- Failing -->
327
+ <img src="map.png" usemap="#regions">
328
+ <map name="regions">
329
+ <area shape="rect" coords="0,0,100,100" href="/north">
330
+ </map>
331
+
332
+ <!-- Passing -->
333
+ <map name="regions">
334
+ <area shape="rect" coords="0,0,100,100" href="/north" alt="North region">
335
+ </map>`,snippetLang:"html"},"object-alt":{summary:"WCAG 1.1.1 — `<object>` element has no fallback text. Screen readers (and browsers that can't render the object) have nothing to convey. Fix: include text content INSIDE the `<object>` describing what it represents — that text serves as the alt + the fallback.",snippet:`<!-- Failing -->
336
+ <object data="chart.svg" type="image/svg+xml"></object>
337
+
338
+ <!-- Passing -->
339
+ <object data="chart.svg" type="image/svg+xml">
340
+ Q4 revenue chart: $4.2M, up 18% YoY.
341
+ </object>`,snippetLang:"html"},"svg-img-alt":{summary:'WCAG 1.1.1 — `<svg>` element with `role="img"` lacks an accessible name. Fix: add `<title>` as the FIRST child of the SVG (screen readers read it as the name), OR use `aria-label` / `aria-labelledby`.',snippet:`<!-- Failing -->
342
+ <svg role="img"><path d="..."/></svg>
343
+
344
+ <!-- Passing -->
345
+ <svg role="img"><title>Search</title><path d="..."/></svg>
346
+
347
+ <!-- Or -->
348
+ <svg role="img" aria-label="Search"><path d="..."/></svg>
349
+
350
+ <!-- Decorative SVG -->
351
+ <svg aria-hidden="true"><path d="..."/></svg>`,snippetLang:"html"},"role-img-alt":{summary:'WCAG 1.1.1 — Element with `role="img"` lacks an accessible name. Without it, SR users hear "image, blank." Fix: add `aria-label` or `aria-labelledby` describing the image\'s content / function.',snippet:`<!-- Failing -->
352
+ <div role="img">★★★★☆</div>
353
+
354
+ <!-- Passing -->
355
+ <div role="img" aria-label="Rated 4 out of 5 stars">★★★★☆</div>`,snippetLang:"html"},"color-contrast-enhanced":{summary:'WCAG 1.4.6 (AAA) — Text contrast is below the AAA threshold (7:1 for normal text, 4.5:1 for large text). AAA is the "enhanced" tier — fix by raising contrast to AAA OR explicitly declaring AA-only conformance (drop this rule from the audit). Same fix mechanics as `color-contrast` but with higher contrast thresholds.',snippet:`/* Same approach as color-contrast — raise foreground darkness or
356
+ background lightness until the computed ratio meets 7:1 (or 4.5:1
357
+ for text >=24px / 19px bold). */`,snippetLang:"css"},"link-in-text-block":{summary:"WCAG 1.4.1 — Inline link inside a paragraph is distinguished from surrounding text by color alone. Color-blind users can't see the difference. Fix: add a non-color affordance — underline (most common), bold weight, icon, or background color. Underline is the conventional choice and what browsers do by default.",snippet:`/* Failing — same family + color only */
358
+ p { color: #333; }
359
+ p a { color: #06f; text-decoration: none; }
360
+
361
+ /* Passing — underline added */
362
+ p a { color: #06f; text-decoration: underline; }`,snippetLang:"css"}};function jh(e){return at[e]??null}const za="aiColorSuggestions";async function ra(){try{const t=(await chrome.storage.local.get(za))[za];return t&&typeof t=="object"?t:{}}catch{return{}}}async function Qo(e){await chrome.storage.local.set({[za]:e})}async function zh(e){const t=await ra();t[e.matchKey]=e,await Qo(t)}async function Hh(e){if(e.length===0)return;const t=await ra();for(const a of e)t[a.matchKey]=a;await Qo(t)}async function Bh(e){return(await ra())[e]??null}async function Bs(){return await ra()}const Zo={AT:{name:"Austria — Beschwerdestelle Bundessozialamt",url:"https://www.sozialministeriumservice.at/"},BE:{name:"Belgium — Unia / Bosa",url:"https://accessibility.belgium.be/"},BG:{name:"Bulgaria — State e-Government Agency",url:"https://e-gov.bg/"},CY:{name:"Cyprus — Department of Electronic Communications",url:"https://www.mtcw.gov.cy/"},CZ:{name:"Czechia — Ministry of the Interior",url:"https://www.mvcr.cz/"},DE:{name:"Germany — Bundesfachstelle Barrierefreiheit",url:"https://www.bundesfachstelle-barrierefreiheit.de/"},DK:{name:"Denmark — Digitaliseringsstyrelsen",url:"https://www.digst.dk/"},EE:{name:"Estonia — Ministry of Economic Affairs and Communications",url:"https://mkm.ee/"},ES:{name:"Spain — Observatorio de Accesibilidad Web",url:"https://administracionelectronica.gob.es/"},FI:{name:"Finland — Etelä-Suomen aluehallintovirasto",url:"https://www.saavutettavuusvaatimukset.fi/"},FR:{name:"France — Défenseur des droits",url:"https://www.defenseurdesdroits.fr/"},GR:{name:"Greece — Ministry of Digital Governance",url:"https://mindigital.gr/"},HR:{name:"Croatia — Information Commissioner",url:"https://www.pristupinfo.hr/"},HU:{name:"Hungary — Information Society Authority",url:"https://nmhh.hu/"},IE:{name:"Ireland — National Disability Authority",url:"https://nda.ie/"},IT:{name:"Italy — Agenzia per l'Italia Digitale",url:"https://www.agid.gov.it/"},LT:{name:"Lithuania — Information Society Development Committee",url:"https://ivpk.lrv.lt/"},LU:{name:"Luxembourg — Service Information et Presse",url:"https://accessibilite.public.lu/"},LV:{name:"Latvia — VARAM",url:"https://www.varam.gov.lv/"},MT:{name:"Malta — MITA",url:"https://www.mita.gov.mt/"},NL:{name:"Netherlands — Logius",url:"https://www.digitoegankelijk.nl/"},PL:{name:"Poland — Ministry of Digital Affairs",url:"https://www.gov.pl/web/dostepnosc-cyfrowa/"},PT:{name:"Portugal — AMA",url:"https://accessmonitor.acessibilidade.gov.pt/"},RO:{name:"Romania — ANCOM",url:"https://www.ancom.ro/"},SE:{name:"Sweden — DIGG",url:"https://www.digg.se/"},SI:{name:"Slovenia — Ministry of Public Administration",url:"https://www.gov.si/"},SK:{name:"Slovakia — Ministry of Investments",url:"https://www.mirri.gov.sk/"}},ei=(e,t)=>`${e} is committed to making its website accessible, in accordance with national legislation transposing Directive (EU) 2016/2102 of the European Parliament and of the Council. This accessibility statement applies to ${t}.`,ti=(e,t)=>`${e} is committed to making its products and services accessible to all users, in accordance with the European Accessibility Act (Directive (EU) 2019/882) and its national transpositions, enforceable from 28 June 2025. This accessibility statement applies to ${t}.`,ai=(e,t,a,n="EN 301 549 v3.2.1")=>{const o=`Web Content Accessibility Guidelines (WCAG) ${t} level ${a}`;switch(e){case"full":return`This website is fully conformant with ${o} and the corresponding ${n} clauses.`;case"partial":return`This website is partially conformant with ${o} and the corresponding ${n} clauses. "Partially conformant" means that some parts of the content do not fully conform to the accessibility standard. The non-accessible content is listed below.`;case"non-conformant":return`This website is currently NOT conformant with ${o}. The non-accessible content is listed below, along with our plan to address it.`}},ni=(e,t)=>`The accessibility of this website was last evaluated on ${t}. The evaluation was conducted by ${e} using automated testing (wcagcheckr — multi-state matrix audit across 144 keyboard / focus / theme / breakpoint / direction combinations, AI-augmented walkthroughs for 5 criteria axe-core cannot evaluate, plus DOM-criterion analyzers for 16 additional criteria). The audit is conformant with the EN 301 549 evaluation methodology.`;function Ks(e,t){const a=[];if(a.push(`# Accessibility Statement — ${e.organizationName}`),a.push(""),a.push(`*Statement prepared on ${e.preparedAt}. Language: ${e.language}.*`),a.push(""),a.push(e.framing==="wad"?ei(e.organizationName,e.siteUrl):ti(e.organizationName,e.siteUrl)),a.push(""),a.push("## Conformance status"),a.push(""),a.push(ai(e.conformanceStatus,e.wcagVersion,e.wcagLevel)),a.push(""),t.length>0){a.push("## Non-accessible content"),a.push(""),a.push("The following content is not yet accessible:"),a.push("");for(const n of t){const o=n.exemption?n.exemption==="disproportionate-burden"?" *(claimed under disproportionate burden — Article 5 WAD / Article 14 EAA)*":" *(not subject to applicable legislation)*":"";a.push(`- **WCAG ${n.criterion}** — ${n.description}${o}`)}a.push("")}else e.conformanceStatus==="full"&&(a.push("## Non-accessible content"),a.push(""),a.push("No non-accessible content is currently known. If you encounter any accessibility barriers, please contact us using the feedback details below."),a.push(""));if(a.push("## Preparation of this accessibility statement"),a.push(""),a.push(ni(e.auditedBy,e.evaluatedAt)),a.push(""),a.push("## Feedback and contact information"),a.push(""),a.push(`If you experience accessibility barriers on ${e.siteUrl}, please contact us so we can address them:`),a.push(""),e.contact.email&&a.push(`- **Email:** ${e.contact.email}`),e.contact.phone&&a.push(`- **Phone:** ${e.contact.phone}`),e.contact.formUrl&&a.push(`- **Web form:** ${e.contact.formUrl}`),a.push(""),e.framing==="wad"&&e.countryCode){const n=Zo[e.countryCode];n&&(a.push("## Enforcement procedure"),a.push(""),a.push(`If you are not satisfied with the response to your accessibility complaint, you may contact the national enforcement body for ${n.name}:`),a.push(""),a.push(`${n.url}`),a.push(""))}return e.framing==="eaa"&&(a.push("## Enforcement"),a.push(""),a.push("Enforcement of the European Accessibility Act is carried out by national market-surveillance authorities in each EU member state. If your accessibility complaint is not addressed satisfactorily, you may contact the market-surveillance authority of the EU member state in which you are located."),a.push("")),a.push("---"),a.push(""),a.push(`_Statement generated by wcagcheckr. Audit methodology details + cryptographic anchoring of the audit results are available on request from ${e.contact.email??"the contact above"}._`),a.join(`
363
+ `)}function Ys(e,t){const a=o=>o.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;"),n=[];if(n.push(`<article lang="${a(e.language)}">`),n.push(`<h1>Accessibility Statement — ${a(e.organizationName)}</h1>`),n.push(`<p><em>Statement prepared on ${a(e.preparedAt)}. Language: ${a(e.language)}.</em></p>`),n.push(`<p>${a(e.framing==="wad"?ei(e.organizationName,e.siteUrl):ti(e.organizationName,e.siteUrl))}</p>`),n.push("<h2>Conformance status</h2>"),n.push(`<p>${a(ai(e.conformanceStatus,e.wcagVersion,e.wcagLevel))}</p>`),t.length>0){n.push("<h2>Non-accessible content</h2>"),n.push("<p>The following content is not yet accessible:</p>"),n.push("<ul>");for(const o of t){const i=o.exemption?o.exemption==="disproportionate-burden"?" <em>(claimed under disproportionate burden — Article 5 WAD / Article 14 EAA)</em>":" <em>(not subject to applicable legislation)</em>":"";n.push(`<li><strong>WCAG ${a(o.criterion)}</strong> — ${a(o.description)}${i}</li>`)}n.push("</ul>")}else e.conformanceStatus==="full"&&(n.push("<h2>Non-accessible content</h2>"),n.push("<p>No non-accessible content is currently known. If you encounter any accessibility barriers, please contact us using the feedback details below.</p>"));if(n.push("<h2>Preparation of this accessibility statement</h2>"),n.push(`<p>${a(ni(e.auditedBy,e.evaluatedAt))}</p>`),n.push("<h2>Feedback and contact information</h2>"),n.push(`<p>If you experience accessibility barriers on ${a(e.siteUrl)}, please contact us so we can address them:</p>`),n.push("<ul>"),e.contact.email&&n.push(`<li><strong>Email:</strong> <a href="mailto:${a(e.contact.email)}">${a(e.contact.email)}</a></li>`),e.contact.phone&&n.push(`<li><strong>Phone:</strong> ${a(e.contact.phone)}</li>`),e.contact.formUrl&&n.push(`<li><strong>Web form:</strong> <a href="${a(e.contact.formUrl)}">${a(e.contact.formUrl)}</a></li>`),n.push("</ul>"),e.framing==="wad"&&e.countryCode){const o=Zo[e.countryCode];o&&(n.push("<h2>Enforcement procedure</h2>"),n.push(`<p>If you are not satisfied with the response to your accessibility complaint, you may contact the national enforcement body for ${a(o.name)}: <a href="${a(o.url)}">${a(o.url)}</a></p>`))}return e.framing==="eaa"&&(n.push("<h2>Enforcement</h2>"),n.push("<p>Enforcement of the European Accessibility Act is carried out by national market-surveillance authorities in each EU member state. If your accessibility complaint is not addressed satisfactorily, you may contact the market-surveillance authority of the EU member state in which you are located.</p>")),n.push("<hr>"),n.push(`<p><small>Statement generated by wcagcheckr. Audit methodology details + cryptographic anchoring of the audit results are available on request from ${a(e.contact.email??"the contact above")}.</small></p>`),n.push("</article>"),n.join(`
364
+ `)}function Js(e,t={},a=new Set){var c;const n=e.flatMap(l=>l.violations),o=new Map;for(const l of n){const d=xe(l.ruleId,l.target.selector);o.has(d)||o.set(d,l)}const i=new Map;for(const l of o.values()){const d=l.wcagCriterion;if(!d||a.has(d))continue;const u=i.get(d)??[];u.push(l),i.set(d,u)}const r=[];for(const[l,d]of i.entries()){const u=d[0];let h=t[l];if(!h){const m=at[u.ruleId];m!=null&&m.summary?h=((c=m.summary.split(/[.!?]\s+/)[0])==null?void 0:c.trim())??u.description:h=u.description}d.length>1&&(h+=` (affects ${d.length} components on the audited page${e.length>1?"s":""})`),r.push({criterion:l,description:h})}return r.sort((l,d)=>{const u=f=>f.split(".").map(g=>parseInt(g,10)),h=u(l.criterion),m=u(d.criterion);for(let f=0;f<Math.max(h.length,m.length);f++){const g=(h[f]??0)-(m[f]??0);if(g!==0)return g}return 0}),r}function Xs(e){const t=Js(e.results,e.customDescriptions,e.dismissedCriteria);return e.outputFormat==="html"?Ys(e.inputs,t):Ks(e.inputs,t)}function Kh(e){if(e.reduce((n,o)=>n+o.violations.length,0)===0)return"full";const a=new Set;for(const n of e)for(const o of n.violations)o.wcagCriterion&&a.add(o.wcagCriterion);return a.size>=10?"non-conformant":"partial"}function _t(e){return/\[Cascade resolution — rc\.\d+ escalation\]/.test(e)}function Yh(e){return/\[Manual verification — rc\.\d+\]/.test(e)}function Jh(e){const t=e.match(/\[Cascade resolution — rc\.\d+ (fallback-\d|escalation)\]/);if(!t)return null;const a=t[1];return a==="fallback-1"||a==="fallback-2"||a==="escalation"?a:null}const Qs="wcag-forensic-log",Zs=1,Pe="audits";let ka=null;function sa(){return ka||(ka=$o(Qs,Zs,{upgrade(e){if(!e.objectStoreNames.contains(Pe)){const t=e.createObjectStore(Pe,{keyPath:["componentId","capturedAt"]});t.createIndex("byCapturedAt","capturedAt"),t.createIndex("byComponentId","componentId")}}})),ka}function Ha(e){return e===null||typeof e!="object"?JSON.stringify(e):Array.isArray(e)?"["+e.map(n=>Ha(n)).join(",")+"]":"{"+Object.keys(e).sort().map(n=>{const o=e[n];return JSON.stringify(n)+":"+Ha(o)}).join(",")+"}"}async function ec(e){const t=Ha(e),a=new TextEncoder().encode(t),n=await crypto.subtle.digest("SHA-256",a);return Array.from(new Uint8Array(n)).map(o=>o.toString(16).padStart(2,"0")).join("")}async function oi(e,t,a){if(e.length===0)return null;const n=e[0],o=n.componentId,i=n.pageUrl??n.scope,r=n.startedAt,c={componentId:o,pageUrl:i,scope:n.scope,grade:t.letter,totals:{critical:t.totals.critical,serious:t.totals.serious,moderate:t.totals.moderate,minor:t.totals.minor,unique:t.totals.unique},axeVersion:n.axeVersion,capturedAt:r,statesAudited:e.length},d={hash:await ec(c),componentId:o,pageUrl:i,scope:n.scope,grade:t.letter,totals:c.totals,axeVersion:n.axeVersion,capturedAt:r,statesAudited:e.length,durationMs:a};try{await(await sa()).put(Pe,d)}catch(u){return console.warn("[forensic-log] failed to record audit",u),d}return d}async function ii(e,t,a){try{const n=await sa(),o=await n.get(Pe,[e,t]);if(!o){console.warn("[forensic-log] no entry to attach receipt to",{componentId:e,capturedAt:t});return}o.receipt=a,await n.put(Pe,o)}catch(n){console.warn("[forensic-log] failed to attach receipt",n)}}async function At(){try{return(await(await sa()).getAll(Pe)).sort((a,n)=>a.capturedAt<n.capturedAt?1:-1)}catch(e){return console.warn("[forensic-log] listAudits failed",e),[]}}async function tc(e){try{return(await(await sa()).transaction(Pe).objectStore(Pe).index("byComponentId").getAll(IDBKeyRange.only(e))).sort((o,i)=>o.capturedAt<i.capturedAt?1:-1)}catch(t){return console.warn("[forensic-log] listAuditsForComponent failed",t),[]}}const ac={"color-contrast":{whatsWrong:"Some text on this page is too light to read against its background.",whyItMatters:"People with low vision, older eyes, or anyone reading on a sunny phone screen can't make out faint text. Color blindness affects about 1 in 12 men and 1 in 200 women. Insufficient contrast is the #1 most-common cause of accessibility lawsuits — every Domino's-style ADA suit cites it.",howToFix:"If you use a website builder (Squarespace, Wix, Shopify, WordPress with a theme), open your site styles or theme color settings and pick darker text colors or lighter backgrounds. Aim for very dark text on white, or very light text on dark. Free check at webaim.org/resources/contrastchecker. If you have a developer, ask them to ensure all text meets WCAG AA contrast (4.5:1 for normal text, 3:1 for large text)."},"color-contrast-enhanced":{whatsWrong:"Text on this page doesn't meet the higher (AAA) contrast level — important for people with significant low vision.",whyItMatters:"AAA is a stricter standard than the legal-minimum AA. If your audience includes older adults, people with cataracts or macular degeneration, or government-related contracts that require AAA conformance, this matters.",howToFix:"Same as basic contrast — go darker on text or lighter on backgrounds. AAA requires 7:1 for normal text and 4.5:1 for large text."},"link-in-text-block":{whatsWrong:"Links inside paragraphs are distinguishable from the surrounding text by color alone.",whyItMatters:"If a user is colorblind or sees in grayscale, they can't tell which words are clickable. WCAG requires another visual cue (underline, bold, icon) in addition to color.",howToFix:"Add an underline to links inside paragraphs. In most theme editors there's a 'link style' setting — turn underlines back on. The underline is the standard convention; it's also more discoverable for everyone."},"image-alt":{whatsWrong:"Some images on this page have no description for screen readers.",whyItMatters:"Blind and low-vision users rely on screen readers to read pages aloud. When an image has no description, the screen reader either skips it entirely or reads out the file name — useless. Missing alt text is the #2 lawsuit-magnet category.",howToFix:"In your site editor, click each image and look for an 'Alt text' or 'Image description' field. Write a short sentence describing what the image shows or what it links to. For purely decorative images (dividers, background flourishes), enter empty alt text or check a 'decorative' box if the platform offers one."},"input-image-alt":{whatsWrong:"Image-based form buttons (submit buttons that are images) have no description.",whyItMatters:"Without a description, screen-reader users can't tell what the button does — they hear 'button, submit.png' or just 'graphic'.",howToFix:"Replace image submit buttons with regular text buttons (most platforms let you do this in form settings), OR if you must use an image, ensure it has alt text describing the action ('Subscribe', 'Send message', 'Submit form')."},"area-alt":{whatsWrong:"An image map (clickable regions inside a single image) has unlabeled regions.",whyItMatters:"Each clickable area is a link — without a description, screen-reader users don't know what it points to.",howToFix:"Image maps are uncommon today. If you need one, your developer should add an alt attribute to each clickable area. The simpler modern approach: use multiple separate linked images or buttons instead."},"svg-img-alt":{whatsWrong:"Some SVG icons that act as images don't have a description.",whyItMatters:"SVG icons used decoratively are fine to leave silent, but icons that convey meaning (status indicators, social-media icons that link out) need a description so screen readers know what they represent.",howToFix:"In most platforms this requires a developer. Tell them: 'meaningful SVG icons need a title or aria-label; decorative SVGs need aria-hidden=true'. Examples: an envelope icon linking to 'Contact us' should be labeled 'Contact us', not silent."},"object-alt":{whatsWrong:"Embedded content (PDFs, videos, embedded apps) has no fallback description.",whyItMatters:"If the embed fails to load — or the user's screen reader can't navigate it — they need a text description of what the embed is.",howToFix:"Replace embedded objects with native modern alternatives where possible (HTML5 video instead of Flash, linked PDF with a description). If you must use an <object> embed, ask your developer to add fallback content describing what it is."},"role-img-alt":{whatsWrong:"An element marked as an image is missing its description.",whyItMatters:"Same as missing image alt — screen-reader users get nothing where they should hear a description.",howToFix:"Ask your developer: 'elements with role=img need aria-label or aria-labelledby'."},"image-redundant-alt":{whatsWrong:"Some images have alt text that duplicates nearby visible text.",whyItMatters:"Screen-reader users hear the same content twice — the surrounding text and then the alt text. Annoying, not blocking. Lower-priority fix.",howToFix:"Find images where the caption or nearby paragraph already says what the image shows, and either change the alt text to add new info or set the alt to empty (so the screen reader skips it as decorative)."},label:{whatsWrong:"Some form fields have no label that screen readers can announce.",whyItMatters:"Visually you can see what each field is for (next to a 'Name:' or 'Email:' text), but screen-reader users hear only 'edit text' if there's no programmatic label connecting the text to the field. The result: blind users can't tell what to type where.",howToFix:"In a website builder, every form field has a 'Label' field or similar. Make sure every input has one filled in. Placeholder text (the gray hint inside the field) does NOT count as a label — it has to be a real label."},"select-name":{whatsWrong:"A dropdown menu has no description.",whyItMatters:"Screen-reader users hear 'combo box, has popup' but not what the dropdown is for. They can't fill out forms reliably.",howToFix:"In your form editor, check that each dropdown has a visible label above or beside it. If your platform has separate label/field settings, both must be filled."},"aria-input-field-name":{whatsWrong:"A custom input control (search box, range slider, custom date picker) has no name.",whyItMatters:"Custom controls are common in modern site builders — they look fancy but break for screen-reader users when not labeled.",howToFix:"This is a developer fix. Ask: 'custom inputs with role=textbox / searchbox / slider / spinbutton need an aria-label or aria-labelledby'."},"aria-toggle-field-name":{whatsWrong:"A toggle switch (on/off control) has no description.",whyItMatters:"User can see the switch but can't tell what it controls without a label.",howToFix:"Developer fix: 'switches and checkboxes with role=switch need an accessible name'."},"form-field-multiple-labels":{whatsWrong:"A form field has multiple labels, which confuses screen readers.",whyItMatters:"Screen readers will read all labels — sometimes saying conflicting things — leaving users unsure what to enter.",howToFix:"Developer fix: 'this field has multiple <label> associations; pick one and remove the others'."},"label-content-name-mismatch":{whatsWrong:"A button's visible text doesn't match the description it announces to screen readers.",whyItMatters:"A user dictating 'click Submit' to voice-control software may see 'Submit' on screen but the announce-name is something else, so the voice command fails.",howToFix:"Developer fix: 'aria-label should start with or fully contain the visible button text'."},"autocomplete-valid":{whatsWrong:"A field that should auto-fill (name, email, phone, address) is missing the right autocomplete hint.",whyItMatters:"Browsers use these hints to auto-fill personal info, helping users with motor disabilities and saving everyone time. Older adults appreciate this most.",howToFix:"In your form builder, look for an 'Autocomplete' or 'Field type' setting per field. Set them to 'email', 'name', 'tel', 'street-address' etc. as appropriate."},"button-name":{whatsWrong:"Some buttons have no description that screen readers can announce.",whyItMatters:"Visual users see an icon (a heart, a trash can, an X), but screen-reader users hear only 'button' — they don't know if it'll favorite a post, delete it, or close a dialog. This is a critical accessibility blocker and a top lawsuit driver.",howToFix:"Every button needs a text label. If you can't put text inside the button (icon-only design), in your platform's button settings look for an 'Aria label' or 'Screen reader text' field — fill it with what the button does ('Close', 'Add to cart', 'Open menu')."},"input-button-name":{whatsWrong:"An <input type=submit> or similar button has no description.",whyItMatters:"Same as button-name — screen-reader users don't know what the button does.",howToFix:"Set the button's 'value' or 'label' to text describing the action ('Submit', 'Subscribe', 'Send')."},"link-name":{whatsWrong:"Some links have no readable text — usually icon-only links.",whyItMatters:"Screen-reader users hear 'link' but not where the link goes. Users navigate by listening to a list of links — useless if every entry is just 'link, link, link'.",howToFix:"In your site editor, for icon-only links (social-media icons, navigation arrows), set an 'Aria label' or 'Title' field describing where it goes ('Follow us on Twitter', 'Next page')."},"empty-button":{whatsWrong:"A button is completely empty — no text, no icon, no description.",whyItMatters:"Both visual and screen-reader users can't tell what an empty button does.",howToFix:"Find empty buttons in your editor (often a leftover from a deleted icon) and either delete them or add text/an icon + description."},"empty-link":{whatsWrong:"A link has no readable text or description.",whyItMatters:"Screen-reader users hear nothing useful; visual users see an empty space they can't tell is clickable.",howToFix:"Same as link-name — add link text or an aria-label describing the destination."},"empty-heading":{whatsWrong:"A heading on this page has no text.",whyItMatters:"Screen-reader users navigate pages by jumping between headings. An empty heading is dead air — they don't know what the section is about.",howToFix:"Find the empty heading in your editor (often a section title that was deleted but the heading element remained) and either delete it or add real heading text."},"document-title":{whatsWrong:"This page's title (in the browser tab) is missing or generic.",whyItMatters:"Tab titles help users who have many tabs open know which one is which. Screen readers also announce the page title when the page loads.",howToFix:"In your platform's page settings or SEO panel, find the 'Page title' field and set it to something descriptive ('Contact Us — YourBusiness', not 'Untitled' or just your business name)."},"html-has-lang":{whatsWrong:"The page doesn't declare what language it's in.",whyItMatters:"Screen readers use this to pick the right voice and pronunciation rules. Without it, English content might be read with a French accent (or vice versa).",howToFix:"In your platform's site settings, find a 'Site language' option and set it (English = en, Spanish = es, etc.). Most modern platforms do this automatically — if it's missing, ask your developer to add lang='en' to the html element."},"html-lang-valid":{whatsWrong:"The page declares a language code that isn't valid.",whyItMatters:"Same as missing language — screen readers can't pick the right voice.",howToFix:"Use a standard ISO language code: en, es, fr, de, ja, zh, pt, etc. Avoid full strings like 'english' — use the two-letter code."},"valid-lang":{whatsWrong:"Text marked as a different language uses an invalid code.",whyItMatters:"Same as above — screen readers can't switch voices for the foreign-language passage.",howToFix:"Use ISO codes (en, fr, es) for the lang attribute on individual elements. This typically requires a developer."},"page-has-heading-one":{whatsWrong:"This page is missing a main heading (H1).",whyItMatters:"Screen-reader users use the H1 to confirm they landed on the right page. The H1 is also a strong SEO signal — Google uses it to understand what the page is about.",howToFix:"Add a clear, descriptive H1 to the top of the page. In website builders, this is usually the 'Title' or 'Page heading' setting. Each page should have exactly one H1."},"heading-order":{whatsWrong:"Heading levels skip — for example, an H2 jumps directly to an H4 with no H3 between them.",whyItMatters:"Screen-reader users navigate by heading levels. Skipped levels suggest missing content and break the page's outline. Confusing for everyone.",howToFix:"In your editor, find the headings that skip levels and either bump them up to the right level (H4 → H3 if no H2 exists between) or insert the missing level. Headings should follow a logical outline like a document outline."},"landmark-one-main":{whatsWrong:"This page has no 'main content' region marked.",whyItMatters:"Screen-reader users press a single shortcut to jump to the main content, skipping menus and headers. Without a marked main region, they have to tab through everything.",howToFix:"Most modern platforms add this automatically. If yours doesn't, ask your developer to wrap the main content in a <main> element."},"landmark-no-duplicate-banner":{whatsWrong:"There are multiple top banners (headers) on the page when there should be one.",whyItMatters:"Screen-reader users get confused by duplicate landmarks — they don't know which is the 'real' header.",howToFix:"Developer fix: 'only one element should have role=banner or be a top-level <header>; remove duplicates'."},"landmark-no-duplicate-contentinfo":{whatsWrong:"There are multiple footer regions on the page when there should be one.",whyItMatters:"Same as duplicate banner — confusing landmark navigation.",howToFix:"Developer fix: 'only one element should have role=contentinfo or be a top-level <footer>; remove duplicates'."},region:{whatsWrong:"Some content on the page isn't inside a recognized region (header, nav, main, footer, aside).",whyItMatters:"Screen-reader users navigate by these regions. Content outside any region can be missed when skimming.",howToFix:"Developer fix: 'wrap orphaned page content in semantic landmark elements (<main>, <aside>, <nav>) so it's reachable via landmark nav'."},bypass:{whatsWrong:"There's no 'skip to main content' link at the top of the page.",whyItMatters:"Keyboard-only users (and some screen-reader users) have to tab through every menu item on every page before reaching the main content. A skip link gives them a single keystroke to jump past the menu.",howToFix:"Most modern themes include a skip link automatically — it's only visible when keyboard-focused. If yours doesn't, ask your developer to add one as the first focusable element on every page."},"aria-required-attr":{whatsWrong:"An element uses a screen-reader role but is missing required information for that role.",whyItMatters:"Custom controls (sliders, tabs, dialogs) need specific extra info to work properly with screen readers. Missing pieces = broken behavior.",howToFix:"Developer fix: 'this role requires additional aria-* attributes; check axe-core docs for the specific role'."},"aria-allowed-attr":{whatsWrong:"An element has a screen-reader hint that doesn't apply to its role.",whyItMatters:"Inappropriate hints can confuse or break screen-reader announcements.",howToFix:"Developer fix: 'this aria-* attribute isn't allowed on this role; remove it or change the role'."},"aria-roles":{whatsWrong:"An element claims to be a type that isn't valid (typo or misuse of a role).",whyItMatters:"Screen readers ignore invalid roles, leaving the element unannounced.",howToFix:"Developer fix: 'this role value isn't recognized; check spelling or use a valid role'."},"aria-valid-attr":{whatsWrong:"An element uses a screen-reader hint that doesn't exist (typo).",whyItMatters:"Same as invalid roles — typos are silently ignored.",howToFix:"Developer fix: 'this aria-* attribute name isn't recognized; check spelling'."},"presentation-role-conflict":{whatsWrong:"An element is marked as decorative but is also focusable or has a description — contradictory.",whyItMatters:"Screen readers handle this inconsistently; some announce it anyway, some don't, leaving users with mixed experiences.",howToFix:"Developer fix: 'role=presentation/none cannot coexist with focusability or aria-label'."},"duplicate-id":{whatsWrong:"Two or more elements on the page share the same internal ID.",whyItMatters:"Some screen reader and skip-link behaviors rely on unique IDs. Duplicates can break label associations and skip-link targets.",howToFix:"Developer fix: 'rename duplicate IDs so each is unique'. Often arises when copying components."},"duplicate-id-aria":{whatsWrong:"An ID referenced by a screen-reader hint exists multiple times.",whyItMatters:"Screen readers may pick the wrong one and announce wrong information.",howToFix:"Developer fix: 'IDs used in aria-labelledby / aria-describedby must be unique'."},"meta-viewport":{whatsWrong:"The page tells mobile browsers users can't zoom in.",whyItMatters:"People with low vision rely on pinch-zoom to read small text. Disabling zoom is a real WCAG failure and a common lawsuit target.",howToFix:"In platform site settings, look for a 'Mobile zoom' or 'Pinch zoom' option and enable it. If your developer added user-scalable=no in a viewport meta tag, ask them to remove it."},"nested-interactive":{whatsWrong:"An interactive element (link, button) is inside another interactive element.",whyItMatters:"Keyboard and screen-reader users can't reliably reach the inner element. Common in 'card-as-link' designs where a button sits inside a clickable card.",howToFix:"Developer fix: 'avoid putting buttons inside links or links inside buttons; use a single interactive element with internal helpers'."},tabindex:{whatsWrong:"Some elements have positive tabindex values, forcing a custom focus order.",whyItMatters:"Custom focus orders are nearly always confusing — focus jumps around unexpectedly. Best practice is to never use positive tabindex.",howToFix:"Developer fix: 'replace tabindex=1/2/3... with tabindex=0 (or remove entirely); use DOM order to control focus order'."},"frame-focusable-content":{whatsWrong:"A frame on the page can't be navigated by keyboard.",whyItMatters:"Keyboard users get stuck — they can't reach interactive content inside the frame.",howToFix:"Developer fix: 'iframes containing interactive content shouldn't have tabindex=-1 on their parent or content'."},"no-focusable-content":{whatsWrong:"An element looks interactive but can't actually receive keyboard focus.",whyItMatters:"Keyboard-only users can't activate the element.",howToFix:"Developer fix: 'replace divs/spans-with-onClick with a real <button>, or add tabindex=0'."},"target-size":{whatsWrong:"Some buttons or links are too small to tap reliably on touch screens.",whyItMatters:"WCAG 2.2 requires touch targets to be at least 24×24 pixels. Small targets are a problem for users with hand tremors, large fingers, or motor disabilities. They're also frustrating for everyone.",howToFix:"In your editor, increase padding around small icon buttons. If your developer is involved, ask them to ensure all interactive elements have minimum 24×24 CSS pixels of clickable area."},"frame-title":{whatsWrong:"An iframe (embedded content) has no title.",whyItMatters:"Screen readers announce frame titles when entering them. Without one, users hear 'frame' with no context.",howToFix:"If using a third-party embed (YouTube, Vimeo, Stripe), most platforms set the title automatically. If yours doesn't, look for a 'Title' or 'Description' field on the embed block."}};function nc(e){const t=ac[e];return t||{whatsWrong:"An accessibility rule failed on this page.",whyItMatters:"Some users — particularly those using screen readers, keyboard-only navigation, or who have low vision or motor disabilities — may have trouble using this part of the page.",howToFix:"Share the technical rule ID with your developer. They can look up the full fix at https://dequeuniversity.com/rules/axe."}}const oc={whatsWrong:"This element appears to have a contrast issue, but it's actually transparent — its parent (or itself) has an opacity below 100%.",whyItMatters:"When something is partially transparent, the colors blend with whatever's behind it, which automated tools see as poor contrast. Most often this happens because the element is part of an entry animation (fade-in on scroll) that hasn't completed when the audit ran. The element will likely look correct once the animation finishes.",howToFix:"Check whether this element is inside a scroll-reveal animation (common patterns: classes like .fade-in, .reveal, .animate-on-scroll; or JavaScript using IntersectionObserver to add a .visible class). If so, this is NOT a real contrast problem — the element will become opaque once the user scrolls past it. If you want the audit to skip these false positives, ensure your site honors prefers-reduced-motion (which serves the final state immediately) or remove the opacity:0 default."};function ri(e,t){return e==="color-contrast"&&t&&(t.self<.99||t.ancestor<.99)?oc:nc(e)}const jt="inflight:audit";async function ca(){return(await chrome.storage.local.get(jt))[jt]??null}async function ze(e){await chrome.storage.local.set({[jt]:e})}async function nt(){await chrome.storage.local.remove(jt)}async function Xh(){const e=await ca();e&&e.state==="running"&&await ze({...e,state:"interrupted",lastProgressAt:new Date().toISOString()})}async function la(e){const t=await ca();t&&await ze({...t,...e,lastProgressAt:new Date().toISOString()})}function Ct(){return crypto.randomUUID()}function ic(e){return e.length<2?[]:[...rc(e),...cc(e),...lc(e),...dc(e)]}function rc(e){const t=[];for(const a of["primary","footer","utility"]){const n=e.map(o=>({snapshot:o,hrefs:o.navLinks.filter(i=>i.region===a&&i.href).map(i=>i.href)})).filter(o=>o.hrefs.length>0);if(!(n.length<2))for(let o=0;o<n.length-1;o++){const i=n[o],r=n[o+1],c=i.hrefs.filter(g=>r.hrefs.includes(g));if(c.length<2)continue;const l=c.map(g=>i.hrefs.indexOf(g)),d=c.map(g=>r.hrefs.indexOf(g)),u=[...l].sort((g,p)=>g-p),h=[...d].sort((g,p)=>g-p),m=u.map(g=>c[l.indexOf(g)]),f=h.map(g=>c[d.indexOf(g)]);sc(m,f)||t.push({ruleId:"wcc-consistent-navigation",wcagCriterion:"3.2.3",wcagLevel:"AA",impact:"moderate",description:`${a}-region nav links appear in a different order on these pages.`,details:[`Page A: ${i.snapshot.url}`,` order: [${m.join(", ")}]`,`Page B: ${r.snapshot.url}`,` order: [${f.join(", ")}]`,"WCAG 3.2.3 requires that components that appear on multiple pages occur in the same relative order. Additions are fine; reorders are not."].join(`
365
+ `),pages:[{url:i.snapshot.url,evidence:m.join(" → ")},{url:r.snapshot.url,evidence:f.join(" → ")}]})}}return t}function sc(e,t){if(e.length!==t.length)return!1;for(let a=0;a<e.length;a++)if(e[a]!==t[a])return!1;return!0}function cc(e){const t=[],a=new Map;for(const n of e)for(const o of n.sharedComponents)a.has(o.fingerprint)||a.set(o.fingerprint,[]),a.get(o.fingerprint).push({url:n.url,accessibleName:o.accessibleName,role:o.role});for(const[n,o]of a.entries()){if(o.length<2)continue;const i=new Set(o.map(r=>gn(r.accessibleName)));i.size<=1||t.push({ruleId:"wcc-consistent-identification",wcagCriterion:"3.2.4",wcagLevel:"AA",impact:"moderate",description:`Same component (${n}) has different accessible names across pages.`,details:[`Fingerprint: ${n}`,`Role: ${o[0].role}`,`Distinct names observed: ${[...i].map(r=>`"${r}"`).join(", ")}`,...o.map(r=>` ${r.url} → "${r.accessibleName}"`),"WCAG 3.2.4 requires components with identical functionality to have identical accessible names across pages."].join(`
366
+ `),pages:o.map(r=>({url:r.url,evidence:r.accessibleName}))})}return t}function gn(e){return e.toLowerCase().replace(/\s+/g," ").trim()}function lc(e){const t=[],a=new Map;for(const n of e)for(const o of n.helpMechanisms){const i=gn(o.label);i&&(a.has(i)||a.set(i,[]),a.get(i).push({url:n.url,landmark:o.landmark,rawLabel:o.label}))}for(const[n,o]of a.entries()){if(o.length<2)continue;const i=new Set(o.map(r=>r.landmark));i.size<=1||t.push({ruleId:"wcc-consistent-help",wcagCriterion:"3.2.6",wcagLevel:"A",impact:"minor",description:`Help mechanism "${n}" appears in different landmarks across pages.`,details:[`Help label: "${n}"`,`Distinct landmarks observed: ${[...i].join(", ")}`,...o.map(r=>` ${r.url} → ${r.landmark}`),"WCAG 3.2.6 (AA, WCAG 2.2): when a help mechanism exists on multiple pages, it must appear in the same relative order. Same-label help that moves between header/footer/nav is a divergence."].join(`
367
+ `),pages:o.map(r=>({url:r.url,evidence:`${r.rawLabel} in ${r.landmark}`}))})}return t}function dc(e){const t=[],a=e.filter(o=>o.isMultiStep);if(a.length<2)return t;const n=new Map;for(const o of a)for(const i of o.formFields){const r=gn(i.name);r&&(n.has(r)||n.set(r,[]),n.get(r).push({url:o.url,label:i.label,type:i.type,isSecurityEssential:i.isSecurityEssential}))}for(const[o,i]of n.entries())i.length<2||i.some(r=>r.isSecurityEssential)||t.push({ruleId:"wcc-redundant-entry",wcagCriterion:"3.3.7",wcagLevel:"A",impact:"minor",description:`Field "${o}" is requested on multiple steps of a multi-step flow.`,details:[`Field name: "${o}"`,"Occurrences:",...i.map(r=>` ${r.url} → label "${r.label}", type ${r.type}`),"WCAG 3.3.7 (A, WCAG 2.2): information previously entered in a multi-step process must be auto-populated or selectable, unless re-entry is essential (e.g., password confirmation, security questions)."].join(`
368
+ `),pages:i.map(r=>({url:r.url,evidence:`${r.label} (${r.type})`}))});return t}const zt={A:0,B:1,C:2,D:3,F:4},Fn={low:0,moderate:1,high:2,critical:3};function uc(e,t){return zt[e]>zt[t]?e:t}function hc(e,t){return Fn[e]>Fn[t]?e:t}function pc(e){const t=[];for(const a of e)for(const n of a.results)for(const o of n.violations)o.needsReview||t.push({url:a.url,v:o});return t}function fc(e,t){const a=[];for(const n of e){const o=t==null?void 0:t.get(n.componentId);for(const i of n.results)for(const r of i.violations)r.needsReview&&(o!=null&&o.has(r.matchKey)||a.push({url:n.url,v:r}))}return a}function gc(e,t,a,n,o,i,r,c,l){const d=t.filter(x=>!x.error).length,u=t.filter(x=>!!x.error).length,h=t.map(x=>{var L;if(x.error)return{url:x.url,grade:"F",risk:"critical",uniqueViolations:0,totals:{critical:0,serious:0,moderate:0,minor:0},durationMs:x.durationMs,error:x.error,walkthroughFindings:[],walkthroughAcknowledgements:[]};const U=x.results.flatMap(G=>G.violations),z=i==null?void 0:i.get(x.componentId),K=r==null?void 0:r.get(x.componentId),ie={};if(z&&z.length>0){const G=new Set((K??[]).map(W=>W.criterionId));for(const W of z){const V=G.has(W.criterionId)?"pass":W.verdict;W.criterionId==="2.4.3"?ie.focusOrder=V:W.criterionId==="2.1.2"?ie.keyboardTrap=V:W.criterionId==="2.4.7"?ie.focusVisible=V:W.criterionId==="1.3.2"?ie.readingOrder=V:W.criterionId==="1.4.11"&&(ie.nonTextContrast=V)}}const re=c==null?void 0:c.get(x.componentId),ve=l==null?void 0:l.get(x.componentId),pe=Se(U,void 0,x.results,re?new Set(re):void 0,z?[...z]:void 0,K?[...K]:void 0,ve?[...ve]:void 0),A=cs({violations:U,auditRan:!0,manualRuns:[],workflows:Ee,heuristicCounts:{},interactiveAuditVerdicts:ie}),B=(L=pe.caps)!=null&&L.cappedAtBByCoverage&&A.overallLetter==="A"?"B":A.overallLetter;return{url:x.url,grade:B,risk:pe.risk,uniqueViolations:pe.totals.unique,totals:{critical:pe.totals.critical,serious:pe.totals.serious,moderate:pe.totals.moderate,minor:pe.totals.minor},durationMs:x.durationMs,walkthroughFindings:pe.walkthroughFindings,walkthroughAcknowledgements:pe.walkthroughAcknowledgements}});h.sort((x,U)=>{const z=zt[U.grade]-zt[x.grade];return z!==0?z:U.uniqueViolations-x.uniqueViolations});let m="A",f="low";for(const x of h)m=uc(m,x.grade),f=hc(f,x.risk);const g=pc(t),p=x=>`${x.ruleId}::${x.target.selector}`,b=new Map;for(const{v:x}of g){const U=p(x);b.has(U)||b.set(U,x)}const w=b.size,k={critical:0,serious:0,moderate:0,minor:0};for(const x of b.values())k[x.impact]++;const y=new Map;for(const{url:x,v:U}of g){let z=y.get(U.ruleId);z||(z={ruleId:U.ruleId,description:U.description,impact:U.impact,urls:new Set,totalOccurrences:0},y.set(U.ruleId,z)),z.urls.add(x),z.totalOccurrences++}const s=Array.from(y.values()).map(x=>({ruleId:x.ruleId,description:x.description,impact:x.impact,urlsAffected:x.urls.size,totalOccurrences:x.totalOccurrences,sampleUrls:Array.from(x.urls).slice(0,5)})).sort((x,U)=>U.urlsAffected!==x.urlsAffected?U.urlsAffected-x.urlsAffected:U.totalOccurrences-x.totalOccurrences),O=fc(t,c),E=new Map,v=new Set;for(const{url:x,v:U}of O){v.add(`${U.ruleId}::${U.target.selector}`);let z=E.get(U.ruleId);z||(z={ruleId:U.ruleId,description:U.description,impact:U.impact,urls:new Set,totalOccurrences:0},E.set(U.ruleId,z)),z.urls.add(x),z.totalOccurrences++}const D=Array.from(E.values()).map(x=>({ruleId:x.ruleId,description:x.description,impact:x.impact,urlsAffected:x.urls.size,totalOccurrences:x.totalOccurrences,sampleUrls:Array.from(x.urls).slice(0,5)})).sort((x,U)=>U.urlsAffected!==x.urlsAffected?U.urlsAffected-x.urlsAffected:U.totalOccurrences-x.totalOccurrences),S=v.size,R={minor:0,moderate:1,serious:2,critical:3},P=new Map;for(const x of t){if(x.error)continue;const U=c==null?void 0:c.get(x.componentId);for(const z of x.results)for(const K of z.violations){if(U!=null&&U.has(K.matchKey))continue;const ie=`${xe(K.ruleId,K.target.selector)}::${K.needsReview?"NR":"ACT"}`;let re=P.get(ie);re||(re={ruleId:K.ruleId,representativeSelector:K.target.selector,representativeOuterHTML:K.target.outerHTML??"",impact:K.impact,wcagCriterion:K.wcagCriterion??null,axeDescription:K.description??"",pages:new Map,totalOccurrences:0,needsReview:K.needsReview??!1,failureSummary:K.target.failureSummary??null},P.set(ie,re)),R[K.impact]>R[re.impact]&&(re.impact=K.impact),re.totalOccurrences++,re.pages.has(x.url)||re.pages.set(x.url,{selector:K.target.selector,outerHTML:K.target.outerHTML??""}),!re.failureSummary&&K.target.failureSummary&&(re.failureSummary=K.target.failureSummary)}}const I=Array.from(P.values()).map(x=>({ruleId:x.ruleId,representativeSelector:x.representativeSelector,representativeOuterHTML:x.representativeOuterHTML,impact:x.impact,wcagCriterion:x.wcagCriterion,axeDescription:x.axeDescription,pages:Array.from(x.pages.entries()).map(([U,z])=>({url:U,selector:z.selector,outerHTML:z.outerHTML})),totalOccurrences:x.totalOccurrences,isShared:x.pages.size>=2,needsReview:x.needsReview,failureSummary:x.failureSummary})).sort((x,U)=>R[U.impact]!==R[x.impact]?R[U.impact]-R[x.impact]:U.pages.length-x.pages.length),$={A:0,B:0,C:0,D:0,F:0};for(const x of h)$[x.grade]++;const C=t.map(x=>x.snapshot).filter(x=>!!x),M=ic(C),F=new Set,q=[],N=[];for(const x of h){for(const U of x.walkthroughFindings){const z=`${U.criterionId}::${U.pageUrl}`;F.has(z)||(F.add(z),q.push(U))}for(const U of x.walkthroughAcknowledgements){const z=`${U.criterionId}::${U.pageUrl}::ack`;F.has(z)||(F.add(z),N.push(U))}}const j=new Map,X={untested:0,inconclusive:1,failing:2},Q=new Map;for(const x of St("2.1","AA"))Q.set(x.id,x.title);const me=new Map;function he(x,U){const z=`${U.pageUrl}::${U.selector??""}::${U.source}::${U.ruleOrStepId}`;let K=me.get(x);if(K||(K=new Set,me.set(x,K)),K.has(z))return;K.add(z);const ie=j.get(x);ie&&(ie.evidence.length>=6||ie.evidence.push(U))}for(const x of t){if(x.error)continue;const U=x.results.flatMap(L=>L.violations),z=i==null?void 0:i.get(x.componentId),K=r==null?void 0:r.get(x.componentId),ie=c==null?void 0:c.get(x.componentId),re=l==null?void 0:l.get(x.componentId),ve=Se(U,void 0,x.results,ie?new Set(ie):void 0,z?[...z]:void 0,K?[...K]:void 0,re?[...re]:void 0);if(!ve.coverage)continue;const pe=(L,G)=>{for(const W of L){const V=Q.get(W)??W,Y=j.get(W);Y?X[G]>X[Y.state]&&j.set(W,{state:G,title:V,evidence:Y.evidence}):j.set(W,{state:G,title:V,evidence:[]})}};pe(ve.coverage.untestedCriteria,"untested"),pe(ve.coverage.inconclusiveCriteria,"inconclusive"),pe(ve.coverage.failingCriteria,"failing");const A=ve.coverage.inconclusiveReasons??{};for(const[L,G]of Object.entries(A))for(const W of G)if(W.elements&&W.elements.length>0)for(const V of W.elements)he(L,{pageUrl:W.pageUrl??x.url,selector:V.selector,ruleOrStepId:W.ruleOrStepId,source:W.source,notes:V.failureSummary});else he(L,{pageUrl:W.pageUrl??x.url,ruleOrStepId:W.ruleOrStepId,source:W.source,notes:W.resolutionHint});const B=ve.coverage.failingReasons??{};for(const[L,G]of Object.entries(B))for(const W of G)he(L,{pageUrl:W.pageUrl??x.url,selector:W.selector,ruleOrStepId:W.ruleOrStepId,source:W.source,notes:W.notes})}const ae=Array.from(j.entries()).map(([x,U])=>({criterionId:x,title:U.title,state:U.state,evidence:U.evidence})).sort((x,U)=>x.criterionId.localeCompare(U.criterionId,void 0,{numeric:!0}));return{startedAt:a,finishedAt:n,startUrl:e,pagesAudited:d,pagesFailed:u,totalDurationMs:o,siteGrade:m,siteRisk:f,totalUniqueViolations:w,totals:k,topViolations:s,needsReviewItems:D,totalNeedsReview:S,findingGroups:I,pages:h,pagesByGrade:$,consistencyFindings:M,walkthroughFindings:q,walkthroughAcknowledgements:N,totalWalkthroughFindings:q.length,blockingCriteria:ae}}const Ba="humanCriterionVerdicts";async function mn(){var a;if(typeof chrome>"u"||!((a=chrome.storage)!=null&&a.local))return[];const t=(await chrome.storage.local.get(Ba))[Ba];return Array.isArray(t)?t:[]}async function si(e){const t=new Set;for(const n of e)n&&t.add(n);return t.size===0?[]:(await mn()).filter(n=>t.has(n.componentId))}async function Qh(e){if(!e.note||!e.note.trim())throw new Error("Human criterion verdict requires a non-empty note explaining the determination.");const a=(await mn()).filter(n=>!(n.componentId===e.componentId&&n.criterionId===e.criterionId));a.push({...e,note:e.note.trim()}),await ci(a)}async function Zh(e,t){const n=(await mn()).filter(o=>!(o.componentId===e&&o.criterionId===t));await ci(n)}async function ci(e){var t;typeof chrome>"u"||!((t=chrome.storage)!=null&&t.local)||await chrome.storage.local.set({[Ba]:e})}async function mc(e,t){var a;try{return((a=(await chrome.scripting.executeScript({target:{tabId:e},func:bc,args:[t]}))[0])==null?void 0:a.result)??null}catch{return null}}function bc(e){var k;function t(y){var D,S;const s=y.getAttribute("aria-label");if(s&&s.trim())return s.trim();const O=y.getAttribute("aria-labelledby");if(O){const R=O.split(/\s+/).map(P=>{var I,$;return(($=(I=document.getElementById(P))==null?void 0:I.textContent)==null?void 0:$.trim())??""}).filter(Boolean);if(R.length)return R.join(" ")}if(y instanceof HTMLInputElement||y instanceof HTMLTextAreaElement||y instanceof HTMLSelectElement){if(y.id){const P=document.querySelector(`label[for="${CSS.escape(y.id)}"]`);if((D=P==null?void 0:P.textContent)!=null&&D.trim())return P.textContent.trim()}const R=y.closest("label");if((S=R==null?void 0:R.textContent)!=null&&S.trim())return R.textContent.trim();if(y instanceof HTMLInputElement&&y.placeholder)return y.placeholder}const E=(y.textContent??"").replace(/\s+/g," ").trim();if(E)return E.slice(0,120);const v=y.getAttribute("title");return v?v.trim():""}function a(y){if(!y||y.startsWith("javascript:")||y.startsWith("#"))return null;try{return new URL(y,document.baseURI).href}catch{return y}}function n(y){let s=y;for(;s&&s!==document.body;){if(s.tagName==="HEADER")return"header";if(s.tagName==="FOOTER")return"footer";if(s.tagName==="NAV")return"nav";if(s.tagName==="MAIN"||s.getAttribute("role")==="main")return"main";if(s.tagName==="ASIDE"||s.getAttribute("role")==="complementary")return"aside";const O=s.getAttribute("role");if(O==="banner")return"header";if(O==="contentinfo")return"footer";if(O==="navigation")return"nav";s=s.parentElement}return"none"}function o(y){let s=y;for(;s&&s!==document.body;){if(s.tagName==="FOOTER"||s.getAttribute("role")==="contentinfo")return"footer";if(s.tagName==="HEADER"||s.getAttribute("role")==="banner")return"primary";s=s.parentElement}return"utility"}const i=[],r=document.querySelectorAll('nav, [role="navigation"]');for(const y of Array.from(r)){const s=o(y),O=y.querySelectorAll("a[href]");let E=0;for(const v of Array.from(O)){const D=a(v.getAttribute("href")),S=t(v);!S&&!D||i.push({href:D,accessibleName:S,index:E++,region:s})}}const c=[],l=new Set;for(const y of i)y.href&&(l.has(y.href)||(l.add(y.href),c.push({fingerprint:`href:${y.href}`,accessibleName:y.accessibleName,role:"link"})));const d=[{sel:'[data-test-id="logo"]',alias:"logo",role:"link"},{sel:".logo",alias:"logo",role:"link"},{sel:'[aria-label*="menu" i]',alias:"menu-toggle",role:"button"},{sel:'[data-test-id="menu-toggle"]',alias:"menu-toggle",role:"button"},{sel:'[aria-label*="search" i]',alias:"search-toggle",role:"button"},{sel:'[data-test-id="search"]',alias:"search-toggle",role:"button"}];for(const{sel:y,alias:s,role:O}of d){const E=document.querySelector(y);if(!E)continue;const v=`alias:${s}`;l.has(v)||(l.add(v),c.push({fingerprint:v,accessibleName:t(E),role:O}))}const u=[],h=/(help|support|contact|faq|chat|live\s*chat)\b/i,m=document.querySelectorAll('a, button, [role="button"], [role="link"]'),f=new Set;for(const y of Array.from(m)){const s=t(y),O=y.getAttribute("aria-label")??"",E=s||O;if(!h.test(E))continue;const v=`${E.toLowerCase().slice(0,32)}::${y.tagName}`;f.has(v)||(f.add(v),u.push({label:E.slice(0,80),href:y instanceof HTMLAnchorElement?a(y.getAttribute("href")):null,landmark:n(y)}))}const g=[],p=/(password|cvc|cvv|otp|one[-_\s]?time|security[-_\s]?answer|secret|2fa|mfa)/i,b=document.querySelectorAll("input, textarea, select");for(const y of Array.from(b)){if(y instanceof HTMLInputElement){const D=(y.type||"text").toLowerCase();if(D==="hidden"||D==="submit"||D==="button"||D==="image"||D==="reset")continue}const s=y.getAttribute("name")||y.id||"";if(!s)continue;const O=y instanceof HTMLInputElement?y.type||"text":y instanceof HTMLTextAreaElement?"textarea":"select",E=t(y),v=p.test(s)||p.test(E)||O==="password";g.push({name:s,type:O,label:E,isSecurityEssential:v})}let w=!1;if(document.querySelector('[role="progressbar"]')&&(w=!0),!w){const y=document.querySelectorAll('button, a, [role="button"]');let s=0,O=0;for(const E of Array.from(y)){const v=(E.textContent??"").trim();if(/step\s*\d+\s*of\s*\d+/i.test(v)){w=!0;break}/^(next|continue)$/i.test(v)&&s++,/^(previous|prev|back)$/i.test(v)&&O++}s>0&&O>0&&(w=!0)}return{url:e,title:(document.title||(((k=document.querySelector("h1"))==null?void 0:k.textContent)??"")).trim(),navLinks:i,sharedComponents:c,helpMechanisms:u,formFields:g,isMultiStep:w}}const yc={input:3/1e6,output:15/1e6},wc={input:1/1e6,output:5/1e6},vc="claude-sonnet-4-6",Ac="https://api.anthropic.com/v1/messages",kc="2023-06-01";function Ic(e){const t=e.model||vc,a=t.includes("haiku")?wc:yc;return{providerId:"anthropic",async judgeAltText({imageUrl:n,alt:o,surroundingContext:i,signal:r}){const c=await qn(n,r);if(!c)return Gn("Could not fetch image to verify alt text",0,t);const l=Fc(o,i),d=await ne({apiKey:e.apiKey,model:t,maxTokens:300,signal:r,messages:[{role:"user",content:[{type:"image",source:{type:"base64",media_type:c.mediaType,data:c.data}},{type:"text",text:l}]}]});return le(d,a)},async judgeHeading({headingText:n,sectionContent:o,signal:i}){const r=await ne({apiKey:e.apiKey,model:t,maxTokens:250,signal:i,messages:[{role:"user",content:[{type:"text",text:Wc(n,o)}]}]});return le(r,a)},async judgeSensoryLanguage({instructionText:n,signal:o}){const i=await ne({apiKey:e.apiKey,model:t,maxTokens:250,signal:o,messages:[{role:"user",content:[{type:"text",text:qc(n)}]}]});return le(i,a)},async judgeAriaSemantics({elementHtml:n,computedRole:o,surroundingHtml:i,signal:r}){const c=await ne({apiKey:e.apiKey,model:t,maxTokens:350,signal:r,messages:[{role:"user",content:[{type:"text",text:Gc(n,o,i)}]}]});return le(c,a)},async judgeImageOfText({imageUrl:n,accessibleName:o,signal:i}){const r=await qn(n,i);if(!r)return Gn("Could not fetch image to inspect for embedded text",0,t);const c=await ne({apiKey:e.apiKey,model:t,maxTokens:300,signal:i,messages:[{role:"user",content:[{type:"image",source:{type:"base64",media_type:r.mediaType,data:r.data}},{type:"text",text:Vc(o)}]}]});return le(c,a)},async judgeColorOnlyMeaning({elementHtml:n,contextDescription:o,signal:i}){const r=await ne({apiKey:e.apiKey,model:t,maxTokens:250,signal:i,messages:[{role:"user",content:[{type:"text",text:jc(n,o)}]}]});return le(r,a)},async judgeGenericLinkText({linkText:n,surroundingText:o,signal:i}){const r=await ne({apiKey:e.apiKey,model:t,maxTokens:250,signal:i,messages:[{role:"user",content:[{type:"text",text:zc(n,o)}]}]});return le(r,a)},async judgeWallOfText({blockText:n,structuralChildrenCount:o,signal:i}){const r=await ne({apiKey:e.apiKey,model:t,maxTokens:250,signal:i,messages:[{role:"user",content:[{type:"text",text:Hc(n,o)}]}]});return le(r,a)},async judgeLanguageMismatch({declaredLang:n,bodyTextSample:o,signal:i}){const r=await ne({apiKey:e.apiKey,model:t,maxTokens:250,signal:i,messages:[{role:"user",content:[{type:"text",text:Bc(n,o)}]}]});return le(r,a)},async suggestColorFix(n){const o=await ne({apiKey:e.apiKey,model:t,maxTokens:600,messages:[{role:"user",content:[{type:"text",text:Kc(n)}]}]});return Jc(o,a)},async generateExecutiveSummary(n){const o=await ne({apiKey:e.apiKey,model:t,maxTokens:800,messages:[{role:"user",content:[{type:"text",text:Yc(n)}]}]});return Zc(o,a)},async resolveAxeIncomplete(n){const o=await ne({apiKey:e.apiKey,model:t,maxTokens:400,signal:n.signal,messages:[{role:"user",content:[{type:"text",text:Xc(n)}]}]});return Qc(o,a,t)},async judgeFocusVisible({pageUrl:n,samples:o,signal:i}){const r=[];for(const l of o){const d=/^data:(image\/[a-z]+);base64,(.+)$/i.exec(l.screenshot);d&&(r.push({type:"image",source:{type:"base64",media_type:d[1],data:d[2]}}),r.push({type:"text",text:`Image #${l.ordinal} — selector: ${l.selector} — role: ${l.role||"(none)"} — name: "${l.name||"(no name)"}" — rect: x=${Math.round(l.rect.x)} y=${Math.round(l.rect.y)} w=${Math.round(l.rect.width)} h=${Math.round(l.rect.height)} — outline: ${l.focusStyle.outlineStyle} ${l.focusStyle.outlineWidth} ${l.focusStyle.outlineColor} (offset ${l.focusStyle.outlineOffset}) — box-shadow: ${l.focusStyle.boxShadow||"none"}`}))}r.push({type:"text",text:Oc(n,o.length)});const c=await ne({apiKey:e.apiKey,model:t,maxTokens:600,signal:i,messages:[{role:"user",content:r}]});return le(c,a)},async judgeConsistencyDivergence({wcagCriterion:n,ruleId:o,description:i,details:r,pages:c,signal:l}){const d=Dc(n,o,i,r,c),u=await ne({apiKey:e.apiKey,model:t,maxTokens:500,signal:l,messages:[{role:"user",content:[{type:"text",text:d}]}]});return le(u,a)},async judgeKeyboardTrap({pageUrl:n,forwardSequence:o,reverseSequence:i,stuckRuns:r,reverseMismatches:c,previousVerdict:l,signal:d}){const u=Mc(n,o,i,r,c,l),h=await ne({apiKey:e.apiKey,model:t,maxTokens:400,signal:d,messages:[{role:"user",content:[{type:"text",text:u}]}]});return le(h,a)},async judgeNonTextContrast({pageUrl:n,pageScreenshot:o,previousVerdict:i,signal:r}){const c=Nc(n,i),l=[],d=/^data:(image\/[a-z]+);base64,(.+)$/i.exec(o);d&&l.push({type:"image",source:{type:"base64",media_type:d[1],data:d[2]}}),l.push({type:"text",text:c});const u=await ne({apiKey:e.apiKey,model:t,maxTokens:1500,signal:r,messages:[{role:"user",content:l}]});return le(u,a)},async judgeReadingOrder({pageUrl:n,pageScreenshot:o,flaggedIssues:i,previousVerdict:r,signal:c}){const l=Lc(n,i,r),d=[];if(o){const h=/^data:(image\/[a-z]+);base64,(.+)$/i.exec(o);h&&d.push({type:"image",source:{type:"base64",media_type:h[1],data:h[2]}})}d.push({type:"text",text:l});const u=await ne({apiKey:e.apiKey,model:t,maxTokens:1500,signal:c,messages:[{role:"user",content:d}]});return le(u,a)},async judgeFocusOrder({pageUrl:n,pageScreenshot:o,tabSequence:i,previousVerdict:r,signal:c}){const l=Pc(n,i,r),d=[];if(o){const h=/^data:(image\/[a-z]+);base64,(.+)$/i.exec(o);h&&d.push({type:"image",source:{type:"base64",media_type:h[1],data:h[2]}})}d.push({type:"text",text:l});const u=await ne({apiKey:e.apiKey,model:t,maxTokens:1500,signal:c,messages:[{role:"user",content:d}]});return le(u,a)},async judgeLabelInName({pageUrl:n,pageScreenshot:o,candidates:i,signal:r}){const c=Uc(n,i),l=[];if(o){const u=/^data:(image\/[a-z]+);base64,(.+)$/i.exec(o);u&&l.push({type:"image",source:{type:"base64",media_type:u[1],data:u[2]}})}l.push({type:"text",text:c});const d=await ne({apiKey:e.apiKey,model:t,maxTokens:1500,signal:r,messages:[{role:"user",content:l}]});return le(d,a)},async judgeFocusOrderFallback({pageUrl:n,pageScreenshot:o,tabSequence:i,primaryUncertainReason:r,signal:c}){const l=_c(n,i,r),d=[];if(o){const h=/^data:(image\/[a-z]+);base64,(.+)$/i.exec(o);h&&d.push({type:"image",source:{type:"base64",media_type:h[1],data:h[2]}})}d.push({type:"text",text:l});const u=await ne({apiKey:e.apiKey,model:t,maxTokens:1500,signal:c,messages:[{role:"user",content:d}]});return le(u,a)}}}class kt extends Error{constructor(){super("Anthropic API: canceled by caller"),this.name="ExternalAbortError"}}const li=3e4,Wn=3,xc=1e4;function Sc(e){return e===429||e>=500}function Cc(e){return e instanceof Error?e.name==="TimeoutError"||e.name==="AbortError"||e instanceof TypeError:!1}function $c(e,t){if(!t)return e;if(typeof AbortSignal.any=="function")return AbortSignal.any([e,t]);const a=new AbortController,n=()=>a.abort();return e.aborted?a.abort():e.addEventListener("abort",n,{once:!0}),t.aborted?a.abort():t.addEventListener("abort",n,{once:!0}),a.signal}async function Tc(e){var o,i;const t=AbortSignal.timeout(li),a=$c(t,e.signal);let n;try{n=await fetch(Ac,{method:"POST",headers:{"x-api-key":e.apiKey,"anthropic-version":kc,"content-type":"application/json","anthropic-dangerous-direct-browser-access":"true"},body:JSON.stringify({model:e.model,max_tokens:e.maxTokens,messages:e.messages,temperature:e.temperature??0}),signal:a})}catch(r){throw(o=e.signal)!=null&&o.aborted?new kt:r}if(!n.ok){const c=((i=(await n.json().catch(()=>({}))).error)==null?void 0:i.message)??`${n.status} ${n.statusText}`,l=new Error(`Anthropic API: ${c}`);throw l.status=n.status,l}return await n.json()}async function ne(e){var a;let t=null;for(let n=1;n<=Wn;n++){if((a=e.signal)!=null&&a.aborted)throw new kt;try{return await Tc(e)}catch(o){if(o instanceof kt)throw o;const i=o.status;if(!(Cc(o)||typeof i=="number"&&Sc(i))||n===Wn)throw o instanceof Error&&(o.name==="TimeoutError"||o.name==="AbortError")?new Error(`Anthropic API: timed out after ${li/1e3}s on each of ${n} attempt${n===1?"":"s"}`):o;t=o instanceof Error?o:new Error(String(o)),await Ec(n*1e3,e.signal)}}throw t??new Error("Anthropic API: all retries failed")}function Ec(e,t){return new Promise((a,n)=>{if(t!=null&&t.aborted){n(new kt);return}const o=setTimeout(()=>{t==null||t.removeEventListener("abort",i),a()},e),i=()=>{clearTimeout(o),n(new kt)};t==null||t.addEventListener("abort",i,{once:!0})})}async function qn(e,t){var i,r;const a=new AbortController,n=setTimeout(()=>a.abort(),xc),o=()=>a.abort();t&&(t.aborted?a.abort():t.addEventListener("abort",o,{once:!0}));try{const c=await fetch(e,{signal:a.signal});if(!c.ok)return null;const l=((r=(i=c.headers.get("content-type"))==null?void 0:i.split(";")[0])==null?void 0:r.trim())??"image/png";if(!l.startsWith("image/")||l!=="image/jpeg"&&l!=="image/png"&&l!=="image/gif"&&l!=="image/webp")return null;const d=await c.arrayBuffer();let u="";const h=new Uint8Array(d),m=32768;for(let f=0;f<h.length;f+=m)u+=String.fromCharCode(...h.slice(f,f+m));return{data:btoa(u),mediaType:l}}catch{return null}finally{clearTimeout(n),t&&t.removeEventListener("abort",o)}}function le(e,t){var i;const a=((i=e.content.find(r=>r.type==="text"))==null?void 0:i.text)??"",n=Rc(a),o=e.usage.input_tokens*t.input+e.usage.output_tokens*t.output;return{verdict:n.verdict,reasoning:n.reasoning,confidence:n.confidence,costUsd:o,model:e.model}}function Rc(e){const a=e.replace(/```(?:json)?/g,"").trim().match(/\{[\s\S]*\}/);if(!a)return{verdict:"uncertain",reasoning:"Model returned no parseable JSON",confidence:0};try{const n=JSON.parse(a[0]),o=n.verdict==="pass"||n.verdict==="fail"?n.verdict:"uncertain",i=(n.reasoning??"").trim().slice(0,600),r=typeof n.confidence=="number"?n.confidence:.5,c=Math.max(0,Math.min(1,r));return{verdict:o,reasoning:i,confidence:c}}catch{return{verdict:"uncertain",reasoning:"Model returned invalid JSON",confidence:0}}}function Gn(e,t,a){return{verdict:"uncertain",reasoning:e,confidence:0,costUsd:t,model:a}}const ue=`
369
+ Respond ONLY with a single JSON object — no preamble, no markdown:
370
+ { "verdict": "pass" | "fail" | "uncertain", "reasoning": "1-3 sentences", "confidence": 0.0-1.0 }
371
+ `.trim();function Oc(e,t){return`You are an accessibility auditor evaluating focus-visibility (WCAG 2.4.7 — Focus Visible).
372
+
373
+ Page: ${e}
374
+ ${t} screenshot(s) above; each shows a different focused element. Each image is paired with a metadata line giving the element's selector, role, accessible name, rect (viewport-relative), and computed outline + box-shadow styles.
375
+
376
+ For each image, look at the element identified by the rect and decide whether it shows a visible focus indicator. A visible indicator can be:
377
+ - A CSS outline (any color contrasting with the background).
378
+ - A box-shadow used as a focus ring.
379
+ - A background-color or border change relative to the unfocused baseline.
380
+ - Any other visual treatment that distinguishes the focused element from its unfocused peers.
381
+
382
+ A "fail" element has NO visible focus indicator — typically \`outline: none\` with no compensating box-shadow / border / background treatment, OR an outline whose color matches the background so it's effectively invisible.
383
+
384
+ Aggregate verdict:
385
+ - "pass" = every sampled element shows a clearly visible focus indicator.
386
+ - "fail" = one or more sampled elements have no visible focus indicator. The reasoning should name the failing selector(s) and why.
387
+ - "uncertain" = images are unreadable, the rects don't match visible elements, or compensating treatments are ambiguous.
388
+
389
+ ${ue}`}function Mc(e,t,a,n,o,i){const r=t.map((h,m)=>` ${m+1}. ${h}`).join(`
390
+ `)||" (no tabbable elements found)",c=a.map((h,m)=>` ${m+1}. ${h}`).join(`
391
+ `)||" (Shift-Tab moved to body / no reverse path)",l=n.length===0?" (none detected)":n.map(h=>` - ${h}`).join(`
392
+ `),d=o.length===0?" (none — Shift-Tab cleanly reversed the forward order)":o.map(h=>` - position ${h.index}: expected ${h.expected}, got ${h.actual}`).join(`
393
+ `);return`You are an accessibility auditor evaluating keyboard-trap risk (WCAG 2.1.2 — No Keyboard Trap).${i?`
394
+
395
+ **Previous verdict on this same page:** ${i.verdict.toUpperCase()} (${(i.confidence*100).toFixed(0)}% confidence)
396
+ Reasoning: "${i.reasoning.slice(0,400).replace(/"/g,"'")}"
397
+
398
+ **Stability anchor:** if you previously called this PASS at decent confidence, you should call it PASS again UNLESS the forward/reverse sequences below now show a NEW concrete trap (stuck run that wasn't there, Shift-Tab can't escape a region that previously was escapable). Run-to-run model variance is NOT new evidence; only specific sequence-level changes are. **Returning UNCERTAIN when you previously returned PASS also counts as a flip — don't hedge to uncertain unless the sequences are ACTUALLY ambiguous in a way they weren't before. The default action when nothing changed is to return the previous verdict.**
399
+ `:""}
400
+
401
+ Page: ${e}
402
+
403
+ A robot walked the page with Tab, then Shift-Tab. Findings below.
404
+
405
+ Forward walk (Tab key, in order):
406
+ ${r}
407
+
408
+ Reverse walk (Shift+Tab, in order — should walk back through the forward sequence):
409
+ ${c}
410
+
411
+ Stuck-run detection (selector appearing 3+ times in a row in the forward walk — indicates focus may not be progressing past that element):
412
+ ${l}
413
+
414
+ Reverse mismatches (expected vs. actual reverse-walk positions — indicates Shift-Tab didn't cleanly walk backward):
415
+ ${d}
416
+
417
+ Evaluate keyboard-trap risk per WCAG 2.1.2:
418
+ - "pass" = focus moves freely forward and backward. No stuck runs. Reverse walk mirrors forward order (allowing minor variation from focus-management widgets).
419
+ - "fail" = clear evidence of a trap. Stuck runs at the same element OR Shift-Tab can't escape a region OR reverse walk diverges significantly from forward order.
420
+ - "uncertain" = too few tabbable elements (<3) to judge meaningfully OR the data is inconsistent for unclear reasons.
421
+
422
+ ${ue}`}function Dc(e,t,a,n,o){const i=o.map((c,l)=>` ${l+1}. ${c.url}
423
+ ${c.evidence}`).join(`
424
+ `);return`You are an accessibility auditor evaluating whether a heuristic finding is a real WCAG violation or a false positive.
425
+
426
+ A site-crawl heuristic flagged this divergence:
427
+
428
+ Criterion: ${e}
429
+ Rule: ${t}
430
+ Summary: ${a}
431
+
432
+ Diagnostic details:
433
+ ${n}
434
+
435
+ Pages involved:
436
+ ${i}
437
+
438
+ WCAG context for this criterion:
439
+ ${{"3.2.3":"WCAG 3.2.3 Consistent Navigation (AA) — navigational mechanisms repeated on multiple pages occur in the same relative order each time they are repeated. Allowed exceptions: locale-driven reorders (RTL vs LTR), additions (one page has extra links), legitimate dynamic personalization where ALL users see the same order on the same page state.","3.2.4":"WCAG 3.2.4 Consistent Identification (AA) — components with the same functionality within a set of pages are identified consistently (same accessible name). Allowed exceptions: language differences in localized sites, intentional brand variation per page-section.","3.2.6":"WCAG 3.2.6 Consistent Help (A, WCAG 2.2) — when a help mechanism is present on multiple pages, it occurs in the same relative order to other page content. Allowed exceptions: pages where help is genuinely contextual to a different function.","3.3.7":"WCAG 3.3.7 Redundant Entry (A, WCAG 2.2) — information previously entered by the user in a multi-step process is auto-populated OR available for the user to select, unless re-entry is essential (security verification, password confirmation, intentional change confirmation)."}[e]}
440
+
441
+ Your job: decide whether this is a real WCAG ${e} divergence that the developer needs to fix, or a false positive the heuristic shouldn't have flagged.
442
+
443
+ Common false-positive patterns to be aware of:
444
+ - Language switchers reordering nav per locale (legitimate)
445
+ - Logged-in vs logged-out menus (different functionality, not a divergence)
446
+ - A/B test variants (intentional)
447
+ - Multi-tenant pages where each tenant has its own brand naming (legitimate)
448
+ - Dynamic personalization where the order varies per-user but is consistent for THAT user (debatable but generally not a fail)
449
+ - Contextual help that legitimately belongs in a different position (e.g., inline help in a multi-step form vs. global help in the footer)
450
+
451
+ Verdict mapping:
452
+ - "pass" = false positive. The flagged pattern is intentional, expected, or otherwise not a WCAG violation. Suppress this finding.
453
+ - "fail" = real divergence. This IS a WCAG ${e} violation and should be reported to the user.
454
+ - "uncertain" = the evidence is genuinely ambiguous; surface the finding so a human can decide.
455
+
456
+ ${ue}`}function Nc(e,t){const a=t?`
457
+
458
+ **Previous verdict on this same page (${new Date().toLocaleDateString()}):** ${t.verdict.toUpperCase()} (${(t.confidence*100).toFixed(0)}% confidence)
459
+ Reasoning: "${t.reasoning}"
460
+
461
+ Anchor your judgment: if you previously called this PASS at high confidence, you should call it PASS again UNLESS the screenshot shows specific new evidence that changed (a new component you didn't see before, a state change, a redesign). State explicitly what changed if you're flipping the verdict. **Returning UNCERTAIN when you previously returned PASS also counts as a flip — don't hedge to uncertain unless the screenshot is ACTUALLY ambiguous in a way it wasn't before. The default action when nothing changed is to return the previous verdict.**
462
+ `:"";return`You are an accessibility auditor evaluating non-text contrast (WCAG 1.4.11 — Non-text Contrast, Level AA).
463
+
464
+ A screenshot of the page (${e}) is attached.${a}
465
+
466
+ WCAG 1.4.11 requires that the following have at least 3:1 contrast against adjacent colors:
467
+ 1. **User Interface Components** — visual information required to identify UI components and states (button borders/backgrounds, form input borders, focus indicators, checkbox/radio borders, disabled-state contrast, hover/active state changes).
468
+ 2. **Graphical Objects** — visual information required to understand non-decorative graphics (icons that convey meaning, status indicators, infographic elements that aren't text-labeled).
469
+
470
+ # What 1.4.11 explicitly EXEMPTS — DO NOT FLAG THESE
471
+
472
+ rc.122 — Be aggressive about exemptions. The most common false-positive
473
+ pattern on this rule is flagging decoration as if it were UI. WCAG
474
+ explicitly exempts the following — they must NEVER cause a fail verdict:
475
+
476
+ - **Pure decoration**: 1-2px horizontal/vertical dividers, gradient
477
+ fades, hairline rules, ornamental flourishes, background patterns,
478
+ starfield/dotgrid backgrounds, animated pulses that aren't conveying
479
+ state. If you see a thin line whose only job is visual separation
480
+ between sections, that is decoration. Move on.
481
+ - **Logos and brand wordmarks**: SPECIFICALLY EXEMPTED by WCAG. Even
482
+ if a logo has low contrast against its background, it does not fail
483
+ 1.4.11. (1.4.3 also exempts logos — different rule, same exemption.)
484
+ - **Icons with redundant adjacent text labels**: when "Menu" appears
485
+ as text next to a hamburger icon, OR a chevron icon sits inside a
486
+ "Read more" link, the icon is decorative because the text carries
487
+ the information. Skip it.
488
+ - **Disabled / inactive controls**: 1.4.11 explicitly exempts disabled
489
+ state contrast. A faded "Submit (disabled)" button is fine.
490
+ - **Hero / banner imagery**: photos and illustrations that establish
491
+ visual mood but don't convey load-bearing information.
492
+ - **Hamburger / kebab / overflow icons in a header**: these are well-
493
+ recognized affordances. As long as the icon shape is roughly
494
+ visible at all (white-on-dark, black-on-light), call PASS — users
495
+ recognize the pattern from convention, not from precise contrast.
496
+
497
+ # What 1.4.11 DOES cover — eligible for FAIL
498
+
499
+ - Button borders + backgrounds where text contrast doesn't tell you
500
+ "this is interactive" (especially "ghost" or "outline" buttons).
501
+ - Form input borders distinguishing the field from page background.
502
+ - Focus indicators (if visible in the screenshot).
503
+ - Checkbox / radio borders.
504
+ - Status indicators conveying meaning by COLOR ALONE (red error dot,
505
+ green success dot) where the dot is the only signal — those need
506
+ 3:1 against their adjacent background.
507
+ - Chart series, data viz colors that distinguish lines / bars.
508
+ - Toggle switch handles + tracks.
509
+
510
+ # Strict-fail rules
511
+
512
+ - ≥2 distinct UI components below 3:1 contrast against their
513
+ immediate background. ONE borderline case is "uncertain", not fail.
514
+ - The component must be one of the "DOES cover" items above. A 1px
515
+ divider is NEVER a fail.
516
+
517
+ Verdict:
518
+ - "pass" = every visible UI component / meaningful icon has clearly
519
+ distinguishable contrast against its adjacent surroundings. ALSO
520
+ default to PASS when the only low-contrast items you see are in
521
+ the exempt list above.
522
+ - "fail" = ≥2 visible UI components in the "DOES cover" list are
523
+ below 3:1 against their adjacent surroundings. Name each
524
+ specifically (e.g., "ghost-style 'Cancel' button border is nearly
525
+ invisible against the page bg").
526
+ - "uncertain" = screenshot is unreadable, page has too few visible
527
+ UI surfaces to judge confidently, OR you see one borderline case
528
+ but can't confirm it's not exempt.
529
+
530
+ When in doubt, prefer PASS or UNCERTAIN over FAIL. Decoration mis-
531
+ classification is the #1 false-positive trap on this rule.
532
+
533
+ ${ue}`}function Lc(e,t,a){const n=t.map(i=>` - DOM #${i.domIndex} → Visual #${i.visualIndex} | selector ${i.selector} | at (x=${Math.round(i.rect.x)}, y=${Math.round(i.rect.y)}, w=${Math.round(i.rect.w)}, h=${Math.round(i.rect.h)}) | text: "${i.textSnippet.slice(0,80).replace(/"/g,"'")}"`).join(`
534
+ `);return`You are an accessibility auditor evaluating reading-order quality (WCAG 1.3.2 — Meaningful Sequence).${a?`
535
+
536
+ **Previous verdict on this same page:** ${a.verdict.toUpperCase()} (${(a.confidence*100).toFixed(0)}% confidence)
537
+ Reasoning: "${a.reasoning.slice(0,400).replace(/"/g,"'")}"
538
+
539
+ **Stability anchor:** if you previously called this PASS at decent confidence (i.e., the multi-column / banked layout was legitimate), call it PASS again unless the flagged issues below show a CONCRETE new break (text appearing out of meaningful order, caption separated from image, broken paragraph flow). Heuristic-flagged DOM-vs-visual divergences alone don't justify a flip if the prior verdict already considered them legitimate. Run-to-run model variance is NOT new evidence. **Returning UNCERTAIN when you previously returned PASS also counts as a flip — don't hedge to uncertain unless the flagged issues are ACTUALLY ambiguous in a way they weren't before. The default action when nothing changed is to return the previous verdict.**
540
+ `:""}
541
+
542
+ A screenshot of the page (${e}) is attached. A heuristic compared DOM source order to visual position (top-to-bottom row bins, then left-to-right within a row) and flagged ${t.length} element(s) whose DOM index diverges notably from visual index. Screen readers read the DOM in source order, so divergence CAN indicate a real problem — but multi-column / banked / grid layouts trigger the heuristic by design and are legitimate.
543
+
544
+ Flagged elements:
545
+ ${n}
546
+
547
+ Evaluate whether the flagged divergences represent REAL reading-order breaks that would confuse a screen-reader user, or normal multi-column / banked layout patterns. Per WCAG 1.3.2: the order must preserve the MEANING of the content — not strict top-to-bottom-left-to-right.
548
+
549
+ Legitimate divergence patterns (these are PASS):
550
+ - Multi-column / newspaper layouts: DOM reads column 1 entirely, then column 2 — visual scan groups by row.
551
+ - Skip-links ("Skip to main content"): visually positioned near focus styling, DOM-early on purpose.
552
+ - Caption-before-image: caption placed before its image in DOM so SR users get context before alt-text.
553
+ - Modal / popover triggers near the document-end portal: the trigger is visually inline with content but DOM-near where the portal mounts.
554
+ - Grouped card layouts: each card's contents are DOM-contiguous, but cards visually wrap into rows.
555
+ - Header/nav blocks vs. main content: nav DOM-early is correct even when visually rendered alongside or below the hero.
556
+
557
+ Real reading-order breaks (these are FAIL):
558
+ - Out-of-order paragraphs within a single text block (paragraph 3 of an article appearing in DOM after paragraph 5).
559
+ - Captions, footnotes, or annotations DOM-far from the content they reference, where they're visually adjacent.
560
+ - Form labels DOM-after their inputs in a way that breaks SR association.
561
+ - Headings that DOM-jump regions (h2 of section B inside section A).
562
+
563
+ Verdict:
564
+ - "pass" = every flagged divergence fits one of the legitimate patterns above. Multi-column / banked layouts default to PASS unless internal scrambling is obvious in the screenshot.
565
+ - "fail" = ≥1 flagged divergence is a real reading-order break. Cite the specific selector(s) and why.
566
+ - "uncertain" = the screenshot is missing OR the layout is too ambiguous to judge confidently.
567
+
568
+ When in doubt, prefer "uncertain" over "fail". Multi-column false positives are the most common trap on modern web pages.
569
+
570
+ ${ue}`}const Ht=32;function di(e){const t=[];let a=null;for(const n of e){const o=Math.round(n.rect.y/Ht);a===null||a.row!==o?(a={row:o,items:[n]},t.push(a)):a.items.push(n)}return t}function Pc(e,t,a){const o=di(t).map(r=>{const c=r.items.map(l=>` ${l.ordinal}. [${l.role||l.selector.split(" ").pop()||"?"}] "${l.name||"(no name)"}" — selector ${l.selector} — x=${Math.round(l.rect.x)}, w=${Math.round(l.rect.width)}`).join(`
571
+ `);return` Row ~y=${r.row*Ht} (${r.items.length} item${r.items.length===1?"":"s"}):
572
+ ${c}`}).join(`
573
+ `);return`You are an accessibility auditor evaluating tab-order quality (WCAG 2.4.3 — Focus Order).${a?`
574
+
575
+ **Previous verdict on this same page:** ${a.verdict.toUpperCase()} (${(a.confidence*100).toFixed(0)}% confidence)
576
+ Reasoning: "${a.reasoning.slice(0,400).replace(/"/g,"'")}"
577
+
578
+ **Stability anchor:** if you previously called this PASS at decent confidence, you should call it PASS again UNLESS the screenshot OR the tab sequence shows specific new evidence that changed (a new positive tabindex, a focus-trap-jump pattern, a region visited twice non-contiguously, etc.). State explicitly what changed if you're flipping the verdict. Run-to-run model variance is NOT new evidence. **Returning UNCERTAIN when you previously returned PASS also counts as a flip — don't hedge to uncertain unless the screenshot/sequence is ACTUALLY ambiguous in a way it wasn't before. The default action when nothing changed is to return the previous verdict.**
579
+ `:""}
580
+
581
+ A screenshot of the page (${e}) is attached. The page's keyboard-tabbable elements were walked in order; the sequence the user actually encountered is below.
582
+
583
+ rc.181 — RED NUMBERED CIRCLES on the screenshot mark each tab-stop's position. The number IS the tab-order ordinal. Use the circles' visual positions as the primary signal: trace 1 → 2 → 3 → ... across the screenshot and judge whether the path follows a logical reading sequence. The selector/rect list below is supplementary; if you can resolve the question from the circles alone, that's preferred. If the circles are dense / overlapping in a region, that's a visual cue about a multi-column or banked layout.
584
+
585
+ rc.120 — Elements are grouped by VISUAL ROW (bucketed by document-relative y, ${Ht}px buckets) so you don't have to interpret sub-pixel y-coordinate noise from flex/grid layout. Items within the same row are visually side-by-side; rows are visually stacked.
586
+
587
+ Tab sequence (in tab order, grouped by visual row):
588
+ ${o}
589
+
590
+ Evaluate whether the focus order preserves the meaning + operability of the page for a keyboard or screen-reader user, per WCAG 2.4.3. A "logical" order:
591
+ - Generally follows reading order WITHIN A REGION. Tab moves through a region's items in reading order, then advances to the next region.
592
+ - WITHIN A SINGLE ROW, x-order (LTR for English) is what matters. Items 8 and 9 sitting in the same row at x=140 and x=320 are simply side-by-side buttons; that's not a tab-order break.
593
+ - ACROSS ROWS, the expected pattern is usually top-to-bottom, but multi-column, banked, sticky-header, and footer-jump-back layouts are common and legitimate.
594
+ - Y-coordinate reversals across rows are EXPECTED when regions are physically nested or stacked unusually (sticky headers, banked phase cards, footer links rendered after a deep scroll area).
595
+ - The relevant question is NOT "does y always increase?" but "would a screen-reader user understand WHY each region is encountered in the order it is?"
596
+
597
+ Strict-fail signals (require multiple to call fail):
598
+ - A region's INTERNAL items are scrambled (e.g., card title last, action button first within the SAME row group).
599
+ - A SINGLE element is skipped entirely (interactive but never receives focus).
600
+ - Focus jumps INTO a region, then BACK OUT, then BACK IN — the same region visited twice non-contiguously.
601
+ - Positive tabindex values are observable in the recorded order (e.g., a deeply-nested element appears first).
602
+
603
+ **rc.271 — Important rule about "skipped" elements (bug 4 from external audit):**
604
+ Only count an element as "skipped" if you have CONCRETE evidence it's actually interactive. Concrete signals: a <button> / <a href> tag, role="button" / role="link", an onClick / event handler attribute, tabindex >= 0, OR a clearly interactive cursor:pointer hover signal in the visual. **Pill / badge styling (yellow background, rounded corners, bold text) is NOT an interactivity signal.** Decorative status badges and section labels are commonly styled this way without being interactive — they're meant to look distinctive, not clickable. If the only signal for "this should be focusable" is "it looks like a button," DO NOT count it as skipped — that's a styling judgment, not an a11y judgment.
605
+
606
+ Verdict:
607
+ - "pass" = order is logical within regions, even if rows are visited non-strictly-top-to-bottom. Multi-column / multi-region pages PASS by default unless internal scrambling is obvious.
608
+ - "fail" = ≥2 strict-fail signals above. Cite the specific items by ordinal AND name the row(s) they're in.
609
+ - "uncertain" = the screenshot is missing, the sequence is too short to judge (<5 items), OR you see row-order patterns but the screenshot suggests a multi-region layout you can't fully resolve.
610
+
611
+ When in doubt, prefer "uncertain" over "fail". Multi-column / banked layouts are the most common false-fail trap. Sub-pixel row noise CANNOT cause a fail any more (we bucketed it out for you).
612
+
613
+ ${ue}`}function Uc(e,t){const a=t.map((n,o)=>`${o+1}. [${n.role}] selector=${n.selector}
614
+ visibleText: "${n.visibleText}"
615
+ accessibleName: "${n.accessibleName}"`).join(`
616
+ `);return`You are an accessibility auditor evaluating WCAG 2.5.3 (Label in Name) for ${e}.
617
+
618
+ WCAG 2.5.3 says: when an interactive control has BOTH visible text AND a programmatically-determined accessible name, the accessible name MUST contain the visible text (case-insensitive, leading non-text characters like icons ignored). This is what enables a speech-recognition user saying "click Submit" to actually activate the button labelled Submit.
619
+
620
+ A screenshot of the page is attached for visual context.
621
+
622
+ The candidates below all failed a strict substring match (the programmatic check). Your job: classify each as a genuine 2.5.3 failure OR an acceptable variation. Common acceptable variations:
623
+ - Icon-prefix buttons: visible "▶ Play" → accessible "Play" — PASS (icons are not text)
624
+ - Visible text wraps invisible icon glyphs in a font-family that AI can see in the screenshot but doesn't appear in DOM text
625
+ - Trailing whitespace, smart-quote vs ASCII-quote, en-dash vs hyphen
626
+ - Number formatting: visible "1,234" → accessible "1234" — PASS unless the spoken form would differ
627
+ - Singular/plural variation: visible "Item" → accessible "Items" — FAIL (speech-recognition would miss)
628
+ - Truncated visible text (with ellipsis) when accessible has the full text — PASS
629
+ - Translation/localization mismatch (visible English, accessible language code) — FAIL
630
+
631
+ Candidates:
632
+ ${a}
633
+
634
+ Verdict over the whole page (NOT per-candidate):
635
+ - "pass" = every candidate above is an acceptable variation
636
+ - "fail" = at least one candidate is a genuine 2.5.3 failure that would break speech recognition
637
+ - "uncertain" = the screenshot is unreadable OR ambiguity cannot be resolved without page interaction
638
+
639
+ Cite specific candidates by ordinal (1, 2, 3...) and selector in the reasoning, especially when calling fail.
640
+
641
+ ${ue}`}function _c(e,t,a){const o=di(t).map(i=>{const r=i.items.map(c=>` ${c.ordinal}. [${c.role||c.selector.split(" ").pop()||"?"}] "${c.name||"(no name)"}" — selector ${c.selector} — x=${Math.round(c.rect.x)}, w=${Math.round(c.rect.width)}`).join(`
642
+ `);return` Row ~y=${i.row*Ht} (${i.items.length} item${i.items.length===1?"":"s"}):
643
+ ${r}`}).join(`
644
+ `);return`You are an accessibility auditor making a cascade-fallback judgment on tab-order quality (WCAG 2.4.3 — Focus Order).
645
+
646
+ CONTEXT — what already happened:
647
+ 1. Primary AI judgment was UNCERTAIN. The primary auditor said: "${a.replace(/"/g,"'").slice(0,600)}"
648
+ 2. WCAG 2.1.2 No Keyboard Trap PASSED on the same DOM. Tab walks forward AND Shift+Tab walks back through every focusable. The focus path is empirically functional.
649
+ 3. A programmatic multi-column-layout detector ran and found NO conventional multi-column pattern (CSS grid with multiple tracks, flex row-wrap, css-columns, multi-child row-flex). So whatever caused the uncertainty is NOT a conventional layout pattern.
650
+
651
+ Your narrower job: given the empirical 2.1.2 PASS and the absence of a conventional multi-column layout, decide whether the tab sequence is a known false-positive pattern from the LONG TAIL that the programmatic detector cannot see. Look at the attached screenshot of ${e}.
652
+
653
+ LONG-TAIL FALSE-POSITIVE PATTERNS — call PASS when you see any of these AND the sequence is consistent with the pattern:
654
+ - Custom JS focus management — focus() calls in onClick handlers, focus traps inside dialogs/disclosures, modal open/close re-routes focus
655
+ - Roving tabindex composite widgets — menus, radio groups, listboxes, treeviews, tablists where only one item has tabindex=0 and arrow keys move between children
656
+ - Sticky sidebars or floating navigation — main content tabbed first, then the sticky sidebar/nav block
657
+ - Skip-link patterns that intentionally jump to main content, then come back to the header on a second pass
658
+ - Table-based layouts where Tab traverses cell-by-cell within columns
659
+ - Absolute positioning that creates visual side-by-side columns the CSS detector can't recognize
660
+ - Header navigation that wraps onto a second visual row at narrow viewports
661
+ - Carousel / slider widgets where tabindex follows logical (not visual-x) order
662
+
663
+ LONG-TAIL TRUE-FAIL PATTERNS — call FAIL when you see these AND the sequence reflects them:
664
+ - Positive tabindex values producing observably out-of-source-order focus (e.g., a deep element appears first)
665
+ - A region's internal items scrambled (card title last, action button first within the same row group)
666
+ - A single element is skipped (clearly interactive but never receives focus in the sequence)
667
+ - Focus enters a region, leaves, then re-enters non-contiguously without a UI affordance that explains it
668
+
669
+ Tab sequence (grouped by visual row):
670
+ ${o}
671
+
672
+ CRITICAL DECISIVENESS BIAS: the primary already returned uncertain. If you ALSO return uncertain, the system will escalate to FAIL + require human verification. So genuine uncertain is fine, but DO NOT default to uncertain when you can make a reasoned call. Specifically:
673
+ - If the sequence is consistent with ANY long-tail-pass pattern above → PASS
674
+ - If the sequence shows ANY long-tail-fail pattern → FAIL
675
+ - ONLY return uncertain if the screenshot is unreadable, the sequence is empty/too short (<3 items), or you genuinely cannot tell what pattern is at play
676
+
677
+ ${ue}`}function Fc(e,t){return`You are an accessibility auditor verifying alt text correctness for screen-reader users (WCAG 1.1.1).
678
+
679
+ Image is attached. Current alt text: ${e?`"${e}"`:"(empty / missing)"}
680
+ ${t?`
681
+ Nearby page context: ${t.slice(0,500)}`:""}
682
+
683
+ Judge whether the alt text accurately represents the image's content or function:
684
+ - "pass" = alt is meaningful and accurately describes what's in the image OR alt="" is correct because the image is decorative/redundant
685
+ - "fail" = alt is wrong, misleading, generic ("image", "photo"), filename-style ("img-1234.jpg"), or omitted on a meaningful image
686
+ - "uncertain" = you can't tell from the image alone whether the alt is appropriate
687
+
688
+ ${ue}`}function Wc(e,t){return`You are an accessibility auditor verifying that headings describe their sections (WCAG 2.4.6).
689
+
690
+ Heading text: "${e}"
691
+ Section content beneath it (first 800 chars): "${t.slice(0,800)}"
692
+
693
+ Judge whether the heading actually describes what the section contains:
694
+ - "pass" = heading meaningfully describes the section's topic
695
+ - "fail" = heading is non-descriptive ("Section 1", "Welcome", "More info"), unrelated to the content, or generic placeholder
696
+ - "uncertain" = section content is too short or ambiguous to judge
697
+
698
+ ${ue}`}function qc(e){return`You are an accessibility auditor checking instructions for sensory-characteristic dependencies (WCAG 1.3.3).
699
+
700
+ Instruction text: "${e.slice(0,800)}"
701
+
702
+ WCAG 1.3.3 says instructions must NOT rely solely on shape, size, color, visual location, sound, or orientation. Judge:
703
+ - "fail" = instruction relies on a sensory characteristic without a programmatic identifier (e.g., "click the round button", "the green save icon", "the link on the right")
704
+ - "pass" = instruction uses programmatic identifiers (button label, link text) — sensory cues are fine when accompanied by a name
705
+ - "uncertain" = ambiguous whether a sensory descriptor is the only cue
706
+
707
+ ${ue}`}function Gc(e,t,a){return`You are an accessibility auditor verifying ARIA role appropriateness (WCAG 4.1.2).
708
+
709
+ Element (truncated): ${e.slice(0,600)}
710
+ Computed role: ${t}
711
+ Surrounding HTML context: ${a.slice(0,500)}
712
+
713
+ Judge whether the role is semantically appropriate for this element's apparent purpose:
714
+ - "pass" = role matches the element's actual behavior + content + interactivity
715
+ - "fail" = role is misused — e.g., role="button" on a div with no event handlers, role="dialog" on a non-modal overlay, role="navigation" on a non-nav block
716
+ - "uncertain" = can't tell the element's true behavior from this static snapshot
717
+
718
+ ${ue}`}function Vc(e){return`You are an accessibility auditor checking for WCAG 1.4.5 (Images of Text) compliance.
719
+
720
+ WCAG 1.4.5 says: if text can be presented using actual text instead of a baked-in image, it should be. Exceptions: logos, decorative use, or text that's essential to a specific visual presentation (e.g., a screenshot of code).
721
+
722
+ Image is attached.${e?`
723
+ Accessible name (alt / aria-label) of this image: "${e.slice(0,200)}"`:""}
724
+
725
+ Judge whether the image bakes substantial text into the raster:
726
+ - "fail" = the image contains body text, headings, button labels, product names, headlines, slogans, or paragraphs that could/should be HTML text. Decorative typography (e.g., a logo) does NOT count as fail unless it's clearly extended text.
727
+ - "pass" = the image is purely visual (photo, illustration, icon) with no substantial baked-in text, OR the baked-in text is a logo / brand mark / unavoidable visual artifact (code screenshot, equation, etc.).
728
+ - "uncertain" = the image is too small / low-resolution to tell, or the text is partial/ambiguous.
729
+
730
+ ${ue}`}function jc(e,t){return`You are an accessibility auditor checking for WCAG 1.4.1 (Use of Color) compliance.
731
+
732
+ WCAG 1.4.1 says: color must not be the only visual means of conveying information, indicating an action, prompting a response, or distinguishing a visual element. There must always be a parallel non-color signal — a text label, an icon shape, a position cue, etc.
733
+
734
+ Element markup (truncated):
735
+ ${e.slice(0,800)}
736
+
737
+ Context: ${t.slice(0,400)}
738
+
739
+ Judge whether the element conveys meaning ONLY through color:
740
+ - "fail" = a status / state / action / category is signaled ONLY by color with no text label, no shape difference, no icon glyph, and no aria-label. Common examples: a colored dot or pill conveying status with empty text content + no aria-label; a validation border (aria-invalid="true") with no accompanying icon or text message; "click the red button" instructions in CONTENT.
741
+ - "pass" = the element has a visible text label, an icon child, an aria-label, OR the color is decorative — any non-color signal that a color-blind / grayscale user could detect.
742
+ - "uncertain" = markup is ambiguous (e.g. the contextDescription says "has icon child" but the icon may itself be color-only).
743
+
744
+ ${ue}`}function zc(e,t){return`You are an accessibility auditor checking for WCAG 2.4.4 (Link Purpose in Context).
745
+
746
+ WCAG 2.4.4 says: the purpose of each link must be determinable from the link text alone, OR from the link text together with its programmatically-determined context (the surrounding sentence / paragraph / list item).
747
+
748
+ Link visible text: "${e.slice(0,200)}"
749
+ Surrounding paragraph / list item / sentence (programmatically associated): "${t.slice(0,800)}"
750
+
751
+ Judge whether a screen-reader user activating "links list" navigation could understand the link's destination from either the text alone or the link+context:
752
+ - "fail" = generic link text ("click here", "learn more", "read more", "this", "here", "more") where surrounding context ALSO does not clarify the destination.
753
+ - "pass" = link text alone is descriptive, OR the surrounding context unambiguously clarifies the destination (e.g., "Pricing details are available — learn more →" makes "learn more" passable).
754
+ - "uncertain" = link text is borderline-generic and context is ambiguous.
755
+
756
+ ${ue}`}function Hc(e,t){return`You are an accessibility auditor checking for WCAG 3.1.5 (Reading Level) and broader cognitive-accessibility issues.
757
+
758
+ A "wall of text" is body content that exceeds reasonable read-without-fatigue size with no structural breaks (subheadings, lists, paragraphs, blockquotes). Cognitively-disabled users + speed-skim readers can't scan it.
759
+
760
+ Block text (first 1200 chars): "${e.slice(0,1200)}"
761
+ Structural children inside the block (paragraphs / lists / sub-headings): ${t}
762
+
763
+ Judge whether this block presents a wall-of-text problem:
764
+ - "fail" = block is long-form prose (≥ 300 words) with effectively NO structural breaks (no sub-headings inside it, fewer than ~3 paragraphs, no lists), making it hard to scan.
765
+ - "pass" = block is short enough OR has adequate structural breaks (paragraphs, sub-headings, lists) for scannable reading.
766
+ - "uncertain" = block is borderline (~200-300 words with minimal structure).
767
+
768
+ ${ue}`}function Bc(e,t){return`You are an accessibility auditor checking for WCAG 3.1.1 (Language of Page) and 3.1.2 (Language of Parts).
769
+
770
+ The page declares its default human language via the HTML \`lang\` attribute. Screen readers use that to pick the correct pronunciation engine. If the declared language doesn't match the actual content, screen-reader output is unintelligible.
771
+
772
+ Declared language (from <html lang="…">): "${e||"(none / empty)"}"
773
+ Body text sample (first 1200 chars): "${t.slice(0,1200)}"
774
+
775
+ Judge whether the declared language matches the content:
776
+ - "fail" = content is clearly in a DIFFERENT language than declared (e.g., lang="en" but text is Spanish), OR no lang was declared and content is non-trivial.
777
+ - "pass" = declared language matches the content's primary language.
778
+ - "uncertain" = content is too short, too multilingual, or in a language closely related to the declared one.
779
+
780
+ ${ue}`}function Kc(e){const t=e.targetLevel==="AAA"?e.fontSize>=18.66||e.fontSize>=14&&e.fontWeight>=700?"4.5":"7.0":e.fontSize>=18.66||e.fontSize>=14&&e.fontWeight>=700?"3.0":"4.5";return`You are a color accessibility expert. Suggest replacement colors that fix a contrast failure while preserving design intent.
781
+
782
+ Current pair (FAILING):
783
+ - Foreground: ${e.foreground}
784
+ - Background: ${e.background}
785
+ - Font size: ${e.fontSize}px, weight: ${e.fontWeight}
786
+ - Required contrast for WCAG ${e.targetLevel}: ${t}:1
787
+
788
+ ${e.paletteHints&&e.paletteHints.length>0?`Available palette tokens (prefer these to keep brand cohesion):
789
+ ${e.paletteHints.slice(0,30).map(a=>` - ${a}`).join(`
790
+ `)}`:"No palette tokens discovered. Stay close in hue + saturation to the original colors."}
791
+
792
+ ${e.brandIntent?`Brand intent: ${e.brandIntent}`:""}
793
+
794
+ Suggest up to 3 candidate replacement pairs. Each candidate should:
795
+ 1. Pass the required ${t}:1 contrast (verify your math).
796
+ 2. Stay close in hue to the original (don't swap a warm palette for a cool one).
797
+ 3. Prefer modifying ONE color (foreground OR background) over both, unless the original is severely off.
798
+ 4. If palette tokens are available, prefer in-palette colors.
799
+
800
+ Respond ONLY with a JSON object — no preamble, no markdown:
801
+ {
802
+ "verdict": "suggested" | "no-suggestion",
803
+ "candidates": [
804
+ { "foreground": "#hexHEX", "background": "#hexHEX", "contrast": 4.6, "rationale": "1 sentence" }
805
+ ],
806
+ "reasoning": "1-3 sentences explaining your overall approach or why no suggestions are possible"
807
+ }`}function Yc(e){return`You are an accessibility auditor writing the executive summary for an automated audit report.
808
+
809
+ Audience: ${e.framing==="defense"?"an internal stakeholder team (engineering + legal + risk-management)":e.framing==="evidence"?"an attorney or accessibility advocate considering a notice-and-cure step":"a non-technical site owner who needs to understand their accessibility status"}.
810
+ Framing: ${e.framing} report.
811
+
812
+ Audit data:
813
+ - Page audited: ${e.pageUrl}
814
+ - Audit date: ${e.auditDate}
815
+ - Compliance grade: ${e.grade}
816
+ - Risk tier: ${e.riskTier}
817
+ - Severity totals: ${e.totals.critical} critical, ${e.totals.serious} serious, ${e.totals.moderate} moderate, ${e.totals.minor} minor (unique: ${e.totals.unique})
818
+ - Top categories driving risk: ${e.riskDrivers.length>0?e.riskDrivers.join(", "):"none flagged"}
819
+ ${e.priorAuditCount!==void 0&&e.priorAuditCount>0?`- Prior audit count in record: ${e.priorAuditCount} (ongoing-diligence context)`:""}
820
+
821
+ Tone:
822
+ - Plain-spoken. No legalese, no marketing hype.
823
+ - Direct about risk without alarmism.
824
+ - "Defense" framing: emphasize good-faith remediation effort + that this audit ran three evaluation layers (axe-core rule-based, wcagcheckr DOM analyzers, AI walkthroughs) covering all 50 WCAG 2.1 AA criteria structurally; human spot-checks remain advisable for content-meaning criteria (caption accuracy, alt-text correctness, sensory cues).
825
+ - "Evidence" framing: present findings objectively + suggest notice-and-cure procedures over immediate litigation.
826
+ - "Owner-report" framing: explain what the findings mean for the site owner in everyday terms, no code talk.
827
+
828
+ Constraints:
829
+ - Do NOT suggest specific dollar amounts, settlement figures, or legal predictions.
830
+ - Do NOT claim the audit proves compliance OR non-compliance — it documents detected violations.
831
+ - Do NOT use "ADA-compliant" or "WCAG-compliant" as adjectives — say "documented violations of [specific WCAG SCs]" instead.
832
+
833
+ Respond ONLY with a JSON object — no preamble, no markdown:
834
+ {
835
+ "lead": "2-3 sentence headline framing",
836
+ "body": "2-3 paragraphs of risk explanation + audit-date framing + ongoing-diligence context if applicable. ~150-300 words total.",
837
+ "closer": "1-2 sentence closing line"
838
+ }`}function Jc(e,t){var r;const o=(((r=e.content.find(c=>c.type==="text"))==null?void 0:r.text)??"").replace(/```(?:json)?/g,"").trim().match(/\{[\s\S]*\}/),i=e.usage.input_tokens*t.input+e.usage.output_tokens*t.output;if(!o)return{verdict:"error",candidates:[],reasoning:"Model returned no parseable JSON",costUsd:i,model:e.model};try{const c=JSON.parse(o[0]),l=(c.candidates??[]).slice(0,3).map(d=>({foreground:typeof d.foreground=="string"?d.foreground:"",background:typeof d.background=="string"?d.background:"",contrast:typeof d.contrast=="number"?d.contrast:0,rationale:typeof d.rationale=="string"?d.rationale:""})).filter(d=>d.foreground&&d.background);return{verdict:c.verdict==="suggested"&&l.length>0?"suggested":"no-suggestion",candidates:l,reasoning:typeof c.reasoning=="string"?c.reasoning:"",costUsd:i,model:e.model}}catch{return{verdict:"error",candidates:[],reasoning:"Could not parse color suggestions",costUsd:i,model:e.model}}}function Xc(e){var n,o,i,r,c,l;const t=(n=e.element.styles)==null?void 0:n.effectiveBackground,a=e.ruleId==="color-contrast"||e.ruleId==="color-contrast-enhanced"?`
839
+ This is a COLOR-CONTRAST evaluation. axe-core couldn't determine the
840
+ contrast ratio (commonly because the element's own background-color
841
+ is transparent and axe didn't walk the DOM cascade to find the
842
+ effective rendered backdrop). The audit pipeline has already done
843
+ that walk for you — use the effectiveBackground below, NOT the
844
+ element's own background-color, when computing contrast.
845
+
846
+ Computed styles axe captured at audit time:
847
+ - Foreground (color): ${((o=e.element.styles)==null?void 0:o.foreground)??"unknown"}
848
+ - Element's OWN background-color: ${((i=e.element.styles)==null?void 0:i.background)??"unknown"} (often transparent; ignore unless solid)
849
+ - Font size: ${((r=e.element.styles)==null?void 0:r.fontSize)??"unknown"} px
850
+ - Font weight: ${((c=e.element.styles)==null?void 0:c.fontWeight)??"unknown"}
851
+ - Text sample: ${JSON.stringify(((l=e.element.styles)==null?void 0:l.textSample)??"")}
852
+
853
+ EFFECTIVE BACKGROUND (the actual color rendered beneath this text,
854
+ captured by walking up the DOM cascade until we hit an opaque
855
+ ancestor background-color):
856
+ - Effective background color: ${(t==null?void 0:t.color)??"unknown"}
857
+ - Came from ancestor: ${(t==null?void 0:t.fromAncestor)??"(the element itself)"}
858
+ - Background-image present in cascade: ${t!=null&&t.hasBackgroundImageInChain?`YES (on ${t.imageAncestor??"an ancestor"})`:"no"}
859
+
860
+ WCAG ${e.targetLevel} contrast thresholds:
861
+ - Normal text (< 18pt, or < 14pt and not bold): 4.5:1 for AA, 7:1 for AAA
862
+ - Large text (≥ 18pt OR ≥ 14pt bold): 3:1 for AA, 4.5:1 for AAA
863
+
864
+ Reasoning approach:
865
+ 1. Bold = font-weight >= 700. Large text threshold uses size + weight.
866
+ 2. If 'Background-image present in cascade' is **no** → compute the
867
+ contrast ratio between foreground and effectiveBackground.color.
868
+ This is what the browser is actually rendering. Return 'pass' or
869
+ 'fail' against the appropriate threshold. Cite the computed ratio.
870
+ 3. If 'Background-image present in cascade' is **YES** → the
871
+ effectiveBackground.color is what shows through the image's
872
+ transparent regions, but the image itself may dominate visually.
873
+ Reason about it:
874
+ - If the image is likely dark and text is light (or vice versa),
875
+ contrast is typically OK — lean 'pass' if foreground +
876
+ effectiveBackground also pass, and call out the assumption.
877
+ - If you genuinely can't tell whether the image dominates with a
878
+ color that breaks contrast (text-over-photo scenarios), return
879
+ 'uncertain' and explain that vision capability is needed.
880
+ 4. NEVER return 'uncertain' just because the element's own background
881
+ is transparent — that's expected and the effectiveBackground
882
+ handles it.
883
+
884
+ Return one of:
885
+ - 'pass' if contrast comfortably meets the threshold (give the computed ratio).
886
+ - 'fail' if contrast is below the threshold.
887
+ - 'uncertain' ONLY when a background-image likely dominates visually
888
+ and you cannot reasonably infer its tone.
889
+ `:`
890
+ This is a general WCAG conformance evaluation for rule '${e.ruleId}'.
891
+ axe-core ran but couldn't conclude pass/fail for this element.
892
+ Use the failureSummary + outerHTML to determine if the rule is
893
+ satisfied. If the evidence is insufficient, return 'uncertain'.
894
+ `;return`You are a WCAG accessibility auditor resolving an axe-core "incomplete"
895
+ result. axe ran the rule successfully but couldn't fully determine
896
+ the outcome on this specific element.
897
+
898
+ Rule: ${e.ruleId}
899
+ Page: ${e.pageUrl}
900
+ Selector: ${e.element.selector}
901
+ axe-core diagnostic: ${e.element.failureSummary??"(no detail provided)"}
902
+
903
+ outerHTML snippet:
904
+ ${e.element.outerHTMLSnippet??"(not captured)"}
905
+ ${a}
906
+
907
+ Respond with strict JSON (no markdown fences):
908
+ {
909
+ "verdict": "pass" | "fail" | "uncertain",
910
+ "reasoning": "one or two sentences explaining the determination, citing the contrast ratio for color-contrast"
911
+ }`}function Qc(e,t,a){var c;const n=((c=e.content.find(l=>l.type==="text"))==null?void 0:c.text)??"",i=n.replace(/```(?:json)?/g,"").trim().match(/\{[\s\S]*\}/),r=e.usage.input_tokens*t.input+e.usage.output_tokens*t.output;if(!i)return{verdict:"uncertain",reasoning:n||"AI returned no parseable response.",costUsd:r,model:e.model||a};try{const l=JSON.parse(i[0]);return{verdict:l.verdict==="pass"||l.verdict==="fail"?l.verdict:"uncertain",reasoning:typeof l.reasoning=="string"&&l.reasoning?l.reasoning:"AI returned no rationale.",costUsd:r,model:e.model||a}}catch{return{verdict:"uncertain",reasoning:n,costUsd:r,model:e.model||a}}}function Zc(e,t){var r;const a=((r=e.content.find(c=>c.type==="text"))==null?void 0:r.text)??"",o=a.replace(/```(?:json)?/g,"").trim().match(/\{[\s\S]*\}/),i=e.usage.input_tokens*t.input+e.usage.output_tokens*t.output;if(!o)return{lead:"",body:a||"Summary generation failed.",closer:"",costUsd:i,model:e.model};try{const c=JSON.parse(o[0]);return{lead:typeof c.lead=="string"?c.lead:"",body:typeof c.body=="string"?c.body:"",closer:typeof c.closer=="string"?c.closer:"",costUsd:i,model:e.model}}catch{return{lead:"",body:a,closer:"",costUsd:i,model:e.model}}}function ce(e){if(!e.enabled)return{ok:!1,reason:"AI augmentation is disabled in settings"};if(!e.apiKey)return{ok:!1,reason:"No API key configured"};switch(e.provider){case"anthropic":return{ok:!0,client:Ic({apiKey:e.apiKey,model:e.model})};case"openai":return{ok:!1,reason:"OpenAI provider not yet implemented"};case"gemini":return{ok:!1,reason:"Gemini provider not yet implemented"};default:return{ok:!1,reason:`Unknown provider: ${e.provider}`}}}const pt=J("consistency-ai-suppression");async function el(e,t={}){var d;if(e.length===0)return{emitted:[],suppressed:0,uncertain:0,apiCalls:0};let a;try{const u=await chrome.storage.local.get("aiConfig");a=be(u.aiConfig)}catch{return pt.debug("aiConfig load failed; emitting heuristic findings unchanged"),{emitted:e,suppressed:0,uncertain:0,apiCalls:0}}const n=ce(a);if(!n.ok)return pt.info(`AI client unavailable (${n.reason}); emitting heuristic findings unchanged`),{emitted:e,suppressed:0,uncertain:0,apiCalls:0};const o=n.client;if(typeof o.judgeConsistencyDivergence!="function")return pt.info("AI client does not support judgeConsistencyDivergence; emitting heuristic findings unchanged"),{emitted:e,suppressed:0,uncertain:0,apiCalls:0};const i=[];let r=0,c=0,l=0;for(const u of e){if((d=t.signal)!=null&&d.aborted){i.push(u);continue}try{const h=await o.judgeConsistencyDivergence({wcagCriterion:u.wcagCriterion,ruleId:u.ruleId,description:u.description,details:u.details,pages:u.pages,signal:t.signal});if(l++,h.verdict==="pass"){r++;continue}if(h.verdict==="fail"){i.push({...u,aiVerdict:{verdict:"real-divergence",confidence:h.confidence??0,reasoning:h.reasoning??""}});continue}c++,i.push({...u,aiVerdict:{verdict:"uncertain",confidence:h.confidence??0,reasoning:h.reasoning??""}})}catch(h){pt.warn(`AI suppression failed for ${u.ruleId} ${u.wcagCriterion}: ${h instanceof Error?h.message:String(h)}`),i.push(u)}}return pt.info(`consistency AI suppression: ${l} API calls, ${r} false-positives suppressed, ${c} uncertain, ${i.length} emitted`),{emitted:i,suppressed:r,uncertain:c,apiCalls:l}}function tl(e){const t=new Map(e.breakpointPresets.map(n=>[n.id,n])),a=[];for(const n of e.breakpoints){const o=t.get(n);if(o)for(const i of e.directions)for(const r of e.themes){const c=e.ariaVariations.length===0?[null]:e.ariaVariations;for(const l of c)for(const d of e.pseudoStates)a.push({pseudoState:d,ariaVariation:l,theme:r,direction:i,breakpoint:o})}}return a}const ui=["color-contrast","color-contrast-enhanced","link-in-text-block"],al=["image-alt","input-image-alt","area-alt","svg-img-alt","object-alt","role-img-alt","label","select-name","aria-input-field-name","aria-toggle-field-name","form-field-multiple-labels","autocomplete-valid","label-content-name-mismatch","label-title-only","button-name","input-button-name","link-name","empty-button","empty-heading","empty-link","frame-title","heading-order","page-has-heading-one","landmark-one-main","landmark-no-duplicate-banner","landmark-no-duplicate-contentinfo","landmark-no-duplicate-main","landmark-unique","landmark-banner-is-top-level","landmark-complementary-is-top-level","landmark-contentinfo-is-top-level","landmark-main-is-top-level","region","bypass","empty-table-header","aria-allowed-attr","aria-required-attr","aria-required-children","aria-required-parent","aria-roles","aria-valid-attr-value","aria-valid-attr","aria-hidden-body","aria-hidden-focus","aria-allowed-role","aria-prohibited-attr","presentation-role-conflict","aria-text","aria-deprecated-role","aria-conditional-attr","aria-braille-equivalent","html-has-lang","html-lang-valid","html-xml-lang-mismatch","valid-lang","nested-interactive","tabindex","frame-focusable-content","no-focusable-content","focus-order-semantics","meta-viewport","meta-refresh","duplicate-id","duplicate-id-active","duplicate-id-aria","video-caption","audio-caption","table-fake-caption","scope-attr-valid","th-has-data-cells","td-headers-attr"];{const e=new Set(ui),t=al.filter(a=>e.has(a));t.length>0&&console.error("axe-rule-classification: rule(s) classified as BOTH state-dependent and markup-eligible:",t)}const nl=J("chrome-api"),ol="1.3",Vn=3e4;class il extends Error{constructor(t,a){super(`CDP ${t} timed out after ${a}ms`),this.method=t,this.timeoutMs=a,this.name="CdpTimeoutError"}}class hi{constructor(t){this.target=t}async attach(){await chrome.debugger.attach(this.target,ol)}async detach(){try{await chrome.debugger.detach(this.target)}catch(t){nl.debug("detach soft-failure",t)}}async send(t,a){const n=chrome.debugger.sendCommand(this.target,t,a);let o;const i=new Promise((r,c)=>{o=setTimeout(()=>c(new il(t,Vn)),Vn)});try{return await Promise.race([n,i])}finally{o!==void 0&&clearTimeout(o)}}}function rl(e){const t=e instanceof Error?e.message:String(e);return/Another debugger is already attached|Cannot attach to/i.test(t)}function pi(e){const t=e instanceof Error?e.message:String(e);return rl(e)?Cn("DEBUGGER_BUSY","DevTools is open on this tab. Close it to run the audit.",!0,{raw:t}):Cn("STATE_DRIVE_FAILED",t,!1)}function sl(e){const t=(a,n)=>e(a,n);return chrome.debugger.onDetach.addListener(t),()=>chrome.debugger.onDetach.removeListener(t)}const ge=J("state-driver"),cl=60*1e3,Re=new Map,Bt=new Map,st=new Map,ot=new Map,ll='button, a[href], input, textarea, select, summary, [tabindex]:not([tabindex="-1"]), [role="button"], [role="link"], [role="checkbox"], [role="radio"], [role="tab"], [role="menuitem"], [role="switch"], [role="combobox"], [role="option"]',It=new Map,dl={default:[],hover:["hover"],focus:["focus"],"focus-visible":["focus","focus-visible"],active:["active"],disabled:[]};async function fi(e){let t=Re.get(e);if(!t){t=new hi({tabId:e});try{await t.attach(),await t.send("Runtime.enable"),await t.send("Page.enable"),await t.send("DOM.enable"),await t.send("DOM.getDocument",{depth:-1,pierce:!0}),await t.send("CSS.enable")}catch(a){try{await t.detach()}catch{}throw a}Re.set(e,t),st.set(e,new Map),ul(e),ge.info("attached",{tabId:e})}return pl(e),t}function ul(e){const t=(a,n,o)=>{var r,c;if(a.tabId!==e)return;const i=st.get(e);if(i)if(n==="Runtime.executionContextCreated"){const l=o,d=l==null?void 0:l.context;d&&((r=d.auxData)!=null&&r.isDefault)&&((c=d.auxData)!=null&&c.frameId)&&i.set(d.auxData.frameId,d.id)}else if(n==="Runtime.executionContextDestroyed"){const l=o,d=l==null?void 0:l.executionContextId;if(d!==void 0)for(const[u,h]of i.entries())h===d&&i.delete(u)}else n==="Runtime.executionContextsCleared"&&i.clear()};chrome.debugger.onEvent.addListener(t)}async function hl(e){const t=Re.get(e);if(Re.delete(e),st.delete(e),ot.delete(e),It.delete(e),t)try{await t.detach()}catch{}}function pl(e){const t=Bt.get(e);t&&clearTimeout(t),Bt.set(e,setTimeout(()=>{da(e).catch(a=>ge.warn("idle detach failed",a))},cl))}async function da(e){const t=Re.get(e);t&&(await t.detach(),Re.delete(e)),st.delete(e),ot.delete(e),It.delete(e);const a=Bt.get(e);a&&(clearTimeout(a),Bt.delete(e))}function gi(e){const t=[{id:e.frame.id,url:e.frame.url}];for(const a of e.childFrames??[])t.push(...gi(a));return t}async function fl(e,t,a){var i;if(!a||a===0)return;const n=st.get(t);if(!n)return;let o;try{o=(i=(await chrome.scripting.executeScript({target:{tabId:t,frameIds:[a]},func:()=>location.href}))[0])==null?void 0:i.result}catch(r){ge.debug("frame URL lookup failed",r)}try{const r=await e.send("Page.getFrameTree"),c=gi(r.frameTree);if(o){const u=c.find(h=>h.url===o);if(u){const h=n.get(u.id);if(h!==void 0)return h}}const l=r.frameTree.frame.id,d=c.find(u=>u.id!==l);return d?n.get(d.id):void 0}catch(r){ge.warn("Page.getFrameTree failed",r);return}}async function gl(e,t,a){var r;const n={expression:`document.querySelector(${JSON.stringify(t)})`,objectGroup:"wcag-auditor",returnByValue:!1};a!==void 0&&(n.contextId=a);const i=(r=(await e.send("Runtime.evaluate",n)).result)==null?void 0:r.objectId;if(!i)return null;try{return(await e.send("DOM.requestNode",{objectId:i})).nodeId??null}finally{e.send("Runtime.releaseObject",{objectId:i}).catch(()=>{})}}async function mi(e){const t=ot.get(e);if(!t||t.length===0)return;const a=Re.get(e);if(a){for(const n of t)try{await a.send("CSS.forcePseudoState",{nodeId:n,forcedPseudoClasses:[]})}catch(o){ge.debug("forcePseudoState clear failed (node may be gone)",o)}ot.delete(e)}}async function ml(e,t){try{return(await e.send("DOM.querySelectorAll",{nodeId:t,selector:ll})).nodeIds??[]}catch(a){return ge.debug("querySelectorAll for interactive descendants failed",a),[]}}async function Ka(e,t,a,n){await chrome.scripting.executeScript({target:bn(e,t),func:(o,i)=>{const r=document.querySelector(o);r&&(i?r.setAttribute("disabled",""):r.removeAttribute("disabled"))},args:[a,n]})}async function bi(e,t){await chrome.scripting.executeScript({target:bn(e,t),func:()=>{const a=document.activeElement;a&&typeof a.blur=="function"&&a.blur()}})}async function yi(e,t){await chrome.scripting.executeScript({target:{tabId:e,frameIds:[0]},func:a=>{document.documentElement.setAttribute("dir",a)},args:[t]})}async function bl(e,t,a,n){await chrome.scripting.executeScript({target:bn(e,t),func:(o,i)=>{const r=document.querySelector(o);if(r)for(const[c,l]of Object.entries(i))r.setAttribute(c,l)},args:[a,n]})}function bn(e,t){return t!==void 0&&t!==0?{tabId:e,frameIds:[t]}:{tabId:e}}async function yl(e,t,a,n){const o=await fi(e),i=It.get(e),r=(i==null?void 0:i.scope)===a&&(i==null?void 0:i.frameId)===n,c=t.ariaVariation?JSON.stringify(t.ariaVariation.attributes):"",l=!r||(i==null?void 0:i.pseudoState)!==t.pseudoState,d=!i||i.theme!==t.theme,u=!i||i.direction!==t.direction,h=!i||i.breakpointId!==t.breakpoint.id,m=!r||(i==null?void 0:i.ariaVariationKey)!==c;if(l&&(await mi(e),a)){t.pseudoState==="disabled"?await Ka(e,n,a,!0):(await Ka(e,n,a,!1),await bi(e,n));const f=dl[t.pseudoState];if(f.length>0){const g=await fl(o,e,n),p=await gl(o,a,g);if(p!==null){const b=await ml(o,p),w=[p,...b];for(const k of w)try{await o.send("CSS.forcePseudoState",{nodeId:k,forcedPseudoClasses:f})}catch(y){ge.debug(`CSS.forcePseudoState failed for nodeId=${k}`,y)}ot.set(e,w),ge.debug(`forced :${t.pseudoState} on ${w.length} node(s)`)}else ge.debug(`could not resolve nodeId for ${a} (frameId=${n})`)}}if(d){const f=[{name:"prefers-reduced-motion",value:"reduce"}];t.theme==="dark"?(f.push({name:"prefers-color-scheme",value:"dark"}),f.push({name:"forced-colors",value:"none"})):t.theme==="light"?(f.push({name:"prefers-color-scheme",value:"light"}),f.push({name:"forced-colors",value:"none"})):t.theme==="forced-colors-active"?f.push({name:"forced-colors",value:"active"}):t.theme==="forced-colors-none"&&f.push({name:"forced-colors",value:"none"}),await o.send("Emulation.setEmulatedMedia",{features:f}),await new Promise(g=>setTimeout(g,50))}u&&await yi(e,t.direction),h&&await o.send("Emulation.setDeviceMetricsOverride",{width:t.breakpoint.width,height:t.breakpoint.height,deviceScaleFactor:t.breakpoint.deviceScaleFactor,mobile:t.breakpoint.mobile}),m&&t.ariaVariation&&a&&await bl(e,n,a,t.ariaVariation.attributes),It.set(e,{scope:a,frameId:n,pseudoState:t.pseudoState,theme:t.theme,direction:t.direction,breakpointId:t.breakpoint.id,ariaVariationKey:c})}async function wl(e,t,a){const n=Re.get(e);if(n){if(await mi(e),It.delete(e),t)try{await Ka(e,a,t,!1),await bi(e,a),await yi(e,"ltr")}catch(o){ge.warn("element-level reset partial failure",o)}try{await n.send("Emulation.setEmulatedMedia",{features:[]}),await n.send("Emulation.clearDeviceMetricsOverride")}catch(o){ge.warn("reset partial failure",o)}await da(e)}}async function Ya(e,t,a,n){try{return await yl(e,t,a,n),H({type:"STATE_CHANGED_EVENT",tabId:e,currentState:t}),{type:"STATE_DRIVE_RESPONSE",success:!0,appliedState:t}}catch(o){return ge.error("state drive failed",o),await hl(e),{type:"STATE_DRIVE_RESPONSE",success:!1,error:pi(o)}}}async function Ft(e,t,a){await wl(e,t,a)}async function ep(e){await da(e);try{await chrome.debugger.detach({tabId:e})}catch{}}async function vl(e,t={}){try{const a=await fi(e),n={format:"jpeg",quality:t.quality??75};t.fullPage&&(n.captureBeyondViewport=!0);const o=await a.send("Page.captureScreenshot",n);return o!=null&&o.data?`data:image/jpeg;base64,${o.data}`:null}catch(a){return ge.debug("Page.captureScreenshot failed",a),null}}function tp(){var a;const e=[];if(e.push(fe("STATE_DRIVE_REQUEST",n=>Ya(n.tabId,n.state,n.scope,n.frameId))),e.push(fe("STATE_RESET_REQUEST",n=>Ft(n.tabId))),(a=chrome.tabs)!=null&&a.onRemoved){const n=o=>{da(o).catch(()=>{})};chrome.tabs.onRemoved.addListener(n),e.push(()=>chrome.tabs.onRemoved.removeListener(n))}const t=sl((n,o)=>{n.tabId!==void 0&&(Re.delete(n.tabId),st.delete(n.tabId),ot.delete(n.tabId),ge.warn("unexpected detach",{tabId:n.tabId,reason:o}))});return e.push(t),ge.info("handlers registered"),()=>e.forEach(n=>n())}const Xe=J("cloud-sync"),jn="cloudSyncEnabled",Ia="cloudSyncEndpoint",xa="licenseToken",Ja="cloudSyncVersionCache";async function yn(){try{const e=await chrome.storage.local.get([jn,Ia,xa]),t=e[jn]===!0,a=typeof e[Ia]=="string"?e[Ia]:"",n=typeof e[xa]=="string"?e[xa]:"";return!t||!a||!n?null:{endpoint:a.replace(/\/$/,""),token:n,enabled:t}}catch{return null}}async function wi(){try{const t=(await chrome.storage.local.get(Ja))[Ja];return t&&typeof t=="object"?t:{}}catch{return{}}}async function Kt(e){try{await chrome.storage.local.set({[Ja]:e})}catch{}}async function Al(e,t,a){const n=await yn();if(!n)return!1;const o=await wi(),i=o[e];try{const r=`${n.endpoint}/v1/products/wcagcheckr/baselines/${encodeURIComponent(e)}`,c=await fetch(r,{method:"PUT",headers:{"Content-Type":"application/json",Authorization:`Bearer ${n.token}`},body:JSON.stringify({snapshot:t,axeVersion:a,expectedVersion:i})});if(c.status===409)return Xe.warn(`baseline ${e} version conflict on push`,{expectedVersion:i}),delete o[e],await Kt(o),!1;if(!c.ok)return Xe.warn("baseline push failed",{status:c.status}),!1;const l=await c.json();return typeof l.version=="number"&&(o[e]=l.version,await Kt(o)),!0}catch(r){return Xe.debug("push network failure (offline?)",r),!1}}async function kl(e){const t=await yn();if(!t)return!1;try{const a=`${t.endpoint}/v1/products/wcagcheckr/baselines/${encodeURIComponent(e)}`,n=await fetch(a,{method:"DELETE",headers:{Authorization:`Bearer ${t.token}`}});if(n.ok){const o=await wi();delete o[e],await Kt(o)}return n.ok}catch(a){return Xe.debug("delete network failure",a),!1}}async function Il(){const e=await yn();if(!e)return[];try{const t=await fetch(`${e.endpoint}/v1/products/wcagcheckr/baselines`,{headers:{Authorization:`Bearer ${e.token}`}});if(!t.ok)return Xe.warn("pull failed",{status:t.status}),[];const n=(await t.json()).items??[],o={};for(const i of n)o[i.componentId]=i.version;return await Kt(o),n}catch(t){return Xe.debug("pull network failure",t),[]}}const vi=J("baseline-store"),zn="componentIdAliases";async function it(e,t){try{return await t()}catch(a){const n=a instanceof Error?a.message:String(a);throw vi.error(`baseline IDB ${e} failed: ${n}`),new tr({code:"BASELINE_DB_ERROR",message:`Baseline storage is unavailable (${e}: ${n}).`,recoverable:!1,details:n})}}async function ua(e){return((await chrome.storage.local.get(zn))[zn]??{})[e]??e}async function xl(e){const t=await ua(e),a=await it("get",()=>Zt(t));return a?{violations:a.violations,snapshotMeta:a.snapshotMeta}:null}async function Sl(e,t,a,n){const o=await ua(e),i=await it("get-for-set",()=>Zt(o)),r=(i==null?void 0:i.seenOnUrls)??[],c=r.includes(a.url)?r:[...r,a.url];await it("set",()=>nn({componentId:o,violations:t,snapshotMeta:a,lastUpdated:new Date().toISOString(),axeVersion:a.axeVersion,announcements:n==null?void 0:n.announcements,focusEvents:n==null?void 0:n.focusEvents,seenOnUrls:c})),Al(o,{violations:t,snapshotMeta:a},a.axeVersion).catch(()=>{})}function Cl(e,t){if(!e||!t||$n(e)===$n(t))return;const a=i=>{var r,c,l,d,u;return(((r=i.pseudoStates)==null?void 0:r.length)??0)*Math.max(1,((c=i.ariaVariations)==null?void 0:c.length)??0)*(((l=i.themes)==null?void 0:l.length)??0)*(((d=i.directions)==null?void 0:d.length)??0)*(((u=i.breakpoints)==null?void 0:u.length)??0)},n=a(e),o=a(t);return`Baseline was captured under a different state-matrix configuration (${n} states) than this run (${o} states). Per-state-instance deltas inflate when the matrix changes — the unique-violation deltas above are still reliable.`}async function wn(e,t,a,n){var d;const o=await ua(e),i=await na(o),r=await it("compare",()=>Zt(o));if(!r){const u=t.filter(m=>i.has(m.matchKey)),h=t.filter(m=>!i.has(m.matchKey));return{new:h,persistent:[],fixed:[],newCount:h.length,persistentCount:0,fixedCount:0,baselineSnapshotMeta:null,comparedAt:new Date().toISOString(),newAnnouncements:a==null?void 0:a.announcements,newFocusEvents:a==null?void 0:a.focusEvents,acknowledged:u,acknowledgedCount:u.length}}const c=er(r.violations,t,r.snapshotMeta,{baselineAnnouncements:r.announcements,currentAnnouncements:a==null?void 0:a.announcements,baselineFocusEvents:r.focusEvents,currentFocusEvents:a==null?void 0:a.focusEvents},i),l=Cl((d=r.snapshotMeta)==null?void 0:d.matrixConfig,n);return l&&(c.matrixMismatchWarning=l),c}async function $l(e){const t=await ua(e);await it("delete",()=>Eo(t)),kl(t).catch(()=>{})}async function ap(){const e=await Il();for(const t of e)await nn({componentId:t.componentId,violations:t.snapshot.violations,snapshotMeta:t.snapshot.snapshotMeta,lastUpdated:t.updatedAt,axeVersion:t.axeVersion});return e.length>0&&H({type:"SCORECARD_UPDATED_EVENT"}),e.length}async function Tl(e){let t=await it("list",()=>Ro());return e!=null&&e.url&&(t=t.filter(a=>a.snapshotMeta.url===e.url)),t.map(a=>{const n=(a.focusEvents??[]).filter(d=>d.isFocusReset).length,o=(a.announcements??[]).length,i=a.violations.filter(d=>d.ruleId==="target-size").length,r=a.violations.some(d=>d.ruleId==="color-contrast"&&d.currentState.pseudoState==="hover"),c=a.violations.filter(d=>d.impact==="critical").length,l=a.violations.filter(d=>d.impact==="serious").length;return{componentId:a.componentId,violationCount:a.violations.length,lastUpdated:a.lastUpdated,seenOnUrlsCount:(a.seenOnUrls??[]).length,metrics:{criticalCount:c,seriousCount:l,focusResetCount:n,announcementCount:o,targetSizeFailCount:i,hoverContrastFail:r}}})}function np(){const e=[];return e.push(fe("BASELINE_GET",async t=>({type:"BASELINE_GET_RESPONSE",baseline:await xl(t.componentId)}))),e.push(fe("BASELINE_SET",async t=>{await Sl(t.componentId,t.violations,t.snapshotMeta,{announcements:t.announcements,focusEvents:t.focusEvents}),H({type:"SCORECARD_UPDATED_EVENT"})})),e.push(fe("BASELINE_COMPARE",async t=>({type:"BASELINE_COMPARE_RESPONSE",delta:await wn(t.componentId,t.currentViolations,{announcements:t.announcements,focusEvents:t.focusEvents},t.currentMatrix)}))),e.push(fe("BASELINE_DELETE",async t=>{await $l(t.componentId),H({type:"SCORECARD_UPDATED_EVENT"})})),e.push(fe("BASELINE_LIST",async t=>({type:"BASELINE_LIST_RESPONSE",items:await Tl(t.filter)}))),vi.info("handlers registered"),()=>e.forEach(t=>t())}const El=J("settings-store"),Hn=1,Bn="__schemaVersion",Xa={stateMatrix:Na,componentIdAliases:{},ignorePatterns:[]};async function We(e,t){return(await chrome.storage.local.get(e))[e]??t}async function op(){const e=await chrome.storage.local.get();if(e[Bn]===Hn)return;const t={[Bn]:Hn};for(const[a,n]of Object.entries(Xa))a in e||(t[a]=n);await chrome.storage.local.set(t),El.info("defaults ensured")}const Rl=["__","inflight:","license:","support:"];function Ol(e){return!Rl.some(t=>e.startsWith(t))}function ip(){const e=[];return e.push(fe("SETTINGS_GET",async t=>({type:"SETTINGS_RESPONSE",data:(await chrome.storage.local.get(t.key))[t.key]??Xa[t.key]}))),e.push(fe("SETTINGS_SET",async t=>{await chrome.storage.local.set({[t.key]:t.value})})),e.push(fe("SETTINGS_GET_ALL",async()=>{const t=await chrome.storage.local.get(),a=Object.fromEntries(Object.entries(t).filter(([n])=>Ol(n)));return{type:"SETTINGS_RESPONSE",data:{...Xa,...a}}})),()=>e.forEach(t=>t())}function Ce(e){const t={capUsd:Math.max(0,e),spentUsd:0,exceeded:!1};return{state:t,canCharge(){return!t.exceeded&&t.spentUsd<t.capUsd},recordCharge(a){t.spentUsd+=Math.max(0,a),t.spentUsd>=t.capUsd&&(t.exceeded=!0)}}}const Ml=J("ai-alt-text");async function Dl(e,t,a={}){var h,m;if(!t.enabled||!t.enabledChecks.altText)return{findings:[],totalCostUsd:0,capExceeded:!1,errors:[]};const n=ce(t);if(!n.ok)return{findings:[],totalCostUsd:0,capExceeded:!1,errors:[`AI client unavailable: ${n.reason}`]};const o=n.client,i=Ce(t.costCapUsd),r=[],c=[],l=Math.max(1,t.maxCandidatesPerCheck??10),d=e.slice(0,l),u=d.length;for(let f=0;f<d.length;f++){const g=d[f];if(!i.canCharge())break;try{if((h=a.signal)!=null&&h.aborted){c.push(`canceled after ${f}/${u} candidates`);break}const p=await o.judgeAltText({imageUrl:g.imageUrl,alt:g.alt,surroundingContext:g.surroundingContext,signal:a.signal});i.recordCharge(p.costUsd),(p.verdict==="fail"||p.verdict==="uncertain"&&p.confidence<.6)&&r.push(await Nl(g,p))}catch(p){if(p instanceof Error&&(p.name==="AbortError"||p.name==="ExternalAbortError")){c.push(`canceled after ${f}/${u} candidates`);break}const w=p instanceof Error?p.message:String(p);c.push(`${g.selector}: ${w}`),Ml.warn("alt-text judgment failed",p)}(m=a.onProgress)==null||m.call(a,f+1,u)}return{findings:r,totalCostUsd:i.state.spentUsd,capExceeded:i.state.exceeded,errors:c}}async function Nl(e,t){const a=t.verdict==="uncertain"?"ai-alt-uncertain":"ai-alt-misleading",n={selector:e.selector,outerHTML:e.outerHTML.slice(0,500),failureSummary:`AI assessment: ${t.reasoning}`,tagName:"IMG",role:null,accessibleName:e.alt,textSnippet:null,attrId:null,attrTestid:null},o=await tn({ruleId:a,componentId:e.componentId,currentState:e.currentState,target:n}),i=t.verdict==="uncertain";return{ruleId:a,wcagCriterion:"1.1.1",wcagLevel:"A",impact:i?"minor":"serious",description:i?"AI couldn't verify whether this alt text is appropriate — flag for human review.":"AI judged the alt text to be misleading, generic, or otherwise inappropriate.",helpUrl:"https://www.w3.org/WAI/WCAG21/Understanding/non-text-content.html",target:n,componentId:e.componentId,currentState:e.currentState,axeVersion:e.axeVersion,detectedAt:new Date().toISOString(),matchKey:o,ai:{reasoning:t.reasoning,confidence:t.confidence,model:t.model,costUsd:t.costUsd},...i?{needsReview:!0}:{}}}const ft=J("interactive-audit"),Ll="1.3",Pl={Tab:{key:"Tab",code:"Tab",windowsVirtualKeyCode:9},Enter:{key:"Enter",code:"Enter",windowsVirtualKeyCode:13},Escape:{key:"Escape",code:"Escape",windowsVirtualKeyCode:27},Space:{key:" ",code:"Space",windowsVirtualKeyCode:32},ArrowLeft:{key:"ArrowLeft",code:"ArrowLeft",windowsVirtualKeyCode:37},ArrowUp:{key:"ArrowUp",code:"ArrowUp",windowsVirtualKeyCode:38},ArrowRight:{key:"ArrowRight",code:"ArrowRight",windowsVirtualKeyCode:39},ArrowDown:{key:"ArrowDown",code:"ArrowDown",windowsVirtualKeyCode:40},Home:{key:"Home",code:"Home",windowsVirtualKeyCode:36},End:{key:"End",code:"End",windowsVirtualKeyCode:35}};class vn{constructor(t){Rt(this,"session");Rt(this,"attached",!1);Rt(this,"steps",[]);this.tabId=t,this.session=new hi({tabId:t})}async attach(){if(!this.attached)try{await chrome.debugger.attach({tabId:this.tabId},Ll),await this.session.send("Runtime.enable"),await this.session.send("Page.enable"),await this.session.send("DOM.enable"),await this.session.send("DOM.getDocument",{depth:-1,pierce:!0}),await this.session.send("Accessibility.enable"),this.attached=!0}catch(t){throw ft.error("attach failed",t),pi(t)}}async dispose(){if(this.attached){try{await chrome.debugger.detach({tabId:this.tabId})}catch(t){ft.debug("detach soft-failure",t)}this.attached=!1}}async pressKey(t,a){const n=Pl[t];if(!n)throw new Error(`Unknown key: ${String(t)}`);const o=a!=null&&a.shift?8:0;await this.session.send("Input.dispatchKeyEvent",{type:"rawKeyDown",key:n.key,code:n.code,windowsVirtualKeyCode:n.windowsVirtualKeyCode,modifiers:o}),await this.session.send("Input.dispatchKeyEvent",{type:"keyUp",key:n.key,code:n.code,windowsVirtualKeyCode:n.windowsVirtualKeyCode,modifiers:o}),await Kn(50)}async pressKeyN(t,a,n){for(let o=0;o<a;o++)await this.pressKey(t,n)}async typeText(t){await this.session.send("Input.insertText",{text:t}),await Kn(50)}async countFocusables(){var a;const t=`(() => {
912
+ const sel = [
913
+ 'a[href]', 'button', 'input', 'select', 'textarea',
914
+ '[tabindex]', 'audio[controls]', 'video[controls]', '[contenteditable]'
915
+ ].join(',');
916
+ const all = Array.from(document.querySelectorAll(sel));
917
+ const visible = (el) => {
918
+ if (el.hasAttribute && el.hasAttribute('disabled')) return false;
919
+ const ti = el.getAttribute && el.getAttribute('tabindex');
920
+ if (ti != null && parseInt(ti, 10) < 0) return false;
921
+ if (el.getAttribute && el.getAttribute('aria-hidden') === 'true') return false;
922
+ if (el.tagName === 'INPUT' && el.type === 'hidden') return false;
923
+ if (el.getClientRects && el.getClientRects().length === 0) return false;
924
+ const cs = getComputedStyle ? getComputedStyle(el) : null;
925
+ if (cs && (cs.display === 'none' || cs.visibility === 'hidden')) return false;
926
+ if (cs && parseFloat(cs.opacity || '1') === 0) return false;
927
+ return true;
928
+ };
929
+ return all.filter(visible).length;
930
+ })()`;try{const n=await this.session.send("Runtime.evaluate",{expression:t,returnByValue:!0}),o=(a=n==null?void 0:n.result)==null?void 0:a.value;return typeof o=="number"&&Number.isFinite(o)?o:0}catch{return 0}}async getFocusedElement(){var a;const t=await this.session.send("Runtime.evaluate",{expression:_l,returnByValue:!0});try{const n=(a=t.result)==null?void 0:a.value;return typeof n=="string"?JSON.parse(n):n}catch(n){return ft.error("getFocusedElement: probe returned non-JSON",n),Ul}}async captureScreenshot(){try{const t=await this.session.send("Page.captureScreenshot",{format:"jpeg",quality:75});return t!=null&&t.data?`data:image/jpeg;base64,${t.data}`:null}catch(t){return ft.debug("captureScreenshot failed",t),null}}async getAxContext(){try{return((await this.session.send("Accessibility.getFullAXTree")).nodes??[]).filter(a=>!a.ignored).map(a=>{var n,o,i;return{nodeId:a.nodeId,role:(n=a.role)==null?void 0:n.value,name:(o=a.name)==null?void 0:o.value,description:(i=a.description)==null?void 0:i.value,childIds:a.childIds}})}catch(t){return ft.debug("getAxContext failed",t),[]}}async recordStep(t,a){const n=await this.getFocusedElement(),o=(a==null?void 0:a.screenshot)!==!1?await this.captureScreenshot():null,i=a!=null&&a.ax?await this.getAxContext():void 0,r={ordinal:this.steps.length,at:new Date().toISOString(),action:t,focused:n,screenshot:o??void 0,axContext:i};return this.steps.push(r),r}}function Kn(e){return new Promise(t=>setTimeout(t,e))}const Ul={selector:"body",tag:"body",role:"",name:"",outerHtml:"",rect:{x:0,y:0,width:0,height:0},isBody:!0},_l=`(() => {
931
+ const el = document.activeElement;
932
+ if (!el || el === document.body) {
933
+ return JSON.stringify({
934
+ selector: 'body',
935
+ tag: 'body',
936
+ role: '',
937
+ name: '',
938
+ outerHtml: '',
939
+ rect: { x: 0, y: 0, width: 0, height: 0 },
940
+ isBody: true,
941
+ });
942
+ }
943
+ function selectorFor(node) {
944
+ if (node.id) return '#' + CSS.escape(node.id);
945
+ const parts = [];
946
+ let cur = node;
947
+ while (cur && cur.nodeType === 1 && cur !== document.body) {
948
+ let part = cur.tagName.toLowerCase();
949
+ if (cur.classList.length > 0) {
950
+ part += '.' + Array.from(cur.classList).slice(0, 2).map(c => CSS.escape(c)).join('.');
951
+ }
952
+ const parent = cur.parentElement;
953
+ if (parent) {
954
+ const siblings = Array.from(parent.children).filter(s => s.tagName === cur.tagName);
955
+ if (siblings.length > 1) {
956
+ part += ':nth-of-type(' + (siblings.indexOf(cur) + 1) + ')';
957
+ }
958
+ }
959
+ parts.unshift(part);
960
+ cur = parent;
961
+ if (cur && cur.id) { parts.unshift('#' + CSS.escape(cur.id)); break; }
962
+ }
963
+ return parts.join(' > ');
964
+ }
965
+ function accName(node) {
966
+ return (
967
+ node.getAttribute('aria-label') ||
968
+ (node.getAttribute('aria-labelledby') && (() => {
969
+ const ids = node.getAttribute('aria-labelledby').split(/\\s+/);
970
+ return ids.map(id => { const r = document.getElementById(id); return r ? r.textContent : ''; }).join(' ').trim();
971
+ })()) ||
972
+ node.getAttribute('alt') ||
973
+ (node.tagName === 'INPUT' && node.labels && node.labels[0] ? node.labels[0].textContent : '') ||
974
+ (node.textContent || '').trim().slice(0, 200)
975
+ );
976
+ }
977
+ const rect = el.getBoundingClientRect();
978
+ // rc.120 — Document-relative coordinates. Pre-rc.120 we captured
979
+ // viewport-relative rect.top, which silently lied as the walker
980
+ // progressed: when a Tab scrolls a later element into view, EARLIER
981
+ // elements that were at top:100 in their initial snapshot now have
982
+ // top:-3500 (scrolled off the top). Sort the rec by y and the
983
+ // sequence looks "descending from y=4260 to y=1240" — geometrically
984
+ // nonsense for a top-to-bottom Tab walk. Document-relative position
985
+ // is invariant under scroll, so the comparator + LLM see the real
986
+ // visual order regardless of how far the walker scrolled.
987
+ return JSON.stringify({
988
+ selector: selectorFor(el),
989
+ tag: el.tagName.toLowerCase(),
990
+ role: el.getAttribute('role') || '',
991
+ name: (accName(el) || '').toString().slice(0, 200),
992
+ outerHtml: (el.outerHTML || '').slice(0, 2000),
993
+ rect: {
994
+ x: rect.left + window.scrollX,
995
+ y: rect.top + window.scrollY,
996
+ width: rect.width,
997
+ height: rect.height,
998
+ },
999
+ isBody: false,
1000
+ });
1001
+ })()`,Fl=[/no parseable JSON/i,/invalid JSON/i];function Yn(e){if(e.verdict!=="uncertain"||e.confidence!==0)return!1;const t=e.reasoning??"";return Fl.some(a=>a.test(t))}async function He(e){const t=await e();if(!Yn(t))return t;try{const a=await e();if(!Yn(a))return a}catch{}return t}async function Wl(e,t){if(t.length===0||typeof OffscreenCanvas>"u"||typeof createImageBitmap>"u")return e;try{const n=await(await fetch(e)).blob(),o=await createImageBitmap(n),i=new OffscreenCanvas(o.width,o.height),r=i.getContext("2d");if(!r)return e;r.drawImage(o,0,0);const c=Math.max(...t.map(g=>g.rect.x+g.rect.width),0),l=c>0&&o.width/c>1.5?o.width/Math.max(1280,c):1;for(const g of t){const p=(g.rect.x+g.rect.width/2)*l,b=(g.rect.y+g.rect.height/2)*l,w=16*l;r.fillStyle="rgba(220, 38, 38, 0.92)",r.strokeStyle="rgba(255, 255, 255, 0.95)",r.lineWidth=2*l,r.beginPath(),r.arc(p,b,w,0,2*Math.PI),r.fill(),r.stroke(),r.fillStyle="white",r.font=`bold ${Math.round(18*l)}px sans-serif`,r.textAlign="center",r.textBaseline="middle",r.fillText(String(g.ordinal),p,b)}const u=await(await i.convertToBlob({type:"image/jpeg",quality:.85})).arrayBuffer(),h=new Uint8Array(u);let m="";const f=8192;for(let g=0;g<h.byteLength;g+=f){const p=h.subarray(g,Math.min(g+f,h.byteLength));m+=String.fromCharCode(...p)}return`data:image/jpeg;base64,${btoa(m)}`}catch{return e}}async function Yt(e,t={}){var r;const a=Date.now(),n=5e3,o=200,i=e;for(;Date.now()-a<n;){const l=(r=(await i.session.send("Runtime.evaluate",{expression:`(() => ({ ready: document.readyState === "complete", focusables: document.querySelectorAll('a[href], button:not([disabled]), input:not([disabled]):not([type="hidden"]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"]):not([disabled]), [contenteditable="true"]').length }))()`,returnByValue:!0})).result)==null?void 0:r.value;if(l&&l.ready&&(l.focusables??0)>0)break;await new Promise(d=>setTimeout(d,o))}t.extraDelayMs&&t.extraDelayMs>0&&await new Promise(c=>setTimeout(c,t.extraDelayMs))}function An(e){const t=(e.name??"").trim().slice(0,60),a=Math.round(e.rect.x),n=Math.round(e.rect.y),o=Math.round(e.rect.width),i=Math.round(e.rect.height);return`${e.selector}|${e.tag}|${t}|${a},${n},${o},${i}`}async function ql(e,t){const a=JSON.stringify({criterionId:e,sequence:t.map(r=>({o:r.ordinal,s:r.selector,r:r.role,x:Math.round(r.rect.x),y:Math.round(r.rect.y),w:Math.round(r.rect.width),h:Math.round(r.rect.height)}))}),n=new TextEncoder().encode(a),o=await crypto.subtle.digest("SHA-256",n);return Array.from(new Uint8Array(o)).map(r=>r.toString(16).padStart(2,"0")).join("")}const Sa=J("focus-order-audit"),Ca="2.4.3",Jn=50;async function Gl(e){var r,c,l;const t=await chrome.storage.local.get("aiConfig"),a=be(t.aiConfig),n=ce(a);if(!n.ok)return{ok:!1,error:n.reason};const o=n.client;if(typeof o.judgeFocusOrder!="function")return{ok:!1,error:"AI client does not support judgeFocusOrder yet."};const i=new vn(e.tabId);try{await i.attach(),await Yt(i),await Vl(i),await i.recordStep("initial",{screenshot:!0});const d=await i.countFocusables(),u=d>0?Math.min(Jn,d):Jn,h=new Set;for(let s=0;s<u;s++){if((r=e.signal)!=null&&r.aborted)return{ok:!1,error:"Audit cancelled."};await i.pressKey("Tab");const O=await i.recordStep(`Tab #${s+1}`,{screenshot:!1});if(O.focused.isBody)break;const E=An(O.focused);if(h.has(E))break;h.add(E),(c=e.onStep)==null||c.call(e,{ordinal:O.ordinal,selector:O.focused.selector,role:O.focused.role,name:O.focused.name})}const m=i.steps.filter(s=>s.action!=="initial"&&!s.focused.isBody).map(s=>({ordinal:s.ordinal,selector:s.focused.selector,role:s.focused.role,name:s.focused.name,rect:s.focused.rect})),f=(l=i.steps[0])==null?void 0:l.screenshot,g=f?await Wl(f,m):void 0,p=await ql(Ca,m);let b,w=null;try{w=await Ne(e.componentId,Ca),w&&!_t(w.reasoning)?b={verdict:w.verdict,reasoning:w.reasoning,confidence:w.confidence}:w&&_t(w.reasoning)&&Sa.info("Skipping anchor — previous 2.4.3 verdict was cascade-escalated (system-imposed); letting primary judge fresh")}catch{}if(w&&w.inputHash===p&&!_t(w.reasoning)){Sa.info(`2.4.3 inputs unchanged (hash match: ${p.slice(0,8)}…); reusing previous verdict=${w.verdict} without AI call`);const s={...w,finishedAt:new Date().toISOString(),costUsd:0,model:`${w.model} (cached, hash-match)`,inputHash:p};return await te(s),{ok:!0,result:s}}const k=await He(()=>o.judgeFocusOrder({pageUrl:e.pageUrl,pageScreenshot:g,tabSequence:m,previousVerdict:b,signal:e.signal})),y={componentId:e.componentId,criterionId:Ca,verdict:k.verdict==="pass"?"pass":k.verdict==="fail"?"fail":"uncertain",reasoning:k.reasoning,confidence:k.confidence,steps:i.steps,pageUrl:e.pageUrl,finishedAt:new Date().toISOString(),costUsd:k.costUsd,model:k.model,inputHash:p};return await te(y),{ok:!0,result:y}}catch(d){return Sa.error("focus-order audit failed",d),{ok:!1,error:d instanceof Error?d.message:String(d)}}finally{await i.dispose()}}async function Vl(e){await e.session.send("Runtime.evaluate",{expression:"(() => { if (document.activeElement && document.activeElement !== document.body) document.activeElement.blur(); window.scrollTo(0, 0); })()"})}async function ha(e,t){const a=JSON.stringify({criterionId:e,input:t}),n=new TextEncoder().encode(a),o=await crypto.subtle.digest("SHA-256",n);return Array.from(new Uint8Array(o)).map(r=>r.toString(16).padStart(2,"0")).join("")}async function pa(e,t,a){const n=await Ne(e,t);return n?n.inputHash!==a?{hit:!1,previous:n}:n.verdict==="uncertain"?{hit:!1,previous:n}:_t(n.reasoning)?{hit:!1,previous:n}:{hit:!0,previous:n}:{hit:!1,previous:null}}function fa(e,t){return{...e,finishedAt:new Date().toISOString(),costUsd:0,model:`${e.model} (cached, hash-match)`,inputHash:t}}const $a=J("keyboard-trap-audit"),gt="2.1.2",Xn=50;async function jl(e){const t=await chrome.storage.local.get("aiConfig"),a=be(t.aiConfig),n=ce(a);if(!n.ok)return{ok:!1,error:n.reason};const o=n.client;if(typeof o.judgeKeyboardTrap!="function")return{ok:!1,error:"AI client does not support judgeKeyboardTrap yet."};const i=new vn(e.tabId);try{await i.attach(),await Yt(i),await Qn(i),await i.recordStep("initial",{screenshot:!0});const r=await i.countFocusables(),c=r>0?Math.min(Xn,r):Xn;let l=await Zn(i,c,e.signal);if(l.forwardSelectors.length===0&&l.reverseSelectors.length>0&&($a.info("2.1.2 detected degenerate 0/N walk — retrying after extra hydration wait"),await Yt(i,{extraDelayMs:1500}),await Qn(i),l=await Zn(i,c,e.signal)),l.forwardSelectors.length===0){const s={componentId:e.componentId,criterionId:gt,verdict:"uncertain",reasoning:`Could not run a meaningful keyboard-trap evaluation: the forward Tab walk found no focusable elements on this page (forward: ${l.forwardSelectors.length}, reverse: ${l.reverseSelectors.length}). This is usually a page-load timing issue (SPA route not finished hydrating when the audit ran) or a page with literally no interactive content. Re-run the audit after verifying the page is fully loaded; if the page genuinely has no interactive content, mark this criterion as Not Applicable.`,confidence:.9,steps:i.steps,pageUrl:e.pageUrl,finishedAt:new Date().toISOString(),costUsd:0,model:"heuristic-degenerate-walk"};return await te(s),{ok:!0,result:s}}const{forwardSelectors:d,reverseSelectors:u,stuckRuns:h}=l,m=d.slice(0,-1).reverse(),f=[];for(let s=0;s<Math.min(m.length,u.length);s++){const O=m[s],E=u[s];O!==E&&f.push({index:s,expected:O,actual:E})}const g=zl(m,u,f,h);if(g.detected){const s={componentId:e.componentId,criterionId:gt,verdict:"pass",reasoning:g.reasoning,confidence:.95,steps:i.steps,pageUrl:e.pageUrl,finishedAt:new Date().toISOString(),costUsd:0,model:"heuristic-only"};return await te(s),{ok:!0,result:s}}const p=await ha(gt,{forwardSequence:d,reverseSequence:u,stuckRuns:h,reverseMismatches:f.slice(0,10)}),b=await pa(e.componentId,gt,p);if(b.hit){$a.info(`2.1.2 inputs unchanged (hash match); reusing verdict=${b.previous.verdict}`);const s=fa(b.previous,p);return await te(s),{ok:!0,result:s}}const w=b.previous?{verdict:b.previous.verdict,reasoning:b.previous.reasoning,confidence:b.previous.confidence}:void 0,k=await He(()=>o.judgeKeyboardTrap({pageUrl:e.pageUrl,forwardSequence:d,reverseSequence:u,stuckRuns:h,reverseMismatches:f.slice(0,10),previousVerdict:w,signal:e.signal})),y={componentId:e.componentId,criterionId:gt,verdict:k.verdict==="pass"?"pass":k.verdict==="fail"?"fail":"uncertain",reasoning:k.reasoning,confidence:k.confidence,steps:i.steps,pageUrl:e.pageUrl,finishedAt:new Date().toISOString(),costUsd:k.costUsd,model:k.model,inputHash:p};return await te(y),{ok:!0,result:y}}catch(r){return $a.error("keyboard-trap audit failed",r),{ok:!1,error:r instanceof Error?r.message:String(r)}}finally{await i.dispose()}}async function Qn(e){await e.session.send("Runtime.evaluate",{expression:"(() => { if (document.activeElement && document.activeElement !== document.body) document.activeElement.blur(); window.scrollTo(0, 0); })()"})}async function Zn(e,t,a){const o=[],i=[];for(let h=0;h<t&&!(a!=null&&a.aborted);h++){await e.pressKey("Tab");const m=await e.recordStep(`Tab #${h+1}`,{screenshot:!1});if(m.focused.isBody)break;o.push(m.focused.selector);const f=An(m.focused);if(i.length>2&&f===i[0])break;i.push(f)}const r=[];let c=0;for(let h=1;h<=i.length;h++)if(h===i.length||i[h]!==i[h-1]){const m=h-c;m>=3&&r.push(`${o[c]} (×${m} consecutive)`),c=h}const l=[],u=Math.min(50,t);for(let h=0;h<u&&!(a!=null&&a.aborted);h++){await e.pressKey("Tab",{shift:!0});const m=await e.recordStep(`Shift+Tab #${h+1}`,{screenshot:!1});if(m.focused.isBody||(l.push(m.focused.selector),l.length>=o.length))break}return{forwardSelectors:o,reverseSelectors:l,stuckRuns:r}}function zl(e,t,a,n){if(e.length<3||t.length<3)return{detected:!1};if(n.length>0)return{detected:!1};const o=Math.min(e.length,t.length);if(a.length<o-1)return{detected:!1};const i=w=>{let k=0;for(let y=0;y<e.length;y++){const s=t[y+w];s!==void 0&&s===e[y]&&k++}return k},r=i(0),c=i(1),l=i(-1),d=Math.floor(e.length*.7),u=Math.max(c,l);if(u<d||u<=r)return{detected:!1};const h=new Set(e),m=new Set(t),f=[...h].filter(w=>m.has(w)),g=new Set([...h,...m]);return g.size-f.length>2?{detected:!1}:{detected:!0,reasoning:`Comparator artifact detected: every reverse-walk position mismatched by the same ${c>=l?"+1":"-1"} offset, but the SET of focused elements matches the forward walk (${f.length}/${g.size} elements common) and no stuck runs occurred. This is the comparator's index math being off-by-one against actual Shift-Tab semantics, not a real keyboard trap. Pass.`}}const eo=J("focus-visible-audit"),Ta="2.4.7",to=30,Hl=10;async function Bl(e){var r;const t=await chrome.storage.local.get("aiConfig"),a=be(t.aiConfig),n=ce(a);if(!n.ok)return{ok:!1,error:n.reason};const o=n.client;if(typeof o.judgeFocusVisible!="function")return{ok:!1,error:"AI client does not support judgeFocusVisible yet."};const i=new vn(e.tabId);try{await i.attach(),await Yt(i),await Kl(i);const c=[],l=await i.countFocusables(),d=l>0?Math.min(to,l):to,u=new Set;for(let p=0;p<d&&c.length<Hl;p++){if((r=e.signal)!=null&&r.aborted)return{ok:!1,error:"Audit cancelled."};await i.pressKey("Tab");const b=await i.recordStep(`Tab #${p+1}`,{screenshot:!0});if(b.focused.isBody)break;const w=An(b.focused);if(u.has(w))break;u.add(w);const k=await Yl(i);b.screenshot&&c.push({ordinal:c.length+1,selector:b.focused.selector,role:b.focused.role,name:b.focused.name,rect:b.focused.rect,screenshot:b.screenshot,focusStyle:k})}if(c.length===0)return{ok:!1,error:"No focusable elements found on the page; cannot evaluate 2.4.7."};const h=await ha(Ta,{samples:c.map(p=>({ordinal:p.ordinal,selector:p.selector,role:p.role,focusStyle:p.focusStyle,rect:{x:Math.round(p.rect.x),y:Math.round(p.rect.y),w:Math.round(p.rect.width),h:Math.round(p.rect.height)}}))}),m=await pa(e.componentId,Ta,h);if(m.hit){eo.info(`2.4.7 inputs unchanged (hash match); reusing verdict=${m.previous.verdict}`);const p=fa(m.previous,h);return await te(p),{ok:!0,result:p}}const f=await He(()=>o.judgeFocusVisible({pageUrl:e.pageUrl,samples:c,signal:e.signal})),g={componentId:e.componentId,criterionId:Ta,verdict:f.verdict==="pass"?"pass":f.verdict==="fail"?"fail":"uncertain",reasoning:f.reasoning,confidence:f.confidence,steps:i.steps,pageUrl:e.pageUrl,finishedAt:new Date().toISOString(),costUsd:f.costUsd,model:f.model,inputHash:h};return await te(g),{ok:!0,result:g}}catch(c){return eo.error("focus-visible audit failed",c),{ok:!1,error:c instanceof Error?c.message:String(c)}}finally{await i.dispose()}}async function Kl(e){await e.session.send("Runtime.evaluate",{expression:"(() => { if (document.activeElement && document.activeElement !== document.body) document.activeElement.blur(); window.scrollTo(0, 0); })()"})}async function Yl(e){const t=`(() => {
1002
+ const el = document.activeElement;
1003
+ if (!el || el === document.body) return JSON.stringify({
1004
+ outlineStyle: '', outlineWidth: '', outlineColor: '', outlineOffset: '',
1005
+ boxShadow: '', border: '', backgroundColor: '',
1006
+ });
1007
+ const cs = getComputedStyle(el);
1008
+ return JSON.stringify({
1009
+ outlineStyle: cs.outlineStyle,
1010
+ outlineWidth: cs.outlineWidth,
1011
+ outlineColor: cs.outlineColor,
1012
+ outlineOffset: cs.outlineOffset,
1013
+ boxShadow: cs.boxShadow,
1014
+ border: cs.border,
1015
+ backgroundColor: cs.backgroundColor,
1016
+ });
1017
+ })()`;try{const a=await e.session.send("Runtime.evaluate",{expression:t,returnByValue:!0});return JSON.parse(a.result.value)}catch{return{outlineStyle:"",outlineWidth:"",outlineColor:"",outlineOffset:"",boxShadow:"",border:"",backgroundColor:""}}}const ao=J("reading-order-audit"),Dt="1.3.2";async function Jl(e){var u,h,m;const t=await chrome.storage.local.get("aiConfig"),a=be(t.aiConfig),n=ce(a);if(!n.ok)return{ok:!1,error:n.reason};const o=n.client;if(typeof o.judgeReadingOrder!="function")return{ok:!1,error:"AI client does not support judgeReadingOrder yet."};const i=((u=e.results[0])==null?void 0:u.readingOrderIssues)??[],r=(h=e.results[0])==null?void 0:h.screenshotDataUrl;if(i.length===0){const f={componentId:e.componentId,criterionId:Dt,verdict:"pass",reasoning:"The reading-order heuristic flagged zero divergences between DOM source order and visual position order. No AI judgment required.",confidence:1,steps:[],pageUrl:e.pageUrl,finishedAt:new Date().toISOString(),costUsd:0,model:"heuristic-only"};return await te(f),{ok:!0,result:f}}if((m=e.signal)!=null&&m.aborted)return{ok:!1,error:"Audit cancelled."};const c=await ha(Dt,{flaggedIssues:i.map(f=>({selector:f.selector,textSnippet:f.textSnippet,domIndex:f.domIndex,visualIndex:f.visualIndex}))}),l=await pa(e.componentId,Dt,c);if(l.hit){ao.info(`1.3.2 inputs unchanged (hash match); reusing verdict=${l.previous.verdict}`);const f=fa(l.previous,c);return await te(f),{ok:!0,result:f}}const d=l.previous?{verdict:l.previous.verdict,reasoning:l.previous.reasoning,confidence:l.previous.confidence}:void 0;try{const f=await He(()=>o.judgeReadingOrder({pageUrl:e.pageUrl,pageScreenshot:r,flaggedIssues:i.map(p=>({selector:p.selector,textSnippet:p.textSnippet,domIndex:p.domIndex,visualIndex:p.visualIndex,rect:p.boundingRect})),previousVerdict:d,signal:e.signal})),g={componentId:e.componentId,criterionId:Dt,verdict:f.verdict==="pass"?"pass":f.verdict==="fail"?"fail":"uncertain",reasoning:f.reasoning,confidence:f.confidence,steps:[],pageUrl:e.pageUrl,finishedAt:new Date().toISOString(),costUsd:f.costUsd,model:f.model,inputHash:c};return await te(g),{ok:!0,result:g}}catch(f){return ao.error("reading-order audit failed",f),{ok:!1,error:f instanceof Error?f.message:String(f)}}}const no=J("non-text-contrast-audit"),Nt="1.4.11";async function Xl(e){var l,d,u,h;const t=await chrome.storage.local.get("aiConfig"),a=be(t.aiConfig),n=ce(a);if(!n.ok)return{ok:!1,error:n.reason};const o=n.client;if(typeof o.judgeNonTextContrast!="function")return{ok:!1,error:"AI client does not support judgeNonTextContrast yet."};const i=(l=e.results[0])==null?void 0:l.screenshotDataUrl;if(!i)return{ok:!1,error:"No page screenshot available — re-run the audit so a screenshot is captured."};if((d=e.signal)!=null&&d.aborted)return{ok:!1,error:"Audit cancelled."};let r=null;if(e.tabId!==void 0)try{const m=await chrome.scripting.executeScript({target:{tabId:e.tabId},func:Ql});r=((u=m==null?void 0:m[0])==null?void 0:u.result)??null}catch(m){no.warn("component contrast measurement failed; falling back to AI",m)}if(r&&r.measured.length>=1){const m=r.measured.filter(p=>p.ratio<3&&p.confident),f=r.measured.filter(p=>p.ratio>=3),g=r.measured.filter(p=>!p.confident);if(m.length>0){const p={componentId:e.componentId,criterionId:Nt,verdict:"fail",reasoning:`Programmatic measurement: ${m.length} of ${r.measured.length} measured components fail WCAG 1.4.11 (< 3:1 contrast against adjacent background). `+m.slice(0,6).map(b=>`${b.selector} — ${b.ratio.toFixed(2)}:1 (${b.borderColor} on ${b.backgroundColor})`).join("; ")+(f.length>0?` · ${f.length} other components pass.`:""),confidence:.98,steps:[],pageUrl:e.pageUrl,finishedAt:new Date().toISOString(),costUsd:0,model:"programmatic-contrast-measurement"};return await te(p),{ok:!0,result:p}}if(g.length===0&&f.length===r.measured.length){const p={componentId:e.componentId,criterionId:Nt,verdict:"pass",reasoning:`Programmatic measurement: ${f.length===1?"the":"all"} ${f.length} measured component${f.length===1?"":"s"} pass${f.length===1?"es":""} WCAG 1.4.11 (≥ 3:1 contrast against adjacent background). Lowest measured: ${(h=f.map(b=>b.ratio).sort((b,w)=>b-w)[0])==null?void 0:h.toFixed(2)}:1. Decorative-only graphics and components with transparent borders over images / gradients (which programmatic measurement cannot resolve) are not in scope of this measurement; if such components exist on the page they should be inspected separately.`,confidence:.95,steps:[],pageUrl:e.pageUrl,finishedAt:new Date().toISOString(),costUsd:0,model:"programmatic-contrast-measurement"};return await te(p),{ok:!0,result:p}}}let c;try{const m=await Ne(e.componentId,Nt);m&&(c={verdict:m.verdict,reasoning:m.reasoning,confidence:m.confidence})}catch{}try{const m=await He(()=>o.judgeNonTextContrast({pageUrl:e.pageUrl,pageScreenshot:i,previousVerdict:c,signal:e.signal})),f={componentId:e.componentId,criterionId:Nt,verdict:m.verdict==="pass"?"pass":m.verdict==="fail"?"fail":"uncertain",reasoning:m.reasoning,confidence:m.confidence,steps:[],pageUrl:e.pageUrl,finishedAt:new Date().toISOString(),costUsd:m.costUsd,model:m.model};return await te(f),{ok:!0,result:f}}catch(m){return no.error("non-text-contrast audit failed",m),{ok:!1,error:m instanceof Error?m.message:String(m)}}}function Ql(){function e(g){const p=g.match(/rgba?\(\s*([0-9.]+)\s*,?\s*([0-9.]+)\s*,?\s*([0-9.]+)(?:\s*[,/]\s*([0-9.]+))?\s*\)/i);if(!p)return null;const b=Math.round(parseFloat(p[1])),w=Math.round(parseFloat(p[2])),k=Math.round(parseFloat(p[3])),y=p[4]!==void 0?parseFloat(p[4]):1;return{r:b,g:w,b:k,a:y}}function t(g){const p=b=>{const w=b/255;return w<=.03928?w/12.92:Math.pow((w+.055)/1.055,2.4)};return .2126*p(g.r)+.7152*p(g.g)+.0722*p(g.b)}function a(g,p){const b=t(g),w=t(p),k=Math.max(b,w),y=Math.min(b,w);return(k+.05)/(y+.05)}function n(g,p){const b=Math.max(0,Math.min(1,g.a));return{r:Math.round(g.r*b+p.r*(1-b)),g:Math.round(g.g*b+p.g*(1-b)),b:Math.round(g.b*b+p.b*(1-b))}}function o(g){let p=g.parentElement;for(;p&&p!==document.documentElement;){const b=getComputedStyle(p);if(b.backgroundImage&&b.backgroundImage!=="none")return{color:b.backgroundColor,hasImage:!0};const k=e(b.backgroundColor);if(k&&k.a>.5)return{color:b.backgroundColor,hasImage:!1};p=p.parentElement}return{color:"rgb(255, 255, 255)",hasImage:!1}}function i(g){if(g.id)return`#${g.id}`;const p=g.tagName.toLowerCase(),b=g.className&&typeof g.className=="string"?g.className.split(/\s+/).filter(Boolean).slice(0,2).join("."):"";return b?`${p}.${b}`:p}function r(g,p){const b=getComputedStyle(g),w=e(b.backgroundColor);return w?w.a>=.999?w:w.a<=.001?p:n(w,p):p}function c(g,p){if((g.textContent??"").trim().length===0)return!1;const b=document.createTreeWalker(g,NodeFilter.SHOW_TEXT);let w;for(;w=b.nextNode();){if((w.textContent??"").trim().length===0)continue;const y=w.parentElement;if(!y)continue;const s=getComputedStyle(y);if(s.visibility==="hidden"||s.display==="none")continue;if(s.position==="absolute"){const I=parseFloat(s.width),$=parseFloat(s.height);if(I>0&&I<=1||$>0&&$<=1||s.clip==="rect(0px, 0px, 0px, 0px)"||s.clipPath==="inset(50%)")continue}const O=e(s.color);if(!O)continue;const E=O.a<1?n(O,p):O,v=a(E,p),D=parseFloat(s.fontSize)||16,S=parseInt(s.fontWeight,10)||400,P=D>=24||D>=18.5&&S>=700?3:4.5;if(v>=P)return!0}return!1}function l(g){const p=g.tagName.toLowerCase();if(p==="textarea"||p==="select")return!0;if(p==="input"){const b=(g.getAttribute("type")??"text").toLowerCase();return b!=="hidden"&&b!=="checkbox"&&b!=="radio"&&b!=="submit"&&b!=="button"&&b!=="reset"&&b!=="image"}return!1}function d(g,p){const b=document.activeElement;try{g.focus({preventScroll:!0})}catch{return null}if(document.activeElement!==g){if(b)try{b.focus({preventScroll:!0})}catch{}return null}const w=getComputedStyle(g);let k=0,y="";const s=w.outlineStyle,O=parseFloat(w.outlineWidth)||0;if(s!=="none"&&O>0){const v=e(w.outlineColor);if(v){const D=v.a<1?n(v,p):v,S=a(D,p);S>k&&(k=S,y=`outline ${O}px ${w.outlineColor}`)}}const E=w.boxShadow;if(E&&E!=="none"){const v=e(E);if(v){const D=v.a<1?n(v,p):v,S=a(D,p);S>k&&(k=S,y=`box-shadow ${E.slice(0,60)}`)}}try{g.blur()}catch{}if(b)try{b.focus({preventScroll:!0})}catch{}return k===0?null:{ratio:k,indicator:y}}function u(g){const p=[{attr:"aria-current",activeVal:"page"},{attr:"aria-pressed",activeVal:"true"},{attr:"aria-selected",activeVal:"true"},{attr:"aria-checked",activeVal:"true"}];let b=null;for(const R of p){const P=g.getAttribute(R.attr);if(P&&(R.attr==="aria-current"?P!=="false"&&P!=="":P===R.activeVal)){b=R;break}}if(!b)return null;const w=g.parentElement;if(!w)return null;const k=g.tagName.toLowerCase(),y=Array.from(w.children).filter(R=>{if(!(R instanceof HTMLElement)||R===g||R.tagName.toLowerCase()!==k)return!1;const P=R.getAttribute(b.attr);return P?b.attr==="aria-current"?P==="false"||P==="":P!==b.activeVal:!0});if(y.length===0)return null;const s=y[0],O=getComputedStyle(g),E=getComputedStyle(s),v=[["backgroundColor","background"],["color","text color"],["borderTopColor","border"]],D=o(g),S=e(D.color);if(!S)return null;for(const[R,P]of v){const I=e(O[R]),$=e(E[R]);if(!I||!$||I.r===$.r&&I.g===$.g&&I.b===$.b&&I.a===$.a)continue;const C=I.a<1?n(I,S):I,M=$.a<1?n($,S):$,F=a(C,M);return{activeColor:C,inactiveColor:M,ratio:F,ariaMarker:`${b.attr}="${g.getAttribute(b.attr)}" vs ${P}`}}return null}const m=Array.from(document.querySelectorAll('input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]), button, select, textarea, a[href][aria-current], a[href][role="tab"], [role="tab"], [role="button"][aria-pressed], [role="checkbox"][aria-checked]')).filter(g=>{const p=g.getBoundingClientRect();if(p.width<8||p.height<8)return!1;const b=getComputedStyle(g);return!(b.visibility==="hidden"||b.display==="none")}),f=[];for(const g of m){const p=getComputedStyle(g),b=o(g),w=b.hasImage?null:e(b.color),k=w?r(g,w):null;if(w&&k){const v=u(g);v&&v.ratio<3&&f.push({selector:i(g),role:g.getAttribute("role")??g.tagName.toLowerCase(),borderColor:`state: ${v.ariaMarker}`,backgroundColor:`active rgb(${v.activeColor.r},${v.activeColor.g},${v.activeColor.b}) vs inactive rgb(${v.inactiveColor.r},${v.inactiveColor.g},${v.inactiveColor.b})`,ratio:v.ratio,confident:!0})}if(w){const v=d(g,w);v!==null&&v.ratio<3&&f.push({selector:i(g),role:g.getAttribute("role")??g.tagName.toLowerCase(),borderColor:`focus: ${v.indicator}`,backgroundColor:b.color,ratio:v.ratio,confident:!0})}if(parseFloat(p.borderTopWidth)+parseFloat(p.borderRightWidth)+parseFloat(p.borderBottomWidth)+parseFloat(p.borderLeftWidth)===0)continue;const s=e(p.borderTopColor);if(!s)continue;if(b.hasImage){f.push({selector:i(g),role:g.getAttribute("role")??g.tagName.toLowerCase(),borderColor:p.borderTopColor,backgroundColor:b.color,ratio:0,confident:!1});continue}if(!w||!k||!l(g)&&c(g,k))continue;const O=s.a<1?n(s,w):s,E=a(O,w);if(f.push({selector:i(g),role:g.getAttribute("role")??g.tagName.toLowerCase(),borderColor:p.borderTopColor,backgroundColor:b.color,ratio:E,confident:!0}),f.length>=20)break}return{measured:f}}const mt=J("label-in-name-audit"),Ke="2.5.3";function Zl(){function e(c){return c.replace(/\s+/g," ").trim().toLowerCase()}function t(c){const l=c.cloneNode(!0);return l.querySelectorAll('[aria-hidden="true"]').forEach(d=>d.remove()),l.querySelectorAll(".sr-only, .visually-hidden, .screen-reader-text, .visuallyhidden").forEach(d=>d.remove()),(l.textContent??"").replace(/\s+/g," ").trim()}function a(c){var h,m,f,g;const l=c.getAttribute("aria-label");if(l&&l.trim())return l.trim();const d=c.getAttribute("aria-labelledby");if(d){const p=d.split(/\s+/).map(b=>document.getElementById(b)).filter(b=>b!==null).map(b=>{var w;return((w=b.textContent)==null?void 0:w.trim())??""}).filter(Boolean);if(p.length>0)return p.join(" ")}if(c.tagName==="INPUT"||c.tagName==="SELECT"||c.tagName==="TEXTAREA"){const p=c.getAttribute("id");if(p){const k=document.querySelector(`label[for="${p}"]`);if(k)return((h=k.textContent)==null?void 0:h.trim())??""}const b=c.closest("label");if(b)return((m=b.textContent)==null?void 0:m.trim())??"";const w=c.getAttribute("placeholder");if(w)return w.trim()}if(c.tagName==="BUTTON"||c.tagName==="A")return((f=c.textContent)==null?void 0:f.trim())??"";const u=c.getAttribute("value");return u&&c.type==="submit"?u.trim():((g=c.textContent)==null?void 0:g.trim())??""}function n(c){if(c.id)return`#${c.id}`;const l=c.tagName.toLowerCase(),d=c.className&&typeof c.className=="string"?"."+c.className.split(/\s+/).filter(Boolean).slice(0,2).join("."):"";return l+d}const o=["a[href]","button","input:not([type=hidden]):not([type=submit])[aria-label], input[aria-labelledby]","input[type=submit]","input[type=button]",'[role="button"]','[role="link"]','[role="checkbox"]','[role="radio"]','[role="switch"]','[role="tab"]','[role="menuitem"]'],i=Array.from(new Set(o.flatMap(c=>Array.from(document.querySelectorAll(c))))).filter(c=>{if(c.hasAttribute("disabled"))return!1;const l=c.getBoundingClientRect();if(l.width===0||l.height===0)return!1;const d=getComputedStyle(c);return!(d.visibility==="hidden"||d.display==="none")}),r=[];for(const c of i){const l=t(c),d=a(c);if(!l||!d)continue;const u=e(l),h=e(d);let m;h.includes(u)?m="pass":h.length===0||u.length===0?m="fail":m="ambiguous";const f=c.getAttribute("role")??c.tagName.toLowerCase();r.push({selector:n(c),role:f,visibleText:l.slice(0,200),accessibleName:d.slice(0,200),programmaticMatch:m})}return r}async function ed(e){var f,g,p,b;if((f=e.signal)!=null&&f.aborted)return{ok:!1,error:"Audit cancelled."};let t;try{const k=(await chrome.scripting.executeScript({target:{tabId:e.tabId},world:"MAIN",func:Zl}))[0];if(!k||!k.result)return{ok:!1,error:"DOM walker returned no result."};t=k.result}catch(w){return mt.warn("DOM walker injection failed",w),{ok:!1,error:`DOM walker failed: ${w instanceof Error?w.message:String(w)}`}}const a=t.filter(w=>w.programmaticMatch==="fail"),n=t.filter(w=>w.programmaticMatch==="ambiguous"),o=t.length;if(o===0){const w={componentId:e.componentId,criterionId:Ke,verdict:"pass",reasoning:"No interactive elements with both visible text AND a separate accessible name were found on the page. WCAG 2.5.3 has no applicable controls — counts as pass / not applicable.",confidence:.95,pageUrl:e.pageUrl,finishedAt:new Date().toISOString(),costUsd:0,model:"programmatic-only"};return await te(w),{ok:!0,result:w}}if(a.length===0&&n.length===0){const w={componentId:e.componentId,criterionId:Ke,verdict:"pass",reasoning:`${o} interactive controls audited; all visible labels are contained in their accessible names (case-insensitive substring match). WCAG 2.5.3 passes.`,confidence:.95,pageUrl:e.pageUrl,finishedAt:new Date().toISOString(),costUsd:0,model:"programmatic-only"};return await te(w),mt.info(`2.5.3 passed programmatically (${o} candidates, all substring-matched)`),{ok:!0,result:w}}const i=await chrome.storage.local.get("aiConfig"),r=be(i.aiConfig),c=ce(r);if(!c.ok){const w=`Programmatic-only verdict (no AI key configured): ${a.length} candidates with visible text NOT contained in their accessible name. Each of these is likely a WCAG 2.5.3 failure: ${a.slice(0,5).map(y=>`${y.role}/${y.selector} (visible="${y.visibleText.slice(0,50)}", accessible="${y.accessibleName.slice(0,50)}")`).join("; ")}${a.length>5?"; ...":""}. ${n.length} additional candidates are AMBIGUOUS (icon-prefix, plurals, smart-quotes) — configure an AI key to disambiguate.`,k={componentId:e.componentId,criterionId:Ke,verdict:a.length>0?"fail":"uncertain",reasoning:w,confidence:.6,pageUrl:e.pageUrl,finishedAt:new Date().toISOString(),costUsd:0,model:"programmatic-only"};return await te(k),{ok:!0,result:k}}const l=c.client;if(typeof l.judgeLabelInName!="function")return{ok:!1,error:"AI client does not support judgeLabelInName."};if((g=e.signal)!=null&&g.aborted)return{ok:!1,error:"Audit cancelled."};const d=(b=(p=e.results)==null?void 0:p[0])==null?void 0:b.screenshotDataUrl,u=[...a,...n],h=await ha(Ke,{candidates:u.map(w=>({selector:w.selector,role:w.role,visibleText:w.visibleText,accessibleName:w.accessibleName,programmaticMatch:w.programmaticMatch}))}),m=await pa(e.componentId,Ke,h);if(m.hit){mt.info(`2.5.3 inputs unchanged (hash match); reusing verdict=${m.previous.verdict}`);const w=fa(m.previous,h);return await te(w),{ok:!0,result:w}}try{const w=await He(()=>l.judgeLabelInName({pageUrl:e.pageUrl,pageScreenshot:d,candidates:u,signal:e.signal})),k=w.verdict==="pass"?"pass":w.verdict==="fail"?"fail":"uncertain",y={componentId:e.componentId,criterionId:Ke,verdict:k,reasoning:`${o} interactive controls audited. ${t.length-u.length} passed the strict substring match; ${u.length} (${a.length} strict fails + ${n.length} ambiguous) sent to AI judgment. AI verdict: ${k}. `+w.reasoning,confidence:w.confidence,pageUrl:e.pageUrl,finishedAt:new Date().toISOString(),costUsd:w.costUsd,model:w.model,inputHash:h};return await te(y),mt.info(`2.5.3 resolved to ${k} (${u.length} ambiguous cases AI-judged)`),{ok:!0,result:y}}catch(w){return mt.error("label-in-name AI judgment failed",w),{ok:!1,error:w instanceof Error?w.message:String(w)}}}const qe=J("focus-order-cascade");function td(){const e=Array.from(document.querySelectorAll('a[href], button, input:not([type=hidden]), select, textarea, [tabindex]:not([tabindex="-1"])')).filter(i=>{if(i.hasAttribute("disabled"))return!1;const r=i.getBoundingClientRect();if(r.width===0||r.height===0)return!1;const c=getComputedStyle(i);return!(c.visibility==="hidden"||c.display==="none")});if(e.length<4)return{matches:!1,pattern:"none",evidence:`only ${e.length} focusable element(s) on page — cascade does not apply`,totalFocusables:e.length,largestCluster:0};const t=new Map;for(const i of e){let r=i.parentElement;for(;r&&r!==document.body&&r!==document.documentElement;){const c=getComputedStyle(r);let l="";if(c.display==="grid"||c.display==="inline-grid"){const d=c.gridTemplateColumns.trim().split(/\s+/).filter(Boolean);d.length>=2&&c.gridTemplateColumns!=="none"&&(l=`grid-${d.length}-cols`)}else if(c.display==="flex"||c.display==="inline-flex")c.flexDirection.startsWith("row")&&(c.flexWrap==="wrap"||c.flexWrap==="wrap-reverse"?l="flex-row-wrap":r.children.length>=2&&(l="flex-row"));else{const d=c.columnCount;d&&d!=="auto"&&parseInt(d,10)>=2&&(l=`css-columns-${d}`)}if(l){const d=t.get(r);d?d.count++:t.set(r,{count:1,pattern:l});break}r=r.parentElement}}let a={count:0,pattern:"none"};for(const i of t.values())i.count>a.count&&(a={count:i.count,pattern:i.pattern});const n=Math.max(2,Math.ceil(e.length*.3)),o=a.count>=n;return{matches:o,pattern:o?a.pattern:"none",evidence:o?`${a.count} of ${e.length} focusable elements share a ${a.pattern} container (threshold ${n})`:`largest multi-column cluster was ${a.count} of ${e.length} focusables; threshold was ${n} — no dominant banked layout`,totalFocusables:e.length,largestCluster:a.count}}function ad(){const e='a[href], button:not([disabled]), input:not([disabled]):not([type="hidden"]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"]):not([disabled])',t=Array.from(document.querySelectorAll('nav, [role="navigation"], [role="group"], section[aria-labelledby], section[aria-label], aside[aria-labelledby], aside[aria-label], header[aria-labelledby], footer[aria-labelledby]')),a=t.filter(i=>t.some(r=>r!==i&&r.contains(i))?!1:!t.some(r=>r!==i&&i.contains(r)));let n=0;for(const i of a)i.querySelector(e)&&n++;const o=n>=2;return{matches:o,evidence:o?`${n} distinct labeled navigation regions found on page (e.g., multiple <nav> blocks in a footer). Column-first focus traversal across these groups is a valid pattern — screen readers announce each group's label before its contents.`:`${n} labeled navigation region(s) found — need ≥2 with focusable content to trigger multi-region override`,groupCount:a.length,groupsWithFocusables:n}}async function nd(e){if(e.focusOrderVerdict!=="uncertain")return{ran:!1,reason:`2.4.3 verdict was ${e.focusOrderVerdict}, not uncertain — cascade does not apply`};if(e.keyboardTrapVerdict!=="pass")return{ran:!1,reason:`2.1.2 verdict was ${e.keyboardTrapVerdict??"absent"}, not pass — cascade does not apply (need empirical focus-path functionality)`};const t=await Ne(e.componentId,"2.4.3");if(!t)return{ran:!1,reason:"2.4.3 record not found in storage — cannot apply cascade"};let a=null;try{const r=(await chrome.scripting.executeScript({target:{tabId:e.tabId},world:"MAIN",func:td}))[0];r&&r.result&&(a=r.result)}catch(i){qe.warn("focus-order cascade detector failed",i)}if(a&&a.matches){const i={...t,verdict:"pass",reasoning:t.reasoning+`
1018
+
1019
+ [Cascade resolution — rc.167 fallback-1]
1020
+ Primary AI verdict was uncertain. WCAG 2.1.2 No Keyboard Trap PASSED on the same DOM (Tab walks forward AND Shift+Tab walks back through every focusable). The programmatic multi-column layout detector confirmed: `+a.evidence+`. The heuristic's "reverse y-coordinate" complaint is a known false-positive pattern for banked layouts — combined with the empirical 2.1.2 PASS, this resolves to PASS. If the visual reading order is intentionally column-first (typical for footers, sidebars, card grids), this is correct behavior.`};return await te(i),qe.info(`2.4.3 cascade resolved uncertain → pass via fallback-1 (${a.pattern}, ${a.largestCluster}/${a.totalFocusables} focusables clustered)`),{ran:!0,resolved:"pass",via:"fallback-1",pattern:a.pattern,evidence:a.evidence}}const n=await od(e,t);if(n.ran&&n.resolved==="pass"||n.ran&&n.resolved==="fail")return n;const o={...t,verdict:"fail",reasoning:t.reasoning+`
1021
+
1022
+ [Cascade resolution — rc.169 escalation]
1023
+ All cascade fallbacks returned uncertain. Primary AI judgment: uncertain. Programmatic multi-column-layout detector: `+(a?a.evidence:"did not run (script injection failed)")+". Focused AI re-call (fallback 2): "+(n.ran?n.evidence??"returned uncertain":n.reason)+". Marked as FAIL with required manual verification — the user can override this verdict via the human-review UI in the side panel."};return await te(o),qe.info("2.4.3 cascade escalated to FAIL — all fallbacks returned uncertain; manual verification required"),{ran:!0,resolved:"fail",via:"escalation",evidence:"all fallbacks returned uncertain; verdict escalated to fail pending manual verification"}}async function od(e,t){if(!e.tabSequence||e.tabSequence.length===0)return{ran:!0,resolved:"uncertain",via:"none",evidence:"fallback-2 skipped — no tab sequence provided (primary walkthrough did not capture steps)"};let a;try{const i=await chrome.storage.local.get("aiConfig");a=be(i.aiConfig)}catch(i){return qe.warn("fallback-2: failed to read aiConfig",i),{ran:!0,resolved:"uncertain",via:"none",evidence:"fallback-2 skipped — could not load aiConfig"}}const n=ce(a);if(!n.ok)return{ran:!0,resolved:"uncertain",via:"none",evidence:`fallback-2 skipped — ${n.reason}`};const o=n.client;if(typeof o.judgeFocusOrderFallback!="function")return{ran:!0,resolved:"uncertain",via:"none",evidence:"fallback-2 skipped — AI client does not support judgeFocusOrderFallback (older provider)"};try{const i=await He(()=>o.judgeFocusOrderFallback({pageUrl:e.pageUrl,pageScreenshot:e.pageScreenshot,tabSequence:e.tabSequence,primaryUncertainReason:t.reasoning,signal:e.signal}));if(i.verdict==="pass"){const r={...t,verdict:"pass",reasoning:t.reasoning+`
1024
+
1025
+ [Cascade resolution — rc.169 fallback-2]
1026
+ Primary AI verdict was uncertain. Programmatic multi-column detector found no matching pattern (long-tail case). Focused AI re-call resolved to PASS with reasoning: `+i.reasoning+`
1027
+
1028
+ The focused prompt explicitly enumerates long-tail false-positive patterns (custom JS focus management, roving tabindex composite widgets, sticky sidebars, skip-link patterns, modal traps, table layouts, absolute positioning) — when the AI identifies one of these AND the tab sequence is consistent with the pattern, the cascade resolves to PASS.`,costUsd:t.costUsd+i.costUsd};return await te(r),qe.info("2.4.3 cascade resolved uncertain → pass via fallback-2 (focused AI re-call)"),{ran:!0,resolved:"pass",via:"fallback-2",evidence:`focused AI re-call returned pass with confidence ${i.confidence.toFixed(2)}`}}if(i.verdict==="fail"){const r={...t,verdict:"fail",reasoning:t.reasoning+`
1029
+
1030
+ [Cascade resolution — rc.169 fallback-2]
1031
+ Primary AI verdict was uncertain. Programmatic multi-column detector found no matching pattern. Focused AI re-call resolved to FAIL with reasoning: `+i.reasoning,costUsd:t.costUsd+i.costUsd};return await te(r),qe.info("2.4.3 cascade resolved uncertain → fail via fallback-2 (focused AI re-call detected a real failure)"),{ran:!0,resolved:"fail",via:"fallback-2",evidence:`focused AI re-call returned fail with confidence ${i.confidence.toFixed(2)}`}}return{ran:!0,resolved:"uncertain",via:"fallback-2",evidence:`focused AI re-call also returned uncertain — ${i.reasoning.slice(0,200)}`}}catch(i){return qe.warn("fallback-2 AI re-call threw",i),{ran:!0,resolved:"uncertain",via:"none",evidence:`fallback-2 AI re-call threw: ${i instanceof Error?i.message:String(i)}`}}}const de=J("interactive-walkthroughs");async function Qe(e,t){if(!e.verifyFixesOnly)return null;const a=await Ne(e.componentId,t);return a?await Ls(e.componentId,t,e.pageUrl)?(de.info(`${t} skipped (verify-fixes): ack present`),a):a.verdict==="pass"?(de.info(`${t} skipped (verify-fixes): previously passed`),a):null:null}async function id(e){var a,n,o,i,r,c,l;const t=[];try{if((a=e.signal)!=null&&a.aborted)t.push({criterionId:"2.4.3",ran:!1,ok:!1,error:"cancelled"});else{const d=await Qe(e,"2.4.3");if(d)t.push({criterionId:"2.4.3",ran:!1,ok:!0,verdict:d.verdict,skippedForVerifyFixes:!0});else{const u=await Gl({tabId:e.tabId,componentId:e.componentId,pageUrl:e.pageUrl,signal:e.signal});t.push({criterionId:"2.4.3",ran:!0,ok:u.ok,verdict:u.ok?u.result.verdict:void 0,error:u.ok?void 0:u.error})}}}catch(d){de.warn("2.4.3 walkthrough threw",d),t.push({criterionId:"2.4.3",ran:!0,ok:!1,error:d instanceof Error?d.message:String(d)})}try{if((n=e.signal)!=null&&n.aborted)t.push({criterionId:"2.1.2",ran:!1,ok:!1,error:"cancelled"});else{const d=await Qe(e,"2.1.2");if(d)t.push({criterionId:"2.1.2",ran:!1,ok:!0,verdict:d.verdict,skippedForVerifyFixes:!0});else{const u=await jl({tabId:e.tabId,componentId:e.componentId,pageUrl:e.pageUrl,signal:e.signal});t.push({criterionId:"2.1.2",ran:!0,ok:u.ok,verdict:u.ok?u.result.verdict:void 0,error:u.ok?void 0:u.error})}}}catch(d){de.warn("2.1.2 walkthrough threw",d),t.push({criterionId:"2.1.2",ran:!0,ok:!1,error:d instanceof Error?d.message:String(d)})}try{if((o=e.signal)!=null&&o.aborted)t.push({criterionId:"2.4.7",ran:!1,ok:!1,error:"cancelled"});else{const d=await Qe(e,"2.4.7");if(d)t.push({criterionId:"2.4.7",ran:!1,ok:!0,verdict:d.verdict,skippedForVerifyFixes:!0});else{const u=await Bl({tabId:e.tabId,componentId:e.componentId,pageUrl:e.pageUrl,signal:e.signal});t.push({criterionId:"2.4.7",ran:!0,ok:u.ok,verdict:u.ok?u.result.verdict:void 0,error:u.ok?void 0:u.error})}}}catch(d){de.warn("2.4.7 walkthrough threw",d),t.push({criterionId:"2.4.7",ran:!0,ok:!1,error:d instanceof Error?d.message:String(d)})}try{const d=t.find(u=>u.criterionId==="2.4.3");if(d!=null&&d.ok&&d.verdict==="fail"){const u=await chrome.scripting.executeScript({target:{tabId:e.tabId},func:ad}),h=(i=u==null?void 0:u[0])==null?void 0:i.result;if(h!=null&&h.matches){const m=await Ne(e.componentId,"2.4.3");if(m&&m.verdict==="fail"){const f={...m,verdict:"pass",reasoning:m.reasoning+`
1032
+
1033
+ [Labeled-group nav override — rc.270] Programmatic detector found ${h.groupsWithFocusables} distinct labeled navigation regions on this page. ${h.evidence} Per WCAG 2.4.3, column-first traversal ACROSS distinct labeled regions is a valid focus order — the user can locate themselves via each region's label (announced by screen readers) before traversing within the region. Overriding fail → pass.`};await te(f),d.verdict="pass",de.info(`2.4.3 fail overridden → pass (rc.270 labeled-group nav: ${h.groupsWithFocusables} regions)`)}}}}catch(d){de.warn("2.4.3 labeled-group nav override step failed",d)}try{const d=t.find(h=>h.criterionId==="2.4.3"),u=t.find(h=>h.criterionId==="2.1.2");if(d!=null&&d.ok&&d.verdict==="fail"&&(u!=null&&u.ok)&&u.verdict==="pass"){const h=await Ne(e.componentId,"2.4.3");if(h&&h.verdict==="fail"){const m={...h,verdict:"uncertain",reasoning:h.reasoning+`
1034
+
1035
+ [Cross-corroboration override] WCAG 2.1.2 No Keyboard Trap PASSED on this same DOM. Tab walks forward AND Shift+Tab walks back. The "reverse y-coordinate" pattern this rule flagged is consistent with a multi-column or banked layout (header → main → sidebar → footer), not an actual focus-order break. Downgraded fail → uncertain. Verify manually if the visual reading order is ambiguous.`};await te(m),d.verdict="uncertain",de.info("2.4.3 fail downgraded to uncertain — 2.1.2 PASSed (multi-column layout corroboration)")}}}catch(d){de.warn("2.4.3 ↔ 2.1.2 cross-corroboration step failed",d)}try{const d=t.find(h=>h.criterionId==="2.4.3"),u=t.find(h=>h.criterionId==="2.1.2");if(d&&d.verdict==="uncertain"&&(u==null?void 0:u.verdict)==="pass"){const h=await Ne(e.componentId,"2.4.3"),m=await nd({tabId:e.tabId,componentId:e.componentId,pageUrl:e.pageUrl,focusOrderVerdict:d.verdict,keyboardTrapVerdict:u.verdict,tabSequence:(r=h==null?void 0:h.steps)==null?void 0:r.filter(f=>f.action!=="initial").map(f=>({ordinal:f.ordinal,selector:f.focused.selector,role:f.focused.role,name:f.focused.name,rect:f.focused.rect??{x:0,y:0,width:0,height:0}})),pageScreenshot:(l=(c=h==null?void 0:h.steps)==null?void 0:c[0])==null?void 0:l.screenshot,signal:e.signal});m.ran&&m.resolved==="pass"?(d.verdict="pass",de.info(`2.4.3 uncertain resolved to PASS via cascade ${m.via} (${m.pattern??m.evidence.slice(0,80)})`)):m.ran&&m.resolved==="fail"?(d.verdict="fail",de.info(`2.4.3 resolved to FAIL via cascade ${m.via}: ${m.evidence.slice(0,80)}`)):m.ran&&m.resolved==="uncertain"?de.info("2.4.3 cascade exhausted all paths — uncertain stays. (this shouldn't happen post-rc.169; escalation should have fired)"):m.ran||de.info(`2.4.3 cascade did not run: ${m.reason}`)}}catch(d){de.warn("2.4.3 cascade step failed",d)}return t}async function rd(e){var t;try{if((t=e.signal)!=null&&t.aborted)return{criterionId:"1.3.2",ran:!1,ok:!1,error:"cancelled"};const a=await Qe(e,"1.3.2");if(a)return{criterionId:"1.3.2",ran:!1,ok:!0,verdict:a.verdict,skippedForVerifyFixes:!0};if(!e.results||e.results.length===0)return{criterionId:"1.3.2",ran:!1,ok:!1,error:"no audit results available"};const n=await Jl({componentId:e.componentId,pageUrl:e.pageUrl,results:e.results,signal:e.signal});return{criterionId:"1.3.2",ran:!0,ok:n.ok,verdict:n.ok?n.result.verdict:void 0,error:n.ok?void 0:n.error}}catch(a){return de.warn("1.3.2 walkthrough threw",a),{criterionId:"1.3.2",ran:!0,ok:!1,error:a instanceof Error?a.message:String(a)}}}async function sd(e){var t;try{if((t=e.signal)!=null&&t.aborted)return{criterionId:"1.4.11",ran:!1,ok:!1,error:"cancelled"};const a=await Qe(e,"1.4.11");if(a)return{criterionId:"1.4.11",ran:!1,ok:!0,verdict:a.verdict,skippedForVerifyFixes:!0};if(!e.results||e.results.length===0)return{criterionId:"1.4.11",ran:!1,ok:!1,error:"no audit results available"};const n=await Xl({componentId:e.componentId,pageUrl:e.pageUrl,results:e.results,signal:e.signal,tabId:e.tabId});return{criterionId:"1.4.11",ran:!0,ok:n.ok,verdict:n.ok?n.result.verdict:void 0,error:n.ok?void 0:n.error}}catch(a){return de.warn("1.4.11 walkthrough threw",a),{criterionId:"1.4.11",ran:!0,ok:!1,error:a instanceof Error?a.message:String(a)}}}async function cd(e){var t;try{if((t=e.signal)!=null&&t.aborted)return{criterionId:"2.5.3",ran:!1,ok:!1,error:"cancelled"};const a=await Qe(e,"2.5.3");if(a)return{criterionId:"2.5.3",ran:!1,ok:!0,verdict:a.verdict,skippedForVerifyFixes:!0};const n=await ed({tabId:e.tabId,componentId:e.componentId,pageUrl:e.pageUrl,results:e.results,signal:e.signal});return{criterionId:"2.5.3",ran:!0,ok:n.ok,verdict:n.ok?n.result.verdict:void 0,error:n.ok?void 0:n.error}}catch(a){return de.warn("2.5.3 walkthrough threw",a),{criterionId:"2.5.3",ran:!0,ok:!1,error:a instanceof Error?a.message:String(a)}}}async function ld(e){const t=JSON.stringify({ruleId:e.ruleId,selector:e.selector,fg:e.foreground??null,fs:e.fontSize??null,fw:e.fontWeight??null,summary:e.failureSummary??null}),a=new TextEncoder().encode(t),n=await crypto.subtle.digest("SHA-256",a);return Array.from(new Uint8Array(n)).map(o=>o.toString(16).padStart(2,"0")).join("")}const Ze=J("pixel-contrast-sampler"),dd="1.3",ud=4.5,hd=3;function pd(e,t){return e?e>=24||e>=18.66&&(t??400)>=700:!1}function fd(e){if(!e)return null;const t=/^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/.exec(e);if(t)return{r:Number(t[1]),g:Number(t[2]),b:Number(t[3])};const a=/^#([0-9a-f])([0-9a-f])([0-9a-f])$/i.exec(e);if(a)return{r:parseInt(a[1]+a[1],16),g:parseInt(a[2]+a[2],16),b:parseInt(a[3]+a[3],16)};const n=/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(e);return n?{r:parseInt(n[1],16),g:parseInt(n[2],16),b:parseInt(n[3],16)}:null}function oo({r:e,g:t,b:a}){const n=o=>{const i=o/255;return i<=.03928?i/12.92:Math.pow((i+.055)/1.055,2.4)};return .2126*n(e)+.7152*n(t)+.0722*n(a)}function gd(e,t){const a=oo(e),n=oo(t),o=Math.max(a,n),i=Math.min(a,n);return(o+.05)/(i+.05)}async function md(e,t){var n;const a=`(() => {
1036
+ const dpr = window.devicePixelRatio || 1;
1037
+ const sx = window.scrollX || 0;
1038
+ const sy = window.scrollY || 0;
1039
+ const sels = ${JSON.stringify(t)};
1040
+ const out = {};
1041
+ // rc.225 — Filter visually-hidden elements BEFORE the sampler runs.
1042
+ // ada.gov's .usa-sr-only is the smoking gun: position:absolute +
1043
+ // clip:rect(0,0,0,0) + width:1px means it's intentionally invisible
1044
+ // to sighted users. axe-core's incomplete logic doesn't always know
1045
+ // this, and the sampler then produces a meaningless 1:1 ratio. The
1046
+ // checks below are the same patterns CSS authors use for "visually
1047
+ // hidden, still in a11y tree" — collectively they cover every
1048
+ // mainstream framework's sr-only utility (USWDS, Bootstrap,
1049
+ // Tailwind's .sr-only, Material's .mat-sr-only, etc.).
1050
+ function isVisuallyHidden(el, r) {
1051
+ const cs = getComputedStyle(el);
1052
+ if (cs.display === 'none') return true;
1053
+ if (cs.visibility === 'hidden' || cs.visibility === 'collapse') return true;
1054
+ if (parseFloat(cs.opacity || '1') === 0) return true;
1055
+ // Sub-pixel sizing pattern (sr-only): width 1px, height 1px.
1056
+ if (r.width <= 1 && r.height <= 1) return true;
1057
+ // Classic clip: rect(0,0,0,0) — older sr-only pattern.
1058
+ const clip = cs.clip || '';
1059
+ if (/rect\\(0\\w*,?\\s*0\\w*,?\\s*0\\w*,?\\s*0\\w*\\)/i.test(clip)) return true;
1060
+ // Modern clip-path: inset(50%) or inset(100%) — current sr-only.
1061
+ const clipPath = cs.clipPath || cs.webkitClipPath || '';
1062
+ if (/inset\\(50%|inset\\(100%/i.test(clipPath)) return true;
1063
+ // Off-screen positioning: top/left far enough out that no real
1064
+ // user can see them. Heuristic — only fires when the absolute
1065
+ // offset is wildly large.
1066
+ if (cs.position === 'absolute' || cs.position === 'fixed') {
1067
+ const top = parseFloat(cs.top || '0');
1068
+ const left = parseFloat(cs.left || '0');
1069
+ if (top <= -9999 || left <= -9999) return true;
1070
+ }
1071
+ return false;
1072
+ }
1073
+ for (const sel of sels) {
1074
+ try {
1075
+ const el = document.querySelector(sel);
1076
+ if (!el) { out[sel] = null; continue; }
1077
+ const r = el.getBoundingClientRect();
1078
+ if (r.width === 0 || r.height === 0) { out[sel] = null; continue; }
1079
+ if (isVisuallyHidden(el, r)) { out[sel] = null; continue; }
1080
+ out[sel] = {
1081
+ x: r.left + sx,
1082
+ y: r.top + sy,
1083
+ width: r.width,
1084
+ height: r.height,
1085
+ devicePixelRatio: dpr,
1086
+ selector: sel,
1087
+ };
1088
+ } catch { out[sel] = null; }
1089
+ }
1090
+ return JSON.stringify(out);
1091
+ })()`;try{const o=await chrome.debugger.sendCommand({tabId:e},"Runtime.evaluate",{expression:a,returnByValue:!0}),i=(n=o==null?void 0:o.result)==null?void 0:n.value;if(!i||typeof i!="string")return new Map;const r=JSON.parse(i);return new Map(Object.entries(r))}catch(o){return Ze.debug("getAllRectsAtOnce failed",o),new Map}}const kn="__wcagcheckr_hidetext";async function bd(e){const t=`
1092
+ (async () => {
1093
+ // Wait for fonts so hide/restore screenshots show identical glyph metrics.
1094
+ try { await document.fonts.ready; } catch {}
1095
+ const s = document.createElement('style');
1096
+ s.id = ${JSON.stringify(kn)};
1097
+ s.textContent = '*, *::before, *::after { color: transparent !important; text-shadow: none !important; -webkit-text-fill-color: transparent !important; caret-color: transparent !important; }';
1098
+ document.documentElement.appendChild(s);
1099
+ // Force a reflow + paint so the next screenshot captures the new state.
1100
+ document.body.offsetHeight;
1101
+ return true;
1102
+ })()
1103
+ `;await chrome.debugger.sendCommand({tabId:e},"Runtime.evaluate",{expression:t,awaitPromise:!0})}async function yd(e){const t=`
1104
+ (() => {
1105
+ const el = document.getElementById(${JSON.stringify(kn)});
1106
+ if (el) el.remove();
1107
+ document.body.offsetHeight;
1108
+ return true;
1109
+ })()
1110
+ `;await chrome.debugger.sendCommand({tabId:e},"Runtime.evaluate",{expression:t})}async function io(e,t){let a;try{a=await chrome.debugger.sendCommand({tabId:e},"Page.captureScreenshot",{format:"png",captureBeyondViewport:!0,clip:{x:t.x,y:t.y,width:t.width,height:t.height,scale:1}})}catch(n){return Ze.warn(`captureElementClip failed: ${n instanceof Error?n.message:String(n)}`),null}if(!(a!=null&&a.data))return null;try{const n=`data:image/png;base64,${a.data}`,o=await fetch(n).then(l=>l.blob()),i=await createImageBitmap(o),c=new OffscreenCanvas(i.width,i.height).getContext("2d");return c?(c.drawImage(i,0,0),c.getImageData(0,0,i.width,i.height)):null}catch(n){return Ze.warn(`clip decode failed: ${n instanceof Error?n.message:String(n)}`),null}}const wd=25,Ai=8,vd=150,Ad=.95,kd=4,Id=400;function xd(e,t,a){if(e.width!==t.width||e.height!==t.height)return null;const n=e.width,o=e.height;let i=0,r=0;for(let y=0;y<o;y++)for(let s=0;s<n;s++){const O=(y*n+s)*4,E=t.data[O],v=t.data[O+1],D=t.data[O+2],S=e.data[O],R=e.data[O+1],P=e.data[O+2],I=Math.abs(S-E)+Math.abs(R-v)+Math.abs(P-D);I>r&&(r=I),I>=wd&&i++}if(i<Ai)return null;const c=r*Ad;let l=0,d=0,u=0,h=0;if(r>=Id)for(let y=0;y<o;y++)for(let s=0;s<n;s++){const O=(y*n+s)*4,E=t.data[O],v=t.data[O+1],D=t.data[O+2],S=e.data[O],R=e.data[O+1],P=e.data[O+2];Math.abs(S-E)+Math.abs(R-v)+Math.abs(P-D)>=c&&(l+=E,d+=v,u+=D,h++)}const m=h>=kd?{r:Math.round(l/h),g:Math.round(d/h),b:Math.round(u/h)}:a,f=new Map;let g=0;for(let y=0;y<o;y++)for(let s=0;s<n;s++){const O=(y*n+s)*4,E=t.data[O],v=t.data[O+1],D=t.data[O+2];if(Math.abs(E-m.r)+Math.abs(v-m.g)+Math.abs(D-m.b)>=vd){g++;const R=E>>4<<8|v>>4<<4|D>>4,P=f.get(R);P?(P.r+=E,P.g+=v,P.b+=D,P.count++):f.set(R,{r:E,g:v,b:D,count:1})}}const p=n*o;if(g<p*.2){f.clear();for(let y=0;y<o;y++)for(let s=0;s<n;s++){const O=(y*n+s)*4,E=e.data[O],v=e.data[O+1],D=e.data[O+2],S=E>>4<<8|v>>4<<4|D>>4,R=f.get(S);R?(R.r+=E,R.g+=v,R.b+=D,R.count++):f.set(S,{r:E,g:v,b:D,count:1})}}let b={r:0,g:0,b:0,count:0};for(const y of f.values())y.count>b.count&&(b=y);const w={r:Math.round(b.r/b.count),g:Math.round(b.g/b.count),b:Math.round(b.b/b.count)},k=gd(m,w);return{textPixelCount:i,worstRatio:k,worstBackground:w,worstForeground:m}}async function Sd(e,t,a){var l,d,u,h,m;const n=new Map;for(const f of t){const g=f.pageUrl??f.scope??"";for(const p of((l=f.axeRulesEvaluated)==null?void 0:l.incomplete)??[])if(p.ruleId==="color-contrast")for(const b of p.elements??[]){const w=`${g}::${p.ruleId}::${b.selector}`;n.has(w)||n.set(w,{pageUrl:g,ruleId:p.ruleId,selector:b.selector,wcagCriterion:p.wcagCriterion??"1.4.3",foreground:(d=b.styles)==null?void 0:d.foreground,fontSize:(u=b.styles)==null?void 0:u.fontSize,fontWeight:(h=b.styles)==null?void 0:h.fontWeight,failureSummary:b.failureSummary})}}if(n.size===0)return{resolved:0,skipped:0};let o=!1,i=null;for(let f=1;f<=6;f++)try{await chrome.debugger.attach({tabId:e},dd),o=!0;break}catch(g){i=g;const p=g instanceof Error?g.message:String(g);if(/already attached/i.test(p)){await new Promise(b=>setTimeout(b,500));continue}break}if(!o)return Ze.warn("debugger attach failed after retries; skipping pixel sampling",i),{resolved:0,skipped:n.size};let r=0,c=0;try{const f=Array.from(n.values()),g=f.map(v=>v.selector),p=await md(e,g);if(a!=null&&a.aborted)return{resolved:0,skipped:n.size};const b=((m=f[0])==null?void 0:m.pageUrl)??"",w=b?await Ko(b):[],k=new Map;for(const v of w)k.set(`${v.ruleId}::${v.selector}`,v);const y=new Map,s=[];for(const v of f){const D=await ld(v);y.set(v.selector,D);const S=k.get(`${v.ruleId}::${v.selector}`);if(S&&S.inputHash===D&&S.verdict!=="uncertain"){const R={...S,resolvedAt:new Date().toISOString(),reasoning:S.reasoning.includes("(cached, hash-match)")?S.reasoning:`${S.reasoning} (cached, hash-match)`};await Fe(R),r++;continue}s.push(v)}if(s.length===0)return Ze.info(`pixel-contrast sampler: all ${f.length} targets reused from hash-cache`),{resolved:r,skipped:c};await bd(e);const O=new Map;for(const v of s){if(a!=null&&a.aborted)break;const D=p.get(v.selector);if(!D)continue;const S=await io(e,D);S&&O.set(v.selector,S)}if(await yd(e),a!=null&&a.aborted)return{resolved:0,skipped:n.size};const E=new Map;for(const v of s){if(a!=null&&a.aborted)break;const D=p.get(v.selector);if(!D)continue;const S=await io(e,D);S&&E.set(v.selector,S)}for(const v of s){if(a!=null&&a.aborted)break;const D=y.get(v.selector);if(!p.get(v.selector)){c++;const j={pageUrl:v.pageUrl,ruleId:v.ruleId,selector:v.selector,verdict:"uncertain",reasoning:"Hide-text observation: element's bounding rect could not be resolved at audit time (element may be display:none, detached from DOM, or zero-size). Cannot sample contrast empirically. Manual verification required if you believe this element is visible.",resolvedAt:new Date().toISOString(),costUsd:0,wcagCriterion:v.wcagCriterion,inputHash:D};await Fe(j);continue}const R=O.get(v.selector),P=E.get(v.selector);if(!R||!P){c++;const j={pageUrl:v.pageUrl,ruleId:v.ruleId,selector:v.selector,verdict:"uncertain",reasoning:"Hide-text observation: per-element clip capture failed via chrome.debugger Page.captureScreenshot. Manual verification required.",resolvedAt:new Date().toISOString(),costUsd:0,wcagCriterion:v.wcagCriterion,inputHash:D};await Fe(j);continue}const I=fd(v.foreground);if(!I){c++;const j={pageUrl:v.pageUrl,ruleId:v.ruleId,selector:v.selector,verdict:"uncertain",reasoning:`Hide-text observation: element's computed CSS foreground color could not be parsed (got ${JSON.stringify(v.foreground)}). Cannot compute contrast against rendered background without an intended foreground color. Manual verification required.`,resolvedAt:new Date().toISOString(),costUsd:0,wcagCriterion:v.wcagCriterion,inputHash:D};await Fe(j);continue}const $=xd(R,P,I);if(!$){c++;const j={pageUrl:v.pageUrl,ruleId:v.ruleId,selector:v.selector,verdict:"uncertain",reasoning:`Hide-text observation: too few text pixels detected (under ${Ai} after diffing hidden vs visible captures). Element may be canvas-rendered, SVG with non-standard rendering, or carry styles that prevent color: transparent from hiding the text. Manual verification required.`,resolvedAt:new Date().toISOString(),costUsd:0,wcagCriterion:v.wcagCriterion,inputHash:D};await Fe(j);continue}const C=pd(v.fontSize,v.fontWeight),M=C?hd:ud,F=$.worstRatio>=M,q=j=>`rgb(${j.r},${j.g},${j.b})`,N={pageUrl:v.pageUrl,ruleId:v.ruleId,selector:v.selector,verdict:F?"pass":"fail",reasoning:`Hide-text observation: ${$.textPixelCount} text-core pixels detected by diffing hidden-vs-visible viewport screenshots. Worst per-pixel contrast: ${$.worstRatio.toFixed(2)}:1 against AA threshold ${M}:1 (${C?"large text":"normal text"}). At worst pixel: rendered foreground ${q($.worstForeground)} on rendered background ${q($.worstBackground)}. ${F?"Passes threshold.":"Falls below threshold — at least one text pixel reads against a background that fails contrast."}`,resolvedAt:new Date().toISOString(),costUsd:0,wcagCriterion:v.wcagCriterion,inputHash:D};await Fe(N),r++}}finally{try{await chrome.debugger.sendCommand({tabId:e},"Runtime.evaluate",{expression:`(() => { const el = document.getElementById(${JSON.stringify(kn)}); if (el) el.remove(); return true; })()`})}catch{}try{await chrome.debugger.detach({tabId:e})}catch{}}return Ze.info(`pixel-contrast sampler: resolved ${r}, skipped ${c}/${n.size}`),{resolved:r,skipped:c}}const ct=J("ai-text");function lt(e){if(e!=null&&e.aborted){const t=new Error("aborted");throw t.name="AbortError",t}}function dt(e){return e instanceof Error&&(e.name==="AbortError"||e.name==="ExternalAbortError"||e.message==="aborted")}async function Cd(e,t,a,n={}){var m;if(!a.enabled||!a.enabledChecks.headings)return{findings:[],totalCostUsd:0,capExceeded:!1,errors:[]};const o=ce(a);if(!o.ok)return{findings:[],totalCostUsd:0,capExceeded:!1,errors:[`AI client unavailable: ${o.reason}`]};const i=o.client,r=Ce(a.costCapUsd),c=[],l=[],d=Math.max(1,a.maxCandidatesPerCheck??10),u=e.slice(0,d),h=u.length;for(let f=0;f<u.length;f++){const g=u[f];if(!r.canCharge())break;try{lt(n.signal);const p=await i.judgeHeading({headingText:g.text,sectionContent:g.sectionContent,signal:n.signal});r.recordCharge(p.costUsd),p.verdict==="fail"&&p.confidence>=.6&&c.push(await ut("ai-heading-not-descriptive","2.4.6","AA","moderate","AI judged this heading does not describe its section content.",g.selector,g.outerHTML,g.text,p,t))}catch(p){if(dt(p)){l.push(`canceled after ${f}/${h} candidates`);break}const b=p instanceof Error?p.message:String(p);l.push(`${g.selector}: ${b}`),ct.warn("heading judgment failed",p)}(m=n.onProgress)==null||m.call(n,f+1,h)}return{findings:c,totalCostUsd:r.state.spentUsd,capExceeded:r.state.exceeded,errors:l}}async function $d(e,t,a,n={}){var m;if(!a.enabled||!a.enabledChecks.sensory)return{findings:[],totalCostUsd:0,capExceeded:!1,errors:[]};const o=ce(a);if(!o.ok)return{findings:[],totalCostUsd:0,capExceeded:!1,errors:[`AI client unavailable: ${o.reason}`]};const i=o.client,r=Ce(a.costCapUsd),c=[],l=[],d=Math.max(1,a.maxCandidatesPerCheck??10),u=e.slice(0,d),h=u.length;for(let f=0;f<u.length;f++){const g=u[f];if(!r.canCharge())break;try{lt(n.signal);const p=await i.judgeSensoryLanguage({instructionText:g.text,signal:n.signal});r.recordCharge(p.costUsd),p.verdict==="fail"&&p.confidence>=.6&&c.push(await ut("ai-sensory-instruction","1.3.3","A","serious","AI flagged this instruction as relying on sensory characteristics (shape, color, location) without a programmatic identifier.",g.selector,g.outerHTML,g.text.slice(0,100),p,t))}catch(p){if(dt(p)){l.push(`canceled after ${f}/${h} candidates`);break}const b=p instanceof Error?p.message:String(p);l.push(`${g.selector}: ${b}`),ct.warn("sensory judgment failed",p)}(m=n.onProgress)==null||m.call(n,f+1,h)}return{findings:c,totalCostUsd:r.state.spentUsd,capExceeded:r.state.exceeded,errors:l}}async function Td(e,t,a,n={}){var m;if(!a.enabled||!a.enabledChecks.aria)return{findings:[],totalCostUsd:0,capExceeded:!1,errors:[]};const o=ce(a);if(!o.ok)return{findings:[],totalCostUsd:0,capExceeded:!1,errors:[`AI client unavailable: ${o.reason}`]};const i=o.client,r=Ce(a.costCapUsd),c=[],l=[],d=Math.max(1,a.maxCandidatesPerCheck??10),u=e.slice(0,d),h=u.length;for(let f=0;f<u.length;f++){const g=u[f];if(!r.canCharge())break;try{lt(n.signal);const p=await i.judgeAriaSemantics({elementHtml:g.outerHTML,computedRole:g.role,surroundingHtml:g.surroundingHtml,signal:n.signal});r.recordCharge(p.costUsd),p.verdict==="fail"&&p.confidence>=.65&&c.push(await ut("ai-aria-misuse","4.1.2","A","serious",`AI judged role="${g.role}" is being used inappropriately on this element.`,g.selector,g.outerHTML,g.role,p,t))}catch(p){if(dt(p)){l.push(`canceled after ${f}/${h} candidates`);break}const b=p instanceof Error?p.message:String(p);l.push(`${g.selector}: ${b}`),ct.warn("aria judgment failed",p)}(m=n.onProgress)==null||m.call(n,f+1,h)}return{findings:c,totalCostUsd:r.state.spentUsd,capExceeded:r.state.exceeded,errors:l}}async function Ed(e,t,a,n={}){var m;if(!a.enabled||!a.enabledChecks.genericLinkText)return{findings:[],totalCostUsd:0,capExceeded:!1,errors:[]};const o=ce(a);if(!o.ok)return{findings:[],totalCostUsd:0,capExceeded:!1,errors:[`AI client unavailable: ${o.reason}`]};const i=o.client,r=Ce(a.costCapUsd),c=[],l=[],d=Math.max(1,a.maxCandidatesPerCheck??10),u=e.slice(0,d),h=u.length;for(let f=0;f<u.length;f++){const g=u[f];if(!r.canCharge())break;try{lt(n.signal);const p=await i.judgeGenericLinkText({linkText:g.linkText,surroundingText:g.surroundingText,signal:n.signal});r.recordCharge(p.costUsd),p.verdict==="fail"&&p.confidence>=.6&&c.push(await ut("ai-generic-link-text","2.4.4","A","serious","AI judged this link's purpose is not clear from its text or surrounding context.",g.selector,g.outerHTML,g.linkText,p,t))}catch(p){if(dt(p)){l.push(`canceled after ${f}/${h} candidates`);break}const b=p instanceof Error?p.message:String(p);l.push(`${g.selector}: ${b}`),ct.warn("generic-link judgment failed",p)}(m=n.onProgress)==null||m.call(n,f+1,h)}return{findings:c,totalCostUsd:r.state.spentUsd,capExceeded:r.state.exceeded,errors:l}}async function Rd(e,t,a,n={}){var m;if(!a.enabled||!a.enabledChecks.wallOfText)return{findings:[],totalCostUsd:0,capExceeded:!1,errors:[]};const o=ce(a);if(!o.ok)return{findings:[],totalCostUsd:0,capExceeded:!1,errors:[`AI client unavailable: ${o.reason}`]};const i=o.client,r=Ce(a.costCapUsd),c=[],l=[],d=Math.max(1,a.maxCandidatesPerCheck??10),u=e.slice(0,d),h=u.length;for(let f=0;f<u.length;f++){const g=u[f];if(!r.canCharge())break;try{lt(n.signal);const p=await i.judgeWallOfText({blockText:g.blockText,structuralChildrenCount:g.structuralChildrenCount,signal:n.signal});r.recordCharge(p.costUsd),p.verdict==="fail"&&p.confidence>=.6&&c.push(await ut("ai-wall-of-text","3.1.5","AAA","moderate","AI flagged this block as a wall of text with insufficient structural breaks (subheadings, lists, paragraphs).",g.selector,g.outerHTML,g.blockText.slice(0,80),p,t))}catch(p){if(dt(p)){l.push(`canceled after ${f}/${h} candidates`);break}const b=p instanceof Error?p.message:String(p);l.push(`${g.selector}: ${b}`),ct.warn("wall-of-text judgment failed",p)}(m=n.onProgress)==null||m.call(n,f+1,h)}return{findings:c,totalCostUsd:r.state.spentUsd,capExceeded:r.state.exceeded,errors:l}}async function Od(e,t,a,n={}){var m;if(!a.enabled||!a.enabledChecks.languageMismatch)return{findings:[],totalCostUsd:0,capExceeded:!1,errors:[]};const o=ce(a);if(!o.ok)return{findings:[],totalCostUsd:0,capExceeded:!1,errors:[`AI client unavailable: ${o.reason}`]};const i=o.client,r=Ce(a.costCapUsd),c=[],l=[],d=Math.max(1,a.maxCandidatesPerCheck??10),u=e.slice(0,d),h=u.length;for(let f=0;f<u.length;f++){const g=u[f];if(!r.canCharge())break;try{lt(n.signal);const p=await i.judgeLanguageMismatch({declaredLang:g.declaredLang,bodyTextSample:g.bodyTextSample,signal:n.signal});r.recordCharge(p.costUsd),p.verdict==="fail"&&p.confidence>=.7&&c.push(await ut("ai-language-mismatch","3.1.1","A","serious",`AI judged the page's content does not match the declared lang="${g.declaredLang||"(none)"}".`,g.selector,g.outerHTML,g.bodyTextSample.slice(0,80),p,t))}catch(p){if(dt(p)){l.push(`canceled after ${f}/${h} candidates`);break}const b=p instanceof Error?p.message:String(p);l.push(`${g.selector}: ${b}`),ct.warn("language-mismatch judgment failed",p)}(m=n.onProgress)==null||m.call(n,f+1,h)}return{findings:c,totalCostUsd:r.state.spentUsd,capExceeded:r.state.exceeded,errors:l}}async function ut(e,t,a,n,o,i,r,c,l,d){const u={selector:i,outerHTML:r.slice(0,500),failureSummary:`AI assessment: ${l.reasoning}`,tagName:"AI",role:null,accessibleName:null,textSnippet:c.slice(0,100)||null,attrId:null,attrTestid:null},h=await tn({ruleId:e,componentId:d.componentId,currentState:d.currentState,target:u});return{ruleId:e,wcagCriterion:t,wcagLevel:a,impact:n,description:o,helpUrl:"https://www.w3.org/WAI/WCAG21/Understanding/",target:u,componentId:d.componentId,currentState:d.currentState,axeVersion:d.axeVersion,detectedAt:new Date().toISOString(),matchKey:h,ai:{reasoning:l.reasoning,confidence:l.confidence,model:l.model,costUsd:l.costUsd}}}const ki=J("ai-vision");function Ii(e){if(e!=null&&e.aborted){const t=new Error("aborted");throw t.name="AbortError",t}}function xi(e){return e instanceof Error&&(e.name==="AbortError"||e.name==="ExternalAbortError"||e.message==="aborted")}async function Md(e,t,a,n={}){var m;if(!a.enabled||!a.enabledChecks.imageOfText)return{findings:[],totalCostUsd:0,capExceeded:!1,errors:[]};const o=ce(a);if(!o.ok)return{findings:[],totalCostUsd:0,capExceeded:!1,errors:[`AI client unavailable: ${o.reason}`]};const i=o.client,r=Ce(a.costCapUsd),c=Math.max(1,a.imageOfTextMaxImages??5),l=[...e].sort((f,g)=>g.pixelArea-f.pixelArea).slice(0,c),d=l.length,u=[],h=[];for(let f=0;f<l.length;f++){const g=l[f];if(!r.canCharge())break;try{Ii(n.signal);const p=await i.judgeImageOfText({imageUrl:g.imageUrl,accessibleName:g.accessibleName,signal:n.signal});r.recordCharge(p.costUsd),p.verdict==="fail"&&p.confidence>=.65&&u.push(await Si("ai-image-of-text","1.4.5","AA","moderate","AI judged this image bakes substantial text into the raster — should be presented as actual HTML text instead.",g.selector,g.outerHTML,g.accessibleName??"",p,t))}catch(p){if(xi(p)){h.push(`canceled after ${f}/${d} candidates`);break}const b=p instanceof Error?p.message:String(p);h.push(`${g.selector}: ${b}`),ki.warn("image-of-text judgment failed",p)}(m=n.onProgress)==null||m.call(n,f+1,d)}return{findings:u,totalCostUsd:r.state.spentUsd,capExceeded:r.state.exceeded,errors:h}}async function Dd(e,t,a,n={}){var m;if(!a.enabled||!a.enabledChecks.colorOnlyMeaning)return{findings:[],totalCostUsd:0,capExceeded:!1,errors:[]};const o=ce(a);if(!o.ok)return{findings:[],totalCostUsd:0,capExceeded:!1,errors:[o.reason]};const i=o.client,r=Ce(a.costCapUsd),c=[],l=[],d=Math.max(1,a.maxCandidatesPerCheck??10),u=e.slice(0,d),h=u.length;for(let f=0;f<u.length;f++){const g=u[f];if(!r.canCharge())break;try{Ii(n.signal);const p=await i.judgeColorOnlyMeaning({elementHtml:g.elementHtml,contextDescription:g.contextDescription,signal:n.signal});r.recordCharge(p.costUsd),p.verdict==="fail"&&p.confidence>=.65&&c.push(await Si("ai-color-only-meaning","1.4.1","A","serious","AI judged this region conveys information only through color (no text label, icon glyph, or shape difference).",g.selector,g.outerHTML,g.contextDescription,p,t))}catch(p){if(xi(p)){l.push(`canceled after ${f}/${h} candidates`);break}const b=p instanceof Error?p.message:String(p);l.push(b),ki.warn("color-only judgment failed",p)}(m=n.onProgress)==null||m.call(n,f+1,h)}return{findings:c,totalCostUsd:r.state.spentUsd,capExceeded:r.state.exceeded,errors:l}}async function Si(e,t,a,n,o,i,r,c,l,d){const u={selector:i,outerHTML:r.slice(0,500),failureSummary:`AI assessment: ${l.reasoning}`,tagName:"AI",role:null,accessibleName:null,textSnippet:c.slice(0,100)||null,attrId:null,attrTestid:null},h=await tn({ruleId:e,componentId:d.componentId,currentState:d.currentState,target:u});return{ruleId:e,wcagCriterion:t,wcagLevel:a,impact:n,description:o,helpUrl:"https://www.w3.org/WAI/WCAG21/Understanding/",target:u,componentId:d.componentId,currentState:d.currentState,axeVersion:d.axeVersion,detectedAt:new Date().toISOString(),matchKey:h,ai:{reasoning:l.reasoning,confidence:l.confidence,model:l.model,costUsd:l.costUsd}}}const Qa=J("ensure-content-script"),Nd=1500,Ld=300;async function Ci(e,t){if(await ro(e,t))return{ok:!0};const a=chrome.runtime.getManifest(),n=[];for(const o of a.content_scripts??[])Array.isArray(o.js)&&n.push(...o.js);if(n.length===0)return Qa.error("manifest declares no content_scripts"),{ok:!1,reason:"inject-failed",detail:"no content_scripts declared in manifest"};try{const o=t!==void 0?{tabId:e,frameIds:[t]}:{tabId:e,allFrames:!0};await chrome.scripting.executeScript({target:o,files:n})}catch(o){return Pd(o)}return await Ud(Ld),await ro(e,t)?(Qa.info("content script injected on demand",{tabId:e,frameId:t}),{ok:!0}):{ok:!1,reason:"unresponsive-after-inject",detail:"content script injected but did not register handlers — likely Content Security Policy on this page is blocking it"}}async function ro(e,t){try{const a=t!==void 0?{frameId:t}:void 0,n=await Promise.race([chrome.tabs.sendMessage(e,{type:"PING_REQUEST"},a),new Promise((o,i)=>setTimeout(()=>i(new Error("ping timeout")),Nd))]);return(n==null?void 0:n.type)==="PING_RESPONSE"}catch{return!1}}function Pd(e){const t=e instanceof Error?e.message:String(e);return/cannot access contents of (?:the page|url)/i.test(t)||/chrome:\/\//i.test(t)||/chrome-extension:\/\//i.test(t)||/devtools:\/\//i.test(t)||/chrome\.google\.com\/webstore/i.test(t)||/the extensions gallery/i.test(t)?{ok:!1,reason:"restricted-url",detail:t}:/no tab with id/i.test(t)||/tab .* was closed/i.test(t)?{ok:!1,reason:"tab-gone",detail:t}:(Qa.warn("content-script injection failed",e),{ok:!1,reason:"inject-failed",detail:t})}function Ud(e){return new Promise(t=>setTimeout(t,e))}function Za(e){switch(e.reason){case"restricted-url":return"Chrome blocks extensions on this page (chrome:// pages, the Chrome Web Store, view-source: pages, and a few others). Open a regular https:// page and try again.";case"tab-gone":return"The tab was closed or navigated away while the audit was starting. Click into the tab you want audited and click Scan again.";case"unresponsive-after-inject":return"We loaded our auditor onto the page, but the page's Content Security Policy appears to be blocking it. Some sites (banks, gov, strict CSPs) prevent extensions from running. Try a different page on the same site, or open DevTools → Console to confirm a CSP error.";case"inject-failed":return`Could not load our auditor onto the page. Try a hard refresh (Ctrl+Shift+R) on the page and click Scan again. If it persists, the page may be using an unusual document type or origin restriction.${e.detail?` (Detail: ${e.detail})`:""}`;case"unknown":default:return"Could not establish connection with the page. Hard-refresh (Ctrl+Shift+R) the tab you want audited and try again."}}const Ea=J("forensic-anchor-client"),_d="https://api.wcagcheckr.com",Fd="wcagcheckr",Wd=15e3;function qd(e){if(typeof e!="object"||e===null)return!1;const t=e;return t.schemaVersion!==1&&t.schemaVersion!==2||!(typeof t.anchoredAt=="string"&&typeof t.tsaName=="string"&&typeof t.rfc3161TokenBase64=="string"&&t.rfc3161TokenBase64.length>0&&typeof t.serverSignatureBase64=="string"&&t.serverSignatureBase64.length>0&&typeof t.serverKeyFingerprint=="string"&&t.serverKeyFingerprint.length>0)?!1:t.schemaVersion===2?typeof t.prevAuditHash=="string"&&/^[0-9a-f]{64}$/.test(t.prevAuditHash):!0}async function $i(e,t){const a={auditHash:e.hash,pageUrl:e.pageUrl,capturedAt:e.capturedAt,...t?{licenseId:t}:{}},n=`${_d}/v1/products/${Fd}/forensic/anchor`;try{const o=await fetch(n,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(a),signal:AbortSignal.timeout(Wd)});if(!o.ok)return Ea.warn(`anchor HTTP ${o.status}; entry stays local-only`),null;const i=await o.json();return qd(i)?i:(Ea.warn("anchor returned malformed receipt; entry stays local-only",i),null)}catch(o){return Ea.warn("anchor request failed; entry stays local-only",o),null}}const so="4.11.4";async function Gd(e){try{return(await chrome.tabs.get(e)).url??""}catch{return""}}const _=J("flows");async function $t(){const e=await Go();if(!e)throw new Error("no audit target tab found");return e}async function In(){var a,n,o,i,r;const e=await We("stateMatrix",Na);return((a=e.pseudoStates)==null?void 0:a.length)>0&&((n=e.themes)==null?void 0:n.length)>0&&((o=e.directions)==null?void 0:o.length)>0&&((i=e.breakpoints)==null?void 0:i.length)>0&&((r=e.breakpointPresets)==null?void 0:r.length)>0?e:(_.warn("stored stateMatrix is invalid; using defaults"),Na)}async function Vd(e,t,a){const n=t.themes.includes("light"),o=t.themes.includes("dark");if(!n||!o)return t;let i=null;try{i=await ye(e,{type:"THEME_AWARENESS_REQUEST"},a)}catch{return t}return!i||i.hasUnreadableSheets?t:i.hasDarkModeCss&&!i.hasLightModeCss?(_.info("theme-axis prune: site is dark-only, skipping light pass"),{...t,themes:t.themes.filter(r=>r!=="light")}):i.hasLightModeCss&&!i.hasDarkModeCss?(_.info("theme-axis prune: site is light-only, skipping dark pass"),{...t,themes:t.themes.filter(r=>r!=="dark")}):t}const jd=200,Ra=3,co=8;async function zd(e,t){const a=[];let n=null,o=null;for(let r=0;r<co&&!t();r++){const c=await e();if(c.error&&(o=c.error),!c.result)break;if(n=c.result,a.push(c.violations),r===1&&a[0].length===0&&a[1].length===0)return{violations:[],lastResult:n,sampleCount:2,stabilized:!0,lastError:o};if(a.length>=Ra){const l=a.slice(-Ra);if(Hd(l))return{violations:l[l.length-1],lastResult:n,sampleCount:a.length,stabilized:!0,lastError:o}}r<co-1&&await new Promise(l=>setTimeout(l,jd))}const i=a.slice(-Ra);return{violations:Bd(i),lastResult:n,sampleCount:a.length,stabilized:!1,lastError:o}}function Hd(e){if(e.length<2)return!0;const t=lo(e[0]);for(let a=1;a<e.length;a++){const n=lo(e[a]);if(n.size!==t.size)return!1;for(const o of t)if(!n.has(o))return!1}return!0}function lo(e){return new Set(e.map(t=>`${t.ruleId}::${t.target.selector}::${t.matchKey}`))}function Bd(e){const t=new Map,a="Audit detected this finding intermittently across multiple samples — likely an animation or transitional state was in progress. Verify manually.";for(const n of e)for(const o of n){const i=`${o.ruleId}::${o.target.selector}::${o.matchKey}`;t.has(i)||t.set(i,{...o,needsReview:!0,flakyAcrossRuns:!0,target:{...o.target,failureSummary:o.target.failureSummary?`${o.target.failureSummary} ${a}`:a}})}return Array.from(t.values())}async function uo(e,t,a,n,o){await Promise.all([(async()=>{var i;try{const r=await ye(e,{type:"READING_ORDER_REQUEST",selector:t},a);n[0]&&(r.issues.length>0?(n[0].readingOrderIssues=r.issues,n[0].positionAnalysisCapturedAt=o):(i=n[0].axeRulesEvaluated)==null||i.passed.push({ruleId:"wcc-reading-order-clean",wcagCriterion:"1.3.2"}))}catch(r){_.debug("reading-order analysis skipped",r)}})(),(async()=>{var i;try{const r=await ye(e,{type:"TAB_ORDER_ANALYZE_REQUEST"},a);n[0]&&(r.issues.length>0?(n[0].tabOrderIssues=r.issues,n[0].positionAnalysisCapturedAt=o):(i=n[0].axeRulesEvaluated)==null||i.passed.push({ruleId:"wcc-tab-order-clean",wcagCriterion:"2.4.3"}))}catch(r){_.debug("tab-order analysis skipped",r)}})(),(async()=>{var i;try{const r=await ye(e,{type:"TYPOGRAPHY_ANALYZE_REQUEST",selector:t},a);r.issues.length>0&&n[0]?n[0].typographyIssues=r.issues:n[0]&&((i=n[0].axeRulesEvaluated)==null||i.passed.push({ruleId:"wcc-typography-clean",wcagCriterion:"1.4.12"}))}catch(r){_.debug("typography analysis skipped",r)}})(),(async()=>{try{const i=await ye(e,{type:"CUSTOM_PROPERTY_REQUEST"},a);i.undefined.length>0&&n[0]&&(n[0].undefinedCustomProperties=i.undefined)}catch(i){_.debug("custom-property analysis skipped",i)}})()])}async function rt(e,t,a,n,o={}){var P,I,$;const i=o.asSubroutine===!0;if(await je())return i||H({type:"AUDIT_FAILED_EVENT",error:{code:ta,message:tt,recoverable:!1}}),{ok:!1,results:[],error:tt};const r=o.tabIdOverride??await $t(),c=await Ci(r,a);if(!c.ok)return i||H({type:"AUDIT_FAILED_EVENT",error:{code:c.reason==="restricted-url"?"RESTRICTED_URL":"CONTENT_SCRIPT_UNREACHABLE",message:Za(c),recoverable:c.reason!=="restricted-url"}}),{ok:!1,results:[],error:Za(c)};try{await chrome.scripting.executeScript({target:{tabId:r,frameIds:a!==void 0?[a]:void 0},world:"MAIN",func:()=>{try{let C=document.getElementById("__wcagcheckr_audit_freeze");C||(C=document.createElement("style"),C.id="__wcagcheckr_audit_freeze",C.textContent="*, *::before, *::after { animation-duration: 0s !important; animation-delay: 0s !important; transition-duration: 0s !important; transition-delay: 0s !important; } html, body { scroll-behavior: auto !important; }",document.documentElement.appendChild(C)),window.scrollTo(0,0);const M=document.activeElement;M&&M instanceof HTMLElement&&M!==document.body&&M.blur()}catch{}}}),await chrome.scripting.executeScript({target:{tabId:r,frameIds:a!==void 0?[a]:void 0},world:"MAIN",func:async()=>{try{await document.fonts.ready}catch{}}})}catch(C){_.debug("audit-start state stabilization failed (non-fatal)",C)}const l=n??await In(),d=await Vd(r,l,a),u=tl(d),h=await nr(`${await Gd(r)}|${e}|${a??0}`),m=await or(d),f=await We("axeDisabledRules",[]),g=await ir(f);let p=null;try{p=(await ye(r,{type:"SCOPE_FINGERPRINT_REQUEST",selector:e},a)).fingerprint}catch(C){_.debug("scope fingerprint failed; skipping cache fast-path",C)}if(p){const C=await rr(h);if(C&&C.fingerprint===p&&C.axeVersion===so&&C.matrixConfigHash===m&&C.disabledRulesHash===g)return _.info(`audit-cache HIT for ${C.componentId} — skipping matrix iteration`),i||(H({type:"AUDIT_COMPLETE_EVENT",componentId:C.componentId,results:C.results,delta:C.delta}),H({type:"SCORECARD_UPDATED_EVENT"})),{ok:!0,results:C.results}}i||await ze({sessionId:Ct(),mode:"single-element",scope:e,startedAt:new Date().toISOString(),lastProgressAt:new Date().toISOString(),state:"running"}),await yt(r,{type:"LIVE_REGIONS_ARM_REQUEST"},a).catch(()=>{}),await yt(r,{type:"FOCUS_TRACE_ARM_REQUEST"},a).catch(()=>{}),await ye(r,{type:"WARMUP_REQUEST"},a).catch(()=>{});const b=[],w=[];let k="",y,s=!1,O=null,E=null,v=null,D=null,S=!1,R=null;try{for(let C=0;C<u.length;C++){if(t.isCanceled()){_.info("single audit canceled by user");break}const M=u[C];try{const F=await Ya(r,M,e,a);if(!F.success){y=(P=F.error)==null?void 0:P.message,_.warn(`state ${C+1}/${u.length} drive failed`,y);continue}M.breakpoint.id!==O&&(await ye(r,{type:"WARMUP_REQUEST"},a).catch(()=>{}),O=M.breakpoint.id);const q=C===0?void 0:[...ui],N=await zd(async()=>{var Q;const X=await ye(r,{type:"AUDIT_REQUEST",selector:e,currentState:M,rulesToRun:q},a);return X.success&&X.result?{result:X.result,violations:X.result.violations,error:null}:{result:null,violations:[],error:((Q=X.error)==null?void 0:Q.message)??null}},()=>t.isCanceled());N.lastError&&(y=N.lastError,_.warn(`state ${C+1}/${u.length} sampling: ${N.lastError}`));const j=N.lastResult;if(!N.stabilized&&N.sampleCount>0&&_.info(`state ${C+1}/${u.length} did not stabilize after ${N.sampleCount} samples — ${N.violations.length} finding(s) routed to needs-review as flickering`),j){s=!0;const X=N.violations;_.info(`[adaptive-sample] state ${C+1}/${u.length} ${M.pseudoState}·${M.theme}·${M.direction}·${M.breakpoint.id}: ${N.sampleCount} sample(s), ${N.stabilized?"stabilized":"flickering"}, ${X.length} finding(s)`);const Q=await vl(r,{quality:75})??void 0;let me,he;try{me=(await ye(r,{type:"LIVE_REGIONS_DRAIN_REQUEST"},a)).announcements}catch{}try{he=(await ye(r,{type:"FOCUS_TRACE_DRAIN_REQUEST"},a)).focusEvents}catch{}if(w.push({...j,violations:X,frameId:a,announcements:me,focusEvents:he,screenshotDataUrl:Q}),b.push(...X),k=j.componentId,C===0&&E===null)try{const ae=await We("aiConfig",{}),x=be(ae);if(x.enabled&&x.apiKey&&k){E=Jt(r,e,a,x,w,k,t).catch(z=>{_.warn("ai augmentation (parallel) failed",z)});const U=((I=w[0])==null?void 0:I.pageUrl)??(($=w[0])==null?void 0:$.scope)??"";v=sd({tabId:r,componentId:k,pageUrl:U,results:w,verifyFixesOnly:o.verifyFixesOnly}).catch(z=>(_.warn("1.4.11 walkthrough (parallel) failed",z),{criterionId:"1.4.11",ran:!0,ok:!1,error:String(z)}))}}catch(ae){_.debug("ai augmentation (parallel) gate-read failed",ae)}!S&&M.pseudoState==="default"&&M.theme==="light"&&M.direction==="ltr"&&M.breakpoint.id==="desktop"&&w[0]&&(S=!0,R=uo(r,e,a,w,M).catch(x=>{_.debug("lightweight deep analyzers (parallel) failed",x)}))}}catch(F){y=F instanceof Error?F.message:String(F),_.warn(`state ${C+1}/${u.length} threw`,F)}H({type:"AUDIT_PROGRESS_EVENT",current:C+1,total:u.length,currentState:M,componentId:k||void 0}),i||await la({})}if(s&&k){const C={pseudoState:"default",ariaVariation:null,theme:"light",direction:"ltr",breakpoint:{id:"desktop",label:"Desktop",width:1280,height:800,deviceScaleFactor:1,mobile:!1}};if(S){if(R)try{await R}catch{}}else{try{await Ya(r,C,e,a),await new Promise(N=>setTimeout(N,200))}catch(N){_.debug("reference-state drive failed; analyzers will use last matrix state",N)}await uo(r,e,a,w,C).catch(N=>{_.debug("lightweight deep analyzers (post-matrix fallback) failed",N)})}try{const N=await We("aiConfig",{}),j=be(N);if(j.enabled&&j.apiKey&&k&&w[0]){const X=w[0].pageUrl??w[0].scope??"";D=rd({tabId:r,componentId:k,pageUrl:X,results:w,verifyFixesOnly:o.verifyFixesOnly}).catch(Q=>(_.warn("1.3.2 walkthrough (parallel) failed",Q),{criterionId:"1.3.2",ran:!0,ok:!1,error:String(Q)}))}}catch(N){_.debug("1.3.2 walkthrough (parallel) gate-read failed",N)}try{if(E)await E;else{const N=await We("aiConfig",{}),j=be(N);j.enabled&&j.apiKey&&await Jt(r,e,a,j,w,k,t)}}catch(N){_.warn("ai augmentation skipped",N)}try{const N=await We("aiConfig",{}),j=be(N);if(j.enabled&&j.apiKey&&k&&w[0]){await Ft(r,e,a).catch(ae=>_.warn("pre-walkthrough resetState failed",ae));const X=w[0].pageUrl??w[0].scope??"",Q=new AbortController,me=setInterval(()=>{t.isCanceled()&&Q.abort()},250),he=await id({tabId:r,componentId:k,pageUrl:X,results:w,signal:Q.signal,verifyFixesOnly:o.verifyFixesOnly}).finally(()=>clearInterval(me));_.info("keyboard walkthroughs complete",he)}}catch(N){_.warn("keyboard walkthroughs failed",N)}try{const N=[];v&&N.push(await v),D&&N.push(await D),N.length>0&&_.info("parallel AI walkthroughs joined",N)}catch(N){_.warn("parallel AI walkthrough join failed",N)}try{if(k&&w[0]){const N=w[0].pageUrl??w[0].scope??"",j=new AbortController,X=setInterval(()=>{t.isCanceled()&&j.abort()},250),Q=await cd({tabId:r,componentId:k,pageUrl:N,results:w,signal:j.signal,verifyFixesOnly:o.verifyFixesOnly}).finally(()=>clearInterval(X));_.info("2.5.3 label-in-name walkthrough complete",Q)}}catch(N){_.warn("2.5.3 walkthrough failed",N)}try{if(k&&w[0]){await Ft(r,e,a).catch(me=>_.warn("pre-sampler resetState failed",me));const N=new AbortController,j=setInterval(()=>{t.isCanceled()&&N.abort()},250),{resolved:X,skipped:Q}=await Sd(r,w,N.signal).finally(()=>clearInterval(j));_.info(`pixel contrast sampler: resolved=${X} skipped=${Q}`)}}catch(N){_.warn("pixel contrast sampler failed",N)}const M=w.flatMap(N=>N.announcements??[]),F=w.flatMap(N=>N.focusEvents??[]),q=await wn(k,b,{announcements:M,focusEvents:F},l);try{const N=await un(w.map(ae=>ae.componentId)),j=await pn(w.map(ae=>ae.componentId)),X=new Set;for(const ae of new Set(w.map(x=>x.componentId))){const x=await na(ae);for(const U of x)X.add(U)}const Q=Se(b,void 0,w,X,N,j),me=w.reduce((ae,x)=>ae+x.durationMs,0),he=await oi(w,Q,me);if(he){const ae=await Me();if(So(ae,"forensicAnchoring"))try{const x=await et(),U=await $i(he,x??void 0);U&&await ii(he.componentId,he.capturedAt,U)}catch(x){_.warn("forensic anchoring failed (entry stays local-only)",x)}}}catch(N){_.warn("forensic log recording failed",N)}if(p)try{await sr({lookupKey:h,fingerprint:p,axeVersion:so,matrixConfigHash:m,disabledRulesHash:g,results:w,delta:q,componentId:k,cachedAt:Date.now()})}catch(N){_.warn("audit-cache write failed",N)}try{await chrome.scripting.executeScript({target:{tabId:r,frameIds:a!==void 0?[a]:void 0},world:"MAIN",func:()=>{const N=document.getElementById("__wcagcheckr_audit_freeze");N&&N.remove()}})}catch(N){_.debug("audit-end state-freeze cleanup failed",N)}i||H({type:"AUDIT_COMPLETE_EVENT",componentId:k,results:w,delta:q})}else i||H({type:"AUDIT_FAILED_EVENT",error:{code:"AXE_FAILED",message:y?`All audits failed. Last error: ${y}`:"No state produced results. Selector may not exist in the audit target frame.",recoverable:!0}});i||await nt()}catch(C){_.error("single audit failed",C),i||H({type:"AUDIT_FAILED_EVENT",componentId:k||void 0,error:Tn(C)?C.payload:{code:"UNKNOWN",message:String(C),recoverable:!1}}),y=Tn(C)?C.payload.message:String(C)}finally{await yt(r,{type:"LIVE_REGIONS_DISARM_REQUEST"},a).catch(()=>{}),await yt(r,{type:"FOCUS_TRACE_DISARM_REQUEST"},a).catch(()=>{}),await Ft(r,e,a)}return{ok:s,results:w,error:s?void 0:y}}async function Ti(e){var t;try{const n=(await chrome.scripting.executeScript({target:{tabId:e,allFrames:!0},world:"MAIN",func:()=>{var i;const o=window;return{detected:typeof o.__STORYBOOK_PREVIEW__=="object",version:(i=o.__STORYBOOK_PREVIEW__)==null?void 0:i.version}}})).find(o=>{var i;return(i=o.result)==null?void 0:i.detected});if(n)return{detected:!0,version:(t=n.result)==null?void 0:t.version,frameId:n.frameId}}catch(a){_.warn("storybook detection scripting failed",a)}return{detected:!1}}async function Kd(e){if(await je()){H({type:"AUDIT_FAILED_EVENT",error:{code:ta,message:tt,recoverable:!1}});return}const t=await $t(),a=await Oi(t);if(await chrome.storage.local.set({"storybook:lastDetected":{detected:a.kind==="storybook"&&a.detected,version:a.version,detectedAt:new Date().toISOString()},"playground:lastDetected":{kind:a.kind,detected:a.detected,version:a.version,detectedAt:new Date().toISOString()}}),!a.detected){H({type:"AUDIT_FAILED_EVENT",error:{code:"STORYBOOK_NOT_DETECTED",message:"No supported component playground detected on this tab. Open a Storybook (iframe.html / storybook-static), Ladle, or Bit page and try again.",recoverable:!0}});return}if(a.kind==="bit"){H({type:"AUDIT_FAILED_EVENT",error:{code:"STORYBOOK_NOT_DETECTED",message:"Bit detected. Auto-iterate is not yet supported for Bit (compositions are iframe-per-component without a unified listing API). Use the element picker to audit individual compositions one at a time.",recoverable:!0}});return}const n=a.frameId??0,o=a.kind==="storybook"?await Xd(t,n):await Zd(t,n);if(!o.length){_.warn(`${a.kind} detected but no stories returned`),H({type:"AUDIT_FAILED_EVENT",error:{code:"STORYBOOK_NOT_DETECTED",message:`${a.kind==="ladle"?"Ladle":"Storybook"} detected but no stories were returned. Try reloading the page.`,recoverable:!0}});return}await ze({sessionId:Ct(),mode:"storybook-all",totalStories:o.length,completedStories:[],startedAt:new Date().toISOString(),lastProgressAt:new Date().toISOString(),state:"running"});for(const i of o){if(e.isCanceled()){_.info(`${a.kind} iteration canceled by user`);break}try{if(a.kind==="storybook"){await Qd(t,n,i.id);const c=await yt(t,{type:"STORYBOOK_GET_STORY_TARGET_REQUEST",tabId:t},n);await rt(c.selector,e,n)}else await eu(t,n,i.id),await rt("#ladle-root",e,n)}catch(c){_.warn(`story ${i.id} failed; continuing with next`,c)}const r=await ca();if((r==null?void 0:r.state)==="interrupted"){_.info("storybook iteration interrupted");break}await la({completedStories:[...(r==null?void 0:r.completedStories)??[],i.id]})}H({type:"SCORECARD_UPDATED_EVENT"}),await nt()}async function Yd(e){if(await je()){H({type:"AUDIT_FAILED_EVENT",error:{code:ta,message:tt,recoverable:!1}});return}const t=await $t();let a=[];try{a=(await chrome.scripting.executeScript({target:{tabId:t,allFrames:!0},func:()=>({url:window.location.href})})).map(o=>{var i;return{frameId:o.frameId,url:((i=o.result)==null?void 0:i.url)??""}}).filter(o=>/^https?:/i.test(o.url))}catch(n){_.warn("multi-frame discovery failed",n)}if(a.length===0){H({type:"AUDIT_FAILED_EVENT",error:{code:"CONTENT_SCRIPT_UNREACHABLE",message:"No auditable frames found on this tab. The page may use only chrome-extension:// or data: frames.",recoverable:!0}});return}await ze({sessionId:Ct(),mode:"all-frames",totalFrames:a.length,completedFrames:[],startedAt:new Date().toISOString(),lastProgressAt:new Date().toISOString(),state:"running"});for(const n of a){if(e.isCanceled()){_.info("all-frames iteration canceled");break}try{await rt("html",e,n.frameId)}catch(i){_.warn(`frame ${n.frameId} (${n.url.slice(0,80)}) audit failed; continuing`,i)}const o=await ca();if((o==null?void 0:o.state)==="interrupted"){_.info("all-frames iteration interrupted");break}await la({completedFrames:[...(o==null?void 0:o.completedFrames)??[],n.frameId]})}H({type:"SCORECARD_UPDATED_EVENT"}),await nt()}async function Jd(e){var n;const t=await Go();let a;if(t!==null)try{a=(await chrome.tabs.get(t)).url}catch(o){_.warn("failed to get target tab url",o)}if(!a){H({type:"AUDIT_FAILED_EVENT",error:{code:"NETWORK",message:"Could not determine target URL for parallel scan.",recoverable:!1}});return}await ze({sessionId:Ct(),mode:"single-element",scope:"html",startedAt:new Date().toISOString(),lastProgressAt:new Date().toISOString(),state:"running"});try{const o=await In(),{runParallelTabAudit:i}=await an(async()=>{const{runParallelTabAudit:u}=await import("./parallel-tab-flow-Xk9RSjay.js");return{runParallelTabAudit:u}},__vite__mapDeps([0,1,2,3])),r=await i({targetUrl:a,scope:"html",matrix:o,parallelism:4,cancel:e});if(_.info(`parallel scan complete: ${r.results.length} results across ${r.tabsCompleted} tabs (${r.tabsFailed} failed) in ${Math.round(r.parallelDurationMs)}ms`),r.results.length===0){H({type:"AUDIT_FAILED_EVENT",error:{code:"NETWORK",message:`All ${r.tabsFailed} worker tabs failed. Falling back to Full Page scan is recommended.`,recoverable:!0}});return}const c=((n=r.results[0])==null?void 0:n.componentId)??"parallel-scan";if(t!==null)try{const u=await We("aiConfig",{}),h=be(u);h.enabled&&h.apiKey&&await Jt(t,"html",void 0,h,r.results,c,e)}catch(u){_.warn("parallel scan: ai augmentation skipped",u)}const l=r.results.flatMap(u=>u.violations);let d;try{d=await wn(c,l)}catch(u){_.warn("parallel scan: baseline compare failed",u),d={new:[],persistent:[],fixed:[],newCount:0,persistentCount:0,fixedCount:0,baselineSnapshotMeta:null,comparedAt:new Date().toISOString()}}H({type:"AUDIT_COMPLETE_EVENT",componentId:c,results:r.results,delta:d}),H({type:"SCORECARD_UPDATED_EVENT"})}catch(o){_.warn("parallel scan failed",o),H({type:"AUDIT_FAILED_EVENT",error:{code:"UNKNOWN",message:o instanceof Error?o.message:String(o),recoverable:!0}})}finally{await nt()}}async function Xd(e,t){var a;try{return((a=(await chrome.scripting.executeScript({target:{tabId:e,frameIds:[t]},world:"MAIN",func:async()=>{var l,d;const o=window.__STORYBOOK_PREVIEW__;if(!o)return[];const i=o.storyStoreValue??o.storyStore;if(!i)return[];typeof i.cacheAllCSFFiles=="function"&&await i.cacheAllCSFFiles();const r=((l=i.extract)==null?void 0:l.call(i))??((d=i.cachedCSFFiles)==null?void 0:d.call(i));if(!r)return[];const c=[];if(r instanceof Map)for(const[u,h]of r){const m=h;c.push({id:String(u),name:m.name??String(u),kind:m.title??m.kind??""})}else if(typeof r=="object"&&r!==null)for(const[u,h]of Object.entries(r)){const m=h;c.push({id:u,name:m.name??u,kind:m.title??m.kind??""})}return c}}))[0])==null?void 0:a.result)??[]}catch(n){return _.warn("listStoriesViaMainWorld failed",n),[]}}async function Qd(e,t,a){try{await chrome.scripting.executeScript({target:{tabId:e,frameIds:[t]},world:"MAIN",args:[a,3e3],func:async(n,o)=>{const i=new URL(window.location.href);i.searchParams.set("id",n),i.searchParams.set("viewMode","story"),window.location.href!==i.href&&(window.history.pushState({},"",i.href),window.dispatchEvent(new PopStateEvent("popstate"))),await new Promise(r=>{const c=setTimeout(()=>r(),o),l=()=>{clearTimeout(c),r()};window.addEventListener("storyrendered",l,{once:!0})})}})}catch(n){_.warn(`navigateToStoryViaMainWorld(${a}) failed`,n)}}async function Ei(e){var t;try{const n=(await chrome.scripting.executeScript({target:{tabId:e,allFrames:!0},world:"MAIN",func:()=>{const o=document.getElementById("ladle-root")!==null,i=document.title==="Ladle",r=!!document.querySelector('link[href*="ladle.css"]');return{detected:o&&(i||r),version:void 0}}})).find(o=>{var i;return(i=o.result)==null?void 0:i.detected});if(n)return{detected:!0,version:(t=n.result)==null?void 0:t.version,frameId:n.frameId}}catch(a){_.warn("ladle detection scripting failed",a)}return{detected:!1}}async function Zd(e,t){var a;try{return((a=(await chrome.scripting.executeScript({target:{tabId:e,frameIds:[t]},world:"MAIN",func:async()=>{try{const o=await fetch(new URL("/meta.json",window.location.href).href,{credentials:"same-origin"});if(!o.ok)return[];const r=(await o.json()).stories??{};return Object.entries(r).map(([c,l])=>({id:c,name:l.name??c,kind:(l.levels??[]).join(" / ")}))}catch{return[]}}}))[0])==null?void 0:a.result)??[]}catch(n){return _.warn("listLadleStoriesViaMainWorld failed",n),[]}}async function eu(e,t,a){try{await chrome.scripting.executeScript({target:{tabId:e,frameIds:[t]},world:"MAIN",args:[a,3e3],func:async(n,o)=>{const i=new URL(window.location.href);i.searchParams.set("story",n),window.location.href!==i.href&&(window.history.pushState({},"",i.href),window.dispatchEvent(new PopStateEvent("popstate"))),await new Promise(r=>setTimeout(r,Math.min(o,500)))}})}catch(n){_.warn(`navigateToLadleStoryViaMainWorld(${a}) failed`,n)}}async function Ri(e){try{return(await chrome.scripting.executeScript({target:{tabId:e,allFrames:!0},world:"MAIN",func:()=>{const a=window.location.href;return/bit\.dev|bit-dev|bitsrc|\/~aspect|\/composition/i.test(a)}})).some(a=>a.result===!0)}catch{return!1}}async function Oi(e){const t=await Ti(e);if(t.detected)return{kind:"storybook",...t};const a=await Ei(e);return a.detected?{kind:"ladle",...a}:await Ri(e)?{kind:"bit",detected:!0}:{kind:null,detected:!1}}const tu=200,au=6e4;function ho(e,t){try{return new URL(e).origin===new URL(t).origin}catch{return!1}}function nu(e,t){try{const a=new URL(e,t);return a.hash="",a.toString()}catch{return null}}async function ou(e,t,a){await chrome.tabs.update(e,{url:t}),await new Promise((n,o)=>{const i=setTimeout(()=>{chrome.tabs.onUpdated.removeListener(r),o(new Error(`navigation timeout (${a}ms): ${t}`))},a);function r(c,l){c===e&&l.status==="complete"&&(clearTimeout(i),chrome.tabs.onUpdated.removeListener(r),n())}chrome.tabs.onUpdated.addListener(r)}),await new Promise(n=>setTimeout(n,400))}async function iu(e){var t;try{return((t=(await chrome.scripting.executeScript({target:{tabId:e},func:()=>Array.from(document.querySelectorAll("a[href]")).map(n=>n.getAttribute("href")).filter(n=>typeof n=="string"&&!n.startsWith("javascript:"))}))[0])==null?void 0:t.result)??[]}catch(a){return _.debug("link discovery failed",a),[]}}async function ru(e,t){var a,n;try{const i=new URL(t).pathname.toLowerCase().replace(/\/+$/,"");if(/(?:^|\/)(?:404|not-found|page-not-found|error|server-error)$/.test(i))return`URL path "${i||"/"}" matches a common error-route pattern.`}catch{}try{const o=await chrome.scripting.executeScript({target:{tabId:e},func:()=>document.title||""}),i=((n=(a=o==null?void 0:o[0])==null?void 0:a.result)==null?void 0:n.trim())??"";if(!i)return null;const r=[[/^4(?:0[0-46-9]|[1-9]\d)\b/,"title starts with a 4xx error code"],[/^5\d{2}\b/,"title starts with a 5xx error code"],[/^(?:page\s+)?not\s+found\b/i,'title starts with "Not Found"'],[/^error\s*[-:]?\s*\d{3}\b/i,'title starts with "Error" + status code'],[/^(?:oops|whoops)[\s,!:.-]/i,'title leads with "Oops" — common error-template pattern']];for(const[c,l]of r)if(c.test(i))return`${l} (title: "${i.slice(0,80)}").`;return null}catch(o){return _.debug("error-page detection failed; proceeding with audit",o),null}}async function su(e,t,a){if(await je()){H({type:"SITE_CRAWL_FAILED_EVENT",error:{code:ta,message:tt,recoverable:!1}});return}const n=await $t(),o=Math.min(t.maxPages??25,tu),i=t.includeRegex?po(t.includeRegex):null,r=t.excludeRegex?po(t.excludeRegex):null,c=new Date().toISOString(),l=Date.now(),d=[e],u=new Set,h=[];await ze({sessionId:Ct(),mode:"storybook-all",scope:e,startedAt:c,lastProgressAt:new Date().toISOString(),state:"running"});try{for(;d.length>0&&u.size<o;){if(a.isCanceled()){_.info("site crawl canceled by user");break}const y=d.shift();if(!y||u.has(y)||!ho(y,e)||i&&!i.test(y)||r&&r.test(y))continue;u.add(y);const s=u.size,O=Date.now();H({type:"SITE_CRAWL_PROGRESS_EVENT",current:s,total:Math.min(o,u.size+d.length),url:y,status:"auditing"});try{await ou(n,y,au);const E=await Ci(n,0);if(!E.ok)throw new Error(Za(E));const v=await ru(n,y);if(v){const $=`Skipped — page appears to be an error response. ${v}`;h.push({url:y,results:[],delta:null,componentId:y,durationMs:Date.now()-O,error:$}),H({type:"SITE_CRAWL_PROGRESS_EVENT",current:s,total:Math.min(o,u.size+d.length),url:y,status:"failed",error:$});continue}await ye(n,{type:"WARMUP_REQUEST"},0).catch(()=>{});const D=await rt("html",a,0,void 0,{asSubroutine:!0,tabIdOverride:n,verifyFixesOnly:t.verifyFixesOnly}),S=D.results,R=D.ok?void 0:D.error,P=Date.now()-O;if(S.length>0){const $=await mc(n,y).catch(()=>null),C=S.reduce((M,F)=>M+F.violations.length,0);h.push({url:y,results:S,delta:null,componentId:S[0].componentId,durationMs:P,snapshot:$??void 0}),H({type:"SITE_CRAWL_PROGRESS_EVENT",current:s,total:Math.min(o,u.size+d.length),url:y,status:"completed",violations:C})}else{const $=R??"audit returned no result";h.push({url:y,results:[],delta:null,componentId:y,durationMs:P,error:$}),H({type:"SITE_CRAWL_PROGRESS_EVENT",current:s,total:Math.min(o,u.size+d.length),url:y,status:"failed",error:$})}const I=await iu(n);for(const $ of I){const C=nu($,y);C&&ho(C,e)&&!u.has(C)&&!d.includes(C)&&d.push(C)}}catch(E){const v=Date.now()-O,D=E instanceof Error?E.message:String(E);h.push({url:y,results:[],delta:null,componentId:y,durationMs:v,error:D}),H({type:"SITE_CRAWL_PROGRESS_EVENT",current:s,total:Math.min(o,u.size+d.length),url:y,status:"failed",error:D})}await la({})}const m=new Date().toISOString(),f=Date.now()-l,g=new Map,p=new Map,b=new Map,w=new Map;for(const y of h){if(!y.componentId)continue;const s=await un([y.componentId]);g.set(y.componentId,s);const O=await pn([y.componentId]);p.set(y.componentId,O);const E=await na(y.componentId);b.set(y.componentId,E);const v=await si([y.componentId]);w.set(y.componentId,v)}const k=gc(e,h,c,m,f,g,p,b,w);if(k.consistencyFindings.length>0)try{const y=await el(k.consistencyFindings);k.consistencyFindings=y.emitted}catch(y){_.warn("consistency AI suppression failed; emitting heuristic findings",y)}try{await _o(h,{startUrl:e,finishedAt:m})}catch(y){const s=y instanceof Error?y.message:String(y);_.error("persisting siteCrawlPerUrlResults failed",y),H({type:"SITE_CRAWL_FAILED_EVENT",error:{code:"CRAWL_PERSIST_FAILED",message:`Crawl finished but persistence failed (${s}). The AI fix prompt export won't have per-page data.`,recoverable:!1}})}H({type:"SITE_CRAWL_COMPLETE_EVENT",report:k}),await nt()}catch(m){_.error("site crawl failed",m),H({type:"SITE_CRAWL_FAILED_EVENT",error:{code:"UNKNOWN",message:String(m),recoverable:!1}}),await nt()}}function po(e){try{return new RegExp(e)}catch{return null}}const Oa=5*6e4,fo=9e4,go={altText:"Verifying alt text…",headings:"Judging heading quality…",sensory:"Scanning for sensory-only instructions…",aria:"Reviewing ARIA semantics…",imageOfText:"Detecting baked-in text in images…",colorOnlyMeaning:"Checking for color-only meaning…",genericLinkText:"Reviewing link clarity…",wallOfText:"Detecting walls of text…",languageMismatch:"Verifying page language…"},cu={altText:["1.1.1"],headings:["2.4.6"],sensory:["1.3.3"],aria:["4.1.2"],imageOfText:["1.4.5"],colorOnlyMeaning:["1.4.1"],genericLinkText:["2.4.4"],wallOfText:[],languageMismatch:["3.1.1","3.1.2"]};async function Jt(e,t,a,n,o,i,r){let c;try{c=await ye(e,{type:"AI_CANDIDATES_REQUEST",selector:t},a)}catch(I){_.debug("ai candidates fetch failed",I);return}const l=o[0];if(!l)return;const d={componentId:i,currentState:l.state,axeVersion:l.axeVersion},u={at:new Date().toISOString(),componentId:i,pageUrl:l.pageUrl??t,enabledChecks:n.enabledChecks,candidateCounts:{images:c.images.length,headings:c.headings.length,instructions:c.instructions.length,ariaElements:c.ariaElements.length,links:c.links.length,longTextBlocks:c.longTextBlocks.length,colorOnlyRegions:c.colorOnlyRegions.length,languageInfo:c.languageInfo?"present":"null"}};_.info("ai augmentation gate snapshot",JSON.stringify(u));try{await chrome.storage.local.set({aiDiagnosticLatest:u})}catch(I){_.warn("failed to persist ai diagnostic to storage.local",I)}const h=[];if(n.enabledChecks.altText&&h.push({key:"altText",run:I=>{const $=c.images.map(C=>({imageUrl:C.imageUrl,alt:C.alt,selector:C.selector,outerHTML:C.outerHTML,surroundingContext:C.surroundingContext,...d}));return Dl($,n,I)}}),n.enabledChecks.headings&&h.push({key:"headings",run:I=>Cd(c.headings,d,n,I)}),n.enabledChecks.sensory&&h.push({key:"sensory",run:I=>$d(c.instructions,d,n,I)}),n.enabledChecks.aria&&h.push({key:"aria",run:I=>Td(c.ariaElements,d,n,I)}),n.enabledChecks.imageOfText&&h.push({key:"imageOfText",run:I=>{const $=c.images.map(C=>({imageUrl:C.imageUrl,accessibleName:C.alt||void 0,selector:C.selector,outerHTML:C.outerHTML,pixelArea:C.pixelArea}));return Md($,d,n,I)}}),n.enabledChecks.colorOnlyMeaning&&h.push({key:"colorOnlyMeaning",run:I=>Dd(c.colorOnlyRegions,d,n,I)}),n.enabledChecks.genericLinkText&&h.push({key:"genericLinkText",run:I=>Ed(c.links,d,n,I)}),n.enabledChecks.wallOfText&&h.push({key:"wallOfText",run:I=>Rd(c.longTextBlocks,d,n,I)}),n.enabledChecks.languageMismatch){const I=c.languageInfo;h.push({key:"languageMismatch",run:$=>Od(I?[I]:[],d,n,$)})}if(h.length===0)return;let m=0,f=0,g=!1;const p={};let b=0,w=0,k=0;const y=[];let s=!1,O=!1;function E(I,$){if(p[I]=(p[I]??0)+$.totalCostUsd,m+=$.totalCostUsd,f+=$.findings.length,g=g||$.capExceeded,b++,$.errors.length>0){k++;for(const C of $.errors)y.includes(C)||y.push(C)}else w++;_.info(`ai ${I}: ${$.findings.length} findings, $${$.totalCostUsd.toFixed(4)}, errs=${$.errors.length}`),$.capExceeded&&_.warn(`ai cost cap exceeded after $${$.totalCostUsd.toFixed(4)} during ${I}`)}const v=(async()=>{for(let I=0;I<h.length;I++){if(r!=null&&r.isCanceled()){O=!0;break}const $=h[I];H({type:"AI_AUGMENTATION_PROGRESS_EVENT",componentId:i,currentCheck:$.key,currentCheckLabel:go[$.key],current:I+1,total:h.length});const C=new AbortController,M=setTimeout(()=>C.abort(),fo),F=r?setInterval(()=>{r.isCanceled()&&C.abort()},500):null;let q;try{q=await $.run({signal:C.signal,onProgress:(N,j)=>{j>0&&H({type:"AI_AUGMENTATION_PROGRESS_EVENT",componentId:i,currentCheck:$.key,currentCheckLabel:go[$.key],current:I+1,total:h.length,candidatesDone:N,candidatesTotal:j})}}),C.signal.aborted&&(r!=null&&r.isCanceled())?q.errors=[...q.errors,`${$.key} canceled by user`]:C.signal.aborted}catch(N){const j=N instanceof Error?N.message:String(N),X=C.signal.aborted,Q=X&&(r!=null&&r.isCanceled())?`${$.key} canceled by user`:X?`${$.key} hit per-check ${fo/1e3}s cap — analyzer aborted`:`${$.key} threw: ${j}`;q={findings:[],totalCostUsd:0,capExceeded:!1,errors:[Q]}}finally{clearTimeout(M),F&&clearInterval(F)}if(q.findings.length>0&&(l.violations=[...l.violations,...q.findings]),q.errors.length===0){const N=cu[$.key];if(N&&N.length>0&&l.axeRulesEvaluated)for(const j of N)l.axeRulesEvaluated.passed.push({ruleId:`ai-${$.key}-coverage`,wcagCriterion:j})}E($.key,q)}})(),D=new Promise(I=>{setTimeout(()=>I("timeout"),Oa)});await Promise.race([v.then(()=>"done"),D])==="timeout"&&(s=!0,_.warn(`ai augmentation hit global ${Oa/1e3}s timeout after ${b}/${h.length} checks`));let R;if(k>0||s||O){s?R=`AI augmentation timed out after ${Oa/1e3}s`:O?R="AI augmentation canceled":R=y[0]??"AI analyzer error";const I=w===0?"total":"partial";H({type:"AI_AUGMENTATION_FAILED_EVENT",componentId:i,severity:I,reason:R,checksAttempted:b,checksSucceeded:w,checksErrored:k,errorDetails:s||O?[R,...y.slice(0,4)]:y.slice(0,5)}),_.warn(`ai augmentation ${I} failure: ${k}/${b} checks errored — ${R}`)}(m>0||f>0||R)&&await cr({at:new Date().toISOString(),pageUrl:l.pageUrl??t,componentId:i,totalCostUsd:m,byCheck:p,findingsCount:f,capExceeded:g,failureReason:R})}const rp=Object.freeze(Object.defineProperty({__proto__:null,detectBit:Ri,detectLadleAcrossFrames:Ei,detectPlayground:Oi,detectStorybookAcrossFrames:Ti,getActiveTabId:$t,getMatrixSettings:In,runAiAugmentation:Jt,runAllFramesAudit:Yd,runParallelScan:Jd,runSingleElementAudit:rt,runSiteCrawl:su,runStorybookAllAudit:Kd},Symbol.toStringTag,{value:"Module"})),bt=J("alert-sender"),lu="wcagcheckr",du="https://api.wcagcheckr.com",uu=`${du}/v1/products/${lu}/alerts/send`,hu=1e4,en="alertConfig:v1",pu={emailEnabled:!1,webhookEnabled:!1,webhookUrl:"",minImpact:"serious"},Lt={minor:1,moderate:2,serious:3,critical:4};async function fu(){const t=(await chrome.storage.local.get(en))[en];return{...pu,...t??{}}}async function sp(e){await chrome.storage.local.set({[en]:e})}async function gu(e){let t;try{t=await fu()}catch(d){return bt.warn("failed to read alert config",d),{fired:!1,reason:"config_read_failed"}}if(!t.emailEnabled&&!t.webhookEnabled)return{fired:!1,reason:"no_channel_enabled"};const a=[];for(const d of e.results){const u=d.delta;u!=null&&u.new&&a.push(...u.new)}const n=Lt[t.minImpact],o=a.filter(d=>Lt[d.impact]>=n);if(o.length===0)return{fired:!1,reason:"below_impact_threshold"};const i=await et();if(!i)return bt.debug("no license token; alert skipped"),{fired:!1,reason:"no_license_token"};o.sort((d,u)=>Lt[u.impact]-Lt[d.impact]);const r=o.slice(0,10).map(d=>({wcag:d.wcagCriterion,impact:d.impact,selector:d.target.selector,help:d.description.slice(0,480),helpUrl:d.helpUrl})),c=e.results.reduce((d,u)=>d+u.violations.length,0),l={token:i,scheduleId:e.scheduleId,auditedUrl:e.auditedUrl,totalViolations:c,newViolationCount:o.length,topViolations:r,webhookUrl:t.webhookEnabled&&t.webhookUrl?t.webhookUrl:void 0,emailEnabled:t.emailEnabled,ranAt:e.ranAt};try{const d=await fetch(uu,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(l),signal:AbortSignal.timeout(hu)}),u=await d.json().catch(()=>null);return d.ok?(bt.info("alert delivered",{emailSent:u==null?void 0:u.emailSent,webhookSent:u==null?void 0:u.webhookSent}),{fired:!0,emailSent:(u==null?void 0:u.emailSent)??!1,webhookSent:(u==null?void 0:u.webhookSent)??!1,serverStatus:d.status}):(bt.warn(`alert server returned ${d.status}`,u),{fired:!1,reason:`server_${d.status}`,serverStatus:d.status})}catch(d){return bt.warn("alert send failed",d),{fired:!1,reason:d instanceof Error?d.message:"unknown_error"}}}const mu="#dc2626",bu="#f59e0b",yu="#059669",wu="#94a3b8";function vu(e){const t=e.filter(c=>c.enabled);let a=0,n=0,o=0,i=0;for(const c of t){const l=c.lastNewViolationCount??0;c.lastResult==="failed"?n++:c.lastResult==="ok"?l>0?a+=l:o++:i++}let r;return t.length===0?r="no-schedules":a>0?r="new-violations":n>0?r="failed":o>0?r="all-clear":r="pending-first-run",{state:r,newViolationsTotal:a,failingCount:n,okCount:o,pendingCount:i,totalEnabled:t.length}}async function Au(e){switch(e.state){case"new-violations":{const t=e.newViolationsTotal>99?"99+":String(e.newViolationsTotal);await chrome.action.setBadgeText({text:t}),await chrome.action.setBadgeBackgroundColor({color:mu}),await chrome.action.setTitle({title:`wcagcheckr — ${e.newViolationsTotal} new WCAG violation${e.newViolationsTotal===1?"":"s"} vs baseline across ${e.totalEnabled} scheduled audit${e.totalEnabled===1?"":"s"}`});break}case"failed":{await chrome.action.setBadgeText({text:"!"}),await chrome.action.setBadgeBackgroundColor({color:bu}),await chrome.action.setTitle({title:`wcagcheckr — ${e.failingCount} scheduled audit${e.failingCount===1?"":"s"} failed to run`});break}case"all-clear":{await chrome.action.setBadgeText({text:"✓"}),await chrome.action.setBadgeBackgroundColor({color:yu}),await chrome.action.setTitle({title:`wcagcheckr — all ${e.okCount} scheduled audit${e.okCount===1?"":"s"} passing`});break}case"pending-first-run":{await chrome.action.setBadgeText({text:""}),await chrome.action.setBadgeBackgroundColor({color:wu}),await chrome.action.setTitle({title:`wcagcheckr — ${e.pendingCount} scheduled audit${e.pendingCount===1?"":"s"} awaiting first run`});break}case"no-schedules":default:{await chrome.action.setBadgeText({text:""}),await chrome.action.setTitle({title:"wcagcheckr"});break}}}async function ku(){const e=await Do(),t=vu(e);try{await Au(t)}catch(a){console.warn("[status-badge] failed to apply",a)}return t}const mo=J("deposition-packet"),Mi="https://api.wcagcheckr.com/verify";function ee(e){return e.replace(/[&<>"']/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[t])}function Iu(e,t,a){if(!e)return`<section class="dp-cover dp-cover-error">
1111
+ <h2 style="margin:0 0 8pt 0; color:#b91c1c">⚠ Chain-of-Custody: UNAVAILABLE</h2>
1112
+ <p>This packet could not be sealed because the underlying audit record was empty. Provenance cannot be verified.</p>
1113
+ </section>`;const n=`${Mi}?hash=${encodeURIComponent(e.hash)}`;return t?`<section class="dp-cover dp-cover-sealed" style="border:2pt solid #059669; background:#ecfdf5; padding:14pt; border-radius:4pt; margin:0 0 14pt 0">
1114
+ <h2 style="margin:0 0 8pt 0; color:#064e3b">🔒 Chain-of-Custody: SEALED (RFC 3161 + ed25519)</h2>
1115
+ <p style="margin:0 0 10pt 0">This audit's content hash was independently witnessed by a public RFC 3161 timestamp authority and counter-signed by the wcagcheckr server. The seal can be verified offline using only published keys.</p>
1116
+ <dl style="margin:0; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size:9pt">
1117
+ <dt style="font-weight:600">Audit hash (SHA-256)</dt>
1118
+ <dd style="margin:0 0 6pt 16pt; word-break:break-all">${ee(e.hash)}</dd>
1119
+ <dt style="font-weight:600">Captured at (extension)</dt>
1120
+ <dd style="margin:0 0 6pt 16pt">${ee(e.capturedAt)}</dd>
1121
+ <dt style="font-weight:600">Anchored at (server)</dt>
1122
+ <dd style="margin:0 0 6pt 16pt">${ee(t.anchoredAt)}</dd>
1123
+ <dt style="font-weight:600">RFC 3161 timestamp authority</dt>
1124
+ <dd style="margin:0 0 6pt 16pt">${ee(t.tsaName)}</dd>
1125
+ <dt style="font-weight:600">Server key fingerprint</dt>
1126
+ <dd style="margin:0 0 6pt 16pt; word-break:break-all">${ee(t.serverKeyFingerprint)}</dd>
1127
+ <dt style="font-weight:600">RFC 3161 token (base64)</dt>
1128
+ <dd style="margin:0 0 6pt 16pt; word-break:break-all; max-height:60pt; overflow:auto; background:#f1f5f9; padding:4pt; border-radius:2pt">${ee(t.rfc3161TokenBase64)}</dd>
1129
+ <dt style="font-weight:600">Server signature (base64, ed25519)</dt>
1130
+ <dd style="margin:0 0 6pt 16pt; word-break:break-all; max-height:60pt; overflow:auto; background:#f1f5f9; padding:4pt; border-radius:2pt">${ee(t.serverSignatureBase64)}</dd>
1131
+ </dl>
1132
+ <p style="margin:10pt 0 0 0">
1133
+ <strong>Verify online:</strong>
1134
+ <a href="${ee(n)}" style="color:#1e40af">${ee(n)}</a>
1135
+ </p>
1136
+ </section>`:`<section class="dp-cover dp-cover-warning" style="border:2pt solid #f59e0b; background:#fef3c7; padding:14pt; border-radius:4pt; margin:0 0 14pt 0">
1137
+ <h2 style="margin:0 0 8pt 0; color:#92400e">⚠ Chain-of-Custody: LOCAL-ONLY (Not Third-Party Anchored)</h2>
1138
+ <p style="margin:0 0 8pt 0">This audit has a SHA-256 integrity hash but was <strong>not anchored</strong> to the wcagcheckr trusted-timestamp service when this packet was generated${a?` (${ee(a)})`:""}. The seal is self-attested rather than independently witnessed by an RFC 3161 timestamp authority.</p>
1139
+ <dl style="margin:8pt 0 0 0; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size:9pt">
1140
+ <dt style="font-weight:600">Audit hash (SHA-256)</dt>
1141
+ <dd style="margin:0 0 6pt 16pt; word-break:break-all">${ee(e.hash)}</dd>
1142
+ <dt style="font-weight:600">Captured at</dt>
1143
+ <dd style="margin:0 0 6pt 16pt">${ee(e.capturedAt)}</dd>
1144
+ <dt style="font-weight:600">Page URL</dt>
1145
+ <dd style="margin:0 0 6pt 16pt">${ee(e.pageUrl)}</dd>
1146
+ <dt style="font-weight:600">Grade</dt>
1147
+ <dd style="margin:0 0 6pt 16pt">${ee(e.grade)}</dd>
1148
+ </dl>
1149
+ <p style="margin:8pt 0 0 0; font-size:10pt"><strong>To convert to anchored status:</strong> re-run this export with an online connection to api.wcagcheckr.com.</p>
1150
+ </section>`}function xu(e,t){var n;if(!e)return"";const a=`${Mi}?hash=${encodeURIComponent(e.hash)}`;return`<section class="dp-verification" style="border:1pt solid #cbd5e1; padding:14pt; margin:24pt 0; border-radius:4pt; background:#f8fafc">
1151
+ <h2 style="margin:0 0 8pt 0">How to verify this audit's integrity</h2>
1152
+ <p style="margin:0 0 8pt 0">This packet is the output of an automated WCAG accessibility audit conducted by the wcagcheckr Chrome extension. The audit's content has been cryptographically sealed so any modification to this document — including changes to the violation list, the screenshots, or the grade — would invalidate the seal.</p>
1153
+
1154
+ <h3 style="margin:14pt 0 6pt 0">Quick verification (recommended)</h3>
1155
+ <ol style="margin:0 0 8pt 20pt; padding:0">
1156
+ <li style="margin-bottom:4pt">Visit <a href="${ee(a)}">${ee(a)}</a></li>
1157
+ <li style="margin-bottom:4pt">The page will look up the audit hash <code>${ee(e.hash.slice(0,16))}…</code> against the wcagcheckr trusted-anchor log.</li>
1158
+ <li style="margin-bottom:4pt">If the page reports "VERIFIED", the seal is intact: the audit's content matches what was witnessed by the timestamp authority at <strong>${ee((t==null?void 0:t.anchoredAt)??"(not anchored)")}</strong>.</li>
1159
+ <li style="margin-bottom:4pt">If the page reports "NOT FOUND" or "INVALID", the document has been modified or the audit was never anchored.</li>
1160
+ </ol>
1161
+
1162
+ <h3 style="margin:14pt 0 6pt 0">Offline verification</h3>
1163
+ <p style="margin:0 0 6pt 0">The seal can be verified without contacting wcagcheckr's servers using only the published RFC 3161 timestamp-authority certificate and the wcagcheckr public ed25519 key. The procedure:</p>
1164
+ <ol style="margin:0 0 8pt 20pt; padding:0">
1165
+ <li style="margin-bottom:4pt">Compute SHA-256 over the canonical-JSON of the audit identity block (hash inputs are documented at <a href="https://api.wcagcheckr.com/verify-format">api.wcagcheckr.com/verify-format</a>).</li>
1166
+ <li style="margin-bottom:4pt">Verify the RFC 3161 token (above) against the published TSA certificate from <strong>${ee((t==null?void 0:t.tsaName)??"(not anchored)")}</strong>.</li>
1167
+ <li style="margin-bottom:4pt">Verify the server ed25519 signature (above) using the wcagcheckr public key with fingerprint <code>${ee(((n=t==null?void 0:t.serverKeyFingerprint)==null?void 0:n.slice(0,16))??"(not anchored)")}…</code> (full key downloadable at <a href="https://api.wcagcheckr.com/v1/products/wcagcheckr/forensic/public-key">api.wcagcheckr.com/v1/products/wcagcheckr/forensic/public-key</a>).</li>
1168
+ </ol>
1169
+
1170
+ <h3 style="margin:14pt 0 6pt 0">What the seal proves</h3>
1171
+ <ul style="margin:0 0 8pt 20pt; padding:0">
1172
+ <li style="margin-bottom:4pt"><strong>Existence at a point in time:</strong> the RFC 3161 timestamp witnesses that this content existed at the anchored timestamp. (An independent third party — the TSA — bound the hash to that moment.)</li>
1173
+ <li style="margin-bottom:4pt"><strong>Integrity:</strong> the SHA-256 hash means any subsequent edit to the audit content (violations, screenshots, grade, etc.) produces a different hash, breaking the seal.</li>
1174
+ <li style="margin-bottom:4pt"><strong>Provenance:</strong> the ed25519 signature is over (audit hash + anchored-at + TSA name + product slug + previous-anchor hash), proving wcagcheckr's server is the source.</li>
1175
+ </ul>
1176
+
1177
+ <h3 style="margin:14pt 0 6pt 0">What the seal does NOT prove</h3>
1178
+ <ul style="margin:0 0 8pt 20pt; padding:0">
1179
+ <li style="margin-bottom:4pt">It does not prove the audit conclusions are correct — only that the audit's content is unchanged since sealing.</li>
1180
+ <li style="margin-bottom:4pt">It does not authenticate the auditor (a human operator may have run the extension; the seal only attests to the audit content).</li>
1181
+ <li style="margin-bottom:4pt">It does not warrant against future protocol breaks (cryptography evolves; an seal valid today may need re-anchoring decades later).</li>
1182
+ </ul>
1183
+ </section>`}function Su(e){return e?`<section class="dp-declaration" style="border:1pt solid #cbd5e1; padding:14pt; margin:24pt 0; border-radius:4pt; page-break-before:always">
1184
+ <h2 style="margin:0 0 8pt 0">Declaration under penalty of perjury (template)</h2>
1185
+ <p style="margin:0 0 8pt 0; font-size:10pt; color:#475569">The template below is provided for the convenience of the auditor and should be reviewed by competent counsel before execution. wcagcheckr makes no representation that this template is sufficient for any particular jurisdiction's requirements.</p>
1186
+ <div style="border:1pt dashed #94a3b8; padding:12pt; background:#ffffff">
1187
+ <p style="margin:0 0 10pt 0">I, <strong>[Name of declarant]</strong>, of <strong>[City, State / Province]</strong>, hereby declare under penalty of perjury under the laws of <strong>[jurisdiction]</strong> that the following is true and correct:</p>
1188
+ <ol style="margin:0 0 10pt 20pt">
1189
+ <li style="margin-bottom:6pt">I conducted an automated accessibility audit of <strong>${ee(e.pageUrl)}</strong> on <strong>${ee(e.capturedAt)}</strong> using the wcagcheckr Chrome extension, version <strong>[extension version]</strong>, running axe-core <strong>${ee(e.axeVersion)}</strong>.</li>
1190
+ <li style="margin-bottom:6pt">The audit produced a SHA-256 content hash of <code style="word-break:break-all">${ee(e.hash)}</code>, which was sealed via RFC 3161 trusted timestamp and the wcagcheckr server's ed25519 signature.</li>
1191
+ <li style="margin-bottom:6pt">The contents of this packet (the chain-of-custody cover, the findings, the per-state screenshots, and the verification instructions) are an accurate and complete representation of the sealed audit. No findings were added, removed, or altered after sealing.</li>
1192
+ <li style="margin-bottom:6pt">[Optional: I am personally familiar with the page audited, and to the best of my knowledge it was in its [production / staging / pre-deployment] state on the audit date.]</li>
1193
+ </ol>
1194
+ <p style="margin:14pt 0 6pt 0">Executed on <strong>[Date]</strong> at <strong>[Location]</strong>.</p>
1195
+ <p style="margin:24pt 0 6pt 0; border-top:1pt solid #0f172a; padding-top:6pt; max-width:300pt"><strong>[Signature]</strong></p>
1196
+ <p style="margin:0; font-size:10pt"><strong>[Printed name]</strong></p>
1197
+ </div>
1198
+ </section>`:""}function Cu(e){const t=e.map(a=>({...a,screenshotDataUrl:void 0,states:void 0}));return`<section class="dp-raw-json" style="display:none" aria-hidden="true">
1199
+ <h2>Raw axe-core JSON (machine-readable)</h2>
1200
+ <p>The block below is the unfiltered axe-core output included for machine re-verification. View HTML source to extract.</p>
1201
+ <pre id="wcagcheckr-axe-raw-json" data-product="wcagcheckr" data-format="deposition-packet-v1">${ee(JSON.stringify(t,null,2))}</pre>
1202
+ </section>`}async function $u(e,t,a){if(e.length===0)return null;const n=e[0],i=(await tc(n.componentId)).find(d=>d.capturedAt===n.startedAt);if(i)return i;const r=e.flatMap(d=>d.violations),c=Se(r,void 0,e,void 0,t,a),l=e.reduce((d,u)=>d+u.durationMs,0);return oi(e,c,l)}async function Di(e){let t;try{t=await $u(e.results,e.interactiveAuditResults,e.walkthroughAcks)}catch(f){mo.warn("ensureForensicEntry failed",f),t=null}let a;if(t&&!t.receipt)try{const f=await $i(t,e.licenseId);f?(await ii(t.componentId,t.capturedAt,f),t={...t,receipt:f}):a="server returned null or malformed receipt"}catch(f){a=f instanceof Error?f.message:String(f),mo.warn("anchor failed",f)}const n=await At().catch(()=>[]),o=Tt(e.results,e.delta,"defense",e.manualRuns,n,e.aiSummary??void 0,e.wallClockMs,e.interactiveAuditResults,e.walkthroughAcks),i=Iu(t,(t==null?void 0:t.receipt)??null,a),r=xu(t,(t==null?void 0:t.receipt)??null),c=Su(t),l=Cu(e.results);let d=o;const u=d.indexOf("<h1");u>-1?d=d.slice(0,u)+i+d.slice(u):d=i+d;const h=d.lastIndexOf("</body>"),m=r+c+l;return d=h>-1?d.slice(0,h)+m+d.slice(h):d+m,{html:d,entry:t,anchored:!!(t!=null&&t.receipt),anchorError:a}}function we(e){return e.replace(/[&<>"']/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[t])}function Tu(e){const t=new Set;for(const a of e){const n=a.axeRulesEvaluated;if(n){for(const o of n.passed??[])t.add(o.ruleId);for(const o of n.inapplicable??[])t.add(o.ruleId);for(const o of n.incomplete??[])t.add(o.ruleId);for(const o of a.violations)t.add(o.ruleId)}}return t}const Eu=new Set(["1.1.1","1.2.2","1.3.1","1.3.4","1.3.5","1.4.1","1.4.2","1.4.3","1.4.4","2.1.1","2.2.2","2.4.1","2.4.2","2.4.4","2.4.6","2.5.3","3.1.1","3.1.2","3.2.2","3.3.1","3.3.2","4.1.1","4.1.2","4.1.3"]);function Ru(e,t,a,n){return`<section>
1203
+ <h2>1. Tool identification</h2>
1204
+ <table style="border-collapse:collapse; margin-top:8pt; font-size:10pt">
1205
+ <tr><td style="padding:2pt 12pt 2pt 0; color:#475569">Tool</td><td><strong>wcagcheckr</strong> — Chrome MV3 accessibility audit extension</td></tr>
1206
+ <tr><td style="padding:2pt 12pt 2pt 0; color:#475569">Extension version</td><td><code>${we(t)}</code></td></tr>
1207
+ <tr><td style="padding:2pt 12pt 2pt 0; color:#475569">Audit engine</td><td><code>axe-core ${we(a)}</code> (pinned; not auto-updated)</td></tr>
1208
+ <tr><td style="padding:2pt 12pt 2pt 0; color:#475569">WCAG target</td><td>WCAG ${we(e)} Level AA</td></tr>
1209
+ <tr><td style="padding:2pt 12pt 2pt 0; color:#475569">Conformance frame</td><td>EN 301 549 v3.2.1 (EU); Section 508 / ADA (US); EAA Directive 2019/882 (EU, enforceable 2025-06-28)</td></tr>
1210
+ ${n?`<tr><td style="padding:2pt 12pt 2pt 0; color:#475569">Audit captured at</td><td><code>${we(n)}</code></td></tr>`:""}
1211
+ </table>
1212
+ </section>`}function Ou(){return`<section>
1213
+ <h2>2. Methodology overview</h2>
1214
+ <p>The wcagcheckr audit framework rests on three layers that complement (rather than overlap) one another:</p>
1215
+ <ol>
1216
+ <li><strong>Multi-state matrix audit.</strong> Most accessibility scanners check a page in its default rendered state — i.e. <em>after</em> a CSS theme loads but <em>before</em> a user has interacted with it. Real users hover, focus, click, type, and toggle disabled/expanded states; the page transitions through dozens of distinct rendering states that a stateless scanner cannot observe. wcagcheckr drives each interactive element through a configurable matrix of pseudo-states (<code>:hover</code>, <code>:focus</code>, <code>:focus-visible</code>, <code>:active</code>, <code>:disabled</code>), ARIA states (<code>aria-expanded</code>, <code>aria-pressed</code>, <code>aria-selected</code>, <code>aria-invalid</code>), themes (light, dark, forced-colors), text direction (LTR, RTL), and viewport breakpoints, running axe-core <em>at every state</em>.</li>
1217
+ <li><strong>Delta-only reporting.</strong> Findings are diffed against a stored baseline. The audit surfaces only <em>new</em> violations relative to the last accepted state — preventing the regression-vs-inherited-debt confusion that floods stateless-scanner reports.</li>
1218
+ <li><strong>Per-component scorecards.</strong> Findings are attributed to a stable component identity (URL + ARIA test-id + structural fingerprint) rather than to a one-off page snapshot, so re-audits over time produce a coherent history per design-system component.</li>
1219
+ </ol>
1220
+ <p>The audit pipeline is fail-safe: each state is captured + scored independently, so a transient layout glitch or third-party script load failure in one state does not invalidate findings from the other states.</p>
1221
+ </section>`}function Mu(){return`<section>
1222
+ <h2>3. State matrix specification</h2>
1223
+ <p>For each in-scope interactive element, the audit drives the following dimensions and records the rendered DOM + computed styles at each combination:</p>
1224
+ <table style="border-collapse:collapse; font-size:10pt; margin-top:8pt">
1225
+ <tr style="border-bottom:1px solid #cbd5e1">
1226
+ <th style="text-align:left; padding:4pt 12pt 4pt 0">Dimension</th>
1227
+ <th style="text-align:left; padding:4pt 12pt 4pt 0">States driven</th>
1228
+ <th style="text-align:left; padding:4pt">Mechanism</th>
1229
+ </tr>
1230
+ <tr><td style="padding:4pt 12pt 4pt 0">Pseudo-states</td><td style="padding:4pt 12pt 4pt 0">default, :hover, :focus, :focus-visible, :active, :disabled</td><td style="padding:4pt; color:#475569">CSS.forcePseudoState via chrome.debugger</td></tr>
1231
+ <tr><td style="padding:4pt 12pt 4pt 0">ARIA states</td><td style="padding:4pt 12pt 4pt 0">aria-expanded, aria-pressed, aria-selected, aria-invalid, aria-busy</td><td style="padding:4pt; color:#475569">Direct attribute mutation + axe re-evaluation</td></tr>
1232
+ <tr><td style="padding:4pt 12pt 4pt 0">Theme</td><td style="padding:4pt 12pt 4pt 0">light, dark, forced-colors (Windows High Contrast)</td><td style="padding:4pt; color:#475569">Emulation.setEmulatedMedia (prefers-color-scheme, forced-colors)</td></tr>
1233
+ <tr><td style="padding:4pt 12pt 4pt 0">Direction</td><td style="padding:4pt 12pt 4pt 0">LTR, RTL</td><td style="padding:4pt; color:#475569">document.dir mutation</td></tr>
1234
+ <tr><td style="padding:4pt 12pt 4pt 0">Viewport</td><td style="padding:4pt 12pt 4pt 0">320px, 768px, 1280px (configurable)</td><td style="padding:4pt; color:#475569">Emulation.setDeviceMetricsOverride</td></tr>
1235
+ <tr><td style="padding:4pt 12pt 4pt 0">Motion preference</td><td style="padding:4pt 12pt 4pt 0">prefers-reduced-motion: reduce</td><td style="padding:4pt; color:#475569">Emulation.setEmulatedMedia</td></tr>
1236
+ </table>
1237
+ <p style="margin-top:10pt">At each combination, a viewport screenshot is captured, the computed-style snapshot is recorded, and axe-core is invoked against the live DOM (not a serialized copy). Findings are tagged with the originating state so reports show which state produced each violation.</p>
1238
+ </section>`}function Du(e,t){const a=new Set;for(const i of bo)for(const r of i.rules)a.add(r);const o=Array.from(a).sort().map(i=>{const r=t.has(i),l=bo.filter(d=>d.rules.includes(i)).map(d=>`${d.ref} ${d.title}`).join(", ")||"(unmapped)";return`<tr>
1239
+ <td style="padding:3pt; font-family:monospace; font-size:9pt"><code>${we(i)}</code></td>
1240
+ <td style="padding:3pt; font-size:9pt">${we(l)}</td>
1241
+ <td style="padding:3pt; font-size:9pt; ${r?"color:#065f46;font-weight:600":"color:#94a3b8"}">${r?"✓ ran":"— not in this run"}</td>
1242
+ </tr>`}).join("");return`<section>
1243
+ <h2>4. Automated rule coverage (axe-core)</h2>
1244
+ <p>${e?'The audit referenced by this methodology document evaluated the following axe-core rules. Rules marked <span style="color:#065f46;font-weight:600">✓ ran</span> were exercised; rules marked otherwise were not in scope for this particular audit (either not triggered by any element on the audited page, or filtered out by the user-configured rule list).':"The wcagcheckr audit framework draws from the axe-core rule set, mapped to WCAG success criteria as follows."}</p>
1245
+ <table style="border-collapse:collapse; font-size:10pt; margin-top:8pt; width:100%">
1246
+ <tr style="border-bottom:1px solid #cbd5e1">
1247
+ <th style="text-align:left; padding:4pt 8pt 4pt 4pt">axe-core rule</th>
1248
+ <th style="text-align:left; padding:4pt 8pt 4pt 4pt">WCAG criterion</th>
1249
+ <th style="text-align:left; padding:4pt">Status${e?" (this audit)":""}</th>
1250
+ </tr>
1251
+ ${o}
1252
+ </table>
1253
+ </section>`}function Nu(e){const t=new Set((e??[]).map(n=>n.workflowId));return`<section>
1254
+ <h2>5. Manual workflow framework (Intelligent Guided Tests)</h2>
1255
+ <p>WCAG includes criteria that cannot be reduced to a single automated heuristic — keyboard reachability, focus management, screen-reader announcement quality, cognitive task complexity. wcagcheckr provides curated step-by-step workflows that surface the right specific checks at the right time, recorded for inclusion in the audit record:</p>
1256
+ <table style="border-collapse:collapse; font-size:10pt; margin-top:8pt; width:100%">
1257
+ <tr style="border-bottom:1px solid #cbd5e1">
1258
+ <th style="text-align:left; padding:4pt">Workflow</th>
1259
+ <th style="text-align:left; padding:4pt">Coverage</th>
1260
+ <th style="text-align:right; padding:4pt">Length</th>
1261
+ <th style="text-align:left; padding:4pt">Status</th>
1262
+ </tr>
1263
+ ${Ee.map(n=>{const o=t.has(n.id),i=n.steps.length;return`<tr>
1264
+ <td style="padding:4pt; font-weight:600">${we(n.name)}</td>
1265
+ <td style="padding:4pt; color:#475569; max-width:380pt">${we(n.blurb)}</td>
1266
+ <td style="padding:4pt; font-size:9pt; text-align:right">${i} step${i===1?"":"s"}</td>
1267
+ <td style="padding:4pt; font-size:9pt; ${o?"color:#065f46;font-weight:600":"color:#94a3b8"}">${o?"✓ executed":"— not in this run"}</td>
1268
+ </tr>`}).join("")}
1269
+ </table>
1270
+ <p style="margin-top:10pt; font-size:10pt; color:#475569">Each step records a pass/fail/skip + free-form notes + a screenshot of the state being evaluated. Workflow run-records are included in the per-component baseline so subsequent audits can compare keyboard/SR behavior over time.</p>
1271
+ </section>`}function Lu(e){const a=St(e,"AA").filter(o=>!Eu.has(o.id)),n=a.map(o=>`<tr>
1272
+ <td style="padding:3pt; font-family:monospace; font-size:9pt"><code>${we(o.id)}</code></td>
1273
+ <td style="padding:3pt"><strong>${we(o.title)}</strong> <span style="color:#475569">(${we(o.level)})</span></td>
1274
+ <td style="padding:3pt; color:#475569; font-size:10pt">${we(o.shortDescription)}</td>
1275
+ </tr>`).join("");return`<section>
1276
+ <h2>6. WCAG criteria requiring manual review</h2>
1277
+ <p>The following ${a.length} WCAG ${e} Level AA criteria have <strong>no automated heuristic</strong> that can produce a defensible pass/fail signal alone. wcagcheckr surfaces these criteria with manual-review prompts via the IGT workflows (Section 5) or via the side-panel "Manual checks needed" list. This list is published verbatim so a reviewer can confirm completeness:</p>
1278
+ <table style="border-collapse:collapse; font-size:10pt; margin-top:8pt; width:100%">
1279
+ <tr style="border-bottom:1px solid #cbd5e1">
1280
+ <th style="text-align:left; padding:4pt">SC</th>
1281
+ <th style="text-align:left; padding:4pt">Criterion</th>
1282
+ <th style="text-align:left; padding:4pt">Short description</th>
1283
+ </tr>
1284
+ ${n}
1285
+ </table>
1286
+ </section>`}function Pu(){return`<section>
1287
+ <h2>7. Limitations of automated detection</h2>
1288
+ <p>wcagcheckr provides high-precision automated coverage where automation can be trusted, and explicit manual prompts where it cannot. The audit framework does <strong>not</strong> claim to detect:</p>
1289
+ <ul>
1290
+ <li>Cognitive accessibility concerns (plain-language quality, readability, comprehension).</li>
1291
+ <li>Sensory accessibility beyond color contrast (icon-only labeling intent, ambient color signaling).</li>
1292
+ <li>Time-based media content (caption accuracy, audio-description completeness).</li>
1293
+ <li>Content-correctness errors (alt-text accuracy, heading-text relevance) — only structural-presence errors (alt-text missing, heading-order wrong).</li>
1294
+ <li>Whole-task accessibility — completion of multi-step user journeys with assistive technology (covered by the IGT keyboard + screen-reader workflows when executed).</li>
1295
+ <li>Dynamic content that requires authentication or sequential user input to reach the audit target — the audit applies to the page as rendered at the time of capture.</li>
1296
+ </ul>
1297
+ <p>Findings produced by this tool support but do not substitute for an expert review.</p>
1298
+ </section>`}function Uu(){return`<section>
1299
+ <h2>8. Audit process (technical specification)</h2>
1300
+ <ol>
1301
+ <li><strong>Tab attach.</strong> The extension's service worker attaches via <code>chrome.debugger</code> (CDP version 1.3) to the audited tab. The user is shown the standard Chrome "developer is debugging this browser" banner — wcagcheckr does not attempt to hide or work around this banner.</li>
1302
+ <li><strong>Page warmup.</strong> The audit waits for <code>document.readyState === 'complete'</code> plus a 500ms post-load buffer to allow late-mounted React/Vue/etc. trees to finish initial render.</li>
1303
+ <li><strong>State driving.</strong> For each combination in the state matrix (Section 3), the audit applies the state via CDP (<code>CSS.forcePseudoState</code>, <code>Emulation.setEmulatedMedia</code>, <code>Emulation.setDeviceMetricsOverride</code>, or direct attribute mutation), then waits for layout to settle.</li>
1304
+ <li><strong>Screenshot capture.</strong> A viewport screenshot is captured via <code>Page.captureScreenshot</code> at each state for inclusion in evidence bundles.</li>
1305
+ <li><strong>axe-core invocation.</strong> The pinned axe-core build is injected via CDP <code>Runtime.evaluate</code> and run against the live DOM. Findings are returned with element selectors, computed accessible names, ARIA contexts, and the state under which they were detected.</li>
1306
+ <li><strong>Result aggregation.</strong> Per-state findings are merged. Each unique violation is keyed by (axe ruleId, logical-group selector). Violations appearing in only some states are flagged as state-dependent; violations stable across states are flagged as persistent.</li>
1307
+ <li><strong>Baseline diff.</strong> The merged result is diffed against the saved baseline for this component. New violations, persistent violations, and fixed violations are categorized separately.</li>
1308
+ <li><strong>Forensic record.</strong> A SHA-256 hash of the canonical audit identity block (componentId, pageUrl, grade, totals, axeVersion, capturedAt, statesAudited) is computed and stored in the local forensic log.</li>
1309
+ </ol>
1310
+ </section>`}function _u(){return`<section>
1311
+ <h2>9. Forensic anchoring (RFC 3161 + ed25519)</h2>
1312
+ <p>Audit records can be sealed against a trusted third-party timestamp authority and counter-signed by the wcagcheckr server. When sealed:</p>
1313
+ <ul>
1314
+ <li>The SHA-256 hash of the audit identity block is submitted to a public RFC 3161 timestamp authority. The TSA returns a signed timestamp token witnessing that the hash existed at the recorded time.</li>
1315
+ <li>The wcagcheckr server counter-signs (audit hash + anchored-at + TSA name + product slug + previous-anchor hash) using its ed25519 private key. The corresponding public key is published at <code>api.wcagcheckr.com/v1/products/wcagcheckr/forensic/public-key</code>.</li>
1316
+ <li>The seal can be verified offline using only the published TSA certificate and the wcagcheckr public key. No server contact is required at verification time.</li>
1317
+ <li>Each new anchor incorporates the hash of the previous anchor in the chain, so removing or backdating an entry would invalidate every later seal.</li>
1318
+ </ul>
1319
+ <p>Sealed audits are surfaced in the deposition packet (G18) with the full receipt + verification instructions. Public verification page: <a href="https://api.wcagcheckr.com/verify">api.wcagcheckr.com/verify</a>.</p>
1320
+ </section>`}function Fu(){return`<section>
1321
+ <h2>10. References</h2>
1322
+ <ul style="font-size:10pt; line-height:1.6">
1323
+ <li>W3C, <a href="https://www.w3.org/TR/WCAG21/">Web Content Accessibility Guidelines (WCAG) 2.1</a>, W3C Recommendation 2018-06-05 (updated 2023-09-21).</li>
1324
+ <li>W3C, <a href="https://www.w3.org/TR/WCAG22/">Web Content Accessibility Guidelines (WCAG) 2.2</a>, W3C Recommendation 2023-10-05.</li>
1325
+ <li>European Telecommunications Standards Institute, <a href="https://www.etsi.org/deliver/etsi_en/301500_301599/301549/03.02.01_60/en_301549v030201p.pdf">EN 301 549 v3.2.1</a>, Accessibility requirements suitable for public procurement of ICT products and services.</li>
1326
+ <li>European Union, Directive (EU) <a href="https://eur-lex.europa.eu/eli/dir/2019/882/oj">2019/882</a>, European Accessibility Act, enforceable 2025-06-28.</li>
1327
+ <li>Deque Systems, <a href="https://github.com/dequelabs/axe-core/blob/master/doc/API.md">axe-core API documentation</a>, MPL 2.0.</li>
1328
+ <li>IETF RFC 3161, <a href="https://datatracker.ietf.org/doc/html/rfc3161">Internet X.509 Public Key Infrastructure Time-Stamp Protocol (TSP)</a>.</li>
1329
+ <li>IETF RFC 8032, <a href="https://datatracker.ietf.org/doc/html/rfc8032">Edwards-Curve Digital Signature Algorithm (EdDSA)</a>.</li>
1330
+ </ul>
1331
+ </section>`}const bo=[{ref:"1.1.1",title:"Non-text Content",rules:["image-alt","input-image-alt","area-alt","object-alt","svg-img-alt"]},{ref:"1.2.2",title:"Captions (Prerecorded)",rules:["video-caption"]},{ref:"1.3.1",title:"Info and Relationships",rules:["heading-order","list","listitem","definition-list","dlitem","th-has-data-cells","td-headers-attr","aria-required-children","aria-required-parent","label"]},{ref:"1.3.4",title:"Orientation",rules:["css-orientation-lock"]},{ref:"1.3.5",title:"Identify Input Purpose",rules:["autocomplete-valid"]},{ref:"1.4.1",title:"Use of Color",rules:["link-in-text-block"]},{ref:"1.4.2",title:"Audio Control",rules:["no-autoplay-audio"]},{ref:"1.4.3",title:"Contrast (Minimum)",rules:["color-contrast"]},{ref:"1.4.4",title:"Resize Text",rules:["meta-viewport"]},{ref:"2.1.1",title:"Keyboard",rules:["accesskeys"]},{ref:"2.2.2",title:"Pause, Stop, Hide",rules:["blink","marquee","meta-refresh"]},{ref:"2.4.1",title:"Bypass Blocks",rules:["bypass","skip-link"]},{ref:"2.4.2",title:"Page Titled",rules:["document-title"]},{ref:"2.4.4",title:"Link Purpose (In Context)",rules:["link-name"]},{ref:"2.4.6",title:"Headings and Labels",rules:["empty-heading"]},{ref:"3.1.1",title:"Language of Page",rules:["html-has-lang","html-lang-valid"]},{ref:"3.1.2",title:"Language of Parts",rules:["valid-lang"]},{ref:"3.3.1",title:"Error Identification",rules:["aria-input-field-name"]},{ref:"3.3.2",title:"Labels or Instructions",rules:["label-title-only","form-field-multiple-labels"]},{ref:"4.1.1",title:"Parsing",rules:["duplicate-id","duplicate-id-active","duplicate-id-aria"]},{ref:"4.1.2",title:"Name, Role, Value",rules:["aria-allowed-attr","aria-required-attr","aria-roles","aria-valid-attr-value","aria-valid-attr","button-name","select-name","role-img-alt","aria-hidden-body","aria-hidden-focus"]},{ref:"4.1.3",title:"Status Messages",rules:["aria-toggle-field-name"]}];function Wu(e={}){var d,u,h,m,f;const t=e.results??[],a=e.manualRuns,n=e.wcagTargetOverride??Le,o=typeof chrome<"u"&&((h=(u=(d=chrome.runtime)==null?void 0:d.getManifest)==null?void 0:u.call(d))==null?void 0:h.version)||"unknown",i=((m=t[0])==null?void 0:m.axeVersion)??"4.11.4",r=((f=t[0])==null?void 0:f.startedAt)??null,c=Tu(t),l=c.size>0;return`<!doctype html>
1332
+ <html lang="en">
1333
+ <head>
1334
+ <meta charset="utf-8" />
1335
+ <title>wcagcheckr — Audit Methodology (WCAG ${we(n)} AA)</title>
1336
+ <style>
1337
+ *, *::before, *::after { box-sizing: border-box; }
1338
+ body { font: 11pt/1.55 -apple-system, system-ui, "Segoe UI", sans-serif; color: #0f172a; max-width: 8in; margin: 0.5in auto; padding: 0 0.25in; }
1339
+ h1 { font-size: 22pt; margin: 0 0 4pt; }
1340
+ h2 { font-size: 14pt; margin: 24pt 0 8pt; padding-bottom: 4pt; border-bottom: 1px solid #cbd5e1; page-break-after: avoid; }
1341
+ .doc-meta { font-size: 10pt; color: #475569; margin-bottom: 12pt; }
1342
+ section { page-break-inside: avoid; }
1343
+ table { width: 100%; }
1344
+ code { background: #f1f5f9; padding: 1pt 4pt; border-radius: 2pt; }
1345
+ ul, ol { padding-left: 20pt; }
1346
+ ul li, ol li { margin-bottom: 4pt; }
1347
+ @media print {
1348
+ body { margin: 0; padding: 0.25in; max-width: none; }
1349
+ }
1350
+ </style>
1351
+ </head>
1352
+ <body>
1353
+ <h1>Audit Methodology — wcagcheckr</h1>
1354
+ <p class="doc-meta">Standalone methodology document. Pair with a specific audit's deposition packet (separate file) to demonstrate both how the audit was conducted and what it found.</p>
1355
+
1356
+ ${Ru(n,o,i,r)}
1357
+ ${Ou()}
1358
+ ${Mu()}
1359
+ ${Du(l,c)}
1360
+ ${Nu(a)}
1361
+ ${Lu(n)}
1362
+ ${Pu()}
1363
+ ${Uu()}
1364
+ ${_u()}
1365
+ ${Fu()}
1366
+
1367
+ </body>
1368
+ </html>`}const qu=[{wcagId:"1.1.1",title:"Non-text Content",level:"A",addedIn:"2.0",en301549Clause:"9.1.1.1",section508:"in-scope",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"in-scope"},{wcagId:"1.2.1",title:"Audio-only and Video-only (Prerecorded)",level:"A",addedIn:"2.0",en301549Clause:"9.1.2.1",section508:"in-scope",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"in-scope"},{wcagId:"1.2.2",title:"Captions (Prerecorded)",level:"A",addedIn:"2.0",en301549Clause:"9.1.2.2",section508:"in-scope",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"in-scope"},{wcagId:"1.2.3",title:"Audio Description or Media Alternative (Prerecorded)",level:"A",addedIn:"2.0",en301549Clause:"9.1.2.3",section508:"in-scope",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"in-scope"},{wcagId:"1.2.4",title:"Captions (Live)",level:"AA",addedIn:"2.0",en301549Clause:"9.1.2.4",section508:"in-scope",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"in-scope"},{wcagId:"1.2.5",title:"Audio Description (Prerecorded)",level:"AA",addedIn:"2.0",en301549Clause:"9.1.2.5",section508:"in-scope",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"in-scope"},{wcagId:"1.3.1",title:"Info and Relationships",level:"A",addedIn:"2.0",en301549Clause:"9.1.3.1",section508:"in-scope",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"in-scope"},{wcagId:"1.3.2",title:"Meaningful Sequence",level:"A",addedIn:"2.0",en301549Clause:"9.1.3.2",section508:"in-scope",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"in-scope"},{wcagId:"1.3.3",title:"Sensory Characteristics",level:"A",addedIn:"2.0",en301549Clause:"9.1.3.3",section508:"in-scope",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"in-scope"},{wcagId:"1.3.4",title:"Orientation",level:"AA",addedIn:"2.1",en301549Clause:"9.1.3.4",section508:"not-in-scope-2.1",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"not-in-scope-2.1",notes:"WCAG 2.1 addition. Section 508 Refresh is bound to WCAG 2.0."},{wcagId:"1.3.5",title:"Identify Input Purpose",level:"AA",addedIn:"2.1",en301549Clause:"9.1.3.5",section508:"not-in-scope-2.1",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"not-in-scope-2.1"},{wcagId:"1.4.1",title:"Use of Color",level:"A",addedIn:"2.0",en301549Clause:"9.1.4.1",section508:"in-scope",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"in-scope"},{wcagId:"1.4.2",title:"Audio Control",level:"A",addedIn:"2.0",en301549Clause:"9.1.4.2",section508:"in-scope",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"in-scope"},{wcagId:"1.4.3",title:"Contrast (Minimum)",level:"AA",addedIn:"2.0",en301549Clause:"9.1.4.3",section508:"in-scope",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"in-scope",notes:"The single most-cited criterion in US Title III website complaints."},{wcagId:"1.4.4",title:"Resize Text",level:"AA",addedIn:"2.0",en301549Clause:"9.1.4.4",section508:"in-scope",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"in-scope"},{wcagId:"1.4.5",title:"Images of Text",level:"AA",addedIn:"2.0",en301549Clause:"9.1.4.5",section508:"in-scope",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"in-scope"},{wcagId:"1.4.10",title:"Reflow",level:"AA",addedIn:"2.1",en301549Clause:"9.1.4.10",section508:"not-in-scope-2.1",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"not-in-scope-2.1"},{wcagId:"1.4.11",title:"Non-text Contrast",level:"AA",addedIn:"2.1",en301549Clause:"9.1.4.11",section508:"not-in-scope-2.1",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"not-in-scope-2.1"},{wcagId:"1.4.12",title:"Text Spacing",level:"AA",addedIn:"2.1",en301549Clause:"9.1.4.12",section508:"not-in-scope-2.1",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"not-in-scope-2.1"},{wcagId:"1.4.13",title:"Content on Hover or Focus",level:"AA",addedIn:"2.1",en301549Clause:"9.1.4.13",section508:"not-in-scope-2.1",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"not-in-scope-2.1",notes:"Cited in tooltip + dropdown-related complaints."},{wcagId:"2.1.1",title:"Keyboard",level:"A",addedIn:"2.0",en301549Clause:"9.2.1.1",section508:"in-scope",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"in-scope"},{wcagId:"2.1.2",title:"No Keyboard Trap",level:"A",addedIn:"2.0",en301549Clause:"9.2.1.2",section508:"in-scope",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"in-scope"},{wcagId:"2.1.4",title:"Character Key Shortcuts",level:"A",addedIn:"2.1",en301549Clause:"9.2.1.4",section508:"not-in-scope-2.1",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"not-in-scope-2.1"},{wcagId:"2.2.1",title:"Timing Adjustable",level:"A",addedIn:"2.0",en301549Clause:"9.2.2.1",section508:"in-scope",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"in-scope"},{wcagId:"2.2.2",title:"Pause, Stop, Hide",level:"A",addedIn:"2.0",en301549Clause:"9.2.2.2",section508:"in-scope",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"in-scope"},{wcagId:"2.3.1",title:"Three Flashes or Below Threshold",level:"A",addedIn:"2.0",en301549Clause:"9.2.3.1",section508:"in-scope",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"in-scope"},{wcagId:"2.4.1",title:"Bypass Blocks",level:"A",addedIn:"2.0",en301549Clause:"9.2.4.1",section508:"in-scope",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"in-scope"},{wcagId:"2.4.2",title:"Page Titled",level:"A",addedIn:"2.0",en301549Clause:"9.2.4.2",section508:"in-scope",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"in-scope"},{wcagId:"2.4.3",title:"Focus Order",level:"A",addedIn:"2.0",en301549Clause:"9.2.4.3",section508:"in-scope",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"in-scope"},{wcagId:"2.4.4",title:"Link Purpose (In Context)",level:"A",addedIn:"2.0",en301549Clause:"9.2.4.4",section508:"in-scope",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"in-scope"},{wcagId:"2.4.5",title:"Multiple Ways",level:"AA",addedIn:"2.0",en301549Clause:"9.2.4.5",section508:"in-scope",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"in-scope"},{wcagId:"2.4.6",title:"Headings and Labels",level:"AA",addedIn:"2.0",en301549Clause:"9.2.4.6",section508:"in-scope",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"in-scope"},{wcagId:"2.4.7",title:"Focus Visible",level:"AA",addedIn:"2.0",en301549Clause:"9.2.4.7",section508:"in-scope",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"in-scope"},{wcagId:"2.4.11",title:"Focus Not Obscured (Minimum)",level:"AA",addedIn:"2.2",en301549Clause:"9.2.4.11",section508:"not-in-scope-2.2",adaTitleIII:"gray-area",eaa:"gray-area",aoda:"not-in-scope-2.2",notes:"WCAG 2.2 addition. EN 301 549 may not yet reference; case law not yet settled."},{wcagId:"2.5.1",title:"Pointer Gestures",level:"A",addedIn:"2.1",en301549Clause:"9.2.5.1",section508:"not-in-scope-2.1",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"not-in-scope-2.1"},{wcagId:"2.5.2",title:"Pointer Cancellation",level:"A",addedIn:"2.1",en301549Clause:"9.2.5.2",section508:"not-in-scope-2.1",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"not-in-scope-2.1"},{wcagId:"2.5.3",title:"Label in Name",level:"A",addedIn:"2.1",en301549Clause:"9.2.5.3",section508:"not-in-scope-2.1",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"not-in-scope-2.1"},{wcagId:"2.5.4",title:"Motion Actuation",level:"A",addedIn:"2.1",en301549Clause:"9.2.5.4",section508:"not-in-scope-2.1",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"not-in-scope-2.1"},{wcagId:"2.5.7",title:"Dragging Movements",level:"AA",addedIn:"2.2",en301549Clause:"9.2.5.7",section508:"not-in-scope-2.2",adaTitleIII:"gray-area",eaa:"gray-area",aoda:"not-in-scope-2.2"},{wcagId:"2.5.8",title:"Target Size (Minimum)",level:"AA",addedIn:"2.2",en301549Clause:"9.2.5.8",section508:"not-in-scope-2.2",adaTitleIII:"gray-area",eaa:"gray-area",aoda:"not-in-scope-2.2"},{wcagId:"3.1.1",title:"Language of Page",level:"A",addedIn:"2.0",en301549Clause:"9.3.1.1",section508:"in-scope",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"in-scope"},{wcagId:"3.1.2",title:"Language of Parts",level:"AA",addedIn:"2.0",en301549Clause:"9.3.1.2",section508:"in-scope",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"in-scope"},{wcagId:"3.2.1",title:"On Focus",level:"A",addedIn:"2.0",en301549Clause:"9.3.2.1",section508:"in-scope",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"in-scope"},{wcagId:"3.2.2",title:"On Input",level:"A",addedIn:"2.0",en301549Clause:"9.3.2.2",section508:"in-scope",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"in-scope"},{wcagId:"3.2.3",title:"Consistent Navigation",level:"AA",addedIn:"2.0",en301549Clause:"9.3.2.3",section508:"in-scope",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"in-scope"},{wcagId:"3.2.4",title:"Consistent Identification",level:"AA",addedIn:"2.0",en301549Clause:"9.3.2.4",section508:"in-scope",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"in-scope"},{wcagId:"3.2.6",title:"Consistent Help",level:"A",addedIn:"2.2",en301549Clause:"9.3.2.6",section508:"not-in-scope-2.2",adaTitleIII:"gray-area",eaa:"gray-area",aoda:"not-in-scope-2.2"},{wcagId:"3.3.1",title:"Error Identification",level:"A",addedIn:"2.0",en301549Clause:"9.3.3.1",section508:"in-scope",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"in-scope"},{wcagId:"3.3.2",title:"Labels or Instructions",level:"A",addedIn:"2.0",en301549Clause:"9.3.3.2",section508:"in-scope",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"in-scope"},{wcagId:"3.3.3",title:"Error Suggestion",level:"AA",addedIn:"2.0",en301549Clause:"9.3.3.3",section508:"in-scope",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"in-scope"},{wcagId:"3.3.4",title:"Error Prevention (Legal, Financial, Data)",level:"AA",addedIn:"2.0",en301549Clause:"9.3.3.4",section508:"in-scope",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"in-scope"},{wcagId:"3.3.7",title:"Redundant Entry",level:"A",addedIn:"2.2",en301549Clause:"9.3.3.7",section508:"not-in-scope-2.2",adaTitleIII:"gray-area",eaa:"gray-area",aoda:"not-in-scope-2.2"},{wcagId:"3.3.8",title:"Accessible Authentication (Minimum)",level:"AA",addedIn:"2.2",en301549Clause:"9.3.3.8",section508:"not-in-scope-2.2",adaTitleIII:"gray-area",eaa:"gray-area",aoda:"not-in-scope-2.2"},{wcagId:"4.1.1",title:"Parsing",level:"A",addedIn:"2.0",en301549Clause:"9.4.1.1",section508:"in-scope",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"in-scope",notes:"OBSOLETE in WCAG 2.2 — modern HTML5 parsing handles legacy use cases."},{wcagId:"4.1.2",title:"Name, Role, Value",level:"A",addedIn:"2.0",en301549Clause:"9.4.1.2",section508:"in-scope",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"in-scope",notes:"Second most-cited after 1.4.3 in US Title III complaints."},{wcagId:"4.1.3",title:"Status Messages",level:"AA",addedIn:"2.1",en301549Clause:"9.4.1.3",section508:"not-in-scope-2.1",adaTitleIII:"de-facto-standard",eaa:"in-scope",aoda:"not-in-scope-2.1"}];function Ie(e){return e.replace(/[&<>"']/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[t])}function Gu(e){const t=new Set;for(const a of e)for(const n of a.violations)t.add(n.wcagCriterion);return t}function ke(e,t){return`<span style="display:inline-block; padding:1pt 6pt; border-radius:3pt; font-size:9pt; background:${t}20; color:${t}; font-weight:600">${Ie(e)}</span>`}function Vu(e){switch(e){case"in-scope":return ke("§E207.2 → WCAG 2.0","#059669");case"not-in-scope-2.1":return ke("Not in scope (WCAG 2.1+)","#94a3b8");case"not-in-scope-2.2":return ke("Not in scope (WCAG 2.2)","#94a3b8")}}function ju(e){switch(e){case"de-facto-standard":return ke("De facto via WCAG AA","#059669");case"gray-area":return ke("Gray area (new SC)","#f59e0b")}}function zu(e){switch(e){case"in-scope":return ke("In scope","#059669");case"product-related":return ke("Product-related","#0ea5e9");case"gray-area":return ke("Gray area","#f59e0b")}}function Hu(e){switch(e){case"in-scope":return ke("In scope (WCAG 2.0)","#059669");case"not-in-scope-2.1":return ke("Not in scope (2.1+)","#94a3b8");case"not-in-scope-2.2":return ke("Not in scope (2.2)","#94a3b8")}}function Bu(e={}){var l;const t=e.wcagTargetOverride??Le,a=e.results?Gu(e.results):new Set,n=(((l=e.results)==null?void 0:l.length)??0)>0,o=new Set(St(t,"AA").map(d=>d.id)),i=qu.map(d=>{const u=a.has(d.wcagId),h=o.has(d.wcagId),m=u?"background:#fee2e2;":h?"":"background:#f8fafc; opacity:0.6;",f=u?'<div style="font-size:9pt; color:#b91c1c; font-weight:700; margin-top:2pt">⚠ Violated in this audit</div>':"";return`<tr style="${m}">
1369
+ <td style="padding:5pt 6pt; vertical-align:top; border-bottom:1px solid #e2e8f0">
1370
+ <div style="font-family:monospace; font-weight:600">${Ie(d.wcagId)}</div>
1371
+ <div style="font-size:9pt; color:#475569">${Ie(d.level)} · added ${Ie(d.addedIn)}</div>
1372
+ </td>
1373
+ <td style="padding:5pt 6pt; vertical-align:top; border-bottom:1px solid #e2e8f0">
1374
+ <div style="font-weight:600">${Ie(d.title)}</div>
1375
+ ${f}
1376
+ ${d.notes?`<div style="font-size:9pt; color:#475569; margin-top:3pt; font-style:italic">${Ie(d.notes)}</div>`:""}
1377
+ </td>
1378
+ <td style="padding:5pt 6pt; vertical-align:top; border-bottom:1px solid #e2e8f0; font-family:monospace; font-size:9pt">
1379
+ ${d.en301549Clause?`§${Ie(d.en301549Clause)}`:'<span style="color:#94a3b8">—</span>'}
1380
+ </td>
1381
+ <td style="padding:5pt 6pt; vertical-align:top; border-bottom:1px solid #e2e8f0">${Vu(d.section508)}</td>
1382
+ <td style="padding:5pt 6pt; vertical-align:top; border-bottom:1px solid #e2e8f0">${ju(d.adaTitleIII)}</td>
1383
+ <td style="padding:5pt 6pt; vertical-align:top; border-bottom:1px solid #e2e8f0">${zu(d.eaa)}</td>
1384
+ <td style="padding:5pt 6pt; vertical-align:top; border-bottom:1px solid #e2e8f0">${Hu(d.aoda)}</td>
1385
+ </tr>`}).join(""),r=a.size,c=n?`<p class="doc-meta">Annotated with ${r} ${r===1?"criterion":"criteria"} violated in the underlying audit (rows highlighted red).</p>`:'<p class="doc-meta">Standalone reference. Pair with a deposition packet to annotate which criteria were violated in a specific audit.</p>';return`<!doctype html>
1386
+ <html lang="en">
1387
+ <head>
1388
+ <meta charset="utf-8" />
1389
+ <title>wcagcheckr — Conformance Crosswalk (WCAG ${Ie(t)} AA → EN / 508 / ADA / EAA / AODA)</title>
1390
+ <style>
1391
+ *, *::before, *::after { box-sizing: border-box; }
1392
+ body { font: 10pt/1.5 -apple-system, system-ui, "Segoe UI", sans-serif; color: #0f172a; max-width: 11in; margin: 0.4in auto; padding: 0 0.25in; }
1393
+ h1 { font-size: 20pt; margin: 0 0 4pt; }
1394
+ h2 { font-size: 13pt; margin: 18pt 0 8pt; padding-bottom: 4pt; border-bottom: 1px solid #cbd5e1; page-break-after: avoid; }
1395
+ .doc-meta { font-size: 10pt; color: #475569; margin: 0 0 14pt; }
1396
+ table { border-collapse: collapse; width: 100%; font-size: 10pt; margin-top: 10pt; }
1397
+ th { text-align: left; padding: 6pt; background: #f1f5f9; border-bottom: 2px solid #cbd5e1; font-weight: 600; }
1398
+ thead { display: table-header-group; }
1399
+ tr { page-break-inside: avoid; }
1400
+ code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 9pt; }
1401
+ .legend { background: #f8fafc; border: 1px solid #e2e8f0; padding: 10pt 14pt; border-radius: 4pt; margin: 14pt 0; }
1402
+ .legend dt { font-weight: 600; margin-top: 6pt; }
1403
+ .legend dd { margin: 2pt 0 0 0; font-size: 9pt; color: #475569; }
1404
+ @media print {
1405
+ body { margin: 0; padding: 0.25in; max-width: none; }
1406
+ }
1407
+ </style>
1408
+ </head>
1409
+ <body>
1410
+ <h1>Conformance Crosswalk</h1>
1411
+ ${c}
1412
+
1413
+ <section class="legend">
1414
+ <h2 style="margin-top:0; border:none; padding:0">How to read this document</h2>
1415
+ <p style="margin:0 0 8pt 0">Each row is a single WCAG success criterion. The columns show how that criterion maps onto five other accessibility frameworks. The same finding cited as a WCAG violation in one jurisdiction may need to be re-cited under a different framework's clause in another.</p>
1416
+ <dl style="margin:0">
1417
+ <dt>WCAG ${Ie(t)} AA</dt>
1418
+ <dd>The dotted SC number (e.g. <code>1.4.3</code>), level, and the WCAG version that introduced it. Criteria de-emphasized when out of scope for the audit's target version (${Ie(t)}).</dd>
1419
+ <dt>EN 301 549 v3.2.1</dt>
1420
+ <dd>European procurement standard. The <code>§9.x.y.z</code> clause is the direct cross-reference. Cited in EAA-related disputes and EU public-procurement complaints.</dd>
1421
+ <dt>Section 508 (36 CFR 1194)</dt>
1422
+ <dd>US federal standard, post-2017 Refresh. Incorporates WCAG 2.0 A + AA by reference (E207.2). Criteria added in WCAG 2.1+ are not currently in scope.</dd>
1423
+ <dt>ADA Title III</dt>
1424
+ <dd>US private-sector. DOJ has not adopted formal technical standards; courts and consent decrees have converged on WCAG AA as the de facto standard. 2.2 criteria are "gray area" — too recent for settled case law.</dd>
1425
+ <dt>EAA (Directive 2019/882)</dt>
1426
+ <dd>EU. Enforceable since 28 June 2025 for private-sector websites + apps + e-commerce in EU markets. References WCAG via EN 301 549.</dd>
1427
+ <dt>AODA (Ontario, Canada)</dt>
1428
+ <dd>Integrated Accessibility Standards Regulation (O. Reg. 191/11) Part II. Adopts WCAG 2.0 AA for public-sector + designated private-sector organizations with > 50 employees.</dd>
1429
+ </dl>
1430
+ </section>
1431
+
1432
+ <table>
1433
+ <thead>
1434
+ <tr>
1435
+ <th>WCAG SC</th>
1436
+ <th>Criterion</th>
1437
+ <th>EN 301 549</th>
1438
+ <th>Section 508</th>
1439
+ <th>ADA Title III</th>
1440
+ <th>EAA</th>
1441
+ <th>AODA</th>
1442
+ </tr>
1443
+ </thead>
1444
+ <tbody>
1445
+ ${i}
1446
+ </tbody>
1447
+ </table>
1448
+
1449
+ <section style="margin-top:24pt; font-size:9pt; color:#475569; border-top:1px solid #e2e8f0; padding-top:10pt">
1450
+ <p><strong>Note on mappings.</strong> Cross-framework mappings shown here are based on published cross-references (ETSI EN 301 549 v3.2.1 Annex A; US Access Board Section 508 Refresh 2017 §E207.2; AODA IASR O. Reg. 191/11 Part II Information &amp; Communications) and on DOJ Title III consent decrees and settlements citing WCAG AA. Mappings are not legal advice. Edge cases (WCAG 2.2 criteria, motion + pointer criteria, accessible-authentication) reflect current case-law uncertainty and may shift as courts and standards bodies update their references.</p>
1451
+ <p><strong>References:</strong> <a href="https://www.w3.org/TR/WCAG21/">WCAG 2.1</a> · <a href="https://www.w3.org/TR/WCAG22/">WCAG 2.2</a> · <a href="https://www.etsi.org/deliver/etsi_en/301500_301599/301549/03.02.01_60/en_301549v030201p.pdf">EN 301 549 v3.2.1</a> · <a href="https://www.access-board.gov/ict/">US Access Board ICT Refresh</a> · <a href="https://eur-lex.europa.eu/eli/dir/2019/882/oj">EU Directive 2019/882 (EAA)</a> · <a href="https://www.ontario.ca/laws/regulation/110191">O. Reg. 191/11 IASR</a>.</p>
1452
+ </section>
1453
+
1454
+ </body>
1455
+ </html>`}const yo={critical:4,serious:3,moderate:2,minor:1},Ni={critical:"🔴",serious:"🟠",moderate:"🟡",minor:"⚪"};function Ku(e,t){return e.length>t?e.slice(0,t-1)+"…":e}function Yu(e){const t=e.split(" > ");return t[t.length-1]??e}function Ju(e,t,a){const n=at[t.ruleId],o=[];return o.push(`## Ticket ${e} — ${Ni[t.impact]} [${t.impact}] ${t.ruleId}`),o.push(""),o.push(`**WCAG:** ${t.wcagCriterion} ${t.wcagLevel}`),o.push(`**Impact:** ${t.impact}`),o.push(`**Selector:** \`${t.target.selector}\``),o.push(`**Page:** ${a}`),t._instances>1&&o.push(`**Instances:** ${t._instances} (this rule fires on ${t._instances} elements; fixing the root cause will resolve all)`),o.push(""),o.push("### What failed"),o.push(""),o.push(t.description),o.push(""),t.target.accessibleName&&(o.push(`Element's current accessible name: \`${t.target.accessibleName}\``),o.push("")),o.push("### Element"),o.push(""),o.push("```html"),o.push(t.target.outerHTML),o.push("```"),o.push(""),t.target.failureSummary&&t.target.failureSummary.trim().length>0&&(o.push("### What axe-core checked"),o.push(""),o.push("```"),o.push(t.target.failureSummary.trim()),o.push("```"),o.push("")),n&&(o.push("### How to fix"),o.push(""),o.push(n.summary),o.push(""),n.snippet&&(o.push("```"+(n.snippetLang??"")),o.push(n.snippet),o.push("```"),o.push(""))),o.push("### Reference"),o.push(""),o.push(`- WCAG ${t.wcagCriterion}: https://www.w3.org/WAI/WCAG21/Understanding/`),o.push(`- axe-core rule: ${t.helpUrl}`),o.push(""),o.join(`
1456
+ `)}function Xu(e){var l;const t=e.results.flatMap(d=>d.violations),a=new Map;for(const d of t){const u=xe(d.ruleId,d.target.selector),h=a.get(u);h?h._instances+=1:a.set(u,{...d,_instances:1})}const n=Array.from(a.values()).sort((d,u)=>{const h=yo[u.impact]-yo[d.impact];return h!==0?h:d.ruleId.localeCompare(u.ruleId)}),o=e.pageUrlOverride??((l=e.results[0])==null?void 0:l.pageUrl)??"(unknown)",i=new Date().toISOString(),r=[];r.push("# Accessibility Remediation Tickets"),r.push(""),r.push(`**Generated:** ${i}`),r.push(`**Source URL:** ${o}`),r.push(`**Total tickets:** ${n.length}`),r.push(`**Total violations (with duplicates):** ${t.length}`),r.push(""),r.push("Each ticket below is a self-contained, copy-pasteable section suitable for "),r.push("filing into Linear, Jira, Asana, Trello, GitHub Issues, or any tool that "),r.push("accepts markdown. Tickets are de-duped by logical group, so a list-grid "),r.push("with 50 instances of the same issue produces one ticket noting the count."),r.push(""),r.push("## Index"),r.push(""),r.push("| # | Impact | Rule | WCAG | Element |"),r.push("|---|--------|------|------|---------|"),n.forEach((d,u)=>{const h=Ku(Yu(d.target.selector),32).replace(/\|/g,"\\|");r.push(`| ${u+1} | ${Ni[d.impact]} ${d.impact} | \`${d.ruleId}\` | ${d.wcagCriterion} ${d.wcagLevel} | \`${h}\` |`)}),r.push(""),r.push("---"),r.push("");const c=n.map((d,u)=>Ju(u+1,d,o)).join(`
1457
+ ---
1458
+
1459
+ `);return n.length===0?[r.join(`
1460
+ `),"_No violations to file. Run an audit first._"].join(`
1461
+ `):r.join(`
1462
+ `)+c}const wo={critical:4,serious:3,moderate:2,minor:1};function se(e){return e.replace(/[&<>"']/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[t])}function Qu(e,t){const a=e.flatMap(o=>o.violations),n=new Map;for(const o of a){const i=`${o.ruleId}::${o.target.selector}::${o.wcagCriterion}`,r=n.get(i);if(r){r._instances+=1;continue}n.set(i,{...o,_instances:1})}return Array.from(n.values()).sort((o,i)=>{const r=wo[i.impact]-wo[o.impact];return r!==0?r:o.ruleId.localeCompare(i.ruleId)}).slice(0,t)}function Zu(e,t){switch(e){case"A":return"No critical or serious WCAG violations detected. The audited page is in good shape; manual review still recommended for criteria automation cannot fully evaluate.";case"B":{const a=(t==null?void 0:t.moderate)??0;return t!=null&&t.cappedByCoverage&&a===0?'No critical, serious, or moderate violations detected — but not every applicable WCAG 2.1 AA criterion has been affirmatively evaluated yet. The exact criteria still needing your judgment are listed in the "Criteria still needing your judgment" section below. Each can be marked pass / N/A / fail in the Coverage gaps panel under the grade in the side panel; once every blocker has a verdict, the grade lifts to A.':t!=null&&t.cappedByCoverage&&a>0?`${a} moderate WCAG finding${a===1?"":"s"} detected, AND the audit hasn't yet affirmatively evaluated every applicable AA criterion. The "Top risks" section lists the moderate finding${a===1?"":"s"}; the "Criteria still needing your judgment" section lists the coverage gaps. Both must close before any public conformance claim.`:`${a||"Several"} moderate WCAG finding${a===1?"":"s"} — no critical or serious violations, but areas worth attention before public claims of full conformance.`}case"C":return"At least one serious WCAG violation detected. These typically affect users of assistive technology and should be addressed before the next public release.";case"D":return"Multiple serious WCAG violations detected. The page in its current state is likely to fail real-world testing by users with disabilities and creates measurable legal exposure.";case"F":return"At least one critical WCAG violation detected. Critical-impact issues block users of assistive technology from core functionality and require immediate remediation."}}function eh(e,t,a=0,n=[]){const o=[];if(e.length===0){if(a>0){const r=n.slice(0,3).join(", "),c=a>3?` (+${a-3} more listed below)`:"";return o.push(`Open the side panel, expand the "${a} criteri${a===1?"on":"a"} need your judgment to lift this to A" panel under the grade, and mark each one pass / N/A / fail with a note. Start with: WCAG ${r}${c}.`),o.push("Schedule a recurring audit (Schedules tab) so any regression is caught within a single cadence window."),o.push("Publish an EAA / WAD accessibility statement (Delta tab → export menu) once coverage is complete."),o}return o.push("Run an IGT manual workflow (keyboard, screen reader) to cover criteria automation cannot fully evaluate."),o.push("Schedule a recurring audit (Schedules tab) so any regression is caught within a single cadence window."),o.push("Publish an EAA / WAD accessibility statement (Delta tab → export menu)."),o}const i=e[0];return o.push(`Resolve the ${i.impact}-impact ${i.ruleId} violation (WCAG ${i.wcagCriterion}). ${i._instances} instance${i._instances===1?"":"s"} on the page; the developer's full findings document has the exact selectors + recommended fixes.`),o.push(t==="F"||t==="D"?"File the top 3 findings into your issue tracker (Delta tab supports GitHub, Jira, GitLab, and Azure DevOps with one click). Block release until critical/serious findings are remediated.":"Add the surfaced findings to the next sprint as accessibility-debt tickets."),o.push("Schedule the page for recurring audit (Schedules tab) so any new violation is caught at the next cadence and surfaces via email or webhook alert."),o}function th(e,t={}){const a=e.flatMap(S=>S.violations),n=Se(a,t.manualRuns,e,t.acknowledgedMatchKeys,t.interactiveAuditResults,t.walkthroughAcks,t.humanCriterionVerdicts),o=n.letter,i={critical:n.totals.critical,serious:n.totals.serious,moderate:n.totals.moderate,minor:n.totals.minor},r=Qu(e,3),c=new Map(St("2.1","AA").map(S=>[S.id,S.title])),l=new Map,d={untested:0,inconclusive:1,failing:2};function u(S,R){for(const P of S){const I=l.get(P);(!I||d[R]>d[I.state])&&l.set(P,{id:P,title:c.get(P)??"(criterion title unavailable)",state:R})}}n.coverage&&(u(n.coverage.untestedCriteria,"untested"),u(n.coverage.inconclusiveCriteria,"inconclusive"),u(n.coverage.failingCriteria,"failing"));const h=Array.from(l.values()).sort((S,R)=>S.id.localeCompare(R.id,void 0,{numeric:!0})),m=eh(r,o,h.length,h.map(S=>S.id)),f=e[0],g=(f==null?void 0:f.pageUrl)??"(unknown)",p=(f==null?void 0:f.axeVersion)??"4.11.4",b=e.length,w=t.asOfDateOverride??(f==null?void 0:f.startedAt)??new Date().toISOString(),k={A:"#059669",B:"#16a34a",C:"#f59e0b",D:"#ea580c",F:"#dc2626"},y=t.orgName?`<p class="org">Prepared for <strong>${se(t.orgName)}</strong></p>`:"",s=t.logoUrl?`<img src="${se(t.logoUrl)}" alt="${se(t.orgName??"Logo")}" class="logo" />`:"",O=r.length===0?'<p class="empty">No findings surfaced. Manual review still recommended for criteria automation cannot evaluate.</p>':`<ol class="risks">${r.map(S=>`<li>
1463
+ <div class="risk-head">
1464
+ <span class="risk-impact risk-${se(S.impact)}">${se(S.impact)}</span>
1465
+ <code>${se(S.ruleId)}</code>
1466
+ <span class="risk-wcag">WCAG ${se(S.wcagCriterion)} (${se(S.wcagLevel)})</span>
1467
+ </div>
1468
+ <p class="risk-desc">${se(S.description)}</p>
1469
+ <p class="risk-meta">${S._instances} instance${S._instances===1?"":"s"} · selector <code>${se(S.target.selector)}</code></p>
1470
+ </li>`).join("")}</ol>`,E=`<ol class="steps">${m.map(S=>`<li>${se(S)}</li>`).join("")}</ol>`,v={untested:{label:"no evidence",bg:"#64748b"},inconclusive:{label:"inconclusive",bg:"#a16207"},failing:{label:"failing",bg:"#dc2626"}},D=h.length===0?"":`<section>
1471
+ <h2>Criteria still needing your judgment <span style="color:#64748b;font-weight:400;font-size:11pt">(${h.length})</span></h2>
1472
+ <p class="risk-meta" style="margin:0 0 8pt">These WCAG 2.1 AA criteria do not yet have affirmative pass/N/A evidence. Open the side panel, expand the "${h.length} criteri${h.length===1?"on":"a"} need your judgment to lift this to A" panel under the grade, and record a verdict for each.</p>
1473
+ <ol class="risks">${h.map(S=>{const R=v[S.state];return`<li>
1474
+ <div class="risk-head">
1475
+ <span class="risk-impact" style="background:${R.bg}">${se(R.label)}</span>
1476
+ <code>WCAG ${se(S.id)}</code>
1477
+ <span class="risk-wcag">${se(S.title)}</span>
1478
+ </div>
1479
+ </li>`}).join("")}</ol>
1480
+ </section>`;return`<!doctype html>
1481
+ <html lang="en">
1482
+ <head>
1483
+ <meta charset="utf-8" />
1484
+ <title>Accessibility Executive Report${t.orgName?" — "+se(t.orgName):""}</title>
1485
+ <style>
1486
+ *, *::before, *::after { box-sizing: border-box; }
1487
+ body { font: 11pt/1.55 -apple-system, system-ui, "Segoe UI", Roboto, sans-serif; color: #0f172a; max-width: 7.5in; margin: 0.5in auto; padding: 0 0.25in; }
1488
+ header { display: flex; align-items: flex-start; justify-content: space-between; gap: 16pt; padding-bottom: 14pt; border-bottom: 2px solid #0f172a; margin-bottom: 18pt; }
1489
+ header h1 { font-size: 22pt; margin: 0; }
1490
+ header .meta { color: #475569; font-size: 10pt; margin: 4pt 0 0; }
1491
+ header .org { color: #475569; font-size: 10pt; margin: 8pt 0 0; }
1492
+ .logo { max-height: 60pt; max-width: 200pt; object-fit: contain; }
1493
+ .hero { display: flex; align-items: center; gap: 18pt; background: #f8fafc; border: 1px solid #cbd5e1; border-radius: 6pt; padding: 16pt; margin-bottom: 18pt; }
1494
+ .grade { font-size: 48pt; font-weight: 800; color: white; width: 72pt; height: 72pt; border-radius: 8pt; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
1495
+ .hero-text { font-size: 11pt; line-height: 1.55; }
1496
+ .hero-text strong { display: block; font-size: 13pt; margin-bottom: 4pt; }
1497
+ h2 { font-size: 13pt; margin: 18pt 0 8pt; padding-bottom: 4pt; border-bottom: 1px solid #cbd5e1; page-break-after: avoid; }
1498
+ section { page-break-inside: avoid; margin-bottom: 14pt; }
1499
+ .tested-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 6pt 16pt; font-size: 10pt; }
1500
+ .tested-grid dt { font-weight: 600; color: #475569; }
1501
+ .tested-grid dd { margin: 0; }
1502
+ .tested-grid code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 9pt; background: #f1f5f9; padding: 1pt 4pt; border-radius: 2pt; }
1503
+ ol.risks { list-style: none; padding: 0; margin: 0; }
1504
+ ol.risks li { padding: 8pt 0; border-bottom: 1px solid #e2e8f0; }
1505
+ ol.risks li:last-child { border-bottom: none; }
1506
+ .risk-head { display: flex; align-items: center; gap: 8pt; flex-wrap: wrap; margin-bottom: 4pt; }
1507
+ .risk-impact { display: inline-block; padding: 1pt 6pt; border-radius: 3pt; font-size: 9pt; font-weight: 600; text-transform: lowercase; color: white; }
1508
+ .risk-critical { background: #dc2626; }
1509
+ .risk-serious { background: #ea580c; }
1510
+ .risk-moderate { background: #f59e0b; }
1511
+ .risk-minor { background: #94a3b8; }
1512
+ .risk-wcag { color: #475569; font-size: 9pt; }
1513
+ .risk-desc { margin: 0 0 4pt; }
1514
+ .risk-meta { margin: 0; font-size: 9pt; color: #64748b; }
1515
+ .risk-meta code { font-family: ui-monospace, monospace; font-size: 8pt; background: #f1f5f9; padding: 0 4pt; }
1516
+ ol.steps { padding-left: 18pt; margin: 0; }
1517
+ ol.steps li { margin: 6pt 0; }
1518
+ .empty { color: #475569; font-style: italic; }
1519
+ footer { margin-top: 24pt; padding-top: 10pt; border-top: 1px solid #cbd5e1; font-size: 9pt; color: #64748b; }
1520
+ @media print {
1521
+ body { margin: 0; padding: 0.4in; max-width: none; }
1522
+ .hero { box-shadow: none; }
1523
+ }
1524
+ </style>
1525
+ </head>
1526
+ <body>
1527
+ <header>
1528
+ <div>
1529
+ <h1>Accessibility Executive Report</h1>
1530
+ <p class="meta">As of ${se(w)}</p>
1531
+ ${y}
1532
+ </div>
1533
+ ${s}
1534
+ </header>
1535
+
1536
+ <section class="hero" aria-label="Audit summary">
1537
+ <div class="grade" style="background:${k[o]}">${o}</div>
1538
+ <div class="hero-text">
1539
+ <strong>${i.critical} critical · ${i.serious} serious · ${i.moderate} moderate · ${i.minor} minor</strong>
1540
+ ${se(Zu(o,{moderate:i.moderate,cappedByCoverage:n.caps.cappedAtBByCoverage}))}
1541
+ </div>
1542
+ </section>
1543
+
1544
+ <section>
1545
+ <h2>What was tested</h2>
1546
+ <dl class="tested-grid">
1547
+ <dt>Page URL</dt><dd><code>${se(g)}</code></dd>
1548
+ <dt>Audit engine</dt><dd>axe-core <code>${se(p)}</code> + wcagcheckr DOM analyzers</dd>
1549
+ <dt>States audited</dt><dd>${b} (pseudo-states × themes × directions × breakpoints)</dd>
1550
+ <dt>WCAG target</dt><dd>WCAG 2.1 Level AA (configurable; see methodology document)</dd>
1551
+ </dl>
1552
+ </section>
1553
+
1554
+ <section>
1555
+ <h2>Top risks</h2>
1556
+ ${O}
1557
+ </section>
1558
+
1559
+ ${D}
1560
+
1561
+ <section>
1562
+ <h2>Recommended next steps</h2>
1563
+ ${E}
1564
+ </section>
1565
+
1566
+ <footer>
1567
+ This is a one-page executive summary. The developer's full findings document, the deposition packet (forensic-anchored), the audit methodology document, and the cross-jurisdiction conformance crosswalk are available as separate exports.
1568
+ </footer>
1569
+ </body>
1570
+ </html>`}const Ma=J("ai-compliance-summarizer");async function Li(e,t){if(!t.enabled)return null;const a=ce(t);if(!a.ok)return Ma.debug(`AI client unavailable: ${a.reason}`),null;const n=Ce(t.costCapUsd);if(!n.canCharge())return null;try{const o=await a.client.generateExecutiveSummary(e);return n.recordCharge(o.costUsd),!o.lead&&!o.body?(Ma.debug("AI summary returned empty"),null):o}catch(o){return Ma.warn("compliance-summary generation failed",o),null}}async function ah(){const e=await chrome.storage.local.get("aiConfig");return be(e.aiConfig)}function cp(){return fe("AI_COMPLIANCE_SUMMARY_REQUEST",async e=>{const t=await ah();if(!t.enabled)return{type:"AI_COMPLIANCE_SUMMARY_RESPONSE",summary:null,unavailableReason:"AI augmentation is disabled in Settings → AI augmentation."};if(!t.apiKey)return{type:"AI_COMPLIANCE_SUMMARY_RESPONSE",summary:null,unavailableReason:"No AI API key configured."};const a=await Li(e.input,t);return{type:"AI_COMPLIANCE_SUMMARY_RESPONSE",summary:a,unavailableReason:a?void 0:"AI summary generation returned no result."}})}const Ae="wcag-component-auditor";function Ve(){try{return chrome.runtime.getManifest().version}catch{return"0.0.0"}}function Ue(e){const t=e[0];return t?t.pageUrl?t.pageUrl:t.scope&&/^https?:\/\//.test(t.scope)?t.scope:t.scope?`(selector: ${t.scope})`:"unknown":"unknown"}function Pi(e,t){return{schemaVersion:1,generatedAt:new Date().toISOString(),tool:{name:Ae,version:Ve()},results:e,delta:t}}function vo(e){return e==="critical"||e==="serious"?"error":e==="moderate"?"warning":"note"}function Ui(e,t){const a=(t?t.new:e.flatMap(c=>c.violations)).filter(c=>!c.needsReview),n=new Set,o=[];for(const c of a)n.has(c.ruleId)||(n.add(c.ruleId),o.push({id:c.ruleId,shortDescription:{text:c.description||c.ruleId},helpUri:c.helpUrl||void 0,defaultConfiguration:{level:vo(c.impact)},properties:{tags:[c.wcagCriterion,`wcag-${c.wcagLevel}`]}}));const i=Ue(e),r=a.map(c=>({ruleId:c.ruleId,level:vo(c.impact),message:{text:`${c.description}`+(c.target.failureSummary?`
1571
+
1572
+ ${c.target.failureSummary}`:"")},locations:[{physicalLocation:{artifactLocation:{uri:i&&/^https?:\/\//.test(i)?`${i}#${c.target.selector}`:c.target.selector?`selector://${c.target.selector}`:"unknown"},region:{snippet:{text:c.target.outerHTML}}},logicalLocations:[{name:c.componentId,kind:"module"}]}],properties:{componentId:c.componentId,wcagCriterion:c.wcagCriterion,wcagLevel:c.wcagLevel,pseudoState:c.currentState.pseudoState,theme:c.currentState.theme,direction:c.currentState.direction,breakpoint:c.currentState.breakpoint.id,detectedAt:c.detectedAt}}));return{$schema:"https://json.schemastore.org/sarif-2.1.0.json",version:"2.1.0",runs:[{tool:{driver:{name:Ae,version:Ve(),informationUri:"https://wcagcheckr.com",rules:o}},results:r}]}}function _i(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&apos;")}function _e(e){return _i(e).replace(/\n/g,"&#10;").replace(/\r/g,"&#13;")}function Fi(e,t){const a=!!t,n=d=>(a?d.violations.filter(u=>t.new.some(h=>h.matchKey===u.matchKey)):d.violations).filter(u=>!u.needsReview),o=e.reduce((d,u)=>d+Math.max(1,u.violations.length),0),i=e.reduce((d,u)=>d+n(u).length,0),r=e.reduce((d,u)=>d+u.durationMs,0)/1e3,c=[];c.push('<?xml version="1.0" encoding="UTF-8"?>');const l=`${Ae} — ${Ue(e)}`;c.push(`<testsuites name="${_e(l)}" tests="${o}" failures="${i}" time="${r.toFixed(3)}">`);for(const d of e){const u=`${d.state.pseudoState}_${d.state.theme}_${d.state.direction}_${d.state.breakpoint.id}`,h=`${d.componentId}::${u}`,m=n(d),f=Math.max(1,d.violations.length);if(c.push(` <testsuite name="${_e(h)}" tests="${f}" failures="${m.length}" time="${(d.durationMs/1e3).toFixed(3)}">`),d.violations.length===0)c.push(` <testcase classname="${_e(h)}" name="no-violations" time="${(d.durationMs/1e3).toFixed(3)}" />`);else for(const g of d.violations){const p=m.some(y=>y.matchKey===g.matchKey),b=`classname="${_e(h)}" name="${_e(g.ruleId)}" time="0"`;if(!p){c.push(` <testcase ${b} />`);continue}const w=`${g.impact} - ${g.description}`,k=`Selector: ${g.target.selector}
1573
+ WCAG: ${g.wcagCriterion} (${g.wcagLevel})
1574
+ State: ${u}
1575
+ `+(g.helpUrl?`Help: ${g.helpUrl}
1576
+ `:"")+(g.target.failureSummary?`
1577
+ ${g.target.failureSummary}`:"");c.push(` <testcase ${b}>`),c.push(` <failure message="${_e(w)}" type="${_e(g.ruleId)}">${_i(k)}</failure>`),c.push(" </testcase>")}c.push(" </testsuite>")}return c.push("</testsuites>"),c.join(`
1578
+ `)}function T(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}function nh(e,t,a){if(e.length===0&&t.length===0)return"";const n=new Map;for(const r of a)n.set(`${r.criterionId}::${r.pageUrl}`,r);const o=e.map((r,c)=>`
1579
+ <tr>
1580
+ <td>${c+1}</td>
1581
+ <td><span class="impact impact-${T(r.impact)}">${T(r.impact)}</span></td>
1582
+ <td><code>${T(r.criterionId)}</code></td>
1583
+ <td>${T(r.verdict.toUpperCase())}</td>
1584
+ <td>${T(r.reasoning)}</td>
1585
+ <td><code class="sel">${T(r.pageUrl)}</code></td>
1586
+ </tr>`).join(""),i=t.map((r,c)=>{const l=n.get(`${r.criterionId}::${r.pageUrl}`),d=(l==null?void 0:l.note)??"(note missing)",u=l!=null&&l.acknowledgedAt?new Date(l.acknowledgedAt).toLocaleString():"(timestamp missing)";return`
1587
+ <tr>
1588
+ <td>${c+1}</td>
1589
+ <td><code>${T(r.criterionId)}</code></td>
1590
+ <td>AI: ${T(r.verdict.toUpperCase())} → ✓ Human verified</td>
1591
+ <td>${T(d)}</td>
1592
+ <td>${T(u)}</td>
1593
+ <td><code class="sel">${T(r.pageUrl)}</code></td>
1594
+ </tr>`}).join("");return`
1595
+ <h2 id="walkthroughs">3a. AI walkthrough findings</h2>
1596
+ <p>The wcagcheckr extension runs interactive AI walkthroughs for criteria that aren't fully decidable from static DOM (Focus Order 2.4.3, No Keyboard Trap 2.1.2, Focus Visible 2.4.7, Meaningful Sequence 1.3.2, Non-text Contrast 1.4.11, Label in Name 2.5.3). FAIL and UNCERTAIN verdicts are listed below, separated into "still open" (blocking grade) and "human verified" (the auditor manually confirmed the criterion passes, with the note logged inline for chain-of-custody).</p>
1597
+ ${e.length===0?"<p><em>No open walkthrough findings — every AI walkthrough verdict is either PASS or has been human-acknowledged.</em></p>":`<h3 style="margin-top:12pt">Still open (${e.length})</h3>
1598
+ <table>
1599
+ <thead>
1600
+ <tr><th>#</th><th>Impact</th><th>Criterion</th><th>AI verdict</th><th>AI reasoning</th><th>Page</th></tr>
1601
+ </thead>
1602
+ <tbody>${o}</tbody>
1603
+ </table>`}
1604
+ ${t.length===0?"":`<h3 style="margin-top:12pt">Human verified (${t.length})</h3>
1605
+ <p style="font-size:10pt; color:#475569">Each row records an AI walkthrough that flagged a potential issue, followed by the human auditor's manual verification. The note is the verifier's explanation of how they confirmed the criterion passes; the timestamp is when the verification was recorded.</p>
1606
+ <table>
1607
+ <thead>
1608
+ <tr><th>#</th><th>Criterion</th><th>Chain</th><th>Verification note</th><th>Recorded at</th><th>Page</th></tr>
1609
+ </thead>
1610
+ <tbody>${i}</tbody>
1611
+ </table>`}`}function xn(e,t,a){var k,y,s,O,E;const n=((k=e[0])==null?void 0:k.componentId)??"unknown",o=Ue(e),i=a??e.reduce((v,D)=>v+D.durationMs,0),r=e.flatMap(v=>v.violations),c=new Map;for(const v of r){const D=`${v.ruleId}::${v.target.selector}`,S=`:${v.currentState.pseudoState} · ${v.currentState.theme} · ${v.currentState.direction}`,R=c.get(D);if(R){R._states.includes(S)||R._states.push(S);continue}c.set(D,{...v,_states:[S]})}const l=Array.from(c.values()),d={critical:[],serious:[],moderate:[],minor:[]};for(const v of l)(d[v.impact]??d.moderate).push(v);const u=new Set(((y=t==null?void 0:t.new)==null?void 0:y.map(v=>`${v.ruleId}::${v.target.selector}`))??[]),h=new Set(((s=t==null?void 0:t.persistent)==null?void 0:s.map(v=>`${v.ruleId}::${v.target.selector}`))??[]),m=new Set(((O=t==null?void 0:t.fixed)==null?void 0:O.map(v=>`${v.ruleId}::${v.target.selector}`))??[]);let f=0,g=0;for(const v of l){const D=`${v.ruleId}::${v.target.selector}`;h.has(D)?g++:u.has(D)&&f++}let p=0;for(const v of m)!h.has(v)&&!u.has(v)&&p++;const b=v=>{const D=u.has(`${v.ruleId}::${v.target.selector}`);return`
1612
+ <article class="violation impact-${T(v.impact)}${D?" is-new":""}">
1613
+ <header>
1614
+ <span class="rule">${T(v.ruleId)}</span>
1615
+ <span class="impact">${T(v.impact)}</span>
1616
+ <span class="wcag">${T(v.wcagCriterion)} ${T(v.wcagLevel)}</span>
1617
+ ${D?'<span class="new-badge">NEW vs baseline</span>':""}
1618
+ </header>
1619
+ <p class="desc">${T(v.description)}</p>
1620
+ <p class="meta"><strong>Selector:</strong> <code>${T(v.target.selector)}</code></p>
1621
+ <p class="meta"><strong>Found in state(s):</strong> ${T(v._states.join(", "))}</p>
1622
+ <pre class="snippet"><code>${T(v.target.outerHTML)}</code></pre>
1623
+ ${v.helpUrl?`<p class="help"><a href="${T(v.helpUrl)}">Reference: ${T(v.helpUrl)}</a></p>`:""}
1624
+ </article>`},w=(v,D)=>D.length===0?"":`
1625
+ <section class="impact-section impact-${T(v)}">
1626
+ <h2>${T(v[0].toUpperCase()+v.slice(1))} (${D.length})</h2>
1627
+ ${D.map(b).join("")}
1628
+ </section>`;return`<!doctype html>
1629
+ <html lang="en">
1630
+ <head>
1631
+ <meta charset="utf-8" />
1632
+ <title>Accessibility audit — ${T(n)}</title>
1633
+ <style>
1634
+ *, *::before, *::after { box-sizing: border-box; }
1635
+ body {
1636
+ font: 12pt/1.5 -apple-system, system-ui, "Segoe UI", sans-serif;
1637
+ color: #0f172a;
1638
+ max-width: 7.5in;
1639
+ margin: 0.5in auto;
1640
+ padding: 0 0.25in;
1641
+ }
1642
+ h1 { font-size: 22pt; margin: 0 0 6pt; }
1643
+ h2 { font-size: 14pt; margin: 18pt 0 8pt; padding-bottom: 4pt; border-bottom: 1px solid #cbd5e1; }
1644
+ header.report-header { margin-bottom: 18pt; }
1645
+ .summary { font-size: 11pt; color: #475569; margin-bottom: 12pt; }
1646
+ .summary span { margin-right: 12pt; }
1647
+ .violation {
1648
+ border-left: 4px solid #cbd5e1;
1649
+ background: #f8fafc;
1650
+ padding: 10pt 12pt;
1651
+ margin: 8pt 0;
1652
+ page-break-inside: avoid;
1653
+ }
1654
+ .violation.impact-critical { border-left-color: #dc2626; }
1655
+ .violation.impact-serious { border-left-color: #ea580c; }
1656
+ .violation.impact-moderate { border-left-color: #ca8a04; }
1657
+ .violation.impact-minor { border-left-color: #2563eb; }
1658
+ .violation.is-new { background: #fef2f2; }
1659
+ .violation .new-badge { font-size: 9pt; padding: 1pt 6pt; border-radius: 3pt; background: #dc2626; color: white; text-transform: uppercase; letter-spacing: 0.04em; }
1660
+ .violation header { display: flex; gap: 8pt; align-items: baseline; margin-bottom: 4pt; }
1661
+ .violation .rule { font-family: "SF Mono", Menlo, monospace; font-weight: 600; font-size: 11pt; }
1662
+ .violation .impact { font-size: 9pt; padding: 1pt 6pt; border-radius: 3pt; background: #e2e8f0; text-transform: uppercase; letter-spacing: 0.04em; }
1663
+ .violation .wcag { font-size: 10pt; color: #64748b; }
1664
+ .violation .desc { margin: 0 0 4pt; }
1665
+ .violation .meta { font-size: 10pt; color: #475569; margin: 2pt 0; }
1666
+ .violation code { font-family: "SF Mono", Menlo, monospace; font-size: 10pt; }
1667
+ .violation .snippet {
1668
+ background: #fff; border: 1px solid #cbd5e1; border-radius: 3pt;
1669
+ padding: 6pt 8pt; overflow-x: auto;
1670
+ font-family: "SF Mono", Menlo, monospace; font-size: 10pt; white-space: pre-wrap;
1671
+ }
1672
+ .violation .help { font-size: 10pt; }
1673
+ footer { margin-top: 24pt; padding-top: 8pt; border-top: 1px solid #cbd5e1; font-size: 9pt; color: #64748b; }
1674
+ @media print {
1675
+ body { margin: 0; max-width: none; }
1676
+ h2 { page-break-after: avoid; }
1677
+ .violation { break-inside: avoid; }
1678
+ }
1679
+ </style>
1680
+ </head>
1681
+ <body>
1682
+ <header class="report-header">
1683
+ <h1>Accessibility audit report</h1>
1684
+ <p class="summary" style="font-size: 11pt; word-break: break-all;">
1685
+ <strong>URL audited:</strong> <code>${T(o)}</code>
1686
+ </p>
1687
+ <p class="summary">
1688
+ <span><strong>Component:</strong> <code>${T(n)}</code></span>
1689
+ <span><strong>Date:</strong> ${T(new Date().toLocaleString())}</span>
1690
+ <span><strong>axe-core:</strong> ${T(((E=e[0])==null?void 0:E.axeVersion)??"n/a")}</span>
1691
+ </p>
1692
+ <p class="summary">
1693
+ <span><strong>States audited:</strong> ${e.length}</span>
1694
+ <span><strong>Unique violations:</strong> ${l.length}</span>
1695
+ <span><strong>Total violation instances:</strong> ${r.length}</span>
1696
+ <span><strong>Audit duration:</strong> ${(i/1e3).toFixed(1)}s</span>
1697
+ </p>
1698
+ ${t!=null&&t.matrixMismatchWarning?`
1699
+ <p class="summary" style="background: #fef3c7; border: 1px solid #f59e0b; padding: 8pt 10pt; border-radius: 3pt;">
1700
+ ⚠ ${T(t.matrixMismatchWarning)}
1701
+ </p>
1702
+ `:""}
1703
+ ${t&&t.baselineSnapshotMeta?`
1704
+ <p class="summary"><strong>Unique-violation deltas (rule + selector pairs):</strong></p>
1705
+ <p class="summary">
1706
+ <span style="color: #dc2626;"><strong>NEW:</strong> ${f}</span>
1707
+ <span><strong>Persistent:</strong> ${g}</span>
1708
+ <span style="color: #15803d;"><strong>Fixed since baseline:</strong> ${p}</span>
1709
+ </p>
1710
+ <p class="summary" style="font-size: 9pt; color: #64748b;">
1711
+ Per-state-instance deltas (each state×rule×selector combination): ${t.newCount} new instances · ${t.persistentCount} persistent · ${t.fixedCount} fixed. The unique-violation counts above are usually what you want to track; instance counts inflate when the matrix size changes.
1712
+ </p>
1713
+ `:t?'<p class="summary"><span style="color: #64748b;">No baseline accepted yet — every violation is unrated.</span></p>':""}
1714
+ <p style="font-size: 10pt; color: #64748b;">
1715
+ Tip: use your browser's print dialog (Ctrl+P / Cmd+P) → "Save as PDF" to produce a PDF file.
1716
+ </p>
1717
+ </header>
1718
+ ${w("critical",d.critical)}
1719
+ ${w("serious",d.serious)}
1720
+ ${w("moderate",d.moderate)}
1721
+ ${w("minor",d.minor)}
1722
+ <footer>
1723
+ Generated by ${Ae} v${Ve()}. Full audit — all detected violations included, deduped across states. Items flagged "NEW vs baseline" were not present in the most-recent accepted baseline.
1724
+ </footer>
1725
+ </body>
1726
+ </html>`}const Wi=[{ref:"1.1.1",title:"Non-text Content",level:"A",rules:["image-alt","input-image-alt","area-alt","object-alt","svg-img-alt"]},{ref:"1.2.1",title:"Audio-only and Video-only (Prerecorded)",level:"A",rules:["wcc-audio-needs-transcript","wcc-clean::1.2.1"]},{ref:"1.2.2",title:"Captions (Prerecorded)",level:"A",rules:["video-caption","wcc-video-no-captions","wcc-clean::1.2.2"]},{ref:"1.2.3",title:"Audio Description or Media Alternative (Prerecorded)",level:"A",rules:["wcc-video-no-audio-desc-or-alt","wcc-clean::1.2.3"]},{ref:"1.2.4",title:"Captions (Live)",level:"AA",rules:["wcc-clean::1.2.4"]},{ref:"1.2.5",title:"Audio Description (Prerecorded)",level:"AA",rules:["wcc-video-no-descriptions","wcc-clean::1.2.5"]},{ref:"1.3.1",title:"Info and Relationships",level:"A",rules:["heading-order","list","listitem","definition-list","dlitem","th-has-data-cells","td-headers-attr","aria-required-children","aria-required-parent","label","aria-pattern-tablist-no-tabs","aria-pattern-tab-no-controls","aria-pattern-tab-broken-controls","aria-pattern-tab-wrong-target-role","aria-pattern-combobox-no-popup","aria-pattern-combobox-broken-popup","aria-pattern-combobox-wrong-popup-role"]},{ref:"1.3.2",title:"Meaningful Sequence",level:"A",rules:[]},{ref:"1.3.3",title:"Sensory Characteristics",level:"A",rules:["wcc-sensory-only-directive","wcc-clean::1.3.3"]},{ref:"1.3.4",title:"Orientation",level:"AA",rules:["css-orientation-lock","wcc-orientation-locked-content","wcc-clean::1.3.4"]},{ref:"1.3.5",title:"Identify Input Purpose",level:"AA",rules:["autocomplete-valid"]},{ref:"1.4.1",title:"Use of Color",level:"A",rules:["link-in-text-block"]},{ref:"1.4.2",title:"Audio Control",level:"A",rules:["no-autoplay-audio","wcc-autoplay-no-controls","wcc-clean::1.4.2"]},{ref:"1.4.3",title:"Contrast (Minimum)",level:"AA",rules:["color-contrast"]},{ref:"1.4.4",title:"Resize Text",level:"AA",rules:["meta-viewport","wcc-viewport-locks-zoom","wcc-clean::1.4.4"]},{ref:"1.4.5",title:"Images of Text",level:"AA",rules:["wcc-image-of-text-candidate","wcc-svg-text-as-content","wcc-clean::1.4.5"]},{ref:"1.4.10",title:"Reflow",level:"AA",rules:["wcc-reflow-horizontal-scroll","wcc-reflow-clean"]},{ref:"1.4.11",title:"Non-text Contrast",level:"AA",rules:[]},{ref:"1.4.12",title:"Text Spacing",level:"AA",rules:["wcc-text-spacing-clip-risk","wcc-clean::1.4.12"]},{ref:"1.4.13",title:"Content on Hover or Focus",level:"AA",rules:["wcc-title-tooltip-no-persistence","wcc-clean::1.4.13"]},{ref:"2.1.1",title:"Keyboard",level:"A",rules:["nested-interactive","frame-focusable-content"]},{ref:"2.1.2",title:"No Keyboard Trap",level:"A",rules:[]},{ref:"2.1.4",title:"Character Key Shortcuts",level:"A",rules:["wcc-inline-key-handler","wcc-clean::2.1.4"]},{ref:"2.2.1",title:"Timing Adjustable",level:"A",rules:["meta-refresh","wcc-meta-refresh","wcc-timer-mutates-content","wcc-clean::2.2.1"]},{ref:"2.2.2",title:"Pause, Stop, Hide",level:"A",rules:["blink","marquee","wcc-marquee-blink","wcc-autoplay-no-controls","wcc-long-infinite-animation","wcc-clean::2.2.2"]},{ref:"2.3.1",title:"Three Flashes or Below Threshold",level:"A",rules:["wcc-flash-risk","wcc-clean::2.3.1"]},{ref:"2.4.1",title:"Bypass Blocks",level:"A",rules:["bypass"]},{ref:"2.4.2",title:"Page Titled",level:"A",rules:["document-title"]},{ref:"2.4.3",title:"Focus Order",level:"A",rules:["tabindex"]},{ref:"2.4.4",title:"Link Purpose (In Context)",level:"A",rules:["link-name"]},{ref:"2.4.5",title:"Multiple Ways",level:"AA",rules:["wcc-only-one-way-to-find","wcc-clean::2.4.5"]},{ref:"2.4.6",title:"Headings and Labels",level:"AA",rules:["empty-heading","empty-table-header"]},{ref:"2.4.7",title:"Focus Visible",level:"AA",rules:[]},{ref:"2.4.11",title:"Focus Not Obscured (Minimum)",level:"AA",rules:["wcc-sticky-may-obscure-focus","wcc-clean::2.4.11"]},{ref:"2.5.1",title:"Pointer Gestures",level:"A",rules:["wcc-gesture-no-alternative","wcc-clean::2.5.1"]},{ref:"2.5.2",title:"Pointer Cancellation",level:"A",rules:["wcc-down-event-fires-action","wcc-clean::2.5.2"]},{ref:"2.5.3",title:"Label in Name",level:"A",rules:["label-content-name-mismatch"]},{ref:"2.5.4",title:"Motion Actuation",level:"A",rules:["wcc-motion-actuation-no-alt","wcc-clean::2.5.4"]},{ref:"2.5.7",title:"Dragging Movements",level:"AA",rules:["wcc-drag-no-alternative","wcc-clean::2.5.7"]},{ref:"2.5.8",title:"Target Size (Minimum)",level:"AA",rules:["target-size"]},{ref:"3.1.1",title:"Language of Page",level:"A",rules:["html-has-lang","html-lang-valid"]},{ref:"3.1.2",title:"Language of Parts",level:"AA",rules:["valid-lang"]},{ref:"3.2.1",title:"On Focus",level:"A",rules:["wcc-onfocus-context-change","wcc-clean::3.2.1"]},{ref:"3.2.2",title:"On Input",level:"A",rules:["wcc-oninput-context-change","wcc-clean::3.2.2"]},{ref:"3.2.3",title:"Consistent Navigation",level:"AA",rules:["wcc-consistent-navigation"]},{ref:"3.2.4",title:"Consistent Identification",level:"AA",rules:["wcc-consistent-identification"]},{ref:"3.2.6",title:"Consistent Help",level:"A",rules:["wcc-consistent-help"]},{ref:"3.3.1",title:"Error Identification",level:"A",rules:["wcc-form-no-aria-error-pattern","wcc-clean::3.3.1"]},{ref:"3.3.2",title:"Labels or Instructions",level:"A",rules:["label","form-field-multiple-labels","select-name"]},{ref:"3.3.3",title:"Error Suggestion",level:"AA",rules:["wcc-form-no-error-suggestion","wcc-clean::3.3.3"]},{ref:"3.3.4",title:"Error Prevention (Legal, Financial, Data)",level:"AA",rules:["wcc-commit-form-no-review-step","wcc-clean::3.3.4"]},{ref:"3.3.7",title:"Redundant Entry",level:"A",rules:["wcc-redundant-entry","wcc-clean::3.3.7"]},{ref:"3.3.8",title:"Accessible Authentication (Minimum)",level:"AA",rules:["wcc-cognitive-auth-challenge","wcc-clean::3.3.8"]},{ref:"4.1.1",title:"Parsing",level:"A",rules:["duplicate-id","duplicate-id-aria","duplicate-id-active","wcc-clean::4.1.1"]},{ref:"4.1.2",title:"Name, Role, Value",level:"A",rules:["button-name","aria-required-attr","aria-allowed-attr","aria-roles","aria-valid-attr","aria-valid-attr-value","aria-input-field-name","aria-toggle-field-name","select-name","input-button-name","aria-pattern-dialog-no-name","aria-pattern-dialog-no-modal-attr","aria-pattern-tabs-no-selected","aria-pattern-tabs-multiple-selected","aria-pattern-combobox-no-expanded","aria-pattern-combobox-invalid-expanded","aria-pattern-combobox-broken-activedescendant"]},{ref:"4.1.3",title:"Status Messages",level:"AA",rules:["wcc-no-live-regions","wcc-clean::4.1.3"]}];function qi(e,t,a,n){const o=n==null?void 0:n.find(l=>l.criterionId===e.ref);if(e.rules.length===0)return o?o.verdict==="pass"?{conformance:"Supports",hits:[]}:o.verdict==="fail"?{conformance:"Partially Supports",hits:["AI walkthrough verdict: fail"]}:{conformance:"Not Evaluated",hits:[]}:{conformance:"Not Applicable",hits:[]};const i=e.rules.filter(l=>t.has(l));if(i.length>0)return{conformance:"Partially Supports",hits:i};if((o==null?void 0:o.verdict)==="fail")return{conformance:"Partially Supports",hits:["AI walkthrough verdict: fail"]};const r=e.rules.some(l=>a.has(l)),c=(o==null?void 0:o.verdict)==="pass";return r||c?{conformance:"Supports",hits:[]}:{conformance:"Not Evaluated",hits:[]}}function Gi(e,t,a){var u,h;const n=((u=e[0])==null?void 0:u.componentId)??"unknown",o=Ue(e),i=e.flatMap(m=>m.violations),r=new Set(i.map(m=>m.ruleId)),c=new Set(r);for(const m of e){const f=m.axeRulesEvaluated;if(f){for(const g of f.passed)c.add(g.ruleId);for(const g of f.inapplicable)c.add(g.ruleId);for(const g of f.incomplete)c.add(g.ruleId)}}const l=m=>{const{conformance:f,hits:g}=qi(m,r,c,a);return`
1727
+ <tr class="${f==="Supports"?"supports":f==="Partially Supports"?"partial":f==="Not Evaluated"?"not-evaluated":"na"}">
1728
+ <td class="ref">${T(m.ref)}</td>
1729
+ <td class="title">${T(m.title)} <span class="level">(${m.level})</span></td>
1730
+ <td class="conf">${f}</td>
1731
+ <td class="notes">${g.length>0?`Auto-detected violations of: ${g.map(b=>`<code>${T(b)}</code>`).join(", ")}. Manual review recommended.`:f==="Not Applicable"?"No automated rule maps to this criterion. Manual evaluation required.":"No automated violations detected. Manual confirmation recommended for full claim."}</td>
1732
+ </tr>`},d=m=>{const f=Wi.filter(g=>g.level===m).map(l).join("");return`
1733
+ <h2>WCAG 2.1 Level ${m}</h2>
1734
+ <table>
1735
+ <thead>
1736
+ <tr><th>Criterion</th><th>Title</th><th>Conformance</th><th>Notes</th></tr>
1737
+ </thead>
1738
+ <tbody>${f}</tbody>
1739
+ </table>`};return`<!doctype html>
1740
+ <html lang="en">
1741
+ <head>
1742
+ <meta charset="utf-8" />
1743
+ <title>VPAT — ${T(n)}</title>
1744
+ <style>
1745
+ body { font: 11pt/1.5 -apple-system, system-ui, sans-serif; color: #0f172a; max-width: 8in; margin: 0.5in auto; padding: 0 0.25in; }
1746
+ h1 { font-size: 18pt; }
1747
+ h2 { font-size: 13pt; margin-top: 20pt; padding-bottom: 4pt; border-bottom: 1px solid #cbd5e1; }
1748
+ .meta { font-size: 10pt; color: #475569; }
1749
+ .preamble { background: #fef3c7; border: 1px solid #f59e0b; padding: 8pt 10pt; border-radius: 3pt; font-size: 10pt; margin: 12pt 0; }
1750
+ table { width: 100%; border-collapse: collapse; margin: 6pt 0 12pt; font-size: 10pt; }
1751
+ th, td { border: 1px solid #cbd5e1; padding: 5pt 7pt; text-align: left; vertical-align: top; }
1752
+ th { background: #f1f5f9; }
1753
+ td.ref { font-family: "SF Mono", Menlo, monospace; white-space: nowrap; }
1754
+ td.conf { font-weight: 600; }
1755
+ tr.supports td.conf { color: #15803d; }
1756
+ tr.partial td.conf { color: #b45309; }
1757
+ tr.na td.conf { color: #64748b; }
1758
+ tr.not-evaluated td.conf { color: #b45309; background: #fef3c7; }
1759
+ .level { color: #94a3b8; font-weight: 400; }
1760
+ code { font-family: "SF Mono", Menlo, monospace; font-size: 9pt; background: #f1f5f9; padding: 0 3pt; }
1761
+ @media print { body { margin: 0; } table, tr { page-break-inside: avoid; } }
1762
+ </style>
1763
+ </head>
1764
+ <body>
1765
+ <h1>Voluntary Product Accessibility Template (VPAT 2.5)</h1>
1766
+ <p class="meta"><strong>Product:</strong> ${T(n)}</p>
1767
+ <p class="meta" style="word-break: break-all;"><strong>URL evaluated:</strong> <code>${T(o)}</code></p>
1768
+ <p class="meta"><strong>Date:</strong> ${T(new Date().toLocaleString())}</p>
1769
+ <p class="meta"><strong>Evaluation method:</strong> Automated audit via wcagcheckr (axe-core ${T(((h=e[0])==null?void 0:h.axeVersion)??"n/a")}) across ${e.length} configured states.</p>
1770
+ <div class="preamble">
1771
+ <strong>Auto-generated draft.</strong> This VPAT was produced automatically from automated test
1772
+ results. Conformance values reflect what automated testing can detect — they are not a complete
1773
+ accessibility claim. Manual review by qualified accessibility evaluators is required before
1774
+ using this document for procurement, contracting, or legal purposes. Criteria with no
1775
+ automated rule are marked "Not Applicable" until manually evaluated.
1776
+ </div>
1777
+ ${d("A")}
1778
+ ${d("AA")}
1779
+ <footer style="margin-top: 24pt; padding-top: 8pt; border-top: 1px solid #cbd5e1; font-size: 9pt; color: #64748b;">
1780
+ Generated by ${Ae} v${Ve()}. Conformance assessed against all violations detected during the audit (not delta-filtered).
1781
+ </footer>
1782
+ </body>
1783
+ </html>`}function oh(e){const t=[],a=e.target.outerHTML;if(e.ruleId==="image-alt"||e.ruleId==="input-image-alt"||e.ruleId==="area-alt"||e.ruleId==="svg-img-alt"||e.ruleId==="object-alt"){const n=/\bsrc\s*=\s*"([^"]+)"|\bsrc\s*=\s*'([^']+)'/i.exec(a);n&&t.push({label:"Image source",value:`\`${n[1]??n[2]}\` — if you have vision capability, view this image and propose alt text. Use \`alt=""\` if it's decorative.`});const o=/<title>([^<]+)<\/title>/i.exec(a);o&&t.push({label:"Inline SVG <title>",value:o[1]})}if(e.ruleId==="button-name"||e.ruleId==="empty-button"||e.ruleId==="input-button-name"){const n=/\baria-label\s*=\s*"([^"]+)"/i.exec(a);n&&t.push({label:"Existing aria-label",value:n[1]});const o=/<svg[^>]*>[\s\S]*?<title>([^<]+)<\/title>/i.exec(a);o&&t.push({label:"SVG <title> inside",value:o[1]});const i=/<(?:i|svg|span)[^>]*class\s*=\s*"([^"]*(?:icon|fa-|material-icon)[^"]*)"/i.exec(a);i&&t.push({label:"Icon class hint",value:`\`${i[1]}\` — name suggests intent`})}if(e.ruleId==="link-name"||e.ruleId==="empty-link"){const n=/\bhref\s*=\s*"([^"]+)"|\bhref\s*=\s*'([^']+)'/i.exec(a);n&&t.push({label:"Link href",value:`\`${n[1]??n[2]}\` — destination may suggest the right link text`});const o=/\baria-label\s*=\s*"([^"]+)"/i.exec(a);o&&t.push({label:"Existing aria-label",value:o[1]})}if(e.ruleId==="label"||e.ruleId==="select-name"||e.ruleId==="aria-input-field-name"){const n=/\bname\s*=\s*"([^"]+)"|\bname\s*=\s*'([^']+)'/i.exec(a);n&&t.push({label:"Field name attribute",value:`\`${n[1]??n[2]}\` — often hints at the intended label`});const o=/\bplaceholder\s*=\s*"([^"]+)"|\bplaceholder\s*=\s*'([^']+)'/i.exec(a);o&&t.push({label:"Placeholder text (visible)",value:o[1]??o[2]});const i=/\btype\s*=\s*"([^"]+)"|\btype\s*=\s*'([^']+)'/i.exec(a);i&&t.push({label:"Input type",value:`\`${i[1]??i[2]}\``});const r=/\bautocomplete\s*=\s*"([^"]+)"|\bautocomplete\s*=\s*'([^']+)'/i.exec(a);r&&t.push({label:"Autocomplete",value:r[1]??r[2]})}return t}function ih(e,t){var r;const a=new Set((t??[]).filter(c=>c.verdict==="pass"||c.verdict==="fail").map(c=>`${c.pageUrl}::${c.ruleId}::${c.selector}`)),n=new Map;for(const c of t??[])c.verdict==="uncertain"&&n.set(`${c.pageUrl}::${c.ruleId}::${c.selector}`,c.reasoning);const o=new Set,i=[];for(const c of e){const l=c.pageUrl??c.scope,d=((r=c.axeRulesEvaluated)==null?void 0:r.incomplete)??[];for(const u of d)for(const h of u.elements??[]){const m=`${l}::${u.ruleId}::${h.selector}`;o.has(m)||a.has(m)||(o.add(m),i.push({pageUrl:l,ruleId:u.ruleId,wcagCriterion:u.wcagCriterion,selector:h.selector,failureSummary:h.failureSummary,styles:h.styles,aiAttemptReasoning:n.get(m)}))}}return i}function Vi(e,t,a,n,o,i,r,c,l){var me,he,ae,x,U,z,K,ie,re,ve,pe;const d=new Set(o??[]),u=new Set(l??[]),h=((me=e[0])==null?void 0:me.componentId)??"unknown",m=Ue(e),f=!!(t&&t.baselineSnapshotMeta),g=e.flatMap(A=>A.violations),p=u.size>0?g.filter(A=>u.has(A.matchKey)):[],b=A=>d.has(`violation::${A}`),w=g.filter(A=>!u.has(A.matchKey)&&!b(A.matchKey)),k=new Map;for(const A of w){const B=A.ruleId.startsWith("ai-")?`${A.ruleId}::${A.target.selector}::${(A.target.outerHTML??"").slice(0,200)}`:xe(A.ruleId,A.target.selector),L=`${A.currentState.pseudoState} · ${A.currentState.theme} · ${A.currentState.direction} · ${A.currentState.breakpoint.id}`,G=k.get(B);if(G){G._states.includes(L)||G._states.push(L),G._instanceSelectors.includes(A.target.selector)||G._instanceSelectors.push(A.target.selector);continue}k.set(B,{...A,_states:[L],_instanceSelectors:[A.target.selector]})}const y=Array.from(k.values()).sort((A,B)=>{const L={critical:0,serious:1,moderate:2,minor:3};return(L[A.impact]??99)-(L[B.impact]??99)}),s=[];s.push("# Accessibility Fix Request"),s.push(""),s.push(`You are a senior accessibility engineer pairing with the user on their codebase. The component below has ${y.length} unique WCAG violation${y.length===1?"":"s"} detected by an automated audit running axe-core ${((he=e[0])==null?void 0:he.axeVersion)??"4.x"} across ${e.length} state combinations (hover, focus, focus-visible, active, disabled × dark / forced-colors / RTL / breakpoints). Your task: locate each violation in the codebase and apply the canonical fix — carefully, atomically, and asking before guessing.`),s.push(""),s.push("## ⚠ Work contract — read before doing anything else"),s.push(""),s.push(`**You will receive ${y.length} numbered finding${y.length===1?"":"s"} below.** Each one needs an outcome BEFORE you report completion. Do not stop early. Do not batch by impact level. Do not declare done without addressing every numbered item.`),s.push(""),s.push("**For each numbered finding (1 through "+y.length+"), produce one of:**"),s.push(""),s.push("- ✅ **Applied** — the canonical fix from this prompt was applied to the source. Cite the file + line."),s.push("- ⚠ **Blocked** — explicitly state WHY you cannot apply the fix (missing info, ambiguous user content, dependency change required, etc.). One sentence per blocked finding."),s.push("- ❓ **Needs user decision** — you found options but need the user to pick. List the options inline."),s.push(""),s.push(`**The single answer you cannot use: "skipped" or "didn't get to it."** If you ran out of context or time, stop and tell the user — do not silently omit items.`),s.push(""),s.push("**Required final response format** — at the end of your work, produce a completion table:"),s.push(""),s.push("```"),s.push("| # | Rule ID | Status | Notes |"),s.push("|---|---------|--------|-------|"),s.push("| 1 | <rule> | ✅ / ⚠ / ❓ | <file:line OR reason> |"),s.push("| 2 | ... | ... | ... |"),s.push(`| ${y.length} | ... | ... | ... |`),s.push("```"),s.push(""),s.push("**Every row must be present.** If status is ⚠ Blocked or ❓ Needs decision, the Notes column explains specifically what the blocker is so the user can address it on the next pass. The point is that ONE pass through this prompt should produce either a fix or a clear next action for every finding — not a partial sweep that leaves the user re-running the loop."),s.push(""),s.push("---"),s.push("");const O=a&&a.length>0?{runs:a,workflows:Ee}:void 0,E=c==null?void 0:c.map(A=>({criterionId:A.criterionId,ruleId:`ai-interactive::${A.criterionId}`,pageUrl:A.pageUrl,verdict:A.verdict,reasoning:A.reasoning})),v=Se(g,O,e,void 0,E);if(s.push("## Context"),s.push(""),s.push(`- **Component / scope:** \`${h}\``),s.push(`- **Audited URL:** ${m}`),s.push(`- **WCAG target:** WCAG ${Le} ${fn} (this audit's goal; the receiving fix should bring the page into compliance with this level)`),s.push("- **Source filter:** Full audit — every detected violation"),s.push(`- **Compliance posture:** Grade **${v.letter}** · **${v.risk.toUpperCase()} risk** (${v.totals.critical} critical · ${v.totals.serious} serious · ${v.totals.moderate} moderate · ${v.totals.minor} minor). ${v.risk==="critical"||v.risk==="high"?"Treat as triage priority — these violations represent meaningful lawsuit exposure under ADA Title III, EAA, and EN 301 549.":v.risk==="moderate"?"Should be addressed in the current sprint cycle; not lawsuit-emergency but real exposure.":"Lower exposure; still worth fixing but not blocking other work."}`),v.coverage){const A=v.coverage.untestedCriteria.length;A>0?s.push(`- **WCAG ${v.coverage.targetVersion} ${v.coverage.targetLevel} coverage:** ${v.coverage.evaluated} of ${v.coverage.totalApplicable} criteria evaluated. **Cannot claim ${v.coverage.targetLevel} conformance** until ${A} untested criteria are evaluated via Guided Tests (Pass / Fail / N/A). Untested: ${v.coverage.untestedCriteria.join(", ")}.`):s.push(`- **WCAG ${v.coverage.targetVersion} ${v.coverage.targetLevel} coverage:** ${v.coverage.evaluated} of ${v.coverage.totalApplicable} criteria evaluated. Full coverage — defensible conformance claim possible once all violations below are fixed.`)}f&&t&&s.push(`- **Trend vs your saved baseline:** ${t.newCount} new · ${t.persistentCount} carried over · ${t.fixedCount} resolved. The fix list below is the FULL current violation set, not just the delta.`),s.push(""),s.push("## Before you start (safety)"),s.push(""),s.push("Do these in order before making any code changes:"),s.push(""),s.push("1. **Verify a clean git working tree** (`git status`). If there are uncommitted changes, ask the user whether to commit, stash, or proceed."),s.push("2. **Switch to a feature branch** dedicated to this work — e.g., `a11y/fix-component-name`. Don't commit directly to `main` / `master` / `develop`."),s.push("3. **Plan one commit per fix.** Each violation gets its own atomic commit so any individual change can be reverted without untangling the rest. Commit messages should follow the pattern: `a11y: fix <ruleId> on <selector or component>`."),s.push("4. **Show diffs before applying** when your tooling supports it (Cursor / Copilot Chat agents). Never silently overwrite files."),s.push("5. **Do not bump dependencies** to resolve accessibility issues — fixes should be code changes, not package upgrades."),s.push(""),s.push("## Ask me these questions first"),s.push(""),s.push("Before applying any fix, ask the user the questions below. Don't proceed until you have answers — guessing here produces fixes that don't match the codebase's conventions and will be rejected on review."),s.push(""),s.push("1. **Framework + style approach?** React / Vue / Svelte / Angular / Solid / vanilla — and CSS Modules / Tailwind / styled-components / Emotion / scoped `<style>` / global CSS / design-tokens system. This determines *how* every fix is written."),s.push("2. **Existing design tokens or raw values?** If the codebase uses `var(--color-text-on-surface)` or a `tokens.ts` / `theme.json`, fixes must use tokens — never introduce new hex codes. Where can you read the available tokens?"),s.push('3. **Existing utility classes or component library?** e.g., does `.text-slate-700` or `<Button variant="primary">` already exist? Use what\'s there before inventing new selectors or components.'),s.push("4. **Files or directories that are off-limits?** Generated code, vendored libraries, third-party widgets, lockfiles — confirm what you must NOT touch."),s.push("5. **For color-contrast fixes: prefer adjusting foreground or background?** Brand color preservation is usually a stakeholder call, not a developer call."),s.push("6. **How should I verify fixes are done?** Re-run this auditor (preferred), run the existing test suite, manual screen-reader pass, or all three?"),s.push(""),s.push("## Decision rules (when ambiguity surfaces during fixes)"),s.push(""),s.push('- **Native HTML beats ARIA.** First rule of ARIA: don\'t use ARIA when a native element works. Prefer `<button>` over `<div role="button">`, `<nav>` over `<div role="navigation">`, etc.'),s.push("- **State-specific failures get state-specific fixes.** A `:hover`-only contrast failure goes in the `:hover` rule, not the default selector."),s.push("- **Where multiple valid fixes exist, surface options — don't pick silently.** E.g., adding an accessible name can be done via visible text, `aria-label`, or `aria-labelledby` referencing existing copy. Present the options with tradeoffs and let the user choose."),s.push("- **When the fix would alter visible design** (color shifts, layout changes, content additions), pause and confirm with the user before applying."),s.push(""),s.push("## ⛔ Do not dismiss findings without empirical verification"),s.push(""),s.push('**Default stance: every finding in this report is real and needs a fix.** Your job is to apply fixes, not to triage which ones "probably aren\'t real." That triage already happened — the auditor ran on a real browser, captured real DOM, real `getBoundingClientRect()` coordinates, real WCAG rule failures from axe-core. You are downstream of all that.'),s.push(""),s.push('Some categories (reading-order, tab-order) are explicitly heuristic and DO have legitimate false-positive cases. Those cases are listed inline in each section. **But "this looks like it might be a false positive based on the pattern" is not verification.** Pattern-matching from the report alone is inference, not evidence.'),s.push(""),s.push("**To dismiss any finding as a false positive, you must do all of the following:**"),s.push(""),s.push("1. **Load the deployed page in a real browser** at the viewport listed in the finding's capture-state header (e.g., 1280×800 LTR for the position-based analyses)."),s.push("2. **Inspect the flagged elements directly** — DevTools, real `getBoundingClientRect()` coordinates, real DOM source order."),s.push("3. **Compare your empirical observation to the auditor's claim.** Does the visual layout actually contradict the DOM order, or not?"),s.push("4. **If you conclude false positive, document why** — include the empirical evidence (coordinates, screenshots, DOM excerpt) so the user can audit your reasoning."),s.push(`5. **Even after empirical verification, ASK THE USER before dismissing.** They have context you don't (e.g., "yes that's an intentional skip-link / multi-column layout / RTL-only behavior").`),s.push(""),s.push("**What does NOT count as verification:**"),s.push(""),s.push("- Looking at the CSS file and not seeing `flex-direction: row-reverse` (the layout could be from `order:`, `grid-area`, absolute positioning, or RTL emulation)"),s.push('- Pattern-matching the finding shape ("this looks like a perfect arithmetic flip therefore it must be an RTL artifact")'),s.push(`- Reading the auditor's false-positive list and concluding your case "looks like one of those"`),s.push(`- Deciding it's "probably fine" because the structure looks reasonable on a quick read`),s.push(""),s.push("**If you cannot run a real browser** (e.g., environment doesn't allow headless Chromium), then you cannot verify, which means you cannot dismiss. In that case: surface the finding to the user, propose the fix, and let them decide."),s.push(""),s.push("**Skipping a finding without verification is worse than fixing one that turns out to be a false positive.** Over-fix is reversible (delete the commit). Under-fix is silent — the user thinks they're compliant when they aren't."),s.push(""),s.push("**When you DO verify a finding is a false positive** (per the empirical rules above), the right action is to recommend the user click **dismiss** on that finding in the auditor's Activity tab. The auditor records the dismissal with your reason, suppresses the finding from displayed counts, the Matrix banner, and future AI prompts. **Don't modify code to silence a verified false positive** — that produces brittle workarounds and pollutes the codebase with comments explaining heuristic quirks. Dismissals persist per-URL and survive re-audits cleanly."),s.push(""),s.push("## Content-decision rules (alt text, button labels, link text)"),s.push(""),s.push("Some violations require copy that the audit can't generate. Don't invent copy out of nowhere, but DON'T just punt to the user either — use the capabilities you have:"),s.push(""),s.push('1. **For images missing alt text** (`image-alt`, `input-image-alt`, `area-alt`, `svg-img-alt`): if you have vision capability, **fetch or view the image** at the `src` URL listed in the violation and PROPOSE descriptive alt text based on what the image actually contains. Mark obviously-decorative images (icons inside buttons that already have text labels, dividers, background flourishes) for `alt=""`.'),s.push('2. **For unlabeled buttons / links** (`button-name`, `link-name`, `empty-button`, `empty-link`): look at the surrounding code context — nearby text, the button\'s icon (often a known SVG name like "close" / "search" / "menu"), the route the link points at — and PROPOSE an accessible name. Show your inference reasoning.'),s.push("3. **For unlabeled form fields** (`label`, `select-name`, `aria-input-field-name`): inspect the field's `name`, `placeholder`, `id`, and any nearby visible text in the parent container. PROPOSE a label."),s.push("4. **For ambiguous content** (alt text where you genuinely can't tell what an image shows; labels where there's no surrounding context), ask the user — don't guess."),s.push("5. **Always show your proposed copy before applying** — let the user approve, edit, or reject."),s.push(""),s.push("## ⚡ Verify with Playwright before asking the user"),s.push(""),s.push(`Many WCAG criteria that look like "needs human verification" are actually scriptable — you can write a short Playwright test, run it, observe the result, and resolve the question without bothering the user. **Default to this path.** Only ask the user when Playwright can't reach the answer (subjective copy decisions, content judgment, business-context questions).`),s.push(""),s.push("### Step 1: Check if Playwright is available"),s.push(""),s.push('Look in the project root for `playwright.config.ts` / `playwright.config.js` / a `tests/` or `e2e/` directory using `@playwright/test`. If yes — use it directly. If no — ask the user once: "Want me to add Playwright (`npm install -D @playwright/test && npx playwright install`) so I can verify a few WCAG criteria automatically?" If they decline, fall back to asking per-item.'),s.push(""),s.push("### Step 2: Run targeted smoke tests instead of asking"),s.push(""),s.push("Here are the manual-checklist items below that Playwright CAN verify on the CURRENT PAGE (no multi-page setup needed). For each, write the test, run it, and only escalate to the user if the test result is ambiguous:"),s.push(""),s.push("**Scope disambiguation:** the criteria below are all **single-page** — they describe behavior of the current page. They are NOT multi-page concerns. Cross-page criteria (3.2.3 Consistent Navigation, 3.2.4 Consistent Identification, 3.2.6 Consistent Help) require comparing multiple pages and are handled by wcagcheckr's Site Crawl flow, NOT by Playwright tests here. Don't skip 3.2.1 / 3.2.2 thinking they're multi-page — they're single-page and Playwright-testable."),s.push(""),s.push("| Criterion | Scope | Playwright test |"),s.push("|---|---|---|"),s.push('| **1.4.4 Resize Text (AA)** | single-page | `await page.evaluate(() => document.body.style.zoom = "200%"); await page.screenshot(...); assert no horizontal scroll on critical content blocks` |'),s.push("| **1.4.10 Reflow (AA)** | single-page | `await page.setViewportSize({ width: 320, height: 800 }); await expect(page).toHaveScreenshot(...); assert documentElement.scrollWidth ≤ clientWidth + 10` |"),s.push("| **1.4.12 Text Spacing (AA)** | single-page | inject `* { line-height: 1.5 !important; letter-spacing: 0.12em !important; word-spacing: 0.16em !important; }` via `addStyleTag`, screenshot, check no clipping/overlap |"),s.push('| **1.4.13 Content on Hover or Focus (AA)** | single-page | hover each tooltip-bearing element, then `await page.keyboard.press("Escape")` — verify tooltip closes (dismissible); move mouse INTO tooltip, verify it stays (hoverable); wait 5s without interaction, verify it stays (persistent) |'),s.push("| **2.1.4 Character Key Shortcuts (A)** | single-page | enumerate document keydown handlers; press each single-key shortcut + verify it only fires when expected (focus-dependent or toggleable) |"),s.push("| **2.2.1 Timing Adjustable (A)** | single-page | scan code for setTimeout/setInterval with user-facing impact; assert each has a pause/extend/disable affordance |"),s.push("| **2.2.2 Pause, Stop, Hide (A)** | single-page | find elements with CSS animation / transition / autoplay video; assert each has a visible pause/stop control |"),s.push('| **2.4.5 Multiple Ways (AA)** | single-page | `await page.locator("nav").count() + page.locator("[role=search]").count() + page.locator("a[href*=sitemap]").count() ≥ 2` — checks the current page exposes at least two ways to reach other pages (nav + search, or nav + sitemap link, etc.). |'),s.push("| **2.5.2 Pointer Cancellation (A)** | single-page | for each `<button>` / `[role=button]`: `page.mouse.down()` on it, `page.mouse.move(elsewhere)`, `page.mouse.up()` — assert no action fired (no URL change, no submitted form, no state mutation) |"),s.push("| **3.2.1 On Focus (A)** | **single-page (per-page test, NOT multi-page)** | iterate every focusable on the current page; `.focus()` on each; assert URL unchanged + no modal opened + no form submitted + no scroll jump. This is about each FOCUS event on this page, not about page-to-page navigation. |"),s.push('| **3.2.2 On Input (A)** | **single-page (per-page test, NOT multi-page)** | for each `<select>` / `<input>` / `<textarea>` / `[contenteditable]` on the current page: change value via `.fill()` or `.selectOption()`; assert no URL change, no auto-submit, no scroll jump UNLESS the form clearly warns ("submits on change" UI affordance). |'),s.push('| **4.1.3 Status Messages (AA)** | single-page | trigger a form-validation error or async update; assert the live region announces (`role="status"` or `role="alert"` exists + has text content) |'),s.push(""),s.push("### Step 3: Vision-capable items"),s.push(""),s.push('For items where the test result is "a screenshot to evaluate":'),s.push("- **Color contrast on a gradient/image background:** Playwright screenshot of the element + 50px context, sample foreground + background pixels at the text's bounding box, compute WCAG contrast ratio, compare to 4.5:1 (normal) or 3:1 (large)."),s.push('- **Alt text accuracy:** fetch the `src=` URL, view the image, propose alt text. If the image is a logo/wordmark/decorative, recommend `alt=""`.'),s.push("- **1.4.5 Images of Text:** screenshot the hero / heading / banner; check whether typography is rendered as text (selectable + DOM-visible) or as a raster image."),s.push(""),s.push("### Step 4: When to fall back to asking the user"),s.push(""),s.push("Ask only when:"),s.push(`1. The verification requires subjective judgment Playwright can't make (e.g., "is this alt text accurate to the brand's voice?")`),s.push('2. The criterion needs production-data context (e.g., "do users hit this timeout in practice?")'),s.push("3. The Playwright test itself is ambiguous and the user has context that resolves it"),s.push(""),s.push('**Report Playwright results inline with your fix proposals.** "I ran a smoke test for 1.4.10 at 320px — no horizontal scroll detected; checking this off" beats "Could you verify 1.4.10 manually?" every time.'),s.push(""),s.push("## Determinism + reproducibility"),s.push(""),s.push("**AI judgments are not reproducible by default.** Anthropic, OpenAI, and most LLM APIs run at non-zero sampling temperature unless you explicitly request otherwise — meaning the same prompt + same context can produce different answers on different runs. wcagcheckr learned this the hard way (rc.190 → rc.192): consecutive audits on an unchanged page produced different verdicts because the audit's own AI calls ran at default temperature. The fix was `temperature: 0` on every judgment call."),s.push(""),s.push("You face the same risk for any decision you make based on AI judgment rather than empirical observation. Mitigate by:"),s.push(""),s.push('1. **Prefer empirical verification over AI judgment.** If a question can be answered by running Playwright, running axe-core, sampling pixel values, or grepping the codebase — do that, not "I think the screenshot shows…". The Playwright section above lists 11 criteria that can be verified deterministically.'),s.push("2. **For any auxiliary AI calls you make** (vision tasks, classification, summarization), pass `temperature: 0` (Anthropic / OpenAI) or `top_p: 0.01` (other providers). Same screenshot + same prompt should produce the same answer twice in a row — verify before trusting the answer."),s.push('3. **Document AI-judgment decisions in commit messages.** When you DO rely on AI for a subjective call ("I chose `aria-label=\\"Search the catalog\\"` over `\\"Search\\"` because…"), record the reasoning in the commit so the next session — yours or a human\'s — can audit the call.'),s.push("4. **Treat any single AI verdict as one data point, not gospel.** If you ask AI the same question twice and get different answers, that's the variance showing. Stick with the FIRST answer you committed to OR cross-check with Playwright; don't flip-flop based on subsequent re-rolls."),s.push("5. **Re-run the auditor (`wcagcheckr`) after every commit** rather than asking AI \"is this fixed?\" — the auditor's output is the source of truth for whether a fix landed, not the fixer AI's self-assessment."),s.push(""),s.push("Sampling variance is the silent enemy of compliance automation. Treat it as a constraint to design around, not noise to ignore."),s.push(""),s.push("## Constraints"),s.push(""),s.push("- Don't fix violations that aren't in this list (no scope creep)."),s.push(`- Don't reformat or reorder unrelated code. No "while I'm here" cleanup.`),s.push(`- Match existing code style. Don't invoke Prettier / ESLint to "fix everything" — focus your diff strictly on the violation's element + relevant style rule.`),s.push("- Don't add new dependencies, configs, or build steps."),s.push("- Add inline comments only where the fix's rationale is non-obvious; otherwise skip comments."),s.push("- Preserve existing functionality and visual design wherever possible."),s.push(""),s.push("## How to use this prompt"),s.push(""),s.push("Paste this into Claude / Cursor / Copilot Chat with your codebase open. The AI should:"),s.push('1. **First**: walk through the "Before you start" + "Ask me these questions" sections with the user.'),s.push("2. **Then**: identify the source files containing each violating element (use the selectors + outerHTML below)."),s.push("3. **Then**: for each violation, propose the fix using the recipe + answers gathered, surface ambiguity per the decision rules, and apply only after the user confirms."),s.push("4. **Finally**: produce one atomic commit per fix, with a clear message."),s.push(""),s.push("## Locating findings in source"),s.push(""),s.push("For each finding below, the most reliable search strategies (try in this order):"),s.push(""),s.push('1. **The accessible name / text content** — works for JSX, Vue templates, Svelte, server-rendered HTML. Search for the visible text in `"quotes"` or as a string literal in component files.'),s.push("2. **A distinctive class name** — e.g., `.cta-primary` → grep `cta-primary` in CSS + component files. BEM-style and Tailwind utility classes are the most stable identifiers."),s.push('3. **A unique data attribute** — `data-testid="checkout-button"`, `data-qa=`, `data-cy=` — these are usually authored deliberately and survive minification.'),s.push("4. **A stable ID** — `#hero-search` → grep `hero-search`. Watch out for IDs that look generated (`#field_3f8a92`)."),s.push('5. **The outerHTML pattern** — fall back when class/id/testid don\'t help. Search for distinctive attribute combinations (e.g., `aria-label="…"` + `class="…"`).'),s.push(""),s.push("**Do not search on these — they're runtime artifacts:**"),s.push("- `:nth-child(N)`, `:nth-of-type(N)` — these are positional, never appear in source"),s.push("- Numeric suffixes that look generated (`-1234`, `__abc123`) — usually from CSS Modules / styled-components hashing"),s.push("- Selector paths with `>` chains — describes a DOM structure, not a source location"),s.push(""),s.push("**For pages built with a framework**, also check:"),s.push("- Storybook stories (`*.stories.tsx`, `*.stories.mdx`) — often the canonical component definition"),s.push("- Shared layout components (`Header.tsx`, `Footer.tsx`, `Layout.tsx`) — most page-level violations live here"),s.push("- Theme / token files (`theme.ts`, `tokens.json`) — color-contrast fixes often patch tokens, not consumers"),s.push(""),s.push("---"),s.push("");const D=(((ae=e[0])==null?void 0:ae.readingOrderIssues)??[]).filter(A=>!d.has(`reading-order::${A.selector}`)),S=(x=e[0])==null?void 0:x.positionAnalysisCapturedAt,R=S?`${S.breakpoint.width}×${S.breakpoint.height} (${S.breakpoint.label}), ${S.direction.toUpperCase()}, ${S.theme} theme, ${S.pseudoState} pseudo-state`:"(reference state unavailable — findings reflect last matrix state)",P=c==null?void 0:c.some(A=>A.criterionId==="1.3.2");if(D.length>0&&!P){s.push("## Reading-order concerns (DOM ≠ visual order)"),s.push(""),s.push(`_Positions captured at: **${R}**. Multi-column layouts, RTL pages, and mobile-stacked sections may have legitimately different reading orders at other viewports — verify each finding against the layout the user actually sees._`),s.push(""),s.push(`Screen readers read the DOM in source order. CSS that visually rearranges things (\`flex-direction: row-reverse\`, \`order:\`, \`grid-area\`, absolute positioning) does NOT reorder what the SR sees. ${D.length} element${D.length===1?" has":"s have"} a notable gap between their DOM position and their visual position:`),s.push(""),s.push("| DOM index | Visual index | Selector | Text |"),s.push("|---|---|---|---|");for(const A of D){const B=A.textSnippet.replace(/\|/g,"\\|").replace(/\n/g," ");s.push(`| ${A.domIndex} | ${A.visualIndex} | \`${A.selector}\` | ${B} |`)}s.push(""),s.push("**How to fix:** match DOM order to intended reading order — reorder the JSX/HTML, NOT the CSS. CSS visual rearrangement is a code smell only because it diverges from DOM order; if the DOM order is what the user should hear, no fix is needed."),s.push(""),s.push("**False-positive cases — these are the ONLY legitimate reasons to skip a flagged element:**"),s.push(""),s.push('1. **Multi-column / newspaper layouts** — DOM order is "column 1 top to bottom, then column 2", which is correct for SR but reads as "out of order" to this analyzer.'),s.push('2. **Skip-links** ("Skip to main content") — visually positioned late but DOM-early on purpose.'),s.push("3. **Caption-first patterns** — captions placed before their image in DOM so SR users get context before the alt text."),s.push("4. **Modal / popover triggers** — the trigger button is DOM-near the modal's portal mount point, which is often `<body>` end."),s.push(""),s.push('**Per the verification rules above (§ "Do not dismiss findings without empirical verification"): even when a finding looks like one of these false-positive cases, you must verify in a real browser before skipping. "Looks like a multi-column layout" is inference, not proof.** Read the DOM. Get real coordinates. Then ask the user. Then skip if confirmed.'),s.push(""),s.push("Surface the candidates above to the user, ask which (if any) are intended, and only reorder the rest. **If in doubt, fix it.** Reordering JSX is reversible; failing a Level A criterion is not."),s.push(""),s.push("---"),s.push("")}const I=(((U=e[0])==null?void 0:U.tabOrderIssues)??[]).filter(A=>!d.has(`tab-order::${A.selector}`)),$=c==null?void 0:c.some(A=>A.criterionId==="2.1.2"&&A.verdict==="pass");if(I.length>0&&!$){const A=I.filter(L=>L.flag==="visual"||L.flag==="both").length,B=I.filter(L=>L.flag==="tabindex"||L.flag==="both").length;s.push("## Tab-order concerns (keyboard focus sequence ≠ visual order) — WCAG 2.4.3 Focus Order (A)"),s.push(""),s.push(`_Positions captured at: **${R}**. Tab-position-vs-visual-position divergence is direction- and breakpoint-sensitive — what looks wrong here may be correct on RTL or mobile (and vice versa). Verify each finding against the layout the user actually sees._`),s.push(""),s.push(`Keyboard users tab through the page in one order; sighted users scan in another. axe-core does not detect this. ${I.length} focusable element${I.length===1?" has":"s have"} a notable divergence. Of these: ${A} ${A===1?"has":"have"} a visual-vs-tab mismatch (focus jumps around the page); ${B} ${B===1?"is":"are"} caused by positive \`tabindex\` reordering DOM source.`),s.push(""),s.push("| Tab pos | Visual pos | DOM pos | Flag | Selector | Text |"),s.push("|---|---|---|---|---|---|");for(const L of I){const G=L.textSnippet.replace(/\|/g,"\\|").replace(/\n/g," ");s.push(`| ${L.tabPosition} | ${L.visualPosition} | ${L.domPosition} | ${L.flag} | \`${L.selector}\` | ${G} |`)}s.push(""),s.push("**How to fix:**"),s.push(""),s.push("- **`flag: visual`** — tab order doesn't match visual layout. Reorder the JSX/HTML so the DOM source order matches the layout order users see. Don't use `tabindex` to \"patch\" this — that creates the next class of bug."),s.push('- **`flag: tabindex`** — a positive `tabindex` value (`tabindex="3"`) is reordering DOM. Remove the positive `tabindex` and rely on DOM source order. Use `tabindex="0"` only to make a non-focusable element focusable; use `tabindex="-1"` only to remove from the tab order.'),s.push("- **`flag: both`** — both conditions present. Fix the DOM order first, then remove the positive `tabindex`."),s.push(""),s.push("**False-positive cases — these are the ONLY legitimate reasons to skip a flagged element:**"),s.push(""),s.push('1. **Skip-links** ("Skip to main content") — visually-late, DOM-early on purpose. Their tab position should be near 1; their visual position is wherever their `:focus` styling places them.'),s.push("2. **Modal triggers + portal-rendered modals** — the trigger button is DOM-near the modal's portal mount point (often `<body>` end), so tab/visual positions can legitimately diverge."),s.push("3. **Dropdown menus / popovers** — a button DOM-far from its portal-rendered menu may produce false positives when the menu is open."),s.push(""),s.push(`**Per the verification rules above (§ "Do not dismiss findings without empirical verification"): even when a finding looks like one of these cases, you must verify in a real browser before skipping. Tabbing through the actual deployed page is empirical evidence; reading the auditor's table and pattern-matching is not.** If in doubt, fix it. Reordering JSX is reversible; failing a Level A criterion is not.`),s.push(""),s.push("Surface candidates to the user; ask which are intended; only fix the rest."),s.push(""),s.push("---"),s.push("")}const C=e.flatMap(A=>A.announcements??[]),F=e.flatMap(A=>A.focusEvents??[]).filter(A=>A.isFocusReset);if(C.length>0||F.length>0){if(s.push("## Runtime behavioral observations"),s.push(""),C.length>0){const A=C.filter(L=>L.politeness==="assertive").length;s.push(`**Live-region announcements** (${C.length} total, ${A} assertive): screen-reader announcements captured during state-driving. WCAG 4.1.3 Status Messages (AA) requires that non-essential changes use polite announcements; only blocking errors warrant assertive. Review:`),s.push("");const B=C.slice(0,8);for(const L of B){const G=L.text.replace(/\|/g,"\\|").replace(/\n/g," ").slice(0,120);s.push(`- \`[${L.politeness}${L.role?`/${L.role}`:""}]\` ${G}`)}C.length>8&&s.push(`- (and ${C.length-8} more)`),s.push(""),s.push("Verify each is appropriate. Common bug: error messages firing as `polite` (SR may not interrupt), or status updates firing as `assertive` (interrupts user mid-sentence)."),s.push("")}if(F.length>0){s.push(`**Focus resets to body** (${F.length} occurrence${F.length===1?"":"s"}): during the audit, focus jumped to \`<body>\` or \`<html>\` unexpectedly. This is almost always a bug — focus should move to a logical landing place (modal, alert, next field), never disappear.`),s.push("");for(const A of F.slice(0,5))s.push(`- \`${A.fromSelector??"(none)"}\` → \`${A.toSelector}\``);F.length>5&&s.push(`- (and ${F.length-5} more)`),s.push(""),s.push("Common cause: an element is removed from the DOM (e.g., modal closes) without `.focus()` being called on a sensible destination."),s.push("")}s.push("---"),s.push("")}const q=((z=e[0])==null?void 0:z.undefinedCustomProperties)??[];if(q.length>0){s.push("## Undefined CSS custom properties (cascade root cause)"),s.push(""),s.push(`${q.length} CSS custom propert${q.length===1?"y is":"ies are"} referenced via \`var(--name)\` but never declared. When a custom property is missing, the browser falls back to the property's initial value (often \`Canvas\` for color slots → resolves to white), which can produce a wave of false contrast failures. **Fix these missing tokens FIRST** — many of the contrast violations below may disappear once the cascade resolves correctly.`),s.push(""),s.push("| Custom property | References | First sample sites |"),s.push("|---|---|---|");for(const A of q){const B=A.sampleSites.map(L=>`\`${L.selector} { ${L.property} }\``).join("; ");s.push(`| \`${A.name}\` | ${A.referenceCount} | ${B} |`)}s.push(""),s.push("Each entry needs a definition in `:root` (or wherever the design-token layer lives). Surface to the user before applying contrast fixes — the missing token may be the root cause."),s.push(""),s.push("---"),s.push("")}if(a&&a.length>0){s.push("## Manual checks already completed (Guided Tests)"),s.push(""),s.push(`The consultant has run ${a.length} Intelligent Guided Test workflow${a.length===1?"":"s"} against this component covering the WCAG criteria automation cannot verify (keyboard navigation, screen-reader experience, focus management, forms, error prevention, etc.). Treat the verdicts below as ground truth — these came from a human running the actual flow.`),s.push(""),s.push("**Manual passes outrank heuristic findings.** When an IGT workflow has passed (e.g. Keyboard 7/7) and the heuristic analyzers below (Tab-order concerns, Reading-order concerns) flag findings on the same WCAG dimension, the manual pass is ground truth. A human ran real keyboard/SR navigation against the deployed page; the heuristic computes visual position from `getBoundingClientRect()` and can produce false positives on sticky / multi-column / grid layouts. **When the IGT and the heuristic disagree on the same WCAG criterion, treat the heuristic findings as suspect first**, run the empirical verification described above, and (if confirmed false positive) recommend dismissal via the Activity tab."),s.push("");const A=Ee;s.push("| Workflow | Status | Notes |"),s.push("|---|---|---|");for(const W of a){const V=A.find(ma=>ma.id===W.workflowId);if(!V)continue;let Y=0,Z=0,$e=0,ga=0,Be=0;for(const ma of V.steps){const ht=W.steps[ma.id];if(!ht){Be++;continue}ht.status==="pass"?Y++:ht.status==="fail"?Z++:ht.status==="skip"?$e++:ht.status==="not-applicable"?ga++:Be++}const Ji=Z>0?`❌ ${Z} failed`:Be>0?`⚠ ${Be} unanswered`:`✓ all ${V.steps.length} answered`,Et=[`${Y}p`,`${Z}f`];ga>0&&Et.push(`${ga} N/A`),$e>0&&Et.push(`${$e} skip`),Be>0&&Et.push(`${Be} unanswered`);const Xi=`${Et.join(" · ")} of ${V.steps.length} steps`;s.push(`| ${V.name} | ${Ji} | ${Xi} |`)}s.push("");const B=[];for(const W of a){const V=A.find(Y=>Y.id===W.workflowId);if(V)for(const Y of V.steps){const Z=W.steps[Y.id];(Z==null?void 0:Z.status)==="fail"&&B.push({workflow:V.name,stepTitle:Y.question,notes:Z.notes})}}if(B.length>0){s.push("**Failed manual checks** (these are real WCAG failures the consultant verified by hand):"),s.push("");for(const W of B)s.push(`- **${W.workflow} → ${W.stepTitle}**${W.notes?`: ${W.notes}`:""}`);s.push(""),s.push("**Address these alongside the automated violations below.** They will not appear in the violations list because no automated rule catches them — but they are still real WCAG failures and must be fixed for compliance."),s.push("")}const L=new Set(a.map(W=>W.workflowId)),G=A.filter(W=>!L.has(W.id));G.length>0&&(s.push(`**Not yet manually verified** (${G.length} workflow${G.length===1?"":"s"}): `+G.map(W=>W.name).join(", ")+". These cover WCAG criteria automation cannot detect; ask the consultant whether to run them before declaring the audit complete."),s.push("")),s.push("---"),s.push("")}if(n&&n.pagesAudited>1){const A=(()=>{try{return new URL(m).origin}catch{return null}})(),B=(()=>{try{return new URL(n.startUrl).origin}catch{return null}})();if(A&&B&&A===B){const L=n.finishedAt,G=L?Date.now()-new Date(L).getTime():0,W=G<6e4?"just now":G<36e5?`${Math.round(G/6e4)} minute${Math.round(G/6e4)===1?"":"s"} ago`:G<864e5?`${Math.round(G/36e5)} hour${Math.round(G/36e5)===1?"":"s"} ago`:`${Math.round(G/864e5)} day${Math.round(G/864e5)===1?"":"s"} ago`;if(s.push("## Site-wide context (from a recent site crawl)"),s.push(""),s.push(`A crawl ${L?`(${W})`:""} of ${n.pagesAudited} page${n.pagesAudited===1?"":"s"} on this origin produced grade **${n.siteGrade}** site-wide with **${n.totalUniqueViolations}** unique violations. Use this context for WCAG 3.2.3 (Consistent Navigation) and 3.2.4 (Consistent Identification) which require cross-page comparison.`),s.push(""),n.topViolations.length>0){s.push("**Most-frequent violations across the site** (rules to prioritize because they affect multiple pages):"),s.push("");for(const Y of n.topViolations.slice(0,6))s.push(`- \`${Y.ruleId}\` — ${Y.impact} — found on ${Y.urlsAffected} page${Y.urlsAffected===1?"":"s"} (${Y.totalOccurrences} total instances)`);s.push(""),s.push("Fixing one of these rules in shared components / layouts is leverage — one fix resolves N pages."),s.push("")}const V=n.consistencyFindings??[];if(V.length===0&&n.topViolations.some(Z=>Z.ruleId==="wcc-consistency-needs-site-crawl"||Z.ruleId==="wcc-redundant-entry-needs-site-crawl")&&(s.push("**Cross-page consistency status:** the comparators ran across the crawled pages and detected **no real divergences** — primary navigation, brand link, footer help mechanism, and multi-step form fields are all consistent across the sample. The `wcc-consistency-needs-site-crawl` and `wcc-redundant-entry-needs-site-crawl` rule IDs in the violations list are placeholder findings emitted per-page by the single-page analyzer (which can't see other pages); the cross-page comparators downgrade them when the crawl confirms consistency. **Do not chase these on a per-page basis** — they're already cleared at the site level."),s.push("")),V.length>0){s.push("**Cross-page consistency divergences detected:**"),s.push("");for(const Y of V){const Z=Y.aiVerdict?Y.aiVerdict.verdict==="uncertain"?" (AI: uncertain — human verify)":` (AI: confirmed real, confidence ${(Y.aiVerdict.confidence*100).toFixed(0)}%)`:"";s.push(`- **${Y.wcagCriterion} ${Y.impact}**${Z} — ${Y.description}`);for(const $e of Y.details.split(`
1784
+ `))s.push(` ${$e}`);(K=Y.aiVerdict)!=null&&K.reasoning&&s.push(` AI reasoning: ${Y.aiVerdict.reasoning}`),s.push("")}}s.push("**Manual cross-page checks the AI should still verify** (the heuristic comparators above can miss edge cases):"),s.push(""),s.push("- **3.2.3 Consistent Navigation** — primary nav DOM order across pages."),s.push("- **3.2.4 Consistent Identification** — same logo / menu / search / nav-link accessible names across pages."),s.push("- **3.2.6 Consistent Help (WCAG 2.2 A)** — help mechanism position consistency."),s.push("- **3.3.7 Redundant Entry (WCAG 2.2 A)** — multi-step forms re-asking for already-entered information."),s.push(""),s.push("---"),s.push("")}}if(p.length>0){const A=new Map;for(const L of p)A.has(L.matchKey)||A.set(L.matchKey,L);const B=Array.from(A.values());s.push("## Previously verified by human — DO NOT re-flag or attempt to fix"),s.push(""),s.push(`The user manually verified each of the following ${B.length} finding${B.length===1?"":"s"} in the wcagcheckr side panel. They WILL appear in raw axe output on every subsequent audit (axe re-detects them because the underlying DOM hasn't changed), but the user has dispositioned each one as acceptable — typically because an automated rule fired on a legitimate edge case (decorative image with empty alt, contrast that passes APCA but not WCAG 2.0 ratio, etc.). **Do not attempt to "fix" any item in this list.** Treat them as resolved. If you genuinely believe one is misjudged, note your reasoning in a comment for the user to review on their next audit — do NOT modify the markup.`),s.push(""),B.forEach((L,G)=>{s.push(`${G+1}. \`${L.ruleId}\` on \`${L.target.selector}\``),L.target.accessibleName&&s.push(` - Accessible name: "${L.target.accessibleName}"`),L.target.textSnippet&&s.push(` - Text: "${L.target.textSnippet.slice(0,120)}"`),s.push(` - WCAG ${L.wcagCriterion} ${L.wcagLevel} · ${L.impact}`)}),s.push(""),s.push("---"),s.push("")}const N=(i??[]).filter(A=>A.verdict==="fail").filter(A=>!u.has(`${A.ruleId}::${A.selector}`));if(N.length>0){const A=new Map;for(const L of N){const G=`${L.ruleId}::${L.selector}`;A.has(G)||A.set(G,L)}const B=Array.from(A.values());s.push("## AI-evaluated fails (axe couldn't determine — AI vision determined FAIL)"),s.push(""),s.push(`axe-core flagged each of the following ${B.length} element${B.length===1?"":"s"} as INCOMPLETE — its automated heuristics couldn't reach a verdict (typically because the background is a gradient, image, or semi-transparent overlay that axe can't pixel-sample). wcagcheckr's AI resolver then evaluated each element via vision + computed-style sampling and determined it is a real **FAIL**. Treat these the same as axe violations: locate the element in source, apply the canonical fix for the rule, and re-audit. The AI's reasoning is included per finding so you don't have to re-derive what was wrong.`),s.push(""),B.forEach((L,G)=>{const W=L.wcagCriterion?` — WCAG ${L.wcagCriterion}`:"";s.push(`### ${G+1}. \`${L.ruleId}\`${W}`),s.push(""),s.push(`**Selector:** \`${L.selector}\``),s.push(""),s.push(`**AI verdict (FAIL):** ${L.reasoning}`),s.push("");const V=at[L.ruleId];V&&(s.push(`**Recipe:** ${V.summary}`),s.push(""),V.snippet&&(s.push("```"+(V.snippetLang??"")),s.push(V.snippet),s.push("```"),s.push("")))}),s.push("---"),s.push("")}s.push("## Violations to fix"),s.push(""),y.forEach((A,B)=>{const L=at[A.ruleId],G=A._instanceSelectors.length;if(s.push(`### ${B+1}. \`${A.ruleId}\` — ${A.impact} — WCAG ${A.wcagCriterion} ${A.wcagLevel}${G>1?` (${G} instances)`:""}`),s.push(""),s.push(`**Description:** ${A.description}`),s.push(""),G>1){s.push(`**Affects ${G} spots — fix at the SOURCE (one CSS rule, one component, one shared class) — DO NOT patch each instance separately:**`);for(const Z of A._instanceSelectors.slice(0,8))s.push(`- \`${Z}\``);A._instanceSelectors.length>8&&s.push(`- (and ${A._instanceSelectors.length-8} more)`),s.push(""),s.push(`> **Find the common source.** ${G} matches means there's a single component / class / CSS rule producing all of them. Search the codebase for the shared class name OR the JSX/template that renders this element, then patch ONCE there. If you can't find a shared source (the elements are genuinely independent), apply the same fix to each — but flag that to the user as "code smell: this pattern is repeated."`)}else s.push(`**Selector:** \`${A.target.selector}\``);s.push(""),s.push(`**Found in state(s):** ${A._states.join(" / ")}`),s.push("");const W=oh(A);if(W.length>0){for(const Z of W)s.push(`**${Z.label}:** ${Z.value}`);s.push("")}A.target.failureSummary&&(s.push(`**Diagnostic:** ${A.target.failureSummary.replace(/\n+/g," ")}`),s.push("")),A.ai&&(s.push(`**🤖 AI-verified finding** (model: ${A.ai.model}, confidence: ${(A.ai.confidence*100).toFixed(0)}%)`),s.push(`> ${A.ai.reasoning}`),s.push(""));const V=A.target.cascadeChain;if(V&&(V.color||V.backgroundColor)){if(s.push("**CSS cascade chain (authored → rendered):**"),V.color){const Z=V.color.vars.length>0?` (vars: ${V.color.vars.map($e=>`\`${$e}\``).join(", ")})`:"";s.push(`- \`color\`: \`${V.color.authored}\` → \`${V.color.rendered}\`${Z}${V.color.ruleSelector?` — set in rule \`${V.color.ruleSelector}\``:""}`)}if(V.backgroundColor){const Z=V.backgroundColor.vars.length>0?` (vars: ${V.backgroundColor.vars.map($e=>`\`${$e}\``).join(", ")})`:"";s.push(`- \`background-color\`: \`${V.backgroundColor.authored}\` → \`${V.backgroundColor.rendered}\`${Z}${V.backgroundColor.ruleSelector?` — set in rule \`${V.backgroundColor.ruleSelector}\``:""}`)}s.push("")}const Y=r==null?void 0:r[A.matchKey];if(Y&&Y.suggestions.length>0){s.push(`**AI color suggestions** (cached from in-UI tooling — ${Y.suggestions.length} candidates passing WCAG):`);for(const Z of Y.suggestions)s.push(`- foreground \`${Z.foreground}\` on background \`${Z.background}\` → ${Z.contrast.toFixed(2)}:1 — ${Z.rationale}`);s.push(`> AI reasoning: ${Y.reasoning}`),s.push("")}s.push("**Element HTML:**"),s.push("```html"),s.push(A.target.outerHTML),s.push("```"),s.push(""),L?(s.push(`**Recipe:** ${L.summary}`),L.snippet&&(s.push(""),s.push("```"+(L.snippetLang??"")),s.push(L.snippet),s.push("```"))):s.push(`**Recipe:** No curated recipe for this rule — apply the standard WCAG ${A.wcagCriterion} guidance.`),A.helpUrl&&(s.push(""),s.push(`**More info:** ${A.helpUrl}`)),s.push(""),s.push("---"),s.push("")});const j=ih(e,i);if(j.length>0){s.push("## Items axe couldn't determine — needs human review"),s.push(""),s.push("axe-core ran these rules but couldn't conclude pass/fail (the most common cause is color-contrast on a gradient / image / semi-transparent background). For each element below: if you have vision capability, sample the rendered background and compute the contrast ratio; otherwise ask the user to verify visually."),s.push("");for(const A of j)s.push(`### \`${A.ruleId}\` on \`${A.selector}\``),s.push(""),s.push(`- **WCAG criterion:** ${A.wcagCriterion}`),s.push(`- **Page URL:** ${A.pageUrl}`),A.failureSummary&&s.push(`- **axe diagnostic:** ${A.failureSummary}`),(ie=A.styles)!=null&&ie.foreground&&s.push(`- **Foreground:** \`${A.styles.foreground}\``),(re=A.styles)!=null&&re.background&&s.push(`- **Background:** \`${A.styles.background}\``),(ve=A.styles)!=null&&ve.fontSize&&s.push(`- **Font size:** ${A.styles.fontSize}px, weight ${A.styles.fontWeight??"unknown"}`),(pe=A.styles)!=null&&pe.textSample&&s.push(`- **Text sample:** ${JSON.stringify(A.styles.textSample)}`),A.aiAttemptReasoning&&(s.push(""),s.push("**Our AI resolver already attempted this element and returned uncertain.** Its reasoning:"),s.push(""),s.push(`> ${A.aiAttemptReasoning.replace(/\n/g,`
1785
+ > `)}`),s.push(""),s.push("**Suggested next step:** if you have stronger vision capability than our resolver, re-evaluate with a fresh screenshot. Otherwise the user must verify this one manually.")),s.push(""),s.push("---"),s.push("")}s.push("## Manual verification checklist — content-meaning criteria"),s.push(""),s.push(`wcagcheckr's three layers (axe-core, DOM analyzers, AI walkthroughs) cover every WCAG 2.1 AA criterion structurally. What automation CANNOT verify is content meaning: whether your caption text actually matches the audio, whether alt text accurately describes the image, whether sensory cues ("click the red button") have an alternative non-visual phrasing. **After applying the fixes above, walk the user through each item below before declaring the audit complete.** Mark each one passed, failed (and add to the fix queue), or N/A (with reasoning).`),s.push(""),s.push('Items the auditor has already flagged in earlier sections (reading-order, tab-order, behavioral observations, IGT manual checks) are listed below as "ALREADY FLAGGED" — confirm those fixes are in place.'),s.push("");const X=new Map;if(c)for(const A of c)X.set(A.criterionId,A.verdict);function Q(A,B){const L=X.get(A);return L==="pass"?`- [x] **${A}** — ✓ AI walkthrough verified pass during audit (see "AI keyboard walkthroughs" section). No manual verification needed.`:L==="fail"?`- [x] **${A}** — ✗ AI walkthrough FAILED during audit (see "AI keyboard walkthroughs" section for the fix). Confirm the code fix is applied and re-audit.`:L==="uncertain"?`- [ ] **${A}** — AI walkthrough was uncertain. ${B}`:`- [ ] **${A}** — ${B}`}return s.push("### Perceivable (audit-already-verified items pre-checked)"),s.push(""),s.push(Q("1.3.2 Meaningful Sequence (A)","verify DOM source order matches the visual / reading order. Screen readers read the DOM; CSS reordering does NOT affect what the SR hears.")),s.push("- [ ] **1.2.1 Audio-only / Video-only Prerecorded (A)** — every audio-only file has a text transcript; every silent-video file has a text alternative or audio description. Ask the user: does the page have media? If yes, list each `<audio>` / `<video>` and verify the alternative."),s.push('- [ ] **1.2.2 Captions Prerecorded (A)** — every prerecorded video with audio has synchronized captions. If `<video>` elements exist without `<track kind="captions">`, ask the user about caption files.'),s.push("- [ ] **1.2.3 Audio Description / Media Alternative Prerecorded (A)** — prerecorded video has either audio description or a full text alternative."),s.push("- [ ] **1.2.4 Captions Live (AA)** — live audio content (livestream, real-time meeting) has captions. Usually N/A for static sites."),s.push("- [ ] **1.2.5 Audio Description Prerecorded (AA)** — prerecorded video has audio description for visual-only information."),s.push("- [ ] **1.4.2 Audio Control (A)** — if any audio plays automatically for >3 seconds, there is a mechanism to pause / stop / control volume. Test by loading the page; if anything autoplays audibly, fail."),s.push("- [ ] **1.4.4 Resize Text (AA)** — zoom the browser to 200%. All text remains readable; nothing is cut off. No horizontal scrolling within content blocks."),s.push("- [ ] **1.4.5 Images of Text (AA)** — confirm text is rendered as actual text, not as images. Exceptions: logos, brand wordmarks. Walk the user through their hero / heading sections."),s.push("- [ ] **1.4.10 Reflow (AA)** — resize browser to 320px wide × 256px tall. Page is usable; no horizontal scrollbar appears (except for data tables / maps / code)."),s.push(Q("1.4.11 Non-text Contrast (AA)","every UI component (button border, form input border, icon, focus indicator) and graphical object has at least 3:1 contrast against adjacent colors. Inspect every interactive control + state indicator visually.")),s.push("- [ ] **1.4.12 Text Spacing (AA)** — apply user-stylesheet overrides: line-height ≥1.5×; paragraph spacing ≥2×; letter-spacing ≥0.12×; word-spacing ≥0.16×. Layout doesn't break, no clipped/overlapping text."),s.push("- [ ] **1.4.13 Content on Hover or Focus (AA)** — for any tooltip / popover / hover-revealed content: verify it is *dismissible* (Esc closes), *hoverable* (mouse can move into it without it disappearing), and *persistent* (doesn't auto-disappear unless user dismisses)."),s.push(""),s.push("### Operable (audit-already-verified items pre-checked)"),s.push(""),s.push(Q("2.1.2 No Keyboard Trap (A)","Tab through the entire page from start to finish; verify focus never gets stuck in a region you can't leave with Tab/Shift+Tab/Esc. Common offenders: video players, custom widgets, modals without proper Esc handling.")),s.push('- [ ] **2.1.4 Character Key Shortcuts (A)** — if the site implements single-key shortcuts (pressing "j" advances feed, etc.), they must be either turn-off-able OR only active when focus is on a specific control. Ask the user about implemented shortcuts.'),s.push('- [ ] **2.2.1 Timing Adjustable (A)** — if any timeout exists (auto-logout, session expiry, "complete this in X seconds"), user can turn it off, adjust it, or extend it (≥10× the default). Exception: real-time events. Ask the user.'),s.push("- [ ] **2.2.2 Pause, Stop, Hide (A)** — for moving / blinking / scrolling / auto-updating content lasting >5s: there is a mechanism to pause/stop/hide it. Carousels, tickers, animations, autoplaying video — all need controls."),s.push("- [ ] **2.3.1 Three Flashes or Below Threshold (A)** — no content flashes more than 3× per second. Test with anything that pulses, blinks, or has rapid color changes (loading animations, alert flashing). Use https://trace.umd.edu/peat/ for borderline cases."),s.push(Q("2.4.3 Focus Order (A)","spot-check by tabbing through the page: focus visits elements in a logical order (matches visual / semantic flow).")),s.push("- [ ] **2.4.5 Multiple Ways (AA)** — at least two ways to find any page within a set: site search + nav menu, OR menu + sitemap, OR menu + table of contents, etc. Single-page apps may exempt; ask user."),s.push(Q("2.4.7 Focus Visible (AA)","every focusable element shows a visible focus indicator. Tab through the page and verify focus is always visually obvious.")),s.push("- [ ] **2.5.1 Pointer Gestures (A)** — any multi-point gesture (pinch-zoom, two-finger swipe) or path-based gesture (signature, drag-along-curve) has a single-pointer alternative. Common offender: maps, signatures, drawing canvases."),s.push("- [ ] **2.5.2 Pointer Cancellation (A)** — actions trigger on the up-event (mouseup / pointerup), NOT the down-event. Pressing then dragging-away cancels. Test by mouse-down on a button → drag off → release: action should not fire."),s.push(Q("2.5.3 Label in Name (A)",`every interactive control's accessible name contains its visible text. Test with speech recognition: "click [visible text]" should activate the correct control.`)),s.push("- [ ] **2.5.4 Motion Actuation (A)** — features triggered by device motion (shake-to-undo, tilt-to-scroll) can be disabled AND have UI-button alternatives. Usually N/A for desktop sites."),s.push("- [ ] **2.5.7 Dragging Movements (AA, WCAG 2.2)** — any drag-only operation (kanban board, slider) has a non-drag alternative (buttons, click-to-place). Test sliders, drag-and-drop, sortable lists."),s.push(""),s.push("### Understandable"),s.push(""),s.push("- [ ] **3.2.1 On Focus (A)** — focusing any element does NOT trigger a context change (URL change, viewport scroll, modal open, form submit). Tab through every focusable element; verify none cause unexpected behavior."),s.push("- [ ] **3.2.2 On Input (A)** — changing the value of any form control does NOT trigger context change unless the user was warned. Common offender: `<select onChange={navigate}>` — must include adjacent submit button or warn the user."),n&&n.pagesAudited>1?(s.push("- [x] **3.2.3 Consistent Navigation (AA)** — site-crawl context provided above. Walk the user through verifying nav appears in the same order across pages."),s.push("- [x] **3.2.4 Consistent Identification (AA)** — verify components with the same function have the same accessible name across pages (logo home link, search button, menu toggle).")):(s.push("- [ ] **3.2.3 Consistent Navigation (AA)** — REQUIRES MULTI-PAGE COMPARISON. Ask the user for 2-3 representative pages; verify primary nav, footer, and search appear in the same DOM order on each."),s.push('- [ ] **3.2.4 Consistent Identification (AA)** — REQUIRES MULTI-PAGE COMPARISON. Components with identical functionality must have identical accessible names. Compare logo "home" link, search button, menu toggle across pages.')),s.push("- [ ] **3.2.6 Consistent Help (A, WCAG 2.2)** — REQUIRES MULTI-PAGE COMPARISON. If a help mechanism exists (chat, contact link, FAQ), it appears in the same relative order on every page where it appears."),s.push("- [ ] **3.3.4 Error Prevention — Legal, Financial, Data (AA)** — for any form that submits legal commitments (contracts, agreement), financial transactions (payments), or modifies/deletes user data: there is at least ONE of (a) reversibility, (b) error-checking with confirmation, (c) review-and-confirm step before final submit."),s.push("- [ ] **3.3.7 Redundant Entry (A, WCAG 2.2)** — forms in a multi-step process do NOT ask for the same information twice unless essential (re-entering password for confirmation is allowed). Auto-fill or pre-fill where possible."),s.push("- [ ] **3.3.8 Accessible Authentication — Minimum (AA, WCAG 2.2)** — login does not require a cognitive function test (puzzle, riddle, character-recognition) unless an alternative is provided (object recognition + autofill support are allowed; CAPTCHAs that require pattern recognition typically fail)."),s.push(""),s.push("### Robust"),s.push(""),C.length>0||F.length>0?s.push("- [x] **4.1.3 Status Messages (AA)** — runtime observations provided above (`Runtime behavioral observations` section). Confirm announcements use appropriate `aria-live` politeness and focus management is correct."):s.push('- [ ] **4.1.3 Status Messages (AA)** — status updates that don\'t cause a focus change (form-validation errors, "saved" confirmations, search-result counts) must use `role="status"` (polite) or `role="alert"` (assertive). Ask the user to demo their form-error and async-update flows; verify SR announces them.'),s.push(""),s.push("### Manual checks STILL TO DO"),s.push(""),s.push(`Walk the user through each unchecked box above (or batch them by criterion type — "let's test all the keyboard ones together"). For each: either mark passed (and move on), failed (add to the fix queue and resolve), or N/A (briefly justify why this site doesn't require this criterion).`),s.push(""),s.push('**Do not declare the audit complete until every box above is checked.** A site that passes only the automated portion is "axe-clean," not WCAG-compliant.'),s.push(""),s.push("---"),s.push(""),s.push("## When you're done — verification"),s.push(""),s.push("Before reporting completion, do all of the following:"),s.push(""),s.push(`1. **Confirm all ${y.length} violation${y.length===1?"":"s"} above are addressed.** Walk back through the list with the user, ticking each one off.`),s.push("2. **Re-run the auditor** (or whatever method we agreed in question 6 above). Verify zero NEW violations were introduced by the fixes."),s.push(`3. **Walk through the Manual verification checklist above** — every item should be marked passed, failed-and-fixed, or N/A. This is the half of WCAG that automation can't reach; skipping it means "axe-clean," not "compliant."`),s.push("4. **Visual diff** — for design-affecting fixes (color, spacing, layout), confirm the change with the user against the original. Storybook stories and visual regression suites are good checkpoints if the codebase has them."),s.push("5. **Commit log review** — surface the list of commits you produced. Each should be atomic and revertable."),s.push("6. **Report what you couldn't do** explicitly. If any violation required information we don't have (e.g., correct alt text), surface a TODO list for the user rather than guessing."),s.push(""),s.push("## Success criteria"),s.push(""),s.push("- All "+y.length+" violation"+(y.length===1?"":"s")+" above are resolved or explicitly deferred with reason."),s.push('- **Every reading-order and tab-order finding** is either fixed OR documented as a verified false positive (per § "Do not dismiss findings without empirical verification" above). Pattern-matching dismissals do not count.'),s.push('- **Every item in the manual verification checklist** above is marked passed, failed-and-fixed, or N/A-with-reason. (This is what separates "axe-clean" from "WCAG-compliant" — do not skip.)'),s.push("- No new violations introduced."),s.push("- State-specific behavior preserved (`:hover`, `:focus`, dark-mode, RTL, breakpoints)."),s.push("- One atomic commit per fix; clean revertable history."),s.push("- No unrelated code reformatted, no dependencies bumped."),s.push("- Diff is reviewable in under 10 minutes per fix."),s.push(""),s.push(`_Generated by ${Ae} v${Ve()}._`),s.join(`
1786
+ `)}const rh=5;function sh(e){const t=new Map;let a=0;for(const n of e){if(!n.screenshotDataUrl||n.violations.length===0)continue;const o=`${n.state.pseudoState} · ${n.state.theme} · ${n.state.direction} · ${n.state.breakpoint.id}`,i=o;t.has(i)||t.set(i,{stateLabel:o,screenshotDataUrl:n.screenshotDataUrl,violations:[]});const r=t.get(i);for(const c of n.violations)a++,r.violations.push({...c,_idx:a})}return Array.from(t.values()).sort((n,o)=>o.violations.length-n.violations.length).slice(0,rh)}const Ao=1280,ko=800,ch={low:"Low risk",moderate:"Moderate risk",high:"High risk",critical:"Critical risk"},lh={low:"#d1fae5",moderate:"#fef3c7",high:"#fed7aa",critical:"#fecaca"},dh={low:"#065f46",moderate:"#92400e",high:"#9a3412",critical:"#991b1b"},uh={A:"#10b981",B:"#84cc16",C:"#eab308",D:"#f97316",F:"#dc2626"},hh={defense:{title:"Accessibility Defense Bundle",documentLabel:"engineering, design, and risk-management",disclaimerHeading:"This document is not a compliance certification.",disclaimerBody:"Automated accessibility audits, including this one, detect a subset (typically 30–50%) of WCAG success criteria. Conformance with the full WCAG specification requires manual evaluation by qualified accessibility professionals, including (but not limited to): keyboard-only operability testing, screen-reader compatibility testing across NVDA / JAWS / VoiceOver, manual review of dynamic content and live regions, and content-quality review for alt text, link text, and labels."},evidence:{title:"Accessibility Compliance Evidence",documentLabel:"documentation and review",disclaimerHeading:"This document is automated audit evidence — not a determination of liability or conformance.",disclaimerBody:"This report documents detected accessibility defects in the audited resource at the date and time of capture. The audit was conducted using axe-core, an industry-standard automated accessibility-rule engine maintained by Deque Systems and used by Microsoft (Accessibility Insights), Google (Lighthouse), IBM, and others. The findings represent objective rule-engine output, not subjective evaluation. Automated audits surface a subset (typically 30–50%) of WCAG success criteria; the absence of a violation in this report is not evidence of conformance, and the presence of a violation is not, by itself, a determination of legal liability."}};function ji(e,t,a,n,o,i,r){return Tt(e,t,"evidence",a,n,o,i,r)}function zi(e,t){return Ki(e,"letter",t)}function Hi(e,t){return Ki(e,"report",t)}function Bi(e,t){var u;const a=e.flatMap(h=>h.violations),n=Se(a,void 0,e,void 0,t),o=Ue(e),i=new Map;for(const h of a){const m=xe(h.ruleId,h.target.selector);i.has(m)||i.set(m,h)}const r=Array.from(i.values()).sort((h,m)=>{const f={critical:0,serious:1,moderate:2,minor:3};return(f[h.impact]??99)-(f[m.impact]??99)}),c=new Map;for(const h of r){const m=c.get(h.ruleId)??[];m.push(h),c.set(h.ruleId,m)}const l=[];if(l.push("# Help me fix accessibility issues on my website"),l.push(""),l.push("You are an accessibility advisor helping me — a non-technical site owner — understand and fix accessibility issues on my website. I ran an automated audit and got the issues below. I may not have technical knowledge, so please explain things in plain English."),l.push(""),l.push("## What I need from you"),l.push(""),l.push("1. **First, ask me what I use to manage my website.** Common platforms: Squarespace, Wix, Shopify, WordPress (with theme name), Webflow, Framer, custom-built. Your advice will be different for each."),l.push("2. **Then, for each issue below, tell me clearly:**"),l.push(" - Whether **I can fix it myself** in my site editor (changing colors, adding alt text, editing button labels, etc.) — and if so, walk me through where to look in my platform"),l.push(" - Or whether **my developer needs to do this** (code-level fix) — and what to ask them to do"),l.push("3. **Prioritize ruthlessly.** I want to start with the issues that:"),l.push(" - Are most likely to come up in an ADA-related lawsuit (critical-impact items)"),l.push(" - Are easiest for me to fix without involving a developer"),l.push("4. **Don't bury me in jargon.** If you have to use a technical term, explain it in plain English first."),l.push("5. **If something is genuinely beyond a non-technical fix, say so.** Tell me what to ask my developer or website-builder support team."),l.push(""),l.push("## Audit summary"),l.push(""),l.push(`- **Page audited:** ${o}`),l.push(`- **Lawsuit-target risk:** ${Jo[n.risk]} — ${Xo[n.risk]}`),l.push(`- **Issues found:** ${n.totals.unique} unique problems (${n.totals.critical} critical · ${n.totals.serious} serious · ${n.totals.moderate} moderate · ${n.totals.minor} minor)`),n.coverage&&n.coverage.untestedCriteria.length>0?l.push(`- **WCAG conformance progress:** ${n.coverage.evaluated} of ${n.coverage.totalApplicable} criteria evaluated. Fixing the issues below will clear me of automated lawsuit-targeting tools. To make a formal claim that the site meets WCAG ${n.coverage.targetVersion} ${n.coverage.targetLevel}, ${n.coverage.untestedCriteria.length} more criteria need human review — see the "Guided Tests" workflows in the auditor side panel for these.`):n.coverage&&n.coverage.untestedCriteria.length===0&&l.push(`- **WCAG conformance:** All ${n.coverage.totalApplicable} applicable WCAG ${n.coverage.targetVersion} ${n.coverage.targetLevel} criteria have been evaluated. Fixing the issues below will produce a defensible conformance claim.`),l.push(""),l.push("## The issues, in plain language"),l.push(""),c.size===0)return l.push("No issues were detected across our three evaluation layers (axe-core rule-based checks, our DOM analyzers covering criteria axe doesn't reach, and AI walkthroughs that drive real keyboard input and judge screenshots). For a formal conformance claim, spot-check the content-meaning criteria — automation can detect missing captions, not whether your caption text accurately matches the audio."),l.join(`
1787
+ `);let d=1;for(const[h,m]of c.entries()){const f=(u=m.find(b=>b.target.opacityContext))==null?void 0:u.target.opacityContext,g=ri(h,f),p=m[0].impact;l.push(`### ${d}. [${p.toUpperCase()}] ${g.whatsWrong}`),l.push(""),l.push(`**Why this matters.** ${g.whyItMatters}`),l.push(""),l.push(`**Plain-language fix guidance.** ${g.howToFix}`),l.push(""),l.push(`**Where on the page (${m.length} ${m.length===1?"spot":"spots"}):**`);for(const b of m.slice(0,8))l.push(`- \`${b.target.selector}\``);m.length>8&&l.push(`- (and ${m.length-8} more spots)`),l.push(""),l.push(`*Technical reference for your developer:* axe-core rule \`${h}\` — full docs at https://dequeuniversity.com/rules/axe/4.11/${h}`),l.push(""),d++}return l.push("---"),l.push(""),l.push("## How to use this output"),l.push(""),l.push("Walk through this list with me one issue at a time. For each:"),l.push("1. Help me understand the issue with a real example of who it affects."),l.push("2. Tell me clearly if I can fix it myself, and if so, walk me through it for my platform."),l.push("3. If I need a developer, give me a short paragraph I can paste into an email or message to them."),l.push("4. Help me prioritize — I have limited time and want to fix the riskiest things first."),l.push(""),l.push("Start by asking me which platform I use."),l.join(`
1788
+ `)}function Ki(e,t,a){const n=e.flatMap(f=>f.violations),o=Se(n,void 0,e,void 0,a),i=Ue(e),r=new Date,c=new Map;for(const f of n){const g=xe(f.ruleId,f.target.selector);c.has(g)||c.set(g,f)}const l=Array.from(c.values()).sort((f,g)=>{const p={critical:0,serious:1,moderate:2,minor:3};return(p[f.impact]??99)-(p[g.impact]??99)}),d=new Map;for(const f of l){const g=d.get(f.ruleId)??[];g.push(f),d.set(f.ruleId,g)}const u=Array.from(d.entries()).map(([f,g])=>{var s;const p=(s=g.find(O=>O.target.opacityContext))==null?void 0:s.target.opacityContext,b=ri(f,p),w=g[0].impact,k=g.slice(0,8).map(O=>`<code>${T(O.target.selector)}</code>`).join(", "),y=g.length>8?` <em>(and ${g.length-8} more)</em>`:"";return`
1789
+ <section class="rule">
1790
+ <div class="rule-header">
1791
+ <span class="impact impact-${T(w)}">${T(w)}</span>
1792
+ <h3>${T(b.whatsWrong)}</h3>
1793
+ </div>
1794
+ <p class="why"><strong>Why this matters.</strong> ${T(b.whyItMatters)}</p>
1795
+ <p class="how"><strong>How to fix.</strong> ${T(b.howToFix)}</p>
1796
+ <p class="where">
1797
+ <strong>Where on the page (${g.length} ${g.length===1?"spot":"spots"}):</strong> ${k}${y}
1798
+ </p>
1799
+ <details class="techy">
1800
+ <summary>Technical reference for the developer</summary>
1801
+ <p>axe-core rule ID: <code>${T(f)}</code>. Full rule documentation:
1802
+ <a href="https://dequeuniversity.com/rules/axe/4.11/${T(f)}">dequeuniversity.com/rules/axe/4.11/${T(f)}</a>.</p>
1803
+ </details>
1804
+ </section>`}).join(""),h=t==="letter"?o.totals.critical>0?"Hi! I ran an automated accessibility check on our site and found some things I think we should fix. A few are flagged as <strong>critical</strong> — those tend to be the ones that come up in ADA-related lawsuits, so they're worth prioritizing.":o.totals.serious>0?"Hi! I ran an automated accessibility check on our site. We have some serious issues to fix — nothing immediately critical, but real problems that affect real users.":"Hi! I ran an automated accessibility check on our site. The issues found are mostly minor or moderate — worth tidying up but not urgent.":o.totals.critical>0?"This report summarizes the accessibility findings for the page below. <strong>Critical-impact issues</strong> were detected — these are the categories that most commonly drive ADA-related lawsuits and should be prioritized.":o.totals.serious>0?"This report summarizes the accessibility findings for the page below. Serious issues were detected — real problems for real users, though none rise to the critical level.":"This report summarizes the accessibility findings for the page below. Findings are mostly minor or moderate — worth addressing during normal site maintenance.",m=t==="letter"?"Accessibility check":"Accessibility report";return`<!doctype html>
1805
+ <html lang="en">
1806
+ <head>
1807
+ <meta charset="utf-8" />
1808
+ <title>${T(m)} — ${T(i)}</title>
1809
+ <style>
1810
+ body { font: 15px/1.6 -apple-system, system-ui, "Segoe UI", sans-serif; color: #0f172a; max-width: 720px; margin: 24px auto; padding: 0 24px 60px; }
1811
+ h1 { font-size: 22px; margin: 0 0 4px; }
1812
+ h2 { font-size: 16px; margin-top: 28px; }
1813
+ h3 { font-size: 15px; margin: 0; flex: 1; }
1814
+ .meta { color: #64748b; font-size: 13px; margin-bottom: 18px; }
1815
+ .summary { background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px; padding: 14px 18px; margin: 18px 0; }
1816
+ .summary .totals { display: flex; gap: 14px; flex-wrap: wrap; margin-top: 8px; font-size: 13px; }
1817
+ .summary .totals span { color: #64748b; }
1818
+ .summary .totals strong { color: #0f172a; }
1819
+ .rule { border: 1px solid #e2e8f0; border-radius: 6px; padding: 14px 18px; margin: 14px 0; background: white; }
1820
+ .rule-header { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
1821
+ .impact { font-size: 11px; padding: 2px 8px; border-radius: 4px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; }
1822
+ .impact-critical { background: #fee2e2; color: #991b1b; }
1823
+ .impact-serious { background: #ffedd5; color: #9a3412; }
1824
+ .impact-moderate { background: #fef9c3; color: #854d0e; }
1825
+ .impact-minor { background: #dbeafe; color: #1e40af; }
1826
+ .rule p { margin: 8px 0; }
1827
+ .where { font-size: 13px; color: #475569; }
1828
+ .where code { background: #f1f5f9; padding: 1px 4px; border-radius: 3px; font-size: 12px; }
1829
+ details.techy { margin-top: 10px; font-size: 12px; color: #64748b; }
1830
+ details.techy summary { cursor: pointer; color: #475569; }
1831
+ footer { margin-top: 40px; padding-top: 16px; border-top: 1px solid #e2e8f0; font-size: 12px; color: #94a3b8; }
1832
+ @media print { body { max-width: none; margin: 0; padding: 0.5in; } }
1833
+ </style>
1834
+ </head>
1835
+ <body>
1836
+ <h1>${T(m)} for ${T(i)}</h1>
1837
+ <p class="meta">${t==="letter"?"Ran":"Audit run"} ${T(r.toLocaleString())} · ${l.length} issue${l.length===1?"":"s"} grouped into ${d.size} ${d.size===1?"category":"categories"}</p>
1838
+
1839
+ <p>${h}</p>
1840
+
1841
+ <div class="summary">
1842
+ <p style="margin: 0;"><strong>${T(Jo[o.risk])}.</strong> ${T(Xo[o.risk])}</p>
1843
+ <div class="totals">
1844
+ ${o.totals.critical>0?`<span><strong>${o.totals.critical}</strong> critical</span>`:""}
1845
+ ${o.totals.serious>0?`<span><strong>${o.totals.serious}</strong> serious</span>`:""}
1846
+ ${o.totals.moderate>0?`<span><strong>${o.totals.moderate}</strong> moderate</span>`:""}
1847
+ ${o.totals.minor>0?`<span><strong>${o.totals.minor}</strong> minor</span>`:""}
1848
+ ${o.totals.unique===0?"<span>No automated issues detected — manual review still recommended.</span>":""}
1849
+ </div>
1850
+ </div>
1851
+
1852
+ <h2>${t==="letter"?"What I found":"Detailed findings"}</h2>
1853
+ ${u.length>0?u:"<p>No automated issues detected on this page.</p>"}
1854
+
1855
+ ${t==="letter"?`
1856
+ <h2>What I&apos;d like you to do</h2>
1857
+ <ol>
1858
+ <li>Take a look at the items above and let me know which ones you can address.</li>
1859
+ <li>For the ones marked <strong>critical</strong> — those tend to be the issues that show up in ADA-related lawsuit complaints, so I&apos;d prioritize those if you can.</li>
1860
+ <li>If anything is unclear or you need more detail, the technical references inside each section link to the full rule documentation. The whole audit was generated by an automated tool that runs the same checks Deque, IBM, and Microsoft all use.</li>
1861
+ </ol>
1862
+
1863
+ <p>Thanks for taking a look!</p>
1864
+ `:`
1865
+ <h2>Recommended next steps</h2>
1866
+ <ol>
1867
+ <li>Address <strong>critical</strong> findings first — these are the categories most commonly cited in ADA-related accessibility lawsuits.</li>
1868
+ <li>Address <strong>serious</strong> findings during the next site update cycle.</li>
1869
+ <li>Save or print this report for your records. It documents an automated audit was performed on ${T(r.toLocaleDateString())}.</li>
1870
+ <li>Re-run the audit after fixes to verify resolution and catch any regressions.</li>
1871
+ </ol>
1872
+ `}
1873
+
1874
+ <footer>
1875
+ <p>Generated by ${T(Ae)} on ${T(r.toISOString())}. Three evaluation layers ran: axe-core (rule-based), wcagcheckr DOM analyzers (state-matrix iteration + structural heuristics for criteria axe doesn't cover), and AI walkthroughs (real keyboard input + screenshot judgment for the 6 criteria neither rule-based nor static-DOM analysis can decide). All 50 applicable WCAG 2.1 AA criteria have an evaluation path. Content-meaning criteria (caption accuracy, alt-text correctness, sensory cues) benefit from a human spot-check before any formal conformance claim.</p>
1876
+ </footer>
1877
+ </body>
1878
+ </html>`}function ph(e){if(!e||e.length===0)return`
1879
+ <h2 id="manual">5. Manual assessment results</h2>
1880
+ <p style="font-size: 10pt; color: #475569;">
1881
+ No manual workflows were completed for this audit. wcagcheckr's three layers (axe-core, our
1882
+ DOM analyzers, AI walkthroughs) already cover all 50 WCAG 2.1 AA criteria structurally; the
1883
+ optional Guided Tests workflows below let you record explicit human verification for
1884
+ content-meaning criteria (caption accuracy, alt-text correctness, sensory cues) — useful
1885
+ when the audit needs to anchor to specific named auditors for formal conformance.
1886
+ When manual workflows are completed, their results are integrated into both the executive
1887
+ summary's compliance grade and this section's per-criterion findings.
1888
+ </p>`;const t=new Map(e.map(c=>[c.workflowId,c])),a=Ee.map(c=>fh(c,t.get(c.id))).filter(c=>c!==null).join("");let n=0,o=0,i=0,r=0;for(const c of Ee){r+=c.steps.length;const l=t.get(c.id);if(l)for(const d of c.steps){const u=l.steps[d.id];u&&(i++,u.status==="fail"&&(d.severity==="required"?n++:o++))}}return`
1889
+ <h2 id="manual">5. Manual assessment results</h2>
1890
+ <p style="font-size: 10pt; color: #475569; margin-bottom: 8pt;">
1891
+ Manual workflows performed by the auditor. ${i} of ${r} steps answered
1892
+ across ${e.length} workflow${e.length===1?"":"s"}.
1893
+ ${n>0?`<strong style="color: #b91c1c;">${n} required step${n===1?"":"s"} failed</strong> — each is a confirmed WCAG breach contributing to the compliance grade above.`:"No required steps failed."}
1894
+ ${o>0?` ${o} advisory step${o===1?"":"s"} failed (best-practice findings).`:""}
1895
+ </p>
1896
+ ${a}`}function fh(e,t){if(!t)return null;const a=[];for(const d of e.steps){const u=t.steps[d.id];u&&a.push({step:d,status:u.status,notes:u.notes})}if(a.length===0)return null;const n=a.filter(d=>d.status==="pass").length,o=a.filter(d=>d.status==="fail").length,i=a.filter(d=>d.status==="skip").length,r=a.filter(d=>d.status==="not-applicable").length,l=a.filter(d=>d.status==="fail").map(d=>`
1897
+ <tr>
1898
+ <td><span class="impact impact-${d.step.severity==="required"?"serious":"moderate"}">${T(d.step.severity)}</span></td>
1899
+ <td>${d.step.wcag?`<code>${T(d.step.wcag)}</code>`:"—"}</td>
1900
+ <td><strong>${T(d.step.question)}</strong>${d.notes?`<br/><span style="font-size: 9pt; color: #475569;">Auditor notes: ${T(d.notes)}</span>`:""}</td>
1901
+ </tr>
1902
+ `).join("");return`
1903
+ <h3>${T(e.name)}</h3>
1904
+ <p style="font-size: 10pt; color: #475569; margin: 0 0 6pt;">${T(e.blurb)}</p>
1905
+ <p style="font-size: 10pt; margin: 0 0 6pt;">
1906
+ <span class="cat-pass">${n} passed</span>
1907
+ ${o>0?` · <span class="cat-fail">${o} failed</span>`:""}
1908
+ ${i>0?` · <span style="color: #64748b;">${i} skipped</span>`:""}
1909
+ ${r>0?` · <span style="color: #64748b;">${r} N/A</span>`:""}
1910
+ · ${a.length} of ${e.steps.length} answered
1911
+ </p>
1912
+ ${o>0?`
1913
+ <table>
1914
+ <thead><tr><th style="width: 80pt;">Severity</th><th style="width: 70pt;">WCAG</th><th>Failed check + auditor notes</th></tr></thead>
1915
+ <tbody>${l}</tbody>
1916
+ </table>
1917
+ `:""}`}function Tt(e,t,a="defense",n,o,i,r,c,l){var S,R,P;const d=hh[a],u=((S=e[0])==null?void 0:S.componentId)??"unknown",h=Ue(e),m=r??e.reduce((I,$)=>I+$.durationMs,0),f=e.flatMap(I=>I.violations),g=n&&n.length>0?{runs:n,workflows:Ee}:void 0,p=Se(f,g,e,void 0,c,l),b=new Date,w=new Map;for(const I of f){const $=xe(I.ruleId,I.target.selector),C=`${I.currentState.pseudoState} · ${I.currentState.theme} · ${I.currentState.direction}`,M=w.get($);if(M){M._states.includes(C)||M._states.push(C);continue}w.set($,{...I,_states:[C]})}const k=Array.from(w.values()).sort((I,$)=>{const C={critical:0,serious:1,moderate:2,minor:3};return(C[I.impact]??99)-(C[$.impact]??99)}),y=sh(e),s=new Map;for(const I of y)for(const $ of I.violations){const C=xe($.ruleId,$.target.selector);s.has(C)||s.set(C,$._idx)}const O=k.map((I,$)=>`
1918
+ <tr>
1919
+ <td>${$+1}</td>
1920
+ <td><span class="impact impact-${T(I.impact)}">${T(I.impact)}</span></td>
1921
+ <td><code>${T(I.ruleId)}</code></td>
1922
+ <td>${T(I.wcagCriterion)} ${T(I.wcagLevel)}</td>
1923
+ <td>${T(I.description)}</td>
1924
+ <td><code class="sel">${T(I.target.selector)}</code></td>
1925
+ <td>${T(I._states.join(", "))}</td>
1926
+ </tr>`).join(""),E=new Set(f.map(I=>I.ruleId)),v=new Set(E);for(const I of e){const $=I.axeRulesEvaluated;if($){for(const C of $.passed)v.add(C.ruleId);for(const C of $.inapplicable)v.add(C.ruleId);for(const C of $.incomplete)v.add(C.ruleId)}}const D=Wi.map(I=>{const{conformance:$,hits:C}=qi(I,E,v,c);return`
1927
+ <tr class="${$==="Supports"?"supports":$==="Partially Supports"?"partial":$==="Not Evaluated"?"not-evaluated":"na"}">
1928
+ <td><code>${T(I.ref)}</code></td>
1929
+ <td>${T(I.title)} <span class="lvl">(${I.level})</span></td>
1930
+ <td>${$}</td>
1931
+ <td>${C.length>0?`Detected violations of: ${C.map(F=>`<code>${T(F)}</code>`).join(", ")}`:$==="Not Applicable"?"Not addressable by automated audit; requires manual review.":"No automated violations detected."}</td>
1932
+ </tr>`}).join("");return`<!doctype html>
1933
+ <html lang="en">
1934
+ <head>
1935
+ <meta charset="utf-8" />
1936
+ <title>${T(d.title)} — ${T(u)}</title>
1937
+ <style>
1938
+ *, *::before, *::after { box-sizing: border-box; }
1939
+ body { font: 11pt/1.55 -apple-system, system-ui, "Segoe UI", sans-serif; color: #0f172a; max-width: 8in; margin: 0.5in auto; padding: 0 0.25in; }
1940
+ h1 { font-size: 22pt; margin: 0 0 4pt; }
1941
+ h2 { font-size: 14pt; margin: 24pt 0 8pt; padding-bottom: 4pt; border-bottom: 1px solid #cbd5e1; page-break-after: avoid; }
1942
+ h3 { font-size: 12pt; margin: 14pt 0 6pt; page-break-after: avoid; }
1943
+ .doc-meta { font-size: 10pt; color: #475569; margin-bottom: 12pt; }
1944
+ .toc { background: #f8fafc; border: 1px solid #e2e8f0; padding: 10pt 14pt; border-radius: 4pt; margin: 14pt 0; }
1945
+ .toc ol { margin: 4pt 0 0; padding-left: 20pt; }
1946
+ .toc a { color: #1e40af; text-decoration: none; }
1947
+ .exec { border: 1px solid #cbd5e1; border-radius: 4pt; overflow: hidden; margin: 12pt 0; display: flex; }
1948
+ .exec-grade { width: 1.4in; display: flex; align-items: center; justify-content: center; color: white; font-size: 56pt; font-weight: 700; line-height: 1; }
1949
+ .exec-body { flex: 1; padding: 14pt 18pt; }
1950
+ .risk-tier { display: inline-block; padding: 2pt 10pt; border-radius: 12pt; font-size: 10pt; font-weight: 600; margin-bottom: 6pt; }
1951
+ .categories { display: grid; grid-template-columns: 1fr 1fr; gap: 4pt 16pt; margin: 8pt 0; font-size: 10pt; }
1952
+ .cat-pass { color: #15803d; }
1953
+ .cat-fail { color: #b91c1c; font-weight: 600; }
1954
+ .cat-unchecked { color: #b45309; }
1955
+ table { width: 100%; border-collapse: collapse; margin: 6pt 0 10pt; font-size: 9.5pt; }
1956
+ th, td { border: 1px solid #cbd5e1; padding: 4pt 7pt; text-align: left; vertical-align: top; }
1957
+ th { background: #f1f5f9; }
1958
+ td.sel, code { font-family: "SF Mono", Menlo, monospace; font-size: 9pt; word-break: break-all; }
1959
+ .impact { font-size: 8.5pt; padding: 1pt 5pt; border-radius: 2pt; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; }
1960
+ .impact-critical { background: #fee2e2; color: #991b1b; }
1961
+ .impact-serious { background: #ffedd5; color: #9a3412; }
1962
+ .impact-moderate { background: #fef9c3; color: #854d0e; }
1963
+ .impact-minor { background: #dbeafe; color: #1e40af; }
1964
+ tr.supports td:nth-child(3) { color: #15803d; font-weight: 600; }
1965
+ tr.partial td:nth-child(3) { color: #b45309; font-weight: 600; }
1966
+ tr.na td:nth-child(3) { color: #64748b; }
1967
+ .lvl { color: #94a3b8; font-weight: 400; font-size: 9pt; }
1968
+ .disclaimer { background: #fef3c7; border: 1px solid #f59e0b; padding: 10pt 14pt; border-radius: 3pt; margin: 16pt 0; font-size: 10pt; }
1969
+ .evidence-state { margin: 12pt 0 18pt; page-break-inside: avoid; }
1970
+ .evidence-state h3 { font-size: 11pt; margin-bottom: 4pt; }
1971
+ .evidence-state .caption { font-size: 9.5pt; color: #475569; margin-bottom: 4pt; }
1972
+ .evidence-frame { display: block; width: 100%; border: 1px solid #cbd5e1; }
1973
+ .evidence-frame svg { display: block; width: 100%; height: auto; }
1974
+ .evidence-frame svg rect.violation-mark { fill: rgba(220, 38, 38, 0.10); stroke: #dc2626; stroke-width: 3; }
1975
+ .evidence-frame svg text.violation-num { font: bold 14px ui-monospace, Menlo, monospace; fill: white; }
1976
+ .evidence-frame svg rect.label-bg { fill: #dc2626; }
1977
+ .methodology { font-size: 10pt; }
1978
+ .methodology dt { font-weight: 600; margin-top: 6pt; }
1979
+ .methodology dd { margin: 0 0 4pt 0; }
1980
+ footer { margin-top: 28pt; padding-top: 8pt; border-top: 1px solid #cbd5e1; font-size: 8.5pt; color: #64748b; }
1981
+ @media print { body { margin: 0; max-width: none; } .toc { page-break-after: always; } table, tr { page-break-inside: avoid; } }
1982
+ </style>
1983
+ </head>
1984
+ <body>
1985
+
1986
+ <h1>${T(d.title)}</h1>
1987
+ <p class="doc-meta">
1988
+ <strong>Subject:</strong> <code>${T(u)}</code> &nbsp;·&nbsp;
1989
+ <strong>URL:</strong> ${T(h)}<br/>
1990
+ <strong>Audit date:</strong> ${T(b.toLocaleString())} &nbsp;·&nbsp;
1991
+ <strong>Audit engine:</strong> axe-core ${T(((R=e[0])==null?void 0:R.axeVersion)??"n/a")} &nbsp;·&nbsp;
1992
+ <strong>Tool:</strong> ${T(Ae)} v${T(Ve())}
1993
+ </p>
1994
+
1995
+ <nav class="toc">
1996
+ <strong>Contents</strong>
1997
+ <ol>
1998
+ <li><a href="#exec">Executive summary</a></li>
1999
+ <li><a href="#methodology">Methodology &amp; scope</a></li>
2000
+ <li><a href="#findings">Detailed findings (automated)</a></li>
2001
+ <li><a href="#evidence">Visual evidence (per-state screenshots)</a></li>
2002
+ <li><a href="#manual">Manual assessment results</a></li>
2003
+ <li><a href="#conformance">WCAG 2.1 conformance assessment</a></li>
2004
+ <li><a href="#remediation">${a==="evidence"?"Recommended approach before legal action":"Remediation plan"}</a></li>
2005
+ <li><a href="#disclaimer">Disclaimer &amp; limitations</a></li>
2006
+ </ol>
2007
+ </nav>
2008
+
2009
+ <h2 id="exec">1. Executive summary</h2>
2010
+ <div class="exec">
2011
+ <div class="exec-grade" style="background: ${uh[p.letter]};">${p.letter}</div>
2012
+ <div class="exec-body">
2013
+ <span class="risk-tier" style="background: ${lh[p.risk]}; color: ${dh[p.risk]};">
2014
+ ${T(ch[p.risk])}
2015
+ </span>
2016
+ <p style="margin: 0 0 6pt;"><strong>${p.totals.unique} unique violations detected</strong> across ${e.length} state combinations
2017
+ (${p.totals.critical} critical · ${p.totals.serious} serious · ${p.totals.moderate} moderate · ${p.totals.minor} minor).</p>
2018
+ <p style="margin: 0; font-size: 10pt; color: #475569;">${T(Hs[p.risk])}</p>
2019
+ </div>
2020
+ </div>
2021
+ ${i&&(i.lead||i.body||i.closer)?`
2022
+ <div style="margin-top: 12pt; padding: 12pt 14pt; border-left: 4px solid #2563eb; background: #f0f6ff; border-radius: 0 4pt 4pt 0;">
2023
+ <p style="margin: 0 0 6pt; font-size: 10pt; color: #1e40af; text-transform: uppercase; letter-spacing: 0.04em;">
2024
+ AI-generated narrative summary (${T(i.model)})
2025
+ </p>
2026
+ ${i.lead?`<p style="margin: 0 0 8pt; font-size: 11.5pt; font-weight: 500;">${T(i.lead)}</p>`:""}
2027
+ ${i.body?i.body.split(/\n{2,}/).map(I=>`<p style="margin: 0 0 6pt;">${T(I.trim())}</p>`).join(`
2028
+ `):""}
2029
+ ${i.closer?`<p style="margin: 8pt 0 0; font-style: italic;">${T(i.closer)}</p>`:""}
2030
+ <p style="margin: 6pt 0 0; font-size: 9pt; color: #475569;">
2031
+ This narrative was generated by an LLM from the audit's structured findings. The numerical
2032
+ data in §1 above and the per-violation details in §3 are the authoritative record.
2033
+ </p>
2034
+ </div>`:""}
2035
+
2036
+ <h3>Per-category breakdown (top-6 lawsuit-magnet categories)</h3>
2037
+ <div class="categories">
2038
+ ${p.categories.map(I=>{const $=I.status==="pass"?"✓":I.status==="fail"?"✗":"⚠",C=I.status==="pass"?"cat-pass":I.status==="fail"?"cat-fail":"cat-unchecked",M=I.status==="fail"?` (${I.violationCount})`:I.needsManualCheck?" — requires manual review":"";return`<div class="${C}">${$} ${T(I.label)}${T(M)}</div>`}).join("")}
2039
+ </div>
2040
+
2041
+ <h2 id="methodology">2. Methodology &amp; scope</h2>
2042
+ <dl class="methodology">
2043
+ <dt>Audit engine</dt>
2044
+ <dd>axe-core ${T(((P=e[0])==null?void 0:P.axeVersion)??"n/a")} (industry-standard, used by Deque, IBM, Microsoft, and Google's Lighthouse).
2045
+ Rule sets enabled: WCAG 2.0 A/AA, WCAG 2.1 A/AA, WCAG 2.2 A/AA, axe-core best-practice.</dd>
2046
+ <dt>State coverage</dt>
2047
+ <dd>${e.length} state combinations were audited, including pseudo-states (<code>:hover</code>, <code>:focus</code>, <code>:focus-visible</code>, <code>:active</code>, <code>:disabled</code>),
2048
+ color-scheme themes (<code>prefers-color-scheme: dark</code>, <code>forced-colors: active</code>),
2049
+ text direction (LTR / RTL), and viewport breakpoints (mobile / tablet / desktop).
2050
+ Audits at non-default states are uncommon among automated tools and represent significant additional WCAG coverage relative to single-state audits.</dd>
2051
+ <dt>Audit duration</dt>
2052
+ <dd>${(m/1e3).toFixed(1)} seconds across ${e.length} states (${(m/Math.max(1,e.length)).toFixed(0)}ms per state).</dd>
2053
+ <dt>Scope</dt>
2054
+ <dd>Component / region: <code>${T(u)}</code></dd>
2055
+ <dt>What this audit covers</dt>
2056
+ <dd>All 50 applicable WCAG 2.1 AA criteria evaluated across three layers: <strong>axe-core</strong> (rule-based DOM checks — color contrast, image alt presence, form labels, accessible names, document structure, ID uniqueness, ARIA validity), <strong>${T(Ae)} DOM analyzers</strong> (state-matrix iteration through hover / focus / active / disabled states + structural heuristics for criteria axe doesn't reach — pointer gestures, motion actuation, reflow, autoplay, character key shortcuts, redundant entry, sensory cues, text spacing, images-of-text candidates), and <strong>AI walkthroughs</strong> (real keyboard input + screenshot-by-screenshot vision judgment for the 6 criteria neither rule-based nor static-DOM analysis can decide — focus order, no keyboard trap, focus visible, meaningful sequence, non-text contrast, label-in-name).</dd>
2057
+ <dt>What still requires human spot-checks</dt>
2058
+ <dd>Content meaning, not structural correctness: whether caption text actually matches the audio, whether alt text accurately describes the image's purpose, whether sensory phrasing ("click the red button") has an inclusive alternative. The ${T(Ae)} Guided Tests workflows provide a structured manual-audit path for these content-meaning criteria.</dd>
2059
+ </dl>
2060
+
2061
+ <h2 id="findings">3. Detailed findings</h2>
2062
+ ${k.length===0?"<p>No automated violations were detected during this audit.</p>":`<table>
2063
+ <thead>
2064
+ <tr><th>#</th><th>Impact</th><th>Rule</th><th>WCAG</th><th>Description</th><th>Selector</th><th>States</th></tr>
2065
+ </thead>
2066
+ <tbody>${O}</tbody>
2067
+ </table>`}
2068
+
2069
+ ${nh(p.walkthroughFindings,p.walkthroughAcknowledgements,l??[])}
2070
+
2071
+ <h2 id="evidence">4. Visual evidence (per-state screenshots)</h2>
2072
+ ${y.length===0?'<p style="font-size: 10pt; color: #64748b;">No per-state screenshots were captured for this audit (no states had violations, or screenshot capture failed).</p>':`<p class="caption">Top ${y.length} state${y.length===1?"":"s"} by violation count shown below. Red rectangles mark violation locations; numbers correspond to entries in §3.</p>`+y.map(I=>{const $=I.violations.filter(C=>C.target.boundingRect).map(C=>{const M=C.target.boundingRect,F=String(C._idx).length*8+14,q=22;return[`<rect class="violation-mark" x="${M.x}" y="${M.y}" width="${M.w}" height="${M.h}" />`,`<rect class="label-bg" x="${M.x}" y="${Math.max(0,M.y-q)}" width="${F}" height="${q}" rx="2" />`,`<text class="violation-num" x="${M.x+6}" y="${Math.max(0,M.y-q)+16}">${C._idx}</text>`].join("")}).join("");return`
2073
+ <div class="evidence-state">
2074
+ <h3>State: ${T(I.stateLabel)} (${I.violations.length} violation${I.violations.length===1?"":"s"})</h3>
2075
+ <div class="evidence-frame">
2076
+ <svg viewBox="0 0 ${Ao} ${ko}" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet">
2077
+ <image href="${I.screenshotDataUrl}" x="0" y="0" width="${Ao}" height="${ko}" preserveAspectRatio="xMidYMid slice" />
2078
+ ${$}
2079
+ </svg>
2080
+ </div>
2081
+ </div>`}).join("")}
2082
+
2083
+ ${ph(n)}
2084
+
2085
+ <h2 id="conformance">6. WCAG 2.1 conformance assessment</h2>
2086
+ <p style="font-size: 10pt;">Per-criterion conformance based on automated detection. Conformance values follow the VPAT 2.5-INT vocabulary.${t&&t.baselineSnapshotMeta?" Comparison against an accepted baseline is available; see Section 6.":""}</p>
2087
+ <table>
2088
+ <thead><tr><th>Criterion</th><th>Title</th><th>Conformance</th><th>Notes</th></tr></thead>
2089
+ <tbody>${D}</tbody>
2090
+ </table>
2091
+
2092
+ ${(()=>{const I=o!==void 0,$=I?"8":"7",C=I?"9":"8",M=I?`<h2 id="audit-history">7. Audit history</h2>
2093
+ <p style="font-size: 10pt; margin-bottom: 8pt;">Chronological record of audits this user has run, with cryptographic hashes of each audit's identifying fields (component, page URL, grade, severity totals, axe version, timestamp, state-matrix size). Hashes are SHA-256 of canonical-JSON serialization, so any tampering with a logged audit's identity is detectable client-side. Entries marked <em>Anchored</em> additionally carry a third-party RFC 3161 trusted timestamp issued by the named TSA — those entries cannot be backdated.</p>
2094
+ ${o&&o.length>0?`<table>
2095
+ <thead>
2096
+ <tr>
2097
+ <th>Captured</th>
2098
+ <th>Component</th>
2099
+ <th>Page</th>
2100
+ <th>Grade</th>
2101
+ <th>Crit</th>
2102
+ <th>Sev</th>
2103
+ <th>States</th>
2104
+ <th>Hash (first 12)</th>
2105
+ <th>Source</th>
2106
+ </tr>
2107
+ </thead>
2108
+ <tbody>${o.map(q=>`
2109
+ <tr>
2110
+ <td style="white-space: nowrap;">${T(q.capturedAt)}</td>
2111
+ <td><code>${T(q.componentId)}</code></td>
2112
+ <td style="word-break: break-all;">${T(q.pageUrl)}</td>
2113
+ <td><strong>${T(q.grade)}</strong></td>
2114
+ <td>${q.totals.critical}</td>
2115
+ <td>${q.totals.serious}</td>
2116
+ <td>${q.statesAudited}</td>
2117
+ <td><code title="${T(q.hash)}">${T(q.hash.slice(0,12))}…</code></td>
2118
+ <td>${q.receipt?`RFC 3161 (${T(q.receipt.tsaName)})`:"Local"}</td>
2119
+ </tr>`).join("")}
2120
+ </tbody>
2121
+ </table>`:'<p style="color: #64748b; font-style: italic;">No prior audits recorded — this is the first audit on record for this installation.</p>'}
2122
+ `:"",F=a==="evidence"?`<h2 id="remediation">${$}. Recommended approach before legal action</h2>
2123
+ <div style="background: #f0f9ff; border: 1px solid #38bdf8; padding: 12pt 16pt; border-radius: 4pt; font-size: 10.5pt;">
2124
+ <p style="margin: 0 0 8pt;"><strong>Most businesses are unaware their digital properties have accessibility defects.</strong> Many will fix them once notified. A good-faith notice-and-cure approach is more likely to resolve the underlying accessibility barrier (which is the actual purpose of disability-rights law) and, where remediation does not occur, strengthens any subsequent legal claim by establishing both notice and a reasonable opportunity to comply.</p>
2125
+ <p style="margin: 0 0 8pt;"><strong>Recommended steps before considering legal action:</strong></p>
2126
+ <ol style="margin: 0 0 8pt; padding-left: 20pt;">
2127
+ <li><strong>Send written notice to the site owner / business operator.</strong> Include this evidence bundle as an attachment. Identify yourself, the date you encountered the barrier, the specific access need affected (e.g., screen-reader use, keyboard-only navigation, low-vision contrast), and a reasonable timeframe for response (commonly 30–60 days, depending on jurisdiction and severity).</li>
2128
+ <li><strong>Offer to discuss remediation.</strong> Many businesses don't have an accessibility lead — pointing them at the WCAG criteria in §5 of this document and the per-rule canonical fixes (axe-core's documentation, linked from each violation's "More info") gives them a starting point.</li>
2129
+ <li><strong>Document the response (or lack of one).</strong> Save the original notice, any reply, dates, and any remediation activity (or absence of it).</li>
2130
+ <li><strong>If after the notice period the business has not engaged or remediated</strong>, the documented good-faith notice combined with this evidence bundle materially strengthens an ADA Title III, EAA, EN 301 549, AODA, or analogous claim, depending on jurisdiction.</li>
2131
+ </ol>
2132
+ <p style="margin: 0; font-size: 9.5pt; color: #475569;">This guidance is general and not legal advice. Specific procedural requirements vary by jurisdiction (e.g., some US states require pre-suit notice under "Unruh Act" amendments; others do not). Consult qualified counsel about the procedural posture appropriate for your situation.</p>
2133
+ </div>`:`<h2 id="remediation">${$}. Remediation plan</h2>
2134
+ <p>Each violation in §3 has a documented canonical fix pattern. The remediation workflow we recommend:</p>
2135
+ <ol>
2136
+ <li>Findings are classified by severity. Address critical and serious findings first.</li>
2137
+ <li>For each rule, ${T(Ae)} produces a structured fix prompt that can be processed by a code-fluent AI assistant operating against the codebase. The fix prompt includes per-violation context (selector, state, recipe), pre-flight safety checks (clean git working tree, atomic commits per fix), and explicit constraints (no scope creep, native HTML preferred over ARIA, fix state-specific failures in state-specific styles).</li>
2138
+ <li>After remediation, re-run the audit to verify each violation is resolved without introducing regressions.</li>
2139
+ <li>Manual workflows (keyboard, screen-reader, focus-management, forms) should be re-completed via the Guided Tests panel.</li>
2140
+ <li>Once resolved, the audited state should be accepted as a baseline; future audits surface only NEW debt vs. that baseline.</li>
2141
+ </ol>`;return`${M}
2142
+
2143
+ ${F}
2144
+
2145
+ <h2 id="disclaimer">${C}. Disclaimer &amp; limitations</h2>`})()}
2146
+ <div class="disclaimer">
2147
+ <p style="margin: 0 0 6pt;"><strong>${T(d.disclaimerHeading)}</strong></p>
2148
+ <p style="margin: 0 0 6pt;">${d.disclaimerBody}</p>
2149
+ <p style="margin: 0;">This document represents detected violations at the time and scope of audit. Subsequent changes to the audited resource may produce different results. The presence or absence of detected violations in this report is not, by itself, ${a==="defense"?"a defense against":"a determination of"} any specific accessibility claim, regulation, or legal action. This document is provided for ${T(d.documentLabel)} purposes.</p>
2150
+ </div>
2151
+
2152
+ <footer>
2153
+ Generated by ${T(Ae)} v${T(Ve())} on ${T(b.toISOString())}.
2154
+ Audit run identifier: ${T(b.getTime().toString(36))}-${T(u.replace(/[^a-z0-9]/gi,"").slice(0,8))}.
2155
+ </footer>
2156
+
2157
+ </body>
2158
+ </html>`}async function gh(){const t=(await chrome.storage.local.get("aiConfig")).aiConfig;return{...lr,...t??{}}}async function Oe(e){return un(e.map(t=>t.componentId))}async function Pt(e){return pn(e.map(t=>t.componentId))}async function Da(e,t,a,n){const o=await gh();if(!o.enabled||!o.apiKey||e.length===0)return null;const i=e.flatMap(d=>d.violations),r=Se(i,void 0,e,void 0,n),c=r.categories.filter(d=>d.status==="fail").sort((d,u)=>u.violationCount-d.violationCount).slice(0,3).map(d=>d.label),l=e[0];return Li({framing:t,riskTier:r.risk,grade:r.letter,totals:{critical:r.totals.critical,serious:r.totals.serious,moderate:r.totals.moderate,minor:r.totals.minor,unique:r.totals.unique},riskDrivers:c,pageUrl:(l==null?void 0:l.pageUrl)??(l==null?void 0:l.scope)??"",auditDate:(l==null?void 0:l.startedAt)??new Date().toISOString(),priorAuditCount:a},o)}function mh(){return fe("EXPORT_REQUEST",async e=>{var t;if(await je())return{type:"EXPORT_RESPONSE",format:e.format,content:"wcagcheckr is in private build phase. A team license is required to export reports."};if(e.siteCrawlSource&&e.format!=="ai-prompt-site-crawl"){const a=await Wa();if(!a||a.length===0)return{type:"EXPORT_RESPONSE",format:e.format,content:"No site-crawl results found in storage. Run a site crawl from the Crawl tab first, then try this export again."};e.results=a.flatMap(n=>n.results??[])}if(e.format==="sarif")return{type:"EXPORT_RESPONSE",format:"sarif",content:JSON.stringify(Ui(e.results,e.delta),null,2)};if(e.format==="junit")return{type:"EXPORT_RESPONSE",format:"junit",content:Fi(e.results,e.delta)};if(e.format==="html-print")return{type:"EXPORT_RESPONSE",format:"html-print",content:xn(e.results,e.delta,e.wallClockMs)};if(e.format==="vpat"){const a=await Oe(e.results),n=await Pt(e.results),o=new Set(n.map(r=>`${r.criterionId}::${r.pageUrl}`)),i=a.map(r=>o.has(`${r.criterionId}::${r.pageUrl}`)?{...r,verdict:"pass",reasoning:`Human verified: ${r.reasoning}`}:r);return{type:"EXPORT_RESPONSE",format:"vpat",content:Gi(e.results,e.delta,i)}}if(e.format==="ai-prompt"){const a=await Bs();let n="";const o=[];try{const r=((t=e.results[0])==null?void 0:t.componentId)??"",c=r?await Va(r):[];for(const l of c)o.push({criterionId:l.criterionId,verdict:l.verdict,reasoning:l.reasoning,pageUrl:l.pageUrl});n=bh(c)}catch(r){console.warn("[report-exporter] walkthrough render skipped:",r)}const i=Vi(e.results,e.delta,e.manualRuns,e.siteCrawlReport,e.dismissedKeys,e.incompleteResolutions,a,o,e.acknowledgedKeys);return{type:"EXPORT_RESPONSE",format:"ai-prompt",content:n?`${i}
2159
+
2160
+ ${n}`:i}}if(e.format==="defense-bundle"){const a=await At(),n=await Oe(e.results),o=await Pt(e.results),i=await Da(e.results,"defense",a.length,n);return{type:"EXPORT_RESPONSE",format:"defense-bundle",content:Tt(e.results,e.delta,"defense",e.manualRuns,a,i,e.wallClockMs,n,o)}}if(e.format==="deposition-packet"){const a=await At(),n=await Oe(e.results),o=await Pt(e.results),i=await Da(e.results,"defense",a.length,n),r=await et().catch(()=>null);return{type:"EXPORT_RESPONSE",format:"deposition-packet",content:(await Di({results:e.results,delta:e.delta,manualRuns:e.manualRuns,aiSummary:i,licenseId:r??void 0,wallClockMs:e.wallClockMs,interactiveAuditResults:n,walkthroughAcks:o})).html}}if(e.format==="methodology-doc")return{type:"EXPORT_RESPONSE",format:"methodology-doc",content:Wu({results:e.results,manualRuns:e.manualRuns})};if(e.format==="conformance-crosswalk")return{type:"EXPORT_RESPONSE",format:"conformance-crosswalk",content:Bu({results:e.results})};if(e.format==="ticket-bundle")return{type:"EXPORT_RESPONSE",format:"ticket-bundle",content:Xu({results:e.results})};if(e.format==="executive-report"){const a=await Oe(e.results),n=await Pt(e.results),{loadAcknowledgedKeysForComponent:o}=await an(async()=>{const{loadAcknowledgedKeysForComponent:d}=await Promise.resolve().then(()=>Zr);return{loadAcknowledgedKeysForComponent:d}},void 0),i=new Set;for(const d of e.results)d.componentId&&i.add(d.componentId);const r=await Promise.all(Array.from(i).map(d=>o(d))),c=new Set;for(const d of r)for(const u of d)c.add(u);const l=await si(i);return{type:"EXPORT_RESPONSE",format:"executive-report",content:th(e.results,{...e.executiveInputs??{},interactiveAuditResults:a,walkthroughAcks:n,acknowledgedMatchKeys:c,humanCriterionVerdicts:l})}}if(e.format==="evidence-bundle"){const a=await At(),n=await Oe(e.results),o=await Da(e.results,"evidence",a.length,n);return{type:"EXPORT_RESPONSE",format:"evidence-bundle",content:ji(e.results,e.delta,e.manualRuns,a,o,e.wallClockMs,n)}}if(e.format==="developer-letter"){const a=await Oe(e.results);return{type:"EXPORT_RESPONSE",format:"developer-letter",content:zi(e.results,a)}}if(e.format==="owner-report"){const a=await Oe(e.results);return{type:"EXPORT_RESPONSE",format:"owner-report",content:Hi(e.results,a)}}if(e.format==="ai-prompt-owner"){const a=await Oe(e.results);return{type:"EXPORT_RESPONSE",format:"ai-prompt-owner",content:Bi(e.results,a)}}if(e.format==="accessibility-statement-markdown"||e.format==="accessibility-statement-html"){if(!e.statementInputs)return{type:"EXPORT_RESPONSE",format:e.format,content:"# Accessibility statement — error\n\nMissing `statementInputs` payload. The accessibility-statement export needs organization info (name, contact, conformance status). Provide it via the side panel UI or the EXPORT_REQUEST `statementInputs` field."};const a=e.format==="accessibility-statement-html"?"html":"markdown";return{type:"EXPORT_RESPONSE",format:e.format,content:Xs({results:e.results,inputs:e.statementInputs,outputFormat:a,customDescriptions:e.statementCustomDescriptions})}}if(e.format==="ai-prompt-site-crawl"){const[a,n]=await Promise.all([Uo(),Wa()]);if(!a||!n||n.length===0)return{type:"EXPORT_RESPONSE",format:"ai-prompt-site-crawl",content:`# Accessibility Fix Request — Site Crawl
2161
+
2162
+ No crawl results found in storage. Run a site crawl from the Crawl tab and try again.
2163
+ `};const o=new Map;return await Promise.all(n.map(async i=>{if(i.componentId)try{const r=await Va(i.componentId);o.set(i.componentId,r.map(c=>({criterionId:c.criterionId,verdict:c.verdict,reasoning:c.reasoning,pageUrl:c.pageUrl})))}catch{}})),{type:"EXPORT_RESPONSE",format:"ai-prompt-site-crawl",content:Yi(n,a,o)}}return{type:"EXPORT_RESPONSE",format:"json",content:JSON.stringify(Pi(e.results,e.delta),null,2)}})}function bh(e){if(!Array.isArray(e)||e.length===0)return"";const t=[];t.push("## AI keyboard walkthroughs — interactive WCAG audits"),t.push(""),t.push("The audit ran AI-driven keyboard walkthroughs against the page. Each one Tab-walked the live DOM and AI judged the result against a specific WCAG criterion. Fail verdicts here are NOT heuristic guesses — AI watched the actual focus path and identified a concrete problem. Treat each fail as a required fix."),t.push("");for(const a of e)if(!(!a||typeof a!="object"))try{const n=a.verdict==="pass"||a.verdict==="fail"?a.verdict:"uncertain",o=n==="pass"?"✓ PASS":n==="fail"?"✗ FAIL":"? UNCERTAIN",i=String(a.criterionId??"?"),r=i==="2.4.3"?"Focus Order (Level A)":i==="2.1.2"?"No Keyboard Trap (Level A)":i==="2.4.7"?"Focus Visible (Level AA)":i==="1.3.2"?"Meaningful Sequence (Level A)":i==="1.4.11"?"Non-text Contrast (Level AA)":i==="2.5.3"?"Label in Name (Level A)":i,c=typeof a.confidence=="number"?Math.max(0,Math.min(1,a.confidence)):0,l=typeof a.reasoning=="string"?a.reasoning:"",d=typeof a.model=="string"?a.model:"unknown";t.push(`### WCAG ${i} — ${r} — ${o}`),t.push(""),t.push(`**AI reasoning** (${Math.round(c*100)}% confidence, model ${d}):`),t.push(""),t.push(l?`> ${l.replace(/\n/g,`
2164
+ > `)}`:"> (no reasoning recorded)"),t.push(""),n==="fail"?(i==="2.4.3"?(t.push("**Fix this:** Reorder DOM source so the natural tab sequence matches the visual reading order, or assign `tabindex` only where you intentionally diverge from source order (and document the reason). Avoid positive `tabindex` values. The tab sequence the AI walked is below — fix every element whose position is out of order vs. its visible position on the page."),t.push(""),t.push('**Anti-patterns to avoid:** adding `tabindex="1"`, `"2"`, etc. (positive values create a parallel focus order that\'s a maintenance nightmare); using `tabindex="-1"` to skip elements that should be reachable; adding JS focus() management to "fix" what DOM-order should solve directly.'),t.push(""),t.push("**Verify by re-auditing:** after the fix, the 2.4.3 walkthrough should return pass.")):i==="2.1.2"?(t.push("**Fix this:** Identify any region where focus gets stuck. Common offenders: modal dialogs that intercept Tab without an escape route (add Escape handler + restore focus on close), custom widgets that loop within their own focus trap without an exit, contenteditable elements consuming Tab as literal input. Ensure forward Tab AND backward Shift+Tab can always reach the rest of the page from any focused element."),t.push(""),t.push("**Anti-patterns to avoid:** suppressing Tab in keydown handlers (event.preventDefault on Tab without specific intent); using `inert` on the wrong container so users can't reach controls that should be focusable."),t.push(""),t.push("**Verify by re-auditing:** after the fix, the 2.1.2 walkthrough should return pass — Tab should cycle forward AND Shift+Tab should cycle backward through every focusable element on the page.")):i==="2.4.7"?(t.push("**Fix this:** Every focusable element must show a visible focus indicator. If you use `outline: none` (e.g., for design reasons), you MUST compensate with a `:focus-visible` box-shadow, border, or background change that has at least 3:1 contrast against the unfocused state. Check every selector in the recorded walk below."),t.push(""),t.push("**Anti-patterns to avoid:** `outline: 0 !important` in a global reset without a `:focus-visible` replacement; relying on `:focus` instead of `:focus-visible` (the latter is the correct hook for keyboard-only focus styling); using ONLY a background color change with insufficient contrast (3:1 minimum against the unfocused state)."),t.push(""),t.push("**Verify by re-auditing:** after the fix, the 2.4.7 walkthrough should return pass on every captured focus state.")):i==="1.3.2"?(t.push("**Fix this:** ensure the DOM source order matches the visual reading order. Screen readers read DOM order; CSS (`flex-direction: row-reverse`, `order:`, `grid-area`, absolute positioning) does NOT reorder what the SR hears. Reorder the HTML so the source flows in the same direction the eye reads, then use minimal CSS for visual placement. Where DOM-reorder is genuinely not feasible (legitimate multi-column footers, RTL emulation, sticky-banner overlays), document the decision and verify the SR experience is still coherent."),t.push(""),t.push('**Anti-patterns to avoid:** using `tabindex` to "fix" reading order (tabindex affects keyboard order, not screen-reader order); adding `aria-flowto` (broadly unsupported; not a real fix); leaving inline `display: none` content that holds the "logical" reading order (it won\'t reach the SR).'),t.push(""),t.push("**Verify by re-auditing:** after the fix, the 1.3.2 walkthrough should return pass — the AI judges whether DOM-flagged divergences are real reading-order breaks vs. legitimate layout patterns.")):i==="1.4.11"?(t.push("**Fix this:** every UI component (button border, form input border, focus ring, icon, status indicator, graphical content needed for understanding) must have at least 3:1 contrast against adjacent colors. For each failed element: raise the foreground/border color, add a darker/lighter background to increase contrast, or thicken the indicator (e.g., 2px instead of 1px border). The AI vision identified specific elements; fix the CSS for each."),t.push(""),t.push('**Anti-patterns to avoid:** ONLY changing opacity/transparency (the rendered contrast on a varied background still fails); using box-shadow as a "border" without ensuring the shadow color meets 3:1 against the surrounding pixels; relying on hover state for contrast (must be visible in default state too).'),t.push(""),t.push("**Verify by re-auditing:** after the fix, the 1.4.11 walkthrough should return pass. Re-run with a fresh screenshot; AI will re-judge the updated colors.")):i==="2.5.3"&&(t.push('**Fix this:** every interactive control\'s accessible name must contain its visible text (case-insensitive, allowing icons/punctuation to be ignored). The AI listed which elements failed. For each, choose ONE of: (a) ensure the visible text matches the accessible name (e.g., button text); (b) use `aria-label` or `aria-labelledby` that STARTS WITH the visible text (e.g., `aria-label="Search — search the catalog"` when visible text is "Search"); (c) remove the conflicting `aria-label` and let the textContent serve as the name. Test by saying "click [visible text]" with speech recognition (Voice Control / Dragon) — the correct fix activates the right control.'),t.push(""),t.push('**Anti-patterns to avoid:** using `aria-label` that REPLACES the visible text entirely (e.g., visible "Cancel" + `aria-label="Close form"` — speech-recognition users saying "click Cancel" will fail); using `title=` attribute alone (not all SRs respect it as the accessible name); using `aria-describedby` for naming (that\'s for descriptions, not names).'),t.push(""),t.push("**Verify by re-auditing:** after the fix, the 2.5.3 walkthrough should return pass — the DOM walker re-collects {visibleText, accessibleName} pairs and AI re-judges ambiguous cases.")),t.push("")):n==="pass"?(t.push("_AI walked the criterion and found no issues. No action needed; this is positive evidence the criterion is met._"),t.push("")):(t.push("_AI could not confidently decide. Manually verify or re-run the walkthrough after rendering changes settle._"),t.push(""));const h=(Array.isArray(a.steps)?a.steps:[]).filter(m=>m&&m.action!=="initial");if(h.length>0){t.push(`<details><summary>Recorded ${h.length}-step keyboard walkthrough</summary>`),t.push(""),t.push("| # | Action | Selector | Role | Accessible name |"),t.push("|---|---|---|---|---|");for(const m of h.slice(0,30)){const f=m.focused??{selector:"?",role:"",name:""},g=String(f.selector??"?"),p=String(f.role??"-")||"-",b=String(f.name??"(no name)").replace(/\|/g,"\\|").slice(0,60),w=String(m.action??""),k=typeof m.ordinal=="number"?m.ordinal:"?";t.push(`| ${k} | ${w} | \`${g}\` | ${p} | ${b} |`)}h.length>30&&t.push(`| … | ${h.length-30} more steps elided | | | |`),t.push(""),t.push("</details>"),t.push("")}}catch(n){console.warn("[report-exporter] walkthrough render skipped:",n)}return t.push("---"),t.join(`
2165
+ `)}function yh(e){var a;const t=new Set;for(const n of e){const o=n.currentState;if(!o)continue;const i=[];o.pseudoState&&o.pseudoState!=="default"&&i.push(o.pseudoState),o.theme&&o.theme!=="light"&&i.push(o.theme),o.direction&&o.direction!=="ltr"&&i.push(o.direction),(a=o.breakpoint)!=null&&a.id&&o.breakpoint.id!=="desktop"&&i.push(o.breakpoint.id),o.ariaVariation&&o.ariaVariation.id!=="none"&&i.push(`aria=${o.ariaVariation.id}`),t.add(i.length===0?"default":i.join("/"))}return{stateCount:t.size,states:Array.from(t).sort()}}const wh=/-needs-site-crawl$/;function Yi(e,t,a=new Map){var r;const n=[];n.push("# Accessibility Fix Request — Site Crawl"),n.push(""),n.push(`You are a senior accessibility engineer pairing with the user on their codebase. The user has crawled ${t.pagesAudited} page${t.pagesAudited===1?"":"s"} on this site and below you'll find the per-page WCAG findings + the cross-page consistency findings. Your task: walk page by page, apply fixes, and produce one atomic commit per fix.`),n.push(""),n.push(`> **Note on this report's scope.** This is a triage + bulk-fix prompt covering all crawled pages in one document. The user can also drop into any single page in the wcagcheckr side panel for per-finding acknowledgements, guided manual checks, fix-preview overlays, and interactive walkthrough re-runs — those are intentionally NOT in this report; they live in the per-page UI surface. If you need that level of control for a specific finding, suggest the user click that page in the Crawl tab's "Pages, worst first" list.`),n.push(""),n.push("## ⚠ Work contract — read before doing anything else"),n.push(""),n.push(`**Below you have ${t.totalUniqueViolations} unique violation${t.totalUniqueViolations===1?"":"s"} across ${t.pagesAudited} page${t.pagesAudited===1?"":"s"}.** When you respond, every numbered finding needs an outcome. Do not stop early. Do not declare done without addressing every finding.`),n.push(""),n.push("**For each numbered finding, produce one of:**"),n.push(""),n.push("- ✅ **Applied** — the canonical fix was applied. Cite the file + line. If one shared-component fix resolves N findings, group them under the SAME fix and list which findings it covers."),n.push("- ⚠ **Blocked** — explicitly state WHY (missing info, ambiguous user content, dependency change required). One sentence per blocked finding."),n.push("- ❓ **Needs user decision** — list the options inline."),n.push(""),n.push(`**The single answer you cannot use: "skipped" or "didn't get to it."** Stop and tell the user if you ran out of context — do not silently omit items.`),n.push(""),n.push("**Required final response format** — completion table:"),n.push(""),n.push("```"),n.push("| # | Rule ID | Page(s) | Status | Notes |"),n.push("|---|---------|---------|--------|-------|"),n.push("| 1 | <rule> | <url> | ✅ / ⚠ / ❓ | <file:line OR reason> |"),n.push(`| ${t.totalUniqueViolations} | ... | ... | ... | ... |`),n.push("```"),n.push(""),n.push("**Every row must be present.** One pass through this prompt should produce a fix or a clear next action for every finding — not a partial sweep that forces the user to re-run."),n.push(""),n.push("---"),n.push(""),n.push("## Site-wide summary"),n.push(""),n.push(`- **Start URL:** ${t.startUrl}`),n.push(`- **Pages audited:** ${t.pagesAudited}${t.pagesFailed>0?` (${t.pagesFailed} failed)`:""}`),n.push(`- **Site grade:** ${t.siteGrade}`),n.push(`- **Total unique violations site-wide:** ${t.totalUniqueViolations}`),n.push(`- **Severity breakdown:** ${t.totals.critical} critical · ${t.totals.serious} serious · ${t.totals.moderate} moderate · ${t.totals.minor} minor`),n.push("");const o=t.topViolations.filter(c=>!/-needs-site-crawl$/.test(c.ruleId));if(o.length>0){n.push("**Most-frequent rules across the site** (fixing one of these in a shared component / layout resolves N pages at once):"),n.push("");for(const c of o.slice(0,8)){const d=c.urlsAffected>=Math.ceil(t.pagesAudited/2)?" — **likely a single shared-component fix**":"";n.push(`- \`${c.ruleId}\` — ${c.impact} — found on ${c.urlsAffected} page${c.urlsAffected===1?"":"s"} (${c.totalOccurrences} total instance${c.totalOccurrences===1?"":"s"})${d}`)}n.push("")}const i=t.consistencyFindings??[];if(i.length>0){n.push("## Cross-page consistency findings"),n.push(""),n.push("These are divergences detected by comparing structure across pages (WCAG 3.2.3, 3.2.4, 3.2.6, 3.3.7). Each finding references multiple pages — typically the fix is in a SHARED component (header / nav / footer / form)."),n.push("");for(const c of i){const l=c.aiVerdict?c.aiVerdict.verdict==="uncertain"?" _(AI: uncertain — verify manually)_":` _(AI: confirmed real, confidence ${(c.aiVerdict.confidence*100).toFixed(0)}%)_`:"";n.push(`### ${c.wcagCriterion} ${c.impact} — ${c.description}${l}`),n.push("");for(const d of c.details.split(`
2166
+ `))n.push(d);(r=c.aiVerdict)!=null&&r.reasoning&&(n.push(""),n.push(`AI reasoning: ${c.aiVerdict.reasoning}`)),n.push("")}}else n.push("## Cross-page consistency findings"),n.push(""),n.push("No cross-page divergences detected by the heuristic comparators. Still verify manually that nav order, accessible names of shared components, and help-mechanism position are consistent across pages."),n.push("");return n.push("---"),n.push(""),n.push(`## Per-page findings (${e.length} page${e.length===1?"":"s"})`),n.push(""),n.push("Each page section below is shaped like a single-page audit report — violations to fix + relevant recipes. Fix one page at a time. The cross-page findings above usually point at a SHARED component; fix that once and re-run the audit to see N pages resolve together."),n.push(""),e.forEach((c,l)=>{const d=l+1;if(n.push(`### Page ${d} of ${e.length}: ${c.url}`),n.push(""),c.error){n.push(`> ⚠️ Audit failed for this page: \`${c.error}\``),n.push(""),n.push("---"),n.push("");return}const u=c.results.flatMap(b=>b.violations).filter(b=>!wh.test(b.ruleId));if(u.length===0){n.push("No automated violations detected on this page. Still apply the manual checklist below."),n.push("");const b=a.get(c.componentId)??[];if(b.length>0){n.push("**AI walkthrough verdicts on this page:**");for(const w of b){const k=w.verdict==="pass"?"✓":w.verdict==="fail"?"✗":"?";n.push(`- ${k} WCAG ${w.criterionId} — ${w.verdict.toUpperCase()}`)}n.push("")}n.push("---"),n.push("");return}const h=new Map,m=new Map;for(const b of u){const w=xe(b.ruleId,b.target.selector);h.has(w)||h.set(w,b);const k=m.get(w)??[];k.push(b),m.set(w,k)}const f=Array.from(h.entries()).map(([b,w])=>({v:w,instances:m.get(b)??[w]})).sort((b,w)=>{const k={critical:0,serious:1,moderate:2,minor:3};return(k[b.v.impact]??99)-(k[w.v.impact]??99)}),g={critical:0,serious:0,moderate:0,minor:0};for(const{v:b}of f)g[b.impact]++;n.push(`**${f.length} unique violation${f.length===1?"":"s"}** · severity: ${g.critical} critical · ${g.serious} serious · ${g.moderate} moderate · ${g.minor} minor`),n.push(""),f.forEach(({v:b,instances:w},k)=>{const y=at[b.ruleId];n.push(`#### ${d}.${k+1} \`${b.ruleId}\` — ${b.impact} — WCAG ${b.wcagCriterion} ${b.wcagLevel}`),n.push(""),n.push(`**Description:** ${b.description}`),n.push(""),n.push(`**Selector:** \`${b.target.selector}\``);const{stateCount:s,states:O}=yh(w);s>1?(n.push(""),n.push(`_Failed in ${s} matrix state${s===1?"":"s"}:_ ${O.join(", ")}`)):w.length>1&&(n.push(""),n.push(`_${w.length} element instances on this page (same state)._`)),n.push(""),b.target.failureSummary&&(n.push("**Diagnostic:**"),n.push("```"),n.push(b.target.failureSummary.trim()),n.push("```"),n.push("")),b.target.outerHTML&&(n.push("**Element HTML:**"),n.push("```html"),n.push(b.target.outerHTML.slice(0,500)),n.push("```"),n.push("")),y!=null&&y.summary&&(n.push("**Recipe:**"),n.push(y.summary),y.snippet&&(n.push(""),n.push("```"+(y.snippetLang??"")),n.push(y.snippet),n.push("```")),n.push("")),b.helpUrl&&(n.push(`**More info:** ${b.helpUrl}`),n.push(""))});const p=a.get(c.componentId)??[];if(p.length>0){n.push("**AI walkthrough verdicts on this page:**");for(const b of p){const w=b.verdict==="pass"?"✓":b.verdict==="fail"?"✗":"?";n.push(`- ${w} WCAG ${b.criterionId} — ${b.verdict.toUpperCase()}: ${b.reasoning.slice(0,240)}${b.reasoning.length>240?"…":""}`)}n.push("")}n.push("---"),n.push("")}),n.push("## Combined verification checklist"),n.push(""),n.push("After all per-page fixes:"),n.push(""),n.push("1. **Re-run the site crawl** at the same depth used here."),n.push("2. Verify the **cross-page consistency findings** above all resolve (or document why a flagged divergence is intentional)."),n.push("3. Verify the **top-frequency rules** across pages resolve in bulk (a single shared-component fix should drop the count on multiple pages)."),n.push("4. **Manual WCAG checks** (audio/video transcripts, character key shortcuts, timing controls, etc.) — the per-page sections above only contain automatable findings. The non-automatable WCAG criteria still apply per page."),n.push("5. **Diff review** — every fix should be reviewable in under 10 minutes. One atomic commit per fix."),n.push(""),n.push(`_Generated by wcag-component-auditor v1.0.0.115 — site crawl mode (${e.length} pages)._`),n.join(`
2167
+ `)}const lp=Object.freeze(Object.defineProperty({__proto__:null,registerReportExporterHandlers:mh,toAiPrompt:Vi,toAiPromptForSiteCrawl:Yi,toAiPromptOwner:Bi,toDefenseBundle:Tt,toDeveloperLetter:zi,toEvidenceBundle:ji,toHtmlPrint:xn,toJUnit:Fi,toJson:Pi,toOwnerReport:Hi,toSarif:Ui,toVpat:Gi},Symbol.toStringTag,{value:"Module"})),Wt=J("sw-auto-export");function vh(e,t,a){const n=hr(e,a);let o="audit";try{o=new URL(t).hostname.replace(/^www\./,"").replace(/[^a-z0-9.-]/gi,"-")}catch{}return`wcagcheckr/${o}/${n}`}async function Ah(e,t,a){try{if(e==="html-print")return xn(t,a);if(e==="defense-bundle"){const n=await At().catch(()=>[]);return Tt(t,a,"defense",void 0,n)}if(e==="deposition-packet")return(await Di({results:t,delta:a,manualRuns:void 0,aiSummary:null})).html}catch(n){return Wt.warn(`buildContent failed for format=${e}`,n),null}return null}async function kh(e,t){var a,n,o;if(e.length!==0)try{const i=await dr();if(!i.enabled||await je())return;const r=await Me().catch(()=>"trial");if(!So(r,"autoExportAuditReports"))return;const c=((a=e[0])==null?void 0:a.pageUrl)??((n=e[0])==null?void 0:n.scope)??"",l=((o=e[0])==null?void 0:o.componentId)??null;for(const d of i.formats)try{const u=await Ah(d,e,t);if(!u||u.length<200)continue;const h=vh(d,c,l),f=`data:${d==="ai-prompt"?"text/markdown":"text/html"};charset=utf-8,${encodeURIComponent(u)}`,g=await chrome.downloads.download({url:f,filename:h,saveAs:!1,conflictAction:"uniquify"});Wt.info(`scheduled auto-export started (id=${g}, file=${h})`),c&&await ur({url:c,exportedAt:new Date().toISOString(),format:d,filename:h})}catch(u){Wt.warn(`format ${d} failed in scheduled auto-export`,u)}}catch(i){Wt.warn("autoExportFromScheduledRun failed (audit itself still succeeded)",i)}}const De=J("scheduled-audit-runner"),Xt="scheduled-audit:",Io={hourly:60,"every-4-hours":240,daily:1440,weekly:10080};function Sn(e){return`${Xt}${e}`}function dp(e){return e.startsWith(Xt)?e.slice(Xt.length):null}async function Ih(e){const t=Sn(e.id);await chrome.alarms.create(t,{periodInMinutes:Io[e.cadence],delayInMinutes:Io[e.cadence]})}async function up(e){await chrome.alarms.clear(Sn(e))}async function hp(){try{const{listScheduledAudits:e}=await an(async()=>{const{listScheduledAudits:o}=await Promise.resolve().then(()=>Nr);return{listScheduledAudits:o}},void 0),t=await e(),a=await chrome.alarms.getAll(),n=new Set(t.filter(o=>o.enabled).map(o=>Sn(o.id)));for(const o of a)o.name.startsWith(Xt)&&!n.has(o.name)&&await chrome.alarms.clear(o.name);for(const o of t)o.enabled&&await Ih(o);De.info(`reconciled ${t.length} scheduled-audit alarms`)}catch(e){De.warn("failed to reconcile scheduled alarms",e)}}async function pp(e){var o;const t=Date.now(),a=await No(e);if(!a){De.warn(`scheduled audit ${e} not found — alarm fired for deleted schedule`);return}if(!a.enabled){De.debug(`scheduled audit ${e} is disabled — skipping`);return}if(await je()){await Ut(a,{result:"failed",error:tt,durationMs:Date.now()-t,totalViolations:0,newVsBaselineViolations:0});return}await rn({...a,lastResult:"running",lastRunAt:new Date().toISOString()});let n;try{if(n=(await chrome.tabs.create({url:a.url,active:!1})).id,!n)throw new Error("chrome.tabs.create returned no id");await xh(n,3e4);const c=await rt("html",{isCanceled:()=>!1},0,void 0,{asSubroutine:!0,tabIdOverride:n});if(!c.ok){await Ut(a,{result:"failed",error:c.error??"audit failed",durationMs:Date.now()-t,totalViolations:0,newVsBaselineViolations:0});return}const l=c.results.reduce((m,f)=>m+f.violations.length,0),d=(o=c.results[0])==null?void 0:o.delta,u=(d==null?void 0:d.newCount)??0,h=new Date().toISOString();if(await Ut(a,{result:"ok",durationMs:Date.now()-t,totalViolations:l,newVsBaselineViolations:u}),kh(c.results,d),u>0)try{const m=await gu({scheduleId:a.id,auditedUrl:a.url,ranAt:h,results:c.results});De.debug(`alert outcome for ${a.id}`,m)}catch(m){De.warn(`alert fire threw for ${a.id}`,m)}}catch(i){const r=i instanceof Error?i.message:String(i);De.warn(`scheduled audit ${e} failed:`,r),await Ut(a,{result:"failed",error:r,durationMs:Date.now()-t,totalViolations:0,newVsBaselineViolations:0})}finally{if(n!==void 0)try{await chrome.tabs.remove(n)}catch{}}}async function Ut(e,t){const a=new Date().toISOString();await Lo({scheduleId:e.id,ranAt:a,durationMs:t.durationMs,totalViolations:t.totalViolations,newVsBaselineViolations:t.newVsBaselineViolations,error:t.error}),await rn({...e,lastRunAt:a,lastResult:t.result,lastError:t.error,lastTotalViolations:t.totalViolations,lastNewViolationCount:t.newVsBaselineViolations}),await Po(e.id);try{await ku()}catch(n){De.warn("status badge refresh failed",n)}}function xh(e,t){return new Promise(a=>{const n=setTimeout(()=>{chrome.tabs.onUpdated.removeListener(o),a()},t),o=(i,r)=>{i===e&&r.status==="complete"&&(clearTimeout(n),chrome.tabs.onUpdated.removeListener(o),setTimeout(a,500))};chrome.tabs.onUpdated.addListener(o)})}export{Ne as $,Gh as A,Fs as B,Ee as C,hs as D,Mh as E,Bh as F,Qr as G,zh as H,Hh as I,Kh as J,_t as K,Yh as L,Jh as M,Zo as N,Nh as O,Jo as P,At as Q,nc as R,zo as S,ca as T,nt as U,Se as V,Us as W,ri as X,St as Y,Fh as Z,Ha as _,pn as a,Ls as a0,_h as a1,Lh as a2,fn as a3,Le as a4,pu as a5,fu as a6,Do as a7,sp as a8,rn as a9,pp as aA,ip as aB,mh as aC,cp as aD,Xh as aE,rt as aF,Yd as aG,Kd as aH,ep as aI,su as aJ,Vh as aK,Hs as aL,tl as aM,Ci as aN,Ya as aO,Ft as aP,Eh as aQ,Zr as aR,Dh as aS,rp as aT,lp as aU,Ih as aa,ku as ab,Io as ac,up as ad,Rr as ae,Dr as af,Mr as ag,Wh as ah,Me as ai,ce as aj,Ce as ak,ed as al,Jl as am,Xl as an,Bl as ao,jl as ap,Gl as aq,op as ar,tp as as,np as at,Rh as au,ap as av,qh as aw,Ph as ax,hp as ay,dp as az,na as b,si as c,gc as d,gs as e,ms as f,Ko as g,Lr as h,Wa as i,jh as j,Uh as k,un as l,Fe as m,Xr as n,aa as o,Uo as p,Oh as q,Zh as r,Qh as s,et as t,Go as u,jo as v,xe as w,Va as x,cs as y,js as z};